@vltpkg/graph 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/graph.js CHANGED
@@ -427,7 +427,11 @@ export class Graph {
427
427
  toFoundNode.manifest = manifest;
428
428
  this.manifests.set(toFoundNode.id, manifest);
429
429
  }
430
- toFoundNode.integrity ??= manifest.dist?.integrity;
430
+ // never onto a node resolved to a `.tar.br`: `dist.integrity`
431
+ // is the `.tgz` hash, a different artifact's bytes
432
+ if (!toFoundNode.brotli) {
433
+ toFoundNode.integrity ??= manifest.dist?.integrity;
434
+ }
431
435
  }
432
436
  return toFoundNode;
433
437
  }
@@ -455,7 +459,10 @@ export class Graph {
455
459
  toNode.optional = flags.optional;
456
460
  if (samePackage)
457
461
  copyPackageMetadata(toNode, samePackage);
458
- toNode.integrity ??= manifest?.dist?.integrity;
462
+ // not onto a node bound for the `.tar.br`: `dist.integrity` is the
463
+ // `.tgz` hash, and that is a different artifact's bytes
464
+ if (!toNode.brotli)
465
+ toNode.integrity ??= manifest?.dist?.integrity;
459
466
  // split extra into modifier and peerSetHash
460
467
  if (extra) {
461
468
  const { modifier, peerSetHash } = splitExtra(extra);
@@ -68,10 +68,13 @@ export const loadNodes = (graph, nodes, options, actual, throwOnMissingManifest)
68
68
  node.peerSetHash = peerSetHash;
69
69
  }
70
70
  }
71
- const { dev, optional } = getBooleanFlagsFromNum(flags);
71
+ const { dev, optional, brotli } = getBooleanFlagsFromNum(flags);
72
72
  node.options = options;
73
73
  node.dev = dev;
74
74
  node.optional = optional;
75
+ // before setResolved(), which needs it to pick the extension for a
76
+ // node whose `resolved` the lockfile left out
77
+ node.brotli = brotli;
75
78
  node.integrity = integrity ?? referenceNode?.integrity;
76
79
  node.resolved =
77
80
  type === 'remote' ? filepath : ((resolved ?? referenceNode?.resolved));
@@ -57,19 +57,30 @@ export declare const LOCKFILE_VERSION = 1;
57
57
  export declare const getFlagNumFromNode: (node: {
58
58
  optional?: boolean;
59
59
  dev?: boolean;
60
- }) => 1 | 0 | 2 | 3;
60
+ brotli?: boolean;
61
+ }) => LockfileNodeFlags;
61
62
  export declare const getBooleanFlagsFromNum: (flags: LockfileNodeFlags) => {
62
63
  dev: boolean;
63
64
  optional: boolean;
65
+ brotli: boolean;
64
66
  };
65
67
  export declare const LockfileNodeFlagNone = 0;
66
68
  export declare const LockfileNodeFlagOptional = 1;
67
69
  export declare const LockfileNodeFlagDev = 2;
68
70
  export declare const LockfileNodeFlagDevOptional = 3;
69
71
  /**
70
- * Bit flags indicating whether a node is optional and/or dev.
72
+ * This node's artifact is the registry's Brotli (`.tar.br`) tarball, so
73
+ * `integrity` is that artifact's hash. Carried as a flag bit rather than a
74
+ * field because a default-registry node writes no `resolved` at all --
75
+ * `Node.setResolved()` rebuilds the URL, and this is what tells it which
76
+ * extension to rebuild.
71
77
  */
72
- export type LockfileNodeFlags = 0 | 1 | 2 | 3;
78
+ export declare const LockfileNodeFlagBrotli = 4;
79
+ /**
80
+ * Bit flags indicating whether a node is optional, dev, and/or served as
81
+ * a Brotli tarball.
82
+ */
83
+ export type LockfileNodeFlags = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
73
84
  /**
74
85
  * Build state constants for lockfile nodes
75
86
  */
@@ -4,18 +4,26 @@
4
4
  * Bump when making breaking format changes.
5
5
  */
6
6
  export const LOCKFILE_VERSION = 1;
7
- export const getFlagNumFromNode = (node) => node.optional && node.dev ? LockfileNodeFlagDevOptional
8
- : node.optional ? LockfileNodeFlagOptional
9
- : node.dev ? LockfileNodeFlagDev
10
- : LockfileNodeFlagNone;
7
+ export const getFlagNumFromNode = (node) => ((node.optional ? LockfileNodeFlagOptional : 0) |
8
+ (node.dev ? LockfileNodeFlagDev : 0) |
9
+ (node.brotli ? LockfileNodeFlagBrotli : 0));
11
10
  export const getBooleanFlagsFromNum = (flags) => ({
12
11
  dev: !!(flags & LockfileNodeFlagDev),
13
12
  optional: !!(flags & LockfileNodeFlagOptional),
13
+ brotli: !!(flags & LockfileNodeFlagBrotli),
14
14
  });
