Bit parses .npmrc through @pnpm/config.reader only to hand the result
back to the engine, and that JS package chain is what keeps Bit's
registry mirror problems alive (its mirror caps @pnpm/config.env-replace
below the version config.reader needs). The engine already resolves the
full config cascade for its own installs; readConfig projects the
subset an embedder consumes: registries with resolved Authorization
headers, authHeaderByUri, proxy, TLS, network limits, directories, and
install behavior settings.
A pnpm-lock.yaml that fails to parse aborted every install flavor,
matching upstream only for --frozen-lockfile. A regular install now
warns ("Ignoring broken lockfile at ..."), resolves from the
manifests, and rewrites the file - the lockfile is regenerable state,
and failing left no way to install at all. Found via Bit: a broken
lockfile aborted the install after node_modules had already been
purged for layout drift, leaving the workspace with neither packages
nor a usable lockfile.
Ports deps-installer/test/lockfile.ts:1288 ("a lockfile with duplicate
keys is fixed") as the regression test.
On a fresh install, extract_children collected a resolved package's
dependencies and optionalDependencies without deduplicating. npm merges
optionalDependencies into dependencies at publish time, so registry
manifests list every platform-specific optional dependency in both maps,
and each got two resolve edges: one optional, one non-optional. The
non-optional edge bypassed the platform gate in
PrefetchingResolver::should_skip_prefetch (it only applies to optional
edges), so fresh installs prefetched the tarball of every platform
variant, e.g. all seven `@typescript/native-preview-*` packages on a
linux-x64 host. The lockfile came out correct regardless; only the
downloads were wasted.
Fold the duplicate into one optional edge keeping the dependencies
range, mirroring the TypeScript resolver's
{...optionalDependencies, ...dependencies} merge in
getNonDevWantedDependencies. The TypeScript CLI is unaffected: its
object-spread merge already collapses the duplicate key, confirmed by
byte-identical lockfiles between the two stacks on the repro.
Bump pnpm/update to the release that adds the
update-pnpm-minimum-release-age input, and set it to 0. pnpm 12
defaults minimumReleaseAge to 24 hours and self-update deliberately
ignores the repo's minimumReleaseAgeExclude, so the job could never
bump the pin to a pnpm release published the same day: the next-12
dist-tag silently resolved to the newest mature version instead.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
pnpm run forwards everything after the script name to the script verbatim,
including the -- separator itself, so the create-release-pr workflow's
`pnpm run bump -- --release <product>` invocations delivered a literal --
as the first argument and the fail-closed parser rejected it.
parseSelectedProducts now skips a single leading --; a -- in any other
position, and any other unrecognized token, still fails closed.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The git ls-remote command spawned by the GitHub Actions checker now includes GIT_TERMINAL_PROMPT=0 in its environment. This ensures that the command fails immediately instead of blocking the user on an interactive prompt when the repository is private and the user lacks HTTPS credentials. Closespnpm/pnpm#13421
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
fix(install-summary): suppress '(X is available)' when latest is held back by minimumReleaseAge
When minimumReleaseAge (default: 24h) left the registry's
dist-tags.latest immature, the install summary still printed
'(X is available)' for it — advertising the exact version the policy
had just refused to install.
The hint only ever names the actual latest tag: the resolvers now
surface dist-tags.latest on the resolve result only when the active
policy would allow installing it (latest_allowed_by_policy /
latestAllowedByPolicy — an O(1) check of the tag's publish timestamp
against the cutoff, honoring publishedByExclude full-name and exact-
version entries). An immature latest suppresses the hint instead of
being rewritten to an older mature version, so the hint never names a
non-latest version as latest. Suppression requires positive evidence
of immaturity: a missing or unparsable timestamp keeps the raw tag,
matching the pick itself, which only enforces the policy when the
packument's time map is usable.
Also fixes a pre-existing divergence in the pacquet reporter: it gated
the hint on 'latest != version' rather than 'latest > version', so it
would suggest downgrading when the installed version was newer than
the latest tag. Now uses node-semver comparison, matching the
TypeScript reporter's semver.lt check.
Closespnpm/pnpm#11698.
An importer whose required-peer round converged skips re-discovery
until its inputs change; later rounds walk only newly added direct
deps (the full direct set still defines the provider context, and
earlier subtrees' scope-filtered missing reports replay from an
accumulator); a children-ownership handover with an unchanged
peer-shadowed context no longer flips sibling occurrences lazy or
bumps the rewrite counter; and resolver-internal maps use rustc-hash.
Peers provided by multiple candidate versions may bind to a different
(still range-valid) provider than before; output stays deterministic.
Measured on a 114-importer workspace: resolution 77s -> 36s, discovery
walks 802 -> ~400, node visits 2.6M -> ~1.0M.
Profile and remaining work: https://github.com/pnpm/pnpm/issues/13505
The peers-cache hit path replayed the cached walk's
auto_install_resolved_peers, so a subtree first resolved under one
importer handed the peer providers it bound to every importer sharing
it. Combined with the owner-scope miss suppression, the sharing
importer neither hoisted the peer from the workspace root nor bound it
in its own context — it inherited the owner context's provider, even
one the consumer's declared range rejects. pnpm's resolver gives a
not-new package resolvedPeers: {} (resolveDependencies.ts), so only
the walk that first resolves a subtree promotes its providers; match
that by replaying no providers on a cache hit.
In a bit.cloud workspace with 114 importers this halved peer-variant
fanout: 25,534 -> 20,791 snapshots (TypeScript CLI: 20,219), with
`@testing-library/react@13.4.0`-suffixed variants dropping from 535 to
43 (TypeScript: 41).
YAML caps a simple key at 1024 characters (the ':' must appear within
that lookahead), and the parser enforces it — but the emitter wrote
peer-suffixed snapshot keys of any length inline. A large workspace
whose dep paths exceed the limit got a lockfile the engine could not
re-read, so every subsequent install silently fell back to a full
re-link. Port js-yaml's explicit-pair rule: keys whose rendered form
exceeds 1024 characters are emitted as '? <key>' with the value on the
following ': ' line, which round-trips and matches what the TypeScript
CLI writes.
importer_direct_wanted_specs merged the manifest's dependency groups
with devDependencies overriding dependencies, while the TypeScript
CLI's filterDependenciesByType spreads {...dev, ...prod, ...optional}.
For a name declared in both groups pacquet resolved the dev range but
recorded the importer entry under dependencies with the prod
specifier, producing a lockfile that fails its own up-to-date check
(version doesn't satisfy range) and re-resolves on every install.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Strengthen the duplicate-PR guidance to point at the issue's
automatically linked PRs, add an AI-assisted contributions section
(agents are welcome, but contributors own the output — check linked
PRs, understand the change, run the tests, keep the diff focused,
disclose agent authorship with the standard footer), and document that
human review starts only after CodeRabbit approves and CI is green.
CodeRabbit is the single required AI approval — Qodo ignores
Dependabot/Renovate PRs, so a two-reviewer gate could never be
satisfied for dependency updates; the PR template is aligned
accordingly.
Multiple contributors keep submitting parallel PRs for the same issue
(e.g. pnpm/pnpm#13114 was superseded by pnpm/pnpm#13124 for issue
pnpm/pnpm#13108). GitHub already cross-links every PR that references
an issue on the issue's timeline, so add a checklist item prompting
authors to check those links before submitting.
pnpm update wrote the new version of an `=`-pinned dependency back as the
bare version, dropping the explicit operator (`=3.5.1` became `3.5.2`).
The two spellings are the same semver range, but the `=` form marks the
pin as deliberate, so the update should keep it.
The save-style enum (formerly PinnedVersion) gains an `exact` variant
and is renamed to RangeSpecStyle: it selects the operator a specifier is
saved with, not a pin granularity. inferRangeSpecStyle (formerly
whichVersionIsPinned) classifies a bare `=` before a full version as
`exact` (partial `=1.0` / `=1` keep pinning like the plain version they
prefix), and the specifier formatters emit `=` for it. A granularity
projection (rangeSpecGranularity / RangeSpecStyle::granularity) collapses
`exact` to `patch` for consumers that only care about range width, such
as the save-workspace-protocol: rolling mapping, where an `=` pin maps
to `workspace:*` like other exact pins. `@pnpm/types` keeps PinnedVersion
as a deprecated alias and stays declaration-only; the shared helpers
live in `@pnpm/pkg-manifest.utils`. save-prefix now accepts `=`, saving
new dependencies as `=x.y.z`; --save-exact still wins and saves the
bare version. Implemented in both the TypeScript CLI and the Rust port.
Closespnpm/pnpm#13168
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
When minimumReleaseAge filtered the current latest target, the fallback scanned mature releases across every major. A chronologically older but SemVer-greater release could therefore replace the registry's authoritative latest selection.
Bound latest fallback candidates to versions at or below the original tag target in both resolver implementations. Leave the tag unset when no safe fallback exists, and keep pacquet's generic version-filter behavior unchanged for package-version guards.
Closespnpm/pnpm#13034.
---------
Co-authored-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
The node/event budgets already scale with the document's byte length
(pnpm/pnpm#13485), but a ~100 MB lockfile still failed with "budget
breached: ScalarBytes" at serde-saphyr's 64 MiB scalar-text default.
Scale every size-proportional budget dimension (scalar bytes, comment
bytes, reader input bytes) the same way: none of them can exceed the
size of an input that is already in memory, so a valid lockfile can
never trip them. The remaining defaults (aliases, anchors, depth,
documents) bound YAML shapes the lockfile emitter never produces and
stay as security caps.
Add a regression test that parses a generated lockfile above the
64 MiB scalar budget.
A workspace dependency that deduped to `link:` on install could be rewritten to
a peer-suffixed `file:` by any run that re-resolves it without deduping back to
`link:` — e.g. `pnpm update <other-pkg>` (with or without --recursive), a
root/catalog bump, or a plain install that hits a genuine peer-context
divergence.
When finalizing importer refs, keep the previous `link:` for a workspace
dependency the run doesn't target: not new, specifier unchanged, and not matched
by `pnpm update <name>`. `updateSpec` is intentionally excluded from the
criterion because a plain install marks every manifest dependency with it. A
workspace `link:` dependency has no version to update, so a run that doesn't
target it must not flip it to `file:`; targeted dependencies still resolve
freely.
Adds regression tests for the update-recursive and plain-install paths (both
fail without the fix) and ports the guard to the Rust package-manager crate.
Completes the fix for pnpm/pnpm#10433 — the first mechanism was fixed in #12800.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
The ubuntu Node 24 smoke job previously gated the Node 22/26 test runs,
delaying them by a full test cycle. Fold the Node 24 (garnet) leg into
the test matrix and gate every test job only on compile-and-lint plus
its platform's pnpr build, so all Node.js versions and all Windows
chunks start in parallel. The trade-off is that a broken build now
surfaces on all test jobs at once instead of costing a single smoke run.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Tarball manifests can contain an empty bundledDependencies array while registry
metadata for the same package omits the field. Treat empty arrays as absent so
lockfile generation does not depend on the manifest source used during
resolution.
Preserve nonempty arrays and true for both field spellings. Continue preferring
bundledDependencies when meaningful, but allow an empty value to fall back to
a meaningful bundleDependencies value.
Mirror the change in pacquet: BundledDependencies::from_manifest no longer
records an empty list and falls through to the legacy spelling instead.
Closespnpm/pnpm#13123.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Normalizes git-hosted tarball dep paths back to the canonical
`name@git+https://host/org/repo.git` key that a clone of the same repository produces, so one
hashless entry approves the package whether pnpm clones it or downloads a tarball. GitHub
(codeload), GitLab archive, and Bitbucket download URLs are covered. The host is part of each
derived key, and the GitHub and Bitbucket download hosts are matched against a literal value
(GitLab's is captured generically to allow self-hosted instances), so a look-alike host cannot
be rewritten into an unrelated repository key. Approving or denying a specific resolved commit
by its full tarball dep path continues to work.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
* fix(projects-graph): resolve workspace deps declared with a relative path
A dependency declared as `workspace:../some/path` was silently dropped
from the project graph. `workspacePrefToNpm` turns it into the bare
string `../some/path`, and because the registry argument is empty every
string satisfies the `startsWith(registry)` check, so `parseBareSpecifier`
tries to parse it as a tarball URL and throws. The surrounding
`catch { return '' }` swallowed the error and `filter(Boolean)` removed
the edge, so the dependency disappeared without any warning, affecting
`--filter` selection and recursive install ordering.
Fall back to resolving the un-prefixed spec as a directory dependency
via the existing `npa.resolve` path, the same way plain relative-path
deps are already handled. This matches the behaviour the Rust engine
already implements, which classifies a path-like workspace version as a
directory before attempting bare-specifier parsing.
* refactor(projects-graph): classify path-like workspace specs before parsing
Route a workspace spec with a relative path (workspace:./foo,
workspace:../foo) straight to directory resolution instead of relying on
parseBareSpecifier throwing and being caught. The try/catch remains only
as a backstop for other malformed specs. Behaviour is unchanged; this
addresses review feedback about using a thrown exception as control flow
for the common path-like case.
* chore: add changeset
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The napi install options deserialized `overrides` into a HashMap, so
the JS object's key order was lost and pnpm-lock.yaml#overrides was
rewritten in a random order on every install driven through the addon
(the freshness comparison is order-insensitive, so this churned the
file without invalidating it). Deserialize into an IndexMap via napi's
object_indexmap feature - matching the order the TypeScript engine and
the pacquet CLI record - and collect the getPeerDependencyIssues JSON
path through the same order-preserving shape.
Also wire pacquet_diagnostics::enable_tracing_by_env into the addon's
module init so the TRACE env var works for napi consumers like it does
for the CLI, and log the staleness reason when a preferred-frozen
install falls through to a fresh resolve - both were needed to diagnose
a Bit workspace that re-resolved on every install.
Batch the readPackage hook dispatch (per-manifest threadsafe calls cost
roughly one event-loop tick each, serializing large resolutions), expose
dedupePeers in the install options (its absence made the freshness gate
treat every Bit lockfile as outdated), and try pick_package's read-only
mirror fast paths before the per-name fetch semaphore so version-pinned
picks stop queueing behind concurrent refreshes of the same package.
Resolve workspace importers concurrently - children-ownership claims
are rank-ordered, so the result is arrival-order independent - which
absorbs the JS hook round-trip latency on large workspaces.
Add projects[].dependencyManifest so hosts can express their readPackage
hook logic engine-side with zero JS round trips (per-manifest deletions
use the existing overrides removal syntax; neverBuiltDependencies stays
rejected in favor of allowBuilds), and apply overrides after the
readPackage hook to match the TypeScript engine's createReadPackageHook
order - a hook that replaced a manifest wholesale (raw-manifest
substitution for injected workspace instances) previously erased the
overrides from that manifest.
Stop repeating the release-age abbreviated->full metadata upgrade fetch
per dependency edge: coalesce it on a per-document permit and remember a
304 Not Modified for the rest of the install, and share the resolver's
workspace-packages map behind an Arc instead of deep-copying every
project manifest on each ResolveOptions clone. Full resolution of a
345-importer workspace: 105 s -> 36 s.
Cut the peer walker's per-node map churn: share ParentRefs behind Arc
copy-on-write, build the collision overlay lazily, and reuse the
per-child parent-package snapshots when the context is unchanged
(peer-heavy 331-importer benchmark: 3.95 s -> 2.78 s).
Share the run-resolved preferred-versions fold across importers: each
importer replayed the whole workspace history into a private map every
hoist round; the fold now lives once on the workspace context and the
hoist call sites materialize merged buckets for just the names they
query (full-workspace benchmark: 886 ms -> 424 ms).
The auto-install-peers hoist loop cloned the workspace tree and ran a
full peer pass - DependenciesGraph construction included - once per
importer per round, making a from-scratch resolve of a large component
workspace quadratic in both time and memory (the regression Bit hit
through the napi engine).
Replace the per-round snapshot + full pass with a PeerHoistDiscovery
engine shared by every hoist round of one workspace resolve:
- discovery walks share one persistent tree view, incrementally synced
from the workspace context; a children-ownership handover that
rewrites existing occurrence nodes (tracked by a rewrite counter), a
re-recorded child list, or a changed peer split discards the whole
view instead of merging;
- the walker's purePkgs / peersCache / parent-context maps persist
across rounds and importers, so a subtree settled by one importer's
pass short-circuits every compatible revisit - the same sharing the
final resolve_peers_workspace pass already applies within one call;
- discovery skips graph construction entirely: the hoist loop only
consumes missing peers and resolved providers, and each peers-cache
item carries its subtree's per-package missing breakdown so the
owner-scope report stays exact under cross-round cache hits.
Also drop the remaining per-importer O(workspace) costs in the initial
barrier: record_first_walk_missing no longer deep-clones the
children-owner map, the preferred-versions fold reads an append-only
resolved-versions log through a per-importer cursor, and the
owner-scope maps are snapshotted once per barrier. The CLI deploy
tests' stderr assertions are also made robust to miette's
path-length-dependent line wrapping.
cargo bench -p pacquet-resolving-deps-resolver --bench workspace_full_resolution
(331 importers, 5,000 shared packages, peer hoisting, no lockfile):
before 65.1s / 3.76 GiB peak RSS; after 1.55s / 1.14 GiB.
Pacquet re-emitted deprecated packages when the same package was encountered at a shallower depth. This could change a transitive warning into a direct warning and required Rust-only deduplication in the default reporter. Emit only when inserting a package into the shared resolved-package map, matching the TypeScript resolver's packageIsNew gate.
Thread the recursive command state into the default reporter so direct dependency warnings use pnpm's workspace-relative form and omit the deprecation reason during recursive installs. Remove reporter-side deduplication so event folding matches the TypeScript reporter.
Serialize package-manager environment resolution before reading or replacing its shared lockfile, acquiring blocking filesystem locks on Tokio’s blocking pool. Treat incomplete or broken cache entries as misses both before and after the engine-install lock so installation can recover safely.
Keep package statistics from prematurely flushing the pending frozen-install message, preserving the lockfile-policy verdict order while verification and materialization run concurrently.
NAPI consumers provide authoritative project manifests in memory. The optimistic repeat-install path uses package.json mtimes as its freshness signal, so it could report an install as already up to date when only the supplied manifest changed.
Disable that shortcut for NAPI installs so the normal lockfile freshness checks compare the supplied manifests and materialize newly requested dependencies. Rebuilds retain the existing fast-path behavior.
Pacquet parsed lockfiles with serde-saphyr's default structural budgets, which reject valid large lockfiles after 250,000 YAML nodes.
Set the event and node budgets to the greater of their defaults and the lockfile's byte length. This keeps the structural work allowance proportional to an input that is already in memory while retaining the parser's other resource limits.
Add a regression test that parses a generated lockfile above the previous node limit.
Fixespnpm/pnpm#12857.
pnpm login refused to run whenever stdin or stdout was not a TTY, even
though the registry web-auth flow only prints an authentication URL and
polls the done endpoint until the browser approval completes - neither
needs a terminal. Agent- and CI-adjacent tooling had to wrap pnpm in a
pseudo-terminal (script -q /dev/null pnpm login) to use the web flow.
Move the non-interactive guard from the top of the login command into
the classic username/password fallback, the only path that prompts on
the terminal. Without a TTY the web flow now prints the authentication
URL and polls as before; the URL is printed without the QR code (a
piped stdout cannot render the block art), and the press-ENTER browser
prompt was already skipped for a non-TTY stdin. A registry without web
login support still fails with ERR_PNPM_LOGIN_NON_INTERACTIVE.
Harden the TypeScript web-login path to match pacquet while touching
it: narrow the attacker-controlled response body at runtime (a missing,
empty, or non-string loginUrl/doneUrl is an invalid response), and
reject URLs containing Unicode control characters with pacquet's
ERR_PNPM_AUTH_COMMANDS_LOGIN_UNSAFE_URL before anything is printed or
polled. The shared error message now says "authentication URL" in both
stacks, since the check covers loginUrl and doneUrl alike.
Implemented in both stacks: the TypeScript CLI moves the guard into
classicLogin and prints a URL-only message via the new
formatAuthUrlOnlyMessage export of the web-auth package; pacquet moves
the same guard into classic_login and selects AuthUrlMessage::UrlOnly
when stdout is not a TTY. The pacquet CLI adapter unit test and the
CLI-tier integration tests now drive the guard through a 404 web-login
probe so it exercises the classic fallback, and a new integration test
covers the headless web flow end-to-end against a mock registry.
Also acknowledge a pre-existing zizmor ref-version-mismatch finding on
the winget-releaser pin in update-latest.yml with the repository's
usual inline ignore: the pinned commit is no longer reachable from any
named ref upstream, so no version comment can describe it accurately.
The first pnpm 12 beta release used pnpm 12.0.0-alpha.21 to publish a wrapper whose workspace name is `pacquet`. That version predates `publishConfig.name`, so npm trusted publishing was attempted for `pacquet` instead of `pnpm` and the root package failed after all native packages had already been published.
Use pnpm 11.18.0, the released TypeScript CLI that supports `publishConfig.name`, for release tooling. Update every `pnpm/setup` consumer to the revision that can install v11 from GitHub release archives. Make the Rust publishing loop query each effective published name and skip versions already on npm, while preserving hard failures for registry errors other than 404. This allows a moved beta tag to resume the partial release and reach the dependent GitHub release job.
Direct dependency and catalog range edits currently force the dependency graph
to be resolved even when every locked version remains valid.
Add transactional fast paths that rewrite only compatible importer specifiers
or catalog snapshots, then validate importer state and lockfile resolutions
before committing them. Stale catalog snapshots may be removed when no
importer references them. Ambiguous or resolution-sensitive cases continue
through the normal resolver.
Implement the same conservative rules in the TypeScript CLI and Rust pnpm
implementation. Tests cover compatible and incompatible direct and catalog
ranges, stale and missing snapshots, simultaneous settings changes, atomic
rollback, malformed versions, peer-bearing packages, and operation while the
registry is unavailable.
Related to pnpm/pnpm#13474.
Make the registry and link override status test independent of whether
platform-specific timestamp and path handling reaches lockfile validation.
Keep the mocked workspace state, lockfiles, and manifest mutually consistent
so the test isolates local-file override classification.
Related to pnpm/pnpm#13464.
An overrides mismatch previously forced a complete dependency graph
resolution even when the lockfile could be updated safely in place.
Add conservative fast paths to both pnpm implementations for exact registry
version replacements and dependency-removal overrides. Replacements resolve
only their package metadata, retain satisfying child resolutions, prune dropped
edges, and attach added edges only when one safe compatible snapshot is already
locked. Independent replacements and removals can be processed together, and
parent-scoped removals match only the selected locked parent snapshots.
Fall back to the full resolver for peer-sensitive, ambiguous, patched, aliased,
exotic, policy-violating, or custom-hook cases. Remove obsolete virtual-store child links
while materializing the rewritten lockfile.
Scope importer-hoisted optional peers to each reused direct dependency's
locked peer suffix. This preserves distinct peer contexts for dependencies
that share an importer without preventing optional-peer deduplication.
Track the hoisted providers per importer so a shared leaf NodeId cannot affect
an unrelated workspace project.
Related to pnpm/pnpm#13305.
Finish the strict output-parity work for pacquet's license, dedupe, and
pre-run verification commands.
Use pnpm-compatible package-name collation and keep license versions aligned
with their single representative paths. Teach dedupe progress to account for
the store and reusable skipped optional subdependencies, restore the added
counter, and preserve pnpm's report spacing. Buffer the frozen-lockfile status
until the lockfile-policy verdict and render zero-change stats as already up to
date.
Closespnpm/pnpm#13457.
Keep verifier-triggered installs on the normal prefer-frozen dispatch path. A regenerated lockfile that is fresh for the current configuration can then be materialized without re-resolving and rewriting its peer graph.
Continue bypassing both optimistic and frozen repeat-install exits for this mode so root lifecycle scripts still run before the requested command.
Fixespnpm/pnpm#13466.
Keep catalog snapshots available while dedupe rebuilds importer and package dependency edges. The resolver validates that the recorded catalog specifier still matches before reusing its version, so changed catalog ranges continue to re-resolve while valid pins remain stable.
Closespnpm/pnpm#13465.
The Rust lockfile-only dedupe path bypassed both ordinary resolution progress and pnpm-style error reporting. Attach a lightweight resolution observer to emit resolved-package progress, then report check failures as a structured pnpm event that each reporter can handle appropriately.
This makes default, NDJSON, and silent output match the TypeScript CLI while retaining the existing exit status and leaving the lockfile untouched.
Related to item 2 of pnpm/pnpm#13457.
The Rust lockfile-only dedupe path bypassed both ordinary resolution progress and pnpm-style error reporting. Attach a lightweight resolution observer to emit resolved-package progress, then report check failures as a structured pnpm event that each reporter can handle appropriately.
This makes default, NDJSON, and silent output match the TypeScript CLI while retaining the existing exit status and leaving the lockfile untouched.
Related to item 2 of pnpm/pnpm#13457.
Normalize author strings and derive repository-based homepages when pacquet
builds license reports. Preserve the license-group insertion order produced by
the TypeScript CLI's package-first traversal.
Addresses item 1 of pnpm/pnpm#13457.
Pacquet's recursive runner hardcoded every script execution as silent, so a
filtered run inherited the script output but omitted the preceding `$ <script>`
line. Pass the reporter's silent state through the recursive execution path so
normal filtered runs echo the command while `--silent` continues to suppress it.
The TypeScript CLI already has the intended behavior, so no TypeScript change is
needed.
Related to pnpm/pnpm#13457, item 3.
Pacquet's cross-importer missing-peer suppression treated all preferred
versions alike. During lockfile re-resolution, this could suppress optional
peer providers that were already encoded in peer suffixes in the wanted
lockfile.
Derive the names of locked peer providers from wanted-lockfile snapshot
suffixes and exempt those names from cross-importer suppression. This keeps
importer-local peer contexts stable during dedupe and aligns the affected n8n
jsdom and vitest variants with the TypeScript CLI.
Related to pnpm/pnpm#13305.