@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,213 @@
1
+ const optionName = (token) => token.slice(2).split('=', 1)[0];
2
+
3
+ // A value may start with a single dash; one that is `--` or names a known flag is a missing value (use --flag=value).
4
+ const looksLikeFlag = (token, known) => token === '--' || (token.startsWith('--') && known.has(optionName(token)));
5
+
6
+ const optionValue = (argv, index, flag, known) => {
7
+ const token = argv[index];
8
+ const equals = token.indexOf('=');
9
+ if (equals >= 0) return { value: token.slice(equals + 1), consumed: 0 };
10
+ if (flag.type === 'boolean') return { value: true, consumed: 0 };
11
+ if (index + 1 >= argv.length || looksLikeFlag(argv[index + 1], known)) {
12
+ throw new Error(`--${flag.name} needs a value (use --${flag.name}=<value> for a value that looks like an option)`);
13
+ }
14
+ return { value: argv[index + 1], consumed: 1 };
15
+ };
16
+
17
+ const typedValue = (flag, raw) => {
18
+ if (flag.type === 'boolean') {
19
+ if (raw === true || raw === 'true') return true;
20
+ if (raw === 'false') return false;
21
+ throw new Error(`--${flag.name} expects true or false`);
22
+ }
23
+ if (flag.type === 'number') {
24
+ const value = String(raw).trim() === '' ? Number.NaN : Number(raw);
25
+ if (!Number.isFinite(value)) throw new Error(`--${flag.name} expects a number`);
26
+ return value;
27
+ }
28
+ if (flag.type === 'enum' && !flag.enum?.includes(raw)) {
29
+ throw new Error(`--${flag.name} expects one of: ${(flag.enum ?? []).join(', ')}`);
30
+ }
31
+ return raw;
32
+ };
33
+
34
+ /** The values a list flag takes after its first one when the verb has no positional slot: every following token that is no option (`--knowledge a b c`), as the contracts write it. */
35
+ const listTail = (argv, index, flag, verb) => {
36
+ if (flag.type !== 'list' || (verb.positional ?? []).length > 0) return [];
37
+ const tail = [];
38
+ while (index + 2 + tail.length < argv.length && !argv[index + 2 + tail.length].startsWith('-')) tail.push(argv[index + 2 + tail.length]);
39
+ return tail;
40
+ };
41
+
42
+ /** One `--name[=value]` token: type-check it into values/global, or pass it through to localArgs. */
43
+ const applyOption = (argv, index, token, { known, internal, values, global, localArgs, verb }) => {
44
+ const name = optionName(token);
45
+ const flag = known.get(name);
46
+ if (!flag) throw new Error(internal.has(name) ? `unknown option --${name}: it is a call of the runtime itself, not a CLI option; ${internal.get(name)}` : `unknown option --${name}`);
47
+ const { value: raw, consumed } = optionValue(argv, index, flag, known);
48
+ const value = typedValue(flag, raw);
49
+ const tail = consumed ? listTail(argv, index, flag, verb) : [];
50
+ if (flag.type === 'list') {
51
+ values[name] = [...(values[name] ?? []), value, ...tail];
52
+ } else if (Object.hasOwn(values, name)) {
53
+ throw new Error(`--${name} may be given only once`);
54
+ } else {
55
+ values[name] = value;
56
+ }
57
+ if (flag.global) { global[name] = values[name]; }
58
+ else {
59
+ localArgs.push(token);
60
+ if (consumed) localArgs.push(argv[index + consumed]);
61
+ // each further value of a list flag reaches the handler as a repeated option, the form every list-flag handler parses
62
+ for (const extra of tail) localArgs.push(token, extra);
63
+ }
64
+ return consumed + tail.length;
65
+ };
66
+
67
+ /** Every required local option must have been given. */
68
+ const requiredCheck = (locals, values) => {
69
+ for (const flag of locals.values()) {
70
+ if (flag.required && !Object.hasOwn(values, flag.name)) throw new Error(`missing required option --${flag.name}`);
71
+ }
72
+ };
73
+
74
+ /** The values one positional slot holds: a variadic slot's list, a single value, or none. */
75
+ const candidatesOf = (supplied) => {
76
+ if (Array.isArray(supplied)) return supplied;
77
+ return supplied === undefined ? [] : [supplied];
78
+ };
79
+
80
+ /** A slot with an enum accepts only its members. */
81
+ const checkEnum = (schema, candidates) => {
82
+ for (const candidate of candidates) {
83
+ if (schema.enum && !schema.enum.includes(candidate)) throw new Error(`${schema.name} expects one of: ${schema.enum.join(', ')}`);
84
+ }
85
+ };
86
+
87
+ /** Positional arity and enum checks; passthrough tokens fill free slots first. */
88
+ const checkPositionals = (verb, positionals, passthrough) => {
89
+ const positionalSchema = verb.positional ?? [];
90
+ const variadicAt = positionalSchema.findIndex((entry) => entry.variadic === true);
91
+ const maximum = variadicAt >= 0 ? Infinity : positionalSchema.length;
92
+ if (positionals.length > maximum) throw new Error(`too many positional arguments for ${verb.verb ?? 'command'}`);
93
+ positionals.push(...passthrough.slice(0, Math.max(0, maximum - positionals.length)));
94
+ for (const [index, schema] of positionalSchema.entries()) {
95
+ const supplied = variadicAt === index ? positionals.slice(index) : positionals[index];
96
+ const empty = Array.isArray(supplied) ? supplied.length === 0 : supplied === undefined;
97
+ if (schema.required && empty) throw new Error(`missing required positional ${schema.name}`);
98
+ checkEnum(schema, candidatesOf(supplied));
99
+ }
100
+ };
101
+
102
+ /** Edition availability, --cwd emptiness and --json support. */
103
+ const checkGlobals = (verb, global) => {
104
+ if (global.edition !== undefined && Array.isArray(verb.editions) && !verb.editions.includes(global.edition)) {
105
+ throw new Error(`${verb.group ?? 'this command'} ${verb.verb ?? ''}`.trim() + ` is not available in the ${global.edition} edition (editions: ${verb.editions.join(', ')})`);
106
+ }
107
+ if (global.cwd === '') throw new Error('--cwd needs a value');
108
+ if (global.json === true && verb.json === 'none') {
109
+ throw new Error(`${verb.group ?? 'this command'} ${verb.verb ?? ''}`.trim() + ' has no machine output');
110
+ }
111
+ };
112
+
113
+ /** args = supplied locals, else their declared defaults (a list flag's scalar default wraps in an array). */
114
+ const defaultArgs = (locals, values) => {
115
+ const args = {};
116
+ for (const flag of locals.values()) {
117
+ if (Object.hasOwn(values, flag.name)) args[flag.name] = values[flag.name];
118
+ else if (flag.default !== undefined) args[flag.name] = flag.type === 'list' && !Array.isArray(flag.default) ? [flag.default] : flag.default;
119
+ }
120
+ return args;
121
+ };
122
+
123
+ /**
124
+ * Validate the arguments after a catalog group and verb.
125
+ *
126
+ * Global options are returned separately so dispatchers can implement them once;
127
+ * localArgs contains only the verb's positionals and local options, in input order.
128
+ * Everything after a bare `--` is passed through unchanged: it is never parsed as an option and
129
+ * only fills positional slots the verb declares.
130
+ */
131
+ export function validateArgs(argv, verb, globalFlags = []) {
132
+ const globals = new Map(globalFlags.map((flag) => [flag.name, { ...flag, global: true }]));
133
+ const locals = new Map((verb.flags ?? []).map((flag) => [flag.name, flag]));
134
+ const known = new Map([...globals, ...locals]);
135
+ // Flags the runtime's own scripts take and the CLI refuses, each with the pointer a person is given instead.
136
+ const internal = new Map((verb.internalFlags ?? []).map((entry) => [entry.name, entry.pointer]));
137
+ const values = {};
138
+ const global = {};
139
+ const localArgs = [];
140
+ const positionals = [];
141
+ const passthrough = [];
142
+ let positionalOnly = false;
143
+
144
+ try {
145
+ for (let index = 0; index < argv.length; index += 1) {
146
+ const token = argv[index];
147
+ if (positionalOnly) {
148
+ passthrough.push(token);
149
+ localArgs.push(token);
150
+ continue;
151
+ }
152
+ if (token === '--') {
153
+ positionalOnly = true;
154
+ localArgs.push(token);
155
+ continue;
156
+ }
157
+ if (token.startsWith('--')) {
158
+ index += applyOption(argv, index, token, { known, internal, values, global, localArgs, verb });
159
+ continue;
160
+ }
161
+ if (token.startsWith('-')) throw new Error(`unknown option ${token}`);
162
+ positionals.push(token);
163
+ localArgs.push(token);
164
+ }
165
+
166
+ requiredCheck(locals, values);
167
+ checkPositionals(verb, positionals, passthrough);
168
+ checkGlobals(verb, global);
169
+ return { ok: true, values, args: defaultArgs(locals, values), global, localArgs, positionals };
170
+ } catch (error) {
171
+ return { ok: false, code: 2, error: String(error?.message ?? error) };
172
+ }
173
+ }
174
+
175
+ /** Insert dispatcher-owned flags before a pass-through `--`, where handlers still parse them as options. */
176
+ export function withFlagsBeforeDashes(args, extra) {
177
+ const at = args.indexOf('--');
178
+ return at < 0 ? [...args, ...extra] : [...args.slice(0, at), ...extra, ...args.slice(at)];
179
+ }
180
+
181
+ const isHelpToken = (token) => token === '--help' || token === '-h' || token === '--help=true';
182
+
183
+ /**
184
+ * Find the group and verb while allowing catalog global options before them.
185
+ * `help` is true when --help or -h appears before any bare `--`; everything after `--` is pass-through.
186
+ */
187
+ export function splitCommand(argv, globalFlags = []) {
188
+ const globals = new Map(globalFlags.map((flag) => [flag.name, flag]));
189
+ const command = [];
190
+ const args = [];
191
+ let help = false;
192
+ for (let index = 0; index < argv.length; index += 1) {
193
+ const token = argv[index];
194
+ if (token === '--') {
195
+ args.push(...argv.slice(index));
196
+ break;
197
+ }
198
+ if (isHelpToken(token)) {
199
+ help = true;
200
+ continue;
201
+ }
202
+ if (token.startsWith('--')) {
203
+ const flag = globals.get(optionName(token));
204
+ args.push(token);
205
+ const next = argv[index + 1];
206
+ if (flag && flag.type !== 'boolean' && !token.includes('=') && next !== undefined && !looksLikeFlag(next, globals)) args.push(argv[++index]);
207
+ } else if (token.startsWith('-') && token.length > 1) {
208
+ args.push(token);
209
+ } else if (command.length < 2) command.push(token);
210
+ else args.push(token);
211
+ }
212
+ return { group: command[0] ?? null, verb: command[1] ?? null, args, help };
213
+ }
@@ -0,0 +1,5 @@
1
+ // add.mjs — `git add <args>`: paths staged into the index.
2
+ import { runGit } from './lib.mjs';
3
+
4
+ /** The spawn result {status, stdout, stderr, error} of `git add ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
5
+ export const add = (args = [], options = {}) => runGit(['add', ...args], options);
@@ -0,0 +1,5 @@
1
+ // cat-file.mjs — `git cat-file <args>`: the type, size or content of repository objects (-e, -p, --batch).
2
+ import { runGit } from './lib.mjs';
3
+
4
+ /** The spawn result {status, stdout, stderr, error} of `git cat-file ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
5
+ export const catFile = (args = [], options = {}) => runGit(['cat-file', ...args], options);
@@ -0,0 +1,7 @@
1
+ // init.mjs — `git -c init.defaultBranch=<branch> init --quiet`: create an empty repository in `cwd` (the install sandbox's app repository).
2
+ import { gitRunner } from './lib.mjs';
3
+
4
+ /** {ok, stdout, stderr}. `env` replaces the process env (a sandbox's own home); git: the caller's runner or null. */
5
+ export function init(cwd, { branch = 'main', env = undefined, git = null } = {}) {
6
+ return gitRunner(git)(['-c', `init.defaultBranch=${branch}`, 'init', '--quiet'], env ? { cwd, env } : { cwd });
7
+ }
@@ -75,3 +75,6 @@ export const gitExecutable = (options = null) => {
75
75
  if (!options) cachedGit = found;
76
76
  return found;
77
77
  };
