2025-04-16 12:56:08 -04:00
|
|
|
import type { ExecInput, ExecResult } from "./sandbox/interface.js";
|
|
|
|
|
import type { SpawnOptions } from "child_process";
|
|
|
|
|
|
|
|
|
|
import { process_patch } from "./apply-patch.js";
|
|
|
|
|
import { SandboxType } from "./sandbox/interface.js";
|
|
|
|
|
import { execWithSeatbelt } from "./sandbox/macos-seatbelt.js";
|
|
|
|
|
import { exec as rawExec } from "./sandbox/raw-exec.js";
|
2025-04-16 14:16:53 -07:00
|
|
|
import { formatCommandForDisplay } from "../../format-command.js";
|
2025-04-16 12:56:08 -04:00
|
|
|
import fs from "fs";
|
|
|
|
|
import os from "os";
|
|
|
|
|
|
|
|
|
|
const DEFAULT_TIMEOUT_MS = 10_000; // 10 seconds
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This function should never return a rejected promise: errors should be
|
|
|
|
|
* mapped to a non-zero exit code and the error message should be in stderr.
|
|
|
|
|
*/
|
|
|
|
|
export function exec(
|
2025-04-17 15:39:26 -07:00
|
|
|
{
|
|
|
|
|
cmd,
|
|
|
|
|
workdir,
|
|
|
|
|
timeoutInMillis,
|
|
|
|
|
additionalWritableRoots,
|
|
|
|
|
}: ExecInput & { additionalWritableRoots: ReadonlyArray<string> },
|
2025-04-16 12:56:08 -04:00
|
|
|
sandbox: SandboxType,
|
|
|
|
|
abortSignal?: AbortSignal,
|
|
|
|
|
): Promise<ExecResult> {
|
|
|
|
|
// This is a temporary measure to understand what are the common base commands
|
|
|
|
|
// until we start persisting and uploading rollouts
|
|
|
|
|
|
|
|
|
|
const execForSandbox =
|
|
|
|
|
sandbox === SandboxType.MACOS_SEATBELT ? execWithSeatbelt : rawExec;
|
|
|
|
|
|
|
|
|
|
const opts: SpawnOptions = {
|
|
|
|
|
timeout: timeoutInMillis || DEFAULT_TIMEOUT_MS,
|
|
|
|
|
...(workdir ? { cwd: workdir } : {}),
|
|
|
|
|
};
|
2025-04-17 15:39:26 -07:00
|
|
|
// Merge default writable roots with any user-specified ones.
|
|
|
|
|
const writableRoots = [
|
|
|
|
|
process.cwd(),
|
|
|
|
|
os.tmpdir(),
|
|
|
|
|
...additionalWritableRoots,
|
|
|
|
|
];
|
2025-04-16 12:56:08 -04:00
|
|
|
return execForSandbox(cmd, opts, writableRoots, abortSignal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function execApplyPatch(patchText: string): ExecResult {
|
|
|
|
|
// This is a temporary measure to understand what are the common base commands
|
|
|
|
|
// until we start persisting and uploading rollouts
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = process_patch(
|
|
|
|
|
patchText,
|
|
|
|
|
(p) => fs.readFileSync(p, "utf8"),
|
|
|
|
|
(p, c) => fs.writeFileSync(p, c, "utf8"),
|
|
|
|
|
(p) => fs.unlinkSync(p),
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
stdout: result,
|
|
|
|
|
stderr: "",
|
|
|
|
|
exitCode: 0,
|
|
|
|
|
};
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
// @ts-expect-error error might not be an object or have a message property.
|
|
|
|
|
const stderr = String(error.message ?? error);
|
|
|
|
|
return {
|
|
|
|
|
stdout: "",
|
|
|
|
|
stderr: stderr,
|
|
|
|
|
exitCode: 1,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getBaseCmd(cmd: Array<string>): string {
|
|
|
|
|
const formattedCommand = formatCommandForDisplay(cmd);
|
|
|
|
|
return formattedCommand.split(" ")[0] || cmd[0] || "<unknown>";
|
|
|
|
|
}
|