aact 2.1.5 → 3.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,248 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'pathe';
3
+ import { p as parseCsvTags } from '../shared/aact.BpV1UCZJ.mjs';
4
+ import { b as buildModel } from '../shared/aact.CfImn7en.mjs';
5
+
6
+ const DATABASE_TECHS = Object.freeze([
7
+ "postgresql",
8
+ "postgres",
9
+ "mysql",
10
+ "mariadb",
11
+ "mongodb",
12
+ "mongo",
13
+ "redis",
14
+ "elasticsearch",
15
+ "dynamodb",
16
+ "cassandra",
17
+ "sqlite",
18
+ "oracle",
19
+ "sqlserver",
20
+ "mssql",
21
+ "clickhouse",
22
+ "snowflake",
23
+ "bigquery",
24
+ "database",
25
+ "db"
26
+ ]);
27
+ const QUEUE_TECHS = Object.freeze([
28
+ "kafka",
29
+ "rabbitmq",
30
+ "rabbit",
31
+ "nats",
32
+ "sqs",
33
+ "sns",
34
+ "amqp",
35
+ "activemq",
36
+ "pulsar",
37
+ "kinesis",
38
+ "redpanda",
39
+ "eventbridge",
40
+ "servicebus"
41
+ ]);
42
+ const matchesAny = (text, patterns) => patterns.some((p) => text.includes(p));
43
+ const inferKindFromTechnology = (technology, name) => {
44
+ const techLower = technology?.toLowerCase() ?? "";
45
+ const nameLower = name?.toLowerCase() ?? "";
46
+ if (matchesAny(techLower, DATABASE_TECHS)) return "ContainerDb";
47
+ if (matchesAny(techLower, QUEUE_TECHS)) return "ContainerQueue";
48
+ if (nameLower.endsWith(" db") || nameLower.endsWith("_db") || nameLower.endsWith("-db") || nameLower.endsWith("database")) {
49
+ return "ContainerDb";
50
+ }
51
+ if (nameLower.endsWith(" queue") || nameLower.endsWith("_queue") || nameLower.endsWith("-queue") || nameLower.endsWith(" topic") || nameLower.endsWith("_topic")) {
52
+ return "ContainerQueue";
53
+ }
54
+ return "Container";
55
+ };
56
+
57
+ const STRUCTURIZR_LOCATION_EXTERNAL = "External";
58
+ const STRUCTURIZR_INTERACTION_ASYNC = "Asynchronous";
59
+ const STRUCTURIZR_TAG_ASYNC = "async";
60
+
61
+ const dslId = (id, properties) => properties?.["structurizr.dsl.identifier"] ?? id;
62
+ const toProperties = (base, group, perspectives) => {
63
+ const out = {};
64
+ if (base) {
65
+ for (const [k, v] of Object.entries(base)) {
66
+ if (typeof v === "string") out[k] = v;
67
+ }
68
+ }
69
+ if (group !== void 0 && group.length > 0) out.group = group;
70
+ if (perspectives) {
71
+ for (const [name, p] of Object.entries(perspectives)) {
72
+ out[`perspective.${name}`] = p.description;
73
+ if (p.value !== void 0) out[`perspective.${name}.value`] = p.value;
74
+ }
75
+ }
76
+ if (Object.keys(out).length === 0) return void 0;
77
+ return Object.freeze(out);
78
+ };
79
+ const isExternal = (system) => system.location === STRUCTURIZR_LOCATION_EXTERNAL || (system.tags?.includes(STRUCTURIZR_LOCATION_EXTERNAL) ?? false);
80
+ const buildPersonContainer = (p) => ({
81
+ name: dslId(p.id, p.properties),
82
+ label: p.name,
83
+ kind: "Person",
84
+ external: false,
85
+ description: p.description ?? "",
86
+ tags: parseCsvTags(p.tags),
87
+ relations: [],
88
+ link: p.url,
89
+ properties: toProperties(p.properties, p.group, p.perspectives)
90
+ });
91
+ const buildExternalSystemContainer = (s) => ({
92
+ name: dslId(s.id, s.properties),
93
+ label: s.name,
94
+ kind: "System",
95
+ external: true,
96
+ description: s.description ?? "",
97
+ tags: parseCsvTags(s.tags),
98
+ relations: [],
99
+ link: s.url,
100
+ properties: toProperties(s.properties, s.group, s.perspectives)
101
+ });
102
+ const buildContainer = (c) => ({
103
+ name: dslId(c.id, c.properties),
104
+ label: c.name,
105
+ kind: inferKindFromTechnology(c.technology, c.name),
106
+ external: false,
107
+ description: c.description ?? "",
108
+ technology: c.technology,
109
+ tags: parseCsvTags(c.tags),
110
+ relations: [],
111
+ link: c.url,
112
+ properties: toProperties(c.properties, c.group, c.perspectives)
113
+ });
114
+ const buildSystemBoundary = (s) => ({
115
+ name: dslId(s.id, s.properties),
116
+ label: s.name,
117
+ kind: "System",
118
+ description: s.description,
119
+ tags: parseCsvTags(s.tags),
120
+ containerNames: (s.containers ?? []).map((c) => dslId(c.id, c.properties)),
121
+ boundaryNames: [],
122
+ link: s.url,
123
+ properties: toProperties(s.properties, s.group, s.perspectives)
124
+ });
125
+ const buildRelation = (rel, targetName) => {
126
+ const baseTags = parseCsvTags(rel.tags);
127
+ const tags = rel.interactionStyle === STRUCTURIZR_INTERACTION_ASYNC ? [...baseTags, STRUCTURIZR_TAG_ASYNC] : baseTags;
128
+ return {
129
+ to: targetName,
130
+ description: rel.description,
131
+ technology: rel.technology,
132
+ tags,
133
+ link: rel.url,
134
+ properties: toProperties(rel.properties, void 0, rel.perspectives)
135
+ };
136
+ };
137
+ const load = async (filePath) => {
138
+ const filepath = path.resolve(filePath);
139
+ const data = await fs.readFile(filepath, "utf8");
140
+ const workspace = JSON.parse(data);
141
+ const containers = [];
142
+ const boundaries = [];
143
+ const rootBoundaryNames = [];
144
+ const idToName = /* @__PURE__ */ new Map();
145
+ const idToContainerName = /* @__PURE__ */ new Map();
146
+ for (const person of workspace.model.people ?? []) {
147
+ const c = buildPersonContainer(person);
148
+ containers.push(c);
149
+ idToName.set(person.id, c.name);
150
+ idToContainerName.set(person.id, c.name);
151
+ }
152
+ for (const system of workspace.model.softwareSystems ?? []) {
153
+ if (isExternal(system)) {
154
+ const c = buildExternalSystemContainer(system);
155
+ containers.push(c);
156
+ idToName.set(system.id, c.name);
157
+ idToContainerName.set(system.id, c.name);
158
+ } else {
159
+ const boundary = buildSystemBoundary(system);
160
+ boundaries.push(boundary);
161
+ rootBoundaryNames.push(boundary.name);
162
+ idToName.set(system.id, boundary.name);
163
+ for (const cont of system.containers ?? []) {
164
+ const c = buildContainer(cont);
165
+ containers.push(c);
166
+ idToName.set(cont.id, c.name);
167
+ idToContainerName.set(cont.id, c.name);
168
+ }
169
+ }
170
+ }
171
+ const elementsWithRelations = [];
172
+ for (const person of workspace.model.people ?? []) {
173
+ if (person.relationships) {
174
+ elementsWithRelations.push({
175
+ sourceId: person.id,
176
+ relationships: person.relationships
177
+ });
178
+ }
179
+ }
180
+ for (const system of workspace.model.softwareSystems ?? []) {
181
+ if (system.relationships) {
182
+ elementsWithRelations.push({
183
+ sourceId: system.id,
184
+ relationships: system.relationships
185
+ });
186
+ }
187
+ for (const cont of system.containers ?? []) {
188
+ if (cont.relationships) {
189
+ elementsWithRelations.push({
190
+ sourceId: cont.id,
191
+ relationships: cont.relationships
192
+ });
193
+ }
194
+ }
195
+ }
196
+ const containersByName = new Map(
197
+ containers.map((c) => [c.name, c])
198
+ );
199
+ for (const { sourceId, relationships } of elementsWithRelations) {
200
+ const sourceName = idToContainerName.get(sourceId);
201
+ if (!sourceName || !relationships) continue;
202
+ const source = containersByName.get(sourceName);
203
+ if (!source) continue;
204
+ const newRelations = [...source.relations];
205
+ for (const rel of relationships) {
206
+ const targetName = idToName.get(rel.destinationId);
207
+ if (!targetName) continue;
208
+ newRelations.push(buildRelation(rel, targetName));
209
+ }
210
+ containersByName.set(sourceName, { ...source, relations: newRelations });
211
+ }
212
+ return buildModel({
213
+ containers: [...containersByName.values()],
214
+ boundaries,
215
+ rootBoundaryNames
216
+ });
217
+ };
218
+
219
+ const structurizrDslSyntax = {
220
+ containerPattern: (name) => `${name} = container`,
221
+ containerDecl: (name, label, tags) => {
222
+ if (tags) {
223
+ return `${name} = container "${label}" {
224
+ tags "${tags}"
225
+ }`;
226
+ }
227
+ return `${name} = container "${label}"`;
228
+ },
229
+ relationPattern: (from, to) => `${from} -> ${to}`,
230
+ relationDecl: (from, to, tech, tags) => {
231
+ const techPart = tech ? ` "${tech}"` : "";
232
+ if (tags) {
233
+ return `${from} -> ${to}${techPart} {
234
+ tags "${tags}"
235
+ }`;
236
+ }
237
+ return `${from} -> ${to}${techPart}`;
238
+ }
239
+ };
240
+
241
+ const structurizrFormat = {
242
+ name: "structurizr",
243
+ defaultPattern: "workspace.json",
244
+ load,
245
+ fix: { syntax: structurizrDslSyntax }
246
+ };
247
+
248
+ export { structurizrFormat };
@@ -0,0 +1,72 @@
1
+ import YAML from 'yaml';
2
+
3
+ const toKebab = (name) => name.replaceAll("_", "-");
4
+ const toEnvKey = (name) => name.replaceAll("-", "_").toUpperCase();
5
+ const buildEnvVar = (relation, targetKind, targetExternal, sourceKebab, options) => {
6
+ const targetKebab = toKebab(relation.to);
7
+ const targetUpper = toEnvKey(targetKebab);
8
+ if (targetKind === "ContainerDb") {
9
+ const value = options.dbConnectionTemplate.replaceAll(
10
+ "{name}",
11
+ sourceKebab
12
+ );
13
+ return { key: "PG_CONNECTION_STRING", value };
14
+ }
15
+ if (relation.tags.includes("async")) {
16
+ const value = relation.technology ?? targetKebab;
17
+ return { key: `KAFKA_${targetUpper}_TOPIC`, value };
18
+ }
19
+ if (targetExternal === true) {
20
+ const value = relation.technology ?? `https://${targetKebab}`;
21
+ return { key: `${targetUpper}_BASE_URL`, value };
22
+ }
23
+ if (targetKind === "Container") {
24
+ const value = relation.technology ?? `http://${targetKebab}:${options.defaultPort}`;
25
+ return { key: `${targetUpper}_BASE_URL`, value };
26
+ }
27
+ return void 0;
28
+ };
29
+ const generate = (model, options) => {
30
+ const defaultPort = options?.defaultPort ?? 8080;
31
+ const dbConnectionTemplate = options?.dbConnectionTemplate ?? "postgresql://{name}:pass-{name}@postgresql:5432/{name}";
32
+ const resolvedOptions = { defaultPort, dbConnectionTemplate };
33
+ const containers = Object.values(model.containers).filter(
34
+ (c) => c.kind === "Container"
35
+ );
36
+ const files = containers.map((container) => {
37
+ const kebabName = toKebab(container.name);
38
+ const envEntries = [];
39
+ for (const relation of container.relations) {
40
+ const target = model.containers[relation.to];
41
+ const entry = buildEnvVar(
42
+ relation,
43
+ target?.kind,
44
+ target?.external,
45
+ kebabName,
46
+ resolvedOptions
47
+ );
48
+ if (entry) envEntries.push(entry);
49
+ }
50
+ envEntries.sort((a, b) => a.key.localeCompare(b.key));
51
+ const doc = { name: kebabName };
52
+ if (envEntries.length > 0) {
53
+ const environment = {};
54
+ for (const { key, value } of envEntries) {
55
+ environment[key] = { default: value };
56
+ }
57
+ doc.environment = environment;
58
+ }
59
+ return {
60
+ path: `${kebabName}.yml`,
61
+ content: YAML.stringify(doc)
62
+ };
63
+ });
64
+ return { files };
65
+ };
66
+
67
+ const kubernetesFormat = {
68
+ name: "kubernetes",
69
+ generate
70
+ };
71
+
72
+ export { kubernetesFormat };