@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,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Graph Visualizer
|
|
4
|
+
*
|
|
5
|
+
* Generates visual representations of the architecture graph:
|
|
6
|
+
* - DOT format (for Graphviz)
|
|
7
|
+
* - Interactive HTML (using viz.js)
|
|
8
|
+
*
|
|
9
|
+
* Output files go to tmp/webpieces/ for easy viewing without committing.
|
|
10
|
+
*/
|
|
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");
|
|
20
|
+
/**
|
|
21
|
+
* Level colors for visualization
|
|
22
|
+
*/
|
|
23
|
+
const LEVEL_COLORS = {
|
|
24
|
+
0: '#E8F5E9', // Light green - foundation
|
|
25
|
+
1: '#E3F2FD', // Light blue - middleware
|
|
26
|
+
2: '#FFF3E0', // Light orange - applications
|
|
27
|
+
3: '#FCE4EC', // Light pink - higher level
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Generate Graphviz DOT format from the graph
|
|
31
|
+
*/
|
|
32
|
+
function generateDot(graph, title = 'WebPieces Architecture') {
|
|
33
|
+
let dot = 'digraph Architecture {\n';
|
|
34
|
+
dot += ' rankdir=TB;\n';
|
|
35
|
+
dot += ' node [shape=box, style=filled, fontname="Arial"];\n';
|
|
36
|
+
dot += ' edge [fontname="Arial"];\n\n';
|
|
37
|
+
// Group projects by level
|
|
38
|
+
const levels = {};
|
|
39
|
+
for (const [project, info] of Object.entries(graph)) {
|
|
40
|
+
if (!levels[info.level])
|
|
41
|
+
levels[info.level] = [];
|
|
42
|
+
levels[info.level].push(project);
|
|
43
|
+
}
|
|
44
|
+
// Create nodes with level-based colors
|
|
45
|
+
for (const [project, info] of Object.entries(graph)) {
|
|
46
|
+
const shortName = project.replace('@webpieces/', '');
|
|
47
|
+
const color = LEVEL_COLORS[info.level] || '#F5F5F5';
|
|
48
|
+
dot += ` "${shortName}" [fillcolor="${color}", label="${shortName}\\n(L${info.level})"];\n`;
|
|
49
|
+
}
|
|
50
|
+
dot += '\n';
|
|
51
|
+
// Create same-rank subgraphs for each level
|
|
52
|
+
for (const [level, projects] of Object.entries(levels)) {
|
|
53
|
+
dot += ` { rank=same; `;
|
|
54
|
+
projects.forEach((p) => {
|
|
55
|
+
const shortName = p.replace('@webpieces/', '');
|
|
56
|
+
dot += `"${shortName}"; `;
|
|
57
|
+
});
|
|
58
|
+
dot += '}\n';
|
|
59
|
+
}
|
|
60
|
+
dot += '\n';
|
|
61
|
+
// Create edges (dependencies)
|
|
62
|
+
for (const [project, info] of Object.entries(graph)) {
|
|
63
|
+
const shortName = project.replace('@webpieces/', '');
|
|
64
|
+
for (const dep of info.dependsOn || []) {
|
|
65
|
+
const depShortName = dep.replace('@webpieces/', '');
|
|
66
|
+
dot += ` "${shortName}" -> "${depShortName}";\n`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
dot += '\n labelloc="t";\n';
|
|
70
|
+
dot += ` label="${title}\\n(from architecture/dependencies.json)";\n`;
|
|
71
|
+
dot += ' fontsize=20;\n';
|
|
72
|
+
dot += '}\n';
|
|
73
|
+
return dot;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate interactive HTML with embedded SVG using viz.js
|
|
77
|
+
*/
|
|
78
|
+
function generateHTML(dot, title = 'WebPieces Architecture') {
|
|
79
|
+
return `<!DOCTYPE html>
|
|
80
|
+
<html>
|
|
81
|
+
<head>
|
|
82
|
+
<title>${title}</title>
|
|
83
|
+
<script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/viz.js"></script>
|
|
84
|
+
<script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/full.render.js"></script>
|
|
85
|
+
<style>
|
|
86
|
+
body {
|
|
87
|
+
margin: 0;
|
|
88
|
+
padding: 20px;
|
|
89
|
+
font-family: Arial, sans-serif;
|
|
90
|
+
background: #f5f5f5;
|
|
91
|
+
}
|
|
92
|
+
h1 {
|
|
93
|
+
text-align: center;
|
|
94
|
+
color: #333;
|
|
95
|
+
}
|
|
96
|
+
#graph {
|
|
97
|
+
text-align: center;
|
|
98
|
+
background: white;
|
|
99
|
+
padding: 20px;
|
|
100
|
+
border-radius: 8px;
|
|
101
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
102
|
+
}
|
|
103
|
+
.legend {
|
|
104
|
+
margin: 20px auto;
|
|
105
|
+
max-width: 600px;
|
|
106
|
+
padding: 15px;
|
|
107
|
+
background: white;
|
|
108
|
+
border-radius: 8px;
|
|
109
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
110
|
+
}
|
|
111
|
+
.legend h2 {
|
|
112
|
+
margin-top: 0;
|
|
113
|
+
}
|
|
114
|
+
.legend-item {
|
|
115
|
+
margin: 8px 0;
|
|
116
|
+
}
|
|
117
|
+
.legend-box {
|
|
118
|
+
display: inline-block;
|
|
119
|
+
width: 20px;
|
|
120
|
+
height: 20px;
|
|
121
|
+
border: 1px solid #ccc;
|
|
122
|
+
margin-right: 10px;
|
|
123
|
+
vertical-align: middle;
|
|
124
|
+
}
|
|
125
|
+
</style>
|
|
126
|
+
</head>
|
|
127
|
+
<body>
|
|
128
|
+
<h1>${title}</h1>
|
|
129
|
+
|
|
130
|
+
<div class="legend">
|
|
131
|
+
<h2>Legend</h2>
|
|
132
|
+
<div class="legend-item">
|
|
133
|
+
<span class="legend-box" style="background: #E8F5E9;"></span>
|
|
134
|
+
<strong>Level 0:</strong> Foundation libraries (no dependencies)
|
|
135
|
+
</div>
|
|
136
|
+
<div class="legend-item">
|
|
137
|
+
<span class="legend-box" style="background: #E3F2FD;"></span>
|
|
138
|
+
<strong>Level 1:</strong> Middleware libraries (depend on Level 0)
|
|
139
|
+
</div>
|
|
140
|
+
<div class="legend-item">
|
|
141
|
+
<span class="legend-box" style="background: #FFF3E0;"></span>
|
|
142
|
+
<strong>Level 2:</strong> Applications (depend on Level 1)
|
|
143
|
+
</div>
|
|
144
|
+
<div class="legend-item" style="margin-top: 15px;">
|
|
145
|
+
<em>Note: Transitive dependencies are allowed but not shown in the graph.</em>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div id="graph"></div>
|
|
150
|
+
|
|
151
|
+
<script>
|
|
152
|
+
const dot = ${JSON.stringify(dot)};
|
|
153
|
+
const viz = new Viz();
|
|
154
|
+
|
|
155
|
+
viz.renderSVGElement(dot)
|
|
156
|
+
.then(element => {
|
|
157
|
+
document.getElementById('graph').appendChild(element);
|
|
158
|
+
})
|
|
159
|
+
.catch(err => {
|
|
160
|
+
console.error(err);
|
|
161
|
+
document.getElementById('graph').innerHTML = '<pre>' + err + '</pre>';
|
|
162
|
+
});
|
|
163
|
+
</script>
|
|
164
|
+
</body>
|
|
165
|
+
</html>`;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Write visualization files to tmp/webpieces/
|
|
169
|
+
*/
|
|
170
|
+
function writeVisualization(graph, workspaceRoot, title = 'WebPieces Architecture') {
|
|
171
|
+
const outputDir = path.join(workspaceRoot, 'tmp', 'webpieces');
|
|
172
|
+
// Ensure directory exists
|
|
173
|
+
if (!fs.existsSync(outputDir)) {
|
|
174
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
175
|
+
}
|
|
176
|
+
// Generate DOT
|
|
177
|
+
const dot = generateDot(graph, title);
|
|
178
|
+
const dotPath = path.join(outputDir, 'architecture.dot');
|
|
179
|
+
fs.writeFileSync(dotPath, dot, 'utf-8');
|
|
180
|
+
// Generate HTML
|
|
181
|
+
const html = generateHTML(dot, title);
|
|
182
|
+
const htmlPath = path.join(outputDir, 'architecture.html');
|
|
183
|
+
fs.writeFileSync(htmlPath, html, 'utf-8');
|
|
184
|
+
return { dotPath, htmlPath };
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Open the HTML visualization in the default browser
|
|
188
|
+
*/
|
|
189
|
+
function openVisualization(htmlPath) {
|
|
190
|
+
try {
|
|
191
|
+
const platform = process.platform;
|
|
192
|
+
let openCommand;
|
|
193
|
+
if (platform === 'darwin') {
|
|
194
|
+
openCommand = `open "${htmlPath}"`;
|
|
195
|
+
}
|
|
196
|
+
else if (platform === 'win32') {
|
|
197
|
+
openCommand = `start "" "${htmlPath}"`;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
openCommand = `xdg-open "${htmlPath}"`;
|
|
201
|
+
}
|
|
202
|
+
(0, child_process_1.execSync)(openCommand, { stdio: 'ignore' });
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=graph-visualizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-visualizer.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/architecture/lib/graph-visualizer.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAoBH,kCAiDC;AAKD,oCAwFC;AAKD,gDAuBC;AAKD,8CAkBC;;AAnND,+CAAyB;AACzB,mDAA6B;AAC7B,iDAAyC;AAGzC;;GAEG;AACH,MAAM,YAAY,GAA2B;IACzC,CAAC,EAAE,SAAS,EAAE,2BAA2B;IACzC,CAAC,EAAE,SAAS,EAAE,0BAA0B;IACxC,CAAC,EAAE,SAAS,EAAE,8BAA8B;IAC5C,CAAC,EAAE,SAAS,EAAE,4BAA4B;CAC7C,CAAC;AAEF;;GAEG;AACH,SAAgB,WAAW,CAAC,KAAoB,EAAE,QAAgB,wBAAwB;IACtF,IAAI,GAAG,GAAG,0BAA0B,CAAC;IACrC,GAAG,IAAI,iBAAiB,CAAC;IACzB,GAAG,IAAI,uDAAuD,CAAC;IAC/D,GAAG,IAAI,gCAAgC,CAAC;IAExC,0BAA0B;IAC1B,MAAM,MAAM,GAA6B,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,uCAAuC;IACvC,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC;QACpD,GAAG,IAAI,MAAM,SAAS,iBAAiB,KAAK,aAAa,SAAS,QAAQ,IAAI,CAAC,KAAK,QAAQ,CAAC;IACjG,CAAC;IAED,GAAG,IAAI,IAAI,CAAC;IAEZ,4CAA4C;IAC5C,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,GAAG,IAAI,iBAAiB,CAAC;QACzB,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACnB,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;YAC/C,GAAG,IAAI,IAAI,SAAS,KAAK,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,GAAG,IAAI,KAAK,CAAC;IACjB,CAAC;IAED,GAAG,IAAI,IAAI,CAAC;IAEZ,8BAA8B;IAC9B,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACrD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;YACrC,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;YACpD,GAAG,IAAI,MAAM,SAAS,SAAS,YAAY,MAAM,CAAC;QACtD,CAAC;IACL,CAAC;IAED,GAAG,IAAI,qBAAqB,CAAC;IAC7B,GAAG,IAAI,YAAY,KAAK,8CAA8C,CAAC;IACvE,GAAG,IAAI,kBAAkB,CAAC;IAC1B,GAAG,IAAI,KAAK,CAAC;IAEb,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,GAAW,EAAE,QAAgB,wBAAwB;IAC9E,OAAO;;;aAGE,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8CR,KAAK;;;;;;;;;;;;;;;;;;;;;;;;sBAwBO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;;;;;;;;;;;;;QAajC,CAAC;AACT,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB,CAC9B,KAAoB,EACpB,aAAqB,EACrB,QAAgB,wBAAwB;IAExC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IAE/D,0BAA0B;IAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,eAAe;IACf,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;IACzD,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAExC,gBAAgB;IAChB,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAC3D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAE1C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,SAAgB,iBAAiB,CAAC,QAAgB;IAC9C,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAClC,IAAI,WAAmB,CAAC;QAExB,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACxB,WAAW,GAAG,SAAS,QAAQ,GAAG,CAAC;QACvC,CAAC;aAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YAC9B,WAAW,GAAG,aAAa,QAAQ,GAAG,CAAC;QAC3C,CAAC;aAAM,CAAC;YACJ,WAAW,GAAG,aAAa,QAAQ,GAAG,CAAC;QAC3C,CAAC;QAED,IAAA,wBAAQ,EAAC,WAAW,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC","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';\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 * 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 = project.replace('@webpieces/', '');\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 = p.replace('@webpieces/', '');\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 = project.replace('@webpieces/', '');\n for (const dep of info.dependsOn || []) {\n const depShortName = dep.replace('@webpieces/', '');\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 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>\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 </style>\n</head>\n<body>\n <h1>${title}</h1>\n\n <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 <div id=\"graph\"></div>\n\n <script>\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 </script>\n</body>\n</html>`;\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): { dotPath: string; htmlPath: string } {\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 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 return false;\n }\n}\n"]}
|
|
@@ -0,0 +1,222 @@
|
|
|
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
|
+
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
import type { EnhancedGraph } from './graph-sorter';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Level colors for visualization
|
|
18
|
+
*/
|
|
19
|
+
const LEVEL_COLORS: Record<number, string> = {
|
|
20
|
+
0: '#E8F5E9', // Light green - foundation
|
|
21
|
+
1: '#E3F2FD', // Light blue - middleware
|
|
22
|
+
2: '#FFF3E0', // Light orange - applications
|
|
23
|
+
3: '#FCE4EC', // Light pink - higher level
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate Graphviz DOT format from the graph
|
|
28
|
+
*/
|
|
29
|
+
export function generateDot(graph: EnhancedGraph, title: string = 'WebPieces Architecture'): string {
|
|
30
|
+
let dot = 'digraph Architecture {\n';
|
|
31
|
+
dot += ' rankdir=TB;\n';
|
|
32
|
+
dot += ' node [shape=box, style=filled, fontname="Arial"];\n';
|
|
33
|
+
dot += ' edge [fontname="Arial"];\n\n';
|
|
34
|
+
|
|
35
|
+
// Group projects by level
|
|
36
|
+
const levels: Record<number, string[]> = {};
|
|
37
|
+
for (const [project, info] of Object.entries(graph)) {
|
|
38
|
+
if (!levels[info.level]) levels[info.level] = [];
|
|
39
|
+
levels[info.level].push(project);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Create nodes with level-based colors
|
|
43
|
+
for (const [project, info] of Object.entries(graph)) {
|
|
44
|
+
const shortName = project.replace('@webpieces/', '');
|
|
45
|
+
const color = LEVEL_COLORS[info.level] || '#F5F5F5';
|
|
46
|
+
dot += ` "${shortName}" [fillcolor="${color}", label="${shortName}\\n(L${info.level})"];\n`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
dot += '\n';
|
|
50
|
+
|
|
51
|
+
// Create same-rank subgraphs for each level
|
|
52
|
+
for (const [level, projects] of Object.entries(levels)) {
|
|
53
|
+
dot += ` { rank=same; `;
|
|
54
|
+
projects.forEach((p) => {
|
|
55
|
+
const shortName = p.replace('@webpieces/', '');
|
|
56
|
+
dot += `"${shortName}"; `;
|
|
57
|
+
});
|
|
58
|
+
dot += '}\n';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
dot += '\n';
|
|
62
|
+
|
|
63
|
+
// Create edges (dependencies)
|
|
64
|
+
for (const [project, info] of Object.entries(graph)) {
|
|
65
|
+
const shortName = project.replace('@webpieces/', '');
|
|
66
|
+
for (const dep of info.dependsOn || []) {
|
|
67
|
+
const depShortName = dep.replace('@webpieces/', '');
|
|
68
|
+
dot += ` "${shortName}" -> "${depShortName}";\n`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
dot += '\n labelloc="t";\n';
|
|
73
|
+
dot += ` label="${title}\\n(from architecture/dependencies.json)";\n`;
|
|
74
|
+
dot += ' fontsize=20;\n';
|
|
75
|
+
dot += '}\n';
|
|
76
|
+
|
|
77
|
+
return dot;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Generate interactive HTML with embedded SVG using viz.js
|
|
82
|
+
*/
|
|
83
|
+
export function generateHTML(dot: string, title: string = 'WebPieces Architecture'): string {
|
|
84
|
+
return `<!DOCTYPE html>
|
|
85
|
+
<html>
|
|
86
|
+
<head>
|
|
87
|
+
<title>${title}</title>
|
|
88
|
+
<script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/viz.js"></script>
|
|
89
|
+
<script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/full.render.js"></script>
|
|
90
|
+
<style>
|
|
91
|
+
body {
|
|
92
|
+
margin: 0;
|
|
93
|
+
padding: 20px;
|
|
94
|
+
font-family: Arial, sans-serif;
|
|
95
|
+
background: #f5f5f5;
|
|
96
|
+
}
|
|
97
|
+
h1 {
|
|
98
|
+
text-align: center;
|
|
99
|
+
color: #333;
|
|
100
|
+
}
|
|
101
|
+
#graph {
|
|
102
|
+
text-align: center;
|
|
103
|
+
background: white;
|
|
104
|
+
padding: 20px;
|
|
105
|
+
border-radius: 8px;
|
|
106
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
107
|
+
}
|
|
108
|
+
.legend {
|
|
109
|
+
margin: 20px auto;
|
|
110
|
+
max-width: 600px;
|
|
111
|
+
padding: 15px;
|
|
112
|
+
background: white;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
115
|
+
}
|
|
116
|
+
.legend h2 {
|
|
117
|
+
margin-top: 0;
|
|
118
|
+
}
|
|
119
|
+
.legend-item {
|
|
120
|
+
margin: 8px 0;
|
|
121
|
+
}
|
|
122
|
+
.legend-box {
|
|
123
|
+
display: inline-block;
|
|
124
|
+
width: 20px;
|
|
125
|
+
height: 20px;
|
|
126
|
+
border: 1px solid #ccc;
|
|
127
|
+
margin-right: 10px;
|
|
128
|
+
vertical-align: middle;
|
|
129
|
+
}
|
|
130
|
+
</style>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<h1>${title}</h1>
|
|
134
|
+
|
|
135
|
+
<div class="legend">
|
|
136
|
+
<h2>Legend</h2>
|
|
137
|
+
<div class="legend-item">
|
|
138
|
+
<span class="legend-box" style="background: #E8F5E9;"></span>
|
|
139
|
+
<strong>Level 0:</strong> Foundation libraries (no dependencies)
|
|
140
|
+
</div>
|
|
141
|
+
<div class="legend-item">
|
|
142
|
+
<span class="legend-box" style="background: #E3F2FD;"></span>
|
|
143
|
+
<strong>Level 1:</strong> Middleware libraries (depend on Level 0)
|
|
144
|
+
</div>
|
|
145
|
+
<div class="legend-item">
|
|
146
|
+
<span class="legend-box" style="background: #FFF3E0;"></span>
|
|
147
|
+
<strong>Level 2:</strong> Applications (depend on Level 1)
|
|
148
|
+
</div>
|
|
149
|
+
<div class="legend-item" style="margin-top: 15px;">
|
|
150
|
+
<em>Note: Transitive dependencies are allowed but not shown in the graph.</em>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div id="graph"></div>
|
|
155
|
+
|
|
156
|
+
<script>
|
|
157
|
+
const dot = ${JSON.stringify(dot)};
|
|
158
|
+
const viz = new Viz();
|
|
159
|
+
|
|
160
|
+
viz.renderSVGElement(dot)
|
|
161
|
+
.then(element => {
|
|
162
|
+
document.getElementById('graph').appendChild(element);
|
|
163
|
+
})
|
|
164
|
+
.catch(err => {
|
|
165
|
+
console.error(err);
|
|
166
|
+
document.getElementById('graph').innerHTML = '<pre>' + err + '</pre>';
|
|
167
|
+
});
|
|
168
|
+
</script>
|
|
169
|
+
</body>
|
|
170
|
+
</html>`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Write visualization files to tmp/webpieces/
|
|
175
|
+
*/
|
|
176
|
+
export function writeVisualization(
|
|
177
|
+
graph: EnhancedGraph,
|
|
178
|
+
workspaceRoot: string,
|
|
179
|
+
title: string = 'WebPieces Architecture'
|
|
180
|
+
): { dotPath: string; htmlPath: string } {
|
|
181
|
+
const outputDir = path.join(workspaceRoot, 'tmp', 'webpieces');
|
|
182
|
+
|
|
183
|
+
// Ensure directory exists
|
|
184
|
+
if (!fs.existsSync(outputDir)) {
|
|
185
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Generate DOT
|
|
189
|
+
const dot = generateDot(graph, title);
|
|
190
|
+
const dotPath = path.join(outputDir, 'architecture.dot');
|
|
191
|
+
fs.writeFileSync(dotPath, dot, 'utf-8');
|
|
192
|
+
|
|
193
|
+
// Generate HTML
|
|
194
|
+
const html = generateHTML(dot, title);
|
|
195
|
+
const htmlPath = path.join(outputDir, 'architecture.html');
|
|
196
|
+
fs.writeFileSync(htmlPath, html, 'utf-8');
|
|
197
|
+
|
|
198
|
+
return { dotPath, htmlPath };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Open the HTML visualization in the default browser
|
|
203
|
+
*/
|
|
204
|
+
export function openVisualization(htmlPath: string): boolean {
|
|
205
|
+
try {
|
|
206
|
+
const platform = process.platform;
|
|
207
|
+
let openCommand: string;
|
|
208
|
+
|
|
209
|
+
if (platform === 'darwin') {
|
|
210
|
+
openCommand = `open "${htmlPath}"`;
|
|
211
|
+
} else if (platform === 'win32') {
|
|
212
|
+
openCommand = `start "" "${htmlPath}"`;
|
|
213
|
+
} else {
|
|
214
|
+
openCommand = `xdg-open "${htmlPath}"`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
execSync(openCommand, { stdio: 'ignore' });
|
|
218
|
+
return true;
|
|
219
|
+
} catch (err: unknown) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
* - Optionally warn about extra deps in package.json not in graph
|
|
30
|
+
*
|
|
31
|
+
* @param graph - Enhanced graph with project dependencies
|
|
32
|
+
* @param workspaceRoot - Absolute path to workspace root
|
|
33
|
+
* @returns Validation result with errors if any
|
|
34
|
+
*/
|
|
35
|
+
export declare function validatePackageJsonDependencies(graph: Record<string, {
|
|
36
|
+
level: number;
|
|
37
|
+
dependsOn: string[];
|
|
38
|
+
}>, workspaceRoot: string): Promise<ValidationResult>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Package Validator
|
|
4
|
+
*
|
|
5
|
+
* Validates that package.json dependencies match the project.json build.dependsOn
|
|
6
|
+
* This ensures the two sources of truth don't drift apart.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.validatePackageJsonDependencies = validatePackageJsonDependencies;
|
|
10
|
+
const tslib_1 = require("tslib");
|
|
11
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
12
|
+
const path = tslib_1.__importStar(require("path"));
|
|
13
|
+
const devkit_1 = require("@nx/devkit");
|
|
14
|
+
/**
|
|
15
|
+
* Read package.json dependencies for a project
|
|
16
|
+
* Returns null if package.json doesn't exist (apps often don't have one)
|
|
17
|
+
*/
|
|
18
|
+
function readPackageJsonDeps(workspaceRoot, projectRoot) {
|
|
19
|
+
const packageJsonPath = path.join(workspaceRoot, projectRoot, 'package.json');
|
|
20
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
21
|
+
return null; // No package.json - skip validation for this project
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
25
|
+
const deps = [];
|
|
26
|
+
// Collect all @webpieces/* dependencies
|
|
27
|
+
for (const depType of ['dependencies', 'peerDependencies']) {
|
|
28
|
+
const depObj = packageJson[depType] || {};
|
|
29
|
+
for (const depName of Object.keys(depObj)) {
|
|
30
|
+
if (depName.startsWith('@webpieces/') && !deps.includes(depName)) {
|
|
31
|
+
deps.push(depName);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return deps.sort();
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.warn(`Could not read package.json at ${packageJsonPath}`);
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Validate that package.json dependencies match the dependency graph
|
|
44
|
+
*
|
|
45
|
+
* For each project in the graph:
|
|
46
|
+
* - Check that all graph dependencies exist in package.json
|
|
47
|
+
* - Optionally warn about extra deps in package.json not in graph
|
|
48
|
+
*
|
|
49
|
+
* @param graph - Enhanced graph with project dependencies
|
|
50
|
+
* @param workspaceRoot - Absolute path to workspace root
|
|
51
|
+
* @returns Validation result with errors if any
|
|
52
|
+
*/
|
|
53
|
+
async function validatePackageJsonDependencies(graph, workspaceRoot) {
|
|
54
|
+
const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
|
|
55
|
+
const projectsConfig = (0, devkit_1.readProjectsConfigurationFromProjectGraph)(projectGraph);
|
|
56
|
+
const errors = [];
|
|
57
|
+
const projectResults = [];
|
|
58
|
+
for (const [projectName, entry] of Object.entries(graph)) {
|
|
59
|
+
// Extract base name (remove @webpieces/ prefix)
|
|
60
|
+
const baseName = projectName.replace('@webpieces/', '');
|
|
61
|
+
// Find the project config
|
|
62
|
+
const projectConfig = projectsConfig.projects[baseName];
|
|
63
|
+
if (!projectConfig) {
|
|
64
|
+
// Project not found in Nx config, skip
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const projectRoot = projectConfig.root;
|
|
68
|
+
const packageJsonDeps = readPackageJsonDeps(workspaceRoot, projectRoot);
|
|
69
|
+
// Skip projects without package.json (common for apps in monorepo)
|
|
70
|
+
if (packageJsonDeps === null) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Check for missing dependencies in package.json
|
|
74
|
+
const missingInPackageJson = [];
|
|
75
|
+
for (const dep of entry.dependsOn) {
|
|
76
|
+
if (!packageJsonDeps.includes(dep)) {
|
|
77
|
+
missingInPackageJson.push(dep);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Check for extra dependencies in package.json (not critical, just informational)
|
|
81
|
+
const extraInPackageJson = [];
|
|
82
|
+
for (const dep of packageJsonDeps) {
|
|
83
|
+
if (!entry.dependsOn.includes(dep)) {
|
|
84
|
+
extraInPackageJson.push(dep);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const valid = missingInPackageJson.length === 0;
|
|
88
|
+
if (!valid) {
|
|
89
|
+
errors.push(`Project ${projectName} (${projectRoot}/package.json) is missing dependencies: ${missingInPackageJson.join(', ')}\n` +
|
|
90
|
+
` Fix: Add these to package.json dependencies`);
|
|
91
|
+
}
|
|
92
|
+
projectResults.push({
|
|
93
|
+
project: projectName,
|
|
94
|
+
valid,
|
|
95
|
+
missingInPackageJson,
|
|
96
|
+
extraInPackageJson,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
valid: errors.length === 0,
|
|
101
|
+
errors,
|
|
102
|
+
projectResults,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=package-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-validator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/architecture/lib/package-validator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Package Validator\n *\n * Validates that package.json dependencies match the project.json build.dependsOn\n * This ensures the two sources of truth don't drift apart.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n createProjectGraphAsync,\n readProjectsConfigurationFromProjectGraph,\n} from '@nx/devkit';\n\n/**\n * Validation result for a single project\n */\nexport interface ProjectValidationResult {\n project: string;\n valid: boolean;\n missingInPackageJson: string[];\n extraInPackageJson: string[];\n}\n\n/**\n * Overall validation result\n */\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n projectResults: ProjectValidationResult[];\n}\n\n/**\n * Read package.json dependencies for a project\n * Returns null if package.json doesn't exist (apps often don't have one)\n */\nfunction readPackageJsonDeps(workspaceRoot: string, projectRoot: string): string[] | null {\n const packageJsonPath = path.join(workspaceRoot, projectRoot, 'package.json');\n\n if (!fs.existsSync(packageJsonPath)) {\n return null; // No package.json - skip validation for this project\n }\n\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n const deps: string[] = [];\n\n // Collect all @webpieces/* dependencies\n for (const depType of ['dependencies', 'peerDependencies']) {\n const depObj = packageJson[depType] || {};\n for (const depName of Object.keys(depObj)) {\n if (depName.startsWith('@webpieces/') && !deps.includes(depName)) {\n deps.push(depName);\n }\n }\n }\n\n return deps.sort();\n } catch (err: unknown) {\n console.warn(`Could not read package.json at ${packageJsonPath}`);\n return [];\n }\n}\n\n/**\n * Validate that package.json dependencies match the dependency graph\n *\n * For each project in the graph:\n * - Check that all graph dependencies exist in package.json\n * - Optionally warn about extra deps in package.json not in graph\n *\n * @param graph - Enhanced graph with project dependencies\n * @param workspaceRoot - Absolute path to workspace root\n * @returns Validation result with errors if any\n */\nexport async function validatePackageJsonDependencies(\n graph: Record<string, { level: number; dependsOn: string[] }>,\n workspaceRoot: string\n): Promise<ValidationResult> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const errors: string[] = [];\n const projectResults: ProjectValidationResult[] = [];\n\n for (const [projectName, entry] of Object.entries(graph)) {\n // Extract base name (remove @webpieces/ prefix)\n const baseName = projectName.replace('@webpieces/', '');\n\n // Find the project config\n const projectConfig = projectsConfig.projects[baseName];\n if (!projectConfig) {\n // Project not found in Nx config, skip\n continue;\n }\n\n const projectRoot = projectConfig.root;\n const packageJsonDeps = readPackageJsonDeps(workspaceRoot, projectRoot);\n\n // Skip projects without package.json (common for apps in monorepo)\n if (packageJsonDeps === null) {\n continue;\n }\n\n // Check for missing dependencies in package.json\n const missingInPackageJson: string[] = [];\n for (const dep of entry.dependsOn) {\n if (!packageJsonDeps.includes(dep)) {\n missingInPackageJson.push(dep);\n }\n }\n\n // Check for extra dependencies in package.json (not critical, just informational)\n const extraInPackageJson: string[] = [];\n for (const dep of packageJsonDeps) {\n if (!entry.dependsOn.includes(dep)) {\n extraInPackageJson.push(dep);\n }\n }\n\n const valid = missingInPackageJson.length === 0;\n\n if (!valid) {\n errors.push(\n `Project ${projectName} (${projectRoot}/package.json) is missing dependencies: ${missingInPackageJson.join(', ')}\\n` +\n ` Fix: Add these to package.json dependencies`\n );\n }\n\n projectResults.push({\n project: projectName,\n valid,\n missingInPackageJson,\n extraInPackageJson,\n });\n }\n\n return {\n valid: errors.length === 0,\n errors,\n projectResults,\n };\n}\n"]}
|