@starci/hfs 4.0.8 → 4.0.9

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 (667) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +81 -39
  3. package/emit/contracts.mjs +56 -9
  4. package/emit/db-types.mjs +169 -0
  5. package/emit/events.mjs +41 -0
  6. package/emit/operations-worker.mjs +1 -1
  7. package/emit/operations.mjs +2 -2
  8. package/emit/schema-worker.mjs +1 -1
  9. package/lint/bound-sys.cjs +50 -0
  10. package/lint/run.mjs +53 -19
  11. package/package.json +14 -9
  12. package/report/sonar.mjs +8 -8
  13. package/runtime/engine/config.mjs +494 -0
  14. package/runtime/engine/invalid-config.mjs +15 -0
  15. package/runtime/engine/orca-config.mjs +27 -0
  16. package/runtime/knowledge/hfs/canon-pins.yaml +160 -29
  17. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
  18. package/runtime/knowledge/hfs/rules.yaml +2059 -0
  19. package/runtime/knowledge/hfs/slots.yaml +912 -120
  20. package/runtime/knowledge/patterns/be/api.yaml +369 -0
  21. package/runtime/knowledge/patterns/be/cli.yaml +297 -0
  22. package/runtime/knowledge/patterns/be/domain.yaml +113 -0
  23. package/runtime/knowledge/patterns/be/event-bus.yaml +285 -0
  24. package/runtime/knowledge/patterns/be/jobs.yaml +261 -0
  25. package/runtime/knowledge/patterns/be/projections.yaml +178 -0
  26. package/runtime/knowledge/patterns/be/queues.yaml +236 -0
  27. package/runtime/knowledge/patterns/be/reactors.yaml +140 -0
  28. package/runtime/knowledge/patterns/be/realtime.yaml +222 -0
  29. package/runtime/knowledge/patterns/be/saga.yaml +248 -0
  30. package/runtime/knowledge/patterns/be/webhooks.yaml +221 -0
  31. package/runtime/knowledge/patterns/fe/folder.yaml +131 -25
  32. package/runtime/knowledge/sonar-gate.yaml +52 -18
  33. package/runtime/modules/kernel/failure-codes.yaml +571 -51
  34. package/runtime/modules/models/registry.yaml +591 -0
  35. package/runtime/modules/models/runtimes.yaml +701 -0
  36. package/runtime/scripts/api/fs/forbidden-root.mjs +51 -0
  37. package/runtime/scripts/api/fs/is-link-like.mjs +25 -0
  38. package/runtime/scripts/api/fs/lib.mjs +10 -2
  39. package/runtime/scripts/api/fs/safe-remove.mjs +12 -132
  40. package/runtime/scripts/api/git/config-get.mjs +10 -0
  41. package/runtime/scripts/api/git/diff.mjs +5 -0
  42. package/runtime/scripts/api/git/lib.mjs +16 -26
  43. package/runtime/scripts/api/git/log.mjs +5 -0
  44. package/runtime/scripts/api/git/ls-files.mjs +6 -0
  45. package/runtime/scripts/api/git/ls-tree.mjs +5 -0
  46. package/runtime/scripts/api/git/merge-base.mjs +9 -0
  47. package/runtime/scripts/api/git/remote.mjs +5 -0
  48. package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
  49. package/runtime/scripts/api/git/show.mjs +6 -0
  50. package/runtime/scripts/api/sops/decrypt.mjs +9 -5
  51. package/runtime/scripts/api/sops/lib.mjs +35 -0
  52. package/runtime/scripts/api/sops/seal.mjs +18 -0
  53. package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
  54. package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
  55. package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
  56. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
  57. package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
  58. package/runtime/scripts/hfs/architecture/config.mjs +9 -6
  59. package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
  60. package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
  61. package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
  62. package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
  63. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
  64. package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
  65. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +4 -10
  66. package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
  67. package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
  68. package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
  69. package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
  70. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +1 -1
  71. package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
  72. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  73. package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
  74. package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
  75. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
  76. package/runtime/scripts/hfs/architecture/index.mjs +17 -4
  77. package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
  78. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
  79. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
  80. package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
  81. package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
  82. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
  83. package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
  84. package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
  85. package/runtime/scripts/hfs/architecture/schema-owner.mjs +17 -114
  86. package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
  87. package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
  88. package/runtime/scripts/hfs/architecture/source-names.mjs +27 -147
  89. package/runtime/scripts/hfs/architecture/sql-owner.mjs +59 -18
  90. package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
  91. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
  92. package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
  93. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
  94. package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
  95. package/runtime/scripts/hfs/architecture/surface.mjs +9 -3
  96. package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
  97. package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
  98. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
  99. package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
  100. package/runtime/scripts/hfs/architecture.mjs +4 -4
  101. package/runtime/scripts/hfs/check.mjs +75 -72
  102. package/runtime/scripts/hfs/coverage-scope.mjs +162 -0
  103. package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
  104. package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
  105. package/runtime/scripts/hfs/edition-slots.mjs +66 -0
  106. package/runtime/scripts/hfs/manifest-shape.mjs +109 -32
  107. package/runtime/scripts/hfs/param-names.mjs +18 -0
  108. package/runtime/scripts/hfs/path-findings.mjs +1 -1
  109. package/runtime/scripts/hfs/repo-identity.mjs +9 -6
  110. package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
  111. package/runtime/scripts/hfs/rules/app-root.mjs +51 -0
  112. package/runtime/scripts/hfs/rules/cli.mjs +37 -0
  113. package/runtime/scripts/hfs/rules/contract-compat.mjs +71 -0
  114. package/runtime/scripts/hfs/rules/contract.mjs +10 -9
  115. package/runtime/scripts/hfs/rules/database-config.mjs +102 -0
  116. package/runtime/scripts/hfs/rules/database-constants.mjs +13 -0
  117. package/runtime/scripts/hfs/rules/database-migrations.mjs +111 -0
  118. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +233 -0
  119. package/runtime/scripts/hfs/rules/database-sql.mjs +336 -0
  120. package/runtime/scripts/hfs/rules/database.mjs +68 -0
  121. package/runtime/scripts/hfs/rules/deps.mjs +1 -1
  122. package/runtime/scripts/hfs/rules/docker.mjs +259 -0
  123. package/runtime/scripts/hfs/rules/edition.mjs +111 -0
  124. package/runtime/scripts/hfs/rules/event-bus.mjs +153 -0
  125. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -1
  126. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
  127. package/runtime/scripts/hfs/rules/frontend-tree.mjs +9 -9
  128. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -6
  129. package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
  130. package/runtime/scripts/hfs/rules/lint-suppression.mjs +1 -1
  131. package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
  132. package/runtime/scripts/hfs/rules/peer-integrations.mjs +3 -3
  133. package/runtime/scripts/hfs/rules/pipeline.mjs +11 -21
  134. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -2
  135. package/runtime/scripts/hfs/rules/read.mjs +1 -1
  136. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +2 -2
  137. package/runtime/scripts/hfs/rules/saga.mjs +170 -0
  138. package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
  139. package/runtime/scripts/hfs/rules/services.mjs +220 -0
  140. package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
  141. package/runtime/scripts/hfs/rules/stacks.mjs +3 -3
  142. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
  143. package/runtime/scripts/hfs/rules/test-topology.mjs +2 -2
  144. package/runtime/scripts/hfs/secret.mjs +175 -0
  145. package/runtime/scripts/hfs/slots.mjs +78 -108
  146. package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
  147. package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
  148. package/runtime/scripts/hfs/tree.mjs +5 -4
  149. package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
  150. package/runtime/scripts/hfs/view.mjs +3 -2
  151. package/runtime/scripts/lib/dockerfile.mjs +124 -0
  152. package/runtime/scripts/lib/env.mjs +10 -0
  153. package/runtime/scripts/lib/event-contract.mjs +103 -0
  154. package/runtime/scripts/lib/git.mjs +36 -0
  155. package/runtime/scripts/lib/graphql-contract.mjs +1 -1
  156. package/runtime/scripts/lib/i18n.mjs +86 -0
  157. package/runtime/scripts/lib/json.mjs +13 -3
  158. package/runtime/scripts/lib/language.mjs +24 -5
  159. package/runtime/scripts/lib/list.mjs +20 -0
  160. package/runtime/scripts/lib/package-at.mjs +37 -0
  161. package/runtime/scripts/lib/path-key.mjs +57 -2
  162. package/runtime/scripts/lib/read-text.mjs +34 -0
  163. package/runtime/scripts/lib/regex.mjs +4 -0
  164. package/runtime/scripts/lib/same-text.mjs +5 -0
  165. package/runtime/scripts/lib/secret-patterns.mjs +4 -3
  166. package/runtime/scripts/lib/sleep-sync.mjs +2 -1
  167. package/runtime/scripts/lib/sops-envelope.mjs +58 -0
  168. package/runtime/scripts/lib/stack-services.mjs +7 -31
  169. package/runtime/scripts/lib/ts-ast.mjs +35 -0
  170. package/runtime/scripts/lib/walk.mjs +4 -3
  171. package/scaffold/add-app.mjs +174 -0
  172. package/scaffold/add-cli-lite.mjs +257 -0
  173. package/scaffold/add-inbox-lite.mjs +314 -0
  174. package/scaffold/add-table.mjs +344 -0
  175. package/scaffold/add.mjs +299 -0
  176. package/scaffold/app.mjs +358 -69
  177. package/scaffold/edition-gate.mjs +25 -0
  178. package/scaffold/image.mjs +19 -0
  179. package/scaffold/lite-exports.mjs +186 -0
  180. package/scaffold/service.mjs +32 -26
  181. package/scaffold/supabase-ports.mjs +26 -0
  182. package/src/main.mjs +327 -0
  183. package/sync/cli.mjs +6 -9
  184. package/sync/format.mjs +1 -1
  185. package/sync/hygiene.mjs +10 -10
  186. package/sync/index.mjs +167 -104
  187. package/sync/managed.mjs +19 -11
  188. package/sync/sonar-key.mjs +5 -0
  189. package/templates/app/ci-workflows/github/workflows/ci.yml +14 -14
  190. package/templates/app/ci-workflows/github/workflows/images.yml +37 -0
  191. package/templates/app/ci-workflows/sonar-steps.yml +9 -0
  192. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +66 -0
  193. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +31 -0
  194. package/templates/app/ci-workflows-lite/github/workflows/images.yml +37 -0
  195. package/templates/app/codegen-lite/scripts/codegen.mjs +32 -0
  196. package/templates/app/docker-ignore/dockerignore +50 -0
  197. package/templates/app/gitignore-lite +35 -0
  198. package/templates/app/hooks/husky/pre-commit +3 -8
  199. package/templates/app/hooks/husky/pre-push +45 -6
  200. package/templates/app/package-scripts/core.json +8 -0
  201. package/templates/app/package-scripts/package.json +3 -10
  202. package/templates/app/package-scripts-lite/package.json +15 -0
  203. package/templates/app/quality-config/codecov.yml +15 -3
  204. package/templates/app/quality-config/sonar-base.properties +4 -0
  205. package/templates/app/quality-config/sonar-project.properties +1 -4
  206. package/templates/app/quality-config-lite/sonar-project.properties +5 -0
  207. package/templates/app/skeleton/.starcistacks/dev/seeds/primary-notes.sql +5 -0
  208. package/templates/app/skeleton/.starciwork/features/system-health/index.yaml +4 -0
  209. package/templates/app/skeleton/.starciwork/{features/index.yaml → index.yaml} +2 -2
  210. package/templates/app/skeleton/README.md +7 -5
  211. package/templates/app/skeleton-lite/.sops.yaml +3 -0
  212. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +5 -0
  213. package/templates/app/skeleton-lite/.starcistacks/dev/secrets/.gitkeep +1 -0
  214. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +55 -0
  215. package/templates/app/skeleton-lite/README.md +40 -0
  216. package/templates/app/skeleton-lite/supabase/config.toml +41 -0
  217. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +56 -0
  218. package/templates/app/skeleton-lite/supabase/seed.sql +0 -0
  219. package/templates/app/task-graph/turbo.json +23 -0
  220. package/templates/app/tool-config-lite/prettierignore +2 -0
  221. package/templates/app/tool-config-lite/prettierrc +1 -0
  222. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +38 -0
  223. package/templates/app/upgrade-full/.starcistacks/dev/README.md +10 -0
  224. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +13 -0
  225. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +3 -0
  226. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +12 -0
  227. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +7 -0
  228. package/templates/be/common/main-listen.ts.partial +14 -0
  229. package/templates/be/common/sql-ident.ts.partial +7 -0
  230. package/templates/be/common/sql-tag.ts.partial +5 -0
  231. package/templates/be/image/api/Dockerfile +38 -0
  232. package/templates/be/image/cli/Dockerfile +38 -0
  233. package/templates/be/image/worker/Dockerfile +36 -0
  234. package/templates/be/inbox/database-client.ts.tpl +50 -0
  235. package/templates/be/inbox/index.ts.tpl +2 -0
  236. package/templates/be/inbox/migration.sql.tpl +29 -0
  237. package/templates/be/inbox/module-definition.ts.tpl +6 -0
  238. package/templates/be/inbox/module.ts.tpl +14 -0
  239. package/templates/be/inbox/options.ts.tpl +2 -0
  240. package/templates/be/inbox/service.ts.tpl +33 -0
  241. package/templates/be/inbox/sql.ts.tpl +16 -0
  242. package/templates/be/inbox/webhook-door.ts.tpl +36 -0
  243. package/templates/be/patterns/api/application.command.http.ts.tpl +10 -0
  244. package/templates/be/patterns/api/application.command.ts.tpl +10 -0
  245. package/templates/be/patterns/api/application.contracts.ts.tpl +11 -0
  246. package/templates/be/patterns/api/application.handler.http.ts.tpl +22 -0
  247. package/templates/be/patterns/api/application.handler.ts.tpl +22 -0
  248. package/templates/be/patterns/api/feature.index.http.ts.tpl +1 -0
  249. package/templates/be/patterns/api/feature.index.ts.tpl +1 -0
  250. package/templates/be/patterns/api/feature.module.ts.tpl +6 -0
  251. package/templates/be/patterns/api/graphql.input.ts.tpl +13 -0
  252. package/templates/be/patterns/api/graphql.mapper.ts.tpl +9 -0
  253. package/templates/be/patterns/api/graphql.module.ts.tpl +7 -0
  254. package/templates/be/patterns/api/graphql.resolver.ts.tpl +27 -0
  255. package/templates/be/patterns/api/graphql.type.ts.tpl +9 -0
  256. package/templates/be/patterns/api/http.controller.ts.tpl +27 -0
  257. package/templates/be/patterns/api/http.mapper.ts.tpl +9 -0
  258. package/templates/be/patterns/api/http.module.ts.tpl +7 -0
  259. package/templates/be/patterns/api/http.request.ts.tpl +10 -0
  260. package/templates/be/patterns/api/http.response.ts.tpl +5 -0
  261. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +15 -0
  262. package/templates/be/patterns/cli/group.cli.ts.tpl +11 -0
  263. package/templates/be/patterns/cli/group.module.ts.tpl +7 -0
  264. package/templates/be/patterns/cli/sub.run.cli.spec.ts.tpl +17 -0
  265. package/templates/be/patterns/cli/sub.run.cli.ts.tpl +15 -0
  266. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +22 -0
  267. package/templates/be/patterns/event-bus/platform/event-bus.config.ts.tpl +19 -0
  268. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +58 -0
  269. package/templates/be/patterns/event-bus/platform/event-bus.decorators.ts.tpl +46 -0
  270. package/templates/be/patterns/event-bus/platform/event-bus.log-events.ts.tpl +12 -0
  271. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +10 -0
  272. package/templates/be/patterns/event-bus/platform/event-bus.module.ts.tpl +52 -0
  273. package/templates/be/patterns/event-bus/platform/event-bus.options.ts.tpl +19 -0
  274. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +28 -0
  275. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +165 -0
  276. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +93 -0
  277. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +90 -0
  278. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +58 -0
  279. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +136 -0
  280. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +56 -0
  281. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +272 -0
  282. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +128 -0
  283. package/templates/be/patterns/event-bus/platform/event-transport.port.ts.tpl +180 -0
  284. package/templates/be/patterns/event-bus/platform/event.policy.spec.ts.tpl +67 -0
  285. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +38 -0
  286. package/templates/be/patterns/event-bus/platform/index.ts.tpl +11 -0
  287. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.spec.ts.tpl +348 -0
  288. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +241 -0
  289. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +15 -0
  290. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +8 -0
  291. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +37 -0
  292. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +11 -0
  293. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +12 -0
  294. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +26 -0
  295. package/templates/be/patterns/jobs/job.index.ts.tpl +1 -0
  296. package/templates/be/patterns/jobs/job.module.ts.tpl +6 -0
  297. package/templates/be/patterns/jobs/job.processor.ts.tpl +20 -0
  298. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +20 -0
  299. package/templates/be/patterns/jobs/job.step.ts.tpl +19 -0
  300. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +31 -0
  301. package/templates/be/patterns/jobs/platform/fenced.processor.ts.tpl +13 -0
  302. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +150 -0
  303. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +100 -0
  304. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +138 -0
  305. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +69 -0
  306. package/templates/be/patterns/jobs/platform/jobs.config.ts.tpl +11 -0
  307. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +62 -0
  308. package/templates/be/patterns/jobs/platform/jobs.decorators.ts.tpl +31 -0
  309. package/templates/be/patterns/jobs/platform/jobs.error.ts.tpl +22 -0
  310. package/templates/be/patterns/jobs/platform/jobs.index.ts.tpl +11 -0
  311. package/templates/be/patterns/jobs/platform/jobs.log-events.ts.tpl +9 -0
  312. package/templates/be/patterns/jobs/platform/jobs.messages.ts.tpl +15 -0
  313. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +10 -0
  314. package/templates/be/patterns/jobs/platform/jobs.module.ts.tpl +32 -0
  315. package/templates/be/patterns/jobs/platform/jobs.options.ts.tpl +11 -0
  316. package/templates/be/patterns/jobs/platform/jobs.port.ts.tpl +36 -0
  317. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +8 -0
  318. package/templates/be/patterns/jobs/platform/persistence.job.entity.ts.tpl +53 -0
  319. package/templates/be/patterns/jobs/platform/persistence.jobs.rows.ts.tpl +13 -0
  320. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +33 -0
  321. package/templates/be/patterns/jobs/platform/persistence.migration.ts.tpl +29 -0
  322. package/templates/be/patterns/outbox/platform/index.ts.tpl +2 -0
  323. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +96 -0
  324. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +78 -0
  325. package/templates/be/patterns/outbox/platform/outbox.log-events.ts.tpl +5 -0
  326. package/templates/be/patterns/projections/migration.ts.tpl +18 -0
  327. package/templates/be/patterns/projections/persistence.connection.ts.tpl +8 -0
  328. package/templates/be/patterns/projections/projection.entity.ts.tpl +9 -0
  329. package/templates/be/patterns/projections/projection.index.ts.tpl +3 -0
  330. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +7 -0
  331. package/templates/be/patterns/projections/projection.module.ts.tpl +18 -0
  332. package/templates/be/patterns/projections/projection.options.ts.tpl +2 -0
  333. package/templates/be/patterns/projections/projection.projection.spec.ts.tpl +55 -0
  334. package/templates/be/patterns/projections/projection.projection.ts.tpl +21 -0
  335. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +142 -0
  336. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +81 -0
  337. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +8 -0
  338. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +25 -0
  339. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +25 -0
  340. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +9 -0
  341. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +12 -0
  342. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +48 -0
  343. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +17 -0
  344. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +128 -0
  345. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +48 -0
  346. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +115 -0
  347. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +91 -0
  348. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +45 -0
  349. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +15 -0
  350. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +31 -0
  351. package/templates/be/patterns/queues/platform/queue.decorators.ts.tpl +45 -0
  352. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +7 -0
  353. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +5 -0
  354. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +10 -0
  355. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +62 -0
  356. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +22 -0
  357. package/templates/be/patterns/queues/platform/queue.policy.spec.ts.tpl +19 -0
  358. package/templates/be/patterns/queues/platform/queue.policy.ts.tpl +11 -0
  359. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +14 -0
  360. package/templates/be/patterns/queues/queue.index.ts.tpl +3 -0
  361. package/templates/be/patterns/queues/queue.module.ts.tpl +6 -0
  362. package/templates/be/patterns/queues/queue.queue.ts.tpl +23 -0
  363. package/templates/be/patterns/reactors/application.command.ts.tpl +10 -0
  364. package/templates/be/patterns/reactors/application.contracts.ts.tpl +12 -0
  365. package/templates/be/patterns/reactors/application.handler.ts.tpl +22 -0
  366. package/templates/be/patterns/reactors/consumer.ts.tpl +19 -0
  367. package/templates/be/patterns/reactors/message.module.ts.tpl +20 -0
  368. package/templates/be/patterns/reactors/reactor.index.ts.tpl +1 -0
  369. package/templates/be/patterns/reactors/reactor.module.ts.tpl +6 -0
  370. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +6 -0
  371. package/templates/be/patterns/realtime/channel.index.ts.tpl +1 -0
  372. package/templates/be/patterns/realtime/channel.input.ts.tpl +12 -0
  373. package/templates/be/patterns/realtime/channel.subscription.spec.ts.tpl +51 -0
  374. package/templates/be/patterns/realtime/channel.subscription.ts.tpl +32 -0
  375. package/templates/be/patterns/realtime/channel.type.ts.tpl +9 -0
  376. package/templates/be/patterns/saga/saga.compensate.command.ts.tpl +11 -0
  377. package/templates/be/patterns/saga/saga.compensate.contracts.ts.tpl +7 -0
  378. package/templates/be/patterns/saga/saga.compensate.handler.ts.tpl +22 -0
  379. package/templates/be/patterns/saga/saga.compensation.ts.tpl +21 -0
  380. package/templates/be/patterns/saga/saga.complete.command.ts.tpl +11 -0
  381. package/templates/be/patterns/saga/saga.complete.contracts.ts.tpl +7 -0
  382. package/templates/be/patterns/saga/saga.complete.handler.ts.tpl +22 -0
  383. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +21 -0
  384. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +21 -0
  385. package/templates/be/patterns/saga/saga.index.ts.tpl +1 -0
  386. package/templates/be/patterns/saga/saga.message.module.ts.tpl +23 -0
  387. package/templates/be/patterns/saga/saga.module.ts.tpl +20 -0
  388. package/templates/be/patterns/saga/saga.service.spec.ts.tpl +70 -0
  389. package/templates/be/patterns/saga/saga.service.ts.tpl +44 -0
  390. package/templates/be/patterns/saga/saga.state.ts.tpl +14 -0
  391. package/templates/be/patterns/saga/saga.step.ts.tpl +11 -0
  392. package/templates/be/patterns/saga/saga.undo.command.ts.tpl +10 -0
  393. package/templates/be/patterns/saga/saga.undo.contracts.ts.tpl +5 -0
  394. package/templates/be/patterns/saga/saga.undo.handler.ts.tpl +21 -0
  395. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +72 -0
  396. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +36 -0
  397. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +6 -0
  398. package/templates/be/patterns/webhooks/webhook.index.ts.tpl +1 -0
  399. package/templates/be/patterns/webhooks/webhook.request.ts.tpl +9 -0
  400. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
  401. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -2
  402. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -15
  403. package/templates/be/skeleton/apps/cli/src/app.module.ts +30 -0
  404. package/templates/be/skeleton/apps/cli/src/cli.options.ts +22 -0
  405. package/templates/be/skeleton/apps/cli/src/main.ts +24 -0
  406. package/templates/be/skeleton/src/features/cli/cli.module.ts +7 -0
  407. package/templates/be/skeleton/src/features/cli/index.ts +1 -0
  408. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +17 -0
  409. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +15 -0
  410. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +7 -0
  411. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.spec.ts +17 -0
  412. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.ts +18 -0
  413. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +17 -0
  414. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +15 -0
  415. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +7 -0
  416. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +33 -0
  417. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +34 -0
  418. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +13 -0
  419. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +40 -0
  420. package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +1 -1
  421. package/templates/be/skeleton/src/modules/domain/note/errors/note.error.ts +16 -0
  422. package/templates/be/skeleton/src/modules/domain/note/index.ts +3 -0
  423. package/templates/be/skeleton/src/modules/domain/note/note.contracts.ts +19 -0
  424. package/templates/be/skeleton/src/modules/domain/note/note.decorators.ts +9 -0
  425. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +7 -0
  426. package/templates/be/skeleton/src/modules/domain/note/note.module.ts +19 -0
  427. package/templates/be/skeleton/src/modules/domain/note/note.options.ts +2 -0
  428. package/templates/be/skeleton/src/modules/domain/note/note.service.spec.ts +76 -0
  429. package/templates/be/skeleton/src/modules/domain/note/note.service.ts +37 -0
  430. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +8 -0
  431. package/templates/be/skeleton/src/modules/domain/note/persistence/entities/note.entity.ts +17 -0
  432. package/templates/be/skeleton/src/modules/domain/note/persistence/migrations/1790000000000-init-note.ts +22 -0
  433. package/templates/be/skeleton/src/modules/domain/note/persistence/note.rows.ts +17 -0
  434. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +4 -0
  435. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +20 -0
  436. package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
  437. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +18 -0
  438. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +41 -0
  439. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.ts +17 -0
  440. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +13 -0
  441. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +77 -0
  442. package/templates/be/skeleton/src/modules/platform/database/database.options.ts +30 -0
  443. package/templates/be/skeleton/src/modules/platform/database/database.port.ts +65 -0
  444. package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +47 -0
  445. package/templates/be/skeleton/src/modules/platform/database/errors/database.error.ts +16 -0
  446. package/templates/be/skeleton/src/modules/platform/database/index.ts +9 -0
  447. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +30 -0
  448. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.spec.ts +84 -0
  449. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.ts +23 -0
  450. package/templates/be/skeleton/src/modules/platform/database/primary.config.ts +9 -0
  451. package/templates/be/skeleton/src/modules/platform/database/primary.connection.ts +2 -0
  452. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +10 -0
  453. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +108 -0
  454. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +75 -0
  455. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +88 -0
  456. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +29 -0
  457. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +48 -0
  458. package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +1 -1
  459. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +12 -0
  460. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -0
  461. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +8 -1
  462. package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +8 -0
  463. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +14 -1
  464. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +12 -0
  465. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +10 -2
  466. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +63 -0
  467. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +108 -0
  468. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.spec.ts +39 -0
  469. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.ts +25 -0
  470. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +172 -0
  471. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.ts +66 -0
  472. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +3 -1
  473. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +52 -0
  474. package/templates/be/skeleton-lite/apps/api/Dockerfile +37 -0
  475. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +16 -0
  476. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +81 -0
  477. package/templates/be/skeleton-lite/apps/api/src/main.ts +34 -0
  478. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +14 -0
  479. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +50 -0
  480. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +26 -0
  481. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +19 -0
  482. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +26 -0
  483. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +7 -0
  484. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +6 -0
  485. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +19 -0
  486. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +10 -0
  487. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +24 -0
  488. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +8 -0
  489. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +17 -0
  490. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +32 -0
  491. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +24 -0
  492. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +4 -0
  493. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +54 -0
  494. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +23 -0
  495. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +10 -0
  496. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +49 -0
  497. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +4 -0
  498. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +21 -0
  499. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +11 -0
  500. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +28 -0
  501. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +25 -0
  502. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +9 -0
  503. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +4 -0
  504. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +5 -0
  505. package/templates/be/table/error.ts.tpl +16 -0
  506. package/templates/be/table/index.ts.tpl +2 -0
  507. package/templates/be/table/migration.sql.tpl +51 -0
  508. package/templates/be/table/module-definition.ts.tpl +7 -0
  509. package/templates/be/table/module.ts.tpl +14 -0
  510. package/templates/be/table/options.ts.tpl +2 -0
  511. package/templates/be/table/rows.ts.tpl +4 -0
  512. package/templates/be/table/service.ts.tpl +23 -0
  513. package/templates/be/table/sql.ts.tpl +4 -0
  514. package/templates/be/tool-config/jest.config.js +3 -1
  515. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +14 -0
  516. package/templates/be/upgrade-full/apps/cli/src/main.ts +26 -0
  517. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +6 -0
  518. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +23 -0
  519. package/templates/common/gitignore.base +1 -2
  520. package/templates/fe/app/package.json.tpl +1 -0
  521. package/templates/fe/app/tsconfig.json +12 -0
  522. package/templates/fe/common/app-layout.tsx.partial +8 -0
  523. package/templates/fe/common/site-shell.tsx.partial +9 -0
  524. package/templates/fe/image/next/Dockerfile +42 -0
  525. package/templates/fe/skeleton/apps/app/next.config.ts +23 -0
  526. package/templates/fe/skeleton/apps/app/postcss.config.mjs +7 -0
  527. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/error.tsx +2 -2
  528. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +22 -0
  529. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/loading.tsx +2 -2
  530. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/not-found.tsx +2 -2
  531. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +9 -0
  532. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/global-error.tsx +2 -2
  533. package/templates/fe/skeleton/apps/app/src/app/globals.css +10 -0
  534. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +18 -0
  535. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/component.tsx +19 -0
  536. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/index.tsx +11 -0
  537. package/templates/fe/skeleton/apps/app/src/features/pages/AppGlobalErrorPage/index.tsx +16 -0
  538. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/component.tsx +10 -0
  539. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +15 -0
  540. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +8 -0
  541. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  542. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +4 -0
  543. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +28 -0
  544. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -0
  545. package/templates/fe/skeleton/apps/app/src/modules/routes/index.ts +4 -0
  546. package/templates/fe/skeleton/apps/app/src/proxy.ts +1 -0
  547. package/templates/fe/skeleton/apps/{__app__ → landing}/next.config.ts +4 -2
  548. package/templates/fe/skeleton/apps/landing/postcss.config.mjs +7 -0
  549. package/templates/fe/skeleton/apps/landing/src/app/[locale]/error.tsx +10 -0
  550. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +22 -0
  551. package/templates/fe/skeleton/apps/landing/src/app/[locale]/loading.tsx +6 -0
  552. package/templates/fe/skeleton/apps/landing/src/app/[locale]/not-found.tsx +6 -0
  553. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +9 -0
  554. package/templates/fe/skeleton/apps/landing/src/app/global-error.tsx +10 -0
  555. package/templates/fe/skeleton/apps/landing/src/app/globals.css +10 -0
  556. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +24 -0
  557. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/component.tsx +18 -0
  558. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/index.tsx +11 -0
  559. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingGlobalErrorPage/index.tsx +16 -0
  560. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +8 -0
  561. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +9 -0
  562. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/component.tsx +24 -0
  563. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +16 -0
  564. package/templates/fe/skeleton/apps/landing/src/modules/config/index.ts +12 -0
  565. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +7 -0
  566. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/vi.json +31 -0
  567. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -0
  568. package/templates/fe/skeleton/apps/landing/src/modules/routes/index.ts +4 -0
  569. package/templates/fe/skeleton/apps/landing/src/proxy.ts +1 -0
  570. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +49 -0
  571. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +24 -0
  572. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +20 -0
  573. package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +23 -0
  574. package/templates/fe/skeleton/packages/__project__-ui/src/composites/ErrorNotice/index.tsx +14 -0
  575. package/templates/fe/skeleton/packages/__project__-ui/src/composites/GlobalErrorNotice/index.tsx +25 -0
  576. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LoadingNotice/index.tsx +9 -0
  577. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +21 -0
  578. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +19 -0
  579. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/classNames.ts +4 -0
  580. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/index.tsx +19 -0
  581. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +6 -0
  582. package/templates/fe/skeleton-lite/apps/web/next.config.ts +22 -0
  583. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/error.tsx +10 -0
  584. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +37 -0
  585. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/loading.tsx +6 -0
  586. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/not-found.tsx +6 -0
  587. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +9 -0
  588. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/providers.tsx +26 -0
  589. package/templates/fe/skeleton-lite/apps/web/src/app/auth/callback/route.ts +24 -0
  590. package/templates/fe/skeleton-lite/apps/web/src/app/global-error.tsx +10 -0
  591. package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/app/globals.css +1 -1
  592. package/templates/fe/skeleton-lite/apps/web/src/app/health/live/route.ts +5 -0
  593. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/component.tsx +29 -0
  594. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +41 -0
  595. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/component.tsx +17 -0
  596. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/index.tsx +38 -0
  597. package/templates/fe/skeleton-lite/apps/web/src/components/composites/ErrorNotice/index.tsx +13 -0
  598. package/templates/fe/skeleton-lite/apps/web/src/components/composites/GlobalErrorNotice/index.tsx +24 -0
  599. package/templates/fe/skeleton-lite/apps/web/src/components/composites/LoadingNotice/index.tsx +7 -0
  600. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +18 -0
  601. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/classNames.ts +4 -0
  602. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/index.tsx +13 -0
  603. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +31 -0
  604. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/component.tsx +12 -0
  605. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/index.tsx +10 -0
  606. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppGlobalErrorPage/index.tsx +15 -0
  607. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +14 -0
  608. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +30 -0
  609. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +8 -0
  610. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  611. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +20 -0
  612. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/index.ts +2 -0
  613. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +20 -0
  614. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/read-session.ts +25 -0
  615. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-in.ts +25 -0
  616. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +17 -0
  617. package/templates/fe/skeleton-lite/apps/web/src/modules/db/browser.ts +14 -0
  618. package/templates/fe/skeleton-lite/apps/web/src/modules/db/index.ts +3 -0
  619. package/templates/fe/skeleton-lite/apps/web/src/modules/db/outcome.ts +59 -0
  620. package/templates/fe/skeleton-lite/apps/web/src/modules/db/principal.ts +29 -0
  621. package/templates/fe/skeleton-lite/apps/web/src/modules/db/server.ts +70 -0
  622. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +50 -0
  623. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +8 -0
  624. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +48 -0
  625. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +16 -0
  626. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +14 -0
  627. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +7 -0
  628. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/safe-next-path.ts +26 -0
  629. package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +43 -0
  630. package/templates/fe/table/read.ts.tpl +16 -0
  631. package/templates/fe/table/write.ts.tpl +16 -0
  632. package/upgrade/check.mjs +137 -0
  633. package/upgrade/index.mjs +524 -0
  634. package/bin/hfs.mjs +0 -233
  635. package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
  636. package/runtime/scripts/lib/test-secrets.mjs +0 -116
  637. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +0 -20
  638. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +0 -9
  639. package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +0 -28
  640. package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +0 -35
  641. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +0 -19
  642. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -11
  643. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -24
  644. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -14
  645. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -21
  646. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +0 -15
  647. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +0 -23
  648. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +0 -8
  649. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +0 -28
  650. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +0 -8
  651. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +0 -8
  652. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +0 -2
  653. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +0 -25
  654. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/request.ts +0 -19
  655. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +0 -9
  656. package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +0 -4
  657. package/templates/fe/skeleton/apps/__app__/src/proxy.ts +0 -10
  658. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.contracts.ts +0 -0
  659. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.handler.ts +0 -0
  660. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.query.ts +0 -0
  661. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/index.ts +0 -0
  662. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/system-health.module.ts +0 -0
  663. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/live.controller.ts +0 -0
  664. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/system-health-http.module.ts +0 -0
  665. /package/templates/fe/skeleton/apps/{__app__ → app}/src/app/health/live/route.ts +0 -0
  666. /package/templates/fe/skeleton/apps/{__app__ → app}/src/modules/config/index.ts +0 -0
  667. /package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/postcss.config.mjs +0 -0
