UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

162 lines • 7.9 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 { OPERAND_CONCURRENCY } from "../../constants.js"; import { parseFindExpression } from "../../../../rs/find.js"; import { DISPATCH_BUILDERS } from "./constants.js"; import { mountStarts, reached } from "./scopes.js"; import { runDispatch } from "../../generic_bind/dispatch.js"; import { specOf } from "../../../spec/builtins.js"; import { FlagView } from "../../../spec/flag_view.js"; import { boundedMap } from "../../../../concurrency/limiter.js"; import { IOResult, materialize } from "../../../../io/types.js"; import { PathSpec } from "../../../../types.js"; import { respellOne } from "../../../../utils/path.js"; import { compareCodePoints } from "../../../../utils/sort.js"; import { mountView } from "../../utils/links.js"; // The tests whose verdict on one entry decides what the walk reads below it, // or that read a listing a mount boundary splits: no mount's own find can // answer them for the whole tree, so such a line keeps the one walk. export const WALK_WIDE = new Set(['-prune', '-empty', '-xdev', '-mount']); /** * The expression for a start point `depth` levels below the operand. * * Its depth limits count from that start point; `alone` matches the start * point only, not what lies below. Null when the limits leave it nothing to * print. */ export function shifted(expr, depth, alone = false) { const { maxDepth, minDepth } = expr; if (maxDepth !== null && depth > maxDepth) return null; if (alone && minDepth !== null && depth < minDepth) return null; const top = alone ? 0 : maxDepth === null ? null : maxDepth - depth; const bottom = minDepth === null ? null : Math.max(0, minDepth - depth); return { ...expr, maxDepth: top, minDepth: bottom }; } /** * The directories between an operand and the mounts below it. * * No mount's own find can answer for them: the parent's backend may hold none * of them (a mount at `/usr/bin` implies `/usr`), so each is matched alone, * through the dispatcher. */ export function joints(path, starts) { const base = path.virtual.replace(/\/+$/, ''); const roots = new Set(starts.slice(1).map((s) => s.virtual)); const between = new Set(); for (const root of roots) { const parts = root .slice(base.length) .replace(/^\/+|\/+$/g, '') .split('/') .slice(0, -1); for (let end = 1; end <= parts.length; end++) { between.add(`${base}/${parts.slice(0, end).join('/')}`); } } return [...between] .filter((virtual) => !roots.has(virtual)) .sort(compareCodePoints) .map((virtual) => new PathSpec({ virtual, directory: virtual, vfsPath: virtual.replace(/^\/+|\/+$/g, ''), rawPath: respellOne(virtual, path.virtual, path.rawPath), })); } /** * Compose find over operands holding mounts from each mount's own find. * * Every operand, and every visible mount below it, runs the find its mount * registered, a few at a time, with the depth limits counted from its own * start point; the directories between them (`joints`) are matched once each * through the dispatcher. Each run answers for the rows its mount owns (the * parent's shadowed keys stay out), and the rows merge in the walk's path * order, so the actions still run once over every mount's `records`; a * mount whose find fails adds its diagnostic and status, as GNU's walk past * an unreadable directory does, and the mounts below it count only where the * walk could still reach them (`reached`). An expression that looks across a * boundary (`WALK_WIDE`, `-L`), or a find that succeeds without structured * rows, keeps the one dispatcher walk. */ export async function runFind(paths, texts, bag, find, dispatch, runSingle, cwd, vfs, stdin = null, signal, // The line's operand resolution, which the one walk's globs take. follow) { const builder = DISPATCH_BUILDERS.get('find'); if (builder === undefined) throw new Error('No dispatch builder for find'); const expr = find ?? parseFindExpression(texts); const walk = () => runDispatch(builder, paths, texts, bag, dispatch, cwd, vfs, stdin, signal, [], follow, undefined, undefined, expr); const mounts = mountView(vfs); if (mounts === undefined || expr.predicates.some((word) => WALK_WIDE.has(word)) || new FlagView(bag, specOf('find')).asBool('L')) return walk(); const plan = []; paths.forEach((path, index) => { const base = path.virtual.replace(/\/+$/, ''); const starts = mountStarts(path, vfs); const steps = [ ...starts.map((start) => [start, false]), ...joints(path, starts).map((joint) => [joint, true]), ]; for (const [start, alone] of steps) { const depth = start === path ? 0 : (start.virtual.slice(base.length).match(/\//g) ?? []).length; const limited = shifted(expr, depth, alone); if (limited !== null) plan.push({ index, start, alone, limited }); } }); const ios = await boundedMap(plan, async (step) => { const [out, io] = step.alone ? await runDispatch(builder, [step.start], texts, bag, dispatch, cwd, vfs, stdin, signal, [], follow, undefined, undefined, step.limited) : await runSingle('find', [step.start], [...texts], bag, { find: step.limited, ...(signal === undefined ? {} : { signal }), }); await materialize(out); return io; }, OPERAND_CONCURRENCY); // A run that failed found no rows and says why; one that succeeded without // structured rows is a find this line cannot compose. if (ios.some((io) => io.records === null && io.exitCode === 0)) return walk(); const between = paths.map((_, index) => new Set(plan.filter((s) => s.index === index && s.alone).map((s) => s.start.virtual))); const kept = await reached(paths, plan.map((step) => [step.index, step.start]), ios.map((io) => io.exitCode !== 0), dispatch); const done = plan.flatMap((step, i) => { const io = ios[i]; return kept[i] === true && io !== undefined ? [{ step, io }] : []; }); const runs = paths.map(() => []); let merged = new IOResult(); for (const { step, io } of done) { const owner = mounts.rootOf(step.start.virtual); for (const { path: row } of io.records ?? []) { const kept = step.alone ? row.virtual === step.start.virtual : mounts.rootOf(row.virtual) === owner && !(between[step.index]?.has(row.virtual) ?? false); if (kept) runs[step.index]?.push(row); } merged = await merged.merge(io); } for (const rows of runs) rows.sort((a, b) => compareCodePoints(a.virtual, b.virtual)); merged.exitCode = Math.max(0, ...done.map(({ io }) => io.exitCode)); merged.records = runs.flatMap((rows, run) => rows.map((path) => ({ path, run }))); const body = new TextEncoder().encode(merged.records.map((record) => `${record.path.rawPath}\n`).join('')); return [body, merged]; } //# sourceMappingURL=find.js.map