@vltpkg/package-info 1.0.9 → 1.1.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 +14 -0
- package/dist/index.js +80 -40
- package/package.json +14 -14
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,20 @@ import type { Pool } from '@vltpkg/tar';
|
|
|
7
7
|
import type { Integrity, Manifest, Packument } from '@vltpkg/types';
|
|
8
8
|
import { Monorepo } from '@vltpkg/workspaces';
|
|
9
9
|
export declare const delimiter = "~";
|
|
10
|
+
/**
|
|
11
|
+
* Accept header for packument requests. Prefers vlt's abbreviated
|
|
12
|
+
* packument and falls back to the full one on registries that do not
|
|
13
|
+
* know the type. See `PackageInfoClient.#fetchPackument`.
|
|
14
|
+
*
|
|
15
|
+
* The trailing wildcard range carries an explicit `q=0.1` so that it stays
|
|
16
|
+
* below `application/json`. A media range with no `q` defaults to `q=1.0`
|
|
17
|
+
* (RFC 9110 12.5.1), which on a registry that negotiates strictly by
|
|
18
|
+
* quality would let an unrelated representation — npm's corgi among them
|
|
19
|
+
* — outrank the full packument and drop `license`. The wildcard is kept
|
|
20
|
+
* only so a registry that rejects what it cannot satisfy exactly still
|
|
21
|
+
* has something to match.
|
|
22
|
+
*/
|
|
23
|
+
export declare const PACKUMENT_ACCEPT = "application/vnd.vlt.packument-v1+json; q=1.0, application/json; q=0.8, */*; q=0.1";
|
|
10
24
|
export type Resolution = {
|
|
11
25
|
resolved: string;
|
|
12
26
|
integrity?: Integrity;
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,9 @@ import { clone, resolve as gitResolve, revs } from '@vltpkg/git';
|
|
|
3
3
|
import { logRequest } from '@vltpkg/output';
|
|
4
4
|
import { PackageJson } from '@vltpkg/package-json';
|
|
5
5
|
import { pickManifest } from '@vltpkg/pick-manifest';
|
|
6
|
+
// subpath import: keeps the lazy `import('@vltpkg/registry-client')`
|
|
7
|
+
// below from becoming an eager dependency on the whole client.
|
|
8
|
+
import { registryErrorMessage } from '@vltpkg/registry-client/registry-error';
|
|
6
9
|
import { Spec } from '@vltpkg/spec';
|
|
7
10
|
import { asPackument } from '@vltpkg/types';
|
|
8
11
|
import ssri from 'ssri';
|
|
@@ -17,6 +20,20 @@ import { rename } from "./rename.js";
|
|
|
17
20
|
const debug = debuglog('vlt');
|
|
18
21
|
const xdg = new XDG('vlt');
|
|
19
22
|
export const delimiter = '~';
|
|
23
|
+
/**
|
|
24
|
+
* Accept header for packument requests. Prefers vlt's abbreviated
|
|
25
|
+
* packument and falls back to the full one on registries that do not
|
|
26
|
+
* know the type. See `PackageInfoClient.#fetchPackument`.
|
|
27
|
+
*
|
|
28
|
+
* The trailing wildcard range carries an explicit `q=0.1` so that it stays
|
|
29
|
+
* below `application/json`. A media range with no `q` defaults to `q=1.0`
|
|
30
|
+
* (RFC 9110 12.5.1), which on a registry that negotiates strictly by
|
|
31
|
+
* quality would let an unrelated representation — npm's corgi among them
|
|
32
|
+
* — outrank the full packument and drop `license`. The wildcard is kept
|
|
33
|
+
* only so a registry that rejects what it cannot satisfy exactly still
|
|
34
|
+
* has something to match.
|
|
35
|
+
*/
|
|
36
|
+
export const PACKUMENT_ACCEPT = 'application/vnd.vlt.packument-v1+json; q=1.0, application/json; q=0.8, */*; q=0.1';
|
|
20
37
|
// the maximum duration of a manifest cache file
|
|
21
38
|
const manifestCacheMaxAge = 5 * 60 * 1000;
|
|
22
39
|
/**
|
|
@@ -26,6 +43,15 @@ const manifestCacheMaxAge = 5 * 60 * 1000;
|
|
|
26
43
|
const noRegistryError = (spec) => error('No registry configured to resolve this spec. Set "registry" in ' +
|
|
27
44
|
'vlt.json, pass --registry, or run `vlt login --registry=<url>`. ' +
|
|
28
45
|
'See https://docs.vlt.sh/cli', { code: 'ECONFIG', spec });
|
|
46
|
+
/**
|
|
47
|
+
* A selector that can point at a different version tomorrow: a dist tag,
|
|
48
|
+
* or a range matching anything (`*`, empty string). Manifest results for
|
|
49
|
+
* these are not cached to disk, and packument requests for them force a
|
|
50
|
+
* revalidation of the registry client's cache entry.
|
|
51
|
+
*
|
|
52
|
+
* Takes a *final* spec (`spec.final`), same as `pickManifest` sees.
|
|
53
|
+
*/
|
|
54
|
+
const isMovingSelector = (f) => !!(f.distTag || f.range?.isAny);
|
|
29
55
|
export class PackageInfoClient {
|
|
30
56
|
#registryClient;
|
|
31
57
|
#projectRoot;
|
|
@@ -39,7 +65,9 @@ export class PackageInfoClient {
|
|
|
39
65
|
#cachePath;
|
|
40
66
|
// In-flight coalescing key is `${registry}${name}` — no representation
|
|
41
67
|
// component. Safe only because every caller requests the same full
|
|
42
|
-
// packument (see #fetchPackument).
|
|
68
|
+
// packument (see #fetchPackument). The one thing that does vary per
|
|
69
|
+
// caller is forceRevalidate, so record it and let a moving selector
|
|
70
|
+
// reuse a forced promise but never a non-forced one (see packument()).
|
|
43
71
|
#packumentPromises = new Map();
|
|
44
72
|
// unique temp file names for atomic manifest cache writes
|
|
45
73
|
#manifestWriteRandom = randomBytes(6).toString('hex');
|
|
@@ -159,7 +187,7 @@ export class PackageInfoClient {
|
|
|
159
187
|
...(useCache === false ? { useCache } : {}),
|
|
160
188
|
});
|
|
161
189
|
if (response.statusCode !== 200) {
|
|
162
|
-
throw this.#resolveError(spec, options,
|
|
190
|
+
throw this.#resolveError(spec, options, `${registryErrorMessage(response)} when ` +
|
|
163
191
|
`fetching the tarball for ${spec}. The resolved version ` +
|
|
164
192
|
`may have been unpublished, or the registry may be ` +
|
|
165
193
|
`misconfigured or unreachable.`, {
|
|
@@ -227,7 +255,7 @@ export class PackageInfoClient {
|
|
|
227
255
|
case 'remote': {
|
|
228
256
|
const response = await (await this.getRegistryClient()).request(r.resolved);
|
|
229
257
|
if (response.statusCode !== 200) {
|
|
230
|
-
throw this.#resolveError(spec, options,
|
|
258
|
+
throw this.#resolveError(spec, options, `failed to fetch remote tarball: ${registryErrorMessage(response)}`, {
|
|
231
259
|
url: r.resolved,
|
|
232
260
|
response,
|
|
233
261
|
});
|
|
@@ -333,11 +361,9 @@ export class PackageInfoClient {
|
|
|
333
361
|
if (options.before) {
|
|
334
362
|
return;
|
|
335
363
|
}
|
|
336
|
-
//
|
|
337
|
-
// matches any range (such as a semver range of `*` or empty string)
|
|
338
|
-
// then we skip caching
|
|
364
|
+
// a moving selector's result is variable, so don't cache it
|
|
339
365
|
const f = spec.final;
|
|
340
|
-
if (f
|
|
366
|
+
if (isMovingSelector(f)) {
|
|
341
367
|
return;
|
|
342
368
|
}
|
|
343
369
|
const key = this.#manifestCacheKey(f, options);
|
|
@@ -398,7 +424,7 @@ export class PackageInfoClient {
|
|
|
398
424
|
...(useCache === false ? { useCache } : {}),
|
|
399
425
|
});
|
|
400
426
|
if (response.statusCode !== 200) {
|
|
401
|
-
throw this.#resolveError(spec, options,
|
|
427
|
+
throw this.#resolveError(spec, options, `${registryErrorMessage(response)} when ` +
|
|
402
428
|
`fetching the tarball for ${spec} at ${tarball}. The ` +
|
|
403
429
|
`version may have been unpublished, or the registry may ` +
|
|
404
430
|
`be misconfigured or unreachable.`, { response, url: tarball });
|
|
@@ -478,7 +504,7 @@ export class PackageInfoClient {
|
|
|
478
504
|
}
|
|
479
505
|
const response = await (await this.getRegistryClient()).request(remoteURL);
|
|
480
506
|
if (response.statusCode !== 200) {
|
|
481
|
-
throw this.#resolveError(spec, options,
|
|
507
|
+
throw this.#resolveError(spec, options, `failed to fetch URL: ${registryErrorMessage(response)}`, { response, url: remoteURL });
|
|
482
508
|
}
|
|
483
509
|
return response.buffer();
|
|
484
510
|
}
|
|
@@ -577,7 +603,7 @@ export class PackageInfoClient {
|
|
|
577
603
|
return await this.#tmpdir(async (dir) => {
|
|
578
604
|
const response = await (await this.getRegistryClient()).request(remoteURL);
|
|
579
605
|
if (response.statusCode !== 200) {
|
|
580
|
-
throw this.#resolveError(s, options,
|
|
606
|
+
throw this.#resolveError(s, options, `failed to fetch URL: ${registryErrorMessage(response)}`, { response, url: remoteURL });
|
|
581
607
|
}
|
|
582
608
|
const buf = response.buffer();
|
|
583
609
|
// Compute integrity for remote/git-with-tarball deps
|
|
@@ -660,52 +686,66 @@ export class PackageInfoClient {
|
|
|
660
686
|
throw noRegistryError(spec);
|
|
661
687
|
// Coalescing key has no representation component (see #fetchPackument).
|
|
662
688
|
const packumentKey = `${registry}${name}`;
|
|
689
|
+
const forced = isMovingSelector(f);
|
|
663
690
|
const inflight = this.#packumentPromises.get(packumentKey);
|
|
664
|
-
|
|
665
|
-
|
|
691
|
+
// a moving selector must not ride along on a non-forced request:
|
|
692
|
+
// that one can settle to a fresh-but-stale cache hit, which is
|
|
693
|
+
// exactly what forceRevalidate exists to avoid. the other
|
|
694
|
+
// direction is fine -- a forced result is never staler.
|
|
695
|
+
// costs at most one extra concurrent GET for the same packument
|
|
696
|
+
// when both shapes are asked for at once.
|
|
697
|
+
if (inflight && (!forced || inflight.forced))
|
|
698
|
+
return inflight.promise;
|
|
666
699
|
const pakuURL = new URL(name, registry);
|
|
667
700
|
const promise = this.#fetchPackument(spec, options, pakuURL);
|
|
668
|
-
|
|
669
|
-
|
|
701
|
+
const record = { promise, forced };
|
|
702
|
+
this.#packumentPromises.set(packumentKey, record);
|
|
703
|
+
// Clean up once settled so we don't leak memory, unless a forced
|
|
704
|
+
// request has since taken the slot over.
|
|
670
705
|
// Use .then/.catch instead of .finally to avoid creating
|
|
671
706
|
// an unhandled rejection from the derived promise.
|
|
672
|
-
|
|
707
|
+
const clear = () => {
|
|
708
|
+
if (this.#packumentPromises.get(packumentKey) === record) {
|
|
709
|
+
this.#packumentPromises.delete(packumentKey);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
promise.then(clear, clear);
|
|
673
713
|
return promise;
|
|
674
714
|
}
|
|
675
715
|
}
|
|
676
716
|
}
|
|
677
717
|
async #fetchPackument(spec, options, pakuURL, useCache) {
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
// application/json; q=0.8, */*
|
|
682
|
-
//
|
|
683
|
-
// Corgi is smaller (~−41% packument bytes, est. 1–3s on clean installs)
|
|
684
|
-
// but is disabled for two load-bearing reasons:
|
|
685
|
-
//
|
|
686
|
-
// 1. Metadata loss reaches the lockfile. The version entry returned
|
|
687
|
-
// here is `node.manifest` and is persisted to vlt-lock.json / the
|
|
688
|
-
// hidden lockfile. Corgi drops `license` (also `time`, `readme`,
|
|
689
|
-
// `maintainers`, `_rev`, `scripts`), so graph queries like
|
|
690
|
-
// `[license=MPL-2.0]` cannot match a field that was never stored.
|
|
691
|
-
// Shipped in 1.0.0-rc.33 via #1692 (8ba2b10c); 24 false-positive
|
|
692
|
-
// edges in the dependency-check job on #1704
|
|
693
|
-
// (`@resvg/resvg-wasm@2.6.2` MPL-2.0 in package.json, blank in the
|
|
694
|
-
// stored graph); isolated in #1705; reverted in #1707.
|
|
718
|
+
// Request vlt's abbreviated packument, falling back to the full one:
|
|
719
|
+
// accept: application/vnd.vlt.packument-v1+json; q=1.0,
|
|
720
|
+
// application/json; q=0.8, */*; q=0.1
|
|
695
721
|
//
|
|
696
|
-
//
|
|
697
|
-
//
|
|
698
|
-
//
|
|
722
|
+
// npm's corgi (`application/vnd.npm.install-v1+json`) is never
|
|
723
|
+
// requested. The version entry returned here becomes `node.manifest`
|
|
724
|
+
// and is persisted to the hidden lockfile, and corgi drops `license`
|
|
725
|
+
// and `time`, so graph queries like `[license=MPL-2.0]` could not
|
|
726
|
+
// match a field that was never stored (shipped in 1.0.0-rc.33 via
|
|
727
|
+
// #1692, reverted in #1707). The vlt type guarantees both, and a
|
|
728
|
+
// registry that does not know it ignores it and serves the full
|
|
729
|
+
// packument, so the graph gets every field it relies on either way.
|
|
730
|
+
// The one shape difference is `scripts`, which the vlt type replaces
|
|
731
|
+
// with `hasInstallScript`; reify reads the extracted package.json in
|
|
732
|
+
// that case.
|
|
699
733
|
//
|
|
700
|
-
//
|
|
701
|
-
//
|
|
702
|
-
//
|
|
734
|
+
// The RegistryClient disk cache key is method + URL only, so every
|
|
735
|
+
// packument request must use this same accept header, and the SWR
|
|
736
|
+
// revalidation child re-requests the representation it was given.
|
|
703
737
|
const response = await (await this.getRegistryClient()).request(pakuURL, {
|
|
704
|
-
headers: { accept:
|
|
738
|
+
headers: { accept: PACKUMENT_ACCEPT },
|
|
705
739
|
...(useCache === false ? { useCache } : {}),
|
|
740
|
+
// costs a conditional GET per moving selector on an otherwise warm
|
|
741
|
+
// cache, install included. 304s are cheap but not free; the
|
|
742
|
+
// alternative is serving a dist tag that moved (#1656).
|
|
743
|
+
...(isMovingSelector(spec.final) ?
|
|
744
|
+
{ forceRevalidate: true }
|
|
745
|
+
: {}),
|
|
706
746
|
});
|
|
707
747
|
if (response.statusCode !== 200) {
|
|
708
|
-
throw this.#resolveError(spec, options,
|
|
748
|
+
throw this.#resolveError(spec, options, `failed to fetch packument: ${registryErrorMessage(response)}`, {
|
|
709
749
|
url: pakuURL,
|
|
710
750
|
response,
|
|
711
751
|
});
|
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
|
|
4
|
+
"version": "1.1.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.0
|
|
17
|
-
"@vltpkg/git": "1.0
|
|
18
|
-
"@vltpkg/output": "1.0
|
|
19
|
-
"@vltpkg/package-json": "1.0
|
|
20
|
-
"@vltpkg/pick-manifest": "1.0
|
|
21
|
-
"@vltpkg/registry-client": "1.0
|
|
22
|
-
"@vltpkg/spec": "1.0
|
|
23
|
-
"@vltpkg/tar": "1.0
|
|
24
|
-
"@vltpkg/types": "1.0
|
|
25
|
-
"@vltpkg/workspaces": "1.0
|
|
26
|
-
"@vltpkg/xdg": "1.0
|
|
16
|
+
"@vltpkg/error-cause": "1.1.0",
|
|
17
|
+
"@vltpkg/git": "1.1.0",
|
|
18
|
+
"@vltpkg/output": "1.1.0",
|
|
19
|
+
"@vltpkg/package-json": "1.1.0",
|
|
20
|
+
"@vltpkg/pick-manifest": "1.1.0",
|
|
21
|
+
"@vltpkg/registry-client": "1.1.0",
|
|
22
|
+
"@vltpkg/spec": "1.1.0",
|
|
23
|
+
"@vltpkg/tar": "1.1.0",
|
|
24
|
+
"@vltpkg/types": "1.1.0",
|
|
25
|
+
"@vltpkg/workspaces": "1.1.0",
|
|
26
|
+
"@vltpkg/xdg": "1.1.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.0
|
|
36
|
-
"@vltpkg/vlt-json": "1.0
|
|
35
|
+
"@vltpkg/cache-unzip": "1.1.0",
|
|
36
|
+
"@vltpkg/vlt-json": "1.1.0",
|
|
37
37
|
"eslint": "^9.39.1",
|
|
38
38
|
"pacote": "^21.0.4",
|
|
39
39
|
"prettier": "^3.7.4",
|