@vltpkg/graph 1.0.0-rc.23 → 1.0.0-rc.25

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 (109) hide show
  1. package/dist/actual/load.d.ts +107 -0
  2. package/dist/actual/load.js +336 -0
  3. package/dist/browser.d.ts +14 -0
  4. package/dist/browser.js +16 -0
  5. package/dist/build.d.ts +28 -0
  6. package/dist/build.js +78 -0
  7. package/dist/dependencies.d.ts +65 -0
  8. package/dist/dependencies.js +111 -0
  9. package/dist/diff.d.ts +119 -0
  10. package/dist/diff.js +151 -0
  11. package/dist/edge.d.ts +46 -0
  12. package/dist/edge.js +77 -0
  13. package/dist/fixup-added-names.d.ts +18 -0
  14. package/dist/fixup-added-names.js +46 -0
  15. package/dist/graph.d.ts +153 -0
  16. package/dist/graph.js +444 -0
  17. package/dist/ideal/append-nodes.d.ts +31 -0
  18. package/dist/ideal/append-nodes.js +560 -0
  19. package/dist/ideal/build-ideal-from-starting-graph.d.ts +14 -0
  20. package/dist/ideal/build-ideal-from-starting-graph.js +69 -0
  21. package/dist/ideal/build.d.ts +40 -0
  22. package/dist/ideal/build.js +84 -0
  23. package/dist/ideal/get-importer-specs.d.ts +20 -0
  24. package/dist/ideal/get-importer-specs.js +180 -0
  25. package/dist/ideal/peers.d.ts +160 -0
  26. package/dist/ideal/peers.js +696 -0
  27. package/dist/ideal/refresh-ideal-graph.d.ts +43 -0
  28. package/dist/ideal/refresh-ideal-graph.js +62 -0
  29. package/dist/ideal/remove-satisfied-specs.d.ts +7 -0
  30. package/dist/ideal/remove-satisfied-specs.js +34 -0
  31. package/dist/ideal/sorting.d.ts +45 -0
  32. package/dist/ideal/sorting.js +70 -0
  33. package/dist/ideal/types.d.ts +107 -0
  34. package/dist/ideal/types.js +1 -0
  35. package/dist/index.d.ts +38 -0
  36. package/dist/index.js +32 -0
  37. package/dist/install.d.ts +19 -0
  38. package/dist/install.js +208 -0
  39. package/dist/lockfile/load-edges.d.ts +11 -0
  40. package/dist/lockfile/load-edges.js +105 -0
  41. package/dist/lockfile/load-nodes.d.ts +4 -0
  42. package/dist/lockfile/load-nodes.js +101 -0
  43. package/dist/lockfile/load.d.ts +45 -0
  44. package/dist/lockfile/load.js +84 -0
  45. package/dist/lockfile/save.d.ts +30 -0
  46. package/dist/lockfile/save.js +174 -0
  47. package/dist/lockfile/types.d.ts +95 -0
  48. package/dist/lockfile/types.js +49 -0
  49. package/dist/modifiers.d.ts +188 -0
  50. package/dist/modifiers.js +329 -0
  51. package/dist/node.d.ts +234 -0
  52. package/dist/node.js +388 -0
  53. package/dist/non-empty-list.d.ts +2 -0
  54. package/dist/non-empty-list.js +2 -0
  55. package/dist/reify/add-edge.d.ts +9 -0
  56. package/dist/reify/add-edge.js +71 -0
  57. package/dist/reify/add-edges.d.ts +4 -0
  58. package/dist/reify/add-edges.js +12 -0
  59. package/dist/reify/add-nodes.d.ts +6 -0
  60. package/dist/reify/add-nodes.js +16 -0
  61. package/dist/reify/bin-chmod.d.ts +10 -0
  62. package/dist/reify/bin-chmod.js +38 -0
  63. package/dist/reify/build.d.ts +13 -0
  64. package/dist/reify/build.js +111 -0
  65. package/dist/reify/calculate-save-value.d.ts +2 -0
  66. package/dist/reify/calculate-save-value.js +50 -0
  67. package/dist/reify/check-needed-build.d.ts +34 -0
  68. package/dist/reify/check-needed-build.js +71 -0
  69. package/dist/reify/delete-edge.d.ts +4 -0
  70. package/dist/reify/delete-edge.js +27 -0
  71. package/dist/reify/delete-edges.d.ts +4 -0
  72. package/dist/reify/delete-edges.js +13 -0
  73. package/dist/reify/delete-nodes.d.ts +4 -0
  74. package/dist/reify/delete-nodes.js +15 -0
  75. package/dist/reify/extract-node.d.ts +23 -0
  76. package/dist/reify/extract-node.js +83 -0
  77. package/dist/reify/index.d.ts +34 -0
  78. package/dist/reify/index.js +161 -0
  79. package/dist/reify/internal-hoist.d.ts +8 -0
  80. package/dist/reify/internal-hoist.js +133 -0
  81. package/dist/reify/optional-fail.d.ts +15 -0
  82. package/dist/reify/optional-fail.js +15 -0
  83. package/dist/reify/rollback.d.ts +4 -0
  84. package/dist/reify/rollback.js +23 -0
  85. package/dist/reify/update-importers-package-json.d.ts +35 -0
  86. package/dist/reify/update-importers-package-json.js +122 -0
  87. package/dist/remove-optional-subgraph.d.ts +33 -0
  88. package/dist/remove-optional-subgraph.js +47 -0
  89. package/dist/resolve-save-type.d.ts +5 -0
  90. package/dist/resolve-save-type.js +4 -0
  91. package/dist/stringify-node.d.ts +2 -0
  92. package/dist/stringify-node.js +32 -0
  93. package/dist/transfer-data/load.d.ts +43 -0
  94. package/dist/transfer-data/load.js +175 -0
  95. package/dist/uninstall.d.ts +14 -0
  96. package/dist/uninstall.js +75 -0
  97. package/dist/update.d.ts +12 -0
  98. package/dist/update.js +73 -0
  99. package/dist/virtual-root.d.ts +15 -0
  100. package/dist/virtual-root.js +78 -0
  101. package/dist/visualization/human-readable-output.d.ts +26 -0
  102. package/dist/visualization/human-readable-output.js +163 -0
  103. package/dist/visualization/json-output.d.ts +41 -0
  104. package/dist/visualization/json-output.js +50 -0
  105. package/dist/visualization/mermaid-output.d.ts +17 -0
  106. package/dist/visualization/mermaid-output.js +170 -0
  107. package/dist/visualization/object-like-output.d.ts +2 -0
  108. package/dist/visualization/object-like-output.js +47 -0
  109. package/package.json +22 -22
