@starci/hfs 4.0.8 → 4.0.10

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 (703) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +81 -39
  4. package/emit/contracts.mjs +56 -9
  5. package/emit/db-types.mjs +169 -0
  6. package/emit/events.mjs +41 -0
  7. package/emit/operations-worker.mjs +1 -1
  8. package/emit/operations.mjs +2 -2
  9. package/emit/schema-worker.mjs +1 -1
  10. package/lint/bound-sys.cjs +50 -0
  11. package/lint/run.mjs +131 -24
  12. package/package.json +16 -10
  13. package/report/sonar.mjs +8 -8
  14. package/runtime/engine/admission.mjs +308 -0
  15. package/runtime/engine/canonical-json.mjs +10 -0
  16. package/runtime/engine/config.mjs +486 -0
  17. package/runtime/engine/db/blob.mjs +315 -0
  18. package/runtime/engine/db/ledger-paths.mjs +83 -0
  19. package/runtime/engine/db/ledger.mjs +1118 -0
  20. package/runtime/engine/db/machine-connection.mjs +135 -0
  21. package/runtime/engine/db/machine-schema.mjs +84 -0
  22. package/runtime/engine/db/machine.mjs +1367 -0
  23. package/runtime/engine/db/migrations/machine/0001-init.sql +924 -0
  24. package/runtime/engine/db/migrations/runtime/0001-init.sql +1108 -0
  25. package/runtime/engine/db/provider-reservations.mjs +101 -0
  26. package/runtime/engine/digest.mjs +16 -0
  27. package/runtime/engine/invalid-config.mjs +15 -0
  28. package/runtime/engine/orca-config.mjs +27 -0
  29. package/runtime/engine/refuse.mjs +11 -0
  30. package/runtime/engine/secrets.mjs +130 -0
  31. package/runtime/knowledge/hfs/canon-pins.yaml +162 -31
  32. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
  33. package/runtime/knowledge/hfs/rules.yaml +2059 -0
  34. package/runtime/knowledge/hfs/slots.yaml +912 -120
  35. package/runtime/knowledge/patterns/be/api.yaml +369 -0
  36. package/runtime/knowledge/patterns/be/cli.yaml +297 -0
  37. package/runtime/knowledge/patterns/be/domain.yaml +113 -0
  38. package/runtime/knowledge/patterns/be/event-bus.yaml +285 -0
  39. package/runtime/knowledge/patterns/be/jobs.yaml +261 -0
  40. package/runtime/knowledge/patterns/be/projections.yaml +178 -0
  41. package/runtime/knowledge/patterns/be/queues.yaml +236 -0
  42. package/runtime/knowledge/patterns/be/reactors.yaml +140 -0
  43. package/runtime/knowledge/patterns/be/realtime.yaml +222 -0
  44. package/runtime/knowledge/patterns/be/saga.yaml +248 -0
  45. package/runtime/knowledge/patterns/be/webhooks.yaml +221 -0
  46. package/runtime/knowledge/patterns/fe/folder.yaml +131 -25
  47. package/runtime/knowledge/sonar-gate.yaml +52 -18
  48. package/runtime/modules/kernel/failure-codes.yaml +611 -51
  49. package/runtime/modules/models/registry.yaml +553 -0
  50. package/runtime/modules/models/runtimes.yaml +695 -0
  51. package/runtime/modules/ops/_labels.yaml +53 -0
  52. package/runtime/scripts/api/fs/forbidden-root.mjs +51 -0
  53. package/runtime/scripts/api/fs/is-link-like.mjs +25 -0
  54. package/runtime/scripts/api/fs/lib.mjs +16 -2
  55. package/runtime/scripts/api/fs/safe-remove.mjs +12 -132
  56. package/runtime/scripts/api/git/config-get.mjs +10 -0
  57. package/runtime/scripts/api/git/diff.mjs +5 -0
  58. package/runtime/scripts/api/git/lib.mjs +18 -26
  59. package/runtime/scripts/api/git/log.mjs +5 -0
  60. package/runtime/scripts/api/git/ls-files.mjs +6 -0
  61. package/runtime/scripts/api/git/ls-tree.mjs +5 -0
  62. package/runtime/scripts/api/git/merge-base.mjs +9 -0
  63. package/runtime/scripts/api/git/remote.mjs +5 -0
  64. package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
  65. package/runtime/scripts/api/git/show.mjs +6 -0
  66. package/runtime/scripts/api/node/lib.mjs +14 -0
  67. package/runtime/scripts/api/node/spawn-node.mjs +6 -0
  68. package/runtime/scripts/api/process/lib.mjs +111 -0
  69. package/runtime/scripts/api/process/owned-process.mjs +78 -0
  70. package/runtime/scripts/api/process/resolve-real-tool.mjs +46 -0
  71. package/runtime/scripts/api/process/run-program.mjs +7 -0
  72. package/runtime/scripts/api/process/stop-owned-process.mjs +9 -0
  73. package/runtime/scripts/api/sops/decrypt.mjs +9 -12
  74. package/runtime/scripts/api/sops/lib.mjs +255 -0
  75. package/runtime/scripts/api/sops/seal.mjs +22 -0
  76. package/runtime/scripts/connectors/lib.mjs +488 -0
  77. package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
  78. package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
  79. package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
  80. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
  81. package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
  82. package/runtime/scripts/hfs/architecture/config.mjs +9 -6
  83. package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
  84. package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
  85. package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
  86. package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
  87. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
  88. package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
  89. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +4 -10
  90. package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
  91. package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
  92. package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
  93. package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
  94. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -2
  95. package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
  96. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  97. package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
  98. package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
  99. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
  100. package/runtime/scripts/hfs/architecture/index.mjs +17 -4
  101. package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
  102. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
  103. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
  104. package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
  105. package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
  106. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
  107. package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
  108. package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
  109. package/runtime/scripts/hfs/architecture/schema-owner.mjs +17 -114
  110. package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
  111. package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
  112. package/runtime/scripts/hfs/architecture/source-names.mjs +27 -147
  113. package/runtime/scripts/hfs/architecture/sql-owner.mjs +59 -18
  114. package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
  115. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
  116. package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
  117. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
  118. package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
  119. package/runtime/scripts/hfs/architecture/surface.mjs +9 -3
  120. package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
  121. package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
  122. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
  123. package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
  124. package/runtime/scripts/hfs/architecture.mjs +4 -4
  125. package/runtime/scripts/hfs/check.mjs +75 -72
  126. package/runtime/scripts/hfs/coverage-scope.mjs +162 -0
  127. package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
  128. package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
  129. package/runtime/scripts/hfs/edition-slots.mjs +66 -0
  130. package/runtime/scripts/hfs/manifest-shape.mjs +109 -32
  131. package/runtime/scripts/hfs/param-names.mjs +18 -0
  132. package/runtime/scripts/hfs/path-findings.mjs +1 -1
  133. package/runtime/scripts/hfs/repo-identity.mjs +9 -6
  134. package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
  135. package/runtime/scripts/hfs/rules/app-root.mjs +51 -0
  136. package/runtime/scripts/hfs/rules/cli.mjs +37 -0
  137. package/runtime/scripts/hfs/rules/contract-compat.mjs +71 -0
  138. package/runtime/scripts/hfs/rules/contract.mjs +10 -9
  139. package/runtime/scripts/hfs/rules/database-config.mjs +102 -0
  140. package/runtime/scripts/hfs/rules/database-constants.mjs +13 -0
  141. package/runtime/scripts/hfs/rules/database-migrations.mjs +111 -0
  142. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +233 -0
  143. package/runtime/scripts/hfs/rules/database-sql.mjs +336 -0
  144. package/runtime/scripts/hfs/rules/database.mjs +68 -0
  145. package/runtime/scripts/hfs/rules/deps.mjs +1 -1
  146. package/runtime/scripts/hfs/rules/docker.mjs +259 -0
  147. package/runtime/scripts/hfs/rules/edition.mjs +111 -0
  148. package/runtime/scripts/hfs/rules/event-bus.mjs +153 -0
  149. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -1
  150. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
  151. package/runtime/scripts/hfs/rules/frontend-tree.mjs +9 -9
  152. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -6
  153. package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
  154. package/runtime/scripts/hfs/rules/lint-suppression.mjs +1 -1
  155. package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
  156. package/runtime/scripts/hfs/rules/peer-integrations.mjs +3 -3
  157. package/runtime/scripts/hfs/rules/pipeline.mjs +11 -21
  158. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -2
  159. package/runtime/scripts/hfs/rules/read.mjs +1 -1
  160. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +2 -2
  161. package/runtime/scripts/hfs/rules/saga.mjs +170 -0
  162. package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
  163. package/runtime/scripts/hfs/rules/services.mjs +220 -0
  164. package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
  165. package/runtime/scripts/hfs/rules/stacks.mjs +3 -3
  166. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
  167. package/runtime/scripts/hfs/rules/test-topology.mjs +2 -2
  168. package/runtime/scripts/hfs/secret.mjs +209 -0
  169. package/runtime/scripts/hfs/slots.mjs +78 -108
  170. package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
  171. package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
  172. package/runtime/scripts/hfs/tree.mjs +5 -4
  173. package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
  174. package/runtime/scripts/hfs/view.mjs +3 -2
  175. package/runtime/scripts/lib/clip.mjs +19 -0
  176. package/runtime/scripts/lib/display-names.mjs +259 -0
  177. package/runtime/scripts/lib/dockerfile.mjs +124 -0
  178. package/runtime/scripts/lib/env.mjs +10 -0
  179. package/runtime/scripts/lib/event-contract.mjs +103 -0
  180. package/runtime/scripts/lib/example-refs.mjs +158 -0
  181. package/runtime/scripts/lib/fs-kind.mjs +14 -4
  182. package/runtime/scripts/lib/git.mjs +36 -0
  183. package/runtime/scripts/lib/graphql-contract.mjs +1 -1
  184. package/runtime/scripts/lib/i18n.mjs +86 -0
  185. package/runtime/scripts/lib/json-schema.mjs +52 -0
  186. package/runtime/scripts/lib/json.mjs +13 -3
  187. package/runtime/scripts/lib/language.mjs +24 -5
  188. package/runtime/scripts/lib/list.mjs +20 -0
  189. package/runtime/scripts/lib/mutation-fence.mjs +15 -0
  190. package/runtime/scripts/lib/package-at.mjs +37 -0
  191. package/runtime/scripts/lib/path-key.mjs +57 -2
  192. package/runtime/scripts/lib/process-identity.mjs +6 -0
  193. package/runtime/scripts/lib/read-text.mjs +34 -0
  194. package/runtime/scripts/lib/read-yaml.mjs +18 -0
  195. package/runtime/scripts/lib/redact.mjs +161 -0
  196. package/runtime/scripts/lib/regex.mjs +4 -0
  197. package/runtime/scripts/lib/same-text.mjs +5 -0
  198. package/runtime/scripts/lib/secret-patterns.mjs +4 -3
  199. package/runtime/scripts/lib/sleep-sync.mjs +2 -1
  200. package/runtime/scripts/lib/sleep.mjs +7 -0
  201. package/runtime/scripts/lib/sops-envelope.mjs +112 -0
  202. package/runtime/scripts/lib/source-phrases.mjs +34 -0
  203. package/runtime/scripts/lib/sqlite.mjs +21 -0
  204. package/runtime/scripts/lib/stack-services.mjs +7 -31
  205. package/runtime/scripts/lib/ts-ast.mjs +35 -0
  206. package/runtime/scripts/lib/walk.mjs +4 -3
  207. package/scaffold/add-app.mjs +174 -0
  208. package/scaffold/add-cli-lite.mjs +257 -0
  209. package/scaffold/add-inbox-lite.mjs +314 -0
  210. package/scaffold/add-table.mjs +344 -0
  211. package/scaffold/add.mjs +299 -0
  212. package/scaffold/app.mjs +358 -69
  213. package/scaffold/edition-gate.mjs +25 -0
  214. package/scaffold/image.mjs +19 -0
  215. package/scaffold/lite-exports.mjs +186 -0
  216. package/scaffold/service.mjs +32 -26
  217. package/scaffold/supabase-ports.mjs +26 -0
  218. package/src/main.mjs +327 -0
  219. package/sync/cli.mjs +6 -9
  220. package/sync/format.mjs +1 -1
  221. package/sync/hygiene.mjs +10 -10
  222. package/sync/index.mjs +167 -104
  223. package/sync/managed.mjs +19 -11
  224. package/sync/sonar-key.mjs +5 -0
  225. package/templates/app/ci-workflows/github/workflows/ci.yml +14 -14
  226. package/templates/app/ci-workflows/github/workflows/images.yml +37 -0
  227. package/templates/app/ci-workflows/sonar-steps.yml +9 -0
  228. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +66 -0
  229. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +31 -0
  230. package/templates/app/ci-workflows-lite/github/workflows/images.yml +37 -0
  231. package/templates/app/codegen-lite/scripts/codegen.mjs +32 -0
  232. package/templates/app/docker-ignore/dockerignore +50 -0
  233. package/templates/app/gitignore-lite +35 -0
  234. package/templates/app/hooks/husky/pre-commit +3 -8
  235. package/templates/app/hooks/husky/pre-push +45 -6
  236. package/templates/app/package-scripts/core.json +8 -0
  237. package/templates/app/package-scripts/package.json +3 -10
  238. package/templates/app/package-scripts-lite/package.json +15 -0
  239. package/templates/app/quality-config/codecov.yml +15 -3
  240. package/templates/app/quality-config/sonar-base.properties +4 -0
  241. package/templates/app/quality-config/sonar-project.properties +1 -4
  242. package/templates/app/quality-config-lite/sonar-project.properties +5 -0
  243. package/templates/app/skeleton/.starcistacks/dev/seeds/primary-notes.sql +5 -0
  244. package/templates/app/skeleton/.starciwork/features/system-health/index.yaml +4 -0
  245. package/templates/app/skeleton/.starciwork/{features/index.yaml → index.yaml} +2 -2
  246. package/templates/app/skeleton/README.md +7 -5
  247. package/templates/app/skeleton-lite/.sops.yaml +3 -0
  248. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +5 -0
  249. package/templates/app/skeleton-lite/.starcistacks/dev/secrets/.gitkeep +1 -0
  250. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +55 -0
  251. package/templates/app/skeleton-lite/README.md +40 -0
  252. package/templates/app/skeleton-lite/supabase/config.toml +41 -0
  253. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +56 -0
  254. package/templates/app/skeleton-lite/supabase/seed.sql +0 -0
  255. package/templates/app/task-graph/turbo.json +23 -0
  256. package/templates/app/tool-config-lite/prettierignore +2 -0
  257. package/templates/app/tool-config-lite/prettierrc +1 -0
  258. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +38 -0
  259. package/templates/app/upgrade-full/.starcistacks/dev/README.md +10 -0
  260. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +13 -0
  261. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +3 -0
  262. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +12 -0
  263. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +7 -0
  264. package/templates/be/common/main-listen.ts.partial +14 -0
  265. package/templates/be/common/sql-ident.ts.partial +7 -0
  266. package/templates/be/common/sql-tag.ts.partial +5 -0
  267. package/templates/be/image/api/Dockerfile +38 -0
  268. package/templates/be/image/cli/Dockerfile +38 -0
  269. package/templates/be/image/worker/Dockerfile +36 -0
  270. package/templates/be/inbox/database-client.ts.tpl +50 -0
  271. package/templates/be/inbox/index.ts.tpl +2 -0
  272. package/templates/be/inbox/migration.sql.tpl +29 -0
  273. package/templates/be/inbox/module-definition.ts.tpl +6 -0
  274. package/templates/be/inbox/module.ts.tpl +14 -0
  275. package/templates/be/inbox/options.ts.tpl +2 -0
  276. package/templates/be/inbox/service.ts.tpl +33 -0
  277. package/templates/be/inbox/sql.ts.tpl +16 -0
  278. package/templates/be/inbox/webhook-door.ts.tpl +36 -0
  279. package/templates/be/patterns/api/application.command.http.ts.tpl +10 -0
  280. package/templates/be/patterns/api/application.command.ts.tpl +10 -0
  281. package/templates/be/patterns/api/application.contracts.ts.tpl +11 -0
  282. package/templates/be/patterns/api/application.handler.http.ts.tpl +22 -0
  283. package/templates/be/patterns/api/application.handler.ts.tpl +22 -0
  284. package/templates/be/patterns/api/feature.index.http.ts.tpl +1 -0
  285. package/templates/be/patterns/api/feature.index.ts.tpl +1 -0
  286. package/templates/be/patterns/api/feature.module.ts.tpl +6 -0
  287. package/templates/be/patterns/api/graphql.input.ts.tpl +13 -0
  288. package/templates/be/patterns/api/graphql.mapper.ts.tpl +9 -0
  289. package/templates/be/patterns/api/graphql.module.ts.tpl +7 -0
  290. package/templates/be/patterns/api/graphql.resolver.ts.tpl +27 -0
  291. package/templates/be/patterns/api/graphql.type.ts.tpl +9 -0
  292. package/templates/be/patterns/api/http.controller.ts.tpl +27 -0
  293. package/templates/be/patterns/api/http.mapper.ts.tpl +9 -0
  294. package/templates/be/patterns/api/http.module.ts.tpl +7 -0
  295. package/templates/be/patterns/api/http.request.ts.tpl +10 -0
  296. package/templates/be/patterns/api/http.response.ts.tpl +5 -0
  297. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +15 -0
  298. package/templates/be/patterns/cli/group.cli.ts.tpl +11 -0
  299. package/templates/be/patterns/cli/group.module.ts.tpl +7 -0
  300. package/templates/be/patterns/cli/sub.run.cli.spec.ts.tpl +17 -0
  301. package/templates/be/patterns/cli/sub.run.cli.ts.tpl +15 -0
  302. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +22 -0
  303. package/templates/be/patterns/event-bus/platform/event-bus.config.ts.tpl +19 -0
  304. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +58 -0
  305. package/templates/be/patterns/event-bus/platform/event-bus.decorators.ts.tpl +46 -0
  306. package/templates/be/patterns/event-bus/platform/event-bus.log-events.ts.tpl +12 -0
  307. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +10 -0
  308. package/templates/be/patterns/event-bus/platform/event-bus.module.ts.tpl +52 -0
  309. package/templates/be/patterns/event-bus/platform/event-bus.options.ts.tpl +19 -0
  310. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +28 -0
  311. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +165 -0
  312. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +93 -0
  313. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +90 -0
  314. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +58 -0
  315. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +136 -0
  316. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +56 -0
  317. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +272 -0
  318. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +128 -0
  319. package/templates/be/patterns/event-bus/platform/event-transport.port.ts.tpl +180 -0
  320. package/templates/be/patterns/event-bus/platform/event.policy.spec.ts.tpl +67 -0
  321. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +38 -0
  322. package/templates/be/patterns/event-bus/platform/index.ts.tpl +11 -0
  323. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.spec.ts.tpl +348 -0
  324. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +241 -0
  325. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +15 -0
  326. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +8 -0
  327. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +37 -0
  328. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +11 -0
  329. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +12 -0
  330. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +26 -0
  331. package/templates/be/patterns/jobs/job.index.ts.tpl +1 -0
  332. package/templates/be/patterns/jobs/job.module.ts.tpl +6 -0
  333. package/templates/be/patterns/jobs/job.processor.ts.tpl +20 -0
  334. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +20 -0
  335. package/templates/be/patterns/jobs/job.step.ts.tpl +19 -0
  336. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +31 -0
  337. package/templates/be/patterns/jobs/platform/fenced.processor.ts.tpl +13 -0
  338. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +150 -0
  339. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +100 -0
  340. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +138 -0
  341. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +69 -0
  342. package/templates/be/patterns/jobs/platform/jobs.config.ts.tpl +11 -0
  343. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +62 -0
  344. package/templates/be/patterns/jobs/platform/jobs.decorators.ts.tpl +31 -0
  345. package/templates/be/patterns/jobs/platform/jobs.error.ts.tpl +22 -0
  346. package/templates/be/patterns/jobs/platform/jobs.index.ts.tpl +11 -0
  347. package/templates/be/patterns/jobs/platform/jobs.log-events.ts.tpl +9 -0
  348. package/templates/be/patterns/jobs/platform/jobs.messages.ts.tpl +15 -0
  349. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +10 -0
  350. package/templates/be/patterns/jobs/platform/jobs.module.ts.tpl +32 -0
  351. package/templates/be/patterns/jobs/platform/jobs.options.ts.tpl +11 -0
  352. package/templates/be/patterns/jobs/platform/jobs.port.ts.tpl +36 -0
  353. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +8 -0
  354. package/templates/be/patterns/jobs/platform/persistence.job.entity.ts.tpl +53 -0
  355. package/templates/be/patterns/jobs/platform/persistence.jobs.rows.ts.tpl +13 -0
  356. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +33 -0
  357. package/templates/be/patterns/jobs/platform/persistence.migration.ts.tpl +29 -0
  358. package/templates/be/patterns/outbox/platform/index.ts.tpl +2 -0
  359. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +96 -0
  360. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +78 -0
  361. package/templates/be/patterns/outbox/platform/outbox.log-events.ts.tpl +5 -0
  362. package/templates/be/patterns/projections/migration.ts.tpl +18 -0
  363. package/templates/be/patterns/projections/persistence.connection.ts.tpl +8 -0
  364. package/templates/be/patterns/projections/projection.entity.ts.tpl +9 -0
  365. package/templates/be/patterns/projections/projection.index.ts.tpl +3 -0
  366. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +7 -0
  367. package/templates/be/patterns/projections/projection.module.ts.tpl +18 -0
  368. package/templates/be/patterns/projections/projection.options.ts.tpl +2 -0
  369. package/templates/be/patterns/projections/projection.projection.spec.ts.tpl +55 -0
  370. package/templates/be/patterns/projections/projection.projection.ts.tpl +21 -0
  371. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +142 -0
  372. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +81 -0
  373. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +8 -0
  374. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +25 -0
  375. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +25 -0
  376. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +9 -0
  377. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +12 -0
  378. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +48 -0
  379. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +17 -0
  380. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +128 -0
  381. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +48 -0
  382. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +115 -0
  383. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +91 -0
  384. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +45 -0
  385. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +15 -0
  386. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +31 -0
  387. package/templates/be/patterns/queues/platform/queue.decorators.ts.tpl +45 -0
  388. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +7 -0
  389. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +5 -0
  390. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +10 -0
  391. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +62 -0
  392. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +22 -0
  393. package/templates/be/patterns/queues/platform/queue.policy.spec.ts.tpl +19 -0
  394. package/templates/be/patterns/queues/platform/queue.policy.ts.tpl +11 -0
  395. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +14 -0
  396. package/templates/be/patterns/queues/queue.index.ts.tpl +3 -0
  397. package/templates/be/patterns/queues/queue.module.ts.tpl +6 -0
  398. package/templates/be/patterns/queues/queue.queue.ts.tpl +23 -0
  399. package/templates/be/patterns/reactors/application.command.ts.tpl +10 -0
  400. package/templates/be/patterns/reactors/application.contracts.ts.tpl +12 -0
  401. package/templates/be/patterns/reactors/application.handler.ts.tpl +22 -0
  402. package/templates/be/patterns/reactors/consumer.ts.tpl +19 -0
  403. package/templates/be/patterns/reactors/message.module.ts.tpl +20 -0
  404. package/templates/be/patterns/reactors/reactor.index.ts.tpl +1 -0
  405. package/templates/be/patterns/reactors/reactor.module.ts.tpl +6 -0
  406. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +6 -0
  407. package/templates/be/patterns/realtime/channel.index.ts.tpl +1 -0
  408. package/templates/be/patterns/realtime/channel.input.ts.tpl +12 -0
  409. package/templates/be/patterns/realtime/channel.subscription.spec.ts.tpl +51 -0
  410. package/templates/be/patterns/realtime/channel.subscription.ts.tpl +32 -0
  411. package/templates/be/patterns/realtime/channel.type.ts.tpl +9 -0
  412. package/templates/be/patterns/saga/saga.compensate.command.ts.tpl +11 -0
  413. package/templates/be/patterns/saga/saga.compensate.contracts.ts.tpl +7 -0
  414. package/templates/be/patterns/saga/saga.compensate.handler.ts.tpl +22 -0
  415. package/templates/be/patterns/saga/saga.compensation.ts.tpl +21 -0
  416. package/templates/be/patterns/saga/saga.complete.command.ts.tpl +11 -0
  417. package/templates/be/patterns/saga/saga.complete.contracts.ts.tpl +7 -0
  418. package/templates/be/patterns/saga/saga.complete.handler.ts.tpl +22 -0
  419. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +21 -0
  420. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +21 -0
  421. package/templates/be/patterns/saga/saga.index.ts.tpl +1 -0
  422. package/templates/be/patterns/saga/saga.message.module.ts.tpl +23 -0
  423. package/templates/be/patterns/saga/saga.module.ts.tpl +20 -0
  424. package/templates/be/patterns/saga/saga.service.spec.ts.tpl +70 -0
  425. package/templates/be/patterns/saga/saga.service.ts.tpl +44 -0
  426. package/templates/be/patterns/saga/saga.state.ts.tpl +14 -0
  427. package/templates/be/patterns/saga/saga.step.ts.tpl +11 -0
  428. package/templates/be/patterns/saga/saga.undo.command.ts.tpl +10 -0
  429. package/templates/be/patterns/saga/saga.undo.contracts.ts.tpl +5 -0
  430. package/templates/be/patterns/saga/saga.undo.handler.ts.tpl +21 -0
  431. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +72 -0
  432. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +36 -0
  433. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +6 -0
  434. package/templates/be/patterns/webhooks/webhook.index.ts.tpl +1 -0
  435. package/templates/be/patterns/webhooks/webhook.request.ts.tpl +9 -0
  436. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
  437. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -2
  438. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -15
  439. package/templates/be/skeleton/apps/cli/src/app.module.ts +30 -0
  440. package/templates/be/skeleton/apps/cli/src/cli.options.ts +22 -0
  441. package/templates/be/skeleton/apps/cli/src/main.ts +24 -0
  442. package/templates/be/skeleton/src/features/cli/cli.module.ts +7 -0
  443. package/templates/be/skeleton/src/features/cli/index.ts +1 -0
  444. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +17 -0
  445. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +15 -0
  446. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +7 -0
  447. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.spec.ts +17 -0
  448. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.ts +18 -0
  449. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +17 -0
  450. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +15 -0
  451. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +7 -0
  452. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +33 -0
  453. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +34 -0
  454. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +13 -0
  455. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +40 -0
  456. package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +1 -1
  457. package/templates/be/skeleton/src/modules/domain/note/errors/note.error.ts +16 -0
  458. package/templates/be/skeleton/src/modules/domain/note/index.ts +3 -0
  459. package/templates/be/skeleton/src/modules/domain/note/note.contracts.ts +19 -0
  460. package/templates/be/skeleton/src/modules/domain/note/note.decorators.ts +9 -0
  461. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +7 -0
  462. package/templates/be/skeleton/src/modules/domain/note/note.module.ts +19 -0
  463. package/templates/be/skeleton/src/modules/domain/note/note.options.ts +2 -0
  464. package/templates/be/skeleton/src/modules/domain/note/note.service.spec.ts +76 -0
  465. package/templates/be/skeleton/src/modules/domain/note/note.service.ts +37 -0
  466. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +8 -0
  467. package/templates/be/skeleton/src/modules/domain/note/persistence/entities/note.entity.ts +17 -0
  468. package/templates/be/skeleton/src/modules/domain/note/persistence/migrations/1790000000000-init-note.ts +22 -0
  469. package/templates/be/skeleton/src/modules/domain/note/persistence/note.rows.ts +17 -0
  470. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +4 -0
  471. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +20 -0
  472. package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
  473. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +18 -0
  474. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +41 -0
  475. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.ts +17 -0
  476. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +13 -0
  477. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +77 -0
  478. package/templates/be/skeleton/src/modules/platform/database/database.options.ts +30 -0
  479. package/templates/be/skeleton/src/modules/platform/database/database.port.ts +65 -0
  480. package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +47 -0
  481. package/templates/be/skeleton/src/modules/platform/database/errors/database.error.ts +16 -0
  482. package/templates/be/skeleton/src/modules/platform/database/index.ts +9 -0
  483. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +30 -0
  484. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.spec.ts +84 -0
  485. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.ts +23 -0
  486. package/templates/be/skeleton/src/modules/platform/database/primary.config.ts +9 -0
  487. package/templates/be/skeleton/src/modules/platform/database/primary.connection.ts +2 -0
  488. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +10 -0
  489. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +108 -0
  490. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +75 -0
  491. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +88 -0
  492. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +29 -0
  493. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +48 -0
  494. package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +1 -1
  495. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +12 -0
  496. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -0
  497. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +8 -1
  498. package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +8 -0
  499. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +14 -1
  500. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +12 -0
  501. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +10 -2
  502. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +63 -0
  503. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +108 -0
  504. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.spec.ts +39 -0
  505. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.ts +25 -0
  506. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +172 -0
  507. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.ts +66 -0
  508. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +3 -1
  509. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +52 -0
  510. package/templates/be/skeleton-lite/apps/api/Dockerfile +37 -0
  511. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +16 -0
  512. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +81 -0
  513. package/templates/be/skeleton-lite/apps/api/src/main.ts +34 -0
  514. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +14 -0
  515. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +50 -0
  516. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +26 -0
  517. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +19 -0
  518. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +26 -0
  519. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +7 -0
  520. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +6 -0
  521. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +19 -0
  522. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +10 -0
  523. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +24 -0
  524. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +8 -0
  525. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +17 -0
  526. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +32 -0
  527. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +24 -0
  528. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +4 -0
  529. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +54 -0
  530. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +23 -0
  531. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +10 -0
  532. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +49 -0
  533. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +4 -0
  534. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +21 -0
  535. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +11 -0
  536. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +28 -0
  537. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +25 -0
  538. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +9 -0
  539. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +4 -0
  540. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +5 -0
  541. package/templates/be/table/error.ts.tpl +16 -0
  542. package/templates/be/table/index.ts.tpl +2 -0
  543. package/templates/be/table/migration.sql.tpl +51 -0
  544. package/templates/be/table/module-definition.ts.tpl +7 -0
  545. package/templates/be/table/module.ts.tpl +14 -0
  546. package/templates/be/table/options.ts.tpl +2 -0
  547. package/templates/be/table/rows.ts.tpl +4 -0
  548. package/templates/be/table/service.ts.tpl +23 -0
  549. package/templates/be/table/sql.ts.tpl +4 -0
  550. package/templates/be/tool-config/jest.config.js +3 -1
  551. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +14 -0
  552. package/templates/be/upgrade-full/apps/cli/src/main.ts +26 -0
  553. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +6 -0
  554. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +23 -0
  555. package/templates/common/gitignore.base +1 -2
  556. package/templates/fe/app/package.json.tpl +1 -0
  557. package/templates/fe/app/tsconfig.json +12 -0
  558. package/templates/fe/common/app-layout.tsx.partial +8 -0
  559. package/templates/fe/common/site-shell.tsx.partial +9 -0
  560. package/templates/fe/image/next/Dockerfile +42 -0
  561. package/templates/fe/skeleton/apps/app/next.config.ts +23 -0
  562. package/templates/fe/skeleton/apps/app/postcss.config.mjs +7 -0
  563. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/error.tsx +2 -2
  564. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +22 -0
  565. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/loading.tsx +2 -2
  566. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/not-found.tsx +2 -2
  567. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +9 -0
  568. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/global-error.tsx +2 -2
  569. package/templates/fe/skeleton/apps/app/src/app/globals.css +10 -0
  570. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +18 -0
  571. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/component.tsx +19 -0
  572. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/index.tsx +11 -0
  573. package/templates/fe/skeleton/apps/app/src/features/pages/AppGlobalErrorPage/index.tsx +16 -0
  574. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/component.tsx +10 -0
  575. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +15 -0
  576. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +8 -0
  577. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  578. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +4 -0
  579. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +28 -0
  580. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -0
  581. package/templates/fe/skeleton/apps/app/src/modules/routes/index.ts +4 -0
  582. package/templates/fe/skeleton/apps/app/src/proxy.ts +1 -0
  583. package/templates/fe/skeleton/apps/{__app__ → landing}/next.config.ts +4 -2
  584. package/templates/fe/skeleton/apps/landing/postcss.config.mjs +7 -0
  585. package/templates/fe/skeleton/apps/landing/src/app/[locale]/error.tsx +10 -0
  586. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +22 -0
  587. package/templates/fe/skeleton/apps/landing/src/app/[locale]/loading.tsx +6 -0
  588. package/templates/fe/skeleton/apps/landing/src/app/[locale]/not-found.tsx +6 -0
  589. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +9 -0
  590. package/templates/fe/skeleton/apps/landing/src/app/global-error.tsx +10 -0
  591. package/templates/fe/skeleton/apps/landing/src/app/globals.css +10 -0
  592. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +24 -0
  593. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/component.tsx +18 -0
  594. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/index.tsx +11 -0
  595. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingGlobalErrorPage/index.tsx +16 -0
  596. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +8 -0
  597. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +9 -0
  598. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/component.tsx +24 -0
  599. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +16 -0
  600. package/templates/fe/skeleton/apps/landing/src/modules/config/index.ts +12 -0
  601. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +7 -0
  602. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/vi.json +31 -0
  603. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -0
  604. package/templates/fe/skeleton/apps/landing/src/modules/routes/index.ts +4 -0
  605. package/templates/fe/skeleton/apps/landing/src/proxy.ts +1 -0
  606. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +49 -0
  607. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +24 -0
  608. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +20 -0
  609. package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +23 -0
  610. package/templates/fe/skeleton/packages/__project__-ui/src/composites/ErrorNotice/index.tsx +14 -0
  611. package/templates/fe/skeleton/packages/__project__-ui/src/composites/GlobalErrorNotice/index.tsx +25 -0
  612. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LoadingNotice/index.tsx +9 -0
  613. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +21 -0
  614. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +19 -0
  615. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/classNames.ts +4 -0
  616. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/index.tsx +19 -0
  617. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +6 -0
  618. package/templates/fe/skeleton-lite/apps/web/next.config.ts +22 -0
  619. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/error.tsx +10 -0
  620. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +37 -0
  621. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/loading.tsx +6 -0
  622. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/not-found.tsx +6 -0
  623. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +9 -0
  624. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/providers.tsx +26 -0
  625. package/templates/fe/skeleton-lite/apps/web/src/app/auth/callback/route.ts +24 -0
  626. package/templates/fe/skeleton-lite/apps/web/src/app/global-error.tsx +10 -0
  627. package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/app/globals.css +1 -1
  628. package/templates/fe/skeleton-lite/apps/web/src/app/health/live/route.ts +5 -0
  629. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/component.tsx +29 -0
  630. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +41 -0
  631. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/component.tsx +17 -0
  632. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/index.tsx +38 -0
  633. package/templates/fe/skeleton-lite/apps/web/src/components/composites/ErrorNotice/index.tsx +13 -0
  634. package/templates/fe/skeleton-lite/apps/web/src/components/composites/GlobalErrorNotice/index.tsx +24 -0
  635. package/templates/fe/skeleton-lite/apps/web/src/components/composites/LoadingNotice/index.tsx +7 -0
  636. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +18 -0
  637. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/classNames.ts +4 -0
  638. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/index.tsx +13 -0
  639. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +31 -0
  640. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/component.tsx +12 -0
  641. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/index.tsx +10 -0
  642. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppGlobalErrorPage/index.tsx +15 -0
  643. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +14 -0
  644. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +30 -0
  645. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +8 -0
  646. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  647. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +20 -0
  648. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/index.ts +2 -0
  649. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +20 -0
  650. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/read-session.ts +25 -0
  651. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-in.ts +25 -0
  652. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +17 -0
  653. package/templates/fe/skeleton-lite/apps/web/src/modules/db/browser.ts +14 -0
  654. package/templates/fe/skeleton-lite/apps/web/src/modules/db/index.ts +3 -0
  655. package/templates/fe/skeleton-lite/apps/web/src/modules/db/outcome.ts +59 -0
  656. package/templates/fe/skeleton-lite/apps/web/src/modules/db/principal.ts +29 -0
  657. package/templates/fe/skeleton-lite/apps/web/src/modules/db/server.ts +70 -0
  658. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +50 -0
  659. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +8 -0
  660. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +48 -0
  661. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +16 -0
  662. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +14 -0
  663. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +7 -0
  664. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/safe-next-path.ts +26 -0
  665. package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +43 -0
  666. package/templates/fe/table/read.ts.tpl +16 -0
  667. package/templates/fe/table/write.ts.tpl +16 -0
  668. package/upgrade/check.mjs +137 -0
  669. package/upgrade/index.mjs +524 -0
  670. package/bin/hfs.mjs +0 -233
  671. package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
  672. package/runtime/scripts/lib/test-secrets.mjs +0 -116
  673. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +0 -20
  674. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +0 -9
  675. package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +0 -28
  676. package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +0 -35
  677. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +0 -19
  678. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -11
  679. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -24
  680. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -14
  681. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -21
  682. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +0 -15
  683. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +0 -23
  684. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +0 -8
  685. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +0 -28
  686. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +0 -8
  687. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +0 -8
  688. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +0 -2
  689. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +0 -25
  690. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/request.ts +0 -19
  691. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +0 -9
  692. package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +0 -4
  693. package/templates/fe/skeleton/apps/__app__/src/proxy.ts +0 -10
  694. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.contracts.ts +0 -0
  695. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.handler.ts +0 -0
  696. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.query.ts +0 -0
  697. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/index.ts +0 -0
  698. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/system-health.module.ts +0 -0
  699. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/live.controller.ts +0 -0
  700. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/system-health-http.module.ts +0 -0
  701. /package/templates/fe/skeleton/apps/{__app__ → app}/src/app/health/live/route.ts +0 -0
  702. /package/templates/fe/skeleton/apps/{__app__ → app}/src/modules/config/index.ts +0 -0
  703. /package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/postcss.config.mjs +0 -0
