@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
73 lines • 4.06 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 { gnuBasename } from "../../../../../utils/path.js";
import { dispatchedCall, dispatchedReadTree } from "../../../generic_bind/files.js";
import { specOf } from "../../../../spec/builtins.js";
import { FlagView } from "../../../../spec/flag_view.js";
import { linkLocation, linkView, mountView } from "../../../utils/links.js";
import { treeWalk } from "../../../utils/tree.js";
import { lsGeneric } from "../../ls.js";
import { crossOpts, flatScopes, readdirOp } from "../utils.js";
// lstat one entry on the mount that owns it, named by its own path.
//
// readdir supplies the parent's names and stat supplies the target's facts,
// and relaying lets the two land on different mounts: statting a nested
// mount's root reaches a backend that calls its own root `/`, not the name
// the parent listing knows it by. The single-mount path never sees this
// because parent and child are one backend. The name has to win from the
// parent's side, or `-R` descends into `parent + "/"`, which is the parent
// again. Mirrors Python's relayed_stat.
function relayedStat(dispatch) {
return async (p) => {
const info = (await dispatchedCall(dispatch, 'stat', p, [], { nofollow: true }));
const name = gnuBasename(p.virtual);
if (name === '' || name === '/' || info.name === name)
return info;
return info.with({ name });
};
}
// List operands spanning mounts through the shared generic ls. ls relays
// rather than fans out because its layout is decided across all operands at
// once: GNU prints non-directory operands first as one globally sorted
// block, then names each directory in sorted order, and heads them only
// when the line carried more than one operand. A per-operand run sees one
// operand and cannot know any of that, so the generic has to see the whole
// line -- which it can, because readdir and stat route per path.
//
// `vfs` is the session's files, whose links a listing marks. The
// dispatcher's stat carries the attribute overlay and its listing the mounts
// below, and a run outside a mount has no boundary to stop at, so the relay
// descends a nested mount itself, as nothing runs behind a relay to
// contribute that group the way the fan-out does for a single-mount run.
export async function runLs(scopes, flagKwargs, dispatch, vfs,
// The session view, for the profile the group column renders.
sessionView) {
const ctx = {
...crossOpts(flagKwargs),
...(vfs !== undefined ? { vfs } : {}),
...(sessionView !== undefined ? { session: sessionView } : {}),
};
// The dispatcher's listing names the links among the entries. Mirrors
// Python's relayed_readdir. Only -R walks; a plain listing reads one
// directory.
const recursive = new FlagView(flagKwargs, specOf('ls')).asBool('recursive');
const links = linkView(vfs);
const [readdir, stat] = treeWalk(recursive && vfs !== undefined
? dispatchedReadTree(dispatch, mountView(vfs), links === undefined ? null : (virtual) => linkLocation(links, virtual), true)
: undefined, readdirOp(dispatch), relayedStat(dispatch), true, ctx.signal);
const result = await lsGeneric(flatScopes(scopes), ctx, readdir, stat, null);
return result ?? [null, new IOResult()];
}
//# sourceMappingURL=ls.js.map