@webpieces/nx-webpieces-rules 0.3.136 → 0.3.138
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 +15 -0
- package/package.json +5 -5
- package/src/executors/generate/executor.js +23 -0
- package/src/executors/generate/executor.js.map +1 -1
- package/src/executors/validate-runtime-architecture/executor.d.ts +21 -0
- package/src/executors/validate-runtime-architecture/executor.js +95 -0
- package/src/executors/validate-runtime-architecture/executor.js.map +1 -0
- package/src/executors/validate-runtime-architecture/schema.json +8 -0
- package/src/executors/validate-runtime-markers/executor.d.ts +25 -0
- package/src/executors/validate-runtime-markers/executor.js +90 -0
- package/src/executors/validate-runtime-markers/executor.js.map +1 -0
- package/src/executors/validate-runtime-markers/schema.json +8 -0
- package/src/executors/visualize-runtime/executor.d.ts +15 -0
- package/src/executors/visualize-runtime/executor.js +45 -0
- package/src/executors/visualize-runtime/executor.js.map +1 -0
- package/src/executors/visualize-runtime/schema.json +8 -0
- package/src/lib/runtime-config.d.ts +35 -0
- package/src/lib/runtime-config.js +51 -0
- package/src/lib/runtime-config.js.map +1 -0
- package/src/lib/runtime-cycles.d.ts +25 -0
- package/src/lib/runtime-cycles.js +74 -0
- package/src/lib/runtime-cycles.js.map +1 -0
- package/src/lib/runtime-graph.d.ts +47 -0
- package/src/lib/runtime-graph.js +186 -0
- package/src/lib/runtime-graph.js.map +1 -0
- package/src/lib/runtime-markers.d.ts +58 -0
- package/src/lib/runtime-markers.js +130 -0
- package/src/lib/runtime-markers.js.map +1 -0
- package/src/lib/runtime-visualizer.d.ts +16 -0
- package/src/lib/runtime-visualizer.js +88 -0
- package/src/lib/runtime-visualizer.js.map +1 -0
- package/src/plugin.d.ts +2 -0
- package/src/plugin.js +55 -53
- package/src/plugin.js.map +1 -1
- package/src/runtime-targets.d.ts +11 -0
- package/src/runtime-targets.js +50 -0
- package/src/runtime-targets.js.map +1 -0
package/executors.json
CHANGED
|
@@ -10,6 +10,21 @@
|
|
|
10
10
|
"schema": "./src/executors/visualize/schema.json",
|
|
11
11
|
"description": "Generate visual representations of the architecture graph"
|
|
12
12
|
},
|
|
13
|
+
"visualize-runtime": {
|
|
14
|
+
"implementation": "./src/executors/visualize-runtime/executor",
|
|
15
|
+
"schema": "./src/executors/visualize-runtime/schema.json",
|
|
16
|
+
"description": "Render the runtime microservice graph (runtime-dependencies.json)"
|
|
17
|
+
},
|
|
18
|
+
"validate-runtime-markers": {
|
|
19
|
+
"implementation": "./src/executors/validate-runtime-markers/executor",
|
|
20
|
+
"schema": "./src/executors/validate-runtime-markers/schema.json",
|
|
21
|
+
"description": "Per-project: validate live.json matches the project's api-project dependencies"
|
|
22
|
+
},
|
|
23
|
+
"validate-runtime-architecture": {
|
|
24
|
+
"implementation": "./src/executors/validate-runtime-architecture/executor",
|
|
25
|
+
"schema": "./src/executors/validate-runtime-architecture/schema.json",
|
|
26
|
+
"description": "Validate the runtime microservice graph: no disallowed cycles, unchanged vs committed"
|
|
27
|
+
},
|
|
13
28
|
"validate-no-architecture-cycles": {
|
|
14
29
|
"implementation": "./src/executors/validate-no-architecture-cycles/executor",
|
|
15
30
|
"schema": "./src/executors/validate-no-architecture-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.138",
|
|
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,10 +18,10 @@
|
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@webpieces/ai-hook-rules": "0.3.
|
|
22
|
-
"@webpieces/code-rules": "0.3.
|
|
23
|
-
"@webpieces/eslint-rules": "0.3.
|
|
24
|
-
"@webpieces/rules-config": "0.3.
|
|
21
|
+
"@webpieces/ai-hook-rules": "0.3.138",
|
|
22
|
+
"@webpieces/code-rules": "0.3.138",
|
|
23
|
+
"@webpieces/eslint-rules": "0.3.138",
|
|
24
|
+
"@webpieces/rules-config": "0.3.138",
|
|
25
25
|
"madge": "8.0.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
@@ -12,7 +12,28 @@ exports.default = runExecutor;
|
|
|
12
12
|
const graph_generator_1 = require("../../lib/graph-generator");
|
|
13
13
|
const graph_sorter_1 = require("../../lib/graph-sorter");
|
|
14
14
|
const graph_loader_1 = require("../../lib/graph-loader");
|
|
15
|
+
const runtime_markers_1 = require("../../lib/runtime-markers");
|
|
16
|
+
const runtime_graph_1 = require("../../lib/runtime-graph");
|
|
17
|
+
const runtime_config_1 = require("../../lib/runtime-config");
|
|
15
18
|
const toError_1 = require("../../toError");
|
|
19
|
+
/**
|
|
20
|
+
* Generate the runtime microservice graph alongside the compile-time graph, so
|
|
21
|
+
* one regenerate produces both committed files. Skipped when the
|
|
22
|
+
* runtime-architecture rule is OFF or no apiProjectPaths are configured.
|
|
23
|
+
*/
|
|
24
|
+
async function generateRuntimeGraph(workspaceRoot) {
|
|
25
|
+
const config = (0, runtime_config_1.loadRuntimeConfig)(workspaceRoot);
|
|
26
|
+
if (config.off || config.servicePaths.length === 0) {
|
|
27
|
+
console.log('⏭️ Runtime graph skipped (runtime-architecture OFF or no servicePaths)');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
console.log('📡 Generating runtime graph from service-contract.json files...');
|
|
31
|
+
const model = await (0, runtime_markers_1.buildWorkspaceModel)(workspaceRoot, config.apiProjectPaths, config.servicePaths);
|
|
32
|
+
const runtimeGraph = (0, runtime_graph_1.assembleRuntimeGraph)(model, workspaceRoot);
|
|
33
|
+
(0, runtime_graph_1.saveRuntimeGraph)(runtimeGraph, workspaceRoot);
|
|
34
|
+
const serviceCount = Object.keys(runtimeGraph.services).length;
|
|
35
|
+
console.log(`✅ Runtime graph saved (${serviceCount} services, ${runtimeGraph.runtimeEdges.length} runtime edges)`);
|
|
36
|
+
}
|
|
16
37
|
async function runExecutor(options, context) {
|
|
17
38
|
const graphPath = options.graphPath;
|
|
18
39
|
const workspaceRoot = context.root;
|
|
@@ -29,6 +50,8 @@ async function runExecutor(options, context) {
|
|
|
29
50
|
console.log('💾 Saving graph to architecture/dependencies.json...');
|
|
30
51
|
(0, graph_loader_1.saveGraph)(enhancedGraph, workspaceRoot, graphPath);
|
|
31
52
|
console.log('✅ Graph saved successfully');
|
|
53
|
+
// Step 4: Generate the runtime microservice graph at the same time
|
|
54
|
+
await generateRuntimeGraph(workspaceRoot);
|
|
32
55
|
// Print summary
|
|
33
56
|
const projectCount = Object.keys(enhancedGraph).length;
|
|
34
57
|
const levels = new Set(Object.values(enhancedGraph).map((e) => e.level));
|
|
@@ -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 { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { saveGraph } from '../../lib/graph-loader';\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: 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 4: 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 return { success: false };\n }\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate Runtime Architecture Executor (workspace)
|
|
3
|
+
*
|
|
4
|
+
* Two workspace-level checks on the runtime microservice graph:
|
|
5
|
+
* 1. No-cycles: every runtime cycle must be in the `allowedCycles` allowlist
|
|
6
|
+
* with an unexpired `until`; any other cycle fails the build.
|
|
7
|
+
* 2. Unchanged: the freshly-assembled graph must match the committed
|
|
8
|
+
* architecture/runtime-dependencies.json (run `architecture:generate`).
|
|
9
|
+
*
|
|
10
|
+
* On/off + a whole-rule grace window come from webpieces.config.json
|
|
11
|
+
* (rule: runtime-architecture).
|
|
12
|
+
*
|
|
13
|
+
* Usage: nx run microsvc:validate-runtime-architecture
|
|
14
|
+
*/
|
|
15
|
+
import type { ExecutorContext } from '@nx/devkit';
|
|
16
|
+
export interface ValidateRuntimeArchitectureOptions {
|
|
17
|
+
}
|
|
18
|
+
export interface ExecutorResult {
|
|
19
|
+
success: boolean;
|
|
20
|
+
}
|
|
21
|
+
export default function runExecutor(_options: ValidateRuntimeArchitectureOptions, context: ExecutorContext): Promise<ExecutorResult>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validate Runtime Architecture Executor (workspace)
|
|
4
|
+
*
|
|
5
|
+
* Two workspace-level checks on the runtime microservice graph:
|
|
6
|
+
* 1. No-cycles: every runtime cycle must be in the `allowedCycles` allowlist
|
|
7
|
+
* with an unexpired `until`; any other cycle fails the build.
|
|
8
|
+
* 2. Unchanged: the freshly-assembled graph must match the committed
|
|
9
|
+
* architecture/runtime-dependencies.json (run `architecture:generate`).
|
|
10
|
+
*
|
|
11
|
+
* On/off + a whole-rule grace window come from webpieces.config.json
|
|
12
|
+
* (rule: runtime-architecture).
|
|
13
|
+
*
|
|
14
|
+
* Usage: nx run microsvc:validate-runtime-architecture
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.default = runExecutor;
|
|
18
|
+
const runtime_markers_1 = require("../../lib/runtime-markers");
|
|
19
|
+
const runtime_graph_1 = require("../../lib/runtime-graph");
|
|
20
|
+
const runtime_cycles_1 = require("../../lib/runtime-cycles");
|
|
21
|
+
const runtime_config_1 = require("../../lib/runtime-config");
|
|
22
|
+
/** Allowlist keys (sorted-services -> entry) that are still in their grace window. */
|
|
23
|
+
function activeAllowedKeys(allowed) {
|
|
24
|
+
const nowSeconds = Date.now() / 1000;
|
|
25
|
+
const map = new Map();
|
|
26
|
+
for (const entry of allowed) {
|
|
27
|
+
const active = entry.until === undefined || nowSeconds < entry.until;
|
|
28
|
+
if (active)
|
|
29
|
+
map.set((0, runtime_cycles_1.cycleKey)(entry.services), entry);
|
|
30
|
+
}
|
|
31
|
+
return map;
|
|
32
|
+
}
|
|
33
|
+
/** Returns disallowed-cycle messages (empty = all cycles allowed or none). */
|
|
34
|
+
function checkCycles(graph, allowed) {
|
|
35
|
+
const cycles = (0, runtime_cycles_1.findRuntimeCycles)((0, runtime_graph_1.runtimeAdjacency)(graph));
|
|
36
|
+
if (cycles.length === 0)
|
|
37
|
+
return [];
|
|
38
|
+
const activeAllow = activeAllowedKeys(allowed);
|
|
39
|
+
const problems = [];
|
|
40
|
+
for (const cycle of cycles) {
|
|
41
|
+
const entry = activeAllow.get(cycle.key);
|
|
42
|
+
if (entry) {
|
|
43
|
+
console.log(`⏳ Allowed runtime cycle [${cycle.services.join(' <-> ')}] — ${entry.reason ?? 'no reason given'}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
problems.push(`runtime cycle: ${cycle.services.join(' -> ')} -> ${cycle.services[0]}`);
|
|
47
|
+
}
|
|
48
|
+
return problems;
|
|
49
|
+
}
|
|
50
|
+
/** Returns an unchanged-check message, or null if the committed graph matches. */
|
|
51
|
+
function checkUnchanged(workspaceRoot, current) {
|
|
52
|
+
if (!(0, runtime_graph_1.runtimeGraphFileExists)(workspaceRoot)) {
|
|
53
|
+
return 'No committed architecture/runtime-dependencies.json — run: nx run architecture:generate';
|
|
54
|
+
}
|
|
55
|
+
const saved = (0, runtime_graph_1.loadRuntimeGraph)(workspaceRoot);
|
|
56
|
+
if (saved && (0, runtime_graph_1.serializeRuntimeGraph)(saved) === (0, runtime_graph_1.serializeRuntimeGraph)(current))
|
|
57
|
+
return null;
|
|
58
|
+
return 'Runtime graph changed since last commit — run: nx run architecture:generate and commit the result';
|
|
59
|
+
}
|
|
60
|
+
async function runExecutor(_options, context) {
|
|
61
|
+
const workspaceRoot = context.root;
|
|
62
|
+
const config = (0, runtime_config_1.loadRuntimeConfig)(workspaceRoot);
|
|
63
|
+
if (config.off) {
|
|
64
|
+
console.log(`\n⏭️ Skipping ${runtime_config_1.RUNTIME_RULE_NAME} (mode: OFF)\n`);
|
|
65
|
+
return { success: true };
|
|
66
|
+
}
|
|
67
|
+
if (config.servicePaths.length === 0) {
|
|
68
|
+
console.log(`\n⏭️ ${runtime_config_1.RUNTIME_RULE_NAME}: no servicePaths configured — nothing to validate\n`);
|
|
69
|
+
return { success: true };
|
|
70
|
+
}
|
|
71
|
+
console.log('\n📡 Validating runtime microservice architecture\n');
|
|
72
|
+
const model = await (0, runtime_markers_1.buildWorkspaceModel)(workspaceRoot, config.apiProjectPaths, config.servicePaths);
|
|
73
|
+
const graph = (0, runtime_graph_1.assembleRuntimeGraph)(model, workspaceRoot);
|
|
74
|
+
const problems = checkCycles(graph, config.allowedCycles);
|
|
75
|
+
const unchanged = checkUnchanged(workspaceRoot, graph);
|
|
76
|
+
if (unchanged)
|
|
77
|
+
problems.push(unchanged);
|
|
78
|
+
for (const u of graph.unresolvedUses) {
|
|
79
|
+
console.log(`⚠️ ${u.service} uses "${u.api}" but no in-repo service implements it (external?)`);
|
|
80
|
+
}
|
|
81
|
+
if (problems.length === 0) {
|
|
82
|
+
console.log('✅ Runtime architecture valid (no disallowed cycles, graph unchanged)\n');
|
|
83
|
+
return { success: true };
|
|
84
|
+
}
|
|
85
|
+
console.error('\n❌ Runtime architecture validation failed:\n');
|
|
86
|
+
for (const p of problems)
|
|
87
|
+
console.error(` - ${p}`);
|
|
88
|
+
console.error('\nAllow a cycle temporarily via runtime-architecture.allowedCycles (services + reason + until) in webpieces.config.json.\n');
|
|
89
|
+
if ((0, runtime_config_1.isGraceActive)(config.ignoreModifiedUntilEpoch)) {
|
|
90
|
+
console.log(`⏳ Reported but not failing (ignoreModifiedUntilEpoch active, expires ${(0, runtime_config_1.epochDate)(config.ignoreModifiedUntilEpoch)}).\n`);
|
|
91
|
+
return { success: true };
|
|
92
|
+
}
|
|
93
|
+
return { success: false };
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +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 microsvc:validate-runtime-architecture\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { buildWorkspaceModel } from '../../lib/runtime-markers';\nimport {\n assembleRuntimeGraph,\n loadRuntimeGraph,\n runtimeAdjacency,\n runtimeGraphFileExists,\n serializeRuntimeGraph,\n} from '../../lib/runtime-graph';\nimport type { RuntimeGraph } from '../../lib/runtime-graph';\nimport { findRuntimeCycles, cycleKey } from '../../lib/runtime-cycles';\nimport type { AllowedCycle } from '../../lib/runtime-config';\nimport { loadRuntimeConfig, isGraceActive, epochDate, RUNTIME_RULE_NAME } from '../../lib/runtime-config';\n\nexport interface ValidateRuntimeArchitectureOptions {\n // Config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/** Allowlist keys (sorted-services -> entry) that are still in their grace window. */\nfunction activeAllowedKeys(allowed: AllowedCycle[]): Map<string, AllowedCycle> {\n const nowSeconds = Date.now() / 1000;\n const map = new Map<string, AllowedCycle>();\n for (const entry of allowed) {\n const active = entry.until === undefined || nowSeconds < entry.until;\n if (active) map.set(cycleKey(entry.services), entry);\n }\n return map;\n}\n\n/** Returns disallowed-cycle messages (empty = all cycles allowed or none). */\nfunction checkCycles(graph: RuntimeGraph, allowed: AllowedCycle[]): string[] {\n const cycles = findRuntimeCycles(runtimeAdjacency(graph));\n if (cycles.length === 0) return [];\n\n const activeAllow = activeAllowedKeys(allowed);\n const problems: string[] = [];\n for (const cycle of cycles) {\n const entry = activeAllow.get(cycle.key);\n if (entry) {\n console.log(`⏳ Allowed runtime cycle [${cycle.services.join(' <-> ')}] — ${entry.reason ?? 'no reason given'}`);\n continue;\n }\n problems.push(`runtime cycle: ${cycle.services.join(' -> ')} -> ${cycle.services[0]}`);\n }\n return problems;\n}\n\n/** Returns an unchanged-check message, or null if the committed graph matches. */\nfunction checkUnchanged(workspaceRoot: string, current: RuntimeGraph): string | null {\n if (!runtimeGraphFileExists(workspaceRoot)) {\n return 'No committed architecture/runtime-dependencies.json — run: nx run architecture:generate';\n }\n const saved = loadRuntimeGraph(workspaceRoot);\n if (saved && serializeRuntimeGraph(saved) === serializeRuntimeGraph(current)) return null;\n return 'Runtime graph changed since last commit — run: nx run architecture:generate and commit the result';\n}\n\nexport default async function runExecutor(\n _options: ValidateRuntimeArchitectureOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const config = loadRuntimeConfig(workspaceRoot);\n\n if (config.off) {\n console.log(`\\n⏭️ Skipping ${RUNTIME_RULE_NAME} (mode: OFF)\\n`);\n return { success: true };\n }\n if (config.servicePaths.length === 0) {\n console.log(`\\n⏭️ ${RUNTIME_RULE_NAME}: no servicePaths configured — nothing to validate\\n`);\n return { success: true };\n }\n\n console.log('\\n📡 Validating runtime microservice architecture\\n');\n const model = await buildWorkspaceModel(workspaceRoot, config.apiProjectPaths, config.servicePaths);\n const graph = assembleRuntimeGraph(model, workspaceRoot);\n\n const problems: string[] = checkCycles(graph, config.allowedCycles);\n const unchanged = checkUnchanged(workspaceRoot, graph);\n if (unchanged) problems.push(unchanged);\n\n for (const u of graph.unresolvedUses) {\n console.log(`⚠️ ${u.service} uses \"${u.api}\" but no in-repo service implements it (external?)`);\n }\n\n if (problems.length === 0) {\n console.log('✅ Runtime architecture valid (no disallowed cycles, graph unchanged)\\n');\n return { success: true };\n }\n\n console.error('\\n❌ Runtime architecture validation failed:\\n');\n for (const p of problems) console.error(` - ${p}`);\n console.error('\\nAllow a cycle temporarily via runtime-architecture.allowedCycles (services + reason + until) in webpieces.config.json.\\n');\n\n if (isGraceActive(config.ignoreModifiedUntilEpoch)) {\n console.log(`⏳ Reported but not failing (ignoreModifiedUntilEpoch active, expires ${epochDate(config.ignoreModifiedUntilEpoch!)}).\\n`);\n return { success: true };\n }\n return { success: false };\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"title": "Validate Runtime Architecture Executor",
|
|
4
|
+
"description": "Workspace: validate the runtime microservice graph has no disallowed cycles and matches the committed runtime-dependencies.json. Config from webpieces.config.json (runtime-architecture).",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {},
|
|
7
|
+
"required": []
|
|
8
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate Runtime Markers Executor (per-project)
|
|
3
|
+
*
|
|
4
|
+
* Validates ONE service's `service-contract.json` against its actual api-project
|
|
5
|
+
* dependencies (from the Nx graph), independently of all other projects:
|
|
6
|
+
*
|
|
7
|
+
* set(api-project deps) === set(implements ∪ uses declared in the contract)
|
|
8
|
+
*
|
|
9
|
+
* - Every service (root matches servicePaths) MUST have a service-contract.json
|
|
10
|
+
* -> FAIL if missing.
|
|
11
|
+
* - An api-project dependency not declared in the contract -> FAIL (undeclared).
|
|
12
|
+
* - A contract entry that is not an actual api dependency -> FAIL (phantom).
|
|
13
|
+
*
|
|
14
|
+
* Only service projects are checked; non-services pass. On/off + a whole-rule
|
|
15
|
+
* grace window come from webpieces.config.json (rule: runtime-architecture).
|
|
16
|
+
*
|
|
17
|
+
* Usage: nx run <project>:validate-runtime-markers
|
|
18
|
+
*/
|
|
19
|
+
import type { ExecutorContext } from '@nx/devkit';
|
|
20
|
+
export interface ValidateRuntimeMarkersOptions {
|
|
21
|
+
}
|
|
22
|
+
export interface ExecutorResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
}
|
|
25
|
+
export default function runExecutor(_options: ValidateRuntimeMarkersOptions, context: ExecutorContext): Promise<ExecutorResult>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validate Runtime Markers Executor (per-project)
|
|
4
|
+
*
|
|
5
|
+
* Validates ONE service's `service-contract.json` against its actual api-project
|
|
6
|
+
* dependencies (from the Nx graph), independently of all other projects:
|
|
7
|
+
*
|
|
8
|
+
* set(api-project deps) === set(implements ∪ uses declared in the contract)
|
|
9
|
+
*
|
|
10
|
+
* - Every service (root matches servicePaths) MUST have a service-contract.json
|
|
11
|
+
* -> FAIL if missing.
|
|
12
|
+
* - An api-project dependency not declared in the contract -> FAIL (undeclared).
|
|
13
|
+
* - A contract entry that is not an actual api dependency -> FAIL (phantom).
|
|
14
|
+
*
|
|
15
|
+
* Only service projects are checked; non-services pass. On/off + a whole-rule
|
|
16
|
+
* grace window come from webpieces.config.json (rule: runtime-architecture).
|
|
17
|
+
*
|
|
18
|
+
* Usage: nx run <project>:validate-runtime-markers
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.default = runExecutor;
|
|
22
|
+
const runtime_markers_1 = require("../../lib/runtime-markers");
|
|
23
|
+
const runtime_config_1 = require("../../lib/runtime-config");
|
|
24
|
+
function apiDepsOf(model, info) {
|
|
25
|
+
return info.deps.filter((dep) => model.projects.get(dep)?.isApi === true).sort();
|
|
26
|
+
}
|
|
27
|
+
async function runExecutor(_options, context) {
|
|
28
|
+
const workspaceRoot = context.root;
|
|
29
|
+
const config = (0, runtime_config_1.loadRuntimeConfig)(workspaceRoot);
|
|
30
|
+
if (config.off) {
|
|
31
|
+
console.log(`\n⏭️ Skipping ${runtime_config_1.RUNTIME_RULE_NAME} markers (mode: OFF)\n`);
|
|
32
|
+
return { success: true };
|
|
33
|
+
}
|
|
34
|
+
if (config.servicePaths.length === 0) {
|
|
35
|
+
return { success: true };
|
|
36
|
+
}
|
|
37
|
+
const projectName = context.projectName ?? '';
|
|
38
|
+
const model = await (0, runtime_markers_1.buildWorkspaceModel)(workspaceRoot, config.apiProjectPaths, config.servicePaths);
|
|
39
|
+
const info = model.projects.get(projectName);
|
|
40
|
+
if (!info || !info.isService) {
|
|
41
|
+
return { success: true };
|
|
42
|
+
}
|
|
43
|
+
const violations = collectViolations(model, info, workspaceRoot);
|
|
44
|
+
if (violations.length === 0) {
|
|
45
|
+
console.log(`\n✅ ${projectName}: service-contract.json matches api dependencies\n`);
|
|
46
|
+
return { success: true };
|
|
47
|
+
}
|
|
48
|
+
console.error(`\n❌ ${projectName}: service-contract.json does not match api dependencies:\n`);
|
|
49
|
+
for (const v of violations)
|
|
50
|
+
console.error(` - ${v}`);
|
|
51
|
+
console.error(`\nFix ${info.root}/service-contract.json so "implements" ∪ "uses" equals the api projects in deps.\n`);
|
|
52
|
+
if ((0, runtime_config_1.isGraceActive)(config.ignoreModifiedUntilEpoch)) {
|
|
53
|
+
console.log(`⏳ Reported but not failing (ignoreModifiedUntilEpoch active, expires ${(0, runtime_config_1.epochDate)(config.ignoreModifiedUntilEpoch)}).\n`);
|
|
54
|
+
return { success: true };
|
|
55
|
+
}
|
|
56
|
+
return { success: false };
|
|
57
|
+
}
|
|
58
|
+
/** Bidirectional-exact comparison of api deps vs the service contract. */
|
|
59
|
+
function collectViolations(model, info, workspaceRoot) {
|
|
60
|
+
const apiDeps = apiDepsOf(model, info);
|
|
61
|
+
const contract = (0, runtime_markers_1.readServiceContract)(workspaceRoot, info.root);
|
|
62
|
+
if (!contract) {
|
|
63
|
+
return [`missing service-contract.json (every service requires one)`];
|
|
64
|
+
}
|
|
65
|
+
const declaredNames = Array.from(new Set([...contract.implements, ...contract.uses]));
|
|
66
|
+
const resolved = (0, runtime_markers_1.resolvePackageNames)(model, declaredNames);
|
|
67
|
+
const declaredProjects = new Set(resolved.projects);
|
|
68
|
+
const apiDepSet = new Set(apiDeps);
|
|
69
|
+
const violations = [];
|
|
70
|
+
for (const pkg of resolved.unknown) {
|
|
71
|
+
violations.push(`declared "${pkg}" which is not a workspace package`);
|
|
72
|
+
}
|
|
73
|
+
for (const proj of resolved.projects) {
|
|
74
|
+
if (!model.projects.get(proj)?.isApi) {
|
|
75
|
+
violations.push(`declared "${proj}" which is not an api project`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const dep of apiDeps) {
|
|
79
|
+
if (!declaredProjects.has(dep)) {
|
|
80
|
+
violations.push(`api dependency "${dep}" is not declared in service-contract.json`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const proj of declaredProjects) {
|
|
84
|
+
if (model.projects.get(proj)?.isApi && !apiDepSet.has(proj)) {
|
|
85
|
+
violations.push(`service-contract.json declares "${proj}" but it is not an actual dependency`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return violations;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-runtime-markers/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Runtime Markers Executor (per-project)\n *\n * Validates ONE service's `service-contract.json` against its actual api-project\n * dependencies (from the Nx graph), independently of all other projects:\n *\n * set(api-project deps) === set(implements ∪ uses declared in the contract)\n *\n * - Every service (root matches servicePaths) MUST have a service-contract.json\n * -> FAIL if missing.\n * - An api-project dependency not declared in the contract -> FAIL (undeclared).\n * - A contract entry that is not an actual api dependency -> FAIL (phantom).\n *\n * Only service projects are checked; non-services pass. On/off + a whole-rule\n * grace window come from webpieces.config.json (rule: runtime-architecture).\n *\n * Usage: nx run <project>:validate-runtime-markers\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { buildWorkspaceModel, readServiceContract, resolvePackageNames } from '../../lib/runtime-markers';\nimport type { WorkspaceModel, ProjectInfo } from '../../lib/runtime-markers';\nimport { loadRuntimeConfig, isGraceActive, epochDate, RUNTIME_RULE_NAME } from '../../lib/runtime-config';\n\nexport interface ValidateRuntimeMarkersOptions {\n // Config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nfunction apiDepsOf(model: WorkspaceModel, info: ProjectInfo): string[] {\n return info.deps.filter((dep: string) => model.projects.get(dep)?.isApi === true).sort();\n}\n\nexport default async function runExecutor(\n _options: ValidateRuntimeMarkersOptions,\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} markers (mode: OFF)\\n`);\n return { success: true };\n }\n if (config.servicePaths.length === 0) {\n return { success: true };\n }\n\n const projectName = context.projectName ?? '';\n const model = await buildWorkspaceModel(workspaceRoot, config.apiProjectPaths, config.servicePaths);\n const info = model.projects.get(projectName);\n if (!info || !info.isService) {\n return { success: true };\n }\n\n const violations = collectViolations(model, info, workspaceRoot);\n if (violations.length === 0) {\n console.log(`\\n✅ ${projectName}: service-contract.json matches api dependencies\\n`);\n return { success: true };\n }\n\n console.error(`\\n❌ ${projectName}: service-contract.json does not match api dependencies:\\n`);\n for (const v of violations) console.error(` - ${v}`);\n console.error(`\\nFix ${info.root}/service-contract.json so \"implements\" ∪ \"uses\" equals the api projects in deps.\\n`);\n\n if (isGraceActive(config.ignoreModifiedUntilEpoch)) {\n console.log(`⏳ Reported but not failing (ignoreModifiedUntilEpoch active, expires ${epochDate(config.ignoreModifiedUntilEpoch!)}).\\n`);\n return { success: true };\n }\n return { success: false };\n}\n\n/** Bidirectional-exact comparison of api deps vs the service contract. */\nfunction collectViolations(model: WorkspaceModel, info: ProjectInfo, workspaceRoot: string): string[] {\n const apiDeps = apiDepsOf(model, info);\n const contract = readServiceContract(workspaceRoot, info.root);\n\n if (!contract) {\n return [`missing service-contract.json (every service requires one)`];\n }\n\n const declaredNames = Array.from(new Set([...contract.implements, ...contract.uses]));\n const resolved = resolvePackageNames(model, declaredNames);\n const declaredProjects = new Set(resolved.projects);\n const apiDepSet = new Set(apiDeps);\n\n const violations: string[] = [];\n for (const pkg of resolved.unknown) {\n violations.push(`declared \"${pkg}\" which is not a workspace package`);\n }\n for (const proj of resolved.projects) {\n if (!model.projects.get(proj)?.isApi) {\n violations.push(`declared \"${proj}\" which is not an api project`);\n }\n }\n for (const dep of apiDeps) {\n if (!declaredProjects.has(dep)) {\n violations.push(`api dependency \"${dep}\" is not declared in service-contract.json`);\n }\n }\n for (const proj of declaredProjects) {\n if (model.projects.get(proj)?.isApi && !apiDepSet.has(proj)) {\n violations.push(`service-contract.json declares \"${proj}\" but it is not an actual dependency`);\n }\n }\n return violations;\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"title": "Validate Runtime Markers Executor",
|
|
4
|
+
"description": "Per-project: validate live.json matches the project's api-project dependencies. Config from webpieces.config.json (runtime-architecture).",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {},
|
|
7
|
+
"required": []
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visualize Runtime Executor
|
|
3
|
+
*
|
|
4
|
+
* Renders the runtime microservice graph (committed
|
|
5
|
+
* architecture/runtime-dependencies.json) to DOT + HTML and opens it.
|
|
6
|
+
*
|
|
7
|
+
* Usage: nx run microsvc:visualize
|
|
8
|
+
*/
|
|
9
|
+
import type { ExecutorContext } from '@nx/devkit';
|
|
10
|
+
export interface VisualizeRuntimeOptions {
|
|
11
|
+
}
|
|
12
|
+
export interface ExecutorResult {
|
|
13
|
+
success: boolean;
|
|
14
|
+
}
|
|
15
|
+
export default function runExecutor(_options: VisualizeRuntimeOptions, context: ExecutorContext): Promise<ExecutorResult>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Visualize Runtime Executor
|
|
4
|
+
*
|
|
5
|
+
* Renders the runtime microservice graph (committed
|
|
6
|
+
* architecture/runtime-dependencies.json) to DOT + HTML and opens it.
|
|
7
|
+
*
|
|
8
|
+
* Usage: nx run microsvc:visualize
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.default = runExecutor;
|
|
12
|
+
const runtime_graph_1 = require("../../lib/runtime-graph");
|
|
13
|
+
const runtime_visualizer_1 = require("../../lib/runtime-visualizer");
|
|
14
|
+
const graph_visualizer_1 = require("../../lib/graph-visualizer");
|
|
15
|
+
const toError_1 = require("../../toError");
|
|
16
|
+
async function runExecutor(_options, context) {
|
|
17
|
+
const workspaceRoot = context.root;
|
|
18
|
+
console.log('\n🎨 Runtime Microservice Visualization\n');
|
|
19
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
20
|
+
try {
|
|
21
|
+
const graph = (0, runtime_graph_1.loadRuntimeGraph)(workspaceRoot);
|
|
22
|
+
if (!graph) {
|
|
23
|
+
console.error('❌ No architecture/runtime-dependencies.json found');
|
|
24
|
+
console.error(' Run: nx run architecture:generate first');
|
|
25
|
+
return { success: false };
|
|
26
|
+
}
|
|
27
|
+
const vizPaths = (0, runtime_visualizer_1.writeRuntimeVisualization)(graph, workspaceRoot);
|
|
28
|
+
console.log(`✅ Generated: ${vizPaths.dotPath}`);
|
|
29
|
+
console.log(`✅ Generated: ${vizPaths.htmlPath}`);
|
|
30
|
+
console.log('\n🌐 Opening visualization in browser...');
|
|
31
|
+
if ((0, graph_visualizer_1.openVisualization)(vizPaths.htmlPath)) {
|
|
32
|
+
console.log('✅ Browser opened');
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.log(`⚠️ Could not auto-open. Open manually: ${vizPaths.htmlPath}`);
|
|
36
|
+
}
|
|
37
|
+
return { success: true };
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const error = (0, toError_1.toError)(err);
|
|
41
|
+
console.error('❌ Runtime visualization failed:', error.message);
|
|
42
|
+
return { success: false };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/visualize-runtime/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAgBH,8BAkCC;AA/CD,2DAA2D;AAC3D,qEAAyE;AACzE,iEAA+D;AAC/D,2CAAwC;AAUzB,KAAK,UAAU,WAAW,CACrC,QAAiC,EACjC,OAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;IAEzD,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,IAAA,gCAAgB,EAAC,aAAa,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;YACnE,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QAED,MAAM,QAAQ,GAAG,IAAA,8CAAyB,EAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QACjE,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEjD,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,IAAI,IAAA,oCAAiB,EAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,GAAG,CAAC,2CAA2C,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChF,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAChE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Visualize Runtime Executor\n *\n * Renders the runtime microservice graph (committed\n * architecture/runtime-dependencies.json) to DOT + HTML and opens it.\n *\n * Usage: nx run microsvc:visualize\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadRuntimeGraph } from '../../lib/runtime-graph';\nimport { writeRuntimeVisualization } from '../../lib/runtime-visualizer';\nimport { openVisualization } from '../../lib/graph-visualizer';\nimport { toError } from '../../toError';\n\nexport interface VisualizeRuntimeOptions {\n // No options.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n _options: VisualizeRuntimeOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n console.log('\\n🎨 Runtime Microservice Visualization\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const graph = loadRuntimeGraph(workspaceRoot);\n if (!graph) {\n console.error('❌ No architecture/runtime-dependencies.json found');\n console.error(' Run: nx run architecture:generate first');\n return { success: false };\n }\n\n const vizPaths = writeRuntimeVisualization(graph, workspaceRoot);\n console.log(`✅ Generated: ${vizPaths.dotPath}`);\n console.log(`✅ Generated: ${vizPaths.htmlPath}`);\n\n console.log('\\n🌐 Opening visualization in browser...');\n if (openVisualization(vizPaths.htmlPath)) {\n console.log('✅ Browser opened');\n } else {\n console.log(`⚠️ Could not auto-open. Open manually: ${vizPaths.htmlPath}`);\n }\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('❌ Runtime visualization failed:', error.message);\n return { success: false };\n }\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Config
|
|
3
|
+
*
|
|
4
|
+
* Loads the `runtime-architecture` rule from webpieces.config.json and exposes
|
|
5
|
+
* typed accessors shared by the generate + validate + visualize executors.
|
|
6
|
+
*
|
|
7
|
+
* "runtime-architecture": {
|
|
8
|
+
* "mode": "ON", // "OFF" disables the whole feature
|
|
9
|
+
* "apiProjectPaths": ["libraries/apis/*"],
|
|
10
|
+
* "ignoreModifiedUntilEpoch": 0, // whole-rule punt (epoch seconds)
|
|
11
|
+
* "allowedCycles": [ { "services": ["a","b"], "reason": "...", "until": 1771931925 } ]
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
14
|
+
export declare const RUNTIME_RULE_NAME = "runtime-architecture";
|
|
15
|
+
export interface AllowedCycle {
|
|
16
|
+
services: string[];
|
|
17
|
+
reason?: string;
|
|
18
|
+
until?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface RuntimeRuleConfig {
|
|
21
|
+
off: boolean;
|
|
22
|
+
apiProjectPaths: string[];
|
|
23
|
+
servicePaths: string[];
|
|
24
|
+
ignoreModifiedUntilEpoch?: number;
|
|
25
|
+
allowedCycles: AllowedCycle[];
|
|
26
|
+
}
|
|
27
|
+
/** Load the runtime-architecture rule config (with safe defaults). */
|
|
28
|
+
export declare function loadRuntimeConfig(workspaceRoot: string): RuntimeRuleConfig;
|
|
29
|
+
/**
|
|
30
|
+
* Whole-rule grace window: while now < epoch, failures are reported but do not
|
|
31
|
+
* fail the build (warn). Mirrors the other webpieces rules.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isGraceActive(epoch: number | undefined): boolean;
|
|
34
|
+
/** Format the epoch as an ISO date for log messages. */
|
|
35
|
+
export declare function epochDate(epoch: number): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Runtime Config
|
|
4
|
+
*
|
|
5
|
+
* Loads the `runtime-architecture` rule from webpieces.config.json and exposes
|
|
6
|
+
* typed accessors shared by the generate + validate + visualize executors.
|
|
7
|
+
*
|
|
8
|
+
* "runtime-architecture": {
|
|
9
|
+
* "mode": "ON", // "OFF" disables the whole feature
|
|
10
|
+
* "apiProjectPaths": ["libraries/apis/*"],
|
|
11
|
+
* "ignoreModifiedUntilEpoch": 0, // whole-rule punt (epoch seconds)
|
|
12
|
+
* "allowedCycles": [ { "services": ["a","b"], "reason": "...", "until": 1771931925 } ]
|
|
13
|
+
* }
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.RUNTIME_RULE_NAME = void 0;
|
|
17
|
+
exports.loadRuntimeConfig = loadRuntimeConfig;
|
|
18
|
+
exports.isGraceActive = isGraceActive;
|
|
19
|
+
exports.epochDate = epochDate;
|
|
20
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
21
|
+
exports.RUNTIME_RULE_NAME = 'runtime-architecture';
|
|
22
|
+
function isUsableCycle(cycle) {
|
|
23
|
+
return Array.isArray(cycle.services) && cycle.services.length > 0;
|
|
24
|
+
}
|
|
25
|
+
/** Load the runtime-architecture rule config (with safe defaults). */
|
|
26
|
+
function loadRuntimeConfig(workspaceRoot) {
|
|
27
|
+
const shared = (0, rules_config_1.loadConfig)(workspaceRoot);
|
|
28
|
+
const rule = shared.rules.get(exports.RUNTIME_RULE_NAME);
|
|
29
|
+
const raw = (rule?.options ?? {});
|
|
30
|
+
return {
|
|
31
|
+
off: rule?.isOff ?? false,
|
|
32
|
+
apiProjectPaths: Array.isArray(raw.apiProjectPaths) ? raw.apiProjectPaths : [],
|
|
33
|
+
servicePaths: Array.isArray(raw.servicePaths) ? raw.servicePaths : [],
|
|
34
|
+
ignoreModifiedUntilEpoch: typeof raw.ignoreModifiedUntilEpoch === 'number' ? raw.ignoreModifiedUntilEpoch : undefined,
|
|
35
|
+
allowedCycles: Array.isArray(raw.allowedCycles) ? raw.allowedCycles.filter(isUsableCycle) : [],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whole-rule grace window: while now < epoch, failures are reported but do not
|
|
40
|
+
* fail the build (warn). Mirrors the other webpieces rules.
|
|
41
|
+
*/
|
|
42
|
+
function isGraceActive(epoch) {
|
|
43
|
+
if (epoch === undefined)
|
|
44
|
+
return false;
|
|
45
|
+
return Date.now() / 1000 < epoch;
|
|
46
|
+
}
|
|
47
|
+
/** Format the epoch as an ISO date for log messages. */
|
|
48
|
+
function epochDate(epoch) {
|
|
49
|
+
return new Date(epoch * 1000).toISOString().split('T')[0];
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=runtime-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-config.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-config.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAqCH,8CAYC;AAMD,sCAGC;AAGD,8BAEC;AA7DD,0DAAqD;AAExC,QAAA,iBAAiB,GAAG,sBAAsB,CAAC;AA4BxD,SAAS,aAAa,CAAC,KAAmB;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;AACtE,CAAC;AAED,sEAAsE;AACtE,SAAgB,iBAAiB,CAAC,aAAqB;IACnD,MAAM,MAAM,GAAG,IAAA,yBAAU,EAAC,aAAa,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAiB,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,CAAmB,CAAC;IACpD,OAAO;QACH,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,KAAK;QACzB,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE;QAC9E,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE;QACrE,wBAAwB,EACpB,OAAO,GAAG,CAAC,wBAAwB,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC,SAAS;QAC/F,aAAa,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE;KACjG,CAAC;AACN,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAAC,KAAyB;IACnD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,KAAK,CAAC;AACrC,CAAC;AAED,wDAAwD;AACxD,SAAgB,SAAS,CAAC,KAAa;IACnC,OAAO,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC","sourcesContent":["/**\n * Runtime Config\n *\n * Loads the `runtime-architecture` rule from webpieces.config.json and exposes\n * typed accessors shared by the generate + validate + visualize executors.\n *\n * \"runtime-architecture\": {\n * \"mode\": \"ON\", // \"OFF\" disables the whole feature\n * \"apiProjectPaths\": [\"libraries/apis/*\"],\n * \"ignoreModifiedUntilEpoch\": 0, // whole-rule punt (epoch seconds)\n * \"allowedCycles\": [ { \"services\": [\"a\",\"b\"], \"reason\": \"...\", \"until\": 1771931925 } ]\n * }\n */\n\nimport { loadConfig } from '@webpieces/rules-config';\n\nexport const RUNTIME_RULE_NAME = 'runtime-architecture';\n\nexport interface AllowedCycle {\n services: string[];\n reason?: string;\n until?: number;\n}\n\nexport interface RuntimeRuleConfig {\n off: boolean;\n apiProjectPaths: string[];\n servicePaths: string[];\n ignoreModifiedUntilEpoch?: number;\n allowedCycles: AllowedCycle[];\n}\n\n/**\n * Typed view of the opaque webpieces.config.json option bag for this rule. The\n * config is trusted (it is the workspace's own file), so we cast once here and\n * defensively narrow arrays/numbers rather than threading `unknown` everywhere.\n */\ninterface RuntimeRuleRaw {\n apiProjectPaths?: string[];\n servicePaths?: string[];\n ignoreModifiedUntilEpoch?: number;\n allowedCycles?: AllowedCycle[];\n}\n\nfunction isUsableCycle(cycle: AllowedCycle): boolean {\n return Array.isArray(cycle.services) && cycle.services.length > 0;\n}\n\n/** Load the runtime-architecture rule config (with safe defaults). */\nexport function loadRuntimeConfig(workspaceRoot: string): RuntimeRuleConfig {\n const shared = loadConfig(workspaceRoot);\n const rule = shared.rules.get(RUNTIME_RULE_NAME);\n const raw = (rule?.options ?? {}) as RuntimeRuleRaw;\n return {\n off: rule?.isOff ?? false,\n apiProjectPaths: Array.isArray(raw.apiProjectPaths) ? raw.apiProjectPaths : [],\n servicePaths: Array.isArray(raw.servicePaths) ? raw.servicePaths : [],\n ignoreModifiedUntilEpoch:\n typeof raw.ignoreModifiedUntilEpoch === 'number' ? raw.ignoreModifiedUntilEpoch : undefined,\n allowedCycles: Array.isArray(raw.allowedCycles) ? raw.allowedCycles.filter(isUsableCycle) : [],\n };\n}\n\n/**\n * Whole-rule grace window: while now < epoch, failures are reported but do not\n * fail the build (warn). Mirrors the other webpieces rules.\n */\nexport function isGraceActive(epoch: number | undefined): boolean {\n if (epoch === undefined) return false;\n return Date.now() / 1000 < epoch;\n}\n\n/** Format the epoch as an ISO date for log messages. */\nexport function epochDate(epoch: number): string {\n return new Date(epoch * 1000).toISOString().split('T')[0];\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Cycles
|
|
3
|
+
*
|
|
4
|
+
* Enumerates ALL cycles in the runtime service graph using Tarjan's
|
|
5
|
+
* strongly-connected-components algorithm. The existing graph-sorter `findCycle`
|
|
6
|
+
* reports only one cycle; runtime validation needs every cycle so each can be
|
|
7
|
+
* checked against the per-cycle allowlist independently.
|
|
8
|
+
*
|
|
9
|
+
* A cycle is any SCC with more than one node, or a single node with a self-edge.
|
|
10
|
+
* Each cycle is keyed by its sorted, comma-joined node names so it can be
|
|
11
|
+
* matched against an `allowedCycles` entry regardless of traversal order.
|
|
12
|
+
*/
|
|
13
|
+
export interface RuntimeCycle {
|
|
14
|
+
/** Sorted service names participating in the cycle. */
|
|
15
|
+
services: string[];
|
|
16
|
+
/** Canonical key: services sorted then joined with ",". */
|
|
17
|
+
key: string;
|
|
18
|
+
}
|
|
19
|
+
/** Canonical key for a set of service names (order-independent). */
|
|
20
|
+
export declare function cycleKey(services: string[]): string;
|
|
21
|
+
/**
|
|
22
|
+
* Find every cycle in a directed graph via Tarjan's SCC algorithm.
|
|
23
|
+
* `graph[node]` lists the nodes `node` points to.
|
|
24
|
+
*/
|
|
25
|
+
export declare function findRuntimeCycles(graph: Record<string, string[]>): RuntimeCycle[];
|