@pnpm/fetching.tarball-fetcher 1102.0.4 → 1102.0.6

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @pnpm/tarball-fetcher
2
2
 
3
+ ## 1102.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies:
8
+ - @pnpm/exec.prepare-package@1100.0.24
9
+
10
+ ## 1102.0.5
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies:
15
+ - @pnpm/core-loggers@1100.2.3
16
+ - @pnpm/exec.prepare-package@1100.0.23
17
+ - @pnpm/fs.packlist@1100.0.3
18
+
3
19
  ## 1102.0.4
4
20
 
5
21
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/fetching.tarball-fetcher",
3
- "version": "1102.0.4",
3
+ "version": "1102.0.6",
4
4
  "description": "Fetcher for packages hosted as tarballs",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -29,13 +29,13 @@
29
29
  "!*.map"
30
30
  ],
31
31
  "dependencies": {
32
- "@pnpm/core-loggers": "1100.2.2",
32
+ "@pnpm/core-loggers": "1100.2.3",
33
33
  "@pnpm/error": "1100.0.1",
34
- "@pnpm/exec.prepare-package": "1100.0.22",
34
+ "@pnpm/exec.prepare-package": "1100.0.24",
35
35
  "@pnpm/fetching.fetcher-base": "1100.2.2",
36
36
  "@pnpm/fetching.types": "1100.0.2",
37
37
  "@pnpm/fs.graceful-fs": "1100.1.0",
38
- "@pnpm/fs.packlist": "1100.0.2",
38
+ "@pnpm/fs.packlist": "1100.0.3",
39
39
  "@pnpm/store.index": "1100.2.1",
40
40
  "@pnpm/types": "1101.4.0",
41
41
  "@zkochan/retry": "^0.2.0",
@@ -45,15 +45,15 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@pnpm/logger": "^1100.0.0",
48
- "@pnpm/worker": "^1100.2.4"
48
+ "@pnpm/worker": "^1100.2.5"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@jest/globals": "30.4.1",
52
- "@pnpm/fetching.tarball-fetcher": "1102.0.4",
52
+ "@pnpm/fetching.tarball-fetcher": "1102.0.6",
53
53
  "@pnpm/logger": "1100.0.0",
54
- "@pnpm/network.fetch": "1100.1.5",
54
+ "@pnpm/network.fetch": "1100.1.6",
55
55
  "@pnpm/store.cafs-types": "1100.0.1",
56
- "@pnpm/store.create-cafs-store": "1100.0.18",
56
+ "@pnpm/store.create-cafs-store": "1100.0.19",
57
57
  "@pnpm/test-fixtures": "1100.0.0",
58
58
  "@pnpm/types": "1101.4.0",
59
59
  "@pnpm/util.lex-comparator": "^4.0.1",
@@ -1,11 +0,0 @@
1
- import { PnpmError } from '@pnpm/error';
2
- export declare class BadTarballError extends PnpmError {
3
- expectedSize: number;
4
- receivedSize: number;
5
- constructor(opts: {
6
- attempts?: number;
7
- expectedSize: number;
8
- receivedSize: number;
9
- tarballUrl: string;
10
- });
11
- }
@@ -1,14 +0,0 @@
1
- import { PnpmError } from '@pnpm/error';
2
- export class BadTarballError extends PnpmError {
3
- expectedSize;
4
- receivedSize;
5
- constructor(opts) {
6
- const message = `Actual size (${opts.receivedSize}) of tarball (${opts.tarballUrl}) did not match the one specified in 'Content-Length' header (${opts.expectedSize})`;
7
- super('BAD_TARBALL_SIZE', message, {
8
- attempts: opts?.attempts,
9
- });
10
- this.expectedSize = opts.expectedSize;
11
- this.receivedSize = opts.receivedSize;
12
- }
13
- }
14
- //# sourceMappingURL=BadTarballError.js.map
@@ -1 +0,0 @@
1
- export { BadTarballError } from './BadTarballError.js';
@@ -1,2 +0,0 @@
1
- export { BadTarballError } from './BadTarballError.js';
2
- //# sourceMappingURL=index.js.map
@@ -1,8 +0,0 @@
1
- import type { FetchFunction } from '@pnpm/fetching.fetcher-base';
2
- import type { StoreIndex } from '@pnpm/store.index';
3
- export interface CreateGitHostedTarballFetcher {
4
- ignoreScripts?: boolean;
5
- storeIndex: StoreIndex;
6
- unsafePerm?: boolean;
7
- }
8
- export declare function createGitHostedTarballFetcher(fetchRemoteTarball: FetchFunction, fetcherOpts: CreateGitHostedTarballFetcher): FetchFunction;
@@ -1,100 +0,0 @@
1
- import assert from 'node:assert';
2
- import util from 'node:util';
3
- import { preparePackage } from '@pnpm/exec.prepare-package';
4
- import { packlist } from '@pnpm/fs.packlist';
5
- import { globalWarn } from '@pnpm/logger';
6
- import { addFilesFromDir } from '@pnpm/worker';
7
- export function createGitHostedTarballFetcher(fetchRemoteTarball, fetcherOpts) {
8
- const fetch = async (cafs, resolution, opts) => {
9
- const rawFilesIndexFile = `${opts.filesIndexFile}\traw`;
10
- const { filesMap, manifest, requiresBuild, integrity } = await fetchRemoteTarball(cafs, resolution, {
11
- ...opts,
12
- filesIndexFile: rawFilesIndexFile,
13
- });
14
- // Flush any queued store index writes so that the raw files index entry
15
- // written during tarball extraction is visible to subsequent reads.
16
- fetcherOpts.storeIndex.flush();
17
- try {
18
- const prepareResult = await prepareGitHostedPkg(filesMap, cafs, rawFilesIndexFile, opts.filesIndexFile, fetcherOpts, opts, resolution);
19
- if (prepareResult.ignoredBuild) {
20
- globalWarn(`The git-hosted package fetched from "${resolution.tarball}" has to be built but the build scripts were ignored.`);
21
- }
22
- return {
23
- filesMap: prepareResult.filesMap,
24
- manifest: prepareResult.manifest ?? manifest,
25
- requiresBuild,
26
- // Propagate the raw tarball integrity so the lockfile pins it and
27
- // future installs detect a tampered tarball from the git host.
28
- integrity,
29
- };
30
- }
31
- catch (err) {
32
- assert(util.types.isNativeError(err));
33
- err.message = `Failed to prepare git-hosted package fetched from "${resolution.tarball}": ${err.message}`;
34
- throw err;
35
- }
36
- };
37
- return fetch;
38
- }
39
- async function prepareGitHostedPkg(filesMap, cafs, rawFilesIndexFile, filesIndexFile, opts, fetcherOpts, resolution) {
40
- const tempLocation = await cafs.tempDir();
41
- cafs.importPackage(tempLocation, {
42
- filesResponse: {
43
- filesMap,
44
- resolvedFrom: 'remote',
45
- requiresBuild: false,
46
- },
47
- force: true,
48
- });
49
- const { shouldBeBuilt, pkgDir } = await preparePackage({
50
- ...opts,
51
- allowBuild: fetcherOpts.allowBuild,
52
- pkgResolutionId: createGitHostedTarballPkgResolutionId(resolution),
53
- }, tempLocation, resolution.path ?? '');
54
- const files = await packlist(pkgDir);
55
- const { storeIndex } = opts;
56
- if (!resolution.path && files.length === filesMap.size) {
57
- if (!shouldBeBuilt) {
58
- const data = storeIndex.get(rawFilesIndexFile);
59
- if (data) {
60
- storeIndex.set(filesIndexFile, data);
61
- storeIndex.delete(rawFilesIndexFile);
62
- }
63
- return {
64
- filesMap,
65
- ignoredBuild: false,
66
- };
67
- }
68
- if (opts.ignoreScripts) {
69
- storeIndex.delete(rawFilesIndexFile);
70
- return {
71
- filesMap,
72
- ignoredBuild: true,
73
- };
74
- }
75
- }
76
- storeIndex.delete(rawFilesIndexFile);
77
- // Important! We cannot remove the temp location at this stage.
78
- // Even though we have the index of the package,
79
- // the linking of files to the store is in progress.
80
- return {
81
- ...await addFilesFromDir({
82
- storeDir: cafs.storeDir,
83
- storeIndex: opts.storeIndex,
84
- dir: pkgDir,
85
- files,
86
- filesIndexFile,
87
- pkg: fetcherOpts.pkg,
88
- readManifest: fetcherOpts.readManifest,
89
- }),
90
- ignoredBuild: Boolean(opts.ignoreScripts),
91
- };
92
- }
93
- function createGitHostedTarballPkgResolutionId(resolution) {
94
- let pkgResolutionId = resolution.tarball;
95
- if (resolution.path) {
96
- pkgResolutionId += `#path:${resolution.path}`;
97
- }
98
- return pkgResolutionId;
99
- }
100
- //# sourceMappingURL=gitHostedTarballFetcher.js.map
package/lib/index.d.ts DELETED
@@ -1,23 +0,0 @@
1
- import type { FetchFunction } from '@pnpm/fetching.fetcher-base';
2
- import type { FetchFromRegistry, GetAuthHeader, RetryTimeoutOptions } from '@pnpm/fetching.types';
3
- import type { StoreIndex } from '@pnpm/store.index';
4
- import { TarballIntegrityError } from '@pnpm/worker';
5
- import { type CreateDownloaderOptions } from './remoteTarballFetcher.js';
6
- export { BadTarballError } from './errorTypes/index.js';
7
- export { TarballIntegrityError };
8
- export { createGitHostedTarballFetcher } from './gitHostedTarballFetcher.js';
9
- export { createLocalTarballFetcher } from './localTarballFetcher.js';
10
- export { createDownloader, type CreateDownloaderOptions, type DownloadFunction } from './remoteTarballFetcher.js';
11
- export interface TarballFetchers {
12
- localTarball: FetchFunction;
13
- remoteTarball: FetchFunction;
14
- gitHostedTarball: FetchFunction;
15
- }
16
- export declare function createTarballFetcher(fetchFromRegistry: FetchFromRegistry, getAuthHeader: GetAuthHeader, opts: {
17
- unsafePerm?: boolean;
18
- ignoreScripts?: boolean;
19
- storeIndex: StoreIndex;
20
- timeout?: number;
21
- retry?: RetryTimeoutOptions;
22
- offline?: boolean;
23
- } & Pick<CreateDownloaderOptions, 'fetchMinSpeedKiBps'>): TarballFetchers;
@@ -1,3 +0,0 @@
1
- import type { FetchFunction } from '@pnpm/fetching.fetcher-base';
2
- import type { StoreIndex } from '@pnpm/store.index';
3
- export declare function createLocalTarballFetcher(storeIndex: StoreIndex): FetchFunction;
@@ -1,29 +0,0 @@
1
- import path from 'node:path';
2
- import gfs from '@pnpm/fs.graceful-fs';
3
- import { addFilesFromTarball } from '@pnpm/worker';
4
- const isAbsolutePath = /^\/|^[A-Z]:/i;
5
- export function createLocalTarballFetcher(storeIndex) {
6
- const fetch = (cafs, resolution, opts) => {
7
- const tarball = resolvePath(opts.lockfileDir, resolution.tarball.slice(5));
8
- const buffer = gfs.readFileSync(tarball);
9
- return addFilesFromTarball({
10
- storeDir: cafs.storeDir,
11
- storeIndex,
12
- buffer,
13
- filesIndexFile: opts.filesIndexFile,
14
- integrity: resolution.integrity,
15
- readManifest: opts.readManifest,
16
- url: tarball,
17
- pkg: opts.pkg,
18
- appendManifest: opts.appendManifest,
19
- ignoreFilePattern: opts.ignoreFilePattern,
20
- });
21
- };
22
- return fetch;
23
- }
24
- function resolvePath(where, spec) {
25
- if (isAbsolutePath.test(spec))
26
- return spec;
27
- return path.resolve(where, spec);
28
- }
29
- //# sourceMappingURL=localTarballFetcher.js.map
@@ -1,37 +0,0 @@
1
- import type { IncomingMessage } from 'node:http';
2
- import type { FetchOptions, FetchResult } from '@pnpm/fetching.fetcher-base';
3
- import type { FetchFromRegistry, GetAuthHeader } from '@pnpm/fetching.types';
4
- import type { Cafs } from '@pnpm/store.cafs-types';
5
- import type { StoreIndex } from '@pnpm/store.index';
6
- export interface HttpResponse {
7
- body: string;
8
- }
9
- export type DownloadOptions = {
10
- getAuthHeaderByURI: GetAuthHeader;
11
- cafs: Cafs;
12
- registry?: string;
13
- onStart?: (totalSize: number | null, attempt: number) => void;
14
- onProgress?: (downloaded: number) => void;
15
- integrity?: string;
16
- storeIndex: StoreIndex;
17
- pkg?: FetchOptions['pkg'];
18
- } & Pick<FetchOptions, 'appendManifest' | 'readManifest' | 'filesIndexFile' | 'ignoreFilePattern'>;
19
- export type DownloadFunction = (url: string, opts: DownloadOptions) => Promise<FetchResult>;
20
- export interface NpmRegistryClient {
21
- get: (url: string, getOpts: object, cb: (err: Error, data: object, raw: object, res: HttpResponse) => void) => void;
22
- fetch: (url: string, opts: {
23
- auth?: object;
24
- }, cb: (err: Error, res: IncomingMessage) => void) => void;
25
- }
26
- export interface CreateDownloaderOptions {
27
- retry?: {
28
- retries?: number;
29
- factor?: number;
30
- minTimeout?: number;
31
- maxTimeout?: number;
32
- randomize?: boolean;
33
- };
34
- timeout?: number;
35
- fetchMinSpeedKiBps?: number;
36
- }
37
- export declare function createDownloader(fetchFromRegistry: FetchFromRegistry, gotOpts: CreateDownloaderOptions): DownloadFunction;
@@ -1,184 +0,0 @@
1
- import util from 'node:util';
2
- import { requestRetryLogger } from '@pnpm/core-loggers';
3
- import { FetchError } from '@pnpm/error';
4
- import { globalWarn } from '@pnpm/logger';
5
- import { addFilesFromTarball } from '@pnpm/worker';
6
- import * as retry from '@zkochan/retry';
7
- import throttle from 'lodash.throttle';
8
- import { BadTarballError } from './errorTypes/index.js';
9
- const BIG_TARBALL_SIZE = 1024 * 1024 * 5; // 5 MB
10
- export function createDownloader(fetchFromRegistry, gotOpts) {
11
- const retryOpts = {
12
- factor: 10,
13
- maxTimeout: 6e4, // 1 minute
14
- minTimeout: 1e4, // 10 seconds
15
- retries: 2,
16
- ...gotOpts.retry,
17
- };
18
- const fetchMinSpeedKiBps = gotOpts.fetchMinSpeedKiBps ?? 50; // 50 KiB/s
19
- return async function download(url, opts) {
20
- const authHeaderValue = opts.getAuthHeaderByURI(url, { pkgName: opts.pkg?.name });
21
- const op = retry.operation(retryOpts);
22
- return new Promise((resolve, reject) => {
23
- op.attempt(async (attempt) => {
24
- try {
25
- resolve(await fetch(attempt));
26
- }
27
- catch (error) { // eslint-disable-line
28
- if (error.response?.status === 401 ||
29
- error.response?.status === 403 ||
30
- error.response?.status === 404 ||
31
- error.code === 'ERR_PNPM_PREPARE_PKG_FAILURE') {
32
- reject(error);
33
- return;
34
- }
35
- const timeout = op.retry(error);
36
- if (timeout === false) {
37
- reject(op.mainError());
38
- return;
39
- }
40
- // Extract error properties into a plain object because Error properties
41
- // are non-enumerable and don't serialize well through the logging system
42
- const errorInfo = {
43
- name: error.name,
44
- message: error.message,
45
- code: error.code,
46
- errno: error.errno,
47
- // For HTTP errors from our ResponseError class
48
- status: error.status,
49
- statusCode: error.statusCode,
50
- // undici wraps the actual network error in a cause property
51
- cause: error.cause ? {
52
- code: error.cause.code,
53
- errno: error.cause.errno,
54
- } : undefined,
55
- };
56
- requestRetryLogger.debug({
57
- attempt,
58
- error: errorInfo,
59
- maxRetries: retryOpts.retries,
60
- method: 'GET',
61
- timeout,
62
- url,
63
- });
64
- }
65
- });
66
- });
67
- async function fetch(currentAttempt) {
68
- let data;
69
- try {
70
- const res = await fetchFromRegistry(url, {
71
- authHeaderValue,
72
- // Tarballs are already compressed; ask the server not to apply an additional
73
- // Content-Encoding so Content-Length matches the body we receive and we don't
74
- // waste CPU on round-trip re-compression. See https://github.com/pnpm/pnpm/issues/11506
75
- headers: { 'accept-encoding': 'identity' },
76
- // The fetch library can retry requests on bad HTTP responses.
77
- // However, it is not enough to retry on bad HTTP responses only.
78
- // Requests should also be retried when the tarball's integrity check fails.
79
- // Hence, we tell fetch to not retry,
80
- // and we perform the retries from this function instead.
81
- retry: { retries: 0 },
82
- timeout: gotOpts.timeout,
83
- });
84
- if (res.status !== 200) {
85
- throw new FetchError({ url, authHeaderValue }, res);
86
- }
87
- // When Content-Encoding is present, Content-Length refers to the encoded form
88
- // of the data, not the decoded bytes that the fetch implementation yields.
89
- // See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Encoding
90
- const isEncoded = isContentEncoded(res.headers.get('content-encoding'));
91
- const contentLength = !isEncoded && res.headers.has('content-length') && res.headers.get('content-length');
92
- const parsedLength = typeof contentLength === 'string' ? parseInt(contentLength, 10) : NaN;
93
- const size = Number.isFinite(parsedLength) && parsedLength >= 0 ? parsedLength : null;
94
- if (opts.onStart != null) {
95
- opts.onStart(size, currentAttempt);
96
- }
97
- // In order to reduce the amount of logs, we only report the download progress of big tarballs
98
- const onProgress = (size != null && size >= BIG_TARBALL_SIZE && opts.onProgress)
99
- ? throttle(opts.onProgress, 500)
100
- : undefined;
101
- const startTime = Date.now();
102
- let downloaded = 0;
103
- if (size !== null) {
104
- // Known size: pre-allocate and copy directly (avoids intermediate array + second copy pass)
105
- data = Buffer.from(new SharedArrayBuffer(size));
106
- for await (const chunk of res.body) {
107
- const c = chunk;
108
- const nextDownloaded = downloaded + c.byteLength;
109
- if (nextDownloaded > size) {
110
- throw new BadTarballError({
111
- expectedSize: size,
112
- receivedSize: nextDownloaded,
113
- tarballUrl: url,
114
- });
115
- }
116
- data.set(c, downloaded);
117
- downloaded = nextDownloaded;
118
- onProgress?.(downloaded);
119
- }
120
- if (size !== downloaded) {
121
- throw new BadTarballError({
122
- expectedSize: size,
123
- receivedSize: downloaded,
124
- tarballUrl: url,
125
- });
126
- }
127
- }
128
- else {
129
- const chunks = [];
130
- for await (const chunk of res.body) {
131
- const c = chunk;
132
- chunks.push(c);
133
- downloaded += c.byteLength;
134
- onProgress?.(downloaded);
135
- }
136
- data = Buffer.from(new SharedArrayBuffer(downloaded));
137
- let offset = 0;
138
- for (const chunk of chunks) {
139
- data.set(chunk, offset);
140
- offset += chunk.byteLength;
141
- }
142
- }
143
- const elapsedSec = (Date.now() - startTime) / 1000;
144
- const avgKiBps = Math.floor((downloaded / elapsedSec) / 1024);
145
- if (downloaded > 0 && elapsedSec > 1 && avgKiBps < fetchMinSpeedKiBps) {
146
- const sizeKb = Math.floor(downloaded / 1024);
147
- globalWarn(`Tarball download average speed ${avgKiBps} KiB/s (size ${sizeKb} KiB) is below ${fetchMinSpeedKiBps} KiB/s: ${url} (GET)`);
148
- }
149
- }
150
- catch (err) {
151
- const error = util.types.isNativeError(err) ? err : new Error(String(err), { cause: err });
152
- Object.assign(error, {
153
- attempts: currentAttempt,
154
- resource: url,
155
- });
156
- throw error;
157
- }
158
- return addFilesFromTarball({
159
- buffer: data,
160
- storeDir: opts.cafs.storeDir,
161
- storeIndex: opts.storeIndex,
162
- readManifest: opts.readManifest,
163
- integrity: opts.integrity,
164
- filesIndexFile: opts.filesIndexFile,
165
- url,
166
- pkg: opts.pkg,
167
- appendManifest: opts.appendManifest,
168
- ignoreFilePattern: opts.ignoreFilePattern,
169
- });
170
- }
171
- };
172
- }
173
- // Per RFC 9110 §8.4, Content-Encoding is a comma-separated list of codings.
174
- // The response is encoded unless every coding is `identity` (case-insensitive,
175
- // surrounding whitespace ignored).
176
- function isContentEncoded(header) {
177
- if (header == null)
178
- return false;
179
- return header
180
- .split(',')
181
- .map(coding => coding.trim().toLowerCase())
182
- .some(coding => coding !== '' && coding !== 'identity');
183
- }
184
- //# sourceMappingURL=remoteTarballFetcher.js.map