@starci/hfs 4.2.17 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (564) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +7 -0
  3. package/db/migrate.mjs +361 -0
  4. package/db/post-cutover.mjs +136 -0
  5. package/emit/db-types.mjs +10 -1
  6. package/lint/run.mjs +1 -1
  7. package/package.json +4 -1
  8. package/release/gate.mjs +298 -0
  9. package/release/index.mjs +103 -0
  10. package/release/push.mjs +110 -0
  11. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  12. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  13. package/runtime/knowledge/hfs/rules.yaml +3437 -160
  14. package/runtime/knowledge/hfs/slots.yaml +430 -92
  15. package/runtime/knowledge/patterns/be/api.yaml +92 -2
  16. package/runtime/knowledge/patterns/be/cli.yaml +37 -9
  17. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  18. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  19. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  20. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  21. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  22. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  23. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  24. package/runtime/knowledge/patterns/be/webhooks.yaml +210 -36
  25. package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
  26. package/runtime/knowledge/sonar-gate.yaml +1 -1
  27. package/runtime/modules/kernel/failure-codes.yaml +3128 -47
  28. package/runtime/modules/models/runtimes.yaml +1 -1
  29. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  30. package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
  31. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  32. package/runtime/scripts/api/git/add.mjs +5 -0
  33. package/runtime/scripts/api/git/cat-file.mjs +5 -0
  34. package/runtime/scripts/api/git/init.mjs +7 -0
  35. package/runtime/scripts/api/git/lib.mjs +3 -0
  36. package/runtime/scripts/api/git/tag.mjs +5 -0
  37. package/runtime/scripts/api/node/lib.mjs +20 -0
  38. package/runtime/scripts/api/node/run-node.mjs +6 -0
  39. package/runtime/scripts/api/sops/lib.mjs +41 -0
  40. package/runtime/scripts/api/sops/resolve-sops.mjs +7 -0
  41. package/runtime/scripts/api/sops/seal.mjs +1 -1
  42. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  43. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  44. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  45. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  46. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  47. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  48. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
  49. package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
  50. package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
  51. package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
  52. package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
  53. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  54. package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
  55. package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
  56. package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
  57. package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
  58. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  59. package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
  60. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  61. package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
  62. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
  63. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  64. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  65. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  66. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
  67. package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
  68. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
  69. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  70. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  71. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  72. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  73. package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
  74. package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
  75. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  76. package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
  77. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
  78. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
  79. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  80. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  81. package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
  82. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
  83. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
  84. package/runtime/scripts/hfs/check-results.mjs +28 -0
  85. package/runtime/scripts/hfs/check.mjs +63 -72
  86. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  87. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  88. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  89. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  90. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  91. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  92. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  93. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  94. package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
  95. package/runtime/scripts/hfs/path-findings.mjs +32 -2
  96. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  97. package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
  98. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  99. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  100. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  101. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  102. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  103. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  104. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  105. package/runtime/scripts/hfs/rules/contract.mjs +66 -5
  106. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  107. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  108. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  109. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  110. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  111. package/runtime/scripts/hfs/rules/database-migrations.mjs +317 -37
  112. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  113. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  114. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  115. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  116. package/runtime/scripts/hfs/rules/database.mjs +11 -2
  117. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  118. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  119. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  120. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  121. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  122. package/runtime/scripts/hfs/rules/edition.mjs +11 -10
  123. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  124. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  125. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  126. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  127. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  128. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  129. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  130. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  131. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  132. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  133. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  134. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  135. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  136. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -0
  137. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  138. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  139. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  140. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  141. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  142. package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
  143. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  144. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  145. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  146. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  147. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  148. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  149. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +168 -0
  150. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  151. package/runtime/scripts/hfs/rules/stacks.mjs +13 -8
  152. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
  153. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  154. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  155. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  156. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  157. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  158. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  159. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  160. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  161. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  162. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  163. package/runtime/scripts/hfs/secret-document.mjs +97 -0
  164. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  165. package/runtime/scripts/hfs/secret-errors.mjs +20 -0
  166. package/runtime/scripts/hfs/secret-files.mjs +124 -0
  167. package/runtime/scripts/hfs/secret-init.mjs +300 -0
  168. package/runtime/scripts/hfs/secret-output.mjs +100 -0
  169. package/runtime/scripts/hfs/secret-sops.mjs +130 -0
  170. package/runtime/scripts/hfs/secret-store.mjs +163 -0
  171. package/runtime/scripts/hfs/secret.mjs +162 -193
  172. package/runtime/scripts/hfs/slot-imports.mjs +18 -3
  173. package/runtime/scripts/hfs/slot-required.mjs +13 -0
  174. package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
  175. package/runtime/scripts/hfs/slots.mjs +1 -0
  176. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  177. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  178. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  179. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  180. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  181. package/runtime/scripts/hfs/tree-findings.mjs +22 -0
  182. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  183. package/runtime/scripts/lib/compose-values.mjs +11 -0
  184. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  185. package/runtime/scripts/lib/json-clone.mjs +61 -0
  186. package/runtime/scripts/lib/list.mjs +4 -0
  187. package/runtime/scripts/lib/roots.mjs +59 -0
  188. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  189. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  190. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  191. package/runtime/scripts/lib/stack-services.mjs +1 -1
  192. package/runtime/scripts/lib/swarm-stack.mjs +126 -0
  193. package/runtime/scripts/lib/trim-end.mjs +8 -0
  194. package/runtime/scripts/lib/ts-analysis.mjs +23 -0
  195. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  196. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  197. package/scaffold/add-app.mjs +77 -59
  198. package/scaffold/add-cli-lite.mjs +24 -17
  199. package/scaffold/add-compose.mjs +79 -0
  200. package/scaffold/add-database-lite.mjs +14 -0
  201. package/scaffold/add-fe-overlay.mjs +122 -0
  202. package/scaffold/add-fe-page.mjs +269 -0
  203. package/scaffold/add-inbox-lite.mjs +87 -124
  204. package/scaffold/add-plan.mjs +183 -0
  205. package/scaffold/add-table.mjs +132 -70
  206. package/scaffold/add.mjs +186 -204
  207. package/scaffold/app.mjs +128 -43
  208. package/scaffold/edition-gate.mjs +3 -3
  209. package/scaffold/image.mjs +2 -2
  210. package/scaffold/lite-exports.mjs +2 -1
  211. package/scaffold/service.mjs +2 -2
  212. package/src/main.mjs +238 -119
  213. package/stack/age-install.mjs +37 -0
  214. package/stack/backup.mjs +100 -0
  215. package/stack/build-args.mjs +42 -0
  216. package/stack/build.mjs +173 -0
  217. package/stack/cli.mjs +130 -0
  218. package/stack/config.mjs +57 -0
  219. package/stack/context.mjs +57 -0
  220. package/stack/converge.mjs +107 -0
  221. package/stack/deploy.mjs +301 -0
  222. package/stack/digest.mjs +16 -0
  223. package/stack/docker-transport.mjs +53 -0
  224. package/stack/edge-alias.mjs +118 -0
  225. package/stack/errors.mjs +21 -0
  226. package/stack/image-scan.mjs +152 -0
  227. package/stack/ingress-create.mjs +114 -0
  228. package/stack/ingress-publish.mjs +273 -0
  229. package/stack/ingress.mjs +706 -0
  230. package/stack/jobs.mjs +51 -0
  231. package/stack/live-restore.mjs +177 -0
  232. package/stack/prepare.mjs +266 -0
  233. package/stack/private-file.mjs +22 -0
  234. package/stack/provision.mjs +90 -0
  235. package/stack/prune.mjs +46 -0
  236. package/stack/release-store.mjs +87 -0
  237. package/stack/remote.mjs +205 -0
  238. package/stack/render.mjs +149 -0
  239. package/stack/retire.mjs +47 -0
  240. package/stack/sealed.mjs +149 -0
  241. package/stack/task.mjs +42 -0
  242. package/stack/verify.mjs +99 -0
  243. package/sync/ci-commands.mjs +13 -0
  244. package/sync/index.mjs +127 -64
  245. package/sync/stack-images.mjs +127 -0
  246. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  247. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  248. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  249. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  250. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  251. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  252. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  253. package/templates/app/codegen-graphql.mjs.partial +13 -2
  254. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  255. package/templates/app/codegen-prelude.mjs.partial +8 -0
  256. package/templates/app/gitignore +4 -0
  257. package/templates/app/gitignore-lite +4 -0
  258. package/templates/app/hooks/husky/pre-commit +7 -12
  259. package/templates/app/package-scripts/package.json +1 -0
  260. package/templates/app/package-scripts/supabase.json +7 -0
  261. package/templates/app/package-scripts-lite/package.json +1 -7
  262. package/templates/app/skeleton/.editorconfig +1 -0
  263. package/templates/app/skeleton/.gitattributes +7 -1
  264. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  265. package/templates/app/skeleton/.nvmrc +1 -1
  266. package/templates/app/skeleton/.sops.yaml +1 -1
  267. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  268. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  269. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  270. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  271. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  272. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  273. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  274. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  275. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  276. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  277. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  278. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  279. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  280. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  281. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  282. package/templates/app/skeleton/README.md +6 -6
  283. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  284. package/templates/app/skeleton-lite/.editorconfig +16 -0
  285. package/templates/app/skeleton-lite/.gitattributes +8 -0
  286. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  287. package/templates/app/skeleton-lite/.nvmrc +1 -0
  288. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  289. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +193 -0
  290. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  291. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  292. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  293. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  294. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  295. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  296. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  297. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  298. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  299. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  300. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  301. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  302. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  303. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  304. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  305. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  306. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  307. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  308. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  309. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  310. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  311. package/templates/app/skeleton-lite/.starciwork/workspace.yaml +9 -0
  312. package/templates/app/skeleton-lite/README.md +8 -4
  313. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  314. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  315. package/templates/app/starciwork.gitignore-lite +6 -0
  316. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  317. package/templates/be/image/worker/Dockerfile +1 -1
  318. package/templates/be/inbox/database-client.spec.ts.tpl +22 -28
  319. package/templates/be/inbox/database-client.ts.tpl +14 -28
  320. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  321. package/templates/be/inbox/event-index.ts.tpl +1 -0
  322. package/templates/be/inbox/events.json.tpl +10 -0
  323. package/templates/be/inbox/migration.sql.tpl +1 -0
  324. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  325. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  326. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  327. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  328. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  329. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  330. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  331. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  332. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  333. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  334. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  335. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  336. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  337. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  338. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  339. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  340. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  341. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  342. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  343. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  344. package/templates/be/inbox/service.spec.ts.tpl +49 -20
  345. package/templates/be/inbox/service.ts.tpl +27 -16
  346. package/templates/be/inbox/sql.ts.tpl +2 -2
  347. package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -3
  348. package/templates/be/inbox/webhook-door.ts.tpl +4 -3
  349. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  350. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  351. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  352. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  353. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  354. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  355. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  356. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  357. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  358. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  359. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  360. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  362. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  363. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  364. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  365. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  366. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  367. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  368. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  369. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  370. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  371. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  372. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  373. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  374. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  375. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  376. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  377. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  378. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  379. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  380. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  381. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  382. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  383. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  384. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  385. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  386. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  387. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  388. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  389. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  390. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  391. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  392. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  393. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  394. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  395. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  396. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +3 -3
  397. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +3 -3
  398. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  399. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  400. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  401. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  402. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  403. package/templates/be/skeleton/apps/__app__/src/__app__.env.ts +21 -0
  404. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +1 -1
  405. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -45
  406. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  407. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  408. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +44 -0
  409. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  410. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  411. package/templates/be/skeleton/apps/__app__/src/main.ts +18 -10
  412. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  413. package/templates/be/skeleton/apps/cli/src/cli.env.ts +13 -0
  414. package/templates/be/skeleton/apps/cli/src/cli.options.ts +7 -17
  415. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  416. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  417. package/templates/be/skeleton/apps/cli/src/main.ts +3 -2
  418. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  419. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  420. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  421. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  422. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  423. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  424. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  425. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  426. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  427. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  428. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  429. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  430. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  431. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  432. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  433. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  434. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  435. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  436. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  437. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  438. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  439. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  440. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  441. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  442. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  443. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  444. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  445. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  446. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  447. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  448. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  449. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  450. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +6 -6
  451. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  452. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  453. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  454. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  455. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  456. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  457. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  458. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  459. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  460. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  461. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  462. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  463. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  464. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  465. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  466. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  467. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  468. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  469. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  470. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  471. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  472. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  473. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  474. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  475. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  476. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  477. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  478. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  479. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  480. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  481. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  482. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  483. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  484. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  485. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  486. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  487. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  488. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  489. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +25 -0
  490. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +1 -1
  491. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +18 -58
  492. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  493. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  494. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  495. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +47 -0
  496. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  497. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  498. package/templates/be/skeleton-lite/apps/api/src/main.ts +5 -13
  499. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +17 -0
  500. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  501. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  502. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  503. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  504. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  505. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  506. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  507. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  508. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  509. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  510. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  511. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  512. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  513. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  514. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  515. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  516. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  517. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  518. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  519. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  520. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  521. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  522. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  523. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  524. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  525. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  526. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  527. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  528. package/templates/be/table/migration.sql.tpl +1 -0
  529. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  530. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  531. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  532. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  533. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  534. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  535. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  536. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  537. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  538. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  539. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  540. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  541. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  542. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  543. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  544. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  545. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  546. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  547. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  548. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  549. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  550. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  551. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  552. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  553. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  554. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  555. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  556. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  557. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  558. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  559. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  560. package/upgrade/check.mjs +3 -1
  561. package/upgrade/file-state.mjs +68 -0
  562. package/upgrade/index.mjs +58 -94
  563. package/runtime/scripts/api/git/log.mjs +0 -5
  564. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +0 -55
