managed sessions only. allow for rename/delete

This commit is contained in:
2026-01-19 19:34:58 -05:00
parent e2048d8b69
commit 313ac44337
32 changed files with 1759 additions and 331 deletions
+156
View File
@@ -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;
}