dpl/scripts/tutorials/watch.mjs

76 lines
2.8 KiB
JavaScript

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)