@@ -0,0 +1,127 @@
1
+ // The Swarm deploy images an app declares, as the managed stack-deploy.yml workflow needs them.
2
+ //
3
+ // The workflow builds one image per `deploy.images` entry of .starcistacks/application-stacks.yaml and hands the pinned references to
4
+ // `starci app stack deploy`. Sync renders that list into the workflow, so it reads the declaration here: the union over the declared
5
+ // environments of `environments.<env>.deploy.images` (service -> { app, buildArgs }). Only names and the presence of build arguments are
6
+ // read; the real validation of the block is stack-deploy-config.mjs (`starci app check`, rule R294). Everything this module returns is
7
+ // pure text assembled from names that match the strict patterns below, so no free text reaches the rendered workflow.
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { parseYaml as bundledParseYaml } from '../runtime/engine/yaml.mjs';
11
+ import { DECLARATION } from './sonar-key.mjs';
12
+ import { byCodeUnit } from '../report/order.mjs';
13
+
14
+ const NAME = /^[a-z][a-z0-9-]*$/u;
15
+ const KEY = /^[A-Z][A-Z0-9_]*$/u;
16
+ const INDENT = ' '.repeat(6);
17
+ const CONTINUE = '\\';
18
+ const NO_IMAGES_MESSAGE = 'no image is declared: declare environments.<env>.deploy (with images) in .starcistacks/application-stacks.yaml before running this workflow';
19
+
20
+ const record = value => value !== null && typeof value === 'object' && !Array.isArray(value);
21
+ const hasBuildArgs = args => record(args) && (Object.keys(record(args.values) ? args.values : {}).length > 0 || record(args.sealed));
22
+
23
+ function environmentImages(environment) {
24
+ const images = environment?.deploy?.images;
25
+ if (!record(images)) return [];
26
+ return Object.entries(images)
27
+ .filter(([service, entry]) => NAME.test(service) && record(entry) && NAME.test(String(entry.app)))
28
+ .map(([service, entry]) => ({ service, app: entry.app, buildArgs: hasBuildArgs(entry.buildArgs) }));
29
+ }
30
+
31
+ /**
32
+ * The images of a parsed declaration, [{ service, app, buildArgs }] sorted by service: the union over its environments (the first
33
+ * environment that names a service decides its app; a later one only adds build arguments). A declaration with no deploy block has none.
34
+ */
35
+ export function declaredStackImages(declaration) {
36
+ const byService = new Map();
37
+ for (const environment of Object.values(record(declaration?.environments) ? declaration.environments : {})) {
38
+ for (const image of environmentImages(environment)) {
39
+ const known = byService.get(image.service);
40
+ byService.set(image.service, known ? { ...known, buildArgs: known.buildArgs || image.buildArgs } : image);
41
+ }
42
+ }
43
+ return [...byService.values()].sort((a, b) => byCodeUnit(a.service, b.service));
44
+ }
45
+
46
+ /** The host variables (names only) the declared environments interpolate into `docker stack config`: the union of `deploy.passEnv`. */
47
+ export function declaredPassEnv(declaration) {
48
+ const names = Object.values(record(declaration?.environments) ? declaration.environments : {})
49
+ .flatMap(environment => (Array.isArray(environment?.deploy?.passEnv) ? environment.deploy.passEnv : []))
50
+ .filter(name => typeof name === 'string' && KEY.test(name));
51
+ return [...new Set(names)].sort(byCodeUnit);
52
+ }
53
+
54
+ /**
55
+ * What the managed workflow renders from the declaration of the app at `root`: { images, passEnv } (both empty when the app has no
56
+ * declaration, no deploy block or a declaration that does not parse; `starci app check` judges those). `parseYaml(text)` is injected.
57
+ */
58
+ export function readDeclaredStack(root, { parseYaml = bundledParseYaml } = {}) {
59
+ const file = path.join(root, DECLARATION);
60
+ if (!fs.existsSync(file)) return { images: [], passEnv: [] };
61
+ let declaration;
62
+ try {
63
+ declaration = parseYaml(fs.readFileSync(file, 'utf8'));
64
+ } catch {
65
+ return { images: [], passEnv: [] };
66
+ }
67
+ return { images: declaredStackImages(declaration), passEnv: declaredPassEnv(declaration) };
68
+ }
69
+
70
+ const envName = service => `IMAGE_${service.toUpperCase().replaceAll('-', '_')}`;
71
+ /** Lines placed at the end of a template line: each one starts with a newline, so nothing declared leaves nothing, not even a blank line. */
72
+ const block = (items, indent = INDENT) => items.map(item => `\n${indent}${item}`).join('');
73
+
74
+ function buildArgsStep(image) {
75
+ return [
76
+ `- name: public build values of ${image.service}`,
77
+ ` id: args-${image.service}`,
78
+ ' env:',
79
+ ' SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}',
80
+ ` run: npx --no-install starci app stack build-args --env "$STACK_ENVIRONMENT" --app ${image.app}`,
81
+ ];
82
+ }
83
+
84
+ function buildPushStep(image, dockerfile) {
85
+ return [
86
+ `- name: build and push ${image.service}`,
87
+ ` id: build-${image.service}`,
88
+ ' uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2',
89
+ ' with:',
90
+ ' context: .',
91
+ ` file: ${dockerfile}`,
92
+ ' push: true',
93
+ ' provenance: false',
94
+ ` tags: \${{ steps.names.outputs.${image.service} }}:\${{ github.sha }}`,
95
+ ...(image.buildArgs ? [` build-args: \${{ steps.args-${image.service}.outputs.args }}`] : []),
96
+ ` cache-from: type=gha,scope=${image.service}`,
97
+ ` cache-to: type=gha,scope=${image.service},mode=max`,
98
+ ];
99
+ }
100
+
101
+ /**
102
+ * The text blocks the stack-deploy.yml template places at the end of a line (each already indented for its position, every line led by
103
+ * a newline; empty when there is nothing to place):
104
+ * stackGuard the first step of the release job when the app declares no image (it fails with the reason)
105
+ * stackNames the `names` step lines assigning each image repository (ghcr.io/<lowercase owner>/<project>-<app>)
106
+ * stackImageSteps per image: the optional build-args step and the build-and-push step
107
+ * stackImageEnv the env entries of the deploy step: IMAGE_<SERVICE> = repository@digest
108
+ * stackImageArgs the `--image "<service>=$IMAGE_<SERVICE>" \` continuation lines of the deploy command
109
+ * stackPassEnv the env entries of the deploy step for the declared host variables (vars.<NAME>)
110
+ * `dockerfileOf(app)` names the Dockerfile of an app (the sync module knows the layout).
111
+ */
112
+ export function stackWorkflowVariables({ images = [], passEnv = [] }, project, dockerfileOf) {
113
+ const guard = images.length ? '' : block([
114
+ '- name: require a declared image',
115
+ ' shell: bash',
116
+ ` run: 'echo "::error::${NO_IMAGES_MESSAGE}"; exit 1'`,
117
+ ]);
118
+ const names = images.map(image => `echo "${image.service}=ghcr.io/\${owner}/${project}-${image.app}" >> "$GITHUB_OUTPUT"`);
119
+ return {
120
+ stackGuard: guard,
121
+ stackNames: block(names, ' '.repeat(10)),
122
+ stackImageSteps: images.map(image => block([...(image.buildArgs ? buildArgsStep(image) : []), ...buildPushStep(image, dockerfileOf(image.app))])).join(''),
123
+ stackImageEnv: block(images.map(image => `${envName(image.service)}: \${{ steps.names.outputs.${image.service} }}@\${{ steps.build-${image.service}.outputs.digest }}`), ' '.repeat(10)),
124
+ stackImageArgs: block(images.map(image => `--image "${image.service}=$${envName(image.service)}" ${CONTINUE}`), ' '.repeat(12)),
125
+ stackPassEnv: block(passEnv.map(name => `${name}: \${{ vars.${name} }}`), ' '.repeat(10)),
126
+ };
127
+ }
@@ -30,14 +30,18 @@ jobs:
30
30
  node-version: {{nodeMajor}}
