@@ -19,17 +19,43 @@ const isWatchOnce = !!process.env.BUILD_WATCH_ONCE
1919// Cache compression control: default none; allow override via env
2020const cacheCompressionEnv = process . env . BUILD_CACHE_COMPRESSION
2121let cacheCompressionOption
22- if ( cacheCompressionEnv ) {
23- const v = String ( cacheCompressionEnv ) . toLowerCase ( )
24- if ( v === '0' || v === 'false' || v === 'none' ) cacheCompressionOption = false
25- else if ( v === 'brotli' ) cacheCompressionOption = 'brotli'
26- else cacheCompressionOption = 'gzip' // treat any truthy/unknown as gzip
22+ if ( cacheCompressionEnv != null ) {
23+ const v = String ( cacheCompressionEnv ) . trim ( ) . toLowerCase ( )
24+ if ( v === '' || v === '0' || v === 'false' || v === 'none' ) {
25+ cacheCompressionOption = false
26+ } else if ( v === 'gzip' || v === 'brotli' ) {
27+ cacheCompressionOption = v
28+ } else {
29+ console . warn (
30+ `[build] Unknown BUILD_CACHE_COMPRESSION="${ cacheCompressionEnv } ", defaulting to no compression` ,
31+ )
32+ cacheCompressionOption = false
33+ }
2734}
28- const cpuCount = os . cpus ( ) ?. length || 1
29- const envWorkers = process . env . BUILD_THREAD_WORKERS
35+ const cpuCountRaw = os . cpus ( ) ?. length
36+ const cpuCount = Number . isInteger ( cpuCountRaw ) && cpuCountRaw > 0 ? cpuCountRaw : 1
37+ const rawWorkers = process . env . BUILD_THREAD_WORKERS
3038 ? parseInt ( process . env . BUILD_THREAD_WORKERS , 10 )
3139 : undefined
32- const threadWorkers = Number . isInteger ( envWorkers ) && envWorkers > 0 ? envWorkers : cpuCount
40+ let threadWorkers
41+ if ( Number . isInteger ( rawWorkers ) && rawWorkers > 0 ) {
42+ const maxWorkers = Math . max ( 1 , cpuCount )
43+ if ( rawWorkers > maxWorkers ) {
44+ console . warn (
45+ `[build] BUILD_THREAD_WORKERS=${ rawWorkers } exceeds CPU count (${ cpuCount } ); capping to ${ maxWorkers } ` ,
46+ )
47+ }
48+ threadWorkers = Math . min ( rawWorkers , maxWorkers )
49+ } else {
50+ if ( process . env . BUILD_THREAD_WORKERS ) {
51+ console . warn (
52+ `[build] Invalid BUILD_THREAD_WORKERS="${ process . env . BUILD_THREAD_WORKERS } ", defaulting to CPU count (${ cpuCount } )` ,
53+ )
54+ }
55+ threadWorkers = cpuCount
56+ }
57+ // Thread-loader pool timeout constants
58+ const PRODUCTION_POOL_TIMEOUT_MS = 2000
3359// Enable threads by default; allow disabling via BUILD_THREAD=0
3460const enableThread = process . env . BUILD_THREAD === '0' ? false : true
3561
@@ -52,7 +78,16 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, sourceBuil
5278 ]
5379 if ( isWithoutKatex ) shared . push ( './src/components' )
5480
55- const sassImpl = await import ( 'sass-embedded' )
81+ // Use the default export from sass-embedded; sass-loader expects the implementation object
82+ const { default : sassImpl } = await import ( 'sass-embedded' )
83+
84+ const variantId = [
85+ isWithoutKatex ? 'no-katex' : 'with-katex' ,
86+ isWithoutTiktoken ? 'no-tiktoken' : 'with-tiktoken' ,
87+ minimal ? 'minimal' : 'full' ,
88+ sourceBuildDir || outdir ,
89+ isProduction ? 'prod' : 'dev' ,
90+ ] . join ( '|' )
5691
5792 const compiler = webpack ( {
5893 entry : {
@@ -81,6 +116,7 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, sourceBuil
81116 devtool : isProduction ? false : 'cheap-module-source-map' ,
82117 cache : {
83118 type : 'filesystem' ,
119+ name : `webpack-${ variantId } ` ,
84120 // default none; override via BUILD_CACHE_COMPRESSION=gzip|brotli
85121 compression : cacheCompressionOption ?? false ,
86122 buildDependencies : {
@@ -164,7 +200,11 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, sourceBuil
164200 options : {
165201 workers : threadWorkers ,
166202 // Ensure one-off dev build exits quickly
167- poolTimeout : isProduction ? 2000 : isWatchOnce ? 0 : Infinity ,
203+ poolTimeout : isProduction
204+ ? PRODUCTION_POOL_TIMEOUT_MS
205+ : isWatchOnce
206+ ? 0
207+ : Infinity ,
168208 } ,
169209 } ,
170210 ]
@@ -174,13 +214,9 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, sourceBuil
174214 options : {
175215 cacheDirectory : true ,
176216 cacheCompression : false ,
177- presets : [
178- '@babel/preset-env' ,
179- {
180- plugins : [ '@babel/plugin-transform-runtime' ] ,
181- } ,
182- ] ,
217+ presets : [ '@babel/preset-env' ] ,
183218 plugins : [
219+ [ '@babel/plugin-transform-runtime' ] ,
184220 [
185221 '@babel/plugin-transform-react-jsx' ,
186222 {
@@ -309,7 +345,11 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, sourceBuil
309345 else {
310346 const watching = compiler . watch ( { } , ( err , stats ) => {
311347 callback ( err , stats )
312- if ( process . env . BUILD_WATCH_ONCE ) watching . close ( ( ) => { } )
348+ if ( process . env . BUILD_WATCH_ONCE ) {
349+ watching . close ( ( closeErr ) => {
350+ if ( closeErr ) console . error ( 'Error closing watcher:' , closeErr )
351+ } )
352+ }
313353 } )
314354 }
315355}
@@ -325,11 +365,23 @@ async function zipFolder(dir) {
325365}
326366
327367async function copyFiles ( entryPoints , targetDir ) {
328- if ( ! fs . existsSync ( targetDir ) ) await fs . mkdir ( targetDir )
368+ if ( ! fs . existsSync ( targetDir ) ) await fs . mkdir ( targetDir , { recursive : true } )
329369 await Promise . all (
330370 entryPoints . map ( async ( entryPoint ) => {
331- if ( await fs . pathExists ( entryPoint . src ) ) {
332- await fs . copy ( entryPoint . src , `${ targetDir } /${ entryPoint . dst } ` )
371+ try {
372+ if ( await fs . pathExists ( entryPoint . src ) ) {
373+ await fs . copy ( entryPoint . src , `${ targetDir } /${ entryPoint . dst } ` )
374+ } else {
375+ // Skip missing CSS in development (placeholders will be created later)
376+ const isCss = String ( entryPoint . dst ) . endsWith ( '.css' )
377+ if ( ! isProduction || isCss ) {
378+ if ( ! isProduction && isCss ) return
379+ }
380+ throw new Error ( `Missing build artifact: ${ entryPoint . src } ` )
381+ }
382+ } catch ( e ) {
383+ console . error ( 'Copy failed:' , entryPoint , e )
384+ throw e
333385 }
334386 } ) ,
335387 )
0 commit comments