@vltpkg/types 1.2.0 → 1.3.1

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
@@ -29,6 +29,29 @@ export type Dist = {
29
29
  keyid: KeyID;
30
30
  sig: string;
31
31
  }[];
32
+ /**
33
+ * Alternate tarball formats the registry offers for this version. A
34
+ * `tar.br` entry is a Brotli-recompressed tar whose `tarball` is a
35
+ * reference relative to this `dist`'s `tarball` (e.g. the bare filename
36
+ * `foo-1.2.3.tar.br`), resolved with `new URL(entry.tarball, tarball)`.
37
+ *
38
+ * An alternate is a different artifact, not a re-encoding of the same
39
+ * bytes. It hashes differently from the `.tgz`, so `dist.integrity` does
40
+ * not describe it.
41
+ *
42
+ * The `integrity` field here is the alternate's own hash. It is optional,
43
+ * because a hash does not compress and a registry may prefer to keep
44
+ * packuments small. If a registry does send it, the client pins it like
45
+ * any other integrity, and can look the package up in the global store
46
+ * without downloading anything first. If it does not, the client verifies
47
+ * the download against the tarball response's RFC 9530 `Repr-Digest`
48
+ * header and learns the hash from there.
49
+ */
50
+ alternates?: {
51
+ kind: string;
52
+ tarball: string;
53
+ integrity?: Integrity;
54
+ }[];
32
55
  };
33
56
  /** An object used to mark some peerDeps as optional */
