@likec4/generators 1.47.0 → 1.49.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/LICENSE +1 -1
- package/dist/index.d.mts +79 -0
- package/dist/index.mjs +928 -0
- package/package.json +20 -17
- package/src/d2/generate-d2.ts +1 -0
- package/src/drawio/generate-drawio.ts +219 -0
- package/src/drawio/index.ts +2 -0
- package/src/drawio/parse-drawio.ts +324 -0
- package/src/index.ts +2 -0
- package/src/mmd/generate-mmd.ts +3 -0
- package/src/puml/generate-puml.ts +3 -0
- package/src/react/generate-react-types.ts +2 -2
- package/dist/d2/generate-d2.d.ts +0 -3
- package/dist/d2/generate-d2.js +0 -87
- package/dist/d2/index.d.ts +0 -1
- package/dist/d2/index.js +0 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -7
- package/dist/mmd/generate-mmd.d.ts +0 -3
- package/dist/mmd/generate-mmd.js +0 -107
- package/dist/mmd/index.d.ts +0 -1
- package/dist/mmd/index.js +0 -1
- package/dist/model/generate-aux.d.ts +0 -4
- package/dist/model/generate-aux.js +0 -67
- package/dist/model/generate-likec4-model.d.ts +0 -4
- package/dist/model/generate-likec4-model.js +0 -22
- package/dist/model/generate-likec4.d.ts +0 -2
- package/dist/model/generate-likec4.js +0 -2
- package/dist/puml/generate-puml.d.ts +0 -3
- package/dist/puml/generate-puml.js +0 -170
- package/dist/puml/index.d.ts +0 -1
- package/dist/puml/index.js +0 -1
- package/dist/react/generate-react-types.d.ts +0 -4
- package/dist/react/generate-react-types.js +0 -64
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.js +0 -1
- package/dist/react-next/generate-react-next.d.ts +0 -20
- package/dist/react-next/generate-react-next.js +0 -102
- package/dist/react-next/index.d.ts +0 -1
- package/dist/react-next/index.js +0 -1
- package/dist/views-data-ts/generate-views-data.d.ts +0 -13
- package/dist/views-data-ts/generate-views-data.js +0 -130
- package/dist/views-data-ts/generateViewId.d.ts +0 -2
- package/dist/views-data-ts/generateViewId.js +0 -7
- package/dist/views-data-ts/index.d.ts +0 -1
- package/dist/views-data-ts/index.js +0 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
import { RichText, flattenMarkdownOrString } from "@likec4/core/types";
|
|
2
|
+
import { isEmptyish, isNullish, keys, map, pipe, values } from "remeda";
|
|
3
|
+
import { CompositeGeneratorNode, NL, expandToNode, joinToNode, toString } from "langium/generate";
|
|
4
|
+
import { nonexhaustive } from "@likec4/core";
|
|
5
|
+
import JSON5 from "json5";
|
|
6
|
+
import { compareNatural, invariant, sortNaturalByFqn } from "@likec4/core/utils";
|
|
7
|
+
const DEFAULT_ELEMENT_COLORS = {
|
|
8
|
+
primary: {
|
|
9
|
+
fill: "#3b82f6",
|
|
10
|
+
stroke: "#2563eb"
|
|
11
|
+
},
|
|
12
|
+
gray: {
|
|
13
|
+
fill: "#6b7280",
|
|
14
|
+
stroke: "#4b5563"
|
|
15
|
+
},
|
|
16
|
+
green: {
|
|
17
|
+
fill: "#22c55e",
|
|
18
|
+
stroke: "#16a34a"
|
|
19
|
+
},
|
|
20
|
+
red: {
|
|
21
|
+
fill: "#ef4444",
|
|
22
|
+
stroke: "#dc2626"
|
|
23
|
+
},
|
|
24
|
+
blue: {
|
|
25
|
+
fill: "#3b82f6",
|
|
26
|
+
stroke: "#2563eb"
|
|
27
|
+
},
|
|
28
|
+
indigo: {
|
|
29
|
+
fill: "#6366f1",
|
|
30
|
+
stroke: "#4f46e5"
|
|
31
|
+
},
|
|
32
|
+
muted: {
|
|
33
|
+
fill: "#9ca3af",
|
|
34
|
+
stroke: "#6b7280"
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const DEFAULT_EDGE_COLOR = "#6b7280";
|
|
38
|
+
function escapeXml(unsafe) {
|
|
39
|
+
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
40
|
+
}
|
|
41
|
+
function drawioShape(shape) {
|
|
42
|
+
switch (shape) {
|
|
43
|
+
case "person": return "shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;";
|
|
44
|
+
case "rectangle": return "shape=rectangle;";
|
|
45
|
+
case "browser": return "shape=rectangle;rounded=1;";
|
|
46
|
+
case "mobile": return "shape=rectangle;rounded=1;";
|
|
47
|
+
case "cylinder": return "shape=cylinder3;whiteSpace=wrap;boundedLbl=1;backgroundOutline=1;size=15;";
|
|
48
|
+
case "queue": return "shape=cylinder3;whiteSpace=wrap;boundedLbl=1;backgroundOutline=1;size=15;";
|
|
49
|
+
case "storage": return "shape=cylinder3;whiteSpace=wrap;boundedLbl=1;backgroundOutline=1;size=15;";
|
|
50
|
+
case "bucket": return "shape=rectangle;rounded=1;";
|
|
51
|
+
case "document": return "shape=document;whiteSpace=wrap;html=1;boundedLbl=1;";
|
|
52
|
+
case "component": return "shape=component;";
|
|
53
|
+
default: return "shape=rectangle;";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function getElementColors(viewmodel, color) {
|
|
57
|
+
const styles = "$styles" in viewmodel && viewmodel.$styles ? viewmodel.$styles : null;
|
|
58
|
+
if (styles) try {
|
|
59
|
+
const values = styles.colors(color);
|
|
60
|
+
return {
|
|
61
|
+
fill: values.elements.fill,
|
|
62
|
+
stroke: values.elements.stroke
|
|
63
|
+
};
|
|
64
|
+
} catch {}
|
|
65
|
+
return DEFAULT_ELEMENT_COLORS[color] ?? DEFAULT_ELEMENT_COLORS["primary"];
|
|
66
|
+
}
|
|
67
|
+
function getEdgeStrokeColor(viewmodel, color) {
|
|
68
|
+
const styles = "$styles" in viewmodel && viewmodel.$styles ? viewmodel.$styles : null;
|
|
69
|
+
if (styles) try {
|
|
70
|
+
return styles.colors(color).relationships.line;
|
|
71
|
+
} catch {}
|
|
72
|
+
return DEFAULT_EDGE_COLOR;
|
|
73
|
+
}
|
|
74
|
+
function generateDrawio(viewmodel) {
|
|
75
|
+
const view = viewmodel.$view;
|
|
76
|
+
const { nodes, edges } = view;
|
|
77
|
+
const rootId = "0";
|
|
78
|
+
const defaultParentId = "1";
|
|
79
|
+
const nodeIds = /* @__PURE__ */ new Map();
|
|
80
|
+
let cellId = 2;
|
|
81
|
+
const getCellId = (nodeId) => {
|
|
82
|
+
let id = nodeIds.get(nodeId);
|
|
83
|
+
if (!id) {
|
|
84
|
+
id = String(cellId++);
|
|
85
|
+
nodeIds.set(nodeId, id);
|
|
86
|
+
}
|
|
87
|
+
return id;
|
|
88
|
+
};
|
|
89
|
+
const vertexCells = [];
|
|
90
|
+
const edgeCells = [];
|
|
91
|
+
const sortedNodes = [...nodes].sort((a, b) => {
|
|
92
|
+
if (isNullish(a.parent) && isNullish(b.parent)) return 0;
|
|
93
|
+
if (isNullish(a.parent)) return -1;
|
|
94
|
+
if (isNullish(b.parent)) return 1;
|
|
95
|
+
if (a.parent === b.parent) return 0;
|
|
96
|
+
if (a.id.startsWith(b.id + ".")) return 1;
|
|
97
|
+
if (b.id.startsWith(a.id + ".")) return -1;
|
|
98
|
+
return 0;
|
|
99
|
+
});
|
|
100
|
+
const getBBox = (n) => {
|
|
101
|
+
const d = n;
|
|
102
|
+
return {
|
|
103
|
+
x: typeof d.x === "number" ? d.x : Array.isArray(d.position) ? d.position[0] : 0,
|
|
104
|
+
y: typeof d.y === "number" ? d.y : Array.isArray(d.position) ? d.position[1] : 0,
|
|
105
|
+
width: typeof d.width === "number" ? d.width : d.size?.width ?? 120,
|
|
106
|
+
height: typeof d.height === "number" ? d.height : d.size?.height ?? 60
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
for (const node of sortedNodes) {
|
|
110
|
+
const id = getCellId(node.id);
|
|
111
|
+
const parentId = node.parent ? getCellId(node.parent) : defaultParentId;
|
|
112
|
+
const label = escapeXml(node.title);
|
|
113
|
+
const shapeStyle = drawioShape(node.shape);
|
|
114
|
+
const { x, y, width, height } = getBBox(node);
|
|
115
|
+
const elemColors = getElementColors(viewmodel, node.color);
|
|
116
|
+
const colorStyle = elemColors != null ? `fillColor=${elemColors.fill};strokeColor=${elemColors.stroke};fontColor=${elemColors.stroke};` : "";
|
|
117
|
+
const description = node.description && flattenMarkdownOrString(node.description);
|
|
118
|
+
const desc = isEmptyish(description) ? "" : escapeXml(description);
|
|
119
|
+
const technology = node.technology && flattenMarkdownOrString(node.technology);
|
|
120
|
+
const tech = isEmptyish(technology) ? "" : escapeXml(technology);
|
|
121
|
+
const userData = desc !== "" || tech !== "" ? `<mxUserObject><data key="likec4Description">${desc}</data><data key="likec4Technology">${tech}</data></mxUserObject>\n ` : "";
|
|
122
|
+
vertexCells.push(`<mxCell id="${id}" value="${label}" style="${shapeStyle}${colorStyle}verticalAlign=middle;align=center;overflow=fill;spacingLeft=2;spacingRight=2;spacingTop=2;spacingBottom=2;" vertex="1" parent="${parentId}">
|
|
123
|
+
${userData}<mxGeometry x="${Math.round(x)}" y="${Math.round(y)}" width="${Math.round(width)}" height="${Math.round(height)}" as="geometry" />
|
|
124
|
+
</mxCell>`);
|
|
125
|
+
}
|
|
126
|
+
for (const edge of edges) {
|
|
127
|
+
const id = String(cellId++);
|
|
128
|
+
const sourceId = getCellId(edge.source);
|
|
129
|
+
const targetId = getCellId(edge.target);
|
|
130
|
+
const label = edge.label ? escapeXml(edge.label) : "";
|
|
131
|
+
const strokeColor = getEdgeStrokeColor(viewmodel, edge.color);
|
|
132
|
+
const dashStyle = edge.line === "dashed" ? "dashed=1;" : edge.line === "dotted" ? "dashed=1;dashPattern=1 1;" : "";
|
|
133
|
+
edgeCells.push(`<mxCell id="${id}" value="${label}" style="endArrow=block;html=1;rounded=0;exitX=1;exitY=0.5;entryX=0;entryY=0.5;strokeColor=${strokeColor};${dashStyle}" edge="1" parent="${defaultParentId}" source="${sourceId}" target="${targetId}">
|
|
134
|
+
<mxGeometry relative="1" as="geometry" />
|
|
135
|
+
</mxCell>`);
|
|
136
|
+
}
|
|
137
|
+
let bounds = {
|
|
138
|
+
x: 0,
|
|
139
|
+
y: 0,
|
|
140
|
+
width: 800,
|
|
141
|
+
height: 600
|
|
142
|
+
};
|
|
143
|
+
try {
|
|
144
|
+
const b = viewmodel.bounds;
|
|
145
|
+
if (b != null && typeof b.x === "number") bounds = b;
|
|
146
|
+
} catch {}
|
|
147
|
+
const allCells = [
|
|
148
|
+
`<mxCell id="${defaultParentId}" vertex="1" parent="${rootId}">
|
|
149
|
+
<mxGeometry x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}" as="geometry" />
|
|
150
|
+
</mxCell>`,
|
|
151
|
+
...vertexCells,
|
|
152
|
+
...edgeCells
|
|
153
|
+
].join("\n");
|
|
154
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
155
|
+
<mxfile host="LikeC4" modified="${(/* @__PURE__ */ new Date()).toISOString()}" agent="LikeC4" version="1.0" etag="" type="device">
|
|
156
|
+
<diagram name="${escapeXml(view.id)}" id="likec4-${escapeXml(view.id)}">
|
|
157
|
+
<mxGraphModel dx="800" dy="800" 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
|
+
<mxCell id="${rootId}" />
|
|
160
|
+
${allCells}
|
|
161
|
+
</root>
|
|
162
|
+
</mxGraphModel>
|
|
163
|
+
</diagram>
|
|
164
|
+
</mxfile>
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
const capitalizeFirstLetter$2 = (value) => value.charAt(0).toLocaleUpperCase() + value.slice(1);
|
|
168
|
+
const fqnName$2 = (nodeId) => nodeId.split(".").map(capitalizeFirstLetter$2).join("");
|
|
169
|
+
const nodeName$2 = (node) => {
|
|
170
|
+
return fqnName$2(node.parent ? node.id.slice(node.parent.length + 1) : node.id);
|
|
171
|
+
};
|
|
172
|
+
const d2direction = ({ autoLayout }) => {
|
|
173
|
+
switch (autoLayout.direction) {
|
|
174
|
+
case "TB": return "down";
|
|
175
|
+
case "BT": return "up";
|
|
176
|
+
case "LR": return "right";
|
|
177
|
+
case "RL": return "left";
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const d2shape = ({ shape }) => {
|
|
181
|
+
switch (shape) {
|
|
182
|
+
case "queue":
|
|
183
|
+
case "cylinder":
|
|
184
|
+
case "rectangle":
|
|
185
|
+
case "document": return shape;
|
|
186
|
+
case "person": return "c4-person";
|
|
187
|
+
case "storage": return "stored_data";
|
|
188
|
+
case "component":
|
|
189
|
+
case "bucket":
|
|
190
|
+
case "mobile":
|
|
191
|
+
case "browser": return "rectangle";
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
function generateD2(viewmodel) {
|
|
195
|
+
const view = viewmodel.$view;
|
|
196
|
+
const { nodes, edges } = view;
|
|
197
|
+
const names = /* @__PURE__ */ new Map();
|
|
198
|
+
const printNode = (node, parentName) => {
|
|
199
|
+
const name = nodeName$2(node);
|
|
200
|
+
const fqnName = (parentName ? parentName + "." : "") + name;
|
|
201
|
+
names.set(node.id, fqnName);
|
|
202
|
+
const label = JSON.stringify(node.title);
|
|
203
|
+
const shape = d2shape(node);
|
|
204
|
+
return new CompositeGeneratorNode().append(name, ": {", NL).indent({
|
|
205
|
+
indentedChildren: (indent) => indent.append("label: ", label, NL).appendIf(shape !== "rectangle", "shape: ", shape, NL).appendIf(node.children.length > 0, NL, joinToNode(nodes.filter((n) => n.parent === node.id), (n) => printNode(n, fqnName))),
|
|
206
|
+
indentation: 2
|
|
207
|
+
}).append("}", NL);
|
|
208
|
+
};
|
|
209
|
+
const printEdge = (edge) => {
|
|
210
|
+
return new CompositeGeneratorNode().append(names.get(edge.source), " -> ", names.get(edge.target)).append((out) => edge.label && out.append(": ", JSON.stringify(edge.label)));
|
|
211
|
+
};
|
|
212
|
+
return toString(new CompositeGeneratorNode().append("direction: ", d2direction(view), NL, NL).append(joinToNode(nodes.filter((n) => isNullish(n.parent)), (n) => printNode(n), { appendNewLineIfNotEmpty: true })).appendIf(edges.length > 0, NL, joinToNode(edges, (e) => printEdge(e), { appendNewLineIfNotEmpty: true })));
|
|
213
|
+
}
|
|
214
|
+
const capitalizeFirstLetter$1 = (value) => value.charAt(0).toLocaleUpperCase() + value.slice(1);
|
|
215
|
+
const fqnName$1 = (nodeId) => nodeId.split(".").map(capitalizeFirstLetter$1).join("");
|
|
216
|
+
const nodeName$1 = (node) => {
|
|
217
|
+
return fqnName$1(node.parent ? node.id.slice(node.parent.length + 1) : node.id);
|
|
218
|
+
};
|
|
219
|
+
const toSingleQuotes = (str) => str.replace(/\\?"/g, `'`);
|
|
220
|
+
const mmdshape = ({ shape, title }) => {
|
|
221
|
+
const label = `label: ${JSON.stringify(title)}`;
|
|
222
|
+
switch (shape) {
|
|
223
|
+
case "queue": return `@{ shape: horizontal-cylinder, ${label} }`;
|
|
224
|
+
case "person": return `@{ icon: "fa:user", shape: rounded, ${label} }`;
|
|
225
|
+
case "storage": return `@{ shape: disk, ${label} }`;
|
|
226
|
+
case "cylinder": return `@{ shape: cylinder, ${label} }`;
|
|
227
|
+
case "mobile":
|
|
228
|
+
case "browser": return `@{ shape: rounded, ${label} }`;
|
|
229
|
+
case "bucket": return `@{ shape: trap-t, ${label} }`;
|
|
230
|
+
case "rectangle": return `@{ shape: rectangle, ${label} }`;
|
|
231
|
+
case "document": return `@{ shape: doc, ${label} }`;
|
|
232
|
+
case "component": return `@{ shape: rectangle, ${label} }`;
|
|
233
|
+
default: nonexhaustive(shape);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
function generateMermaid(viewmodel) {
|
|
237
|
+
const view = viewmodel.$view;
|
|
238
|
+
const { nodes, edges } = view;
|
|
239
|
+
const names = /* @__PURE__ */ new Map();
|
|
240
|
+
const printNode = (node, parentName) => {
|
|
241
|
+
const name = nodeName$1(node);
|
|
242
|
+
const fqnName = (parentName ? parentName + "." : "") + name;
|
|
243
|
+
names.set(node.id, fqnName);
|
|
244
|
+
const baseNode = new CompositeGeneratorNode();
|
|
245
|
+
if (node.children.length > 0) {
|
|
246
|
+
const label = toSingleQuotes(node.title);
|
|
247
|
+
baseNode.append("subgraph ", fqnName, "[\"`", label, "`\"]", NL).indent({
|
|
248
|
+
indentedChildren: [joinToNode(nodes.filter((n) => n.parent === node.id), (n) => printNode(n, fqnName), { appendNewLineIfNotEmpty: true })],
|
|
249
|
+
indentation: 2
|
|
250
|
+
}).append("end", NL);
|
|
251
|
+
} else baseNode.append(fqnName, mmdshape(node));
|
|
252
|
+
return baseNode;
|
|
253
|
+
};
|
|
254
|
+
const printEdge = (edge) => {
|
|
255
|
+
return new CompositeGeneratorNode().append(names.get(edge.source), " -.", edge.label ? " \"`" + toSingleQuotes(edge.label) + "`\" .-" : "-", "> ", names.get(edge.target));
|
|
256
|
+
};
|
|
257
|
+
return toString(new CompositeGeneratorNode().append("---", NL, `title: ${JSON.stringify(toSingleQuotes(viewmodel.titleOrId))}`, NL, "---", NL).append("graph ", view.autoLayout.direction, NL).indent({
|
|
258
|
+
indentedChildren: (indent) => {
|
|
259
|
+
indent.append(joinToNode(nodes.filter((n) => isNullish(n.parent)), (n) => printNode(n), { appendNewLineIfNotEmpty: true })).appendIf(edges.length > 0, joinToNode(edges, (e) => printEdge(e), { appendNewLineIfNotEmpty: true }));
|
|
260
|
+
},
|
|
261
|
+
indentation: 2
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
function getAttr(attrs, name) {
|
|
265
|
+
const re = new RegExp(`${name}="([^"]*)"`, "i");
|
|
266
|
+
const m = attrs.match(re);
|
|
267
|
+
return m ? m[1] : void 0;
|
|
268
|
+
}
|
|
269
|
+
function parseNum(s) {
|
|
270
|
+
if (s === void 0 || s === "") return void 0;
|
|
271
|
+
const n = Number.parseFloat(s);
|
|
272
|
+
return Number.isNaN(n) ? void 0 : n;
|
|
273
|
+
}
|
|
274
|
+
function parseStyle(style) {
|
|
275
|
+
const map = /* @__PURE__ */ new Map();
|
|
276
|
+
if (!style) return map;
|
|
277
|
+
for (const part of style.split(";")) {
|
|
278
|
+
const eq = part.indexOf("=");
|
|
279
|
+
if (eq > 0) {
|
|
280
|
+
const k = part.slice(0, eq).trim();
|
|
281
|
+
const v = part.slice(eq + 1).trim();
|
|
282
|
+
if (k && v) map.set(k.toLowerCase(), v);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return map;
|
|
286
|
+
}
|
|
287
|
+
function parseUserData(fullTag) {
|
|
288
|
+
const out = {};
|
|
289
|
+
const descMatch = fullTag.match(/<data\s+key="likec4Description"[^>]*>([\s\S]*?)<\/data>/i);
|
|
290
|
+
if (descMatch?.[1]) out.description = decodeXmlEntities(descMatch[1].trim());
|
|
291
|
+
const techMatch = fullTag.match(/<data\s+key="likec4Technology"[^>]*>([\s\S]*?)<\/data>/i);
|
|
292
|
+
if (techMatch?.[1]) out.technology = decodeXmlEntities(techMatch[1].trim());
|
|
293
|
+
return out;
|
|
294
|
+
}
|
|
295
|
+
function parseDrawioXml(xml) {
|
|
296
|
+
const cells = [];
|
|
297
|
+
const mxCellRe = /<mxCell\s+([^>]+?)(?:\s*\/>|>([\s\S]*?)<\/mxCell>)/gi;
|
|
298
|
+
const geomAttr = (tag, name) => getAttr(tag, name);
|
|
299
|
+
let m;
|
|
300
|
+
while ((m = mxCellRe.exec(xml)) !== null) {
|
|
301
|
+
const attrs = m[1] ?? "";
|
|
302
|
+
const inner = m[2] ?? "";
|
|
303
|
+
const id = getAttr(attrs, "id");
|
|
304
|
+
if (!id) continue;
|
|
305
|
+
const valueRaw = getAttr(attrs, "value");
|
|
306
|
+
const parent = getAttr(attrs, "parent");
|
|
307
|
+
const source = getAttr(attrs, "source");
|
|
308
|
+
const target = getAttr(attrs, "target");
|
|
309
|
+
const vertex = getAttr(attrs, "vertex") === "1";
|
|
310
|
+
const edge = getAttr(attrs, "edge") === "1";
|
|
311
|
+
const style = getAttr(attrs, "style");
|
|
312
|
+
const geomMatch = m[0].match(/<mxGeometry[^>]*>/i);
|
|
313
|
+
const geomStr = geomMatch ? geomMatch[0] : "";
|
|
314
|
+
const styleMap = parseStyle(style ?? void 0);
|
|
315
|
+
const userData = parseUserData(inner);
|
|
316
|
+
const x = parseNum(geomAttr(geomStr, "x"));
|
|
317
|
+
const y = parseNum(geomAttr(geomStr, "y"));
|
|
318
|
+
const width = parseNum(geomAttr(geomStr, "width"));
|
|
319
|
+
const height = parseNum(geomAttr(geomStr, "height"));
|
|
320
|
+
const fillColor = styleMap.get("fillcolor") ?? styleMap.get("fillColor");
|
|
321
|
+
const strokeColor = styleMap.get("strokecolor") ?? styleMap.get("strokeColor");
|
|
322
|
+
const cell = {
|
|
323
|
+
id,
|
|
324
|
+
...valueRaw != null && valueRaw !== "" ? { value: decodeXmlEntities(valueRaw) } : {},
|
|
325
|
+
...parent != null && parent !== "" ? { parent } : {},
|
|
326
|
+
...source != null && source !== "" ? { source } : {},
|
|
327
|
+
...target != null && target !== "" ? { target } : {},
|
|
328
|
+
vertex,
|
|
329
|
+
edge,
|
|
330
|
+
...style != null && style !== "" ? { style } : {},
|
|
331
|
+
...x !== void 0 ? { x } : {},
|
|
332
|
+
...y !== void 0 ? { y } : {},
|
|
333
|
+
...width !== void 0 ? { width } : {},
|
|
334
|
+
...height !== void 0 ? { height } : {},
|
|
335
|
+
...fillColor !== void 0 ? { fillColor } : {},
|
|
336
|
+
...strokeColor !== void 0 ? { strokeColor } : {},
|
|
337
|
+
...userData.description != null ? { description: userData.description } : {},
|
|
338
|
+
...userData.technology != null ? { technology: userData.technology } : {}
|
|
339
|
+
};
|
|
340
|
+
cells.push(cell);
|
|
341
|
+
}
|
|
342
|
+
return cells;
|
|
343
|
+
}
|
|
344
|
+
function decodeXmlEntities(s) {
|
|
345
|
+
return s.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'").replace(/&/g, "&");
|
|
346
|
+
}
|
|
347
|
+
function inferKind(style) {
|
|
348
|
+
if (!style) return "container";
|
|
349
|
+
const s = style.toLowerCase();
|
|
350
|
+
if (s.includes("umlactor") || s.includes("shape=person")) return "actor";
|
|
351
|
+
if (s.includes("swimlane") || s.includes("shape=rectangle") && s.includes("rounded")) return "system";
|
|
352
|
+
return "container";
|
|
353
|
+
}
|
|
354
|
+
function toId(name) {
|
|
355
|
+
return name.trim().replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_.-]/g, "").replace(/^[0-9]/, "_$&") || "element";
|
|
356
|
+
}
|
|
357
|
+
function parseDrawioToLikeC4(xml) {
|
|
358
|
+
const cells = parseDrawioXml(xml);
|
|
359
|
+
const byId = /* @__PURE__ */ new Map();
|
|
360
|
+
for (const c of cells) byId.set(c.id, c);
|
|
361
|
+
const vertices = cells.filter((c) => c.vertex && c.id !== "1");
|
|
362
|
+
const edges = cells.filter((c) => c.edge && c.source && c.target);
|
|
363
|
+
const rootId = "1";
|
|
364
|
+
const idToFqn = /* @__PURE__ */ new Map();
|
|
365
|
+
const idToCell = /* @__PURE__ */ new Map();
|
|
366
|
+
for (const v of vertices) idToCell.set(v.id, v);
|
|
367
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
368
|
+
function uniqueName(base) {
|
|
369
|
+
let name = toId(base || "element");
|
|
370
|
+
let n = name;
|
|
371
|
+
let i = 0;
|
|
372
|
+
while (usedNames.has(n)) n = `${name}_${++i}`;
|
|
373
|
+
usedNames.add(n);
|
|
374
|
+
return n;
|
|
375
|
+
}
|
|
376
|
+
for (const v of vertices) if (v.parent === rootId || !v.parent) {
|
|
377
|
+
const name = uniqueName(v.value ?? v.id);
|
|
378
|
+
idToFqn.set(v.id, name);
|
|
379
|
+
}
|
|
380
|
+
let changed = true;
|
|
381
|
+
while (changed) {
|
|
382
|
+
changed = false;
|
|
383
|
+
for (const v of vertices) {
|
|
384
|
+
if (idToFqn.has(v.id)) continue;
|
|
385
|
+
const parent = v.parent ? idToFqn.get(v.parent) : null;
|
|
386
|
+
if (parent != null) {
|
|
387
|
+
const local = uniqueName(v.value ?? v.id);
|
|
388
|
+
idToFqn.set(v.id, `${parent}.${local}`);
|
|
389
|
+
changed = true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
for (const v of vertices) if (!idToFqn.has(v.id)) idToFqn.set(v.id, uniqueName(v.value ?? v.id));
|
|
394
|
+
const hexToCustomName = /* @__PURE__ */ new Map();
|
|
395
|
+
let customColorIndex = 0;
|
|
396
|
+
for (const v of vertices) {
|
|
397
|
+
const fill = v.fillColor?.trim();
|
|
398
|
+
if (fill && /^#[0-9A-Fa-f]{3,8}$/.test(fill)) {
|
|
399
|
+
if (!hexToCustomName.has(fill)) hexToCustomName.set(fill, `drawio_color_${++customColorIndex}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const lines = [];
|
|
403
|
+
if (hexToCustomName.size > 0) {
|
|
404
|
+
lines.push("specification {");
|
|
405
|
+
for (const [hex, name] of hexToCustomName) lines.push(` color ${name} ${hex}`);
|
|
406
|
+
lines.push("}");
|
|
407
|
+
lines.push("");
|
|
408
|
+
}
|
|
409
|
+
lines.push("model {");
|
|
410
|
+
lines.push("");
|
|
411
|
+
const children = /* @__PURE__ */ new Map();
|
|
412
|
+
const roots = [];
|
|
413
|
+
for (const [cellId, fqn] of idToFqn) {
|
|
414
|
+
const cell = idToCell.get(cellId);
|
|
415
|
+
if (!cell) continue;
|
|
416
|
+
if (cell.parent === rootId || !cell.parent) roots.push({
|
|
417
|
+
cellId,
|
|
418
|
+
fqn
|
|
419
|
+
});
|
|
420
|
+
else {
|
|
421
|
+
const parentFqn = idToFqn.get(cell.parent);
|
|
422
|
+
if (parentFqn != null) {
|
|
423
|
+
const list = children.get(parentFqn) ?? [];
|
|
424
|
+
list.push({
|
|
425
|
+
cellId,
|
|
426
|
+
fqn
|
|
427
|
+
});
|
|
428
|
+
children.set(parentFqn, list);
|
|
429
|
+
} else roots.push({
|
|
430
|
+
cellId,
|
|
431
|
+
fqn
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function emitElement(cellId, fqn, indent) {
|
|
436
|
+
const cell = idToCell.get(cellId);
|
|
437
|
+
if (!cell) return;
|
|
438
|
+
const kind = inferKind(cell.style);
|
|
439
|
+
const title = cell.value && cell.value.trim() || fqn.split(".").pop() || "Element";
|
|
440
|
+
const name = fqn.split(".").pop();
|
|
441
|
+
const pad = " ".repeat(indent);
|
|
442
|
+
const desc = cell.description?.trim();
|
|
443
|
+
const tech = cell.technology?.trim();
|
|
444
|
+
const colorName = cell.fillColor && /^#[0-9A-Fa-f]{3,8}$/.test(cell.fillColor.trim()) ? hexToCustomName.get(cell.fillColor.trim()) : void 0;
|
|
445
|
+
if (kind === "actor") lines.push(`${pad}${name} = actor '${title.replace(/'/g, "''")}'`);
|
|
446
|
+
else if (kind === "system") lines.push(`${pad}${name} = system '${title.replace(/'/g, "''")}'`);
|
|
447
|
+
else lines.push(`${pad}${name} = container '${title.replace(/'/g, "''")}'`);
|
|
448
|
+
const childList = children.get(fqn);
|
|
449
|
+
if (childList && childList.length > 0 || desc || tech || colorName) {
|
|
450
|
+
lines.push(`${pad}{`);
|
|
451
|
+
if (colorName) lines.push(`${pad} style { color ${colorName} }`);
|
|
452
|
+
if (desc) lines.push(`${pad} description '${desc.replace(/'/g, "''")}'`);
|
|
453
|
+
if (tech) lines.push(`${pad} technology '${tech.replace(/'/g, "''")}'`);
|
|
454
|
+
if (childList && childList.length > 0) for (const ch of childList) emitElement(ch.cellId, ch.fqn, indent + 1);
|
|
455
|
+
lines.push(`${pad}}`);
|
|
456
|
+
} else {
|
|
457
|
+
lines.push(`${pad}{`);
|
|
458
|
+
lines.push(`${pad}}`);
|
|
459
|
+
}
|
|
460
|
+
lines.push("");
|
|
461
|
+
}
|
|
462
|
+
for (const { cellId, fqn } of roots) emitElement(cellId, fqn, 1);
|
|
463
|
+
for (const e of edges) {
|
|
464
|
+
const src = idToFqn.get(e.source);
|
|
465
|
+
const tgt = idToFqn.get(e.target);
|
|
466
|
+
if (!src || !tgt) continue;
|
|
467
|
+
const label = e.value && e.value.trim() ? ` '${e.value.replace(/'/g, "''")}'` : "";
|
|
468
|
+
lines.push(` ${src} -> ${tgt}${label}`);
|
|
469
|
+
}
|
|
470
|
+
lines.push("}");
|
|
471
|
+
lines.push("");
|
|
472
|
+
lines.push("views {");
|
|
473
|
+
lines.push(" view index {");
|
|
474
|
+
lines.push(" include *");
|
|
475
|
+
lines.push(" }");
|
|
476
|
+
lines.push("}");
|
|
477
|
+
lines.push("");
|
|
478
|
+
return lines.join("\n");
|
|
479
|
+
}
|
|
480
|
+
function toUnion(elements) {
|
|
481
|
+
if (elements.length === 0) return "never";
|
|
482
|
+
return elements.sort(compareNatural).map((v) => ` | ${JSON.stringify(v)}`).join("\n").trimStart();
|
|
483
|
+
}
|
|
484
|
+
function elementIdToUnion(_elements) {
|
|
485
|
+
const elements = values(_elements);
|
|
486
|
+
if (elements.length === 0) return "never";
|
|
487
|
+
return pipe(elements, sortNaturalByFqn, map((v) => ` | ${JSON.stringify(v.id)}`)).join("\n").trimStart();
|
|
488
|
+
}
|
|
489
|
+
function generateAux(model, options = {}) {
|
|
490
|
+
const { useCorePackage = false } = options;
|
|
491
|
+
return `
|
|
492
|
+
import type { Aux, SpecAux } from '${useCorePackage ? "@likec4/core/types" : "likec4/model"}';
|
|
493
|
+
|
|
494
|
+
export type $Specs = SpecAux<
|
|
495
|
+
// Element kinds
|
|
496
|
+
${toUnion(keys(model.specification.elements))},
|
|
497
|
+
// Deployment kinds
|
|
498
|
+
${toUnion(keys(model.specification.deployments ?? {}))},
|
|
499
|
+
// Relationship kinds
|
|
500
|
+
${toUnion(keys(model.specification.relationships ?? {}))},
|
|
501
|
+
// Tags
|
|
502
|
+
${toUnion(keys(model.specification.tags ?? {}))},
|
|
503
|
+
// Metadata keys
|
|
504
|
+
${toUnion(model.specification.metadataKeys ?? [])}
|
|
505
|
+
>
|
|
506
|
+
|
|
507
|
+
export type $Aux = Aux<
|
|
508
|
+
${JSON.stringify(model.stage)},
|
|
509
|
+
// Elements
|
|
510
|
+
${elementIdToUnion(model.$data.elements)},
|
|
511
|
+
// Deployments
|
|
512
|
+
${elementIdToUnion(model.$data.deployments.elements)},
|
|
513
|
+
// Views
|
|
514
|
+
${toUnion(keys(model.$data.views))},
|
|
515
|
+
// Project ID
|
|
516
|
+
${JSON.stringify(model.projectId)},
|
|
517
|
+
$Specs
|
|
518
|
+
>
|
|
519
|
+
|
|
520
|
+
export type $ElementId = $Aux['ElementId']
|
|
521
|
+
export type $DeploymentId = $Aux['DeploymentId']
|
|
522
|
+
export type $ViewId = $Aux['ViewId']
|
|
523
|
+
|
|
524
|
+
export type $ElementKind = $Aux['ElementKind']
|
|
525
|
+
export type $RelationKind = $Aux['RelationKind']
|
|
526
|
+
export type $DeploymentKind = $Aux['DeploymentKind']
|
|
527
|
+
export type $Tag = $Aux['Tag']
|
|
528
|
+
export type $Tags = readonly $Aux['Tag'][]
|
|
529
|
+
export type $MetadataKey = $Aux['MetadataKey']
|
|
530
|
+
`.trimStart();
|
|
531
|
+
}
|
|
532
|
+
function generateLikeC4Model(model, options = {}) {
|
|
533
|
+
const aux = generateAux(model, options);
|
|
534
|
+
const { useCorePackage = false } = options;
|
|
535
|
+
return `
|
|
536
|
+
/* prettier-ignore-start */
|
|
537
|
+
/* eslint-disable */
|
|
538
|
+
|
|
539
|
+
/******************************************************************************
|
|
540
|
+
* This file was generated
|
|
541
|
+
* DO NOT EDIT MANUALLY!
|
|
542
|
+
******************************************************************************/
|
|
543
|
+
|
|
544
|
+
import { LikeC4Model } from '${useCorePackage ? "@likec4/core" : "likec4"}/model'
|
|
545
|
+
${aux}
|
|
546
|
+
|
|
547
|
+
export const likec4model: LikeC4Model<$Aux> = new LikeC4Model(${JSON5.stringify(model.$data, {
|
|
548
|
+
space: 2,
|
|
549
|
+
quote: "'"
|
|
550
|
+
})} as any) as any
|
|
551
|
+
|
|
552
|
+
/* prettier-ignore-end */
|
|
553
|
+
`.trimStart();
|
|
554
|
+
}
|
|
555
|
+
const capitalizeFirstLetter = (value) => value.charAt(0).toLocaleUpperCase() + value.slice(1);
|
|
556
|
+
const fqnName = (nodeId) => {
|
|
557
|
+
return nodeId.split(/[.-]/).map(capitalizeFirstLetter).join("");
|
|
558
|
+
};
|
|
559
|
+
const nodeName = (node) => {
|
|
560
|
+
return fqnName(node.parent ? node.id.slice(node.parent.length + 1) : node.id);
|
|
561
|
+
};
|
|
562
|
+
const pumlColor = (color, customColorProvider, defaultColor = "#3b82f6") => {
|
|
563
|
+
if (color) return customColorProvider(color) ?? defaultColor;
|
|
564
|
+
return defaultColor;
|
|
565
|
+
};
|
|
566
|
+
const pumlDirection = ({ autoLayout }) => {
|
|
567
|
+
switch (autoLayout.direction) {
|
|
568
|
+
case "TB": return "top to bottom";
|
|
569
|
+
case "BT":
|
|
570
|
+
console.warn("Bottom to top direction is not supported. Defaulting to top to bottom.");
|
|
571
|
+
return "top to bottom";
|
|
572
|
+
case "LR": return "left to right";
|
|
573
|
+
case "RL":
|
|
574
|
+
console.warn("Right to left direction is not supported. Defaulting to left to right.");
|
|
575
|
+
return "left to right";
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
const pumlShape = ({ shape }) => {
|
|
579
|
+
switch (shape) {
|
|
580
|
+
case "queue":
|
|
581
|
+
case "rectangle":
|
|
582
|
+
case "person": return shape;
|
|
583
|
+
case "storage":
|
|
584
|
+
case "cylinder": return "database";
|
|
585
|
+
case "component": return "component";
|
|
586
|
+
case "document":
|
|
587
|
+
case "mobile":
|
|
588
|
+
case "bucket":
|
|
589
|
+
case "browser": return "rectangle";
|
|
590
|
+
default: nonexhaustive(shape);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
const escapeLabel = (label) => isEmptyish(label) ? null : JSON.stringify(label).slice(1, -1).replace(/\\"/g, "\"");
|
|
594
|
+
function generatePuml(viewmodel) {
|
|
595
|
+
const view = viewmodel.$view;
|
|
596
|
+
const colors = viewmodel.$model.$styles.theme.colors;
|
|
597
|
+
const { nodes, edges } = view;
|
|
598
|
+
const elemntColorProvider = (key) => (colorKey) => colorKey in colors ? colors[colorKey].elements[key] : void 0;
|
|
599
|
+
const relationshipsColorProvider = (key) => (colorKey) => colorKey in colors ? colors[colorKey].relationships[key] : void 0;
|
|
600
|
+
const names = /* @__PURE__ */ new Map();
|
|
601
|
+
const printHeader = () => {
|
|
602
|
+
return new CompositeGeneratorNode().append("title \"", viewmodel.titleOrId, "\"", NL).append(pumlDirection(view), " direction", NL);
|
|
603
|
+
};
|
|
604
|
+
const printTheme = () => {
|
|
605
|
+
return new CompositeGeneratorNode().append("hide stereotype", NL).append("skinparam ranksep ", "60", NL).append("skinparam nodesep ", "30", NL).append("skinparam {", NL).indent({
|
|
606
|
+
indentedChildren: (indent) => indent.append("arrowFontSize ", "10", NL).append("defaultTextAlignment ", "center", NL).append("wrapWidth ", "200", NL).append("maxMessageSize ", "100", NL).append("shadowing ", "false", NL),
|
|
607
|
+
indentation: 2
|
|
608
|
+
}).append("}", NL);
|
|
609
|
+
};
|
|
610
|
+
const printStereotypes = (node) => {
|
|
611
|
+
const shape = pumlShape(node);
|
|
612
|
+
const fqn = fqnName(node.id);
|
|
613
|
+
return new CompositeGeneratorNode().append("skinparam ", shape, "<<", fqn, ">>", "{", NL).indent({
|
|
614
|
+
indentedChildren: (indent) => indent.append("BackgroundColor ", pumlColor(node.color, elemntColorProvider("fill")), NL).append("FontColor ", pumlColor(node.color, elemntColorProvider("hiContrast"), "#FFFFFF"), NL).append("BorderColor ", pumlColor(node.color, elemntColorProvider("stroke")), NL),
|
|
615
|
+
indentation: 2
|
|
616
|
+
}).append("}", NL);
|
|
617
|
+
};
|
|
618
|
+
const printNode = (node) => {
|
|
619
|
+
const shape = pumlShape(node);
|
|
620
|
+
const fqn = fqnName(node.id);
|
|
621
|
+
const label = escapeLabel(node.title) || nodeName(node);
|
|
622
|
+
const tech = escapeLabel(node.technology);
|
|
623
|
+
names.set(node.id, fqn);
|
|
624
|
+
const description = RichText.from(node.description);
|
|
625
|
+
return new CompositeGeneratorNode().append(shape, " ").append("\"").append("==", label).appendIf(!!tech, `\\n<size:10>[`, tech, "]</size>").appendIf(description.nonEmpty, `\\n\\n`, escapeLabel(description.text)).append("\"", " <<", fqn, ">> ", "as ", fqn, NL);
|
|
626
|
+
};
|
|
627
|
+
const printBoundary = (node) => {
|
|
628
|
+
const label = escapeLabel(node.title) || nodeName(node);
|
|
629
|
+
const fqn = fqnName(node.id);
|
|
630
|
+
names.set(node.id, fqn);
|
|
631
|
+
return new CompositeGeneratorNode().append("rectangle \"", label, "\" <<", fqn, ">> as ", fqn, " {", NL).indent({
|
|
632
|
+
indentedChildren: (indent) => indent.append("skinparam ", "RectangleBorderColor<<", fqn, ">> ", pumlColor(node.color, elemntColorProvider("fill")), NL).append("skinparam ", "RectangleFontColor<<", fqn, ">> ", pumlColor(node.color, elemntColorProvider("fill")), NL).append("skinparam ", "RectangleBorderStyle<<", fqn, ">> ", "dashed", NL, NL).append(joinToNode(nodes.filter((n) => n.parent === node.id), (c) => c.children.length > 0 ? printBoundary(c) : printNode(c))),
|
|
633
|
+
indentation: 2
|
|
634
|
+
}).append("}", NL);
|
|
635
|
+
};
|
|
636
|
+
const printEdge = (edge) => {
|
|
637
|
+
const tech = edge.technology || "";
|
|
638
|
+
const label = edge.label || tech;
|
|
639
|
+
const color = pumlColor(edge.color, relationshipsColorProvider("line"), "#777777");
|
|
640
|
+
const withColor = (text) => `<color:${color}>${text.replaceAll("\"", `'`)}`;
|
|
641
|
+
const out = new CompositeGeneratorNode().append(names.get(edge.source), " .[", color, ",thickness=2].> ", names.get(edge.target));
|
|
642
|
+
if (label || tech) {
|
|
643
|
+
out.append(" : ", label.split("\n").map((l) => isEmptyish(l) ? l : withColor(l)).join("\\n"));
|
|
644
|
+
if (tech && tech !== label) out.append("\\n<size:8>[", withColor(tech), "]</size>");
|
|
645
|
+
}
|
|
646
|
+
return out.append(NL);
|
|
647
|
+
};
|
|
648
|
+
return toString(new CompositeGeneratorNode().append("@startuml", NL).append(printHeader(), NL).append(printTheme(), NL).append(joinToNode(nodes.filter((n) => n.children.length == 0), (n) => printStereotypes(n), { appendNewLineIfNotEmpty: true })).append(joinToNode(nodes.filter((n) => isNullish(n.parent)), (n) => n.children.length > 0 ? printBoundary(n) : printNode(n), { appendNewLineIfNotEmpty: true })).appendIf(edges.length > 0, NL, joinToNode(edges, (e) => printEdge(e), { appendNewLineIfNotEmpty: true })).append(`@enduml`, NL));
|
|
649
|
+
}
|
|
650
|
+
function generateViewId(views) {
|
|
651
|
+
return joinToNode(views, (view) => expandToNode`${JSON5.stringify(view.id)}`, { separator: " | " });
|
|
652
|
+
}
|
|
653
|
+
function generateViewsDataJs(diagrams) {
|
|
654
|
+
const views = Array.from(diagrams);
|
|
655
|
+
const out = new CompositeGeneratorNode();
|
|
656
|
+
out.appendTemplate`
|
|
657
|
+
/******************************************************************************
|
|
658
|
+
* This file was generated
|
|
659
|
+
* DO NOT EDIT MANUALLY!
|
|
660
|
+
******************************************************************************/
|
|
661
|
+
/* prettier-ignore-start */
|
|
662
|
+
/* eslint-disable */
|
|
663
|
+
|
|
664
|
+
`.append(NL, NL);
|
|
665
|
+
if (views.length == 0) {
|
|
666
|
+
out.append("export const LikeC4Views = {}", NL);
|
|
667
|
+
return toString(out);
|
|
668
|
+
}
|
|
669
|
+
out.appendTemplate`
|
|
670
|
+
export const LikeC4Views = {
|
|
671
|
+
`.indent({
|
|
672
|
+
indentation: 2,
|
|
673
|
+
indentedChildren(indented) {
|
|
674
|
+
indented.appendNewLineIf(views.length > 1).append(joinToNode(views, (view) => expandToNode`${JSON5.stringify(view.id)}: ${JSON5.stringify(view)}`, {
|
|
675
|
+
separator: ",",
|
|
676
|
+
appendNewLineIfNotEmpty: true
|
|
677
|
+
}));
|
|
678
|
+
}
|
|
679
|
+
}).append("}", NL, NL).appendTemplate`
|
|
680
|
+
|
|
681
|
+
export function isLikeC4ViewId(value) {
|
|
682
|
+
return (
|
|
683
|
+
value != null &&
|
|
684
|
+
typeof value === 'string' &&
|
|
685
|
+
!!LikeC4Views[value]
|
|
686
|
+
)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* prettier-ignore-end */
|
|
690
|
+
`.append(NL);
|
|
691
|
+
return toString(out);
|
|
692
|
+
}
|
|
693
|
+
function generateViewsDataTs(diagrams) {
|
|
694
|
+
const views = Array.from(diagrams);
|
|
695
|
+
const out = new CompositeGeneratorNode();
|
|
696
|
+
out.appendTemplate`
|
|
697
|
+
/******************************************************************************
|
|
698
|
+
* This file was generated
|
|
699
|
+
* DO NOT EDIT MANUALLY!
|
|
700
|
+
******************************************************************************/
|
|
701
|
+
/* prettier-ignore-start */
|
|
702
|
+
/* eslint-disable */
|
|
703
|
+
|
|
704
|
+
// @ts-nocheck
|
|
705
|
+
|
|
706
|
+
import type { DiagramView } from 'likec4'
|
|
707
|
+
`.append(NL, NL);
|
|
708
|
+
if (views.length === 0) {
|
|
709
|
+
out.append("export {}", NL);
|
|
710
|
+
return toString(out);
|
|
711
|
+
}
|
|
712
|
+
out.appendTemplate`
|
|
713
|
+
export type LikeC4ViewId = ${generateViewId(views)};
|
|
714
|
+
export const LikeC4Views = {
|
|
715
|
+
`.indent({
|
|
716
|
+
indentation: 2,
|
|
717
|
+
indentedChildren(indented) {
|
|
718
|
+
indented.appendNewLineIf(views.length > 1).append(joinToNode(views, (view) => expandToNode`${JSON5.stringify(view.id)}: (${JSON5.stringify(view)} as unknown) as DiagramView`, {
|
|
719
|
+
separator: ",",
|
|
720
|
+
appendNewLineIfNotEmpty: true
|
|
721
|
+
}));
|
|
722
|
+
}
|
|
723
|
+
}).append("} as const satisfies Record<LikeC4ViewId, DiagramView>", NL, NL).appendTemplate`
|
|
724
|
+
export type LikeC4Views = typeof LikeC4Views
|
|
725
|
+
|
|
726
|
+
export function isLikeC4ViewId(value: unknown): value is LikeC4ViewId {
|
|
727
|
+
return (
|
|
728
|
+
value != null &&
|
|
729
|
+
typeof value === 'string' &&
|
|
730
|
+
!!LikeC4Views[value]
|
|
731
|
+
)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/* prettier-ignore-end */
|
|
735
|
+
`.append(NL);
|
|
736
|
+
return toString(out);
|
|
737
|
+
}
|
|
738
|
+
function generateViewsDataDTs(diagrams) {
|
|
739
|
+
const views = Array.from(diagrams);
|
|
740
|
+
const out = new CompositeGeneratorNode();
|
|
741
|
+
out.appendTemplate`
|
|
742
|
+
/******************************************************************************
|
|
743
|
+
* This file was generated
|
|
744
|
+
* DO NOT EDIT MANUALLY!
|
|
745
|
+
******************************************************************************/
|
|
746
|
+
/* prettier-ignore-start */
|
|
747
|
+
/* eslint-disable */
|
|
748
|
+
|
|
749
|
+
import type { DiagramView } from 'likec4'
|
|
750
|
+
`.append(NL, NL);
|
|
751
|
+
if (views.length == 0) {
|
|
752
|
+
out.append("export {}", NL);
|
|
753
|
+
return toString(out);
|
|
754
|
+
}
|
|
755
|
+
out.appendTemplate`
|
|
756
|
+
export type LikeC4ViewId = ${generateViewId(views)};
|
|
757
|
+
export type LikeC4Views = Record<LikeC4ViewId, DiagramView>
|
|
758
|
+
|
|
759
|
+
export declare const LikeC4Views: LikeC4Views
|
|
760
|
+
export declare function isLikeC4ViewId(value: unknown): value is LikeC4ViewId
|
|
761
|
+
|
|
762
|
+
/* prettier-ignore-end */
|
|
763
|
+
`.append(NL);
|
|
764
|
+
return toString(out);
|
|
765
|
+
}
|
|
766
|
+
function generateReactNext(views) {
|
|
767
|
+
return {
|
|
768
|
+
viewsData: {
|
|
769
|
+
fileName: "likec4-views-data",
|
|
770
|
+
js: generateViewsDataJs(views),
|
|
771
|
+
dts: generateViewsDataDTs(views)
|
|
772
|
+
},
|
|
773
|
+
components: {
|
|
774
|
+
fileName: "likec4-components",
|
|
775
|
+
...generateComponents()
|
|
776
|
+
},
|
|
777
|
+
index: generateIndex()
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function generateComponents() {
|
|
781
|
+
const js = new CompositeGeneratorNode().appendTemplate`
|
|
782
|
+
/******************************************************************************
|
|
783
|
+
* This file was generated
|
|
784
|
+
* DO NOT EDIT MANUALLY!
|
|
785
|
+
******************************************************************************/
|
|
786
|
+
/* prettier-ignore-start */
|
|
787
|
+
/* eslint-disable */
|
|
788
|
+
import { createElement } from "react";
|
|
789
|
+
import { LikeC4Diagram, EmbeddedLikeC4Diagram } from "@likec4/diagram";
|
|
790
|
+
import { LikeC4Views } from "./likec4-views-data";
|
|
791
|
+
export function LikeC4View({ viewId, ...props }) {
|
|
792
|
+
const view = LikeC4Views[viewId];
|
|
793
|
+
if (!view) {
|
|
794
|
+
throw new Error(\`LikeC4View NotFound: "\${viewId}"\`);
|
|
795
|
+
}
|
|
796
|
+
return createElement(LikeC4Diagram, { view: view, ...props });
|
|
797
|
+
}
|
|
798
|
+
export function EmbeddedLikeC4View({ viewId, ...props }) {
|
|
799
|
+
return createElement(EmbeddedLikeC4Diagram, { viewId: viewId, views: LikeC4Views, ...props });
|
|
800
|
+
}
|
|
801
|
+
/* prettier-ignore-end */
|
|
802
|
+
`;
|
|
803
|
+
const dts = new CompositeGeneratorNode().appendTemplate`
|
|
804
|
+
/// <reference types="react" />
|
|
805
|
+
/******************************************************************************
|
|
806
|
+
* This file was generated
|
|
807
|
+
* DO NOT EDIT MANUALLY!
|
|
808
|
+
******************************************************************************/
|
|
809
|
+
/* prettier-ignore-start */
|
|
810
|
+
/* eslint-disable */
|
|
811
|
+
|
|
812
|
+
import type { LikeC4DiagramProps, EmbeddedLikeC4DiagramProps } from "@likec4/diagram";
|
|
813
|
+
import type { LikeC4ViewId } from "./likec4-views-data";
|
|
814
|
+
|
|
815
|
+
export type LikeC4ViewProps = {
|
|
816
|
+
viewId: LikeC4ViewId;
|
|
817
|
+
} & Omit<LikeC4DiagramProps, "view">;
|
|
818
|
+
|
|
819
|
+
export declare function LikeC4View({ viewId, ...props }: LikeC4ViewProps): JSX.Element;
|
|
820
|
+
|
|
821
|
+
export type EmbeddedLikeC4ViewProps = {
|
|
822
|
+
viewId: LikeC4ViewId;
|
|
823
|
+
} & Omit<EmbeddedLikeC4DiagramProps, "viewId" | "views">;
|
|
824
|
+
|
|
825
|
+
export declare function EmbeddedLikeC4View({ viewId, ...props }: EmbeddedLikeC4ViewProps): JSX.Element;
|
|
826
|
+
/* prettier-ignore-end */
|
|
827
|
+
`;
|
|
828
|
+
return {
|
|
829
|
+
js: toString(js),
|
|
830
|
+
dts: toString(dts)
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
function generateIndex() {
|
|
834
|
+
const js = new CompositeGeneratorNode().appendTemplate`
|
|
835
|
+
/* prettier-ignore-start */
|
|
836
|
+
/* eslint-disable */
|
|
837
|
+
|
|
838
|
+
// You are safe to edit/move these style imports,
|
|
839
|
+
// but they are required
|
|
840
|
+
import "@mantine/core/styles.css";
|
|
841
|
+
import "@likec4/diagram/style.css";
|
|
842
|
+
|
|
843
|
+
export * from "./likec4-components";
|
|
844
|
+
|
|
845
|
+
// OR with lazy loading:
|
|
846
|
+
//
|
|
847
|
+
// import { lazy } from "react";
|
|
848
|
+
// export const LikeC4View = lazy(async () => await import("./likec4-components").then(m => ({default: m.LikeC4View})));
|
|
849
|
+
// export const EmbeddedLikeC4View = lazy(async () => await import("./likec4-components").then(m => ({default: m.EmbeddedLikeC4View})));
|
|
850
|
+
|
|
851
|
+
/* prettier-ignore-end */
|
|
852
|
+
`;
|
|
853
|
+
const dts = new CompositeGeneratorNode().appendTemplate`
|
|
854
|
+
/* prettier-ignore-start */
|
|
855
|
+
/* eslint-disable */
|
|
856
|
+
|
|
857
|
+
export * from "./likec4-components";
|
|
858
|
+
|
|
859
|
+
/* prettier-ignore-end */
|
|
860
|
+
`;
|
|
861
|
+
return {
|
|
862
|
+
js: toString(js),
|
|
863
|
+
dts: toString(dts)
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function generateReactTypes(model, options = {}) {
|
|
867
|
+
const { useCorePackage = false } = options;
|
|
868
|
+
invariant(!model.isParsed(), "can not generate react types for parsed model");
|
|
869
|
+
const aux = generateAux(model, options);
|
|
870
|
+
return `
|
|
871
|
+
/* prettier-ignore-start */
|
|
872
|
+
/* eslint-disable */
|
|
873
|
+
|
|
874
|
+
/******************************************************************************
|
|
875
|
+
* This file was generated
|
|
876
|
+
* DO NOT EDIT MANUALLY!
|
|
877
|
+
******************************************************************************/
|
|
878
|
+
|
|
879
|
+
import type { PropsWithChildren } from 'react'
|
|
880
|
+
import type { JSX } from 'react/jsx-runtime'
|
|
881
|
+
import type { LikeC4Model } from '${useCorePackage ? "@likec4/core" : "likec4"}/model'
|
|
882
|
+
import type { LayoutedView } from '${useCorePackage ? "@likec4/core/types" : "likec4/model"}'
|
|
883
|
+
import type {
|
|
884
|
+
LikeC4ViewProps as GenericLikeC4ViewProps,
|
|
885
|
+
ReactLikeC4Props as GenericReactLikeC4Props
|
|
886
|
+
} from 'likec4/react'
|
|
887
|
+
|
|
888
|
+
${aux}
|
|
889
|
+
|
|
890
|
+
declare function isLikeC4ViewId(value: unknown): value is $ViewId;
|
|
891
|
+
|
|
892
|
+
declare const likec4model: LikeC4Model<$Aux>;
|
|
893
|
+
declare function useLikeC4Model(): LikeC4Model<$Aux>;
|
|
894
|
+
declare function useLikeC4View(viewId: $ViewId): LayoutedView<$Aux>;
|
|
895
|
+
|
|
896
|
+
declare function LikeC4ModelProvider(props: PropsWithChildren): JSX.Element;
|
|
897
|
+
|
|
898
|
+
type IconRendererProps = {
|
|
899
|
+
node: {
|
|
900
|
+
id: string
|
|
901
|
+
title: string
|
|
902
|
+
icon?: string | undefined
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
declare function RenderIcon(props: IconRendererProps): JSX.Element;
|
|
906
|
+
|
|
907
|
+
type LikeC4ViewProps = GenericLikeC4ViewProps<$Aux>;
|
|
908
|
+
declare function LikeC4View({viewId, ...props}: LikeC4ViewProps): JSX.Element;
|
|
909
|
+
|
|
910
|
+
type ReactLikeC4Props = GenericReactLikeC4Props<$Aux>
|
|
911
|
+
declare function ReactLikeC4({viewId, ...props}: ReactLikeC4Props): JSX.Element;
|
|
912
|
+
|
|
913
|
+
export {
|
|
914
|
+
type LikeC4ViewProps,
|
|
915
|
+
type ReactLikeC4Props,
|
|
916
|
+
isLikeC4ViewId,
|
|
917
|
+
useLikeC4Model,
|
|
918
|
+
useLikeC4View,
|
|
919
|
+
likec4model,
|
|
920
|
+
LikeC4ModelProvider,
|
|
921
|
+
LikeC4View,
|
|
922
|
+
RenderIcon,
|
|
923
|
+
ReactLikeC4
|
|
924
|
+
}
|
|
925
|
+
/* prettier-ignore-end */
|
|
926
|
+
`.trimStart();
|
|
927
|
+
}
|
|
928
|
+
export { generateD2, generateDrawio, generateLikeC4Model, generateMermaid, generatePuml, generateReactNext, generateReactTypes, generateViewsDataDTs, generateViewsDataJs, generateViewsDataTs, parseDrawioToLikeC4 };
|