@alepha/devtools 0.13.5 → 0.13.7

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 (62) hide show
  1. package/dist/{index.d.mts → index.d.ts} +250 -32
  2. package/dist/{index.mjs → index.js} +254 -22
  3. package/dist/index.js.map +1 -0
  4. package/package.json +12 -6
  5. package/src/{DevToolsProvider.ts → api/DevToolsProvider.ts} +29 -1
  6. package/src/{providers → api/providers}/DevToolsMetadataProvider.ts +210 -2
  7. package/src/api/schemas/DevAtomMetadata.ts +26 -0
  8. package/src/api/schemas/DevCommandMetadata.ts +9 -0
  9. package/src/api/schemas/DevEntityMetadata.ts +57 -0
  10. package/src/api/schemas/DevEnvMetadata.ts +22 -0
  11. package/src/{schemas → api/schemas}/DevMetadata.ts +10 -1
  12. package/src/api/schemas/DevRouteMetadata.ts +8 -0
  13. package/src/index.ts +23 -16
  14. package/src/ui/AppRouter.tsx +85 -2
  15. package/src/ui/components/DevAtomsViewer.tsx +636 -0
  16. package/src/ui/components/DevCacheInspector.tsx +423 -0
  17. package/src/ui/components/DevDashboard.tsx +188 -0
  18. package/src/ui/components/DevEnvExplorer.tsx +462 -0
  19. package/src/ui/components/DevLayout.tsx +65 -4
  20. package/src/ui/components/DevLogViewer.tsx +161 -163
  21. package/src/ui/components/DevQueueMonitor.tsx +51 -0
  22. package/src/ui/components/DevTopicsViewer.tsx +690 -0
  23. package/src/ui/components/actions/ActionGroup.tsx +37 -0
  24. package/src/ui/components/actions/ActionItem.tsx +138 -0
  25. package/src/ui/components/actions/DevActionsExplorer.tsx +132 -0
  26. package/src/ui/components/actions/MethodBadge.tsx +18 -0
  27. package/src/ui/components/actions/SchemaViewer.tsx +21 -0
  28. package/src/ui/components/actions/TryItPanel.tsx +140 -0
  29. package/src/ui/components/actions/constants.ts +7 -0
  30. package/src/ui/components/actions/helpers.ts +18 -0
  31. package/src/ui/components/actions/index.ts +8 -0
  32. package/src/ui/components/db/ColumnBadge.tsx +55 -0
  33. package/src/ui/components/db/DevDbStudio.tsx +485 -0
  34. package/src/ui/components/db/constants.ts +11 -0
  35. package/src/ui/components/db/index.ts +4 -0
  36. package/src/ui/components/db/types.ts +7 -0
  37. package/src/ui/components/graph/DevDependencyGraph.tsx +358 -0
  38. package/src/ui/components/graph/GraphControls.tsx +162 -0
  39. package/src/ui/components/graph/NodeDetails.tsx +181 -0
  40. package/src/ui/components/graph/ProviderNode.tsx +97 -0
  41. package/src/ui/components/graph/constants.ts +35 -0
  42. package/src/ui/components/graph/helpers.ts +443 -0
  43. package/src/ui/components/graph/index.ts +7 -0
  44. package/src/ui/components/graph/types.ts +28 -0
  45. package/src/ui/styles.css +0 -6
  46. package/src/ui/resources/wotfardregular/stylesheet.css +0 -12
  47. package/src/ui/resources/wotfardregular/wotfard-regular-webfont.eot +0 -0
  48. package/src/ui/resources/wotfardregular/wotfard-regular-webfont.ttf +0 -0
  49. package/src/ui/resources/wotfardregular/wotfard-regular-webfont.woff2 +0 -0
  50. /package/src/{entities → api/entities}/logs.ts +0 -0
  51. /package/src/{providers → api/providers}/DevToolsDatabaseProvider.ts +0 -0
  52. /package/src/{repositories → api/repositories}/LogRepository.ts +0 -0
  53. /package/src/{schemas → api/schemas}/DevActionMetadata.ts +0 -0
  54. /package/src/{schemas → api/schemas}/DevBucketMetadata.ts +0 -0
  55. /package/src/{schemas → api/schemas}/DevCacheMetadata.ts +0 -0
  56. /package/src/{schemas → api/schemas}/DevModuleMetadata.ts +0 -0
  57. /package/src/{schemas → api/schemas}/DevPageMetadata.ts +0 -0
  58. /package/src/{schemas → api/schemas}/DevProviderMetadata.ts +0 -0
  59. /package/src/{schemas → api/schemas}/DevQueueMetadata.ts +0 -0
  60. /package/src/{schemas → api/schemas}/DevRealmMetadata.ts +0 -0
  61. /package/src/{schemas → api/schemas}/DevSchedulerMetadata.ts +0 -0
  62. /package/src/{schemas → api/schemas}/DevTopicMetadata.ts +0 -0
