* chore(release): 11.28.5, pacquet 12.10.0
* docs(release): curate the 11.28.5 and 12.10.0 release notes
Add a lead paragraph to each page and group the patch entries under
subject headings, with the security fixes first.
- Merge the unpublish path-boundary fix into the unpublish entry, the
legacy metadata cache prune into the metadata cache entry, the two
binary size entries, and the two 64 MiB archive limits on the v11 page.
- Drop the manifest-utils saveType entry from the CLI page: the CLI
only passes saveType values derived from its own flags, so no CLI user
can reach the fixed path.
- Rewrite mechanism-only entries (PAX header validation, bundled
dependency symlinks, custom resolutions under name@version keys, proxy
setting validation) in terms of what the user sees.
- Use one link style for pnpm/pnpm issues.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
* chore(release): 11.28.1, pacquet 12.8.0, pnpr 0.1.0-alpha.14
* docs(release): park Rust changelogs and curate release notes
The release bump ran with pnpm 12.7.0, which since pnpm/pnpm#14934
writes the changelog of every private project to a committed
CHANGELOG.md. pacquet, `@pnpm/napi` and `@pnpm/pnpr` are private
in-repo but published, and release.yml and the publish-time changelog
composer both read their parked section. Move the three sections back
to .changeset/changelogs so the GitHub release bodies and the npm
tarball changelogs are not empty. The tooling follow-up is recorded
on pnpm/tasks#38.
Curate the pnpm 11.28.1, pnpm 12.8.0 and pnpr 0.1.0-alpha.14 pages:
add lead paragraphs, group patch entries by subject, merge entries
describing one change, drop internals no CLI user sees, and fix
"instead of" tails, implementation names and link placement. This
addresses both Qodo findings on the catalog entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
StackBlitz WebContainers reject asynchronous fs calls with errors that
inherit from Error but were not created by the native constructor, so
util.types.isNativeError() returns false for them. pnpm used that check
to recognize ENOENT and other codes, so a missing pnpm-lock.yaml was
rethrown and pnpm install failed on any project without a lockfile.
Add isError() to @pnpm/error, which accepts a native error or any
instanceof Error, and use it at every non-test call site. The native
check alone is still needed for Jest, where errors from another realm
fail instanceof. Update the AGENTS.md guidance to match.
Closespnpm/pnpm#15649
* chore(release): 11.28.0, pacquet 12.7.0, pnpr 0.1.0-alpha.13
* docs(release): curate the 11.28.0 and 12.7.0 release pages
Group both pages under subject headings with a lead paragraph that
names the headline changes and the security fixes, and move the
security entries to the top of the patch section.
Merge entries that describe one change: the node shim version files,
ranged selectors without a package.json, SemVer build metadata,
completion injection, custom modulesDir command lookup,
list --only-projects, store status, npm_execpath, catalog and npm-alias
workspace edges, the macOS CA fallback, and the prepare/devPreinstall
skips.
Drop entries that describe no change a published version could show:
the test-only --no-optional entry on both pages, the exported
CreateNewStoreControllerOptions type, the publish availability-probe
fix for a flag first shipping in 12.7.0, and the executable-bit check
the Rust CLI never had wrong.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(release): describe the bin shim part of the modulesDir entry accurately
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A request that hits fetchTimeout failed with an error that did not say so.
The TypeScript CLI reported undici's bare `fetch failed` or `terminated`
(a stalled metadata body even surfaced as BROKEN_METADATA_JSON) and kept
UND_ERR_HEADERS_TIMEOUT / UND_ERR_BODY_TIMEOUT only in `error.cause`. The
Rust CLI rendered metadata failures with reqwest's top-level Display, which
drops the `operation timed out` source.
TypeScript: detect undici's timeout codes in the cause chain and raise
FetchTimeoutError, keeping META_FETCH_FAIL for metadata and using
FETCH_TIMEOUT for tarballs, with a hint about fetchTimeout.
Rust: move the tarball error's reqwest source-chain walk into pnpm-network
and use it for the metadata Network and BodyRead errors, dropping the URL
reqwest repeats since the message already names it.
Closespnpm/pnpm#3646
`pnpm add ./pkg`, `pnpm add file:./pkg`, `pnpm add ./pkg-1.0.0.tgz` and
`pnpm add https://host/pkg.tgz` all failed with
`ERR_PNPM_PACKAGE_MANAGER_ADD_RESOLVE_LATEST`: the Rust CLI's `add` only
special-cased an alias-less *git* selector before falling through to
`split_name_spec`, which read every other bare specifier as a registry
package name.
`add` decides the manifest specifier before the install runs, so an
alias-less selector has to learn the package's name from the package
itself. `resolve_aliasless_specifier` now dispatches the way the install's
resolver chain does — git, then a remote tarball URL, then the local
filesystem — and reads the name out of the checkout, the downloaded
archive, or the directory / local tarball. A classified tarball failure is
forwarded transparently, so `add` reports the same code an install of the
same URL does. Resolving a remote tarball downloads it once more than
before: the fetcher never reads the mem cache, and the warm-store reuse is
keyed off the prior lockfile, which cannot hold a URL being added for the
first time.
The local branch dispatches on `is_local_filesystem_specifier`, a new
predicate deliberately narrower than what the local resolver itself claims:
a bare `<a>/<b>` is a hosted-git shorthand and a `<alias>:<pkg>` a
named-registry reference, and the resolver chain only gets away with
claiming those by running the local resolver last.
Auto-cataloging now holds back every project-relative path in both stacks.
A catalog entry is read by every project that references it, so it cannot
hold a path that resolves against the project declaring it: the catalog
resolver refuses a `link:` / `file:` entry with
`ERR_PNPM_CATALOG_ENTRY_INVALID_SPEC`, and — worse — accepts a
`workspace:../pkg` one, leaving every consumer to resolve the relative path
from its own directory. The TypeScript CLI is affected for a local tarball,
which resolves to a version; the `link:` and `workspace:` directory shapes
escaped only because they resolve to none.
Separately, every `TarballError` that names a URL rendered it verbatim, so a
failed fetch of `https://user:pass@host/pkg.tgz` printed the password into
terminal scrollback and CI logs, and the unclassified-failure path printed the
query token of a signed URL — `redact_and_sanitize` keeps the query and
fragment. Each such message now goes through `redact_url_for_display`, and the
unclassified cause is flattened into redacted text rather than attached as a
source, since miette renders every frame of a source chain.
Closespnpm/pnpm#14437
Port `fetchWarnTimeoutMs` and `fetchMinSpeedKiBps` through pacquet's workspace
YAML, environment, CLI install flags, and N-API configuration surfaces. Keep
the two thresholds alongside the other settings used to construct the shared
HTTP client so every install-family path receives the resolved values.
Measure successful registry metadata requests through body parsing and emit a
warning only when the configured timeout is exceeded. Measure complete tarball
bodies and warn when a download lasting more than one second falls below the
configured average KiB/s threshold. Match pnpm 11's comparison boundaries and
message formats, and route the warnings through the selected reporter. Strip
credentials, query parameters, fragments, and control characters from the URL
text in both implementations before it reaches reporter output.
The TypeScript CLI already implements both settings, so this completes the Rust
side of the parity work.
Related to pnpm/pnpm#12042.
[ERR_PNPM_GIT_FETCH_FAILED] Failed to fetch "@logux/core" from the git repository
"git@github.com:logux/core.git": fatal: Could not read from remote repository.
...
The lockfile records an SSH remote for this dependency, so fetching it needs an SSH key
for github.com.
If its specifier does not ask for SSH (for example "github:owner/repo"), the lockfile
entry was written before pnpm v11.21 and can be re-recorded over HTTPS:
pnpm update @logux/core
"pnpm install --force" and "pnpm install --resolution-only" do not re-resolve git
dependencies, so neither clears it.
The GitHub Actions dependency checking in `outdated` and `update`
hardcoded https://github.com as the git host of every `uses:`
repository. On GitHub Enterprise Server, actions resolve against the
GHES instance instead, so `git ls-remote` failed with "Repository not
found" and the error aborted the entire command (the same failure
mode existed for private or deleted action repositories on
github.com).
- Read refs failures per repository are now non-fatal: the repository
is skipped with a `globalWarn` ("Skipping the GitHub Actions from
...") instead of failing the command. One warning per repository.
- New `update.githubActionsServer` setting: the base URL of the
GitHub server hosting the action repositories, used for both the
git remote and the homepage links. Defaults to the
GITHUB_SERVER_URL environment variable (set by GitHub runners,
including GHES) and then https://github.com. Trailing slashes are
stripped; the empty string counts as unset.
- `update.githubActions: false` (explicit) now opts `pnpm outdated`
and the interactive `pnpm update` out of GitHub Actions checking.
Unset preserves the previous behavior, and the explicit
`--include-github-actions` flag still overrides the config.
Both stacks change together. On the pacquet side, the `outdated`
command dispatch now threads the reporter type so the skip warnings
reach the `globalWarn` channel, matching the TypeScript CLI's log
emissions, and the recursive outdated now includes GitHub Actions,
closing a pre-existing parity gap.
Hardening: the skip warning is credential-redacted and stripped of
control characters in both stacks (new redactAndSanitize export in
the error package); the resolved server URL is restricted to http(s)
(ERR_PNPM_GITHUB_ACTIONS_SERVER_PROTOCOL) so a repo-controlled value
cannot select another git transport such as ext::; and the
TypeScript getRepoRefs passes "--" before the repository URL like
the Rust runner already did. The interactive update no longer
re-enables actions after an explicit opt-out; only the
--include-github-actions flag overrides it.
Closespnpm/pnpm#13220.
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.
Lockfile verification fetches registry metadata to bind each entry's tarball URL
(and to apply the minimumReleaseAge / trust-downgrade policies). On a fetch
failure the abbreviated-metadata fetch collapsed every failure mode — 403, 401,
network error, 5xx — into the same "missing" value as a version genuinely absent
from the metadata, so a transport failure was reported as a tampering-style
ERR_PNPM_TARBALL_URL_MISMATCH.
Rather than mint a dedicated violation code/message for the transport case, the
verifier now propagates the registry's own fetch error and the install aborts
with it:
- runTarballUrlCheck / runAgeCheck / runTrustCheck rethrow the underlying fetch
error rather than folding it into a policy violation. The abbreviated-metadata
age shortcut still swallows the error and falls back to per-version lookups.
- The verification gate captures a thrown error per entry and rethrows the first
after the fan-out settles, so concurrent siblings hitting the same dead
registry do not raise unhandled rejections. A transport failure takes
precedence over collected policy violations: the run never finished, so the
batch is incomplete and the actionable failure is the transport error; a
re-run surfaces any remaining violations once the registry is reachable.
- Credential leak fixed at the source: `@pnpm/error`'s redactUrlCredentials
strips user:pass@ userinfo from a URL in FetchError messages, and the npm
resolver redacts the message, stack, and cause of META_FETCH_FAIL. It is a
single forward scan, not a regex (the input is uncontrolled, so a backtracking
pattern is a ReDoS vector), and it strips up to the last @ in the authority so
a raw @ inside the password cannot leak its tail.
pacquet parity: ResolutionVerification::FetchFailed is added (the runner aborts
with it rather than collecting it as a violation); the gate aborts via
VerifyError::RegistryMetaFetchFailed (ERR_PNPM_META_FETCH_FAIL),
collect_resolution_policy_violations returns a Result, and the pnpr server maps
the abort to a 502. The surfaced fetch error is credential-redacted. The
TARBALL_URL_FETCH_FAILED code, its VerifyError variant and hint, and the
pnpr-client interning are removed. Note: pacquet surfaces its existing fetch
error under ERR_PNPM_META_FETCH_FAIL rather than pnpm's literal ERR_PNPM_FETCH_403
— pacquet's fetch errors do not use the per-status code scheme today, so aligning
that globally is a separate effort. The behavior matches: abort with the
registry's error, never a tampering label, no credential leak.
Fixespnpm/pnpm#12489.
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.