mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
d01ee7b19a
Join identity-verified PTY family cleanup and native exit before workspace removal, retaining failed cleanup capabilities for retry. Replace duplicate kill-and-reject loops with shared process ownership and deadlines. Capture configuration before setup using owned file handles and compare raw bytes. Preserve interactive cancellation, terminal restoration, redirected output, and selected inputs; remove the test-only forwarding export. Verified 122 focused cases, 9 README cases, build, static checks, docs, discovery, and duplication at 3.58%.
143 lines
4.9 KiB
JavaScript
143 lines
4.9 KiB
JavaScript
import { runOwnedVerifierTerminal } from "./owned-verifier-terminal.mjs";
|
|
import {
|
|
captureVerifierFileSnapshot,
|
|
assertVerifierFilesUnchanged,
|
|
} from "./verifier-file-snapshots.mjs";
|
|
|
|
export const terminalModesRestored = (termios) => {
|
|
if (typeof termios !== "string") return false;
|
|
const modes = new Set(termios.trim().split(/\s+/u));
|
|
return modes.has("icanon") && modes.has("echo");
|
|
};
|
|
|
|
export async function verifyInteractiveSetup({
|
|
commands,
|
|
command,
|
|
environment,
|
|
root,
|
|
unchangedPaths,
|
|
}) {
|
|
const unchanged = await captureVerifierFileSnapshot(commands, unchangedPaths);
|
|
let cancelled = false;
|
|
const wrapper = `
|
|
const { spawnSync } = require("node:child_process");
|
|
const child = spawnSync(process.argv[1], ["setup"], { stdio: "inherit", env: process.env, shell: process.platform === "win32" });
|
|
const terminal = process.platform === "win32"
|
|
? spawnSync("cmd.exe", ["/c", "mode", "CON"], { encoding: "utf8", stdio: ["inherit", "pipe", "inherit"] })
|
|
: spawnSync("stty", ["-a"], { encoding: "utf8", stdio: ["inherit", "pipe", "inherit"] });
|
|
process.stdout.write("\\n__REA_SETUP_EXIT__" + JSON.stringify({ status: child.status, termios: terminal.stdout, error: child.error?.message }));
|
|
process.exit(child.status ?? 1);
|
|
`;
|
|
let result;
|
|
try {
|
|
result = await runOwnedVerifierTerminal({
|
|
commands,
|
|
command: process.execPath,
|
|
args: ["-e", wrapper, command],
|
|
cwd: root,
|
|
environment: { ...environment, NO_COLOR: "1", TERM: "xterm-256color" },
|
|
timeoutMs: 20_000,
|
|
onData(output, write) {
|
|
if (!cancelled && output.includes("Which agents should use REA?")) {
|
|
cancelled = true;
|
|
write("\u0003");
|
|
}
|
|
},
|
|
});
|
|
} catch (cause) {
|
|
if (cause?._tag === "VerifierTerminalFailure" && cause.reason === "timeout")
|
|
throw new Error(
|
|
`packaged setup wizard timed out after output: ${cause.output}`,
|
|
{ cause },
|
|
);
|
|
throw cause;
|
|
}
|
|
const { output, exitCode } = result;
|
|
const marker = output.lastIndexOf("__REA_SETUP_EXIT__");
|
|
let childResult;
|
|
try {
|
|
childResult =
|
|
marker < 0 ? undefined : JSON.parse(output.slice(marker + 18).trim());
|
|
} catch {
|
|
childResult = undefined;
|
|
}
|
|
if (
|
|
!cancelled ||
|
|
exitCode !== 0 ||
|
|
childResult?.status !== 0 ||
|
|
!output.includes("\u001b[?25h") ||
|
|
!terminalModesRestored(childResult?.termios) ||
|
|
!output.includes("No changes were made.")
|
|
)
|
|
throw new Error(
|
|
`packaged rea-agents setup did not cancel with a restored terminal and exit 0: ${output}`,
|
|
);
|
|
await assertVerifierFilesUnchanged(
|
|
commands,
|
|
unchanged,
|
|
"Interactive cancellation",
|
|
);
|
|
}
|
|
|
|
export async function verifyRedirectedStdoutSetup({
|
|
commands,
|
|
command,
|
|
environment,
|
|
root,
|
|
unchangedPaths,
|
|
}) {
|
|
const unchanged = await captureVerifierFileSnapshot(commands, unchangedPaths);
|
|
const wrapper = `
|
|
const { spawnSync } = require("node:child_process");
|
|
const child = spawnSync(process.argv[1], ["setup"], { encoding: "utf8", env: process.env, stdio: ["inherit", "pipe", "inherit"], shell: process.platform === "win32" });
|
|
process.stdout.write("\\n__REA_REDIRECTED_SETUP__" + JSON.stringify({ status: child.status, stdout: child.stdout, error: child.error?.message, stdinTTY: process.stdin.isTTY, stderrTTY: process.stderr.isTTY, stdoutRedirected: true }));
|
|
process.exit(child.status ?? 1);
|
|
`;
|
|
let result;
|
|
try {
|
|
result = await runOwnedVerifierTerminal({
|
|
commands,
|
|
command: process.execPath,
|
|
args: ["-e", wrapper, command],
|
|
cwd: root,
|
|
environment: { ...environment, NO_COLOR: "1", TERM: "xterm-256color" },
|
|
timeoutMs: 15_000,
|
|
});
|
|
} catch (cause) {
|
|
if (cause?._tag === "VerifierTerminalFailure" && cause.reason === "timeout")
|
|
throw new Error(
|
|
`packaged setup started an interactive prompt with redirected stdout: ${cause.output}`,
|
|
{ cause },
|
|
);
|
|
throw cause;
|
|
}
|
|
const { output, exitCode } = result;
|
|
const marker = output.lastIndexOf("__REA_REDIRECTED_SETUP__");
|
|
let childResult;
|
|
try {
|
|
childResult =
|
|
marker < 0 ? undefined : JSON.parse(output.slice(marker + 24).trim());
|
|
} catch {
|
|
childResult = undefined;
|
|
}
|
|
const allOutput = `${output}\n${childResult?.stdout ?? ""}`;
|
|
if (
|
|
(exitCode !== 0 && exitCode !== 1) ||
|
|
childResult?.status !== exitCode ||
|
|
childResult?.stdinTTY !== true ||
|
|
childResult?.stderrTTY !== true ||
|
|
childResult?.stdoutRedirected !== true ||
|
|
childResult?.stdout?.includes("Which agents should use REA?") ||
|
|
allOutput.includes("Which agents should use REA?") ||
|
|
/\u001b\[[0-9;?]*[ -/]*[@-~]/u.test(allOutput)
|
|
)
|
|
throw new Error(
|
|
`packaged setup did not remain plain and non-interactive with redirected stdout: ${output}`,
|
|
);
|
|
await assertVerifierFilesUnchanged(
|
|
commands,
|
|
unchanged,
|
|
"Redirected-stdout setup",
|
|
);
|
|
}
|