@starci/hfs 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (515) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/db/migrate.mjs +361 -0
  3. package/db/post-cutover.mjs +136 -0
  4. package/emit/db-types.mjs +10 -1
  5. package/lint/run.mjs +1 -1
  6. package/package.json +4 -1
  7. package/release/gate.mjs +298 -0
  8. package/release/index.mjs +103 -0
  9. package/release/push.mjs +110 -0
  10. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  11. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  12. package/runtime/knowledge/hfs/rules.yaml +3401 -152
  13. package/runtime/knowledge/hfs/slots.yaml +344 -62
  14. package/runtime/knowledge/patterns/be/api.yaml +32 -5
  15. package/runtime/knowledge/patterns/be/cli.yaml +14 -4
  16. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  17. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  18. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  19. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  20. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  21. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  22. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  23. package/runtime/knowledge/patterns/be/webhooks.yaml +23 -4
  24. package/runtime/knowledge/patterns/fe/folder.yaml +1 -0
  25. package/runtime/knowledge/sonar-gate.yaml +1 -1
  26. package/runtime/modules/kernel/failure-codes.yaml +3113 -43
  27. package/runtime/modules/models/runtimes.yaml +1 -1
  28. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  29. package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
  30. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  31. package/runtime/scripts/api/git/add.mjs +5 -0
  32. package/runtime/scripts/api/git/init.mjs +7 -0
  33. package/runtime/scripts/api/git/lib.mjs +3 -0
  34. package/runtime/scripts/api/git/tag.mjs +5 -0
  35. package/runtime/scripts/api/node/lib.mjs +20 -0
  36. package/runtime/scripts/api/node/run-node.mjs +6 -0
  37. package/runtime/scripts/api/sops/lib.mjs +41 -0
  38. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  39. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  40. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  41. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  42. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  43. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  44. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +1 -8
  45. package/runtime/scripts/hfs/architecture/backend.mjs +2 -7
  46. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  47. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  48. package/runtime/scripts/hfs/architecture/dead-exports.mjs +9 -41
  49. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  50. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  51. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  52. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  53. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +28 -13
  54. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  55. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  56. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  57. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  58. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -2
  59. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  60. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +211 -11
  61. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -1
  62. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  63. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  64. package/runtime/scripts/hfs/architecture/tiers.mjs +3 -3
  65. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +8 -2
  66. package/runtime/scripts/hfs/check-results.mjs +28 -0
  67. package/runtime/scripts/hfs/check.mjs +46 -47
  68. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  69. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  70. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  71. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  72. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  73. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  74. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  75. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  76. package/runtime/scripts/hfs/manifest-shape.mjs +67 -7
  77. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  78. package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
  79. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  80. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  81. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  82. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  83. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  84. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  85. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  86. package/runtime/scripts/hfs/rules/contract.mjs +66 -5
  87. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  88. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  89. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  90. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  91. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  92. package/runtime/scripts/hfs/rules/database-migrations.mjs +286 -7
  93. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  94. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  95. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  96. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  97. package/runtime/scripts/hfs/rules/database.mjs +9 -0
  98. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  99. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  100. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  101. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  102. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  103. package/runtime/scripts/hfs/rules/edition.mjs +9 -8
  104. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  105. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  106. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  107. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  108. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  109. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  110. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  111. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  112. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  113. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  114. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  115. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  116. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  117. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  118. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  119. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  120. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  121. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  122. package/runtime/scripts/hfs/rules/secrets.mjs +13 -0
  123. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  124. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  125. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  126. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  127. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  128. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  129. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +41 -1
  130. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  131. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
  132. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  133. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  134. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  135. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  136. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  137. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  138. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  139. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  140. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  141. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  142. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  143. package/runtime/scripts/hfs/secret-init.mjs +127 -19
  144. package/runtime/scripts/hfs/secret-sops.mjs +38 -6
  145. package/runtime/scripts/hfs/secret-store.mjs +56 -54
  146. package/runtime/scripts/hfs/secret.mjs +34 -5
  147. package/runtime/scripts/hfs/slot-imports.mjs +18 -5
  148. package/runtime/scripts/hfs/slots.mjs +1 -0
  149. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  150. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  151. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  152. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  153. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  154. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  155. package/runtime/scripts/lib/compose-values.mjs +11 -0
  156. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  157. package/runtime/scripts/lib/json-clone.mjs +61 -0
  158. package/runtime/scripts/lib/list.mjs +4 -0
  159. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  160. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  161. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  162. package/runtime/scripts/lib/stack-services.mjs +1 -1
  163. package/runtime/scripts/lib/swarm-stack.mjs +5 -18
  164. package/runtime/scripts/lib/trim-end.mjs +8 -0
  165. package/runtime/scripts/lib/ts-analysis.mjs +5 -2
  166. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  167. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  168. package/scaffold/add-app.mjs +77 -59
  169. package/scaffold/add-cli-lite.mjs +14 -6
  170. package/scaffold/add-compose.mjs +79 -0
  171. package/scaffold/add-database-lite.mjs +14 -0
  172. package/scaffold/add-fe-overlay.mjs +122 -0
  173. package/scaffold/add-fe-page.mjs +269 -0
  174. package/scaffold/add-inbox-lite.mjs +74 -42
  175. package/scaffold/add-table.mjs +110 -87
  176. package/scaffold/add.mjs +159 -44
  177. package/scaffold/app.mjs +124 -39
  178. package/scaffold/edition-gate.mjs +3 -3
  179. package/scaffold/lite-exports.mjs +2 -1
  180. package/scaffold/service.mjs +2 -2
  181. package/src/main.mjs +229 -116
  182. package/stack/age-install.mjs +37 -0
  183. package/stack/backup.mjs +100 -0
  184. package/stack/build-args.mjs +42 -0
  185. package/stack/build.mjs +173 -0
  186. package/stack/cli.mjs +130 -0
  187. package/stack/config.mjs +57 -0
  188. package/stack/context.mjs +57 -0
  189. package/stack/converge.mjs +107 -0
  190. package/stack/deploy.mjs +301 -0
  191. package/stack/digest.mjs +16 -0
  192. package/stack/docker-transport.mjs +53 -0
  193. package/stack/edge-alias.mjs +118 -0
  194. package/stack/errors.mjs +21 -0
  195. package/stack/image-scan.mjs +152 -0
  196. package/stack/ingress-create.mjs +114 -0
  197. package/stack/ingress-publish.mjs +273 -0
  198. package/stack/ingress.mjs +706 -0
  199. package/stack/jobs.mjs +51 -0
  200. package/stack/live-restore.mjs +177 -0
  201. package/stack/prepare.mjs +266 -0
  202. package/stack/private-file.mjs +22 -0
  203. package/stack/provision.mjs +90 -0
  204. package/stack/prune.mjs +46 -0
  205. package/stack/release-store.mjs +87 -0
  206. package/stack/remote.mjs +205 -0
  207. package/stack/render.mjs +149 -0
  208. package/stack/retire.mjs +47 -0
  209. package/stack/sealed.mjs +149 -0
  210. package/stack/task.mjs +42 -0
  211. package/stack/verify.mjs +99 -0
  212. package/sync/ci-commands.mjs +13 -0
  213. package/sync/index.mjs +108 -52
  214. package/sync/stack-images.mjs +127 -0
  215. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  216. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  217. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  218. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  219. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  220. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  221. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  222. package/templates/app/codegen-graphql.mjs.partial +13 -2
  223. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  224. package/templates/app/codegen-prelude.mjs.partial +8 -0
  225. package/templates/app/gitignore +3 -0
  226. package/templates/app/gitignore-lite +3 -0
  227. package/templates/app/package-scripts/package.json +1 -0
  228. package/templates/app/package-scripts/supabase.json +7 -0
  229. package/templates/app/package-scripts-lite/package.json +1 -7
  230. package/templates/app/skeleton/.editorconfig +1 -0
  231. package/templates/app/skeleton/.gitattributes +7 -1
  232. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  233. package/templates/app/skeleton/.nvmrc +1 -1
  234. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  235. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  236. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  237. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  238. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  239. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  240. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  241. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  242. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  243. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  244. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  245. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  246. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  247. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  248. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  249. package/templates/app/skeleton/README.md +6 -6
  250. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  251. package/templates/app/skeleton-lite/.editorconfig +16 -0
  252. package/templates/app/skeleton-lite/.gitattributes +8 -0
  253. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  254. package/templates/app/skeleton-lite/.nvmrc +1 -0
  255. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  256. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +178 -1
  257. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  258. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  259. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  260. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  261. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  262. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  263. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  264. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  265. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  266. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  267. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  268. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  269. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  270. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  271. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  272. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  273. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  274. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  275. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  276. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  277. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  278. package/templates/app/skeleton-lite/README.md +5 -3
  279. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  280. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  281. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  282. package/templates/be/image/worker/Dockerfile +1 -1
  283. package/templates/be/inbox/database-client.spec.ts.tpl +17 -18
  284. package/templates/be/inbox/database-client.ts.tpl +12 -11
  285. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  286. package/templates/be/inbox/event-index.ts.tpl +1 -0
  287. package/templates/be/inbox/events.json.tpl +10 -0
  288. package/templates/be/inbox/migration.sql.tpl +1 -0
  289. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  290. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  291. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  292. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  293. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  294. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  295. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  296. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  297. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  298. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  299. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  300. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  301. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  302. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  303. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  304. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  305. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  306. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  307. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  308. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  309. package/templates/be/inbox/service.spec.ts.tpl +42 -9
  310. package/templates/be/inbox/service.ts.tpl +23 -8
  311. package/templates/be/inbox/webhook-door.spec.ts.tpl +1 -1
  312. package/templates/be/inbox/webhook-door.ts.tpl +2 -2
  313. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  314. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  315. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  316. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  317. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  318. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  319. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  320. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  321. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  322. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  323. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  324. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  325. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  326. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  327. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  328. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  329. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  330. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  331. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  332. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  333. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  334. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  335. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  336. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  337. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  338. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  339. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  340. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  341. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  342. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  343. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  344. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  345. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  346. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  347. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  348. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  349. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  350. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  351. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  352. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  353. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  354. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  355. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  356. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  357. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  358. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  359. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  360. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  362. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  363. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  364. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  365. package/templates/be/skeleton/apps/__app__/src/app.module.ts +11 -9
  366. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  367. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  368. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +2 -0
  369. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  370. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  371. package/templates/be/skeleton/apps/__app__/src/main.ts +15 -1
  372. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  373. package/templates/be/skeleton/apps/cli/src/cli.options.ts +6 -14
  374. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  375. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  376. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  377. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  378. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  379. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  380. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  381. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  382. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  383. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  384. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  385. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  386. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  387. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  388. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  389. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  390. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  391. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  392. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  393. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  394. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  395. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  396. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  397. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  398. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  399. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  400. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  401. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  402. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  403. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  404. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  405. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  406. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  407. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  408. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  409. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  410. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  411. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  412. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  413. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  414. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  415. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  416. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  417. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  418. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  419. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  420. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  421. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  422. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  423. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  424. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  425. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  426. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  427. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  428. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  429. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  430. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  431. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  432. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  433. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  434. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  435. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  436. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  437. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  438. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  439. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  440. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  441. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  442. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  443. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  444. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  445. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  446. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +14 -18
  447. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  448. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  449. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  450. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +2 -0
  451. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  452. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  453. package/templates/be/skeleton-lite/apps/api/src/main.ts +2 -3
  454. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  455. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  456. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  457. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  458. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  459. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  460. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  461. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  462. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  463. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  464. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  465. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  466. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  467. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  468. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  469. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  470. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  471. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  472. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  473. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  474. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  475. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  476. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  477. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  478. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  479. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  480. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  481. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  482. package/templates/be/table/migration.sql.tpl +1 -0
  483. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  484. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  485. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  486. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  487. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  488. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  489. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  490. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  491. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  492. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  493. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  494. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  495. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  496. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  497. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  498. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  499. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  500. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  501. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  502. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  503. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  504. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  505. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  506. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  507. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  508. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  509. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  510. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  511. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  512. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  513. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  514. package/upgrade/check.mjs +3 -1
  515. package/upgrade/index.mjs +53 -40
