Commit Graph

69 Commits

Author SHA1 Message Date
Zoltan Kochan
b741d91e67 chore(release): 11.5.0 (#12068) 2026-05-29 17:26:13 +02:00
Marvin Hagemeister
49e6074644 test: replace @pnpm/registry-mock with an in-repo in-process registry (#11927)
Replace the external `@pnpm/registry-mock` (Verdaccio) test dependency with an in-repo, in-process registry that serves package fixtures to **both** the pacquet Rust tests and the pnpm CLI (Jest) tests. No separately managed registry process is needed.

### How it works

- **Fixtures** live at `registry/.fixtures/packages/<name>/<version>/…`, moved verbatim from [`pnpm/registry-mock`](https://github.com/pnpm/registry-mock) (keyed by each `package.json`'s `name`+`version`).
- **`pnpm-registry-fixtures`** builds verdaccio-shaped storage from those fixtures; the in-tree **`pnpm-registry`** crate serves it.
  - Files whose names differ only by case (`@pnpm.e2e/with-same-file-in-different-cases`) and `bundleDependencies` trees are composed **in memory** by the builder, since neither can be committed to the working tree.
- **pacquet**: `pacquet-testing-utils`' `TestRegistry` starts the server lazily (once per process) in proxy mode, serving `@pnpm.e2e` fixtures locally and falling through to the npm uplink for real packages (`is-positive`, `is-negative`, …) — matching how registry-mock behaved.
- **pnpm CLI**: the `with-registry` Jest `globalSetup` builds storage from the fixtures via the new `pnpm-registry-prepare` binary (built from source in the Test CI job) and serves it with `pnpm-registry`. `REGISTRY_MOCK_PORT` / `REGISTRY_MOCK_CREDENTIALS` / `getIntegrity` now come from `@pnpm/testing.registry-mock`.

### Result

`@pnpm/registry-mock` is removed from every manifest, the catalog, and `packageExtensions`; `cargo test` / `cargo nextest run` / `just test` and the pnpm CLI Jest suites all run registry-backed tests without launching Verdaccio.
2026-05-29 14:35:45 +02:00
Abdullah Alaqeel
2cadfb5d3d refactor: replace enquirer with @inquirer/prompts (#11942)
Replaces the unmaintained `enquirer` package with `@inquirer/prompts` for all interactive CLI prompts. Fixes the `update -i` scrolling overflow bug where long choice lists were clipped in the terminal.

Fixes #6643

## User-facing changes

- **`pnpm update -i` / `pnpm update -i --latest`**: Scrolling now works correctly when many packages are available; the new library uses visual-line-aware pagination via `usePagination`
- **`pnpm audit --fix -i`**: Same scrolling fix for vulnerability selection
- **`pnpm approve-builds`**: Interactive build approval prompts updated
- **`pnpm patch`**: Version selection and "apply to all" prompts updated
- **`pnpm patch-remove`**: Patch removal selection updated
- **`pnpm publish`**: Branch confirmation prompt updated
- **`pnpm login`**: Credential prompts updated
- **`pnpm run` / `pnpm exec`** (with `verifyDepsBeforeRun=prompt`): Confirmation prompt updated

## Internal changes

- `OtpEnquirer` DI interface changed from `{ prompt }` to `{ input }`
- `LoginEnquirer` DI interface changed from `{ prompt }` to `{ input, password }`
- `enquirer` removed from catalog and all 8 package.json files
- `@inquirer/prompts` v8.4.3 added to catalog and all 8 package.json files
- Removed `OtpPromptOptions` and `OtpPromptResponse` exports from `@pnpm/network.web-auth` (no longer needed)

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-05-28 17:53:52 +02:00
Zoltan Kochan
72d997cc34 chore(release): 11.4.0 (#11989) 2026-05-27 15:15:01 +02:00
Zoltan Kochan
f2a4d2caef chore(release): 11.3.0 (#11894) 2026-05-24 02:23:07 +02:00
Zoltan Kochan
501681044e chore(release): 11.2.2 (#11817) 2026-05-21 15:45:17 +02:00
Zoltan Kochan
11a43b15da chore(release): 11.2.1 (#11777) 2026-05-20 16:51:13 +02:00
Zoltan Kochan
0fb723323f chore(release): 11.2.0 (#11764) 2026-05-20 12:41:09 +02:00
Zoltan Kochan
cd80b2c8ae chore(release): 11.1.3 (#11717) 2026-05-18 15:42:32 +02:00
Zoltan Kochan
5dc8be8a42 fix(graph-hasher): resolve GVS engine per-snapshot for runtime-pinned deps (#11693)
Closes #11690.

A dependency that declares `engines.runtime` in its manifest carries the desugared `dependencies.node: 'runtime:<version>'` pin in the lockfile, and pnpm's bin linker spawns that dep's lifecycle scripts through the pinned Node downloaded into `<pkgDir>/node_modules/node/`. The GVS hash and the side-effects-cache key prefix were still anchored to the install-wide runtime — so the pinning snapshot's slot encoded the wrong Node major, and a reinstall on the same host could read the cached side-effects under a key whose `<platform>;<arch>;node<major>` triple disagreed with the Node the build actually ran on.

Per-snapshot resolution now matches what `bins/linker` already does on a per-package basis: a snapshot's own pin wins; the install-wide value (from #11689's `findRuntimeNodeVersion`) is the fallback.

### TypeScript

- `deps/graph-hasher/src/index.ts:72-77` — adds `readSnapshotRuntimePin(children)`: pulls the bare Node version from a graph node's `children.node` entry when that points at a `node@runtime:<version>` snapshot. Factors out a small `extractRuntimeNodeVersion(snapshotKey)` parser shared with `findRuntimeNodeVersion`.
- `deps/graph-hasher/src/index.ts:115-116,245-246` — `calcDepState` and `calcGraphNodeHash` consult `readSnapshotRuntimePin(graph[depPath].children)` first and only fall back to the install-wide `nodeVersion` parameter when the snapshot doesn't pin its own Node. No caller changes required — install-wide fallback continues to be computed via `findRuntimeNodeVersion(Object.keys(graph))` at each call site.
- **Refactor (separate commit):** `findRuntimeNodeVersion` moved from `@pnpm/engine.runtime.system-node-version` to `@pnpm/deps.graph-hasher` (along with the new `readSnapshotRuntimePin`). `system-node-version` is about probing the *host* Node — `getSystemNodeVersion`, `engineName`. The lockfile-shape parsers fit better next to the package that actually composes the engine string. Every caller already depended on graph-hasher, so no new deps; six packages drop the now-unused dependency on `system-node-version`.

### Pacquet

- `pacquet/crates/package-manager/src/install_frozen_lockfile.rs:1309-1345` — new `find_own_runtime_node_major(snapshot)` reads a snapshot's `dependencies` for a `node` entry with `Prefix::Runtime`, returning the bare major.
- `pacquet/crates/package-manager/src/virtual_store_layout.rs:178-205` — `VirtualStoreLayout::new` resolves engine per-snapshot inside the hash loop via `engine_name(own_major, None, None)` when the snapshot pins, otherwise inherits the install-wide `engine` argument.

### Migration

Snapshots of dependencies that declare their own `engines.runtime` re-hash under that dep's pinned Node instead of the install-wide value. Old slots become prune-eligible on next install.
2026-05-17 13:25:05 +02:00
Zoltan Kochan
3ddde2b975 fix(pacquet): align GVS slot layout with pnpm (#11689)
## Summary

Adds three end-to-end **GVS parity tests** under `pacquet/crates/cli/tests/pnpm_compatibility.rs` that run `pnpm install` and `pacquet install --frozen-lockfile` against the same workspace + lockfile with `enableGlobalVirtualStore: true`, then diff the resulting `<store>/v11/links/` slot trees. The tests surfaced three independent divergences, each fixed in its own commit set:

1. **`<store>/v11/links` prefix.** `getStorePath` appends `STORE_VERSION` (`v11`) to the configured `storeDir` before `extendInstallOptions.ts:352` joins `'links'` onto it, so pnpm's GVS lives at `<store>/v11/links/` — pacquet's `StoreDir::links()` was one level shallower, joining onto `self.root`. Same gap on `projects()`. Anchored both under `self.v11()` so the on-disk paths agree.

2. **GVS engine-name resolution.** `ENGINE_NAME` was computed from `process.version`, which is wrong in two cases:
   - **`@pnpm/exe` SEA bundle.** The bundle has its own embedded Node, not the `node` on PATH that runs lifecycle scripts. Two pnpm installs on the same machine (one SEA, one npm-package) therefore disagreed on the cache key, partitioning the side-effects cache and the global virtual store.
   - **`engines.runtime` / `devEngines.runtime` pin.** When a project pins a Node version, pnpm downloads that Node into `node_modules/node/` and uses it to run lifecycle scripts. But the hash still anchored to whichever Node ran pnpm itself, not to the pinned Node.

   `@pnpm/engine.runtime.system-node-version` now exports `engineName(nodeVersion?)` and `findRuntimeNodeVersion(snapshotKeys)`. The override has priority; otherwise the helper falls through to `getSystemNodeVersion()` — which already prefers shell `node --version` over `process.version` in SEA contexts — and finally to `process.version` as a last resort. `@pnpm/deps.graph-hasher`'s `calcDepState`, `calcGraphNodeHash`, and `iterateHashedGraphNodes` accept an optional `nodeVersion`. Every install-side caller (`deps.graph-builder`, `installing.deps-resolver`, `installing.deps-restorer`, `installing.deps-installer/install/link`, `building.during-install`, `building.after-install`) derives the project's pinned runtime via `findRuntimeNodeVersion` once per invocation and forwards it. The legacy `ENGINE_NAME` constant in `@pnpm/constants` is unchanged so external consumers and existing tests keep working.

   Pacquet mirrors this with `find_runtime_node_major` in `install_frozen_lockfile.rs` — it scans the lockfile's `snapshots:` map for a `node@runtime:<version>` entry and uses that major outright, only falling back to the host probe when no pin is present.

3. **Slot bin-shim layout.** Pacquet was emitting `.cmd` / `.ps1` shims on every host platform, even though pnpm only writes them on Windows ([`@zkochan/cmd-shim` `createCmdFile: isWindows`](https://github.com/pnpm/cmd-shim/blob/0d79ca9534/src/index.ts#L32) + `bins/linker`'s [`POWER_SHELL_IS_SUPPORTED = IS_WINDOWS`](https://github.com/pnpm/pnpm/blob/29a42efc3b/bins/linker/src/index.ts#L28) gate). Pacquet also excluded the slot's own package from the slot-local `node_modules/.bin/` based on a stale assumption ("which pnpm doesn't"), but pnpm's [`linkBinsOfDependencies`](https://github.com/pnpm/pnpm/blob/29a42efc3b/building/during-install/src/index.ts#L272-L298) appends `depNode` to the bin-source list unconditionally, so a leaf package like `hello-world-js-bin` writes a self-shim at `<slot>/node_modules/<pkg>/node_modules/.bin/<pkg>`. Both behaviors now match pnpm.

## Test plan

- [x] `cargo nextest run -p pacquet-cli --test pnpm_compatibility` — 5 active tests pass, 1 ignored (see below)
- [x] `cargo nextest run -p pacquet-store-dir -p pacquet-config -p pacquet-cmd-shim -p pacquet-package-manager` — 600+ tests pass after the prefix / bin-shim updates
- [x] `same_global_virtual_store_layout_pure_js` — pacquet & pnpm produce byte-identical `<store>/v11/links/` trees for `@pnpm.e2e/hello-world-js-bin-parent`
- [x] `same_global_virtual_store_layout_diamond` — same for `pkg-with-1-dep` + `parent-of-pkg-with-1-dep`, verifying `calc_dep_graph_hash` memoization parity
- [x] Three new TS unit tests in `engine/runtime/system-node-version/test/` cover the `engineName(version)` override branch and `findRuntimeNodeVersion`'s extraction rule (with and without peer suffix)
- [ ] `same_global_virtual_store_layout_with_approved_postinstall` is currently `#[ignore]`d. It requires pnpm and pacquet to agree on the `<platform>;<arch>;node<major>` triple in the engine-included hash branch. The `pnpm/setup` action on CI installs an `@pnpm/exe` SEA bundle whose embedded Node (node26) differs from the runner's PATH `node` (node24), so the digests don't line up. The pnpm-side fix in this PR resolves `engineName()` via `getSystemNodeVersion()` which prefers the shell `node`, so once a published pnpm version with the fix reaches `pnpm/setup` the test will pass without modification — re-enable it then. The other two GVS parity tests are unaffected since they exercise the engine-agnostic branch.

## Notes

- Two pacquet integration tests in `package-manager/src/install/tests.rs` had hard-coded `<store_dir>/projects/` assertions; updated to `<store_dir>/v11/projects/` to follow the prefix fix.
- The `link_bins_rewrites_when_only_sh_flavor_exists` cmd-shim test is now `#[cfg(windows)]` — the upgrade-recovery scenario it exercises is meaningless on Unix where `.cmd`/`.ps1` are no longer written in the first place.
- Review feedback addressed: (a) test YAML helper now guarantees a trailing newline before appending GVS keys; (b) `findRuntimeNodeVersion` calls in `installing/deps-restorer/` switched from `Object.keys(graph)` (install-dir-keyed in that module) to extracting `depPath` per node, with the computation lifted out of the recursion; (c) `dlx.e2e.ts`'s `jest.unstable_mockModule` against `@pnpm/engine.runtime.system-node-version` now forwards every exported symbol so transitive importers of `engineName` don't break.
- Known caveat: pacquet's non-lockfile install path (`run_with_readdir`) still excludes the slot's own bin via `link_bins_excluding`. That path runs only for the legacy flat layout where GVS parity isn't a constraint, so it's deliberately out of scope here.
- Known caveat tracked in #11690: when a dependency's own manifest declares `engines.runtime`, the resolver desugars it into a regular `dependencies.node: 'runtime:<v>'` entry on that package, so the **deps** portion of the hash captures it on both sides. The **engine** portion is still install-wide rather than per-snapshot, so cached side-effects for dep-pinned runtimes can be reused under the wrong host Node. pnpm has this same gap today; closing it on both sides requires per-snapshot engine resolution and is outside this PR's scope.
2026-05-16 23:58:53 +02:00
Zoltan Kochan
8a80235c7b chore(release): 11.1.2 2026-05-14 13:31:53 +02:00
Zoltan Kochan
9a327522ce chore(release): 11.1.1 2026-05-12 12:56:32 +02:00
Zoltan Kochan
732312f49e chore(release): 11.1.0 2026-05-11 19:56:10 +02:00
Zoltan Kochan
f2b28f85ff chore(release): 11.0.9 2026-05-09 02:06:35 +02:00
Zoltan Kochan
a516c24ce4 chore(release): 11.0.8 2026-05-07 08:35:07 +02:00
Zoltan Kochan
0c3ef0ec94 chore(release): 11.0.7 2026-05-07 00:21:03 +02:00
Zoltan Kochan
60fd20536d fix: pin integrity of git-hosted tarballs in lockfile (#11481)
For git-hosted tarballs (`codeload.github.com` / `gitlab.com` / `bitbucket.org`) the fetcher dropped the integrity it computed while downloading, so the lockfile only ever stored the URL. A compromised git host or man-in-the-middle could serve a substituted tarball on subsequent installs and pnpm would install it — the lockfile had no hash to compare against.

This pins the SHA-512 SRI of the raw tarball in the lockfile, in the same `sha512-<base64>` form npm-registry tarballs use. The only difference is the source: for npm we pass through `dist.integrity`, for git we compute it locally from the downloaded buffer. Subsequent installs validate the download against that integrity in the worker (`addTarballToStore` → `parseIntegrity` → hash compare), so a tampered tarball fails with `TarballIntegrityError`.

## Why git-hosted stays on `gitHostedStoreIndexKey`

The lockfile pins integrity for security, but the *store key* for git-hosted resolutions stays on `gitHostedStoreIndexKey(pkgId, { built })` rather than collapsing under the integrity-based key. Reason: git-hosted tarballs are post-processed (`preparePackage` / `packlist`), so the cached file set depends on whether build scripts ran during fetch. The integrity-only key would fold the built and not-built variants into a single slot, letting one overwrite the other and serving the wrong content if `ignoreScripts` was toggled between runs. Keeping git-hosted on the existing key shape preserves that dimension; the integrity is still validated on every fresh download.

## How the routing stays clean

The naive way to express "use gitHostedStoreIndexKey for git-hosted, integrity key for npm" is to call `isGitHostedPkgUrl(resolution.tarball)` everywhere a store key is computed — fragile, scattered, and easy to forget when adding new readers (Copilot caught two of those during review). Instead, a typed annotation: `TarballResolution` gets an optional `gitHosted: boolean` field. The git resolver sets it; the lockfile loader (`convertToLockfileObject`) backfills it for entries written by older pnpm versions; `toLockfileResolution` carries it through on serialize. Every consumer reads `resolution.gitHosted` directly. URL detection lives in exactly two places — the resolver and the loader — instead of seven.

## Changes

### Security fix
- `fetching/tarball-fetcher/src/gitHostedTarballFetcher.ts` — return the `integrity` that the inner remote-tarball fetch already computed (was being silently dropped by the destructure).

### Lockfile schema (additive)
- `@pnpm/lockfile.types` and `@pnpm/resolving.resolver-base` — `TarballResolution` gains optional `gitHosted: boolean`.
- `@pnpm/resolving.git-resolver` — sets `gitHosted: true` on every git-hosted tarball it produces.
- `@pnpm/lockfile.fs` (`convertToLockfileObject`) — backfills the field on load for older lockfiles via inlined URL detection.
- `@pnpm/lockfile.utils` (`toLockfileResolution`, `pkgSnapshotToResolution`) — preserve / read the field.

### Store-key consumers (now one-line typed reads, dropped the URL-sniffing dep)
- `installing/package-requester` (`getFilesIndexFilePath`)
- `store/pkg-finder` (`readPackageFileMap`)
- `modules-mounter/daemon` (`createFuseHandlers`)
- `building/after-install` (side-effects-cache lookup + write)
- `store/commands/storeStatus`
- `installing/deps-installer` (agent-mode store-controller wrapper)

### Fetcher routing
- `fetching/pick-fetcher` — `pickFetcher` prefers `resolution.gitHosted`; URL fallback retained for ad-hoc resolutions.

### Tests
- New integrity-validation test in `tarball-fetcher` (mismatched `integrity` on the resolution must throw `TarballIntegrityError`).
- New git-hosted lookup test in `pkg-finder` asserting routing through `gitHostedStoreIndexKey` even when integrity is present.
- New `toLockfileResolution` test asserting `gitHosted: true` flows through serialization.
- `fromRepo.ts` lockfile snapshot updated for the now-pinned integrity + `gitHosted: true`.
- `git-resolver` tests updated to assert `gitHosted: true` in produced resolutions.
2026-05-06 13:22:25 +02:00
Zoltan Kochan
65f9327014 chore(release): 11.0.6 2026-05-05 19:50:32 +02:00
palkim
ab6c42d99e fix: refresh ignored builds when allowBuilds changes (#11366)
* fix: refresh ignored builds when allowBuilds changes

* refactor: extract isBuildExplicitlyDisallowed into @pnpm/building.policy

Removes the duplicated ignored-build filter from deps-installer and
deps-restorer and exposes it as `isBuildExplicitlyDisallowed` on
`@pnpm/building.policy`, alongside `createAllowBuildFunction`.

* fix: respect ignoredWorkspaceStateSettings in allowBuilds stale-state check

The fallback that flagged installs when allowBuilds went from unset to
non-empty bypassed the ignoredSettings filter, so callers that explicitly
opted out of allowBuilds tracking (via ignoredWorkspaceStateSettings)
could still be forced into a redundant install.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-05-05 19:36:42 +02:00
Zoltan Kochan
cc373c39f1 chore(release): 11.0.5 2026-05-04 22:14:24 +02:00
Zoltan Kochan
f6bc1db683 fix(dlx): prompt to approve ignored builds (#11452)
`pnpm dlx` (and `pnpx`/`pnx`/`pnpm create`) now mirrors the `pnpm add -g` flow when the launched package's transitive deps have install scripts:

- dlx overrides `strictDepBuilds: false` for its install so the v11 default no longer turns ignored builds into an `ERR_PNPM_IGNORED_BUILDS` error. Without this, `pnpx @google/gemini-cli` (and similar — `node-pty`, `@github/keytar`) failed outright and forced users to retry with `--allow-build=<pkg>` for every offending dependency.
- After install, dlx detects skipped builds via `getAutomaticallyIgnoredBuilds` and runs the same interactive `approve-builds` prompt as `pnpm add -g`. In non-interactive mode the install is committed with builds skipped, matching `pnpm add -g` in CI; users who need those scripts can re-invoke with `--allow-build=<pkg>` to force a fresh cache key.
- If the install errors for unrelated reasons (network, etc.) the partially-populated prepare directory is removed so the next dlx run starts clean.

Closes #11444.

### Plumbing

- Exports `getAutomaticallyIgnoredBuilds` from `@pnpm/building.commands` so dlx can detect skipped builds without re-implementing modules-yaml reading.
- Adds `strictDepBuilds` (optional) to `InstallCommandOptions` — already accepted at runtime via the spread, this just makes it explicit at the type level so callers can override it.
2026-05-04 22:11:22 +02:00
Zoltan Kochan
3a5534d75e chore(release): 11.0.4 2026-05-03 01:24:22 +02:00
Zoltan Kochan
6ef34b7a11 chore(release): 11.0.3 2026-04-30 23:03:46 +02:00
Zoltan Kochan
184ce26f3f docs: fix package names in README files (#11409)
* docs: fix package names in README files

* docs: update links to point to npmx.dev
2026-04-30 22:59:17 +02:00
Zoltan Kochan
a53f78b111 chore(release): 11.0.2 2026-04-30 17:16:34 +02:00
Zoltan Kochan
38ffda2a18 chore(release): 11.0.1 2026-04-29 23:00:21 +02:00
Zoltan Kochan
8aeeff4c46 chore(release): 11.0.0 2026-04-28 11:27:43 +02:00
Zoltan Kochan
4d7cd56ccc chore: upgrade @typescript/native-preview to 7.0.0-dev.20260421.2 (#11332)
* chore: upgrade @typescript/native-preview to 7.0.0-dev.20260421.2

- Add explicit `types: ["node"]` to the shared tsconfig because tsgo
  20260421 no longer auto-acquires `@types/*` from `node_modules`.
- Refactor test files to explicitly import jest globals (`describe`,
  `it`, `test`, `expect`, `beforeEach`, etc.) from `@jest/globals`
  instead of relying on `@types/jest` ambient declarations. Under the
  new tsgo build, `import { jest } from '@jest/globals'` shadows the
  ambient `jest` namespace, breaking `@types/jest`'s `declare var
  describe: jest.Describe;` globals.
- Add `@jest/globals` to each package's devDependencies where tests
  now import from it, and add `@types/node` to packages that need it
  but were relying on hoisted resolution.
- Replace `fail()` calls with `throw new Error(...)` since `fail` is
  no longer globally available.

* chore: fix remaining tsgo type-strictness errors

- Strip `as <PnpmType>` casts on objects passed to toMatchObject /
  toStrictEqual / toEqual; @jest/globals rejects the typed objects
  (which include AsymmetricMatchers) vs. the repo-specific type.
- Type `jest.fn<...>()` explicitly where the mock's signature matters
  for toHaveBeenCalledWith.
- Replace `beforeEach(() => X)` with `beforeEach(() => { X })` so the
  return value is void, as the stricter jest typing requires.
- Use `expect.objectContaining({...})` in one place where the full
  expected object triggered stricter type resolution.
- Cast `prompt.mock.calls` arg through `as unknown as Record<...>[]`
  for patch.test.ts's nested-array matchers.
- Fix off-by-one `<reference path>` in pnpm/test/getConfig.test.ts
  that only surfaced now.
- Move `@jest/globals` from devDependencies to dependencies in the
  two `__utils__` packages that import it from `src/`.
- Clean up unused imports from the @jest/globals migration.

* chore: address Copilot review on #11332

- Move misplaced `@jest/globals` imports to the top import block in
  checkEngine, run.ts, and workspace/root-finder tests where the
  script dropped them below executable code.
- Replace `try { await x(); throw new Error('should have thrown') } catch`
  in bins/linker, lockfile/fs, and resolving/local-resolver tests with
  `await expect(x()).rejects.toMatchObject({...})`. The old pattern
  swallowed an unrelated `throw` if the under-test call silently
  succeeded, which would fail on the catch-block assertion with a
  misleading message.
2026-04-21 23:21:52 +02:00
Zoltan Kochan
fd437ded13 chore(release): 11.0.0-rc.4 2026-04-21 15:03:02 +02:00
Zoltan Kochan
aa93759d9b chore(release): drop eslint from lib prepublishOnly (#11320)
Library packages had `prepublishOnly: pn compile`, which expands to
`tsgo --build && pn lint --fix`. During `pn release` that runs eslint
against ~150 packages for no benefit — the code has already been linted
in CI and the release flow's upfront compile has already built dist/.
Switch lib prepublishOnly to a bare `tsgo --build` so the safety-net
compile stays but the per-package eslint cost is gone.
2026-04-21 01:18:03 +02:00
Zoltan Kochan
fcdd50aaa7 chore(release): 11.0.0-rc.3 2026-04-21 00:17:38 +02:00
Zoltan Kochan
72c1e050e9 feat: add pnpm pack-app command for packing CJS entries into standalone executables (#11312)
* fix: give each runtime variant its own global virtual store entry

When a runtime package (e.g. node@runtime:X.Y.Z) uses a variations
resolution, createFullPkgId() in @pnpm/deps.graph-hasher was hashing
the whole VariationsResolution — the same hash on every host — so the
global virtual store path collided between variants. Whichever variant
installed first won, and a later `pnpm add --libc=musl node@runtime:<v>`
silently reused the cached glibc (or macOS/Windows) binary.

The fix threads supportedArchitectures down to createFullPkgId so the
selected variant's integrity is used as the package fingerprint. Two
related cleanups land with it:

- Extract the platform-variant selection logic to @pnpm/resolving.resolver-base
  as selectPlatformVariant/resolvePlatformSelector. The helper's libc
  match also required a fix: a variant with no libc is the "default"
  build, and a request for a non-default libc (e.g. musl) must require
  an exact match so the default variant doesn't silently win.
- @pnpm/installing.package-requester's findResolution now delegates to
  the shared helper, and the new supportedArchitectures param is plumbed
  through calcDepState / calcGraphNodeHash / iterateHashedGraphNodes /
  lockfileToDepGraph and their callers in deps-resolver, deps-restorer,
  deps-installer, graph-builder, and building.after-install.

* feat: add pnpm build-sea command for building Node.js SEA executables

Adds `pnpm build-sea` under @pnpm/releasing.commands. Takes a CommonJS
entry file and a set of target triplets (linux-x64, linux-x64-musl,
linux-arm64, linux-arm64-musl, macos-x64, macos-arm64, win-x64,
win-arm64) and produces a standalone executable per target under
dist-sea/<target>/.

Each target's Node.js runtime is fetched via `pnpm add node@runtime:<v>
--os=<os> --cpu=<arch> --libc=<libc>` into $PNPM_HOME/build-sea/<target>-<v>/
so binaries are hardlinked from the global content-addressable store and
`pnpm store prune` can reclaim them.

Requires Node.js v25.5+ to perform the --build-sea injection. If the
running Node is older, a v25 binary is downloaded and used as the builder
automatically. macOS outputs are ad-hoc signed with codesign (on macOS)
or ldid (when cross-compiling from Linux), which is required because SEA
injection invalidates the binary's existing signature.

* fix(build-sea): reject malformed --target, --output-name and use mkdtemp for config

Addresses Copilot review feedback on the build-sea command:

- parseTarget() previously destructured the target string, silently
  accepting extra `-` segments. Inputs like `linux-x64-musl-../../outside`
  would pass validation and flow into path.join. Validation is now done
  with a strict anchored regex.
- --output-name was passed into path.join() without sanitization, so a
  caller could escape the output directory with path separators or `..`.
  validateOutputName() now rejects anything that isn't a plain basename.
- The per-target SEA config file was written to a predictable path under
  os.tmpdir() (derived from the target name and Date.now()), which is
  unsafe on multi-user systems. It now lives inside a fresh mkdtemp()
  directory and is opened with the exclusive "wx" flag.
- New test cases cover extra-segment targets, uppercase/whitespace
  variants, and the full matrix of invalid --output-name inputs.

* rename: build-sea → pack-app

`build-sea` required knowing what a SEA is. `pack-app` is self-describing,
doesn't collide with pnpm's existing `bin` concept, and parallels the
existing `pack` command.

- Command name: build-sea → pack-app
- Default output dir: dist-sea → dist-app
- Error codes: PACK_APP_* (was BUILD_SEA_*)
- Export/type: packApp / PackAppOptions (was buildSea / BuildSeaOptions)
- Install cache dir: $PNPM_HOME/pack-app (was $PNPM_HOME/build-sea)

The Node.js `--build-sea` flag name itself is unchanged — that's a
Node.js feature and outside this project's naming.

* fix(pack-app): reject directory entries, pin builder to >=25.5, refuse macOS target on Windows

Addresses Copilot review feedback on the pack-app command:

- entry validation now rejects non-file paths (directories, symlinks to
  non-files) with a dedicated PACK_APP_ENTRY_NOT_FILE instead of
  surfacing a less actionable error later in the SEA build.
- DEFAULT_BUILDER_SPEC was the bare major ("25"), which would satisfy
  with 25.0.x if that version is still present — those point releases
  predate --build-sea support. Tightened to ">=25.5.0 <26.0.0" so the
  download is guaranteed to support the flag without ever crossing a
  major.
- adHocSignMacBinary() silently skipped re-signing on Windows hosts.
  Now throws PACK_APP_MACOS_SIGN_UNSUPPORTED_HOST with a hint to build
  the target on macOS/Linux or re-sign manually.
- resolvePlatformSelector() JSDoc now matches what the code actually
  does (picks the first entry when it is not "current"; later entries
  are ignored).
- New test case covers the directory-as-entry rejection.

* refactor(pack-app): switch target OS names to process.platform constants

Previously `pack-app` accepted `macos-*` / `win-*` as the OS portion of a
target triplet and translated them to `darwin` / `win32` internally. The
translation layer made the CLI surface inconsistent with the values that
`pnpm add --os=…` and `supportedArchitectures.os` already use, and added
a small footgun (e.g. users setting `supportedArchitectures: { os: [darwin] }`
but typing `macos-arm64` for pack-app).

The supported target OS set is now `linux | darwin | win32`, matching
`process.platform`. Old inputs like `macos-arm64` or `win-x64` now fail
validation with a clear error pointing to the new naming. The internal
parseTarget helper drops its TARGET_OS_MAP lookup entirely.

This is a change to an unreleased command so there is no back-compat
concern. pnpm's own artifact directory names (`pnpm/artifacts/macos-*/`,
`pnpm/artifacts/win-*/`) are an internal implementation detail and are
not affected by this change.

* feat(pack-app): read defaults from pnpm.app in package.json

Every pack-app flag (--entry, --target, --node-version, --output-dir,
--output-name) can now be preconfigured in the project's package.json
under a new "pnpm.app" object:

  {
    "name": "my-cli",
    "pnpm": {
      "app": {
        "entry": "dist/index.cjs",
        "targets": ["linux-x64", "darwin-arm64", "win32-x64"],
        "nodeVersion": "25",
        "outputDir": "release",
        "outputName": "my-cli"
      }
    }
  }

CLI flags always win. --target replaces the configured list rather than
appending, so a user can narrow the default set at the command line.

The config loader is strict: unknown keys under pnpm.app and any
type-mismatched values throw PACK_APP_INVALID_CONFIG so mistakes surface
at invocation time instead of silently being ignored.

Chose pnpm.app over pnpm.packApp because it's the shorter, cleaner
namespace for anything related to the app bundle (future sibling
commands like run-app / deploy-app could share the same object without
a naming clash). Chose package.json over pnpm-workspace.yaml because
the config is inherently per-project, whereas pnpm-workspace.yaml is
workspace-root-only.

* fix(pack-app): deterministic libc selection and stricter output-name validation

Addresses Copilot review feedback:

- ensureNodeRuntime() now always passes an explicit --libc for linux
  targets. Without a suffix, linux-x64 and linux-arm64 default to
  --libc=glibc instead of letting the user's supportedArchitectures.libc
  config or the host's detected libc decide the variant. The install
  cache directory mirrors this, so glibc and musl variants are always
  distinct (linux-x64-glibc vs linux-x64-musl).
- resolveBuilderBinary() now pins the host libc when downloading a
  builder Node on Linux. A user whose config sets supportedArchitectures.libc
  to musl no longer ends up with a musl Node that the glibc host cannot
  execute.
- validateOutputName() rejects Windows-invalid filename characters
  (<>:"|?* and NUL), Windows reserved device names (CON, NUL, COM1, etc.),
  and names ending in a dot or space — problems surface at invocation
  time rather than during writeFile(outputFile, ...) on Windows.
- lockfileToDepGraph variants tests no longer derive the "host"
  variant from process.platform/process.arch; they always pass an
  explicit supportedArchitectures selector so the expectations hold on
  any CI host (including Alpine/musl).

* chore: add "toctou" to cspell wordlist

`TOCTOU` (time-of-check-to-time-of-use) is the standard term for the
race-condition class the pack-app SEA-config comment describes. Adding
it to the wordlist unblocks the Lint CI step.

* fix: lint
2026-04-20 14:29:49 +02:00
Zoltan Kochan
7d9aae9662 fix: prevent frozen-lockfile error when approving builds in global install (#11294)
In --global mode, globalAdd passes workspaceDir to approve-builds so it can
update the global pnpm-workspace.yaml. approve-builds then forwarded that
workspaceDir into install.handler, which (with workspacePackagePatterns
undefined) recursively discovered sibling install dirs as workspace projects
and failed the frozen-lockfile check on stale @pnpm/exe install dirs.
2026-04-19 01:39:24 +02:00
Zoltan Kochan
96ece9d736 chore(release): 11.0.0-rc.2 2026-04-17 18:21:35 +02:00
Zoltan Kochan
f7c23231a9 chore(release): 11.0.0-rc.1 2026-04-16 01:18:55 +02:00
Zoltan Kochan
06d6c2d405 chore(release): 11.0.0-rc.0 2026-04-10 18:30:33 +02:00
Zoltan Kochan
51b04c3e9a refactor!: remove ignoreDepScripts and neverBuiltDependencies (#11220)
* refactor: remove ignoreDepScripts and neverBuiltDependencies settings

These settings are redundant in v11:
- `ignore-dep-scripts` is superseded by the default behavior of `allowBuilds`
- `neverBuiltDependencies` was already dead code, replaced by `allowBuilds`

* chore: add changeset for removed ignore-dep-scripts setting
2026-04-07 13:41:13 +02:00
Zoltan Kochan
b103439d9a refactor(test): extract shared DEFAULT_OPTS into @pnpm/testing.command-defaults (#11208)
12 command test suites had near-identical ~50-field DEFAULT_OPTS objects
copy-pasted between them. Extract the common fields into a single shared
package so each suite only declares its overrides.
2026-04-05 21:22:24 +02:00
Zoltan Kochan
45a6cb6b2a refactor(auth): unify auth/SSL into structured configByUri (#11201)
Replaces the dual `authConfig` (raw .npmrc) + `authInfos` (parsed auth) + `sslConfigs` (parsed SSL) pattern with a single structured `configByUri: Record<string, RegistryConfig>` field on Config.

### New types (`@pnpm/types`)
- **`RegistryConfig`** — per-registry config: `{ creds?: Creds, tls?: TlsConfig }`
- **`Creds`** — auth credentials: `{ authToken?, basicAuth?, tokenHelper? }`
- **`TlsConfig`** — TLS config: `{ cert?, key?, ca? }`

### Key changes
- Rewrite `createGetAuthHeaderByURI` to accept `Record<string, RegistryConfig>` instead of raw .npmrc key-value pairs
- Eliminate duplicate auth parsing between `getAuthHeadersFromConfig` and `getNetworkConfigs`
- Remove `authConfig` from the install pipeline (`StrictInstallOptions`, `HeadlessOptions`), replaced by `configByUri`
- Remove `sslConfigs` from Config — SSL fields now live in `configByUri[uri].tls`
- Remove `authConfig['registry']` mutation in `extendInstallOptions` (default registry now passed directly to `createGetAuthHeaderByURI`)
- `authConfig` remains on Config only for raw .npmrc access (config commands, error reporting, config inheritance)

### Security
- tokenHelper in project .npmrc now throws instead of being silently stripped
- tokenHelper execution uses `shell: false` to prevent shell metacharacter injection
- Basic auth uses `Buffer.from().toString('base64')` instead of `btoa()` for Unicode safety
- Dispatcher only creates custom agents when entries actually have TLS fields
2026-04-05 20:15:10 +02:00
Zoltan Kochan
b5d93c6ba9 refactor(config): remove rawLocalConfig and force* hoist flags (#11199)
rawLocalConfig detected whether hoist settings were explicitly
set. In v11, config values are always authoritative.

- Remove rawLocalConfig from ConfigContext, config reader,
  inheritPickedConfig, UniversalOptions
- Remove forceHoistPattern, forcePublicHoistPattern,
  forceShamefullyHoist — validateModules always checks now
- Simplify save-workspace-protocol check
- Remove dead rawLocalConfig overrides in deploy/patchCommit
2026-04-05 11:36:36 +02:00
Zoltan Kochan
3033bee430 refactor(config): split Config interface into settings + runtime context (#11197)
* refactor(config): split Config interface into settings + runtime context

Create ConfigContext for runtime state (hooks, finders, workspace graph,
CLI metadata) and keep Config for user-facing settings only. Functions
use Pick<Config, ...> & Pick<ConfigContext, ...> to express which fields
they need from each interface.

getConfig() now returns { config, context, warnings }. The CLI wrapper
returns { config, context } and spreads both when calling command
handlers (to be refactored to separate params in follow-up PRs).

Closes #11195

* fix: address review feedback

- Initialize cliOptions on pnpmConfig so context.cliOptions is never undefined
- Move rootProjectManifestDir assignment before ignoreLocalSettings guard
- Add allProjectsGraph to INTERNAL_CONFIG_KEYS

* refactor: remove INTERNAL_CONFIG_KEYS from configToRecord

configToRecord now accepts Config and ConfigContext separately, so
context fields are never in scope. Only auth-related Config fields
(authConfig, authInfos, sslConfigs) need filtering.

* refactor: eliminate INTERNAL_CONFIG_KEYS from configToRecord

configToRecord now receives the clean Config object and explicitlySetKeys
separately (via opts.config and opts.context), so context fields are
never in scope. main.ts passes the original split objects alongside
the spread for command handlers that need them.

* fix: spelling

* fix: import sorting

* fix: --config.xxx nconf overrides conflicting with --config CLI flag

When `pnpm add` registers `config: Boolean`, nopt captures
--config.xxx=yyy as the --config flag value instead of treating it
as a nconf-style config override. Fix by extracting --config.xxx args
before nopt parsing and re-parsing them separately.

Also rename the split config/context properties on the command opts
object to _config/_context to avoid clashing with the --config CLI option.
2026-04-04 23:44:25 +02:00
Zoltan Kochan
96704a1c58 refactor(config): rename rawConfig to authConfig, add nodeDownloadMirrors, simplify config reader (#11194)
Major cleanup of the config system after migrating settings from `.npmrc` to `pnpm-workspace.yaml`.

### Config reader simplification
- Remove `checkUnknownSetting` (dead code, always `false`)
- Trim `npmConfigTypes` from ~127 to ~67 keys (remove unused npm config keys)
- Replace `rcOptions` iteration over all type keys with direct construction from defaults + auth overlay
- Remove `rcOptionsTypes` parameter from `getConfig()` and its assembly chain

### Rename `rawConfig` to `authConfig`
- `rawConfig` was a confusing mix of auth data and general settings
- Non-auth settings are already on the typed `Config` object — stop duplicating them in `rawConfig`
- Rename `rawConfig` → `authConfig` across the codebase to clarify it only contains auth/registry data from `.npmrc`

### Remove `rawConfig` from non-auth consumers
- **Lifecycle hooks**: replace `rawConfig: object` with `userAgent?: string` — only user-agent was read
- **Fetchers**: remove unused `rawConfig` from git fetcher, binary fetcher, tarball fetcher, prepare-package
- **Update command**: use `opts.production/dev/optional` instead of `rawConfig.*`
- **`pnpm init`**: accept typed init properties instead of parsing `rawConfig`

### Add `nodeDownloadMirrors` setting
- New `nodeDownloadMirrors?: Record<string, string>` on `PnpmSettings` and `Config`
- Replaces the `node-mirror:<channel>` pattern that was stored in `rawConfig`
- Configured in `pnpm-workspace.yaml`:
  ```yaml
  nodeDownloadMirrors:
    release: https://my-mirror.example.com/download/release/
  ```
- Remove unused `rawConfig` from deno-resolver and bun-resolver

### Refactor `pnpm config get/list`
- New `configToRecord()` builds display data from typed Config properties on the fly
- Excludes sensitive internals (`authInfos`, `sslConfigs`, etc.)
- Non-types keys (e.g., `package-extensions`) resolve through `configToRecord` instead of direct property access
- Delete `processConfig.ts` (replaced by `configToRecord.ts`)

### Pre-push hook improvement
- Add `compile-only` (`tsgo --build`) to pre-push hook to catch type errors before push
2026-04-04 20:33:43 +02:00
sotanengel
c7203b99ad feat!: set default minimumReleaseAge to 1 day (1440 minutes) (#11158)
set default minimumReleaseAge to 1 day

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-04-04 13:26:22 +02:00
Zoltan Kochan
d6b8e281b6 chore: use pn instead of pnpm (#11124) 2026-03-28 11:55:51 +01:00
Zoltan Kochan
36b3826f7b feat: make clean/setup/deploy prefer user scripts over built-in commands (#11118)
* feat: make clean/setup/deploy prefer user scripts over built-in commands

When a project's package.json has a script named "clean", "setup", or
"deploy", running `pnpm clean/setup/deploy` now executes the script
instead of the built-in command. This prevents surprising behavior for
users with existing scripts.

When running from a workspace subdirectory where the root package.json
has one of these scripts, an error is thrown with guidance on how to
proceed.

Added "purge" as an alias for the built-in clean command, which always
runs the built-in regardless of scripts.

Closes #6816

* feat: also make rebuild prefer user scripts over the built-in

* refactor: move scriptOverride to command definitions

Each command now declares `scriptOverride = true` instead of a
centralized list in main.ts. All command names including aliases
are overridable by same-named scripts.

* refactor: rename scriptOverride to overridableByScript

* test: add e2e tests for script override behavior in clean/purge

* fix: address review feedback

- Fix JSDoc to reflect that aliases are also overridable by scripts
- Update npm_command env var to 'run-script' when redirecting to run
- Add 'purge' alias to clean command help text
2026-03-27 20:04:52 +01:00
Zoltan Kochan
0e8042e6dc revert: "feat: add allowBuildsOfTrustedDeps setting (true by default) (#11078)"
This reverts commit 5a3dc4ab2f.
2026-03-26 15:19:24 +01:00
Zoltan Kochan
5a3dc4ab2f feat: add allowBuildsOfTrustedDeps setting (true by default) (#11078)
* feat: load default trusted deps list from @pnpm/plugin-trusted-deps

Add a new `use-default-trusted-deps` setting (default: true) that
automatically loads a curated list of known-good packages into
`allowBuilds` from @pnpm/plugin-trusted-deps. User-configured
allowBuilds entries take precedence over the defaults. Set
`use-default-trusted-deps=false` to disable.

* fix: use catalog reference for @pnpm/plugin-trusted-deps

* fix: use default import for @pnpm/plugin-trusted-deps CJS compat

The package uses Object.defineProperty for DEFAULT_ALLOW_BUILDS,
which Node.js/Jest ESM interop can't detect as a named export.
Switch to a default import to fix test failures.

* fix: use named ESM import from @pnpm/plugin-trusted-deps@0.3.0-1

The package now ships an ESM entry point with proper named exports,
so we can use a clean named import instead of the default import
workaround.

* fix: update @pnpm/plugin-trusted-deps to 0.3.0-2

Uses static JSON import attributes in ESM entry, fixing the bundle
issue where createRequire resolved paths relative to the bundle
output instead of the original package.

* refactor: rename setting to allow-builds-for-trusted-deps

* test: disable default trusted deps in approveBuilds tests

The tests assert exact allowBuilds contents, so the default trusted
list must be disabled to avoid polluting the expected values.

* fix: don't persist default trusted deps list to pnpm-workspace.yaml

Track the user's original allowBuilds separately as userAllowBuilds
before merging the default trusted list. Use userAllowBuilds when
writing back to pnpm-workspace.yaml to avoid persisting the ~370
default entries from @pnpm/plugin-trusted-deps.

* refactor: rename setting to allow-builds-of-trusted-deps

* docs: use camelCase for setting name in changeset

* fix: include userAllowBuilds in install command opts types

Without this, userAllowBuilds wasn't passed through to
handleIgnoredBuilds, causing the default trusted list to be
written to pnpm-workspace.yaml during e2e tests.

* fix: set userAllowBuilds to empty object when user has no config

When the user has no allowBuilds configured, userAllowBuilds was
undefined, causing handleIgnoredBuilds to fall back to the merged
allowBuilds (with defaults). Use empty object instead so the
fallback doesn't trigger.

* fix: read allowBuilds from workspace manifest when writing back

Instead of tracking userAllowBuilds separately (which gets stale
when other code writes to pnpm-workspace.yaml mid-install), read
the current allowBuilds directly from pnpm-workspace.yaml before
writing. This avoids persisting the default trusted list and
preserves entries written by --allow-build earlier in the flow.

Also update e2e test expectation: esbuild is now in the default
trusted list, so it builds instead of being ignored.

* chore: update tsconfig references for new dependencies

* test: disable default trusted deps in approveBuilds e2e install

The execPnpmInstall helper runs the bundled CLI which picks up
the default allowBuildsOfTrustedDeps=true. This causes extra
placeholder entries in pnpm-workspace.yaml that break assertions.

* fix: revert approveBuilds to use config-based allowBuilds

approveBuilds.handler should use opts.allowBuilds from getConfig()
(which excludes trusted deps defaults when disabled) rather than
reading the workspace manifest. The handler's job is to write
approve/deny decisions, not merge with auto-populated placeholders.

* test: add config reader tests for allowBuildsOfTrustedDeps

Cover: (1) default enabled with trusted defaults merged,
(2) user allowBuilds overrides defaults, (3) setting
allow-builds-of-trusted-deps=false disables the merge.
2026-03-25 16:42:36 +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
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