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.
Close the four CLI-surface differences left over from pnpm/pnpm#13315
after pnpm/pnpm#13359 and pnpm/pnpm#13375 landed the other two.
Three land in the Rust engine. The `Scope:` line arrives via a new
`pnpm:scope` channel and a port of `reportScope.ts`; pnpm gates it on the
command being one that reports scope *and* the run being workspace-wide,
and since the engine's recursive-by-default set is narrower than pnpm's,
that conjunction is computed at dispatch and seeded into the reporter.
The event is emitted where the counts already exist — the `--filter`
resolution and the installer's workspace walk — so no install pays for a
second directory walk. A `sharedWorkspaceLockfile: false` plan reports
once for the whole selection rather than once per child install, which
would otherwise overwrite the filtered count with the workspace total.
The second is the `allowBuilds` placeholder an install writes for a build
it blocked, which pnpm/pnpm#13375 taught the engine to read but not yet to
write. The third is the elapsed time on a cache-satisfied lockfile
verdict.
The fourth goes the other way. The store block a first install prints is
unreachable in the TypeScript CLI: `reportContext` needs
`pnpm:package-import-method`, which only `@pnpm/fs.indexed-pkg-importer`
emits, and that runs inside a `@pnpm/worker` thread whose logger never
reaches the reporter. The main process already receives the import method
back from the worker, so it is reported from there instead.
Reporter globals are now seeded before the pre-command checks. They are
`OnceLock`s and the pre-command checks emit, so whatever fired first was
locking in unset values.
Closespnpm/pnpm#13315
Remove the legacy repository changelog files now that release changelog storage defaults to the registry. The publish path composes and injects CHANGELOG.md into release tarballs, so keeping historical copies in source control duplicates generated release data.
Update adm-zip to the patched 0.6 release and override vulnerable transitive versions after the dependency audit began rejecting versions below 0.6.0.
The strict minimumReleaseAge approval prompt is shown mid-install while
background resolution keeps emitting progress. The default reporter redrew
each frame in place and erased below it, moving the cursor into the prompt's
lines and wiping the question, so the install hung on input the user could
not see.
Bracket the prompt with pnpm:prompt start/end events and hold every frame
redraw while it is open, recreating the differ on resume so the next frame
renders in full below the answered prompt. The erase-below cleanup for
external prompts (e.g. an SSH passphrase) stays intact for the normal case.
Ref: pnpm/pnpm#13019
Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
The team command communicates with the registry through the standard npm team API endpoints. The scope:team format is parsed to separate the organization scope from the team name. For mutation subcommands (create, destroy, add, rm) the registry URL is resolved per scope from the registries map with an optional --registry override, the auth header is resolved from the configured credentials honoring scoped credentials, and the request is sent with retry support and bounded response reads. When an OTP is in play, the Rust side restricts redirects to the configured registry origins so the npm-otp header cannot leak to another host; the TypeScript fetch layer already strips it on cross-host redirects. The ls subcommand dispatches to listing teams within an org when given @scope and to listing members of a specific team when given @scope:team. Output supports three modes, the default human-readable listing, --parseable which emits newline-delimited names, and --json which emits structured arrays.
On the TypeScript side the command is registered in pnpm/src/cmd/index.ts and removed from the notImplemented list. On the Rust side it is added as a CliCommand variant, routed in dispatch, and dispatched in dispatch_query. Both sides include comprehensive tests covering all subcommands, error paths for 401 403 404 and 409 responses, empty results, and the three output formats.
pnpr serves the npm team API from each hosted registry's config-declared teams: GET /-/org/{scope}/team and GET /-/team/{scope}/{team}/user list teams and members, gated by the registry-level access with denials masked as not-found, while team mutations answer an explicit 403 since pnpr teams are config-managed.
@pnpm/cli.parse-cli-args no longer stops option parsing at an escape word (create, exec, test) that appears as another command's parameter, which previously made pnpm team create drop a trailing --registry option.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Pure directory move plus path fixups: the Rust port ships as pnpm v12,
so the source tree now lives at pnpm/ (alongside pnpm11/, the frozen
TypeScript line). No identifiers change in this pass — crate names
(pacquet-*), the pacquet bin, PACQUET_VERSION, the @pacquet/* npm
package names v11's runPacquet spawns, the .pacquet virtual-store dir,
the benchmark harness's clone dir, and the pacquet-*.yml workflow
filenames (npm trusted publishing is bound to them) all stay for a
follow-up.
Also removes the root /pnpm/ .gitignore entry (build detritus in the
pre-pnpm11 package location): pnpm/ is real source now and must not be
ignored. Developers with a stale generated pnpm/ dir should delete it
before checking out this change.
Register the pn short alias in generated shell completion scripts.
pnpm exposes pn as a binary alias, but completion generation only registered
pnpm with the supported shells. This meant zsh completions generated by
pnpm completion zsh registered pnpm only, so pn did not receive the same
completion function.
Post-process the tabtab-generated scripts to register pn alongside pnpm for
bash, fish, pwsh, and zsh, and cover each shell in the completion generator
tests.
Fixespnpm/pnpm#11955.
---------
Co-authored-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <zoltankochan@gmail.com>
* fix(default-reporter): restore ansi-diff to stop duplicated output lines
PR pnpm/pnpm#12351 replaced ansi-diff with manual full-frame reprint,
which re-writes unchanged sticky blocks on every progress tick. This
caused cached lockfile verdicts and deprecation warnings to appear
dozens of times in terminals that record each write (Warp, script,
CI logs). The manual countRows also produced unbounded cursor-up
values when the frame was taller than the terminal, causing an
infinite repaint loop on pnpm run --parallel.
TypeScript CLI: restore the `ansi-diff` npm package for differential
rendering, wrapping its output with \r (column reset) + \x1b[0J
(erase below frame) to preserve the external-process-output fix
from pnpm/pnpm#12351.
Rust pacquet port: implement a zero-dependency differential renderer
(`diff.rs`) with the same algorithm, replacing the full-frame reprint
in `Sink::write_output`.
Closespnpm/pnpm#12634.
* fix(default-reporter): pass dylint, add trailing newline, harden reporter test
Builds on the restored ansi-diff differential renderer:
- pacquet: guarantee a trailing newline before diffing a frame, matching the
TypeScript reporter. This keeps an interactive prompt on a fresh line below
the frame instead of joined onto the last progress line, and leaves the
differ's tracked cursor column at 0 so it stays in sync with the \r
prepended on the next update (otherwise the inline diff computes relative
moves from a stale column and corrupts in-place rendering).
- pacquet: move the differ's inline test module into src/diff/tests.rs with
explicit imports and drop a stray trailing comma, so cargo dylint passes.
- TypeScript: locate the sticky lockfile verdict's first render with findIndex
instead of assuming writes[0], and wrap each reporter test body in
try/finally so the subscription is always stopped.
* fix(default-reporter): align rust diff with ansi-diff edge cases
- Add col !== width guard before inline diff, matching the JS
ansi-diff edgecase check that prevents mispositioning at exact-width
wrap boundaries.
- Fix clearDown to start from the cursor's current row (self.row)
instead of new_last_row, matching the JS _clearDown pattern. The
previous code could clear frame content when trailing lines were
unchanged and the frame shrank.
- Document the visible_width vs wcwidth simplification.
* test(default-reporter): expand diff coverage to 14 tests
Add 8 new tests covering:
- inline diff skipped for ANSI-containing lines
- inline diff threshold edge case
- full line rewrite with clear
- clear_down starting from cursor row (not new_last_row)
- soft-wrapped line height tracking
- empty frame clears all previous content
- multiple progress ticks (simulated install sequence)
- frame without trailing newline
* fix(default-reporter): tighten test assertions and avoid hot-path allocation
- clear_down_from_cursor_row: replace weak || assertion with direct
checks that Line A is absent, Line B changed is present, and clear
sequence is emitted.
- TS reporterRenderer: capture writes.length before the 'fetched'
event and wait for an increase, so the test can't pass early.
- pacquet Sink::write_output: write \r, diff output, and erase codes
as separate write_all calls instead of format!() which allocated a
new String on every frame.
* fix(default-reporter): make frame redraw a single write and fix diff test
Compose the \r + diff + erase sequence into a reusable per-sink buffer and
emit it with one write_all, keeping each frame update atomic against
interleaved writers without allocating on the hot progress path.
Rework inline_diff_skipped_for_small_change (which actually exercised the
inline-diff happy path already covered by inline_diff_writes_only_changed_chars)
into inline_diff_skipped_for_short_common_affix, covering the
left + right <= 4 full-rewrite branch its name promised.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
* fix: avoid passing infinite width to cli-truncate when streaming lifecycle output
cli-truncate 6.1.0 rejects a non-finite width, which crashed the reporter
when streaming lifecycle script output (streamLifecycleOutput passes Infinity
to mean "do not truncate").
* chore: drop changeset
The broken build this fixes was never released, so no release note is needed.
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.