diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index bd28673..f266b8b 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -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 diff --git a/next.config.ts b/next.config.ts index 7beea83..b2952b3 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,10 +1,4 @@ import type { NextConfig } from "next"; -import path from "node:path"; -import { createRequire } from "node:module"; - -type DefinePluginCtor = new (defs: Record) => 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, diff --git a/package.json b/package.json index 838d117..20f37f1 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/check-next-server-bundle.mjs b/scripts/check-next-server-bundle.mjs new file mode 100644 index 0000000..9621cb4 --- /dev/null +++ b/scripts/check-next-server-bundle.mjs @@ -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');