@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
103 lines • 4.07 kB
JavaScript
// ========= 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 { IOResult } from "../../../io/types.js";
import { FileType } from "../../../types.js";
import { isEnoent, isFsError } from "../../../errors/fs.js";
import { missingOperandError } from "../../spec/usage.js";
import { descendantPath } from "../utils/paths.js";
// rm's answer to a line with no operand, in GNU's words: nothing at all under
// -f, and a missing-operand usage error otherwise (coreutils 9.7). Mirrors
// Python's rm_without_operands.
export function rmWithoutOperands(force) {
if (force)
return [null, new IOResult()];
throw missingOperandError('rm', null);
}
/**
* Remove a directory tree entry by entry, as GNU `rm -r` does.
*
* For a tree whose one-call removal the dispatcher declined, so each removal
* is judged on its own. An entry that cannot be removed, or a directory that
* cannot be opened, is a failure, and the directories above it stay without a
* line of their own, since they are not empty (coreutils 9.7). The links a
* directory holds go with it; a hidden one is left to the directory's own
* removal, which takes what the session cannot see. A mount below is never
* entered, as GNU's `--one-file-system` does, and the directories holding one
* stay. Under `-f` an entry gone before its removal is no failure. Mirrors
* Python's `remove_tree`.
*/
export async function removeTree(root, ops) {
const roots = new Set(ops.mounts?.descendants(root.virtual) ?? []);
const removed = [];
const failures = [];
const remove = async (path, isDir) => {
let names;
try {
if (!isDir) {
await ops.unlink(path);
removed.push({ path: path.virtual, isDir: false });
return true;
}
names = await ops.readdir(path);
}
catch (err) {
if (!isFsError(err))
throw err;
if (ops.force && isEnoent(err))
return true;
failures.push([path, err]);
return false;
}
const base = path.virtual.replace(/\/+$/, '');
let cleared = true;
for (const name of names) {
const child = descendantPath(root, name.replace(/\/+$/, ''));
if (roots.has(child.virtual))
continue;
let info;
try {
info = await ops.stat(child);
}
catch (err) {
if (!isFsError(err))
throw err;
if (err.code === 'ENOENT')
continue;
failures.push([child, err]);
cleared = false;
continue;
}
const gone = await remove(child, info.type === FileType.DIRECTORY);
cleared = cleared && gone;
}
if (!cleared || [...roots].some((r) => r.startsWith(`${base}/`)))
return false;
try {
await ops.rmdir(path);
}
catch (err) {
if (!isFsError(err))
throw err;
if (ops.force && isEnoent(err))
return true;
failures.push([path, err]);
return false;
}
removed.push({ path: path.virtual, isDir: true });
return true;
};
await remove(root, true);
return { removed, failures };
}
//# sourceMappingURL=rm_cmd.js.map