package/sync/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // hfs sync: render, verify and write the files an app cannot `extends`.
1
+ // starci app sync: render, verify and write the files an app cannot `extends`.
2
2
  //
3
3
  // An app is one repository: its root holds the one package.json (all dependencies, all scripts), the lockfile, the hfs.json of
4
4
  // kind app, CI, the hooks, the formatter and Sonar configuration and .starciwork; its sides be/ and fe/ hold each side's tool
@@ -8,26 +8,32 @@
8
8
  // side (be, fe) for a side slot, whose files land under that side's folder. Two targets are not whole files of a managedBy slot
9
9
  // and are listed in this module: the marked block of the root .gitignore, and .starciwork/.gitignore, which lives inside the
10
10
  // .starciwork directory slot. Every file is rendered with the app's hfs.json (both sides and their apps) and, for the Sonar
11
- // exclusions and the one coverage scope (sonar.coverage.exclusions, its complement, and the codecov.yml paths alike), the jest
12
- // preset the app installs for its be side. `--check` compares the sha256 of the rendered content with the
11
+ // exclusions, the jest preset the app installs for its be side and the one coverage scope derived from the slot manifest
12
+ // (scripts/hfs/coverage-scope.mjs: the be/jest.config.js scope, sonar.coverage.exclusions and the codecov.yml paths and components). `--check` compares the sha256 of the rendered content with the
13
13
  // file on disk and fails on any drift; `--write` rewrites the drifted files. `.gitignore` is the one shared file: only the marked
