Commit Graph

6 Commits

Author SHA1 Message Date
Zoltan Kochan
0a4d6656c9 feat(pacquet): implement the update command (#12102)
* feat(package-manager): implement the `update` command in pacquet

Port pnpm's `update` (aliases `up`/`upgrade`) onto pacquet's
always-fresh-resolve install path.

- Compatible bump: withhold matched names' lockfile pins from the
  preferred-versions seed (new `UpdateSeedPolicy`) so they re-resolve
  to highest-in-range; `package.json` is left untouched. This is what
  distinguishes `update` from `install`.
- `--latest`: fetch each matched direct dep's `latest` tag and rewrite
  the manifest range (`^v`, or exact under `--save-exact`), like `add`.
- Selectors: bare-name/glob patterns (`depth>0`, no `--latest`) match
  every locked package name at any depth; versioned (`foo@2`) or
  `--latest` selectors match direct deps only; `--latest` + spec is
  rejected with `ERR_PNPM_LATEST_WITH_SPEC`.
- CLI flags: `-L/--latest`, `-E/--save-exact`, `-P/-D/--no-optional`
  (faithful `makeIncludeDependenciesFromCLI`), `--depth`,
  `--lockfile-only`, `-i/--interactive` (dialoguer + inline outdated
  check).
- `--global` and `--workspace` error out for now: the global-dir and
  workspace-version-linking subsystems are not ported yet.

The resolver's `UpdateBehavior` tri-state and the npm resolver's
`include_latest_tag` already existed; this change drives them from a
CLI command.

Written by an agent (Claude Code, claude-opus-4-8).

* feat(package-manager): port more pnpm update tests + fix selector parsing

Reviewing pnpm's `update` test suite surfaced gaps:

- Fix `parse_update_param` to match pnpm's `parseUpdateParam`: search
  for the version `@` starting at index 2 for `!`-negated patterns (1
  otherwise), so `!@scope/pkg-*` is no longer wrongly split into
  pattern `!` + version `scope/pkg-*`. Negation selectors now work.
- Add `--no-save`: the range rewrites still drive resolution (lockfile
  updates) but `package.json` is not persisted. Mirrors pnpm's
  `updatePackageManifest: opts.save !== false`.
- Add `ERR_PNPM_NO_PACKAGE_IN_DEPENDENCIES`: a selector that matches no
  direct dependency under `--depth 0` (without `--latest`) now errors,
  matching pnpm; `--latest` with an unmatched selector is a no-op.

Ports the negation-pattern, `--no-save`, and no-package-in-deps tests
from pnpm's `installing/commands/test/update/update.ts`.

Written by an agent (Claude Code, claude-opus-4-8).

* perf(package-manager): avoid per-snapshot String alloc in update seed filter

`UpdateSeedPolicy::DropOnly`'s snapshot filter allocated a `String` for
every lockfile snapshot key. Parse the (small) update-target set to
`PkgName` once and compare against `key.name` directly. Addresses a
review comment on #12102.

Written by an agent (Claude Code, claude-opus-4-8).
2026-06-01 15:01:22 +02:00
Zoltan Kochan
54d2b57000 feat(pnpr): server-accelerated installs via pnprServer (endpoints + client + CLI) (#12077)
## What

Adds an opt-in **`pnprServer`** setting that offloads the slow part of an install — dependency resolution and computing which files the local store is missing — to a [pnpr](https://github.com/pnpm/pnpm/tree/main/pnpr) server, which streams back only the missing files. `node_modules` is still linked **locally** from the server-produced lockfile (like server-side rendering: the compute runs remotely, the result is materialized locally).

Realizes the agent concept from [RFC #9](https://github.com/pnpm/rfcs/pull/9), reworked around how it's actually used and rewritten in Rust on pacquet + pnpr.

## How it works

1. `pacquet install` (with `pnprServer` set) handshakes the server — `GET /-/pnpr` — to negotiate a protocol version.
2. It `POST`s `/v1/install` with the project's dependencies, the integrities already in its store, and **its own registry config** (default `registry`, `namedRegistries`, `overrides`, `minimumReleaseAge`).
3. The server resolves against *those* registries, fetches any uncached packages into its store, and streams NDJSON: `D` (missing file digests), `I` (pre-packed store-index entries), `L` (lockfile + stats).
4. The client downloads the missing files from `/v1/files` (gzip binary), writes them into its CAFS **by digest** (no re-hashing), writes the index entries, and runs a frozen install to link `node_modules` from the server's lockfile.

## Pieces

- **Server (`pnpr`)** — `GET /-/pnpr` handshake + `POST /v1/install` (NDJSON) + `POST /v1/files` (gzip), additive and opt-in alongside the npm-compatible API. Resolves against the client-sent registries, interning a `&'static Config` per distinct client config to bound the leak.
- **Client (`pacquet-pnpr-client`)** — `PnprClient`: reads store integrities, negotiates the protocol version, sends the registry config, parses the stream, materializes files + index entries, returns the lockfile. Rejects unrequested file entries and repairs truncated CAFS files.
- **CLI** — the `pnprServer` setting (`--pnpr-server`, `pnprServer:` in `pnpm-workspace.yaml`, `PNPM_CONFIG_PNPR_SERVER`). When set, `pacquet install` routes through the client and then links locally — pnpm's `install()` → `installFromPnpmRegistry` shape. `trustPolicy: no-downgrade` is refused (the server can't enforce it), matching pnpm's `TRUST_POLICY_INCOMPATIBLE_WITH_AGENT`.

## Design notes

- **A distinct URL, not the registry.** The server resolves from the registries the client sends, so it's a compute service — not "a registry that resolves from itself" — which is why it's a separate `pnprServer` URL rather than reusing `registry`. The same server works for any client's registry setup, and a single pnpr can be both registry and `pnprServer`.
- **Handshake = version negotiation + fail-fast.** Explicit opt-in, so there's no silent fallback to local resolution; a non-pnpr server (404) or a version mismatch errors clearly.
- **Naming:** everything is `pnpr`; "agent" survives only in upstream citations (`@pnpm/agent.client`, the pnpm-agent PoC, pnpm's `TRUST_POLICY_INCOMPATIBLE_WITH_AGENT` error code).

## Tests

- `pacquet-pnpr-client`: resolve + download, multi-file package, warm-store no-op, and handshake rejection. The pnpr server's own uplink is left at the default, so resolution provably uses the **client-sent** registry.
- `pacquet-cli`: a real `pacquet install --pnpr-server <url>` against an in-process pnpr (resolving from the mocked fixtures registry) links `node_modules`.
- `pnpr`: `/v1/files` binary-framing round-trip + handshake route.

Full suites green; clippy / dylint (Perfectionist) / fmt / taplo / `cargo doc -D warnings` clean.

## Deferred

Auth/credential forwarding (so private/scoped registries resolve), `pacquet add` / `remove` via `pnprServer`, multi-project workspaces, and true streaming (responses are buffered today).

Refs https://github.com/pnpm/rfcs/pull/9
2026-05-31 16:50:20 +02:00
Zoltan Kochan
1db05c6fca fix: inconsistent resolution of a peer shared through a diamond (#12081)
* fix: inconsistent resolution of a peer shared through a diamond

When a package peer-depends both another package and one of that
package's own peer dependencies (e.g. @typescript-eslint/eslint-plugin
peer-depends both @typescript-eslint/parser and typescript, and
@typescript-eslint/parser peer-depends typescript), pnpm reused a
hoisted instance of the shared peer that was resolved against a
different version, producing an inconsistent resolution.

Close #12079

* test: cover the peer-diamond resolution in pnpm and pacquet

Add @pnpm.e2e/peer-diamond-* fixtures modeling #12079 (plugin
peer-depends both parser and ts; parser peer-depends ts) and
integration tests on both stacks. The pnpm test guards the fix; the
pacquet test confirms pacquet already resolves the diamond consistently
(its merge always prefers the node's own child).

* docs: fix grammar in changeset (peer-depends on both)
2026-05-31 16:03:12 +02:00
Zoltan Kochan
d99b725878 chore: license pnpr and pnpm-agent under PolyForm Shield 1.0.0 (#12082)
* chore(pnpr): license under PolyForm Shield 1.0.0

Relicense the pnpr/ subtree (the pnpm-compatible registry server) from
MIT to the source-available PolyForm Shield License 1.0.0. The rest of
the monorepo stays MIT. pnpr may be run, modified, and self-hosted for
any purpose except providing a product that competes with it.

- Add pnpr/LICENSE.md (PolyForm Shield 1.0.0).
- Override the inherited workspace MIT in the pnpr crates via
  license-file.
- Point the @pnpm/pnpr npm wrapper at the bundled LICENSE.md.
- Note the carve-out in the root README (the root LICENSE stays
  pristine MIT so license detection keeps recognizing it).

* chore(agent): license pnpm-agent under PolyForm Shield 1.0.0

Relicense the pnpm-agent server (agent/server) from MIT to the
source-available PolyForm Shield License 1.0.0, matching pnpr. The
@pnpm/agent.client package stays MIT so the agent protocol remains
openly implementable.

- Add agent/server/LICENSE.md (PolyForm Shield 1.0.0).
- Set the package license to "SEE LICENSE IN LICENSE.md".
- Exempt pnpm-agent from meta-updater's MIT normalization via a
  SOURCE_AVAILABLE_PKGS set, so lint:meta stays green.
- Note the carve-out in the agent/server README + add a changeset.

pnpm-agent is only a devDependency of the pnpm CLI, so no source-
available code ships in the MIT-licensed CLI artifact.

* docs(license): add contribution terms with relicensing grant for pnpr and pnpm-agent

Contributions to the source-available trees (pnpr/, agent/server) are
accepted under the same PolyForm Shield License plus a grant letting the
licensor relicense them under other terms. This preserves the option to
later relax to a more permissive source-available license or offer a
separate commercial license without per-contributor consent.

- Add pnpr/CONTRIBUTING.md and agent/server/CONTRIBUTING.md.
- Point to them from each tree's README license section.

* docs(license): add npm trademark/non-affiliation notice to pnpr and pnpm-agent

State that pnpr and pnpm-agent are not affiliated with or endorsed by
npm, Inc., GitHub, or Microsoft, and that "npm" is used only to describe
registry-protocol compatibility. Also add a License section to the
published @pnpm/pnpr npm wrapper README.
2026-05-30 22:51:14 +02:00
Khải
13b1b9aaa2 chore(rust/dylint): upgrade perfectionist to 0.0.0-rc.17 (#12070)
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-29 20:58:31 +00:00
Zoltan Kochan
c5d9d3a8f3 refactor(pnpr): rename pnpm-registry to pnpr (#12069)
* refactor(pnpr): rename pnpm-registry to pnpr

Rename the registry server across the board to match the npm wrapper
package name, which was already `@pnpm/pnpr`.

- crate `pnpm-registry` -> `pnpr`, `pnpm-registry-fixtures` -> `pnpr-fixtures`
- binaries `pnpm-registry` -> `pnpr`, `pnpm-registry-prepare` -> `pnpr-prepare`
- module paths and log targets `pnpm_registry::*` -> `pnpr::*`
- binary-locating env vars `PNPM_REGISTRY_BIN` -> `PNPR_BIN`,
  `PNPM_REGISTRY_PREPARE_BIN` -> `PNPR_PREPARE_BIN`
- top-level directory `registry/` -> `pnpr/` (crates, npm wrapper, fixtures)

The registry-mock storage concept is intentionally left as-is:
`PNPM_REGISTRY_MOCK_PORT`/`PNPM_REGISTRY_MOCK_STORAGE`/`PNPM_REGISTRY_STORAGE`,
the `~/.cache/pnpm-registry/storage` path + benchmark cache keys, and the
external `pnpm-registry-mock` npm package referenced in test fixtures.

* style(pnpr): rustfmt import grouping after rename

* ci(pnpr): point typos at pnpr instead of removed registry dir

* chore(pnpr): update pre-push path filter from registry to pnpr
2026-05-29 20:02:10 +02:00