34
57
  export type PeerDependenciesMetaValue = {
@@ -299,6 +322,17 @@ export type Manifest = {
299
322
  contributors?: Person[];
300
323
  /** the license of the package */
301
324
  license?: string;
325
+ /**
326
+ * npm/yarn-style workspace globs. Honored by vlt as a fallback when
327
+ * `vlt.json` has no `workspaces` field of its own. Only the npm
328
+ * (`string[]`) and yarn-classic (`{packages}`) forms are accepted;
329
+ * vlt's named workspace groups are a `vlt.json` feature. yarn's
330
+ * `nohoist` is parsed and ignored.
331
+ */
332
+ workspaces?: string[] | {
333
+ packages?: string[] | string;
334
+ nohoist?: string[];
335
+ } | string;
302
336
  };
303
337
  export type NormalizedFields = {
304
338
  bugs: NormalizedBugs | undefined;
@@ -387,6 +421,55 @@ export declare const assertIntegrity: (i: unknown) => asserts i is Integrity;
387
421
  * sha512 integrity. Names tarball cache and global store entries.
388
422
  */
389
423
  export declare const integrityHex: (i: unknown) => string | undefined;
424
+ /**
425
+ * How a tarball's bytes are compressed. `gzip` is sniffed from the two
426
+ * magic bytes (the `.tgz` and raw-tar cases), so it never has to be
427
+ * passed. Brotli has no magic-byte signature, so a `.tar.br` MUST be
428
+ * declared by the caller -- it can never be detected from the bytes.
429
+ */
430
+ export type TarballFormat = 'gzip' | 'brotli';
431
+ /** Filename extension of a Brotli-recompressed tarball. */
432
+ export declare const BROTLI_TARBALL_EXT = ".tar.br";
433
+ /**
434
+ * The {@link TarballFormat} a tarball URL (or a registry-client cache key,
435
+ * which is the URL) names, or undefined when the bytes can be sniffed.
436
+ *
437
+ * The extension is the only signal there is: brotli bytes are opaque, and
438
+ * the response carries no `Content-Encoding` (a `.tar.br` is an artifact in
439
+ * its own right, not a transfer encoding of the `.tgz`). Query and fragment
440
+ * are ignored so a signed or cache-busted URL still reads correctly.
441
+ */
442
+ export declare const tarballFormat: (url: string) => TarballFormat | undefined;
443
+ /**
444
+ * `https://…/foo-1.2.3.tgz` -> `https://…/foo-1.2.3.tar.br`: the name a
445
+ * registry gives a version's Brotli alternate, same stem as the `.tgz`
446
+ * and a different extension.
447
+ */
448
+ export declare const brotliTarballName: (tgz: string) => string;
449
+ /**
450
+ * The absolute URL of a version's Brotli (`.tar.br`) tarball, from the
451
+ * `tar.br` entry in its `dist.alternates` -- but only when that entry
452
+ * resolves to the `.tgz`'s own sibling, i.e. {@link brotliTarballName} of
453
+ * `tarball`. Anything else reads as no alternate at all.
454
+ *
455
+ * `alternates[].tarball` is a reference relative to `dist.tarball`, and
456
+ * the protocol lets a registry point it anywhere. This client uses only
457
+ * the conventional name, because two things downstream re-derive it and
458
+ * both would otherwise be wrong: the format is read back off the URL
459
+ * suffix (brotli bytes carry no signature to sniff), and a lockfile node
460
+ * spends a single flag bit instead of a second URL, rebuilding the
461
+ * address from the `.tgz` by this same convention. Narrowing here, at
462
+ * the one point where the alternate is chosen, is what makes both of
463
+ * those derivations sound -- and costs an unusual reference the
464
+ * optimization rather than the install.
465
+ */
466
+ export type BrotliAlternate = {
467
+ /** The absolute URL of the `.tar.br`. */
468
+ tarball: string;
469
+ /** The alternate's own hash, if the registry sent one. */
470
+ integrity?: Integrity;
471
+ };
472
+ export declare const brotliTarballUrl: (tarball: string | undefined, alternates: Dist["alternates"]) => BrotliAlternate | undefined;
390
473
  export declare const keyIDRE: RegExp;
391
474
  export declare const isKeyID: (k: unknown) => k is KeyID;
392
475
  export declare const asKeyID: (k: unknown) => KeyID;
@@ -537,6 +620,14 @@ export type NodeLike = {
537
620
  integrity?: string | null;
538
621
  resolved?: string | null;
539
622
  resolvedFromLockfile?: boolean;
623
+ /**
624
+ * This node's artifact is the registry's Brotli (`.tar.br`) tarball
625
+ * rather than the gzip `.tgz`, so `resolved` and `integrity` both
626
+ * describe that artifact. Persisted in the lockfile as a flag bit, which
627
+ * is what lets `setResolved()` rebuild the `.tar.br` URL for a node whose
628
+ * `resolved` is not written out.
629
+ */
630
+ brotli?: boolean;
540
631
  importer: boolean;
541
632
  graph: GraphLike;
542
633
  mainImporter: boolean;
package/dist/index.js CHANGED
@@ -544,6 +544,54 @@ export const assertIntegrity = i => {
544
544
  export const integrityHex = (i) => isIntegrity(i) ?
545
545
  Buffer.from(i.slice(7), 'base64').toString('hex')
546
546
  : undefined;
547
+ /** Filename extension of a Brotli-recompressed tarball. */
548
+ export const BROTLI_TARBALL_EXT = '.tar.br';
549
+ /**
550
+ * The {@link TarballFormat} a tarball URL (or a registry-client cache key,
551
+ * which is the URL) names, or undefined when the bytes can be sniffed.
552
+ *
553
+ * The extension is the only signal there is: brotli bytes are opaque, and
554
+ * the response carries no `Content-Encoding` (a `.tar.br` is an artifact in
555
+ * its own right, not a transfer encoding of the `.tgz`). Query and fragment
556
+ * are ignored so a signed or cache-busted URL still reads correctly.
557
+ */
558
+ export const tarballFormat = (url) => {
559
+ // endsWith's second argument reads the string as if it ended there
560
+ const end = /[?#]/.exec(url)?.index ?? url.length;
561
+ return url.endsWith(BROTLI_TARBALL_EXT, end) ? 'brotli' : undefined;
562
+ };
563
+ /**
564
+ * `https://…/foo-1.2.3.tgz` -> `https://…/foo-1.2.3.tar.br`: the name a
565
+ * registry gives a version's Brotli alternate, same stem as the `.tgz`
566
+ * and a different extension.
567
+ */
568
+ export const brotliTarballName = (tgz) => tgz.replace(/\.tgz$/, BROTLI_TARBALL_EXT);
569
+ export const brotliTarballUrl = (tarball, alternates) => {
570
+ if (!tarball)
571
+ return undefined;
572
+ const entry = alternates?.find(a => a.kind === 'tar.br' && !!a.tarball);
573
+ if (!entry)
574
+ return undefined;
575
+ try {
576
+ const href = new URL(entry.tarball, tarball).href;
577
+ if (href !== new URL(brotliTarballName(tarball)).href) {
578
+ return undefined;
579
+ }
580
+ // Ignore a hash we cannot parse. Pinning it would fail every check,
581
+ // so it is better to fall back to the Repr-Digest.
582
+ return {
583
+ tarball: href,
584
+ ...(isIntegrity(entry.integrity) && {
585
+ integrity: entry.integrity,
586
+ }),
587
+ };
588
+ /* c8 ignore start - a malformed reference just means no brotli */
589
+ }
590
+ catch {
591
+ return undefined;
592
+ }
593
+ /* c8 ignore stop */
594
+ };
547
595
  export const keyIDRE = /^SHA256:[a-zA-Z0-9/+]{43}$/;
548
596
  export const isKeyID = (k) => typeof k === 'string' && keyIDRE.test(k);
549
597
  export const asKeyID = (k) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vltpkg/types",
3
3
  "description": "definitions for some of vlt's core types",
4
- "version": "1.2.0",
4
+ "version": "1.3.1",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/vltpkg/vltpkg.git",
@@ -13,10 +13,10 @@
13
13
  "url": "http://vlt.sh"
14
14
  },
15
15
  "dependencies": {
16
- "@vltpkg/dep-id": "1.2.0",
17
- "@vltpkg/error-cause": "1.2.0",
18
- "@vltpkg/semver": "1.2.0",
19
- "@vltpkg/spec": "1.2.0"
16
+ "@vltpkg/dep-id": "1.3.1",
17
+ "@vltpkg/error-cause": "1.3.1",
18
+ "@vltpkg/semver": "1.3.1",
19
+ "@vltpkg/spec": "1.3.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@eslint/js": "^9.39.1",