@agilewallaby/c4-model 2.7.0 → 3.0.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/src/index.cjs CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  Component: () => Component,
34
34
  Container: () => Container,
35
35
  ContainerGroup: () => ContainerGroup,
36
+ ELEMENT_KINDS: () => ELEMENT_KINDS,
36
37
  ElementArchetype: () => ElementArchetype,
37
38
  Model: () => Model,
38
39
  ModelGroup: () => ModelGroup,
@@ -44,58 +45,14 @@ __export(index_exports, {
44
45
  View: () => View,
45
46
  Views: () => Views,
46
47
  buildModel: () => buildModel,
47
- buildModelWithCatalog: () => buildModelWithCatalog,
48
+ exportWorkspaceJson: () => exportWorkspaceJson,
49
+ exportWorkspaceJsonFromDsl: () => exportWorkspaceJsonFromDsl,
48
50
  generateDiagrams: () => generateDiagrams,
49
- mergeArchetypeWithOverride: () => mergeArchetypeWithOverride
51
+ mergeArchetypeWithOverride: () => mergeArchetypeWithOverride,
52
+ validateModel: () => validateModel
50
53
  });
51
54
  module.exports = __toCommonJS(index_exports);
52
55
 
53
- // libs/c4-model/src/archetype.ts
54
- var ElementArchetype = class {
55
- constructor(name, elementKind, definition, parent) {
56
- this.name = name;
57
- this.elementKind = elementKind;
58
- this.parent = parent;
59
- this.ownDescription = definition?.description;
60
- this.ownTechnology = definition?.technology;
61
- this.ownTags = definition?.tags ?? [];
62
- this.description = this.ownDescription ?? parent?.description;
63
- this.technology = this.ownTechnology ?? parent?.technology;
64
- this.tags = [...parent?.tags ?? [], ...this.ownTags];
65
- }
66
- ownDescription;
67
- ownTechnology;
68
- ownTags;
69
- description;
70
- technology;
71
- tags;
72
- };
73
- var RelationshipArchetype = class {
74
- constructor(name, definition, parent) {
75
- this.name = name;
76
- this.parent = parent;
77
- this.ownDescription = definition?.description;
78
- this.ownTechnology = definition?.technology;
79
- this.ownTags = definition?.tags ?? [];
80
- this.description = this.ownDescription ?? parent?.description;
81
- this.technology = this.ownTechnology ?? parent?.technology;
82
- this.tags = [...parent?.tags ?? [], ...this.ownTags];
83
- }
84
- ownDescription;
85
- ownTechnology;
86
- ownTags;
87
- description;
88
- technology;
89
- tags;
90
- };
91
- function mergeArchetypeWithOverride(archetype, override) {
92
- return {
93
- description: override?.description ?? archetype.description,
94
- technology: override?.technology ?? archetype.technology,
95
- tags: [...archetype.tags, ...override?.tags ?? []]
96
- };
97
- }
98
-
99
56
  // node_modules/.pnpm/change-case@5.4.4/node_modules/change-case/dist/index.js
100
57
  var SPLIT_LOWER_UPPER_RE = /([\p{Ll}\d])(\p{Lu})/gu;
101
58
  var SPLIT_UPPER_UPPER_RE = /(\p{Lu})([\p{Lu}][\p{Ll}])/gu;
@@ -182,6 +139,64 @@ function splitPrefixSuffix(input, options = {}) {
182
139
  ];
183
140
  }
184
141
 
142
+ // libs/c4-model/src/archetype.ts
143
+ var ELEMENT_KINDS = {
144
+ person: "person",
145
+ softwareSystem: "softwareSystem",
146
+ container: "container",
147
+ component: "component"
148
+ };
149
+ var ElementArchetype = class {
150
+ constructor(name, elementKind, definition, parent) {
151
+ this.name = name;
152
+ this.elementKind = elementKind;
153
+ this.parent = parent;
154
+ this.ownDescription = definition?.description;
155
+ this.ownTechnology = definition?.technology;
156
+ this.ownTags = definition?.tags ?? [];
157
+ this.description = this.ownDescription ?? parent?.description;
158
+ this.technology = this.ownTechnology ?? parent?.technology;
159
+ this.tags = [...parent?.tags ?? [], ...this.ownTags];
160
+ }
161
+ ownDescription;
162
+ ownTechnology;
163
+ ownTags;
164
+ description;
165
+ technology;
166
+ tags;
167
+ get canonicalName() {
168
+ return camelCase(this.name);
169
+ }
170
+ };
171
+ var RelationshipArchetype = class {
172
+ constructor(name, definition, parent) {
173
+ this.name = name;
174
+ this.parent = parent;
175
+ this.ownDescription = definition?.description;
176
+ this.ownTechnology = definition?.technology;
177
+ this.ownTags = definition?.tags ?? [];
178
+ this.description = this.ownDescription ?? parent?.description;
179
+ this.technology = this.ownTechnology ?? parent?.technology;
180
+ this.tags = [...parent?.tags ?? [], ...this.ownTags];
181
+ }
182
+ ownDescription;
183
+ ownTechnology;
184
+ ownTags;
185
+ description;
186
+ technology;
187
+ tags;
188
+ get canonicalName() {
189
+ return camelCase(this.name);
190
+ }
191
+ };
192
+ function mergeArchetypeWithOverride(archetype, override) {
193
+ return {
194
+ description: override?.description ?? archetype.description,
195
+ technology: override?.technology ?? archetype.technology,
196
+ tags: [...archetype.tags, ...override?.tags ?? []]
197
+ };
198
+ }
199
+
185
200
  // libs/c4-model/src/core.ts
