You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
3.3 KiB
TypeScript
140 lines
3.3 KiB
TypeScript
import { existsSync, mkdirSync, openSync, rmSync, writeFileSync } from 'fs';
|
|
import { join, dirname } from 'path';
|
|
|
|
export async function ensureWebBuilt(): Promise<void> {
|
|
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<void>;
|
|
pid: number | null;
|
|
port: number;
|
|
};
|
|
|
|
export async function startWebServer(opts: {
|
|
port?: number;
|
|
env?: Record<string, string>;
|
|
} = {}): Promise<RunningServer> {
|
|
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));
|
|
}
|
|
};
|
|
}
|