@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
115 lines • 5.66 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 { compareCodePoints } from "../../../../utils/sort.js";
import { FlagView } from "../../../spec/flag_view.js";
import { AmbiguousArgumentError, EmptyPathspecError, GitError, InvalidRevisionNameError, UsageError, } from "./errors.js";
import { readIndex } from "./index_file.js";
import { pathspecPatterns, pathspecSelects, repoRelative } from "./pathspec.js";
import { quotePath, relativePath } from "./render.js";
import { configBool } from "./repo.js";
import { entryPath, printed } from "./rows.js";
import { placeNamed } from "./seen.js";
import { opened } from "./session.js";
import { GITLINK_MODE } from "./constants.js";
import { listedTree, resolveTree, TREE_MODE } from "./tree.js";
import { checkSwitches, startPoint, verbUsage } from "./util.js";
/**
* List index paths, including conflict stages when requested.
*
* Without a pathspec the listing is the start directory's subtree; with one
* it is whatever the pathspec names, anywhere in the tree, spelled relative to
* the start directory (`ls-files ..` from a subdirectory prints
* `../README.md`).
*/
export async function lsFiles(_accessor, paths, texts, ctx) {
const fl = new FlagView(ctx.flags);
checkSwitches(ctx, texts);
const repo = await opened(ctx, fl);
const fully = await configBool(repo, 'core.quotepath', true);
const state = await readIndex(repo, repo.vfs);
const start = startPoint(fl).virtual;
const prefix = repoRelative(repo.location, start, '.');
const patterns = pathspecPatterns(repo.location, start, texts);
const rows = [...state.entries.values()];
for (const conflict of state.conflicts.values()) {
for (const entry of [conflict.ancestor, conflict.this, conflict.other])
if (entry)
rows.push(entry);
}
rows.sort((a, b) => compareCodePoints(a.path, b.path) || a.stage - b.stage);
const nul = fl.asBool('z');
const out = [];
for (const entry of rows) {
if (!pathspecSelects(entry.path, patterns.length ? patterns : [prefix]))
continue;
const relative = relativePath(entry.path, prefix);
const label = nul ? relative : quotePath(relative, false, fully);
const metadata = fl.asBool('stage')
? `${entry.mode.toString(8).padStart(6, '0')} ${entry.oid} ${String(entry.stage)}\t`
: '';
const text = metadata + label + (nul ? '\0' : '\n');
out.push({ path: entryPath(repo.location, entry.path), text });
}
return [null, new IOResult({ rows: { items: out, render: printed } })];
}
/** List tree metadata without reading file contents. */
export async function lsTree(_accessor, _paths, texts, ctx) {
const fl = new FlagView(ctx.flags);
checkSwitches(ctx, texts);
const [name, ...paths] = texts;
if (name === undefined)
throw new UsageError('', verbUsage(ctx));
const repo = await opened(ctx, fl);
let tree;
try {
tree = await resolveTree(repo, name);
}
catch (err) {
if (err instanceof AmbiguousArgumentError || err instanceof InvalidRevisionNameError)
throw new GitError(`Not a valid object name ${name}`);
throw err;
}
// Each row stands where the tree does, so the hides it is filtered by are
// the ones at that place, not at the root.
const base = await placeNamed(repo, name, tree);
const fullTree = fl.asBool('full_tree');
const start = fullTree ? repo.location.worktree.virtual : startPoint(fl).virtual;
const prefix = repoRelative(repo.location, start, '.');
let patterns = paths.map((path) => {
if (path === '')
throw new EmptyPathspecError();
const relative = repoRelative(repo.location, start, path);
const directory = /(?:^|\/)(?:\.\.?|)$/.test(path);
return relative + (relative && directory ? '/' : '');
});
if (!patterns.length && prefix)
patterns = [prefix + '/'];
const rows = await listedTree(repo, tree, patterns, fl.asBool('r'), fl.asBool('t'), fl.asBool('d'));
const fully = await configBool(repo, 'core.quotepath', true);
const nul = fl.asBool('z');
const names = fl.asBool('name_only') || fl.asBool('name_status');
const fullName = fullTree || fl.asBool('full_name');
const terminator = nul ? '\0' : '\n';
const out = [];
for (const [path, mode, oid] of rows) {
const relative = fullName ? path : relativePath(path, prefix);
const label = nul ? relative : quotePath(relative, false, fully);
const kind = mode === TREE_MODE ? 'tree' : mode === GITLINK_MODE ? 'commit' : 'blob';
const text = (names ? '' : `${mode} ${kind} ${oid}\t`) + label + terminator;
out.push({ path: entryPath(repo.location, base ? `${base}/${path}` : path), text });
}
return [null, new IOResult({ rows: { items: out, render: printed } })];
}
//# sourceMappingURL=ls_files.js.map