@tdsoft-tech/aikit 0.1.19 → 0.1.30
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/LICENSE +63 -0
- package/README.md +348 -0
- package/dist/cli.js +337 -193
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +337 -193
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +337 -193
- package/dist/mcp-server.js.map +1 -1
- package/dist/tools/drawio-convert/convert-to-drawio.d.ts +300 -0
- package/dist/tools/drawio-convert/convert-to-drawio.js +264 -0
- package/dist/tools/drawio-convert/convert-to-drawio.js.map +1 -0
- package/dist/tools/drawio-convert/convert-to-mermaid.d.ts +249 -0
- package/dist/tools/drawio-convert/convert-to-mermaid.js +216 -0
- package/dist/tools/drawio-convert/convert-to-mermaid.js.map +1 -0
- package/dist/tools/drawio-convert/diagram-utils.d.ts +270 -0
- package/dist/tools/drawio-convert/diagram-utils.js +180 -0
- package/dist/tools/drawio-convert/diagram-utils.js.map +1 -0
- package/dist/tools/drawio-convert/open-diagram.d.ts +118 -0
- package/dist/tools/drawio-convert/open-diagram.js +88 -0
- package/dist/tools/drawio-convert/open-diagram.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { validateMermaidSyntax } from './diagram-utils.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mermaid → Draw.io Converter
|
|
7
|
+
*
|
|
8
|
+
* Converts Mermaid files to Draw.io XML format with error handling
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse Mermaid flowchart to extract graph structure
|
|
14
|
+
* @param {string} mermaidCode - Mermaid code
|
|
15
|
+
* @returns {Object} Parsed graph structure
|
|
16
|
+
*/
|
|
17
|
+
function parseMermaidFlowchart(mermaidCode) {
|
|
18
|
+
const lines = mermaidCode.split('\n').map(l => l.trim()).filter(l => l);
|
|
19
|
+
const graph = { direction: 'TD', nodes: new Map(), edges: [] };
|
|
20
|
+
|
|
21
|
+
// Parse direction
|
|
22
|
+
const directionMatch = lines[0]?.match(/graph\s+(TD|LR|TB|RL|BT)/i);
|
|
23
|
+
if (directionMatch) {
|
|
24
|
+
graph.direction = directionMatch[1].toUpperCase();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// First pass: extract standalone nodes
|
|
28
|
+
lines.forEach((line) => {
|
|
29
|
+
if (!line || line.startsWith('%') || line.startsWith('//') || line.startsWith('graph') || line.startsWith('style')) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Square bracket nodes: NodeID[Label]
|
|
34
|
+
const bracketMatch = line.match(/^(\w+)\[([^\]]+)\]/);
|
|
35
|
+
if (bracketMatch && !line.includes('-->')) {
|
|
36
|
+
const [, id, label] = bracketMatch;
|
|
37
|
+
if (!graph.nodes.has(id)) {
|
|
38
|
+
graph.nodes.set(id, { id, label });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Curly brace nodes: NodeID{Label}
|
|
43
|
+
const curlyMatch = line.match(/^(\w+)\{([^\}]+)\}/);
|
|
44
|
+
if (curlyMatch && !line.includes('-->')) {
|
|
45
|
+
const [, id, label] = curlyMatch;
|
|
46
|
+
if (!graph.nodes.has(id)) {
|
|
47
|
+
graph.nodes.set(id, { id, label });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Second pass: extract edges and connected nodes
|
|
53
|
+
lines.forEach((line) => {
|
|
54
|
+
if (!line || line.startsWith('%') || line.startsWith('//') || line.startsWith('graph') || line.startsWith('style')) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Edge with label: A -->|label| B
|
|
59
|
+
const labeledEdgeMatch = line.match(/(\w+)\s*-->\s*\|([^\|]+)\|\s*(\w+)/);
|
|
60
|
+
if (labeledEdgeMatch) {
|
|
61
|
+
const [, fromId, edgeLabel, toId] = labeledEdgeMatch;
|
|
62
|
+
if (!graph.nodes.has(fromId)) {
|
|
63
|
+
graph.nodes.set(fromId, { id: fromId, label: fromId });
|
|
64
|
+
}
|
|
65
|
+
if (!graph.nodes.has(toId)) {
|
|
66
|
+
graph.nodes.set(toId, { id: toId, label: toId });
|
|
67
|
+
}
|
|
68
|
+
graph.edges.push({ from: fromId, to: toId, label: edgeLabel });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Simple edge: A --> B
|
|
73
|
+
const simpleEdgeMatch = line.match(/(\w+)\s*-->\s*(\w+)/);
|
|
74
|
+
if (simpleEdgeMatch) {
|
|
75
|
+
const [, fromId, toId] = simpleEdgeMatch;
|
|
76
|
+
if (!graph.nodes.has(fromId)) {
|
|
77
|
+
graph.nodes.set(fromId, { id: fromId, label: fromId });
|
|
78
|
+
}
|
|
79
|
+
if (!graph.nodes.has(toId)) {
|
|
80
|
+
graph.nodes.set(toId, { id: toId, label: toId });
|
|
81
|
+
}
|
|
82
|
+
const exists = graph.edges.some(e => e.from === fromId && e.to === toId);
|
|
83
|
+
if (!exists) {
|
|
84
|
+
graph.edges.push({ from: fromId, to: toId, label: '' });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return graph;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Calculate node positions for layout
|
|
94
|
+
* @param {Array} nodes - Array of node objects
|
|
95
|
+
* @param {Array} edges - Array of edge objects
|
|
96
|
+
* @param {string} direction - Graph direction (TD, LR, etc.)
|
|
97
|
+
* @returns {Object} Node positions
|
|
98
|
+
*/
|
|
99
|
+
function calculateNodePositions(nodes, edges, direction) {
|
|
100
|
+
const positions = {};
|
|
101
|
+
const spacing = 200;
|
|
102
|
+
|
|
103
|
+
nodes.forEach((node, index) => {
|
|
104
|
+
if (direction === 'TD' || direction === 'TB') {
|
|
105
|
+
const col = index % 3;
|
|
106
|
+
const row = Math.floor(index / 3);
|
|
107
|
+
positions[node.id] = { x: 40 + col * spacing, y: 40 + row * spacing };
|
|
108
|
+
} else {
|
|
109
|
+
// LR or RL
|
|
110
|
+
const row = index % 3;
|
|
111
|
+
const col = Math.floor(index / 3);
|
|
112
|
+
positions[node.id] = { x: 40 + col * spacing, y: 40 + row * spacing };
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return positions;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Generate Draw.io XML from graph structure
|
|
121
|
+
* @param {Object} graph - Graph structure
|
|
122
|
+
* @param {string} diagramName - Diagram name
|
|
123
|
+
* @returns {string} Draw.io XML
|
|
124
|
+
*/
|
|
125
|
+
function generateDrawioXML(graph, diagramName = 'Diagram') {
|
|
126
|
+
const nodes = Array.from(graph.nodes.values());
|
|
127
|
+
const edges = graph.edges;
|
|
128
|
+
const nodePositions = calculateNodePositions(nodes, edges, graph.direction);
|
|
129
|
+
|
|
130
|
+
let mxCells = '';
|
|
131
|
+
mxCells += ` <mxCell id="0" />\n`;
|
|
132
|
+
mxCells += ` <mxCell id="1" parent="0" />\n`;
|
|
133
|
+
|
|
134
|
+
nodes.forEach((node) => {
|
|
135
|
+
const pos = nodePositions[node.id];
|
|
136
|
+
const width = 120;
|
|
137
|
+
const height = 60;
|
|
138
|
+
const style = 'rounded=1;whiteSpace=wrap;html=1;align=center;';
|
|
139
|
+
|
|
140
|
+
mxCells += ` <mxCell id="${node.id}" value="${node.label}" style="${style}" vertex="1" parent="1">\n`;
|
|
141
|
+
mxCells += ` <mxGeometry x="${pos.x}" y="${pos.y}" width="${width}" height="${height}" as="geometry" />\n`;
|
|
142
|
+
mxCells += ` </mxCell>\n`;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
edges.forEach((edge) => {
|
|
146
|
+
const edgeId = `edge-${edge.from}-${edge.to}`;
|
|
147
|
+
const edgeStyle = 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;';
|
|
148
|
+
|
|
149
|
+
mxCells += ` <mxCell id="${edgeId}" value="${edge.label}" style="${edgeStyle}" edge="1" parent="1" source="${edge.from}" target="${edge.to}">\n`;
|
|
150
|
+
mxCells += ` <mxGeometry relative="1" as="geometry" />\n`;
|
|
151
|
+
mxCells += ` </mxCell>\n`;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
155
|
+
<mxfile host="aikit" modified="${new Date().toISOString()}" agent="aikit-drawio-convert" version="1.0.0">
|
|
156
|
+
<diagram name="${diagramName}" id="${diagramName}">
|
|
157
|
+
<mxGraphModel dx="1422" dy="794" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
158
|
+
<root>
|
|
159
|
+
${mxCells} </root>
|
|
160
|
+
</mxGraphModel>
|
|
161
|
+
</diagram>
|
|
162
|
+
</mxfile>`;
|
|
163
|
+
|
|
164
|
+
return xml;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Convert Mermaid code to Draw.io format
|
|
169
|
+
* @param {string} mermaidCode - Mermaid code
|
|
170
|
+
* @param {string} diagramName - Diagram name
|
|
171
|
+
* @returns {Object} { xml, stats, errors }
|
|
172
|
+
*/
|
|
173
|
+
function convertToDrawio(mermaidCode, diagramName = 'Diagram') {
|
|
174
|
+
const errors = [];
|
|
175
|
+
const warnings = [];
|
|
176
|
+
|
|
177
|
+
// 1. Validate Mermaid syntax
|
|
178
|
+
const validation = validateMermaidSyntax(mermaidCode);
|
|
179
|
+
if (!validation.valid) {
|
|
180
|
+
return {
|
|
181
|
+
xml: null,
|
|
182
|
+
stats: null,
|
|
183
|
+
errors: validation.errors.map(e => `Line ${e.line}: ${e.message}`),
|
|
184
|
+
warnings: [],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 2. Parse Mermaid code
|
|
189
|
+
let graph;
|
|
190
|
+
try {
|
|
191
|
+
graph = parseMermaidFlowchart(mermaidCode);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
return {
|
|
194
|
+
xml: null,
|
|
195
|
+
stats: null,
|
|
196
|
+
errors: [`Failed to parse Mermaid code: ${err.message}`],
|
|
197
|
+
warnings: [],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 3. Check for conversion issues
|
|
202
|
+
if (graph.nodes.size === 0) {
|
|
203
|
+
warnings.push('No nodes found in diagram');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 4. Generate Draw.io XML
|
|
207
|
+
let drawioXML;
|
|
208
|
+
try {
|
|
209
|
+
drawioXML = generateDrawioXML(graph, diagramName);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
return {
|
|
212
|
+
xml: null,
|
|
213
|
+
stats: null,
|
|
214
|
+
errors: [`Failed to generate Draw.io XML: ${err.message}`],
|
|
215
|
+
warnings: [],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 5. Return result
|
|
220
|
+
return {
|
|
221
|
+
xml: drawioXML,
|
|
222
|
+
stats: {
|
|
223
|
+
nodes: graph.nodes.size,
|
|
224
|
+
edges: graph.edges.length,
|
|
225
|
+
direction: graph.direction,
|
|
226
|
+
},
|
|
227
|
+
errors,
|
|
228
|
+
warnings,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Convert Mermaid file to Draw.io and save
|
|
234
|
+
* @param {string} mermaidPath - Path to .mmd file
|
|
235
|
+
* @param {string} drawioPath - Path to save .drawio file
|
|
236
|
+
* @param {string} diagramName - Diagram name
|
|
237
|
+
* @returns {Object} { success, errors, warnings }
|
|
238
|
+
*/
|
|
239
|
+
function convertToDrawioFile(mermaidPath, drawioPath, diagramName) {
|
|
240
|
+
// 1. Check file exists
|
|
241
|
+
if (!fs.existsSync(mermaidPath)) {
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
errors: [`File not found: ${mermaidPath}`],
|
|
245
|
+
warnings: [],
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 2. Read file
|
|
250
|
+
let mermaidCode;
|
|
251
|
+
try {
|
|
252
|
+
mermaidCode = fs.readFileSync(mermaidPath, 'utf-8');
|
|
253
|
+
} catch (err) {
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
errors: [`Failed to read file: ${err.message}`],
|
|
257
|
+
warnings: [],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 3. Extract diagram name from path if not provided
|
|
262
|
+
const finalDiagramName = diagramName || path.basename(mermaidPath, '.mmd');
|
|
263
|
+
|
|
264
|
+
// 4. Convert
|
|
265
|
+
const result = convertToDrawio(mermaidCode, finalDiagramName);
|
|
266
|
+
|
|
267
|
+
if (result.errors.length > 0 || !result.xml) {
|
|
268
|
+
return {
|
|
269
|
+
success: false,
|
|
270
|
+
errors: result.errors,
|
|
271
|
+
warnings: result.warnings,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 5. Write file
|
|
276
|
+
try {
|
|
277
|
+
// Ensure directory exists
|
|
278
|
+
const dir = path.dirname(drawioPath);
|
|
279
|
+
if (!fs.existsSync(dir)) {
|
|
280
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
fs.writeFileSync(drawioPath, result.xml, 'utf-8');
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
errors: [],
|
|
288
|
+
warnings: result.warnings,
|
|
289
|
+
stats: result.stats,
|
|
290
|
+
};
|
|
291
|
+
} catch (err) {
|
|
292
|
+
return {
|
|
293
|
+
success: false,
|
|
294
|
+
errors: [`Failed to write Draw.io file: ${err.message}`],
|
|
295
|
+
warnings: result.warnings,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export { convertToDrawio, convertToDrawioFile, generateDrawioXML, parseMermaidFlowchart };
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/tools/drawio-convert/convert-to-drawio.js
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
|
|
7
|
+
// src/tools/drawio-convert/diagram-utils.js
|
|
8
|
+
function validateMermaidSyntax(code) {
|
|
9
|
+
const errors = [];
|
|
10
|
+
const lines = code.split("\n");
|
|
11
|
+
if (!lines[0]?.match(/graph\s+(TD|LR|TB|RL|BT)/i)) {
|
|
12
|
+
errors.push({
|
|
13
|
+
line: 1,
|
|
14
|
+
message: 'Missing or invalid graph declaration. Should start with "graph TD" or "graph LR"'
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
lines.forEach((line, index) => {
|
|
18
|
+
const lineNum = index + 1;
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
if (!trimmed || trimmed.startsWith("%") || trimmed.startsWith("//") || trimmed.startsWith("style")) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (trimmed.includes("|") && !trimmed.match(/\|[^|]*\|/)) {
|
|
24
|
+
const match = trimmed.match(/\|[^|]*/);
|
|
25
|
+
if (match) {
|
|
26
|
+
errors.push({
|
|
27
|
+
line: lineNum,
|
|
28
|
+
message: `Unclosed edge label. Use |text| syntax. Found: "${match[0]}"`
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (trimmed.match(/--[^>\s]/)) {
|
|
33
|
+
errors.push({
|
|
34
|
+
line: lineNum,
|
|
35
|
+
message: "Invalid edge syntax. Use --> for edges, not -- > or other variations"
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
valid: errors.length === 0,
|
|
41
|
+
errors
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/tools/drawio-convert/convert-to-drawio.js
|
|
46
|
+
function parseMermaidFlowchart(mermaidCode) {
|
|
47
|
+
const lines = mermaidCode.split("\n").map((l) => l.trim()).filter((l) => l);
|
|
48
|
+
const graph = { direction: "TD", nodes: /* @__PURE__ */ new Map(), edges: [] };
|
|
49
|
+
const directionMatch = lines[0]?.match(/graph\s+(TD|LR|TB|RL|BT)/i);
|
|
50
|
+
if (directionMatch) {
|
|
51
|
+
graph.direction = directionMatch[1].toUpperCase();
|
|
52
|
+
}
|
|
53
|
+
lines.forEach((line) => {
|
|
54
|
+
if (!line || line.startsWith("%") || line.startsWith("//") || line.startsWith("graph") || line.startsWith("style")) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const bracketMatch = line.match(/^(\w+)\[([^\]]+)\]/);
|
|
58
|
+
if (bracketMatch && !line.includes("-->")) {
|
|
59
|
+
const [, id, label] = bracketMatch;
|
|
60
|
+
if (!graph.nodes.has(id)) {
|
|
61
|
+
graph.nodes.set(id, { id, label });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const curlyMatch = line.match(/^(\w+)\{([^\}]+)\}/);
|
|
65
|
+
if (curlyMatch && !line.includes("-->")) {
|
|
66
|
+
const [, id, label] = curlyMatch;
|
|
67
|
+
if (!graph.nodes.has(id)) {
|
|
68
|
+
graph.nodes.set(id, { id, label });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
lines.forEach((line) => {
|
|
73
|
+
if (!line || line.startsWith("%") || line.startsWith("//") || line.startsWith("graph") || line.startsWith("style")) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const labeledEdgeMatch = line.match(/(\w+)\s*-->\s*\|([^\|]+)\|\s*(\w+)/);
|
|
77
|
+
if (labeledEdgeMatch) {
|
|
78
|
+
const [, fromId, edgeLabel, toId] = labeledEdgeMatch;
|
|
79
|
+
if (!graph.nodes.has(fromId)) {
|
|
80
|
+
graph.nodes.set(fromId, { id: fromId, label: fromId });
|
|
81
|
+
}
|
|
82
|
+
if (!graph.nodes.has(toId)) {
|
|
83
|
+
graph.nodes.set(toId, { id: toId, label: toId });
|
|
84
|
+
}
|
|
85
|
+
graph.edges.push({ from: fromId, to: toId, label: edgeLabel });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const simpleEdgeMatch = line.match(/(\w+)\s*-->\s*(\w+)/);
|
|
89
|
+
if (simpleEdgeMatch) {
|
|
90
|
+
const [, fromId, toId] = simpleEdgeMatch;
|
|
91
|
+
if (!graph.nodes.has(fromId)) {
|
|
92
|
+
graph.nodes.set(fromId, { id: fromId, label: fromId });
|
|
93
|
+
}
|
|
94
|
+
if (!graph.nodes.has(toId)) {
|
|
95
|
+
graph.nodes.set(toId, { id: toId, label: toId });
|
|
96
|
+
}
|
|
97
|
+
const exists = graph.edges.some((e) => e.from === fromId && e.to === toId);
|
|
98
|
+
if (!exists) {
|
|
99
|
+
graph.edges.push({ from: fromId, to: toId, label: "" });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return graph;
|
|
104
|
+
}
|
|
105
|
+
function calculateNodePositions(nodes, edges, direction) {
|
|
106
|
+
const positions = {};
|
|
107
|
+
const spacing = 200;
|
|
108
|
+
nodes.forEach((node, index) => {
|
|
109
|
+
if (direction === "TD" || direction === "TB") {
|
|
110
|
+
const col = index % 3;
|
|
111
|
+
const row = Math.floor(index / 3);
|
|
112
|
+
positions[node.id] = { x: 40 + col * spacing, y: 40 + row * spacing };
|
|
113
|
+
} else {
|
|
114
|
+
const row = index % 3;
|
|
115
|
+
const col = Math.floor(index / 3);
|
|
116
|
+
positions[node.id] = { x: 40 + col * spacing, y: 40 + row * spacing };
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return positions;
|
|
120
|
+
}
|
|
121
|
+
function generateDrawioXML(graph, diagramName = "Diagram") {
|
|
122
|
+
const nodes = Array.from(graph.nodes.values());
|
|
123
|
+
const edges = graph.edges;
|
|
124
|
+
const nodePositions = calculateNodePositions(nodes, edges, graph.direction);
|
|
125
|
+
let mxCells = "";
|
|
126
|
+
mxCells += ` <mxCell id="0" />
|
|
127
|
+
`;
|
|
128
|
+
mxCells += ` <mxCell id="1" parent="0" />
|
|
129
|
+
`;
|
|
130
|
+
nodes.forEach((node) => {
|
|
131
|
+
const pos = nodePositions[node.id];
|
|
132
|
+
const width = 120;
|
|
133
|
+
const height = 60;
|
|
134
|
+
const style = "rounded=1;whiteSpace=wrap;html=1;align=center;";
|
|
135
|
+
mxCells += ` <mxCell id="${node.id}" value="${node.label}" style="${style}" vertex="1" parent="1">
|
|
136
|
+
`;
|
|
137
|
+
mxCells += ` <mxGeometry x="${pos.x}" y="${pos.y}" width="${width}" height="${height}" as="geometry" />
|
|
138
|
+
`;
|
|
139
|
+
mxCells += ` </mxCell>
|
|
140
|
+
`;
|
|
141
|
+
});
|
|
142
|
+
edges.forEach((edge) => {
|
|
143
|
+
const edgeId = `edge-${edge.from}-${edge.to}`;
|
|
144
|
+
const edgeStyle = "edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;";
|
|
145
|
+
mxCells += ` <mxCell id="${edgeId}" value="${edge.label}" style="${edgeStyle}" edge="1" parent="1" source="${edge.from}" target="${edge.to}">
|
|
146
|
+
`;
|
|
147
|
+
mxCells += ` <mxGeometry relative="1" as="geometry" />
|
|
148
|
+
`;
|
|
149
|
+
mxCells += ` </mxCell>
|
|
150
|
+
`;
|
|
151
|
+
});
|
|
152
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
153
|
+
<mxfile host="aikit" modified="${(/* @__PURE__ */ new Date()).toISOString()}" agent="aikit-drawio-convert" version="1.0.0">
|
|
154
|
+
<diagram name="${diagramName}" id="${diagramName}">
|
|
155
|
+
<mxGraphModel dx="1422" dy="794" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
156
|
+
<root>
|
|
157
|
+
${mxCells} </root>
|
|
158
|
+
</mxGraphModel>
|
|
159
|
+
</diagram>
|
|
160
|
+
</mxfile>`;
|
|
161
|
+
return xml;
|
|
162
|
+
}
|
|
163
|
+
function convertToDrawio(mermaidCode, diagramName = "Diagram") {
|
|
164
|
+
const errors = [];
|
|
165
|
+
const warnings = [];
|
|
166
|
+
const validation = validateMermaidSyntax(mermaidCode);
|
|
167
|
+
if (!validation.valid) {
|
|
168
|
+
return {
|
|
169
|
+
xml: null,
|
|
170
|
+
stats: null,
|
|
171
|
+
errors: validation.errors.map((e) => `Line ${e.line}: ${e.message}`),
|
|
172
|
+
warnings: []
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
let graph;
|
|
176
|
+
try {
|
|
177
|
+
graph = parseMermaidFlowchart(mermaidCode);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
return {
|
|
180
|
+
xml: null,
|
|
181
|
+
stats: null,
|
|
182
|
+
errors: [`Failed to parse Mermaid code: ${err.message}`],
|
|
183
|
+
warnings: []
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (graph.nodes.size === 0) {
|
|
187
|
+
warnings.push("No nodes found in diagram");
|
|
188
|
+
}
|
|
189
|
+
let drawioXML;
|
|
190
|
+
try {
|
|
191
|
+
drawioXML = generateDrawioXML(graph, diagramName);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
return {
|
|
194
|
+
xml: null,
|
|
195
|
+
stats: null,
|
|
196
|
+
errors: [`Failed to generate Draw.io XML: ${err.message}`],
|
|
197
|
+
warnings: []
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
xml: drawioXML,
|
|
202
|
+
stats: {
|
|
203
|
+
nodes: graph.nodes.size,
|
|
204
|
+
edges: graph.edges.length,
|
|
205
|
+
direction: graph.direction
|
|
206
|
+
},
|
|
207
|
+
errors,
|
|
208
|
+
warnings
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function convertToDrawioFile(mermaidPath, drawioPath, diagramName) {
|
|
212
|
+
if (!fs.existsSync(mermaidPath)) {
|
|
213
|
+
return {
|
|
214
|
+
success: false,
|
|
215
|
+
errors: [`File not found: ${mermaidPath}`],
|
|
216
|
+
warnings: []
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
let mermaidCode;
|
|
220
|
+
try {
|
|
221
|
+
mermaidCode = fs.readFileSync(mermaidPath, "utf-8");
|
|
222
|
+
} catch (err) {
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
errors: [`Failed to read file: ${err.message}`],
|
|
226
|
+
warnings: []
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const finalDiagramName = diagramName || path.basename(mermaidPath, ".mmd");
|
|
230
|
+
const result = convertToDrawio(mermaidCode, finalDiagramName);
|
|
231
|
+
if (result.errors.length > 0 || !result.xml) {
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
errors: result.errors,
|
|
235
|
+
warnings: result.warnings
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const dir = path.dirname(drawioPath);
|
|
240
|
+
if (!fs.existsSync(dir)) {
|
|
241
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
242
|
+
}
|
|
243
|
+
fs.writeFileSync(drawioPath, result.xml, "utf-8");
|
|
244
|
+
return {
|
|
245
|
+
success: true,
|
|
246
|
+
errors: [],
|
|
247
|
+
warnings: result.warnings,
|
|
248
|
+
stats: result.stats
|
|
249
|
+
};
|
|
250
|
+
} catch (err) {
|
|
251
|
+
return {
|
|
252
|
+
success: false,
|
|
253
|
+
errors: [`Failed to write Draw.io file: ${err.message}`],
|
|
254
|
+
warnings: result.warnings
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
export {
|
|
259
|
+
convertToDrawio,
|
|
260
|
+
convertToDrawioFile,
|
|
261
|
+
generateDrawioXML,
|
|
262
|
+
parseMermaidFlowchart
|
|
263
|
+
};
|
|
264
|
+
//# sourceMappingURL=convert-to-drawio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/tools/drawio-convert/convert-to-drawio.js","../../../src/tools/drawio-convert/diagram-utils.js"],"sourcesContent":["/**\n * Mermaid → Draw.io Converter\n *\n * Converts Mermaid files to Draw.io XML format with error handling\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { validateMermaidSyntax } from './diagram-utils.js';\n\n/**\n * Parse Mermaid flowchart to extract graph structure\n * @param {string} mermaidCode - Mermaid code\n * @returns {Object} Parsed graph structure\n */\nfunction parseMermaidFlowchart(mermaidCode) {\n const lines = mermaidCode.split('\\n').map(l => l.trim()).filter(l => l);\n const graph = { direction: 'TD', nodes: new Map(), edges: [] };\n\n // Parse direction\n const directionMatch = lines[0]?.match(/graph\\s+(TD|LR|TB|RL|BT)/i);\n if (directionMatch) {\n graph.direction = directionMatch[1].toUpperCase();\n }\n\n // First pass: extract standalone nodes\n lines.forEach((line) => {\n if (!line || line.startsWith('%') || line.startsWith('//') || line.startsWith('graph') || line.startsWith('style')) {\n return;\n }\n\n // Square bracket nodes: NodeID[Label]\n const bracketMatch = line.match(/^(\\w+)\\[([^\\]]+)\\]/);\n if (bracketMatch && !line.includes('-->')) {\n const [, id, label] = bracketMatch;\n if (!graph.nodes.has(id)) {\n graph.nodes.set(id, { id, label });\n }\n }\n\n // Curly brace nodes: NodeID{Label}\n const curlyMatch = line.match(/^(\\w+)\\{([^\\}]+)\\}/);\n if (curlyMatch && !line.includes('-->')) {\n const [, id, label] = curlyMatch;\n if (!graph.nodes.has(id)) {\n graph.nodes.set(id, { id, label });\n }\n }\n });\n\n // Second pass: extract edges and connected nodes\n lines.forEach((line) => {\n if (!line || line.startsWith('%') || line.startsWith('//') || line.startsWith('graph') || line.startsWith('style')) {\n return;\n }\n\n // Edge with label: A -->|label| B\n const labeledEdgeMatch = line.match(/(\\w+)\\s*-->\\s*\\|([^\\|]+)\\|\\s*(\\w+)/);\n if (labeledEdgeMatch) {\n const [, fromId, edgeLabel, toId] = labeledEdgeMatch;\n if (!graph.nodes.has(fromId)) {\n graph.nodes.set(fromId, { id: fromId, label: fromId });\n }\n if (!graph.nodes.has(toId)) {\n graph.nodes.set(toId, { id: toId, label: toId });\n }\n graph.edges.push({ from: fromId, to: toId, label: edgeLabel });\n return;\n }\n\n // Simple edge: A --> B\n const simpleEdgeMatch = line.match(/(\\w+)\\s*-->\\s*(\\w+)/);\n if (simpleEdgeMatch) {\n const [, fromId, toId] = simpleEdgeMatch;\n if (!graph.nodes.has(fromId)) {\n graph.nodes.set(fromId, { id: fromId, label: fromId });\n }\n if (!graph.nodes.has(toId)) {\n graph.nodes.set(toId, { id: toId, label: toId });\n }\n const exists = graph.edges.some(e => e.from === fromId && e.to === toId);\n if (!exists) {\n graph.edges.push({ from: fromId, to: toId, label: '' });\n }\n }\n });\n\n return graph;\n}\n\n/**\n * Calculate node positions for layout\n * @param {Array} nodes - Array of node objects\n * @param {Array} edges - Array of edge objects\n * @param {string} direction - Graph direction (TD, LR, etc.)\n * @returns {Object} Node positions\n */\nfunction calculateNodePositions(nodes, edges, direction) {\n const positions = {};\n const spacing = 200;\n\n nodes.forEach((node, index) => {\n if (direction === 'TD' || direction === 'TB') {\n const col = index % 3;\n const row = Math.floor(index / 3);\n positions[node.id] = { x: 40 + col * spacing, y: 40 + row * spacing };\n } else {\n // LR or RL\n const row = index % 3;\n const col = Math.floor(index / 3);\n positions[node.id] = { x: 40 + col * spacing, y: 40 + row * spacing };\n }\n });\n\n return positions;\n}\n\n/**\n * Generate Draw.io XML from graph structure\n * @param {Object} graph - Graph structure\n * @param {string} diagramName - Diagram name\n * @returns {string} Draw.io XML\n */\nfunction generateDrawioXML(graph, diagramName = 'Diagram') {\n const nodes = Array.from(graph.nodes.values());\n const edges = graph.edges;\n const nodePositions = calculateNodePositions(nodes, edges, graph.direction);\n\n let mxCells = '';\n mxCells += ` <mxCell id=\"0\" />\\n`;\n mxCells += ` <mxCell id=\"1\" parent=\"0\" />\\n`;\n\n nodes.forEach((node) => {\n const pos = nodePositions[node.id];\n const width = 120;\n const height = 60;\n const style = 'rounded=1;whiteSpace=wrap;html=1;align=center;';\n\n mxCells += ` <mxCell id=\"${node.id}\" value=\"${node.label}\" style=\"${style}\" vertex=\"1\" parent=\"1\">\\n`;\n mxCells += ` <mxGeometry x=\"${pos.x}\" y=\"${pos.y}\" width=\"${width}\" height=\"${height}\" as=\"geometry\" />\\n`;\n mxCells += ` </mxCell>\\n`;\n });\n\n edges.forEach((edge) => {\n const edgeId = `edge-${edge.from}-${edge.to}`;\n const edgeStyle = 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;';\n\n mxCells += ` <mxCell id=\"${edgeId}\" value=\"${edge.label}\" style=\"${edgeStyle}\" edge=\"1\" parent=\"1\" source=\"${edge.from}\" target=\"${edge.to}\">\\n`;\n mxCells += ` <mxGeometry relative=\"1\" as=\"geometry\" />\\n`;\n mxCells += ` </mxCell>\\n`;\n });\n\n const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<mxfile host=\"aikit\" modified=\"${new Date().toISOString()}\" agent=\"aikit-drawio-convert\" version=\"1.0.0\">\n <diagram name=\"${diagramName}\" id=\"${diagramName}\">\n <mxGraphModel dx=\"1422\" dy=\"794\" grid=\"1\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"1\" pageScale=\"1\" pageWidth=\"827\" pageHeight=\"1169\" math=\"0\" shadow=\"0\">\n <root>\n${mxCells} </root>\n </mxGraphModel>\n </diagram>\n</mxfile>`;\n\n return xml;\n}\n\n/**\n * Convert Mermaid code to Draw.io format\n * @param {string} mermaidCode - Mermaid code\n * @param {string} diagramName - Diagram name\n * @returns {Object} { xml, stats, errors }\n */\nexport function convertToDrawio(mermaidCode, diagramName = 'Diagram') {\n const errors = [];\n const warnings = [];\n\n // 1. Validate Mermaid syntax\n const validation = validateMermaidSyntax(mermaidCode);\n if (!validation.valid) {\n return {\n xml: null,\n stats: null,\n errors: validation.errors.map(e => `Line ${e.line}: ${e.message}`),\n warnings: [],\n };\n }\n\n // 2. Parse Mermaid code\n let graph;\n try {\n graph = parseMermaidFlowchart(mermaidCode);\n } catch (err) {\n return {\n xml: null,\n stats: null,\n errors: [`Failed to parse Mermaid code: ${err.message}`],\n warnings: [],\n };\n }\n\n // 3. Check for conversion issues\n if (graph.nodes.size === 0) {\n warnings.push('No nodes found in diagram');\n }\n\n // 4. Generate Draw.io XML\n let drawioXML;\n try {\n drawioXML = generateDrawioXML(graph, diagramName);\n } catch (err) {\n return {\n xml: null,\n stats: null,\n errors: [`Failed to generate Draw.io XML: ${err.message}`],\n warnings: [],\n };\n }\n\n // 5. Return result\n return {\n xml: drawioXML,\n stats: {\n nodes: graph.nodes.size,\n edges: graph.edges.length,\n direction: graph.direction,\n },\n errors,\n warnings,\n };\n}\n\n/**\n * Convert Mermaid file to Draw.io and save\n * @param {string} mermaidPath - Path to .mmd file\n * @param {string} drawioPath - Path to save .drawio file\n * @param {string} diagramName - Diagram name\n * @returns {Object} { success, errors, warnings }\n */\nexport function convertToDrawioFile(mermaidPath, drawioPath, diagramName) {\n // 1. Check file exists\n if (!fs.existsSync(mermaidPath)) {\n return {\n success: false,\n errors: [`File not found: ${mermaidPath}`],\n warnings: [],\n };\n }\n\n // 2. Read file\n let mermaidCode;\n try {\n mermaidCode = fs.readFileSync(mermaidPath, 'utf-8');\n } catch (err) {\n return {\n success: false,\n errors: [`Failed to read file: ${err.message}`],\n warnings: [],\n };\n }\n\n // 3. Extract diagram name from path if not provided\n const finalDiagramName = diagramName || path.basename(mermaidPath, '.mmd');\n\n // 4. Convert\n const result = convertToDrawio(mermaidCode, finalDiagramName);\n\n if (result.errors.length > 0 || !result.xml) {\n return {\n success: false,\n errors: result.errors,\n warnings: result.warnings,\n };\n }\n\n // 5. Write file\n try {\n // Ensure directory exists\n const dir = path.dirname(drawioPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n fs.writeFileSync(drawioPath, result.xml, 'utf-8');\n\n return {\n success: true,\n errors: [],\n warnings: result.warnings,\n stats: result.stats,\n };\n } catch (err) {\n return {\n success: false,\n errors: [`Failed to write Draw.io file: ${err.message}`],\n warnings: result.warnings,\n };\n }\n}\n\nexport { parseMermaidFlowchart, generateDrawioXML };\n","/**\n * Diagram Utilities\n *\n * Shared utilities for drawio-convert modules\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\n/**\n * Resolve diagram path from user input\n * @param {string} input - Diagram name or file path\n * @param {'drawio' | 'mermaid'} type - Target file type\n * @param {string} projectRoot - Project root directory\n * @returns {string} Absolute path to diagram file\n */\nexport function resolveDiagramPath(input, type, projectRoot) {\n // 1. Absolute path - use as-is\n if (path.isAbsolute(input)) {\n return input;\n }\n\n // 2. Relative path or contains directory separators\n if (input.includes('/') || input.includes('\\\\')) {\n return path.resolve(projectRoot, input);\n }\n\n // 3. Just a name - use standard location\n if (type === 'drawio') {\n return path.join(projectRoot, '.aikit/assets/drawio', `${input}.drawio`);\n } else {\n return path.join(projectRoot, 'mermaid', `${input}.mmd`);\n }\n}\n\n/**\n * Find paired diagram file (drawio ↔ mermaid)\n * @param {string} filePath - Path to current file\n * @param {string} projectRoot - Project root directory\n * @returns {string|null} Path to paired file or null\n */\nexport function findPairedDiagram(filePath, projectRoot) {\n const ext = path.extname(filePath);\n const basename = path.basename(filePath, ext);\n\n if (ext === '.drawio') {\n // Look for corresponding .mmd file\n const possiblePaths = [\n path.join(projectRoot, 'mermaid', `${basename}.mmd`),\n path.join(path.dirname(filePath), `${basename}.mmd`),\n ];\n\n for (const p of possiblePaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n } else if (ext === '.mmd') {\n // Look for corresponding .drawio file\n const possiblePaths = [\n path.join(projectRoot, '.aikit/assets/drawio', `${basename}.drawio`),\n path.join(path.dirname(filePath), `${basename}.drawio`),\n ];\n\n for (const p of possiblePaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Validate Mermaid syntax (basic validation)\n * @param {string} code - Mermaid code\n * @returns {Object} { valid, errors }\n */\nexport function validateMermaidSyntax(code) {\n const errors = [];\n const lines = code.split('\\n');\n\n // Check for graph declaration\n if (!lines[0]?.match(/graph\\s+(TD|LR|TB|RL|BT)/i)) {\n errors.push({\n line: 1,\n message: 'Missing or invalid graph declaration. Should start with \"graph TD\" or \"graph LR\"',\n });\n }\n\n // Check for common syntax errors\n lines.forEach((line, index) => {\n const lineNum = index + 1;\n const trimmed = line.trim();\n\n // Skip comments and empty lines\n if (!trimmed || trimmed.startsWith('%') || trimmed.startsWith('//') || trimmed.startsWith('style')) {\n return;\n }\n\n // Check for malformed edge labels (missing closing |)\n if (trimmed.includes('|') && !trimmed.match(/\\|[^|]*\\|/)) {\n const match = trimmed.match(/\\|[^|]*/);\n if (match) {\n errors.push({\n line: lineNum,\n message: `Unclosed edge label. Use |text| syntax. Found: \"${match[0]}\"`,\n });\n }\n }\n\n // Check for malformed edge syntax (--> with extra characters)\n if (trimmed.match(/--[^>\\s]/)) {\n errors.push({\n line: lineNum,\n message: 'Invalid edge syntax. Use --> for edges, not -- > or other variations',\n });\n }\n });\n\n return {\n valid: errors.length === 0,\n errors,\n };\n}\n\n/**\n * Validate Draw.io XML (basic validation)\n * @param {string} xml - Draw.io XML content\n * @returns {Object} { valid, errors }\n */\nexport function validateDrawioXML(xml) {\n const errors = [];\n\n // Check for required XML tags\n if (!xml.includes('<mxfile')) {\n errors.push({\n message: 'Missing <mxfile> tag. Not a valid Draw.io file',\n });\n }\n\n if (!xml.includes('<mxGraphModel')) {\n errors.push({\n message: 'Missing <mxGraphModel> tag. Not a valid Draw.io file',\n });\n }\n\n if (!xml.includes('<root>')) {\n errors.push({\n message: 'Missing <root> tag. Not a valid Draw.io file',\n });\n }\n\n // Check for XML declaration\n if (!xml.startsWith('<?xml')) {\n errors.push({\n message: 'Missing XML declaration. File should start with <?xml version=\"1.0\"',\n });\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n}\n\n/**\n * Get list of all diagrams in the project\n * @param {string} projectRoot - Project root directory\n * @returns {Array} List of diagram objects\n */\nexport function getDiagramList(projectRoot) {\n const diagrams = [];\n const mermaidDir = path.join(projectRoot, 'mermaid');\n const drawioDir = path.join(projectRoot, '.aikit/assets/drawio');\n\n // Scan mermaid directory\n if (fs.existsSync(mermaidDir)) {\n const mmdFiles = fs.readdirSync(mermaidDir)\n .filter(f => f.endsWith('.mmd'))\n .map(f => {\n const name = path.basename(f, '.mmd');\n const mmdPath = path.join(mermaidDir, f);\n const drawioPath = path.join(drawioDir, `${name}.drawio`);\n\n return {\n name,\n mermaid: mmdPath,\n drawio: fs.existsSync(drawioPath) ? drawioPath : null,\n hasMermaid: true,\n hasDrawio: fs.existsSync(drawioPath),\n };\n });\n\n diagrams.push(...mmdFiles);\n }\n\n // Scan drawio directory for files without mermaid counterpart\n if (fs.existsSync(drawioDir)) {\n const drawioFiles = fs.readdirSync(drawioDir)\n .filter(f => f.endsWith('.drawio'))\n .map(f => {\n const name = path.basename(f, '.drawio');\n const drawioPath = path.join(drawioDir, f);\n const mmdPath = path.join(mermaidDir, `${name}.mmd`);\n\n // Skip if already in list\n if (diagrams.some(d => d.name === name)) {\n return null;\n }\n\n return {\n name,\n mermaid: fs.existsSync(mmdPath) ? mmdPath : null,\n drawio: drawioPath,\n hasMermaid: fs.existsSync(mmdPath),\n hasDrawio: true,\n };\n })\n .filter(d => d !== null);\n\n diagrams.push(...drawioFiles);\n }\n\n return diagrams;\n}\n\n/**\n * Format conversion stats for display\n * @param {Object} stats - Conversion stats\n * @returns {string} Formatted stats string\n */\nexport function formatConversionStats(stats) {\n const parts = [];\n\n if (stats.nodes !== undefined) {\n parts.push(`${stats.nodes} node${stats.nodes !== 1 ? 's' : ''}`);\n }\n\n if (stats.edges !== undefined) {\n parts.push(`${stats.edges} edge${stats.edges !== 1 ? 's' : ''}`);\n }\n\n if (stats.direction) {\n parts.push(`direction: ${stats.direction}`);\n }\n\n return parts.join(', ');\n}\n\n/**\n * Ensure required directories exist\n * @param {string} projectRoot - Project root directory\n */\nexport function ensureDiagramDirectories(projectRoot) {\n const dirs = [\n path.join(projectRoot, 'mermaid'),\n path.join(projectRoot, '.aikit/assets/drawio'),\n ];\n\n dirs.forEach(dir => {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n });\n}\n"],"mappings":"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","names":[]}
|