@vltpkg/types 1.2.0 → 1.3.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 CHANGED
@@ -29,6 +29,21 @@ 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 different encoding of the
39
+ * same bytes: it carries no `integrity` here and hashes differently from
40
+ * the `.tgz`. A client that downloads one pins that artifact's own hash,
41
+ * taken from the tarball response's RFC 9530 `Repr-Digest`.
42
+ */
43
+ alternates?: {
44
+ kind: string;
45
+ tarball: string;
46
+ }[];
32
47
  };
33
48
  /** An object used to mark some peerDeps as optional */
34
49
  export type PeerDependenciesMetaValue = {
@@ -299,6 +314,17 @@ export type Manifest = {
299
314
  contributors?: Person[];
300
315
  /** the license of the package */
301
316
  license?: string;
317
+ /**
318
+ * npm/yarn-style workspace globs. Honored by vlt as a fallback when
319
+ * `vlt.json` has no `workspaces` field of its own. Only the npm
320
+ * (`string[]`) and yarn-classic (`{packages}`) forms are accepted;
321
+ * vlt's named workspace groups are a `vlt.json` feature. yarn's
322
+ * `nohoist` is parsed and ignored.
323
+ */
324
+ workspaces?: string[] | {
325
+ packages?: string[] | string;
326
+ nohoist?: string[];
327
+ } | string;
302
328
  };
303
329
  export type NormalizedFields = {
304
330
  bugs: NormalizedBugs | undefined;
@@ -387,6 +413,49 @@ export declare const assertIntegrity: (i: unknown) => asserts i is Integrity;
387
413
  * sha512 integrity. Names tarball cache and global store entries.
388
414
  */
389
415
  export declare const integrityHex: (i: unknown) => string | undefined;
416
+ /**
417
+ * How a tarball's bytes are compressed. `gzip` is sniffed from the two
418
+ * magic bytes (the `.tgz` and raw-tar cases), so it never has to be
419
+ * passed. Brotli has no magic-byte signature, so a `.tar.br` MUST be
420
+ * declared by the caller -- it can never be detected from the bytes.
421
+ */
422
+ export type TarballFormat = 'gzip' | 'brotli';
423
+ /** Filename extension of a Brotli-recompressed tarball. */
424
+ export declare const BROTLI_TARBALL_EXT = ".tar.br";
425
+ /**
426
+ * The {@link TarballFormat} a tarball URL (or a registry-client cache key,
427
+ * which is the URL) names, or undefined when the bytes can be sniffed.
428
+ *
429
+ * The extension is the only signal there is: brotli bytes are opaque, and
430
+ * the response carries no `Content-Encoding` (a `.tar.br` is an artifact in
431
+ * its own right, not a transfer encoding of the `.tgz`). Query and fragment
432
+ * are ignored so a signed or cache-busted URL still reads correctly.
433
+ */
434
+ export declare const tarballFormat: (url: string) => TarballFormat | undefined;
435
+ /**
436
+ * `https://…/foo-1.2.3.tgz` -> `https://…/foo-1.2.3.tar.br`: the name a
437
+ * registry gives a version's Brotli alternate, same stem as the `.tgz`
438
+ * and a different extension.
439
+ */
440
+ export declare const brotliTarballName: (tgz: string) => string;
441
+ /**
442
+ * The absolute URL of a version's Brotli (`.tar.br`) tarball, from the
443
+ * `tar.br` entry in its `dist.alternates` -- but only when that entry
444
+ * resolves to the `.tgz`'s own sibling, i.e. {@link brotliTarballName} of
445
+ * `tarball`. Anything else reads as no alternate at all.
446
+ *
447
+ * `alternates[].tarball` is a reference relative to `dist.tarball`, and
448
+ * the protocol lets a registry point it anywhere. This client uses only
449
+ * the conventional name, because two things downstream re-derive it and
450
+ * both would otherwise be wrong: the format is read back off the URL
451
+ * suffix (brotli bytes carry no signature to sniff), and a lockfile node
452
+ * spends a single flag bit instead of a second URL, rebuilding the
453
+ * address from the `.tgz` by this same convention. Narrowing here, at
454
+ * the one point where the alternate is chosen, is what makes both of
455
+ * those derivations sound -- and costs an unusual reference the
456
+ * optimization rather than the install.
457
+ */
458
+ export declare const brotliTarballUrl: (tarball: string | undefined, alternates: Dist["alternates"]) => string | undefined;
390
459
  export declare const keyIDRE: RegExp;
391
460
  export declare const isKeyID: (k: unknown) => k is KeyID;
392
461
  export declare const asKeyID: (k: unknown) => KeyID;
@@ -537,6 +606,14 @@ export type NodeLike = {
537
606
  integrity?: string | null;
538
607
  resolved?: string | null;
539
608
  resolvedFromLockfile?: boolean;
609
+ /**
610
+ * This node's artifact is the registry's Brotli (`.tar.br`) tarball
611
+ * rather than the gzip `.tgz`, so `resolved` and `integrity` both
612
+ * describe that artifact. Persisted in the lockfile as a flag bit, which
613
+ * is what lets `setResolved()` rebuild the `.tar.br` URL for a node whose
614
+ * `resolved` is not written out.
615
+ */
616
+ brotli?: boolean;
540
617
  importer: boolean;
541
618
  graph: GraphLike;
542
619
  mainImporter: boolean;
package/dist/index.js CHANGED
@@ -544,6 +544,63 @@ 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
+ /**
570
+ * The absolute URL of a version's Brotli (`.tar.br`) tarball, from the
571
+ * `tar.br` entry in its `dist.alternates` -- but only when that entry
572
+ * resolves to the `.tgz`'s own sibling, i.e. {@link brotliTarballName} of
573
+ * `tarball`. Anything else reads as no alternate at all.
574
+ *
575
+ * `alternates[].tarball` is a reference relative to `dist.tarball`, and
576
+ * the protocol lets a registry point it anywhere. This client uses only
577
+ * the conventional name, because two things downstream re-derive it and
578
+ * both would otherwise be wrong: the format is read back off the URL
579
+ * suffix (brotli bytes carry no signature to sniff), and a lockfile node
580
+ * spends a single flag bit instead of a second URL, rebuilding the
581
+ * address from the `.tgz` by this same convention. Narrowing here, at
582
+ * the one point where the alternate is chosen, is what makes both of
583
+ * those derivations sound -- and costs an unusual reference the
584
+ * optimization rather than the install.
585
+ */
586
+ export const brotliTarballUrl = (tarball, alternates) => {
587
+ if (!tarball)
588
+ return undefined;
589
+ const entry = alternates?.find(a => a.kind === 'tar.br' && !!a.tarball);
590
+ if (!entry)
591
+ return undefined;
592
+ try {
593
+ const href = new URL(entry.tarball, tarball).href;
594
+ return href === new URL(brotliTarballName(tarball)).href ?
595
+ href
596
+ : undefined;
597
+ /* c8 ignore start - a malformed reference just means no brotli */
598
+ }
599
+ catch {
600
+ return undefined;
601
+ }
602
+ /* c8 ignore stop */
603
+ };
547
604
  export const keyIDRE = /^SHA256:[a-zA-Z0-9/+]{43}$/;
548
605
  export const isKeyID = (k) => typeof k === 'string' && keyIDRE.test(k);
549
606
  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.0",
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.0",
17
+ "@vltpkg/error-cause": "1.3.0",
18
+ "@vltpkg/semver": "1.3.0",
19
+ "@vltpkg/spec": "1.3.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@eslint/js": "^9.39.1",