@vltpkg/package-info 1.0.0-rc.25 → 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 +118 -40
- package/package.json +15 -14
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,11 +5,11 @@ 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';
|
|
12
|
-
import { randomBytes } from 'node:crypto';
|
|
12
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
13
13
|
import { mkdir, readFile, rm, stat, symlink, unlink, writeFile, } from 'node:fs/promises';
|
|
14
14
|
import { basename, dirname, resolve as pathResolve, relative, } from 'node:path';
|
|
15
15
|
import { create as tarC } from 'tar';
|
|
@@ -65,9 +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
|
-
|
|
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 ?
|
|
71
74
|
{ resolved, integrity, spec }
|
|
72
75
|
: await this.resolve(spec, options);
|
|
73
76
|
switch (f.type) {
|
|
@@ -98,24 +101,73 @@ export class PackageInfoClient {
|
|
|
98
101
|
// fallthrough if a remote tarball url present
|
|
99
102
|
}
|
|
100
103
|
case 'registry': {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
throw this.#resolveError(spec, options, 'failed to fetch tarball', {
|
|
108
|
-
url: r.resolved,
|
|
109
|
-
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 } : {}),
|
|
110
110
|
});
|
|
111
|
+
if (response.statusCode !== 200) {
|
|
112
|
+
throw this.#resolveError(spec, options, 'failed to fetch tarball', {
|
|
113
|
+
url: r.resolved,
|
|
114
|
+
response,
|
|
115
|
+
});
|
|
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();
|
|
111
153
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
}
|
|
116
168
|
}
|
|
117
169
|
try {
|
|
118
|
-
await this.tarPool.unpack(
|
|
170
|
+
await this.tarPool.unpack(buf, target);
|
|
119
171
|
}
|
|
120
172
|
catch (er) {
|
|
121
173
|
throw this.#resolveError(spec, options, 'tar unpack failed', { cause: er });
|
|
@@ -278,21 +330,56 @@ export class PackageInfoClient {
|
|
|
278
330
|
if (!tarball) {
|
|
279
331
|
throw this.#resolveError(spec, options, 'no tarball found in manifest.dist');
|
|
280
332
|
}
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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();
|
|
289
373
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
374
|
+
catch (er) {
|
|
375
|
+
if (er instanceof Error &&
|
|
376
|
+
'cause' in er &&
|
|
377
|
+
er.cause
|
|
378
|
+
?.code === 'EINTEGRITY') {
|
|
379
|
+
return await fetchTarball(false);
|
|
380
|
+
}
|
|
381
|
+
throw er;
|
|
294
382
|
}
|
|
295
|
-
return response.buffer();
|
|
296
383
|
}
|
|
297
384
|
case 'git': {
|
|
298
385
|
const { remoteURL, gitRemote, gitCommittish, gitSelectorParsed, } = f;
|
|
@@ -392,15 +479,6 @@ export class PackageInfoClient {
|
|
|
392
479
|
: pickManifest(await this.packument(f, options), spec, options);
|
|
393
480
|
if (!mani)
|
|
394
481
|
throw this.#resolveError(spec, options);
|
|
395
|
-
const { integrity, tarball } = mani.dist ?? /* c8 ignore next */ {};
|
|
396
|
-
if (isIntegrity(integrity) && tarball) {
|
|
397
|
-
const registryOrigin = new URL(String(f.registry)).origin;
|
|
398
|
-
const tgzOrigin = new URL(tarball).origin;
|
|
399
|
-
// if it comes from the same origin, trust the integrity
|
|
400
|
-
if (tgzOrigin === registryOrigin) {
|
|
401
|
-
this.#trustedIntegrities.set(tarball, integrity);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
482
|
// Cache the manifest data
|
|
405
483
|
if (cachePath) {
|
|
406
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",
|
|
@@ -9,19 +9,20 @@
|
|
|
9
9
|
},
|
|
10
10
|
"author": {
|
|
11
11
|
"name": "vlt technology inc.",
|
|
12
|
-
"email": "support@vlt.sh"
|
|
12
|
+
"email": "support@vlt.sh",
|
|
13
|
+
"url": "http://vlt.sh"
|
|
13
14
|
},
|
|
14
15
|
"dependencies": {
|
|
15
|
-
"@vltpkg/error-cause": "1.0.0-rc.
|
|
16
|
-
"@vltpkg/git": "1.0.0-rc.
|
|
17
|
-
"@vltpkg/package-json": "1.0.0-rc.
|
|
18
|
-
"@vltpkg/pick-manifest": "1.0.0-rc.
|
|
19
|
-
"@vltpkg/registry-client": "1.0.0-rc.
|
|
20
|
-
"@vltpkg/spec": "1.0.0-rc.
|
|
21
|
-
"@vltpkg/tar": "1.0.0-rc.
|
|
22
|
-
"@vltpkg/types": "1.0.0-rc.
|
|
23
|
-
"@vltpkg/workspaces": "1.0.0-rc.
|
|
24
|
-
"@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",
|
|
25
26
|
"ssri": "^13.0.0",
|
|
26
27
|
"tar": "^7.5.2"
|
|
27
28
|
},
|
|
@@ -30,8 +31,8 @@
|
|
|
30
31
|
"@types/node": "^22.19.2",
|
|
31
32
|
"@types/pacote": "^11.1.8",
|
|
32
33
|
"@vltpkg/benchmark": "0.0.0",
|
|
33
|
-
"@vltpkg/cache-unzip": "1.0.0-rc.
|
|
34
|
-
"@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",
|
|
35
36
|
"eslint": "^9.39.1",
|
|
36
37
|
"pacote": "^21.0.4",
|
|
37
38
|
"prettier": "^3.7.4",
|