31
31
  cache: npm
32
32
  - run: npm ci --ignore-scripts
33
+ - name: production dependency audit
34
+ run: npm audit --omit=dev --audit-level=high
33
35
  - name: lint
34
- run: npm run lint -- --sonar reports/lint.sonar.json
36
+ run: {{ciLintSonar}}
35
37
  - name: format
36
38
  run: npm run format:check
37
39
  - name: typecheck
38
40
  run: npm run typecheck
39
41
  - name: unit
40
42
  run: npm test -- --ci
43
+ - name: coverage proof
44
+ run: {{ciCoverageProof}}
41
45
  - name: coverage upload
42
46
  uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
43
47
  if: ${{ !cancelled() && startsWith(github.ref, 'refs/tags/v') }}
@@ -1,12 +1,11 @@
1
1
  {{header}}
2
- # Builds the image of every app on the release tag, so a Dockerfile that no longer builds fails the release (or a manual dispatch) that carries it.
2
+ # Builds the image of every app on its release tag, so a Dockerfile that no longer builds fails that release.
3
3
  # It never pushes: publishing an image is the product's decision, not this file's.
4
4
  name: images
5
5
 
6
6
  on:
7
7
  push:
8
8
  tags: ["v*"]
9
- workflow_dispatch:
10
9
 
