You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(types): verify a link:/file: install by the LOCATION its host declared (#17542)
* feat(types): verify a link:/file: install by the location the host declared
WIP checkpoint: second verification axis + tests, before the closure build.
Claude-Session: https://claude.ai/code/session_01DapQyvYrFb1MxSYe7BL2nt
Co-authored-by: Claude <noreply@anthropic.com>
* fix(cli): defer the multi-org declared-unresolvable remedy to the cause
Also updates content/docs/deployment/tenancy-modes.mdx for the two shapes the
declared-unresolvable kind now covers, and adds both changesets.
Claude-Session: https://claude.ai/code/session_01DapQyvYrFb1MxSYe7BL2nt
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Host importer: a `link:` / `file:` install is now verified by the LOCATION the app declared, so a correctly linked package loads instead of being refused.
6
+
7
+
The ESM fallback finder (`createHostImporter`) verifies the one directory it consults — `<hostRoot>/node_modules/<key>` — against what the host's own `package.json` declares. Until now it could only do that by NAME, and a `link:` / `file:` value promises no name, so the KEY stood in for one: a package linked exactly as the app asked, whose own manifest happens to be named something else, was refused with `declared-unresolvable` / `MODULE_NOT_FOUND`. Nothing was broken, and the only way out was to stop using a supported linking mode.
8
+
9
+
Such a declaration does name something checkable — a directory — so the finder now checks that too: `realpath(node_modules/<key>)` against `realpath(resolve(hostRoot, <declared path>))`, both sides canonicalised, compared exactly (no basename matching, no case folding). If they are the same directory, the host declared it and it loads.
10
+
11
+
This is a second verification axis, not a looser first one. A directory the app declared neither by name nor by path is refused exactly as before, and the finder stays strictly tighter than the CommonJS resolution it backs up, which asks neither question. Unchanged: a plain version range licenses no path; an `npm:` alias is still checked by name; `github:` / tarball URLs and the bare `owner/repo` shorthand name no on-disk location, so they gain nothing; a package that publishes a `require` condition never reaches this fallback at all, so no load that succeeds today changes.
12
+
13
+
Measured on pnpm 10.33: `link:` symlinks the key at the declared directory and verifies; a `file:` directory install routes through pnpm's virtual store (a copy), so it does not, and keeps today's refusal. The refusal's text now states what the location check compared instead of asserting a limit the finder no longer has.
`serve`: the multi-org runtime's stage-1 refusal no longer prints its own install remedy for a `declared-unresolvable` failure — it defers to the importer's message, which the same refusal already prints as its `cause:` line.
6
+
7
+
Driven on both shapes that kind covers, the minted bullet ("Repair the INSTALL … run `pnpm install`, check that a production prune did not drop it, and that its dist is actually built") was wrong twice over. For a genuinely broken install it repeated, word for word, the three remedies the cause line four lines below already carried. For a location install the finder cannot tie to the declaration, the cause says outright that re-running `pnpm install`, un-pruning a deploy and rebuilding a dist all change nothing — so one screen contradicted itself.
8
+
9
+
The arm now says only what it uniquely knows (the app DOES declare the package, so re-reading `package.json` will not help) and names the cause as the authority on the remedy — the same deferral the `declared-no-loadable-entry` arm has had since it landed.
0 commit comments