A hosted git specifier that resolved over HTTPS without the repository
being provably public lost its committish from the specifier written
back to the manifest: `pnpm add owner/repo#develop` recorded
`git+https://github.com/owner/repo.git`.
The URL that branch records doubles as the `git ls-remote` target, which
must carry no committish, so it was built with `noCommittish`. The
committish still reached resolveRef, so the install pinned the right
commit and nothing looked wrong — until the next re-resolve read a
specifier that no longer named a branch and moved the dependency to the
default branch.
Keep the `ls-remote` target committish-free and build the recorded
specifier from the same host template with the committish left in, which
is what every other branch of fromHostedGit already produced through
shortcut().
pacquet reaches its equivalent branch only for a URL carrying its own
credentials, and drops the committish there the same way; it is fixed
alongside.
Fixespnpm/pnpm#13999
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
## Summary
SSH Git dependencies without user information could crash during parsing because the TypeScript resolver expected every authority to contain `user@host`. Bracketed IPv6 addresses exposed a second issue: colons inside the address were mistaken for an SCP-style separator.
This change keeps the TypeScript and Rust implementations aligned:
- the host lookup falls back to the full authority when no user information is present;
- only the part after a bracketed host is inspected for an SCP separator or port;
- the Rust rewrite finds the final separator directly with `rfind(':')`, which expresses the intended operation without allocating a temporary vector;
- regression coverage includes no-user-info URLs, bracketed IPv6 hosts, ports, SCP-style paths, and path extraction.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
A specifier that does not ask for SSH resolves over HTTPS, because the URL
pnpm records is shared by every machine that installs the lockfile
(pnpm/pnpm#13290). On a machine whose git cannot use that transport - a
container without the host's CA certificates, say - resolution then failed
with a raw execa dump: "Command failed with exit code 128: git ls-remote --
'https://github.com/...'" and a JS stack, naming neither the dependency nor
a way forward.
A failed ls-remote is now ERR_PNPM_GIT_RESOLVE_FAILED, naming the dependency
and redacting the credentials a URL git echoes back can carry. When the
resolution went over HTTPS, the error also says why, and points at git's own
url.<base>.insteadOf to substitute the transport on this machine only,
leaving the recorded URL alone. A missing git binary says so instead of
surfacing a bare spawn error.
The TypeScript resolver also stops probing anonymous HTTPS git access for a
repository the visibility probe already proved public: both outcomes of that
probe recorded the same resolution, so its only effect was an extra
ls-remote round-trip per git dependency. pacquet never probed.
Carrying the code and the remediation across pacquet's type-erased resolver
seam takes the mechanism resolver-base documents for exactly that: the
diagnostic is boxed outermost and recovered by downcast, in the tree walker
and in add.
Related to pnpm/pnpm#13743.
#13471 passed GIT_TERMINAL_PROMPT=0 in the options given to
graceful-git, but graceful-git forwards only `cwd` to the process it
spawns, so the override never reached git: a repository that needs
credentials still made git prompt on the terminal, and `pnpm outdated`
with a private GitHub Actions repo (as well as resolving a private git
dependency) still hung.
Spawn git through safe-execa directly with the guard in the child
environment, keeping the single-retry policy of the ref-read call site.
The regression went unnoticed because the test mocked graceful-git and
asserted that the env option was handed to it, not that it reached git;
the mocks now sit on the process-spawn boundary and every invocation is
checked, so dropping the guard fails 25 tests.
The Rust runner sets the variable on the Command itself and was already
correct.
Fixespnpm/pnpm#13522
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Two flaws let a transient probe failure record an SSH URL for a spec
that never asked for SSH: the visibility HEAD probe ran once with
retries disabled and treated any failure as private, and the SSH
ls-remote probe ran before the anonymous HTTPS one. On a machine with
SSH keys, a throttled HEAD request resolved a public repo to
git@host:..., breaking later installs on keyless CI runners with
Permission denied (publickey).
The resolver now retries transient HEAD failures, probes anonymous
HTTPS git access before SSH for every representation except explicit
SSH URLs, falls back to HTTPS when every probe fails, and records the
host-archive tarball only for repos proven public - otherwise the
resolution stays type: git so ambient credentials apply.
TypeScript CLI only: pacquet resolves this bug class structurally via
the v12 identity redesign (pnpm/pnpm#13684), which supersedes the
Rust-side commits this PR previously carried.
Closespnpm/pnpm#13276.
Co-authored-by: Zoltan Kochan <z@kochan.io>
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>
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.
Teach `outdated`, interactive `update`, and opt-in non-interactive updates to discover GitHub Actions dependencies in workflow files and referenced local action definitions.
Model actions as development dependencies so the existing production/development filters, compatible/latest behavior, explicit selectors, interactive selection, recursive workspace handling, and no-save/lockfile-only semantics remain consistent with package and runtime updates. Keep non-interactive updates package-only unless `--include-github-actions` is passed or `update.githubActions` is enabled in `pnpm-workspace.yaml`.
Resolve semantic release tags through Git refs, but never persist a tag as the executable reference. Every changed action is pinned to the resolved commit SHA, with the semantic tag retained in an adjacent comment for readability and future version comparison. Non-semver and Docker references are left untouched.
Implement the behavior in both the TypeScript CLI and pacquet and cover discovery, version selection, exact-SHA pinning, comment preservation, selector handling, and formatting preservation.
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 git-resolver unit tests hit live github.com by default: the mocks for
fetchWithDispatcher and graceful-git existed, but beforeEach restored the
real implementations. When GitHub throttles the shared CI runner IPs, the
HEAD probe in isRepoPublic() fails (it has zero retries and treats any
error as "private"), and resolution silently degrades from the hosted
tarball to a git clone, changing the resolved id and failing the
assertions. This broke the main branch build at
https://github.com/pnpm/pnpm/actions/runs/29026897310/job/86153736091
The mocks are now the default: fetch reports every repository as public
and graceful-git serves ls-remote output from a fixture table captured
from the real repositories, with the same commit hashes the assertions
already expected. The private-repo-over-HTTPS test now calls
mockFetchAsPrivate() explicitly instead of relying on a real 404 for the
nonexistent github.com/foo/bar. The one live-network case in
parsePref.test.ts got the same treatment. The suite drops from ~40s to
under half a second and runs offline.
The dlx e2e test stays a genuine end-to-end test against GitHub, but its
allowBuild list now approves both resolution shapes of the same commit
(codeload tarball and git+https clone), so the resolver's
rate-limit-induced fallback no longer trips the
GIT_DEP_PREPARE_NOT_ALLOWED gate, as seen in
https://github.com/pnpm/pnpm/actions/runs/29029971938/job/86170695840
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.