@agilewallaby/c4-model 2.7.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.js CHANGED
@@ -169,9 +169,9 @@ var Element = class {
169
169
  getRelationshipsInHierarchy() {
170
170
  return this._relationships.concat(this.getChildElements().flatMap((element) => element.getRelationshipsInHierarchy()));
171
171
  }
172
- getChildElementNames(path2) {
172
+ getChildElementNames(path4) {
173
173
  const result = Array.from(this.getChildElements()).flatMap((reference) => {
174
- const currentPath = `${path2 ? path2 : "" + this.name}.${reference.name}`;
174
+ const currentPath = `${path4 ? path4 : "" + this.name}.${reference.name}`;
175
175
  return [currentPath, ...reference.getChildElementNames(currentPath)];
176
176
  });
177
177
  return result;
@@ -227,21 +227,46 @@ var Component = class extends TechnicalElement {
227
227
  };
228
228
 
229
229
  // libs/c4-model/src/container.ts
230
- var ContainerGroup = class extends Group {
231
- constructor(name, container) {
230
+ var ContainerGroup = class _ContainerGroup extends Group {
231
+ constructor(name, container, pathSegments = []) {
232
232
  super(name);
233
233
  this.name = name;
234
234
  this.container = container;
235
+ this.pathSegments = pathSegments;
235
236
  }
236
237
  _components = /* @__PURE__ */ new Map();
238
+ _groups = /* @__PURE__ */ new Map();
239
+ get canonicalName() {
240
+ return camelCase([...this.pathSegments, this.name].join(" "));
241
+ }
242
+ get dslName() {
243
+ return [...this.pathSegments, this.name].join("/");
244
+ }
237
245
  component(name, archetypeOrDef, override) {
238
246
  const component = this.container.component(name, archetypeOrDef, override);
239
247
  this._components.set(name, component);
240
248
  return component;
241
249
  }
250
+ group(groupName) {
251
+ let group = this._groups.get(groupName);
252
+ if (!group) {
253
+ group = new _ContainerGroup(groupName, this.container, [...this.pathSegments, this.name]);
254
+ this._groups.set(groupName, group);
255
+ }
256
+ return group;
257
+ }
258
+ getGroups() {
259
+ return Array.from(this._groups.values());
260
+ }
242
261
  getComponents() {
243
262
  return Array.from(this._components.values());
244
263
  }
264
+ getAllComponents() {
265
+ return [
266
+ ...this._components.values(),
267
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllComponents())
268
+ ];
269
+ }
245
270
  };
246
271
  var Container = class extends TechnicalElement {
247
272
  constructor(name, definition, archetype, overrideDefinition) {
@@ -278,7 +303,7 @@ var Container = class extends TechnicalElement {
278
303
  return Array.from(this._groups.values());
279
304
  }
280
305
  getComponentsNotInGroups() {
281
- const componentsInGroups = this.getGroups().flatMap((group) => group.getComponents());
306
+ const componentsInGroups = this.getGroups().flatMap((group) => group.getAllComponents());
282
307
  return Array.from(this._components.values()).filter((component) => !componentsInGroups.includes(component));
283
308
  }
284
309
  getChildElements() {
@@ -287,21 +312,46 @@ var Container = class extends TechnicalElement {
287
312
  };
288
313
 
289
314
  // libs/c4-model/src/softwareSystem.ts
290
- var SoftwareSystemGroup = class extends Group {
291
- constructor(name, softwareSystem) {
315
+ var SoftwareSystemGroup = class _SoftwareSystemGroup extends Group {
316
+ constructor(name, softwareSystem, pathSegments = []) {
292
317
  super(name);
293
318
  this.name = name;
294
319
  this.softwareSystem = softwareSystem;
320
+ this.pathSegments = pathSegments;
295
321
  }
296
322
  _containers = /* @__PURE__ */ new Map();
323
+ _groups = /* @__PURE__ */ new Map();
324
+ get canonicalName() {
325
+ return camelCase([...this.pathSegments, this.name].join(" "));
326
+ }
327
+ get dslName() {
328
+ return [...this.pathSegments, this.name].join("/");
329
+ }
297
330
  container(name, archetypeOrDef, override) {
298
331
  const container = this.softwareSystem.container(name, archetypeOrDef, override);
299
332
  this._containers.set(name, container);
300
333
  return container;
301
334
  }
335
+ group(groupName) {
336
+ let group = this._groups.get(groupName);
337
+ if (!group) {
338
+ group = new _SoftwareSystemGroup(groupName, this.softwareSystem, [...this.pathSegments, this.name]);
339
+ this._groups.set(groupName, group);
340
+ }
341
+ return group;
342
+ }
343
+ getGroups() {
344
+ return Array.from(this._groups.values());
345
+ }
302
346
  getContainers() {
303
347
  return Array.from(this._containers.values());
304
348
  }
349
+ getAllContainers() {
350
+ return [
351
+ ...this._containers.values(),
352
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllContainers())
353
+ ];
354
+ }
305
355
  };
306
356
  var SoftwareSystem = class extends Element {
307
357
  constructor(name, definition, archetype, overrideDefinition) {
@@ -341,7 +391,7 @@ var SoftwareSystem = class extends Element {
341
391
  return Array.from(this._containers.values());
342
392
  }
343
393
  getContainersNotInGroups() {
344
- const containersInGroups = Array.from(this._groups.values()).flatMap((group) => group.getContainers());
394
+ const containersInGroups = Array.from(this._groups.values()).flatMap((group) => group.getAllContainers());
345
395
  return Array.from(this._containers.values()).filter((container) => !containersInGroups.includes(container));
346
396
  }
347
397
  };
@@ -358,14 +408,22 @@ var Person = class extends Element {
358
408
  };
359
409
 
360
410
  // libs/c4-model/src/model.ts
361
- var ModelGroup = class extends Group {
362
- constructor(name, model) {
411
+ var ModelGroup = class _ModelGroup extends Group {
412
+ constructor(name, model, pathSegments = []) {
363
413
  super(name);
364
414
  this.name = name;
365
415
  this.model = model;
416
+ this.pathSegments = pathSegments;
366
417
  }
367
418
  softwareSystems = /* @__PURE__ */ new Map();
368
419
  people = /* @__PURE__ */ new Map();
420
+ _groups = /* @__PURE__ */ new Map();
421
+ get canonicalName() {
422
+ return camelCase([...this.pathSegments, this.name].join(" "));
423
+ }
424
+ get dslName() {
425
+ return [...this.pathSegments, this.name].join("/");
426
+ }
369
427
  softwareSystem(name, archetypeOrDef, override) {
370
428
  const softwareSystem = this.model.softwareSystem(name, archetypeOrDef, override);
371
429
  this.softwareSystems.set(name, softwareSystem);
@@ -376,12 +434,35 @@ var ModelGroup = class extends Group {
376
434
  this.people.set(name, person);
377
435
  return person;
378
436
  }
437
+ group(groupName) {
438
+ let group = this._groups.get(groupName);
439
+ if (!group) {
440
+ group = new _ModelGroup(groupName, this.model, [...this.pathSegments, this.name]);
441
+ this._groups.set(groupName, group);
442
+ }
443
+ return group;
444
+ }
445
+ getGroups() {
446
+ return Array.from(this._groups.values());
447
+ }
379
448
  getSoftwareSystems() {
380
449
  return Array.from(this.softwareSystems.values());
381
450
  }
382
451
  getPeople() {
383
452
  return Array.from(this.people.values());
384
453
  }
454
+ getAllSoftwareSystems() {
455
+ return [
456
+ ...this.softwareSystems.values(),
457
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllSoftwareSystems())
458
+ ];
459
+ }
460
+ getAllPeople() {
461
+ return [
462
+ ...this.people.values(),
463
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllPeople())
464
+ ];
465
+ }
385
466
  };
386
467
  var Model = class {
387
468
  constructor(name) {
@@ -406,7 +487,6 @@ var Model = class {
406
487
  this.softwareSystems.set(name, system);
407
488
  return system;
408
489
  }
409
- // TODO:Should be a Group<SoftwareSystem | Person> if that is added back in
410
490
  group(groupName) {
411
491
  let group = this.groups.get(groupName);
412
492
  if (!group) {
@@ -441,11 +521,11 @@ var Model = class {
441
521
  return Array.from(this.softwareSystems.values());
442
522
  }
443
523
  getPeopleNotInGroups() {
444
- const peopleInGroups = Array.from(this.groups.values()).flatMap((group) => group.getPeople());
524
+ const peopleInGroups = Array.from(this.groups.values()).flatMap((group) => group.getAllPeople());
445
525
  return Array.from(this.people.values()).filter((person) => !peopleInGroups.includes(person));
446
526
  }
447
527
  getSoftwareSystemsNotInGroups() {
448
- const systemsInGroups = Array.from(this.groups.values()).flatMap((group) => group.getSoftwareSystems());
528
+ const systemsInGroups = Array.from(this.groups.values()).flatMap((group) => group.getAllSoftwareSystems());
449
529
  return Array.from(this.softwareSystems.values()).filter((system) => !systemsInGroups.includes(system));
450
530
  }
451
531
  getGroups() {
@@ -465,6 +545,9 @@ var View = class {
465
545
  description;
466
546
  title;
467
547
  _scopes = [];
548
+ _autoLayout;
549
+ _isDefault = false;
550
+ _properties = /* @__PURE__ */ new Map();
468
551
  includeAll() {
469
552
  this._scopes.push("include *");
470
553
  }
@@ -483,15 +566,37 @@ var View = class {
483
566
  excludeExpression(expression) {
484
567
  this._scopes.push(`exclude ${expression}`);
485
568
  }
569
+ autoLayout(direction, rankSeparation, nodeSeparation) {
570
+ this._autoLayout = { direction, rankSeparation, nodeSeparation };
571
+ }
572
+ setDefault() {
573
+ this._isDefault = true;
574
+ }
575
+ addProperty(name, value) {
576
+ this._properties.set(name, value);
577
+ }
486
578
  get scopes() {
487
579
  return this._scopes;
488
580
  }
581
+ get autoLayoutConfig() {
582
+ return this._autoLayout;
583
+ }
584
+ get isDefault() {
585
+ return this._isDefault;
586
+ }
587
+ get properties() {
588
+ return this._properties;
589
+ }
489
590
  };
490
591
  var Views = class {
491
592
  _systemLandscapeViews = /* @__PURE__ */ new Map();
492
593
  _systemContextViews = /* @__PURE__ */ new Map();
493
594
  _containerViews = /* @__PURE__ */ new Map();
494
595
  _componentViews = /* @__PURE__ */ new Map();
596
+ _elementStyles = [];
597
+ _relationshipStyles = [];
598
+ _themes = [];
599
+ _properties = /* @__PURE__ */ new Map();
495
600
  addSystemLandscapeView(key, definition) {
496
601
  const view = new View(key, { subject: void 0, description: definition.description, title: definition.title });
497
602
  this._systemLandscapeViews.set(key, view);
@@ -512,6 +617,18 @@ var Views = class {
512
617
  this._componentViews.set(key, view);
513
618
  return view;
514
619
  }
620
+ addElementStyle(tag, definition) {
621
+ this._elementStyles.push({ tag, definition });
622
+ }
623
+ addRelationshipStyle(tag, definition) {
624
+ this._relationshipStyles.push({ tag, definition });
625
+ }
626
+ addTheme(url) {
627
+ this._themes.push(url);
628
+ }
629
+ addProperty(name, value) {
630
+ this._properties.set(name, value);
631
+ }
515
632
  get systemLandscapeViews() {
516
633
  return Array.from(this._systemLandscapeViews.values());
517
634
  }
@@ -524,6 +641,18 @@ var Views = class {
524
641
  get componentViews() {
525
642
  return Array.from(this._componentViews.values());
526
643
  }
644
+ get elementStyles() {
645
+ return this._elementStyles;
646
+ }
647
+ get relationshipStyles() {
648
+ return this._relationshipStyles;
649
+ }
650
+ get themes() {
651
+ return this._themes;
652
+ }
653
+ get properties() {
654
+ return this._properties;
655
+ }
527
656
  };
528
657
 
529
658
  // libs/c4-model/src/structurizrDslWriter.ts
@@ -658,11 +787,17 @@ var StructurizrDSLWriter = class {
658
787
  }
659
788
  writeContainerGroup(group, level) {
660
789
  let containerGroupDsl = "";
661
- containerGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.name}" {`, level);
662
- group.getComponents().forEach((component) => {
663
- containerGroupDsl += this.writeComponent(component, level + 1);
790
+ const hasDirect = group.getComponents().length > 0;
791
+ if (hasDirect) {
792
+ containerGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
793
+ group.getComponents().forEach((component) => {
794
+ containerGroupDsl += this.writeComponent(component, level + 1);
795
+ });
796
+ containerGroupDsl += this.writeLine(`}`, level);
797
+ }
798
+ group.getGroups().forEach((nested) => {
799
+ containerGroupDsl += this.writeContainerGroup(nested, level);
664
800
  });
665
- containerGroupDsl += this.writeLine(`}`, level);
666
801
  return containerGroupDsl;
667
802
  }
668
803
  writeContainer(container, level) {
@@ -682,11 +817,17 @@ var StructurizrDSLWriter = class {
682
817
  }
683
818
  writeSoftwareSystemGroup(group, level) {
684
819
  let softwareSystemGroupDsl = "";
685
- softwareSystemGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.name}" {`, level);
686
- group.getContainers().forEach((container) => {
687
- softwareSystemGroupDsl += this.writeContainer(container, level + 1);
820
+ const hasDirect = group.getContainers().length > 0;
821
+ if (hasDirect) {
822
+ softwareSystemGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
823
+ group.getContainers().forEach((container) => {
824
+ softwareSystemGroupDsl += this.writeContainer(container, level + 1);
825
+ });
826
+ softwareSystemGroupDsl += this.writeLine(`}`, level);
827
+ }
828
+ group.getGroups().forEach((nested) => {
829
+ softwareSystemGroupDsl += this.writeSoftwareSystemGroup(nested, level);
688
830
  });
689
- softwareSystemGroupDsl += this.writeLine(`}`, level);
690
831
  return softwareSystemGroupDsl;
691
832
  }
692
833
  writeSoftwareSystem(softwareSystem, level) {
@@ -738,21 +879,51 @@ var StructurizrDSLWriter = class {
738
879
  });
739
880
  return relationshipsDsl;
740
881
  }
882
+ hasNestedModelGroups(groups) {
883
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedModelGroups(g.getGroups()));
884
+ }
885
+ hasNestedSoftwareSystemGroups(groups) {
886
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedSoftwareSystemGroups(g.getGroups()));
887
+ }
888
+ hasNestedContainerGroups(groups) {
889
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedContainerGroups(g.getGroups()));
890
+ }
891
+ hasNestedGroups() {
892
+ if (this.hasNestedModelGroups(this.model.getGroups())) return true;
893
+ for (const ss of this.model.getSoftwareSystems()) {
894
+ if (this.hasNestedSoftwareSystemGroups(ss.getGroups())) return true;
895
+ for (const c of ss.getGroups().flatMap((g) => g.getAllContainers())) {
896
+ if (this.hasNestedContainerGroups(c.getGroups())) return true;
897
+ }
898
+ }
899
+ return false;
900
+ }
741
901
  writeModelGroup(group, level) {
742
902
  let modelGroupDsl = "";
743
- modelGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.name}" {`, level);
744
- group.getPeople().forEach((person) => {
745
- modelGroupDsl += this.writeElement("person", person, level + 1);
746
- });
747
- group.getSoftwareSystems().forEach((softwareSystem) => {
748
- modelGroupDsl += this.writeSoftwareSystem(softwareSystem, level + 1);
903
+ const hasDirect = group.getPeople().length > 0 || group.getSoftwareSystems().length > 0;
904
+ if (hasDirect) {
905
+ modelGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
906
+ group.getPeople().forEach((person) => {
907
+ modelGroupDsl += this.writeElement("person", person, level + 1);
908
+ });
909
+ group.getSoftwareSystems().forEach((softwareSystem) => {
910
+ modelGroupDsl += this.writeSoftwareSystem(softwareSystem, level + 1);
911
+ });
912
+ modelGroupDsl += this.writeLine(`}`, level);
913
+ }
914
+ group.getGroups().forEach((nested) => {
915
+ modelGroupDsl += this.writeModelGroup(nested, level);
749
916
  });
750
- modelGroupDsl += this.writeLine(`}`, level);
751
917
  return modelGroupDsl;
752
918
  }
753
919
  writeModel(model, level) {
754
920
  let modelDsl = "";
755
921
  modelDsl += this.writeLine(`model {`, level);
922
+ if (this.hasNestedGroups()) {
923
+ modelDsl += this.writeLine(`properties {`, level + 1);
924
+ modelDsl += this.writeLine(`"structurizr.groupSeparator" "/"`, level + 2);
925
+ modelDsl += this.writeLine(`}`, level + 1);
926
+ }
756
927
  modelDsl += this.writeArchetypes(level + 1);
757
928
  modelDsl += this.writeLine("// Elements", level + 1);
758
929
  model.getPeopleNotInGroups().forEach((person) => {
@@ -775,12 +946,66 @@ var StructurizrDSLWriter = class {
775
946
  if (view.title) {
776
947
  viewDsl += this.writeLine(`title "${view.title}"`, level + 1);
777
948
  }
949
+ if (view.isDefault) {
950
+ viewDsl += this.writeLine("default", level + 1);
951
+ }
778
952
  view.scopes.forEach((scope) => {
779
953
  viewDsl += this.writeLine(`${scope}`, level + 1);
780
954
  });
955
+ if (view.autoLayoutConfig) {
956
+ const { direction, rankSeparation, nodeSeparation } = view.autoLayoutConfig;
957
+ let line = "autoLayout";
958
+ if (direction) line += ` ${direction}`;
959
+ if (rankSeparation !== void 0) line += ` ${rankSeparation}`;
960
+ if (nodeSeparation !== void 0) line += ` ${nodeSeparation}`;
961
+ viewDsl += this.writeLine(line, level + 1);
962
+ }
963
+ if (view.properties.size > 0) {
964
+ viewDsl += this.writeLine("properties {", level + 1);
965
+ for (const [name, value] of view.properties) {
966
+ viewDsl += this.writeLine(`"${name}" "${value}"`, level + 2);
967
+ }
968
+ viewDsl += this.writeLine("}", level + 1);
969
+ }
781
970
  viewDsl += this.writeLine(`}`, level);
782
971
  return viewDsl;
783
972
  }
973
+ writeStyles(views, level) {
974
+ const { elementStyles, relationshipStyles } = views;
975
+ if (elementStyles.length === 0 && relationshipStyles.length === 0) return "";
976
+ let dsl = this.writeLine("styles {", level);
977
+ for (const { tag, definition: d } of elementStyles) {
978
+ dsl += this.writeLine(`element "${tag}" {`, level + 1);
979
+ if (d.shape) dsl += this.writeLine(`shape ${d.shape}`, level + 2);
980
+ if (d.icon) dsl += this.writeLine(`icon "${d.icon}"`, level + 2);
981
+ if (d.width) dsl += this.writeLine(`width ${d.width}`, level + 2);
982
+ if (d.height) dsl += this.writeLine(`height ${d.height}`, level + 2);
983
+ if (d.background) dsl += this.writeLine(`background "${d.background}"`, level + 2);
984
+ if (d.color) dsl += this.writeLine(`color "${d.color}"`, level + 2);
985
+ if (d.stroke) dsl += this.writeLine(`stroke "${d.stroke}"`, level + 2);
986
+ if (d.strokeWidth) dsl += this.writeLine(`strokeWidth ${d.strokeWidth}`, level + 2);
987
+ if (d.fontSize) dsl += this.writeLine(`fontSize ${d.fontSize}`, level + 2);
988
+ if (d.border) dsl += this.writeLine(`border ${d.border}`, level + 2);
989
+ if (d.opacity !== void 0) dsl += this.writeLine(`opacity ${d.opacity}`, level + 2);
990
+ if (d.metadata !== void 0) dsl += this.writeLine(`metadata ${d.metadata}`, level + 2);
991
+ if (d.description !== void 0) dsl += this.writeLine(`description ${d.description}`, level + 2);
992
+ dsl += this.writeLine("}", level + 1);
993
+ }
994
+ for (const { tag, definition: d } of relationshipStyles) {
995
+ dsl += this.writeLine(`relationship "${tag}" {`, level + 1);
996
+ if (d.thickness) dsl += this.writeLine(`thickness ${d.thickness}`, level + 2);
997
+ if (d.color) dsl += this.writeLine(`color "${d.color}"`, level + 2);
998
+ if (d.style) dsl += this.writeLine(`style ${d.style}`, level + 2);
999
+ if (d.routing) dsl += this.writeLine(`routing ${d.routing}`, level + 2);
1000
+ if (d.fontSize) dsl += this.writeLine(`fontSize ${d.fontSize}`, level + 2);
1001
+ if (d.width) dsl += this.writeLine(`width ${d.width}`, level + 2);
1002
+ if (d.position !== void 0) dsl += this.writeLine(`position ${d.position}`, level + 2);
1003
+ if (d.opacity !== void 0) dsl += this.writeLine(`opacity ${d.opacity}`, level + 2);
1004
+ dsl += this.writeLine("}", level + 1);
1005
+ }
1006
+ dsl += this.writeLine("}", level);
1007
+ return dsl;
1008
+ }
784
1009
  writeViews(views, level) {
785
1010
  let viewDsl = "";
786
1011
  viewDsl += this.writeLine(`views {`, level);
@@ -800,6 +1025,19 @@ var StructurizrDSLWriter = class {
800
1025
  views.componentViews.forEach((view) => {
801
1026
  viewDsl += this.writeView(view, "component", level + 1);
802
1027
  });
1028
+ viewDsl += this.writeStyles(views, level + 1);
1029
+ if (views.themes.length === 1) {
1030
+ viewDsl += this.writeLine(`theme ${views.themes[0]}`, level + 1);
1031
+ } else if (views.themes.length > 1) {
1032
+ viewDsl += this.writeLine(`themes ${views.themes.join(" ")}`, level + 1);
1033
+ }
1034
+ if (views.properties.size > 0) {
1035
+ viewDsl += this.writeLine("properties {", level + 1);
1036
+ for (const [name, value] of views.properties) {
1037
+ viewDsl += this.writeLine(`"${name}" "${value}"`, level + 2);
1038
+ }
1039
+ viewDsl += this.writeLine("}", level + 1);
1040
+ }
803
1041
  viewDsl += this.writeLine(`}`, level);
804
1042
  return viewDsl;
805
1043
  }
@@ -824,8 +1062,8 @@ import { glob } from "glob";
824
1062
  import { join, dirname } from "path";
825
1063
  import { fileURLToPath } from "url";
826
1064
  var _dirname = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
827
- async function buildModelWithCatalog(options = {}) {
828
- const { modelName = "model", modules: explicitModules, archetypes = {} } = options;
1065
+ async function buildModel(options = {}) {
1066
+ const { modelName = "model", modules: explicitModules, archetypes = {}, addViews } = options;
829
1067
  const model = new Model(modelName);
830
1068
  let c4Modules;
831
1069
  if (explicitModules) {
@@ -849,14 +1087,16 @@ async function buildModelWithCatalog(options = {}) {
849
1087
  rootCatalog[instance.key] = local;
850
1088
  registrations.push({ instance, key: instance.key, local });
851
1089
  }
1090
+ const dependenciesFor = (key) => Object.fromEntries(Object.entries(rootCatalog).filter(([k]) => k !== key));
852
1091
  for (const { instance, key, local } of registrations) {
853
- const dependencies = Object.fromEntries(Object.entries(rootCatalog).filter(([k]) => k !== key));
854
- instance.buildRelationships(local, dependencies, archetypes);
1092
+ instance.addRelationships(local, dependenciesFor(key), archetypes);
855
1093
  }
856
- return { model, catalog: rootCatalog };
857
- }
858
- async function buildModel(options = {}) {
859
- return (await buildModelWithCatalog(options)).model;
1094
+ const views = new Views();
1095
+ addViews?.(views, rootCatalog);
1096
+ for (const { instance, key, local } of registrations) {
1097
+ instance.addViews?.(views, local, dependenciesFor(key));
1098
+ }
1099
+ return { model, catalog: rootCatalog, views };
860
1100
  }
861
1101
 
862
1102
  // libs/c4-model/src/generateDiagrams.ts
@@ -865,9 +1105,8 @@ import * as os from "os";
865
1105
  import * as path from "path";
866
1106
  import { GenericContainer, Wait } from "testcontainers";
867
1107
  async function generateDiagrams(options) {
868
- const { views: viewsFactory, outputDir, ...buildOptions } = options;
869
- const { model, catalog } = await buildModelWithCatalog(buildOptions);
870
- const views = viewsFactory(catalog);
1108
+ const { outputDir, ...buildOptions } = options;
1109
+ const { model, views } = await buildModel(buildOptions);
871
1110
  const dsl = new StructurizrDSLWriter(model, views).write();
872
1111
  const tmpDir = await fs.promises.mkdtemp(path.join(fs.realpathSync(os.tmpdir()), "c4-diagrams-"));
873
1112
  await fs.promises.writeFile(path.join(tmpDir, "workspace.dsl"), dsl, "utf8");
@@ -888,6 +1127,60 @@ async function generateDiagrams(options) {
888
1127
  }
889
1128
  return generatedFiles;
890
1129
  }
1130
+
1131
+ // libs/c4-model/src/validateModel.ts
1132
+ import * as fs2 from "fs";
1133
+ import * as os2 from "os";
1134
+ import * as path2 from "path";
1135
+ import { GenericContainer as GenericContainer2, Wait as Wait2 } from "testcontainers";
1136
+ async function validateModel(model, views) {
1137
+ const dsl = new StructurizrDSLWriter(model, views).write();
1138
+ const tmpDir = await fs2.promises.mkdtemp(path2.join(fs2.realpathSync(os2.tmpdir()), "c4-validate-"));
1139
+ try {
1140
+ await fs2.promises.writeFile(path2.join(tmpDir, "workspace.dsl"), dsl, "utf8");
1141
+ const logs = [];
1142
+ try {
1143
+ await new GenericContainer2("structurizr/structurizr").withBindMounts([{ source: tmpDir, target: "/workspace", mode: "rw" }]).withCommand(["validate", "-workspace", "/workspace/workspace.dsl"]).withWaitStrategy(Wait2.forOneShotStartup()).withLogConsumer((stream) => stream.on("data", (chunk) => logs.push(chunk.toString()))).start();
1144
+ } catch {
1145
+ throw new Error(`Structurizr validation failed:
1146
+ ${logs.join("")}`);
1147
+ }
1148
+ } finally {
1149
+ await fs2.promises.rm(tmpDir, { recursive: true, force: true });
1150
+ }
1151
+ }
1152
+
1153
+ // libs/c4-model/src/exportWorkspaceJson.ts
1154
+ import * as fs3 from "fs";
1155
+ import * as os3 from "os";
1156
+ import * as path3 from "path";
1157
+ import { GenericContainer as GenericContainer3, Wait as Wait3 } from "testcontainers";
1158
+ async function exportWorkspaceJsonFromDsl(dsl) {
1159
+ const tmpDir = await fs3.promises.mkdtemp(path3.join(fs3.realpathSync(os3.tmpdir()), "c4-export-"));
1160
+ try {
1161
+ await fs3.promises.writeFile(path3.join(tmpDir, "workspace.dsl"), dsl, "utf8");
1162
+ const logs = [];
1163
+ try {
1164
+ await new GenericContainer3("structurizr/structurizr").withBindMounts([{ source: tmpDir, target: "/workspace", mode: "rw" }]).withCommand(["export", "-w", "/workspace/workspace.dsl", "-f", "json", "-o", "/workspace"]).withWaitStrategy(Wait3.forOneShotStartup()).withLogConsumer((stream) => stream.on("data", (chunk) => logs.push(chunk.toString()))).start();
1165
+ } catch {
1166
+ throw new Error(`Structurizr JSON export failed:
1167
+ ${logs.join("")}`);
1168
+ }
1169
+ const files = await fs3.promises.readdir(tmpDir);
1170
+ const jsonFile = files.find((f) => f.endsWith(".json"));
1171
+ if (!jsonFile) {
1172
+ throw new Error(`Structurizr JSON export produced no .json file. Files: ${files.join(", ")}`);
1173
+ }
1174
+ const jsonContent = await fs3.promises.readFile(path3.join(tmpDir, jsonFile), "utf8");
1175
+ return JSON.parse(jsonContent);
1176
+ } finally {
1177
+ await fs3.promises.rm(tmpDir, { recursive: true, force: true });
1178
+ }
1179
+ }
1180
+ async function exportWorkspaceJson(model, views) {
1181
+ const dsl = new StructurizrDSLWriter(model, views).write();
1182
+ return exportWorkspaceJsonFromDsl(dsl);
1183
+ }
891
1184
  export {
892
1185
  Component,
893
1186
  Container,
@@ -903,7 +1196,9 @@ export {
903
1196
  View,
904
1197
  Views,
905
1198
  buildModel,
906
- buildModelWithCatalog,
1199
+ exportWorkspaceJson,
1200
+ exportWorkspaceJsonFromDsl,
907
1201
  generateDiagrams,
908
- mergeArchetypeWithOverride
1202
+ mergeArchetypeWithOverride,
1203
+ validateModel
909
1204
  };
package/src/model.d.ts CHANGED
@@ -2,6 +2,7 @@ import { Group } from './core';
2
2
  import { SoftwareSystem, SoftwareSystemDefinition } from './softwareSystem';
3
3
  import { Person, PersonDefinition } from './person';
4
4
  import { ElementArchetype, RelationshipArchetype } from './archetype';
5
+ import { Views } from './views';
5
6
  export type CatalogKeyOf<TRoot, TModule> = {
6
7
  [K in keyof TRoot]: TRoot[K] extends TModule ? K : never;
7
8
  }[keyof TRoot];
@@ -9,7 +10,8 @@ export type Dependencies<TRoot, TModule> = Omit<TRoot, CatalogKeyOf<TRoot, TModu
9
10
  export interface C4Module<TRoot, TLocal, TArchetypes = Record<string, ElementArchetype | RelationshipArchetype>> {
10
11
  readonly key: CatalogKeyOf<TRoot, TLocal>;
11
12
  registerDefinitions(model: Model, archetypes: TArchetypes): TLocal;
12
- buildRelationships(local: TLocal, dependencies: Dependencies<TRoot, TLocal>, archetypes: TArchetypes): void;
13
+ addRelationships(local: TLocal, dependencies: Dependencies<TRoot, TLocal>, archetypes: TArchetypes): void;
14
+ addViews?(views: Views, local: TLocal, dependencies: Dependencies<TRoot, TLocal>): void;
13
15
  }
14
16
  interface DefineSoftwareSystem {
15
17
  softwareSystem(name: string, archetypeOrDef?: ElementArchetype | SoftwareSystemDefinition, override?: SoftwareSystemDefinition): SoftwareSystem;
@@ -20,13 +22,21 @@ interface DefinePerson {
20
22
  export declare class ModelGroup extends Group<Person | SoftwareSystem> implements DefineSoftwareSystem, DefinePerson {
21
23
  readonly name: string;
22
24
  private readonly model;
25
+ private readonly pathSegments;
23
26
  private softwareSystems;
24
27
  private people;
25
- constructor(name: string, model: DefineSoftwareSystem & DefinePerson);
28
+ private _groups;
29
+ constructor(name: string, model: DefineSoftwareSystem & DefinePerson, pathSegments?: string[]);
30
+ get canonicalName(): string;
31
+ get dslName(): string;
26
32
  softwareSystem(name: string, archetypeOrDef?: ElementArchetype | SoftwareSystemDefinition, override?: SoftwareSystemDefinition): SoftwareSystem;
27
33
  person(name: string, archetypeOrDef?: ElementArchetype | PersonDefinition, override?: PersonDefinition): Person;
34
+ group(groupName: string): ModelGroup;
35
+ getGroups(): ReadonlyArray<ModelGroup>;
28
36
  getSoftwareSystems(): ReadonlyArray<SoftwareSystem>;
29
37
  getPeople(): ReadonlyArray<Person>;
38
+ getAllSoftwareSystems(): ReadonlyArray<SoftwareSystem>;
39
+ getAllPeople(): ReadonlyArray<Person>;
30
40
  }
31
41
  export interface ModelDefinitions {
32
42
  softwareSystem(name: string, archetypeOrDef?: ElementArchetype | SoftwareSystemDefinition, override?: SoftwareSystemDefinition): SoftwareSystem;
@@ -39,7 +49,7 @@ export declare class Model {
39
49
  private people;
40
50
  private groups;
41
51
  softwareSystem(name: string, archetypeOrDef?: ElementArchetype | SoftwareSystemDefinition, override?: SoftwareSystemDefinition): SoftwareSystem;
42
- group(groupName: string): Group & ModelDefinitions;
52
+ group(groupName: string): ModelGroup;
43
53
  person(name: string, archetypeOrDef?: ElementArchetype | PersonDefinition, override?: PersonDefinition): Person;
44
54
  validate(): void;
45
55
  getPeople(): ReadonlyArray<Person>;
@@ -11,10 +11,17 @@ export interface SoftwareSystemReference {
11
11
  export declare class SoftwareSystemGroup extends Group<Container> implements DefineContainer {
12
12
  readonly name: string;
13
13
  private readonly softwareSystem;
14
+ private readonly pathSegments;
14
15
  private _containers;
15
- constructor(name: string, softwareSystem: DefineContainer);
16
+ private _groups;
17
+ constructor(name: string, softwareSystem: DefineContainer, pathSegments?: string[]);
18
+ get canonicalName(): string;
19
+ get dslName(): string;
16
20
  container(name: string, archetypeOrDef?: ElementArchetype | ContainerDefinition, override?: ContainerDefinition): Container;
21
+ group(groupName: string): SoftwareSystemGroup;
22
+ getGroups(): ReadonlyArray<SoftwareSystemGroup>;
17
23
  getContainers(): ReadonlyArray<Container>;
24
+ getAllContainers(): ReadonlyArray<Container>;
18
25
  }
19
26
  export declare class SoftwareSystem extends Element<Container> implements DefineContainer {
20
27
  readonly name: string;
@@ -14,9 +14,14 @@ export declare class StructurizrDSLWriter {
14
14
  private writeSoftwareSystem;
15
15
  private writeRelationship;
16
16
  private writeRelationships;
17
+ private hasNestedModelGroups;
18
+ private hasNestedSoftwareSystemGroups;
19
+ private hasNestedContainerGroups;
20
+ private hasNestedGroups;
17
21
  private writeModelGroup;
18
22
  private writeModel;
19
23
  private writeView;
24
+ private writeStyles;
20
25
  private writeViews;
21
26
  write(): string;
22
27
  private writeLine;