@pnpm/resolving.git-resolver 1100.1.14 → 1100.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/lib/index.js +0 -1
- package/lib/lsRemote.d.ts +2 -0
- package/lib/lsRemote.js +18 -7
- package/lib/parseBareSpecifier.js +51 -44
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @pnpm/git-resolver
|
|
2
2
|
|
|
3
|
+
## 1100.1.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fixed a CI regression where `github:owner/repo` dependencies (and other shorthand Git specifiers) would fail to install with `Permission denied (publickey)` on CI runners that lack SSH keys. The Git resolver no longer records an SSH URL unless the user explicitly wrote one (e.g. `git+ssh://` or `git@host:...`):
|
|
8
|
+
|
|
9
|
+
- The repository visibility probe (an HTTP HEAD request) now retries transient failures such as `429 Too Many Requests`, so host throttling of CI runners is no longer mistaken for a private repository.
|
|
10
|
+
- For non-SSH specifiers, anonymous HTTPS `git ls-remote` access is now tried before SSH, so a public repository whose visibility probe fails still resolves to a portable HTTPS URL instead of an SSH URL that only works where SSH keys are configured.
|
|
11
|
+
- When every probe fails, the resolver falls back to HTTPS for shorthand and HTTPS-style specifiers, and only guesses SSH when the user explicitly provided an SSH URL.
|
|
12
|
+
- A repository that could not be confirmed public is no longer resolved to the host's anonymous archive URL (e.g. `codeload.github.com`, which would fail to download for a private repository); it stays a regular `git` resolution so installs can use ambient Git credentials such as credential helpers and tokens.
|
|
13
|
+
|
|
14
|
+
Note that a private repository that is reachable both over authenticated HTTPS and over SSH now resolves to its HTTPS URL, where previous versions recorded the SSH URL.
|
|
15
|
+
|
|
16
|
+
Fixes [pnpm/pnpm#13276](https://github.com/pnpm/pnpm/issues/13276).
|
|
17
|
+
|
|
18
|
+
<!-- cspell:ignore publickey -->
|
|
19
|
+
|
|
20
|
+
- Resolving a private git repository no longer blocks on an interactive credential prompt: `git ls-remote` now fails fast with an authentication error when git has no credentials for the repository [#13522](https://github.com/pnpm/pnpm/issues/13522).
|
|
21
|
+
|
|
22
|
+
## 1100.1.15
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- Updated dependencies:
|
|
27
|
+
- @pnpm/error@1100.1.1
|
|
28
|
+
- @pnpm/network.fetch@1100.1.11
|
|
29
|
+
- @pnpm/resolving.resolver-base@1101.1.0
|
|
30
|
+
|
|
3
31
|
## 1100.1.14
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
package/lib/index.js
CHANGED
|
@@ -99,7 +99,6 @@ export async function getRepoRefs(repo, ref) {
|
|
|
99
99
|
// This is needed because annotated tags have their own SHA, and we need the commit SHA they point to
|
|
100
100
|
gitArgs.push(`${ref}^{}`);
|
|
101
101
|
}
|
|
102
|
-
// graceful-git by default retries 10 times, reduce to single retry
|
|
103
102
|
const result = await lsRemote(gitArgs, { retries: 1 });
|
|
104
103
|
const refs = {};
|
|
105
104
|
for (const line of result.stdout.split('\n')) {
|
package/lib/lsRemote.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Runs `git ls-remote` with interactive credential prompts disabled, so it
|
|
3
3
|
* fails fast on private repos instead of blocking on user input. All
|
|
4
4
|
* ls-remote invocations must go through this function to keep that guarantee.
|
|
5
|
+
*
|
|
6
|
+
* Failed runs are retried immediately, matching the Rust runner's policy.
|
|
5
7
|
*/
|
|
6
8
|
export declare function lsRemote(args: string[], opts: {
|
|
7
9
|
retries: number;
|
package/lib/lsRemote.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { safeExeca as execa } from 'execa';
|
|
2
2
|
/**
|
|
3
3
|
* Runs `git ls-remote` with interactive credential prompts disabled, so it
|
|
4
4
|
* fails fast on private repos instead of blocking on user input. All
|
|
5
5
|
* ls-remote invocations must go through this function to keep that guarantee.
|
|
6
|
+
*
|
|
7
|
+
* Failed runs are retried immediately, matching the Rust runner's policy.
|
|
6
8
|
*/
|
|
7
9
|
export async function lsRemote(args, opts) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
let lastErr;
|
|
11
|
+
for (let attempt = 0; attempt <= opts.retries; attempt++) {
|
|
12
|
+
try {
|
|
13
|
+
const { stdout } = await execa('git', ['ls-remote', ...args], {
|
|
14
|
+
// Snapshotted per call so changes to auth/proxy env vars made by a
|
|
15
|
+
// long-lived host process are picked up.
|
|
16
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
17
|
+
});
|
|
18
|
+
return { stdout: stdout };
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
lastErr = err;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
throw lastErr;
|
|
14
25
|
}
|
|
15
26
|
//# sourceMappingURL=lsRemote.js.map
|
|
@@ -49,53 +49,46 @@ function urlToFetchSpec(url) {
|
|
|
49
49
|
}
|
|
50
50
|
async function fromHostedGit(hosted, dispatcherOptions) {
|
|
51
51
|
let fetchSpec = null;
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
const httpsUrl = hosted.https({ noCommittish: true, noGitPlus: true });
|
|
53
|
+
const sshUrl = hosted.ssh({ noCommittish: true });
|
|
54
|
+
// SSH is probed before the HTTPS fallbacks (and used as the last-resort guess)
|
|
55
|
+
// only when the user explicitly wrote an SSH URL. For every other representation
|
|
56
|
+
// (`shortcut`, `https`, ...) an SSH remote was never asked for, and recording one
|
|
57
|
+
// in the lockfile breaks installs in environments without SSH keys, so every
|
|
58
|
+
// HTTPS transport is exhausted first.
|
|
59
|
+
const preferSsh = hosted.default === 'sshurl';
|
|
60
|
+
const repoIsPublic = httpsUrl != null && await isRepoPublic(httpsUrl, dispatcherOptions);
|
|
61
|
+
if (httpsUrl && repoIsPublic && await accessRepository(httpsUrl)) {
|
|
62
|
+
fetchSpec = httpsUrl;
|
|
56
63
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (gitSshUrl && await accessRepository(gitSshUrl)) {
|
|
60
|
-
fetchSpec = gitSshUrl;
|
|
61
|
-
}
|
|
64
|
+
if (!fetchSpec && preferSsh && sshUrl && await accessRepository(sshUrl)) {
|
|
65
|
+
fetchSpec = sshUrl;
|
|
62
66
|
}
|
|
63
|
-
if (!fetchSpec) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// use HTTP HEAD request to test whether this is a private repo, to avoid login prompt.
|
|
82
|
-
// this is very similar to yarn classic's behavior.
|
|
83
|
-
// npm instead tries git ls-remote directly which prompts user for login credentials.
|
|
84
|
-
// HTTP HEAD on https://domain/user/repo, strip out ".git"
|
|
85
|
-
const response = await fetchWithDispatcher(httpsUrl.replace(/\.git$/, ''), { method: 'HEAD', redirect: 'manual', retry: { retries: 0 }, dispatcherOptions });
|
|
86
|
-
if (response.ok) {
|
|
87
|
-
fetchSpec = httpsUrl;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
// ignore
|
|
92
|
-
}
|
|
93
|
-
}
|
|
67
|
+
if (!fetchSpec && httpsUrl) {
|
|
68
|
+
if ((hosted.auth || !repoIsPublic) && await accessRepository(httpsUrl)) {
|
|
69
|
+
// Reachable over HTTPS without being provably public, so resolve as
|
|
70
|
+
// `type: git` against this exact URL: the host's archive endpoint would
|
|
71
|
+
// carry neither the URL's credentials nor ambient ones (helpers, tokens).
|
|
72
|
+
return {
|
|
73
|
+
fetchSpec: httpsUrl,
|
|
74
|
+
hosted: {
|
|
75
|
+
...hosted,
|
|
76
|
+
_fill: hosted._fill,
|
|
77
|
+
tarball: undefined,
|
|
78
|
+
},
|
|
79
|
+
normalizedBareSpecifier: `git+${httpsUrl}`,
|
|
80
|
+
...parseGitParams(hosted.committish),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (repoIsPublic) {
|
|
84
|
+
fetchSpec = httpsUrl;
|
|
94
85
|
}
|
|
95
86
|
}
|
|
87
|
+
if (!fetchSpec && !preferSsh && sshUrl && await accessRepository(sshUrl)) {
|
|
88
|
+
fetchSpec = sshUrl;
|
|
89
|
+
}
|
|
96
90
|
if (!fetchSpec) {
|
|
97
|
-
|
|
98
|
-
fetchSpec = hosted.sshurl({ noCommittish: true });
|
|
91
|
+
fetchSpec = preferSsh ? hosted.sshurl({ noCommittish: true }) : httpsUrl;
|
|
99
92
|
}
|
|
100
93
|
return {
|
|
101
94
|
fetchSpec: fetchSpec,
|
|
@@ -103,7 +96,10 @@ async function fromHostedGit(hosted, dispatcherOptions) {
|
|
|
103
96
|
...hosted,
|
|
104
97
|
tarballtemplate: hosted.type === 'gitlab' ? gitlabTarballTemplate : hosted.tarballtemplate,
|
|
105
98
|
_fill: hosted._fill,
|
|
106
|
-
|
|
99
|
+
// Same rationale as the early return above: without proof that the repo
|
|
100
|
+
// is public, the host's anonymous archive endpoint cannot be assumed to
|
|
101
|
+
// work, so the resolution must stay `type: git`.
|
|
102
|
+
tarball: repoIsPublic ? hosted.tarball : undefined,
|
|
107
103
|
},
|
|
108
104
|
normalizedBareSpecifier: hosted.shortcut(),
|
|
109
105
|
...parseGitParams(hosted.committish),
|
|
@@ -116,9 +112,20 @@ function gitlabTarballTemplate({ domain, user, project, committish }) {
|
|
|
116
112
|
const ref = committish ? encodeURIComponent(committish) : 'HEAD';
|
|
117
113
|
return `https://${domain}/${user}/${project}/-/archive/${ref}/${project}-${ref}.tar.gz`;
|
|
118
114
|
}
|
|
115
|
+
// An HTTP HEAD on the project page (without ".git") instead of `git ls-remote`:
|
|
116
|
+
// probing a private repo with ls-remote would trigger a credential prompt. This is
|
|
117
|
+
// very similar to yarn classic's behavior; npm instead tries git ls-remote directly,
|
|
118
|
+
// which prompts for login credentials. Transient failures (429/5xx/network errors)
|
|
119
|
+
// are retried by the fetch layer so registry throttling of CI runners is not
|
|
120
|
+
// mistaken for a private repository.
|
|
119
121
|
async function isRepoPublic(httpsUrl, dispatcherOptions) {
|
|
120
122
|
try {
|
|
121
|
-
const response = await fetchWithDispatcher(httpsUrl.replace(/\.git$/, ''), {
|
|
123
|
+
const response = await fetchWithDispatcher(httpsUrl.replace(/\.git$/, ''), {
|
|
124
|
+
method: 'HEAD',
|
|
125
|
+
redirect: 'manual',
|
|
126
|
+
retry: { retries: 2, factor: 2, minTimeout: 500, maxTimeout: 2_000 },
|
|
127
|
+
dispatcherOptions,
|
|
128
|
+
});
|
|
122
129
|
return response.ok;
|
|
123
130
|
}
|
|
124
131
|
catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/resolving.git-resolver",
|
|
3
|
-
"version": "1100.1.
|
|
3
|
+
"version": "1100.1.16",
|
|
4
4
|
"description": "Resolver for git-hosted packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -29,19 +29,19 @@
|
|
|
29
29
|
"!*.map"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@pnpm/error": "1100.1.
|
|
33
|
-
"@pnpm/network.fetch": "1100.1.
|
|
34
|
-
"@pnpm/resolving.resolver-base": "1101.
|
|
35
|
-
"
|
|
32
|
+
"@pnpm/error": "1100.1.1",
|
|
33
|
+
"@pnpm/network.fetch": "1100.1.11",
|
|
34
|
+
"@pnpm/resolving.resolver-base": "1101.1.0",
|
|
35
|
+
"execa": "npm:safe-execa@0.3.0",
|
|
36
36
|
"hosted-git-info": "npm:@pnpm/hosted-git-info@1.0.0",
|
|
37
37
|
"semver": "^7.8.5"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@jest/globals": "30.4.1",
|
|
41
|
-
"@pnpm/resolving.git-resolver": "1100.1.
|
|
41
|
+
"@pnpm/resolving.git-resolver": "1100.1.16",
|
|
42
42
|
"@types/hosted-git-info": "^3.0.5",
|
|
43
43
|
"@types/is-windows": "^1.0.2",
|
|
44
|
-
"@types/semver": "7.
|
|
44
|
+
"@types/semver": "7.8.0",
|
|
45
45
|
"is-windows": "^1.0.2"
|
|
46
46
|
},
|
|
47
47
|
"engines": {
|