Skip to content

fix: derive package name from package.json under Yarn nodeLinker: pnpm store#1852

Merged
webpro merged 1 commit into
webpro-nl:mainfrom
blowery:fix/yarn-pnpm-linker-store-package-name
Jul 4, 2026
Merged

fix: derive package name from package.json under Yarn nodeLinker: pnpm store#1852
webpro merged 1 commit into
webpro-nl:mainfrom
blowery:fix/yarn-pnpm-linker-store-package-name

Conversation

@blowery

@blowery blowery commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #1851

Problem

Under Yarn Berry with nodeLinker: pnpm, Knip reports bogus unlisted dependencies named .store.

getPackageNameFromFilePath derives the package name from the last node_modules/ segment. That holds for the pnpm CLI (node_modules/<pkg>/), but Yarn's pnpm linker lays packages out in a content-addressed store instead:

node_modules/.store/<flattened-locator>/package/…

There is no nested node_modules/<pkg>, so the derived name is literally .store. It typically surfaces via plugins that resolve config files through store realpaths (e.g. Storybook/Babel addons via dirname(require.resolve('<addon>/package.json'))).

Why not parse the folder name

The store folder is <flatIdent>-<protocol>-<reference>-<hash> with scopes flattened (@scope/name@scope-name), which is ambiguous two ways:

  1. Flattened scope: @remix-run-router could be @remix-run/router or @remix/run-router.
  2. Protocol markers (npm/virtual/patch/file/http/portal) are indistinguishable from real name segments (@dmsnell/diff-match-patch, write-file-atomic, @types/http-errors, is-npm).

So the folder name can't be parsed back to a package name reliably. The store package's own package.json name field is authoritative, so this reads that instead.

Change

In getPackageNameFromFilePath, when — and only when — the derived last segment is .store, resolve the name from the store package's package.json:

  • Matches …/node_modules/.store/<locator>/package with a (?:\/|$) boundary so it handles both paths that continue past /package/… and paths that end exactly at …/package (e.g. addon dirs from dirname(require.resolve('<addon>/package.json'))).
  • Caches per store root, so classic node_modules layouts never hit the filesystem and each store package is read at most once.
  • Falls back to .store (prior behavior) if the package.json is missing/unreadable.

This mirrors the existing sync-read + Map cache pattern in src/manifest/helpers.ts.

Tests

Added a fixture (fixtures/yarn-pnpm-store) laid out as a Yarn pnpm store and unit tests in test/util/modules.test.ts covering:

  • ambiguous flattened scope → @remix-run/router
  • protocol-collision unscoped name → write-file-atomic
  • path ending at /package with no trailing slash
  • graceful fallback to .store when no package.json exists
  • unchanged behavior for classic node_modules paths (including nested)

pnpm build (tsgo), oxlint, oxfmt --check, and the module tests pass locally (node + bun). The approach is validated in a production repo using nodeLinker: pnpm.

Yarn's nodeLinker: pnpm lays packages out as
node_modules/.store/<flattened-locator>/package/ with no nested
node_modules/<pkg>, so getPackageNameFromFilePath derived the last
node_modules segment as '.store', surfacing bogus unlisted deps.

Read the store package's own package.json name field, which is
authoritative. The folder name (<flatIdent>-<protocol>-<reference>-<hash>)
can't be parsed unambiguously: flattened scopes (@remix-run-router) and
protocol markers colliding with real name segments (write-file-atomic).
@pkg-pr-new

pkg-pr-new Bot commented Jul 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/knip@1852
npm i https://pkg.pr.new/@knip/language-server@1852
npm i https://pkg.pr.new/@knip/mcp@1852

commit: b702eab

@webpro

webpro commented Jul 4, 2026

Copy link
Copy Markdown
Member

Thanks Ben!

@webpro
webpro merged commit 3764605 into webpro-nl:main Jul 4, 2026
30 checks passed
@webpro

webpro commented Jul 7, 2026

Copy link
Copy Markdown
Member

🚀 This pull request is included in v6.25.0. See Release 6.25.0 for release notes.

Using Knip in a commercial project? Please consider becoming a sponsor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Package names mis-derived as .store under Yarn nodeLinker: pnpm

2 participants