15
15
  export const LockfileNodeFlagNone = 0;
16
16
  export const LockfileNodeFlagOptional = 1;
17
17
  export const LockfileNodeFlagDev = 2;
18
18
  export const LockfileNodeFlagDevOptional = 3;
19
+ /**
20
+ * This node's artifact is the registry's Brotli (`.tar.br`) tarball, so
21
+ * `integrity` is that artifact's hash. Carried as a flag bit rather than a
22
+ * field because a default-registry node writes no `resolved` at all --
23
+ * `Node.setResolved()` rebuilds the URL, and this is what tells it which
24
+ * extension to rebuild.
25
+ */
26
+ export const LockfileNodeFlagBrotli = 4;
19
27
  /**
20
28
  * Build state constants for lockfile nodes
21
29
  */
package/dist/node.d.ts CHANGED
@@ -7,6 +7,11 @@ import type { Graph } from './graph.ts';
7
7
  export type NodeOptions = SpecOptions & {
8
8
  projectRoot: string;
9
9
  graph: GraphLike;
10
+ /**
11
+ * Resolve to a version's Brotli (`.tar.br`) alternate when its manifest
12
+ * advertises one. Defaults to true; false always resolves to the `.tgz`.
13
+ */
14
+ 'brotli-tarballs'?: boolean;
10
15
  };