@@ -3,7 +3,9 @@ import path from 'node:path';
3
3
  import { canonical, isInside } from './config.mjs';
4
4
  import { readJsonFile as readJson } from '../../lib/json.mjs';
5
5
  import { frameworkPinnedRootFiles } from './framework-pinned.mjs';
6
- import { isUnshadowedCommonJsRequire, reachableViolation, relativePath, sourceLocation, unwrapExpression } from './typescript.mjs';
6
+ import { exportTargetStrings, isUnshadowedCommonJsRequire, reachableViolation, relativePath, unwrapExpression } from './typescript.mjs';
7
+ import { normalizedSymbolValue, returnedExpressions as sharedReturnedExpressions } from './ast-walks.mjs';
8
+ import { sourceLocation } from '../../lib/ts-ast.mjs';
7
9
 
8
10
  const FEATURE_TIERS = new Set(['pages', 'layouts', 'overlays']);
9
11
  const COMPONENT_TIERS = new Set(['blocks', 'composites', 'branches', 'leaves']);
@@ -52,23 +54,16 @@ function violation(config, sourceFile, node, ruleId, message, extra = {}) {
52
54
  };
53
55
  }
54
56
 
55
- function exportTargets(value) {
56
- if (typeof value === 'string') return [value];
57
- if (Array.isArray(value)) return value.flatMap(exportTargets);
58
- if (value && typeof value === 'object' && !Array.isArray(value)) return Object.values(value).flatMap(exportTargets);
59
- return [];
60
- }
61
-
62
57
  function grammarExport(manifest, packageName, specifier) {
63
58
  const key = specifier === packageName ? '.' : `.${specifier.slice(packageName.length)}`;
64
59
  const value = manifest?.exports?.[key];
65
- const targets = exportTargets(value);
60
+ const targets = exportTargetStrings(value);
66
61
  return targets.length > 0 && targets.every(target => target.startsWith('./') && !target.includes('\\') && !target.split('/').includes('..'));
67
62
  }
