* chore(release): 11.28.7, pacquet 12.15.0, pnpr 0.1.0-alpha.16
* docs(release): curate 11.28.7, pacquet 12.15.0, and pnpr 0.1.0-alpha.16 release notes
Add lead paragraphs and group the patch entries by what the reader was
doing, with security fixes first on each page.
- v11: lead with the registry metadata name check, pinned pnpm
trustPolicy check, maxTarballUnpackedSize, and the removal of registry
revisions; merge overlapping entries for minimumReleaseAge metadata
revalidation, frozen-lockfile directory dependency checks,
syncInjectedDepsAfterScripts, verifyDepsBeforeRun, pnpm sbom, and
unreadable config settings.
- v12: order minor highlights from config includes and
lockfile.integrityTarget down to interactive update styling; drop the
pnpr-only distribution note from the CLI changelog; merge overlapping
entries for workspace and pnpr install speedups, minimumReleaseAge and
If-Modified-Since revalidation, frozen-lockfile and injected package
checks, verifyDepsBeforeRun, pnpm prune, pnpm pack, pnpm licenses
list, pnpm self-update, pinned pnpm flag forwarding, and WebContainer
fixes.
- pnpr: lead the minor section with the breaking ecosystem URL prefix,
hosted storage layout, artifacts.orgs, and strict config schema
changes; fold dist.pnpr tarball metadata into one entry and drop the
redundant /metrics note; lead the patch section with reserved-username
and auth security fixes.
- Use one issue-link label style across the release pages.
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Node.js keeps one context object for the whole resolve hook chain and
merges whatever a hook passes to nextResolve into it, both for
module.registerHooks() and for module.register(). The NODE_PATH fallback
retries a bare specifier with a synthetic parentURL inside each NODE_PATH
entry. When every retry failed and the hook rethrew the original error, a
hook registered after it, which runs first, read the synthetic parentURL
from its context. A tsconfig paths resolver then looked for the alias
target under the project's node_modules and failed with
ERR_MODULE_NOT_FOUND.
The hook now saves the importer's parentURL before the retries and puts
it back on the context when they finish, whether a retry resolves or not.
The retries still pass { ...context, parentURL } to nextResolve, so the
caller's export conditions are kept. The TypeScript and Rust CLIs embed
the same hook source, and the shared golden flag is regenerated.
The new tests run the scenario from the issue in both stacks: a hook
loaded with --import after the NODE_OPTIONS flag resolves an `@/` alias
from context.parentURL. The test hook falls back to module.register()
where module.registerHooks() is missing, so the Node.js 22.13 CI job
covers the async loader variant as well.
Closespnpm/pnpm#16979
* chore(release): 11.28.4, pacquet 12.9.1
* docs(release): curate 11.28.4 and pacquet 12.9.1 release notes
Add a lead paragraph to both pages. Move the two credential-leak fixes
in 11.28.4 to the top and group the remaining 27 entries by what the
reader was doing. Merge entries that described one change: the two
INVALID_SETTING validations, the two metadata revalidation fixes, the
two hoisted filtered-install fixes, and, in pacquet 12.9.1, the two
[<since>] selector fixes. Order pacquet 12.9.1 so the `@pnpm/wasm` move,
which WebContainer users must act on, comes first.
Normalize issue links to the [#N] form and link the ETag fix to
pnpm/pnpm#16506.
Every fix was checked against the code released in 11.28.3 and
pacquet 12.9.0; none fixes a defect that never shipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(release): describe the frozen-lockfile fixes accurately in the 11.28.4 lead
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Any `--import` flag in NODE_OPTIONS makes Node.js run the main entry
point through the ESM loader, on every Node.js version. The ESM loader
rejects extensions that only CommonJS require hooks handle, so
`ts-node index.ts` failed with ERR_UNKNOWN_FILE_EXTENSION once the
global virtual store injected its NODE_PATH loader flag. Node.js 24+
only appeared unaffected because it strips TypeScript types natively.
The resolve hook now marks an entry point that the ESM loader cannot
classify as CommonJS, which hands it back to the CommonJS loader, as
Node.js does without `--import`. `.js` entry points and entry points
without an extension are left alone because Node.js may detect module
syntax in them. The hook stays resolve-only: registering any synchronous
load hook breaks require.extensions for CommonJS mains on Node.js 22.15.
The hook is now installed even when NODE_PATH is empty, since the flag
alone already routes the entry point through the ESM loader.
Closespnpm/pnpm#16436
With a global virtual store, package directories live outside the
project, so Node's upward node_modules walk from their real paths never
reaches the project's hoisted node_modules — undeclared (phantom)
dependencies stop resolving, and Node ignores NODE_PATH entirely for
ESM imports (Related to pnpm/pnpm#9618).
When enableGlobalVirtualStore is on (and extendNodePath is not
disabled), every child process pnpm spawns — run, exec, lifecycle
scripts, dlx — now receives NODE_PATH pointing at the private hoist dir
and the root node_modules, plus a NODE_OPTIONS --import flag that
registers a resolve hook restoring NODE_PATH lookups for ESM, replacing
the plugin-esm-node-path config dependency.
The flag is a self-contained constant: the registration module and the
hook are nested data: URLs, so no loader file has to exist on disk and
the flag stays valid regardless of which project or pnpm version
spawned the child. It prefers module.registerHooks() (in-thread, no
DEP0205 deprecation warning) and falls back to module.register() on
Node >=18.19 <22.15. Everything outside the RFC 3986 unreserved set is
percent-encoded: encodeURIComponent alone leaves single quotes bare,
and the NODE_OPTIONS tokenizer treats those as quote delimiters.
Both stacks embed identical hook sources and each asserts its derived
flag against the shared golden file
pnpm11/exec/esm-node-path-loader/test/import-flag.txt, so they cannot
drift apart silently.
Where the nodeOptions setting overwrites NODE_OPTIONS (run, exec,
recursive run, install lifecycle), the flag is re-applied. pacquet's
dlx and global installs force GVS off for their self-contained trees
and now strip the injected env from the caller's cloned config, so a
tool never resolves phantoms from the invoking project's tree; the
TypeScript dlx (GVS on by default) adds the flag and lets the bin shim
supply NODE_PATH.
This deliberately does not address the TypeScript-compiler side of
pnpm/pnpm#13210: tsc, tsserver, and bundlers implement their own
resolution and honor neither NODE_PATH nor Node loader hooks. That
class remains packageExtensions / compat-DB territory.