11
10
  permissions:
12
11
  contents: read
@@ -26,12 +25,25 @@ jobs:
26
25
  {{imageMatrix}}
27
26
  steps:
28
27
  - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
28
+ - name: release tag matches package version
29
+ run: node -e "const version=require('./package.json').version;if(process.env.GITHUB_REF_NAME!=='v'+version)process.exit(1)"
29
30
  - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
30
31
  - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
31
32
  with:
32
33
  context: .
33
34
  file: ${{ matrix.file }}
34
35
  push: false
35
- tags: {{imageProject}}/${{ matrix.name }}:ci
36
+ tags: {{imageProject}}/${{ matrix.name }}:${{ github.ref_name }}
37
+ # OCI keeps the SBOM and provenance attestations without publishing to a registry.
38
+ outputs: type=oci,dest=${{ runner.temp }}/${{ matrix.name }}.oci.tar
39
+ sbom: true
40
+ provenance: mode=max
36
41
  cache-from: type=gha,scope=${{ matrix.name }}
37
42
  cache-to: type=gha,scope=${{ matrix.name }},mode=max
43
+ - name: retain image with SBOM and provenance
44
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
45
+ with:
46
+ name: image-${{ matrix.name }}-${{ github.sha }}-${{ github.run_attempt }}
47
+ path: ${{ runner.temp }}/${{ matrix.name }}.oci.tar
48
+ if-no-files-found: error
49
+ compression-level: 0
@@ -0,0 +1,148 @@
1
+ {{header}}
2
+ # Deploys the app's Docker Swarm stack. This file only assembles arguments: the logic lives in the verbs connect, build-args, deploy,
3
+ # rollback and disconnect of "starci app stack". It runs by hand only (Actions > deploy stack > Run workflow) from a release tag v*
4
+ # whose version is the package.json version; it has no push or schedule trigger.
5
+ # operation deploy builds and pushes one image per environments.<env>.deploy.images entry of .starcistacks/application-stacks.yaml,
6
+ # then deploys their immutable digests (render, provision, converge, verify, accept; a failure redeploys the previous release)
7
+ # operation rollback redeploys a stored release; "release" is 16 lowercase hex characters, empty means the previous accepted release
8
+ # The environment input names a protected GitHub environment (required reviewers) that holds:
9
+ # vars VPS_HOST, VPS_USER (the swarm manager); a variable named in deploy.passEnv, when the stack interpolates one
10
+ # secrets SOPS_AGE_KEY (the age identity of .sops.yaml), VPS_SSH_KEY (CI-only deploy key), VPS_KNOWN_HOSTS (pinned host keys)
11
+ # Nothing else is a secret: images are pushed with the workflow token. Dispatch inputs reach scripts only through env.
12
+ name: deploy stack
13
+
14
+ on:
15
+ workflow_dispatch:
16
+ inputs:
17
+ environment:
18
+ description: Protected environment holding the VPS variables and the sealed custody under .starcistacks
19
+ required: true
20
+ type: environment
21
+ operation:
22
+ description: Deploy a new release or roll back to a stored one
23
+ required: true
24
+ type: choice
25
+ default: deploy
26
+ options:
27
+ - deploy
28
+ - rollback
29
+ release:
30
+ description: "Rollback only: release id (16 lowercase hex) to deploy again; empty means the previous accepted release"
31
+ required: false
32
+ type: string
33
+ default: ""
34
+
35
+ permissions:
36
+ contents: read
37
+
38
+ concurrency:
39
+ group: stack-${{ inputs.environment }}
40
+ cancel-in-progress: false
41
+
42
+ jobs:
43
+ release:
44
+ if: ${{ inputs.operation == 'deploy' }}
45
+ runs-on: ubuntu-latest
46
+ environment: ${{ inputs.environment }}
47
+ timeout-minutes: 60
48
+ permissions:
49
+ contents: read
50
+ packages: write
51
+ env:
52
+ STACK_ENVIRONMENT: ${{ inputs.environment }}
53
+ XDG_DATA_HOME: ${{ runner.temp }}/starci-data
54
+ steps:{{stackGuard}}
55
+ - name: require release tag
56
+ shell: bash
57
+ run: '[[ "$GITHUB_REF" == refs/tags/v* ]] || exit 1'
58
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
59
+ - name: release tag matches package version
60
+ run: node -e "const version=require('./package.json').version;if(process.env.GITHUB_REF_NAME!=='v'+version)process.exit(1)"
61
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
62
+ with:
63
+ node-version: {{nodeMajor}}
64
+ cache: npm
65
+ - run: npm ci --ignore-scripts
66
+ - name: install SOPS
67
+ uses: mdgreenwald/mozilla-sops-action@bb2d96a13ef438ad122e5827a57ebd75d6ff118d # v2.1.0
68
+ with:
69
+ version: v3.13.2
70
+ - name: image repositories
71
+ id: names
72
+ shell: bash
73
+ run: |
74
+ owner="${GITHUB_REPOSITORY_OWNER,,}"{{stackNames}}
75
+ - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
76
+ - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
77
+ with:
78
+ registry: ghcr.io
79
+ username: ${{ github.actor }}
80
+ password: ${{ github.token }}{{stackImageSteps}}
81
+ - name: connect to the swarm manager
82
+ env:
83
+ VPS_SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
84
+ VPS_KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
85
+ VPS_HOST: ${{ vars.VPS_HOST }}
86
+ VPS_USER: ${{ vars.VPS_USER }}
87
+ run: npx --no-install starci app stack connect --env "$STACK_ENVIRONMENT" --execute
88
+ - name: deploy the release
89
+ env:
90
+ SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}{{stackImageEnv}}{{stackPassEnv}}
91
+ run: |
92
+ npx --no-install starci app stack deploy --env "$STACK_ENVIRONMENT" \{{stackImageArgs}}
93
+ --source-sha "$GITHUB_SHA" --execute
94
+ - name: disconnect from the swarm manager
95
+ if: ${{ always() }}
96
+ run: npx --no-install starci app stack disconnect --env "$STACK_ENVIRONMENT" --execute
97
+
98
+ rollback:
99
+ if: ${{ inputs.operation == 'rollback' }}
100
+ runs-on: ubuntu-latest
101
+ environment: ${{ inputs.environment }}
102
+ timeout-minutes: 30
103
+ permissions:
104
+ contents: read
105
+ packages: read
106
+ env:
107
+ STACK_ENVIRONMENT: ${{ inputs.environment }}
108
+ XDG_DATA_HOME: ${{ runner.temp }}/starci-data
109
+ steps:
110
+ - name: require release tag
111
+ shell: bash
112
+ run: '[[ "$GITHUB_REF" == refs/tags/v* ]] || exit 1'
113
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
114
+ - name: release tag matches package version
115
+ run: node -e "const version=require('./package.json').version;if(process.env.GITHUB_REF_NAME!=='v'+version)process.exit(1)"
116
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
117
+ with:
118
+ node-version: {{nodeMajor}}
119
+ cache: npm
120
+ - run: npm ci --ignore-scripts
121
+ - name: install SOPS
122
+ uses: mdgreenwald/mozilla-sops-action@bb2d96a13ef438ad122e5827a57ebd75d6ff118d # v2.1.0
123
+ with:
124
+ version: v3.13.2
125
+ - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
126
+ with:
127
+ registry: ghcr.io
128
+ username: ${{ github.actor }}
129
+ password: ${{ github.token }}
130
+ - name: connect to the swarm manager
131
+ env:
132
+ VPS_SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
133
+ VPS_KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
134
+ VPS_HOST: ${{ vars.VPS_HOST }}
135
+ VPS_USER: ${{ vars.VPS_USER }}
136
+ run: npx --no-install starci app stack connect --env "$STACK_ENVIRONMENT" --execute
137
+ - name: redeploy the stored release
138
+ env:
139
+ SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
140
+ RELEASE: ${{ inputs.release }}
141
+ shell: bash
142
+ run: |
143
+ release=()
144
+ [ -z "$RELEASE" ] || release=(--release "$RELEASE")
145
+ npx --no-install starci app stack rollback --env "$STACK_ENVIRONMENT" "${release[@]}" --execute
146
+ - name: disconnect from the swarm manager
147
+ if: ${{ always() }}
148
+ run: npx --no-install starci app stack disconnect --env "$STACK_ENVIRONMENT" --execute
@@ -30,18 +30,22 @@ jobs:
30
30
  node-version: {{nodeMajor}}
