@webpieces/nx-webpieces-rules 0.4.700 → 0.4.702
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.
- package/package.json +6 -6
- package/src/executors/generate/executor.js +5 -1
- package/src/executors/generate/executor.js.map +1 -1
- package/src/executors/validate-runtime-architecture/executor.d.ts +10 -4
- package/src/executors/validate-runtime-architecture/executor.js +46 -35
- package/src/executors/validate-runtime-architecture/executor.js.map +1 -1
- package/src/lib/api-usage/api-ast.d.ts +1 -16
- package/src/lib/api-usage/api-ast.js +1 -42
- package/src/lib/api-usage/api-ast.js.map +1 -1
- package/src/lib/api-usage/api-relations.d.ts +1 -17
- package/src/lib/api-usage/api-relations.js +10 -2
- package/src/lib/api-usage/api-relations.js.map +1 -1
- package/src/lib/api-usage/api-scanner.d.ts +1 -1
- package/src/lib/api-usage/api-scanner.js +1 -10
- package/src/lib/api-usage/api-scanner.js.map +1 -1
- package/src/lib/cut-legacy-cycle-resolver.d.ts +47 -0
- package/src/lib/cut-legacy-cycle-resolver.js +68 -0
- package/src/lib/cut-legacy-cycle-resolver.js.map +1 -0
- package/src/lib/graph-loader.js +3 -0
- package/src/lib/graph-loader.js.map +1 -1
- package/src/lib/graph-metadata.js +10 -0
- package/src/lib/graph-metadata.js.map +1 -1
- package/src/lib/graph-sorter.d.ts +8 -1
- package/src/lib/graph-sorter.js.map +1 -1
- package/src/lib/runtime-config.d.ts +0 -7
- package/src/lib/runtime-config.js +0 -5
- package/src/lib/runtime-config.js.map +1 -1
- package/src/lib/runtime-cycles.d.ts +10 -12
- package/src/lib/runtime-cycles.js +11 -14
- package/src/lib/runtime-cycles.js.map +1 -1
- package/src/lib/runtime-graph-levels.d.ts +52 -6
- package/src/lib/runtime-graph-levels.js +118 -19
- package/src/lib/runtime-graph-levels.js.map +1 -1
- package/src/lib/runtime-graph-model.d.ts +8 -1
- package/src/lib/runtime-graph-model.js.map +1 -1
- package/src/lib/runtime-graph.d.ts +1 -1
- package/src/lib/runtime-graph.js +10 -41
- package/src/lib/runtime-graph.js.map +1 -1
- package/src/lib/runtime-visualizer.js +7 -3
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/runtime-viz-theme.d.ts +7 -0
- package/src/lib/runtime-viz-theme.js +8 -1
- package/src/lib/runtime-viz-theme.js.map +1 -1
- package/src/lib/service-name-resolver.d.ts +1 -1
- package/src/lib/service-name-resolver.js +1 -1
- package/src/lib/service-name-resolver.js.map +1 -1
- package/src/lib/runtime-host-nodes.d.ts +0 -10
- package/src/lib/runtime-host-nodes.js +0 -15
- package/src/lib/runtime-host-nodes.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.702",
|
|
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",
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@webpieces/ai-hook-rules": "0.4.
|
|
22
|
-
"@webpieces/code-rules": "0.4.
|
|
23
|
-
"@webpieces/eslint-rules": "0.4.
|
|
24
|
-
"@webpieces/pr-gate": "0.4.
|
|
25
|
-
"@webpieces/rules-config": "0.4.
|
|
21
|
+
"@webpieces/ai-hook-rules": "0.4.702",
|
|
22
|
+
"@webpieces/code-rules": "0.4.702",
|
|
23
|
+
"@webpieces/eslint-rules": "0.4.702",
|
|
24
|
+
"@webpieces/pr-gate": "0.4.702",
|
|
25
|
+
"@webpieces/rules-config": "0.4.702",
|
|
26
26
|
"madge": "8.0.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
@@ -175,7 +175,11 @@ async function runExecutor(options, context) {
|
|
|
175
175
|
}
|
|
176
176
|
catch (err) {
|
|
177
177
|
const error = (0, toError_1.toError)(err);
|
|
178
|
-
|
|
178
|
+
// A RuleFailError carries its cures as Option[]; `Error.message` is only the aiMessage, so
|
|
179
|
+
// printing that alone silently drops them. renderRuleFailForHuman is the ONE renderer that
|
|
180
|
+
// numbers them, and this catch is the top-level handler for the nx target.
|
|
181
|
+
const rendered = error instanceof rules_config_1.RuleFailError ? (0, rules_config_1.renderRuleFailForHuman)(error) : error.message;
|
|
182
|
+
console.error('❌ Graph generation failed:', rendered);
|
|
179
183
|
if (error instanceof graph_metadata_1.MetadataValidationError) {
|
|
180
184
|
const mdPath = (0, rules_config_1.writeTemplate)(workspaceRoot, 'webpieces.responsibilities.md');
|
|
181
185
|
console.error('');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/generate/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAyMH,8BAuBC;AA7ND,0DAAwD;AACxD,+DAAiE;AACjE,yDAAgE;AAChE,yDAA8D;AAC9D,yDAAmD;AACnD,6DAAoG;AAEpG,iEAMyC;AACzC,2EAA4E;AAE5E,6DAA6D;AAE7D,iEAA6D;AAC7D,2DAAqF;AACrF,yFAAgF;AAChF,2CAAwC;AAUxC;;;;;GAKG;AACH,2GAA2G;AAC3G,SAAS,oBAAoB,CACzB,aAAqB,EACrB,KAAoB,EACpB,cAA2B,EAC3B,YAA0B,EAC1B,eAAoC;IAEpC,OAAO,CAAC,GAAG,CAAC,iFAAiF,CAAC,CAAC;IAC/F,MAAM,MAAM,GAAG,IAAA,wCAAwB,EAAC,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC;IAC9F,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,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IAC3D,OAAO,CAAC,GAAG,CACP,0BAA0B,YAAY,cAAc,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,kBAAkB;QAClG,GAAG,UAAU,YAAY,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,0BAA0B,CACtF,CAAC;IACF,iGAAiG;IACjG,gGAAgG;IAChG,oBAAoB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAA,qDAAsB,EAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1C,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,CACrB,aAAqB,EACrB,KAAoB,EACpB,YAAsC;IAEtC,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;IACvE,MAAM,gBAAgB,GAAG,IAAA,kCAAiB,EAAC,aAAa,CAAC,CAAC,gBAAgB,CAAC;IAC3E,MAAM,IAAI,GAAG,IAAA,uCAAyB,EAAC,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;IAC7F,IAAI,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,IAAA,wCAA0B,EAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC1G,0FAA0F;IAC1F,+FAA+F;IAC/F,IAAI,IAAI,CAAC,uBAAuB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,IAAA,6CAA+B,EAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAChF,CAAC;IACD,MAAM,SAAS,GAAG,IAAA,+BAAiB,EAAC,IAAI,CAAC,CAAC;IAC1C,kGAAkG;IAClG,0FAA0F;IAC1F,MAAM,UAAU,GAAG,IAAA,6CAA+B,EAAC,SAAS,CAAC,CAAC;IAC9D,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC,MAAM,kDAAkD,CAAC,CAAC;QACxF,KAAK,MAAM,QAAQ,IAAI,UAAU;YAAE,OAAO,CAAC,KAAK,CAAC,UAAU,QAAQ,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,IAAI,aAAa,CAAC,SAAS,EAAE,IAAA,uCAAoB,EAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,oGAAoG;AACpG,MAAM,aAAa;IAEK;IACA;IAFpB,YACoB,YAA0B,EAC1B,eAAoC;QADpC,iBAAY,GAAZ,YAAY,CAAc;QAC1B,oBAAe,GAAf,eAAe,CAAqB;IACrD,CAAC;CACP;AAED,+FAA+F;AAC/F,2GAA2G;AAC3G,SAAS,gBAAgB,CAAC,KAAoB;IAC1C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,WAAW,KAAK,KAAK;YAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,2GAA2G;AAC3G,SAAS,iBAAiB,CAAC,KAAoB;IAC3C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACrF,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,cAAc,MAAM,CAAC,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACH,2GAA2G;AAC3G,KAAK,UAAU,kBAAkB,CAAC,aAAqB,EAAE,SAA6B;IAClF,gFAAgF;IAChF,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;IACzE,MAAM,YAAY,GAAG,MAAM,IAAA,sCAAoB,GAAE,CAAC;IAElD,2FAA2F;IAC3F,oFAAoF;IACpF,8FAA8F;IAC9F,gDAAgD;IAChD,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,IAAI,mCAAoB,EAAE,CAAC;IAC1C,MAAM,CAAC,aAAa,CAAC,YAAY,EAAE,sBAAsB,CAAC,CAAC;IAE3D,gEAAgE;IAChE,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAClD,MAAM,aAAa,GAAG,IAAA,qCAAsB,EAAC,YAAY,CAAC,CAAC;IAC3D,8FAA8F;IAC9F,6FAA6F;IAC7F,4CAA4C;IAC5C,MAAM,CAAC,mBAAmB,CAAC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,0BAA0B,CAAC,CAAC;IAErG,qEAAqE;IACrE,uEAAuE;IACvE,qEAAqE;IACrE,OAAO,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAC;IAClF,MAAM,YAAY,GAAG,MAAM,IAAA,mCAAkB,GAAE,CAAC;IAChD,IAAA,4BAAW,EAAC,aAAa,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;IAExD,wEAAwE;IACxE,0EAA0E;IAC1E,+BAA+B;IAC/B,MAAM,OAAO,GAAG,gBAAgB,CAAC,aAAa,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC;IAC7E,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAE1C,0FAA0F;IAC1F,8FAA8F;IAC9F,oFAAoF;IACpF,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;IACpE,IAAA,wBAAS,EAAC,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1F,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAE1C,wEAAwE;IACxE,qEAAqE;IACrE,MAAM,QAAQ,GAAG,IAAI,kCAAe,EAAE,CAAC,kBAAkB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IACxF,OAAO,CAAC,GAAG,CAAC,WAAW,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE5C,sEAAsE;IACtE,wEAAwE;IACxE,yEAAyE;IACzE,oBAAoB,CAChB,aAAa,EACb,aAAa,EACb,gBAAgB,CAAC,aAAa,CAAC,EAC/B,YAAY,EACZ,OAAO,CAAC,eAAe,CAC1B,CAAC;IAEF,iBAAiB,CAAC,aAAa,CAAC,CAAC;AACrC,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,MAAM,kBAAkB,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QACnD,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 { ProjectCycleDetector } from '../../lib/graph-cycles';\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 describeNonLiteralDecoratorArgs,\n buildApiContracts,\n describeMismatchedEndpointKinds,\n} from '../../lib/api-usage/api-scanner';\nimport { buildExternalSystems } from '../../lib/api-usage/external-systems';\nimport type { ApiContracts, ExternalSystemDecls } 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 { printAutoHiddenServers } from '../../lib/runtime-participant-resolver';\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 externalSystems: ExternalSystemDecls,\n): void {\n console.log('📡 Deriving runtime graph from dependencies.json (implements × uses per API)...');\n const report = deriveRuntimeGraphReport(graph, hiddenProjects, apiContracts, externalSystems);\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 printAutoHiddenServers(report.autoHidden);\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): ScannedTables {\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 // A decorator argument we could not read costs the graph a basePath, a method, or a whole\n // contract — none of which leaves a trace in the output. Name them before anything is written.\n if (scan.nonLiteralDecoratorArgs.length > 0) {\n console.warn(describeNonLiteralDecoratorArgs(scan.nonLiteralDecoratorArgs));\n }\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 new ScannedTables(contracts, buildExternalSystems(scan.apiIndex, projectInfos));\n}\n\n/** The two committed tables one scan produces, kept together so callers cannot persist just one. */\nclass ScannedTables {\n constructor(\n public readonly apiContracts: ApiContracts,\n public readonly externalSystems: ExternalSystemDecls,\n ) {}\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\n/**\n * The whole generation pipeline. Split out of runExecutor so that function stays a thin\n * try/catch-and-report shell — the error reporting is what a caller reads on failure, and it should\n * not be separated from the throw by fifty lines of steps.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file\nasync function generateEverything(workspaceRoot: string, graphPath: string | undefined): Promise<void> {\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 1b: The graph is a BUILD graph — refuse a cyclic one, naming EVERY cycle. This runs\n // before the sort deliberately: the sort also refuses, but reports one cycle and an\n // undifferentiated list of everything tangled with it, so a repo with several cycles pays one\n // full regeneration per cycle to discover them.\n console.log('🔄 Checking the project graph is acyclic...');\n const cycles = new ProjectCycleDetector();\n cycles.assertAcyclic(reducedGraph, 'the nx project graph');\n\n // Step 2: Topological sort (to assign levels for visualization)\n console.log('🔄 Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n // ...and assert the stratification it just produced actually holds: every dependency strictly\n // below its dependent. Safe to assert only because the graph was sorted a line ago — a stale\n // committed file is never checked this way.\n cycles.assertLevelsDescend(reducedGraph, cycles.levelsOf(enhancedGraph), 'the freshly sorted graph');\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 scanned = scanApiRelations(workspaceRoot, enhancedGraph, projectInfos);\n const apiContracts = scanned.apiContracts;\n\n // Step 4: Save the graph, INCLUDING the per-contract method table and the external-system\n // declarations the runtime derivation reads back — generate derives from the in-memory graph,\n // validate from the file, so anything not written here would make the two disagree.\n console.log('💾 Saving graph to architecture/dependencies.json...');\n saveGraph(enhancedGraph, workspaceRoot, graphPath, apiContracts, scanned.externalSystems);\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(\n workspaceRoot,\n enhancedGraph,\n hiddenProjectsIn(enhancedGraph),\n apiContracts,\n scanned.externalSystems,\n );\n\n printGraphSummary(enhancedGraph);\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 await generateEverything(workspaceRoot, graphPath);\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, RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { ProjectCycleDetector } from '../../lib/graph-cycles';\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 describeNonLiteralDecoratorArgs,\n buildApiContracts,\n describeMismatchedEndpointKinds,\n} from '../../lib/api-usage/api-scanner';\nimport { buildExternalSystems } from '../../lib/api-usage/external-systems';\nimport type { ApiContracts, ExternalSystemDecls } 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 { printAutoHiddenServers } from '../../lib/runtime-participant-resolver';\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 externalSystems: ExternalSystemDecls,\n): void {\n console.log('📡 Deriving runtime graph from dependencies.json (implements × uses per API)...');\n const report = deriveRuntimeGraphReport(graph, hiddenProjects, apiContracts, externalSystems);\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 printAutoHiddenServers(report.autoHidden);\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): ScannedTables {\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 // A decorator argument we could not read costs the graph a basePath, a method, or a whole\n // contract — none of which leaves a trace in the output. Name them before anything is written.\n if (scan.nonLiteralDecoratorArgs.length > 0) {\n console.warn(describeNonLiteralDecoratorArgs(scan.nonLiteralDecoratorArgs));\n }\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 new ScannedTables(contracts, buildExternalSystems(scan.apiIndex, projectInfos));\n}\n\n/** The two committed tables one scan produces, kept together so callers cannot persist just one. */\nclass ScannedTables {\n constructor(\n public readonly apiContracts: ApiContracts,\n public readonly externalSystems: ExternalSystemDecls,\n ) {}\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\n/**\n * The whole generation pipeline. Split out of runExecutor so that function stays a thin\n * try/catch-and-report shell — the error reporting is what a caller reads on failure, and it should\n * not be separated from the throw by fifty lines of steps.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, like the rest of this executor file\nasync function generateEverything(workspaceRoot: string, graphPath: string | undefined): Promise<void> {\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 1b: The graph is a BUILD graph — refuse a cyclic one, naming EVERY cycle. This runs\n // before the sort deliberately: the sort also refuses, but reports one cycle and an\n // undifferentiated list of everything tangled with it, so a repo with several cycles pays one\n // full regeneration per cycle to discover them.\n console.log('🔄 Checking the project graph is acyclic...');\n const cycles = new ProjectCycleDetector();\n cycles.assertAcyclic(reducedGraph, 'the nx project graph');\n\n // Step 2: Topological sort (to assign levels for visualization)\n console.log('🔄 Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n // ...and assert the stratification it just produced actually holds: every dependency strictly\n // below its dependent. Safe to assert only because the graph was sorted a line ago — a stale\n // committed file is never checked this way.\n cycles.assertLevelsDescend(reducedGraph, cycles.levelsOf(enhancedGraph), 'the freshly sorted graph');\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 scanned = scanApiRelations(workspaceRoot, enhancedGraph, projectInfos);\n const apiContracts = scanned.apiContracts;\n\n // Step 4: Save the graph, INCLUDING the per-contract method table and the external-system\n // declarations the runtime derivation reads back — generate derives from the in-memory graph,\n // validate from the file, so anything not written here would make the two disagree.\n console.log('💾 Saving graph to architecture/dependencies.json...');\n saveGraph(enhancedGraph, workspaceRoot, graphPath, apiContracts, scanned.externalSystems);\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(\n workspaceRoot,\n enhancedGraph,\n hiddenProjectsIn(enhancedGraph),\n apiContracts,\n scanned.externalSystems,\n );\n\n printGraphSummary(enhancedGraph);\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 await generateEverything(workspaceRoot, graphPath);\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n // A RuleFailError carries its cures as Option[]; `Error.message` is only the aiMessage, so\n // printing that alone silently drops them. renderRuleFailForHuman is the ONE renderer that\n // numbers them, and this catch is the top-level handler for the nx target.\n const rendered = error instanceof RuleFailError ? renderRuleFailForHuman(error) : error.message;\n console.error('❌ Graph generation failed:', rendered);\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,11 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validate Runtime Architecture Executor (workspace)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 1.
|
|
6
|
-
* with an unexpired `until`; any other cycle fails the build.
|
|
7
|
-
* 2. Unchanged: the freshly-assembled graph must match the committed
|
|
4
|
+
* Workspace-level checks on the runtime microservice graph:
|
|
5
|
+
* 1. Unchanged: the freshly-assembled graph must match the committed
|
|
8
6
|
* architecture/runtime-dependencies.json (run `architecture:generate`).
|
|
7
|
+
* 2. Every role:server project has a node on the graph.
|
|
8
|
+
*
|
|
9
|
+
* CYCLES ARE NOT CHECKED HERE, and there is deliberately no allowlist for them any more. A runtime
|
|
10
|
+
* cycle now fails inside the DERIVATION — `assignLevels` (lib/runtime-graph-levels.ts) throws a
|
|
11
|
+
* RuleFailError naming every chain, because CD deploys in dependency order and a cyclic architecture
|
|
12
|
+
* has no such order. `deriveRuntimeGraphReport` below therefore cannot return a cyclic graph, and the
|
|
13
|
+
* only way past it is to break the cycle, or to declare ONE edge with a `cutLegacyCycle:<target>` nx
|
|
14
|
+
* tag on the CALLING project.
|
|
9
15
|
*
|
|
10
16
|
* On/off + a whole-rule grace window come from webpieces.config.json
|
|
11
17
|
* (rule: runtime-architecture).
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Validate Runtime Architecture Executor (workspace)
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* 1.
|
|
7
|
-
* with an unexpired `until`; any other cycle fails the build.
|
|
8
|
-
* 2. Unchanged: the freshly-assembled graph must match the committed
|
|
5
|
+
* Workspace-level checks on the runtime microservice graph:
|
|
6
|
+
* 1. Unchanged: the freshly-assembled graph must match the committed
|
|
9
7
|
* architecture/runtime-dependencies.json (run `architecture:generate`).
|
|
8
|
+
* 2. Every role:server project has a node on the graph.
|
|
9
|
+
*
|
|
10
|
+
* CYCLES ARE NOT CHECKED HERE, and there is deliberately no allowlist for them any more. A runtime
|
|
11
|
+
* cycle now fails inside the DERIVATION — `assignLevels` (lib/runtime-graph-levels.ts) throws a
|
|
12
|
+
* RuleFailError naming every chain, because CD deploys in dependency order and a cyclic architecture
|
|
13
|
+
* has no such order. `deriveRuntimeGraphReport` below therefore cannot return a cyclic graph, and the
|
|
14
|
+
* only way past it is to break the cycle, or to declare ONE edge with a `cutLegacyCycle:<target>` nx
|
|
15
|
+
* tag on the CALLING project.
|
|
10
16
|
*
|
|
11
17
|
* On/off + a whole-rule grace window come from webpieces.config.json
|
|
12
18
|
* (rule: runtime-architecture).
|
|
@@ -16,38 +22,38 @@
|
|
|
16
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
23
|
exports.checkServersPresent = checkServersPresent;
|
|
18
24
|
exports.default = runExecutor;
|
|
25
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
19
26
|
const graph_loader_1 = require("../../lib/graph-loader");
|
|
27
|
+
const toError_1 = require("../../toError");
|
|
20
28
|
const runtime_graph_1 = require("../../lib/runtime-graph");
|
|
21
|
-
const runtime_cycles_1 = require("../../lib/runtime-cycles");
|
|
22
29
|
const runtime_participant_resolver_1 = require("../../lib/runtime-participant-resolver");
|
|
23
30
|
const runtime_config_1 = require("../../lib/runtime-config");
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The derived report, or the RENDERED failure when the derivation refused to produce one.
|
|
33
|
+
*
|
|
34
|
+
* `assignLevels` throws a `RuleFailError` on a cyclic graph, and its cures live in that error's
|
|
35
|
+
* `Option[]` — which `Error.message` does NOT carry. This executor is the top-level handler for its
|
|
36
|
+
* nx target, so it renders through the one human renderer here; letting the throw escape `runExecutor`
|
|
37
|
+
* would print the message with every cure silently dropped. Nothing is swallowed: the caller reports
|
|
38
|
+
* the rendered text and fails, and anything that is not a rule verdict is rethrown untouched.
|
|
39
|
+
*
|
|
40
|
+
* A rendered failure ends the run rather than joining `problems`: there is no graph left to run the
|
|
41
|
+
* other checks against, and a cycle is deliberately the one failure this rule does NOT route through
|
|
42
|
+
* its report-only window — an architecture with no deploy order is not a style preference.
|
|
43
|
+
*/
|
|
44
|
+
// webpieces-disable no-function-outside-class -- executor step helper, matches checkUnchanged in this file
|
|
45
|
+
function deriveOrRender(depsFile, hiddenProjects) {
|
|
46
|
+
// webpieces-disable no-unmanaged-exceptions -- top-level handler for this nx target; it renders, it does not swallow
|
|
47
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
48
|
+
try {
|
|
49
|
+
return (0, runtime_graph_1.deriveRuntimeGraphReport)(depsFile.projects, hiddenProjects, depsFile.apiContracts, depsFile.externalSystems);
|
|
32
50
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (cycles.length === 0)
|
|
39
|
-
return [];
|
|
40
|
-
const activeAllow = activeAllowedKeys(allowed);
|
|
41
|
-
const problems = [];
|
|
42
|
-
for (const cycle of cycles) {
|
|
43
|
-
const entry = activeAllow.get(cycle.key);
|
|
44
|
-
if (entry) {
|
|
45
|
-
console.log(`⏳ Allowed runtime cycle [${cycle.services.join(' <-> ')}] — ${entry.reason ?? 'no reason given'}`);
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
problems.push(`runtime cycle: ${cycle.services.join(' -> ')} -> ${cycle.services[0]}`);
|
|
51
|
+
catch (err) {
|
|
52
|
+
const error = (0, toError_1.toError)(err);
|
|
53
|
+
if (error instanceof rules_config_1.RuleFailError)
|
|
54
|
+
return (0, rules_config_1.renderRuleFailForHuman)(error);
|
|
55
|
+
throw error;
|
|
49
56
|
}
|
|
50
|
-
return problems;
|
|
51
57
|
}
|
|
52
58
|
/**
|
|
53
59
|
* Every `role:server` project must have a NODE in the runtime graph. A pure set difference against
|
|
@@ -56,7 +62,7 @@ function checkCycles(graph, allowed) {
|
|
|
56
62
|
* Now the only sanctioned way for a server to be absent from the drawing is `drawOnGraph:false`, and
|
|
57
63
|
* anything else that ever removes one fails the build by name instead of disappearing quietly.
|
|
58
64
|
*/
|
|
59
|
-
// webpieces-disable no-function-outside-class -- executor step helper, matches
|
|
65
|
+
// webpieces-disable no-function-outside-class -- executor step helper, matches checkUnchanged in this file
|
|
60
66
|
function checkServersPresent(projects, hiddenProjects, graph) {
|
|
61
67
|
const problems = [];
|
|
62
68
|
for (const name of Object.keys(projects).sort()) {
|
|
@@ -105,7 +111,14 @@ async function runExecutor(_options, context) {
|
|
|
105
111
|
}
|
|
106
112
|
// apiContracts comes from the SAME loaded file, so the queue/trigger data the derivation sees
|
|
107
113
|
// here is byte-identical to what generate saw in memory.
|
|
108
|
-
|
|
114
|
+
// A cyclic graph leaves no graph to run the rest of the checks against, and is the one failure
|
|
115
|
+
// this rule never softens through the report-only window below — see deriveOrRender.
|
|
116
|
+
const derived = deriveOrRender(depsFile, hiddenProjects);
|
|
117
|
+
if (typeof derived === 'string') {
|
|
118
|
+
console.error(`\n❌ Runtime architecture validation failed:\n\n${derived}\n`);
|
|
119
|
+
return { success: false };
|
|
120
|
+
}
|
|
121
|
+
const report = derived;
|
|
109
122
|
const graph = report.graph;
|
|
110
123
|
for (const warning of report.warnings)
|
|
111
124
|
console.warn(`⚠️ ${warning}`);
|
|
@@ -114,7 +127,6 @@ async function runExecutor(_options, context) {
|
|
|
114
127
|
// the name is a typo or a stale rename, and the graph only ever hid it by fanning the edge out.
|
|
115
128
|
const problems = [
|
|
116
129
|
...report.problems,
|
|
117
|
-
...checkCycles(graph, config.allowedCycles),
|
|
118
130
|
...checkServersPresent(depsFile.projects, hiddenProjects, graph),
|
|
119
131
|
];
|
|
120
132
|
const unchanged = checkUnchanged(workspaceRoot, graph);
|
|
@@ -124,13 +136,12 @@ async function runExecutor(_options, context) {
|
|
|
124
136
|
console.log(`⚠️ ${u.service} uses "${u.api}" but no in-repo service implements it (external?)`);
|
|
125
137
|
}
|
|
126
138
|
if (problems.length === 0) {
|
|
127
|
-
console.log('✅ Runtime architecture valid (
|
|
139
|
+
console.log('✅ Runtime architecture valid (graph unchanged)\n');
|
|
128
140
|
return { success: true };
|
|
129
141
|
}
|
|
130
142
|
console.error('\n❌ Runtime architecture validation failed:\n');
|
|
131
143
|
for (const p of problems)
|
|
132
144
|
console.error(` - ${p}`);
|
|
133
|
-
console.error('\nAllow a cycle temporarily via runtime-architecture.allowedCycles (services + reason + until) in webpieces.config.json.\n');
|
|
134
145
|
const reportOnly = (0, runtime_config_1.runtimeReportOnly)(config);
|
|
135
146
|
if (reportOnly.skip) {
|
|
136
147
|
console.log(`⏳ Reported but not failing (${reportOnly.reason}).\n`);
|
|
@@ -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 type { EnhancedGraph } from '../../lib/graph-sorter';\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 { printAutoHiddenServers } from '../../lib/runtime-participant-resolver';\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/**\n * Every `role:server` project must have a NODE in the runtime graph. A pure set difference against\n * what the derivation produced, and it exists because the derivation once dropped relation-less\n * servers silently: the diagram claimed to be complete while a deployed service was missing from it.\n * Now the only sanctioned way for a server to be absent from the drawing is `drawOnGraph:false`, and\n * anything else that ever removes one fails the build by name instead of disappearing quietly.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches checkCycles/checkUnchanged in this file\nexport function checkServersPresent(\n projects: EnhancedGraph,\n hiddenProjects: Set<string>,\n graph: RuntimeGraph,\n): string[] {\n const problems: string[] = [];\n for (const name of Object.keys(projects).sort()) {\n if (projects[name].role !== 'server') continue;\n if (hiddenProjects.has(name)) continue;\n if (graph.services[name] !== undefined) continue;\n problems.push(\n `role:server '${name}' has no node in the runtime graph. Tag it drawOnGraph:false in its ` +\n `project.json if that is intended.`,\n );\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(\n depsFile.projects,\n hiddenProjects,\n depsFile.apiContracts,\n depsFile.externalSystems,\n );\n const graph = report.graph;\n for (const warning of report.warnings) console.warn(`⚠️ ${warning}`);\n printAutoHiddenServers(report.autoHidden);\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[] = [\n ...report.problems,\n ...checkCycles(graph, config.allowedCycles),\n ...checkServersPresent(depsFile.projects, hiddenProjects, graph),\n ];\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":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;AAgEH,kDAgBC;AAYD,8BAmEC;AA5JD,0DAAgF;AAChF,yDAA0D;AAE1D,2CAAwC;AAExC,2DAKiC;AAEjC,yFAAgF;AAChF,6DAAmG;AAUnG;;;;;;;;;;;;GAYG;AACH,2GAA2G;AAC3G,SAAS,cAAc,CACnB,QAA0B,EAC1B,cAA2B;IAE3B,qHAAqH;IACrH,8DAA8D;IAC9D,IAAI,CAAC;QACD,OAAO,IAAA,wCAAwB,EAC3B,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC5F,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,KAAK,YAAY,4BAAa;YAAE,OAAO,IAAA,qCAAsB,EAAC,KAAK,CAAC,CAAC;QACzE,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,2GAA2G;AAC3G,SAAgB,mBAAmB,CAC/B,QAAuB,EACvB,cAA2B,EAC3B,KAAmB;IAEnB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC9C,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAS;QAC/C,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACvC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,SAAS;YAAE,SAAS;QACjD,QAAQ,CAAC,IAAI,CACT,gBAAgB,IAAI,sEAAsE;YACtF,mCAAmC,CAC1C,CAAC;IACN,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,kFAAkF;AAClF,SAAS,cAAc,CAAC,aAAqB,EAAE,OAAqB;IAChE,IAAI,CAAC,IAAA,sCAAsB,EAAC,aAAa,CAAC,EAAE,CAAC;QACzC,OAAO,yFAAyF,CAAC;IACrG,CAAC;IACD,MAAM,KAAK,GAAG,IAAA,gCAAgB,EAAC,aAAa,CAAC,CAAC;IAC9C,IAAI,KAAK,IAAI,IAAA,qCAAqB,EAAC,KAAK,CAAC,KAAK,IAAA,qCAAqB,EAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1F,OAAO,mGAAmG,CAAC;AAC/G,CAAC;AAEc,KAAK,UAAU,WAAW,CACrC,QAA4C,EAC5C,OAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IACnC,MAAM,MAAM,GAAG,IAAA,kCAAiB,EAAC,aAAa,CAAC,CAAC;IAEhD,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,kBAAkB,kCAAiB,gBAAgB,CAAC,CAAC;QACjE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;IACnE,4FAA4F;IAC5F,6FAA6F;IAC7F,6FAA6F;IAC7F,iDAAiD;IACjD,MAAM,QAAQ,GAAG,IAAA,+BAAgB,EAAC,aAAa,CAAC,CAAC;IACjD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,yEAAyE,CAAC,CAAC;QACzF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IACD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChD,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,KAAK,KAAK;YAAE,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChF,CAAC;IACD,8FAA8F;IAC9F,yDAAyD;IACzD,+FAA+F;IAC/F,qFAAqF;IACrF,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IACzD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,kDAAkD,OAAO,IAAI,CAAC,CAAC;QAC7E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC;IACvB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC;IACtE,IAAA,qDAAsB,EAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAE1C,8FAA8F;IAC9F,gGAAgG;IAChG,MAAM,QAAQ,GAAa;QACvB,GAAG,MAAM,CAAC,QAAQ;QAClB,GAAG,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,CAAC;KACnE,CAAC;IACF,MAAM,SAAS,GAAG,cAAc,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IACvD,IAAI,SAAS;QAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAExC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,GAAG,oDAAoD,CAAC,CAAC;IACrG,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;QAChE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAC/D,KAAK,MAAM,CAAC,IAAI,QAAQ;QAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAEpD,MAAM,UAAU,GAAG,IAAA,kCAAiB,EAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,+BAA+B,UAAU,CAAC,MAAM,MAAM,CAAC,CAAC;QACpE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC","sourcesContent":["/**\n * Validate Runtime Architecture Executor (workspace)\n *\n * Workspace-level checks on the runtime microservice graph:\n * 1. Unchanged: the freshly-assembled graph must match the committed\n * architecture/runtime-dependencies.json (run `architecture:generate`).\n * 2. Every role:server project has a node on the graph.\n *\n * CYCLES ARE NOT CHECKED HERE, and there is deliberately no allowlist for them any more. A runtime\n * cycle now fails inside the DERIVATION — `assignLevels` (lib/runtime-graph-levels.ts) throws a\n * RuleFailError naming every chain, because CD deploys in dependency order and a cyclic architecture\n * has no such order. `deriveRuntimeGraphReport` below therefore cannot return a cyclic graph, and the\n * only way past it is to break the cycle, or to declare ONE edge with a `cutLegacyCycle:<target>` nx\n * tag on the CALLING project.\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 { RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport { loadBlessedGraph } from '../../lib/graph-loader';\nimport type { DependenciesFile } from '../../lib/graph-loader';\nimport { toError } from '../../toError';\nimport type { EnhancedGraph } from '../../lib/graph-sorter';\nimport {\n deriveRuntimeGraphReport,\n loadRuntimeGraph,\n runtimeGraphFileExists,\n serializeRuntimeGraph,\n} from '../../lib/runtime-graph';\nimport type { RuntimeGraph, RuntimeGraphReport } from '../../lib/runtime-graph';\nimport { printAutoHiddenServers } from '../../lib/runtime-participant-resolver';\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/**\n * The derived report, or the RENDERED failure when the derivation refused to produce one.\n *\n * `assignLevels` throws a `RuleFailError` on a cyclic graph, and its cures live in that error's\n * `Option[]` — which `Error.message` does NOT carry. This executor is the top-level handler for its\n * nx target, so it renders through the one human renderer here; letting the throw escape `runExecutor`\n * would print the message with every cure silently dropped. Nothing is swallowed: the caller reports\n * the rendered text and fails, and anything that is not a rule verdict is rethrown untouched.\n *\n * A rendered failure ends the run rather than joining `problems`: there is no graph left to run the\n * other checks against, and a cycle is deliberately the one failure this rule does NOT route through\n * its report-only window — an architecture with no deploy order is not a style preference.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches checkUnchanged in this file\nfunction deriveOrRender(\n depsFile: DependenciesFile,\n hiddenProjects: Set<string>,\n): RuntimeGraphReport | string {\n // webpieces-disable no-unmanaged-exceptions -- top-level handler for this nx target; it renders, it does not swallow\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return deriveRuntimeGraphReport(\n depsFile.projects, hiddenProjects, depsFile.apiContracts, depsFile.externalSystems);\n } catch (err: unknown) {\n const error = toError(err);\n if (error instanceof RuleFailError) return renderRuleFailForHuman(error);\n throw error;\n }\n}\n\n/**\n * Every `role:server` project must have a NODE in the runtime graph. A pure set difference against\n * what the derivation produced, and it exists because the derivation once dropped relation-less\n * servers silently: the diagram claimed to be complete while a deployed service was missing from it.\n * Now the only sanctioned way for a server to be absent from the drawing is `drawOnGraph:false`, and\n * anything else that ever removes one fails the build by name instead of disappearing quietly.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches checkUnchanged in this file\nexport function checkServersPresent(\n projects: EnhancedGraph,\n hiddenProjects: Set<string>,\n graph: RuntimeGraph,\n): string[] {\n const problems: string[] = [];\n for (const name of Object.keys(projects).sort()) {\n if (projects[name].role !== 'server') continue;\n if (hiddenProjects.has(name)) continue;\n if (graph.services[name] !== undefined) continue;\n problems.push(\n `role:server '${name}' has no node in the runtime graph. Tag it drawOnGraph:false in its ` +\n `project.json if that is intended.`,\n );\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 // A cyclic graph leaves no graph to run the rest of the checks against, and is the one failure\n // this rule never softens through the report-only window below — see deriveOrRender.\n const derived = deriveOrRender(depsFile, hiddenProjects);\n if (typeof derived === 'string') {\n console.error(`\\n❌ Runtime architecture validation failed:\\n\\n${derived}\\n`);\n return { success: false };\n }\n const report = derived;\n const graph = report.graph;\n for (const warning of report.warnings) console.warn(`⚠️ ${warning}`);\n printAutoHiddenServers(report.autoHidden);\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[] = [\n ...report.problems,\n ...checkServersPresent(depsFile.projects, hiddenProjects, graph),\n ];\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 (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\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"]}
|
|
@@ -157,28 +157,13 @@ export declare function isAbstractClass(cls: ts.ClassDeclaration): boolean;
|
|
|
157
157
|
export declare function hasClassDecorator(cls: ts.ClassDeclaration, name: string): boolean;
|
|
158
158
|
/**
|
|
159
159
|
* The service a client-factory call aims at, from its config argument:
|
|
160
|
-
* `createRpcClient(WarmupApi, new ClientConfig('helper-fsdb'
|
|
160
|
+
* `createRpcClient(WarmupApi, new ClientConfig('helper-fsdb'))` → `'helper-fsdb'`.
|
|
161
161
|
*
|
|
162
162
|
* Only a `new <Xxx>ClientConfig('<string literal>')` yields a name. A variable, a template string
|
|
163
163
|
* or a computed expression yields null — the target is genuinely unknown at scan time, and the
|
|
164
164
|
* runtime graph must fall back to fan-out (loudly) rather than guess.
|
|
165
165
|
*/
|
|
166
166
|
export declare function targetServiceOf(call: ts.CallExpression): string | null;
|
|
167
|
-
/**
|
|
168
|
-
* The RUNTIME-HOST identity a client-factory call declares, or null when the client targets a
|
|
169
|
-
* deployed service:
|
|
170
|
-
* `createRpcClient(PartnerWebhookApi, new ClientConfig('partner-webhooks', new RuntimeHostFromContext(r)))`
|
|
171
|
-
* → `'partner-webhooks'`.
|
|
172
|
-
*
|
|
173
|
-
* It reads the SECOND constructor argument — the host policy — and returns the FIRST, the svcName,
|
|
174
|
-
* because under a runtime policy the svcName resolves to nothing and exists to be exactly this: the
|
|
175
|
-
* identity of the outbound node on the runtime graph.
|
|
176
|
-
*
|
|
177
|
-
* Reading the construction site rather than a new annotation is deliberate. The fact "this client's
|
|
178
|
-
* host is data" is already written there, in a class the caller had to name to get the behaviour, so
|
|
179
|
-
* there is nothing to keep in step and no second place for it to be wrong.
|
|
180
|
-
*/
|
|
181
|
-
export declare function runtimeHostOf(call: ts.CallExpression): string | null;
|
|
182
167
|
export declare function calleeMethodName(call: ts.CallExpression): string | null;
|
|
183
168
|
export declare function isTestFile(fileName: string): boolean;
|
|
184
169
|
/** {api, owner, type:'rpc'|'pubsub'} for an in-repo contract class, else null. */
|
|
@@ -31,7 +31,6 @@ exports.implementedTypeNames = implementedTypeNames;
|
|
|
31
31
|
exports.isAbstractClass = isAbstractClass;
|
|
32
32
|
exports.hasClassDecorator = hasClassDecorator;
|
|
33
33
|
exports.targetServiceOf = targetServiceOf;
|
|
34
|
-
exports.runtimeHostOf = runtimeHostOf;
|
|
35
34
|
exports.calleeMethodName = calleeMethodName;
|
|
36
35
|
exports.isTestFile = isTestFile;
|
|
37
36
|
exports.apiClassInfoFromNode = apiClassInfoFromNode;
|
|
@@ -61,13 +60,6 @@ const EXTERNAL_SYSTEM_TAG = 'externalSystem';
|
|
|
61
60
|
* `svcName` first, and a consumer's own `XxxClientConfig` follows the same shape.
|
|
62
61
|
*/
|
|
63
62
|
const CLIENT_CONFIG_SUFFIX = 'ClientConfig';
|
|
64
|
-
/**
|
|
65
|
-
* Class-name prefix of a host policy whose destination arrives at RUNTIME —
|
|
66
|
-
* `RuntimeHostFromContext`, `RuntimeHostFromContextAllowingInternalAddresses`. Matched as a PREFIX
|
|
67
|
-
* so a policy added later is picked up without a scanner release, and because the permissive variant
|
|
68
|
-
* is deliberately spelled as a longer name on the same stem.
|
|
69
|
-
*/
|
|
70
|
-
const RUNTIME_HOST_POLICY_PREFIX = 'RuntimeHost';
|
|
71
63
|
/**
|
|
72
64
|
* The module-scope `const NAME = '<string literal>'` bindings of ONE source file.
|
|
73
65
|
*
|
|
@@ -457,7 +449,7 @@ function hasClassDecorator(cls, name) {
|
|
|
457
449
|
}
|
|
458
450
|
/**
|
|
459
451
|
* The service a client-factory call aims at, from its config argument:
|
|
460
|
-
* `createRpcClient(WarmupApi, new ClientConfig('helper-fsdb'
|
|
452
|
+
* `createRpcClient(WarmupApi, new ClientConfig('helper-fsdb'))` → `'helper-fsdb'`.
|
|
461
453
|
*
|
|
462
454
|
* Only a `new <Xxx>ClientConfig('<string literal>')` yields a name. A variable, a template string
|
|
463
455
|
* or a computed expression yields null — the target is genuinely unknown at scan time, and the
|
|
@@ -477,39 +469,6 @@ function targetServiceOf(call) {
|
|
|
477
469
|
return null;
|
|
478
470
|
return first.text.length > 0 ? first.text : null;
|
|
479
471
|
}
|
|
480
|
-
/**
|
|
481
|
-
* The RUNTIME-HOST identity a client-factory call declares, or null when the client targets a
|
|
482
|
-
* deployed service:
|
|
483
|
-
* `createRpcClient(PartnerWebhookApi, new ClientConfig('partner-webhooks', new RuntimeHostFromContext(r)))`
|
|
484
|
-
* → `'partner-webhooks'`.
|
|
485
|
-
*
|
|
486
|
-
* It reads the SECOND constructor argument — the host policy — and returns the FIRST, the svcName,
|
|
487
|
-
* because under a runtime policy the svcName resolves to nothing and exists to be exactly this: the
|
|
488
|
-
* identity of the outbound node on the runtime graph.
|
|
489
|
-
*
|
|
490
|
-
* Reading the construction site rather than a new annotation is deliberate. The fact "this client's
|
|
491
|
-
* host is data" is already written there, in a class the caller had to name to get the behaviour, so
|
|
492
|
-
* there is nothing to keep in step and no second place for it to be wrong.
|
|
493
|
-
*/
|
|
494
|
-
// webpieces-disable no-function-outside-class -- pure AST accessor, matching the sibling helpers in di-graph/bindings.ts
|
|
495
|
-
function runtimeHostOf(call) {
|
|
496
|
-
if (call.arguments.length < 2)
|
|
497
|
-
return null;
|
|
498
|
-
const config = call.arguments[1];
|
|
499
|
-
if (!ts.isNewExpression(config) || !ts.isIdentifier(config.expression))
|
|
500
|
-
return null;
|
|
501
|
-
if (!config.expression.text.endsWith(CLIENT_CONFIG_SUFFIX))
|
|
502
|
-
return null;
|
|
503
|
-
const policy = config.arguments?.[1];
|
|
504
|
-
if (policy === undefined || !ts.isNewExpression(policy) || !ts.isIdentifier(policy.expression))
|
|
505
|
-
return null;
|
|
506
|
-
if (!policy.expression.text.startsWith(RUNTIME_HOST_POLICY_PREFIX))
|
|
507
|
-
return null;
|
|
508
|
-
const first = config.arguments?.[0];
|
|
509
|
-
if (first === undefined || !ts.isStringLiteral(first))
|
|
510
|
-
return null;
|
|
511
|
-
return first.text.length > 0 ? first.text : null;
|
|
512
|
-
}
|
|
513
472
|
// webpieces-disable no-function-outside-class -- pure AST accessor, matching the sibling helpers in di-graph/bindings.ts
|
|
514
473
|
function calleeMethodName(call) {
|
|
515
474
|
const callee = call.expression;
|