@markdy/compat 1.3.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hoang Yell (https://hoangyell.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # @markdy/compat
2
+
3
+ Universal Ingestion Transpilers & Backwards-Compatibility Gate for Markdy.
4
+
5
+ `@markdy/compat` enables seamless migration from existing diagramming and infrastructure tools into animated, diagram-native MarkdyScript scenes.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - **Mermaid Transpiler**: Converts Mermaid Flowcharts (`flowchart LR/TB/TD`) and Sequence Diagrams (`sequenceDiagram`) into animated Markdy scenes.
12
+ - **Draw.io Ingestion**: Converts `.drawio` / `.xml` diagram graphs into structured MarkdyScript scenes with port attachments and label preservation.
13
+ - **Docker Compose Ingestion**: Parses `docker-compose.yml` into connected service topologies with port allocations and dependency order.
14
+ - **Kubernetes Manifests Transpiler**: Converts multi-document K8s manifests into namespace groups, ingresses, load balancers, and workloads.
15
+ - **Terraform State Ingestion**: Converts `.tfstate` files into VPC-clustered infrastructure diagrams.
16
+ - **Backwards-Compatibility Snapshot Gate**: Automated regression test suite ensuring Markdy parser stability across releases.
17
+
18
+ <p align="center">
19
+ <a href="https://markdy.com/playground/">
20
+ <img src="https://raw.githubusercontent.com/HoangYell/markdy-com/main/website/public/images/markdy-universal-ingestion.webp" alt="Universal Ingestion Transpilers" width="900" />
21
+ </a>
22
+ </p>
23
+ <p align="center">
24
+ <a href="https://markdy.com/playground/">
25
+ <img src="https://raw.githubusercontent.com/HoangYell/markdy-com/main/website/public/images/scene-nested-security.webp" alt="Kubernetes Manifest Ingestion Preview" width="900" />
26
+ </a>
27
+ </p>
28
+
29
+ ---
30
+
31
+ ## Programmatic API
32
+
33
+ ```typescript
34
+ import {
35
+ transpileMermaidToMarkdy,
36
+ transpileDrawioToMarkdy,
37
+ transpileDockerComposeToMarkdy,
38
+ transpileKubernetesManifestsToMarkdy,
39
+ transpileTerraformStateToMarkdy,
40
+ } from "@markdy/compat";
41
+
42
+ // 1. Mermaid Flowchart
43
+ const mermaidResult = transpileMermaidToMarkdy(`
44
+ flowchart LR
45
+ Client --> Gateway[API Gateway]
46
+ Gateway --> DB[(Database)]
47
+ `);
48
+ console.log(mermaidResult.code);
49
+
50
+ // 2. Docker Compose
51
+ const composeCode = transpileDockerComposeToMarkdy(dockerComposeYamlString);
52
+
53
+ // 3. Kubernetes Manifests
54
+ const k8sCode = transpileKubernetesManifestsToMarkdy(k8sManifestYamlString);
55
+
56
+ // 4. Terraform State
57
+ const tfCode = transpileTerraformStateToMarkdy(tfstateJsonString);
58
+ ```
59
+
60
+ ---
61
+
62
+ ## License
63
+
64
+ MIT © [Hoang Yell](https://hoangyell.com)
@@ -0,0 +1,185 @@
1
+ // src/mermaid/mermaid-transpiler.ts
2
+ function sanitizeId(id) {
3
+ return id.trim().replace(/[^a-zA-Z0-9_]/g, "_");
4
+ }
5
+ function cleanLabel(raw) {
6
+ return raw.trim().replace(/^["'\[\(\{]+/, "").replace(/["'\]\)\}]+$/, "").replace(/<br\s*\/?>/gi, " ");
7
+ }
8
+ function inferKindFromMermaid(id, label, shapeBracket) {
9
+ const text = `${id} ${label}`.toLowerCase();
10
+ if (shapeBracket === "[(" || shapeBracket === ")]" || /(db|database|sql|postgres|mongo|dynamo|redis)/.test(text)) {
11
+ return "database";
12
+ }
13
+ if (shapeBracket === "{{" || shapeBracket === "}}" || /(queue|kafka|rabbitmq|sqs|event|bus)/.test(text)) {
14
+ return "queue";
15
+ }
16
+ if (shapeBracket === "{" || shapeBracket === "}" || /(decision|check|valid|auth|gate)/.test(text)) {
17
+ return "gateway";
18
+ }
19
+ if (shapeBracket === "([" || shapeBracket === "])" || /(client|user|browser|ui|app|web|frontend)/.test(text)) {
20
+ return "browser";
21
+ }
22
+ if (/(cache|memcached|varnish)/.test(text)) return "cache";
23
+ if (/(storage|s3|bucket|blob)/.test(text)) return "storage";
24
+ if (/(worker|cron|job|lambda|function)/.test(text)) return "worker";
25
+ return "service";
26
+ }
27
+ function transpileMermaidToMarkdy(mermaidSource, sceneTitle = "Imported Diagram") {
28
+ const lines = mermaidSource.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("%%"));
29
+ if (lines.length === 0) {
30
+ return {
31
+ code: `scene theme=paper
32
+ layout LR
33
+ `,
34
+ diagramType: "architecture",
35
+ nodeCount: 0,
36
+ edgeCount: 0
37
+ };
38
+ }
39
+ const firstLine = lines[0].toLowerCase();
40
+ if (firstLine.startsWith("sequencediagram")) {
41
+ return transpileSequenceDiagram(lines.slice(1), sceneTitle);
42
+ }
43
+ return transpileFlowchart(lines, sceneTitle);
44
+ }
45
+ function transpileSequenceDiagram(lines, title) {
46
+ const participants = /* @__PURE__ */ new Map();
47
+ const messages = [];
48
+ for (const line of lines) {
49
+ const partMatch = /^(?:participant|actor)\s+([^\s]+)(?:\s+as\s+(.+))?$/i.exec(line);
50
+ if (partMatch) {
51
+ const id = sanitizeId(partMatch[1]);
52
+ const label = partMatch[2] ? cleanLabel(partMatch[2]) : id;
53
+ const kind = inferKindFromMermaid(id, label);
54
+ participants.set(id, { id, label, kind });
55
+ continue;
56
+ }
57
+ const msgMatch = /^([a-zA-Z0-9_]+)\s*(-->>|->>|->|-->|-\)|~>)\s*([a-zA-Z0-9_]+)\s*:\s*(.+)$/.exec(line);
58
+ if (msgMatch) {
59
+ const from = sanitizeId(msgMatch[1]);
60
+ const arrow = msgMatch[2];
61
+ const to = sanitizeId(msgMatch[3]);
62
+ const label = cleanLabel(msgMatch[4]);
63
+ if (!participants.has(from)) participants.set(from, { id: from, label: from, kind: inferKindFromMermaid(from, from) });
64
+ if (!participants.has(to)) participants.set(to, { id: to, label: to, kind: inferKindFromMermaid(to, to) });
65
+ let kind = "->";
66
+ if (arrow === "-->>" || arrow === "-->" || arrow === "-.->") kind = "~>";
67
+ messages.push({ from, to, label, kind });
68
+ }
69
+ }
70
+ const out = [];
71
+ out.push(title ? `scene "${title}" type=sequence theme=paper` : `scene type=sequence theme=paper`);
72
+ out.push("");
73
+ for (const p of participants.values()) {
74
+ out.push(`${p.kind} ${p.id} "${p.label}"`);
75
+ }
76
+ out.push("");
77
+ out.push('beat main "Sequence Flow":');
78
+ out.push(" show $nodes");
79
+ for (const msg of messages) {
80
+ out.push(` ${msg.from} ${msg.kind} ${msg.to} "${msg.label}"`);
81
+ }
82
+ return {
83
+ code: out.join("\n"),
84
+ diagramType: "sequence",
85
+ nodeCount: participants.size,
86
+ edgeCount: messages.length
87
+ };
88
+ }
89
+ function transpileFlowchart(lines, title) {
90
+ let direction = "LR";
91
+ const first = lines[0].toLowerCase();
92
+ if (first.startsWith("graph") || first.startsWith("flowchart")) {
93
+ const dirMatch = /\b(lr|rl|tb|td|bt)\b/i.exec(first);
94
+ if (dirMatch) {
95
+ direction = dirMatch[1].toUpperCase();
96
+ if (direction === "TD") direction = "TB";
97
+ }
98
+ lines = lines.slice(1);
99
+ }
100
+ const nodes = /* @__PURE__ */ new Map();
101
+ const groups = /* @__PURE__ */ new Map();
102
+ const flows = [];
103
+ let currentSubgraph = null;
104
+ const explicitNodeRe = /([a-zA-Z0-9_-]+)\s*(\[\([^\n]*?\)\]|\[\[[^\n]*?\]\]|\(\[[^\n]*?\]\)|\(\([^\n]*?\)\)|\[[^\n]*?\]|\{[^\n]*?\}|\([^\n]*?\))/g;
105
+ const flowPattern = /([a-zA-Z0-9_-]+)\s*(?:\[[^\]]*\]|\([^)]*\)|\{[^}]*\})?\s*(-->|->|==>|-.->|--\s*([^-]+)\s*-->)\s*(?:\|([^|]+)\|)?\s*([a-zA-Z0-9_-]+)/;
106
+ for (const line of lines) {
107
+ const subMatch = /^subgraph\s+([a-zA-Z0-9_]+)?\s*(\[.*\]|".*")?/i.exec(line);
108
+ if (subMatch) {
109
+ const rawId = subMatch[1] || `group_${groups.size + 1}`;
110
+ const id = sanitizeId(rawId);
111
+ const label = subMatch[2] ? cleanLabel(subMatch[2]) : id;
112
+ currentSubgraph = { id, label, members: [] };
113
+ groups.set(id, currentSubgraph);
114
+ continue;
115
+ }
116
+ if (line === "end" && currentSubgraph) {
117
+ currentSubgraph = null;
118
+ continue;
119
+ }
120
+ let match;
121
+ while ((match = explicitNodeRe.exec(line)) !== null) {
122
+ const rawId = match[1];
123
+ const rawBody = match[2];
124
+ const id = sanitizeId(rawId);
125
+ const bracket = rawBody.slice(0, 2);
126
+ const label = cleanLabel(rawBody) || id;
127
+ const kind = inferKindFromMermaid(id, label, bracket);
128
+ nodes.set(id, { id, label, kind });
129
+ if (currentSubgraph && !currentSubgraph.members.includes(id)) {
130
+ currentSubgraph.members.push(id);
131
+ }
132
+ }
133
+ let remainingLine = line;
134
+ while (true) {
135
+ const fMatch = flowPattern.exec(remainingLine);
136
+ if (!fMatch) break;
137
+ const from = sanitizeId(fMatch[1]);
138
+ const arrow = fMatch[2];
139
+ const inlineLabel = fMatch[3];
140
+ const pipeLabel = fMatch[4];
141
+ const to = sanitizeId(fMatch[5]);
142
+ const label = pipeLabel ? cleanLabel(pipeLabel) : inlineLabel ? cleanLabel(inlineLabel) : void 0;
143
+ if (!nodes.has(from)) nodes.set(from, { id: from, label: from, kind: inferKindFromMermaid(from, from) });
144
+ if (!nodes.has(to)) nodes.set(to, { id: to, label: to, kind: inferKindFromMermaid(to, to) });
145
+ let op = "->";
146
+ if (arrow.includes("-.->") || arrow.includes("~")) op = "~>";
147
+ flows.push({ from, to, op, label });
148
+ const toIndex = fMatch.index + fMatch[0].lastIndexOf(fMatch[5]);
149
+ if (toIndex === 0) break;
150
+ remainingLine = remainingLine.substring(toIndex);
151
+ }
152
+ }
153
+ const out = [];
154
+ out.push(title ? `scene "${title}" theme=paper` : `scene theme=paper`);
155
+ out.push(`layout ${direction}`);
156
+ out.push("");
157
+ for (const g of groups.values()) {
158
+ if (g.members.length > 0) {
159
+ out.push(`group ${g.id} "${g.label}": ${g.members.join(" ")}`);
160
+ }
161
+ }
162
+ for (const n of nodes.values()) {
163
+ out.push(`${n.kind} ${n.id} "${n.label}"`);
164
+ }
165
+ out.push("");
166
+ out.push('beat main "Render Diagram":');
167
+ out.push(" show $nodes stagger=60ms");
168
+ for (const flow of flows) {
169
+ if (flow.label) {
170
+ out.push(` ${flow.from} ${flow.op} ${flow.to} "${flow.label}"`);
171
+ } else {
172
+ out.push(` ${flow.from} ${flow.op} ${flow.to}`);
173
+ }
174
+ }
175
+ return {
176
+ code: out.join("\n"),
177
+ diagramType: "architecture",
178
+ nodeCount: nodes.size,
179
+ edgeCount: flows.length
180
+ };
181
+ }
182
+
183
+ export {
184
+ transpileMermaidToMarkdy
185
+ };
@@ -0,0 +1,125 @@
1
+ export { MermaidTranspileResult, transpileMermaidToMarkdy } from './mermaid/mermaid-transpiler.js';
2
+
3
+ /**
4
+ * packages/compat/src/infra/docker-compose-transpiler.ts
5
+ * Ingests Docker Compose configurations and transpiles to animated MarkdyScript scenes.
6
+ * Zero external dependencies.
7
+ */
8
+ interface ComposeServiceSpec {
9
+ name: string;
10
+ image?: string;
11
+ ports?: string[];
12
+ dependsOn?: string[];
13
+ networks?: string[];
14
+ }
15
+ declare function parseSimpleYaml(content: string): Record<string, unknown>;
16
+ declare function transpileDockerComposeToMarkdy(yamlContent: string, title?: string): string;
17
+
18
+ /**
19
+ * packages/compat/src/infra/k8s-transpiler.ts
20
+ * Transpiles Kubernetes YAML manifests into animated Markdy architecture scenes.
21
+ * Zero external dependencies.
22
+ */
23
+ interface K8sManifest {
24
+ kind: string;
25
+ name: string;
26
+ namespace?: string;
27
+ selectorLabels?: Record<string, string>;
28
+ podLabels?: Record<string, string>;
29
+ services?: string[];
30
+ }
31
+ declare function transpileKubernetesManifestsToMarkdy(manifestContent: string, title?: string): string;
32
+
33
+ /**
34
+ * packages/compat/src/infra/terraform-transpiler.ts
35
+ * Ingests Terraform state files (.tfstate) and emits structured MarkdyScript scenes.
36
+ * Zero external dependencies.
37
+ */
38
+ interface TfResourceAttributes {
39
+ id?: string;
40
+ name?: string;
41
+ arn?: string;
42
+ tags?: Record<string, string>;
43
+ vpc_id?: string;
44
+ subnet_id?: string;
45
+ cluster_id?: string;
46
+ load_balancer_arn?: string;
47
+ [key: string]: unknown;
48
+ }
49
+ interface TfResourceInstance {
50
+ attributes: TfResourceAttributes;
51
+ }
52
+ interface TfResource {
53
+ type: string;
54
+ name: string;
55
+ provider: string;
56
+ instances: TfResourceInstance[];
57
+ }
58
+ interface TerraformStateJSON {
59
+ version: number;
60
+ terraform_version?: string;
61
+ resources: TfResource[];
62
+ }
63
+ declare function transpileTerraformStateToMarkdy(tfstateContent: string, sceneTitle?: string): string;
64
+
65
+ /**
66
+ * packages/compat/src/infra/drawio-transpiler.ts
67
+ * Transpiles Draw.io / diagrams.net XML and compressed model files into animated MarkdyScript scenes.
68
+ * Zero external dependencies.
69
+ */
70
+ interface DrawioCell {
71
+ id: string;
72
+ value: string;
73
+ style: string;
74
+ isVertex: boolean;
75
+ isEdge: boolean;
76
+ source?: string;
77
+ target?: string;
78
+ parent?: string;
79
+ }
80
+ interface DrawioModel {
81
+ title: string;
82
+ cells: DrawioCell[];
83
+ }
84
+ /**
85
+ * Parses XML string extracting mxCell elements.
86
+ */
87
+ declare function parseDrawioXml(xml: string, defaultTitle?: string): DrawioModel;
88
+ /**
89
+ * Transpiles Draw.io XML or model into MarkdyScript.
90
+ */
91
+ declare function transpileDrawioToMarkdy(source: string, customTitle?: string): Promise<{
92
+ code: string;
93
+ nodeCount: number;
94
+ edgeCount: number;
95
+ }>;
96
+
97
+ /**
98
+ * packages/compat/src/infra/d2-transpiler.ts
99
+ * Transpiles D2 declarative diagram scripts into MarkdyScript DSL.
100
+ * Zero external dependencies.
101
+ */
102
+ interface D2TranspileResult {
103
+ markdyScript: string;
104
+ nodeCount: number;
105
+ edgeCount: number;
106
+ containerCount: number;
107
+ warnings: string[];
108
+ }
109
+ declare function transpileD2ToMarkdy(d2Source: string): D2TranspileResult;
110
+
111
+ /**
112
+ * packages/compat/src/infra/plantuml-transpiler.ts
113
+ * Transpiles PlantUML architecture & component diagrams into MarkdyScript DSL.
114
+ * Zero external dependencies.
115
+ */
116
+ interface PlantUmlTranspileResult {
117
+ markdyScript: string;
118
+ nodeCount: number;
119
+ edgeCount: number;
120
+ groupCount: number;
121
+ warnings: string[];
122
+ }
123
+ declare function transpilePlantUmlToMarkdy(pumlSource: string): PlantUmlTranspileResult;
124
+
125
+ export { type ComposeServiceSpec, type D2TranspileResult, type DrawioCell, type DrawioModel, type K8sManifest, type PlantUmlTranspileResult, type TerraformStateJSON, type TfResource, type TfResourceAttributes, type TfResourceInstance, parseDrawioXml, parseSimpleYaml, transpileD2ToMarkdy, transpileDockerComposeToMarkdy, transpileDrawioToMarkdy, transpileKubernetesManifestsToMarkdy, transpilePlantUmlToMarkdy, transpileTerraformStateToMarkdy };
package/dist/index.js ADDED
@@ -0,0 +1,835 @@
1
+ import {
2
+ transpileMermaidToMarkdy
3
+ } from "./chunk-UQF5UJH6.js";
4
+
5
+ // src/infra/docker-compose-transpiler.ts
6
+ function sanitizeIdentifier(name) {
7
+ return name.replace(/[^a-zA-Z0-9_]/g, "_");
8
+ }
9
+ function inferSemanticKind(serviceName, image) {
10
+ const combined = `${serviceName} ${image ?? ""}`.toLowerCase();
11
+ if (/(postgres|mysql|mongo|mariadb|sqlite|cockroach|db)/.test(combined)) return "database";
12
+ if (/(redis|memcache)/.test(combined)) return "cache";
13
+ if (/(kafka|rabbitmq|sqs|pulsar|nats|queue)/.test(combined)) return "queue";
14
+ if (/(nginx|envoy|traefik|caddy|gateway|haproxy)/.test(combined)) return "gateway";
15
+ if (/(web|frontend|client|ui|react|vue|next)/.test(combined)) return "browser";
16
+ if (/(worker|job|cron|consumer)/.test(combined)) return "worker";
17
+ if (/(minio|s3|storage|blob)/.test(combined)) return "storage";
18
+ return "service";
19
+ }
20
+ function parseSimpleYaml(content) {
21
+ const result = {};
22
+ const lines = content.split(/\r?\n/);
23
+ const stack = [
24
+ { indent: -1, obj: result }
25
+ ];
26
+ for (let i = 0; i < lines.length; i++) {
27
+ const rawLine = lines[i];
28
+ if (!rawLine.trim() || rawLine.trim().startsWith("#")) continue;
29
+ const indent = rawLine.length - rawLine.trimStart().length;
30
+ const trimmed = rawLine.trim();
31
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
32
+ stack.pop();
33
+ }
34
+ if (trimmed.startsWith("- ")) {
35
+ const val = trimmed.slice(2).trim().replace(/^['"]|['"]$/g, "");
36
+ const parent2 = stack[stack.length - 1].obj;
37
+ if (Array.isArray(parent2)) {
38
+ parent2.push(val);
39
+ }
40
+ continue;
41
+ }
42
+ const colonIdx = trimmed.indexOf(":");
43
+ if (colonIdx === -1) continue;
44
+ const key = trimmed.slice(0, colonIdx).trim().replace(/^['"]|['"]$/g, "");
45
+ const valRaw = trimmed.slice(colonIdx + 1).trim();
46
+ const parent = stack[stack.length - 1].obj;
47
+ if (valRaw === "" || valRaw === "|" || valRaw === ">") {
48
+ let isArray = false;
49
+ for (let j = i + 1; j < lines.length; j++) {
50
+ const nextLine = lines[j];
51
+ if (!nextLine.trim() || nextLine.trim().startsWith("#")) continue;
52
+ const nextIndent = nextLine.length - nextLine.trimStart().length;
53
+ if (nextIndent > indent && nextLine.trim().startsWith("- ")) {
54
+ isArray = true;
55
+ }
56
+ break;
57
+ }
58
+ if (isArray) {
59
+ const child = [];
60
+ parent[key] = child;
61
+ stack.push({ indent, obj: child });
62
+ } else {
63
+ const child = {};
64
+ parent[key] = child;
65
+ stack.push({ indent, obj: child });
66
+ }
67
+ } else if (valRaw.startsWith("[") && valRaw.endsWith("]")) {
68
+ const inner = valRaw.slice(1, -1).trim();
69
+ const items = inner ? inner.split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean) : [];
70
+ parent[key] = items;
71
+ } else {
72
+ parent[key] = valRaw.replace(/^['"]|['"]$/g, "");
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+ function transpileDockerComposeToMarkdy(yamlContent, title = "Container Topology") {
78
+ const parsed = parseSimpleYaml(yamlContent);
79
+ const rawServices = parsed["services"] ?? {};
80
+ const serviceList = [];
81
+ for (const [svcName, svcConfig] of Object.entries(rawServices)) {
82
+ if (typeof svcConfig !== "object" || svcConfig === null) continue;
83
+ const cfg = svcConfig;
84
+ let ports = [];
85
+ if (Array.isArray(cfg["ports"])) {
86
+ ports = cfg["ports"].map((p) => String(p).replace(/^['"]|['"]$/g, ""));
87
+ } else if (typeof cfg["ports"] === "string") {
88
+ const pStr = cfg["ports"].trim();
89
+ if (pStr.startsWith("[") && pStr.endsWith("]")) {
90
+ ports = pStr.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
91
+ } else {
92
+ ports = [pStr.replace(/^['"]|['"]$/g, "")];
93
+ }
94
+ }
95
+ let dependsOn = [];
96
+ if (Array.isArray(cfg["depends_on"])) {
97
+ dependsOn = cfg["depends_on"].map((d) => String(d).replace(/^['"]|['"]$/g, ""));
98
+ } else if (typeof cfg["depends_on"] === "object" && cfg["depends_on"] !== null) {
99
+ dependsOn = Object.keys(cfg["depends_on"]);
100
+ } else if (typeof cfg["depends_on"] === "string") {
101
+ const dStr = cfg["depends_on"].trim();
102
+ if (dStr.startsWith("[") && dStr.endsWith("]")) {
103
+ dependsOn = dStr.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
104
+ } else {
105
+ dependsOn = [dStr.replace(/^['"]|['"]$/g, "")];
106
+ }
107
+ }
108
+ serviceList.push({
109
+ name: svcName,
110
+ image: typeof cfg["image"] === "string" ? cfg["image"] : void 0,
111
+ ports,
112
+ dependsOn
113
+ });
114
+ }
115
+ const out = [];
116
+ out.push(title ? `scene "${title}" theme=paper` : `scene theme=paper`);
117
+ out.push("layout LR");
118
+ out.push("");
119
+ for (const svc of serviceList) {
120
+ const id = sanitizeIdentifier(svc.name);
121
+ const kind = inferSemanticKind(svc.name, svc.image);
122
+ const ports = svc.ports ?? [];
123
+ const label = ports.length > 0 ? `${svc.name} :${ports[0]}` : svc.name;
124
+ out.push(`${kind} ${id} "${label}"`);
125
+ }
126
+ out.push("");
127
+ out.push('beat main "Initialize and connect services":');
128
+ out.push(" show $nodes stagger=60ms");
129
+ for (const svc of serviceList) {
130
+ const sourceId = sanitizeIdentifier(svc.name);
131
+ for (const dep of svc.dependsOn ?? []) {
132
+ const targetId = sanitizeIdentifier(dep);
133
+ out.push(` ${sourceId} -> ${targetId} "depends on"`);
134
+ }
135
+ }
136
+ return out.join("\n");
137
+ }
138
+
139
+ // src/infra/k8s-transpiler.ts
140
+ function sanitize(id) {
141
+ return id.replace(/[^a-zA-Z0-9_]/g, "_");
142
+ }
143
+ function transpileKubernetesManifestsToMarkdy(manifestContent, title = "Kubernetes Cluster Topology") {
144
+ const docs = manifestContent.split(/^---/m).map((d) => d.trim()).filter((d) => d.length > 0);
145
+ const manifests = [];
146
+ for (const doc of docs) {
147
+ const raw = parseSimpleYaml(doc);
148
+ const kind = typeof raw["kind"] === "string" ? raw["kind"] : "";
149
+ const meta = raw["metadata"] || {};
150
+ const name = typeof meta["name"] === "string" ? meta["name"] : "";
151
+ const namespace = typeof meta["namespace"] === "string" ? meta["namespace"] : "default";
152
+ if (!kind || !name) continue;
153
+ manifests.push({
154
+ kind,
155
+ name,
156
+ namespace
157
+ });
158
+ }
159
+ const out = [];
160
+ out.push(title ? `scene "${title}" theme=paper` : `scene theme=paper`);
161
+ out.push("layout TB");
162
+ out.push("");
163
+ const namespaces = new Set(manifests.map((m) => m.namespace || "default"));
164
+ for (const ns of namespaces) {
165
+ const nsMembers = manifests.filter((m) => (m.namespace || "default") === ns).map((m) => sanitize(`${m.kind}_${m.name}`));
166
+ if (nsMembers.length > 0) {
167
+ out.push(`group ns_${sanitize(ns)} "Namespace: ${ns}": ${nsMembers.join(" ")}`);
168
+ }
169
+ }
170
+ out.push("");
171
+ for (const m of manifests) {
172
+ const id = sanitize(`${m.kind}_${m.name}`);
173
+ let kind = "service";
174
+ if (m.kind === "Ingress") kind = "gateway";
175
+ else if (m.kind === "Service") kind = "load_balancer";
176
+ else if (m.kind === "StatefulSet") kind = "database";
177
+ else if (m.kind === "CronJob" || m.kind === "Job") kind = "worker";
178
+ else if (m.kind === "PersistentVolumeClaim") kind = "storage";
179
+ out.push(`${kind} ${id} "${m.name} (${m.kind})"`);
180
+ }
181
+ out.push("");
182
+ out.push('beat main "Cluster Ingress & Service Mesh":');
183
+ out.push(" show $nodes stagger=50ms");
184
+ const ingresses = manifests.filter((m) => m.kind === "Ingress");
185
+ const services = manifests.filter((m) => m.kind === "Service");
186
+ const workloads = manifests.filter((m) => ["Deployment", "StatefulSet", "DaemonSet"].includes(m.kind));
187
+ for (const ing of ingresses) {
188
+ for (const svc of services) {
189
+ out.push(` ${sanitize(`${ing.kind}_${ing.name}`)} -> ${sanitize(`${svc.kind}_${svc.name}`)} "route"`);
190
+ }
191
+ }
192
+ for (const svc of services) {
193
+ for (const wl of workloads) {
194
+ out.push(` ${sanitize(`${svc.kind}_${svc.name}`)} -> ${sanitize(`${wl.kind}_${wl.name}`)} "balance"`);
195
+ }
196
+ }
197
+ return out.join("\n");
198
+ }
199
+
200
+ // src/infra/terraform-transpiler.ts
201
+ function sanitizeId(raw) {
202
+ return raw.replace(/[^a-zA-Z0-9_]/g, "_");
203
+ }
204
+ function inferKindFromTfType(type) {
205
+ if (type.includes("database") || type.includes("db_instance") || type.includes("rds") || type.includes("dynamodb")) {
206
+ return "database";
207
+ }
208
+ if (type.includes("elasticache") || type.includes("redis") || type.includes("memcached")) {
209
+ return "cache";
210
+ }
211
+ if (type.includes("sqs") || type.includes("pubsub") || type.includes("servicebus") || type.includes("queue")) {
212
+ return "queue";
213
+ }
214
+ if (type.includes("s3_bucket") || type.includes("storage_bucket") || type.includes("blob")) {
215
+ return "storage";
216
+ }
217
+ if (type.includes("lb") || type.includes("alb") || type.includes("apigateway") || type.includes("gateway")) {
218
+ return "gateway";
219
+ }
220
+ if (type.includes("cloudfront") || type.includes("cdn")) {
221
+ return "cdn";
222
+ }
223
+ if (type.includes("lambda") || type.includes("cloudfunctions") || type.includes("function_app")) {
224
+ return "worker";
225
+ }
226
+ if (type.includes("eks") || type.includes("gke") || type.includes("aks") || type.includes("cluster")) {
227
+ return "cluster";
228
+ }
229
+ return "service";
230
+ }
231
+ function transpileTerraformStateToMarkdy(tfstateContent, sceneTitle = "Cloud Infrastructure Architecture") {
232
+ let parsed;
233
+ try {
234
+ parsed = JSON.parse(tfstateContent);
235
+ } catch {
236
+ throw new Error("Invalid Terraform state JSON");
237
+ }
238
+ if (!parsed.resources || !Array.isArray(parsed.resources)) {
239
+ return sceneTitle ? `scene "${sceneTitle}" theme=paper
240
+ layout LR
241
+ ` : `scene theme=paper
242
+ layout LR
243
+ `;
244
+ }
245
+ const nodes = [];
246
+ const edges = [];
247
+ const vpcGroups = /* @__PURE__ */ new Map();
248
+ const arnToResId = /* @__PURE__ */ new Map();
249
+ for (const res of parsed.resources) {
250
+ if (res.type.startsWith("aws_iam_") || res.type.includes("route_table") || res.type.includes("security_group")) {
251
+ continue;
252
+ }
253
+ const firstInst = res.instances?.[0]?.attributes;
254
+ const resId = sanitizeId(`${res.type}_${res.name}`);
255
+ const kind = inferKindFromTfType(res.type);
256
+ const label = firstInst?.tags?.["Name"] || firstInst?.name || `${res.type.split("_").slice(-1)[0]}: ${res.name}`;
257
+ const vpcId = typeof firstInst?.vpc_id === "string" ? sanitizeId(firstInst.vpc_id) : void 0;
258
+ nodes.push({ id: resId, kind, label, vpcId });
259
+ if (typeof firstInst?.arn === "string") {
260
+ arnToResId.set(firstInst.arn, resId);
261
+ }
262
+ if (vpcId) {
263
+ if (!vpcGroups.has(vpcId)) vpcGroups.set(vpcId, []);
264
+ vpcGroups.get(vpcId).push(resId);
265
+ }
266
+ if (typeof firstInst?.load_balancer_arn === "string") {
267
+ edges.push({ from: firstInst.load_balancer_arn, to: resId, label: "routes" });
268
+ }
269
+ }
270
+ for (let i = 0; i < edges.length; i++) {
271
+ const edge = edges[i];
272
+ if (edge.from.startsWith("arn:") && arnToResId.has(edge.from)) {
273
+ edge.from = arnToResId.get(edge.from);
274
+ } else if (edge.from.startsWith("arn:")) {
275
+ edge.from = sanitizeId(edge.from);
276
+ }
277
+ }
278
+ const out = [];
279
+ out.push(sceneTitle ? `scene "${sceneTitle}" theme=paper` : `scene theme=paper`);
280
+ out.push("layout LR");
281
+ out.push("");
282
+ for (const [vpc, members] of vpcGroups) {
283
+ if (members.length > 1) {
284
+ out.push(`group ${vpc} "VPC Network": ${members.join(" ")}`);
285
+ }
286
+ }
287
+ for (const n of nodes) {
288
+ out.push(`${n.kind} ${n.id} "${n.label}"`);
289
+ }
290
+ out.push("");
291
+ out.push('beat main "Provisioned Infrastructure Flow":');
292
+ out.push(" show $nodes stagger=40ms");
293
+ if (edges.length > 0) {
294
+ for (const e of edges) {
295
+ out.push(` ${e.from} -> ${e.to} "${e.label || "connect"}"`);
296
+ }
297
+ } else if (nodes.length >= 2) {
298
+ out.push(` ${nodes[0].id} -> ${nodes[1].id} "traffic"`);
299
+ }
300
+ return out.join("\n");
301
+ }
302
+
303
+ // src/infra/drawio-transpiler.ts
304
+ import { classifyTechnology } from "@markdy/core";
305
+ function sanitizeId2(raw, fallback) {
306
+ const cleaned = raw.replace(/[^a-zA-Z0-9_]/g, "");
307
+ if (/^[0-9]/.test(cleaned) || cleaned.length === 0) {
308
+ return `${fallback}_${cleaned}`;
309
+ }
310
+ return cleaned;
311
+ }
312
+ function stripHtml(raw) {
313
+ return raw.replace(/<br\s*\/?>/gi, " ").replace(/<\/?[^>]+(>|$)/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').trim();
314
+ }
315
+ function inferKindFromStyleAndLabel(style, label) {
316
+ const styleLower = style.toLowerCase();
317
+ const labelLower = label.toLowerCase();
318
+ if (styleLower.includes("shape=cylinder") || styleLower.includes("datastore") || styleLower.includes("database")) {
319
+ return "database";
320
+ }
321
+ if (styleLower.includes("shape=cloud") || styleLower.includes("network")) {
322
+ return "cloud";
323
+ }
324
+ if (styleLower.includes("shape=actor") || styleLower.includes("person") || styleLower.includes("user")) {
325
+ return "user";
326
+ }
327
+ if (styleLower.includes("shape=hexagon") || styleLower.includes("gateway")) {
328
+ return "gateway";
329
+ }
330
+ if (styleLower.includes("queue") || styleLower.includes("message") || styleLower.includes("kafka") || styleLower.includes("sqs")) {
331
+ return "queue";
332
+ }
333
+ if (styleLower.includes("cache") || styleLower.includes("redis")) {
334
+ return "cache";
335
+ }
336
+ if (styleLower.includes("storage") || styleLower.includes("bucket") || styleLower.includes("s3")) {
337
+ return "storage";
338
+ }
339
+ const profile = classifyTechnology(labelLower);
340
+ if (profile.kind && profile.kind !== "service") {
341
+ return profile.kind;
342
+ }
343
+ return "service";
344
+ }
345
+ function parseDrawioXml(xml, defaultTitle = "Imported Draw.io") {
346
+ const cells = [];
347
+ const diagramTitleMatch = xml.match(/<diagram[^>]*name="([^"]+)"/i);
348
+ const title = diagramTitleMatch ? diagramTitleMatch[1] : defaultTitle;
349
+ const cellRegex = /<mxCell\s+([^>]+)(?:\/>|>([\s\S]*?)<\/mxCell>)/gi;
350
+ let match;
351
+ while ((match = cellRegex.exec(xml)) !== null) {
352
+ const attrStr = match[1];
353
+ const innerContent = match[2] || "";
354
+ const idMatch = attrStr.match(/id="([^"]+)"/i);
355
+ if (!idMatch) continue;
356
+ const id = idMatch[1];
357
+ if (id === "0" || id === "1") continue;
358
+ const valueAttrMatch = attrStr.match(/value="([^"]*)"/i);
359
+ let rawValue = valueAttrMatch ? valueAttrMatch[1] : "";
360
+ if (!rawValue && innerContent) {
361
+ rawValue = innerContent;
362
+ }
363
+ const cleanValue = stripHtml(rawValue);
364
+ const styleMatch = attrStr.match(/style="([^"]*)"/i);
365
+ const style = styleMatch ? styleMatch[1] : "";
366
+ const isVertex = /vertex="1"/i.test(attrStr);
367
+ const isEdge = /edge="1"/i.test(attrStr);
368
+ const sourceMatch = attrStr.match(/source="([^"]+)"/i);
369
+ const targetMatch = attrStr.match(/target="([^"]+)"/i);
370
+ const parentMatch = attrStr.match(/parent="([^"]+)"/i);
371
+ cells.push({
372
+ id,
373
+ value: cleanValue,
374
+ style,
375
+ isVertex,
376
+ isEdge,
377
+ source: sourceMatch ? sourceMatch[1] : void 0,
378
+ target: targetMatch ? targetMatch[1] : void 0,
379
+ parent: parentMatch ? parentMatch[1] : void 0
380
+ });
381
+ }
382
+ return { title, cells };
383
+ }
384
+ async function transpileDrawioToMarkdy(source, customTitle) {
385
+ let xml = source.trim();
386
+ if (xml.includes("<diagram") && !xml.includes("<mxGraphModel>")) {
387
+ const diagramMatch = xml.match(/<diagram[^>]*>([\s\S]*?)<\/diagram>/i);
388
+ if (diagramMatch) {
389
+ const payload = diagramMatch[1].trim();
390
+ try {
391
+ const binaryStr = atob(payload);
392
+ if (binaryStr.includes("<mxGraphModel")) {
393
+ xml = binaryStr;
394
+ } else if (typeof DecompressionStream !== "undefined") {
395
+ const uint8 = new Uint8Array(binaryStr.length);
396
+ for (let i = 0; i < binaryStr.length; i++) {
397
+ uint8[i] = binaryStr.charCodeAt(i);
398
+ }
399
+ try {
400
+ const ds = new DecompressionStream("deflate-raw");
401
+ const writer = ds.writable.getWriter();
402
+ writer.write(uint8);
403
+ writer.close();
404
+ const reader = ds.readable.getReader();
405
+ const chunks = [];
406
+ let totalLen = 0;
407
+ while (true) {
408
+ const { done, value } = await reader.read();
409
+ if (done) break;
410
+ if (value) {
411
+ chunks.push(value);
412
+ totalLen += value.length;
413
+ }
414
+ }
415
+ const decompressed = new Uint8Array(totalLen);
416
+ let offset = 0;
417
+ for (const c of chunks) {
418
+ decompressed.set(c, offset);
419
+ offset += c.length;
420
+ }
421
+ const decodedStr = new TextDecoder().decode(decompressed);
422
+ xml = decodeURIComponent(decodedStr);
423
+ } catch (e) {
424
+ }
425
+ }
426
+ } catch {
427
+ }
428
+ }
429
+ }
430
+ const model = parseDrawioXml(xml, customTitle);
431
+ const vertexCells = model.cells.filter((c) => c.isVertex);
432
+ const edgeCells = model.cells.filter((c) => c.isEdge);
433
+ const cellIdToNodeId = /* @__PURE__ */ new Map();
434
+ const nodes = [];
435
+ for (const cell of vertexCells) {
436
+ const rawLabel = cell.value || `Component_${cell.id}`;
437
+ const rawIdentifier = isNaN(Number(cell.id)) && cell.id.length > 0 ? cell.id : cell.value || `node_${cell.id}`;
438
+ const nodeId = sanitizeId2(rawIdentifier, `node_${cell.id}`);
439
+ const kind = inferKindFromStyleAndLabel(cell.style, rawLabel);
440
+ cellIdToNodeId.set(cell.id, nodeId);
441
+ nodes.push({ id: nodeId, kind, label: rawLabel });
442
+ }
443
+ const lines = [];
444
+ const sceneName = customTitle || model.title;
445
+ lines.push(sceneName ? `scene "${sceneName}" theme=paper` : `scene theme=paper`);
446
+ lines.push(`layout LR`);
447
+ lines.push(``);
448
+ if (nodes.length > 0) {
449
+ for (const node of nodes) {
450
+ lines.push(`${node.kind} ${node.id} "${node.label}"`);
451
+ }
452
+ lines.push(``);
453
+ }
454
+ const flows = [];
455
+ for (const edge of edgeCells) {
456
+ if (edge.source && edge.target) {
457
+ const sourceId = cellIdToNodeId.get(edge.source);
458
+ const targetId = cellIdToNodeId.get(edge.target);
459
+ if (sourceId && targetId) {
460
+ flows.push({
461
+ from: sourceId,
462
+ to: targetId,
463
+ label: edge.value || void 0
464
+ });
465
+ }
466
+ }
467
+ }
468
+ lines.push(`beat main "System Flow":`);
469
+ lines.push(` show $nodes stagger=60ms`);
470
+ if (flows.length > 0) {
471
+ for (const flow of flows) {
472
+ if (flow.label) {
473
+ lines.push(` ${flow.from} -> ${flow.to} "${flow.label}"`);
474
+ } else {
475
+ lines.push(` ${flow.from} -> ${flow.to}`);
476
+ }
477
+ }
478
+ }
479
+ return {
480
+ code: lines.join("\n"),
481
+ nodeCount: nodes.length,
482
+ edgeCount: flows.length
483
+ };
484
+ }
485
+
486
+ // src/infra/d2-transpiler.ts
487
+ function transpileD2ToMarkdy(d2Source) {
488
+ const lines = d2Source.split("\n");
489
+ const warnings = [];
490
+ const nodes = /* @__PURE__ */ new Map();
491
+ const edges = [];
492
+ const containers = /* @__PURE__ */ new Map();
493
+ let currentContainer = null;
494
+ let sceneTitle = "Architecture Diagram";
495
+ function sanitizeId3(raw) {
496
+ return raw.trim().replace(/[^a-zA-Z0-9_]/g, "_");
497
+ }
498
+ function inferNodeKind(labelOrId, shape) {
499
+ const lower = (labelOrId + " " + (shape || "")).toLowerCase();
500
+ if (lower.includes("db") || lower.includes("database") || lower.includes("postgres") || lower.includes("sql") || lower.includes("cylinder")) {
501
+ return "database";
502
+ }
503
+ if (lower.includes("cache") || lower.includes("redis") || lower.includes("memcached")) {
504
+ return "cache";
505
+ }
506
+ if (lower.includes("browser") || lower.includes("client") || lower.includes("web") || lower.includes("ui") || lower.includes("mobile")) {
507
+ return "browser";
508
+ }
509
+ if (lower.includes("gateway") || lower.includes("proxy") || lower.includes("nginx") || lower.includes("envoy") || lower.includes("ingress")) {
510
+ return "gateway";
511
+ }
512
+ if (lower.includes("queue") || lower.includes("kafka") || lower.includes("rabbit") || lower.includes("sqs") || lower.includes("stream")) {
513
+ return "queue";
514
+ }
515
+ if (lower.includes("storage") || lower.includes("s3") || lower.includes("bucket")) {
516
+ return "storage";
517
+ }
518
+ return "service";
519
+ }
520
+ for (let idx = 0; idx < lines.length; idx++) {
521
+ const rawLine = lines[idx].trim();
522
+ if (!rawLine || rawLine.startsWith("#")) continue;
523
+ const titleMatch = rawLine.match(/^title\s*:\s*["']?([^"']+)["']?$/i);
524
+ if (titleMatch) {
525
+ sceneTitle = titleMatch[1].trim();
526
+ continue;
527
+ }
528
+ const containerOpenMatch = rawLine.match(/^([a-zA-Z0-9_-]+)\s*:\s*\{$/);
529
+ if (containerOpenMatch) {
530
+ currentContainer = sanitizeId3(containerOpenMatch[1]);
531
+ containers.set(currentContainer, []);
532
+ continue;
533
+ }
534
+ if (rawLine === "}") {
535
+ currentContainer = null;
536
+ continue;
537
+ }
538
+ const connMatch = rawLine.match(/^([a-zA-Z0-9_.-]+)\s*(->|<-|<->|--|\.\.>|\.\.)\s*([a-zA-Z0-9_.-]+)(?:\s*:\s*(.*))?$/);
539
+ if (connMatch) {
540
+ const fromId = sanitizeId3(connMatch[1]);
541
+ const rawOp = connMatch[2];
542
+ const toId = sanitizeId3(connMatch[3]);
543
+ let label = connMatch[4]?.trim();
544
+ if (label?.startsWith('"') && label.endsWith('"')) {
545
+ label = label.slice(1, -1);
546
+ }
547
+ if (!nodes.has(fromId)) {
548
+ nodes.set(fromId, { id: fromId, label: fromId, kind: inferNodeKind(fromId) });
549
+ }
550
+ if (!nodes.has(toId)) {
551
+ nodes.set(toId, { id: toId, label: toId, kind: inferNodeKind(toId) });
552
+ }
553
+ if (rawOp === "<->") {
554
+ edges.push({ from: fromId, to: toId, op: "->", label });
555
+ edges.push({ from: toId, to: fromId, op: "<-", label });
556
+ continue;
557
+ }
558
+ let op = "->";
559
+ if (rawOp === "<-") op = "<-";
560
+ else if (rawOp === "--" || rawOp === ".." || rawOp === "..>") op = "..>";
561
+ edges.push({ from: fromId, to: toId, op, label });
562
+ continue;
563
+ }
564
+ const propMatch = rawLine.match(/^([a-zA-Z0-9_.-]+)\.(shape|icon|style\.fill)\s*:\s*(.*)$/i);
565
+ if (propMatch) {
566
+ const id = sanitizeId3(propMatch[1]);
567
+ const propKey = propMatch[2].toLowerCase();
568
+ let propVal = propMatch[3].trim();
569
+ if (propVal.startsWith('"') && propVal.endsWith('"')) {
570
+ propVal = propVal.slice(1, -1);
571
+ }
572
+ const existing = nodes.get(id) || { id, label: id, kind: inferNodeKind(id) };
573
+ if (propKey === "shape") {
574
+ existing.kind = inferNodeKind(existing.label, propVal);
575
+ } else if (propKey === "icon") {
576
+ existing.icon = propVal.toLowerCase().replace(/[^a-z0-9]/g, "");
577
+ }
578
+ nodes.set(id, existing);
579
+ continue;
580
+ }
581
+ const nodeMatch = rawLine.match(/^([a-zA-Z0-9_.-]+)\s*:\s*(.*)$/);
582
+ if (nodeMatch) {
583
+ const rawKey = nodeMatch[1];
584
+ let label = nodeMatch[2].trim();
585
+ if (label.startsWith('"') && label.endsWith('"')) {
586
+ label = label.slice(1, -1);
587
+ }
588
+ if (rawKey.includes(".")) {
589
+ const parts = rawKey.split(".");
590
+ const parent = sanitizeId3(parts[0]);
591
+ const childId = sanitizeId3(rawKey);
592
+ if (!containers.has(parent)) {
593
+ containers.set(parent, []);
594
+ }
595
+ containers.get(parent).push(childId);
596
+ const kind2 = inferNodeKind(label || childId);
597
+ nodes.set(childId, { id: childId, label: label || childId, kind: kind2 });
598
+ continue;
599
+ }
600
+ const id = sanitizeId3(rawKey);
601
+ const kind = inferNodeKind(label || id);
602
+ nodes.set(id, { id, label: label || id, kind });
603
+ if (currentContainer) {
604
+ containers.get(currentContainer)?.push(id);
605
+ }
606
+ continue;
607
+ }
608
+ if (/^[a-zA-Z0-9_.-]+$/.test(rawLine)) {
609
+ const id = sanitizeId3(rawLine);
610
+ if (!nodes.has(id)) {
611
+ nodes.set(id, { id, label: id, kind: inferNodeKind(id) });
612
+ }
613
+ if (currentContainer) {
614
+ containers.get(currentContainer)?.push(id);
615
+ }
616
+ }
617
+ }
618
+ const outLines = [
619
+ `scene "${sceneTitle}" theme=midnight`,
620
+ `layout LR`,
621
+ ""
622
+ ];
623
+ for (const [, node] of nodes) {
624
+ const iconAttr = node.icon ? ` icon=${node.icon}` : "";
625
+ outLines.push(`${node.kind} ${node.id} "${node.label}"${iconAttr}`);
626
+ }
627
+ if (containers.size > 0) {
628
+ outLines.push("");
629
+ for (const [containerId, members] of containers) {
630
+ if (members.length > 0) {
631
+ outLines.push(`group ${containerId} "${containerId}": ${members.join(" ")}`);
632
+ }
633
+ }
634
+ }
635
+ outLines.push("");
636
+ outLines.push(`beat main_flow "1. Architecture Dataflow":`);
637
+ outLines.push(` show $nodes stagger=40ms`);
638
+ for (const edge of edges) {
639
+ const lbl = edge.label ? ` "${edge.label}"` : "";
640
+ outLines.push(` ${edge.from} ${edge.op} ${edge.to}${lbl}`);
641
+ }
642
+ return {
643
+ markdyScript: outLines.join("\n") + "\n",
644
+ nodeCount: nodes.size,
645
+ edgeCount: edges.length,
646
+ containerCount: containers.size,
647
+ warnings
648
+ };
649
+ }
650
+
651
+ // src/infra/plantuml-transpiler.ts
652
+ function transpilePlantUmlToMarkdy(pumlSource) {
653
+ const lines = pumlSource.split("\n");
654
+ const warnings = [];
655
+ const nodes = /* @__PURE__ */ new Map();
656
+ const edges = [];
657
+ const groups = /* @__PURE__ */ new Map();
658
+ let currentGroup = null;
659
+ let sceneTitle = "PlantUML System Architecture";
660
+ function sanitizeId3(raw) {
661
+ return raw.trim().replace(/[^a-zA-Z0-9_]/g, "_");
662
+ }
663
+ function mapPlantUmlKind(rawType, label) {
664
+ const type = rawType.toLowerCase();
665
+ const lbl = label.toLowerCase();
666
+ if (type === "database" || lbl.includes("database") || lbl.includes("postgres") || lbl.includes("mysql") || lbl.includes("sql")) {
667
+ return "database";
668
+ }
669
+ if (type === "queue" || lbl.includes("queue") || lbl.includes("kafka") || lbl.includes("rabbitmq")) {
670
+ return "queue";
671
+ }
672
+ if (type === "boundary" || lbl.includes("gateway") || lbl.includes("proxy") || lbl.includes("ingress")) {
673
+ return "gateway";
674
+ }
675
+ if (type === "actor" || lbl.includes("user") || lbl.includes("client") || lbl.includes("browser")) {
676
+ return "browser";
677
+ }
678
+ if (lbl.includes("cache") || lbl.includes("redis")) {
679
+ return "cache";
680
+ }
681
+ if (lbl.includes("s3") || lbl.includes("storage") || lbl.includes("blob")) {
682
+ return "storage";
683
+ }
684
+ return "service";
685
+ }
686
+ for (let idx = 0; idx < lines.length; idx++) {
687
+ let line = lines[idx].trim();
688
+ if (!line || line.startsWith("'") || line.startsWith("@startuml") || line.startsWith("@enduml")) {
689
+ continue;
690
+ }
691
+ const titleMatch = line.match(/^title\s+["']?([^"']+)["']?$/i);
692
+ if (titleMatch) {
693
+ sceneTitle = titleMatch[1].trim();
694
+ continue;
695
+ }
696
+ const groupOpenMatch = line.match(/^(?:package|rectangle|node|cloud|frame)\s+(?:"([^"]+)"|(\w+))(?:\s+as\s+(\w+))?\s*\{/i);
697
+ if (groupOpenMatch) {
698
+ const label = groupOpenMatch[1] || groupOpenMatch[2];
699
+ const id = sanitizeId3(groupOpenMatch[3] || label);
700
+ currentGroup = id;
701
+ groups.set(id, { id, label, members: [] });
702
+ continue;
703
+ }
704
+ if (line === "}") {
705
+ currentGroup = null;
706
+ continue;
707
+ }
708
+ const c4NodeMatch = line.match(/^(Person|Person_Ext|System|System_Ext|SystemDb|Container|ContainerDb|ContainerQueue|Component|ComponentDb)\s*\(\s*([a-zA-Z0-9_]+)\s*,\s*["']([^"']+)["'](?:\s*,\s*["']([^"']*)["'])?(?:\s*,\s*["']([^"']*)["'])?\s*\)/i);
709
+ if (c4NodeMatch) {
710
+ const macro = c4NodeMatch[1].toLowerCase();
711
+ const id = sanitizeId3(c4NodeMatch[2]);
712
+ const label = c4NodeMatch[3];
713
+ const techOrDesc = c4NodeMatch[4] || "";
714
+ let kind = "service";
715
+ let icon = "nodejs";
716
+ if (macro.includes("person")) {
717
+ kind = "browser";
718
+ icon = "chrome";
719
+ } else if (macro.includes("db")) {
720
+ kind = "database";
721
+ icon = "postgresql";
722
+ } else if (macro.includes("queue")) {
723
+ kind = "queue";
724
+ icon = "kafka";
725
+ } else if (macro.includes("gateway") || techOrDesc.toLowerCase().includes("gateway") || techOrDesc.toLowerCase().includes("proxy")) {
726
+ kind = "gateway";
727
+ icon = "nginx";
728
+ }
729
+ nodes.set(id, { id, label, kind, icon });
730
+ if (currentGroup && groups.has(currentGroup)) {
731
+ groups.get(currentGroup).members.push(id);
732
+ }
733
+ continue;
734
+ }
735
+ const c4RelMatch = line.match(/^Rel(?:_[RLDUB]|_Neighbor|_Back)?\s*\(\s*([a-zA-Z0-9_]+)\s*,\s*([a-zA-Z0-9_]+)\s*,\s*["']([^"']+)["'](?:\s*,\s*["']([^"']*)["'])?\s*\)/i);
736
+ if (c4RelMatch) {
737
+ const fromId = sanitizeId3(c4RelMatch[1]);
738
+ const toId = sanitizeId3(c4RelMatch[2]);
739
+ const desc = c4RelMatch[3];
740
+ const tech = c4RelMatch[4];
741
+ const fullLabel = tech ? `${desc} (${tech})` : desc;
742
+ if (!nodes.has(fromId)) {
743
+ nodes.set(fromId, { id: fromId, label: fromId, kind: mapPlantUmlKind("service", fromId) });
744
+ }
745
+ if (!nodes.has(toId)) {
746
+ nodes.set(toId, { id: toId, label: toId, kind: mapPlantUmlKind("service", toId) });
747
+ }
748
+ edges.push({ from: fromId, to: toId, op: "->", label: fullLabel });
749
+ continue;
750
+ }
751
+ const declMatch = line.match(/^(component|interface|database|queue|actor|boundary|control|entity|participant|node)\s+(?:"([^"]+)"\s+as\s+(\w+)|(\w+)(?:\s+as\s+(\w+))?|(\w+))/i);
752
+ if (declMatch) {
753
+ const rawType = declMatch[1];
754
+ const label = declMatch[2] || declMatch[4] || declMatch[6];
755
+ const id = sanitizeId3(declMatch[3] || declMatch[5] || declMatch[6] || label);
756
+ nodes.set(id, {
757
+ id,
758
+ label,
759
+ kind: mapPlantUmlKind(rawType, label)
760
+ });
761
+ if (currentGroup && groups.has(currentGroup)) {
762
+ groups.get(currentGroup).members.push(id);
763
+ }
764
+ continue;
765
+ }
766
+ const connMatch = line.match(/^([a-zA-Z0-9_.-]+)\s*(<[-.]+>|[-.]+>|<[-.]+|[-.]+)\s*([a-zA-Z0-9_.-]+)(?:\s*:\s*(.*))?$/);
767
+ if (connMatch) {
768
+ const fromId = sanitizeId3(connMatch[1]);
769
+ const arrow = connMatch[2];
770
+ const toId = sanitizeId3(connMatch[3]);
771
+ let label = connMatch[4]?.trim();
772
+ if (label?.startsWith('"') && label.endsWith('"')) {
773
+ label = label.slice(1, -1);
774
+ }
775
+ if (!nodes.has(fromId)) {
776
+ nodes.set(fromId, { id: fromId, label: fromId, kind: mapPlantUmlKind("service", fromId) });
777
+ }
778
+ if (!nodes.has(toId)) {
779
+ nodes.set(toId, { id: toId, label: toId, kind: mapPlantUmlKind("service", toId) });
780
+ }
781
+ if (arrow.startsWith("<") && arrow.endsWith(">")) {
782
+ edges.push({ from: fromId, to: toId, op: "->", label });
783
+ edges.push({ from: toId, to: fromId, op: "<-", label });
784
+ continue;
785
+ }
786
+ let op = "->";
787
+ if (arrow.startsWith("<")) op = "<-";
788
+ else if (arrow.includes(".") || arrow.startsWith("--") || arrow === "-") op = "..>";
789
+ edges.push({ from: fromId, to: toId, op, label });
790
+ continue;
791
+ }
792
+ warnings.push(`Ignored unsupported PlantUML syntax at line ${idx + 1}: ${line.slice(0, 40)}`);
793
+ }
794
+ const scriptLines = [];
795
+ scriptLines.push(`scene "${sceneTitle}" theme=auto`);
796
+ scriptLines.push(`layout LR`);
797
+ scriptLines.push(``);
798
+ for (const node of nodes.values()) {
799
+ const iconProp = node.icon ? ` icon=${node.icon}` : "";
800
+ scriptLines.push(`${node.kind} ${node.id} "${node.label}"${iconProp}`);
801
+ }
802
+ if (groups.size > 0) {
803
+ scriptLines.push(``);
804
+ for (const group of groups.values()) {
805
+ if (group.members.length > 0) {
806
+ scriptLines.push(`group ${group.id} "${group.label}": ${group.members.join(" ")}`);
807
+ }
808
+ }
809
+ }
810
+ scriptLines.push(``);
811
+ scriptLines.push(`beat system_flow "Imported PlantUML Flow":`);
812
+ scriptLines.push(` show $nodes stagger=50ms`);
813
+ for (const edge of edges) {
814
+ const label = edge.label ? ` "${edge.label}"` : "";
815
+ scriptLines.push(` ${edge.from} ${edge.op} ${edge.to}${label}`);
816
+ }
817
+ return {
818
+ markdyScript: scriptLines.join("\n") + "\n",
819
+ nodeCount: nodes.size,
820
+ edgeCount: edges.length,
821
+ groupCount: groups.size,
822
+ warnings
823
+ };
824
+ }
825
+ export {
826
+ parseDrawioXml,
827
+ parseSimpleYaml,
828
+ transpileD2ToMarkdy,
829
+ transpileDockerComposeToMarkdy,
830
+ transpileDrawioToMarkdy,
831
+ transpileKubernetesManifestsToMarkdy,
832
+ transpileMermaidToMarkdy,
833
+ transpilePlantUmlToMarkdy,
834
+ transpileTerraformStateToMarkdy
835
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * packages/compat/src/mermaid/mermaid-transpiler.ts
3
+ * Transpiles Mermaid Flowcharts and Sequence Diagrams into animated MarkdyScript.
4
+ * Zero external dependencies.
5
+ */
6
+ interface MermaidTranspileResult {
7
+ code: string;
8
+ diagramType: "architecture" | "sequence" | "flowchart";
9
+ nodeCount: number;
10
+ edgeCount: number;
11
+ }
12
+ declare function transpileMermaidToMarkdy(mermaidSource: string, sceneTitle?: string): MermaidTranspileResult;
13
+
14
+ export { type MermaidTranspileResult, transpileMermaidToMarkdy };
@@ -0,0 +1,6 @@
1
+ import {
2
+ transpileMermaidToMarkdy
3
+ } from "../chunk-UQF5UJH6.js";
4
+ export {
5
+ transpileMermaidToMarkdy
6
+ };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@markdy/compat",
3
+ "version": "1.3.0",
4
+ "description": "Universal ingestion transpilers (D2, PlantUML, Mermaid, Docker Compose, Kubernetes, Terraform, Draw.io) and backwards-compatibility gate for Markdy.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ },
19
+ "./mermaid": {
20
+ "types": "./dist/mermaid/mermaid-transpiler.d.ts",
21
+ "import": "./dist/mermaid/mermaid-transpiler.js"
22
+ },
23
+ "./infra": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ }
27
+ },
28
+ "keywords": [
29
+ "markdy",
30
+ "diagram-native",
31
+ "diagram-as-code",
32
+ "transpiler",
33
+ "ingestion",
34
+ "d2",
35
+ "plantuml",
36
+ "mermaid",
37
+ "architecture-diagram"
38
+ ],
39
+ "author": "Hoang Yell <hoangyell@gmail.com> (https://hoangyell.com)",
40
+ "homepage": "https://markdy.com",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/HoangYell/markdy-com.git",
44
+ "directory": "packages/compat"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/HoangYell/markdy-com/issues"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "registry": "https://registry.npmjs.org"
52
+ },
53
+ "dependencies": {
54
+ "@markdy/core": "1.3.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^25.9.5",
58
+ "tsup": "^8.5.1",
59
+ "tsx": "^4.23.1",
60
+ "typescript": "^5.9.3",
61
+ "vitest": "^4.1.7"
62
+ },
63
+ "scripts": {
64
+ "build": "tsup",
65
+ "test": "vitest run",
66
+ "gate": "tsx src/gate.ts",
67
+ "gate:update": "tsx src/gate.ts --update",
68
+ "typecheck": "tsc --noEmit",
69
+ "lint": "tsc --noEmit"
70
+ }
71
+ }