jevstrudel.git / tools / dev / dev.mjs
dev.mjsannotateddev.mjssource61 lines · 2.6 KB · raw
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));