aact 3.0.0-beta.2 → 3.0.0-beta.4

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.
@@ -209,7 +209,23 @@ const filterElements = (elements) => {
209
209
  const TAGS_MARKER = "__aact_tags__:";
210
210
  const LINK_MARKER = "__aact_link__:";
211
211
  const SPRITE_MARKER = "__aact_sprite__:";
212
- const preTransformNamedArgs = (raw) => raw.replaceAll(/, \$tags="(.+?)"/g, `, "${TAGS_MARKER}$1"`).replaceAll(/, \$link="(.+?)"/g, `, "${LINK_MARKER}$1"`).replaceAll(/, \$sprite="(.+?)"/g, `, "${SPRITE_MARKER}$1"`).replaceAll('""', '" "');
212
+ const INDEX_MARKER = "__aact_index__:";
213
+ const preTransformNamedArgs = (raw) => raw.replaceAll(/, \$tags="(.+?)"/g, `, "${TAGS_MARKER}$1"`).replaceAll(/, \$link="(.+?)"/g, `, "${LINK_MARKER}$1"`).replaceAll(/, \$sprite="(.+?)"/g, `, "${SPRITE_MARKER}$1"`).replaceAll(
214
+ /, \$index=(?:"([^"]+)"|([^,)\s]+))/g,
215
+ (_match, quoted, bare) => `, "${INDEX_MARKER}${quoted ?? bare ?? ""}"`
216
+ ).replaceAll('""', '" "');
217
+ const COMPONENT_BOUNDARY_ALIAS_RE = /\bComponent_Boundary\(\s*([A-Za-z0-9_.]+)/g;
218
+ const preTransform = (raw) => {
219
+ const componentBoundaryAliases = /* @__PURE__ */ new Set();
220
+ for (const match of raw.matchAll(COMPONENT_BOUNDARY_ALIAS_RE)) {
221
+ componentBoundaryAliases.add(match[1]);
222
+ }
223
+ const source = preTransformNamedArgs(raw).replaceAll(
224
+ /\bComponent_Boundary\(/g,
225
+ "Container_Boundary("
226
+ );
227
+ return { source, componentBoundaryAliases };
228
+ };
213
229
  const stripMarker = (value, marker) => value && value.startsWith(marker) ? value.slice(marker.length) : void 0;
214
230
  const extractMarked = (marker, ...slots) => {
215
231
  for (const slot of slots) {
@@ -234,7 +250,7 @@ const normalizeRelBack = (elements) => {
234
250
  }
235
251
  }
236
252
  };
237
- const ALL_MARKERS = [TAGS_MARKER, LINK_MARKER, SPRITE_MARKER];
253
+ const ALL_MARKERS = [TAGS_MARKER, LINK_MARKER, SPRITE_MARKER, INDEX_MARKER];
238
254
  const buildContainer = (el) => {
239
255
  const macroKind = parseC4MacroKind(el.type_.name);
240
256
  const kind = macroKind?.kind ?? "Container";
@@ -278,30 +294,18 @@ const buildContainer = (el) => {
278
294
  };
279
295
  };
280
296
  const buildRelation = (rel) => {
281
- const taggedValue = extractMarked(
282
- TAGS_MARKER,
283
- rel.techn,
284
- rel.descr,
285
- rel.sprite,
286
- rel.tags,
287
- rel.link
288
- );
289
- const linkValue = extractMarked(
290
- LINK_MARKER,
291
- rel.techn,
292
- rel.descr,
293
- rel.sprite,
294
- rel.tags,
295
- rel.link
296
- );
297
- const spriteNamedValue = extractMarked(
298
- SPRITE_MARKER,
297
+ const relSlots = [
299
298
  rel.techn,
300
299
  rel.descr,
301
300
  rel.sprite,
302
301
  rel.tags,
303
302
  rel.link
304
- );
303
+ ];
304
+ const taggedValue = extractMarked(TAGS_MARKER, ...relSlots);
305
+ const linkValue = extractMarked(LINK_MARKER, ...relSlots);
306
+ const spriteNamedValue = extractMarked(SPRITE_MARKER, ...relSlots);
307
+ const indexValue = extractMarked(INDEX_MARKER, ...relSlots);
308
+ const order = indexValue !== void 0 && Number.isFinite(Number(indexValue)) ? Number(indexValue) : void 0;
305
309
  return {
306
310
  to: rel.to,
307
311
  description: cleanSlot(rel.label, ...ALL_MARKERS) || void 0,
@@ -310,16 +314,18 @@ const buildRelation = (rel) => {
310
314
  cleanSlot(rel.descr, ...ALL_MARKERS) || cleanSlot(rel.tags, ...ALL_MARKERS)
311
315
  ) : parseCsvTags(taggedValue),
312
316
  sprite: spriteNamedValue ?? cleanSlot(rel.sprite, ...ALL_MARKERS),
313
- link: linkValue ?? cleanSlot(rel.link, ...ALL_MARKERS)
317
+ link: linkValue ?? cleanSlot(rel.link, ...ALL_MARKERS),
318
+ order
314
319
  };
315
320
  };
316
- const buildBoundary = (el, childContainers, childBoundaries) => {
321
+ const buildBoundary = (el, childContainers, childBoundaries, componentBoundaryAliases) => {
317
322
  const taggedValue = extractMarked(TAGS_MARKER, el.tags, el.link);
318
323
  const linkValue = extractMarked(LINK_MARKER, el.tags, el.link);
324
+ const kind = componentBoundaryAliases.has(el.alias) ? "Component" : parseBoundaryMacro(el.type_.name);
319
325
  return {
320
326
  name: el.alias,
321
327
  label: el.label,
322
- kind: parseBoundaryMacro(el.type_.name),
328
+ kind,
323
329
  tags: taggedValue === void 0 ? parseCsvTags(cleanSlot(el.tags, ...ALL_MARKERS)) : parseCsvTags(taggedValue),
324
330
  containerNames: childContainers,
325
331
  boundaryNames: childBoundaries,
@@ -371,7 +377,7 @@ const collectChildBoundaryNames = (boundaryElements) => {
371
377
  const load = async (filePath) => {
372
378
  const filepath = path.resolve(filePath);
373
379
  const raw = await fs.readFile(filepath, "utf8");
374
- const transformed = preTransformNamedArgs(raw);
380
+ const { source: transformed, componentBoundaryAliases } = preTransform(raw);
375
381
  const [{ elements: rawElements }] = parse(transformed);
376
382
  const elements = filterElements(rawElements);
377
383
  normalizeRelBack(elements);
@@ -388,7 +394,12 @@ const load = async (filePath) => {
388
394
  const childOfBoundary = collectChildBoundaryNames(boundaryElements);
389
395
  const boundaries = boundaryElements.map((b) => {
390
396
  const { containers, boundaries: childBoundaries } = collectBoundaryChildren(b);
391
- return buildBoundary(b, containers, childBoundaries);
397
+ return buildBoundary(
398
+ b,
399
+ containers,
400
+ childBoundaries,
401
+ componentBoundaryAliases
402
+ );
392
403
  });
393
404
  const rootBoundaryNames = boundaries.map((b) => b.name).filter((name) => !childOfBoundary.has(name));
394
405
  return buildModel({
@@ -8,7 +8,7 @@ import * as v from 'valibot';
8
8
  import fs, { readFile, writeFile } from 'node:fs/promises';
9
9
  import { colors, box } from 'consola/utils';
10
10
 
11
- const version = "3.0.0-beta.2";
11
+ const version = "3.0.0-beta.4";
12
12
 
13
13
  const matchesPattern = (filePath, pattern) => {
14
14
  if (pattern.startsWith("*")) {
@@ -540,6 +540,32 @@ const config: AactConfig = {
540
540
  commonReuse: true, // Reuse all of a context's public API or none
541
541
  },
542
542
 
543
+ // -----------------------------------------------------------------------
544
+ // Project-specific (custom) rules
545
+ //
546
+ // After \`npm install aact\` locally, switch from this type-only import to
547
+ // \`defineConfig\` to register your own checks alongside the built-ins:
548
+ //
549
+ // import { defineConfig } from "aact";
550
+ // import { bcIsolationRule } from "./rules/bcIsolation";
551
+ //
552
+ // export default defineConfig({
553
+ // source: { type: "plantuml", path: "./architecture.puml" },
554
+ //
555
+ // customRules: [bcIsolationRule],
556
+ //
557
+ // rules: {
558
+ // acl: true,
559
+ // // Configure custom rules with the same syntax as built-ins.
560
+ // // TypeScript autocompletes options based on the rule definition.
561
+ // bcIsolation: { apiSuffix: "_api" },
562
+ // },
563
+ // });
564
+ //
565
+ // Worked example with two rules and tests:
566
+ // https://github.com/Byndyusoft/aact/tree/main/examples/custom-rules
567
+ // -----------------------------------------------------------------------
568
+
543
569
  // PlantUML generation from Kubernetes configs (aact generate)
544
570
  // generate: {
545
571
  // kubernetes: { path: "./fixtures/kubernetes" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aact",
3
- "version": "3.0.0-beta.2",
3
+ "version": "3.0.0-beta.4",
4
4
  "type": "module",
5
5
  "description": "Architecture analysis and compliance tool",
6
6
  "keywords": [