managed sessions only. allow for rename/delete
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user