@girardelli/architect 1.1.0
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/CONTRIBUTING.md +140 -0
- package/LICENSE +21 -0
- package/PROJECT_STRUCTURE.txt +168 -0
- package/README.md +269 -0
- package/dist/analyzer.d.ts +17 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +254 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/anti-patterns.d.ts +17 -0
- package/dist/anti-patterns.d.ts.map +1 -0
- package/dist/anti-patterns.js +211 -0
- package/dist/anti-patterns.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +164 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/diagram.d.ts +9 -0
- package/dist/diagram.d.ts.map +1 -0
- package/dist/diagram.js +116 -0
- package/dist/diagram.js.map +1 -0
- package/dist/html-reporter.d.ts +23 -0
- package/dist/html-reporter.d.ts.map +1 -0
- package/dist/html-reporter.js +454 -0
- package/dist/html-reporter.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +151 -0
- package/dist/index.js.map +1 -0
- package/dist/reporter.d.ts +13 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +135 -0
- package/dist/reporter.js.map +1 -0
- package/dist/scanner.d.ts +25 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +288 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scorer.d.ts +15 -0
- package/dist/scorer.d.ts.map +1 -0
- package/dist/scorer.js +172 -0
- package/dist/scorer.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/examples/sample-report.md +207 -0
- package/jest.config.js +18 -0
- package/package.json +70 -0
- package/src/analyzer.ts +310 -0
- package/src/anti-patterns.ts +264 -0
- package/src/cli.ts +183 -0
- package/src/config.ts +82 -0
- package/src/diagram.ts +144 -0
- package/src/html-reporter.ts +485 -0
- package/src/index.ts +212 -0
- package/src/reporter.ts +166 -0
- package/src/scanner.ts +298 -0
- package/src/scorer.ts +193 -0
- package/src/types.ts +114 -0
- package/tests/anti-patterns.test.ts +94 -0
- package/tests/scanner.test.ts +55 -0
- package/tests/scorer.test.ts +80 -0
- package/tsconfig.json +24 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Architect CLI
|
|
5
|
+
* Executa análise arquitetural e gera relatórios em múltiplos formatos
|
|
6
|
+
*
|
|
7
|
+
* Uso:
|
|
8
|
+
* npx architect analyze ./src
|
|
9
|
+
* npx architect analyze ./src --format html --output report.html
|
|
10
|
+
* npx architect diagram ./src
|
|
11
|
+
* npx architect score ./src
|
|
12
|
+
* npx architect anti-patterns ./src
|
|
13
|
+
* npx architect layers ./src
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { architect } from './index.js';
|
|
17
|
+
import { ReportGenerator } from './reporter.js';
|
|
18
|
+
import { HtmlReportGenerator } from './html-reporter.js';
|
|
19
|
+
import { writeFileSync } from 'fs';
|
|
20
|
+
import { resolve, basename } from 'path';
|
|
21
|
+
|
|
22
|
+
type OutputFormat = 'json' | 'markdown' | 'html';
|
|
23
|
+
|
|
24
|
+
interface CliOptions {
|
|
25
|
+
command: string;
|
|
26
|
+
path: string;
|
|
27
|
+
format: OutputFormat;
|
|
28
|
+
output?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseArgs(args: string[]): CliOptions {
|
|
32
|
+
const command = args[0] || 'analyze';
|
|
33
|
+
const pathArg = args.find((a) => !a.startsWith('--') && a !== command) || '.';
|
|
34
|
+
const formatIdx = args.indexOf('--format');
|
|
35
|
+
const format = (formatIdx >= 0 ? args[formatIdx + 1] : 'html') as OutputFormat;
|
|
36
|
+
const outputIdx = args.indexOf('--output');
|
|
37
|
+
const output = outputIdx >= 0 ? args[outputIdx + 1] : undefined;
|
|
38
|
+
|
|
39
|
+
return { command, path: resolve(pathArg), format, output };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function printUsage(): void {
|
|
43
|
+
console.log(`
|
|
44
|
+
🏗️ Architect — AI-powered architecture analysis
|
|
45
|
+
|
|
46
|
+
Usage:
|
|
47
|
+
architect <command> [path] [options]
|
|
48
|
+
|
|
49
|
+
Commands:
|
|
50
|
+
analyze Full architecture analysis (default)
|
|
51
|
+
diagram Generate architecture diagram only
|
|
52
|
+
score Calculate quality score only
|
|
53
|
+
anti-patterns Detect anti-patterns only
|
|
54
|
+
layers Analyze layer structure only
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
--format <type> Output format: html, json, markdown (default: html)
|
|
58
|
+
--output <file> Output file path (default: architect-report.<ext>)
|
|
59
|
+
--help Show this help message
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
architect analyze ./src
|
|
63
|
+
architect analyze ./src --format html --output report.html
|
|
64
|
+
architect score ./src --format json
|
|
65
|
+
architect anti-patterns ./backend/src
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function main(): Promise<void> {
|
|
70
|
+
const args = process.argv.slice(2);
|
|
71
|
+
|
|
72
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
73
|
+
printUsage();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const options = parseArgs(args);
|
|
78
|
+
|
|
79
|
+
console.log('🏗️ Architect — Architecture Analysis');
|
|
80
|
+
console.log(`📂 Path: ${options.path}`);
|
|
81
|
+
console.log(`📋 Command: ${options.command}`);
|
|
82
|
+
console.log(`📄 Format: ${options.format}\n`);
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
switch (options.command) {
|
|
86
|
+
case 'analyze': {
|
|
87
|
+
const report = await architect.analyze(options.path);
|
|
88
|
+
const projectName = report.projectInfo.name || basename(options.path);
|
|
89
|
+
|
|
90
|
+
if (options.format === 'html') {
|
|
91
|
+
const htmlGenerator = new HtmlReportGenerator();
|
|
92
|
+
const html = htmlGenerator.generateHtml(report);
|
|
93
|
+
const outputPath = options.output || `architect-report-${projectName}.html`;
|
|
94
|
+
writeFileSync(outputPath, html);
|
|
95
|
+
console.log(`✅ HTML report saved to: ${outputPath}`);
|
|
96
|
+
console.log(`📊 Score: ${report.score.overall}/100`);
|
|
97
|
+
console.log(`⚠️ Anti-patterns: ${report.antiPatterns.length}`);
|
|
98
|
+
} else if (options.format === 'markdown') {
|
|
99
|
+
const mdGenerator = new ReportGenerator();
|
|
100
|
+
const markdown = mdGenerator.generateMarkdownReport(report);
|
|
101
|
+
const outputPath = options.output || `architect-report-${projectName}.md`;
|
|
102
|
+
writeFileSync(outputPath, markdown);
|
|
103
|
+
console.log(`✅ Markdown report saved to: ${outputPath}`);
|
|
104
|
+
} else {
|
|
105
|
+
const outputPath = options.output || `architect-report-${projectName}.json`;
|
|
106
|
+
writeFileSync(outputPath, JSON.stringify(report, null, 2));
|
|
107
|
+
console.log(`✅ JSON report saved to: ${outputPath}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Print summary to console
|
|
111
|
+
console.log(`\n═══════════════════════════════════════`);
|
|
112
|
+
console.log(` SCORE: ${report.score.overall}/100`);
|
|
113
|
+
console.log(`═══════════════════════════════════════`);
|
|
114
|
+
console.log(`├─ Modularity: ${report.score.breakdown.modularity}`);
|
|
115
|
+
console.log(`├─ Coupling: ${report.score.breakdown.coupling}`);
|
|
116
|
+
console.log(`├─ Cohesion: ${report.score.breakdown.cohesion}`);
|
|
117
|
+
console.log(`└─ Layering: ${report.score.breakdown.layering}`);
|
|
118
|
+
console.log(`\n📁 Files: ${report.projectInfo.totalFiles} | 📝 Lines: ${report.projectInfo.totalLines.toLocaleString()}`);
|
|
119
|
+
console.log(`⚠️ Anti-patterns: ${report.antiPatterns.length}`);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
case 'diagram': {
|
|
124
|
+
const diagram = await architect.diagram(options.path);
|
|
125
|
+
if (options.output) {
|
|
126
|
+
writeFileSync(options.output, diagram);
|
|
127
|
+
console.log(`✅ Diagram saved to: ${options.output}`);
|
|
128
|
+
} else {
|
|
129
|
+
console.log(diagram);
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
case 'score': {
|
|
135
|
+
const score = await architect.score(options.path);
|
|
136
|
+
if (options.format === 'json') {
|
|
137
|
+
console.log(JSON.stringify(score, null, 2));
|
|
138
|
+
} else {
|
|
139
|
+
console.log(`Score: ${score.overall}/100`);
|
|
140
|
+
for (const [name, value] of Object.entries(score.breakdown)) {
|
|
141
|
+
console.log(` ${name}: ${value}/100`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
case 'anti-patterns': {
|
|
148
|
+
const patterns = await architect.antiPatterns(options.path);
|
|
149
|
+
if (options.format === 'json') {
|
|
150
|
+
console.log(JSON.stringify(patterns, null, 2));
|
|
151
|
+
} else {
|
|
152
|
+
console.log(`Found ${patterns.length} anti-pattern(s):\n`);
|
|
153
|
+
for (const p of patterns) {
|
|
154
|
+
console.log(` [${p.severity}] ${p.name}: ${p.description}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
case 'layers': {
|
|
161
|
+
const layers = await architect.layers(options.path);
|
|
162
|
+
if (options.format === 'json') {
|
|
163
|
+
console.log(JSON.stringify(layers, null, 2));
|
|
164
|
+
} else {
|
|
165
|
+
for (const l of layers) {
|
|
166
|
+
console.log(`${l.name}: ${l.files.length} files`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
default:
|
|
173
|
+
console.error(`❌ Unknown command: ${options.command}`);
|
|
174
|
+
printUsage();
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error('❌ Error:', error instanceof Error ? error.message : error);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
main();
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { ArchitectConfig } from './types.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CONFIG: ArchitectConfig = {
|
|
6
|
+
ignore: [
|
|
7
|
+
'node_modules',
|
|
8
|
+
'dist',
|
|
9
|
+
'build',
|
|
10
|
+
'coverage',
|
|
11
|
+
'.git',
|
|
12
|
+
'.next',
|
|
13
|
+
'venv',
|
|
14
|
+
'__pycache__',
|
|
15
|
+
'target',
|
|
16
|
+
],
|
|
17
|
+
frameworks: {
|
|
18
|
+
detect: true,
|
|
19
|
+
},
|
|
20
|
+
antiPatterns: {
|
|
21
|
+
godClass: {
|
|
22
|
+
linesThreshold: 500,
|
|
23
|
+
methodsThreshold: 10,
|
|
24
|
+
},
|
|
25
|
+
shotgunSurgery: {
|
|
26
|
+
changePropagationThreshold: 5,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
score: {
|
|
30
|
+
modularity: 0.4,
|
|
31
|
+
coupling: 0.25,
|
|
32
|
+
cohesion: 0.2,
|
|
33
|
+
layering: 0.15,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export class ConfigLoader {
|
|
38
|
+
static loadConfig(projectPath: string): ArchitectConfig {
|
|
39
|
+
const configPath = join(projectPath, '.architect.json');
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
43
|
+
const userConfig = JSON.parse(content) as ArchitectConfig;
|
|
44
|
+
return this.mergeConfigs(DEFAULT_CONFIG, userConfig);
|
|
45
|
+
} catch {
|
|
46
|
+
return DEFAULT_CONFIG;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private static mergeConfigs(
|
|
51
|
+
defaults: ArchitectConfig,
|
|
52
|
+
user: ArchitectConfig
|
|
53
|
+
): ArchitectConfig {
|
|
54
|
+
return {
|
|
55
|
+
ignore: user.ignore ?? defaults.ignore,
|
|
56
|
+
frameworks: {
|
|
57
|
+
detect: user.frameworks?.detect ?? defaults.frameworks?.detect,
|
|
58
|
+
},
|
|
59
|
+
antiPatterns: {
|
|
60
|
+
godClass: {
|
|
61
|
+
linesThreshold:
|
|
62
|
+
user.antiPatterns?.godClass?.linesThreshold ??
|
|
63
|
+
defaults.antiPatterns?.godClass?.linesThreshold,
|
|
64
|
+
methodsThreshold:
|
|
65
|
+
user.antiPatterns?.godClass?.methodsThreshold ??
|
|
66
|
+
defaults.antiPatterns?.godClass?.methodsThreshold,
|
|
67
|
+
},
|
|
68
|
+
shotgunSurgery: {
|
|
69
|
+
changePropagationThreshold:
|
|
70
|
+
user.antiPatterns?.shotgunSurgery?.changePropagationThreshold ??
|
|
71
|
+
defaults.antiPatterns?.shotgunSurgery?.changePropagationThreshold,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
score: {
|
|
75
|
+
modularity: user.score?.modularity ?? defaults.score?.modularity,
|
|
76
|
+
coupling: user.score?.coupling ?? defaults.score?.coupling,
|
|
77
|
+
cohesion: user.score?.cohesion ?? defaults.score?.cohesion,
|
|
78
|
+
layering: user.score?.layering ?? defaults.score?.layering,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/diagram.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { DependencyEdge, Layer } from './types.js';
|
|
2
|
+
|
|
3
|
+
export class DiagramGenerator {
|
|
4
|
+
generateComponentDiagram(
|
|
5
|
+
edges: DependencyEdge[],
|
|
6
|
+
layers: Layer[]
|
|
7
|
+
): string {
|
|
8
|
+
const nodes = new Set<string>();
|
|
9
|
+
edges.forEach((e) => {
|
|
10
|
+
nodes.add(e.from);
|
|
11
|
+
nodes.add(e.to);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const nodeColors: Record<string, string> = {};
|
|
15
|
+
for (const layer of layers) {
|
|
16
|
+
for (const file of layer.files) {
|
|
17
|
+
const moduleName = file.split('/').pop() || file;
|
|
18
|
+
const colorMap: Record<string, string> = {
|
|
19
|
+
API: '#FFB6C1',
|
|
20
|
+
Service: '#87CEEB',
|
|
21
|
+
Data: '#90EE90',
|
|
22
|
+
UI: '#FFD700',
|
|
23
|
+
Infrastructure: '#D3D3D3',
|
|
24
|
+
};
|
|
25
|
+
nodeColors[moduleName] = colorMap[layer.name] || '#FFFFFF';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let mermaid = 'graph TB\n';
|
|
30
|
+
|
|
31
|
+
for (const node of Array.from(nodes).slice(0, 20)) {
|
|
32
|
+
const moduleName = node.split('/').pop() || node;
|
|
33
|
+
const color = nodeColors[moduleName] || '#FFFFFF';
|
|
34
|
+
mermaid += ` ${this.sanitizeNodeName(node)}["${moduleName}"]:::${this.getStyleClass(color)}\n`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
mermaid += '\n';
|
|
38
|
+
|
|
39
|
+
for (const edge of edges.slice(0, 30)) {
|
|
40
|
+
mermaid += ` ${this.sanitizeNodeName(edge.from)} --> ${this.sanitizeNodeName(edge.to)}\n`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
mermaid += '\n';
|
|
44
|
+
mermaid += ' classDef apiStyle fill:#FFB6C1,stroke:#333,color:#000\n';
|
|
45
|
+
mermaid += ' classDef serviceStyle fill:#87CEEB,stroke:#333,color:#000\n';
|
|
46
|
+
mermaid += ' classDef dataStyle fill:#90EE90,stroke:#333,color:#000\n';
|
|
47
|
+
mermaid += ' classDef uiStyle fill:#FFD700,stroke:#333,color:#000\n';
|
|
48
|
+
mermaid += ' classDef infraStyle fill:#D3D3D3,stroke:#333,color:#000\n';
|
|
49
|
+
|
|
50
|
+
return mermaid;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
generateLayerDiagram(layers: Layer[]): string {
|
|
54
|
+
let mermaid = 'graph LR\n';
|
|
55
|
+
|
|
56
|
+
const layerOrder = ['UI', 'API', 'Service', 'Data', 'Infrastructure'];
|
|
57
|
+
const colorMap: Record<string, string> = {
|
|
58
|
+
UI: 'uiStyle',
|
|
59
|
+
API: 'apiStyle',
|
|
60
|
+
Service: 'serviceStyle',
|
|
61
|
+
Data: 'dataStyle',
|
|
62
|
+
Infrastructure: 'infraStyle',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
for (const layerName of layerOrder) {
|
|
66
|
+
const layer = layers.find((l) => l.name === layerName);
|
|
67
|
+
if (layer && layer.files.length > 0) {
|
|
68
|
+
const nodeId = layerName.replace(/\s+/g, '_');
|
|
69
|
+
const fileCount = layer.files.length;
|
|
70
|
+
mermaid += ` ${nodeId}["${layerName}<br/>(${fileCount} files)"]:::${colorMap[layerName]}\n`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < layerOrder.length - 1; i++) {
|
|
75
|
+
const from = layerOrder[i].replace(/\s+/g, '_');
|
|
76
|
+
const to = layerOrder[i + 1].replace(/\s+/g, '_');
|
|
77
|
+
mermaid += ` ${from} --> ${to}\n`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
mermaid += '\n';
|
|
81
|
+
mermaid += ' classDef apiStyle fill:#FFB6C1,stroke:#333,color:#000\n';
|
|
82
|
+
mermaid += ' classDef serviceStyle fill:#87CEEB,stroke:#333,color:#000\n';
|
|
83
|
+
mermaid += ' classDef dataStyle fill:#90EE90,stroke:#333,color:#000\n';
|
|
84
|
+
mermaid += ' classDef uiStyle fill:#FFD700,stroke:#333,color:#000\n';
|
|
85
|
+
mermaid += ' classDef infraStyle fill:#D3D3D3,stroke:#333,color:#000\n';
|
|
86
|
+
|
|
87
|
+
return mermaid;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
generateDependencyFlowDiagram(edges: DependencyEdge[]): string {
|
|
91
|
+
const flowMap: Record<string, number> = {};
|
|
92
|
+
|
|
93
|
+
for (const edge of edges) {
|
|
94
|
+
const flowKey = `${edge.from} -> ${edge.to}`;
|
|
95
|
+
flowMap[flowKey] = (flowMap[flowKey] || 0) + edge.weight;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const topFlows = Object.entries(flowMap)
|
|
99
|
+
.sort((a, b) => b[1] - a[1])
|
|
100
|
+
.slice(0, 15);
|
|
101
|
+
|
|
102
|
+
let mermaid = 'graph LR\n';
|
|
103
|
+
|
|
104
|
+
const nodes = new Set<string>();
|
|
105
|
+
for (const [flow] of topFlows) {
|
|
106
|
+
const [from, _, to] = flow.split(' ');
|
|
107
|
+
nodes.add(from);
|
|
108
|
+
nodes.add(to);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const node of nodes) {
|
|
112
|
+
const moduleName = node.split('/').pop() || node;
|
|
113
|
+
mermaid += ` ${this.sanitizeNodeName(node)}["${moduleName}"]\n`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
mermaid += '\n';
|
|
117
|
+
|
|
118
|
+
for (const [flow, weight] of topFlows) {
|
|
119
|
+
const [from, _, to] = flow.split(' ');
|
|
120
|
+
const label = weight > 1 ? ` | ${weight}` : '';
|
|
121
|
+
mermaid += ` ${this.sanitizeNodeName(from)} -->|${label}| ${this.sanitizeNodeName(to)}\n`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return mermaid;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private sanitizeNodeName(path: string): string {
|
|
128
|
+
return path
|
|
129
|
+
.replace(/[^a-zA-Z0-9]/g, '_')
|
|
130
|
+
.replace(/_+/g, '_')
|
|
131
|
+
.toLowerCase();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private getStyleClass(color: string): string {
|
|
135
|
+
const classMap: Record<string, string> = {
|
|
136
|
+
'#FFB6C1': 'apiStyle',
|
|
137
|
+
'#87CEEB': 'serviceStyle',
|
|
138
|
+
'#90EE90': 'dataStyle',
|
|
139
|
+
'#FFD700': 'uiStyle',
|
|
140
|
+
'#D3D3D3': 'infraStyle',
|
|
141
|
+
};
|
|
142
|
+
return classMap[color] || 'defaultStyle';
|
|
143
|
+
}
|
|
144
|
+
}
|