@webpieces/dev-config 0.0.0-dev → 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.
- package/architecture/executors/generate/executor.d.ts +17 -0
- package/architecture/executors/generate/executor.js +67 -0
- package/architecture/executors/generate/executor.js.map +1 -0
- package/architecture/executors/generate/executor.ts +83 -0
- package/architecture/executors/generate/schema.json +14 -0
- package/architecture/executors/validate-architecture-unchanged/executor.d.ts +17 -0
- package/architecture/executors/validate-architecture-unchanged/executor.js +65 -0
- package/architecture/executors/validate-architecture-unchanged/executor.js.map +1 -0
- package/architecture/executors/validate-architecture-unchanged/executor.ts +81 -0
- package/architecture/executors/validate-architecture-unchanged/schema.json +14 -0
- package/architecture/executors/validate-no-cycles/executor.d.ts +16 -0
- package/architecture/executors/validate-no-cycles/executor.js +48 -0
- package/architecture/executors/validate-no-cycles/executor.js.map +1 -0
- package/architecture/executors/validate-no-cycles/executor.ts +60 -0
- package/architecture/executors/validate-no-cycles/schema.json +8 -0
- package/architecture/executors/validate-no-skiplevel-deps/executor.d.ts +19 -0
- package/architecture/executors/validate-no-skiplevel-deps/executor.js +227 -0
- package/architecture/executors/validate-no-skiplevel-deps/executor.js.map +1 -0
- package/architecture/executors/validate-no-skiplevel-deps/executor.ts +267 -0
- package/architecture/executors/validate-no-skiplevel-deps/schema.json +8 -0
- package/architecture/executors/visualize/executor.d.ts +17 -0
- package/architecture/executors/visualize/executor.js +49 -0
- package/architecture/executors/visualize/executor.js.map +1 -0
- package/architecture/executors/visualize/executor.ts +63 -0
- package/architecture/executors/visualize/schema.json +14 -0
- package/architecture/index.d.ts +19 -0
- package/architecture/index.js +23 -0
- package/architecture/index.js.map +1 -0
- package/architecture/index.ts +20 -0
- package/architecture/lib/graph-comparator.d.ts +39 -0
- package/architecture/lib/graph-comparator.js +100 -0
- package/architecture/lib/graph-comparator.js.map +1 -0
- package/architecture/lib/graph-comparator.ts +141 -0
- package/architecture/lib/graph-generator.d.ts +19 -0
- package/architecture/lib/graph-generator.js +88 -0
- package/architecture/lib/graph-generator.js.map +1 -0
- package/architecture/lib/graph-generator.ts +102 -0
- package/architecture/lib/graph-loader.d.ts +31 -0
- package/architecture/lib/graph-loader.js +70 -0
- package/architecture/lib/graph-loader.js.map +1 -0
- package/architecture/lib/graph-loader.ts +82 -0
- package/architecture/lib/graph-sorter.d.ts +37 -0
- package/architecture/lib/graph-sorter.js +110 -0
- package/architecture/lib/graph-sorter.js.map +1 -0
- package/architecture/lib/graph-sorter.ts +137 -0
- package/architecture/lib/graph-visualizer.d.ts +29 -0
- package/architecture/lib/graph-visualizer.js +209 -0
- package/architecture/lib/graph-visualizer.js.map +1 -0
- package/architecture/lib/graph-visualizer.ts +222 -0
- package/architecture/lib/package-validator.d.ts +38 -0
- package/architecture/lib/package-validator.js +105 -0
- package/architecture/lib/package-validator.js.map +1 -0
- package/architecture/lib/package-validator.ts +144 -0
- package/config/eslint/base.mjs +6 -0
- package/eslint-plugin/__tests__/catch-error-pattern.test.ts +0 -1
- package/eslint-plugin/__tests__/max-file-lines.test.ts +29 -17
- package/eslint-plugin/__tests__/max-method-lines.test.ts +27 -15
- package/eslint-plugin/__tests__/no-unmanaged-exceptions.test.ts +359 -0
- package/eslint-plugin/index.d.ts +9 -0
- package/eslint-plugin/index.js +11 -0
- package/eslint-plugin/index.js.map +1 -1
- package/eslint-plugin/index.ts +11 -0
- package/eslint-plugin/rules/enforce-architecture.d.ts +15 -0
- package/eslint-plugin/rules/enforce-architecture.js +406 -0
- package/eslint-plugin/rules/enforce-architecture.js.map +1 -0
- package/eslint-plugin/rules/enforce-architecture.ts +469 -0
- package/eslint-plugin/rules/max-file-lines.js +11 -11
- package/eslint-plugin/rules/max-file-lines.js.map +1 -1
- package/eslint-plugin/rules/max-file-lines.ts +11 -11
- package/eslint-plugin/rules/max-method-lines.js +71 -88
- package/eslint-plugin/rules/max-method-lines.js.map +1 -1
- package/eslint-plugin/rules/max-method-lines.ts +85 -102
- package/eslint-plugin/rules/no-unmanaged-exceptions.d.ts +22 -0
- package/eslint-plugin/rules/no-unmanaged-exceptions.js +605 -0
- package/eslint-plugin/rules/no-unmanaged-exceptions.js.map +1 -0
- package/eslint-plugin/rules/no-unmanaged-exceptions.ts +621 -0
- package/executors.json +29 -0
- package/package.json +13 -7
- package/plugins/circular-deps/index.d.ts +8 -0
- package/plugins/circular-deps/index.js +14 -0
- package/plugins/circular-deps/index.js.map +1 -0
- package/plugins/circular-deps/index.ts +9 -0
- package/plugins/circular-deps/plugin.d.ts +32 -0
- package/plugins/circular-deps/plugin.js +73 -0
- package/plugins/circular-deps/plugin.js.map +1 -0
- 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;
|