@webpieces/nx-webpieces-rules 0.4.486 → 0.4.488

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 (35) 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-runtime-architecture/executor.js +3 -1
  9. package/src/executors/validate-runtime-architecture/executor.js.map +1 -1
  10. package/src/lib/api-usage/api-ast.d.ts +71 -0
  11. package/src/lib/api-usage/api-ast.js +250 -0
  12. package/src/lib/api-usage/api-ast.js.map +1 -0
  13. package/src/lib/api-usage/api-relations.d.ts +72 -3
  14. package/src/lib/api-usage/api-relations.js.map +1 -1
  15. package/src/lib/api-usage/api-scanner.d.ts +43 -4
  16. package/src/lib/api-usage/api-scanner.js +121 -103
  17. package/src/lib/api-usage/api-scanner.js.map +1 -1
  18. package/src/lib/graph-loader.d.ts +21 -2
  19. package/src/lib/graph-loader.js +38 -4
  20. package/src/lib/graph-loader.js.map +1 -1
  21. package/src/lib/runtime-config.d.ts +7 -1
  22. package/src/lib/runtime-config.js +7 -1
  23. package/src/lib/runtime-config.js.map +1 -1
  24. package/src/lib/runtime-graph-io.d.ts +17 -0
  25. package/src/lib/runtime-graph-io.js +59 -0
  26. package/src/lib/runtime-graph-io.js.map +1 -0
  27. package/src/lib/runtime-graph-model.d.ts +121 -0
  28. package/src/lib/runtime-graph-model.js +14 -0
  29. package/src/lib/runtime-graph-model.js.map +1 -0
  30. package/src/lib/runtime-graph.d.ts +6 -75
  31. package/src/lib/runtime-graph.js +163 -51
  32. package/src/lib/runtime-graph.js.map +1 -1
  33. package/src/lib/runtime-visualizer.d.ts +5 -0
  34. package/src/lib/runtime-visualizer.js +63 -6
  35. 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.486",
