@likec4/generators 1.52.0 → 1.53.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.
@@ -0,0 +1,11 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __exportAll = (all, no_symbols) => {
3
+ let target = {};
4
+ for (var name in all) __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true
7
+ });
8
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
9
+ return target;
10
+ };
11
+ export { __exportAll as t };
package/dist/index.d.mts CHANGED
@@ -15,6 +15,8 @@ type DrawioViewModelLike = {
15
15
  $view: ProcessedView<aux.Unknown>;
16
16
  readonly $styles?: LikeC4Styles | null;
17
17
  };
18
+ /** Draw.io export profile: default (round-trip) or leanix (bridge-managed metadata for LeanIX interoperability). */
19
+ type DrawioExportProfile = 'default' | 'leanix';
18
20
  /** Optional overrides for round-trip (e.g. from parsed comment blocks). Keys are node/edge ids from the view. */
19
21
  type GenerateDrawioOptions = {
20
22
  /** Node id -> bbox to use instead of viewmodel layout */layoutOverride?: Record<string, BBox>; /** Node id -> stroke color hex (e.g. from likec4.strokeColor.vertices comment) */
@@ -31,6 +33,13 @@ type GenerateDrawioOptions = {
31
33
  * Set for deterministic output (e.g. tests, content-addressable storage).
32
34
  */
33
35
  modified?: string;
36
+ /**
37
+ * Export profile. When 'leanix', adds bridge-managed metadata (likec4Id, likec4Kind, likec4ViewId,
38
+ * likec4ProjectId, likec4RelationId, bridgeManaged) for round-trip and LeanIX interoperability.
39
+ */
40
+ profile?: DrawioExportProfile; /** Project id (included when profile is 'leanix' as likec4ProjectId). */
41
+ projectId?: string; /** Optional mapping of element kind -> LeanIX fact sheet type (included when profile is 'leanix' as leanixFactSheetType on vertices). */
42
+ leanixFactSheetTypeByKind?: Record<string, string>;
34
43
  };
35
44
  /**
36
45
  * Generate a single DrawIO file from one view.
@@ -79,6 +88,14 @@ declare function buildDrawioExportOptionsForViews(viewIds: string[], sourceConte
79
88
  declare function generateDrawioEditUrl(xml: string): string;
80
89
  //#endregion
81
90
  //#region src/drawio/parse-drawio.d.ts
91
+ /**
92
+ * Decompress draw.io diagram content: base64 → inflateRaw → decodeURIComponent.
93
+ * Exported for tests (error message contract). Handles both Node (Buffer) and browser (atob).
94
+ * @param base64Content - Compressed diagram string from <diagram> inner content.
95
+ * @returns Decoded mxGraphModel XML string.
96
+ * @throws Error when base64 decode, inflate, or URI decode fails.
97
+ */
98
+ declare function decompressDrawioDiagram(base64Content: string): string;
82
99
  /**
83
100
  * One diagram's name, id and raw or compressed content from mxfile (single-tab or one tab in multi-tab).
84
101
  * content is decompressed mxGraphModel XML when the source was compressed.
@@ -184,4 +201,4 @@ declare function generateViewsDataTs(diagrams: Iterable<DiagramView>): string;
184
201
  */
185
202
  declare function generateViewsDataDTs(diagrams: Iterable<DiagramView>): string;
186
203
  //#endregion
187
- export { DEFAULT_DRAWIO_ALL_FILENAME, type DrawioViewModelLike, type GenerateDrawioOptions, buildDrawioExportOptionsForViews, buildDrawioExportOptionsFromSource, generateD2, generateDrawio, generateDrawioEditUrl, generateDrawioMulti, generateLikeC4Model, generateMermaid, generatePuml, generateReactNext, generateReactTypes, generateViewsDataDTs, generateViewsDataJs, generateViewsDataTs, getAllDiagrams, parseDrawioRoundtripComments, parseDrawioToLikeC4, parseDrawioToLikeC4Multi };
204
+ export { DEFAULT_DRAWIO_ALL_FILENAME, type DrawioExportProfile, type DrawioViewModelLike, type GenerateDrawioOptions, buildDrawioExportOptionsForViews, buildDrawioExportOptionsFromSource, decompressDrawioDiagram, generateD2, generateDrawio, generateDrawioEditUrl, generateDrawioMulti, generateLikeC4Model, generateMermaid, generatePuml, generateReactNext, generateReactTypes, generateViewsDataDTs, generateViewsDataJs, generateViewsDataTs, getAllDiagrams, parseDrawioRoundtripComments, parseDrawioToLikeC4, parseDrawioToLikeC4Multi };
package/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import { LikeC4Styles, nonexhaustive } from "@likec4/core";
4
4
  import { RichText, flattenMarkdownOrString } from "@likec4/core/types";
5
5
  import pako from "pako";
6
6
  import JSON5 from "json5";
7
- import { compareNatural, invariant, sortNaturalByFqn } from "@likec4/core/utils";
7
+ import { compareNatural, invariant as invariant$1, sortNaturalByFqn } from "@likec4/core/utils";
8
8
  const capitalizeFirstLetter$2 = (value) => value.charAt(0).toLocaleUpperCase() + value.slice(1);
9
9
  const fqnName$2 = (nodeId) => nodeId.split(".").map(capitalizeFirstLetter$2).join("");
10
10
  const nodeName$2 = (node) => {
@@ -165,6 +165,63 @@ function getAttr(attrs, name) {
165
165
  i = start + 1;
166
166
  }
167
167
  }
168
+ /**
169
+ * Fallback to extract style value from attrs when getAttr(attrs, 'style') returns undefined (e.g. spacing like "style =\"").
170
+ * Looks for style="..." (case-insensitive, optional spaces before '=') and returns the quoted value so the cell gets style when present in XML.
171
+ */
172
+ function extractStyleFromAttrsFallback(attrs) {
173
+ const lower = attrs.toLowerCase();
174
+ let i = lower.indexOf("style");
175
+ while (i !== -1) {
176
+ if (!isAttrBoundaryChar(i === 0 ? " " : attrs[i - 1] ?? " ")) {
177
+ i = lower.indexOf("style", i + 1);
178
+ continue;
179
+ }
180
+ let j = i + 5;
181
+ while (j < attrs.length && isAttrBoundaryChar(attrs[j] ?? "")) j += 1;
182
+ if (j < attrs.length && attrs[j] === "=") {
183
+ j += 1;
184
+ while (j < attrs.length && isAttrBoundaryChar(attrs[j] ?? "")) j += 1;
185
+ if (j < attrs.length && attrs[j] === "\"") {
186
+ const valueStart = j + 1;
187
+ const valueEnd = attrs.indexOf("\"", valueStart);
188
+ if (valueEnd !== -1) return attrs.slice(valueStart, valueEnd);
189
+ }
190
+ }
191
+ i = lower.indexOf("style", i + 1);
192
+ }
193
+ }
194
+ /** Extract style value from full open tag (e.g. from fullTag.slice(0, fullTag.indexOf('>'))). Use when attrs-based extraction missed it. */
195
+ function extractStyleFromOpenTag(fullTag) {
196
+ const gt = fullTag.indexOf(">");
197
+ if (gt === -1) return void 0;
198
+ return getAttr(fullTag.slice(7, gt), "style") ?? extractStyleFromAttrsFallback(fullTag.slice(7, gt));
199
+ }
200
+ /** Max fullTag length to run style re-extraction (avoids scanning huge strings). */
201
+ const MAX_FULLTAG_LENGTH_FOR_STYLE_SCAN = 1e4;
202
+ /** Characters to scan for style=" or style=' when re-extracting from tag (covers open tag). */
203
+ const STYLE_VALUE_SCAN_CHARS = 1500;
204
+ /**
205
+ * Re-extract style attribute value from tag content when getAttr missed it.
206
+ * Scans the first maxScan chars for style="..." or style='...'.
207
+ */
208
+ function extractStyleFromTagContent(fullTag, maxScan = STYLE_VALUE_SCAN_CHARS) {
209
+ const scan = fullTag.slice(0, maxScan);
210
+ const styleDq = scan.toLowerCase().indexOf("style=\"");
211
+ const styleSq = scan.toLowerCase().indexOf("style='");
212
+ const useDq = styleDq !== -1 && (styleSq === -1 || styleDq <= styleSq);
213
+ const styleIdx = useDq ? styleDq : styleSq;
214
+ const quote = styleIdx !== -1 ? useDq ? "\"" : "'" : "";
215
+ if (styleIdx === -1 || !quote) return void 0;
216
+ const valueStart = styleIdx + 7;
217
+ const valueEnd = scan.indexOf(quote, valueStart);
218
+ return valueEnd !== -1 ? scan.slice(valueStart, valueEnd) : void 0;
219
+ }
220
+ /** True when style or fullTag (lowercased) indicates actor/person shape. */
221
+ function styleOrTagIndicatesActor(style, fullTagLower) {
222
+ const s = style?.toLowerCase() ?? "";
223
+ return s.includes("shape=actor") || s.includes("shape=person") || s.includes("umlactor") || fullTagLower.includes("shape=actor") || fullTagLower.includes("shape=person") || fullTagLower.includes("umlactor");
224
+ }
168
225
  /** Find end of XML open tag (first unquoted '>'). Handles both single- and double-quoted attributes. Avoids regex for S5852. */
169
226
  function findOpenTagEnd(xml, start) {
170
227
  let quoteChar = "";
@@ -336,12 +393,15 @@ function buildCellOptionalFields(params) {
336
393
  const relationshipKind = getDecodedStyle(styleMap, "likec4relationshipkind");
337
394
  const notation = getDecodedStyle(styleMap, "likec4notation");
338
395
  const metadata = getDecodedStyle(styleMap, "likec4metadata");
396
+ const likec4Id = getDecodedStyle(styleMap, "likec4id");
397
+ const likec4RelationId = getDecodedStyle(styleMap, "likec4relationid");
339
398
  const optional = {};
340
399
  if (params.valueRaw != null && params.valueRaw !== "") optional.value = decodeXmlEntities(params.valueRaw);
341
400
  if (params.parent != null && params.parent !== "") optional.parent = params.parent;
342
401
  if (params.source != null && params.source !== "") optional.source = params.source;
343
402
  if (params.target != null && params.target !== "") optional.target = params.target;
344
403
  if (params.style != null && params.style !== "") optional.style = params.style;
404
+ else if (params.styleMap.has("shape")) optional.style = `shape=${params.styleMap.get("shape")};`;
345
405
  if (x !== void 0) optional.x = x;
346
406
  if (y !== void 0) optional.y = y;
347
407
  if (width !== void 0) optional.width = width;
@@ -372,6 +432,12 @@ function buildCellOptionalFields(params) {
372
432
  if (relationshipKind != null) optional.relationshipKind = relationshipKind;
373
433
  if (notation != null) optional.notation = notation;
374
434
  if (metadata != null && edge) optional.metadata = metadata;
435
+ if (likec4Id != null && vertex) optional.likec4Id = likec4Id;
436
+ if (likec4RelationId != null && edge) optional.likec4RelationId = likec4RelationId;
437
+ let shapeFromStyle = params.styleMap.get("shape")?.trim();
438
+ const fullTagLower = params.fullTag.toLowerCase();
439
+ if ((shapeFromStyle == null || shapeFromStyle === "") && vertex && styleOrTagIndicatesActor(params.style, fullTagLower)) shapeFromStyle = "actor";
440
+ if (shapeFromStyle != null && shapeFromStyle !== "" && vertex) optional.shapeFromStyle = shapeFromStyle;
375
441
  if (userData.customData != null) optional.customData = userData.customData;
376
442
  if (edge) {
377
443
  const pts = parseEdgePoints(fullTag);
@@ -389,29 +455,37 @@ function buildCellFromMxCell(attrs, inner, fullTag, overrides) {
389
455
  if (!id) return null;
390
456
  const vertex = getAttr(attrs, "vertex") === "1";
391
457
  const edge = getAttr(attrs, "edge") === "1";
392
- const style = getAttr(attrs, "style");
458
+ let style = getAttr(attrs, "style") ?? extractStyleFromAttrsFallback(attrs) ?? extractStyleFromOpenTag(fullTag);
459
+ const styleMissing = !style || style.trim() === "";
460
+ const tagHasActor = fullTag.length < MAX_FULLTAG_LENGTH_FOR_STYLE_SCAN && fullTag.toLowerCase().includes("shape=actor");
461
+ if ((styleMissing || tagHasActor && !style?.toLowerCase().includes("shape=actor")) && fullTag.length < MAX_FULLTAG_LENGTH_FOR_STYLE_SCAN) {
462
+ const reExtracted = extractStyleFromTagContent(fullTag);
463
+ if (reExtracted != null) style = reExtracted;
464
+ }
393
465
  const geomStr = extractMxGeometryOpenTag(fullTag);
394
- const styleMap = parseStyle(style ?? void 0);
466
+ const styleMap = parseStyle(style?.trim() || void 0);
395
467
  const userData = parseUserData(inner);
396
468
  const navigateTo = overrides?.navigateTo ?? getDecodedStyle(styleMap, "likec4navigateto");
469
+ const optional = buildCellOptionalFields({
470
+ valueRaw: getAttr(attrs, "value"),
471
+ parent: getAttr(attrs, "parent"),
472
+ source: getAttr(attrs, "source"),
473
+ target: getAttr(attrs, "target"),
474
+ style,
475
+ styleMap,
476
+ userData,
477
+ geomStr,
478
+ fullTag,
479
+ vertex,
480
+ edge,
481
+ navigateTo
482
+ });
397
483
  return {
398
484
  id,
399
485
  vertex,
400
486
  edge,
401
- ...buildCellOptionalFields({
402
- valueRaw: getAttr(attrs, "value"),
403
- parent: getAttr(attrs, "parent"),
404
- source: getAttr(attrs, "source"),
405
- target: getAttr(attrs, "target"),
406
- style: getAttr(attrs, "style"),
407
- styleMap,
408
- userData,
409
- geomStr,
410
- fullTag,
411
- vertex,
412
- edge,
413
- navigateTo
414
- })
487
+ ...style != null && style !== "" ? { style } : {},
488
+ ...optional
415
489
  };
416
490
  }
417
491
  /** Extract one mxCell from xml starting at tagStart. Returns attrs, inner, fullTag and next search index, or null. */
@@ -516,26 +590,32 @@ function likec4LineType(dashed, dashPattern) {
516
590
  return "dashed";
517
591
  }
518
592
  }
593
+ /** True when style or shapeFromStyle indicates actor/person (DrawIO shape=actor, shape=person, umlActor). */
594
+ function isActorShapeInStyle(style, shapeFromStyle) {
595
+ const s = style?.toLowerCase() ?? "";
596
+ const shape = shapeFromStyle?.toLowerCase().trim();
597
+ return shape === "actor" || shape === "person" || s.includes("shape=actor") || s.includes("shape=person") || s.includes("umlactor");
598
+ }
519
599
  /**
520
600
  * Infer LikeC4 element kind from DrawIO shape style. When parent is a container (container=1), child is component.
521
601
  * Explicit container=1 in style → system (context box); others default to container unless actor/swimlane.
602
+ * Uses shapeFromStyle when raw style is missing so actor/person is still inferred.
522
603
  */
523
- function inferKind(style, parentCell) {
604
+ function inferKind(style, parentCell, shapeFromStyle) {
524
605
  const s = style?.toLowerCase() ?? "";
606
+ if (isActorShapeInStyle(style, shapeFromStyle)) return "actor";
525
607
  switch (true) {
526
- case !style: return parentCell?.style?.toLowerCase().includes("container=1") ? "component" : "container";
527
- case s.includes("umlactor") || s.includes("shape=person") || s.includes("shape=actor"): return "actor";
608
+ case !style && !shapeFromStyle: return parentCell?.style?.toLowerCase().includes("container=1") ? "component" : "container";
528
609
  case s.includes("swimlane"):
529
610
  case s.includes("container=1"): return "system";
530
611
  case !!parentCell?.style?.toLowerCase().includes("container=1"): return "component";
531
612
  default: return "container";
532
613
  }
533
614
  }
534
- /** Infer LikeC4 shape from DrawIO style when possible (person, cylinder, document, etc.). */
535
- function inferShape(style) {
536
- if (!style) return void 0;
537
- const s = style.toLowerCase();
538
- if (s.includes("shape=actor") || s.includes("shape=person") || s.includes("umlactor")) return "person";
615
+ /** Infer LikeC4 shape from DrawIO style (or shapeFromStyle) when possible (person, cylinder, document, etc.). */
616
+ function inferShape(style, shapeFromStyle) {
617
+ const s = style?.toLowerCase() ?? "";
618
+ if (isActorShapeInStyle(style, shapeFromStyle)) return "person";
539
619
  if (s.includes("shape=cylinder") || s.includes("cylinder3")) return "cylinder";
540
620
  if (s.includes("shape=document")) return "document";
541
621
  if (s.includes("shape=rectangle") && s.includes("rounded")) return "rectangle";
@@ -557,10 +637,41 @@ function makeUniqueName(usedNames) {
557
637
  return n;
558
638
  };
559
639
  }
560
- /** Assign FQNs to element vertices: root first, then hierarchy by parent, then orphans (DRY). */
561
- function assignFqnsToElementVertices(idToFqn, elementVertices, containerIdToTitle, isRootParent, uniqueName) {
640
+ /** True when s is a syntactically valid dot-separated FQN (each segment non-empty, identifier-like). */
641
+ function isValidFqn(s) {
642
+ if (s.length === 0) return false;
643
+ return s.split(".").every((seg) => /^[a-zA-Z0-9_-]+$/.test(seg));
644
+ }
645
+ /** Depth of vertex from root (0 = root or parent not in diagram). Cycle-safe: cycles in parent graph return 0. */
646
+ function vertexDepth(v, idToVertex, visited = /* @__PURE__ */ new Set()) {
647
+ if (visited.has(v.id)) return 0;
648
+ visited.add(v.id);
649
+ if (v.parent == null || !idToVertex.has(v.parent)) return 0;
650
+ return 1 + vertexDepth(idToVertex.get(v.parent), idToVertex, visited);
651
+ }
652
+ /** True when bridgeId is valid FQN and matches parent chain (root or prefix). */
653
+ function isUsableBridgeId(bridgeId, v, idToFqn, _idToVertex, isRootParent) {
654
+ if (!isValidFqn(bridgeId)) return false;
655
+ const parentFqn = v.parent ? idToFqn.get(v.parent) : void 0;
656
+ if (parentFqn === void 0) return isRootParent(v.parent);
657
+ return bridgeId.startsWith(parentFqn + ".") && bridgeId.length > parentFqn.length + 1;
658
+ }
659
+ /** Assign FQNs to element vertices: bridge-managed likec4Id first (when valid), then root, hierarchy, orphans (DRY). */
660
+ function assignFqnsToElementVertices(idToFqn, elementVertices, containerIdToTitle, isRootParent, uniqueName, usedNames) {
562
661
  const baseName = (v) => v.value ?? containerIdToTitle.get(v.id) ?? v.id;
563
- for (const v of elementVertices) if (isRootParent(v.parent)) idToFqn.set(v.id, uniqueName(baseName(v)));
662
+ const idToVertex = new Map(elementVertices.map((v) => [v.id, v]));
663
+ const byDepth = [...elementVertices].sort((a, b) => vertexDepth(a, idToVertex) - vertexDepth(b, idToVertex));
664
+ for (const v of byDepth) {
665
+ const bridgeId = v.likec4Id?.trim();
666
+ if (bridgeId && isUsableBridgeId(bridgeId, v, idToFqn, idToVertex, isRootParent)) {
667
+ idToFqn.set(v.id, bridgeId);
668
+ for (const segment of bridgeId.split(".")) usedNames.add(segment);
669
+ }
670
+ }
671
+ for (const v of elementVertices) {
672
+ if (idToFqn.has(v.id)) continue;
673
+ if (isRootParent(v.parent)) idToFqn.set(v.id, uniqueName(baseName(v)));
674
+ }
564
675
  let changed = true;
565
676
  while (changed) {
566
677
  changed = false;
@@ -686,7 +797,7 @@ function pushElementHeader(ctx, pad, name, kind, title) {
686
797
  function pushElementStyleBlock(ctx, pad, cell, colorName) {
687
798
  const border = cell.border?.trim();
688
799
  const opacityVal = cell.opacity;
689
- const shapeOverride = inferShape(cell.style);
800
+ const shapeOverride = inferShape(cell.style, cell.shapeFromStyle);
690
801
  const sizeVal = cell.size?.trim();
691
802
  const paddingVal = cell.padding?.trim();
692
803
  const textSizeVal = cell.textSize?.trim();
@@ -734,7 +845,7 @@ function pushElementLinks(ctx, pad, linksJson, nativeLink) {
734
845
  }
735
846
  /** Whether element has any body content (Clean Code: single place for hasBody condition). */
736
847
  function elementHasBody(cell, childList, colorName, opts) {
737
- return (childList?.length ?? 0) > 0 || !!opts.desc || !!opts.tech || !!opts.notes || !!opts.summary || !!opts.linksJson || !!opts.nativeLink || !!opts.notation || opts.tagList.length > 0 || !!colorName || !!cell.border?.trim() || !!cell.opacity || !!inferShape(cell.style) || !!cell.size || !!cell.padding || !!cell.textSize || !!cell.iconPosition || !!opts.navigateTo || !!opts.icon;
848
+ return (childList?.length ?? 0) > 0 || !!opts.desc || !!opts.tech || !!opts.notes || !!opts.summary || !!opts.linksJson || !!opts.nativeLink || !!opts.notation || opts.tagList.length > 0 || !!colorName || !!cell.border?.trim() || !!cell.opacity || !!inferShape(cell.style, cell.shapeFromStyle) || !!cell.size || !!cell.padding || !!cell.textSize || !!cell.iconPosition || !!opts.navigateTo || !!opts.icon;
738
849
  }
739
850
  /** Push element body lines (style, tags, description, links, children). */
740
851
  function pushElementBody(ctx, pad, cell, childList, fqn, indent, colorName, opts) {
@@ -757,7 +868,7 @@ function emitElementToLines(ctx, cellId, fqn, indent) {
757
868
  const cell = ctx.idToCell.get(cellId);
758
869
  if (!cell) return;
759
870
  const parentCell = cell.parent ? ctx.byId.get(cell.parent) : void 0;
760
- const kind = inferKind(cell.style, parentCell);
871
+ const kind = inferKind(cell.style, parentCell, cell.shapeFromStyle);
761
872
  const title = stripHtml(cell.value && cell.value.trim() || "") || (ctx.containerIdToTitle.get(cell.id) ?? ctx.containerIdToTitle.get(cellId) ?? "") || fqn.split(".").pop() || "Element";
762
873
  const name = fqn.split(".").pop();
763
874
  const pad = " ".repeat(indent);
@@ -1018,7 +1129,8 @@ function buildCommonDiagramStateFromCells(cells, diagramName) {
1018
1129
  for (const v of vertices) idToCell.set(v.id, v);
1019
1130
  const { containerIdToTitle, titleCellIds } = computeContainerTitles(vertices);
1020
1131
  const elementVertices = vertices.filter((v) => !titleCellIds.has(v.id));
1021
- assignFqnsToElementVertices(idToFqn, elementVertices, containerIdToTitle, isRootParent, makeUniqueName(/* @__PURE__ */ new Set()));
1132
+ const usedNames = /* @__PURE__ */ new Set();
1133
+ assignFqnsToElementVertices(idToFqn, elementVertices, containerIdToTitle, isRootParent, makeUniqueName(usedNames), usedNames);
1022
1134
  const hexToCustomName = buildHexToCustomName(elementVertices, edges);
1023
1135
  const children = /* @__PURE__ */ new Map();
1024
1136
  const roots = [];
@@ -1650,6 +1762,25 @@ function buildLikec4StyleForNode(params) {
1650
1762
  pushStylePart(parts, "likec4Notation", params.nodeNotation ?? void 0);
1651
1763
  return parts.length > 0 ? parts.join(";") + ";" : "";
1652
1764
  }
1765
+ /** Bridge-managed style parts for profile 'leanix': likec4Id, likec4Kind, likec4ViewId, likec4ProjectId, bridgeManaged, optional leanixFactSheetType. */
1766
+ function buildBridgeManagedStyleForNode(nodeId, nodeKind, viewId, options) {
1767
+ if (options?.profile !== "leanix") return "";
1768
+ const parts = [
1769
+ "bridgeManaged=true",
1770
+ `likec4Id=${encodeURIComponent(nodeId)}`,
1771
+ `likec4Kind=${encodeURIComponent(nodeKind)}`,
1772
+ `likec4ViewId=${encodeURIComponent(viewId)}`
1773
+ ];
1774
+ if (options.projectId != null && options.projectId !== "") parts.push(`likec4ProjectId=${encodeURIComponent(options.projectId)}`);
1775
+ const factSheetType = options.leanixFactSheetTypeByKind?.[nodeKind];
1776
+ if (factSheetType != null && factSheetType !== "") parts.push(`leanixFactSheetType=${encodeURIComponent(factSheetType)}`);
1777
+ return parts.join(";") + ";";
1778
+ }
1779
+ /** Bridge-managed style parts for edge when profile is 'leanix': likec4RelationId, bridgeManaged. */
1780
+ function buildBridgeManagedStyleForEdge(relationId, options) {
1781
+ if (options?.profile !== "leanix") return "";
1782
+ return `bridgeManaged=true;likec4RelationId=${encodeURIComponent(relationId)};`;
1783
+ }
1653
1784
  /** Build mxUserObject XML from customData for round-trip; returns empty string when customData is missing or empty. */
1654
1785
  function buildMxUserObjectXml(customData) {
1655
1786
  if (!customData || typeof customData !== "object" || Array.isArray(customData) || Object.keys(customData).length === 0) return "";
@@ -1682,8 +1813,8 @@ function buildEdgeGeometryXml(edge, edgeWaypoints) {
1682
1813
  if (!(edgePoints.length > 0)) return "<mxGeometry relative=\"1\" as=\"geometry\" />";
1683
1814
  return `<mxGeometry relative="1" as="geometry">${"<Array as=\"points\">" + edgePoints.map(([px, py]) => `<mxPoint x="${Math.round(px)}" y="${Math.round(py)}"/>`).join("") + "</Array>"}</mxGeometry>`;
1684
1815
  }
1685
- /** Full edge style string for mxCell (arrows, anchors, stroke, dash, label, likec4 roundtrip). */
1686
- function buildEdgeStyleString(edge, layout, viewmodel, label) {
1816
+ /** Full edge style string for mxCell (arrows, anchors, stroke, dash, label, likec4 roundtrip, optional bridge-managed). */
1817
+ function buildEdgeStyleString(edge, layout, viewmodel, label, options) {
1687
1818
  const { bboxes, fontFamily } = layout;
1688
1819
  const sourceBbox = bboxes.get(edge.source);
1689
1820
  const targetBbox = bboxes.get(edge.target);
@@ -1708,8 +1839,9 @@ function buildEdgeStyleString(edge, layout, viewmodel, label) {
1708
1839
  edgeLinksJson: linksToStyleJson(edgeOptionalFields.getLinks(edge)),
1709
1840
  edgeMetadataJson: metadataToStyleJson(edgeOptionalFields.getMetadata(edge))
1710
1841
  });
1842
+ const edgeBridgeStyle = buildBridgeManagedStyleForEdge(edge.id, options);
1711
1843
  const edgeLabelColors = getEdgeLabelColors(viewmodel, edge.color);
1712
- return `endArrow=${endArrow};startArrow=${startArrow};html=1;rounded=0;${anchorStyle}strokeColor=${strokeColor};strokeWidth=2;${dashStyle}${label === "" ? "" : `fontColor=${edgeLabelColors.font};fontSize=12;align=center;verticalAlign=middle;labelBackgroundColor=none;fontFamily=${encodeURIComponent(fontFamily)};`}${edgeLikec4Style}`;
1844
+ return `endArrow=${endArrow};startArrow=${startArrow};html=1;rounded=0;${anchorStyle}strokeColor=${strokeColor};strokeWidth=2;${dashStyle}${label === "" ? "" : `fontColor=${edgeLabelColors.font};fontSize=12;align=center;verticalAlign=middle;labelBackgroundColor=none;fontFamily=${encodeURIComponent(fontFamily)};`}${edgeLikec4Style}${edgeBridgeStyle}`;
1713
1845
  }
1714
1846
  /** Build a single edge mxCell XML (orchestrator: label + geometry + style + assembly). */
1715
1847
  function buildEdgeCellXml(edge, layout, options, viewmodel, getCellId, edgeCellId) {
@@ -1718,7 +1850,7 @@ function buildEdgeCellXml(edge, layout, options, viewmodel, getCellId, edgeCellI
1718
1850
  const targetId = getCellId(edge.target);
1719
1851
  const label = buildEdgeLabelValue(edge);
1720
1852
  const edgeGeometryXml = buildEdgeGeometryXml(edge, options?.edgeWaypoints);
1721
- return `<mxCell id="${edgeCellId}" value="${label}" style="${buildEdgeStyleString(edge, layout, viewmodel, label)}" edge="1" parent="${defaultParentId}" source="${sourceId}" target="${targetId}">
1853
+ return `<mxCell id="${edgeCellId}" value="${label}" style="${buildEdgeStyleString(edge, layout, viewmodel, label, options)}" edge="1" parent="${defaultParentId}" source="${sourceId}" target="${targetId}">
1722
1854
  ${edgeGeometryXml}${buildMxUserObjectXml(edgeOptionalFields.getCustomData(edge))}
1723
1855
  </mxCell>`;
1724
1856
  }
@@ -1745,6 +1877,7 @@ function computeNodeStylePartsAndValue(node, layout, options, viewmodel) {
1745
1877
  const strokeColorByNodeId = options?.strokeColorByNodeId;
1746
1878
  const strokeWidthByNodeId = options?.strokeWidthByNodeId;
1747
1879
  const isContainer = containerNodeIds.has(node.id);
1880
+ const nodeKind = node.kind ?? "";
1748
1881
  const title = node.title;
1749
1882
  const desc = toExportString(node.description);
1750
1883
  const tech = toExportString(node.technology);
@@ -1753,7 +1886,8 @@ function computeNodeStylePartsAndValue(node, layout, options, viewmodel) {
1753
1886
  const tagList = Array.isArray(tags) && tags.length > 0 ? tags.join(",") : "";
1754
1887
  const navTo = toNonEmptyString(nodeOptionalFields.getNavigateTo(node));
1755
1888
  const iconName = toNonEmptyString(nodeOptionalFields.getIcon(node));
1756
- const shapeStyle = isContainer ? "shape=rectangle;rounded=0;container=1;collapsible=0;startSize=0;" : drawioShape(node.shape);
1889
+ const isActor = nodeKind === "actor" || node.shape === "person";
1890
+ const shapeStyle = isContainer ? "shape=rectangle;rounded=0;container=1;collapsible=0;startSize=0;" : isActor ? "shape=actor;" : drawioShape(node.shape);
1757
1891
  const strokeColorOverride = strokeColorByNodeId?.[node.id];
1758
1892
  const strokeWidthOverride = strokeWidthByNodeId?.[node.id];
1759
1893
  const elemColors = strokeColorOverride ? applyStrokeColorOverride(getElementColors(viewmodel, node.color), strokeColorOverride) : getElementColors(viewmodel, node.color);
@@ -1770,7 +1904,7 @@ function computeNodeStylePartsAndValue(node, layout, options, viewmodel) {
1770
1904
  const containerDashed = getContainerDashedStyle(isContainer, borderVal);
1771
1905
  const containerOpacityNum = isContainer === true ? nodeStyle?.opacity ?? DEFAULT_CONTAINER_OPACITY : void 0;
1772
1906
  const fillOpacityStyle = containerOpacityNum != null && isContainer === true ? `fillOpacity=${Math.min(100, Math.max(0, containerOpacityNum))};` : "";
1773
- const likec4Style = buildLikec4StyleForNode({
1907
+ const likec4StyleWithBridge = buildLikec4StyleForNode({
1774
1908
  desc,
1775
1909
  tech,
1776
1910
  notes,
@@ -1786,12 +1920,12 @@ function computeNodeStylePartsAndValue(node, layout, options, viewmodel) {
1786
1920
  nodeStyle,
1787
1921
  strokeHex,
1788
1922
  nodeNotation: nodeOptionalFields.getNotation(node)
1789
- });
1923
+ }) + buildBridgeManagedStyleForNode(node.id, nodeKind, layout.view.id, options);
1790
1924
  const userObjectXml = buildMxUserObjectXml(nodeOptionalFields.getCustomData(node));
1791
1925
  const navLinkStyle = buildNavLinkStyle(navTo);
1792
1926
  return {
1793
1927
  value,
1794
- styleStr: `${isContainer ? "align=left;verticalAlign=top;overflow=fill;whiteSpace=wrap;html=1;" : `align=center;verticalAlign=middle;verticalLabelPosition=middle;labelPosition=center;fontSize=${fontSizePx};fontStyle=1;spacingTop=4;spacingLeft=2;spacingRight=2;spacingBottom=2;overflow=fill;whiteSpace=wrap;html=1;fontFamily=${encodeURIComponent(fontFamily)};`}${shapeStyle}${colorStyle}${strokeWidthStyle}${containerDashed}${fillOpacityStyle}${navLinkStyle}${likec4Style}`,
1928
+ styleStr: `${isContainer ? "align=left;verticalAlign=top;overflow=fill;whiteSpace=wrap;html=1;" : `align=center;verticalAlign=middle;verticalLabelPosition=middle;labelPosition=center;fontSize=${fontSizePx};fontStyle=1;spacingTop=4;spacingLeft=2;spacingRight=2;spacingBottom=2;overflow=fill;whiteSpace=wrap;html=1;fontFamily=${encodeURIComponent(fontFamily)};`}${shapeStyle}${colorStyle}${strokeWidthStyle}${containerDashed}${fillOpacityStyle}${navLinkStyle}${likec4StyleWithBridge}`,
1795
1929
  userObjectXml,
1796
1930
  navTo,
1797
1931
  isContainer,
@@ -1862,20 +1996,28 @@ function getViewDescriptionString(view) {
1862
1996
  if (typeof raw === "string") return raw;
1863
1997
  return "";
1864
1998
  }
1865
- /** Build root cell style string from view metadata (title, description, notation) for round-trip. */
1866
- function buildRootCellStyle(view) {
1999
+ /** Returns draw.io style tokens for the leanix profile (bridgeManaged, likec4ViewId, likec4ProjectId). Each token ends with ";". */
2000
+ function getLeanixRootStyleParts(view, options) {
2001
+ const parts = ["bridgeManaged=true;", `likec4ViewId=${encodeURIComponent(view.id)};`];
2002
+ if (options.projectId != null && options.projectId !== "") parts.push(`likec4ProjectId=${encodeURIComponent(options.projectId)};`);
2003
+ return parts;
2004
+ }
2005
+ /** Build root cell style string from view metadata (title, description, notation) for round-trip; when profile is 'leanix' adds likec4ViewId, likec4ProjectId, bridgeManaged. */
2006
+ function buildRootCellStyle(view, options) {
1867
2007
  const viewTitle = getViewTitle(view);
1868
2008
  const viewDesc = getViewDescriptionString(view);
1869
2009
  const viewDescEnc = viewDesc.trim() !== "" ? encodeURIComponent(viewDesc.trim()) : "";
1870
2010
  const viewNotationRaw = view.notation;
1871
2011
  const viewNotation = typeof viewNotationRaw === "string" && viewNotationRaw !== "" ? viewNotationRaw : void 0;
1872
2012
  const viewNotationEnc = viewNotation != null ? encodeURIComponent(viewNotation) : "";
1873
- return [
2013
+ const rootParts = [
1874
2014
  "rounded=1;whiteSpace=wrap;html=1;fillColor=none;strokeColor=none;",
1875
2015
  `likec4ViewTitle=${encodeURIComponent(viewTitle ?? view.id)};`,
1876
2016
  viewDescEnc !== "" ? `likec4ViewDescription=${viewDescEnc};` : "",
1877
2017
  viewNotationEnc !== "" ? `likec4ViewNotation=${viewNotationEnc};` : ""
1878
- ].join("");
2018
+ ];
2019
+ if (options?.profile === "leanix") rootParts.push(...getLeanixRootStyleParts(view, options));
2020
+ return rootParts.join("");
1879
2021
  }
1880
2022
  /**
1881
2023
  * Map LikeC4 RelationshipArrowType to draw.io endArrow/startArrow style value.
@@ -2078,7 +2220,7 @@ function generateDiagramContent(viewmodel, options) {
2078
2220
  edgeCells.push(buildEdgeCellXml(edge, layout, options, viewmodel, getCellId, edgeId));
2079
2221
  }
2080
2222
  const allCells = [
2081
- `<mxCell id="${defaultParentId}" value="" style="${buildRootCellStyle(view)}" vertex="1" parent="${rootId}">
2223
+ `<mxCell id="${defaultParentId}" value="" style="${buildRootCellStyle(view, options)}" vertex="1" parent="${rootId}">
2082
2224
  <mxGeometry x="0" y="0" width="${canvasWidth}" height="${canvasHeight}" as="geometry" />
2083
2225
  </mxCell>`,
2084
2226
  ...containerCells,
@@ -2646,7 +2788,7 @@ function generateIndex() {
2646
2788
  }
2647
2789
  function generateReactTypes(model, options = {}) {
2648
2790
  const { useCorePackage = false } = options;
2649
- invariant(!model.isParsed(), "can not generate react types for parsed model");
2791
+ invariant$1(!model.isParsed(), "can not generate react types for parsed model");
2650
2792
  const aux = generateAux(model, options);
2651
2793
  return `
2652
2794
  /* prettier-ignore-start */
@@ -2706,4 +2848,4 @@ export {
2706
2848
  /* prettier-ignore-end */
2707
2849
  `.trimStart();
2708
2850
  }
2709
- export { DEFAULT_DRAWIO_ALL_FILENAME, buildDrawioExportOptionsForViews, buildDrawioExportOptionsFromSource, generateD2, generateDrawio, generateDrawioEditUrl, generateDrawioMulti, generateLikeC4Model, generateMermaid, generatePuml, generateReactNext, generateReactTypes, generateViewsDataDTs, generateViewsDataJs, generateViewsDataTs, getAllDiagrams, parseDrawioRoundtripComments, parseDrawioToLikeC4, parseDrawioToLikeC4Multi };
2851
+ export { DEFAULT_DRAWIO_ALL_FILENAME, buildDrawioExportOptionsForViews, buildDrawioExportOptionsFromSource, decompressDrawioDiagram, generateD2, generateDrawio, generateDrawioEditUrl, generateDrawioMulti, generateLikeC4Model, generateMermaid, generatePuml, generateReactNext, generateReactTypes, generateViewsDataDTs, generateViewsDataJs, generateViewsDataTs, getAllDiagrams, parseDrawioRoundtripComments, parseDrawioToLikeC4, parseDrawioToLikeC4Multi };