@vltpkg/package-info 1.0.0-rc.32 → 1.0.0-rc.33

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 CHANGED
@@ -28,6 +28,15 @@ export type PackageInfoClientOptions = RegistryClientOptions & SpecOptions & {
28
28
  export type PackageInfoClientRequestOptions = PickManifestOptions & RegistryClientRequestOptions & {
29
29
  /** dir to resolve `file://` specifiers against. Defaults to projectRoot. */
30
30
  from?: string;
31
+ /**
32
+ * When true, requests the full packument document from the registry
33
+ * instead of the abbreviated "corgi" format. Use this when you need
34
+ * fields like `time`, `maintainers`, or `_rev` that are stripped
35
+ * from the abbreviated response.
36
+ *
37
+ * Defaults to `false` (abbreviated packument for faster installs).
38
+ */
39
+ fullPackument?: boolean;
31
40
  };
32
41
  export type PackageInfoClientExtractOptions = PackageInfoClientRequestOptions & {
33
42
  integrity?: Integrity;
package/dist/index.js CHANGED
@@ -18,6 +18,13 @@ const xdg = new XDG('vlt');
18
18
  export const delimiter = '~';
19
19
  // the maximum duration of a manifest cache file
20
20
  const manifestCacheMaxAge = 5 * 60 * 1000;
21
+ /**
22
+ * There is no default registry. Anything that needs to build a registry
23
+ * URL fails with a `ECONFIG` error when none has been configured.
24
+ */
25
+ const noRegistryError = (spec) => error('No registry configured to resolve this spec. Set "registry" in ' +
26
+ 'vlt.json, pass --registry, or run `vlt login --registry=<url>`. ' +
27
+ 'See https://docs.vlt.sh/cli', { code: 'ECONFIG', spec });
21
28
  export class PackageInfoClient {
22
29
  #registryClient;
23
30
  #projectRoot;
@@ -29,6 +36,7 @@ export class PackageInfoClient {
29
36
  #trustedIntegrities = new Map();
30
37
  #manifestCacheMinAge = Date.now() - manifestCacheMaxAge;
31
38
  #cachePath;
39
+ #packumentPromises = new Map();
32
40
  get registryClient() {
33
41
  if (!this.#registryClient) {
34
42
  this.#registryClient = new RegistryClient(this.options);
@@ -109,7 +117,10 @@ export class PackageInfoClient {
109
117
  ...(useCache === false ? { useCache } : {}),
110
118
  });
111
119
  if (response.statusCode !== 200) {
112
- throw this.#resolveError(spec, options, 'failed to fetch tarball', {
120
+ throw this.#resolveError(spec, options, `Registry returned HTTP ${response.statusCode} when ` +
121
+ `fetching the tarball for ${spec}. The resolved version ` +
122
+ `may have been unpublished, or the registry may be ` +
123
+ `misconfigured or unreachable.`, {
113
124
  url: r.resolved,
114
125
  response,
115
126
  });
@@ -272,7 +283,7 @@ export class PackageInfoClient {
272
283
  if (options.arch) {
273
284
  extra += `${delimiter}arch:${options.arch}`;
274
285
  }
275
- return encodeURIComponent(`${spec.registry}${delimiter}${spec}${extra}`);
286
+ return encodeURIComponent(`${spec.registry ?? ''}${delimiter}${spec}${extra}`);
276
287
  }
277
288
  /**
278
289
  * Conditionally return the path to the manifest cache file. The logic
@@ -293,30 +304,6 @@ export class PackageInfoClient {
293
304
  const key = this.#manifestCacheKey(f, options);
294
305
  return pathResolve(this.#cachePath, 'package-info', key);
295
306
  }
296
- async #registryManifestRequest(spec, options) {
297
- const { registry, name, registrySpec } = spec.final;
298
- /* c8 ignore start */
299
- if (!spec.range?.isSingle || !registrySpec) {
300
- throw this.#resolveError(spec, options, 'failed to request manifest', { spec });
301
- }
302
- /* c8 ignore stop */
303
- const possibleLeadingChars = ['=', '^', '~', 'v'];
304
- const hasLeadingRange = possibleLeadingChars.some(char => registrySpec.startsWith(char));
305
- const version = hasLeadingRange ? registrySpec.slice(1) : registrySpec;
306
- const pakuURL = new URL(`${name}/${version}`, registry);
307
- const response = await this.registryClient.request(pakuURL, {
308
- headers: {
309
- accept: 'application/json',
310
- },
311
- });
312
- if (response.statusCode !== 200) {
313
- throw this.#resolveError(spec, options, 'failed to fetch manifest', {
314
- url: pakuURL,
315
- response,
316
- });
317
- }
318
- return response.json();
319
- }
320
307
  async tarball(spec, options = {}) {
321
308
  if (typeof spec === 'string')
322
309
  spec = Spec.parse(spec, this.options);
@@ -325,10 +312,14 @@ export class PackageInfoClient {
325
312
  case 'registry': {
326
313
  const { dist } = await this.manifest(spec, options);
327
314
  if (!dist)
328
- throw this.#resolveError(spec, options, 'no dist object found in manifest');
315
+ throw this.#resolveError(spec, options, `The registry manifest for ${spec} has no "dist" section, ` +
316
+ `so no tarball can be resolved. The package version may be ` +
317
+ `malformed or unpublished.`);
329
318
  const { tarball, integrity } = dist;
330
319
  if (!tarball) {
331
- throw this.#resolveError(spec, options, 'no tarball found in manifest.dist');
320
+ throw this.#resolveError(spec, options, `The registry manifest for ${spec} is missing a tarball URL ` +
321
+ `(dist.tarball). The package version may be malformed or ` +
322
+ `unpublished in this registry.`);
332
323
  }
333
324
  const fetchTarball = async (useCache) => {
334
325
  const trustIntegrity = this.#trustedIntegrities.get(tarball) === integrity;
@@ -339,7 +330,10 @@ export class PackageInfoClient {
339
330
  ...(useCache === false ? { useCache } : {}),
340
331
  });
341
332
  if (response.statusCode !== 200) {
342
- throw this.#resolveError(spec, options, 'failed to fetch tarball', { response, url: tarball });
333
+ throw this.#resolveError(spec, options, `Registry returned HTTP ${response.statusCode} when ` +
334
+ `fetching the tarball for ${spec} at ${tarball}. The ` +
335
+ `version may have been unpublished, or the registry may ` +
336
+ `be misconfigured or unreachable.`, { response, url: tarball });
343
337
  }
344
338
  // if we don't already trust it, but it's valid, start
345
339
  // trusting it
@@ -474,9 +468,7 @@ export class PackageInfoClient {
474
468
  // Cache miss, fetch from packument
475
469
  }
476
470
  }
477
- const mani = spec.range?.isSingle ?
478
- await this.#registryManifestRequest(spec, options)
479
- : pickManifest(await this.packument(f, options), spec, options);
471
+ const mani = pickManifest(await this.packument(f, options), spec, options);
480
472
  if (!mani)
481
473
  throw this.#resolveError(spec, options);
482
474
  // Cache the manifest data
@@ -606,22 +598,44 @@ export class PackageInfoClient {
606
598
  }
607
599
  case 'registry': {
608
600
  const { registry, name } = f;
609
- const pakuURL = new URL(name, registry);
610
- const response = await this.registryClient.request(pakuURL, {
611
- headers: {
612
- accept: 'application/json',
613
- },
614
- });
615
- if (response.statusCode !== 200) {
616
- throw this.#resolveError(spec, options, 'failed to fetch packument', {
617
- url: pakuURL,
618
- response,
619
- });
601
+ if (!registry)
602
+ throw noRegistryError(spec);
603
+ // Full packument requests bypass coalescing since they
604
+ // return different data than the abbreviated format.
605
+ if (options.fullPackument) {
606
+ const pakuURL = new URL(name, registry);
607
+ return this.#fetchPackument(spec, options, pakuURL);
620
608
  }
621
- return response.json();
609
+ const packumentKey = `${registry}${name}`;
610
+ const inflight = this.#packumentPromises.get(packumentKey);
611
+ if (inflight)
612
+ return inflight;
613
+ const pakuURL = new URL(name, registry);
614
+ const promise = this.#fetchPackument(spec, options, pakuURL);
615
+ this.#packumentPromises.set(packumentKey, promise);
616
+ // Clean up once settled so we don't leak memory.
617
+ // Use .then/.catch instead of .finally to avoid creating
618
+ // an unhandled rejection from the derived promise.
619
+ promise.then(() => this.#packumentPromises.delete(packumentKey), () => this.#packumentPromises.delete(packumentKey));
620
+ return promise;
622
621
  }
623
622
  }
