Add minimal add-in playgrounds and orchestrator

This commit is contained in:
2025-12-07 21:26:10 -06:00
parent 40bcd939ff
commit ca95d296b5
54 changed files with 13165 additions and 26764 deletions
+104
View File
@@ -0,0 +1,104 @@
#!/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); });