/** * Launches an isolated MetoYou signaling server for E2E tests. * * Creates a temporary data directory so the test server gets its own * fresh SQLite database. The server process inherits stdio so Playwright * can watch stdout for readiness and the developer can see logs. * * Cleanup: the temp directory is removed when the process exits. */ const { mkdtempSync, writeFileSync, mkdirSync, rmSync } = require('fs'); const { join } = require('path'); const { tmpdir } = require('os'); const { spawn } = require('child_process'); const TEST_PORT = process.env.TEST_SERVER_PORT || '3099'; const SERVER_DIR = join(__dirname, '..', '..', 'server'); const SERVER_ENTRY = join(SERVER_DIR, 'src', 'index.ts'); const SERVER_TSCONFIG = join(SERVER_DIR, 'tsconfig.json'); // ── Create isolated temp data directory ────────────────────────────── const tmpDir = mkdtempSync(join(tmpdir(), 'metoyou-e2e-')); const dataDir = join(tmpDir, 'data'); mkdirSync(dataDir, { recursive: true }); writeFileSync( join(dataDir, 'variables.json'), JSON.stringify({ serverPort: parseInt(TEST_PORT, 10), serverProtocol: 'http', serverHost: '', klipyApiKey: '', releaseManifestUrl: '', linkPreview: { enabled: false, cacheTtlMinutes: 60, maxCacheSizeMb: 10 }, }) ); console.log(`[E2E Server] Temp data dir: ${tmpDir}`); console.log(`[E2E Server] Starting on port ${TEST_PORT}...`); // ── Spawn the server with cwd = temp dir ───────────────────────────── // process.cwd() is used by getRuntimeBaseDir() in the server, so data/ // (database, variables.json) will resolve to our temp directory. // Module resolution (require/import) uses __dirname, so server source // and node_modules are found from the real server/ directory. const child = spawn( 'npx', ['ts-node', '--project', SERVER_TSCONFIG, SERVER_ENTRY], { cwd: tmpDir, env: { ...process.env, PORT: TEST_PORT, SSL: 'false', NODE_ENV: 'test', DB_SYNCHRONIZE: 'true', }, stdio: 'inherit', shell: true, } ); let shuttingDown = false; child.on('error', (err) => { console.error('[E2E Server] Failed to start:', err.message); cleanup(); process.exit(1); }); child.on('exit', (code) => { console.log(`[E2E Server] Exited with code ${code}`); cleanup(); if (shuttingDown) { process.exit(0); } }); // ── Cleanup on signals ─────────────────────────────────────────────── function cleanup() { try { rmSync(tmpDir, { recursive: true, force: true }); console.log(`[E2E Server] Cleaned up temp dir: ${tmpDir}`); } catch { // already gone } } function shutdown() { if (shuttingDown) { return; } shuttingDown = true; child.kill('SIGTERM'); // Give child 3s to exit, then force kill setTimeout(() => { if (child.exitCode === null) { child.kill('SIGKILL'); } }, 3_000); } process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown); process.on('exit', cleanup);