@@ -0,0 +1,163 @@
1
+ import { defaultRegistryName } from '@vltpkg/spec';
2
+ import { splitDepID } from '@vltpkg/dep-id';
3
+ import { styleText as utilStyleText } from 'node:util';
4
+ const styleText = (format, s) => utilStyleText(format, s, { validateStream: false });
5
+ const chars = new Map(Object.entries({
6
+ connection: '─',
7
+ down: '│',
8
+ 'last-child': '└',
9
+ 'middle-child': '├',
10
+ t: '┬',
11
+ }));
12
+ const isSelected = (options, edge, node) => (!node || options.nodes.includes(node)) &&
13
+ (!edge || options.edges.includes(edge));
14
+ /**
15
+ * Returns a map of tree items to be printed.
16
+ */
17
+ const getTreeItems = (initialItems, options) => {
18
+ const seenNodes = new Set();
19
+ const treeItems = new Map();
20
+ const traverse = new Set(initialItems);
21
+ for (const item of traverse) {
22
+ if (item.node) {
23
+ if (seenNodes.has(item.node)) {
24
+ item.seen = true;
25
+ continue;
26
+ }
27
+ seenNodes.add(item.node);
28
+ const edges = [...item.node.edgesOut.values()];
29
+ const workspaces = item.node.workspaces ? [...item.node.workspaces.values()] : [];
30
+ const allEdges = [...edges, ...workspaces].sort((a, b) => a.name.localeCompare(b.name, 'en'));
31
+ // keeps track of seen edge names to avoid duplicating printed
32
+ // items when a workspace is also a regular dependency of the root
33
+ const seenEdgeName = new Set();
34
+ for (const edge of allEdges) {
35
+ if (seenEdgeName.has(edge.name)) {
36
+ continue;
37
+ }
38
+ seenEdgeName.add(edge.name);
39
+ const toItems = treeItems.get(edge) ?? new Map();
40
+ const nextItem = {
41
+ edge,
42
+ node: edge.to,
43
+ hasSibling: false,
44
+ padding: '',
45
+ prefix: '',
46
+ seen: false,
47
+ include: isSelected(options, edge, edge.to),
48
+ parent: item,
49
+ };
50
+ toItems.set(edge.to, nextItem);
51
+ treeItems.set(edge, toItems);
52
+ traverse.add(nextItem);
53
+ }
54
+ }
55
+ }
56
+ for (const item of [...traverse].reverse()) {
57
+ if (item.include && item.parent) {
58
+ item.parent.include = true;
59
+ }
60
+ }
61
+ return treeItems;
62
+ };
63
+ /**
64
+ * Returns a human-readable output of the graph.
65
+ */
66
+ export function humanReadableOutput(options, { colors }) {
67
+ const { importers } = options;
68
+ const createStyleText = (style) => (s) => colors ? styleText(style, s) : s;
69
+ const dim = createStyleText('dim');
70
+ const red = createStyleText('red');
71
+ const reset = createStyleText('reset');
72
+ const yellow = createStyleText('yellow');
73
+ const initialItems = new Set();
74
+ for (const importer of importers) {
75
+ initialItems.add({
76
+ name: importer.name,
77
+ edge: undefined,
78
+ node: importer,
79
+ prefix: '',
80
+ padding: '',
81
+ hasSibling: false,
82
+ seen: false,
83
+ include: isSelected(options, undefined, importer),
84
+ parent: undefined,
85
+ });
86
+ }
87
+ const treeItems = getTreeItems(initialItems, options);
88
+ let res = '';
89
+ const traverse = (item) => {
90
+ let header = '';
91
+ let content = '';
92
+ const name = item.name;
93
+ const decoratedName = (options.highlightSelection &&
94
+ isSelected(options, item.edge, item.node) &&
95
+ name) ?
96
+ yellow(name)
97
+ : name;
98
+ if (!item.node && item.include) {
99
+ const missing = item.edge?.type.endsWith('ptional') ?
100
+ dim('(missing optional)')
101
+ : red('(missing)');
102
+ return `${item.padding}${item.prefix}${decoratedName} ${missing}\n`;
103
+ }
104
+ header += `${item.padding}${item.prefix}${decoratedName}\n`;
105
+ // seen items need not to be printed or traversed
106
+ if (!item.seen) {
107
+ const edges = item.node ? [...item.node.edgesOut.values()] : [];
108
+ const workspaces = item.node?.workspaces ?
109
+ [...item.node.workspaces.values()]
110
+ : [];
111
+ const allEdges = [...edges, ...workspaces].sort((a, b) => a.name.localeCompare(b.name, 'en'));
112
+ const nextItems = allEdges.map(i => treeItems.get(i)?.get(i.to));
113
+ const includedItems = nextItems.filter(i => i?.include);
114
+ for (const nextItem of nextItems) {
115
+ /* c8 ignore next -- impossible but TS doesn't know that */
116
+ if (!nextItem)
117
+ continue;
118
+ const parent = item;
119
+ const isLast = includedItems.indexOf(nextItem) === includedItems.length - 1;
120
+ // prefixes the node name with the registry name
121
+ // if a custom registry name is found
122
+ const depIdTuple = nextItem.node?.id && splitDepID(nextItem.node.id);
123
+ const hasCustomReg = depIdTuple?.[0] === 'registry' &&
124
+ depIdTuple[1] &&
125
+ depIdTuple[1] !== defaultRegistryName;
126
+ const nodeName = hasCustomReg ?
127
+ `${depIdTuple[1]}:${nextItem.node?.name}`
128
+ : nextItem.node?.name;
129
+ const toName = nextItem.node?.version ?
130
+ `${nodeName}@${nextItem.node.version}`
131
+ : nodeName;
132
+ const nextChar = isLast ? 'last-child' : 'middle-child';
133
+ const aliasedPackage = hasCustomReg ||
134
+ (nextItem.node?.name &&
135
+ nextItem.edge?.name !== nextItem.node.name);
136
+ nextItem.name =
137
+ nextItem.node?.confused ?
138
+ `${nextItem.edge?.name} ${red('(confused)')}`
139
+ : nextItem.edge?.spec.overridden ?
140
+ /* c8 ignore next */
141
+ `${nextItem.edge.name}@${nextItem.node?.version || nextItem.edge.spec.bareSpec}${aliasedPackage ? ` (${toName})` : ''} ${yellow('(overridden)')}`
142
+ : aliasedPackage ? `${nextItem.edge?.name} (${toName})`
143
+ : `${nextItem.edge?.name}@${nextItem.node?.version || nextItem.edge?.spec.bareSpec}`;
144
+ nextItem.padding =
145
+ parent.prefix.length ?
146
+ `${parent.padding}${parent.hasSibling ? chars.get('down') : ' '} `
147
+ : '';
148
+ nextItem.prefix = `${chars.get(nextChar)}${chars.get('connection')}${chars.get('connection')} `;
149
+ nextItem.hasSibling = !isLast;
150
+ content += traverse(nextItem);
151
+ }
152
+ }
153
+ const regularConnection = `${chars.get('connection')} `;
154
+ const parentConnection = `${chars.get('t')} `;
155
+ return item.include ?
156
+ `${content ? header.replace(regularConnection, parentConnection) : header}${content}`
157
+ : '';
158
+ };
159
+ for (const item of initialItems) {
160
+ res += traverse(item);
161
+ }
162
+ return reset(res);
163
+ }
@@ -0,0 +1,41 @@
1
+ import type { DepID } from '@vltpkg/dep-id';
2
+ import type { EdgeLike, NodeLike } from '@vltpkg/types';
3
+ /**
4
+ * A JSON output item describes a package that is present in the install
5
+ * graph. It represents an edge plus it's linking Node.
6
+ */
7
+ export type JSONOutputItem = {
8
+ /**
9
+ * The name of the package.
10
+ */
11
+ name: string;
12
+ /**
13
+ * A reference to the id of the Node that this package is linked from.
14
+ */
15
+ fromID?: DepID;
16
+ /**
17
+ * The dependency spec definition for this package.
18
+ */
19
+ spec?: string;
20
+ /**
21
+ * The package type.
22
+ */
23
+ type?: EdgeLike['type'];
24
+ /**
25
+ * A representation of the package object that fulfills this dependency.
26
+ */
27
+ to?: NodeLike;
28
+ /**
29
+ * Whether this edge was overridden by a graph modifier.
30
+ */
31
+ overridden: boolean;
32
+ };
33
+ export type JSONOutputGraph = {
34
+ edges: EdgeLike[];
35
+ nodes: NodeLike[];
36
+ importers: Set<NodeLike>;
37
+ };
38
+ /**
39
+ * Returns a JSON string representation of the graph.
40
+ */
41
+ export declare function jsonOutput({ edges, nodes, importers, }: JSONOutputGraph): JSONOutputItem[];
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Returns a JSON string representation of the graph.
3
+ */
4
+ export function jsonOutput({ edges, nodes, importers, }) {
5
+ const res = [];
6
+ const seenIds = new Set();
7
+ // Collects edge & it's linked nodes as json output items
8
+ const orderedEdges = [...edges].sort((a, b) => {
9
+ const aIsWorkspace = a.spec.type === 'workspace';
10
+ const bIsWorkspace = b.spec.type === 'workspace';
11
+ if (aIsWorkspace && !bIsWorkspace)
12
+ return -1;
13
+ /* c8 ignore next */
14
+ if (!aIsWorkspace && bIsWorkspace)
15
+ return 1;
16
+ return 0; // preserve original order otherwise
17
+ });
18
+ for (const edge of orderedEdges) {
19
+ if (edge.to)
20
+ seenIds.add(edge.to.id);
21
+ res.push({
22
+ name: edge.name,
23
+ fromID: edge.from.id,
24
+ spec: String(edge.spec),
25
+ type: edge.type,
26
+ to: edge.to,
27
+ overridden: edge.spec.overridden,
28
+ });
29
+ }
30
+ // Collects included importer nodes json output items
31
+ /* c8 ignore next 3 */
32
+ const orderedImporters = [...importers].sort((a, b) => {
33
+ if (!a.name)
34
+ return 1;
35
+ if (!b.name)
36
+ return -1;
37
+ return a.name.localeCompare(b.name);
38
+ });
39
+ for (const node of orderedImporters) {
40
+ if (!nodes.includes(node) || seenIds.has(node.id))
41
+ continue;
42
+ res.unshift({
43
+ /* c8 ignore next - name can't be missing but ts won't know */
44
+ name: node.name || node.id,
45
+ to: node,
46
+ overridden: false,
47
+ });
48
+ }
49
+ return res;
50
+ }
@@ -0,0 +1,17 @@
1
+ import type { EdgeLike, NodeLike } from '@vltpkg/types';
2
+ export type MermaidOutputGraph = {
3
+ edges: EdgeLike[];
4
+ importers: Set<NodeLike>;
5
+ nodes: NodeLike[];
6
+ highlightSelection?: boolean;
7
+ };
8
+ /**
9
+ * Generates a short identifier for a given index following the pattern:
10
+ * 0 -> a, 1 -> b, ..., 25 -> z, 26 -> A, ..., 51 -> Z, 52 -> aa, 53 -> ab, etc.
11
+ * This implements a bijective base-52 numbering system where a-z = 0-25, A-Z = 26-51
12
+ */
13
+ export declare function generateShortId(index: number): string;
14
+ /**
15
+ * Returns a mermaid string representation of the graph.
16
+ */
17
+ export declare function mermaidOutput(options: MermaidOutputGraph): string;
@@ -0,0 +1,170 @@
1
+ import { Edge } from "../edge.js";
2
+ import { Node } from "../node.js";
3
+ let missingCount = 0;
4
+ const isSelected = (options, edge, node) => (!node || options.nodes.includes(node)) &&
5
+ /* c8 ignore next */ (!edge || options.edges.includes(edge));
6
+ /**
7
+ * Generates a short identifier for a given index following the pattern:
8
+ * 0 -> a, 1 -> b, ..., 25 -> z, 26 -> A, ..., 51 -> Z, 52 -> aa, 53 -> ab, etc.
9
+ * This implements a bijective base-52 numbering system where a-z = 0-25, A-Z = 26-51
10
+ */
11
+ export function generateShortId(index) {
12
+ const base = 52;
13
+ // Helper function to convert a digit (0-51) to character
14
+ const digitToChar = (digit) => {
15
+ if (digit < 26) {
16
+ // a-z (0-25)
17
+ return String.fromCharCode(97 + digit);
18
+ }
19
+ else {
20
+ // A-Z (26-51)
21
+ return String.fromCharCode(65 + (digit - 26));
22
+ }
23
+ };
24
+ // Bijective base-52 conversion
25
+ let result = '';
26
+ let num = index + 1; // Convert to 1-based for bijective base
27
+ while (num > 0) {
28
+ num--; // Adjust for 0-based indexing in each position
29
+ const remainder = num % base;
30
+ result = digitToChar(remainder) + result;
31
+ num = Math.floor(num / base);
32
+ }
33
+ return result;
34
+ }
35
+ /**
36
+ * Creates a mapping from DepID to short identifier
37
+ */
38
+ function createDepIdMapping(importers) {
39
+ const mapping = new Map();
40
+ const uniqueDepIds = new Set();
41
+ // Collect all unique DepIDs from nodes & importers
42
+ const [importer] = importers;
43
+ if (importer) {
44
+ for (const node of importer.graph.nodes.values()) {
45
+ uniqueDepIds.add(node.id);
46
+ }
47
+ }
48
+ // Create mapping for each unique DepID
49
+ let index = 0;
50
+ for (const depId of uniqueDepIds) {
51
+ mapping.set(depId, generateShortId(index++));
52
+ }
53
+ return mapping;
54
+ }
55
+ /**
56
+ * Returns a node reference: on the first occurrence of a node, returns the full label;
57
+ * on subsequent occurrences, returns just the shortId.
58
+ *
59
+ * Note: Mutates the `labeledNodes` set as a side effect to track which nodes have already
60
+ * been labeled. This tracking is shared across multiple importers to prevent duplicate
61
+ * labels in the output.
62
+ */
63
+ const nodeRef = (node, labeledNodes, depIdMapping, options) => {
64
+ const shortId = depIdMapping.get(node.id) /* c8 ignore next - should not be possible */ ?? '';
65
+ const selectedClass = (options.highlightSelection &&
66
+ isSelected(options, undefined, node)) ?
67
+ ':::a'
68
+ : '';
69
+ if (labeledNodes.has(node.id)) {
70
+ return shortId + selectedClass;
71
+ }
72
+ labeledNodes.add(node.id);
73
+ return `${shortId}("${String(node).replaceAll('@', '#64;')}")${selectedClass}`;
74
+ };
75
+ function parseNode(seenNodes, labeledNodes, includedItems, depIdMapping, options, node, isImporter = false) {
76
+ if (seenNodes.has(node.id) || !includedItems.get(node)) {
77
+ return '';
78
+ }
79
+ seenNodes.add(node.id);
80
+ // For importers, render the node label first as a standalone line before processing edges,
81
+ // since they appear at the top of the graph. Non-importer nodes are labeled inline as part of edge definitions.
82
+ const nodeLabel = isImporter ?
83
+ nodeRef(node, labeledNodes, depIdMapping, options)
84
+ : '';
85
+ // Include both regular edges and workspace edges (if any)
86
+ const allEdges = [
87
+ ...node.edgesOut.values(),
88
+ ...(node.workspaces?.values() ?? []),
89
+ ];
90
+ const edges = allEdges
91
+ .map(e => parseEdge(seenNodes, labeledNodes, includedItems, depIdMapping, options, e))
92
+ .filter(Boolean)
93
+ .join('\n');
94
+ // Only render node standalone for importers, others are rendered as part of edges
95
+ if (isImporter) {
96
+ /* c8 ignore next */
97
+ return `${nodeLabel}${edges.length ? '\n' : ''}${edges}`;
98
+ }
99
+ return edges;
100
+ }
101
+ function parseEdge(seenNodes, labeledNodes, includedItems, depIdMapping, options, edge) {
102
+ if (!includedItems.get(edge)) {
103
+ return '';
104
+ }
105
+ const edgeType = edge.type === 'prod' ? '' : ` (${edge.type})`;
106
+ const edgeResult = nodeRef(edge.from, labeledNodes, depIdMapping, options) +
107
+ ` -->|"${String(edge.spec).replaceAll('@', '#64;')}${edgeType}"| `;
108
+ const missingLabel = edge.type.endsWith('ptional') ? 'Missing Optional' : 'Missing';
109
+ if (!edge.to) {
110
+ return edgeResult + `missing-${missingCount++}(${missingLabel})`;
111
+ }
112
+ // Label the target node first so that if it's referenced again later in the graph,
113
+ // it will use the short identifier instead of repeating the full label.
114
+ const toRef = nodeRef(edge.to, labeledNodes, depIdMapping, options);
115
+ const childEdges = parseNode(seenNodes, labeledNodes, includedItems, depIdMapping, options, edge.to);
116
+ return edgeResult + toRef + (childEdges ? '\n' + childEdges : '');
117
+ }
118
+ /**
119
+ * Returns a mermaid string representation of the graph.
120
+ */
121
+ export function mermaidOutput(options) {
122
+ const { edges, importers, nodes, highlightSelection } = options;
123
+ const seen = new Set();
124
+ const includedItems = new Map();
125
+ const traverse = new Set([...importers].map(i => ({ self: i, parent: undefined })));
126
+ for (const item of traverse) {
127
+ if (seen.has(item.self))
128
+ continue;
129
+ seen.add(item.self);
130
+ if (item.self instanceof Edge) {
131
+ if (edges.includes(item.self)) {
132
+ includedItems.set(item.self, true);
133
+ }
134
+ if (item.self.to) {
135
+ traverse.add({ self: item.self.to, parent: item.self });
136
+ }
137
+ }
138
+ if (item.self instanceof Node) {
139
+ if (nodes.includes(item.self)) {
140
+ includedItems.set(item.self, true);
141
+ }
142
+ for (const edge of item.self.edgesOut.values()) {
143
+ traverse.add({ self: edge, parent: item.self });
144
+ }
145
+ // Also traverse workspace edges (only mainImporter has these)
146
+ if (item.self.workspaces) {
147
+ for (const edge of item.self.workspaces.values()) {
148
+ traverse.add({ self: edge, parent: item.self });
149
+ }
150
+ }
151
+ }
152
+ }
153
+ for (const item of [...traverse].reverse()) {
154
+ if (includedItems.has(item.self) && item.parent) {
155
+ includedItems.set(item.parent, true);
156
+ }
157
+ }
158
+ // Create DepID to short identifier mapping
159
+ const depIdMapping = createDepIdMapping(importers);
160
+ // Track nodes that have had their label printed (shared across all importers)
161
+ const labeledNodes = new Set();
162
+ // Track nodes that have been processed (shared across all importers to avoid duplicates)
163
+ const seenNodes = new Set();
164
+ const graphOutput = [...importers]
165
+ .map(i => parseNode(seenNodes, labeledNodes, includedItems, depIdMapping, options, i, true))
166
+ .filter(Boolean)
167
+ .join('\n');
168
+ const styleDefinition = highlightSelection ? '\nclassDef a fill:gold,color:#242424' : '';
169
+ return 'flowchart TD\n' + graphOutput + styleDefinition;
170
+ }
@@ -0,0 +1,2 @@
1
+ import type { GraphLike } from '@vltpkg/types';
2
+ export declare function objectLikeOutput(graph: GraphLike): string;
@@ -0,0 +1,47 @@
1
+ import { inspect } from 'node:util';
2
+ const addInspect = (o, fn) => (o[inspect.custom] = fn);
3
+ function parseNode(seenNodes, graph, node) {
4
+ addInspect(node, () => {
5
+ const res = 'Node ' +
6
+ inspect(seenNodes.has(node) ?
7
+ { ref: node.id }
8
+ : {
9
+ id: node.id,
10
+ location: node.location,
11
+ ...(node.importer ? { importer: true } : null),
12
+ ...(node.dev ? { dev: true } : null),
13
+ ...(node.optional ? { optional: true } : null),
14
+ ...(node.resolved ? { resolved: node.resolved } : null),
15
+ ...(node.integrity ?
16
+ { integrity: node.integrity }
17
+ : null),
18
+ ...(node.edgesOut.size ?
19
+ {
20
+ edgesOut: [...node.edgesOut.values()].map(i => {
21
+ seenNodes.add(node);
22
+ return parseEdge(seenNodes, graph, i);
23
+ }),
24
+ }
25
+ : (seenNodes.add(node), null)),
26
+ }, { depth: Infinity });
27
+ return res;
28
+ });
29
+ return node;
30
+ }
31
+ function parseEdge(seenNodes, graph, edge) {
32
+ addInspect(edge, () => {
33
+ const missingNode = `[missing package]: <${edge.name}@${edge.spec.bareSpec}>`;
34
+ const toLabel = edge.to ?
35
+ inspect(parseNode(seenNodes, graph, edge.to), {
36
+ depth: Infinity,
37
+ })
38
+ : missingNode;
39
+ return `Edge spec(${String(edge.spec)}) -${edge.type}-> to: ${toLabel}`;
40
+ });
41
+ return edge;
42
+ }
43
+ export function objectLikeOutput(graph) {
44
+ const seenNodes = new Set();
45
+ const importers = [...graph.importers];
46
+ return inspect(importers.map(i => parseNode(seenNodes, graph, i)), { depth: Infinity });
47
+ }
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.0.0-rc.23",
4
+ "version": "1.0.0-rc.25",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/vltpkg/vltpkg.git",
@@ -12,33 +12,33 @@
12
12
  "email": "support@vlt.sh"
