@starci/hfs 4.2.17 → 4.4.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 (564) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +7 -0
  3. package/db/migrate.mjs +361 -0
  4. package/db/post-cutover.mjs +136 -0
  5. package/emit/db-types.mjs +10 -1
  6. package/lint/run.mjs +1 -1
  7. package/package.json +4 -1
  8. package/release/gate.mjs +298 -0
  9. package/release/index.mjs +103 -0
  10. package/release/push.mjs +110 -0
  11. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  12. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  13. package/runtime/knowledge/hfs/rules.yaml +3437 -160
  14. package/runtime/knowledge/hfs/slots.yaml +430 -92
  15. package/runtime/knowledge/patterns/be/api.yaml +92 -2
  16. package/runtime/knowledge/patterns/be/cli.yaml +37 -9
  17. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  18. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  19. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  20. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  21. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  22. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  23. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  24. package/runtime/knowledge/patterns/be/webhooks.yaml +210 -36
  25. package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
  26. package/runtime/knowledge/sonar-gate.yaml +1 -1
  27. package/runtime/modules/kernel/failure-codes.yaml +3128 -47
  28. package/runtime/modules/models/runtimes.yaml +1 -1
  29. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  30. package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
  31. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  32. package/runtime/scripts/api/git/add.mjs +5 -0
  33. package/runtime/scripts/api/git/cat-file.mjs +5 -0
  34. package/runtime/scripts/api/git/init.mjs +7 -0
  35. package/runtime/scripts/api/git/lib.mjs +3 -0
  36. package/runtime/scripts/api/git/tag.mjs +5 -0
  37. package/runtime/scripts/api/node/lib.mjs +20 -0
  38. package/runtime/scripts/api/node/run-node.mjs +6 -0
  39. package/runtime/scripts/api/sops/lib.mjs +41 -0
  40. package/runtime/scripts/api/sops/resolve-sops.mjs +7 -0
  41. package/runtime/scripts/api/sops/seal.mjs +1 -1
  42. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  43. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  44. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  45. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  46. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  47. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  48. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
  49. package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
  50. package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
  51. package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
  52. package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
  53. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  54. package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
  55. package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
  56. package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
  57. package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
  58. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  59. package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
  60. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  61. package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
  62. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
  63. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  64. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  65. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  66. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
  67. package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
  68. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
  69. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  70. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  71. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  72. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  73. package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
  74. package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
  75. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  76. package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
  77. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
  78. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
  79. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  80. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  81. package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
  82. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
  83. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
  84. package/runtime/scripts/hfs/check-results.mjs +28 -0
  85. package/runtime/scripts/hfs/check.mjs +63 -72
  86. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  87. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  88. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  89. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  90. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  91. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  92. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  93. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  94. package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
  95. package/runtime/scripts/hfs/path-findings.mjs +32 -2
  96. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  97. package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
  98. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  99. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  100. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  101. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  102. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  103. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  104. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  105. package/runtime/scripts/hfs/rules/contract.mjs +66 -5
  106. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  107. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  108. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  109. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  110. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  111. package/runtime/scripts/hfs/rules/database-migrations.mjs +317 -37
  112. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  113. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  114. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  115. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  116. package/runtime/scripts/hfs/rules/database.mjs +11 -2
  117. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  118. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  119. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  120. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  121. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  122. package/runtime/scripts/hfs/rules/edition.mjs +11 -10
  123. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  124. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  125. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  126. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  127. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  128. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  129. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  130. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  131. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  132. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  133. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  134. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  135. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  136. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -0
  137. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  138. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  139. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  140. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  141. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  142. package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
  143. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  144. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  145. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  146. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  147. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  148. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  149. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +168 -0
  150. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  151. package/runtime/scripts/hfs/rules/stacks.mjs +13 -8
  152. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
  153. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  154. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  155. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  156. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  157. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  158. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  159. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  160. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  161. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  162. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  163. package/runtime/scripts/hfs/secret-document.mjs +97 -0
  164. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  165. package/runtime/scripts/hfs/secret-errors.mjs +20 -0
  166. package/runtime/scripts/hfs/secret-files.mjs +124 -0
  167. package/runtime/scripts/hfs/secret-init.mjs +300 -0
  168. package/runtime/scripts/hfs/secret-output.mjs +100 -0
  169. package/runtime/scripts/hfs/secret-sops.mjs +130 -0
  170. package/runtime/scripts/hfs/secret-store.mjs +163 -0
  171. package/runtime/scripts/hfs/secret.mjs +162 -193
  172. package/runtime/scripts/hfs/slot-imports.mjs +18 -3
  173. package/runtime/scripts/hfs/slot-required.mjs +13 -0
  174. package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
  175. package/runtime/scripts/hfs/slots.mjs +1 -0
  176. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  177. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  178. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  179. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  180. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  181. package/runtime/scripts/hfs/tree-findings.mjs +22 -0
  182. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  183. package/runtime/scripts/lib/compose-values.mjs +11 -0
  184. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  185. package/runtime/scripts/lib/json-clone.mjs +61 -0
  186. package/runtime/scripts/lib/list.mjs +4 -0
  187. package/runtime/scripts/lib/roots.mjs +59 -0
  188. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  189. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  190. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  191. package/runtime/scripts/lib/stack-services.mjs +1 -1
  192. package/runtime/scripts/lib/swarm-stack.mjs +126 -0
  193. package/runtime/scripts/lib/trim-end.mjs +8 -0
  194. package/runtime/scripts/lib/ts-analysis.mjs +23 -0
  195. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  196. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  197. package/scaffold/add-app.mjs +77 -59
  198. package/scaffold/add-cli-lite.mjs +24 -17
  199. package/scaffold/add-compose.mjs +79 -0
  200. package/scaffold/add-database-lite.mjs +14 -0
  201. package/scaffold/add-fe-overlay.mjs +122 -0
  202. package/scaffold/add-fe-page.mjs +269 -0
  203. package/scaffold/add-inbox-lite.mjs +87 -124
  204. package/scaffold/add-plan.mjs +183 -0
  205. package/scaffold/add-table.mjs +132 -70
  206. package/scaffold/add.mjs +186 -204
  207. package/scaffold/app.mjs +128 -43
  208. package/scaffold/edition-gate.mjs +3 -3
  209. package/scaffold/image.mjs +2 -2
  210. package/scaffold/lite-exports.mjs +2 -1
  211. package/scaffold/service.mjs +2 -2
  212. package/src/main.mjs +238 -119
  213. package/stack/age-install.mjs +37 -0
  214. package/stack/backup.mjs +100 -0
  215. package/stack/build-args.mjs +42 -0
  216. package/stack/build.mjs +173 -0
  217. package/stack/cli.mjs +130 -0
  218. package/stack/config.mjs +57 -0
  219. package/stack/context.mjs +57 -0
  220. package/stack/converge.mjs +107 -0
  221. package/stack/deploy.mjs +301 -0
  222. package/stack/digest.mjs +16 -0
  223. package/stack/docker-transport.mjs +53 -0
  224. package/stack/edge-alias.mjs +118 -0
  225. package/stack/errors.mjs +21 -0
  226. package/stack/image-scan.mjs +152 -0
  227. package/stack/ingress-create.mjs +114 -0
  228. package/stack/ingress-publish.mjs +273 -0
  229. package/stack/ingress.mjs +706 -0
  230. package/stack/jobs.mjs +51 -0
  231. package/stack/live-restore.mjs +177 -0
  232. package/stack/prepare.mjs +266 -0
  233. package/stack/private-file.mjs +22 -0
  234. package/stack/provision.mjs +90 -0
  235. package/stack/prune.mjs +46 -0
  236. package/stack/release-store.mjs +87 -0
  237. package/stack/remote.mjs +205 -0
  238. package/stack/render.mjs +149 -0
  239. package/stack/retire.mjs +47 -0
  240. package/stack/sealed.mjs +149 -0
  241. package/stack/task.mjs +42 -0
  242. package/stack/verify.mjs +99 -0
  243. package/sync/ci-commands.mjs +13 -0
  244. package/sync/index.mjs +127 -64
  245. package/sync/stack-images.mjs +127 -0
  246. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  247. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  248. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  249. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  250. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  251. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  252. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  253. package/templates/app/codegen-graphql.mjs.partial +13 -2
  254. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  255. package/templates/app/codegen-prelude.mjs.partial +8 -0
  256. package/templates/app/gitignore +4 -0
  257. package/templates/app/gitignore-lite +4 -0
  258. package/templates/app/hooks/husky/pre-commit +7 -12
  259. package/templates/app/package-scripts/package.json +1 -0
  260. package/templates/app/package-scripts/supabase.json +7 -0
  261. package/templates/app/package-scripts-lite/package.json +1 -7
  262. package/templates/app/skeleton/.editorconfig +1 -0
  263. package/templates/app/skeleton/.gitattributes +7 -1
  264. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  265. package/templates/app/skeleton/.nvmrc +1 -1
  266. package/templates/app/skeleton/.sops.yaml +1 -1
  267. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  268. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  269. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  270. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  271. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  272. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  273. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  274. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  275. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  276. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  277. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  278. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  279. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  280. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  281. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  282. package/templates/app/skeleton/README.md +6 -6
  283. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  284. package/templates/app/skeleton-lite/.editorconfig +16 -0
  285. package/templates/app/skeleton-lite/.gitattributes +8 -0
  286. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  287. package/templates/app/skeleton-lite/.nvmrc +1 -0
  288. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  289. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +193 -0
  290. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  291. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  292. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  293. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  294. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  295. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  296. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  297. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  298. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  299. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  300. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  301. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  302. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  303. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  304. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  305. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  306. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  307. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  308. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  309. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  310. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  311. package/templates/app/skeleton-lite/.starciwork/workspace.yaml +9 -0
  312. package/templates/app/skeleton-lite/README.md +8 -4
  313. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  314. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  315. package/templates/app/starciwork.gitignore-lite +6 -0
  316. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  317. package/templates/be/image/worker/Dockerfile +1 -1
  318. package/templates/be/inbox/database-client.spec.ts.tpl +22 -28
  319. package/templates/be/inbox/database-client.ts.tpl +14 -28
  320. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  321. package/templates/be/inbox/event-index.ts.tpl +1 -0
  322. package/templates/be/inbox/events.json.tpl +10 -0
  323. package/templates/be/inbox/migration.sql.tpl +1 -0
  324. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  325. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  326. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  327. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  328. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  329. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  330. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  331. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  332. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  333. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  334. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  335. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  336. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  337. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  338. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  339. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  340. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  341. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  342. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  343. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  344. package/templates/be/inbox/service.spec.ts.tpl +49 -20
  345. package/templates/be/inbox/service.ts.tpl +27 -16
  346. package/templates/be/inbox/sql.ts.tpl +2 -2
  347. package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -3
  348. package/templates/be/inbox/webhook-door.ts.tpl +4 -3
  349. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  350. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  351. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  352. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  353. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  354. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  355. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  356. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  357. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  358. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  359. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  360. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  362. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  363. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  364. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  365. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  366. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  367. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  368. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  369. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  370. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  371. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  372. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  373. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  374. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  375. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  376. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  377. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  378. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  379. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  380. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  381. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  382. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  383. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  384. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  385. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  386. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  387. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  388. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  389. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  390. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  391. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  392. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  393. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  394. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  395. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  396. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +3 -3
  397. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +3 -3
  398. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  399. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  400. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  401. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  402. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  403. package/templates/be/skeleton/apps/__app__/src/__app__.env.ts +21 -0
  404. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +1 -1
  405. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -45
  406. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  407. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  408. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +44 -0
  409. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  410. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  411. package/templates/be/skeleton/apps/__app__/src/main.ts +18 -10
  412. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  413. package/templates/be/skeleton/apps/cli/src/cli.env.ts +13 -0
  414. package/templates/be/skeleton/apps/cli/src/cli.options.ts +7 -17
  415. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  416. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  417. package/templates/be/skeleton/apps/cli/src/main.ts +3 -2
  418. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  419. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  420. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  421. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  422. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  423. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  424. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  425. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  426. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  427. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  428. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  429. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  430. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  431. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  432. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  433. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  434. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  435. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  436. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  437. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  438. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  439. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  440. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  441. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  442. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  443. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  444. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  445. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  446. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  447. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  448. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  449. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  450. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +6 -6
  451. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  452. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  453. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  454. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  455. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  456. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  457. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  458. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  459. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  460. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  461. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  462. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  463. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  464. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  465. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  466. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  467. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  468. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  469. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  470. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  471. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  472. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  473. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  474. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  475. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  476. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  477. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  478. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  479. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  480. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  481. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  482. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  483. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  484. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  485. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  486. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  487. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  488. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  489. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +25 -0
  490. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +1 -1
  491. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +18 -58
  492. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  493. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  494. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  495. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +47 -0
  496. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  497. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  498. package/templates/be/skeleton-lite/apps/api/src/main.ts +5 -13
  499. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +17 -0
  500. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  501. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  502. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  503. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  504. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  505. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  506. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  507. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  508. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  509. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  510. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  511. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  512. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  513. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  514. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  515. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  516. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  517. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  518. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  519. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  520. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  521. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  522. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  523. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  524. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  525. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  526. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  527. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  528. package/templates/be/table/migration.sql.tpl +1 -0
  529. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  530. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  531. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  532. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  533. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  534. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  535. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  536. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  537. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  538. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  539. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  540. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  541. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  542. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  543. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  544. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  545. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  546. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  547. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  548. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  549. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  550. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  551. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  552. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  553. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  554. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  555. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  556. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  557. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  558. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  559. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  560. package/upgrade/check.mjs +3 -1
  561. package/upgrade/file-state.mjs +68 -0
  562. package/upgrade/index.mjs +58 -94
  563. package/runtime/scripts/api/git/log.mjs +0 -5
  564. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +0 -55
