Skip to content

Commit ee5c711

Browse files
Build: Address latest review feedback for robustness and CI stability
- Exit explicitly after one-off watch closes; set exit code on errors - Guard os.cpus() with try/catch; default cpuCount to 1 - Leave one CPU core free by default for thread-loader workers - Add fallback to if import fails - Close webpack compiler after production run completes - Version filesystem cache with env-affecting options (thread/worker/cache/prod) - Harden zip creation with error handling and close event awaiting These changes reduce chances of hung processes, stale/incorrect caches, resource leaks, and unnoticed zip errors, improving reliability in local and CI builds.
1 parent 78d7999 commit ee5c711

File tree

1 file changed

+62
-12
lines changed

1 file changed

+62
-12
lines changed

build.mjs

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,14 @@ if (cacheCompressionEnv != null) {
3232
cacheCompressionOption = false
3333
}
3434
}
35-
const cpuCountRaw = os.cpus()?.length
36-
const cpuCount = Number.isInteger(cpuCountRaw) && cpuCountRaw > 0 ? cpuCountRaw : 1
35+
let cpuCount = 1
36+
try {
37+
const cpuInfo = os.cpus && os.cpus()
38+
const len = Array.isArray(cpuInfo) ? cpuInfo.length : 0
39+
cpuCount = Number.isInteger(len) && len > 0 ? len : 1
40+
} catch {
41+
cpuCount = 1
42+
}
3743
const rawWorkers = process.env.BUILD_THREAD_WORKERS
3844
? parseInt(process.env.BUILD_THREAD_WORKERS, 10)
3945
: undefined
@@ -78,8 +84,16 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, sourceBuil
7884
]
7985
if (isWithoutKatex) shared.push('./src/components')
8086

81-
// Use the default export from sass-embedded; sass-loader expects the implementation object
82-
const { default: sassImpl } = await import('sass-embedded')
87+
// Use the default export from sass-embedded; sass-loader expects the implementation object.
88+
// Fallback to `sass` if sass-embedded is unavailable.
89+
let sassImpl
90+
try {
91+
const mod = await import('sass-embedded')
92+
sassImpl = mod.default || mod
93+
} catch (e) {
94+
const mod = await import('sass')
95+
sassImpl = mod.default || mod
96+
}
8397

8498
const variantId = [
8599
isWithoutKatex ? 'no-katex' : 'with-katex',
@@ -117,6 +131,12 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, sourceBuil
117131
cache: {
118132
type: 'filesystem',
119133
name: `webpack-${variantId}`,
134+
version: JSON.stringify({
135+
THREAD: enableThread,
136+
WORKERS: threadWorkers,
137+
CACHE_COMP: cacheCompressionOption ?? false,
138+
PROD: isProduction,
139+
}),
120140
// default none; override via BUILD_CACHE_COMPRESSION=gzip|brotli
121141
compression: cacheCompressionOption ?? false,
122142
buildDependencies: {
@@ -341,27 +361,57 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, sourceBuil
341361
],
342362
},
343363
})
344-
if (isProduction) compiler.run(callback)
345-
else {
364+
if (isProduction) {
365+
// Ensure compiler is properly closed after production runs
366+
compiler.run((err, stats) => {
367+
const finishClose = () =>
368+
compiler.close((closeErr) => {
369+
if (closeErr) console.error('Error closing compiler:', closeErr)
370+
})
371+
try {
372+
const ret = callback(err, stats)
373+
if (ret && typeof ret.then === 'function') {
374+
ret.then(finishClose, finishClose)
375+
} else {
376+
finishClose()
377+
}
378+
} catch (_) {
379+
finishClose()
380+
}
381+
})
382+
} else {
346383
const watching = compiler.watch({}, (err, stats) => {
384+
const hasErrors = !!(
385+
err ||
386+
(stats && typeof stats.hasErrors === 'function' && stats.hasErrors())
387+
)
347388
callback(err, stats)
348389
if (process.env.BUILD_WATCH_ONCE) {
349390
watching.close((closeErr) => {
350391
if (closeErr) console.error('Error closing watcher:', closeErr)
392+
// Exit explicitly to prevent hanging processes in CI
393+
// Use non-zero exit code when errors occurred
394+
process.exit(hasErrors || closeErr ? 1 : 0)
351395
})
352396
}
353397
})
354398
}
355399
}
356400

357401
async function zipFolder(dir) {
358-
const output = fs.createWriteStream(`${dir}.zip`)
359-
const archive = archiver('zip', {
360-
zlib: { level: 9 },
402+
const zipPath = `${dir}.zip`
403+
await fs.ensureDir(path.dirname(zipPath))
404+
await new Promise((resolve, reject) => {
405+
const output = fs.createWriteStream(zipPath)
406+
const archive = archiver('zip', { zlib: { level: 9 } })
407+
const onError = (err) => reject(err)
408+
output.on('error', onError)
409+
archive.on('error', onError)
410+
output.on('close', resolve)
411+
archive.pipe(output)
412+
archive.directory(dir, false)
413+
archive.finalize().catch(onError)
361414
})
362-
archive.pipe(output)
363-
archive.directory(dir, false)
364-
await archive.finalize()
365415
}
366416

367417
async function copyFiles(entryPoints, targetDir) {

0 commit comments

Comments
 (0)