13
13
  },
14
14
  "dependencies": {
15
- "@vltpkg/cmd-shim": "1.0.0-rc.23",
16
- "@vltpkg/dep-id": "1.0.0-rc.23",
17
- "@vltpkg/dss-breadcrumb": "1.0.0-rc.23",
18
- "@vltpkg/error-cause": "1.0.0-rc.23",
19
- "@vltpkg/fast-split": "1.0.0-rc.23",
20
- "@vltpkg/graph-run": "1.0.0-rc.23",
21
- "@vltpkg/init": "1.0.0-rc.23",
22
- "@vltpkg/output": "1.0.0-rc.23",
23
- "@vltpkg/package-info": "1.0.0-rc.23",
24
- "@vltpkg/package-json": "1.0.0-rc.23",
25
- "@vltpkg/pick-manifest": "1.0.0-rc.23",
26
- "@vltpkg/query": "1.0.0-rc.23",
27
- "@vltpkg/rollback-remove": "1.0.0-rc.23",
28
- "@vltpkg/run": "1.0.0-rc.23",
29
- "@vltpkg/satisfies": "1.0.0-rc.23",
30
- "@vltpkg/security-archive": "1.0.0-rc.23",
31
- "@vltpkg/spec": "1.0.0-rc.23",
32
- "@vltpkg/types": "1.0.0-rc.23",
33
- "@vltpkg/vlt-json": "1.0.0-rc.23",
34
- "@vltpkg/workspaces": "1.0.0-rc.23",
15
+ "@vltpkg/cmd-shim": "1.0.0-rc.25",
16
+ "@vltpkg/dep-id": "1.0.0-rc.25",
17
+ "@vltpkg/dss-breadcrumb": "1.0.0-rc.25",
18
+ "@vltpkg/error-cause": "1.0.0-rc.25",
19
+ "@vltpkg/fast-split": "1.0.0-rc.25",
20
+ "@vltpkg/graph-run": "1.0.0-rc.25",
21
+ "@vltpkg/init": "1.0.0-rc.25",
22
+ "@vltpkg/output": "1.0.0-rc.25",
23
+ "@vltpkg/package-info": "1.0.0-rc.25",
24
+ "@vltpkg/package-json": "1.0.0-rc.25",
25
+ "@vltpkg/pick-manifest": "1.0.0-rc.25",
26
+ "@vltpkg/query": "1.0.0-rc.25",
27
+ "@vltpkg/rollback-remove": "1.0.0-rc.25",
28
+ "@vltpkg/run": "1.0.0-rc.25",
29
+ "@vltpkg/satisfies": "1.0.0-rc.25",
30
+ "@vltpkg/security-archive": "1.0.0-rc.25",
31
+ "@vltpkg/spec": "1.0.0-rc.25",
32
+ "@vltpkg/types": "1.0.0-rc.25",
33
+ "@vltpkg/vlt-json": "1.0.0-rc.25",
34
+ "@vltpkg/workspaces": "1.0.0-rc.25",
35
35
  "path-scurry": "^2.0.1",
36
36
  "promise-call-limit": "^3.0.2"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@eslint/js": "^9.39.1",
40
40
  "@types/node": "^22.19.2",
41
- "@vltpkg/vlt-json": "1.0.0-rc.23",
41
+ "@vltpkg/vlt-json": "1.0.0-rc.25",
42
42
  "eslint": "^9.39.1",
43
43
  "prettier": "^3.7.4",
44
44
  "tap": "^21.5.0",