31
31
  cache: npm
32
32
  - run: npm ci --ignore-scripts
33
+ - name: production dependency audit
34
+ run: npm audit --omit=dev --audit-level=high
33
35
  - name: lint
34
36
  if: env.SONAR_TOKEN == ''
35
- run: npm run lint
37
+ run: {{ciLint}}
36
38
  - name: lint with Sonar import
37
39
  if: env.SONAR_TOKEN != ''
38
- run: npm run lint -- --sonar reports/lint.sonar.json
40
+ run: {{ciLintSonar}}
39
41
  - name: format
40
42
  run: npm run format:check
41
43
  - name: typecheck
42
44
  run: npm run typecheck
43
45
  - name: unit
44
46
  run: npm test -- --ci
47
+ - name: coverage proof
48
+ run: {{ciCoverageProof}}
45
49
  - name: coverage upload
46
50
  uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
47
51
  if: ${{ !cancelled() && startsWith(github.ref, 'refs/tags/v') }}
@@ -55,7 +59,7 @@ jobs:
55
59
  - name: build fe
56
60
  run: npm run build:fe
57
61
  - name: database migration checks
58
- run: npm run db:lint
62
+ run: {{ciDatabaseLint}}
59
63
  {{> app/ci-workflows/sonar-steps.yml}}
