fix(scripts): make compatible with all Node runtimes

This commit is contained in:
PalmDevs 2026-07-26 15:36:54 +07:00
parent ee8d5620a0
commit f67c8db768
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
5 changed files with 105 additions and 64 deletions

10
scripts/_shared.ts Normal file
View file

@ -0,0 +1,10 @@
import { stat } from 'fs/promises'
export const exists = async (path: string) => {
try {
await stat(path)
return true
} catch {
return false
}
}

View file

@ -1,19 +1,23 @@
import { $, main } from 'bun'
import chalk from 'chalk'
import { exists, mkdir, readdir, rm, writeFile } from 'fs/promises'
import { parse } from 'path'
import { execSync } from 'child_process'
import { mkdir, readdir, rm, writeFile } from 'fs/promises'
import { dirname, parse } from 'path'
import { rolldown } from 'rolldown'
import { importGlobPlugin } from 'rolldown/experimental'
import pkg from '../package.json'
import { fileURLToPath } from 'url'
import pkg from '../package.json' with { type: 'json' }
import { exists } from './_shared'
import asRequire from './plugins/as-require'
import hermesSwcPlugin from './plugins/hermes-swc'
import hermesCPlugin from './plugins/hermesc'
import importDefer from './plugins/import-defer'
import shimAliases from './plugins/shim-aliases'
const ShimsDir = `${import.meta.dir}/../shims`
const AssetsDir = `${import.meta.dir}/../src/assets`
const GeneratedAssetsDir = `${import.meta.dir}/../dist/assets/generated`
const __dirname = dirname(fileURLToPath(import.meta.url))
const ShimsDir = `${__dirname}/../shims`
const AssetsDir = `${__dirname}/../src/assets`
const GeneratedAssetsDir = `${__dirname}/../dist/assets/generated`
await rm(GeneratedAssetsDir, { recursive: true, force: true })
.then(() =>
@ -25,7 +29,7 @@ const Dev =
process.argv.includes('--dev') || process.env.NODE_ENV === 'development'
// If this file is being run directly, build the project
if (main === import.meta.filename) build()
if (import.meta.main) build()
export default async function build(dev = Dev, log = true) {
const start = performance.now()
@ -35,7 +39,10 @@ export default async function build(dev = Dev, log = true) {
if (log) console.info(chalk.cyanBright('\u{1F5BB} Assets generated'))
if (log) console.info(chalk.gray('\u{1F5CE} Compiling JS...'))
const COMMIT = (await $`git rev-parse HEAD`.text()).trim().substring(0, 7)
const COMMIT = execSync('git rev-parse HEAD')
.toString()
.trim()
.substring(0, 7)
const REPO = 'revenge-mod/revenge-bundle-next'
const bundle = await rolldown({
@ -67,7 +74,9 @@ export default async function build(dev = Dev, log = true) {
__BUILD_VERSION__: JSON.stringify(pkg.version),
__BUILD_COMMIT__: JSON.stringify(COMMIT),
__BUILD_BRANCH__: JSON.stringify(
(await $`git rev-parse --abbrev-ref HEAD`.text()).trim(),
execSync('git rev-parse --abbrev-ref HEAD')
.toString()
.trim(),
),
__DEV__: String(dev),
@ -79,6 +88,11 @@ export default async function build(dev = Dev, log = true) {
},
},
tsconfig: 'tsconfig.json',
// propertyReadSideEffects: false works around a Rolldown bug where
// property reads on tree-shaken import bindings are kept as "side
// effects" while their imports are removed, producing free-variable
// references (e.g. `FibonacciHeap.MinFibonacciHeap` from mnemonist's
// barrel) that throw ReferenceError at runtime.
treeshake: true,
moduleTypes: {
'.webp': 'dataurl',

View file

@ -1,14 +1,20 @@
import watcher from '@parcel/watcher'
import { debounce } from '@revenge-mod/utils/callback'
import chalk from 'chalk'
import { readFile } from 'fs/promises'
import { createServer } from 'http'
import os from 'os'
import { join } from 'path'
import pkg from '../package.json'
import { crc32 } from 'zlib'
import pkg from '../package.json' with { type: 'json' }
import build from './build'
const prod = process.argv.includes('--prod')
const lanHost = process.argv.includes('--lan')
const Port = 4040
const BundlePath = './dist/revenge.bundle'
console.info(chalk.redBright(`\nRevenge ${chalk.white(`v${pkg.version}`)}\n`))
const cwdify = (path: string) => join(process.cwd(), path)
@ -43,51 +49,46 @@ watcher.subscribe(process.cwd(), (err, events) => {
)
})
const server = Bun.serve({
hostname: lanHost ? '0.0.0.0' : '127.0.0.1',
async fetch(req, srv) {
try {
if (needRebuild) await debouncedBuild()
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) {
console.debug(
chalk.gray(
`\u{1F79B} Receiving request from ${srv.requestIP(req)!.address}`,
),
chalk.gray('\u{1F4BE} ETag matched, responding with 304'),
)
const file = Bun.file('./dist/revenge.bundle')
if (await file.exists()) {
const hash = Bun.hash
.crc32(await file.arrayBuffer())
.toString(16)
if (req.headers.get('If-None-Match') === hash) {
console.debug(
chalk.gray(
'\u{1F4BE} ETag matched, responding with 304',
),
)
return new Response(null, {
status: 304,
})
}
return new Response(file, {
status: 200,
headers: {
ETag: hash,
},
})
} else throw new Error('Could not serve the bundle! No file found.')
} catch (e) {
console.error(e)
throw new Error('Build failed. Check console for details.')
res.writeHead(304)
res.end()
return
}
},
port: 4040,
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.')
}
})
server.listen(Port, lanHost ? '0.0.0.0' : '127.0.0.1')
if (lanHost) console.info(chalk.gray('\u24D8 Listening on all interfaces...'))
else
console.info(
@ -99,12 +100,12 @@ else
if (!prod) console.info(chalk.gray('\u24D8 Use --prod to build for production'))
else console.info(chalk.gray('\u24D8 Building for production...'))
console.info(chalk.cyanBright(`\u24D8 Serving on port ${server.port}`))
console.info(chalk.cyanBright(`\u24D8 Serving on port ${Port}`))
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
console.info(chalk.gray(`- http://${det.address}:${server.port}`))
console.info(chalk.gray(`- http://${det.address}:${Port}`))
}

View file

@ -8,8 +8,10 @@ import type { RolldownPlugin } from 'rolldown'
* - Dynamic side-effect imports: `import('module')` and `await import('module')`
*/
export default function asRequire() {
const name = 'as-require'
return {
name: 'as-require',
name,
transform(code, id) {
if (!code.includes('// @as-require')) return null
@ -45,7 +47,7 @@ export default function asRequire() {
}
this.warn(
`[${this.pluginName}] Warning: Could not transform statement in ${id}:\n${statement}\n` +
`[${name}] Warning: Could not transform statement in ${id}:\n${statement}\n` +
`The line after the comment must be a side-effect import or a dynamic import with .then().`,
)

View file

@ -1,3 +1,4 @@
import { spawnSync } from 'child_process'
import { existsSync, readFileSync } from 'fs'
import type { OutputChunk, RolldownPlugin } from 'rolldown'
@ -37,27 +38,40 @@ export default function hermesCPlugin({
const file = bundle['revenge.js'] as OutputChunk
if (!file) throw new Error('No code to compile')
const cmdlist = [binPath, '-emit-binary', ...(flags ?? [])]
const argList = ['-emit-binary', ...(flags ?? [])]
const cmd = Bun.spawnSync<'pipe', 'pipe'>(cmdlist, {
// @ts-expect-error: Types are incorrect, but this works
stdin: new Blob([file.code]),
stdout: 'pipe',
})
const cmd =
// TODO: Move away from Bun: https://github.com/oven-sh/bun/issues/25498
typeof Bun !== 'undefined'
? (() => {
const cmd = Bun.spawnSync([binPath, ...argList], {
// @ts-expect-error: Works but types are incorrect
stdio: [new Blob([file.code]), 'pipe'],
})
if (cmd.exitCode) {
return {
status: cmd.exitCode,
stdout: cmd.stdout,
stderr: cmd.stderr,
}
})()
: spawnSync(binPath, argList, {
stdio: ['pipe', 'pipe'],
input: file.code,
})
if (cmd.status !== 0) {
if (cmd.stderr.length)
throw new Error(
`Got error from hermesc: ${cmd.stderr.toString()}`,
)
else
throw new Error(`hermesc exited with code: ${cmd.exitCode}`)
else throw new Error(`hermesc exited with code: ${cmd.status}`)
}
const buf = cmd.stdout
if (!buf.length)
throw new Error(
`No output from hermesc. Probably a compilation error.\nTry running the command manually: ${cmdlist.join(' ')}`,
`No output from hermesc. Probably a compilation error.\nTry running the command manually: ${binPath} ${argList.join(' ')}`,
)
this.emitFile({