import { existsSync, mkdirSync, openSync, rmSync, writeFileSync } from 'fs'; import { join, dirname } from 'path'; export async function ensureWebBuilt(): Promise { const buildEntry = join('apps', 'web', 'build', 'index.js'); const isTest = String(process.env.NODE_ENV || '').toLowerCase() === 'test'; if (existsSync(buildEntry) && !isTest) return; const lockFile = join('apps', 'web', '.build.lock'); let haveLock = false; try { // Intentar crear lock (exclusivo). Si existe, esperar a que termine la otra build. openSync(lockFile, 'wx'); haveLock = true; } catch { // Otra build en progreso o ya hecha. Esperar hasta que exista el build. const timeoutMs = 60_000; const start = Date.now(); while (!existsSync(buildEntry)) { if (Date.now() - start > timeoutMs) { throw new Error('Timeout esperando a que termine el build de apps/web'); } // Dormir 100ms await new Promise((res) => setTimeout(res, 100)); } return; } try { // Asegurar carpeta build try { mkdirSync(dirname(buildEntry), { recursive: true }); } catch {} // Ejecutar "bun run build" dentro de apps/web const proc = Bun.spawn({ cmd: [process.execPath, 'run', 'build'], cwd: join('apps', 'web'), stdout: 'inherit', stderr: 'inherit', env: { ...process.env, NODE_ENV: 'production' } }); const exitCode = await proc.exited; if (exitCode !== 0) { throw new Error(`Fallo al construir apps/web (exit ${exitCode})`); } } finally { // Liberar lock try { rmSync(lockFile, { force: true }); } catch {} } } export type RunningServer = { baseUrl: string; stop: () => Promise; pid: number | null; port: number; }; export async function startWebServer(opts: { port?: number; env?: Record; } = {}): Promise { await ensureWebBuilt(); const port = Number(opts.port || 19080); // Lanzar servidor Node adapter: "bun ./build/index.js" en apps/web const child = Bun.spawn({ cmd: [process.execPath, './build/index.js'], cwd: join('apps', 'web'), stdout: 'pipe', stderr: 'pipe', env: { ...process.env, PORT: String(port), NODE_ENV: 'test', ...(opts.env || {}) } }); // Esperar a que esté arriba (ping a "/") const baseUrl = `http://127.0.0.1:${port}`; const startedAt = Date.now(); const timeoutMs = 30_000; let lastErr: any = null; while (Date.now() - startedAt < timeoutMs) { try { const res = await fetch(baseUrl + '/', { method: 'GET' }); if (res) break; } catch (e) { lastErr = e; } await new Promise((res) => setTimeout(res, 100)); } if (Date.now() - startedAt >= timeoutMs) { try { child.kill(); } catch {} throw new Error(`Timeout esperando al servidor web: ${lastErr?.message || lastErr}`); } // Conectar logs a consola (opcional) (async () => { try { for await (const chunk of child.stdout) { try { process.stderr.write(`[web] ${new TextDecoder().decode(chunk)}`); } catch {} } } catch {} })(); (async () => { try { for await (const chunk of child.stderr) { try { process.stderr.write(`[web] ${new TextDecoder().decode(chunk)}`); } catch {} } } catch {} })(); return { baseUrl, port, pid: child.pid, stop: async () => { try { child.kill(); } catch {} // Pequeña espera para liberar puerto await new Promise((res) => setTimeout(res, 50)); } }; }