14
14
  // block is managed and the app's own lines around it are left alone. The root package.json is managed by its `scripts` block
15
- // only (mode scripts, compared as parsed JSON): the rest of the file (dependencies, npm workspaces of fe/packages/*) is the app's.
15
+ // only (mode scripts, compared as parsed JSON): the rest of the file (dependencies, the npm workspaces fe/apps/* and fe/packages/*,
16
+ // HFS_MONO_WORKSPACES) is the app's. The fe side runs through turbo (turbo.json, app.task-graph): each fe app and package is a workspace.
16
17
  import { createHash } from 'node:crypto';
17
18
  import { createRequire } from 'node:module';
18
19
  import fs from 'node:fs';
19
20
  import path from 'node:path';
20
21
  import { braceVariants } from '../runtime/scripts/lib/glob.mjs';
21
- import { APP_SCOPE, SIDES, loadSlotManifest, resolveRepoDeclaration } from '../runtime/scripts/hfs/slots.mjs';
22
+ import { codecovPaths, coverageComponents, jestCoverage, sonarCoverageExclusions } from '../runtime/scripts/hfs/coverage-scope.mjs';
23
+ import { APP_SCOPE, SIDES, litePresenceOf, loadSlotManifest, resolveRepoDeclaration, slotInEdition } from '../runtime/scripts/hfs/slots.mjs';
24
+ import { managedGroupOf } from '../runtime/scripts/hfs/edition-slots.mjs';
25
+ import { feAppPackageName } from '../runtime/scripts/hfs/rules/monorepo.mjs';
26
+ import { DEFAULT_PORT, NODE_IMAGE, dockerfilePath } from '../runtime/scripts/hfs/rules/docker.mjs';
22
27
  import { readDeclaredSonarKey } from './sonar-key.mjs';