@@ -0,0 +1,97 @@
1
+ import { ui } from "@alepha/ui";
2
+ import { Badge, Box, Flex, Text } from "@mantine/core";
3
+ import { Handle, Position } from "@xyflow/react";
4
+ import { getModuleColor } from "./constants.ts";
5
+ import type { ProviderNodeData } from "./types.ts";
6
+
7
+ interface ProviderNodeProps {
8
+ data: ProviderNodeData;
9
+ selected?: boolean;
10
+ }
11
+
12
+ export const ProviderNode = ({ data, selected }: ProviderNodeProps) => {
13
+ const moduleColor = getModuleColor(data.module);
14
+ const isFaded = data.isFaded && !data.isHighlighted;
15
+ const isModule = data.isModule;
16
+
17
+ return (
18
+ <Box
19
+ p="xs"
20
+ style={{
21
+ borderRadius: isModule ? 12 : 8,
22
+ border: `2px solid ${selected || data.isSelected ? moduleColor : ui.colors.border}`,
23
+ backgroundColor: ui.colors.surface,
24
+ minWidth: isModule ? 200 : 160,
25
+ opacity: isFaded ? 0.3 : 1,
26
+ transition: "opacity 0.2s, border-color 0.2s",
27
+ boxShadow: data.isHighlighted ? `0 0 10px ${moduleColor}` : undefined,
28
+ }}
29
+ >
30
+ <Handle
31
+ type="target"
32
+ position={Position.Top}
33
+ style={{
34
+ background: moduleColor,
35
+ width: 8,
36
+ height: 8,
37
+ border: "none",
38
+ }}
39
+ />
40
+
41
+ <Flex direction="column" gap={4}>
42
+ {isModule ? (
43
+ <>
44
+ <Text size="xs" fw={600} style={{ wordBreak: "break-word" }}>
45
+ {data.label}
46
+ </Text>
47
+ <Text size="xs" c="dimmed">
48
+ {data.providerCount} services
49
+ </Text>
50
+ </>
51
+ ) : (
52
+ <>
53
+ <Text size="xs" fw={600} style={{ wordBreak: "break-word" }}>
54
+ {data.label.split(".").pop()}
55
+ </Text>
56
+ {data.module && (
57
+ <Badge
58
+ size="xs"
59
+ variant="light"
60
+ style={{
61
+ backgroundColor: `${moduleColor}20`,
62
+ color: moduleColor,
63
+ }}
64
+ >
65
+ {data.module}
66
+ </Badge>
67
+ )}
68
+ </>
69
+ )}
70
+
71
+ <Flex gap={4}>
72
+ {data.dependencies.length > 0 && (
73
+ <Text size="xs" c="dimmed">
74
+ {data.dependencies.length} deps
75
+ </Text>
76
+ )}
77
+ {data.dependents.length > 0 && (
78
+ <Text size="xs" c="dimmed">
79
+ {data.dependents.length} refs
80
+ </Text>
81
+ )}
82
+ </Flex>
83
+ </Flex>
84
+
85
+ <Handle
86
+ type="source"
87
+ position={Position.Bottom}
88
+ style={{
89
+ background: moduleColor,
90
+ width: 8,
91
+ height: 8,
92
+ border: "none",
93
+ }}
94
+ />
95
+ </Box>
96
+ );
97
+ };
@@ -0,0 +1,35 @@
1
+ export const MODULE_COLORS: Record<string, string> = {
2
+ "alepha.core": "#868e96",
3
+ "alepha.server": "#228be6",
4
+ "alepha.security": "#fa5252",
5
+ "alepha.orm": "#40c057",
6
+ "alepha.cache": "#fab005",
7
+ "alepha.queue": "#fd7e14",
8
+ "alepha.topic": "#be4bdb",
9
+ "alepha.bucket": "#15aabf",
10
+ "alepha.scheduler": "#e64980",
11
+ "alepha.logger": "#74c0fc",
12
+ "alepha.devtools": "#845ef7",
13
+ };
14
+
15
+ export const DEFAULT_MODULE_COLOR = "#495057";
16
+
17
+ export const getModuleColor = (module?: string): string => {
18
+ if (!module) return DEFAULT_MODULE_COLOR;
19
+
20
+ // Check for exact match first
21
+ if (MODULE_COLORS[module]) return MODULE_COLORS[module];
22
+
23
+ // Check for prefix match (e.g., "alepha.core.something" -> "alepha.core")
24
+ for (const [key, color] of Object.entries(MODULE_COLORS)) {
25
+ if (module.startsWith(key)) return color;
26
+ }
27
+
28
+ // Generate consistent color from module name
29
+ let hash = 0;
30
+ for (let i = 0; i < module.length; i++) {
31
+ hash = module.charCodeAt(i) + ((hash << 5) - hash);
32
+ }
33
+ const hue = Math.abs(hash % 360);
34
+ return `hsl(${hue}, 60%, 50%)`;
35
+ };
@@ -0,0 +1,443 @@
1
+ import type { DevProviderMetadata } from "../../../api/schemas/DevProviderMetadata.ts";
2
+ import type {
3
+ GraphFilters,
4
+ LayoutType,
5
+ ProviderEdge,
6
+ ProviderNode,
7
+ } from "./types.ts";
8
+
9
+ export const buildProviderGraph = (
10
+ providers: DevProviderMetadata[],
11
+ filters: GraphFilters,
12
+ ): { nodes: ProviderNode[]; edges: ProviderEdge[] } => {
13
+ // Build dependents map (reverse of dependencies)
14
+ const dependentsMap = new Map<string, string[]>();
15
+ for (const provider of providers) {
16
+ for (const dep of provider.dependencies) {
17
+ const dependents = dependentsMap.get(dep) || [];
18
+ dependents.push(provider.name);
19
+ dependentsMap.set(dep, dependents);
20
+ }
21
+ }
22
+
23
+ // Filter providers
24
+ const filtered = providers.filter((p) => {
25
+ if (filters.hideFramework && p.module?.startsWith("alepha.")) {
26
+ return false;
27
+ }
28
+ if (
29
+ filters.module &&
30
+ filters.module !== "all" &&
31
+ p.module !== filters.module
32
+ ) {
33
+ return false;
34
+ }
35
+ if (filters.search) {
36
+ const searchLower = filters.search.toLowerCase();
37
+ return (
38
+ p.name.toLowerCase().includes(searchLower) ||
39
+ p.module?.toLowerCase().includes(searchLower)
40
+ );
41
+ }
42
+ return true;
43
+ });
44
+
45
+ const filteredNames = new Set(filtered.map((p) => p.name));
46
+
47
+ // Create nodes
48
+ const nodes: ProviderNode[] = filtered.map((provider) => ({
49
+ id: provider.name,
50
+ type: "provider",
51
+ position: { x: 0, y: 0 },
52
+ data: {
53
+ label: provider.name,
54
+ module: provider.module,
55
+ dependencies: provider.dependencies,
56
+ dependents: dependentsMap.get(provider.name) || [],
57
+ aliases: provider.aliases,
58
+ isModule: false,
59
+ },
60
+ }));
61
+
62
+ // Create edges (only between visible nodes)
63
+ const edges: ProviderEdge[] = [];
64
+ for (const provider of filtered) {
65
+ for (const dep of provider.dependencies) {
66
+ if (filteredNames.has(dep)) {
67
+ edges.push({
68
+ id: `${provider.name}->${dep}`,
69
+ source: provider.name,
70
+ target: dep,
71
+ animated: false,
72
+ style: { stroke: "#495057", strokeWidth: 1.5 },
73
+ });
74
+ }
75
+ }
76
+ }
77
+
78
+ return { nodes, edges };
79
+ };
80
+
81
+ export const buildModuleGraph = (
82
+ providers: DevProviderMetadata[],
83
+ filters: GraphFilters,
84
+ ): { nodes: ProviderNode[]; edges: ProviderEdge[] } => {
85
+ // Group providers by module
86
+ const moduleMap = new Map<string, DevProviderMetadata[]>();
87
+ for (const provider of providers) {
88
+ const module = provider.module || "Other";
89
+ if (filters.hideFramework && module.startsWith("alepha.")) {
90
+ continue;
91
+ }
92
+ if (filters.search) {
93
+ const searchLower = filters.search.toLowerCase();
94
+ if (!module.toLowerCase().includes(searchLower)) {
95
+ continue;
96
+ }
97
+ }
98
+ const list = moduleMap.get(module) || [];
99
+ list.push(provider);
100
+ moduleMap.set(module, list);
101
+ }
102
+
103
+ // Build module dependencies and dependents
104
+ const moduleDeps = new Map<string, Set<string>>();
105
+ const moduleDependents = new Map<string, Set<string>>();
106
+
107
+ for (const [module, moduleProviders] of moduleMap) {
108
+ moduleDeps.set(module, new Set());
109
+ moduleDependents.set(module, new Set());
110
+ }
111
+
112
+ for (const [module, moduleProviders] of moduleMap) {
113
+ for (const provider of moduleProviders) {
114
+ for (const dep of provider.dependencies) {
115
+ // Find which module this dependency belongs to
116
+ const depProvider = providers.find((p) => p.name === dep);
117
+ if (depProvider) {
118
+ const depModule = depProvider.module || "Other";
119
+ if (depModule !== module && moduleMap.has(depModule)) {
120
+ moduleDeps.get(module)?.add(depModule);
121
+ moduleDependents.get(depModule)?.add(module);
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ // Filter by selected module
129
+ let filteredModules = Array.from(moduleMap.keys());
130
+ if (filters.module && filters.module !== "all") {
131
+ filteredModules = filteredModules.filter((m) => m === filters.module);
132
+ }
133
+ const filteredModuleSet = new Set(filteredModules);
134
+
135
+ // Create module nodes
136
+ const nodes: ProviderNode[] = filteredModules.map((module) => {
137
+ const moduleProviders = moduleMap.get(module) || [];
138
+ return {
139
+ id: module,
140
+ type: "provider",
141
+ position: { x: 0, y: 0 },
142
+ data: {
143
+ label: module,
144
+ module: module,
145
+ dependencies: Array.from(moduleDeps.get(module) || []),
146
+ dependents: Array.from(moduleDependents.get(module) || []),
147
+ providers: moduleProviders.map((p) => p.name),
148
+ providerCount: moduleProviders.length,
149
+ isModule: true,
150
+ },
151
+ };
152
+ });
153
+
154
+ // Create edges between modules
155
+ const edges: ProviderEdge[] = [];
156
+ const edgeSet = new Set<string>();
157
+
158
+ for (const module of filteredModules) {
159
+ for (const dep of moduleDeps.get(module) || []) {
160
+ if (filteredModuleSet.has(dep)) {
161
+ const edgeId = `${module}->${dep}`;
162
+ if (!edgeSet.has(edgeId)) {
163
+ edgeSet.add(edgeId);
164
+ edges.push({
165
+ id: edgeId,
166
+ source: module,
167
+ target: dep,
168
+ animated: false,
169
+ style: { stroke: "#495057", strokeWidth: 1.5 },
170
+ });
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ return { nodes, edges };
177
+ };
178
+
179
+ export const buildGraph = (
180
+ providers: DevProviderMetadata[],
181
+ filters: GraphFilters,
182
+ ): { nodes: ProviderNode[]; edges: ProviderEdge[] } => {
183
+ if (filters.viewMode === "modules") {
184
+ return buildModuleGraph(providers, filters);
185
+ }
186
+ return buildProviderGraph(providers, filters);
187
+ };
188
+
189
+ export const applyDagreLayout = (
190
+ nodes: ProviderNode[],
191
+ edges: ProviderEdge[],
192
+ direction: "TB" | "LR" = "TB",
193
+ ): ProviderNode[] => {
194
+ // Simple grid layout as fallback (dagre would need additional library)
195
+ const nodeWidth = 180;
196
+ const nodeHeight = 60;
197
+ const horizontalSpacing = 50;
198
+ const verticalSpacing = 80;
199
+
200
+ // Build adjacency list and calculate levels
201
+ const adj = new Map<string, string[]>();
202
+ const inDegree = new Map<string, number>();
203
+
204
+ for (const node of nodes) {
205
+ adj.set(node.id, []);
206
+ inDegree.set(node.id, 0);
207
+ }
208
+
209
+ for (const edge of edges) {
210
+ adj.get(edge.source)?.push(edge.target);
211
+ inDegree.set(edge.target, (inDegree.get(edge.target) || 0) + 1);
212
+ }
213
+
214
+ // Topological sort to get levels
215
+ const levels = new Map<string, number>();
216
+ const queue: string[] = [];
217
+
218
+ for (const [id, degree] of inDegree) {
219
+ if (degree === 0) {
220
+ queue.push(id);
221
+ levels.set(id, 0);
222
+ }
223
+ }
224
+
225
+ while (queue.length > 0) {
226
+ const current = queue.shift()!;
227
+ const currentLevel = levels.get(current) || 0;
228
+
229
+ for (const neighbor of adj.get(current) || []) {
230
+ const newDegree = (inDegree.get(neighbor) || 0) - 1;
231
+ inDegree.set(neighbor, newDegree);
232
+
233
+ if (newDegree === 0) {
234
+ queue.push(neighbor);
235
+ levels.set(neighbor, currentLevel + 1);
236
+ }
237
+ }
238
+ }
239
+
240
+ // Handle cycles - assign remaining nodes to max level + 1
241
+ const maxLevel = Math.max(...Array.from(levels.values()), 0);
242
+ for (const node of nodes) {
243
+ if (!levels.has(node.id)) {
244
+ levels.set(node.id, maxLevel + 1);
245
+ }
246
+ }
247
+
248
+ // Group nodes by level
249
+ const levelGroups = new Map<number, string[]>();
250
+ for (const [id, level] of levels) {
251
+ const group = levelGroups.get(level) || [];
252
+ group.push(id);
253
+ levelGroups.set(level, group);
254
+ }
255
+
256
+ // Position nodes
257
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
258
+ const positioned: ProviderNode[] = [];
259
+
260
+ for (const [level, ids] of levelGroups) {
261
+ const levelWidth = ids.length * (nodeWidth + horizontalSpacing);
262
+ const startX = -levelWidth / 2;
263
+
264
+ ids.forEach((id, index) => {
265
+ const node = nodeMap.get(id);
266
+ if (node) {
267
+ positioned.push({
268
+ ...node,
269
+ position: {
270
+ x:
271
+ direction === "TB"
272
+ ? startX + index * (nodeWidth + horizontalSpacing)
273
+ : level * (nodeWidth + horizontalSpacing),
274
+ y:
275
+ direction === "TB"
276
+ ? level * (nodeHeight + verticalSpacing)
277
+ : startX + index * (nodeHeight + verticalSpacing),
278
+ },
279
+ });
280
+ }
281
+ });
282
+ }
283
+
284
+ return positioned;
285
+ };
286
+
287
+ export const applyForceLayout = (nodes: ProviderNode[]): ProviderNode[] => {
288
+ // Simple circular layout for force simulation
289
+ const radius = Math.max(200, nodes.length * 30);
290
+ const angleStep = (2 * Math.PI) / nodes.length;
291
+
292
+ return nodes.map((node, index) => ({
293
+ ...node,
294
+ position: {
295
+ x: Math.cos(index * angleStep) * radius,
296
+ y: Math.sin(index * angleStep) * radius,
297
+ },
298
+ }));
299
+ };
300
+
301
+ export const applyCircularLayout = (
302
+ nodes: ProviderNode[],
303
+ groupByModule: boolean = true,
304
+ ): ProviderNode[] => {
305
+ if (!groupByModule) {
306
+ return applyForceLayout(nodes);
307
+ }
308
+
309
+ // Group by module
310
+ const moduleGroups = new Map<string, ProviderNode[]>();
311
+ for (const node of nodes) {
312
+ const module = node.data.module || "Other";
313
+ const group = moduleGroups.get(module) || [];
314
+ group.push(node);
315
+ moduleGroups.set(module, group);
316
+ }
317
+
318
+ const modules = Array.from(moduleGroups.keys()).sort();
319
+ const moduleAngleStep = (2 * Math.PI) / modules.length;
320
+ const moduleRadius = Math.max(300, modules.length * 80);
321
+
322
+ const positioned: ProviderNode[] = [];
323
+
324
+ modules.forEach((module, moduleIndex) => {
325
+ const moduleNodes = moduleGroups.get(module) || [];
326
+ const moduleAngle = moduleIndex * moduleAngleStep;
327
+ const moduleCenterX = Math.cos(moduleAngle) * moduleRadius;
328
+ const moduleCenterY = Math.sin(moduleAngle) * moduleRadius;
329
+
330
+ const innerRadius = Math.max(100, moduleNodes.length * 25);
331
+ const nodeAngleStep = (2 * Math.PI) / moduleNodes.length;
332
+
333
+ moduleNodes.forEach((node, nodeIndex) => {
334
+ const nodeAngle = nodeIndex * nodeAngleStep;
335
+ positioned.push({
336
+ ...node,
337
+ position: {
338
+ x: moduleCenterX + Math.cos(nodeAngle) * innerRadius,
339
+ y: moduleCenterY + Math.sin(nodeAngle) * innerRadius,
340
+ },
341
+ });
342
+ });
343
+ });
344
+
345
+ return positioned;
346
+ };
347
+
348
+ export const applyLayout = (
349
+ nodes: ProviderNode[],
350
+ edges: ProviderEdge[],
351
+ layout: LayoutType,
352
+ ): ProviderNode[] => {
353
+ switch (layout) {
354
+ case "dagre":
355
+ return applyDagreLayout(nodes, edges);
356
+ case "force":
357
+ return applyForceLayout(nodes);
358
+ case "circular":
359
+ return applyCircularLayout(nodes);
360
+ default:
361
+ return applyDagreLayout(nodes, edges);
362
+ }
363
+ };
364
+
365
+ export const findDependencyChain = (
366
+ nodeId: string,
367
+ nodes: ProviderNode[],
368
+ edges: ProviderEdge[],
369
+ ): Set<string> => {
370
+ const chain = new Set<string>([nodeId]);
371
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
372
+
373
+ // Find all dependencies (downstream)
374
+ const findDeps = (id: string) => {
375
+ const node = nodeMap.get(id);
376
+ if (!node) return;
377
+ for (const dep of node.data.dependencies) {
378
+ if (!chain.has(dep)) {
379
+ chain.add(dep);
380
+ findDeps(dep);
381
+ }
382
+ }
383
+ };
384
+
385
+ // Find all dependents (upstream)
386
+ const findDependents = (id: string) => {
387
+ const node = nodeMap.get(id);
388
+ if (!node) return;
389
+ for (const dep of node.data.dependents) {
390
+ if (!chain.has(dep)) {
391
+ chain.add(dep);
392
+ findDependents(dep);
393
+ }
394
+ }
395
+ };
396
+
397
+ findDeps(nodeId);
398
+ findDependents(nodeId);
399
+
400
+ return chain;
401
+ };
402
+
403
+ export const detectCircularDependencies = (
404
+ nodes: ProviderNode[],
405
+ edges: ProviderEdge[],
406
+ ): string[][] => {
407
+ const cycles: string[][] = [];
408
+ const visited = new Set<string>();
409
+ const recStack = new Set<string>();
410
+ const adj = new Map<string, string[]>();
411
+
412
+ for (const node of nodes) {
413
+ adj.set(node.id, []);
414
+ }
415
+ for (const edge of edges) {
416
+ adj.get(edge.source)?.push(edge.target);
417
+ }
418
+
419
+ const dfs = (node: string, path: string[]): void => {
420
+ visited.add(node);
421
+ recStack.add(node);
422
+ path.push(node);
423
+
424
+ for (const neighbor of adj.get(node) || []) {
425
+ if (!visited.has(neighbor)) {
426
+ dfs(neighbor, [...path]);
427
+ } else if (recStack.has(neighbor)) {
428
+ const cycleStart = path.indexOf(neighbor);
429
+ cycles.push([...path.slice(cycleStart), neighbor]);
430
+ }
431
+ }
432
+
433
+ recStack.delete(node);
434
+ };
435
+
436
+ for (const node of nodes) {
437
+ if (!visited.has(node.id)) {
438
+ dfs(node.id, []);
439
+ }
440
+ }
441
+
442
+ return cycles;
443
+ };
@@ -0,0 +1,7 @@
1
+ export { getModuleColor, MODULE_COLORS } from "./constants.ts";
2
+ export { DevDependencyGraph } from "./DevDependencyGraph.tsx";
3
+ export { GraphControls } from "./GraphControls.tsx";
4
+ export * from "./helpers.ts";
5
+ export { NodeDetails } from "./NodeDetails.tsx";
6
+ export { ProviderNode } from "./ProviderNode.tsx";
7
+ export * from "./types.ts";
@@ -0,0 +1,28 @@
1
+ import type { Edge, Node } from "@xyflow/react";
2
+
3
+ export interface ProviderNodeData extends Record<string, unknown> {
4
+ label: string;
5
+ module?: string;
6
+ dependencies: string[];
7
+ dependents: string[];
8
+ aliases?: string[];
9
+ providers?: string[]; // For module nodes, list of providers in this module
10
+ providerCount?: number; // For module nodes
11
+ isHighlighted?: boolean;
12
+ isSelected?: boolean;
13
+ isFaded?: boolean;
14
+ isModule?: boolean; // True if this node represents a module
15
+ }
16
+
17
+ export type ProviderNode = Node<ProviderNodeData, "provider">;
18
+ export type ProviderEdge = Edge;
19
+
20
+ export type LayoutType = "dagre" | "force" | "circular";
21
+ export type ViewMode = "modules" | "providers";
22
+
23
+ export interface GraphFilters {
24
+ search: string;
25
+ module: string;
26
+ hideFramework: boolean;
27
+ viewMode: ViewMode;
28
+ }
package/src/ui/styles.css CHANGED
@@ -1,7 +1 @@
1
1
  @import "@alepha/ui/styles";
2
- @import "./resources/wotfardregular/stylesheet.css";
3
-
4
- :root {
5
- --mantine-font-family: "wotfardregular", Arial, sans-serif;
6
- --mantine-font-family-headings: "wotfardregular", Arial, sans-serif;
7
- }
@@ -1,12 +0,0 @@
1
- /*! Generated by Font Squirrel (https://www.fontsquirrel.com) on March 27, 2020 */
2
-
3
- @font-face {
4
- font-family: "wotfardregular";
5
- src: url("wotfard-regular-webfont.eot");
6
- src:
7
- url("wotfard-regular-webfont.eot?#iefix") format("embedded-opentype"),
8
- url("wotfard-regular-webfont.woff2") format("woff2"),
9
- url("wotfard-regular-webfont.ttf") format("truetype");
10
- font-weight: normal;
11
- font-style: normal;
12
- }
File without changes
File without changes