@webpieces/dev-config 0.2.17 → 0.2.21

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 (87) hide show
  1. package/architecture/executors/generate/executor.d.ts +17 -0
  2. package/architecture/executors/generate/executor.js +67 -0
  3. package/architecture/executors/generate/executor.js.map +1 -0
  4. package/architecture/executors/generate/executor.ts +83 -0
  5. package/architecture/executors/generate/schema.json +14 -0
  6. package/architecture/executors/validate-architecture-unchanged/executor.d.ts +17 -0
  7. package/architecture/executors/validate-architecture-unchanged/executor.js +65 -0
  8. package/architecture/executors/validate-architecture-unchanged/executor.js.map +1 -0
  9. package/architecture/executors/validate-architecture-unchanged/executor.ts +81 -0
  10. package/architecture/executors/validate-architecture-unchanged/schema.json +14 -0
  11. package/architecture/executors/validate-no-cycles/executor.d.ts +16 -0
  12. package/architecture/executors/validate-no-cycles/executor.js +48 -0
  13. package/architecture/executors/validate-no-cycles/executor.js.map +1 -0
  14. package/architecture/executors/validate-no-cycles/executor.ts +60 -0
  15. package/architecture/executors/validate-no-cycles/schema.json +8 -0
  16. package/architecture/executors/validate-no-skiplevel-deps/executor.d.ts +19 -0
  17. package/architecture/executors/validate-no-skiplevel-deps/executor.js +227 -0
  18. package/architecture/executors/validate-no-skiplevel-deps/executor.js.map +1 -0
  19. package/architecture/executors/validate-no-skiplevel-deps/executor.ts +267 -0
  20. package/architecture/executors/validate-no-skiplevel-deps/schema.json +8 -0
  21. package/architecture/executors/visualize/executor.d.ts +17 -0
  22. package/architecture/executors/visualize/executor.js +49 -0
  23. package/architecture/executors/visualize/executor.js.map +1 -0
  24. package/architecture/executors/visualize/executor.ts +63 -0
  25. package/architecture/executors/visualize/schema.json +14 -0
  26. package/architecture/index.d.ts +19 -0
  27. package/architecture/index.js +23 -0
  28. package/architecture/index.js.map +1 -0
  29. package/architecture/index.ts +20 -0
  30. package/architecture/lib/graph-comparator.d.ts +39 -0
  31. package/architecture/lib/graph-comparator.js +100 -0
  32. package/architecture/lib/graph-comparator.js.map +1 -0
  33. package/architecture/lib/graph-comparator.ts +141 -0
  34. package/architecture/lib/graph-generator.d.ts +19 -0
  35. package/architecture/lib/graph-generator.js +88 -0
  36. package/architecture/lib/graph-generator.js.map +1 -0
  37. package/architecture/lib/graph-generator.ts +102 -0
  38. package/architecture/lib/graph-loader.d.ts +31 -0
  39. package/architecture/lib/graph-loader.js +70 -0
  40. package/architecture/lib/graph-loader.js.map +1 -0
  41. package/architecture/lib/graph-loader.ts +82 -0
  42. package/architecture/lib/graph-sorter.d.ts +37 -0
  43. package/architecture/lib/graph-sorter.js +110 -0
  44. package/architecture/lib/graph-sorter.js.map +1 -0
  45. package/architecture/lib/graph-sorter.ts +137 -0
  46. package/architecture/lib/graph-visualizer.d.ts +29 -0
  47. package/architecture/lib/graph-visualizer.js +209 -0
  48. package/architecture/lib/graph-visualizer.js.map +1 -0
  49. package/architecture/lib/graph-visualizer.ts +222 -0
  50. package/architecture/lib/package-validator.d.ts +38 -0
  51. package/architecture/lib/package-validator.js +105 -0
  52. package/architecture/lib/package-validator.js.map +1 -0
  53. package/architecture/lib/package-validator.ts +144 -0
  54. package/config/eslint/base.mjs +6 -0
  55. package/eslint-plugin/__tests__/max-file-lines.test.ts +207 -0
  56. package/eslint-plugin/__tests__/max-method-lines.test.ts +258 -0
  57. package/eslint-plugin/__tests__/no-unmanaged-exceptions.test.ts +359 -0
  58. package/eslint-plugin/index.d.ts +11 -0
  59. package/eslint-plugin/index.js +15 -0
  60. package/eslint-plugin/index.js.map +1 -1
  61. package/eslint-plugin/index.ts +15 -0
  62. package/eslint-plugin/rules/enforce-architecture.d.ts +15 -0
  63. package/eslint-plugin/rules/enforce-architecture.js +406 -0
  64. package/eslint-plugin/rules/enforce-architecture.js.map +1 -0
  65. package/eslint-plugin/rules/enforce-architecture.ts +469 -0
  66. package/eslint-plugin/rules/max-file-lines.d.ts +12 -0
  67. package/eslint-plugin/rules/max-file-lines.js +257 -0
  68. package/eslint-plugin/rules/max-file-lines.js.map +1 -0
  69. package/eslint-plugin/rules/max-file-lines.ts +272 -0
  70. package/eslint-plugin/rules/max-method-lines.d.ts +12 -0
  71. package/eslint-plugin/rules/max-method-lines.js +240 -0
  72. package/eslint-plugin/rules/max-method-lines.js.map +1 -0
  73. package/eslint-plugin/rules/max-method-lines.ts +287 -0
  74. package/eslint-plugin/rules/no-unmanaged-exceptions.d.ts +22 -0
  75. package/eslint-plugin/rules/no-unmanaged-exceptions.js +605 -0
  76. package/eslint-plugin/rules/no-unmanaged-exceptions.js.map +1 -0
  77. package/eslint-plugin/rules/no-unmanaged-exceptions.ts +621 -0
  78. package/executors.json +29 -0
  79. package/package.json +13 -3
  80. package/plugins/circular-deps/index.d.ts +8 -0
  81. package/plugins/circular-deps/index.js +14 -0
  82. package/plugins/circular-deps/index.js.map +1 -0
  83. package/plugins/circular-deps/index.ts +9 -0
  84. package/plugins/circular-deps/plugin.d.ts +32 -0
  85. package/plugins/circular-deps/plugin.js +73 -0
  86. package/plugins/circular-deps/plugin.js.map +1 -0
  87. package/plugins/circular-deps/plugin.ts +83 -0
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * Graph Loader
4
+ *
5
+ * Handles loading and saving the blessed dependency graph file.
6
+ * The graph is stored at architecture/dependencies.json in the workspace root.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.DEFAULT_GRAPH_PATH = void 0;
10
+ exports.loadBlessedGraph = loadBlessedGraph;
11
+ exports.saveGraph = saveGraph;
12
+ exports.graphFileExists = graphFileExists;
13
+ const tslib_1 = require("tslib");
14
+ const fs = tslib_1.__importStar(require("fs"));
15
+ const path = tslib_1.__importStar(require("path"));
16
+ /**
17
+ * Default path for the dependencies file (relative to workspace root)
18
+ */
19
+ exports.DEFAULT_GRAPH_PATH = 'architecture/dependencies.json';
20
+ /**
21
+ * Load the blessed graph from disk
22
+ *
23
+ * @param workspaceRoot - Absolute path to workspace root
24
+ * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)
25
+ * @returns The blessed graph, or null if file doesn't exist
26
+ */
27
+ function loadBlessedGraph(workspaceRoot, graphPath = exports.DEFAULT_GRAPH_PATH) {
28
+ const fullPath = path.join(workspaceRoot, graphPath);
29
+ if (!fs.existsSync(fullPath)) {
30
+ return null;
31
+ }
32
+ try {
33
+ const content = fs.readFileSync(fullPath, 'utf-8');
34
+ return JSON.parse(content);
35
+ }
36
+ catch (err) {
37
+ throw new Error(`Failed to load graph from ${fullPath}: ${err}`);
38
+ }
39
+ }
40
+ /**
41
+ * Save the graph to disk
42
+ *
43
+ * @param graph - The graph to save
44
+ * @param workspaceRoot - Absolute path to workspace root
45
+ * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)
46
+ */
47
+ function saveGraph(graph, workspaceRoot, graphPath = exports.DEFAULT_GRAPH_PATH) {
48
+ const fullPath = path.join(workspaceRoot, graphPath);
49
+ const dir = path.dirname(fullPath);
50
+ // Ensure directory exists
51
+ if (!fs.existsSync(dir)) {
52
+ fs.mkdirSync(dir, { recursive: true });
53
+ }
54
+ // Sort keys for deterministic output
55
+ const sortedGraph = {};
56
+ const sortedKeys = Object.keys(graph).sort();
57
+ for (const key of sortedKeys) {
58
+ sortedGraph[key] = graph[key];
59
+ }
60
+ const content = JSON.stringify(sortedGraph, null, 2) + '\n';
61
+ fs.writeFileSync(fullPath, content, 'utf-8');
62
+ }
63
+ /**
64
+ * Check if the graph file exists
65
+ */
66
+ function graphFileExists(workspaceRoot, graphPath = exports.DEFAULT_GRAPH_PATH) {
67
+ const fullPath = path.join(workspaceRoot, graphPath);
68
+ return fs.existsSync(fullPath);
69
+ }
70
+ //# sourceMappingURL=graph-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-loader.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/architecture/lib/graph-loader.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAkBH,4CAgBC;AASD,8BAsBC;AAKD,0CAMC;;AA1ED,+CAAyB;AACzB,mDAA6B;AAG7B;;GAEG;AACU,QAAA,kBAAkB,GAAG,gCAAgC,CAAC;AAEnE;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAC5B,aAAqB,EACrB,YAAoB,0BAAkB;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAErD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAkB,CAAC;IAChD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,KAAK,GAAG,EAAE,CAAC,CAAC;IACrE,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,SAAS,CACrB,KAAoB,EACpB,aAAqB,EACrB,YAAoB,0BAAkB;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEnC,0BAA0B;IAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,qCAAqC;IACrC,MAAM,WAAW,GAAkB,EAAE,CAAC;IACtC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7C,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC3B,WAAW,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IAC5D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC;AAED;;GAEG;AACH,SAAgB,eAAe,CAC3B,aAAqB,EACrB,YAAoB,0BAAkB;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IACrD,OAAO,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC","sourcesContent":["/**\n * Graph Loader\n *\n * Handles loading and saving the blessed dependency graph file.\n * The graph is stored at architecture/dependencies.json in the workspace root.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { EnhancedGraph } from './graph-sorter';\n\n/**\n * Default path for the dependencies file (relative to workspace root)\n */\nexport const DEFAULT_GRAPH_PATH = 'architecture/dependencies.json';\n\n/**\n * Load the blessed graph from disk\n *\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)\n * @returns The blessed graph, or null if file doesn't exist\n */\nexport function loadBlessedGraph(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): EnhancedGraph | null {\n const fullPath = path.join(workspaceRoot, graphPath);\n\n if (!fs.existsSync(fullPath)) {\n return null;\n }\n\n try {\n const content = fs.readFileSync(fullPath, 'utf-8');\n return JSON.parse(content) as EnhancedGraph;\n } catch (err: unknown) {\n throw new Error(`Failed to load graph from ${fullPath}: ${err}`);\n }\n}\n\n/**\n * Save the graph to disk\n *\n * @param graph - The graph to save\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)\n */\nexport function saveGraph(\n graph: EnhancedGraph,\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): void {\n const fullPath = path.join(workspaceRoot, graphPath);\n const dir = path.dirname(fullPath);\n\n // Ensure directory exists\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Sort keys for deterministic output\n const sortedGraph: EnhancedGraph = {};\n const sortedKeys = Object.keys(graph).sort();\n for (const key of sortedKeys) {\n sortedGraph[key] = graph[key];\n }\n\n const content = JSON.stringify(sortedGraph, null, 2) + '\\n';\n fs.writeFileSync(fullPath, content, 'utf-8');\n}\n\n/**\n * Check if the graph file exists\n */\nexport function graphFileExists(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): boolean {\n const fullPath = path.join(workspaceRoot, graphPath);\n return fs.existsSync(fullPath);\n}\n"]}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Graph Loader
3
+ *
4
+ * Handles loading and saving the blessed dependency graph file.
5
+ * The graph is stored at architecture/dependencies.json in the workspace root.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import type { EnhancedGraph } from './graph-sorter';
11
+
12
+ /**
13
+ * Default path for the dependencies file (relative to workspace root)
14
+ */
15
+ export const DEFAULT_GRAPH_PATH = 'architecture/dependencies.json';
16
+
17
+ /**
18
+ * Load the blessed graph from disk
19
+ *
20
+ * @param workspaceRoot - Absolute path to workspace root
21
+ * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)
22
+ * @returns The blessed graph, or null if file doesn't exist
23
+ */
24
+ export function loadBlessedGraph(
25
+ workspaceRoot: string,
26
+ graphPath: string = DEFAULT_GRAPH_PATH
27
+ ): EnhancedGraph | null {
28
+ const fullPath = path.join(workspaceRoot, graphPath);
29
+
30
+ if (!fs.existsSync(fullPath)) {
31
+ return null;
32
+ }
33
+
34
+ try {
35
+ const content = fs.readFileSync(fullPath, 'utf-8');
36
+ return JSON.parse(content) as EnhancedGraph;
37
+ } catch (err: unknown) {
38
+ throw new Error(`Failed to load graph from ${fullPath}: ${err}`);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Save the graph to disk
44
+ *
45
+ * @param graph - The graph to save
46
+ * @param workspaceRoot - Absolute path to workspace root
47
+ * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)
48
+ */
49
+ export function saveGraph(
50
+ graph: EnhancedGraph,
51
+ workspaceRoot: string,
52
+ graphPath: string = DEFAULT_GRAPH_PATH
53
+ ): void {
54
+ const fullPath = path.join(workspaceRoot, graphPath);
55
+ const dir = path.dirname(fullPath);
56
+
57
+ // Ensure directory exists
58
+ if (!fs.existsSync(dir)) {
59
+ fs.mkdirSync(dir, { recursive: true });
60
+ }
61
+
62
+ // Sort keys for deterministic output
63
+ const sortedGraph: EnhancedGraph = {};
64
+ const sortedKeys = Object.keys(graph).sort();
65
+ for (const key of sortedKeys) {
66
+ sortedGraph[key] = graph[key];
67
+ }
68
+
69
+ const content = JSON.stringify(sortedGraph, null, 2) + '\n';
70
+ fs.writeFileSync(fullPath, content, 'utf-8');
71
+ }
72
+
73
+ /**
74
+ * Check if the graph file exists
75
+ */
76
+ export function graphFileExists(
77
+ workspaceRoot: string,
78
+ graphPath: string = DEFAULT_GRAPH_PATH
79
+ ): boolean {
80
+ const fullPath = path.join(workspaceRoot, graphPath);
81
+ return fs.existsSync(fullPath);
82
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Graph Sorter
3
+ *
4
+ * Performs topological sorting on the dependency graph to:
5
+ * 1. Detect circular dependencies (fails if cycle found)
6
+ * 2. Assign level numbers to each project (level 0 = no deps, level 1 = depends on level 0, etc.)
7
+ * 3. Group projects into layers for deterministic ordering
8
+ */
9
+ /**
10
+ * Graph entry with level metadata
11
+ */
12
+ export interface GraphEntry {
13
+ level: number;
14
+ dependsOn: string[];
15
+ }
16
+ /**
17
+ * Enhanced graph format with level information
18
+ */
19
+ export type EnhancedGraph = Record<string, GraphEntry>;
20
+ /**
21
+ * Compute topological layers for dependency graph using Kahn's algorithm
22
+ *
23
+ * Projects are grouped into layers where each layer only depends on previous layers.
24
+ * Throws an error if a circular dependency is detected.
25
+ *
26
+ * @param graph - Dependency graph { project: [deps] }
27
+ * @returns Array of layers, each containing sorted project names
28
+ */
29
+ export declare function computeTopologicalLayers(graph: Record<string, string[]>): string[][];
30
+ /**
31
+ * Sort graph in topological order with alphabetical sorting within layers
32
+ * Returns enhanced format with level metadata
33
+ *
34
+ * @param graph - Unsorted dependency graph { project: [deps] }
35
+ * @returns Sorted graph with level metadata { project: { level: number, dependsOn: [deps] } }
36
+ */
37
+ export declare function sortGraphTopologically(graph: Record<string, string[]>): EnhancedGraph;
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ /**
3
+ * Graph Sorter
4
+ *
5
+ * Performs topological sorting on the dependency graph to:
6
+ * 1. Detect circular dependencies (fails if cycle found)
7
+ * 2. Assign level numbers to each project (level 0 = no deps, level 1 = depends on level 0, etc.)
8
+ * 3. Group projects into layers for deterministic ordering
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.computeTopologicalLayers = computeTopologicalLayers;
12
+ exports.sortGraphTopologically = sortGraphTopologically;
13
+ /**
14
+ * Compute topological layers for dependency graph using Kahn's algorithm
15
+ *
16
+ * Projects are grouped into layers where each layer only depends on previous layers.
17
+ * Throws an error if a circular dependency is detected.
18
+ *
19
+ * @param graph - Dependency graph { project: [deps] }
20
+ * @returns Array of layers, each containing sorted project names
21
+ */
22
+ function computeTopologicalLayers(graph) {
23
+ const layers = [];
24
+ const processed = new Set();
25
+ const allProjects = Object.keys(graph);
26
+ while (processed.size < allProjects.length) {
27
+ const currentLayer = [];
28
+ for (const project of allProjects) {
29
+ if (processed.has(project))
30
+ continue;
31
+ const deps = graph[project] || [];
32
+ // Check if all dependencies are in previous layers (already processed)
33
+ const allDepsInPrevLayers = deps.every((dep) => processed.has(dep));
34
+ if (allDepsInPrevLayers) {
35
+ currentLayer.push(project);
36
+ }
37
+ }
38
+ if (currentLayer.length === 0) {
39
+ // No progress made = circular dependency detected
40
+ const remaining = allProjects.filter((p) => !processed.has(p));
41
+ // Try to identify the cycle
42
+ const cycleInfo = findCycle(graph, remaining);
43
+ throw new Error(`Circular dependency detected among: ${remaining.join(', ')}\n` +
44
+ (cycleInfo ? `Cycle: ${cycleInfo}\n` : '') +
45
+ 'Fix: Remove one of the dependencies to break the cycle.');
46
+ }
47
+ // Sort alphabetically within layer for deterministic output
48
+ currentLayer.sort();
49
+ layers.push(currentLayer);
50
+ // Mark as processed
51
+ currentLayer.forEach((p) => processed.add(p));
52
+ }
53
+ return layers;
54
+ }
55
+ /**
56
+ * Try to find and describe a cycle in the graph
57
+ */
58
+ function findCycle(graph, remaining) {
59
+ const visited = new Set();
60
+ const path = [];
61
+ function dfs(node) {
62
+ if (path.includes(node)) {
63
+ const cycleStart = path.indexOf(node);
64
+ return [...path.slice(cycleStart), node].join(' -> ');
65
+ }
66
+ if (visited.has(node))
67
+ return null;
68
+ visited.add(node);
69
+ path.push(node);
70
+ const deps = graph[node] || [];
71
+ for (const dep of deps) {
72
+ if (remaining.includes(dep)) {
73
+ const result = dfs(dep);
74
+ if (result)
75
+ return result;
76
+ }
77
+ }
78
+ path.pop();
79
+ return null;
80
+ }
81
+ for (const node of remaining) {
82
+ const cycle = dfs(node);
83
+ if (cycle)
84
+ return cycle;
85
+ }
86
+ return null;
87
+ }
88
+ /**
89
+ * Sort graph in topological order with alphabetical sorting within layers
90
+ * Returns enhanced format with level metadata
91
+ *
92
+ * @param graph - Unsorted dependency graph { project: [deps] }
93
+ * @returns Sorted graph with level metadata { project: { level: number, dependsOn: [deps] } }
94
+ */
95
+ function sortGraphTopologically(graph) {
96
+ const layers = computeTopologicalLayers(graph);
97
+ const result = {};
98
+ // Add projects layer by layer (dependencies before dependents)
99
+ layers.forEach((layer, levelIndex) => {
100
+ for (const project of layer) {
101
+ // Already sorted alphabetically within layer
102
+ result[project] = {
103
+ level: levelIndex,
104
+ dependsOn: (graph[project] || []).sort(),
105
+ };
106
+ }
107
+ });
108
+ return result;
109
+ }
110
+ //# sourceMappingURL=graph-sorter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-sorter.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/architecture/lib/graph-sorter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Sorter\n *\n * Performs topological sorting on the dependency graph to:\n * 1. Detect circular dependencies (fails if cycle found)\n * 2. Assign level numbers to each project (level 0 = no deps, level 1 = depends on level 0, etc.)\n * 3. Group projects into layers for deterministic ordering\n */\n\n/**\n * Graph entry with level metadata\n */\nexport interface GraphEntry {\n level: number;\n dependsOn: string[];\n}\n\n/**\n * Enhanced graph format with level information\n */\nexport type EnhancedGraph = Record<string, GraphEntry>;\n\n/**\n * Compute topological layers for dependency graph using Kahn's algorithm\n *\n * Projects are grouped into layers where each layer only depends on previous layers.\n * Throws an error if a circular dependency is detected.\n *\n * @param graph - Dependency graph { project: [deps] }\n * @returns Array of layers, each containing sorted project names\n */\nexport function computeTopologicalLayers(graph: Record<string, string[]>): string[][] {\n const layers: string[][] = [];\n const processed = new Set<string>();\n const allProjects = Object.keys(graph);\n\n while (processed.size < allProjects.length) {\n const currentLayer: string[] = [];\n\n for (const project of allProjects) {\n if (processed.has(project)) continue;\n\n const deps = graph[project] || [];\n // Check if all dependencies are in previous layers (already processed)\n const allDepsInPrevLayers = deps.every((dep) => processed.has(dep));\n\n if (allDepsInPrevLayers) {\n currentLayer.push(project);\n }\n }\n\n if (currentLayer.length === 0) {\n // No progress made = circular dependency detected\n const remaining = allProjects.filter((p) => !processed.has(p));\n\n // Try to identify the cycle\n const cycleInfo = findCycle(graph, remaining);\n\n throw new Error(\n `Circular dependency detected among: ${remaining.join(', ')}\\n` +\n (cycleInfo ? `Cycle: ${cycleInfo}\\n` : '') +\n 'Fix: Remove one of the dependencies to break the cycle.'\n );\n }\n\n // Sort alphabetically within layer for deterministic output\n currentLayer.sort();\n layers.push(currentLayer);\n\n // Mark as processed\n currentLayer.forEach((p) => processed.add(p));\n }\n\n return layers;\n}\n\n/**\n * Try to find and describe a cycle in the graph\n */\nfunction findCycle(graph: Record<string, string[]>, remaining: string[]): string | null {\n const visited = new Set<string>();\n const path: string[] = [];\n\n function dfs(node: string): string | null {\n if (path.includes(node)) {\n const cycleStart = path.indexOf(node);\n return [...path.slice(cycleStart), node].join(' -> ');\n }\n if (visited.has(node)) return null;\n\n visited.add(node);\n path.push(node);\n\n const deps = graph[node] || [];\n for (const dep of deps) {\n if (remaining.includes(dep)) {\n const result = dfs(dep);\n if (result) return result;\n }\n }\n\n path.pop();\n return null;\n }\n\n for (const node of remaining) {\n const cycle = dfs(node);\n if (cycle) return cycle;\n }\n\n return null;\n}\n\n/**\n * Sort graph in topological order with alphabetical sorting within layers\n * Returns enhanced format with level metadata\n *\n * @param graph - Unsorted dependency graph { project: [deps] }\n * @returns Sorted graph with level metadata { project: { level: number, dependsOn: [deps] } }\n */\nexport function sortGraphTopologically(graph: Record<string, string[]>): EnhancedGraph {\n const layers = computeTopologicalLayers(graph);\n const result: EnhancedGraph = {};\n\n // Add projects layer by layer (dependencies before dependents)\n layers.forEach((layer, levelIndex) => {\n for (const project of layer) {\n // Already sorted alphabetically within layer\n result[project] = {\n level: levelIndex,\n dependsOn: (graph[project] || []).sort(),\n };\n }\n });\n\n return result;\n}\n"]}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Graph Sorter
3
+ *
4
+ * Performs topological sorting on the dependency graph to:
5
+ * 1. Detect circular dependencies (fails if cycle found)
6
+ * 2. Assign level numbers to each project (level 0 = no deps, level 1 = depends on level 0, etc.)
7
+ * 3. Group projects into layers for deterministic ordering
8
+ */
9
+
10
+ /**
11
+ * Graph entry with level metadata
12
+ */
13
+ export interface GraphEntry {
14
+ level: number;
15
+ dependsOn: string[];
16
+ }
17
+
18
+ /**
19
+ * Enhanced graph format with level information
20
+ */
21
+ export type EnhancedGraph = Record<string, GraphEntry>;
22
+
23
+ /**
24
+ * Compute topological layers for dependency graph using Kahn's algorithm
25
+ *
26
+ * Projects are grouped into layers where each layer only depends on previous layers.
27
+ * Throws an error if a circular dependency is detected.
28
+ *
29
+ * @param graph - Dependency graph { project: [deps] }
30
+ * @returns Array of layers, each containing sorted project names
31
+ */
32
+ export function computeTopologicalLayers(graph: Record<string, string[]>): string[][] {
33
+ const layers: string[][] = [];
34
+ const processed = new Set<string>();
35
+ const allProjects = Object.keys(graph);
36
+
37
+ while (processed.size < allProjects.length) {
38
+ const currentLayer: string[] = [];
39
+
40
+ for (const project of allProjects) {
41
+ if (processed.has(project)) continue;
42
+
43
+ const deps = graph[project] || [];
44
+ // Check if all dependencies are in previous layers (already processed)
45
+ const allDepsInPrevLayers = deps.every((dep) => processed.has(dep));
46
+
47
+ if (allDepsInPrevLayers) {
48
+ currentLayer.push(project);
49
+ }
50
+ }
51
+
52
+ if (currentLayer.length === 0) {
53
+ // No progress made = circular dependency detected
54
+ const remaining = allProjects.filter((p) => !processed.has(p));
55
+
56
+ // Try to identify the cycle
57
+ const cycleInfo = findCycle(graph, remaining);
58
+
59
+ throw new Error(
60
+ `Circular dependency detected among: ${remaining.join(', ')}\n` +
61
+ (cycleInfo ? `Cycle: ${cycleInfo}\n` : '') +
62
+ 'Fix: Remove one of the dependencies to break the cycle.'
63
+ );
64
+ }
65
+
66
+ // Sort alphabetically within layer for deterministic output
67
+ currentLayer.sort();
68
+ layers.push(currentLayer);
69
+
70
+ // Mark as processed
71
+ currentLayer.forEach((p) => processed.add(p));
72
+ }
73
+
74
+ return layers;
75
+ }
76
+
77
+ /**
78
+ * Try to find and describe a cycle in the graph
79
+ */
80
+ function findCycle(graph: Record<string, string[]>, remaining: string[]): string | null {
81
+ const visited = new Set<string>();
82
+ const path: string[] = [];
83
+
84
+ function dfs(node: string): string | null {
85
+ if (path.includes(node)) {
86
+ const cycleStart = path.indexOf(node);
87
+ return [...path.slice(cycleStart), node].join(' -> ');
88
+ }
89
+ if (visited.has(node)) return null;
90
+
91
+ visited.add(node);
92
+ path.push(node);
93
+
94
+ const deps = graph[node] || [];
95
+ for (const dep of deps) {
96
+ if (remaining.includes(dep)) {
97
+ const result = dfs(dep);
98
+ if (result) return result;
99
+ }
100
+ }
101
+
102
+ path.pop();
103
+ return null;
104
+ }
105
+
106
+ for (const node of remaining) {
107
+ const cycle = dfs(node);
108
+ if (cycle) return cycle;
109
+ }
110
+
111
+ return null;
112
+ }
113
+
114
+ /**
115
+ * Sort graph in topological order with alphabetical sorting within layers
116
+ * Returns enhanced format with level metadata
117
+ *
118
+ * @param graph - Unsorted dependency graph { project: [deps] }
119
+ * @returns Sorted graph with level metadata { project: { level: number, dependsOn: [deps] } }
120
+ */
121
+ export function sortGraphTopologically(graph: Record<string, string[]>): EnhancedGraph {
122
+ const layers = computeTopologicalLayers(graph);
123
+ const result: EnhancedGraph = {};
124
+
125
+ // Add projects layer by layer (dependencies before dependents)
126
+ layers.forEach((layer, levelIndex) => {
127
+ for (const project of layer) {
128
+ // Already sorted alphabetically within layer
129
+ result[project] = {
130
+ level: levelIndex,
131
+ dependsOn: (graph[project] || []).sort(),
132
+ };
133
+ }
134
+ });
135
+
136
+ return result;
137
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Graph Visualizer
3
+ *
4
+ * Generates visual representations of the architecture graph:
5
+ * - DOT format (for Graphviz)
6
+ * - Interactive HTML (using viz.js)
7
+ *
8
+ * Output files go to tmp/webpieces/ for easy viewing without committing.
9
+ */
10
+ import type { EnhancedGraph } from './graph-sorter';
11
+ /**
12
+ * Generate Graphviz DOT format from the graph
13
+ */
14
+ export declare function generateDot(graph: EnhancedGraph, title?: string): string;
15
+ /**
16
+ * Generate interactive HTML with embedded SVG using viz.js
17
+ */
18
+ export declare function generateHTML(dot: string, title?: string): string;
19
+ /**
20
+ * Write visualization files to tmp/webpieces/
21
+ */
22
+ export declare function writeVisualization(graph: EnhancedGraph, workspaceRoot: string, title?: string): {
23
+ dotPath: string;
24
+ htmlPath: string;
25
+ };
26
+ /**
27
+ * Open the HTML visualization in the default browser
28
+ */
29
+ export declare function openVisualization(htmlPath: string): boolean;