UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

101 lines • 4.84 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 { PathSpec } from "../../../types.js"; import { respelled } from "../../../utils/key_prefix.js"; import { rstripSlash } from "../../../utils/slash.js"; import { wordRefusalError } from "../../spec/usage.js"; import { matchWord } from "../../spec/word_choice.js"; // GNU's version-control words, the aliases of one value on one line as GNU // lists them back. const VERSION_CONTROL_WORDS = [ ['none', 'off'], ['simple', 'never'], ['existing', 'nil'], ['numbered', 't'], ]; const NUMBERED_SUFFIX = /^\.~(\d+)~$/; // Resolve -b/--backup[=CONTROL]/-S into a backup control. Deliberate // divergence from GNU: the VERSION_CONTROL and SIMPLE_BACKUP_SUFFIX // environment variables are not consulted; the default control is GNU's // env-less default, 'existing'. A bare -S SUFFIX enables backups on its // own, matching GNU 9.7. Returns the canonical control // ('none'/'simple'/'existing'/'numbered'), or null when backups are not // requested. export function backupControl(cmdName, value, suffix) { const enabled = value !== undefined && value !== false; if (!enabled && suffix === null) return null; // An EMPTY control is the default, not a refusal: `cp --backup=` is // `cp --backup` (measured on coreutils 9.4: exit 0, and it writes the // `existing` backup). This is the one word-choice slot in the repo where // the empty word is neither invalid nor ambiguous. if (typeof value === 'string' && value !== '') { // The canonical word of each class IS its control, so a match answers // the control directly: `--backup=e` is `existing`, // while `--backup=n` spans none/never/nil/numbered and is ambiguous // (both measured on coreutils 9.4). const match = matchWord(value, VERSION_CONTROL_WORDS); if (!match.matched) { throw wordRefusalError(cmdName, 'backup type', value, VERSION_CONTROL_WORDS, 1, match.kind); } return match.word; } return 'existing'; } // A path next to `path` whose name carries an appended suffix (e.g. '~'). export function siblingPath(path, appended) { const virtual = rstripSlash(path.virtual) + appended; return respelled(PathSpec.fromStrPath(virtual), (rstripSlash(path.rawPath) || path.rawPath) + appended); } // The directory containing `path`. Mirrors Python's parent_path. export function parentPath(path) { const stripped = rstripSlash(path.virtual); return PathSpec.fromStrPath(stripped.slice(0, stripped.lastIndexOf('/')) || '/'); } // Existing numbered-backup versions (`name.~N~`) next to a target. A missing // lister reads as no numbered backups; a failing listing propagates, because // reading it as "none" would pick `.~1~` (or fall back to the simple suffix) // and silently overwrite backup history that may exist. async function numberedVersions(readdir, target) { if (readdir === undefined) return []; const base = rstripSlash(target.virtual).split('/').pop() ?? ''; const children = await readdir(parentPath(target)); const versions = []; for (const child of children) { const name = rstripSlash(child).split('/').pop() ?? ''; if (!name.startsWith(base)) continue; const match = NUMBERED_SUFFIX.exec(name.slice(base.length)); if (match !== null) versions.push(Number(match[1])); } return versions; } // Pick the backup path for a target about to be overwritten. GNU naming: // 'simple' appends the suffix, 'numbered' appends `.~N~` one past the // highest existing version, and 'existing' stays numbered while any // numbered backup exists and is simple otherwise. export async function backupTarget(readdir, target, control, suffix) { if (control === 'none') return null; if (control === 'simple') return siblingPath(target, suffix); const versions = await numberedVersions(readdir, target); if (control === 'numbered' || versions.length > 0) { return siblingPath(target, `.~${String(Math.max(0, ...versions) + 1)}~`); } return siblingPath(target, suffix); } //# sourceMappingURL=backup.js.map