@starci/hfs 4.0.10 → 4.1.0

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.
Files changed (271) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/emit/operations.mjs +1 -1
  3. package/emit/type-schema.mjs +2 -1
  4. package/lint/run.mjs +3 -2
  5. package/package.json +1 -1
  6. package/report/order.mjs +2 -0
  7. package/runtime/config.example.yaml +187 -0
  8. package/runtime/engine/by-code-unit.mjs +12 -0
  9. package/runtime/engine/config.mjs +221 -239
  10. package/runtime/engine/invalid-config.mjs +5 -5
  11. package/runtime/engine/model-config.mjs +90 -0
  12. package/runtime/engine/orca-config.mjs +3 -1
  13. package/runtime/engine/removed-vocabulary.mjs +28 -0
  14. package/runtime/engine/resources-config.mjs +52 -0
  15. package/runtime/engine/runtime-root.mjs +17 -0
  16. package/runtime/engine/secrets.mjs +35 -31
  17. package/runtime/engine/sonar-config.mjs +21 -0
  18. package/runtime/engine/temp-root.mjs +32 -0
  19. package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
  20. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
  21. package/runtime/knowledge/hfs/rules.yaml +98 -65
  22. package/runtime/knowledge/hfs/slots.yaml +11 -14
  23. package/runtime/knowledge/patterns/be/api.yaml +7 -24
  24. package/runtime/knowledge/patterns/be/cli.yaml +3 -12
  25. package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
  26. package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
  27. package/runtime/modules/kernel/failure-codes.yaml +1 -41
  28. package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
  29. package/runtime/modules/models/registry.yaml +25 -359
  30. package/runtime/modules/models/runtimes.yaml +92 -256
  31. package/runtime/modules/models/tiers.yaml +65 -0
  32. package/runtime/scripts/api/fs/claim-file.mjs +30 -0
  33. package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
  34. package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
  35. package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
  36. package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
  37. package/runtime/scripts/api/git/lib.mjs +34 -1
  38. package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
  39. package/runtime/scripts/api/process/run-program.mjs +2 -1
  40. package/runtime/scripts/api/sops/lib.mjs +198 -120
  41. package/runtime/scripts/hfs/allows.mjs +3 -3
  42. package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
  43. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
  44. package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
  45. package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
  46. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
  47. package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
  48. package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
  49. package/runtime/scripts/hfs/architecture/config.mjs +95 -80
  50. package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
  51. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
  52. package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
  53. package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
  54. package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
  55. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
  56. package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
  57. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
  58. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
  59. package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
  60. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
  61. package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
  62. package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
  63. package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
  64. package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
  65. package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
  66. package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
  67. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
  68. package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
  69. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  70. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
  71. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
  72. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
  73. package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
  74. package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
  75. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
  76. package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
  77. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
  78. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
  79. package/runtime/scripts/hfs/architecture/index.mjs +158 -102
  80. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
  81. package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
  82. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
  83. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
  84. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
  85. package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
  86. package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
  87. package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
  88. package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
  89. package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
  90. package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
  91. package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
  92. package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
  93. package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
  94. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
  95. package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
  96. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
  97. package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
  98. package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
  99. package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
  100. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
  101. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
  102. package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
  103. package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
  104. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
  105. package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
  106. package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
  107. package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
  108. package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
  109. package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
  110. package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
  111. package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
  112. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
  113. package/runtime/scripts/hfs/architecture.mjs +3 -4
  114. package/runtime/scripts/hfs/check.mjs +61 -70
  115. package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
  116. package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
  117. package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
  118. package/runtime/scripts/hfs/edition-slots.mjs +5 -3
  119. package/runtime/scripts/hfs/linear-text.mjs +30 -0
  120. package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
  121. package/runtime/scripts/hfs/path-findings.mjs +58 -38
  122. package/runtime/scripts/hfs/pin-findings.mjs +31 -0
  123. package/runtime/scripts/hfs/repo-identity.mjs +5 -2
  124. package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
  125. package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
  126. package/runtime/scripts/hfs/rules/cli.mjs +5 -1
  127. package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
  128. package/runtime/scripts/hfs/rules/contract.mjs +35 -27
  129. package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
  130. package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
  131. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
  132. package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
  133. package/runtime/scripts/hfs/rules/database.mjs +36 -23
  134. package/runtime/scripts/hfs/rules/deps.mjs +64 -32
  135. package/runtime/scripts/hfs/rules/docker.mjs +113 -65
  136. package/runtime/scripts/hfs/rules/edition.mjs +118 -111
  137. package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
  138. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
  139. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
  140. package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
  141. package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
  142. package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
  143. package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
  144. package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
  145. package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
  146. package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
  147. package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
  148. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
  149. package/runtime/scripts/hfs/rules/saga.mjs +128 -61
  150. package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
  151. package/runtime/scripts/hfs/rules/services.mjs +34 -16
  152. package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
  153. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
  154. package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
  155. package/runtime/scripts/hfs/secret.mjs +59 -48
  156. package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
  157. package/runtime/scripts/hfs/slot-classify.mjs +65 -0
  158. package/runtime/scripts/hfs/slot-errors.mjs +11 -0
  159. package/runtime/scripts/hfs/slot-imports.mjs +76 -0
  160. package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
  161. package/runtime/scripts/hfs/slot-match.mjs +153 -0
  162. package/runtime/scripts/hfs/slot-path.mjs +8 -0
  163. package/runtime/scripts/hfs/slot-required.mjs +76 -0
  164. package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
  165. package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
  166. package/runtime/scripts/hfs/slots.mjs +72 -693
  167. package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
  168. package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
  169. package/runtime/scripts/hfs/tree.mjs +19 -14
  170. package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
  171. package/runtime/scripts/hfs/view.mjs +1 -1
  172. package/runtime/scripts/lib/dockerfile.mjs +56 -33
  173. package/runtime/scripts/lib/event-contract.mjs +17 -13
  174. package/runtime/scripts/lib/git.mjs +2 -2
  175. package/runtime/scripts/lib/graphql-contract.mjs +158 -322
  176. package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
  177. package/runtime/scripts/lib/i18n.mjs +3 -2
  178. package/runtime/scripts/lib/in-order.mjs +71 -0
  179. package/runtime/scripts/lib/language.mjs +3 -3
  180. package/runtime/scripts/lib/list.mjs +8 -1
  181. package/runtime/scripts/lib/path-key.mjs +38 -5
  182. package/runtime/scripts/lib/pid-alive.mjs +7 -0
  183. package/runtime/scripts/lib/regex.mjs +1 -1
  184. package/runtime/scripts/lib/same-text.mjs +1 -1
  185. package/runtime/scripts/lib/secret-patterns.mjs +6 -2
  186. package/runtime/scripts/lib/sleep-sync.mjs +2 -2
  187. package/runtime/scripts/lib/sops-envelope.mjs +95 -48
  188. package/runtime/scripts/lib/stack-services.mjs +1 -2
  189. package/runtime/scripts/lib/ts-ast.mjs +9 -0
  190. package/runtime/scripts/lib/walk.mjs +6 -1
  191. package/scaffold/add-cli-lite.mjs +2 -1
  192. package/scaffold/add-table.mjs +2 -2
  193. package/scaffold/add.mjs +3 -2
  194. package/scaffold/app.mjs +4 -2
  195. package/scaffold/edition-gate.mjs +25 -25
  196. package/scaffold/lite-exports.mjs +2 -1
  197. package/scaffold/service.mjs +5 -4
  198. package/sync/hygiene.mjs +15 -9
  199. package/sync/index.mjs +7 -3
  200. package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
  201. package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
  202. package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
  203. package/templates/app/ci-workflows/sonar-steps.yml +2 -2
  204. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
  205. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
  206. package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
  207. package/templates/app/starciwork.gitignore +1 -1
  208. package/templates/be/image/api/Dockerfile +1 -1
  209. package/templates/be/image/cli/Dockerfile +1 -1
  210. package/templates/be/image/worker/Dockerfile +1 -1
  211. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
  212. package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
  213. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
  214. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
  215. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
  216. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
  217. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
  218. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
  219. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
  220. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
  221. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
  222. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
  223. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
  224. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
  225. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
  226. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  227. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
  228. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
  229. package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
  230. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  231. package/templates/fe/image/next/Dockerfile +2 -1
  232. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
  233. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
  234. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
  235. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
  236. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
  237. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
  238. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
  239. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
  240. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
  241. package/upgrade/index.mjs +9 -2
  242. package/runtime/engine/admission.mjs +0 -308
  243. package/runtime/engine/canonical-json.mjs +0 -10
  244. package/runtime/engine/db/blob.mjs +0 -315
  245. package/runtime/engine/db/ledger-paths.mjs +0 -83
  246. package/runtime/engine/db/ledger.mjs +0 -1118
  247. package/runtime/engine/db/machine-connection.mjs +0 -135
  248. package/runtime/engine/db/machine-schema.mjs +0 -84
  249. package/runtime/engine/db/machine.mjs +0 -1367
  250. package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
  251. package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
  252. package/runtime/engine/db/provider-reservations.mjs +0 -101
  253. package/runtime/engine/digest.mjs +0 -16
  254. package/runtime/engine/refuse.mjs +0 -11
  255. package/runtime/modules/ops/_labels.yaml +0 -53
  256. package/runtime/scripts/api/node/lib.mjs +0 -14
  257. package/runtime/scripts/api/node/spawn-node.mjs +0 -6
  258. package/runtime/scripts/api/process/lib.mjs +0 -111
  259. package/runtime/scripts/api/process/owned-process.mjs +0 -78
  260. package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
  261. package/runtime/scripts/connectors/lib.mjs +0 -488
  262. package/runtime/scripts/lib/clip.mjs +0 -19
  263. package/runtime/scripts/lib/display-names.mjs +0 -259
  264. package/runtime/scripts/lib/example-refs.mjs +0 -158
  265. package/runtime/scripts/lib/json-schema.mjs +0 -52
  266. package/runtime/scripts/lib/process-identity.mjs +0 -6
  267. package/runtime/scripts/lib/read-yaml.mjs +0 -18
  268. package/runtime/scripts/lib/redact.mjs +0 -161
  269. package/runtime/scripts/lib/sleep.mjs +0 -7
  270. package/runtime/scripts/lib/source-phrases.mjs +0 -34
  271. package/runtime/scripts/lib/sqlite.mjs +0 -21
