scripts/verify-host.mjs
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { HOST_CHECK_SLOTS, hostChecksHeldMs, readHostChecksOwners } from './lib/verification-host.mjs';
import { waitForHostChecks } from './lib/verification-host-wait.mjs';
const args = process.argv.slice(2);
if (!args.length || args[0] === '--help') {
console.log('Usage: npm run verify:host -- [--wait-ms MS] [--poll-ms MS] [--] COMMAND [ARG ...]\n npm run verify:host -- status\nReserve one of the host\'s two slots for a serial build, workerd, boot, test-database or full-suite group. Typecheck, plain unit tests and mutation passes that start no server, port or test database run without it. Bounded waiting is opt-in; a hold over 15 minutes is reported to waiters once, never taken. No shell expansion, stealing or process killing. Timeout exits 3; cancellation exits 130. A command that cannot start releases its slot and exits 2; an interrupted command keeps it for reconciliation.\nstatus prints each slot\'s owner (PID, checkout, start, whether the PID is live), or null for a free slot, as JSON, and never reserves; it reports free while any slot is free. It wins over a command named status; to run one, put -- first: npm run verify:host -- -- status.');
} else if (args[0] === 'status') {
try {
if (args.length > 1) throw new Error('status takes no arguments. Use --help.');
const owners = readHostChecksOwners();
const slots = HOST_CHECK_SLOTS.map((file) => {
const held = owners.find((owner) => owner.file === file);
if (!held) return { file, owner: null, heldMs: null };
const { pid, cwd, startedAt, liveness } = held;
return { file, owner: { pid, cwd, startedAt, liveness }, heldMs: hostChecksHeldMs(held) };
});
console.log(JSON.stringify({ status: owners.length < slots.length ? 'free' : 'reserved', slots }));
} catch (error) {
console.error(error.message);
process.exitCode = 2;
}
} else {
const controller = new AbortController();
const cancel = () => controller.abort();
process.once('SIGINT', cancel); process.once('SIGTERM', cancel);
try {
const options = { waitMs: 0, pollMs: 1000 };
while (args[0]?.startsWith('--') && args[0] !== '--') {
const flag = args.shift();
if (!['--wait-ms', '--poll-ms'].includes(flag)) throw new Error('Unknown host-check option. Use --help.');
const value = args.shift();
if (!value || value.startsWith('-')) throw new Error(`${flag} requires milliseconds.`);
options[flag === '--wait-ms' ? 'waitMs' : 'pollMs'] = Number(value);
}
if (args[0] === '--') args.shift();
if (!args.length) throw new Error('A command is required.');
const lease = await waitForHostChecks({ ...options, signal: controller.signal,
onWait: (event) => console.error(JSON.stringify(event)) });
process.removeListener('SIGINT', cancel); process.removeListener('SIGTERM', cancel);
if (options.waitMs) console.error(JSON.stringify({ status: 'acquired', waitedMs: lease.waitedMs }));
const result = spawnSync(args[0], args.slice(1), { stdio: 'inherit', env: { ...process.env, ...lease.env } });
if (!result.pid) {
lease.release();
throw new Error(`Command could not start: ${args[0]} (${result.error.code}). It holds no host reservation.`);
}
if (!result.signal && !result.error) lease.release();
else console.error(`Interrupted command: host reservation retained. Reconcile its processes before removing the exact ${lease.file}.`);
process.exitCode = result.status ?? 1;
} catch (error) {
console.error(error.message);
process.exitCode = error.code === 'HOST_CHECKS_TIMEOUT' ? 3 : error.code === 'HOST_CHECKS_CANCELLED' ? 130 : 2;
} finally {
process.removeListener('SIGINT', cancel); process.removeListener('SIGTERM', cancel);
}
}