78
+
79
+ /** The output ceiling of the whole-diff and `cat-file --batch` calls `test affected` batches (a release diff is tens of megabytes). */
80
+ export const BATCH_MAX_BUFFER = 1024 * 1024 * 1024;
@@ -0,0 +1,5 @@
1
+ // tag.mjs — `git tag <args>`: list or create tags (--points-at <rev>, -l <pattern>, -a <name> -m <message>).
2
+ import { runGit } from './lib.mjs';
3
+
4
+ /** The spawn result {status, stdout, stderr, error} of `git tag ...args`; options are runGit's (cwd or dir, config, timeout, maxBuffer, input, encoding, env, git). */
5
+ export const tag = (args = [], options = {}) => runGit(['tag', ...args], options);
@@ -0,0 +1,20 @@
1
+ // scripts/api/node/lib.mjs — the runner of the runtime's own scripts as child processes: the node binary that runs this
2
+ // process (process.execPath), a hidden window, utf8 text. The call files beside it (run-script.mjs, syntax-check.mjs,
3
+ // run-node.mjs, spawn-node.mjs, exec-node.mjs) each name one use; nothing outside scripts/api/node imports this runner.
4
+ import { assertMutationFence } from '../../lib/mutation-fence.mjs';
5
+ import { execFile, spawn, spawnSync } from 'node:child_process';
6
+ import { withTempEnv } from '../../../engine/temp-root.mjs';
7
+ import { withoutTestRunner } from '../../lib/env.mjs';
8
+
9
+ /** Whether `node <args>` starts the test runner (`--test`, `--test=...` before any script): its env must not carry an enclosing run's mark. THE one seam of every runtime child that runs specs. */
10
+ const startsTestRunner = (args) => args.some((arg) => arg === '--test' || String(arg).startsWith('--test='));
11
+ const forChild = (args, options) => (startsTestRunner(args) ? { ...options, env: withoutTestRunner(options?.env ?? process.env) } : options);
12
+
13
+ /** `node <args>` with this process's node binary, waited for; options pass through last (cwd, stdio, env, timeout, maxBuffer, input). */
14
+ export const nodeSpawn = (args, options = {}) => { assertMutationFence({ kind: 'node-effect', args }); return spawnSync(process.execPath, args, withTempEnv({ encoding: 'utf8', windowsHide: true, ...forChild(args, options) })); };
15
+
16
+ /** `node <args>` with this process's node binary, started and not waited for: the ChildProcess (detached, stdio, env pass through). */
17
+ export const nodeStart = (args, options = {}) => { assertMutationFence({ kind: 'node-effect', args }); return spawn(process.execPath, args, withTempEnv({ windowsHide: true, ...forChild(args, options) })); };
18
+
19
+ /** `node <args>` with this process's node binary, waited for without blocking the thread: callback(error, stdout, stderr). */
20
+ export const nodeExecFile = (args, options, callback) => { assertMutationFence({ kind: 'node-effect', args }); return execFile(process.execPath, args, withTempEnv({ encoding: 'utf8', windowsHide: true, ...forChild(args, options) }), callback); };
@@ -0,0 +1,6 @@
1
+ // run-node.mjs — `node <args>` with this process's node binary, waited for: a runtime script (or `-e`/`--test`) as a child
2
+ // whose output the caller reads. Returns the spawnSync result {status, stdout, stderr, error, signal} (utf8 text).
3
+ import { nodeSpawn } from './lib.mjs';
4
+
5
+ /** Runs `node <args>`; options (cwd, env, timeout, input, maxBuffer, stdio) pass through. */
6
+ export const runNode = (args, options = {}) => nodeSpawn(args, options);
@@ -212,6 +212,47 @@ const toolProfileSupported = (version, ageVersion) => captured(version) && captu
212
212
 
