@vltpkg/package-info 1.2.0 → 1.3.0
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 +8 -2
- package/dist/index.js +58 -33
- package/package.json +14 -14
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,12 @@ export type PackageInfoClientOptions = RegistryClientOptions & SpecOptions & {
|
|
|
67
67
|
* tarball, anything else goes through the global store first.
|
|
68
68
|
*/
|
|
69
69
|
'store-linker'?: StoreLinker;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve to the registry's Brotli (`.tar.br`) tarball when it
|
|
72
|
+
* advertises one in `dist.alternates`. Defaults to true; set false to
|
|
73
|
+
* always resolve to the gzip `.tgz`.
|
|
74
|
+
*/
|
|
75
|
+
'brotli-tarballs'?: boolean;
|
|
70
76
|
};
|
|
71
77
|
export type PackageInfoClientRequestOptions = PickManifestOptions & RegistryClientRequestOptions & {
|
|
72
78
|
/** dir to resolve `file://` specifiers against. Defaults to projectRoot. */
|
|
@@ -84,8 +90,8 @@ export type PackageInfoClientExtractOptions = PackageInfoClientRequestOptions &
|
|
|
84
90
|
/**
|
|
85
91
|
* When true, indicates that integrity + resolved came from a
|
|
86
92
|
* lockfile (i.e. they were already verified on first install).
|
|
87
|
-
* Skips
|
|
88
|
-
*
|
|
93
|
+
* Skips re-hashing a refetched body of an already verified url.
|
|
94
|
+
* Other network bodies are always checked.
|
|
89
95
|
*/
|
|
90
96
|
fromLockfile?: boolean;
|
|
91
97
|
/**
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { pickManifest } from '@vltpkg/pick-manifest';
|
|
|
8
8
|
import { registryErrorMessage, tokenRefusalAdvice, } from '@vltpkg/registry-client/registry-error';
|
|
9
9
|
import { storeRoot } from '@vltpkg/registry-client/store-root';
|
|
10
10
|
import { Spec } from '@vltpkg/spec';
|
|
11
|
-
import { asPackument, integrityHex } from '@vltpkg/types';
|
|
11
|
+
import { asPackument, brotliTarballUrl, integrityHex, tarballFormat, } from '@vltpkg/types';
|
|
12
12
|
import ssri from 'ssri';
|
|
13
13
|
import { Monorepo } from '@vltpkg/workspaces';
|
|
14
14
|
import { XDG } from '@vltpkg/xdg';
|
|
@@ -178,6 +178,18 @@ export class PackageInfoClient {
|
|
|
178
178
|
});
|
|
179
179
|
return this.#tarPoolPromise;
|
|
180
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* The absolute URL of a version's Brotli (`.tar.br`) tarball, or
|
|
183
|
+
* undefined when the registry advertised none, advertised one this
|
|
184
|
+
* client does not use (see {@link brotliTarballUrl}), or
|
|
185
|
+
* `--no-brotli-tarballs` is set. `dist.tarball` is already absolute
|
|
186
|
+
* here -- see `absolutizeTarballs`.
|
|
187
|
+
*/
|
|
188
|
+
#brotliTarball(tarball, alternates) {
|
|
189
|
+
if (this.options['brotli-tarballs'] === false)
|
|
190
|
+
return undefined;
|
|
191
|
+
return brotliTarballUrl(tarball, alternates);
|
|
192
|
+
}
|
|
181
193
|
constructor(options = {}) {
|
|
182
194
|
this.options = options;
|
|
183
195
|
this.#projectRoot = options.projectRoot || process.cwd();
|
|
@@ -210,10 +222,19 @@ export class PackageInfoClient {
|
|
|
210
222
|
const { from = this.#projectRoot, integrity, resolved, fromLockfile = false, installScripts = false, } = options;
|
|
211
223
|
const f = spec.final;
|
|
212
224
|
// If the caller already provides both integrity and resolved
|
|
213
|
-
// (from lockfile or prior resolution), skip re-resolving.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
225
|
+
// (from lockfile or prior resolution), skip re-resolving. A
|
|
226
|
+
// `.tar.br` needs only the URL: its hash is not knowable before the
|
|
227
|
+
// download, so the graph can never hand one over, and `Repr-Digest`
|
|
228
|
+
// is what pins it -- `required`, so a registry that serves the
|
|
229
|
+
// alternate unlabelled is rejected rather than trusted.
|
|
230
|
+
const brotli = !!resolved && tarballFormat(resolved) === 'brotli';
|
|
231
|
+
const r = resolved && (integrity || brotli) ?
|
|
232
|
+
{
|
|
233
|
+
resolved,
|
|
234
|
+
integrity,
|
|
235
|
+
spec,
|
|
236
|
+
...(brotli && !integrity ? { digestRequired: true } : {}),
|
|
237
|
+
}
|
|
217
238
|
: await this.resolve(spec, options);
|
|
218
239
|
switch (f.type) {
|
|
219
240
|
case 'git': {
|
|
@@ -244,6 +265,9 @@ export class PackageInfoClient {
|
|
|
244
265
|
}
|
|
245
266
|
case 'registry': {
|
|
246
267
|
const pool = await this.getTarPool();
|
|
268
|
+
// brotli bytes carry no signature of their own, so every unpack
|
|
269
|
+
// below has to be told; gzip and raw tar are sniffed.
|
|
270
|
+
const format = tarballFormat(r.resolved);
|
|
247
271
|
// git tarballs keep the unpack path
|
|
248
272
|
const hex = f.type === 'registry' && this.#storeLinker !== 'unpack' ?
|
|
249
273
|
integrityHex(r.integrity)
|
|
@@ -281,12 +305,14 @@ export class PackageInfoClient {
|
|
|
281
305
|
});
|
|
282
306
|
if (cached) {
|
|
283
307
|
try {
|
|
284
|
-
await pool.unpack(cached.body, target);
|
|
308
|
+
await pool.unpack(cached.body, target, format);
|
|
285
309
|
logRequest(r.resolved, 'cache');
|
|
286
310
|
r.integrity ??= cached.integrity;
|
|
287
311
|
// a warm install writes nothing to the cache, so queue the
|
|
288
312
|
// store miss here or an existing cache never converges,
|
|
289
|
-
// and a gzipped body with no store link, to unzip it
|
|
313
|
+
// and a gzipped body with no store link, to unzip it.
|
|
314
|
+
// (a brotli body is never rewritten: cache-unzip only
|
|
315
|
+
// un-gzips, and leaving it compressed keeps the cache small.)
|
|
290
316
|
if (hex || cached.gzip) {
|
|
291
317
|
rc.queueForStore(cached.key, r.integrity);
|
|
292
318
|
}
|
|
@@ -316,24 +342,21 @@ export class PackageInfoClient {
|
|
|
316
342
|
response,
|
|
317
343
|
});
|
|
318
344
|
}
|
|
345
|
+
// checkIntegrity() hashes the body unless its url is trusted
|
|
346
|
+
const verified = !trustIntegrity &&
|
|
347
|
+
response.checkIntegrity({ spec, url: resolved });
|
|
319
348
|
// if it's not trusted already, but valid, start trusting
|
|
320
|
-
if (
|
|
321
|
-
response.checkIntegrity({ spec, url: resolved })) {
|
|
349
|
+
if (verified) {
|
|
322
350
|
this.#trustedIntegrities.set(r.resolved, response.integrity);
|
|
323
351
|
}
|
|
324
352
|
const buf = response.buffer();
|
|
325
353
|
if (r.integrity) {
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
// so the gzip-hash can never match. Skip lockfile-sourced
|
|
330
|
-
// integrity: it was verified on first install.
|
|
331
|
-
if (!fromLockfile && !response.fromCache) {
|
|
354
|
+
// hash only network bytes checkIntegrity() skipped (trusted
|
|
355
|
+
// url). cache bodies were verified when cached.
|
|
356
|
+
if (!verified && !fromLockfile && !response.fromCache) {
|
|
332
357
|
const hash = createHash('sha512');
|
|
333
358
|
hash.update(buf);
|
|
334
359
|
const computed = `sha512-${hash.digest('base64')}`;
|
|
335
|
-
/* c8 ignore start - defense-in-depth: registry client's
|
|
336
|
-
* checkIntegrity() usually catches mismatches first. */
|
|
337
360
|
if (computed !== r.integrity) {
|
|
338
361
|
throw error('Tarball integrity check failed', {
|
|
339
362
|
code: 'EINTEGRITY',
|
|
@@ -343,7 +366,6 @@ export class PackageInfoClient {
|
|
|
343
366
|
found: computed,
|
|
344
367
|
});
|
|
345
368
|
}
|
|
346
|
-
/* c8 ignore stop */
|
|
347
369
|
}
|
|
348
370
|
}
|
|
349
371
|
else if (response.fromCache) {
|
|
@@ -379,7 +401,7 @@ export class PackageInfoClient {
|
|
|
379
401
|
}
|
|
380
402
|
}
|
|
381
403
|
try {
|
|
382
|
-
await
|
|
404
|
+
await pool.unpack(buf, target, format);
|
|
383
405
|
}
|
|
384
406
|
catch (er) {
|
|
385
407
|
throw this.#resolveError(spec, options, 'tar unpack failed', { cause: er });
|
|
@@ -568,19 +590,19 @@ export class PackageInfoClient {
|
|
|
568
590
|
`version may have been unpublished, or the registry may ` +
|
|
569
591
|
`be misconfigured or unreachable.`, { response, url: tarball });
|
|
570
592
|
}
|
|
593
|
+
const verified = !trustIntegrity &&
|
|
594
|
+
response.checkIntegrity({ spec, url: tarball });
|
|
571
595
|
// if we don't already trust it, but it's valid, start
|
|
572
596
|
// trusting it
|
|
573
|
-
if (
|
|
574
|
-
response.checkIntegrity({ spec, url: tarball })) {
|
|
597
|
+
if (verified) {
|
|
575
598
|
this.#trustedIntegrities.set(tarball, response.integrity);
|
|
576
599
|
}
|
|
577
600
|
const buf = response.buffer();
|
|
578
|
-
// Same as extract()
|
|
579
|
-
if (integrity && !response.fromCache) {
|
|
601
|
+
// Same as extract()
|
|
602
|
+
if (integrity && !verified && !response.fromCache) {
|
|
580
603
|
const hash = createHash('sha512');
|
|
581
604
|
hash.update(buf);
|
|
582
605
|
const computed = `sha512-${hash.digest('base64')}`;
|
|
583
|
-
/* c8 ignore start - defense-in-depth (see extract) */
|
|
584
606
|
if (computed !== integrity) {
|
|
585
607
|
throw error('Tarball integrity check failed', {
|
|
586
608
|
code: 'EINTEGRITY',
|
|
@@ -590,7 +612,6 @@ export class PackageInfoClient {
|
|
|
590
612
|
found: computed,
|
|
591
613
|
});
|
|
592
614
|
}
|
|
593
|
-
/* c8 ignore stop */
|
|
594
615
|
}
|
|
595
616
|
return buf;
|
|
596
617
|
};
|
|
@@ -977,15 +998,19 @@ export class PackageInfoClient {
|
|
|
977
998
|
case 'registry': {
|
|
978
999
|
const mani = await this.manifest(spec, options);
|
|
979
1000
|
if (mani.dist) {
|
|
980
|
-
const { integrity, tarball, signatures } = mani.dist;
|
|
1001
|
+
const { integrity, tarball, signatures, alternates } = mani.dist;
|
|
981
1002
|
if (tarball) {
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1003
|
+
// A `.tar.br` is a distinct artifact, not a re-encoding of the
|
|
1004
|
+
// `.tgz`: it hashes differently, so `dist.integrity` and the
|
|
1005
|
+
// signatures over it describe the wrong bytes and are dropped.
|
|
1006
|
+
// What pins it instead is its own `Repr-Digest`, which the
|
|
1007
|
+
// registry that advertised it always sends -- hence `required`.
|
|
1008
|
+
const brotli = this.#brotliTarball(tarball, alternates);
|
|
1009
|
+
const r = brotli ?
|
|
1010
|
+
{ resolved: brotli, spec, digestRequired: true }
|
|
1011
|
+
: { resolved: tarball, integrity, signatures, spec };
|
|
1012
|
+
if (!brotli &&
|
|
1013
|
+
!integrity &&
|
|
989
1014
|
this.#vltPackuments.has(`${f.registry}${f.name}`)) {
|
|
990
1015
|
r.digestRequired = true;
|
|
991
1016
|
}
|
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.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
@@ -13,17 +13,17 @@
|
|
|
13
13
|
"url": "http://vlt.sh"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@vltpkg/error-cause": "1.
|
|
17
|
-
"@vltpkg/git": "1.
|
|
18
|
-
"@vltpkg/output": "1.
|
|
19
|
-
"@vltpkg/package-json": "1.
|
|
20
|
-
"@vltpkg/pick-manifest": "1.
|
|
21
|
-
"@vltpkg/registry-client": "1.
|
|
22
|
-
"@vltpkg/spec": "1.
|
|
23
|
-
"@vltpkg/tar": "1.
|
|
24
|
-
"@vltpkg/types": "1.
|
|
25
|
-
"@vltpkg/workspaces": "1.
|
|
26
|
-
"@vltpkg/xdg": "1.
|
|
16
|
+
"@vltpkg/error-cause": "1.3.0",
|
|
17
|
+
"@vltpkg/git": "1.3.0",
|
|
18
|
+
"@vltpkg/output": "1.3.0",
|
|
19
|
+
"@vltpkg/package-json": "1.3.0",
|
|
20
|
+
"@vltpkg/pick-manifest": "1.3.0",
|
|
21
|
+
"@vltpkg/registry-client": "1.3.0",
|
|
22
|
+
"@vltpkg/spec": "1.3.0",
|
|
23
|
+
"@vltpkg/tar": "1.3.0",
|
|
24
|
+
"@vltpkg/types": "1.3.0",
|
|
25
|
+
"@vltpkg/workspaces": "1.3.0",
|
|
26
|
+
"@vltpkg/xdg": "1.3.0",
|
|
27
27
|
"ssri": "^13.0.0",
|
|
28
28
|
"tar": "^7.5.2"
|
|
29
29
|
},
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"@types/node": "^22.19.2",
|
|
33
33
|
"@types/pacote": "^11.1.8",
|
|
34
34
|
"@vltpkg/benchmark": "0.0.0",
|
|
35
|
-
"@vltpkg/cache-unzip": "1.
|
|
36
|
-
"@vltpkg/vlt-json": "1.
|
|
35
|
+
"@vltpkg/cache-unzip": "1.3.0",
|
|
36
|
+
"@vltpkg/vlt-json": "1.3.0",
|
|
37
37
|
"eslint": "^9.39.1",
|
|
38
38
|
"pacote": "^21.0.4",
|
|
39
39
|
"prettier": "^3.7.4",
|