3
+ "version": "0.4.488",
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.486",
25
- "@webpieces/code-rules": "0.4.486",
26
- "@webpieces/eslint-rules": "0.4.486",
27
- "@webpieces/pr-gate": "0.4.486",
28
- "@webpieces/rules-config": "0.4.486",
24
+ "@webpieces/ai-hook-rules": "0.4.488",
25
+ "@webpieces/code-rules": "0.4.488",
26
+ "@webpieces/eslint-rules": "0.4.488",
27
+ "@webpieces/pr-gate": "0.4.488",
28
+ "@webpieces/rules-config": "0.4.488",
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":"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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 { 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"]}
@@ -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"]}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * API contract AST accessors
3
+ *
4
+ * The pure, stateless half of the api scan: given a TypeScript node, what contract / endpoint /
5
+ * injected type does it describe? Split out of api-scanner.ts, which owns the STATEFUL walk (project
6
+ * programs, the source index, relation accumulation) and had grown past the file-size limit.
7
+ *
8
+ * Everything here is parser-level on purpose. Decorators must be read exactly as written, and a
9
+ * plain parse cannot be diverted to a decorator-erased `.d.ts` by module resolution — the bug
10
+ * api-scanner's source pre-pass exists to guard against.
11
+ */
12
+ import * as ts from 'typescript';
13
+ import { ApiClassInfo, ApiMethodMeta, ApiTransport } from './api-relations';
14
+ /** {api, owner: `project`, type} when `cls` is an `abstract class` carrying `@ApiPath`, else null. */
15
+ export declare function apiClassInfoFrom(cls: ts.ClassDeclaration, project: string): ApiClassInfo | null;
16
+ export declare function apiTransport(cls: ts.ClassDeclaration): ApiTransport;
17
+ /**
18
+ * Every `@Endpoint(path, kind)` method on a contract class, in declaration order.
19
+ *
20
+ * `kind` is a REQUIRED argument of the decorator, so a missing/non-literal second argument means the
21
+ * source does not compile (or is mid-edit) — we skip the method rather than defaulting it. Defaulting
22
+ * would put an undeclared cron or webhook into the graph as an ordinary rpc call, which is precisely
23
+ * the blindness the required argument exists to remove.
24
+ */
25
+ export declare function endpointMethodsOf(cls: ts.ClassDeclaration, api: string): ApiMethodMeta[];
26
+ /** The arguments of a decorator's call expression, or [] when it is a bare `@Foo` reference. */
27
+ export declare function decoratorArgs(decorator: ts.Decorator): ts.NodeArray<ts.Expression> | ts.Expression[];
28
+ /** The named decorator on a class member, or null. */
29
+ export declare function memberDecorator(member: ts.ClassElement, name: string): ts.Decorator | null;
30
+ /** The first argument of a class decorator when it is a string literal (`@ApiPath('/x')`), else null. */
31
+ export declare function decoratorStringArg(cls: ts.ClassDeclaration, name: string): string | null;
32
+ /** The constructor's parameters, or [] when the class declares no constructor. */
33
+ export declare function constructorParamsOf(cls: ts.ClassDeclaration): readonly ts.ParameterDeclaration[];
34
+ /**
35
+ * The bare name of a type reference (`GmailApi`, or `gmail.GmailApi` -> `GmailApi`), else null.
36
+ * Generic wrappers are deliberately NOT unwrapped: `Provider<GmailApi>` hands out the contract
37
+ * lazily, which is still a use, but it is not the shape any of these seams take today and guessing
38
+ * at type arguments would start matching things that merely mention a contract.
39
+ */
40
+ export declare function typeReferenceName(type: ts.TypeNode | undefined): string | null;
41
+ /** Every type name in the class's `implements` clause — the contracts this class IS, not ones it calls. */
42
+ export declare function implementedTypeNames(cls: ts.ClassDeclaration): Set<string>;
43
+ export declare function isAbstractClass(cls: ts.ClassDeclaration): boolean;
44
+ export declare function hasClassDecorator(cls: ts.ClassDeclaration, name: string): boolean;
45
+ /**
46
+ * The service a client-factory call aims at, from its config argument:
47
+ * `createRpcClient(WarmupApi, new ClientConfig('helper-fsdb'))` → `'helper-fsdb'`.
48
+ *
49
+ * Only a `new <Xxx>ClientConfig('<string literal>')` yields a name. A variable, a template string
50
+ * or a computed expression yields null — the target is genuinely unknown at scan time, and the
51
+ * runtime graph must fall back to fan-out (loudly) rather than guess.
52
+ */
53
+ export declare function targetServiceOf(call: ts.CallExpression): string | null;
54
+ export declare function calleeMethodName(call: ts.CallExpression): string | null;
55
+ export declare function isTestFile(fileName: string): boolean;
56
+ /** {api, owner, type:'rpc'|'pubsub'} for an in-repo contract class, else null. */
57
+ export declare function apiClassInfoFromNode(node: ts.Node, project: string): ApiClassInfo | null;
58
+ /**
59
+ * {api, owner, type:'external'} for a VENDOR contract, else null.
60
+ *
61
+ * A vendor contract cannot be detected the way an in-repo one is. It carries no @ApiPath (there is
62
+ * no route — the call leaves through a vendor SDK), and it is usually a plain `interface` bound to a
63
+ * Symbol token, which is not even a class. So inside a project the workspace has DECLARED external
64
+ * (`runtime-architecture.externalApiPaths`) the signal is structural instead: an exported
65
+ * `interface`/`abstract class` whose name ends in `Api`. That deliberately picks up `GmailApi` and
66
+ * `StorageApi` while leaving their DTOs, `*Config` types and `*Client` implementations alone.
67
+ */
68
+ export declare function externalApiInfoFrom(node: ts.Node, project: string): ApiClassInfo | null;
69
+ /** True when the declaration carries an `export` modifier. */
70
+ export declare function isExported(node: ts.InterfaceDeclaration | ts.ClassDeclaration): boolean;
71
+ export declare function collectTsFiles(dir: string): string[];