@agilewallaby/c4-model 2.6.0 → 2.8.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
@@ -44,9 +44,11 @@ __export(index_exports, {
44
44
  View: () => View,
45
45
  Views: () => Views,
46
46
  buildModel: () => buildModel,
47
- buildModelWithCatalog: () => buildModelWithCatalog,
47
+ exportWorkspaceJson: () => exportWorkspaceJson,
48
+ exportWorkspaceJsonFromDsl: () => exportWorkspaceJsonFromDsl,
48
49
  generateDiagrams: () => generateDiagrams,
49
- mergeArchetypeWithOverride: () => mergeArchetypeWithOverride
50
+ mergeArchetypeWithOverride: () => mergeArchetypeWithOverride,
51
+ validateModel: () => validateModel
50
52
  });
51
53
  module.exports = __toCommonJS(index_exports);
52
54
 
@@ -214,12 +216,16 @@ var Element = class {
214
216
  get relationships() {
215
217
  return this._relationships;
216
218
  }
219
+ with(callback) {
220
+ const children = callback(this);
221
+ return Object.assign(this, children);
222
+ }
217
223
  getRelationshipsInHierarchy() {
218
224
  return this._relationships.concat(this.getChildElements().flatMap((element) => element.getRelationshipsInHierarchy()));
219
225
  }
220
- getChildElementNames(path2) {
226
+ getChildElementNames(path4) {
221
227
  const result = Array.from(this.getChildElements()).flatMap((reference) => {
222
- const currentPath = `${path2 ? path2 : "" + this.name}.${reference.name}`;
228
+ const currentPath = `${path4 ? path4 : "" + this.name}.${reference.name}`;
223
229
  return [currentPath, ...reference.getChildElementNames(currentPath)];
224
230
  });
225
231
  return result;
@@ -252,6 +258,13 @@ var Group = class {
252
258
  constructor(name) {
253
259
  this.name = name;
254
260
  }
261
+ get canonicalName() {
262
+ return camelCase(this.name);
263
+ }
264
+ with(callback) {
265
+ const children = callback(this);
266
+ return Object.assign(this, children);
267
+ }
255
268
  // TODO: Implement this in some useful way?
256
269
  // public addToGroup(groupCollection: string, groupMember: T) {}
257
270
  };
@@ -268,21 +281,46 @@ var Component = class extends TechnicalElement {
268
281
  };
269
282
 
270
283
  // libs/c4-model/src/container.ts
271
- var ContainerGroup = class extends Group {
272
- constructor(name, container) {
284
+ var ContainerGroup = class _ContainerGroup extends Group {
285
+ constructor(name, container, pathSegments = []) {
273
286
  super(name);
274
287
  this.name = name;
275
288
  this.container = container;
289
+ this.pathSegments = pathSegments;
276
290
  }
277
291
  _components = /* @__PURE__ */ new Map();
292
+ _groups = /* @__PURE__ */ new Map();
293
+ get canonicalName() {
294
+ return camelCase([...this.pathSegments, this.name].join(" "));
295
+ }
296
+ get dslName() {
297
+ return [...this.pathSegments, this.name].join("/");
298
+ }
278
299
  component(name, archetypeOrDef, override) {
279
300
  const component = this.container.component(name, archetypeOrDef, override);
280
301
  this._components.set(name, component);
281
302
  return component;
282
303
  }
304
+ group(groupName) {
305
+ let group = this._groups.get(groupName);
306
+ if (!group) {
307
+ group = new _ContainerGroup(groupName, this.container, [...this.pathSegments, this.name]);
308
+ this._groups.set(groupName, group);
309
+ }
310
+ return group;
311
+ }
312
+ getGroups() {
313
+ return Array.from(this._groups.values());
314
+ }
283
315
  getComponents() {
284
316
  return Array.from(this._components.values());
285
317
  }
318
+ getAllComponents() {
319
+ return [
320
+ ...this._components.values(),
321
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllComponents())
322
+ ];
323
+ }
286
324
  };
287
325
  var Container = class extends TechnicalElement {
288
326
  constructor(name, definition, archetype, overrideDefinition) {
@@ -307,7 +345,7 @@ var Container = class extends TechnicalElement {
307
345
  this._components.set(name, component);
308
346
  return component;
309
347
  }
310
- addGroup(groupName) {
348
+ group(groupName) {
311
349
  let group = this._groups.get(groupName);
312
350
  if (!group) {
313
351
  group = new ContainerGroup(groupName, this);
@@ -319,7 +357,7 @@ var Container = class extends TechnicalElement {
319
357
  return Array.from(this._groups.values());
320
358
  }
321
359
  getComponentsNotInGroups() {
322
- const componentsInGroups = this.getGroups().flatMap((group) => group.getComponents());
360
+ const componentsInGroups = this.getGroups().flatMap((group) => group.getAllComponents());
323
361
  return Array.from(this._components.values()).filter((component) => !componentsInGroups.includes(component));
324
362
  }
325
363
  getChildElements() {
@@ -328,21 +366,46 @@ var Container = class extends TechnicalElement {
328
366
  };
329
367
 
330
368
  // libs/c4-model/src/softwareSystem.ts
331
- var SoftwareSystemGroup = class extends Group {
332
- constructor(name, softwareSystem) {
369
+ var SoftwareSystemGroup = class _SoftwareSystemGroup extends Group {
370
+ constructor(name, softwareSystem, pathSegments = []) {
333
371
  super(name);
334
372
  this.name = name;
335
373
  this.softwareSystem = softwareSystem;
374
+ this.pathSegments = pathSegments;
336
375
  }
337
376
  _containers = /* @__PURE__ */ new Map();
377
+ _groups = /* @__PURE__ */ new Map();
378
+ get canonicalName() {
379
+ return camelCase([...this.pathSegments, this.name].join(" "));
380
+ }
381
+ get dslName() {
382
+ return [...this.pathSegments, this.name].join("/");
383
+ }
338
384
  container(name, archetypeOrDef, override) {
339
385
  const container = this.softwareSystem.container(name, archetypeOrDef, override);
340
386
  this._containers.set(name, container);
341
387
  return container;
342
388
  }
389
+ group(groupName) {
390
+ let group = this._groups.get(groupName);
391
+ if (!group) {
392
+ group = new _SoftwareSystemGroup(groupName, this.softwareSystem, [...this.pathSegments, this.name]);
393
+ this._groups.set(groupName, group);
394
+ }
395
+ return group;
396
+ }
397
+ getGroups() {
398
+ return Array.from(this._groups.values());
399
+ }
343
400
  getContainers() {
344
401
  return Array.from(this._containers.values());
345
402
  }
403
+ getAllContainers() {
404
+ return [
405
+ ...this._containers.values(),
406
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllContainers())
407
+ ];
408
+ }
346
409
  };
347
410
  var SoftwareSystem = class extends Element {
348
411
  constructor(name, definition, archetype, overrideDefinition) {
@@ -367,7 +430,7 @@ var SoftwareSystem = class extends Element {
367
430
  this._containers.set(name, container);
368
431
  return container;
369
432
  }
370
- addGroup(groupName) {
433
+ group(groupName) {
371
434
  let group = this._groups.get(groupName);
372
435
  if (!group) {
373
436
  group = new SoftwareSystemGroup(groupName, this);
@@ -382,7 +445,7 @@ var SoftwareSystem = class extends Element {
382
445
  return Array.from(this._containers.values());
383
446
  }
384
447
  getContainersNotInGroups() {
385
- const containersInGroups = Array.from(this._groups.values()).flatMap((group) => group.getContainers());
448
+ const containersInGroups = Array.from(this._groups.values()).flatMap((group) => group.getAllContainers());
386
449
  return Array.from(this._containers.values()).filter((container) => !containersInGroups.includes(container));
387
450
  }
388
451
  };
@@ -399,14 +462,22 @@ var Person = class extends Element {
399
462
  };
400
463
 
401
464
  // libs/c4-model/src/model.ts
402
- var ModelGroup = class extends Group {
403
- constructor(name, model) {
465
+ var ModelGroup = class _ModelGroup extends Group {
466
+ constructor(name, model, pathSegments = []) {
404
467
  super(name);
405
468
  this.name = name;
406
469
  this.model = model;
470
+ this.pathSegments = pathSegments;
407
471
  }
408
472
  softwareSystems = /* @__PURE__ */ new Map();
409
473
  people = /* @__PURE__ */ new Map();
474
+ _groups = /* @__PURE__ */ new Map();
475
+ get canonicalName() {
476
+ return camelCase([...this.pathSegments, this.name].join(" "));
477
+ }
478
+ get dslName() {
479
+ return [...this.pathSegments, this.name].join("/");
480
+ }
410
481
  softwareSystem(name, archetypeOrDef, override) {
411
482
  const softwareSystem = this.model.softwareSystem(name, archetypeOrDef, override);
412
483
  this.softwareSystems.set(name, softwareSystem);
@@ -417,12 +488,35 @@ var ModelGroup = class extends Group {
417
488
  this.people.set(name, person);
418
489
  return person;
419
490
  }
491
+ group(groupName) {
492
+ let group = this._groups.get(groupName);
493
+ if (!group) {
494
+ group = new _ModelGroup(groupName, this.model, [...this.pathSegments, this.name]);
495
+ this._groups.set(groupName, group);
496
+ }
497
+ return group;
498
+ }
499
+ getGroups() {
500
+ return Array.from(this._groups.values());
501
+ }
420
502
  getSoftwareSystems() {
421
503
  return Array.from(this.softwareSystems.values());
422
504
  }
423
505
  getPeople() {
424
506
  return Array.from(this.people.values());
425
507
  }
508
+ getAllSoftwareSystems() {
509
+ return [
510
+ ...this.softwareSystems.values(),
511
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllSoftwareSystems())
512
+ ];
513
+ }
514
+ getAllPeople() {
515
+ return [
516
+ ...this.people.values(),
517
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllPeople())
518
+ ];
519
+ }
426
520
  };
427
521
  var Model = class {
428
522
  constructor(name) {
@@ -447,8 +541,7 @@ var Model = class {
447
541
  this.softwareSystems.set(name, system);
448
542
  return system;
449
543
  }
450
- // TODO:Should be a Group<SoftwareSystem | Person> if that is added back in
451
- addGroup(groupName) {
544
+ group(groupName) {
452
545
  let group = this.groups.get(groupName);
453
546
  if (!group) {
454
547
  group = new ModelGroup(groupName, this);
@@ -472,6 +565,7 @@ var Model = class {
472
565
  this.people.set(name, person);
473
566
  return person;
474
567
  }
568
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
475
569
  validate() {
476
570
  }
477
571
  getPeople() {
@@ -481,11 +575,11 @@ var Model = class {
481
575
  return Array.from(this.softwareSystems.values());
482
576
  }
483
577
  getPeopleNotInGroups() {
484
- const peopleInGroups = Array.from(this.groups.values()).flatMap((group) => group.getPeople());
578
+ const peopleInGroups = Array.from(this.groups.values()).flatMap((group) => group.getAllPeople());
485
579
  return Array.from(this.people.values()).filter((person) => !peopleInGroups.includes(person));
486
580
  }
487
581
  getSoftwareSystemsNotInGroups() {
488
- const systemsInGroups = Array.from(this.groups.values()).flatMap((group) => group.getSoftwareSystems());
582
+ const systemsInGroups = Array.from(this.groups.values()).flatMap((group) => group.getAllSoftwareSystems());
489
583
  return Array.from(this.softwareSystems.values()).filter((system) => !systemsInGroups.includes(system));
490
584
  }
491
585
  getGroups() {
@@ -505,6 +599,9 @@ var View = class {
505
599
  description;
506
600
  title;
507
601
  _scopes = [];
602
+ _autoLayout;
603
+ _isDefault = false;
604
+ _properties = /* @__PURE__ */ new Map();
508
605
  includeAll() {
509
606
  this._scopes.push("include *");
510
607
  }
@@ -523,15 +620,37 @@ var View = class {
523
620
  excludeExpression(expression) {
524
621
  this._scopes.push(`exclude ${expression}`);
525
622
  }
623
+ autoLayout(direction, rankSeparation, nodeSeparation) {
624
+ this._autoLayout = { direction, rankSeparation, nodeSeparation };
625
+ }
626
+ setDefault() {
627
+ this._isDefault = true;
628
+ }
629
+ addProperty(name, value) {
630
+ this._properties.set(name, value);
631
+ }
526
632
  get scopes() {
527
633
  return this._scopes;
528
634
  }
635
+ get autoLayoutConfig() {
636
+ return this._autoLayout;
637
+ }
638
+ get isDefault() {
639
+ return this._isDefault;
640
+ }
641
+ get properties() {
642
+ return this._properties;
643
+ }
529
644
  };
530
645
  var Views = class {
531
646
  _systemLandscapeViews = /* @__PURE__ */ new Map();
532
647
  _systemContextViews = /* @__PURE__ */ new Map();
533
648
  _containerViews = /* @__PURE__ */ new Map();
534
649
  _componentViews = /* @__PURE__ */ new Map();
650
+ _elementStyles = [];
651
+ _relationshipStyles = [];
652
+ _themes = [];
653
+ _properties = /* @__PURE__ */ new Map();
535
654
  addSystemLandscapeView(key, definition) {
536
655
  const view = new View(key, { subject: void 0, description: definition.description, title: definition.title });
537
656
  this._systemLandscapeViews.set(key, view);
@@ -552,6 +671,18 @@ var Views = class {
552
671
  this._componentViews.set(key, view);
553
672
  return view;
554
673
  }
674
+ addElementStyle(tag, definition) {
675
+ this._elementStyles.push({ tag, definition });
676
+ }
677
+ addRelationshipStyle(tag, definition) {
678
+ this._relationshipStyles.push({ tag, definition });
679
+ }
680
+ addTheme(url) {
681
+ this._themes.push(url);
682
+ }
683
+ addProperty(name, value) {
684
+ this._properties.set(name, value);
685
+ }
555
686
  get systemLandscapeViews() {
556
687
  return Array.from(this._systemLandscapeViews.values());
557
688
  }
@@ -564,6 +695,18 @@ var Views = class {
564
695
  get componentViews() {
565
696
  return Array.from(this._componentViews.values());
566
697
  }
698
+ get elementStyles() {
699
+ return this._elementStyles;
700
+ }
701
+ get relationshipStyles() {
702
+ return this._relationshipStyles;
703
+ }
704
+ get themes() {
705
+ return this._themes;
706
+ }
707
+ get properties() {
708
+ return this._properties;
709
+ }
567
710
  };
568
711
 
569
712
  // libs/c4-model/src/structurizrDslWriter.ts
@@ -698,11 +841,17 @@ var StructurizrDSLWriter = class {
698
841
  }
699
842
  writeContainerGroup(group, level) {
700
843
  let containerGroupDsl = "";
701
- containerGroupDsl += this.writeLine(`${group.name} = group "${group.name}" {`, level);
702
- group.getComponents().forEach((component) => {
703
- containerGroupDsl += this.writeComponent(component, level + 1);
844
+ const hasDirect = group.getComponents().length > 0;
845
+ if (hasDirect) {
846
+ containerGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
847
+ group.getComponents().forEach((component) => {
848
+ containerGroupDsl += this.writeComponent(component, level + 1);
849
+ });
850
+ containerGroupDsl += this.writeLine(`}`, level);
851
+ }
852
+ group.getGroups().forEach((nested) => {
853
+ containerGroupDsl += this.writeContainerGroup(nested, level);
704
854
  });
705
- containerGroupDsl += this.writeLine(`}`, level);
706
855
  return containerGroupDsl;
707
856
  }
708
857
  writeContainer(container, level) {
@@ -722,11 +871,17 @@ var StructurizrDSLWriter = class {
722
871
  }
723
872
  writeSoftwareSystemGroup(group, level) {
724
873
  let softwareSystemGroupDsl = "";
725
- softwareSystemGroupDsl += this.writeLine(`${group.name} = group "${group.name}" {`, level);
726
- group.getContainers().forEach((container) => {
727
- softwareSystemGroupDsl += this.writeContainer(container, level + 1);
874
+ const hasDirect = group.getContainers().length > 0;
875
+ if (hasDirect) {
876
+ softwareSystemGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
877
+ group.getContainers().forEach((container) => {
878
+ softwareSystemGroupDsl += this.writeContainer(container, level + 1);
879
+ });
880
+ softwareSystemGroupDsl += this.writeLine(`}`, level);
881
+ }
882
+ group.getGroups().forEach((nested) => {
883
+ softwareSystemGroupDsl += this.writeSoftwareSystemGroup(nested, level);
728
884
  });
729
- softwareSystemGroupDsl += this.writeLine(`}`, level);
730
885
  return softwareSystemGroupDsl;
731
886
  }
732
887
  writeSoftwareSystem(softwareSystem, level) {
@@ -778,21 +933,51 @@ var StructurizrDSLWriter = class {
778
933
  });
779
934
  return relationshipsDsl;
780
935
  }
936
+ hasNestedModelGroups(groups) {
937
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedModelGroups(g.getGroups()));
938
+ }
939
+ hasNestedSoftwareSystemGroups(groups) {
940
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedSoftwareSystemGroups(g.getGroups()));
941
+ }
942
+ hasNestedContainerGroups(groups) {
943
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedContainerGroups(g.getGroups()));
944
+ }
945
+ hasNestedGroups() {
946
+ if (this.hasNestedModelGroups(this.model.getGroups())) return true;
947
+ for (const ss of this.model.getSoftwareSystems()) {
948
+ if (this.hasNestedSoftwareSystemGroups(ss.getGroups())) return true;
949
+ for (const c of ss.getGroups().flatMap((g) => g.getAllContainers())) {
950
+ if (this.hasNestedContainerGroups(c.getGroups())) return true;
951
+ }
952
+ }
953
+ return false;
954
+ }
781
955
  writeModelGroup(group, level) {
782
956
  let modelGroupDsl = "";
783
- modelGroupDsl += this.writeLine(`${group.name} = group "${group.name}" {`, level);
784
- group.getPeople().forEach((person) => {
785
- modelGroupDsl += this.writeElement("person", person, level + 1);
786
- });
787
- group.getSoftwareSystems().forEach((softwareSystem) => {
788
- modelGroupDsl += this.writeSoftwareSystem(softwareSystem, level + 1);
957
+ const hasDirect = group.getPeople().length > 0 || group.getSoftwareSystems().length > 0;
958
+ if (hasDirect) {
959
+ modelGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
960
+ group.getPeople().forEach((person) => {
961
+ modelGroupDsl += this.writeElement("person", person, level + 1);
962
+ });
963
+ group.getSoftwareSystems().forEach((softwareSystem) => {
964
+ modelGroupDsl += this.writeSoftwareSystem(softwareSystem, level + 1);
965
+ });
966
+ modelGroupDsl += this.writeLine(`}`, level);
967
+ }
968
+ group.getGroups().forEach((nested) => {
969
+ modelGroupDsl += this.writeModelGroup(nested, level);
789
970
  });
790
- modelGroupDsl += this.writeLine(`}`, level);
791
971
  return modelGroupDsl;
792
972
  }
793
973
  writeModel(model, level) {
794
974
  let modelDsl = "";
795
975
  modelDsl += this.writeLine(`model {`, level);
976
+ if (this.hasNestedGroups()) {
977
+ modelDsl += this.writeLine(`properties {`, level + 1);
978
+ modelDsl += this.writeLine(`"structurizr.groupSeparator" "/"`, level + 2);
979
+ modelDsl += this.writeLine(`}`, level + 1);
980
+ }
796
981
  modelDsl += this.writeArchetypes(level + 1);
797
982
  modelDsl += this.writeLine("// Elements", level + 1);
798
983
  model.getPeopleNotInGroups().forEach((person) => {
@@ -815,12 +1000,66 @@ var StructurizrDSLWriter = class {
815
1000
  if (view.title) {
816
1001
  viewDsl += this.writeLine(`title "${view.title}"`, level + 1);
817
1002
  }
1003
+ if (view.isDefault) {
1004
+ viewDsl += this.writeLine("default", level + 1);
1005
+ }
818
1006
  view.scopes.forEach((scope) => {
819
1007
  viewDsl += this.writeLine(`${scope}`, level + 1);
820
1008
  });
1009
+ if (view.autoLayoutConfig) {
1010
+ const { direction, rankSeparation, nodeSeparation } = view.autoLayoutConfig;
1011
+ let line = "autoLayout";
1012
+ if (direction) line += ` ${direction}`;
1013
+ if (rankSeparation !== void 0) line += ` ${rankSeparation}`;
1014
+ if (nodeSeparation !== void 0) line += ` ${nodeSeparation}`;
1015
+ viewDsl += this.writeLine(line, level + 1);
1016
+ }
1017
+ if (view.properties.size > 0) {
1018
+ viewDsl += this.writeLine("properties {", level + 1);
1019
+ for (const [name, value] of view.properties) {
1020
+ viewDsl += this.writeLine(`"${name}" "${value}"`, level + 2);
1021
+ }
1022
+ viewDsl += this.writeLine("}", level + 1);
1023
+ }
821
1024
  viewDsl += this.writeLine(`}`, level);
822
1025
  return viewDsl;
823
1026
  }
1027
+ writeStyles(views, level) {
1028
+ const { elementStyles, relationshipStyles } = views;
1029
+ if (elementStyles.length === 0 && relationshipStyles.length === 0) return "";
1030
+ let dsl = this.writeLine("styles {", level);
1031
+ for (const { tag, definition: d } of elementStyles) {
1032
+ dsl += this.writeLine(`element "${tag}" {`, level + 1);
1033
+ if (d.shape) dsl += this.writeLine(`shape ${d.shape}`, level + 2);
1034
+ if (d.icon) dsl += this.writeLine(`icon "${d.icon}"`, level + 2);
1035
+ if (d.width) dsl += this.writeLine(`width ${d.width}`, level + 2);
1036
+ if (d.height) dsl += this.writeLine(`height ${d.height}`, level + 2);
1037
+ if (d.background) dsl += this.writeLine(`background "${d.background}"`, level + 2);
1038
+ if (d.color) dsl += this.writeLine(`color "${d.color}"`, level + 2);
1039
+ if (d.stroke) dsl += this.writeLine(`stroke "${d.stroke}"`, level + 2);
1040
+ if (d.strokeWidth) dsl += this.writeLine(`strokeWidth ${d.strokeWidth}`, level + 2);
1041
+ if (d.fontSize) dsl += this.writeLine(`fontSize ${d.fontSize}`, level + 2);
1042
+ if (d.border) dsl += this.writeLine(`border ${d.border}`, level + 2);
1043
+ if (d.opacity !== void 0) dsl += this.writeLine(`opacity ${d.opacity}`, level + 2);
1044
+ if (d.metadata !== void 0) dsl += this.writeLine(`metadata ${d.metadata}`, level + 2);
1045
+ if (d.description !== void 0) dsl += this.writeLine(`description ${d.description}`, level + 2);
1046
+ dsl += this.writeLine("}", level + 1);
1047
+ }
1048
+ for (const { tag, definition: d } of relationshipStyles) {
1049
+ dsl += this.writeLine(`relationship "${tag}" {`, level + 1);
1050
+ if (d.thickness) dsl += this.writeLine(`thickness ${d.thickness}`, level + 2);
1051
+ if (d.color) dsl += this.writeLine(`color "${d.color}"`, level + 2);
1052
+ if (d.style) dsl += this.writeLine(`style ${d.style}`, level + 2);
1053
+ if (d.routing) dsl += this.writeLine(`routing ${d.routing}`, level + 2);
1054
+ if (d.fontSize) dsl += this.writeLine(`fontSize ${d.fontSize}`, level + 2);
1055
+ if (d.width) dsl += this.writeLine(`width ${d.width}`, level + 2);
1056
+ if (d.position !== void 0) dsl += this.writeLine(`position ${d.position}`, level + 2);
1057
+ if (d.opacity !== void 0) dsl += this.writeLine(`opacity ${d.opacity}`, level + 2);
1058
+ dsl += this.writeLine("}", level + 1);
1059
+ }
1060
+ dsl += this.writeLine("}", level);
1061
+ return dsl;
1062
+ }
824
1063
  writeViews(views, level) {
825
1064
  let viewDsl = "";
826
1065
  viewDsl += this.writeLine(`views {`, level);
@@ -840,6 +1079,19 @@ var StructurizrDSLWriter = class {
840
1079
  views.componentViews.forEach((view) => {
841
1080
  viewDsl += this.writeView(view, "component", level + 1);
842
1081
  });
1082
+ viewDsl += this.writeStyles(views, level + 1);
1083
+ if (views.themes.length === 1) {
1084
+ viewDsl += this.writeLine(`theme ${views.themes[0]}`, level + 1);
1085
+ } else if (views.themes.length > 1) {
1086
+ viewDsl += this.writeLine(`themes ${views.themes.join(" ")}`, level + 1);
1087
+ }
1088
+ if (views.properties.size > 0) {
1089
+ viewDsl += this.writeLine("properties {", level + 1);
1090
+ for (const [name, value] of views.properties) {
1091
+ viewDsl += this.writeLine(`"${name}" "${value}"`, level + 2);
1092
+ }
1093
+ viewDsl += this.writeLine("}", level + 1);
1094
+ }
843
1095
  viewDsl += this.writeLine(`}`, level);
844
1096
  return viewDsl;
845
1097
  }
@@ -865,8 +1117,8 @@ var import_path = require("path");
865
1117
  var import_url = require("url");
866
1118
  var import_meta = {};
867
1119
  var _dirname = typeof __dirname !== "undefined" ? __dirname : (0, import_path.dirname)((0, import_url.fileURLToPath)(import_meta.url));
868
- async function buildModelWithCatalog(options = {}) {
869
- const { modelName = "model", modules: explicitModules, archetypes = {} } = options;
1120
+ async function buildModel(options = {}) {
1121
+ const { modelName = "model", modules: explicitModules, archetypes = {}, addViews } = options;
870
1122
  const model = new Model(modelName);
871
1123
  let c4Modules;
872
1124
  if (explicitModules) {
@@ -890,14 +1142,16 @@ async function buildModelWithCatalog(options = {}) {
890
1142
  rootCatalog[instance.key] = local;
891
1143
  registrations.push({ instance, key: instance.key, local });
892
1144
  }
1145
+ const dependenciesFor = (key) => Object.fromEntries(Object.entries(rootCatalog).filter(([k]) => k !== key));
893
1146
  for (const { instance, key, local } of registrations) {
894
- const dependencies = Object.fromEntries(Object.entries(rootCatalog).filter(([k]) => k !== key));
895
- instance.buildRelationships(local, dependencies, archetypes);
1147
+ instance.addRelationships(local, dependenciesFor(key), archetypes);
896
1148
  }
897
- return { model, catalog: rootCatalog };
898
- }
899
- async function buildModel(options = {}) {
900
- return (await buildModelWithCatalog(options)).model;
1149
+ const views = new Views();
1150
+ addViews?.(views, rootCatalog);
1151
+ for (const { instance, key, local } of registrations) {
1152
+ instance.addViews?.(views, local, dependenciesFor(key));
1153
+ }
1154
+ return { model, catalog: rootCatalog, views };
901
1155
  }
902
1156
 
903
1157
  // libs/c4-model/src/generateDiagrams.ts
@@ -906,9 +1160,8 @@ var os = __toESM(require("os"), 1);
906
1160
  var path = __toESM(require("path"), 1);
907
1161
  var import_testcontainers = require("testcontainers");
908
1162
  async function generateDiagrams(options) {
909
- const { views: viewsFactory, outputDir, ...buildOptions } = options;
910
- const { model, catalog } = await buildModelWithCatalog(buildOptions);
911
- const views = viewsFactory(catalog);
1163
+ const { outputDir, ...buildOptions } = options;
1164
+ const { model, views } = await buildModel(buildOptions);
912
1165
  const dsl = new StructurizrDSLWriter(model, views).write();
913
1166
  const tmpDir = await fs.promises.mkdtemp(path.join(fs.realpathSync(os.tmpdir()), "c4-diagrams-"));
914
1167
  await fs.promises.writeFile(path.join(tmpDir, "workspace.dsl"), dsl, "utf8");
@@ -929,6 +1182,60 @@ async function generateDiagrams(options) {
929
1182
  }
930
1183
  return generatedFiles;
931
1184
  }
1185
+
1186
+ // libs/c4-model/src/validateModel.ts
1187
+ var fs2 = __toESM(require("fs"), 1);
1188
+ var os2 = __toESM(require("os"), 1);
1189
+ var path2 = __toESM(require("path"), 1);
1190
+ var import_testcontainers2 = require("testcontainers");
1191
+ async function validateModel(model, views) {
1192
+ const dsl = new StructurizrDSLWriter(model, views).write();
1193
+ const tmpDir = await fs2.promises.mkdtemp(path2.join(fs2.realpathSync(os2.tmpdir()), "c4-validate-"));
1194
+ try {
1195
+ await fs2.promises.writeFile(path2.join(tmpDir, "workspace.dsl"), dsl, "utf8");
1196
+ const logs = [];
1197
+ try {
1198
+ 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();
1199
+ } catch {
1200
+ throw new Error(`Structurizr validation failed:
1201
+ ${logs.join("")}`);
1202
+ }
1203
+ } finally {
1204
+ await fs2.promises.rm(tmpDir, { recursive: true, force: true });
1205
+ }
1206
+ }
1207
+
1208
+ // libs/c4-model/src/exportWorkspaceJson.ts
1209
+ var fs3 = __toESM(require("fs"), 1);
1210
+ var os3 = __toESM(require("os"), 1);
1211
+ var path3 = __toESM(require("path"), 1);
1212
+ var import_testcontainers3 = require("testcontainers");
1213
+ async function exportWorkspaceJsonFromDsl(dsl) {
1214
+ const tmpDir = await fs3.promises.mkdtemp(path3.join(fs3.realpathSync(os3.tmpdir()), "c4-export-"));
1215
+ try {
1216
+ await fs3.promises.writeFile(path3.join(tmpDir, "workspace.dsl"), dsl, "utf8");
1217
+ const logs = [];
1218
+ try {
1219
+ 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();
1220
+ } catch {
1221
+ throw new Error(`Structurizr JSON export failed:
1222
+ ${logs.join("")}`);
1223
+ }
1224
+ const files = await fs3.promises.readdir(tmpDir);
1225
+ const jsonFile = files.find((f) => f.endsWith(".json"));
1226
+ if (!jsonFile) {
1227
+ throw new Error(`Structurizr JSON export produced no .json file. Files: ${files.join(", ")}`);
1228
+ }
1229
+ const jsonContent = await fs3.promises.readFile(path3.join(tmpDir, jsonFile), "utf8");
1230
+ return JSON.parse(jsonContent);
1231
+ } finally {
1232
+ await fs3.promises.rm(tmpDir, { recursive: true, force: true });
1233
+ }
1234
+ }
1235
+ async function exportWorkspaceJson(model, views) {
1236
+ const dsl = new StructurizrDSLWriter(model, views).write();
1237
+ return exportWorkspaceJsonFromDsl(dsl);
1238
+ }
932
1239
  // Annotate the CommonJS export names for ESM import in node:
933
1240
  0 && (module.exports = {
934
1241
  Component,
@@ -945,7 +1252,9 @@ async function generateDiagrams(options) {
945
1252
  View,
946
1253
  Views,
947
1254
  buildModel,
948
- buildModelWithCatalog,
1255
+ exportWorkspaceJson,
1256
+ exportWorkspaceJsonFromDsl,
949
1257
  generateDiagrams,
950
- mergeArchetypeWithOverride
1258
+ mergeArchetypeWithOverride,
1259
+ validateModel
951
1260
  });