mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-28 01:45:30 -04:00
511e74200d00a9c4706cb22433da1cbef7d7c310
44 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
302a2f7d2c |
fix(config/reader): don't warn when packageManager and devEngines.packageManager match (#12287)
When `package.json` sets both `packageManager` and `devEngines.packageManager` to the same pnpm version with the same integrity hash pnpm prints a spurious warning on every command. For example, a `package.json` file that looks like:
```json
{
"packageManager": "pnpm@11.5.1+sha512.93f7b57422ea7068257235b4c16eb60762eb68e1dc23723199cc739043ea9be2c4143274a399d8c6defa2b1176226d9ca1c4b63482d6200c1a8fbaa78c1d1485",
"devEngines": {
"packageManager": {
"name": "pnpm",
"version": "11.5.1+sha512.93f7b57422ea7068257235b4c16eb60762eb68e1dc23723199cc739043ea9be2c4143274a399d8c6defa2b1176226d9ca1c4b63482d6200c1a8fbaa78c1d1485",
"onFail": "ignore"
},
"runtime": [
{
"name": "node",
"version": "26.3.0",
"onFail": "ignore"
}
]
}
}
```
Issues a warning on every `pnpm` command:
> Cannot use both "packageManager" and "devEngines.packageManager" in package.json. "packageManager" will be ignored.
## Root cause
`getWantedPackageManager` compares the two fields to decide whether to warn, but the two sides were normalized differently:
- `parsePackageManager` **strips the integrity hash** from the legacy `packageManager` field → `11.5.1`
- the `devEngines.packageManager` version was compared **with its hash intact** → `11.5.1+sha512.93f7b57…`
So, `"11.5.1" !== "11.5.1+sha512…"` was always true and the warning fired, even for identical specs. An earlier fix in #11307 only suppressed the warning when *neither* side carried a hash.
## Fix
`parsePackageManager` now also returns the hash (via a shared `splitPackageManagerVersion`), and `getPackageManagerConflictWarning` compares the fields structurally. The warning is suppressed **only when the two specifiers are identical** (name + version + hash, both-absent counts as equal):
| name | version | hash | result |
|------|---------|------|--------|
| same | same | both absent, or both present & equal | ✅ no warning |
| same | same | present on **one side only** | ⚠️ generic "Cannot use both…" |
| same | same | both present & **differ** | ⚠️ "…contradictory integrity hashes" |
| same | **differ** | — | ⚠️ "…different versions of pnpm" |
| **differ** | — | — | ⚠️ "…different package managers" |
A hash on only one side is still a divergence — dropping the ignored `packageManager` field would lose that hash — so it warns with the original generic message. Two contradictory hashes for one version (a likely wrong-hash mistake) get a dedicated message. The generic single message is otherwise replaced by one tailored to each conflict, each ending with `"packageManager" will be ignored`.
Closes #12028.
---------
Signed-off-by: C. Spencer Beggs <spencer@beggs.codes>
Signed-off-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Zoltan Kochan <z@kochan.io>
|
||
|
|
681b593eb2 |
fix: support scope-specific registry auth tokens (#12392)
pnpm can now use different auth tokens for different package scopes, even when those scopes use the same registry URL. Previously, auth was selected only by registry URL. If `@org-a` and `@org-b` both used `https://npm.pkg.github.com/`, they had to share the same token. This caused problems for registries that issue tokens per organization or per scope. Configure a scope-specific token by adding the package scope after the registry URL in the auth key: ```ini @org-a:registry=https://npm.pkg.github.com/ @org-b:registry=https://npm.pkg.github.com/ //npm.pkg.github.com/:@org-a:_authToken=${ORG_A_TOKEN} //npm.pkg.github.com/:@org-b:_authToken=${ORG_B_TOKEN} //npm.pkg.github.com/:_authToken=${FALLBACK_TOKEN} ``` `pnpm login --registry=https://npm.pkg.github.com --scope=@org-a` writes the token to the same scope-specific auth key. When installing or publishing `@org-a/*`, pnpm uses `ORG_A_TOKEN`. For `@org-b/*`, pnpm uses `ORG_B_TOKEN`. Packages without a matching scope continue to use the registry-wide fallback token. |
||
|
|
61810aa684 |
feat: add --frozen-store for installs against a read-only store (#12190)
## What
Adds an opt-in `frozenStore` / `--frozen-store` setting (default `false`) that lets `pnpm install --offline --frozen-lockfile` run against a package store that lives on a **read-only filesystem** — a Nix store, a read-only bind mount, an OCI layer.
## Why
A normal install fails against such a store **not** because it writes package content, but because it unconditionally:
1. opens the SQLite `index.db` in WAL mode, which needs to create `-shm`/`-wal` sidecars in the store directory; and
2. writes a project-registry entry under the store.
Both fail with `attempt to write a readonly database` / `EROFS` on a read-only store directory, even when the store is complete and the lockfile is frozen. This blocks any deployment that wants an immutable, content-addressed store.
## How
When `frozenStore` is enabled, pnpm opens `index.db` through the SQLite **`immutable=1`** URI — which tells SQLite the file cannot change underneath it, so it bypasses the WAL/`-shm` sidecar machinery entirely and reads the raw file with zero sidecar creation — and suppresses every store-write path. Pair it with `--offline --frozen-lockfile` against a fully-populated store. It is incompatible with two settings that would write into the store, and each throws a clear config-conflict error before any network or store access: **`--force`** (which bypasses the no-write-on-hit skip → `ERR_PNPM_CONFIG_CONFLICT_FROZEN_STORE_WITH_FORCE`) and a configured **pnpr server** (which would fetch and write packages into the store → `ERR_PNPM_FROZEN_STORE_INCOMPATIBLE_WITH_PNPR`).
> Plain `SQLITE_OPEN_READ_ONLY` is **not** sufficient: opening a WAL-mode db read-only still tries to create the `-shm` sidecar, which fails on a read-only *directory*. `immutable=1` is the load-bearing piece.
> **Node.js requirement:** `node:sqlite` only passes `SQLITE_OPEN_URI` to SQLite (so the `immutable=1` query is honored rather than treated as part of a literal filename) starting in **v22.15.0** (22.x line), **v23.11.0**, and every **v24+**. pnpm's `engines` floor is `>=22.13`, so on a runtime older than that the frozen open is detected up front and fails with a clear `ERR_PNPM_FROZEN_STORE_UNSUPPORTED_NODE` instead of SQLite's cryptic "unable to open database file". (pacquet uses rusqlite with an explicit `SQLITE_OPEN_URI` flag, so it has no such floor.)
> The `immutable=1` URI path is also percent-encoded (`%`→`%25`, `?`→`%3f`, `#`→`%23`, in that order, leaving `/` literal) so a store path containing those characters doesn't truncate the path or inject a spurious query parameter — applied identically in both stacks.
### Build backstop under the global virtual store
Under the global virtual store (default), a package's directory lives **inside** the store (`{storeDir}/links/...`). Applying a patch or running an allowlisted lifecycle script writes into that directory — so on a frozen store it would crash mid-build with a raw `EROFS`. A fully-seeded store never reaches the build step (patched/built packages are imported from the side-effects cache and filtered out by the `isBuilt` gate), so any residual build candidate means the seed is **missing that package's build output**.
`buildModules` now refuses up front with `ERR_PNPM_FROZEN_STORE_NEEDS_BUILD` and actionable guidance ("rebuild the seed with their scripts enabled, or remove them from `onlyBuiltDependencies`") instead of failing cryptically once a script starts. The check is gated on the global virtual store — under the isolated linker, slot directories live in the writable project-local store, so builds there are fine. Non-allowlisted scripts never run, so they are not treated as a blocking write.
Bin-linking has its own read-only-store edge under the global virtual store. On a **warm** checkout (the project's `.bin/<name>` already points at the seed target) `linkBin` returns before touching the store, so it is write-free. But on a **fresh** checkout it (re)creates the bin and calls `fixBin`, whose `chmod` targets the bin's **source file inside the store** (`{storeDir}/links/...`) — which is refused with `EPERM`/`EACCES` on a read-only store, even though a complete seed already ships that bin executable (so the `chmod` is redundant). `@pnpm/bins.linker` now wraps that call in `ensureExecutable`: it swallows the refusal when the target is already executable and rethrows otherwise, so bin-linking is write-free against a frozen store on a cold checkout too, while a genuinely non-executable bin (a broken seed) still surfaces as an error.
The blocking predicate distinguishes the two write kinds: a **patch** is applied regardless of `ignoreScripts`, so a patched package is always blocked; a **lifecycle script** is suppressed under `ignoreScripts`, so an allowlisted build-requiring package is *not* blocked when scripts are off (it would write nothing). This avoids falsely rejecting a valid `--ignore-scripts` frozen install. **Optional dependencies are exempt**: a build or patch failure on an optional dependency is non-fatal at runtime, so a seed missing an optional package's build output skips that build (emitting the `skipped-optional-dependency` log) instead of blocking the install — in both stacks.
### Both stacks (parity rule)
**TypeScript pnpm CLI**
- Config plumbing (`@pnpm/config.reader`): `frozen-store` type, config-file key, default, `Config.frozenStore`.
- The read-only open branch (`@pnpm/store.index`): `immutable=1`, read-only statements, throwing mutators.
- Wiring through `@pnpm/store.controller` and `@pnpm/store.connection-manager` to the sole `StoreIndex` construction site.
- Gating the project-registry write (`@pnpm/installing.context`).
- The `--force` / pnpr-server conflict guards (`@pnpm/installing.deps-installer`) and CLI surface (`@pnpm/installing.commands`).
- **After-install rebuild** (`@pnpm/building.after-install`): the post-install rebuild opens its `StoreIndex` immutably under the flag, so re-reading the store for a rebuild never attempts a writable open against the frozen store.
- **Worker fix:** `@pnpm/worker` opens its *own* writable `StoreIndex` on every `readPkgFromCafs` cache hit, so a pure read crashed on a frozen store. `frozenStore` is threaded through to `getStoreIndex` and keyed into its connection cache.
- **Build backstop** (`@pnpm/building.during-install`): `buildModules` throws `ERR_PNPM_FROZEN_STORE_NEEDS_BUILD` for a GVS slot that would build/patch on a frozen store, honoring `ignoreScripts`; threaded from `@pnpm/installing.deps-installer`.
- **Bin-linking on a read-only store** (`@pnpm/bins.linker`): `linkBin` wraps the `fixBin` chmod in `ensureExecutable`, which tolerates `EPERM`/`EACCES` when the bin's store-resident source is already executable (a complete seed) and rethrows otherwise — so a fresh checkout against a frozen store links bins without crashing on the redundant chmod. Catch-on-failure keeps the writable hot path at zero added syscalls.
**Rust pacquet**
- A dedicated `open_immutable` / `shared_immutable_in` opens via `immutable=1`, selected only under the flag. Plain `open_readonly` keeps the ordinary `SQLITE_OPEN_READ_ONLY` open (WAL locking intact) because normal installs read the index while the same process's `StoreIndexWriter` writes it concurrently — an immutable connection skips all locking and change detection, so a concurrent writer would make those reads undefined.
- `--frozen-store` CLI flag + `frozenStore` workspace-yaml setting.
- The store-index writer is replaced with a drain-and-drop stub (`spawn_disabled`) and `init_store_dir_best_effort` is skipped under the flag.
- **Build backstop:** `build_modules` returns `BuildModulesError::FrozenStoreNeedsBuild` (`ERR_PNPM_FROZEN_STORE_NEEDS_BUILD`) under the same GVS + frozen-store condition, threaded from `config.frozen_store`. The gate keys off `should_run_scripts` (which already folds the allow-build policy), so it is correct without an explicit ignore-scripts branch — pacquet has no configurable ignore-scripts mode yet.
pacquet already separated read-only index access (`shared_readonly_in`, or `shared_immutable_in` under the flag) from writes, so it never had the worker-conflation bug; the flag makes the "no writes attempted" contract explicit and gates the remaining best-effort write attempts.
## Testing
- **TS:** `store.index` frozen-mode-on-`0555`-directory test (reads work, writes throw `ERR_PNPM_FROZEN_STORE_WRITE`) plus a path-with-`?` open test — both gated on the runtime's immutable-URI support, with a complementary test asserting `ERR_PNPM_FROZEN_STORE_UNSUPPORTED_NODE` fires where that support is absent (the CI Node 22.13.0 path); `config.reader` round-trip; `deps-installer` `--force` and pnpr-server conflict guards; `worker`/`package-requester` unit tests. End-to-end on a `chmod -R 0555` store: install succeeds, `node_modules` materializes, no `-shm`/`-wal`/`-journal` sidecars; negative control without the flag fails as expected; incomplete store → clean offline error.
- **pacquet:** `open_immutable_reads_wal_db_on_readonly_directory` unit test plus the `immutable_sqlite_uri` encoding test and a path-with-`?` open test; yaml + CLI fold tests; **integration test** `frozen_store_installs_against_a_read_only_store` — primes a store, `chmod 0555` the tree, runs `install --frozen-lockfile --frozen-store --offline`, asserts success + materialized `node_modules` + zero sidecars. Confirmed load-bearing by reverting the `immutable=1` fix (test then fails).
- **Build backstop (both stacks):** `building/during-install` unit tests — approved-build-not-cached and patched-not-cached refuse; cached, non-allowlisted, and non-GVS cases pass through; **approved-build-under-`ignoreScripts` passes through while patched-under-`ignoreScripts` still refuses** — and the matching pacquet `build_modules` tests (`frozen_store_gvs_patch_not_seeded_refuses` + GVS-off / frozen-off controls). Each confirmed load-bearing by disabling the relevant guard and watching the corresponding test fail.
- **Bin-linking (`bins/linker`):** `ensureExecutable` tests with `fixBin` mocked to reject with `EPERM` — an already-executable bin source resolves (and `fixBin` is asserted called, so it isn't the warm skip-guard passing), a non-executable one rethrows `EPERM`. Confirmed end-to-end by running the built `linkBins` against a real `chflags uchg`-immutable store: the executable-seed case resolves and links the bin, the non-executable-seed control throws `EPERM`.
A changeset is included with `"pnpm": minor` and `"@pnpm/bins.linker": patch` (the read-only-store bin-linking fix).
|
||
|
|
615c6694e1 |
feat: support URL-scoped registry auth via npm_config_// and pnpm_config_// env vars (#12338)
* feat: support URL-scoped registry auth via npm_config_// and pnpm_config_// env vars Adds a file-free way to configure registry authentication, e.g. npm_config_//registry.npmjs.org/:_authToken=<token> pnpm_config_//registry.npmjs.org/:_authToken=<token> These are host-scoped by construction — the registry the value applies to is encoded in the (trusted) variable name — so they cannot be redirected to another host by repository-controlled config. The env value is trusted: it overrides a project/workspace .npmrc but is still overridden by CLI options. pnpm_config_ wins over npm_config_ for the same key. * feat(pacquet): support URL-scoped registry auth via npm_config_// and pnpm_config_// env vars Pacquet parity for the same feature on the JS side: read URL-scoped registry credentials from npm_config_//… and pnpm_config_//… environment variables (e.g. npm_config_//registry.npmjs.org/:_authToken=<token>). These are trusted (sourced from the environment, not the repository) and host-scoped by construction, so they sit at the top of the .npmrc precedence chain — above the project .npmrc. pnpm_config_ wins over npm_config_ for the same key. Adds an EnvVar::vars() enumeration capability (default empty, so existing fakes keep compiling; production providers override it). * fix(pacquet): avoid Unicode ellipsis in a line comment (dylint) * fix: exclude tokenHelper from URL-scoped env auth; add case-insensitive tests Address review feedback on pnpm/pnpm#12338: - A `//host/:tokenHelper` env var would land in authConfig but trip the TOKEN_HELPER_IN_PROJECT_CONFIG guard (which only trusts the user .npmrc), incorrectly failing. tokenHelper names an executable, so it is now excluded from the env-scoped layer entirely. - Add tests for case-insensitive prefix matching and the tokenHelper exclusion. - Add a 'text' language hint to the changeset's fenced block (MD040). * fix(pacquet): avoid panics on non-UTF-8 / non-ASCII env var names Address CodeRabbit review on the pacquet env-auth code: - EnvVar::vars() used std::env::vars(), which panics if any env var name or value is not valid UTF-8. Iterate vars_os() and skip non-UTF-8 entries, matching var()'s .ok() behavior. (SystemEnv and Host.) - parse_url_scoped_env_name sliced with name[..prefix.len()], which panics when the byte index lands inside a multi-byte char. Use boundary-checked name.get(..) instead. - Add a regression test with non-ASCII env var names. * test: cover env-auth precedence and pacquet end-to-end wiring Fill the coverage gaps in the URL-scoped env-auth feature: - JS: assert a CLI-provided //host/:_authToken still beats the same env var (workspace < env < CLI), and that non-token cred fields work while a non-URL-scoped env key is ignored. - pacquet: add end-to-end tests through the full config load — that a npm_config_//… var is honored and outranks a project .npmrc token for the same host, and that the prefix is matched case-insensitively. FakeEnv now enumerates via vars() so the env-scoped reader sees the fixture. |
||
|
|
bc9ed78f48 |
fix: clearer warning when a project .npmrc uses env variables in registry/auth settings (#12333)
* fix: clearer warning when a project .npmrc uses env variables in registry/auth settings
The previous warning only said the setting was ignored. It now explains why
(the project .npmrc is committed to the repository and must not expand secrets
into request destinations or credentials) and how to fix it: move the value to a
trusted source such as the user-level ~/.npmrc or via pnpm config set, with a
link to the docs.
The suggested 'pnpm config set' example is only shown when the key has no
${...} placeholder, so the snippet is always safe to copy-paste (a shell would
otherwise expand a placeholder embedded in the key). The wording does not claim
a specific destination file.
* fix: only suggest a pnpm config set command for shell-safe keys
The key embedded in the warning's suggested 'pnpm config set' command comes
from a repository-controlled .npmrc. The previous guard only suppressed the
example for keys containing a ${...} placeholder, but a shell also expands
$(...), backticks and $VAR inside double quotes — so a crafted key could turn
the suggested copy-paste command into command execution. The example is now
emitted only for keys made up entirely of shell-inert characters.
|
||
|
|
822beb5fa0 |
fix: harden package-manager bootstrap metadata (#12296)
- Resolve package-manager bootstrap metadata through trusted user/CLI registries and trusted network config, defaulting to the public npm registry instead of project/workspace registry settings. - Apply that bootstrap config in `switchCliVersion()` and `syncEnvLockfile()` so repository `.npmrc` proxy/TLS/configByUri values cannot steer package-manager bootstrap traffic. - Validate repository-provided package-manager env-lockfile entries before auto-switch install/execution: dependency paths must be registry package paths and package records must use integrity-only resolutions. - Preserve the fast path for fully resolved, valid package-manager metadata; incomplete metadata is still resolved through trusted bootstrap registries. - Handle peer-suffixed package-manager snapshots by looking up `packages` entries with `removeSuffix(depPath)` while keeping `snapshots` keyed by the full dep path. |
||
|
|
1017c36776 |
fix: block untrusted request destination env expansion (#12291)
Fixes CAND-PNPM-122 / GHSA-3qhv-2rgh-x77r by making environment expansion trust-aware for registry/auth config and request destinations.
- Stops project `.npmrc` from expanding `${...}` placeholders in registry/proxy request destinations, URL-scoped keys, and registry credential values.
- Stops repository-controlled `pnpm-workspace.yaml` from expanding `${...}` placeholders in request destinations: `registry`, `registries`, `namedRegistries`, and `pnprServer`.
- Preserves env expansion for trusted user/global/auth.ini/CLI/global config/env config so existing token, registry, and pnpr server setup flows continue to work.
- Ports the same trust boundary to pacquet for dependency-management commands.
|
||
|
|
894ea6af2c |
feat: deprecate $ version references in overrides (#12262)
Using the "$" syntax in overrides (e.g. "react": "$react") now emits a deprecation warning. The syntax still resolves as before. Catalogs are the recommended replacement: reference a catalog entry with the "catalog:" protocol. Refs pnpm/pnpm#12160 |
||
|
|
3537020817 |
fix: respect pmOnFail ignore in self-update (#12231)
* fix: respect pmOnFail ignore in self-update * fix: preserve devEngines lockfile writes * fix: restore unrelated whitespace hunks --------- Co-authored-by: Zoltan Kochan <z@kochan.io> |
||
|
|
a23956e3ab |
fix(config/reader): pin unscoped per-registry settings to their source's registry at load time (#11953)
* fix(config/reader): drop user-level default auth when workspace overrides registry
When a workspace `.npmrc` overrides `registry=` to a different value than the
user's `~/.npmrc` or `~/.config/pnpm/auth.ini` would have set, do not bind
unscoped/default credentials (`_authToken`, `_auth`, `username`/`_password`)
from the user-level config to the workspace-selected registry. The previous
behavior leaked user-trusted credentials to whatever registry an untrusted
workspace `.npmrc` pointed at. Reported by JUNYI LIU.
* chore(cspell): allow JUNYI in changeset and tests
* fix(config/reader): also defend when pnpm-workspace.yaml overrides registry
Move the rebind defense to after all config layers (CLI, env vars,
pnpm-workspace.yaml, .npmrc) have settled. Compare the final resolved
default registry against what the user-level config alone would produce,
and skip the check entirely if the user requested a registry via CLI/env
themselves.
* feat(config/reader): deprecate unscoped authentication credentials
Emit a per-file warning whenever an .npmrc or auth.ini contains an
unscoped auth value (_authToken, _auth, username, _password,
tokenHelper). URL-scoped tokens have been npm's recommended pattern
since npm@9, and unscoped credentials are slated for removal in a
future major. The warning fires independently of whether the rebind
defense rejects the credentials, so users see the deprecation even when
their setup happens to be safe today.
* refactor(config/reader): rescope unscoped credentials at load time instead of detecting rebinds post-merge
Each .npmrc / auth.ini / CLI source's unscoped credential keys
(_authToken, _auth, username, _password, tokenHelper) are rewritten to
their URL-scoped equivalent during load, using the same source's
registry= value (or the npmjs default if it declares none). A later
layer overriding registry= can no longer rebind a credential to its own
registry — the credential is already pinned to the URL its author
intended.
This removes the post-merge source-tracking defense and replaces it
with the simpler per-source normalization. Each rescope emits a
deprecation warning so users migrate to writing the URL-scoped form
directly.
* refactor(network/auth-header): drop empty-string default-registry slot
After load-time rescoping, no source can populate configByUri[''] —
every credential is either URL-scoped from the start or rewritten to
the URL-scoped form during the .npmrc / auth.ini / CLI parse. The
runtime fallback that re-keyed configByUri[''] onto the merged default
registry, and the publish-side fallback that read it, are both dead
code.
Removed:
- empty-string handling in getAuthHeadersFromCreds, including its
defaultRegistry parameter
- defaultRegistry parameter from createGetAuthHeaderByURI
- the corresponding dedicated unit test
- the configByUri['']?.creds fallback in publishPackedPkg.ts
- empty-key assertions in config/reader tests
Updated all ~16 call sites of createGetAuthHeaderByURI to drop the now
unused second argument.
* feat(config/reader): extend per-source rescoping to client TLS cert/key
The same trust-boundary issue that affected unscoped credentials applies
to client TLS settings: an unscoped cert=/key= would be presented to
whatever registry the merged config settles on, even if a later layer
(workspace .npmrc, pnpm-workspace.yaml, CLI flag) overrode it. The
existing rescope helper now also rewrites unscoped `cert` and `key`
to their URL-scoped form, pinning them to the registry their author
named in the same source.
`ca`/`cafile` are intentionally left unscoped: they're trust anchors,
not credentials, and corporate MITM-proxy setups depend on them
applying to every HTTPS request. The default-registry override can't
weaponize an unscoped CA — the attacker would need a cert signed by it.
`certfile`/`keyfile` (file-path variants) are not rescoped either:
`certfile` isn't read unscoped by pnpm today (asymmetric vs. `keyfile`
in NPM_AUTH_SETTINGS), and supporting only one of them would be
confusing. Users wanting the path form can write it URL-scoped
directly.
* chore(config/reader): remove dead unscoped `keyfile` allowlist entry
`keyfile` was listed in NPM_AUTH_SETTINGS so unscoped `keyfile=<path>`
passed the .npmrc filter and ended up in authConfig — but nothing in
the codebase ever read it from there. The dispatcher uses `opts.key`
(inline PEM) and `configByUri[host].tls.key` (URL-scoped path/inline
content), neither of which is populated from unscoped `keyfile=`.
`certfile` was already absent from the allowlist for the same reason,
so this also removes the asymmetry between the two file-path variants.
URL-scoped `//host/:certfile=...` and `//host/:keyfile=...` continue
to work via `tryParseSslKey` and are unaffected.
* test(network/auth-header): drop test for removed default-registry slot
This test exercised the configByUri[''] re-keying path that was
removed in the rescope-at-load refactor. With createGetAuthHeaderByURI
no longer accepting a defaultRegistry parameter and unscoped
credentials no longer reaching the merged config, the scenario the
test described is structurally unreachable.
* fix(config/reader): handle empty/invalid registry value in rescope
Two CI fixes:
1. When a source's `registry=` resolves to an empty string (e.g. an
unresolved `${ENV_VAR}` placeholder), `new URL(...)` inside
`nerfDart` throws. Guard the call with try/catch: drop the
unscoped per-registry keys (a bare token has nowhere safe to bind)
and emit a warning naming the offending source.
2. Update `.npmrc does not load pnpm settings` to expect the rescoped
form of unscoped `_authToken`/`username` in `authConfig` — they
now appear as `//registry.npmjs.org/:_authToken` etc. since the
test's .npmrc declares no `registry=` of its own.
* chore(cspell): allow "rescoping"
* test(installing/deps-installer): drop "legacy way" auth test
This test passed credentials via the configByUri[''] empty-string slot,
which the auth-header layer re-keyed to the merged default registry at
request time. That slot was removed in the rescope-at-load refactor —
credentials are now always URL-scoped before they reach configByUri,
so the empty-key entry is unreachable from any code path.
The scenario the test covered (basicAuth via username/password) is
already exercised by the existing "installing a package that need
authentication, using password" test using the URL-scoped form.
|
||
|
|
3687b0e180 |
fix(config/reader): resolve relative cafile path against the .npmrc directory (#11726)
* fix(config/reader): resolve relative cafile path against the .npmrc directory `cafile=<relative-path>` in `.npmrc` was being read via `fs.readFileSync`, which resolves relative paths against `process.cwd()`. When pnpm is invoked from a different cwd than the project (e.g. `pnpm --dir <project> install` in CI wrappers and monorepo scripts), the CA file silently failed to load: the `try/catch` in the loader dropped the CA list, the install proceeded without the configured CA, and the user only saw TLS errors against a private registry — with no log line tying back to the wrong path. Resolve relative `cafile` values in `readAndFilterNpmrc` against `path.dirname(filePath)` of the .npmrc that declared the key, before `loadCAFile` reads the file. Absolute paths (the dominant CI shape) and CLI `--cafile` are unchanged. Ref: #11624 * refactor(config/reader): tighten cafile-fix comments The test name and the linked issue already describe the failure mode, so the 4-line preamble on the test and the 5-line in-line comment on the implementation were re-narrating what the tests document. --- Written by an agent (Claude Code, claude-opus-4-7). * fix(pacquet/config): resolve relative cafile path against the .npmrc directory Pacquet's `NpmrcAuth::from_ini` used to store the `cafile=` value verbatim and pass it to `std::fs::read_to_string` at apply time. A relative path therefore resolved against the process cwd, so a project `.npmrc` containing `cafile=certs/ca.pem` reached via `pacquet --dir <proj>` from a different cwd silently failed to load the CA — same failure mode as pnpm/pnpm#11624 on the TypeScript side, which the parent commit fixed by resolving against `path.dirname` of the `.npmrc`. Mirrors the parent commit on the pacquet side: - `NpmrcAuth::from_ini` now takes the directory the `.npmrc` was loaded from. A relative non-empty `cafile=` value is resolved against that directory via `npmrc_dir.join(...)`; empty and absolute values pass through unchanged. - `Config::current` tracks which of `start_dir` / home dir actually provided the `.npmrc` text and passes that path through. - The `load_cafile` doc comment that documented "matches pnpm's surprising cwd-resolution behavior" is gone; that caveat was current only as long as pnpm itself had the bug. - Existing tests updated mechanically to pass `Path::new("")` for the new parameter; four new tests cover the resolution branches (relative resolves, absolute passes through, empty passes through, end-to-end load via `apply_to` with a real tempdir-based fixture). --- Written by an agent (Claude Code, claude-opus-4-7). * fix(pacquet/config): add trailing commas inside multi-line assert_eq! macros `perfectionist::macro-trailing-comma` is enforced via dylint at CI and ran clean before the cafile port. Rustfmt reflowed two `assert_eq!` calls in `parses_strict_ssl_true_and_false` onto multiple lines when the `Path::new("")` argument made the line too long, but did not add the trailing comma the dylint rule wants on the last macro argument. --- Written by an agent (Claude Code, claude-opus-4-7). --------- Co-authored-by: shiminshen <16914659+shiminshen@users.noreply.github.com> Co-authored-by: Zoltan Kochan <z@kochan.io> |
||
|
|
d1b340f3fe |
fix: synchronize default registry from pnpm-workspace.yaml for login/logout commands (#11744)
Closes #10099 |
||
|
|
8df408c901 |
fix(config): warn when package.json has a legacy "pnpm" field with migrated settings (#11680)
* fix(config): warn when package.json has a legacy "pnpm" field In v11, pnpm stopped reading settings from the `pnpm` field of package.json (#10086). Most former pnpm-field settings now live in `pnpm-workspace.yaml`; a few (e.g. `onlyBuiltDependencies`, `executionEnv`) were removed entirely. Until now the old field was silently ignored, so users upgrading from v10 had no signal that their overrides or patched dependencies had stopped taking effect. Emit a warning whenever the `pnpm` field contains any key that pnpm no longer reads from package.json. The check is an allowlist (only `pnpm.app`, consumed by `pnpm pack-app`, is still active), so the warning won't go stale as new settings are added or removed in future versions. The message points users at https://pnpm.io/settings rather than prescribing a single fix, since the new home depends on the key. Closes #11677. * fix(config): only warn for migrated pnpm-field keys, not unrelated ones Previously the warning fired for every key under `pnpm` except `app`, which would surface false positives for third-party tooling that piggybacks on the `pnpm` namespace. Switch to an explicit denylist of the v10 settings that moved to pnpm-workspace.yaml, matching the PR's stated contract. --------- Co-authored-by: Damon <damon@deeplearning.ai> Co-authored-by: Zoltan Kochan <z@kochan.io> |
||
|
|
ba2c8844c9 |
fix(config): apply pmOnFail default to devEngines.packageManager (singular) (#11682)
* fix(config): apply pmOnFail default to devEngines.packageManager (singular)
The pnpm v11 release notes document the `pmOnFail` default as `download`
(via the migration table that maps `managePackageManagerVersions: true` →
`pmOnFail: download (default)`). The legacy `packageManager` field already
gets that default applied at the central onFail-resolution site, but the
singular form of `devEngines.packageManager` short-circuited it by setting
`onFail = 'error'` inside `parseDevEnginesPackageManager`, so projects that
pinned a different pnpm via `devEngines.packageManager` saw a hard version
mismatch instead of an auto-download.
Drop that local `?? 'error'` and let the central default apply. The array
form of `devEngines.packageManager` keeps its own per-element defaults
('error' for the last entry, 'ignore' for the rest) — those reflect
explicit prioritisation by the user, not a system-wide fallback. Explicit
`onFail` values are still honored everywhere.
Closes #11676.
* chore: fix spelling (prioritisation → prioritization)
cspell flagged the British spelling at pre-push.
---------
Co-authored-by: Damon <damon@deeplearning.ai>
|
||
|
|
020ac45d3d |
fix: tolerate padded auth base64 (#11694)
* fix: tolerate padded auth base64 * fix: avoid regex in auth padding normalization |
||
|
|
d3f8408def |
fix: global installs respect build policy from global config.yaml when GVS is enabled (#11363)
* fix(config.reader): move GVS allowBuilds default after globalDepsBuildConfig re-application
The GVS default allowBuilds = {} was applied too early — before
workspace manifest settings were read and before .npmrc values
(dangerouslyAllowAllBuilds) were re-applied via globalDepsBuildConfig.
This caused hasDependencyBuildOptions() to return true (because {}} is
not null), blocking restoration of .npmrc values. Global installs
with GVS enabled would silently skip all build scripts even when
the config explicitly allowed them.
This fix moves the GVS default to after both workspace manifest
reading and globalDepsBuildConfig re-application, so that:
1. Workspace manifest allowBuilds takes precedence (if present)
2. .npmrc dangerously-allow-all-builds is properly restored
3. Empty {}} is only applied as a last resort
Closes #9249
* fix(config.reader): apply Copilot suggestion for GVS allowBuilds guard
From PR review discussion_r3141002317:
- Replace hasDependencyBuildOptions() == null with hasDependencyBuildOptions()
so the GVS default only applies when no build policy at all is
configured (not even dangerouslyAllowAllBuilds). This is cleaner because
the condition now matches the re-application guard on the line
immediately before it.
- Add regression test verifying that dangerouslyAllowAllBuilds with GVS
preserves allowBuilds when no global workspace manifest exists.
* docs: update changeset
* fix(config.reader): address PR review feedback
- Fix unreachable GVS allowBuilds default: hasDependencyBuildOptions()
always returns true after globalDepsBuildConfig re-applies defaults
(dangerouslyAllowAllBuilds: false is != null). Replace with explicit
allowBuilds == null && dangerouslyAllowAllBuilds !== true check.
- Rename .npmrc references to global config.yaml in changeset, comments,
and test names (zkochan: v11 reads from global rc file, not .npmrc).
- Add try/finally env cleanup for XDG_CONFIG_HOME and PNPM_HOME in tests.
- Add test for workspace manifest allowBuilds precedence over config.yaml.
* fix(config.reader): fix GVS workspace manifest test
- Use import.meta.dirname/global/v11 for globalPkgDir (matches env.PNPM_HOME)
- Fix assertion: dangerouslyAllowAllBuilds coexists with allowBuilds
- Clean up global/v11 directory in finally block to prevent test leakage
* fix(config.reader): use object form for workspace manifest allowBuilds; clean up parent global/ dir
Fixes two PR #11363 review threads:
1. allowBuilds in workspace manifest must be Record<string, boolean>,
not array — createAllowBuildFunction uses Object.entries()
2. Remove empty config/reader/test/global/ directory after test
* fix(config.reader): address production review nits
- Update changeset: use camelCase dangerouslyAllowAllBuilds (YAML key, not .npmrc)
- Add enableGlobalVirtualStore assertion to first GVS test
- Add comment explaining dangerouslyAllowAllBuilds coexistence on config object
* fix(config.reader): address Copilot review — env safety, GLOBAL_LAYOUT_VERSION, try/finally
- Move XDG_CONFIG_HOME mutation and file setup inside try blocks
so env is always restored even if setup throws
- Replace hard-coded v11 with GLOBAL_LAYOUT_VERSION import
- Fix corrupted try/finally in workspace manifest precedence test
(missing finally block and mangled expect line from prior bad edit)
- Reword comment: enableGlobalVirtualStore defaults to true for
global installs, not \"when not in CI\"
* fix(config.reader): address last 3 Copilot review threads — comment wording, cleanup placement, test rename
* fix(config.reader): fix block-scoped globalDir leak in GVS test
* fix: address Copilot review #4194783789 — restore auth test, fix naming, remove artifacts
* Remove local dev tooling — not part of this PR
* Remove PR.md — issue context is in the PR description
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Tom Hale <tom@hale.net>
|
||
|
|
a62f959242 |
fix(config): drop unresolved ${VAR} placeholders from .npmrc auth values (#11526)
Closes #11513. `actions/setup-node` writes `_authToken=${NODE_AUTH_TOKEN}` to `.npmrc`. When the user relies on OIDC trusted publishing without setting `NODE_AUTH_TOKEN`, pnpm previously passed the literal placeholder through verbatim — so any time OIDC fallback failed, pnpm sent `Authorization: Bearer ${NODE_AUTH_TOKEN}` to the registry and the publish came back as a 404. This worked in v10 because `pnpm publish` shelled out to `npm publish`, whose own OIDC flow handled the case. The fix lives in `@pnpm/config.env-replace@4.1.0`, which adds an `envReplaceLossy` variant that returns `{ value, unresolved }` instead of throwing. Unresolved `${VAR}` placeholders become `''` and are reported back as a list — leaving OIDC trusted publishing as the sole auth source. Resolvable placeholders and `${VAR-default}` / `${VAR:-default}` fallbacks elsewhere in the same string still expand normally, so a value like `pre-${SET}-mid-${UNSET}-${OTHER-default}-post` now produces `pre-AAA-mid--default-post` rather than dropping every placeholder. Also treats `{ KEY: undefined }` in the env object the same as a missing key (the `Record<string, string | undefined>` contract), so a `${KEY-default}` reaches the fallback in that case. ### Changes - `@pnpm/config.env-replace` catalog bumped from `^3.0.2` → `^4.1.0` (`pnpm-workspace.yaml`, `pnpm-lock.yaml`) - `config/reader/src/loadNpmrcFiles.ts` — `substituteEnv` now calls `envReplaceLossy` and pushes one warning per unresolved placeholder - `config/reader/test/index.ts` + `parseCreds.test.ts` — regression tests covering the OIDC case, mixed resolvable/unresolved placeholders, explicit-undefined env values, and `parseCreds({ authToken: '' })` - `.changeset/oidc-unresolved-env-placeholder.md` — patch bump for `@pnpm/config.reader` and `pnpm` - `pacquet/crates/config/{env_replace.rs, npmrc_auth.rs, npmrc_auth/tests.rs}` — mirrors the lossy semantics in pacquet's local `env_replace_lossy`, with matching test coverage |
||
|
|
6925be3b90 |
fix(config): honor NPM_CONFIG_USERCONFIG as a low-priority fallback (#11545)
* fix(config): honor NPM_CONFIG_USERCONFIG as a low-priority fallback
Restores compatibility with environments that point npm at a custom
.npmrc via NPM_CONFIG_USERCONFIG (e.g. actions/setup-node writing to
${runner.temp}/.npmrc), which silently broke after the v11 env var
prefix change. PNPM-prefixed env vars and npmrcAuthFile from the
global config.yaml continue to take precedence.
Closes #11539
* fix(config): treat empty NPM_CONFIG_USERCONFIG as unset
`??` accepts an empty string as a defined value, so an exported but
unset NPM_CONFIG_USERCONFIG would short-circuit the fallback chain and
make normalizePath('') resolve to process.cwd(). Mirror readEnvVar's
empty-string-to-undefined coercion via a readNpmEnvVar helper so the
fallback to ~/.npmrc works as expected.
|
||
|
|
7bcfd970e9 |
fix(config): align scoped registry resolution between config get and publish (#11494)
Fixes [#11492](https://github.com/pnpm/pnpm/issues/11492). In pnpm v11 a scoped registry resolved to different URLs depending on which command read it: - `pnpm config get @<scope>:registry` returned the value from `.npmrc` - `pnpm publish` used the value from `pnpm-workspace.yaml`'s `registries` block When the two sources disagreed, `pnpm publish` silently targeted the URL from `pnpm-workspace.yaml`, even though `pnpm config get` reported a different (and seemingly authoritative) URL — so users could publish to the wrong registry without any indication. This PR makes `pnpm config get @<scope>:registry` read from the merged `Config.registries` map (the same map `publish` and the resolvers use) before falling back to `authConfig`. Both commands now report and use the same URL. |
||
|
|
fcec623c00 |
fix(config): allow user-level preferences in global config.yaml (#11477)
Moves 20 user-level preference settings from the workspace-only exclusion list into the global config allowlist (`config/reader/src/configFileKey.ts`): - Shell / scripts: `scriptShell`, `shellEmulator` - Notifications & UI: `updateNotifier`, `useStderr` - Trust policy (already DLX-inherited as user-level posture): `trustPolicy`, `trustPolicyExclude`, `trustPolicyIgnoreAfter` - Store / virtual store: `globalVirtualStoreDir`, `virtualStoreDir`, `virtualStoreDirMaxLength`, `verifyStoreIntegrity`, `sideEffectsCache`, `sideEffectsCacheReadonly` - Build / dep verification: `strictDepBuilds`, `verifyDepsBeforeRun` - Misc personal/system prefs: `stateDir`, `registrySupportsTimeField`, `initPackageManager`, `initType`, `agent` These are personal/system preferences rather than workspace structure. In v10 they could be set in `~/.npmrc`. v11 silently dropped them from both `~/.npmrc` and the new global `config.yaml`, leaving `pnpm-workspace.yaml` as the only working location — which the issue author rightly points out is impractical for system-level defaults like `scriptShell`. After this change: - Settings in `~/.config/pnpm/config.yaml` are applied instead of being filtered out by `isConfigFileKey` (`config/reader/src/index.ts:296`). - `pnpm config set --location global scriptShell <path>` succeeds instead of throwing `ConfigSetUnsupportedYamlConfigKeyError` (same predicate used in `config/commands/src/configSet.ts:237`). `pmOnFail` and `runtimeOnFail` are intentionally left workspace-only because they would cause lockfile divergence between contributors when set globally. `~/.npmrc` support for non-auth/non-network keys is also intentionally not restored — the team has moved those settings to YAML config. Closes #11474. |
||
|
|
42378d07fb |
fix(config): warn on ignored settings in global config.yaml (#11470)
- pnpm v11 silently drops local-only settings (e.g. `nodeLinker`, `hoistPattern`, `linkWorkspacePackages`) when they appear in the global `config.yaml`. Users had no way to tell their global configuration was being ignored. - The reader now emits a warning that names the ignored keys and the path of the global config file, and directs users to either move the settings to a project-level `pnpm-workspace.yaml` or share them across projects via [config dependencies](https://pnpm.io/11.x/config-dependencies). - Allowed keys (e.g. `dangerouslyAllowAllBuilds`, proxy settings) continue to be accepted with no warning. close #11429 |
||
|
|
d74ddaaecd |
fix: accept uppercase PNPM_CONFIG_* env vars (#11468)
* fix(config): accept uppercase PNPM_CONFIG_* env vars Env vars are case-sensitive on macOS/Linux, so PNPM_CONFIG_USERCONFIG — the rename suggested by the v11 migration guide — was silently ignored because parseEnvVars only matched lowercase pnpm_config_*. Also wire the env var into the early npmrcAuthFile lookup so it actually decides which user-level .npmrc gets read. Closes #11465 * chore: add changeset for npmrc auth file env-var load order * test: cover lowercase pnpm_config_npmrc_auth_file env var Matches the exact env var name reported in #11465. Without the early env-var lookup before loadNpmrcConfig, this case is parsed too late to actually load the custom .npmrc. * test: lock precedence when both lowercase and uppercase env vars are set |
||
|
|
6d7903a8b7 |
fix: reject invalid overrides values (#11380)
* fix: reject invalid overrides values * fix: improve overrides validation error messages |
||
|
|
e3ccf6b134 |
fix(config): default minimumReleaseAgeStrict to true when user sets minimumReleaseAge (#11436)
* fix(config): default minimumReleaseAgeStrict to true when user sets minimumReleaseAge Without this, a user-set `minimumReleaseAge` would silently fall back to installing an immature version when no mature version satisfied the requested range, making the setting look like it had no effect (#11433). The built-in default of `minimumReleaseAge` (1440) stays non-strict for backward compatibility, and an explicit `minimumReleaseAgeStrict: false` is still respected. * chore(changeset): downgrade to patch * fix(config): apply minimumReleaseAgeStrict default after env var parsing Move the strict-default logic to run after `parseEnvVars` so `pnpm_config_minimum_release_age` is also covered. * test(config): also assert minimumReleaseAge in the strict=false test |
||
|
|
b61e268d57 |
feat: add support for github prefix and named registries (#11324)
This is consistent with #9358, but implements support for the GitHub Packages npm registry and, more broadly, for vlt-style https://docs.vlt.sh/cli/registries for any registry. This PR adds a built-in gh: specifier that resolves against the GitHub Packages npm registry, plus a namedRegistries config key so a project can map its own aliases to arbitrary registries. A project can mix public npm packages and private GitHub Packages (or self-hosted) ones without applying a scope-wide registry override to every @scope/* package. - pnpm add gh:@acme/private writes "@acme/private": "gh:^1.0.0" and resolves from https://npm.pkg.github.com/. - pnpm add gh:@acme/private@^1.0.0 (with or without an alias) is also supported. Aliased form writes "my-alias": "gh:@acme/private@^1.0.0". - Auth comes from the existing per-URL .npmrc mechanism, e.g. //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}. No new auth surface. - @github is intentionally not defaulted to https://npm.pkg.github.com/ - hardcoding that would hijack installs of the public @github/* packages on npmjs.org (e.g. @github/relative-time-element) for users without a scope-wide override. Use gh: to install from GitHub Packages, or configure @github:registry=... yourself if that's really what you want. - Additional named registries (a self-hosted proxy, GitHub Enterprise Server, etc.) can be configured in pnpm-workspace.yaml: ```yml namedRegistries: gh: https://npm.pkg.github.example.com/ # optional: overrides the built-in `gh` alias for GHES work: https://npm.work.example.com/ ``` - Then work:@corp/lib@^2.0.0 resolves against https://npm.work.example.com/, and the built-in gh alias can be redirected to a GHES host. - Env-var substitution (${VAR}) is supported in namedRegistries values, mirroring the .npmrc convention. - Reserved alias names (npm, jsr, github, workspace, catalog, file, git, http, https, link, patch, and related git host shorthands) cannot be redefined as user-named registries - the resolver throws ERR_PNPM_RESERVED_NAMED_REGISTRY_ALIAS at startup rather than silently shadowing another protocol. Malformed URLs throw ERR_PNPM_INVALID_NAMED_REGISTRY_URL at startup too, instead of failing as a confusing 404 during resolution. - On publish, createExportableManifest strips any named-registry prefix (both the built-in gh: and any user-configured alias) so npm and yarn consumers can still resolve the dependency via their own scope-registry configuration - mirroring the user-facing requirement when installing such a dep without the prefix. The prefix is gh: rather than github: because github: is reserved by npm-package-arg / hosted-git-info as a git host shorthand (e.g. github:owner/repo) - reusing it would be a deviation from the specs used by the npm CLI. gh: is shorter, matches vlt's convention, and cannot collide with any existing npm scheme. Unlike jsr:, gh: (and any other named-registry alias) does not rewrite the package name - gh:@acme/foo resolves @acme/foo from the GitHub Packages registry as-is. This also means npm/yarn consumers see the original name after the prefix is stripped on publish. --------- Co-authored-by: Zoltan Kochan <z@kochan.io> |
||
|
|
187049055f |
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.
|
||
|
|
ff7733ce21 |
feat: add runtimeOnFail setting (#11277)
* feat: add runtimeOnFail setting
Adds a `runtimeOnFail` config setting ('ignore' | 'warn' | 'error' |
'download') that overrides the `onFail` field on `devEngines.runtime`
and `engines.runtime` in the root project's package.json. This makes
it possible to opt into (or out of) runtime auto-download without
changing the project manifest.
* fix: skip runtime download when version is missing
Without a version, convertEnginesRuntimeToDependencies would write
`runtime:undefined` into the manifest. Warn and skip instead.
* feat: apply runtimeOnFail override during install
The config reader override only mutates the context's rootProjectManifest,
but installDeps reads the manifest fresh via tryReadProjectManifest and
findWorkspaceProjects. Apply the override there too so `runtimeOnFail`
actually affects what gets installed. Adds an e2e test covering both
download and ignore overrides through the real CLI bundle.
|
||
|
|
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 |
||
|
|
b65204762a |
refactor(config): move network settings from .npmrc to YAML config (#11209)
Proxy settings (httpProxy, httpsProxy, noProxy), local-address, strict-ssl, and git-shallow-hosts are now written to config.yaml (global) or pnpm-workspace.yaml (local) instead of auth.ini/.npmrc. They are still readable from .npmrc for easier migration from npm CLI. The canonical YAML key names (httpProxy, httpsProxy, noProxy) match Yarn Berry's naming convention. - Add httpProxy, httpsProxy, noProxy to PnpmSettings type - Add http-proxy to pnpmTypes and pnpmConfigFileKeys - Separate network keys from auth keys in config routing - Add isNpmrcReadableKey for backward-compatible .npmrc reading |
||
|
|
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
|
||
|
|
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 |
||
|
|
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.
|
||
|
|
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
|
||
|
|
8bba5c3858 |
refactor(config): only read auth/registry from .npmrc, add registries to pnpm-workspace.yaml (#11189)
Replace the unmaintained @pnpm/npm-conf package with a purpose-built module that reads only auth/registry-related settings from .npmrc files using read-ini-file + @pnpm/config.env-replace (both already deps). All non-registry settings (hoist-pattern, node-linker, etc.) are now only read from pnpm-workspace.yaml, CLI options, or environment variables. Registry-related settings (auth tokens, registry URLs, SSL certs, proxy settings) continue to be read from .npmrc for migration compatibility, and can also be set in pnpm-workspace.yaml. New modules: - loadNpmrcFiles.ts: reads .npmrc from standard locations, filters to auth/registry keys, returns structured layers - npmConfigTypes.ts: inlined npm config type definitions - npmDefaults.ts: inlined npm defaults (registry, unsafe-perm, etc.) |
||
|
|
2df8b71467 |
refactor(config): stop shelling out to npm for auth settings (#11146)
* refactor(config): stop shelling out to npm for auth settings Read and write auth-related settings (registry, tokens, credentials, scoped registries) directly to INI config files instead of delegating to `npm config`. Removes the @pnpm/exec.run-npm dependency from @pnpm/config.commands. * fix(config): give pnpm global rc priority over ~/.npmrc for auth settings Auth settings from the pnpm global rc file (e.g. ~/.config/pnpm/rc) now override ~/.npmrc in rawConfig. This ensures tokens written by `pnpm login` are correctly picked up by `pnpm publish`, since login writes to the pnpm global rc but ~/.npmrc previously took priority in the npm-conf chain. * chore: remove @pnpm/exec.run-npm package No longer used after removing npm config CLI delegation. * chore: remove accidentally committed __typecheck__/tsconfig.json * fix(config): narrow non-string rejection to credential keys, add priority test Non-string value rejection now only applies to credential keys (_auth, _authToken, _password, username), registry URLs, and scoped/registry- prefixed keys — not to INI settings like strict-ssl, proxy, or ca that can legitimately have boolean/null values. Added a test verifying that auth tokens from the pnpm global rc take priority over ~/.npmrc. |
||
|
|
0e8042e6dc |
revert: "feat: add allowBuildsOfTrustedDeps setting (true by default) (#11078)"
This reverts commit
|
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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> |
||
|
|
2e55dcf130 |
fix: default to ['.'] for workspacePackagePatterns when packages field is missing (#10996)
Commit |
||
|
|
303ca410f5 |
feat!: stop reading settings from the pnpm field of package.json (#10086)
Settings should be read from pnpm-workspace.yaml |
||
|
|
dba4153767 |
refactor: rename packages and consolidate runtime resolvers (#10999)
* refactor: rename workspace.sort-packages and workspace.pkgs-graph - workspace.sort-packages -> workspace.projects-sorter - workspace.pkgs-graph -> workspace.projects-graph Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: rename packages/ to core/ and pkg-manifest.read-package-json to reader - Rename packages/ directory to core/ for clarity - Rename pkg-manifest/read-package-json to pkg-manifest/reader (@pnpm/pkg-manifest.reader) - Update all tsconfig, package.json, and lockfile references Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: consolidate runtime resolvers under engine/runtime domain - Remove unused @pnpm/engine.runtime.node.fetcher package - Rename engine/runtime/node.resolver to node-resolver (dash convention) - Move resolving/bun-resolver to engine/runtime/bun-resolver - Move resolving/deno-resolver to engine/runtime/deno-resolver - Update all package names, tsconfig paths, and lockfile references Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: update lockfile after removing node.fetcher Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: sort tsconfig references and package.json deps alphabetically Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: auto-fix import sorting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: update __typings__ paths in tsconfig.lint.json for moved resolvers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove deno-resolver from deps of bun-resolver --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
4a36b9a110 |
refactor: rename internal packages to @pnpm/<domain>.<leaf> convention (#10997)
## Summary Rename all internal packages so their npm names follow the `@pnpm/<domain>.<leaf>` convention, matching their directory structure. Also rename directories to remove redundancy and improve clarity. ### Bulk rename (94 packages) All `@pnpm/` packages now derive their name from their directory path using dot-separated segments. Exceptions: `packages/`, `__utils__/`, and `pnpm/artifacts/` keep leaf names only. ### Directory renames (removing redundant prefixes) - `cli/cli-meta` → `cli/meta`, `cli/cli-utils` → `cli/utils` - `config/config` → `config/reader`, `config/config-writer` → `config/writer` - `fetching/fetching-types` → `fetching/types` - `lockfile/lockfile-to-pnp` → `lockfile/to-pnp` - `store/store-connection-manager` → `store/connection-manager` - `store/store-controller-types` → `store/controller-types` - `store/store-path` → `store/path` ### Targeted renames (clarity improvements) - `deps/dependency-path` → `deps/path` (`@pnpm/deps.path`) - `deps/calc-dep-state` → `deps/graph-hasher` (`@pnpm/deps.graph-hasher`) - `deps/inspection/dependencies-hierarchy` → `deps/inspection/tree-builder` (`@pnpm/deps.inspection.tree-builder`) - `bins/link-bins` → `bins/linker`, `bins/remove-bins` → `bins/remover`, `bins/package-bins` → `bins/resolver` - `installing/get-context` → `installing/context` - `store/package-store` → `store/controller` - `pkg-manifest/manifest-utils` → `pkg-manifest/utils` ### Manifest reader/writer renames - `workspace/read-project-manifest` → `workspace/project-manifest-reader` (`@pnpm/workspace.project-manifest-reader`) - `workspace/write-project-manifest` → `workspace/project-manifest-writer` (`@pnpm/workspace.project-manifest-writer`) - `workspace/read-manifest` → `workspace/workspace-manifest-reader` (`@pnpm/workspace.workspace-manifest-reader`) - `workspace/manifest-writer` → `workspace/workspace-manifest-writer` (`@pnpm/workspace.workspace-manifest-writer`) ### Workspace package renames - `workspace/find-packages` → `workspace/projects-reader` - `workspace/find-workspace-dir` → `workspace/root-finder` - `workspace/resolve-workspace-range` → `workspace/range-resolver` - `workspace/filter-packages-from-dir` merged into `workspace/filter-workspace-packages` → `workspace/projects-filter` ### Domain moves - `pkg-manifest/read-project-manifest` → `workspace/project-manifest-reader` - `pkg-manifest/write-project-manifest` → `workspace/project-manifest-writer` - `pkg-manifest/exportable-manifest` → `releasing/exportable-manifest` ### Scope - 1206 files changed - Updated: package.json names/deps, TypeScript imports, tsconfig references, changeset files, renovate.json, test fixtures, import ordering |