@webpieces/nx-webpieces-rules 0.4.873 → 0.4.875
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/README.md +31 -18
- package/package.json +7 -7
- package/src/executors/visualize/executor.d.ts +17 -3
- package/src/executors/visualize/executor.js +53 -33
- package/src/executors/visualize/executor.js.map +1 -1
- package/src/lib/graph-color-modes.d.ts +56 -34
- package/src/lib/graph-color-modes.js +111 -100
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-filter.client.d.ts +13 -2
- package/src/lib/graph-filter.client.js +27 -7
- package/src/lib/graph-filter.client.js.map +1 -1
- package/src/lib/graph-impact.d.ts +37 -3
- package/src/lib/graph-impact.js +42 -11
- package/src/lib/graph-impact.js.map +1 -1
- package/src/lib/graph-legend.js +1 -1
- package/src/lib/graph-legend.js.map +1 -1
- package/src/lib/graph-level-bands.d.ts +8 -0
- package/src/lib/graph-level-bands.js +13 -1
- package/src/lib/graph-level-bands.js.map +1 -1
- package/src/lib/graph-node-menu.js +10 -108
- package/src/lib/graph-node-menu.js.map +1 -1
- package/src/lib/graph-page-shell.d.ts +31 -1
- package/src/lib/graph-page-shell.js +75 -7
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-page-styles.d.ts +14 -3
- package/src/lib/graph-page-styles.js +89 -18
- package/src/lib/graph-page-styles.js.map +1 -1
- package/src/lib/graph-render-model.d.ts +14 -6
- package/src/lib/graph-render-model.js +22 -8
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +82 -5
- package/src/lib/graph-visualizer.client.js +351 -23
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/graph-visualizer.js +1 -1
- package/src/lib/graph-visualizer.js.map +1 -1
package/README.md
CHANGED
|
@@ -109,23 +109,27 @@ their existing checks independently of visualization.
|
|
|
109
109
|
### Color modes and the page shell
|
|
110
110
|
|
|
111
111
|
`architecture/dependencies.html` opens with the graph filling the viewport and every control
|
|
112
|
-
in a collapsible
|
|
113
|
-
(`/` focuses it, `Esc` unlocks), a **Hide
|
|
114
|
-
button) and the edge key. Help is the **?**
|
|
115
|
-
**Responsibilities** opens a right-hand panel from the
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
in a collapsible charcoal drawer on the left (violet accent): the **Color by** pulldown, the
|
|
113
|
+
**Filter** button, a type-to-search **Lock** field (`/` focuses it, `Esc` unlocks), a **Hide
|
|
114
|
+
unconnected** toggle, the legend (with a pop-out button) and the edge key. Help is the **?**
|
|
115
|
+
popover, zoom floats bottom-right, and **Responsibilities** opens a right-hand panel from the
|
|
116
|
+
drawer footer.
|
|
117
|
+
|
|
118
|
+
The graph answers one question at a time. Pick a mode from the drawer's **Color by** pulldown
|
|
119
|
+
(the one place to switch) or with keys `1` `2` `3`; the choice is remembered per viewer and
|
|
120
|
+
deep-linkable as `#runtime`, `#architecture` or `#impact`. Every box carries the same three
|
|
121
|
+
lines in every mode: `L3 name` (level dimmed, name bold), its role, and every framework tag.
|
|
122
|
+
|
|
123
|
+
- **Runtime** — where the code can run. One runtime is a solid fill. One runtime plus a
|
|
124
|
+
specialization (angular/react inside browser, express inside node) is nested: a frame in the
|
|
125
|
+
base color around a rounded inner box in the specialization's color, which carries the text.
|
|
126
|
+
Several runtimes are full-height vertical stripes in the fixed order browser | node |
|
|
127
|
+
react-native, the text written across them (a specialization there shows in line 3 only). A
|
|
128
|
+
box may depend on a box that carries every color it has.
|
|
126
129
|
- **Architecture** — servers · clients · APIs. A solid fill by role (server, client, app, bundle,
|
|
127
130
|
api-lib, api-client, designed-lib, lib); framework color is not shown.
|
|
128
|
-
- **Impact** — what this branch touches. `architecture:generate`
|
|
131
|
+
- **Impact** — what this branch touches. `architecture:generate` and `architecture:visualize`
|
|
132
|
+
(`pnpm arch:visualize`, no regenerate needed) both run
|
|
129
133
|
`nx show projects --affected --base=<fork point> --json` (no `--head`, so uncommitted and
|
|
130
134
|
untracked work counts). Projects that own a changed file, by nx's own file ownership, are solid
|
|
131
135
|
amber ("touched — files changed on this branch"); the rest of the affected set is light amber
|
|
@@ -135,8 +139,17 @@ box's menu, or with keys `1` `2` `3`; the choice is remembered per viewer and de
|
|
|
135
139
|
it is written to `architecture/.impact/dependencies.impact.js`, never into the committed html or
|
|
136
140
|
json. That directory holds its own `.gitignore` (`*`), so a consuming repo needs no gitignore
|
|
137
141
|
entry. When nx or git cannot answer, no sidecar is written and Impact is disabled with a
|
|
138
|
-
one-line reason;
|
|
139
|
-
|
|
142
|
+
one-line reason; neither command fails over it. Impact state is never written on a box: color
|
|
143
|
+
and legend only. When files changed but no project owns one (a lockfile, the workspace
|
|
144
|
+
manifest), the legend names that workspace-global cause for the everything-affected result.
|
|
145
|
+
|
|
146
|
+
**Filter** opens a popover of three groups that INTERSECT: *Changes on this branch* (Everything ·
|
|
147
|
+
Changed · Changed + what uses them (nx affected) · Changed + what they use · Whole build of this
|
|
148
|
+
branch, each with its project count; disabled with Impact's reason when there is no Impact
|
|
149
|
+
data), *Runtime* chips and *Role* chips (a box matches a group when it carries any selected
|
|
150
|
+
chip). "Show N projects" applies it; active filters show as removable pills in the top bar and
|
|
151
|
+
a count badge on the drawer button. Every remaining box keeps its own L-row, a level left empty
|
|
152
|
+
is a thin labeled band, and edges between remaining boxes stay drawn.
|
|
140
153
|
|
|
141
154
|
The legend is generated per mode from the projects actually drawn, and the framework colors
|
|
142
155
|
are keyed off `KNOWN_FRAMEWORKS` (`@webpieces/rules-sdk`), the same list the framework-tag
|
|
@@ -152,7 +165,7 @@ APIs and systems. The anchor indicator and all surviving node menus offer **Turn
|
|
|
152
165
|
Filter**, restoring the full layout and independent Lock state. Responsibilities
|
|
153
166
|
follow the retained set intersected with architecture Lock. Architecture Lock pins
|
|
154
167
|
its chain in the foreground; hover adds a temporary chain without dimming locked
|
|
155
|
-
nodes or edges, and leaving restores the Lock chain. The locked anchor keeps
|
|
168
|
+
nodes or edges, and leaving restores the Lock chain. The locked anchor keeps a violet outline,
|
|
156
169
|
and hover never changes responsibilities. A filtered-out Lock is suspended until
|
|
157
170
|
the full graph returns. Hover and menu dismissal
|
|
158
171
|
do not clear filtering, and no filter is persisted across reloads. Saved graph facts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.875",
|
|
4
4
|
"description": "Nx-specific webpieces validation rules, graph tooling, executors, and inference plugin.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@webpieces/api-doc-model": "0.4.
|
|
24
|
-
"@webpieces/core-util": "0.4.
|
|
25
|
-
"@webpieces/eslint-rules": "0.4.
|
|
26
|
-
"@webpieces/rules-config": "0.4.
|
|
23
|
+
"@webpieces/api-doc-model": "0.4.875",
|
|
24
|
+
"@webpieces/core-util": "0.4.875",
|
|
25
|
+
"@webpieces/eslint-rules": "0.4.875",
|
|
26
|
+
"@webpieces/rules-config": "0.4.875",
|
|
27
27
|
"madge": "8.0.0",
|
|
28
|
-
"@webpieces/rules-sdk": "0.4.
|
|
28
|
+
"@webpieces/rules-sdk": "0.4.875"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@nx/devkit": ">=18.0.0"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@webpieces/tooling-testkit": "0.4.
|
|
44
|
+
"@webpieces/tooling-testkit": "0.4.875"
|
|
45
45
|
},
|
|
46
46
|
"types": "./src/index.d.ts",
|
|
47
47
|
"bin": {
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Visualize Executor
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Renders the SAVED architecture graph (DOT + HTML), refreshes this branch's Impact sidecar, and opens
|
|
5
|
+
* the visualization in a browser. It never regenerates the graph: the architecture facts stay the
|
|
6
|
+
* saved snapshot. Impact, though, is per-branch and gitignored (graph-impact.ts) — it is exactly the
|
|
7
|
+
* thing that goes stale when you switch branches — so viewing refreshes it with the same
|
|
8
|
+
* `nx show projects --affected --base=<fork point>` scan generate runs, against the saved graph.
|
|
6
9
|
*
|
|
7
10
|
* Usage:
|
|
8
|
-
* nx run architecture:visualize
|
|
11
|
+
* nx run architecture:visualize (pnpm arch:visualize)
|
|
9
12
|
*/
|
|
10
13
|
import type { ExecutorContext } from '@nx/devkit';
|
|
14
|
+
import { SavedSnapshot } from '../../lib/saved-snapshot';
|
|
15
|
+
import { GraphVisualizer } from '../../lib/graph-visualizer';
|
|
16
|
+
import { ImpactRefresh } from '../../lib/graph-impact';
|
|
11
17
|
export interface VisualizeExecutorOptions {
|
|
12
18
|
graphPath?: string;
|
|
13
19
|
}
|
|
14
20
|
export interface ExecutorResult {
|
|
15
21
|
success: boolean;
|
|
16
22
|
}
|
|
23
|
+
/** The view pipeline: load the saved graph, write the page, refresh Impact, open the page. */
|
|
24
|
+
export declare class SavedGraphViewer {
|
|
25
|
+
private readonly visualizer;
|
|
26
|
+
private readonly impact;
|
|
27
|
+
private readonly snapshot;
|
|
28
|
+
constructor(visualizer?: GraphVisualizer, impact?: ImpactRefresh, snapshot?: SavedSnapshot);
|
|
29
|
+
view(options: VisualizeExecutorOptions, context: ExecutorContext): Promise<ExecutorResult>;
|
|
30
|
+
}
|
|
17
31
|
export default function runExecutor(options: VisualizeExecutorOptions, context: ExecutorContext): Promise<ExecutorResult>;
|
|
@@ -2,51 +2,71 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Visualize Executor
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Renders the SAVED architecture graph (DOT + HTML), refreshes this branch's Impact sidecar, and opens
|
|
6
|
+
* the visualization in a browser. It never regenerates the graph: the architecture facts stay the
|
|
7
|
+
* saved snapshot. Impact, though, is per-branch and gitignored (graph-impact.ts) — it is exactly the
|
|
8
|
+
* thing that goes stale when you switch branches — so viewing refreshes it with the same
|
|
9
|
+
* `nx show projects --affected --base=<fork point>` scan generate runs, against the saved graph.
|
|
7
10
|
*
|
|
8
11
|
* Usage:
|
|
9
|
-
* nx run architecture:visualize
|
|
12
|
+
* nx run architecture:visualize (pnpm arch:visualize)
|
|
10
13
|
*/
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SavedGraphViewer = void 0;
|
|
12
16
|
exports.default = runExecutor;
|
|
17
|
+
const tslib_1 = require("tslib");
|
|
18
|
+
const path = tslib_1.__importStar(require("path"));
|
|
13
19
|
const saved_snapshot_1 = require("../../lib/saved-snapshot");
|
|
14
20
|
const graph_loader_1 = require("../../lib/graph-loader");
|
|
15
21
|
const graph_visualizer_1 = require("../../lib/graph-visualizer");
|
|
22
|
+
const graph_impact_1 = require("../../lib/graph-impact");
|
|
16
23
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
17
24
|
const toError_1 = require("../../toError");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
console.log('🎨
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
/** The view pipeline: load the saved graph, write the page, refresh Impact, open the page. */
|
|
26
|
+
class SavedGraphViewer {
|
|
27
|
+
visualizer;
|
|
28
|
+
impact;
|
|
29
|
+
snapshot;
|
|
30
|
+
constructor(visualizer = new graph_visualizer_1.GraphVisualizer(), impact = new graph_impact_1.ImpactRefresh(), snapshot = new saved_snapshot_1.SavedSnapshot()) {
|
|
31
|
+
this.visualizer = visualizer;
|
|
32
|
+
this.impact = impact;
|
|
33
|
+
this.snapshot = snapshot;
|
|
34
|
+
}
|
|
35
|
+
async view(options, context) {
|
|
36
|
+
const graphPath = options.graphPath ?? graph_loader_1.DEFAULT_GRAPH_PATH;
|
|
37
|
+
const workspaceRoot = context.root;
|
|
38
|
+
console.log('\n🎨 Architecture Visualization\n');
|
|
39
|
+
console.log(this.snapshot.message());
|
|
40
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Nx command rendering boundary; success:false propagates failure to Nx
|
|
41
|
+
try {
|
|
42
|
+
console.log('📂 Loading saved graph...');
|
|
43
|
+
const graph = this.snapshot.loadProjects(workspaceRoot, graphPath).projects;
|
|
44
|
+
console.log('🎨 Generating visualization...');
|
|
45
|
+
const vizPaths = this.visualizer.writeVisualization(graph, workspaceRoot);
|
|
46
|
+
console.log(`✅ Generated: ${vizPaths.htmlPath}`);
|
|
47
|
+
// Impact is optional: it reports, it never throws, and it never touches committed files.
|
|
48
|
+
console.log('🟠 Refreshing this branch\'s impact (nx affected since the fork point)...');
|
|
49
|
+
console.log(await this.impact.run(path.dirname(vizPaths.htmlPath), workspaceRoot, graph));
|
|
50
|
+
console.log('\n🌐 Opening visualization in browser...');
|
|
51
|
+
if (this.visualizer.openVisualization(vizPaths.htmlPath)) {
|
|
52
|
+
console.log('✅ Browser opened');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log(`⚠️ Could not auto-open. Open manually: ${vizPaths.htmlPath}`);
|
|
56
|
+
}
|
|
57
|
+
return { success: true };
|
|
39
58
|
}
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
catch (err) {
|
|
60
|
+
const error = (0, toError_1.toError)(err);
|
|
61
|
+
const rendered = error instanceof rules_config_1.RuleFailError ? (0, rules_config_1.renderRuleFailForHuman)(error) : error.message;
|
|
62
|
+
console.error(`❌ Visualization failed for ${graphPath}:`, rendered);
|
|
63
|
+
return { success: false };
|
|
42
64
|
}
|
|
43
|
-
return { success: true };
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
const error = (0, toError_1.toError)(err);
|
|
47
|
-
const rendered = error instanceof rules_config_1.RuleFailError ? (0, rules_config_1.renderRuleFailForHuman)(error) : error.message;
|
|
48
|
-
console.error(`❌ Visualization failed for ${graphPath}:`, rendered);
|
|
49
|
-
return { success: false };
|
|
50
65
|
}
|
|
51
66
|
}
|
|
67
|
+
exports.SavedGraphViewer = SavedGraphViewer;
|
|
68
|
+
// webpieces-disable no-function-outside-class -- nx executor module: nx resolves a default-export function here
|
|
69
|
+
async function runExecutor(options, context) {
|
|
70
|
+
return new SavedGraphViewer().view(options, context);
|
|
71
|
+
}
|
|
52
72
|
//# sourceMappingURL=executor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/visualize/executor.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/visualize/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAiEH,8BAKC;;AApED,mDAA6B;AAE7B,6DAAyD;AACzD,yDAA4D;AAC5D,iEAA6D;AAC7D,yDAAuD;AACvD,0DAAgF;AAChF,2CAAwC;AAUxC,8FAA8F;AAC9F,MAAa,gBAAgB;IAEJ;IACA;IACA;IAHrB,YACqB,aAA8B,IAAI,kCAAe,EAAE,EACnD,SAAwB,IAAI,4BAAa,EAAE,EAC3C,WAA0B,IAAI,8BAAa,EAAE;QAF7C,eAAU,GAAV,UAAU,CAAyC;QACnD,WAAM,GAAN,MAAM,CAAqC;QAC3C,aAAQ,GAAR,QAAQ,CAAqC;IAC/D,CAAC;IAEJ,KAAK,CAAC,IAAI,CAAC,OAAiC,EAAE,OAAwB;QAClE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,iCAAkB,CAAC;QAC1D,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;QAEnC,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QAErC,uIAAuI;QACvI,IAAI,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;YACzC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC;YAE5E,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;YAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;YAC1E,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;YAEjD,yFAAyF;YACzF,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAC;YACzF,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC;YAE1F,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;YACxD,IAAI,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACvD,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACJ,OAAO,CAAC,GAAG,CAAC,2CAA2C,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;YAChF,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,MAAM,QAAQ,GAAG,KAAK,YAAY,4BAAa,CAAC,CAAC,CAAC,IAAA,qCAAsB,EAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;YAChG,OAAO,CAAC,KAAK,CAAC,8BAA8B,SAAS,GAAG,EAAE,QAAQ,CAAC,CAAC;YACpE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;IACL,CAAC;CACJ;AA1CD,4CA0CC;AAED,gHAAgH;AACjG,KAAK,UAAU,WAAW,CACrC,OAAiC,EACjC,OAAwB;IAExB,OAAO,IAAI,gBAAgB,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACzD,CAAC","sourcesContent":["/**\n * Visualize Executor\n *\n * Renders the SAVED architecture graph (DOT + HTML), refreshes this branch's Impact sidecar, and opens\n * the visualization in a browser. It never regenerates the graph: the architecture facts stay the\n * saved snapshot. Impact, though, is per-branch and gitignored (graph-impact.ts) — it is exactly the\n * thing that goes stale when you switch branches — so viewing refreshes it with the same\n * `nx show projects --affected --base=<fork point>` scan generate runs, against the saved graph.\n *\n * Usage:\n * nx run architecture:visualize (pnpm arch:visualize)\n */\n\nimport * as path from 'path';\nimport type { ExecutorContext } from '@nx/devkit';\nimport { SavedSnapshot } from '../../lib/saved-snapshot';\nimport { DEFAULT_GRAPH_PATH } from '../../lib/graph-loader';\nimport { GraphVisualizer } from '../../lib/graph-visualizer';\nimport { ImpactRefresh } from '../../lib/graph-impact';\nimport { RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport { toError } from '../../toError';\n\nexport interface VisualizeExecutorOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/** The view pipeline: load the saved graph, write the page, refresh Impact, open the page. */\nexport class SavedGraphViewer {\n constructor(\n private readonly visualizer: GraphVisualizer = new GraphVisualizer(),\n private readonly impact: ImpactRefresh = new ImpactRefresh(),\n private readonly snapshot: SavedSnapshot = new SavedSnapshot(),\n ) {}\n\n async view(options: VisualizeExecutorOptions, context: ExecutorContext): Promise<ExecutorResult> {\n const graphPath = options.graphPath ?? DEFAULT_GRAPH_PATH;\n const workspaceRoot = context.root;\n\n console.log('\\n🎨 Architecture Visualization\\n');\n console.log(this.snapshot.message());\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Nx command rendering boundary; success:false propagates failure to Nx\n try {\n console.log('📂 Loading saved graph...');\n const graph = this.snapshot.loadProjects(workspaceRoot, graphPath).projects;\n\n console.log('🎨 Generating visualization...');\n const vizPaths = this.visualizer.writeVisualization(graph, workspaceRoot);\n console.log(`✅ Generated: ${vizPaths.htmlPath}`);\n\n // Impact is optional: it reports, it never throws, and it never touches committed files.\n console.log('🟠 Refreshing this branch\\'s impact (nx affected since the fork point)...');\n console.log(await this.impact.run(path.dirname(vizPaths.htmlPath), workspaceRoot, graph));\n\n console.log('\\n🌐 Opening visualization in browser...');\n if (this.visualizer.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 const rendered = error instanceof RuleFailError ? renderRuleFailForHuman(error) : error.message;\n console.error(`❌ Visualization failed for ${graphPath}:`, rendered);\n return { success: false };\n }\n }\n}\n\n// webpieces-disable no-function-outside-class -- nx executor module: nx resolves a default-export function here\nexport default async function runExecutor(\n options: VisualizeExecutorOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n return new SavedGraphViewer().view(options, context);\n}\n"]}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Graph color modes (#1155)
|
|
2
|
+
* Graph color modes (#1155, restyled by #1158)
|
|
3
3
|
*
|
|
4
4
|
* The architecture graph answers ONE question at a time instead of asking every box to encode two
|
|
5
5
|
* (fill = framework AND border = role). Each box is emitted once per mode, so the page can switch
|
|
6
|
-
* modes and re-render without regenerating anything
|
|
6
|
+
* modes and re-render without regenerating anything. Every mode writes the SAME three lines on a box
|
|
7
|
+
* (level + name, role, every framework tag); only the color changes:
|
|
7
8
|
*
|
|
8
|
-
* - RUNTIME "where the code can run".
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* - RUNTIME "where the code can run". One runtime is a solid fill. One runtime plus a
|
|
10
|
+
* specialization (angular/react inside browser, express inside node) is NESTED: an
|
|
11
|
+
* outer frame in the base color around an inner rounded box in the specialization's
|
|
12
|
+
* color, carrying the text. Several runtimes are full-height vertical stripes in the
|
|
13
|
+
* fixed order browser | node | react-native, with the text written across them. The
|
|
14
|
+
* dependency rule becomes visual: a box may depend on a box carrying every color it has.
|
|
14
15
|
* - ARCHITECTURE "servers · clients · APIs". A solid fill by ROLE; framework color is not shown.
|
|
15
16
|
* - IMPACT "what this branch touches". Projects owning a changed file are solid amber
|
|
16
17
|
* ("touched"); the rest of nx's affected set is light amber ("affected": their ci
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
* affected is light slate with a dashed border ("build input": compiled or restored
|
|
19
20
|
* from cache, unchanged); everything else is grey. Which box is which is only known
|
|
20
21
|
* per branch, so every box carries all four variants and the page picks one from the
|
|
21
|
-
* sidecar (graph-impact.ts).
|
|
22
|
+
* sidecar (graph-impact.ts). The state is never written on the box: color + legend.
|
|
22
23
|
*/
|
|
23
24
|
import { KnownFramework } from '@webpieces/rules-sdk';
|
|
24
25
|
export declare enum GraphMode {
|
|
@@ -26,7 +27,7 @@ export declare enum GraphMode {
|
|
|
26
27
|
ARCHITECTURE = "architecture",
|
|
27
28
|
IMPACT = "impact"
|
|
28
29
|
}
|
|
29
|
-
/** One selectable mode as the drawer
|
|
30
|
+
/** One selectable mode as the drawer's Color-by pulldown shows it. */
|
|
30
31
|
export declare class ModeInfo {
|
|
31
32
|
readonly mode: GraphMode;
|
|
32
33
|
readonly name: string;
|
|
@@ -35,25 +36,40 @@ export declare class ModeInfo {
|
|
|
35
36
|
}
|
|
36
37
|
/** In key order: `1` selects the first, `2` the second, `3` the third. */
|
|
37
38
|
export declare const GRAPH_MODES: readonly ModeInfo[];
|
|
38
|
-
/**
|
|
39
|
+
/** Text on a light fill. */
|
|
40
|
+
export declare const DARK_TEXT = "#1a1c22";
|
|
41
|
+
/** Text on a dark fill. */
|
|
42
|
+
export declare const LIGHT_TEXT = "#ffffff";
|
|
43
|
+
/**
|
|
44
|
+
* A framework value's color, the text color that reads on it, and the BASE runtime it is drawn
|
|
45
|
+
* inside (itself, for a base).
|
|
46
|
+
*/
|
|
39
47
|
export declare class FrameworkStyle {
|
|
40
48
|
readonly framework: KnownFramework;
|
|
41
49
|
readonly base: KnownFramework;
|
|
42
50
|
readonly color: string;
|
|
51
|
+
readonly fontColor: string;
|
|
43
52
|
readonly description: string;
|
|
44
|
-
constructor(framework: KnownFramework, base: KnownFramework, color: string, description: string);
|
|
53
|
+
constructor(framework: KnownFramework, base: KnownFramework, color: string, fontColor: string, description: string);
|
|
45
54
|
isBase(): boolean;
|
|
46
55
|
}
|
|
47
56
|
/**
|
|
48
57
|
* Keyed by the shared vocabulary, so a framework added to KNOWN_FRAMEWORKS without a color here is a
|
|
49
58
|
* COMPILE error — not a box silently painted grey and missing from the legend, which is exactly how
|
|
50
|
-
* react-native shipped (#1155).
|
|
59
|
+
* react-native shipped (#1155). Every base is mid-light, so dark text reads on every stripe.
|
|
51
60
|
*/
|
|
52
61
|
export declare const FRAMEWORK_STYLES: Record<KnownFramework, FrameworkStyle>;
|
|
53
62
|
/** Stripe order, left to right. Fixed, so the same set always reads the same way. */
|
|
54
63
|
export declare const BASE_ORDER: readonly KnownFramework[];
|
|
64
|
+
/**
|
|
65
|
+
* The Filter popover's Runtime chips, and the order line 3 of a box lists framework tags in: the
|
|
66
|
+
* bases first, then the specializations.
|
|
67
|
+
*/
|
|
68
|
+
export declare const FRAMEWORK_ORDER: readonly KnownFramework[];
|
|
55
69
|
/** A box with no known framework tag. */
|
|
56
70
|
export declare const NO_RUNTIME_COLOR = "#e4e6ec";
|
|
71
|
+
/** The class Graphviz stamps on a multi-runtime box's `<g>`: its outline is its LAST shape. */
|
|
72
|
+
export declare const STRIPED_CLASS = "wp-striped";
|
|
57
73
|
/** A role's solid fill and the text color that stays readable on it. */
|
|
58
74
|
export declare class RoleStyle {
|
|
59
75
|
readonly role: string;
|
|
@@ -62,7 +78,10 @@ export declare class RoleStyle {
|
|
|
62
78
|
readonly description: string;
|
|
63
79
|
constructor(role: string, fill: string, fontColor: string, description: string);
|
|
64
80
|
}
|
|
65
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Every role role-resolver knows, in legend order (runnable things first) — also the Filter popover's
|
|
83
|
+
* Role chips. `bundle` is indigo so no box color equals the page's violet accent.
|
|
84
|
+
*/
|
|
66
85
|
export declare const ROLE_STYLES: readonly RoleStyle[];
|
|
67
86
|
/** The four impact shades (graph-impact.ts decides which box gets which). */
|
|
68
87
|
export declare const IMPACT_TOUCHED = "#f5a524";
|
|
@@ -70,9 +89,7 @@ export declare const IMPACT_AFFECTED = "#fde3b0";
|
|
|
70
89
|
export declare const IMPACT_BUILD_INPUT = "#e3e9f2";
|
|
71
90
|
export declare const IMPACT_BUILD_INPUT_BORDER = "#7c8aa3";
|
|
72
91
|
export declare const IMPACT_UNTOUCHED = "#eef0f4";
|
|
73
|
-
/**
|
|
74
|
-
export declare const LABEL_PLATE = "#fbfbfd";
|
|
75
|
-
/** What a box's per-mode rendering is computed from. */
|
|
92
|
+
/** What a box's per-mode rendering — and the page's Filter — is computed from. */
|
|
76
93
|
export declare class NodeFacts {
|
|
77
94
|
readonly nodeId: string;
|
|
78
95
|
readonly shortName: string;
|
|
@@ -110,27 +127,32 @@ export declare class NodeModeStyler {
|
|
|
110
127
|
dots(facts: NodeFacts): NodeModeDots;
|
|
111
128
|
/** Fold one drawn box into the legend facts. */
|
|
112
129
|
record(legend: LegendFacts, facts: NodeFacts): void;
|
|
113
|
-
private statement;
|
|
114
|
-
private plainLabel;
|
|
115
|
-
private runtimeMeta;
|
|
116
|
-
private envMeta;
|
|
117
|
-
private architectureAttrs;
|
|
118
130
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* is an HTML-like table of three rows: a band of equal stripes, a light PLATE carrying the name and
|
|
122
|
-
* meta across all of them, and the same stripes again. The stripes and insets stay visible above
|
|
123
|
-
* and below the text, and the text never straddles two colors.
|
|
131
|
+
* Line 3 of every box: every framework tag, bases before specializations, any tag outside the
|
|
132
|
+
* shared vocabulary last (it still runs somewhere somebody declared).
|
|
124
133
|
*/
|
|
125
|
-
|
|
134
|
+
frameworkLine(frameworks: string[]): string;
|
|
135
|
+
/**
|
|
136
|
+
* The three lines, identical in every mode: `L3 name` (level dimmed, name bold), the role, and
|
|
137
|
+
* the framework tags. The level is dimmed by giving its text color an alpha channel, which
|
|
138
|
+
* Graphviz writes as `fill-opacity`, so it dims on any fill.
|
|
139
|
+
*/
|
|
140
|
+
labelText(facts: NodeFacts, fontColor: string): string;
|
|
126
141
|
/** `filled` for one runtime; equal `striped` weights for several (the last takes the remainder). */
|
|
127
142
|
stripeAttrs(bases: KnownFramework[]): string;
|
|
143
|
+
private statement;
|
|
144
|
+
/** A solid fill, the three lines on it. `extra` carries border attributes. */
|
|
145
|
+
private plain;
|
|
146
|
+
/**
|
|
147
|
+
* Runtime mode. No runtime, or one runtime with no specialization: a solid fill. One runtime with a
|
|
148
|
+
* specialization: NESTED. Several runtimes: full-height stripes with the text across them (a
|
|
149
|
+
* specialization on such a box shows in line 3 only).
|
|
150
|
+
*/
|
|
151
|
+
private runtime;
|
|
128
152
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
153
|
+
* An outer cell in the base color whose padding is the visible frame, holding an inner ROUNDED
|
|
154
|
+
* table in the specialization's color that carries the text. The node's own polygon stays its
|
|
155
|
+
* FIRST shape, so the hover glow and the lock outline land on the outer box only.
|
|
132
156
|
*/
|
|
133
|
-
private
|
|
134
|
-
/** One stripe of a band: the base color, with its specialization inset inside a margin of it. */
|
|
135
|
-
private stripeCell;
|
|
157
|
+
private nestedTable;
|
|
136
158
|
}
|