@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
104 lines • 4.51 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 } from "../../../../errors/fs.js";
import { CallStack } from "../../../../shell/call_stack.js";
import { ExitSignal, ReturnSignal } from "../../../../shell/errors.js";
import { wordText } from "../../../../types.js";
import { positionalParams, setPositionalParams } from "../../../session/state.js";
import { ExecutionNode } from "../../../types.js";
import { ended, returning } from "../../control.js";
import { scopePath } from "../scope.js";
import { builtinUsage } from "../shared.js";
import { readScriptText, scriptError } from "./script.js";
/**
* Read a script file and execute it in the calling shell, `name` (`source`
* or `.`) as typed. A sourced file is the caller, so whatever it sets stays
* set; only the positional parameters come back, which `args` replaces
* while it runs. It runs in a frame of its own, which `return` ends,
* `FUNCNAME` names `source` and the RETURN action runs in as it returns; a
* `break` in it ends a caller's loop. An empty name fails as a missing file
* does, and bash blames a file it cannot read on itself, not the builtin.
*/
export async function handleSource(dispatch, executeFn, path, session, args = [], stdin = null, callStack = null, sink, name = 'source') {
const raw = scopePath(path);
if (wordText(path) === '') {
return scriptError('bash', ': No such file or directory', 1, 'source ');
}
let script;
try {
script = await readScriptText(dispatch, raw, session.cwd);
}
catch (err) {
const strerror = fsStrerror(err);
if (strerror === null)
throw err;
if (err.code === 'EISDIR') {
return scriptError(`bash: ${name}`, `${raw}: is a directory`, 1, `source ${raw}`);
}
return scriptError('bash', `${raw}: ${strerror}`, 1, `source ${raw}`);
}
const cs = callStack ?? new CallStack(session.shellNesting);
cs.admit(name);
cs.push(args.length > 0 ? args : [...positionalParams(session, cs)], 'source', true);
const outerNames = session.functionNames;
if (outerNames !== null)
session.functionNames = cs.functionNames();
let io;
let stdout;
try {
try {
io = await executeFn(script, {
sessionId: session.sessionId,
stdin,
callStack: cs,
...(sink === undefined ? {} : { sink }),
});
}
catch (err) {
if (!(err instanceof ReturnSignal))
throw err;
io = ended(err);
}
;
[stdout, io] = await returning(executeFn, session, stdin, cs, io.stdout, io);
}
catch (err) {
if (err instanceof ExitSignal)
err.sourced = true;
throw err;
}
finally {
const frame = cs.pop();
if (session.functionNames !== null)
session.functionNames = outerNames;
if (args.length === 0)
setPositionalParams(session, cs, frame.positional);
}
return [stdout, io, new ExecutionNode({ command: `source ${raw}`, exitCode: io.exitCode })];
}
/**
* The `source` / `.` arm. Positional parameters keep the words as typed,
* so a path operand contributes its spelling, not its resolved mount path.
*/
export async function sourceBuiltin(call) {
const operands = [...call.argv.operands];
const target = operands[0];
const name = call.argv.name;
if (target === undefined) {
return scriptError(`bash: ${name}`, `filename argument required\n${builtinUsage(name).trimEnd()}`, 2, name);
}
const sourceArgs = operands.slice(1).map((o) => wordText(o));
return handleSource(call.dispatch, call.executeFn, target, call.context.session, sourceArgs, call.stdin, call.callStack, call.sink, name);
}
//# sourceMappingURL=source.js.map