2025-03-21 19:59:16 +07:00
|
|
|
import watcher from '@parcel/watcher'
|
2025-07-11 17:59:36 +07:00
|
|
|
import { debounce } from '@revenge-mod/utils/callback'
|
2025-03-21 19:59:16 +07:00
|
|
|
import chalk from 'chalk'
|
2026-07-26 15:36:54 +07:00
|
|
|
import { readFile } from 'fs/promises'
|
|
|
|
|
import { createServer } from 'http'
|
2025-06-03 16:24:58 +07:00
|
|
|
import os from 'os'
|
|
|
|
|
import { join } from 'path'
|
2026-07-26 15:36:54 +07:00
|
|
|
import { crc32 } from 'zlib'
|
|
|
|
|
import pkg from '../package.json' with { type: 'json' }
|
2025-05-05 21:17:42 +07:00
|
|
|
import build from './build'
|
2025-04-15 14:50:14 +07:00
|
|
|
|
2025-05-04 16:48:33 +07:00
|
|
|
const prod = process.argv.includes('--prod')
|
2025-04-15 14:50:14 +07:00
|
|
|
const lanHost = process.argv.includes('--lan')
|
|
|
|
|
|
2026-07-26 15:36:54 +07:00
|
|
|
const Port = 4040
|
|
|
|
|
const BundlePath = './dist/revenge.bundle'
|
|
|
|
|
|
2025-04-15 14:50:14 +07:00
|
|
|
console.info(chalk.redBright(`\nRevenge ${chalk.white(`v${pkg.version}`)}\n`))
|
2025-03-21 19:59:16 +07:00
|
|
|
|
|
|
|
|
const cwdify = (path: string) => join(process.cwd(), path)
|
2025-06-10 17:45:38 +07:00
|
|
|
const Sources = [
|
|
|
|
|
'src',
|
|
|
|
|
'lib',
|
|
|
|
|
'plugins',
|
2025-06-03 16:24:58 +07:00
|
|
|
'shims',
|
|
|
|
|
'package.json',
|
|
|
|
|
'bun.lock',
|
|
|
|
|
'tsconfig.json',
|
|
|
|
|
].map(cwdify)
|
2025-06-10 17:45:38 +07:00
|
|
|
const ExitTriggers = ['scripts'].map(cwdify)
|
2025-03-21 19:59:16 +07:00
|
|
|
|
2025-07-04 22:15:03 +07:00
|
|
|
const debouncedBuild = debounce(
|
|
|
|
|
() => ((needRebuild = false), build(!prod)),
|
2025-06-03 16:24:58 +07:00
|
|
|
250,
|
|
|
|
|
)
|
2025-03-21 19:59:16 +07:00
|
|
|
|
2025-03-29 23:30:25 +07:00
|
|
|
let needRebuild = true
|
|
|
|
|
|
2025-04-15 14:50:14 +07:00
|
|
|
watcher.subscribe(process.cwd(), (err, events) => {
|
2025-03-21 19:59:16 +07:00
|
|
|
if (err) return console.error(err)
|
|
|
|
|
|
|
|
|
|
if (events.some(it => ExitTriggers.some(se => it.path.startsWith(se)))) {
|
2025-06-10 17:45:38 +07:00
|
|
|
console.error(chalk.redBright('\u26A0 Scripts has changed, exiting!'))
|
2025-03-21 19:59:16 +07:00
|
|
|
process.exit()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-03 16:24:58 +07:00
|
|
|
needRebuild ||= events.some(it =>
|
|
|
|
|
Sources.some(src => it.path.startsWith(src)),
|
|
|
|
|
)
|
2025-03-21 19:59:16 +07:00
|
|
|
})
|
|
|
|
|
|
2026-07-26 15:36:54 +07:00
|
|
|
const server = createServer(async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
if (needRebuild) await debouncedBuild()
|
|
|
|
|
console.debug(
|
|
|
|
|
chalk.gray(
|
|
|
|
|
`\u{1F79B} Receiving request from ${req.socket.remoteAddress}`,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const bundle = await readFile(BundlePath).catch(() => null)
|
|
|
|
|
if (!bundle)
|
|
|
|
|
throw new Error('Could not serve the bundle! No file found.')
|
|
|
|
|
|
|
|
|
|
const hash = crc32(bundle).toString(16)
|
|
|
|
|
|
|
|
|
|
if (req.headers['if-none-match'] === hash) {
|
2025-06-10 17:45:38 +07:00
|
|
|
console.debug(
|
2026-07-26 15:36:54 +07:00
|
|
|
chalk.gray('\u{1F4BE} ETag matched, responding with 304'),
|
2025-06-10 17:45:38 +07:00
|
|
|
)
|
|
|
|
|
|
2026-07-26 15:36:54 +07:00
|
|
|
res.writeHead(304)
|
|
|
|
|
res.end()
|
|
|
|
|
return
|
2025-06-10 17:45:38 +07:00
|
|
|
}
|
2026-07-26 15:36:54 +07:00
|
|
|
|
|
|
|
|
res.writeHead(200, {
|
|
|
|
|
ETag: hash,
|
|
|
|
|
'Content-Length': bundle.byteLength,
|
|
|
|
|
})
|
|
|
|
|
res.end(bundle)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e)
|
|
|
|
|
|
|
|
|
|
res.writeHead(500)
|
|
|
|
|
res.end('Build failed. Check console for details.')
|
|
|
|
|
}
|
2025-03-21 19:59:16 +07:00
|
|
|
})
|
|
|
|
|
|
2026-07-26 15:36:54 +07:00
|
|
|
server.listen(Port, lanHost ? '0.0.0.0' : '127.0.0.1')
|
|
|
|
|
|
2025-04-15 14:50:14 +07:00
|
|
|
if (lanHost) console.info(chalk.gray('\u24D8 Listening on all interfaces...'))
|
2025-06-03 16:24:58 +07:00
|
|
|
else
|
|
|
|
|
console.info(
|
|
|
|
|
chalk.gray(
|
|
|
|
|
'\u24D8 Listening locally... Use --lan to listen on all interfaces',
|
|
|
|
|
),
|
|
|
|
|
)
|
2025-04-15 14:50:14 +07:00
|
|
|
|
2025-05-04 16:48:33 +07:00
|
|
|
if (!prod) console.info(chalk.gray('\u24D8 Use --prod to build for production'))
|
|
|
|
|
else console.info(chalk.gray('\u24D8 Building for production...'))
|
|
|
|
|
|
2026-07-26 15:36:54 +07:00
|
|
|
console.info(chalk.cyanBright(`\u24D8 Serving on port ${Port}`))
|
2025-04-15 14:50:14 +07:00
|
|
|
console.info(chalk.gray('\u24D8 Accessible on:'))
|
|
|
|
|
|
|
|
|
|
for (const int of Object.values(os.networkInterfaces()))
|
|
|
|
|
if (int)
|
|
|
|
|
for (const det of int) {
|
|
|
|
|
if (det.family !== 'IPv4' || (!det.internal && !lanHost)) continue
|
2026-07-26 15:36:54 +07:00
|
|
|
console.info(chalk.gray(`- http://${det.address}:${Port}`))
|
2025-04-15 14:50:14 +07:00
|
|
|
}
|