@webpieces/nx-webpieces-rules 0.4.856 โ 0.4.858
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 +5 -1
- package/package.json +7 -7
- package/src/executors/validate-api-relations/executor.js +1 -0
- package/src/executors/validate-api-relations/executor.js.map +1 -1
- package/src/lib/di-graph/angular-providers.js +7 -0
- package/src/lib/di-graph/angular-providers.js.map +1 -1
- package/src/lib/di-graph/bindings.js +5 -0
- package/src/lib/di-graph/bindings.js.map +1 -1
- package/src/lib/di-graph/runtime-client-bindings.d.ts +8 -0
- package/src/lib/di-graph/runtime-client-bindings.js +38 -0
- package/src/lib/di-graph/runtime-client-bindings.js.map +1 -0
- package/src/lib/di-graph/token-resolver.js +14 -0
- package/src/lib/di-graph/token-resolver.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +1 -1
- package/src/lib/graph-visualizer.client.js +26 -16
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/graph-visualizer.js +3 -0
- package/src/lib/graph-visualizer.js.map +1 -1
package/README.md
CHANGED
|
@@ -114,7 +114,11 @@ incoming and outgoing chains, then reruns Graphviz. Architecture L-number rows k
|
|
|
114
114
|
their original identities; runtime chains include rendered queues, clocks, external
|
|
115
115
|
APIs and systems. The anchor indicator and all surviving node menus offer **Turn off
|
|
116
116
|
Filter**, restoring the full layout and independent Lock state. Responsibilities
|
|
117
|
-
follow the retained set intersected with architecture Lock.
|
|
117
|
+
follow the retained set intersected with architecture Lock. Architecture Lock pins
|
|
118
|
+
its chain in the foreground; hover adds a temporary chain without dimming locked
|
|
119
|
+
nodes or edges, and leaving restores the Lock chain. The locked anchor keeps an amber outline,
|
|
120
|
+
and hover never changes responsibilities. A filtered-out Lock is suspended until
|
|
121
|
+
the full graph returns. Hover and menu dismissal
|
|
118
122
|
do not clear filtering, and no filter is persisted across reloads. Saved graph facts
|
|
119
123
|
and visibility options remain untouched. See
|
|
120
124
|
[dependency graphs](../../../docs/architecture/dependency-graphs.md#filter-unconnected)
|
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.858",
|
|
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.858",
|
|
24
|
+
"@webpieces/core-util": "0.4.858",
|
|
25
|
+
"@webpieces/eslint-rules": "0.4.858",
|
|
26
|
+
"@webpieces/rules-config": "0.4.858",
|
|
27
27
|
"madge": "8.0.0",
|
|
28
|
-
"@webpieces/rules-sdk": "0.4.
|
|
28
|
+
"@webpieces/rules-sdk": "0.4.858"
|
|
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.858"
|
|
45
45
|
},
|
|
46
46
|
"types": "./src/index.d.ts",
|
|
47
47
|
"bin": {
|
|
@@ -32,6 +32,7 @@ async function runExecutor(_options, context) {
|
|
|
32
32
|
const rawGraph = await (0, graph_generator_1.generateGraph)();
|
|
33
33
|
const graph = (0, graph_sorter_1.sortGraphTopologically)(rawGraph);
|
|
34
34
|
const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
|
|
35
|
+
(0, graph_metadata_1.enrichGraph)(graph, projectInfos, workspaceRoot);
|
|
35
36
|
new approved_graph_1.ApprovedWiringGraph().attach(workspaceRoot, graph, projectInfos);
|
|
36
37
|
const saved = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot);
|
|
37
38
|
if (saved === null)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-api-relations/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AA2BH,
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-api-relations/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate API Relations Executor\n *\n * Fails when a runnable project (role:server / role:client) depends on an api-lib\n * but the source scan finds it neither IMPLEMENTS (serves via addRoutes) nor USES\n * (calls via createRpcClient/createPubSubClient) any of that api-lib's contracts.\n * Such an edge is an unexplained dependency โ a dead import or forgotten wiring.\n *\n * Usage:\n * nx run architecture:validate-api-relations\n */\n\nimport { RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { collectProjectInfo, enrichGraph } from '../../lib/graph-metadata';\nimport { ApprovedWiringGraph } from '../../lib/runtime-wiring/approved-graph';\nimport { loadBlessedGraph } from '../../lib/graph-loader';\nimport { ApiContractFiles } from '../../lib/api-contract-files';\nimport {\n ApiRelationEvidence,\n ApiOwnerIdentity,\n findUnclassifiedApiDeps,\n describeUnclassifiedApiDep,\n} from '../../lib/api-usage/api-relations-validator';\nimport { toError } from '../../toError';\n\nexport interface ValidateApiRelationsOptions {\n // No options needed\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n// webpieces-disable no-function-outside-class -- nx executor entry point (default export), like every sibling executor\nexport default async function runExecutor(\n _options: ValidateApiRelationsOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n console.log('\\n๐ Validating API Relations (implements/uses per api-lib dependency)\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Full (un-reduced) graph so a direct api-lib dependency is never hidden by\n // transitive reduction, then attach the derived apiRelations.\n const rawGraph = await generateGraph();\n const graph = sortGraphTopologically(rawGraph);\n const projectInfos = await collectProjectInfo();\n enrichGraph(graph, projectInfos, workspaceRoot);\n new ApprovedWiringGraph().attach(workspaceRoot, graph, projectInfos);\n const saved = loadBlessedGraph(workspaceRoot);\n if (saved === null) return { success: false };\n const contracts = new ApiContractFiles().load(\n workspaceRoot,\n 'architecture/dependencies.json',\n saved.apiContractFiles,\n );\n const scan = new ApiRelationEvidence(\n new Set(Object.keys(graph).filter((name) => graph[name].runtimeComposition)),\n new Set(Object.values(contracts).map((contract) => contract.owner)),\n [],\n new Map(\n Object.entries(contracts).map(\n (entry) => [entry[0], new ApiOwnerIdentity(entry[0], entry[1].owner)] as const,\n ),\n ),\n );\n const violations = findUnclassifiedApiDeps(graph, projectInfos, scan);\n if (violations.length === 0) {\n console.log('โ
Every server/client api-lib dependency is implemented or used.');\n return { success: true };\n }\n\n console.error(`โ ${violations.length} unexplained api-lib dependenc(ies):\\n`);\n for (const violation of violations) {\n console.error(describeUnclassifiedApiDep(violation));\n console.error('');\n }\n return { success: false };\n } catch (err: unknown) {\n const error = toError(err);\n console.error(\n 'โ API relations validation failed:',\n error instanceof RuleFailError ? renderRuleFailForHuman(error) : error.message,\n );\n return { success: false };\n }\n}\n"]}
|
|
@@ -27,6 +27,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
27
27
|
exports.collectAngularProviders = collectAngularProviders;
|
|
28
28
|
const tslib_1 = require("tslib");
|
|
29
29
|
const ts = tslib_1.__importStar(require("typescript"));
|
|
30
|
+
const runtime_client_bindings_1 = require("./runtime-client-bindings");
|
|
30
31
|
const model_1 = require("./model");
|
|
31
32
|
const bindings_1 = require("./bindings");
|
|
32
33
|
const token_resolver_1 = require("./token-resolver");
|
|
@@ -153,11 +154,17 @@ function collectInjectableSelfBinding(cls, checker, workspaceRoot, table) {
|
|
|
153
154
|
*/
|
|
154
155
|
function collectAngularProviders(program, checker, workspaceRoot) {
|
|
155
156
|
const table = new bindings_1.BindingTable();
|
|
157
|
+
const clients = new runtime_client_bindings_1.RuntimeClientBindings();
|
|
156
158
|
for (const sourceFile of program.getSourceFiles()) {
|
|
157
159
|
if (!isAnalyzableFile(sourceFile))
|
|
158
160
|
continue;
|
|
159
161
|
const file = (0, token_resolver_1.relativeFile)(workspaceRoot, sourceFile);
|
|
160
162
|
const visit = (node) => {
|
|
163
|
+
if (ts.isCallExpression(node)) {
|
|
164
|
+
const client = clients.collectBrowser(node, checker, workspaceRoot);
|
|
165
|
+
if (client !== undefined)
|
|
166
|
+
table.add(client);
|
|
167
|
+
}
|
|
161
168
|
if (ts.isClassDeclaration(node)) {
|
|
162
169
|
collectInjectableSelfBinding(node, checker, workspaceRoot, table);
|
|
163
170
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"angular-providers.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/angular-providers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Angular Provider Table (pass 1, Angular flavor)\n *\n * The Angular analog of `bindings.ts` (Inversify): scans the program and maps\n * Angular's provider forms onto the SAME token-keyed {@link BindingTable} the\n * shared walker consumes, so `angular-analyzer.ts` reuses every downstream\n * mechanism (leaf labeling, factory-dep edges, unresolved handling).\n *\n * Provider sources (v1 flat global table โ component-scoped shadowing is an\n * accepted approximation):\n * - Any `providers: [...]` array (ApplicationConfig, `@Component`, `@Directive`)\n * - `@Injectable({ providedIn: 'root' | 'platform' | 'any' })` self-registration\n *\n * Provider forms per array element:\n * - bare class `Foo` โ to/self binding (implClass=Foo)\n * - `{ provide, useClass }` โ to binding (implClass)\n * - `{ provide, useValue }` โ toConstantValue leaf\n * - `{ provide, useFactory, deps: [A, B] }` โ toDynamicValue leaf + factoryDeps\n * - `{ provide, useExisting }` โ alias โ target impl class\n * - `multi: true` โ multiple bindings per token (fan-out)\n *\n * Framework-internal `provideXxx()` calls (`provideRouter`,\n * `provideZoneChangeDetection`, ...) have no DI leaves and are skipped.\n */\n\nimport * as ts from 'typescript';\nimport { Binding, DiScope, TokenRef } from './model';\nimport {\n BindingTable,\n classDecorators,\n decoratorCall,\n decoratorName,\n isApiClientBoundary,\n resolveClassDeclaration,\n} from './bindings';\nimport { classTokenKey, relativeFile, resolveTokenKey } from './token-resolver';\n\n// Angular injector-scoped providers are effectively singletons at their scope.\nconst ANGULAR_SCOPE: DiScope = 'singleton';\n\nfunction isAnalyzableFile(sourceFile: ts.SourceFile): boolean {\n if (sourceFile.isDeclarationFile) return false;\n if (sourceFile.fileName.includes('/node_modules/')) return false;\n return true;\n}\n\n/** First line of an expression's source text, truncated for leaf labels. */\nfunction firstLine(text: string): string {\n const line = text.split('\\n')[0].trim();\n return line.length > 60 ? line.slice(0, 57) + '...' : line;\n}\n\n/** Pull `{ name: value }` properties out of an object literal into a map. */\nfunction objectProps(obj: ts.ObjectLiteralExpression): Map<string, ts.Expression> {\n const props = new Map<string, ts.Expression>();\n for (const prop of obj.properties) {\n if (ts.isPropertyAssignment(prop) && (ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name))) {\n props.set(prop.name.text, prop.initializer);\n }\n }\n return props;\n}\n\n/** Resolve each `deps: [A, B]` element to a token reference for factory-dep edges. */\nfunction collectDeps(depsExpr: ts.Expression | undefined, checker: ts.TypeChecker, workspaceRoot: string): TokenRef[] {\n if (!depsExpr || !ts.isArrayLiteralExpression(depsExpr)) return [];\n const deps: TokenRef[] = [];\n for (const element of depsExpr.elements) {\n // Simple token form (`EnvironmentConfig`); the decorated array form\n // (`[new Optional(), Token]`) is out of scope for v1.\n if (ts.isIdentifier(element) || ts.isPropertyAccessExpression(element)) {\n const cls = resolveClassDeclaration(element, checker);\n deps.push(cls ? classTokenKey(cls, workspaceRoot) : resolveTokenKey(element, checker, workspaceRoot));\n }\n }\n return deps;\n}\n\n/** Record one provider-object literal (`{ provide, useX }`) as a binding. */\nfunction collectProviderObject(\n obj: ts.ObjectLiteralExpression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n file: string,\n table: BindingTable,\n): void {\n const props = objectProps(obj);\n const provideExpr = props.get('provide');\n if (!provideExpr) return;\n\n const provideClass = resolveClassDeclaration(provideExpr, checker);\n const token = provideClass\n ? classTokenKey(provideClass, workspaceRoot)\n : resolveTokenKey(provideExpr, checker, workspaceRoot);\n\n const useClass = props.get('useClass');\n const useValue = props.get('useValue');\n const useFactory = props.get('useFactory');\n const useExisting = props.get('useExisting');\n\n if (useClass) {\n const impl = resolveClassDeclaration(useClass, checker);\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, impl, useClass.getText(), file));\n return;\n }\n if (useExisting) {\n // Alias: T resolves to whatever `useExisting` points at โ resolve through\n // to the target impl class so the walk continues into its dependencies.\n const impl = resolveClassDeclaration(useExisting, checker);\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, impl, useExisting.getText(), file));\n return;\n }\n if (useFactory) {\n const deps = collectDeps(props.get('deps'), checker, workspaceRoot);\n const isApiBoundary = isApiClientBoundary(useFactory, checker);\n table.add(\n new Binding(token.key, token.display, 'toDynamicValue', ANGULAR_SCOPE, null, firstLine(useFactory.getText()), file, deps, isApiBoundary),\n );\n return;\n }\n if (useValue) {\n table.add(\n new Binding(token.key, token.display, 'toConstantValue', ANGULAR_SCOPE, null, firstLine(useValue.getText()), file),\n );\n return;\n }\n // `{ provide: T }` with no recipe โ treat the token itself as the impl class.\n if (provideClass) {\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, provideClass, provideExpr.getText(), file));\n }\n}\n\n/** Record one element of a `providers: [...]` array. */\nfunction collectProviderElement(\n element: ts.Expression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n file: string,\n table: BindingTable,\n): void {\n // Framework-internal `provideRouter(...)` / `provideZoneChangeDetection(...)`.\n if (ts.isCallExpression(element)) return;\n\n // Bare class provider: `providers: [MyService]` โ useClass: MyService.\n if (ts.isIdentifier(element) || ts.isPropertyAccessExpression(element)) {\n const cls = resolveClassDeclaration(element, checker);\n if (cls) {\n const token = classTokenKey(cls, workspaceRoot);\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, cls, element.getText(), file));\n }\n return;\n }\n\n if (ts.isObjectLiteralExpression(element)) {\n collectProviderObject(element, checker, workspaceRoot, file, table);\n }\n}\n\n/** `@Injectable({ providedIn: 'root' | 'platform' | 'any' })` self-registration. */\nfunction collectInjectableSelfBinding(\n cls: ts.ClassDeclaration,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n for (const decorator of classDecorators(cls)) {\n if (decoratorName(decorator) !== 'Injectable') continue;\n const call = decoratorCall(decorator);\n const arg = call?.arguments[0];\n if (!arg || !ts.isObjectLiteralExpression(arg)) return;\n const providedIn = objectProps(arg).get('providedIn');\n if (providedIn && ts.isStringLiteralLike(providedIn)) {\n const token = classTokenKey(cls, workspaceRoot);\n const file = relativeFile(workspaceRoot, cls.getSourceFile());\n table.add(new Binding(token.key, token.display, 'decorator', ANGULAR_SCOPE, cls, '', file));\n }\n return;\n }\n}\n\n/**\n * Collect every Angular provider in the program into a token-keyed table.\n * `checker` is required so cross-package class tokens (`SaveApi`, `ClientConfig`)\n * resolve to the same declaration the injection sites reference.\n */\nexport function collectAngularProviders(\n program: ts.Program,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n): BindingTable {\n const table = new BindingTable();\n\n for (const sourceFile of program.getSourceFiles()) {\n if (!isAnalyzableFile(sourceFile)) continue;\n const file = relativeFile(workspaceRoot, sourceFile);\n\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n collectInjectableSelfBinding(node, checker, workspaceRoot, table);\n } else if (\n ts.isPropertyAssignment(node) &&\n (ts.isIdentifier(node.name) || ts.isStringLiteralLike(node.name)) &&\n node.name.text === 'providers' &&\n ts.isArrayLiteralExpression(node.initializer)\n ) {\n for (const element of node.initializer.elements) {\n collectProviderElement(element, checker, workspaceRoot, file, table);\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n }\n\n return table;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"angular-providers.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/angular-providers.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;;AAmKH,0DAmCC;;AApMD,uDAAiC;AACjC,uEAAkE;AAClE,mCAAqD;AACrD,yCAOoB;AACpB,qDAAgF;AAEhF,+EAA+E;AAC/E,MAAM,aAAa,GAAY,WAAW,CAAC;AAE3C,SAAS,gBAAgB,CAAC,UAAyB;IAC/C,IAAI,UAAU,CAAC,iBAAiB;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAAE,OAAO,KAAK,CAAC;IACjE,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,4EAA4E;AAC5E,SAAS,SAAS,CAAC,IAAY;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACxC,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/D,CAAC;AAED,6EAA6E;AAC7E,SAAS,WAAW,CAAC,GAA+B;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAChC,IAAI,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACrG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAChD,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,sFAAsF;AACtF,SAAS,WAAW,CAAC,QAAmC,EAAE,OAAuB,EAAE,aAAqB;IACpG,IAAI,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC,wBAAwB,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAC;IACnE,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACtC,oEAAoE;QACpE,sDAAsD;QACtD,IAAI,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,0BAA0B,CAAC,OAAO,CAAC,EAAE,CAAC;YACrE,MAAM,GAAG,GAAG,IAAA,kCAAuB,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACtD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAA,8BAAa,EAAC,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,IAAA,gCAAe,EAAC,OAAO,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;QAC1G,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,6EAA6E;AAC7E,SAAS,qBAAqB,CAC1B,GAA+B,EAC/B,OAAuB,EACvB,aAAqB,EACrB,IAAY,EACZ,KAAmB;IAEnB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACzC,IAAI,CAAC,WAAW;QAAE,OAAO;IAEzB,MAAM,YAAY,GAAG,IAAA,kCAAuB,EAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,YAAY;QACtB,CAAC,CAAC,IAAA,8BAAa,EAAC,YAAY,EAAE,aAAa,CAAC;QAC5C,CAAC,CAAC,IAAA,gCAAe,EAAC,WAAW,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IAE3D,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAE7C,IAAI,QAAQ,EAAE,CAAC;QACX,MAAM,IAAI,GAAG,IAAA,kCAAuB,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACxD,KAAK,CAAC,GAAG,CAAC,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QACtG,OAAO;IACX,CAAC;IACD,IAAI,WAAW,EAAE,CAAC;QACd,0EAA0E;QAC1E,wEAAwE;QACxE,MAAM,IAAI,GAAG,IAAA,kCAAuB,EAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC3D,KAAK,CAAC,GAAG,CAAC,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QACzG,OAAO;IACX,CAAC;IACD,IAAI,UAAU,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QACpE,MAAM,aAAa,GAAG,IAAA,8BAAmB,EAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC/D,KAAK,CAAC,GAAG,CACL,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,CAC3I,CAAC;QACF,OAAO;IACX,CAAC;IACD,IAAI,QAAQ,EAAE,CAAC;QACX,KAAK,CAAC,GAAG,CACL,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,CACrH,CAAC;QACF,OAAO;IACX,CAAC;IACD,8EAA8E;IAC9E,IAAI,YAAY,EAAE,CAAC;QACf,KAAK,CAAC,GAAG,CAAC,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;IACrH,CAAC;AACL,CAAC;AAED,wDAAwD;AACxD,SAAS,sBAAsB,CAC3B,OAAsB,EACtB,OAAuB,EACvB,aAAqB,EACrB,IAAY,EACZ,KAAmB;IAEnB,+EAA+E;IAC/E,IAAI,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC;QAAE,OAAO;IAEzC,uEAAuE;IACvE,IAAI,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,0BAA0B,CAAC,OAAO,CAAC,EAAE,CAAC;QACrE,MAAM,GAAG,GAAG,IAAA,kCAAuB,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACtD,IAAI,GAAG,EAAE,CAAC;YACN,MAAM,KAAK,GAAG,IAAA,8BAAa,EAAC,GAAG,EAAE,aAAa,CAAC,CAAC;YAChD,KAAK,CAAC,GAAG,CAAC,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QACxG,CAAC;QACD,OAAO;IACX,CAAC;IAED,IAAI,EAAE,CAAC,yBAAyB,CAAC,OAAO,CAAC,EAAE,CAAC;QACxC,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACxE,CAAC;AACL,CAAC;AAED,oFAAoF;AACpF,SAAS,4BAA4B,CACjC,GAAwB,EACxB,OAAuB,EACvB,aAAqB,EACrB,KAAmB;IAEnB,KAAK,MAAM,SAAS,IAAI,IAAA,0BAAe,EAAC,GAAG,CAAC,EAAE,CAAC;QAC3C,IAAI,IAAA,wBAAa,EAAC,SAAS,CAAC,KAAK,YAAY;YAAE,SAAS;QACxD,MAAM,IAAI,GAAG,IAAA,wBAAa,EAAC,SAAS,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,yBAAyB,CAAC,GAAG,CAAC;YAAE,OAAO;QACvD,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACtD,IAAI,UAAU,IAAI,EAAE,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC;YACnD,MAAM,KAAK,GAAG,IAAA,8BAAa,EAAC,GAAG,EAAE,aAAa,CAAC,CAAC;YAChD,MAAM,IAAI,GAAG,IAAA,6BAAY,EAAC,aAAa,EAAE,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC;YAC9D,KAAK,CAAC,GAAG,CAAC,IAAI,eAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QAChG,CAAC;QACD,OAAO;IACX,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,SAAgB,uBAAuB,CACnC,OAAmB,EACnB,OAAuB,EACvB,aAAqB;IAErB,MAAM,KAAK,GAAG,IAAI,uBAAY,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,+CAAqB,EAAE,CAAC;IAE5C,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;QAChD,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC;YAAE,SAAS;QAC5C,MAAM,IAAI,GAAG,IAAA,6BAAY,EAAC,aAAa,EAAE,UAAU,CAAC,CAAC;QAErD,MAAM,KAAK,GAAG,CAAC,IAAa,EAAQ,EAAE;YAClC,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;gBACpE,IAAI,MAAM,KAAK,SAAS;oBAAE,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAChD,CAAC;YACD,IAAI,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,4BAA4B,CAAC,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;YACtE,CAAC;iBAAM,IACH,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC;gBAC7B,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACjE,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,WAAW;gBAC9B,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,WAAW,CAAC,EAC/C,CAAC;gBACC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;oBAC9C,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;gBACzE,CAAC;YACL,CAAC;YACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC,CAAC;QACF,KAAK,CAAC,UAAU,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["/**\n * Angular Provider Table (pass 1, Angular flavor)\n *\n * The Angular analog of `bindings.ts` (Inversify): scans the program and maps\n * Angular's provider forms onto the SAME token-keyed {@link BindingTable} the\n * shared walker consumes, so `angular-analyzer.ts` reuses every downstream\n * mechanism (leaf labeling, factory-dep edges, unresolved handling).\n *\n * Provider sources (v1 flat global table โ component-scoped shadowing is an\n * accepted approximation):\n * - Any `providers: [...]` array (ApplicationConfig, `@Component`, `@Directive`)\n * - `@Injectable({ providedIn: 'root' | 'platform' | 'any' })` self-registration\n *\n * Provider forms per array element:\n * - bare class `Foo` โ to/self binding (implClass=Foo)\n * - `{ provide, useClass }` โ to binding (implClass)\n * - `{ provide, useValue }` โ toConstantValue leaf\n * - `{ provide, useFactory, deps: [A, B] }` โ toDynamicValue leaf + factoryDeps\n * - `{ provide, useExisting }` โ alias โ target impl class\n * - `multi: true` โ multiple bindings per token (fan-out)\n *\n * Framework-internal `provideXxx()` calls (`provideRouter`,\n * `provideZoneChangeDetection`, ...) have no DI leaves and are skipped.\n */\n\nimport * as ts from 'typescript';\nimport { RuntimeClientBindings } from './runtime-client-bindings';\nimport { Binding, DiScope, TokenRef } from './model';\nimport {\n BindingTable,\n classDecorators,\n decoratorCall,\n decoratorName,\n isApiClientBoundary,\n resolveClassDeclaration,\n} from './bindings';\nimport { classTokenKey, relativeFile, resolveTokenKey } from './token-resolver';\n\n// Angular injector-scoped providers are effectively singletons at their scope.\nconst ANGULAR_SCOPE: DiScope = 'singleton';\n\nfunction isAnalyzableFile(sourceFile: ts.SourceFile): boolean {\n if (sourceFile.isDeclarationFile) return false;\n if (sourceFile.fileName.includes('/node_modules/')) return false;\n return true;\n}\n\n/** First line of an expression's source text, truncated for leaf labels. */\nfunction firstLine(text: string): string {\n const line = text.split('\\n')[0].trim();\n return line.length > 60 ? line.slice(0, 57) + '...' : line;\n}\n\n/** Pull `{ name: value }` properties out of an object literal into a map. */\nfunction objectProps(obj: ts.ObjectLiteralExpression): Map<string, ts.Expression> {\n const props = new Map<string, ts.Expression>();\n for (const prop of obj.properties) {\n if (ts.isPropertyAssignment(prop) && (ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name))) {\n props.set(prop.name.text, prop.initializer);\n }\n }\n return props;\n}\n\n/** Resolve each `deps: [A, B]` element to a token reference for factory-dep edges. */\nfunction collectDeps(depsExpr: ts.Expression | undefined, checker: ts.TypeChecker, workspaceRoot: string): TokenRef[] {\n if (!depsExpr || !ts.isArrayLiteralExpression(depsExpr)) return [];\n const deps: TokenRef[] = [];\n for (const element of depsExpr.elements) {\n // Simple token form (`EnvironmentConfig`); the decorated array form\n // (`[new Optional(), Token]`) is out of scope for v1.\n if (ts.isIdentifier(element) || ts.isPropertyAccessExpression(element)) {\n const cls = resolveClassDeclaration(element, checker);\n deps.push(cls ? classTokenKey(cls, workspaceRoot) : resolveTokenKey(element, checker, workspaceRoot));\n }\n }\n return deps;\n}\n\n/** Record one provider-object literal (`{ provide, useX }`) as a binding. */\nfunction collectProviderObject(\n obj: ts.ObjectLiteralExpression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n file: string,\n table: BindingTable,\n): void {\n const props = objectProps(obj);\n const provideExpr = props.get('provide');\n if (!provideExpr) return;\n\n const provideClass = resolveClassDeclaration(provideExpr, checker);\n const token = provideClass\n ? classTokenKey(provideClass, workspaceRoot)\n : resolveTokenKey(provideExpr, checker, workspaceRoot);\n\n const useClass = props.get('useClass');\n const useValue = props.get('useValue');\n const useFactory = props.get('useFactory');\n const useExisting = props.get('useExisting');\n\n if (useClass) {\n const impl = resolveClassDeclaration(useClass, checker);\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, impl, useClass.getText(), file));\n return;\n }\n if (useExisting) {\n // Alias: T resolves to whatever `useExisting` points at โ resolve through\n // to the target impl class so the walk continues into its dependencies.\n const impl = resolveClassDeclaration(useExisting, checker);\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, impl, useExisting.getText(), file));\n return;\n }\n if (useFactory) {\n const deps = collectDeps(props.get('deps'), checker, workspaceRoot);\n const isApiBoundary = isApiClientBoundary(useFactory, checker);\n table.add(\n new Binding(token.key, token.display, 'toDynamicValue', ANGULAR_SCOPE, null, firstLine(useFactory.getText()), file, deps, isApiBoundary),\n );\n return;\n }\n if (useValue) {\n table.add(\n new Binding(token.key, token.display, 'toConstantValue', ANGULAR_SCOPE, null, firstLine(useValue.getText()), file),\n );\n return;\n }\n // `{ provide: T }` with no recipe โ treat the token itself as the impl class.\n if (provideClass) {\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, provideClass, provideExpr.getText(), file));\n }\n}\n\n/** Record one element of a `providers: [...]` array. */\nfunction collectProviderElement(\n element: ts.Expression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n file: string,\n table: BindingTable,\n): void {\n // Framework-internal `provideRouter(...)` / `provideZoneChangeDetection(...)`.\n if (ts.isCallExpression(element)) return;\n\n // Bare class provider: `providers: [MyService]` โ useClass: MyService.\n if (ts.isIdentifier(element) || ts.isPropertyAccessExpression(element)) {\n const cls = resolveClassDeclaration(element, checker);\n if (cls) {\n const token = classTokenKey(cls, workspaceRoot);\n table.add(new Binding(token.key, token.display, 'to', ANGULAR_SCOPE, cls, element.getText(), file));\n }\n return;\n }\n\n if (ts.isObjectLiteralExpression(element)) {\n collectProviderObject(element, checker, workspaceRoot, file, table);\n }\n}\n\n/** `@Injectable({ providedIn: 'root' | 'platform' | 'any' })` self-registration. */\nfunction collectInjectableSelfBinding(\n cls: ts.ClassDeclaration,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n for (const decorator of classDecorators(cls)) {\n if (decoratorName(decorator) !== 'Injectable') continue;\n const call = decoratorCall(decorator);\n const arg = call?.arguments[0];\n if (!arg || !ts.isObjectLiteralExpression(arg)) return;\n const providedIn = objectProps(arg).get('providedIn');\n if (providedIn && ts.isStringLiteralLike(providedIn)) {\n const token = classTokenKey(cls, workspaceRoot);\n const file = relativeFile(workspaceRoot, cls.getSourceFile());\n table.add(new Binding(token.key, token.display, 'decorator', ANGULAR_SCOPE, cls, '', file));\n }\n return;\n }\n}\n\n/**\n * Collect every Angular provider in the program into a token-keyed table.\n * `checker` is required so cross-package class tokens (`SaveApi`, `ClientConfig`)\n * resolve to the same declaration the injection sites reference.\n */\nexport function collectAngularProviders(\n program: ts.Program,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n): BindingTable {\n const table = new BindingTable();\n const clients = new RuntimeClientBindings();\n\n for (const sourceFile of program.getSourceFiles()) {\n if (!isAnalyzableFile(sourceFile)) continue;\n const file = relativeFile(workspaceRoot, sourceFile);\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) {\n const client = clients.collectBrowser(node, checker, workspaceRoot);\n if (client !== undefined) table.add(client);\n }\n if (ts.isClassDeclaration(node)) {\n collectInjectableSelfBinding(node, checker, workspaceRoot, table);\n } else if (\n ts.isPropertyAssignment(node) &&\n (ts.isIdentifier(node.name) || ts.isStringLiteralLike(node.name)) &&\n node.name.text === 'providers' &&\n ts.isArrayLiteralExpression(node.initializer)\n ) {\n for (const element of node.initializer.elements) {\n collectProviderElement(element, checker, workspaceRoot, file, table);\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n }\n\n return table;\n}\n"]}
|
|
@@ -24,6 +24,7 @@ exports.classDecorators = classDecorators;
|
|
|
24
24
|
exports.collectBindings = collectBindings;
|
|
25
25
|
const tslib_1 = require("tslib");
|
|
26
26
|
const ts = tslib_1.__importStar(require("typescript"));
|
|
27
|
+
const runtime_client_bindings_1 = require("./runtime-client-bindings");
|
|
27
28
|
const model_1 = require("./model");
|
|
28
29
|
const token_resolver_1 = require("./token-resolver");
|
|
29
30
|
const BIND_METHOD_NAMES = new Set(['to', 'toSelf', 'toConstantValue', 'toDynamicValue']);
|
|
@@ -333,6 +334,7 @@ function collectProviderBinding(call, checker, workspaceRoot, table) {
|
|
|
333
334
|
*/
|
|
334
335
|
function collectBindings(program, checker, workspaceRoot) {
|
|
335
336
|
const table = new BindingTable();
|
|
337
|
+
const clients = new runtime_client_bindings_1.RuntimeClientBindings();
|
|
336
338
|
for (const sourceFile of program.getSourceFiles()) {
|
|
337
339
|
if (!isAnalyzableFile(sourceFile))
|
|
338
340
|
continue;
|
|
@@ -340,6 +342,9 @@ function collectBindings(program, checker, workspaceRoot) {
|
|
|
340
342
|
if (ts.isCallExpression(node)) {
|
|
341
343
|
collectBindCall(node, checker, workspaceRoot, table);
|
|
342
344
|
collectProviderBinding(node, checker, workspaceRoot, table);
|
|
345
|
+
const client = clients.collectNode(node, checker, workspaceRoot);
|
|
346
|
+
if (client !== undefined)
|
|
347
|
+
table.add(client);
|
|
343
348
|
}
|
|
344
349
|
else if (ts.isClassDeclaration(node)) {
|
|
345
350
|
collectDecoratorBindings(node, checker, workspaceRoot, table);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/bindings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Binding Table (pass 1 of the DI graph analyzer)\n *\n * Scans every source file in the project's TypeScript program (skipping .d.ts and\n * node_modules) and collects all Inversify bindings into a Map<tokenKey, Binding[]>:\n *\n * - ContainerModule bodies: bind(TOKEN).to(Impl) / .toSelf() / .toConstantValue(x)\n * / .toDynamicValue(fn), with .inSingletonScope() etc.\n * - Decorators: @provideSingleton() / @provideTransient() (self-binding)\n * and @provideSingletonDefaultForApi(TOKEN)\n *\n * Arrays because multiInject tokens (e.g. HEADER_TYPES.PlatformHeadersExtension) are\n * bound once per ContainerModule across several packages.\n */\n\nimport * as ts from 'typescript';\nimport { Binding, BindingKind, DiScope } from './model';\nimport { classTokenKey, relativeFile, resolveTokenKey } from './token-resolver';\n\nconst BIND_METHOD_NAMES = new Set(['to', 'toSelf', 'toConstantValue', 'toDynamicValue']);\n\nexport class BindingTable {\n private readonly byToken = new Map<string, Binding[]>();\n /** providerTokenKey -> the class its get() resolves. See bindFrameworkProvider. */\n private readonly providerTargets = new Map<string, ts.ClassDeclaration>();\n\n add(binding: Binding): void {\n const list = this.byToken.get(binding.tokenKey);\n if (list) {\n list.push(binding);\n } else {\n this.byToken.set(binding.tokenKey, [binding]);\n }\n }\n\n lookup(tokenKey: string): Binding[] {\n return this.byToken.get(tokenKey) ?? [];\n }\n\n /** Record `bindFrameworkProvider(ProviderClass, TargetClass)`. */\n addProviderTarget(providerTokenKey: string, target: ts.ClassDeclaration): void {\n this.providerTargets.set(providerTokenKey, target);\n }\n\n /**\n * The class a Provider hands out, if this token is a registered Provider subclass.\n * The walker follows this so `Factory -> XProvider -> X` is visible in the design,\n * instead of the provider dead-ending as an opaque toDynamicValue leaf.\n */\n providerTarget(providerTokenKey: string): ts.ClassDeclaration | undefined {\n return this.providerTargets.get(providerTokenKey);\n }\n}\n\nfunction isAnalyzableFile(sourceFile: ts.SourceFile): boolean {\n if (sourceFile.isDeclarationFile) return false;\n if (sourceFile.fileName.includes('/node_modules/')) return false;\n return true;\n}\n\n/**\n * A class the walker treats as an EXTERNAL boundary: its declaration lives in a\n * `.d.ts` file or under `node_modules` โ i.e. a published package outside this\n * nx workspace. The exact inverse of {@link isAnalyzableFile}'s file test. In an\n * nx monorepo internal libs resolve through tsconfig path mappings to real `.ts`\n * source, so they are NOT external and keep expanding; only third-party packages\n * (resolved to `.d.ts` in `node_modules`) trip this. Pass-2 renders such a class\n * as a leaf `external` node and stops โ it does not descend into its ctor deps.\n */\nexport function isExternalClass(cls: ts.ClassDeclaration): boolean {\n const sourceFile = cls.getSourceFile();\n return sourceFile.isDeclarationFile || sourceFile.fileName.includes('/node_modules/');\n}\n\n/**\n * Walk `.inSingletonScope()` / `.inTransientScope()` suffixes above a binding call.\n *\n * No scope call at all means TRANSIENT, not \"unknown\": that is inversify's\n * `DEFAULT_DEFAULT_SCOPE`, and no `new Container(...)` in this workspace overrides `defaultScope`.\n */\nfunction scopeFromChain(bindingCall: ts.CallExpression): DiScope {\n let node: ts.Node = bindingCall;\n while (\n node.parent &&\n ts.isPropertyAccessExpression(node.parent) &&\n node.parent.parent &&\n ts.isCallExpression(node.parent.parent)\n ) {\n const methodName = node.parent.name.text;\n if (methodName === 'inSingletonScope') return 'singleton';\n if (methodName === 'inTransientScope') return 'transient';\n node = node.parent.parent;\n }\n return 'transient';\n}\n\n/**\n * If `expr` is (or resolves through the checker to) a class declaration, return it.\n */\nexport function resolveClassDeclaration(\n expr: ts.Expression,\n checker: ts.TypeChecker,\n): ts.ClassDeclaration | null {\n let symbol = checker.getSymbolAtLocation(expr);\n if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {\n symbol = checker.getAliasedSymbol(symbol);\n }\n for (const decl of symbol?.declarations ?? []) {\n if (ts.isClassDeclaration(decl)) return decl;\n }\n return null;\n}\n\n/**\n * Recognize `bind(TOKEN)` at the bottom of a fluent chain. Accepts a bare `bind(...)`\n * identifier call or `options.bind(...)` property call.\n */\nfunction asBindCall(expr: ts.Expression): ts.CallExpression | null {\n if (!ts.isCallExpression(expr)) return null;\n const callee = expr.expression;\n if (ts.isIdentifier(callee) && callee.text === 'bind') return expr;\n if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'bind') return expr;\n return null;\n}\n\n/**\n * Handle one `<receiver>.to*(...)` call: if the receiver bottoms out at bind(TOKEN),\n * record the binding.\n */\nfunction collectBindCall(\n call: ts.CallExpression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n if (!ts.isPropertyAccessExpression(call.expression)) return;\n const methodName = call.expression.name.text;\n if (!BIND_METHOD_NAMES.has(methodName)) return;\n\n const bindCall = asBindCall(call.expression.expression);\n if (!bindCall || bindCall.arguments.length === 0) return;\n\n const tokenExpr = bindCall.arguments[0];\n const file = relativeFile(workspaceRoot, call.getSourceFile());\n const scope = scopeFromChain(call);\n\n if (methodName === 'toSelf') {\n const cls = resolveClassDeclaration(tokenExpr, checker);\n const token = cls\n ? classTokenKey(cls, workspaceRoot)\n : resolveTokenKey(tokenExpr, checker, workspaceRoot);\n table.add(new Binding(token.key, tokenExpr.getText(), 'toSelf', scope, cls, '', file));\n return;\n }\n\n const token = resolveTokenKey(tokenExpr, checker, workspaceRoot);\n\n if (methodName === 'to') {\n const implExpr = call.arguments[0];\n const cls = implExpr ? resolveClassDeclaration(implExpr, checker) : null;\n const valueText = implExpr ? implExpr.getText() : '';\n table.add(new Binding(token.key, token.display, 'to', scope, cls, valueText, file));\n return;\n }\n\n const kind: BindingKind = methodName === 'toConstantValue' ? 'toConstantValue' : 'toDynamicValue';\n const valueExpr = call.arguments[0];\n const valueText = valueExpr ? firstLine(valueExpr.getText()) : '';\n const isApiBoundary = kind === 'toDynamicValue' && valueExpr ? isApiClientBoundary(valueExpr, checker) : false;\n table.add(new Binding(token.key, token.display, kind, scope, null, valueText, file, [], isApiBoundary));\n}\n\n/**\n * True when `expr` (the argument to `.toDynamicValue(...)` / an Angular\n * `useFactory`) builds an API-client proxy: it contains a `createApiClient(Api, ...)`\n * call whose first argument resolves to an @ApiPath-decorated contract class.\n *\n * This is the DI-graph boundary marker for generated (and external) API clients:\n * the walk renders such a binding as an `api` leaf and stops, rather than\n * descending into the client's own transport config (ClientConfig โ ...).\n */\n// webpieces-disable no-function-outside-class -- pure AST predicate, matching every sibling in this file\nexport function isApiClientBoundary(expr: ts.Expression, checker: ts.TypeChecker): boolean {\n const call = findCreateApiClientCall(expr);\n if (!call || call.arguments.length === 0) return false;\n const apiClass = resolveClassDeclaration(call.arguments[0], checker);\n return apiClass ? hasApiPathDecorator(apiClass) : false;\n}\n\n/** Find the first `createApiClient(...)` call anywhere inside `node`, else null. */\n// webpieces-disable no-function-outside-class -- pure AST walker, matching every sibling in this file\nfunction findCreateApiClientCall(node: ts.Node): ts.CallExpression | null {\n if (ts.isCallExpression(node)) {\n const callee = node.expression;\n const name = ts.isIdentifier(callee)\n ? callee.text\n : ts.isPropertyAccessExpression(callee)\n ? callee.name.text\n : null;\n if (name === 'createApiClient') return node;\n }\n let found: ts.CallExpression | null = null;\n ts.forEachChild(node, (child: ts.Node) => {\n if (!found) found = findCreateApiClientCall(child);\n });\n return found;\n}\n\n/** True when the class carries the `@ApiPath(...)` contract decorator. */\n// webpieces-disable no-function-outside-class -- pure AST predicate, matching every sibling in this file\nfunction hasApiPathDecorator(cls: ts.ClassDeclaration): boolean {\n return classDecorators(cls).some((d: ts.Decorator) => decoratorName(d) === 'ApiPath');\n}\n\nfunction firstLine(text: string): string {\n const line = text.split('\\n')[0].trim();\n return line.length > 60 ? line.slice(0, 57) + '...' : line;\n}\n\n/** Return the decorator call expression when `decorator` is `@name(...)`, else null. */\nexport function decoratorCall(decorator: ts.Decorator): ts.CallExpression | null {\n return ts.isCallExpression(decorator.expression) ? decorator.expression : null;\n}\n\n/** The identifier name of a decorator like `@provideSingleton()` or `@inject(X)`. */\nexport function decoratorName(decorator: ts.Decorator): string | null {\n const call = decoratorCall(decorator);\n const callee = call ? call.expression : decorator.expression;\n if (ts.isIdentifier(callee)) return callee.text;\n if (ts.isPropertyAccessExpression(callee)) return callee.name.text;\n return null;\n}\n\nexport function classDecorators(cls: ts.ClassDeclaration): ts.Decorator[] {\n const decorators = ts.getDecorators(cls);\n return decorators ? [...decorators] : [];\n}\n\n/**\n * Scope of an `@injectable(bindingScopeValues.Singleton|Transient)` SELF-binding, read from the\n * decorator ARGUMENT (either `bindingScopeValues.Singleton` or the raw 'Singleton'/'Transient'\n * string), NOT the decorator name like `@provideSingleton`.\n *\n * Returns null for a BARE `@injectable()` (no scope argument): that is NOT a self-binding โ it just\n * marks a class injectable for an explicit `bind(TOKEN).to(ThatClass)` elsewhere (e.g. SimpleCounter\n * bound to a Counter token). Only `@injectable(<scope>)` with an argument opts a concrete class into\n * autobind self-binding, the inject-by-type replacement for @provideSingleton.\n */\n// webpieces-disable no-function-outside-class -- pure AST predicate, matching every sibling in this file\nfunction injectableScope(decorator: ts.Decorator): DiScope | null {\n const call = decoratorCall(decorator);\n const arg = call?.arguments[0];\n if (!arg) return null;\n const scopeName = ts.isPropertyAccessExpression(arg)\n ? arg.name.text\n : ts.isStringLiteral(arg)\n ? arg.text\n : '';\n if (scopeName === 'Singleton') return 'singleton';\n if (scopeName === 'Transient') return 'transient';\n return null;\n}\n\nfunction collectDecoratorBindings(\n cls: ts.ClassDeclaration,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n const file = relativeFile(workspaceRoot, cls.getSourceFile());\n // A provide* decorator is the explicit binding; when present it wins over a plain @injectable on\n // the same class (a transitional state where both appear). Only a class with @injectable and NO\n // provide* decorator self-binds via autobind.\n const PROVIDE_BINDERS = new Set([\n 'provideSingleton',\n 'provideTransient',\n 'provideFrameworkSingleton',\n 'provideFrameworkTransient',\n 'provideSingletonDefaultForApi',\n 'provideFrameworkSingletonDefaultForApi',\n ]);\n const hasProvideBinding = classDecorators(cls).some((d: ts.Decorator) => {\n const n = decoratorName(d);\n return n !== null && PROVIDE_BINDERS.has(n);\n });\n for (const decorator of classDecorators(cls)) {\n const name = decoratorName(decorator);\n // provideFrameworkSingleton(As)/Transient are the framework-registry twins of\n // provideSingleton(As)/Transient (see @webpieces/core-context frameworkProvide.ts) โ\n // same self/token binding, same scopes.\n if (\n name === 'provideSingleton' ||\n name === 'provideTransient' ||\n name === 'provideFrameworkSingleton' ||\n name === 'provideFrameworkTransient'\n ) {\n const token = classTokenKey(cls, workspaceRoot);\n const transient = name === 'provideTransient' || name === 'provideFrameworkTransient';\n const scope: DiScope = transient ? 'transient' : 'singleton';\n table.add(new Binding(token.key, token.display, 'decorator', scope, cls, '', file));\n } else if (name === 'provideSingletonDefaultForApi' || name === 'provideFrameworkSingletonDefaultForApi') {\n const call = decoratorCall(decorator);\n const tokenExpr = call?.arguments[0];\n if (!tokenExpr) continue;\n const token = resolveTokenKey(tokenExpr, checker, workspaceRoot);\n table.add(new Binding(token.key, token.display, 'decorator', 'singleton', cls, '', file));\n } else if (name === 'injectable' && !hasProvideBinding) {\n // @injectable(bindingScopeValues.Singleton|Transient) self-binds the concrete class under\n // the app container's autobind (the inject-by-type replacement for @provideSingleton).\n // A bare @injectable() (scope null) is skipped โ it is bound via an explicit .to(token).\n const scope = injectableScope(decorator);\n if (scope !== null) {\n const token = classTokenKey(cls, workspaceRoot);\n table.add(new Binding(token.key, token.display, 'decorator', scope, cls, '', file));\n }\n }\n }\n}\n\n/**\n * `bindFrameworkProvider(TOKEN, X)` โ the Guice-style Provider registration in\n * @webpieces/core-context. Records that a `Provider<X>` injected under TOKEN yields X.\n *\n * The TOKEN is whatever names the provider (a Symbol, since `Provider<T>` is erased at runtime and\n * cannot be its own token). We never draw a node for it: a Provider is DI plumbing, not wiring.\n * The walker renders `Consumer -> X` directly, and X's OWN binding decides X's scope โ and hence\n * whether the design draws one box (a lazy singleton) or a stack (a fresh instance per get()).\n */\n// webpieces-disable no-function-outside-class -- ts AST visitor, matching every sibling collector in this file\nfunction collectProviderBinding(\n call: ts.CallExpression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n if (!ts.isIdentifier(call.expression) || call.expression.text !== 'bindFrameworkProvider') return;\n if (call.arguments.length < 2) return;\n\n // The token may be a Symbol, a class, anything โ resolveTokenKey canonicalizes all of them.\n const token = resolveTokenKey(call.arguments[0], checker, workspaceRoot);\n const targetClass = resolveClassDeclaration(call.arguments[1], checker);\n if (!targetClass) return;\n\n table.addProviderTarget(token.key, targetClass);\n}\n\n/**\n * Pass 1: collect every binding in the program into a token-keyed table.\n */\nexport function collectBindings(\n program: ts.Program,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n): BindingTable {\n const table = new BindingTable();\n\n for (const sourceFile of program.getSourceFiles()) {\n if (!isAnalyzableFile(sourceFile)) continue;\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) {\n collectBindCall(node, checker, workspaceRoot, table);\n collectProviderBinding(node, checker, workspaceRoot, table);\n } else if (ts.isClassDeclaration(node)) {\n collectDecoratorBindings(node, checker, workspaceRoot, table);\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n }\n\n return table;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bindings.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/bindings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Binding Table (pass 1 of the DI graph analyzer)\n *\n * Scans every source file in the project's TypeScript program (skipping .d.ts and\n * node_modules) and collects all Inversify bindings into a Map<tokenKey, Binding[]>:\n *\n * - ContainerModule bodies: bind(TOKEN).to(Impl) / .toSelf() / .toConstantValue(x)\n * / .toDynamicValue(fn), with .inSingletonScope() etc.\n * - Decorators: @provideSingleton() / @provideTransient() (self-binding)\n * and @provideSingletonDefaultForApi(TOKEN)\n *\n * Arrays because multiInject tokens (e.g. HEADER_TYPES.PlatformHeadersExtension) are\n * bound once per ContainerModule across several packages.\n */\n\nimport * as ts from 'typescript';\nimport { RuntimeClientBindings } from './runtime-client-bindings';\nimport { Binding, BindingKind, DiScope } from './model';\nimport { classTokenKey, relativeFile, resolveTokenKey } from './token-resolver';\n\nconst BIND_METHOD_NAMES = new Set(['to', 'toSelf', 'toConstantValue', 'toDynamicValue']);\n\nexport class BindingTable {\n private readonly byToken = new Map<string, Binding[]>();\n /** providerTokenKey -> the class its get() resolves. See bindFrameworkProvider. */\n private readonly providerTargets = new Map<string, ts.ClassDeclaration>();\n\n add(binding: Binding): void {\n const list = this.byToken.get(binding.tokenKey);\n if (list) {\n list.push(binding);\n } else {\n this.byToken.set(binding.tokenKey, [binding]);\n }\n }\n\n lookup(tokenKey: string): Binding[] {\n return this.byToken.get(tokenKey) ?? [];\n }\n\n /** Record `bindFrameworkProvider(ProviderClass, TargetClass)`. */\n addProviderTarget(providerTokenKey: string, target: ts.ClassDeclaration): void {\n this.providerTargets.set(providerTokenKey, target);\n }\n\n /**\n * The class a Provider hands out, if this token is a registered Provider subclass.\n * The walker follows this so `Factory -> XProvider -> X` is visible in the design,\n * instead of the provider dead-ending as an opaque toDynamicValue leaf.\n */\n providerTarget(providerTokenKey: string): ts.ClassDeclaration | undefined {\n return this.providerTargets.get(providerTokenKey);\n }\n}\n\nfunction isAnalyzableFile(sourceFile: ts.SourceFile): boolean {\n if (sourceFile.isDeclarationFile) return false;\n if (sourceFile.fileName.includes('/node_modules/')) return false;\n return true;\n}\n\n/**\n * A class the walker treats as an EXTERNAL boundary: its declaration lives in a\n * `.d.ts` file or under `node_modules` โ i.e. a published package outside this\n * nx workspace. The exact inverse of {@link isAnalyzableFile}'s file test. In an\n * nx monorepo internal libs resolve through tsconfig path mappings to real `.ts`\n * source, so they are NOT external and keep expanding; only third-party packages\n * (resolved to `.d.ts` in `node_modules`) trip this. Pass-2 renders such a class\n * as a leaf `external` node and stops โ it does not descend into its ctor deps.\n */\nexport function isExternalClass(cls: ts.ClassDeclaration): boolean {\n const sourceFile = cls.getSourceFile();\n return sourceFile.isDeclarationFile || sourceFile.fileName.includes('/node_modules/');\n}\n\n/**\n * Walk `.inSingletonScope()` / `.inTransientScope()` suffixes above a binding call.\n *\n * No scope call at all means TRANSIENT, not \"unknown\": that is inversify's\n * `DEFAULT_DEFAULT_SCOPE`, and no `new Container(...)` in this workspace overrides `defaultScope`.\n */\nfunction scopeFromChain(bindingCall: ts.CallExpression): DiScope {\n let node: ts.Node = bindingCall;\n while (\n node.parent &&\n ts.isPropertyAccessExpression(node.parent) &&\n node.parent.parent &&\n ts.isCallExpression(node.parent.parent)\n ) {\n const methodName = node.parent.name.text;\n if (methodName === 'inSingletonScope') return 'singleton';\n if (methodName === 'inTransientScope') return 'transient';\n node = node.parent.parent;\n }\n return 'transient';\n}\n\n/**\n * If `expr` is (or resolves through the checker to) a class declaration, return it.\n */\nexport function resolveClassDeclaration(\n expr: ts.Expression,\n checker: ts.TypeChecker,\n): ts.ClassDeclaration | null {\n let symbol = checker.getSymbolAtLocation(expr);\n if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {\n symbol = checker.getAliasedSymbol(symbol);\n }\n for (const decl of symbol?.declarations ?? []) {\n if (ts.isClassDeclaration(decl)) return decl;\n }\n return null;\n}\n\n/**\n * Recognize `bind(TOKEN)` at the bottom of a fluent chain. Accepts a bare `bind(...)`\n * identifier call or `options.bind(...)` property call.\n */\nfunction asBindCall(expr: ts.Expression): ts.CallExpression | null {\n if (!ts.isCallExpression(expr)) return null;\n const callee = expr.expression;\n if (ts.isIdentifier(callee) && callee.text === 'bind') return expr;\n if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'bind') return expr;\n return null;\n}\n\n/**\n * Handle one `<receiver>.to*(...)` call: if the receiver bottoms out at bind(TOKEN),\n * record the binding.\n */\nfunction collectBindCall(\n call: ts.CallExpression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n if (!ts.isPropertyAccessExpression(call.expression)) return;\n const methodName = call.expression.name.text;\n if (!BIND_METHOD_NAMES.has(methodName)) return;\n\n const bindCall = asBindCall(call.expression.expression);\n if (!bindCall || bindCall.arguments.length === 0) return;\n\n const tokenExpr = bindCall.arguments[0];\n const file = relativeFile(workspaceRoot, call.getSourceFile());\n const scope = scopeFromChain(call);\n\n if (methodName === 'toSelf') {\n const cls = resolveClassDeclaration(tokenExpr, checker);\n const token = cls\n ? classTokenKey(cls, workspaceRoot)\n : resolveTokenKey(tokenExpr, checker, workspaceRoot);\n table.add(new Binding(token.key, tokenExpr.getText(), 'toSelf', scope, cls, '', file));\n return;\n }\n\n const token = resolveTokenKey(tokenExpr, checker, workspaceRoot);\n\n if (methodName === 'to') {\n const implExpr = call.arguments[0];\n const cls = implExpr ? resolveClassDeclaration(implExpr, checker) : null;\n const valueText = implExpr ? implExpr.getText() : '';\n table.add(new Binding(token.key, token.display, 'to', scope, cls, valueText, file));\n return;\n }\n\n const kind: BindingKind = methodName === 'toConstantValue' ? 'toConstantValue' : 'toDynamicValue';\n const valueExpr = call.arguments[0];\n const valueText = valueExpr ? firstLine(valueExpr.getText()) : '';\n const isApiBoundary = kind === 'toDynamicValue' && valueExpr ? isApiClientBoundary(valueExpr, checker) : false;\n table.add(new Binding(token.key, token.display, kind, scope, null, valueText, file, [], isApiBoundary));\n}\n\n/**\n * True when `expr` (the argument to `.toDynamicValue(...)` / an Angular\n * `useFactory`) builds an API-client proxy: it contains a `createApiClient(Api, ...)`\n * call whose first argument resolves to an @ApiPath-decorated contract class.\n *\n * This is the DI-graph boundary marker for generated (and external) API clients:\n * the walk renders such a binding as an `api` leaf and stops, rather than\n * descending into the client's own transport config (ClientConfig โ ...).\n */\n// webpieces-disable no-function-outside-class -- pure AST predicate, matching every sibling in this file\nexport function isApiClientBoundary(expr: ts.Expression, checker: ts.TypeChecker): boolean {\n const call = findCreateApiClientCall(expr);\n if (!call || call.arguments.length === 0) return false;\n const apiClass = resolveClassDeclaration(call.arguments[0], checker);\n return apiClass ? hasApiPathDecorator(apiClass) : false;\n}\n\n/** Find the first `createApiClient(...)` call anywhere inside `node`, else null. */\n// webpieces-disable no-function-outside-class -- pure AST walker, matching every sibling in this file\nfunction findCreateApiClientCall(node: ts.Node): ts.CallExpression | null {\n if (ts.isCallExpression(node)) {\n const callee = node.expression;\n const name = ts.isIdentifier(callee)\n ? callee.text\n : ts.isPropertyAccessExpression(callee)\n ? callee.name.text\n : null;\n if (name === 'createApiClient') return node;\n }\n let found: ts.CallExpression | null = null;\n ts.forEachChild(node, (child: ts.Node) => {\n if (!found) found = findCreateApiClientCall(child);\n });\n return found;\n}\n\n/** True when the class carries the `@ApiPath(...)` contract decorator. */\n// webpieces-disable no-function-outside-class -- pure AST predicate, matching every sibling in this file\nfunction hasApiPathDecorator(cls: ts.ClassDeclaration): boolean {\n return classDecorators(cls).some((d: ts.Decorator) => decoratorName(d) === 'ApiPath');\n}\n\nfunction firstLine(text: string): string {\n const line = text.split('\\n')[0].trim();\n return line.length > 60 ? line.slice(0, 57) + '...' : line;\n}\n\n/** Return the decorator call expression when `decorator` is `@name(...)`, else null. */\nexport function decoratorCall(decorator: ts.Decorator): ts.CallExpression | null {\n return ts.isCallExpression(decorator.expression) ? decorator.expression : null;\n}\n\n/** The identifier name of a decorator like `@provideSingleton()` or `@inject(X)`. */\nexport function decoratorName(decorator: ts.Decorator): string | null {\n const call = decoratorCall(decorator);\n const callee = call ? call.expression : decorator.expression;\n if (ts.isIdentifier(callee)) return callee.text;\n if (ts.isPropertyAccessExpression(callee)) return callee.name.text;\n return null;\n}\n\nexport function classDecorators(cls: ts.ClassDeclaration): ts.Decorator[] {\n const decorators = ts.getDecorators(cls);\n return decorators ? [...decorators] : [];\n}\n\n/**\n * Scope of an `@injectable(bindingScopeValues.Singleton|Transient)` SELF-binding, read from the\n * decorator ARGUMENT (either `bindingScopeValues.Singleton` or the raw 'Singleton'/'Transient'\n * string), NOT the decorator name like `@provideSingleton`.\n *\n * Returns null for a BARE `@injectable()` (no scope argument): that is NOT a self-binding โ it just\n * marks a class injectable for an explicit `bind(TOKEN).to(ThatClass)` elsewhere (e.g. SimpleCounter\n * bound to a Counter token). Only `@injectable(<scope>)` with an argument opts a concrete class into\n * autobind self-binding, the inject-by-type replacement for @provideSingleton.\n */\n// webpieces-disable no-function-outside-class -- pure AST predicate, matching every sibling in this file\nfunction injectableScope(decorator: ts.Decorator): DiScope | null {\n const call = decoratorCall(decorator);\n const arg = call?.arguments[0];\n if (!arg) return null;\n const scopeName = ts.isPropertyAccessExpression(arg)\n ? arg.name.text\n : ts.isStringLiteral(arg)\n ? arg.text\n : '';\n if (scopeName === 'Singleton') return 'singleton';\n if (scopeName === 'Transient') return 'transient';\n return null;\n}\n\nfunction collectDecoratorBindings(\n cls: ts.ClassDeclaration,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n const file = relativeFile(workspaceRoot, cls.getSourceFile());\n // A provide* decorator is the explicit binding; when present it wins over a plain @injectable on\n // the same class (a transitional state where both appear). Only a class with @injectable and NO\n // provide* decorator self-binds via autobind.\n const PROVIDE_BINDERS = new Set([\n 'provideSingleton',\n 'provideTransient',\n 'provideFrameworkSingleton',\n 'provideFrameworkTransient',\n 'provideSingletonDefaultForApi',\n 'provideFrameworkSingletonDefaultForApi',\n ]);\n const hasProvideBinding = classDecorators(cls).some((d: ts.Decorator) => {\n const n = decoratorName(d);\n return n !== null && PROVIDE_BINDERS.has(n);\n });\n for (const decorator of classDecorators(cls)) {\n const name = decoratorName(decorator);\n // provideFrameworkSingleton(As)/Transient are the framework-registry twins of\n // provideSingleton(As)/Transient (see @webpieces/core-context frameworkProvide.ts) โ\n // same self/token binding, same scopes.\n if (\n name === 'provideSingleton' ||\n name === 'provideTransient' ||\n name === 'provideFrameworkSingleton' ||\n name === 'provideFrameworkTransient'\n ) {\n const token = classTokenKey(cls, workspaceRoot);\n const transient = name === 'provideTransient' || name === 'provideFrameworkTransient';\n const scope: DiScope = transient ? 'transient' : 'singleton';\n table.add(new Binding(token.key, token.display, 'decorator', scope, cls, '', file));\n } else if (name === 'provideSingletonDefaultForApi' || name === 'provideFrameworkSingletonDefaultForApi') {\n const call = decoratorCall(decorator);\n const tokenExpr = call?.arguments[0];\n if (!tokenExpr) continue;\n const token = resolveTokenKey(tokenExpr, checker, workspaceRoot);\n table.add(new Binding(token.key, token.display, 'decorator', 'singleton', cls, '', file));\n } else if (name === 'injectable' && !hasProvideBinding) {\n // @injectable(bindingScopeValues.Singleton|Transient) self-binds the concrete class under\n // the app container's autobind (the inject-by-type replacement for @provideSingleton).\n // A bare @injectable() (scope null) is skipped โ it is bound via an explicit .to(token).\n const scope = injectableScope(decorator);\n if (scope !== null) {\n const token = classTokenKey(cls, workspaceRoot);\n table.add(new Binding(token.key, token.display, 'decorator', scope, cls, '', file));\n }\n }\n }\n}\n\n/**\n * `bindFrameworkProvider(TOKEN, X)` โ the Guice-style Provider registration in\n * @webpieces/core-context. Records that a `Provider<X>` injected under TOKEN yields X.\n *\n * The TOKEN is whatever names the provider (a Symbol, since `Provider<T>` is erased at runtime and\n * cannot be its own token). We never draw a node for it: a Provider is DI plumbing, not wiring.\n * The walker renders `Consumer -> X` directly, and X's OWN binding decides X's scope โ and hence\n * whether the design draws one box (a lazy singleton) or a stack (a fresh instance per get()).\n */\n// webpieces-disable no-function-outside-class -- ts AST visitor, matching every sibling collector in this file\nfunction collectProviderBinding(\n call: ts.CallExpression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n table: BindingTable,\n): void {\n if (!ts.isIdentifier(call.expression) || call.expression.text !== 'bindFrameworkProvider') return;\n if (call.arguments.length < 2) return;\n\n // The token may be a Symbol, a class, anything โ resolveTokenKey canonicalizes all of them.\n const token = resolveTokenKey(call.arguments[0], checker, workspaceRoot);\n const targetClass = resolveClassDeclaration(call.arguments[1], checker);\n if (!targetClass) return;\n\n table.addProviderTarget(token.key, targetClass);\n}\n\n/**\n * Pass 1: collect every binding in the program into a token-keyed table.\n */\nexport function collectBindings(\n program: ts.Program,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n): BindingTable {\n const table = new BindingTable();\n const clients = new RuntimeClientBindings();\n\n for (const sourceFile of program.getSourceFiles()) {\n if (!isAnalyzableFile(sourceFile)) continue;\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) {\n collectBindCall(node, checker, workspaceRoot, table);\n collectProviderBinding(node, checker, workspaceRoot, table);\n const client = clients.collectNode(node, checker, workspaceRoot);\n if (client !== undefined) table.add(client);\n } else if (ts.isClassDeclaration(node)) {\n collectDecoratorBindings(node, checker, workspaceRoot, table);\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n }\n\n return table;\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { Binding } from './model';
|
|
3
|
+
/** Canonical lazy client helpers retain the same DI token and singleton boundary. */
|
|
4
|
+
export declare class RuntimeClientBindings {
|
|
5
|
+
collectNode(call: ts.CallExpression, checker: ts.TypeChecker, root: string): Binding | undefined;
|
|
6
|
+
collectBrowser(call: ts.CallExpression, checker: ts.TypeChecker, root: string): Binding | undefined;
|
|
7
|
+
private binding;
|
|
8
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuntimeClientBindings = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const ts = tslib_1.__importStar(require("typescript"));
|
|
6
|
+
const model_1 = require("./model");
|
|
7
|
+
const token_resolver_1 = require("./token-resolver");
|
|
8
|
+
/** Canonical lazy client helpers retain the same DI token and singleton boundary. */
|
|
9
|
+
class RuntimeClientBindings {
|
|
10
|
+
collectNode(call, checker, root) {
|
|
11
|
+
const callee = call.expression;
|
|
12
|
+
if (!ts.isPropertyAccessExpression(callee))
|
|
13
|
+
return;
|
|
14
|
+
const owner = checker.getTypeAtLocation(callee.expression).getSymbol()?.getName();
|
|
15
|
+
if ((callee.name.text === 'bindRpc' && owner === 'RuntimeClients') ||
|
|
16
|
+
(callee.name.text === 'bindPubSub' && owner === 'RuntimeTaskClients')) {
|
|
17
|
+
return this.binding(call, checker, root);
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
collectBrowser(call, checker, root) {
|
|
22
|
+
let symbol = checker.getSymbolAtLocation(call.expression);
|
|
23
|
+
if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0)
|
|
24
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
25
|
+
if (symbol?.getName() !== 'provideRpcClient' ||
|
|
26
|
+
checker.getTypeAtLocation(call).getSymbol()?.getName() !== 'RpcClientProvider')
|
|
27
|
+
return;
|
|
28
|
+
return this.binding(call, checker, root);
|
|
29
|
+
}
|
|
30
|
+
binding(call, checker, root) {
|
|
31
|
+
if (call.arguments.length < 3)
|
|
32
|
+
return;
|
|
33
|
+
const token = (0, token_resolver_1.resolveTokenKey)(call.arguments[0], checker, root);
|
|
34
|
+
return new model_1.Binding(token.key, token.display, 'toDynamicValue', 'singleton', null, call.expression.getText(), (0, token_resolver_1.relativeFile)(root, call.getSourceFile()), [], true);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.RuntimeClientBindings = RuntimeClientBindings;
|
|
38
|
+
//# sourceMappingURL=runtime-client-bindings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-client-bindings.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/runtime-client-bindings.ts"],"names":[],"mappings":";;;;AAAA,uDAAiC;AACjC,mCAAkC;AAClC,qDAAiE;AAEjE,qFAAqF;AACrF,MAAa,qBAAqB;IAC9B,WAAW,CACP,IAAuB,EACvB,OAAuB,EACvB,IAAY;QAEZ,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,0BAA0B,CAAC,MAAM,CAAC;YAAE,OAAO;QACnD,MAAM,KAAK,GAAG,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC;QAClF,IACI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,gBAAgB,CAAC;YAC9D,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY,IAAI,KAAK,KAAK,oBAAoB,CAAC,EACvE,CAAC;YACC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,cAAc,CACV,IAAuB,EACvB,OAAuB,EACvB,IAAY;QAEZ,IAAI,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1D,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC;YACrD,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAC9C,IACI,MAAM,EAAE,OAAO,EAAE,KAAK,kBAAkB;YACxC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,mBAAmB;YAE9E,OAAO;QACX,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAEO,OAAO,CACX,IAAuB,EACvB,OAAuB,EACvB,IAAY;QAEZ,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO;QACtC,MAAM,KAAK,GAAG,IAAA,gCAAe,EAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAChE,OAAO,IAAI,eAAO,CACd,KAAK,CAAC,GAAG,EACT,KAAK,CAAC,OAAO,EACb,gBAAgB,EAChB,WAAW,EACX,IAAI,EACJ,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,EACzB,IAAA,6BAAY,EAAC,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,EACxC,EAAE,EACF,IAAI,CACP,CAAC;IACN,CAAC;CACJ;AArDD,sDAqDC","sourcesContent":["import * as ts from 'typescript';\nimport { Binding } from './model';\nimport { relativeFile, resolveTokenKey } from './token-resolver';\n\n/** Canonical lazy client helpers retain the same DI token and singleton boundary. */\nexport class RuntimeClientBindings {\n collectNode(\n call: ts.CallExpression,\n checker: ts.TypeChecker,\n root: string,\n ): Binding | undefined {\n const callee = call.expression;\n if (!ts.isPropertyAccessExpression(callee)) return;\n const owner = checker.getTypeAtLocation(callee.expression).getSymbol()?.getName();\n if (\n (callee.name.text === 'bindRpc' && owner === 'RuntimeClients') ||\n (callee.name.text === 'bindPubSub' && owner === 'RuntimeTaskClients')\n ) {\n return this.binding(call, checker, root);\n }\n return undefined;\n }\n\n collectBrowser(\n call: ts.CallExpression,\n checker: ts.TypeChecker,\n root: string,\n ): Binding | undefined {\n let symbol = checker.getSymbolAtLocation(call.expression);\n if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0)\n symbol = checker.getAliasedSymbol(symbol);\n if (\n symbol?.getName() !== 'provideRpcClient' ||\n checker.getTypeAtLocation(call).getSymbol()?.getName() !== 'RpcClientProvider'\n )\n return;\n return this.binding(call, checker, root);\n }\n\n private binding(\n call: ts.CallExpression,\n checker: ts.TypeChecker,\n root: string,\n ): Binding | undefined {\n if (call.arguments.length < 3) return;\n const token = resolveTokenKey(call.arguments[0], checker, root);\n return new Binding(\n token.key,\n token.display,\n 'toDynamicValue',\n 'singleton',\n null,\n call.expression.getText(),\n relativeFile(root, call.getSourceFile()),\n [],\n true,\n );\n }\n}\n"]}
|
|
@@ -72,6 +72,11 @@ function declarationInitializer(decl) {
|
|
|
72
72
|
*/
|
|
73
73
|
function resolveTokenKey(expr, checker, workspaceRoot) {
|
|
74
74
|
const display = expr.getText();
|
|
75
|
+
if (ts.isPropertyAccessExpression(expr) &&
|
|
76
|
+
expr.name.text === 'identifier' &&
|
|
77
|
+
checker.getTypeAtLocation(expr.expression).getSymbol()?.getName() === 'ClientToken') {
|
|
78
|
+
return new model_1.TokenRef(resolveTokenKey(expr.expression, checker, workspaceRoot).key, display);
|
|
79
|
+
}
|
|
75
80
|
let symbol = checker.getSymbolAtLocation(expr);
|
|
76
81
|
// For `TYPES.Counter` the symbol is on the property name; for a bare identifier it
|
|
77
82
|
// may be an import alias โ follow aliases to the original declaration.
|
|
@@ -92,6 +97,15 @@ function resolveTokenKey(expr, checker, workspaceRoot) {
|
|
|
92
97
|
const key = symbolCallKey(init, declFile, name);
|
|
93
98
|
if (key)
|
|
94
99
|
return new model_1.TokenRef(key, display);
|
|
100
|
+
if (ts.isNewExpression(init) &&
|
|
101
|
+
checker.getTypeAtLocation(expr).getSymbol()?.getName() === 'ClientToken') {
|
|
102
|
+
const identifier = init.arguments?.[0];
|
|
103
|
+
if (identifier !== undefined) {
|
|
104
|
+
const typedKey = symbolCallKey(identifier, declFile, name) ??
|
|
105
|
+
resolveTokenKey(identifier, checker, workspaceRoot).key;
|
|
106
|
+
return new model_1.TokenRef(typedKey, display);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
95
109
|
}
|
|
96
110
|
return new model_1.TokenRef(`expr:${declFile}#${name}`, display);
|
|
97
111
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-resolver.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/token-resolver.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAOH,oCAEC;AAmDD,
|
|
1
|
+
{"version":3,"file":"token-resolver.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/token-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Token Resolver\n *\n * Canonicalizes a DI token expression (the argument of @inject(...), @multiInject(...),\n * @provideSingletonDefaultForApi(...), or bind(...)) into a stable string key so that a token\n * DEFINITION (e.g. `TYPES.Counter` in one file) and its BIND SITE (e.g.\n * `bind(TYPES.Counter)` in another package) resolve to the same key.\n *\n * Key forms:\n * - Symbol.for('X') โ \"symbol.for:X\" (global symbol registry โ string key is identity)\n * - Symbol('X') โ \"symbol:<file>#<name>\" (per-declaration identity)\n * - class-as-token โ \"class:<file>#<Name>\" (e.g. @inject(HeaderMethods), bind(X).toSelf())\n * - anything else โ \"expr:<file>#<text>\" (best-effort fallback)\n */\n\nimport * as ts from 'typescript';\nimport * as path from 'path';\nimport { TokenRef } from './model';\n\n/** Workspace-relative posix path for a source file. */\nexport function relativeFile(workspaceRoot: string, sourceFile: ts.SourceFile): string {\n return path.relative(workspaceRoot, sourceFile.fileName).split(path.sep).join('/');\n}\n\n/** Match a `Symbol.for('X')` or `Symbol('X')` call; returns the TokenRef key core, or null. */\nfunction symbolCallKey(init: ts.Expression, declFile: string, declName: string): string | null {\n if (!ts.isCallExpression(init)) return null;\n const callee = init.expression;\n const arg = init.arguments[0];\n const argText = arg && ts.isStringLiteralLike(arg) ? arg.text : null;\n\n // Symbol.for('X')\n if (\n ts.isPropertyAccessExpression(callee) &&\n ts.isIdentifier(callee.expression) &&\n callee.expression.text === 'Symbol' &&\n callee.name.text === 'for'\n ) {\n return argText !== null ? `symbol.for:${argText}` : `symbol.for:${declFile}#${declName}`;\n }\n\n // Symbol('X')\n if (ts.isIdentifier(callee) && callee.text === 'Symbol') {\n return `symbol:${declFile}#${argText ?? declName}`;\n }\n\n return null;\n}\n\n/** Extract the property/variable name a declaration binds. */\nfunction declaredName(decl: ts.Declaration): string {\n const named = decl as ts.NamedDeclaration;\n if (named.name && (ts.isIdentifier(named.name) || ts.isStringLiteralLike(named.name))) {\n return named.name.text;\n }\n return '<anonymous>';\n}\n\n/** Pull the initializer expression off a property-assignment or variable declaration. */\nfunction declarationInitializer(decl: ts.Declaration): ts.Expression | null {\n if (ts.isPropertyAssignment(decl)) return decl.initializer;\n if (ts.isVariableDeclaration(decl) && decl.initializer) return decl.initializer;\n if (ts.isPropertyDeclaration(decl) && decl.initializer) return decl.initializer;\n return null;\n}\n\n/**\n * Resolve a token expression to its canonical key + display text.\n *\n * Uses the type checker to follow the expression (identifier or property access,\n * possibly imported from another package) to its declaration, then inspects the\n * declaration's initializer for Symbol.for / Symbol calls.\n */\nexport function resolveTokenKey(\n expr: ts.Expression,\n checker: ts.TypeChecker,\n workspaceRoot: string,\n): TokenRef {\n const display = expr.getText();\n if (\n ts.isPropertyAccessExpression(expr) &&\n expr.name.text === 'identifier' &&\n checker.getTypeAtLocation(expr.expression).getSymbol()?.getName() === 'ClientToken'\n ) {\n return new TokenRef(resolveTokenKey(expr.expression, checker, workspaceRoot).key, display);\n }\n\n let symbol = checker.getSymbolAtLocation(expr);\n // For `TYPES.Counter` the symbol is on the property name; for a bare identifier it\n // may be an import alias โ follow aliases to the original declaration.\n if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {\n symbol = checker.getAliasedSymbol(symbol);\n }\n\n const decl = symbol?.declarations?.[0];\n if (!decl) {\n return new TokenRef(`expr:<unresolved>#${display}`, display);\n }\n\n const declFile = relativeFile(workspaceRoot, decl.getSourceFile());\n const name = declaredName(decl);\n\n if (ts.isClassDeclaration(decl)) {\n return new TokenRef(`class:${declFile}#${name}`, display);\n }\n\n const init = declarationInitializer(decl);\n if (init) {\n const key = symbolCallKey(init, declFile, name);\n if (key) return new TokenRef(key, display);\n if (\n ts.isNewExpression(init) &&\n checker.getTypeAtLocation(expr).getSymbol()?.getName() === 'ClientToken'\n ) {\n const identifier = init.arguments?.[0];\n if (identifier !== undefined) {\n const typedKey =\n symbolCallKey(identifier, declFile, name) ??\n resolveTokenKey(identifier, checker, workspaceRoot).key;\n return new TokenRef(typedKey, display);\n }\n }\n }\n\n return new TokenRef(`expr:${declFile}#${name}`, display);\n}\n\n/** Build the class-as-token key for a class declaration (bind(X).toSelf(), @provideSingleton). */\nexport function classTokenKey(\n cls: ts.ClassDeclaration,\n workspaceRoot: string,\n): TokenRef {\n const file = relativeFile(workspaceRoot, cls.getSourceFile());\n const name = cls.name ? cls.name.text : '<anonymous>';\n return new TokenRef(`class:${file}#${name}`, name);\n}\n"]}
|
|
@@ -5,10 +5,10 @@ declare class GraphHighlighter {
|
|
|
5
5
|
private readonly model;
|
|
6
6
|
private readonly page;
|
|
7
7
|
private readonly nodeByName;
|
|
8
|
+
private hovered;
|
|
8
9
|
constructor(svg: SVGSVGElement, chain: WpGraphChain, model: RenderModelJson, page: GraphPage);
|
|
9
10
|
wire(): void;
|
|
10
11
|
private clear;
|
|
11
|
-
private highlight;
|
|
12
12
|
relight(): void;
|
|
13
13
|
private wireHover;
|
|
14
14
|
private wireMenu;
|
|
@@ -6,6 +6,7 @@ class GraphHighlighter {
|
|
|
6
6
|
model;
|
|
7
7
|
page;
|
|
8
8
|
nodeByName = new Map();
|
|
9
|
+
hovered = null;
|
|
9
10
|
constructor(svg, chain, model, page) {
|
|
10
11
|
this.svg = svg;
|
|
11
12
|
this.chain = chain;
|
|
@@ -26,18 +27,28 @@ class GraphHighlighter {
|
|
|
26
27
|
}
|
|
27
28
|
clear() {
|
|
28
29
|
this.svg.classList.remove('wp-dim');
|
|
29
|
-
this.svg.querySelectorAll('.wp-focus, .wp-neighbor, .wp-hl').forEach((el) => {
|
|
30
|
-
el.classList.remove('wp-focus', 'wp-neighbor', 'wp-hl');
|
|
30
|
+
this.svg.querySelectorAll('.wp-focus, .wp-neighbor, .wp-hl, .wp-locked').forEach((el) => {
|
|
31
|
+
el.classList.remove('wp-focus', 'wp-neighbor', 'wp-hl', 'wp-locked');
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
relight() {
|
|
34
35
|
this.clear();
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
36
|
+
const locked = this.page.lockSelection();
|
|
37
|
+
if (locked !== null)
|
|
38
|
+
this.nodeByName.get(locked)?.classList.add('wp-locked');
|
|
39
|
+
// A filtered-out Lock is suspended, including its otherwise visible relatives.
|
|
40
|
+
const anchors = [locked, this.hovered].filter((name) => name !== null && this.nodeByName.has(name));
|
|
41
|
+
if (anchors.length === 0)
|
|
37
42
|
return;
|
|
38
|
-
const lit =
|
|
43
|
+
const lit = new Set();
|
|
44
|
+
for (const name of anchors) {
|
|
45
|
+
this.nodeByName.get(name)?.classList.add('wp-focus');
|
|
46
|
+
for (const id of this.chain.nodes(name)) {
|
|
47
|
+
if (this.nodeByName.has(id))
|
|
48
|
+
lit.add(id);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
39
51
|
this.svg.classList.add('wp-dim');
|
|
40
|
-
focus.classList.add('wp-focus');
|
|
41
52
|
for (const id of lit)
|
|
42
53
|
this.nodeByName.get(id)?.classList.add('wp-neighbor');
|
|
43
54
|
for (const edge of this.model.edges) {
|
|
@@ -46,17 +57,16 @@ class GraphHighlighter {
|
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
59
|
}
|
|
49
|
-
relight() {
|
|
50
|
-
const locked = this.page.lockSelection();
|
|
51
|
-
if (locked === null)
|
|
52
|
-
this.clear();
|
|
53
|
-
else
|
|
54
|
-
this.highlight(locked);
|
|
55
|
-
}
|
|
56
60
|
wireHover() {
|
|
57
61
|
this.nodeByName.forEach((g, name) => {
|
|
58
|
-
g.addEventListener('mouseenter', () =>
|
|
59
|
-
|
|
62
|
+
g.addEventListener('mouseenter', () => {
|
|
63
|
+
this.hovered = name;
|
|
64
|
+
this.relight();
|
|
65
|
+
});
|
|
66
|
+
g.addEventListener('mouseleave', () => {
|
|
67
|
+
this.hovered = null;
|
|
68
|
+
this.relight();
|
|
69
|
+
});
|
|
60
70
|
});
|
|
61
71
|
}
|
|
62
72
|
wireMenu() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-visualizer.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-visualizer.client.ts"],"names":[],"mappings":"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","sourcesContent":["/** Architecture interactions bind to each new SVG; logical Lock/Filter state belongs to the page. */\nclass GraphHighlighter {\n private readonly nodeByName = new Map<string, SVGGElement>();\n\n constructor(\n private readonly svg: SVGSVGElement,\n private readonly chain: WpGraphChain,\n private readonly model: RenderModelJson,\n private readonly page: GraphPage,\n ) {}\n\n wire(): void {\n this.svg.querySelectorAll<SVGGElement>('g.node').forEach((g) => {\n if (g.classList.contains('wp-layout')) return;\n const name = g.querySelector('title')?.textContent;\n if (name !== null && name !== undefined) this.nodeByName.set(name, g);\n });\n this.wireHover();\n this.wireMenu();\n this.relight();\n }\n\n private clear(): void {\n this.svg.classList.remove('wp-dim');\n this.svg.querySelectorAll('.wp-focus, .wp-neighbor, .wp-hl').forEach((el) => {\n el.classList.remove('wp-focus', 'wp-neighbor', 'wp-hl');\n });\n }\n\n private highlight(name: string): void {\n this.clear();\n const focus = this.nodeByName.get(name);\n if (focus === undefined) return;\n const lit = this.chain.nodes(name);\n this.svg.classList.add('wp-dim');\n focus.classList.add('wp-focus');\n for (const id of lit) this.nodeByName.get(id)?.classList.add('wp-neighbor');\n for (const edge of this.model.edges) {\n if (lit.has(edge.from) && lit.has(edge.to)) {\n this.svg.querySelector(`[id=\"${edge.id}\"]`)?.classList.add('wp-hl');\n }\n }\n }\n\n relight(): void {\n const locked = this.page.lockSelection();\n if (locked === null) this.clear();\n else this.highlight(locked);\n }\n\n private wireHover(): void {\n this.nodeByName.forEach((g, name) => {\n g.addEventListener('mouseenter', () => this.highlight(name));\n g.addEventListener('mouseleave', () => this.relight());\n });\n }\n\n private wireMenu(): void {\n const designs = new Map(__DESIGN_LINKS__.map((link) => [link.nodeId, link.href]));\n WpNodeMenu.wire(this.svg, (name) => {\n const items: WpNodeMenuItem[] = [];\n const href = designs.get(name);\n if (href !== undefined)\n items.push(new WpNodeMenuItem('View Design', () => window.open(href, '_blank')));\n const locked = this.page.lockSelection() === name;\n items.push(\n new WpNodeMenuItem(locked ? 'Unlock' : 'Lock', () =>\n this.page.setLock(locked ? null : name),\n ),\n );\n items.push(this.page.filterItem(name));\n return items;\n });\n }\n}\n\nclass GraphPage extends WpFilterPage {\n private highlighter: GraphHighlighter | null = null;\n\n protected override captureBinding(_svg: SVGSVGElement | null): () => void {\n const previous = this.highlighter;\n return (): void => { this.highlighter = previous; this.filterCards(); };\n }\n\n protected override wireControls(): void {\n const select = document.getElementById('wp-lock') as HTMLSelectElement | null;\n select?.addEventListener('change', () =>\n this.setLock(select.value === '' ? null : select.value),\n );\n }\n\n protected wireSvg(svg: SVGSVGElement): void {\n this.highlighter = new GraphHighlighter(svg, this.chain, this.model, this);\n this.highlighter.wire();\n this.filterCards();\n svg.querySelectorAll<SVGGElement>('g.node').forEach((node) => {\n if (node.querySelector('title')?.textContent === this.anchor)\n node.classList.add('wp-filter-anchor');\n });\n }\n\n lockSelection(): string | null {\n return this.locked;\n }\n\n setLock(name: string | null): void {\n this.locked = name;\n const select = document.getElementById('wp-lock') as HTMLSelectElement | null;\n if (select !== null) select.value = name ?? '';\n this.highlighter?.relight();\n this.filterCards();\n }\n\n private filterCards(): void {\n const lit = this.locked === null ? this.retained : this.chain.nodes(this.locked);\n document.querySelectorAll('.wp-resp-card').forEach((card) => {\n const name = card.getAttribute('data-node');\n card.classList.toggle(\n 'wp-hidden',\n name === null || !this.retained.has(name) || !lit.has(name),\n );\n });\n }\n}\n\nnew GraphPage(__RENDER_MODEL__).render();\n"]}
|
|
1
|
+
{"version":3,"file":"graph-visualizer.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-visualizer.client.ts"],"names":[],"mappings":"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","sourcesContent":["/** Architecture interactions bind to each new SVG; logical Lock/Filter state belongs to the page. */\nclass GraphHighlighter {\n private readonly nodeByName = new Map<string, SVGGElement>();\n private hovered: string | null = null;\n\n constructor(\n private readonly svg: SVGSVGElement,\n private readonly chain: WpGraphChain,\n private readonly model: RenderModelJson,\n private readonly page: GraphPage,\n ) {}\n\n wire(): void {\n this.svg.querySelectorAll<SVGGElement>('g.node').forEach((g) => {\n if (g.classList.contains('wp-layout')) return;\n const name = g.querySelector('title')?.textContent;\n if (name !== null && name !== undefined) this.nodeByName.set(name, g);\n });\n this.wireHover();\n this.wireMenu();\n this.relight();\n }\n\n private clear(): void {\n this.svg.classList.remove('wp-dim');\n this.svg.querySelectorAll('.wp-focus, .wp-neighbor, .wp-hl, .wp-locked').forEach((el: Element) => {\n el.classList.remove('wp-focus', 'wp-neighbor', 'wp-hl', 'wp-locked');\n });\n }\n\n relight(): void {\n this.clear();\n const locked = this.page.lockSelection();\n if (locked !== null) this.nodeByName.get(locked)?.classList.add('wp-locked');\n // A filtered-out Lock is suspended, including its otherwise visible relatives.\n const anchors = [locked, this.hovered].filter(\n (name: string | null): name is string => name !== null && this.nodeByName.has(name),\n );\n if (anchors.length === 0) return;\n const lit = new Set<string>();\n for (const name of anchors) {\n this.nodeByName.get(name)?.classList.add('wp-focus');\n for (const id of this.chain.nodes(name)) {\n if (this.nodeByName.has(id)) lit.add(id);\n }\n }\n this.svg.classList.add('wp-dim');\n for (const id of lit) this.nodeByName.get(id)?.classList.add('wp-neighbor');\n for (const edge of this.model.edges) {\n if (lit.has(edge.from) && lit.has(edge.to)) {\n this.svg.querySelector(`[id=\"${edge.id}\"]`)?.classList.add('wp-hl');\n }\n }\n }\n\n private wireHover(): void {\n this.nodeByName.forEach((g: SVGGElement, name: string) => {\n g.addEventListener('mouseenter', () => {\n this.hovered = name;\n this.relight();\n });\n g.addEventListener('mouseleave', () => {\n this.hovered = null;\n this.relight();\n });\n });\n }\n\n private wireMenu(): void {\n const designs = new Map(__DESIGN_LINKS__.map((link) => [link.nodeId, link.href]));\n WpNodeMenu.wire(this.svg, (name) => {\n const items: WpNodeMenuItem[] = [];\n const href = designs.get(name);\n if (href !== undefined)\n items.push(new WpNodeMenuItem('View Design', () => window.open(href, '_blank')));\n const locked = this.page.lockSelection() === name;\n items.push(\n new WpNodeMenuItem(locked ? 'Unlock' : 'Lock', () =>\n this.page.setLock(locked ? null : name),\n ),\n );\n items.push(this.page.filterItem(name));\n return items;\n });\n }\n}\n\nclass GraphPage extends WpFilterPage {\n private highlighter: GraphHighlighter | null = null;\n\n protected override captureBinding(_svg: SVGSVGElement | null): () => void {\n const previous = this.highlighter;\n return (): void => { this.highlighter = previous; this.filterCards(); };\n }\n\n protected override wireControls(): void {\n const select = document.getElementById('wp-lock') as HTMLSelectElement | null;\n select?.addEventListener('change', () =>\n this.setLock(select.value === '' ? null : select.value),\n );\n }\n\n protected wireSvg(svg: SVGSVGElement): void {\n this.highlighter = new GraphHighlighter(svg, this.chain, this.model, this);\n this.highlighter.wire();\n this.filterCards();\n svg.querySelectorAll<SVGGElement>('g.node').forEach((node) => {\n if (node.querySelector('title')?.textContent === this.anchor)\n node.classList.add('wp-filter-anchor');\n });\n }\n\n lockSelection(): string | null {\n return this.locked;\n }\n\n setLock(name: string | null): void {\n this.locked = name;\n const select = document.getElementById('wp-lock') as HTMLSelectElement | null;\n if (select !== null) select.value = name ?? '';\n this.highlighter?.relight();\n this.filterCards();\n }\n\n private filterCards(): void {\n const lit = this.locked === null ? this.retained : this.chain.nodes(this.locked);\n document.querySelectorAll('.wp-resp-card').forEach((card) => {\n const name = card.getAttribute('data-node');\n card.classList.toggle(\n 'wp-hidden',\n name === null || !this.retained.has(name) || !lit.has(name),\n );\n });\n }\n}\n\nnew GraphPage(__RENDER_MODEL__).render();\n"]}
|
|
@@ -429,6 +429,9 @@ class GraphVisualizer {
|
|
|
429
429
|
* shared verbatim with the per-project design pages. */
|
|
430
430
|
${this.nodeMenu.styles()}
|
|
431
431
|
${this.nodeMenu.dimStyles('#graph')}
|
|
432
|
+
#graph g.node.wp-locked polygon,
|
|
433
|
+
#graph g.node.wp-locked ellipse,
|
|
434
|
+
#graph g.node.wp-locked path { stroke: #b26a00; stroke-width: 3; }
|
|
432
435
|
#graph {
|
|
433
436
|
text-align: center;
|
|
434
437
|
background: white;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-visualizer.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-visualizer.ts"],"names":[],"mappings":"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","sourcesContent":["import { GraphNavigation } from './graph-navigation';\n/**\n * Graph Visualizer\n *\n * Generates visual representations of the architecture graph:\n * - DOT format (for Graphviz)\n * - Interactive HTML (using viz.js)\n *\n * All behavior lives on the injectable GraphVisualizer class so webpieces DI +\n * @DocumentDesign can wire it โ module-scope functions are a dead end the DI\n * graph can't reach.\n */\n\nimport * as fs from 'fs';\nimport { SavedSnapshot } from './saved-snapshot';\nimport * as path from 'path';\nimport { execSync } from 'child_process';\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { ApiRef, ApiRelation, ApiRelationKind } from './api-usage/api-relations';\nimport { GraphNames, NodeIdOwner } from './graph-names';\nimport { LevelBand, LevelBandLayout } from './graph-level-bands';\nimport { ProjectAdjacency, ProjectCycleDetector } from './graph-cycles';\nimport { ResponsibilitiesRenderer } from './graph-responsibilities';\nimport { GraphNodeMenu } from './graph-node-menu';\nimport { dotValue } from './dot-syntax';\nimport { GraphRenderModel, GraphFilterAssets } from './graph-render-model';\nimport { toError } from '../toError';\n\n/**\n * Framework (libType) colors for visualization โ nodes are filled by the FIRST\n * env in their set that has a color, so it is obvious at a glance which side a\n * project targets. A project's full env set is shown in the label.\n */\nconst FRAMEWORK_COLORS: Record<string, string> = {\n angular: '#FCE4EC', // pink - Angular front-end\n react: '#E3F2FD', // blue - React front-end\n browser: '#EDE7F6', // purple - browser (front-end base env)\n express: '#E8F5E9', // green - Express / server side\n node: '#FFF9C4', // yellow - node (server base env)\n};\n\nconst DEFAULT_FRAMEWORK_COLOR = '#F5F5F5'; // grey - unknown/empty env set\n\n/**\n * Directory (repo-relative) that the committed architecture HTML lives in.\n * Node click-through links are computed relative to this so they resolve when\n * the file is opened straight from the checkout.\n */\nconst ARCH_OUTPUT_DIR = 'architecture';\n\n/** Contracts named on one `implements` edge label before it truncates to \"+N more\". */\nconst MAX_EDGE_LABEL_APIS = 4;\n\n/**\n * One node's design page, handed to the browser so the node menu can offer \"View Design\" for the\n * boxes that HAVE one and omit the item entirely for the boxes that do not.\n *\n * The link is data on the PAGE now, not a `URL=` attribute in the DOT: a URL made viz.js wrap the\n * box in an `<a>` and clicking navigated straight out, which the menu replaces.\n */\nexport class DesignLink {\n constructor(\n public readonly nodeId: string,\n public readonly href: string,\n ) {}\n}\n\nexport class VisualizationPaths {\n htmlPath: string;\n\n constructor(htmlPath: string) {\n this.htmlPath = htmlPath;\n }\n}\n\n/**\n * The token the client scripts carry where the DOT belongs. It must appear EXACTLY ONCE in a compiled\n * client โ the inliner is a blind split/join, so a second occurrence (in a comment, say) would be\n * replaced by the entire DOT too, bloating every generated page.\n */\nexport const CLIENT_MODEL_PLACEHOLDER = '__' + 'RENDER_MODEL' + '__';\n\n/** Same contract as CLIENT_MODEL_PLACEHOLDER, for the node โ design.html links the menu offers. */\nexport const CLIENT_DESIGN_LINKS_PLACEHOLDER = '__' + 'DESIGN_LINKS' + '__';\n\n/**\n * Read a compiled browser client sitting beside this file.\n *\n * It is generated from the matching `.client.ts` by tsc, so it exists in `dist` and in the published\n * tarball but NOT in a source checkout. The error says that outright rather than surfacing a bare\n * ENOENT, because \"the build has not run\" and \"the file is missing\" look identical otherwise.\n */\n// webpieces-disable no-function-outside-class -- module-level resolver for the default injected into GraphVisualizer/RuntimeHtmlPage; making it a class would need a container in a module both of them load eagerly\nexport function readCompiledClient(name: string): string {\n const file = path.join(__dirname, name);\n if (!fs.existsSync(file)) {\n throw new Error(\n `${name} not found beside ${__dirname}. It is COMPILED from ${name.replace(/\\.js$/, '.ts')} ` +\n 'by tsc, so it only exists after a build โ run the package build, or inject the text.',\n );\n }\n return fs.readFileSync(file, 'utf-8');\n}\n\nexport class GraphVisualizer {\n private readonly filterAssets = new GraphFilterAssets();\n private readonly snapshot = new SavedSnapshot();\n private readonly names = new GraphNames();\n private readonly responsibilities = new ResponsibilitiesRenderer();\n private readonly bandLayout = new LevelBandLayout();\n private readonly cycles = new ProjectCycleDetector();\n /** The ONE floating-node-menu implementation, shared with the per-project design pages. */\n private readonly nodeMenu = new GraphNodeMenu();\n\n /**\n * How to obtain the browser client's text. Injected so HTML generation does not depend on BUILD\n * ORDER: the default reads the compiled sibling, which exists in the tarball and in dist but NOT in\n * a source checkout (the source there is .client.ts). A unit test running from source supplies the\n * text itself rather than requiring the package to have been built first.\n */\n constructor(\n private readonly clientJs: () => string = (): string =>\n readCompiledClient('graph-visualizer.client.js'),\n private readonly filterJs: () => string = (): string =>\n readCompiledClient('graph-filter.client.js'),\n ) {}\n\n /**\n * A project tagged `drawOnGraph:false` is hidden from the rendered graph โ\n * its node, its rank placement, its dropdown option, its responsibilities\n * card, and every edge touching it are all omitted. It stays in the JSON.\n */\n private isHidden(entry: EnhancedGraph[string]): boolean {\n return entry.drawOnGraph === false;\n }\n\n /**\n * Fill color for an env set โ the color of the first env in the set that has\n * a known color, else the default.\n */\n private frameworkColor(frameworks: string[]): string {\n for (const env of frameworks) {\n const color = FRAMEWORK_COLORS[env];\n if (color !== undefined) return color;\n }\n return DEFAULT_FRAMEWORK_COLOR;\n }\n\n /**\n * Role border styling โ fill stays keyed on framework; the border shows a\n * project's ROLE at a glance. Server and client are the top-level runnable\n * nodes, so they get bold, colored borders to stand out:\n * server โ thick GREEN border (a runnable server app)\n * app โ thick BLUE border (a runnable non-HTTP app, e.g. a tooling app)\n * bundle โ thick PURPLE border (an aggregator that bundles several apps)\n * client โ thick RED border (a client app, e.g. angular)\n * designed-lib โ bold border (a library with a generated @DocumentDesign design)\n * lib / other โ plain thin border\n */\n private roleBorderAttrs(role: string): string {\n if (role === 'server') return ', color=\"green\", penwidth=3';\n if (role === 'app') return ', color=\"#1976d2\", penwidth=3';\n if (role === 'bundle') return ', color=\"#6A1B9A\", penwidth=3';\n if (role === 'client') return ', color=\"red\", penwidth=3';\n if (role === 'api-lib') return ', color=\"#EF6C00\", penwidth=2';\n if (role === 'api-client') return ', color=\"#00838F\", penwidth=2';\n if (role === 'designed-lib') return ', penwidth=2';\n return '';\n }\n\n /**\n * Edge styling by API-relation kind (why the edge exists):\n * implements โ BLACK dashed (a controller serves this api-lib's contract)\n * uses โ BLACK solid (a generated client calls it) โ same as a\n * plain library import, since a plain dependency IS a use.\n * uses-implements โ BLUE dashed, thicker (does both โ implements some\n * contracts of the api-lib, uses others)\n * plain lib (none) โ the default thin black solid arrow, unchanged.\n * `kind` is undefined for every non-api-lib dependency edge.\n */\n private edgeAttrs(kind: ApiRelationKind | undefined): string {\n if (kind === 'implements') return 'style=dashed';\n if (kind === 'uses-implements') return 'style=dashed, color=\"#1976d2\", penwidth=2';\n return '';\n }\n\n /**\n * The DOT attribute list for ONE dependency edge. An edge that IMPLEMENTS contracts is also\n * LABELED with them: the dashed line alone says \"something here is implemented\", which reads as\n * \"the tool did not detect anything\" to everyone who has not memorized the legend. Naming the\n * contracts makes `auth-store-api` visibly resolve to the server that serves it โ the single\n * most important relationship in a microservice architecture, and the one the diagram was\n * silent about.\n */\n private edgeDot(from: string, to: string, relation: ApiRelation | undefined): string {\n const attrs: string[] = [];\n const styling = this.edgeAttrs(relation?.kind);\n if (styling !== '') attrs.push(styling);\n const served = (relation?.implements ?? []).map((ref: ApiRef) => ref.api);\n if (served.length > 0)\n attrs.push(`label=\"implements: ${this.labelledApis(served)}\", fontsize=9`);\n const suffix = attrs.length === 0 ? '' : ` [${attrs.join(', ')}]`;\n return ` \"${dotValue(from)}\" -> \"${dotValue(to)}\"${suffix};\\n`;\n }\n\n /**\n * The contract names for an edge label, sorted and capped so a shared api-lib serving a dozen\n * contracts cannot blow the edge label up into a wall of text. The truncation is stated in the\n * label (\"+N more\") rather than silent โ the full list is in dependencies.json.\n */\n private labelledApis(apis: string[]): string {\n const sorted = [...apis].sort();\n if (sorted.length <= MAX_EDGE_LABEL_APIS) return sorted.join(', ');\n const shown = sorted.slice(0, MAX_EDGE_LABEL_APIS).join(', ');\n return `${shown} +${sorted.length - MAX_EDGE_LABEL_APIS} more`;\n }\n\n /**\n * Href for a node's design page: the project's committed design.html, made\n * relative to architecture/dependencies.html. Returns null when the project\n * has no generated DI design (no design.json โ no design page exists).\n */\n private designHtmlHref(designFile: string | undefined): string | null {\n if (!designFile) return null;\n const designHtml = designFile.replace(/design\\.json$/, 'design.html');\n return path.posix.relative(ARCH_OUTPUT_DIR, designHtml);\n }\n\n /**\n * The design pages that EXIST, one entry per visible node that has one. A node absent from this\n * list gets no \"View Design\" item at all โ the item is never rendered dead or greyed out.\n */\n designLinks(graph: EnhancedGraph): DesignLink[] {\n const links: DesignLink[] = [];\n for (const project of Object.keys(graph)) {\n const info = graph[project];\n if (this.isHidden(info)) continue;\n const href = this.designHtmlHref(info.designFile);\n if (href === null) continue;\n links.push(new DesignLink(this.names.getNodeId(project), href));\n }\n return links;\n }\n\n /**\n * Generate Graphviz DOT format from the graph\n */\n generateDot(graph: EnhancedGraph, title: string = 'Monorepo Dependency Architecture'): string {\n return this.generateRenderModel(graph, title).fullDot;\n }\n\n generateRenderModel(\n graph: EnhancedGraph,\n title: string = 'Monorepo Dependency Architecture',\n ): GraphRenderModel {\n this.assertDrawable(graph);\n const model = new GraphRenderModel();\n let dot = 'digraph Architecture {\\n';\n dot += ' rankdir=TB;\\n';\n dot += ' node [shape=box, style=filled, fontname=\"Arial\"];\\n';\n dot += ' edge [fontname=\"Arial\"];\\n\\n';\n\n model.header = dot;\n const bands = this.levelBands(graph);\n model.bands = bands;\n\n dot += this.dotNodes(graph, model);\n dot += '\\n';\n dot += this.bandLayout.dot(bands);\n dot += '\\n';\n dot += this.dotEdges(graph, model);\n\n const footerStart = dot.length;\n dot += '\\n labelloc=\"t\";\\n';\n dot += ` label=\"${dotValue(title)}\\\\n(from architecture/dependencies.json)\";\\n`;\n dot += ' fontsize=20;\\n';\n dot += '}\\n';\n\n model.footer = dot.slice(footerStart);\n model.completeEndpoints();\n model.fullDot = dot;\n return model;\n }\n\n /**\n * Refuse to draw a graph that would render something false.\n *\n * Two conditions, both of which produced a confidently-wrong picture with no warning at all:\n * - two projects sharing a node id fuse into one box, which UNIONS their two rank sets and\n * collapses two whole dependency levels onto one row (see graph-names.ts);\n * - a cycle makes the level numbers every row is keyed on meaningless (see graph-cycles.ts).\n */\n private assertDrawable(graph: EnhancedGraph): void {\n const owners: NodeIdOwner[] = [];\n const adjacency: ProjectAdjacency = {};\n for (const project of Object.keys(graph)) {\n owners.push(new NodeIdOwner(project, graph[project].level));\n adjacency[project] = graph[project].dependsOn ?? [];\n }\n this.names.assertUniqueNodeIds(owners);\n this.cycles.assertAcyclic(adjacency, 'architecture/dependencies.json');\n }\n\n /**\n * The visible projects grouped into one band per dependency level, ordered HIGHEST LEVEL FIRST\n * so the emitted bands read top-to-bottom with L0 last (hidden projects are omitted, so no\n * stray rank=same name is emitted for an absent node). LevelBandLayout turns these into the\n * rank sets and the invisible chain that pins them โ see graph-level-bands.ts for why the\n * chain is required at all.\n */\n private levelBands(graph: EnhancedGraph): LevelBand[] {\n const byLevel = new Map<number, string[]>();\n for (const project of Object.keys(graph)) {\n if (this.isHidden(graph[project])) continue;\n const level = graph[project].level;\n const nodeIds = byLevel.get(level);\n if (nodeIds === undefined) byLevel.set(level, [this.names.getNodeId(project)]);\n else nodeIds.push(this.names.getNodeId(project));\n }\n const levels = [...byLevel.keys()].sort((a: number, b: number): number => b - a);\n return levels.map(\n (level: number): LevelBand =>\n new LevelBand(level, [...(byLevel.get(level) as string[])].sort()),\n );\n }\n\n // Node lines: fill colored by framework env set (libType), border shaped by\n // role; the label shows the env set + role (e.g. [browser, node] ยท server).\n // No node carries a URL: EVERY box is clickable and opens the floating node\n // menu instead, which is where a design page is reached (see designLinks).\n private dotNodes(graph: EnhancedGraph, model: GraphRenderModel): string {\n let dot = '';\n for (const project of Object.keys(graph)) {\n const info = graph[project];\n if (this.isHidden(info)) continue;\n const nodeId = this.names.getNodeId(project);\n const shortName = this.names.getShortName(project);\n const frameworks = info.framework ?? [];\n const role = info.role ?? 'lib';\n const color = this.frameworkColor(frameworks);\n const border = this.roleBorderAttrs(role);\n const envSet = `[${frameworks.join(', ')}]`;\n const labelMeta = `L${info.level} ยท ${envSet} ยท ${role}`;\n // Identity is the project key; the LABEL is the pretty short name.\n dot += model.node(\n nodeId,\n ` \"${dotValue(nodeId)}\" [fillcolor=\"${color}\"${border}, label=\"${dotValue(shortName)}\\\\n(${dotValue(labelMeta)})\"];\\n`,\n );\n }\n return dot;\n }\n\n // Edge lines (dependencies). An edge to an api-lib is styled by WHY it exists\n // (implements/uses/uses-implements, from apiRelations); every other dependency\n // keeps the default plain arrow.\n private dotEdges(graph: EnhancedGraph, model: GraphRenderModel): string {\n let dot = '';\n for (const project of Object.keys(graph)) {\n const info = graph[project];\n if (this.isHidden(info)) continue;\n const nodeId = this.names.getNodeId(project);\n for (const dep of info.dependsOn || []) {\n // Both endpoints must be visible โ an edge to/from a hidden box\n // is dropped so no connection dangles into empty space.\n if (graph[dep] !== undefined && this.isHidden(graph[dep])) continue;\n dot += model.edge(\n nodeId,\n this.names.getNodeId(dep),\n this.edgeDot(nodeId, this.names.getNodeId(dep), info.apiRelations?.[dep]),\n );\n }\n }\n return dot;\n }\n\n /**\n * Generate interactive HTML with embedded SVG using viz.js\n */\n generateHTML(\n model: GraphRenderModel,\n links: DesignLink[],\n title: string = 'Monorepo Dependency Architecture',\n lockControl: string = '',\n responsibilitiesHtml: string = '',\n ): string {\n const styles = this.styles();\n const legend = this.legend();\n const script = this.script(model, links);\n\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>${title}</title>\n <script src=\"https://cdn.jsdelivr.net/npm/@viz-js/viz@3.28.0/dist/viz-global.js\"></script>\n <style>${styles}</style>\n</head>\n<body>\n <h1>${title}</h1>\n ${this.snapshot.html()}\n <p class=\"hint\">๐ก <strong>Click any box</strong> for its menu โ <strong>View Design</strong> (only where that project has a generated <strong>design.html</strong>, i.e. what the AI sees inside it) and <strong>Lock/Unlock</strong>, which is the same lock as the dropdown below. <strong>Filter Unconnected</strong> removes unrelated boxes and compacts the graph while preserving each L-number row.</p>\n <p class=\"hint\">๐ฆ <strong>Hover any box</strong> to trace its <em>entire</em> dependency chain โ every ancestor above it (all the way up) <em>and</em> every dependency below it (all the way down), with all the boxes and lines between โ while the rest of the graph dims so you can follow one box at a glance.</p>\n ${legend}\n ${lockControl}\n ${this.filterAssets.html()}\n <div id=\"graph\"></div>\n ${responsibilitiesHtml}\n <script>${this.nodeMenu.script()}</script>\n <script>${this.filterJs()}</script>\n <script>${new GraphNavigation().script()}</script>\n <script>${script}</script>\n</body>\n</html>`;\n }\n\n /**\n * The lock control (a single-select dropdown, rendered below the legend).\n * Picking a module LOCKS the graph into that box's hover view โ its full\n * ancestor + descendant chain stays lit while everything else stays dimmed โ\n * and narrows the responsibilities list below the graph to just that chain.\n * The first option, \"All\", is the default and clears the lock. Hover still\n * works on top of a lock; leaving a box returns to the locked view.\n *\n * Options are ordered by level DESCENDING to match the responsibilities cards.\n */\n lockControl(graph: EnhancedGraph): string {\n const projects = Object.keys(graph);\n projects.sort((a: string, b: string): number => {\n const levelDiff = graph[b].level - graph[a].level;\n if (levelDiff !== 0) return levelDiff;\n return a.localeCompare(b);\n });\n let options = '';\n for (const project of projects) {\n if (this.isHidden(graph[project])) continue;\n // The VALUE is the node id the SVG is keyed on; the TEXT stays the pretty short name.\n const nodeId = this.names.getNodeId(project);\n const shortName = this.names.getShortName(project);\n options += `<option value=\"${nodeId}\">L${graph[project].level} ยท ${shortName}</option>`;\n }\n return `<div class=\"wp-lock-control\">\n <label for=\"wp-lock\">๐ Lock a box (dim the rest & filter responsibilities):</label>\n <select id=\"wp-lock\"><option value=\"\">All (no lock)</option>${options}</select>\n </div>`;\n }\n\n private styles(): string {\n return `\n body { margin: 0; padding: 20px; font-family: Arial, sans-serif; background: #f5f5f5; }\n h1 { text-align: center; color: #333; }\n .hint { text-align: center; color: #555; margin: 0 0 16px; }\n /* Every box is clickable (the floating node menu replaced direct\n * navigation), so the menu's own stylesheet carries the cursor + blue\n * glow, and the dim/undim rules a hover or a lock toggles. Both are\n * shared verbatim with the per-project design pages. */\n ${this.nodeMenu.styles()}\n ${this.nodeMenu.dimStyles('#graph')}\n #graph {\n text-align: center;\n background: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n .legend {\n margin: 20px auto;\n max-width: 1100px;\n padding: 15px;\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n .legend h2 { margin-top: 0; }\n .legend-item { margin: 8px 0; }\n .legend-box {\n display: inline-block;\n width: 20px;\n height: 20px;\n border: 1px solid #ccc;\n margin-right: 10px;\n vertical-align: middle;\n }\n ${this.componentStyles()}\n ${this.filterAssets.styles()}\n ${new GraphNavigation().styles()}`;\n }\n\n // Styles for the lock dropdown and the responsibilities card list below the\n // graph. Split out of styles() to keep each method within the line limit.\n private componentStyles(): string {\n return `\n /* The architecture graph is very wide, so lay the legend out as three\n * side-by-side columns (fill / border / edge) instead of one tall\n * column โ it keeps the legend short next to the wide graph, and\n * collapses back to a single column on narrow viewports. */\n .legend-columns {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 28px;\n align-items: start;\n }\n .legend-col h3 { margin: 0 0 8px; color: #333; font-size: 15px; }\n @media (max-width: 800px) { .legend-columns { grid-template-columns: 1fr; } }\n .wp-lock-control {\n max-width: 600px;\n margin: 0 auto 16px;\n padding: 12px 15px;\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n text-align: center;\n }\n .wp-lock-control label { font-weight: bold; color: #333; margin-right: 8px; }\n .wp-lock-control select { font-size: 14px; padding: 4px 8px; }\n #wp-responsibilities { max-width: 900px; margin: 24px auto 0; }\n #wp-responsibilities h2 { color: #333; }\n .wp-resp-card {\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n margin: 10px 0;\n padding: 10px 15px;\n }\n .wp-resp-card > summary { cursor: pointer; color: #333; }\n .wp-resp-level {\n display: inline-block;\n min-width: 26px;\n padding: 1px 6px;\n margin-right: 6px;\n border-radius: 4px;\n background: #eef;\n font-size: 12px;\n font-weight: bold;\n text-align: center;\n }\n .wp-resp-body { margin-top: 8px; color: #444; }\n .wp-resp-body code {\n background: #f2f2f2;\n padding: 1px 4px;\n border-radius: 3px;\n font-family: monospace;\n }\n .wp-hidden { display: none; }`;\n }\n\n // The legend is laid out in three side-by-side columns (fill / border /\n // edge) so it stays short next to the very wide architecture graph. Each\n // column's rows come from a helper below to keep this method within the line\n // limit; the footnote spans the full width beneath the columns.\n private legend(): string {\n return `<div class=\"legend\">\n <h2>Legend</h2>\n <div class=\"legend-columns\">\n <div class=\"legend-col\">\n <h3>Fill = framework (libType)</h3>\n ${this.fillItems()}\n </div>\n <div class=\"legend-col\">\n <h3>Border = role</h3>\n ${this.borderItems()}\n </div>\n <div class=\"legend-col\">\n <h3>Edge lines โ <em>why</em> a project depends on an api-lib</h3>\n ${this.edgeItems()}\n </div>\n </div>\n <div class=\"legend-item\" style=\"margin-top: 15px;\">\n <em>Each node label shows its dependency level (L#), its framework env set (e.g. [browser, node]), and its role. Every row is one dependency level and nothing else: the HIGHEST level is the top row, levels descend as you read down, and L0 โ the foundation libraries everything else is built on โ is always the bottom row. Transitive dependencies are allowed but not shown.</em>\n </div>\n </div>`;\n }\n\n // Column 1 โ fill color keyed on the project's framework (libType) env set.\n private fillItems(): string {\n return `<div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #FCE4EC;\"></span>\n <strong>angular:</strong> Angular front-end\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #E3F2FD;\"></span>\n <strong>react:</strong> React front-end\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #EDE7F6;\"></span>\n <strong>browser:</strong> browser front-end base env\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #E8F5E9;\"></span>\n <strong>express:</strong> Express / server side\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #FFF9C4;\"></span>\n <strong>node:</strong> node server base env\n </div>`;\n }\n\n // Column 2 โ border style keyed on the project's role.\n private borderItems(): string {\n return `<div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 3px solid green;\"></span>\n <strong>server:</strong> runnable server app (thick green border)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 3px solid red;\"></span>\n <strong>client:</strong> client app, e.g. angular (thick red border)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 2px solid #333;\"></span>\n <strong>designed-lib:</strong> library with a generated @DocumentDesign design (bold border)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 1px solid #ccc;\"></span>\n <strong>lib:</strong> plain library, no generated design (thin border)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 2px solid #EF6C00;\"></span>\n <strong>api-lib:</strong> API-contract library (defines <code>@ApiPath</code>/<code>@Rpc</code>/<code>@PubSub</code> <code>*Api</code> classes)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 2px solid #00838F;\"></span>\n <strong>api-client:</strong> a contract plus its default implementation that talks to an outside system through its SDK\n </div>`;\n }\n\n // Column 3 โ edge line style keyed on WHY a project depends on an api-lib.\n private edgeItems(): string {\n return `<div class=\"legend-item\">\n <svg width=\"42\" height=\"12\" style=\"vertical-align: middle; margin-right: 10px;\"><line x1=\"0\" y1=\"6\" x2=\"42\" y2=\"6\" stroke=\"#333\" stroke-width=\"2\"/></svg>\n <strong>uses:</strong> calls the API (generates an rpc/pubsub client via <code>createRpcClient</code>/<code>createPubSubClient</code>) โ also covers a plain library import, since a plain dependency is just a use.\n </div>\n <div class=\"legend-item\">\n <svg width=\"42\" height=\"12\" style=\"vertical-align: middle; margin-right: 10px;\"><line x1=\"0\" y1=\"6\" x2=\"42\" y2=\"6\" stroke=\"#333\" stroke-width=\"2\" stroke-dasharray=\"5,3\"/></svg>\n <strong>implements:</strong> serves the API โ labeled <code>implements: <contracts></code> so you can see WHICH contracts resolve to this server without opening the JSON. NOTE: this is a build-dependency diagram, so a UML <em>implements</em> arrow can't be used; we use a dashed line to signal a build dep, because this server implements the api and the api is built first, then this server after.\n </div>\n <div class=\"legend-item\">\n <svg width=\"42\" height=\"12\" style=\"vertical-align: middle; margin-right: 10px;\"><line x1=\"0\" y1=\"6\" x2=\"42\" y2=\"6\" stroke=\"#1976d2\" stroke-width=\"2\" stroke-dasharray=\"5,3\"/></svg>\n <strong>uses/implements:</strong> both โ implements some of the api-lib's contracts, uses others\n </div>`;\n }\n\n /**\n * The page script. The browser code lives in graph-visualizer.client.ts โ real, linted TypeScript\n * that tsc compiles in place, so what this inlines is its COMPILED .js sitting beside this file.\n *\n * The substitution is a blind split/join, which is why the placeholder token must appear EXACTLY\n * ONCE in the client (never in one of its comments): every literal occurrence would otherwise be\n * replaced by the whole DOT.\n */\n private script(model: GraphRenderModel, links: DesignLink[]): string {\n return this.clientJs()\n .split(CLIENT_MODEL_PLACEHOLDER)\n .join(this.filterAssets.json(model))\n .split(CLIENT_DESIGN_LINKS_PLACEHOLDER)\n .join(this.filterAssets.json(links));\n }\n\n /**\n * Write the committed architecture visualization to\n * architecture/dependencies.html, next to dependencies.json.\n *\n * This is a checked-in artifact, regenerated deterministically by\n * architecture:generate so every box keeps its menu, and the \"View Design\"\n * item keeps pointing at each project's committed design.html as designs\n * come and go. The DOT is embedded in the HTML (rendered client-side by\n * viz.js). Output is deterministic (sorted graph in โ same bytes out) so git\n * only shows a diff when the architecture actually changed.\n */\n writeVisualization(\n graph: EnhancedGraph,\n workspaceRoot: string,\n title: string = 'Monorepo Dependency Architecture',\n ): VisualizationPaths {\n const outputDir = path.join(workspaceRoot, ARCH_OUTPUT_DIR);\n\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true });\n }\n\n const lockControl = this.lockControl(graph);\n const responsibilities = this.responsibilities.generateSection(graph, workspaceRoot);\n const html = this.generateHTML(\n this.generateRenderModel(graph, title),\n this.designLinks(graph),\n title,\n lockControl,\n responsibilities,\n );\n const htmlPath = path.join(outputDir, 'dependencies.html');\n fs.writeFileSync(htmlPath, html, 'utf-8');\n\n return new VisualizationPaths(htmlPath);\n }\n\n /**\n * Open the HTML visualization in the default browser\n */\n openVisualization(htmlPath: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const platform = process.platform;\n let openCommand: string;\n\n if (platform === 'darwin') {\n openCommand = `open \"${htmlPath}\"`;\n } else if (platform === 'win32') {\n openCommand = `start \"\" \"${htmlPath}\"`;\n } else {\n openCommand = `xdg-open \"${htmlPath}\"`;\n }\n\n execSync(openCommand, { stdio: 'ignore' });\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(`โ ๏ธ Could not open browser: ${error.message}`);\n return false;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-visualizer.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-visualizer.ts"],"names":[],"mappings":"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","sourcesContent":["import { GraphNavigation } from './graph-navigation';\n/**\n * Graph Visualizer\n *\n * Generates visual representations of the architecture graph:\n * - DOT format (for Graphviz)\n * - Interactive HTML (using viz.js)\n *\n * All behavior lives on the injectable GraphVisualizer class so webpieces DI +\n * @DocumentDesign can wire it โ module-scope functions are a dead end the DI\n * graph can't reach.\n */\n\nimport * as fs from 'fs';\nimport { SavedSnapshot } from './saved-snapshot';\nimport * as path from 'path';\nimport { execSync } from 'child_process';\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { ApiRef, ApiRelation, ApiRelationKind } from './api-usage/api-relations';\nimport { GraphNames, NodeIdOwner } from './graph-names';\nimport { LevelBand, LevelBandLayout } from './graph-level-bands';\nimport { ProjectAdjacency, ProjectCycleDetector } from './graph-cycles';\nimport { ResponsibilitiesRenderer } from './graph-responsibilities';\nimport { GraphNodeMenu } from './graph-node-menu';\nimport { dotValue } from './dot-syntax';\nimport { GraphRenderModel, GraphFilterAssets } from './graph-render-model';\nimport { toError } from '../toError';\n\n/**\n * Framework (libType) colors for visualization โ nodes are filled by the FIRST\n * env in their set that has a color, so it is obvious at a glance which side a\n * project targets. A project's full env set is shown in the label.\n */\nconst FRAMEWORK_COLORS: Record<string, string> = {\n angular: '#FCE4EC', // pink - Angular front-end\n react: '#E3F2FD', // blue - React front-end\n browser: '#EDE7F6', // purple - browser (front-end base env)\n express: '#E8F5E9', // green - Express / server side\n node: '#FFF9C4', // yellow - node (server base env)\n};\n\nconst DEFAULT_FRAMEWORK_COLOR = '#F5F5F5'; // grey - unknown/empty env set\n\n/**\n * Directory (repo-relative) that the committed architecture HTML lives in.\n * Node click-through links are computed relative to this so they resolve when\n * the file is opened straight from the checkout.\n */\nconst ARCH_OUTPUT_DIR = 'architecture';\n\n/** Contracts named on one `implements` edge label before it truncates to \"+N more\". */\nconst MAX_EDGE_LABEL_APIS = 4;\n\n/**\n * One node's design page, handed to the browser so the node menu can offer \"View Design\" for the\n * boxes that HAVE one and omit the item entirely for the boxes that do not.\n *\n * The link is data on the PAGE now, not a `URL=` attribute in the DOT: a URL made viz.js wrap the\n * box in an `<a>` and clicking navigated straight out, which the menu replaces.\n */\nexport class DesignLink {\n constructor(\n public readonly nodeId: string,\n public readonly href: string,\n ) {}\n}\n\nexport class VisualizationPaths {\n htmlPath: string;\n\n constructor(htmlPath: string) {\n this.htmlPath = htmlPath;\n }\n}\n\n/**\n * The token the client scripts carry where the DOT belongs. It must appear EXACTLY ONCE in a compiled\n * client โ the inliner is a blind split/join, so a second occurrence (in a comment, say) would be\n * replaced by the entire DOT too, bloating every generated page.\n */\nexport const CLIENT_MODEL_PLACEHOLDER = '__' + 'RENDER_MODEL' + '__';\n\n/** Same contract as CLIENT_MODEL_PLACEHOLDER, for the node โ design.html links the menu offers. */\nexport const CLIENT_DESIGN_LINKS_PLACEHOLDER = '__' + 'DESIGN_LINKS' + '__';\n\n/**\n * Read a compiled browser client sitting beside this file.\n *\n * It is generated from the matching `.client.ts` by tsc, so it exists in `dist` and in the published\n * tarball but NOT in a source checkout. The error says that outright rather than surfacing a bare\n * ENOENT, because \"the build has not run\" and \"the file is missing\" look identical otherwise.\n */\n// webpieces-disable no-function-outside-class -- module-level resolver for the default injected into GraphVisualizer/RuntimeHtmlPage; making it a class would need a container in a module both of them load eagerly\nexport function readCompiledClient(name: string): string {\n const file = path.join(__dirname, name);\n if (!fs.existsSync(file)) {\n throw new Error(\n `${name} not found beside ${__dirname}. It is COMPILED from ${name.replace(/\\.js$/, '.ts')} ` +\n 'by tsc, so it only exists after a build โ run the package build, or inject the text.',\n );\n }\n return fs.readFileSync(file, 'utf-8');\n}\n\nexport class GraphVisualizer {\n private readonly filterAssets = new GraphFilterAssets();\n private readonly snapshot = new SavedSnapshot();\n private readonly names = new GraphNames();\n private readonly responsibilities = new ResponsibilitiesRenderer();\n private readonly bandLayout = new LevelBandLayout();\n private readonly cycles = new ProjectCycleDetector();\n /** The ONE floating-node-menu implementation, shared with the per-project design pages. */\n private readonly nodeMenu = new GraphNodeMenu();\n\n /**\n * How to obtain the browser client's text. Injected so HTML generation does not depend on BUILD\n * ORDER: the default reads the compiled sibling, which exists in the tarball and in dist but NOT in\n * a source checkout (the source there is .client.ts). A unit test running from source supplies the\n * text itself rather than requiring the package to have been built first.\n */\n constructor(\n private readonly clientJs: () => string = (): string =>\n readCompiledClient('graph-visualizer.client.js'),\n private readonly filterJs: () => string = (): string =>\n readCompiledClient('graph-filter.client.js'),\n ) {}\n\n /**\n * A project tagged `drawOnGraph:false` is hidden from the rendered graph โ\n * its node, its rank placement, its dropdown option, its responsibilities\n * card, and every edge touching it are all omitted. It stays in the JSON.\n */\n private isHidden(entry: EnhancedGraph[string]): boolean {\n return entry.drawOnGraph === false;\n }\n\n /**\n * Fill color for an env set โ the color of the first env in the set that has\n * a known color, else the default.\n */\n private frameworkColor(frameworks: string[]): string {\n for (const env of frameworks) {\n const color = FRAMEWORK_COLORS[env];\n if (color !== undefined) return color;\n }\n return DEFAULT_FRAMEWORK_COLOR;\n }\n\n /**\n * Role border styling โ fill stays keyed on framework; the border shows a\n * project's ROLE at a glance. Server and client are the top-level runnable\n * nodes, so they get bold, colored borders to stand out:\n * server โ thick GREEN border (a runnable server app)\n * app โ thick BLUE border (a runnable non-HTTP app, e.g. a tooling app)\n * bundle โ thick PURPLE border (an aggregator that bundles several apps)\n * client โ thick RED border (a client app, e.g. angular)\n * designed-lib โ bold border (a library with a generated @DocumentDesign design)\n * lib / other โ plain thin border\n */\n private roleBorderAttrs(role: string): string {\n if (role === 'server') return ', color=\"green\", penwidth=3';\n if (role === 'app') return ', color=\"#1976d2\", penwidth=3';\n if (role === 'bundle') return ', color=\"#6A1B9A\", penwidth=3';\n if (role === 'client') return ', color=\"red\", penwidth=3';\n if (role === 'api-lib') return ', color=\"#EF6C00\", penwidth=2';\n if (role === 'api-client') return ', color=\"#00838F\", penwidth=2';\n if (role === 'designed-lib') return ', penwidth=2';\n return '';\n }\n\n /**\n * Edge styling by API-relation kind (why the edge exists):\n * implements โ BLACK dashed (a controller serves this api-lib's contract)\n * uses โ BLACK solid (a generated client calls it) โ same as a\n * plain library import, since a plain dependency IS a use.\n * uses-implements โ BLUE dashed, thicker (does both โ implements some\n * contracts of the api-lib, uses others)\n * plain lib (none) โ the default thin black solid arrow, unchanged.\n * `kind` is undefined for every non-api-lib dependency edge.\n */\n private edgeAttrs(kind: ApiRelationKind | undefined): string {\n if (kind === 'implements') return 'style=dashed';\n if (kind === 'uses-implements') return 'style=dashed, color=\"#1976d2\", penwidth=2';\n return '';\n }\n\n /**\n * The DOT attribute list for ONE dependency edge. An edge that IMPLEMENTS contracts is also\n * LABELED with them: the dashed line alone says \"something here is implemented\", which reads as\n * \"the tool did not detect anything\" to everyone who has not memorized the legend. Naming the\n * contracts makes `auth-store-api` visibly resolve to the server that serves it โ the single\n * most important relationship in a microservice architecture, and the one the diagram was\n * silent about.\n */\n private edgeDot(from: string, to: string, relation: ApiRelation | undefined): string {\n const attrs: string[] = [];\n const styling = this.edgeAttrs(relation?.kind);\n if (styling !== '') attrs.push(styling);\n const served = (relation?.implements ?? []).map((ref: ApiRef) => ref.api);\n if (served.length > 0)\n attrs.push(`label=\"implements: ${this.labelledApis(served)}\", fontsize=9`);\n const suffix = attrs.length === 0 ? '' : ` [${attrs.join(', ')}]`;\n return ` \"${dotValue(from)}\" -> \"${dotValue(to)}\"${suffix};\\n`;\n }\n\n /**\n * The contract names for an edge label, sorted and capped so a shared api-lib serving a dozen\n * contracts cannot blow the edge label up into a wall of text. The truncation is stated in the\n * label (\"+N more\") rather than silent โ the full list is in dependencies.json.\n */\n private labelledApis(apis: string[]): string {\n const sorted = [...apis].sort();\n if (sorted.length <= MAX_EDGE_LABEL_APIS) return sorted.join(', ');\n const shown = sorted.slice(0, MAX_EDGE_LABEL_APIS).join(', ');\n return `${shown} +${sorted.length - MAX_EDGE_LABEL_APIS} more`;\n }\n\n /**\n * Href for a node's design page: the project's committed design.html, made\n * relative to architecture/dependencies.html. Returns null when the project\n * has no generated DI design (no design.json โ no design page exists).\n */\n private designHtmlHref(designFile: string | undefined): string | null {\n if (!designFile) return null;\n const designHtml = designFile.replace(/design\\.json$/, 'design.html');\n return path.posix.relative(ARCH_OUTPUT_DIR, designHtml);\n }\n\n /**\n * The design pages that EXIST, one entry per visible node that has one. A node absent from this\n * list gets no \"View Design\" item at all โ the item is never rendered dead or greyed out.\n */\n designLinks(graph: EnhancedGraph): DesignLink[] {\n const links: DesignLink[] = [];\n for (const project of Object.keys(graph)) {\n const info = graph[project];\n if (this.isHidden(info)) continue;\n const href = this.designHtmlHref(info.designFile);\n if (href === null) continue;\n links.push(new DesignLink(this.names.getNodeId(project), href));\n }\n return links;\n }\n\n /**\n * Generate Graphviz DOT format from the graph\n */\n generateDot(graph: EnhancedGraph, title: string = 'Monorepo Dependency Architecture'): string {\n return this.generateRenderModel(graph, title).fullDot;\n }\n\n generateRenderModel(\n graph: EnhancedGraph,\n title: string = 'Monorepo Dependency Architecture',\n ): GraphRenderModel {\n this.assertDrawable(graph);\n const model = new GraphRenderModel();\n let dot = 'digraph Architecture {\\n';\n dot += ' rankdir=TB;\\n';\n dot += ' node [shape=box, style=filled, fontname=\"Arial\"];\\n';\n dot += ' edge [fontname=\"Arial\"];\\n\\n';\n\n model.header = dot;\n const bands = this.levelBands(graph);\n model.bands = bands;\n\n dot += this.dotNodes(graph, model);\n dot += '\\n';\n dot += this.bandLayout.dot(bands);\n dot += '\\n';\n dot += this.dotEdges(graph, model);\n\n const footerStart = dot.length;\n dot += '\\n labelloc=\"t\";\\n';\n dot += ` label=\"${dotValue(title)}\\\\n(from architecture/dependencies.json)\";\\n`;\n dot += ' fontsize=20;\\n';\n dot += '}\\n';\n\n model.footer = dot.slice(footerStart);\n model.completeEndpoints();\n model.fullDot = dot;\n return model;\n }\n\n /**\n * Refuse to draw a graph that would render something false.\n *\n * Two conditions, both of which produced a confidently-wrong picture with no warning at all:\n * - two projects sharing a node id fuse into one box, which UNIONS their two rank sets and\n * collapses two whole dependency levels onto one row (see graph-names.ts);\n * - a cycle makes the level numbers every row is keyed on meaningless (see graph-cycles.ts).\n */\n private assertDrawable(graph: EnhancedGraph): void {\n const owners: NodeIdOwner[] = [];\n const adjacency: ProjectAdjacency = {};\n for (const project of Object.keys(graph)) {\n owners.push(new NodeIdOwner(project, graph[project].level));\n adjacency[project] = graph[project].dependsOn ?? [];\n }\n this.names.assertUniqueNodeIds(owners);\n this.cycles.assertAcyclic(adjacency, 'architecture/dependencies.json');\n }\n\n /**\n * The visible projects grouped into one band per dependency level, ordered HIGHEST LEVEL FIRST\n * so the emitted bands read top-to-bottom with L0 last (hidden projects are omitted, so no\n * stray rank=same name is emitted for an absent node). LevelBandLayout turns these into the\n * rank sets and the invisible chain that pins them โ see graph-level-bands.ts for why the\n * chain is required at all.\n */\n private levelBands(graph: EnhancedGraph): LevelBand[] {\n const byLevel = new Map<number, string[]>();\n for (const project of Object.keys(graph)) {\n if (this.isHidden(graph[project])) continue;\n const level = graph[project].level;\n const nodeIds = byLevel.get(level);\n if (nodeIds === undefined) byLevel.set(level, [this.names.getNodeId(project)]);\n else nodeIds.push(this.names.getNodeId(project));\n }\n const levels = [...byLevel.keys()].sort((a: number, b: number): number => b - a);\n return levels.map(\n (level: number): LevelBand =>\n new LevelBand(level, [...(byLevel.get(level) as string[])].sort()),\n );\n }\n\n // Node lines: fill colored by framework env set (libType), border shaped by\n // role; the label shows the env set + role (e.g. [browser, node] ยท server).\n // No node carries a URL: EVERY box is clickable and opens the floating node\n // menu instead, which is where a design page is reached (see designLinks).\n private dotNodes(graph: EnhancedGraph, model: GraphRenderModel): string {\n let dot = '';\n for (const project of Object.keys(graph)) {\n const info = graph[project];\n if (this.isHidden(info)) continue;\n const nodeId = this.names.getNodeId(project);\n const shortName = this.names.getShortName(project);\n const frameworks = info.framework ?? [];\n const role = info.role ?? 'lib';\n const color = this.frameworkColor(frameworks);\n const border = this.roleBorderAttrs(role);\n const envSet = `[${frameworks.join(', ')}]`;\n const labelMeta = `L${info.level} ยท ${envSet} ยท ${role}`;\n // Identity is the project key; the LABEL is the pretty short name.\n dot += model.node(\n nodeId,\n ` \"${dotValue(nodeId)}\" [fillcolor=\"${color}\"${border}, label=\"${dotValue(shortName)}\\\\n(${dotValue(labelMeta)})\"];\\n`,\n );\n }\n return dot;\n }\n\n // Edge lines (dependencies). An edge to an api-lib is styled by WHY it exists\n // (implements/uses/uses-implements, from apiRelations); every other dependency\n // keeps the default plain arrow.\n private dotEdges(graph: EnhancedGraph, model: GraphRenderModel): string {\n let dot = '';\n for (const project of Object.keys(graph)) {\n const info = graph[project];\n if (this.isHidden(info)) continue;\n const nodeId = this.names.getNodeId(project);\n for (const dep of info.dependsOn || []) {\n // Both endpoints must be visible โ an edge to/from a hidden box\n // is dropped so no connection dangles into empty space.\n if (graph[dep] !== undefined && this.isHidden(graph[dep])) continue;\n dot += model.edge(\n nodeId,\n this.names.getNodeId(dep),\n this.edgeDot(nodeId, this.names.getNodeId(dep), info.apiRelations?.[dep]),\n );\n }\n }\n return dot;\n }\n\n /**\n * Generate interactive HTML with embedded SVG using viz.js\n */\n generateHTML(\n model: GraphRenderModel,\n links: DesignLink[],\n title: string = 'Monorepo Dependency Architecture',\n lockControl: string = '',\n responsibilitiesHtml: string = '',\n ): string {\n const styles = this.styles();\n const legend = this.legend();\n const script = this.script(model, links);\n\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>${title}</title>\n <script src=\"https://cdn.jsdelivr.net/npm/@viz-js/viz@3.28.0/dist/viz-global.js\"></script>\n <style>${styles}</style>\n</head>\n<body>\n <h1>${title}</h1>\n ${this.snapshot.html()}\n <p class=\"hint\">๐ก <strong>Click any box</strong> for its menu โ <strong>View Design</strong> (only where that project has a generated <strong>design.html</strong>, i.e. what the AI sees inside it) and <strong>Lock/Unlock</strong>, which is the same lock as the dropdown below. <strong>Filter Unconnected</strong> removes unrelated boxes and compacts the graph while preserving each L-number row.</p>\n <p class=\"hint\">๐ฆ <strong>Hover any box</strong> to trace its <em>entire</em> dependency chain โ every ancestor above it (all the way up) <em>and</em> every dependency below it (all the way down), with all the boxes and lines between โ while the rest of the graph dims so you can follow one box at a glance.</p>\n ${legend}\n ${lockControl}\n ${this.filterAssets.html()}\n <div id=\"graph\"></div>\n ${responsibilitiesHtml}\n <script>${this.nodeMenu.script()}</script>\n <script>${this.filterJs()}</script>\n <script>${new GraphNavigation().script()}</script>\n <script>${script}</script>\n</body>\n</html>`;\n }\n\n /**\n * The lock control (a single-select dropdown, rendered below the legend).\n * Picking a module LOCKS the graph into that box's hover view โ its full\n * ancestor + descendant chain stays lit while everything else stays dimmed โ\n * and narrows the responsibilities list below the graph to just that chain.\n * The first option, \"All\", is the default and clears the lock. Hover still\n * works on top of a lock; leaving a box returns to the locked view.\n *\n * Options are ordered by level DESCENDING to match the responsibilities cards.\n */\n lockControl(graph: EnhancedGraph): string {\n const projects = Object.keys(graph);\n projects.sort((a: string, b: string): number => {\n const levelDiff = graph[b].level - graph[a].level;\n if (levelDiff !== 0) return levelDiff;\n return a.localeCompare(b);\n });\n let options = '';\n for (const project of projects) {\n if (this.isHidden(graph[project])) continue;\n // The VALUE is the node id the SVG is keyed on; the TEXT stays the pretty short name.\n const nodeId = this.names.getNodeId(project);\n const shortName = this.names.getShortName(project);\n options += `<option value=\"${nodeId}\">L${graph[project].level} ยท ${shortName}</option>`;\n }\n return `<div class=\"wp-lock-control\">\n <label for=\"wp-lock\">๐ Lock a box (dim the rest & filter responsibilities):</label>\n <select id=\"wp-lock\"><option value=\"\">All (no lock)</option>${options}</select>\n </div>`;\n }\n\n private styles(): string {\n return `\n body { margin: 0; padding: 20px; font-family: Arial, sans-serif; background: #f5f5f5; }\n h1 { text-align: center; color: #333; }\n .hint { text-align: center; color: #555; margin: 0 0 16px; }\n /* Every box is clickable (the floating node menu replaced direct\n * navigation), so the menu's own stylesheet carries the cursor + blue\n * glow, and the dim/undim rules a hover or a lock toggles. Both are\n * shared verbatim with the per-project design pages. */\n ${this.nodeMenu.styles()}\n ${this.nodeMenu.dimStyles('#graph')}\n #graph g.node.wp-locked polygon,\n #graph g.node.wp-locked ellipse,\n #graph g.node.wp-locked path { stroke: #b26a00; stroke-width: 3; }\n #graph {\n text-align: center;\n background: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n .legend {\n margin: 20px auto;\n max-width: 1100px;\n padding: 15px;\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n .legend h2 { margin-top: 0; }\n .legend-item { margin: 8px 0; }\n .legend-box {\n display: inline-block;\n width: 20px;\n height: 20px;\n border: 1px solid #ccc;\n margin-right: 10px;\n vertical-align: middle;\n }\n ${this.componentStyles()}\n ${this.filterAssets.styles()}\n ${new GraphNavigation().styles()}`;\n }\n\n // Styles for the lock dropdown and the responsibilities card list below the\n // graph. Split out of styles() to keep each method within the line limit.\n private componentStyles(): string {\n return `\n /* The architecture graph is very wide, so lay the legend out as three\n * side-by-side columns (fill / border / edge) instead of one tall\n * column โ it keeps the legend short next to the wide graph, and\n * collapses back to a single column on narrow viewports. */\n .legend-columns {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 28px;\n align-items: start;\n }\n .legend-col h3 { margin: 0 0 8px; color: #333; font-size: 15px; }\n @media (max-width: 800px) { .legend-columns { grid-template-columns: 1fr; } }\n .wp-lock-control {\n max-width: 600px;\n margin: 0 auto 16px;\n padding: 12px 15px;\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n text-align: center;\n }\n .wp-lock-control label { font-weight: bold; color: #333; margin-right: 8px; }\n .wp-lock-control select { font-size: 14px; padding: 4px 8px; }\n #wp-responsibilities { max-width: 900px; margin: 24px auto 0; }\n #wp-responsibilities h2 { color: #333; }\n .wp-resp-card {\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n margin: 10px 0;\n padding: 10px 15px;\n }\n .wp-resp-card > summary { cursor: pointer; color: #333; }\n .wp-resp-level {\n display: inline-block;\n min-width: 26px;\n padding: 1px 6px;\n margin-right: 6px;\n border-radius: 4px;\n background: #eef;\n font-size: 12px;\n font-weight: bold;\n text-align: center;\n }\n .wp-resp-body { margin-top: 8px; color: #444; }\n .wp-resp-body code {\n background: #f2f2f2;\n padding: 1px 4px;\n border-radius: 3px;\n font-family: monospace;\n }\n .wp-hidden { display: none; }`;\n }\n\n // The legend is laid out in three side-by-side columns (fill / border /\n // edge) so it stays short next to the very wide architecture graph. Each\n // column's rows come from a helper below to keep this method within the line\n // limit; the footnote spans the full width beneath the columns.\n private legend(): string {\n return `<div class=\"legend\">\n <h2>Legend</h2>\n <div class=\"legend-columns\">\n <div class=\"legend-col\">\n <h3>Fill = framework (libType)</h3>\n ${this.fillItems()}\n </div>\n <div class=\"legend-col\">\n <h3>Border = role</h3>\n ${this.borderItems()}\n </div>\n <div class=\"legend-col\">\n <h3>Edge lines โ <em>why</em> a project depends on an api-lib</h3>\n ${this.edgeItems()}\n </div>\n </div>\n <div class=\"legend-item\" style=\"margin-top: 15px;\">\n <em>Each node label shows its dependency level (L#), its framework env set (e.g. [browser, node]), and its role. Every row is one dependency level and nothing else: the HIGHEST level is the top row, levels descend as you read down, and L0 โ the foundation libraries everything else is built on โ is always the bottom row. Transitive dependencies are allowed but not shown.</em>\n </div>\n </div>`;\n }\n\n // Column 1 โ fill color keyed on the project's framework (libType) env set.\n private fillItems(): string {\n return `<div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #FCE4EC;\"></span>\n <strong>angular:</strong> Angular front-end\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #E3F2FD;\"></span>\n <strong>react:</strong> React front-end\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #EDE7F6;\"></span>\n <strong>browser:</strong> browser front-end base env\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #E8F5E9;\"></span>\n <strong>express:</strong> Express / server side\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"background: #FFF9C4;\"></span>\n <strong>node:</strong> node server base env\n </div>`;\n }\n\n // Column 2 โ border style keyed on the project's role.\n private borderItems(): string {\n return `<div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 3px solid green;\"></span>\n <strong>server:</strong> runnable server app (thick green border)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 3px solid red;\"></span>\n <strong>client:</strong> client app, e.g. angular (thick red border)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 2px solid #333;\"></span>\n <strong>designed-lib:</strong> library with a generated @DocumentDesign design (bold border)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 1px solid #ccc;\"></span>\n <strong>lib:</strong> plain library, no generated design (thin border)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 2px solid #EF6C00;\"></span>\n <strong>api-lib:</strong> API-contract library (defines <code>@ApiPath</code>/<code>@Rpc</code>/<code>@PubSub</code> <code>*Api</code> classes)\n </div>\n <div class=\"legend-item\">\n <span class=\"legend-box\" style=\"border: 2px solid #00838F;\"></span>\n <strong>api-client:</strong> a contract plus its default implementation that talks to an outside system through its SDK\n </div>`;\n }\n\n // Column 3 โ edge line style keyed on WHY a project depends on an api-lib.\n private edgeItems(): string {\n return `<div class=\"legend-item\">\n <svg width=\"42\" height=\"12\" style=\"vertical-align: middle; margin-right: 10px;\"><line x1=\"0\" y1=\"6\" x2=\"42\" y2=\"6\" stroke=\"#333\" stroke-width=\"2\"/></svg>\n <strong>uses:</strong> calls the API (generates an rpc/pubsub client via <code>createRpcClient</code>/<code>createPubSubClient</code>) โ also covers a plain library import, since a plain dependency is just a use.\n </div>\n <div class=\"legend-item\">\n <svg width=\"42\" height=\"12\" style=\"vertical-align: middle; margin-right: 10px;\"><line x1=\"0\" y1=\"6\" x2=\"42\" y2=\"6\" stroke=\"#333\" stroke-width=\"2\" stroke-dasharray=\"5,3\"/></svg>\n <strong>implements:</strong> serves the API โ labeled <code>implements: <contracts></code> so you can see WHICH contracts resolve to this server without opening the JSON. NOTE: this is a build-dependency diagram, so a UML <em>implements</em> arrow can't be used; we use a dashed line to signal a build dep, because this server implements the api and the api is built first, then this server after.\n </div>\n <div class=\"legend-item\">\n <svg width=\"42\" height=\"12\" style=\"vertical-align: middle; margin-right: 10px;\"><line x1=\"0\" y1=\"6\" x2=\"42\" y2=\"6\" stroke=\"#1976d2\" stroke-width=\"2\" stroke-dasharray=\"5,3\"/></svg>\n <strong>uses/implements:</strong> both โ implements some of the api-lib's contracts, uses others\n </div>`;\n }\n\n /**\n * The page script. The browser code lives in graph-visualizer.client.ts โ real, linted TypeScript\n * that tsc compiles in place, so what this inlines is its COMPILED .js sitting beside this file.\n *\n * The substitution is a blind split/join, which is why the placeholder token must appear EXACTLY\n * ONCE in the client (never in one of its comments): every literal occurrence would otherwise be\n * replaced by the whole DOT.\n */\n private script(model: GraphRenderModel, links: DesignLink[]): string {\n return this.clientJs()\n .split(CLIENT_MODEL_PLACEHOLDER)\n .join(this.filterAssets.json(model))\n .split(CLIENT_DESIGN_LINKS_PLACEHOLDER)\n .join(this.filterAssets.json(links));\n }\n\n /**\n * Write the committed architecture visualization to\n * architecture/dependencies.html, next to dependencies.json.\n *\n * This is a checked-in artifact, regenerated deterministically by\n * architecture:generate so every box keeps its menu, and the \"View Design\"\n * item keeps pointing at each project's committed design.html as designs\n * come and go. The DOT is embedded in the HTML (rendered client-side by\n * viz.js). Output is deterministic (sorted graph in โ same bytes out) so git\n * only shows a diff when the architecture actually changed.\n */\n writeVisualization(\n graph: EnhancedGraph,\n workspaceRoot: string,\n title: string = 'Monorepo Dependency Architecture',\n ): VisualizationPaths {\n const outputDir = path.join(workspaceRoot, ARCH_OUTPUT_DIR);\n\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true });\n }\n\n const lockControl = this.lockControl(graph);\n const responsibilities = this.responsibilities.generateSection(graph, workspaceRoot);\n const html = this.generateHTML(\n this.generateRenderModel(graph, title),\n this.designLinks(graph),\n title,\n lockControl,\n responsibilities,\n );\n const htmlPath = path.join(outputDir, 'dependencies.html');\n fs.writeFileSync(htmlPath, html, 'utf-8');\n\n return new VisualizationPaths(htmlPath);\n }\n\n /**\n * Open the HTML visualization in the default browser\n */\n openVisualization(htmlPath: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const platform = process.platform;\n let openCommand: string;\n\n if (platform === 'darwin') {\n openCommand = `open \"${htmlPath}\"`;\n } else if (platform === 'win32') {\n openCommand = `start \"\" \"${htmlPath}\"`;\n } else {\n openCommand = `xdg-open \"${htmlPath}\"`;\n }\n\n execSync(openCommand, { stdio: 'ignore' });\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(`โ ๏ธ Could not open browser: ${error.message}`);\n return false;\n }\n }\n}\n"]}
|