@@ -0,0 +1,101 @@
1
+ // Value flow for R280: the expressions an identifier, property or destructured element can hold,
2
+ // followed through variables, parameters (state.arguments), array callbacks, for-of and literal tables.
3
+ import { nameText as nameOf } from '../../lib/ts-ast.mjs';
4
+ import { unwrapCatalogExpression as unwrap } from '../../lib/ts-analysis.mjs';
5
+
6
+ const ARRAY_CALLBACK = /^(?:map|flatMap|forEach|filter|find|some|every)$/u;
7
+
8
+ export const symbolAt = (state, node) => {
9
+ const symbol = state.checker.getSymbolAtLocation(node);
10
+ return symbol?.flags === state.ts.SymbolFlags.Alias ? state.checker.getAliasedSymbol(symbol) : symbol;
11
+ };
12
+
13
+ /** The expression bound to `name` in an object literal or a JSX attribute list. */
14
+ function propertyValues(state, owner, name) {
15
+ const { ts } = state;
16
+ if (ts.isObjectLiteralExpression(owner)) return owner.properties.filter(entry => ts.isPropertyAssignment(entry) && nameOf(ts, entry.name) === name).map(entry => entry.initializer);
17
+ if (!ts.isJsxAttributes(owner)) return [];
18
+ const attributes = owner.properties.filter(entry => ts.isJsxAttribute(entry) && entry.name.text === name && entry.initializer);
19
+ return attributes.map(entry => ts.isJsxExpression(entry.initializer) ? entry.initializer.expression : entry.initializer).filter(Boolean);
20
+ }
21
+
22
+ /** `TABLE[key]`: the entry of a literal key, else every value of the table (any of them can be read). */
23
+ function tableValues(state, node, seen) {
24
+ const { ts } = state;
25
+ const literal = ts.isStringLiteralLike(node.argumentExpression) ? node.argumentExpression.text : null;
26
+ return valuesOf(state, node.expression, seen).flatMap(owner => {
27
+ if (ts.isArrayLiteralExpression(owner)) return [...owner.elements];
28
+ if (!ts.isObjectLiteralExpression(owner)) return [];
29
+ if (literal !== null) return propertyValues(state, owner, literal);
30
+ return owner.properties.filter(entry => ts.isPropertyAssignment(entry)).map(entry => entry.initializer);
31
+ });
32
+ }
33
+
34
+ const resolveAll = (state, nodes, seen) => nodes.flatMap(node => valuesOf(state, node, seen));
35
+
36
+ function elementsOf(state, expression, seen) {
37
+ return valuesOf(state, expression, seen).flatMap(owner => state.ts.isArrayLiteralExpression(owner) ? [...owner.elements] : []);
38
+ }
39
+
40
+ function parameterValues(state, declaration, seen) {
41
+ const { ts } = state;
42
+ const callback = declaration.parent;
43
+ const call = callback.parent;
44
+ if (ts.isCallExpression(call) && call.arguments[0] === callback && callback.parameters[0] === declaration
45
+ && ts.isPropertyAccessExpression(call.expression) && ARRAY_CALLBACK.test(call.expression.name.text)) {
46
+ return resolveAll(state, elementsOf(state, call.expression.expression, seen), seen);
47
+ }
48
+ const key = ts.isIdentifier(declaration.name) ? state.checker.getSymbolAtLocation(declaration.name) : declaration;
49
+ return resolveAll(state, state.arguments.get(key) ?? [], seen);
50
+ }
51
+
52
+ function bindingValues(state, declaration, seen) {
53
+ const { ts } = state;
54
+ const pattern = declaration.parent;
55
+ if (declaration.dotDotDotToken) return [];
56
+ const owners = declarationValues(state, pattern.parent, seen);
57
+ if (ts.isObjectBindingPattern(pattern)) {
58
+ const name = nameOf(ts, declaration.propertyName ?? declaration.name);
59
+ return resolveAll(state, owners.flatMap(owner => propertyValues(state, owner, name)), seen);
60
+ }
61
+ const index = pattern.elements.indexOf(declaration);
62
+ return resolveAll(state, owners.flatMap(owner => ts.isArrayLiteralExpression(owner) && owner.elements[index] ? [owner.elements[index]] : []), seen);
63
+ }
64
+
65
+ function declarationValues(state, declaration, seen) {
66
+ const { ts } = state;
67
+ if (ts.isParameter(declaration)) return parameterValues(state, declaration, seen);
68
+ if (ts.isBindingElement(declaration)) return bindingValues(state, declaration, seen);
69
+ if (!ts.isVariableDeclaration(declaration)) return [];
70
+ const loop = declaration.parent?.parent;
71
+ if (loop && ts.isForOfStatement(loop)) return resolveAll(state, elementsOf(state, loop.expression, seen), seen);
72
+ return declaration.initializer ? valuesOf(state, declaration.initializer, seen) : [];
73
+ }
74
+
75
+ /** Leaf expressions `expression` may evaluate to. A translator variable and any unresolvable expression is its own leaf. */
76
+ export function valuesOf(state, expression, visited = new Set()) {
77
+ const { ts } = state;
78
+ const node = unwrap(ts, expression);
79
+ if (!node || visited.has(node)) return [];
80
+ const seen = new Set([...visited, node]);
81
+ if (ts.isConditionalExpression(node)) return [...valuesOf(state, node.whenTrue, seen), ...valuesOf(state, node.whenFalse, seen)];
82
+ if (ts.isPropertyAccessExpression(node)) return resolveAll(state, valuesOf(state, node.expression, seen).flatMap(owner => propertyValues(state, owner, node.name.text)), seen);
83
+ if (ts.isElementAccessExpression(node)) return resolveAll(state, tableValues(state, node, seen), seen);
84
+ if (!ts.isIdentifier(node)) return [node];
85
+ const symbol = symbolAt(state, node);
86
+ if (!symbol?.valueDeclaration || state.translators.has(symbol)) return [node];
87
+ return declarationValues(state, symbol.valueDeclaration, seen);
88
+ }
89
+
90
+ /** The expressions a function returns: its expression body, or the operand of each return outside nested functions. */
91
+ export function returnedExpressions(ts, fn) {
92
+ if (!fn.body) return [];
93
+ if (!ts.isBlock(fn.body)) return [fn.body];
94
+ const found = [];
95
+ const visit = node => {
96
+ if (ts.isReturnStatement(node) && node.expression) found.push(node.expression);
97
+ if (!ts.isFunctionLike(node)) ts.forEachChild(node, visit);
98
+ };
99
+ ts.forEachChild(fn.body, visit);
100
+ return found;
101
+ }
@@ -0,0 +1,77 @@
1
+ // R280 safety net: the app-owned ledger of catalog keys the detector cannot see.
2
+ // A side (fe or be) declares them in <side>/i18n-dynamic-keys.yaml; a key the ledger names counts as read, a stale
3
+ // or malformed ledger is itself an HFS_UNUSED_SYMBOL finding, so the ledger cannot rot.
4
+ import { isPlainObject } from '../../../engine/plain-object.mjs';
5
+ import { parseYaml } from '../../../engine/yaml.mjs';
6
+ import { found, readText } from './read.mjs';
7
+
8
+ const LEDGER_FILE = 'i18n-dynamic-keys.yaml';
9
+ export const LEDGER_SCHEMA = 'starci/i18n-dynamic-keys@1';
10
+ const CODE = 'HFS_UNUSED_SYMBOL';
11
+ const MATCHERS = ['key', 'prefix', 'suffix'];
12
+ const FIELDS = new Set([...MATCHERS, 'reason']);
13
+ const SIDES = new Set(['fe', 'be']);
14
+ const text = value => typeof value === 'string' && value.trim() !== '';
15
+ const describe = entry => `${entry.kind} ${JSON.stringify(entry.value)}`;
16
+
17
+ /** The catalog namespace (the dotted path before the leaf; '' when none) and the leaf of a candidate name. */
18
+ function splitCatalogName(name) {
19
+ const at = name.lastIndexOf('.');
20
+ return at < 0 ? { namespace: '', leaf: name } : { namespace: name.slice(0, at), leaf: name.slice(at + 1) };
21
+ }
22
+
23
+ const MATCH = { key: (leaf, value) => leaf === value, prefix: (leaf, value) => leaf.startsWith(value), suffix: (leaf, value) => leaf.endsWith(value) };
24
+ const matches = (entry, leaf) => MATCH[entry.kind](leaf, entry.value);
25
+
26
+ function entryProblem(entry) {
27
+ if (!isPlainObject(entry)) return 'is not a mapping';
28
+ const unknown = Object.keys(entry).filter(field => !FIELDS.has(field));
29
+ if (unknown.length) return `has unknown field ${unknown.map(field => JSON.stringify(field)).join(', ')} (allowed: key, prefix, suffix, reason)`;
30
+ const given = MATCHERS.filter(field => field in entry);
31
+ if (given.length !== 1) return `must have exactly one of key, prefix or suffix (has ${given.length ? given.join(' and ') : 'none'})`;
32
+ if (!text(entry[given[0]])) return `${given[0]} must be a non-empty string`;
33
+ if (!text(entry.reason)) return 'must have a non-empty string reason';
34
+ return null;
35
+ }
36
+
37
+ /** Parse the ledger text: `{ entries, problems }`; a problem is one precise message about the file's shape. */
38
+ function parseLedger(source) {
39
+ let doc;
40
+ try { doc = parseYaml(source); } catch { return { entries: [], problems: ['is not valid YAML'] }; }
41
+ if (!isPlainObject(doc)) return { entries: [], problems: ['must be a mapping with schema and namespaces'] };
42
+ const problems = [];
43
+ for (const field of Object.keys(doc)) if (!['schema', 'namespaces'].includes(field)) problems.push(`has unknown top-level field ${JSON.stringify(field)}`);
44
+ if (doc.schema !== LEDGER_SCHEMA) problems.push(`schema must be ${LEDGER_SCHEMA}`);
45
+ if (!isPlainObject(doc.namespaces)) { problems.push('namespaces must be a mapping of namespace to a list of entries'); return { entries: [], problems }; }
46
+ const entries = [];
47
+ for (const [namespace, list] of Object.entries(doc.namespaces)) {
48
+ if (!Array.isArray(list)) { problems.push(`namespace ${JSON.stringify(namespace)} must be a list of entries`); continue; }
49
+ list.forEach((entry, index) => {
50
+ const problem = entryProblem(entry);
51
+ if (problem) { problems.push(`namespace ${JSON.stringify(namespace)} entry ${index + 1} ${problem}`); return; }
52
+ const kind = MATCHERS.find(field => field in entry);
53
+ entries.push({ namespace, kind, value: entry[kind] });
54
+ });
55
+ }
56
+ return { entries, problems };
57
+ }
58
+
59
+ /**
60
+ * Mark every catalog key the side's ledger names as read, and return the ledger's own findings (malformed, or an entry
61
+ * matching no catalog key). No file means no ledger. Only a fe side reads its fe ledger and a be side its be ledger.
62
+ */
63
+ export function applyDynamicKeysLedger(state, profile) {
64
+ if (!SIDES.has(profile)) return [];
65
+ const source = readText(state.repoRoot, LEDGER_FILE);
66
+ if (source === null) return [];
67
+ const { entries, problems } = parseLedger(source);
68
+ const own = (message, name) => found(CODE, LEDGER_FILE, `${LEDGER_FILE}: ${message}`, { line: 1, column: 1, kind: 'dynamic-keys ledger', name });
69
+ const findings = problems.map(problem => own(`malformed dynamic-keys ledger: ${problem}.`, 'ledger'));
70
+ const catalog = state.candidates.filter(candidate => candidate.kind === 'catalog key').map(candidate => ({ candidate, ...splitCatalogName(candidate.name) }));
71
+ for (const entry of entries) {
72
+ const hits = catalog.filter(({ namespace, leaf }) => namespace === entry.namespace && matches(entry, leaf));
73
+ for (const { candidate } of hits) candidate.used = true;
74
+ if (!hits.length) findings.push(own(`dynamic-keys ledger entry ${describe(entry)} in namespace ${JSON.stringify(entry.namespace)} matches no catalog key; delete it.`, `${entry.namespace}:${describe(entry)}`));
75
+ }
76
+ return findings;
77
+ }
@@ -0,0 +1,165 @@
1
+ // Translators of R280: which catalog namespace a `t(...)` call reads, followed through destructuring and props.
2
+ import { nameText } from '../../lib/ts-ast.mjs';
3
+ import { unwrapCatalogExpression as unwrap } from '../../lib/ts-analysis.mjs';
4
+ import { callable, staticStrings } from './unused-symbol-values.mjs';
5
+ import { returnedExpressions, symbolAt, valuesOf } from './unused-symbol-flow.mjs';
6
+
7
+ const TRANSLATOR_NAME = /^(?:t|translate)$/u;
8
+
9
+ function translationFactories(ts, source) {
10
+ const factories = new Set(['useTranslations', 'getTranslations']);
11
+ for (const statement of source.statements) {
12
+ if (!ts.isImportDeclaration(statement) || !/^(?:next-intl|next-intl\/server)$/u.test(statement.moduleSpecifier.text)) continue;
13
+ const named = statement.importClause?.namedBindings;
14
+ if (named && ts.isNamedImports(named)) for (const element of named.elements) {
15
+ if (factories.has((element.propertyName ?? element.name).text)) factories.add(element.name.text);
16
+ }
17
+ }
18
+ return factories;
19
+ }
20
+
21
+ function translatorNamespace(state, initializer, factories) {
22
+ const { ts } = state;
23
+ let init = unwrap(ts, initializer);
24
+ if (init && ts.isAwaitExpression(init)) init = unwrap(ts, init.expression);
25
+ if (!init || !ts.isCallExpression(init) || !ts.isIdentifier(init.expression) || !factories.has(init.expression.text)) return null;
26
+ let first = init.arguments[0];
27
+ if (first && ts.isObjectLiteralExpression(first)) first = first.properties.find(property => nameText(ts, property.name, { numbers: true }) === 'namespace')?.initializer;
28
+ return first ? staticStrings(state, first) : [''];
29
+ }
30
+
31
+ /** `Promise.all([...])` and a plain array literal hand each element to the binding at the same index. */
32
+ function translatorList(ts, initializer) {
33
+ let list = unwrap(ts, initializer);
34
+ if (list && ts.isAwaitExpression(list)) list = unwrap(ts, list.expression);
35
+ if (list && ts.isCallExpression(list) && ts.isPropertyAccessExpression(list.expression) && list.expression.name.text === 'all') list = unwrap(ts, list.arguments[0]);
36
+ return list && ts.isArrayLiteralExpression(list) ? list.elements : [];
37
+ }
38
+
39
+ const isFunction = (ts, node) => Boolean(node) && (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node));
40
+
41
+ /** A function whose result is a call on a bound translator with its own first parameter is that translator. */
42
+ function forwardedNamespaces(state, fn) {
43
+ const { ts, checker } = state;
44
+ const parameter = fn.parameters[0];
45
+ if (!parameter || !ts.isIdentifier(parameter.name)) return [];
46
+ const own = checker.getSymbolAtLocation(parameter.name);
47
+ return returnedExpressions(ts, fn).flatMap(expression => {
48
+ const call = unwrap(ts, expression);
49
+ const first = call && ts.isCallExpression(call) ? unwrap(ts, call.arguments[0]) : null;
50
+ if (!first || !ts.isIdentifier(first) || checker.getSymbolAtLocation(first) !== own) return [];
51
+ return receiverNamespaces(state, call.expression);
52
+ });
53
+ }
54
+
55
+ /** useCallback(fn, deps) and useMemo(() => fn, deps) are the function fn. */
56
+ function transparentFunction(state, expression) {
57
+ const { ts } = state;
58
+ const node = unwrap(ts, expression);
59
+ if (!node || !ts.isCallExpression(node)) return node;
60
+ const name = leafName(ts, node.expression);
61
+ const inner = /^(?:useCallback|useMemo)$/u.test(name) ? unwrap(ts, node.arguments[0]) : null;
62
+ if (!inner) return node;
63
+ if (name === 'useCallback') return inner;
64
+ if (!isFunction(ts, inner)) return node;
65
+ return returnedExpressions(ts, inner).map(returned => unwrap(ts, returned)).find(candidate => isFunction(ts, candidate)) ?? node;
66
+ }
67
+
68
+ /** A hook returns such a forwarding function, bare or wrapped in useCallback or useMemo. */
69
+ function hookNamespaces(state, hook) {
70
+ const { ts } = state;
71
+ return returnedExpressions(ts, hook).flatMap(expression => {
72
+ const inner = transparentFunction(state, expression);
73
+ return isFunction(ts, inner) ? forwardedNamespaces(state, inner) : [];
74
+ });
75
+ }
76
+
77
+ function wrapperNamespaces(state, initializer) {
78
+ const { ts } = state;
79
+ let init = transparentFunction(state, initializer);
80
+ if (init && ts.isAwaitExpression(init)) init = unwrap(ts, init.expression);
81
+ if (isFunction(ts, init)) return forwardedNamespaces(state, init);
82
+ const target = init && ts.isCallExpression(init) ? callable(state, init.expression) : null;
83
+ return isFunction(ts, target) ? hookNamespaces(state, target) : [];
84
+ }
85
+
86
+ function bindTranslator(state, name, initializer, factories) {
87
+ const { ts, checker } = state;
88
+ if (ts.isIdentifier(name)) {
89
+ const namespaces = translatorNamespace(state, initializer, factories) ?? wrapperNamespaces(state, initializer);
90
+ if (namespaces?.length) state.translators.set(checker.getSymbolAtLocation(name), namespaces);
91
+ } else if (ts.isArrayBindingPattern(name)) {
92
+ const elements = translatorList(ts, initializer);
93
+ name.elements.forEach((element, index) => { if (ts.isBindingElement(element) && elements[index]) bindTranslator(state, element.name, elements[index], factories); });
94
+ }
95
+ }
96
+
97
+ /** Wrappers depend on translators bound in other modules, so bind until nothing new appears. */
98
+ export function translationBindings(state, sources) {
99
+ const factories = new Map([...sources.values()].map(source => [source, translationFactories(state.ts, source)]));
100
+ for (let pass = 0, size = -1; pass < 4 && size !== state.translators.size; pass++) {
101
+ size = state.translators.size;
102
+ for (const source of sources.values()) {
103
+ const visit = node => {
104
+ if (state.ts.isVariableDeclaration(node) && node.initializer) bindTranslator(state, node.name, node.initializer, factories.get(source));
105
+ state.ts.forEachChild(node, visit);
106
+ };
107
+ visit(source);
108
+ }
109
+ }
110
+ }
111
+
112
+ /** Namespaces of the translators a receiver can be: declared here, or handed in through arguments and props. */
113
+ function receiverNamespaces(state, receiver) {
114
+ const { ts, checker } = state;
115
+ const direct = state.translators.get(checker.getSymbolAtLocation(receiver));
116
+ if (direct !== undefined) return direct;
117
+ return valuesOf(state, receiver).flatMap(leaf => {
118
+ // A translator factory called in place as an argument: f(useTranslations("admin")).
119
+ if (ts.isCallExpression(leaf)) return translatorNamespace(state, leaf, translationFactories(ts, leaf.getSourceFile())) ?? [];
120
+ return ts.isIdentifier(leaf) ? state.translators.get(checker.getSymbolAtLocation(leaf)) ?? [] : [];
121
+ });
122
+ }
123
+
124
+ function handedIn(state, receiver) {
125
+ const { ts } = state;
126
+ let root = receiver;
127
+ while (ts.isPropertyAccessExpression(root)) root = root.expression;
128
+ const declaration = ts.isIdentifier(root) ? symbolAt(state, root)?.valueDeclaration : null;
129
+ return Boolean(declaration) && (ts.isParameter(declaration) || ts.isBindingElement(declaration));
130
+ }
131
+
132
+ function leafName(ts, node) {
133
+ if (ts.isPropertyAccessExpression(node)) return node.name.text;
134
+ return ts.isIdentifier(node) ? node.text : '';
135
+ }
136
+
137
+ /** A callback typed (string, ...) => string: the shape of a translator whatever its name. */
138
+ function translatorShaped(state, receiver) {
139
+ const { checker } = state;
140
+ const [signature] = checker.getTypeAtLocation(receiver).getCallSignatures();
141
+ const first = signature?.getParameters()[0];
142
+ if (!first) return false;
143
+ const isString = type => Boolean(type.flags & state.ts.TypeFlags.String);
144
+ return isString(checker.getTypeOfSymbolAtLocation(first, receiver)) && isString(signature.getReturnType());
145
+ }
146
+
147
+ /**
148
+ * Namespaces read by a call of `callee`; `null` stands for a translator handed in whose namespace cannot be followed
149
+ * (R106 reads such a key by its leaf), and no result means the call is no catalog read.
150
+ */
151
+ export function catalogNamespace(state, callee) {
152
+ const { ts } = state;
153
+ let receiver = callee;
154
+ if (ts.isPropertyAccessExpression(callee) && /^(?:rich|raw|markup)$/u.test(callee.name.text)) receiver = callee.expression;
155
+ const resolved = receiverNamespaces(state, receiver);
156
+ if (resolved.length) return resolved;
157
+ if (handedIn(state, receiver) && (TRANSLATOR_NAME.test(leafName(ts, receiver)) || translatorShaped(state, receiver))) return [null];
158
+ if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'get') {
159
+ const owner = callee.expression;
160
+ const name = nameText(ts, ts.isPropertyAccessExpression(owner) ? owner.name : owner, { numbers: true }) ?? '';
161
+ if (/message|catalog/iu.test(name)) return [''];
162
+ }
163
+ if (ts.isIdentifier(callee) && TRANSLATOR_NAME.test(callee.text)) return [''];
164
+ return [];
165
+ }
@@ -0,0 +1,103 @@
1
+ // Static values used at runtime by catalog consumers. A declaration alone is never a read.
2
+ import path from 'node:path';
3
+ import { unwrapCatalogExpression as unwrap } from '../../lib/ts-analysis.mjs';
4
+ import { symbolAt, valuesOf } from './unused-symbol-flow.mjs';
5
+
6
+ const JSON_CATALOG = /(?:^|\/)(?:messages|locales)\/[^/]+\.json$|\.catalog\.json$/u;
7
+
8
+ export function callable(state, node) {
9
+ const declaration = symbolAt(state, node)?.valueDeclaration;
10
+ if (declaration && state.ts.isVariableDeclaration(declaration)) return unwrap(state.ts, declaration.initializer);
11
+ return declaration;
12
+ }
13
+
14
+ function bindArgument(state, parameter, argument) {
15
+ if (!parameter || !argument) return;
16
+ const key = state.ts.isIdentifier(parameter.name) ? state.checker.getSymbolAtLocation(parameter.name) : parameter;
17
+ state.arguments.set(key, [...state.arguments.get(key) ?? [], argument]);
18
+ }
19
+
20
+ /** Record the actual arguments of local functions, including imported functions and arrow exports. */
21
+ export function argumentBindings(state, sources) {
22
+ for (const source of sources.values()) {
23
+ const visit = node => {
24
+ if (state.ts.isCallExpression(node)) {
25
+ const target = callable(state, node.expression);
26
+ for (const [index, parameter] of (target?.parameters ?? []).entries()) bindArgument(state, parameter, node.arguments[index]);
27
+ } else if (state.ts.isJsxSelfClosingElement(node) || state.ts.isJsxOpeningElement(node)) {
28
+ // A component's props parameter receives the attribute list of every element that renders it.
29
+ bindArgument(state, callable(state, node.tagName)?.parameters?.[0], node.attributes);
30
+ }
31
+ state.ts.forEachChild(node, visit);
32
+ };
33
+ visit(source);
34
+ }
35
+ }
36
+
37
+ function typeStrings(state, node) {
38
+ const type = state.checker.getTypeAtLocation(node);
39
+ const types = type.isUnion() ? type.types : [type];
40
+ return types.length && types.every(part => part.isStringLiteral()) ? types.map(part => part.value) : [];
41
+ }
42
+
43
+ /** Follow finite literals only; a broad string or an unbounded dynamic expression gives no evidence. */
44
+ export function staticStrings(state, expression, visited = new Set()) {
45
+ const { ts } = state;
46
+ const node = unwrap(ts, expression);
47
+ if (!node || visited.has(node)) return [];
48
+ const seen = new Set([...visited, node]);
49
+ if (ts.isStringLiteralLike(node)) return [node.text];
50
+ if (ts.isConditionalExpression(node)) return [...staticStrings(state, node.whenTrue, seen), ...staticStrings(state, node.whenFalse, seen)];
51
+ const typed = typeStrings(state, node);
52
+ if (typed.length) return typed;
53
+ const declaration = symbolAt(state, node)?.valueDeclaration;
54
+ if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer) return staticStrings(state, declaration.initializer, seen);
55
+ const bound = declaration && ts.isParameter(declaration) ? state.arguments.get(symbolAt(state, node)) ?? [] : [];
56
+ if (bound.length) return bound.flatMap(argument => staticStrings(state, argument, seen));
57
+ // Tables, tuples, nested maps, for-of and destructured elements resolve to the literals they hold.
58
+ return valuesOf(state, node, visited).flatMap(leaf => leaf === node ? [] : staticStrings(state, leaf, seen));
59
+ }
60
+
61
+ /** JSON imports are catalog owners; their runtime member reads survive named aliases and re-exports. */
62
+ export function jsonCatalogBindings(state, sources, files) {
63
+ const tracked = new Set(files);
64
+ for (const source of sources.values()) for (const statement of source.statements) {
65
+ if (!state.ts.isImportDeclaration(statement) || !statement.importClause?.name) continue;
66
+ const specifier = statement.moduleSpecifier.text;
67
+ if (!specifier?.endsWith('.json')) continue;
68
+ const target = state.resolveModule(specifier, source.fileName)?.resolvedFileName;
69
+ if (!target) continue;
70
+ const relative = path.relative(state.repoRoot, target).replaceAll('\\', '/');
71
+ if (!tracked.has(relative) || !JSON_CATALOG.test(relative)) continue;
72
+ state.catalogObjects.set(state.checker.getSymbolAtLocation(statement.importClause.name), '');
73
+ }
74
+ }
75
+
76
+ function catalogObjectPath(state, expression, seen = new Set()) {
77
+ const { ts } = state;
78
+ const node = unwrap(ts, expression);
79
+ if (!node || seen.has(node)) return null;
80
+ seen.add(node);
81
+ if (ts.isPropertyAccessExpression(node)) {
82
+ const prefix = catalogObjectPath(state, node.expression, seen);
83
+ return prefix === null ? null : `${prefix}${node.name.text}.`;
84
+ }
85
+ if (ts.isElementAccessExpression(node) && ts.isStringLiteralLike(node.argumentExpression)) {
86
+ const prefix = catalogObjectPath(state, node.expression, seen);
87
+ return prefix === null ? null : `${prefix}${node.argumentExpression.text}.`;
88
+ }
89
+ const direct = state.catalogObjects.get(state.checker.getSymbolAtLocation(node));
90
+ if (direct !== undefined) return direct;
91
+ const declaration = symbolAt(state, node)?.valueDeclaration;
92
+ return declaration && ts.isVariableDeclaration(declaration) ? catalogObjectPath(state, declaration.initializer, seen) : null;
93
+ }
94
+
95
+ export function jsonCatalogRead(state, node) {
96
+ if (!state.ts.isPropertyAccessExpression(node) && !state.ts.isElementAccessExpression(node)) return;
97
+ const key = catalogObjectPath(state, node);
98
+ if (key !== null) state.catalogReads.add(key.slice(0, -1));
99
+ if (state.ts.isElementAccessExpression(node) && !state.ts.isStringLiteralLike(node.argumentExpression)) {
100
+ const base = catalogObjectPath(state, node.expression);
101
+ if (base !== null) for (const value of staticStrings(state, node.argumentExpression)) state.catalogReads.add(`${base}${value}`);
102
+ }
103
+ }