Pulse/tests/integration/scripts/managed-dev-runtime.mjs
2026-03-24 14:15:38 +00:00

150 lines
4.5 KiB
JavaScript

import { spawn } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { clearRuntimeState, readRuntimeState, writeRuntimeState } from './runtime-state.mjs';
const truthy = (value) =>
['1', 'true', 'yes', 'on'].includes(String(value || '').trim().toLowerCase());
const trim = (value) => String(value || '').trim();
function repoRootFromEnv(env = process.env) {
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
return trim(env.PULSE_E2E_REPO_ROOT) || path.resolve(scriptDir, '..', '..', '..');
}
function hotDevBgScriptPath(env = process.env) {
return path.join(repoRootFromEnv(env), 'scripts', 'hot-dev-bg.sh');
}
function hotDevBrowserURL(env = process.env) {
const host = trim(env.FRONTEND_DEV_HOST) || '127.0.0.1';
const port = trim(env.FRONTEND_DEV_PORT) || '5173';
return `http://${host}:${port}`;
}
function hotDevBackendURL(env = process.env) {
const host = trim(env.PULSE_DEV_API_HOST) || '127.0.0.1';
const port = trim(env.PULSE_DEV_API_PORT) || '7655';
return `http://${host}:${port}`;
}
function run(command, args, options = {}) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { ...options });
let stdout = '';
let stderr = '';
child.stdout?.on('data', (chunk) => {
stdout += chunk.toString();
});
child.stderr?.on('data', (chunk) => {
stderr += chunk.toString();
});
child.on('error', reject);
child.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));
});
}
async function runHotDevBg(args, env = process.env) {
const mergedEnv = { ...process.env, ...env };
return run(hotDevBgScriptPath(mergedEnv), args, {
cwd: repoRootFromEnv(mergedEnv),
env: mergedEnv,
});
}
function statusReportsRunning(output) {
return output.includes('[hot-dev-bg] Running');
}
async function ensureHealthyStatus(env = process.env) {
const status = await runHotDevBg(['status'], env);
if (status.code !== 0) {
throw new Error(`hot-dev-bg status failed: ${status.stderr || status.stdout}`);
}
const output = `${status.stdout}${status.stderr}`;
const requiredMarkers = [
'[hot-dev-bg] Frontend shell HTTP: 200',
'[hot-dev-bg] Frontend proxy /api/health: 200',
'[hot-dev-bg] Backend /api/health: 200',
];
for (const marker of requiredMarkers) {
if (!output.includes(marker)) {
throw new Error(`Managed dev runtime is not healthy enough for browser proof:\n${output}`);
}
}
return output;
}
export async function startManagedDevRuntime({
env = process.env,
logger = console,
} = {}) {
await clearRuntimeState(env);
const statusBefore = await runHotDevBg(['status'], env);
if (statusBefore.code !== 0) {
throw new Error(`hot-dev-bg status failed before start: ${statusBefore.stderr || statusBefore.stdout}`);
}
const wasRunning = statusReportsRunning(`${statusBefore.stdout}${statusBefore.stderr}`);
const startArgs = ['start'];
if (truthy(env.PULSE_E2E_HOT_DEV_TAKEOVER)) {
startArgs.push('--takeover');
}
const startResult = await runHotDevBg(startArgs, env);
if (startResult.code !== 0) {
throw new Error(`hot-dev-bg start failed: ${startResult.stderr || startResult.stdout}`);
}
await ensureHealthyStatus(env);
const runtimeState = {
managedDevRuntime: true,
baseURL: hotDevBrowserURL(env),
browserURL: hotDevBrowserURL(env),
backendURL: hotDevBackendURL(env),
startedByHarness: !wasRunning,
};
await writeRuntimeState(runtimeState, env);
logger.log(
`[integration] Managed dev runtime ready at ${runtimeState.browserURL} (backend ${runtimeState.backendURL})`,
);
return runtimeState;
}
export async function restartManagedDevRuntimeBackend({
env = process.env,
} = {}) {
const result = await runHotDevBg(['backend-restart'], env);
if (result.code !== 0) {
throw new Error(`hot-dev-bg backend-restart failed: ${result.stderr || result.stdout}`);
}
await ensureHealthyStatus(env);
}
export async function stopManagedDevRuntime({
env = process.env,
logger = console,
} = {}) {
const runtimeState = await readRuntimeState(env);
if (!runtimeState?.managedDevRuntime) {
await clearRuntimeState(env);
return false;
}
if (runtimeState.startedByHarness) {
const result = await runHotDevBg(['stop'], env);
if (result.code !== 0) {
throw new Error(`hot-dev-bg stop failed: ${result.stderr || result.stdout}`);
}
logger.log('[integration] Stopped managed dev runtime started by harness');
}
await clearRuntimeState(env);
return true;
}