68
63
 
69
64
  function grammarExportTargets(manifest, packageRoot, packageName, specifier) {
70
65
  const key = specifier === packageName ? '.' : `.${specifier.slice(packageName.length)}`;
71
- return exportTargets(manifest?.exports?.[key]).filter(target => target.startsWith('./') && !target.includes('\\') && !target.split('/').includes('..'))
66
+ return exportTargetStrings(manifest?.exports?.[key]).filter(target => target.startsWith('./') && !target.includes('\\') && !target.split('/').includes('..'))
72
67
  .map(target => canonical(path.resolve(packageRoot, target)));
73
68
  }
74
69
 
@@ -106,7 +101,7 @@ function checkGrammar(config, context) {
106
101
  const violations = [];
107
102
  const local = context.workspaces.find(workspace => workspace.name === grammar.package);
108
103
  // Node resolution per consumer: in an npm-workspaces monorepo a consumer whose range differs from the
109
- // hoisted copy gets its own apps/<app>/node_modules/<package> (nivo-fe: apps/app on 0.5.0 beside a hoisted
104
+ // hoisted copy gets its own apps/<app>/node_modules/<package> (one consumer: apps/app on 0.5.0 beside a hoisted
110
105
  // 0.4.11). Every consumer is judged against the copy it actually resolves, never the hoisted one alone.
111
106
  // An app installs once, at its root (config.packageRoot): a side folder has no node_modules of its own.
112
107
  const installRoot = config.packageRoot ?? config.root;
@@ -391,7 +386,7 @@ function checkCustomHookLocations(config, context, sourceFile, roots) {
391
386
  const violations = [];
392
387
  const seen = new Set();
393
388
  const report = (symbol, node) => {
394
- const identity = unaliasSymbol(ts, checker, symbol) ?? symbol;
389
+ const identity = normalizedSymbolValue(ts, checker, symbol) ?? symbol;
395
390
  if (!identity || seen.has(identity) || checker.getTypeOfSymbolAtLocation(identity, node).getCallSignatures().length === 0) return;
396
391
  seen.add(identity);
397
392
  violations.push(violation(config, sourceFile, node, 'FE_CUSTOM_HOOK_LOCATION',
@@ -411,7 +406,7 @@ function checkCustomHookLocations(config, context, sourceFile, roots) {
411
406
  const module = checker.getSymbolAtLocation(sourceFile);
412
407
  for (const exposed of module ? checker.getExportsOfModule(module) : []) {
413
408
  if (!/^use[A-Z0-9]/.test(exposed.name)) continue;
414
- const identity = unaliasSymbol(ts, checker, exposed);
409
+ const identity = normalizedSymbolValue(ts, checker, exposed);
415
410
  for (const declaration of identity?.getDeclarations?.() ?? []) if (declaration.getSourceFile() === sourceFile) {
416
411
  const node = declarationName(ts, declaration) ?? declaration;
417
412
  report(identity, node);
@@ -473,17 +468,6 @@ function checkPureAndData(config, context, sourceFile, roots) {
473
468
  return violations;
474
469
  }
475
470
 
476
- function unaliasSymbol(ts, checker, value) {
477
- let symbol = value ?? null;
478
- const seen = new Set();
479
- while (symbol && (symbol.flags & ts.SymbolFlags.Alias) && !seen.has(symbol)) {
480
- seen.add(symbol);
481
- const target = checker.getAliasedSymbol(symbol);
482
- if (!target || target === symbol) break;
483
- symbol = target;
484
- }
485
- return symbol;
486
- }
487
471
 
488
472
  function selectedSymbol(ts, checker, expression) {
489
473
  const selected = unwrapExpression(ts, expression);
@@ -498,19 +482,9 @@ function selectedSymbol(ts, checker, expression) {
498
482
  return null;
499
483
  }
500
484
 
501
- function returnedExpressions(ts, declaration) {
502
- if ((ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration)) && !ts.isBlock(declaration.body)) return [declaration.body];
503
- const body = ts.isFunctionLike(declaration) ? declaration.body : null;
504
- if (!body || !ts.isBlock(body)) return [];
505
- const returned = [];
506
- const visit = node => {
507
- if (node !== body && ts.isFunctionLike(node)) return;
508
- if (ts.isReturnStatement(node) && node.expression) returned.push(node.expression);
509
- else ts.forEachChild(node, visit);
510
- };
511
- visit(body);
512
- return returned;
513
- }
485
+ /* The wider boundary set: `ts.isFunctionLike` (includes call signatures' declarations) rather than the
486
+ * four default callable kinds. */
487
+ const returnedExpressions = (ts, declaration) => sharedReturnedExpressions(ts, declaration, { functionLike: ts.isFunctionLike });
514
488
 
515
489
  function knownWorldImport(specifier, imported) {
516
490
  if (specifier === 'next-intl') return WORLD_INTL_CALLS.has(imported);
@@ -611,7 +585,7 @@ function checkWorldRenderBoundaries(config, context, roots) {
611
585
  };
612
586
 
613
587
  const intrinsicUiHookSymbol = (symbol, checker) => {
614
- const identity = unaliasSymbol(ts, checker, symbol);
588
+ const identity = normalizedSymbolValue(ts, checker, symbol);
615
589
  const declarations = identity?.getDeclarations?.() ?? [];
616
590
  return declarations.length > 0 && declarations.every(declaration => intrinsicUiHookFile(declaration.getSourceFile().fileName));
617
591
  };
@@ -668,7 +642,7 @@ function checkWorldRenderBoundaries(config, context, roots) {
668
642
  : ts.isPropertyAccessExpression(selected) ? selected.name.text : null;
669
643
  if (['forwardRef', 'memo'].includes(name) && expression.arguments[0]) return expressionFunctions(expression.arguments[0], checker, seen);
670
644
  }
671
- const symbol = unaliasSymbol(ts, checker, selectedSymbol(ts, checker, expression));
645
+ const symbol = normalizedSymbolValue(ts, checker, selectedSymbol(ts, checker, expression));
672
646
  if (!symbol || seen.has(symbol)) return [];
673
647
  const nextSeen = new Set(seen).add(symbol);
674
648
  const functions = [];
@@ -692,7 +666,7 @@ function checkWorldRenderBoundaries(config, context, roots) {
692
666
  for (const [namespace, declaration] of info.namespaces) if (namespace === raw) {
693
667
  return declaration.worldEdge || knownWorldImport(declaration.specifier, propertyName ?? '');
694
668
  }
695
- const identity = unaliasSymbol(ts, info.checker, raw);
669
+ const identity = normalizedSymbolValue(ts, info.checker, raw);
696
670
  return (identity?.getDeclarations?.() ?? []).some(declaration => {
697
671
  const fileName = declaration.getSourceFile().fileName;
698
672
  return insideAny(roots.hooks, fileName) || insideAny(roots.transport, fileName);
@@ -716,7 +690,7 @@ function checkWorldRenderBoundaries(config, context, roots) {
716
690
  if (namespaceIsWorld(namespace, sourceFile, property)) return true;
717
691
  if (symbolIsWorld(selectedSymbol(ts, checker, expression), sourceFile)) return true;
718
692
  }
719
- const symbol = unaliasSymbol(ts, checker, selectedSymbol(ts, checker, expression));
693
+ const symbol = normalizedSymbolValue(ts, checker, selectedSymbol(ts, checker, expression));
720
694
  if (!symbol || seen.has(symbol)) return false;
721
695
  const nextSeen = new Set(seen).add(symbol);
722
696
  const declarations = symbol.getDeclarations?.() ?? [];
@@ -763,7 +737,7 @@ function checkWorldRenderBoundaries(config, context, roots) {
763
737
  if (name === 'map' && expression.arguments[0]) return expressionFunctions(expression.arguments[0], checker)
764
738
  .some(fn => functionHasRender(fn, checker, seen));
765
739
  }
766
- const symbol = unaliasSymbol(ts, checker, selectedSymbol(ts, checker, ts.isCallExpression(expression) ? expression.expression : expression));
740
+ const symbol = normalizedSymbolValue(ts, checker, selectedSymbol(ts, checker, ts.isCallExpression(expression) ? expression.expression : expression));
767
741
  if (!symbol || seen.has(symbol)) return false;
768
742
  const nextSeen = new Set(seen).add(symbol);
769
743
  if (ts.isIdentifier(expression)) for (const declaration of symbol.getDeclarations?.() ?? []) {
@@ -839,7 +813,7 @@ function checkWorldRenderBoundaries(config, context, roots) {
839
813
  if (ts.isArrayLiteralExpression(expression)) return expression.elements.length > 0
840
814
  && expression.elements.every(item => renderBoundary(item, checker, seen, allowEmpty, expectedFile));
841
815
  if (ts.isIdentifier(expression)) {
842
- const symbol = unaliasSymbol(ts, checker, checker.getSymbolAtLocation(expression));
816
+ const symbol = normalizedSymbolValue(ts, checker, checker.getSymbolAtLocation(expression));
843
817
  if (!symbol || seen.has(symbol)) return false;
844
818
  const nextSeen = new Set(seen).add(symbol);
845
819
  const values = (symbol.getDeclarations?.() ?? []).flatMap(declaration => ts.isVariableDeclaration(declaration) && declaration.initializer
@@ -1,11 +1,12 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { isIP } from 'node:net';
4
- import { gitOutput } from '../../api/git/lib.mjs';
3
+ import { lsFiles } from '../../api/git/ls-files.mjs'; import { revParseQuery } from '../../api/git/rev-parse-query.mjs'; import { configGet } from '../../api/git/config-get.mjs'; import { gitOutputOf } from '../../lib/git.mjs';
5
4
  import { repositoryName } from '../repo-identity.mjs';
6
5
  import { braceVariants } from '../../lib/glob.mjs';
7
6
  import { createSlotResolver, loadSlotManifest, openHfs } from '../slots.mjs';
8
7
  import { isFeTestPath } from '../rules/fe-no-tests.mjs';
8
+ import { readTextFile } from '../../lib/read-text.mjs';
9
+ import { managedScriptNames } from './managed-scripts.mjs';
9
10
 
10
11
  /**
11
12
  * HFS repository-tree check (knowledge/hfs/README.md): every StarCi repository is an
@@ -17,6 +18,7 @@ import { isFeTestPath } from '../rules/fe-no-tests.mjs';
17
18
  */
18
19
 
19
20
  export const HFS_RULE_IDS = [
21
+ 'BE_TEST_TOPOLOGY',
20
22
  'HFS_APPS_REQUIRED',
21
23
  'HFS_APP_LAYOUT_INVALID',
22
24
  'HFS_E2E_IN_AUTOMATIC_GATE',
@@ -37,7 +39,6 @@ export const HFS_RULE_IDS = [
37
39
  'HFS_ROOT_SRC_FORBIDDEN_FE',
38
40
  'HFS_SRC_LAYOUT_INVALID',
39
41
  'HFS_STACKS_IN_SIDE',
40
- 'HFS_TEST_KIND_RETIRED',
41
42
  'HFS_WORK_IN_FE',
42
43
  ];
43
44
 
@@ -46,7 +47,7 @@ const RUNTIME_ROOT_MARKDOWN = new Set(['README.md', 'CONTEXT.md', 'CONTRIBUTING.
46
47
  const PRODUCT_ROOT_MARKDOWN = new Set(['README.md']);
47
48
  const README_SECTIONS = ['Overview', 'Stack', 'Repository layout', 'Development'];
48
49
  const BACKEND_SRC_CHILDREN = new Set(['features', 'modules', 'tests']);
49
- const MODULE_TIERS = new Set(['domain', 'integrations', 'platform']);
50
+ const moduleTiersOf = resolver => new Set(resolver.slots().filter(slot => slot.profiles.includes('be')).flatMap(slot => braceVariants(slot.path)).map(variant => /^src\/modules\/([a-z][a-z-]*)\//.exec(variant)?.[1]).filter(Boolean)); // the folders of src/modules/ the slot manifest knows (domain, platform, integrations, events, queues, projections)
50
51
  // Owner test layout 2026-09-30: unit `<name>.spec.ts`, integration, e2e and contract by folder and suffix; int-spec and harness-spec stay banned.
51
52
  const RETIRED_TEST_SUFFIX = /\.(?:int|harness)-spec\.[cm]?[jt]sx?$/u;
52
53
  const RETIRED_TEST_FOLDER = /^src\/tests\/(?:harness|live|e2e\/live)(?:\/|$)/u;
@@ -100,7 +101,7 @@ function slotTestChildren(resolver) {
100
101
  */
101
102
  function ownsGitTopLevel(root) {
102
103
  try {
103
- const top = gitOutput(['rev-parse', '--show-toplevel'], { cwd: root }).trim();
104
+ const top = gitOutputOf(revParseQuery(['--show-toplevel'], { cwd: root }), 'git rev-parse --show-toplevel').trim();
104
105
  const same = (a, b) => (process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b);
105
106
  return same(fs.realpathSync(path.resolve(top)), fs.realpathSync(path.resolve(root)));
106
107
  } catch {
@@ -113,8 +114,8 @@ function gitPaths(root) {
113
114
  try {
114
115
  // An empty index is still a Git tree. Falling back to disk in that case would count
115
116
  // untracked build output as repository content.
116
- gitOutput(['rev-parse', '--is-inside-work-tree'], { cwd: root });
117
- const out = gitOutput(['ls-files', '--cached', '-z', '--', '.'], { cwd: root, maxBuffer: 256 * 1024 * 1024 });
117
+ gitOutputOf(revParseQuery(['--is-inside-work-tree'], { cwd: root }), 'git rev-parse --is-inside-work-tree');
118
+ const out = gitOutputOf(lsFiles(['--cached', '-z', '--', '.'], { cwd: root, maxBuffer: 256 * 1024 * 1024 }), 'git ls-files');
118
119
  return out.split('\0').filter(Boolean);
119
120
  } catch {
120
121
  return null;
@@ -181,11 +182,12 @@ function treeView(root) {
181
182
  };
182
183
  }
183
184
 
185
+ // `hostname` is a WHATWG URL hostname: a name never holds ':', so after the brackets go only an IPv6 literal does.
184
186
  function privateHost(hostname) {
185
187
  const host = hostname.toLowerCase().replace(/^\[|\]$/g, '').replace(/\.$/, '');
186
188
  if (host === 'localhost' || host === '::1' || host === '0.0.0.0' ||
187
189
  /(?:\.localhost|\.local|\.internal|\.lan)$/u.test(host)) return true;
188
- if (isIP(host) === 6) return /^(?:::|f[cd][0-9a-f]*:|fe[89ab][0-9a-f]*:)/iu.test(host);
190
+ if (host.includes(':')) return /^(?:::|f[cd][0-9a-f]*:|fe[89ab][0-9a-f]*:)/iu.test(host);
189
191
  if (!host.includes('.')) return true;
190
192
  const octets = host.split('.');
191
193
  if (octets.length !== 4 || !octets.every(part => /^\d{1,3}$/u.test(part) && Number(part) <= 255)) return false;
@@ -197,20 +199,6 @@ function privateHost(hostname) {
197
199
  // The scripts the README Development section shows; each is required only when the managed package-scripts template of the
198
200
  // profile (packages/hfs/templates/<profile>/package-scripts/package.json, the one source of the managed script names) has it.
199
201
  const DEVELOPMENT_SCRIPTS = ['typecheck', 'lint', 'build', 'test'];
200
- // The templates sit beside the runtime in a checkout (packages/hfs/templates) and one level above the bundled runtime of @starci/hfs.
201
- const TEMPLATE_ROOTS = [path.resolve(import.meta.dirname, '..', '..', '..', 'packages', 'hfs', 'templates'), path.resolve(import.meta.dirname, '..', '..', '..', '..', 'templates')];
202
- const managedScriptCache = new Map();
203
-
204
- /** The script names of the managed package-scripts template of a profile. The template holds a {{appScripts}} placeholder, so it is read by key, not parsed as JSON. */
205
- function managedScriptNames(profile) {
206
- if (!managedScriptCache.has(profile)) {
207
- const file = TEMPLATE_ROOTS.map(dir => path.join(dir, profile, 'package-scripts', 'package.json')).find(candidate => fs.existsSync(candidate));
208
- if (!file) throw Error(`The managed package-scripts template of profile ${profile} cannot be found next to the runtime.`);
209
- managedScriptCache.set(profile, new Set([...fs.readFileSync(file, 'utf8').matchAll(/^\s*"([A-Za-z0-9:_.-]+)":\s*"/gmu)].map(match => match[1])));
210
- }
211
- return managedScriptCache.get(profile);
212
- }
213
-
214
202
  /** The README command that runs a managed script: `npm test` for test, `npm run <name>` for the others (never a longer script name that starts with it). */
215
203
  function scriptCommand(name) {
216
204
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
@@ -218,7 +206,7 @@ function scriptCommand(name) {
218
206
  }
219
207
 
220
208
  /** Presentation checks shared by the product HFS gate and this runtime's own standalone gate. */
221
- export function checkRepoPresentation({ root, runtime = false, tree = treeView(root), profile = 'app' }) {
209
+ export function checkRepoPresentation({ root, runtime = false, tree = treeView(root), profile = 'app', edition = 'full' }) {
222
210
  const violations = [];
223
211
  const finding = (ruleId, entry, message, line = 1) => violations.push({ ruleId, path: entry, line, column: 1, message });
224
212
  for (const entry of tree.top) {
@@ -268,7 +256,7 @@ export function checkRepoPresentation({ root, runtime = false, tree = treeView(r
268
256
  };
269
257
  if (!runtime && headings.some(item => item.name === 'Development')) {
270
258
  const development = sectionBody('Development');
271
- const scripts = DEVELOPMENT_SCRIPTS.filter(name => managedScriptNames(profile).has(name));
259
+ const scripts = DEVELOPMENT_SCRIPTS.filter(name => managedScriptNames(profile, edition).has(name));
272
260
  const commands = [/npm (?:ci|install)/u, ...scripts.map(scriptCommand)];
273
261
  if (commands.some(command => !command.test(development)))
274
262
  finding('HFS_README_DEVELOPMENT_INCOMPLETE', 'README.md',
@@ -319,9 +307,7 @@ const UNIT_RUN_SCRIPTS = ['test', 'test:unit', 'test:ci', 'test:affected', 'test
319
307
  const runsE2e = text => E2E_COMMAND.test(String(text).replace(/--ignore-pattern[= ]+(?:"[^"]*"|'[^']*'|\S+)/gu, ''));
320
308
  const withoutComments = text => text.split('\n').filter(line => !/^\s*#/u.test(line)).join('\n');
321
309
 
322
- function readText(root, relative) {
323
- try { return fs.readFileSync(path.join(root, ...relative.split('/')), 'utf8'); } catch { return null; }
324
- }
310
+ const readText = readTextFile;
325
311
 
326
312
  // A repository-local core.hooksPath that points anywhere but husky's own directory switches the commit and push
327
313
  // hooks off for that clone (a lane once redirected it to skip husky), so the gate every other clone runs never ran.
@@ -331,10 +317,9 @@ const HUSKY_HOOKS_PATH = /^\.husky(?:\/_)?\/?$/u;
331
317
  /** A directory that is not the top level of its own work tree has no hooks of its own: the check is not applicable there. */
332
318
  function hooksPathNotRedirected({ root, finding }) {
333
319
  if (!ownsGitTopLevel(root)) return { status: 'not-applicable', reason: 'the checked root is not the top level of its own Git work tree, so it has no hooks of its own' };
334
- let value = '';
335
- try {
336
- value = gitOutput(['config', '--local', '--get', 'core.hooksPath'], { cwd: root }).trim();
337
- } catch { return { status: 'checked' }; } // key not set: nothing is redirected
320
+ const set = configGet(root, 'core.hooksPath', { local: true });
321
+ if (!set.ok) return { status: 'checked' }; // key not set: nothing is redirected
322
+ const value = set.stdout;
338
323
  if (value && !HUSKY_HOOKS_PATH.test(value.replaceAll('\\', '/')))
339
324
  finding('HFS_HOOKS_PATH_REDIRECTED', '.git/config', `core.hooksPath is set to ${value} in this clone. Unset it (git config --local --unset core.hooksPath) and let husky own the hooks; a redirected path skips the pre-commit and pre-push gates.`);
340
325
  return { status: 'checked' };
@@ -450,7 +435,7 @@ export function checkHfs(config) {
450
435
  continue;
451
436
  }
452
437
  if (!tree.hasDir(`apps/${app}/src`)) missing.push('src/');
453
- // A back-end app must hold exactly what the slot of its kind requires (be.app.migrate has no app.module.ts).
438
+ // A back-end app must hold exactly what the slot of its kind requires (each kind's slot names its own requires).
454
439
  if (backend) for (const required of resolver.requiredFiles(`apps/${app}/src/main.ts`)) {
455
440
  const directory = required.endsWith('/');
456
441
  if (!(directory ? tree.hasDir(required.slice(0, -1)) : tree.hasFile(required))) missing.push(required.slice(`apps/${app}/`.length));
@@ -471,8 +456,8 @@ export function checkHfs(config) {
471
456
  }
472
457
  }
473
458
  for (const tier of tree.children('src/modules').sort()) {
474
- if (!MODULE_TIERS.has(tier)) {
475
- finding('HFS_MODULE_TIER_INVALID', `src/modules/${tier}`, `Module tier ${tier} is not one of domain, platform, integrations.`);
459
+ if (!moduleTiersOf(resolver).has(tier)) {
460
+ finding('HFS_MODULE_TIER_INVALID', `src/modules/${tier}`, `Module tier ${tier} is not one of ${[...moduleTiersOf(resolver)].sort().join(', ')}.`);
476
461
  }
477
462
  }
478
463
  const testChildren = slotTestChildren(resolver);
@@ -488,11 +473,11 @@ export function checkHfs(config) {
488
473
  // live/; a backend spec sits by its kind (beside its subject, or under src/tests/{integration,e2e,contract}/).
489
474
  for (const file of tree.files()) {
490
475
  if (RETIRED_TEST_SUFFIX.test(file))
491
- finding('HFS_TEST_KIND_RETIRED', file, `${file} uses a retired test kind. Only unit *.spec.ts, *.integration-spec.ts, *.e2e-spec.ts and *.contract-spec.ts exist, each in its own folder under src/tests/.`);
476
+ finding('BE_TEST_TOPOLOGY', file, `${file} uses a retired test kind. Only unit *.spec.ts, *.integration-spec.ts, *.e2e-spec.ts and *.contract-spec.ts exist, each in its own folder under src/tests/.`);
492
477
  else if (backend && RETIRED_TEST_FOLDER.test(file))
493
- finding('HFS_TEST_KIND_RETIRED', file, `${file} sits in a retired test folder. Unit specs sit beside their subject, flows go under src/tests/e2e/<area>/ and test infrastructure under src/tests/world/.`);
478
+ finding('BE_TEST_TOPOLOGY', file, `${file} sits in a retired test folder. Unit specs sit beside their subject, flows go under src/tests/e2e/<area>/ and test infrastructure under src/tests/world/.`);
494
479
  else if (backend && /^src\/tests\//u.test(file) && EXTRA_TEST_CONFIG.test(file))
495
- finding('HFS_TEST_KIND_RETIRED', file, `${file} is a per-lane test config. One root jest.config.js declares exactly the unit, integration, e2e and contract projects.`);
480
+ finding('BE_TEST_TOPOLOGY', file, `${file} is a per-lane test config. One root jest.config.js declares exactly the unit, integration, e2e and contract projects.`);
496
481
  }
497
482
 
498
483
  if (backend) testTreesOutOfDefaultProgram({ root: config.root, tree, finding });
@@ -511,13 +496,13 @@ export function checkHfs(config) {
511
496
 
512
497
  /**
513
498
  * The HFS tree check of the app root (`resolver` is the app's): the README, the root entries the app-root slots allow and require,
514
- * the automatic gates (hooks, the root scripts they call, the workflows) and the hooks path. `hfs check` runs it once per app; the
499
+ * the automatic gates (hooks, the root scripts they call, the workflows) and the hooks path. `starci app check` runs it once per app; the
515
500
  * machine runs checkHfs once per side folder.
516
501
  */
517
502
  export function checkAppRoot({ root, resolver, tree = treeView(root) }) {
518
503
  const violations = [];
519
504
  const finding = (ruleId, entry, message) => violations.push({ ruleId, path: entry, line: 1, column: 1, message });
520
- violations.push(...checkRepoPresentation({ root, tree, profile: 'app' }).violations);
505
+ violations.push(...checkRepoPresentation({ root, tree, profile: 'app', edition: resolver.repo?.edition ?? 'full' }).violations);
521
506
  const allowed = slotRootEntries(resolver, 'app');
522
507
  for (const entry of [...tree.top].sort()) {
523
508
  if (NON_NPM_ENTRIES.has(entry) || /\.md$/iu.test(entry)) continue;
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
 
4
4
  /**
5
5
  * R106 `i18n-keys` (FE_I18N_KEYS). The catalogs of an app (`apps/<app>/src/modules/i18n/messages/<locale>.json`, slot fe.modules.i18n) and
6
- * the source that reads them agree, both ways. The key sets of the locales agree with each other (FE_I18N_CATALOG, `hfs check`); this
6
+ * the source that reads them agree, both ways. The key sets of the locales agree with each other (FE_I18N_CATALOG, `starci app check`); this
7
7
  * rule holds the source to the catalogs:
8
8
  *
9
9
  * - a key read with a literal is in every locale: `useTranslations("ns")` / `getTranslations("ns")` (also `{ namespace }`, `await`)
@@ -31,6 +31,11 @@ import { checkUnitSpecProviders, UNIT_SPEC_PROVIDERS_RULE_IDS } from './unit-spe
31
31
  import { checkInjectionTokenExported, INJECTION_TOKEN_EXPORTED_RULE_IDS } from './injection-token-exported.mjs';
32
32
  import { checkContractFixtureGuard, CONTRACT_FIXTURE_GUARD_RULE_IDS } from './contract-fixture-guard.mjs';
33
33
  import { checkSchemaOwner, SCHEMA_OWNER_RULE_IDS } from './schema-owner.mjs';
34
+ import { checkSqlReturning, SQL_RETURNING_RULE_IDS } from './sql-returning.mjs';
35
+ import { checkContextOwner, CONTEXT_OWNER_RULE_IDS } from './context-owner.mjs';
36
+ import { checkContextCoupling, CONTEXT_COUPLING_RULE_IDS } from './context-coupling.mjs';
37
+ import { checkContextTransaction, CONTEXT_TRANSACTION_RULE_IDS } from './context-transaction.mjs';
38
+ import { checkContextPlatformTables, CONTEXT_PLATFORM_TABLES_RULE_IDS } from './context-platform-tables.mjs';
34
39
  import { checkModulePerTransport, MODULE_PER_TRANSPORT_RULE_IDS } from './module-per-transport.mjs';
35
40
  import { checkBackgroundUnowned, BACKGROUND_UNOWNED_RULE_IDS } from './background-unowned.mjs';
36
41
  import { checkTransportOwner, TRANSPORT_OWNER_RULE_IDS } from './transport-owner.mjs';
@@ -42,6 +47,7 @@ import { checkPackageShape, PACKAGE_SHAPE_RULE_IDS } from './package-shape.mjs';
42
47
  import { checkFeSlotAllows, FE_SLOT_ALLOWS_RULE_IDS } from './fe-slot-allows.mjs';
43
48
  import { checkI18nKeys, I18N_KEYS_RULE_IDS } from './i18n-keys.mjs';
44
49
  import { checkDocLanguage, DOC_LANGUAGE_RULE_IDS } from './doc-language.mjs';
50
+ import { checkBackendSupabase, checkFrontendSupabase, BACKEND_SUPABASE_RULE_IDS, FRONTEND_SUPABASE_RULE_IDS } from './supabase.mjs';
45
51
  import { LINT_CODES, onLintSurface } from './surface.mjs';
46
52
 
47
53
  export { REGISTRATION_RULE_IDS, SWR_DATA_RULE_IDS };
@@ -68,10 +74,16 @@ const BACKEND_MACHINE = {
68
74
  unitSpecProviders: [checkUnitSpecProviders, UNIT_SPEC_PROVIDERS_RULE_IDS],
69
75
  injectionTokenExported: [checkInjectionTokenExported, INJECTION_TOKEN_EXPORTED_RULE_IDS],
70
76
  schemaOwner: [checkSchemaOwner, SCHEMA_OWNER_RULE_IDS],
77
+ sqlReturning: [checkSqlReturning, SQL_RETURNING_RULE_IDS],
78
+ contextOwner: [checkContextOwner, CONTEXT_OWNER_RULE_IDS],
79
+ contextCoupling: [checkContextCoupling, CONTEXT_COUPLING_RULE_IDS],
80
+ contextTransaction: [checkContextTransaction, CONTEXT_TRANSACTION_RULE_IDS],
81
+ contextPlatformTables: [checkContextPlatformTables, CONTEXT_PLATFORM_TABLES_RULE_IDS],
71
82
  modulePerTransport: [checkModulePerTransport, MODULE_PER_TRANSPORT_RULE_IDS],
72
83
  backgroundUnowned: [checkBackgroundUnowned, BACKGROUND_UNOWNED_RULE_IDS],
73
84
  testWorldFiles: [checkTestWorldFiles, TEST_WORLD_FILES_RULE_IDS],
74
85
  contractFixtureGuard: [checkContractFixtureGuard, CONTRACT_FIXTURE_GUARD_RULE_IDS],
86
+ supabaseBackend: [checkBackendSupabase, BACKEND_SUPABASE_RULE_IDS],
75
87
  };
76
88
 
77
89
  // The frontend repository machine (R21, R50, R54, R55, R56, R63, R94, R106): same shape, run for a front-end repository only.
@@ -84,6 +96,7 @@ const FRONTEND_MACHINE = {
84
96
  crossAppDuplicate: [checkCrossAppDuplicate, CROSS_APP_DUPLICATE_RULE_IDS],
85
97
  feSlotAllows: [checkFeSlotAllows, FE_SLOT_ALLOWS_RULE_IDS],
86
98
  i18nKeys: [checkI18nKeys, I18N_KEYS_RULE_IDS],
99
+ supabaseFrontend: [checkFrontendSupabase, FRONTEND_SUPABASE_RULE_IDS],
87
100
  };
88
101
 
89
102
  /** Errors the machine reports when a program cannot be built or an edge cannot be proven: it cannot judge, so it refuses. */
@@ -125,8 +138,8 @@ const FRONTEND_RULE_IDS = [
125
138
  'FE_SOURCE_LAYOUT_INVALID',
126
139
  'FE_WORLD_OWNER_RENDER_BOUNDARY',
127
140
  ];
128
- export const OWNER_RULE_IDS = ['ARCH_OWNER_EXPORT_BYPASS', 'ARCH_OWNER_EXPORT_STAR'];
129
- export const GRAMMAR_RULE_IDS = ['ARCH_GRAMMAR_CONTRACT_INVALID', 'ARCH_GRAMMAR_EXPORT_BYPASS'];
141
+ const OWNER_RULE_IDS = ['ARCH_OWNER_EXPORT_BYPASS', 'ARCH_OWNER_EXPORT_STAR'];
142
+ const GRAMMAR_RULE_IDS = ['ARCH_GRAMMAR_CONTRACT_INVALID', 'ARCH_GRAMMAR_EXPORT_BYPASS'];
130
143
 
131
144
  /**
132
145
  * Every code the machine reports as an error because it cannot judge (a check that cannot run is an error, never a pass).
@@ -152,7 +165,7 @@ export const HFS_MACHINE_RULE_IDS = Object.freeze((() => {
152
165
  return [...new Set(machineIds().filter((id) => !ordinary.has(id)))].sort();
153
166
  })());
154
167
 
155
- /** Every code the machine can emit, derived from the rule id lists of its checks (`hfs check` ships exactly these why entries). */
168
+ /** Every code the machine can emit, derived from the rule id lists of its checks (`starci app check` ships exactly these why entries). */
156
169
  export const ARCHITECTURE_RULE_IDS = Object.freeze([...new Set([
157
170
  ...COMMON_RULE_IDS, ...BACKEND_RULE_IDS, ...FRONTEND_RULE_IDS, ...HFS_RULE_IDS, ...TIER_RULE_IDS, ...REACHABILITY_RULE_IDS,
158
171
  ...DEAD_EXPORT_RULE_IDS, ...DOC_LANGUAGE_RULE_IDS, ...REQUIRED_FILE_RULE_IDS, ...CLONE_RULE_IDS, ...OWNER_RULE_IDS, ...GRAMMAR_RULE_IDS,
@@ -253,7 +266,7 @@ export function checkArchitecture({ repositoryRoot, injectedTypeScript, paths =
253
266
  const asViolation = item => LINT_CODES.has(item.ruleId);
254
267
  const allErrors = context.errors.filter(item => item.ruleId.startsWith('ARCH_TSCONFIG_') || !item.path || inScope(item));
255
268
  const obligations = surface === 'all' ? violations : [...violations, ...context.errors.filter(asViolation).filter(inScope)];
256
- // The lint surface is what an editor can show on a line of a TypeScript file; `hfs check` keeps every other finding of the machine.
269
+ // The lint surface is what an editor can show on a line of a TypeScript file; `starci app check` keeps every other finding of the machine.
257
270
  const onSurface = item => surface === 'all' || onLintSurface(config.root, item) === (surface === 'lint');
258
271
  const errors = stable(surface === 'all' ? allErrors : allErrors.filter(item => !asViolation(item)));
259
272
  const scopedViolations = stable(obligations.filter(inScope).filter(onSurface));
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { canonical } from './config.mjs';
3
- import { sourceLocation } from './typescript.mjs';
3
+ import { sourceLocation } from '../../lib/ts-ast.mjs';
4
4
 
5
5
  /**
6
6
  * The small TypeScript reading kit the R33/R38/R39/R41/R45/R84/R86 machine checks share (connection-map, sql-owner,
@@ -115,18 +115,51 @@ export function machineKit({ config, context, graph }) {
115
115
  };
116
116
 
117
117
  /** The `{ provide: <token>, ... }` provider literals of `file` whose token is `name` imported from `moduleName`, in source order. */
118
+ const HELPER_DEPTH = 4;
119
+
120
+ /** The body a call or a spread identifier resolves to when it is a function or a const declared in a file of the program (not a package), else null. */
121
+ const helperBodyOf = (checker, node) => {
122
+ const target = ts.isCallExpression(node) ? node.expression : node;
123
+ for (const declaration of declarationsOf(checker, ts.isPropertyAccessExpression(target) ? target.name : target)) {
124
+ if (!graphPath(declaration)) continue;
125
+ if (ts.isFunctionDeclaration(declaration) && declaration.body) return { declaration, body: declaration.body };
126
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer) return { declaration, body: declaration.initializer };
127
+ }
128
+ return null;
129
+ };
130
+
131
+ /**
132
+ * The `{ provide: <token>, ... }` provider literals of `file` whose token is `name` imported from `moduleName`, in source order.
133
+ * A call or a spread of a function or a const declared in another file of the program is followed (to a depth of four), so a
134
+ * helper that builds the `APP_GUARD` or `APP_FILTER` entries outside the app root file is read too: the entry's `node` is then the
135
+ * call or spread inside the app root (where the entry is provided), its `useClass` and `checker` those of the helper's file.
136
+ */
118
137
  const providersOf = (file, name, moduleName) => {
119
- const checker = checkerOf(file.sourceFile);
120
138
  const found = [];
121
- walk(file.sourceFile, node => {
122
- if (!ts.isObjectLiteralExpression(node)) return true;
123
- const provide = propertyOf(node, 'provide');
124
- if (provide && isImportOf(checker, valueOfProperty(provide), name, moduleName)) {
125
- const use = propertyOf(node, 'useClass');
126
- found.push({ node, useClass: use ? valueOfProperty(use) : null, checker });
127
- }
128
- return true;
129
- });
139
+ const seen = new Set();
140
+ const scan = (root, anchor, depth) => {
141
+ const checker = checkerOf(root.getSourceFile());
142
+ walk(root, node => {
143
+ if (ts.isObjectLiteralExpression(node)) {
144
+ const provide = propertyOf(node, 'provide');
145
+ if (provide && isImportOf(checker, valueOfProperty(provide), name, moduleName)) {
146
+ const use = propertyOf(node, 'useClass');
147
+ found.push({ node: anchor ?? node, useClass: use ? valueOfProperty(use) : null, checker });
148
+ }
149
+ return true;
150
+ }
151
+ const reference = ts.isSpreadElement(node) ? node.expression : (ts.isCallExpression(node) ? node : null);
152
+ if (reference && depth < HELPER_DEPTH) {
153
+ const helper = helperBodyOf(checker, reference);
154
+ if (helper && !seen.has(helper.declaration)) {
155
+ seen.add(helper.declaration);
156
+ scan(helper.body, anchor ?? node, depth + 1);
157
+ }
158
+ }
159
+ return true;
160
+ });
161
+ };
162
+ scan(file.sourceFile, null, 0);
130
163
  return found.sort((a, b) => a.node.getStart() - b.node.getStart());
131
164
  };
132
165
 
@@ -0,0 +1,27 @@
1
+ // managed-scripts.mjs - reads the script names from the managed package-script template for one HFS profile and
2
+ // edition. Templates stay the source of truth; README presentation checks consume only this cached name set.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { managedGroupOf } from '../edition-slots.mjs';
6
+ import { loadSlotManifest } from '../slots.mjs';
7
+
8
+ const TEMPLATE_ROOTS = [
9
+ path.resolve(import.meta.dirname, '..', '..', '..', 'packages', 'hfs', 'templates'),
10
+ path.resolve(import.meta.dirname, '..', '..', '..', '..', 'templates'),
11
+ ];
12
+ const managedScriptCache = new Map();
13
+
14
+ /** The script names of a profile/edition template after its partials are expanded. */
15
+ export function managedScriptNames(profile, edition = 'full') {
16
+ const key = `${profile}:${edition}`;
17
+ if (!managedScriptCache.has(key)) {
18
+ const slot = loadSlotManifest().slots.find((entry) => entry.id === `${profile}.package-manifest`);
19
+ const group = slot && managedGroupOf(slot, edition);
20
+ const templates = group && TEMPLATE_ROOTS.find((dir) => fs.existsSync(path.join(dir, profile, group, 'package.json')));
21
+ if (!templates) throw Error(`The managed package-scripts template of profile ${profile} cannot be found next to the runtime.`);
22
+ const text = fs.readFileSync(path.join(templates, profile, group, 'package.json'), 'utf8')
23
+ .replace(/^\{\{> ([\w./-]+)\}\}\r?\n/gmu, (_, partial) => fs.readFileSync(path.join(templates, partial), 'utf8'));
24
+ managedScriptCache.set(key, new Set([...text.matchAll(/^\s*"([A-Za-z0-9:_.-]+)":\s*"/gmu)].map((match) => match[1])));
25
+ }
26
+ return managedScriptCache.get(key);
27
+ }
@@ -11,8 +11,11 @@ import { treeOf } from './required-files.mjs';
11
11
  * anywhere in a feature, and an `@Module` class (or a `ConfigurableModuleBuilder`/`ConfigurableModuleClass` use) in a
12
12
  * feature file that is neither the application module nor the transport module of its own folder are refused;
13
13
  * - apps import only transport modules, of the kinds their app kind allows: the `composedBy` list of each transport slot
14
- * in knowledge/hfs/slots.yaml (api: graphql, http, websocket; worker: schedule, message; cli: cli). An app root that
15
- * lists a feature's application module, or the transport module of another kind, is refused.
14
+ * in knowledge/hfs/slots.yaml (api: graphql, http, websocket; worker: schedule, message). An app root that
15
+ * lists a feature's application module, or the transport module of another kind, is refused;
16
+ * - a composed root (an owner slot with `composedBy`, be.cli) holds one static module per folder, named after the folder
17
+ * (cli.module.ts, <group>/<group>.module.ts); an app of a composing kind lists the root module only, and an app of any
18
+ * other kind lists none of its modules.
16
19
  */
17
20
  export const MODULE_PER_TRANSPORT_RULE_IDS = ['BE_MODULE_SHAPE'];
18
21
 
@@ -31,18 +34,30 @@ export function checkModulePerTransport(input) {
31
34
  const owner = resolver.ownerOf(rel);
32
35
  return owner && resolver.slot(owner.slot)?.tier === 'feature' ? owner : null;
33
36
  };
34
- /** {feature, root, protocol} of a file inside a feature: protocol is the transport folder, null for the application side. */
37
+ /**
38
+ * {feature, root, protocol, composed} of a file inside a feature: protocol is the transport folder, null for the application
39
+ * side. composed is set for a composed root, an owner slot with `composedBy` (be.cli): an app of those kinds lists its root
40
+ * module, and every folder of the root holds one static module named after the folder.
41
+ */
35
42
  const placeOf = rel => {
36
43
  const owner = featureOf(rel);
37
44
  if (!owner) return null;
45
+ const slot = resolver.slot(owner.slot);
38
46
  const below = rel.slice(owner.root.length + 1).split('/');
39
- return { root: owner.root, feature: path.posix.basename(owner.root), protocol: below[0] === 'transport' && below.length > 2 ? below[1] : null, below };
47
+ const feature = path.posix.basename(owner.root);
48
+ if (slot.composedBy) return { root: owner.root, feature, protocol: null, below, composed: slot };
49
+ return { root: owner.root, feature, protocol: below[0] === 'transport' && below.length > 2 ? below[1] : null, below, composed: null };
50
+ };
51
+ /** The one module file a folder of a composed root may hold: <folder>/<folder>.module.ts. */
52
+ const composedModuleOf = rel => {
53
+ const dir = path.posix.dirname(rel);
54
+ return `${dir}/${path.posix.basename(dir)}.module.ts`;
40
55
  };
41
56
 
42
57
  // The transport slots and the app kinds that compose them, by protocol folder name.
43
58
  const composedBy = new Map();
44
59
  for (const slot of resolver.slots()) {
45
- if (!slot.composedBy) continue;
60
+ if (!slot.composedBy || slot.owner) continue;
46
61
  composedBy.set(path.posix.basename(slot.path.replace(/\/+$/u, '')), slot.composedBy);
47
62
  }
48
63
 
@@ -74,6 +89,11 @@ export function checkModulePerTransport(input) {
74
89
  if (!isModule) continue;
75
90
  modules.set(statement, { name: statement.name.text, file });
76
91
  if (!place) continue;
92
+ if (place.composed) {
93
+ const own = composedModuleOf(file.rel);
94
+ if (file.rel !== own) report(file, statement.name, `${statement.name.text} is a Nest module declared in ${file.rel}; every folder of ${place.root} holds exactly one static module named after it (${own}), never a module per command.`, { feature: place.feature });
95
+ continue;
96
+ }
77
97
  const own = place.protocol === null
78
98
  ? `${place.root}/${place.feature}.module.ts`
79
99
  : `${place.root}/transport/${place.protocol}/${place.feature}-${place.protocol}.module.ts`;
@@ -111,7 +131,11 @@ export function checkModulePerTransport(input) {
111
131
  if (!place || seen.has(node)) continue;
112
132
  seen.add(node);
113
133
  references += 1;
114
- if (place.protocol === null) {
134
+ if (place.composed) {
135
+ const rootModule = `${place.root}/${place.feature}.module.ts`;
136
+ if (!place.composed.composedBy.includes(app.kind)) report(root, node, `App ${app.name} is of kind ${app.kind} and lists ${module.name} from ${place.root}; only an app of kind ${place.composed.composedBy.join(', ')} composes that root.`, { app: app.name, module: module.name });
137
+ else if (module.file.rel !== rootModule) report(root, node, `App ${app.name} lists ${module.name}, a group module inside ${place.root}; the app lists the root module (${rootModule}), which imports every group module.`, { app: app.name, module: module.name });
138
+ } else if (place.protocol === null) {
115
139
  report(root, node, `App ${app.name} lists ${module.name}, the application module of feature ${place.feature}; apps import only transport modules (${place.feature}-<protocol>.module.ts), which import the application module themselves.`, { app: app.name, module: module.name });
116
140
  } else if (!(composedBy.get(place.protocol) ?? []).includes(app.kind)) {
117
141
  const allowed = [...composedBy].filter(([, kinds]) => kinds.includes(app.kind)).map(([protocol]) => protocol).sort();