213
213
  const invalidCaptureBudget = (deadline, maxBuffer) => !Number.isInteger(deadline) || deadline < 1 || deadline > 2_147_483_647 || !Number.isInteger(maxBuffer) || maxBuffer < 1;
214
214
 
215
+ const publicAgeFailure = reason => ({ ok: false, reason });
216
+ const publicAgeRequestValid = ({ env, cwd, invocation }) => env && typeof env === 'object' && !Array.isArray(env)
217
+ && path.isAbsolute(cwd ?? '') && typeof invocation?.runProgram === 'function'
218
+ && typeof invocation?.resolveRealTool === 'function';
219
+ const publicAgeCapture = result => captured(result) && result.stdout.length <= CREDENTIAL_FILE_MAX_BYTES
220
+ && result.stderr.length <= CREDENTIAL_FILE_MAX_BYTES;
221
+
222
+ const deriveSelectedAgeRecipient = (state, { env, cwd, invocation }, selection) => {
223
+ const key = env[selection.inlineName];
224
+ if (typeof key !== 'string' || !/^AGE-SECRET-KEY-1[0-9A-Z]+$/.test(key.trim())) return publicAgeFailure('invalid-inline');
225
+ const age = invocation.resolveRealTool('age-keygen', { env });
226
+ if (!nativeFile(age)) return publicAgeFailure('native-tool-unavailable');
227
+ const publicEnv = isolatedSopsEnv(env, null);
228
+ const options = { cwd, shell: false, encoding: 'buffer', windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'],
229
+ timeout: IDENTITY_TIMEOUT_MS, maxBuffer: CREDENTIAL_FILE_MAX_BYTES };
230
+ const run = selectedRunner(invocation, options, state.captures, publicEnv);
231
+ const version = run(age, ['--version']);
232
+ if (!publicAgeCapture(version) || !SELECTED_AGE_VERSIONS.has(/^v?(\d+\.\d+\.\d+)/.exec(decoded(version.stdout))?.[1])) return publicAgeFailure('unsupported-tool-profile');
233
+ state.identity = Buffer.from(key.trim(), 'utf8');
234
+ const derived = run(age, ['-y'], state.identity);
235
+ if (!publicAgeCapture(derived)) return publicAgeFailure('recipient-unproven');
236
+ const recipient = recipientOf(derived.stdout);
237
+ if (recipient === null) return publicAgeFailure('recipient-unproven');
238
+ return { ok: true, publicRecipient: recipient };
239
+ };
240
+
241
+ /** Derive only the public recipient of an explicitly selected inline identity; no private capture escapes. */
242
+ export function selectedAgeRecipient(options = {}) {
243
+ if (!publicAgeRequestValid(options)) return publicAgeFailure('invalid-request');
244
+ const selection = sopsIdentityEnv(options.env, { required: true });
245
+ if (selection.error?.identityRefusal !== 'inline-context-unqualified') return publicAgeFailure(selection.error?.identityRefusal ?? 'selected-inline-required');
246
+ const state = { identity: undefined, captures: [] };
247
+ try {
248
+ return deriveSelectedAgeRecipient(state, options, selection);
249
+ } catch { return publicAgeFailure('selected-native-refused'); }
250
+ finally {
251
+ state.identity?.fill(0);
252
+ wipeCaptures(state.captures);
253
+ }
254
+ }
255
+
215
256
  // The document bytes into `state.input`; the refusal when they are unavailable or over budget, else null.
