1#!/usr/bin/env node 2// `buck2 run //:dev`: the dev server, as two supervised processes. 3// 4// - The jevstrudel Worker (worker/) under `wrangler dev --env dev`, on the 5// port in worker/wrangler.json: the Jev relay and the MCP hub, 6// supervised by supervise.mjs, its local D1 database migrated first 7// (migrate.mjs). 8// - Astro's dev server for the site, on http://localhost:4322/, which 9// proxies the Worker's paths to it (website/astro.config.mjs). 10// 11// When Astro exits, or this process is stopped, the Worker goes too. 12import { execFileSync, spawn } from 'node:child_process'; 13import { mkdirSync, readFileSync } from 'node:fs'; 14import { migrateLocal } from './migrate.mjs'; 15import { has, superviseWorker } from './supervise.mjs'; 16 17process.chdir(execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim()); 18 19for (const tool of ['pnpm', 'wrangler', 'setpriv']) { 20 if (!has(tool)) { 21 console.error(`dev: ${tool} is not on PATH; run inside the devshell (nix develop, or direnv)`); 22 process.exit(1); 23 } 24} 25 26const run = (cmd, args) => execFileSync(cmd, args, { stdio: 'inherit' }); 27run('pnpm', ['install', '--frozen-lockfile', '--prefer-offline']); 28// The REPL's autocompletion and reference tab read doc.json; upstream's 29// prestart regenerates it the same way. 30run('pnpm', ['run', 'jsdoc-json']); 31 32const port = JSON.parse(readFileSync('worker/wrangler.json', 'utf8')).dev.port; 33// wrangler.json's `assets` (production's ASSETS binding, which the votes 34// read the song list through) is inherited by env.dev and needs a 35// directory. Dev has no built site: Astro serves it, and the Worker reads 36// the song list from Astro (SONGS_URL), so its assets are an empty folder. 37const assets = 'worker/.wrangler/dev-assets'; 38mkdirSync(assets, { recursive: true }); 39// The Worker's local D1 (worker/.wrangler/state), migrated before it starts. 40migrateLocal({ env: 'dev' }); 41// --test-scheduled: wrangler dev fires no cron triggers by itself, so the 42// Worker's every-minute sweep of what Jev owes listeners' content 43// (worker/src/content-jobs.ts) runs on `curl localhost:4323/__scheduled`. 44const worker = superviseWorker({ 45 name: 'dev', 46 port, 47 args: ['--env', 'dev', '--assets', assets, '--test-scheduled'], 48}); 49 50const astro = spawn('setpriv', ['--pdeathsig', 'TERM', '--', 'pnpm', 'exec', 'astro', 'dev'], { 51 cwd: 'website', 52 stdio: 'inherit', 53}); 54 55function shutdown(code) { 56 worker.stop(); 57 if (astro.exitCode === null) astro.kill('SIGTERM'); 58 setTimeout(() => process.exit(code), 500).unref(); 59} 60astro.on('exit', (code) => shutdown(code ?? 1)); 61for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, () => shutdown(0));