Commit Graph

10667 Commits

Author SHA1 Message Date
Victor Sumner
6656baaea3 fix(cafs): update locker cache when file exists with correct integrity (#11085)
* fix(cafs): update locker cache when file exists with correct integrity

The CAS locker cache was not updated when a file already existed on disk
with correct integrity. This caused repeated verifyFileIntegrity calls
on subsequent lookups within the same process, adding unnecessary I/O.

* fix(test): assert locker cache value not just key existence

Strengthen the test to verify locker.get() returns the correct
checkedAt timestamp, not just that the key exists.
2026-03-25 02:12:00 +01:00
Victor Sumner
f8e6774273 perf(cafs): optimize hot path string operations (#11086)
* perf(cafs): optimize hot path string operations

Replace path.join with string concatenation in contentPathFromHex and
getFilePathByModeInCafs. These functions are called ~30k times per
install and the simpler string operations avoid path.join's argument
validation overhead.

Increase gunzipSync chunk size from default 16KB to 128KB for faster
tarball decompression with fewer zlib iterations.

* refactor: remove dead Buffer.isBuffer check in tarball path

tarballBuffer is typed as Buffer, so the isBuffer/Buffer.from
fallback was unreachable dead code.

* docs: add comments explaining path.join bypass and chunkSize choice

Address review feedback:
- Explain why string concat is used instead of path.join in CAS hot path
- Document why 128KB chunkSize was chosen (microbenchmarks, diminishing
  returns at larger sizes, bounded memory cost)

* fix: cspell — use 'Benchmarks' instead of 'Microbenchmarks'

* fix(cafs): restore Buffer.isBuffer check for worker thread compatibility

The structured clone algorithm converts Buffer to Uint8Array when sent
via postMessage to worker threads. parseTarball relies on
Buffer.prototype.toString('utf8', ...) which doesn't exist on
Uint8Array — Uint8Array.toString() returns comma-separated decimal
values, causing parseOctal to misparse tar headers.
2026-03-25 02:08:19 +01:00
Zoltan Kochan
439cb684a3 fix: allow benchmark workflow to run against PRs from forks 2026-03-24 21:34:46 +01:00
Zoltan Kochan
eba01e6ad3 fix: handle non-native Error throws in requirePnpmfile (#11081)
* fix: handle non-native Error throws in requirePnpmfile

When a pnpmfile throws a non-native Error value (e.g. a string),
`assert(util.types.isNativeError(err))` crashes pnpm with an
unhelpful assertion failure. Replace the assertion with a guard
that wraps non-native errors into a proper Error and reports them
via PnpmFileFailError.

* fix: improve non-native error wrapping with toError helper
2026-03-24 18:33:03 +01:00
btea
a1807b11d3 fix(workspace): treat catalog refs in workspace overrides as used during cleanupUnusedCatalogs (#11075)
* fix(workspace): treat catalog refs in workspace overrides as used during cleanupUnusedCatalogs

* fix: update

* fix: update
2026-03-24 16:43:13 +01:00
Devendr Mishra
74cdef5e46 fix: resolve patch file paths during pnpm fetch (#11054)
* fix: ensure patches are applied during pnpm fetch

* test: add coverage for patch file resolution during pnpm fetch fallback

* fix(test): remove invalid pnpm property in fetch tests

* fix: resolve lint errors in fetch test
2026-03-24 14:38:18 +01:00
Zoltan Kochan
606f53e78f feat: add dedupePeers option to reduce peer dependency duplication (#11071)
* feat: add `dedupePeers` option to reduce peer dependency duplication

When enabled, this option applies two optimizations to peer dependency resolution:

1. Version-only peer suffixes: Uses name@version instead of full dep paths
   (including nested peer suffixes) when building peer identity hashes.
   This eliminates deeply nested suffixes like (foo@1.0.0(bar@2.0.0)).

2. Transitive peer pruning: Only directly declared peer dependencies are
   included in a package's suffix. Transitive peers from children are not
   propagated upward, preventing combinatorial explosion while maintaining
   correct node_modules layout.

The option is scoped per-project: each workspace project defines a peer
resolution environment, and all packages within that project's tree share
that environment. Projects with different peer versions correctly produce
different instances.

Closes #11070

* fix: pass dedupePeers to getOutdatedLockfileSetting and use spread for lockfile write

The frozen install path (used by approve-builds) calls getOutdatedLockfileSetting
but was missing the dedupePeers parameter. This caused a false LOCKFILE_CONFIG_MISMATCH
error because the lockfile had the key written (as undefined/null via YAML serialization)
while the check function received undefined for the config value.

Fix: pass dedupePeers to the settings check call, and use spread syntax to only write
the dedupePeers key to lockfile settings when it's truthy (avoiding undefined keys).

* fix: write dedupePeers to lockfile like other settings

Write the value directly instead of spread syntax, and use the same
!= null guard pattern as autoInstallPeers in the settings checker.

* test: add integration test for dedupePeers in peerDependencies.ts

* fix: only write dedupePeers to lockfile when enabled

When dedupePeers is false (default), don't write it to lockfile settings.
This avoids adding a new key to every lockfile.

* test: simplify dedupePeers test assertions

* test: check exact snapshot keys in dedupePeers integration test

* test: add workspace test for dedupePeers with different peer versions

* fix: keep transitive peers in suffix with version-only IDs

Instead of pruning transitive peers entirely (which prevented per-project
differentiation), keep them but use version-only identifiers. This way:

- Packages like abc-grand-parent still get a peer suffix when different
  projects provide different peer versions (correct per-project isolation)
- But the suffixes use name@version instead of full dep paths, eliminating
  the nested parentheses that cause combinatorial explosion

* refactor: extract peerNodeIdToPeerId helper in resolvePeers

* refactor: simplify peerNodeIdToPeerId return

* fix: pin peer-a dist tag in dedupePeers tests for CI stability

* fix: address review comments

- Register dedupe-peers in config schema, types, and defaults so
  .npmrc/pnpm-workspace.yaml settings are parsed correctly
- Use Boolean() comparison in settings checker so enabling dedupePeers
  on a pre-existing lockfile triggers re-resolution
- Fix changeset text and test names: transitive peers are still
  propagated, just with version-only IDs (no nested dep paths)
2026-03-24 13:51:17 +01:00
Rohan Santhosh Kumar
833955341d docs: fix duplicated word in DirPatcher comment (#11077)
Co-authored-by: rohan436 <rohan.santhoshkumar@googlemail.com>
2026-03-24 08:59:50 +01:00
Victor Sumner
615bd240eb perf: skip redundant GVS internal linking on warm reinstall (#11073)
* perf: skip redundant GVS internal linking on warm reinstall

When GVS is enabled and the store is warm (added === 0), skip
re-creating internal symlinks, re-linking bins inside the GVS store,
and re-importing packages since they already persist outside
node_modules/. Also filter directPkgDirs by hasBin to avoid
unnecessary package.json reads when linking direct dep bins.

* fix: preserve link: deps in hasBin filter for bin linking

The hasBin filter was dropping directories not present in the dep graph
(e.g. link: dependencies), which would silently break bin linking for
linked local packages that expose binaries.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-03-24 01:22:12 +00:00
Zoltan Kochan
263a8bce95 feat: add pnpm peers check command (#11061)
Adds a `--check-peers` flag to `pnpm list` that detects unmet and
missing peer dependency issues by reading the lockfile. This allows
users to check for peer dependency problems without triggering a
full resolution, which is especially useful in CI or after pulling
a lockfile from another developer.

Closes #7087
2026-03-23 10:31:09 +01:00
Zoltan Kochan
54ffb948bd refactor: add recursiveByDefault property to CommandDefinition (#11062)
Replace the hardcoded command name list in main.ts with a declarative
recursiveByDefault property on CommandDefinition. Each command that
should run workspace-wide by default now exports this property.

Also adds recursiveByDefault to list, ll, and why commands.
2026-03-22 16:04:20 +01:00
zybo
e9318ce974 fix: use ENOENT check instead of which.sync for command-not-found on Windows (#11004)
* fix: use ENOENT check instead of which.sync for command-not-found on Windows

On Windows, `which.sync()` only checks if a command exists in PATH,
not whether it actually executed successfully. This caused false
"Command not found" errors when a command exists but exits with a
non-zero code. Use the same `spawn ENOENT` check across all platforms,
which is reliable thanks to cross-spawn used by execa.

Closes #11000

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve prependPaths against exec prefix for correct Windows command lookup

The previous ENOENT-only approach doesn't work on Windows because execa 9.x
uses cross-spawn only for command parsing, not spawning. This means cross-spawn's
ENOENT hook (hookChildProcess) never fires, and non-existent commands wrapped as
`cmd.exe /c <command>` exit with code 1 instead of emitting ENOENT.

Restore the which.sync fallback for Windows, but fix the original #11000 bug by
resolving relative prependPaths (like ./node_modules/.bin) against the exec prefix
instead of relying on process.cwd(). This ensures correct path resolution in
--filter contexts where the command runs in a different package directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: zubeyralmaho <zubeyralmaho@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 13:28:54 +01:00
Zoltan Kochan
f7bb668100 docs: add PR conflict resolution guide and helper script (#11060)
Add a "Resolving Conflicts in GitHub PRs" section to AGENTS.md with
step-by-step instructions for force-fetching refs, rebasing, resolving
lockfile conflicts, and verifying mergeability.

Add shell/resolve-pr-conflicts.sh that automates the workflow: fetches
metadata, force-updates the base ref, rebases, auto-resolves lockfile
conflicts via pnpm install, force-pushes, and verifies the result.
2026-03-22 13:22:11 +01:00
Alessio Attilio
d5be835735 feat: implement native recursive version command (#10879)
* feat: implement non-interactive version command

* fix: address review issues in version command

- Fix changeset package name to @pnpm/releasing.commands
- Use writeProjectManifest instead of writeJsonFile to preserve formatting
- Remove dead updateWorkspaceDependencies placeholder function
- Remove unused imports (path, ProjectManifest, writeJsonFile)
- Add expect.assertions(1) to prevent silent test pass on no-throw

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 11:51:54 +00:00
Sumit Kumar
449dacf02e fix(link-bins): apply bin ownership overrides in conflict resolution (#10975)
BIN_OWNER_OVERRIDES was only used in checkGlobalBinConflicts for global
installs. This change applies the same ownership rules in
compareCommandsInConflict so that conflict resolution is consistent
between global conflict checking and actual bin linking.

This ensures packages like npm get priority for bins like npx even in
non-global installs.

Closes #10850

* test(link-bins): add missing fixture for bin-owner-override test

* refactor: extract BIN_OWNER_OVERRIDES to @pnpm/package-bins

Move shared logic to avoid code duplication between link-bins
and checkGlobalBinConflicts.

* fix(link-bins): use regex for Windows path compatibility in test

* refactor(link-bins): remove redundant ownName field

pkgOwnsBin already handles the binName === pkgName case, making
the ownName field and its associated checks redundant.

* Change versioning to patch for bins resolver and linker

Added BIN_OWNER_OVERRIDES and pkgOwnsBin to @pnpm/bins.resolver for improved conflict resolution in bin linking.

* test: remove node_modules from bin-owner-override fixture

Move fixture packages to the directory root instead of nesting them
inside node_modules, avoiding committing node_modules to the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 11:50:56 +00:00
Zoltan Kochan
421ceac0b3 chore: compile pnpm CLI bundle before tests that use it (#11059)
Packages whose tests spawn the local pnpm CLI (pnpm/bin/pnpm.mjs) need
the bundle (pnpm/dist/pnpm.mjs) to exist. Add `pnpm --filter pnpm run
compile` to their test scripts so the bundle is built before tests run.
2026-03-22 10:56:36 +01:00
Brandon Cheng
6557dc09f9 fix: clearCache function in @pnpm/resolving.npm-resolver (#11050)
* test: add test for `clearCache` function in `@pnpm/resolving.npm-resolver`

* fix: clear pMemoize when clearing NPM resolver `clearCache` function
2026-03-22 01:48:25 +01:00
Brandon Cheng
f98a2db373 fix: invalid specifiers for peers on all non-exact version selectors (#11049)
* test: add test for hoist peers when given all range version selectors

* fix: invalid specifiers for peers on non-string version selectors

In tests, the bare specifier for the `@pnpm.e2e/peer-a` dependency
became ` || 1.0.0`. This was because the `versions` array could be
empty, causing the `.join(' || ')` operation to execute on a holey
array.

This caused a test in `installing/commands/test/update/update.ts` to
fail.
2026-03-22 01:47:12 +01:00
Brandon Cheng
831f574330 fix: propagate error cause when throwing PnpmError in @pnpm/npm-resolver (#10990)
* fix: show error cause when failing to read metadata

* fix: correct changeset package name and add cause assertion tests

- Fix changeset to reference @pnpm/resolving.npm-resolver (not @pnpm/npm-resolver)
- Add PnpmError cause unit tests in @pnpm/error
- Fix npm-resolver tests to actually verify cause on thrown errors
  (.toThrow() only checks message, not cause/hint/code properties)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:59:52 +01:00
Zoltan Kochan
6586604b19 refactor: remove hardcoded runtime bin workaround from linker (#11058)
Since v11 uses a new store version, all runtime packages (node, deno, bun)
have a generated package.json with bin fields. The hardcoded switch block
in the linker is no longer needed.

Also moves getNodeBinsForCurrentOS, getDenoBinLocationForCurrentOS, and
getBunBinLocationForCurrentOS out of @pnpm/constants into their respective
resolver packages, since each is only used in one place.
2026-03-22 00:21:55 +01:00
Khải
2e9101d724 chore(typescript): make typecheck threading configurable (#11057)
* feat: make tsgo --singleThreaded configurable via env var

Set PNPM_TYPECHECK_SINGLE_THREADED=false to allow tsgo to use multiple
threads during typechecking. Defaults to true (preserving current
behavior) for environments where memory is constrained.

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* feat: replace binary PNPM_TYPECHECK_SINGLE_THREADED with configurable PNPM_TYPECHECK_THREADING

Replace the binary on/off env var with PNPM_TYPECHECK_THREADING that
accepts three modes: auto, single-threaded, multi-threaded.

Resolution order:
1. PNPM_TYPECHECK_THREADING env var
2. .pnpm-typecheck.json config file (git-ignored, per-developer)
3. Default: "auto"

Auto mode checks system memory: <8GB uses single-threaded, >=8GB uses
multi-threaded.

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* refactor: simplify threading mode resolution

Move validation into the default branch of the switch, removing the
ThreadingMode type and VALID_THREADING_MODES set. readThreadingMode now
returns { mode: string, source: string } so error messages indicate
where the invalid value came from.

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* refactor: error on invalid threading mode instead of warning

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* refactor: remove auto-detect log message

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* refactor: use string literal union type for threading source

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* revert: revert source type back to string

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* refactor: remove try-catch, let parse errors propagate to user

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* refactor: simplify auto case to return directly

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* fix: normalize and validate threading mode input

Trim whitespace and lowercase env var and config file values so that
empty/whitespace-only strings fall through to the default, and
case-insensitive values like "Auto" or "SINGLE-THREADED" are accepted.

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* style: use single quotes for string without interpolation

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

* feat: support .local-settings dir for typecheck config shared across worktrees

Read pnpm-typecheck.json from .local-settings/ directory (with fallback
to the old .pnpm-typecheck.json location). The worktree-new script now
symlinks .local-settings alongside .claude so the config is shared
across all worktrees without manual copying.

https://claude.ai/code/session_01MRhydwHLce7vwZDkf1yvzE

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 00:14:40 +01:00
Trevor Burnham
d0aea45b28 feat: warn when optimistic-repeat-install skips shouldRefreshResolution hooks (#10995)
* feat: warn when optimistic-repeat-install skips shouldRefreshResolution hooks

* Fix log message for optimistic repeat install

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-03-21 23:52:01 +01:00
Brandon Cheng
41dc031a67 test: use resolution-mode=highest in tests (#10989)
* fix: configure default resolution-mode to highest in pkg-manager/core

* test: update catalog tests for resolution-mode=highest

* test: fix `--fix-lockfile` test for new resolution-mode default

```
  ● fix broken lockfile with --fix-lockfile

    expect(received).toBeTruthy()

    Received: undefined

      55 |   const lockfile: LockfileFile = readYamlFileSync(WANTED_LOCKFILE)
      56 |   expect(Object.keys(lockfile.packages as PackageSnapshots)).toHaveLength(2)
    > 57 |   expect(lockfile.packages?.['@types/semver@5.3.31']).toBeTruthy()
         |                                                       ^
      58 |   expect(lockfile.packages?.['@types/semver@5.3.31']?.resolution).toEqual({
      59 |     integrity: 'sha512-WBv5F9HrWTyG800cB9M3veCVkFahqXN7KA7c3VUCYZm/xhNzzIFiXiq+rZmj75j7GvWelN3YNrLX7FjtqBvhMw==',
      60 |   })

      at Object.<anonymous> (test/install/fixLockfile.ts:57:55)
```

* test: fix lockfile conflict test

  ● a lockfile v6 with merge conflicts is autofixed

    expect(received).toHaveProperty(path, value)

    Expected path: "version"

    Expected value: "100.1.0"
    Received value: "101.0.0"

      1284 |
      1285 |   const lockfile = project.readLockfile()
    > 1286 |   expect(lockfile.importers?.['.'].dependencies?.['@pnpm.e2e/dep-of-pkg-with-1-dep']).toHaveProperty('version', '100.1.0')
           |                                                                                       ^
      1287 | })
      1288 |
      1289 | test('a lockfile with duplicate keys is fixed', async () => {

      at Object.<anonymous> (test/lockfile.ts:1286:87)

* test: fix deploy shared lockfile test

  ● deploy with a shared lockfile that has peer dependencies suffix in workspace package dependency paths

    expect(received).toMatchObject(expected)

    - Expected  - 6
    + Received  + 1

    @@ -1,11 +1,11 @@
      Object {
        "importers": Object {
          "packages/project-0": Object {
            "dependencies": Object {
              "project-1": Object {
    -           "version": "file:packages/project-1(is-negative@1.0.0)(project-2@file:packages/project-2(is-positive@1.0.0))",
    +           "version": "file:packages/project-1(is-negative@2.1.0)(project-2@file:packages/project-2(is-positive@1.0.0))",
              },
              "project-2": Object {
                "version": "file:packages/project-2(is-positive@1.0.0)",
              },
            },
    @@ -31,13 +31,8 @@
              "type": "directory",
            },
          },
        },
        "snapshots": Object {
    -     "project-1@file:packages/project-1(is-negative@1.0.0)(project-2@file:packages/project-2(is-positive@1.0.0))": Object {
    -       "dependencies": Object {
    -         "project-2": "file:packages/project-2(is-positive@1.0.0)",
    -       },
    -     },
          "project-2@file:packages/project-2(is-positive@1.0.0)": Object {},
        },
      }

      950 |     workspaceDir: process.cwd(),
      951 |   })
    > 952 |   expect(assertProject('.').readLockfile()).toMatchObject({
          |                                             ^
      953 |     importers: {
      954 |       'packages/project-0': {
      955 |         dependencies: {

      at Object.<anonymous> (test/shared-lockfile.test.ts:952:45)

* test: fix injectLocalPackages test
2026-03-21 23:21:04 +01:00
Brandon Cheng
021f70d0b0 fix: handle non-string version selectors in hoistPeers (#11048)
* test: add test for version selector with weight in hoistPeers

* fix: handle non-string version selectors in hoistPeers
2026-03-21 23:17:24 +01:00
Zoltan Kochan
8d4119608d feat: add pn and pnx short aliases (#11052)
- `pn` is an alias for `pnpm`
- `pnx` is an alias for `pnpx` (i.e. `pnpm dlx`)

Supported across all installation methods:
- npm install: via bin field in package.json
- @pnpm/exe: hardlink (pn) + shell scripts (pnpx, pnx) created by setup.js
- pnpm setup: shell scripts for pn, pnpx, pnx
- Corepack: via bin field (same as npm install)
- curl install: via pnpm setup
2026-03-21 22:11:37 +01:00
Zoltan Kochan
c296d17c78 fix: revert some not needed info messages
reverts some logs added via https://github.com/pnpm/pnpm/pull/11039
2026-03-21 22:09:48 +01:00
btea
2f98ec84f4 feat: store prune displays the total size of removed files (#11047)
* feat: store prune displays the total size of removed files

* test: update
2026-03-21 20:01:58 +01:00
Zoltan Kochan
cd2dc7d481 refactor: prefix internal scripts with . to hide them (#11051)
* fix: ensure PNPM_HOME/bin is in PATH during pnpm setup

When upgrading from old pnpm (global bin = PNPM_HOME) to new pnpm
(global bin = PNPM_HOME/bin), `pnpm setup` would fail because the
spawned `pnpm add -g` checks that the global bin dir is in PATH.
Prepend PNPM_HOME/bin to PATH in the spawned process env so the
check passes during the transition.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: update pnpm to v11 beta 2

* chore: update pnpm to v11 beta 2

* chore: update pnpm to v11 beta 2

* chore: update pnpm to v11 beta 2

* fix: lint

* refactor: rename _-prefixed scripts to .-prefixed scripts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update root package.json to use .test instead of _test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: update action-setup

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:30:56 +01:00
Zoltan Kochan
0fedb7a7ef revert: "chore: update pnpm to v11 beta 2"
This reverts commit b3b6e348e0.
2026-03-21 13:40:53 +01:00
Zoltan Kochan
b3b6e348e0 chore: update pnpm to v11 beta 2 2026-03-21 13:39:26 +01:00
Zoltan Kochan
8b2ded30d9 chore(release): 11.0.0-beta.2 v11.0.0-beta.2 2026-03-21 13:32:00 +01:00
Zoltan Kochan
bb9226cd98 fix: ensure PNPM_HOME/bin is in PATH during pnpm setup
When upgrading from old pnpm (global bin = PNPM_HOME) to new pnpm
(global bin = PNPM_HOME/bin), `pnpm setup` would fail because the
spawned `pnpm add -g` checks that the global bin dir is in PATH.
Prepend PNPM_HOME/bin to PATH in the spawned process env so the
check passes during the transition.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:30:07 +01:00
Zoltan Kochan
0ba71da576 chore(release): 11.0.0-beta.1 v11.0.0-beta.1 2026-03-21 12:57:49 +01:00
Zoltan Kochan
9b801c888d fix: check allowBuild for packages with cached side-effects (#11039)
Closes #11035

## Summary

### Root cause fix: don't apply cached side-effects for unapproved packages
When importing packages from the store, side-effects cache was applied for any package not explicitly denied (`allowBuild !== false`). This meant unapproved packages (`allowBuild === undefined`) got cached build artifacts, setting `isBuilt: true` and bypassing the `allowBuild` check in `buildModules`.

**Fix:** Only apply side-effects cache when `allowBuild` returns `true` (explicitly approved). Changed in three locations:
- `installing/deps-restorer/src/index.ts` (isolated linker)
- `installing/deps-restorer/src/linkHoistedModules.ts` (hoisted linker)
- `installing/deps-installer/src/install/link.ts` (non-headless install)

### Revocation detection
When a package's build approval is revoked between installs (was `true` in `.modules.yaml`, now undefined), detect it in `mutateModules` and add to `ignoredBuilds` so `strictDepBuilds` fails.

### Status messages in `_rebuild`
Users now see what happened to each package during rebuild:
- `pkg@version: built successfully`
- `pkg@version: skipped (no build scripts)`
- `pkg@version: skipped (not allowed)`
- `pkg@version: reused from store cache`

And during install:
- `pkg@version: reused from store (side effects cache)`

### `buildSelectedPkgs` fixes
- Preserve `storeDir`, `virtualStoreDir`, `virtualStoreDirMaxLength` from existing `.modules.yaml` instead of overwriting with config-derived values (which caused "reinstall from scratch" prompt)
- Write `allowBuilds` to `.modules.yaml` so GVS doesn't detect a mismatch on next install
- Merge `ignoredBuilds` with existing entries for packages not being rebuilt
2026-03-21 12:51:24 +01:00
Zoltan Kochan
9fc552d37a fix: update GVS symlinks after approve-builds by running install (#11043)
Fixes #11042

- **Root cause**: When `enableGlobalVirtualStore` is true and `allowBuilds` is not configured, `createAllowBuildFunction()` returned `undefined`, causing all GVS hashes to include `ENGINE_NAME`. When `approve-builds` later configured `allowBuilds`, the hash didn't change because the engine was already included.
- **Fix**: Default `allowBuilds` to `{}` in GVS mode so hashes are engine-agnostic by default, and have `approve-builds` call `install.handler()` in GVS mode instead of the low-level `install()` function, so it properly handles workspaces and updates symlinks.
- **Refactor**: Broke circular dependencies between `building/commands`, `installing/commands`, and `global/commands` using dependency injection via a `commands` map passed as the third argument to command handlers. Added `CommandHandler` and `CommandHandlerMap` types to `@pnpm/cli.command`.

## Changes

### Architecture
- Command handlers now receive a `commands` map as an optional third argument `(opts, params, commands?)`
- The CLI dispatcher in `main.ts` passes the full commands map to every handler
- Handlers that need other commands (e.g., `globalAdd` needs `approve-builds`, `recursive` needs `rebuild`) access them from this map
- This replaces direct cross-package imports that would create circular dependencies

### Packages changed
- `@pnpm/cli.command` — new `CommandHandler` and `CommandHandlerMap` types
- `@pnpm/building.commands` — `approve-builds` uses `install.handler` for GVS
- `@pnpm/global.commands` — removed `building/commands` dependency; receives `approve-builds` via commands map
- `@pnpm/installing.commands` — receives `rebuild` via commands map instead of direct import
- `@pnpm/installing.deps-installer` / `@pnpm/installing.deps-restorer` — default `allowBuilds` to `{}` in GVS mode
- `pnpm` CLI — dispatcher passes commands map to all handlers
2026-03-21 12:50:46 +01:00
Brandon Cheng
659bb13793 test: wait for Verdaccio to come online before running tests (#11037) 2026-03-21 11:56:09 +01:00
Zoltan Kochan
f0ae1b97d7 fix: store global binaries in PNPM_HOME/bin subdirectory (#11038)
Previously, globally installed binaries were placed directly in
PNPM_HOME, which also contains internal directories (global/, store/).
This polluted shell autocompletion with non-executable entries.

Now binaries are stored in PNPM_HOME/bin, keeping the PATH clean.

Closes #10986
2026-03-20 18:16:52 +01:00
Zoltan Kochan
0407e36ab2 feat: support hidden scripts starting with '.' (#11041)
Scripts starting with '.' are hidden:
- Cannot be run directly via 'pnpm run .script' (throws HIDDEN_SCRIPT error)
- Can only be called from other scripts (detected via npm_lifecycle_event)
- Omitted from 'pnpm run' listing

This allows packages to have internal scripts that are implementation
details, preventing accidental direct execution. Similar to how
dotfiles are hidden in file systems.
2026-03-20 17:59:11 +01:00
Zoltan Kochan
996284f8cc feat(approve-builds): positional args, !pkg deny syntax, and auto-populate allowBuilds (#11030)
### `pnpm approve-builds` positional arguments
- `pnpm approve-builds foo` — approves `foo`, leaves everything else untouched
- `pnpm approve-builds !bar` — denies `bar`, leaves everything else untouched
- `pnpm approve-builds foo !bar` — approves `foo`, denies `bar`
- Only mentioned packages are modified; unmentioned packages remain pending
- `--all` cannot be combined with positional arguments
- Contradictory arguments (`pkg !pkg`) are rejected

### Auto-populate `allowBuilds` during install
- When `pnpm install` encounters packages with build scripts that aren't yet in `allowBuilds`, they are automatically written to `pnpm-workspace.yaml` with a `'set this to true or false'` placeholder
- Users can then edit the config directly instead of running `approve-builds`
- The placeholder behaves like a missing entry: builds are skipped and `strictDepBuilds` still fails
- Existing `allowBuilds` entries are preserved (only new packages get placeholders)
2026-03-20 14:58:56 +01:00
Rohan Santhosh Kumar
f7960244ea docs(contributing): fix commit message guideline wording (#11036)
Co-authored-by: rohan436 <rohan.santhoshkumar@googlemail.com>
2026-03-20 11:23:09 +01:00
Zoltan Kochan
cd0e887db3 refactor: remove unused @pnpm/fs.msgpack-file package and lockfile-directory setting (#11033)
Remove the @pnpm/fs.msgpack-file package which was never imported in
source code (only in its own tests). Also remove the deprecated
lockfile-directory CLI option alias — users should use lockfile-dir.
2026-03-20 00:38:02 +01:00
Zoltan Kochan
a4a691a801 chore: update symlink-dir 2026-03-19 23:34:53 +01:00
Zoltan Kochan
0d88df854f chore: update all dependencies to latest versions (#11032)
* chore: update all dependencies to latest versions

Update all outdated dependencies across the monorepo catalog and fix
breaking changes from major version bumps.

Notable updates:
- ESLint 9 → 10 (fix custom rule API, disable new no-useless-assignment)
- @stylistic/eslint-plugin 4 → 5 (auto-fixed indent changes)
- @cyclonedx/cyclonedx-library 9 → 10 (adapt to removed SPDX API)
- esbuild 0.25 → 0.27
- TypeScript 5.9.2 → 5.9.3
- Various @types packages, test utilities, and build tools

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update unified/remark/mdast imports for v11/v4 API changes

Update imports in get-release-text for the new ESM named exports:
- mdast-util-to-string: default → { toString }
- unified: default → { unified }

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve typecheck errors from dependency updates

- isexe v4: use named import { sync } instead of default export
- remark-parse/remark-stringify v11: add vfile as packageExtension
  dependency so TypeScript can resolve type declarations
- get-release-text: remove unused @ts-expect-error directives

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert runtime dependency major version bumps

Revert major version bumps for runtime dependencies that are bundled
into pnpm to fix test failures where pnpm add silently fails:
- bin-links: keep ^5.0.0 (was ^6.0.0)
- cli-truncate: keep ^4.0.0 (was ^5.2.0)
- delay: keep ^6.0.0 (was ^7.0.0)
- filenamify: keep ^6.0.0 (was ^7.0.1)
- find-up: keep ^7.0.0 (was ^8.0.0)
- isexe: keep 2.0.0 (was 4.0.0)
- normalize-newline: keep 4.1.0 (was 5.0.0)
- p-queue: keep ^8.1.0 (was ^9.1.0)
- ps-list: keep ^8.1.1 (was ^9.0.0)
- string-length: keep ^6.0.0 (was ^7.0.1)
- symlink-dir: keep ^7.0.0 (was ^9.0.0)
- terminal-link: keep ^4.0.0 (was ^5.0.0)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: restore runtime dependency major version bumps

Re-apply all runtime dependency major version bumps that were
previously reverted. All packages maintain their default exports
except isexe v4 which needs named imports.

Updated runtime deps:
- bin-links: ^5.0.0 → ^6.0.0
- cli-truncate: ^4.0.0 → ^5.2.0
- delay: ^6.0.0 → ^7.0.0
- filenamify: ^6.0.0 → ^7.0.1
- find-up: ^7.0.0 → ^8.0.0
- isexe: 2.0.0 → 4.0.0 (fix: use named import { sync })
- normalize-newline: 4.1.0 → 5.0.0
- p-queue: ^8.1.0 → ^9.1.0
- ps-list: ^8.1.1 → ^9.0.0
- string-length: ^6.0.0 → ^7.0.1
- symlink-dir: ^7.0.0 → ^9.0.0
- terminal-link: ^4.0.0 → ^5.0.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert tempy to 3.0.0 to fix bundle hang

tempy 3.2.0 pulls in temp-dir 3.0.0 which uses async fs.realpath()
inside its module init. When bundled by esbuild into the __esm lazy
init pattern, this causes a deadlock during module initialization,
making the pnpm binary hang silently on startup.

Keeping tempy at 3.0.0 which uses temp-dir 2.x (sync fs.realpathSync).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add comment explaining why tempy cannot be upgraded

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert nock to 13.3.4 for node-fetch compatibility

nock 14 changed its HTTP interception mechanism in a way that doesn't
properly intercept node-fetch requests, causing audit tests to hang
waiting for responses that are never intercepted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add comment explaining why nock cannot be upgraded

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update symlink-dir imports for v10 ESM named exports

symlink-dir v10 removed the default export and switched to named
exports: { symlinkDir, symlinkDirSync }.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert @typescript/native-preview to working version

Newer tsgo dev builds (>= 20260318) have a regression where
@types/node cannot be resolved, breaking all node built-in types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: vulnerabilities

* fix: align comment indentation in runLifecycleHook

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: pin msgpackr to 1.11.8 for TypeScript 5.9 compatibility

msgpackr 1.11.9 has broken type definitions that use Iterable/Iterator
without required type arguments, causing compile errors with TS 5.9.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 23:28:53 +01:00
Zoltan Kochan
812cae93d6 test(releasing): fix 2026-03-19 19:02:17 +01:00
Zoltan Kochan
6d0eeeeafb chore: remove npm_config_verify_deps_before_run from extraEnv (#11029)
This was marked for removal in v11. Only the pnpm_config_ prefixed
version is kept.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:53:42 +01:00
Zoltan Kochan
f1dbe4edf8 feat!: stop falling back to npm CLI for unimplemented commands (#10642)
* feat!: stop falling back to npm CLI

* chore: update pnpm-lock.yaml

* fix: resolve conflicts

* revert: keep falling back to npm CLI for config get/set

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: lint errors in notImplemented.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: update changeset description

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: suggest using npm CLI in not-implemented error message

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review comments

- Check combined stdout+stderr in test for ERR_PNPM_NOT_IMPLEMENTED
- Skip unknown options validation for not-implemented commands
- Mention aliases (s, se, v) in changeset

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:46:00 +01:00
zybo
194d36856e feat(ci): implement pnpm ci command (#11003)
* feat(ci): implement pnpm ci command

This implements the `pnpm ci` (clean-install) command, which is similar
to `npm ci`. The command:

- Removes `node_modules` before installation (clean install)
- Installs dependencies from the lockfile with `--frozen-lockfile`
- Fails if the lockfile is missing or out of sync with `package.json`
- Supports workspaces (removes `node_modules` from all workspace projects)

This is useful for CI/CD environments where you want to ensure
reproducible builds.

Aliases: `pnpm clean-install`, `pnpm ic`, `pnpm install-clean`

Closes #6100

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update tsconfig.json references

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(ci): simplify pnpm ci to compose clean + install --frozen-lockfile

Per maintainer feedback, simplify the ci command to just call
`clean.handler()` then `install.handler()` with frozenLockfile: true,
following the same composition pattern as installTest.

Moved ci command from installing/commands to pnpm/src/cmd/ where it
can import both clean and install handlers directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(ci): remove ci stub from installing/commands

The ci command now lives entirely in pnpm/src/cmd/ci.ts,
so the old stub in installing/commands is no longer needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(ci): rename ci.ts to cleanInstall.ts

Per reviewer feedback, use the full command name for the file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): use non-dotfile marker in ci test

The clean command preserves dotfiles in node_modules (except pnpm's
own .bin, .modules.yaml, .pnpm), so the test marker starting with "."
was not being removed. Renamed to a regular file name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: zubeyralmaho <zubeyralmaho@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 14:43:05 +01:00
Zoltan Kochan
94ca54e20b fix: reuse sortLockfileKeys for env lockfile sorting (#11026)
Replace the custom sortEnvLockfile function with the shared
sortLockfileKeys, ensuring env lockfile fields are sorted
consistently with the main lockfile document.
2026-03-19 13:27:29 +01:00
Zoltan Kochan
1a09015839 chore: update pnpm to v11 beta 0 2026-03-19 11:49:56 +01:00
Zoltan Kochan
a798efeb4d chore(release): 11.0.0-beta.0 v11.0.0-beta.0 2026-03-19 11:19:27 +01:00