216
257
  const readSelectedInput = (state, request, spec, file) => {
217
258
  try { state.input = request.operation === 'seal' ? Buffer.from(spec.plaintext) : readSecretBytes(file); } catch { return held('actual-document-unavailable'); }
@@ -0,0 +1,7 @@
1
+ // resolve-sops.mjs — find the sops executable: PATH, and on Windows the WinGet links and packages.
2
+ import { resolveSops as resolve } from './lib.mjs';
3
+
4
+ /** The absolute sops path, or null. `options`: platform, pathext (PATHEXT names), wingetPackageTree (the full package tree), filesystem (a probe). */
5
+ export function resolveSops(env = process.env, options = {}) {
6
+ return resolve(env, options);
7
+ }
@@ -1,4 +1,4 @@
1
- // seal.mjs - one `sops encrypt` of a plaintext document held in memory: the canon command `starci app secret set` and `starci app secret gen`
1
+ // seal.mjs - one `sops encrypt` of a plaintext document held in memory: `starci app secret set` (including --generate)
2
2
  // (scripts/hfs/secret.mjs). The plaintext travels on the child's stdin and never on its argument list, so it is not visible in a
3
3
  // process listing; the ciphertext comes back on stdout.
4
4
  import { spawnSync } from 'node:child_process';
@@ -0,0 +1,7 @@
1
+ // selected-age-recipient.mjs - public recipient derivation for app secret initialization.
2
+ import { selectedAgeRecipient as selectedRecipient } from './lib.mjs';
3
+
4
+ /** Return a public recipient or a typed refusal; selected inline identity bytes never leave the owner. */
5
+ export function selectedAgeRecipient(options = {}) {
6
+ return selectedRecipient(options);
7
+ }
@@ -0,0 +1,5 @@
1
+ // database-dump.mjs - schema dump for an app's local or linked Supabase database.
2
+ import { supabaseCall } from './lib.mjs';
3
+
4
+ /** The spawn result of supabase db dump; arguments and runner options remain with the caller. */
5
+ export function databaseDump(...args) { return supabaseCall(['db', 'dump'], ...args); }
@@ -0,0 +1,5 @@
1
+ // database-query.mjs - database query for an app's local or linked Supabase database.
2
+ import { supabaseCall } from './lib.mjs';
3
+
4
+ /** The spawn result of supabase db query; arguments and runner options remain with the caller. */
5
+ export function databaseQuery(...args) { return supabaseCall(['db', 'query'], ...args); }
@@ -0,0 +1,44 @@
1
+ // lib.mjs - resolve and spawn the app-local Supabase CLI for this API system, with no shell.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ const candidateNames = (platform) => platform === 'win32'
7
+ ? ['supabase.exe', 'supabase', 'supabase.cmd']
8
+ : ['supabase'];
9
+
10
+ /** Resolve the app-local Supabase CLI first, then PATH, retaining a PATH lookup fallback for useful spawn errors. */
11
+ function resolveSupabase(appRoot, { env = process.env, platform = process.platform, exists = fs.existsSync } = {}) {
12
+ const names = candidateNames(platform);
13
+ const local = path.join(appRoot, 'node_modules', '.bin');
14
+ for (const name of names) {
15
+ const file = path.join(local, name);
16
+ if (exists(file)) return file;
17
+ }
18
+ const delimiter = platform === 'win32' ? ';' : ':';
19
+ for (const directory of String(env.PATH ?? '').split(delimiter).filter(Boolean)) {
20
+ for (const name of names) {
21
+ const file = path.join(directory, name);
22
+ if (exists(file)) return file;
23
+ }
24
+ }
25
+ return 'supabase';
26
+ }
27
+
28
+ /** Run one Supabase call for an app root; options retain the injectable spawn seam used by focused specs. */
29
+ export const supabaseSpawn = (appRoot, args, {
30
+ env = process.env,
31
+ supabase,
32
+ timeout = 60_000,
33
+ run = spawnSync,
34
+ } = {}) => run(supabase ?? resolveSupabase(appRoot, { env }), args, {
35
+ cwd: appRoot,
36
+ encoding: 'utf8',
37
+ env,
38
+ timeout,
39
+ windowsHide: true,
40
+ });
41
+
42
+ /** Invoke one Supabase command prefix with caller arguments and runner options. */
43
+ export const supabaseCall = (prefix, appRoot, args = [], options = {}) =>
44
+ supabaseSpawn(appRoot, [...prefix, ...args], options);
@@ -0,0 +1,5 @@
1
+ // migration-repair.mjs - migration history repair for an app's local or linked Supabase database.
2
+ import { supabaseCall } from './lib.mjs';
3
+
4
+ /** The spawn result of supabase migration repair; arguments and runner options remain with the caller. */
5
+ export function migrationRepair(...args) { return supabaseCall(['migration', 'repair'], ...args); }
@@ -0,0 +1,201 @@
1
+ // R272: compile the actual Nest AppModule in a child, with explicit infrastructure doubles.
2
+ // Dynamic roots need public fixture options: src/tests/fixtures/builders/<app>.builder.ts may export
3
+ // buildWiringProof(AppModule) => { options, overrides: [{ token, value }], request? }. The runner always chooses
4
+ // the real root; a fixture cannot substitute an empty module. No environment parser or main.ts runs.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { createRequire } from 'node:module';
8
+ import { runNode } from '../api/node/run-node.mjs';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { invocationDir } from '../lib/roots.mjs';
11
+ import { isMain } from '../lib/is-main.mjs';
12
+ import { readJson } from './rules/read.mjs';
13
+
14
+ export const BOOT_PROOF_CODE = 'BE_APP_BOOT_PROOF_MISSING';
15
+ const WORKER = fileURLToPath(import.meta.url);
16
+ const DEFAULT_TIMEOUT = 15000;
17
+ const DI_FAILURES = new Map([
18
+ ['UnknownDependenciesException', 'unresolved DI provider'],
19
+ ['CircularDependencyException', 'circular DI dependency'],
20
+ ['UndefinedModuleException', 'undefined or circular module import'],
21
+ ['WiringTimeout', 'DI compilation timed out'],
22
+ ]);
23
+ const COMPILATION_FAILURE = 'module or proof could not be compiled';
24
+ const WORKER_FAILURE = 'proof worker failed or timed out';
25
+ const SAFE_REASONS = new Set([...DI_FAILURES.values(), COMPILATION_FAILURE]);
26
+ const modulePath = app => `apps/${app}/src/app.module.ts`;
27
+ export const wiringFixturePath = app => `src/tests/fixtures/builders/${app}.builder.ts`;
28
+
29
+ const refusal = (file, reason) => ({ code: BOOT_PROOF_CODE, level: 'error', path: file,
30
+ message: `${file}: lite boot proof failed (${reason}); compile the real AppModule with explicit fixture options and infrastructure doubles using starci app check --wiring`, reason });
31
+
32
+ /** Stable diagnostics never forward exception text, source values or child output. */
33
+ function failureReason(error) {
34
+ // Nest exceptions inherit Error.name; their constructor identifies the refusal.
35
+ return DI_FAILURES.get(error?.constructor?.name) ?? DI_FAILURES.get(error?.name) ?? COMPILATION_FAILURE;
36
+ }
37
+
38
+ /** Nest bootstrap defers request/transient trees: prove them in their original module's scope too. */
39
+ async function compileGraph(builder, Nest, request) {
40
+ const compiled = await builder.compile();
41
+ const { ContextIdFactory, ModuleRef } = Nest;
42
+ const contextId = ContextIdFactory.create();
43
+ compiled.registerRequestByContextId(request, contextId);
44
+ const resolutions = [];
45
+ // Read the actual compiled container, not an application provider claiming to be ModulesContainer.
46
+ for (const module of compiled.container.getModules().values()) {
47
+ // Native REQUEST/INQUIRER factories belong to Nest itself; their empty result is intentional.
48
+ if (module === compiled.container.getInternalCoreModuleRef()) continue;
49
+ const moduleRef = module.providers.get(ModuleRef)?.instance;
50
+ for (const wrappers of [module.providers, module.controllers, module.injectables]) {
51
+ for (const wrapper of wrappers.values()) {
52
+ if (wrapper.isTransient || !wrapper.isDependencyTreeStatic()) {
53
+ resolutions.push(() => moduleRef.resolve(wrapper.token, contextId, { strict: true }));
54
+ }
55
+ }
56
+ }
57
+ }
58
+ await Promise.all(resolutions.map(resolve => resolve()));
59
+ }
60
+
61
+ /** Compile a real root, with overrides of existing tokens only; never auto-mock an unresolved dependency. */
62
+ async function compileAppModule({ AppModule, options, overrides = [], request = {}, Test, Nest, timeout = DEFAULT_TIMEOUT }) {
63
+ if (typeof AppModule !== 'function') throw new Error('the actual AppModule is missing');
64
+ if (typeof AppModule.register === 'function' && options === undefined) throw new Error('dynamic AppModule needs fixture options');
65
+ const root = typeof AppModule.register === 'function' ? await AppModule.register(options) : AppModule;
66
+ if (root?.module && root.module !== AppModule) throw new Error('registration must return the actual AppModule');
67
+ const builder = Test.createTestingModule({ imports: [root] });
68
+ for (const override of overrides) {
69
+ if (!override || !Object.hasOwn(override, 'token') || !Object.hasOwn(override, 'value')) throw new Error('an infrastructure double needs a token and value');
70
+ builder.overrideProvider(override.token).useValue(override.value);
71
+ }
72
+ let deadline;
73
+ const expired = new Promise((_, reject) => { deadline = setTimeout(() => {
74
+ const error = new Error('compilation deadline'); error.name = 'WiringTimeout'; reject(error);
75
+ }, timeout); });
76
+ try {
77
+ // compile proves DI. Neither init nor close runs: both invoke application lifecycle hooks.
78
+ // The isolated worker exits naturally once every infrastructure resource has a double.
79
+ await Promise.race([compileGraph(builder, Nest, request), expired]);
80
+ } finally { clearTimeout(deadline); }
81
+ }
82
+
83
+ /** The strict in-memory TS loader uses this app's installed compiler and Nest, including inherited aliases. */
84
+ function sourceLoader(repoRoot, entry, fixture) {
85
+ const require = createRequire(path.join(repoRoot, 'package.json'));
86
+ const ts = require('typescript');
87
+ const config = ts.getParsedCommandLineOfConfigFile(path.join(repoRoot, 'tsconfig.json'), {},
88
+ { ...ts.sys, onUnRecoverableConfigFileDiagnostic: () => { throw new Error('invalid TypeScript project'); } });
89
+ if (!config || config.errors.length) throw new Error('invalid TypeScript project');
90
+ const options = { ...config.options, module: ts.ModuleKind.CommonJS, moduleResolution: ts.ModuleResolutionKind.Node10,
91
+ experimentalDecorators: true, emitDecoratorMetadata: true, noEmit: false, noEmitOnError: false,
92
+ declaration: false, declarationMap: false, sourceMap: false, inlineSourceMap: false,
93
+ incremental: false, composite: false, tsBuildInfoFile: undefined, outDir: undefined, rootDir: undefined };
94
+ const program = ts.createProgram({ rootNames: [entry, ...(fixture ? [fixture] : [])], options });
95
+ if (program.getSyntacticDiagnostics().length) throw new Error('invalid TypeScript source');
96
+ const outputs = new Map();
97
+ program.emit(undefined, (file, text, _bom, _error, sources) => {
98
+ if (file.endsWith('.js')) for (const source of sources ?? []) outputs.set(path.resolve(source.fileName), text);
99
+ });
100
+ const NodeModule = require('node:module');
101
+ const resolve = NodeModule._resolveFilename;
102
+ NodeModule._resolveFilename = function resolveSource(request, parent, ...rest) {
103
+ if (parent?.filename && (request.startsWith('.') || Object.keys(options.paths ?? {}).some(alias => request.startsWith(alias.replace(/\*$/, ''))))) {
104
+ const resolved = ts.resolveModuleName(request, parent.filename, options, ts.sys).resolvedModule;
105
+ if (resolved && !resolved.isExternalLibraryImport && !resolved.resolvedFileName.endsWith('.d.ts')) return resolved.resolvedFileName;
106
+ }
107
+ return resolve.call(this, request, parent, ...rest);
108
+ };
109
+ require.extensions['.ts'] = (loaded, file) => {
110
+ const output = outputs.get(path.resolve(file));
111
+ if (output === undefined) throw new Error('source is outside the compiled graph');
112
+ loaded._compile(output, file);
113
+ };
114
+ require('reflect-metadata');
115
+ return require;
116
+ }
117
+
118
+ async function wiringWorker({ repoRoot, app, fixture, timeout }) {
119
+ // Disable network entry points before loading application source. A forgotten resource double is a refusal.
120
+ const systemRequire = createRequire(import.meta.url);
121
+ for (const [packageName, methods] of [['node:net', ['connect', 'createConnection']], ['node:tls', ['connect']],
122
+ ['node:http', ['request', 'get']], ['node:https', ['request', 'get']]]) {
123
+ const api = systemRequire(packageName);
124
+ for (const method of methods) api[method] = () => { throw new Error('wiring needs an infrastructure double'); };
125
+ }
126
+ systemRequire('node:net').Socket.prototype.connect = () => { throw new Error('wiring needs an infrastructure double'); };
127
+ systemRequire('node:net').Server.prototype.listen = () => { throw new Error('wiring needs an infrastructure double'); };
128
+ globalThis.fetch = () => { throw new Error('wiring needs an infrastructure double'); };
129
+ const entry = path.join(repoRoot, modulePath(app));
130
+ const fixtureFile = fixture ? path.join(repoRoot, fixture) : null;
131
+ const require = sourceLoader(repoRoot, entry, fixtureFile);
132
+ const { AppModule } = require(entry);
133
+ const proof = fixtureFile ? await require(fixtureFile).buildWiringProof(AppModule) : {};
134
+ if (!proof || typeof proof !== 'object') throw new Error('invalid wiring fixture');
135
+ await compileAppModule({ AppModule, options: proof.options, overrides: proof.overrides, request: proof.request,
136
+ Test: require('@nestjs/testing').Test, Nest: require('@nestjs/core'), timeout });
137
+ }
138
+
139
+ function wiringApps(repo, files, prefix) {
140
+ const declared = (repo.sides?.be?.apps ?? repo.apps ?? []).map(app => app.name);
141
+ const observed = (files ?? []).flatMap(file => {
142
+ const match = file.match(new RegExp(String.raw`^${prefix}apps/([^/]+)/src/app\.module\.ts$`));
143
+ return match ? [match[1]] : [];
144
+ });
145
+ return [...new Set([...declared, ...observed])];
146
+ }
147
+
148
+ function appWiringFinding({ repoRoot, beRoot, prefix, app, files, fixtures, timeout, run }) {
149
+ const file = `${prefix}${modulePath(app)}`;
150
+ if (!/^[a-z][a-z0-9-]*$/.test(app) || !fs.existsSync(path.join(repoRoot, file)) || (files && !files.includes(file))) {
151
+ return refusal(file, 'app module is missing');
152
+ }
153
+ const fixture = fixtures[app] ?? wiringFixturePath(app);
154
+ const fixtureExists = fs.existsSync(path.join(beRoot, fixture)) && (!files || files.includes(`${prefix}${fixture}`));
155
+ const args = [WORKER, '--worker', JSON.stringify({ repoRoot: beRoot, app, fixture: fixtureExists ? fixture : null, timeout })];
156
+ const options = { cwd: beRoot, encoding: 'utf8', env: {}, windowsHide: true, timeout: Math.max(60000, timeout * 4) + 2000, maxBuffer: 1024 * 1024 };
157
+ const result = run ? run(process.execPath, args, options) : runNode(args, options);
158
+ let proof;
159
+ try { proof = JSON.parse(result.stdout ?? ''); } catch { /* Output from app source cannot serve as a proof. */ }
160
+ if (result.status !== 0 || proof?.ok !== true) return refusal(file, SAFE_REASONS.has(proof?.reason) ? proof.reason : WORKER_FAILURE);
161
+ return null;
162
+ }
163
+
164
+ /** Synchronous check seam for the dispatcher and content finder. Paths in findings are relative to repoRoot. */
165
+ export function appWiring({ repoRoot, repo = readJson(repoRoot, 'hfs.json'), files, fixtures = {}, timeout = DEFAULT_TIMEOUT, run } = {}) {
166
+ if (repo?.edition !== 'lite' || repo?.profile === 'fe' || repo?.kind === 'fe') return { ok: true, status: 'skipped', findings: [], apps: [] };
167
+ const appRoot = Boolean(repo.sides);
168
+ const beRoot = appRoot ? path.join(repoRoot, 'be') : repoRoot;
169
+ const prefix = appRoot ? 'be/' : '';
170
+ const apps = wiringApps(repo, files, prefix);
171
+ const findings = [];
172
+ for (const app of apps) {
173
+ const finding = appWiringFinding({ repoRoot, beRoot, prefix, app, files, fixtures, timeout, run });
174
+ if (finding) findings.push(finding);
175
+ }
176
+ return { ok: findings.length === 0, status: 'checked', apps, findings };
177
+ }
178
+
179
+ /** Optional standalone entry; coordinator connects the same seam to app check --wiring. */
180
+ export function appWiringMain(argv, { cwd = invocationDir(), stdout = text => process.stdout.write(text), stderr = text => process.stderr.write(text) } = {}) {
181
+ let directory = cwd, json = false;
182
+ for (let at = 0; at < argv.length; at += 1) {
183
+ if (argv[at] === '--cwd' && argv[at + 1]) directory = path.resolve(cwd, argv[++at]);
184
+ else if (argv[at] === '--json') json = true;
185
+ else if (argv[at] !== '--wiring') { stderr('usage: starci app check --wiring [--cwd <dir>] [--json]\n'); return 2; }
186
+ }
187
+ const repo = readJson(directory, 'hfs.json');
188
+ if (!repo) { stderr('wiring needs a readable hfs.json\n'); return 2; }
189
+ const result = appWiring({ repoRoot: directory, repo });
190
+ const trailingNewline = result.findings.length ? '\n' : '';
191
+ const report = `${result.status}: ${result.apps.length} app(s), ${result.findings.length} boot proof failure(s)\n${result.findings.map(f => f.message).join('\n')}${trailingNewline}`;
192
+ stdout(json ? `${JSON.stringify(result)}\n` : report);
193
+ return result.ok ? 0 : 1;
194
+ }
195
+
196
+ if (isMain(import.meta.url)) {
197
+ if (process.argv[2] === '--worker') {
198
+ try { await wiringWorker(JSON.parse(process.argv[3])); process.stdout.write(JSON.stringify({ ok: true })); }
199
+ catch (error) { process.stdout.write(JSON.stringify({ ok: false, reason: failureReason(error) })); }
200
+ } else process.exitCode = appWiringMain(process.argv.slice(2));
201
+ }