blob: a9a3af255587e7bb1214d35235ace0b6477ab5ea [file]
import {
candidateStartupFailureExitCode,
classifyCandidateStartupFailure,
} from './candidate-startup-failure.js';
import { parseInteractiveRuntimeHostCandidateArguments } from './candidate-cli.js';
import { writeCandidateStartupDiagnostic } from './control/startup-diagnostic.js';
import { installRuntimeHostLogCapture, runtimeHostLogBuffer } from './process-diagnostics.js';
import {
type ExecutionRuntimeHostCandidateDependencies,
type ExecutionRuntimeHostCandidateOptions,
startExecutionRuntimeHostCandidate,
} from './server/execution-candidate.js';
import type { RuntimeHostKernel } from './server/host-kernel.js';
import { runRuntimeHostProcessLifecycle } from './server/process-lifecycle.js';
export interface ExecutionCandidateEntryHooks {
/** Applied to the parsed command line before the candidate starts. */
readonly overrideOptions?: (
options: ExecutionRuntimeHostCandidateOptions,
) => ExecutionRuntimeHostCandidateOptions;
readonly dependencies?: ExecutionRuntimeHostCandidateDependencies;
/** Runs once the candidate has won; the returned callback stops the watch. */
readonly onWon?: (host: RuntimeHostKernel) => () => void;
}
/**
* The bootstrap both candidate entry modules share. The entry file is the only
* thing that differs between the production and Desktop E2E processes, so the
* argument parsing, startup diagnostics, and lifecycle live here instead of
* being copied — a copy is what silently drifts when either one gains a step.
*/
export async function runExecutionCandidateEntry(
argv: readonly string[],
hooks: ExecutionCandidateEntryHooks = {},
): Promise<never | void> {
installRuntimeHostLogCapture();
let result: Awaited<ReturnType<typeof startExecutionRuntimeHostCandidate>>;
let rootId: string | undefined;
let startupAttemptId: string | undefined;
try {
const parsed = parseInteractiveRuntimeHostCandidateArguments(argv);
rootId = parsed.expectedRootId;
const { startupAttemptId: parsedStartupAttemptId, ...options } = parsed;
startupAttemptId = parsedStartupAttemptId;
result = await startExecutionRuntimeHostCandidate(
hooks.overrideOptions ? hooks.overrideOptions(options) : options,
hooks.dependencies ?? {},
);
} catch (error) {
const failure = classifyCandidateStartupFailure(error);
const logs = runtimeHostLogBuffer.snapshot();
console.error('[runtime-host] startup failed:', error);
if (rootId && startupAttemptId) {
await writeCandidateStartupDiagnostic({
rootId,
startupAttemptId,
failure,
error,
logs,
}).catch(() => undefined);
}
process.exit(candidateStartupFailureExitCode(failure));
}
if (result.kind === 'loser') process.exit(2);
const stopWatch = hooks.onWon?.(result.host);
try {
await runRuntimeHostProcessLifecycle(result.host);
} catch {
process.exitCode = 1;
} finally {
stopWatch?.();
}
}