@vltpkg/graph 1.3.6 → 1.3.8

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.d.ts CHANGED
@@ -100,9 +100,10 @@ export declare class Graph implements GraphLike {
100
100
  /**
101
101
  * Whether the lockfile on disk no longer matches this graph even
102
102
  * though no node changed, e.g. an importer edge spec was rewritten to
103
- * the value saved to `package.json`, or the config options the graph
104
- * was built with differ from the ones stored in the lockfile. Reify
105
- * saves the lockfiles from its no-diff early return when set.
103
+ * the value saved to `package.json`, a node dev/optional flag was
104
+ * re-derived, or the config options the graph was built with differ
105
+ * from the ones stored in the lockfile. Reify saves the lockfiles
106
+ * from its no-diff early return when set.
106
107
  */
107
108
  lockfileStale: boolean;
108
109
  /**
@@ -126,6 +127,13 @@ export declare class Graph implements GraphLike {
126
127
  * Get the next peer context index.
127
128
  */
128
129
  nextPeerContextIndex(): number;
130
+ /**
131
+ * Re-derive node `dev`/`optional` flags: a node is dev/optional only
132
+ * when every importer path crosses a dev/optional edge. Placement only
133
+ * ever lowers these flags, so removed or retyped edges leave them
134
+ * stale. Sets `lockfileStale` when any flag changes.
135
+ */
136
+ recomputeFlags(): void;
129
137
  /**
130
138
  * Delete all nodes and edges that are unreachable from the importers.
131
139
  * The collection of deleted nodes is returned.
package/dist/graph.js CHANGED
@@ -110,9 +110,10 @@ export class Graph {
110
110
  /**
111
111
  * Whether the lockfile on disk no longer matches this graph even
112
112
  * though no node changed, e.g. an importer edge spec was rewritten to
113
- * the value saved to `package.json`, or the config options the graph
114
- * was built with differ from the ones stored in the lockfile. Reify
115
- * saves the lockfiles from its no-diff early return when set.
113
+ * the value saved to `package.json`, a node dev/optional flag was
114
+ * re-derived, or the config options the graph was built with differ
115
+ * from the ones stored in the lockfile. Reify saves the lockfiles
116
+ * from its no-diff early return when set.
116
117
  */
117
118
  lockfileStale = false;
118
119
  /**
@@ -195,6 +196,35 @@ export class Graph {
195
196
  nextPeerContextIndex() {
196
197
  return ++this.currentPeerContextIndex;
197
198
  }
199
+ /**
200
+ * Re-derive node `dev`/`optional` flags: a node is dev/optional only
201
+ * when every importer path crosses a dev/optional edge. Placement only
202
+ * ever lowers these flags, so removed or retyped edges leave them
203
+ * stale. Sets `lockfileStale` when any flag changes.
204
+ */
205
+ recomputeFlags() {
206
+ const reach = (skip) => {
207
+ const seen = new Set(this.importers);
208
+ for (const node of seen) {
209
+ for (const edge of node.edgesOut.values()) {
210
+ if (edge.to && !skip(edge))
211
+ seen.add(edge.to);
212
+ }
213
+ }
214
+ return seen;
215
+ };
216
+ const nonDev = reach(edge => edge.dev);
217
+ const nonOptional = reach(edge => edge.optional);
218
+ for (const node of this.nodes.values()) {
219
+ const dev = !nonDev.has(node);
220
+ const optional = !nonOptional.has(node);
221
+ if (node.dev !== dev || node.optional !== optional) {
222
+ node.dev = dev;
223
+ node.optional = optional;
224
+ this.lockfileStale = true;
225
+ }
226
+ }
227
+ }
198
228
  /**
199
229
  * Delete all nodes and edges that are unreachable from the importers.
200
230
  * The collection of deleted nodes is returned.
@@ -93,5 +93,6 @@ export const buildIdealFromStartingGraph = async (options) => {
93
93
  hasProvisionalPeerIds(options.graph)) {
94
94
  await movePeerStoreDirs(canonicalizePeerIds(options.graph), options);
95
95
  }
96
+ options.graph.recomputeFlags();
96
97
  return options.graph;
97
98
  };
@@ -1,6 +1,7 @@
1
1
  import { error } from '@vltpkg/error-cause';
2
2
  import { asDepID, splitDepID, splitExtra, } from '@vltpkg/dep-id/browser';
3
3
  import { getBooleanFlagsFromNum, getBuildStateFromNum, } from "./types.js";
4
+ import { tarballFormat } from '@vltpkg/types';
4
5
  export const loadNodes = (graph, nodes, options, actual, throwOnMissingManifest) => {
5
6
  const entries = Object.entries(nodes);
6
7
  const nodeCount = entries.length;
@@ -76,8 +77,15 @@ export const loadNodes = (graph, nodes, options, actual, throwOnMissingManifest)
76
77
  // node whose `resolved` the lockfile left out
77
78
  node.brotli = brotli;
78
79
  node.integrity = integrity ?? referenceNode?.integrity;
80
+ // `integrity` is the hash of the artifact `resolved` points at, so a
81
+ // reference url only stands in for an elided one when it names the
82
+ // kind of tarball the flag bit says the lockfile's own hash is for
83
+ const refResolved = referenceNode?.resolved;
84
+ const refResolvedUsable = !integrity ||
85
+ !refResolved ||
86
+ (tarballFormat(refResolved) === 'brotli') === brotli;
79
87
  node.resolved =
80
- type === 'remote' ? filepath : ((resolved ?? referenceNode?.resolved));
88
+ type === 'remote' ? filepath : ((resolved ?? (refResolvedUsable ? refResolved : undefined)));
81
89
  node.resolvedFromLockfile = !!(node.integrity && node.resolved);
82
90
  node.projectRoot = graph.projectRoot;
83
91
  if (!node.resolved)
package/dist/node.d.ts CHANGED
@@ -263,9 +263,13 @@ export declare class Node implements NodeLike {
263
263
  }
264
264
  /**
265
265
  * Copy the tarball metadata `to` is missing from another node for the
266
- * same package version. Lockfile provenance only carries over once both
267
- * `integrity` and `resolved` are set, since that pair is what makes it
268
- * usable without refetching.
266
+ * same package version. `integrity` is one artifact's hash, so it never
267
+ * crosses onto a node bound for the other kind of tarball: a node with
268
+ * no metadata of its own takes `from`'s kind along with it, one that
269
+ * already has a url or a hash only fills in what matches its own.
270
+ * Lockfile provenance only carries over once both `integrity` and
271
+ * `resolved` are set, since that pair is what makes it usable without
272
+ * refetching.
269
273
  */
270
274
  export declare const copyPackageMetadata: (to: Node, from: Node) => void;
271
275
  export declare const isNode: (value: unknown) => value is Node;
package/dist/node.js CHANGED
@@ -483,11 +483,21 @@ export class Node {
483
483
  }
484
484
  /**
485
485
  * Copy the tarball metadata `to` is missing from another node for the
486
- * same package version. Lockfile provenance only carries over once both
487
- * `integrity` and `resolved` are set, since that pair is what makes it
488
- * usable without refetching.
486
+ * same package version. `integrity` is one artifact's hash, so it never
487
+ * crosses onto a node bound for the other kind of tarball: a node with
488
+ * no metadata of its own takes `from`'s kind along with it, one that
489
+ * already has a url or a hash only fills in what matches its own.
490
+ * Lockfile provenance only carries over once both `integrity` and
491
+ * `resolved` are set, since that pair is what makes it usable without
492
+ * refetching.
489
493
  */
490
494
  export const copyPackageMetadata = (to, from) => {
495
+ if (!from.integrity && !from.resolved)
496
+ return;
497
+ if (!to.integrity && !to.resolved)
498
+ to.brotli = from.brotli;
499
+ if (to.brotli !== from.brotli)
500
+ return;
491
501
  to.integrity ??= from.integrity;
492
502
  to.resolved ??= from.resolved;
493
503
  if (from.resolvedFromLockfile && to.integrity && to.resolved) {
@@ -96,8 +96,9 @@ export const reify = async (options) => {
96
96
  // Even when there are no changes to reify, ensure lockfiles
97
97
  // exist on disk. This handles the case where a project has no
98
98
  // dependencies (or only workspace importers) but still needs
99
- // lockfiles written on the first install. A stale flag means an
100
- // edge spec was rewritten above, which the lockfiles have to carry.
99
+ // lockfiles written on the first install. A stale flag means
100
+ // lockfile data changed w/o a node diff (edge spec/type, options,
101
+ // dev/optional flags), which the lockfiles have to carry.
101
102
  if (!hasLockfiles || graph.lockfileStale) {
102
103
  // with no diff at all the ideal graph is the actual graph, so the
103
104
  // hidden lockfile can be written from it; an optional-only diff
package/dist/uninstall.js CHANGED
@@ -5,12 +5,18 @@ import { reify } from "./reify/index.js";
5
5
  import { lockfile } from "./index.js";
6
6
  import { updatePackageJson } from "./reify/update-importers-package-json.js";
7
7
  import { RollbackRemove } from '@vltpkg/rollback-remove';
8
+ import { Monorepo } from '@vltpkg/workspaces';
8
9
  import { existsSync, rmSync } from 'node:fs';
9
10
  import { resolve } from 'node:path';
10
11
  export const uninstall = async (options, remove) => {
11
12
  const mainManifest = options.packageJson.read(options.projectRoot);
12
13
  const modifiers = GraphModifier.maybeLoad(options);
13
14
  const remover = new RollbackRemove();
15
+ // graph spans every workspace; -w only picks the `remove` targets
16
+ const monorepo = Monorepo.maybeLoad(options.projectRoot, {
17
+ packageJson: options.packageJson,
18
+ scurry: options.scurry,
19
+ });
14
20
  try {
15
21
  // Load the actual graph before building the ideal graph so that
16
22
  // manifest data from installed packages can be used to hydrate
@@ -20,12 +26,14 @@ export const uninstall = async (options, remove) => {
20
26
  // due to throwOnMissingManifest and leaving stale entries behind.
21
27
  const act = actualLoad({
22
28
  ...options,
29
+ monorepo,
23
30
  modifiers: undefined,
24
31
  mainManifest,
25
32
  loadManifests: true,
26
33
  });
27
34
  const graph = await idealBuild({
28
35
  ...options,
36
+ monorepo,
29
37
  actual: act,
30
38
  remove,
31
39
  mainManifest,
package/dist/update.js CHANGED
@@ -5,10 +5,45 @@ import { GraphModifier } from "./modifiers.js";
5
5
  import { init } from '@vltpkg/init';
6
6
  import { asError } from '@vltpkg/types';
7
7
  import { Graph } from "./graph.js";
8
+ import { load as loadVirtual } from "./lockfile/load.js";
9
+ import { isPathSecurityError } from "./path-security-error.js";
8
10
  import { graphStep } from '@vltpkg/output';
9
11
  import { RollbackRemove } from '@vltpkg/rollback-remove';
12
+ import { Monorepo } from '@vltpkg/workspaces';
10
13
  import { existsSync, rmSync } from 'node:fs';
11
14
  import { resolve } from 'node:path';
15
+ /**
16
+ * Graph to update from. `-w` filtered: the lockfile (else installed)
17
+ * graph minus the selected workspaces' deps, so only those re-resolve
18
+ * (deps shared with other workspaces stay locked). Else: empty.
19
+ */
20
+ const startingGraph = (options, monorepo) => {
21
+ const fresh = () => new Graph({ ...options, monorepo });
22
+ if (!options.monorepo || !monorepo)
23
+ return fresh();
24
+ const selected = new Set([...options.monorepo.values()].map(ws => ws.id));
25
+ if (selected.size === monorepo.size)
26
+ return fresh();
27
+ let graph;
28
+ try {
29
+ graph = loadVirtual({ ...options, monorepo });
30
+ }
31
+ catch (err) {
32
+ if (isPathSecurityError(err))
33
+ throw err;
34
+ graph = actualLoad({ ...options, monorepo, loadManifests: true });
35
+ }
36
+ for (const importer of graph.importers) {
37
+ if (!selected.has(importer.id))
38
+ continue;
39
+ for (const edge of importer.edgesOut.values()) {
40
+ edge.to?.edgesIn.delete(edge);
41
+ }
42
+ importer.edgesOut.clear();
43
+ }
44
+ graph.gc();
45
+ return graph;
46
+ };
12
47
  export const update = async (options) => {
13
48
  let mainManifest = undefined;
14
49
  try {
@@ -27,6 +62,10 @@ export const update = async (options) => {
27
62
  }
28
63
  const modifiers = GraphModifier.maybeLoad(options);
29
64
  const remover = new RollbackRemove();
65
+ const monorepo = Monorepo.maybeLoad(options.projectRoot, {
66
+ packageJson: options.packageJson,
67
+ scurry: options.scurry,
68
+ });
30
69
  try {
31
70
  const done = graphStep('build');
32
71
  const graph = await buildIdealFromStartingGraph({
@@ -35,13 +74,14 @@ export const update = async (options) => {
35
74
  remove: Object.assign(new Map(), {
36
75
  modifiedDependencies: false,
37
76
  }),
38
- graph: new Graph({ ...options, mainManifest }),
77
+ graph: startingGraph({ ...options, mainManifest, modifiers }, monorepo),
39
78
  modifiers,
40
79
  remover,
41
80
  });
42
81
  done();
43
82
  const act = actualLoad({
44
83
  ...options,
84
+ monorepo,
45
85
  mainManifest,
46
86
  loadManifests: true,
47
87
  });
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.3.6",
4
+ "version": "1.3.8",
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.3.6",
17
- "@vltpkg/dep-id": "1.3.6",
18
- "@vltpkg/dss-breadcrumb": "1.3.6",
19
- "@vltpkg/error-cause": "1.3.6",
20
- "@vltpkg/fast-split": "1.3.6",
21
- "@vltpkg/graph-run": "1.3.6",
22
- "@vltpkg/init": "1.3.6",
23
- "@vltpkg/output": "1.3.6",
24
- "@vltpkg/package-info": "1.3.6",
25
- "@vltpkg/package-json": "1.3.6",
26
- "@vltpkg/pick-manifest": "1.3.6",
27
- "@vltpkg/query": "1.3.6",
28
- "@vltpkg/rollback-remove": "1.3.6",
29
- "@vltpkg/run": "1.3.6",
30
- "@vltpkg/satisfies": "1.3.6",
31
- "@vltpkg/security-archive": "1.3.6",
32
- "@vltpkg/spec": "1.3.6",
33
- "@vltpkg/types": "1.3.6",
34
- "@vltpkg/vlt-json": "1.3.6",
35
- "@vltpkg/workspaces": "1.3.6",
16
+ "@vltpkg/cmd-shim": "1.3.8",
17
+ "@vltpkg/dep-id": "1.3.8",
18
+ "@vltpkg/dss-breadcrumb": "1.3.8",
19
+ "@vltpkg/error-cause": "1.3.8",
20
+ "@vltpkg/fast-split": "1.3.8",
21
+ "@vltpkg/graph-run": "1.3.8",
22
+ "@vltpkg/init": "1.3.8",
23
+ "@vltpkg/output": "1.3.8",
24
+ "@vltpkg/package-info": "1.3.8",
25
+ "@vltpkg/package-json": "1.3.8",
26
+ "@vltpkg/pick-manifest": "1.3.8",
27
+ "@vltpkg/query": "1.3.8",
28
+ "@vltpkg/rollback-remove": "1.3.8",
29
+ "@vltpkg/run": "1.3.8",
30
+ "@vltpkg/satisfies": "1.3.8",
31
+ "@vltpkg/security-archive": "1.3.8",
32
+ "@vltpkg/spec": "1.3.8",
33
+ "@vltpkg/types": "1.3.8",
34
+ "@vltpkg/vlt-json": "1.3.8",
35
+ "@vltpkg/workspaces": "1.3.8",
36
36
  "path-scurry": "^2.0.1",
37
37
  "promise-call-limit": "^3.0.2"
38
38
  },