@webpieces/nx-webpieces-rules 0.4.487 → 0.4.489

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 (44) hide show
  1. package/package.json +6 -6
  2. package/src/executors/generate/executor.js +42 -19
  3. package/src/executors/generate/executor.js.map +1 -1
  4. package/src/executors/validate-api-relations/executor.js +6 -1
  5. package/src/executors/validate-api-relations/executor.js.map +1 -1
  6. package/src/executors/validate-architecture-unchanged/executor.js +65 -15
  7. package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
  8. package/src/executors/validate-eslint-sync/executor.js +3 -2
  9. package/src/executors/validate-eslint-sync/executor.js.map +1 -1
  10. package/src/executors/validate-packagejson/executor.js +4 -2
  11. package/src/executors/validate-packagejson/executor.js.map +1 -1
  12. package/src/executors/validate-runtime-architecture/executor.js +3 -1
  13. package/src/executors/validate-runtime-architecture/executor.js.map +1 -1
  14. package/src/executors/validate-versions-locked/executor.js +3 -2
  15. package/src/executors/validate-versions-locked/executor.js.map +1 -1
  16. package/src/lib/api-usage/api-ast.d.ts +71 -0
  17. package/src/lib/api-usage/api-ast.js +250 -0
  18. package/src/lib/api-usage/api-ast.js.map +1 -0
  19. package/src/lib/api-usage/api-relations.d.ts +72 -3
  20. package/src/lib/api-usage/api-relations.js.map +1 -1
  21. package/src/lib/api-usage/api-scanner.d.ts +43 -4
  22. package/src/lib/api-usage/api-scanner.js +121 -103
  23. package/src/lib/api-usage/api-scanner.js.map +1 -1
  24. package/src/lib/graph-loader.d.ts +21 -2
  25. package/src/lib/graph-loader.js +38 -4
  26. package/src/lib/graph-loader.js.map +1 -1
  27. package/src/lib/rule-gate.d.ts +8 -5
  28. package/src/lib/rule-gate.js +8 -5
  29. package/src/lib/rule-gate.js.map +1 -1
  30. package/src/lib/runtime-config.d.ts +7 -1
  31. package/src/lib/runtime-config.js +7 -1
  32. package/src/lib/runtime-config.js.map +1 -1
  33. package/src/lib/runtime-graph-io.d.ts +17 -0
  34. package/src/lib/runtime-graph-io.js +59 -0
  35. package/src/lib/runtime-graph-io.js.map +1 -0
  36. package/src/lib/runtime-graph-model.d.ts +121 -0
  37. package/src/lib/runtime-graph-model.js +14 -0
  38. package/src/lib/runtime-graph-model.js.map +1 -0
  39. package/src/lib/runtime-graph.d.ts +6 -75
  40. package/src/lib/runtime-graph.js +163 -51
  41. package/src/lib/runtime-graph.js.map +1 -1
  42. package/src/lib/runtime-visualizer.d.ts +5 -0
  43. package/src/lib/runtime-visualizer.js +63 -6
  44. package/src/lib/runtime-visualizer.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/nx-webpieces-rules",
3
- "version": "0.4.487",
3
+ "version": "0.4.489",
4
4
  "description": "Nx-specific webpieces validation rules and graph tooling. Bundles all @webpieces rule packages with Nx graph validators and an inference plugin.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -21,11 +21,11 @@
21
21
  "README.md"
22
22
  ],
23
23
  "dependencies": {
24
- "@webpieces/ai-hook-rules": "0.4.487",
25
- "@webpieces/code-rules": "0.4.487",
26
- "@webpieces/eslint-rules": "0.4.487",
27
- "@webpieces/pr-gate": "0.4.487",
28
- "@webpieces/rules-config": "0.4.487",
24
+ "@webpieces/ai-hook-rules": "0.4.489",
25
+ "@webpieces/code-rules": "0.4.489",
26
+ "@webpieces/eslint-rules": "0.4.489",
27
+ "@webpieces/pr-gate": "0.4.489",
28
+ "@webpieces/rules-config": "0.4.489",
29
29
  "madge": "8.0.0"
30
30
  },