@@ -7,7 +7,7 @@ import {
7
7
  } from "../runtime/scripts/hfs/slots.mjs";
8
8
  import { imageFiles, render, TEMPLATES_DIR } from "../sync/index.mjs";
9
9
  import { ScaffoldError, pascalOf } from "./service.mjs";
10
- import { PARTIAL_VARIABLES, coreFeApps, jsonText, packageJsonText } from "./app.mjs";
10
+ import { PARTIAL_VARIABLES, bundledPins, callbackSourceFiles, coreFeApps, hasAuthCallback, jsonText, packageJsonText } from "./app.mjs";
11
11
 
12
12
  /** The shared fe app core (modules/api, auth, toast, redux, the providers, the hooks): the base tree, then the edition overlay. */
13
13
  const CORE_DIRS = Object.freeze({
@@ -35,37 +35,8 @@ const fill = (text, values, file) =>
35
35
  return values[key];
36
36
  });
37
37
 
38
- /**
39
- * Adds one Next workspace from the edition's canonical app skeleton and the shared app core (both editions, owner ruling
40
- * 2026-10-13: lite and full have one fe structure), then registers it in hfs.json. The root workspace glob is added only when an
41
- * older app manifest does not already carry it.
42
- */
43
- export function addApp({ root, name }) {
44
- if (!NAME.test(String(name)))
45
- throw new ScaffoldError(
46
- "HFS_ADD_NAME_INVALID",
47
- `app name ${name} must be kebab-case (letters, digits and single dashes)`,
48
- );
49
- const declarationFile = path.join(root, "hfs.json");
50
- const packageFile = path.join(root, "package.json");
51
- if (!fs.existsSync(declarationFile) || !fs.existsSync(packageFile))
52
- throw new ScaffoldError(
53
- "HFS_ADD_NOT_AN_APP",
54
- "add app runs at the app root containing hfs.json and package.json",
55
- );
56
- const manifest = loadSlotManifest();
57
- const repo = readRepoDeclaration(manifest, root);
58
- if (!repo.sides?.fe)
59
- throw new ScaffoldError(
60
- "HFS_ADD_NO_FRONT_END",
61
- "this app has no front-end side",
62
- );
63
- if (repo.sides.fe.apps.some((app) => app.name === name))
64
- throw new ScaffoldError(
65
- "HFS_ADD_EXISTS",
66
- `front-end app ${name} is already declared`,
67
- );
68
-
38
+ /** Render the edition skeleton followed by its shared core and edition overlay. */
39
+ function planAppTemplates(repo, name) {
69
40
  const source = path.join(
70
41
  TEMPLATES_DIR,
71
42
  "fe",
@@ -94,8 +65,13 @@ export function addApp({ root, name }) {
94
65
  if (!fs.existsSync(dir)) continue;
95
66
  for (const relative of listFiles(dir)) byPath.set(relative, { relative: `${targetBase}/${relative}`, body: readTemplate(dir, relative) });
96
67
  }
97
- const planned = [...byPath.values()];
68
+ return callbackSourceFiles([...byPath.values()].map(file => ({ path: file.relative, content: file.body })), repo)
69
+ .map(file => ({ relative: file.path, body: file.content }));
70
+ }
98
71
 
72
+ /** The package and TypeScript manifests of a new Next workspace. */
73
+ function planWorkspaceManifests(root, repo, name) {
74
+ const targetBase = `fe/apps/${name}`;
99
75
  // The dependency source is a product app (it carries the core's fe-stack packages), else the first Next app.
100
76
  const firstApp = coreFeApps(repo.sides.fe.apps)[0] ?? repo.sides.fe.apps.find((app) => app.kind === "next");
101
77
  const firstPackage = firstApp
@@ -108,8 +84,14 @@ export function addApp({ root, name }) {
108
84
  );
109
85
  const workspacePackage = JSON.parse(fs.readFileSync(firstPackage, "utf8"));
110
86
  workspacePackage.name = `@${repo.project}/${name}`;
87
+ if (hasAuthCallback(repo)) {
88
+ const pins = bundledPins();
89
+ workspacePackage.dependencies ??= {};
90
+ for (const dependency of ["@supabase/ssr", "@supabase/supabase-js", "server-only"])
91
+ workspacePackage.dependencies[dependency] = pins[dependency].version;
92
+ }
111
93
  const appTemplates = path.join(TEMPLATES_DIR, "fe", "app");
112
- planned.push(
94
+ return [
113
95
  {
114
96
  relative: `${targetBase}/package.json`,
115
97
  body:
@@ -121,32 +103,10 @@ export function addApp({ root, name }) {
121
103
  relative: `${targetBase}/tsconfig.json`,
122
104
  body: fs.readFileSync(path.join(appTemplates, "tsconfig.json"), "utf8"),
123
105
  },
124
- );
125
- const declaration = JSON.parse(fs.readFileSync(declarationFile, "utf8"));
126
- declaration.sides.fe.apps.push({ name, kind: "next" });
127
- if (repo.edition === "full") {
128
- const image = imageFiles(
129
- resolveRepoDeclaration(manifest, declaration),
130
- ).find((file) => file.path === `${targetBase}/Dockerfile`);
131
- if (!image)
132
- throw new ScaffoldError(
133
- "HFS_ADD_TEMPLATE_MISSING",
134
- `the Dockerfile of front-end app ${name} did not render`,
135
- );
136
- planned.push({ relative: image.path, body: image.content });
137
- }
138
-
139
- const clashes = planned
140
- .filter((file) =>
141
- fs.existsSync(path.join(root, ...file.relative.split("/"))),
142
- )
143
- .map((file) => file.relative);
144
- if (clashes.length)
145
- throw new ScaffoldError(
146
- "HFS_ADD_EXISTS",
147
- `front-end app ${name} already exists: ${clashes.join(", ")}`,
148
- );
106
+ ];
107
+ }
149
108
 
109
+ function persistAppFiles({ root, name, planned, declaration, declarationFile, packageFile }) {
150
110
  const rootPackage = JSON.parse(fs.readFileSync(packageFile, "utf8"));
151
111
  const oldDeclaration = fs.readFileSync(declarationFile, "utf8");
152
112
  const oldPackage = fs.readFileSync(packageFile, "utf8");
@@ -173,6 +133,64 @@ export function addApp({ root, name }) {
173
133
  fs.writeFileSync(packageFile, oldPackage);
174
134
  throw error;
175
135
  }
136
+ }
137
+
138
+ /**
139
+ * Adds one Next workspace from the edition's canonical app skeleton and the shared app core (both editions, owner ruling
140
+ * 2026-10-13: lite and full have one fe structure), then registers it in hfs.json. The root workspace glob is added only when an
141
+ * older app manifest does not already carry it.
142
+ */
143
+ export function addApp({ root, name }) {
144
+ if (!NAME.test(String(name)))
145
+ throw new ScaffoldError(
146
+ "HFS_ADD_NAME_INVALID",
147
+ `app name ${name} must be kebab-case (letters, digits and single dashes)`,
148
+ );
149
+ const declarationFile = path.join(root, "hfs.json");
150
+ const packageFile = path.join(root, "package.json");
151
+ if (!fs.existsSync(declarationFile) || !fs.existsSync(packageFile))
152
+ throw new ScaffoldError(
153
+ "HFS_ADD_NOT_AN_APP",
154
+ "add app runs at the app root containing hfs.json and package.json",
155
+ );
156
+ const manifest = loadSlotManifest();
157
+ const repo = readRepoDeclaration(manifest, root);
158
+ if (!repo.sides?.fe)
159
+ throw new ScaffoldError(
160
+ "HFS_ADD_NO_FRONT_END",
161
+ "this app has no front-end side",
162
+ );
163
+ if (repo.sides.fe.apps.some((app) => app.name === name))
164
+ throw new ScaffoldError(
165
+ "HFS_ADD_EXISTS",
166
+ `front-end app ${name} is already declared`,
167
+ );
168
+ const targetBase = `fe/apps/${name}`;
169
+ const planned = [...planAppTemplates(repo, name), ...planWorkspaceManifests(root, repo, name)];
170
+ const declaration = JSON.parse(fs.readFileSync(declarationFile, "utf8"));
171
+ declaration.sides.fe.apps.push({ name, kind: "next" });
172
+ const image = imageFiles(
173
+ resolveRepoDeclaration(manifest, declaration),
174
+ ).find((file) => file.path === `${targetBase}/Dockerfile`);
175
+ if (!image && repo.edition !== "lite")
176
+ throw new ScaffoldError(
177
+ "HFS_ADD_TEMPLATE_MISSING",
178
+ `the Dockerfile of front-end app ${name} did not render`,
179
+ );
180
+ if (image) planned.push({ relative: image.path, body: image.content });
181
+
182
+ const clashes = planned
183
+ .filter((file) =>
184
+ fs.existsSync(path.join(root, ...file.relative.split("/"))),
185
+ )
186
+ .map((file) => file.relative);
187
+ if (clashes.length)
188
+ throw new ScaffoldError(
189
+ "HFS_ADD_EXISTS",
190
+ `front-end app ${name} already exists: ${clashes.join(", ")}`,
191
+ );
192
+
193
+ persistAppFiles({ root, name, planned, declaration, declarationFile, packageFile });
176
194
  return {
177
195
  created: planned.map((file) => file.relative),
178
196
  registered: { app: name, workspace: "fe/apps/*" },
@@ -109,10 +109,15 @@ function enableLiteCliDatabase(root) {
109
109
  text = text.replace(anchor, `${anchor}\nimport { MigrationRunnerService } from "./migration-runner.service"`);
110
110
  }
111
111
  if (!text.includes('import { SeedRunnerService } from "./seed-runner.service"')) {
112
- const anchor = 'import { PRIMARY_ENTITY_MANAGER } from "./primary.decorators"';
113
- if (!text.includes(anchor)) throw new ScaffoldError("HFS_ADD_CLI_DATABASE", `${relative} has an unknown import shape`);
112
+ // The import of the entity-manager token, with or without the probe token beside it.
113
+ const anchor = /^import \{[^}]*\bPRIMARY_ENTITY_MANAGER\b[^}]*\} from "\.\/primary\.decorators"$/m.exec(text)?.[0];
114
+ if (!anchor) throw new ScaffoldError("HFS_ADD_CLI_DATABASE", `${relative} has an unknown import shape`);
114
115
  text = text.replace(anchor, `${anchor}\nimport { SeedRunnerService } from "./seed-runner.service"`);
115
116
  }
117
+ const probeProviders = "providers: [...(base.providers ?? []), ...managers, ...probes]";
118
+ if (text.includes(probeProviders)) {
119
+ text = text.replace(probeProviders, "providers: [...(base.providers ?? []), ...managers, ...probes, MigrationRunnerService, SeedRunnerService]");
120
+ }
116
121
  const providers = "providers: [...(base.providers ?? []), ...managers]";
117
122
  if (text.includes(providers)) {
118
123
  text = text.replace(providers, "providers: [...(base.providers ?? []), ...managers, MigrationRunnerService, SeedRunnerService]");
@@ -180,6 +185,9 @@ export function ensureLiteCli({ root, manifest, repo }) {
180
185
  "be/apps/cli/src/app.module.ts",
181
186
  read("be/skeleton/apps/cli/src/app.module.ts"),
182
187
  ],
188
+ ["be/apps/cli/src/compose/platform.ts", read("be/skeleton-lite/apps/cli/src/compose/platform.ts")],
189
+ ["be/apps/cli/src/compose/cli.ts", read("be/skeleton/apps/cli/src/compose/cli.ts")],
190
+ ["be/src/tests/fixtures/builders/cli.builder.ts", read("be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts")],
183
191
  ["be/apps/cli/src/cli.env.ts", read("be/skeleton-lite/apps/cli/src/cli.env.ts")],
184
192
  ["be/apps/cli/src/cli.options.ts", CLI_OPTIONS],
185
193
  [
@@ -203,7 +211,7 @@ export function ensureLiteCli({ root, manifest, repo }) {
203
211
  read("be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts"),
204
212
  ],
205
213
  ["be/src/features/cli/migrate/subs/run.cli.ts", MIGRATE_RUN],
206
- // Owner ruling 2026-10-12: each cli command of a lite back end owes its unit spec, as in full (ruleParams.be.unitRoles).
214
+ // ruling:R212/case-1, amended by ruling:R212/case-4: each cli command of a lite back end owes its unit spec, as in full (ruleParams.be.unitRoles).
207
215
  [
208
216
  "be/src/features/cli/migrate/migrate.cli.spec.ts",
209
217
  read("be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts"),
@@ -255,7 +263,7 @@ export function ensureLiteCli({ root, manifest, repo }) {
255
263
  );
256
264
  const nest = JSON.parse(fs.readFileSync(nestFile, "utf8"));
257
265
  nest.projects = {
258
- ...(nest.projects ?? {}),
266
+ ...nest.projects,
259
267
  cli: {
260
268
  type: "application",
261
269
  root: "apps/cli",
@@ -269,9 +277,9 @@ export function ensureLiteCli({ root, manifest, repo }) {
269
277
  const pin = parseYaml(fs.readFileSync(PINS_FILE, "utf8")).pins?.["nest-commander"]?.version;
270
278
  if (typeof pin !== "string" || pin === "")
271
279
  throw new ScaffoldError("HFS_ADD_CANON_PIN_MISSING", "add cli needs the nest-commander canon pin");
272
- packageManifest.dependencies = { ...(packageManifest.dependencies ?? {}), "nest-commander": pin };
280
+ packageManifest.dependencies = { ...packageManifest.dependencies, "nest-commander": pin };
273
281
  packageManifest.scripts = {
274
- ...(packageManifest.scripts ?? {}),
282
+ ...packageManifest.scripts,
275
283
  ...managedScripts(resolved, appSource(root)),
276
284
  };
277
285
  for (const [relative, body] of files) {
@@ -0,0 +1,79 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { TEMPLATES_DIR } from '../sync/index.mjs';
4
+ import { ScaffoldError } from './service.mjs';
5
+
6
+ /** Adds `importLine` and one `symbol` entry of the `imports` array of a module file's lines, once. */
7
+ export function addModuleImport(lines, { importLine, entry, relative, noun }) {
8
+ const active = compositionCode(lines.join('\n')).split('\n');
9
+ const imported = active.some(line => line.replace(/;\s*$/, '') === importLine);
10
+ const registered = active.some(line => line.trim() === `${entry},` ||
11
+ (/\bimports:\s*\[/.test(line) && !line.trim().startsWith('//') && line.replace(/\s+/g, '').includes(entry.replace(/\s+/g, ''))));
12
+ const lastImport = active.reduce((last, line, index) => line.startsWith('import ') ? index : last, -1);
13
+ const importsLine = line => /\bimports:\s*\[/.test(line) && !line.trim().startsWith('//');
14
+ if (lastImport < 0 || active.findIndex(importsLine) < 0) throw new ScaffoldError('HFS_ADD_WIRE_INVALID', `add ${noun} could not find the imports array in ${relative}`);
15
+ if (imported && registered) return;
16
+ if (!imported) lines.splice(lastImport + 1, 0, importLine);
17
+ if (registered) return;
18
+ const imports = compositionCode(lines.join('\n')).split('\n').findIndex(importsLine);
19
+ const indent = /^(\s*)/.exec(lines[imports])?.[1] ?? '';
20
+ if (/^\s*imports: \[$/.test(lines[imports])) lines.splice(imports + 1, 0, `${indent} ${entry},`);
21
+ else lines[imports] = lines[imports].replace(/(\bimports:\s*\[)\s*/, `$1${entry}, `);
22
+ }
23
+
24
+ /** Masks comments without changing offsets or treating quoted module specifiers as comments. */
25
+ export function compositionCode(text) {
26
+ return text.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|\/\*[\s\S]*?\*\/|\/\/[^\n]*/g,
27
+ value => value.startsWith('/') ? value.replace(/[^\r\n]/g, ' ') : value);
28
+ }
29
+
30
+
31
+ /** Reads the explicitly typed expression body of a group without treating comments as active declarations. */
32
+ export function readComposeGroup({ repoRoot, repo, app, area, noun }) {
33
+ const relative = `be/apps/${app}/src/compose/${area}.ts`;
34
+ const file = path.join(repoRoot, ...relative.split('/'));
35
+ const exists = fs.existsSync(file);
36
+ const skeleton = repo.edition === 'lite' ? 'skeleton-lite/apps/api' : 'skeleton/apps/__app__';
37
+ const template = path.join(TEMPLATES_DIR, 'be', ...skeleton.split('/'), 'src', 'compose', `${area}.ts`);
38
+ const text = fs.readFileSync(exists ? file : template, 'utf8').replace(/\r\n/g, '\n');
39
+ const argument = area === 'domain' && repo.edition === 'lite' ? 'verifyAccessToken: VerifySupabaseAccessToken' : '';
40
+ const marker = `export const ${area}Modules = (${argument}): Array<Type<unknown> | DynamicModule> => [`;
41
+ const declarationAt = compositionCode(text).indexOf(`\n${marker}`);
42
+ const active = declarationAt >= 0;
43
+ const tail = active ? text.slice(declarationAt + 1 + marker.length).trimEnd().replace(/;$/, '').trimEnd() : '';
44
+ const body = tail.endsWith(']') ? tail.slice(0, -1) : undefined;
45
+ if (body === undefined) throw new ScaffoldError('HFS_ADD_WIRE_INVALID', `add ${noun} needs the typed ${area}Modules group in ${relative}`);
46
+ return { relative, file, exists, text, marker, body };
47
+ }
48
+
49
+ /** Creates a typed group from the edition's template, or adds members without duplicating inline registrations or semicolon imports. */
50
+ export function planComposeGroup(input) {
51
+ const { relative, file, exists, marker, body, text: original } = readComposeGroup(input);
52
+ let text = original;
53
+ let compactBody = compositionCode(body).replace(/\s+/g, '');
54
+ for (const { importLine, entry } of input.entries) {
55
+ const lines = text.split('\n');
56
+ if (!lines.some(line => line.replace(/;\s*$/, '') === importLine)) {
57
+ const lastImport = lines.reduce((last, line, index) => line.startsWith('import ') ? index : last, -1);
58
+ lines.splice(lastImport + 1, 0, importLine);
59
+ text = lines.join('\n');
60
+ }
61
+ const compactEntry = entry.replace(/\s+/g, '');
62
+ const members = `,${compactBody},`;
63
+ if (!members.includes(`,${compactEntry},`)) {
64
+ const afterMarker = compositionCode(text).indexOf(marker) + marker.length;
65
+ const empty = text.slice(afterMarker).startsWith(']');
66
+ text = `${text.slice(0, afterMarker)}\n ${entry},${empty ? '\n' : ''}${text.slice(afterMarker)}`;
67
+ compactBody = `${compactEntry},${compactBody}`;
68
+ }
69
+ }
70
+ return { relative, file, exists, text };
71
+ }
72
+
73
+ /** Writes the planned group and reports a newly created composition file. */
74
+ export function writeComposeGroup(input) {
75
+ const { relative, file, exists, text } = planComposeGroup(input);
76
+ fs.mkdirSync(path.dirname(file), { recursive: true });
77
+ fs.writeFileSync(file, text);
78
+ return exists ? [] : [relative];
79
+ }
@@ -0,0 +1,14 @@
1
+ import { ScaffoldError } from './service.mjs';
2
+
3
+ /** Enables the lazy SQL identifier guard's error family when a lite generator installs database.sql.ts. */
4
+ export function withSqlIdentifierError(text) {
5
+ const code = ' IdentifierRejected = "DATABASE_IDENTIFIER_REJECTED",';
6
+ if (text.includes(code)) return text;
7
+ const enumMarker = 'export enum DatabaseErrorCode {';
8
+ const kindsMarker = 'export const DATABASE_ERROR_KINDS: Record<DatabaseErrorCode, ErrorKind> = {';
9
+ if (!text.includes(enumMarker) || !text.includes(kindsMarker)) {
10
+ throw new ScaffoldError('HFS_ADD_DATABASE_ERROR', 'platform/database has an unknown error-family shape');
11
+ }
12
+ return text.replace(enumMarker, enumMarker + '\n /** A dynamic SQL identifier was not on the names its caller allows. */\n' + code)
13
+ .replace(kindsMarker, kindsMarker + '\n [DatabaseErrorCode.IdentifierRejected]: "internal",');
14
+ }
@@ -0,0 +1,122 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ import { ScaffoldError, pascalOf } from "./service.mjs";
4
+ import { feName, readFeContext, readFeFile, requireNewFeOwner, planFeCatalogs, writeFePlan } from "./add-fe-page.mjs";
5
+
6
+ function loadTypeScript(root) {
7
+ try { return createRequire(path.join(root, "package.json"))("typescript"); } catch {
8
+ throw new ScaffoldError("HFS_ADD_TYPESCRIPT_MISSING", "add overlay reads the host page with its installed TypeScript compiler");
9
+ }
10
+ }
11
+
12
+ function pageArrow(ts, source, symbol) {
13
+ for (const statement of source.statements) {
14
+ if (!ts.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) continue;
15
+ const candidate = statement.declarationList.declarations.find((declaration) => declaration.name.getText(source) === symbol);
16
+ if (candidate?.initializer && ts.isArrowFunction(candidate.initializer)) return candidate.initializer;
17
+ }
18
+ return null;
19
+ }
20
+
21
+ function pageRender(ts, arrow) {
22
+ if (!arrow) return null;
23
+ if (!ts.isBlock(arrow.body)) {
24
+ let body = arrow.body;
25
+ while (ts.isParenthesizedExpression(body)) body = body.expression;
26
+ return body;
27
+ }
28
+ const returns = arrow.body.statements.filter(ts.isReturnStatement);
29
+ const unconditional = arrow.body.statements.every((statement) => ts.isVariableStatement(statement) || ts.isExpressionStatement(statement) || ts.isReturnStatement(statement));
30
+ if (!unconditional || returns.length !== 1) return null;
31
+ let render = returns[0].expression;
32
+ while (render && ts.isParenthesizedExpression(render)) render = render.expression;
33
+ return render;
34
+ }
35
+
36
+ function mountOverlay({ ts, before, relative, pageSymbol, symbol }) {
37
+ const source = ts.createSourceFile(relative, before, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
38
+ const client = source.statements.some((statement) => ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression) && statement.expression.text === "use client");
39
+ const render = pageRender(ts, pageArrow(ts, source, pageSymbol));
40
+ const isJsx = render && (ts.isJsxElement(render) || ts.isJsxSelfClosingElement(render) || ts.isJsxFragment(render));
41
+ if (source.parseDiagnostics.length || client || !isJsx) {
42
+ throw new ScaffoldError("HFS_ADD_WIRE_INVALID", `${relative} must export the server ${pageSymbol} arrow with one unconditional JSX return`);
43
+ }
44
+ if (before.split(/[^A-Za-z0-9_]/).includes(symbol)) {
45
+ throw new ScaffoldError("HFS_ADD_EXISTS", `${relative} already references ${symbol}`);
46
+ }
47
+ const from = render.getStart(source);
48
+ const wrapped = ["(", " <>", ` ${before.slice(from, render.end)}`, ` <${symbol} />`, " </>", " )"].join("\n");
49
+ const body = before.slice(0, from) + wrapped + before.slice(render.end);
50
+ const imports = source.statements.filter(ts.isImportDeclaration);
51
+ const last = imports.at(-1)?.end ?? 0;
52
+ const importLine = `import { ${symbol} } from "@/features/overlays/${symbol}"`;
53
+ return `${body.slice(0, last)}\n${importLine}${body.slice(last)}`;
54
+ }
55
+
56
+ function overlaySources(symbol, namespace) {
57
+ return {
58
+ entry: `"use client"
59
+
60
+ import { useTranslations } from "next-intl"
61
+ import { ${symbol}Base } from "./component"
62
+
63
+ /** Resolve the overlay's copy at its client interaction boundary. */
64
+ export const ${symbol} = () => {
65
+ const t = useTranslations(${JSON.stringify(namespace)})
66
+ return <${symbol}Base props={{ title: t("title"), open: t("open"), close: t("close"), description: t("description") }} />
67
+ }
68
+ `,
69
+ component: `import { Button, Dialog, Text } from "@starci/grammar/common"
70
+
71
+ /** Settled labels and content for the overlay's accessible dialog. */
72
+ export interface ${symbol}BaseProps {
73
+ readonly props: {
74
+ readonly title: string
75
+ readonly open: string
76
+ readonly close: string
77
+ readonly description: string
78
+ }
79
+ }
80
+
81
+ /** Draw an accessible dialog with an intrinsic trigger and dismissal. */
82
+ export const ${symbol}Base = (props: ${symbol}BaseProps) => (
83
+ <Dialog
84
+ title={props.props.title}
85
+ closeLabel={props.props.close}
86
+ trigger={<Button>{props.props.open}</Button>}
87
+ >
88
+ <Text>{props.props.description}</Text>
89
+ </Dialog>
90
+ )
91
+ `,
92
+ };
93
+ }
94
+
95
+ /** Add a localized client overlay and mount its public entry in a server feature page. */
96
+ export function addFeOverlay({ root, app, name, page, messages, ts }) {
97
+ feName(name, "overlay name");
98
+ if (page === undefined) throw new ScaffoldError("HFS_ADD_OPTION_MISSING", "add overlay needs --page naming its server feature page");
99
+ feName(page, "host page name");
100
+ const context = readFeContext({ root, app });
101
+ const symbol = `${pascalOf(name)}Overlay`;
102
+ const owner = `${context.source}/features/overlays/${symbol}`;
103
+ requireNewFeOwner(context, owner);
104
+ const pageSymbol = `${pascalOf(page)}Page`;
105
+ const relative = `${context.source}/features/pages/${pageSymbol}/index.tsx`;
106
+ const before = readFeFile(context, relative);
107
+ const body = mountOverlay({ ts: ts ?? loadTypeScript(context.root), before, relative, pageSymbol, symbol });
108
+ const catalogs = planFeCatalogs(context, {
109
+ group: "overlays", name, messages,
110
+ defaults: {
111
+ vi: { title: "Th\u00f4ng tin", open: "M\u1edf th\u00f4ng tin", close: "\u0110\u00f3ng", description: "Xem th\u00f4ng tin c\u1ee7a trang n\u00e0y." },
112
+ en: { title: "Information", open: "Open information", close: "Close", description: "View information about this page." },
113
+ },
114
+ });
115
+ const sources = overlaySources(symbol, catalogs.namespace);
116
+ const created = [
117
+ { relative: `${owner}/index.tsx`, body: sources.entry },
118
+ { relative: `${owner}/component.tsx`, body: sources.component },
119
+ ];
120
+ const updates = [...catalogs.updates, { relative, before, body }];
121
+ return { ...writeFePlan(context, created, updates), registered: { app: context.app, overlay: symbol, page: pageSymbol } };
122
+ }