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:
Felipe Britto
2026-09-18 11:31:10 -03:00
committed by GitHub
parent 53a147c7ca
commit 138ccf2d04
2 changed files with 54 additions and 0 deletions

View File

@@ -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"] },
{