# Migration Guide: 11.25.3 → 11.25.5

## Overview

| Category | Details |
|----------|---------|
| **Breaking Changes** | None |
| **New Features** | None |
| **Bugfixes** | (1) `MODULE_NOT_FOUND` at runtime for `@nestjs/*/dist/*` subpath imports — affected `node dist/main.js`, the migrate CLI, and any vendor-mode consumer. Tests passed because Vite/Vitest uses a different resolver. (#541) <br> (2) BetterAuth `Invalid trustedOrigin format: /` when running tests via Vitest with env-var-based `trustedOrigins` — the framework's own e2e config now filters Vite's auto-injected `process.env.BASE_URL='/'`. **Affects only consumer projects that build `betterAuth.trustedOrigins` or top-level `baseUrl` from `process.env.BASE_URL`** — see "BASE_URL Pitfall" below. |
| **Security** | (1) `postcss@<8.5.10` override (GHSA-qx2v-qp2m-jg93, XSS via unescaped `</style>` in CSS stringify output, transitive via `vite`). <br> (2) `axios@<1.16.0` override (extended from `<1.15.0`) covering multiple CVEs (SSRF via `NO_PROXY`, prototype pollution, header injection, CRLF injection in `form-data`, XSRF leakage, auth bypass, response tampering, no_proxy IP alias bypass, streamed upload/response bypass, `toFormData` DoS, null byte injection) — transitive via `@getbrevo/brevo` and `node-mailjet`. <br> (3) New `fast-uri@<3.1.2` override (GHSA-3vp4-mxqf-vmh4 host confusion + GHSA-q4mq-mw7v-xq57 path traversal) — transitive via `@compodoc/compodoc>@angular-devkit/*>ajv`. <br> (4) New `@babel/plugin-transform-modules-systemjs@>=7.12.0 <7.29.4` override (GHSA-7p7r-hpq8-6w8q prototype-pollution variable shadowing) — transitive via `@compodoc/compodoc>@babel/preset-env`. <br> All framework-internal — no consumer action needed. |
| **Maintenance** | Refreshed runtime + dev dependencies. Notable: `vite` 7.3.2 → **8.0.11** (major) and `vite-plugin-node` 7.0.0 → **8.0.0** (major) coupled lockstep upgrade — all 1751 framework tests pass. `mongoose` 9.4.1 → 9.6.2, `better-auth` 1.5.5 → 1.6.10, `@nestjs/apollo` & `@nestjs/graphql` 13.2.5 → 13.4.0, `@tus/*`, `mongodb` 7.1.1 → 7.2.0, `graphql` 16.13.2 → 16.14.0, plus dev tools (oxlint, oxfmt, swc, vitest, @types/node). |
| **Tooling** | `scripts/check-server-start.sh` now strips ANSI color escape sequences from the port-detection one-liner output, preventing `ERR_SOCKET_BAD_PORT` when running under workspace runners (lerna/nx). |
| **Migration Effort** | None required — update the package, run install + tests. Read "BASE_URL Pitfall" below if you build `betterAuth.trustedOrigins` or `baseUrl` from `process.env.BASE_URL` in your own `config.env.ts`. |

---

## Quick Migration

```bash
# Update package
pnpm install @lenne.tech/nest-server@11.25.5

# Verify
pnpm run build
pnpm test
```

No code changes required. All changes are backward compatible.

---

## Bug Fix: `.js` Suffix on `@nestjs/*/dist/*` Subpath Imports (#541)

### Symptoms before the fix

When the framework code was executed with **pure Node** (i.e. `node dist/main.js`, `pnpm run migrate:up`, vendor-mode consumers running their bundled core), Node aborted with:

```
Error: Cannot find module '.../node_modules/@nestjs/graphql/dist/schema-builder/storages/type-metadata.storage'
ELIFECYCLE  Command failed with exit code 1.
```

This affected:
- **npm-mode consumers**: any boot path that triggered the late `require()` in `interceptor.helper.ts:73` (e.g. when `ResponseModelInterceptor` resolves a return type via reflection at request time).
- **vendor-mode consumers**: every `pnpm run migrate:up` and every `node dist/main.js` from the second the project was integrated. Reproduces deterministically with `lt fullstack init --framework-mode vendor` and `lt fullstack convert-mode --to vendor` (see PR #541 reproducible steps).
- **Tests passed regardless** because Vite/Vitest uses an `enhanced-resolve`-based resolver that auto-appends `.js`. The bug was therefore invisible in the framework's own test suite.

### Root cause

Several `@nestjs/*` packages declare an `exports` field of the form:

```jsonc
{
  "exports": {
    ".": "./dist/index.js",
    "./*": "./*"
  }
}
```

When `exports` is present, **Node honors the pattern literally** and does NOT auto-append `.js` for matched subpaths. The bare specifier
`@nestjs/graphql/dist/schema-builder/storages/type-metadata.storage` resolves to a literal path that does not exist on disk (only `.../type-metadata.storage.js` exists). Vite/Vitest implement the older "tryExtensions" heuristic, masking the issue in tests.

### Fix

All `@nestjs/*/dist/*` subpath imports in the framework now end with `.js`:

| File | Before | After |
|---|---|---|
| `src/core/common/decorators/unified-field.decorator.ts` | `.../type-metadata.storage` | `.../type-metadata.storage.js` |
| `src/core/common/decorators/unified-field.decorator.ts` | `.../schema-object-metadata.interface` | `.../schema-object-metadata.interface.js` (now `import type`) |
| `src/core/common/helpers/interceptor.helper.ts` | `.../type-metadata.storage` (in `require()`) | `.../type-metadata.storage.js` |
| `src/core/common/interfaces/server-options.interface.ts` | `@nestjs/jwt/dist/interfaces` | `@nestjs/jwt/dist/interfaces/index.js` (now `import type`) |
| `src/core/common/interfaces/server-options.interface.ts` | `.../mongoose.health` | `.../mongoose.health.js` (now `import type`) |
| `src/core/common/interfaces/server-options.interface.ts` | `.../disk-health-options.type` | `.../disk-health-options.type.js` (now `import type`) |
| `src/core/modules/auth/guards/auth.guard.ts` | `@nestjs/passport/dist/options` | `@nestjs/passport/dist/options.js` |
| `src/core/modules/auth/guards/auth.guard.ts` | `@nestjs/passport/dist/utils/memoize.util` | `@nestjs/passport/dist/utils/memoize.util.js` |
| `src/core/modules/health-check/core-health-check.service.ts` | `.../mongoose.health` | `.../mongoose.health.js` (now `import type`) |
| `src/core/modules/health-check/core-health-check.service.ts` | `.../disk-health-options.type` | `.../disk-health-options.type.js` (now `import type`) |

Type-only imports were additionally promoted to `import type` for preventive hygiene once `verbatimModuleSyntax` is turned on.

### Action required for consumers

**None for npm-mode consumers** — `pnpm install` pulls the patched code. The next `pnpm run build` of the consumer project keeps the (already correct) generated `node_modules/@lenne.tech/nest-server/dist/...` output, but now subsequent runtime calls into those files no longer crash.

**None for vendor-mode consumers either**, but the recommended sync flow is:

```bash
# Option 1 — re-run the lt CLI vendor-update from your monorepo root
lt fullstack update

# Option 2 — manually pull the patched paths into your vendored core
#   (only the 5 files listed above need updating — see PR #541 diff)
```

Vendor-mode users who hit the original `Cannot find module ...type-metadata.storage` error will see it disappear immediately after the sync.

---

## Security: New & Extended Overrides

The following entries were added or extended in `pnpm.overrides`:

```jsonc
{
  "pnpm": {
    "overrides": {
      "axios@<1.16.0": "1.16.0",                                       // extended in 11.25.5 (was <1.15.0)
      "fast-uri@<3.1.2": "3.1.2",                                       // new in 11.25.5
      "@babel/plugin-transform-modules-systemjs@>=7.12.0 <7.29.4": "7.29.4", // new in 11.25.5
      "postcss@<8.5.10": "8.5.12"                                       // added in 11.25.4
    }
  }
}
```

| Override | Advisory / CVE | Transitive chain | Status |
|---|---|---|---|
| `axios@<1.16.0` | Multiple CVEs (SSRF via `NO_PROXY`, prototype pollution, header injection, CRLF in `form-data`, XSRF leakage, auth bypass, response tampering, no_proxy IP alias bypass, streamed upload/response bypass, `toFormData` DoS, null byte injection) | `@getbrevo/brevo`, `node-mailjet` | Extended in 11.25.5 — remove when both upstreams ship `axios>=1.16.0`. |
| `fast-uri@<3.1.2` | GHSA-3vp4-mxqf-vmh4 (host confusion), GHSA-q4mq-mw7v-xq57 (path traversal) | `@compodoc/compodoc>@angular-devkit/schematics>@angular-devkit/core>ajv` | New in 11.25.5. |
| `@babel/plugin-transform-modules-systemjs@>=7.12.0 <7.29.4` | GHSA-7p7r-hpq8-6w8q (prototype-pollution variable shadowing in generated code) | `@compodoc/compodoc>@babel/preset-env` | New in 11.25.5. |
| `postcss@<8.5.10` | GHSA-qx2v-qp2m-jg93 (XSS via unescaped `</style>`) | `vite` | Added in 11.25.4. Remove when `vite` ships with `postcss>=8.5.10` natively. |

All entries follow the project rule that override **targets must be fixed versions** (no `^`, `~`, `>=`). All are framework-internal — no consumer action needed.

---

## Bug Fix: BASE_URL Pitfall in `config.env.ts` (added in 11.25.5)

### Symptom

Tests fail in `beforeAll` with:

```
BetterAuth configuration invalid: Invalid trustedOrigin format: /
BetterAuth configuration invalid: Invalid auto-detected trustedOrigin format: /; Invalid passkey origin format: /
```

### Root cause

Vite injects `process.env.BASE_URL = '/'` (its asset base path default — see `vite/dist/node/chunks/node.js`, `env: { BASE_URL, MODE, DEV, PROD }`). This is **also true under Vitest**, because Vitest sits on Vite. As a result, any consumer config that derives URLs via:

```typescript
trustedOrigins: [process.env.BASE_URL || 'http://localhost:3000', ...] // BUG
baseUrl: process.env.BASE_URL                                          // BUG
```

ends up with `'/'` instead of the fallback under Vitest, because `'/'` is truthy. BetterAuth's URL validation then rejects `'/'` and the entire test boot path fails.

This was **never introduced by a specific Vite version** — Vite has had `BASE_URL='/'` injection for years. The bug only surfaces when consumers move from hardcoded URLs to env-var-driven URLs in their own `config.env.ts`.

### Fix in your project's `config.env.ts`

```typescript
// trustedOrigins (e.g. in your betterAuth config)
trustedOrigins: [
  process.env.BASE_URL && process.env.BASE_URL !== '/' ? process.env.BASE_URL : 'http://localhost:3000',
  process.env.APP_URL || 'http://localhost:3001',
],

// top-level baseUrl
baseUrl: process.env.BASE_URL && process.env.BASE_URL !== '/' ? process.env.BASE_URL : undefined,
```

Note: `APP_URL`, `MODE` (other than the four Vite built-ins), `DEV`, `PROD` are **not** auto-injected as `'/'`, so they don't need the same guard. Only `BASE_URL` is affected.

### Alternative (cleanest)

Use a different env var name that doesn't collide with Vite's built-ins:

```typescript
trustedOrigins: [process.env.API_URL || 'http://localhost:3000', process.env.APP_URL || 'http://localhost:3001'],
```

Or use the framework's `NSC__` prefix for the merge mechanism (see `getEnvironmentObject()` in `core/common/helpers/config.helper.ts`):

```bash
# in .env
NSC__BETTER_AUTH__TRUSTED_ORIGINS=http://localhost:3000,http://localhost:3001
```

---

## Maintenance: Dependency Refresh

| Package | 11.25.3 | 11.25.5 | Notes |
|---|---|---|---|
| `@apollo/server` | 5.5.0 | 5.5.1 | Patch — added 11.25.5. |
| `better-auth` | 1.5.5 | **1.6.10** | Minor bump. `@better-auth/core@1.6.9` declared `@opentelemetry/api` as `peerDependenciesMeta.optional` (previously required and was the upgrade blocker). 1.6.10 is a patch on top. All 1751 framework tests pass; manual smoke-testing of auth flows (sign-in, passkey, 2FA) is recommended in your project before deploying. |
| `@better-auth/passkey` | 1.5.5 | 1.6.10 | Lockstep with `better-auth`. |
| `@nestjs/apollo` | 13.2.5 | 13.4.0 | Patch + minor — fixes upstream. |
| `@nestjs/graphql` | 13.2.5 | 13.4.0 | Patch + minor — fixes upstream. |
| `@nestjs/swagger` | 11.3.0 | 11.4.2 | Patch-level fixes upstream. |
| `@tus/file-store` | 2.0.0 | 2.1.0 | Minor — fully backward compatible per upstream changelog. |
| `@tus/server` | 2.3.0 | 2.4.1 | Lockstep with `@tus/file-store`. |
| `graphql` | 16.13.2 | 16.14.0 | Minor — added 11.25.5. |
| `mongodb` | 7.1.1 | 7.2.0 | Minor — added 11.25.5 (now in sync with `mongoose@9.6.x`). |
| `mongoose` | 9.4.1 | 9.6.2 | Minor — bundled MongoDB driver now 7.2.x. |
| `nodemailer` | 8.0.5 | 8.0.7 | Patch. |
| `otpauth` | 9.5.0 | 9.5.1 | Patch. |
| `vite` | 7.3.2 | **8.0.11** | **Major** — added 11.25.5. Coupled lockstep upgrade with `vite-plugin-node@8.0.0`. All 1751 framework tests pass; consumers using their own `vitest`/`vite-plugin-node` setups should verify compatibility. |
| `vite-plugin-node` | 7.0.0 | **8.0.0** | **Major** — lockstep with `vite@8`. |
| `vitest` | 4.1.4 | 4.1.5 | Patch (lockstep with `@vitest/coverage-v8` and `@vitest/ui`). |
| `@swc/core` | 1.15.26 | 1.15.33 | Patch. |
| `@types/node` | 25.6.0 | 25.6.2 | Patch — added 11.25.5. |
| `oxfmt` | 0.45.0 | 0.48.0 | Minor — formatter, dev-only. |
| `oxlint` | 1.60.0 | 1.63.0 | Minor — linter, dev-only. |
| `pnpm` (packageManager) | 10.33.0 | 10.33.2 | Patch. |

**Knowingly not updated** (with rationale):

| Package | Current | Latest | Reason |
|---|---|---|---|
| `@getbrevo/brevo` | 3.0.1 | 5.0.4 | Major API redesign — separate PR. |
| `graphql-upload` | 15.0.2 | 17.0.0 | ESM-only — would force ESM migration of multiple modules. |
| `ts-morph` | 27.0.2 | 28.0.0 | `@compodoc/compodoc` peer-deps still pin `^27`. |
| `typescript` | 5.9.3 | 6.0.3 | `@nestjs/cli@11.0.21` pins `typescript@5.9.3`; major upgrade would conflict. |

---

## Tooling: ANSI-Safe Port Detection in `check-server-start.sh`

When `pnpm run check` runs under a workspace runner that wraps stdout with ANSI color codes (lerna, nx, turbo, certain CI environments), the inline Node one-liner that picks a free port can return output like:

```
\x1b[33m50604\x1b[39m
```

That string then enters `FREE_PORT` and crashes `net.Server#listen` with `ERR_SOCKET_BAD_PORT`. The script now strips ANSI escape sequences via `sed` (NOT via `tr -cd '0-9'`, which would corrupt the port digits because the escape codes contain digits themselves):

```bash
FREE_PORT=$(node -e "..." | sed $'s/\x1b\\[[0-9;]*m//g' | tr -d '[:space:]')
```

Defensive change — has no effect when no ANSI codes are present.

---

## Compatibility Notes

- **API surface**: unchanged. All exports from `src/index.ts` keep the same shape.
- **CoreModule.forRoot()**: unchanged.
- **Mongoose plugins, decorators, interceptors, guards**: unchanged.
- **Test contracts**: unchanged. `pnpm test` passes 1751 / 1751 cases on both `develop` HEAD and the new release.
- **Override targets**: all fixed versions per `.claude/rules/package-management.md`.

---

## Troubleshooting

**Q: After updating, `pnpm test` fails on tests that I had previously skipped because of the type-metadata.storage error.**
A: Those tests should now pass. If they don't, ensure your `pnpm-lock.yaml` is in sync (`pnpm install`) and the patched files were actually pulled.

**Q: Vendor-mode project still throws `Cannot find module ...type-metadata.storage` after I updated to 11.25.5.**
A: Run `lt fullstack update` from your monorepo root, or manually re-sync the five files listed in the bug-fix section above. The `lt` CLI's `fullstack update` will detect the version bump and pull the new vendored core.

**Q: My tests fail with `Invalid trustedOrigin format: /` or `Invalid passkey origin format: /` after switching `betterAuth.trustedOrigins` or `baseUrl` to `process.env.BASE_URL`.**
A: See "BASE_URL Pitfall" above. Vite/Vitest auto-inject `process.env.BASE_URL='/'`, so the `||` fallback never fires. Filter against `'/'` explicitly, use a different env var name (e.g. `API_URL`), or use the `NSC__` prefix mechanism.

**Q: `pnpm install` warns about unmet peer-deps for `typescript@^5.6.3 vs found 6.0.3`.**
A: Harmless — this is by design. We are deliberately staying on `typescript@5.9.x` because TS 6.0 was just released and ecosystem peer-dep readiness is still in flux. The warning will go away when downstream packages widen their peer ranges.

**Q: After running `pnpm run check` in a vendor-mode consumer, oxfmt complains about format issues in `src/core/core.module.ts` or `src/server/modules/file/file.controller.ts`.**
A: This is a separate `lt CLI`-side issue (the convert-mode does not run a final `oxfmt --write` pass). Fix locally with `pnpm run format` and report against the `lenneTech/cli` repo.

---

## Related Documentation

- **PR #541** — `fix(core): add .js suffix to @nestjs/*/dist/* subpath imports` (full diff and reproducible test plan)
- [Previous migration guide](./11.25.0-to-11.25.3.md)
- [`.claude/rules/package-management.md`](../.claude/rules/package-management.md) — fixed-version policy for `package.json` and overrides