@@ -1,8 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import { isInside } from './config.mjs';
3
- import { relativePath, UNPROVEN_FRAMEWORK, unwrapExpression } from './typescript.mjs';
4
- import { commonJsRequireReasons, constructedDecoratorKind, decoratorCallee, moduleExportsOf, mutableDecoratorKind, nodeDecorators, normalizedSymbol, normalizedSymbolValue, programSourcesOf, returnedExpressions, selectedNode, tracedFrameworkKinds, valueSymbol, violation } from './ast-walks.mjs';
5
- import { sourceLocation } from '../../lib/ts-ast.mjs';
3
+ import { relativePath, unwrapExpression } from './typescript.mjs';
4
+ import { commonJsRequireReasons, decoratorCallee, moduleExportsOf, nodeDecorators, normalizedSymbol, normalizedSymbolValue, programSourcesOf, returnedExpressions, selectedNode, valueSymbol, violation } from './ast-walks.mjs';
5
+ import { createBackendSourceShapeChecker } from './source-names-shape.mjs';
6
6
 
7
7
  const SOURCE_LAYOUT_RULE_ID = 'BE_FEATURE_LAYOUT_INVALID';
8
8
  const SOURCE_NAME_RULE_ID = 'BE_SOURCE_FORM';
@@ -33,6 +33,7 @@ const PORT_DECLARATION_ROLES = new Set(['contracts', 'port']);
33
33
  const NON_CLASS_ROLES = new Set(['constants', 'contracts', 'decorators', 'enum', 'providers', 'types']);
