import { watch } from 'node:fs' import http from 'node:http' import path from 'node:path' import { fileURLToPath } from 'node:url' import { execFile } from 'node:child_process' import { promisify } from 'node:util' const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..') const run = promisify(execFile) const clients = new Set() const allowedOrigins = new Set(['http://127.0.0.1:8587', 'http://localhost:8587']) let revision = Date.now().toString() let dirty = false let building = false let timer function send(response, event) { response.write(`event: ${event}\ndata: ${JSON.stringify({ revision })}\n\n`) } async function build() { if (building) { dirty = true; return } building = true do { dirty = false try { const { stdout } = await run(process.execPath, ['scripts/tutorials/build.mjs', '--live-reload'], { cwd: root }) process.stdout.write(stdout) revision = Date.now().toString() for (const response of clients) send(response, 'update') } catch (error) { console.error('Tutorial build failed; keeping the current preview.', error.stderr || error.message) } } while (dirty) building = false } const watchers = ['content/tutorials', 'resources/tutorials'].map(folder => { const watcher = watch(path.join(root, folder), { recursive: true }, (_, filename) => { if (!filename || /(^|[\/])\./.test(filename)) return clearTimeout(timer) timer = setTimeout(build, 180) }) watcher.on('error', error => console.error('Tutorial watcher error:', error.message)) return watcher }) await build() const server = http.createServer((request, response) => { if (request.url !== '/events') { response.writeHead(404).end(); return } const origin = request.headers.origin if (origin && !allowedOrigins.has(origin)) { response.writeHead(403).end(); return } response.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', ...(origin ? { 'Access-Control-Allow-Origin': origin, 'Vary': 'Origin' } : {}), }) response.flushHeaders() clients.add(response) send(response, 'ready') request.on('close', () => clients.delete(response)) }) server.on('error', error => { console.error('Live reload server:', error.message); process.exit(1) }) server.listen(8588, '127.0.0.1', () => console.log('Tutorial watch enabled: save content, images or shared styles to refresh the local preview.')) const heartbeat = setInterval(() => { for (const response of clients) response.write(': keepalive\n\n') }, 20000) function shutdown() { clearInterval(heartbeat) clearTimeout(timer) for (const watcher of watchers) watcher.close() for (const response of clients) response.end() server.close(() => process.exit(0)) } process.on('SIGINT', shutdown) process.on('SIGTERM', shutdown)