157 lines
4.0 KiB
TypeScript
157 lines
4.0 KiB
TypeScript
import { spawn, ChildProcess, execSync } from 'child_process';
|
|
import { resolve, dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
const ROOT_DIR = resolve(__dirname, '..');
|
|
const SERVER_DIR = resolve(ROOT_DIR, 'server');
|
|
const CLIENT_DIR = resolve(ROOT_DIR, 'client');
|
|
|
|
export interface ServerProcesses {
|
|
backend: ChildProcess | null;
|
|
frontend: ChildProcess | null;
|
|
}
|
|
|
|
let processes: ServerProcesses = {
|
|
backend: null,
|
|
frontend: null,
|
|
};
|
|
|
|
function waitForServer(url: string, timeout = 30000): Promise<void> {
|
|
const start = Date.now();
|
|
// Allow self-signed certificates for HTTPS
|
|
const originalTlsReject = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
if (url.startsWith('https://')) {
|
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
const cleanup = () => {
|
|
if (originalTlsReject !== undefined) {
|
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = originalTlsReject;
|
|
} else {
|
|
delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
}
|
|
};
|
|
const check = async () => {
|
|
try {
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
|
const response = await fetch(url, { signal: controller.signal });
|
|
clearTimeout(timeoutId);
|
|
if (response.ok) {
|
|
// Add small delay to ensure server is fully ready
|
|
await new Promise((r) => setTimeout(r, 500));
|
|
cleanup();
|
|
resolve();
|
|
return;
|
|
}
|
|
} catch {
|
|
// Server not ready yet
|
|
}
|
|
|
|
if (Date.now() - start > timeout) {
|
|
cleanup();
|
|
reject(new Error(`Server at ${url} did not start within ${timeout}ms`));
|
|
return;
|
|
}
|
|
|
|
setTimeout(check, 500);
|
|
};
|
|
check();
|
|
});
|
|
}
|
|
|
|
export async function startBackend(port = 3000): Promise<ChildProcess> {
|
|
console.log('Starting backend server...');
|
|
|
|
// Use a test database to avoid polluting the main one
|
|
const testDbPath = resolve(SERVER_DIR, 'test-e2e.db');
|
|
|
|
// Remove old test database
|
|
try {
|
|
execSync(`rm -f ${testDbPath}`);
|
|
} catch {
|
|
// Ignore if doesn't exist
|
|
}
|
|
|
|
const backend = spawn('clj', ['-M:run'], {
|
|
cwd: SERVER_DIR,
|
|
env: {
|
|
...process.env,
|
|
SPICEFLOW_PORT: String(port),
|
|
SPICEFLOW_DB: testDbPath,
|
|
},
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
});
|
|
|
|
backend.stdout?.on('data', (data) => {
|
|
console.log(`[backend] ${data.toString().trim()}`);
|
|
});
|
|
|
|
backend.stderr?.on('data', (data) => {
|
|
console.error(`[backend] ${data.toString().trim()}`);
|
|
});
|
|
|
|
processes.backend = backend;
|
|
|
|
await waitForServer(`http://localhost:${port}/api/health`);
|
|
console.log('Backend server ready');
|
|
|
|
return backend;
|
|
}
|
|
|
|
export async function startFrontend(port = 5173, backendPort = 3000): Promise<ChildProcess> {
|
|
console.log('Starting frontend server...');
|
|
|
|
const frontend = spawn('npm', ['run', 'dev', '--', '--port', String(port)], {
|
|
cwd: CLIENT_DIR,
|
|
env: {
|
|
...process.env,
|
|
},
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
});
|
|
|
|
frontend.stdout?.on('data', (data) => {
|
|
console.log(`[frontend] ${data.toString().trim()}`);
|
|
});
|
|
|
|
frontend.stderr?.on('data', (data) => {
|
|
console.error(`[frontend] ${data.toString().trim()}`);
|
|
});
|
|
|
|
processes.frontend = frontend;
|
|
|
|
await waitForServer(`https://localhost:${port}`);
|
|
console.log('Frontend server ready');
|
|
|
|
return frontend;
|
|
}
|
|
|
|
export async function startServers(backendPort = 3000, frontendPort = 5173): Promise<ServerProcesses> {
|
|
await startBackend(backendPort);
|
|
await startFrontend(frontendPort, backendPort);
|
|
return processes;
|
|
}
|
|
|
|
export function stopServers(): void {
|
|
console.log('Stopping servers...');
|
|
|
|
if (processes.frontend) {
|
|
processes.frontend.kill('SIGTERM');
|
|
processes.frontend = null;
|
|
}
|
|
|
|
if (processes.backend) {
|
|
processes.backend.kill('SIGTERM');
|
|
processes.backend = null;
|
|
}
|
|
|
|
console.log('Servers stopped');
|
|
}
|
|
|
|
export function getProcesses(): ServerProcesses {
|
|
return processes;
|
|
}
|