UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

109 lines • 4.59 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { descendantPath } from "./paths.js"; import { FileType } from "../../../types.js"; import { eacces, eloop, enoent, enotdir, isMissingPath } from "../../../errors/fs.js"; // The destination verdicts GNU meets at the destination's own stat, before // any create or rename: a plain file in its chain, a link loop in it, or a // refused stat, a store's or a policy's. cp and mv both word them `cannot stat 'DST'` // (coreutils 9.7). Mirrors Python's STAT_REFUSALS. export const STAT_REFUSALS = new Set([ 'ENOTDIR', 'ELOOP', 'EACCES', ]); import { rstripSlash } from "../../../utils/slash.js"; import { respelled } from "../../../utils/key_prefix.js"; import { parent } from "../../../utils/path.js"; export function backendKeyDefault(path) { return rstripSlash(path.virtual); } function childPath(parent, name) { return descendantPath(parent, `${rstripSlash(parent.virtual)}/${name}`); } // Multiple sources require the directory form, and GNU distinguishes why it // is unusable: an absent target is "No such file or directory"; an existing // non-directory is "Not a directory", and so is a target that can never // exist because a plain file stands in its chain or behind its slash // (`cp a b reg/x`, `cp a b reg/`), which the destination probe reports as // its strerror (identical wording in cp and mv). // // A source typed as `..` lands as `dotdot`: `..`, the destination's parent, // for mv; `.` for cp, which never copies into `dir/..` (coreutils 9.7). export function copyTargets(sources, dst, dstIsDir, dstExists = true, dstErr = null, dotdot = '..') { if (sources.length > 1 && !dstIsDir) { if (dstErr === 'ELOOP') throw eloop(`target '${dst.rawPath}'`); if (dstErr === 'EACCES') throw eacces(`target '${dst.rawPath}'`); if (!dstExists && dstErr !== 'ENOTDIR') throw enoent(`target '${dst.rawPath}'`); throw enotdir(`target '${dst.rawPath}'`); } if (!dstIsDir) { const first = sources[0]; return first === undefined ? [] : [[first, dst]]; } return sources.map((src) => [src, landingPath(dst, src, dotdot)]); } // The operand's last component as typed, trailing slashes dropped. export function typedName(src) { return rstripSlash(src.rawPath).split('/').pop() ?? ''; } /** * Where a source lands inside a directory destination. GNU appends the * operand's last component as typed, so a link the router followed still * lands under its own name (`cp al dir` makes `dir/al`, not `dir/a.txt`), and * `s/.` lands at `dir/.`, the directory itself: `cp -r s/. dir` merges s's * contents into dir. An operand with no name of its own (the root) keeps the * name of what it resolves to. `dotdot` is what a `..` operand lands as (see * copyTargets). Mirrors Python's landing_path. */ export function landingPath(dst, src, dotdot = '..') { let typed = typedName(src); if (typed === '..') typed = dotdot; if (typed === '') typed = rstripSlash(src.virtual).split('/').pop() ?? ''; if (typed !== '.' && typed !== '..') return childPath(dst, typed); const base = dst.rawPath; const shown = base === '' || base.endsWith('/') ? `${base}${typed}` : `${base}/${typed}`; if (typed === '.') return respelled(dst, shown); return respelled(descendantPath(dst, parent(rstripSlash(dst.virtual))), shown); } export async function pathExists(stat, path) { try { await stat(path); } catch (err) { if (isMissingPath(err)) return false; throw err; } return true; } export async function isDirectory(stat, path) { try { const info = await stat(path); return info.type === FileType.DIRECTORY; } catch (err) { if (isMissingPath(err)) return false; throw err; } } //# sourceMappingURL=copy.js.map