@webpieces/nx-webpieces-rules 0.3.334 ā 0.3.336
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/executors.json +5 -0
- package/package.json +6 -6
- package/src/executors/generate/executor.js +6 -0
- package/src/executors/generate/executor.js.map +1 -1
- package/src/executors/validate-api-relations/executor.d.ts +18 -0
- package/src/executors/validate-api-relations/executor.js +51 -0
- package/src/executors/validate-api-relations/executor.js.map +1 -0
- package/src/executors/validate-api-relations/schema.json +8 -0
- package/src/executors/validate-architecture-unchanged/executor.js +22 -11
- package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
- package/src/lib/api-usage/api-relations-validator.d.ts +30 -0
- package/src/lib/api-usage/api-relations-validator.js +74 -0
- package/src/lib/api-usage/api-relations-validator.js.map +1 -0
- package/src/lib/api-usage/api-relations.d.ts +51 -0
- package/src/lib/api-usage/api-relations.js +34 -0
- package/src/lib/api-usage/api-relations.js.map +1 -0
- package/src/lib/api-usage/api-scanner.d.ts +60 -0
- package/src/lib/api-usage/api-scanner.js +239 -0
- package/src/lib/api-usage/api-scanner.js.map +1 -0
- package/src/lib/di-graph/analyzer-strategy.d.ts +1 -0
- package/src/lib/di-graph/analyzer-strategy.js +3 -0
- package/src/lib/di-graph/analyzer-strategy.js.map +1 -1
- package/src/lib/graph-comparator.js +8 -0
- package/src/lib/graph-comparator.js.map +1 -1
- package/src/lib/graph-loader.js +18 -0
- package/src/lib/graph-loader.js.map +1 -1
- package/src/lib/graph-sorter.d.ts +5 -0
- package/src/lib/graph-sorter.js.map +1 -1
- package/src/lib/graph-visualizer.d.ts +10 -0
- package/src/lib/graph-visualizer.js +48 -3
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/role-resolver.d.ts +3 -0
- package/src/lib/role-resolver.js +4 -1
- package/src/lib/role-resolver.js.map +1 -1
package/executors.json
CHANGED
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
"schema": "./src/executors/validate-no-skiplevel-deps/schema.json",
|
|
41
41
|
"description": "Validate no project has redundant transitive dependencies"
|
|
42
42
|
},
|
|
43
|
+
"validate-api-relations": {
|
|
44
|
+
"implementation": "./src/executors/validate-api-relations/executor",
|
|
45
|
+
"schema": "./src/executors/validate-api-relations/schema.json",
|
|
46
|
+
"description": "Validate every server/client api-lib dependency is implemented or used (no unexplained edges)"
|
|
47
|
+
},
|
|
43
48
|
"validate-no-file-import-cycles": {
|
|
44
49
|
"implementation": "./src/executors/validate-no-file-import-cycles/executor",
|
|
45
50
|
"schema": "./src/executors/validate-no-file-import-cycles/schema.json",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.336",
|
|
4
4
|
"description": "Nx-specific webpieces validation rules and graph tooling. Bundles all @webpieces rule packages with Nx graph validators and an inference plugin.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"README.md"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@webpieces/ai-hook-rules": "0.3.
|
|
25
|
-
"@webpieces/code-rules": "0.3.
|
|
26
|
-
"@webpieces/eslint-rules": "0.3.
|
|
27
|
-
"@webpieces/pr-gate": "0.3.
|
|
28
|
-
"@webpieces/rules-config": "0.3.
|
|
24
|
+
"@webpieces/ai-hook-rules": "0.3.336",
|
|
25
|
+
"@webpieces/code-rules": "0.3.336",
|
|
26
|
+
"@webpieces/eslint-rules": "0.3.336",
|
|
27
|
+
"@webpieces/pr-gate": "0.3.336",
|
|
28
|
+
"@webpieces/rules-config": "0.3.336",
|
|
29
29
|
"madge": "8.0.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
@@ -14,6 +14,7 @@ const graph_generator_1 = require("../../lib/graph-generator");
|
|
|
14
14
|
const graph_sorter_1 = require("../../lib/graph-sorter");
|
|
15
15
|
const graph_loader_1 = require("../../lib/graph-loader");
|
|
16
16
|
const graph_metadata_1 = require("../../lib/graph-metadata");
|
|
17
|
+
const api_scanner_1 = require("../../lib/api-usage/api-scanner");
|
|
17
18
|
const graph_visualizer_1 = require("../../lib/graph-visualizer");
|
|
18
19
|
const runtime_markers_1 = require("../../lib/runtime-markers");
|
|
19
20
|
const runtime_graph_1 = require("../../lib/runtime-graph");
|
|
@@ -55,6 +56,11 @@ async function runExecutor(options, context) {
|
|
|
55
56
|
console.log('š·ļø Enriching graph with framework + responsibilities metadata...');
|
|
56
57
|
const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
|
|
57
58
|
(0, graph_metadata_1.enrichGraph)(enhancedGraph, projectInfos, workspaceRoot);
|
|
59
|
+
// Step 3b: Classify each api-lib edge (implements/uses + rpc/pubsub) by
|
|
60
|
+
// scanning source, so dependencies.json + the viz + the runtime graph all
|
|
61
|
+
// read the same derived truth.
|
|
62
|
+
console.log('š Scanning source for implements/uses API relations...');
|
|
63
|
+
(0, api_scanner_1.scanAndAttachApiRelations)(workspaceRoot, enhancedGraph, projectInfos);
|
|
58
64
|
// Step 4: Save the graph
|
|
59
65
|
console.log('š¾ Saving graph to architecture/dependencies.json...');
|
|
60
66
|
(0, graph_loader_1.saveGraph)(enhancedGraph, workspaceRoot, graphPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/generate/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/generate/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generate Executor\n *\n * Generates the architecture dependency graph and saves it to architecture/dependencies.json.\n *\n * Usage:\n * nx run architecture:generate\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { writeTemplate } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { saveGraph } from '../../lib/graph-loader';\nimport { collectProjectInfo, enrichGraph, MetadataValidationError } from '../../lib/graph-metadata';\nimport { scanAndAttachApiRelations } from '../../lib/api-usage/api-scanner';\nimport { GraphVisualizer } from '../../lib/graph-visualizer';\nimport { buildWorkspaceModel } from '../../lib/runtime-markers';\nimport { assembleRuntimeGraph, saveRuntimeGraph } from '../../lib/runtime-graph';\nimport { loadRuntimeConfig } from '../../lib/runtime-config';\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, so\n * one regenerate produces both committed files. Skipped when the\n * runtime-architecture rule is OFF or no apiProjectPaths are configured.\n */\nasync function generateRuntimeGraph(workspaceRoot: string): Promise<void> {\n const config = loadRuntimeConfig(workspaceRoot);\n if (config.off || config.servicePaths.length === 0) {\n console.log('āļø Runtime graph skipped (runtime-architecture OFF or no servicePaths)');\n return;\n }\n console.log('š” Generating runtime graph from service-contract.json files...');\n const model = await buildWorkspaceModel(workspaceRoot, config.apiProjectPaths, config.servicePaths);\n const runtimeGraph = assembleRuntimeGraph(model, workspaceRoot);\n saveRuntimeGraph(runtimeGraph, workspaceRoot);\n const serviceCount = Object.keys(runtimeGraph.services).length;\n console.log(\n `ā
Runtime graph saved (${serviceCount} services, ${runtimeGraph.runtimeEdges.length} runtime edges)`,\n );\n}\n\nexport default async function runExecutor(\n options: GenerateExecutorOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n console.log('\\nš Architecture Graph Generator\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Build the full graph from nx, then transitively reduce it to the view\n console.log(\"š Generating dependency graph from nx's project graph...\");\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to assign levels for visualization)\n console.log('š Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Enrich with AI metadata (framework, shortDescription, file\n // pointers). This VALIDATES (responsibilities.md required per project)\n // and throws before any write, so a failure never clobbers the file.\n console.log('š·ļø Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(enhancedGraph, projectInfos, workspaceRoot);\n\n // Step 3b: Classify each api-lib edge (implements/uses + rpc/pubsub) by\n // scanning source, so dependencies.json + the viz + the runtime graph all\n // read the same derived truth.\n console.log('š Scanning source for implements/uses API relations...');\n scanAndAttachApiRelations(workspaceRoot, enhancedGraph, projectInfos);\n\n // Step 4: Save the graph\n console.log('š¾ Saving graph to architecture/dependencies.json...');\n saveGraph(enhancedGraph, workspaceRoot, graphPath);\n console.log('ā
Graph saved successfully');\n\n // Step 4b: Write the committed, clickable HTML view next to the JSON so\n // dependencies.html regenerates in lock-step with dependencies.json.\n const vizPaths = new GraphVisualizer().writeVisualization(enhancedGraph, workspaceRoot);\n console.log(`ā
Wrote ${vizPaths.htmlPath}`);\n\n // Step 5: Generate the runtime microservice graph at the same time\n await generateRuntimeGraph(workspaceRoot);\n\n // Print summary\n const projectCount = Object.keys(enhancedGraph).length;\n const levels = new Set(Object.values(enhancedGraph).map((e) => e.level));\n console.log(`\\nš Graph Summary:`);\n console.log(` Projects: ${projectCount}`);\n console.log(` Levels: ${levels.size} (0-${Math.max(...levels)})`);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('ā Graph generation failed:', error.message);\n if (error instanceof MetadataValidationError) {\n const mdPath = writeTemplate(workspaceRoot, 'webpieces.responsibilities.md');\n console.error('');\n console.error('ā ļø *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** ā ļø');\n }\n return { success: false };\n }\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate API Relations Executor
|
|
3
|
+
*
|
|
4
|
+
* Fails when a runnable project (role:server / role:client) depends on an api-lib
|
|
5
|
+
* but the source scan finds it neither IMPLEMENTS (serves via addRoutes) nor USES
|
|
6
|
+
* (calls via createRpcClient/createPubSubClient) any of that api-lib's contracts.
|
|
7
|
+
* Such an edge is an unexplained dependency ā a dead import or forgotten wiring.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* nx run architecture:validate-api-relations
|
|
11
|
+
*/
|
|
12
|
+
import type { ExecutorContext } from '@nx/devkit';
|
|
13
|
+
export interface ValidateApiRelationsOptions {
|
|
14
|
+
}
|
|
15
|
+
export interface ExecutorResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
}
|
|
18
|
+
export default function runExecutor(_options: ValidateApiRelationsOptions, context: ExecutorContext): Promise<ExecutorResult>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validate API Relations Executor
|
|
4
|
+
*
|
|
5
|
+
* Fails when a runnable project (role:server / role:client) depends on an api-lib
|
|
6
|
+
* but the source scan finds it neither IMPLEMENTS (serves via addRoutes) nor USES
|
|
7
|
+
* (calls via createRpcClient/createPubSubClient) any of that api-lib's contracts.
|
|
8
|
+
* Such an edge is an unexplained dependency ā a dead import or forgotten wiring.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* nx run architecture:validate-api-relations
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.default = runExecutor;
|
|
15
|
+
const graph_generator_1 = require("../../lib/graph-generator");
|
|
16
|
+
const graph_sorter_1 = require("../../lib/graph-sorter");
|
|
17
|
+
const graph_metadata_1 = require("../../lib/graph-metadata");
|
|
18
|
+
const api_scanner_1 = require("../../lib/api-usage/api-scanner");
|
|
19
|
+
const api_relations_validator_1 = require("../../lib/api-usage/api-relations-validator");
|
|
20
|
+
const toError_1 = require("../../toError");
|
|
21
|
+
// webpieces-disable no-function-outside-class -- nx executor entry point (default export), like every sibling executor
|
|
22
|
+
async function runExecutor(_options, context) {
|
|
23
|
+
const workspaceRoot = context.root;
|
|
24
|
+
console.log('\nš Validating API Relations (implements/uses per api-lib dependency)\n');
|
|
25
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
26
|
+
try {
|
|
27
|
+
// Full (un-reduced) graph so a direct api-lib dependency is never hidden by
|
|
28
|
+
// transitive reduction, then attach the derived apiRelations.
|
|
29
|
+
const rawGraph = await (0, graph_generator_1.generateGraph)();
|
|
30
|
+
const graph = (0, graph_sorter_1.sortGraphTopologically)(rawGraph);
|
|
31
|
+
const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
|
|
32
|
+
const scan = (0, api_scanner_1.scanAndAttachApiRelations)(workspaceRoot, graph, projectInfos);
|
|
33
|
+
const violations = (0, api_relations_validator_1.findUnclassifiedApiDeps)(graph, projectInfos, scan);
|
|
34
|
+
if (violations.length === 0) {
|
|
35
|
+
console.log('ā
Every server/client api-lib dependency is implemented or used.');
|
|
36
|
+
return { success: true };
|
|
37
|
+
}
|
|
38
|
+
console.error(`ā ${violations.length} unexplained api-lib dependenc(ies):\n`);
|
|
39
|
+
for (const violation of violations) {
|
|
40
|
+
console.error((0, api_relations_validator_1.describeUnclassifiedApiDep)(violation));
|
|
41
|
+
console.error('');
|
|
42
|
+
}
|
|
43
|
+
return { success: false };
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const error = (0, toError_1.toError)(err);
|
|
47
|
+
console.error('ā API relations validation failed:', error.message);
|
|
48
|
+
return { success: false };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-api-relations/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AAmBH,8BAkCC;AAlDD,+DAA0D;AAC1D,yDAAgE;AAChE,6DAA8D;AAC9D,iEAA4E;AAC5E,yFAAkH;AAClH,2CAAwC;AAUxC,uHAAuH;AACxG,KAAK,UAAU,WAAW,CACrC,QAAqC,EACrC,OAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;IAExF,8DAA8D;IAC9D,IAAI,CAAC;QACD,4EAA4E;QAC5E,8DAA8D;QAC9D,MAAM,QAAQ,GAAG,MAAM,IAAA,+BAAa,GAAE,CAAC;QACvC,MAAM,KAAK,GAAG,IAAA,qCAAsB,EAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,YAAY,GAAG,MAAM,IAAA,mCAAkB,GAAE,CAAC;QAChD,MAAM,IAAI,GAAG,IAAA,uCAAyB,EAAC,aAAa,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;QAE3E,MAAM,UAAU,GAAG,IAAA,iDAAuB,EAAC,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;QACtE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;YAChF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC,MAAM,wCAAwC,CAAC,CAAC;QAC9E,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACjC,OAAO,CAAC,KAAK,CAAC,IAAA,oDAA0B,EAAC,SAAS,CAAC,CAAC,CAAC;YACrD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACnE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Validate API Relations Executor\n *\n * Fails when a runnable project (role:server / role:client) depends on an api-lib\n * but the source scan finds it neither IMPLEMENTS (serves via addRoutes) nor USES\n * (calls via createRpcClient/createPubSubClient) any of that api-lib's contracts.\n * Such an edge is an unexplained dependency ā a dead import or forgotten wiring.\n *\n * Usage:\n * nx run architecture:validate-api-relations\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { collectProjectInfo } from '../../lib/graph-metadata';\nimport { scanAndAttachApiRelations } from '../../lib/api-usage/api-scanner';\nimport { findUnclassifiedApiDeps, describeUnclassifiedApiDep } from '../../lib/api-usage/api-relations-validator';\nimport { toError } from '../../toError';\n\nexport interface ValidateApiRelationsOptions {\n // No options needed\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n// webpieces-disable no-function-outside-class -- nx executor entry point (default export), like every sibling executor\nexport default async function runExecutor(\n _options: ValidateApiRelationsOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n console.log('\\nš Validating API Relations (implements/uses per api-lib dependency)\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Full (un-reduced) graph so a direct api-lib dependency is never hidden by\n // transitive reduction, then attach the derived apiRelations.\n const rawGraph = await generateGraph();\n const graph = sortGraphTopologically(rawGraph);\n const projectInfos = await collectProjectInfo();\n const scan = scanAndAttachApiRelations(workspaceRoot, graph, projectInfos);\n\n const violations = findUnclassifiedApiDeps(graph, projectInfos, scan);\n if (violations.length === 0) {\n console.log('ā
Every server/client api-lib dependency is implemented or used.');\n return { success: true };\n }\n\n console.error(`ā ${violations.length} unexplained api-lib dependenc(ies):\\n`);\n for (const violation of violations) {\n console.error(describeUnclassifiedApiDep(violation));\n console.error('');\n }\n return { success: false };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('ā API relations validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
|
|
@@ -16,6 +16,7 @@ const graph_sorter_1 = require("../../lib/graph-sorter");
|
|
|
16
16
|
const graph_comparator_1 = require("../../lib/graph-comparator");
|
|
17
17
|
const graph_loader_1 = require("../../lib/graph-loader");
|
|
18
18
|
const graph_metadata_1 = require("../../lib/graph-metadata");
|
|
19
|
+
const api_scanner_1 = require("../../lib/api-usage/api-scanner");
|
|
19
20
|
const toError_1 = require("../../toError");
|
|
20
21
|
const TMP_MD_FILE = 'webpieces.dependencies.md';
|
|
21
22
|
/**
|
|
@@ -42,6 +43,24 @@ function reportMismatch(summary, workspaceRoot) {
|
|
|
42
43
|
console.error(' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change');
|
|
43
44
|
console.error(' 3. Commit the updated architecture/dependencies.json');
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Build the current dependency graph exactly as the generator does: reduce the nx
|
|
48
|
+
* graph, sort into levels, enrich with metadata, and attach the derived
|
|
49
|
+
* apiRelations ā so this validator compares like-for-like against the committed file.
|
|
50
|
+
*/
|
|
51
|
+
// webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch/writeTmpInstructionsFile in this file
|
|
52
|
+
async function buildCurrentGraph(workspaceRoot) {
|
|
53
|
+
console.log('š Generating current dependency graph...');
|
|
54
|
+
const reducedGraph = await (0, graph_generator_1.generateReducedGraph)();
|
|
55
|
+
console.log('š Computing topological layers...');
|
|
56
|
+
const currentGraph = (0, graph_sorter_1.sortGraphTopologically)(reducedGraph);
|
|
57
|
+
console.log('š·ļø Enriching graph with framework + responsibilities metadata...');
|
|
58
|
+
const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
|
|
59
|
+
(0, graph_metadata_1.enrichGraph)(currentGraph, projectInfos, workspaceRoot);
|
|
60
|
+
console.log('š Scanning source for implements/uses API relations...');
|
|
61
|
+
(0, api_scanner_1.scanAndAttachApiRelations)(workspaceRoot, currentGraph, projectInfos);
|
|
62
|
+
return currentGraph;
|
|
63
|
+
}
|
|
45
64
|
async function runExecutor(options, context) {
|
|
46
65
|
const graphPath = options.graphPath;
|
|
47
66
|
const workspaceRoot = context.root;
|
|
@@ -59,17 +78,9 @@ async function runExecutor(options, context) {
|
|
|
59
78
|
console.error(' 4. Commit architecture/dependencies.json');
|
|
60
79
|
return { success: false };
|
|
61
80
|
}
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
// Step 2: Topological sort (to get enhanced graph with levels)
|
|
66
|
-
console.log('š Computing topological layers...');
|
|
67
|
-
const currentGraph = (0, graph_sorter_1.sortGraphTopologically)(reducedGraph);
|
|
68
|
-
// Step 3: Enrich with AI metadata so edits to responsibilities.md,
|
|
69
|
-
// framework tags, or project moves are caught as graph changes
|
|
70
|
-
console.log('š·ļø Enriching graph with framework + responsibilities metadata...');
|
|
71
|
-
const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
|
|
72
|
-
(0, graph_metadata_1.enrichGraph)(currentGraph, projectInfos, workspaceRoot);
|
|
81
|
+
// Steps 1-3: build + enrich + scan the current graph (same pipeline the
|
|
82
|
+
// generator runs, so any drift is caught).
|
|
83
|
+
const currentGraph = await buildCurrentGraph(workspaceRoot);
|
|
73
84
|
// Step 4: Load saved graph
|
|
74
85
|
console.log('š Loading saved graph...');
|
|
75
86
|
const savedGraph = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot, graphPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-architecture-unchanged/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-architecture-unchanged/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Architecture Unchanged Executor\n *\n * Validates that the current architecture graph matches the saved blessed graph.\n * This ensures no unapproved architecture changes have been made.\n *\n * Usage:\n * nx run architecture:validate-architecture-unchanged\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { writeTemplate } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { compareGraphs } from '../../lib/graph-comparator';\nimport { loadBlessedGraph, graphFileExists } from '../../lib/graph-loader';\nimport { collectProjectInfo, enrichGraph, MetadataValidationError } from '../../lib/graph-metadata';\nimport { scanAndAttachApiRelations } from '../../lib/api-usage/api-scanner';\nimport type { EnhancedGraph } from '../../lib/graph-sorter';\nimport { toError } from '../../toError';\n\nexport interface ValidateArchitectureUnchangedOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst TMP_MD_FILE = 'webpieces.dependencies.md';\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructionsFile(workspaceRoot: string): string {\n const mdPath = writeTemplate(workspaceRoot, TMP_MD_FILE);\n\n return mdPath;\n}\n\n/**\n * Report a current-vs-saved graph mismatch and write the AI instructions file.\n */\nfunction reportMismatch(summary: string, workspaceRoot: string): void {\n const mdPath = writeTmpInstructionsFile(workspaceRoot);\n\n console.error('ā Architecture has changed since last update!');\n console.error('\\nDifferences:');\n console.error(summary);\n console.error('');\n console.error('ā ļø *** Refer to ' + mdPath + ' for instructions on how to fix *** ā ļø');\n console.error('');\n console.error('To fix:');\n console.error(' 1. Review the changes above');\n console.error(' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change');\n console.error(' 3. Commit the updated architecture/dependencies.json');\n}\n\n/**\n * Build the current dependency graph exactly as the generator does: reduce the nx\n * graph, sort into levels, enrich with metadata, and attach the derived\n * apiRelations ā so this validator compares like-for-like against the committed file.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch/writeTmpInstructionsFile in this file\nasync function buildCurrentGraph(workspaceRoot: string): Promise<EnhancedGraph> {\n console.log('š Generating current dependency graph...');\n const reducedGraph = await generateReducedGraph();\n console.log('š Computing topological layers...');\n const currentGraph = sortGraphTopologically(reducedGraph);\n console.log('š·ļø Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(currentGraph, projectInfos, workspaceRoot);\n console.log('š Scanning source for implements/uses API relations...');\n scanAndAttachApiRelations(workspaceRoot, currentGraph, projectInfos);\n return currentGraph;\n}\n\nexport default async function runExecutor(\n options: ValidateArchitectureUnchangedOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n console.log('\\nš Validating Architecture Unchanged\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Check if saved graph exists\n if (!graphFileExists(workspaceRoot, graphPath)) {\n console.error('ā No saved graph found at architecture/dependencies.json');\n console.error('');\n console.error('To initialize:');\n console.error(' 1. Run: nx run architecture:generate');\n console.error(' 2. Run: nx run architecture:visualize');\n console.error(' 3. Manually inspect the generated graph to confirm it is the desired architecture');\n console.error(' 4. Commit architecture/dependencies.json');\n return { success: false };\n }\n\n // Steps 1-3: build + enrich + scan the current graph (same pipeline the\n // generator runs, so any drift is caught).\n const currentGraph = await buildCurrentGraph(workspaceRoot);\n\n // Step 4: Load saved graph\n console.log('š Loading saved graph...');\n const savedGraph = loadBlessedGraph(workspaceRoot, graphPath);\n\n if (!savedGraph) {\n console.error('ā Could not load saved graph');\n return { success: false };\n }\n\n // Step 5: Compare graphs\n console.log('š Comparing current graph to saved graph...');\n const comparison = compareGraphs(currentGraph, savedGraph.projects);\n\n if (comparison.identical) {\n console.log('ā
Architecture unchanged - current graph matches saved graph');\n return { success: true };\n }\n reportMismatch(comparison.summary, workspaceRoot);\n return { success: false };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('ā Architecture validation failed:', error.message);\n if (error instanceof MetadataValidationError) {\n const mdPath = writeTemplate(workspaceRoot, 'webpieces.responsibilities.md');\n console.error('');\n console.error('ā ļø *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** ā ļø');\n }\n return { success: false };\n }\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Relations Validator
|
|
3
|
+
*
|
|
4
|
+
* Enforces that the dependency graph is TRUTHFUL: a runnable project (role server
|
|
5
|
+
* or client) that compiles against an api-lib must actually IMPLEMENT (serve) or
|
|
6
|
+
* USE (call) at least one of its APIs. A dependency that is neither is almost
|
|
7
|
+
* always a mistake ā a forgotten client wiring, a dead import, or a controller
|
|
8
|
+
* that was never registered ā and it would draw an unexplained edge in the graph.
|
|
9
|
+
*
|
|
10
|
+
* The check reuses the same source scan that produces `apiRelations`, so "does P
|
|
11
|
+
* relate to api-lib D" is answered by real code, never a declaration.
|
|
12
|
+
*/
|
|
13
|
+
import type { EnhancedGraph } from '../graph-sorter';
|
|
14
|
+
import { ProjectInfo } from '../project-info';
|
|
15
|
+
import { ApiScanResult } from './api-scanner';
|
|
16
|
+
/** One `role:server`/`role:client` project that depends on an api-lib it neither implements nor uses. */
|
|
17
|
+
export interface UnclassifiedApiDep {
|
|
18
|
+
project: string;
|
|
19
|
+
role: string;
|
|
20
|
+
apiLib: string;
|
|
21
|
+
/** The API contract class names that api-lib exports (for the fix hint). */
|
|
22
|
+
apis: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Every server/client ā api-lib edge for which the scan found NO implements and
|
|
26
|
+
* NO uses. `graph` must already carry `apiRelations` (call scanAndAttachApiRelations first).
|
|
27
|
+
*/
|
|
28
|
+
export declare function findUnclassifiedApiDeps(graph: EnhancedGraph, projectInfos: Map<string, ProjectInfo>, scan: ApiScanResult): UnclassifiedApiDep[];
|
|
29
|
+
/** Human-readable, fix-oriented report for one unclassified dependency. */
|
|
30
|
+
export declare function describeUnclassifiedApiDep(violation: UnclassifiedApiDep): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* API Relations Validator
|
|
4
|
+
*
|
|
5
|
+
* Enforces that the dependency graph is TRUTHFUL: a runnable project (role server
|
|
6
|
+
* or client) that compiles against an api-lib must actually IMPLEMENT (serve) or
|
|
7
|
+
* USE (call) at least one of its APIs. A dependency that is neither is almost
|
|
8
|
+
* always a mistake ā a forgotten client wiring, a dead import, or a controller
|
|
9
|
+
* that was never registered ā and it would draw an unexplained edge in the graph.
|
|
10
|
+
*
|
|
11
|
+
* The check reuses the same source scan that produces `apiRelations`, so "does P
|
|
12
|
+
* relate to api-lib D" is answered by real code, never a declaration.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.findUnclassifiedApiDeps = findUnclassifiedApiDeps;
|
|
16
|
+
exports.describeUnclassifiedApiDep = describeUnclassifiedApiDep;
|
|
17
|
+
const role_resolver_1 = require("../role-resolver");
|
|
18
|
+
/** Roles that must justify every api-lib dependency (top-level runnables). */
|
|
19
|
+
const CHECKED_ROLES = ['server', 'client'];
|
|
20
|
+
/** The API class names owned by `apiLib`, sorted (for a stable fix hint). */
|
|
21
|
+
// webpieces-disable no-function-outside-class -- pure lookup helper, matches the validator-lib style
|
|
22
|
+
function apisOwnedBy(scan, apiLib) {
|
|
23
|
+
const names = [];
|
|
24
|
+
for (const info of scan.apiIndex.values()) {
|
|
25
|
+
if (info.owner === apiLib)
|
|
26
|
+
names.push(info.api);
|
|
27
|
+
}
|
|
28
|
+
return names.sort();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Every server/client ā api-lib edge for which the scan found NO implements and
|
|
32
|
+
* NO uses. `graph` must already carry `apiRelations` (call scanAndAttachApiRelations first).
|
|
33
|
+
*/
|
|
34
|
+
// webpieces-disable no-function-outside-class -- module entry point, mirrors findUnclassified-style validators
|
|
35
|
+
function findUnclassifiedApiDeps(graph, projectInfos, scan) {
|
|
36
|
+
const violations = [];
|
|
37
|
+
for (const projectName of Object.keys(graph)) {
|
|
38
|
+
const info = projectInfos.get(projectName);
|
|
39
|
+
if (!info)
|
|
40
|
+
continue;
|
|
41
|
+
const role = (0, role_resolver_1.resolveRole)(info).role;
|
|
42
|
+
if (role === null || !CHECKED_ROLES.includes(role))
|
|
43
|
+
continue;
|
|
44
|
+
const entry = graph[projectName];
|
|
45
|
+
for (const dep of entry.dependsOn) {
|
|
46
|
+
if (!scan.apiLibProjects.has(dep))
|
|
47
|
+
continue;
|
|
48
|
+
if (entry.apiRelations && entry.apiRelations[dep])
|
|
49
|
+
continue;
|
|
50
|
+
violations.push({ project: projectName, role, apiLib: dep, apis: apisOwnedBy(scan, dep) });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return violations;
|
|
54
|
+
}
|
|
55
|
+
/** Human-readable, fix-oriented report for one unclassified dependency. */
|
|
56
|
+
// webpieces-disable no-function-outside-class -- pure formatter, matches the validator-lib style
|
|
57
|
+
function describeUnclassifiedApiDep(violation) {
|
|
58
|
+
const apiHint = violation.apis.length > 0 ? violation.apis.join(', ') : 'the API';
|
|
59
|
+
const theApi = violation.apis[0] ?? 'TheApi';
|
|
60
|
+
const lines = [
|
|
61
|
+
` ā '${violation.project}' (role:${violation.role}) depends on api-lib '${violation.apiLib}' ` +
|
|
62
|
+
`but neither IMPLEMENTS nor USES any of its APIs (${apiHint}).`,
|
|
63
|
+
` Do ONE of:`,
|
|
64
|
+
` 1. USE it as a client: inject ClientHttpFactory (@webpieces/http-client-node) or ` +
|
|
65
|
+
`ClientHttpBrowserFactory (@webpieces/http-client-browser) and call ` +
|
|
66
|
+
`factory.createRpcClient(${theApi}, config); for a @PubSub api use ` +
|
|
67
|
+
`ClientCloudTasksFactory.createPubSubClient(${theApi}, config).`,
|
|
68
|
+
` 2. IMPLEMENT it: add a controller and register it ā ` +
|
|
69
|
+
`apiFactory.addRoutes(${theApi}, TheController).`,
|
|
70
|
+
` 3. If the dependency is unused, remove '${violation.apiLib}' from '${violation.project}'.`,
|
|
71
|
+
];
|
|
72
|
+
return lines.join('\n');
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=api-relations-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-relations-validator.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-relations-validator.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;AAkCH,0DAoBC;AAID,gEAgBC;AAtED,oDAA+C;AAG/C,8EAA8E;AAC9E,MAAM,aAAa,GAA0B,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAWlE,6EAA6E;AAC7E,qGAAqG;AACrG,SAAS,WAAW,CAAC,IAAmB,EAAE,MAAc;IACpD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QACxC,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,+GAA+G;AAC/G,SAAgB,uBAAuB,CACnC,KAAoB,EACpB,YAAsC,EACtC,IAAmB;IAEnB,MAAM,UAAU,GAAyB,EAAE,CAAC;IAC5C,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,IAAI,GAAG,IAAA,2BAAW,EAAC,IAAI,CAAC,CAAC,IAAI,CAAC;QACpC,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAS;QAE7D,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;QACjC,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YAC5C,IAAI,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC;gBAAE,SAAS;YAC5D,UAAU,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/F,CAAC;IACL,CAAC;IACD,OAAO,UAAU,CAAC;AACtB,CAAC;AAED,2EAA2E;AAC3E,iGAAiG;AACjG,SAAgB,0BAA0B,CAAC,SAA6B;IACpE,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAClF,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC;IAC7C,MAAM,KAAK,GAAG;QACV,QAAQ,SAAS,CAAC,OAAO,WAAW,SAAS,CAAC,IAAI,yBAAyB,SAAS,CAAC,MAAM,IAAI;YAC3F,oDAAoD,OAAO,IAAI;QACnE,iBAAiB;QACjB,0FAA0F;YACtF,qEAAqE;YACrE,2BAA2B,MAAM,mCAAmC;YACpE,8CAA8C,MAAM,YAAY;QACpE,6DAA6D;YACzD,wBAAwB,MAAM,mBAAmB;QACrD,kDAAkD,SAAS,CAAC,MAAM,WAAW,SAAS,CAAC,OAAO,IAAI;KACrG,CAAC;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["/**\n * API Relations Validator\n *\n * Enforces that the dependency graph is TRUTHFUL: a runnable project (role server\n * or client) that compiles against an api-lib must actually IMPLEMENT (serve) or\n * USE (call) at least one of its APIs. A dependency that is neither is almost\n * always a mistake ā a forgotten client wiring, a dead import, or a controller\n * that was never registered ā and it would draw an unexplained edge in the graph.\n *\n * The check reuses the same source scan that produces `apiRelations`, so \"does P\n * relate to api-lib D\" is answered by real code, never a declaration.\n */\n\nimport type { EnhancedGraph } from '../graph-sorter';\nimport { ProjectInfo } from '../project-info';\nimport { resolveRole } from '../role-resolver';\nimport { ApiScanResult } from './api-scanner';\n\n/** Roles that must justify every api-lib dependency (top-level runnables). */\nconst CHECKED_ROLES: ReadonlyArray<string> = ['server', 'client'];\n\n/** One `role:server`/`role:client` project that depends on an api-lib it neither implements nor uses. */\nexport interface UnclassifiedApiDep {\n project: string;\n role: string;\n apiLib: string;\n /** The API contract class names that api-lib exports (for the fix hint). */\n apis: string[];\n}\n\n/** The API class names owned by `apiLib`, sorted (for a stable fix hint). */\n// webpieces-disable no-function-outside-class -- pure lookup helper, matches the validator-lib style\nfunction apisOwnedBy(scan: ApiScanResult, apiLib: string): string[] {\n const names: string[] = [];\n for (const info of scan.apiIndex.values()) {\n if (info.owner === apiLib) names.push(info.api);\n }\n return names.sort();\n}\n\n/**\n * Every server/client ā api-lib edge for which the scan found NO implements and\n * NO uses. `graph` must already carry `apiRelations` (call scanAndAttachApiRelations first).\n */\n// webpieces-disable no-function-outside-class -- module entry point, mirrors findUnclassified-style validators\nexport function findUnclassifiedApiDeps(\n graph: EnhancedGraph,\n projectInfos: Map<string, ProjectInfo>,\n scan: ApiScanResult,\n): UnclassifiedApiDep[] {\n const violations: UnclassifiedApiDep[] = [];\n for (const projectName of Object.keys(graph)) {\n const info = projectInfos.get(projectName);\n if (!info) continue;\n const role = resolveRole(info).role;\n if (role === null || !CHECKED_ROLES.includes(role)) continue;\n\n const entry = graph[projectName];\n for (const dep of entry.dependsOn) {\n if (!scan.apiLibProjects.has(dep)) continue;\n if (entry.apiRelations && entry.apiRelations[dep]) continue;\n violations.push({ project: projectName, role, apiLib: dep, apis: apisOwnedBy(scan, dep) });\n }\n }\n return violations;\n}\n\n/** Human-readable, fix-oriented report for one unclassified dependency. */\n// webpieces-disable no-function-outside-class -- pure formatter, matches the validator-lib style\nexport function describeUnclassifiedApiDep(violation: UnclassifiedApiDep): string {\n const apiHint = violation.apis.length > 0 ? violation.apis.join(', ') : 'the API';\n const theApi = violation.apis[0] ?? 'TheApi';\n const lines = [\n ` ā '${violation.project}' (role:${violation.role}) depends on api-lib '${violation.apiLib}' ` +\n `but neither IMPLEMENTS nor USES any of its APIs (${apiHint}).`,\n ` Do ONE of:`,\n ` 1. USE it as a client: inject ClientHttpFactory (@webpieces/http-client-node) or ` +\n `ClientHttpBrowserFactory (@webpieces/http-client-browser) and call ` +\n `factory.createRpcClient(${theApi}, config); for a @PubSub api use ` +\n `ClientCloudTasksFactory.createPubSubClient(${theApi}, config).`,\n ` 2. IMPLEMENT it: add a controller and register it ā ` +\n `apiFactory.addRoutes(${theApi}, TheController).`,\n ` 3. If the dependency is unused, remove '${violation.apiLib}' from '${violation.project}'.`,\n ];\n return lines.join('\\n');\n}\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Relations model
|
|
3
|
+
*
|
|
4
|
+
* The typed classification of a compile-time dependency edge P -> apiLib in
|
|
5
|
+
* architecture/dependencies.json. Where the flat `dependsOn` only says "P depends
|
|
6
|
+
* on apiLib", `apiRelations[apiLib]` says WHY: which API contracts P IMPLEMENTS
|
|
7
|
+
* (serves, `class Ctrl extends XxxApi`) and which it USES (calls as a client,
|
|
8
|
+
* `factory.createRpcClient(XxxApi, ...)` / `createPubSubClient(...)`), each tagged
|
|
9
|
+
* with its transport.
|
|
10
|
+
*
|
|
11
|
+
* Interfaces + object literals here mirror the sibling runtime-graph.ts model ā
|
|
12
|
+
* these are serialization DTOs written verbatim into the committed JSON, and
|
|
13
|
+
* `implements`/`uses` are legal interface property names (they are reserved words
|
|
14
|
+
* only as binding identifiers, not as member names).
|
|
15
|
+
*/
|
|
16
|
+
/** Transport of an API contract: synchronous RPC (HTTP) vs fire-and-forget PubSub (Cloud Tasks). */
|
|
17
|
+
export type ApiTransport = 'rpc' | 'pubsub';
|
|
18
|
+
/**
|
|
19
|
+
* How a project relates to ONE api-lib it depends on:
|
|
20
|
+
* - `implements` ā it serves the api (a controller extends it)
|
|
21
|
+
* - `uses` ā it calls the api (generates a client)
|
|
22
|
+
* - `uses-implements` ā it does BOTH (implements some of the api-lib's contracts,
|
|
23
|
+
* uses others)
|
|
24
|
+
*/
|
|
25
|
+
export type ApiRelationKind = 'implements' | 'uses' | 'uses-implements';
|
|
26
|
+
/** One API class a project implements or uses, with its transport. */
|
|
27
|
+
export interface ApiRef {
|
|
28
|
+
api: string;
|
|
29
|
+
type: ApiTransport;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A project's relationship to ONE api-lib it depends on. Serialized verbatim into
|
|
33
|
+
* architecture/dependencies.json under `apiRelations[apiLibProjectName]`.
|
|
34
|
+
*/
|
|
35
|
+
export interface ApiRelation {
|
|
36
|
+
kind: ApiRelationKind;
|
|
37
|
+
implements: ApiRef[];
|
|
38
|
+
uses: ApiRef[];
|
|
39
|
+
}
|
|
40
|
+
/** apiLibProjectName -> relation. Attached to a GraphEntry as `apiRelations`. */
|
|
41
|
+
export type ProjectApiRelations = Record<string, ApiRelation>;
|
|
42
|
+
/** A discovered API contract class: its name, the api-lib project that owns it, and its transport. */
|
|
43
|
+
export interface ApiClassInfo {
|
|
44
|
+
api: string;
|
|
45
|
+
owner: string;
|
|
46
|
+
type: ApiTransport;
|
|
47
|
+
}
|
|
48
|
+
/** Derive the relation kind from the (possibly empty) implements/uses ref lists. */
|
|
49
|
+
export declare function deriveApiRelationKind(implementsRefs: ApiRef[], usesRefs: ApiRef[]): ApiRelationKind;
|
|
50
|
+
/** Stable-sort a ref list by api name so the committed JSON is deterministic. */
|
|
51
|
+
export declare function sortApiRefs(refs: ApiRef[]): ApiRef[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* API Relations model
|
|
4
|
+
*
|
|
5
|
+
* The typed classification of a compile-time dependency edge P -> apiLib in
|
|
6
|
+
* architecture/dependencies.json. Where the flat `dependsOn` only says "P depends
|
|
7
|
+
* on apiLib", `apiRelations[apiLib]` says WHY: which API contracts P IMPLEMENTS
|
|
8
|
+
* (serves, `class Ctrl extends XxxApi`) and which it USES (calls as a client,
|
|
9
|
+
* `factory.createRpcClient(XxxApi, ...)` / `createPubSubClient(...)`), each tagged
|
|
10
|
+
* with its transport.
|
|
11
|
+
*
|
|
12
|
+
* Interfaces + object literals here mirror the sibling runtime-graph.ts model ā
|
|
13
|
+
* these are serialization DTOs written verbatim into the committed JSON, and
|
|
14
|
+
* `implements`/`uses` are legal interface property names (they are reserved words
|
|
15
|
+
* only as binding identifiers, not as member names).
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.deriveApiRelationKind = deriveApiRelationKind;
|
|
19
|
+
exports.sortApiRefs = sortApiRefs;
|
|
20
|
+
/** Derive the relation kind from the (possibly empty) implements/uses ref lists. */
|
|
21
|
+
// webpieces-disable no-function-outside-class -- pure data helper for these serialization DTOs
|
|
22
|
+
function deriveApiRelationKind(implementsRefs, usesRefs) {
|
|
23
|
+
if (implementsRefs.length > 0 && usesRefs.length > 0)
|
|
24
|
+
return 'uses-implements';
|
|
25
|
+
if (implementsRefs.length > 0)
|
|
26
|
+
return 'implements';
|
|
27
|
+
return 'uses';
|
|
28
|
+
}
|
|
29
|
+
/** Stable-sort a ref list by api name so the committed JSON is deterministic. */
|
|
30
|
+
// webpieces-disable no-function-outside-class -- pure data helper for these serialization DTOs
|
|
31
|
+
function sortApiRefs(refs) {
|
|
32
|
+
return [...refs].sort((a, b) => a.api.localeCompare(b.api));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=api-relations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-relations.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-relations.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AA0CH,sDAIC;AAID,kCAEC;AAZD,oFAAoF;AACpF,+FAA+F;AAC/F,SAAgB,qBAAqB,CAAC,cAAwB,EAAE,QAAkB;IAC9E,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAC/E,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,YAAY,CAAC;IACnD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,iFAAiF;AACjF,+FAA+F;AAC/F,SAAgB,WAAW,CAAC,IAAc;IACtC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAChF,CAAC","sourcesContent":["/**\n * API Relations model\n *\n * The typed classification of a compile-time dependency edge P -> apiLib in\n * architecture/dependencies.json. Where the flat `dependsOn` only says \"P depends\n * on apiLib\", `apiRelations[apiLib]` says WHY: which API contracts P IMPLEMENTS\n * (serves, `class Ctrl extends XxxApi`) and which it USES (calls as a client,\n * `factory.createRpcClient(XxxApi, ...)` / `createPubSubClient(...)`), each tagged\n * with its transport.\n *\n * Interfaces + object literals here mirror the sibling runtime-graph.ts model ā\n * these are serialization DTOs written verbatim into the committed JSON, and\n * `implements`/`uses` are legal interface property names (they are reserved words\n * only as binding identifiers, not as member names).\n */\n\n/** Transport of an API contract: synchronous RPC (HTTP) vs fire-and-forget PubSub (Cloud Tasks). */\nexport type ApiTransport = 'rpc' | 'pubsub';\n\n/**\n * How a project relates to ONE api-lib it depends on:\n * - `implements` ā it serves the api (a controller extends it)\n * - `uses` ā it calls the api (generates a client)\n * - `uses-implements` ā it does BOTH (implements some of the api-lib's contracts,\n * uses others)\n */\nexport type ApiRelationKind = 'implements' | 'uses' | 'uses-implements';\n\n/** One API class a project implements or uses, with its transport. */\nexport interface ApiRef {\n api: string;\n type: ApiTransport;\n}\n\n/**\n * A project's relationship to ONE api-lib it depends on. Serialized verbatim into\n * architecture/dependencies.json under `apiRelations[apiLibProjectName]`.\n */\nexport interface ApiRelation {\n kind: ApiRelationKind;\n implements: ApiRef[];\n uses: ApiRef[];\n}\n\n/** apiLibProjectName -> relation. Attached to a GraphEntry as `apiRelations`. */\nexport type ProjectApiRelations = Record<string, ApiRelation>;\n\n/** A discovered API contract class: its name, the api-lib project that owns it, and its transport. */\nexport interface ApiClassInfo {\n api: string;\n owner: string;\n type: ApiTransport;\n}\n\n/** Derive the relation kind from the (possibly empty) implements/uses ref lists. */\n// webpieces-disable no-function-outside-class -- pure data helper for these serialization DTOs\nexport function deriveApiRelationKind(implementsRefs: ApiRef[], usesRefs: ApiRef[]): ApiRelationKind {\n if (implementsRefs.length > 0 && usesRefs.length > 0) return 'uses-implements';\n if (implementsRefs.length > 0) return 'implements';\n return 'uses';\n}\n\n/** Stable-sort a ref list by api name so the committed JSON is deterministic. */\n// webpieces-disable no-function-outside-class -- pure data helper for these serialization DTOs\nexport function sortApiRefs(refs: ApiRef[]): ApiRef[] {\n return [...refs].sort((a: ApiRef, b: ApiRef) => a.api.localeCompare(b.api));\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Usage Scanner
|
|
3
|
+
*
|
|
4
|
+
* Derives, by scanning real source (not a declaration file), how every project
|
|
5
|
+
* relates to the api-lib projects it depends on. This is the single source of
|
|
6
|
+
* truth for the `apiRelations` field in architecture/dependencies.json AND for
|
|
7
|
+
* the runtime microservice graph.
|
|
8
|
+
*
|
|
9
|
+
* Signals (all resolved through the TypeScript checker, so re-exports resolve):
|
|
10
|
+
* - IMPLEMENTS: `apiFactory.addRoutes(XxxApi, XxxController)` ā the registration
|
|
11
|
+
* that actually SERVES the contract over the wire. We deliberately
|
|
12
|
+
* do NOT use `class Ctrl extends XxxApi`: a class can extend an API
|
|
13
|
+
* as an in-process test double / simulator (e.g. Server2Simulator)
|
|
14
|
+
* without ever serving it ā only `addRoutes` proves a served route.
|
|
15
|
+
* - USES: `factory.createRpcClient(XxxApi, ...)` ā rpc client
|
|
16
|
+
* `factory.createPubSubClient(XxxApi, ...)` ā pubsub (Cloud Tasks) client
|
|
17
|
+
* An api-lib is DETECTED, not tagged: a project exporting an `abstract class`
|
|
18
|
+
* carrying `@ApiPath` owns that API. Its transport is `@PubSub` ā 'pubsub', else 'rpc'.
|
|
19
|
+
*/
|
|
20
|
+
import type { EnhancedGraph } from '../graph-sorter';
|
|
21
|
+
import { ProjectInfo } from '../project-info';
|
|
22
|
+
import { ApiClassInfo, ProjectApiRelations } from './api-relations';
|
|
23
|
+
/** The whole-workspace result of a scan. */
|
|
24
|
+
export interface ApiScanResult {
|
|
25
|
+
/** projectName -> { apiLibProject -> relation }; only projects with ā„1 relation appear. */
|
|
26
|
+
relationsByProject: Map<string, ProjectApiRelations>;
|
|
27
|
+
/** Every project that owns ā„1 API contract class. */
|
|
28
|
+
apiLibProjects: Set<string>;
|
|
29
|
+
/** apiClassName -> where it lives + its transport. */
|
|
30
|
+
apiIndex: Map<string, ApiClassInfo>;
|
|
31
|
+
}
|
|
32
|
+
/** Statically scans every project for its api-lib implements/uses relationships. */
|
|
33
|
+
export declare class ApiUsageScanner {
|
|
34
|
+
private readonly workspaceRoot;
|
|
35
|
+
private readonly projectInfos;
|
|
36
|
+
private readonly locator;
|
|
37
|
+
private readonly apiLibProjects;
|
|
38
|
+
private readonly apiIndex;
|
|
39
|
+
private readonly relationsByProject;
|
|
40
|
+
constructor(workspaceRoot: string, projectInfos: Map<string, ProjectInfo>);
|
|
41
|
+
scan(): ApiScanResult;
|
|
42
|
+
private scanProject;
|
|
43
|
+
private visit;
|
|
44
|
+
/** Register a project-owned API contract (abstract @ApiPath class) into the index. */
|
|
45
|
+
private recordApiClass;
|
|
46
|
+
private recordCall;
|
|
47
|
+
private addImplementsFromExpr;
|
|
48
|
+
/** Resolve an expression to the API contract it names, or null if it is not one. */
|
|
49
|
+
private apiInfoFromExpr;
|
|
50
|
+
/** {api, owner, type} when `cls` is an `abstract class` carrying `@ApiPath`, else null. */
|
|
51
|
+
private apiClassInfoFor;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Run the scan and attach the derived `apiRelations` onto each graph entry in
|
|
55
|
+
* place. Shared by `architecture:generate` (which then saves) and
|
|
56
|
+
* `architecture:validate-architecture-unchanged` (which regenerates in memory
|
|
57
|
+
* and must attach the SAME field, or it would see a phantom diff). Returns the
|
|
58
|
+
* full scan so callers (validators, runtime graph) can reuse the api index.
|
|
59
|
+
*/
|
|
60
|
+
export declare function scanAndAttachApiRelations(workspaceRoot: string, graph: EnhancedGraph, projectInfos: Map<string, ProjectInfo>): ApiScanResult;
|