Files
nexu-io-open-design/e2e/lib/tools-dev/runtime.ts
PerishFire 891981d460 [codex] Optimize CI runtime topology (#4450)
* Optimize e2e tools-dev runtime parallelism

* Remove visual networkidle wait

* Optimize e2e vitest parallelism

* Optimize Nix flake caching

* Test CI on Blacksmith runners

* Allow parallel manual CI runs

* Tier CI runner sizes

* Temporarily narrow CI debug scope

* Instrument watcher CI timeouts

* Instrument watcher event diagnostics

* Avoid default polling in watcher tests

* Skip runtime trace during watcher debug

* Probe ARM runner tiers for CI

* Focus CI runner probe on x64 browser

* Probe browser workers by runner size

* Probe Playwright file parallelism

* Probe Playwright worker scaling on 8v

* Reshape CI topology

* Fix split E2E Vitest CI lane

* Simplify daemon CI topology

* Optimize Windows payload CI setup

* Revert "Optimize Windows payload CI setup"

This reverts commit 5cbc48c0af.

* Cache better-sqlite3 Nix binding separately

* Revert "Cache better-sqlite3 Nix binding separately"

This reverts commit 0384e3787e.

* Remove unused Nix cache setup from CI

* Use Blacksmith ARM for lightweight CI jobs
2026-06-17 12:53:06 +08:00

147 lines
4.5 KiB
TypeScript

import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { isToolsDevPortConflict, runToolsDevJson } from './cli.ts';
import { allocateToolsDevPorts } from './ports.ts';
import type {
ToolsDevCheckResult,
ToolsDevLogResult,
ToolsDevPortAllocation,
ToolsDevStartResult,
ToolsDevStatusResult,
ToolsDevSuite,
ToolsDevSuiteSpec,
} from './types.ts';
const e2eRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
const workspaceRoot = dirname(e2eRoot);
export function e2eWorkspaceRoot(): string {
return workspaceRoot;
}
export function createToolsDevSuite(spec: ToolsDevSuiteSpec): ToolsDevSuite {
let currentPortAllocation: ToolsDevPortAllocation | null = null;
let currentDaemonUrl: string | null = null;
let currentWebUrl: string | null = null;
async function command<T>(args: string[], env?: Record<string, string | undefined>): Promise<T> {
return await runToolsDevJson<T>(workspaceRoot, spec, args, env);
}
async function startWeb(env: Record<string, string | undefined> = {}): Promise<ToolsDevStartResult> {
let lastError: unknown = null;
for (let attempt = 1; attempt <= 3; attempt++) {
currentPortAllocation = await allocateToolsDevPorts();
try {
// Keep both ports reserved until immediately before tools-dev starts so
// parallel workers do not race each other for the same "free" port.
await currentPortAllocation.release();
const result = await command<ToolsDevStartResult>(
[
'start',
'web',
'--namespace',
spec.namespace,
'--tools-dev-root',
spec.toolsDevRoot,
'--daemon-port',
String(currentPortAllocation.daemonPort),
'--web-port',
String(currentPortAllocation.webPort),
'--json',
],
env,
);
currentDaemonUrl = assertRuntimeUrl(result.daemon?.status.url, 'daemon');
currentWebUrl = assertRuntimeUrl(result.web?.status.url, 'web');
return result;
} catch (error) {
lastError = error;
if (attempt === 3 || !isToolsDevPortConflict(error)) throw error;
await stopWeb(env).catch(() => {});
}
}
throw lastError instanceof Error ? lastError : new Error(String(lastError));
}
async function stopWeb(env: Record<string, string | undefined> = {}): Promise<unknown> {
const result = await command<unknown>(
[
'stop',
'web',
'--namespace',
spec.namespace,
'--tools-dev-root',
spec.toolsDevRoot,
'--json',
],
env,
);
currentDaemonUrl = null;
currentWebUrl = null;
return result;
}
function requireUrl(value: string | null, app: 'daemon' | 'web'): string {
if (value == null) throw new Error(`${app} runtime has not started`);
return value;
}
function appendPath(base: string, path = '/'): string {
const url = new URL(base);
url.pathname = path.startsWith('/') ? path : `/${path}`;
url.search = '';
url.hash = '';
return url.toString();
}
return {
...spec,
get daemonPort() {
return currentPortAllocation?.daemonPort ?? null;
},
get daemonUrl() {
return currentDaemonUrl;
},
get portAllocation() {
return currentPortAllocation;
},
url: {
api: (path = '/') => appendPath(requireUrl(currentDaemonUrl, 'daemon'), path),
daemon: (path = '/') => appendPath(requireUrl(currentDaemonUrl, 'daemon'), path),
web: (path = '/') => appendPath(requireUrl(currentWebUrl, 'web'), path),
},
get webPort() {
return currentPortAllocation?.webPort ?? null;
},
get webUrl() {
return currentWebUrl;
},
check: (env) =>
command<ToolsDevCheckResult>(
['check', '--namespace', spec.namespace, '--tools-dev-root', spec.toolsDevRoot, '--json'],
env,
),
logs: (env) =>
command<Record<string, ToolsDevLogResult>>(
['logs', '--namespace', spec.namespace, '--tools-dev-root', spec.toolsDevRoot, '--json'],
env,
),
startWeb,
status: (env) =>
command<ToolsDevStatusResult>(
['status', '--namespace', spec.namespace, '--tools-dev-root', spec.toolsDevRoot, '--json'],
env,
),
stopWeb,
};
}
function assertRuntimeUrl(value: string | null | undefined, app: string): string {
if (typeof value !== 'string' || !value.startsWith('http://')) {
throw new Error(`${app} runtime did not expose status URL: ${String(value)}`);
}
return value;
}