34
34
  const KNOWN_HYPHEN_ROLES = [...CLASS_ROLE_SUFFIX.keys()].sort((a, b) => b.length - a.length);
35
35
  const SPECIAL_BASENAMES = new Set(['config', 'configuration', 'constants', 'decorators', 'env', 'environment', 'index', 'main', 'types']);
36
+ const NO_PROGRAM_CHECKER = 'a TypeScript program checker could not be associated with its source';
36
37
  const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/i;
37
38
 
38
39
  function absoluteRoots(root, relatives) {
@@ -43,20 +44,25 @@ function canonical(file) {
43
44
  return path.resolve(file);
44
45
  }
45
46
 
46
-
47
+ function addFrameworkTargets(config, context, checker, sourceFile, statement, bySymbol, reasons) {
48
+ const bound = moduleExportsOf(context.ts, checker, statement);
49
+ if (!bound || !FRAMEWORK_EXPORTS.has(bound.specifier)) return;
50
+ if (!bound.symbol) {
51
+ reasons.push(`${relativePath(config.root, sourceFile.fileName)} cannot resolve ${bound.specifier}`);
52
+ return;
53
+ }
54
+ const selected = FRAMEWORK_EXPORTS.get(bound.specifier);
55
+ for (const [name, target] of bound.exports) if (selected.has(name) && target) bySymbol.set(target, name);
56
+ }
47
57
 
48
58
  function frameworkTargets(config, context, checker, localFiles) {
49
59
  const reasons = [];
50
60
  const bySymbol = new Map();
51
61
  const files = programSourcesOf(context, checker, localFiles);
52
- if (!files) return { bySymbol, reasons: ['a TypeScript program checker could not be associated with its source'] };
62
+ if (!files) return { bySymbol, reasons: [NO_PROGRAM_CHECKER] };
53
63
  for (const sourceFile of files) {
54
64
  for (const statement of sourceFile.statements) {
55
- const bound = moduleExportsOf(context.ts, checker, statement);
56
- if (!bound || !FRAMEWORK_EXPORTS.has(bound.specifier)) continue;
57
- if (!bound.symbol) { reasons.push(`${relativePath(config.root, sourceFile.fileName)} cannot resolve ${bound.specifier}`); continue; }
58
- const selected = FRAMEWORK_EXPORTS.get(bound.specifier);
59
- for (const [name, target] of bound.exports) if (selected.has(name) && target) bySymbol.set(target, name);
65
+ addFrameworkTargets(config, context, checker, sourceFile, statement, bySymbol, reasons);
60
66
  }
61
67
  commonJsRequireReasons(context.ts, checker, sourceFile, (specifier) => FRAMEWORK_EXPORTS.has(specifier),
62
68
  reasons, relativePath(config.root, sourceFile.fileName), 'binding whose source role cannot be proved');
@@ -113,6 +119,33 @@ function combineClassValue(results) {
113
119
  return { status: 'class', names: [...new Set(classes.flatMap(result => result.names))] };
114
120
  }
115
121
 
122
+ function pushReturnedClassStatuses(ts, checker, results, container, nextSeen, depth) {
123
+ for (const returned of returnedExpressions(ts, container)) results.push(classValueStatus(ts, checker, returned, nextSeen, depth + 1));
124
+ }
125
+
126
+ function classDeclarationResults(ts, checker, expression, declaration, nextSeen, depth) {
127
+ const results = [];
128
+ if (ts.isClassDeclaration(declaration)) results.push({ status: 'class', names: declaration.name ? [declaration.name.text] : [] });
129
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
130
+ if (ts.isCallExpression(expression)
131
+ && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) {
132
+ pushReturnedClassStatuses(ts, checker, results, declaration.initializer, nextSeen, depth);
133
+ } else results.push(classValueStatus(ts, checker, declaration.initializer, nextSeen, depth + 1));
134
+ }
135
+ if (ts.isCallExpression(expression)) pushReturnedClassStatuses(ts, checker, results, declaration, nextSeen, depth);
136
+ return results;
137
+ }
138
+
139
+ function classSymbolStatus(ts, checker, expression, seen, depth) {
140
+ const selected = selectedNode(ts, ts.isCallExpression(expression) ? expression.expression : expression);
141
+ const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
142
+ if (!symbol || seen.has(symbol)) return { status: 'not-class', names: [] };
143
+ const nextSeen = new Set(seen).add(symbol);
144
+ const results = [];
145
+ for (const declaration of symbol.getDeclarations?.() ?? []) results.push(...classDeclarationResults(ts, checker, expression, declaration, nextSeen, depth));
146
+ return results.length ? combineClassValue(results) : { status: 'not-class', names: [] };
147
+ }
148
+
116
149
  function classValueStatus(ts, checker, expression, seen = new Set(), depth = 0) {
117
150
  if (!expression) return { status: 'not-class', names: [] };
118
151
  if (depth > 10) return { status: 'unavailable', names: [] };
@@ -122,26 +155,7 @@ function classValueStatus(ts, checker, expression, seen = new Set(), depth = 0)
122
155
  classValueStatus(ts, checker, expression.whenTrue, seen, depth + 1),
123
156
  classValueStatus(ts, checker, expression.whenFalse, seen, depth + 1),
124
157
  ]);
125
- const selected = selectedNode(ts, ts.isCallExpression(expression) ? expression.expression : expression);
126
- const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
127
- if (!symbol || seen.has(symbol)) return { status: 'not-class', names: [] };
128
- const nextSeen = new Set(seen).add(symbol);
129
- const results = [];
130
- for (const declaration of symbol.getDeclarations?.() ?? []) {
131
- if (ts.isClassDeclaration(declaration)) results.push({ status: 'class', names: declaration.name ? [declaration.name.text] : [] });
132
- if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
133
- if (ts.isCallExpression(expression)
134
- && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) {
135
- for (const returned of returnedExpressions(ts, declaration.initializer)) {
136
- results.push(classValueStatus(ts, checker, returned, nextSeen, depth + 1));
137
- }
138
- } else results.push(classValueStatus(ts, checker, declaration.initializer, nextSeen, depth + 1));
139
- }
140
- if (ts.isCallExpression(expression)) for (const returned of returnedExpressions(ts, declaration)) {
141
- results.push(classValueStatus(ts, checker, returned, nextSeen, depth + 1));
142
- }
143
- }
144
- return results.length ? combineClassValue(results) : { status: 'not-class', names: [] };
158
+ return classSymbolStatus(ts, checker, expression, seen, depth);
145
159
  }
