#!/usr/bin/env node /* start-all.js — orchestrator for local dev - Ensures the playground server (3002) and the add-in static server (3003) are running - If healthy endpoints are reachable, does not start duplicates - Streams logs from started processes and ensures clean shutdown */ const { spawn } = require('child_process'); const fetch = require('node-fetch'); const path = require('path'); const services = [ { name: 'playground', dir: path.join(__dirname, 'minimal-uploader-playground'), startCmd: ['npm', ['run','server']], healthUrl: 'http://localhost:3002/api/health' }, { name: 'addin', dir: path.join(__dirname, 'officeaddin', 'minimal-playground'), startCmd: ['npm', ['start']], // static taskpane page used as a health endpoint healthUrl: 'http://localhost:3003/taskpane.html' } ]; async function checkHealth(url, timeout = 2000) { try { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); const res = await fetch(url, { signal: controller.signal }); clearTimeout(id); return res.ok; } catch (e) { return false; } } function spawnService(svc) { const [cmd, args] = svc.startCmd; const proc = spawn(cmd, args, { cwd: svc.dir, shell: true, env: process.env }); proc.stdout.on('data', (d) => process.stdout.write(`[${svc.name}] ${d}`)); proc.stderr.on('data', (d) => process.stderr.write(`[${svc.name}] ${d}`)); proc.on('exit', (code, sig) => console.log(`[${svc.name}] exited code=${code} signal=${sig}`)); return proc; } async function startAll() { console.log('Orchestrator: checking service health...'); const started = []; for (const svc of services) { const healthy = await checkHealth(svc.healthUrl, 1500); if (healthy) { console.log(`${svc.name} is already healthy at ${svc.healthUrl}`); started.push({ svc, proc: null, already: true }); continue; } console.log(`${svc.name} not healthy — starting: ${svc.startCmd.join(' ')}`); const proc = spawnService(svc); started.push({ svc, proc, already: false }); // wait for the service to report healthy const maxWait = 30 * 1000; // 30s const start = Date.now(); let ok = false; while (Date.now() - start < maxWait) { // wait a moment await new Promise((r) => setTimeout(r, 500)); if (await checkHealth(svc.healthUrl, 1500)) { ok = true; break; } } if (!ok) { console.error(` Service ${svc.name} did not become healthy at ${svc.healthUrl} within ${maxWait/1000}s. Check its logs above.`); // Continue retrying other services — do not abort so we can still start them } else { console.log(`${svc.name} is healthy at ${svc.healthUrl}`); } } console.log('All start requests processed. Press Ctrl-C to stop (or send SIGTERM).'); // graceful cleanup process.on('SIGINT', () => shutdown(started)); process.on('SIGTERM', () => shutdown(started)); } function shutdown(started) { console.log('\nStopping orchestrator and children...'); for (const { svc, proc, already } of started) { if (proc && !proc.killed) { try { proc.kill('SIGINT'); } catch (e) { /* ignore */ } console.log(`Requested stop for ${svc.name}`); } else if (already) { console.log(`Note: ${svc.name} was already running and will not be killed by the orchestrator.`); } } process.exit(0); } startAll().catch((err) => { console.error('Orchestrator failed:', err); process.exit(1); });