wabachi 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +5 -0
  3. package/dist/architecture/cli.js +47 -32
  4. package/dist/architecture/cli.js.map +1 -1
  5. package/dist/architecture/documentation/react-flow.d.ts +23 -0
  6. package/dist/architecture/documentation/react-flow.js +436 -0
  7. package/dist/architecture/documentation/react-flow.js.map +1 -0
  8. package/dist/architecture/documentation/site.d.ts +10 -13
  9. package/dist/architecture/documentation/site.js +58 -24
  10. package/dist/architecture/documentation/site.js.map +1 -1
  11. package/dist/architecture/projection/react-flow.d.ts +96 -0
  12. package/dist/architecture/projection/react-flow.js +388 -0
  13. package/dist/architecture/projection/react-flow.js.map +1 -0
  14. package/dist/cli.js +1 -1
  15. package/dist/command-contract.d.ts +3 -2
  16. package/dist/command-contract.js +6 -5
  17. package/dist/command-contract.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/runtime/repository.d.ts +3 -0
  22. package/dist/runtime/repository.js +140 -16
  23. package/dist/runtime/repository.js.map +1 -1
  24. package/dist/runtime/run.js +3 -1
  25. package/dist/runtime/run.js.map +1 -1
  26. package/dist/working-set/codec.d.ts +7 -0
  27. package/dist/working-set/codec.js +53 -0
  28. package/dist/working-set/codec.js.map +1 -0
  29. package/dist/working-set/conflicts.d.ts +46 -0
  30. package/dist/working-set/conflicts.js +97 -0
  31. package/dist/working-set/conflicts.js.map +1 -0
  32. package/dist/working-set/derive.d.ts +34 -0
  33. package/dist/working-set/derive.js +634 -0
  34. package/dist/working-set/derive.js.map +1 -0
  35. package/dist/working-set/index.d.ts +3 -0
  36. package/dist/working-set/index.js +3 -0
  37. package/dist/working-set/index.js.map +1 -0
  38. package/dist/working-set/model.d.ts +93 -0
  39. package/dist/working-set/model.js +204 -0
  40. package/dist/working-set/model.js.map +1 -0
  41. package/dist/working-set/quality.d.ts +201 -0
  42. package/dist/working-set/quality.js +447 -0
  43. package/dist/working-set/quality.js.map +1 -0
  44. package/dist/working-set/seeds.d.ts +135 -0
  45. package/dist/working-set/seeds.js +433 -0
  46. package/dist/working-set/seeds.js.map +1 -0
  47. package/docs/USAGE.md +30 -22
  48. package/docs/examples/minimal-canon.json +24 -0
  49. package/package.json +10 -3
  50. package/skills/wabachi/SKILL.md +10 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wabachi",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Repository analysis and Architecture Canon workflows through the Wabachi CLI.",
5
5
  "skills": "skills/wabachi"
6
6
  }
package/README.md CHANGED
@@ -53,6 +53,11 @@ Wabachi keeps repository facts and architecture declarations explicit. Repositor
53
53
 
54
54
  The matrix workflow emits retained artifacts so provider output, correlations, and reports can be inspected rather than treated as opaque agent context.
55
55
 
56
+ Architecture rendering is npm-native: `wabachi architecture render
57
+ ./architecture.json --out ./artifacts/site` uses the bundled React Flow + ELK
58
+ renderer and does not require Java, Docker, Chromium, Structurizr, a CDN, or
59
+ network access.
60
+
56
61
  ## Development
57
62
 
