@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
189 lines • 7.77 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 { fsStrerror, walkRefusal } from "../../../../errors/fs.js";
import { IOResult } from "../../../../io/types.js";
import { StreamRefusal } from "../../../../rs/errors.js";
import { refused } from "../../../../rs/scan.js";
import { openStreamSync } from "../../../../rs/stream.js";
import { byteView } from "../../../../shell/bytes.js";
import { Follow, LineFacts, PathSpec } from "../../../../types.js";
import { mountKey } from "../../../../utils/key_prefix.js";
import { rstripSlash } from "../../../../utils/slash.js";
import { specOf } from "../../../spec/builtins.js";
import { FlagView } from "../../../spec/flag_view.js";
import { linksOf, mountRoot, mountView } from "../../utils/links.js";
import { formatOptionalRecords } from "../../utils/output.js";
import { hostedStream, StreamHost } from "../../utils/stream_host.js";
import { extractDest } from "../archive/extract.js";
import { checkDirectories, planCreate } from "./create.js";
function makePathSpec(virtual, prefix, operand) {
return new PathSpec({
virtual,
directory: virtual,
vfsPath: mountKey(virtual, prefix),
resolved: true,
rawPath: operand?.rawPath ?? virtual,
dotted: operand?.dotted ?? null,
walkError: operand?.walkError ?? null,
});
}
const MODES = ['create', 'extract', 'list'];
const CODECS = ['gzip', 'bzip2', 'xz'];
/** The options the tar engine reads, each text as a byte view. Mirrors
* Python's `_typed`. */
function typedConfig(typed) {
return typed.map(([name, text]) => [name, byteView(text)]);
}
/**
* tar's flags, refused by the tar engine as argp refuses them: the flags, or
* tar's refusal, exit 2. Mirrors Python's parse_flags.
*/
export function parseFlags(bag) {
const fl = new FlagView(bag, specOf('tar'));
const typed = fl.texts(...MODES, 'strip_components', 'file');
try {
openStreamSync('tar', { typed: typedConfig(typed) }).close();
}
catch (err) {
if (!(err instanceof StreamRefusal))
throw err;
return refused(err);
}
return {
create: fl.asBool('create'),
extract: fl.asBool('extract'),
list: fl.asBool('list'),
codec: CODECS.find((codec) => fl.asBool(codec)) ?? null,
verbose: fl.asBool('verbose'),
dereference: fl.asBool('dereference'),
toStdout: fl.asBool('to_stdout'),
archive: fl.asPath('file') ?? null,
directories: fl.asPaths('directory'),
typed,
exclude: fl.asStr('exclude') ?? null,
oneFileSystem: fl.asBool('one_file_system'),
};
}
/**
* Plan what the host decides and run mirage-rs's tar over it: -c walks its
* operands and checks every -C here, then the engine encodes; -t and -x
* check every -C here, and the engine reads the archive and writes what it
* extracts where the last -C or the working directory says, on whichever
* mount owns it. Mirrors Python's tar.
*/
export async function tarGeneric(flags, paths, texts, ctx,
// What tar needs from the mount it runs on. `stat` and `walk` are what
// make a directory operand archivable at all; `isDir` answers on two
// channels so a prefix-store directory (no object of its own) is not
// mistaken for an absent one.
deps, relay = false) {
const prefix = relay ? '' : mountRoot(ctx);
// With no -f the archive is standard input or output, which is GNU tar's
// compiled-in default (no TAPE in the environment).
const archive = flags.archive !== null
? makePathSpec(flags.archive.virtual, prefix, flags.archive)
: new PathSpec({
virtual: '/dev/stdin',
directory: '/dev/',
vfsPath: 'dev/stdin',
resolved: true,
rawPath: '-',
});
const directories = flags.directories.map((dir) => makePathSpec(dir.virtual, prefix, dir));
const config = {
archive: byteView(archive.rawPath),
codec: flags.codec,
verbose: flags.verbose,
};
let host;
if (flags.create) {
const plan = await planCreate(paths, {
archive,
exclude: flags.exclude,
dereference: flags.dereference,
stat: deps.stat,
walk: deps.walk,
isDir: deps.isDir,
directories,
links: linksOf(ctx) ?? null,
// Every mount nested under an operand bounds the scan, a relayed run's too.
mounts: mountView(ctx.vfs) ?? null,
oneFileSystem: flags.oneFileSystem,
});
if (!plan.write) {
const stderr = formatOptionalRecords(plan.notices);
return [
null,
new IOResult({ exitCode: plan.exitCode, ...(stderr !== null ? { stderr } : {}) }),
];
}
if (archive.walkError !== null) {
config.unopened = byteView(fsStrerror(walkRefusal(archive)) ?? '');
}
const reads = [archive];
const members = [];
for (const member of plan.members) {
const item = {
name: byteView(member.name),
kind: member.kind,
target: byteView(member.target),
spelled: byteView(member.spelled ?? ''),
size: member.size ?? null,
};
if (member.path !== null) {
reads.push(member.path);
item.path = byteView(member.path.rawPath);
item.operand = reads.length;
}
members.push(item);
}
Object.assign(config, {
mode: 'create',
output: byteView(archive.virtual),
members,
notices: plan.notices.map((notice) => byteView(notice)),
status: plan.exitCode,
});
host = new StreamHost(reads, deps.stream, ctx, false);
}
else {
const refusal = await checkDirectories(directories, deps.isDir, deps.stat);
Object.assign(config, {
mode: flags.list ? 'list' : 'extract',
probe: archive.walkError === null,
selectors: texts.map((selector) => byteView(selector)),
typed: typedConfig(flags.typed),
to_stdout: flags.toStdout,
base: byteView(rstripSlash(extractDest(directories.at(-1) ?? null, ctx.cwd.virtual))),
refusal: refusal.map((line) => byteView(line)),
});
host = new StreamHost([archive], deps.stream, ctx, false);
}
const io = new IOResult();
return [hostedStream('tar', config, host, io, ctx.signal), io];
}
/**
* A tar line hands a link operand to its planner as given, which reads `-h`
* itself, reads below what it archives under `-c`, and writes to create or
* extract, not to list or extract to stdout. Mirrors Python's `facts`.
*/
export function facts(fl, _operands) {
const create = fl.asBool('create');
return new LineFacts({
follows: Follow.NONE,
readsSubtree: create,
writes: create || (fl.asBool('extract') && !fl.asBool('to_stdout')),
});
}
//# sourceMappingURL=tar.js.map