76 lines
2.8 KiB
JavaScript
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)
|