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

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.
Files changed (2) hide show
  1. package/dist/index.js +49 -44
  2. package/package.json +21 -14
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,35 @@ export class PackageInfoClient {
606
598
  }
607
599
  case 'registry': {
608
600
  const { registry, name } = f;
601
+ if (!registry)
602
+ throw noRegistryError(spec);
603
+ const packumentKey = `${registry}${name}`;
604
+ const inflight = this.#packumentPromises.get(packumentKey);
605
+ if (inflight)
606
+ return inflight;
609
607
  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
- });
620
- }
621
- return response.json();
608
+ const promise = this.#fetchPackument(spec, options, pakuURL);
609
+ this.#packumentPromises.set(packumentKey, promise);
610
+ // Clean up once settled so we don't leak memory.
611
+ // Use .then/.catch instead of .finally to avoid creating
612
+ // an unhandled rejection from the derived promise.
613
+ promise.then(() => this.#packumentPromises.delete(packumentKey), () => this.#packumentPromises.delete(packumentKey));
614
+ return promise;
622
615
  }
623
616
  }
624
617
  }
618
+ async #fetchPackument(spec, options, pakuURL) {
619
+ const response = await this.registryClient.request(pakuURL, {
620
+ headers: { accept: 'application/json' },
621
+ });
622
+ if (response.statusCode !== 200) {
623
+ throw this.#resolveError(spec, options, 'failed to fetch packument', {
624
+ url: pakuURL,
625
+ response,
626
+ });
627
+ }
628
+ return response.json();
629
+ }
625
630
  async resolve(spec, options = {}) {
626
631
  const memoKey = String(spec);
627
632
  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.34",
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.34",
17
+ "@vltpkg/git": "1.0.0-rc.34",
18
+ "@vltpkg/package-json": "1.0.0-rc.34",
19
+ "@vltpkg/pick-manifest": "1.0.0-rc.34",
20
+ "@vltpkg/registry-client": "1.0.0-rc.34",
21
+ "@vltpkg/spec": "1.0.0-rc.34",
22
+ "@vltpkg/tar": "1.0.0-rc.34",
23
+ "@vltpkg/types": "1.0.0-rc.34",
24
+ "@vltpkg/workspaces": "1.0.0-rc.34",
25
+ "@vltpkg/xdg": "1.0.0-rc.34",
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.34",
35
+ "@vltpkg/vlt-json": "1.0.0-rc.34",
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
  }