@pnpm/fetching.tarball-fetcher 1102.1.5 → 1102.1.7
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 +19 -0
- package/lib/gitHostedTarballFetcher.js +72 -37
- package/lib/remoteTarballFetcher.js +200 -152
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @pnpm/tarball-fetcher
|
|
2
2
|
|
|
3
|
+
## 1102.1.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies:
|
|
8
|
+
- @pnpm/core-loggers@1101.0.2
|
|
9
|
+
- @pnpm/exec.prepare-package@1100.0.41
|
|
10
|
+
- @pnpm/fs.packlist@1100.0.7
|
|
11
|
+
- @pnpm/network.fetch@1100.1.21
|
|
12
|
+
|
|
13
|
+
## 1102.1.6
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies:
|
|
18
|
+
- @pnpm/exec.prepare-package@1100.0.40
|
|
19
|
+
- @pnpm/network.fetch@1100.1.20
|
|
20
|
+
- @pnpm/store.index@1101.0.0
|
|
21
|
+
|
|
3
22
|
## 1102.1.5
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
|
@@ -45,6 +45,58 @@ export function createGitHostedTarballFetcher(fetchRemoteTarball, fetcherOpts) {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
async function prepareGitHostedPkg(filesMap, cafs, rawFilesIndexFile, filesIndexFile, opts, fetcherOpts, resolution) {
|
|
48
|
+
const { shouldBeBuilt, pkgDir, ignoredBuild } = await unpackAndPreparePackage(cafs, filesMap, opts, fetcherOpts, resolution);
|
|
49
|
+
filesIndexFile = resolveGitHostedIndexFile({
|
|
50
|
+
filesIndexFile,
|
|
51
|
+
ignoredBuild,
|
|
52
|
+
ignoreScripts: opts.ignoreScripts,
|
|
53
|
+
pkgResolutionId: fetcherOpts.pkgResolutionId ?? createGitHostedTarballPkgResolutionId(resolution),
|
|
54
|
+
shouldBeBuilt,
|
|
55
|
+
});
|
|
56
|
+
const files = await packlist(pkgDir);
|
|
57
|
+
const isUnchanged = !resolution.path && files.length === filesMap.size && (!shouldBeBuilt || ignoredBuild);
|
|
58
|
+
if (isUnchanged) {
|
|
59
|
+
return handleUnchangedGitPkg({
|
|
60
|
+
filesIndexFile,
|
|
61
|
+
filesMap,
|
|
62
|
+
ignoredBuild,
|
|
63
|
+
ignoreScripts: opts.ignoreScripts,
|
|
64
|
+
rawFilesIndexFile,
|
|
65
|
+
shouldBeBuilt,
|
|
66
|
+
storeIndex: opts.storeIndex,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return reindexAndAddGitPkgFiles({
|
|
70
|
+
cafs,
|
|
71
|
+
fetcherOpts,
|
|
72
|
+
files,
|
|
73
|
+
filesIndexFile,
|
|
74
|
+
ignoredBuild,
|
|
75
|
+
opts,
|
|
76
|
+
pkgDir,
|
|
77
|
+
rawFilesIndexFile,
|
|
78
|
+
shouldBeBuilt,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function reindexAndAddGitPkgFiles(opts) {
|
|
82
|
+
opts.opts.storeIndex.delete(opts.rawFilesIndexFile);
|
|
83
|
+
return {
|
|
84
|
+
filesIndexFile: opts.filesIndexFile,
|
|
85
|
+
...await addFilesFromDir({
|
|
86
|
+
storeDir: opts.cafs.storeDir,
|
|
87
|
+
storeIndex: opts.opts.storeIndex,
|
|
88
|
+
dir: opts.pkgDir,
|
|
89
|
+
files: opts.files,
|
|
90
|
+
filesIndexFile: opts.filesIndexFile,
|
|
91
|
+
pkg: opts.fetcherOpts.pkg,
|
|
92
|
+
readManifest: opts.fetcherOpts.readManifest,
|
|
93
|
+
requiresPrepare: opts.shouldBeBuilt,
|
|
94
|
+
}),
|
|
95
|
+
ignoredBuild: opts.ignoredBuild,
|
|
96
|
+
requiresPrepare: opts.shouldBeBuilt,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function unpackAndPreparePackage(cafs, filesMap, opts, fetcherOpts, resolution) {
|
|
48
100
|
const tempLocation = await cafs.tempDir();
|
|
49
101
|
cafs.importPackage(tempLocation, {
|
|
50
102
|
filesResponse: {
|
|
@@ -59,47 +111,30 @@ async function prepareGitHostedPkg(filesMap, cafs, rawFilesIndexFile, filesIndex
|
|
|
59
111
|
allowBuild: fetcherOpts.allowBuild,
|
|
60
112
|
pkgResolutionId: fetcherOpts.pkgResolutionId ?? createGitHostedTarballPkgResolutionId(resolution),
|
|
61
113
|
}, tempLocation, resolution.path ?? '');
|
|
62
|
-
|
|
63
|
-
|
|
114
|
+
return { shouldBeBuilt, pkgDir, ignoredBuild };
|
|
115
|
+
}
|
|
116
|
+
function resolveGitHostedIndexFile(opts) {
|
|
117
|
+
const shouldReindex = opts.shouldBeBuilt &&
|
|
118
|
+
((opts.ignoredBuild && !opts.ignoreScripts) || (!opts.ignoredBuild && opts.filesIndexFile.endsWith('\tnot-built')));
|
|
119
|
+
if (shouldReindex) {
|
|
120
|
+
return gitHostedStoreIndexKey(opts.pkgResolutionId, { built: !opts.ignoredBuild });
|
|
64
121
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
storeIndex.set(filesIndexFile, data);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
storeIndex.delete(rawFilesIndexFile);
|
|
77
|
-
return {
|
|
78
|
-
filesIndexFile,
|
|
79
|
-
filesMap,
|
|
80
|
-
ignoredBuild,
|
|
81
|
-
requiresPrepare: shouldBeBuilt,
|
|
82
|
-
};
|
|
122
|
+
return opts.filesIndexFile;
|
|
123
|
+
}
|
|
124
|
+
function handleUnchangedGitPkg(opts) {
|
|
125
|
+
if (!opts.ignoredBuild || !opts.ignoreScripts) {
|
|
126
|
+
const data = opts.storeIndex.get(opts.rawFilesIndexFile);
|
|
127
|
+
if (data) {
|
|
128
|
+
data.requiresPrepare = opts.shouldBeBuilt;
|
|
129
|
+
opts.storeIndex.set(opts.filesIndexFile, data);
|
|
83
130
|
}
|
|
84
131
|
}
|
|
85
|
-
storeIndex.delete(rawFilesIndexFile);
|
|
86
|
-
// Important! We cannot remove the temp location at this stage.
|
|
87
|
-
// Even though we have the index of the package,
|
|
88
|
-
// the linking of files to the store is in progress.
|
|
132
|
+
opts.storeIndex.delete(opts.rawFilesIndexFile);
|
|
89
133
|
return {
|
|
90
|
-
filesIndexFile,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
dir: pkgDir,
|
|
95
|
-
files,
|
|
96
|
-
filesIndexFile,
|
|
97
|
-
pkg: fetcherOpts.pkg,
|
|
98
|
-
readManifest: fetcherOpts.readManifest,
|
|
99
|
-
requiresPrepare: shouldBeBuilt,
|
|
100
|
-
}),
|
|
101
|
-
ignoredBuild,
|
|
102
|
-
requiresPrepare: shouldBeBuilt,
|
|
134
|
+
filesIndexFile: opts.filesIndexFile,
|
|
135
|
+
filesMap: opts.filesMap,
|
|
136
|
+
ignoredBuild: opts.ignoredBuild,
|
|
137
|
+
requiresPrepare: opts.shouldBeBuilt,
|
|
103
138
|
};
|
|
104
139
|
}
|
|
105
140
|
function createGitHostedTarballPkgResolutionId(resolution) {
|
|
@@ -9,7 +9,7 @@ import throttle from 'lodash.throttle';
|
|
|
9
9
|
import { BadTarballError } from './errorTypes/index.js';
|
|
10
10
|
const BIG_TARBALL_SIZE = 1024 * 1024 * 5; // 5 MB
|
|
11
11
|
export function createDownloader(fetchFromRegistry, gotOpts) {
|
|
12
|
-
const
|
|
12
|
+
const defaultRetryOpts = {
|
|
13
13
|
factor: 10,
|
|
14
14
|
maxTimeout: 6e4, // 1 minute
|
|
15
15
|
minTimeout: 1e4, // 10 seconds
|
|
@@ -19,164 +19,212 @@ export function createDownloader(fetchFromRegistry, gotOpts) {
|
|
|
19
19
|
const fetchMinSpeedKiBps = gotOpts.fetchMinSpeedKiBps ?? 50; // 50 KiB/s
|
|
20
20
|
return async function download(url, opts) {
|
|
21
21
|
const authHeaderValue = getSecureNodeMirrorAuthHeader(opts.getAuthHeaderByURI(url, { pkgName: opts.pkg?.name }), url, opts.appendManifest?.name);
|
|
22
|
-
const downloadRetryOpts = { ...
|
|
22
|
+
const downloadRetryOpts = { ...defaultRetryOpts, ...opts.retry };
|
|
23
23
|
const op = retry.operation(downloadRetryOpts);
|
|
24
24
|
return new Promise((resolve, reject) => {
|
|
25
|
-
op.attempt(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
catch (error) { // eslint-disable-line
|
|
30
|
-
if ((opts.redirect === 'manual' && error.response?.status >= 300 && error.response.status < 400) ||
|
|
31
|
-
error.response?.status === 401 ||
|
|
32
|
-
error.response?.status === 403 ||
|
|
33
|
-
error.response?.status === 404 ||
|
|
34
|
-
error.code === 'ERR_PNPM_PREPARE_PKG_FAILURE' ||
|
|
35
|
-
isNonRetryableError(error)) {
|
|
36
|
-
reject(error);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const timeout = op.retry(error);
|
|
40
|
-
if (timeout === false) {
|
|
41
|
-
reject(op.mainError());
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
// Extract error properties into a plain object because Error properties
|
|
45
|
-
// are non-enumerable and don't serialize well through the logging system
|
|
46
|
-
const errorInfo = {
|
|
47
|
-
name: error.name,
|
|
48
|
-
message: error.message,
|
|
49
|
-
code: error.code,
|
|
50
|
-
errno: error.errno,
|
|
51
|
-
// For HTTP errors from our ResponseError class
|
|
52
|
-
status: error.status,
|
|
53
|
-
statusCode: error.statusCode,
|
|
54
|
-
// undici wraps the actual network error in a cause property
|
|
55
|
-
cause: error.cause ? {
|
|
56
|
-
code: error.cause.code,
|
|
57
|
-
errno: error.cause.errno,
|
|
58
|
-
} : undefined,
|
|
59
|
-
};
|
|
60
|
-
requestRetryLogger.debug({
|
|
61
|
-
attempt,
|
|
62
|
-
error: errorInfo,
|
|
63
|
-
maxRetries: downloadRetryOpts.retries,
|
|
64
|
-
method: 'GET',
|
|
65
|
-
timeout,
|
|
66
|
-
url: redactUrlForDisplay(url),
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
async function fetch(currentAttempt) {
|
|
72
|
-
let data;
|
|
73
|
-
try {
|
|
74
|
-
const res = await fetchFromRegistry(url, {
|
|
25
|
+
op.attempt((attempt) => {
|
|
26
|
+
void executeDownloadAttempt({
|
|
27
|
+
attempt,
|
|
75
28
|
authHeaderValue,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
retry: { retries: 0 },
|
|
86
|
-
redirect: opts.redirect,
|
|
87
|
-
timeout: gotOpts.timeout,
|
|
88
|
-
});
|
|
89
|
-
if (res.status !== 200) {
|
|
90
|
-
throw new FetchError({ url, authHeaderValue }, res);
|
|
91
|
-
}
|
|
92
|
-
// When Content-Encoding is present, Content-Length refers to the encoded form
|
|
93
|
-
// of the data, not the decoded bytes that the fetch implementation yields.
|
|
94
|
-
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Encoding
|
|
95
|
-
const isEncoded = isContentEncoded(res.headers.get('content-encoding'));
|
|
96
|
-
const contentLength = !isEncoded && res.headers.has('content-length') && res.headers.get('content-length');
|
|
97
|
-
const parsedLength = typeof contentLength === 'string' ? parseInt(contentLength, 10) : NaN;
|
|
98
|
-
const size = Number.isFinite(parsedLength) && parsedLength >= 0 ? parsedLength : null;
|
|
99
|
-
if (opts.onStart != null) {
|
|
100
|
-
opts.onStart(size, currentAttempt);
|
|
101
|
-
}
|
|
102
|
-
// In order to reduce the amount of logs, we only report the download progress of big tarballs
|
|
103
|
-
const onProgress = (size != null && size >= BIG_TARBALL_SIZE && opts.onProgress)
|
|
104
|
-
? throttle(opts.onProgress, 500)
|
|
105
|
-
: undefined;
|
|
106
|
-
const startTime = Date.now();
|
|
107
|
-
let downloaded = 0;
|
|
108
|
-
if (size !== null) {
|
|
109
|
-
// Known size: pre-allocate and copy directly (avoids intermediate array + second copy pass)
|
|
110
|
-
data = Buffer.from(new SharedArrayBuffer(size));
|
|
111
|
-
for await (const chunk of res.body) {
|
|
112
|
-
const c = chunk;
|
|
113
|
-
const nextDownloaded = downloaded + c.byteLength;
|
|
114
|
-
if (nextDownloaded > size) {
|
|
115
|
-
throw new BadTarballError({
|
|
116
|
-
expectedSize: size,
|
|
117
|
-
receivedSize: nextDownloaded,
|
|
118
|
-
tarballUrl: url,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
data.set(c, downloaded);
|
|
122
|
-
downloaded = nextDownloaded;
|
|
123
|
-
onProgress?.(downloaded);
|
|
124
|
-
}
|
|
125
|
-
if (size !== downloaded) {
|
|
126
|
-
throw new BadTarballError({
|
|
127
|
-
expectedSize: size,
|
|
128
|
-
receivedSize: downloaded,
|
|
129
|
-
tarballUrl: url,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
const chunks = [];
|
|
135
|
-
for await (const chunk of res.body) {
|
|
136
|
-
const c = chunk;
|
|
137
|
-
chunks.push(c);
|
|
138
|
-
downloaded += c.byteLength;
|
|
139
|
-
onProgress?.(downloaded);
|
|
140
|
-
}
|
|
141
|
-
data = Buffer.from(new SharedArrayBuffer(downloaded));
|
|
142
|
-
let offset = 0;
|
|
143
|
-
for (const chunk of chunks) {
|
|
144
|
-
data.set(chunk, offset);
|
|
145
|
-
offset += chunk.byteLength;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
const elapsedSec = (Date.now() - startTime) / 1000;
|
|
149
|
-
const avgKiBps = Math.floor((downloaded / elapsedSec) / 1024);
|
|
150
|
-
if (downloaded > 0 && elapsedSec > 1 && avgKiBps < fetchMinSpeedKiBps) {
|
|
151
|
-
const sizeKb = Math.floor(downloaded / 1024);
|
|
152
|
-
globalWarn(`Tarball download average speed ${avgKiBps} KiB/s (size ${sizeKb} KiB) is below ${fetchMinSpeedKiBps} KiB/s: ${redactUrlForDisplay(url)} (GET)`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
catch (err) {
|
|
156
|
-
const error = isFetchTimeoutError(err)
|
|
157
|
-
? new FetchTimeoutError('FETCH_TIMEOUT', url, gotOpts.timeout, { cause: err })
|
|
158
|
-
: isError(err) ? err : new Error(String(err), { cause: err });
|
|
159
|
-
Object.assign(error, {
|
|
160
|
-
attempts: currentAttempt,
|
|
161
|
-
resource: url,
|
|
29
|
+
downloadRetryOpts,
|
|
30
|
+
fetchFromRegistry,
|
|
31
|
+
fetchMinSpeedKiBps,
|
|
32
|
+
gotOpts,
|
|
33
|
+
op,
|
|
34
|
+
opts,
|
|
35
|
+
reject,
|
|
36
|
+
resolve,
|
|
37
|
+
url,
|
|
162
38
|
});
|
|
163
|
-
throw error;
|
|
164
|
-
}
|
|
165
|
-
return addFilesFromTarball({
|
|
166
|
-
buffer: data,
|
|
167
|
-
storeDir: opts.cafs.storeDir,
|
|
168
|
-
storeIndex: opts.storeIndex,
|
|
169
|
-
readManifest: opts.readManifest,
|
|
170
|
-
integrity: opts.integrity,
|
|
171
|
-
filesIndexFile: opts.filesIndexFile,
|
|
172
|
-
pkgId: opts.pkgId,
|
|
173
|
-
url,
|
|
174
|
-
pkg: opts.pkg,
|
|
175
|
-
appendManifest: opts.appendManifest,
|
|
176
|
-
ignoreFilePattern: opts.ignoreFilePattern,
|
|
177
39
|
});
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function executeDownloadAttempt(ctx) {
|
|
44
|
+
try {
|
|
45
|
+
const result = await fetchTarball({
|
|
46
|
+
attempt: ctx.attempt,
|
|
47
|
+
authHeaderValue: ctx.authHeaderValue,
|
|
48
|
+
fetchFromRegistry: ctx.fetchFromRegistry,
|
|
49
|
+
fetchMinSpeedKiBps: ctx.fetchMinSpeedKiBps,
|
|
50
|
+
gotOpts: ctx.gotOpts,
|
|
51
|
+
opts: ctx.opts,
|
|
52
|
+
url: ctx.url,
|
|
53
|
+
});
|
|
54
|
+
ctx.resolve(result);
|
|
55
|
+
}
|
|
56
|
+
catch (error) { // eslint-disable-line
|
|
57
|
+
handleDownloadRetry({
|
|
58
|
+
attempt: ctx.attempt,
|
|
59
|
+
downloadRetryOpts: ctx.downloadRetryOpts,
|
|
60
|
+
error,
|
|
61
|
+
op: ctx.op,
|
|
62
|
+
opts: ctx.opts,
|
|
63
|
+
reject: ctx.reject,
|
|
64
|
+
url: ctx.url,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function fetchTarball(ctx) {
|
|
69
|
+
let data;
|
|
70
|
+
try {
|
|
71
|
+
const res = await ctx.fetchFromRegistry(ctx.url, {
|
|
72
|
+
authHeaderValue: ctx.authHeaderValue,
|
|
73
|
+
headers: { 'accept-encoding': 'identity' },
|
|
74
|
+
retry: { retries: 0 },
|
|
75
|
+
redirect: ctx.opts.redirect,
|
|
76
|
+
timeout: ctx.gotOpts.timeout,
|
|
77
|
+
});
|
|
78
|
+
if (res.status !== 200) {
|
|
79
|
+
throw new FetchError({ url: ctx.url, authHeaderValue: ctx.authHeaderValue }, res);
|
|
178
80
|
}
|
|
81
|
+
const size = parseTarballContentLength(res);
|
|
82
|
+
ctx.opts.onStart?.(size, ctx.attempt);
|
|
83
|
+
const onProgress = (size != null && size >= BIG_TARBALL_SIZE && ctx.opts.onProgress)
|
|
84
|
+
? throttle(ctx.opts.onProgress, 500)
|
|
85
|
+
: undefined;
|
|
86
|
+
data = await readResponseBody({
|
|
87
|
+
body: res.body,
|
|
88
|
+
fetchMinSpeedKiBps: ctx.fetchMinSpeedKiBps,
|
|
89
|
+
onProgress,
|
|
90
|
+
size,
|
|
91
|
+
url: ctx.url,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
throw wrapFetchError(err, ctx.url, ctx.attempt, ctx.gotOpts.timeout);
|
|
96
|
+
}
|
|
97
|
+
return addFilesFromTarball({
|
|
98
|
+
buffer: data,
|
|
99
|
+
storeDir: ctx.opts.cafs.storeDir,
|
|
100
|
+
storeIndex: ctx.opts.storeIndex,
|
|
101
|
+
readManifest: ctx.opts.readManifest,
|
|
102
|
+
integrity: ctx.opts.integrity,
|
|
103
|
+
filesIndexFile: ctx.opts.filesIndexFile,
|
|
104
|
+
pkgId: ctx.opts.pkgId,
|
|
105
|
+
url: ctx.url,
|
|
106
|
+
pkg: ctx.opts.pkg,
|
|
107
|
+
appendManifest: ctx.opts.appendManifest,
|
|
108
|
+
ignoreFilePattern: ctx.opts.ignoreFilePattern,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function parseTarballContentLength(res) {
|
|
112
|
+
const isEncoded = isContentEncoded(res.headers.get('content-encoding'));
|
|
113
|
+
const contentLength = !isEncoded && res.headers.has('content-length') && res.headers.get('content-length');
|
|
114
|
+
const parsedLength = typeof contentLength === 'string' ? parseInt(contentLength, 10) : NaN;
|
|
115
|
+
return Number.isFinite(parsedLength) && parsedLength >= 0 ? parsedLength : null;
|
|
116
|
+
}
|
|
117
|
+
async function readResponseBody(opts) {
|
|
118
|
+
const startTime = Date.now();
|
|
119
|
+
let downloaded = 0;
|
|
120
|
+
let data;
|
|
121
|
+
if (opts.size !== null) {
|
|
122
|
+
data = await readKnownSizeBody(opts.body, opts.size, opts.url, opts.onProgress);
|
|
123
|
+
downloaded = opts.size;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const result = await readChunkedBody(opts.body, opts.onProgress);
|
|
127
|
+
data = result.data;
|
|
128
|
+
downloaded = result.downloaded;
|
|
129
|
+
}
|
|
130
|
+
checkDownloadSpeed(downloaded, startTime, opts.fetchMinSpeedKiBps, opts.url);
|
|
131
|
+
return data;
|
|
132
|
+
}
|
|
133
|
+
async function readKnownSizeBody(body, size, url, onProgress) {
|
|
134
|
+
const data = Buffer.from(new SharedArrayBuffer(size));
|
|
135
|
+
let downloaded = 0;
|
|
136
|
+
for await (const chunk of body) {
|
|
137
|
+
const bytes = chunk;
|
|
138
|
+
const nextDownloaded = downloaded + bytes.byteLength;
|
|
139
|
+
if (nextDownloaded > size) {
|
|
140
|
+
throw new BadTarballError({ expectedSize: size, receivedSize: nextDownloaded, tarballUrl: url });
|
|
141
|
+
}
|
|
142
|
+
data.set(bytes, downloaded);
|
|
143
|
+
downloaded = nextDownloaded;
|
|
144
|
+
onProgress?.(downloaded);
|
|
145
|
+
}
|
|
146
|
+
if (size !== downloaded) {
|
|
147
|
+
throw new BadTarballError({ expectedSize: size, receivedSize: downloaded, tarballUrl: url });
|
|
148
|
+
}
|
|
149
|
+
return data;
|
|
150
|
+
}
|
|
151
|
+
async function readChunkedBody(body, onProgress) {
|
|
152
|
+
const chunks = [];
|
|
153
|
+
let downloaded = 0;
|
|
154
|
+
for await (const chunk of body) {
|
|
155
|
+
const bytes = chunk;
|
|
156
|
+
chunks.push(bytes);
|
|
157
|
+
downloaded += bytes.byteLength;
|
|
158
|
+
onProgress?.(downloaded);
|
|
159
|
+
}
|
|
160
|
+
const data = Buffer.from(new SharedArrayBuffer(downloaded));
|
|
161
|
+
let offset = 0;
|
|
162
|
+
for (const chunk of chunks) {
|
|
163
|
+
data.set(chunk, offset);
|
|
164
|
+
offset += chunk.byteLength;
|
|
165
|
+
}
|
|
166
|
+
return { data, downloaded };
|
|
167
|
+
}
|
|
168
|
+
function checkDownloadSpeed(downloaded, startTime, fetchMinSpeedKiBps, url) {
|
|
169
|
+
const elapsedSec = (Date.now() - startTime) / 1000;
|
|
170
|
+
const avgKiBps = Math.floor((downloaded / elapsedSec) / 1024);
|
|
171
|
+
if (downloaded > 0 && elapsedSec > 1 && avgKiBps < fetchMinSpeedKiBps) {
|
|
172
|
+
const sizeKb = Math.floor(downloaded / 1024);
|
|
173
|
+
globalWarn(`Tarball download average speed ${avgKiBps} KiB/s (size ${sizeKb} KiB) is below ${fetchMinSpeedKiBps} KiB/s: ${redactUrlForDisplay(url)} (GET)`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function wrapFetchError(err, url, attempt, timeout) {
|
|
177
|
+
const error = isFetchTimeoutError(err)
|
|
178
|
+
? new FetchTimeoutError('FETCH_TIMEOUT', url, timeout, { cause: err })
|
|
179
|
+
: isError(err) ? err : new Error(String(err), { cause: err });
|
|
180
|
+
Object.assign(error, {
|
|
181
|
+
attempts: attempt,
|
|
182
|
+
resource: url,
|
|
183
|
+
});
|
|
184
|
+
return error;
|
|
185
|
+
}
|
|
186
|
+
function handleDownloadRetry(opts) {
|
|
187
|
+
const status = opts.error.response?.status;
|
|
188
|
+
const isManualRedirect = opts.opts.redirect === 'manual' && status >= 300 && status < 400;
|
|
189
|
+
const isFatal = isManualRedirect ||
|
|
190
|
+
status === 401 ||
|
|
191
|
+
status === 403 ||
|
|
192
|
+
status === 404 ||
|
|
193
|
+
opts.error.code === 'ERR_PNPM_PREPARE_PKG_FAILURE' ||
|
|
194
|
+
isNonRetryableError(opts.error);
|
|
195
|
+
if (isFatal) {
|
|
196
|
+
opts.reject(opts.error);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const timeout = opts.op.retry(opts.error);
|
|
200
|
+
if (timeout === false) {
|
|
201
|
+
opts.reject(opts.op.mainError());
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
logRetryAttempt(opts.attempt, opts.error, opts.downloadRetryOpts.retries ?? 0, timeout, opts.url);
|
|
205
|
+
}
|
|
206
|
+
function logRetryAttempt(attempt, error, // eslint-disable-line
|
|
207
|
+
maxRetries, timeout, url) {
|
|
208
|
+
const errorInfo = {
|
|
209
|
+
name: error.name,
|
|
210
|
+
message: error.message,
|
|
211
|
+
code: error.code,
|
|
212
|
+
errno: error.errno,
|
|
213
|
+
status: error.status,
|
|
214
|
+
statusCode: error.statusCode,
|
|
215
|
+
cause: error.cause ? {
|
|
216
|
+
code: error.cause.code,
|
|
217
|
+
errno: error.cause.errno,
|
|
218
|
+
} : undefined,
|
|
179
219
|
};
|
|
220
|
+
requestRetryLogger.debug({
|
|
221
|
+
attempt,
|
|
222
|
+
error: errorInfo,
|
|
223
|
+
maxRetries,
|
|
224
|
+
method: 'GET',
|
|
225
|
+
timeout,
|
|
226
|
+
url: redactUrlForDisplay(url),
|
|
227
|
+
});
|
|
180
228
|
}
|
|
181
229
|
function getSecureNodeMirrorAuthHeader(authHeaderValue, url, appendedPackageName) {
|
|
182
230
|
if (authHeaderValue == null || appendedPackageName !== 'node')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/fetching.tarball-fetcher",
|
|
3
|
-
"version": "1102.1.
|
|
3
|
+
"version": "1102.1.7",
|
|
4
4
|
"description": "Fetcher for packages hosted as tarballs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -29,15 +29,15 @@
|
|
|
29
29
|
"!*.map"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@pnpm/core-loggers": "1101.0.
|
|
32
|
+
"@pnpm/core-loggers": "1101.0.2",
|
|
33
33
|
"@pnpm/error": "1100.2.1",
|
|
34
|
-
"@pnpm/exec.prepare-package": "1100.0.
|
|
34
|
+
"@pnpm/exec.prepare-package": "1100.0.41",
|
|
35
35
|
"@pnpm/fetching.fetcher-base": "1100.2.12",
|
|
36
36
|
"@pnpm/fetching.types": "1100.0.3",
|
|
37
37
|
"@pnpm/fs.graceful-fs": "1100.2.4",
|
|
38
|
-
"@pnpm/fs.packlist": "1100.0.
|
|
39
|
-
"@pnpm/network.fetch": "1100.1.
|
|
40
|
-
"@pnpm/store.index": "
|
|
38
|
+
"@pnpm/fs.packlist": "1100.0.7",
|
|
39
|
+
"@pnpm/network.fetch": "1100.1.21",
|
|
40
|
+
"@pnpm/store.index": "1101.0.0",
|
|
41
41
|
"@pnpm/types": "1102.1.1",
|
|
42
42
|
"@zkochan/retry": "^0.2.0",
|
|
43
43
|
"lodash.throttle": "4.1.1",
|
|
@@ -45,14 +45,14 @@
|
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@pnpm/logger": "^1100.0.0",
|
|
48
|
-
"@pnpm/worker": "^1100.4.
|
|
48
|
+
"@pnpm/worker": "^1100.4.7"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@jest/globals": "30.4.1",
|
|
52
|
-
"@pnpm/fetching.tarball-fetcher": "1102.1.
|
|
52
|
+
"@pnpm/fetching.tarball-fetcher": "1102.1.7",
|
|
53
53
|
"@pnpm/logger": "1100.0.0",
|
|
54
54
|
"@pnpm/store.cafs-types": "1100.1.1",
|
|
55
|
-
"@pnpm/store.create-cafs-store": "1100.0.
|
|
55
|
+
"@pnpm/store.create-cafs-store": "1100.0.33",
|
|
56
56
|
"@pnpm/test-fixtures": "1100.0.1",
|
|
57
57
|
"@pnpm/text.ordinal-comparator": "1100.0.0",
|
|
58
58
|
"@pnpm/types": "1102.1.1",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@types/ssri": "^7.1.5",
|
|
63
63
|
"ssri": "13.0.1",
|
|
64
64
|
"tempy": "3.0.0",
|
|
65
|
-
"undici": "^7.29.
|
|
65
|
+
"undici": "^7.29.1"
|
|
66
66
|
},
|
|
67
67
|
"engines": {
|
|
68
68
|
"node": ">=22.13"
|