60
64
 
61
65
  db-types:
@@ -5,27 +5,59 @@ on:
5
5
  workflow_dispatch:
6
6
  inputs:
7
7
  environment:
8
- description: GitHub environment whose Supabase secrets will be used
8
+ description: Protected environment with sealed Supabase custody under .starcistacks
9
9
  required: true
10
10
  type: environment
11
+ backup_receipt:
12
+ description: Tracked backup receipt path verified by the deployment owner before migration
13
+ required: true
14
+ type: string
11
15
 
12
16
  permissions:
13
17
  contents: read
14
18
 
19
+ concurrency:
20
+ group: database-${{ inputs.environment }}
21
+ cancel-in-progress: false
22
+
15
23
  jobs:
16
24
  deploy:
17
25
  runs-on: ubuntu-latest
18
26
  environment: ${{ inputs.environment }}
19
27
  env:
20
- SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
21
- SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
22
- SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }}
28
+ STACK_ENVIRONMENT: ${{ inputs.environment }}
29
+ BACKUP_RECEIPT: ${{ inputs.backup_receipt }}
30
+ SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
31
+ XDG_DATA_HOME: ${{ runner.temp }}/starci-data
23
32
  steps:
33
+ - name: require release tag
34
+ shell: bash
35
+ run: '[[ "$GITHUB_REF" == refs/tags/v* ]] || exit 1'
24
36
  - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
37
+ - name: release tag matches package version
38
+ run: node -e "const version=require('./package.json').version;if(process.env.GITHUB_REF_NAME!=='v'+version)process.exit(1)"
25
39
  - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
26
40
  with:
27
41
  node-version: {{nodeMajor}}
28
42
  cache: npm
29
43
  - run: npm ci --ignore-scripts
