@webpieces/nx-webpieces-rules 0.3.252 → 0.3.254
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/executors/di-graph-generate/executor.js +37 -7
- package/src/executors/di-graph-generate/executor.js.map +1 -1
- package/src/lib/di-graph/analyzer-strategy.d.ts +21 -4
- package/src/lib/di-graph/analyzer-strategy.js +47 -12
- package/src/lib/di-graph/analyzer-strategy.js.map +1 -1
- package/src/lib/di-graph/analyzer.d.ts +10 -5
- package/src/lib/di-graph/analyzer.js +29 -13
- package/src/lib/di-graph/analyzer.js.map +1 -1
- package/src/lib/di-graph/dot.js +4 -2
- package/src/lib/di-graph/dot.js.map +1 -1
- package/src/lib/di-graph/mermaid.js +10 -1
- package/src/lib/di-graph/mermaid.js.map +1 -1
- package/src/lib/di-graph/model.d.ts +1 -1
- package/src/lib/di-graph/model.js.map +1 -1
- package/src/lib/graph-loader.js +1 -0
- package/src/lib/graph-loader.js.map +1 -1
- package/src/lib/graph-metadata.d.ts +18 -0
- package/src/lib/graph-metadata.js +49 -1
- package/src/lib/graph-metadata.js.map +1 -1
- package/src/lib/graph-sorter.d.ts +1 -0
- package/src/lib/graph-sorter.js.map +1 -1
- package/src/lib/graph-visualizer.js +40 -4
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/role-resolver.d.ts +39 -0
- package/src/lib/role-resolver.js +59 -0
- package/src/lib/role-resolver.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.254",
|
|
4
4
|
"description": "Nx-specific webpieces validation rules and graph tooling. Bundles all @webpieces rule packages with Nx graph validators and an inference plugin.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"README.md"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@webpieces/ai-hook-rules": "0.3.
|
|
25
|
-
"@webpieces/code-rules": "0.3.
|
|
26
|
-
"@webpieces/eslint-rules": "0.3.
|
|
27
|
-
"@webpieces/pr-gate": "0.3.
|
|
28
|
-
"@webpieces/rules-config": "0.3.
|
|
24
|
+
"@webpieces/ai-hook-rules": "0.3.254",
|
|
25
|
+
"@webpieces/code-rules": "0.3.254",
|
|
26
|
+
"@webpieces/eslint-rules": "0.3.254",
|
|
27
|
+
"@webpieces/pr-gate": "0.3.254",
|
|
28
|
+
"@webpieces/rules-config": "0.3.254",
|
|
29
29
|
"madge": "8.0.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
@@ -36,6 +36,7 @@ const RULE_NAME = 'di-graph';
|
|
|
36
36
|
// Inversify decorator isn't short-circuited to empty.
|
|
37
37
|
const DI_MARKERS = [
|
|
38
38
|
'@Controller(',
|
|
39
|
+
'@ApiImplementation(',
|
|
39
40
|
'@provideSingleton',
|
|
40
41
|
'@provideTransient',
|
|
41
42
|
'@injectable',
|
|
@@ -86,6 +87,34 @@ function writeDesignFiles(projectRootAbs, graph) {
|
|
|
86
87
|
fs.writeFileSync(path.join(projectRootAbs, 'design.json'), (0, serializer_1.toDesignJson)(graph));
|
|
87
88
|
fs.writeFileSync(path.join(projectRootAbs, 'design.md'), (0, mermaid_1.toDesignMarkdown)(graph));
|
|
88
89
|
}
|
|
90
|
+
/** The analyzer chosen for a project plus the role tag that drove the choice. */
|
|
91
|
+
class AnalyzerChoice {
|
|
92
|
+
analyzer;
|
|
93
|
+
role;
|
|
94
|
+
constructor(analyzer, role) {
|
|
95
|
+
this.analyzer = analyzer;
|
|
96
|
+
this.role = role;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Select the analyzer by role (server→@Controller, designed-lib→@ApiImplementation,
|
|
101
|
+
* client→angular design, lib→skip). The explicit `role:` nx tag is the source of
|
|
102
|
+
* truth; when absent we fall back to the legacy `framework:` selection + marker
|
|
103
|
+
* pre-scan so designs stay identical until a project is retagged.
|
|
104
|
+
*/
|
|
105
|
+
function chooseAnalyzer(tags, srcDir) {
|
|
106
|
+
const role = (0, analyzer_strategy_1.explicitRoleTag)(tags);
|
|
107
|
+
const framework = (0, analyzer_strategy_1.explicitFrameworkTag)(tags);
|
|
108
|
+
const analyzer = (0, analyzer_strategy_1.selectAnalyzer)(role, framework, detectFrameworkMarkers(srcDir));
|
|
109
|
+
console.log(` Analyzer: ${analyzer.constructor.name} ` +
|
|
110
|
+
`(role tag: ${role ?? 'none'}, framework tag: ${framework ?? 'none'})`);
|
|
111
|
+
return new AnalyzerChoice(analyzer, role);
|
|
112
|
+
}
|
|
113
|
+
function reportDesignedLibMissingRoot(projectName) {
|
|
114
|
+
console.error(`❌ ${projectName} is tagged role:designed-lib but has no @ApiImplementation class.\n` +
|
|
115
|
+
` Annotate the library's top implementation class with @ApiImplementation ` +
|
|
116
|
+
`(from @webpieces/http-routing), or retag the project role:lib if it has no design.`);
|
|
117
|
+
}
|
|
89
118
|
async function runExecutor(_options, context) {
|
|
90
119
|
const shared = (0, rules_config_1.loadAndValidate)(context.root).resolved;
|
|
91
120
|
const rule = shared.rules.get(RULE_NAME);
|
|
@@ -112,13 +141,14 @@ async function runExecutor(_options, context) {
|
|
|
112
141
|
writeDesignFiles(projectRootAbs, new model_1.DiGraph(projectName));
|
|
113
142
|
return { success: true };
|
|
114
143
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
144
|
+
const choice = chooseAnalyzer(projectConfig?.tags ?? [], srcDir);
|
|
145
|
+
const graph = choice.analyzer.analyzeProject(program, context.root, projectRoot, projectName);
|
|
146
|
+
// A designed-lib MUST expose at least one @ApiImplementation root, else its
|
|
147
|
+
// design would be empty and the tag is meaningless — fail loudly.
|
|
148
|
+
if (choice.role === 'designed-lib' && graph.designs.length === 0) {
|
|
149
|
+
reportDesignedLibMissingRoot(projectName);
|
|
150
|
+
return { success: false };
|
|
151
|
+
}
|
|
122
152
|
writeDesignFiles(projectRootAbs, graph);
|
|
123
153
|
const nodeCount = graph.designs.reduce((sum, d) => sum + d.nodes.length, 0);
|
|
124
154
|
const edgeCount = graph.designs.reduce((sum, d) => sum + d.edges.length, 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/di-graph-generate/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DI Graph Generate Executor\n *\n * Per-project: statically analyzes the project's Inversify dependency DAG\n * (constructor injection from controllers — or library top-of-DAG classes —\n * down to leaves) and writes two checked-in files at the project root:\n *\n * design.json — machine-readable graph (deterministic, sorted)\n * design.md — Mermaid diagram rendered by GitHub/IDEs in PRs\n *\n * Runs on every build (cache:false; the build gates on\n * validate-di-graph-unchanged which dependsOn this target). Unrecognized DI\n * patterns become \"unresolved\" nodes rather than failing the build.\n *\n * Config (webpieces.config.json, rule key `di-graph`): mode RUN_EVERY_TIME | OFF.\n *\n * Usage: nx run <project>:di-graph-generate\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadAndValidate } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n explicitFrameworkTag,\n FrameworkMarkers,\n selectAnalyzer,\n} from '../../lib/di-graph/analyzer-strategy';\nimport { createProjectProgram } from '../../lib/di-graph/program';\nimport { toDesignJson } from '../../lib/di-graph/serializer';\nimport { toDesignMarkdown } from '../../lib/di-graph/mermaid';\nimport { DiDesign, DiGraph } from '../../lib/di-graph/model';\nimport { toError } from '../../toError';\n\nexport interface DiGraphGenerateOptions {\n // No options here — config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst RULE_NAME = 'di-graph';\n\n// Cheap substring pre-scan: a project whose source never mentions any DI marker\n// gets an empty graph without paying for a ts.Program. Angular markers\n// (@Component/bootstrapApplication) are included so an Angular app that uses no\n// Inversify decorator isn't short-circuited to empty.\nconst DI_MARKERS = [\n '@Controller(',\n '@provideSingleton',\n '@provideTransient',\n '@injectable',\n 'new ContainerModule',\n '@inject(',\n '@Component(',\n 'bootstrapApplication',\n];\n\nconst ANGULAR_MARKERS = ['@Component(', 'bootstrapApplication'];\nconst CONTROLLER_MARKER = '@Controller(';\n\n/** Recursively read every project .ts file, folding each into `visit`. */\nfunction forEachSourceFile(dir: string, visit: (content: string) => void): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === 'node_modules' || entry.name === 'dist') continue;\n forEachSourceFile(full, visit);\n } else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) {\n visit(fs.readFileSync(full, 'utf-8'));\n }\n }\n}\n\nfunction sourceHasDiMarkers(dir: string): boolean {\n let found = false;\n forEachSourceFile(dir, (content: string) => {\n if (!found && DI_MARKERS.some((marker: string) => content.includes(marker))) found = true;\n });\n return found;\n}\n\n/** Pre-scan a project's source for the framework markers used when no tag is set. */\nfunction detectFrameworkMarkers(dir: string): FrameworkMarkers {\n let angular = false;\n let controller = false;\n forEachSourceFile(dir, (content: string) => {\n if (!angular && ANGULAR_MARKERS.some((marker: string) => content.includes(marker))) angular = true;\n if (!controller && content.includes(CONTROLLER_MARKER)) controller = true;\n });\n return new FrameworkMarkers(angular, controller);\n}\n\nfunction writeDesignFiles(projectRootAbs: string, graph: DiGraph): void {\n fs.writeFileSync(path.join(projectRootAbs, 'design.json'), toDesignJson(graph));\n fs.writeFileSync(path.join(projectRootAbs, 'design.md'), toDesignMarkdown(graph));\n}\n\nexport default async function runExecutor(\n _options: DiGraphGenerateOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get(RULE_NAME);\n if (rule && rule.isOff) {\n console.log(`\\n⏭️ Skipping ${RULE_NAME} generation (mode: OFF)\\n`);\n return { success: true };\n }\n\n const projectName = context.projectName ?? 'project';\n const projectConfig = context.projectsConfigurations?.projects[projectName];\n const projectRoot = projectConfig?.root ?? '.';\n const projectRootAbs = path.join(context.root, projectRoot);\n const srcDir = path.join(projectRootAbs, 'src');\n\n console.log(`\\n🧬 Generating DI design graph for ${projectName}\\n`);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- chokepoint: a generator crash must produce an actionable failure, not a stack trace mid-build\n try {\n if (!sourceHasDiMarkers(srcDir)) {\n console.log(' No DI markers found — writing empty design graph');\n writeDesignFiles(projectRootAbs, new DiGraph(projectName));\n return { success: true };\n }\n\n const program = createProjectProgram(projectRootAbs);\n if (!program) {\n console.log(' No usable tsconfig/source — writing empty design graph');\n writeDesignFiles(projectRootAbs, new DiGraph(projectName));\n return { success: true };\n }\n\n // Select the analyzer by framework: express → Inversify, angular →\n // Angular, else skip. The explicit `framework:` nx tag wins; a marker\n // pre-scan corroborates only when the tag is absent.\n const framework = explicitFrameworkTag(projectConfig?.tags ?? []);\n const analyzer = selectAnalyzer(framework, detectFrameworkMarkers(srcDir));\n console.log(` Analyzer: ${analyzer.constructor.name} (framework tag: ${framework ?? 'none'})`);\n\n const graph = analyzer.analyzeProject(program, context.root, projectRoot, projectName);\n writeDesignFiles(projectRootAbs, graph);\n\n const nodeCount = graph.designs.reduce((sum: number, d: DiDesign) => sum + d.nodes.length, 0);\n const edgeCount = graph.designs.reduce((sum: number, d: DiDesign) => sum + d.edges.length, 0);\n console.log(\n `✅ Wrote ${projectRoot}/design.json + design.md ` +\n `(${graph.designs.length} design(s), ${nodeCount} node(s), ${edgeCount} edge(s))`,\n );\n const unresolved = [...new Set(graph.designs.flatMap((d: DiDesign) => d.unresolved))];\n if (unresolved.length > 0) {\n console.warn(`⚠️ ${unresolved.length} unresolved token(s)/type(s): ${unresolved.join(', ')}`);\n }\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error(`❌ DI graph generation failed for ${projectName}: ${error.message}`);\n console.error(` To unblock builds, set rules[\"${RULE_NAME}\"].mode=\"OFF\" in webpieces.config.json.`);\n return { success: false };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/di-graph-generate/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DI Graph Generate Executor\n *\n * Per-project: statically analyzes the project's Inversify dependency DAG\n * (constructor injection from controllers — or library top-of-DAG classes —\n * down to leaves) and writes two checked-in files at the project root:\n *\n * design.json — machine-readable graph (deterministic, sorted)\n * design.md — Mermaid diagram rendered by GitHub/IDEs in PRs\n *\n * Runs on every build (cache:false; the build gates on\n * validate-di-graph-unchanged which dependsOn this target). Unrecognized DI\n * patterns become \"unresolved\" nodes rather than failing the build.\n *\n * Config (webpieces.config.json, rule key `di-graph`): mode RUN_EVERY_TIME | OFF.\n *\n * Usage: nx run <project>:di-graph-generate\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadAndValidate } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n DiAnalyzer,\n explicitFrameworkTag,\n explicitRoleTag,\n FrameworkMarkers,\n selectAnalyzer,\n} from '../../lib/di-graph/analyzer-strategy';\nimport { createProjectProgram } from '../../lib/di-graph/program';\nimport { toDesignJson } from '../../lib/di-graph/serializer';\nimport { toDesignMarkdown } from '../../lib/di-graph/mermaid';\nimport { DiDesign, DiGraph } from '../../lib/di-graph/model';\nimport { toError } from '../../toError';\n\nexport interface DiGraphGenerateOptions {\n // No options here — config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst RULE_NAME = 'di-graph';\n\n// Cheap substring pre-scan: a project whose source never mentions any DI marker\n// gets an empty graph without paying for a ts.Program. Angular markers\n// (@Component/bootstrapApplication) are included so an Angular app that uses no\n// Inversify decorator isn't short-circuited to empty.\nconst DI_MARKERS = [\n '@Controller(',\n '@ApiImplementation(',\n '@provideSingleton',\n '@provideTransient',\n '@injectable',\n 'new ContainerModule',\n '@inject(',\n '@Component(',\n 'bootstrapApplication',\n];\n\nconst ANGULAR_MARKERS = ['@Component(', 'bootstrapApplication'];\nconst CONTROLLER_MARKER = '@Controller(';\n\n/** Recursively read every project .ts file, folding each into `visit`. */\nfunction forEachSourceFile(dir: string, visit: (content: string) => void): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === 'node_modules' || entry.name === 'dist') continue;\n forEachSourceFile(full, visit);\n } else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) {\n visit(fs.readFileSync(full, 'utf-8'));\n }\n }\n}\n\nfunction sourceHasDiMarkers(dir: string): boolean {\n let found = false;\n forEachSourceFile(dir, (content: string) => {\n if (!found && DI_MARKERS.some((marker: string) => content.includes(marker))) found = true;\n });\n return found;\n}\n\n/** Pre-scan a project's source for the framework markers used when no tag is set. */\nfunction detectFrameworkMarkers(dir: string): FrameworkMarkers {\n let angular = false;\n let controller = false;\n forEachSourceFile(dir, (content: string) => {\n if (!angular && ANGULAR_MARKERS.some((marker: string) => content.includes(marker))) angular = true;\n if (!controller && content.includes(CONTROLLER_MARKER)) controller = true;\n });\n return new FrameworkMarkers(angular, controller);\n}\n\nfunction writeDesignFiles(projectRootAbs: string, graph: DiGraph): void {\n fs.writeFileSync(path.join(projectRootAbs, 'design.json'), toDesignJson(graph));\n fs.writeFileSync(path.join(projectRootAbs, 'design.md'), toDesignMarkdown(graph));\n}\n\n/** The analyzer chosen for a project plus the role tag that drove the choice. */\nclass AnalyzerChoice {\n constructor(\n public readonly analyzer: DiAnalyzer,\n public readonly role: string | null,\n ) {}\n}\n\n/**\n * Select the analyzer by role (server→@Controller, designed-lib→@ApiImplementation,\n * client→angular design, lib→skip). The explicit `role:` nx tag is the source of\n * truth; when absent we fall back to the legacy `framework:` selection + marker\n * pre-scan so designs stay identical until a project is retagged.\n */\nfunction chooseAnalyzer(tags: string[], srcDir: string): AnalyzerChoice {\n const role = explicitRoleTag(tags);\n const framework = explicitFrameworkTag(tags);\n const analyzer = selectAnalyzer(role, framework, detectFrameworkMarkers(srcDir));\n console.log(\n ` Analyzer: ${analyzer.constructor.name} ` +\n `(role tag: ${role ?? 'none'}, framework tag: ${framework ?? 'none'})`,\n );\n return new AnalyzerChoice(analyzer, role);\n}\n\nfunction reportDesignedLibMissingRoot(projectName: string): void {\n console.error(\n `❌ ${projectName} is tagged role:designed-lib but has no @ApiImplementation class.\\n` +\n ` Annotate the library's top implementation class with @ApiImplementation ` +\n `(from @webpieces/http-routing), or retag the project role:lib if it has no design.`,\n );\n}\n\nexport default async function runExecutor(\n _options: DiGraphGenerateOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get(RULE_NAME);\n if (rule && rule.isOff) {\n console.log(`\\n⏭️ Skipping ${RULE_NAME} generation (mode: OFF)\\n`);\n return { success: true };\n }\n\n const projectName = context.projectName ?? 'project';\n const projectConfig = context.projectsConfigurations?.projects[projectName];\n const projectRoot = projectConfig?.root ?? '.';\n const projectRootAbs = path.join(context.root, projectRoot);\n const srcDir = path.join(projectRootAbs, 'src');\n\n console.log(`\\n🧬 Generating DI design graph for ${projectName}\\n`);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- chokepoint: a generator crash must produce an actionable failure, not a stack trace mid-build\n try {\n if (!sourceHasDiMarkers(srcDir)) {\n console.log(' No DI markers found — writing empty design graph');\n writeDesignFiles(projectRootAbs, new DiGraph(projectName));\n return { success: true };\n }\n\n const program = createProjectProgram(projectRootAbs);\n if (!program) {\n console.log(' No usable tsconfig/source — writing empty design graph');\n writeDesignFiles(projectRootAbs, new DiGraph(projectName));\n return { success: true };\n }\n\n const choice = chooseAnalyzer(projectConfig?.tags ?? [], srcDir);\n const graph = choice.analyzer.analyzeProject(program, context.root, projectRoot, projectName);\n\n // A designed-lib MUST expose at least one @ApiImplementation root, else its\n // design would be empty and the tag is meaningless — fail loudly.\n if (choice.role === 'designed-lib' && graph.designs.length === 0) {\n reportDesignedLibMissingRoot(projectName);\n return { success: false };\n }\n\n writeDesignFiles(projectRootAbs, graph);\n\n const nodeCount = graph.designs.reduce((sum: number, d: DiDesign) => sum + d.nodes.length, 0);\n const edgeCount = graph.designs.reduce((sum: number, d: DiDesign) => sum + d.edges.length, 0);\n console.log(\n `✅ Wrote ${projectRoot}/design.json + design.md ` +\n `(${graph.designs.length} design(s), ${nodeCount} node(s), ${edgeCount} edge(s))`,\n );\n const unresolved = [...new Set(graph.designs.flatMap((d: DiDesign) => d.unresolved))];\n if (unresolved.length > 0) {\n console.warn(`⚠️ ${unresolved.length} unresolved token(s)/type(s): ${unresolved.join(', ')}`);\n }\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error(`❌ DI graph generation failed for ${projectName}: ${error.message}`);\n console.error(` To unblock builds, set rules[\"${RULE_NAME}\"].mode=\"OFF\" in webpieces.config.json.`);\n return { success: false };\n }\n}\n"]}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type * as ts from 'typescript';
|
|
18
18
|
import { DiGraph } from './model';
|
|
19
|
+
import { DiRootMode } from './analyzer';
|
|
19
20
|
/** Which framework a source tree's decorators point at (marker pre-scan fallback). */
|
|
20
21
|
export declare class FrameworkMarkers {
|
|
21
22
|
angular: boolean;
|
|
@@ -26,8 +27,14 @@ export declare class FrameworkMarkers {
|
|
|
26
27
|
export interface DiAnalyzer {
|
|
27
28
|
analyzeProject(program: ts.Program, workspaceRoot: string, projectRoot: string, projectName: string): DiGraph;
|
|
28
29
|
}
|
|
29
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Inversify analyzer: one design per root decorator.
|
|
32
|
+
* - `'controller'` (server projects) roots on `@Controller`.
|
|
33
|
+
* - `'apiImplementation'` (role:designed-lib) roots on `@ApiImplementation`.
|
|
34
|
+
*/
|
|
30
35
|
export declare class InversifyAnalyzer implements DiAnalyzer {
|
|
36
|
+
private readonly rootMode;
|
|
37
|
+
constructor(rootMode?: DiRootMode);
|
|
31
38
|
analyzeProject(program: ts.Program, workspaceRoot: string, projectRoot: string, projectName: string): DiGraph;
|
|
32
39
|
}
|
|
33
40
|
/** Angular: one design per entry component (bootstrap + routed). */
|
|
@@ -40,8 +47,18 @@ export declare class EmptyAnalyzer implements DiAnalyzer {
|
|
|
40
47
|
}
|
|
41
48
|
/** Extract the explicit `framework:<value>` nx tag, or null when the project has none. */
|
|
42
49
|
export declare function explicitFrameworkTag(tags: readonly string[]): string | null;
|
|
50
|
+
/** Extract the explicit `role:<value>` nx tag, or null when the project has none. */
|
|
51
|
+
export declare function explicitRoleTag(tags: readonly string[]): string | null;
|
|
43
52
|
/**
|
|
44
|
-
* Choose the analyzer for a project. `
|
|
45
|
-
*
|
|
53
|
+
* Choose the analyzer for a project. `role` (server|designed-lib|lib|client) is
|
|
54
|
+
* the source of truth for WHAT to root on; `framework` distinguishes the client
|
|
55
|
+
* runtime (angular vs other); `markers` corroborate only when the role tag is
|
|
56
|
+
* ABSENT (rollout fallback keeps pre-retag designs identical).
|
|
57
|
+
*
|
|
58
|
+
* - `server` → Inversify, roots on `@Controller`
|
|
59
|
+
* - `designed-lib` → Inversify, roots on `@ApiImplementation`
|
|
60
|
+
* - `client` → Angular design for angular apps; otherwise skip
|
|
61
|
+
* - `lib` → skip (plain libraries get no design)
|
|
62
|
+
* - role absent → legacy framework/marker selection
|
|
46
63
|
*/
|
|
47
|
-
export declare function selectAnalyzer(framework: string | null, markers: FrameworkMarkers): DiAnalyzer;
|
|
64
|
+
export declare function selectAnalyzer(role: string | null, framework: string | null, markers: FrameworkMarkers): DiAnalyzer;
|
|
@@ -18,11 +18,13 @@
|
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
19
|
exports.EmptyAnalyzer = exports.AngularAnalyzer = exports.InversifyAnalyzer = exports.FrameworkMarkers = void 0;
|
|
20
20
|
exports.explicitFrameworkTag = explicitFrameworkTag;
|
|
21
|
+
exports.explicitRoleTag = explicitRoleTag;
|
|
21
22
|
exports.selectAnalyzer = selectAnalyzer;
|
|
22
23
|
const model_1 = require("./model");
|
|
23
24
|
const analyzer_1 = require("./analyzer");
|
|
24
25
|
const angular_analyzer_1 = require("./angular-analyzer");
|
|
25
26
|
const FRAMEWORK_TAG_PREFIX = 'framework:';
|
|
27
|
+
const ROLE_TAG_PREFIX = 'role:';
|
|
26
28
|
/** Which framework a source tree's decorators point at (marker pre-scan fallback). */
|
|
27
29
|
class FrameworkMarkers {
|
|
28
30
|
angular;
|
|
@@ -33,10 +35,18 @@ class FrameworkMarkers {
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
exports.FrameworkMarkers = FrameworkMarkers;
|
|
36
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Inversify analyzer: one design per root decorator.
|
|
40
|
+
* - `'controller'` (server projects) roots on `@Controller`.
|
|
41
|
+
* - `'apiImplementation'` (role:designed-lib) roots on `@ApiImplementation`.
|
|
42
|
+
*/
|
|
37
43
|
class InversifyAnalyzer {
|
|
44
|
+
rootMode;
|
|
45
|
+
constructor(rootMode = 'controller') {
|
|
46
|
+
this.rootMode = rootMode;
|
|
47
|
+
}
|
|
38
48
|
analyzeProject(program, workspaceRoot, projectRoot, projectName) {
|
|
39
|
-
return (0, analyzer_1.buildDiGraph)(program, workspaceRoot, projectRoot, projectName);
|
|
49
|
+
return (0, analyzer_1.buildDiGraph)(program, workspaceRoot, projectRoot, projectName, false, this.rootMode);
|
|
40
50
|
}
|
|
41
51
|
}
|
|
42
52
|
exports.InversifyAnalyzer = InversifyAnalyzer;
|
|
@@ -54,33 +64,58 @@ class EmptyAnalyzer {
|
|
|
54
64
|
}
|
|
55
65
|
}
|
|
56
66
|
exports.EmptyAnalyzer = EmptyAnalyzer;
|
|
57
|
-
/** Extract the explicit
|
|
58
|
-
function
|
|
67
|
+
/** Extract the explicit `<prefix><value>` nx tag, or null when the project has none. */
|
|
68
|
+
function explicitTag(tags, prefix) {
|
|
59
69
|
for (const tag of tags) {
|
|
60
|
-
if (tag.startsWith(
|
|
61
|
-
const value = tag.slice(
|
|
70
|
+
if (tag.startsWith(prefix)) {
|
|
71
|
+
const value = tag.slice(prefix.length).trim();
|
|
62
72
|
if (value.length > 0)
|
|
63
73
|
return value;
|
|
64
74
|
}
|
|
65
75
|
}
|
|
66
76
|
return null;
|
|
67
77
|
}
|
|
78
|
+
/** Extract the explicit `framework:<value>` nx tag, or null when the project has none. */
|
|
79
|
+
function explicitFrameworkTag(tags) {
|
|
80
|
+
return explicitTag(tags, FRAMEWORK_TAG_PREFIX);
|
|
81
|
+
}
|
|
82
|
+
/** Extract the explicit `role:<value>` nx tag, or null when the project has none. */
|
|
83
|
+
function explicitRoleTag(tags) {
|
|
84
|
+
return explicitTag(tags, ROLE_TAG_PREFIX);
|
|
85
|
+
}
|
|
68
86
|
/**
|
|
69
|
-
* Choose the analyzer for a project. `
|
|
70
|
-
*
|
|
87
|
+
* Choose the analyzer for a project. `role` (server|designed-lib|lib|client) is
|
|
88
|
+
* the source of truth for WHAT to root on; `framework` distinguishes the client
|
|
89
|
+
* runtime (angular vs other); `markers` corroborate only when the role tag is
|
|
90
|
+
* ABSENT (rollout fallback keeps pre-retag designs identical).
|
|
91
|
+
*
|
|
92
|
+
* - `server` → Inversify, roots on `@Controller`
|
|
93
|
+
* - `designed-lib` → Inversify, roots on `@ApiImplementation`
|
|
94
|
+
* - `client` → Angular design for angular apps; otherwise skip
|
|
95
|
+
* - `lib` → skip (plain libraries get no design)
|
|
96
|
+
* - role absent → legacy framework/marker selection
|
|
71
97
|
*/
|
|
72
|
-
function selectAnalyzer(framework, markers) {
|
|
98
|
+
function selectAnalyzer(role, framework, markers) {
|
|
99
|
+
if (role === 'server')
|
|
100
|
+
return new InversifyAnalyzer('controller');
|
|
101
|
+
if (role === 'designed-lib')
|
|
102
|
+
return new InversifyAnalyzer('apiImplementation');
|
|
103
|
+
if (role === 'client')
|
|
104
|
+
return framework === 'angular' ? new AngularAnalyzer() : new EmptyAnalyzer();
|
|
105
|
+
if (role === 'lib')
|
|
106
|
+
return new EmptyAnalyzer();
|
|
107
|
+
// Role tag absent — fall back to the legacy framework/marker selection so
|
|
108
|
+
// designs stay identical until a project is retagged.
|
|
73
109
|
if (framework === 'express')
|
|
74
|
-
return new InversifyAnalyzer();
|
|
110
|
+
return new InversifyAnalyzer('controller');
|
|
75
111
|
if (framework === 'angular')
|
|
76
112
|
return new AngularAnalyzer();
|
|
77
113
|
if (framework !== null)
|
|
78
114
|
return new EmptyAnalyzer();
|
|
79
|
-
// Tag absent — fall back to marker corroboration.
|
|
80
115
|
if (markers.angular)
|
|
81
116
|
return new AngularAnalyzer();
|
|
82
117
|
if (markers.controller)
|
|
83
|
-
return new InversifyAnalyzer();
|
|
118
|
+
return new InversifyAnalyzer('controller');
|
|
84
119
|
return new EmptyAnalyzer();
|
|
85
120
|
}
|
|
86
121
|
//# sourceMappingURL=analyzer-strategy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer-strategy.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/analyzer-strategy.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;
|
|
1
|
+
{"version":3,"file":"analyzer-strategy.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/analyzer-strategy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DI Analyzer Strategy\n *\n * Picks which DI analyzer generates a project's design graph, driven by the\n * project's `framework` nx tag (see the framework/libType tagging PR):\n *\n * - `express` → {@link InversifyAnalyzer} (roots on `@Controller`)\n * - `angular` → {@link AngularAnalyzer} (roots on the bootstrap/route components)\n * - anything else (`react`, `all`, ...) → {@link EmptyAnalyzer} (skip; the\n * controller-less library top-of-DAG behavior is deferred for v1)\n *\n * The explicit tag is the source of truth. When it is ABSENT (e.g. before the\n * tagging PR lands), a cheap marker pre-scan corroborates: `@Component(` /\n * `bootstrapApplication` → angular; `@Controller(` → express. This makes the\n * committed design.* identical whether selection is tag- or marker-driven.\n */\n\nimport type * as ts from 'typescript';\nimport { DiGraph } from './model';\nimport { buildDiGraph, DiRootMode } from './analyzer';\nimport { buildAngularDiGraph } from './angular-analyzer';\n\nconst FRAMEWORK_TAG_PREFIX = 'framework:';\nconst ROLE_TAG_PREFIX = 'role:';\n\n/** Which framework a source tree's decorators point at (marker pre-scan fallback). */\nexport class FrameworkMarkers {\n angular: boolean;\n controller: boolean;\n\n constructor(angular: boolean, controller: boolean) {\n this.angular = angular;\n this.controller = controller;\n }\n}\n\n/** Statically analyzes one project's DI dependency DAG into a `DiGraph`. */\nexport interface DiAnalyzer {\n analyzeProject(program: ts.Program, workspaceRoot: string, projectRoot: string, projectName: string): DiGraph;\n}\n\n/**\n * Inversify analyzer: one design per root decorator.\n * - `'controller'` (server projects) roots on `@Controller`.\n * - `'apiImplementation'` (role:designed-lib) roots on `@ApiImplementation`.\n */\nexport class InversifyAnalyzer implements DiAnalyzer {\n private readonly rootMode: DiRootMode;\n\n constructor(rootMode: DiRootMode = 'controller') {\n this.rootMode = rootMode;\n }\n\n analyzeProject(program: ts.Program, workspaceRoot: string, projectRoot: string, projectName: string): DiGraph {\n return buildDiGraph(program, workspaceRoot, projectRoot, projectName, false, this.rootMode);\n }\n}\n\n/** Angular: one design per entry component (bootstrap + routed). */\nexport class AngularAnalyzer implements DiAnalyzer {\n analyzeProject(program: ts.Program, workspaceRoot: string, projectRoot: string, projectName: string): DiGraph {\n return buildAngularDiGraph(program, workspaceRoot, projectRoot, projectName);\n }\n}\n\n/** Non-angular/non-express projects: an empty graph (skip). */\nexport class EmptyAnalyzer implements DiAnalyzer {\n analyzeProject(\n _program: ts.Program,\n _workspaceRoot: string,\n _projectRoot: string,\n projectName: string,\n ): DiGraph {\n return new DiGraph(projectName);\n }\n}\n\n/** Extract the explicit `<prefix><value>` nx tag, or null when the project has none. */\nfunction explicitTag(tags: readonly string[], prefix: string): string | null {\n for (const tag of tags) {\n if (tag.startsWith(prefix)) {\n const value = tag.slice(prefix.length).trim();\n if (value.length > 0) return value;\n }\n }\n return null;\n}\n\n/** Extract the explicit `framework:<value>` nx tag, or null when the project has none. */\nexport function explicitFrameworkTag(tags: readonly string[]): string | null {\n return explicitTag(tags, FRAMEWORK_TAG_PREFIX);\n}\n\n/** Extract the explicit `role:<value>` nx tag, or null when the project has none. */\nexport function explicitRoleTag(tags: readonly string[]): string | null {\n return explicitTag(tags, ROLE_TAG_PREFIX);\n}\n\n/**\n * Choose the analyzer for a project. `role` (server|designed-lib|lib|client) is\n * the source of truth for WHAT to root on; `framework` distinguishes the client\n * runtime (angular vs other); `markers` corroborate only when the role tag is\n * ABSENT (rollout fallback keeps pre-retag designs identical).\n *\n * - `server` → Inversify, roots on `@Controller`\n * - `designed-lib` → Inversify, roots on `@ApiImplementation`\n * - `client` → Angular design for angular apps; otherwise skip\n * - `lib` → skip (plain libraries get no design)\n * - role absent → legacy framework/marker selection\n */\nexport function selectAnalyzer(\n role: string | null,\n framework: string | null,\n markers: FrameworkMarkers,\n): DiAnalyzer {\n if (role === 'server') return new InversifyAnalyzer('controller');\n if (role === 'designed-lib') return new InversifyAnalyzer('apiImplementation');\n if (role === 'client') return framework === 'angular' ? new AngularAnalyzer() : new EmptyAnalyzer();\n if (role === 'lib') return new EmptyAnalyzer();\n\n // Role tag absent — fall back to the legacy framework/marker selection so\n // designs stay identical until a project is retagged.\n if (framework === 'express') return new InversifyAnalyzer('controller');\n if (framework === 'angular') return new AngularAnalyzer();\n if (framework !== null) return new EmptyAnalyzer();\n if (markers.angular) return new AngularAnalyzer();\n if (markers.controller) return new InversifyAnalyzer('controller');\n return new EmptyAnalyzer();\n}\n"]}
|
|
@@ -25,6 +25,11 @@ export declare class ParamInjection {
|
|
|
25
25
|
}
|
|
26
26
|
export declare function readParamDecorators(param: ts.ParameterDeclaration): ParamInjection;
|
|
27
27
|
export declare function isControllerClass(cls: ts.ClassDeclaration): boolean;
|
|
28
|
+
/** A `@ApiImplementation` class — the explicit DI-design root for a `role:designed-lib` project. */
|
|
29
|
+
export declare function isApiImplementationClass(cls: ts.ClassDeclaration): boolean;
|
|
30
|
+
/** Node kind for a class: `@Controller` → controller, `@ApiImplementation` → apiImplementation, else class. */
|
|
31
|
+
export declare function rootKindOfClass(cls: ts.ClassDeclaration): DiNodeKind;
|
|
32
|
+
export type DiRootMode = 'controller' | 'apiImplementation';
|
|
28
33
|
export declare function findConstructor(cls: ts.ClassDeclaration): ts.ConstructorDeclaration | null;
|
|
29
34
|
/** All class declarations in files under the project root. */
|
|
30
35
|
export declare function projectClasses(program: ts.Program, workspaceRoot: string, projectRoot: string): ts.ClassDeclaration[];
|
|
@@ -78,7 +83,7 @@ export declare abstract class DiDesignBuilder {
|
|
|
78
83
|
constructor(checker: ts.TypeChecker, table: BindingTable, workspaceRoot: string, design: DiDesign);
|
|
79
84
|
/** Collect the injection sites for one class (constructor params, field inject(), ...). */
|
|
80
85
|
protected abstract collectInjections(cls: ts.ClassDeclaration): Injection[];
|
|
81
|
-
/** Node kind for a root/reached class — `controller` (Inversify) or `component` (Angular). */
|
|
86
|
+
/** Node kind for a root/reached class — `controller`/`apiImplementation` (Inversify) or `component` (Angular). */
|
|
82
87
|
protected rootKindOf(cls: ts.ClassDeclaration): DiNodeKind;
|
|
83
88
|
addRoot(cls: ts.ClassDeclaration): void;
|
|
84
89
|
/**
|
|
@@ -147,8 +152,8 @@ export declare function buildDesign(root: ts.ClassDeclaration, rootKind: DiNodeK
|
|
|
147
152
|
* Build the full Inversify DI graph for one project: one self-contained
|
|
148
153
|
* `DiDesign` per @Controller root. `projectRoot` is workspace-relative.
|
|
149
154
|
*
|
|
150
|
-
* `includeLibraryRoots` (default false
|
|
151
|
-
*
|
|
152
|
-
*
|
|
155
|
+
* `includeLibraryRoots` (default false) roots ONLY on @Controller classes; when
|
|
156
|
+
* true, a controller-less project falls back to top-of-DAG DI classes. `rootMode`
|
|
157
|
+
* `'apiImplementation'` (role:designed-lib) instead roots on `@ApiImplementation`.
|
|
153
158
|
*/
|
|
154
|
-
export declare function buildDiGraph(program: ts.Program, workspaceRoot: string, projectRoot: string, projectName: string, includeLibraryRoots?: boolean): DiGraph;
|
|
159
|
+
export declare function buildDiGraph(program: ts.Program, workspaceRoot: string, projectRoot: string, projectName: string, includeLibraryRoots?: boolean, rootMode?: DiRootMode): DiGraph;
|
|
@@ -18,6 +18,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
18
18
|
exports.InversifyDesignBuilder = exports.DiDesignBuilder = exports.Injection = exports.ParamInjection = void 0;
|
|
19
19
|
exports.readParamDecorators = readParamDecorators;
|
|
20
20
|
exports.isControllerClass = isControllerClass;
|
|
21
|
+
exports.isApiImplementationClass = isApiImplementationClass;
|
|
22
|
+
exports.rootKindOfClass = rootKindOfClass;
|
|
21
23
|
exports.findConstructor = findConstructor;
|
|
22
24
|
exports.projectClasses = projectClasses;
|
|
23
25
|
exports.byClassName = byClassName;
|
|
@@ -79,6 +81,18 @@ function hasDecoratorNamed(cls, names) {
|
|
|
79
81
|
function isControllerClass(cls) {
|
|
80
82
|
return hasDecoratorNamed(cls, new Set(['Controller']));
|
|
81
83
|
}
|
|
84
|
+
/** A `@ApiImplementation` class — the explicit DI-design root for a `role:designed-lib` project. */
|
|
85
|
+
function isApiImplementationClass(cls) {
|
|
86
|
+
return hasDecoratorNamed(cls, new Set(['ApiImplementation']));
|
|
87
|
+
}
|
|
88
|
+
/** Node kind for a class: `@Controller` → controller, `@ApiImplementation` → apiImplementation, else class. */
|
|
89
|
+
function rootKindOfClass(cls) {
|
|
90
|
+
if (isControllerClass(cls))
|
|
91
|
+
return 'controller';
|
|
92
|
+
if (isApiImplementationClass(cls))
|
|
93
|
+
return 'apiImplementation';
|
|
94
|
+
return 'class';
|
|
95
|
+
}
|
|
82
96
|
function isDiRegisteredClass(cls) {
|
|
83
97
|
return hasDecoratorNamed(cls, DI_DECORATORS);
|
|
84
98
|
}
|
|
@@ -171,9 +185,9 @@ class DiDesignBuilder {
|
|
|
171
185
|
this.workspaceRoot = workspaceRoot;
|
|
172
186
|
this.design = design;
|
|
173
187
|
}
|
|
174
|
-
/** Node kind for a root/reached class — `controller` (Inversify) or `component` (Angular). */
|
|
188
|
+
/** Node kind for a root/reached class — `controller`/`apiImplementation` (Inversify) or `component` (Angular). */
|
|
175
189
|
rootKindOf(cls) {
|
|
176
|
-
return
|
|
190
|
+
return rootKindOfClass(cls);
|
|
177
191
|
}
|
|
178
192
|
addRoot(cls) {
|
|
179
193
|
const id = this.classNode(cls);
|
|
@@ -512,23 +526,25 @@ function buildDesign(root, rootKind, workspaceRoot, makeBuilder) {
|
|
|
512
526
|
* Build the full Inversify DI graph for one project: one self-contained
|
|
513
527
|
* `DiDesign` per @Controller root. `projectRoot` is workspace-relative.
|
|
514
528
|
*
|
|
515
|
-
* `includeLibraryRoots` (default false
|
|
516
|
-
*
|
|
517
|
-
*
|
|
529
|
+
* `includeLibraryRoots` (default false) roots ONLY on @Controller classes; when
|
|
530
|
+
* true, a controller-less project falls back to top-of-DAG DI classes. `rootMode`
|
|
531
|
+
* `'apiImplementation'` (role:designed-lib) instead roots on `@ApiImplementation`.
|
|
518
532
|
*/
|
|
519
|
-
function buildDiGraph(program, workspaceRoot, projectRoot, projectName, includeLibraryRoots = false) {
|
|
533
|
+
function buildDiGraph(program, workspaceRoot, projectRoot, projectName, includeLibraryRoots = false, rootMode = 'controller') {
|
|
520
534
|
const checker = program.getTypeChecker();
|
|
521
535
|
const table = (0, bindings_1.collectBindings)(program, checker, workspaceRoot);
|
|
522
536
|
const graph = new model_1.DiGraph(projectName);
|
|
523
537
|
const classes = projectClasses(program, workspaceRoot, projectRoot);
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
538
|
+
const roots = rootMode === 'apiImplementation'
|
|
539
|
+
? classes.filter((cls) => isApiImplementationClass(cls))
|
|
540
|
+
: (() => {
|
|
541
|
+
const controllers = classes.filter((cls) => isControllerClass(cls));
|
|
542
|
+
if (controllers.length > 0)
|
|
543
|
+
return controllers;
|
|
544
|
+
return includeLibraryRoots ? findLibraryRoots(classes, checker, table, workspaceRoot) : [];
|
|
545
|
+
})();
|
|
530
546
|
for (const root of [...roots].sort(byClassName)) {
|
|
531
|
-
const rootKind =
|
|
547
|
+
const rootKind = rootKindOfClass(root);
|
|
532
548
|
graph.designs.push(buildDesign(root, rootKind, workspaceRoot, (design) => new InversifyDesignBuilder(checker, table, workspaceRoot, design)));
|
|
533
549
|
}
|
|
534
550
|
return graph;
|