Install stage: package-manager detection (lockfile beats the packageManager field, npm default — vite-kitchen's shape), install run inside the clone with staged status heartbeats, and every failure — including install-before-clone — mapped to the named install-failed cause with a bounded output tail. The spawned-runner suite now runs the full clone → install pipeline on the daemon-served vite-kitchen (real npm registry install) and greps the child's entire stdout+stderr plus the clone's .git/config for the PAT and workspace token. Shared test doubles extracted (exec.mock.ts, tests/runner-session.ts) to keep the suites duplication-free. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
54 lines
1.5 KiB
TypeScript
54 lines
1.5 KiB
TypeScript
import { mkdtemp, rm } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { createLogger } from "@/log";
|
|
import { startRunnerServer, type RunnerServer } from "@/server";
|
|
import {
|
|
connectProtocolClient,
|
|
handshake,
|
|
type ProtocolClient,
|
|
} from "./protocol-client";
|
|
|
|
/**
|
|
* An in-process runner server plus one already-handshaken client, with
|
|
* log capture (for the credential-leak assertions) and one-call
|
|
* teardown. In-process so v8 coverage sees the server; the spawn suite
|
|
* covers the real-child-process shape.
|
|
*/
|
|
export interface RunnerSession {
|
|
client: ProtocolClient;
|
|
workspaceDir: string;
|
|
/** Every log line the runner emitted — greppable for leaks. */
|
|
logLines: string[];
|
|
close: () => Promise<void>;
|
|
}
|
|
|
|
export async function startRunnerSession(options: {
|
|
token: string;
|
|
tmpPrefix: string;
|
|
heartbeatMs?: number;
|
|
}): Promise<RunnerSession> {
|
|
const workspaceDir = await mkdtemp(path.join(os.tmpdir(), options.tmpPrefix));
|
|
const logLines: string[] = [];
|
|
const server: RunnerServer = await startRunnerServer({
|
|
host: "127.0.0.1",
|
|
port: 0,
|
|
token: options.token,
|
|
workspaceDir,
|
|
heartbeatMs: options.heartbeatMs ?? 100,
|
|
log: createLogger((line) => logLines.push(line)),
|
|
});
|
|
const client = await connectProtocolClient(server.port, options.token);
|
|
await handshake(client);
|
|
return {
|
|
client,
|
|
workspaceDir,
|
|
logLines,
|
|
close: async () => {
|
|
client.close();
|
|
await server.close();
|
|
await rm(workspaceDir, { recursive: true, force: true });
|
|
},
|
|
};
|
|
}
|