// Boots a built Nuxt server (`/server/index.mjs`) on 127.0.0.1 // against a given Journal API base and waits until it answers HTTP. // Plain .mjs with JSDoc types so it runs from node_modules unchanged. import { spawn } from 'node:child_process' import { createServer } from 'node:net' import { existsSync } from 'node:fs' import { join, resolve } from 'node:path' /** @returns {Promise} */ function freePort() { return new Promise((done, fail) => { const srv = createServer() srv.once('error', fail) srv.listen(0, '127.0.0.1', () => { const { port } = /** @type {import('node:net').AddressInfo} */ (srv.address()) srv.close(() => done(port)) }) }) } /** * @param {{ outputDir: string, apiBase: string, port?: number, env?: Record, timeoutMs?: number }} opts * @returns {Promise<{ url: string, close: () => Promise }>} */ export async function startNuxtServer({ outputDir, apiBase, port = 0, env = {}, timeoutMs = 30000 }) { const entry = join(resolve(outputDir), 'server', 'index.mjs') if (!existsSync(entry)) throw new Error(`nuxt-server: ${entry} does not exist; build first`) const listenPort = port || (await freePort()) const url = `http://127.0.0.1:${listenPort}` const child = spawn(process.execPath, [entry], { env: { ...process.env, NODE_ENV: 'production', NITRO_HOST: '127.0.0.1', NITRO_PORT: String(listenPort), NUXT_JOURNAL_API_BASE: apiBase, ...env, }, stdio: ['ignore', 'pipe', 'pipe'], }) let log = '' child.stdout.on('data', (b) => (log += b)) child.stderr.on('data', (b) => (log += b)) let exited = false child.once('exit', () => (exited = true)) const close = () => new Promise((/** @type {(v?: void) => void} */ done) => { if (exited) return done() child.once('exit', () => done()) child.kill('SIGTERM') setTimeout(() => { if (!exited) child.kill('SIGKILL') }, 5000).unref() }) const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { if (exited) throw new Error(`nuxt-server: server exited before answering\n${log}`) try { await fetch(url + '/', { redirect: 'manual', signal: AbortSignal.timeout(2000) }) return { url, close } } catch { await new Promise((r) => setTimeout(r, 200)) } } await close() throw new Error(`nuxt-server: no answer on ${url} within ${timeoutMs} ms\n${log}`) }