Files
pnpm/pnpm11/core/error/test
Zoltan Kochan 8333a69288 fix(add): resolve alias-less local, tarball and URL selectors (#14442)
`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.

Closes pnpm/pnpm#14437
2026-09-02 11:08:08 +02:00
..