11
16
  export declare class Node implements NodeLike {
12
17
  #private;
@@ -85,6 +90,16 @@ export declare class Node implements NodeLike {
85
90
  * The manifest this node represents in the graph.
86
91
  */
87
92
  manifest?: NormalizedManifest;
93
+ /**
94
+ * True when this node's artifact is the registry's Brotli (`.tar.br`)
95
+ * tarball rather than the gzip `.tgz`, which makes `integrity` that
96
+ * artifact's hash. `resolved` is authoritative whenever it is set, so
97
+ * the two can never drift; the stored bit only carries the lockfile's
98
+ * answer across to `setResolved()`, which has to know the extension
99
+ * before it can build the URL.
100
+ */
101
+ get brotli(): boolean;
102
+ set brotli(brotli: boolean);
88
103
  /**
89
104
  * Project where this node resides
90
105
  */
package/dist/node.js CHANGED
@@ -1,8 +1,15 @@
1
1
  import { isPackageNameConfused, getId, hydrateTuple, splitDepID, } from '@vltpkg/dep-id';
2
2
  import { typeError } from '@vltpkg/error-cause';
3
- import { expandNormalizedManifestSymbols } from '@vltpkg/types';
3
+ import { brotliTarballName, brotliTarballUrl, expandNormalizedManifestSymbols, tarballFormat, } from '@vltpkg/types';
4
4
  import { Edge } from "./edge.js";
5
5
  import { stringifyNode } from "./stringify-node.js";
6
+ /**
7
+ * The absolute URL of a version's Brotli (`.tar.br`) tarball. `dist`
8
+ * arrives here with an absolute `tarball`, so the alternate's relative
9
+ * reference resolves against it. See {@link brotliTarballUrl} for which
10
+ * references this client accepts.
11
+ */
12
+ const brotliAlternate = (dist) => brotliTarballUrl(dist?.tarball, dist?.alternates);
6
13
  export class Node {
7
14
  get [Symbol.toStringTag]() {
8
15
  return '@vltpkg/graph.Node';
@@ -110,6 +117,31 @@ export class Node {
110
117
  * The manifest this node represents in the graph.
111
118
  */
112
119
  manifest;
120
+ /**
121
+ * Whether this node's artifact is the `.tar.br`: from the manifest's
122
+ * `dist.alternates` at construction, or overwritten by the lockfile's
123
+ * flag bit when the node came from one (so an existing lockfile keeps
124
+ * whichever artifact it pinned). Only consulted while `resolved` is
125
+ * still unset -- once there is a URL, it is the answer (see the
126
+ * getter).
127
+ */
128
+ #brotli = false;
129
+ /**
130
+ * True when this node's artifact is the registry's Brotli (`.tar.br`)
131
+ * tarball rather than the gzip `.tgz`, which makes `integrity` that
132
+ * artifact's hash. `resolved` is authoritative whenever it is set, so
133
+ * the two can never drift; the stored bit only carries the lockfile's
134
+ * answer across to `setResolved()`, which has to know the extension
135
+ * before it can build the URL.
136
+ */
137
+ get brotli() {
138
+ return this.resolved ?
139
+ tarballFormat(this.resolved) === 'brotli'
140
+ : this.#brotli;
141
+ }
142
+ set brotli(brotli) {
143
+ this.#brotli = brotli;
144
+ }
113
145
  /**
114
146
  * Project where this node resides
115
147
  */
@@ -245,6 +277,13 @@ export class Node {
245
277
  }
246
278
  this.graph = options.graph;
247
279
  this.manifest = manifest;
280
+ // Settled here, not in setResolved(), because `integrity` is filled
281
+ // in from `dist.integrity` between the two -- and that hash is the
282
+ // `.tgz`'s. A node whose artifact is the `.tar.br` has to be able to
283
+ // turn that down before it arrives.
284
+ this.#brotli =
285
+ options['brotli-tarballs'] !== false &&
286
+ !!brotliAlternate(manifest?.dist);
248
287
  this.#name = name || this.manifest?.name;
249
288
  this.version = version || this.manifest?.version;
250
289
  }
@@ -263,9 +302,28 @@ export class Node {
263
302
  }
264
303
  #registryNodeResolved(tuple) {
265
304
  const spec = hydrateTuple(tuple, this.#name, this.#options);
266
- this.resolved =
267
- this.manifest?.dist?.tarball || spec.conventionalRegistryTarball;
268
- this.integrity ??= this.manifest?.dist?.integrity;
305
+ const dist = this.manifest?.dist;
306
+ const tarball = dist?.tarball || spec.conventionalRegistryTarball;
307
+ // Use the manifest's own reference if there is one. Otherwise build
308
+ // the URL the way the registry names these: the same stem as the
309
+ // `.tgz` with a different extension. Relying on that convention is
310
+ // what lets a lockfile node, which has no manifest, store one flag
311
+ // bit instead of a second URL.
312
+ const alt = this.#brotli ? brotliAlternate(dist) : undefined;
313
+ const brotli = this.#brotli ?
314
+ (alt?.tarball ?? (tarball && brotliTarballName(tarball)))
315
+ : undefined;
316
+ if (brotli) {
317
+ this.resolved = brotli;
318
+ // `dist.integrity` belongs to the `.tgz`, so never copy it here.
319
+ // Use the alternate's own hash if the registry sent one. If not,
320
+ // the first fetch learns it from the Repr-Digest header.
321
+ if (alt?.integrity)
322
+ this.integrity ??= alt.integrity;
323
+ return;
324
+ }
325
+ this.resolved = tarball;
326
+ this.integrity ??= dist?.integrity;
269
327
  }
270
328
  get options() {
271
329
  return this.#options;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vltpkg/graph",
3
3
  "description": "A library that helps understanding & expressing what happens on an install",
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,26 +13,26 @@
13
13
  "url": "http://vlt.sh"
14
14
  },
15
15
  "dependencies": {
16
- "@vltpkg/cmd-shim": "1.2.0",
17
- "@vltpkg/dep-id": "1.2.0",
18
- "@vltpkg/dss-breadcrumb": "1.2.0",
19
- "@vltpkg/error-cause": "1.2.0",
20
- "@vltpkg/fast-split": "1.2.0",
21
- "@vltpkg/graph-run": "1.2.0",
22
- "@vltpkg/init": "1.2.0",
23
- "@vltpkg/output": "1.2.0",
24
- "@vltpkg/package-info": "1.2.0",
25
- "@vltpkg/package-json": "1.2.0",
26
- "@vltpkg/pick-manifest": "1.2.0",
27
- "@vltpkg/query": "1.2.0",
28
- "@vltpkg/rollback-remove": "1.2.0",
29
- "@vltpkg/run": "1.2.0",
30
- "@vltpkg/satisfies": "1.2.0",
31
- "@vltpkg/security-archive": "1.2.0",
32
- "@vltpkg/spec": "1.2.0",
33
- "@vltpkg/types": "1.2.0",
34
- "@vltpkg/vlt-json": "1.2.0",
35
- "@vltpkg/workspaces": "1.2.0",
16
+ "@vltpkg/cmd-shim": "1.3.1",
17
+ "@vltpkg/dep-id": "1.3.1",
18
+ "@vltpkg/dss-breadcrumb": "1.3.1",
19
+ "@vltpkg/error-cause": "1.3.1",
20
+ "@vltpkg/fast-split": "1.3.1",
21
+ "@vltpkg/graph-run": "1.3.1",
22
+ "@vltpkg/init": "1.3.1",
23
+ "@vltpkg/output": "1.3.1",
24
+ "@vltpkg/package-info": "1.3.1",
25
+ "@vltpkg/package-json": "1.3.1",
26
+ "@vltpkg/pick-manifest": "1.3.1",
27
+ "@vltpkg/query": "1.3.1",
28
+ "@vltpkg/rollback-remove": "1.3.1",
29
+ "@vltpkg/run": "1.3.1",
30
+ "@vltpkg/satisfies": "1.3.1",
31
+ "@vltpkg/security-archive": "1.3.1",
32
+ "@vltpkg/spec": "1.3.1",
33
+ "@vltpkg/types": "1.3.1",
34
+ "@vltpkg/vlt-json": "1.3.1",
35
+ "@vltpkg/workspaces": "1.3.1",
36
36
  "path-scurry": "^2.0.1",
37
37
  "promise-call-limit": "^3.0.2"
38
38
  },