@webpieces/nx-webpieces-rules 0.0.1 → 0.2.113
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/package.json +5 -4
- package/src/executor-result.d.ts +4 -0
- package/src/executor-result.js +10 -0
- package/src/executor-result.js.map +1 -0
- package/src/executors/generate/executor.d.ts +16 -0
- package/src/executors/generate/{executor.ts → executor.js} +15 -30
- package/src/executors/generate/executor.js.map +1 -0
- package/src/executors/help/executor.d.ts +8 -0
- package/src/executors/help/{executor.ts → executor.js} +5 -12
- package/src/executors/help/executor.js.map +1 -0
- package/src/executors/validate-architecture-unchanged/executor.d.ts +17 -0
- package/src/executors/validate-architecture-unchanged/{executor.ts → executor.js} +24 -46
- package/src/executors/validate-architecture-unchanged/executor.js.map +1 -0
- package/src/executors/validate-catch-error-pattern/executor.d.ts +3 -0
- package/src/executors/validate-catch-error-pattern/executor.js +10 -0
- package/src/executors/validate-catch-error-pattern/executor.js.map +1 -0
- package/src/executors/validate-code/executor.d.ts +3 -0
- package/src/executors/validate-code/executor.js +10 -0
- package/src/executors/validate-code/executor.js.map +1 -0
- package/src/executors/validate-dtos/executor.d.ts +3 -0
- package/src/executors/validate-dtos/executor.js +10 -0
- package/src/executors/validate-dtos/executor.js.map +1 -0
- package/src/executors/validate-eslint-sync/executor.d.ts +7 -0
- package/src/executors/validate-eslint-sync/{executor.ts → executor.js} +19 -37
- package/src/executors/validate-eslint-sync/executor.js.map +1 -0
- package/src/executors/validate-modified-files/executor.d.ts +3 -0
- package/src/executors/validate-modified-files/executor.js +10 -0
- package/src/executors/validate-modified-files/executor.js.map +1 -0
- package/src/executors/validate-modified-methods/executor.d.ts +3 -0
- package/src/executors/validate-modified-methods/executor.js +10 -0
- package/src/executors/validate-modified-methods/executor.js.map +1 -0
- package/src/executors/validate-new-methods/executor.d.ts +3 -0
- package/src/executors/validate-new-methods/executor.js +10 -0
- package/src/executors/validate-new-methods/executor.js.map +1 -0
- package/src/executors/validate-no-any-unknown/executor.d.ts +3 -0
- package/src/executors/validate-no-any-unknown/executor.js +10 -0
- package/src/executors/validate-no-any-unknown/executor.js.map +1 -0
- package/src/executors/validate-no-architecture-cycles/executor.d.ts +16 -0
- package/src/executors/validate-no-architecture-cycles/{executor.ts → executor.js} +16 -28
- package/src/executors/validate-no-architecture-cycles/executor.js.map +1 -0
- package/src/executors/validate-no-destructure/executor.d.ts +3 -0
- package/src/executors/validate-no-destructure/executor.js +10 -0
- package/src/executors/validate-no-destructure/executor.js.map +1 -0
- package/src/executors/validate-no-direct-api-resolver/executor.d.ts +3 -0
- package/src/executors/validate-no-direct-api-resolver/executor.js +10 -0
- package/src/executors/validate-no-direct-api-resolver/executor.js.map +1 -0
- package/src/executors/validate-no-implicit-any/executor.d.ts +3 -0
- package/src/executors/validate-no-implicit-any/executor.js +10 -0
- package/src/executors/validate-no-implicit-any/executor.js.map +1 -0
- package/src/executors/validate-no-inline-types/executor.d.ts +3 -0
- package/src/executors/validate-no-inline-types/executor.js +10 -0
- package/src/executors/validate-no-inline-types/executor.js.map +1 -0
- package/src/executors/validate-no-skiplevel-deps/executor.d.ts +19 -0
- package/src/executors/validate-no-skiplevel-deps/{executor.ts → executor.js} +23 -63
- package/src/executors/validate-no-skiplevel-deps/executor.js.map +1 -0
- package/src/executors/validate-no-unmanaged-exceptions/executor.d.ts +3 -0
- package/src/executors/validate-no-unmanaged-exceptions/executor.js +10 -0
- package/src/executors/validate-no-unmanaged-exceptions/executor.js.map +1 -0
- package/src/executors/validate-packagejson/executor.d.ts +16 -0
- package/src/executors/validate-packagejson/{executor.ts → executor.js} +15 -32
- package/src/executors/validate-packagejson/executor.js.map +1 -0
- package/src/executors/validate-prisma-converters/executor.d.ts +3 -0
- package/src/executors/validate-prisma-converters/executor.js +10 -0
- package/src/executors/validate-prisma-converters/executor.js.map +1 -0
- package/src/executors/validate-return-types/executor.d.ts +3 -0
- package/src/executors/validate-return-types/executor.js +10 -0
- package/src/executors/validate-return-types/executor.js.map +1 -0
- package/src/executors/validate-ts-in-src/executor.d.ts +32 -0
- package/src/executors/validate-ts-in-src/{executor.ts → executor.js} +80 -135
- package/src/executors/validate-ts-in-src/executor.js.map +1 -0
- package/src/executors/validate-versions-locked/executor.d.ts +22 -0
- package/src/executors/validate-versions-locked/{executor.ts → executor.js} +49 -116
- package/src/executors/validate-versions-locked/executor.js.map +1 -0
- package/src/executors/visualize/executor.d.ts +17 -0
- package/src/executors/visualize/{executor.ts → executor.js} +16 -30
- package/src/executors/visualize/executor.js.map +1 -0
- package/src/{index.ts → index.d.ts} +5 -1
- package/src/index.js +14 -0
- package/src/index.js.map +1 -0
- package/src/lib/graph-comparator.d.ts +39 -0
- package/src/lib/{graph-comparator.ts → graph-comparator.js} +18 -67
- package/src/lib/graph-comparator.js.map +1 -0
- package/src/lib/graph-generator.d.ts +19 -0
- package/src/lib/{graph-generator.ts → graph-generator.js} +17 -30
- package/src/lib/graph-generator.js.map +1 -0
- package/src/lib/graph-loader.d.ts +31 -0
- package/src/lib/{graph-loader.ts → graph-loader.js} +24 -42
- package/src/lib/graph-loader.js.map +1 -0
- package/src/lib/graph-sorter.d.ts +37 -0
- package/src/lib/{graph-sorter.ts → graph-sorter.js} +26 -53
- package/src/lib/graph-sorter.js.map +1 -0
- package/src/lib/graph-visualizer.d.ts +31 -0
- package/src/lib/{graph-visualizer.ts → graph-visualizer.js} +32 -56
- package/src/lib/graph-visualizer.js.map +1 -0
- package/src/lib/package-validator.d.ts +40 -0
- package/src/lib/{package-validator.ts → package-validator.js} +28 -88
- package/src/lib/package-validator.js.map +1 -0
- package/src/plugin.d.ts +86 -0
- package/src/{plugin.ts → plugin.js} +100 -255
- package/src/plugin.js.map +1 -0
- package/src/toError.d.ts +5 -0
- package/src/{toError.ts → toError.js} +7 -6
- package/src/toError.js.map +1 -0
- package/LICENSE +0 -373
- package/src/executor-result.ts +0 -7
- package/src/executors/validate-catch-error-pattern/executor.ts +0 -11
- package/src/executors/validate-code/executor.ts +0 -11
- package/src/executors/validate-dtos/executor.ts +0 -11
- package/src/executors/validate-modified-files/executor.ts +0 -11
- package/src/executors/validate-modified-methods/executor.ts +0 -11
- package/src/executors/validate-new-methods/executor.ts +0 -11
- package/src/executors/validate-no-any-unknown/executor.ts +0 -11
- package/src/executors/validate-no-destructure/executor.ts +0 -11
- package/src/executors/validate-no-direct-api-resolver/executor.ts +0 -11
- package/src/executors/validate-no-implicit-any/executor.ts +0 -11
- package/src/executors/validate-no-inline-types/executor.ts +0 -11
- package/src/executors/validate-no-unmanaged-exceptions/executor.ts +0 -11
- package/src/executors/validate-prisma-converters/executor.ts +0 -11
- package/src/executors/validate-return-types/executor.ts +0 -11
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Graph Sorter
|
|
3
4
|
*
|
|
@@ -6,20 +7,9 @@
|
|
|
6
7
|
* 2. Assign level numbers to each project (level 0 = no deps, level 1 = depends on level 0, etc.)
|
|
7
8
|
* 3. Group projects into layers for deterministic ordering
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.computeTopologicalLayers = computeTopologicalLayers;
|
|
12
|
+
exports.sortGraphTopologically = sortGraphTopologically;
|
|
23
13
|
/**
|
|
24
14
|
* Compute topological layers for dependency graph using Kahn's algorithm
|
|
25
15
|
*
|
|
@@ -29,88 +19,72 @@ export type EnhancedGraph = Record<string, GraphEntry>;
|
|
|
29
19
|
* @param graph - Dependency graph { project: [deps] }
|
|
30
20
|
* @returns Array of layers, each containing sorted project names
|
|
31
21
|
*/
|
|
32
|
-
|
|
33
|
-
const layers
|
|
34
|
-
const processed = new Set
|
|
22
|
+
function computeTopologicalLayers(graph) {
|
|
23
|
+
const layers = [];
|
|
24
|
+
const processed = new Set();
|
|
35
25
|
const allProjects = Object.keys(graph);
|
|
36
|
-
|
|
37
26
|
while (processed.size < allProjects.length) {
|
|
38
|
-
const currentLayer
|
|
39
|
-
|
|
27
|
+
const currentLayer = [];
|
|
40
28
|
for (const project of allProjects) {
|
|
41
|
-
if (processed.has(project))
|
|
42
|
-
|
|
29
|
+
if (processed.has(project))
|
|
30
|
+
continue;
|
|
43
31
|
const deps = graph[project] || [];
|
|
44
32
|
// Check if all dependencies are in previous layers (already processed)
|
|
45
33
|
const allDepsInPrevLayers = deps.every((dep) => processed.has(dep));
|
|
46
|
-
|
|
47
34
|
if (allDepsInPrevLayers) {
|
|
48
35
|
currentLayer.push(project);
|
|
49
36
|
}
|
|
50
37
|
}
|
|
51
|
-
|
|
52
38
|
if (currentLayer.length === 0) {
|
|
53
39
|
// No progress made = circular dependency detected
|
|
54
40
|
const remaining = allProjects.filter((p) => !processed.has(p));
|
|
55
|
-
|
|
56
41
|
// Try to identify the cycle
|
|
57
42
|
const cycleInfo = findCycle(graph, remaining);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
(cycleInfo ? `Cycle: ${cycleInfo}\n` : '') +
|
|
62
|
-
'Fix: Remove one of the dependencies to break the cycle.'
|
|
63
|
-
);
|
|
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.');
|
|
64
46
|
}
|
|
65
|
-
|
|
66
47
|
// Sort alphabetically within layer for deterministic output
|
|
67
48
|
currentLayer.sort();
|
|
68
49
|
layers.push(currentLayer);
|
|
69
|
-
|
|
70
50
|
// Mark as processed
|
|
71
51
|
currentLayer.forEach((p) => processed.add(p));
|
|
72
52
|
}
|
|
73
|
-
|
|
74
53
|
return layers;
|
|
75
54
|
}
|
|
76
|
-
|
|
77
55
|
/**
|
|
78
56
|
* Try to find and describe a cycle in the graph
|
|
79
57
|
*/
|
|
80
|
-
function findCycle(graph
|
|
81
|
-
const visited = new Set
|
|
82
|
-
const path
|
|
83
|
-
|
|
84
|
-
function dfs(node: string): string | null {
|
|
58
|
+
function findCycle(graph, remaining) {
|
|
59
|
+
const visited = new Set();
|
|
60
|
+
const path = [];
|
|
61
|
+
function dfs(node) {
|
|
85
62
|
if (path.includes(node)) {
|
|
86
63
|
const cycleStart = path.indexOf(node);
|
|
87
64
|
return [...path.slice(cycleStart), node].join(' -> ');
|
|
88
65
|
}
|
|
89
|
-
if (visited.has(node))
|
|
90
|
-
|
|
66
|
+
if (visited.has(node))
|
|
67
|
+
return null;
|
|
91
68
|
visited.add(node);
|
|
92
69
|
path.push(node);
|
|
93
|
-
|
|
94
70
|
const deps = graph[node] || [];
|
|
95
71
|
for (const dep of deps) {
|
|
96
72
|
if (remaining.includes(dep)) {
|
|
97
73
|
const result = dfs(dep);
|
|
98
|
-
if (result)
|
|
74
|
+
if (result)
|
|
75
|
+
return result;
|
|
99
76
|
}
|
|
100
77
|
}
|
|
101
|
-
|
|
102
78
|
path.pop();
|
|
103
79
|
return null;
|
|
104
80
|
}
|
|
105
|
-
|
|
106
81
|
for (const node of remaining) {
|
|
107
82
|
const cycle = dfs(node);
|
|
108
|
-
if (cycle)
|
|
83
|
+
if (cycle)
|
|
84
|
+
return cycle;
|
|
109
85
|
}
|
|
110
|
-
|
|
111
86
|
return null;
|
|
112
87
|
}
|
|
113
|
-
|
|
114
88
|
/**
|
|
115
89
|
* Sort graph in topological order with alphabetical sorting within layers
|
|
116
90
|
* Returns enhanced format with level metadata
|
|
@@ -118,10 +92,9 @@ function findCycle(graph: Record<string, string[]>, remaining: string[]): string
|
|
|
118
92
|
* @param graph - Unsorted dependency graph { project: [deps] }
|
|
119
93
|
* @returns Sorted graph with level metadata { project: { level: number, dependsOn: [deps] } }
|
|
120
94
|
*/
|
|
121
|
-
|
|
95
|
+
function sortGraphTopologically(graph) {
|
|
122
96
|
const layers = computeTopologicalLayers(graph);
|
|
123
|
-
const result
|
|
124
|
-
|
|
97
|
+
const result = {};
|
|
125
98
|
// Add projects layer by layer (dependencies before dependents)
|
|
126
99
|
layers.forEach((layer, levelIndex) => {
|
|
127
100
|
for (const project of layer) {
|
|
@@ -132,6 +105,6 @@ export function sortGraphTopologically(graph: Record<string, string[]>): Enhance
|
|
|
132
105
|
};
|
|
133
106
|
}
|
|
134
107
|
});
|
|
135
|
-
|
|
136
108
|
return result;
|
|
137
109
|
}
|
|
110
|
+
//# sourceMappingURL=graph-sorter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-sorter.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/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,31 @@
|
|
|
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
|
+
interface VisualizationPaths {
|
|
20
|
+
dotPath: string;
|
|
21
|
+
htmlPath: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Write visualization files to tmp/webpieces/
|
|
25
|
+
*/
|
|
26
|
+
export declare function writeVisualization(graph: EnhancedGraph, workspaceRoot: string, title?: string): VisualizationPaths;
|
|
27
|
+
/**
|
|
28
|
+
* Open the HTML visualization in the default browser
|
|
29
|
+
*/
|
|
30
|
+
export declare function openVisualization(htmlPath: string): boolean;
|
|
31
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Graph Visualizer
|
|
3
4
|
*
|
|
@@ -7,57 +8,54 @@
|
|
|
7
8
|
*
|
|
8
9
|
* Output files go to tmp/webpieces/ for easy viewing without committing.
|
|
9
10
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.generateDot = generateDot;
|
|
13
|
+
exports.generateHTML = generateHTML;
|
|
14
|
+
exports.writeVisualization = writeVisualization;
|
|
15
|
+
exports.openVisualization = openVisualization;
|
|
16
|
+
const tslib_1 = require("tslib");
|
|
17
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
18
|
+
const path = tslib_1.__importStar(require("path"));
|
|
19
|
+
const child_process_1 = require("child_process");
|
|
17
20
|
/**
|
|
18
21
|
* Level colors for visualization
|
|
19
22
|
*/
|
|
20
|
-
const LEVEL_COLORS
|
|
23
|
+
const LEVEL_COLORS = {
|
|
21
24
|
0: '#E8F5E9', // Light green - foundation
|
|
22
25
|
1: '#E3F2FD', // Light blue - middleware
|
|
23
26
|
2: '#FFF3E0', // Light orange - applications
|
|
24
27
|
3: '#FCE4EC', // Light pink - higher level
|
|
25
28
|
};
|
|
26
|
-
|
|
27
29
|
/**
|
|
28
30
|
* Remove scope from name for display
|
|
29
31
|
* '@scope/name' → 'name'
|
|
30
32
|
* 'name' → 'name'
|
|
31
33
|
*/
|
|
32
|
-
function getShortName(name
|
|
33
|
-
return name.includes('/') ? name.split('/').pop()
|
|
34
|
+
function getShortName(name) {
|
|
35
|
+
return name.includes('/') ? name.split('/').pop() : name;
|
|
34
36
|
}
|
|
35
|
-
|
|
36
37
|
/**
|
|
37
38
|
* Generate Graphviz DOT format from the graph
|
|
38
39
|
*/
|
|
39
|
-
|
|
40
|
+
function generateDot(graph, title = 'WebPieces Architecture') {
|
|
40
41
|
let dot = 'digraph Architecture {\n';
|
|
41
42
|
dot += ' rankdir=TB;\n';
|
|
42
43
|
dot += ' node [shape=box, style=filled, fontname="Arial"];\n';
|
|
43
44
|
dot += ' edge [fontname="Arial"];\n\n';
|
|
44
|
-
|
|
45
45
|
// Group projects by level
|
|
46
|
-
const levels
|
|
46
|
+
const levels = {};
|
|
47
47
|
for (const [project, info] of Object.entries(graph)) {
|
|
48
|
-
if (!levels[info.level])
|
|
48
|
+
if (!levels[info.level])
|
|
49
|
+
levels[info.level] = [];
|
|
49
50
|
levels[info.level].push(project);
|
|
50
51
|
}
|
|
51
|
-
|
|
52
52
|
// Create nodes with level-based colors
|
|
53
53
|
for (const [project, info] of Object.entries(graph)) {
|
|
54
54
|
const shortName = getShortName(project);
|
|
55
55
|
const color = LEVEL_COLORS[info.level] || '#F5F5F5';
|
|
56
56
|
dot += ` "${shortName}" [fillcolor="${color}", label="${shortName}\\n(L${info.level})"];\n`;
|
|
57
57
|
}
|
|
58
|
-
|
|
59
58
|
dot += '\n';
|
|
60
|
-
|
|
61
59
|
// Create same-rank subgraphs for each level
|
|
62
60
|
for (const [level, projects] of Object.entries(levels)) {
|
|
63
61
|
dot += ` { rank=same; `;
|
|
@@ -67,9 +65,7 @@ export function generateDot(graph: EnhancedGraph, title: string = 'WebPieces Arc
|
|
|
67
65
|
});
|
|
68
66
|
dot += '}\n';
|
|
69
67
|
}
|
|
70
|
-
|
|
71
68
|
dot += '\n';
|
|
72
|
-
|
|
73
69
|
// Create edges (dependencies)
|
|
74
70
|
for (const [project, info] of Object.entries(graph)) {
|
|
75
71
|
const shortName = getShortName(project);
|
|
@@ -78,23 +74,19 @@ export function generateDot(graph: EnhancedGraph, title: string = 'WebPieces Arc
|
|
|
78
74
|
dot += ` "${shortName}" -> "${depShortName}";\n`;
|
|
79
75
|
}
|
|
80
76
|
}
|
|
81
|
-
|
|
82
77
|
dot += '\n labelloc="t";\n';
|
|
83
78
|
dot += ` label="${title}\\n(from architecture/dependencies.json)";\n`;
|
|
84
79
|
dot += ' fontsize=20;\n';
|
|
85
80
|
dot += '}\n';
|
|
86
|
-
|
|
87
81
|
return dot;
|
|
88
82
|
}
|
|
89
|
-
|
|
90
83
|
/**
|
|
91
84
|
* Generate interactive HTML with embedded SVG using viz.js
|
|
92
85
|
*/
|
|
93
|
-
|
|
86
|
+
function generateHTML(dot, title = 'WebPieces Architecture') {
|
|
94
87
|
const styles = generateHTMLStyles();
|
|
95
88
|
const legend = generateHTMLLegend();
|
|
96
89
|
const script = generateHTMLScript(dot);
|
|
97
|
-
|
|
98
90
|
return `<!DOCTYPE html>
|
|
99
91
|
<html>
|
|
100
92
|
<head>
|
|
@@ -111,8 +103,7 @@ export function generateHTML(dot: string, title: string = 'WebPieces Architectur
|
|
|
111
103
|
</body>
|
|
112
104
|
</html>`;
|
|
113
105
|
}
|
|
114
|
-
|
|
115
|
-
function generateHTMLStyles(): string {
|
|
106
|
+
function generateHTMLStyles() {
|
|
116
107
|
return `
|
|
117
108
|
body {
|
|
118
109
|
margin: 0;
|
|
@@ -155,8 +146,7 @@ function generateHTMLStyles(): string {
|
|
|
155
146
|
}
|
|
156
147
|
`;
|
|
157
148
|
}
|
|
158
|
-
|
|
159
|
-
function generateHTMLLegend(): string {
|
|
149
|
+
function generateHTMLLegend() {
|
|
160
150
|
return `<div class="legend">
|
|
161
151
|
<h2>Legend</h2>
|
|
162
152
|
<div class="legend-item">
|
|
@@ -176,8 +166,7 @@ function generateHTMLLegend(): string {
|
|
|
176
166
|
</div>
|
|
177
167
|
</div>`;
|
|
178
168
|
}
|
|
179
|
-
|
|
180
|
-
function generateHTMLScript(dot: string): string {
|
|
169
|
+
function generateHTMLScript(dot) {
|
|
181
170
|
return `
|
|
182
171
|
const dot = ${JSON.stringify(dot)};
|
|
183
172
|
const viz = new Viz();
|
|
@@ -192,62 +181,49 @@ function generateHTMLScript(dot: string): string {
|
|
|
192
181
|
});
|
|
193
182
|
`;
|
|
194
183
|
}
|
|
195
|
-
|
|
196
|
-
interface VisualizationPaths {
|
|
197
|
-
dotPath: string;
|
|
198
|
-
htmlPath: string;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
184
|
/**
|
|
202
185
|
* Write visualization files to tmp/webpieces/
|
|
203
186
|
*/
|
|
204
|
-
|
|
205
|
-
graph: EnhancedGraph,
|
|
206
|
-
workspaceRoot: string,
|
|
207
|
-
title: string = 'WebPieces Architecture'
|
|
208
|
-
): VisualizationPaths {
|
|
187
|
+
function writeVisualization(graph, workspaceRoot, title = 'WebPieces Architecture') {
|
|
209
188
|
const outputDir = path.join(workspaceRoot, 'tmp', 'webpieces');
|
|
210
|
-
|
|
211
189
|
// Ensure directory exists
|
|
212
190
|
if (!fs.existsSync(outputDir)) {
|
|
213
191
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
214
192
|
}
|
|
215
|
-
|
|
216
193
|
// Generate DOT
|
|
217
194
|
const dot = generateDot(graph, title);
|
|
218
195
|
const dotPath = path.join(outputDir, 'architecture.dot');
|
|
219
196
|
fs.writeFileSync(dotPath, dot, 'utf-8');
|
|
220
|
-
|
|
221
197
|
// Generate HTML
|
|
222
198
|
const html = generateHTML(dot, title);
|
|
223
199
|
const htmlPath = path.join(outputDir, 'architecture.html');
|
|
224
200
|
fs.writeFileSync(htmlPath, html, 'utf-8');
|
|
225
|
-
|
|
226
201
|
return { dotPath, htmlPath };
|
|
227
202
|
}
|
|
228
|
-
|
|
229
203
|
/**
|
|
230
204
|
* Open the HTML visualization in the default browser
|
|
231
205
|
*/
|
|
232
|
-
|
|
206
|
+
function openVisualization(htmlPath) {
|
|
233
207
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
234
208
|
try {
|
|
235
209
|
const platform = process.platform;
|
|
236
|
-
let openCommand
|
|
237
|
-
|
|
210
|
+
let openCommand;
|
|
238
211
|
if (platform === 'darwin') {
|
|
239
212
|
openCommand = `open "${htmlPath}"`;
|
|
240
|
-
}
|
|
213
|
+
}
|
|
214
|
+
else if (platform === 'win32') {
|
|
241
215
|
openCommand = `start "" "${htmlPath}"`;
|
|
242
|
-
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
243
218
|
openCommand = `xdg-open "${htmlPath}"`;
|
|
244
219
|
}
|
|
245
|
-
|
|
246
|
-
execSync(openCommand, { stdio: 'ignore' });
|
|
220
|
+
(0, child_process_1.execSync)(openCommand, { stdio: 'ignore' });
|
|
247
221
|
return true;
|
|
248
|
-
}
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
249
224
|
//const error = toError(err);
|
|
250
225
|
void err;
|
|
251
226
|
return false;
|
|
252
227
|
}
|
|
253
228
|
}
|
|
229
|
+
//# sourceMappingURL=graph-visualizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-visualizer.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-visualizer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Visualizer\n *\n * Generates visual representations of the architecture graph:\n * - DOT format (for Graphviz)\n * - Interactive HTML (using viz.js)\n *\n * Output files go to tmp/webpieces/ for easy viewing without committing.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { execSync } from 'child_process';\nimport type { EnhancedGraph } from './graph-sorter';\nimport { toError } from '../toError';\n\n/**\n * Level colors for visualization\n */\nconst LEVEL_COLORS: Record<number, string> = {\n 0: '#E8F5E9', // Light green - foundation\n 1: '#E3F2FD', // Light blue - middleware\n 2: '#FFF3E0', // Light orange - applications\n 3: '#FCE4EC', // Light pink - higher level\n};\n\n/**\n * Remove scope from name for display\n * '@scope/name' → 'name'\n * 'name' → 'name'\n */\nfunction getShortName(name: string): string {\n return name.includes('/') ? name.split('/').pop()! : name;\n}\n\n/**\n * Generate Graphviz DOT format from the graph\n */\nexport function generateDot(graph: EnhancedGraph, title: string = 'WebPieces Architecture'): string {\n let dot = 'digraph Architecture {\\n';\n dot += ' rankdir=TB;\\n';\n dot += ' node [shape=box, style=filled, fontname=\"Arial\"];\\n';\n dot += ' edge [fontname=\"Arial\"];\\n\\n';\n\n // Group projects by level\n const levels: Record<number, string[]> = {};\n for (const [project, info] of Object.entries(graph)) {\n if (!levels[info.level]) levels[info.level] = [];\n levels[info.level].push(project);\n }\n\n // Create nodes with level-based colors\n for (const [project, info] of Object.entries(graph)) {\n const shortName = getShortName(project);\n const color = LEVEL_COLORS[info.level] || '#F5F5F5';\n dot += ` \"${shortName}\" [fillcolor=\"${color}\", label=\"${shortName}\\\\n(L${info.level})\"];\\n`;\n }\n\n dot += '\\n';\n\n // Create same-rank subgraphs for each level\n for (const [level, projects] of Object.entries(levels)) {\n dot += ` { rank=same; `;\n projects.forEach((p) => {\n const shortName = getShortName(p);\n dot += `\"${shortName}\"; `;\n });\n dot += '}\\n';\n }\n\n dot += '\\n';\n\n // Create edges (dependencies)\n for (const [project, info] of Object.entries(graph)) {\n const shortName = getShortName(project);\n for (const dep of info.dependsOn || []) {\n const depShortName = getShortName(dep);\n dot += ` \"${shortName}\" -> \"${depShortName}\";\\n`;\n }\n }\n\n dot += '\\n labelloc=\"t\";\\n';\n dot += ` label=\"${title}\\\\n(from architecture/dependencies.json)\";\\n`;\n dot += ' fontsize=20;\\n';\n dot += '}\\n';\n\n return dot;\n}\n\n/**\n * Generate interactive HTML with embedded SVG using viz.js\n */\nexport function generateHTML(dot: string, title: string = 'WebPieces Architecture'): string {\n const styles = generateHTMLStyles();\n const legend = generateHTMLLegend();\n const script = generateHTMLScript(dot);\n\n return `<!DOCTYPE html>\n<html>\n<head>\n <title>${title}</title>\n <script src=\"https://cdn.jsdelivr.net/npm/viz.js@2.1.2/viz.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/viz.js@2.1.2/full.render.js\"></script>\n <style>${styles}</style>\n</head>\n<body>\n <h1>${title}</h1>\n ${legend}\n <div id=\"graph\"></div>\n <script>${script}</script>\n</body>\n</html>`;\n}\n\nfunction generateHTMLStyles(): string {\n return `\n body {\n margin: 0;\n padding: 20px;\n font-family: Arial, sans-serif;\n background: #f5f5f5;\n }\n h1 {\n text-align: center;\n color: #333;\n }\n #graph {\n text-align: center;\n background: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n .legend {\n margin: 20px auto;\n max-width: 600px;\n padding: 15px;\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n .legend h2 {\n margin-top: 0;\n }\n .legend-item {\n margin: 8px 0;\n }\n .legend-box {\n display: inline-block;\n width: 20px;\n height: 20px;\n border: 1px solid #ccc;\n margin-right: 10px;\n vertical-align: middle;\n }\n `;\n}\n\nfunction generateHTMLLegend(): string {\n return `<div class=\"legend\">\n <h2>Legend</h2>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #E8F5E9;\"></span>\n <strong>Level 0:</strong> Foundation libraries (no dependencies)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #E3F2FD;\"></span>\n <strong>Level 1:</strong> Middleware libraries (depend on Level 0)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #FFF3E0;\"></span>\n <strong>Level 2:</strong> Applications (depend on Level 1)\n </div>\n <div class=\"legend-item\" style=\"margin-top: 15px;\">\n <em>Note: Transitive dependencies are allowed but not shown in the graph.</em>\n </div>\n </div>`;\n}\n\nfunction generateHTMLScript(dot: string): string {\n return `\n const dot = ${JSON.stringify(dot)};\n const viz = new Viz();\n\n viz.renderSVGElement(dot)\n .then(element => {\n document.getElementById('graph').appendChild(element);\n })\n .catch(err => {\n console.error(err);\n document.getElementById('graph').innerHTML = '<pre>' + err + '</pre>';\n });\n `;\n}\n\ninterface VisualizationPaths {\n dotPath: string;\n htmlPath: string;\n}\n\n/**\n * Write visualization files to tmp/webpieces/\n */\nexport function writeVisualization(\n graph: EnhancedGraph,\n workspaceRoot: string,\n title: string = 'WebPieces Architecture'\n): VisualizationPaths {\n const outputDir = path.join(workspaceRoot, 'tmp', 'webpieces');\n\n // Ensure directory exists\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true });\n }\n\n // Generate DOT\n const dot = generateDot(graph, title);\n const dotPath = path.join(outputDir, 'architecture.dot');\n fs.writeFileSync(dotPath, dot, 'utf-8');\n\n // Generate HTML\n const html = generateHTML(dot, title);\n const htmlPath = path.join(outputDir, 'architecture.html');\n fs.writeFileSync(htmlPath, html, 'utf-8');\n\n return { dotPath, htmlPath };\n}\n\n/**\n * Open the HTML visualization in the default browser\n */\nexport function openVisualization(htmlPath: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const platform = process.platform;\n let openCommand: string;\n\n if (platform === 'darwin') {\n openCommand = `open \"${htmlPath}\"`;\n } else if (platform === 'win32') {\n openCommand = `start \"\" \"${htmlPath}\"`;\n } else {\n openCommand = `xdg-open \"${htmlPath}\"`;\n }\n\n execSync(openCommand, { stdio: 'ignore' });\n return true;\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates that package.json dependencies match the project.json build.dependsOn
|
|
5
|
+
* This ensures the two sources of truth don't drift apart.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Validation result for a single project
|
|
9
|
+
*/
|
|
10
|
+
export interface ProjectValidationResult {
|
|
11
|
+
project: string;
|
|
12
|
+
valid: boolean;
|
|
13
|
+
missingInPackageJson: string[];
|
|
14
|
+
extraInPackageJson: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Overall validation result
|
|
18
|
+
*/
|
|
19
|
+
export interface ValidationResult {
|
|
20
|
+
valid: boolean;
|
|
21
|
+
errors: string[];
|
|
22
|
+
projectResults: ProjectValidationResult[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validate that package.json dependencies match the dependency graph
|
|
26
|
+
*
|
|
27
|
+
* For each project in the graph:
|
|
28
|
+
* - Check that all graph dependencies exist in package.json
|
|
29
|
+
* - Maps project names to package names for accurate comparison
|
|
30
|
+
*
|
|
31
|
+
* @param graph - Enhanced graph with project dependencies (uses project names)
|
|
32
|
+
* @param workspaceRoot - Absolute path to workspace root
|
|
33
|
+
* @returns Validation result with errors if any
|
|
34
|
+
*/
|
|
35
|
+
interface GraphEntry {
|
|
36
|
+
level: number;
|
|
37
|
+
dependsOn: string[];
|
|
38
|
+
}
|
|
39
|
+
export declare function validatePackageJsonDependencies(graph: Record<string, GraphEntry>, workspaceRoot: string): Promise<ValidationResult>;
|
|
40
|
+
export {};
|