mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-19 05:12:16 +03:00
fix(build): copy ioredis and bcryptjs into the standalone bundle (#13352)
Both packages are only reachable through code paths the standalone tracer never follows, so they get silently dropped from the built node_modules/: - ioredis is a deliberately lazy dependency (#6559 in rateLimiter.ts) — reached only via a runtime `await import("ioredis")` in rateLimiter.ts, warmupScheduler/circuitBreakerFactory.ts and quota/redisQuotaStore.ts, never through a static top-level import. Any self-hosted deployment that sets REDIS_URL crashes on first use with "Cannot find module 'ioredis'". - bcryptjs is statically imported by src/lib/auth/managementPassword.ts, so the main server bundle is fine (Next inlines the small pure-JS package into the compiled chunk). bin/cli/settings-store.mjs (the `omniroute reset-password` CLI) is a separate, unbundled entrypoint that needs the real package physically present in node_modules/ — nothing else requires it as a loose runtime dependency, so it was never copied. `node bin/reset-password.mjs --password-stdin` failed with "Cannot find package 'bcryptjs'" (ERR_MODULE_NOT_FOUND) on an otherwise healthy production deployment. Both reproduced on a real self-hosted Docker deployment (v3.8.49/51). Adds the two entries to EXTRA_MODULE_ENTRIES (the single source of truth cited in the Dockerfile) and extends the sync/async parity test with fixtures + assertions for both.
This commit is contained in:
@@ -163,6 +163,43 @@ const EXTRA_MODULE_ENTRIES = [
|
||||
dest: ["node_modules", "pino-pretty"],
|
||||
},
|
||||
{ label: "split2", src: ["node_modules", "split2"], dest: ["node_modules", "split2"] },
|
||||
{
|
||||
// ioredis is a deliberately LAZY dependency (Redis is optional — see the
|
||||
// #6559 comment in src/shared/utils/rateLimiter.ts) — reached only via a
|
||||
// runtime `await import("ioredis")` in rateLimiter.ts,
|
||||
// warmupScheduler/circuitBreakerFactory.ts and quota/redisQuotaStore.ts,
|
||||
// never through a static top-level import. The standalone tracer only
|
||||
// follows statically-analyzable imports, so it never sees these call
|
||||
// sites and drops ioredis from node_modules/ entirely. Any self-hosted
|
||||
// deployment that actually sets REDIS_URL crashes the first time it
|
||||
// reaches one of those call sites with "Cannot find module 'ioredis'" —
|
||||
// reproduced on a production Docker deployment (REDIS_URL configured,
|
||||
// v3.8.49) where the standalone image shipped ioredis/package.json but
|
||||
// none of its own dependencies or built/ output.
|
||||
label: "ioredis (dynamic import — #6559)",
|
||||
src: ["node_modules", "ioredis"],
|
||||
dest: ["node_modules", "ioredis"],
|
||||
},
|
||||
{
|
||||
// bcryptjs IS statically imported by src/lib/auth/managementPassword.ts,
|
||||
// so the main server bundle is fine — Next's server compiler inlines the
|
||||
// small pure-JS package directly into the compiled route chunk instead of
|
||||
// leaving it as an external node_modules dependency. bin/cli/settings-
|
||||
// store.mjs (the `omniroute reset-password` / bin/reset-password.mjs
|
||||
// CLI, used to recover a lost dashboard password) is a separate,
|
||||
// unbundled entrypoint that does a plain runtime `import bcrypt from
|
||||
// "bcryptjs"` and needs the real package physically present in
|
||||
// node_modules/ — which nothing else requires as a loose runtime
|
||||
// dependency, so it is never copied. Reproduced on a production
|
||||
// deployment: `node bin/reset-password.mjs --password-stdin` failed with
|
||||
// "Cannot find package 'bcryptjs' imported from
|
||||
// /app/bin/cli/settings-store.mjs" (ERR_MODULE_NOT_FOUND) even though the
|
||||
// same container's dashboard login (which also depends on bcryptjs) was
|
||||
// working normally.
|
||||
label: "bcryptjs (bin/cli/settings-store.mjs — reset-password CLI)",
|
||||
src: ["node_modules", "bcryptjs"],
|
||||
dest: ["node_modules", "bcryptjs"],
|
||||
},
|
||||
{ label: "migrations", src: ["src", "lib", "db", "migrations"], dest: ["migrations"] },
|
||||
{ label: "MITM server", src: ["src", "mitm", "server.cjs"], dest: ["src", "mitm", "server.cjs"] },
|
||||
{
|
||||
|
||||
@@ -40,6 +40,10 @@ function seedSidecarSources(root: string) {
|
||||
"node_modules/pino-abstract-transport/index.js",
|
||||
"node_modules/pino-pretty/index.js",
|
||||
"node_modules/split2/index.js",
|
||||
"node_modules/ioredis/package.json",
|
||||
"node_modules/ioredis/built/index.js",
|
||||
"node_modules/bcryptjs/package.json",
|
||||
"node_modules/bcryptjs/index.js",
|
||||
"node_modules/playwright-core/index.js",
|
||||
"node_modules/sql.js/package.json",
|
||||
"node_modules/sql.js/dist/sql-wasm.js",
|
||||
@@ -173,6 +177,19 @@ test("async and sync sidecar copy paths produce identical bundle trees", async (
|
||||
]) {
|
||||
assert.ok(asyncTree.includes(sqlJsFile), `sql.js runtime file copied: ${sqlJsFile}`);
|
||||
}
|
||||
// #6559 / reset-password CLI: both are only reachable at runtime (a dynamic
|
||||
// `import("ioredis")`, or a separate unbundled bin/cli/ entrypoint for
|
||||
// bcryptjs), so the standalone tracer never picks them up on its own —
|
||||
// regression guard for the two "Cannot find module/package" crashes
|
||||
// reproduced on a real self-hosted deployment.
|
||||
for (const runtimeOnlyFile of [
|
||||
"node_modules/ioredis/package.json",
|
||||
"node_modules/ioredis/built/index.js",
|
||||
"node_modules/bcryptjs/package.json",
|
||||
"node_modules/bcryptjs/index.js",
|
||||
]) {
|
||||
assert.ok(asyncTree.includes(runtimeOnlyFile), `runtime-only dep copied: ${runtimeOnlyFile}`);
|
||||
}
|
||||
fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user