146
160
 
147
161
  function combineObjectStatus(statuses) {
@@ -150,6 +164,22 @@ function combineObjectStatus(statuses) {
150
164
  return 'not-object';
151
165
  }
152
166
 
167
+ function declarationObjectStatus(ts, checker, declaration, seen, depth) {
168
+ if (ts.isInterfaceDeclaration(declaration)) return 'object';
169
+ if (ts.isTypeAliasDeclaration(declaration)) return objectTypeStatus(ts, checker, declaration.type, seen, depth + 1);
170
+ if (ts.isTypeParameterDeclaration(declaration) || ts.isClassDeclaration(declaration)) return 'unavailable';
171
+ return 'not-object';
172
+ }
173
+
174
+ function typeReferenceStatus(ts, checker, node, seen, depth) {
175
+ const symbol = normalizedSymbol(ts, checker, node.typeName);
176
+ if (!symbol || seen.has(symbol)) return 'unavailable';
177
+ const nextSeen = new Set(seen).add(symbol);
178
+ const declarations = symbol.getDeclarations?.() ?? [];
179
+ if (!declarations.length) return 'unavailable';
180
+ return combineObjectStatus(declarations.map(declaration => declarationObjectStatus(ts, checker, declaration, nextSeen, depth)));
181
+ }
182
+
153
183
  function objectTypeStatus(ts, checker, node, seen = new Set(), depth = 0) {
154
184
  if (!node || depth > 12) return 'unavailable';
155
185
  if (ts.isTypeLiteralNode(node) || ts.isMappedTypeNode(node)) return 'object';
@@ -157,20 +187,7 @@ function objectTypeStatus(ts, checker, node, seen = new Set(), depth = 0) {
157
187
  if (ts.isIntersectionTypeNode(node) || ts.isUnionTypeNode(node)) {
158
188
  return combineObjectStatus(node.types.map(type => objectTypeStatus(ts, checker, type, seen, depth + 1)));
159
189
  }
160
- if (ts.isTypeReferenceNode(node)) {
161
- const symbol = normalizedSymbol(ts, checker, node.typeName);
162
- if (!symbol || seen.has(symbol)) return 'unavailable';
163
- const nextSeen = new Set(seen).add(symbol);
164
- const declarations = symbol.getDeclarations?.() ?? [];
165
- if (!declarations.length) return 'unavailable';
166
- return combineObjectStatus(declarations.map(declaration => {
167
- if (ts.isInterfaceDeclaration(declaration)) return 'object';
168
- if (ts.isTypeAliasDeclaration(declaration)) return objectTypeStatus(ts, checker, declaration.type, nextSeen, depth + 1);
169
- if (ts.isTypeParameterDeclaration(declaration)) return 'unavailable';
170
- if (ts.isClassDeclaration(declaration)) return 'unavailable';
171
- return 'not-object';
172
- }));
173
- }
190
+ if (ts.isTypeReferenceNode(node)) return typeReferenceStatus(ts, checker, node, seen, depth);
174
191
  if (ts.isConditionalTypeNode(node) || ts.isIndexedAccessTypeNode(node) || ts.isInferTypeNode(node) || ts.isTypeQueryNode(node)) return 'unavailable';
175
192
  return 'not-object';
176
193
  }
@@ -206,7 +223,7 @@ function staticProperty(ts, object, name) {
206
223
  : { status: 'dynamic', node: matching[0].initializer };
207
224
  }
208
225
 
209
- function graphqlField(config, context, sourceFile, node, decorator, kind, namingReasons, violations) {
226
+ function graphqlField({ config, context, sourceFile, node, decorator, kind, namingReasons, violations }) {
210
227
  const call = decorator.expression;
211
228
  if (!context.ts.isCallExpression(call)) {
212
229
  namingReasons.push(`${relativePath(config.root, sourceFile.fileName)} has a non-call @${kind} decorator`);
@@ -269,219 +286,37 @@ function implementsFramework(ts, checker, declaration, targets, name) {
269
286
  }
270
287
 
271
288
  /** Names of the interfaces a class implements that are declared in a *.port.ts or *.contracts.ts file (resolved by the type checker). */
272
- function implementedPortNames(ts, checker, declaration) {
289
+ function portNamesForImplementedType(ts, checker, type) {
273
290
  const names = [];
274
- for (const clause of declaration.heritageClauses ?? []) if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
275
- for (const type of clause.types) {
276
- const symbol = normalizedSymbol(ts, checker, type.expression);
277
- for (const target of symbol?.getDeclarations?.() ?? []) {
278
- if (!ts.isInterfaceDeclaration(target) && !ts.isTypeAliasDeclaration(target)) continue;
279
- const declaredRole = path.basename(target.getSourceFile().fileName).replace(SOURCE_EXTENSION, '').split('.').at(-1);
280
- if (PORT_DECLARATION_ROLES.has(declaredRole)) names.push(target.name.text);
281
- }
282
- }
291
+ const symbol = normalizedSymbol(ts, checker, type.expression);
292
+ for (const target of symbol?.getDeclarations?.() ?? []) {
293
+ if (!ts.isInterfaceDeclaration(target) && !ts.isTypeAliasDeclaration(target)) continue;
294
+ const declaredRole = path.basename(target.getSourceFile().fileName).replace(SOURCE_EXTENSION, '').split('.').at(-1);
295
+ if (PORT_DECLARATION_ROLES.has(declaredRole)) names.push(target.name.text);
283
296
  }
284
297
  return names;
285
298
  }
286
299
 
300
+ function implementedPortNames(ts, checker, declaration) {
301
+ return (declaration.heritageClauses ?? []).filter(clause => clause.token === ts.SyntaxKind.ImplementsKeyword)
302
+ .flatMap(clause => clause.types.flatMap(type => portNamesForImplementedType(ts, checker, type)));
303
+ }
304
+
287
305
  function locatedRoot(roots, fileName) {
288
306
  return roots.find(root => isInside(root, fileName)) ?? null;
289
307
  }
290
308
 
291
309
  /** Check the adopted domain-first backend source layout without inferring semantic ownership from folder names alone. */
292
- export function checkBackendSourceShape(config, context) {
293
- const ts = context.ts;
294
- const featureRoots = absoluteRoots(config.root, config.backend.features);
295
- const moduleRoots = absoluteRoots(config.root, config.backend.modules);
296
- const localFiles = new Set(context.files.map(file => canonical(file.fileName)));
297
- const frameworkByChecker = new Map();
298
- const violations = [];
299
- const layoutReasons = [];
300
- const namingReasons = [];
301
- let checkedFiles = 0;
302
-
303
- for (const sourceFile of context.files) {
304
- const fileName = canonical(sourceFile.fileName);
305
- const featureRoot = locatedRoot(featureRoots, fileName);
306
- const moduleRoot = locatedRoot(moduleRoots, fileName);
307
- if (!featureRoot && !moduleRoot) continue;
308
- checkedFiles += 1;
309
- const checker = context.checkerFor(fileName);
310
- if (!frameworkByChecker.has(checker)) frameworkByChecker.set(checker, frameworkTargets(config, context, checker, localFiles));
311
- const framework = frameworkByChecker.get(checker);
312
- const relativeToOwner = relativePath(featureRoot ?? moduleRoot, fileName);
313
- const parts = relativeToOwner.split('/');
314
- const directories = parts.slice(0, -1).map(part => part.toLowerCase());
315
- const applicationIndex = directories.indexOf('application');
316
- const transportIndex = directories.indexOf('transport');
317
- const inApplication = applicationIndex >= 0;
318
- const inTransport = transportIndex >= 0;
319
- const role = sourceRole(ts, sourceFile);
320
- const relative = relativePath(config.root, fileName);
321
- const migrationName = /^\d{10,}-[A-Z][A-Za-z0-9]*$/.test(role.base);
322
- const persistencePath = !role.spec && (migrationName || directories.includes('migrations'));
323
- const special = SPECIAL_BASENAMES.has(role.base.toLowerCase()) || migrationName
324
- || ['constants', 'enums', 'errors', 'migrations'].some(folder => directories.includes(folder));
325
- const publicDeclarations = exportedDeclarations(ts, checker, sourceFile);
326
-
327
- if (!migrationName && !kebabSourceBase(role.base)) violations.push(violation(config, sourceFile, sourceFile, SOURCE_NAME_RULE_ID,
328
- `Source basename ${role.base} must use kebab-case segments plus an explicit role suffix.`));
329
- if (!role.role && !special) namingReasons.push(`${relative} has no statically identifiable source role`);
330
-
331
- if (featureRoot) {
332
- if (inApplication && inTransport) violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
333
- 'A feature source cannot belong to both application and transport layers.'));
334
- if (role.role && APPLICATION_ROLES.has(role.role) && !inApplication) violations.push(violation(config, sourceFile, sourceFile,
335
- SOURCE_LAYOUT_RULE_ID, `${role.role} source belongs under the feature application/ layer.`));
336
- if (role.role && TRANSPORT_ROLES.has(role.role) && !inTransport) violations.push(violation(config, sourceFile, sourceFile,
337
- SOURCE_LAYOUT_RULE_ID, `${role.role} source belongs under the feature transport/<protocol>/ layer.`));
338
- if (inApplication && role.role && TRANSPORT_ROLES.has(role.role)) violations.push(violation(config, sourceFile, sourceFile,
339
- SOURCE_LAYOUT_RULE_ID, `Transport role ${role.role} cannot live in application/.`));
340
- if (inTransport && role.role && APPLICATION_ROLES.has(role.role)) violations.push(violation(config, sourceFile, sourceFile,
341
- SOURCE_LAYOUT_RULE_ID, `Application role ${role.role} cannot live in transport/.`));
342
- if (inTransport && transportIndex >= directories.length - 1) violations.push(violation(config, sourceFile, sourceFile,
343
- SOURCE_LAYOUT_RULE_ID, 'Feature transport source must name a protocol below transport/.'));
344
- const topLevelFeatureFile = directories.length === 1 && (role.base === 'index' || role.role === 'module' || role.spec);
345
- if (inApplication && role.base !== 'index' && (!role.role || !APPLICATION_LAYER_ROLES.has(role.role))
346
- && !(role.role && (TRANSPORT_ROLES.has(role.role) || role.role === 'entity'))) {
347
- layoutReasons.push(`${relative} role ${role.role ?? '(unclassified)'} is not a selected application-layer role`);
348
- }
349
- if (inTransport && role.base !== 'index' && (!role.role || !TRANSPORT_LAYER_ROLES.has(role.role))
350
- && !(role.role && (APPLICATION_ROLES.has(role.role) || role.role === 'entity'))) {
351
- layoutReasons.push(`${relative} role ${role.role ?? '(unclassified)'} is not a selected transport-layer role`);
352
- }
353
- if (!inApplication && !inTransport && !topLevelFeatureFile && role.role && role.role !== 'entity'
354
- && !APPLICATION_ROLES.has(role.role) && !TRANSPORT_ROLES.has(role.role)) {
355
- layoutReasons.push(`${relative} role ${role.role} has no statically selected feature layer`);
356
- }
357
- if (!inApplication && !inTransport && !topLevelFeatureFile && !role.role && !persistencePath) {
358
- layoutReasons.push(`${relative} has no statically selected feature layer`);
359
- }
360
- if (persistencePath) violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
361
- 'Migration source cannot be owned by a feature; place it under the declared persistence module.'));
362
- if (role.role === 'entity') violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
363
- 'Entity source cannot be owned by a feature; place schema under the declared persistence module.'));
364
- }
310
+ const checkBackendSourceShapeImpl = createBackendSourceShapeChecker({
311
+ absoluteRoots, canonical, frameworkTargets, locatedRoot, sourceRole, kebabSourceBase, exportedDeclarations,
312
+ classValueStatus, objectContractStatus, companionInterfaceAllowed, hasModifier, graphqlField, graphqlArgument,
313
+ graphqlEnumRegistration, selectedCallKind, callsNamedHelper, implementsFramework, implementedPortNames, decoratorKind,
314
+ SOURCE_LAYOUT_RULE_ID, SOURCE_NAME_RULE_ID, APPLICATION_ROLES, TRANSPORT_ROLES, APPLICATION_LAYER_ROLES,
315
+ TRANSPORT_LAYER_ROLES, CLASS_ROLE_SUFFIX, TRANSPORT_OBJECT_SUFFIX, NON_CLASS_ROLES, SPECIAL_BASENAMES,
316
+ TRACE_NEW,
317
+ });
365
318
 
366
- const classDeclarations = sourceFile.statements.filter(statement => ts.isClassDeclaration(statement));
367
- for (const declaration of classDeclarations) {
368
- const declarationDecorators = nodeDecorators(ts, declaration);
369
- const kinds = declarationDecorators.map(decorator => decoratorKind(ts, checker, decorator, framework.bySymbol)).filter(Boolean);
370
- const graphQlDto = kinds.some(kind => kind === 'ArgsType' || kind === 'InputType' || kind === 'ObjectType');
371
- if (graphQlDto && (!featureRoot || !inTransport || directories[transportIndex + 1] !== 'graphql')) {
372
- violations.push(violation(config, sourceFile, declaration.name ?? declaration, SOURCE_LAYOUT_RULE_ID,
373
- 'GraphQL DTO classes belong under a feature transport/graphql/ boundary.'));
374
- }
375
- const persistenceSchema = kinds.includes('Entity') || kinds.includes('ViewEntity')
376
- || implementsFramework(ts, checker, declaration, framework.bySymbol, 'MigrationInterface');
377
- if (persistenceSchema && featureRoot) violations.push(violation(config, sourceFile, declaration.name ?? declaration, SOURCE_LAYOUT_RULE_ID,
378
- 'TypeORM entities and migrations cannot be owned by a feature; place schema under its declared persistence module.'));
379
- const classSuffix = role.role && !NON_CLASS_ROLES.has(role.role) ? CLASS_ROLE_SUFFIX.get(role.role) : null;
380
- if (publicDeclarations.has(declaration) && !declaration.name) namingReasons.push(`${relative} exports an anonymous class whose role name cannot be proved`);
381
- else if (publicDeclarations.has(declaration) && declaration.name && classSuffix && !declaration.name.text.endsWith(classSuffix)
382
- && !implementedPortNames(ts, checker, declaration).some(port => declaration.name.text.endsWith(port))) {
383
- violations.push(violation(config, sourceFile, declaration.name, SOURCE_NAME_RULE_ID,
384
- `Exported class ${declaration.name.text} must end in ${classSuffix} for a ${role.role} source file, or in the name of the port it implements.`));
385
- } else if (publicDeclarations.has(declaration) && declaration.name && role.role && !NON_CLASS_ROLES.has(role.role) && !classSuffix) {
386
- namingReasons.push(`${relative} declares exported class ${declaration.name.text} with unclassified file role ${role.role}`);
387
- }
388
- }
389
-
390
- for (const statement of sourceFile.statements) if (ts.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) {
391
- if (!publicDeclarations.has(declaration) || !declaration.initializer) continue;
392
- const classValue = classValueStatus(ts, checker, declaration.initializer);
393
- if (classValue.status === 'unavailable') {
394
- namingReasons.push(`${relative} exports a class-valued declaration whose role identity is not statically proved`);
395
- continue;
396
- }
397
- if (classValue.status !== 'class') continue;
398
- const classSuffix = role.role && !NON_CLASS_ROLES.has(role.role) ? CLASS_ROLE_SUFFIX.get(role.role) : null;
399
- if (!classSuffix || !ts.isIdentifier(declaration.name)) {
400
- namingReasons.push(`${relative} exports a class-valued declaration without a statically selected class role`);
401
- continue;
402
- }
403
- const names = [declaration.name.text, ...classValue.names];
404
- const invalid = names.find(name => !name.endsWith(classSuffix));
405
- if (invalid) violations.push(violation(config, sourceFile, declaration.name, SOURCE_NAME_RULE_ID,
406
- `Exported class value ${invalid} must end in ${classSuffix} for a ${role.role} source file.`));
407
- }
408
-
409
- for (const statement of sourceFile.statements) {
410
- if (ts.isEnumDeclaration(statement)) {
411
- if (hasModifier(ts, statement, ts.SyntaxKind.ConstKeyword) || !/^[A-Z][A-Za-z0-9]*$/.test(statement.name.text)) {
412
- violations.push(violation(config, sourceFile, statement.name, SOURCE_NAME_RULE_ID,
413
- 'Enums must be non-const declarations with PascalCase names.'));
414
- }
415
- for (const member of statement.members) {
416
- const name = ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name) ? member.name.text : null;
417
- const initializer = member.initializer && unwrapExpression(ts, member.initializer);
418
- if (!name || !/^[A-Z][A-Za-z0-9]*$/.test(name) || !initializer || !ts.isStringLiteralLike(initializer)) {
419
- violations.push(violation(config, sourceFile, member.name, SOURCE_NAME_RULE_ID,
420
- 'Enum members must use PascalCase names with explicit static string values.'));
421
- }
422
- }
423
- }
424
- const contractStatus = objectContractStatus(ts, checker, statement);
425
- const transportSuffix = TRANSPORT_OBJECT_SUFFIX.get(role.role);
426
- if (contractStatus === 'object' && publicDeclarations.has(statement) && transportSuffix && !role.spec
427
- && !statement.name.text.endsWith(transportSuffix) && !companionInterfaceAllowed(ts, sourceFile, statement.name.text)) {
428
- violations.push(violation(config, sourceFile, statement.name, SOURCE_NAME_RULE_ID,
429
- `Public object contract ${statement.name.text} in a ${role.role} source file must end in ${transportSuffix}.`));
430
- }
431
- }
432
-
433
- const visit = node => {
434
- for (const decorator of nodeDecorators(ts, node)) {
435
- const kind = decoratorKind(ts, checker, decorator, framework.bySymbol);
436
- if (!kind) {
437
- const dynamic = mutableDecoratorKind(ts, checker, decorator, framework.bySymbol)
438
- ?? constructedDecoratorKind(ts, checker, decorator, framework.bySymbol, TRACE_NEW);
439
- if (dynamic) {
440
- const detail = `${relative} uses a mutable or constructed ${dynamic} decorator identity`;
441
- if (['Entity', 'ViewEntity', 'ArgsType', 'InputType', 'ObjectType', 'multiple framework', 'unproven framework'].includes(dynamic)) layoutReasons.push(detail);
442
- if (!['Entity', 'ViewEntity'].includes(dynamic)) namingReasons.push(detail);
443
- }
444
- }
445
- if (kind === 'Mutation' || kind === 'Query') graphqlField(config, context, sourceFile, node, decorator, kind, namingReasons, violations);
446
- if (kind === 'Args') graphqlArgument(config, context, sourceFile, node, decorator, namingReasons, violations);
447
- }
448
- if (ts.isCallExpression(node)) {
449
- if (selectedCallKind(ts, checker, node, framework.bySymbol) === 'registerEnumType') {
450
- graphqlEnumRegistration(config, context, sourceFile, node, namingReasons, violations);
451
- } else if (callsNamedHelper(ts, checker, node, 'createEnumType')) {
452
- namingReasons.push(`${relative} calls a project GraphQL enum adapter whose emitted type name is not statically proved`);
453
- }
454
- }
455
- const exportedFactoryCall = ts.isCallExpression(node) && ts.isVariableDeclaration(node.parent) && publicDeclarations.has(node.parent);
456
- if ((ts.isNewExpression(node) || exportedFactoryCall) && featureRoot) {
457
- const constructed = tracedFrameworkKinds(ts, checker, node, framework.bySymbol, TRACE_NEW);
458
- if (constructed.has('EntitySchema')) violations.push(violation(config, sourceFile, node.expression, SOURCE_LAYOUT_RULE_ID,
459
- 'TypeORM EntitySchema cannot be owned by a feature; place schema under its declared persistence module.'));
460
- if (constructed.has(UNPROVEN_FRAMEWORK)) layoutReasons.push(`${relative} has a constructed provider identity beyond the bounded static trace`);
461
- }
462
- ts.forEachChild(node, visit);
463
- };
464
- visit(sourceFile);
465
- }
466
-
467
- for (const framework of frameworkByChecker.values()) {
468
- layoutReasons.push(...framework.reasons);
469
- namingReasons.push(...framework.reasons.filter(reason => reason.includes('@nestjs/graphql')));
470
- }
471
- if (checkedFiles === 0) {
472
- layoutReasons.push('the checked TypeScript program contains no configured backend feature or module source');
473
- namingReasons.push('the checked TypeScript program contains no configured backend feature or module source');
474
- }
475
- const coverage = {
476
- files: checkedFiles,
477
- layout: layoutReasons.length
478
- ? { status: 'unavailable', reason: 'one or more backend source placement relations are not statically proved', details: [...new Set(layoutReasons)].sort() }
479
- : { status: 'checked' },
480
- naming: namingReasons.length
481
- ? { status: 'unavailable', reason: 'one or more backend source naming relations are not statically proved', details: [...new Set(namingReasons)].sort() }
482
- : { status: 'checked' },
483
- };
484
- return { violations, coverage };
319
+ export function checkBackendSourceShape(config, context) {
320
+ return checkBackendSourceShapeImpl(config, context);
485
321
  }
486
-
487
322
  export { SOURCE_LAYOUT_RULE_ID, SOURCE_NAME_RULE_ID };