tlc-claude-code 1.2.29 → 1.4.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/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/UsagePane.d.ts +13 -0
- package/dashboard/dist/components/UsagePane.js +51 -0
- package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
- package/dashboard/dist/components/UsagePane.test.js +142 -0
- package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
- package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
- package/dashboard/dist/components/WorkspacePane.js +17 -0
- package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspacePane.test.js +84 -0
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/architecture-command.js +450 -0
- package/server/lib/architecture-command.test.js +754 -0
- package/server/lib/ast-analyzer.js +324 -0
- package/server/lib/ast-analyzer.test.js +437 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/auth-system.test.js +4 -1
- package/server/lib/boundary-detector.js +427 -0
- package/server/lib/boundary-detector.test.js +320 -0
- package/server/lib/budget-alerts.js +138 -0
- package/server/lib/budget-alerts.test.js +235 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/candidates-tracker.js +210 -0
- package/server/lib/candidates-tracker.test.js +300 -0
- package/server/lib/checkpoint-manager.js +251 -0
- package/server/lib/checkpoint-manager.test.js +474 -0
- package/server/lib/circular-detector.js +337 -0
- package/server/lib/circular-detector.test.js +353 -0
- package/server/lib/cohesion-analyzer.js +310 -0
- package/server/lib/cohesion-analyzer.test.js +447 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/contract-testing.js +625 -0
- package/server/lib/contract-testing.test.js +342 -0
- package/server/lib/conversion-planner.js +469 -0
- package/server/lib/conversion-planner.test.js +361 -0
- package/server/lib/convert-command.js +351 -0
- package/server/lib/convert-command.test.js +608 -0
- package/server/lib/coupling-calculator.js +189 -0
- package/server/lib/coupling-calculator.test.js +509 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/dependency-graph.js +367 -0
- package/server/lib/dependency-graph.test.js +516 -0
- package/server/lib/duplication-detector.js +349 -0
- package/server/lib/duplication-detector.test.js +401 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mermaid-generator.js +358 -0
- package/server/lib/mermaid-generator.test.js +301 -0
- package/server/lib/messaging-patterns.js +750 -0
- package/server/lib/messaging-patterns.test.js +213 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/microservice-template.js +386 -0
- package/server/lib/microservice-template.test.js +325 -0
- package/server/lib/new-project-microservice.js +450 -0
- package/server/lib/new-project-microservice.test.js +600 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/refactor-command.js +326 -0
- package/server/lib/refactor-command.test.js +528 -0
- package/server/lib/refactor-executor.js +254 -0
- package/server/lib/refactor-executor.test.js +305 -0
- package/server/lib/refactor-observer.js +292 -0
- package/server/lib/refactor-observer.test.js +422 -0
- package/server/lib/refactor-progress.js +193 -0
- package/server/lib/refactor-progress.test.js +251 -0
- package/server/lib/refactor-reporter.js +237 -0
- package/server/lib/refactor-reporter.test.js +247 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/traefik-config.js +282 -0
- package/server/lib/traefik-config.test.js +312 -0
- package/server/lib/usage-command.js +218 -0
- package/server/lib/usage-command.test.js +391 -0
- package/server/lib/usage-formatter.js +192 -0
- package/server/lib/usage-formatter.test.js +267 -0
- package/server/lib/usage-history.js +122 -0
- package/server/lib/usage-history.test.js +206 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Interaction Diagram Generator
|
|
3
|
+
* Generate detailed service interaction diagrams (sequence, component, deployment, ER)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Simple YAML parser for docker-compose files
|
|
8
|
+
* Handles the subset of YAML needed for docker-compose parsing
|
|
9
|
+
* @param {string} yamlContent - YAML content to parse
|
|
10
|
+
* @returns {Object} Parsed object
|
|
11
|
+
*/
|
|
12
|
+
function parseSimpleYaml(yamlContent) {
|
|
13
|
+
if (!yamlContent || yamlContent.trim() === '') {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = {};
|
|
18
|
+
const lines = yamlContent.split('\n');
|
|
19
|
+
|
|
20
|
+
// Stack stores the context at each indentation level
|
|
21
|
+
// { container, key, indent } where container[key] is the current object/array
|
|
22
|
+
const stack = [{ container: { root: result }, key: 'root', indent: -2 }];
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < lines.length; i++) {
|
|
25
|
+
const line = lines[i];
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
|
|
28
|
+
// Skip empty lines and comments
|
|
29
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Calculate indentation
|
|
34
|
+
const indent = line.search(/\S/);
|
|
35
|
+
if (indent === -1) continue;
|
|
36
|
+
|
|
37
|
+
// Pop stack until we find the parent at the right level
|
|
38
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
39
|
+
stack.pop();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const current = stack[stack.length - 1];
|
|
43
|
+
const parent = current.container[current.key];
|
|
44
|
+
|
|
45
|
+
// Handle array items (- value)
|
|
46
|
+
if (trimmed.startsWith('- ')) {
|
|
47
|
+
const value = trimmed.slice(2).trim();
|
|
48
|
+
|
|
49
|
+
// Convert parent to array if it's currently an empty object
|
|
50
|
+
if (typeof parent === 'object' && !Array.isArray(parent) && Object.keys(parent).length === 0) {
|
|
51
|
+
current.container[current.key] = [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const arr = current.container[current.key];
|
|
55
|
+
if (Array.isArray(arr)) {
|
|
56
|
+
// Handle "key: value" inside array item (but not port mappings like "80:80")
|
|
57
|
+
if (value.includes(':') && !value.match(/^["']?[\d.]+:[\d.]+["']?$/)) {
|
|
58
|
+
const colonIdx = value.indexOf(':');
|
|
59
|
+
const objKey = value.slice(0, colonIdx).trim();
|
|
60
|
+
const objVal = value.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
61
|
+
const obj = {};
|
|
62
|
+
obj[objKey] = objVal;
|
|
63
|
+
arr.push(obj);
|
|
64
|
+
} else {
|
|
65
|
+
arr.push(value.replace(/^["']|["']$/g, ''));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle key-value pairs
|
|
72
|
+
const colonIndex = trimmed.indexOf(':');
|
|
73
|
+
if (colonIndex === -1) continue;
|
|
74
|
+
|
|
75
|
+
const key = trimmed.slice(0, colonIndex).trim();
|
|
76
|
+
let value = trimmed.slice(colonIndex + 1).trim();
|
|
77
|
+
|
|
78
|
+
if (value === '' || value === '|' || value === '>') {
|
|
79
|
+
// Nested object (or array, we'll convert if we see '-')
|
|
80
|
+
parent[key] = {};
|
|
81
|
+
stack.push({ container: parent, key: key, indent: indent });
|
|
82
|
+
} else if (value.startsWith('[') && value.endsWith(']')) {
|
|
83
|
+
// Inline array
|
|
84
|
+
const arrayContent = value.slice(1, -1);
|
|
85
|
+
if (arrayContent.trim() === '') {
|
|
86
|
+
parent[key] = [];
|
|
87
|
+
} else {
|
|
88
|
+
parent[key] = arrayContent.split(',').map(s => s.trim().replace(/^["']|["']$/g, ''));
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// Simple value
|
|
92
|
+
value = value.replace(/^["']|["']$/g, '');
|
|
93
|
+
if (value === 'true') value = true;
|
|
94
|
+
else if (value === 'false') value = false;
|
|
95
|
+
else if (/^\d+$/.test(value)) value = parseInt(value, 10);
|
|
96
|
+
parent[key] = value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* ServiceInteractionDiagram class for generating various Mermaid diagrams
|
|
105
|
+
*/
|
|
106
|
+
class ServiceInteractionDiagram {
|
|
107
|
+
/**
|
|
108
|
+
* Create a ServiceInteractionDiagram instance
|
|
109
|
+
* @param {Object} options - Configuration options
|
|
110
|
+
*/
|
|
111
|
+
constructor(options = {}) {
|
|
112
|
+
this.options = {
|
|
113
|
+
direction: options.direction || 'TB',
|
|
114
|
+
groupByLayer: options.groupByLayer || false,
|
|
115
|
+
showPorts: options.showPorts !== false,
|
|
116
|
+
...options,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Generate a sequence diagram from API flow
|
|
122
|
+
* @param {Object} apiFlow - API flow definition
|
|
123
|
+
* @returns {string} Mermaid sequence diagram
|
|
124
|
+
*/
|
|
125
|
+
generateSequenceDiagram(apiFlow) {
|
|
126
|
+
if (!apiFlow.steps || apiFlow.steps.length === 0) {
|
|
127
|
+
return 'sequenceDiagram\n Note over Client: No interactions defined';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let mermaid = 'sequenceDiagram\n';
|
|
131
|
+
|
|
132
|
+
// Add title if provided
|
|
133
|
+
if (apiFlow.name) {
|
|
134
|
+
mermaid += ` title ${this.escapeLabel(apiFlow.name)}\n`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Collect all participants
|
|
138
|
+
const participants = new Set();
|
|
139
|
+
for (const step of apiFlow.steps) {
|
|
140
|
+
participants.add(step.from);
|
|
141
|
+
participants.add(step.to);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Declare participants
|
|
145
|
+
for (const participant of participants) {
|
|
146
|
+
const sanitized = this.escapeLabel(participant);
|
|
147
|
+
mermaid += ` participant ${this.sanitizeId(participant)} as ${sanitized}\n`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
mermaid += '\n';
|
|
151
|
+
|
|
152
|
+
// Generate steps
|
|
153
|
+
for (const step of apiFlow.steps) {
|
|
154
|
+
const fromId = this.sanitizeId(step.from);
|
|
155
|
+
const toId = this.sanitizeId(step.to);
|
|
156
|
+
const action = this.escapeLabel(step.action);
|
|
157
|
+
|
|
158
|
+
let arrow;
|
|
159
|
+
switch (step.type) {
|
|
160
|
+
case 'response':
|
|
161
|
+
arrow = '-->>';
|
|
162
|
+
break;
|
|
163
|
+
case 'async':
|
|
164
|
+
case 'event':
|
|
165
|
+
arrow = '-)'
|
|
166
|
+
break;
|
|
167
|
+
case 'query':
|
|
168
|
+
case 'call':
|
|
169
|
+
case 'request':
|
|
170
|
+
default:
|
|
171
|
+
arrow = '->>';
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
mermaid += ` ${fromId}${arrow}${toId}: ${action}\n`;
|
|
176
|
+
|
|
177
|
+
// Add note if provided
|
|
178
|
+
if (step.note) {
|
|
179
|
+
mermaid += ` Note right of ${toId}: ${this.escapeLabel(step.note)}\n`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return mermaid;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generate a component diagram showing service boundaries
|
|
188
|
+
* @param {Object} services - Services configuration
|
|
189
|
+
* @returns {string} Mermaid flowchart diagram
|
|
190
|
+
*/
|
|
191
|
+
generateComponentDiagram(services) {
|
|
192
|
+
let mermaid = `flowchart ${this.options.direction}\n`;
|
|
193
|
+
|
|
194
|
+
if (!services.components || services.components.length === 0) {
|
|
195
|
+
mermaid += ' empty[No components defined]\n';
|
|
196
|
+
return mermaid;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Add styles
|
|
200
|
+
mermaid += this.generateComponentStyles();
|
|
201
|
+
|
|
202
|
+
// Group by layer if enabled
|
|
203
|
+
if (this.options.groupByLayer) {
|
|
204
|
+
const layers = this.groupByLayer(services.components);
|
|
205
|
+
for (const [layerName, components] of Object.entries(layers)) {
|
|
206
|
+
mermaid += `\n subgraph ${layerName}["${this.capitalizeFirst(layerName)} Layer"]\n`;
|
|
207
|
+
for (const component of components) {
|
|
208
|
+
mermaid += this.generateComponentNode(component, ' ');
|
|
209
|
+
}
|
|
210
|
+
mermaid += ' end\n';
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
// Flat list of components
|
|
214
|
+
mermaid += '\n %% Components\n';
|
|
215
|
+
for (const component of services.components) {
|
|
216
|
+
mermaid += this.generateComponentNode(component, ' ');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Generate connections
|
|
221
|
+
if (services.connections && services.connections.length > 0) {
|
|
222
|
+
mermaid += '\n %% Connections\n';
|
|
223
|
+
for (const conn of services.connections) {
|
|
224
|
+
const fromId = this.sanitizeId(conn.from);
|
|
225
|
+
const toId = this.sanitizeId(conn.to);
|
|
226
|
+
const label = conn.protocol || conn.type || '';
|
|
227
|
+
mermaid += ` ${fromId} -->|${label}| ${toId}\n`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return mermaid;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Generate a component node with appropriate shape
|
|
236
|
+
* @param {Object} component - Component definition
|
|
237
|
+
* @param {string} indent - Indentation
|
|
238
|
+
* @returns {string} Mermaid node definition
|
|
239
|
+
*/
|
|
240
|
+
generateComponentNode(component, indent = ' ') {
|
|
241
|
+
const id = this.sanitizeId(component.name);
|
|
242
|
+
const label = this.sanitizeLabel(component.name);
|
|
243
|
+
let shape;
|
|
244
|
+
|
|
245
|
+
switch (component.type) {
|
|
246
|
+
case 'gateway':
|
|
247
|
+
shape = `{{${label}}}`;
|
|
248
|
+
break;
|
|
249
|
+
case 'database':
|
|
250
|
+
shape = `[(${label})]`;
|
|
251
|
+
break;
|
|
252
|
+
case 'cache':
|
|
253
|
+
shape = `([${label}])`;
|
|
254
|
+
break;
|
|
255
|
+
case 'queue':
|
|
256
|
+
shape = `>/${label}/]`;
|
|
257
|
+
break;
|
|
258
|
+
case 'service':
|
|
259
|
+
default:
|
|
260
|
+
shape = `[${label}]`;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let node = `${indent}${id}${shape}`;
|
|
265
|
+
|
|
266
|
+
// Add class for styling
|
|
267
|
+
if (component.type) {
|
|
268
|
+
node += `:::${component.type}`;
|
|
269
|
+
}
|
|
270
|
+
node += '\n';
|
|
271
|
+
|
|
272
|
+
return node;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Group components by layer
|
|
277
|
+
* @param {Array} components - Components array
|
|
278
|
+
* @returns {Object} Grouped components
|
|
279
|
+
*/
|
|
280
|
+
groupByLayer(components) {
|
|
281
|
+
const layers = {};
|
|
282
|
+
for (const component of components) {
|
|
283
|
+
const layer = component.layer || 'default';
|
|
284
|
+
if (!layers[layer]) {
|
|
285
|
+
layers[layer] = [];
|
|
286
|
+
}
|
|
287
|
+
layers[layer].push(component);
|
|
288
|
+
}
|
|
289
|
+
return layers;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Generate deployment diagram from docker-compose YAML
|
|
294
|
+
* @param {string} dockerCompose - Docker-compose YAML content
|
|
295
|
+
* @returns {string} Mermaid flowchart diagram
|
|
296
|
+
*/
|
|
297
|
+
generateDeploymentDiagram(dockerCompose) {
|
|
298
|
+
let mermaid = `flowchart ${this.options.direction}\n`;
|
|
299
|
+
|
|
300
|
+
if (!dockerCompose || dockerCompose.trim() === '') {
|
|
301
|
+
mermaid += ' empty[No infrastructure configuration found]\n';
|
|
302
|
+
return mermaid;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let config;
|
|
306
|
+
try {
|
|
307
|
+
config = parseSimpleYaml(dockerCompose);
|
|
308
|
+
} catch (e) {
|
|
309
|
+
// Invalid YAML, return minimal diagram
|
|
310
|
+
mermaid += ' error[Invalid infrastructure configuration]\n';
|
|
311
|
+
return mermaid;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!config || !config.services) {
|
|
315
|
+
mermaid += ' empty[No infrastructure configuration found]\n';
|
|
316
|
+
return mermaid;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Add styles
|
|
320
|
+
mermaid += this.generateDeploymentStyles();
|
|
321
|
+
|
|
322
|
+
// Parse services
|
|
323
|
+
const services = config.services || {};
|
|
324
|
+
const volumes = config.volumes || {};
|
|
325
|
+
const networks = config.networks || {};
|
|
326
|
+
|
|
327
|
+
// Generate network subgraphs
|
|
328
|
+
if (Object.keys(networks).length > 0) {
|
|
329
|
+
for (const [networkName, _networkConfig] of Object.entries(networks)) {
|
|
330
|
+
const servicesInNetwork = [];
|
|
331
|
+
for (const [serviceName, serviceConfig] of Object.entries(services)) {
|
|
332
|
+
if (serviceConfig.networks && serviceConfig.networks.includes(networkName)) {
|
|
333
|
+
servicesInNetwork.push(serviceName);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (servicesInNetwork.length > 0) {
|
|
337
|
+
mermaid += `\n subgraph ${this.sanitizeId(networkName)}["Network: ${networkName}"]\n`;
|
|
338
|
+
for (const svc of servicesInNetwork) {
|
|
339
|
+
mermaid += ` ${this.sanitizeId(svc)}_net[${svc}]\n`;
|
|
340
|
+
}
|
|
341
|
+
mermaid += ' end\n';
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Generate service nodes
|
|
347
|
+
mermaid += '\n %% Services\n';
|
|
348
|
+
for (const [name, serviceConfig] of Object.entries(services)) {
|
|
349
|
+
const id = this.sanitizeId(name);
|
|
350
|
+
const image = serviceConfig.image || 'custom';
|
|
351
|
+
let label = `${name}`;
|
|
352
|
+
|
|
353
|
+
// Add port info
|
|
354
|
+
if (serviceConfig.ports && serviceConfig.ports.length > 0) {
|
|
355
|
+
const ports = serviceConfig.ports.map(p => {
|
|
356
|
+
if (typeof p === 'string') {
|
|
357
|
+
return p.split(':')[0];
|
|
358
|
+
}
|
|
359
|
+
return p.published || p;
|
|
360
|
+
});
|
|
361
|
+
label += `<br/>Ports: ${ports.join(', ')}`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
mermaid += ` ${id}["${label}"]:::service\n`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Generate volume nodes
|
|
368
|
+
if (Object.keys(volumes).length > 0) {
|
|
369
|
+
mermaid += '\n %% Volumes\n';
|
|
370
|
+
for (const volumeName of Object.keys(volumes)) {
|
|
371
|
+
const id = this.sanitizeId(`vol_${volumeName}`);
|
|
372
|
+
mermaid += ` ${id}[("${volumeName}")]:::volume\n`;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Generate dependencies (depends_on)
|
|
377
|
+
mermaid += '\n %% Dependencies\n';
|
|
378
|
+
for (const [name, serviceConfig] of Object.entries(services)) {
|
|
379
|
+
const id = this.sanitizeId(name);
|
|
380
|
+
if (serviceConfig.depends_on) {
|
|
381
|
+
const deps = Array.isArray(serviceConfig.depends_on)
|
|
382
|
+
? serviceConfig.depends_on
|
|
383
|
+
: Object.keys(serviceConfig.depends_on);
|
|
384
|
+
for (const dep of deps) {
|
|
385
|
+
const depId = this.sanitizeId(dep);
|
|
386
|
+
mermaid += ` ${id} -->|depends_on| ${depId}\n`;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Generate volume mounts
|
|
392
|
+
for (const [name, serviceConfig] of Object.entries(services)) {
|
|
393
|
+
const id = this.sanitizeId(name);
|
|
394
|
+
if (serviceConfig.volumes) {
|
|
395
|
+
for (const vol of serviceConfig.volumes) {
|
|
396
|
+
const volName = typeof vol === 'string' ? vol.split(':')[0] : vol.source;
|
|
397
|
+
if (volumes[volName]) {
|
|
398
|
+
const volId = this.sanitizeId(`vol_${volName}`);
|
|
399
|
+
mermaid += ` ${id} -.->|mounts| ${volId}\n`;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return mermaid;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Generate ER diagram from schema definitions
|
|
410
|
+
* @param {Array} schemas - Array of parsed schema models
|
|
411
|
+
* @returns {string} Mermaid ER diagram
|
|
412
|
+
*/
|
|
413
|
+
generateERDiagram(schemas) {
|
|
414
|
+
if (!schemas || schemas.length === 0) {
|
|
415
|
+
return 'erDiagram\n EMPTY["No entities defined"]';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
let mermaid = 'erDiagram\n';
|
|
419
|
+
|
|
420
|
+
// Build table name lookup for relationship inference
|
|
421
|
+
const tableNames = new Set(schemas.map(s => s.tableName));
|
|
422
|
+
|
|
423
|
+
// Track relationships to avoid duplicates
|
|
424
|
+
const relationships = new Set();
|
|
425
|
+
|
|
426
|
+
// Generate entities
|
|
427
|
+
for (const schema of schemas) {
|
|
428
|
+
const tableName = this.sanitizeId(schema.tableName);
|
|
429
|
+
mermaid += ` ${tableName} {\n`;
|
|
430
|
+
|
|
431
|
+
for (const column of schema.columns) {
|
|
432
|
+
const type = column.type || 'unknown';
|
|
433
|
+
let markers = '';
|
|
434
|
+
|
|
435
|
+
if (column.primary) {
|
|
436
|
+
markers = 'PK';
|
|
437
|
+
} else if (column.foreignKey) {
|
|
438
|
+
markers = 'FK';
|
|
439
|
+
} else if (column.unique) {
|
|
440
|
+
markers = 'UK';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const markerStr = markers ? ` "${markers}"` : '';
|
|
444
|
+
mermaid += ` ${type} ${column.name}${markerStr}\n`;
|
|
445
|
+
|
|
446
|
+
// Track explicit foreign keys
|
|
447
|
+
if (column.foreignKey) {
|
|
448
|
+
const refTable = this.sanitizeId(column.foreignKey.table);
|
|
449
|
+
const rel = `${refTable} ||--o{ ${tableName}`;
|
|
450
|
+
if (!relationships.has(rel)) {
|
|
451
|
+
relationships.add(rel);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Infer relationships from naming conventions (e.g., user_id -> users)
|
|
456
|
+
if (!column.foreignKey && column.name.endsWith('_id')) {
|
|
457
|
+
const inferredTable = column.name.slice(0, -3) + 's'; // user_id -> users
|
|
458
|
+
if (tableNames.has(inferredTable)) {
|
|
459
|
+
const refTable = this.sanitizeId(inferredTable);
|
|
460
|
+
const rel = `${refTable} ||--o{ ${tableName}`;
|
|
461
|
+
if (!relationships.has(rel)) {
|
|
462
|
+
relationships.add(rel);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
mermaid += ' }\n';
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Add relationships
|
|
472
|
+
if (relationships.size > 0) {
|
|
473
|
+
mermaid += '\n';
|
|
474
|
+
for (const rel of relationships) {
|
|
475
|
+
mermaid += ` ${rel} : has\n`;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return mermaid;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Extract API flow from route handler code
|
|
484
|
+
* @param {string} handlerName - Name of the handler
|
|
485
|
+
* @param {string} code - Handler code
|
|
486
|
+
* @returns {Object} Extracted API flow
|
|
487
|
+
*/
|
|
488
|
+
extractAPIFlow(handlerName, code) {
|
|
489
|
+
const flow = {
|
|
490
|
+
name: handlerName,
|
|
491
|
+
steps: [],
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// Find await calls to extract service interactions
|
|
495
|
+
const awaitPattern = /await\s+(\w+)\.(\w+)\s*\([^)]*\)/g;
|
|
496
|
+
let match;
|
|
497
|
+
let stepOrder = 0;
|
|
498
|
+
|
|
499
|
+
while ((match = awaitPattern.exec(code)) !== null) {
|
|
500
|
+
const [fullMatch, service, method] = match;
|
|
501
|
+
stepOrder++;
|
|
502
|
+
|
|
503
|
+
flow.steps.push({
|
|
504
|
+
from: handlerName,
|
|
505
|
+
to: service,
|
|
506
|
+
action: `${service}.${method}()`,
|
|
507
|
+
type: this.inferStepType(service, method),
|
|
508
|
+
order: stepOrder,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Detect response patterns
|
|
513
|
+
const responsePattern = /return\s+res\.status\((\d+)\)\.json/;
|
|
514
|
+
const responseMatch = code.match(responsePattern);
|
|
515
|
+
if (responseMatch) {
|
|
516
|
+
flow.steps.push({
|
|
517
|
+
from: handlerName,
|
|
518
|
+
to: 'Client',
|
|
519
|
+
action: `${responseMatch[1]} Response`,
|
|
520
|
+
type: 'response',
|
|
521
|
+
order: stepOrder + 1,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return flow;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Infer step type from service and method names
|
|
530
|
+
* @param {string} service - Service name
|
|
531
|
+
* @param {string} method - Method name
|
|
532
|
+
* @returns {string} Step type
|
|
533
|
+
*/
|
|
534
|
+
inferStepType(service, method) {
|
|
535
|
+
const serviceLower = service.toLowerCase();
|
|
536
|
+
const methodLower = method.toLowerCase();
|
|
537
|
+
|
|
538
|
+
if (serviceLower.includes('repository') || serviceLower.includes('db') ||
|
|
539
|
+
methodLower.includes('find') || methodLower.includes('create') ||
|
|
540
|
+
methodLower.includes('update') || methodLower.includes('delete')) {
|
|
541
|
+
return 'query';
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (serviceLower.includes('event') || serviceLower.includes('bus') ||
|
|
545
|
+
methodLower.includes('publish') || methodLower.includes('emit')) {
|
|
546
|
+
return 'event';
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (serviceLower.includes('cache') || serviceLower.includes('redis')) {
|
|
550
|
+
return 'cache';
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return 'call';
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Generate a combined workspace diagram
|
|
558
|
+
* @param {Object} workspace - Workspace configuration
|
|
559
|
+
* @returns {string} Mermaid flowchart diagram
|
|
560
|
+
*/
|
|
561
|
+
generateWorkspaceDiagram(workspace) {
|
|
562
|
+
let mermaid = `flowchart ${this.options.direction}\n`;
|
|
563
|
+
|
|
564
|
+
// Add styles
|
|
565
|
+
mermaid += this.generateWorkspaceStyles();
|
|
566
|
+
|
|
567
|
+
// Generate service nodes
|
|
568
|
+
if (workspace.services && workspace.services.length > 0) {
|
|
569
|
+
mermaid += '\n %% Services\n';
|
|
570
|
+
for (const service of workspace.services) {
|
|
571
|
+
const id = this.sanitizeId(service.name);
|
|
572
|
+
const shape = service.type === 'gateway' ? `{{${service.name}}}` : `[${service.name}]`;
|
|
573
|
+
mermaid += ` ${id}${shape}:::service\n`;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Generate database nodes
|
|
578
|
+
if (workspace.databases && workspace.databases.length > 0) {
|
|
579
|
+
mermaid += '\n %% Databases\n';
|
|
580
|
+
for (const db of workspace.databases) {
|
|
581
|
+
const id = this.sanitizeId(db.name);
|
|
582
|
+
mermaid += ` ${id}[(${db.name})]:::database\n`;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Generate queue nodes
|
|
587
|
+
if (workspace.queues && workspace.queues.length > 0) {
|
|
588
|
+
mermaid += '\n %% Message Queues\n';
|
|
589
|
+
for (const queue of workspace.queues) {
|
|
590
|
+
const id = this.sanitizeId(queue.name);
|
|
591
|
+
mermaid += ` ${id}([${queue.name}]):::queue\n`;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Generate connections
|
|
596
|
+
if (workspace.connections && workspace.connections.length > 0) {
|
|
597
|
+
mermaid += '\n %% Connections\n';
|
|
598
|
+
for (const conn of workspace.connections) {
|
|
599
|
+
const fromId = this.sanitizeId(conn.from);
|
|
600
|
+
const toId = this.sanitizeId(conn.to);
|
|
601
|
+
const label = conn.type || '';
|
|
602
|
+
mermaid += ` ${fromId} -->|${label}| ${toId}\n`;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return mermaid;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Generate component diagram styles
|
|
611
|
+
* @returns {string} Style definitions
|
|
612
|
+
*/
|
|
613
|
+
generateComponentStyles() {
|
|
614
|
+
return ` %% Styles
|
|
615
|
+
classDef gateway fill:#fff3e0,stroke:#e65100,stroke-width:2px
|
|
616
|
+
classDef service fill:#e1f5fe,stroke:#01579b,stroke-width:2px
|
|
617
|
+
classDef database fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px
|
|
618
|
+
classDef cache fill:#fce4ec,stroke:#880e4f,stroke-width:2px
|
|
619
|
+
classDef queue fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
|
|
620
|
+
\n`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Generate deployment diagram styles
|
|
625
|
+
* @returns {string} Style definitions
|
|
626
|
+
*/
|
|
627
|
+
generateDeploymentStyles() {
|
|
628
|
+
return ` %% Styles
|
|
629
|
+
classDef service fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
|
|
630
|
+
classDef volume fill:#f5f5f5,stroke:#616161,stroke-width:2px,stroke-dasharray: 5 5
|
|
631
|
+
\n`;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Generate workspace diagram styles
|
|
636
|
+
* @returns {string} Style definitions
|
|
637
|
+
*/
|
|
638
|
+
generateWorkspaceStyles() {
|
|
639
|
+
return ` %% Styles
|
|
640
|
+
classDef service fill:#e1f5fe,stroke:#01579b,stroke-width:2px
|
|
641
|
+
classDef database fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px
|
|
642
|
+
classDef queue fill:#fff3e0,stroke:#e65100,stroke-width:2px
|
|
643
|
+
\n`;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Sanitize string for use as Mermaid ID
|
|
648
|
+
* @param {string} str - String to sanitize
|
|
649
|
+
* @returns {string} Sanitized ID
|
|
650
|
+
*/
|
|
651
|
+
sanitizeId(str) {
|
|
652
|
+
return str
|
|
653
|
+
.replace(/@/g, '')
|
|
654
|
+
.replace(/\//g, '_')
|
|
655
|
+
.replace(/[^a-zA-Z0-9]/g, '_')
|
|
656
|
+
.replace(/^_+|_+$/g, '')
|
|
657
|
+
.replace(/_+/g, '_')
|
|
658
|
+
|| 'node';
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Escape label for Mermaid
|
|
663
|
+
* @param {string} str - String to escape
|
|
664
|
+
* @returns {string} Escaped label
|
|
665
|
+
*/
|
|
666
|
+
escapeLabel(str) {
|
|
667
|
+
return str
|
|
668
|
+
.replace(/"/g, "'")
|
|
669
|
+
.replace(/\[/g, '(')
|
|
670
|
+
.replace(/\]/g, ')')
|
|
671
|
+
.replace(/>/g, ')')
|
|
672
|
+
.replace(/</g, '(')
|
|
673
|
+
.replace(/\{/g, '(')
|
|
674
|
+
.replace(/\}/g, ')');
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Escape and sanitize label for component names (more aggressive than escapeLabel)
|
|
679
|
+
* @param {string} str - String to escape
|
|
680
|
+
* @returns {string} Escaped label
|
|
681
|
+
*/
|
|
682
|
+
sanitizeLabel(str) {
|
|
683
|
+
return this.escapeLabel(str)
|
|
684
|
+
.replace(/@/g, '')
|
|
685
|
+
.replace(/\//g, '_')
|
|
686
|
+
.replace(/-/g, '_')
|
|
687
|
+
.replace(/\./g, '_');
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Capitalize first letter
|
|
692
|
+
* @param {string} str - String to capitalize
|
|
693
|
+
* @returns {string} Capitalized string
|
|
694
|
+
*/
|
|
695
|
+
capitalizeFirst(str) {
|
|
696
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
module.exports = { ServiceInteractionDiagram };
|