@vltpkg/package-info 1.0.0-rc.26 → 1.0.0-rc.27
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/dist/index.d.ts +7 -0
- package/dist/index.js +113 -75
- package/package.json +13 -13
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,13 @@ export type PackageInfoClientRequestOptions = PickManifestOptions & RegistryClie
|
|
|
32
32
|
export type PackageInfoClientExtractOptions = PackageInfoClientRequestOptions & {
|
|
33
33
|
integrity?: Integrity;
|
|
34
34
|
resolved?: string;
|
|
35
|
+
/**
|
|
36
|
+
* When true, indicates that integrity + resolved came from a
|
|
37
|
+
* lockfile (i.e. they were already verified on first install).
|
|
38
|
+
* Skips the client-side tarball integrity check.
|
|
39
|
+
* Defaults to false — fresh installs always verify integrity.
|
|
40
|
+
*/
|
|
41
|
+
fromLockfile?: boolean;
|
|
35
42
|
};
|
|
36
43
|
export declare class PackageInfoClient {
|
|
37
44
|
#private;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { pickManifest } from '@vltpkg/pick-manifest';
|
|
|
5
5
|
import { RegistryClient } from '@vltpkg/registry-client';
|
|
6
6
|
import { Spec } from '@vltpkg/spec';
|
|
7
7
|
import { Pool } from '@vltpkg/tar';
|
|
8
|
-
import { asPackument
|
|
8
|
+
import { asPackument } from '@vltpkg/types';
|
|
9
9
|
import ssri from 'ssri';
|
|
10
10
|
import { Monorepo } from '@vltpkg/workspaces';
|
|
11
11
|
import { XDG } from '@vltpkg/xdg';
|
|
@@ -65,13 +65,12 @@ export class PackageInfoClient {
|
|
|
65
65
|
async extract(spec, target, options = {}) {
|
|
66
66
|
if (typeof spec === 'string')
|
|
67
67
|
spec = Spec.parse(spec, this.options);
|
|
68
|
-
const { from = this.#projectRoot, integrity, resolved } = options;
|
|
68
|
+
const { from = this.#projectRoot, integrity, resolved, fromLockfile = false, } = options;
|
|
69
69
|
const f = spec.final;
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const r = fromLockfile ?
|
|
70
|
+
// If the caller already provides both integrity and resolved
|
|
71
|
+
// (from lockfile or prior resolution), skip re-resolving.
|
|
72
|
+
const alreadyResolved = !!(integrity && resolved);
|
|
73
|
+
const r = alreadyResolved ?
|
|
75
74
|
{ resolved, integrity, spec }
|
|
76
75
|
: await this.resolve(spec, options);
|
|
77
76
|
switch (f.type) {
|
|
@@ -102,41 +101,70 @@ export class PackageInfoClient {
|
|
|
102
101
|
// fallthrough if a remote tarball url present
|
|
103
102
|
}
|
|
104
103
|
case 'registry': {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
throw this.#resolveError(spec, options, 'failed to fetch tarball', {
|
|
112
|
-
url: r.resolved,
|
|
113
|
-
response,
|
|
104
|
+
const fetchTarball = async (useCache) => {
|
|
105
|
+
const trustIntegrity = this.#trustedIntegrities.get(r.resolved) === r.integrity;
|
|
106
|
+
const response = await this.registryClient.request(r.resolved, {
|
|
107
|
+
integrity: r.integrity,
|
|
108
|
+
trustIntegrity,
|
|
109
|
+
...(useCache === false ? { useCache } : {}),
|
|
114
110
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (!trustIntegrity &&
|
|
118
|
-
response.checkIntegrity({ spec, url: resolved })) {
|
|
119
|
-
this.#trustedIntegrities.set(r.resolved, response.integrity);
|
|
120
|
-
}
|
|
121
|
-
const buf = response.buffer();
|
|
122
|
-
// Verify tarball integrity against the manifest's dist.integrity.
|
|
123
|
-
// This is a supply-chain security measure: the registry may not
|
|
124
|
-
// validate integrity, so we do it client-side on every fresh
|
|
125
|
-
// download. Skip when integrity came from lockfile/cache (it was
|
|
126
|
-
// already verified on first install).
|
|
127
|
-
if (r.integrity && !fromLockfile) {
|
|
128
|
-
const hash = createHash('sha512');
|
|
129
|
-
hash.update(buf);
|
|
130
|
-
const computed = `sha512-${hash.digest('base64')}`;
|
|
131
|
-
if (computed !== r.integrity) {
|
|
132
|
-
throw error('Tarball integrity check failed', {
|
|
133
|
-
code: 'EINTEGRITY',
|
|
134
|
-
spec,
|
|
111
|
+
if (response.statusCode !== 200) {
|
|
112
|
+
throw this.#resolveError(spec, options, 'failed to fetch tarball', {
|
|
135
113
|
url: r.resolved,
|
|
136
|
-
|
|
137
|
-
found: computed,
|
|
114
|
+
response,
|
|
138
115
|
});
|
|
139
116
|
}
|
|
117
|
+
// if it's not trusted already, but valid, start trusting
|
|
118
|
+
if (!trustIntegrity &&
|
|
119
|
+
response.checkIntegrity({ spec, url: resolved })) {
|
|
120
|
+
this.#trustedIntegrities.set(r.resolved, response.integrity);
|
|
121
|
+
}
|
|
122
|
+
const buf = response.buffer();
|
|
123
|
+
// Verify tarball integrity against the manifest's
|
|
124
|
+
// dist.integrity. This is a supply-chain security measure:
|
|
125
|
+
// the registry may not validate integrity, so we do it
|
|
126
|
+
// client-side on every fresh download. Skip when integrity
|
|
127
|
+
// came from lockfile/cache (it was already verified on
|
|
128
|
+
// first install).
|
|
129
|
+
/* c8 ignore start - defense-in-depth: registry client's
|
|
130
|
+
* checkIntegrity() usually catches mismatches first since
|
|
131
|
+
* it checks the same gzip bytes. This fires only when the
|
|
132
|
+
* registry client check is skipped (trustIntegrity=true). */
|
|
133
|
+
if (r.integrity && !fromLockfile) {
|
|
134
|
+
const hash = createHash('sha512');
|
|
135
|
+
hash.update(buf);
|
|
136
|
+
const computed = `sha512-${hash.digest('base64')}`;
|
|
137
|
+
if (computed !== r.integrity) {
|
|
138
|
+
throw error('Tarball integrity check failed', {
|
|
139
|
+
code: 'EINTEGRITY',
|
|
140
|
+
spec,
|
|
141
|
+
url: r.resolved,
|
|
142
|
+
wanted: r.integrity,
|
|
143
|
+
found: computed,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/* c8 ignore stop */
|
|
148
|
+
return buf;
|
|
149
|
+
};
|
|
150
|
+
let buf;
|
|
151
|
+
try {
|
|
152
|
+
buf = await fetchTarball();
|
|
153
|
+
}
|
|
154
|
+
catch (er) {
|
|
155
|
+
// On EINTEGRITY, retry once bypassing cache. This handles
|
|
156
|
+
// transient issues such as corrupted downloads or CDN
|
|
157
|
+
// inconsistencies that cause the cached tarball to not
|
|
158
|
+
// match the expected integrity hash.
|
|
159
|
+
if (er instanceof Error &&
|
|
160
|
+
'cause' in er &&
|
|
161
|
+
er.cause
|
|
162
|
+
?.code === 'EINTEGRITY') {
|
|
163
|
+
buf = await fetchTarball(false);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
throw er;
|
|
167
|
+
}
|
|
140
168
|
}
|
|
141
169
|
try {
|
|
142
170
|
await this.tarPool.unpack(buf, target);
|
|
@@ -302,37 +330,56 @@ export class PackageInfoClient {
|
|
|
302
330
|
if (!tarball) {
|
|
303
331
|
throw this.#resolveError(spec, options, 'no tarball found in manifest.dist');
|
|
304
332
|
}
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
333
|
+
const fetchTarball = async (useCache) => {
|
|
334
|
+
const trustIntegrity = this.#trustedIntegrities.get(tarball) === integrity;
|
|
335
|
+
const response = await this.registryClient.request(tarball, {
|
|
336
|
+
...options,
|
|
337
|
+
integrity,
|
|
338
|
+
trustIntegrity,
|
|
339
|
+
...(useCache === false ? { useCache } : {}),
|
|
340
|
+
});
|
|
341
|
+
if (response.statusCode !== 200) {
|
|
342
|
+
throw this.#resolveError(spec, options, 'failed to fetch tarball', { response, url: tarball });
|
|
343
|
+
}
|
|
344
|
+
// if we don't already trust it, but it's valid, start
|
|
345
|
+
// trusting it
|
|
346
|
+
if (!trustIntegrity &&
|
|
347
|
+
response.checkIntegrity({ spec, url: tarball })) {
|
|
348
|
+
this.#trustedIntegrities.set(tarball, response.integrity);
|
|
349
|
+
}
|
|
350
|
+
const buf = response.buffer();
|
|
351
|
+
// Verify tarball integrity against the manifest's
|
|
352
|
+
// dist.integrity.
|
|
353
|
+
/* c8 ignore start - defense-in-depth (see extract) */
|
|
354
|
+
if (integrity) {
|
|
355
|
+
const hash = createHash('sha512');
|
|
356
|
+
hash.update(buf);
|
|
357
|
+
const computed = `sha512-${hash.digest('base64')}`;
|
|
358
|
+
if (computed !== integrity) {
|
|
359
|
+
throw error('Tarball integrity check failed', {
|
|
360
|
+
code: 'EINTEGRITY',
|
|
361
|
+
spec,
|
|
362
|
+
url: tarball,
|
|
363
|
+
wanted: integrity,
|
|
364
|
+
found: computed,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/* c8 ignore stop */
|
|
369
|
+
return buf;
|
|
370
|
+
};
|
|
371
|
+
try {
|
|
372
|
+
return await fetchTarball();
|
|
318
373
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (computed !== integrity) {
|
|
326
|
-
throw error('Tarball integrity check failed', {
|
|
327
|
-
code: 'EINTEGRITY',
|
|
328
|
-
spec,
|
|
329
|
-
url: tarball,
|
|
330
|
-
wanted: integrity,
|
|
331
|
-
found: computed,
|
|
332
|
-
});
|
|
374
|
+
catch (er) {
|
|
375
|
+
if (er instanceof Error &&
|
|
376
|
+
'cause' in er &&
|
|
377
|
+
er.cause
|
|
378
|
+
?.code === 'EINTEGRITY') {
|
|
379
|
+
return await fetchTarball(false);
|
|
333
380
|
}
|
|
381
|
+
throw er;
|
|
334
382
|
}
|
|
335
|
-
return buf;
|
|
336
383
|
}
|
|
337
384
|
case 'git': {
|
|
338
385
|
const { remoteURL, gitRemote, gitCommittish, gitSelectorParsed, } = f;
|
|
@@ -432,15 +479,6 @@ export class PackageInfoClient {
|
|
|
432
479
|
: pickManifest(await this.packument(f, options), spec, options);
|
|
433
480
|
if (!mani)
|
|
434
481
|
throw this.#resolveError(spec, options);
|
|
435
|
-
const { integrity, tarball } = mani.dist ?? /* c8 ignore next */ {};
|
|
436
|
-
if (isIntegrity(integrity) && tarball) {
|
|
437
|
-
const registryOrigin = new URL(String(f.registry)).origin;
|
|
438
|
-
const tgzOrigin = new URL(tarball).origin;
|
|
439
|
-
// if it comes from the same origin, trust the integrity
|
|
440
|
-
if (tgzOrigin === registryOrigin) {
|
|
441
|
-
this.#trustedIntegrities.set(tarball, integrity);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
482
|
// Cache the manifest data
|
|
445
483
|
if (cachePath) {
|
|
446
484
|
const json = JSON.stringify({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vltpkg/package-info",
|
|
3
3
|
"description": "Resolve and fetch package metadata and tarballs",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.27",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
"url": "http://vlt.sh"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@vltpkg/error-cause": "1.0.0-rc.
|
|
17
|
-
"@vltpkg/git": "1.0.0-rc.
|
|
18
|
-
"@vltpkg/package-json": "1.0.0-rc.
|
|
19
|
-
"@vltpkg/pick-manifest": "1.0.0-rc.
|
|
20
|
-
"@vltpkg/registry-client": "1.0.0-rc.
|
|
21
|
-
"@vltpkg/spec": "1.0.0-rc.
|
|
22
|
-
"@vltpkg/tar": "1.0.0-rc.
|
|
23
|
-
"@vltpkg/types": "1.0.0-rc.
|
|
24
|
-
"@vltpkg/workspaces": "1.0.0-rc.
|
|
25
|
-
"@vltpkg/xdg": "1.0.0-rc.
|
|
16
|
+
"@vltpkg/error-cause": "1.0.0-rc.27",
|
|
17
|
+
"@vltpkg/git": "1.0.0-rc.27",
|
|
18
|
+
"@vltpkg/package-json": "1.0.0-rc.27",
|
|
19
|
+
"@vltpkg/pick-manifest": "1.0.0-rc.27",
|
|
20
|
+
"@vltpkg/registry-client": "1.0.0-rc.27",
|
|
21
|
+
"@vltpkg/spec": "1.0.0-rc.27",
|
|
22
|
+
"@vltpkg/tar": "1.0.0-rc.27",
|
|
23
|
+
"@vltpkg/types": "1.0.0-rc.27",
|
|
24
|
+
"@vltpkg/workspaces": "1.0.0-rc.27",
|
|
25
|
+
"@vltpkg/xdg": "1.0.0-rc.27",
|
|
26
26
|
"ssri": "^13.0.0",
|
|
27
27
|
"tar": "^7.5.2"
|
|
28
28
|
},
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"@types/node": "^22.19.2",
|
|
32
32
|
"@types/pacote": "^11.1.8",
|
|
33
33
|
"@vltpkg/benchmark": "0.0.0",
|
|
34
|
-
"@vltpkg/cache-unzip": "1.0.0-rc.
|
|
35
|
-
"@vltpkg/vlt-json": "1.0.0-rc.
|
|
34
|
+
"@vltpkg/cache-unzip": "1.0.0-rc.27",
|
|
35
|
+
"@vltpkg/vlt-json": "1.0.0-rc.27",
|
|
36
36
|
"eslint": "^9.39.1",
|
|
37
37
|
"pacote": "^21.0.4",
|
|
38
38
|
"prettier": "^3.7.4",
|