58
63
  ```bash
@@ -3,9 +3,8 @@ import path from "node:path";
3
3
  import { parseCanonicalArchitectureDocument } from "./canon/codec.js";
4
4
  import { projectArchitectureDocument } from "./documentation/project.js";
5
5
  import { ArchitectureSiteError, buildArchitectureSite } from "./documentation/site.js";
6
- import { projectArchitectureDocumentToStructurizr } from "./projection/structurizr.js";
7
- import { commandUsage } from "../command-contract.js";
8
- const DEFAULT_STRUCTURIZR_COMMAND = "structurizr";
6
+ import { projectArchitectureDocumentToReactFlow } from "./projection/react-flow.js";
7
+ import { commandUsage, DEFAULT_ARCHITECTURE_CANON_PATH } from "../command-contract.js";
9
8
  const MAX_DIAGNOSTIC_LENGTH = 240;
10
9
  function usage(command) {
11
10
  if (command === "validate")
@@ -23,15 +22,29 @@ function bounded(value) {
23
22
  function errorMessage(error) {
24
23
  return bounded(error instanceof Error ? error.message : String(error));
25
24
  }
25
+ function architectureCanonCounts(document) {
26
+ return {
27
+ elements: document.elements.length,
28
+ interfaces: document.interfaces.length,
29
+ relationships: document.relationships.length,
30
+ flows: document.flows.length,
31
+ views: document.views.length,
32
+ };
33
+ }
34
+ function architectureCanonSummary(counts) {
35
+ return `Architecture Canon summary: elements=${counts.elements}, interfaces=${counts.interfaces}, relationships=${counts.relationships}, flows=${counts.flows}, views=${counts.views}`;
36
+ }
37
+ function isMissingFileError(error) {
38
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
39
+ }
26
40
  function parseArguments(args) {
27
41
  const command = args[0];
28
42
  const json = args.includes("--json");
29
- if (command !== "validate" && command !== "render") {
43
+ if (command !== "example" && command !== "validate" && command !== "render") {
30
44
  return { ok: false, json, message: usage() };
31
45
  }
32
46
  let file;
33
47
  let outputRoot;
34
- let structurizrCommand;
35
48
  for (let index = 1; index < args.length; index += 1) {
36
49
  const argument = args[index];
37
50
  if (argument === "--json")
@@ -47,33 +60,22 @@ function parseArguments(args) {
47
60
  index += 1;
48
61
  continue;
49
62
  }
50
- if (argument === "--structurizr-command") {
51
- const value = args[index + 1];
52
- if (value === undefined || value.startsWith("--")) {
53
- return { ok: false, json, message: "--structurizr-command requires an executable path" };
54
- }
55
- if (structurizrCommand !== undefined) {
56
- return { ok: false, json, message: "--structurizr-command may be provided only once" };
57
- }
58
- structurizrCommand = value;
59
- index += 1;
60
- continue;
61
- }
62
63
  if (argument.startsWith("--"))
63
64
  return { ok: false, json, message: `unknown option: ${argument}` };
64
65
  if (file !== undefined)
65
66
  return { ok: false, json, message: "architecture commands accept one explicit file" };
66
67
  file = argument;
67
68
  }
68
- if (file === undefined)
69
- return { ok: false, json, message: usage(command) };
70
- if (command === "validate" && (outputRoot !== undefined || structurizrCommand !== undefined)) {
69
+ if (command === "example" && file !== undefined) {
70
+ return { ok: false, json, message: "architecture example does not accept a file" };
71
+ }
72
+ if (command === "validate" && outputRoot !== undefined) {
71
73
  return { ok: false, json, message: "validate does not accept render options" };
72
74
  }
73
75
  if (command === "render" && outputRoot === undefined) {
74
76
  return { ok: false, json, message: "render requires --out <dir>" };
75
77
  }
76
- return { ok: true, value: { command, file, outputRoot, structurizrCommand, json } };
78
+ return { ok: true, value: { command, file, outputRoot, json } };
77
79
  }
78
80
  function writeFailure(command, json, diagnostic) {
79
81
  const result = {
@@ -104,38 +106,51 @@ export async function runArchitectureCli(args) {
104
106
  const parsed = parseArguments(args);
105
107
  if (!parsed.ok)
106
108
  return writeUsageFailure(parsed.json, parsed.message);
107
- const { command, file, json } = parsed.value;
109
+ const { command, json } = parsed.value;
110
+ if (command === "example") {
111
+ try {
112
+ const source = await readFile(new URL("../../docs/examples/minimal-canon.json", import.meta.url), "utf8");
113
+ parseCanonicalArchitectureDocument(source);
114
+ console.log(source.trim());
115
+ return 0;
116
+ }
117
+ catch (error) {
118
+ return writeFailure(command, json, { code: "example-unavailable", message: errorMessage(error) });
119
+ }
120
+ }
121
+ const file = parsed.value.file ?? DEFAULT_ARCHITECTURE_CANON_PATH;
108
122
  let document;
109
123
  try {
110
124
  document = parseCanonicalArchitectureDocument(await readFile(file, "utf8"));
111
125
  }
112
126
  catch (error) {
127
+ if (isMissingFileError(error)) {
128
+ return writeFailure(command, json, {
129
+ code: "invalid-canon",
130
+ message: `architecture document not found: ${file}`,
131
+ });
132
+ }
113
133
  return writeFailure(command, json, { code: "invalid-canon", message: errorMessage(error) });
114
134
  }
115
135
  if (command === "validate") {
136
+ const counts = architectureCanonCounts(document);
116
137
  if (json) {
117
- console.log(JSON.stringify({ ok: true, command: "architecture validate", file }));
138
+ console.log(JSON.stringify({ ok: true, command: "architecture validate", file, ...counts }));
118
139
  }
119
140
  else {
120
141
  console.log(`valid Architecture Canon: ${file}`);
142
+ console.log(architectureCanonSummary(counts));
121
143
  }
122
144
  return 0;
123
145
  }
124
146
  try {
125
147
  const documentation = projectArchitectureDocument(document);
126
- const structurizr = projectArchitectureDocumentToStructurizr(document);
148
+ const reactFlow = await projectArchitectureDocumentToReactFlow(document);
127
149
  const result = await buildArchitectureSite({
128
150
  outputRoot: path.resolve(parsed.value.outputRoot),
129
151
  documentation,
130
- structurizr,
131
- launcher: { executable: parsed.value.structurizrCommand ?? DEFAULT_STRUCTURIZR_COMMAND },
152
+ reactFlow,
132
153
  });
133
- if (!result.complete) {
134
- return writeFailure(command, json, {
135
- code: "export-failed",
136
- message: result.export.diagnostic?.message ?? "Structurizr static export failed",
137
- });
138
- }
139
154
  if (json) {
140
155
  console.log(JSON.stringify({
141
156
  ok: true,
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/architecture/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,kCAAkC,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AACvF,OAAO,EAAE,wCAAwC,EAAE,MAAM,6BAA6B,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD,MAAM,2BAA2B,GAAG,aAAa,CAAC;AAClD,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAiBlC,SAAS,KAAK,CAAC,OAA6B;IAC1C,IAAI,OAAO,KAAK,UAAU;QAAE,OAAO,YAAY,CAAC,uBAAuB,CAAC,CAAC;IACzE,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC,qBAAqB,CAAC,CAAC;IACrE,OAAO,YAAY,CAAC,mBAAmB,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,OAAO,CAAC,KAAa;IAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,IAAI,OAAO,CAAC,MAAM,IAAI,qBAAqB;QAAE,OAAO,OAAO,CAAC;IAC5D,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,GAAG,CAAC,CAAC,GAAG,CAAC;AAC3D,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,OAAO,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,cAAc,CACrB,IAAuB;IAIvB,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACnD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC;IAC/C,CAAC;IAED,IAAI,IAAwB,CAAC;IAC7B,IAAI,UAA8B,CAAC;IACnC,IAAI,kBAAsC,CAAC;IAE3C,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,QAAQ,KAAK,QAAQ;YAAE,SAAS;QAEpC,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YAC9B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,4BAA4B,EAAE,CAAC;YACpE,CAAC;YACD,IAAI,UAAU,KAAK,SAAS;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,iCAAiC,EAAE,CAAC;YACrG,UAAU,GAAG,KAAK,CAAC;YACnB,KAAK,IAAI,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,KAAK,uBAAuB,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YAC9B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,mDAAmD,EAAE,CAAC;YAC3F,CAAC;YACD,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;gBACrC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,iDAAiD,EAAE,CAAC;YACzF,CAAC;YACD,kBAAkB,GAAG,KAAK,CAAC;YAC3B,KAAK,IAAI,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,mBAAmB,QAAQ,EAAE,EAAE,CAAC;QAClG,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,gDAAgD,EAAE,CAAC;QAC9G,IAAI,GAAG,QAAQ,CAAC;IAClB,CAAC;IAED,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5E,IAAI,OAAO,KAAK,UAAU,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,kBAAkB,KAAK,SAAS,CAAC,EAAE,CAAC;QAC7F,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,yCAAyC,EAAE,CAAC;IACjF,CAAC;IACD,IAAI,OAAO,KAAK,QAAQ,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QACrD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC;IACrE,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,kBAAkB,EAAE,IAAI,EAAE,EAAE,CAAC;AACtF,CAAC;AAED,SAAS,YAAY,CAAC,OAA4B,EAAE,IAAa,EAAE,UAAkC;IACnG,MAAM,MAAM,GAAG;QACb,EAAE,EAAE,KAAK;QACT,OAAO,EAAE,gBAAgB,OAAO,EAAE;QAClC,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;KACxF,CAAC;IACF,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IACtC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,KAAK,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,gBAAgB,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAa,EAAE,OAAe;IACvD,MAAM,UAAU,GAAG,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5E,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IACjG,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAuB;IAC9D,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,iBAAiB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAEtE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC;IAC7C,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,kCAAkC,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3B,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACpF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,2BAA2B,CAAC,QAAQ,CAAC,CAAC;QAC5D,MAAM,WAAW,GAAG,wCAAwC,CAAC,QAAQ,CAAC,CAAC;QACvE,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC;YACzC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAoB,CAAC;YAC3D,aAAa;YACb,WAAW;YACX,QAAQ,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,kBAAkB,IAAI,2BAA2B,EAAE;SACzF,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,OAAO,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE;gBACjC,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,IAAI,kCAAkC;aACjF,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CAAC;gBACb,EAAE,EAAE,IAAI;gBACR,OAAO,EAAE,qBAAqB;gBAC9B,IAAI;gBACJ,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;aACvC,CAAC,CACH,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,+BAA+B,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,GAAG,KAAK,YAAY,qBAAqB,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC;QACnF,OAAO,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/architecture/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { ReactFlowProjection } from "../projection/react-flow.js";
2
+ export interface ReactFlowStaticAsset {
3
+ readonly path: "wabachi-react-flow.css";
4
+ readonly contentType: "text/css";
5
+ readonly content: string;
6
+ }
7
+ export interface ReactFlowStaticRenderResult {
8
+ /** An HTML fragment that can be inserted into a Wabachi-owned document. */
9
+ readonly markup: string;
10
+ /** Local assets required by the fragment; no network request is needed. */
11
+ readonly assets: readonly ReactFlowStaticAsset[];
12
+ }
13
+ export declare class ReactFlowStaticRenderError extends Error {
14
+ readonly code: "invalid-projection";
15
+ constructor(message: string);
16
+ }
17
+ export declare const REACT_FLOW_DIAGRAM_CSS: string;
18
+ export declare const REACT_FLOW_STATIC_ASSETS: readonly ReactFlowStaticAsset[];
19
+ /** Render the React Flow projection with @xyflow/react's ReactFlow component and local assets. */
20
+ export declare function renderReactFlowStatic(projection: ReactFlowProjection): ReactFlowStaticRenderResult;
21
+ /** Convenience form for callers that only need the composable HTML fragment. */
22
+ export declare function renderReactFlowHtml(projection: ReactFlowProjection): string;
23
+ export declare const renderReactFlowProjection: typeof renderReactFlowStatic;
@@ -0,0 +1,436 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import React from "react";
4
+ import { renderToStaticMarkup } from "react-dom/server";
5
+ import { BaseEdge, Handle, MarkerType, Position, ReactFlow, ReactFlowProvider, } from "@xyflow/react";
6
+ export class ReactFlowStaticRenderError extends Error {
7
+ code = "invalid-projection";
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "ReactFlowStaticRenderError";
11
+ }
12
+ }
13
+ const require = createRequire(import.meta.url);
14
+ function reactFlowStylesheet() {
15
+ try {
16
+ return readFileSync(require.resolve("@xyflow/react/dist/style.css"), "utf8");
17
+ }
18
+ catch (error) {
19
+ const detail = error instanceof Error ? `: ${error.message}` : "";
20
+ throw new ReactFlowStaticRenderError(`React Flow stylesheet is unavailable${detail}`);
21
+ }
22
+ }
23
+ const WABACHI_REACT_FLOW_CSS = `.wabachi-react-flow-diagrams {
24
+ --wabachi-flow-ink: #202124;
25
+ --wabachi-flow-muted: #5f6368;
26
+ --wabachi-flow-border: #aeb7c4;
27
+ --wabachi-flow-edge: #53657a;
28
+ --wabachi-flow-surface: #ffffff;
29
+ --wabachi-flow-canvas: #f8fafc;
30
+ display: grid;
31
+ gap: 2rem;
32
+ color: var(--wabachi-flow-ink);
33
+ font-family: system-ui, sans-serif;
34
+ }
35
+
36
+ .wabachi-react-flow-diagram {
37
+ min-inline-size: 0;
38
+ }
39
+
40
+ .wabachi-react-flow-diagram > header {
41
+ margin-block-end: 1rem;
42
+ }
43
+
44
+ .wabachi-react-flow-diagram > header h2,
45
+ .wabachi-react-flow-diagram > header p {
46
+ margin-block: 0.25rem;
47
+ }
48
+
49
+ .wabachi-react-flow-diagram > header p,
50
+ .wabachi-react-flow-losses {
51
+ color: var(--wabachi-flow-muted);
52
+ }
53
+
54
+ .wabachi-react-flow-canvas {
55
+ display: block;
56
+ inline-size: 100%;
57
+ min-block-size: 12rem;
58
+ overflow: hidden;
59
+ background: var(--wabachi-flow-canvas);
60
+ border: 1px solid var(--wabachi-flow-border);
61
+ border-radius: 0.5rem;
62
+ }
63
+
64
+ .wabachi-react-flow-surface {
65
+ color: var(--wabachi-flow-ink);
66
+ background: var(--wabachi-flow-canvas);
67
+ }
68
+
69
+ .wabachi-react-flow-node {
70
+ display: grid;
71
+ place-items: center;
72
+ box-sizing: border-box;
73
+ inline-size: 100%;
74
+ block-size: 100%;
75
+ padding: 0.75rem;
76
+ color: var(--wabachi-flow-ink);
77
+ background: var(--wabachi-flow-surface);
78
+ border: 2px solid var(--wabachi-flow-border);
79
+ border-radius: 0.5rem;
80
+ font-size: 14px;
81
+ font-weight: 600;
82
+ text-align: center;
83
+ }
84
+
85
+ .wabachi-react-flow-node--parent {
86
+ border-style: dashed;
87
+ }
88
+
89
+ .wabachi-react-flow-node[data-kind="service"],
90
+ .wabachi-react-flow-node[data-kind="system"] {
91
+ background: #eef6ff;
92
+ }
93
+
94
+ .wabachi-react-flow-node[data-kind="component"] {
95
+ background: #f4f0ff;
96
+ }
97
+
98
+ .wabachi-react-flow-node[data-kind="actor"] {
99
+ background: #fff7e6;
100
+ }
101
+
102
+ .wabachi-react-flow-node > .react-flow__handle {
103
+ opacity: 0;
104
+ }
105
+
106
+ .wabachi-react-flow-edge .react-flow__edge-path {
107
+ stroke: var(--wabachi-flow-edge);
108
+ stroke-width: 2;
109
+ }
110
+
111
+ .wabachi-react-flow-edge .react-flow__edge-text {
112
+ fill: var(--wabachi-flow-ink);
113
+ font-size: 12px;
114
+ }
115
+
116
+ .wabachi-react-flow-edge .react-flow__edge-textbg {
117
+ fill: var(--wabachi-flow-canvas);
118
+ opacity: 0.95;
119
+ }
120
+ `;
121
+ export const REACT_FLOW_DIAGRAM_CSS = `${reactFlowStylesheet()}\n${WABACHI_REACT_FLOW_CSS}`;
122
+ export const REACT_FLOW_STATIC_ASSETS = Object.freeze([
123
+ Object.freeze({
124
+ path: "wabachi-react-flow.css",
125
+ contentType: "text/css",
126
+ content: REACT_FLOW_DIAGRAM_CSS,
127
+ }),
128
+ ]);
129
+ const HTML_ESCAPE_PATTERN = /[&<>"']/g;
130
+ const HTML_ESCAPES = {
131
+ "&": "&amp;",
132
+ "<": "&lt;",
133
+ ">": "&gt;",
134
+ '"': "&quot;",
135
+ "'": "&#39;",
136
+ };
137
+ function escapeHtml(value) {
138
+ return value.replace(HTML_ESCAPE_PATTERN, (character) => HTML_ESCAPES[character] ?? character);
139
+ }
140
+ function identityToken(value) {
141
+ const token = Array.from(value, (character) => {
142
+ if (/^[A-Za-z0-9]$/u.test(character))
143
+ return character;
144
+ return `_u${character.codePointAt(0)?.toString(16) ?? "0"}_`;
145
+ }).join("");
146
+ return token.length === 0 ? "empty" : token;
147
+ }
148
+ function compareStrings(left, right) {
149
+ if (left < right)
150
+ return -1;
151
+ if (left > right)
152
+ return 1;
153
+ return 0;
154
+ }
155
+ function finiteNumber(value, path) {
156
+ if (typeof value !== "number" || !Number.isFinite(value)) {
157
+ throw new ReactFlowStaticRenderError(`${path} must be a finite number`);
158
+ }
159
+ return Object.is(value, -0) ? 0 : value;
160
+ }
161
+ function formatNumber(value) {
162
+ return String(Object.is(value, -0) ? 0 : value);
163
+ }
164
+ function nodePosition(node, path) {
165
+ return Object.freeze({
166
+ x: finiteNumber(node.position.x, `${path}.position.x`),
167
+ y: finiteNumber(node.position.y, `${path}.position.y`),
168
+ });
169
+ }
170
+ function nodeSize(node, path) {
171
+ const width = finiteNumber(node.width, `${path}.width`);
172
+ const height = finiteNumber(node.height, `${path}.height`);
173
+ if (width <= 0 || height <= 0) {
174
+ throw new ReactFlowStaticRenderError(`${path} dimensions must be positive`);
175
+ }
176
+ return Object.freeze({ width, height });
177
+ }
178
+ function sectionPoints(section, path) {
179
+ const points = [section.startPoint, ...section.bendPoints, section.endPoint];
180
+ return Object.freeze(points.map((point, index) => ({
181
+ x: finiteNumber(point.x, `${path}[${index}].x`),
182
+ y: finiteNumber(point.y, `${path}[${index}].y`),
183
+ })));
184
+ }
185
+ function edgePath(edge) {
186
+ if (edge.data.sections.length === 0) {
187
+ throw new ReactFlowStaticRenderError(`view ${edge.data.viewKey} edge ${edge.id} has no routed sections`);
188
+ }
189
+ const paths = [];
190
+ let labelPoint;
191
+ edge.data.sections.forEach((section, sectionIndex) => {
192
+ const points = sectionPoints(section, `views[${edge.data.viewKey}].edges[${edge.id}].sections[${sectionIndex}]`);
193
+ if (points.length < 2) {
194
+ throw new ReactFlowStaticRenderError(`view ${edge.data.viewKey} edge ${edge.id} has an incomplete routed section`);
195
+ }
196
+ const [first, ...rest] = points;
197
+ paths.push(`M ${formatNumber(first.x)} ${formatNumber(first.y)} ${rest.map((point) => `L ${formatNumber(point.x)} ${formatNumber(point.y)}`).join(" ")}`);
198
+ if (labelPoint === undefined) {
199
+ const midpoint = Math.floor((points.length - 1) / 2);
200
+ const start = points[midpoint];
201
+ const end = points[midpoint + 1];
202
+ labelPoint = Object.freeze({ x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 });
203
+ }
204
+ });
205
+ return Object.freeze({ path: paths.join(" "), labelPoint: labelPoint });
206
+ }
207
+ function includePoint(bounds, x, y) {
208
+ bounds.minX = Math.min(bounds.minX, x);
209
+ bounds.minY = Math.min(bounds.minY, y);
210
+ bounds.maxX = Math.max(bounds.maxX, x);
211
+ bounds.maxY = Math.max(bounds.maxY, y);
212
+ }
213
+ function addNodeBounds(bounds, node, absolute, path) {
214
+ const size = nodeSize(node, path);
215
+ includePoint(bounds, absolute.x, absolute.y);
216
+ includePoint(bounds, absolute.x + size.width, absolute.y + size.height);
217
+ }
218
+ function validateProjection(projection) {
219
+ if (projection === null || typeof projection !== "object" || !Array.isArray(projection.views)) {
220
+ throw new ReactFlowStaticRenderError("React Flow renderer requires a projection with views");
221
+ }
222
+ const viewKeys = new Set();
223
+ for (const [viewIndex, view] of projection.views.entries()) {
224
+ if (typeof view.key !== "string" || view.key.length === 0) {
225
+ throw new ReactFlowStaticRenderError(`views[${viewIndex}].key must be a non-empty string`);
226
+ }
227
+ if (viewKeys.has(view.key)) {
228
+ throw new ReactFlowStaticRenderError(`projection contains duplicate view key: ${view.key}`);
229
+ }
230
+ viewKeys.add(view.key);
231
+ const nodeIds = new Set();
232
+ for (const [nodeIndex, node] of view.nodes.entries()) {
233
+ const path = `views[${viewIndex}].nodes[${nodeIndex}]`;
234
+ if (typeof node.id !== "string" || node.id.length === 0) {
235
+ throw new ReactFlowStaticRenderError(`${path}.id must be a non-empty string`);
236
+ }
237
+ if (nodeIds.has(node.id)) {
238
+ throw new ReactFlowStaticRenderError(`view ${view.key} contains duplicate node id: ${node.id}`);
239
+ }
240
+ nodeIds.add(node.id);
241
+ if (node.data.viewKey !== view.key) {
242
+ throw new ReactFlowStaticRenderError(`${path}.data.viewKey does not match view key ${view.key}`);
243
+ }
244
+ nodePosition(node, path);
245
+ nodeSize(node, path);
246
+ }
247
+ for (const node of view.nodes) {
248
+ if (node.parentId !== undefined && !nodeIds.has(node.parentId)) {
249
+ throw new ReactFlowStaticRenderError(`view ${view.key} node ${node.id} refers to missing parent ${node.parentId}`);
250
+ }
251
+ }
252
+ for (const [edgeIndex, edge] of view.edges.entries()) {
253
+ const path = `views[${viewIndex}].edges[${edgeIndex}]`;
254
+ if (typeof edge.id !== "string" || edge.id.length === 0) {
255
+ throw new ReactFlowStaticRenderError(`${path}.id must be a non-empty string`);
256
+ }
257
+ if (!nodeIds.has(edge.source) || !nodeIds.has(edge.target)) {
258
+ throw new ReactFlowStaticRenderError(`view ${view.key} edge ${edge.id} refers to a missing endpoint`);
259
+ }
260
+ if (edge.data.viewKey !== view.key) {
261
+ throw new ReactFlowStaticRenderError(`${path}.data.viewKey does not match view key ${view.key}`);
262
+ }
263
+ edgePath(edge);
264
+ }
265
+ }
266
+ }
267
+ function absoluteNodePosition(node, nodesById, cache, visiting) {
268
+ const cached = cache.get(node.id);
269
+ if (cached !== undefined)
270
+ return cached;
271
+ if (visiting.has(node.id))
272
+ throw new ReactFlowStaticRenderError(`view contains a parent cycle at node ${node.id}`);
273
+ visiting.add(node.id);
274
+ const position = nodePosition(node, `node ${node.id}`);
275
+ const parent = node.parentId === undefined ? undefined : nodesById.get(node.parentId);
276
+ const parentPosition = parent === undefined ? { x: 0, y: 0 } : absoluteNodePosition(parent, nodesById, cache, visiting);
277
+ const result = Object.freeze({ x: position.x + parentPosition.x, y: position.y + parentPosition.y });
278
+ visiting.delete(node.id);
279
+ cache.set(node.id, result);
280
+ return result;
281
+ }
282
+ function viewBounds(view, absolutePositions) {
283
+ const bounds = { minX: 0, minY: 0, maxX: 320, maxY: 160 };
284
+ for (const node of view.nodes) {
285
+ const position = absolutePositions.get(node.id);
286
+ addNodeBounds(bounds, node, position, `node ${node.id}`);
287
+ }
288
+ for (const edge of view.edges) {
289
+ edge.data.sections.forEach((section, sectionIndex) => {
290
+ for (const point of sectionPoints(section, `edge ${edge.id}.sections[${sectionIndex}]`)) {
291
+ includePoint(bounds, point.x, point.y);
292
+ }
293
+ });
294
+ }
295
+ const padding = 32;
296
+ return Object.freeze({
297
+ width: Math.max(320, bounds.maxX - bounds.minX + padding * 2),
298
+ height: Math.max(160, bounds.maxY - bounds.minY + padding * 2),
299
+ defaultViewport: Object.freeze({ x: padding - bounds.minX, y: padding - bounds.minY, zoom: 1 }),
300
+ });
301
+ }
302
+ function ArchitectureNode({ data, parentId }) {
303
+ const className = data.isParent
304
+ ? "wabachi-react-flow-node wabachi-react-flow-node--parent"
305
+ : "wabachi-react-flow-node";
306
+ return React.createElement(React.Fragment, null, React.createElement(Handle, { type: "target", position: Position.Left, "aria-hidden": true }), React.createElement("div", {
307
+ className,
308
+ "data-canon-id": data.canonId,
309
+ "data-view-key": data.viewKey,
310
+ "data-kind": data.kind,
311
+ ...(parentId === undefined ? {} : { "data-parent-id": parentId }),
312
+ role: "group",
313
+ }, data.label), React.createElement(Handle, { type: "source", position: Position.Right, "aria-hidden": true }));
314
+ }
315
+ function RoutedEdge({ id, data, label, markerEnd }) {
316
+ if (data === undefined) {
317
+ throw new ReactFlowStaticRenderError(`React Flow edge ${id} has no projection data`);
318
+ }
319
+ const routed = edgePath({ id, data });
320
+ return React.createElement("g", {
321
+ "data-view-key": data.viewKey,
322
+ "data-canon-source-id": data.canonSourceId,
323
+ "data-canon-target-id": data.canonTargetId,
324
+ ...(data.canonRelationshipId === undefined ? {} : { "data-canon-relationship-id": data.canonRelationshipId }),
325
+ "data-section-count": String(data.sections.length),
326
+ }, React.createElement(BaseEdge, {
327
+ id,
328
+ path: routed.path,
329
+ label,
330
+ labelX: routed.labelPoint.x,
331
+ labelY: routed.labelPoint.y,
332
+ markerEnd,
333
+ className: "wabachi-react-flow-edge",
334
+ }));
335
+ }
336
+ const NODE_TYPES = Object.freeze({ architecture: ArchitectureNode });
337
+ const EDGE_TYPES = Object.freeze({ routed: RoutedEdge });
338
+ function flowNodes(view) {
339
+ const nodes = [...view.nodes].sort((left, right) => compareStrings(left.id, right.id));
340
+ const parentIds = new Set(nodes.flatMap((node) => (node.parentId === undefined ? [] : [node.parentId])));
341
+ return Object.freeze(nodes
342
+ .map((node) => ({
343
+ id: node.id,
344
+ type: "architecture",
345
+ data: { ...node.data, isParent: parentIds.has(node.id) },
346
+ position: { ...node.position },
347
+ width: node.width,
348
+ height: node.height,
349
+ handles: [
350
+ { type: "target", position: Position.Left, x: 0, y: node.height / 2 },
351
+ { type: "source", position: Position.Right, x: node.width, y: node.height / 2 },
352
+ ],
353
+ ...(node.parentId === undefined ? {} : { parentId: node.parentId, extent: "parent" }),
354
+ }))
355
+ .map((node) => ({
356
+ ...node,
357
+ data: node.data,
358
+ })));
359
+ }
360
+ function flowEdges(view) {
361
+ const edges = [...view.edges].sort((left, right) => compareStrings(left.id, right.id));
362
+ return Object.freeze(edges
363
+ .map((edge) => ({
364
+ id: edge.id,
365
+ type: "routed",
366
+ source: edge.source,
367
+ target: edge.target,
368
+ label: edge.label,
369
+ markerEnd: { type: MarkerType.ArrowClosed },
370
+ data: edge.data,
371
+ }))
372
+ .map((edge) => edge));
373
+ }
374
+ function renderView(projection, view) {
375
+ const nodes = [...flowNodes(view)];
376
+ const edges = [...flowEdges(view)];
377
+ const nodesById = new Map(view.nodes.map((node) => [node.id, node]));
378
+ const absolutePositions = new Map();
379
+ for (const node of view.nodes)
380
+ absoluteNodePosition(node, nodesById, absolutePositions, new Set());
381
+ const bounds = viewBounds(view, absolutePositions);
382
+ const viewToken = identityToken(view.key);
383
+ const titleId = `react-flow-title-${viewToken}`;
384
+ const title = view.title ?? view.key;
385
+ const description = view.description === undefined ? "" : `<p>${escapeHtml(view.description)}</p>`;
386
+ const losses = view.losses.length === 0
387
+ ? ""
388
+ : `<p class="wabachi-react-flow-losses" data-loss-count="${String(view.losses.length)}">Projection losses: ${String(view.losses.length)}</p>`;
389
+ const flowMarkup = renderToStaticMarkup(React.createElement(ReactFlowProvider, {
390
+ initialNodes: nodes,
391
+ initialEdges: edges,
392
+ children: React.createElement(ReactFlow, {
393
+ id: `react-flow-${viewToken}`,
394
+ nodes,
395
+ edges,
396
+ nodeTypes: NODE_TYPES,
397
+ edgeTypes: EDGE_TYPES,
398
+ defaultViewport: bounds.defaultViewport,
399
+ nodesDraggable: false,
400
+ nodesConnectable: false,
401
+ elementsSelectable: false,
402
+ panOnDrag: false,
403
+ zoomOnScroll: false,
404
+ zoomOnPinch: false,
405
+ zoomOnDoubleClick: false,
406
+ proOptions: { hideAttribution: true },
407
+ className: "wabachi-react-flow-surface",
408
+ style: { width: "100%", height: `${formatNumber(bounds.height)}px` },
409
+ "aria-label": `${title} architecture diagram`,
410
+ }),
411
+ }));
412
+ return `<section id="react-flow-view-${escapeHtml(viewToken)}" class="wabachi-react-flow-diagram" data-view-key="${escapeHtml(view.key)}" data-canon-document-id="${escapeHtml(projection.documentId)}" data-canon-version="${escapeHtml(String(projection.canonVersion))}" data-projection-loss-count="${String(view.losses.length)}">
413
+ <header>
414
+ <h2 id="${escapeHtml(titleId)}">${escapeHtml(title)}</h2>
415
+ ${description}${losses}
416
+ </header>
417
+ <div class="wabachi-react-flow-canvas" data-view-key="${escapeHtml(view.key)}" data-canon-document-id="${escapeHtml(projection.documentId)}" data-canon-version="${escapeHtml(String(projection.canonVersion))}" style="height: ${formatNumber(bounds.height)}px" role="img" aria-labelledby="${escapeHtml(titleId)}">
418
+ ${flowMarkup}
419
+ </div>
420
+ </section>`;
421
+ }
422
+ /** Render the React Flow projection with @xyflow/react's ReactFlow component and local assets. */
423
+ export function renderReactFlowStatic(projection) {
424
+ validateProjection(projection);
425
+ const views = [...projection.views].sort((left, right) => compareStrings(left.key, right.key));
426
+ const markup = `<div class="wabachi-react-flow-diagrams" data-canon-document-id="${escapeHtml(projection.documentId)}" data-canon-version="${escapeHtml(String(projection.canonVersion))}">
427
+ ${views.map((view) => renderView(projection, view)).join("\n")}
428
+ </div>`;
429
+ return Object.freeze({ markup, assets: REACT_FLOW_STATIC_ASSETS });
430
+ }
431
+ /** Convenience form for callers that only need the composable HTML fragment. */
432
+ export function renderReactFlowHtml(projection) {
433
+ return renderReactFlowStatic(projection).markup;
434
+ }
435
+ export const renderReactFlowProjection = renderReactFlowStatic;
436
+ //# sourceMappingURL=react-flow.js.map