Commit Graph
14 Commits
Author SHA1 Message Date
Zoltan Kochan f8ba6e18e7 fix(patching): support patching in workspaces with sharedWorkspaceLockfile false (#17155)
Fix `pnpm patch`, `pnpm patch-commit`, `pnpm patch-remove`, and `pnpm install` in workspaces with `sharedWorkspaceLockfile: false` in both pnpm v11 (`pnpm11/`) and pnpm v12 (`pnpm/`):

1. `pnpm patch`: When `sharedWorkspaceLockfile: false` and the target directory has no current lockfile, merge `packages` and `snapshots` across all workspace projects' current lockfiles to resolve the installed package version. Anchor `patchesDir` to `workspaceDir ?? lockfileDir`.
2. `pnpm patch-commit`: Search workspace project `modulesDir`s and `<editDir>/../..` when resolving the patched package directory, write patches relative to `workspaceDir ?? lockfileDir` without mutating `opts.patchedDependencies` to relative paths before delegating to `install`, and update lockfile snapshots across all workspace projects.
3. `pnpm patch-remove`: Resolve patch files relative to `workspaceDir ?? lockfileDir` and run a workspace-wide dedicated-lockfile install when `sharedWorkspaceLockfile: false`.
4. `pnpm install`: Skip per-project unused-patch errors during individual workspace project installs when `sharedWorkspaceLockfile: false`, and verify patches across the union of all workspace project lockfiles once the full workspace install finishes.

Closes pnpm/tasks#176
2026-10-20 16:56:23 +02:00
Zoltan Kochan 430d892741 feat(pnpr)!: serve every ecosystem under its own prefix (#17087)
pnpr served npm at the server root when npm was the only configured
ecosystem and moved it under /npm/ as soon as a second ecosystem was
added. Adding an ecosystem therefore broke every .npmrc registry URL,
every tarball URL already written into a lockfile, named-registry URLs,
and OIDC workload publish paths, and /npm meant the package `npm` on one
server and the ecosystem prefix on another. This settles section A1 of
pnpm/tasks#131: every ecosystem is always prefixed.

- Ecosystem::base_path is now independent of the configuration. The
  per-configuration Registries::base_path and is_only_ecosystem are gone,
  and the routes, the resolver's tarball URLs, OIDC workload paths and
  pnpr's own-origin route classification all use it.
- The default image registry still answers at /v2/ on the host root; an
  image client cannot be pointed at a prefix. The registry directory keeps
  its `prefixed` and `namedPrefixed` flags for the web UI.
- The read-only token allowlist matched the npm audit endpoints at the
  root only, so read-only tokens were already refused for audits on
  multi-ecosystem servers. It now matches them under the npm prefix.
- /metrics no longer switches between Prometheus metrics and the npm
  package `metrics` on the Accept header; the package is /npm/metrics.
- The pnpm v11 and v12 test harnesses, the registry-mock crate and the
  integrated benchmark address the fixture registry at /npm/ and keep the
  server origin for the pnpr protocol endpoints. The benchmark asks each
  revision mock's registry directory whether npm is prefixed, so it can
  still compare against a pnpr revision from before this change.
2026-10-18 16:55:55 +02:00
KhảiandClaude Opus 5 1606ba03fb docs: add the documentation and comment principles and apply them (#16516)
* docs(agents): add the documentation and comment principles

Import the principles that transfer from KSXGitHub/perfectionist's
contributor guide, plus four of our own: concise and non-repeating text,
structured text over prose, removal over addition when reviewing or
fixing nitpicks, and a definition of the user-facing text that must not
name implementation details.

The root guide's "Comments" section gains "Register", "Prose that cannot
go stale", "User-facing text", and "Reviewing documentation and
comments". The pacquet style guide adds the Rust specifics (clap and
napi doc comments are user-facing; a default is documented on the
field, not the variant). DOCUMENTATION.md gains the renderer and link
table for each markdown surface.

The guides themselves are brought in line: counts over growable lists
become plain references, a quoted heading becomes an anchor link, and
the size limits are stated once, in the style guide, instead of three
times.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs: keep user-facing text free of implementation names

The `@pnpm/napi` README and type declarations named the Rust engine, the
in-repo package name, and TypeScript internals (`filterPkgsDiff`,
`nameFormatter`) that a user of the addon cannot reach. The pnpmfile
page in both docs trees explained a limitation by the IPC mechanism
behind it. Four changesets named the release line or implementation,
which the package targets already express.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(crates): cut restating and stale comments in pack, modules-yaml and nine more crates

Apply the comment principles to pack, versioning, pnpr-client,
package-is-installable, fs-packlist, workspace-task-scheduler,
engine-runtime-node-resolver, exportable-manifest, modules-yaml,
pnpr-registry, and pnpr-auth. Removed: call-site comments that repeat
the callee's doc, module rosters of exports that the `pub use` lines
already list, "not yet implemented" notes for behaviour that exists
(`beforePacking`, `hoistedLocations`), body narration, and history
framing. Comment-only; no code changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(crates): cut restating and stale comments in the small resolver, catalog and pnpr crates

Apply the comment principles to the local, tarball, default, deno and
jsr resolvers, the catalog crates, detect-libc, config-parse-overrides,
env-replace, git-utils, matcher, diagnostics, and the pnpr policy,
cargo, error, osv and fixture crates. Removed: parentheticals that
repeat a `#[diagnostic(code)]` attribute, counts over lists of
variants, callers and entry points, rosters of callers, doc paragraphs
that repeat a sibling's or the module's text, comments narrating the
match arms or condition below them, and history framing. The
`pnpr-prepare --help` text no longer names the test harness.
Comment-only; no code changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(crates): cut restating and stale comments in publish, workspace, registry and graph-hasher

Apply the comment principles to publish, auth-commands, registry,
workspace, lockfile-verification, default-reporter, the git resolver,
deps-inspection, hooks, graph-hasher, package-manifest, and the pnpr
shared-artifacts and route crates. Removed: a module roster of every
export, counts over lists, plan notes for work that landed ("Phase 5",
"Phase 6"), call-site comments repeating the callee's doc, docs
narrating match arms, a default claim on a `#[default]` variant, a
misattached doc, and history framing. Comment-only; no code changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(crates): cut restating comments in napi, reporter and pnpr crates

Apply the comment principles to napi, reporter, resolving-resolver-base,
real-hoist, git-fetcher, workspace-manifest-writer, and the pnpr config
and storage crates. Removed: "Mirrors X in index.d.ts" sentences on
`#[napi]` items and other implementation names in addon-facing docs,
stale "not emitted yet" and "when it lands" notes for behaviour that
exists, step-by-step comments narrating the body, emit-site and caller
rosters, docs duplicated from a sibling or the module, a doc attached to
the wrong item, and history framing. Comment-only; no code changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs: cut restating and stale comments across the remaining crates and pnpm11

Apply the comment principles to the rest of the Rust workspace and to the
TypeScript CLI: the cli crate and its clap definitions, package-manager,
config, patching, the deps resolver, lockfile, pnpr, the npm resolver,
tarball, network, executor, cmd-shim, the benchmark and fixture tooling,
and the pnpm11 packages.

Removed: comments narrating the body, docs repeating a callee's, a
sibling's or the module's text, counts over growable lists, rosters of
callers and exports, "not yet implemented" notes for behaviour that
exists, history framing, and `@param` lines restating a signature.

One exception to comment-only: in `pnpr/crates/pnpr/src/resolver/resolve.rs`
the `Workspace` struct sat inside the doc block written for `resolve`, so
the struct moved above it and each doc now attaches to its own item.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(crates): cut body narration and duplicated docs in resolver, fs and package-manager

Removed: a numbered "Sequence:" list narrating `ensure_file`'s body and
the paragraph restating its backoff constants, the stress-test module
doc's roster of its own tests, refactor-history paragraphs in the peer
walk's finalize pass, call-site and caller-side copies of a callee's doc
(the `optional` AND-fold stays documented on `register_seeded_package`
and on `ResolvedPackage`), a verbatim quotation of a TypeScript test's
expected output in the pnpr search module doc, and field and digest-input
rosters in the package-manager.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(deps-restorer): cut plan references and re-attach misplaced docs

Removed the "slice 4" plan references and the comments narrating the
bodies they sat above. The `Auto` downgrade-chain doc moved from
`copy_and_log` to `auto_link`, the function it describes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(deps-restorer): cut the remaining body narration in the hoist path

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs: cut the last body narration and a history clause in the cmd-shim test

`build_graph.rs` kept a `patches` parameter doc that re-derived what
`BuildOptions::patches` documents. The PowerShell-arguments test on main
said the arguments survived "before the shim ended its batch context
too", which describes the change rather than the behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(cmd-shim): point the PowerShell-shim link at its current module

Main moved `wants_powershell_shim` into `windows_shim_policy`, so the
shim writer's link to it no longer resolved and `cargo doc` failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(cli): link the fixture the ignored-build setup installs

The shortened doc named `workspace_with_unapproved_build` in backticks,
which `perfectionist::bare_identifier_reference` rejects for an item that
resolves in scope. It is an intra-doc link now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(crates): restore two contracts and fix four dangling references

A review of this branch's sweep flagged seven places. Six needed a
change:

- `from_ini` documents again that a relative `cafile=` resolves against
  `npmrc_dir`, a precondition its callers have to honour.
- `hash_object` documents again that the private serializer's bytestream
  has to match pnpm 11's, because the digest is persisted and read by
  both.
- The hooked-manifests note no longer says "above" of a gate that lives
  in another module.
- The store-dir race test states its synchronization contract instead of
  labelling it "Post-Option-C".
- `GraphNodeHashOptions.nodeVersion` keeps `undefined` as a literal.
- The pnpr streaming module drops the sentence fragment that introduced
  its list.

The seventh, a `beforePacking` limitation in exportable-manifest, stays
removed: the note said the pnpmfile bridge cannot supply that hook, and
`pnpm_pack::lifecycle::apply_before_packing` has been running it since
before this branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(cli): say in `pnpm why --help` that `--exclude-peers` is ignored

Demoting the caveat to an ordinary comment took it out of the help,
which then advertised a flag that does nothing: `WhyArgs::run` never
reads `exclude_peers`, and pnpm 11's `why` handler never forwards its
`excludePeers` either. The doc comment states that behavior in the words
a user sees, and the parity reason stays in a comment beside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

* docs(cli): drop the doc comment that described the wrong module

The comment described the `pnpm_deps_inspection as deps_tree` re-export
above it, but a doc comment attaches to the item that follows, so rustdoc
showed it as the description of `mod dispatch`. The `use` item already
names the alias, so the comment goes rather than moving.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-02 20:28:21 +02:00
Zoltan Kochan 4edeef9176 style(ts): port the perfectionist lint rules to ESLint (#16415)
Add TypeScript ports of the perfectionist rules that the Rust workspace
enforces through dylint.toml to @pnpm/eslint-config, with the same limits,
and fix every finding that needs no refactor.

The size and shape rules (function length, cognitive complexity, nesting,
local bindings, file length) are enforced for packages that already meet
them. The packages that do not are listed in PENDING_SIZE_AND_SHAPE_REFACTOR
in eslint.config.mjs and are refactored in follow-up changes.

The custom rules mirror perfectionist's semantics rather than reusing
sonarjs or max-lines-per-function, so that one limit means the same thing
in both codebases: a closure is a level of the body that contains it, a
nested function declaration is measured on its own, and a test-framework
callback plays the role of a #[test] function.

Bugs found and fixed along the way:
- a registry error body cut at exactly 64 KiB lost its truncation marker
- removing a dependency could leave dangling .bin shims
- constructor/__proto__ names crashed the projects graph, were skipped by
  the injected-deps syncer, and were dropped from writeWantedLockfile's
  returned lockfile

The CI chunk runner learns to run the node:test suite of the ESLint
config package.
2026-09-30 15:10:08 +02:00
Maxime Jeunot a17983125f fix(patch-commit): throw error if git not found on Linux (#11100) 2026-09-26 01:57:48 +02:00
Alexis H. MunsayacandClaude Opus 5.5 ffc5f29a3e fix(patch): reuse the existing patch of a git-hosted dependency (#15666)
`pnpm patch-commit` records the patch of a git-hosted package under
`name@version`, but `pnpm patch` looked the existing patch up under
`name@<tarball URL>`, the bare specifier it picks for such a package.
The lookup never matched, so re-running `pnpm patch` gave a clean copy
without the committed changes.

Fall back to `name@version` when the bare specifier finds no patch, in
both the TypeScript and the Rust CLI.

Closes https://github.com/pnpm/pnpm/issues/9699

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 16:34:16 +02:00
Zoltan Kochan 8fad8a309d fix(patching): fall back to copy when hard linking fails in patch-commit (#15395)
In environments where hard linking fails (such as cross-device
boundaries or filesystems without hard link support), fall back to
copying package files when preparing the diff view.

Closes pnpm/pnpm#7295
2026-09-23 23:36:35 +02:00
Zoltan Kochan 769892f2e0 fix(patch): update lockfile snapshot on patch-commit when manifest dependencies change (#15350)
When a package is patched and its package.json has dependencies,
optionalDependencies, or peerDependencies modified or removed,
pnpm patch-commit now updates the corresponding lockfile snapshot
and prunes unreachable packages prior to re-resolving install in
both pnpm v11 and pnpm v12 (pacquet).

Closes pnpm/pnpm#6866.
2026-09-23 14:58:32 +02:00
Zoltan Kochan 8727478a8a fix(patch): resolve default patch path with pnpm patch-commit <pkg> (#15333)
When running `pnpm patch-commit <pkg>` or `pnpm patch-commit <pkg>@<version>`,
resolve the active edit directory from `.pnpm_patches` and `state.json` instead
of requiring the full directory path. If multiple versions of the same package
are currently being patched, emit an ambiguous patch target error.

Closes pnpm/pnpm#6206.
2026-09-23 08:05:39 +02:00
Zoltan Kochan f7cd88dc6a test: tolerate hosted git transport fallback (#15219)
Request the pinned archive explicitly in the patch test that asserts a tarball choice.
Permit the same pinned ajv-keywords commit over the Git transport in the license test
when the public-repository probe cannot confirm archive availability.
2026-09-21 21:32:47 +02:00
Woohyun Park 43593366dd fix(patching): support non-ASCII patch paths (#13802)
Pass core.quotePath=false when patch-commit asks Git to diff the extracted and edited package directories. Otherwise Git C-quotes non-ASCII arguments in diff headers, preventing pnpm from normalizing and parsing its own generated patch.

Mirror the option and regression coverage in the TypeScript CLI and the Rust pnpm port.

Closes pnpm/pnpm#13801.
2026-08-17 15:29:01 +02:00
Zoltan Kochan c9b8632ea6 feat(config): declare each registry once in the registries setting (#13942)
Registries do not all lay out tarball URLs the way the npm registry does.
JFrog Artifactory repeats the scope in a scoped package's tarball filename
(`@acme/widget/-/@acme/widget-1.0.0.tgz`) where npm strips it. pnpm cannot
rebuild such a URL, so it writes it out for every scoped package instead of
omitting it, and the lockfile carries a host-specific URL per dependency.

pnpm had no place to record such a fact, because it had no place to
describe a registry at all — only three ways to name one. `registries`
mapped a scope to a URL, `namedRegistries` mapped a bare-specifier prefix
to a URL, and neither could carry anything else.

`registries` now declares a registry once, keyed by its URL, with every
fact about it in the entry: a `serverType`, the `scopes` routed to it,
and the `prefix` it answers to. `serverType` has three states:

  undeclared  strict; only the exact canonical URL is reconstructible
  npm         also serves the percent-encoded scoped path
  artifactory repeats the scope in the tarball filename

registry.npmjs.org resolves to `npm` as a built-in, so its behavior is
unchanged and the old hostname check becomes that one default rather than a
special case in the predicate. `npm` cannot be the default: asserting
npmjs-compatibility is a claim only the operator can make.

The URL is the key because every fact in an entry is a fact about that
server. Keying the layout by scope would bind it to whoever the scope
currently points at, so two developers whose scope resolves differently
would write lockfiles that disagree about which URLs may be omitted.
`scopes` and `prefix` are routes to the registry and are inverted at
config-read time into the two lookups the rest of pnpm already queries,
leaving the resolver, installer, and lockfile layers untouched and the
precedence chain (builtin < .npmrc < yaml < `_auth` < CLI) unchanged.

The layout is declared, never inferred. Sniffing the registry URL cannot work:
a virtual repository serves both layouts at once, depending on whether each
package was synced from upstream or published locally, so no registry-level
signal — route, response header, or probe — decides it. Declaring it also
keeps a wrong guess a fixable misconfiguration instead of a silent breakage.

`serverType` feeds a single URL builder that both sides of the lockfile use:
the writer omits a tarball URL only when the builder reproduces it, and the
reader rebuilds it with the same call. They therefore agree by construction,
so pnpm never has to assume a registry serves some second URL as well.

The setting lives in pnpm-workspace.yaml rather than .npmrc because the
lockfile depends on it: one developer omitting URLs that another reconstructs
differently would break a frozen install. A `serverType` in the global
config.yaml is ignored for the same reason, while the routes declared
alongside it are kept. Credentials are rejected there — the file is
committed — and still belong in .npmrc. The registry URL is the map key, so
the request-destination env gate applies to keys as well as values.
Credentials and unknown fields are refused after parsing, since a parse
error renders the offending source line verbatim.

A map whose values are all strings is the older `<scope>: <url>` shape and
is still read as one. Mixing the two shapes in one map is refused, and so is
a URL-keyed entry written as a string. `namedRegistries` is deprecated in
favor of `prefix` and is read only for prefixes `registries` does not
declare; a prefix stays singular because it is the registry's identity in a
lockfile dep path.

An entry that routes nothing to itself and matches no configured registry is
reported as a warning rather than silently ignored; it is a warning and not
an error because a shared config dependency can legitimately describe
registries a given project does not use.

Config dependencies and pnpr-server-mode resolution pass no server type; in
both, the writer and the reader share that default, so they stay consistent.

Closes pnpm/get-npm-tarball-url#16. Supersedes pnpm/pnpm#13920.
2026-08-17 01:28:21 +02:00
YES!HYUNGSEOKandClaude 0a154b1c35 fix(patching.commands): keep the dependency alias when patching a single git-hosted version (#12518)
The single git-hosted version branch of getPatchedDependency spread the options
object instead of the parsed dependency, so the result lost `alias` (the package
name) and carried unrelated option fields. Spread `dep`, matching the sibling
branches.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-23 15:06:36 +02:00
Zoltan Kochan fc2f33912e refactor: move the TypeScript pnpm CLI into a pnpm11/ directory (#12537)
The TypeScript pnpm CLI freezes at v11; pnpm 12 will be the Rust pacquet
port. To make that split legible, all TypeScript source, test, and build
directories move under a new top-level pnpm11/ directory. The name states
the version boundary rather than implying a behavioral fork, since the two
stacks are meant to behave identically.

Scope is source-only: the shared workspace root stays at the repo root.
pnpm-workspace.yaml, package.json, pnpm-lock.yaml, .pnpmfile.cjs,
.meta-updater, __patches__, .changeset, .husky, and the lint/spell configs
remain in place, so one pnpm workspace and one Cargo workspace still span
all three products. pnpr/client and pacquet/tasks/registry-mock stay as
cross-product workspace members.

Rewiring the move required:
- pnpm-workspace.yaml globs prefixed with pnpm11/
- root package.json script paths, eslint.config.mjs, tsconfig.lint.json,
  .gitignore, and CODEOWNERS updated
- .meta-updater/src/index.ts literals repointed (pnpm11/pnpm/package.json,
  pnpm11/__utils__, pnpm11/__typings__, and the main package directory)
- regenerated every moved package's repository/homepage URL via meta-updater
- pnpm11/pnpm/bundle-deps.ts and __utils__/scripts/src/typecheck-only.ts
  climb one more level to reach the repo root

.meta-updater stays at the repo root because @pnpm/meta-updater resolves
its config at <cwd>/.meta-updater/main.mjs.

TS CI (.github/workflows/ci.yml) now only runs when pnpm11/-relevant paths
change, via a dorny/paths-filter changes job plus a TS CI / Success
aggregate gate; branch protection should require only that gate.
2026-06-20 14:36:25 +02:00