@pnpm/fetching.git-fetcher 1102.0.13 → 1102.0.15
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 +23 -0
- package/lib/index.js +84 -7
- package/package.json +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @pnpm/git-fetcher
|
|
2
2
|
|
|
3
|
+
## 1102.0.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies:
|
|
8
|
+
- @pnpm/error@1100.1.3
|
|
9
|
+
- @pnpm/exec.prepare-package@1100.0.33
|
|
10
|
+
- @pnpm/fetching.fetcher-base@1100.2.8
|
|
11
|
+
- @pnpm/resolving.git-resolver@1100.1.18
|
|
12
|
+
- @pnpm/store.index@1100.2.5
|
|
13
|
+
|
|
14
|
+
## 1102.0.14
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- A git dependency whose clone (or shallow fetch) fails now reports which package it belongs to, under the `ERR_PNPM_GIT_FETCH_FAILED` code, with credentials in the repository URL redacted. When the lockfile records an SSH remote, the error also explains that fetching it needs an SSH key for that host, and that a lockfile entry written before pnpm v11.21 can be re-recorded over HTTPS with `pnpm update <package>` [#13743](https://github.com/pnpm/pnpm/issues/13743).
|
|
19
|
+
|
|
20
|
+
- Updated dependencies:
|
|
21
|
+
- @pnpm/error@1100.1.2
|
|
22
|
+
- @pnpm/exec.prepare-package@1100.0.32
|
|
23
|
+
- @pnpm/resolving.git-resolver@1100.1.17
|
|
24
|
+
- @pnpm/store.index@1100.2.4
|
|
25
|
+
|
|
3
26
|
## 1102.0.13
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import assert from 'node:assert';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { URL } from 'node:url';
|
|
4
4
|
import util from 'node:util';
|
|
5
|
-
import { PnpmError } from '@pnpm/error';
|
|
5
|
+
import { PnpmError, redactAndSanitize, redactAndSanitizeMultiline } from '@pnpm/error';
|
|
6
6
|
import { preparePackage } from '@pnpm/exec.prepare-package';
|
|
7
7
|
import { packlist } from '@pnpm/fs.packlist';
|
|
8
8
|
import { globalWarn } from '@pnpm/logger';
|
|
@@ -18,13 +18,19 @@ export function createGitFetcher(createOpts) {
|
|
|
18
18
|
throw new PnpmError('INVALID_GIT_COMMIT', `Invalid git commit hash "${resolution.commit}" for repository "${resolution.repo}". Expected a 40-character hexadecimal SHA.`);
|
|
19
19
|
}
|
|
20
20
|
const tempLocation = await cafs.tempDir();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
try {
|
|
22
|
+
if (allowedHosts.size > 0 && shouldUseShallow(resolution.repo, allowedHosts)) {
|
|
23
|
+
await execGit(['init'], { cwd: tempLocation });
|
|
24
|
+
await execGit(['remote', 'add', 'origin', resolution.repo], { cwd: tempLocation });
|
|
25
|
+
await execGit(['fetch', '--depth', '1', 'origin', resolution.commit], { cwd: tempLocation });
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
await execGit(['clone', resolution.repo, tempLocation]);
|
|
29
|
+
}
|
|
25
30
|
}
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
catch (err) {
|
|
32
|
+
assert(util.types.isNativeError(err));
|
|
33
|
+
throw gitFetchError(err, resolution.repo, opts.pkg?.name);
|
|
28
34
|
}
|
|
29
35
|
await execGit(['checkout', resolution.commit], { cwd: tempLocation });
|
|
30
36
|
const receivedCommit = await execGit(['rev-parse', 'HEAD'], { cwd: tempLocation });
|
|
@@ -73,6 +79,77 @@ export function createGitFetcher(createOpts) {
|
|
|
73
79
|
function isValidCommitHash(commit) {
|
|
74
80
|
return /^[0-9a-f]{40}$/i.test(commit);
|
|
75
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Restate a failure of the transport-touching git invocations, naming the
|
|
84
|
+
* package the resolution belongs to.
|
|
85
|
+
*
|
|
86
|
+
* Every interpolated value is untrusted: a lockfile URL can carry `user:pass@`
|
|
87
|
+
* credentials, and git echoes it back through stderr. Only the values go
|
|
88
|
+
* through {@link redactAndSanitize} — it strips control characters, which would
|
|
89
|
+
* collapse the deliberately multi-line hint.
|
|
90
|
+
*/
|
|
91
|
+
function gitFetchError(err, repo, pkgName) {
|
|
92
|
+
if (err.code === 'ENOENT') {
|
|
93
|
+
return new PnpmError('GIT_FETCHER_GIT_NOT_FOUND', '`git` executable not found on PATH. Install git to fetch git-hosted packages.');
|
|
94
|
+
}
|
|
95
|
+
const safePkgName = pkgName == null ? undefined : redactAndSanitize(pkgName);
|
|
96
|
+
const subject = safePkgName == null ? '' : `"${safePkgName}" `;
|
|
97
|
+
return new PnpmError('GIT_FETCH_FAILED', `Failed to fetch ${subject}from the git repository "${redactAndSanitize(repo)}": ${redactAndSanitizeMultiline(gitFailureDetail(err))}`, { hint: sshRemediationHint(repo, safePkgName) });
|
|
98
|
+
}
|
|
99
|
+
// git appends the child's stderr to its own message, which repeats the repository
|
|
100
|
+
// and leaks the store's temp directory. The stderr alone is what the user needs.
|
|
101
|
+
function gitFailureDetail(err) {
|
|
102
|
+
const stderr = err.stderr?.trim();
|
|
103
|
+
return stderr == null || stderr === '' ? err.message : stderr;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Guidance for a git dependency locked to an SSH remote, or `undefined` when the
|
|
107
|
+
* lockfile records a transport that needs no key.
|
|
108
|
+
*
|
|
109
|
+
* A lockfile written before pnpm v11.21 could record an SSH URL for a dependency
|
|
110
|
+
* whose specifier never asked for SSH, and resolution is skipped while that
|
|
111
|
+
* lockfile stays up to date — so the entry survives the upgrade that fixed it and
|
|
112
|
+
* the install keeps failing wherever no SSH key is configured.
|
|
113
|
+
*/
|
|
114
|
+
function sshRemediationHint(repo, pkgName) {
|
|
115
|
+
const host = sshRepoHost(repo);
|
|
116
|
+
if (host == null)
|
|
117
|
+
return undefined;
|
|
118
|
+
return `The lockfile records an SSH remote for this dependency, so fetching it needs an SSH key for ${redactAndSanitize(host)}.
|
|
119
|
+
|
|
120
|
+
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:
|
|
121
|
+
|
|
122
|
+
pnpm update ${pkgName ?? '<package>'}
|
|
123
|
+
|
|
124
|
+
"pnpm install --force" and "pnpm install --resolution-only" do not re-resolve git dependencies, so neither clears it.`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The host an SSH git reference points at, or `undefined` if `repo` is not one.
|
|
128
|
+
*
|
|
129
|
+
* Covers the URL form (`[git+]ssh://[user@]host[:port]/path`) and the scp-style
|
|
130
|
+
* shorthand (`[user@]host:path`) that carries no scheme. The `user@` is mandatory
|
|
131
|
+
* in the shorthand, which is what keeps a Windows drive path (`C:\repo`) from
|
|
132
|
+
* being read as a host.
|
|
133
|
+
*/
|
|
134
|
+
function sshRepoHost(repo) {
|
|
135
|
+
const sshUrl = repo.replace(/^git\+/, '');
|
|
136
|
+
if (sshUrl.startsWith('ssh://')) {
|
|
137
|
+
try {
|
|
138
|
+
return new URL(sshUrl).hostname || undefined;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (repo.includes('://'))
|
|
145
|
+
return undefined;
|
|
146
|
+
const colonPos = repo.indexOf(':');
|
|
147
|
+
if (colonPos === -1)
|
|
148
|
+
return undefined;
|
|
149
|
+
const authority = repo.slice(0, colonPos);
|
|
150
|
+
const atPos = authority.lastIndexOf('@');
|
|
151
|
+
return atPos === -1 ? undefined : authority.slice(atPos + 1) || undefined;
|
|
152
|
+
}
|
|
76
153
|
function shouldUseShallow(repoUrl, allowedHosts) {
|
|
77
154
|
try {
|
|
78
155
|
const { host } = new URL(repoUrl);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/fetching.git-fetcher",
|
|
3
|
-
"version": "1102.0.
|
|
3
|
+
"version": "1102.0.15",
|
|
4
4
|
"description": "A fetcher for git-hosted packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -28,27 +28,27 @@
|
|
|
28
28
|
"!*.map"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@pnpm/error": "1100.1.
|
|
32
|
-
"@pnpm/exec.prepare-package": "1100.0.
|
|
33
|
-
"@pnpm/fetching.fetcher-base": "1100.2.
|
|
31
|
+
"@pnpm/error": "1100.1.3",
|
|
32
|
+
"@pnpm/exec.prepare-package": "1100.0.33",
|
|
33
|
+
"@pnpm/fetching.fetcher-base": "1100.2.8",
|
|
34
34
|
"@pnpm/fs.packlist": "1100.0.4",
|
|
35
|
-
"@pnpm/resolving.git-resolver": "1100.1.
|
|
36
|
-
"@pnpm/store.index": "1100.2.
|
|
35
|
+
"@pnpm/resolving.git-resolver": "1100.1.18",
|
|
36
|
+
"@pnpm/store.index": "1100.2.5",
|
|
37
37
|
"@zkochan/rimraf": "^4.0.0",
|
|
38
38
|
"execa": "npm:safe-execa@0.3.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@pnpm/logger": "^1100.0.0",
|
|
42
|
-
"@pnpm/worker": "^1100.
|
|
42
|
+
"@pnpm/worker": "^1100.3.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@jest/globals": "30.4.1",
|
|
46
|
-
"@pnpm/fetching.git-fetcher": "1102.0.
|
|
46
|
+
"@pnpm/fetching.git-fetcher": "1102.0.15",
|
|
47
47
|
"@pnpm/logger": "1100.0.0",
|
|
48
|
-
"@pnpm/store.cafs": "1100.
|
|
49
|
-
"@pnpm/store.create-cafs-store": "1100.0.
|
|
48
|
+
"@pnpm/store.cafs": "1100.2.0",
|
|
49
|
+
"@pnpm/store.create-cafs-store": "1100.0.26",
|
|
50
50
|
"@pnpm/text.ordinal-comparator": "1100.0.0",
|
|
51
|
-
"@pnpm/types": "
|
|
51
|
+
"@pnpm/types": "1102.0.0",
|
|
52
52
|
"tempy": "3.0.0"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|