23
28
 
24
29
  export const TEMPLATES_DIR = path.join(import.meta.dirname, '..', 'templates');
25
- export const NODE_MAJOR = 22;
30
+ const NODE_MAJOR = 22;
31
+ const APP_BINARY = 'starci app';
26
32
  /** The scopes a target is rendered for: the app root, then each side. */
27
33
  export const SCOPES = Object.freeze([APP_SCOPE, ...SIDES]);
28
- export const BLOCK_BEGIN = '# >>> hfs sync (managed block; do not edit) >>>';
29
- export const BLOCK_END = '# <<< hfs sync <<<';
30
- const HEADER = scope => `# Generated by hfs sync (${scope === APP_SCOPE ? 'app root' : `${scope} side`}). Do not edit: run "npx hfs sync --write".`;
34
+ export const BLOCK_BEGIN = '# >>> starci app sync (managed block; do not edit) >>>';
35
+ export const BLOCK_END = '# <<< starci app sync <<<';
36
+ const HEADER = scope => `# Generated by ${APP_BINARY} sync (${scope === APP_SCOPE ? 'app root' : `${scope} side`}). Do not edit: run "npx ${APP_BINARY} sync --write".`;
31
37
 
32
38
  export class SyncError extends Error {
33
39
  constructor(code, message) {
@@ -39,11 +45,11 @@ export class SyncError extends Error {
39
45
  // mode: file = whole file, block = the marked block inside a file the app also writes to, scripts = the `scripts` block of
40
46
  // package.json. A template that can hold a comment opens with {{header}}; a file whose exact content is specified (JSON, the
41
47
  // eslint one-liner) has no header.
42
- const MODE_OF_GROUP = Object.freeze({ 'package-scripts': 'scripts' });
48
+ const MODE_OF_GROUP = Object.freeze({ 'package-scripts': 'scripts', 'package-scripts-lite': 'scripts' });
43
49
  const LITERAL_FILE = /^[^*?<>{}[\]]+[^/*?<>{}[\]]$/;
44
50
 
45
51
  /** The targets no managedBy slot lists as a whole file (see the header of this module); both belong to the app root. */
46
- export const UNLISTED_TARGETS = Object.freeze([
52
+ const UNLISTED_TARGETS = Object.freeze([
47
53
  { path: '.gitignore', template: 'app/gitignore', mode: 'block' },
48
54
  { path: '.starciwork/.gitignore', template: 'app/starciwork.gitignore', mode: 'file' },
49
55
  ].map(Object.freeze));
@@ -56,18 +62,27 @@ const onSide = (scope, file) => (scope === APP_SCOPE ? file : `${scope}/${file}`
56
62
  * list, then (app root) the unlisted ones. A slot that names managedBy lists literal files only, and each has a template under
57
63
  * its scope or under common.
58
64
  */
59
- export function targetsOf(scope, { manifest = loadSlotManifest(), hasTemplate = name => fs.existsSync(path.join(TEMPLATES_DIR, name)) } = {}) {
65
+ export function targetsOf(scope, { edition = 'full', manifest = loadSlotManifest(), hasTemplate = name => fs.existsSync(path.join(TEMPLATES_DIR, name)) } = {}) {
60
66
  const listed = [];
61
67
  for (const slot of manifest.slots) {
62
- if (slot.managedBy === undefined || !slot.profiles.includes(scope)) continue;
63
- for (const file of braceVariants(slot.path)) {
64
- if (!LITERAL_FILE.test(file)) throw new SyncError('HFS_SYNC_MANIFEST_MANAGED', `slot ${slot.id} names managedBy but its path ${slot.path} is not a list of literal files (${file})`);
65
- const template = [scope, 'common'].map(dir => templateName(dir, slot.managedBy, file)).find(hasTemplate);
66
- if (!template) throw new SyncError('HFS_SYNC_TEMPLATE_MISSING', `slot ${slot.id} manages ${file} for ${scope}, but templates/{${scope},common}/${slot.managedBy} has no template for it`);
67
- listed.push({ path: file, template, mode: MODE_OF_GROUP[slot.managedBy] ?? 'file', slot: slot.id });
68
+ if (!slot.profiles.includes(scope) || !slotInEdition(manifest, slot, edition)) continue;
69
+ if (edition === 'lite' && litePresenceOf(slot, scope) === 'forbidden') continue;
70
+ const group = managedGroupOf(slot, edition);
71
+ if (group === undefined) continue;
72
+ const effectivePath = edition === 'lite' ? (slot.lite?.path ?? slot.path) : slot.path;
73
+ for (const file of braceVariants(effectivePath)) {
74
+ if (!LITERAL_FILE.test(file)) throw new SyncError('HFS_SYNC_MANIFEST_MANAGED', `slot ${slot.id} names ${edition === 'lite' && slot.liteManagedBy !== undefined ? 'liteManagedBy' : 'managedBy'} but its path ${effectivePath} is not a list of literal files (${file})`);
75
+ const template = [scope, 'common'].map(dir => templateName(dir, group, file)).find(hasTemplate);
76
+ if (!template) throw new SyncError('HFS_SYNC_TEMPLATE_MISSING', `slot ${slot.id} manages ${file} for ${scope}, but templates/{${scope},common}/${group} has no template for it`);
77
+ listed.push({ path: file, template, mode: MODE_OF_GROUP[group] ?? 'file', slot: slot.id });
68
78
  }
69
79
  }
70
- return [...listed, ...(scope === APP_SCOPE ? UNLISTED_TARGETS.map(target => ({ ...target })) : [])];
80
+ const unlisted = scope === APP_SCOPE
81
+ ? UNLISTED_TARGETS.map(target => edition === 'lite' && target.path === '.gitignore'
82
+ ? { ...target, template: 'app/gitignore-lite' }
83
+ : { ...target })
84
+ : [];
85
+ return [...listed, ...unlisted];
71
86
  }
72
87
 
73
88
  const lf = text => text.replace(/\r\n/g, '\n');
@@ -75,7 +90,7 @@ export const hashOf = text => createHash('sha256').update(lf(text)).digest('hex'
75
90
 
76
91
  /**
77
92
  * The hfs.json sync reads: an app declaration valid for the manifest it pins (both sides, their apps, connections, optional slots,
78
- * reads). Sync never renders configs for a declaration the eslint canons and hfs check would refuse, so a pin bump cannot leave
93
+ * reads). Sync never renders configs for a declaration the eslint canons and starci app check would refuse, so a pin bump cannot leave
79
94
  * lint unable to start. Answers the resolved app.
80
95
  */
81
96
  export function validateHfs(hfs, manifest = loadSlotManifest()) {
@@ -99,11 +114,9 @@ export function render(text, vars, readTemplate = readBundled) {
99
114
  });
100
115
  }
101
116
 
102
- /**
103
- * What the app's sync reads from the jest preset it installs for its be side: the Sonar exclusions and the coverage sources
104
- * (`COVERAGE_SOURCES`, the globs jest collects coverage from): { sonarExclusions, coverageSources }.
105
- */
106
- export async function loadPresets(root) {
117
+ /** What the app's sync reads from the jest preset it installs for its be side: the Sonar exclusions ({ sonarExclusions }); a lite app installs none. */
118
+ export async function loadPresets(root, edition = 'full') {
119
+ if (edition === 'lite') return {};
107
120
  const name = '@starci/jest-preset';
108
121
  const require = createRequire(path.join(root, 'package.json'));
109
122
  let resolved;
@@ -112,110 +125,158 @@ export async function loadPresets(root) {
112
125
  } catch {
113
126
  throw new SyncError('HFS_SYNC_PRESET_MISSING', `${name} is not installed under ${root}; set it to the exact version in knowledge/hfs/canon-pins.yaml and reinstall`);
114
127
  }
115
- const preset = require(resolved);
116
- return { sonarExclusions: preset.sonarExclusions(), coverageSources: [...preset.COVERAGE_SOURCES] };
128
+ return { sonarExclusions: require(resolved).sonarExclusions() };
117
129
  }
118
130
 
119
131
  /** Where the be unit run writes the lcov report (jest `coverageDirectory` coverage under be/, reporter lcov), from the app root. */
120
- export const LCOV_REPORT = 'be/coverage/lcov.info';
132
+ const LCOV_REPORT = 'be/coverage/lcov.info';
133
+
134
+ const SOURCE_FILE = /\.[cm]?ts$/;
135
+ const SOURCE_SKIPPED = new Set(['node_modules', 'dist', 'coverage']);
121
136
 
122
137
  /**
123
- * THE coverage scope of an app, from the app root: the preset's coverage sources on the be side (`be/src/**` + `/*.service.ts`).
124
- * It is the one source of the codecov.yml status paths and, through coverageExclusions, of Sonar's coverage scope, so the two
125
- * can never drift; fe/ is outside it (a front end has no tests).
138
+ * The be source of the app at `root` the coverage components read: { files, read }, `files` be-relative posix paths of every `*.ts` file under
139
+ * be/apps and be/src (build output and dependencies skipped) and `read` their text. An app with no be folder has no files.
126
140
  */
127
- export function coverageScope(presets) {
128
- const sources = presets?.coverageSources;
129
- if (!Array.isArray(sources) || !sources.length) throw new SyncError('HFS_SYNC_PRESET_MISSING', '@starci/jest-preset gives no COVERAGE_SOURCES: the coverage scope cannot be rendered');
130
- return sources.map(glob => `be/${glob}`);
141
+ export function appSource(root) {
142
+ const files = [];
143
+ const walk = dir => {
144
+ let entries;
145
+ try { entries = fs.readdirSync(path.join(root, 'be', dir), { withFileTypes: true }); } catch { return; }
146
+ for (const entry of entries) {
147
+ const rel = `${dir}/${entry.name}`;
148
+ if (entry.isDirectory()) { if (!SOURCE_SKIPPED.has(entry.name)) walk(rel); } else if (SOURCE_FILE.test(entry.name)) files.push(rel);
149
+ }
150
+ };
151
+ for (const dir of ['apps', 'src']) walk(dir);
152
+ return { files: files.sort(), read: file => fs.readFileSync(path.join(root, 'be', file), 'utf8') };
131
153
  }
132
154
 
133
- const COVERED_SUFFIX = /\*\.([a-z][a-z0-9-]*)\.ts$/;
134
- const asGlob = (text) => String(text).replace(/<[^>]+>/g, '*');
135
-
136
155
  /**
137
- * Sonar's coverage scope of an app as `sonar.coverage.exclusions` globs: the COMPLEMENT of coverageScope. SonarQube has no
138
- * coverage inclusions and its globs have no negation, so every executable file that is not a coverage source is excluded:
139
- * - every be source role of the slot manifest's closed suffix vocabulary (ruleParams.be.suffixes, R89) other than the
140
- * roles the coverage sources name (`service`), as `be/**` + `/*.<role>.ts`;
141
- * - every be file name outside that vocabulary a be slot declares (`main.ts`, `index.ts`, `connection.ts`, the migrations,
142
- * the test world's files, dto/ and kit/ files), as `be/<slot path>/<name>` with each `<placeholder>` a `*`;
143
- * - all of fe/ (a front end has no tests).
144
- * The result is sorted and has no duplicate. A coverage source that is not `<dir>/**` + `/*.<role>.ts` is refused: its
145
- * complement cannot be written in Sonar's globs.
156
+ * The Codecov `individual_components` entries of components [{ id, name, paths }]: one `component_id` block each, `prefix` ahead of every path
157
+ * (the app folder in the runtime repository) and `ids` mapping a component id to its name there.
146
158
  */
147
- export function coverageExclusions(presets, manifest = loadSlotManifest()) {
148
- const covered = new Set(coverageScope(presets).map(glob => {
149
- const role = COVERED_SUFFIX.exec(glob)?.[1];
150
- if (!role) throw new SyncError('HFS_SYNC_COVERAGE_SCOPE', `coverage source ${glob} is not <dir>/**/*.<role>.ts: Sonar's coverage exclusions cannot express its complement`);
151
- return role;
152
- }));
153
- const suffixes = manifest.ruleParams?.be?.suffixes ?? [];
154
- const isRole = new RegExp(`\\.(?:${suffixes.map(s => s.replace(/[-]/g, '\\-')).join('|')})\\.ts$`);
155
- const globs = new Set(suffixes.filter(role => !covered.has(role)).map(role => `be/**/*.${role}.ts`));
156
- for (const slot of manifest.slots) {
157
- if (!slot.profiles.includes('be') || slot.presence === 'forbidden') continue;
158
- for (const entry of [...(slot.requires ?? []), ...(slot.allows ?? [])]) {
159
- if (!/\.ts$/.test(entry) || entry.includes('<role>')) continue;
160
- for (const name of braceVariants(entry)) {
161
- if (isRole.test(asGlob(name).replace(/\*/g, 'x'))) continue;
162
- for (const dir of braceVariants(slot.path)) globs.add(`be/${asGlob(dir)}${asGlob(name)}`.replace(/\/\/+/g, '/'));
163
- }
164
- }
165
- }
166
- globs.add('fe/**');
167
- return [...globs].sort();
159
+ export function componentsYaml(components, { prefix = '', ids = id => id } = {}) {
160
+ return components.map(component => [` - component_id: ${ids(component.id)}`, ` name: ${ids(component.name)}`, ' paths:', ...component.paths.map(glob => ` - ${JSON.stringify(prefix + glob)}`)].join('\n')).join('\n');
161
+ }
162
+
163
+ /** The managed be/jest.config.js option text of a coverage scope { roots, roles, excludes }, laid out exactly as prettier lays it out (width 120, indent 4). */
164
+ export function jestCoverageSource(coverage) {
165
+ const list = (key, items) => {
166
+ const one = ` ${key}: [${items.map(item => JSON.stringify(item)).join(', ')}],`;
167
+ return one.length <= 120 ? one : [` ${key}: [`, ...items.map(item => ` ${JSON.stringify(item)},`), ' ],'].join('\n');
168
+ };
169
+ return ['{', list('roots', coverage.roots), list('roles', coverage.roles), list('excludes', coverage.excludes), ' }'].join('\n');
168
170
  }
169
171
 
170
172
  /**
171
- * The script lines the apps add to the root package.json, each ending with a comma (the template puts them mid-object). Every
172
- * script runs a side from the app root: a be script from be/ (`cd be && ...`, the folder its tsconfig and jest configuration
173
- * are relative to), an fe script from fe/. be: `dev:be` runs the one api app from source and restarts it on change (`dev:be:<app>`
174
- * each, with several), `start:<app>` runs a built api, worker or cli app, `migrate` the migrate app (`migrate:<app>` with several).
175
- * fe: `dev:fe` runs the one Next app in development (`dev:fe:<app>` each, with several), `start:<app>` serves a built one.
173
+ * The script lines the apps add to the root package.json, each ending with a comma (the template puts them mid-object). A be
174
+ * script runs from be/ (`cd be && ...`, the folder its tsconfig and jest configuration are relative to) or runs the built app; an
175
+ * fe script runs the fe app's workspace (@<project>/<app>): through turbo for dev (so the packages it imports are built first) and
176
+ * through npm `-w` for start. be: `dev:be` runs the one api app from source and restarts it on change (`dev:be:<app>` each, with
177
+ * several), `start:<app>` runs a built api or worker app, `cli` the built cli app (`npm run cli -- <group> <command>`) and, when the
178
+ * back end declares a connection, `migrate` its migrate command (`cli migrate run`). fe: `dev:fe` runs the one Next app in
179
+ * development (`dev:fe:<app>` each, with several), `start:<app>` serves a built one. Images:
180
+ * `docker:build:<app>` builds the image of one app (the exact command its Dockerfile header states, tag `<project>/<app>:dev`) and
181
+ * `docker:build` builds every image, one after the other, never pushing.
176
182
  */
177
183
  export function appScripts(app) {
178
184
  const line = (name, command) => `${JSON.stringify(name)}: ${JSON.stringify(command)},`;
179
185
  const be = app.sides.be.apps;
180
186
  const fe = app.sides.fe.apps;
181
187
  const apis = be.filter(entry => entry.kind === 'api');
182
- const migrates = be.filter(entry => entry.kind === 'migrate');
188
+ const connections = app.sides.be.connections ?? [];
183
189
  const watch = entry => `cd be && ts-node-dev --respawn -r tsconfig-paths/register apps/${entry.name}/src/main.ts`;
190
+ const built = entry => `node be/dist/apps/${entry.name}/src/main.js`;
191
+ const workspace = entry => feAppPackageName(app.project, entry.name);
192
+ const dev = entry => `${app.edition === 'lite' ? '' : 'npm run codegen --silent && '}turbo run dev --filter=${workspace(entry)}`;
193
+ const images = imageEntries(app);
194
+ const imageCommand = ({ side, entry }) => `docker build -f ${dockerfilePath(side, entry.name)} -t ${app.project}/${entry.name}:dev .`;
184
195
  return [
185
196
  ...(apis.length === 1 ? [line('dev:be', watch(apis[0]))] : apis.map(entry => line(`dev:be:${entry.name}`, watch(entry)))),
186
- ...(fe.length === 1 ? [line('dev:fe', `npm run codegen --silent && cd fe/apps/${fe[0].name} && next dev`)] : fe.map(entry => line(`dev:fe:${entry.name}`, `npm run codegen --silent && cd fe/apps/${entry.name} && next dev`))),
187
- ...be.map(entry => line(entry.kind === 'migrate' ? (migrates.length === 1 ? 'migrate' : `migrate:${entry.name}`) : `start:${entry.name}`, `node be/dist/apps/${entry.name}/src/main.js`)),
188
- ...fe.map(entry => line(`start:${entry.name}`, `cd fe/apps/${entry.name} && next start`)),
197
+ ...(fe.length === 1 ? [line('dev:fe', dev(fe[0]))] : fe.map(entry => line(`dev:fe:${entry.name}`, dev(entry)))),
198
+ ...be.flatMap(entry => {
199
+ if (entry.kind !== 'cli') return [line(`start:${entry.name}`, built(entry))];
200
+ return [line('cli', built(entry)), ...(connections.length ? [line('migrate', `${built(entry)} ${MIGRATE_COMMAND}`)] : [])];
201
+ }),
202
+ ...fe.map(entry => line(`start:${entry.name}`, `npm run start -w ${workspace(entry)}`)),
203
+ ...(app.edition !== 'lite' && app.optionalSlots.includes('app.browser') ? [line('test:browser', 'playwright test -c browser/playwright.config.ts')] : []),
204
+ line('docker:build', images.map(image => `npm run docker:build:${image.entry.name}`).join(' && ')),
205
+ ...images.map(image => line(`docker:build:${image.entry.name}`, imageCommand(image))),
189
206
  ].join('\n ');
190
207
  }
191
208
 
209
+ /** The cli command that migrates every connection (src/features/cli/migrate/subs/run.cli.ts). */
210
+ const MIGRATE_COMMAND = 'migrate run';
211
+
192
212
  /** True when the fe side opts into a workspace package (repo.packages or any fe.package.* slot): the sources then include fe/packages/. */
193
- export const opensPackages = side => (side.optionalSlots ?? []).some(id => id === 'repo.packages' || String(id).startsWith('fe.package.'));
213
+ const opensPackages = side => (side.optionalSlots ?? []).some(id => id === 'repo.packages' || String(id).startsWith('fe.package.'));
194
214
 
195
215
  /** The stylesheets of the fe side the CSS canon judges, relative to fe/: every app and every workspace package (slot fe.route, fe.package.*). */
196
216
  export const STYLE_GLOB = '{apps,packages}/*/src/**/*.css';
197
217
 
218
+ /** The images a declaration renders commands and CI entries for. Lite's default deployable is the back-end API image. */
219
+ function imageEntries(app) {
220
+ return [...app.sides.be.apps.map(entry => ({ side: 'be', entry })), ...(app.edition === 'lite' ? [] : app.sides.fe.apps.map(entry => ({ side: 'fe', entry })))];
221
+ }
222
+
223
+ /**
224
+ * The build matrix of the images workflow: one entry per declared app (be then fe), each with the image name and its Dockerfile. The workflow
225
+ * runs on the release tag, so every image is built there; no path filter narrows it. A YAML block mapping per entry, indented for
226
+ * `strategy.matrix.include`.
227
+ */
228
+ export function imageMatrix(app) {
229
+ const entry = (side, item) => ` - name: ${item.name}\n file: ${dockerfilePath(side, item.name)}`;
230
+ return imageEntries(app).map(image => entry(image.side, image.entry)).join('\n');
231
+ }
232
+
233
+ const IMAGE_TEMPLATE = Object.freeze({ api: 'be/image/api', worker: 'be/image/worker', cli: 'be/image/cli', next: 'fe/image/next' });
234
+
235
+ /**
236
+ * The Dockerfile of every declared app, [{ path, content }] (app-relative): written once by starci app scaffold from templates/<side>/image/<kind>
237
+ * and then the app's own, judged by the docker rules (R187-R191). A Next image copies the fe packages only when the fe side opts into
238
+ * them and the be contracts only when the fe side reads them, so each COPY names a folder the app has.
239
+ */
240
+ export function imageFiles(app) {
241
+ const side = app.sides.fe;
242
+ const inputs = [...(opensPackages(side) ? ['fe/packages'] : []), ...((side.reads ?? []).includes('be/contracts/') ? ['be/contracts'] : [])];
243
+ const copyInputs = inputs.map(folder => `COPY ${folder} ${folder}\n`).join('');
244
+ const entries = imageEntries(app).map(({ side: which, entry: item }) => ({ side: which, item }));
245
+ return entries.map(({ side: which, item }) => {
246
+ const template = IMAGE_TEMPLATE[item.kind];
247
+ const vars = { project: app.project, app: item.name, nodeImage: NODE_IMAGE, port: DEFAULT_PORT, copyInputs };
248
+ return { path: dockerfilePath(which, item.name), content: render(readBundled(`${template}/Dockerfile`), vars) };
249
+ });
250
+ }
251
+
198
252
  /** Every value a template of `scope` can name, derived from hfs.json and the presets. */
199
- export function variables(app, scope, presets, sonarKey, manifest = loadSlotManifest()) {
253
+ export function variables(app, scope, presets, sonarKey, manifest = loadSlotManifest(), source = { files: [], read: () => '' }) {
200
254
  const fe = app.sides.fe;
201
255
  const packages = opensPackages(fe);
202
- const feApps = fe.apps.map(entry => `fe/apps/${entry.name}`);
203
- const feTsconfigs = [...feApps.map(dir => `${dir}/tsconfig.json`), ...(packages ? ['fe/packages/*/tsconfig.json'] : [])];
256
+ const lite = app.edition === 'lite';
257
+ const providers = (app.sides.be.connections ?? []).map(connection => connection.provider);
204
258
  return {
205
259
  header: HEADER(scope),
260
+ appBinary: APP_BINARY,
206
261
  appScripts: appScripts(app),
207
- buildFe: ['npm run codegen --silent', ...(packages ? ['npm run build --workspaces --if-present'] : []), fe.apps.map(entry => `(cd fe/apps/${entry.name} && next build)`).join(' && ')].join(' && '),
208
- // The fe apps import the workspace packages from their dist/, so the packages are built before the apps are type-checked.
209
- typecheck: ['npm run codegen --silent', 'tsc -p be/tsconfig.json', ...(packages ? ['npm run build --workspaces --if-present'] : []), ...feTsconfigs.filter(file => !file.includes('*')).map(file => `tsc -p ${file} --noEmit`), ...(packages ? ['npm run typecheck --workspaces --if-present'] : [])].join(' && '),
262
+ // turbo builds every fe app workspace; ^build (turbo.json) builds the packages each app imports first, and caches both.
263
+ buildFe: `${lite ? '' : 'npm run codegen --silent && '}turbo run build --filter=./fe/apps/*`,
264
+ // The be side with its tsc; every fe workspace with its own typecheck script through turbo (the packages are built first: ^build).
265
+ typecheck: `${lite ? '' : 'npm run codegen --silent && '}tsc -p be/tsconfig.json && turbo run typecheck`,
266
+ lintPrefix: lite ? '' : 'npm run codegen --silent && ',
210
267
  nodeMajor: String(NODE_MAJOR),
211
268
  sonarKey: sonarKey ?? app.project,
212
- sonarExclusions: [presets?.sonarExclusions, '**/.next/**', '**/node_modules/**', '**/src/messages/**'].filter(Boolean).join(','),
269
+ sonarExclusions: (lite ? ['**/.next/**', '**/node_modules/**', '**/src/messages/**', 'supabase/types/**'] : [presets?.sonarExclusions, '**/.next/**', '**/node_modules/**', '**/src/messages/**']).filter(Boolean).join(','),
213
270
  sonarSources: ['be/apps', 'be/src', 'fe/apps', ...(packages ? ['fe/packages'] : [])].join(','),
214
271
  lcovReport: LCOV_REPORT,
215
- coverageExclusions: scope === APP_SCOPE ? coverageExclusions(presets, manifest).join(',') : '',
216
- codecovPaths: scope === APP_SCOPE ? coverageScope(presets).map(glob => ` - ${JSON.stringify(glob)}`).join('\n') : '',
217
- tsconfigPaths: ['be/tsconfig.json', ...feTsconfigs].join(','),
272
+ jestCoverage: !lite && scope === 'be' ? jestCoverageSource(jestCoverage(manifest, providers)) : '',
273
+ coverageExclusions: !lite && scope === APP_SCOPE ? sonarCoverageExclusions(manifest, providers).join(',') : '',
274
+ codecovPaths: !lite && scope === APP_SCOPE ? codecovPaths(manifest, providers).map(glob => ` - ${JSON.stringify(glob)}`).join('\n') : '',
275
+ codecovComponents: !lite && scope === APP_SCOPE ? componentsYaml(coverageComponents(manifest, { ...source, apps: app.sides.be.apps, providers })) : '',
276
+ tsconfigPaths: ['be/tsconfig.json', ...fe.apps.map(entry => `fe/apps/${entry.name}/tsconfig.json`), ...(packages ? ['fe/packages/*/tsconfig.json'] : [])].join(','),
218
277
  styleGlob: STYLE_GLOB,
278
+ imageMatrix: imageMatrix(app),
279
+ imageProject: app.project,
219
280
  };
220
281
  }
221
282
 
@@ -236,12 +297,12 @@ function parseScripts(text) {
236
297
  * its folder); a scripts target also carries `scripts`, the parsed block. `sonarKey` is the key the be side's stack declaration
237
298
  * names; without one the key is the project.
238
299
  */
239
- export function renderTargets(hfs, presets, { sonarKey, readTemplate = readBundled, manifest = loadSlotManifest() } = {}) {
300
+ export function renderTargets(hfs, presets, { sonarKey, readTemplate = readBundled, manifest = loadSlotManifest(), source } = {}) {
240
301
  const app = validateHfs(hfs, manifest);
241
302
  const hasTemplate = name => { try { return readTemplate(name) !== undefined; } catch { return false; } };
242
303
  return SCOPES.flatMap(scope => {
243
- const vars = variables(app, scope, presets, sonarKey, manifest);
244
- return targetsOf(scope, { manifest, hasTemplate }).map(target => {
304
+ const vars = variables(app, scope, presets, sonarKey, manifest, source);
305
+ return targetsOf(scope, { edition: app.edition, manifest, hasTemplate }).map(target => {
245
306
  const body = render(readTemplate(target.template), vars, readTemplate);
246
307
  const where = onSide(scope, target.path);
247
308
  if (target.mode === 'scripts') {
@@ -318,41 +379,43 @@ export function writeTargets(root, targets) {
318
379
  }
319
380
 
320
381
  /** The hfs.json object of the app at `root` (validated), or a SyncError. */
321
- export function loadHfs(root) {
382
+ export function loadHfs(root, manifest = loadSlotManifest()) {
322
383
  const file = path.join(root, 'hfs.json');
323
- if (!fs.existsSync(file)) throw new SyncError('HFS_SYNC_HFS_INVALID', `${file} does not exist; hfs sync runs at the app root`);
384
+ if (!fs.existsSync(file)) throw new SyncError('HFS_SYNC_HFS_INVALID', `${file} does not exist; starci app sync runs at the app root`);
324
385
  let hfs;
325
386
  try {
326
387
  hfs = JSON.parse(fs.readFileSync(file, 'utf8'));
327
388
  } catch (error) {
328
389
  throw new SyncError('HFS_SYNC_HFS_INVALID', `${file} is not valid JSON: ${error.message}`);
329
390
  }
330
- validateHfs(hfs);
391
+ validateHfs(hfs, manifest);
331
392
  return hfs;
332
393
  }
333
394
 
334
395
  /** The rendered targets of the app at `root`: its hfs.json, the Sonar key the be side's stack declaration names, the presets it installs. */
335
- export async function renderRepo(root, { presets, parseYaml } = {}) {
336
- const hfs = loadHfs(root);
396
+ export async function renderRepo(root, { presets, parseYaml, manifest = loadSlotManifest() } = {}) {
397
+ const hfs = loadHfs(root, manifest);
337
398
  const sonarKey = await readDeclaredSonarKey(root, { parseYaml, fail: message => { throw new SyncError('HFS_SYNC_SONAR_KEY', message); } });
338
- return { hfs, targets: renderTargets(hfs, presets ?? await loadPresets(root), { sonarKey }) };
399
+ return { hfs, targets: renderTargets(hfs, presets ?? await loadPresets(root, hfs.edition), { sonarKey, manifest, source: appSource(root) }) };
339
400
  }
340
401
 
341
- /** `hfs sync --check | --write [--root <dir>]` at the app root; returns the exit code (0 clean, 1 drift or error, 2 usage). */
402
+ /** `starci app sync --check | --write [--cwd <dir>]` at the app root; returns 0 clean, 1 drift, 2 usage. */
342
403
  export async function runSync(argv, { cwd = process.cwd(), out = line => process.stdout.write(`${line}\n`), presets, parseYaml } = {}) {
343
404
  const check = argv.includes('--check'), write = argv.includes('--write');
344
- const rootAt = argv.indexOf('--root');
345
- if ([check, write].filter(Boolean).length !== 1) {
346
- out('usage: hfs sync (--check | --write) [--root <dir>]');
405
+ const cwdAt = argv.indexOf('--cwd');
406
+ const known = new Set(['--check', '--write', '--cwd']);
407
+ const unknown = argv.find((arg, index) => !known.has(arg) && index !== cwdAt + 1);
408
+ if ([check, write].filter(Boolean).length !== 1 || unknown !== undefined || (cwdAt >= 0 && argv[cwdAt + 1] === undefined)) {
409
+ out('usage: starci app sync (--check | --write) [--cwd <dir>]');
347
410
  return 2;
348
411
  }
349
- const root = rootAt >= 0 ? path.resolve(cwd, argv[rootAt + 1] ?? '') : cwd;
412
+ const root = cwdAt >= 0 ? path.resolve(cwd, argv[cwdAt + 1]) : cwd;
350
413
  try {
351
414
  const { targets } = await renderRepo(root, { presets, parseYaml });
352
415
  if (write) {
353
416
  const written = writeTargets(root, targets);
354
417
  for (const file of written) out(`wrote ${file}`);
355
- out(`hfs sync: ${written.length} written, ${targets.length - written.length} already in sync`);
418
+ out(`starci app sync: ${written.length} written, ${targets.length - written.length} already in sync`);
356
419
  return 0;
357
420
  }
358
421
  const results = checkTargets(root, targets);
@@ -362,7 +425,7 @@ export async function runSync(argv, { cwd = process.cwd(), out = line => process
362
425
  const where = result.difference ? ` (line ${result.difference.line}: expected ${JSON.stringify(result.difference.expected)}, found ${JSON.stringify(result.difference.actual)})` : '';
363
426
  out(`HFS_SYNC_DRIFT ${result.path}: ${result.status}, expected sha256 ${result.expectedHash.slice(0, 12)}${found}${where}`);
364
427
  }
365
- out(`hfs sync --check: ${results.length - bad.length} of ${results.length} in sync${bad.length ? '; run "npx hfs sync --write"' : ''}`);
428
+ out(`starci app sync --check: ${results.length - bad.length} of ${results.length} in sync${bad.length ? '; run "npx starci app sync --write"' : ''}`);
366
429
  return bad.length ? 1 : 0;
367
430
  } catch (error) {
368
431
  if (!(error instanceof SyncError)) throw error;
package/sync/managed.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // The managed-file findings of `hfs check` (BE-CONVENTION 1.17, 3.1 principle 4): the rendered set of `hfs sync`, compared
1
+ // The managed-file findings of `starci app check`: the rendered set of `starci app sync`, compared
2
2
  // with the tracked repository, and the tool configuration nothing may add to it.
3
3
  // HFS_MANAGED_FILE_DRIFT (R05) a managed file that exists but differs from its render: tsconfig*.json (but see R22),
4
4
  // jest.config.js (back end), .prettierrc, .prettierignore, husky hooks, both workflows and sonar
@@ -19,8 +19,12 @@
19
19
  // configuration, and a package.json that carries a tool configuration key (`eslintConfig`, `stylelint`,
20
20
  // `prettier`, `lint-staged`, `jest`). Forbidden tool-config files (.eslintrc*, .eslintignore, a second
21
21
  // eslint.config.*) are refused by hfs-check through the slot manifest under the same code.
22
+ // HFS_COVERAGE_SCOPE_DRIFT (R204)
23
+ // a file that states the coverage scope no longer states what the slot manifest derives (the `coverage` field of
24
+ // knowledge/hfs/slots.yaml through scripts/hfs/coverage-scope.mjs): be/jest.config.js, codecov.yml, or the
25
+ // `sonar.coverage.exclusions` line of sonar-project.properties. The scope is never edited by hand.
22
26
  // HFS_SONAR_CONFIG (R11) sonar-project.properties differs from its render (the render names no host URL, sources and tests that
23
- // do not overlap, the `sonar.exclusions` of the jest preset (a back end), the be lcov with the services as the coverage scope, the ESLint report and the
27
+ // do not overlap, the `sonar.exclusions` of the jest preset (a back end), the be lcov with the logic of be/src/modules as the coverage scope, the ESLint report and the
24
28
  // HFS import files Sonar reads), or the stack declaration names a quality gate other than the one
25
29
  // gate of knowledge/sonar-gate.yaml (bundled in the runtime copy). One edit is one finding.
26
30
  // HFS_TS_STRICT (R22) the root tsconfig.json drift (either profile), named by flag (ts-strict.mjs) instead of by hash.
@@ -28,12 +32,16 @@ import fs from 'node:fs';
28
32
  import path from 'node:path';
29
33
  import { SyncError, checkTargets, renderRepo } from './index.mjs';
30
34
  import { parseYaml as bundledParseYaml } from '../runtime/engine/yaml.mjs';
31
- import { readDeclaredSonarGate } from './sonar-key.mjs';
35
+ import { readDeclaredSonarGate, sonarGateName } from './sonar-key.mjs';
32
36
  import { TS_STRICT_FILE, tsStrictFindings } from './ts-strict.mjs';
33
37
 
34
38
  // The managed one-liners whose difference from the render is the proof that no rule is off, warned or redefined (R17).
35
39
  const ONE_LINER_FILES = new Set(['eslint.config.mjs', 'stylelint.config.mjs']);
36
40
  const SONAR_PROPERTIES_FILE = 'sonar-project.properties';
41
+ // The files whose whole content is a statement of the coverage scope, and the one line of the Sonar file that is (R204).
42
+ const COVERAGE_SCOPE_FILES = new Set(['be/jest.config.js', 'codecov.yml']);
43
+ const SONAR_COVERAGE_LINE = 'sonar.coverage.exclusions=';
44
+ const statesCoverageScope = result => COVERAGE_SCOPE_FILES.has(result.path) || (result.path === SONAR_PROPERTIES_FILE && String(result.difference?.expected ?? '').startsWith(SONAR_COVERAGE_LINE));
37
45
  const SONAR_GATE_FILE = new URL('../runtime/knowledge/sonar-gate.yaml', import.meta.url);
38
46
  const MAX_SCANNED_BYTES = 1024 * 1024;
39
47
  const RULE_FILE = /\.[cm]?[jt]s$/;
@@ -80,19 +88,19 @@ function driftFindings(repoRoot, targets) {
80
88
  continue;
81
89
  }
82
90
  }
83
- const code = target.mode === 'block' ? 'HFS_GITIGNORE_BLOCK_DRIFT' : ONE_LINER_FILES.has(path.posix.basename(result.path)) ? 'HFS_RULE_OFF_WITHOUT_REPLACEMENT' : result.path === SONAR_PROPERTIES_FILE ? 'HFS_SONAR_CONFIG' : 'HFS_MANAGED_FILE_DRIFT';
91
+ const code = target.mode === 'block' ? 'HFS_GITIGNORE_BLOCK_DRIFT' : ONE_LINER_FILES.has(path.posix.basename(result.path)) ? 'HFS_RULE_OFF_WITHOUT_REPLACEMENT' : statesCoverageScope(result) ? 'HFS_COVERAGE_SCOPE_DRIFT' : result.path === SONAR_PROPERTIES_FILE ? 'HFS_SONAR_CONFIG' : 'HFS_MANAGED_FILE_DRIFT';
84
92
  const where = result.difference ? `; line ${result.difference.line} expected ${JSON.stringify(result.difference.expected)}, found ${JSON.stringify(result.difference.actual)}` : '';
85
- const what = target.mode === 'block' ? `the managed block of ${result.path} is ${result.status === 'missing' ? 'missing' : 'not its render'}` : target.mode === 'scripts' ? 'the scripts block of package.json is not the rendered one' : `${result.path} is not its render${code === 'HFS_RULE_OFF_WITHOUT_REPLACEMENT' ? ', so a rule can be off or redefined in it' : ''}`;
86
- findings.push({ code, level: 'error', path: result.path, mode: target.mode, expectedHash: result.expectedHash, ...(result.actualHash ? { actualHash: result.actualHash } : {}), message: `${what} (expected sha256 ${result.expectedHash.slice(0, 12)}${result.actualHash ? `, found ${result.actualHash.slice(0, 12)}` : ''}${where}); run "npx hfs sync --write"` });
93
+ const what = target.mode === 'block' ? `the managed block of ${result.path} is ${result.status === 'missing' ? 'missing' : 'not its render'}` : target.mode === 'scripts' ? 'the scripts block of package.json is not the rendered one' : `${result.path} is not its render${code === 'HFS_RULE_OFF_WITHOUT_REPLACEMENT' ? ', so a rule can be off or redefined in it' : code === 'HFS_COVERAGE_SCOPE_DRIFT' ? ', so it no longer states the coverage scope derived from the slot manifest' : ''}`;
94
+ findings.push({ code, level: 'error', path: result.path, mode: target.mode, expectedHash: result.expectedHash, ...(result.actualHash ? { actualHash: result.actualHash } : {}), message: `${what} (expected sha256 ${result.expectedHash.slice(0, 12)}${result.actualHash ? `, found ${result.actualHash.slice(0, 12)}` : ''}${where}); run "npx starci app sync --write"` });
87
95
  }
88
96
  return findings;
89
97
  }
90
98
 
91
99
  /** R11: the quality gate the be side's stack declaration names is the one gate of the bundled knowledge/sonar-gate.yaml. */
92
- function sonarGateFindings(repoRoot, parseYaml) {
100
+ function sonarGateFindings(repoRoot, parseYaml, edition) {
93
101
  const declared = readDeclaredSonarGate(repoRoot, { parseYaml });
94
102
  if (declared === null) return [];
95
- const gate = (parseYaml ?? bundledParseYaml)(fs.readFileSync(SONAR_GATE_FILE, 'utf8'))?.gate?.name;
103
+ const gate = sonarGateName((parseYaml ?? bundledParseYaml)(fs.readFileSync(SONAR_GATE_FILE, 'utf8')), edition);
96
104
  if (declared.qualityGate === gate) return [];
97
105
  return [{ code: 'HFS_SONAR_CONFIG', level: 'error', path: declared.file, message: `${declared.file} services.sonar.qualityGate is ${declared.qualityGate ?? 'absent'}; every product names the one gate ${gate} of knowledge/sonar-gate.yaml and states no threshold of its own` }];
98
106
  }
@@ -153,17 +161,17 @@ function flagFindings(repoRoot, tracked, managed) {
153
161
  * is unreadable has none: the slot check reports the declaration. A missing preset or an unreadable stack declaration is
154
162
  * a SyncError the caller reports as a refusal.
155
163
  */
156
- export async function managedFindings({ repoRoot, tracked, presets, parseYaml }) {
164
+ export async function managedFindings({ repoRoot, tracked, presets, parseYaml, manifest }) {
157
165
  let rendered;
158
166
  try {
159
- rendered = await renderRepo(repoRoot, { presets, parseYaml });
167
+ rendered = await renderRepo(repoRoot, { presets, parseYaml, ...(manifest ? { manifest } : {}) });
160
168
  } catch (error) {
161
169
  if (error instanceof SyncError && error.code === 'HFS_SYNC_HFS_INVALID') return [];
162
170
  throw error;
163
171
  }
164
172
  const { targets } = rendered;
165
173
  const findings = driftFindings(repoRoot, targets);
166
- findings.push(...sonarGateFindings(repoRoot, parseYaml));
174
+ findings.push(...sonarGateFindings(repoRoot, parseYaml, rendered.hfs.edition ?? 'full'));
167
175
  findings.push(...ruleFileFindings(repoRoot, tracked));
168
176
  findings.push(...toolKeyFindings(repoRoot, tracked));
169
177
  findings.push(...flagFindings(repoRoot, tracked, new Set(targets.map(target => target.path))));
@@ -52,3 +52,8 @@ export function readDeclaredSonarGate(root, { parseYaml } = {}) {
52
52
  if (!sonar || sonar.mode === 'disabled') return null;
53
53
  return { file: path.relative(root, file).split(path.sep).join('/'), qualityGate: typeof sonar.qualityGate === 'string' ? sonar.qualityGate : null };
54
54
  }
55
+
56
+ /** The quality-gate name for an app edition from the one sonar-gate document. */
57
+ export function sonarGateName(document, edition = 'full') {
58
+ return (edition === 'lite' ? document?.lite : document)?.gate?.name ?? null;
59
+ }
@@ -3,18 +3,26 @@ name: ci
3
3
 
4
4
  on:
5
5
  push:
6
- branches: [main]
7
- pull_request:
6
+ tags: ["v*"]
7
+ workflow_dispatch:
8
8
 
9
9
  permissions:
10
10
  contents: read
11
11
 
12
+ # One run per ref: a release tag is never cancelled by another run.
13
+ concurrency:
14
+ group: ci-${{ github.ref }}
15
+ cancel-in-progress: false
16
+
12
17
  jobs:
13
18
  ci:
14
19
  runs-on: ubuntu-latest
20
+ # id-token: write lets the Codecov upload authenticate with GitHub's OIDC token: no repository secret, no silent skip.
21
+ permissions:
22
+ contents: read
23
+ id-token: write
15
24
  env:
16
25
  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
17
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
18
26
  steps:
19
27
  - uses: actions/checkout@v4
20
28
  - uses: actions/setup-node@v4
@@ -32,9 +40,9 @@ jobs:
32
40
  run: npm test -- --ci
33
41
  - name: coverage upload
34
42
  uses: codecov/codecov-action@v5
35
- if: ${{ !cancelled() && env.CODECOV_TOKEN != '' }}
43
+ if: ${{ !cancelled() && startsWith(github.ref, 'refs/tags/v') }}
36
44
  with:
37
- token: ${{ env.CODECOV_TOKEN }}
45
+ use_oidc: true
38
46
  files: {{lcovReport}}
39
47
  disable_search: true
40
48
  fail_ci_if_error: true
@@ -42,12 +50,4 @@ jobs:
42
50
  run: npm run build:be
43
51
  - name: build fe
44
52
  run: npm run build:fe
45
- - uses: SonarSource/sonarqube-scan-action@v7
46
- if: ${{ !cancelled() && env.SONAR_TOKEN != '' }}
47
- env:
48
- SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
49
- - uses: SonarSource/sonarqube-quality-gate-action@v1
50
- if: ${{ !cancelled() && env.SONAR_TOKEN != '' }}
51
- timeout-minutes: 10
52
- env:
53
- SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
53
+ {{> app/ci-workflows/sonar-steps.yml}}