@visulima/vis 1.0.0-alpha.1 → 1.0.0-alpha.11

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 (120) hide show
  1. package/CHANGELOG.md +403 -12
  2. package/LICENSE.md +283 -0
  3. package/README.md +254 -9
  4. package/dist/bin.js +9 -146
  5. package/dist/config/index.d.ts +1818 -0
  6. package/dist/config/index.js +2 -0
  7. package/dist/generate/index.d.ts +157 -0
  8. package/dist/generate/index.js +3 -0
  9. package/dist/packem_chunks/applyDefaults.js +336 -0
  10. package/dist/packem_chunks/bin.js +9577 -0
  11. package/dist/packem_chunks/doctor-probe.js +112 -0
  12. package/dist/packem_chunks/fix.js +234 -0
  13. package/dist/packem_chunks/handler.js +99 -0
  14. package/dist/packem_chunks/handler10.js +53 -0
  15. package/dist/packem_chunks/handler11.js +32 -0
  16. package/dist/packem_chunks/handler12.js +100 -0
  17. package/dist/packem_chunks/handler13.js +25 -0
  18. package/dist/packem_chunks/handler14.js +916 -0
  19. package/dist/packem_chunks/handler15.js +206 -0
  20. package/dist/packem_chunks/handler16.js +124 -0
  21. package/dist/packem_chunks/handler17.js +13 -0
  22. package/dist/packem_chunks/handler18.js +106 -0
  23. package/dist/packem_chunks/handler19.js +19 -0
  24. package/dist/packem_chunks/handler2.js +75 -0
  25. package/dist/packem_chunks/handler20.js +29 -0
  26. package/dist/packem_chunks/handler21.js +222 -0
  27. package/dist/packem_chunks/handler22.js +237 -0
  28. package/dist/packem_chunks/handler23.js +101 -0
  29. package/dist/packem_chunks/handler24.js +110 -0
  30. package/dist/packem_chunks/handler25.js +402 -0
  31. package/dist/packem_chunks/handler26.js +13 -0
  32. package/dist/packem_chunks/handler27.js +63 -0
  33. package/dist/packem_chunks/handler28.js +34 -0
  34. package/dist/packem_chunks/handler29.js +458 -0
  35. package/dist/packem_chunks/handler3.js +95 -0
  36. package/dist/packem_chunks/handler30.js +170 -0
  37. package/dist/packem_chunks/handler31.js +530 -0
  38. package/dist/packem_chunks/handler32.js +214 -0
  39. package/dist/packem_chunks/handler33.js +119 -0
  40. package/dist/packem_chunks/handler34.js +630 -0
  41. package/dist/packem_chunks/handler35.js +283 -0
  42. package/dist/packem_chunks/handler36.js +542 -0
  43. package/dist/packem_chunks/handler37.js +762 -0
  44. package/dist/packem_chunks/handler38.js +989 -0
  45. package/dist/packem_chunks/handler39.js +574 -0
  46. package/dist/packem_chunks/handler4.js +90 -0
  47. package/dist/packem_chunks/handler40.js +1685 -0
  48. package/dist/packem_chunks/handler41.js +1088 -0
  49. package/dist/packem_chunks/handler42.js +797 -0
  50. package/dist/packem_chunks/handler43.js +2658 -0
  51. package/dist/packem_chunks/handler44.js +3886 -0
  52. package/dist/packem_chunks/handler45.js +2574 -0
  53. package/dist/packem_chunks/handler46.js +3769 -0
  54. package/dist/packem_chunks/handler47.js +1491 -0
  55. package/dist/packem_chunks/handler5.js +174 -0
  56. package/dist/packem_chunks/handler6.js +95 -0
  57. package/dist/packem_chunks/handler7.js +115 -0
  58. package/dist/packem_chunks/handler8.js +12 -0
  59. package/dist/packem_chunks/handler9.js +29 -0
  60. package/dist/packem_chunks/heal-accept.js +522 -0
  61. package/dist/packem_chunks/heal.js +673 -0
  62. package/dist/packem_chunks/index.js +873 -0
  63. package/dist/packem_chunks/loader.js +23 -0
  64. package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +1316 -0
  65. package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +5 -0
  66. package/dist/packem_shared/ai-analysis-CHeB1joD.js +367 -0
  67. package/dist/packem_shared/ai-cache-Be_jexe4.js +142 -0
  68. package/dist/packem_shared/ai-fix-B9iQVcD2.js +379 -0
  69. package/dist/packem_shared/cache-directory-2qvs4goY.js +98 -0
  70. package/dist/packem_shared/catalog-BJTtyi-O.js +1371 -0
  71. package/dist/packem_shared/dependency-scan-A0KSklpG.js +188 -0
  72. package/dist/packem_shared/docker-2iZzc280.js +181 -0
  73. package/dist/packem_shared/failure-log-Cz3Z4SKL.js +100 -0
  74. package/dist/packem_shared/flakiness-goTxXuCX.js +180 -0
  75. package/dist/packem_shared/otel-DCvqCTz_.js +158 -0
  76. package/dist/packem_shared/otelPlugin-DFaLDvJf.js +3 -0
  77. package/dist/packem_shared/registry-CbqXI0rc.js +272 -0
  78. package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +130 -0
  79. package/dist/packem_shared/runtime-check-Cobi3p6l.js +127 -0
  80. package/dist/packem_shared/selectors-SM69TfqC.js +194 -0
  81. package/dist/packem_shared/symbols-Ta7g2nU-.js +14 -0
  82. package/dist/packem_shared/toolchain-BdZd9eBi.js +975 -0
  83. package/dist/packem_shared/typosquats-C-bCh3PX.js +1210 -0
  84. package/dist/packem_shared/use-measured-height-CNP0vT4M.js +20 -0
  85. package/dist/packem_shared/utils-CthVdBPS.js +40 -0
  86. package/dist/packem_shared/xxh3-Ck8mXNg1.js +239 -0
  87. package/index.js +773 -0
  88. package/package.json +82 -21
  89. package/schemas/project.schema.json +420 -0
  90. package/schemas/vis-config.schema.json +501 -0
  91. package/skills/vis/SKILL.md +96 -0
  92. package/templates/buildkite-ci/.buildkite/pipeline.yml.tera +85 -0
  93. package/templates/buildkite-ci/template.yml +20 -0
  94. package/dist/ai-analysis.d.ts +0 -40
  95. package/dist/ai-cache.d.ts +0 -21
  96. package/dist/bin.d.ts +0 -1
  97. package/dist/catalog.d.ts +0 -110
  98. package/dist/commands/affected.d.ts +0 -3
  99. package/dist/commands/ai.d.ts +0 -3
  100. package/dist/commands/analyze.d.ts +0 -3
  101. package/dist/commands/check.d.ts +0 -3
  102. package/dist/commands/graph.d.ts +0 -3
  103. package/dist/commands/hook/constants.d.ts +0 -8
  104. package/dist/commands/hook/index.d.ts +0 -3
  105. package/dist/commands/hook/install.d.ts +0 -7
  106. package/dist/commands/hook/migrate.d.ts +0 -27
  107. package/dist/commands/hook/uninstall.d.ts +0 -3
  108. package/dist/commands/migrate/constants.d.ts +0 -12
  109. package/dist/commands/migrate/deps.d.ts +0 -32
  110. package/dist/commands/migrate/index.d.ts +0 -3
  111. package/dist/commands/migrate/json.d.ts +0 -20
  112. package/dist/commands/migrate/lint-staged.d.ts +0 -62
  113. package/dist/commands/migrate/types.d.ts +0 -20
  114. package/dist/commands/run.d.ts +0 -3
  115. package/dist/commands/staged.d.ts +0 -3
  116. package/dist/commands/update.d.ts +0 -3
  117. package/dist/config.d.ts +0 -40
  118. package/dist/config.js +0 -1
  119. package/dist/package-manager.d.ts +0 -23
  120. package/dist/workspace.d.ts +0 -58
