deno
563aa7be - fix(npm): share copy-package variants via symlink for class identity (#34691)

Commit
141 days ago
fix(npm): share copy-package variants via symlink for class identity (#34691) ## Summary When `deno install` resolves an npm package through multiple peer-dep contexts, deno_npm produces `copy_index` variants of the same name+version (`@pkg@1.0.0`, `@pkg@1.0.0_1`, ...). The local installer filled each variant's `node_modules/<pkg>/` with `clone_dir_recursive` (per-file hardlinks at distinct directory paths). `realpath()` then returned the variant path verbatim, so Deno's CJS `Module._cache` kept one entry per variant and every variant produced a distinct class object. NestJS DI compares the constructor-parameter type captured by `@Injectable()` decorator metadata to the registered provider class via `===`. With two `ModuleRef` classes alive the lookup fails: ``` [Nest] ... ERROR [ExceptionHandler] Nest can't resolve dependencies of the TypeOrmHealthIndicator (?). Please make sure that the argument ModuleRef at index [0] is available in the TerminusModule context. ``` That is denoland/deno#26427 — `@nestjs/terminus` triggers it because it pulls `@nestjs/core` through a peer-dep cycle (core ↔ platform-express), producing five physical copies of `@nestjs/core` in the user's `node_modules`. The fix symlinks each variant's package directory to the canonical (copy_index=0) variant's package directory instead of cloning. The variant's own `node_modules/` neighbours (the peer-dep symlinks) remain, but `realpath()` of any file inside the package collapses to a single canonical path. `Module._cache` then has one entry per package and the class identity is preserved across require chains, matching Node + npm and pnpm semantics (first-peer-wins for the inner peer-resolution). The global cache install path (`ensure_copy_package` in `libs/npm_cache/lib.rs`) is unchanged. ## Test plan - [x] Added regression spec `tests/specs/npm/peer_dep_copy_class_identity` with new fixture packages `@denotest/peer-dep-test-class@1.0.0` and `@denotest/peer-dep-test-class-consumer@{1,2}.0.0`: asserts a class exported from the (formerly duplicated) variant compares `===` regardless of which consumer (peer-dep context) reached it. - [x] Updated existing `tests/specs/npm/peer_deps_with_copied_folders_and_lockfile` `--node-modules-dir=auto` outputs: the inner-peer values change from `1\n2` to `1\n1` because the previously distinct grandchild copies now share a module instance (first-peer-wins). Global cache runs are untouched and still produce `1\n2`. - [x] Manually verified upstream repro (`lucassusanto/nestjs-deno-terminus-issue`): `[NestApplication] Nest application successfully started`. - [x] `cargo test --test specs -- specs::npm::` — 276 passed; the 12 pre-existing failures all baseline-fail without this change (tarball-integrity drift in the local test registry, unrelated). - [x] `cargo build --bin deno` clean. Refs #26427 Closes denoland/divybot#404 --------- Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author
Parents
Loading