@webpieces/nx-webpieces-rules 0.4.920 โ 0.4.922
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/src/executors/generate/executor.d.ts +7 -0
- package/src/executors/generate/executor.js +13 -5
- package/src/executors/generate/executor.js.map +1 -1
- package/src/executors/validate-architecture-unchanged/executor.d.ts +1 -20
- package/src/executors/validate-architecture-unchanged/executor.js +2 -45
- package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
- package/src/executors/visualize/executor.d.ts +8 -1
- package/src/executors/visualize/executor.js +11 -3
- package/src/executors/visualize/executor.js.map +1 -1
- package/src/executors/visualize-runtime/executor.js +2 -1
- package/src/executors/visualize-runtime/executor.js.map +1 -1
- package/src/executors/visualize-runtime-goal/executor.js +2 -1
- package/src/executors/visualize-runtime-goal/executor.js.map +1 -1
- package/src/lib/current-architecture.d.ts +26 -0
- package/src/lib/current-architecture.js +56 -0
- package/src/lib/current-architecture.js.map +1 -0
- package/src/lib/graph-edge-diff.d.ts +115 -0
- package/src/lib/graph-edge-diff.js +263 -0
- package/src/lib/graph-edge-diff.js.map +1 -0
- package/src/lib/graph-impact.d.ts +21 -4
- package/src/lib/graph-impact.js +33 -6
- package/src/lib/graph-impact.js.map +1 -1
- package/src/lib/graph-legend.d.ts +4 -0
- package/src/lib/graph-legend.js +14 -1
- package/src/lib/graph-legend.js.map +1 -1
- package/src/lib/graph-mode-state.client.d.ts +64 -0
- package/src/lib/graph-mode-state.client.js +209 -3
- package/src/lib/graph-mode-state.client.js.map +1 -1
- package/src/lib/graph-navigation.js +4 -2
- package/src/lib/graph-navigation.js.map +1 -1
- package/src/lib/graph-page-shell.d.ts +3 -1
- package/src/lib/graph-page-shell.js +3 -0
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +3 -2
- package/src/lib/graph-visualizer.client.js +26 -13
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/runtime-html-page.js +2 -2
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/native-actions.js +1 -1
- package/src/native-actions.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/visualize-runtime-goal/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/visualize-runtime-goal/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Visualize Runtime Goal Executor (#1239)\n *\n * Renders the hand-authored runtime PROPOSAL, architecture/runtime-dependencies-goal.json, with the\n * same shared runtime renderer as visualize-runtime, into its own files:\n * tmp/webpieces/runtime-architecture-goal.{dot,html}. It never generates anything from source first,\n * never writes either JSON input, and never overwrites the current page.\n *\n * The page carries Goal vs Current when the saved current graph is usable; otherwise that mode says\n * why. A missing goal fails with its expected path and how to start one โ the current graph is never\n * rendered in its place.\n *\n * `--prepare` is the one explicit, WRITING step: it recomputes the goal's derived levels, self-queue\n * flags, contract queues and maxLevel with the generator's own level machinery, saves the goal, and\n * then views it.\n *\n * Usage: nx run architecture:visualize-runtime-goal [--prepare]\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ExecutorContext } from '@nx/devkit';\nimport { RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport { DEFAULT_GRAPH_PATH } from '../../lib/graph-loader';\nimport { ImpactRefresh, ImpactSidecar, VISUALIZE_IMPACT_KINDS } from '../../lib/graph-impact';\nimport { LiveGraphSource } from '../../lib/graph-edge-diff';\nimport { SavedSnapshot } from '../../lib/saved-snapshot';\nimport { writeRuntimeVisualization, RuntimeVizOptions, RuntimePageKind } from '../../lib/runtime-visualizer';\nimport { loadRuntimeConfig } from '../../lib/runtime-config';\nimport { GraphVisualizer } from '../../lib/graph-visualizer';\nimport { RuntimeComparisonInputs } from '../../lib/runtime-goal-view';\nimport { RuntimeGoalFile, RuntimeGoalProvenance, RUNTIME_GOAL_PREPARE_COMMAND } from '../../lib/runtime-goal-file';\nimport type { RuntimeGraph } from '../../lib/runtime-graph-model';\nimport { toError } from '../../toError';\n\nexport interface VisualizeRuntimeGoalOptions {\n /** Recompute and SAVE the goal's derived levels before viewing. Viewing alone never writes. */\n prepare?: boolean;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n// webpieces-disable no-function-outside-class -- Nx executor entry point: Nx requires a default-exported function\nexport default async function runExecutor(\n options: VisualizeRuntimeGoalOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const goalFile = new RuntimeGoalFile();\n const snapshot = new SavedSnapshot();\n\n console.log('\\n๐ฏ Runtime Goal Visualization\\n');\n console.log(new RuntimeGoalProvenance().message());\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Nx command rendering boundary; success:false propagates failure to Nx\n try {\n const goal = options.prepare === true ? prepared(goalFile, workspaceRoot) : goalFile.load(workspaceRoot);\n reportDrift(goalFile, goal);\n const config = loadRuntimeConfig(workspaceRoot);\n const projects = fs.existsSync(path.join(workspaceRoot, DEFAULT_GRAPH_PATH)) ? snapshot.loadProjects(workspaceRoot, DEFAULT_GRAPH_PATH).projects : null;\n const vizOptions = new RuntimeVizOptions(config.showExternalNodes, projects, workspaceRoot, RuntimePageKind.GOAL);\n const inputs = new RuntimeComparisonInputs(snapshot, goalFile);\n const comparison = inputs.view(inputs.current(workspaceRoot), goal, vizOptions);\n console.log(comparison.available ? `Goal vs Current: ${comparison.summary}` : `Goal vs Current unavailable: ${comparison.reason}`);\n const vizPaths = writeRuntimeVisualization(goal, workspaceRoot, 'Goal / Proposed Runtime Architecture', vizOptions, comparison);\n console.log(`โ
Generated: ${vizPaths.dotPath}`);\n console.log(`โ
Generated: ${vizPaths.htmlPath}`);\n if (projects !== null) console.log(await new ImpactRefresh().run(path.dirname(vizPaths.htmlPath), workspaceRoot, projects, VISUALIZE_IMPACT_KINDS, new LiveGraphSource()));\n else { new ImpactSidecar().remove(path.dirname(vizPaths.htmlPath)); console.log('Impact and framework metadata unavailable: no saved project graph. Proposed services carry their goal facts only.'); }\n\n console.log('\\n๐ Opening visualization in browser...');\n if (new GraphVisualizer().openVisualization(vizPaths.htmlPath)) console.log('โ
Browser opened');\n else console.log(`โ ๏ธ Could not auto-open. Open manually: ${vizPaths.htmlPath}`);\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n const rendered = error instanceof RuleFailError ? renderRuleFailForHuman(error) : error.message;\n console.error(`โ Runtime goal visualization failed for ${goalFile.goalPath}:`, rendered);\n return { success: false };\n }\n}\n\n// webpieces-disable no-function-outside-class -- executor step helper, sibling of runExecutor\nfunction prepared(goalFile: RuntimeGoalFile, workspaceRoot: string): RuntimeGraph {\n const preparation = goalFile.prepare(workspaceRoot);\n if (!preparation.written) console.log(`โ
${goalFile.goalPath} derived facts already consistent; nothing written.`);\n else if (preparation.changes.length === 0) console.log(`โ๏ธ Prepared ${goalFile.goalPath}: rewrote explanatory levelBecause text only.`);\n else console.log(`โ๏ธ Prepared ${goalFile.goalPath}:\\n ${preparation.changes.join('\\n ')}`);\n return preparation.graph;\n}\n\n// webpieces-disable no-function-outside-class -- executor step helper, sibling of runExecutor\nfunction reportDrift(goalFile: RuntimeGoalFile, goal: RuntimeGraph): void {\n const drift = goalFile.drift(goal);\n if (drift.cycle !== null) {\n console.warn(`โ ๏ธ ${drift.cycle}\\n Rendered with its saved levels. ${goalFile.cycleOption().text}`);\n return;\n }\n if (drift.differences.length === 0) return;\n console.warn(\n `โ ๏ธ ${goalFile.goalPath} saves derived facts that disagree with its relationships (rendered as saved):\\n ` +\n `${drift.differences.join('\\n ')}\\n Recompute them explicitly: ${RUNTIME_GOAL_PREPARE_COMMAND}`,\n );\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The architecture graph as the CURRENT source says it is, built live by the same pipeline
|
|
3
|
+
* `architecture:generate` runs โ never read back from the committed architecture/*.json. Shared by
|
|
4
|
+
* `validate-architecture-unchanged` (which diffs it against the committed file) and the Impact view's
|
|
5
|
+
* edge changes (#1264, which diff it against the fork point's committed file).
|
|
6
|
+
*/
|
|
7
|
+
import type { EnhancedGraph } from './graph-sorter';
|
|
8
|
+
import type { ExternalSystemDecls } from './api-usage/api-relations';
|
|
9
|
+
import type { ApiContractFileRefs } from './api-contract-files';
|
|
10
|
+
import type { RemoteServices } from './remote-services';
|
|
11
|
+
/**
|
|
12
|
+
* Build the current dependency graph exactly as the generator does: reduce the nx
|
|
13
|
+
* graph, sort into levels, enrich with metadata, and attach the derived
|
|
14
|
+
* apiRelations โ so this validator compares like-for-like against the committed file.
|
|
15
|
+
*/
|
|
16
|
+
export declare class CurrentGraphBuilder {
|
|
17
|
+
build(workspaceRoot: string, graphPath?: string): Promise<CurrentArchitecture>;
|
|
18
|
+
}
|
|
19
|
+
/** The regenerated graph plus the tables beside it โ everything dependencies.json holds. */
|
|
20
|
+
export declare class CurrentArchitecture {
|
|
21
|
+
readonly graph: EnhancedGraph;
|
|
22
|
+
readonly apiContractFiles: ApiContractFileRefs;
|
|
23
|
+
readonly externalSystems: ExternalSystemDecls;
|
|
24
|
+
readonly remoteServices: RemoteServices;
|
|
25
|
+
constructor(graph: EnhancedGraph, apiContractFiles: ApiContractFileRefs, externalSystems: ExternalSystemDecls, remoteServices: RemoteServices);
|
|
26
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The architecture graph as the CURRENT source says it is, built live by the same pipeline
|
|
4
|
+
* `architecture:generate` runs โ never read back from the committed architecture/*.json. Shared by
|
|
5
|
+
* `validate-architecture-unchanged` (which diffs it against the committed file) and the Impact view's
|
|
6
|
+
* edge changes (#1264, which diff it against the fork point's committed file).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.CurrentArchitecture = exports.CurrentGraphBuilder = void 0;
|
|
10
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
11
|
+
const graph_generator_1 = require("./graph-generator");
|
|
12
|
+
const graph_sorter_1 = require("./graph-sorter");
|
|
13
|
+
const graph_loader_1 = require("./graph-loader");
|
|
14
|
+
const graph_metadata_1 = require("./graph-metadata");
|
|
15
|
+
const tag_truth_1 = require("./tag-truth");
|
|
16
|
+
const approved_graph_1 = require("./runtime-wiring/approved-graph");
|
|
17
|
+
/**
|
|
18
|
+
* Build the current dependency graph exactly as the generator does: reduce the nx
|
|
19
|
+
* graph, sort into levels, enrich with metadata, and attach the derived
|
|
20
|
+
* apiRelations โ so this validator compares like-for-like against the committed file.
|
|
21
|
+
*/
|
|
22
|
+
class CurrentGraphBuilder {
|
|
23
|
+
async build(workspaceRoot, graphPath) {
|
|
24
|
+
console.log('๐ Generating current dependency graph...');
|
|
25
|
+
const reducedGraph = await (0, graph_generator_1.generateReducedGraph)();
|
|
26
|
+
console.log('๐ Computing topological layers...');
|
|
27
|
+
const currentGraph = (0, graph_sorter_1.sortGraphTopologically)(reducedGraph);
|
|
28
|
+
console.log('๐ท๏ธ Enriching graph with framework + responsibilities metadata...');
|
|
29
|
+
const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
|
|
30
|
+
(0, graph_metadata_1.enrichGraph)(currentGraph, projectInfos, workspaceRoot);
|
|
31
|
+
new tag_truth_1.TagTruthCheck().assertTrue(currentGraph, projectInfos, workspaceRoot);
|
|
32
|
+
new approved_graph_1.ApprovedWiringGraph().attach(workspaceRoot, currentGraph, projectInfos, graphPath);
|
|
33
|
+
const saved = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot, graphPath);
|
|
34
|
+
if (saved === null)
|
|
35
|
+
throw new rules_config_1.RuleFailError('validate-runtime-architecture', 'Missing approved graph.', undefined, undefined, [
|
|
36
|
+
new rules_config_1.Option('Migrate src/wiring.ts and review runtime-deps.json and API contract approvals.', true),
|
|
37
|
+
]);
|
|
38
|
+
return new CurrentArchitecture(currentGraph, saved.apiContractFiles, saved.externalSystems, saved.remoteServices);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.CurrentGraphBuilder = CurrentGraphBuilder;
|
|
42
|
+
/** The regenerated graph plus the tables beside it โ everything dependencies.json holds. */
|
|
43
|
+
class CurrentArchitecture {
|
|
44
|
+
graph;
|
|
45
|
+
apiContractFiles;
|
|
46
|
+
externalSystems;
|
|
47
|
+
remoteServices;
|
|
48
|
+
constructor(graph, apiContractFiles, externalSystems, remoteServices) {
|
|
49
|
+
this.graph = graph;
|
|
50
|
+
this.apiContractFiles = apiContractFiles;
|
|
51
|
+
this.externalSystems = externalSystems;
|
|
52
|
+
this.remoteServices = remoteServices;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.CurrentArchitecture = CurrentArchitecture;
|
|
56
|
+
//# sourceMappingURL=current-architecture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"current-architecture.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/current-architecture.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEH,0DAAgE;AAChE,uDAAyD;AACzD,iDAAwD;AAExD,iDAAkD;AAClD,qDAAmE;AACnE,2CAA4C;AAC5C,oEAAsE;AAKtE;;;;GAIG;AACH,MAAa,mBAAmB;IAC5B,KAAK,CAAC,KAAK,CAAC,aAAqB,EAAE,SAAkB;QACjD,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;QACzD,MAAM,YAAY,GAAG,MAAM,IAAA,sCAAoB,GAAE,CAAC;QAClD,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QAClD,MAAM,YAAY,GAAG,IAAA,qCAAsB,EAAC,YAAY,CAAC,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAC;QAClF,MAAM,YAAY,GAAG,MAAM,IAAA,mCAAkB,GAAE,CAAC;QAChD,IAAA,4BAAW,EAAC,YAAY,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;QACvD,IAAI,yBAAa,EAAE,CAAC,UAAU,CAAC,YAAY,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;QAC1E,IAAI,oCAAmB,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QACvF,MAAM,KAAK,GAAG,IAAA,+BAAgB,EAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QACzD,IAAI,KAAK,KAAK,IAAI;YACd,MAAM,IAAI,4BAAa,CACnB,+BAA+B,EAC/B,yBAAyB,EACzB,SAAS,EACT,SAAS,EACT;gBACI,IAAI,qBAAM,CACN,gFAAgF,EAChF,IAAI,CACP;aACJ,CACJ,CAAC;QACN,OAAO,IAAI,mBAAmB,CAC1B,YAAY,EACZ,KAAK,CAAC,gBAAgB,EACtB,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,cAAc,CACvB,CAAC;IACN,CAAC;CACJ;AAhCD,kDAgCC;AAED,4FAA4F;AAC5F,MAAa,mBAAmB;IAER;IACA;IACA;IACA;IAJpB,YACoB,KAAoB,EACpB,gBAAqC,EACrC,eAAoC,EACpC,cAA8B;QAH9B,UAAK,GAAL,KAAK,CAAe;QACpB,qBAAgB,GAAhB,gBAAgB,CAAqB;QACrC,oBAAe,GAAf,eAAe,CAAqB;QACpC,mBAAc,GAAd,cAAc,CAAgB;IAC/C,CAAC;CACP;AAPD,kDAOC","sourcesContent":["/**\n * The architecture graph as the CURRENT source says it is, built live by the same pipeline\n * `architecture:generate` runs โ never read back from the committed architecture/*.json. Shared by\n * `validate-architecture-unchanged` (which diffs it against the committed file) and the Impact view's\n * edge changes (#1264, which diff it against the fork point's committed file).\n */\n\nimport { Option, RuleFailError } from '@webpieces/rules-config';\nimport { generateReducedGraph } from './graph-generator';\nimport { sortGraphTopologically } from './graph-sorter';\nimport type { EnhancedGraph } from './graph-sorter';\nimport { loadBlessedGraph } from './graph-loader';\nimport { collectProjectInfo, enrichGraph } from './graph-metadata';\nimport { TagTruthCheck } from './tag-truth';\nimport { ApprovedWiringGraph } from './runtime-wiring/approved-graph';\nimport type { ExternalSystemDecls } from './api-usage/api-relations';\nimport type { ApiContractFileRefs } from './api-contract-files';\nimport type { RemoteServices } from './remote-services';\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 */\nexport class CurrentGraphBuilder {\n async build(workspaceRoot: string, graphPath?: string): Promise<CurrentArchitecture> {\n console.log('๐ Generating current dependency graph...');\n const reducedGraph = await generateReducedGraph();\n console.log('๐ Computing topological layers...');\n const currentGraph = sortGraphTopologically(reducedGraph);\n console.log('๐ท๏ธ Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(currentGraph, projectInfos, workspaceRoot);\n new TagTruthCheck().assertTrue(currentGraph, projectInfos, workspaceRoot);\n new ApprovedWiringGraph().attach(workspaceRoot, currentGraph, projectInfos, graphPath);\n const saved = loadBlessedGraph(workspaceRoot, graphPath);\n if (saved === null)\n throw new RuleFailError(\n 'validate-runtime-architecture',\n 'Missing approved graph.',\n undefined,\n undefined,\n [\n new Option(\n 'Migrate src/wiring.ts and review runtime-deps.json and API contract approvals.',\n true,\n ),\n ],\n );\n return new CurrentArchitecture(\n currentGraph,\n saved.apiContractFiles,\n saved.externalSystems,\n saved.remoteServices,\n );\n }\n}\n\n/** The regenerated graph plus the tables beside it โ everything dependencies.json holds. */\nexport class CurrentArchitecture {\n constructor(\n public readonly graph: EnhancedGraph,\n public readonly apiContractFiles: ApiContractFileRefs,\n public readonly externalSystems: ExternalSystemDecls,\n public readonly remoteServices: RemoteServices,\n ) {}\n}\n"]}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge changes against the base (#1264): which dependency and runtime edges this branch GAINED or
|
|
3
|
+
* LOST, so "why is this building?" is answerable for a project whose only change is an edge.
|
|
4
|
+
*
|
|
5
|
+
* BASE is the FORK POINT โ the branch comparison's `git merge-base HEAD origin/main` โ read as
|
|
6
|
+
* `git show <forkPoint>:architecture/dependencies.json` and `โฆ:architecture/runtime-dependencies.json`.
|
|
7
|
+
* Main keeps both regenerated and validated (`validate-architecture-unchanged`), so the fork point's
|
|
8
|
+
* copies are trustworthy.
|
|
9
|
+
*
|
|
10
|
+
* CURRENT is the graph computed LIVE when the page is refreshed (`LiveGraphSource`: the same pipeline
|
|
11
|
+
* `architecture:generate` runs), NEVER HEAD's committed architecture/*.json. Whether or not the branch
|
|
12
|
+
* already regenerated and committed those files must not matter: comparing against HEAD's committed
|
|
13
|
+
* copy would show no change at all once it had been regenerated.
|
|
14
|
+
*
|
|
15
|
+
* Neither file at the fork point (first adoption) is an answer, not an error: no edge diff, and the
|
|
16
|
+
* page says why. Nothing here ever throws into generation.
|
|
17
|
+
*/
|
|
18
|
+
import type { EnhancedGraph } from './graph-sorter';
|
|
19
|
+
import type { RuntimeGraph } from './runtime-graph-model';
|
|
20
|
+
export declare enum EdgeChangeKind {
|
|
21
|
+
GAINED = "gained",
|
|
22
|
+
LOST = "lost"
|
|
23
|
+
}
|
|
24
|
+
export declare enum EdgeRelation {
|
|
25
|
+
/** A build dependency (architecture/dependencies.json `dependsOn`). */
|
|
26
|
+
DEPENDS = "depends",
|
|
27
|
+
/** A runtime call (architecture/runtime-dependencies.json `runtimeEdges`). */
|
|
28
|
+
CALLS = "calls"
|
|
29
|
+
}
|
|
30
|
+
/** One gained or lost edge, `from` โ `to` (node ids), with the words the page shows for it. */
|
|
31
|
+
export declare class EdgeChange {
|
|
32
|
+
readonly from: string;
|
|
33
|
+
readonly to: string;
|
|
34
|
+
readonly change: EdgeChangeKind;
|
|
35
|
+
readonly relation: EdgeRelation;
|
|
36
|
+
/** CALLS: the APIs gained or lost on this edge, sorted. Empty for DEPENDS. */
|
|
37
|
+
readonly apis: string[];
|
|
38
|
+
/** "new on this branch: a now depends on b" / "a no longer calls b (SaveApi)". */
|
|
39
|
+
readonly text: string;
|
|
40
|
+
constructor(from: string, to: string, change: EdgeChangeKind, relation: EdgeRelation,
|
|
41
|
+
/** CALLS: the APIs gained or lost on this edge, sorted. Empty for DEPENDS. */
|
|
42
|
+
apis: string[],
|
|
43
|
+
/** "new on this branch: a now depends on b" / "a no longer calls b (SaveApi)". */
|
|
44
|
+
text: string);
|
|
45
|
+
}
|
|
46
|
+
/** The edge changes of the dependency graph and of the runtime graph, or why each is not shown. */
|
|
47
|
+
export declare class EdgeDiffReport {
|
|
48
|
+
/** The fork point's short sha ('' when there is none). */
|
|
49
|
+
readonly base: string;
|
|
50
|
+
/** '' when `dependency` was computed; otherwise why the page shows no dependency edge changes. */
|
|
51
|
+
readonly dependencyReason: string;
|
|
52
|
+
readonly dependency: EdgeChange[];
|
|
53
|
+
/** '' when `runtime` was computed; otherwise why the page shows no runtime edge changes. */
|
|
54
|
+
readonly runtimeReason: string;
|
|
55
|
+
readonly runtime: EdgeChange[];
|
|
56
|
+
constructor(
|
|
57
|
+
/** The fork point's short sha ('' when there is none). */
|
|
58
|
+
base: string,
|
|
59
|
+
/** '' when `dependency` was computed; otherwise why the page shows no dependency edge changes. */
|
|
60
|
+
dependencyReason: string, dependency: EdgeChange[],
|
|
61
|
+
/** '' when `runtime` was computed; otherwise why the page shows no runtime edge changes. */
|
|
62
|
+
runtimeReason: string, runtime: EdgeChange[]);
|
|
63
|
+
}
|
|
64
|
+
/** The two graphs the branch has NOW, computed live. `runtime` null when it could not be derived. */
|
|
65
|
+
export declare class LiveGraphs {
|
|
66
|
+
readonly projects: EnhancedGraph;
|
|
67
|
+
readonly runtime: RuntimeGraph | null;
|
|
68
|
+
/** Why `runtime` is null ('' otherwise). */
|
|
69
|
+
readonly runtimeReason: string;
|
|
70
|
+
constructor(projects: EnhancedGraph, runtime: RuntimeGraph | null,
|
|
71
|
+
/** Why `runtime` is null ('' otherwise). */
|
|
72
|
+
runtimeReason?: string);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Computes the live graphs: `CurrentGraphBuilder` (generate's own pipeline, writing nothing) and the
|
|
76
|
+
* runtime graph derived from it exactly as generate derives it. `architecture:generate` passes the
|
|
77
|
+
* graphs it just built instead (`FixedLiveGraphs`), so nothing is computed twice.
|
|
78
|
+
*/
|
|
79
|
+
export declare class LiveGraphSource {
|
|
80
|
+
graphs(workspaceRoot: string): Promise<LiveGraphs>;
|
|
81
|
+
}
|
|
82
|
+
/** The graphs a caller already holds (architecture:generate just built them). */
|
|
83
|
+
export declare class FixedLiveGraphs extends LiveGraphSource {
|
|
84
|
+
private readonly live;
|
|
85
|
+
constructor(live: LiveGraphs);
|
|
86
|
+
graphs(): Promise<LiveGraphs>;
|
|
87
|
+
}
|
|
88
|
+
/** A committed file as it was at a commit. Injected so specs never spawn git. */
|
|
89
|
+
export declare class GitFileAt {
|
|
90
|
+
/** The file's text at `sha`; null when it did not exist there (or git cannot answer). */
|
|
91
|
+
read(workspaceRoot: string, sha: string, file: string): string | null;
|
|
92
|
+
}
|
|
93
|
+
/** Diffs base against live; pure. */
|
|
94
|
+
export declare class EdgeDiffer {
|
|
95
|
+
dependency(base: EnhancedGraph, live: EnhancedGraph): EdgeChange[];
|
|
96
|
+
runtime(base: RuntimeGraph, live: RuntimeGraph): EdgeChange[];
|
|
97
|
+
private dependencyEdges;
|
|
98
|
+
private runtimeEdges;
|
|
99
|
+
/** Per edge: gained when absent at base; lost when absent now; for a call, the APIs that came or went. */
|
|
100
|
+
private compare;
|
|
101
|
+
private change;
|
|
102
|
+
}
|
|
103
|
+
/** Reads the fork point's committed graphs and diffs them against the live ones. Never throws. */
|
|
104
|
+
export declare class EdgeChangeScanner {
|
|
105
|
+
private readonly git;
|
|
106
|
+
private readonly differ;
|
|
107
|
+
constructor(git?: GitFileAt, differ?: EdgeDiffer);
|
|
108
|
+
scan(workspaceRoot: string, forkPoint: string | null, source: LiveGraphSource): Promise<EdgeDiffReport>;
|
|
109
|
+
/** No edge changes to show, with the one reason the page says instead. */
|
|
110
|
+
private none;
|
|
111
|
+
/** dependencies.json's `projects` at the fork point; null when absent or not that shape. */
|
|
112
|
+
private projectsAt;
|
|
113
|
+
private runtimeAt;
|
|
114
|
+
private parse;
|
|
115
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Edge changes against the base (#1264): which dependency and runtime edges this branch GAINED or
|
|
4
|
+
* LOST, so "why is this building?" is answerable for a project whose only change is an edge.
|
|
5
|
+
*
|
|
6
|
+
* BASE is the FORK POINT โ the branch comparison's `git merge-base HEAD origin/main` โ read as
|
|
7
|
+
* `git show <forkPoint>:architecture/dependencies.json` and `โฆ:architecture/runtime-dependencies.json`.
|
|
8
|
+
* Main keeps both regenerated and validated (`validate-architecture-unchanged`), so the fork point's
|
|
9
|
+
* copies are trustworthy.
|
|
10
|
+
*
|
|
11
|
+
* CURRENT is the graph computed LIVE when the page is refreshed (`LiveGraphSource`: the same pipeline
|
|
12
|
+
* `architecture:generate` runs), NEVER HEAD's committed architecture/*.json. Whether or not the branch
|
|
13
|
+
* already regenerated and committed those files must not matter: comparing against HEAD's committed
|
|
14
|
+
* copy would show no change at all once it had been regenerated.
|
|
15
|
+
*
|
|
16
|
+
* Neither file at the fork point (first adoption) is an answer, not an error: no edge diff, and the
|
|
17
|
+
* page says why. Nothing here ever throws into generation.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.EdgeChangeScanner = exports.EdgeDiffer = exports.GitFileAt = exports.FixedLiveGraphs = exports.LiveGraphSource = exports.LiveGraphs = exports.EdgeDiffReport = exports.EdgeChange = exports.EdgeRelation = exports.EdgeChangeKind = void 0;
|
|
21
|
+
const child_process_1 = require("child_process");
|
|
22
|
+
const graph_loader_1 = require("./graph-loader");
|
|
23
|
+
const runtime_graph_io_1 = require("./runtime-graph-io");
|
|
24
|
+
const runtime_graph_1 = require("./runtime-graph");
|
|
25
|
+
const api_contract_files_1 = require("./api-contract-files");
|
|
26
|
+
const current_architecture_1 = require("./current-architecture");
|
|
27
|
+
const toError_1 = require("../toError");
|
|
28
|
+
var EdgeChangeKind;
|
|
29
|
+
(function (EdgeChangeKind) {
|
|
30
|
+
EdgeChangeKind["GAINED"] = "gained";
|
|
31
|
+
EdgeChangeKind["LOST"] = "lost";
|
|
32
|
+
})(EdgeChangeKind || (exports.EdgeChangeKind = EdgeChangeKind = {}));
|
|
33
|
+
var EdgeRelation;
|
|
34
|
+
(function (EdgeRelation) {
|
|
35
|
+
/** A build dependency (architecture/dependencies.json `dependsOn`). */
|
|
36
|
+
EdgeRelation["DEPENDS"] = "depends";
|
|
37
|
+
/** A runtime call (architecture/runtime-dependencies.json `runtimeEdges`). */
|
|
38
|
+
EdgeRelation["CALLS"] = "calls";
|
|
39
|
+
})(EdgeRelation || (exports.EdgeRelation = EdgeRelation = {}));
|
|
40
|
+
/** One gained or lost edge, `from` โ `to` (node ids), with the words the page shows for it. */
|
|
41
|
+
class EdgeChange {
|
|
42
|
+
from;
|
|
43
|
+
to;
|
|
44
|
+
change;
|
|
45
|
+
relation;
|
|
46
|
+
apis;
|
|
47
|
+
text;
|
|
48
|
+
constructor(from, to, change, relation,
|
|
49
|
+
/** CALLS: the APIs gained or lost on this edge, sorted. Empty for DEPENDS. */
|
|
50
|
+
apis,
|
|
51
|
+
/** "new on this branch: a now depends on b" / "a no longer calls b (SaveApi)". */
|
|
52
|
+
text) {
|
|
53
|
+
this.from = from;
|
|
54
|
+
this.to = to;
|
|
55
|
+
this.change = change;
|
|
56
|
+
this.relation = relation;
|
|
57
|
+
this.apis = apis;
|
|
58
|
+
this.text = text;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.EdgeChange = EdgeChange;
|
|
62
|
+
/** The edge changes of the dependency graph and of the runtime graph, or why each is not shown. */
|
|
63
|
+
class EdgeDiffReport {
|
|
64
|
+
base;
|
|
65
|
+
dependencyReason;
|
|
66
|
+
dependency;
|
|
67
|
+
runtimeReason;
|
|
68
|
+
runtime;
|
|
69
|
+
constructor(
|
|
70
|
+
/** The fork point's short sha ('' when there is none). */
|
|
71
|
+
base,
|
|
72
|
+
/** '' when `dependency` was computed; otherwise why the page shows no dependency edge changes. */
|
|
73
|
+
dependencyReason, dependency,
|
|
74
|
+
/** '' when `runtime` was computed; otherwise why the page shows no runtime edge changes. */
|
|
75
|
+
runtimeReason, runtime) {
|
|
76
|
+
this.base = base;
|
|
77
|
+
this.dependencyReason = dependencyReason;
|
|
78
|
+
this.dependency = dependency;
|
|
79
|
+
this.runtimeReason = runtimeReason;
|
|
80
|
+
this.runtime = runtime;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.EdgeDiffReport = EdgeDiffReport;
|
|
84
|
+
/** The two graphs the branch has NOW, computed live. `runtime` null when it could not be derived. */
|
|
85
|
+
class LiveGraphs {
|
|
86
|
+
projects;
|
|
87
|
+
runtime;
|
|
88
|
+
runtimeReason;
|
|
89
|
+
constructor(projects, runtime,
|
|
90
|
+
/** Why `runtime` is null ('' otherwise). */
|
|
91
|
+
runtimeReason = '') {
|
|
92
|
+
this.projects = projects;
|
|
93
|
+
this.runtime = runtime;
|
|
94
|
+
this.runtimeReason = runtimeReason;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.LiveGraphs = LiveGraphs;
|
|
98
|
+
/**
|
|
99
|
+
* Computes the live graphs: `CurrentGraphBuilder` (generate's own pipeline, writing nothing) and the
|
|
100
|
+
* runtime graph derived from it exactly as generate derives it. `architecture:generate` passes the
|
|
101
|
+
* graphs it just built instead (`FixedLiveGraphs`), so nothing is computed twice.
|
|
102
|
+
*/
|
|
103
|
+
class LiveGraphSource {
|
|
104
|
+
async graphs(workspaceRoot) {
|
|
105
|
+
const current = await new current_architecture_1.CurrentGraphBuilder().build(workspaceRoot);
|
|
106
|
+
const hidden = new Set(Object.keys(current.graph).filter((name) => current.graph[name].drawOnGraph === false));
|
|
107
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- the runtime half is optional; the dependency half still shows
|
|
108
|
+
try {
|
|
109
|
+
const contracts = new api_contract_files_1.ApiContractFiles().load(workspaceRoot, graph_loader_1.DEFAULT_GRAPH_PATH, current.apiContractFiles);
|
|
110
|
+
const report = (0, runtime_graph_1.deriveRuntimeGraphReport)(current.graph, hidden, contracts, current.externalSystems, current.remoteServices);
|
|
111
|
+
return new LiveGraphs(current.graph, report.graph);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
const error = (0, toError_1.toError)(err);
|
|
115
|
+
return new LiveGraphs(current.graph, null, `the live runtime graph could not be derived (${error.message})`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.LiveGraphSource = LiveGraphSource;
|
|
120
|
+
/** The graphs a caller already holds (architecture:generate just built them). */
|
|
121
|
+
class FixedLiveGraphs extends LiveGraphSource {
|
|
122
|
+
live;
|
|
123
|
+
constructor(live) {
|
|
124
|
+
super();
|
|
125
|
+
this.live = live;
|
|
126
|
+
}
|
|
127
|
+
async graphs() {
|
|
128
|
+
return this.live;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.FixedLiveGraphs = FixedLiveGraphs;
|
|
132
|
+
/** A committed file as it was at a commit. Injected so specs never spawn git. */
|
|
133
|
+
class GitFileAt {
|
|
134
|
+
/** The file's text at `sha`; null when it did not exist there (or git cannot answer). */
|
|
135
|
+
read(workspaceRoot, sha, file) {
|
|
136
|
+
const shown = (0, child_process_1.spawnSync)('git', ['show', `${sha}:${file}`], {
|
|
137
|
+
cwd: workspaceRoot,
|
|
138
|
+
encoding: 'utf8',
|
|
139
|
+
stdio: 'pipe',
|
|
140
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
141
|
+
});
|
|
142
|
+
return shown.status === 0 ? shown.stdout : null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.GitFileAt = GitFileAt;
|
|
146
|
+
/** The base graphs' edges: `from|to` โ the APIs on that edge (empty for a dependency). */
|
|
147
|
+
class EdgeSet {
|
|
148
|
+
edges = new Map();
|
|
149
|
+
add(from, to, apis) {
|
|
150
|
+
const key = `${from}\u0000${to}`;
|
|
151
|
+
const known = this.edges.get(key) ?? new Set();
|
|
152
|
+
for (const api of apis)
|
|
153
|
+
known.add(api);
|
|
154
|
+
this.edges.set(key, known);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Diffs base against live; pure. */
|
|
158
|
+
class EdgeDiffer {
|
|
159
|
+
dependency(base, live) {
|
|
160
|
+
return this.compare(this.dependencyEdges(base), this.dependencyEdges(live), EdgeRelation.DEPENDS);
|
|
161
|
+
}
|
|
162
|
+
runtime(base, live) {
|
|
163
|
+
return this.compare(this.runtimeEdges(base), this.runtimeEdges(live), EdgeRelation.CALLS);
|
|
164
|
+
}
|
|
165
|
+
dependencyEdges(graph) {
|
|
166
|
+
const set = new EdgeSet();
|
|
167
|
+
for (const name of Object.keys(graph))
|
|
168
|
+
for (const dep of graph[name].dependsOn ?? [])
|
|
169
|
+
set.add(name, dep, []);
|
|
170
|
+
return set;
|
|
171
|
+
}
|
|
172
|
+
runtimeEdges(graph) {
|
|
173
|
+
const set = new EdgeSet();
|
|
174
|
+
for (const edge of graph.runtimeEdges ?? [])
|
|
175
|
+
set.add(edge.from, edge.to, edge.via ?? []);
|
|
176
|
+
return set;
|
|
177
|
+
}
|
|
178
|
+
/** Per edge: gained when absent at base; lost when absent now; for a call, the APIs that came or went. */
|
|
179
|
+
compare(base, live, relation) {
|
|
180
|
+
const changes = [];
|
|
181
|
+
const keys = [...new Set([...base.edges.keys(), ...live.edges.keys()])].sort();
|
|
182
|
+
for (const key of keys) {
|
|
183
|
+
const ends = key.split('\u0000');
|
|
184
|
+
const before = base.edges.get(key);
|
|
185
|
+
const after = live.edges.get(key);
|
|
186
|
+
const gained = after === undefined ? [] : [...after].filter((api) => before === undefined || !before.has(api));
|
|
187
|
+
const lost = before === undefined ? [] : [...before].filter((api) => after === undefined || !after.has(api));
|
|
188
|
+
if (before === undefined || gained.length > 0)
|
|
189
|
+
changes.push(this.change(ends[0], ends[1], EdgeChangeKind.GAINED, relation, gained.sort()));
|
|
190
|
+
if (after === undefined || lost.length > 0)
|
|
191
|
+
changes.push(this.change(ends[0], ends[1], EdgeChangeKind.LOST, relation, lost.sort()));
|
|
192
|
+
}
|
|
193
|
+
return changes;
|
|
194
|
+
}
|
|
195
|
+
change(from, to, kind, relation, apis) {
|
|
196
|
+
const verb = relation === EdgeRelation.DEPENDS ? 'depends on' : 'calls';
|
|
197
|
+
const via = apis.length === 0 ? '' : ` (${apis.join(', ')})`;
|
|
198
|
+
const text = kind === EdgeChangeKind.GAINED
|
|
199
|
+
? `new on this branch: ${from} now ${verb} ${to}${via}`
|
|
200
|
+
: `${from} no longer ${verb} ${to}${via}`;
|
|
201
|
+
return new EdgeChange(from, to, kind, relation, apis, text);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
exports.EdgeDiffer = EdgeDiffer;
|
|
205
|
+
/** Reads the fork point's committed graphs and diffs them against the live ones. Never throws. */
|
|
206
|
+
class EdgeChangeScanner {
|
|
207
|
+
git;
|
|
208
|
+
differ;
|
|
209
|
+
constructor(git = new GitFileAt(), differ = new EdgeDiffer()) {
|
|
210
|
+
this.git = git;
|
|
211
|
+
this.differ = differ;
|
|
212
|
+
}
|
|
213
|
+
async scan(workspaceRoot, forkPoint, source) {
|
|
214
|
+
if (forkPoint === null)
|
|
215
|
+
return this.none('Edge changes compare a branch with its fork point; there is no branch comparison here (main or a detached HEAD)');
|
|
216
|
+
const short = forkPoint.slice(0, 7);
|
|
217
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- edge changes are optional; a failure becomes the reason the page shows
|
|
218
|
+
try {
|
|
219
|
+
const baseProjects = this.projectsAt(workspaceRoot, forkPoint);
|
|
220
|
+
const baseRuntime = this.runtimeAt(workspaceRoot, forkPoint);
|
|
221
|
+
if (baseProjects === null && baseRuntime === null)
|
|
222
|
+
return this.none(`No architecture graph at the fork point ${short} (first adoption), so there is nothing to compare edges against`, short);
|
|
223
|
+
const live = await source.graphs(workspaceRoot);
|
|
224
|
+
const dependencyReason = baseProjects === null ? `No ${graph_loader_1.DEFAULT_GRAPH_PATH} at the fork point ${short}` : '';
|
|
225
|
+
const runtimeReason = baseRuntime === null ? `No ${runtime_graph_io_1.DEFAULT_RUNTIME_GRAPH_PATH} at the fork point ${short}` : live.runtimeReason;
|
|
226
|
+
return new EdgeDiffReport(short, dependencyReason, baseProjects === null ? [] : this.differ.dependency(baseProjects, live.projects), runtimeReason, baseRuntime === null || live.runtime === null ? [] : this.differ.runtime(baseRuntime, live.runtime));
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
const error = (0, toError_1.toError)(err);
|
|
230
|
+
return this.none(`Edge changes unavailable: ${error.message}`, short);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** No edge changes to show, with the one reason the page says instead. */
|
|
234
|
+
none(reason, base = '') {
|
|
235
|
+
return new EdgeDiffReport(base, reason, [], reason, []);
|
|
236
|
+
}
|
|
237
|
+
/** dependencies.json's `projects` at the fork point; null when absent or not that shape. */
|
|
238
|
+
projectsAt(workspaceRoot, sha) {
|
|
239
|
+
const parsed = this.parse(this.git.read(workspaceRoot, sha, graph_loader_1.DEFAULT_GRAPH_PATH));
|
|
240
|
+
const projects = parsed === null ? undefined : parsed.projects;
|
|
241
|
+
return projects !== undefined && typeof projects === 'object' && projects !== null ? projects : null;
|
|
242
|
+
}
|
|
243
|
+
runtimeAt(workspaceRoot, sha) {
|
|
244
|
+
const parsed = this.parse(this.git.read(workspaceRoot, sha, runtime_graph_io_1.DEFAULT_RUNTIME_GRAPH_PATH));
|
|
245
|
+
return parsed !== null && Array.isArray(parsed.runtimeEdges) ? parsed : null;
|
|
246
|
+
}
|
|
247
|
+
// webpieces-disable no-any-unknown -- committed JSON from another commit is untrusted until narrowed by the caller
|
|
248
|
+
parse(text) {
|
|
249
|
+
if (text === null)
|
|
250
|
+
return null;
|
|
251
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an unreadable file at the fork point is the same as no file there
|
|
252
|
+
try {
|
|
253
|
+
return JSON.parse(text);
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
const error = (0, toError_1.toError)(err);
|
|
257
|
+
void error; // not JSON at the fork point: treated as absent
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
exports.EdgeChangeScanner = EdgeChangeScanner;
|
|
263
|
+
//# sourceMappingURL=graph-edge-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-edge-diff.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-edge-diff.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Edge changes against the base (#1264): which dependency and runtime edges this branch GAINED or\n * LOST, so \"why is this building?\" is answerable for a project whose only change is an edge.\n *\n * BASE is the FORK POINT โ the branch comparison's `git merge-base HEAD origin/main` โ read as\n * `git show <forkPoint>:architecture/dependencies.json` and `โฆ:architecture/runtime-dependencies.json`.\n * Main keeps both regenerated and validated (`validate-architecture-unchanged`), so the fork point's\n * copies are trustworthy.\n *\n * CURRENT is the graph computed LIVE when the page is refreshed (`LiveGraphSource`: the same pipeline\n * `architecture:generate` runs), NEVER HEAD's committed architecture/*.json. Whether or not the branch\n * already regenerated and committed those files must not matter: comparing against HEAD's committed\n * copy would show no change at all once it had been regenerated.\n *\n * Neither file at the fork point (first adoption) is an answer, not an error: no edge diff, and the\n * page says why. Nothing here ever throws into generation.\n */\n\nimport { spawnSync } from 'child_process';\nimport { DEFAULT_GRAPH_PATH } from './graph-loader';\nimport type { EnhancedGraph } from './graph-sorter';\nimport { DEFAULT_RUNTIME_GRAPH_PATH } from './runtime-graph-io';\nimport type { RuntimeGraph } from './runtime-graph-model';\nimport { deriveRuntimeGraphReport } from './runtime-graph';\nimport { ApiContractFiles } from './api-contract-files';\nimport { CurrentGraphBuilder } from './current-architecture';\nimport { toError } from '../toError';\n\nexport enum EdgeChangeKind {\n GAINED = 'gained',\n LOST = 'lost',\n}\n\nexport enum EdgeRelation {\n /** A build dependency (architecture/dependencies.json `dependsOn`). */\n DEPENDS = 'depends',\n /** A runtime call (architecture/runtime-dependencies.json `runtimeEdges`). */\n CALLS = 'calls',\n}\n\n/** One gained or lost edge, `from` โ `to` (node ids), with the words the page shows for it. */\nexport class EdgeChange {\n constructor(\n public readonly from: string,\n public readonly to: string,\n public readonly change: EdgeChangeKind,\n public readonly relation: EdgeRelation,\n /** CALLS: the APIs gained or lost on this edge, sorted. Empty for DEPENDS. */\n public readonly apis: string[],\n /** \"new on this branch: a now depends on b\" / \"a no longer calls b (SaveApi)\". */\n public readonly text: string,\n ) {}\n}\n\n/** The edge changes of the dependency graph and of the runtime graph, or why each is not shown. */\nexport class EdgeDiffReport {\n constructor(\n /** The fork point's short sha ('' when there is none). */\n public readonly base: string,\n /** '' when `dependency` was computed; otherwise why the page shows no dependency edge changes. */\n public readonly dependencyReason: string,\n public readonly dependency: EdgeChange[],\n /** '' when `runtime` was computed; otherwise why the page shows no runtime edge changes. */\n public readonly runtimeReason: string,\n public readonly runtime: EdgeChange[],\n ) {}\n}\n\n/** The one field of a committed dependencies.json the base side reads. */\ninterface SavedProjectsFile {\n projects?: EnhancedGraph;\n}\n\n/** The two graphs the branch has NOW, computed live. `runtime` null when it could not be derived. */\nexport class LiveGraphs {\n constructor(\n public readonly projects: EnhancedGraph,\n public readonly runtime: RuntimeGraph | null,\n /** Why `runtime` is null ('' otherwise). */\n public readonly runtimeReason: string = '',\n ) {}\n}\n\n/**\n * Computes the live graphs: `CurrentGraphBuilder` (generate's own pipeline, writing nothing) and the\n * runtime graph derived from it exactly as generate derives it. `architecture:generate` passes the\n * graphs it just built instead (`FixedLiveGraphs`), so nothing is computed twice.\n */\nexport class LiveGraphSource {\n async graphs(workspaceRoot: string): Promise<LiveGraphs> {\n const current = await new CurrentGraphBuilder().build(workspaceRoot);\n const hidden = new Set<string>(Object.keys(current.graph).filter((name: string): boolean => current.graph[name].drawOnGraph === false));\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- the runtime half is optional; the dependency half still shows\n try {\n const contracts = new ApiContractFiles().load(workspaceRoot, DEFAULT_GRAPH_PATH, current.apiContractFiles);\n const report = deriveRuntimeGraphReport(current.graph, hidden, contracts, current.externalSystems, current.remoteServices);\n return new LiveGraphs(current.graph, report.graph);\n } catch (err: unknown) {\n const error = toError(err);\n return new LiveGraphs(current.graph, null, `the live runtime graph could not be derived (${error.message})`);\n }\n }\n}\n\n/** The graphs a caller already holds (architecture:generate just built them). */\nexport class FixedLiveGraphs extends LiveGraphSource {\n constructor(private readonly live: LiveGraphs) {\n super();\n }\n\n override async graphs(): Promise<LiveGraphs> {\n return this.live;\n }\n}\n\n/** A committed file as it was at a commit. Injected so specs never spawn git. */\nexport class GitFileAt {\n /** The file's text at `sha`; null when it did not exist there (or git cannot answer). */\n read(workspaceRoot: string, sha: string, file: string): string | null {\n const shown = spawnSync('git', ['show', `${sha}:${file}`], {\n cwd: workspaceRoot,\n encoding: 'utf8',\n stdio: 'pipe',\n maxBuffer: 64 * 1024 * 1024,\n });\n return shown.status === 0 ? shown.stdout : null;\n }\n}\n\n/** The base graphs' edges: `from|to` โ the APIs on that edge (empty for a dependency). */\nclass EdgeSet {\n readonly edges = new Map<string, Set<string>>();\n\n add(from: string, to: string, apis: string[]): void {\n const key = `${from}\\u0000${to}`;\n const known = this.edges.get(key) ?? new Set<string>();\n for (const api of apis) known.add(api);\n this.edges.set(key, known);\n }\n}\n\n/** Diffs base against live; pure. */\nexport class EdgeDiffer {\n dependency(base: EnhancedGraph, live: EnhancedGraph): EdgeChange[] {\n return this.compare(this.dependencyEdges(base), this.dependencyEdges(live), EdgeRelation.DEPENDS);\n }\n\n runtime(base: RuntimeGraph, live: RuntimeGraph): EdgeChange[] {\n return this.compare(this.runtimeEdges(base), this.runtimeEdges(live), EdgeRelation.CALLS);\n }\n\n private dependencyEdges(graph: EnhancedGraph): EdgeSet {\n const set = new EdgeSet();\n for (const name of Object.keys(graph)) for (const dep of graph[name].dependsOn ?? []) set.add(name, dep, []);\n return set;\n }\n\n private runtimeEdges(graph: RuntimeGraph): EdgeSet {\n const set = new EdgeSet();\n for (const edge of graph.runtimeEdges ?? []) set.add(edge.from, edge.to, edge.via ?? []);\n return set;\n }\n\n /** Per edge: gained when absent at base; lost when absent now; for a call, the APIs that came or went. */\n private compare(base: EdgeSet, live: EdgeSet, relation: EdgeRelation): EdgeChange[] {\n const changes: EdgeChange[] = [];\n const keys = [...new Set<string>([...base.edges.keys(), ...live.edges.keys()])].sort();\n for (const key of keys) {\n const ends = key.split('\\u0000');\n const before = base.edges.get(key);\n const after = live.edges.get(key);\n const gained = after === undefined ? [] : [...after].filter((api: string): boolean => before === undefined || !before.has(api));\n const lost = before === undefined ? [] : [...before].filter((api: string): boolean => after === undefined || !after.has(api));\n if (before === undefined || gained.length > 0)\n changes.push(this.change(ends[0], ends[1], EdgeChangeKind.GAINED, relation, gained.sort()));\n if (after === undefined || lost.length > 0)\n changes.push(this.change(ends[0], ends[1], EdgeChangeKind.LOST, relation, lost.sort()));\n }\n return changes;\n }\n\n private change(from: string, to: string, kind: EdgeChangeKind, relation: EdgeRelation, apis: string[]): EdgeChange {\n const verb = relation === EdgeRelation.DEPENDS ? 'depends on' : 'calls';\n const via = apis.length === 0 ? '' : ` (${apis.join(', ')})`;\n const text =\n kind === EdgeChangeKind.GAINED\n ? `new on this branch: ${from} now ${verb} ${to}${via}`\n : `${from} no longer ${verb} ${to}${via}`;\n return new EdgeChange(from, to, kind, relation, apis, text);\n }\n}\n\n/** Reads the fork point's committed graphs and diffs them against the live ones. Never throws. */\nexport class EdgeChangeScanner {\n constructor(\n private readonly git: GitFileAt = new GitFileAt(),\n private readonly differ: EdgeDiffer = new EdgeDiffer(),\n ) {}\n\n async scan(workspaceRoot: string, forkPoint: string | null, source: LiveGraphSource): Promise<EdgeDiffReport> {\n if (forkPoint === null)\n return this.none('Edge changes compare a branch with its fork point; there is no branch comparison here (main or a detached HEAD)');\n const short = forkPoint.slice(0, 7);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- edge changes are optional; a failure becomes the reason the page shows\n try {\n const baseProjects = this.projectsAt(workspaceRoot, forkPoint);\n const baseRuntime = this.runtimeAt(workspaceRoot, forkPoint);\n if (baseProjects === null && baseRuntime === null)\n return this.none(`No architecture graph at the fork point ${short} (first adoption), so there is nothing to compare edges against`, short);\n const live = await source.graphs(workspaceRoot);\n const dependencyReason = baseProjects === null ? `No ${DEFAULT_GRAPH_PATH} at the fork point ${short}` : '';\n const runtimeReason = baseRuntime === null ? `No ${DEFAULT_RUNTIME_GRAPH_PATH} at the fork point ${short}` : live.runtimeReason;\n return new EdgeDiffReport(\n short,\n dependencyReason,\n baseProjects === null ? [] : this.differ.dependency(baseProjects, live.projects),\n runtimeReason,\n baseRuntime === null || live.runtime === null ? [] : this.differ.runtime(baseRuntime, live.runtime),\n );\n } catch (err: unknown) {\n const error = toError(err);\n return this.none(`Edge changes unavailable: ${error.message}`, short);\n }\n }\n\n /** No edge changes to show, with the one reason the page says instead. */\n private none(reason: string, base = ''): EdgeDiffReport {\n return new EdgeDiffReport(base, reason, [], reason, []);\n }\n\n /** dependencies.json's `projects` at the fork point; null when absent or not that shape. */\n private projectsAt(workspaceRoot: string, sha: string): EnhancedGraph | null {\n const parsed = this.parse(this.git.read(workspaceRoot, sha, DEFAULT_GRAPH_PATH));\n const projects = parsed === null ? undefined : (parsed as SavedProjectsFile).projects;\n return projects !== undefined && typeof projects === 'object' && projects !== null ? projects : null;\n }\n\n private runtimeAt(workspaceRoot: string, sha: string): RuntimeGraph | null {\n const parsed = this.parse(this.git.read(workspaceRoot, sha, DEFAULT_RUNTIME_GRAPH_PATH));\n return parsed !== null && Array.isArray((parsed as RuntimeGraph).runtimeEdges) ? (parsed as RuntimeGraph) : null;\n }\n\n // webpieces-disable no-any-unknown -- committed JSON from another commit is untrusted until narrowed by the caller\n private parse(text: string | null): unknown {\n if (text === null) return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an unreadable file at the fork point is the same as no file there\n try {\n return JSON.parse(text);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // not JSON at the fork point: treated as absent\n return null;\n }\n }\n}\n"]}
|
|
@@ -55,6 +55,7 @@ import type { FileChange } from 'nx/src/project-graph/file-utils';
|
|
|
55
55
|
import type { PackageJson } from 'nx/src/utils/package-json';
|
|
56
56
|
import { DiffScope } from '@webpieces/rules-config';
|
|
57
57
|
import { AffectedClassification, AffectedClassifier, AffectedReason, WorkspaceNx } from './graph-impact-reasons';
|
|
58
|
+
import { EdgeChangeScanner, EdgeDiffReport, LiveGraphSource } from './graph-edge-diff';
|
|
58
59
|
import type { EnhancedGraph } from './graph-sorter';
|
|
59
60
|
/** Relative to the architecture/ directory the page lives in; the page's `<script src>`. */
|
|
60
61
|
export declare const IMPACT_SIDECAR_DIR = ".impact";
|
|
@@ -165,7 +166,19 @@ export declare class ImpactCounts {
|
|
|
165
166
|
export declare class ImpactSidecarData {
|
|
166
167
|
readonly scans: ImpactReport[];
|
|
167
168
|
readonly defaultKind: ImpactKind;
|
|
168
|
-
|
|
169
|
+
/**
|
|
170
|
+
* The edges this branch gained or lost since its FORK POINT, the live graph against the fork
|
|
171
|
+
* point's committed one (graph-edge-diff.ts, #1264). One per sidecar: it does not depend on
|
|
172
|
+
* which scan is shown.
|
|
173
|
+
*/
|
|
174
|
+
readonly edges: EdgeDiffReport;
|
|
175
|
+
constructor(scans: ImpactReport[], defaultKind: ImpactKind,
|
|
176
|
+
/**
|
|
177
|
+
* The edges this branch gained or lost since its FORK POINT, the live graph against the fork
|
|
178
|
+
* point's committed one (graph-edge-diff.ts, #1264). One per sidecar: it does not depend on
|
|
179
|
+
* which scan is shown.
|
|
180
|
+
*/
|
|
181
|
+
edges: EdgeDiffReport);
|
|
169
182
|
}
|
|
170
183
|
/** A scan (or a whole plan) that could not produce an answer. */
|
|
171
184
|
export declare class ImpactUnavailable {
|
|
@@ -303,12 +316,16 @@ export declare class ImpactRefresh {
|
|
|
303
316
|
private readonly scanner;
|
|
304
317
|
private readonly sidecar;
|
|
305
318
|
private readonly resolver;
|
|
306
|
-
|
|
319
|
+
private readonly edges;
|
|
320
|
+
constructor(scanner?: NxImpactScanner, sidecar?: ImpactSidecar, resolver?: ImpactScopeResolver, edges?: EdgeChangeScanner);
|
|
307
321
|
/**
|
|
308
322
|
* Scans the comparisons in `kinds` that exist here (`VISUALIZE_IMPACT_KINDS` /
|
|
309
|
-
* `GENERATE_IMPACT_KINDS`)
|
|
323
|
+
* `GENERATE_IMPACT_KINDS`), and the edge changes since the fork point against the LIVE graphs
|
|
324
|
+
* `live` computes. Never throws โ Impact is optional, generation is not.
|
|
310
325
|
*/
|
|
311
|
-
run(architectureDir: string, workspaceRoot: string, graph: EnhancedGraph, kinds: readonly ImpactKind[]): Promise<string>;
|
|
326
|
+
run(architectureDir: string, workspaceRoot: string, graph: EnhancedGraph, kinds: readonly ImpactKind[], live: LiveGraphSource): Promise<string>;
|
|
327
|
+
/** `โ
Edges since abc1234: dependencies 1 gained ยท 1 lost; runtime 0 gained ยท 0 lost`, or why not. */
|
|
328
|
+
private edgeLine;
|
|
312
329
|
/** The plan's default when that scan is available, else the first available one. */
|
|
313
330
|
private defaultKind;
|
|
314
331
|
private none;
|