import * as esbuild from 'esbuild'; import { spawn } from 'node:child_process'; import { existsSync, watch } from 'node:fs'; import { mkdir, rm } from 'node:fs/promises'; import { buildOptions, outdir, writeManifest } from './build.js'; await rm('dist', { recursive: true, force: true }); await mkdir(outdir, { recursive: true }); const ctx = await esbuild.context({ ...buildOptions({ dev: true }), plugins: [ { name: 'manifest', setup(build) { build.onEnd((result) => result.metafile && writeManifest(result.metafile)); }, }, ], }); await ctx.watch(); // Restarting the server ourselves instead of node --watch-path: on Node 24 the // watch-mode parent sometimes crashes on shutdown and leaves the server running. const args = [...(existsSync('.env') ? ['--env-file=.env'] : []), 'src/server.js']; let server; function start() { server = spawn(process.execPath, args, { stdio: 'inherit', env: { ...process.env, NODE_ENV: 'development' } }); } function restart() { if (!server || server.exitCode !== null) return start(); server.once('exit', start); server.kill(); } let timer; for (const dir of ['src', 'locales']) { watch(dir, { recursive: true }, (event, file) => { if (file?.endsWith('.njk')) return; // templates are re-read on every request anyway clearTimeout(timer); timer = setTimeout(() => { console.log(`[dev] ${dir}/${file} changed, restarting`); restart(); }, 150); }); } start(); async function stop() { await ctx.dispose(); if (server?.exitCode === null) { server.once('exit', () => process.exit(0)); server.kill(); } else { process.exit(0); } } process.on('SIGINT', stop); process.on('SIGTERM', stop);