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.
* 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.
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
* 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.
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
### `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)