/** * Bring an instance up: clicker dependencies, PM2 processes, then a warm-up. * * Run by the "at log on" scheduled task. Safe to run by hand at any time. * * The warm-up is not cosmetic. getClients() in lib/clients.ts is lazily * bootstrapped — Tradovate clients, the contract resolver, the reporter and the * persisted-schedule resume all start inside it — so until something makes an * HTTP request the process sits idle and none of that happens. */ import { spawnSync } from 'node:child_process'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { ensurePythonDeps } from './ensure-python-deps.mjs'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const PORT = process.env.AUTOFIRMER_PORT ?? '3000'; const BASE = `http://127.0.0.1:${PORT}`; const log = (msg) => console.log(`[start-all] ${msg}`); function run(cmd, args) { return spawnSync(cmd, args, { cwd: ROOT, encoding: 'utf8', stdio: 'inherit', shell: process.platform === 'win32', }); } async function waitForApp(timeoutMs = 120_000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { try { const res = await fetch(`${BASE}/api/auto-trade`, { signal: AbortSignal.timeout(5_000) }); if (res.ok) return true; } catch { /* not up yet */ } await new Promise((r) => setTimeout(r, 2_000)); } return false; } // ── clicker dependencies (non-fatal) ──────────────────────────────────────── ensurePythonDeps({ log }); // ── PM2 ───────────────────────────────────────────────────────────────────── log('restoring PM2 processes'); if (run('pm2', ['resurrect']).status !== 0) { log('pm2 resurrect failed — is PM2 installed and has `pm2 save` been run?'); process.exit(1); } // ── warm-up ───────────────────────────────────────────────────────────────── log('waiting for the dashboard to answer'); if (!(await waitForApp())) { log(`dashboard did not come up on ${BASE} within 120s — check \`pm2 logs autofirmer\``); process.exit(1); } log('warming up (this is what triggers the client bootstrap)'); try { await fetch(`${BASE}/api/state`, { signal: AbortSignal.timeout(60_000) }); } catch (err) { log(`warm-up request failed: ${err.message}`); } try { const status = await (await fetch(`${BASE}/api/auto-trade`)).json(); log(status.running ? `scheduler resumed — ${status.action} ${status.symbol}` : 'scheduler is stopped'); } catch { /* non-critical */ } log('up');