@webpieces/nx-webpieces-rules 0.3.263 → 0.3.271

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/nx-webpieces-rules",
3
- "version": "0.3.263",
3
+ "version": "0.3.271",
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.263",
25
- "@webpieces/code-rules": "0.3.263",
26
- "@webpieces/eslint-rules": "0.3.263",
27
- "@webpieces/pr-gate": "0.3.263",
28
- "@webpieces/rules-config": "0.3.263",
24
+ "@webpieces/ai-hook-rules": "0.3.271",
25
+ "@webpieces/code-rules": "0.3.271",
26
+ "@webpieces/eslint-rules": "0.3.271",
27
+ "@webpieces/pr-gate": "0.3.271",
28
+ "@webpieces/rules-config": "0.3.271",
29
29
  "madge": "8.0.0"
30
30
  },
31
31
  "peerDependencies": {
@@ -32,13 +32,13 @@ const design_visualizer_1 = require("../../lib/di-graph/design-visualizer");
32
32
  const model_1 = require("../../lib/di-graph/model");
33
33
  const toError_1 = require("../../toError");
34
34
  const RULE_NAME = 'di-graph';
35
+ const MISSING_DESIGN_RULE_NAME = 'missing-design-annotation';
35
36
  // Cheap substring pre-scan: a project whose source never mentions any DI marker
36
37
  // gets an empty graph without paying for a ts.Program. Angular markers
37
38
  // (@Component/bootstrapApplication) are included so an Angular app that uses no
38
39
  // Inversify decorator isn't short-circuited to empty.
39
40
  const DI_MARKERS = [
40
- '@Controller(',
41
- '@ApiImplementation(',
41
+ '@DocumentDesign(',
42
42
  '@provideSingleton',
43
43
  '@provideTransient',
44
44
  '@injectable',
@@ -48,7 +48,9 @@ const DI_MARKERS = [
48
48
  'bootstrapApplication',
49
49
  ];
50
50
  const ANGULAR_MARKERS = ['@Component(', 'bootstrapApplication'];
51
- const CONTROLLER_MARKER = '@Controller(';
51
+ // A non-Angular DI-design root. When no role tag is set, its presence steers the
52
+ // marker-fallback toward the Inversify analyzer (server/controller mode).
53
+ const DESIGN_ROOT_MARKER = '@DocumentDesign(';
52
54
  /** Recursively read every project .ts file, folding each into `visit`. */
53
55
  function forEachSourceFile(dir, visit) {
54
56
  if (!fs.existsSync(dir))
@@ -80,7 +82,7 @@ function detectFrameworkMarkers(dir) {
80
82
  forEachSourceFile(dir, (content) => {
81
83
  if (!angular && ANGULAR_MARKERS.some((marker) => content.includes(marker)))
82
84
  angular = true;
83
- if (!controller && content.includes(CONTROLLER_MARKER))
85
+ if (!controller && content.includes(DESIGN_ROOT_MARKER))
84
86
  controller = true;
85
87
  });
86
88
  return new analyzer_strategy_1.FrameworkMarkers(angular, controller);
@@ -111,7 +113,7 @@ class AnalyzerChoice {
111
113
  }
112
114
  }
113
115
  /**
114
- * Select the analyzer by role (server→@Controller, designed-lib→@ApiImplementation,
116
+ * Select the analyzer by role (server & designed-lib → @DocumentDesign,
115
117
  * client→angular design, lib→skip). The explicit `role:` nx tag is the source of
116
118
  * truth; when absent we fall back to the legacy `framework:` selection + marker
117
119
  * pre-scan so designs stay identical until a project is retagged.
@@ -124,10 +126,38 @@ function chooseAnalyzer(tags, srcDir) {
124
126
  `(role tag: ${role ?? 'none'}, framework tags: ${frameworks.length > 0 ? frameworks.join(', ') : 'none'})`);
125
127
  return new AnalyzerChoice(analyzer, role);
126
128
  }
127
- function reportDesignedLibMissingRoot(projectName) {
128
- console.error(`❌ ${projectName} is tagged role:designed-lib but has no @ApiImplementation class.\n` +
129
- ` Annotate the library's top implementation class with @ApiImplementation ` +
130
- `(from @webpieces/http-routing), or retag the project role:lib if it has no design.`);
129
+ /**
130
+ * A server/designed-lib project that produced no design (zero @DocumentDesign
131
+ * roots) fails the build with role-specific guidance. Enforced under the
132
+ * `missing-design-annotation` rule.
133
+ */
134
+ function reportMissingDesignAnnotation(projectName, role) {
135
+ if (role === 'server') {
136
+ console.error(`❌ ${projectName} is tagged role:server but has no @DocumentDesign class.\n` +
137
+ ` All controllers should be annotated with @DocumentDesign() ` +
138
+ `(from @webpieces/http-routing) so their design.json / design.html get generated\n` +
139
+ ` and linked from architecture/dependencies.html.`);
140
+ return;
141
+ }
142
+ console.error(`❌ ${projectName} is tagged role:designed-lib but has no @DocumentDesign class.\n` +
143
+ ` One or more classes you want a design printed for need @DocumentDesign() ` +
144
+ `(from @webpieces/http-routing) added —\n` +
145
+ ` or retag the project role:lib if it has no design.`);
146
+ }
147
+ /**
148
+ * A server/designed-lib project MUST expose at least one @DocumentDesign root,
149
+ * else its design is empty and the role is meaningless. Returns true (and reports)
150
+ * when the build should fail. The `missing-design-annotation` rule gates it:
151
+ * absent (an older published config) → enforce; OFF → skip.
152
+ */
153
+ function failsMissingDesignAnnotation(shared, role, graph, projectName) {
154
+ const missingRule = shared.rules.get(MISSING_DESIGN_RULE_NAME);
155
+ const enforce = !missingRule || !missingRule.isOff;
156
+ if (enforce && graph.designs.length === 0 && (role === 'server' || role === 'designed-lib')) {
157
+ reportMissingDesignAnnotation(projectName, role);
158
+ return true;
159
+ }
160
+ return false;
131
161
  }
132
162
  async function runExecutor(_options, context) {
133
163
  const shared = (0, rules_config_1.loadAndValidate)(context.root).resolved;
@@ -157,10 +187,7 @@ async function runExecutor(_options, context) {
157
187
  }
158
188
  const choice = chooseAnalyzer(projectConfig?.tags ?? [], srcDir);
159
189
  const graph = choice.analyzer.analyzeProject(program, context.root, projectRoot, projectName);
160
- // A designed-lib MUST expose at least one @ApiImplementation root, else its
161
- // design would be empty and the tag is meaningless — fail loudly.
162
- if (choice.role === 'designed-lib' && graph.designs.length === 0) {
163
- reportDesignedLibMissingRoot(projectName);
190
+ if (failsMissingDesignAnnotation(shared, choice.role, graph, projectName)) {
164
191
  return { success: false };
165
192
  }
166
193
  writeDesignFiles(projectRootAbs, projectRoot, graph);
@@ -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 three 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 * design.html — clickable viz.js page (linked from architecture/dependencies.html)\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 frameworkTags,\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 { generateDesignHTML } from '../../lib/di-graph/design-visualizer';\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\n/**\n * Repo-relative back link from a project's committed design.html up to\n * architecture/dependencies.html, so a reader who clicked a box in the\n * architecture graph can click back out. E.g. 'packages/http/http-api' →\n * '../../../architecture/dependencies.html'.\n */\nfunction architectureBackHref(projectRoot: string): string {\n return path.posix.relative(projectRoot.replace(/\\\\/g, '/'), 'architecture/dependencies.html');\n}\n\nfunction writeDesignFiles(projectRootAbs: string, projectRoot: string, graph: DiGraph): void {\n // toDesignJson sorts the graph in place, so design.md/design.html below all\n // see the same deterministic ordering (no git churn on re-run).\n fs.writeFileSync(path.join(projectRootAbs, 'design.json'), toDesignJson(graph));\n fs.writeFileSync(path.join(projectRootAbs, 'design.md'), toDesignMarkdown(graph));\n fs.writeFileSync(\n path.join(projectRootAbs, 'design.html'),\n generateDesignHTML(graph, architectureBackHref(projectRoot)),\n );\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 frameworks = frameworkTags(tags);\n const analyzer = selectAnalyzer(role, frameworks, detectFrameworkMarkers(srcDir));\n console.log(\n ` Analyzer: ${analyzer.constructor.name} ` +\n `(role tag: ${role ?? 'none'}, framework tags: ${frameworks.length > 0 ? frameworks.join(', ') : '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, projectRoot, 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, projectRoot, 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, projectRoot, 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 + design.html ` +\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 three 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 * design.html — clickable viz.js page (linked from architecture/dependencies.html)\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, ResolvedConfig } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n DiAnalyzer,\n frameworkTags,\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 { generateDesignHTML } from '../../lib/di-graph/design-visualizer';\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';\nconst MISSING_DESIGN_RULE_NAME = 'missing-design-annotation';\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 '@DocumentDesign(',\n '@provideSingleton',\n '@provideTransient',\n '@injectable',\n 'new ContainerModule',\n '@inject(',\n '@Component(',\n 'bootstrapApplication',\n];\n\nconst ANGULAR_MARKERS = ['@Component(', 'bootstrapApplication'];\n// A non-Angular DI-design root. When no role tag is set, its presence steers the\n// marker-fallback toward the Inversify analyzer (server/controller mode).\nconst DESIGN_ROOT_MARKER = '@DocumentDesign(';\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(DESIGN_ROOT_MARKER)) controller = true;\n });\n return new FrameworkMarkers(angular, controller);\n}\n\n/**\n * Repo-relative back link from a project's committed design.html up to\n * architecture/dependencies.html, so a reader who clicked a box in the\n * architecture graph can click back out. E.g. 'packages/http/http-api' →\n * '../../../architecture/dependencies.html'.\n */\nfunction architectureBackHref(projectRoot: string): string {\n return path.posix.relative(projectRoot.replace(/\\\\/g, '/'), 'architecture/dependencies.html');\n}\n\nfunction writeDesignFiles(projectRootAbs: string, projectRoot: string, graph: DiGraph): void {\n // toDesignJson sorts the graph in place, so design.md/design.html below all\n // see the same deterministic ordering (no git churn on re-run).\n fs.writeFileSync(path.join(projectRootAbs, 'design.json'), toDesignJson(graph));\n fs.writeFileSync(path.join(projectRootAbs, 'design.md'), toDesignMarkdown(graph));\n fs.writeFileSync(\n path.join(projectRootAbs, 'design.html'),\n generateDesignHTML(graph, architectureBackHref(projectRoot)),\n );\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 & designed-lib → @DocumentDesign,\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 frameworks = frameworkTags(tags);\n const analyzer = selectAnalyzer(role, frameworks, detectFrameworkMarkers(srcDir));\n console.log(\n ` Analyzer: ${analyzer.constructor.name} ` +\n `(role tag: ${role ?? 'none'}, framework tags: ${frameworks.length > 0 ? frameworks.join(', ') : 'none'})`,\n );\n return new AnalyzerChoice(analyzer, role);\n}\n\n/**\n * A server/designed-lib project that produced no design (zero @DocumentDesign\n * roots) fails the build with role-specific guidance. Enforced under the\n * `missing-design-annotation` rule.\n */\nfunction reportMissingDesignAnnotation(projectName: string, role: string): void {\n if (role === 'server') {\n console.error(\n `❌ ${projectName} is tagged role:server but has no @DocumentDesign class.\\n` +\n ` All controllers should be annotated with @DocumentDesign() ` +\n `(from @webpieces/http-routing) so their design.json / design.html get generated\\n` +\n ` and linked from architecture/dependencies.html.`,\n );\n return;\n }\n console.error(\n `❌ ${projectName} is tagged role:designed-lib but has no @DocumentDesign class.\\n` +\n ` One or more classes you want a design printed for need @DocumentDesign() ` +\n `(from @webpieces/http-routing) added —\\n` +\n ` or retag the project role:lib if it has no design.`,\n );\n}\n\n/**\n * A server/designed-lib project MUST expose at least one @DocumentDesign root,\n * else its design is empty and the role is meaningless. Returns true (and reports)\n * when the build should fail. The `missing-design-annotation` rule gates it:\n * absent (an older published config) → enforce; OFF → skip.\n */\nfunction failsMissingDesignAnnotation(\n shared: ResolvedConfig,\n role: string | null,\n graph: DiGraph,\n projectName: string,\n): boolean {\n const missingRule = shared.rules.get(MISSING_DESIGN_RULE_NAME);\n const enforce = !missingRule || !missingRule.isOff;\n if (enforce && graph.designs.length === 0 && (role === 'server' || role === 'designed-lib')) {\n reportMissingDesignAnnotation(projectName, role);\n return true;\n }\n return false;\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, projectRoot, 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, projectRoot, 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 if (failsMissingDesignAnnotation(shared, choice.role, graph, projectName)) {\n return { success: false };\n }\n\n writeDesignFiles(projectRootAbs, projectRoot, 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 + design.html ` +\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"]}
@@ -5,13 +5,13 @@
5
5
  * driver is the project's `role` nx tag; the `framework` env set (its libType)
6
6
  * only distinguishes the client runtime by set membership:
7
7
  *
8
- * - env set includes `express` → {@link InversifyAnalyzer} (roots on `@Controller`)
8
+ * - env set includes `express` → {@link InversifyAnalyzer} (roots on `@DocumentDesign`)
9
9
  * - env set includes `angular` → {@link AngularAnalyzer} (roots on the bootstrap/route components)
10
10
  * - anything else (`react`, `browser`, `node`, ...) → {@link EmptyAnalyzer} (skip)
11
11
  *
12
12
  * The explicit tags are the source of truth. When the role tag is ABSENT (e.g.
13
13
  * before retag), a cheap marker pre-scan corroborates: `@Component(` /
14
- * `bootstrapApplication` → angular; `@Controller(` → express. This makes the
14
+ * `bootstrapApplication` → angular; `@DocumentDesign(` → Inversify. This makes the
15
15
  * committed design.* identical whether selection is tag- or marker-driven.
16
16
  */
17
17
  import type * as ts from 'typescript';
@@ -28,9 +28,10 @@ export interface DiAnalyzer {
28
28
  analyzeProject(program: ts.Program, workspaceRoot: string, projectRoot: string, projectName: string): DiGraph;
29
29
  }
30
30
  /**
31
- * Inversify analyzer: one design per root decorator.
32
- * - `'controller'` (server projects) roots on `@Controller`.
33
- * - `'apiImplementation'` (role:designed-lib) roots on `@ApiImplementation`.
31
+ * Inversify analyzer: one design per `@DocumentDesign` root. The `rootMode` only
32
+ * sets the root box kind:
33
+ * - `'controller'` (server projects) `controller`.
34
+ * - `'apiImplementation'` (role:designed-lib) → `apiImplementation`.
34
35
  */
35
36
  export declare class InversifyAnalyzer implements DiAnalyzer {
36
37
  private readonly rootMode;
@@ -58,8 +59,8 @@ export declare function explicitRoleTag(tags: readonly string[]): string | null;
58
59
  * membership; `markers` corroborate only when the role tag is ABSENT (rollout
59
60
  * fallback keeps pre-retag designs identical).
60
61
  *
61
- * - `server` → Inversify, roots on `@Controller`
62
- * - `designed-lib` → Inversify, roots on `@ApiImplementation`
62
+ * - `server` → Inversify, roots on `@DocumentDesign` (controller kind)
63
+ * - `designed-lib` → Inversify, roots on `@DocumentDesign` (apiImplementation kind)
63
64
  * - `client` → Angular design for angular apps; otherwise skip
64
65
  * - `lib` → skip (plain libraries get no design)
65
66
  * - role absent → legacy framework/marker selection
@@ -6,13 +6,13 @@
6
6
  * driver is the project's `role` nx tag; the `framework` env set (its libType)
7
7
  * only distinguishes the client runtime by set membership:
8
8
  *
9
- * - env set includes `express` → {@link InversifyAnalyzer} (roots on `@Controller`)
9
+ * - env set includes `express` → {@link InversifyAnalyzer} (roots on `@DocumentDesign`)
10
10
  * - env set includes `angular` → {@link AngularAnalyzer} (roots on the bootstrap/route components)
11
11
  * - anything else (`react`, `browser`, `node`, ...) → {@link EmptyAnalyzer} (skip)
12
12
  *
13
13
  * The explicit tags are the source of truth. When the role tag is ABSENT (e.g.
14
14
  * before retag), a cheap marker pre-scan corroborates: `@Component(` /
15
- * `bootstrapApplication` → angular; `@Controller(` → express. This makes the
15
+ * `bootstrapApplication` → angular; `@DocumentDesign(` → Inversify. This makes the
16
16
  * committed design.* identical whether selection is tag- or marker-driven.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -37,9 +37,10 @@ class FrameworkMarkers {
37
37
  }
38
38
  exports.FrameworkMarkers = FrameworkMarkers;
39
39
  /**
40
- * Inversify analyzer: one design per root decorator.
41
- * - `'controller'` (server projects) roots on `@Controller`.
42
- * - `'apiImplementation'` (role:designed-lib) roots on `@ApiImplementation`.
40
+ * Inversify analyzer: one design per `@DocumentDesign` root. The `rootMode` only
41
+ * sets the root box kind:
42
+ * - `'controller'` (server projects) `controller`.
43
+ * - `'apiImplementation'` (role:designed-lib) → `apiImplementation`.
43
44
  */
44
45
  class InversifyAnalyzer {
45
46
  rootMode;
@@ -103,8 +104,8 @@ function explicitRoleTag(tags) {
103
104
  * membership; `markers` corroborate only when the role tag is ABSENT (rollout
104
105
  * fallback keeps pre-retag designs identical).
105
106
  *
106
- * - `server` → Inversify, roots on `@Controller`
107
- * - `designed-lib` → Inversify, roots on `@ApiImplementation`
107
+ * - `server` → Inversify, roots on `@DocumentDesign` (controller kind)
108
+ * - `designed-lib` → Inversify, roots on `@DocumentDesign` (apiImplementation kind)
108
109
  * - `client` → Angular design for angular apps; otherwise skip
109
110
  * - `lib` → skip (plain libraries get no design)
110
111
  * - role absent → legacy framework/marker selection
@@ -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":"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","sourcesContent":["/**\n * DI Analyzer Strategy\n *\n * Picks which DI analyzer generates a project's design graph. The primary\n * driver is the project's `role` nx tag; the `framework` env set (its libType)\n * only distinguishes the client runtime by set membership:\n *\n * - env set includes `express` → {@link InversifyAnalyzer} (roots on `@Controller`)\n * - env set includes `angular` → {@link AngularAnalyzer} (roots on the bootstrap/route components)\n * - anything else (`react`, `browser`, `node`, ...) → {@link EmptyAnalyzer} (skip)\n *\n * The explicit tags are the source of truth. When the role tag is ABSENT (e.g.\n * before retag), 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 FIRST 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 every explicit `framework:<value>` nx tag as an env set (the project's libType). */\nexport function frameworkTags(tags: readonly string[]): string[] {\n const values: string[] = [];\n for (const tag of tags) {\n if (tag.startsWith(FRAMEWORK_TAG_PREFIX)) {\n const value = tag.slice(FRAMEWORK_TAG_PREFIX.length).trim();\n if (value.length > 0) values.push(value);\n }\n }\n return values;\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; `frameworks` is the project's env set\n * (its libType) and distinguishes the client runtime (angular vs other) by set\n * membership; `markers` corroborate only when the role tag is ABSENT (rollout\n * 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 frameworks: string[],\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 frameworks.includes('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 (frameworks.includes('express')) return new InversifyAnalyzer('controller');\n if (frameworks.includes('angular')) return new AngularAnalyzer();\n if (frameworks.length > 0) return new EmptyAnalyzer();\n if (markers.angular) return new AngularAnalyzer();\n if (markers.controller) return new InversifyAnalyzer('controller');\n return new EmptyAnalyzer();\n}\n"]}
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. The primary\n * driver is the project's `role` nx tag; the `framework` env set (its libType)\n * only distinguishes the client runtime by set membership:\n *\n * - env set includes `express` → {@link InversifyAnalyzer} (roots on `@DocumentDesign`)\n * - env set includes `angular` → {@link AngularAnalyzer} (roots on the bootstrap/route components)\n * - anything else (`react`, `browser`, `node`, ...) → {@link EmptyAnalyzer} (skip)\n *\n * The explicit tags are the source of truth. When the role tag is ABSENT (e.g.\n * before retag), a cheap marker pre-scan corroborates: `@Component(` /\n * `bootstrapApplication` → angular; `@DocumentDesign(` → Inversify. 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 `@DocumentDesign` root. The `rootMode` only\n * sets the root box kind:\n * - `'controller'` (server projects) `controller`.\n * - `'apiImplementation'` (role:designed-lib) `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 FIRST 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 every explicit `framework:<value>` nx tag as an env set (the project's libType). */\nexport function frameworkTags(tags: readonly string[]): string[] {\n const values: string[] = [];\n for (const tag of tags) {\n if (tag.startsWith(FRAMEWORK_TAG_PREFIX)) {\n const value = tag.slice(FRAMEWORK_TAG_PREFIX.length).trim();\n if (value.length > 0) values.push(value);\n }\n }\n return values;\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; `frameworks` is the project's env set\n * (its libType) and distinguishes the client runtime (angular vs other) by set\n * membership; `markers` corroborate only when the role tag is ABSENT (rollout\n * fallback keeps pre-retag designs identical).\n *\n * - `server` → Inversify, roots on `@DocumentDesign` (controller kind)\n * - `designed-lib` → Inversify, roots on `@DocumentDesign` (apiImplementation kind)\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 frameworks: string[],\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 frameworks.includes('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 (frameworks.includes('express')) return new InversifyAnalyzer('controller');\n if (frameworks.includes('angular')) return new AngularAnalyzer();\n if (frameworks.length > 0) return new EmptyAnalyzer();\n if (markers.angular) return new AngularAnalyzer();\n if (markers.controller) return new InversifyAnalyzer('controller');\n return new EmptyAnalyzer();\n}\n"]}
@@ -9,7 +9,7 @@
9
9
  * - toConstantValue/toDynamicValue bindings → leaf nodes, no recursion
10
10
  * - unresolvable tokens/types → kind "unresolved" nodes (generation never fails)
11
11
  *
12
- * Roots: @Controller() classes when the project has any; otherwise every
12
+ * Roots: @DocumentDesign() classes when the project has any; otherwise every
13
13
  * DI-registered class in the project that no other project class injects
14
14
  * (the tops of the local DAG).
15
15
  */
@@ -24,12 +24,15 @@ export declare class ParamInjection {
24
24
  constructor(expr: ts.Expression | null, multi: boolean, optional: boolean, unmanaged: boolean);
25
25
  }
26
26
  export declare function readParamDecorators(param: ts.ParameterDeclaration): ParamInjection;
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;
27
+ /** A `@DocumentDesign` class — the explicit DI-design root (server controller or designed-lib impl). */
28
+ export declare function isDocumentDesignClass(cls: ts.ClassDeclaration): boolean;
29
+ /**
30
+ * The node kind for a `@DocumentDesign` root, chosen by the analyzer's root mode
31
+ * rather than the decorator (a single `@DocumentDesign` marks both). `server` →
32
+ * `controller`, `designed-lib` → `apiImplementation`.
33
+ */
32
34
  export type DiRootMode = 'controller' | 'apiImplementation';
35
+ export declare function rootKindForMode(mode: DiRootMode): DiNodeKind;
33
36
  export declare function findConstructor(cls: ts.ClassDeclaration): ts.ConstructorDeclaration | null;
34
37
  /** All class declarations in files under the project root. */
35
38
  export declare function projectClasses(program: ts.Program, workspaceRoot: string, projectRoot: string): ts.ClassDeclaration[];
@@ -80,10 +83,16 @@ export declare abstract class DiDesignBuilder {
80
83
  private readonly unresolvedIds;
81
84
  private readonly usedIds;
82
85
  private readonly visited;
86
+ protected rootClass: ts.ClassDeclaration | null;
83
87
  constructor(checker: ts.TypeChecker, table: BindingTable, workspaceRoot: string, design: DiDesign);
84
88
  /** Collect the injection sites for one class (constructor params, field inject(), ...). */
85
89
  protected abstract collectInjections(cls: ts.ClassDeclaration): Injection[];
86
- /** Node kind for a root/reached class — `controller`/`apiImplementation` (Inversify) or `component` (Angular). */
90
+ /**
91
+ * Node kind for a root/reached class. Default: the ROOT box takes the design's
92
+ * `rootKind` (`controller`/`apiImplementation`, chosen by root mode); every
93
+ * reached dependency is a plain `class`. Angular overrides this to render
94
+ * component classes as `component`.
95
+ */
87
96
  protected rootKindOf(cls: ts.ClassDeclaration): DiNodeKind;
88
97
  addRoot(cls: ts.ClassDeclaration): void;
89
98
  /**
@@ -150,10 +159,11 @@ export declare function assignLevels(design: DiDesign): void;
150
159
  export declare function buildDesign(root: ts.ClassDeclaration, rootKind: DiNodeKind, workspaceRoot: string, makeBuilder: (design: DiDesign) => DiDesignBuilder): DiDesign;
151
160
  /**
152
161
  * Build the full Inversify DI graph for one project: one self-contained
153
- * `DiDesign` per @Controller root. `projectRoot` is workspace-relative.
162
+ * `DiDesign` per @DocumentDesign root. `projectRoot` is workspace-relative.
154
163
  *
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`.
164
+ * Both `rootMode`s root on @DocumentDesign classes; the mode only sets the root
165
+ * box kind (`'controller'` for server, `'apiImplementation'` for designed-lib).
166
+ * `includeLibraryRoots` (default false) lets a project with NO @DocumentDesign
167
+ * class fall back to top-of-DAG DI classes (rendered as plain `class` roots).
158
168
  */
159
169
  export declare function buildDiGraph(program: ts.Program, workspaceRoot: string, projectRoot: string, projectName: string, includeLibraryRoots?: boolean, rootMode?: DiRootMode): DiGraph;
@@ -10,16 +10,15 @@
10
10
  * - toConstantValue/toDynamicValue bindings → leaf nodes, no recursion
11
11
  * - unresolvable tokens/types → kind "unresolved" nodes (generation never fails)
12
12
  *
13
- * Roots: @Controller() classes when the project has any; otherwise every
13
+ * Roots: @DocumentDesign() classes when the project has any; otherwise every
14
14
  * DI-registered class in the project that no other project class injects
15
15
  * (the tops of the local DAG).
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.InversifyDesignBuilder = exports.DiDesignBuilder = exports.Injection = exports.ParamInjection = void 0;
19
19
  exports.readParamDecorators = readParamDecorators;
20
- exports.isControllerClass = isControllerClass;
21
- exports.isApiImplementationClass = isApiImplementationClass;
22
- exports.rootKindOfClass = rootKindOfClass;
20
+ exports.isDocumentDesignClass = isDocumentDesignClass;
21
+ exports.rootKindForMode = rootKindForMode;
23
22
  exports.findConstructor = findConstructor;
24
23
  exports.projectClasses = projectClasses;
25
24
  exports.byClassName = byClassName;
@@ -78,20 +77,12 @@ function hasDecoratorNamed(cls, names) {
78
77
  }
79
78
  return false;
80
79
  }
81
- function isControllerClass(cls) {
82
- return hasDecoratorNamed(cls, new Set(['Controller']));
80
+ /** A `@DocumentDesign` class — the explicit DI-design root (server controller or designed-lib impl). */
81
+ function isDocumentDesignClass(cls) {
82
+ return hasDecoratorNamed(cls, new Set(['DocumentDesign']));
83
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';
84
+ function rootKindForMode(mode) {
85
+ return mode === 'apiImplementation' ? 'apiImplementation' : 'controller';
95
86
  }
96
87
  function isDiRegisteredClass(cls) {
97
88
  return hasDecoratorNamed(cls, DI_DECORATORS);
@@ -179,17 +170,24 @@ class DiDesignBuilder {
179
170
  unresolvedIds = new Map();
180
171
  usedIds = new Set();
181
172
  visited = new Set();
173
+ rootClass = null;
182
174
  constructor(checker, table, workspaceRoot, design) {
183
175
  this.checker = checker;
184
176
  this.table = table;
185
177
  this.workspaceRoot = workspaceRoot;
186
178
  this.design = design;
187
179
  }
188
- /** Node kind for a root/reached class — `controller`/`apiImplementation` (Inversify) or `component` (Angular). */
180
+ /**
181
+ * Node kind for a root/reached class. Default: the ROOT box takes the design's
182
+ * `rootKind` (`controller`/`apiImplementation`, chosen by root mode); every
183
+ * reached dependency is a plain `class`. Angular overrides this to render
184
+ * component classes as `component`.
185
+ */
189
186
  rootKindOf(cls) {
190
- return rootKindOfClass(cls);
187
+ return cls === this.rootClass ? this.design.rootKind : 'class';
191
188
  }
192
189
  addRoot(cls) {
190
+ this.rootClass = cls;
193
191
  const id = this.classNode(cls);
194
192
  this.design.root = id;
195
193
  this.walkClass(cls);
@@ -524,27 +522,29 @@ function buildDesign(root, rootKind, workspaceRoot, makeBuilder) {
524
522
  }
525
523
  /**
526
524
  * Build the full Inversify DI graph for one project: one self-contained
527
- * `DiDesign` per @Controller root. `projectRoot` is workspace-relative.
525
+ * `DiDesign` per @DocumentDesign root. `projectRoot` is workspace-relative.
528
526
  *
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`.
527
+ * Both `rootMode`s root on @DocumentDesign classes; the mode only sets the root
528
+ * box kind (`'controller'` for server, `'apiImplementation'` for designed-lib).
529
+ * `includeLibraryRoots` (default false) lets a project with NO @DocumentDesign
530
+ * class fall back to top-of-DAG DI classes (rendered as plain `class` roots).
532
531
  */
533
532
  function buildDiGraph(program, workspaceRoot, projectRoot, projectName, includeLibraryRoots = false, rootMode = 'controller') {
534
533
  const checker = program.getTypeChecker();
535
534
  const table = (0, bindings_1.collectBindings)(program, checker, workspaceRoot);
536
535
  const graph = new model_1.DiGraph(projectName);
537
536
  const classes = projectClasses(program, workspaceRoot, projectRoot);
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
- })();
537
+ const designRoots = classes.filter((cls) => isDocumentDesignClass(cls));
538
+ // Both modes root on @DocumentDesign classes. In controller mode a project with
539
+ // no explicit design root may fall back to top-of-DAG DI classes (those render
540
+ // as plain `class` roots, preserving prior behaviour).
541
+ const roots = designRoots.length > 0
542
+ ? designRoots
543
+ : rootMode === 'controller' && includeLibraryRoots
544
+ ? findLibraryRoots(classes, checker, table, workspaceRoot)
545
+ : [];
546
546
  for (const root of [...roots].sort(byClassName)) {
547
- const rootKind = rootKindOfClass(root);
547
+ const rootKind = isDocumentDesignClass(root) ? rootKindForMode(rootMode) : 'class';
548
548
  graph.designs.push(buildDesign(root, rootKind, workspaceRoot, (design) => new InversifyDesignBuilder(checker, table, workspaceRoot, design)));
549
549
  }
550
550
  return graph;