Skip to content

Commit 7bad746

Browse files
Build: polish dev DX and cache correctness; Docs tweaks
- Watch-once: await async callback before closing watcher/exiting - Copy external source maps in development for CSP-safe debugging - copyFiles: avoid TOCTOU by relying on fs.copy and handling ENOENT (dev CSS only) - Use fs.ensureDir for idempotent targetDir creation - Webpack filesystem cache: add lockfiles/package.json to buildDependencies - Docs: fix list indentation and clarify performance defaults wording
1 parent 4211e11 commit 7bad746

File tree

2 files changed

+41
-28
lines changed

2 files changed

+41
-28
lines changed

.github/copilot-instructions.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ Always reference these instructions first and fall back to search or bash comman
2929
- Options: `0|false|none`, `gzip` (or `brotli` if explicitly desired)
3030
- Affects only `.cache/webpack` size/speed; does not change final artifacts
3131
- BUILD_WATCH_ONCE (dev): When set, `npm run dev` runs a single build and exits (useful for timing)
32-
- BUILD_POOL_TIMEOUT: Override thread-loader production pool timeout (ms)
33-
- Default: `2000`. Increase if workers recycle too aggressively on slow machines/CI
32+
- BUILD_POOL_TIMEOUT: Override thread-loader production pool timeout (ms)
33+
- Default: `2000`. Increase if workers recycle too aggressively on slow machines/CI
3434

35-
Performance defaults: esbuild handles JS/CSS minification; in development CSS is injected via style-loader;
36-
in production CSS is extracted via MiniCssExtractPlugin; thread-loader is enabled by default in both dev and prod.
35+
Performance defaults: esbuild handles JS/CSS minification. In development, CSS is injected via style-loader;
36+
in production, CSS is extracted via MiniCssExtractPlugin. Thread-loader is enabled by default in both dev and prod.
3737

3838
### Build Output Structure
3939

build.mjs

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, sourceBuil
146146
// default none; override via BUILD_CACHE_COMPRESSION=gzip|brotli
147147
compression: cacheCompressionOption ?? false,
148148
buildDependencies: {
149-
config: [path.resolve('build.mjs')],
149+
config: [
150+
path.resolve('build.mjs'),
151+
path.resolve('package.json'),
152+
path.resolve('package-lock.json'),
153+
],
150154
},
151155
},
152156
optimization: {
@@ -391,14 +395,17 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, sourceBuil
391395
err ||
392396
(stats && typeof stats.hasErrors === 'function' && stats.hasErrors())
393397
)
394-
callback(err, stats)
398+
const ret = callback(err, stats)
395399
if (process.env.BUILD_WATCH_ONCE) {
396-
watching.close((closeErr) => {
397-
if (closeErr) console.error('Error closing watcher:', closeErr)
398-
// Exit explicitly to prevent hanging processes in CI
399-
// Use non-zero exit code when errors occurred
400-
process.exit(hasErrors || closeErr ? 1 : 0)
401-
})
400+
const finalize = () =>
401+
watching.close((closeErr) => {
402+
if (closeErr) console.error('Error closing watcher:', closeErr)
403+
// Exit explicitly to prevent hanging processes in CI
404+
// Use non-zero exit code when errors occurred
405+
process.exit(hasErrors || closeErr ? 1 : 0)
406+
})
407+
if (ret && typeof ret.then === 'function') ret.then(finalize, finalize)
408+
else finalize()
402409
}
403410
})
404411
}
@@ -421,27 +428,24 @@ async function zipFolder(dir) {
421428
}
422429

423430
async function copyFiles(entryPoints, targetDir) {
424-
if (!fs.existsSync(targetDir)) await fs.mkdir(targetDir, { recursive: true })
431+
await fs.ensureDir(targetDir)
425432
await Promise.all(
426433
entryPoints.map(async (entryPoint) => {
427434
try {
428-
if (await fs.pathExists(entryPoint.src)) {
429-
await fs.copy(entryPoint.src, `${targetDir}/${entryPoint.dst}`)
430-
} else {
431-
// Skip missing CSS in development (placeholders will be created later)
432-
const isCss = String(entryPoint.dst).endsWith('.css')
433-
if (!isProduction || isCss) {
434-
if (!isProduction && isCss) {
435-
console.log(
436-
`[build] Skipping missing CSS file: ${entryPoint.src} -> ${entryPoint.dst} (placeholder will be created)`,
437-
)
438-
return
439-
}
435+
await fs.copy(entryPoint.src, `${targetDir}/${entryPoint.dst}`)
436+
} catch (e) {
437+
const isCss = String(entryPoint.dst).endsWith('.css')
438+
if (e && e.code === 'ENOENT') {
439+
if (!isProduction && isCss) {
440+
console.log(
441+
`[build] Skipping missing CSS file: ${entryPoint.src} -> ${entryPoint.dst} (placeholder will be created)`,
442+
)
443+
return
440444
}
441-
throw new Error(`Missing build artifact: ${entryPoint.src}`)
445+
console.error('Missing build artifact:', entryPoint.src)
446+
} else {
447+
console.error('Copy failed:', entryPoint, e)
442448
}
443-
} catch (e) {
444-
console.error('Copy failed:', entryPoint, e)
445449
throw e
446450
}
447451
}),
@@ -466,6 +470,15 @@ async function finishOutput(outputDirSuffix, sourceBuildDir = outdir) {
466470

467471
{ src: `${sourceBuildDir}/IndependentPanel.js`, dst: 'IndependentPanel.js' },
468472
{ src: 'src/pages/IndependentPanel/index.html', dst: 'IndependentPanel.html' },
473+
// Dev-only: copy external source maps for CSP-safe debugging
474+
...(isProduction
475+
? []
476+
: [
477+
{ src: `${sourceBuildDir}/content-script.js.map`, dst: 'content-script.js.map' },
478+
{ src: `${sourceBuildDir}/background.js.map`, dst: 'background.js.map' },
479+
{ src: `${sourceBuildDir}/popup.js.map`, dst: 'popup.js.map' },
480+
{ src: `${sourceBuildDir}/IndependentPanel.js.map`, dst: 'IndependentPanel.js.map' },
481+
]),
469482
]
470483

471484
// chromium

0 commit comments

Comments
 (0)