hard-cut batch D: enforce worker bundle isolation and CI guard
This commit is contained in:
parent
f77ee8cfd4
commit
fb0c95dfb1
4 changed files with 59 additions and 35 deletions
4
.github/workflows/playwright.yml
vendored
4
.github/workflows/playwright.yml
vendored
|
|
@ -36,6 +36,10 @@ jobs:
|
|||
weed version
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build app and enforce bundle guard
|
||||
run: |
|
||||
pnpm build
|
||||
pnpm build:bundle-guard
|
||||
- name: Verify ffprobe
|
||||
run: ffprobe -version
|
||||
- name: Install Playwright Browsers
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
import type { NextConfig } from "next";
|
||||
import path from "node:path";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
type DefinePluginCtor = new (defs: Record<string, string>) => unknown;
|
||||
const require = createRequire(import.meta.url);
|
||||
const { DefinePlugin } = require('webpack') as { DefinePlugin: DefinePluginCtor };
|
||||
|
||||
const securityHeaders = [
|
||||
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
||||
|
|
@ -21,11 +15,7 @@ const securityHeaders = [
|
|||
},
|
||||
];
|
||||
|
||||
const computeModeRaw = (process.env.COMPUTE_MODE || 'local').trim().toLowerCase();
|
||||
const computeMode = computeModeRaw === 'none' || computeModeRaw === 'worker' || computeModeRaw === 'local'
|
||||
? computeModeRaw
|
||||
: 'local';
|
||||
const bundleWorkerCompute = computeMode === 'worker';
|
||||
const bundleWorkerCompute = true;
|
||||
const serverExternalPackages = [
|
||||
'@napi-rs/canvas',
|
||||
'better-sqlite3',
|
||||
|
|
@ -48,7 +38,7 @@ const nextConfig: NextConfig = {
|
|||
canvas: '@napi-rs/canvas',
|
||||
},
|
||||
},
|
||||
transpilePackages: !bundleWorkerCompute ? ['@openreader/compute-core'] : [],
|
||||
transpilePackages: [],
|
||||
serverExternalPackages,
|
||||
outputFileTracingIncludes: {
|
||||
'/api/audiobook': [
|
||||
|
|
@ -73,35 +63,14 @@ const nextConfig: NextConfig = {
|
|||
outputFileTracingExcludes: {
|
||||
'/*': [
|
||||
'./docstore/**/*',
|
||||
...(bundleWorkerCompute
|
||||
? [
|
||||
'./node_modules/onnxruntime-node/**/*',
|
||||
'./node_modules/@huggingface/tokenizers/**/*',
|
||||
]
|
||||
: []),
|
||||
'./node_modules/onnxruntime-node/**/*',
|
||||
'./node_modules/@huggingface/tokenizers/**/*',
|
||||
],
|
||||
},
|
||||
webpack: (config, { isServer }) => {
|
||||
if (isServer && bundleWorkerCompute) {
|
||||
config.plugins = config.plugins || [];
|
||||
config.plugins.push(
|
||||
new DefinePlugin({
|
||||
__OPENREADER_COMPUTE_MODE__: JSON.stringify('worker'),
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (isServer && bundleWorkerCompute) {
|
||||
const workerComputeEntry = path.resolve(__dirname, 'src/lib/server/compute/index.worker.ts');
|
||||
const computeIndexTs = path.resolve(__dirname, 'src/lib/server/compute/index.ts');
|
||||
const computeIndexNoExt = path.resolve(__dirname, 'src/lib/server/compute/index');
|
||||
const computeDir = path.resolve(__dirname, 'src/lib/server/compute');
|
||||
config.resolve.alias = {
|
||||
...(config.resolve.alias || {}),
|
||||
'@/lib/server/compute$': workerComputeEntry,
|
||||
'@/lib/server/compute/index$': workerComputeEntry,
|
||||
[`${computeIndexTs}$`]: workerComputeEntry,
|
||||
[`${computeIndexNoExt}$`]: workerComputeEntry,
|
||||
[`${computeDir}$`]: workerComputeEntry,
|
||||
'@openreader/compute-core/local-runtime$': false,
|
||||
'onnxruntime-node$': false,
|
||||
'@huggingface/tokenizers$': false,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"dev": "node scripts/openreader-entrypoint.mjs -- pnpm dev:raw",
|
||||
"dev:raw": "next dev --turbopack -p 3003",
|
||||
"build": "next build",
|
||||
"build:bundle-guard": "node scripts/check-next-server-bundle.mjs",
|
||||
"start": "node scripts/openreader-entrypoint.mjs -- pnpm start:raw",
|
||||
"start:raw": "next start -p 3003",
|
||||
"lint": "next lint",
|
||||
|
|
|
|||
50
scripts/check-next-server-bundle.mjs
Normal file
50
scripts/check-next-server-bundle.mjs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const root = process.cwd();
|
||||
const serverDir = path.join(root, '.next', 'server');
|
||||
|
||||
if (!fs.existsSync(serverDir)) {
|
||||
console.error('[bundle-guard] Missing .next/server. Run `pnpm build` first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const forbidden = [
|
||||
'onnxruntime-node',
|
||||
'@huggingface/tokenizers',
|
||||
'@openreader/compute-core/local-runtime',
|
||||
];
|
||||
|
||||
const includeExt = new Set(['.js', '.mjs', '.cjs']);
|
||||
const failures = [];
|
||||
|
||||
function walk(dir) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walk(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile()) continue;
|
||||
const ext = path.extname(entry.name).toLowerCase();
|
||||
if (!includeExt.has(ext)) continue;
|
||||
const text = fs.readFileSync(fullPath, 'utf8');
|
||||
for (const pattern of forbidden) {
|
||||
if (text.includes(pattern)) {
|
||||
failures.push({ file: fullPath, pattern });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(serverDir);
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error('[bundle-guard] Forbidden compute deps detected in Next server bundle:');
|
||||
for (const failure of failures) {
|
||||
console.error(`- ${failure.pattern} in ${path.relative(root, failure.file)}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.info('[bundle-guard] OK: no forbidden compute deps in .next/server');
|
||||
Loading…
Reference in a new issue