31
31
  "peerDependencies": {
@@ -15,6 +15,7 @@ const graph_sorter_1 = require("../../lib/graph-sorter");
15
15
  const graph_loader_1 = require("../../lib/graph-loader");
16
16
  const graph_metadata_1 = require("../../lib/graph-metadata");
17
17
  const api_scanner_1 = require("../../lib/api-usage/api-scanner");
18
+ const runtime_config_1 = require("../../lib/runtime-config");
18
19
  const graph_visualizer_1 = require("../../lib/graph-visualizer");
19
20
  const runtime_graph_1 = require("../../lib/runtime-graph");
20
21
  const toError_1 = require("../../toError");
@@ -25,12 +26,14 @@ const toError_1 = require("../../toError");
25
26
  * pubsub APIs become edges the viz draws through a queue.
26
27
  */
27
28
  // webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file
28
- function generateRuntimeGraph(workspaceRoot, graph, hiddenProjects) {
29
+ function generateRuntimeGraph(workspaceRoot, graph, hiddenProjects, apiContracts) {
29
30
  console.log('šŸ“” Deriving runtime graph from dependencies.json (implements Ɨ uses per API)...');
30
- const report = (0, runtime_graph_1.deriveRuntimeGraphReport)(graph, hiddenProjects);
31
+ const report = (0, runtime_graph_1.deriveRuntimeGraphReport)(graph, hiddenProjects, apiContracts);
31
32
  (0, runtime_graph_1.saveRuntimeGraph)(report.graph, workspaceRoot);
32
33
  const serviceCount = Object.keys(report.graph.services).length;
33
- console.log(`āœ… Runtime graph saved (${serviceCount} services, ${report.graph.runtimeEdges.length} runtime edges)`);
34
+ const queueCount = Object.keys(report.graph.queues).length;
35
+ console.log(`āœ… Runtime graph saved (${serviceCount} services, ${report.graph.runtimeEdges.length} runtime edges, ` +
36
+ `${queueCount} queues, ${report.graph.triggers.length} cron/external triggers)`);
34
37
  // Every edge the derivation had to GUESS at. Loud on purpose: a fanned-out edge is committed and
35
38
  // then reasoned about as if it were derived, which is how a fictional call survives for months.
36
39
  printRuntimeWarnings(report.warnings);
@@ -60,9 +63,37 @@ function printRuntimeWarnings(warnings) {
60
63
  // webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file
61
64
  function scanApiRelations(workspaceRoot, graph, projectInfos) {
62
65
  console.log('šŸ”Ž Scanning source for implements/uses API relations...');
63
- const scan = (0, api_scanner_1.scanAndAttachApiRelations)(workspaceRoot, graph, projectInfos);
66
+ const externalApiPaths = (0, runtime_config_1.loadRuntimeConfig)(workspaceRoot).externalApiPaths;
67
+ const scan = (0, api_scanner_1.scanAndAttachApiRelations)(workspaceRoot, graph, projectInfos, externalApiPaths);
64
68
  if (scan.unresolvedApiCalls.length > 0)
65
69
  console.warn((0, api_scanner_1.describeUnresolvedApiCalls)(scan.unresolvedApiCalls));
70
+ const contracts = (0, api_scanner_1.buildApiContracts)(scan);
71
+ // An endpoint whose declared trigger its api kind cannot deliver would silently draw a queue or a
72
+ // clock that nothing could ever fire — name it here, where the fix is one decorator away.
73
+ const mismatches = (0, api_scanner_1.describeMismatchedEndpointKinds)(contracts);
74
+ if (mismatches.length > 0) {
75
+ console.error(`āŒ ${mismatches.length} @Endpoint kind(s) conflict with their api kind:`);
76
+ for (const mismatch of mismatches)
77
+ console.error(` • ${mismatch}`);
78
+ }
79
+ return contracts;
80
+ }
81
+ /** Projects tagged drawOnGraph:false — kept in the JSON, omitted from every rendered graph. */
82
+ // webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file
83
+ function hiddenProjectsIn(graph) {
84
+ const hidden = new Set();
85
+ for (const name of Object.keys(graph)) {
86
+ if (graph[name].drawOnGraph === false)
87
+ hidden.add(name);
88
+ }
89
+ return hidden;
90
+ }
91
+ // webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file
92
+ function printGraphSummary(graph) {
93
+ const levels = new Set(Object.values(graph).map((entry) => entry.level));
94
+ console.log(`\nšŸ“ˆ Graph Summary:`);
95
+ console.log(` Projects: ${Object.keys(graph).length}`);
96
+ console.log(` Levels: ${levels.size} (0-${Math.max(...levels)})`);
66
97
  }
67
98
  async function runExecutor(options, context) {
68
99
  const graphPath = options.graphPath;
@@ -85,10 +116,12 @@ async function runExecutor(options, context) {
85
116
  // Step 3b: Classify each api-lib edge (implements/uses + rpc/pubsub) by
86
117
  // scanning source, so dependencies.json + the viz + the runtime graph all
87
118
  // read the same derived truth.
88
- scanApiRelations(workspaceRoot, enhancedGraph, projectInfos);
89
- // Step 4: Save the graph
119
+ const apiContracts = scanApiRelations(workspaceRoot, enhancedGraph, projectInfos);
120
+ // Step 4: Save the graph, INCLUDING the per-contract method table the runtime derivation
121
+ // reads back — generate derives from the in-memory graph, validate from the file, so
122
+ // anything not written here would make the two disagree.
90
123
  console.log('šŸ’¾ Saving graph to architecture/dependencies.json...');
91
- (0, graph_loader_1.saveGraph)(enhancedGraph, workspaceRoot, graphPath);
124
+ (0, graph_loader_1.saveGraph)(enhancedGraph, workspaceRoot, graphPath, apiContracts);
92
125
  console.log('āœ… Graph saved successfully');
93
126
  // Step 4b: Write the committed, clickable HTML view next to the JSON so
94
127
  // dependencies.html regenerates in lock-step with dependencies.json.
@@ -97,18 +130,8 @@ async function runExecutor(options, context) {
97
130
  // Step 5: Generate the runtime microservice graph from the same scan.
98
131
  // Projects tagged drawOnGraph:false are threaded through so the runtime
99
132
  // graph hides them too (they stay flagged in runtime-dependencies.json).
100
- const hiddenProjects = new Set();
101
- for (const name of Object.keys(enhancedGraph)) {
102
- if (enhancedGraph[name].drawOnGraph === false)
103
- hiddenProjects.add(name);
104
- }
105
- generateRuntimeGraph(workspaceRoot, enhancedGraph, hiddenProjects);
106
- // Print summary
107
- const projectCount = Object.keys(enhancedGraph).length;
108
- const levels = new Set(Object.values(enhancedGraph).map((e) => e.level));
109
- console.log(`\nšŸ“ˆ Graph Summary:`);
110
- console.log(` Projects: ${projectCount}`);
111
- console.log(` Levels: ${levels.size} (0-${Math.max(...levels)})`);
133
+ generateRuntimeGraph(workspaceRoot, enhancedGraph, hiddenProjectsIn(enhancedGraph), apiContracts);
134
+ printGraphSummary(enhancedGraph);
112
135
  return { success: true };
113
136
  }
114
137
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/generate/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAsEH,8BAoEC;AAvID,0DAAwD;AACxD,+DAAiE;AACjE,yDAAgE;AAChE,yDAAmD;AACnD,6DAAoG;AAEpG,iEAAwG;AAExG,iEAA6D;AAC7D,2DAAqF;AACrF,2CAAwC;AAUxC;;;;;GAKG;AACH,2GAA2G;AAC3G,SAAS,oBAAoB,CAAC,aAAqB,EAAE,KAAoB,EAAE,cAA2B;IAClG,OAAO,CAAC,GAAG,CAAC,iFAAiF,CAAC,CAAC;IAC/F,MAAM,MAAM,GAAG,IAAA,wCAAwB,EAAC,KAAK,EAAE,cAAc,CAAC,CAAC;IAC/D,IAAA,gCAAgB,EAAC,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;IAC/D,OAAO,CAAC,GAAG,CACP,0BAA0B,YAAY,cAAc,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,iBAAiB,CACxG,CAAC;IACF,iGAAiG;IACjG,gGAAgG;IAChG,oBAAoB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,8FAA8F;IAC9F,2FAA2F;IAC3F,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM,sDAAsD,CAAC,CAAC;QACjG,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ;YAAE,OAAO,CAAC,KAAK,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;QAC1E,OAAO,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC/E,CAAC;AACL,CAAC;AAED,sGAAsG;AACtG,2GAA2G;AAC3G,SAAS,oBAAoB,CAAC,QAAkB;IAC5C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAClC,OAAO,CAAC,IAAI,CAAC,OAAO,QAAQ,CAAC,MAAM,wDAAwD,CAAC,CAAC;IAC7F,KAAK,MAAM,OAAO,IAAI,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,2GAA2G;AAC3G,SAAS,gBAAgB,CAAC,aAAqB,EAAE,KAAoB,EAAE,YAAsC;IACzG,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;IACvE,MAAM,IAAI,GAAG,IAAA,uCAAyB,EAAC,aAAa,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAC3E,IAAI,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,IAAA,wCAA0B,EAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAC9G,CAAC;AAEc,KAAK,UAAU,WAAW,CACrC,OAAgC,EAChC,OAAwB;IAExB,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IACpC,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;IAEnD,8DAA8D;IAC9D,IAAI,CAAC;QACD,gFAAgF;QAChF,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;QACzE,MAAM,YAAY,GAAG,MAAM,IAAA,sCAAoB,GAAE,CAAC;QAElD,gEAAgE;QAChE,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,IAAA,qCAAsB,EAAC,YAAY,CAAC,CAAC;QAE3D,qEAAqE;QACrE,uEAAuE;QACvE,qEAAqE;QACrE,OAAO,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAC;QAClF,MAAM,YAAY,GAAG,MAAM,IAAA,mCAAkB,GAAE,CAAC;QAChD,IAAA,4BAAW,EAAC,aAAa,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;QAExD,wEAAwE;QACxE,0EAA0E;QAC1E,+BAA+B;QAC/B,gBAAgB,CAAC,aAAa,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC;QAE7D,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;QACpE,IAAA,wBAAS,EAAC,aAAa,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAE1C,wEAAwE;QACxE,qEAAqE;QACrE,MAAM,QAAQ,GAAG,IAAI,kCAAe,EAAE,CAAC,kBAAkB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACxF,OAAO,CAAC,GAAG,CAAC,WAAW,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAE5C,sEAAsE;QACtE,wEAAwE;QACxE,yEAAyE;QACzE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YAC5C,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,WAAW,KAAK,KAAK;gBAAE,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5E,CAAC;QACD,oBAAoB,CAAC,aAAa,EAAE,aAAa,EAAE,cAAc,CAAC,CAAC;QAEnE,gBAAgB;QAChB,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACzE,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,gBAAgB,YAAY,EAAE,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,cAAc,MAAM,CAAC,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAEpE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3D,IAAI,KAAK,YAAY,wCAAuB,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,IAAA,4BAAa,EAAC,aAAa,EAAE,+BAA+B,CAAC,CAAC;YAC7E,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,mBAAmB,GAAG,MAAM,GAAG,qDAAqD,CAAC,CAAC;QACxG,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Generate Executor\n *\n * Generates the architecture dependency graph and saves it to architecture/dependencies.json.\n *\n * Usage:\n * nx run architecture:generate\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { writeTemplate } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { saveGraph } from '../../lib/graph-loader';\nimport { collectProjectInfo, enrichGraph, MetadataValidationError } from '../../lib/graph-metadata';\nimport { ProjectInfo } from '../../lib/project-info';\nimport { scanAndAttachApiRelations, describeUnresolvedApiCalls } from '../../lib/api-usage/api-scanner';\nimport type { EnhancedGraph } from '../../lib/graph-sorter';\nimport { GraphVisualizer } from '../../lib/graph-visualizer';\nimport { deriveRuntimeGraphReport, saveRuntimeGraph } from '../../lib/runtime-graph';\nimport { toError } from '../../toError';\n\nexport interface GenerateExecutorOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/**\n * Generate the runtime microservice graph alongside the compile-time graph, DERIVED from the same\n * dependencies.json (its per-project apiRelations) — one regenerate produces both committed files, and\n * validate derives from the SAME source so they can't diverge. rpc APIs become direct runtime edges;\n * pubsub APIs become edges the viz draws through a queue.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file\nfunction generateRuntimeGraph(workspaceRoot: string, graph: EnhancedGraph, hiddenProjects: Set<string>): void {\n console.log('šŸ“” Deriving runtime graph from dependencies.json (implements Ɨ uses per API)...');\n const report = deriveRuntimeGraphReport(graph, hiddenProjects);\n saveRuntimeGraph(report.graph, workspaceRoot);\n const serviceCount = Object.keys(report.graph.services).length;\n console.log(\n `āœ… Runtime graph saved (${serviceCount} services, ${report.graph.runtimeEdges.length} runtime edges)`,\n );\n // Every edge the derivation had to GUESS at. Loud on purpose: a fanned-out edge is committed and\n // then reasoned about as if it were derived, which is how a fictional call survives for months.\n printRuntimeWarnings(report.warnings);\n // Printed here, FAILED by validate-runtime-architecture: generate must still write the graph,\n // or the validator would have nothing to compare against and the error would be unfixable.\n if (report.problems.length > 0) {\n console.error(`āŒ ${report.problems.length} client call(s) name a service no module answers to:`);\n for (const problem of report.problems) console.error(` • ${problem}`);\n console.error(' This FAILS architecture:validate-runtime-architecture.');\n }\n}\n\n/** Print the derivation's guessed-edge warnings (nothing at all when the graph is fully targeted). */\n// webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file\nfunction printRuntimeWarnings(warnings: string[]): void {\n if (warnings.length === 0) return;\n console.warn(`āš ļø ${warnings.length} runtime edge(s) could not be targeted to ONE service:`);\n for (const warning of warnings) console.warn(` • ${warning}`);\n}\n\n/**\n * Scan for api relations and attach them, surfacing any contract we could NOT map back to source.\n * Unresolved contracts mean the graph we are about to WRITE is incomplete, so they must be loud —\n * a green run that quietly omits a service gets committed, rendered, and trusted.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file\nfunction scanApiRelations(workspaceRoot: string, graph: EnhancedGraph, projectInfos: Map<string, ProjectInfo>): void {\n console.log('šŸ”Ž Scanning source for implements/uses API relations...');\n const scan = scanAndAttachApiRelations(workspaceRoot, graph, projectInfos);\n if (scan.unresolvedApiCalls.length > 0) console.warn(describeUnresolvedApiCalls(scan.unresolvedApiCalls));\n}\n\nexport default async function runExecutor(\n options: GenerateExecutorOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n console.log('\\nšŸ“Š Architecture Graph Generator\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Build the full graph from nx, then transitively reduce it to the view\n console.log(\"šŸ“Š Generating dependency graph from nx's project graph...\");\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to assign levels for visualization)\n console.log('šŸ”„ Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Enrich with AI metadata (framework, shortDescription, file\n // pointers). This VALIDATES (responsibilities.md required per project)\n // and throws before any write, so a failure never clobbers the file.\n console.log('šŸ·ļø Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(enhancedGraph, projectInfos, workspaceRoot);\n\n // Step 3b: Classify each api-lib edge (implements/uses + rpc/pubsub) by\n // scanning source, so dependencies.json + the viz + the runtime graph all\n // read the same derived truth.\n scanApiRelations(workspaceRoot, enhancedGraph, projectInfos);\n\n // Step 4: Save the graph\n console.log('šŸ’¾ Saving graph to architecture/dependencies.json...');\n saveGraph(enhancedGraph, workspaceRoot, graphPath);\n console.log('āœ… Graph saved successfully');\n\n // Step 4b: Write the committed, clickable HTML view next to the JSON so\n // dependencies.html regenerates in lock-step with dependencies.json.\n const vizPaths = new GraphVisualizer().writeVisualization(enhancedGraph, workspaceRoot);\n console.log(`āœ… Wrote ${vizPaths.htmlPath}`);\n\n // Step 5: Generate the runtime microservice graph from the same scan.\n // Projects tagged drawOnGraph:false are threaded through so the runtime\n // graph hides them too (they stay flagged in runtime-dependencies.json).\n const hiddenProjects = new Set<string>();\n for (const name of Object.keys(enhancedGraph)) {\n if (enhancedGraph[name].drawOnGraph === false) hiddenProjects.add(name);\n }\n generateRuntimeGraph(workspaceRoot, enhancedGraph, hiddenProjects);\n\n // Print summary\n const projectCount = Object.keys(enhancedGraph).length;\n const levels = new Set(Object.values(enhancedGraph).map((e) => e.level));\n console.log(`\\nšŸ“ˆ Graph Summary:`);\n console.log(` Projects: ${projectCount}`);\n console.log(` Levels: ${levels.size} (0-${Math.max(...levels)})`);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Graph generation failed:', error.message);\n if (error instanceof MetadataValidationError) {\n const mdPath = writeTemplate(workspaceRoot, 'webpieces.responsibilities.md');\n console.error('');\n console.error('āš ļø *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** āš ļø');\n }\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/generate/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generate Executor\n *\n * Generates the architecture dependency graph and saves it to architecture/dependencies.json.\n *\n * Usage:\n * nx run architecture:generate\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { writeTemplate } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { saveGraph } from '../../lib/graph-loader';\nimport { collectProjectInfo, enrichGraph, MetadataValidationError } from '../../lib/graph-metadata';\nimport { ProjectInfo } from '../../lib/project-info';\nimport {\n scanAndAttachApiRelations,\n describeUnresolvedApiCalls,\n buildApiContracts,\n describeMismatchedEndpointKinds,\n} from '../../lib/api-usage/api-scanner';\nimport type { ApiContracts } from '../../lib/api-usage/api-relations';\nimport { loadRuntimeConfig } from '../../lib/runtime-config';\nimport type { EnhancedGraph, GraphEntry } from '../../lib/graph-sorter';\nimport { GraphVisualizer } from '../../lib/graph-visualizer';\nimport { deriveRuntimeGraphReport, saveRuntimeGraph } from '../../lib/runtime-graph';\nimport { toError } from '../../toError';\n\nexport interface GenerateExecutorOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/**\n * Generate the runtime microservice graph alongside the compile-time graph, DERIVED from the same\n * dependencies.json (its per-project apiRelations) — one regenerate produces both committed files, and\n * validate derives from the SAME source so they can't diverge. rpc APIs become direct runtime edges;\n * pubsub APIs become edges the viz draws through a queue.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file\nfunction generateRuntimeGraph(\n workspaceRoot: string,\n graph: EnhancedGraph,\n hiddenProjects: Set<string>,\n apiContracts: ApiContracts,\n): void {\n console.log('šŸ“” Deriving runtime graph from dependencies.json (implements Ɨ uses per API)...');\n const report = deriveRuntimeGraphReport(graph, hiddenProjects, apiContracts);\n saveRuntimeGraph(report.graph, workspaceRoot);\n const serviceCount = Object.keys(report.graph.services).length;\n const queueCount = Object.keys(report.graph.queues).length;\n console.log(\n `āœ… Runtime graph saved (${serviceCount} services, ${report.graph.runtimeEdges.length} runtime edges, ` +\n `${queueCount} queues, ${report.graph.triggers.length} cron/external triggers)`,\n );\n // Every edge the derivation had to GUESS at. Loud on purpose: a fanned-out edge is committed and\n // then reasoned about as if it were derived, which is how a fictional call survives for months.\n printRuntimeWarnings(report.warnings);\n // Printed here, FAILED by validate-runtime-architecture: generate must still write the graph,\n // or the validator would have nothing to compare against and the error would be unfixable.\n if (report.problems.length > 0) {\n console.error(`āŒ ${report.problems.length} client call(s) name a service no module answers to:`);\n for (const problem of report.problems) console.error(` • ${problem}`);\n console.error(' This FAILS architecture:validate-runtime-architecture.');\n }\n}\n\n/** Print the derivation's guessed-edge warnings (nothing at all when the graph is fully targeted). */\n// webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file\nfunction printRuntimeWarnings(warnings: string[]): void {\n if (warnings.length === 0) return;\n console.warn(`āš ļø ${warnings.length} runtime edge(s) could not be targeted to ONE service:`);\n for (const warning of warnings) console.warn(` • ${warning}`);\n}\n\n/**\n * Scan for api relations and attach them, surfacing any contract we could NOT map back to source.\n * Unresolved contracts mean the graph we are about to WRITE is incomplete, so they must be loud —\n * a green run that quietly omits a service gets committed, rendered, and trusted.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file\nfunction scanApiRelations(\n workspaceRoot: string,\n graph: EnhancedGraph,\n projectInfos: Map<string, ProjectInfo>,\n): ApiContracts {\n console.log('šŸ”Ž Scanning source for implements/uses API relations...');\n const externalApiPaths = loadRuntimeConfig(workspaceRoot).externalApiPaths;\n const scan = scanAndAttachApiRelations(workspaceRoot, graph, projectInfos, externalApiPaths);\n if (scan.unresolvedApiCalls.length > 0) console.warn(describeUnresolvedApiCalls(scan.unresolvedApiCalls));\n const contracts = buildApiContracts(scan);\n // An endpoint whose declared trigger its api kind cannot deliver would silently draw a queue or a\n // clock that nothing could ever fire — name it here, where the fix is one decorator away.\n const mismatches = describeMismatchedEndpointKinds(contracts);\n if (mismatches.length > 0) {\n console.error(`āŒ ${mismatches.length} @Endpoint kind(s) conflict with their api kind:`);\n for (const mismatch of mismatches) console.error(` • ${mismatch}`);\n }\n return contracts;\n}\n\n/** Projects tagged drawOnGraph:false — kept in the JSON, omitted from every rendered graph. */\n// webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file\nfunction hiddenProjectsIn(graph: EnhancedGraph): Set<string> {\n const hidden = new Set<string>();\n for (const name of Object.keys(graph)) {\n if (graph[name].drawOnGraph === false) hidden.add(name);\n }\n return hidden;\n}\n\n// webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file\nfunction printGraphSummary(graph: EnhancedGraph): void {\n const levels = new Set(Object.values(graph).map((entry: GraphEntry) => entry.level));\n console.log(`\\nšŸ“ˆ Graph Summary:`);\n console.log(` Projects: ${Object.keys(graph).length}`);\n console.log(` Levels: ${levels.size} (0-${Math.max(...levels)})`);\n}\n\nexport default async function runExecutor(\n options: GenerateExecutorOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n console.log('\\nšŸ“Š Architecture Graph Generator\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Build the full graph from nx, then transitively reduce it to the view\n console.log(\"šŸ“Š Generating dependency graph from nx's project graph...\");\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to assign levels for visualization)\n console.log('šŸ”„ Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Enrich with AI metadata (framework, shortDescription, file\n // pointers). This VALIDATES (responsibilities.md required per project)\n // and throws before any write, so a failure never clobbers the file.\n console.log('šŸ·ļø Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(enhancedGraph, projectInfos, workspaceRoot);\n\n // Step 3b: Classify each api-lib edge (implements/uses + rpc/pubsub) by\n // scanning source, so dependencies.json + the viz + the runtime graph all\n // read the same derived truth.\n const apiContracts = scanApiRelations(workspaceRoot, enhancedGraph, projectInfos);\n\n // Step 4: Save the graph, INCLUDING the per-contract method table the runtime derivation\n // reads back — generate derives from the in-memory graph, validate from the file, so\n // anything not written here would make the two disagree.\n console.log('šŸ’¾ Saving graph to architecture/dependencies.json...');\n saveGraph(enhancedGraph, workspaceRoot, graphPath, apiContracts);\n console.log('āœ… Graph saved successfully');\n\n // Step 4b: Write the committed, clickable HTML view next to the JSON so\n // dependencies.html regenerates in lock-step with dependencies.json.\n const vizPaths = new GraphVisualizer().writeVisualization(enhancedGraph, workspaceRoot);\n console.log(`āœ… Wrote ${vizPaths.htmlPath}`);\n\n // Step 5: Generate the runtime microservice graph from the same scan.\n // Projects tagged drawOnGraph:false are threaded through so the runtime\n // graph hides them too (they stay flagged in runtime-dependencies.json).\n generateRuntimeGraph(workspaceRoot, enhancedGraph, hiddenProjectsIn(enhancedGraph), apiContracts);\n\n printGraphSummary(enhancedGraph);\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Graph generation failed:', error.message);\n if (error instanceof MetadataValidationError) {\n const mdPath = writeTemplate(workspaceRoot, 'webpieces.responsibilities.md');\n console.error('');\n console.error('āš ļø *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** āš ļø');\n }\n return { success: false };\n }\n}\n"]}
@@ -16,6 +16,7 @@ const graph_generator_1 = require("../../lib/graph-generator");
16
16
  const graph_sorter_1 = require("../../lib/graph-sorter");
17
17
  const graph_metadata_1 = require("../../lib/graph-metadata");
18
18
  const api_scanner_1 = require("../../lib/api-usage/api-scanner");
19
+ const runtime_config_1 = require("../../lib/runtime-config");
19
20
  const api_relations_validator_1 = require("../../lib/api-usage/api-relations-validator");
20
21
  const toError_1 = require("../../toError");
21
22
  // webpieces-disable no-function-outside-class -- nx executor entry point (default export), like every sibling executor
@@ -29,7 +30,11 @@ async function runExecutor(_options, context) {
29
30
  const rawGraph = await (0, graph_generator_1.generateGraph)();
30
31
  const graph = (0, graph_sorter_1.sortGraphTopologically)(rawGraph);
31
32
  const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
32
- const scan = (0, api_scanner_1.scanAndAttachApiRelations)(workspaceRoot, graph, projectInfos);
33
+ // Same externalApiPaths as generate: a vendor lib must be classified by the SAME rules here,
34
+ // or a server depending on one would be reported unclassified purely because this scan was
35
+ // configured differently from the one that wrote the graph.
36
+ const externalApiPaths = (0, runtime_config_1.loadRuntimeConfig)(workspaceRoot).externalApiPaths;
37
+ const scan = (0, api_scanner_1.scanAndAttachApiRelations)(workspaceRoot, graph, projectInfos, externalApiPaths);
33
38
  const violations = (0, api_relations_validator_1.findUnclassifiedApiDeps)(graph, projectInfos, scan);
34
39
  if (violations.length === 0) {
35
40
  console.log('āœ… Every server/client api-lib dependency is implemented or used.');
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-api-relations/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AAmBH,8BAkCC;AAlDD,+DAA0D;AAC1D,yDAAgE;AAChE,6DAA8D;AAC9D,iEAA4E;AAC5E,yFAAkH;AAClH,2CAAwC;AAUxC,uHAAuH;AACxG,KAAK,UAAU,WAAW,CACrC,QAAqC,EACrC,OAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;IAExF,8DAA8D;IAC9D,IAAI,CAAC;QACD,4EAA4E;QAC5E,8DAA8D;QAC9D,MAAM,QAAQ,GAAG,MAAM,IAAA,+BAAa,GAAE,CAAC;QACvC,MAAM,KAAK,GAAG,IAAA,qCAAsB,EAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,YAAY,GAAG,MAAM,IAAA,mCAAkB,GAAE,CAAC;QAChD,MAAM,IAAI,GAAG,IAAA,uCAAyB,EAAC,aAAa,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;QAE3E,MAAM,UAAU,GAAG,IAAA,iDAAuB,EAAC,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;QACtE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;YAChF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC,MAAM,wCAAwC,CAAC,CAAC;QAC9E,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACjC,OAAO,CAAC,KAAK,CAAC,IAAA,oDAA0B,EAAC,SAAS,CAAC,CAAC,CAAC;YACrD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACnE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Validate API Relations Executor\n *\n * Fails when a runnable project (role:server / role:client) depends on an api-lib\n * but the source scan finds it neither IMPLEMENTS (serves via addRoutes) nor USES\n * (calls via createRpcClient/createPubSubClient) any of that api-lib's contracts.\n * Such an edge is an unexplained dependency — a dead import or forgotten wiring.\n *\n * Usage:\n * nx run architecture:validate-api-relations\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { collectProjectInfo } from '../../lib/graph-metadata';\nimport { scanAndAttachApiRelations } from '../../lib/api-usage/api-scanner';\nimport { findUnclassifiedApiDeps, describeUnclassifiedApiDep } from '../../lib/api-usage/api-relations-validator';\nimport { toError } from '../../toError';\n\nexport interface ValidateApiRelationsOptions {\n // No options needed\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n// webpieces-disable no-function-outside-class -- nx executor entry point (default export), like every sibling executor\nexport default async function runExecutor(\n _options: ValidateApiRelationsOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n console.log('\\nšŸ”— Validating API Relations (implements/uses per api-lib dependency)\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Full (un-reduced) graph so a direct api-lib dependency is never hidden by\n // transitive reduction, then attach the derived apiRelations.\n const rawGraph = await generateGraph();\n const graph = sortGraphTopologically(rawGraph);\n const projectInfos = await collectProjectInfo();\n const scan = scanAndAttachApiRelations(workspaceRoot, graph, projectInfos);\n\n const violations = findUnclassifiedApiDeps(graph, projectInfos, scan);\n if (violations.length === 0) {\n console.log('āœ… Every server/client api-lib dependency is implemented or used.');\n return { success: true };\n }\n\n console.error(`āŒ ${violations.length} unexplained api-lib dependenc(ies):\\n`);\n for (const violation of violations) {\n console.error(describeUnclassifiedApiDep(violation));\n console.error('');\n }\n return { success: false };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ API relations validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-api-relations/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AAoBH,8BAsCC;AAvDD,+DAA0D;AAC1D,yDAAgE;AAChE,6DAA8D;AAC9D,iEAA4E;AAC5E,6DAA6D;AAC7D,yFAAkH;AAClH,2CAAwC;AAUxC,uHAAuH;AACxG,KAAK,UAAU,WAAW,CACrC,QAAqC,EACrC,OAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;IAExF,8DAA8D;IAC9D,IAAI,CAAC;QACD,4EAA4E;QAC5E,8DAA8D;QAC9D,MAAM,QAAQ,GAAG,MAAM,IAAA,+BAAa,GAAE,CAAC;QACvC,MAAM,KAAK,GAAG,IAAA,qCAAsB,EAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,YAAY,GAAG,MAAM,IAAA,mCAAkB,GAAE,CAAC;QAChD,6FAA6F;QAC7F,2FAA2F;QAC3F,4DAA4D;QAC5D,MAAM,gBAAgB,GAAG,IAAA,kCAAiB,EAAC,aAAa,CAAC,CAAC,gBAAgB,CAAC;QAC3E,MAAM,IAAI,GAAG,IAAA,uCAAyB,EAAC,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;QAE7F,MAAM,UAAU,GAAG,IAAA,iDAAuB,EAAC,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;QACtE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;YAChF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC,MAAM,wCAAwC,CAAC,CAAC;QAC9E,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACjC,OAAO,CAAC,KAAK,CAAC,IAAA,oDAA0B,EAAC,SAAS,CAAC,CAAC,CAAC;YACrD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACnE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Validate API Relations Executor\n *\n * Fails when a runnable project (role:server / role:client) depends on an api-lib\n * but the source scan finds it neither IMPLEMENTS (serves via addRoutes) nor USES\n * (calls via createRpcClient/createPubSubClient) any of that api-lib's contracts.\n * Such an edge is an unexplained dependency — a dead import or forgotten wiring.\n *\n * Usage:\n * nx run architecture:validate-api-relations\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { collectProjectInfo } from '../../lib/graph-metadata';\nimport { scanAndAttachApiRelations } from '../../lib/api-usage/api-scanner';\nimport { loadRuntimeConfig } from '../../lib/runtime-config';\nimport { findUnclassifiedApiDeps, describeUnclassifiedApiDep } from '../../lib/api-usage/api-relations-validator';\nimport { toError } from '../../toError';\n\nexport interface ValidateApiRelationsOptions {\n // No options needed\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n// webpieces-disable no-function-outside-class -- nx executor entry point (default export), like every sibling executor\nexport default async function runExecutor(\n _options: ValidateApiRelationsOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n console.log('\\nšŸ”— Validating API Relations (implements/uses per api-lib dependency)\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Full (un-reduced) graph so a direct api-lib dependency is never hidden by\n // transitive reduction, then attach the derived apiRelations.\n const rawGraph = await generateGraph();\n const graph = sortGraphTopologically(rawGraph);\n const projectInfos = await collectProjectInfo();\n // Same externalApiPaths as generate: a vendor lib must be classified by the SAME rules here,\n // or a server depending on one would be reported unclassified purely because this scan was\n // configured differently from the one that wrote the graph.\n const externalApiPaths = loadRuntimeConfig(workspaceRoot).externalApiPaths;\n const scan = scanAndAttachApiRelations(workspaceRoot, graph, projectInfos, externalApiPaths);\n\n const violations = findUnclassifiedApiDeps(graph, projectInfos, scan);\n if (violations.length === 0) {\n console.log('āœ… Every server/client api-lib dependency is implemented or used.');\n return { success: true };\n }\n\n console.error(`āŒ ${violations.length} unexplained api-lib dependenc(ies):\\n`);\n for (const violation of violations) {\n console.error(describeUnclassifiedApiDep(violation));\n console.error('');\n }\n return { success: false };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ API relations validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
@@ -17,6 +17,7 @@ const graph_comparator_1 = require("../../lib/graph-comparator");
17
17
  const graph_loader_1 = require("../../lib/graph-loader");
18
18
  const graph_metadata_1 = require("../../lib/graph-metadata");
19
19
  const api_scanner_1 = require("../../lib/api-usage/api-scanner");
20
+ const runtime_config_1 = require("../../lib/runtime-config");
20
21
  const rule_gate_1 = require("../../lib/rule-gate");
21
22
  const toError_1 = require("../../toError");
22
23
  const TMP_MD_FILE = 'webpieces.dependencies.md';
@@ -44,6 +45,29 @@ function reportMismatch(summary, workspaceRoot) {
44
45
  console.error(' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change');
45
46
  console.error(' 3. Commit the updated architecture/dependencies.json');
46
47
  }
48
+ /**
49
+ * A human-readable summary of how the regenerated api contract table differs from the committed one,
50
+ * or null when they match. Named per contract so the message points at the api that changed rather
51
+ * than dumping two JSON blobs.
52
+ */
53
+ // webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch in this file
54
+ function describeContractDrift(current, saved) {
55
+ const names = [...new Set([...Object.keys(current), ...Object.keys(saved)])].sort();
56
+ const changes = [];
57
+ for (const name of names) {
58
+ const a = current[name];
59
+ const b = saved[name];
60
+ if (a === undefined)
61
+ changes.push(` - ${name}: in dependencies.json but no longer in source`);
62
+ else if (b === undefined)
63
+ changes.push(` + ${name}: in source but missing from dependencies.json`);
64
+ else if (JSON.stringify(a) !== JSON.stringify(b))
65
+ changes.push(` ~ ${name}: endpoints/kinds/queues changed`);
66
+ }
67
+ if (changes.length === 0)
68
+ return null;
69
+ return `apiContracts drift (${changes.length} contract(s)):\n${changes.join('\n')}`;
70
+ }
47
71
  /**
48
72
  * Build the current dependency graph exactly as the generator does: reduce the nx
49
73
  * graph, sort into levels, enrich with metadata, and attach the derived
@@ -59,8 +83,31 @@ async function buildCurrentGraph(workspaceRoot) {
59
83
  const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
60
84
  (0, graph_metadata_1.enrichGraph)(currentGraph, projectInfos, workspaceRoot);
61
85
  console.log('šŸ”Ž Scanning source for implements/uses API relations...');
62
- (0, api_scanner_1.scanAndAttachApiRelations)(workspaceRoot, currentGraph, projectInfos);
63
- return currentGraph;
86
+ // The SAME externalApiPaths the generator uses: scanning without them would drop every vendor
87
+ // relation from the regenerated graph and report drift against a perfectly fresh file.
88
+ const externalApiPaths = (0, runtime_config_1.loadRuntimeConfig)(workspaceRoot).externalApiPaths;
89
+ const scan = (0, api_scanner_1.scanAndAttachApiRelations)(workspaceRoot, currentGraph, projectInfos, externalApiPaths);
90
+ return new CurrentArchitecture(currentGraph, (0, api_scanner_1.buildApiContracts)(scan));
91
+ }
92
+ /** The regenerated graph plus its api contract table — both halves of what dependencies.json holds. */
93
+ class CurrentArchitecture {
94
+ graph;
95
+ apiContracts;
96
+ constructor(graph, apiContracts) {
97
+ this.graph = graph;
98
+ this.apiContracts = apiContracts;
99
+ }
100
+ }
101
+ /** The bootstrap path: there is nothing to diff against yet, so say how to create it. */
102
+ // webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch in this file
103
+ function reportMissingGraph() {
104
+ console.error('āŒ No saved graph found at architecture/dependencies.json');
105
+ console.error('');
106
+ console.error('To initialize:');
107
+ console.error(' 1. Run: nx run architecture:generate');
108
+ console.error(' 2. Run: nx run architecture:visualize');
109
+ console.error(' 3. Manually inspect the generated graph to confirm it is the desired architecture');
110
+ console.error(' 4. Commit architecture/dependencies.json');
64
111
  }
65
112
  async function runExecutor(options, context) {
66
113
  const graphPath = options.graphPath;
@@ -75,13 +122,7 @@ async function runExecutor(options, context) {
75
122
  try {
76
123
  // Check if saved graph exists
77
124
  if (!(0, graph_loader_1.graphFileExists)(workspaceRoot, graphPath)) {
78
- console.error('āŒ No saved graph found at architecture/dependencies.json');
79
- console.error('');
80
- console.error('To initialize:');
81
- console.error(' 1. Run: nx run architecture:generate');
82
- console.error(' 2. Run: nx run architecture:visualize');
83
- console.error(' 3. Manually inspect the generated graph to confirm it is the desired architecture');
84
- console.error(' 4. Commit architecture/dependencies.json');
125
+ reportMissingGraph();
85
126
  return { success: false };
86
127
  }
87
128
  // Steps 1-3: build + enrich + scan the current graph (same pipeline the
@@ -96,13 +137,22 @@ async function runExecutor(options, context) {
96
137
  }
97
138
  // Step 5: Compare graphs
98
139
  console.log('šŸ” Comparing current graph to saved graph...');
99
- const comparison = (0, graph_comparator_1.compareGraphs)(currentGraph, savedGraph.projects);
100
- if (comparison.identical) {
101
- console.log('āœ… Architecture unchanged - current graph matches saved graph');
102
- return { success: true };
140
+ const comparison = (0, graph_comparator_1.compareGraphs)(currentGraph.graph, savedGraph.projects);
141
+ if (!comparison.identical) {
142
+ reportMismatch(comparison.summary, workspaceRoot);
143
+ return { success: false };
103
144
  }
104
- reportMismatch(comparison.summary, workspaceRoot);
105
- return { success: false };
145
+ // Step 6: Compare the api contract table too. It is NOT part of the project graph, so a
146
+ // changed @Endpoint kind or queue name would otherwise pass here AND pass
147
+ // validate-runtime-architecture (which derives from this same stale file) — leaving a queue
148
+ // or cron in the committed graph that no longer matches the source.
149
+ const contractDrift = describeContractDrift(currentGraph.apiContracts, savedGraph.apiContracts);
150
+ if (contractDrift !== null) {
151
+ reportMismatch(contractDrift, workspaceRoot);
152
+ return { success: false };
153
+ }
154
+ console.log('āœ… Architecture unchanged - current graph matches saved graph');
155
+ return { success: true };
106
156
  }
107
157
  catch (err) {
108
158
  const error = (0, toError_1.toError)(err);
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-architecture-unchanged/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Architecture Unchanged Executor\n *\n * Validates that the current architecture graph matches the saved blessed graph.\n * This ensures no unapproved architecture changes have been made.\n *\n * Usage:\n * nx run architecture:validate-architecture-unchanged\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { writeTemplate } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { compareGraphs } from '../../lib/graph-comparator';\nimport { loadBlessedGraph, graphFileExists } from '../../lib/graph-loader';\nimport { collectProjectInfo, enrichGraph, MetadataValidationError } from '../../lib/graph-metadata';\nimport { scanAndAttachApiRelations } from '../../lib/api-usage/api-scanner';\nimport { RuleGate } from '../../lib/rule-gate';\nimport type { EnhancedGraph } from '../../lib/graph-sorter';\nimport { toError } from '../../toError';\n\nexport interface ValidateArchitectureUnchangedOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst TMP_MD_FILE = 'webpieces.dependencies.md';\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructionsFile(workspaceRoot: string): string {\n const mdPath = writeTemplate(workspaceRoot, TMP_MD_FILE);\n\n return mdPath;\n}\n\n/**\n * Report a current-vs-saved graph mismatch and write the AI instructions file.\n */\nfunction reportMismatch(summary: string, workspaceRoot: string): void {\n const mdPath = writeTmpInstructionsFile(workspaceRoot);\n\n console.error('āŒ Architecture has changed since last update!');\n console.error('\\nDifferences:');\n console.error(summary);\n console.error('');\n console.error('āš ļø *** Refer to ' + mdPath + ' for instructions on how to fix *** āš ļø');\n console.error('');\n console.error('To fix:');\n console.error(' 1. Review the changes above');\n console.error(' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change');\n console.error(' 3. Commit the updated architecture/dependencies.json');\n}\n\n/**\n * Build the current dependency graph exactly as the generator does: reduce the nx\n * graph, sort into levels, enrich with metadata, and attach the derived\n * apiRelations — so this validator compares like-for-like against the committed file.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch/writeTmpInstructionsFile in this file\nasync function buildCurrentGraph(workspaceRoot: string): Promise<EnhancedGraph> {\n console.log('šŸ“Š Generating current dependency graph...');\n const reducedGraph = await generateReducedGraph();\n console.log('šŸ”„ Computing topological layers...');\n const currentGraph = sortGraphTopologically(reducedGraph);\n console.log('šŸ·ļø Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(currentGraph, projectInfos, workspaceRoot);\n console.log('šŸ”Ž Scanning source for implements/uses API relations...');\n scanAndAttachApiRelations(workspaceRoot, currentGraph, projectInfos);\n return currentGraph;\n}\n\nexport default async function runExecutor(\n options: ValidateArchitectureUnchangedOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n // Epoch-gateable: this rule diffs against the blessed architecture/dependencies.json, so\n // \"grandfather the current drift until <epoch>\" is meaningful — hence honorEpoch = true.\n if (new RuleGate().isDisabled(workspaceRoot, 'validate-architecture-unchanged', true)) {\n return { success: true };\n }\n\n console.log('\\nšŸ” Validating Architecture Unchanged\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Check if saved graph exists\n if (!graphFileExists(workspaceRoot, graphPath)) {\n console.error('āŒ No saved graph found at architecture/dependencies.json');\n console.error('');\n console.error('To initialize:');\n console.error(' 1. Run: nx run architecture:generate');\n console.error(' 2. Run: nx run architecture:visualize');\n console.error(' 3. Manually inspect the generated graph to confirm it is the desired architecture');\n console.error(' 4. Commit architecture/dependencies.json');\n return { success: false };\n }\n\n // Steps 1-3: build + enrich + scan the current graph (same pipeline the\n // generator runs, so any drift is caught).\n const currentGraph = await buildCurrentGraph(workspaceRoot);\n\n // Step 4: Load saved graph\n console.log('šŸ“‚ Loading saved graph...');\n const savedGraph = loadBlessedGraph(workspaceRoot, graphPath);\n\n if (!savedGraph) {\n console.error('āŒ Could not load saved graph');\n return { success: false };\n }\n\n // Step 5: Compare graphs\n console.log('šŸ” Comparing current graph to saved graph...');\n const comparison = compareGraphs(currentGraph, savedGraph.projects);\n\n if (comparison.identical) {\n console.log('āœ… Architecture unchanged - current graph matches saved graph');\n return { success: true };\n }\n reportMismatch(comparison.summary, workspaceRoot);\n return { success: false };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Architecture validation failed:', error.message);\n if (error instanceof MetadataValidationError) {\n const mdPath = writeTemplate(workspaceRoot, 'webpieces.responsibilities.md');\n console.error('');\n console.error('āš ļø *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** āš ļø');\n }\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-architecture-unchanged/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Architecture Unchanged Executor\n *\n * Validates that the current architecture graph matches the saved blessed graph.\n * This ensures no unapproved architecture changes have been made.\n *\n * Usage:\n * nx run architecture:validate-architecture-unchanged\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { writeTemplate } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { compareGraphs } from '../../lib/graph-comparator';\nimport { loadBlessedGraph, graphFileExists } from '../../lib/graph-loader';\nimport { collectProjectInfo, enrichGraph, MetadataValidationError } from '../../lib/graph-metadata';\nimport { scanAndAttachApiRelations, buildApiContracts } from '../../lib/api-usage/api-scanner';\nimport type { ApiContracts } from '../../lib/api-usage/api-relations';\nimport { loadRuntimeConfig } from '../../lib/runtime-config';\nimport { RuleGate } from '../../lib/rule-gate';\nimport type { EnhancedGraph } from '../../lib/graph-sorter';\nimport { toError } from '../../toError';\n\nexport interface ValidateArchitectureUnchangedOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst TMP_MD_FILE = 'webpieces.dependencies.md';\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructionsFile(workspaceRoot: string): string {\n const mdPath = writeTemplate(workspaceRoot, TMP_MD_FILE);\n\n return mdPath;\n}\n\n/**\n * Report a current-vs-saved graph mismatch and write the AI instructions file.\n */\nfunction reportMismatch(summary: string, workspaceRoot: string): void {\n const mdPath = writeTmpInstructionsFile(workspaceRoot);\n\n console.error('āŒ Architecture has changed since last update!');\n console.error('\\nDifferences:');\n console.error(summary);\n console.error('');\n console.error('āš ļø *** Refer to ' + mdPath + ' for instructions on how to fix *** āš ļø');\n console.error('');\n console.error('To fix:');\n console.error(' 1. Review the changes above');\n console.error(' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change');\n console.error(' 3. Commit the updated architecture/dependencies.json');\n}\n\n/**\n * A human-readable summary of how the regenerated api contract table differs from the committed one,\n * or null when they match. Named per contract so the message points at the api that changed rather\n * than dumping two JSON blobs.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch in this file\nfunction describeContractDrift(current: ApiContracts, saved: ApiContracts): string | null {\n const names = [...new Set([...Object.keys(current), ...Object.keys(saved)])].sort();\n const changes: string[] = [];\n for (const name of names) {\n const a = current[name];\n const b = saved[name];\n if (a === undefined) changes.push(` - ${name}: in dependencies.json but no longer in source`);\n else if (b === undefined) changes.push(` + ${name}: in source but missing from dependencies.json`);\n else if (JSON.stringify(a) !== JSON.stringify(b)) changes.push(` ~ ${name}: endpoints/kinds/queues changed`);\n }\n if (changes.length === 0) return null;\n return `apiContracts drift (${changes.length} contract(s)):\\n${changes.join('\\n')}`;\n}\n\n/**\n * Build the current dependency graph exactly as the generator does: reduce the nx\n * graph, sort into levels, enrich with metadata, and attach the derived\n * apiRelations — so this validator compares like-for-like against the committed file.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch/writeTmpInstructionsFile in this file\nasync function buildCurrentGraph(workspaceRoot: string): Promise<CurrentArchitecture> {\n console.log('šŸ“Š Generating current dependency graph...');\n const reducedGraph = await generateReducedGraph();\n console.log('šŸ”„ Computing topological layers...');\n const currentGraph = sortGraphTopologically(reducedGraph);\n console.log('šŸ·ļø Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(currentGraph, projectInfos, workspaceRoot);\n console.log('šŸ”Ž Scanning source for implements/uses API relations...');\n // The SAME externalApiPaths the generator uses: scanning without them would drop every vendor\n // relation from the regenerated graph and report drift against a perfectly fresh file.\n const externalApiPaths = loadRuntimeConfig(workspaceRoot).externalApiPaths;\n const scan = scanAndAttachApiRelations(workspaceRoot, currentGraph, projectInfos, externalApiPaths);\n return new CurrentArchitecture(currentGraph, buildApiContracts(scan));\n}\n\n/** The regenerated graph plus its api contract table — both halves of what dependencies.json holds. */\nclass CurrentArchitecture {\n constructor(\n public readonly graph: EnhancedGraph,\n public readonly apiContracts: ApiContracts,\n ) {}\n}\n\n/** The bootstrap path: there is nothing to diff against yet, so say how to create it. */\n// webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch in this file\nfunction reportMissingGraph(): void {\n console.error('āŒ No saved graph found at architecture/dependencies.json');\n console.error('');\n console.error('To initialize:');\n console.error(' 1. Run: nx run architecture:generate');\n console.error(' 2. Run: nx run architecture:visualize');\n console.error(' 3. Manually inspect the generated graph to confirm it is the desired architecture');\n console.error(' 4. Commit architecture/dependencies.json');\n}\n\nexport default async function runExecutor(\n options: ValidateArchitectureUnchangedOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n // Epoch-gateable: this rule diffs against the blessed architecture/dependencies.json, so\n // \"grandfather the current drift until <epoch>\" is meaningful — hence honorEpoch = true.\n if (new RuleGate().isDisabled(workspaceRoot, 'validate-architecture-unchanged', true)) {\n return { success: true };\n }\n\n console.log('\\nšŸ” Validating Architecture Unchanged\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Check if saved graph exists\n if (!graphFileExists(workspaceRoot, graphPath)) {\n reportMissingGraph();\n return { success: false };\n }\n\n // Steps 1-3: build + enrich + scan the current graph (same pipeline the\n // generator runs, so any drift is caught).\n const currentGraph = await buildCurrentGraph(workspaceRoot);\n\n // Step 4: Load saved graph\n console.log('šŸ“‚ Loading saved graph...');\n const savedGraph = loadBlessedGraph(workspaceRoot, graphPath);\n\n if (!savedGraph) {\n console.error('āŒ Could not load saved graph');\n return { success: false };\n }\n\n // Step 5: Compare graphs\n console.log('šŸ” Comparing current graph to saved graph...');\n const comparison = compareGraphs(currentGraph.graph, savedGraph.projects);\n\n if (!comparison.identical) {\n reportMismatch(comparison.summary, workspaceRoot);\n return { success: false };\n }\n\n // Step 6: Compare the api contract table too. It is NOT part of the project graph, so a\n // changed @Endpoint kind or queue name would otherwise pass here AND pass\n // validate-runtime-architecture (which derives from this same stale file) — leaving a queue\n // or cron in the committed graph that no longer matches the source.\n const contractDrift = describeContractDrift(currentGraph.apiContracts, savedGraph.apiContracts);\n if (contractDrift !== null) {\n reportMismatch(contractDrift, workspaceRoot);\n return { success: false };\n }\n\n console.log('āœ… Architecture unchanged - current graph matches saved graph');\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Architecture validation failed:', error.message);\n if (error instanceof MetadataValidationError) {\n const mdPath = writeTemplate(workspaceRoot, 'webpieces.responsibilities.md');\n console.error('');\n console.error('āš ļø *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** āš ļø');\n }\n return { success: false };\n }\n}\n"]}
@@ -14,8 +14,9 @@ function normalizeContent(content) {
14
14
  }
15
15
  async function validateEslintSyncExecutor(options, context) {
16
16
  const workspaceRoot = context.root;
17
- // All-or-nothing (honorEpoch = false): the two files either match or they do not.
18
- if (new rule_gate_1.RuleGate().isDisabled(workspaceRoot, 'validate-eslint-sync', false)) {
17
+ // Time-boxable (honorEpoch = true): a schedule is not a baseline — "do not enforce until <epoch>
18
+ // / off <branch>" is coherent even though the two files simply match or not. See rule-configs.ts.
19
+ if (new rule_gate_1.RuleGate().isDisabled(workspaceRoot, 'validate-eslint-sync', true)) {
19
20
  return { success: true };
20
21
  }
21
22
  const templatePath = (0, path_1.join)(workspaceRoot, 'packages/tooling/nx-webpieces-rules/templates/eslint.webpieces.config.mjs');
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-eslint-sync/executor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExecutorContext } from '@nx/devkit';\nimport { readFileSync } from 'fs';\nimport { join } from 'path';\nimport { createHash } from 'crypto';\nimport { RuleGate } from '../../lib/rule-gate';\n\nexport interface ValidateEslintSyncOptions {}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nfunction calculateHash(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nfunction normalizeContent(content: string): string {\n // Normalize line endings and trim whitespace\n return content.replace(/\\r\\n/g, '\\n').trim();\n}\n\nexport default async function validateEslintSyncExecutor(\n options: ValidateEslintSyncOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n // All-or-nothing (honorEpoch = false): the two files either match or they do not.\n if (new RuleGate().isDisabled(workspaceRoot, 'validate-eslint-sync', false)) {\n return { success: true };\n }\n\n const templatePath = join(workspaceRoot, 'packages/tooling/nx-webpieces-rules/templates/eslint.webpieces.config.mjs');\n const workspacePath = join(workspaceRoot, 'eslint.webpieces.config.mjs');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const templateContent = readFileSync(templatePath, 'utf-8');\n const workspaceContent = readFileSync(workspacePath, 'utf-8');\n\n const templateRules = extractRulesSection(templateContent);\n const workspaceRules = extractRulesSection(workspaceContent);\n\n const templateHash = calculateHash(normalizeContent(templateRules));\n const workspaceHash = calculateHash(normalizeContent(workspaceRules));\n\n if (templateHash !== workspaceHash) {\n printValidationError(templatePath, workspacePath);\n return { success: false };\n }\n\n console.log('āœ… ESLint configuration sync validated - rules match!');\n return { success: true };\n\n } catch (err: unknown) {\n // Error occurred during validation - log and fail\n // eslint-disable-next-line @webpieces/catch-error-pattern\n console.error('āŒ Error validating ESLint sync:', err);\n return { success: false };\n }\n}\n\nfunction printValidationError(templatePath: string, workspacePath: string): void {\n console.error('');\n console.error('āŒ ESLint configuration sync validation FAILED');\n console.error('');\n console.error('The @webpieces ESLint rules must be identical in both files:');\n console.error(` 1. ${templatePath}`);\n console.error(` 2. ${workspacePath}`);\n console.error('');\n console.error('These files must have identical rules sections so that:');\n console.error(' - External clients get the same rules we use internally');\n console.error(' - We \"eat our own dog food\" - same rules for everyone');\n console.error('');\n console.error('To fix:');\n console.error(' 1. Modify the rules in ONE file (recommend: templates/eslint.webpieces.config.mjs)');\n console.error(' 2. Copy the rules section to the other file');\n console.error(' 3. Keep import statements different (template uses npm, workspace uses loadWorkspaceRules)');\n console.error('');\n console.error('Customization for webpieces workspace goes in: eslint.config.mjs');\n console.error('');\n}\n\nfunction extractRulesSection(content: string): string {\n // Extract everything between \"export default [\" and the final \"];\"\n // This includes the rules configuration\n const match = content.match(/export default \\[([\\s\\S]*)\\];/);\n if (!match) {\n throw new Error('Could not extract rules section - export default not found');\n }\n\n return match[1].trim();\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-eslint-sync/executor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExecutorContext } from '@nx/devkit';\nimport { readFileSync } from 'fs';\nimport { join } from 'path';\nimport { createHash } from 'crypto';\nimport { RuleGate } from '../../lib/rule-gate';\n\nexport interface ValidateEslintSyncOptions {}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nfunction calculateHash(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nfunction normalizeContent(content: string): string {\n // Normalize line endings and trim whitespace\n return content.replace(/\\r\\n/g, '\\n').trim();\n}\n\nexport default async function validateEslintSyncExecutor(\n options: ValidateEslintSyncOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n // Time-boxable (honorEpoch = true): a schedule is not a baseline — \"do not enforce until <epoch>\n // / off <branch>\" is coherent even though the two files simply match or not. See rule-configs.ts.\n if (new RuleGate().isDisabled(workspaceRoot, 'validate-eslint-sync', true)) {\n return { success: true };\n }\n\n const templatePath = join(workspaceRoot, 'packages/tooling/nx-webpieces-rules/templates/eslint.webpieces.config.mjs');\n const workspacePath = join(workspaceRoot, 'eslint.webpieces.config.mjs');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const templateContent = readFileSync(templatePath, 'utf-8');\n const workspaceContent = readFileSync(workspacePath, 'utf-8');\n\n const templateRules = extractRulesSection(templateContent);\n const workspaceRules = extractRulesSection(workspaceContent);\n\n const templateHash = calculateHash(normalizeContent(templateRules));\n const workspaceHash = calculateHash(normalizeContent(workspaceRules));\n\n if (templateHash !== workspaceHash) {\n printValidationError(templatePath, workspacePath);\n return { success: false };\n }\n\n console.log('āœ… ESLint configuration sync validated - rules match!');\n return { success: true };\n\n } catch (err: unknown) {\n // Error occurred during validation - log and fail\n // eslint-disable-next-line @webpieces/catch-error-pattern\n console.error('āŒ Error validating ESLint sync:', err);\n return { success: false };\n }\n}\n\nfunction printValidationError(templatePath: string, workspacePath: string): void {\n console.error('');\n console.error('āŒ ESLint configuration sync validation FAILED');\n console.error('');\n console.error('The @webpieces ESLint rules must be identical in both files:');\n console.error(` 1. ${templatePath}`);\n console.error(` 2. ${workspacePath}`);\n console.error('');\n console.error('These files must have identical rules sections so that:');\n console.error(' - External clients get the same rules we use internally');\n console.error(' - We \"eat our own dog food\" - same rules for everyone');\n console.error('');\n console.error('To fix:');\n console.error(' 1. Modify the rules in ONE file (recommend: templates/eslint.webpieces.config.mjs)');\n console.error(' 2. Copy the rules section to the other file');\n console.error(' 3. Keep import statements different (template uses npm, workspace uses loadWorkspaceRules)');\n console.error('');\n console.error('Customization for webpieces workspace goes in: eslint.config.mjs');\n console.error('');\n}\n\nfunction extractRulesSection(content: string): string {\n // Extract everything between \"export default [\" and the final \"];\"\n // This includes the rules configuration\n const match = content.match(/export default \\[([\\s\\S]*)\\];/);\n if (!match) {\n throw new Error('Could not extract rules section - export default not found');\n }\n\n return match[1].trim();\n}\n"]}
@@ -57,8 +57,10 @@ class ValidationReporter {
57
57
  }
58
58
  async function runExecutor(options, context) {
59
59
  const workspaceRoot = context.root;
60
- // All-or-nothing (honorEpoch = false): there is no blessed baseline to grandfather against.
61
- if (new rule_gate_1.RuleGate().isDisabled(workspaceRoot, 'validate-packagejson', false)) {
60
+ // Time-boxable (honorEpoch = true): a schedule is not a baseline — "do not enforce until <epoch>
61
+ // / off <branch>" is coherent even with nothing to grandfather (e.g. hold this off until an
62
+ // upgrade PR that fixes the flagged package.json lands). See rule-configs.ts (the five-validator block).
63
+ if (new rule_gate_1.RuleGate().isDisabled(workspaceRoot, 'validate-packagejson', true)) {
62
64
  return { success: true };
63
65
  }
64
66
  console.log('\nšŸ“¦ Validating Package.json Dependencies\n');
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-packagejson/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Package.json Executor\n *\n * Validates that package.json dependencies match project.json build dependencies.\n * This ensures the two sources of truth don't drift apart.\n *\n * Usage:\n * nx run architecture:validate-packagejson\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport {\n PackageValidatorOptions,\n ProjectValidationResult,\n TestOnlyDepMode,\n validatePackageJsonDependencies,\n} from '../../lib/package-validator';\nimport { RuleGate } from '../../lib/rule-gate';\nimport { toError } from '../../toError';\n\nexport interface ValidatePackageJsonOptions {\n /**\n * Strictness for \"a test-only package is listed in dependencies\" (i.e. it lands in\n * the `pnpm deploy --prod` closure and ships to production).\n * 'error' (default) | 'warn' (migration) | 'off'.\n */\n testOnlyDepMode?: TestOnlyDepMode;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/**\n * Console reporting for the validation result.\n *\n * Extracted from `runExecutor` purely to keep that method under the max-method-lines\n * limit: it crossed 70 lines when the config gate (this branch) and the test-vs-prod\n * dependency classification (main, #481) both landed in it.\n */\nclass ValidationReporter {\n reportWarnings(warnings: string[]): void {\n // Warnings never fail the build (e.g. runtime-only / peer deps).\n if (warnings.length === 0) {\n return;\n }\n console.warn('\\nāš ļø Package.json notices (non-fatal):');\n for (const warning of warnings) {\n console.warn(` ${warning}`);\n }\n }\n\n reportErrors(errors: string[]): void {\n console.error('\\nāŒ Package.json validation failed!');\n console.error('\\nErrors:');\n for (const error of errors) {\n console.error(` ${error}`);\n }\n console.error('\\nTo fix:');\n console.error(' 1. Review each error above — it names the SECTION the dependency belongs in');\n console.error(' 2. Imported by production source → package.json \"dependencies\"');\n console.error(' 3. Imported only by *.spec.ts / __tests__ / test configs → \"devDependencies\"');\n console.error(' (devDependencies are excluded from `pnpm deploy --prod`, so test-only');\n console.error(' packages never reach the production image)');\n }\n\n reportSummary(projectResults: ProjectValidationResult[]): void {\n const validProjects = projectResults.filter(r => r.valid).length;\n const totalProjects = projectResults.length;\n console.log(`\\nšŸ“ˆ Validation Summary:`);\n console.log(` Projects validated: ${totalProjects}`);\n console.log(` Valid: ${validProjects}`);\n console.log(` Invalid: ${totalProjects - validProjects}`);\n }\n}\n\nexport default async function runExecutor(\n options: ValidatePackageJsonOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n // All-or-nothing (honorEpoch = false): there is no blessed baseline to grandfather against.\n if (new RuleGate().isDisabled(workspaceRoot, 'validate-packagejson', false)) {\n return { success: true };\n }\n\n console.log('\\nšŸ“¦ Validating Package.json Dependencies\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Build the full graph from nx, then transitively reduce it (the view)\n console.log(\"šŸ“Š Generating dependency graph from nx's project graph...\");\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to get enhanced graph with levels)\n console.log('šŸ”„ Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Validate package.json dependencies match\n console.log('šŸ“¦ Validating package.json dependencies match the architecture graph...');\n const validatorOptions = new PackageValidatorOptions(options.testOnlyDepMode ?? 'error');\n const packageValidation = await validatePackageJsonDependencies(\n enhancedGraph,\n workspaceRoot,\n validatorOptions\n );\n\n const reporter = new ValidationReporter();\n reporter.reportWarnings(packageValidation.warnings);\n\n if (!packageValidation.valid) {\n reporter.reportErrors(packageValidation.errors);\n return { success: false };\n }\n\n console.log('āœ… Package.json dependencies cover the architecture graph');\n reporter.reportSummary(packageValidation.projectResults);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Package.json validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-packagejson/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Package.json Executor\n *\n * Validates that package.json dependencies match project.json build dependencies.\n * This ensures the two sources of truth don't drift apart.\n *\n * Usage:\n * nx run architecture:validate-packagejson\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport {\n PackageValidatorOptions,\n ProjectValidationResult,\n TestOnlyDepMode,\n validatePackageJsonDependencies,\n} from '../../lib/package-validator';\nimport { RuleGate } from '../../lib/rule-gate';\nimport { toError } from '../../toError';\n\nexport interface ValidatePackageJsonOptions {\n /**\n * Strictness for \"a test-only package is listed in dependencies\" (i.e. it lands in\n * the `pnpm deploy --prod` closure and ships to production).\n * 'error' (default) | 'warn' (migration) | 'off'.\n */\n testOnlyDepMode?: TestOnlyDepMode;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/**\n * Console reporting for the validation result.\n *\n * Extracted from `runExecutor` purely to keep that method under the max-method-lines\n * limit: it crossed 70 lines when the config gate (this branch) and the test-vs-prod\n * dependency classification (main, #481) both landed in it.\n */\nclass ValidationReporter {\n reportWarnings(warnings: string[]): void {\n // Warnings never fail the build (e.g. runtime-only / peer deps).\n if (warnings.length === 0) {\n return;\n }\n console.warn('\\nāš ļø Package.json notices (non-fatal):');\n for (const warning of warnings) {\n console.warn(` ${warning}`);\n }\n }\n\n reportErrors(errors: string[]): void {\n console.error('\\nāŒ Package.json validation failed!');\n console.error('\\nErrors:');\n for (const error of errors) {\n console.error(` ${error}`);\n }\n console.error('\\nTo fix:');\n console.error(' 1. Review each error above — it names the SECTION the dependency belongs in');\n console.error(' 2. Imported by production source → package.json \"dependencies\"');\n console.error(' 3. Imported only by *.spec.ts / __tests__ / test configs → \"devDependencies\"');\n console.error(' (devDependencies are excluded from `pnpm deploy --prod`, so test-only');\n console.error(' packages never reach the production image)');\n }\n\n reportSummary(projectResults: ProjectValidationResult[]): void {\n const validProjects = projectResults.filter(r => r.valid).length;\n const totalProjects = projectResults.length;\n console.log(`\\nšŸ“ˆ Validation Summary:`);\n console.log(` Projects validated: ${totalProjects}`);\n console.log(` Valid: ${validProjects}`);\n console.log(` Invalid: ${totalProjects - validProjects}`);\n }\n}\n\nexport default async function runExecutor(\n options: ValidatePackageJsonOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n // Time-boxable (honorEpoch = true): a schedule is not a baseline — \"do not enforce until <epoch>\n // / off <branch>\" is coherent even with nothing to grandfather (e.g. hold this off until an\n // upgrade PR that fixes the flagged package.json lands). See rule-configs.ts (the five-validator block).\n if (new RuleGate().isDisabled(workspaceRoot, 'validate-packagejson', true)) {\n return { success: true };\n }\n\n console.log('\\nšŸ“¦ Validating Package.json Dependencies\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Build the full graph from nx, then transitively reduce it (the view)\n console.log(\"šŸ“Š Generating dependency graph from nx's project graph...\");\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to get enhanced graph with levels)\n console.log('šŸ”„ Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Validate package.json dependencies match\n console.log('šŸ“¦ Validating package.json dependencies match the architecture graph...');\n const validatorOptions = new PackageValidatorOptions(options.testOnlyDepMode ?? 'error');\n const packageValidation = await validatePackageJsonDependencies(\n enhancedGraph,\n workspaceRoot,\n validatorOptions\n );\n\n const reporter = new ValidationReporter();\n reporter.reportWarnings(packageValidation.warnings);\n\n if (!packageValidation.valid) {\n reporter.reportErrors(packageValidation.errors);\n return { success: false };\n }\n\n console.log('āœ… Package.json dependencies cover the architecture graph');\n reporter.reportSummary(packageValidation.projectResults);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Package.json validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
@@ -79,7 +79,9 @@ async function runExecutor(_options, context) {
79
79
  if (depsFile.projects[name].drawOnGraph === false)
80
80
  hiddenProjects.add(name);
81
81
  }
82
- const report = (0, runtime_graph_1.deriveRuntimeGraphReport)(depsFile.projects, hiddenProjects);
82
+ // apiContracts comes from the SAME loaded file, so the queue/trigger data the derivation sees
83
+ // here is byte-identical to what generate saw in memory.
84
+ const report = (0, runtime_graph_1.deriveRuntimeGraphReport)(depsFile.projects, hiddenProjects, depsFile.apiContracts);
83
85
  const graph = report.graph;
84
86
  for (const warning of report.warnings)
85
87
  console.warn(`āš ļø ${warning}`);
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-runtime-architecture/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Runtime Architecture Executor (workspace)\n *\n * Two workspace-level checks on the runtime microservice graph:\n * 1. No-cycles: every runtime cycle must be in the `allowedCycles` allowlist\n * with an unexpired `until`; any other cycle fails the build.\n * 2. Unchanged: the freshly-assembled graph must match the committed\n * architecture/runtime-dependencies.json (run `architecture:generate`).\n *\n * On/off + a whole-rule grace window come from webpieces.config.json\n * (rule: runtime-architecture).\n *\n * Usage: nx run architecture:validate-runtime-architecture\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadBlessedGraph } from '../../lib/graph-loader';\nimport {\n deriveRuntimeGraphReport,\n loadRuntimeGraph,\n runtimeAdjacency,\n runtimeGraphFileExists,\n serializeRuntimeGraph,\n} from '../../lib/runtime-graph';\nimport type { RuntimeGraph } from '../../lib/runtime-graph';\nimport { findRuntimeCycles, cycleKey } from '../../lib/runtime-cycles';\nimport type { AllowedCycle } from '../../lib/runtime-config';\nimport { loadRuntimeConfig, runtimeReportOnly, RUNTIME_RULE_NAME } from '../../lib/runtime-config';\n\nexport interface ValidateRuntimeArchitectureOptions {\n // Config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/** Allowlist keys (sorted-services -> entry) that are still in their grace window. */\nfunction activeAllowedKeys(allowed: AllowedCycle[]): Map<string, AllowedCycle> {\n const nowSeconds = Date.now() / 1000;\n const map = new Map<string, AllowedCycle>();\n for (const entry of allowed) {\n const active = entry.until === undefined || nowSeconds < entry.until;\n if (active) map.set(cycleKey(entry.services), entry);\n }\n return map;\n}\n\n/** Returns disallowed-cycle messages (empty = all cycles allowed or none). */\nfunction checkCycles(graph: RuntimeGraph, allowed: AllowedCycle[]): string[] {\n const cycles = findRuntimeCycles(runtimeAdjacency(graph));\n if (cycles.length === 0) return [];\n\n const activeAllow = activeAllowedKeys(allowed);\n const problems: string[] = [];\n for (const cycle of cycles) {\n const entry = activeAllow.get(cycle.key);\n if (entry) {\n console.log(`ā³ Allowed runtime cycle [${cycle.services.join(' <-> ')}] — ${entry.reason ?? 'no reason given'}`);\n continue;\n }\n problems.push(`runtime cycle: ${cycle.services.join(' -> ')} -> ${cycle.services[0]}`);\n }\n return problems;\n}\n\n/** Returns an unchanged-check message, or null if the committed graph matches. */\nfunction checkUnchanged(workspaceRoot: string, current: RuntimeGraph): string | null {\n if (!runtimeGraphFileExists(workspaceRoot)) {\n return 'No committed architecture/runtime-dependencies.json — run: nx run architecture:generate';\n }\n const saved = loadRuntimeGraph(workspaceRoot);\n if (saved && serializeRuntimeGraph(saved) === serializeRuntimeGraph(current)) return null;\n return 'Runtime graph changed since last commit — run: nx run architecture:generate and commit the result';\n}\n\nexport default async function runExecutor(\n _options: ValidateRuntimeArchitectureOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const config = loadRuntimeConfig(workspaceRoot);\n\n if (config.off) {\n console.log(`\\nā­ļø Skipping ${RUNTIME_RULE_NAME} (mode: OFF)\\n`);\n return { success: true };\n }\n\n console.log('\\nšŸ“” Validating runtime microservice architecture\\n');\n // Derive the \"current\" runtime graph from the committed dependencies.json — the SAME source\n // architecture:generate derives from — so the unchanged-check compares like-for-like and can\n // never fail on a clean, freshly-generated tree. (validate-architecture-unchanged separately\n // guarantees dependencies.json itself is fresh.)\n const depsFile = loadBlessedGraph(workspaceRoot);\n if (depsFile === null) {\n console.error('āŒ No architecture/dependencies.json — run: nx run architecture:generate');\n return { success: false };\n }\n const hiddenProjects = new Set<string>();\n for (const name of Object.keys(depsFile.projects)) {\n if (depsFile.projects[name].drawOnGraph === false) hiddenProjects.add(name);\n }\n const report = deriveRuntimeGraphReport(depsFile.projects, hiddenProjects);\n const graph = report.graph;\n for (const warning of report.warnings) console.warn(`āš ļø ${warning}`);\n\n // A call site naming a service no module answers to FAILS: the contract is served in-repo, so\n // the name is a typo or a stale rename, and the graph only ever hid it by fanning the edge out.\n const problems: string[] = [...report.problems, ...checkCycles(graph, config.allowedCycles)];\n const unchanged = checkUnchanged(workspaceRoot, graph);\n if (unchanged) problems.push(unchanged);\n\n for (const u of graph.unresolvedUses) {\n console.log(`āš ļø ${u.service} uses \"${u.api}\" but no in-repo service implements it (external?)`);\n }\n\n if (problems.length === 0) {\n console.log('āœ… Runtime architecture valid (no disallowed cycles, graph unchanged)\\n');\n return { success: true };\n }\n\n console.error('\\nāŒ Runtime architecture validation failed:\\n');\n for (const p of problems) console.error(` - ${p}`);\n console.error('\\nAllow a cycle temporarily via runtime-architecture.allowedCycles (services + reason + until) in webpieces.config.json.\\n');\n\n const reportOnly = runtimeReportOnly(config);\n if (reportOnly.skip) {\n console.log(`ā³ Reported but not failing (${reportOnly.reason}).\\n`);\n return { success: true };\n }\n return { success: false };\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-runtime-architecture/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Runtime Architecture Executor (workspace)\n *\n * Two workspace-level checks on the runtime microservice graph:\n * 1. No-cycles: every runtime cycle must be in the `allowedCycles` allowlist\n * with an unexpired `until`; any other cycle fails the build.\n * 2. Unchanged: the freshly-assembled graph must match the committed\n * architecture/runtime-dependencies.json (run `architecture:generate`).\n *\n * On/off + a whole-rule grace window come from webpieces.config.json\n * (rule: runtime-architecture).\n *\n * Usage: nx run architecture:validate-runtime-architecture\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadBlessedGraph } from '../../lib/graph-loader';\nimport {\n deriveRuntimeGraphReport,\n loadRuntimeGraph,\n runtimeAdjacency,\n runtimeGraphFileExists,\n serializeRuntimeGraph,\n} from '../../lib/runtime-graph';\nimport type { RuntimeGraph } from '../../lib/runtime-graph';\nimport { findRuntimeCycles, cycleKey } from '../../lib/runtime-cycles';\nimport type { AllowedCycle } from '../../lib/runtime-config';\nimport { loadRuntimeConfig, runtimeReportOnly, RUNTIME_RULE_NAME } from '../../lib/runtime-config';\n\nexport interface ValidateRuntimeArchitectureOptions {\n // Config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/** Allowlist keys (sorted-services -> entry) that are still in their grace window. */\nfunction activeAllowedKeys(allowed: AllowedCycle[]): Map<string, AllowedCycle> {\n const nowSeconds = Date.now() / 1000;\n const map = new Map<string, AllowedCycle>();\n for (const entry of allowed) {\n const active = entry.until === undefined || nowSeconds < entry.until;\n if (active) map.set(cycleKey(entry.services), entry);\n }\n return map;\n}\n\n/** Returns disallowed-cycle messages (empty = all cycles allowed or none). */\nfunction checkCycles(graph: RuntimeGraph, allowed: AllowedCycle[]): string[] {\n const cycles = findRuntimeCycles(runtimeAdjacency(graph));\n if (cycles.length === 0) return [];\n\n const activeAllow = activeAllowedKeys(allowed);\n const problems: string[] = [];\n for (const cycle of cycles) {\n const entry = activeAllow.get(cycle.key);\n if (entry) {\n console.log(`ā³ Allowed runtime cycle [${cycle.services.join(' <-> ')}] — ${entry.reason ?? 'no reason given'}`);\n continue;\n }\n problems.push(`runtime cycle: ${cycle.services.join(' -> ')} -> ${cycle.services[0]}`);\n }\n return problems;\n}\n\n/** Returns an unchanged-check message, or null if the committed graph matches. */\nfunction checkUnchanged(workspaceRoot: string, current: RuntimeGraph): string | null {\n if (!runtimeGraphFileExists(workspaceRoot)) {\n return 'No committed architecture/runtime-dependencies.json — run: nx run architecture:generate';\n }\n const saved = loadRuntimeGraph(workspaceRoot);\n if (saved && serializeRuntimeGraph(saved) === serializeRuntimeGraph(current)) return null;\n return 'Runtime graph changed since last commit — run: nx run architecture:generate and commit the result';\n}\n\nexport default async function runExecutor(\n _options: ValidateRuntimeArchitectureOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const config = loadRuntimeConfig(workspaceRoot);\n\n if (config.off) {\n console.log(`\\nā­ļø Skipping ${RUNTIME_RULE_NAME} (mode: OFF)\\n`);\n return { success: true };\n }\n\n console.log('\\nšŸ“” Validating runtime microservice architecture\\n');\n // Derive the \"current\" runtime graph from the committed dependencies.json — the SAME source\n // architecture:generate derives from — so the unchanged-check compares like-for-like and can\n // never fail on a clean, freshly-generated tree. (validate-architecture-unchanged separately\n // guarantees dependencies.json itself is fresh.)\n const depsFile = loadBlessedGraph(workspaceRoot);\n if (depsFile === null) {\n console.error('āŒ No architecture/dependencies.json — run: nx run architecture:generate');\n return { success: false };\n }\n const hiddenProjects = new Set<string>();\n for (const name of Object.keys(depsFile.projects)) {\n if (depsFile.projects[name].drawOnGraph === false) hiddenProjects.add(name);\n }\n // apiContracts comes from the SAME loaded file, so the queue/trigger data the derivation sees\n // here is byte-identical to what generate saw in memory.\n const report = deriveRuntimeGraphReport(depsFile.projects, hiddenProjects, depsFile.apiContracts);\n const graph = report.graph;\n for (const warning of report.warnings) console.warn(`āš ļø ${warning}`);\n\n // A call site naming a service no module answers to FAILS: the contract is served in-repo, so\n // the name is a typo or a stale rename, and the graph only ever hid it by fanning the edge out.\n const problems: string[] = [...report.problems, ...checkCycles(graph, config.allowedCycles)];\n const unchanged = checkUnchanged(workspaceRoot, graph);\n if (unchanged) problems.push(unchanged);\n\n for (const u of graph.unresolvedUses) {\n console.log(`āš ļø ${u.service} uses \"${u.api}\" but no in-repo service implements it (external?)`);\n }\n\n if (problems.length === 0) {\n console.log('āœ… Runtime architecture valid (no disallowed cycles, graph unchanged)\\n');\n return { success: true };\n }\n\n console.error('\\nāŒ Runtime architecture validation failed:\\n');\n for (const p of problems) console.error(` - ${p}`);\n console.error('\\nAllow a cycle temporarily via runtime-architecture.allowedCycles (services + reason + until) in webpieces.config.json.\\n');\n\n const reportOnly = runtimeReportOnly(config);\n if (reportOnly.skip) {\n console.log(`ā³ Reported but not failing (${reportOnly.reason}).\\n`);\n return { success: true };\n }\n return { success: false };\n}\n"]}
@@ -288,8 +288,9 @@ function checkSemverRanges(workspaceRoot) {
288
288
  }
289
289
  async function runExecutor(_options, context) {
290
290
  const workspaceRoot = context.root;
291
- // All-or-nothing (honorEpoch = false): there is no blessed baseline to grandfather against.
292
- if (new rule_gate_1.RuleGate().isDisabled(workspaceRoot, 'validate-versions-locked', false)) {
291
+ // Time-boxable (honorEpoch = true): a schedule is not a baseline — "do not enforce until <epoch>
292
+ // / off <branch>" is coherent even with nothing to grandfather. See rule-configs.ts (five-validator block).
293
+ if (new rule_gate_1.RuleGate().isDisabled(workspaceRoot, 'validate-versions-locked', true)) {
293
294
  return { success: true };
294
295
  }
295
296
  console.log('\nšŸ”’ Validating Package Versions are LOCKED and CONSISTENT\n');