@tdsoft-tech/aikit 0.1.20 → 0.1.31
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/CHANGELOG.md +14 -0
- package/LICENSE +63 -0
- package/README.md +359 -0
- package/dist/cli.js +1258 -583
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +630 -256
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +350 -256
- 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 +5 -3
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
4
|
+
import { validateDrawioXML } from './diagram-utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Draw.io → Mermaid Converter
|
|
8
|
+
*
|
|
9
|
+
* Converts Draw.io XML files to Mermaid format with error handling
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse Draw.io XML to extract graph structure
|
|
15
|
+
* @param {string} drawioXML - Draw.io XML content
|
|
16
|
+
* @returns {Object} Parsed graph structure
|
|
17
|
+
*/
|
|
18
|
+
function parseDrawioXML(drawioXML) {
|
|
19
|
+
const parser = new XMLParser({
|
|
20
|
+
ignoreAttributes: false,
|
|
21
|
+
attributeNamePrefix: '',
|
|
22
|
+
textNodeName: '_text'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const xml = parser.parse(drawioXML);
|
|
26
|
+
const diagram = xml.mxfile.diagram;
|
|
27
|
+
const graphModel = diagram.mxGraphModel;
|
|
28
|
+
const root = graphModel.root;
|
|
29
|
+
|
|
30
|
+
const graph = { nodes: new Map(), edges: [] };
|
|
31
|
+
|
|
32
|
+
if (Array.isArray(root.mxCell)) {
|
|
33
|
+
root.mxCell.forEach(cell => {
|
|
34
|
+
if (cell.id === '0' || cell.id === '1') return;
|
|
35
|
+
|
|
36
|
+
if (cell.vertex === '1' || cell.vertex === true) {
|
|
37
|
+
graph.nodes.set(cell.id, {
|
|
38
|
+
id: cell.id,
|
|
39
|
+
label: cell.value || cell.id,
|
|
40
|
+
style: cell.style || ''
|
|
41
|
+
});
|
|
42
|
+
} else if (cell.edge === '1' || cell.edge === true) {
|
|
43
|
+
graph.edges.push({
|
|
44
|
+
id: cell.id,
|
|
45
|
+
from: cell.source,
|
|
46
|
+
to: cell.target,
|
|
47
|
+
label: cell.value || ''
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
} else if (root.mxCell) {
|
|
52
|
+
// Handle single cell case
|
|
53
|
+
const cell = root.mxCell;
|
|
54
|
+
if (cell.id !== '0' && cell.id !== '1') {
|
|
55
|
+
if (cell.vertex === '1' || cell.vertex === true) {
|
|
56
|
+
graph.nodes.set(cell.id, {
|
|
57
|
+
id: cell.id,
|
|
58
|
+
label: cell.value || cell.id,
|
|
59
|
+
style: cell.style || ''
|
|
60
|
+
});
|
|
61
|
+
} else if (cell.edge === '1' || cell.edge === true) {
|
|
62
|
+
graph.edges.push({
|
|
63
|
+
id: cell.id,
|
|
64
|
+
from: cell.source,
|
|
65
|
+
to: cell.target,
|
|
66
|
+
label: cell.value || ''
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return graph;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generate Mermaid code from graph structure
|
|
77
|
+
* @param {Object} graph - Graph structure
|
|
78
|
+
* @returns {string} Mermaid code
|
|
79
|
+
*/
|
|
80
|
+
function generateMermaidCode(graph) {
|
|
81
|
+
const nodes = Array.from(graph.nodes.values());
|
|
82
|
+
const edges = graph.edges;
|
|
83
|
+
|
|
84
|
+
let mermaid = 'graph TD\n';
|
|
85
|
+
|
|
86
|
+
edges.forEach((edge) => {
|
|
87
|
+
const fromNode = graph.nodes.get(edge.from);
|
|
88
|
+
const toNode = graph.nodes.get(edge.to);
|
|
89
|
+
|
|
90
|
+
if (!fromNode || !toNode) return;
|
|
91
|
+
|
|
92
|
+
const fromLabel = sanitizeNodeId(fromNode.label);
|
|
93
|
+
const toLabel = sanitizeNodeId(toNode.label);
|
|
94
|
+
const edgeLabel = edge.label ? `|"${edge.label}"|` : '';
|
|
95
|
+
|
|
96
|
+
mermaid += ` ${fromLabel}[${fromNode.label}] --> ${edgeLabel} ${toLabel}[${toNode.label}]\n`;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
nodes.forEach((node) => {
|
|
100
|
+
const hasEdge = edges.some(e => e.from === node.id || e.to === node.id);
|
|
101
|
+
if (!hasEdge) {
|
|
102
|
+
const label = sanitizeNodeId(node.label);
|
|
103
|
+
mermaid += ` ${label}[${node.label}]\n`;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return mermaid;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Sanitize node ID for Mermaid (remove special characters)
|
|
112
|
+
* @param {string} label - Node label
|
|
113
|
+
* @returns {string} Sanitized ID
|
|
114
|
+
*/
|
|
115
|
+
function sanitizeNodeId(label) {
|
|
116
|
+
return label
|
|
117
|
+
.replace(/[^a-zA-Z0-9]/g, '_')
|
|
118
|
+
.replace(/^[0-9]/, '_$&'); // Prefix numbers with underscore
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Convert Draw.io file to Mermaid format
|
|
123
|
+
* @param {string} drawioPath - Path to .drawio file
|
|
124
|
+
* @returns {Object} { code, stats, errors }
|
|
125
|
+
*/
|
|
126
|
+
function convertToMermaid(drawioPath) {
|
|
127
|
+
const errors = [];
|
|
128
|
+
const warnings = [];
|
|
129
|
+
|
|
130
|
+
// 1. Check file exists
|
|
131
|
+
if (!fs.existsSync(drawioPath)) {
|
|
132
|
+
return {
|
|
133
|
+
code: null,
|
|
134
|
+
stats: null,
|
|
135
|
+
errors: [`File not found: ${drawioPath}`],
|
|
136
|
+
warnings: [],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 2. Read file
|
|
141
|
+
let drawioXML;
|
|
142
|
+
try {
|
|
143
|
+
drawioXML = fs.readFileSync(drawioPath, 'utf-8');
|
|
144
|
+
} catch (err) {
|
|
145
|
+
return {
|
|
146
|
+
code: null,
|
|
147
|
+
stats: null,
|
|
148
|
+
errors: [`Failed to read file: ${err.message}`],
|
|
149
|
+
warnings: [],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 3. Validate XML structure
|
|
154
|
+
const validation = validateDrawioXML(drawioXML);
|
|
155
|
+
if (!validation.valid) {
|
|
156
|
+
return {
|
|
157
|
+
code: null,
|
|
158
|
+
stats: null,
|
|
159
|
+
errors: validation.errors.map(e => e.message),
|
|
160
|
+
warnings: [],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 4. Parse and convert
|
|
165
|
+
let graph;
|
|
166
|
+
try {
|
|
167
|
+
graph = parseDrawioXML(drawioXML);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return {
|
|
170
|
+
code: null,
|
|
171
|
+
stats: null,
|
|
172
|
+
errors: [`Failed to parse Draw.io XML: ${err.message}`],
|
|
173
|
+
warnings: [],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 5. Generate Mermaid code
|
|
178
|
+
let mermaidCode;
|
|
179
|
+
try {
|
|
180
|
+
mermaidCode = generateMermaidCode(graph);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return {
|
|
183
|
+
code: null,
|
|
184
|
+
stats: null,
|
|
185
|
+
errors: [`Failed to generate Mermaid code: ${err.message}`],
|
|
186
|
+
warnings: [],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 6. Check for conversion issues
|
|
191
|
+
if (graph.nodes.size === 0) {
|
|
192
|
+
warnings.push('No nodes found in diagram');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 7. Return result
|
|
196
|
+
return {
|
|
197
|
+
code: mermaidCode,
|
|
198
|
+
stats: {
|
|
199
|
+
nodes: graph.nodes.size,
|
|
200
|
+
edges: graph.edges.length,
|
|
201
|
+
},
|
|
202
|
+
errors,
|
|
203
|
+
warnings,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Convert Draw.io to Mermaid and save to file
|
|
209
|
+
* @param {string} drawioPath - Path to .drawio file
|
|
210
|
+
* @param {string} mermaidPath - Path to save .mmd file
|
|
211
|
+
* @returns {Object} { success, errors, warnings }
|
|
212
|
+
*/
|
|
213
|
+
function convertToMermaidFile(drawioPath, mermaidPath) {
|
|
214
|
+
const result = convertToMermaid(drawioPath);
|
|
215
|
+
|
|
216
|
+
if (result.errors.length > 0 || !result.code) {
|
|
217
|
+
return {
|
|
218
|
+
success: false,
|
|
219
|
+
errors: result.errors,
|
|
220
|
+
warnings: result.warnings,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
// Ensure directory exists
|
|
226
|
+
const dir = path.dirname(mermaidPath);
|
|
227
|
+
if (!fs.existsSync(dir)) {
|
|
228
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Write Mermaid file
|
|
232
|
+
fs.writeFileSync(mermaidPath, result.code, 'utf-8');
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
success: true,
|
|
236
|
+
errors: [],
|
|
237
|
+
warnings: result.warnings,
|
|
238
|
+
stats: result.stats,
|
|
239
|
+
};
|
|
240
|
+
} catch (err) {
|
|
241
|
+
return {
|
|
242
|
+
success: false,
|
|
243
|
+
errors: [`Failed to write Mermaid file: ${err.message}`],
|
|
244
|
+
warnings: result.warnings,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export { convertToMermaid, convertToMermaidFile, generateMermaidCode, parseDrawioXML };
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/tools/drawio-convert/convert-to-mermaid.js
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { XMLParser } from "fast-xml-parser";
|
|
7
|
+
|
|
8
|
+
// src/tools/drawio-convert/diagram-utils.js
|
|
9
|
+
function validateDrawioXML(xml) {
|
|
10
|
+
const errors = [];
|
|
11
|
+
if (!xml.includes("<mxfile")) {
|
|
12
|
+
errors.push({
|
|
13
|
+
message: "Missing <mxfile> tag. Not a valid Draw.io file"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
if (!xml.includes("<mxGraphModel")) {
|
|
17
|
+
errors.push({
|
|
18
|
+
message: "Missing <mxGraphModel> tag. Not a valid Draw.io file"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
if (!xml.includes("<root>")) {
|
|
22
|
+
errors.push({
|
|
23
|
+
message: "Missing <root> tag. Not a valid Draw.io file"
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (!xml.startsWith("<?xml")) {
|
|
27
|
+
errors.push({
|
|
28
|
+
message: 'Missing XML declaration. File should start with <?xml version="1.0"'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
valid: errors.length === 0,
|
|
33
|
+
errors
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/tools/drawio-convert/convert-to-mermaid.js
|
|
38
|
+
function parseDrawioXML(drawioXML) {
|
|
39
|
+
const parser = new XMLParser({
|
|
40
|
+
ignoreAttributes: false,
|
|
41
|
+
attributeNamePrefix: "",
|
|
42
|
+
textNodeName: "_text"
|
|
43
|
+
});
|
|
44
|
+
const xml = parser.parse(drawioXML);
|
|
45
|
+
const diagram = xml.mxfile.diagram;
|
|
46
|
+
const graphModel = diagram.mxGraphModel;
|
|
47
|
+
const root = graphModel.root;
|
|
48
|
+
const graph = { nodes: /* @__PURE__ */ new Map(), edges: [] };
|
|
49
|
+
if (Array.isArray(root.mxCell)) {
|
|
50
|
+
root.mxCell.forEach((cell) => {
|
|
51
|
+
if (cell.id === "0" || cell.id === "1") return;
|
|
52
|
+
if (cell.vertex === "1" || cell.vertex === true) {
|
|
53
|
+
graph.nodes.set(cell.id, {
|
|
54
|
+
id: cell.id,
|
|
55
|
+
label: cell.value || cell.id,
|
|
56
|
+
style: cell.style || ""
|
|
57
|
+
});
|
|
58
|
+
} else if (cell.edge === "1" || cell.edge === true) {
|
|
59
|
+
graph.edges.push({
|
|
60
|
+
id: cell.id,
|
|
61
|
+
from: cell.source,
|
|
62
|
+
to: cell.target,
|
|
63
|
+
label: cell.value || ""
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
} else if (root.mxCell) {
|
|
68
|
+
const cell = root.mxCell;
|
|
69
|
+
if (cell.id !== "0" && cell.id !== "1") {
|
|
70
|
+
if (cell.vertex === "1" || cell.vertex === true) {
|
|
71
|
+
graph.nodes.set(cell.id, {
|
|
72
|
+
id: cell.id,
|
|
73
|
+
label: cell.value || cell.id,
|
|
74
|
+
style: cell.style || ""
|
|
75
|
+
});
|
|
76
|
+
} else if (cell.edge === "1" || cell.edge === true) {
|
|
77
|
+
graph.edges.push({
|
|
78
|
+
id: cell.id,
|
|
79
|
+
from: cell.source,
|
|
80
|
+
to: cell.target,
|
|
81
|
+
label: cell.value || ""
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return graph;
|
|
87
|
+
}
|
|
88
|
+
function generateMermaidCode(graph) {
|
|
89
|
+
const nodes = Array.from(graph.nodes.values());
|
|
90
|
+
const edges = graph.edges;
|
|
91
|
+
let mermaid = "graph TD\n";
|
|
92
|
+
edges.forEach((edge) => {
|
|
93
|
+
const fromNode = graph.nodes.get(edge.from);
|
|
94
|
+
const toNode = graph.nodes.get(edge.to);
|
|
95
|
+
if (!fromNode || !toNode) return;
|
|
96
|
+
const fromLabel = sanitizeNodeId(fromNode.label);
|
|
97
|
+
const toLabel = sanitizeNodeId(toNode.label);
|
|
98
|
+
const edgeLabel = edge.label ? `|"${edge.label}"|` : "";
|
|
99
|
+
mermaid += ` ${fromLabel}[${fromNode.label}] --> ${edgeLabel} ${toLabel}[${toNode.label}]
|
|
100
|
+
`;
|
|
101
|
+
});
|
|
102
|
+
nodes.forEach((node) => {
|
|
103
|
+
const hasEdge = edges.some((e) => e.from === node.id || e.to === node.id);
|
|
104
|
+
if (!hasEdge) {
|
|
105
|
+
const label = sanitizeNodeId(node.label);
|
|
106
|
+
mermaid += ` ${label}[${node.label}]
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return mermaid;
|
|
111
|
+
}
|
|
112
|
+
function sanitizeNodeId(label) {
|
|
113
|
+
return label.replace(/[^a-zA-Z0-9]/g, "_").replace(/^[0-9]/, "_$&");
|
|
114
|
+
}
|
|
115
|
+
function convertToMermaid(drawioPath) {
|
|
116
|
+
const errors = [];
|
|
117
|
+
const warnings = [];
|
|
118
|
+
if (!fs.existsSync(drawioPath)) {
|
|
119
|
+
return {
|
|
120
|
+
code: null,
|
|
121
|
+
stats: null,
|
|
122
|
+
errors: [`File not found: ${drawioPath}`],
|
|
123
|
+
warnings: []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
let drawioXML;
|
|
127
|
+
try {
|
|
128
|
+
drawioXML = fs.readFileSync(drawioPath, "utf-8");
|
|
129
|
+
} catch (err) {
|
|
130
|
+
return {
|
|
131
|
+
code: null,
|
|
132
|
+
stats: null,
|
|
133
|
+
errors: [`Failed to read file: ${err.message}`],
|
|
134
|
+
warnings: []
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const validation = validateDrawioXML(drawioXML);
|
|
138
|
+
if (!validation.valid) {
|
|
139
|
+
return {
|
|
140
|
+
code: null,
|
|
141
|
+
stats: null,
|
|
142
|
+
errors: validation.errors.map((e) => e.message),
|
|
143
|
+
warnings: []
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
let graph;
|
|
147
|
+
try {
|
|
148
|
+
graph = parseDrawioXML(drawioXML);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
return {
|
|
151
|
+
code: null,
|
|
152
|
+
stats: null,
|
|
153
|
+
errors: [`Failed to parse Draw.io XML: ${err.message}`],
|
|
154
|
+
warnings: []
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
let mermaidCode;
|
|
158
|
+
try {
|
|
159
|
+
mermaidCode = generateMermaidCode(graph);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
return {
|
|
162
|
+
code: null,
|
|
163
|
+
stats: null,
|
|
164
|
+
errors: [`Failed to generate Mermaid code: ${err.message}`],
|
|
165
|
+
warnings: []
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (graph.nodes.size === 0) {
|
|
169
|
+
warnings.push("No nodes found in diagram");
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
code: mermaidCode,
|
|
173
|
+
stats: {
|
|
174
|
+
nodes: graph.nodes.size,
|
|
175
|
+
edges: graph.edges.length
|
|
176
|
+
},
|
|
177
|
+
errors,
|
|
178
|
+
warnings
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function convertToMermaidFile(drawioPath, mermaidPath) {
|
|
182
|
+
const result = convertToMermaid(drawioPath);
|
|
183
|
+
if (result.errors.length > 0 || !result.code) {
|
|
184
|
+
return {
|
|
185
|
+
success: false,
|
|
186
|
+
errors: result.errors,
|
|
187
|
+
warnings: result.warnings
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const dir = path.dirname(mermaidPath);
|
|
192
|
+
if (!fs.existsSync(dir)) {
|
|
193
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
194
|
+
}
|
|
195
|
+
fs.writeFileSync(mermaidPath, result.code, "utf-8");
|
|
196
|
+
return {
|
|
197
|
+
success: true,
|
|
198
|
+
errors: [],
|
|
199
|
+
warnings: result.warnings,
|
|
200
|
+
stats: result.stats
|
|
201
|
+
};
|
|
202
|
+
} catch (err) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
errors: [`Failed to write Mermaid file: ${err.message}`],
|
|
206
|
+
warnings: result.warnings
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export {
|
|
211
|
+
convertToMermaid,
|
|
212
|
+
convertToMermaidFile,
|
|
213
|
+
generateMermaidCode,
|
|
214
|
+
parseDrawioXML
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=convert-to-mermaid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/tools/drawio-convert/convert-to-mermaid.js","../../../src/tools/drawio-convert/diagram-utils.js"],"sourcesContent":["/**\n * Draw.io → Mermaid Converter\n *\n * Converts Draw.io XML files to Mermaid format with error handling\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { XMLParser } from 'fast-xml-parser';\nimport { validateDrawioXML } from './diagram-utils.js';\n\n/**\n * Parse Draw.io XML to extract graph structure\n * @param {string} drawioXML - Draw.io XML content\n * @returns {Object} Parsed graph structure\n */\nfunction parseDrawioXML(drawioXML) {\n const parser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: '',\n textNodeName: '_text'\n });\n\n const xml = parser.parse(drawioXML);\n const diagram = xml.mxfile.diagram;\n const graphModel = diagram.mxGraphModel;\n const root = graphModel.root;\n\n const graph = { nodes: new Map(), edges: [] };\n\n if (Array.isArray(root.mxCell)) {\n root.mxCell.forEach(cell => {\n if (cell.id === '0' || cell.id === '1') return;\n\n if (cell.vertex === '1' || cell.vertex === true) {\n graph.nodes.set(cell.id, {\n id: cell.id,\n label: cell.value || cell.id,\n style: cell.style || ''\n });\n } else if (cell.edge === '1' || cell.edge === true) {\n graph.edges.push({\n id: cell.id,\n from: cell.source,\n to: cell.target,\n label: cell.value || ''\n });\n }\n });\n } else if (root.mxCell) {\n // Handle single cell case\n const cell = root.mxCell;\n if (cell.id !== '0' && cell.id !== '1') {\n if (cell.vertex === '1' || cell.vertex === true) {\n graph.nodes.set(cell.id, {\n id: cell.id,\n label: cell.value || cell.id,\n style: cell.style || ''\n });\n } else if (cell.edge === '1' || cell.edge === true) {\n graph.edges.push({\n id: cell.id,\n from: cell.source,\n to: cell.target,\n label: cell.value || ''\n });\n }\n }\n }\n\n return graph;\n}\n\n/**\n * Generate Mermaid code from graph structure\n * @param {Object} graph - Graph structure\n * @returns {string} Mermaid code\n */\nfunction generateMermaidCode(graph) {\n const nodes = Array.from(graph.nodes.values());\n const edges = graph.edges;\n\n let mermaid = 'graph TD\\n';\n\n edges.forEach((edge) => {\n const fromNode = graph.nodes.get(edge.from);\n const toNode = graph.nodes.get(edge.to);\n\n if (!fromNode || !toNode) return;\n\n const fromLabel = sanitizeNodeId(fromNode.label);\n const toLabel = sanitizeNodeId(toNode.label);\n const edgeLabel = edge.label ? `|\"${edge.label}\"|` : '';\n\n mermaid += ` ${fromLabel}[${fromNode.label}] --> ${edgeLabel} ${toLabel}[${toNode.label}]\\n`;\n });\n\n nodes.forEach((node) => {\n const hasEdge = edges.some(e => e.from === node.id || e.to === node.id);\n if (!hasEdge) {\n const label = sanitizeNodeId(node.label);\n mermaid += ` ${label}[${node.label}]\\n`;\n }\n });\n\n return mermaid;\n}\n\n/**\n * Sanitize node ID for Mermaid (remove special characters)\n * @param {string} label - Node label\n * @returns {string} Sanitized ID\n */\nfunction sanitizeNodeId(label) {\n return label\n .replace(/[^a-zA-Z0-9]/g, '_')\n .replace(/^[0-9]/, '_$&'); // Prefix numbers with underscore\n}\n\n/**\n * Convert Draw.io file to Mermaid format\n * @param {string} drawioPath - Path to .drawio file\n * @returns {Object} { code, stats, errors }\n */\nexport function convertToMermaid(drawioPath) {\n const errors = [];\n const warnings = [];\n\n // 1. Check file exists\n if (!fs.existsSync(drawioPath)) {\n return {\n code: null,\n stats: null,\n errors: [`File not found: ${drawioPath}`],\n warnings: [],\n };\n }\n\n // 2. Read file\n let drawioXML;\n try {\n drawioXML = fs.readFileSync(drawioPath, 'utf-8');\n } catch (err) {\n return {\n code: null,\n stats: null,\n errors: [`Failed to read file: ${err.message}`],\n warnings: [],\n };\n }\n\n // 3. Validate XML structure\n const validation = validateDrawioXML(drawioXML);\n if (!validation.valid) {\n return {\n code: null,\n stats: null,\n errors: validation.errors.map(e => e.message),\n warnings: [],\n };\n }\n\n // 4. Parse and convert\n let graph;\n try {\n graph = parseDrawioXML(drawioXML);\n } catch (err) {\n return {\n code: null,\n stats: null,\n errors: [`Failed to parse Draw.io XML: ${err.message}`],\n warnings: [],\n };\n }\n\n // 5. Generate Mermaid code\n let mermaidCode;\n try {\n mermaidCode = generateMermaidCode(graph);\n } catch (err) {\n return {\n code: null,\n stats: null,\n errors: [`Failed to generate Mermaid code: ${err.message}`],\n warnings: [],\n };\n }\n\n // 6. Check for conversion issues\n if (graph.nodes.size === 0) {\n warnings.push('No nodes found in diagram');\n }\n\n // 7. Return result\n return {\n code: mermaidCode,\n stats: {\n nodes: graph.nodes.size,\n edges: graph.edges.length,\n },\n errors,\n warnings,\n };\n}\n\n/**\n * Convert Draw.io to Mermaid and save to file\n * @param {string} drawioPath - Path to .drawio file\n * @param {string} mermaidPath - Path to save .mmd file\n * @returns {Object} { success, errors, warnings }\n */\nexport function convertToMermaidFile(drawioPath, mermaidPath) {\n const result = convertToMermaid(drawioPath);\n\n if (result.errors.length > 0 || !result.code) {\n return {\n success: false,\n errors: result.errors,\n warnings: result.warnings,\n };\n }\n\n try {\n // Ensure directory exists\n const dir = path.dirname(mermaidPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Write Mermaid file\n fs.writeFileSync(mermaidPath, result.code, '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 Mermaid file: ${err.message}`],\n warnings: result.warnings,\n };\n }\n}\n\nexport { parseDrawioXML, generateMermaidCode };\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":[]}
|