30
- - name: push migrations
31
- run: npm run db:push
44
+ - name: install SOPS
45
+ uses: mdgreenwald/mozilla-sops-action@bb2d96a13ef438ad122e5827a57ebd75d6ff118d # v2.1.0
46
+ with:
47
+ version: v3.13.2
48
+ - name: materialize sealed custody and push migrations
49
+ shell: bash
50
+ run: |
51
+ set -euo pipefail
52
+ [[ "$STACK_ENVIRONMENT" =~ ^[a-z][a-z0-9-]*$ ]]
53
+ git ls-files --error-unmatch -- "$BACKUP_RECEIPT" > /dev/null
54
+ test -s "$BACKUP_RECEIPT"
55
+ secret_dir="$XDG_DATA_HOME/StarCi/secrets/{{project}}/$STACK_ENVIRONMENT"
56
+ trap 'rm -rf -- "$secret_dir"' EXIT
57
+ npx --no-install starci app secret materialize "$STACK_ENVIRONMENT"
58
+ export SUPABASE_ACCESS_TOKEN="$(cat "$secret_dir/SUPABASE_ACCESS_TOKEN")"
59
+ export SUPABASE_DB_PASSWORD="$(cat "$secret_dir/SUPABASE_DB_PASSWORD")"
60
+ project_id="$(cat "$secret_dir/SUPABASE_PROJECT_ID")"
61
+ npx --no-install supabase link --project-ref "$project_id"
62
+ npm run db:push -- --dry-run
63
+ npm run db:push
@@ -6,7 +6,6 @@ name: images
6
6
  on:
7
7
  push:
8
8
  tags: ["v*"]
9
- workflow_dispatch:
10
9
 
11
10
  permissions:
12
11
  contents: read
@@ -26,12 +25,25 @@ jobs:
26
25
  {{imageMatrix}}
27
26
  steps:
28
27
  - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
28
+ - name: release tag matches package version
29
+ run: node -e "const version=require('./package.json').version;if(process.env.GITHUB_REF_NAME!=='v'+version)process.exit(1)"
29
30
  - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
30
31
  - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
31
32
  with:
32
33
  context: .
33
34
  file: ${{ matrix.file }}
34
35
  push: false
35
- tags: {{imageProject}}/${{ matrix.name }}:ci
36
+ tags: {{imageProject}}/${{ matrix.name }}:${{ github.ref_name }}
37
+ # OCI keeps the SBOM and provenance attestations without publishing to a registry.
38
+ outputs: type=oci,dest=${{ runner.temp }}/${{ matrix.name }}.oci.tar
39
+ sbom: true
40
+ provenance: mode=max
36
41
  cache-from: type=gha,scope=${{ matrix.name }}
37
42
  cache-to: type=gha,scope=${{ matrix.name }},mode=max
