@webpieces/nx-webpieces-rules 0.4.701 → 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/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 +7 -0
- 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 +7 -0
- package/src/lib/runtime-graph-model.js.map +1 -1
- package/src/lib/runtime-graph.js +4 -2
- 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/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":"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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 { 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":"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","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"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cut-Legacy-Cycle Resolver
|
|
3
|
+
*
|
|
4
|
+
* Reads the `cutLegacyCycle:<targetService>` nx tags off a project.json.
|
|
5
|
+
*
|
|
6
|
+
* WHAT THE TAG MEANS — and it is deliberately not flattering. The runtime graph must be acyclic:
|
|
7
|
+
* CD rolls services out in dependency order and a cycle has no such order, so `assignLevels`
|
|
8
|
+
* (runtime-graph-levels.ts) THROWS rather than levelling one. This tag is the escape, and it does
|
|
9
|
+
* NOT claim the edge is harmless. It ADMITS the cycle is real and is being tolerated as LEGACY
|
|
10
|
+
* DEBT — an IOU written into project.json, not a design choice and not an approved pattern. The
|
|
11
|
+
* honest cures come first (retag a node that is not really a deployed service `role:lib`; make the
|
|
12
|
+
* hop asynchronous with `@PubSub()`, whose queued edges are already excluded; extract a shared
|
|
13
|
+
* `role:api-lib` both sides depend on); this is what you write down when none of them fit yet.
|
|
14
|
+
*
|
|
15
|
+
* It is PER-EDGE, not per-project: the tag names the TARGET service, so cutting `a -> b` says
|
|
16
|
+
* nothing about `a -> c`. A blanket project-level exemption would silently disable the check for
|
|
17
|
+
* every future edge that project grows.
|
|
18
|
+
*
|
|
19
|
+
* The edge is still DRAWN. It is stamped `cutLegacyCycle` on the runtime edge and rendered as a
|
|
20
|
+
* dashed red "legacy cycle" arrow — the whole point of the graph is that hops are visible, so an
|
|
21
|
+
* exemption that erased the arrow would hide exactly the debt it is recording.
|
|
22
|
+
*
|
|
23
|
+
* Keep it greppable: `grep -rn cutLegacyCycle` over the repo enumerates every tolerated cycle, which
|
|
24
|
+
* is the reason the tag says out loud what it is rather than hiding behind a neutral name.
|
|
25
|
+
*
|
|
26
|
+
* Resolution order:
|
|
27
|
+
* 1. Every `cutLegacyCycle:<targetService>` tag on the project (project.json tags). A project may
|
|
28
|
+
* carry several — one per edge it cuts — and each value must be a non-empty service name.
|
|
29
|
+
* 2. Fallback: no cuts, which is the state every project should be in.
|
|
30
|
+
*
|
|
31
|
+
* A tag naming a service NOTHING answers to fails the build; that check needs the resolved runtime
|
|
32
|
+
* nodes and therefore lives in runtime-cycle-cuts.ts, not here.
|
|
33
|
+
*/
|
|
34
|
+
import { ProjectInfo } from './project-info';
|
|
35
|
+
export declare const CUT_LEGACY_CYCLE_TAG_PREFIX = "cutLegacyCycle:";
|
|
36
|
+
export declare class CutLegacyCycleResolution {
|
|
37
|
+
/** Target service names this project cuts its runtime edge to, sorted; null when resolution failed. */
|
|
38
|
+
readonly targets: string[] | null;
|
|
39
|
+
/** Problem description when resolution failed, otherwise null */
|
|
40
|
+
readonly problem: string | null;
|
|
41
|
+
constructor(
|
|
42
|
+
/** Target service names this project cuts its runtime edge to, sorted; null when resolution failed. */
|
|
43
|
+
targets: string[] | null,
|
|
44
|
+
/** Problem description when resolution failed, otherwise null */
|
|
45
|
+
problem: string | null);
|
|
46
|
+
}
|
|
47
|
+
export declare function resolveCutLegacyCycles(info: ProjectInfo): CutLegacyCycleResolution;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cut-Legacy-Cycle Resolver
|
|
4
|
+
*
|
|
5
|
+
* Reads the `cutLegacyCycle:<targetService>` nx tags off a project.json.
|
|
6
|
+
*
|
|
7
|
+
* WHAT THE TAG MEANS — and it is deliberately not flattering. The runtime graph must be acyclic:
|
|
8
|
+
* CD rolls services out in dependency order and a cycle has no such order, so `assignLevels`
|
|
9
|
+
* (runtime-graph-levels.ts) THROWS rather than levelling one. This tag is the escape, and it does
|
|
10
|
+
* NOT claim the edge is harmless. It ADMITS the cycle is real and is being tolerated as LEGACY
|
|
11
|
+
* DEBT — an IOU written into project.json, not a design choice and not an approved pattern. The
|
|
12
|
+
* honest cures come first (retag a node that is not really a deployed service `role:lib`; make the
|
|
13
|
+
* hop asynchronous with `@PubSub()`, whose queued edges are already excluded; extract a shared
|
|
14
|
+
* `role:api-lib` both sides depend on); this is what you write down when none of them fit yet.
|
|
15
|
+
*
|
|
16
|
+
* It is PER-EDGE, not per-project: the tag names the TARGET service, so cutting `a -> b` says
|
|
17
|
+
* nothing about `a -> c`. A blanket project-level exemption would silently disable the check for
|
|
18
|
+
* every future edge that project grows.
|
|
19
|
+
*
|
|
20
|
+
* The edge is still DRAWN. It is stamped `cutLegacyCycle` on the runtime edge and rendered as a
|
|
21
|
+
* dashed red "legacy cycle" arrow — the whole point of the graph is that hops are visible, so an
|
|
22
|
+
* exemption that erased the arrow would hide exactly the debt it is recording.
|
|
23
|
+
*
|
|
24
|
+
* Keep it greppable: `grep -rn cutLegacyCycle` over the repo enumerates every tolerated cycle, which
|
|
25
|
+
* is the reason the tag says out loud what it is rather than hiding behind a neutral name.
|
|
26
|
+
*
|
|
27
|
+
* Resolution order:
|
|
28
|
+
* 1. Every `cutLegacyCycle:<targetService>` tag on the project (project.json tags). A project may
|
|
29
|
+
* carry several — one per edge it cuts — and each value must be a non-empty service name.
|
|
30
|
+
* 2. Fallback: no cuts, which is the state every project should be in.
|
|
31
|
+
*
|
|
32
|
+
* A tag naming a service NOTHING answers to fails the build; that check needs the resolved runtime
|
|
33
|
+
* nodes and therefore lives in runtime-cycle-cuts.ts, not here.
|
|
34
|
+
*/
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CutLegacyCycleResolution = exports.CUT_LEGACY_CYCLE_TAG_PREFIX = void 0;
|
|
37
|
+
exports.resolveCutLegacyCycles = resolveCutLegacyCycles;
|
|
38
|
+
exports.CUT_LEGACY_CYCLE_TAG_PREFIX = 'cutLegacyCycle:';
|
|
39
|
+
class CutLegacyCycleResolution {
|
|
40
|
+
targets;
|
|
41
|
+
problem;
|
|
42
|
+
constructor(
|
|
43
|
+
/** Target service names this project cuts its runtime edge to, sorted; null when resolution failed. */
|
|
44
|
+
targets,
|
|
45
|
+
/** Problem description when resolution failed, otherwise null */
|
|
46
|
+
problem) {
|
|
47
|
+
this.targets = targets;
|
|
48
|
+
this.problem = problem;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.CutLegacyCycleResolution = CutLegacyCycleResolution;
|
|
52
|
+
// webpieces-disable no-function-outside-class -- pure tag resolver, mirrors the sibling resolveRole/resolveDrawOnGraph
|
|
53
|
+
function resolveCutLegacyCycles(info) {
|
|
54
|
+
const values = info.tags
|
|
55
|
+
.filter((tag) => tag.startsWith(exports.CUT_LEGACY_CYCLE_TAG_PREFIX))
|
|
56
|
+
.map((tag) => tag.slice(exports.CUT_LEGACY_CYCLE_TAG_PREFIX.length).trim());
|
|
57
|
+
if (values.some((value) => value.length === 0)) {
|
|
58
|
+
return new CutLegacyCycleResolution(null, `${info.name}: a 'cutLegacyCycle:' tag has an empty value — it must name the target ` +
|
|
59
|
+
`service whose runtime edge is being cut, e.g. 'cutLegacyCycle:server2'`);
|
|
60
|
+
}
|
|
61
|
+
const unique = Array.from(new Set(values)).sort();
|
|
62
|
+
if (unique.length !== values.length) {
|
|
63
|
+
return new CutLegacyCycleResolution(null, `${info.name}: has duplicate 'cutLegacyCycle:' tags (${values.join(', ')}) — one tag per ` +
|
|
64
|
+
`target service`);
|
|
65
|
+
}
|
|
66
|
+
return new CutLegacyCycleResolution(unique, null);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=cut-legacy-cycle-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cut-legacy-cycle-resolver.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/cut-legacy-cycle-resolver.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;;;AAgBH,wDAqBC;AAjCY,QAAA,2BAA2B,GAAG,iBAAiB,CAAC;AAE7D,MAAa,wBAAwB;IAGb;IAEA;IAJpB;IACI,uGAAuG;IACvF,OAAwB;IACxC,iEAAiE;IACjD,OAAsB;QAFtB,YAAO,GAAP,OAAO,CAAiB;QAExB,YAAO,GAAP,OAAO,CAAe;IACvC,CAAC;CACP;AAPD,4DAOC;AAED,uHAAuH;AACvH,SAAgB,sBAAsB,CAAC,IAAiB;IACpD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI;SACnB,MAAM,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,mCAA2B,CAAC,CAAC;SACpE,GAAG,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,mCAA2B,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAEhF,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QACrD,OAAO,IAAI,wBAAwB,CAC/B,IAAI,EACJ,GAAG,IAAI,CAAC,IAAI,yEAAyE;YACjF,wEAAwE,CAC/E,CAAC;IACN,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,IAAI,wBAAwB,CAC/B,IAAI,EACJ,GAAG,IAAI,CAAC,IAAI,2CAA2C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB;YACtF,gBAAgB,CACvB,CAAC;IACN,CAAC;IACD,OAAO,IAAI,wBAAwB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AACtD,CAAC","sourcesContent":["/**\n * Cut-Legacy-Cycle Resolver\n *\n * Reads the `cutLegacyCycle:<targetService>` nx tags off a project.json.\n *\n * WHAT THE TAG MEANS — and it is deliberately not flattering. The runtime graph must be acyclic:\n * CD rolls services out in dependency order and a cycle has no such order, so `assignLevels`\n * (runtime-graph-levels.ts) THROWS rather than levelling one. This tag is the escape, and it does\n * NOT claim the edge is harmless. It ADMITS the cycle is real and is being tolerated as LEGACY\n * DEBT — an IOU written into project.json, not a design choice and not an approved pattern. The\n * honest cures come first (retag a node that is not really a deployed service `role:lib`; make the\n * hop asynchronous with `@PubSub()`, whose queued edges are already excluded; extract a shared\n * `role:api-lib` both sides depend on); this is what you write down when none of them fit yet.\n *\n * It is PER-EDGE, not per-project: the tag names the TARGET service, so cutting `a -> b` says\n * nothing about `a -> c`. A blanket project-level exemption would silently disable the check for\n * every future edge that project grows.\n *\n * The edge is still DRAWN. It is stamped `cutLegacyCycle` on the runtime edge and rendered as a\n * dashed red \"legacy cycle\" arrow — the whole point of the graph is that hops are visible, so an\n * exemption that erased the arrow would hide exactly the debt it is recording.\n *\n * Keep it greppable: `grep -rn cutLegacyCycle` over the repo enumerates every tolerated cycle, which\n * is the reason the tag says out loud what it is rather than hiding behind a neutral name.\n *\n * Resolution order:\n * 1. Every `cutLegacyCycle:<targetService>` tag on the project (project.json tags). A project may\n * carry several — one per edge it cuts — and each value must be a non-empty service name.\n * 2. Fallback: no cuts, which is the state every project should be in.\n *\n * A tag naming a service NOTHING answers to fails the build; that check needs the resolved runtime\n * nodes and therefore lives in runtime-cycle-cuts.ts, not here.\n */\n\nimport { ProjectInfo } from './project-info';\n\nexport const CUT_LEGACY_CYCLE_TAG_PREFIX = 'cutLegacyCycle:';\n\nexport class CutLegacyCycleResolution {\n constructor(\n /** Target service names this project cuts its runtime edge to, sorted; null when resolution failed. */\n public readonly targets: string[] | null,\n /** Problem description when resolution failed, otherwise null */\n public readonly problem: string | null,\n ) {}\n}\n\n// webpieces-disable no-function-outside-class -- pure tag resolver, mirrors the sibling resolveRole/resolveDrawOnGraph\nexport function resolveCutLegacyCycles(info: ProjectInfo): CutLegacyCycleResolution {\n const values = info.tags\n .filter((tag: string) => tag.startsWith(CUT_LEGACY_CYCLE_TAG_PREFIX))\n .map((tag: string) => tag.slice(CUT_LEGACY_CYCLE_TAG_PREFIX.length).trim());\n\n if (values.some((value: string) => value.length === 0)) {\n return new CutLegacyCycleResolution(\n null,\n `${info.name}: a 'cutLegacyCycle:' tag has an empty value — it must name the target ` +\n `service whose runtime edge is being cut, e.g. 'cutLegacyCycle:server2'`,\n );\n }\n const unique = Array.from(new Set(values)).sort();\n if (unique.length !== values.length) {\n return new CutLegacyCycleResolution(\n null,\n `${info.name}: has duplicate 'cutLegacyCycle:' tags (${values.join(', ')}) — one tag per ` +\n `target service`,\n );\n }\n return new CutLegacyCycleResolution(unique, null);\n}\n"]}
|
package/src/lib/graph-loader.js
CHANGED
|
@@ -220,6 +220,9 @@ function formatEntryLines(entry) {
|
|
|
220
220
|
pushOptionalField(lines, 'serviceName', entry.serviceName);
|
|
221
221
|
pushOptionalCallsServiceField(lines, entry.callsService);
|
|
222
222
|
pushOptionalBooleanField(lines, 'drawOnGraph', entry.drawOnGraph);
|
|
223
|
+
// MUST be persisted: validate re-derives the runtime graph from this file, so a cut written only
|
|
224
|
+
// in-memory by generate would make generate and validate level (and cycle-check) different graphs.
|
|
225
|
+
pushOptionalArrayField(lines, 'cutLegacyCycle', entry.cutLegacyCycle);
|
|
223
226
|
pushOptionalField(lines, 'shortDescription', entry.shortDescription);
|
|
224
227
|
pushOptionalField(lines, 'responsibilitiesFile', entry.responsibilitiesFile);
|
|
225
228
|
pushOptionalField(lines, 'designFile', entry.designFile);
|