624
623
  }
624
+ async #fetchPackument(spec, options, pakuURL) {
625
+ const accept = options.fullPackument ?
626
+ 'application/json'
627
+ : 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*';
628
+ const response = await this.registryClient.request(pakuURL, {
629
+ headers: { accept },
630
+ });
631
+ if (response.statusCode !== 200) {
632
+ throw this.#resolveError(spec, options, 'failed to fetch packument', {
633
+ url: pakuURL,
634
+ response,
635
+ });
636
+ }
637
+ return response.json();
638
+ }
625
639
  async resolve(spec, options = {}) {
626
640
  const memoKey = String(spec);
627
641
  if (typeof spec === 'string')
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.32",
4
+ "version": "1.0.0-rc.33",
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.32",
17
- "@vltpkg/git": "1.0.0-rc.32",
18
- "@vltpkg/package-json": "1.0.0-rc.32",
19
- "@vltpkg/pick-manifest": "1.0.0-rc.32",
20
- "@vltpkg/registry-client": "1.0.0-rc.32",
21
- "@vltpkg/spec": "1.0.0-rc.32",
22
- "@vltpkg/tar": "1.0.0-rc.32",
23
- "@vltpkg/types": "1.0.0-rc.32",
24
- "@vltpkg/workspaces": "1.0.0-rc.32",
25
- "@vltpkg/xdg": "1.0.0-rc.32",
16
+ "@vltpkg/error-cause": "1.0.0-rc.33",
17
+ "@vltpkg/git": "1.0.0-rc.33",
18
+ "@vltpkg/package-json": "1.0.0-rc.33",
19
+ "@vltpkg/pick-manifest": "1.0.0-rc.33",
20
+ "@vltpkg/registry-client": "1.0.0-rc.33",
21
+ "@vltpkg/spec": "1.0.0-rc.33",
22
+ "@vltpkg/tar": "1.0.0-rc.33",
23
+ "@vltpkg/types": "1.0.0-rc.33",
24
+ "@vltpkg/workspaces": "1.0.0-rc.33",
25
+ "@vltpkg/xdg": "1.0.0-rc.33",
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.32",
35
- "@vltpkg/vlt-json": "1.0.0-rc.32",
34
+ "@vltpkg/cache-unzip": "1.0.0-rc.33",
35
+ "@vltpkg/vlt-json": "1.0.0-rc.33",
36
36
  "eslint": "^9.39.1",
37
37
  "pacote": "^21.0.4",
38
38
  "prettier": "^3.7.4",
@@ -73,5 +73,12 @@
73
73
  },
74
74
  "files": [
75
75
  "dist"
76
- ]
76
+ ],
77
+ "bugs": [
78
+ {
79
+ "type": "link",
80
+ "url": "https://github.com/vltpkg/vltpkg/issues"
81
+ }
82
+ ],
83
+ "homepage": "https://github.com/vltpkg/vltpkg/tree/main/src/package-info#readme"
77
84
  }