pnpm 12.15.0 → 12.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +189 -0
  2. package/README.md +4 -4
  3. package/bin/pnpm.mjs +11 -233
  4. package/dist/node_modules/.pnpm-workspace-state-v1.json +97 -269
  5. package/dist/node_modules/confbox/LICENSE +27 -0
  6. package/dist/node_modules/confbox/dist/_chunks/_format.d.mts +22 -0
  7. package/dist/node_modules/confbox/dist/_chunks/_format.mjs +1 -0
  8. package/dist/node_modules/confbox/dist/_chunks/libs/detect-indent.mjs +1 -0
  9. package/dist/node_modules/confbox/dist/_chunks/libs/ini.mjs +3 -0
  10. package/dist/node_modules/confbox/dist/_chunks/libs/js-yaml.d.mts +33 -0
  11. package/dist/node_modules/confbox/dist/_chunks/libs/js-yaml.mjs +38 -0
  12. package/dist/node_modules/confbox/dist/_chunks/libs/json5.mjs +14 -0
  13. package/dist/node_modules/confbox/dist/_chunks/libs/smol-toml.mjs +11 -0
  14. package/dist/node_modules/confbox/dist/_chunks/libs/strip-json-comments.mjs +4 -0
  15. package/dist/node_modules/confbox/dist/_chunks/rolldown-runtime.mjs +1 -0
  16. package/dist/node_modules/confbox/dist/index.d.mts +7 -0
  17. package/dist/node_modules/confbox/dist/index.mjs +1 -0
  18. package/dist/node_modules/confbox/dist/ini.d.mts +59 -0
  19. package/dist/node_modules/confbox/dist/ini.mjs +1 -0
  20. package/dist/node_modules/confbox/dist/json.d.mts +25 -0
  21. package/dist/node_modules/confbox/dist/json.mjs +1 -0
  22. package/dist/node_modules/confbox/dist/json5.d.mts +55 -0
  23. package/dist/node_modules/confbox/dist/json5.mjs +1 -0
  24. package/dist/node_modules/confbox/dist/jsonc.d.mts +29 -0
  25. package/dist/node_modules/confbox/dist/jsonc.mjs +1 -0
  26. package/dist/node_modules/confbox/dist/toml.d.mts +20 -0
  27. package/dist/node_modules/confbox/dist/toml.mjs +1 -0
  28. package/dist/node_modules/confbox/dist/yaml.d.mts +101 -0
  29. package/dist/node_modules/confbox/dist/yaml.mjs +1 -0
  30. package/dist/node_modules/confbox/package.json +68 -0
  31. package/dist/node_modules/get-pnpm/lib/corepack.js +79 -0
  32. package/dist/node_modules/get-pnpm/lib/downloadExecutable.js +10 -4
  33. package/dist/node_modules/get-pnpm/lib/extractTarballMember.js +45 -35
  34. package/dist/node_modules/get-pnpm/lib/index.js +3 -219
  35. package/dist/node_modules/get-pnpm/lib/install.js +293 -0
  36. package/dist/node_modules/get-pnpm/lib/npmConfig.js +280 -0
  37. package/dist/node_modules/get-pnpm/lib/npmSigningKeys.js +2 -6
  38. package/dist/node_modules/get-pnpm/lib/platformPackageName.js +11 -7
  39. package/dist/node_modules/get-pnpm/lib/pnpmConfig.js +297 -0
  40. package/dist/node_modules/get-pnpm/lib/projectPin.js +201 -0
  41. package/dist/node_modules/get-pnpm/lib/registry.js +265 -41
  42. package/dist/node_modules/get-pnpm/lib/resolveVersion.js +19 -2
  43. package/dist/node_modules/get-pnpm/lib/sameFileContents.js +31 -27
  44. package/dist/node_modules/get-pnpm/lib/shadowingPnpm.js +174 -0
  45. package/dist/node_modules/get-pnpm/lib/undoHint.js +12 -0
  46. package/dist/node_modules/get-pnpm/package.json +20 -14
  47. package/package.json +15 -15
package/CHANGELOG.md CHANGED
@@ -1,5 +1,194 @@
1
1
  # pnpm
2
2
 