@@ -0,0 +1,989 @@
1
+ import { createRequire as __cjs_createRequire } from "node:module";
2
+
3
+ const __cjs_require = __cjs_createRequire(import.meta.url);
4
+
5
+ const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
6
+
7
+ const __cjs_getBuiltinModule = (module) => {
8
+ // Check if we're in Node.js and version supports getBuiltinModule
9
+ if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
+ const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
+ // Node.js 20.16.0+ and 22.3.0+
12
+ if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
+ return __cjs_getProcess.getBuiltinModule(module);
14
+ }
15
+ }
16
+ // Fallback to createRequire
17
+ return __cjs_require(module);
18
+ };
19
+
20
+ const {
21
+ writeFileSync
22
+ } = __cjs_getBuiltinModule("node:fs");
23
+ import { bold, cyan, dim } from '@visulima/colorize';
24
+ import { projectGraphToDot } from '@visulima/task-runner';
25
+ import { Box, Text, ScrollView, ScrollBar, useApp, useWindowSize, useInput, Dialog, render } from '@visulima/tui';
26
+ import { _ as QuitDialog, d as discoverWorkspace, b as buildProjectGraph, i as isInCi } from './bin.js';
27
+ import React, { useSyncExternalStore, useState, useRef, useMemo, useCallback, useEffect } from 'react';
28
+ import { jsx, jsxs } from 'react/jsx-runtime';
29
+
30
+ const buildNodes = (projectGraph) => {
31
+ const reverseDeps = /* @__PURE__ */ new Map();
32
+ for (const [source, deps] of Object.entries(projectGraph.dependencies)) {
33
+ for (const dep of deps) {
34
+ const existing = reverseDeps.get(dep.target) ?? [];
35
+ existing.push(source);
36
+ reverseDeps.set(dep.target, existing);
37
+ }
38
+ }
39
+ return Object.values(projectGraph.nodes).map((node) => {
40
+ return {
41
+ deps: projectGraph.dependencies[node.name] ?? [],
42
+ name: node.name,
43
+ reverseDeps: reverseDeps.get(node.name) ?? [],
44
+ type: node.type
45
+ };
46
+ }).sort((a, b) => {
47
+ if (a.type !== b.type) {
48
+ return a.type === "application" ? -1 : 1;
49
+ }
50
+ return a.name.localeCompare(b.name);
51
+ });
52
+ };
53
+ const filterNodes = (allNodes, filterType, filterText) => {
54
+ let filtered = allNodes;
55
+ if (filterType === "app") {
56
+ filtered = filtered.filter((n) => n.type === "application");
57
+ } else if (filterType === "lib") {
58
+ filtered = filtered.filter((n) => n.type !== "application");
59
+ }
60
+ if (filterText) {
61
+ const lower = filterText.toLowerCase();
62
+ filtered = filtered.filter((n) => n.name.toLowerCase().includes(lower));
63
+ }
64
+ return filtered;
65
+ };
66
+ class GraphStore {
67
+ #state;
68
+ #listeners = /* @__PURE__ */ new Set();
69
+ #projectGraph;
70
+ constructor(projectGraph) {
71
+ this.#projectGraph = projectGraph;
72
+ const allNodes = buildNodes(projectGraph);
73
+ this.#state = {
74
+ allNodes,
75
+ filterActive: false,
76
+ filterText: "",
77
+ filterType: "all",
78
+ focusedPanel: "list",
79
+ selectedIndex: 0
80
+ };
81
+ }
82
+ // ── React integration ───────────────────────────────────────────
83
+ getSnapshot = () => this.#state;
84
+ subscribe = (listener) => {
85
+ this.#listeners.add(listener);
86
+ return () => {
87
+ this.#listeners.delete(listener);
88
+ };
89
+ };
90
+ // ── Derived data ────────────────────────────────────────────────
91
+ getFilteredNodes() {
92
+ return filterNodes(this.#state.allNodes, this.#state.filterType, this.#state.filterText);
93
+ }
94
+ getStats() {
95
+ const apps = this.#state.allNodes.filter((n) => n.type === "application").length;
96
+ const total = this.#state.allNodes.length;
97
+ const deps = Object.values(this.#projectGraph.dependencies).reduce((s, d) => s + d.length, 0);
98
+ return { apps, deps, libs: total - apps, total };
99
+ }
100
+ // ── Navigation ──────────────────────────────────────────────────
101
+ setSelectedIndex(index) {
102
+ const filtered = this.getFilteredNodes();
103
+ const clamped = filtered.length === 0 ? -1 : Math.max(0, Math.min(index, filtered.length - 1));
104
+ if (clamped !== this.#state.selectedIndex) {
105
+ this.#emit({ ...this.#state, selectedIndex: clamped });
106
+ }
107
+ }
108
+ setFocusedPanel(panel) {
109
+ if (panel !== this.#state.focusedPanel) {
110
+ this.#emit({ ...this.#state, focusedPanel: panel });
111
+ }
112
+ }
113
+ // ── Filtering ───────────────────────────────────────────────────
114
+ setFilterType(type) {
115
+ if (type !== this.#state.filterType) {
116
+ this.#emit({
117
+ ...this.#state,
118
+ filterType: type,
119
+ selectedIndex: 0
120
+ });
121
+ }
122
+ }
123
+ setFilter(text) {
124
+ this.#emit({
125
+ ...this.#state,
126
+ filterText: text,
127
+ selectedIndex: 0
128
+ });
129
+ }
130
+ setFilterActive(active) {
131
+ if (active !== this.#state.filterActive) {
132
+ this.#emit({
133
+ ...this.#state,
134
+ filterActive: active,
135
+ filterText: active ? this.#state.filterText : "",
136
+ selectedIndex: active ? this.#state.selectedIndex : 0
137
+ });
138
+ }
139
+ }
140
+ // ── Internal ────────────────────────────────────────────────────
141
+ #emit(newState) {
142
+ this.#state = newState;
143
+ for (const listener of this.#listeners) {
144
+ try {
145
+ listener();
146
+ } catch {
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ const ProjectDetailPanel = ({ focused, node, scrollRef }) => {
153
+ const borderColor = focused ? "white" : "gray";
154
+ if (!node) {
155
+ return jsx(Box, { alignItems: "center", borderColor: "gray", borderStyle: "single", flexDirection: "column", flexGrow: 1, justifyContent: "center", children: jsx(Text, { dimColor: true, children: "No project selected" }) });
156
+ }
157
+ const isApp = node.type === "application";
158
+ const typeColor = isApp ? "yellow" : "cyan";
159
+ const typeLabel = isApp ? "Application" : "Library";
160
+ return jsx(
161
+ Box,
162
+ {
163
+ borderColor,
164
+ borderStyle: "single",
165
+ borderTopRightTitle: ` ${typeLabel} `,
166
+ borderTopTitle: ` ${node.name} `,
167
+ flexDirection: "column",
168
+ flexGrow: 1,
169
+ children: jsxs(ScrollView, { flexGrow: 1, flexShrink: 1, paddingX: 2, ref: scrollRef, scrollbar: true, scrollbarColor: "gray", children: [
170
+ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
171
+ jsxs(Box, { children: [
172
+ jsx(Text, { dimColor: true, children: "── " }),
173
+ jsx(Text, { bold: true, color: "white", children: "DEPENDS ON" }),
174
+ jsxs(Text, { dimColor: true, children: [
175
+ " (",
176
+ node.deps.length,
177
+ ")"
178
+ ] })
179
+ ] }),
180
+ node.deps.length === 0 ? jsx(Box, { marginTop: 1, paddingLeft: 2, children: jsx(Text, { dimColor: true, children: "No dependencies" }) }) : jsx(Box, { flexDirection: "column", marginTop: 1, children: node.deps.map((dep) => jsxs(Box, { gap: 1, paddingLeft: 2, children: [
181
+ jsx(Text, { color: "cyan", children: "→" }),
182
+ jsx(Text, { children: dep.target }),
183
+ dep.type !== "static" && jsxs(Text, { dimColor: true, children: [
184
+ "(",
185
+ dep.type,
186
+ ")"
187
+ ] })
188
+ ] }, dep.target)) })
189
+ ] }),
190
+ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
191
+ jsxs(Box, { children: [
192
+ jsx(Text, { dimColor: true, children: "── " }),
193
+ jsx(Text, { bold: true, color: "white", children: "REQUIRED BY" }),
194
+ jsxs(Text, { dimColor: true, children: [
195
+ " (",
196
+ node.reverseDeps.length,
197
+ ")"
198
+ ] })
199
+ ] }),
200
+ node.reverseDeps.length === 0 ? jsx(Box, { marginTop: 1, paddingLeft: 2, children: jsx(Text, { dimColor: true, children: "No reverse dependencies" }) }) : jsx(Box, { flexDirection: "column", marginTop: 1, children: node.reverseDeps.map((rdep) => jsxs(Box, { gap: 1, paddingLeft: 2, children: [
201
+ jsx(Text, { color: "magenta", children: "←" }),
202
+ jsx(Text, { children: rdep })
203
+ ] }, rdep)) })
204
+ ] }),
205
+ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
206
+ jsxs(Box, { children: [
207
+ jsx(Text, { dimColor: true, children: "── " }),
208
+ jsx(Text, { bold: true, color: "white", children: "INFO" })
209
+ ] }),
210
+ jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, children: [
211
+ jsxs(Box, { children: [
212
+ jsx(Box, { width: 16, children: jsx(Text, { dimColor: true, children: "Type:" }) }),
213
+ jsx(Text, { color: typeColor, children: typeLabel })
214
+ ] }),
215
+ jsxs(Box, { children: [
216
+ jsx(Box, { width: 16, children: jsx(Text, { dimColor: true, children: "Dependencies:" }) }),
217
+ jsx(Text, { children: String(node.deps.length) })
218
+ ] }),
219
+ jsxs(Box, { children: [
220
+ jsx(Box, { width: 16, children: jsx(Text, { dimColor: true, children: "Required by:" }) }),
221
+ jsx(Text, { children: String(node.reverseDeps.length) })
222
+ ] }),
223
+ jsxs(Box, { children: [
224
+ jsx(Box, { width: 16, children: jsx(Text, { dimColor: true, children: "Connectivity:" }) }),
225
+ jsx(Text, { children: String(node.deps.length + node.reverseDeps.length) })
226
+ ] })
227
+ ] })
228
+ ] })
229
+ ] })
230
+ }
231
+ );
232
+ };
233
+
234
+ const FILTER_LABELS = [
235
+ { key: "all", label: "All", shortcut: "1" },
236
+ { key: "app", label: "Apps", shortcut: "2" },
237
+ { key: "lib", label: "Libs", shortcut: "3" }
238
+ ];
239
+ const ProjectRow = ({ isSelected, node }) => {
240
+ const isApp = node.type === "application";
241
+ const typeColor = isApp ? "yellow" : "cyan";
242
+ const typeLabel = isApp ? "app" : "lib";
243
+ return jsxs(Box, { flexShrink: 0, height: 1, children: [
244
+ jsxs(Text, { children: [
245
+ isSelected ? "▶" : " ",
246
+ " "
247
+ ] }),
248
+ jsx(Box, { flexGrow: 1, children: jsx(Text, { bold: isSelected, inverse: isSelected, wrap: "truncate", children: node.name }) }),
249
+ jsxs(Text, { color: typeColor, children: [
250
+ " ",
251
+ typeLabel
252
+ ] }),
253
+ jsxs(Text, { dimColor: true, children: [
254
+ " ",
255
+ "→",
256
+ node.deps.length,
257
+ " ",
258
+ "←",
259
+ node.reverseDeps.length
260
+ ] })
261
+ ] });
262
+ };
263
+ const TypeHeader = ({ count, label }) => jsxs(Box, { flexShrink: 0, height: 1, marginTop: 1, children: [
264
+ jsxs(Text, { dimColor: true, children: [
265
+ "▼",
266
+ " "
267
+ ] }),
268
+ jsx(Text, { bold: true, color: "white", children: label.toUpperCase() }),
269
+ jsxs(Text, { dimColor: true, children: [
270
+ " (",
271
+ count,
272
+ ")"
273
+ ] })
274
+ ] });
275
+ const ProjectListPanel = ({
276
+ filterActive,
277
+ filterText,
278
+ filterType,
279
+ focused,
280
+ nodes,
281
+ scrollOffset,
282
+ selectedIndex,
283
+ stats,
284
+ viewportHeight
285
+ }) => {
286
+ const borderColor = focused ? "white" : "gray";
287
+ const apps = nodes.filter((n) => n.type === "application");
288
+ const libs = nodes.filter((n) => n.type !== "application");
289
+ const rows = [];
290
+ let flatIndex = 0;
291
+ if (apps.length > 0) {
292
+ rows.push(jsx(TypeHeader, { count: apps.length, label: "Applications" }, "hdr-apps"));
293
+ for (const node of apps) {
294
+ const currentIndex = flatIndex;
295
+ rows.push(jsx(ProjectRow, { isSelected: currentIndex === selectedIndex, node }, node.name));
296
+ flatIndex++;
297
+ }
298
+ }
299
+ if (libs.length > 0) {
300
+ rows.push(jsx(TypeHeader, { count: libs.length, label: "Libraries" }, "hdr-libs"));
301
+ for (const node of libs) {
302
+ const currentIndex = flatIndex;
303
+ rows.push(jsx(ProjectRow, { isSelected: currentIndex === selectedIndex, node }, node.name));
304
+ flatIndex++;
305
+ }
306
+ }
307
+ let contentHeight = 0;
308
+ if (apps.length > 0) {
309
+ contentHeight += 2 + apps.length;
310
+ }
311
+ if (libs.length > 0) {
312
+ contentHeight += 2 + libs.length;
313
+ }
314
+ const showScrollbar = contentHeight > viewportHeight && viewportHeight > 0;
315
+ return jsxs(Box, { borderColor, borderStyle: "single", flexDirection: "column", flexGrow: 1, children: [
316
+ jsxs(Box, { flexShrink: 0, gap: 1, paddingX: 1, children: [
317
+ jsx(Text, { bold: true, inverse: true, children: " VIS " }),
318
+ jsxs(Text, { wrap: "truncate", children: [
319
+ stats.total,
320
+ " packages"
321
+ ] }),
322
+ jsxs(Text, { dimColor: true, children: [
323
+ "(",
324
+ stats.apps,
325
+ " apps, ",
326
+ stats.libs,
327
+ " libs, ",
328
+ stats.deps,
329
+ " deps)"
330
+ ] })
331
+ ] }),
332
+ jsx(Box, { flexShrink: 0, gap: 1, paddingX: 1, paddingY: 1, children: FILTER_LABELS.map((f) => {
333
+ const isActive = filterType === f.key;
334
+ return jsxs(Box, { children: [
335
+ jsx(Text, { dimColor: !isActive, children: "[" }),
336
+ jsx(Text, { bold: isActive, color: isActive ? "cyan" : "gray", children: f.shortcut }),
337
+ jsx(Text, { dimColor: !isActive, children: "]" }),
338
+ jsxs(Text, { color: isActive ? "white" : "gray", children: [
339
+ " ",
340
+ f.label
341
+ ] })
342
+ ] }, f.key);
343
+ }) }),
344
+ filterActive && jsxs(Box, { flexShrink: 0, paddingX: 1, children: [
345
+ jsx(Text, { bold: true, color: "white", children: "/ " }),
346
+ jsx(Text, { children: filterText }),
347
+ jsx(Text, { inverse: true, children: " " })
348
+ ] }),
349
+ jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [
350
+ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingLeft: 1, children: jsx(Box, { flexDirection: "column", marginTop: -scrollOffset, children: rows }) }),
351
+ showScrollbar && jsx(Box, { flexShrink: 0, marginLeft: 1, marginRight: 1, children: jsx(ScrollBar, { contentHeight, placement: "inset", scrollOffset, style: "block", viewportHeight }) })
352
+ ] })
353
+ ] });
354
+ };
355
+
356
+ const MIN_HORIZONTAL_WIDTH = 100;
357
+ const MIN_VIEWPORT_WIDTH = 40;
358
+ const MIN_VIEWPORT_HEIGHT = 10;
359
+ const FILTER_KEYS = {
360
+ 1: "all",
361
+ 2: "app",
362
+ 3: "lib"
363
+ };
364
+ const VisGraphApp = ({ autoExitSeconds = 0, store }) => {
365
+ const { exit } = useApp();
366
+ const { columns, rows } = useWindowSize();
367
+ const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
368
+ const [helpVisible, setHelpVisible] = useState(false);
369
+ const helpScrollRef = useRef(null);
370
+ const detailScrollRef = useRef(null);
371
+ const [listScrollOffset, setListScrollOffset] = useState(0);
372
+ const [quitDialogVisible, setQuitDialogVisible] = useState(false);
373
+ const filteredNodes = useMemo(() => store.getFilteredNodes(), [state.allNodes, state.filterType, state.filterText]);
374
+ const stats = useMemo(() => store.getStats(), [state.allNodes]);
375
+ const selectedNode = filteredNodes[state.selectedIndex] ?? null;
376
+ const getRowForIndex = useCallback(
377
+ (index) => {
378
+ const apps = filteredNodes.filter((n) => n.type === "application");
379
+ const libs = filteredNodes.filter((n) => n.type !== "application");
380
+ let row = 0;
381
+ let count = 0;
382
+ if (apps.length > 0) {
383
+ row += 2;
384
+ for (let i = 0; i < apps.length; i++) {
385
+ if (count === index) {
386
+ return row;
387
+ }
388
+ row += 1;
389
+ count++;
390
+ }
391
+ }
392
+ if (libs.length > 0) {
393
+ row += 2;
394
+ for (let i = 0; i < libs.length; i++) {
395
+ if (count === index) {
396
+ return row;
397
+ }
398
+ row += 1;
399
+ count++;
400
+ }
401
+ }
402
+ return row;
403
+ },
404
+ [filteredNodes]
405
+ );
406
+ const listViewportHeight = Math.max(1, rows - 8 - (state.filterActive ? 1 : 0));
407
+ const scrollToIndex = useCallback(
408
+ (index) => {
409
+ const targetRow = getRowForIndex(index);
410
+ setListScrollOffset((current) => {
411
+ if (targetRow > current + listViewportHeight - 2) {
412
+ return Math.max(0, targetRow - listViewportHeight + 2);
413
+ }
414
+ if (targetRow < current + 1) {
415
+ return Math.max(0, targetRow - 1);
416
+ }
417
+ return current;
418
+ });
419
+ },
420
+ [getRowForIndex, listViewportHeight]
421
+ );
422
+ useEffect(() => {
423
+ detailScrollRef.current?.scrollToTop();
424
+ }, [selectedNode?.name]);
425
+ useInput(
426
+ (input, key) => {
427
+ if (input === "c" && key.ctrl) {
428
+ exit();
429
+ return;
430
+ }
431
+ if (quitDialogVisible) {
432
+ return;
433
+ }
434
+ if (helpVisible) {
435
+ if (key.escape || input === "?") {
436
+ setHelpVisible(false);
437
+ } else if (input === "q") {
438
+ setHelpVisible(false);
439
+ setQuitDialogVisible(true);
440
+ } else if (key.downArrow || input === "j") {
441
+ helpScrollRef.current?.scrollBy(1);
442
+ } else if (key.upArrow || input === "k") {
443
+ helpScrollRef.current?.scrollBy(-1);
444
+ }
445
+ return;
446
+ }
447
+ if (input === "?") {
448
+ setHelpVisible(true);
449
+ return;
450
+ }
451
+ if (input === "q") {
452
+ setQuitDialogVisible(true);
453
+ return;
454
+ }
455
+ if (key.tab) {
456
+ store.setFocusedPanel(state.focusedPanel === "list" ? "detail" : "list");
457
+ return;
458
+ }
459
+ if (FILTER_KEYS[input]) {
460
+ store.setFilterType(FILTER_KEYS[input]);
461
+ return;
462
+ }
463
+ if (state.filterActive) {
464
+ if (key.escape) {
465
+ store.setFilterActive(false);
466
+ return;
467
+ }
468
+ if (key.return) {
469
+ store.setFilterActive(false);
470
+ return;
471
+ }
472
+ if (key.backspace) {
473
+ store.setFilter(state.filterText.slice(0, -1));
474
+ return;
475
+ }
476
+ if (input && !key.ctrl && !key.meta) {
477
+ store.setFilter(state.filterText + input);
478
+ return;
479
+ }
480
+ return;
481
+ }
482
+ if (state.focusedPanel === "list") {
483
+ if (filteredNodes.length === 0) {
484
+ if (input === "/") {
485
+ store.setFilterActive(true);
486
+ }
487
+ return;
488
+ }
489
+ if (key.downArrow || input === "j") {
490
+ const next = Math.min(state.selectedIndex + 1, filteredNodes.length - 1);
491
+ store.setSelectedIndex(next);
492
+ scrollToIndex(next);
493
+ return;
494
+ }
495
+ if (key.upArrow || input === "k") {
496
+ const next = Math.max(state.selectedIndex - 1, 0);
497
+ store.setSelectedIndex(next);
498
+ scrollToIndex(next);
499
+ return;
500
+ }
501
+ if (key.pageDown) {
502
+ const next = Math.min(state.selectedIndex + 10, filteredNodes.length - 1);
503
+ store.setSelectedIndex(next);
504
+ scrollToIndex(next);
505
+ return;
506
+ }
507
+ if (key.pageUp) {
508
+ const next = Math.max(state.selectedIndex - 10, 0);
509
+ store.setSelectedIndex(next);
510
+ scrollToIndex(next);
511
+ return;
512
+ }
513
+ if (key.home) {
514
+ store.setSelectedIndex(0);
515
+ setListScrollOffset(0);
516
+ return;
517
+ }
518
+ if (key.end) {
519
+ const last = filteredNodes.length - 1;
520
+ store.setSelectedIndex(last);
521
+ scrollToIndex(last);
522
+ return;
523
+ }
524
+ if (input === "/") {
525
+ store.setFilterActive(true);
526
+ return;
527
+ }
528
+ if (key.rightArrow) {
529
+ store.setFocusedPanel("detail");
530
+ return;
531
+ }
532
+ return;
533
+ }
534
+ if (state.focusedPanel === "detail") {
535
+ if (key.escape || key.leftArrow) {
536
+ store.setFocusedPanel("list");
537
+ return;
538
+ }
539
+ if (key.downArrow || input === "j") {
540
+ detailScrollRef.current?.scrollBy(1);
541
+ return;
542
+ }
543
+ if (key.upArrow || input === "k") {
544
+ detailScrollRef.current?.scrollBy(-1);
545
+ return;
546
+ }
547
+ if (key.pageDown) {
548
+ detailScrollRef.current?.scrollBy(10);
549
+ return;
550
+ }
551
+ if (key.pageUp) {
552
+ detailScrollRef.current?.scrollBy(-10);
553
+ return;
554
+ }
555
+ if (key.home) {
556
+ detailScrollRef.current?.scrollToTop();
557
+ return;
558
+ }
559
+ if (key.end) {
560
+ detailScrollRef.current?.scrollToBottom();
561
+ }
562
+ }
563
+ },
564
+ { isActive: true }
565
+ );
566
+ if (columns < MIN_VIEWPORT_WIDTH || rows < MIN_VIEWPORT_HEIGHT) {
567
+ return jsx(Box, { alignItems: "center", height: rows, justifyContent: "center", width: columns, children: jsxs(Text, { color: "yellow", children: [
568
+ "Terminal too small (",
569
+ columns,
570
+ "x",
571
+ rows,
572
+ ")"
573
+ ] }) });
574
+ }
575
+ const isHorizontal = columns >= MIN_HORIZONTAL_WIDTH;
576
+ const footer = jsx(Box, { borderBottom: false, borderColor: "gray", borderLeft: false, borderRight: false, borderStyle: "single", flexShrink: 0, children: jsxs(Box, { flexWrap: "wrap", gap: 2, paddingX: 1, children: [
577
+ jsxs(Box, { gap: 1, children: [
578
+ jsx(Text, { bold: true, color: "white", children: "q" }),
579
+ jsx(Text, { dimColor: true, children: "QUIT" })
580
+ ] }, "q"),
581
+ jsxs(Box, { gap: 1, children: [
582
+ jsx(Text, { bold: true, color: "white", children: "?" }),
583
+ jsx(Text, { dimColor: true, children: "HELP" })
584
+ ] }, "?"),
585
+ jsxs(Box, { gap: 1, children: [
586
+ jsx(Text, { bold: true, color: "white", children: "↑↓" }),
587
+ jsx(Text, { dimColor: true, children: "NAV" })
588
+ ] }, "nav"),
589
+ jsxs(Box, { gap: 1, children: [
590
+ jsx(Text, { bold: true, color: "white", children: "1-3 /" }),
591
+ jsx(Text, { dimColor: true, children: "FILTER" })
592
+ ] }, "f"),
593
+ jsxs(Box, { gap: 1, children: [
594
+ jsx(Text, { bold: true, color: "white", children: "Tab" }),
595
+ jsx(Text, { dimColor: true, children: "PANEL" })
596
+ ] }, "t")
597
+ ] }) });
598
+ const helpPopup = jsxs(
599
+ Dialog,
600
+ {
601
+ footer: jsxs(Text, { dimColor: true, children: [
602
+ jsx(Text, { bold: true, color: "white", children: "↑↓" }),
603
+ " ",
604
+ "scroll",
605
+ " ",
606
+ jsx(Text, { bold: true, color: "white", children: "?" }),
607
+ "/",
608
+ jsx(Text, { bold: true, color: "white", children: "Esc" }),
609
+ " ",
610
+ "close"
611
+ ] }),
612
+ scrollRef: helpScrollRef,
613
+ title: "KEYBOARD SHORTCUTS",
614
+ visible: helpVisible,
615
+ width: 52,
616
+ children: [
617
+ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
618
+ jsxs(Box, { marginBottom: 1, children: [
619
+ jsx(Text, { dimColor: true, children: "── " }),
620
+ jsx(Text, { bold: true, color: "white", children: "NAVIGATION" })
621
+ ] }),
622
+ jsxs(Box, { children: [
623
+ jsx(Box, { width: 24, children: jsxs(Text, { children: [
624
+ jsxs(Text, { bold: true, color: "white", children: [
625
+ " ",
626
+ "↑",
627
+ "/k"
628
+ ] }),
629
+ jsx(Text, { dimColor: true, children: " Move up" })
630
+ ] }) }),
631
+ jsxs(Text, { children: [
632
+ jsxs(Text, { bold: true, color: "white", children: [
633
+ " ",
634
+ "↓",
635
+ "/j"
636
+ ] }),
637
+ jsx(Text, { dimColor: true, children: " Move down" })
638
+ ] })
639
+ ] }),
640
+ jsxs(Text, { children: [
641
+ jsxs(Text, { bold: true, color: "white", children: [
642
+ " ",
643
+ "Tab"
644
+ ] }),
645
+ jsx(Text, { dimColor: true, children: " Switch panel" })
646
+ ] }),
647
+ jsxs(Text, { children: [
648
+ jsxs(Text, { bold: true, color: "white", children: [
649
+ " ",
650
+ "→",
651
+ "/",
652
+ "←"
653
+ ] }),
654
+ jsx(Text, { dimColor: true, children: " Focus detail/list" })
655
+ ] }),
656
+ jsxs(Text, { children: [
657
+ jsxs(Text, { bold: true, color: "white", children: [
658
+ " ",
659
+ "PgUp/PgDn"
660
+ ] }),
661
+ jsx(Text, { dimColor: true, children: " Jump 10 items" })
662
+ ] }),
663
+ jsxs(Text, { children: [
664
+ jsxs(Text, { bold: true, color: "white", children: [
665
+ " ",
666
+ "Home/End"
667
+ ] }),
668
+ jsx(Text, { dimColor: true, children: " Jump to start/end" })
669
+ ] })
670
+ ] }),
671
+ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
672
+ jsxs(Box, { marginBottom: 1, children: [
673
+ jsx(Text, { dimColor: true, children: "── " }),
674
+ jsx(Text, { bold: true, color: "white", children: "FILTERS" })
675
+ ] }),
676
+ jsxs(Box, { children: [
677
+ jsx(Box, { width: 24, children: jsxs(Text, { children: [
678
+ jsxs(Text, { bold: true, color: "white", children: [
679
+ " ",
680
+ "1"
681
+ ] }),
682
+ jsx(Text, { dimColor: true, children: " All" })
683
+ ] }) }),
684
+ jsxs(Text, { children: [
685
+ jsxs(Text, { bold: true, color: "white", children: [
686
+ " ",
687
+ "2"
688
+ ] }),
689
+ jsx(Text, { dimColor: true, children: " Apps only" })
690
+ ] })
691
+ ] }),
692
+ jsxs(Text, { children: [
693
+ jsxs(Text, { bold: true, color: "white", children: [
694
+ " ",
695
+ "3"
696
+ ] }),
697
+ jsx(Text, { dimColor: true, children: " Libraries only" })
698
+ ] }),
699
+ jsxs(Text, { children: [
700
+ jsxs(Text, { bold: true, color: "white", children: [
701
+ " ",
702
+ "/"
703
+ ] }),
704
+ jsx(Text, { dimColor: true, children: " Text filter" })
705
+ ] })
706
+ ] }),
707
+ jsxs(Box, { flexDirection: "column", children: [
708
+ jsxs(Box, { marginBottom: 1, children: [
709
+ jsx(Text, { dimColor: true, children: "── " }),
710
+ jsx(Text, { bold: true, color: "white", children: "ACTIONS" })
711
+ ] }),
712
+ jsxs(Text, { children: [
713
+ jsxs(Text, { bold: true, color: "white", children: [
714
+ " ",
715
+ "q"
716
+ ] }),
717
+ jsx(Text, { dimColor: true, children: " Quit" })
718
+ ] }),
719
+ jsxs(Text, { children: [
720
+ jsxs(Text, { bold: true, color: "white", children: [
721
+ " ",
722
+ "?"
723
+ ] }),
724
+ jsx(Text, { dimColor: true, children: " Toggle help" })
725
+ ] })
726
+ ] })
727
+ ]
728
+ }
729
+ );
730
+ const listPanel = jsx(
731
+ ProjectListPanel,
732
+ {
733
+ filterActive: state.filterActive,
734
+ filterText: state.filterText,
735
+ filterType: state.filterType,
736
+ focused: state.focusedPanel === "list",
737
+ nodes: filteredNodes,
738
+ scrollOffset: listScrollOffset,
739
+ selectedIndex: state.selectedIndex,
740
+ stats,
741
+ viewportHeight: listViewportHeight
742
+ }
743
+ );
744
+ const detailPanel = jsx(ProjectDetailPanel, { focused: state.focusedPanel === "detail", node: selectedNode, scrollRef: detailScrollRef });
745
+ if (isHorizontal) {
746
+ const detailWidth = Math.floor(columns * 0.35);
747
+ return jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
748
+ jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [
749
+ jsx(Box, { flexGrow: 1, children: listPanel }),
750
+ jsx(Box, { width: detailWidth, children: detailPanel })
751
+ ] }),
752
+ footer,
753
+ jsx(
754
+ QuitDialog,
755
+ {
756
+ autoExitSeconds: autoExitSeconds ?? 3,
757
+ onCancel: () => {
758
+ setQuitDialogVisible(false);
759
+ },
760
+ visible: quitDialogVisible
761
+ }
762
+ ),
763
+ helpPopup
764
+ ] });
765
+ }
766
+ const listHeight = Math.floor(rows * 0.55);
767
+ return jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
768
+ jsx(Box, { height: listHeight, children: listPanel }),
769
+ jsx(Box, { flexGrow: 1, children: detailPanel }),
770
+ footer,
771
+ jsx(
772
+ QuitDialog,
773
+ {
774
+ autoExitSeconds: autoExitSeconds ?? 3,
775
+ onCancel: () => {
776
+ setQuitDialogVisible(false);
777
+ },
778
+ visible: quitDialogVisible
779
+ }
780
+ ),
781
+ helpPopup
782
+ ] });
783
+ };
784
+
785
+ var __freeze = Object.freeze;
786
+ var __defProp = Object.defineProperty;
787
+ var __template = (cooked, raw) => __freeze(__defProp(cooked, "raw", { value: __freeze(raw || cooked.slice()) }));
788
+ const printDepsTree = (name, prefix, isLast, nodes, printed, lines, maxDepth, currentDepth) => {
789
+ const connector = isLast ? dim("└── ") : dim("├── ");
790
+ const isDuplicate = printed.has(name);
791
+ const suffix = isDuplicate ? dim(" (*)") : "";
792
+ const node = nodes.get(name);
793
+ const isApp = node?.type === "application";
794
+ const displayName = isApp ? bold(name) : name;
795
+ lines.push(`${prefix}${connector}${displayName}${suffix}`);
796
+ if (isDuplicate) {
797
+ return;
798
+ }
799
+ printed.add(name);
800
+ const deps = node?.deps ?? [];
801
+ const childPrefix = isLast ? `${prefix} ` : `${prefix}${dim("│")} `;
802
+ if (currentDepth >= maxDepth && deps.length > 0) {
803
+ lines.push(`${childPrefix}${dim(`... ${deps.length} more`)}`);
804
+ return;
805
+ }
806
+ for (let i = 0; i < deps.length; i++) {
807
+ const dep = deps[i];
808
+ if (dep) {
809
+ printDepsTree(dep.target, childPrefix, i === deps.length - 1, nodes, printed, lines, maxDepth, currentDepth + 1);
810
+ }
811
+ }
812
+ };
813
+ const printRootProject = (name, nodes, printed, lines, maxDepth, indent) => {
814
+ const node = nodes.get(name);
815
+ const isApp = node?.type === "application";
816
+ const displayName = isApp ? bold(name) : name;
817
+ lines.push(`${indent}${displayName}`);
818
+ printed.add(name);
819
+ const deps = node?.deps ?? [];
820
+ if (deps.length === 0) {
821
+ lines.push(`${indent} ${dim("(no dependencies)")}`);
822
+ return;
823
+ }
824
+ if (maxDepth <= 0) {
825
+ lines.push(`${indent} ${dim(`... ${deps.length} dependencies`)}`);
826
+ return;
827
+ }
828
+ for (let i = 0; i < deps.length; i++) {
829
+ const dep = deps[i];
830
+ if (dep) {
831
+ printDepsTree(dep.target, indent, i === deps.length - 1, nodes, printed, lines, maxDepth, 1);
832
+ }
833
+ }
834
+ };
835
+ const projectGraphToAscii = (projectGraph, maxDepth) => {
836
+ const nodes = /* @__PURE__ */ new Map();
837
+ for (const [name, node] of Object.entries(projectGraph.nodes)) {
838
+ nodes.set(name, {
839
+ deps: (projectGraph.dependencies[name] ?? []).map((d) => {
840
+ return { target: d.target, type: d.type };
841
+ }),
842
+ name,
843
+ type: node.type
844
+ });
845
+ }
846
+ const apps = [];
847
+ const libs = [];
848
+ for (const [name, node] of nodes) {
849
+ if (node.type === "application") {
850
+ apps.push(name);
851
+ } else {
852
+ libs.push(name);
853
+ }
854
+ }
855
+ apps.sort();
856
+ libs.sort();
857
+ const totalPackages = apps.length + libs.length;
858
+ const totalDeps = Object.values(projectGraph.dependencies).reduce((sum, deps) => sum + deps.length, 0);
859
+ const lines = [];
860
+ lines.push(bold("Project Dependency Graph"), "");
861
+ if (apps.length > 0) {
862
+ lines.push(` ${bold(cyan(`Applications (${apps.length})`))}`, "");
863
+ for (const name of apps) {
864
+ const printed = /* @__PURE__ */ new Set();
865
+ printRootProject(name, nodes, printed, lines, maxDepth, " ");
866
+ lines.push("");
867
+ }
868
+ }
869
+ if (libs.length > 0) {
870
+ lines.push(` ${bold(cyan(`Libraries (${libs.length})`))}`, "");
871
+ for (const name of libs) {
872
+ const printed = /* @__PURE__ */ new Set();
873
+ printRootProject(name, nodes, printed, lines, maxDepth, " ");
874
+ lines.push("");
875
+ }
876
+ }
877
+ const width = process.stdout.columns || 80;
878
+ lines.push(dim("─".repeat(Math.min(width, 60))));
879
+ lines.push(
880
+ `${bold(String(totalPackages))} packages ${dim("·")} ${bold(String(totalDeps))} dependencies ${dim("·")} ${bold(String(apps.length))} apps${dim(",")} ${bold(String(libs.length))} libraries`
881
+ );
882
+ const allPrinted = /* @__PURE__ */ new Set();
883
+ let hasDuplicates = false;
884
+ for (const name of [...apps, ...libs]) {
885
+ const deps = nodes.get(name)?.deps ?? [];
886
+ for (const dep of deps) {
887
+ if (allPrinted.has(dep.target)) {
888
+ hasDuplicates = true;
889
+ }
890
+ allPrinted.add(dep.target);
891
+ }
892
+ allPrinted.add(name);
893
+ }
894
+ if (hasDuplicates) {
895
+ lines.push(dim("(*) = already shown above"));
896
+ }
897
+ return lines.join("\n");
898
+ };
899
+ const projectGraphToJson = (projectGraph) => {
900
+ const nodes = Object.values(projectGraph.nodes).map((node) => {
901
+ return {
902
+ name: node.name,
903
+ type: node.type
904
+ };
905
+ });
906
+ const edges = Object.values(projectGraph.dependencies).flat();
907
+ return { edges, nodes };
908
+ };
909
+ var _a;
910
+ const projectGraphToHtml = (projectGraph) => {
911
+ const nodes = Object.values(projectGraph.nodes).map((node) => {
912
+ return {
913
+ name: node.name,
914
+ type: node.type
915
+ };
916
+ });
917
+ const edges = [];
918
+ for (const deps of Object.values(projectGraph.dependencies)) {
919
+ for (const dep of deps) {
920
+ edges.push({ source: dep.source, target: dep.target, type: dep.type });
921
+ }
922
+ }
923
+ const apps = nodes.filter((n) => n.type === "application");
924
+ const libs = nodes.filter((n) => n.type !== "application");
925
+ const graphData = { apps: apps.length, edges, libs: libs.length, nodes };
926
+ return String.raw(_a || (_a = __template(['<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<title>Project Dependency Graph</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #e2e8f0; overflow: hidden; }\n svg { width: 100vw; height: 100vh; }\n .edge { fill: none; marker-end: url(#arrow); }\n .node rect { rx: 8; ry: 8; cursor: pointer; transition: stroke-width 0.15s; }\n .node text { font-size: 12px; font-weight: 600; pointer-events: none; }\n .node:hover rect { stroke-width: 2.5; stroke: #fff; }\n #info { position: fixed; top: 16px; right: 16px; background: #1e293b; padding: 14px 20px; border-radius: 10px; font-size: 13px; border: 1px solid #334155; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }\n #info b { font-variant-numeric: tabular-nums; }\n .app-count { color: #fbbf24; }\n .lib-count { color: #38bdf8; }\n .dep-count { color: #a78bfa; }\n #legend { position: fixed; bottom: 16px; left: 16px; background: #1e293b; padding: 12px 16px; border-radius: 10px; font-size: 12px; border: 1px solid #334155; display: flex; gap: 16px; align-items: center; }\n .legend-dot { width: 12px; height: 12px; border-radius: 3px; display: inline-block; vertical-align: middle; margin-right: 6px; }\n #tooltip { position: fixed; display: none; background: #1e293b; border: 1px solid #475569; border-radius: 8px; padding: 12px 16px; font-size: 12px; max-width: 320px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); z-index: 10; pointer-events: none; }\n #tooltip h3 { font-size: 14px; margin-bottom: 6px; }\n #tooltip .type-badge { display: inline-block; padding: 1px 8px; border-radius: 4px; font-size: 10px; font-weight: 700; text-transform: uppercase; margin-left: 8px; }\n #tooltip .dep-section { margin-top: 8px; color: #94a3b8; }\n #tooltip ul { list-style: none; padding-left: 0; margin-top: 4px; }\n #tooltip li { padding: 1px 0; color: #cbd5e1; }\n #tooltip li::before { content: "92 "; color: #64748b; }\n</style>\n</head>\n<body>\n<div id="info">\n <b class="app-count">', '</b> apps &middot;\n <b class="lib-count">', '</b> libraries &middot;\n <b class="dep-count">', '</b> dependencies\n</div>\n<div id="legend">\n <span><span class="legend-dot" style="background:#fbbf24"></span>Application</span>\n <span><span class="legend-dot" style="background:#38bdf8"></span>Library</span>\n <span style="color:#64748b">&mdash; solid = static &nbsp; - - - = implicit</span>\n</div>\n<div id="tooltip"></div>\n<svg id="graph">\n <defs>\n <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto">\n <path d="M 0 0 L 10 5 L 0 10 z" fill="#64748b"/>\n </marker>\n </defs>\n</svg>\n<script>\nconst data = ', ";\nconst svg = document.getElementById('graph');\nconst tooltip = document.getElementById('tooltip');\nconst W = window.innerWidth, H = window.innerHeight;\n\n// Build adjacency\nconst depMap = {}, rdepMap = {};\ndata.nodes.forEach(n => { depMap[n.name] = []; rdepMap[n.name] = []; });\ndata.edges.forEach(e => { depMap[e.source]?.push(e.target); rdepMap[e.target]?.push(e.source); });\n\n// Escape text for safe DOM insertion\nfunction esc(s) { const d = document.createElement('div'); d.textContent = s; return d.textContent; }\n\n// Force-directed layout\nconst repulsion = 5000 + data.nodes.length * 150;\nconst nodes = data.nodes.map(n => ({\n ...n, x: W/2 + (Math.random()-0.5)*Math.min(W*0.6, 600),\n y: H/2 + (Math.random()-0.5)*Math.min(H*0.6, 400), vx: 0, vy: 0\n}));\nconst nodeMap = new Map(nodes.map(n => [n.name, n]));\nconst edges = data.edges.map(e => ({\n source: nodeMap.get(e.source), target: nodeMap.get(e.target), type: e.type\n}));\n\nfor (let iter = 0; iter < 400; iter++) {\n for (let i = 0; i < nodes.length; i++) {\n for (let j = i+1; j < nodes.length; j++) {\n let dx = nodes[j].x - nodes[i].x, dy = nodes[j].y - nodes[i].y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = repulsion / (d * d);\n nodes[i].vx -= dx/d * f; nodes[i].vy -= dy/d * f;\n nodes[j].vx += dx/d * f; nodes[j].vy += dy/d * f;\n }\n }\n edges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n let dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = (d - 180) * 0.008;\n e.source.vx += dx/d * f; e.source.vy += dy/d * f;\n e.target.vx -= dx/d * f; e.target.vy -= dy/d * f;\n });\n nodes.forEach(n => {\n n.vx += (W/2 - n.x) * 0.001; n.vy += (H/2 - n.y) * 0.001;\n n.x += n.vx * 0.3; n.y += n.vy * 0.3;\n n.vx *= 0.75; n.vy *= 0.75;\n n.x = Math.max(80, Math.min(W-80, n.x));\n n.y = Math.max(40, Math.min(H-40, n.y));\n });\n}\n\n// Measure text widths\nconst measure = document.createElementNS('http://www.w3.org/2000/svg','text');\nmeasure.setAttribute('font-size','12'); measure.setAttribute('font-weight','600');\nmeasure.setAttribute('font-family','system-ui');\nsvg.appendChild(measure);\nconst widths = {};\nnodes.forEach(n => { measure.textContent = n.name; widths[n.name] = measure.getComputedTextLength(); });\nsvg.removeChild(measure);\n\n// Render edges\nedges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n const sw = (widths[e.source.name]||80)/2 + 12;\n const tw = (widths[e.target.name]||80)/2 + 12;\n const dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;\n const d = Math.sqrt(dx*dx+dy*dy)||1;\n const x1 = e.source.x + dx/d*sw, y1 = e.source.y + dy/d*14;\n const x2 = e.target.x - dx/d*tw, y2 = e.target.y - dy/d*14;\n const line = document.createElementNS('http://www.w3.org/2000/svg','line');\n line.setAttribute('x1',x1); line.setAttribute('y1',y1);\n line.setAttribute('x2',x2); line.setAttribute('y2',y2);\n line.setAttribute('class','edge');\n const edgeColors = { implicit: '#475569', devDependency: '#888888', peerDependency: '#CC8800' };\n line.setAttribute('stroke', edgeColors[e.type] || '#64748b');\n line.setAttribute('stroke-width', '1.5');\n if (e.type === 'implicit' || e.type === 'peerDependency') {\n line.setAttribute('stroke-dasharray', '6,4');\n }\n if (e.type === 'devDependency') {\n line.setAttribute('stroke-dasharray', '3,3');\n }\n svg.appendChild(line);\n});\n\n// Render nodes\nnodes.forEach(n => {\n const w = (widths[n.name]||80) + 24;\n const h = 32;\n const isApp = n.type === 'application';\n const fill = isApp ? '#fbbf24' : '#38bdf8';\n const textFill = '#0f172a';\n const stroke = isApp ? '#f59e0b' : '#0284c7';\n\n const g = document.createElementNS('http://www.w3.org/2000/svg','g');\n g.setAttribute('class','node');\n g.setAttribute('transform','translate('+(n.x - w/2)+','+(n.y - h/2)+')');\n const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');\n rect.setAttribute('width', w); rect.setAttribute('height', h);\n rect.setAttribute('fill', fill); rect.setAttribute('stroke', stroke); rect.setAttribute('stroke-width','1.5');\n g.appendChild(rect);\n const text = document.createElementNS('http://www.w3.org/2000/svg','text');\n text.setAttribute('x', w/2); text.setAttribute('y', h/2 + 4.5);\n text.setAttribute('text-anchor','middle'); text.setAttribute('fill', textFill);\n text.textContent = n.name;\n g.appendChild(text);\n\n g.addEventListener('mouseenter', (ev) => {\n const deps = depMap[n.name] || [];\n const rdeps = rdepMap[n.name] || [];\n\n // Build tooltip using safe DOM methods\n tooltip.textContent = '';\n\n const heading = document.createElement('h3');\n heading.textContent = n.name;\n const badge = document.createElement('span');\n badge.className = 'type-badge';\n badge.style.background = fill;\n badge.style.color = '#0f172a';\n badge.textContent = n.type;\n heading.appendChild(badge);\n tooltip.appendChild(heading);\n\n if (deps.length) {\n const label = document.createElement('div');\n label.className = 'dep-section';\n label.textContent = 'Depends on:';\n tooltip.appendChild(label);\n const ul = document.createElement('ul');\n deps.forEach(d => { const li = document.createElement('li'); li.textContent = d; ul.appendChild(li); });\n tooltip.appendChild(ul);\n }\n if (rdeps.length) {\n const label = document.createElement('div');\n label.className = 'dep-section';\n label.textContent = 'Required by:';\n tooltip.appendChild(label);\n const ul = document.createElement('ul');\n rdeps.forEach(d => { const li = document.createElement('li'); li.textContent = d; ul.appendChild(li); });\n tooltip.appendChild(ul);\n }\n if (!deps.length && !rdeps.length) {\n const empty = document.createElement('div');\n empty.style.marginTop = '6px';\n empty.style.color = '#64748b';\n empty.textContent = 'No dependencies';\n tooltip.appendChild(empty);\n }\n\n tooltip.style.display = 'block';\n const rect = tooltip.getBoundingClientRect();\n tooltip.style.left = Math.min(ev.clientX + 12, W - rect.width - 12) + 'px';\n tooltip.style.top = Math.min(ev.clientY + 12, H - rect.height - 12) + 'px';\n });\n g.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; });\n svg.appendChild(g);\n});\n<\/script>\n</body>\n</html>"], ['<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<title>Project Dependency Graph</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #e2e8f0; overflow: hidden; }\n svg { width: 100vw; height: 100vh; }\n .edge { fill: none; marker-end: url(#arrow); }\n .node rect { rx: 8; ry: 8; cursor: pointer; transition: stroke-width 0.15s; }\n .node text { font-size: 12px; font-weight: 600; pointer-events: none; }\n .node:hover rect { stroke-width: 2.5; stroke: #fff; }\n #info { position: fixed; top: 16px; right: 16px; background: #1e293b; padding: 14px 20px; border-radius: 10px; font-size: 13px; border: 1px solid #334155; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }\n #info b { font-variant-numeric: tabular-nums; }\n .app-count { color: #fbbf24; }\n .lib-count { color: #38bdf8; }\n .dep-count { color: #a78bfa; }\n #legend { position: fixed; bottom: 16px; left: 16px; background: #1e293b; padding: 12px 16px; border-radius: 10px; font-size: 12px; border: 1px solid #334155; display: flex; gap: 16px; align-items: center; }\n .legend-dot { width: 12px; height: 12px; border-radius: 3px; display: inline-block; vertical-align: middle; margin-right: 6px; }\n #tooltip { position: fixed; display: none; background: #1e293b; border: 1px solid #475569; border-radius: 8px; padding: 12px 16px; font-size: 12px; max-width: 320px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); z-index: 10; pointer-events: none; }\n #tooltip h3 { font-size: 14px; margin-bottom: 6px; }\n #tooltip .type-badge { display: inline-block; padding: 1px 8px; border-radius: 4px; font-size: 10px; font-weight: 700; text-transform: uppercase; margin-left: 8px; }\n #tooltip .dep-section { margin-top: 8px; color: #94a3b8; }\n #tooltip ul { list-style: none; padding-left: 0; margin-top: 4px; }\n #tooltip li { padding: 1px 0; color: #cbd5e1; }\n #tooltip li::before { content: "\\2192 "; color: #64748b; }\n</style>\n</head>\n<body>\n<div id="info">\n <b class="app-count">', '</b> apps &middot;\n <b class="lib-count">', '</b> libraries &middot;\n <b class="dep-count">', '</b> dependencies\n</div>\n<div id="legend">\n <span><span class="legend-dot" style="background:#fbbf24"></span>Application</span>\n <span><span class="legend-dot" style="background:#38bdf8"></span>Library</span>\n <span style="color:#64748b">&mdash; solid = static &nbsp; - - - = implicit</span>\n</div>\n<div id="tooltip"></div>\n<svg id="graph">\n <defs>\n <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto">\n <path d="M 0 0 L 10 5 L 0 10 z" fill="#64748b"/>\n </marker>\n </defs>\n</svg>\n<script>\nconst data = ', ";\nconst svg = document.getElementById('graph');\nconst tooltip = document.getElementById('tooltip');\nconst W = window.innerWidth, H = window.innerHeight;\n\n// Build adjacency\nconst depMap = {}, rdepMap = {};\ndata.nodes.forEach(n => { depMap[n.name] = []; rdepMap[n.name] = []; });\ndata.edges.forEach(e => { depMap[e.source]?.push(e.target); rdepMap[e.target]?.push(e.source); });\n\n// Escape text for safe DOM insertion\nfunction esc(s) { const d = document.createElement('div'); d.textContent = s; return d.textContent; }\n\n// Force-directed layout\nconst repulsion = 5000 + data.nodes.length * 150;\nconst nodes = data.nodes.map(n => ({\n ...n, x: W/2 + (Math.random()-0.5)*Math.min(W*0.6, 600),\n y: H/2 + (Math.random()-0.5)*Math.min(H*0.6, 400), vx: 0, vy: 0\n}));\nconst nodeMap = new Map(nodes.map(n => [n.name, n]));\nconst edges = data.edges.map(e => ({\n source: nodeMap.get(e.source), target: nodeMap.get(e.target), type: e.type\n}));\n\nfor (let iter = 0; iter < 400; iter++) {\n for (let i = 0; i < nodes.length; i++) {\n for (let j = i+1; j < nodes.length; j++) {\n let dx = nodes[j].x - nodes[i].x, dy = nodes[j].y - nodes[i].y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = repulsion / (d * d);\n nodes[i].vx -= dx/d * f; nodes[i].vy -= dy/d * f;\n nodes[j].vx += dx/d * f; nodes[j].vy += dy/d * f;\n }\n }\n edges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n let dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;\n let d = Math.sqrt(dx*dx + dy*dy) || 1;\n let f = (d - 180) * 0.008;\n e.source.vx += dx/d * f; e.source.vy += dy/d * f;\n e.target.vx -= dx/d * f; e.target.vy -= dy/d * f;\n });\n nodes.forEach(n => {\n n.vx += (W/2 - n.x) * 0.001; n.vy += (H/2 - n.y) * 0.001;\n n.x += n.vx * 0.3; n.y += n.vy * 0.3;\n n.vx *= 0.75; n.vy *= 0.75;\n n.x = Math.max(80, Math.min(W-80, n.x));\n n.y = Math.max(40, Math.min(H-40, n.y));\n });\n}\n\n// Measure text widths\nconst measure = document.createElementNS('http://www.w3.org/2000/svg','text');\nmeasure.setAttribute('font-size','12'); measure.setAttribute('font-weight','600');\nmeasure.setAttribute('font-family','system-ui');\nsvg.appendChild(measure);\nconst widths = {};\nnodes.forEach(n => { measure.textContent = n.name; widths[n.name] = measure.getComputedTextLength(); });\nsvg.removeChild(measure);\n\n// Render edges\nedges.forEach(e => {\n if (!e.source || !e.target) {\n return;\n }\n const sw = (widths[e.source.name]||80)/2 + 12;\n const tw = (widths[e.target.name]||80)/2 + 12;\n const dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;\n const d = Math.sqrt(dx*dx+dy*dy)||1;\n const x1 = e.source.x + dx/d*sw, y1 = e.source.y + dy/d*14;\n const x2 = e.target.x - dx/d*tw, y2 = e.target.y - dy/d*14;\n const line = document.createElementNS('http://www.w3.org/2000/svg','line');\n line.setAttribute('x1',x1); line.setAttribute('y1',y1);\n line.setAttribute('x2',x2); line.setAttribute('y2',y2);\n line.setAttribute('class','edge');\n const edgeColors = { implicit: '#475569', devDependency: '#888888', peerDependency: '#CC8800' };\n line.setAttribute('stroke', edgeColors[e.type] || '#64748b');\n line.setAttribute('stroke-width', '1.5');\n if (e.type === 'implicit' || e.type === 'peerDependency') {\n line.setAttribute('stroke-dasharray', '6,4');\n }\n if (e.type === 'devDependency') {\n line.setAttribute('stroke-dasharray', '3,3');\n }\n svg.appendChild(line);\n});\n\n// Render nodes\nnodes.forEach(n => {\n const w = (widths[n.name]||80) + 24;\n const h = 32;\n const isApp = n.type === 'application';\n const fill = isApp ? '#fbbf24' : '#38bdf8';\n const textFill = '#0f172a';\n const stroke = isApp ? '#f59e0b' : '#0284c7';\n\n const g = document.createElementNS('http://www.w3.org/2000/svg','g');\n g.setAttribute('class','node');\n g.setAttribute('transform','translate('+(n.x - w/2)+','+(n.y - h/2)+')');\n const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');\n rect.setAttribute('width', w); rect.setAttribute('height', h);\n rect.setAttribute('fill', fill); rect.setAttribute('stroke', stroke); rect.setAttribute('stroke-width','1.5');\n g.appendChild(rect);\n const text = document.createElementNS('http://www.w3.org/2000/svg','text');\n text.setAttribute('x', w/2); text.setAttribute('y', h/2 + 4.5);\n text.setAttribute('text-anchor','middle'); text.setAttribute('fill', textFill);\n text.textContent = n.name;\n g.appendChild(text);\n\n g.addEventListener('mouseenter', (ev) => {\n const deps = depMap[n.name] || [];\n const rdeps = rdepMap[n.name] || [];\n\n // Build tooltip using safe DOM methods\n tooltip.textContent = '';\n\n const heading = document.createElement('h3');\n heading.textContent = n.name;\n const badge = document.createElement('span');\n badge.className = 'type-badge';\n badge.style.background = fill;\n badge.style.color = '#0f172a';\n badge.textContent = n.type;\n heading.appendChild(badge);\n tooltip.appendChild(heading);\n\n if (deps.length) {\n const label = document.createElement('div');\n label.className = 'dep-section';\n label.textContent = 'Depends on:';\n tooltip.appendChild(label);\n const ul = document.createElement('ul');\n deps.forEach(d => { const li = document.createElement('li'); li.textContent = d; ul.appendChild(li); });\n tooltip.appendChild(ul);\n }\n if (rdeps.length) {\n const label = document.createElement('div');\n label.className = 'dep-section';\n label.textContent = 'Required by:';\n tooltip.appendChild(label);\n const ul = document.createElement('ul');\n rdeps.forEach(d => { const li = document.createElement('li'); li.textContent = d; ul.appendChild(li); });\n tooltip.appendChild(ul);\n }\n if (!deps.length && !rdeps.length) {\n const empty = document.createElement('div');\n empty.style.marginTop = '6px';\n empty.style.color = '#64748b';\n empty.textContent = 'No dependencies';\n tooltip.appendChild(empty);\n }\n\n tooltip.style.display = 'block';\n const rect = tooltip.getBoundingClientRect();\n tooltip.style.left = Math.min(ev.clientX + 12, W - rect.width - 12) + 'px';\n tooltip.style.top = Math.min(ev.clientY + 12, H - rect.height - 12) + 'px';\n });\n g.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; });\n svg.appendChild(g);\n});\n<\/script>\n</body>\n</html>"])), graphData.apps, graphData.libs, graphData.edges.length, JSON.stringify(graphData).replaceAll("</", String.raw`<\/`));
927
+ };
928
+ const execute = async ({ logger, options, visConfig, workspaceRoot: wsRoot }) => {
929
+ if (!wsRoot) {
930
+ throw new Error("Could not determine workspace root. Run this command inside a monorepo.");
931
+ }
932
+ const workspaceRoot = wsRoot;
933
+ const { packageJsons, workspace } = discoverWorkspace(workspaceRoot, visConfig);
934
+ const projectGraph = buildProjectGraph(workspaceRoot, workspace, packageJsons);
935
+ const isTTY = Boolean(process.stdout.isTTY) && !isInCi;
936
+ const format = options.format ?? (isTTY ? "tui" : "ascii");
937
+ const outputFile = options.output;
938
+ const maxDepth = options.depth ?? Infinity;
939
+ let output;
940
+ switch (format) {
941
+ case "dot": {
942
+ output = projectGraphToDot(projectGraph);
943
+ break;
944
+ }
945
+ case "html": {
946
+ output = projectGraphToHtml(projectGraph);
947
+ break;
948
+ }
949
+ case "json": {
950
+ output = JSON.stringify(projectGraphToJson(projectGraph), void 0, 2);
951
+ break;
952
+ }
953
+ case "tui": {
954
+ if (!isTTY) {
955
+ output = projectGraphToAscii(projectGraph, maxDepth);
956
+ break;
957
+ }
958
+ const autoExitSeconds = visConfig?.tui?.autoExit === true ? 3 : typeof visConfig?.tui?.autoExit === "number" ? visConfig.tui.autoExit : 0;
959
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
960
+ process.stdin.setRawMode(true);
961
+ process.stdin.ref();
962
+ process.stdin.resume();
963
+ }
964
+ const keepAlive = setInterval(() => {
965
+ }, 1e3);
966
+ const store = new GraphStore(projectGraph);
967
+ const instance = render(React.createElement(VisGraphApp, { autoExitSeconds, store }), {
968
+ alternateScreen: true,
969
+ exitOnCtrlC: false,
970
+ interactive: true,
971
+ patchConsole: true
972
+ });
973
+ await instance.waitUntilExit();
974
+ clearInterval(keepAlive);
975
+ return;
976
+ }
977
+ default: {
978
+ output = projectGraphToAscii(projectGraph, maxDepth);
979
+ }
980
+ }
981
+ if (outputFile) {
982
+ writeFileSync(outputFile, output, "utf8");
983
+ logger.info(`Graph written to ${outputFile}`);
984
+ } else {
985
+ logger.info(output);
986
+ }
987
+ };
988
+
989
+ export { execute as default };