43
+ - name: retain image with SBOM and provenance
44
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
45
+ with:
46
+ name: image-${{ matrix.name }}-${{ github.sha }}-${{ github.run_attempt }}
47
+ path: ${{ runner.temp }}/${{ matrix.name }}.oci.tar
48
+ if-no-files-found: error
49
+ compression-level: 0
@@ -0,0 +1,148 @@
1
+ {{header}}
2
+ # Deploys the app's Docker Swarm stack. This file only assembles arguments: the logic lives in the verbs connect, build-args, deploy,
3
+ # rollback and disconnect of "starci app stack". It runs by hand only (Actions > deploy stack > Run workflow) from a release tag v*
4
+ # whose version is the package.json version; it has no push or schedule trigger.
5
+ # operation deploy builds and pushes one image per environments.<env>.deploy.images entry of .starcistacks/application-stacks.yaml,
6
+ # then deploys their immutable digests (render, provision, converge, verify, accept; a failure redeploys the previous release)
7
+ # operation rollback redeploys a stored release; "release" is 16 lowercase hex characters, empty means the previous accepted release
8
+ # The environment input names a protected GitHub environment (required reviewers) that holds:
9
+ # vars VPS_HOST, VPS_USER (the swarm manager); a variable named in deploy.passEnv, when the stack interpolates one
10
+ # secrets SOPS_AGE_KEY (the age identity of .sops.yaml), VPS_SSH_KEY (CI-only deploy key), VPS_KNOWN_HOSTS (pinned host keys)
11
+ # Nothing else is a secret: images are pushed with the workflow token. Dispatch inputs reach scripts only through env.
12
+ name: deploy stack
13
+
14
+ on:
15
+ workflow_dispatch:
16
+ inputs:
17
+ environment:
18
+ description: Protected environment holding the VPS variables and the sealed custody under .starcistacks
19
+ required: true
20
+ type: environment
21
+ operation:
22
+ description: Deploy a new release or roll back to a stored one
23
+ required: true
24
+ type: choice
25
+ default: deploy
26
+ options:
27
+ - deploy
28
+ - rollback
29
+ release:
30
+ description: "Rollback only: release id (16 lowercase hex) to deploy again; empty means the previous accepted release"
31
+ required: false
32
+ type: string
33
+ default: ""
34
+
35
+ permissions:
36
+ contents: read
37
+
38
+ concurrency:
39
+ group: stack-${{ inputs.environment }}
40
+ cancel-in-progress: false
41
+
42
+ jobs:
43
+ release:
44
+ if: ${{ inputs.operation == 'deploy' }}
45
+ runs-on: ubuntu-latest
46
+ environment: ${{ inputs.environment }}
47
+ timeout-minutes: 60
48
+ permissions:
49
+ contents: read
50
+ packages: write
51
+ env:
52
+ STACK_ENVIRONMENT: ${{ inputs.environment }}
53
+ XDG_DATA_HOME: ${{ runner.temp }}/starci-data
54
+ steps:{{stackGuard}}
55
+ - name: require release tag
56
+ shell: bash
57
+ run: '[[ "$GITHUB_REF" == refs/tags/v* ]] || exit 1'
58
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
59
+ - name: release tag matches package version
60
+ run: node -e "const version=require('./package.json').version;if(process.env.GITHUB_REF_NAME!=='v'+version)process.exit(1)"
61
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
62
+ with:
63
+ node-version: {{nodeMajor}}
64
+ cache: npm
65
+ - run: npm ci --ignore-scripts
66
+ - name: install SOPS
67
+ uses: mdgreenwald/mozilla-sops-action@bb2d96a13ef438ad122e5827a57ebd75d6ff118d # v2.1.0
68
+ with:
69
+ version: v3.13.2
70
+ - name: image repositories
71
+ id: names
72
+ shell: bash
73
+ run: |
74
+ owner="${GITHUB_REPOSITORY_OWNER,,}"{{stackNames}}
75
+ - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
76
+ - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
77
+ with:
78
+ registry: ghcr.io
79
+ username: ${{ github.actor }}
80
+ password: ${{ github.token }}{{stackImageSteps}}
81
+ - name: connect to the swarm manager
82
+ env:
83
+ VPS_SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
84
+ VPS_KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
85
+ VPS_HOST: ${{ vars.VPS_HOST }}
86
+ VPS_USER: ${{ vars.VPS_USER }}
87
+ run: npx --no-install starci app stack connect --env "$STACK_ENVIRONMENT" --execute
88
+ - name: deploy the release
89
+ env:
90
+ SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}{{stackImageEnv}}{{stackPassEnv}}
91
+ run: |
92
+ npx --no-install starci app stack deploy --env "$STACK_ENVIRONMENT" \{{stackImageArgs}}
93
+ --source-sha "$GITHUB_SHA" --execute
94
+ - name: disconnect from the swarm manager
95
+ if: ${{ always() }}
96
+ run: npx --no-install starci app stack disconnect --env "$STACK_ENVIRONMENT" --execute
97
+
98
+ rollback:
99
+ if: ${{ inputs.operation == 'rollback' }}
100
+ runs-on: ubuntu-latest
101
+ environment: ${{ inputs.environment }}
102
+ timeout-minutes: 30
103
+ permissions:
104
+ contents: read
105
+ packages: read
106
+ env:
107
+ STACK_ENVIRONMENT: ${{ inputs.environment }}
108
+ XDG_DATA_HOME: ${{ runner.temp }}/starci-data
109
+ steps:
110
+ - name: require release tag
111
+ shell: bash
112
+ run: '[[ "$GITHUB_REF" == refs/tags/v* ]] || exit 1'
113
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
114
+ - name: release tag matches package version
115
+ run: node -e "const version=require('./package.json').version;if(process.env.GITHUB_REF_NAME!=='v'+version)process.exit(1)"
116
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
117
+ with:
118
+ node-version: {{nodeMajor}}
119
+ cache: npm
120
+ - run: npm ci --ignore-scripts
121
+ - name: install SOPS
122
+ uses: mdgreenwald/mozilla-sops-action@bb2d96a13ef438ad122e5827a57ebd75d6ff118d # v2.1.0
123
+ with:
124
+ version: v3.13.2
125
+ - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
126
+ with:
127
+ registry: ghcr.io
128
+ username: ${{ github.actor }}
129
+ password: ${{ github.token }}
130
+ - name: connect to the swarm manager
131
+ env:
132
+ VPS_SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
133
+ VPS_KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
134
+ VPS_HOST: ${{ vars.VPS_HOST }}
135
+ VPS_USER: ${{ vars.VPS_USER }}
136
+ run: npx --no-install starci app stack connect --env "$STACK_ENVIRONMENT" --execute
137
+ - name: redeploy the stored release
138
+ env:
139
+ SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
140
+ RELEASE: ${{ inputs.release }}
141
+ shell: bash
142
+ run: |
143
+ release=()
144
+ [ -z "$RELEASE" ] || release=(--release "$RELEASE")
145
+ npx --no-install starci app stack rollback --env "$STACK_ENVIRONMENT" "${release[@]}" --execute
146
+ - name: disconnect from the swarm manager
147
+ if: ${{ always() }}
148
+ run: npx --no-install starci app stack disconnect --env "$STACK_ENVIRONMENT" --execute
@@ -8,12 +8,12 @@ const posix = (file) => file.split(path.sep).join("/")
8
8
  const schemas = (fs.existsSync(CONTRACTS) ? fs.readdirSync(CONTRACTS) : [])
9
9
  .map((app) => path.join(CONTRACTS, app, "schema.graphql"))
10
10
  .filter((file) => fs.existsSync(file))
11
- .sort()
11
+ .sort(byCodeUnit)
12
12
  .map(posix)
13
13
  const operationRoots = (fs.existsSync(FE_APPS) ? fs.readdirSync(FE_APPS) : [])
14
14
  .map((app) => path.join(FE_APPS, app, "src", "modules", "api", "graphql"))
15
15
  .filter((dir) => fs.existsSync(dir))
16
- .sort()
16
+ .sort(byCodeUnit)
17
17
  .map(posix)
18
18
  if (schemas.length === 0 || operationRoots.length === 0) {
19
19
  process.stdout.write("codegen: no GraphQL contract under be/contracts yet\n")
@@ -31,4 +31,15 @@ if (schemas.length === 0 || operationRoots.length === 0) {
31
31
  ]),
32
32
  )
33
33
  await generate({ schema: schemas, generates, ignoreNoDocuments: true, silent: true }, true)
34
+ // Stamp only a successful generation, using the same sorted paths and LF-normalized schemas as R52.
35
+ const hash = createHash("sha256")
36
+ for (const schema of schemas) {
37
+ const source = fs.readFileSync(schema, "utf8").replaceAll("\r\n", "\n")
38
+ hash.update(`${schema}\0${source}\0`)
39
+ }
40
+ const digest = hash.digest("hex")
41
+ for (const root of operationRoots) {
42
+ const generated = `${root}/__generated__`
43
+ if (fs.existsSync(generated)) fs.writeFileSync(`${generated}/.schema-sha256`, `${digest}\n`)
44
+ }
34
45
  }