@buun_group/plotwork-mcp 0.1.0-beta.6 → 0.1.0-beta.8

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/plotwork-mcp.mjs CHANGED
@@ -51,7 +51,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
51
51
  var package_default;
52
52
  var init_package = __esm({
53
53
  "package.json"() {
54
- package_default = { version: "0.1.0-beta.6" };
54
+ package_default = { version: "0.1.0-beta.8" };
55
55
  }
56
56
  });
57
57
 
@@ -160,6 +160,71 @@ var init_flow_node_types = __esm({
160
160
  }
161
161
  });
162
162
 
163
+ // ../../packages/diagrams/src/constants/auto-group-types.ts
164
+ var AUTO_GROUP_TYPES, REGION_CODE, AZURE_REGION_NAMES;
165
+ var init_auto_group_types = __esm({
166
+ "../../packages/diagrams/src/constants/auto-group-types.ts"() {
167
+ "use strict";
168
+ AUTO_GROUP_TYPES = {
169
+ network: { ends: ["vpc", "vnet", "network", "virtual network", "private network", "virtual private cloud", "vcn"] },
170
+ subnet: { ends: ["subnet", "subnets"] },
171
+ "public-subnet": { ends: ["public subnet", "public subnets"] },
172
+ "private-subnet": { ends: ["private subnet", "private subnets"] },
173
+ environment: { ends: ["environment", "env"], whole: ["production", "prod", "staging", "dev", "development", "qa", "uat"] },
174
+ "ci-cd": {
175
+ ends: ["ci", "ci cd", "cicd", "ci pipeline", "ci cd pipeline", "build pipeline", "release pipeline", "deploy pipeline", "deployment pipeline", "delivery pipeline"],
176
+ whole: ["pipeline", "pipelines"]
177
+ },
178
+ messaging: { ends: ["kafka", "kafka cluster", "queue", "queues", "message queue", "message queues", "event bus", "message bus", "messaging", "event streaming", "service bus", "pub sub", "pubsub"] },
179
+ cluster: { ends: ["kubernetes", "k8s", "kubernetes cluster", "k8s cluster", "eks cluster", "aks cluster", "gke cluster", "container cluster"], whole: ["cluster"] },
180
+ region: { ends: ["region"] },
181
+ account: { ends: ["aws account"] },
182
+ "data-tier": { ends: ["data", "data tier", "database tier", "data layer", "database layer", "databases", "data stores", "data store", "storage tier", "persistence", "persistence layer"] },
183
+ security: { ends: ["security", "iam", "security services"] },
184
+ observability: { ends: ["monitoring", "observability", "logging", "telemetry"] }
185
+ };
186
+ REGION_CODE = /^(?:(?:us|eu|ap|sa|ca|me|af|il|mx)-[a-z]+-\d|(?:us|europe|asia|australia|southamerica|northamerica|me|africa)-[a-z]+\d)$/;
187
+ AZURE_REGION_NAMES = /* @__PURE__ */ new Set([
188
+ "eastus",
189
+ "eastus2",
190
+ "westus",
191
+ "westus2",
192
+ "westus3",
193
+ "centralus",
194
+ "northcentralus",
195
+ "southcentralus",
196
+ "westcentralus",
197
+ "canadacentral",
198
+ "canadaeast",
199
+ "brazilsouth",
200
+ "northeurope",
201
+ "westeurope",
202
+ "uksouth",
203
+ "ukwest",
204
+ "francecentral",
205
+ "germanywestcentral",
206
+ "swedencentral",
207
+ "switzerlandnorth",
208
+ "norwayeast",
209
+ "polandcentral",
210
+ "italynorth",
211
+ "eastasia",
212
+ "southeastasia",
213
+ "japaneast",
214
+ "japanwest",
215
+ "koreacentral",
216
+ "centralindia",
217
+ "southindia",
218
+ "australiaeast",
219
+ "australiasoutheast",
220
+ "uaenorth",
221
+ "southafricanorth",
222
+ "qatarcentral",
223
+ "israelcentral"
224
+ ]);
225
+ }
226
+ });
227
+
163
228
  // ../../packages/diagrams/src/constants/group-types.ts
164
229
  function groupTypeIcon(type, provider, canUse = () => true) {
165
230
  const spec = GROUP_TYPE_SPECS[type];
@@ -386,7 +451,7 @@ var init_auto_icons = __esm({
386
451
  ICON_MODE_SPEC = {
387
452
  key: "icons",
388
453
  values: ICON_MODES.join(", "),
389
- meaning: 'auto (the default): an item with no icon= gets the icon its label, else its sub= or tech=, names for certain: a brand ("Postgres"), a cloud product ("Amazon S3"), or in some kinds a role ("Email"). A name that is also an everyday word ("Vault") stays plain. icon=none keeps one item plain; off keeps all plain.',
454
+ meaning: 'auto (the default): an item with no icon= gets the icon its label, else its sub= or tech=, names for certain: a brand ("Postgres"), a cloud product ("Amazon S3"), or in some kinds a role ("Email"). A name that is also an everyday word ("Vault") stays plain. icon=none keeps one item plain; off keeps all plain, with flow edge labels, and stops a flow group taking its type from its name.',
390
455
  example: 'flow "Checkout" icons=off'
391
456
  };
392
457
  PLAIN_WORD_NAMES = /* @__PURE__ */ new Set([
@@ -643,7 +708,7 @@ var init_view_links = __esm({
643
708
  };
644
709
  CLI_RELEASE = {
645
710
  packageName: "@buun_group/plotwork",
646
- version: "0.1.0-beta.7",
711
+ version: "0.1.0-beta.9",
647
712
  onNpm: true
648
713
  };
649
714
  }
@@ -662,7 +727,7 @@ var init_mcp_release = __esm({
662
727
  "use strict";
663
728
  MCP_RELEASE = {
664
729
  packageName: "@buun_group/plotwork-mcp",
665
- version: "0.1.0-beta.5"
730
+ version: "0.1.0-beta.6"
666
731
  };
667
732
  }
668
733
  });
@@ -3500,6 +3565,195 @@ var init_label_icons = __esm({
3500
3565
  }
3501
3566
  });
3502
3567
 
3568
+ // ../../packages/diagrams/src/constants/label-vocabulary.ts
3569
+ var EDGE_STYLES, LABEL_VERB_PHRASES, LABEL_VERB_MAX_WORDS, NOT_VERB_PHRASES, STYLE_STEPS;
3570
+ var init_label_vocabulary = __esm({
3571
+ "../../packages/diagrams/src/constants/label-vocabulary.ts"() {
3572
+ "use strict";
3573
+ EDGE_STYLES = {
3574
+ /** Reads stored data: a query, a select. */
3575
+ reads: { icon: "lucide:eye", role: "data" },
3576
+ /** Reads its settings, such as a Kubernetes ConfigMap; not stored data, so no role. */
3577
+ "reads-settings": { icon: "lucide:eye", role: null },
3578
+ /** Writes stored data: an insert, an update. */
3579
+ writes: { icon: "lucide:square-pen", role: "data" },
3580
+ /** Reads and writes a store the file names without saying which: a database, a bucket, a cache. */
3581
+ stores: { icon: "lucide:database", role: "data" },
3582
+ /** Mounts a volume or a claim. */
3583
+ mounts: { icon: "lucide:hard-drive", role: "data" },
3584
+ /** Sends, publishes, produces, or emits a message it does not wait for. */
3585
+ sends: { icon: "lucide:send", role: "async" },
3586
+ /** Receives, consumes, or subscribes to messages. */
3587
+ receives: { icon: "lucide:inbox", role: "async" },
3588
+ /** Calls or requests and waits for the answer. */
3589
+ calls: { icon: "lucide:arrow-right-left", role: null },
3590
+ /** Routes outside traffic: a host, a path, a public address, a DNS name. */
3591
+ routes: { icon: "lucide:globe", role: null },
3592
+ /** Needs another thing to be there first. */
3593
+ "depends-on": { icon: "lucide:link", role: null },
3594
+ /** Provides an API. */
3595
+ provides: { icon: "lucide:plug", role: null },
3596
+ /** Consumes an API. */
3597
+ consumes: { icon: "lucide:plug-zap", role: null },
3598
+ /** Starts another thing: a function on an event, a downstream pipeline. */
3599
+ triggers: { icon: "lucide:zap", role: null },
3600
+ /** Deploys to an environment. */
3601
+ deploys: { icon: "lucide:rocket", role: null },
3602
+ /** Grants a role, assumes one, or authenticates. */
3603
+ grants: { icon: "lucide:key-round", role: "identity" },
3604
+ /** Uses a credential: a secret, a password, a key. */
3605
+ secret: { icon: "lucide:key-round", role: "identity" },
3606
+ /** Reaches another over a private network: a private endpoint, VPC peering, VNet integration. */
3607
+ private: { icon: "lucide:lock", role: "private" },
3608
+ /** Is guarded by a firewall or a security group. */
3609
+ filters: { icon: "lucide:shield", role: null },
3610
+ /** Sends logs or telemetry. */
3611
+ reports: { icon: "lucide:activity", role: null },
3612
+ /** Pulls container images. */
3613
+ pulls: { icon: "lucide:download", role: null },
3614
+ /** Builds on a base, such as a Kustomize overlay on its base. */
3615
+ "builds-on": { icon: "lucide:layers", role: null },
3616
+ /** Includes a component. */
3617
+ includes: { icon: "lucide:package", role: null },
3618
+ /** Patches what another file holds. */
3619
+ patches: { icon: "lucide:replace", role: null }
3620
+ };
3621
+ LABEL_VERB_PHRASES = {
3622
+ reads: ["reads", "read", "reading", "reads from", "read from", "queries", "query", "querying", "looks up", "look up", "selects", "select from", "loads from", "fetches from"],
3623
+ writes: [
3624
+ "writes",
3625
+ "write",
3626
+ "writing",
3627
+ "writes to",
3628
+ "write to",
3629
+ "inserts",
3630
+ "insert",
3631
+ "inserts into",
3632
+ "updates",
3633
+ "update",
3634
+ "saves",
3635
+ "save",
3636
+ "saves to",
3637
+ "persists",
3638
+ "persist",
3639
+ "persists to",
3640
+ "deletes",
3641
+ "delete",
3642
+ "deletes from",
3643
+ "uploads",
3644
+ "upload",
3645
+ "uploads to",
3646
+ "upload to"
3647
+ ],
3648
+ stores: ["stores", "store", "stores in", "store in", "caches", "caches in", "reads and writes", "reads writes", "read write", "read and write"],
3649
+ mounts: ["mounts", "mount"],
3650
+ sends: [
3651
+ "sends",
3652
+ "send",
3653
+ "sends to",
3654
+ "publishes",
3655
+ "publish",
3656
+ "publishes to",
3657
+ "publish to",
3658
+ "emits",
3659
+ "emit",
3660
+ "produces",
3661
+ "produce",
3662
+ "produces to",
3663
+ "enqueues",
3664
+ "enqueue",
3665
+ "pushes to",
3666
+ "notifies",
3667
+ "notify",
3668
+ "fires"
3669
+ ],
3670
+ receives: ["receives", "receive", "consumes", "consume", "consumes from", "subscribes", "subscribes to", "subscribe to", "listens to", "listens on", "polls", "dequeues"],
3671
+ calls: ["calls", "call", "invokes", "invoke", "requests", "request", "fetches", "fetch", "queries api", "http", "https", "grpc", "rest", "graphql"],
3672
+ routes: ["routes", "routes to", "route to", "forwards", "forwards to", "forward to", "proxies", "proxies to", "redirects to", "load balances"],
3673
+ "depends-on": ["depends on", "depend on", "requires", "needs"],
3674
+ provides: ["provides", "exposes", "serves"],
3675
+ triggers: ["triggers", "trigger", "starts", "kicks off", "schedules", "wakes"],
3676
+ deploys: ["deploys", "deploy", "deploys to", "deploy to", "releases to", "ships to", "promotes to", "rolls out", "rolls out to"],
3677
+ grants: [
3678
+ "authenticates",
3679
+ "authenticate",
3680
+ "authenticates with",
3681
+ "authenticates against",
3682
+ "authorizes",
3683
+ "authorize",
3684
+ "authorises",
3685
+ "authorise",
3686
+ "signs in",
3687
+ "sign in",
3688
+ "signs in with",
3689
+ "logs in",
3690
+ "log in",
3691
+ "logs in with",
3692
+ "grants",
3693
+ "grant",
3694
+ "assumes role",
3695
+ "issues token",
3696
+ "issues tokens",
3697
+ "verifies token",
3698
+ "verify token",
3699
+ "validates token",
3700
+ "validate token",
3701
+ "logs out",
3702
+ "log out",
3703
+ "signs out",
3704
+ "sign out"
3705
+ ],
3706
+ secret: ["reads secret", "reads secrets", "read secret", "read secrets", "read credentials", "fetches secret", "fetches secrets", "reads credentials", "gets secret", "gets secrets"],
3707
+ reports: [
3708
+ "reports",
3709
+ "reports to",
3710
+ "logs",
3711
+ "logs to",
3712
+ "metrics",
3713
+ "telemetry",
3714
+ "traces",
3715
+ "logs and metrics",
3716
+ "sends logs",
3717
+ "sends logs to",
3718
+ "sends metrics",
3719
+ "sends metrics to",
3720
+ "sends telemetry",
3721
+ "sends traces",
3722
+ "ships logs",
3723
+ "ships logs to",
3724
+ "exports metrics",
3725
+ "exports traces",
3726
+ "emits metrics",
3727
+ "emits logs"
3728
+ ],
3729
+ pulls: ["pulls image", "pulls images", "pulls images from", "pulls image from"]
3730
+ };
3731
+ LABEL_VERB_MAX_WORDS = 5;
3732
+ NOT_VERB_PHRASES = [
3733
+ "read replica",
3734
+ "read replicas",
3735
+ "read model",
3736
+ "read only",
3737
+ "write model",
3738
+ "write ahead",
3739
+ "request id",
3740
+ "request body",
3741
+ "call center",
3742
+ "call centre",
3743
+ "update available",
3744
+ "query string",
3745
+ "query params"
3746
+ ];
3747
+ STYLE_STEPS = [
3748
+ 'Label each arrow with a verb, such as "reads", "writes orders", "publishes OrderPlaced", "calls", "deploys to", or "authenticates": the label then draws the icon of that relationship (verbIcons). icon= picks another icon; icon=none keeps the label plain.',
3749
+ "Add role= to the arrows whose line means something: role=data for reads and writes of stored data, role=async for messages nobody waits for, role=identity for sign-in and credentials, role=private for private network links. The line takes the role's colour and the legend names it; warning W500 lists the arrows to mark.",
3750
+ 'Give every group a type= (network, subnet, environment, data-tier, messaging, cluster, ci-cd, observability, and the rest), so its frame, tone, and icon follow. A group named plainly, such as "VPC", "Production", "Kafka", or "Monitoring", gets its type by itself; type=none keeps it plain.',
3751
+ 'Name each node after the product it is ("Postgres", "Redis", "Amazon S3") so it draws the mark, and choose the node type that says what it is.',
3752
+ "Keep labels to two to four words; the picture carries the rest."
3753
+ ];
3754
+ }
3755
+ });
3756
+
3503
3757
  // ../../packages/diagrams/src/constants/status-icons.ts
3504
3758
  var STATUS_ICON_SPECS;
3505
3759
  var init_status_icons = __esm({
@@ -3548,6 +3802,7 @@ var init_constants = __esm({
3548
3802
  init_directions();
3549
3803
  init_word_list();
3550
3804
  init_flow_node_types();
3805
+ init_auto_group_types();
3551
3806
  init_group_types();
3552
3807
  init_overlays();
3553
3808
  init_node_change();
@@ -3576,6 +3831,7 @@ var init_constants = __esm({
3576
3831
  init_icon_concepts();
3577
3832
  init_icon_sets();
3578
3833
  init_label_icons();
3834
+ init_label_vocabulary();
3579
3835
  init_status_icons();
3580
3836
  init_tones();
3581
3837
  }
@@ -3644,7 +3900,7 @@ function formatDiagnostic(diagnostic, source) {
3644
3900
  if (diagnostic.hint !== null) parts.push(`hint: ${diagnostic.hint}`);
3645
3901
  return parts.join("\n");
3646
3902
  }
3647
- var DIAGNOSTIC_CODES;
3903
+ var DIAGNOSTIC_CODES, STYLE_HINT_CODES;
3648
3904
  var init_diagnostics = __esm({
3649
3905
  "../../packages/diagrams/src/core/diagnostics.ts"() {
3650
3906
  "use strict";
@@ -3677,6 +3933,9 @@ var init_diagnostics = __esm({
3677
3933
  suggestIcon: "W204",
3678
3934
  suggestGroupType: "W205",
3679
3935
  overlayCovers: "W206",
3936
+ // Style suggestions. W5xx says how a valid diagram would look more finished; like W204 and W205 it is a suggestion, not a problem
3937
+ // (`isStyleHint`), so a check that fails on warnings passes it.
3938
+ suggestLineRole: "W500",
3680
3939
  // Mermaid import. M1xx stops a conversion; M2xx reports something dropped or changed and never stops it.
3681
3940
  // They point into the Mermaid source that was submitted, not into the Plotwork source.
3682
3941
  mermaidNotDiagram: "M100",
@@ -3853,6 +4112,7 @@ var init_diagnostics = __esm({
3853
4112
  overrideRefused: "S202",
3854
4113
  overridesMissing: "S203"
3855
4114
  };
4115
+ STYLE_HINT_CODES = /* @__PURE__ */ new Set([DIAGNOSTIC_CODES.suggestIcon, DIAGNOSTIC_CODES.suggestGroupType, DIAGNOSTIC_CODES.suggestLineRole]);
3856
4116
  }
3857
4117
  });
3858
4118
 
@@ -4023,13 +4283,13 @@ function indexOf(set2) {
4023
4283
  const known3 = indexes.get(set2);
4024
4284
  if (known3 !== void 0) return known3;
4025
4285
  const byName = /* @__PURE__ */ new Map();
4026
- const phrases = /* @__PURE__ */ new Map();
4286
+ const phrases2 = /* @__PURE__ */ new Map();
4027
4287
  for (const icon of set2.icons) {
4028
4288
  if (byName.has(icon.name)) continue;
4029
4289
  byName.set(icon.name, definitionOf(icon));
4030
- phrases.set(icon.name, new Set([normalizePhrase(icon.name), ...icon.aliases.map(normalizePhrase)].filter(Boolean)));
4290
+ phrases2.set(icon.name, new Set([normalizePhrase(icon.name), ...icon.aliases.map(normalizePhrase)].filter(Boolean)));
4031
4291
  }
4032
- const index2 = { byName, phrases };
4292
+ const index2 = { byName, phrases: phrases2 };
4033
4293
  indexes.set(set2, index2);
4034
4294
  return index2;
4035
4295
  }
@@ -6697,9 +6957,9 @@ function cloudPhrases(generic) {
6697
6957
  const ref = `${set2}:${spec.name}`;
6698
6958
  const bases = [normalizePhrase(spec.name), ...(spec.aliases ?? []).map(normalizePhrase)];
6699
6959
  const title = normalizePhrase(spec.title);
6700
- const phrases = /* @__PURE__ */ new Set([title, ...bases]);
6701
- for (const providerName2 of providerNames) for (const base of bases) phrases.add(`${providerName2} ${base}`);
6702
- for (const phrase2 of phrases) {
6960
+ const phrases2 = /* @__PURE__ */ new Set([title, ...bases]);
6961
+ for (const providerName2 of providerNames) for (const base of bases) phrases2.add(`${providerName2} ${base}`);
6962
+ for (const phrase2 of phrases2) {
6703
6963
  if (spec.vendor !== void 0 && claimed.has(phrase2)) continue;
6704
6964
  const isScoped = spec.scopeToProvider === true && phrase2 !== title && !providerNames.some((providerName2) => phrase2.startsWith(`${providerName2} `));
6705
6965
  out.push({ phrase: phrase2, words: phrase2.split(" ").length, ref, set: set2, isGeneric: generic.has(phrase2) || isScoped, isBrand: false });
@@ -6712,8 +6972,8 @@ function cloudPhrases(generic) {
6712
6972
  function brandPhrases() {
6713
6973
  return listSetNames("simple").flatMap((name2) => {
6714
6974
  const ref = `simple:${name2}`;
6715
- const phrases = /* @__PURE__ */ new Set([normalizePhrase(name2), ...(ICON_ALIASES[ref] ?? []).map(normalizePhrase), ...(findIcon(ref)?.aliases ?? []).map(normalizePhrase)]);
6716
- return [...phrases].map((phrase2) => ({ phrase: phrase2, words: phrase2.split(" ").length, ref, set: "simple", isGeneric: false, isBrand: true }));
6975
+ const phrases2 = /* @__PURE__ */ new Set([normalizePhrase(name2), ...(ICON_ALIASES[ref] ?? []).map(normalizePhrase), ...(findIcon(ref)?.aliases ?? []).map(normalizePhrase)]);
6976
+ return [...phrases2].map((phrase2) => ({ phrase: phrase2, words: phrase2.split(" ").length, ref, set: "simple", isGeneric: false, isBrand: true }));
6717
6977
  });
6718
6978
  }
6719
6979
  function listProductPhrases() {
@@ -6800,7 +7060,7 @@ function indexIcon(ref) {
6800
7060
  const glyph = glyphNameOf(ref);
6801
7061
  const plainNames = [normalizePhrase(name2), ...(ICON_ALIASES[ref] ?? []).map(normalizePhrase)];
6802
7062
  const aliases = (icon?.aliases ?? []).map(normalizePhrase);
6803
- const phrases = /* @__PURE__ */ new Set([...plainNames, ...aliases]);
7063
+ const phrases2 = /* @__PURE__ */ new Set([...plainNames, ...aliases]);
6804
7064
  const kindPhrases = /* @__PURE__ */ new Set();
6805
7065
  const providerKindPhrases = /* @__PURE__ */ new Set();
6806
7066
  const titleWords = /* @__PURE__ */ new Set();
@@ -6809,11 +7069,11 @@ function indexIcon(ref) {
6809
7069
  for (const word2 of normalizePhrase(spec.title).split(" ")) titleWords.add(word2);
6810
7070
  for (const base of [...plainNames, ...aliases, normalizePhrase(spec.title)]) stems.add(stemPhrase(base));
6811
7071
  const provider = CLOUD_PROVIDER_SPECS[cloudSet];
6812
- phrases.add(normalizePhrase(spec.title));
6813
- if (spec.kind === "provider") for (const providerName2 of provider.names) phrases.add(providerName2);
7072
+ phrases2.add(normalizePhrase(spec.title));
7073
+ if (spec.kind === "provider") for (const providerName2 of provider.names) phrases2.add(providerName2);
6814
7074
  const kindPhraseList = CLOUD_KIND_SPECS[spec.kind].phrases.map(normalizePhrase);
6815
7075
  for (const providerName2 of provider.names) {
6816
- for (const base of [...plainNames, ...aliases]) phrases.add(`${providerName2} ${base}`);
7076
+ for (const base of [...plainNames, ...aliases]) phrases2.add(`${providerName2} ${base}`);
6817
7077
  for (const kindPhrase of kindPhraseList) providerKindPhrases.add(`${providerName2} ${kindPhrase}`);
6818
7078
  }
6819
7079
  for (const kindPhrase of kindPhraseList) kindPhrases.add(kindPhrase);
@@ -6822,8 +7082,8 @@ function indexIcon(ref) {
6822
7082
  ref,
6823
7083
  tags: (icon?.tags ?? []).map((tag) => tag.toLowerCase()),
6824
7084
  concept: glyph === null ? void 0 : CONCEPTS_BY_GLYPH.get(glyph),
6825
- phrases,
6826
- haystack: ` ${[...phrases].join(" | ")} `,
7085
+ phrases: phrases2,
7086
+ haystack: ` ${[...phrases2].join(" | ")} `,
6827
7087
  stems,
6828
7088
  titleWords,
6829
7089
  isVendorOnly: spec?.vendor !== void 0,
@@ -6944,7 +7204,11 @@ var init_guide = __esm({
6944
7204
  helm: { reads: "The output of helm template, or a chart folder (Chart.yaml, charts/, Chart.lock, values.yaml).", draws: "flow as kubernetes framed by release and chart, or a flow of charts and their dependencies", options: "none." },
6945
7205
  kustomize: { reads: "The output of kustomize build, or a folder of kustomization.yaml files with what they list.", draws: "flow as kubernetes, or a flow of overlays, bases, and components", options: "none." },
6946
7206
  backstage: { reads: "Backstage catalog descriptors (catalog-info.yaml), one file or a folder.", draws: "flow of components, APIs, and resources in systems and domains", options: "groupBy owner: frame by the owning group instead." },
7207
+ structurizr: { reads: "A Structurizr workspace: its DSL (workspace.dsl, with !identifiers hierarchical) or its JSON export.", draws: "c4 of one view: the containers of the first system that has some, or the view the workspace defines first", options: "view context, container, or component; root: the system or container, by identifier or name." },
7208
+ asyncapi: { reads: "AsyncAPI 2.x or 3.x documents in YAML or JSON; send one application's, or a folder of them in files.", draws: "flow of applications and channels in their brokers' frames, an arrow for each send or receive labelled with its messages", options: "none." },
6947
7209
  "github-actions": { reads: "A GitHub Actions workflow file.", draws: "flow of the trigger, the jobs in needs order, and their steps", options: "none." },
7210
+ "gitlab-ci": { reads: "A .gitlab-ci.yml; extends and hidden jobs are resolved, include: is not fetched.", draws: "flow of stages as frames in order, jobs by needs or stage after stage, dashed when manual or by rules, triggers to downstream pipelines", options: "none." },
7211
+ "azure-pipelines": { reads: "An azure-pipelines.yml: stages, jobs, and deployment jobs, or a file of only jobs or steps; templates are not fetched.", draws: "flow of stages as frames, jobs by dependsOn at both levels, deployments with their environment", options: "none." },
6948
7212
  csv: { reads: "A CSV, TSV, or semicolon-separated table.", draws: "the chart its shape suits: xychart, pie, sankey, treemap, or matrix", options: "chart: auto or one of those." },
6949
7213
  json: { reads: "Records, rows, a name to number object, or nested objects of numbers.", draws: "the same charts as csv", options: "chart." },
6950
7214
  tree: { reads: "tree output, a list of paths, or an indented outline.", draws: "tree", options: "none." },
@@ -6958,8 +7222,8 @@ var IMPORT_FORMATS, IMPORT_VIEWS, IMPORT_CHARTS, IMPORT_GROUP_BY, NO_STATS;
6958
7222
  var init_types2 = __esm({
6959
7223
  "../../packages/diagrams/src/importers/types.ts"() {
6960
7224
  "use strict";
6961
- IMPORT_FORMATS = ["sql", "prisma", "drizzle", "openapi", "terraform", "terraform-plan", "bicep", "arm", "pulumi", "cloudformation", "cdk", "compose", "kubernetes", "helm", "kustomize", "backstage", "github-actions", "csv", "json", "tree", "git-log"];
6962
- IMPORT_VIEWS = ["endpoints", "map", "sequence", "git", "timeline"];
7225
+ IMPORT_FORMATS = ["sql", "prisma", "drizzle", "openapi", "terraform", "terraform-plan", "bicep", "arm", "pulumi", "cloudformation", "cdk", "compose", "kubernetes", "helm", "kustomize", "backstage", "structurizr", "asyncapi", "github-actions", "gitlab-ci", "azure-pipelines", "csv", "json", "tree", "git-log"];
7226
+ IMPORT_VIEWS = ["endpoints", "map", "sequence", "git", "timeline", "context", "container", "component"];
6963
7227
  IMPORT_CHARTS = ["auto", "xychart", "pie", "sankey", "treemap", "matrix"];
6964
7228
  IMPORT_GROUP_BY = ["module", "network", "file", "owner"];
6965
7229
  NO_STATS = { items: 0, links: 0, groups: 0, dropped: 0 };
@@ -7023,7 +7287,8 @@ var init_looks = __esm({
7023
7287
  system: { caption: "Software System", frame: "dashed", icon: null },
7024
7288
  container: { caption: "Container", frame: "dashed", icon: null },
7025
7289
  enterprise: { caption: "Enterprise", frame: "dashed", icon: null },
7026
- deployment: { caption: "Deployment Node", frame: "solid", icon: "lucide:server" }
7290
+ deployment: { caption: "Deployment Node", frame: "solid", icon: "lucide:server" },
7291
+ group: { caption: "Group", frame: "dashed", icon: null }
7027
7292
  };
7028
7293
  }
7029
7294
  });
@@ -102504,13 +102769,13 @@ var init_stretch = __esm({
102504
102769
  function tryRoutes(scene, changes) {
102505
102770
  const old = changes.map(({ edge }) => ({ edge, points: edge.points, label: edge.label, hits: intrusions(scene, edge, edge.points, edge.label) }));
102506
102771
  const crossingsOf2 = (edge) => scene.edges.reduce((sum, other) => other === edge ? sum : sum + crossingsNear(edge.points, other.points), 0);
102507
- const runsOf5 = (edge) => scene.edges.reduce((sum, other) => other === edge ? sum : sum + sharedRuns(edge.points, other.points), 0);
102508
- const before = changes.reduce((sum, { edge }) => ({ crossings: sum.crossings + crossingsOf2(edge), runs: sum.runs + runsOf5(edge), bends: sum.bends + bends(edge.points) }), { crossings: 0, runs: 0, bends: 0 });
102772
+ const runsOf6 = (edge) => scene.edges.reduce((sum, other) => other === edge ? sum : sum + sharedRuns(edge.points, other.points), 0);
102773
+ const before = changes.reduce((sum, { edge }) => ({ crossings: sum.crossings + crossingsOf2(edge), runs: sum.runs + runsOf6(edge), bends: sum.bends + bends(edge.points) }), { crossings: 0, runs: 0, bends: 0 });
102509
102774
  for (const change of changes) {
102510
102775
  change.edge.points = change.points;
102511
102776
  change.edge.label = change.label;
102512
102777
  }
102513
- const after = changes.reduce((sum, { edge }) => ({ crossings: sum.crossings + crossingsOf2(edge), runs: sum.runs + runsOf5(edge), bends: sum.bends + bends(edge.points) }), { crossings: 0, runs: 0, bends: 0 });
102778
+ const after = changes.reduce((sum, { edge }) => ({ crossings: sum.crossings + crossingsOf2(edge), runs: sum.runs + runsOf6(edge), bends: sum.bends + bends(edge.points) }), { crossings: 0, runs: 0, bends: 0 });
102514
102779
  const isClean = old.every(({ edge, hits }) => isSubset(intrusions(scene, edge, edge.points, edge.label), hits)) && after.crossings <= before.crossings && after.runs <= before.runs && after.bends <= before.bends;
102515
102780
  if (isClean) return true;
102516
102781
  for (const { edge, points, label } of old) {
@@ -105226,7 +105491,7 @@ var init_words = __esm({
105226
105491
  C4_VIEWS = ["context", "container", "component", "deployment", "dynamic"];
105227
105492
  DEFAULT_VIEW = "container";
105228
105493
  C4_ELEMENT_WORDS = ["person", "system", "container", "component", "db"];
105229
- C4_BOUNDARY_KINDS = ["system", "container", "enterprise", "deployment"];
105494
+ C4_BOUNDARY_KINDS = ["system", "container", "enterprise", "deployment", "group"];
105230
105495
  DEFAULT_BOUNDARY_KIND = "system";
105231
105496
  C4_HEADER_OPTIONS = ["view", "direction", "desc", "icons"];
105232
105497
  C4_ELEMENT_OPTIONS = ["desc", "tech", "external", "icon", "tone"];
@@ -105351,7 +105616,7 @@ function resolveRelationships(state) {
105351
105616
  const elementIds = new Set(state.elements.map((element) => element.id));
105352
105617
  const boundaryIds = new Set(state.boundaries.map((boundary) => boundary.id));
105353
105618
  const names8 = [...elementIds];
105354
- const resolved = [];
105619
+ const resolved2 = [];
105355
105620
  let suggested = 0;
105356
105621
  for (const pending of state.pending) {
105357
105622
  const missing = [pending.from, pending.to].filter((end) => !elementIds.has(end.text));
@@ -105362,19 +105627,19 @@ function resolveRelationships(state) {
105362
105627
  state.report.push(errorAt(DIAGNOSTIC_CODES.unknownNode, message2, spanOf2(end), `${guess}Declare it first with: container ${end.text} "Name"`));
105363
105628
  }
105364
105629
  if (missing.length > 0) continue;
105365
- resolved.push({
105366
- id: toEdgeId(resolved.length),
105630
+ resolved2.push({
105631
+ id: toEdgeId(resolved2.length),
105367
105632
  from: toNodeId(pending.from.text),
105368
105633
  to: toNodeId(pending.to.text),
105369
105634
  label: pending.label,
105370
105635
  tech: pending.tech,
105371
105636
  line: pending.arrow.line,
105372
105637
  heads: pending.arrow.heads,
105373
- ...state.view === "dynamic" ? { step: DEFAULT_FIRST_STEP + resolved.length } : {},
105638
+ ...state.view === "dynamic" ? { step: DEFAULT_FIRST_STEP + resolved2.length } : {},
105374
105639
  at: pending.at
105375
105640
  });
105376
105641
  }
105377
- return resolved;
105642
+ return resolved2;
105378
105643
  }
105379
105644
  function dispatch(line3, state, scopes) {
105380
105645
  const cursor = new TokenCursor(line3.tokens);
@@ -106393,7 +106658,7 @@ function c4Vocabulary() {
106393
106658
  example: 'container api "API Application" tech="Spring Boot" desc="Serves the banking data as JSON."'
106394
106659
  },
106395
106660
  {
106396
- syntax: 'boundary <id> "Name" kind=system|container|enterprise|deployment tech="<technology>" {',
106661
+ syntax: 'boundary <id> "Name" kind=system|container|enterprise|deployment|group tech="<technology>" {',
106397
106662
  meaning: "Opens a boundary: a frame with its name and a caption such as [Software System] in a corner tab, around the elements written inside it. Boundaries nest, up to four deep, and close with } on a line of its own. A deployment boundary is a solid frame with a server icon, and tech= is its technology, such as Ubuntu 22.04.",
106398
106663
  example: 'boundary bank "Internet Banking System" kind=system {'
106399
106664
  },
@@ -108974,15 +109239,15 @@ function resolveRelation(pending, state, ids, known3, index2) {
108974
109239
  }
108975
109240
  function resolveNotes(state) {
108976
109241
  const ids = state.classes.map((node3) => node3.id);
108977
- const resolved = [];
109242
+ const resolved2 = [];
108978
109243
  for (const note of state.notes) {
108979
109244
  if (note.target !== null && !ids.includes(note.target.text)) {
108980
109245
  state.report.push(errorAt(DIAGNOSTIC_CODES.unknownNode, `Unknown class "${note.target.text}"`, note.target, `${didYouMean(note.target.text, ids)} A note belongs to a class: note on Order : "text"`));
108981
109246
  continue;
108982
109247
  }
108983
- resolved.push({ id: `note${resolved.length}`, text: note.text, target: note.target === null ? null : toNodeId(note.target.text), at: note.at });
109248
+ resolved2.push({ id: `note${resolved2.length}`, text: note.text, target: note.target === null ? null : toNodeId(note.target.text), at: note.at });
108984
109249
  }
108985
- return resolved;
109250
+ return resolved2;
108986
109251
  }
108987
109252
  function parseClass(lines, report3) {
108988
109253
  const state = {
@@ -109017,8 +109282,8 @@ function parseClass(lines, report3) {
109017
109282
  const ids = state.classes.map((node3) => node3.id);
109018
109283
  const known3 = new Set(ids);
109019
109284
  for (const pending of state.relations) {
109020
- const resolved = resolveRelation(pending, state, ids, known3, relations.length);
109021
- if (resolved !== null) relations.push(resolved);
109285
+ const resolved2 = resolveRelation(pending, state, ids, known3, relations.length);
109286
+ if (resolved2 !== null) relations.push(resolved2);
109022
109287
  }
109023
109288
  return {
109024
109289
  kind: "class",
@@ -110253,9 +110518,39 @@ var init_flow_outline = __esm({
110253
110518
  }
110254
110519
  });
110255
110520
 
110521
+ // ../../packages/diagrams/src/parse/label-icon.ts
110522
+ function verbPhrases() {
110523
+ phrases ??= Object.entries(LABEL_VERB_PHRASES).flatMap(([verb, words2]) => (words2 ?? []).map((phrase2) => [phrase2, verb])).sort((a, b) => b[0].split(" ").length - a[0].split(" ").length || b[0].length - a[0].length);
110524
+ return phrases;
110525
+ }
110526
+ function startsWithWords(text3, phrase2) {
110527
+ return text3 === phrase2 || text3.startsWith(`${phrase2} `);
110528
+ }
110529
+ function verbNamedBy(label) {
110530
+ const text3 = normalizePhrase(label);
110531
+ if (text3 === "" || text3.split(" ").length > LABEL_VERB_MAX_WORDS) return null;
110532
+ if (NOT_VERB_PHRASES.some((phrase2) => startsWithWords(text3, phrase2))) return null;
110533
+ const found = verbPhrases().find(([phrase2]) => startsWithWords(text3, phrase2));
110534
+ return found === void 0 ? null : found[1];
110535
+ }
110536
+ function labelIconFor(label) {
110537
+ const word2 = label.trim().toLowerCase();
110538
+ if (Object.hasOwn(LABEL_ICON_DEFAULTS, word2)) return LABEL_ICON_DEFAULTS[word2] ?? null;
110539
+ const verb = verbNamedBy(label);
110540
+ return verb === null ? null : EDGE_STYLES[verb].icon;
110541
+ }
110542
+ var phrases;
110543
+ var init_label_icon = __esm({
110544
+ "../../packages/diagrams/src/parse/label-icon.ts"() {
110545
+ "use strict";
110546
+ init_constants();
110547
+ init_phrase();
110548
+ }
110549
+ });
110550
+
110256
110551
  // ../../packages/diagrams/src/parse/group-options.ts
110257
110552
  function resolveGroupType(option, state) {
110258
- if (option === void 0) return null;
110553
+ if (option === void 0 || option.value === PLAIN_GROUP_TYPE) return null;
110259
110554
  const type = GROUP_TYPES.find((candidate2) => candidate2 === option.value);
110260
110555
  if (type !== void 0) return type;
110261
110556
  state.report.push(errorAt(DIAGNOSTIC_CODES.unknownGroupType, `Unknown group type "${option.value}"`, option.valueToken, didYouMean(option.value, GROUP_TYPES)));
@@ -110268,8 +110563,29 @@ function resolveProvider(option, state) {
110268
110563
  state.report.push(errorAt(DIAGNOSTIC_CODES.unknownProvider, `Unknown provider "${option.value}"`, option.valueToken, providerHint(option.value)));
110269
110564
  return null;
110270
110565
  }
110271
- function resolveGroupLook(options3, parent, state) {
110272
- const type = resolveGroupType(findOption(options3, "type"), state);
110566
+ function wordsOf2(phrase2) {
110567
+ return phrase2.split(" ").length;
110568
+ }
110569
+ function longestOtherReading(normalized, type) {
110570
+ const lengths = GROUP_TYPES.filter((other) => other !== type).flatMap((other) => GROUP_TYPE_SPECS[other].phrases.filter((phrase2) => ` ${normalized} `.includes(` ${phrase2} `)).map(wordsOf2));
110571
+ return Math.max(0, ...lengths);
110572
+ }
110573
+ function autoGroupType(label) {
110574
+ const trimmed4 = label.trim().toLowerCase();
110575
+ if (REGION_CODE.test(trimmed4) || AZURE_REGION_NAMES.has(trimmed4)) return "region";
110576
+ const normalized = normalizePhrase(label);
110577
+ let best = null;
110578
+ for (const [type, phrases2] of Object.entries(AUTO_GROUP_TYPES)) {
110579
+ const fits = [...(phrases2?.ends ?? []).filter((phrase2) => normalized === phrase2 || normalized.endsWith(` ${phrase2}`)), ...(phrases2?.whole ?? []).filter((phrase2) => normalized === phrase2)];
110580
+ for (const phrase2 of fits) if (best === null || wordsOf2(phrase2) > best.words) best = { type, words: wordsOf2(phrase2) };
110581
+ }
110582
+ return best === null || longestOtherReading(normalized, best.type) > best.words ? null : best.type;
110583
+ }
110584
+ function resolveGroupLook(options3, parent, state, label = null) {
110585
+ const typeOption = findOption(options3, "type");
110586
+ const isPlain = typeOption?.value === PLAIN_GROUP_TYPE;
110587
+ const named = typeOption === void 0 && label !== null && !iconsAreOff(state) ? autoGroupType(label) : null;
110588
+ const type = named ?? resolveGroupType(typeOption, state);
110273
110589
  const inherited = parent === null ? state.provider : state.groups.find((group2) => group2.id === parent)?.provider ?? state.provider;
110274
110590
  const provider = resolveProvider(findOption(options3, "provider"), state) ?? inherited;
110275
110591
  const spec = type === null ? null : GROUP_TYPE_SPECS[type];
@@ -110278,19 +110594,24 @@ function resolveGroupLook(options3, parent, state) {
110278
110594
  provider,
110279
110595
  tone: resolveTone(findOption(options3, "tone"), spec?.tone ?? GROUP_DEFAULTS.tone, state),
110280
110596
  icon: resolveIcon(findOption(options3, "icon"), type === null ? GROUP_DEFAULTS.icon : groupIcon(type, provider), state),
110281
- frame: spec?.frame ?? GROUP_DEFAULTS.frame
110597
+ frame: spec?.frame ?? GROUP_DEFAULTS.frame,
110598
+ ...isPlain ? { isPlain: true } : {}
110282
110599
  };
110283
110600
  }
110601
+ var PLAIN_GROUP_TYPE;
110284
110602
  var init_group_options = __esm({
110285
110603
  "../../packages/diagrams/src/parse/group-options.ts"() {
110286
110604
  "use strict";
110287
110605
  init_constants();
110288
110606
  init_core();
110289
110607
  init_icons();
110608
+ init_phrase();
110609
+ init_auto_icon();
110290
110610
  init_icon_hint();
110291
110611
  init_options();
110292
110612
  init_statement_helpers();
110293
110613
  init_suggest();
110614
+ PLAIN_GROUP_TYPE = "none";
110294
110615
  }
110295
110616
  });
110296
110617
 
@@ -110533,7 +110854,7 @@ function parseGroupStatement(cursor, state, line3, parent) {
110533
110854
  state.groups.push({
110534
110855
  id: groupId,
110535
110856
  label,
110536
- ...resolveGroupLook(options3, parent, state),
110857
+ ...resolveGroupLook(options3, parent, state, label),
110537
110858
  parent,
110538
110859
  sub: findOption(options3, "sub")?.value ?? null,
110539
110860
  stack: resolveStack(findOption(options3, "stack"), state, true),
@@ -110560,12 +110881,12 @@ function parseEdgeStatement(cursor, state, line3) {
110560
110881
  if (iconOption2 !== void 0 && label === null) {
110561
110882
  state.report.push(errorAt(DIAGNOSTIC_CODES.syntax, "An edge icon needs a label", iconOption2.keyToken, 'Write: a -> b : "label" icon=status:success'));
110562
110883
  }
110563
- const icon = resolveIcon(iconOption2, label === null ? null : LABEL_ICON_DEFAULTS[label.trim().toLowerCase()] ?? null, state);
110884
+ const icon = resolveIcon(iconOption2, label === null || iconsAreOff(state) ? null : labelIconFor(label), state);
110564
110885
  rejectTrailing(cursor, state, "Write one edge per line; chains like a -> b -> c are not supported.");
110565
110886
  const spec = ARROW_SPECS[arrow.text];
110566
110887
  const motion = readLineMotion(options3, state.motion, spec.heads, from, state.report);
110567
110888
  const role = readLineRole(findOption(options3, "role"), state.report);
110568
- state.edges.push({ from: from.text, to: to.text, fromToken: from, toToken: to, label, icon, line: spec.line, heads: spec.heads, ...motion, ...role === void 0 ? {} : { role } });
110889
+ state.edges.push({ from: from.text, to: to.text, fromToken: from, toToken: to, label, icon, ...iconOption2 === void 0 ? {} : { hasOwnIcon: true }, line: spec.line, heads: spec.heads, ...motion, ...role === void 0 ? {} : { role } });
110569
110890
  }
110570
110891
  function parseOverlayStatement(cursor, state, line3, parent) {
110571
110892
  cursor.next();
@@ -110606,6 +110927,7 @@ var init_flow_statements = __esm({
110606
110927
  init_constants();
110607
110928
  init_core();
110608
110929
  init_auto_icon();
110930
+ init_label_icon();
110609
110931
  init_group_options();
110610
110932
  init_line_motion();
110611
110933
  init_line_role();
@@ -110654,7 +110976,7 @@ function isRefused(member, at3, overlay, seen, state, parents) {
110654
110976
  function resolveOverlays(state) {
110655
110977
  const parents = parentsOf2(state);
110656
110978
  const claimedBy = /* @__PURE__ */ new Map();
110657
- const resolved = [];
110979
+ const resolved2 = [];
110658
110980
  for (const overlay of state.overlays) {
110659
110981
  const seen = /* @__PURE__ */ new Set();
110660
110982
  let isSound = true;
@@ -110678,9 +111000,9 @@ function resolveOverlays(state) {
110678
111000
  if (!isSound) continue;
110679
111001
  for (const member of overlay.members) claimedBy.set(member, overlay.id);
110680
111002
  const { memberAt: _memberAt, ...rest } = overlay;
110681
- resolved.push(rest);
111003
+ resolved2.push(rest);
110682
111004
  }
110683
- return resolved;
111005
+ return resolved2;
110684
111006
  }
110685
111007
  var init_overlay_resolve = __esm({
110686
111008
  "../../packages/diagrams/src/parse/overlay-resolve.ts"() {
@@ -110734,7 +111056,7 @@ function closeScope3(line3, state, scopes) {
110734
111056
  function resolveEdges(state) {
110735
111057
  const nodeIds = state.nodes.map((node3) => node3.id);
110736
111058
  const known3 = new Set(nodeIds);
110737
- const resolved = [];
111059
+ const resolved2 = [];
110738
111060
  for (const edge of state.edges) {
110739
111061
  const missing = [
110740
111062
  { id: edge.from, token: edge.fromToken },
@@ -110746,12 +111068,13 @@ function resolveEdges(state) {
110746
111068
  state.report.push(errorAt(DIAGNOSTIC_CODES.unknownNode, `Unknown node "${end.id}"`, end.token, hint2));
110747
111069
  }
110748
111070
  if (missing.length === 0) {
110749
- resolved.push({
110750
- id: toEdgeId(resolved.length),
111071
+ resolved2.push({
111072
+ id: toEdgeId(resolved2.length),
110751
111073
  from: toNodeId(edge.from),
110752
111074
  to: toNodeId(edge.to),
110753
111075
  label: edge.label,
110754
111076
  icon: edge.icon,
111077
+ ...edge.hasOwnIcon === void 0 ? {} : { hasOwnIcon: edge.hasOwnIcon },
110755
111078
  line: edge.line,
110756
111079
  heads: edge.heads,
110757
111080
  ...edge.isAnimated === void 0 ? {} : { isAnimated: edge.isAnimated },
@@ -110760,7 +111083,7 @@ function resolveEdges(state) {
110760
111083
  });
110761
111084
  }
110762
111085
  }
110763
- return resolved;
111086
+ return resolved2;
110764
111087
  }
110765
111088
  function parseFlow(lines, report3) {
110766
111089
  const state = {
@@ -111154,7 +111477,7 @@ function typesNamedBy(label) {
111154
111477
  function suggestGroupTypes(diagram) {
111155
111478
  const warnings = [];
111156
111479
  for (const group2 of diagram.groups) {
111157
- if (group2.type !== null) continue;
111480
+ if (group2.type !== null || group2.isPlain === true) continue;
111158
111481
  const types = typesNamedBy(group2.label);
111159
111482
  const [type] = types;
111160
111483
  if (types.length !== 1 || type === void 0) continue;
@@ -111172,7 +111495,6 @@ function suggestGroupTypes(diagram) {
111172
111495
  }
111173
111496
  return warnings;
111174
111497
  }
111175
- var REGION_CODE;
111176
111498
  var init_suggest_group_types = __esm({
111177
111499
  "../../packages/diagrams/src/lint/suggest-group-types.ts"() {
111178
111500
  "use strict";
@@ -111180,7 +111502,53 @@ var init_suggest_group_types = __esm({
111180
111502
  init_core();
111181
111503
  init_icons();
111182
111504
  init_suggest_icons();
111183
- REGION_CODE = /^(?:(?:us|eu|ap|sa|ca|me|af|il|mx)-[a-z]+-\d|(?:us|europe|asia|australia|southamerica|northamerica|me|africa)-[a-z]+\d)$/;
111505
+ }
111506
+ });
111507
+
111508
+ // ../../packages/diagrams/src/lint/suggest-line-roles.ts
111509
+ function nameOf(edge) {
111510
+ return `${edge.from} -> ${edge.to}`;
111511
+ }
111512
+ function suggestLineRoles(diagram) {
111513
+ const byRole = /* @__PURE__ */ new Map();
111514
+ for (const edge of diagram.edges) {
111515
+ if (edge.role !== void 0 || edge.hasOwnIcon === true || edge.label === null) continue;
111516
+ const verb = verbNamedBy(edge.label);
111517
+ const role = verb === null ? null : EDGE_STYLES[verb].role;
111518
+ if (role !== null) byRole.set(role, [...byRole.get(role) ?? [], edge]);
111519
+ }
111520
+ return LINE_ROLES.flatMap((role) => {
111521
+ const edges = byRole.get(role) ?? [];
111522
+ if (edges.length === 0) return [];
111523
+ const named = edges.slice(0, NAMED_ARROWS).map(nameOf).join(", ");
111524
+ const rest = edges.length > NAMED_ARROWS ? ` and ${edges.length - NAMED_ARROWS} more` : "";
111525
+ const doing = edges.length === 1 ? `1 arrow ${ROLE_DOING[role].one}` : `${edges.length} arrows ${ROLE_DOING[role].many}`;
111526
+ const spec = LINE_ROLE_SPECS[role];
111527
+ return [
111528
+ warningAt(
111529
+ DIAGNOSTIC_CODES.suggestLineRole,
111530
+ `${doing} (${named}${rest}) with no role=`,
111531
+ HEADER,
111532
+ edges.length === 1 ? `Add role=${role} to it, which draws the line ${spec.tone} and names it ${spec.legend} in the legend.` : `Add role=${role} to each, which draws the lines ${spec.tone} and names them ${spec.legend} in the legend.`
111533
+ )
111534
+ ];
111535
+ });
111536
+ }
111537
+ var ROLE_DOING, NAMED_ARROWS;
111538
+ var init_suggest_line_roles = __esm({
111539
+ "../../packages/diagrams/src/lint/suggest-line-roles.ts"() {
111540
+ "use strict";
111541
+ init_constants();
111542
+ init_core();
111543
+ init_label_icon();
111544
+ init_general_warnings();
111545
+ ROLE_DOING = {
111546
+ data: { one: "reads or writes stored data", many: "read or write stored data" },
111547
+ async: { one: "sends or receives a message that is not waited for", many: "send or receive messages that are not waited for" },
111548
+ identity: { one: "signs in or carries a credential", many: "sign in or carry credentials" },
111549
+ private: { one: "crosses a private network", many: "cross a private network" }
111550
+ };
111551
+ NAMED_ARROWS = 4;
111184
111552
  }
111185
111553
  });
111186
111554
 
@@ -111221,7 +111589,7 @@ function lintFlow(diagram, measure) {
111221
111589
  shortenedWarning(shortenedIds3(diagram)),
111222
111590
  widthWarning(measure.size, "Use direction=down, shorten labels, or split the diagram.")
111223
111591
  ]);
111224
- return [...general, ...overlayWarnings(diagram, measure.overlayCovers), ...suggestGroupTypes(diagram), ...suggestIcons(diagram)];
111592
+ return [...general, ...overlayWarnings(diagram, measure.overlayCovers), ...suggestGroupTypes(diagram), ...suggestIcons(diagram), ...suggestLineRoles(diagram)];
111225
111593
  }
111226
111594
  var init_lint3 = __esm({
111227
111595
  "../../packages/diagrams/src/kinds/flow/lint.ts"() {
@@ -111235,6 +111603,7 @@ var init_lint3 = __esm({
111235
111603
  init_overlay_warnings();
111236
111604
  init_suggest_group_types();
111237
111605
  init_suggest_icons();
111606
+ init_suggest_line_roles();
111238
111607
  }
111239
111608
  });
111240
111609
 
@@ -112649,19 +113018,19 @@ function resolve(declaration) {
112649
113018
  if (fill !== null && tone === null) leftOut.unshift(`fill ${fill} has no tone`);
112650
113019
  return { tone, fill, leftOut };
112651
113020
  }
112652
- function report2(state, declaration, resolved, subject, isApplied) {
113021
+ function report2(state, declaration, resolved2, subject, isApplied) {
112653
113022
  const { reporter } = state.ctx;
112654
- const leftOut = [...resolved.leftOut];
112655
- if (resolved.tone !== null && resolved.fill !== null && isApplied) {
113023
+ const leftOut = [...resolved2.leftOut];
113024
+ if (resolved2.tone !== null && resolved2.fill !== null && isApplied) {
112656
113025
  reporter.add(
112657
113026
  DIAGNOSTIC_CODES.mermaidMappedToNearest,
112658
113027
  declaration.span,
112659
- `The fill ${resolved.fill} of ${subject} became the tone ${resolved.tone}.`,
113028
+ `The fill ${resolved2.fill} of ${subject} became the tone ${resolved2.tone}.`,
112660
113029
  "Check that the tone says what it means. The theme draws it in light and dark.",
112661
113030
  "fill-tone"
112662
113031
  );
112663
- } else if (resolved.tone !== null && resolved.fill !== null) {
112664
- leftOut.unshift(`fill ${resolved.fill} has no node or subgraph to colour`);
113032
+ } else if (resolved2.tone !== null && resolved2.fill !== null) {
113033
+ leftOut.unshift(`fill ${resolved2.fill} has no node or subgraph to colour`);
112665
113034
  }
112666
113035
  if (leftOut.length > 0) reporter.dropped("styleLeftOut", declaration.span, leftOut.join(", "));
112667
113036
  }
@@ -112669,9 +113038,9 @@ function applyStyles(state) {
112669
113038
  const { builder, ctx } = state;
112670
113039
  const classTones = /* @__PURE__ */ new Map();
112671
113040
  for (const declaration of state.classDefs) {
112672
- const resolved = resolve(declaration);
112673
- if (resolved.tone !== null) for (const name2 of declaration.targets) classTones.set(name2, resolved.tone);
112674
- report2(state, declaration, resolved, `the class ${declaration.targets.map((name2) => `"${name2}"`).join(", ")}`, resolved.tone !== null);
113041
+ const resolved2 = resolve(declaration);
113042
+ if (resolved2.tone !== null) for (const name2 of declaration.targets) classTones.set(name2, resolved2.tone);
113043
+ report2(state, declaration, resolved2, `the class ${declaration.targets.map((name2) => `"${name2}"`).join(", ")}`, resolved2.tone !== null);
112675
113044
  }
112676
113045
  for (const node3 of builder.nodes) {
112677
113046
  for (const name2 of node3.classes) {
@@ -112680,9 +113049,9 @@ function applyStyles(state) {
112680
113049
  }
112681
113050
  }
112682
113051
  for (const declaration of state.styles) {
112683
- const resolved = resolve(declaration);
112684
- const applied = resolved.tone === null ? [] : declaration.targets.filter((target) => builder.setTone(target, resolved.tone));
112685
- report2(state, declaration, resolved, `the style of ${declaration.targets.map((target) => `"${target}"`).join(", ")}`, applied.length > 0);
113052
+ const resolved2 = resolve(declaration);
113053
+ const applied = resolved2.tone === null ? [] : declaration.targets.filter((target) => builder.setTone(target, resolved2.tone));
113054
+ report2(state, declaration, resolved2, `the style of ${declaration.targets.map((target) => `"${target}"`).join(", ")}`, applied.length > 0);
112686
113055
  }
112687
113056
  for (const use of state.classUses) {
112688
113057
  if (!classTones.has(use.name)) ctx.reporter.dropped("classAssign", use.span, use.name);
@@ -112830,7 +113199,7 @@ function matches(rule, words2) {
112830
113199
  return rule.whole.includes(whole2) || rule.last.includes(last) || words2.some((word2) => rule.product.includes(word2));
112831
113200
  }
112832
113201
  function keywordType(label) {
112833
- const words2 = wordsOf2(label);
113202
+ const words2 = wordsOf3(label);
112834
113203
  if (words2.length === 0 || words2.length > KEYWORD_MAX_WORDS) return null;
112835
113204
  const found = new Set(KEYWORD_RULES.filter((rule) => matches(rule, words2)).map((rule) => rule.type));
112836
113205
  const [only] = [...found];
@@ -112842,7 +113211,7 @@ function statusCodeTone(label) {
112842
113211
  return family === "4" || family === "5" ? "red" : null;
112843
113212
  }
112844
113213
  function productType(text3) {
112845
- const words2 = wordsOf2(text3);
113214
+ const words2 = wordsOf3(text3);
112846
113215
  if (words2.length === 0 || words2.length > KEYWORD_MAX_WORDS) return null;
112847
113216
  const found = new Set(KEYWORD_RULES.filter((rule) => words2.some((word2) => rule.product.includes(word2))).map((rule) => rule.type));
112848
113217
  const [only] = [...found];
@@ -112879,7 +113248,7 @@ function inferNodeIcon(node3, type) {
112879
113248
  if (NOT_PRODUCT_TYPES2.has(type) || FLOW_NODE_TYPE_DEFAULTS[type].shape === "pill") return null;
112880
113249
  return brandMarkNamedBy(node3.label) ?? (node3.sub === null ? null : brandMarkNamedBy(node3.sub));
112881
113250
  }
112882
- var wordsOf2, STATUS_CODE, NOT_PRODUCT_TYPES2;
113251
+ var wordsOf3, STATUS_CODE, NOT_PRODUCT_TYPES2;
112883
113252
  var init_infer = __esm({
112884
113253
  "../../packages/diagrams/src/import/infer.ts"() {
112885
113254
  "use strict";
@@ -112887,7 +113256,7 @@ var init_infer = __esm({
112887
113256
  init_icons();
112888
113257
  init_suggest_icons();
112889
113258
  init_mapping();
112890
- wordsOf2 = (label) => label.toLowerCase().split(/[^a-z0-9]+/).filter((word2) => word2 !== "");
113259
+ wordsOf3 = (label) => label.toLowerCase().split(/[^a-z0-9]+/).filter((word2) => word2 !== "");
112891
113260
  STATUS_CODE = /^(?:(?:return|returns|respond|respond with|response|http)\s+)?(?:http\s+)?([2-5])\d\d(?:\s|$)/i;
112892
113261
  NOT_PRODUCT_TYPES2 = /* @__PURE__ */ new Set(["decision", "terminal", "step"]);
112893
113262
  }
@@ -114278,7 +114647,7 @@ function resolveWindows(ir, calendar, ctx) {
114278
114647
  if (target === void 0) return fail2(task, `${what} "${key}", which is not a task of this chart.`, hint2);
114279
114648
  return windowOf(target, ir.tasks.indexOf(target)) ?? fail2(task, `${what} "${key}", which cannot be placed.`, "Fix that task first, or give this one a start date.");
114280
114649
  };
114281
- const startOf3 = (task, index2) => {
114650
+ const startOf4 = (task, index2) => {
114282
114651
  if (task.start.kind === "date") return task.start.day;
114283
114652
  if (task.start.kind === "previous") {
114284
114653
  const previous = ir.tasks[index2 - 1];
@@ -114289,7 +114658,7 @@ function resolveWindows(ir, calendar, ctx) {
114289
114658
  return ends.length === 0 || ends.includes(null) ? null : Math.max(...ends.map((day) => day ?? 0));
114290
114659
  };
114291
114660
  const place2 = (task, index2) => {
114292
- const wanted2 = startOf3(task, index2);
114661
+ const wanted2 = startOf4(task, index2);
114293
114662
  if (wanted2 === null) return null;
114294
114663
  const start2 = task.start.kind === "date" || !calendar.isActive ? wanted2 : nextWorkingDay(calendar, wanted2);
114295
114664
  const { end } = task;
@@ -116339,7 +116708,7 @@ function summarizeGit(diagram) {
116339
116708
  const counts = `${countOf(diagram.commits.length, "commit")} on ${countOf(diagram.branches.length, "branch", "branches")}${optionalCount(merges, "merge")}${optionalCount(picks2, "cherry-pick")}${optionalCount(tags, "tag")}`;
116340
116709
  return `${title}: ${counts}.${description}`;
116341
116710
  }
116342
- function nameOf(commit) {
116711
+ function nameOf2(commit) {
116343
116712
  return commit.message === null ? `commit ${commit.index + 1}` : `"${commit.message}"`;
116344
116713
  }
116345
116714
  function describeCommit(commit, diagram) {
@@ -116353,7 +116722,7 @@ function describeCommit(commit, diagram) {
116353
116722
  return `merge of ${commit.mergedBranch ?? "a branch"} into ${commit.branch}${message2}${nothing2}${kind}${id}${tags}`;
116354
116723
  }
116355
116724
  if (commit.origin === "cherry-pick") {
116356
- const of = source === void 0 ? "a commit" : `${source.id ?? nameOf(source)}${source.message === null ? "" : ` ("${source.message}")`}`;
116725
+ const of = source === void 0 ? "a commit" : `${source.id ?? nameOf2(source)}${source.message === null ? "" : ` ("${source.message}")`}`;
116357
116726
  const own2 = commit.message !== null && commit.message !== source?.message ? `, message "${commit.message}"` : "";
116358
116727
  return `cherry-pick of ${of}${own2}${kind}${id}${tags}`;
116359
116728
  }
@@ -116362,7 +116731,7 @@ function describeCommit(commit, diagram) {
116362
116731
  function describeBranch(branch, diagram) {
116363
116732
  const parent = branch.forkedFrom === null ? void 0 : diagram.commits[branch.forkedFrom];
116364
116733
  if (parent === void 0 || branch.parentBranch === null) return `${branch.name} is the default branch`;
116365
- return `${branch.name} was made from ${branch.parentBranch} at ${nameOf(parent)}`;
116734
+ return `${branch.name} was made from ${branch.parentBranch} at ${nameOf2(parent)}`;
116366
116735
  }
116367
116736
  function outlineGit(diagram) {
116368
116737
  const lines = [summarizeGit(diagram), "Branches, in lane order:"];
@@ -117191,7 +117560,7 @@ var init_check2 = __esm({
117191
117560
  });
117192
117561
 
117193
117562
  // ../../packages/diagrams/src/kinds/journey/layout-columns.ts
117194
- function wordsOf3(text3, role) {
117563
+ function wordsOf4(text3, role) {
117195
117564
  const widths = (text3 ?? "").split(/\s+/).filter(Boolean).map((word2) => measureRole(word2, role));
117196
117565
  return { widths, space: measureRole(" ", role), longest: widths.reduce((most, width) => Math.max(most, width), 0) };
117197
117566
  }
@@ -117231,7 +117600,7 @@ function candidates(needed) {
117231
117600
  }
117232
117601
  function chooseColumn(sections, hasTabs) {
117233
117602
  const tasks = sections.flatMap((section) => section.tasks);
117234
- const words2 = tasks.map((task) => ({ label: wordsOf3(task.label, "label"), sub: wordsOf3(task.sub, "sub") }));
117603
+ const words2 = tasks.map((task) => ({ label: wordsOf4(task.label, "label"), sub: wordsOf4(task.sub, "sub") }));
117235
117604
  const longest = words2.reduce((most, item) => Math.max(most, item.label.longest, item.sub.longest), 0);
117236
117605
  const widths = candidates(longest + card.paddingX * 2);
117237
117606
  const isClean = (column) => {
@@ -117540,11 +117909,11 @@ function assignTones(chosen) {
117540
117909
  return given2;
117541
117910
  });
117542
117911
  }
117543
- function wordsOf4(name2) {
117912
+ function wordsOf5(name2) {
117544
117913
  return (name2.match(WORD) ?? []).map((word2) => [...word2]);
117545
117914
  }
117546
117915
  function initialsCandidates(name2) {
117547
- const words2 = wordsOf4(name2);
117916
+ const words2 = wordsOf5(name2);
117548
117917
  const first = words2[0] ?? [];
117549
117918
  if (first.length === 0) return [[...name2.trim()][0]?.toUpperCase() ?? "?"];
117550
117919
  const upper = (chars) => chars.join("").toUpperCase();
@@ -119178,7 +119547,7 @@ function outlineLayers(diagram) {
119178
119547
  ...diagram.links.map((link2) => `- ${describeLink(link2, labels2)}`)
119179
119548
  ];
119180
119549
  }
119181
- var STYLE_WORDS, nameOf2, listOf;
119550
+ var STYLE_WORDS, nameOf3, listOf;
119182
119551
  var init_outline6 = __esm({
119183
119552
  "../../packages/diagrams/src/kinds/layers/outline.ts"() {
119184
119553
  "use strict";
@@ -119186,8 +119555,8 @@ var init_outline6 = __esm({
119186
119555
  init_check3();
119187
119556
  init_text3();
119188
119557
  STYLE_WORDS = { solid: "", dashed: ", optional or indirect", dotted: ", asynchronous" };
119189
- nameOf2 = (block) => block.sub === null ? oneLine2(block.label) : `${oneLine2(block.label)} (${oneLine2(block.sub)})`;
119190
- listOf = (blocks2) => blocks2.length === 0 ? "no blocks." : `${blocks2.map(nameOf2).join(", ")}.`;
119558
+ nameOf3 = (block) => block.sub === null ? oneLine2(block.label) : `${oneLine2(block.label)} (${oneLine2(block.sub)})`;
119559
+ listOf = (blocks2) => blocks2.length === 0 ? "no blocks." : `${blocks2.map(nameOf3).join(", ")}.`;
119191
119560
  }
119192
119561
  });
119193
119562
 
@@ -119403,7 +119772,7 @@ function close(line3, state) {
119403
119772
  function resolveLinks(state) {
119404
119773
  const ids = state.layers.flatMap((layer2) => layer2.blocks.map((block) => block.id));
119405
119774
  const inAside = new Set(state.asides.flatMap((aside) => aside.blocks.map((block) => block.id)));
119406
- const resolved = [];
119775
+ const resolved2 = [];
119407
119776
  for (const link2 of state.links) {
119408
119777
  const missing = [link2.from, link2.to].filter((end) => !ids.includes(end.text));
119409
119778
  for (const end of missing) {
@@ -119412,9 +119781,9 @@ function resolveLinks(state) {
119412
119781
  }
119413
119782
  if (missing.length > 0) continue;
119414
119783
  if (link2.from.text === link2.to.text) syntax(state, `The block "${link2.from.text}" cannot link to itself`, link2.to, "Link two different blocks.");
119415
- else resolved.push({ from: link2.from.text, to: link2.to.text, label: link2.label, line: link2.line, heads: link2.heads });
119784
+ else resolved2.push({ from: link2.from.text, to: link2.to.text, label: link2.label, line: link2.line, heads: link2.heads });
119416
119785
  }
119417
- return resolved;
119786
+ return resolved2;
119418
119787
  }
119419
119788
  function parseLayers(lines, report3) {
119420
119789
  const state = { title: null, description: null, direction: "down", showCounts: false, layers: [], asides: [], links: [], open: [], declared: /* @__PURE__ */ new Map(), report: report3 };
@@ -125659,21 +126028,21 @@ var init_marks2 = __esm({
125659
126028
  });
125660
126029
 
125661
126030
  // ../../packages/diagrams/src/kinds/sankey/node-content.ts
125662
- function nameOf3(node3) {
126031
+ function nameOf4(node3) {
125663
126032
  const name2 = oneLine6(node3.label);
125664
126033
  const chars = [...name2];
125665
126034
  return chars.length > MAX_NAME_CHARS ? { text: chars.slice(0, MAX_NAME_CHARS).join(""), isClipped: true } : { text: name2, isClipped: false };
125666
126035
  }
125667
126036
  function wantedWidth(node3, valueText) {
125668
- const text3 = Math.max(measureRole(nameOf3(node3).text, "label"), valueText === null ? 0 : measureRole(valueText, "sub"));
126037
+ const text3 = Math.max(measureRole(nameOf4(node3).text, "label"), valueText === null ? 0 : measureRole(valueText, "sub"));
125669
126038
  return Math.ceil(2 * SANKEY_METRICS.padX + (node3.icon === null ? 0 : SANKEY_METRICS.chipSize + SANKEY_METRICS.chipGap) + text3);
125670
126039
  }
125671
126040
  function floorWidth(node3, valueText) {
125672
- const longest = nameOf3(node3).text.split(" ").reduce((most, word2) => Math.max(most, measureRole(word2, "label")), 0);
126041
+ const longest = nameOf4(node3).text.split(" ").reduce((most, word2) => Math.max(most, measureRole(word2, "label")), 0);
125673
126042
  return Math.ceil(2 * SANKEY_METRICS.padX + (node3.icon === null ? 0 : SANKEY_METRICS.chipSize + SANKEY_METRICS.chipGap) + Math.max(longest, valueText === null ? 0 : measureRole(valueText, "sub")));
125674
126043
  }
125675
126044
  function fitNode(node3, valueText, width) {
125676
- const name2 = nameOf3(node3);
126045
+ const name2 = nameOf4(node3);
125677
126046
  const hasChip = node3.icon !== null;
125678
126047
  const textWidth3 = width - 2 * SANKEY_METRICS.padX - (hasChip ? SANKEY_METRICS.chipSize + SANKEY_METRICS.chipGap : 0);
125679
126048
  const fitted = fitText(name2.text, "label", textWidth3, textWidth3, SANKEY_METRICS.maxLabelLines);
@@ -127198,14 +127567,14 @@ function resolveEnd(token, tables, report3) {
127198
127567
  return { table: table2, column: columnName2 };
127199
127568
  }
127200
127569
  function resolveRelations(state) {
127201
- const resolved = [];
127570
+ const resolved2 = [];
127202
127571
  for (const pending of state.relations) {
127203
127572
  const from = resolveEnd(pending.from, state.tables, state.report);
127204
127573
  const to = resolveEnd(pending.to, state.tables, state.report);
127205
127574
  if (from === null || to === null) continue;
127206
127575
  const fromColumn = from.table.columns.find((column) => column.name === from.column);
127207
- resolved.push({
127208
- id: toEdgeId(resolved.length),
127576
+ resolved2.push({
127577
+ id: toEdgeId(resolved2.length),
127209
127578
  fromTable: toTableId(from.table.id),
127210
127579
  fromColumn: from.column,
127211
127580
  toTable: toTableId(to.table.id),
@@ -127214,7 +127583,7 @@ function resolveRelations(state) {
127214
127583
  cardinality: fromColumn?.isPrimaryKey === true || fromColumn?.isUnique === true ? "one-to-one" : "many-to-one"
127215
127584
  });
127216
127585
  }
127217
- return resolved;
127586
+ return resolved2;
127218
127587
  }
127219
127588
  function parseSchema(lines, report3) {
127220
127589
  const state = { title: null, description: null, tables: [], relations: [], openTable: null, declared: /* @__PURE__ */ new Map(), report: report3 };
@@ -130819,7 +131188,7 @@ function planState(ir, ctx) {
130819
131188
  origin: node3.span.line
130820
131189
  }));
130821
131190
  const kindOf3 = new Map(real.map((node3) => [node3.key, node3.kind]));
130822
- const nameOf10 = (key) => {
131191
+ const nameOf11 = (key) => {
130823
131192
  const marker = ir.nodes.find((node3) => node3.key === key);
130824
131193
  return marker !== void 0 && (marker.kind === "start" || marker.kind === "end") ? `the ${marker.kind} marker` : `"${key}"`;
130825
131194
  };
@@ -130842,7 +131211,7 @@ function planState(ir, ctx) {
130842
131211
  }
130843
131212
  const isNested = transition.from !== transition.to && (lineageOf2(transition.to, parentKeys).includes(transition.from) || lineageOf2(transition.from, parentKeys).includes(transition.to));
130844
131213
  if (isNested) {
130845
- reporter.add(DIAGNOSTIC_CODES.mermaidDroppedMeaning, span, `Dropped a transition between ${nameOf10(transition.from)} and ${nameOf10(transition.to)}, because one is inside the other. A transition cannot join a state to one inside it.`, "Start the transition from a state inside the composite state, or from its own start marker.", "nested-transition");
131214
+ reporter.add(DIAGNOSTIC_CODES.mermaidDroppedMeaning, span, `Dropped a transition between ${nameOf11(transition.from)} and ${nameOf11(transition.to)}, because one is inside the other. A transition cannot join a state to one inside it.`, "Start the transition from a state inside the composite state, or from its own start marker.", "nested-transition");
130846
131215
  continue;
130847
131216
  }
130848
131217
  const { composite, region } = transition.scope;
@@ -130907,7 +131276,7 @@ function summarizeState(diagram) {
130907
131276
  const description = diagram.meta.description === null ? "" : ` ${diagram.meta.description}`;
130908
131277
  return `${title}: ${counts}.${description}`;
130909
131278
  }
130910
- function nameOf4(diagram, node3) {
131279
+ function nameOf5(diagram, node3) {
130911
131280
  const holder = node3.parent === null ? void 0 : diagram.states.find((candidate2) => candidate2.id === node3.parent);
130912
131281
  const scope = holder === void 0 ? "" : ` of ${holder.label}${node3.region === null ? "" : regionSuffix(diagram, holder.id, node3.region)}`;
130913
131282
  if (node3.kind === "start" || node3.kind === "end") return `${KIND_NAMES[node3.kind]}${scope}`;
@@ -130922,7 +131291,7 @@ function regionSuffix(diagram, parent, index2) {
130922
131291
  }
130923
131292
  function nodeLine2(diagram, node3) {
130924
131293
  const detail = node3.description.length === 0 ? "" : ` (${node3.description.join("; ")})`;
130925
- const name2 = nameOf4(diagram, node3);
131294
+ const name2 = nameOf5(diagram, node3);
130926
131295
  if (node3.kind === "state") return `${name2}${detail}, state`;
130927
131296
  if (node3.kind === "composite") return `${name2}${detail}, composite state`;
130928
131297
  if (node3.kind === "choice") return `${name2}, choice`;
@@ -130933,7 +131302,7 @@ function nodeLine2(diagram, node3) {
130933
131302
  function transitionLine(diagram, transition) {
130934
131303
  const name2 = (id) => {
130935
131304
  const node3 = diagram.states.find((candidate2) => candidate2.id === id);
130936
- return node3 === void 0 ? id : nameOf4(diagram, node3);
131305
+ return node3 === void 0 ? id : nameOf5(diagram, node3);
130937
131306
  };
130938
131307
  const on = transition.event === null ? "" : ` on ${transition.event}`;
130939
131308
  const guard = transition.guard === null ? "" : `, if ${transition.guard}`;
@@ -130965,7 +131334,7 @@ function outlineState(diagram) {
130965
131334
  lines.push("Notes:");
130966
131335
  for (const note of diagram.notes) {
130967
131336
  const target = diagram.states.find((node3) => node3.id === note.target);
130968
- lines.push(`- On ${target === void 0 ? note.target : nameOf4(diagram, target)}: ${note.text}`);
131337
+ lines.push(`- On ${target === void 0 ? note.target : nameOf5(diagram, target)}: ${note.text}`);
130969
131338
  }
130970
131339
  }
130971
131340
  return lines;
@@ -131287,7 +131656,7 @@ function lineage(id, byId) {
131287
131656
  }
131288
131657
  function resolveTransitions(state, byId) {
131289
131658
  const names8 = state.nodes.filter((node3) => node3.kind === "state" || node3.kind === "composite" || node3.kind === "choice" || node3.kind === "fork" || node3.kind === "join" || node3.kind === "history").map((node3) => node3.id);
131290
- const resolved = [];
131659
+ const resolved2 = [];
131291
131660
  let suggested = 0;
131292
131661
  for (const pending of state.transitions) {
131293
131662
  const missing = [
@@ -131310,9 +131679,9 @@ function resolveTransitions(state, byId) {
131310
131679
  continue;
131311
131680
  }
131312
131681
  const parts = pending.label === null ? { event: null, guard: null, action: null } : splitLabel(pending.label);
131313
- resolved.push({ id: `t${resolved.length}`, from: pending.from, to: pending.to, label: pending.label, ...parts, at: pending.at });
131682
+ resolved2.push({ id: `t${resolved2.length}`, from: pending.from, to: pending.to, label: pending.label, ...parts, at: pending.at });
131314
131683
  }
131315
- return resolved;
131684
+ return resolved2;
131316
131685
  }
131317
131686
  function resolveNotes2(state, byId) {
131318
131687
  const names8 = state.nodes.filter((node3) => node3.kind === "state" || node3.kind === "composite" || node3.kind === "choice").map((node3) => node3.id);
@@ -132593,7 +132962,7 @@ function parseFlow2(cursor, p, line3) {
132593
132962
  }
132594
132963
  function resolveFlows(p) {
132595
132964
  const names8 = p.items.map((item) => item.id);
132596
- const resolved = [];
132965
+ const resolved2 = [];
132597
132966
  let suggested = 0;
132598
132967
  for (const pending of p.pending) {
132599
132968
  const missing = [pending.from, pending.to].filter((end) => !names8.includes(end.text));
@@ -132608,9 +132977,9 @@ function resolveFlows(p) {
132608
132977
  continue;
132609
132978
  }
132610
132979
  const parts = pending.label === null ? { event: null, guard: null, action: null } : splitLabel(pending.label);
132611
- resolved.push({ id: `f${resolved.length}`, from: pending.from.text, to: pending.to.text, label: pending.label, ...parts, line: pending.line, ...pending.step === void 0 ? {} : { step: pending.step }, at: pending.at });
132980
+ resolved2.push({ id: `f${resolved2.length}`, from: pending.from.text, to: pending.to.text, label: pending.label, ...parts, line: pending.line, ...pending.step === void 0 ? {} : { step: pending.step }, at: pending.at });
132612
132981
  }
132613
- return resolved;
132982
+ return resolved2;
132614
132983
  }
132615
132984
  function dispatch11(line3, p, scope) {
132616
132985
  const cursor = new TokenCursor(line3.tokens);
@@ -138992,7 +139361,7 @@ api --> email : "Sends email using" tech="SMTP"
138992
139361
  email --> customer : "Sends emails to"
138993
139362
  ` },
138994
139363
  { kind: "class", title: "Notification service", source: 'class "Notification service" desc="A notifier queues messages and delivers each one through a channel. Channels are interchangeable, so a new one needs no change to the notifier."\n\nclass Notifier stereotype="service" {\n "-queue: Queue<Message>"\n "+send(m: Message): void"\n "+retry(): int"\n}\n\nclass Message {\n "+id: uuid"\n "+body: String"\n "+priority: Priority"\n}\n\nclass Priority type=enum {\n "LOW"\n "NORMAL"\n "URGENT"\n}\n\nclass Template "Template<T>" type=abstract {\n "+render(data: T): String" abstract\n "+named(n: String): Template" static\n}\n\npackage channels "Channels" {\n class Channel type=interface {\n "+deliver(m: Message): Ack"\n }\n class Email "EmailChannel" {\n "-host: String"\n "+deliver(m: Message): Ack"\n }\n class Sms "SmsChannel" {\n "-gateway: String"\n "+deliver(m: Message): Ack"\n }\n}\n\nNotifier "1" owns "*" Message : "queues"\nNotifier "1" has "1..*" Channel : "delivers through"\nNotifier --> Template : "renders with"\nMessage --> Priority\nEmail implements Channel\nSms implements Channel\n\nnote on Message : "Kept until every channel has confirmed delivery."\n' },
138995
- { kind: "flow", title: "Login request", source: 'flow "Login request" desc="A user signs in through an API that checks credentials in a database."\n\nnode user person "User"\nnode api service "Auth API" sub="Validates credentials"\n\ngroup data "Data" icon=lucide:database {\n node db database "PostgreSQL" sub="Password hashes"\n node cache cache "Redis" sub="Sessions"\n}\n\nuser -> api : "POST /login"\napi -> db : "look up user"\ndb --> api : "row"\napi -> cache : "store session"\n' },
139364
+ { kind: "flow", title: "Login request", source: 'flow "Login request" desc="A user signs in through an API that checks credentials in a database and keeps the session in a cache."\n\nnode user person "User"\nnode api service "Auth API" sub="Validates credentials"\n\ngroup data "Data" {\n node db database "PostgreSQL" sub="Password hashes"\n node cache cache "Redis" sub="Sessions"\n}\n\nuser -> api : "signs in" role=identity\napi -> db : "looks up user" role=data\ndb --> api : "row"\napi -> cache : "stores session" role=data\n' },
138996
139365
  { kind: "gantt", title: "Product launch plan", source: 'gantt "Product launch plan" start=2026-01-05 unit=week today=2026-02-11 weekends=skip holidays=2026-04-03 desc="A fourteen week plan from research to launch, with design and build running side by side."\n\nsection "Discover" {\n task research "User research" start=2026-01-05 duration=2w status=done\n task brief "Product brief" after=research duration=1w status=done\n}\n\nsection "Design" tone=violet {\n task ux "UX flows" after=brief duration=2w status=done\n task ui "Visual design" after=ux duration=3w status=active\n task ds "Design system" after=brief duration=4w status=active\n}\n\nsection "Build" tone=aqua {\n task api "API and data model" after=brief duration=5w status=active\n task web "Web app" after=ui,api duration=4w status=critical\n task mobile "Mobile app" after=ui,api duration=4w\n milestone beta "Beta ready" after=web\n}\n\nsection "Launch" tone=orange {\n task docs "Help center" after=ui duration=3w\n task qa "Testing and fixes" after=web,mobile duration=2w status=critical\n milestone ga "Public launch" after=qa status=critical\n}\n' },
138997
139366
  { kind: "git", title: "Release and hotfix", source: 'git "Release and hotfix" desc="A feature is merged into develop, develop is released as v1.0, and a hotfix is tagged and picked back."\n\ncommit "Initial commit"\nbranch develop order=2\ncommit "Set up CI"\nbranch "feature/search" from=develop\ncommit "Add search box"\ncommit "Index the docs"\ncheckout develop\nmerge "feature/search" "Add search"\ncheckout main\nmerge develop "Release 1.0" tag="v1.0" type=highlight\nbranch hotfix order=1\ncommit "Fix null check" id=fix\ncheckout main\nmerge hotfix tag="v1.0.1"\ncheckout develop\ncherry-pick fix\ncommit "Revert flaky test" type=reverse\n' },
138998
139367
  { kind: "journey", title: "Checkout experience", source: 'journey "Checkout experience" desc="How a shopper feels from finding a product to holding the parcel, and who is involved at each step."\n\nactor "Customer" tone=blue\nactor "Support agent" tone=violet\nactor "Bank"\nactor "Courier"\n\nsection "Browse" {\n task "Find a product" score=5 actors="Customer"\n task "Compare prices" score=3 actors="Customer, Support agent" sub="Asks about delivery"\n}\n\nsection "Pay" {\n task "Enter card details" score=2 actors="Customer"\n task "Confirm with the bank" score=1 actors="Customer, Bank"\n task "Get a receipt" score=4 actors="Customer"\n}\n\nsection "Deliver" {\n task "Track the parcel" score=4 actors="Customer, Courier"\n task "Receive it" score=5 actors="Customer, Courier"\n}\n' },
@@ -139050,7 +139419,7 @@ ship -> delivered : "arrived"
139050
139419
  { kind: "treemap", title: "Monthly cloud spend", source: 'treemap "Monthly cloud spend" unit="$" better=down desc="Where the monthly cloud bill goes, by service, with the change since last month. A rise in cost is drawn red."\n\ngroup "Compute" icon=lucide:cpu {\n item "EC2" 18400 icon=aws:ec2 delta="8%"\n item "Fargate" 6800 icon=aws:fargate delta="21%"\n item "Lambda" 4200 icon=aws:lambda delta="-12%"\n item "EKS" 3100 icon=aws:eks\n}\n\ngroup "Data" icon=lucide:database {\n item "RDS" 9600 icon=aws:rds delta="4%"\n item "S3" 5200 icon=aws:s3 delta="-3%"\n item "DynamoDB" 3900 icon=aws:dynamodb\n item "OpenSearch" 3400 icon=aws:opensearch\n item "ElastiCache" 2800\n}\n\ngroup "Network" icon=lucide:globe {\n item "Data transfer" 4800 delta="15%"\n item "NAT gateway" 3300 icon=aws:nat-gateway\n item "CloudFront" 2200 icon=aws:cloudfront delta="-6%"\n item "Load balancers" 1900 icon=aws:elb\n}\n\ngroup "Operations" icon=lucide:activity {\n item "CloudWatch" 4600 icon=aws:cloudwatch\n item "Support plan" 3200\n group "Security" icon=lucide:shield {\n item "GuardDuty" 2400 icon=aws:guardduty\n item "WAF" 1900 icon=aws:waf\n }\n}\n' },
139051
139420
  { kind: "xychart", title: "API latency against the SLO", source: 'xychart "API latency against the SLO" desc="Weekly p50 and p95 latency of the checkout API against its 400 ms objective: p95 crossed the line after release 2.3 and came back under it after the cache fix."\n\nx "Week" ["W1" "W2" "W3" "W4" "W5" "W6" "W7" "W8"]\ny "Latency" unit="ms" min=0 max=500\n\nbar "p95" [310 330 345 420 455 440 350 335]\nline "p50" [120 125 128 160 170 165 135 130]\n\nthreshold "SLO 400 ms" 400 tone=red\n\nnote "Release 2.3" at="W4"\nnote "Cache fix" at="W7"\n' }
139052
139421
  ];
139053
- ARCHITECTURE_EXAMPLE = { kind: "flow", title: "Web application on a cloud", source: 'flow "Web application on a cloud" provider=azure direction=down desc="Visitors load a single-page app from storage through a content delivery network and sign in through an identity service. The app calls an API on an application plan, which stores uploads, reads a database, and hands slow work to a queue that a background worker drains. A pipeline builds and deploys the API, and a monitoring service collects telemetry."\n\nnode visitors client "Visitors" sub="Browsers"\n\ngroup edge "Edge" type=edge {\n node cdn gateway "Content delivery" icon=azure:cdn\n}\n\ngroup sign-in "Sign-in" type=identity-boundary {\n node idp identity "Identity service" icon=azure:entra-id\n}\n\ngroup files "Storage" type=storage-account {\n node site storage "Web app files" icon=azure:blob-storage stack=simple:react,simple:typescript\n node uploads storage "Uploads" icon=azure:blob-storage\n}\n\ngroup plan "Application plan" type=service-plan {\n node api service "Orders API" icon=azure:app-service stack=simple:nodedotjs\n}\n\ngroup messaging "Messaging" type=messaging {\n node jobs queue "Job queue" icon=azure:service-bus\n}\n\nnode worker function "Background worker" icon=azure:functions stack=simple:nodedotjs\nnode db database "Database" icon=azure:sql-database\n\ngroup delivery "Delivery pipeline" type=ci-cd {\n node repo step "Source code" icon=lucide:git-branch\n node build step "Build pipeline" icon=azure:devops\n}\n\ngroup monitoring "Monitoring" type=observability {\n node telemetry service "Metrics and logs" icon=azure:monitor\n}\n\nvisitors -> cdn : "HTTPS"\nvisitors -> idp : "sign in"\ncdn -> site\ncdn -> api : "API calls"\napi -> uploads : "store files"\napi -> jobs : "enqueue"\njobs ..> worker : "event"\nworker -> db : "writes"\napi -> db : "reads"\nrepo -> build\nbuild ..> api : "deploys image"\napi ..> telemetry : "telemetry"\n' };
139422
+ ARCHITECTURE_EXAMPLE = { kind: "flow", title: "Web application on a cloud", source: 'flow "Web application on a cloud" provider=azure direction=down desc="Visitors load a single-page app from storage through a content delivery network and sign in through an identity service. The app calls an API on an application plan, which stores uploads, reads a database, and hands slow work to a queue that a background worker drains. A pipeline builds and deploys the API, and a monitoring service collects telemetry."\n\nnode visitors client "Visitors" sub="Browsers"\n\ngroup edge "Edge" type=edge {\n node cdn gateway "Content delivery" icon=azure:cdn\n}\n\ngroup sign-in "Sign-in" type=identity-boundary {\n node idp identity "Identity service" icon=azure:entra-id\n}\n\ngroup files "Storage" type=storage-account {\n node site storage "Web app files" icon=azure:blob-storage stack=simple:react,simple:typescript\n node uploads storage "Uploads" icon=azure:blob-storage\n}\n\ngroup plan "Application plan" type=service-plan {\n node api service "Orders API" icon=azure:app-service stack=simple:nodedotjs\n}\n\ngroup messaging "Messaging" type=messaging {\n node jobs queue "Job queue" icon=azure:service-bus\n}\n\nnode worker function "Background worker" icon=azure:functions stack=simple:nodedotjs\nnode db database "Database" icon=azure:sql-database\n\ngroup delivery "Delivery pipeline" type=ci-cd {\n node repo step "Source code" icon=lucide:git-branch\n node build step "Build pipeline" icon=azure:devops\n}\n\ngroup monitoring "Monitoring" type=observability {\n node telemetry service "Metrics and logs" icon=azure:monitor\n}\n\nvisitors -> cdn : "HTTPS"\nvisitors -> idp : "sign in" role=identity\ncdn -> site\ncdn -> api : "API calls"\napi -> uploads : "store files" role=data\napi -> jobs : "enqueue" role=async\njobs ..> worker : "event"\nworker -> db : "writes" role=data\napi -> db : "reads" role=data\nrepo -> build\nbuild ..> api : "deploys image"\napi ..> telemetry : "telemetry"\n' };
139054
139423
  }
139055
139424
  });
139056
139425
 
@@ -139102,6 +139471,8 @@ function buildVocabulary() {
139102
139471
  },
139103
139472
  lineOptions: LINE_OPTION_SPECS.map((spec) => ({ ...spec })),
139104
139473
  labelIcons: Object.entries(LABEL_ICON_DEFAULTS).map(([label, icon]) => ({ label, icon })),
139474
+ verbIcons: Object.entries(LABEL_VERB_PHRASES).map(([verb, words2]) => ({ icon: EDGE_STYLES[verb].icon, words: [...words2 ?? []] })),
139475
+ style: [...STYLE_STEPS],
139105
139476
  autoIcons: { ...ICON_MODE_SPEC },
139106
139477
  roleIcons: roleIconTable(),
139107
139478
  statusIcons: Object.entries(STATUS_ICON_SPECS).map(([name2, spec]) => ({ ref: `status:${name2}`, tone: spec.tone, meaning: spec.meaning })),
@@ -139210,6 +139581,8 @@ var init_build_vocabulary = __esm({
139210
139581
  arrows: ["overview", "flow", "sequence"],
139211
139582
  lineOptions: ["overview", "flow", "sequence"],
139212
139583
  labelIcons: ["overview", "flow"],
139584
+ verbIcons: ["flow"],
139585
+ style: ["overview", "flow"],
139213
139586
  autoIcons: AUTO_ICON_KINDS,
139214
139587
  roleIcons: ["flow", "c4", "layers", "mindmap", "swimlane"],
139215
139588
  blockTypes: ["overview", "sequence"],
@@ -139830,7 +140203,7 @@ function descendantsOf(scan, index2) {
139830
140203
  });
139831
140204
  return inside;
139832
140205
  }
139833
- function nameOf5(element) {
140206
+ function nameOf6(element) {
139834
140207
  if (element.role === "edge") return `${element.from ?? ""} ${element.arrow ?? ""} ${element.to ?? ""}`;
139835
140208
  return element.id ?? (element.label === null ? element.rest : `"${element.label}"`);
139836
140209
  }
@@ -139854,7 +140227,7 @@ function subjectOf(element) {
139854
140227
  function renamedName(element, rename2) {
139855
140228
  if (element.role === "edge") return `${rename2(element.from ?? "")} -> ${rename2(element.to ?? "")}`;
139856
140229
  if (element.role === "header") return "header";
139857
- return element.id === null ? nameOf5(element) : rename2(element.id);
140230
+ return element.id === null ? nameOf6(element) : rename2(element.id);
139858
140231
  }
139859
140232
  function entriesOf3(source, rename2) {
139860
140233
  const { elements } = scanSource(source);
@@ -143355,7 +143728,7 @@ var init_schemas = __esm({
143355
143728
  }
143356
143729
  return propValues;
143357
143730
  });
143358
- const isObject5 = isObject;
143731
+ const isObject6 = isObject;
143359
143732
  const catchall = def.catchall;
143360
143733
  let value;
143361
143734
  const memo3 = globalConfig.memoizer;
@@ -143363,7 +143736,7 @@ var init_schemas = __esm({
143363
143736
  inst._zod.parse = (payload, ctx) => {
143364
143737
  value ?? (value = _normalized.value);
143365
143738
  const input2 = payload.value;
143366
- if (!isObject5(input2)) {
143739
+ if (!isObject6(input2)) {
143367
143740
  payload.issues.push({
143368
143741
  expected: "object",
143369
143742
  code: "invalid_type",
@@ -143499,7 +143872,7 @@ var init_schemas = __esm({
143499
143872
  return doc.compile();
143500
143873
  };
143501
143874
  let fastpass;
143502
- const isObject5 = isObject;
143875
+ const isObject6 = isObject;
143503
143876
  const jit = !globalConfig.jitless;
143504
143877
  const allowsEval2 = allowsEval;
143505
143878
  const fastEnabled = jit && allowsEval2.value;
@@ -143508,7 +143881,7 @@ var init_schemas = __esm({
143508
143881
  inst._zod.parse = (payload, ctx) => {
143509
143882
  value ?? (value = _normalized.value);
143510
143883
  const input2 = payload.value;
143511
- if (!isObject5(input2)) {
143884
+ if (!isObject6(input2)) {
143512
143885
  payload.issues.push({
143513
143886
  expected: "object",
143514
143887
  code: "invalid_type",
@@ -159272,8 +159645,8 @@ function convertBaseSchema(schema, ctx) {
159272
159645
  });
159273
159646
  }
159274
159647
  ctx.processing.add(refPath);
159275
- const resolved = resolveRef(refPath, ctx);
159276
- const zodSchema2 = convertSchema(resolved, ctx);
159648
+ const resolved2 = resolveRef(refPath, ctx);
159649
+ const zodSchema2 = convertSchema(resolved2, ctx);
159277
159650
  ctx.refs.set(refPath, zodSchema2);
159278
159651
  ctx.processing.delete(refPath);
159279
159652
  return zodSchema2;
@@ -161646,9 +162019,9 @@ async function runSync(document2, settings, ops, cache, limit) {
161646
162019
  const render = async (written3, id) => {
161647
162020
  const source = applyDefaults(written3, settings.defaults).source;
161648
162021
  if (calls.length >= limit) {
161649
- const capped = calls.length >= settings.countLimit ? "count" : "bytes";
161650
- calls.push({ id, source, detail: null, capped });
161651
- return { diagnostics: capped === "count" ? CAPPED_BY_COUNT : CAPPED_BY_BYTES };
162022
+ const capped2 = calls.length >= settings.countLimit ? "count" : "bytes";
162023
+ calls.push({ id, source, detail: null, capped: capped2 });
162024
+ return { diagnostics: capped2 === "count" ? CAPPED_BY_COUNT : CAPPED_BY_BYTES };
161652
162025
  }
161653
162026
  const key = `${id}\0${source}`;
161654
162027
  let detail = cache.get(key);
@@ -171949,9 +172322,9 @@ var init_ajvProvider_CEoC_sr = __esm({
171949
172322
  }
171950
172323
  function resolve5(baseURI, relativeURI, options3) {
171951
172324
  const schemelessOptions = options3 ? Object.assign({ scheme: "null" }, options3) : { scheme: "null" };
171952
- const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
172325
+ const resolved2 = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
171953
172326
  schemelessOptions.skipEscape = true;
171954
- return serialize(resolved, schemelessOptions);
172327
+ return serialize(resolved2, schemelessOptions);
171955
172328
  }
171956
172329
  function resolveComponent(base, relative, options3, skipNormalization) {
171957
172330
  const target = {};
@@ -178123,7 +178496,7 @@ var init_sources = __esm({
178123
178496
  tags: external_exports.array(external_exports.string().min(1).max(200)).max(200).optional().describe("openapi: the tags to read."),
178124
178497
  groupBy: external_exports.enum(IMPORT_GROUP_BY).optional().describe("terraform, bicep, arm, pulumi, cloudformation, cdk: module (the default) frames each module's resources and nests them by what they name; network nests by what they name alone; file (terraform) frames them by their .tf file; owner (backstage) frames a catalog by the group that owns each entity."),
178125
178498
  varFiles: external_exports.array(repositoryPath).max(20).optional().describe("terraform: the variables files to apply, as -var-file would; bicep: the .bicepparam file; arm: the parameters file; pulumi: the Pulumi.<stack>.yaml file."),
178126
- root: repositoryPath.optional().describe("terraform, bicep, pulumi, cloudformation, cdk: the root module, deployment, program, template, or stack to draw when the files hold several, such as envs/prod; each is a part of its own when left out.")
178499
+ root: repositoryPath.optional().describe("terraform, bicep, pulumi, cloudformation, cdk: the root module, deployment, program, template, or stack to draw when the files hold several, such as envs/prod; each is a part of its own when left out. structurizr: the system or container the view is about.")
178127
178500
  }).meta({ id: "SourceOptions", description: "The reader options of one source, as import_source takes them." });
178128
178501
  SourcePublishSchema = external_exports.strictObject({
178129
178502
  alias: external_exports.string().min(1).max(60).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "An alias is lower-case words joined by single hyphens, such as checkout-flow").optional().describe("A stable link of a personal key: one address that always shows the latest version."),
@@ -178139,7 +178512,7 @@ var init_sources = __esm({
178139
178512
  from: external_exports.union([pattern2, external_exports.array(pattern2).min(1).max(GENERATE.maxPatterns)]).describe("The files to read: one pattern or a list."),
178140
178513
  format: external_exports.enum(IMPORT_FORMATS).describe("The format of the files, as import_source reads them."),
178141
178514
  out: outPath.describe("The diagram to write: a .diagram file, or a Markdown file and the id of a ```diagram id=<id> fence in it."),
178142
- view: external_exports.enum(IMPORT_VIEWS).optional().describe("openapi: endpoints, map, or sequence; git-log: git or timeline."),
178515
+ view: external_exports.enum(IMPORT_VIEWS).optional().describe("openapi: endpoints, map, or sequence; git-log: git or timeline; structurizr: context, container, or component."),
178143
178516
  title: external_exports.string().min(1).max(120).optional().describe("The title of the diagram; the reader takes one from the files when left out."),
178144
178517
  options: SourceOptionsSchema.optional(),
178145
178518
  overrides: repositoryPath.refine((path) => path.endsWith(".json"), { message: "An overrides file is a .json file" }).optional().describe("The overrides file, applied after every import. Left out: the out path with .overrides.json in place of .diagram, or <file>.<fence id>.overrides.json beside a Markdown file."),
@@ -178582,6 +178955,8 @@ var init_vocabulary18 = __esm({
178582
178955
  group: GroupSchema.describe("How a flow group looks and what it accepts; its corner tab shows its icon and name."),
178583
178956
  lineOptions: external_exports.array(LineOptionSchema).describe("Options that animate a line (animate=on makes dashes travel toward the arrowhead) or number the steps of a flow or sequence (numbered=on, start=, and step= on a line). All are off by default."),
178584
178957
  labelIcons: external_exports.array(LabelIconSchema).describe("Edge labels that get a tick or cross automatically; icon=none on the edge removes it."),
178958
+ verbIcons: external_exports.array(external_exports.object({ icon: external_exports.string().describe("The icon drawn in the label."), words: external_exports.array(external_exports.string()).describe('Phrases that draw it: the whole label, or its first words followed by what is acted on ("reads orders").') })).describe("The icons a flow edge label draws when it names a relationship with a verb. icon= on the edge wins, icon=none removes it, and icons=off on the header turns them all off."),
178959
+ style: external_exports.array(external_exports.string()).describe("How to make a flow diagram look finished: verbs on arrows, role= on lines that mean something, typed groups, product names."),
178585
178960
  autoIcons: AutoIconsSchema.describe("Items with no icon= get the icon their label names for certain (a brand, a cloud product, a common role). icon=none keeps one item plain; icons=off on the header keeps them all plain."),
178586
178961
  roleIcons: external_exports.array(external_exports.object({ icon: external_exports.string().describe("The icon drawn."), words: external_exports.array(external_exports.string()).describe("Whole words of a label of three words or fewer that draw it.") })).describe("The plain icons for common roles, drawn on flow and sequence service and external items, C4 elements, layers blocks, mind map topics, and swimlane lanes. The last role word of the label wins."),
178587
178962
  statusIcons: external_exports.array(StatusIconSchema).describe("Colored status and outcome icons: a green check, a red X, and so on."),
@@ -179201,7 +179576,7 @@ function finding(level2, code, message2, at3, hint2) {
179201
179576
  function appliesTo(rule, kind) {
179202
179577
  return rule.kinds === void 0 || rule.kinds.includes(kind);
179203
179578
  }
179204
- function nameOf6(subject) {
179579
+ function nameOf7(subject) {
179205
179580
  return subject.id === null || subject.id === subject.label ? `"${subject.label}"` : `${subject.id} ("${subject.label}")`;
179206
179581
  }
179207
179582
  function iconAccepted(icon, choices) {
@@ -179228,7 +179603,7 @@ function iconFindings(rule, subjects, level2, places) {
179228
179603
  const drawn = subject.icon === null ? "draws no icon" : `draws ${subject.icon}`;
179229
179604
  const [first] = rule.use;
179230
179605
  const choices = rule.use.length === 1 ? `icon=${first ?? ""}` : `icon= with one of ${rule.use.join(", ")}`;
179231
- return [finding(rule.level ?? level2, DIAGNOSTIC_CODES.ruleIcon, `${nameOf6(subject)} matches /${rule.label}/ and ${drawn}`, places.of(subject), rule.hint ?? `Give it ${choices}.`)];
179606
+ return [finding(rule.level ?? level2, DIAGNOSTIC_CODES.ruleIcon, `${nameOf7(subject)} matches /${rule.label}/ and ${drawn}`, places.of(subject), rule.hint ?? `Give it ${choices}.`)];
179232
179607
  });
179233
179608
  }
179234
179609
  function toneFindings(rule, subjects, level2, places) {
@@ -179238,7 +179613,7 @@ function toneFindings(rule, subjects, level2, places) {
179238
179613
  if (!broken) return [];
179239
179614
  const of = rule.types === void 0 ? "a node" : `a ${subject.type}`;
179240
179615
  const fix = rule.allow !== void 0 ? `Use tone=${rule.allow.join(" or tone=")}.` : `Use a tone other than ${(rule.forbid ?? []).join(", ")}.`;
179241
- return [finding(rule.level ?? level2, DIAGNOSTIC_CODES.ruleTone, `${nameOf6(subject)} is ${subject.tone}, a tone the team does not use for ${of}`, places.of(subject), rule.hint ?? fix)];
179616
+ return [finding(rule.level ?? level2, DIAGNOSTIC_CODES.ruleTone, `${nameOf7(subject)} is ${subject.tone}, a tone the team does not use for ${of}`, places.of(subject), rule.hint ?? fix)];
179242
179617
  });
179243
179618
  }
179244
179619
  function diagramFindings(diagram, rules, count3, level2, places) {
@@ -179531,9 +179906,9 @@ users -> ingress : "HTTPS"
179531
179906
  ingress -> web
179532
179907
  ingress -> api
179533
179908
  ingress -> admin
179534
- api -> config : "reads"
179909
+ api -> config : "reads" icon=lucide:eye
179535
179910
  web ..> monitor : "metrics"
179536
- api -> secrets : "reads"`;
179911
+ api -> secrets : "reads secrets" role=identity`;
179537
179912
  NEUTRAL2 = `flow "{{title}}" direction=down desc="{{description}}"
179538
179913
 
179539
179914
  group delivery "{{pipeline}}" type=ci-cd {
@@ -179839,8 +180214,8 @@ bus ..> processor : "all events"
179839
180214
  bus ..> notifier : "alerts"
179840
180215
  bus ..> dlq : "after retries"
179841
180216
  archiver -> archive : "archive"
179842
- processor -> db : "write"
179843
- notifier ..> mail : "send"`;
180217
+ processor -> db : "write" role=data
180218
+ notifier ..> mail : "send" role=async`;
179844
180219
  NEUTRAL5 = `flow "{{title}}" direction=down desc="{{description}}"
179845
180220
 
179846
180221
  group producers "{{producers}}" type=app-tier {
@@ -180068,7 +180443,7 @@ var init_microservices_gateway = __esm({
180068
180443
  { id: "billing-database", default: "Billing database", meaning: "The database that only the billing service reads and writes." }
180069
180444
  ];
180070
180445
  EDGES6 = `clients -> gateway : "HTTPS"
180071
- gateway -> idp : "verify token"
180446
+ gateway -> idp : "verify token" role=identity
180072
180447
  gateway -> catalog : "{{catalog-path}}"
180073
180448
  gateway -> booking : "{{booking-path}}"
180074
180449
  catalog -> catalog-db
@@ -180218,12 +180593,12 @@ var init_queue_worker = __esm({
180218
180593
  { id: "alarm", default: "Queue depth alarm", meaning: "The alarm that fires when too many jobs wait in the queue." }
180219
180594
  ];
180220
180595
  EDGES7 = `client -> api : "{{submit}}"
180221
- api ..> queue : "enqueue job"
180596
+ api ..> queue : "enqueue job" role=async
180222
180597
  queue ..> w1
180223
180598
  queue ..> w2
180224
180599
  queue ..> w3
180225
180600
  queue ..> alarm
180226
- w2 -> db : "write result"
180601
+ w2 -> db : "write result" role=data
180227
180602
  w3 ..> dlq : "after {{retries}} tries"`;
180228
180603
  OVERLAY = 'overlay pool "{{pool}}" type=scale-group members=w1,w2,w3';
180229
180604
  NEUTRAL9 = `flow "{{title}}" direction=down desc="{{description}}"
@@ -180320,9 +180695,9 @@ var init_serverless_api = __esm({
180320
180695
  gateway -> create : "POST {{path}}"
180321
180696
  gateway -> list : "GET {{path}}"
180322
180697
  gateway -> get : "GET {{path}}/id"
180323
- gateway -> auth : "verify token"
180324
- create -> bucket : "upload"
180325
- create -> db : "write"
180698
+ gateway -> auth : "verify token" role=identity
180699
+ create -> bucket : "upload" role=data
180700
+ create -> db : "write" role=data
180326
180701
  list -> db
180327
180702
  get -> db
180328
180703
  get ..> monitor : "logs and metrics"`;
@@ -180516,7 +180891,7 @@ dns -> cdn
180516
180891
  cert -> cdn
180517
180892
  publish ..> cdn
180518
180893
  cdn -> bucket : "fetch files"
180519
- publish -> bucket : "upload"`;
180894
+ publish -> bucket : "upload" role=data`;
180520
180895
  NEUTRAL12 = `flow "{{title}}" direction=down desc="{{description}}"
180521
180896
 
180522
180897
  group pipeline "{{pipeline}}" type=ci-cd {
@@ -180900,14 +181275,14 @@ var init_web_app_on_cloud = __esm({
180900
181275
  { id: "telemetry", default: "Metrics and logs", meaning: "The service that collects what the API reports." }
180901
181276
  ];
180902
181277
  EDGES11 = `visitors -> cdn : "HTTPS"
180903
- visitors -> idp : "sign in"
181278
+ visitors -> idp : "sign in" role=identity
180904
181279
  cdn -> site
180905
181280
  cdn -> api : "API calls"
180906
- api -> uploads : "store files"
180907
- api -> jobs : "enqueue"
181281
+ api -> uploads : "store files" role=data
181282
+ api -> jobs : "enqueue" role=async
180908
181283
  jobs ..> worker : "event"
180909
- worker -> db : "writes"
180910
- api -> db : "reads"
181284
+ worker -> db : "writes" role=data
181285
+ api -> db : "reads" role=data
180911
181286
  repo -> build
180912
181287
  build ..> api : "deploys image"
180913
181288
  api ..> telemetry : "telemetry"`;
@@ -181191,6 +181566,14 @@ var init_direction = __esm({
181191
181566
  }
181192
181567
  });
181193
181568
 
181569
+ // ../../packages/diagrams/src/importers/shared/edge-style.ts
181570
+ var init_edge_style = __esm({
181571
+ "../../packages/diagrams/src/importers/shared/edge-style.ts"() {
181572
+ "use strict";
181573
+ init_constants();
181574
+ }
181575
+ });
181576
+
181194
181577
  // ../../packages/diagrams/src/importers/shared/icons.ts
181195
181578
  function known2(ref) {
181196
181579
  return ref !== null && isKnownIcon(ref) ? ref : null;
@@ -181683,7 +182066,7 @@ function writeOneFlow(draft, report3) {
181683
182066
  const children = (parent) => groups.filter((group2) => parentOf4(group2) === parent);
181684
182067
  const writeGroup = (group2, depth) => {
181685
182068
  lines.gap();
181686
- lines.add(`group ${ids.idFor(`group:${group2.key}`)} ${quote(group2.label)}${options2({ type: group2.type ?? namedType(group2.label), icon: known2(group2.icon ?? null), sub: group2.sub ?? null, tone: group2.tone ?? null })} {`, depth);
182069
+ lines.add(`group ${ids.idFor(`group:${group2.key}`)} ${quote(group2.label)}${options2({ type: group2.type ?? namedType(group2.label) ?? group2.otherwise ?? null, icon: known2(group2.icon ?? null), sub: group2.sub ?? null, tone: group2.tone ?? null })} {`, depth);
181687
182070
  for (const node3 of kept2) if ((node3.group ?? null) === group2.key) nodeLine4(node3, depth + 1);
181688
182071
  for (const child of children(group2.key)) writeGroup(child, depth + 1);
181689
182072
  lines.add("}", depth);
@@ -181693,16 +182076,24 @@ function writeOneFlow(draft, report3) {
181693
182076
  for (const node3 of kept2) if (node3.group === void 0 || node3.group === null || !groupKeys.has(node3.group)) nodeLine4(node3, 0);
181694
182077
  for (const group2 of children(null)) writeGroup(group2, 0);
181695
182078
  const written3 = /* @__PURE__ */ new Set();
181696
- let links = 0;
181697
- lines.gap();
182079
+ const drawn = [];
181698
182080
  for (const edge of draft.edges) {
181699
182081
  const pair2 = `${edge.from}\0${edge.to}`;
181700
182082
  if (edge.from === edge.to || !keptKeys.has(edge.from) || !keptKeys.has(edge.to) || written3.has(pair2)) continue;
181701
182083
  written3.add(pair2);
181702
- links += 1;
181703
- const label = edge.label === void 0 || edge.label === null || edge.label === "" ? "" : ` : ${quote(edge.label)}`;
181704
- lines.add(`${ids.idFor(`node:${edge.from}`)} ${edge.arrow ?? "->"} ${ids.idFor(`node:${edge.to}`)}${label}`);
182084
+ drawn.push(edge);
182085
+ }
182086
+ const roleOf3 = (edge) => edge.verb === void 0 || edge.verb === null ? null : EDGE_STYLES[edge.verb].role;
182087
+ const roles = new Set(drawn.map(roleOf3));
182088
+ const colour = roles.size > 1;
182089
+ lines.gap();
182090
+ for (const edge of drawn) {
182091
+ const hasLabel = edge.label !== void 0 && edge.label !== null && edge.label !== "";
182092
+ const label = hasLabel ? ` : ${quote(edge.label ?? "")}` : "";
182093
+ const style = edge.verb === void 0 || edge.verb === null ? null : EDGE_STYLES[edge.verb];
182094
+ lines.add(`${ids.idFor(`node:${edge.from}`)} ${edge.arrow ?? "->"} ${ids.idFor(`node:${edge.to}`)}${label}${options2({ icon: hasLabel ? known2(style?.icon ?? null) : null, role: colour ? style?.role ?? null : null })}`);
181705
182095
  }
182096
+ const links = drawn.length;
181706
182097
  const coverage = draft.coverage ?? { noun: draft.noun, total: draft.nodes.length, drawn: kept2.length, summarised: 0 };
181707
182098
  const named = { ...ids.mapped("group:"), ...ids.mapped("node:") };
181708
182099
  return { kind: "flow", source: lines.toString(), stats: { items: kept2.length, links, groups: groups.length, dropped: left.length }, advice: draft.advice ?? null, coverage, ids: named };
@@ -181714,6 +182105,7 @@ var init_flow2 = __esm({
181714
182105
  init_core();
181715
182106
  init_lint21();
181716
182107
  init_suggest_icons();
182108
+ init_edge_style();
181717
182109
  init_icons3();
181718
182110
  init_reporter2();
181719
182111
  init_split_flow();
@@ -182085,6 +182477,7 @@ var init_shared2 = __esm({
182085
182477
  "../../packages/diagrams/src/importers/shared/index.ts"() {
182086
182478
  "use strict";
182087
182479
  init_direction();
182480
+ init_edge_style();
182088
182481
  init_flow2();
182089
182482
  init_split_flow();
182090
182483
  init_icons3();
@@ -182672,7 +183065,7 @@ function drawCharts(files, context, index2) {
182672
183065
  const edges = [];
182673
183066
  const repositories = new Set(charts.flatMap((chart) => chart.dependencies.map((dependency) => dependency.repository)).filter((repository) => repository !== null && repository !== "" && !repository.startsWith("file://")));
182674
183067
  const framed = repositories.size > 1;
182675
- const groups = framed ? [...repositories].sort().map((repository) => ({ key: `repository:${repository}`, label: repositoryLabel(repository), sub: "Chart repository", icon: "simple:helm", idHint: repositoryLabel(repository).split("/").pop() ?? "repository" })) : [];
183068
+ const groups = framed ? [...repositories].sort().map((repository) => ({ key: `repository:${repository}`, label: repositoryLabel(repository), sub: "Chart repository", type: "external-systems", icon: "simple:helm", idHint: repositoryLabel(repository).split("/").pop() ?? "repository" })) : [];
182676
183069
  for (const chart of charts) {
182677
183070
  const look = imageLook(chart.name);
182678
183071
  nodes.push({ key: chartKey(chart), type: look?.type ?? "service", label: chart.name, sub: cleanText([[chart.library ? "Library chart" : "Chart", chart.version].filter((part) => part !== null).join(" "), chart.appVersion === null ? null : `app ${chart.appVersion}`].filter((part) => part !== null).join(", ")), icon: look?.icon ?? "simple:helm", idHint: chart.name });
@@ -182690,7 +183083,7 @@ function drawCharts(files, context, index2) {
182690
183083
  nodes.push({ key, type: look?.type ?? "service", label: shown, sub: cleanText([shown === dependency.name ? "Chart" : `Chart ${dependency.name}`, off ? "off in values" : dependency.condition !== null && setting !== "true" ? "optional" : null].filter((part) => part !== null).join(", ")), icon: look?.icon ?? iconForName(dependency.name, look?.type ?? "service") ?? "simple:helm", group: repository, idHint: shown });
182691
183084
  }
182692
183085
  const version2 = chart.locked.get(dependency.name) ?? dependency.version;
182693
- edges.push({ from: chartKey(chart), to: key, label: version2, ...off ? { arrow: "-->" } : {} });
183086
+ edges.push({ from: chartKey(chart), to: key, label: version2, verb: "depends-on", ...off ? { arrow: "-->" } : {} });
182694
183087
  }
182695
183088
  }
182696
183089
  const root = charts[0];
@@ -182906,7 +183299,7 @@ function edgesOf(drawn, folded, context) {
182906
183299
  const from = keyOf(manifest.kind, manifest.namespace, manifest.name);
182907
183300
  for (const route of manifest.routes) {
182908
183301
  const service = keyOf("Service", manifest.namespace, route.service);
182909
- if (has("Service", manifest.namespace, route.service)) edges.push({ from, to: folded.get(service) ?? service, label: route.label });
183302
+ if (has("Service", manifest.namespace, route.service)) edges.push({ from, to: folded.get(service) ?? service, label: route.label, verb: "routes" });
182910
183303
  else unresolved(manifest, `Service ${route.service}`);
182911
183304
  }
182912
183305
  if (manifest.kind === "Service" && !folded.has(from)) {
@@ -182924,7 +183317,7 @@ function edgesOf(drawn, folded, context) {
182924
183317
  }
182925
183318
  }
182926
183319
  for (const use of manifest.uses) {
182927
- if (has(use.kind, manifest.namespace, use.name)) edges.push({ from, to: keyOf(use.kind, manifest.namespace, use.name), arrow: use.kind === "PersistentVolumeClaim" ? "->" : "-->", label: use.kind === "PersistentVolumeClaim" ? "mounts" : "reads" });
183320
+ if (has(use.kind, manifest.namespace, use.name)) edges.push({ from, to: keyOf(use.kind, manifest.namespace, use.name), arrow: use.kind === "PersistentVolumeClaim" ? "->" : "-->", label: use.kind === "PersistentVolumeClaim" ? "mounts" : "reads", verb: use.kind === "PersistentVolumeClaim" ? "mounts" : use.kind === "Secret" ? "secret" : "reads-settings" });
182928
183321
  else unresolved(manifest, `${use.kind} ${use.name}`);
182929
183322
  }
182930
183323
  }
@@ -182966,13 +183359,13 @@ function drawManifests(documents, context, framing) {
182966
183359
  const ordered = [...drawn].sort((a, b) => (LOOKS[a.kind]?.rank ?? 2) - (LOOKS[b.kind]?.rank ?? 2));
182967
183360
  const namespaces = [...new Set(ordered.map((manifest) => manifest.namespace))];
182968
183361
  const grouped = namespaces.length > 1;
182969
- const groups = grouped ? namespaces.map((namespace) => ({ key: `ns:${namespace}`, label: namespace, type: namespaceType(namespace), sub: "Namespace", idHint: `ns_${namespace}` })) : [];
183362
+ const groups = grouped ? namespaces.map((namespace) => ({ key: `ns:${namespace}`, label: namespace, type: namespaceType(namespace), otherwise: "resource-group", tone: namespaceType(namespace) === null ? "violet" : null, sub: "Namespace", idHint: `ns_${namespace}` })) : [];
182970
183363
  const framed = /* @__PURE__ */ new Map();
182971
183364
  for (const manifest of framing === null ? [] : ordered) {
182972
183365
  let parent = grouped ? `ns:${manifest.namespace}` : null;
182973
183366
  for (const frame of framing?.framesOf(manifest) ?? []) {
182974
183367
  const key = grouped ? `${manifest.namespace}/${frame.key}` : frame.key;
182975
- if (!groups.some((group2) => group2.key === key)) groups.push({ key, label: frame.label, sub: frame.sub, icon: frame.icon, parent, idHint: frame.idHint });
183368
+ if (!groups.some((group2) => group2.key === key)) groups.push({ key, label: frame.label, sub: frame.sub, icon: frame.icon, otherwise: frame.type, tone: frame.tone ?? null, parent, idHint: frame.idHint });
182976
183369
  parent = key;
182977
183370
  }
182978
183371
  if (parent !== null) framed.set(manifest, parent);
@@ -183130,18 +183523,18 @@ function drawFolder(files, context, index2) {
183130
183523
  for (const entry2 of ordered) {
183131
183524
  const role = roleOf3(entry2);
183132
183525
  const frame = `frame:${entry2.folder}`;
183133
- groups.push({ key: frame, label: entry2.folder.slice(common.length) || baseName2(entry2.folder) || "kustomization", sub: role, icon: "lucide:layers", idHint: entry2.folder.slice(common.length) || baseName2(entry2.folder) || "root" });
183526
+ groups.push({ key: frame, label: entry2.folder.slice(common.length) || baseName2(entry2.folder) || "kustomization", sub: role, ...role === "Overlay" ? { type: "environment" } : { otherwise: "resource-group" }, icon: "lucide:layers", idHint: entry2.folder.slice(common.length) || baseName2(entry2.folder) || "root" });
183134
183527
  nodes.push({ key: own2(entry2), type: "service", label: baseName2(entry2.folder) || "kustomization", sub: cleanText(entry2.details.length === 0 ? "kustomization.yaml" : entry2.details.slice(0, 4).join(", ")), icon: "lucide:layers", group: frame, idHint: `${baseName2(entry2.folder) || "kustomization"}_kustomization` });
183135
183528
  for (const resource of entry2.resources) {
183136
183529
  if (isRemote(resource)) {
183137
183530
  nodes.push({ key: `remote:${resource}`, type: "service", label: cleanText(resource.replace(/^https?:\/\//, "").replace(/\?.*$/, "").split("/").slice(-2).join("/")), sub: "Remote resource", icon: "lucide:globe", group: frame, idHint: "remote" });
183138
- edges.push({ from: own2(entry2), to: `remote:${resource}`, label: "builds on" });
183531
+ edges.push({ from: own2(entry2), to: `remote:${resource}`, label: "builds on", verb: "builds-on" });
183139
183532
  continue;
183140
183533
  }
183141
183534
  const path = resolvePath(entry2.folder, resource);
183142
183535
  const inner = path === null ? void 0 : byFolder.get(path);
183143
183536
  if (inner !== void 0) {
183144
- edges.push({ from: own2(entry2), to: own2(inner), label: "builds on" });
183537
+ edges.push({ from: own2(entry2), to: own2(inner), label: "builds on", verb: "builds-on" });
183145
183538
  continue;
183146
183539
  }
183147
183540
  const found = path === null ? void 0 : objects.get(path);
@@ -183155,7 +183548,7 @@ function drawFolder(files, context, index2) {
183155
183548
  }
183156
183549
  for (const component of entry2.components) {
183157
183550
  const inner = byFolder.get(resolvePath(entry2.folder, component) ?? "\0");
183158
- if (inner !== void 0) edges.push({ from: own2(entry2), to: own2(inner), label: "includes" });
183551
+ if (inner !== void 0) edges.push({ from: own2(entry2), to: own2(inner), label: "includes", verb: "includes" });
183159
183552
  else missing.push(`${entry2.folder === "" ? "" : `${entry2.folder}/`}${component}`);
183160
183553
  }
183161
183554
  for (const generator of entry2.generators) {
@@ -183170,16 +183563,16 @@ function drawFolder(files, context, index2) {
183170
183563
  const key = `patch:${entry2.folder}/${patch.path ?? `${kind ?? "any"}/${name2 ?? "any"}`}`;
183171
183564
  nodes.push({ key, type: "service", label: patch.path === null ? `${kind ?? "object"} ${name2 ?? ""}`.trim() : baseName2(patch.path), sub: cleanText(`Patch${kind === null ? "" : ` of ${kind}${name2 === null ? "" : ` ${name2}`}`}`), icon: "lucide:file-code", group: frame, idHint: `${baseName2(patch.path ?? "patch").replace(/\.ya?ml$/, "")}_patch` });
183172
183565
  const target = kind === null && name2 === null ? null : fileOf2(entry2, kind, name2) ?? [...objects].find(([, inside]) => inside.some((object2) => (kind === null || object2.kind === kind) && (name2 === null || object2.name === name2)))?.[0] ?? null;
183173
- if (target !== null) edges.push({ from: key, to: `file:${target}`, label: "patches" });
183566
+ if (target !== null) edges.push({ from: key, to: `file:${target}`, label: "patches", verb: "patches" });
183174
183567
  }
183175
183568
  }
183176
183569
  const drawnFiles = [...objects].filter(([path]) => nodes.some((node3) => node3.key === `file:${path}`));
183177
183570
  for (const [path, manifests] of drawnFiles) {
183178
183571
  for (const manifest of manifests) {
183179
183572
  for (const use of manifest.uses) {
183180
- for (const node3 of nodes) if (node3.key.startsWith("generator:") && node3.key.endsWith(`/${use.kind}/${use.name}`)) edges.push({ from: `file:${path}`, to: node3.key, arrow: "-->", label: "reads" });
183573
+ for (const node3 of nodes) if (node3.key.startsWith("generator:") && node3.key.endsWith(`/${use.kind}/${use.name}`)) edges.push({ from: `file:${path}`, to: node3.key, arrow: "-->", label: "reads", verb: use.kind === "Secret" ? "secret" : "reads-settings" });
183181
183574
  const holder = drawnFiles.find(([, others]) => others.some((other) => other.kind === use.kind && other.name === use.name));
183182
- if (holder !== void 0 && holder[0] !== path) edges.push({ from: `file:${path}`, to: `file:${holder[0]}`, arrow: "-->", label: use.kind === "PersistentVolumeClaim" ? "mounts" : "reads" });
183575
+ if (holder !== void 0 && holder[0] !== path) edges.push({ from: `file:${path}`, to: `file:${holder[0]}`, arrow: "-->", label: use.kind === "PersistentVolumeClaim" ? "mounts" : "reads", verb: use.kind === "PersistentVolumeClaim" ? "mounts" : use.kind === "Secret" ? "secret" : "reads-settings" });
183183
183576
  }
183184
183577
  for (const [other, inside] of drawnFiles) {
183185
183578
  if (other === path) continue;
@@ -183291,6 +183684,8 @@ var init_detect = __esm({
183291
183684
  { format: "cdk", test: (text3) => /"?aws:cdk:path"?\s*:/.test(text3) || /"artifacts"\s*:/.test(text3) && /"aws:cloudformation:stack"/.test(text3) },
183292
183685
  { format: "cloudformation", test: (text3) => /^\s*"?AWSTemplateFormatVersion"?\s*:/m.test(text3) || /^Transform\s*:\s*AWS::Serverless/m.test(text3) || /^\s*"?Resources"?\s*:/m.test(text3) && /"?Type"?\s*:\s*["']?AWS::[A-Za-z0-9]+::/.test(text3) },
183293
183686
  { format: "backstage", test: (text3) => /^apiVersion\s*:\s*["']?backstage\.io\//m.test(text3) },
183687
+ { format: "asyncapi", test: (text3) => /^\s*["']?asyncapi["']?\s*:\s*["']?[23]\./m.test(text3) },
183688
+ { format: "structurizr", test: (text3) => /^\s*workspace\b[^\n{]*\{/m.test(text3) && /^\s*model\s*\{/m.test(text3) || /^\s*\{/.test(text3) && /"model"\s*:\s*\{/.test(text3) && /"(?:softwareSystems|people)"\s*:\s*\[/.test(text3) },
183294
183689
  // A kustomization, or what kustomize build makes of one: a generated ConfigMap or Secret whose name ends in its hash.
183295
183690
  { format: "kustomize", test: (text3) => isKustomization(text3) || /^kind\s*:\s*(?:ConfigMap|Secret)\s*$/m.test(text3) && /^\s+name\s*:\s*[\w.-]+-(?=[^-\s]*\d)[b-df-hj-np-tv-z0-9]{10}\s*$/m.test(text3) },
183296
183691
  { format: "helm", test: (text3) => /^\s*apiVersion\s*:/m.test(text3) && /^\s*kind\s*:\s*\w+/m.test(text3) && (/^# Source: [^\n]*\/templates\//m.test(text3) || /^\s*helm\.sh\/chart\s*:/m.test(text3)) || isChartFile(text3) },
@@ -183306,9 +183701,13 @@ var init_detect = __esm({
183306
183701
  NAMES = [
183307
183702
  { format: "compose", words: /\b(?:docker[- ]compose|compose (?:file|yaml|yml|stack))\b/i },
183308
183703
  { format: "backstage", words: /\b(?:backstage|catalog-info(?:\.yaml)?|software catalog)\b/i },
183704
+ { format: "asyncapi", words: /\b(?:asyncapi|async api)\b/i },
183705
+ { format: "structurizr", words: /\b(?:structurizr|workspace\.dsl|c4 (?:model|workspace))\b/i },
183309
183706
  { format: "kustomize", words: /\b(?:kustomize|kustomization(?:\.yaml)?)\b/i },
183310
183707
  { format: "helm", words: /\b(?:helm (?:charts?|template|release)|chart\.yaml)\b/i },
183311
183708
  { format: "kubernetes", words: /\b(?:kubernetes|k8s)\b/i },
183709
+ { format: "gitlab-ci", words: /(?:\.gitlab-ci\.ya?ml\b|\bgitlab(?:[- ]ci| pipelines?| ci\/cd)\b)/i },
183710
+ { format: "azure-pipelines", words: /(?:\bazure-pipelines\.ya?ml\b|\bazure (?:devops )?pipelines?\b)/i },
183312
183711
  { format: "github-actions", words: /\b(?:github actions?|workflow (?:file|yaml|yml)|ci workflow)\b/i },
183313
183712
  { format: "terraform-plan", words: /\b(?:terraform|tofu) (?:plan|state|show -json)\b|\bplan\.json\b/i },
183314
183713
  { format: "terraform", words: /\b(?:terraform|opentofu|hcl|\.tf files?)\b/i },
@@ -183326,7 +183725,7 @@ var init_detect = __esm({
183326
183725
  { format: "json", words: /\bjson (?:data|file|records|export)\b/i },
183327
183726
  { format: "tree", words: /\b(?:tree output|folder (?:structure|tree|listing)|directory (?:tree|listing))\b/i }
183328
183727
  ];
183329
- SURE_FROM_DATA = /* @__PURE__ */ new Set(["sql", "prisma", "drizzle", "openapi", "terraform", "terraform-plan", "bicep", "arm", "pulumi", "cloudformation", "cdk", "compose", "kubernetes", "helm", "kustomize", "backstage", "github-actions", "git-log"]);
183728
+ SURE_FROM_DATA = /* @__PURE__ */ new Set(["sql", "prisma", "drizzle", "openapi", "terraform", "terraform-plan", "bicep", "arm", "pulumi", "cloudformation", "cdk", "compose", "kubernetes", "helm", "kustomize", "backstage", "structurizr", "asyncapi", "github-actions", "git-log"]);
183330
183729
  }
183331
183730
  });
183332
183731
 
@@ -183638,8 +184037,8 @@ function rawValue(resource, name2) {
183638
184037
  return resource.block.attributes.find((attribute2) => attribute2.name === name2)?.raw ?? null;
183639
184038
  }
183640
184039
  function tagName(resource) {
183641
- const resolved = resource.values.get("tags.Name");
183642
- if (resolved !== void 0 && !resolved.includes("{")) return resolved;
184040
+ const resolved2 = resource.values.get("tags.Name");
184041
+ if (resolved2 !== void 0 && !resolved2.includes("{")) return resolved2;
183643
184042
  const tags = rawValue(resource, "tags");
183644
184043
  if (tags === null) return null;
183645
184044
  const match = /\bName\s*=\s*"((?:[^"\\$\n]|\\.)*)"/.exec(tags);
@@ -184323,7 +184722,7 @@ function bundleArrows(nodes, groups, edges, ownBox, joinPairs = true) {
184323
184722
  if (sources.length < BUNDLE_AT) continue;
184324
184723
  if (sources.length < FEW && (!joinPairs || !HOSTS2.has(typeOf.get(frame) ?? "") || (pairs.get(frame) ?? 0) < 2 || sources.some((edge) => !edges.some((other) => !joinable.has(other) && (other.from === edge.from || other.to === edge.from))))) continue;
184325
184724
  for (const edge of sources) joined2.add(edge);
184326
- replacements.set(first, { from: own2.key, to: first.to, label: `${first.label ?? ""} (${sources.length})` });
184725
+ replacements.set(first, { from: own2.key, to: first.to, label: `${first.label ?? ""} (${sources.length})`, verb: first.verb ?? null });
184327
184726
  }
184328
184727
  const out = [];
184329
184728
  const written3 = /* @__PURE__ */ new Set();
@@ -184335,7 +184734,7 @@ function bundleArrows(nodes, groups, edges, ownBox, joinPairs = true) {
184335
184734
  if (written3.has(pair2)) {
184336
184735
  const index2 = out.findIndex((candidate2) => `${candidate2.from}\0${candidate2.to}` === pair2);
184337
184736
  const before = out[index2];
184338
- out[index2] = { ...before, label: [before.label, next.label].filter((label) => label !== null && label !== void 0 && label !== "").join(", ") };
184737
+ out[index2] = { ...before, label: [before.label, next.label].filter((label) => label !== null && label !== void 0 && label !== "").join(", "), verb: before.verb === next.verb ? before.verb ?? null : null };
184339
184738
  continue;
184340
184739
  }
184341
184740
  written3.add(pair2);
@@ -184858,6 +185257,10 @@ function stackArrows(plan) {
184858
185257
  }
184859
185258
  return { arrows, unjoined };
184860
185259
  }
185260
+ function verbOf(arrow) {
185261
+ if (arrow.grant || arrow.order !== true && POLICY_KEY.test(arrow.via)) return "grants";
185262
+ return LABEL_VERBS[arrow.label ?? ""] ?? null;
185263
+ }
184861
185264
  function joinArrows(arrows) {
184862
185265
  const joined2 = new Set(arrows.filter((arrow) => arrow.order !== true).flatMap((arrow) => [`${arrow.from}\0${arrow.to}`, `${arrow.to}\0${arrow.from}`]));
184863
185266
  const byPair = /* @__PURE__ */ new Map();
@@ -184869,10 +185272,12 @@ function joinArrows(arrows) {
184869
185272
  const grantWords = grants.length === 0 ? [] : grants.length === 1 ? [grants[0]?.label ?? "role"] : [`${grants.length} roles`];
184870
185273
  const words2 = [...grantWords, ...others];
184871
185274
  const label = words2.length === 0 ? null : words2.length <= MAX_LABELS2 ? words2.join(", ") : `${group2.length} links`;
184872
- return { from: first.from, to: first.to, label, ...group2.every((arrow) => arrow.order === true) ? { arrow: "-->" } : {} };
185275
+ const verbs = new Set(group2.map(verbOf));
185276
+ const verb = verbs.size === 1 ? [...verbs][0] ?? null : null;
185277
+ return { from: first.from, to: first.to, label, verb, ...group2.every((arrow) => arrow.order === true) ? { arrow: "-->" } : {} };
184873
185278
  });
184874
185279
  }
184875
- var ORDER_LABEL, INBOUND, QUIET, ATTRIBUTE_LABELS, TYPE_LABELS, VAGUE_ATTRIBUTES, MAX_LABELS2;
185280
+ var ORDER_LABEL, INBOUND, QUIET, ATTRIBUTE_LABELS, TYPE_LABELS, VAGUE_ATTRIBUTES, LABEL_VERBS, POLICY_KEY, MAX_LABELS2;
184876
185281
  var init_edges2 = __esm({
184877
185282
  "../../packages/diagrams/src/importers/terraform/edges.ts"() {
184878
185283
  "use strict";
@@ -184933,6 +185338,34 @@ var init_edges2 = __esm({
184933
185338
  service: "calls"
184934
185339
  };
184935
185340
  VAGUE_ATTRIBUTES = /* @__PURE__ */ new Set(["value", "values", "config", "configuration", "settings", "properties", "body", "content", "input", "inputs", "parameters", "source", "data"]);
185341
+ LABEL_VERBS = {
185342
+ identity: "grants",
185343
+ role: "grants",
185344
+ policy: "grants",
185345
+ grants: "grants",
185346
+ "grants on": "grants",
185347
+ principal: "grants",
185348
+ data: "stores",
185349
+ storage: "stores",
185350
+ cache: "stores",
185351
+ secret: "secret",
185352
+ secrets: "secret",
185353
+ password: "secret",
185354
+ messages: "sends",
185355
+ "dead letters": "sends",
185356
+ triggers: "triggers",
185357
+ logs: "reports",
185358
+ "logs to": "reports",
185359
+ telemetry: "reports",
185360
+ images: "pulls",
185361
+ "private link": "private",
185362
+ "VNet integration": "private",
185363
+ "public IP": "routes",
185364
+ firewall: "filters",
185365
+ calls: "calls",
185366
+ [ORDER_LABEL]: "depends-on"
185367
+ };
185368
+ POLICY_KEY = /(?:^|\.)[a-z0-9_]*polic(?:y|ies)[a-z0-9_]*\./;
184936
185369
  MAX_LABELS2 = 2;
184937
185370
  }
184938
185371
  });
@@ -185196,7 +185629,7 @@ function picture(plan, context, groupType) {
185196
185629
  if (shape === "container") {
185197
185630
  const frame = containerFrame(resource.key);
185198
185631
  const parent = byFile ? parentOf4(frame, resource) : plan.parents.get(frame) ?? null;
185199
- groups.push({ key: frame, label: box.label, type: null, icon: box.icon ?? null, sub: null, parent, idHint: `${resource.name}_group` });
185632
+ groups.push({ key: frame, label: box.label, type: HOLDER_TYPES[box.type] ?? "resource-group", icon: box.icon ?? null, sub: null, parent, idHint: `${resource.name}_group` });
185200
185633
  const product = role.role === "node" ? productOf2(resource, plan) : box.label;
185201
185634
  nodes.push({ ...box, label: box.label === product ? box.label : product, sub: box.label === product ? box.sub ?? null : withParts(literalDetails(resource), multiplicity(resource), resource.kind === "data" && !plan.compatible ? "existing" : null), group: frame });
185202
185635
  } else {
@@ -185216,7 +185649,7 @@ function picture(plan, context, groupType) {
185216
185649
  for (const [key, module] of plan.modules) {
185217
185650
  const source = module.source.replace(/^(?:\.\.?\/)+/, "");
185218
185651
  const label = `${humanize(module.name)}${module.instance === null ? "" : ` ${module.instance}`}`;
185219
- groups.push({ key, label, type: null, icon: moduleIcon(module.name, module.source, stackProvider(plan)) ?? "lucide:package", sub: cleanText(`${module.noun ?? "Module"} ${source}`), parent: plan.parents.get(key) ?? null, idHint: `${module.name}${module.instance === null ? "" : `_${module.instance}`}` });
185652
+ groups.push({ key, label, type: null, otherwise: "resource-group", icon: moduleIcon(module.name, module.source, stackProvider(plan)) ?? "lucide:package", sub: cleanText(`${module.noun ?? "Module"} ${source}`), parent: plan.parents.get(key) ?? null, idHint: `${module.name}${module.instance === null ? "" : `_${module.instance}`}` });
185220
185653
  }
185221
185654
  const holding = /* @__PURE__ */ new Set();
185222
185655
  for (const node3 of nodes) for (const key of [node3.group ?? "", ...plan.ancestors(node3.group ?? "")]) holding.add(key);
@@ -185258,7 +185691,7 @@ function withoutIdleBoxes(nodes, groups, edges) {
185258
185691
  const framed = groups.map((group2) => {
185259
185692
  const box = idle.get(group2.key);
185260
185693
  if (box === void 0) return group2;
185261
- const said = group2.label.includes(box.label) || group2.type !== null && group2.type !== void 0 ? null : box.label;
185694
+ const said = group2.label.includes(box.label) || group2.type !== null && group2.type !== void 0 && group2.key !== containerFrame(box.key) ? null : box.label;
185262
185695
  return { ...group2, sub: withParts(group2.sub ?? null, said, box.sub ?? null) };
185263
185696
  });
185264
185697
  return { nodes: kept2, groups: framed };
@@ -185282,7 +185715,7 @@ function literalDetails(resource) {
185282
185715
  const values = names8.map((name2) => literal2(resource, name2)).filter((value) => value !== null && value !== "");
185283
185716
  return values.length === 0 ? null : values.join(" ");
185284
185717
  }
185285
- var ACCOUNT_NAMES;
185718
+ var ACCOUNT_NAMES, HOLDER_TYPES;
185286
185719
  var init_picture = __esm({
185287
185720
  "../../packages/diagrams/src/importers/terraform/picture.ts"() {
185288
185721
  "use strict";
@@ -185295,6 +185728,17 @@ var init_picture = __esm({
185295
185728
  init_plan9();
185296
185729
  init_resources();
185297
185730
  ACCOUNT_NAMES = { aws: "AWS account", azure: "Azure subscription", gcp: "Google Cloud project" };
185731
+ HOLDER_TYPES = {
185732
+ database: "data-tier",
185733
+ cache: "data-tier",
185734
+ storage: "storage-account",
185735
+ queue: "messaging",
185736
+ secret: "security",
185737
+ identity: "identity-boundary",
185738
+ service: "app-tier",
185739
+ function: "app-tier",
185740
+ gateway: "edge"
185741
+ };
185298
185742
  }
185299
185743
  });
185300
185744
 
@@ -186358,13 +186802,13 @@ function expression(value, depth) {
186358
186802
  }
186359
186803
  function body5(object2, depth, lines) {
186360
186804
  for (const [key, value] of Object.entries(object2)) {
186361
- const isObject5 = (item) => typeof item === "object" && item !== null && !Array.isArray(item);
186805
+ const isObject6 = (item) => typeof item === "object" && item !== null && !Array.isArray(item);
186362
186806
  if (key === "locals" || key === "tags" || key === "labels" || key === "app_settings" || key === "default" || depth > MAX_DEPTH9) lines.push(`${keyOf2(key)} = ${expression(value, depth)}`);
186363
- else if (isObject5(value)) {
186807
+ else if (isObject6(value)) {
186364
186808
  lines.push(`${keyOf2(key)} {`);
186365
186809
  body5(value, depth + 1, lines);
186366
186810
  lines.push("}");
186367
- } else if (Array.isArray(value) && value.length > 0 && value.every(isObject5)) {
186811
+ } else if (Array.isArray(value) && value.length > 0 && value.every(isObject6)) {
186368
186812
  for (const item of value) {
186369
186813
  lines.push(`${keyOf2(key)} {`);
186370
186814
  body5(item, depth + 1, lines);
@@ -186975,9 +187419,9 @@ var init_evaluate = __esm({
186975
187419
  }
186976
187420
  case "replace": {
186977
187421
  const [value, search, replacement] = args();
186978
- const [known3, find, put] = [value instanceof Partial ? value.name : text3(value), text3(search), text3(replacement)];
186979
- if (known3 === null || find === null || put === null || find === "") return value ?? null;
186980
- const out = known3.split(find).join(put);
187422
+ const [known3, find2, put] = [value instanceof Partial ? value.name : text3(value), text3(search), text3(replacement)];
187423
+ if (known3 === null || find2 === null || put === null || find2 === "") return value ?? null;
187424
+ const out = known3.split(find2).join(put);
186981
187425
  return value instanceof Partial ? new Partial(out) : out;
186982
187426
  }
186983
187427
  case "take":
@@ -187351,7 +187795,7 @@ function chainOf(expr) {
187351
187795
  }
187352
187796
  return { base: current, steps };
187353
187797
  }
187354
- function nameOf7(slot, evaluator) {
187798
+ function nameOf8(slot, evaluator) {
187355
187799
  const named = bodyObject(slot.declaration.value)?.entries.find((entry2) => entry2.key === "name")?.value;
187356
187800
  return named === void 0 ? null : textOf5(evaluator.evaluate(named, slot.env));
187357
187801
  }
@@ -187366,15 +187810,15 @@ var init_slots2 = __esm({
187366
187810
 
187367
187811
  // ../../packages/diagrams/src/importers/bicep/items.ts
187368
187812
  function withoutReadCopies(items2) {
187369
- const nameOf10 = (item) => item.attributes.find((attribute2) => attribute2.name === "name")?.value ?? null;
187813
+ const nameOf11 = (item) => item.attributes.find((attribute2) => attribute2.name === "name")?.value ?? null;
187370
187814
  const made = /* @__PURE__ */ new Map();
187371
187815
  for (const item of items2) {
187372
- const name2 = nameOf10(item);
187816
+ const name2 = nameOf11(item);
187373
187817
  if (!item.existing && name2 !== null) made.set(`${item.type}\0${name2}`, item.name);
187374
187818
  }
187375
187819
  const alias = /* @__PURE__ */ new Map();
187376
187820
  for (const item of items2) {
187377
- const name2 = nameOf10(item);
187821
+ const name2 = nameOf11(item);
187378
187822
  const real = name2 === null ? void 0 : made.get(`${item.type}\0${name2}`);
187379
187823
  if (item.existing && real !== void 0) alias.set(item.name, real);
187380
187824
  }
@@ -187467,18 +187911,18 @@ var init_items3 = __esm({
187467
187911
  }
187468
187912
  /** The resource a child names as its parent by the start of its name (`vnet-shop` of `vnet-shop/app`), when the stack declares it. */
187469
187913
  parentByName(slot) {
187470
- const name2 = nameOf7(slot, this.evaluator);
187914
+ const name2 = nameOf8(slot, this.evaluator);
187471
187915
  const segments = slot.armType.split("/");
187472
187916
  if (name2 === null || !name2.includes("/") || segments.length < 3) return null;
187473
187917
  const parentType = segments.slice(0, -1).join("/").toLowerCase();
187474
187918
  const parentName = name2.slice(0, name2.lastIndexOf("/"));
187475
- for (const list of this.slots.values()) for (const candidate2 of list) if (candidate2.armType.toLowerCase() === parentType && nameOf7(candidate2, this.evaluator) === parentName) return candidate2.transparent ? candidate2.holder?.name ?? null : candidate2.name;
187919
+ for (const list of this.slots.values()) for (const candidate2 of list) if (candidate2.armType.toLowerCase() === parentType && nameOf8(candidate2, this.evaluator) === parentName) return candidate2.transparent ? candidate2.holder?.name ?? null : candidate2.name;
187476
187920
  if (/\/(?:blob|queue|table|file)services$/.test(parentType) && segments.length >= 4) return this.declared(segments.slice(0, -2).join("/"), parentName.slice(0, parentName.lastIndexOf("/")));
187477
187921
  return null;
187478
187922
  }
187479
187923
  /** The item of a type and a name the deployment declares, or null. */
187480
187924
  declared(type, name2) {
187481
- for (const list of this.slots.values()) for (const candidate2 of list) if (candidate2.armType.toLowerCase() === type.toLowerCase() && nameOf7(candidate2, this.evaluator) === name2) return candidate2.name;
187925
+ for (const list of this.slots.values()) for (const candidate2 of list) if (candidate2.armType.toLowerCase() === type.toLowerCase() && nameOf8(candidate2, this.evaluator) === name2) return candidate2.name;
187482
187926
  return null;
187483
187927
  }
187484
187928
  /** The item that stands for a name: a storage service's account, since the service itself is not drawn. */
@@ -187719,9 +188163,9 @@ var init_references = __esm({
187719
188163
  if (type !== null && last !== null && last !== void 0) {
187720
188164
  for (const list of this.slots.values()) {
187721
188165
  for (const slot of list) {
187722
- const own2 = nameOf7(slot, this.evaluator);
188166
+ const own2 = nameOf8(slot, this.evaluator);
187723
188167
  if (slot.armType.toLowerCase() === type && (own2 === last || own2 === names8.join("/"))) return [slot.name];
187724
- if (type === `${slot.armType.toLowerCase()}/subnets` && nameOf7(slot, this.evaluator) === names8[names8.length - 2]) {
188168
+ if (type === `${slot.armType.toLowerCase()}/subnets` && nameOf8(slot, this.evaluator) === names8[names8.length - 2]) {
187725
188169
  const subnet = slot.subnets.find((entry2) => entry2.display === last || entry2.label === last);
187726
188170
  if (subnet !== void 0) return [subnet.name];
187727
188171
  }
@@ -188815,8 +189259,626 @@ var init_arm = __esm({
188815
189259
  }
188816
189260
  });
188817
189261
 
189262
+ // ../../packages/diagrams/src/importers/asyncapi/document.ts
189263
+ function nodeOf2(value, depth = 0) {
189264
+ const at3 = { line: 1, column: 1 };
189265
+ if (depth > MAX_DEPTH13 || value === null || value === void 0) return { kind: "scalar", value: "", quoted: false, isNull: true, ...at3 };
189266
+ if (Array.isArray(value)) return { kind: "seq", items: value.map((item) => nodeOf2(item, depth + 1)), ...at3 };
189267
+ if (typeof value === "object") return { kind: "map", entries: Object.entries(value).map(([key, entry2]) => ({ key, value: nodeOf2(entry2, depth + 1), ...at3 })), ...at3 };
189268
+ return { kind: "scalar", value: String(value), quoted: typeof value === "string", isNull: false, ...at3 };
189269
+ }
189270
+ function readDocument(text3) {
189271
+ if (/^\s*\{/.test(text3)) {
189272
+ try {
189273
+ return { root: nodeOf2(JSON.parse(text3)), problem: null };
189274
+ } catch (error62) {
189275
+ return { root: null, problem: { line: 1, column: 1, message: `The JSON cannot be read: ${error62 instanceof Error ? error62.message : "it is not valid"}.`, hint: "Fix the JSON, often a missing or extra comma or quote." } };
189276
+ }
189277
+ }
189278
+ const read = parseYaml(text3);
189279
+ if (read.error !== null) return { root: null, problem: { ...read.error, message: `The YAML cannot be read: ${read.error.message}` } };
189280
+ return { root: read.documents.find((document2) => document2.root !== null)?.root ?? null, problem: null };
189281
+ }
189282
+ function pointerPart(part) {
189283
+ const plain3 = part.replace(/~1/g, "/").replace(/~0/g, "~");
189284
+ try {
189285
+ return decodeURIComponent(plain3);
189286
+ } catch {
189287
+ return plain3;
189288
+ }
189289
+ }
189290
+ function nameOfRef(ref) {
189291
+ const path = ref.slice(ref.indexOf("#") + 1);
189292
+ const last = path.split("/").filter((part) => part !== "").pop() ?? ref;
189293
+ return pointerPart(last);
189294
+ }
189295
+ function deref(root, node3) {
189296
+ let at3 = node3;
189297
+ for (let hop = 0; hop < MAX_HOPS; hop += 1) {
189298
+ const ref = refOf(at3);
189299
+ if (ref === null || !ref.startsWith("#/")) return at3;
189300
+ let target = root;
189301
+ for (const part of ref.slice(2).split("/")) target = get(target, pointerPart(part));
189302
+ if (target === void 0) return at3;
189303
+ at3 = target;
189304
+ }
189305
+ return at3;
189306
+ }
189307
+ var MAX_HOPS, MAX_DEPTH13, refOf;
189308
+ var init_document2 = __esm({
189309
+ "../../packages/diagrams/src/importers/asyncapi/document.ts"() {
189310
+ "use strict";
189311
+ init_yaml();
189312
+ MAX_HOPS = 10;
189313
+ MAX_DEPTH13 = 64;
189314
+ refOf = (node3) => {
189315
+ const ref = get(node3, "$ref");
189316
+ return ref?.kind === "scalar" ? ref.value : null;
189317
+ };
189318
+ }
189319
+ });
189320
+
189321
+ // ../../packages/diagrams/src/importers/asyncapi/layers.ts
189322
+ function adjacency(keys, arrows, reverse) {
189323
+ const next = new Map(keys.map((key) => [key, []]));
189324
+ for (const arrow of arrows) next.get(reverse ? arrow.to : arrow.from)?.push(reverse ? arrow.from : arrow.to);
189325
+ return next;
189326
+ }
189327
+ function finishOrder(start2, next, seen, finished2) {
189328
+ const stack = [{ key: start2, index: 0 }];
189329
+ seen.add(start2);
189330
+ while (stack.length > 0) {
189331
+ const top = stack[stack.length - 1];
189332
+ const following = next.get(top.key) ?? [];
189333
+ if (top.index < following.length) {
189334
+ const key = following[top.index];
189335
+ top.index += 1;
189336
+ if (!seen.has(key)) {
189337
+ seen.add(key);
189338
+ stack.push({ key, index: 0 });
189339
+ }
189340
+ } else {
189341
+ finished2.push(top.key);
189342
+ stack.pop();
189343
+ }
189344
+ }
189345
+ }
189346
+ function layersOf(keys, arrows) {
189347
+ const forward = adjacency(keys, arrows, false);
189348
+ const backward = adjacency(keys, arrows, true);
189349
+ const finished2 = [];
189350
+ const seen = /* @__PURE__ */ new Set();
189351
+ for (const key of keys) if (!seen.has(key)) finishOrder(key, forward, seen, finished2);
189352
+ const component = /* @__PURE__ */ new Map();
189353
+ const members2 = [];
189354
+ const assigned = /* @__PURE__ */ new Set();
189355
+ for (const key of [...finished2].reverse()) {
189356
+ if (assigned.has(key)) continue;
189357
+ const found = [];
189358
+ finishOrder(key, backward, assigned, found);
189359
+ for (const member of found) component.set(member, members2.length);
189360
+ members2.push(found);
189361
+ }
189362
+ const looped = new Set(members2.filter((group2) => group2.length > 1).flat());
189363
+ for (const arrow of arrows) if (arrow.from === arrow.to) looped.add(arrow.from);
189364
+ const step = new Array(members2.length).fill(0);
189365
+ const fed = /* @__PURE__ */ new Set();
189366
+ for (let index2 = 0; index2 < members2.length; index2 += 1) {
189367
+ for (const key of members2[index2] ?? []) {
189368
+ for (const to of forward.get(key) ?? []) {
189369
+ const target = component.get(to);
189370
+ if (target === index2) continue;
189371
+ step[target] = Math.max(step[target], step[index2] + 1);
189372
+ fed.add(target);
189373
+ }
189374
+ }
189375
+ }
189376
+ for (let index2 = members2.length - 1; index2 >= 0; index2 -= 1) {
189377
+ if (fed.has(index2)) continue;
189378
+ const targets = (members2[index2] ?? []).flatMap((key) => forward.get(key) ?? []).map((to) => component.get(to)).filter((target) => target !== index2);
189379
+ if (targets.length > 0) step[index2] = Math.min(...targets.map((target) => step[target])) - 1;
189380
+ }
189381
+ return { layer: new Map(keys.map((key) => [key, step[component.get(key)]])), looped };
189382
+ }
189383
+ var init_layers2 = __esm({
189384
+ "../../packages/diagrams/src/importers/asyncapi/layers.ts"() {
189385
+ "use strict";
189386
+ }
189387
+ });
189388
+
189389
+ // ../../packages/diagrams/src/importers/asyncapi/spec.ts
189390
+ function messageName(root, node3, fallback) {
189391
+ const ref = refOf(node3);
189392
+ const message2 = deref(root, node3);
189393
+ return text(get(message2, "name")) ?? text(get(message2, "title")) ?? text(get(message2, "messageId")) ?? fallback ?? (ref === null ? null : nameOfRef(ref));
189394
+ }
189395
+ function messagesOf2(root, operation) {
189396
+ const message2 = get(operation, "message");
189397
+ const resolved2 = deref(root, message2);
189398
+ const choices = items(get(resolved2, "oneOf"));
189399
+ const list = choices.length > 0 ? choices : message2 === void 0 ? [] : [message2];
189400
+ return list.map((node3) => messageName(root, node3, null)).filter((name2) => name2 !== null);
189401
+ }
189402
+ function serversOf(root) {
189403
+ return entries(get(root, "servers")).map((entry2) => {
189404
+ const server = deref(root, entry2.value);
189405
+ const host = text(get(server, "host")) ?? text(get(server, "url"));
189406
+ return { name: entry2.key, protocol: (text(get(server, "protocol")) ?? "").toLowerCase(), host, line: entry2.line, column: entry2.column };
189407
+ });
189408
+ }
189409
+ function channelServers(root, channel) {
189410
+ return items(get(channel, "servers")).map((node3) => text(node3) ?? (refOf(node3) === null ? null : nameOfRef(refOf(node3) ?? ""))).filter((name2) => name2 !== null);
189411
+ }
189412
+ function readVersion2(root) {
189413
+ const channels = [];
189414
+ const operations = [];
189415
+ for (const entry2 of entries(get(root, "channels"))) {
189416
+ const channel = deref(root, entry2.value);
189417
+ channels.push({ id: entry2.key, address: entry2.key, description: text(get(channel, "description")), servers: channelServers(root, channel), line: entry2.line, column: entry2.column });
189418
+ for (const [key, direction] of [["subscribe", "send"], ["publish", "receive"]]) {
189419
+ const operation = get(channel, key);
189420
+ if (operation === void 0) continue;
189421
+ const at3 = channel?.kind === "map" ? channel.entries.find((candidate2) => candidate2.key === key) : void 0;
189422
+ operations.push({ id: text(get(operation, "operationId")) ?? `${key} ${entry2.key}`, channel: entry2.key, direction, messages: messagesOf2(root, operation), line: at3?.line ?? entry2.line, column: at3?.column ?? entry2.column });
189423
+ }
189424
+ }
189425
+ return { channels, operations, unresolved: [] };
189426
+ }
189427
+ function readVersion3(root) {
189428
+ const channels = [];
189429
+ const messagesOfChannel = /* @__PURE__ */ new Map();
189430
+ for (const entry2 of entries(get(root, "channels"))) {
189431
+ const channel = deref(root, entry2.value);
189432
+ channels.push({ id: entry2.key, address: text(get(channel, "address")) ?? entry2.key, description: text(get(channel, "description")) ?? text(get(channel, "summary")), servers: channelServers(root, channel), line: entry2.line, column: entry2.column });
189433
+ messagesOfChannel.set(
189434
+ entry2.key,
189435
+ entries(get(channel, "messages")).map((message2) => messageName(root, message2.value, message2.key)).filter((name2) => name2 !== null)
189436
+ );
189437
+ }
189438
+ const operations = [];
189439
+ const unresolved = [];
189440
+ for (const entry2 of entries(get(root, "operations"))) {
189441
+ const operation = deref(root, entry2.value);
189442
+ const action = text(get(operation, "action"));
189443
+ const ref = refOf(get(operation, "channel"));
189444
+ const channel = ref === null ? null : ref.startsWith("#/channels/") ? nameOfRef(ref) : null;
189445
+ if (channel === null || !messagesOfChannel.has(channel)) {
189446
+ unresolved.push({ name: ref ?? entry2.key, line: entry2.line, column: entry2.column });
189447
+ continue;
189448
+ }
189449
+ if (action !== "send" && action !== "receive") continue;
189450
+ const listed2 = items(get(operation, "messages")).map((node3) => {
189451
+ const target = refOf(node3);
189452
+ const key = target === null ? null : nameOfRef(target);
189453
+ return messageName(root, node3, null) ?? key;
189454
+ }).filter((name2) => name2 !== null);
189455
+ operations.push({ id: entry2.key, channel, direction: action, messages: listed2.length > 0 ? listed2 : messagesOfChannel.get(channel) ?? [], line: entry2.line, column: entry2.column });
189456
+ }
189457
+ return { channels, operations, unresolved };
189458
+ }
189459
+ function majorOf(root) {
189460
+ const version2 = text(get(root, "asyncapi"));
189461
+ if (version2 === null) return null;
189462
+ return version2.startsWith("2.") ? 2 : version2.startsWith("3.") ? 3 : null;
189463
+ }
189464
+ function readApplication(root, major) {
189465
+ const info = get(root, "info");
189466
+ const read = major === 2 ? readVersion2(root) : readVersion3(root);
189467
+ return { title: text(get(info, "title")) ?? "Application", version: text(get(root, "asyncapi")) ?? "", appVersion: text(get(info, "version")), major, servers: serversOf(root), ...read };
189468
+ }
189469
+ var init_spec = __esm({
189470
+ "../../packages/diagrams/src/importers/asyncapi/spec.ts"() {
189471
+ "use strict";
189472
+ init_yaml();
189473
+ init_document2();
189474
+ }
189475
+ });
189476
+
189477
+ // ../../packages/diagrams/src/importers/asyncapi/index.ts
189478
+ function familyOf2(protocol) {
189479
+ if (protocol.startsWith("kafka")) return "kafka";
189480
+ if (protocol.startsWith("amqp")) return "amqp";
189481
+ if (protocol.includes("mqtt")) return "mqtt";
189482
+ if (protocol === "wss") return "ws";
189483
+ if (protocol === "https") return "http";
189484
+ return protocol === "" ? "broker" : protocol;
189485
+ }
189486
+ function messagesLabel(messages) {
189487
+ const unique2 = [...new Set(messages.map((message2) => cleanText(message2)))];
189488
+ if (unique2.length === 0) return null;
189489
+ return unique2.length <= 2 ? unique2.join(", ") : `${unique2[0]} and ${unique2.length - 1} more`;
189490
+ }
189491
+ function readAsyncApi(text3, context) {
189492
+ const index2 = new LineIndex(text3);
189493
+ const files = context.files.map((file2, position) => ({ path: file2.path, start: file2.start, text: text3.slice(file2.start, context.files[position + 1]?.start ?? text3.length) }));
189494
+ const at3 = (file2, line3, column) => index2.span(file2.start + file2.text.split("\n").slice(0, line3 - 1).join("\n").length + (line3 > 1 ? 1 : 0) + column - 1);
189495
+ const applications = [];
189496
+ for (const file2 of files) {
189497
+ const read = readDocument(file2.text);
189498
+ const name2 = cleanText(file2.path, "The file");
189499
+ if (read.problem !== null || read.root === null) {
189500
+ const problem = read.problem ?? { line: 1, column: 1, message: "The file is empty.", hint: "Send an AsyncAPI document." };
189501
+ context.report.add(files.length === 1 ? DIAGNOSTIC_CODES.importUnreadable : DIAGNOSTIC_CODES.importSkipped, at3(file2, problem.line, problem.column), `${files.length === 1 ? "" : `${name2} is not drawn: `}${problem.message}`, problem.hint);
189502
+ if (files.length === 1) return null;
189503
+ continue;
189504
+ }
189505
+ const major = majorOf(read.root);
189506
+ if (major === null) {
189507
+ const said = read.root.kind === "map" && read.root.entries.some((entry2) => entry2.key === "asyncapi") ? "is of an AsyncAPI version this reader does not read" : "has no asyncapi: field, so it is not an AsyncAPI document";
189508
+ if (files.length > 1) context.report.add(DIAGNOSTIC_CODES.importSkipped, at3(file2, 1, 1), `${name2} ${said}, and is not drawn.`, "Send AsyncAPI 2.x or 3.x documents; convert a 1.x document with the AsyncAPI converter.");
189509
+ else context.report.add(DIAGNOSTIC_CODES.importNoShape, START, `The text ${said}.`, "Send an AsyncAPI 2.x or 3.x document, which starts with asyncapi: and a version.");
189510
+ continue;
189511
+ }
189512
+ applications.push({ app: readApplication(read.root, major), file: file2 });
189513
+ }
189514
+ if (applications.length === 0) {
189515
+ if (files.length > 1) context.report.add(DIAGNOSTIC_CODES.importNoShape, START, "None of the files is an AsyncAPI document.", "Send AsyncAPI 2.x or 3.x documents, each starting with asyncapi: and a version.");
189516
+ return null;
189517
+ }
189518
+ const nodes = [];
189519
+ const groups = [];
189520
+ const edges = [];
189521
+ const arrows = /* @__PURE__ */ new Map();
189522
+ const channelBox = /* @__PURE__ */ new Map();
189523
+ const channelFamily = /* @__PURE__ */ new Map();
189524
+ const brokerHosts = /* @__PURE__ */ new Map();
189525
+ const brokerServers = /* @__PURE__ */ new Map();
189526
+ let operations = 0;
189527
+ for (const { app, file: file2 } of applications) {
189528
+ for (const problem of app.unresolved) context.report.add(DIAGNOSTIC_CODES.importUnresolved, at3(file2, problem.line, problem.column), `The operation names a channel the document does not hold: ${cleanText(problem.name)}, so no arrow is drawn for it.`, "Point channel at an entry of channels, such as $ref: '#/channels/orderPlaced'.");
189529
+ const appKey = `app:${app.title}`;
189530
+ if (!nodes.some((node3) => node3.key === appKey)) nodes.push({ key: appKey, type: "service", label: cleanText(app.title), sub: app.appVersion === null ? null : `Version ${cleanText(app.appVersion)}`, idHint: app.title.toLowerCase() });
189531
+ const serverFamily = new Map(app.servers.map((server) => [server.name, familyOf2(server.protocol)]));
189532
+ for (const server of app.servers) {
189533
+ const family = familyOf2(server.protocol);
189534
+ brokerServers.set(family, [...brokerServers.get(family) ?? [], server]);
189535
+ if (server.host !== null) brokerHosts.set(family, (brokerHosts.get(family) ?? /* @__PURE__ */ new Set()).add(server.host));
189536
+ }
189537
+ for (const channel of app.channels) {
189538
+ const families = new Set((channel.servers.length > 0 ? channel.servers : app.servers.map((server) => server.name)).map((name2) => serverFamily.get(name2)).filter((family2) => family2 !== void 0));
189539
+ const family = families.size === 1 ? [...families][0] : void 0;
189540
+ const key = `channel:${channel.address}`;
189541
+ const existing = channelBox.get(key);
189542
+ if (existing === void 0) {
189543
+ const box = { key, type: "queue", label: cleanText(channel.address), sub: channel.description === null ? null : cleanText(channel.description), group: null, idHint: channel.address };
189544
+ channelBox.set(key, box);
189545
+ if (family !== void 0) channelFamily.set(key, family);
189546
+ nodes.push(box);
189547
+ }
189548
+ }
189549
+ for (const operation of app.operations) {
189550
+ const channel = app.channels.find((candidate2) => candidate2.id === operation.channel);
189551
+ if (channel === void 0) continue;
189552
+ const channelKey = `channel:${channel.address}`;
189553
+ operations += 1;
189554
+ const [from, to] = operation.direction === "send" ? [appKey, channelKey] : [channelKey, appKey];
189555
+ const pair2 = `${from}\0${to}`;
189556
+ arrows.set(pair2, { from, to, messages: [...arrows.get(pair2)?.messages ?? [], ...operation.messages] });
189557
+ }
189558
+ }
189559
+ for (const arrow of arrows.values()) edges.push({ from: arrow.from, to: arrow.to, label: messagesLabel(arrow.messages), verb: arrow.from.startsWith("channel:") ? "receives" : "sends" });
189560
+ const { layer: layer2, looped } = layersOf(
189561
+ nodes.map((node3) => node3.key),
189562
+ edges
189563
+ );
189564
+ const brokerIcon = (family) => BROKERS[family]?.icon ?? (brokerServers.get(family) ?? []).map((server) => iconForName(`${server.name} ${server.host ?? ""}`, "queue")).find((icon) => icon?.startsWith("simple:") === true) ?? null;
189565
+ const frameSteps = /* @__PURE__ */ new Map();
189566
+ const frameKey = (family, step) => {
189567
+ const index3 = (frameSteps.get(family) ?? []).indexOf(step);
189568
+ return index3 === 0 ? `broker:${family}` : `broker:${family}:${index3 + 1}`;
189569
+ };
189570
+ const placed = nodes.map((node3) => {
189571
+ const family = channelFamily.get(node3.key);
189572
+ if (family === void 0) return node3;
189573
+ if (looped.has(node3.key)) return { ...node3, icon: brokerIcon(family) };
189574
+ const step = layer2.get(node3.key) ?? 0;
189575
+ const steps = frameSteps.get(family) ?? [];
189576
+ if (!steps.includes(step)) frameSteps.set(family, [...steps, step]);
189577
+ return { ...node3, group: frameKey(family, step) };
189578
+ });
189579
+ const usedFamilies = new Set(channelFamily.values());
189580
+ for (const family of [...brokerServers.keys()]) {
189581
+ const broker = BROKERS[family];
189582
+ const hosts = [...brokerHosts.get(family) ?? []];
189583
+ const where = hosts.length === 0 ? null : hosts.length === 1 ? hosts[0] ?? null : `${hosts.length} hosts`;
189584
+ const steps = [...frameSteps.get(family) ?? []].sort((a, b) => a - b);
189585
+ for (const step of steps) {
189586
+ groups.push({ key: frameKey(family, step), label: broker?.label ?? `${family.toUpperCase()} broker`, type: "messaging", icon: brokerIcon(family), sub: where === null || step !== steps[0] ? null : cleanText(where), idHint: family });
189587
+ }
189588
+ }
189589
+ const unused = [...brokerServers.entries()].filter(([family]) => !usedFamilies.has(family)).flatMap(([, servers]) => servers);
189590
+ if (unused.length > 0) {
189591
+ const first = unused[0];
189592
+ const file2 = applications.find(({ app }) => app.servers.includes(first))?.file ?? files[0];
189593
+ context.report.add(DIAGNOSTIC_CODES.importSkipped, at3(file2, first.line, first.column), `${countOf2(unused.length, "server")} carr${unused.length === 1 ? "ies" : "y"} no channel that is drawn in a frame, so ${unused.length === 1 ? "it is" : "they are"} not drawn: ${unused.slice(0, 3).map((server) => cleanText(server.name)).join(", ")}.`, "Name the servers of each channel with servers:, so each channel sits in the frame of its broker.");
189594
+ }
189595
+ const sends = (key) => edges.some((edge) => edge.from === key);
189596
+ const receives = (key) => edges.some((edge) => edge.to === key);
189597
+ const rank = (node3) => node3.type === "queue" ? 1 : sends(node3.key) && !receives(node3.key) ? 0 : !sends(node3.key) && receives(node3.key) ? 2 : 1;
189598
+ const ordered = [...placed].sort((a, b) => rank(a) - rank(b));
189599
+ const one = applications.length === 1 ? applications[0]?.app : void 0;
189600
+ const channels = channelBox.size;
189601
+ return writeFlow(
189602
+ {
189603
+ title: context.title ?? (one === void 0 ? "Event flows" : cleanText(one.title)),
189604
+ desc: one === void 0 ? `${countOf2(applications.length, "application")} and the ${countOf2(channels, "channel")} where they meet: an arrow into a channel sends to it, an arrow out of one receives from it, labelled with the messages. Imported from AsyncAPI.` : `What ${cleanText(one.title)} sends and receives on ${countOf2(channels, "channel")}: an arrow into a channel sends to it, an arrow out of one receives from it, labelled with the messages. Imported from AsyncAPI ${cleanText(one.version)}.`,
189605
+ direction: "down",
189606
+ nodes: ordered,
189607
+ groups,
189608
+ edges,
189609
+ noun: "channels",
189610
+ advice: one === void 0 ? "Check that each channel has the same address in every file that uses it, so producers and consumers meet on one box." : "Import the AsyncAPI documents of the other applications with this one, as files, to see who sends and who receives on each channel.",
189611
+ coverage: { noun: "channels and operations", total: channels + operations, drawn: channels + operations, summarised: 0 }
189612
+ },
189613
+ context.report
189614
+ );
189615
+ }
189616
+ var BROKERS, asyncapiImporter;
189617
+ var init_asyncapi = __esm({
189618
+ "../../packages/diagrams/src/importers/asyncapi/index.ts"() {
189619
+ "use strict";
189620
+ init_core();
189621
+ init_shared2();
189622
+ init_document2();
189623
+ init_layers2();
189624
+ init_spec();
189625
+ BROKERS = {
189626
+ kafka: { label: "Kafka", icon: "simple:apachekafka" },
189627
+ amqp: { label: "AMQP broker", icon: null },
189628
+ mqtt: { label: "MQTT broker", icon: null },
189629
+ nats: { label: "NATS", icon: null },
189630
+ redis: { label: "Redis", icon: "simple:redis" },
189631
+ pulsar: { label: "Pulsar", icon: "simple:apachepulsar" },
189632
+ sns: { label: "Amazon SNS", icon: "aws:sns" },
189633
+ sqs: { label: "Amazon SQS", icon: "aws:sqs" },
189634
+ googlepubsub: { label: "Google Pub/Sub", icon: "gcp:pubsub" },
189635
+ ibmmq: { label: "IBM MQ", icon: null },
189636
+ jms: { label: "JMS broker", icon: null },
189637
+ solace: { label: "Solace", icon: null },
189638
+ stomp: { label: "STOMP broker", icon: null },
189639
+ ws: { label: "WebSocket", icon: null },
189640
+ http: { label: "HTTP", icon: null },
189641
+ anypointmq: { label: "Anypoint MQ", icon: null },
189642
+ mercure: { label: "Mercure", icon: null }
189643
+ };
189644
+ asyncapiImporter = { format: "asyncapi", kinds: ["flow"], joins: "\n", extensions: [".yaml", ".yml", ".json"], read: readAsyncApi };
189645
+ }
189646
+ });
189647
+
189648
+ // ../../packages/diagrams/src/importers/azure-pipelines/pipeline.ts
189649
+ function dependsOnOf2(node3) {
189650
+ const value = get(node3, "dependsOn");
189651
+ if (value === void 0) return null;
189652
+ return items(value).map((item) => text(item)).filter((name2) => name2 !== null && name2 !== "");
189653
+ }
189654
+ function stepLabel(step) {
189655
+ const named = text(get(step, "displayName")) ?? text(get(step, "name"));
189656
+ if (named !== null) return named;
189657
+ const task = text(get(step, "task"));
189658
+ if (task !== null) return task.split("@")[0] ?? task;
189659
+ const checkout = text(get(step, "checkout"));
189660
+ if (checkout !== null) return checkout === "none" ? "No checkout" : `Check out ${checkout}`;
189661
+ for (const key of ["script", "bash", "pwsh", "powershell"]) {
189662
+ const script = text(get(step, key));
189663
+ if (script === null) continue;
189664
+ const first = script.split("\n").map((line3) => line3.trim()).find((line3) => line3 !== "" && !line3.startsWith("#")) ?? "Run a script";
189665
+ if (first.length <= MAX_STEP_LABEL) return first;
189666
+ const cut = first.slice(0, MAX_STEP_LABEL);
189667
+ return cut.lastIndexOf(" ") > 12 ? cut.slice(0, cut.lastIndexOf(" ")) : cut;
189668
+ }
189669
+ for (const key of ["download", "publish", "getPackage", "restoreCache", "reviewApp"]) if (get(step, key) !== void 0) return `${key.charAt(0).toUpperCase()}${key.slice(1)} ${text(get(step, key)) ?? ""}`.trim();
189670
+ return "Step";
189671
+ }
189672
+ function stepsOf(list, templates) {
189673
+ const out = [];
189674
+ for (const step of items(list)) {
189675
+ const template = text(get(step, "template"));
189676
+ if (template !== null) templates.push({ path: template, line: step.line, column: step.column });
189677
+ else if (step.kind === "map") out.push({ label: cleanText(stepLabel(step), "Step"), line: step.line, column: step.column });
189678
+ }
189679
+ return out;
189680
+ }
189681
+ function poolOf(node3, fallback) {
189682
+ const pool = get(node3, "pool");
189683
+ if (pool === void 0) return fallback;
189684
+ return text(pool) ?? text(get(pool, "vmImage")) ?? text(get(pool, "name")) ?? fallback;
189685
+ }
189686
+ function deploymentSteps(job, templates) {
189687
+ const strategy = get(job, "strategy");
189688
+ return entries(strategy).flatMap((kind) => entries(kind.value).flatMap((hook) => (hook.key === "on" ? entries(hook.value).map((when) => when.value) : [hook.value]).flatMap((phase) => stepsOf(get(phase, "steps"), templates))));
189689
+ }
189690
+ function jobsOf(list, pool, templates) {
189691
+ const out = [];
189692
+ for (const job of items(list)) {
189693
+ const template = text(get(job, "template"));
189694
+ if (template !== null) {
189695
+ templates.push({ path: template, line: job.line, column: job.column });
189696
+ continue;
189697
+ }
189698
+ const deployment = text(get(job, "deployment"));
189699
+ const name2 = deployment ?? text(get(job, "job"));
189700
+ if (name2 === null) continue;
189701
+ const strategy = get(job, "strategy");
189702
+ const matrix = entries(get(strategy, "matrix")).length;
189703
+ const parallel = Number(text(get(strategy, "parallel")));
189704
+ const environment = get(job, "environment");
189705
+ out.push({
189706
+ name: name2,
189707
+ label: cleanText(text(get(job, "displayName")) ?? name2, name2),
189708
+ dependsOn: dependsOnOf2(job),
189709
+ conditional: get(job, "condition") !== void 0,
189710
+ environment: deployment === null ? null : text(environment) ?? text(get(environment, "name")),
189711
+ pool: poolOf(job, pool),
189712
+ runs: matrix > 1 ? matrix : Number.isInteger(parallel) && parallel > 1 ? parallel : 1,
189713
+ matrix: matrix > 1,
189714
+ steps: deployment === null ? stepsOf(get(job, "steps"), templates) : deploymentSteps(job, templates),
189715
+ line: job.line,
189716
+ column: job.column
189717
+ });
189718
+ }
189719
+ return out;
189720
+ }
189721
+ function readPipeline(root) {
189722
+ const templates = [];
189723
+ const extended2 = text(get(get(root, "extends"), "template"));
189724
+ if (extended2 !== null) {
189725
+ const at3 = root.kind === "map" ? root.entries.find((entry2) => entry2.key === "extends") : void 0;
189726
+ templates.push({ path: extended2, line: at3?.line ?? 1, column: at3?.column ?? 1 });
189727
+ }
189728
+ const pool = poolOf(root, null);
189729
+ if (get(root, "stages") !== void 0) {
189730
+ const stages = [];
189731
+ for (const stage of items(get(root, "stages"))) {
189732
+ const template = text(get(stage, "template"));
189733
+ if (template !== null) {
189734
+ templates.push({ path: template, line: stage.line, column: stage.column });
189735
+ continue;
189736
+ }
189737
+ const name2 = text(get(stage, "stage"));
189738
+ if (name2 === null) continue;
189739
+ stages.push({ name: name2, label: cleanText(text(get(stage, "displayName")) ?? name2, name2), dependsOn: dependsOnOf2(stage), conditional: get(stage, "condition") !== void 0, jobs: jobsOf(get(stage, "jobs"), poolOf(stage, pool), templates), line: stage.line, column: stage.column });
189740
+ }
189741
+ return { stages, shape: "stages", templates };
189742
+ }
189743
+ if (get(root, "jobs") !== void 0) return { stages: [{ name: "", label: "", dependsOn: [], conditional: false, jobs: jobsOf(get(root, "jobs"), pool, templates), line: 1, column: 1 }], shape: "jobs", templates };
189744
+ const steps = stepsOf(get(root, "steps"), templates);
189745
+ const job = { name: "Job", label: "Job", dependsOn: null, conditional: false, environment: null, pool, runs: 1, matrix: false, steps, line: 1, column: 1 };
189746
+ return { stages: steps.length === 0 ? [] : [{ name: "", label: "", dependsOn: [], conditional: false, jobs: [job], line: 1, column: 1 }], shape: "steps", templates };
189747
+ }
189748
+ var MAX_STEP_LABEL;
189749
+ var init_pipeline = __esm({
189750
+ "../../packages/diagrams/src/importers/azure-pipelines/pipeline.ts"() {
189751
+ "use strict";
189752
+ init_shared2();
189753
+ init_yaml();
189754
+ MAX_STEP_LABEL = 48;
189755
+ }
189756
+ });
189757
+
189758
+ // ../../packages/diagrams/src/importers/azure-pipelines/index.ts
189759
+ function subOf(job, stage) {
189760
+ const parts = [
189761
+ job.conditional || stage.conditional ? "when its condition holds" : null,
189762
+ job.runs > 1 ? `${job.runs} ${job.matrix ? "matrix" : "parallel"} runs` : null,
189763
+ job.environment === null ? null : `deploys to ${job.environment}`,
189764
+ job.pool,
189765
+ job.steps.length === 0 ? null : countOf2(job.steps.length, "step")
189766
+ ].filter((part) => part !== null);
189767
+ return parts.length === 0 ? null : cleanText(parts.join(", "));
189768
+ }
189769
+ function readAzurePipelines(source, context) {
189770
+ const read = parseYaml(source);
189771
+ if (read.error !== null) {
189772
+ context.report.add(DIAGNOSTIC_CODES.importUnreadable, spanAt3(read.error.line, read.error.column), read.error.message, read.error.hint);
189773
+ return null;
189774
+ }
189775
+ const root = read.documents.find((document2) => document2.root?.kind === "map")?.root ?? null;
189776
+ const pipeline = root === null ? null : readPipeline(root);
189777
+ const firstTemplate = pipeline?.templates[0];
189778
+ if (pipeline !== null && firstTemplate !== void 0) {
189779
+ const paths = [...new Set(pipeline.templates.map((template) => cleanText(template.path)))];
189780
+ context.report.add(DIAGNOSTIC_CODES.importSkipped, spanAt3(firstTemplate.line, firstTemplate.column), `${countOf2(paths.length, "template")} ${paths.length === 1 ? "is" : "are"} not fetched, so what ${paths.length === 1 ? "it adds is" : "they add is"} not drawn: ${paths.slice(0, 3).join(", ")}.`, "Import the template files too, or draw this file knowing their stages, jobs, and steps are missing.");
189781
+ }
189782
+ if (pipeline === null || pipeline.stages.every((stage) => stage.jobs.length === 0)) {
189783
+ context.report.add(DIAGNOSTIC_CODES.importNoShape, START, "The file has no stages, jobs, or steps of its own.", "Send an azure-pipelines.yml with stages:, jobs:, or steps:.");
189784
+ return null;
189785
+ }
189786
+ const stages = pipeline.stages.filter((stage) => stage.jobs.length > 0);
189787
+ const nodes = [{ key: START_KEY, type: "terminal", label: "Pipeline starts" }];
189788
+ const groups = [];
189789
+ const edges = [];
189790
+ const unresolved = [];
189791
+ if (pipeline.shape === "steps") {
189792
+ const job = stages[0]?.jobs[0];
189793
+ job.steps.forEach((step, index2) => nodes.push({ key: `step:${index2}`, type: "step", label: step.label, idHint: `step_${index2 + 1}` }));
189794
+ job.steps.forEach((_step, index2) => edges.push({ from: index2 === 0 ? START_KEY : `step:${index2 - 1}`, to: `step:${index2}` }));
189795
+ return writeFlow(
189796
+ {
189797
+ title: context.title ?? "Azure pipeline",
189798
+ desc: `The ${countOf2(job.steps.length, "step")} of an Azure pipeline of one job${job.pool === null ? "" : ` on ${cleanText(job.pool)}`}, in order. Imported from Azure Pipelines.`,
189799
+ direction: "down",
189800
+ nodes,
189801
+ groups,
189802
+ edges,
189803
+ noun: "steps",
189804
+ coverage: { noun: "steps", total: job.steps.length, drawn: job.steps.length, summarised: 0 },
189805
+ advice: "Give each step a displayName, so the picture says what it does."
189806
+ },
189807
+ context.report
189808
+ );
189809
+ }
189810
+ const keyOf5 = (stage, job) => pipeline.shape === "jobs" ? `job:${job.name}` : `job:${stage.name}.${job.name}`;
189811
+ const stageNames = new Set(pipeline.stages.map((stage) => stage.name));
189812
+ for (const stage of stages) {
189813
+ if (pipeline.shape === "stages") groups.push({ key: `stage:${stage.name}`, label: stage.label, type: "ci-cd", idHint: `stage_${hintOf(stage.name)}` });
189814
+ for (const job of stage.jobs) nodes.push({ key: keyOf5(stage, job), type: "step", label: job.label, sub: subOf(job, stage), group: pipeline.shape === "stages" ? `stage:${stage.name}` : null, idHint: hintOf(job.name) });
189815
+ }
189816
+ const inStage = (stage, job) => (job.dependsOn ?? []).filter((name2) => stage.jobs.some((other) => other.name === name2));
189817
+ const firstJobs = (stage) => stage.jobs.filter((job) => inStage(stage, job).length === 0);
189818
+ const lastJobs = (stage) => stage.jobs.filter((job) => !stage.jobs.some((other) => inStage(stage, other).includes(job.name)));
189819
+ stages.forEach((stage, index2) => {
189820
+ const dashed = (job) => job.conditional || stage.conditional ? "-->" : void 0;
189821
+ const link2 = (from, job) => {
189822
+ const arrow = dashed(job);
189823
+ edges.push({ from, to: keyOf5(stage, job), ...arrow === void 0 ? {} : { arrow } });
189824
+ };
189825
+ const before = stage.dependsOn === null ? index2 === 0 ? [] : [stages[index2 - 1]] : stage.dependsOn.flatMap((name2) => {
189826
+ const found = stages.find((other) => other.name === name2);
189827
+ if (found === void 0 && !stageNames.has(name2)) unresolved.push({ name: `stage ${name2}`, line: stage.line, column: stage.column });
189828
+ return found === void 0 ? [] : [found];
189829
+ });
189830
+ for (const job of stage.jobs) {
189831
+ for (const name2 of job.dependsOn ?? []) {
189832
+ if (stage.jobs.some((other) => other.name === name2)) link2(keyOf5(stage, stage.jobs.find((other) => other.name === name2)), job);
189833
+ else unresolved.push({ name: `job ${name2}`, line: job.line, column: job.column });
189834
+ }
189835
+ }
189836
+ for (const job of firstJobs(stage)) {
189837
+ if (before.length === 0) link2(START_KEY, job);
189838
+ for (const previous of before) for (const last of lastJobs(previous)) link2(keyOf5(previous, last), job);
189839
+ }
189840
+ });
189841
+ const first = unresolved[0];
189842
+ if (first !== void 0) {
189843
+ context.report.add(DIAGNOSTIC_CODES.importUnresolved, spanAt3(first.line, first.column), `${countOf2(unresolved.length, "dependsOn")} name${unresolved.length === 1 ? "s" : ""} what the file does not hold, so no arrow is drawn for ${unresolved.length === 1 ? "it" : "them"}: ${unresolved.slice(0, 3).map((entry2) => cleanText(entry2.name)).join(", ")}.`, "Fix the name in dependsOn, or import the template that defines it.");
189844
+ }
189845
+ const jobs = stages.reduce((sum, stage) => sum + stage.jobs.length, 0);
189846
+ const deployments = stages.reduce((sum, stage) => sum + stage.jobs.filter((job) => job.environment !== null).length, 0);
189847
+ return writeFlow(
189848
+ {
189849
+ title: context.title ?? "Azure pipeline",
189850
+ desc: `${countOf2(jobs, "job")}${pipeline.shape === "stages" ? ` in ${countOf2(stages.length, "stage")}` : ""}${deployments === 0 ? "" : `, ${deployments} of which ${deployments === 1 ? "deploys" : "deploy"} to an environment`}, in the order dependsOn sets. A dashed arrow reaches a job that runs only when its condition holds. Imported from Azure Pipelines.`,
189851
+ direction: "down",
189852
+ nodes,
189853
+ groups,
189854
+ edges,
189855
+ noun: "jobs",
189856
+ capSpan: (key) => {
189857
+ const job = stages.flatMap((stage) => stage.jobs.map((entry2) => ({ key: keyOf5(stage, entry2), entry: entry2 }))).find((candidate2) => candidate2.key === key)?.entry;
189858
+ return spanAt3(job?.line ?? 1, job?.column ?? 1);
189859
+ },
189860
+ coverage: { noun: "jobs", total: jobs, drawn: jobs, summarised: 0 },
189861
+ advice: "Give each stage and job a displayName, so the picture says what it does."
189862
+ },
189863
+ context.report
189864
+ );
189865
+ }
189866
+ var hintOf, START_KEY, azurePipelinesImporter;
189867
+ var init_azure_pipelines = __esm({
189868
+ "../../packages/diagrams/src/importers/azure-pipelines/index.ts"() {
189869
+ "use strict";
189870
+ init_core();
189871
+ init_shared2();
189872
+ init_yaml();
189873
+ init_pipeline();
189874
+ hintOf = (name2) => name2.replace(/([a-z0-9])([A-Z][a-z])/g, "$1_$2").toLowerCase();
189875
+ START_KEY = "pipeline";
189876
+ azurePipelinesImporter = { format: "azure-pipelines", kinds: ["flow"], joins: null, extensions: [], read: readAzurePipelines };
189877
+ }
189878
+ });
189879
+
188818
189880
  // ../../packages/diagrams/src/importers/backstage/entities.ts
188819
- function refOf(written3, fallbackKind, namespace) {
189881
+ function refOf2(written3, fallbackKind, namespace) {
188820
189882
  const colon = written3.indexOf(":");
188821
189883
  const kind = colon === -1 ? fallbackKind : written3.slice(0, colon).toLowerCase();
188822
189884
  const rest = colon === -1 ? written3 : written3.slice(colon + 1);
@@ -188833,9 +189895,9 @@ function readEntity2(root) {
188833
189895
  const spec = get(root, "spec");
188834
189896
  const one = (key, fallback) => {
188835
189897
  const value = text(get(spec, key));
188836
- return value === null || value === "" ? null : refOf(value, fallback, namespace);
189898
+ return value === null || value === "" ? null : refOf2(value, fallback, namespace);
188837
189899
  };
188838
- const many = (key, fallback) => names5(get(spec, key)).map((value) => refOf(value, fallback, namespace));
189900
+ const many = (key, fallback) => names5(get(spec, key)).map((value) => refOf2(value, fallback, namespace));
188839
189901
  return {
188840
189902
  kind,
188841
189903
  ref: `${kind.toLowerCase()}:${namespace}/${name2}`,
@@ -188856,14 +189918,14 @@ function readEntity2(root) {
188856
189918
  column: root.column
188857
189919
  };
188858
189920
  }
188859
- var DRAWN_KINDS2, QUIET_KINDS2, nameOfRef;
189921
+ var DRAWN_KINDS2, QUIET_KINDS2, nameOfRef2;
188860
189922
  var init_entities = __esm({
188861
189923
  "../../packages/diagrams/src/importers/backstage/entities.ts"() {
188862
189924
  "use strict";
188863
189925
  init_yaml();
188864
189926
  DRAWN_KINDS2 = ["Domain", "System", "Component", "API", "Resource", "Group", "User"];
188865
189927
  QUIET_KINDS2 = /* @__PURE__ */ new Set(["Location"]);
188866
- nameOfRef = (ref) => ref.slice(ref.lastIndexOf("/") + 1);
189928
+ nameOfRef2 = (ref) => ref.slice(ref.lastIndexOf("/") + 1);
188867
189929
  }
188868
189930
  });
188869
189931
 
@@ -188877,7 +189939,7 @@ function brandOf(...texts) {
188877
189939
  }
188878
189940
  function boxOf3(entity, byOwner) {
188879
189941
  const type = (entity.type ?? "").toLowerCase();
188880
- const said = (words3) => cleanText([words3, entity.lifecycle === null || entity.lifecycle === "production" ? null : entity.lifecycle, byOwner || entity.owner === null ? null : `owned by ${nameOfRef(entity.owner)}`].filter((part) => part !== null).join(", "));
189942
+ const said = (words3) => cleanText([words3, entity.lifecycle === null || entity.lifecycle === "production" ? null : entity.lifecycle, byOwner || entity.owner === null ? null : `owned by ${nameOfRef2(entity.owner)}`].filter((part) => part !== null).join(", "));
188881
189943
  const label = entity.title ?? entity.name;
188882
189944
  if (entity.kind === "API") return { type: "gateway", label, sub: said(`${API_NAMES[type] ?? entity.type ?? ""} API`.trim()), icon: API_ICONS[type] ?? "lucide:plug", idHint: entity.name };
188883
189945
  if (entity.kind === "Resource") {
@@ -188920,37 +189982,37 @@ function readBackstage(textIn, context) {
188920
189982
  for (const group2 of entities.filter((entity) => entity.kind === "Group")) {
188921
189983
  const members2 = entities.filter((entity) => entity.kind === "User" && entity.memberOf.includes(group2.ref)).map((user) => user.title ?? user.name);
188922
189984
  const said = members2.length === 0 ? null : members2.length <= 2 ? members2.join(", ") : countOf2(members2.length, "member");
188923
- groups.push({ key: group2.ref, label: group2.title ?? group2.name, sub: cleanText(["Group", group2.type, said].filter((part) => part !== null).join(", ")), icon: "lucide:users", parent: group2.parent !== null && byRef.has(group2.parent) ? group2.parent : null, idHint: group2.name });
189985
+ groups.push({ key: group2.ref, label: group2.title ?? group2.name, sub: cleanText(["Group", group2.type, said].filter((part) => part !== null).join(", ")), type: "identity-boundary", icon: "lucide:users", parent: group2.parent !== null && byRef.has(group2.parent) ? group2.parent : null, idHint: group2.name });
188924
189986
  }
188925
189987
  for (const entity of boxes2) if (entity.owner !== null && byRef.get(entity.owner)?.kind === "Group") frameOf3.set(entity.ref, entity.owner);
188926
189988
  } else {
188927
- for (const domain2 of entities.filter((entity) => entity.kind === "Domain")) groups.push({ key: domain2.ref, label: domain2.title ?? domain2.name, sub: cleanText(`Domain${domain2.owner === null ? "" : `, owned by ${nameOfRef(domain2.owner)}`}`), icon: "lucide:map", idHint: domain2.name });
188928
- for (const system of entities.filter((entity) => entity.kind === "System")) groups.push({ key: system.ref, label: system.title ?? system.name, sub: cleanText(`System${system.owner === null ? "" : `, owned by ${nameOfRef(system.owner)}`}`), icon: "lucide:boxes", parent: system.domain !== null && byRef.has(system.domain) ? system.domain : null, idHint: system.name });
189989
+ for (const domain2 of entities.filter((entity) => entity.kind === "Domain")) groups.push({ key: domain2.ref, label: domain2.title ?? domain2.name, sub: cleanText(`Domain${domain2.owner === null ? "" : `, owned by ${nameOfRef2(domain2.owner)}`}`), type: "resource-group", tone: "blue", icon: "lucide:map", idHint: domain2.name });
189990
+ for (const system of entities.filter((entity) => entity.kind === "System")) groups.push({ key: system.ref, label: system.title ?? system.name, sub: cleanText(`System${system.owner === null ? "" : `, owned by ${nameOfRef2(system.owner)}`}`), type: "app-tier", icon: "lucide:boxes", parent: system.domain !== null && byRef.has(system.domain) ? system.domain : null, idHint: system.name });
188929
189991
  for (const entity of boxes2) if (entity.system !== null && byRef.get(entity.system)?.kind === "System") frameOf3.set(entity.ref, entity.system);
188930
189992
  }
188931
189993
  const order = { Component: 0, API: 1, Resource: 2 };
188932
189994
  const ordered = [...boxes2].sort((a, b) => order[a.kind] - order[b.kind]);
188933
189995
  const nodes = ordered.map((entity) => {
188934
189996
  const box = boxOf3(entity, byOwner);
188935
- const system = byOwner && entity.system !== null ? `, in ${nameOfRef(entity.system)}` : "";
189997
+ const system = byOwner && entity.system !== null ? `, in ${nameOfRef2(entity.system)}` : "";
188936
189998
  return { key: entity.ref, ...box, sub: box.sub === null || box.sub === void 0 ? null : cleanText(`${box.sub}${system}`), group: frameOf3.get(entity.ref) ?? null };
188937
189999
  });
188938
190000
  const drawn = new Set(nodes.map((node3) => node3.key));
188939
190001
  const edges = [];
188940
190002
  const unknown2 = /* @__PURE__ */ new Set();
188941
- const link2 = (from, to, label, arrow) => {
190003
+ const link2 = (from, to, label, verb, arrow) => {
188942
190004
  if (!drawn.has(to)) {
188943
190005
  if (!byRef.has(to)) unknown2.add(to);
188944
190006
  return;
188945
190007
  }
188946
- edges.push({ from, to, label, ...arrow === void 0 ? {} : { arrow } });
190008
+ edges.push({ from, to, label, verb, ...arrow === void 0 ? {} : { arrow } });
188947
190009
  };
188948
190010
  for (const entity of ordered) {
188949
- for (const api of entity.providesApis) link2(entity.ref, api, "provides");
188950
- for (const api of entity.consumesApis) link2(entity.ref, api, "consumes");
188951
- for (const target of entity.dependsOn) link2(entity.ref, target, "depends on");
188952
- for (const source of entity.dependencyOf) if (drawn.has(source)) link2(source, entity.ref, "depends on");
188953
- if (entity.kind === "Component" && entity.parent !== null) link2(entity.ref, entity.parent, "part of", "-->");
190011
+ for (const api of entity.providesApis) link2(entity.ref, api, "provides", "provides");
190012
+ for (const api of entity.consumesApis) link2(entity.ref, api, "consumes", "consumes");
190013
+ for (const target of entity.dependsOn) link2(entity.ref, target, "depends on", "depends-on");
190014
+ for (const source of entity.dependencyOf) if (drawn.has(source)) link2(source, entity.ref, "depends on", "depends-on");
190015
+ if (entity.kind === "Component" && entity.parent !== null) link2(entity.ref, entity.parent, "part of", null, "-->");
188954
190016
  }
188955
190017
  if (unknown2.size > 0) {
188956
190018
  context.report.add(DIAGNOSTIC_CODES.importUnresolved, START, `${countOf2(unknown2.size, "relation")} name${unknown2.size === 1 ? "s an entity" : " entities"} the files do not describe, so no arrow is drawn: ${[...unknown2].slice(0, 3).join(", ")}.`, "Send the catalog-info.yaml files that describe them, or check that each reference names its kind, such as resource:orders-db.");
@@ -189225,14 +190287,14 @@ function templateItems(template, offsetOf) {
189225
190287
  }
189226
190288
  return { items: items2, empty };
189227
190289
  }
189228
- var MAX_DEPTH13, PSEUDO, entryOf, scalarOf, itemsOf, subNames, Template;
190290
+ var MAX_DEPTH14, PSEUDO, entryOf, scalarOf, itemsOf, subNames, Template;
189229
190291
  var init_template = __esm({
189230
190292
  "../../packages/diagrams/src/importers/cloudformation/template.ts"() {
189231
190293
  "use strict";
189232
190294
  init_attributes();
189233
190295
  init_type_names();
189234
190296
  init_value();
189235
- MAX_DEPTH13 = 16;
190297
+ MAX_DEPTH14 = 16;
189236
190298
  PSEUDO = {
189237
190299
  "AWS::Region": "region",
189238
190300
  "AWS::AccountId": "account",
@@ -189264,7 +190326,7 @@ var init_template = __esm({
189264
190326
  conditionMemo = /* @__PURE__ */ new Map();
189265
190327
  /** The value of a node as far as the template's defaults give it. */
189266
190328
  evaluate(node3, depth = 0) {
189267
- if (node3 === void 0 || depth > MAX_DEPTH13) return new Unknown("value");
190329
+ if (node3 === void 0 || depth > MAX_DEPTH14) return new Unknown("value");
189268
190330
  const intrinsic = intrinsicOf(node3);
189269
190331
  if (intrinsic !== null) return this.call(intrinsic, depth);
189270
190332
  if (node3.kind === "scalar") return node3.isNull ? null : node3.value;
@@ -189372,7 +190434,7 @@ var init_template = __esm({
189372
190434
  }
189373
190435
  /** The resources a node names: `Ref`, `GetAtt`, and `${ }` of `Sub`, and only the branch an `If` takes when its condition is known. */
189374
190436
  refs(node3, depth = 0) {
189375
- if (node3 === void 0 || depth > MAX_DEPTH13) return [];
190437
+ if (node3 === void 0 || depth > MAX_DEPTH14) return [];
189376
190438
  const intrinsic = intrinsicOf(node3);
189377
190439
  if (intrinsic !== null) {
189378
190440
  const argument = intrinsic.argument;
@@ -189409,7 +190471,7 @@ var init_template = __esm({
189409
190471
  }
189410
190472
  /** Every reference a node holds, by the keys that lead to it. */
189411
190473
  refsByPath(node3, path, out, depth = 0) {
189412
- if (node3 === void 0 || depth > MAX_DEPTH13) return;
190474
+ if (node3 === void 0 || depth > MAX_DEPTH14) return;
189413
190475
  if (intrinsicOf(node3) === null && node3.kind === "map") {
189414
190476
  for (const entry2 of node3.entries) this.refsByPath(entry2.value, [...path, entry2.key], out, depth + 1);
189415
190477
  return;
@@ -189426,17 +190488,17 @@ var init_template = __esm({
189426
190488
  });
189427
190489
 
189428
190490
  // ../../packages/diagrams/src/importers/cloudformation/index.ts
189429
- function nodeOf2(value, depth = 0) {
190491
+ function nodeOf3(value, depth = 0) {
189430
190492
  const at3 = { line: 1, column: 1 };
189431
190493
  if (depth > 64 || value === null || value === void 0) return { kind: "scalar", value: "", quoted: false, isNull: true, ...at3 };
189432
- if (Array.isArray(value)) return { kind: "seq", items: value.map((item) => nodeOf2(item, depth + 1)), ...at3 };
189433
- if (typeof value === "object") return { kind: "map", entries: Object.entries(value).map(([key, entry2]) => ({ key, value: nodeOf2(entry2, depth + 1), ...at3 })), ...at3 };
190494
+ if (Array.isArray(value)) return { kind: "seq", items: value.map((item) => nodeOf3(item, depth + 1)), ...at3 };
190495
+ if (typeof value === "object") return { kind: "map", entries: Object.entries(value).map(([key, entry2]) => ({ key, value: nodeOf3(entry2, depth + 1), ...at3 })), ...at3 };
189434
190496
  return { kind: "scalar", value: String(value), quoted: typeof value === "string", isNull: false, ...at3 };
189435
190497
  }
189436
190498
  function readFile(text3) {
189437
190499
  if (/^\s*\{/.test(text3)) {
189438
190500
  try {
189439
- return { root: nodeOf2(JSON.parse(text3)), problem: null };
190501
+ return { root: nodeOf3(JSON.parse(text3)), problem: null };
189440
190502
  } catch (error62) {
189441
190503
  return { root: null, problem: { line: 1, column: 1, message: `The template is not JSON: ${error62 instanceof Error ? error62.message : "it cannot be read"}.`, hint: "Send the template as CloudFormation writes it, in JSON or YAML." } };
189442
190504
  }
@@ -189669,7 +190731,7 @@ function readable(segment, stack) {
189669
190731
  const at3 = stack.length >= 3 ? own2.indexOf(stack) : -1;
189670
190732
  return (at3 > 0 ? own2.slice(0, at3) : own2) || segment;
189671
190733
  }
189672
- function frameModules2(tree, classes, startOf3) {
190734
+ function frameModules2(tree, classes, startOf4) {
189673
190735
  const scopes = /* @__PURE__ */ new Map();
189674
190736
  const used = /* @__PURE__ */ new Set();
189675
190737
  const modules = [];
@@ -189684,7 +190746,7 @@ function frameModules2(tree, classes, startOf3) {
189684
190746
  const kind = tree.frames.get(path) ?? "Construct";
189685
190747
  const fqn = classes.get(path) ?? "";
189686
190748
  const own2 = /^(?:aws-cdk-lib|@aws-cdk\/[^.]+)\./.test(fqn) && !/\.(?:Stack|NestedStack|Stage)$/.test(fqn) ? fqn.split(".").pop() ?? "" : "";
189687
- modules.push({ scope, name: path, label: id, instance: null, source: own2, start: startOf3(path), noun: kind });
190749
+ modules.push({ scope, name: path, label: id, instance: null, source: own2, start: startOf4(path), noun: kind });
189688
190750
  }
189689
190751
  return { modules, scopes };
189690
190752
  }
@@ -189849,7 +190911,7 @@ function readCdk(joined2, context) {
189849
190911
  }
189850
190912
  let hops = 0;
189851
190913
  const refsOf = (read, node3) => {
189852
- if (node3 === void 0 || hops >= MAX_HOPS) return [];
190914
+ if (node3 === void 0 || hops >= MAX_HOPS2) return [];
189853
190915
  hops += 1;
189854
190916
  try {
189855
190917
  return read.model.refs(node3).map((ref) => read.names.get(ref) ?? ref);
@@ -189915,12 +190977,12 @@ function readCdk(joined2, context) {
189915
190977
  const maker = makers.get(item.name) ?? null;
189916
190978
  if ((labelCount.get(item.label) ?? 0) > 1 && maker !== null && !item.label.startsWith(`${maker}_`)) items2[position] = { ...item, label: `${maker}_${item.label}` };
189917
190979
  }
189918
- const startOf3 = (path) => {
190980
+ const startOf4 = (path) => {
189919
190981
  const owner = reads.find((read) => read.template.stackPath === path) ?? reads.find((read) => read.template.topPath === path.split("/")[0]);
189920
190982
  const at3 = owner === void 0 ? -1 : owner.template.file.text.indexOf(`"${path}/`);
189921
190983
  return owner === void 0 ? 0 : owner.template.file.start + Math.max(0, at3);
189922
190984
  };
189923
- const { modules, scopes } = frameModules2(tree, assembly.classes, startOf3);
190985
+ const { modules, scopes } = frameModules2(tree, assembly.classes, startOf4);
189924
190986
  const scoped = items2.map((item) => {
189925
190987
  const frames = placements.get(item.name) ?? [];
189926
190988
  const innermost = frames[frames.length - 1];
@@ -189950,7 +191012,7 @@ function readCdk(joined2, context) {
189950
191012
  framing: "construct"
189951
191013
  });
189952
191014
  }
189953
- var LISTED9, MAX_HOPS, METADATA_TYPE, withoutHash, cdkImporter;
191015
+ var LISTED9, MAX_HOPS2, METADATA_TYPE, withoutHash, cdkImporter;
189954
191016
  var init_cdk = __esm({
189955
191017
  "../../packages/diagrams/src/importers/cdk/index.ts"() {
189956
191018
  "use strict";
@@ -189964,7 +191026,7 @@ var init_cdk = __esm({
189964
191026
  init_assembly();
189965
191027
  init_constructs();
189966
191028
  LISTED9 = 3;
189967
- MAX_HOPS = 8;
191029
+ MAX_HOPS2 = 8;
189968
191030
  METADATA_TYPE = "AWS::CDK::Metadata";
189969
191031
  withoutHash = (logical) => logical.replace(/[0-9A-F]{8}$/, "") || logical;
189970
191032
  cdkImporter = { format: "cdk", kinds: ["flow"], joins: "\n", extensions: [".json", ".yaml", ".yml", ".out"], read: readCdk };
@@ -190119,17 +191181,17 @@ var init_services2 = __esm({
190119
191181
  });
190120
191182
 
190121
191183
  // ../../packages/diagrams/src/importers/compose/index.ts
190122
- function subOf(service) {
191184
+ function subOf2(service) {
190123
191185
  const runs = service.image ?? (service.build === null ? null : `build ${service.build}`);
190124
191186
  const ports = service.ports.length === 0 ? null : `${service.ports.length === 1 ? "port" : "ports"} ${service.ports.join(", ")}`;
190125
191187
  const replicas = service.replicas === null || service.replicas === "1" ? null : `${service.replicas} replicas`;
190126
191188
  const parts = [runs, ports, replicas].filter((part) => part !== null);
190127
191189
  return parts.length === 0 ? null : parts.join(", ");
190128
191190
  }
190129
- function nodeOf3(service, group2) {
191191
+ function nodeOf4(service, group2) {
190130
191192
  const look = (service.image === null ? null : imageLook(service.image)) ?? imageLook(service.name);
190131
191193
  const type = look?.type ?? "service";
190132
- return { key: `service:${service.name}`, type, label: service.name, sub: subOf(service), icon: look?.icon ?? iconForName(service.name, type), group: group2, idHint: service.name };
191194
+ return { key: `service:${service.name}`, type, label: service.name, sub: subOf2(service), icon: look?.icon ?? iconForName(service.name, type), group: group2, idHint: service.name };
190133
191195
  }
190134
191196
  function groupsOf(services, networks) {
190135
191197
  const home = /* @__PURE__ */ new Map();
@@ -190160,7 +191222,7 @@ function edgesOf2(services, volumes) {
190160
191222
  for (const other of services) {
190161
191223
  if (other.name !== service.name && service.environment.some((value) => namesHost2(value, other.name))) edges.push({ from: `service:${service.name}`, to: `service:${other.name}` });
190162
191224
  }
190163
- for (const volume of service.volumes) if (volumes.has(volume)) edges.push({ from: `service:${service.name}`, to: `volume:${volume}`, label: "mounts" });
191225
+ for (const volume of service.volumes) if (volumes.has(volume)) edges.push({ from: `service:${service.name}`, to: `volume:${volume}`, label: "mounts", verb: "mounts" });
190164
191226
  }
190165
191227
  return edges;
190166
191228
  }
@@ -190187,7 +191249,7 @@ function readCompose(source, context) {
190187
191249
  const mounted = new Set(services.flatMap((service) => service.volumes));
190188
191250
  const volumes = declared(root, "volumes").filter((volume) => mounted.has(volume));
190189
191251
  const nodes = [
190190
- ...services.map((service) => nodeOf3(service, home.get(service.name) ?? null)),
191252
+ ...services.map((service) => nodeOf4(service, home.get(service.name) ?? null)),
190191
191253
  ...volumes.map((volume) => ({ key: `volume:${volume}`, type: "storage", label: volume, sub: "Named volume", idHint: `${volume}_volume` }))
190192
191254
  ];
190193
191255
  const name2 = text(get(root, "name"));
@@ -191876,12 +192938,12 @@ function helmFrames(manifests) {
191876
192938
  const release = releaseOf(manifest);
191877
192939
  const chart = chartLabel(manifest);
191878
192940
  const partOf2 = manifest.meta.get("app.kubernetes.io/part-of");
191879
- if (partOf2 !== void 0 && partOf2 !== "" && partOf2 !== release) frames.push({ key: `part-of:${partOf2}`, label: partOf2, sub: "Application", icon: "lucide:boxes", idHint: partOf2 });
192941
+ if (partOf2 !== void 0 && partOf2 !== "" && partOf2 !== release) frames.push({ key: `part-of:${partOf2}`, label: partOf2, sub: "Application", icon: "lucide:boxes", type: "app-tier", idHint: partOf2 });
191880
192942
  if (release === null) return frames;
191881
192943
  const several = (charts.get(release)?.size ?? 0) > 1;
191882
192944
  const only = several || chart === null ? null : chart;
191883
- frames.push({ key: `release:${release}`, label: release, sub: cleanText(only === null ? "Release" : `Release of ${only.name}${only.version === null ? "" : ` ${only.version}`}`), icon: "simple:helm", idHint: release });
191884
- if (several && chart !== null) frames.push({ key: `chart:${release}/${chart.name}`, label: chart.name, sub: cleanText(`Chart${chart.version === null ? "" : ` ${chart.version}`}`), icon: "lucide:package", idHint: chart.name });
192945
+ frames.push({ key: `release:${release}`, label: release, sub: cleanText(only === null ? "Release" : `Release of ${only.name}${only.version === null ? "" : ` ${only.version}`}`), icon: "simple:helm", type: "resource-group", tone: "blue", idHint: release });
192946
+ if (several && chart !== null) frames.push({ key: `chart:${release}/${chart.name}`, label: chart.name, sub: cleanText(`Chart${chart.version === null ? "" : ` ${chart.version}`}`), icon: "lucide:package", type: "resource-group", idHint: chart.name });
191885
192947
  return frames;
191886
192948
  };
191887
192949
  }
@@ -191942,8 +193004,8 @@ function actionLabel(uses) {
191942
193004
  }
191943
193005
  function commandLabel(run3) {
191944
193006
  const first = run3.split("\n").map((line3) => line3.trim()).find((line3) => line3 !== "" && !line3.startsWith("#")) ?? "Run a script";
191945
- if (first.length <= MAX_STEP_LABEL) return first;
191946
- const cut = first.slice(0, MAX_STEP_LABEL);
193007
+ if (first.length <= MAX_STEP_LABEL2) return first;
193008
+ const cut = first.slice(0, MAX_STEP_LABEL2);
191947
193009
  const space = cut.lastIndexOf(" ");
191948
193010
  return space > 12 ? cut.slice(0, space) : cut;
191949
193011
  }
@@ -192015,13 +193077,13 @@ function triggerOf(root) {
192015
193077
  const short3 = `${words2.map((word2) => word2.short).join(" or ")}${more}`;
192016
193078
  return { label: cleanText(`${short3.charAt(0).toUpperCase()}${short3.slice(1)}`), sentence: cleanText(`${words2.map((word2) => word2.long).join(" or ")}${more}`) };
192017
193079
  }
192018
- var MAX_STEP_LABEL, ACTION_ICONS, COMMAND_ICONS;
193080
+ var MAX_STEP_LABEL2, ACTION_ICONS, COMMAND_ICONS;
192019
193081
  var init_workflow = __esm({
192020
193082
  "../../packages/diagrams/src/importers/github-actions/workflow.ts"() {
192021
193083
  "use strict";
192022
193084
  init_shared2();
192023
193085
  init_yaml();
192024
- MAX_STEP_LABEL = 48;
193086
+ MAX_STEP_LABEL2 = 48;
192025
193087
  ACTION_ICONS = {
192026
193088
  "actions/checkout": "lucide:git-branch",
192027
193089
  "actions/setup-node": "simple:nodedotjs",
@@ -192095,7 +193157,7 @@ function stepBoxes(jobs) {
192095
193157
  last.set(job.id, keys[keys.length - 1]);
192096
193158
  }
192097
193159
  for (const job of jobs) for (const need of job.needs) edges.push({ from: last.get(need) ?? "", to: first.get(job.id) ?? "" });
192098
- const groups = jobs.map((job) => ({ key: `job:${job.id}`, label: job.label, sub: job.detail, idHint: `job_${job.id}` }));
193160
+ const groups = jobs.map((job) => ({ key: `job:${job.id}`, label: job.label, sub: job.detail, otherwise: "ci-cd", idHint: `job_${job.id}` }));
192099
193161
  return { nodes, edges, groups };
192100
193162
  }
192101
193163
  function readWorkflow(source, context) {
@@ -192111,31 +193173,31 @@ function readWorkflow(source, context) {
192111
193173
  return null;
192112
193174
  }
192113
193175
  const ids = new Set(jobs.map((job) => job.id));
192114
- const resolved = jobs.map((job) => {
193176
+ const resolved2 = jobs.map((job) => {
192115
193177
  const missing = job.needs.filter((need) => !ids.has(need));
192116
193178
  for (const need of missing) context.report.add(DIAGNOSTIC_CODES.importUnresolved, spanAt3(job.line, job.column, job.id.length), `Job ${job.id} needs ${need}, which is not a job of this workflow.`, "Fix the name in needs:, or add the job.");
192117
193179
  return missing.length === 0 ? job : { ...job, needs: job.needs.filter((need) => ids.has(need)) };
192118
193180
  });
192119
- const expanded = fitsSteps(resolved);
192120
- const drawn = expanded ? stepBoxes(resolved) : { ...jobBoxes(resolved), groups: [] };
193181
+ const expanded = fitsSteps(resolved2);
193182
+ const drawn = expanded ? stepBoxes(resolved2) : { ...jobBoxes(resolved2), groups: [] };
192121
193183
  const trigger = triggerOf(root);
192122
193184
  const firstOf = (job) => expanded ? job.steps.length === 0 ? `job:${job.id}` : `step:${job.id}:0` : `job:${job.id}`;
192123
- const starts = resolved.filter((job) => job.needs.length === 0).map((job) => ({ from: TRIGGER, to: firstOf(job) }));
193185
+ const starts = resolved2.filter((job) => job.needs.length === 0).map((job) => ({ from: TRIGGER, to: firstOf(job) }));
192124
193186
  const name2 = text(get(root, "name"));
192125
- const depth = depthOf5(resolved);
192126
- const steps = resolved.reduce((sum, job) => sum + job.steps.length, 0);
193187
+ const depth = depthOf5(resolved2);
193188
+ const steps = resolved2.reduce((sum, job) => sum + job.steps.length, 0);
192127
193189
  const coverage = expanded ? { noun: "steps", total: drawn.nodes.length, drawn: drawn.nodes.length, summarised: 0 } : { noun: "steps", total: steps, drawn: 0, summarised: steps };
192128
193190
  return writeFlow(
192129
193191
  {
192130
193192
  title: context.title ?? name2 ?? "GitHub Actions workflow",
192131
- desc: `${countOf2(resolved.length, "job")}${expanded ? " and their steps" : ""} of ${name2 === null ? "a GitHub Actions workflow" : `the ${name2} workflow`}, started by ${trigger === null ? "its trigger" : trigger.sentence}, in the order their needs set, imported from GitHub Actions.`,
193193
+ desc: `${countOf2(resolved2.length, "job")}${expanded ? " and their steps" : ""} of ${name2 === null ? "a GitHub Actions workflow" : `the ${name2} workflow`}, started by ${trigger === null ? "its trigger" : trigger.sentence}, in the order their needs set, imported from GitHub Actions.`,
192132
193194
  direction: expanded || depth > 4 ? "down" : "right",
192133
193195
  nodes: [{ key: TRIGGER, type: "terminal", label: trigger?.label ?? "Workflow starts" }, ...drawn.nodes],
192134
193196
  groups: drawn.groups,
192135
193197
  edges: [...starts, ...drawn.edges],
192136
193198
  noun: expanded ? "steps" : "jobs",
192137
193199
  capSpan: (key) => {
192138
- const job = resolved.find((candidate2) => key === `job:${candidate2.id}`);
193200
+ const job = resolved2.find((candidate2) => key === `job:${candidate2.id}`);
192139
193201
  return spanAt3(job?.line ?? 1, job?.column ?? 1);
192140
193202
  },
192141
193203
  coverage,
@@ -192159,6 +193221,213 @@ var init_github_actions = __esm({
192159
193221
  }
192160
193222
  });
192161
193223
 
193224
+ // ../../packages/diagrams/src/importers/gitlab-ci/pipeline.ts
193225
+ function resolved(name2, byName, missing, depth = 0) {
193226
+ const own2 = byName.get(name2);
193227
+ const out = /* @__PURE__ */ new Map();
193228
+ if (own2 === void 0 || depth > MAX_EXTENDS) return out;
193229
+ for (const parent of names5(get(own2, "extends"))) {
193230
+ if (!byName.has(parent)) {
193231
+ if (!missing.includes(parent)) missing.push(parent);
193232
+ continue;
193233
+ }
193234
+ for (const [key, value] of resolved(parent, byName, missing, depth + 1)) out.set(key, value);
193235
+ }
193236
+ for (const entry2 of entries(own2)) if (entry2.key !== "extends") out.set(entry2.key, entry2.value);
193237
+ return out;
193238
+ }
193239
+ function runsOf5(parallel) {
193240
+ const count3 = Number(text(parallel));
193241
+ if (Number.isInteger(count3) && count3 > 1) return { runs: count3, matrix: false };
193242
+ const matrix = items(get(parallel, "matrix"));
193243
+ if (matrix.length === 0) return { runs: 1, matrix: false };
193244
+ const runs = matrix.reduce((sum, entry2) => sum + entries(entry2).reduce((product, variable) => product * Math.max(1, items(variable.value).length), 1), 0);
193245
+ return { runs, matrix: true };
193246
+ }
193247
+ function startOf3(keys) {
193248
+ const when = text(keys.get("when"));
193249
+ if (when === "manual") return "manual";
193250
+ if (when === "delayed") return "delayed";
193251
+ if (when === "on_failure") return "on_failure";
193252
+ const rules = items(keys.get("rules"));
193253
+ if (rules.some((rule) => text(get(rule, "when")) === "manual")) return "manual";
193254
+ if (rules.length > 0) return "rules";
193255
+ if (keys.has("only") || keys.has("except")) return "only";
193256
+ return "always";
193257
+ }
193258
+ function triggerOf2(node3) {
193259
+ if (node3 === void 0) return null;
193260
+ const project2 = text(node3) ?? text(get(node3, "project"));
193261
+ if (project2 !== null) return { label: project2, child: false };
193262
+ const include = get(node3, "include");
193263
+ const file2 = text(include) ?? items(include).map((entry2) => text(entry2) ?? text(get(entry2, "local")) ?? text(get(entry2, "file")) ?? text(get(entry2, "template")) ?? text(get(entry2, "artifact"))).find((value) => value !== null) ?? null;
193264
+ return { label: file2 ?? "Child pipeline", child: true };
193265
+ }
193266
+ function includesOf(root) {
193267
+ const include = get(root, "include");
193268
+ return items(include).map((entry2) => {
193269
+ const said = text(entry2) ?? (text(get(entry2, "project")) === null ? null : `${text(get(entry2, "project"))}: ${names5(get(entry2, "file")).join(", ")}`) ?? text(get(entry2, "local")) ?? text(get(entry2, "remote")) ?? text(get(entry2, "template")) ?? text(get(entry2, "component")) ?? "an include";
193270
+ return { said: cleanText(said), line: entry2.line, column: entry2.column };
193271
+ });
193272
+ }
193273
+ function readPipeline2(root) {
193274
+ const byName = /* @__PURE__ */ new Map();
193275
+ for (const entry2 of entries(root)) if (entry2.value.kind === "map") byName.set(entry2.key, entry2.value);
193276
+ const written3 = names5(get(root, "stages"));
193277
+ const defaults = get(root, "default");
193278
+ const fallbackImage = text(get(defaults, "image")) ?? text(get(get(defaults, "image"), "name")) ?? text(get(root, "image")) ?? text(get(get(root, "image"), "name"));
193279
+ const jobs = [];
193280
+ for (const entry2 of entries(root)) {
193281
+ if (entry2.key.startsWith(".") || GLOBAL_KEYS.has(entry2.key) || entry2.value.kind !== "map") continue;
193282
+ const missing = [];
193283
+ const keys = resolved(entry2.key, byName, missing);
193284
+ const needsNode = keys.get("needs");
193285
+ const needs2 = needsNode === void 0 ? null : items(needsNode).map((need) => text(need) ?? (get(need, "pipeline") === void 0 && get(need, "project") === void 0 ? text(get(need, "job")) : null)).filter((need) => need !== null);
193286
+ const image = text(keys.get("image")) ?? text(get(keys.get("image"), "name")) ?? fallbackImage;
193287
+ const environment = text(keys.get("environment")) ?? text(get(keys.get("environment"), "name"));
193288
+ const allow = keys.get("allow_failure");
193289
+ jobs.push({
193290
+ name: entry2.key,
193291
+ stage: text(keys.get("stage")) ?? "test",
193292
+ needs: needs2,
193293
+ start: startOf3(keys),
193294
+ image: image === null ? null : image.split("/").pop() ?? image,
193295
+ ...runsOf5(keys.get("parallel")),
193296
+ environment,
193297
+ trigger: triggerOf2(keys.get("trigger")),
193298
+ allowFailure: text(allow) === "true" || allow?.kind === "map",
193299
+ missingExtends: missing,
193300
+ line: entry2.line,
193301
+ column: entry2.column
193302
+ });
193303
+ }
193304
+ return { stages: written3.length > 0 ? written3 : [...DEFAULT_STAGES], stagesWritten: written3.length > 0, jobs, includes: includesOf(root) };
193305
+ }
193306
+ var DEFAULT_STAGES, GLOBAL_KEYS, MAX_EXTENDS;
193307
+ var init_pipeline2 = __esm({
193308
+ "../../packages/diagrams/src/importers/gitlab-ci/pipeline.ts"() {
193309
+ "use strict";
193310
+ init_shared2();
193311
+ init_yaml();
193312
+ DEFAULT_STAGES = [".pre", "build", "test", "deploy", ".post"];
193313
+ GLOBAL_KEYS = /* @__PURE__ */ new Set(["default", "include", "stages", "variables", "workflow", "image", "services", "cache", "before_script", "after_script", "types", "spec"]);
193314
+ MAX_EXTENDS = 11;
193315
+ }
193316
+ });
193317
+
193318
+ // ../../packages/diagrams/src/importers/gitlab-ci/index.ts
193319
+ function subOf3(job) {
193320
+ const parts = [
193321
+ START_WORDS[job.start],
193322
+ job.runs > 1 ? `${job.runs} ${job.matrix ? "matrix" : "parallel"} runs` : null,
193323
+ job.environment === null ? null : `deploys to ${job.environment}`,
193324
+ job.image,
193325
+ job.allowFailure ? "may fail" : null
193326
+ ].filter((part) => part !== null);
193327
+ return parts.length === 0 ? null : cleanText(parts.join(", "));
193328
+ }
193329
+ function readGitlabCi(source, context) {
193330
+ const read = parseYaml(source);
193331
+ if (read.error !== null) {
193332
+ context.report.add(DIAGNOSTIC_CODES.importUnreadable, spanAt3(read.error.line, read.error.column), read.error.message, read.error.hint);
193333
+ return null;
193334
+ }
193335
+ const root = [...read.documents].reverse().find((document2) => document2.root?.kind === "map")?.root ?? null;
193336
+ const pipeline = root === null ? null : readPipeline2(root);
193337
+ if (pipeline === null || pipeline.jobs.length === 0) {
193338
+ const includes = pipeline?.includes.length ?? 0;
193339
+ context.report.add(DIAGNOSTIC_CODES.importNoShape, START, includes > 0 ? "The file has no jobs of its own; they come from what it includes, which is not fetched." : "The file has no jobs.", includes > 0 ? "Import the included files, or a .gitlab-ci.yml with jobs." : "Send a .gitlab-ci.yml with at least one job, a key with a script or a trigger.");
193340
+ return null;
193341
+ }
193342
+ const firstInclude = pipeline.includes[0];
193343
+ if (firstInclude !== void 0) {
193344
+ context.report.add(DIAGNOSTIC_CODES.importSkipped, spanAt3(firstInclude.line, firstInclude.column), `${countOf2(pipeline.includes.length, "include")} ${pipeline.includes.length === 1 ? "is" : "are"} not fetched, so the jobs ${pipeline.includes.length === 1 ? "it adds are" : "they add are"} not drawn: ${pipeline.includes.slice(0, 3).map((include) => include.said).join(", ")}.`, "Import the included files too, or draw this file alone knowing those jobs are missing.");
193345
+ }
193346
+ const names8 = new Set(pipeline.jobs.map((job) => job.name));
193347
+ const missingExtends = pipeline.jobs.filter((job) => job.missingExtends.length > 0);
193348
+ const firstMissing = missingExtends[0];
193349
+ if (firstMissing !== void 0) {
193350
+ const parents = [...new Set(missingExtends.flatMap((job) => job.missingExtends))];
193351
+ context.report.add(DIAGNOSTIC_CODES.importUnresolved, spanAt3(firstMissing.line, firstMissing.column, firstMissing.name.length), `${countOf2(missingExtends.length, "job")} extend${missingExtends.length === 1 ? "s" : ""} ${parents.length === 1 ? "a job" : "jobs"} the file does not hold, so what ${parents.length === 1 ? "it sets is" : "they set is"} not read: ${parents.slice(0, 3).map((name2) => cleanText(name2)).join(", ")}.`, "They often come from an include; import the file that defines them with this one.");
193352
+ }
193353
+ const stages = pipeline.stages.filter((stage) => pipeline.jobs.some((job) => job.stage === stage));
193354
+ const unknown2 = [...new Set(pipeline.jobs.map((job) => job.stage).filter((stage) => !pipeline.stages.includes(stage)))];
193355
+ const firstUnknown = pipeline.jobs.find((job) => unknown2.includes(job.stage));
193356
+ if (firstUnknown !== void 0) {
193357
+ context.report.add(DIAGNOSTIC_CODES.importUnresolved, spanAt3(firstUnknown.line, firstUnknown.column, firstUnknown.name.length), `${countOf2(unknown2.length, "stage")} ${unknown2.length === 1 ? "is" : "are"} used by jobs but not listed in stages, so ${unknown2.length === 1 ? "it is" : "they are"} drawn last: ${unknown2.slice(0, 3).map((stage) => cleanText(stage)).join(", ")}.`, "List every stage in stages:, in the order they run.");
193358
+ }
193359
+ const ordered = [...stages, ...unknown2];
193360
+ const jobsOf2 = (stage) => pipeline.jobs.filter((job) => job.stage === stage);
193361
+ const nodes = [{ key: START_KEY2, type: "terminal", label: "Pipeline starts" }];
193362
+ const groups = [];
193363
+ const edges = [];
193364
+ const unresolvedNeeds = [];
193365
+ for (const stage of ordered) {
193366
+ groups.push({ key: `stage:${stage}`, label: cleanText(stage), type: "ci-cd", idHint: `stage_${stage.replace(/^\./, "")}` });
193367
+ for (const job of jobsOf2(stage)) {
193368
+ nodes.push({ key: `job:${job.name}`, type: "step", label: cleanText(job.name), sub: subOf3(job), icon: job.image === null ? null : imageLook(job.image)?.icon ?? null, group: `stage:${stage}`, idHint: job.name });
193369
+ if (job.trigger !== null) {
193370
+ nodes.push({ key: `trigger:${job.name}`, type: "external", label: cleanText(job.trigger.label), sub: job.trigger.child ? "Child pipeline" : "Downstream pipeline", icon: "simple:gitlab", idHint: `${job.name}_pipeline` });
193371
+ edges.push({ from: `job:${job.name}`, to: `trigger:${job.name}`, label: "triggers", verb: "triggers" });
193372
+ }
193373
+ }
193374
+ }
193375
+ for (const job of pipeline.jobs) {
193376
+ const arrow = job.start === "always" ? void 0 : "-->";
193377
+ const into = (from) => {
193378
+ edges.push({ from, to: `job:${job.name}`, ...arrow === void 0 ? {} : { arrow } });
193379
+ };
193380
+ if (job.needs !== null) {
193381
+ for (const need of job.needs) {
193382
+ if (names8.has(need)) into(`job:${need}`);
193383
+ else unresolvedNeeds.push({ job, need });
193384
+ }
193385
+ if (job.needs.filter((need) => names8.has(need)).length === 0) into(START_KEY2);
193386
+ continue;
193387
+ }
193388
+ const before = ordered.slice(0, ordered.indexOf(job.stage)).reverse().find((stage) => jobsOf2(stage).length > 0);
193389
+ if (before === void 0) into(START_KEY2);
193390
+ else for (const previous of jobsOf2(before)) into(`job:${previous.name}`);
193391
+ }
193392
+ const firstNeed = unresolvedNeeds[0];
193393
+ if (firstNeed !== void 0) {
193394
+ context.report.add(DIAGNOSTIC_CODES.importUnresolved, spanAt3(firstNeed.job.line, firstNeed.job.column, firstNeed.job.name.length), `${countOf2(unresolvedNeeds.length, "need")} name${unresolvedNeeds.length === 1 ? "s a job" : " jobs"} the file does not hold, so no arrow is drawn for ${unresolvedNeeds.length === 1 ? "it" : "them"}: ${unresolvedNeeds.slice(0, 3).map((entry2) => `${cleanText(entry2.job.name)} needs ${cleanText(entry2.need)}`).join(", ")}.`, "Fix the name in needs:, or import the file that defines the job.");
193395
+ }
193396
+ const triggers = pipeline.jobs.filter((job) => job.trigger !== null).length;
193397
+ return writeFlow(
193398
+ {
193399
+ title: context.title ?? "GitLab CI/CD pipeline",
193400
+ desc: `${countOf2(pipeline.jobs.length, "job")} in ${countOf2(ordered.length, "stage")}${triggers === 0 ? "" : `, ${triggers} of which ${triggers === 1 ? "starts" : "start"} another pipeline`}, in the order they run: by needs, or stage after stage. A dashed arrow reaches a job that runs by hand or by its rules. Imported from GitLab CI/CD.`,
193401
+ // Stages run down the page: across, even three stages of a few jobs are wider than a column.
193402
+ direction: "down",
193403
+ nodes,
193404
+ groups,
193405
+ edges,
193406
+ noun: "jobs",
193407
+ capSpan: (key) => {
193408
+ const job = pipeline.jobs.find((candidate2) => key === `job:${candidate2.name}`);
193409
+ return spanAt3(job?.line ?? 1, job?.column ?? 1);
193410
+ },
193411
+ coverage: { noun: "jobs", total: pipeline.jobs.length, drawn: pipeline.jobs.length, summarised: 0 },
193412
+ advice: pipeline.stagesWritten ? "Add needs: to jobs that do not wait for a whole stage, so the picture shows what really runs in parallel." : "List the stages in stages:, so the picture shows them in the order they run."
193413
+ },
193414
+ context.report
193415
+ );
193416
+ }
193417
+ var START_KEY2, START_WORDS, gitlabCiImporter;
193418
+ var init_gitlab_ci = __esm({
193419
+ "../../packages/diagrams/src/importers/gitlab-ci/index.ts"() {
193420
+ "use strict";
193421
+ init_core();
193422
+ init_shared2();
193423
+ init_yaml();
193424
+ init_pipeline2();
193425
+ START_KEY2 = "pipeline";
193426
+ START_WORDS = { always: null, manual: "manual", rules: "when its rules match", only: "only on some refs", delayed: "delayed", on_failure: "on failure" };
193427
+ gitlabCiImporter = { format: "gitlab-ci", kinds: ["flow"], joins: null, extensions: [], read: readGitlabCi };
193428
+ }
193429
+ });
193430
+
192162
193431
  // ../../packages/diagrams/src/importers/json/locate.ts
192163
193432
  function stringEnd3(text3, start2) {
192164
193433
  for (let index2 = start2 + 1; index2 < text3.length; index2 += 1) {
@@ -192483,7 +193752,7 @@ function withoutSchemaBodies(text3) {
192483
193752
  }
192484
193753
  return lines.join("\n");
192485
193754
  }
192486
- function readDocument(text3, report3) {
193755
+ function readDocument2(text3, report3) {
192487
193756
  const index2 = new LineIndex(text3);
192488
193757
  if (text3.trimStart().startsWith("{")) {
192489
193758
  try {
@@ -192503,7 +193772,7 @@ function readDocument(text3, report3) {
192503
193772
  return root === void 0 || root === null ? null : plain2(root);
192504
193773
  }
192505
193774
  var UNREAD_SECTIONS;
192506
- var init_document2 = __esm({
193775
+ var init_document3 = __esm({
192507
193776
  "../../packages/diagrams/src/importers/openapi/document.ts"() {
192508
193777
  "use strict";
192509
193778
  init_core();
@@ -192860,7 +194129,7 @@ var METHODS;
192860
194129
  var init_operations = __esm({
192861
194130
  "../../packages/diagrams/src/importers/openapi/operations.ts"() {
192862
194131
  "use strict";
192863
- init_document2();
194132
+ init_document3();
192864
194133
  METHODS = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
192865
194134
  }
192866
194135
  });
@@ -192964,7 +194233,7 @@ function readOpenApi(text3, context) {
192964
194233
  report3.add(DIAGNOSTIC_CODES.importNotFound, START, `An OpenAPI document has no "${asked}" view.`, 'Set options.view to "endpoints" for an access matrix, "map" for the endpoints by tag, or "sequence" for one operation.');
192965
194234
  return null;
192966
194235
  }
192967
- const document2 = readDocument(text3, report3);
194236
+ const document2 = readDocument2(text3, report3);
192968
194237
  if (document2 === null) return null;
192969
194238
  if (!isOpenApi3(document2)) {
192970
194239
  report3.add(DIAGNOSTIC_CODES.importNoShape, START, "The document has no openapi: 3.x field, so it is not an OpenAPI 3 document.", "Send an OpenAPI 3.0 or 3.1 document. A Swagger 2.0 file can be converted with the swagger2openapi tool first.");
@@ -193011,7 +194280,7 @@ var init_openapi = __esm({
193011
194280
  "use strict";
193012
194281
  init_core();
193013
194282
  init_shared2();
193014
- init_document2();
194283
+ init_document3();
193015
194284
  init_endpoints();
193016
194285
  init_map();
193017
194286
  init_operations();
@@ -193303,19 +194572,19 @@ function piecesOf4(value) {
193303
194572
  }
193304
194573
  return out;
193305
194574
  }
193306
- var MAX_DEPTH14, Program;
194575
+ var MAX_DEPTH15, Program;
193307
194576
  var init_program = __esm({
193308
194577
  "../../packages/diagrams/src/importers/pulumi/program.ts"() {
193309
194578
  "use strict";
193310
194579
  init_value();
193311
194580
  init_yaml();
193312
194581
  init_item();
193313
- MAX_DEPTH14 = 16;
194582
+ MAX_DEPTH15 = 16;
193314
194583
  Program = class {
193315
- constructor(root, config2, stack, project) {
194584
+ constructor(root, config2, stack, project2) {
193316
194585
  this.config = config2;
193317
194586
  this.stack = stack;
193318
- this.project = project;
194587
+ this.project = project2;
193319
194588
  this.resources = entries(get(root, "resources")).filter((entry2) => entry2.value.kind === "map").map((entry2) => ({ name: entry2.key, node: entry2.value, line: entry2.line, column: entry2.column }));
193320
194589
  this.variables = new Map(entries(get(root, "variables")).map((entry2) => [entry2.key, entry2.value]));
193321
194590
  const defaults = /* @__PURE__ */ new Map();
@@ -193338,7 +194607,7 @@ var init_program = __esm({
193338
194607
  }
193339
194608
  /** The value of a node as far as the program and the stack give it. */
193340
194609
  evaluate(node3, depth = 0) {
193341
- if (node3 === void 0 || depth > MAX_DEPTH14) return new Unknown("value");
194610
+ if (node3 === void 0 || depth > MAX_DEPTH15) return new Unknown("value");
193342
194611
  if (node3.kind === "scalar") {
193343
194612
  if (node3.isNull) return null;
193344
194613
  if (!node3.value.includes("${")) return node3.value;
@@ -193423,7 +194692,7 @@ var init_program = __esm({
193423
194692
  }
193424
194693
  /** The resources a node names, through the variables it uses. */
193425
194694
  refs(node3, depth = 0) {
193426
- if (node3 === void 0 || depth > MAX_DEPTH14) return [];
194695
+ if (node3 === void 0 || depth > MAX_DEPTH15) return [];
193427
194696
  if (node3.kind === "scalar") {
193428
194697
  if (!node3.value.includes("${")) return [];
193429
194698
  return piecesOf4(node3.value).flatMap((piece) => {
@@ -193446,7 +194715,7 @@ var init_program = __esm({
193446
194715
  }
193447
194716
  /** Every reference a node holds, by the keys that lead to it. */
193448
194717
  refsByPath(node3, path, out, depth) {
193449
- if (node3 === void 0 || depth > MAX_DEPTH14) return;
194718
+ if (node3 === void 0 || depth > MAX_DEPTH15) return;
193450
194719
  if (node3.kind === "map" && !(node3.entries.length === 1 && node3.entries[0]?.key.startsWith("fn::"))) {
193451
194720
  for (const entry2 of node3.entries) this.refsByPath(entry2.value, [...path, entry2.key], out, depth + 1);
193452
194721
  return;
@@ -193561,7 +194830,7 @@ function stateResources(state, offsetOf) {
193561
194830
  const byUrn = new Map(all.map((resource) => [resource.urn, resource]));
193562
194831
  const counts = /* @__PURE__ */ new Map();
193563
194832
  for (const resource of all) counts.set(logicalName(resource.urn), (counts.get(logicalName(resource.urn)) ?? 0) + 1);
193564
- const nameOf10 = (urn) => (counts.get(logicalName(urn)) ?? 0) > 1 ? urn : logicalName(urn);
194833
+ const nameOf11 = (urn) => (counts.get(logicalName(urn)) ?? 0) > 1 ? urn : logicalName(urn);
193565
194834
  const safe3 = (name2) => name2.replace(/[^A-Za-z0-9_-]+/g, "_");
193566
194835
  const scopeOf2 = (resource) => {
193567
194836
  const chain2 = [];
@@ -193586,21 +194855,21 @@ function stateResources(state, offsetOf) {
193586
194855
  const outputs = target?.outputs;
193587
194856
  const name2 = typeof outputs === "object" && outputs !== null && !Array.isArray(outputs) ? outputs["name"] : null;
193588
194857
  const path = pathTo(inputs[property] ?? null, [target?.id ?? "", typeof name2 === "string" && property.endsWith("Name") ? name2 : ""], [property]);
193589
- return { path: path ?? [property], refs: [nameOf10(urn)] };
194858
+ return { path: path ?? [property], refs: [nameOf11(urn)] };
193590
194859
  })
193591
194860
  );
193592
194861
  const parent = resource.parent === null ? void 0 : byUrn.get(resource.parent);
193593
194862
  for (const urns of Object.values(resource.propertyDependencies)) for (const urn of urns) named.add(urn);
193594
- const dependsOn = resource.dependencies.filter((urn) => !named.has(urn) && byUrn.get(urn)?.custom === true && urn !== resource.urn && urn !== resource.parent).map(nameOf10);
194863
+ const dependsOn = resource.dependencies.filter((urn) => !named.has(urn) && byUrn.get(urn)?.custom === true && urn !== resource.urn && urn !== resource.parent).map(nameOf11);
193595
194864
  const properties = mergeValues2(resource.outputs, resource.inputs);
193596
194865
  return {
193597
- name: nameOf10(resource.urn),
194866
+ name: nameOf11(resource.urn),
193598
194867
  logical: logicalName(resource.urn),
193599
194868
  token: resource.type,
193600
194869
  properties,
193601
194870
  refs: refs.filter((entry2) => entry2.refs.length > 0),
193602
194871
  dependsOn,
193603
- parent: parent?.custom === true ? nameOf10(parent.urn) : null,
194872
+ parent: parent?.custom === true ? nameOf11(parent.urn) : null,
193604
194873
  existing: false,
193605
194874
  start: offsetOf(resource.urn),
193606
194875
  scope: scopeOf2(resource)
@@ -194244,6 +195513,724 @@ var init_sql = __esm({
194244
195513
  }
194245
195514
  });
194246
195515
 
195516
+ // ../../packages/diagrams/src/importers/structurizr/tokens.ts
195517
+ function tokensOf(line3) {
195518
+ const tokens = [];
195519
+ let index2 = 0;
195520
+ while (index2 < line3.length) {
195521
+ const char = line3.charAt(index2);
195522
+ if (char === " " || char === " ") {
195523
+ index2 += 1;
195524
+ continue;
195525
+ }
195526
+ if (char === '"') {
195527
+ let value = "";
195528
+ let at3 = index2 + 1;
195529
+ for (; at3 < line3.length && line3.charAt(at3) !== '"'; at3 += 1) {
195530
+ if (line3.charAt(at3) === "\\" && at3 + 1 < line3.length) at3 += 1;
195531
+ value += line3.charAt(at3);
195532
+ }
195533
+ tokens.push({ text: value, quoted: true, column: index2 + 1 });
195534
+ index2 = at3 + 1;
195535
+ continue;
195536
+ }
195537
+ if (char === "#" || line3.startsWith("//", index2)) break;
195538
+ let end = index2;
195539
+ while (end < line3.length && !' "'.includes(line3.charAt(end))) end += 1;
195540
+ const word2 = line3.slice(index2, end);
195541
+ let start2 = 0;
195542
+ for (let at3 = 0; at3 < word2.length; at3 += 1) {
195543
+ const single = word2.charAt(at3) === "{" || word2.charAt(at3) === "}" || word2.charAt(at3) === "=";
195544
+ const arrow = word2.startsWith("->", at3);
195545
+ if (!single && !arrow) continue;
195546
+ if (at3 > start2) tokens.push({ text: word2.slice(start2, at3), quoted: false, column: index2 + start2 + 1 });
195547
+ const size = arrow ? 2 : 1;
195548
+ tokens.push({ text: word2.slice(at3, at3 + size), quoted: false, column: index2 + at3 + 1 });
195549
+ start2 = at3 + size;
195550
+ at3 += size - 1;
195551
+ }
195552
+ if (start2 < word2.length) tokens.push({ text: word2.slice(start2), quoted: false, column: index2 + start2 + 1 });
195553
+ index2 = end;
195554
+ }
195555
+ return tokens;
195556
+ }
195557
+ function statementsOf2(text3) {
195558
+ const lines = text3.split("\n");
195559
+ const out = [];
195560
+ let inComment = false;
195561
+ let pending = null;
195562
+ for (let index2 = 0; index2 < lines.length && out.length < MAX_STATEMENTS; index2 += 1) {
195563
+ let line3 = lines[index2] ?? "";
195564
+ if (inComment) {
195565
+ const close2 = line3.indexOf("*/");
195566
+ if (close2 === -1) continue;
195567
+ line3 = " ".repeat(close2 + 2) + line3.slice(close2 + 2);
195568
+ inComment = false;
195569
+ }
195570
+ const trimmed4 = line3.trim();
195571
+ if (trimmed4.startsWith("/*")) {
195572
+ const close2 = line3.indexOf("*/", line3.indexOf("/*") + 2);
195573
+ if (close2 === -1) {
195574
+ inComment = true;
195575
+ continue;
195576
+ }
195577
+ line3 = " ".repeat(close2 + 2) + line3.slice(close2 + 2);
195578
+ }
195579
+ if (line3.trimEnd().endsWith("\\")) {
195580
+ const head = line3.trimEnd().slice(0, -1);
195581
+ pending = pending === null ? { text: head, line: index2 + 1 } : { text: `${pending.text} ${head.trim()}`, line: pending.line };
195582
+ continue;
195583
+ }
195584
+ const whole2 = pending === null ? { text: line3, line: index2 + 1 } : { text: `${pending.text} ${line3.trim()}`, line: pending.line };
195585
+ pending = null;
195586
+ const tokens = tokensOf(whole2.text);
195587
+ if (tokens.length === 0) continue;
195588
+ const open2 = tokens.findIndex((token) => !token.quoted && token.text === "{");
195589
+ const last = tokens[tokens.length - 1];
195590
+ if (open2 !== -1 && open2 < tokens.length - 1 && last !== void 0 && !last.quoted && last.text === "}") {
195591
+ out.push({ tokens: tokens.slice(0, open2 + 1), line: whole2.line });
195592
+ if (open2 + 1 < tokens.length - 1) out.push({ tokens: tokens.slice(open2 + 1, -1), line: whole2.line });
195593
+ out.push({ tokens: [last], line: whole2.line });
195594
+ continue;
195595
+ }
195596
+ out.push({ tokens, line: whole2.line });
195597
+ }
195598
+ return out;
195599
+ }
195600
+ var MAX_STATEMENTS;
195601
+ var init_tokens4 = __esm({
195602
+ "../../packages/diagrams/src/importers/structurizr/tokens.ts"() {
195603
+ "use strict";
195604
+ MAX_STATEMENTS = 5e4;
195605
+ }
195606
+ });
195607
+
195608
+ // ../../packages/diagrams/src/importers/structurizr/dsl.ts
195609
+ function readDsl(text3) {
195610
+ const statements = statementsOf2(text3);
195611
+ const elements = [];
195612
+ const byKey = /* @__PURE__ */ new Map();
195613
+ const groups = [];
195614
+ const relationships = [];
195615
+ const views = [];
195616
+ const cylinderTags = [];
195617
+ const skipped = [];
195618
+ const constants = /* @__PURE__ */ new Map();
195619
+ const stack = [];
195620
+ let hierarchical = false;
195621
+ let name2 = null;
195622
+ let description = null;
195623
+ const skip = (what, at3, hint2) => {
195624
+ if (!skipped.some((entry2) => entry2.what === what)) skipped.push({ what, line: at3.line, column: at3.column, hint: hint2 });
195625
+ };
195626
+ const expand2 = (value) => value.includes("${") ? value.replace(/\$\{([A-Za-z0-9_-]{1,64})\}/g, (whole2, key) => constants.get(key) ?? whole2) : value;
195627
+ const currentElement = () => {
195628
+ for (let index2 = stack.length - 1; index2 >= 0; index2 -= 1) {
195629
+ const frame = stack[index2];
195630
+ if (frame?.type === "element") return frame.key;
195631
+ }
195632
+ return null;
195633
+ };
195634
+ const currentGroup = () => {
195635
+ for (let index2 = stack.length - 1; index2 >= 0; index2 -= 1) {
195636
+ const frame = stack[index2];
195637
+ if (frame?.type === "group") return frame.key;
195638
+ if (frame?.type === "element") return null;
195639
+ }
195640
+ return null;
195641
+ };
195642
+ const resolve5 = (reference, scope) => {
195643
+ if (reference === "this") return scope;
195644
+ const lower = reference.toLowerCase();
195645
+ if (hierarchical) {
195646
+ for (let at3 = scope; at3 !== null; at3 = byKey.get(at3.toLowerCase())?.parent ?? null) {
195647
+ const found = byKey.get(`${at3.toLowerCase()}.${lower}`);
195648
+ if (found !== void 0) return found.key;
195649
+ }
195650
+ }
195651
+ return byKey.get(lower)?.key ?? null;
195652
+ };
195653
+ const addElement = (statement, identifier, kind, args) => {
195654
+ const parent = currentElement();
195655
+ const parentDraft = parent === null ? void 0 : byKey.get(parent.toLowerCase());
195656
+ const label = args[0]?.text ?? kind;
195657
+ const named = parentDraft === void 0 ? label : `${parentDraft.key}/${label}`;
195658
+ const key = identifier === null ? named : hierarchical && parentDraft !== void 0 ? `${parentDraft.key}.${identifier}` : identifier;
195659
+ const hasTech = kind === "container" || kind === "component";
195660
+ const tags = tagsOf2(args.slice(hasTech ? 3 : 2));
195661
+ const draft = {
195662
+ key,
195663
+ kind,
195664
+ name: label,
195665
+ description: args[1]?.text === "" ? null : args[1]?.text ?? null,
195666
+ technology: hasTech && args[2] !== void 0 && args[2].text !== "" ? args[2].text : null,
195667
+ tags,
195668
+ parent: parentDraft?.key ?? null,
195669
+ group: currentGroup(),
195670
+ external: tags.some((tag) => tag.toLowerCase() === "external"),
195671
+ line: statement.line,
195672
+ column: statement.tokens[0]?.column ?? 1
195673
+ };
195674
+ if (!byKey.has(key.toLowerCase())) {
195675
+ elements.push(draft);
195676
+ byKey.set(key.toLowerCase(), draft);
195677
+ }
195678
+ return { type: "element", key: byKey.get(key.toLowerCase())?.key ?? key };
195679
+ };
195680
+ const readModelStatement = (statement, words2) => {
195681
+ const first = words2[0];
195682
+ if (first === void 0) return { type: "skip" };
195683
+ const keyword = first.text.toLowerCase();
195684
+ const scope = currentElement();
195685
+ if (!first.quoted && keyword === "!identifiers") {
195686
+ hierarchical = words2[1]?.text.toLowerCase() === "hierarchical";
195687
+ return { type: "skip" };
195688
+ }
195689
+ if (!first.quoted && (keyword === "!element" || keyword === "!ref")) {
195690
+ const key = resolve5(words2[1]?.text ?? "", scope);
195691
+ if (key === null) {
195692
+ skip(`!element ${words2[1]?.text ?? ""}`, { line: statement.line, column: first.column }, "The element it extends is not in the files sent; send the workspace that defines it.");
195693
+ return { type: "skip" };
195694
+ }
195695
+ return { type: "element", key };
195696
+ }
195697
+ const assigned = words2[1]?.text === "=" && !words2[1].quoted;
195698
+ const rest = assigned ? words2.slice(2) : words2;
195699
+ const arrow = rest.findIndex((token) => !token.quoted && token.text === "->");
195700
+ if (arrow === 0 || arrow === 1) {
195701
+ const from = arrow === 0 ? "this" : rest[0]?.text ?? "";
195702
+ const to = rest[arrow + 1];
195703
+ if (to === void 0 || arrow === 0 && scope === null) {
195704
+ skip("A relationship with no source", { line: statement.line, column: first.column }, "Write the relationship as source -> destination, or inside the block of its source.");
195705
+ return { type: "skip" };
195706
+ }
195707
+ const args = rest.slice(arrow + 2);
195708
+ relationships.push({ from, to: to.text, scope, description: args[0]?.text === "" ? null : args[0]?.text ?? null, technology: args[1]?.text === "" ? null : args[1]?.text ?? null, tags: tagsOf2(args.slice(2)), line: statement.line, column: first.column });
195709
+ return { type: "relationship", index: relationships.length - 1 };
195710
+ }
195711
+ const word2 = (rest[0]?.text ?? "").toLowerCase();
195712
+ const kind = rest[0]?.quoted === true ? void 0 : ELEMENT_WORDS[word2];
195713
+ if (kind !== void 0) return addElement(statement, assigned ? first.text : null, kind, rest.slice(1));
195714
+ if (word2 === "group" || word2 === "enterprise") {
195715
+ const label = rest[1]?.text ?? (word2 === "enterprise" ? "Enterprise" : "Group");
195716
+ const parent = currentGroup();
195717
+ const key = parent !== null ? `${parent}/${label}` : scope !== null ? `${scope}/${label}` : label;
195718
+ if (!groups.some((group2) => group2.key === key)) groups.push({ key, name: label, parent, enterprise: word2 === "enterprise", line: statement.line });
195719
+ return { type: "group", key };
195720
+ }
195721
+ if (DEPLOYMENT_WORDS.has(word2)) {
195722
+ skip("Deployment environments", { line: statement.line, column: first.column }, "The picture shows the system context, container, or component level; a deployment view is not drawn.");
195723
+ return { type: "skip" };
195724
+ }
195725
+ if (word2 === "element") {
195726
+ skip("Custom elements", { line: statement.line, column: first.column }, "Only people, software systems, containers, and components are drawn.");
195727
+ return { type: "skip" };
195728
+ }
195729
+ if (scope !== null && (word2 === "description" || word2 === "technology" || word2 === "tags" || word2 === "tag")) {
195730
+ const draft = byKey.get(scope.toLowerCase());
195731
+ const value = rest[1]?.text ?? "";
195732
+ if (draft !== void 0 && word2 === "description") draft.description = value === "" ? null : value;
195733
+ if (draft !== void 0 && word2 === "technology") draft.technology = value === "" ? null : value;
195734
+ if (draft !== void 0 && (word2 === "tags" || word2 === "tag")) {
195735
+ draft.tags = [...draft.tags, ...tagsOf2(rest.slice(1))];
195736
+ draft.external ||= draft.tags.some((tag) => tag.toLowerCase() === "external");
195737
+ }
195738
+ return { type: "skip" };
195739
+ }
195740
+ if (scope !== null && word2 === "location") {
195741
+ const draft = byKey.get(scope.toLowerCase());
195742
+ if (draft !== void 0 && rest[1]?.text.toLowerCase() === "external") draft.external = true;
195743
+ return { type: "skip" };
195744
+ }
195745
+ if (word2 === "!include") skip(`!include ${rest[1]?.text ?? ""}`, { line: statement.line, column: first.column }, "Included files are not fetched; send the workspace with the included text in place.");
195746
+ return { type: "skip" };
195747
+ };
195748
+ for (const statement of statements) {
195749
+ const tokens = statement.tokens.map((token) => ({ ...token, text: expand2(token.text) }));
195750
+ const first = tokens[0];
195751
+ if (first === void 0) continue;
195752
+ if (!first.quoted && first.text === "}") {
195753
+ stack.pop();
195754
+ continue;
195755
+ }
195756
+ const last = tokens[tokens.length - 1];
195757
+ const opens = last !== void 0 && !last.quoted && last.text === "{";
195758
+ const words2 = opens ? tokens.slice(0, -1) : tokens;
195759
+ const keyword = first.quoted ? "" : first.text.toLowerCase();
195760
+ const frame = stack[stack.length - 1];
195761
+ let next = { type: "skip" };
195762
+ if (keyword === "!const" || keyword === "!constant" || keyword === "!var") {
195763
+ const key = words2[1]?.text;
195764
+ if (key !== void 0) constants.set(key, words2[2]?.text ?? "");
195765
+ } else if (frame === void 0) {
195766
+ if (keyword === "workspace") {
195767
+ if (words2[1]?.text.toLowerCase() === "extends" && !words2[1].quoted) skip("workspace extends", { line: statement.line, column: first.column }, "The workspace it extends is not fetched; send the base workspace, or one that holds the whole model.");
195768
+ else {
195769
+ name2 = words2[1]?.text ?? null;
195770
+ description = words2[2]?.text ?? null;
195771
+ }
195772
+ next = { type: "workspace" };
195773
+ }
195774
+ } else if (frame.type === "workspace") {
195775
+ if (keyword === "model") next = { type: "model" };
195776
+ else if (keyword === "views") next = { type: "views" };
195777
+ else if (keyword === "name") name2 = words2[1]?.text ?? name2;
195778
+ else if (keyword === "description") description = words2[1]?.text ?? description;
195779
+ else if (keyword === "!identifiers") hierarchical = words2[1]?.text.toLowerCase() === "hierarchical";
195780
+ else if (keyword === "!include") skip(`!include ${words2[1]?.text ?? ""}`, { line: statement.line, column: first.column }, "Included files are not fetched; send the workspace with the included text in place.");
195781
+ } else if (frame.type === "model" || frame.type === "element" || frame.type === "group") {
195782
+ next = readModelStatement(statement, words2);
195783
+ } else if (frame.type === "relationship") {
195784
+ const relationship = relationships[frame.index];
195785
+ if (relationship !== void 0 && (keyword === "tags" || keyword === "tag")) relationship.tags.push(...tagsOf2(words2.slice(1)));
195786
+ if (relationship !== void 0 && keyword === "description") relationship.description = words2[1]?.text ?? relationship.description;
195787
+ if (relationship !== void 0 && keyword === "technology") relationship.technology = words2[1]?.text ?? relationship.technology;
195788
+ } else if (frame.type === "views") {
195789
+ const level2 = VIEW_WORDS2[keyword];
195790
+ if (level2 !== void 0 && words2[1] !== void 0) {
195791
+ views.push({ level: level2, scope: words2[1].text, title: null, direction: null, line: statement.line, column: words2[1].column });
195792
+ next = { type: "view", index: views.length - 1 };
195793
+ } else if (keyword === "styles") next = { type: "styles" };
195794
+ } else if (frame.type === "view") {
195795
+ const view = views[frame.index];
195796
+ if (view !== void 0 && keyword === "title") view.title = words2[1]?.text ?? null;
195797
+ if (view !== void 0 && keyword === "autolayout") view.direction = /^(?:lr|rl)$/i.test(words2[1]?.text ?? "") ? "right" : "down";
195798
+ } else if (frame.type === "styles") {
195799
+ if (keyword === "element" && words2[1] !== void 0) next = { type: "style", tag: words2[1].text };
195800
+ } else if (frame.type === "style") {
195801
+ if (keyword === "shape" && words2[1]?.text.toLowerCase() === "cylinder") cylinderTags.push(frame.tag);
195802
+ }
195803
+ if (opens) stack.push(next);
195804
+ }
195805
+ const unresolved = [];
195806
+ const resolved2 = [];
195807
+ for (const relationship of relationships) {
195808
+ const from = resolve5(relationship.from, relationship.scope);
195809
+ const to = resolve5(relationship.to, relationship.scope);
195810
+ for (const [written3, key] of [[relationship.from, from], [relationship.to, to]]) if (key === null) unresolved.push({ name: written3, line: relationship.line, column: relationship.column });
195811
+ if (from !== null && to !== null) resolved2.push({ from, to, description: relationship.description, technology: relationship.technology, tags: relationship.tags, line: relationship.line, column: relationship.column });
195812
+ }
195813
+ const modelViews = [];
195814
+ for (const view of views) {
195815
+ const scope = resolve5(view.scope, null);
195816
+ if (scope === null) unresolved.push({ name: view.scope, line: view.line, column: view.column });
195817
+ else modelViews.push({ level: view.level, scope, title: view.title, direction: view.direction, line: view.line });
195818
+ }
195819
+ return { workspace: { name: name2, description, elements, relationships: resolved2, groups, views: modelViews, cylinderTags, skipped }, unresolved };
195820
+ }
195821
+ var ELEMENT_WORDS, VIEW_WORDS2, DEPLOYMENT_WORDS, tagsOf2;
195822
+ var init_dsl = __esm({
195823
+ "../../packages/diagrams/src/importers/structurizr/dsl.ts"() {
195824
+ "use strict";
195825
+ init_tokens4();
195826
+ ELEMENT_WORDS = { person: "person", softwaresystem: "system", container: "container", component: "component" };
195827
+ VIEW_WORDS2 = { systemcontext: "context", container: "container", component: "component" };
195828
+ DEPLOYMENT_WORDS = /* @__PURE__ */ new Set(["deploymentenvironment", "deploymentnode", "infrastructurenode", "softwaresysteminstance", "containerinstance", "deploymentgroup"]);
195829
+ tagsOf2 = (tokens) => tokens.flatMap((token) => token.text.split(",")).map((tag) => tag.trim()).filter((tag) => tag !== "");
195830
+ }
195831
+ });
195832
+
195833
+ // ../../packages/diagrams/src/importers/structurizr/json.ts
195834
+ function isWorkspaceJson(value) {
195835
+ return isObject4(value) && isObject4(value["model"]) && (Array.isArray(value["model"]["softwareSystems"]) || Array.isArray(value["model"]["people"]));
195836
+ }
195837
+ function readWorkspaceJson(json2) {
195838
+ const model = isObject4(json2["model"]) ? json2["model"] : {};
195839
+ const elements = [];
195840
+ const keyOfId = /* @__PURE__ */ new Map();
195841
+ const groups = [];
195842
+ const rawRelationships = [];
195843
+ const add2 = (entry2, kind, parent) => {
195844
+ const name2 = textOf7(entry2["name"]) ?? kind;
195845
+ const properties = isObject4(entry2["properties"]) ? entry2["properties"] : {};
195846
+ const identifier = textOf7(properties["structurizr.dsl.identifier"]);
195847
+ const tags = tagsOf3(entry2["tags"]);
195848
+ const group2 = textOf7(entry2["group"]);
195849
+ if (group2 !== null && !groups.some((existing) => existing.key === group2)) groups.push({ key: group2, name: group2, parent: null, enterprise: false, line: 1 });
195850
+ const element = {
195851
+ key: identifier ?? (parent === null ? name2 : `${parent.key}/${name2}`),
195852
+ kind,
195853
+ name: name2,
195854
+ description: textOf7(entry2["description"]),
195855
+ technology: kind === "container" || kind === "component" ? textOf7(entry2["technology"]) : null,
195856
+ tags,
195857
+ parent: parent?.key ?? null,
195858
+ group: group2,
195859
+ external: textOf7(entry2["location"])?.toLowerCase() === "external" || tags.some((tag) => tag.toLowerCase() === "external"),
195860
+ line: 1,
195861
+ column: 1
195862
+ };
195863
+ elements.push(element);
195864
+ const id = textOf7(entry2["id"]);
195865
+ if (id !== null) keyOfId.set(id, element.key);
195866
+ rawRelationships.push(...listOf4(entry2["relationships"]));
195867
+ return element;
195868
+ };
195869
+ for (const person of listOf4(model["people"])) add2(person, "person", null);
195870
+ for (const system of listOf4(model["softwareSystems"])) {
195871
+ const parent = add2(system, "system", null);
195872
+ for (const container of listOf4(system["containers"])) {
195873
+ const holder = add2(container, "container", parent);
195874
+ for (const component of listOf4(container["components"])) add2(component, "component", holder);
195875
+ }
195876
+ }
195877
+ const relationships = [];
195878
+ for (const relationship of rawRelationships) {
195879
+ if (relationship["linkedRelationshipId"] !== void 0) continue;
195880
+ const from = keyOfId.get(textOf7(relationship["sourceId"]) ?? "");
195881
+ const to = keyOfId.get(textOf7(relationship["destinationId"]) ?? "");
195882
+ if (from === void 0 || to === void 0) continue;
195883
+ relationships.push({ from, to, description: textOf7(relationship["description"]), technology: textOf7(relationship["technology"]), tags: tagsOf3(relationship["tags"]), line: 1, column: 1 });
195884
+ }
195885
+ const viewsJson = isObject4(json2["views"]) ? json2["views"] : {};
195886
+ const views = [];
195887
+ const lists = [
195888
+ ["systemContextViews", "context", "softwareSystemId"],
195889
+ ["containerViews", "container", "softwareSystemId"],
195890
+ ["componentViews", "component", "containerId"]
195891
+ ];
195892
+ for (const [list, level2, field] of lists) {
195893
+ for (const view of listOf4(viewsJson[list])) {
195894
+ const scope = keyOfId.get(textOf7(view[field]) ?? "");
195895
+ const rank = isObject4(view["automaticLayout"]) ? textOf7(view["automaticLayout"]["rankDirection"]) : null;
195896
+ if (scope !== void 0) views.push({ level: level2, scope, title: textOf7(view["title"]), direction: rank === null ? null : /^(?:LeftRight|RightLeft)$/i.test(rank) ? "right" : "down", line: 1 });
195897
+ }
195898
+ }
195899
+ const configuration = isObject4(viewsJson["configuration"]) ? viewsJson["configuration"] : {};
195900
+ const styles = isObject4(configuration["styles"]) ? configuration["styles"] : {};
195901
+ const cylinderTags = listOf4(styles["elements"]).filter((style) => textOf7(style["shape"])?.toLowerCase() === "cylinder").map((style) => textOf7(style["tag"]) ?? "").filter((tag) => tag !== "");
195902
+ return { name: textOf7(json2["name"]), description: textOf7(json2["description"]), elements, relationships, groups, views, cylinderTags, skipped: [] };
195903
+ }
195904
+ var isObject4, listOf4, textOf7, DEFAULT_TAGS, tagsOf3;
195905
+ var init_json2 = __esm({
195906
+ "../../packages/diagrams/src/importers/structurizr/json.ts"() {
195907
+ "use strict";
195908
+ isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
195909
+ listOf4 = (value) => Array.isArray(value) ? value.filter(isObject4) : [];
195910
+ textOf7 = (value) => typeof value === "string" && value.trim() !== "" ? value : typeof value === "number" ? String(value) : null;
195911
+ DEFAULT_TAGS = /* @__PURE__ */ new Set(["element", "person", "software system", "container", "component", "relationship"]);
195912
+ tagsOf3 = (value) => (textOf7(value) ?? "").split(",").map((tag) => tag.trim()).filter((tag) => tag !== "" && !DEFAULT_TAGS.has(tag.toLowerCase()));
195913
+ }
195914
+ });
195915
+
195916
+ // ../../packages/diagrams/src/importers/structurizr/view.ts
195917
+ function find(workspace, name2) {
195918
+ const lower = name2.toLowerCase();
195919
+ return workspace.elements.find((element) => element.key.toLowerCase() === lower) ?? workspace.elements.find((element) => element.name.toLowerCase() === lower);
195920
+ }
195921
+ function chooseView(workspace, level2, root) {
195922
+ const named = root === null ? null : find(workspace, root) ?? null;
195923
+ if (root !== null && named === null) return { view: null, missing: root };
195924
+ const systems = workspace.elements.filter((element) => element.kind === "system");
195925
+ const withChildren = (kind) => workspace.elements.find((element) => element.kind === kind && childrenOf(workspace, element.key).length > 0);
195926
+ const systemOf = (element) => {
195927
+ let at3 = element;
195928
+ for (let depth = 0; at3.parent !== null && depth < 3; depth += 1) at3 = workspace.elements.find((candidate2) => candidate2.key === at3.parent) ?? at3;
195929
+ return at3;
195930
+ };
195931
+ if (level2 === null && named === null) {
195932
+ const defined2 = workspace.views[0];
195933
+ const scope = defined2 === void 0 ? void 0 : workspace.elements.find((element) => element.key === defined2.scope);
195934
+ if (defined2 !== void 0 && scope !== void 0) return { view: { level: defined2.level, scope, title: defined2.title, direction: defined2.direction }, missing: null };
195935
+ const system2 = withChildren("system");
195936
+ if (system2 !== void 0) return { view: { level: "container", scope: system2, title: null, direction: null }, missing: null };
195937
+ const first = systems[0];
195938
+ return { view: first === void 0 ? null : { level: "context", scope: first, title: null, direction: null }, missing: null };
195939
+ }
195940
+ const wanted2 = level2 ?? (named?.kind === "container" || named?.kind === "component" ? "component" : named !== null && childrenOf(workspace, named.key).length > 0 ? "container" : "context");
195941
+ const defined = (scope) => workspace.views.find((view) => view.level === wanted2 && view.scope === scope.key);
195942
+ if (wanted2 === "component") {
195943
+ const container = named === null ? withChildren("container") : named.kind === "component" ? workspace.elements.find((element) => element.key === named.parent) : named.kind === "container" ? named : childrenOf(workspace, named.key).find((child) => childrenOf(workspace, child.key).length > 0) ?? childrenOf(workspace, named.key)[0];
195944
+ return { view: container === void 0 ? null : { level: "component", scope: container, title: defined(container)?.title ?? null, direction: defined(container)?.direction ?? null }, missing: null };
195945
+ }
195946
+ const system = named === null ? wanted2 === "container" ? withChildren("system") ?? systems[0] : withChildren("system") ?? systems[0] : named.kind === "person" ? void 0 : systemOf(named);
195947
+ return { view: system === void 0 ? null : { level: wanted2, scope: system, title: defined(system)?.title ?? null, direction: defined(system)?.direction ?? null }, missing: null };
195948
+ }
195949
+ function hiddenWords(workspace, element) {
195950
+ const containers = childrenOf(workspace, element.key);
195951
+ const components = containers.flatMap((child) => childrenOf(workspace, child.key));
195952
+ const parts = [
195953
+ containers.length === 0 ? null : `${containers.length} ${element.kind === "container" ? containers.length === 1 ? "component" : "components" : containers.length === 1 ? "container" : "containers"}`,
195954
+ components.length === 0 ? null : `${components.length} ${components.length === 1 ? "component" : "components"}`
195955
+ ].filter((part) => part !== null);
195956
+ return { count: containers.length + components.length, words: parts.join(" and ") };
195957
+ }
195958
+ function project(workspace, view) {
195959
+ const byKey = new Map(workspace.elements.map((element) => [element.key, element]));
195960
+ const ancestors = (element) => {
195961
+ const out = [];
195962
+ for (let at3 = element.parent === null ? void 0 : byKey.get(element.parent); at3 !== void 0 && out.length < 3; at3 = at3.parent === null ? void 0 : byKey.get(at3.parent)) out.push(at3);
195963
+ return out;
195964
+ };
195965
+ const within = (element, holder) => element.key === holder.key || ancestors(element).some((ancestor) => ancestor.key === holder.key);
195966
+ const top = (element) => ancestors(element).pop() ?? element;
195967
+ const scope = view.scope;
195968
+ const system = top(scope);
195969
+ const inner = view.level === "context" ? [scope] : childrenOf(workspace, scope.key);
195970
+ const innerKeys = new Set(inner.map((element) => element.key));
195971
+ const innerOf2 = (element) => [element, ...ancestors(element)].find((candidate2) => innerKeys.has(candidate2.key)) ?? null;
195972
+ const outerOf = (element) => {
195973
+ if (view.level === "component" && within(element, system) && !within(element, scope)) return [element, ...ancestors(element)].find((candidate2) => candidate2.kind === "container") ?? null;
195974
+ if (within(element, system)) return null;
195975
+ return top(element);
195976
+ };
195977
+ const shown = new Map(inner.map((element) => [element.key, element]));
195978
+ const lines = [];
195979
+ const seen = /* @__PURE__ */ new Set();
195980
+ for (const relationship of workspace.relationships) {
195981
+ const fromElement = byKey.get(relationship.from);
195982
+ const toElement = byKey.get(relationship.to);
195983
+ if (fromElement === void 0 || toElement === void 0) continue;
195984
+ const fromInner = innerOf2(fromElement);
195985
+ const toInner = innerOf2(toElement);
195986
+ if (fromInner === null && toInner === null) continue;
195987
+ const from = fromInner ?? outerOf(fromElement);
195988
+ const to = toInner ?? outerOf(toElement);
195989
+ if (from === null || to === null || from.key === to.key) continue;
195990
+ shown.set(from.key, from);
195991
+ shown.set(to.key, to);
195992
+ const pair2 = `${from.key}\0${to.key}`;
195993
+ if (seen.has(pair2)) continue;
195994
+ seen.add(pair2);
195995
+ lines.push({ from: from.key, to: to.key, relationship });
195996
+ }
195997
+ for (const relationship of workspace.relationships) {
195998
+ const fromElement = byKey.get(relationship.from);
195999
+ const toElement = byKey.get(relationship.to);
196000
+ if (fromElement === void 0 || toElement === void 0 || innerOf2(fromElement) !== null || innerOf2(toElement) !== null) continue;
196001
+ const from = outerOf(fromElement);
196002
+ const to = outerOf(toElement);
196003
+ if (from === null || to === null || from.key === to.key || !shown.has(from.key) || !shown.has(to.key)) continue;
196004
+ const pair2 = `${from.key}\0${to.key}`;
196005
+ if (seen.has(pair2)) continue;
196006
+ seen.add(pair2);
196007
+ lines.push({ from: from.key, to: to.key, relationship });
196008
+ }
196009
+ const boundaries = [];
196010
+ const groupBoundary = (groupKey, holder) => {
196011
+ const group2 = groupKey === null ? void 0 : workspace.groups.find((entry2) => entry2.key === groupKey);
196012
+ if (group2 === void 0) return holder;
196013
+ const parent = groupBoundary(group2.parent, holder);
196014
+ const key = `group:${group2.key}`;
196015
+ if (!boundaries.some((boundary) => boundary.key === key)) boundaries.push({ key, name: group2.name, kind: group2.enterprise ? "enterprise" : "group", parent, tech: null, element: null });
196016
+ return key;
196017
+ };
196018
+ let scopeBoundary = null;
196019
+ if (view.level === "component") {
196020
+ boundaries.push({ key: system.key, name: system.name, kind: "system", parent: groupBoundary(system.group, null), tech: null, element: system });
196021
+ boundaries.push({ key: scope.key, name: scope.name, kind: "container", parent: system.key, tech: scope.technology, element: scope });
196022
+ scopeBoundary = scope.key;
196023
+ } else if (view.level === "container") {
196024
+ boundaries.push({ key: scope.key, name: scope.name, kind: "system", parent: groupBoundary(scope.group, null), tech: null, element: scope });
196025
+ scopeBoundary = scope.key;
196026
+ }
196027
+ const enterprise = workspace.groups.some((group2) => group2.enterprise);
196028
+ const inEnterprise = (element) => {
196029
+ let group2 = workspace.groups.find((entry2) => entry2.key === top(element).group);
196030
+ for (let depth = 0; group2 !== void 0 && depth < workspace.groups.length; depth += 1) {
196031
+ if (group2.enterprise) return true;
196032
+ group2 = workspace.groups.find((entry2) => entry2.key === group2?.parent);
196033
+ }
196034
+ return false;
196035
+ };
196036
+ const order = (element) => innerKeys.has(element.key) ? 1 : element.kind === "person" ? 0 : 2;
196037
+ const placed = [...shown.values()].sort((a, b) => order(a) - order(b) || workspace.elements.indexOf(a) - workspace.elements.indexOf(b)).map((element) => {
196038
+ const holder = innerKeys.has(element.key) && view.level !== "context" ? scopeBoundary : view.level === "component" && within(element, system) ? system.key : null;
196039
+ const hidden = hiddenWords(workspace, element);
196040
+ return { element, boundary: groupBoundary(element.group, holder), external: element.external || element.kind === "system" && element.key !== system.key && (enterprise ? !inEnterprise(element) : true), hidden: hidden.count, hiddenWords: hidden.words };
196041
+ });
196042
+ const depthOf7 = (key) => {
196043
+ let depth = 0;
196044
+ for (let at3 = key; at3 !== null && depth <= boundaries.length; depth += 1) at3 = boundaries.find((boundary) => boundary.key === at3)?.parent ?? null;
196045
+ return depth;
196046
+ };
196047
+ const kept2 = (key) => {
196048
+ let at3 = key;
196049
+ while (at3 !== null && depthOf7(at3) > MAX_DEPTH16) at3 = boundaries.find((boundary) => boundary.key === at3)?.parent ?? null;
196050
+ return at3;
196051
+ };
196052
+ const elements = placed.map((entry2) => ({ ...entry2, boundary: kept2(entry2.boundary) }));
196053
+ const used = new Set(elements.map((entry2) => entry2.boundary));
196054
+ for (let changed = true, round2 = 0; changed && round2 <= boundaries.length; round2 += 1) {
196055
+ changed = false;
196056
+ for (const boundary of boundaries) if (used.has(boundary.key) && boundary.parent !== null && !used.has(boundary.parent)) {
196057
+ used.add(boundary.parent);
196058
+ changed = true;
196059
+ }
196060
+ }
196061
+ const shownBoundaries = boundaries.filter((boundary) => depthOf7(boundary.key) <= MAX_DEPTH16 && (boundary.element !== null || used.has(boundary.key)));
196062
+ const drawn = /* @__PURE__ */ new Set([...elements.map((entry2) => entry2.element.key), ...shownBoundaries.flatMap((boundary) => boundary.element === null ? [] : [boundary.element.key])]);
196063
+ const summarisedBy = (element) => ancestors(element).some((ancestor) => shown.has(ancestor.key));
196064
+ const summarised = workspace.elements.filter((element) => !drawn.has(element.key) && summarisedBy(element)).length;
196065
+ const omitted = workspace.elements.filter((element) => !drawn.has(element.key) && !summarisedBy(element));
196066
+ return { elements, boundaries: shownBoundaries, relationships: lines, summarised, omitted };
196067
+ }
196068
+ var MAX_DEPTH16, childrenOf;
196069
+ var init_view = __esm({
196070
+ "../../packages/diagrams/src/importers/structurizr/view.ts"() {
196071
+ "use strict";
196072
+ MAX_DEPTH16 = 4;
196073
+ childrenOf = (workspace, key) => workspace.elements.filter((element) => element.parent === key);
196074
+ }
196075
+ });
196076
+
196077
+ // ../../packages/diagrams/src/importers/structurizr/index.ts
196078
+ function isDatabase(element, workspace) {
196079
+ if (element.kind !== "container" && element.kind !== "component") return false;
196080
+ const tags = element.tags.map((tag) => tag.toLowerCase());
196081
+ return tags.includes("database") || workspace.cylinderTags.some((tag) => tags.includes(tag.toLowerCase())) || element.technology !== null && DATABASE_WORDS.test(element.technology);
196082
+ }
196083
+ function readWorkspace(text3, context) {
196084
+ if (/^\s*\{/.test(text3)) {
196085
+ let json2;
196086
+ try {
196087
+ json2 = JSON.parse(text3);
196088
+ } catch (error62) {
196089
+ context.report.add(DIAGNOSTIC_CODES.importUnreadable, START, `The text is not JSON: ${error62 instanceof Error ? error62.message : "it cannot be read"}.`, "Send the workspace as Structurizr exports it (workspace.json), or its DSL (workspace.dsl).");
196090
+ return null;
196091
+ }
196092
+ if (!isWorkspaceJson(json2)) {
196093
+ context.report.add(DIAGNOSTIC_CODES.importNoShape, START, "The JSON has no model of people or software systems, so it is not a Structurizr workspace.", "Send workspace.json as Structurizr exports it, or the workspace DSL.");
196094
+ return null;
196095
+ }
196096
+ return { workspace: readWorkspaceJson(json2), unresolved: [] };
196097
+ }
196098
+ return readDsl(text3);
196099
+ }
196100
+ function writeC4(workspace, view, shown, title, desc) {
196101
+ const ids = new IdAllocator();
196102
+ const hint2 = (element) => element.key.includes("/") ? element.name : element.key.split(".").pop() ?? element.name;
196103
+ for (const boundary of shown.boundaries) if (boundary.element !== null) ids.idFor(`b:${boundary.key}`, hint2(boundary.element));
196104
+ for (const entry2 of shown.elements) ids.idFor(`e:${entry2.element.key}`, hint2(entry2.element));
196105
+ for (const boundary of shown.boundaries) if (boundary.element === null) ids.idFor(`b:${boundary.key}`, boundary.name.toLowerCase());
196106
+ const lines = new SourceLines();
196107
+ lines.add(`c4 ${quote(title)}${options2({ view: view.level, direction: view.direction, desc })}`);
196108
+ const elementLine2 = (entry2, depth) => {
196109
+ const { element } = entry2;
196110
+ const type = isDatabase(element, workspace) ? "db" : element.kind;
196111
+ const text3 = [element.description === null ? null : cleanText(element.description), entry2.hidden === 0 ? null : `Holds ${entry2.hiddenWords}.`].filter((part) => part !== null).join(" ");
196112
+ lines.add(`${type} ${ids.idFor(`e:${element.key}`)} ${quote(cleanText(element.name))}${options2({ tech: element.technology === null ? null : cleanText(element.technology), desc: text3, external: entry2.external ? "on" : null }, ["tech", "desc"])}`, depth);
196113
+ };
196114
+ const writeBoundary = (boundary, depth) => {
196115
+ lines.gap();
196116
+ lines.add(`boundary ${ids.idFor(`b:${boundary.key}`)} ${quote(cleanText(boundary.name))}${options2({ kind: boundary.kind, tech: boundary.tech === null ? null : cleanText(boundary.tech), icon: BOUNDARY_LOOKS2[boundary.kind].icon, tone: BOUNDARY_LOOKS2[boundary.kind].tone }, ["tech"])} {`, depth);
196117
+ for (const entry2 of shown.elements) if (entry2.boundary === boundary.key) elementLine2(entry2, depth + 1);
196118
+ for (const child of shown.boundaries) if (child.parent === boundary.key) writeBoundary(child, depth + 1);
196119
+ lines.add("}", depth);
196120
+ };
196121
+ lines.gap();
196122
+ const people = shown.elements.filter((entry2) => entry2.boundary === null && entry2.element.kind === "person");
196123
+ for (const entry2 of people) elementLine2(entry2, 0);
196124
+ for (const boundary of shown.boundaries) if (boundary.parent === null) writeBoundary(boundary, 0);
196125
+ lines.gap();
196126
+ for (const entry2 of shown.elements) if (entry2.boundary === null && entry2.element.kind !== "person") elementLine2(entry2, 0);
196127
+ lines.gap();
196128
+ const kindOf3 = new Map(shown.elements.map((entry2) => [entry2.element.key, entry2.element.kind]));
196129
+ const ordered = [...shown.relationships].sort((a, b) => Number(kindOf3.get(a.from) !== "person") - Number(kindOf3.get(b.from) !== "person"));
196130
+ for (const line3 of ordered) {
196131
+ const { relationship } = line3;
196132
+ const async = relationship.tags.some((tag) => /^async(?:hronous)?$/i.test(tag));
196133
+ const label = relationship.description === null ? "" : ` : ${quote(cleanText(relationship.description))}${options2({ tech: relationship.technology === null ? null : cleanText(relationship.technology) }, ["tech"])}`;
196134
+ lines.add(`${ids.idFor(`e:${line3.from}`)} ${async ? "-->" : "->"} ${ids.idFor(`e:${line3.to}`)}${label}`);
196135
+ }
196136
+ return { source: lines.toString(), ids: { ...ids.mapped("b:"), ...ids.mapped("e:") }, links: shown.relationships.length };
196137
+ }
196138
+ function capped(shown, context) {
196139
+ const elements = shown.elements.slice(0, C4_BUDGET.maxElements);
196140
+ const keys = new Set(elements.map((entry2) => entry2.element.key));
196141
+ const relationships = shown.relationships.filter((line3) => keys.has(line3.from) && keys.has(line3.to)).slice(0, C4_BUDGET.maxRelationships);
196142
+ const leftElements = shown.elements.length - elements.length;
196143
+ const leftLines = shown.relationships.length - relationships.length;
196144
+ if (leftElements + leftLines > 0) {
196145
+ const first = shown.elements[C4_BUDGET.maxElements]?.element;
196146
+ context.report.add(
196147
+ DIAGNOSTIC_CODES.importDropped,
196148
+ first === void 0 ? START : spanAt3(first.line, first.column),
196149
+ `${[leftElements === 0 ? null : countOf2(leftElements, "element"), leftLines === 0 ? null : countOf2(leftLines, "relationship")].filter((part) => part !== null).join(" and ")} past what one C4 picture holds ${leftElements + leftLines === 1 ? "was" : "were"} left out.`,
196150
+ "Draw a smaller view: one container with options.view component, or one system with options.root."
196151
+ );
196152
+ }
196153
+ return { ...shown, elements, relationships, omitted: [...shown.omitted, ...shown.elements.slice(C4_BUDGET.maxElements).map((entry2) => entry2.element)] };
196154
+ }
196155
+ function readStructurizr(text3, context) {
196156
+ const read = readWorkspace(text3, context);
196157
+ if (read === null) return null;
196158
+ const { workspace, unresolved } = read;
196159
+ if (workspace.elements.length === 0) {
196160
+ context.report.add(DIAGNOSTIC_CODES.importNoShape, START, "The workspace has no people, software systems, containers, or components.", 'Send a workspace with a model block, such as workspace { model { user = person "User" } }.');
196161
+ return null;
196162
+ }
196163
+ for (const skipped of workspace.skipped) context.report.add(DIAGNOSTIC_CODES.importSkipped, spanAt3(skipped.line, skipped.column), `${cleanText(skipped.what)} ${skipped.what.startsWith("!") || skipped.what.startsWith("workspace") ? "is" : "are"} not drawn.`, skipped.hint);
196164
+ const firstUnresolved = unresolved[0];
196165
+ if (firstUnresolved !== void 0) {
196166
+ context.report.add(DIAGNOSTIC_CODES.importUnresolved, spanAt3(firstUnresolved.line, firstUnresolved.column), `${countOf2(unresolved.length, "reference")} name${unresolved.length === 1 ? "s" : ""} no element of the model: ${[...new Set(unresolved.map((entry2) => entry2.name))].slice(0, 3).map((name2) => cleanText(name2)).join(", ")}.`, "Check the identifier; with !identifiers hierarchical, an element inside another is named by its path, such as shop.api.");
196167
+ }
196168
+ const requested = context.options.view;
196169
+ const level2 = requested !== void 0 && LEVELS.includes(requested) ? requested : null;
196170
+ const chosen = chooseView(workspace, level2, context.options.root ?? null);
196171
+ if (chosen.missing !== null) {
196172
+ context.report.add(DIAGNOSTIC_CODES.importNotFound, START, `No element of the workspace is named ${cleanText(chosen.missing)}.`, `Set options.root to the identifier or name of a software system or container, such as ${workspace.elements.find((element) => element.kind === "system")?.key ?? "shop"}.`);
196173
+ return null;
196174
+ }
196175
+ if (chosen.view === null) {
196176
+ context.report.add(DIAGNOSTIC_CODES.importNoShape, START, level2 === "component" ? "No container of the workspace has components." : "The workspace has no software system to draw a view of.", "Add a softwareSystem to the model, or choose another level with options.view.");
196177
+ return null;
196178
+ }
196179
+ const view = chosen.view;
196180
+ const shown = capped(project(workspace, view), context);
196181
+ const omitted = shown.omitted;
196182
+ const firstOmitted = omitted[0];
196183
+ if (firstOmitted !== void 0) {
196184
+ context.report.add(
196185
+ DIAGNOSTIC_CODES.importSkipped,
196186
+ spanAt3(firstOmitted.line, firstOmitted.column),
196187
+ `${countOf2(omitted.length, "element")} ${omitted.length === 1 ? "is" : "are"} not in the ${LEVEL_WORDS[view.level].title} view of ${cleanText(view.scope.name)}: ${omitted.slice(0, 3).map((element) => cleanText(element.key)).join(", ")}.`,
196188
+ "An element is drawn when a relationship joins it to what the view is about. Choose another view with options.view (context, container, or component) and options.root."
196189
+ );
196190
+ }
196191
+ const scopeName = cleanText(view.scope.name);
196192
+ const title = context.title ?? (view.title === null ? `${scopeName}: ${LEVEL_WORDS[view.level].title}` : cleanText(view.title));
196193
+ const what = view.level === "context" ? `${scopeName} ${LEVEL_WORDS.context.sentence}` : `${LEVEL_WORDS[view.level].sentence} ${scopeName} and what they talk to`;
196194
+ const desc = `${what.charAt(0).toUpperCase()}${what.slice(1)}, imported from the Structurizr workspace${workspace.name === null ? "" : ` ${cleanText(workspace.name)}`}.`;
196195
+ const written3 = writeC4(workspace, view, shown, title, desc);
196196
+ const drawn = shown.elements.length + shown.boundaries.filter((boundary) => boundary.element !== null).length;
196197
+ const others = workspace.views.length > 1 ? ` The workspace defines ${countOf2(workspace.views.length, "view")}; draw another with options.view and options.root.` : "";
196198
+ return {
196199
+ kind: "c4",
196200
+ source: written3.source,
196201
+ stats: { items: shown.elements.length, links: written3.links, groups: shown.boundaries.length, dropped: omitted.length },
196202
+ advice: `Check that each element has a description and each relationship a technology, so the cards and lines say what they do.${others}`,
196203
+ coverage: { noun: "elements", total: workspace.elements.length, drawn, summarised: shown.summarised },
196204
+ ids: written3.ids
196205
+ };
196206
+ }
196207
+ var BOUNDARY_LOOKS2, DATABASE_WORDS, LEVELS, LEVEL_WORDS, structurizrImporter;
196208
+ var init_structurizr = __esm({
196209
+ "../../packages/diagrams/src/importers/structurizr/index.ts"() {
196210
+ "use strict";
196211
+ init_core();
196212
+ init_limits();
196213
+ init_shared2();
196214
+ init_dsl();
196215
+ init_json2();
196216
+ init_view();
196217
+ BOUNDARY_LOOKS2 = {
196218
+ system: { icon: "lucide:blocks", tone: "blue" },
196219
+ container: { icon: "lucide:box", tone: "blue" },
196220
+ group: { icon: "lucide:boxes", tone: "violet" },
196221
+ enterprise: { icon: "lucide:building", tone: "neutral" }
196222
+ };
196223
+ DATABASE_WORDS = /\b(?:database|postgres(?:ql)?|mysql|mariadb|oracle|sql server|sqlite|mongodb|dynamodb|cassandra|cosmos ?db|couchdb|neo4j|elasticsearch|opensearch|clickhouse|snowflake|bigquery|redshift)\b/i;
196224
+ LEVELS = ["context", "container", "component"];
196225
+ LEVEL_WORDS = {
196226
+ context: { title: "system context", sentence: "and the people and systems around it" },
196227
+ container: { title: "containers", sentence: "the containers of" },
196228
+ component: { title: "components", sentence: "the components of" }
196229
+ };
196230
+ structurizrImporter = { format: "structurizr", kinds: ["c4"], joins: null, extensions: [".dsl", ".json"], read: readStructurizr };
196231
+ }
196232
+ });
196233
+
194247
196234
  // ../../packages/diagrams/src/importers/terraform-plan/document.ts
194248
196235
  function moduleOfAddress(address, type, mode) {
194249
196236
  const own2 = `${mode === "data" ? "data." : ""}${type}.`;
@@ -194251,7 +196238,7 @@ function moduleOfAddress(address, type, mode) {
194251
196238
  return at3 <= 0 ? "" : address.slice(0, at3 - 1);
194252
196239
  }
194253
196240
  function resourceOf3(raw, module) {
194254
- if (!isObject4(raw) || typeof raw["address"] !== "string" || typeof raw["type"] !== "string" || typeof raw["name"] !== "string") return null;
196241
+ if (!isObject5(raw) || typeof raw["address"] !== "string" || typeof raw["type"] !== "string" || typeof raw["name"] !== "string") return null;
194255
196242
  const mode = raw["mode"] === "data" ? "data" : "managed";
194256
196243
  const index2 = typeof raw["index"] === "string" || typeof raw["index"] === "number" ? raw["index"] : null;
194257
196244
  return {
@@ -194261,13 +196248,13 @@ function resourceOf3(raw, module) {
194261
196248
  name: raw["name"],
194262
196249
  index: index2,
194263
196250
  module: module === "" ? moduleOfAddress(raw["address"], raw["type"], mode) : module,
194264
- values: isObject4(raw["values"]) ? raw["values"] : {},
194265
- sensitive: isObject4(raw["sensitive_values"]) ? raw["sensitive_values"] : {},
196251
+ values: isObject5(raw["values"]) ? raw["values"] : {},
196252
+ sensitive: isObject5(raw["sensitive_values"]) ? raw["sensitive_values"] : {},
194266
196253
  dependsOn: strings2(raw["depends_on"])
194267
196254
  };
194268
196255
  }
194269
196256
  function moduleResources(raw, depth = 0) {
194270
- if (!isObject4(raw) || depth > MAX_MODULE_DEPTH2) return [];
196257
+ if (!isObject5(raw) || depth > MAX_MODULE_DEPTH2) return [];
194271
196258
  const address = typeof raw["address"] === "string" ? raw["address"] : "";
194272
196259
  const own2 = (Array.isArray(raw["resources"]) ? raw["resources"] : []).map((entry2) => resourceOf3(entry2, address)).filter((entry2) => entry2 !== null);
194273
196260
  const children = Array.isArray(raw["child_modules"]) ? raw["child_modules"].flatMap((child) => moduleResources(child, depth + 1)) : [];
@@ -194275,9 +196262,9 @@ function moduleResources(raw, depth = 0) {
194275
196262
  }
194276
196263
  function variablesOf(raw, sensitive) {
194277
196264
  const out = /* @__PURE__ */ new Map();
194278
- if (!isObject4(raw)) return out;
196265
+ if (!isObject5(raw)) return out;
194279
196266
  for (const [name2, entry2] of Object.entries(raw)) {
194280
- if (sensitive.has(name2) || !isObject4(entry2)) continue;
196267
+ if (sensitive.has(name2) || !isObject5(entry2)) continue;
194281
196268
  const value = entry2["value"];
194282
196269
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") out.set(name2, String(value));
194283
196270
  }
@@ -194286,28 +196273,28 @@ function variablesOf(raw, sensitive) {
194286
196273
  function sensitiveVariables(configuration) {
194287
196274
  const out = /* @__PURE__ */ new Set();
194288
196275
  const root = configuration === null ? null : configuration["root_module"];
194289
- const variables = isObject4(root) && isObject4(root["variables"]) ? root["variables"] : {};
194290
- for (const [name2, entry2] of Object.entries(variables)) if (isObject4(entry2) && entry2["sensitive"] === true) out.add(name2);
196276
+ const variables = isObject5(root) && isObject5(root["variables"]) ? root["variables"] : {};
196277
+ for (const [name2, entry2] of Object.entries(variables)) if (isObject5(entry2) && entry2["sensitive"] === true) out.add(name2);
194291
196278
  return out;
194292
196279
  }
194293
196280
  function changesOf(raw) {
194294
196281
  const out = /* @__PURE__ */ new Map();
194295
196282
  if (!Array.isArray(raw)) return out;
194296
196283
  for (const entry2 of raw) {
194297
- if (!isObject4(entry2) || typeof entry2["address"] !== "string" || !isObject4(entry2["change"])) continue;
196284
+ if (!isObject5(entry2) || typeof entry2["address"] !== "string" || !isObject5(entry2["change"])) continue;
194298
196285
  const change = entry2["change"];
194299
- out.set(entry2["address"], { actions: strings2(change["actions"]), previous: typeof entry2["previous_address"] === "string" ? entry2["previous_address"] : null, before: isObject4(change["before"]) ? change["before"] : null });
196286
+ out.set(entry2["address"], { actions: strings2(change["actions"]), previous: typeof entry2["previous_address"] === "string" ? entry2["previous_address"] : null, before: isObject5(change["before"]) ? change["before"] : null });
194300
196287
  }
194301
196288
  return out;
194302
196289
  }
194303
196290
  function readPlanDocument(json2) {
194304
- if (!isObject4(json2)) return null;
194305
- const configuration = isObject4(json2["configuration"]) ? json2["configuration"] : null;
194306
- const providers = configuration !== null && isObject4(configuration["provider_config"]) ? configuration["provider_config"] : {};
196291
+ if (!isObject5(json2)) return null;
196292
+ const configuration = isObject5(json2["configuration"]) ? json2["configuration"] : null;
196293
+ const providers = configuration !== null && isObject5(configuration["provider_config"]) ? configuration["provider_config"] : {};
194307
196294
  const variables = variablesOf(json2["variables"], sensitiveVariables(configuration));
194308
- if (isObject4(json2["planned_values"]) || Array.isArray(json2["resource_changes"])) {
194309
- const planned = isObject4(json2["planned_values"]) ? moduleResources(json2["planned_values"]["root_module"]) : [];
194310
- const prior = isObject4(json2["prior_state"]) && isObject4(json2["prior_state"]["values"]) ? moduleResources(json2["prior_state"]["values"]["root_module"]) : [];
196295
+ if (isObject5(json2["planned_values"]) || Array.isArray(json2["resource_changes"])) {
196296
+ const planned = isObject5(json2["planned_values"]) ? moduleResources(json2["planned_values"]["root_module"]) : [];
196297
+ const prior = isObject5(json2["prior_state"]) && isObject5(json2["prior_state"]["values"]) ? moduleResources(json2["prior_state"]["values"]["root_module"]) : [];
194311
196298
  const changes = changesOf(json2["resource_changes"]);
194312
196299
  const kept2 = new Set(planned.map((resource) => resource.address));
194313
196300
  const data = prior.filter((resource) => resource.mode === "data" && !kept2.has(resource.address));
@@ -194320,11 +196307,11 @@ function readPlanDocument(json2) {
194320
196307
  }
194321
196308
  const drift = /* @__PURE__ */ new Set();
194322
196309
  if (Array.isArray(json2["resource_drift"])) {
194323
- for (const entry2 of json2["resource_drift"]) if (isObject4(entry2) && typeof entry2["address"] === "string") drift.add(entry2["address"]);
196310
+ for (const entry2 of json2["resource_drift"]) if (isObject5(entry2) && typeof entry2["address"] === "string") drift.add(entry2["address"]);
194324
196311
  }
194325
196312
  return { kind: "plan", resources: [...planned, ...data], destroyed, changes, drift, configuration, providers, variables };
194326
196313
  }
194327
- if (isObject4(json2["values"]) && isObject4(json2["values"]["root_module"])) {
196314
+ if (isObject5(json2["values"]) && isObject5(json2["values"]["root_module"])) {
194328
196315
  return { kind: "state", resources: moduleResources(json2["values"]["root_module"]), destroyed: [], changes: /* @__PURE__ */ new Map(), drift: /* @__PURE__ */ new Set(), configuration, providers, variables };
194329
196316
  }
194330
196317
  if (Array.isArray(json2["resources"]) && typeof json2["version"] === "number") return { kind: "state", resources: rawStateResources(json2["resources"]), destroyed: [], changes: /* @__PURE__ */ new Map(), drift: /* @__PURE__ */ new Set(), configuration: null, providers: {}, variables: /* @__PURE__ */ new Map() };
@@ -194333,12 +196320,12 @@ function readPlanDocument(json2) {
194333
196320
  function rawStateResources(raw) {
194334
196321
  const out = [];
194335
196322
  for (const block of raw) {
194336
- if (!isObject4(block) || typeof block["type"] !== "string" || typeof block["name"] !== "string" || !Array.isArray(block["instances"])) continue;
196323
+ if (!isObject5(block) || typeof block["type"] !== "string" || typeof block["name"] !== "string" || !Array.isArray(block["instances"])) continue;
194337
196324
  const module = typeof block["module"] === "string" ? block["module"] : "";
194338
196325
  const mode = block["mode"] === "data" ? "data" : "managed";
194339
196326
  const base = `${module === "" ? "" : `${module}.`}${mode === "data" ? "data." : ""}${block["type"]}.${block["name"]}`;
194340
196327
  for (const instance of block["instances"]) {
194341
- if (!isObject4(instance)) continue;
196328
+ if (!isObject5(instance)) continue;
194342
196329
  const key = instance["index_key"];
194343
196330
  const index2 = typeof key === "string" || typeof key === "number" ? key : null;
194344
196331
  const address = `${base}${index2 === null ? "" : typeof index2 === "number" ? `[${index2}]` : `[${JSON.stringify(index2)}]`}`;
@@ -194346,20 +196333,20 @@ function rawStateResources(raw) {
194346
196333
  if (Array.isArray(instance["sensitive_attributes"])) {
194347
196334
  for (const path of instance["sensitive_attributes"]) {
194348
196335
  const first = Array.isArray(path) ? path[0] : null;
194349
- if (isObject4(first) && typeof first["value"] === "string") sensitive[first["value"]] = true;
196336
+ if (isObject5(first) && typeof first["value"] === "string") sensitive[first["value"]] = true;
194350
196337
  }
194351
196338
  }
194352
- out.push({ address, mode, type: block["type"], name: block["name"], index: index2, module, values: isObject4(instance["attributes"]) ? instance["attributes"] : {}, sensitive, dependsOn: strings2(instance["dependencies"]) });
196339
+ out.push({ address, mode, type: block["type"], name: block["name"], index: index2, module, values: isObject5(instance["attributes"]) ? instance["attributes"] : {}, sensitive, dependsOn: strings2(instance["dependencies"]) });
194353
196340
  }
194354
196341
  }
194355
196342
  return out;
194356
196343
  }
194357
- var MAX_MODULE_DEPTH2, isObject4, strings2, destroys;
194358
- var init_document3 = __esm({
196344
+ var MAX_MODULE_DEPTH2, isObject5, strings2, destroys;
196345
+ var init_document4 = __esm({
194359
196346
  "../../packages/diagrams/src/importers/terraform-plan/document.ts"() {
194360
196347
  "use strict";
194361
196348
  MAX_MODULE_DEPTH2 = 16;
194362
- isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
196349
+ isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
194363
196350
  strings2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
194364
196351
  destroys = (change) => change !== void 0 && change.actions.length === 1 && change.actions[0] === "delete";
194365
196352
  }
@@ -194367,14 +196354,14 @@ var init_document3 = __esm({
194367
196354
 
194368
196355
  // ../../packages/diagrams/src/importers/terraform-plan/references.ts
194369
196356
  function attributesOf3(expressions, depth = 0) {
194370
- if (!isObject4(expressions) || depth > MAX_NESTING) return [];
196357
+ if (!isObject5(expressions) || depth > MAX_NESTING) return [];
194371
196358
  const out = [];
194372
196359
  for (const [name2, value] of Object.entries(expressions)) {
194373
- if (isObject4(value) && ("references" in value || "constant_value" in value)) {
196360
+ if (isObject5(value) && ("references" in value || "constant_value" in value)) {
194374
196361
  const refs = strings3(value["references"]);
194375
196362
  out.push({ name: name2, refs: refs.filter((ref) => !SAME_INSTANCE.has(ref)), paired: refs.some((ref) => SAME_INSTANCE.has(ref)) });
194376
196363
  } else if (Array.isArray(value)) for (const block of value) out.push(...attributesOf3(block, depth + 1));
194377
- else if (isObject4(value)) out.push(...attributesOf3(value, depth + 1));
196364
+ else if (isObject5(value)) out.push(...attributesOf3(value, depth + 1));
194378
196365
  }
194379
196366
  return out;
194380
196367
  }
@@ -194399,13 +196386,13 @@ function parentOf3(module) {
194399
196386
  const at3 = module.lastIndexOf(".module.");
194400
196387
  return at3 === -1 ? "" : module.slice(0, at3);
194401
196388
  }
194402
- var MAX_DEPTH15, MAX_NESTING, TYPE2, SAME_INSTANCE, strings3, Configuration;
196389
+ var MAX_DEPTH17, MAX_NESTING, TYPE2, SAME_INSTANCE, strings3, Configuration;
194403
196390
  var init_references2 = __esm({
194404
196391
  "../../packages/diagrams/src/importers/terraform-plan/references.ts"() {
194405
196392
  "use strict";
194406
196393
  init_address();
194407
- init_document3();
194408
- MAX_DEPTH15 = 8;
196394
+ init_document4();
196395
+ MAX_DEPTH17 = 8;
194409
196396
  MAX_NESTING = 8;
194410
196397
  TYPE2 = /^[a-z][a-z0-9]*_[a-z0-9_]+$/;
194411
196398
  SAME_INSTANCE = /* @__PURE__ */ new Set(["each.key", "each.value", "count.index"]);
@@ -194421,11 +196408,11 @@ var init_references2 = __esm({
194421
196408
  moduleAt(address) {
194422
196409
  let module = this.root;
194423
196410
  for (const call of callsOf2(address)) {
194424
- const calls = isObject4(module) && isObject4(module["module_calls"]) ? module["module_calls"] : null;
196411
+ const calls = isObject5(module) && isObject5(module["module_calls"]) ? module["module_calls"] : null;
194425
196412
  const entry2 = calls === null ? null : calls[call.name];
194426
- module = isObject4(entry2) ? entry2["module"] : null;
196413
+ module = isObject5(entry2) ? entry2["module"] : null;
194427
196414
  }
194428
- return isObject4(module) ? module : null;
196415
+ return isObject5(module) ? module : null;
194429
196416
  }
194430
196417
  /** The call of a module instance, as its parent's configuration writes it: its source, version, and the values it passes. */
194431
196418
  callAt(address) {
@@ -194433,20 +196420,20 @@ var init_references2 = __esm({
194433
196420
  const last = calls[calls.length - 1];
194434
196421
  if (last === void 0) return null;
194435
196422
  const parent = this.moduleAt(parentOf3(address));
194436
- const entry2 = parent !== null && isObject4(parent["module_calls"]) ? parent["module_calls"][last.name] : null;
194437
- return isObject4(entry2) ? entry2 : null;
196423
+ const entry2 = parent !== null && isObject5(parent["module_calls"]) ? parent["module_calls"][last.name] : null;
196424
+ return isObject5(entry2) ? entry2 : null;
194438
196425
  }
194439
196426
  /** The configured block of a resource, by the module instance it is in and its address inside the module. */
194440
196427
  resourceAt(module, local2) {
194441
196428
  const config2 = this.moduleAt(module);
194442
196429
  const list = config2 !== null && Array.isArray(config2["resources"]) ? config2["resources"] : [];
194443
- const found = list.find((entry2) => isObject4(entry2) && entry2["address"] === local2);
196430
+ const found = list.find((entry2) => isObject5(entry2) && entry2["address"] === local2);
194444
196431
  if (found === void 0) return null;
194445
196432
  return { attributes: attributesOf3(found["expressions"]), dependsOn: strings3(found["depends_on"]), providerKey: typeof found["provider_config_key"] === "string" ? found["provider_config_key"] : null };
194446
196433
  }
194447
196434
  /** What a reference written in a module instance names, followed through module outputs and the variables its call passes. */
194448
196435
  resolve(ref, module, depth = 0) {
194449
- if (depth > MAX_DEPTH15) return [];
196436
+ if (depth > MAX_DEPTH17) return [];
194450
196437
  const steps = references(ref)[0] ?? [];
194451
196438
  const [first, second, third, fourth] = steps;
194452
196439
  const name2 = (step) => step?.kind === "name" ? step.text : null;
@@ -194471,9 +196458,9 @@ var init_references2 = __esm({
194471
196458
  const out = [];
194472
196459
  for (const instance of chosen) {
194473
196460
  const config2 = this.moduleAt(instance);
194474
- const outputs = config2 !== null && isObject4(config2["outputs"]) ? config2["outputs"] : {};
196461
+ const outputs = config2 !== null && isObject5(config2["outputs"]) ? config2["outputs"] : {};
194475
196462
  const entry2 = outputs[output2];
194476
- const expression2 = isObject4(entry2) && isObject4(entry2["expression"]) ? entry2["expression"] : null;
196463
+ const expression2 = isObject5(entry2) && isObject5(entry2["expression"]) ? entry2["expression"] : null;
194477
196464
  for (const inner of strings3(expression2?.["references"])) out.push(...this.resolve(inner, instance, depth + 1));
194478
196465
  }
194479
196466
  return out;
@@ -194482,9 +196469,9 @@ var init_references2 = __esm({
194482
196469
  variable(module, variable, depth) {
194483
196470
  if (module === "") return [{ kind: "var", name: variable }];
194484
196471
  const call = this.callAt(module);
194485
- const expressions = call !== null && isObject4(call["expressions"]) ? call["expressions"] : {};
196472
+ const expressions = call !== null && isObject5(call["expressions"]) ? call["expressions"] : {};
194486
196473
  const passed = expressions[variable];
194487
- const refs = isObject4(passed) ? strings3(passed["references"]).filter((ref) => !SAME_INSTANCE.has(ref)) : [];
196474
+ const refs = isObject5(passed) ? strings3(passed["references"]).filter((ref) => !SAME_INSTANCE.has(ref)) : [];
194488
196475
  return refs.flatMap((ref) => this.resolve(ref, parentOf3(module), depth + 1));
194489
196476
  }
194490
196477
  };
@@ -194493,7 +196480,7 @@ var init_references2 = __esm({
194493
196480
 
194494
196481
  // ../../packages/diagrams/src/importers/terraform-plan/resources.ts
194495
196482
  function rawOf(value) {
194496
- if (isObject4(value)) {
196483
+ if (isObject5(value)) {
194497
196484
  const entries2 = Object.entries(value).filter((entry2) => typeof entry2[1] === "string");
194498
196485
  return `{ ${entries2.map(([key, entry2]) => `${key} = ${JSON.stringify(entry2)}`).join(", ")} }`;
194499
196486
  }
@@ -194507,7 +196494,7 @@ function valuesOf2(resource) {
194507
196494
  }
194508
196495
  const tags = resource.values["tags"];
194509
196496
  const hidden = resource.sensitive["tags"];
194510
- if (isObject4(tags) && typeof tags["Name"] === "string" && !(isObject4(hidden) && hidden["Name"] === true) && hidden !== true) out.set("tags.Name", tags["Name"]);
196497
+ if (isObject5(tags) && typeof tags["Name"] === "string" && !(isObject5(hidden) && hidden["Name"] === true) && hidden !== true) out.set("tags.Name", tags["Name"]);
194511
196498
  return out;
194512
196499
  }
194513
196500
  function blockOf2(resource, values, start2) {
@@ -194525,7 +196512,7 @@ function stringsOf(values, sensitive) {
194525
196512
  if (out.length >= MAX_VALUES || depth > MAX_VALUE_DEPTH || hidden === true) return;
194526
196513
  if (typeof value === "string") out.push({ name: name2, value });
194527
196514
  else if (Array.isArray(value)) value.forEach((item, index2) => walk(item, name2, Array.isArray(hidden) ? hidden[index2] : void 0, depth + 1));
194528
- else if (isObject4(value)) for (const [key, entry2] of Object.entries(value)) walk(entry2, key, isObject4(hidden) ? hidden[key] : void 0, depth + 1);
196515
+ else if (isObject5(value)) for (const [key, entry2] of Object.entries(value)) walk(entry2, key, isObject5(hidden) ? hidden[key] : void 0, depth + 1);
194529
196516
  };
194530
196517
  for (const [name2, value] of Object.entries(values)) if (!OWN_IDS.has(name2) && !SKIPPED2.has(name2) && name2 !== "tags") walk(value, name2, sensitive[name2], 0);
194531
196518
  return out;
@@ -194546,7 +196533,7 @@ function planResources(document2, text3) {
194546
196533
  if (byBase.has(same)) alias.set(baseOf(resource), same);
194547
196534
  }
194548
196535
  const instances = [...new Set(all.map((resource) => resource.module).filter((module) => module !== "").flatMap((module) => callsOf2(module).map((_call, index2, calls) => calls.slice(0, index2 + 1).map((call) => `module.${call.name}${call.suffix}`).join("."))))];
194549
- const configuration = new Configuration(document2.configuration === null ? null : isObject4(document2.configuration["root_module"]) ? document2.configuration["root_module"] : null, instances);
196536
+ const configuration = new Configuration(document2.configuration === null ? null : isObject5(document2.configuration["root_module"]) ? document2.configuration["root_module"] : null, instances);
194550
196537
  const expand2 = (target, own2, paired) => {
194551
196538
  if (target.kind !== "item") return [];
194552
196539
  const base = alias.get(target.address) ?? target.address;
@@ -194688,7 +196675,7 @@ var init_resources2 = __esm({
194688
196675
  init_generic();
194689
196676
  init_graph4();
194690
196677
  init_resources();
194691
- init_document3();
196678
+ init_document4();
194692
196679
  init_references2();
194693
196680
  MAX_VALUE_DEPTH = 6;
194694
196681
  MAX_VALUES = 400;
@@ -194719,19 +196706,19 @@ function importedOf(json2) {
194719
196706
  const out = /* @__PURE__ */ new Set();
194720
196707
  const changes = json2["resource_changes"];
194721
196708
  if (Array.isArray(changes)) {
194722
- for (const entry2 of changes) if (isObject4(entry2) && isObject4(entry2["change"]) && entry2["change"]["importing"] !== void 0 && typeof entry2["address"] === "string") out.add(entry2["address"]);
196709
+ for (const entry2 of changes) if (isObject5(entry2) && isObject5(entry2["change"]) && entry2["change"]["importing"] !== void 0 && typeof entry2["address"] === "string") out.add(entry2["address"]);
194723
196710
  }
194724
196711
  return out;
194725
196712
  }
194726
196713
  function accountsOf(document2, types) {
194727
196714
  const out = /* @__PURE__ */ new Map();
194728
196715
  for (const entry2 of Object.values(document2.providers)) {
194729
- if (!isObject4(entry2) || typeof entry2["name"] !== "string") continue;
196716
+ if (!isObject5(entry2) || typeof entry2["name"] !== "string") continue;
194730
196717
  const cloud = providerOf2(`${entry2["name"]}_x`);
194731
196718
  if (cloud === null || out.get(cloud) !== void 0) continue;
194732
- const expressions = isObject4(entry2["expressions"]) ? entry2["expressions"] : {};
194733
- const project = isObject4(expressions["project"]) ? expressions["project"]["constant_value"] : void 0;
194734
- out.set(cloud, cloud === "gcp" && typeof project === "string" ? project : null);
196719
+ const expressions = isObject5(entry2["expressions"]) ? entry2["expressions"] : {};
196720
+ const project2 = isObject5(expressions["project"]) ? expressions["project"]["constant_value"] : void 0;
196721
+ out.set(cloud, cloud === "gcp" && typeof project2 === "string" ? project2 : null);
194735
196722
  }
194736
196723
  for (const type of types) {
194737
196724
  const cloud = providerOf2(type);
@@ -194743,9 +196730,9 @@ function regionsOf2(document2) {
194743
196730
  const out = /* @__PURE__ */ new Map();
194744
196731
  const byProvider = /* @__PURE__ */ new Map();
194745
196732
  for (const [key, entry2] of Object.entries(document2.providers)) {
194746
- if (!isObject4(entry2) || typeof entry2["name"] !== "string" || key.includes(":")) continue;
194747
- const expressions = isObject4(entry2["expressions"]) ? entry2["expressions"] : {};
194748
- const region = isObject4(expressions["region"]) ? expressions["region"]["constant_value"] : void 0;
196733
+ if (!isObject5(entry2) || typeof entry2["name"] !== "string" || key.includes(":")) continue;
196734
+ const expressions = isObject5(entry2["expressions"]) ? entry2["expressions"] : {};
196735
+ const region = isObject5(expressions["region"]) ? expressions["region"]["constant_value"] : void 0;
194749
196736
  if (typeof region !== "string" || region === "") continue;
194750
196737
  out.set(key, region);
194751
196738
  byProvider.set(entry2["name"], /* @__PURE__ */ new Set([...byProvider.get(entry2["name"]) ?? [], region]));
@@ -194778,7 +196765,7 @@ function readTerraformPlan(text3, context) {
194778
196765
  return null;
194779
196766
  }
194780
196767
  const document2 = readPlanDocument(json2);
194781
- if (document2 === null || !isObject4(json2)) {
196768
+ if (document2 === null || !isObject5(json2)) {
194782
196769
  context.report.add(DIAGNOSTIC_CODES.importNoShape, START, "The JSON is neither a plan (planned_values and resource_changes) nor a state (values.root_module).", "Run terraform plan -out=plan.out, then send the output of terraform show -json plan.out; or send terraform show -json for the state.");
194783
196770
  return null;
194784
196771
  }
@@ -194846,7 +196833,7 @@ var init_terraform_plan = __esm({
194846
196833
  init_labels();
194847
196834
  init_resources();
194848
196835
  init_values2();
194849
- init_document3();
196836
+ init_document4();
194850
196837
  init_resources2();
194851
196838
  CHANGES = {
194852
196839
  create: { tone: "green", word: "create", said: "one marked + is created", mark: "create" },
@@ -194869,7 +196856,7 @@ function childOf(parent, name2, line3) {
194869
196856
  parent.push(entry2);
194870
196857
  return entry2;
194871
196858
  }
194872
- function nameOf8(raw) {
196859
+ function nameOf9(raw) {
194873
196860
  const text3 = cleanText(raw, "");
194874
196861
  const isDir = text3.endsWith("/") && text3.length > 1;
194875
196862
  return { name: isDir ? text3.slice(0, -1) : text3, isDir };
@@ -194896,7 +196883,7 @@ function readCommand(lines) {
194896
196883
  depth += 1;
194897
196884
  at3 += 4;
194898
196885
  }
194899
- const { name: name2, isDir } = nameOf8(line3.slice(at3));
196886
+ const { name: name2, isDir } = nameOf9(line3.slice(at3));
194900
196887
  if (name2 === "") continue;
194901
196888
  if (!branched && root === null && top.length === 0) {
194902
196889
  root = name2;
@@ -194946,7 +196933,7 @@ function readOutline(lines) {
194946
196933
  for (const [index2, raw] of lines.entries()) {
194947
196934
  if (raw.trim() === "") continue;
194948
196935
  const { width, text: text3 } = indentOf6(raw);
194949
- const { name: name2, isDir } = nameOf8(text3);
196936
+ const { name: name2, isDir } = nameOf9(text3);
194950
196937
  if (name2 === "") continue;
194951
196938
  while (stack.length > 0 && (stack[stack.length - 1]?.width ?? 0) >= width) stack.pop();
194952
196939
  const parent = stack[stack.length - 1]?.entry;
@@ -195162,6 +197149,8 @@ var init_registry4 = __esm({
195162
197149
  "../../packages/diagrams/src/importers/registry.ts"() {
195163
197150
  "use strict";
195164
197151
  init_arm();
197152
+ init_asyncapi();
197153
+ init_azure_pipelines();
195165
197154
  init_backstage();
195166
197155
  init_bicep();
195167
197156
  init_cdk();
@@ -195172,6 +197161,7 @@ var init_registry4 = __esm({
195172
197161
  init_git_log();
195173
197162
  init_helm();
195174
197163
  init_github_actions();
197164
+ init_gitlab_ci();
195175
197165
  init_json();
195176
197166
  init_kubernetes();
195177
197167
  init_kustomize();
@@ -195179,6 +197169,7 @@ var init_registry4 = __esm({
195179
197169
  init_prisma();
195180
197170
  init_pulumi();
195181
197171
  init_sql();
197172
+ init_structurizr();
195182
197173
  init_terraform();
195183
197174
  init_terraform_plan();
195184
197175
  init_tree5();
@@ -195199,7 +197190,11 @@ var init_registry4 = __esm({
195199
197190
  helm: helmImporter,
195200
197191
  kustomize: kustomizeImporter,
195201
197192
  backstage: backstageImporter,
197193
+ structurizr: structurizrImporter,
197194
+ asyncapi: asyncapiImporter,
195202
197195
  "github-actions": githubActionsImporter,
197196
+ "gitlab-ci": gitlabCiImporter,
197197
+ "azure-pipelines": azurePipelinesImporter,
195203
197198
  csv: csvImporter,
195204
197199
  json: jsonImporter,
195205
197200
  tree: treeImporter,
@@ -196698,14 +198693,14 @@ function isProduct(ref) {
196698
198693
  return cloudSetOf2(ref) !== void 0 || setOf(ref) === "simple";
196699
198694
  }
196700
198695
  function isTheIcon(query, ref) {
196701
- if (normalizePhrase(nameOf9(ref)) === query) return true;
198696
+ if (normalizePhrase(nameOf10(ref)) === query) return true;
196702
198697
  if (customIconPhrases(ref)?.has(query) === true) return true;
196703
198698
  const set2 = cloudSetOf2(ref);
196704
- if (set2 !== void 0 && nameOf9(ref) === "cloud" && CLOUD_PROVIDER_SPECS[set2].names.includes(query)) return true;
198699
+ if (set2 !== void 0 && nameOf10(ref) === "cloud" && CLOUD_PROVIDER_SPECS[set2].names.includes(query)) return true;
196705
198700
  return (phrasesByText().get(query) ?? []).some((phrase2) => phrase2.ref === ref && !phrase2.isGeneric);
196706
198701
  }
196707
198702
  function nameHasWords(query, ref) {
196708
- const words2 = nameOf9(ref).split("-");
198703
+ const words2 = nameOf10(ref).split("-");
196709
198704
  return query.split(" ").every((word2) => words2.includes(word2));
196710
198705
  }
196711
198706
  function isContested(query, top, others, needsSameMatch) {
@@ -196721,7 +198716,7 @@ function classifyIcon(name2, results) {
196721
198716
  if (nameHasWords(query, top) && !isContested(query, top, others, false)) return "strong";
196722
198717
  return "ambiguous";
196723
198718
  }
196724
- var phraseIndex, setOf, nameOf9, cloudSetOf2;
198719
+ var phraseIndex, setOf, nameOf10, cloudSetOf2;
196725
198720
  var init_icon_confidence = __esm({
196726
198721
  "../../packages/diagrams/src/tools/workflows/icon-confidence.ts"() {
196727
198722
  "use strict";
@@ -196729,7 +198724,7 @@ var init_icon_confidence = __esm({
196729
198724
  init_icons();
196730
198725
  phraseIndex = null;
196731
198726
  setOf = (ref) => ref.slice(0, ref.indexOf(":"));
196732
- nameOf9 = (ref) => ref.slice(ref.indexOf(":") + 1);
198727
+ nameOf10 = (ref) => ref.slice(ref.indexOf(":") + 1);
196733
198728
  cloudSetOf2 = (ref) => CLOUD_SETS.find((set2) => set2 === setOf(ref));
196734
198729
  }
196735
198730
  });
@@ -197080,13 +199075,13 @@ function isMarkCell(cell2) {
197080
199075
  const word2 = cell2.trim().toLowerCase();
197081
199076
  return YES_WORDS.includes(word2) || NO_WORDS.includes(word2) || TICKS.includes(word2) || CROSSES2.includes(word2) || word2 === "partial";
197082
199077
  }
197083
- function wordsOf5(text3) {
199078
+ function wordsOf6(text3) {
197084
199079
  return text3.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter((word2) => word2 !== "");
197085
199080
  }
197086
199081
  function cleanLabel2(text3) {
197087
199082
  return text3.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[`*_~"\\]/g, "").replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
197088
199083
  }
197089
- function stepLabel(text3) {
199084
+ function stepLabel2(text3) {
197090
199085
  const clean = cleanLabel2(text3).replace(/[.;]$/, "");
197091
199086
  const capital2 = `${clean.charAt(0).toUpperCase()}${clean.slice(1)}`;
197092
199087
  if (capital2.length <= ONE_LINE) return { label: capital2, sub: null };
@@ -197097,7 +199092,7 @@ function stepLabel(text3) {
197097
199092
  return { label: capital2, sub: null };
197098
199093
  }
197099
199094
  function stepText2(text3) {
197100
- const { label, sub } = stepLabel(text3);
199095
+ const { label, sub } = stepLabel2(text3);
197101
199096
  return sub === null ? `"${label}"` : `"${label}" sub="${sub}"`;
197102
199097
  }
197103
199098
  function withDescription(source, description) {
@@ -197114,7 +199109,7 @@ function idMaker(fallback) {
197114
199109
  let count3 = 0;
197115
199110
  return (label) => {
197116
199111
  count3 += 1;
197117
- const words2 = wordsOf5(label).slice(0, 3);
199112
+ const words2 = wordsOf6(label).slice(0, 3);
197118
199113
  let id = words2.join("_");
197119
199114
  if (!/^[a-z]/.test(id) || RESERVED_IDS.has(id)) id = `${fallback}${count3}`;
197120
199115
  id = id.slice(0, 24);
@@ -197852,7 +199847,7 @@ function singular2(word2) {
197852
199847
  function iconFor(name2) {
197853
199848
  const product = iconNamedBy(name2, null);
197854
199849
  if (product !== null) return product;
197855
- for (const word2 of wordsOf5(name2)) {
199850
+ for (const word2 of wordsOf6(name2)) {
197856
199851
  const ref = ROLE_ICONS2[word2] ?? ROLE_ICONS2[singular2(word2)];
197857
199852
  if (ref !== void 0 && isKnownIcon(ref)) return ref;
197858
199853
  }
@@ -197944,7 +199939,7 @@ var init_icons4 = __esm({
197944
199939
  function chain(ids, arrow = "->", label = "") {
197945
199940
  return ids.slice(1).map((id, index2) => `${ids[index2] ?? id} ${arrow} ${id}${label}`);
197946
199941
  }
197947
- function layersOf(material) {
199942
+ function layersOf2(material) {
197948
199943
  const lists = listsOf(material);
197949
199944
  if (lists.length >= 2) return lists.slice(0, 6).map((list) => ({ name: list.label, blocks: list.values.slice(0, 6) }));
197950
199945
  if (material.items.some((item) => item.depth > 0)) {
@@ -197966,10 +199961,15 @@ function partType(name2) {
197966
199961
  if (/\b(web app|browser|mobile|app|client|frontend)\b/i.test(name2)) return "client";
197967
199962
  return "service";
197968
199963
  }
199964
+ function roleOption(verb) {
199965
+ const named = verbNamedBy(verb);
199966
+ const role = named === null ? null : EDGE_STYLES[named].role;
199967
+ return role === null ? "" : ` role=${role}`;
199968
+ }
197969
199969
  function linkedFlow(material, title) {
197970
199970
  const ids = idMaker("part");
197971
199971
  const nodes = /* @__PURE__ */ new Map();
197972
- const nodeOf4 = (label) => {
199972
+ const nodeOf5 = (label) => {
197973
199973
  const key = label.toLowerCase();
197974
199974
  const known3 = nodes.get(key);
197975
199975
  if (known3 !== void 0 && /^[a-z]/.test(known3.label) && /^[A-Z]/.test(label)) known3.label = label;
@@ -197978,8 +199978,8 @@ function linkedFlow(material, title) {
197978
199978
  nodes.set(key, node3);
197979
199979
  return node3.id;
197980
199980
  };
197981
- const links = material.relations.slice(0, MOST * 2).map((relation2) => ({ from: nodeOf4(relation2.from), to: nodeOf4(relation2.to), verb: relation2.verb }));
197982
- const edges = links.map((link2) => `${link2.from} -> ${link2.to} : ${quoted4(link2.verb)}`);
199981
+ const links = material.relations.slice(0, MOST * 2).map((relation2) => ({ from: nodeOf5(relation2.from), to: nodeOf5(relation2.to), verb: relation2.verb }));
199982
+ const edges = links.map((link2) => `${link2.from} -> ${link2.to} : ${quoted4(link2.verb)}${roleOption(link2.verb)}`);
197983
199983
  return sourceOf(`flow ${quoted4(title)} direction=${directionFor(longestChain2(links))}`, [
197984
199984
  ...[...nodes.values()].map((node3) => `node ${node3.id} ${partType(node3.label)} ${quoted4(node3.label)}${iconOption(node3.label)}`),
197985
199985
  "",
@@ -197998,7 +199998,7 @@ function longestChain2(links) {
197998
199998
  };
197999
199999
  return Math.max(0, ...links.map((link2) => walk(link2.from, /* @__PURE__ */ new Set([link2.from]))));
198000
200000
  }
198001
- function stepsOf(material) {
200001
+ function stepsOf2(material) {
198002
200002
  const pairs = material.labelled.filter((line3) => cleanLabel2(line3.text) !== "");
198003
200003
  const repeated = new Set(pairs.map((line3) => line3.label.toLowerCase())).size < pairs.length;
198004
200004
  if (pairs.length >= 2 && repeated || pairs.length >= 3) return pairs.slice(0, MOST).map((pair2) => ({ label: pair2.label, text: pair2.text }));
@@ -198015,6 +200015,8 @@ var WIDEST_CHAIN, SYSTEM_BUILDERS;
198015
200015
  var init_systems = __esm({
198016
200016
  "../../packages/diagrams/src/guidance/starters/systems.ts"() {
198017
200017
  "use strict";
200018
+ init_constants();
200019
+ init_label_icon();
198018
200020
  init_icons4();
198019
200021
  init_text6();
198020
200022
  init_shared4();
@@ -198101,7 +200103,7 @@ var init_systems = __esm({
198101
200103
  },
198102
200104
  layers: (material, title) => {
198103
200105
  const ids = idMaker("block");
198104
- const body6 = layersOf(material).flatMap((layer2) => {
200106
+ const body6 = layersOf2(material).flatMap((layer2) => {
198105
200107
  const blocks2 = distinctLabels(layer2.blocks);
198106
200108
  return [`layer ${quoted4(layer2.name)} {`, ...(blocks2.length > 0 ? blocks2 : [layer2.name]).map((block) => ` block ${ids(block)} ${quoted4(block)}`), "}", ""];
198107
200109
  });
@@ -198109,7 +200111,7 @@ var init_systems = __esm({
198109
200111
  },
198110
200112
  swimlane: (material, title) => {
198111
200113
  const ids = idMaker("step");
198112
- const steps = stepsOf(material);
200114
+ const steps = stepsOf2(material);
198113
200115
  const laneNames = distinctLabels(steps.map((step) => step.label)).slice(0, 6);
198114
200116
  const placed = steps.map((step) => ({ lane: laneNames.findIndex((name2) => name2.toLowerCase() === cleanLabel2(step.label).toLowerCase()), label: cleanLabel2(step.text) })).filter((step) => step.lane >= 0).map((step) => ({ ...step, id: ids(step.label) }));
198115
200117
  const begin = ids("Start");
@@ -198155,13 +200157,13 @@ function stem(word2) {
198155
200157
  return word2;
198156
200158
  }
198157
200159
  function keyOf4(text3) {
198158
- return wordsOf5(text3).map(stem).join(" ");
200160
+ return wordsOf6(text3).map(stem).join(" ");
198159
200161
  }
198160
200162
  function wordPoints(texts) {
198161
200163
  const strong = /* @__PURE__ */ new Map();
198162
200164
  const weak = /* @__PURE__ */ new Map();
198163
200165
  for (const { text: text3, weight } of texts) {
198164
- const words2 = wordsOf5(text3).map(stem);
200166
+ const words2 = wordsOf6(text3).map(stem);
198165
200167
  const seen = /* @__PURE__ */ new Set();
198166
200168
  for (let start2 = 0; start2 < words2.length; start2 += 1) {
198167
200169
  let key = "";
@@ -198284,8 +200286,8 @@ var init_rank = __esm({
198284
200286
  MEDIUM = 4;
198285
200287
  INDEX = /* @__PURE__ */ new Map();
198286
200288
  for (const intent of INTENTS) {
198287
- for (const [phrases, isStrong] of [[intent.strong, true], [intent.weak, false]]) {
198288
- for (const phrase2 of phrases) INDEX.set(keyOf4(phrase2), [...INDEX.get(keyOf4(phrase2)) ?? [], { id: intent.id, isStrong }]);
200289
+ for (const [phrases2, isStrong] of [[intent.strong, true], [intent.weak, false]]) {
200290
+ for (const phrase2 of phrases2) INDEX.set(keyOf4(phrase2), [...INDEX.get(keyOf4(phrase2)) ?? [], { id: intent.id, isStrong }]);
198289
200291
  }
198290
200292
  }
198291
200293
  LONGEST_PHRASE = Math.max(...[...INDEX.keys()].map((key) => key.split(" ").length));
@@ -198889,7 +200891,7 @@ var init_draw_for_view = __esm({
198889
200891
  });
198890
200892
 
198891
200893
  // ../../packages/diagrams/src/view/index.ts
198892
- var init_view = __esm({
200894
+ var init_view2 = __esm({
198893
200895
  "../../packages/diagrams/src/view/index.ts"() {
198894
200896
  "use strict";
198895
200897
  init_fragment();
@@ -198974,7 +200976,7 @@ var init_view_link = __esm({
198974
200976
  init_constants();
198975
200977
  init_apply_defaults();
198976
200978
  init_schema4();
198977
- init_view();
200979
+ init_view2();
198978
200980
  SOURCE_OF = {
198979
200981
  draw_diagram: (args, answer) => answer["valid"] === true ? args["source"] : null,
198980
200982
  draw_from_pattern: (_args, answer) => answer["valid"] === true ? answer["source"] : null,
@@ -199379,7 +201381,7 @@ var init_server_info = __esm({
199379
201381
  `Draws ${joinWords(kindNouns(), "and")} diagrams from a small line-oriented text language.`,
199380
201382
  "Prefer the workflow tools: each does the work of several single tools in one call.",
199381
201383
  "When you are unsure which kind of diagram fits, call suggest_diagram with the request in plain words and any data: it ranks the kinds and gives a starter that parses; for a whole document, plan_document_diagrams returns the sections a picture would help, each with a starter.",
199382
- "When the diagram should come from a file the person has (SQL, Prisma, or Drizzle models, an OpenAPI document, Terraform, Bicep, ARM, Pulumi, CloudFormation, or the CDK, docker-compose, Kubernetes manifests, Helm, Kustomize, a Backstage catalog, a GitHub Actions workflow, CSV or JSON data, a folder listing, or a git log), call import_source with its text and format: it builds the whole diagram from the file in one call, and its diagnostics say what was left out get_diagram_syntax with topic import-formats says what each format reads.",
201384
+ "When the diagram should come from a file the person has (SQL, Prisma, or Drizzle models, an OpenAPI document, Terraform, Bicep, ARM, Pulumi, CloudFormation, or the CDK, docker-compose, Kubernetes manifests, Helm, Kustomize, a Backstage catalog, a Structurizr workspace, AsyncAPI documents, a GitHub Actions workflow, a GitLab CI/CD or Azure Pipelines pipeline, CSV or JSON data, a folder listing, or a git log), call import_source with its text and format: it builds the whole diagram from the file in one call, and its diagnostics say what was left out get_diagram_syntax with topic import-formats says what each format reads.",
199383
201385
  "For a request to draw a common architecture, such as a three-tier network, a serverless API, a container platform, or a queue with workers, call find_pattern first with the request in plain words; when a pattern fits, call draw_from_pattern with its id, a provider flavour (neutral, aws, azure, or gcp), and slot values that rename its parts, or get_pattern to read and edit its source, and when nothing fits, write the diagram from scratch with draw_diagram.",
199384
201386
  "Call get_diagram_syntax once with no kind to read the shared language, and again with kind set to the kind you draw for its statements, limits, and example.",
199385
201387
  'Write the source and call draw_diagram: it returns every error with line, column, and hint and draws nothing until the source is valid, so fix every error and call again; once valid it returns the warnings, an outline to check the meaning, and the SVG for light and dark. The pictures are most of the answer and you cannot look at them, so pass svg "never" to check a diagram, and draw only when you will save, show, or publish the pictures; when a drawing is too big to read in one answer the SVG is left out, picturesOmitted says so, and svg "always" forces it.',
@@ -199387,6 +201389,7 @@ var init_server_info = __esm({
199387
201389
  "Set embedSource true on draw_diagram whenever you save an SVG into a repository, so the file carries its source and read_diagram can give it back next time; the source is then readable by anyone who has the file.",
199388
201390
  ...SAVING_INSTRUCTIONS,
199389
201391
  'An item with no icon= gets the icon its label names when the match is certain, such as the Postgres mark on a node labelled "Postgres", so label items with the product they are; icon=none keeps one item plain and icons=off on the header keeps them all plain.',
201392
+ 'For a finished look, label flow arrows with verbs ("reads", "publishes", "deploys to") so they draw icons, add role= (data, async, identity, private) to the lines that carry those, and give groups a type=; style in get_diagram_syntax says more.',
199390
201393
  "Use map_icons to choose icons for many names at once; it never guesses, so choose from the candidates for an ambiguous name, and never invent an icon.",
199391
201394
  "To move Mermaid to Plotwork, call convert_mermaid with one diagram, read every diagnostic and its hint, then choose node types and icons; for a whole Markdown file call sync_markdown with the document, its path, and convertMermaid true, write the returned markdown and files, then review the diff.",
199392
201395
  "When the repository has a plotwork.config.json (or .plotwork/config.json) at its root, read it and pass it as settings to every tool that takes settings: its defaults fill the header options the source leaves out, and broken style rules come back as warnings to fix.",
@@ -199763,7 +201766,7 @@ var init_create_server = __esm({
199763
201766
  "use strict";
199764
201767
  init_dist();
199765
201768
  init_tools();
199766
- init_view();
201769
+ init_view2();
199767
201770
  init_activity();
199768
201771
  init_audit();
199769
201772
  init_engine_runner();
@@ -200227,7 +202230,7 @@ var VIEWER_URL_VARIABLE;
200227
202230
  var init_viewer_address = __esm({
200228
202231
  "src/viewer-address.ts"() {
200229
202232
  "use strict";
200230
- init_view();
202233
+ init_view2();
200231
202234
  VIEWER_URL_VARIABLE = "PLOTWORK_VIEWER_URL";
200232
202235
  }
200233
202236
  });