3
+ ## 12.16.0
4
+
5
+ This release adds a global `--env-file` flag, updates to a symlinked `pnpm-lock.yaml`, and `pnpm setup --no-modify-path`. It also fixes two security issues, in zstd decompression and in signing.
6
+
7
+ ### Minor Changes
8
+
9
+ - Added a repeatable global `--env-file <path>` flag to load environment variables from dotenv-style files before running a command. Later files override earlier files, variables already in the environment are preserved, and loaded variables are visible to user-level `.npmrc` interpolation and lifecycle scripts [#7111](https://github.com/pnpm/pnpm/issues/7111).
10
+
11
+ - pnpm can now update a `pnpm-lock.yaml` that is a symlink. List the directories the lockfile may link into in the `symlinkedLockfileTargetDirs` setting, in the global config or with `PNPM_CONFIG_SYMLINKED_LOCKFILE_TARGET_DIRS`. pnpm writes the update to the file the symlink points to if that file is inside one of these directories. The symlink stays in place. pnpm ignores this setting in `pnpm-workspace.yaml` [#17222](https://github.com/pnpm/pnpm/issues/17222).
12
+
13
+ - Added a `--no-modify-path` option to `pnpm setup`. It installs pnpm without adding it to `PATH`, leaving the shell config file, the Windows registry, and the GitHub Actions environment files unchanged. `pnpm setup` then prints the lines to add yourself [get.pnpm.io#37](https://github.com/pnpm/get.pnpm.io/issues/37).
14
+
15
+ ### Patch Changes
16
+
17
+ #### Security
18
+
19
+ - Fixed memory exposure and allocation handling vulnerabilities when processing zstd-compressed data [#17388](https://github.com/pnpm/pnpm/pull/17388).
20
+
21
+ - Signing in pnpm and pnpr no longer uses arithmetic that may leak the private key through timing. This covers shared artifacts, OCI registry tokens, and OIDC login state [advisory](https://rustsec.org/advisories/RUSTSEC-2026-0387).
22
+
23
+ #### Installing packages
24
+
25
+ - A dependency symlink that pnpm cannot point at the wanted target now fails the install with an error that names the link. This happens when the filesystem still serves the old link after pnpm removed it, as a `node_modules` served by a virtual filesystem does. pnpm used to remove the link again without end and crashed with a segmentation fault once the thread ran out of stack.
26
+
27
+ - Concurrent workspace installs no longer lose store-index rows when initializing the shared SQLite index [#17388](https://github.com/pnpm/pnpm/pull/17388).
28
+
29
+ - Agent skill links now keep the same target across installs when a package version is installed with several sets of peer dependencies. An install no longer fails with `ERR_PNPM_AGENT_SKILL_PATH_OCCUPIED` on a skill link that pnpm created for another of those copies [#17319](https://github.com/pnpm/pnpm/issues/17319).
30
+
31
+ - pnpm now fails at once, without retrying, when a downloaded package archive contains a rejected entry path [#17205](https://github.com/pnpm/pnpm/issues/17205).
32
+
33
+ - pnpm now builds a git-hosted dependency in every new location it links the dependency into, such as a new global virtual store slot or a fresh `node_modules`. When the side-effects cache holds the build output, pnpm restores it from there. Before, only the install that fetched the dependency built it. This also fixes dependencies that are already in the store [#17224](https://github.com/pnpm/pnpm/issues/17224).
34
+
35
+ - Changes a `preResolution` hook makes to the lockfiles now take effect. The hook now also runs when pnpm installs from an up-to-date lockfile. An error thrown by the hook now fails the install.
36
+
37
+ - A custom fetcher in the pnpmfile can now call `fetchers.directory` to fetch a package from a local directory. The call rejects with `ERR_PNPM_TARBALL_INTEGRITY` if the lockfile pins an archive integrity for the package. Previously, calling `fetchers.directory` failed with a `TypeError`.
38
+
39
+ - `packageImportMethod: copy` now also applies to packages that have lifecycle scripts or patches. On a filesystem that supports block cloning, such as ReFS or a Dev Drive on Windows, these packages were cloned [#17195](https://github.com/pnpm/pnpm/issues/17195).
40
+
41
+ - With `nodeLinker: hoisted` and `autoInstallPeers: false`, a package that workspace projects use with different peer versions is now installed once per peer variant, so each project's copy resolves that project's peer [#17395](https://github.com/pnpm/pnpm/issues/17395).
42
+
43
+ - Under `nodeLinker: hoisted`, `pnpm install` now leaves a patched package that is already in place alone. The same holds for a package whose peers include a patched package, such as every package peering on a patched `react-native` [#17188](https://github.com/pnpm/pnpm/issues/17188).
44
+
45
+ - With `nodeLinker: hoisted`, an install that stops partway no longer leaves the next install trusting the packages it was replacing. Before, reverting the change that started the stopped install, such as a patch edit, kept the half-replaced package in `node_modules`.
46
+
47
+ #### Repeat installs and changed settings
48
+
49
+ - A repeat `pnpm install` now acts on a change to these settings. Before, it reported "Already up to date".
50
+
51
+ - `storeDir` or `virtualStoreDir`: it recreates `node_modules`.
52
+ - `globalVirtualStoreDir`: it links the packages from the new directory.
53
+ - `virtualStoreDirMaxLength`: it renames the virtual store directories.
54
+ - `dangerouslyAllowAllBuilds` turned on: it runs the builds it skipped.
55
+ - `trustLockfile` turned off: it verifies the lockfile.
56
+
57
+ - `pnpm add`, `pnpm remove`, and `pnpm update` now fail if `node_modules` does not match the current settings. Run `pnpm install` to recreate it. Previously, these commands removed and reinstalled `node_modules` without asking. They report:
58
+
59
+ - `ERR_PNPM_UNEXPECTED_STORE` if `node_modules` was linked from a different store.
60
+ - `ERR_PNPM_UNEXPECTED_VIRTUAL_STORE` if it was linked from a different virtual store directory.
61
+ - `ERR_PNPM_MODULES_BREAKING_CHANGE` if it was installed by an incompatible pnpm version, or if `node_modules/.modules.yaml` cannot be read.
62
+ - `ERR_PNPM_NODE_LINKER_DIFF` if it was installed with a different `nodeLinker`.
63
+
64
+ `pnpm update` now also fails with `ERR_PNPM_HOIST_PATTERN_DIFF` and the other errors `pnpm add` reports for a changed hoist pattern or `virtualStoreDirMaxLength`.
65
+
66
+ - An install filtered to some workspace projects now fails if `node_modules` was installed with a different `nodeLinker`, `hoistPattern`, `publicHoistPattern`, store, or virtual store. Before, it recreated `node_modules` and left the projects it skipped without their dependencies. A `pnpm install` of the whole workspace still recreates `node_modules` [pnpm/tasks#217](https://github.com/pnpm/tasks/issues/217).
67
+
68
+ - `pnpm install` and the `verifyDepsBeforeRun` check now notice an edited patch file when `sharedWorkspaceLockfile` is `false`.
69
+
70
+ - `pnpm install` now takes its fast path in a workspace with `patchedDependencies` when the previous install ran with another major version of pnpm.
71
+
72
+ - `pnpm install` now relinks the bins of a workspace package when its `bin` field changes, including with `--frozen-lockfile` and an unchanged lockfile. A filtered install relinks them in the selected projects, and the next install that covers the remaining projects relinks them there [#17324](https://github.com/pnpm/pnpm/issues/17324).
73
+
74
+ - `pnpm install` now removes command shims from `node_modules/.bin` when a workspace dependency renames or removes an entry from its `bin` field [pnpm/tasks#211](https://github.com/pnpm/tasks/issues/211).
75
+
76
+ #### Resolving dependencies and the lockfile
77
+
78
+ - A package that a plain range and a `<prefix>:` specifier both resolve from one registry is now recorded once in `pnpm-lock.yaml` and installed once. The lockfile key names the registry a package resolved from. When that registry declares a prefix, through `prefix` in `registries` or through `namedRegistries`, the package is keyed `<name>@<prefix>:<version>` however its dependency was written: with the prefix, as a plain range its scope routes to that registry, or through the default registry. Before, the plain range was keyed `<name>@<version>`, so one tarball was installed twice and `instanceof` checks and module singletons broke between the two copies. The same name and version served by two registries still get separate entries. When several prefixes declare one registry, its packages are keyed by the lexicographically smallest prefix.
79
+
80
+ If a scope you route reaches a registry that also declares a prefix, your next `pnpm install` re-keys those entries, which shows up as a lockfile diff. Commit it. `pnpm install --frozen-lockfile` installs the lockfile as recorded.
81
+
82
+ - `pnpm install` now resolves a scope's packages again when the scope is routed to another registry in `registries`. Before, the lockfile kept the versions and integrity resolved from the old registry, so the install failed when the new registry did not serve a locked version or served different content. pnpm records in the workspace state which registry each scope routed to, so a fresh clone without that record installs the lockfile as recorded. Changing the default registry resolves nothing again, and `pnpm install --frozen-lockfile` is unchanged.
83
+
84
+ - When `autoDedupe` is off, a workspace project's dependencies now resolve from its own ranges, like they do with `sharedWorkspaceLockfile: false`. Previously, another project's narrower range could hold them back. For example, `ajv: "*"` now resolves to the latest Ajv even if another project depends on `ajv@^6`. Packages that a project lists in `peerDependencies` still follow the versions other projects use. `pnpm dedupe` and installs with `autoDedupe: true` still pick one version that satisfies every range. Set `autoDedupe: true` if CI runs `pnpm dedupe --check` [#17235](https://github.com/pnpm/pnpm/issues/17235).
85
+
86
+ - An optional dependency written with a named-registry prefix, such as `work:^1.0.0`, now fails the install when it cannot be resolved but the lockfile already has a matching entry from that registry. Such a dependency was skipped, which removed its entries from the lockfile.
87
+
88
+ - With `injectWorkspacePackages: true`, a `workspace:` dependency written as a relative path, such as `workspace:../lib-a`, in a workspace package now resolves from that package's directory [#17284](https://github.com/pnpm/pnpm/issues/17284).
89
+
90
+ - Fixed `pnpm install` failing with `ERR_PNPM_UNSUPPORTED_PROTOCOL` when a workspace package injected through a relative path, such as `workspace:../lib`, has `catalog:` dependencies. Their catalog entries now resolve the way they do for a package injected by name [#17280](https://github.com/pnpm/pnpm/issues/17280).
91
+
92
+ - `pnpm dedupe` now refreshes cached registry metadata while re-resolving locked versions [#17208](https://github.com/pnpm/pnpm/issues/17208).
93
+
94
+ - `pnpm dedupe --check` no longer fails when the only difference is a `deprecated` notice that the registry added or removed after the lockfile was written [#17198](https://github.com/pnpm/pnpm/issues/17198).
95
+
96
+ - Installs delegated to a pnpr server now resolve every prefix declared for a registry URL. A dependency or lockfile entry that used the second of two prefixes for the same registry, such as `zeta:` next to `work:` in `namedRegistries`, failed on the server [pnpm/tasks#204](https://github.com/pnpm/tasks/issues/204).
97
+
98
+ - Registry revisions are now addressed at `<registry>/<name>@<version>+r<revision>` [pnpm/tasks#208](https://github.com/pnpm/tasks/issues/208).
99
+
100
+ #### Adding packages and global packages
101
+
102
+ - `pnpm add <pkg>@<tag>` and `pnpm add <pkg>@<range>` now install the version the tag or range resolves to, even when the saved range allows a newer one [#17234](https://github.com/pnpm/pnpm/issues/17234).
103
+
104
+ - `pnpm add <pkg>` without a version now installs the version under the dist-tag set by the `tag` setting. An `npm:` alias or a `jsr:` dependency without a version resolves that tag too. The setting is read from `pnpm-workspace.yaml`, the global config, `PNPM_CONFIG_TAG`, and `--config.tag`, and defaults to `latest`.
105
+
106
+ - `pnpm add` now accepts the lowercase short options `-p`, `-d`, `-o`, and `-e` as aliases of `--save-prod`, `--save-dev`, `--save-optional`, and `--save-exact`.
107
+
108
+ - `pnpm -D install <pkg>` now saves the package to devDependencies. An `add` option such as `-D` or `-E` written before `install <pkg>` was rejected as an unexpected argument.
109
+
110
+ - `pnpm add -T` now reports that the short option is ambiguous and points to `--save-types` and `--tilde`.
111
+
112
+ - Global packages installed with `pnpm add -g` are now linked from the global virtual store, so global packages that share dependencies share one copy. Set `virtualStoreType: project` to give each global install its own virtual store.
113
+
114
+ - `pnpm add -g` and the other `--global` commands now fail with `ERR_PNPM_CONFIG_CONFLICT_VIRTUAL_STORE_DIR_WITH_GLOBAL` when `--virtual-store-dir` is passed. A custom `--hoist-pattern` fails with `ERR_PNPM_CONFIG_CONFLICT_HOIST_PATTERN_WITH_GLOBAL`. Passing `--link-workspace-packages`, `--shared-workspace-lockfile`, or `--config.lockfile-dir` with `--global` also fails, with the matching `ERR_PNPM_CONFIG_CONFLICT_*_WITH_GLOBAL` code.
115
+
116
+ #### Registries, proxies, and login
117
+
118
+ - pnpm now reads the `strictSsl` and `localAddress` settings from `pnpm-workspace.yaml`, the global `config.yaml`, `PNPM_CONFIG_*` environment variables, and `--config.*` options. This includes `pnpm self-update` and the download of the pnpm version pinned in `packageManager`. Before, pnpm read them only from `.npmrc` as `strict-ssl` and `local-address` and ignored the other sources [pnpm/tasks#201](https://github.com/pnpm/tasks/issues/201).
119
+
120
+ - When Corepack runs pnpm and the native binary has to be downloaded, the download now uses the proxy from pnpm's config. That proxy comes from `PNPM_CONFIG_HTTPS_PROXY` and the matching variables, the global `config.yaml`, and the user `.npmrc`. Previously, the download read the proxy only from `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` [pnpm/tasks#215](https://github.com/pnpm/tasks/issues/215).
121
+
122
+ - When the `pnpm` package downloads its native binary on first run, as it does under Corepack, it now trusts the CA certificates from the `ca`, `ca[]`, and `cafile` settings of `.npmrc`, including the ones scoped to the registry. A registry behind a private CA failed with `fetch failed` before, unless `NODE_EXTRA_CA_CERTS` named the CA. A project `.npmrc` is used only while the download is checked against npm's signature. Node.js versions that cannot add CA certificates after startup still need `NODE_EXTRA_CA_CERTS`.
123
+
124
+ - `pnpm login` saves the token in `<pnpm config>/auth.ini` again. Since v12.1.0, an unscoped login had made its registry the default for every project that did not name a registry. A login now also removes the token an earlier login saved for that registry or scope under `_auth` in the global `config.yaml`. `pnpm login --scope` still routes the scope through the global `registries` setting.
125
+
126
+ - pnpm no longer warns that a `registries` entry routed through the `@` scope matches no configured registry. pnpm applied such an entry and its options such as `serverType` while reporting it as ignored [pnpm/tasks#184](https://github.com/pnpm/tasks/issues/184).
127
+
128
+ #### Running scripts and other commands
129
+
130
+ - `pnpm ci --lockfile` and `pnpm ci --config.lockfile=true` no longer delete `pnpm-lock.yaml`. `pnpm ci` now always keeps the lockfile and runs a frozen install from it.
131
+
132
+ - `pnpm deploy --legacy` now checks the workspace lockfile against `minimumReleaseAge` and the other resolution policies, as `pnpm install` does, and fails with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION` when a locked version is too new. It used to deploy an older version that is not in the lockfile without a warning [#17209](https://github.com/pnpm/pnpm/issues/17209).
133
+
134
+ - `pnpm deploy` now keeps a deployed symlink to a directory inside the project even if the directory is not among the deployed files, such as a link into `node_modules` [#17288](https://github.com/pnpm/pnpm/issues/17288).
135
+
136
+ - `pnpm pack` and `pnpm publish` now keep `pnpm.config` in the published `package.json`. They also always include the file `pnpm.config` points to in the tarball [#17210](https://github.com/pnpm/pnpm/issues/17210).
137
+
138
+ - `pnpm -r publish --batch` now sends `PUT /-/pnpr/v0/publish`, the batch publish endpoint that pnpr serves.
139
+
140
+ - `pnpm audit --ignore` and `pnpm audit --ignore-unfixable` now exit with a non-zero code when vulnerabilities at or above the audit level remain after ignoring. Before, these commands always exited with code 0.
141
+
142
+ - `pnpm audit --fix` and `pnpm audit --interactive` now fail with `ERR_PNPM_AUDIT_FIX_WITH_IGNORE` when combined with `--ignore` or `--ignore-unfixable`. Before, the fix ran and the ignore flags had no effect.
143
+
144
+ - `pnpm -r run <script>` now honors the `requiredScripts` setting from `pnpm-workspace.yaml`. If the script is listed there and a selected project does not define it, the run fails with `ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT` and names the projects that lack it.
145
+
146
+ - `pnpm run` and `pnpm exec` no longer report the dependencies as out of date after a lockfile change when `dedupePeers` is `true` [#17322](https://github.com/pnpm/pnpm/issues/17322).
147
+
148
+ - On macOS, killing pnpm while it runs scripts in several projects at once now ends every one of those scripts. Before, a script could keep running after pnpm died.
149
+
150
+ - `pnpm run`, `pnpm exec`, and `pnpm dlx` no longer dump a core file for `pnpm` itself when a command they started dies from a signal [#17204](https://github.com/pnpm/pnpm/issues/17204).
151
+
152
+ - The `runtime` setting is now read. Setting `runtime: false` in `pnpm-workspace.yaml`, `pnpm_config_runtime=false`, or `--config.runtime=false` stops `pnpm install`, `pnpm add`, `pnpm dedupe`, and the other installing commands from installing the runtimes in `devEngines.runtime` and `engines.runtime`. `pnpm dedupe` now also accepts `--no-runtime`.
153
+
154
+ - Directory selectors with `legacyDirFiltering: true` now include the project in the named directory along with its descendants. Excluding a directory also excludes that project [pnpm/tasks#196](https://github.com/pnpm/tasks/issues/196).
155
+
156
+ - `pnpm help --all` and `pnpm help -a` now print the full list of commands. They used to fail with an "unrecognized subcommand" error.
157
+
158
+ - Reading the file-creation mask on Linux no longer sets it. pnpm had to replace the mask to learn what it was, and a file another thread created in the window between the two calls was created unmasked.
159
+
160
+ - pnpm now passes an interrupt on to the handler it replaced, with the arguments that handler expects. A handler that asked for the signal's details was called without them. Only a program that embeds pnpm and installs its own `SIGINT`, `SIGTERM` or `SIGHUP` handler was affected.
161
+
162
+ #### Store
163
+
164
+ - `pnpm store prune` now keeps the packages that a project still uses when pnpm copied or cloned them into `node_modules`. These include patched packages, built packages, and every package installed with `packageImportMethod: copy`. Before, the prune removed them, and the next install downloaded them again [#7192](https://github.com/pnpm/pnpm/issues/7192).
165
+
166
+ - `pnpm store prune` now keeps the packages of a project when the store path reaches its directory through a symlink. Previously, prune dropped such a project from its list and removed the packages only that project used from the global virtual store.
167
+
168
+ - `pnpm store prune` now removes the package metadata cache, the `metadata*` directories in the cache directory. This clears the outdated integrity checksums that cause `ERR_PNPM_TARBALL_INTEGRITY` when a registry republishes a version.
169
+
170
+ #### Speed
171
+
172
+ - `pnpm install` resolves dependencies faster in large workspaces on Linux, macOS, and Windows on x64 and arm64.
173
+
174
+ - Sped up cold installs and `pnpm fetch` on macOS. pnpm now links downloaded packages into the virtual store on a separate thread, so a slow link pass no longer pauses the downloads that are still in flight [#17271](https://github.com/pnpm/pnpm/issues/17271).
175
+
176
+ - Sped up installs that add new projects to a workspace with a peer-heavy lockfile. pnpm now reads the lockfile's locked peer versions once for all new projects [#17311](https://github.com/pnpm/pnpm/issues/17311).
177
+
178
+ - Warm installs no longer read the `package.json` of every git-hosted dependency. pnpm now stores the manifest of a git-hosted package in the store index when it fetches the package.
179
+
180
+ - `pnpm update --depth <n>` with a large `n`, such as the `--depth=9999` that `update-browserslist-db` runs, now finishes as fast as `--depth Infinity`. It used memory and stack in proportion to `n` on lockfiles with dependency cycles, and could run out of memory or crash [#17329](https://github.com/pnpm/pnpm/issues/17329).
181
+
182
+ #### Output and warnings
183
+
184
+ - Output that fills the terminal, such as `pnpm run --stream` script output, is now written once. Before, every new line also rewrote all the lines already on screen [#17387](https://github.com/pnpm/pnpm/issues/17387).
185
+
186
+ - pnpm now warns when a pnpmfile defines the `importPackage` hook. pnpm does not run this hook. Use the `packageImportMethod` setting to choose how packages are written to `node_modules`.
187
+
188
+ - `pnpm` now warns when a project's `pnpm-workspace.yaml` sets `ci`, `globalShims`, or `tools`, and names where to set them instead. These settings were already ignored there without a warning. `pnpm config set` also refuses to write them to a project's `pnpm-workspace.yaml`.
189
+
190
+ - pnpm no longer documents the `scriptsPrependNodePath` setting, which has no effect. A `pnpm-workspace.yaml` that sets it still loads.
191
+
3
192
  ## 12.15.0
4
193
 
5
194
  This release lets `pnpm-workspace.yaml` delegate settings to other files and config dependencies with `include`, adds `lockfile.integrityTarget: tar` and tar-registry support, accepts zstd-compressed registry responses, and honors `SOURCE_DATE_EPOCH` in `pnpm deploy` and `pnpm install`. It also rejects registry metadata that names a different package than the one requested and speeds up workspace installs and macOS file linking.
package/README.md CHANGED
@@ -24,7 +24,7 @@ Fast, disk space efficient package manager:
24
24
 
25
25
  To quote the [Rush](https://rushjs.io/) team:
26
26
 
27
- > Microsoft uses pnpm in Rush repos with hundreds of projects and hundreds of PRs per day, and we’ve found it to be very fast and reliable.
27
+ > Microsoft uses pnpm in Rush repos with hundreds of projects and hundreds of PRs per day, and we've found it to be very fast and reliable.
28
28
 
29
29
  [![npm version](https://img.shields.io/npm/v/pnpm.svg?label=latest)](https://github.com/pnpm/pnpm/releases/latest)
30
30
  [![OpenCollective](https://opencollective.com/pnpm/backers/badge.svg)](https://opencollective.com/pnpm)
@@ -239,7 +239,7 @@ With pnpm, lodash will be stored in a content-addressable storage, so:
239
239
  1. All the files are saved in a single place on the disk. When packages are installed, their files are linked
240
240
  from that single place consuming no additional disk space. Linking is performed using either hard-links or reflinks (copy-on-write).
241
241
 
242
- As a result, you save gigabytes of space on your disk and you have a lot faster installations!
242
+ As a result, you save gigabytes of space on your disk and you have a lot faster installations.
243
243
  If you'd like more details about the unique `node_modules` structure that pnpm creates and
244
244
  why it works fine with the Node.js ecosystem, read this small article: [Flat node_modules is not the only way](https://pnpm.io/blog/2020/05/27/flat-node-modules-is-not-the-only-way).
245
245
 
@@ -251,7 +251,7 @@ For installation options [visit our website](https://pnpm.io/installation).
251
251
 
252
252
  ## Usage
253
253
 
254
- Just use pnpm in place of npm/Yarn. E.g., install dependencies via:
254
+ Use pnpm in place of npm/Yarn. E.g., install dependencies via:
255
255
 
256
256
  ```
257
257
  pnpm install
@@ -261,7 +261,7 @@ For more advanced usage, read [pnpm CLI](https://pnpm.io/pnpm-cli) on our websit
261
261
 
262
262
  ## Benchmark
263
263
 
264
- pnpm is up to 2x faster than npm and Yarn classic. See all benchmarks [here](https://r.pnpm.io/benchmarks).
264
+ pnpm is up to 2x faster than npm and Yarn classic. See [all benchmarks](https://r.pnpm.io/benchmarks).
265
265
 
266
266
  Benchmarks on an app with lots of dependencies:
267
267
 
package/bin/pnpm.mjs CHANGED
@@ -15,15 +15,13 @@
15
15
  // payload it ships node-gyp in. The placeholder's installs usually do carry
16
16
  // that package, and the binary is taken from there.
17
17
  //
18
- // The download itself is `get-pnpm`, the package behind https://get.pnpm.io,
18
+ // The download itself is `get-pnpm`, the installer in ../../get-pnpm,
19
19
  // which already knows how to verify one; it travels in that same `dist/`
20
- // payload. What is left here is what it does not know: where to download from,
21
- // what credentials to use, and whose signature to trust.
22
- import { Buffer } from 'node:buffer'
20
+ // payload, and reads the registry, credentials, and signing keys from pnpm's
21
+ // and Corepack's config.
23
22
  import { spawnSync } from 'node:child_process'
24
23
  import console from 'node:console'
25
24
  import fs from 'node:fs'
26
- import net from 'node:net'
27
25
  import os from 'node:os'
28
26
  import path from 'node:path'
29
27
  import process from 'node:process'
@@ -37,7 +35,6 @@ const DOWNLOADED_BINARY = path.join(
37
35
  process.platform === 'win32' ? 'pnpm-native.exe' : 'pnpm-native'
38
36
  )
39
37
  const GET_PNPM = new URL('../dist/node_modules/get-pnpm/lib/index.js', import.meta.url)
40
- const DEFAULT_REGISTRY = 'https://registry.npmjs.org'
41
38
 
42
39
  if ('webcontainer' in process.versions) {
43
40
  fail('The pnpm package runs a native binary, which WebContainers cannot execute. Install @pnpm/wasm instead.')
@@ -70,242 +67,23 @@ async function nativeBinary () {
70
67
  return DOWNLOADED_BINARY
71
68
  }
72
69
 
73
- if (process.env.COREPACK_ENABLE_NETWORK === '0') {
74
- fail('Network access is disabled by the environment, so the pnpm binary cannot be downloaded.')
75
- }
76
-
77
70
  const { version } = readWrapperManifest()
78
- console.error(`Downloading the pnpm ${version} binary for ${process.platform}-${process.arch}...`)
79
- const { downloadPnpmExecutable } = await import(GET_PNPM).catch((err) => {
71
+ const { corepackDownloadOptions, downloadPnpmExecutable } = await import(GET_PNPM).catch((err) => {
80
72
  fail(`This copy of the pnpm package is missing the downloader it needs: ${err.message}`)
81
73
  })
82
- const signature = signaturePolicy()
74
+ let options
83
75
  try {
84
- await downloadPnpmExecutable({
85
- version,
86
- destPath: DOWNLOADED_BINARY,
87
- ...registryAccess({ readProjectNpmrc: signature.verifySignature !== false }),
88
- ...signature,
89
- })
76
+ options = corepackDownloadOptions()
90
77
  } catch (err) {
91
- fail(`Could not download the pnpm ${version} binary: ${err.message}`)
92
- }
93
- return DOWNLOADED_BINARY
94
- }
95
-
96
- /**
97
- * The registry to download from, and the headers to send it. Corepack's
98
- * `COREPACK_NPM_REGISTRY` wins. Otherwise npm's sources decide, highest first:
99
- * the environment, the project `.npmrc`, the user `.npmrc`.
100
- *
101
- * The project `.npmrc` is read only with `readProjectNpmrc`, which the caller
102
- * passes while the download is checked against npm's signature. The signature
103
- * pins the bytes, so a repository picks only where they come from. Unchecked,
104
- * it would pick the binary itself, which is kept next to this wrapper and run
105
- * in every later project (GHSA-j2hc-m6cf-6jm8). `get-pnpm` keeps the headers on
106
- * the registry's origin, so a download host it names never receives them.
107
- */
108
- function registryAccess ({ readProjectNpmrc }) {
109
- const corepackRegistry = process.env.COREPACK_NPM_REGISTRY
110
- if (corepackRegistry) {
111
- return { registry: corepackRegistry, headers: corepackHeaders() }
78
+ fail(err.message)
112
79
  }
113
- const npmrc = readUserNpmrc()
114
- if (readProjectNpmrc) {
115
- Object.assign(npmrc, readNpmrc(path.join(projectDir(), '.npmrc'), projectEntry))
116
- }
117
- const registry = npmrc['@pnpm:registry'] || settingFromEnv('registry') || npmrc.registry
118
- if (!registry) {
119
- return { registry: DEFAULT_REGISTRY, headers: corepackHeaders() }
120
- }
121
- return { registry, headers: npmrcHeaders(npmrc, registry) }
122
- }
123
-
124
- /** Credentials for Corepack's registry, read the way Corepack reads its own. */
125
- function corepackHeaders () {
126
- const { COREPACK_NPM_TOKEN, COREPACK_NPM_USERNAME, COREPACK_NPM_PASSWORD } = process.env
127
- if (COREPACK_NPM_TOKEN) {
128
- return { authorization: `Bearer ${COREPACK_NPM_TOKEN}` }
129
- }
130
- if (COREPACK_NPM_USERNAME && COREPACK_NPM_PASSWORD) {
131
- return { authorization: `Basic ${base64(`${COREPACK_NPM_USERNAME}:${COREPACK_NPM_PASSWORD}`)}` }
132
- }
133
- return undefined
134
- }
135
-
136
- function settingFromEnv (key) {
137
- const upper = key.toUpperCase()
138
- const { env } = process
139
- return env[`pnpm_config_${key}`] || env[`PNPM_CONFIG_${upper}`] ||
140
- env[`npm_config_${key}`] || env[`NPM_CONFIG_${upper}`] || undefined
141
- }
142
-
143
- /** The user `.npmrc`, with `${VAR}` placeholders expanded. */
144
- function readUserNpmrc () {
145
- return readNpmrc(
146
- settingFromEnv('userconfig') || path.join(os.homedir(), '.npmrc'),
147
- (key, value) => [expandEnv(key), expandEnv(value)]
148
- )
149
- }
150
-
151
- /**
152
- * A project `.npmrc` entry, which is not expanded. One with a `${VAR}`
153
- * placeholder in a registry setting or a credential is dropped, as pnpm drops
154
- * it: a repository must not route the user's environment to a registry.
155
- */
156
- function projectEntry (key, value, file) {
157
- if (!key.includes('${') && !value.includes('${')) {
158
- return [key, value]
159
- }
160
- if (key.startsWith('//') || key === 'registry' || key.endsWith(':registry')) {
161
- console.error(`Ignored ${key} from ${file}: a project .npmrc cannot use environment variables in registry settings or credentials.`)
162
- }
163
- return undefined
164
- }
165
-
166
- /** Where pnpm reads the project `.npmrc`: the workspace root, or else the current directory. */
167
- function projectDir () {
168
- const cwd = process.cwd()
169
- for (let dir = cwd; ; dir = path.dirname(dir)) {
170
- if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
171
- return dir
172
- }
173
- if (path.dirname(dir) === dir) {
174
- return cwd
175
- }
176
- }
177
- }
178
-
179
- /**
180
- * The top-level settings of the `.npmrc` at `file`, each passed through
181
- * `parseEntry`, which returns the `[key, value]` to keep or `undefined`.
182
- */
183
- function readNpmrc (file, parseEntry) {
184
- const settings = Object.create(null)
185
- let text
186
- try {
187
- text = fs.readFileSync(file, 'utf8')
188
- } catch (err) {
189
- if (err.code !== 'ENOENT' && err.code !== 'EISDIR') {
190
- console.error(`Could not read ${file}, so its settings are not used: ${err.message}`)
191
- }
192
- return settings
193
- }
194
- for (const rawLine of text.split(/\r?\n/)) {
195
- const line = rawLine.trim()
196
- // Settings under a `[section]` are not top-level ones.
197
- if (line.startsWith('[')) {
198
- break
199
- }
200
- const separator = line.indexOf('=')
201
- if (separator === -1 || line.startsWith(';') || line.startsWith('#')) {
202
- continue
203
- }
204
- const entry = parseEntry(line.slice(0, separator).trim(), unquote(line.slice(separator + 1).trim()), file)
205
- if (entry != null) {
206
- settings[entry[0]] = entry[1]
207
- }
208
- }
209
- return settings
210
- }
211
-
212
- function unquote (value) {
213
- const quoted = value.length >= 2 && (value[0] === '"' || value[0] === "'") && value.at(-1) === value[0]
214
- return quoted ? value.slice(1, -1) : value
215
- }
216
-
217
- function expandEnv (value) {
218
- return value.replace(/\$\{([^${}]+)\}/g, (_, name) => process.env[name] ?? '')
219
- }
220
-
221
- /**
222
- * The credentials the `.npmrc` holds for `registry`, looked up as pnpm looks
223
- * them up for an `@pnpm` package: the `@pnpm` scope's own credentials first,
224
- * then the registry-wide ones. Each is the longest `//host[:port]/path/`
225
- * prefix of the registry URL that has any, then the same without the port.
226
- * They are sent only over HTTPS or to the local machine.
227
- */
228
- function npmrcHeaders (npmrc, registry) {
229
- const url = new URL(registry.endsWith('/') ? registry : `${registry}/`)
230
- if (url.protocol !== 'https:' && !(url.protocol === 'http:' && isLoopback(url.hostname))) {
231
- return undefined
232
- }
233
- const prefixes = []
234
- for (const host of new Set([url.host, url.hostname])) {
235
- for (let pathname = url.pathname; ; pathname = pathname.slice(0, pathname.lastIndexOf('/', pathname.length - 2) + 1)) {
236
- prefixes.push(`//${host}${pathname}`)
237
- if (pathname === '/') {
238
- break
239
- }
240
- }
241
- }
242
- for (const scope of [':@pnpm', '']) {
243
- for (const prefix of prefixes) {
244
- const authorization = npmrcAuthorization(npmrc, `${prefix}${scope}`)
245
- if (authorization != null) {
246
- return { authorization }
247
- }
248
- }
249
- }
250
- return undefined
251
- }
252
-
253
- function isLoopback (hostname) {
254
- return hostname === 'localhost' || hostname === '[::1]' ||
255
- (net.isIPv4(hostname) && hostname.startsWith('127.'))
256
- }
257
-
258
- function npmrcAuthorization (npmrc, prefix) {
259
- const token = npmrc[`${prefix}:_authToken`]
260
- if (token) {
261
- return `Bearer ${token}`
262
- }
263
- const auth = npmrc[`${prefix}:_auth`]
264
- if (auth) {
265
- return `Basic ${auth}`
266
- }
267
- const username = npmrc[`${prefix}:username`]
268
- const password = npmrc[`${prefix}:_password`]
269
- if (username && password) {
270
- return `Basic ${base64(`${username}:${Buffer.from(password, 'base64').toString('utf8')}`)}`
271
- }
272
- return undefined
273
- }
274
-
275
- function base64 (text) {
276
- return Buffer.from(text, 'utf8').toString('base64')
277
- }
278
-
279
- /**
280
- * Whose signature over the download to trust, following `COREPACK_INTEGRITY_KEYS`
281
- * exactly as Corepack does: npm's own keys when it is unset, the keys it names
282
- * when it holds a key set, and no signature check at all when it is `0` or
283
- * empty — which is the state a registry that re-publishes packages, and
284
- * therefore carries no npm signatures, already has to be in for Corepack to
285
- * have installed this wrapper from it.
286
- */
287
- function signaturePolicy () {
288
- const configured = process.env.COREPACK_INTEGRITY_KEYS
289
- if (configured == null) {
290
- return {}
291
- }
292
- if (configured === '' || configured === '0') {
293
- return { verifySignature: false }
294
- }
295
- let keys
80
+ console.error(`Downloading the pnpm ${version} binary for ${process.platform}-${process.arch}...`)
296
81
  try {
297
- keys = JSON.parse(configured).npm
82
+ await downloadPnpmExecutable({ version, destPath: DOWNLOADED_BINARY, ...options })
298
83
  } catch (err) {
299
- fail(`COREPACK_INTEGRITY_KEYS is not readable as JSON: ${err.message}`)
300
- }
301
- // An absent or malformed `npm` entry would otherwise be passed on as no keys
302
- // at all, which falls back to npm's own — the opposite of what setting the
303
- // variable asked for. An empty set is left alone: it names no key to trust,
304
- // and Corepack reads it the same way, so every download is refused.
305
- if (!Array.isArray(keys)) {
306
- fail('COREPACK_INTEGRITY_KEYS holds no "npm" key set to verify the pnpm binary against.')
84
+ fail(`Could not download the pnpm ${version} binary: ${err.message}`)
307
85
  }
308
- return { keys }
86
+ return DOWNLOADED_BINARY
309
87
  }
310
88
 
311
89
  function fail (message) {