186
201
  var Element = class {
187
202
  constructor(name, defaultTags = [], definition, archetype, overrideDefinition) {
@@ -221,9 +236,9 @@ var Element = class {
221
236
  getRelationshipsInHierarchy() {
222
237
  return this._relationships.concat(this.getChildElements().flatMap((element) => element.getRelationshipsInHierarchy()));
223
238
  }
224
- getChildElementNames(path2) {
239
+ getChildElementNames(path4) {
225
240
  const result = Array.from(this.getChildElements()).flatMap((reference) => {
226
- const currentPath = `${path2 ? path2 : "" + this.name}.${reference.name}`;
241
+ const currentPath = `${path4 ? path4 : "" + this.name}.${reference.name}`;
227
242
  return [currentPath, ...reference.getChildElementNames(currentPath)];
228
243
  });
229
244
  return result;
@@ -279,21 +294,46 @@ var Component = class extends TechnicalElement {
279
294
  };
280
295
 
281
296
  // libs/c4-model/src/container.ts
282
- var ContainerGroup = class extends Group {
283
- constructor(name, container) {
297
+ var ContainerGroup = class _ContainerGroup extends Group {
298
+ constructor(name, container, pathSegments = []) {
284
299
  super(name);
285
300
  this.name = name;
286
301
  this.container = container;
302
+ this.pathSegments = pathSegments;
287
303
  }
288
304
  _components = /* @__PURE__ */ new Map();
305
+ _groups = /* @__PURE__ */ new Map();
306
+ get canonicalName() {
307
+ return camelCase([...this.pathSegments, this.name].join(" "));
308
+ }
309
+ get dslName() {
310
+ return [...this.pathSegments, this.name].join("/");
311
+ }
289
312
  component(name, archetypeOrDef, override) {
290
313
  const component = this.container.component(name, archetypeOrDef, override);
291
314
  this._components.set(name, component);
292
315
  return component;
293
316
  }
317
+ group(groupName) {
318
+ let group = this._groups.get(groupName);
319
+ if (!group) {
320
+ group = new _ContainerGroup(groupName, this.container, [...this.pathSegments, this.name]);
321
+ this._groups.set(groupName, group);
322
+ }
323
+ return group;
324
+ }
325
+ getGroups() {
326
+ return Array.from(this._groups.values());
327
+ }
294
328
  getComponents() {
295
329
  return Array.from(this._components.values());
296
330
  }
331
+ getAllComponents() {
332
+ return [
333
+ ...this._components.values(),
334
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllComponents())
335
+ ];
336
+ }
297
337
  };
298
338
  var Container = class extends TechnicalElement {
299
339
  constructor(name, definition, archetype, overrideDefinition) {
@@ -330,7 +370,7 @@ var Container = class extends TechnicalElement {
330
370
  return Array.from(this._groups.values());
331
371
  }
332
372
  getComponentsNotInGroups() {
333
- const componentsInGroups = this.getGroups().flatMap((group) => group.getComponents());
373
+ const componentsInGroups = this.getGroups().flatMap((group) => group.getAllComponents());
334
374
  return Array.from(this._components.values()).filter((component) => !componentsInGroups.includes(component));
335
375
  }
336
376
  getChildElements() {
@@ -339,21 +379,46 @@ var Container = class extends TechnicalElement {
339
379
  };
340
380
 
341
381
  // libs/c4-model/src/softwareSystem.ts
342
- var SoftwareSystemGroup = class extends Group {
343
- constructor(name, softwareSystem) {
382
+ var SoftwareSystemGroup = class _SoftwareSystemGroup extends Group {
383
+ constructor(name, softwareSystem, pathSegments = []) {
344
384
  super(name);
345
385
  this.name = name;
346
386
  this.softwareSystem = softwareSystem;
387
+ this.pathSegments = pathSegments;
347
388
  }
348
389
  _containers = /* @__PURE__ */ new Map();
390
+ _groups = /* @__PURE__ */ new Map();
391
+ get canonicalName() {
392
+ return camelCase([...this.pathSegments, this.name].join(" "));
393
+ }
394
+ get dslName() {
395
+ return [...this.pathSegments, this.name].join("/");
396
+ }
349
397
  container(name, archetypeOrDef, override) {
350
398
  const container = this.softwareSystem.container(name, archetypeOrDef, override);
351
399
  this._containers.set(name, container);
352
400
  return container;
353
401
  }
402
+ group(groupName) {
403
+ let group = this._groups.get(groupName);
404
+ if (!group) {
405
+ group = new _SoftwareSystemGroup(groupName, this.softwareSystem, [...this.pathSegments, this.name]);
406
+ this._groups.set(groupName, group);
407
+ }
408
+ return group;
409
+ }
410
+ getGroups() {
411
+ return Array.from(this._groups.values());
412
+ }
354
413
  getContainers() {
355
414
  return Array.from(this._containers.values());
356
415
  }
416
+ getAllContainers() {
417
+ return [
418
+ ...this._containers.values(),
419
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllContainers())
420
+ ];
421
+ }
357
422
  };
358
423
  var SoftwareSystem = class extends Element {
359
424
  constructor(name, definition, archetype, overrideDefinition) {
@@ -393,7 +458,7 @@ var SoftwareSystem = class extends Element {
393
458
  return Array.from(this._containers.values());
394
459
  }
395
460
  getContainersNotInGroups() {
396
- const containersInGroups = Array.from(this._groups.values()).flatMap((group) => group.getContainers());
461
+ const containersInGroups = Array.from(this._groups.values()).flatMap((group) => group.getAllContainers());
397
462
  return Array.from(this._containers.values()).filter((container) => !containersInGroups.includes(container));
398
463
  }
399
464
  };
@@ -410,14 +475,22 @@ var Person = class extends Element {
410
475
  };
411
476
 
412
477
  // libs/c4-model/src/model.ts
413
- var ModelGroup = class extends Group {
414
- constructor(name, model) {
478
+ var ModelGroup = class _ModelGroup extends Group {
479
+ constructor(name, model, pathSegments = []) {
415
480
  super(name);
416
481
  this.name = name;
417
482
  this.model = model;
483
+ this.pathSegments = pathSegments;
418
484
  }
419
485
  softwareSystems = /* @__PURE__ */ new Map();
420
486
  people = /* @__PURE__ */ new Map();
487
+ _groups = /* @__PURE__ */ new Map();
488
+ get canonicalName() {
489
+ return camelCase([...this.pathSegments, this.name].join(" "));
490
+ }
491
+ get dslName() {
492
+ return [...this.pathSegments, this.name].join("/");
493
+ }
421
494
  softwareSystem(name, archetypeOrDef, override) {
422
495
  const softwareSystem = this.model.softwareSystem(name, archetypeOrDef, override);
423
496
  this.softwareSystems.set(name, softwareSystem);
@@ -428,12 +501,35 @@ var ModelGroup = class extends Group {
428
501
  this.people.set(name, person);
429
502
  return person;
430
503
  }
504
+ group(groupName) {
505
+ let group = this._groups.get(groupName);
506
+ if (!group) {
507
+ group = new _ModelGroup(groupName, this.model, [...this.pathSegments, this.name]);
508
+ this._groups.set(groupName, group);
509
+ }
510
+ return group;
511
+ }
512
+ getGroups() {
513
+ return Array.from(this._groups.values());
514
+ }
431
515
  getSoftwareSystems() {
432
516
  return Array.from(this.softwareSystems.values());
433
517
  }
434
518
  getPeople() {
435
519
  return Array.from(this.people.values());
436
520
  }
521
+ getAllSoftwareSystems() {
522
+ return [
523
+ ...this.softwareSystems.values(),
524
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllSoftwareSystems())
525
+ ];
526
+ }
527
+ getAllPeople() {
528
+ return [
529
+ ...this.people.values(),
530
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllPeople())
531
+ ];
532
+ }
437
533
  };
438
534
  var Model = class {
439
535
  constructor(name) {
@@ -458,7 +554,6 @@ var Model = class {
458
554
  this.softwareSystems.set(name, system);
459
555
  return system;
460
556
  }
461
- // TODO:Should be a Group<SoftwareSystem | Person> if that is added back in
462
557
  group(groupName) {
463
558
  let group = this.groups.get(groupName);
464
559
  if (!group) {
@@ -493,11 +588,11 @@ var Model = class {
493
588
  return Array.from(this.softwareSystems.values());
494
589
  }
495
590
  getPeopleNotInGroups() {
496
- const peopleInGroups = Array.from(this.groups.values()).flatMap((group) => group.getPeople());
591
+ const peopleInGroups = Array.from(this.groups.values()).flatMap((group) => group.getAllPeople());
497
592
  return Array.from(this.people.values()).filter((person) => !peopleInGroups.includes(person));
498
593
  }
499
594
  getSoftwareSystemsNotInGroups() {
500
- const systemsInGroups = Array.from(this.groups.values()).flatMap((group) => group.getSoftwareSystems());
595
+ const systemsInGroups = Array.from(this.groups.values()).flatMap((group) => group.getAllSoftwareSystems());
501
596
  return Array.from(this.softwareSystems.values()).filter((system) => !systemsInGroups.includes(system));
502
597
  }
503
598
  getGroups() {
@@ -517,6 +612,9 @@ var View = class {
517
612
  description;
518
613
  title;
519
614
  _scopes = [];
615
+ _autoLayout;
616
+ _isDefault = false;
617
+ _properties = /* @__PURE__ */ new Map();
520
618
  includeAll() {
521
619
  this._scopes.push("include *");
522
620
  }
@@ -535,15 +633,37 @@ var View = class {
535
633
  excludeExpression(expression) {
536
634
  this._scopes.push(`exclude ${expression}`);
537
635
  }
636
+ autoLayout(direction, rankSeparation, nodeSeparation) {
637
+ this._autoLayout = { direction, rankSeparation, nodeSeparation };
638
+ }
639
+ setDefault() {
640
+ this._isDefault = true;
641
+ }
642
+ addProperty(name, value) {
643
+ this._properties.set(name, value);
644
+ }
538
645
  get scopes() {
539
646
  return this._scopes;
540
647
  }
648
+ get autoLayoutConfig() {
649
+ return this._autoLayout;
650
+ }
651
+ get isDefault() {
652
+ return this._isDefault;
653
+ }
654
+ get properties() {
655
+ return this._properties;
656
+ }
541
657
  };
542
658
  var Views = class {
543
659
  _systemLandscapeViews = /* @__PURE__ */ new Map();
544
660
  _systemContextViews = /* @__PURE__ */ new Map();
545
661
  _containerViews = /* @__PURE__ */ new Map();
546
662
  _componentViews = /* @__PURE__ */ new Map();
663
+ _elementStyles = [];
664
+ _relationshipStyles = [];
665
+ _themes = [];
666
+ _properties = /* @__PURE__ */ new Map();
547
667
  addSystemLandscapeView(key, definition) {
548
668
  const view = new View(key, { subject: void 0, description: definition.description, title: definition.title });
549
669
  this._systemLandscapeViews.set(key, view);
@@ -564,6 +684,18 @@ var Views = class {
564
684
  this._componentViews.set(key, view);
565
685
  return view;
566
686
  }
687
+ addElementStyle(tag, definition) {
688
+ this._elementStyles.push({ tag, definition });
689
+ }
690
+ addRelationshipStyle(tag, definition) {
691
+ this._relationshipStyles.push({ tag, definition });
692
+ }
693
+ addTheme(url) {
694
+ this._themes.push(url);
695
+ }
696
+ addProperty(name, value) {
697
+ this._properties.set(name, value);
698
+ }
567
699
  get systemLandscapeViews() {
568
700
  return Array.from(this._systemLandscapeViews.values());
569
701
  }
@@ -576,6 +708,18 @@ var Views = class {
576
708
  get componentViews() {
577
709
  return Array.from(this._componentViews.values());
578
710
  }
711
+ get elementStyles() {
712
+ return this._elementStyles;
713
+ }
714
+ get relationshipStyles() {
715
+ return this._relationshipStyles;
716
+ }
717
+ get themes() {
718
+ return this._themes;
719
+ }
720
+ get properties() {
721
+ return this._properties;
722
+ }
579
723
  };
580
724
 
581
725
  // libs/c4-model/src/structurizrDslWriter.ts
@@ -631,7 +775,7 @@ var StructurizrDSLWriter = class {
631
775
  if (elementArchetypes.length === 0 && relationshipArchetypes.length === 0) return "";
632
776
  let dsl = this.writeLine(`archetypes {`, level);
633
777
  for (const arch of elementArchetypes) {
634
- const baseType = arch.parent ? arch.parent.name : arch.elementKind;
778
+ const baseType = arch.parent ? arch.parent.canonicalName : arch.elementKind;
635
779
  let inner = "";
636
780
  if (arch.ownDescription) {
637
781
  inner += this.writeLine(`description "${arch.ownDescription}"`, level + 2);
@@ -643,15 +787,15 @@ var StructurizrDSLWriter = class {
643
787
  inner += this.writeLine(`tags ${arch.ownTags.map((t) => `"${t}"`).join(" ")}`, level + 2);
644
788
  }
645
789
  if (inner) {
646
- dsl += this.writeLine(`${arch.name} = ${baseType} {`, level + 1);
790
+ dsl += this.writeLine(`${arch.canonicalName} = ${baseType} {`, level + 1);
647
791
  dsl += inner;
648
792
  dsl += this.writeLine(`}`, level + 1);
649
793
  } else {
650
- dsl += this.writeLine(`${arch.name} = ${baseType} {}`, level + 1);
794
+ dsl += this.writeLine(`${arch.canonicalName} = ${baseType} {}`, level + 1);
651
795
  }
652
796
  }
653
797
  for (const arch of relationshipArchetypes) {
654
- const arrow = arch.parent ? `--${arch.parent.name}->` : `->`;
798
+ const arrow = arch.parent ? `--${arch.parent.canonicalName}->` : `->`;
655
799
  let inner = "";
656
800
  if (arch.ownDescription) {
657
801
  inner += this.writeLine(`description "${arch.ownDescription}"`, level + 2);
@@ -663,11 +807,11 @@ var StructurizrDSLWriter = class {
663
807
  inner += this.writeLine(`tags ${arch.ownTags.map((t) => `"${t}"`).join(" ")}`, level + 2);
664
808
  }
665
809
  if (inner) {
666
- dsl += this.writeLine(`${arch.name} = ${arrow} {`, level + 1);
810
+ dsl += this.writeLine(`${arch.canonicalName} = ${arrow} {`, level + 1);
667
811
  dsl += inner;
668
812
  dsl += this.writeLine(`}`, level + 1);
669
813
  } else {
670
- dsl += this.writeLine(`${arch.name} = ${arrow} {}`, level + 1);
814
+ dsl += this.writeLine(`${arch.canonicalName} = ${arrow} {}`, level + 1);
671
815
  }
672
816
  }
673
817
  dsl += this.writeLine(`}`, level);
@@ -675,7 +819,7 @@ var StructurizrDSLWriter = class {
675
819
  }
676
820
  writeElement(elementType, element, level, closeElement = true) {
677
821
  let elementDsl = "";
678
- const type = element.archetype ? element.archetype.name : elementType;
822
+ const type = element.archetype ? element.archetype.canonicalName : elementType;
679
823
  elementDsl += this.writeLine(`${element.canonicalName} = ${type} "${element.name}" {`, level);
680
824
  if (element.archetype) {
681
825
  const ovr = element.overrideDefinition;
@@ -710,11 +854,17 @@ var StructurizrDSLWriter = class {
710
854
  }
711
855
  writeContainerGroup(group, level) {
712
856
  let containerGroupDsl = "";
713
- containerGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.name}" {`, level);
714
- group.getComponents().forEach((component) => {
715
- containerGroupDsl += this.writeComponent(component, level + 1);
857
+ const hasDirect = group.getComponents().length > 0;
858
+ if (hasDirect) {
859
+ containerGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
860
+ group.getComponents().forEach((component) => {
861
+ containerGroupDsl += this.writeComponent(component, level + 1);
862
+ });
863
+ containerGroupDsl += this.writeLine(`}`, level);
864
+ }
865
+ group.getGroups().forEach((nested) => {
866
+ containerGroupDsl += this.writeContainerGroup(nested, level);
716
867
  });
717
- containerGroupDsl += this.writeLine(`}`, level);
718
868
  return containerGroupDsl;
719
869
  }
720
870
  writeContainer(container, level) {
@@ -734,11 +884,17 @@ var StructurizrDSLWriter = class {
734
884
  }
735
885
  writeSoftwareSystemGroup(group, level) {
736
886
  let softwareSystemGroupDsl = "";
737
- softwareSystemGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.name}" {`, level);
738
- group.getContainers().forEach((container) => {
739
- softwareSystemGroupDsl += this.writeContainer(container, level + 1);
887
+ const hasDirect = group.getContainers().length > 0;
888
+ if (hasDirect) {
889
+ softwareSystemGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
890
+ group.getContainers().forEach((container) => {
891
+ softwareSystemGroupDsl += this.writeContainer(container, level + 1);
892
+ });
893
+ softwareSystemGroupDsl += this.writeLine(`}`, level);
894
+ }
895
+ group.getGroups().forEach((nested) => {
896
+ softwareSystemGroupDsl += this.writeSoftwareSystemGroup(nested, level);
740
897
  });
741
- softwareSystemGroupDsl += this.writeLine(`}`, level);
742
898
  return softwareSystemGroupDsl;
743
899
  }
744
900
  writeSoftwareSystem(softwareSystem, level) {
@@ -756,7 +912,7 @@ var StructurizrDSLWriter = class {
756
912
  writeRelationship(relationship, level) {
757
913
  let dsl = "";
758
914
  if (relationship.archetype) {
759
- const arrow = `--${relationship.archetype.name}->`;
915
+ const arrow = `--${relationship.archetype.canonicalName}->`;
760
916
  const ovr = relationship.overrideDefinition;
761
917
  const desc = ovr?.description ?? relationship.description ?? "uses";
762
918
  dsl += this.writeLine(
@@ -790,21 +946,51 @@ var StructurizrDSLWriter = class {
790
946
  });
791
947
  return relationshipsDsl;
792
948
  }
949
+ hasNestedModelGroups(groups) {
950
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedModelGroups(g.getGroups()));
951
+ }
952
+ hasNestedSoftwareSystemGroups(groups) {
953
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedSoftwareSystemGroups(g.getGroups()));
954
+ }
955
+ hasNestedContainerGroups(groups) {
956
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedContainerGroups(g.getGroups()));
957
+ }
958
+ hasNestedGroups() {
959
+ if (this.hasNestedModelGroups(this.model.getGroups())) return true;
960
+ for (const ss of this.model.getSoftwareSystems()) {
961
+ if (this.hasNestedSoftwareSystemGroups(ss.getGroups())) return true;
962
+ for (const c of ss.getGroups().flatMap((g) => g.getAllContainers())) {
963
+ if (this.hasNestedContainerGroups(c.getGroups())) return true;
964
+ }
965
+ }
966
+ return false;
967
+ }
793
968
  writeModelGroup(group, level) {
794
969
  let modelGroupDsl = "";
795
- modelGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.name}" {`, level);
796
- group.getPeople().forEach((person) => {
797
- modelGroupDsl += this.writeElement("person", person, level + 1);
798
- });
799
- group.getSoftwareSystems().forEach((softwareSystem) => {
800
- modelGroupDsl += this.writeSoftwareSystem(softwareSystem, level + 1);
970
+ const hasDirect = group.getPeople().length > 0 || group.getSoftwareSystems().length > 0;
971
+ if (hasDirect) {
972
+ modelGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
973
+ group.getPeople().forEach((person) => {
974
+ modelGroupDsl += this.writeElement("person", person, level + 1);
975
+ });
976
+ group.getSoftwareSystems().forEach((softwareSystem) => {
977
+ modelGroupDsl += this.writeSoftwareSystem(softwareSystem, level + 1);
978
+ });
979
+ modelGroupDsl += this.writeLine(`}`, level);
980
+ }
981
+ group.getGroups().forEach((nested) => {
982
+ modelGroupDsl += this.writeModelGroup(nested, level);
801
983
  });
802
- modelGroupDsl += this.writeLine(`}`, level);
803
984
  return modelGroupDsl;
804
985
  }
805
986
  writeModel(model, level) {
806
987
  let modelDsl = "";
807
988
  modelDsl += this.writeLine(`model {`, level);
989
+ if (this.hasNestedGroups()) {
990
+ modelDsl += this.writeLine(`properties {`, level + 1);
991
+ modelDsl += this.writeLine(`"structurizr.groupSeparator" "/"`, level + 2);
992
+ modelDsl += this.writeLine(`}`, level + 1);
993
+ }
808
994
  modelDsl += this.writeArchetypes(level + 1);
809
995
  modelDsl += this.writeLine("// Elements", level + 1);
810
996
  model.getPeopleNotInGroups().forEach((person) => {
@@ -827,12 +1013,66 @@ var StructurizrDSLWriter = class {
827
1013
  if (view.title) {
828
1014
  viewDsl += this.writeLine(`title "${view.title}"`, level + 1);
829
1015
  }
1016
+ if (view.isDefault) {
1017
+ viewDsl += this.writeLine("default", level + 1);
1018
+ }
830
1019
  view.scopes.forEach((scope) => {
831
1020
  viewDsl += this.writeLine(`${scope}`, level + 1);
832
1021
  });
1022
+ if (view.autoLayoutConfig) {
1023
+ const { direction, rankSeparation, nodeSeparation } = view.autoLayoutConfig;
1024
+ let line = "autoLayout";
1025
+ if (direction) line += ` ${direction}`;
1026
+ if (rankSeparation !== void 0) line += ` ${rankSeparation}`;
1027
+ if (nodeSeparation !== void 0) line += ` ${nodeSeparation}`;
1028
+ viewDsl += this.writeLine(line, level + 1);
1029
+ }
1030
+ if (view.properties.size > 0) {
1031
+ viewDsl += this.writeLine("properties {", level + 1);
1032
+ for (const [name, value] of view.properties) {
1033
+ viewDsl += this.writeLine(`"${name}" "${value}"`, level + 2);
1034
+ }
1035
+ viewDsl += this.writeLine("}", level + 1);
1036
+ }
833
1037
  viewDsl += this.writeLine(`}`, level);
834
1038
  return viewDsl;
835
1039
  }
1040
+ writeStyles(views, level) {
1041
+ const { elementStyles, relationshipStyles } = views;
1042
+ if (elementStyles.length === 0 && relationshipStyles.length === 0) return "";
1043
+ let dsl = this.writeLine("styles {", level);
1044
+ for (const { tag, definition: d } of elementStyles) {
1045
+ dsl += this.writeLine(`element "${tag}" {`, level + 1);
1046
+ if (d.shape) dsl += this.writeLine(`shape ${d.shape}`, level + 2);
1047
+ if (d.icon) dsl += this.writeLine(`icon "${d.icon}"`, level + 2);
1048
+ if (d.width) dsl += this.writeLine(`width ${d.width}`, level + 2);
1049
+ if (d.height) dsl += this.writeLine(`height ${d.height}`, level + 2);
1050
+ if (d.background) dsl += this.writeLine(`background "${d.background}"`, level + 2);
1051
+ if (d.color) dsl += this.writeLine(`color "${d.color}"`, level + 2);
1052
+ if (d.stroke) dsl += this.writeLine(`stroke "${d.stroke}"`, level + 2);
1053
+ if (d.strokeWidth) dsl += this.writeLine(`strokeWidth ${d.strokeWidth}`, level + 2);
1054
+ if (d.fontSize) dsl += this.writeLine(`fontSize ${d.fontSize}`, level + 2);
1055
+ if (d.border) dsl += this.writeLine(`border ${d.border}`, level + 2);
1056
+ if (d.opacity !== void 0) dsl += this.writeLine(`opacity ${d.opacity}`, level + 2);
1057
+ if (d.metadata !== void 0) dsl += this.writeLine(`metadata ${d.metadata}`, level + 2);
1058
+ if (d.description !== void 0) dsl += this.writeLine(`description ${d.description}`, level + 2);
1059
+ dsl += this.writeLine("}", level + 1);
1060
+ }
1061
+ for (const { tag, definition: d } of relationshipStyles) {
1062
+ dsl += this.writeLine(`relationship "${tag}" {`, level + 1);
1063
+ if (d.thickness) dsl += this.writeLine(`thickness ${d.thickness}`, level + 2);
1064
+ if (d.color) dsl += this.writeLine(`color "${d.color}"`, level + 2);
1065
+ if (d.style) dsl += this.writeLine(`style ${d.style}`, level + 2);
1066
+ if (d.routing) dsl += this.writeLine(`routing ${d.routing}`, level + 2);
1067
+ if (d.fontSize) dsl += this.writeLine(`fontSize ${d.fontSize}`, level + 2);
1068
+ if (d.width) dsl += this.writeLine(`width ${d.width}`, level + 2);
1069
+ if (d.position !== void 0) dsl += this.writeLine(`position ${d.position}`, level + 2);
1070
+ if (d.opacity !== void 0) dsl += this.writeLine(`opacity ${d.opacity}`, level + 2);
1071
+ dsl += this.writeLine("}", level + 1);
1072
+ }
1073
+ dsl += this.writeLine("}", level);
1074
+ return dsl;
1075
+ }
836
1076
  writeViews(views, level) {
837
1077
  let viewDsl = "";
838
1078
  viewDsl += this.writeLine(`views {`, level);
@@ -852,6 +1092,19 @@ var StructurizrDSLWriter = class {
852
1092
  views.componentViews.forEach((view) => {
853
1093
  viewDsl += this.writeView(view, "component", level + 1);
854
1094
  });
1095
+ viewDsl += this.writeStyles(views, level + 1);
1096
+ if (views.themes.length === 1) {
1097
+ viewDsl += this.writeLine(`theme ${views.themes[0]}`, level + 1);
1098
+ } else if (views.themes.length > 1) {
1099
+ viewDsl += this.writeLine(`themes ${views.themes.join(" ")}`, level + 1);
1100
+ }
1101
+ if (views.properties.size > 0) {
1102
+ viewDsl += this.writeLine("properties {", level + 1);
1103
+ for (const [name, value] of views.properties) {
1104
+ viewDsl += this.writeLine(`"${name}" "${value}"`, level + 2);
1105
+ }
1106
+ viewDsl += this.writeLine("}", level + 1);
1107
+ }
855
1108
  viewDsl += this.writeLine(`}`, level);
856
1109
  return viewDsl;
857
1110
  }
@@ -877,8 +1130,8 @@ var import_path = require("path");
877
1130
  var import_url = require("url");
878
1131
  var import_meta = {};
879
1132
  var _dirname = typeof __dirname !== "undefined" ? __dirname : (0, import_path.dirname)((0, import_url.fileURLToPath)(import_meta.url));
880
- async function buildModelWithCatalog(options = {}) {
881
- const { modelName = "model", modules: explicitModules, archetypes = {} } = options;
1133
+ async function buildModel(options = {}) {
1134
+ const { modelName = "model", modules: explicitModules, archetypes = {}, addViews } = options;
882
1135
  const model = new Model(modelName);
883
1136
  let c4Modules;
884
1137
  if (explicitModules) {
@@ -902,14 +1155,16 @@ async function buildModelWithCatalog(options = {}) {
902
1155
  rootCatalog[instance.key] = local;
903
1156
  registrations.push({ instance, key: instance.key, local });
904
1157
  }
1158
+ const dependenciesFor = (key) => Object.fromEntries(Object.entries(rootCatalog).filter(([k]) => k !== key));
905
1159
  for (const { instance, key, local } of registrations) {
906
- const dependencies = Object.fromEntries(Object.entries(rootCatalog).filter(([k]) => k !== key));
907
- instance.buildRelationships(local, dependencies, archetypes);
1160
+ instance.addRelationships(local, dependenciesFor(key), archetypes);
908
1161
  }
909
- return { model, catalog: rootCatalog };
910
- }
911
- async function buildModel(options = {}) {
912
- return (await buildModelWithCatalog(options)).model;
1162
+ const views = new Views();
1163
+ addViews?.(views, rootCatalog);
1164
+ for (const { instance, key, local } of registrations) {
1165
+ instance.addViews?.(views, local, dependenciesFor(key));
1166
+ }
1167
+ return { model, catalog: rootCatalog, views };
913
1168
  }
914
1169
 
915
1170
  // libs/c4-model/src/generateDiagrams.ts
@@ -918,9 +1173,8 @@ var os = __toESM(require("os"), 1);
918
1173
  var path = __toESM(require("path"), 1);
919
1174
  var import_testcontainers = require("testcontainers");
920
1175
  async function generateDiagrams(options) {
921
- const { views: viewsFactory, outputDir, ...buildOptions } = options;
922
- const { model, catalog } = await buildModelWithCatalog(buildOptions);
923
- const views = viewsFactory(catalog);
1176
+ const { outputDir, ...buildOptions } = options;
1177
+ const { model, views } = await buildModel(buildOptions);
924
1178
  const dsl = new StructurizrDSLWriter(model, views).write();
925
1179
  const tmpDir = await fs.promises.mkdtemp(path.join(fs.realpathSync(os.tmpdir()), "c4-diagrams-"));
926
1180
  await fs.promises.writeFile(path.join(tmpDir, "workspace.dsl"), dsl, "utf8");
@@ -941,11 +1195,66 @@ async function generateDiagrams(options) {
941
1195
  }
942
1196
  return generatedFiles;
943
1197
  }
1198
+
1199
+ // libs/c4-model/src/validateModel.ts
1200
+ var fs2 = __toESM(require("fs"), 1);
1201
+ var os2 = __toESM(require("os"), 1);
1202
+ var path2 = __toESM(require("path"), 1);
1203
+ var import_testcontainers2 = require("testcontainers");
1204
+ async function validateModel(model, views) {
1205
+ const dsl = new StructurizrDSLWriter(model, views).write();
1206
+ const tmpDir = await fs2.promises.mkdtemp(path2.join(fs2.realpathSync(os2.tmpdir()), "c4-validate-"));
1207
+ try {
1208
+ await fs2.promises.writeFile(path2.join(tmpDir, "workspace.dsl"), dsl, "utf8");
1209
+ const logs = [];
1210
+ try {
1211
+ await new import_testcontainers2.GenericContainer("structurizr/structurizr").withBindMounts([{ source: tmpDir, target: "/workspace", mode: "rw" }]).withCommand(["validate", "-workspace", "/workspace/workspace.dsl"]).withWaitStrategy(import_testcontainers2.Wait.forOneShotStartup()).withLogConsumer((stream) => stream.on("data", (chunk) => logs.push(chunk.toString()))).start();
1212
+ } catch {
1213
+ throw new Error(`Structurizr validation failed:
1214
+ ${logs.join("")}`);
1215
+ }
1216
+ } finally {
1217
+ await fs2.promises.rm(tmpDir, { recursive: true, force: true });
1218
+ }
1219
+ }
1220
+
1221
+ // libs/c4-model/src/exportWorkspaceJson.ts
1222
+ var fs3 = __toESM(require("fs"), 1);
1223
+ var os3 = __toESM(require("os"), 1);
1224
+ var path3 = __toESM(require("path"), 1);
1225
+ var import_testcontainers3 = require("testcontainers");
1226
+ async function exportWorkspaceJsonFromDsl(dsl) {
1227
+ const tmpDir = await fs3.promises.mkdtemp(path3.join(fs3.realpathSync(os3.tmpdir()), "c4-export-"));
1228
+ try {
1229
+ await fs3.promises.writeFile(path3.join(tmpDir, "workspace.dsl"), dsl, "utf8");
1230
+ const logs = [];
1231
+ try {
1232
+ await new import_testcontainers3.GenericContainer("structurizr/structurizr").withBindMounts([{ source: tmpDir, target: "/workspace", mode: "rw" }]).withCommand(["export", "-w", "/workspace/workspace.dsl", "-f", "json", "-o", "/workspace"]).withWaitStrategy(import_testcontainers3.Wait.forOneShotStartup()).withLogConsumer((stream) => stream.on("data", (chunk) => logs.push(chunk.toString()))).start();
1233
+ } catch {
1234
+ throw new Error(`Structurizr JSON export failed:
1235
+ ${logs.join("")}`);
1236
+ }
1237
+ const files = await fs3.promises.readdir(tmpDir);
1238
+ const jsonFile = files.find((f) => f.endsWith(".json"));
1239
+ if (!jsonFile) {
1240
+ throw new Error(`Structurizr JSON export produced no .json file. Files: ${files.join(", ")}`);
1241
+ }
1242
+ const jsonContent = await fs3.promises.readFile(path3.join(tmpDir, jsonFile), "utf8");
1243
+ return JSON.parse(jsonContent);
1244
+ } finally {
1245
+ await fs3.promises.rm(tmpDir, { recursive: true, force: true });
1246
+ }
1247
+ }
1248
+ async function exportWorkspaceJson(model, views) {
1249
+ const dsl = new StructurizrDSLWriter(model, views).write();
1250
+ return exportWorkspaceJsonFromDsl(dsl);
1251
+ }
944
1252
  // Annotate the CommonJS export names for ESM import in node:
945
1253
  0 && (module.exports = {
946
1254
  Component,
947
1255
  Container,
948
1256
  ContainerGroup,
1257
+ ELEMENT_KINDS,
949
1258
  ElementArchetype,
950
1259
  Model,
951
1260
  ModelGroup,
@@ -957,7 +1266,9 @@ async function generateDiagrams(options) {
957
1266
  View,
958
1267
  Views,
959
1268
  buildModel,
960
- buildModelWithCatalog,
1269
+ exportWorkspaceJson,
1270
+ exportWorkspaceJsonFromDsl,
961
1271
  generateDiagrams,
962
- mergeArchetypeWithOverride
1272
+ mergeArchetypeWithOverride,
1273
+ validateModel
963
1274
  });