@starci/hfs 4.3.0 → 4.4.1

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 (538) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/db/migrate.mjs +361 -0
  4. package/db/post-cutover.mjs +136 -0
  5. package/emit/contracts.mjs +2 -2
  6. package/emit/db-types.mjs +12 -3
  7. package/lint/run.mjs +3 -3
  8. package/package.json +4 -1
  9. package/release/gate.mjs +298 -0
  10. package/release/index.mjs +103 -0
  11. package/release/push.mjs +110 -0
  12. package/report/sonar.mjs +1 -1
  13. package/runtime/config.example.yaml +7 -6
  14. package/runtime/engine/config.mjs +10 -18
  15. package/runtime/engine/removed-vocabulary.mjs +0 -2
  16. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  17. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  18. package/runtime/knowledge/hfs/rules.yaml +2855 -163
  19. package/runtime/knowledge/hfs/slots.yaml +333 -62
  20. package/runtime/knowledge/patterns/be/api.yaml +32 -5
  21. package/runtime/knowledge/patterns/be/cli.yaml +14 -4
  22. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  23. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  24. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  25. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  26. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  27. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  28. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  29. package/runtime/knowledge/patterns/be/webhooks.yaml +23 -4
  30. package/runtime/knowledge/patterns/fe/folder.yaml +1 -0
  31. package/runtime/knowledge/sonar-gate.yaml +1 -1
  32. package/runtime/modules/kernel/failure-codes.yaml +3091 -43
  33. package/runtime/modules/kernel/removed-vocabulary.yaml +30 -0
  34. package/runtime/modules/models/runtimes.yaml +5 -7
  35. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  36. package/runtime/packages/cli/src/catalog.generated.mjs +13769 -0
  37. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  38. package/runtime/scripts/api/git/add.mjs +5 -0
  39. package/runtime/scripts/api/git/init.mjs +7 -0
  40. package/runtime/scripts/api/git/lib.mjs +4 -1
  41. package/runtime/scripts/api/git/tag.mjs +5 -0
  42. package/runtime/scripts/api/node/lib.mjs +20 -0
  43. package/runtime/scripts/api/node/run-node.mjs +6 -0
  44. package/runtime/scripts/api/sops/lib.mjs +42 -1
  45. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  46. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  47. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  48. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  49. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  50. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  51. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +1 -8
  52. package/runtime/scripts/hfs/architecture/backend.mjs +2 -7
  53. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  54. package/runtime/scripts/hfs/architecture/config.mjs +1 -1
  55. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  56. package/runtime/scripts/hfs/architecture/dead-exports.mjs +9 -41
  57. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  58. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  59. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  60. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  61. package/runtime/scripts/hfs/architecture/index.mjs +1 -1
  62. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +28 -13
  63. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  64. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  65. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  66. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  67. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -2
  68. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  69. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +211 -11
  70. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -1
  71. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  72. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  73. package/runtime/scripts/hfs/architecture/tiers.mjs +4 -4
  74. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +8 -2
  75. package/runtime/scripts/hfs/architecture.mjs +1 -1
  76. package/runtime/scripts/hfs/check-results.mjs +28 -0
  77. package/runtime/scripts/hfs/check.mjs +46 -47
  78. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  79. package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
  80. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  81. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  82. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  83. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  84. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  85. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  86. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  87. package/runtime/scripts/hfs/manifest-shape.mjs +69 -9
  88. package/runtime/scripts/hfs/param-names.mjs +1 -1
  89. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  90. package/runtime/scripts/hfs/rule-catalog.mjs +158 -11
  91. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  92. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  93. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  94. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  95. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  96. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  97. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  98. package/runtime/scripts/hfs/rules/cli.mjs +1 -1
  99. package/runtime/scripts/hfs/rules/contract-compat.mjs +1 -1
  100. package/runtime/scripts/hfs/rules/contract.mjs +67 -6
  101. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  102. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  103. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  104. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  105. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  106. package/runtime/scripts/hfs/rules/database-migrations.mjs +286 -7
  107. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  108. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  109. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  110. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  111. package/runtime/scripts/hfs/rules/database.mjs +9 -0
  112. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  113. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  114. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  115. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  116. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  117. package/runtime/scripts/hfs/rules/edition.mjs +9 -8
  118. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  119. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  120. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  121. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  122. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  123. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  124. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  125. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  126. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  127. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  128. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  129. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  130. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  131. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  132. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  133. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  134. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  135. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  136. package/runtime/scripts/hfs/rules/secrets.mjs +13 -0
  137. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  138. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  139. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  140. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  141. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  142. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  143. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +41 -1
  144. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  145. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +2 -2
  146. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  147. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  148. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  149. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  150. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  151. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  152. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  153. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  154. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  155. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  156. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  157. package/runtime/scripts/hfs/secret-init.mjs +127 -19
  158. package/runtime/scripts/hfs/secret-sops.mjs +38 -6
  159. package/runtime/scripts/hfs/secret-store.mjs +56 -54
  160. package/runtime/scripts/hfs/secret.mjs +34 -5
  161. package/runtime/scripts/hfs/slot-imports.mjs +18 -5
  162. package/runtime/scripts/hfs/slots.mjs +3 -2
  163. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  164. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  165. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  166. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  167. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  168. package/runtime/scripts/hfs/typescript-programs.mjs +0 -8
  169. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  170. package/runtime/scripts/lib/compose-values.mjs +11 -0
  171. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  172. package/runtime/scripts/lib/env.mjs +2 -2
  173. package/runtime/scripts/lib/git.mjs +1 -1
  174. package/runtime/scripts/lib/i18n.mjs +0 -2
  175. package/runtime/scripts/lib/json-clone.mjs +61 -0
  176. package/runtime/scripts/lib/language.mjs +1 -1
  177. package/runtime/scripts/lib/list.mjs +4 -0
  178. package/runtime/scripts/lib/roots.mjs +2 -4
  179. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  180. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  181. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  182. package/runtime/scripts/lib/stack-services.mjs +1 -1
  183. package/runtime/scripts/lib/swarm-stack.mjs +5 -18
  184. package/runtime/scripts/lib/trim-end.mjs +8 -0
  185. package/runtime/scripts/lib/ts-analysis.mjs +5 -2
  186. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  187. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  188. package/scaffold/add-app.mjs +77 -59
  189. package/scaffold/add-cli-lite.mjs +14 -6
  190. package/scaffold/add-compose.mjs +79 -0
  191. package/scaffold/add-database-lite.mjs +14 -0
  192. package/scaffold/add-fe-overlay.mjs +1 -0
  193. package/scaffold/add-fe-page.mjs +1 -0
  194. package/scaffold/add-inbox-lite.mjs +74 -42
  195. package/scaffold/add-table.mjs +110 -87
  196. package/scaffold/add.mjs +159 -44
  197. package/scaffold/app.mjs +124 -39
  198. package/scaffold/edition-gate.mjs +3 -3
  199. package/scaffold/lite-exports.mjs +2 -1
  200. package/scaffold/service.mjs +2 -2
  201. package/scaffold/supabase-ports.mjs +1 -1
  202. package/src/main.mjs +229 -116
  203. package/stack/age-install.mjs +37 -0
  204. package/stack/backup.mjs +100 -0
  205. package/stack/build-args.mjs +42 -0
  206. package/stack/build.mjs +173 -0
  207. package/stack/cli.mjs +130 -0
  208. package/stack/config.mjs +57 -0
  209. package/stack/context.mjs +57 -0
  210. package/stack/converge.mjs +107 -0
  211. package/stack/deploy.mjs +301 -0
  212. package/stack/digest.mjs +16 -0
  213. package/stack/docker-transport.mjs +53 -0
  214. package/stack/edge-alias.mjs +118 -0
  215. package/stack/errors.mjs +21 -0
  216. package/stack/image-scan.mjs +152 -0
  217. package/stack/ingress-create.mjs +114 -0
  218. package/stack/ingress-publish.mjs +273 -0
  219. package/stack/ingress.mjs +706 -0
  220. package/stack/jobs.mjs +51 -0
  221. package/stack/live-restore.mjs +177 -0
  222. package/stack/prepare.mjs +266 -0
  223. package/stack/private-file.mjs +22 -0
  224. package/stack/provision.mjs +90 -0
  225. package/stack/prune.mjs +46 -0
  226. package/stack/release-store.mjs +87 -0
  227. package/stack/remote.mjs +205 -0
  228. package/stack/render.mjs +149 -0
  229. package/stack/retire.mjs +47 -0
  230. package/stack/sealed.mjs +149 -0
  231. package/stack/task.mjs +42 -0
  232. package/stack/verify.mjs +99 -0
  233. package/sync/ci-commands.mjs +13 -0
  234. package/sync/format.mjs +1 -1
  235. package/sync/hygiene.mjs +1 -1
  236. package/sync/index.mjs +110 -54
  237. package/sync/stack-images.mjs +127 -0
  238. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  239. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  240. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  241. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  242. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  243. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  244. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  245. package/templates/app/codegen-graphql.mjs.partial +13 -2
  246. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  247. package/templates/app/codegen-prelude.mjs.partial +8 -0
  248. package/templates/app/gitignore +3 -0
  249. package/templates/app/gitignore-lite +3 -0
  250. package/templates/app/package-scripts/package.json +1 -0
  251. package/templates/app/package-scripts/supabase.json +7 -0
  252. package/templates/app/package-scripts-lite/package.json +1 -7
  253. package/templates/app/skeleton/.editorconfig +1 -0
  254. package/templates/app/skeleton/.gitattributes +7 -1
  255. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  256. package/templates/app/skeleton/.nvmrc +1 -1
  257. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  258. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  259. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  260. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  261. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  262. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  263. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  264. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  265. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  266. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  267. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  268. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  269. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  270. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  271. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  272. package/templates/app/skeleton/README.md +6 -6
  273. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  274. package/templates/app/skeleton-lite/.editorconfig +16 -0
  275. package/templates/app/skeleton-lite/.gitattributes +8 -0
  276. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  277. package/templates/app/skeleton-lite/.nvmrc +1 -0
  278. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  279. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +178 -1
  280. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  281. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  282. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  283. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  284. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  285. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  286. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  287. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  288. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  289. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  290. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  291. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  292. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  293. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  294. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  295. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  296. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  297. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  298. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  299. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  300. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  301. package/templates/app/skeleton-lite/README.md +5 -3
  302. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  303. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  304. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  305. package/templates/be/image/worker/Dockerfile +1 -1
  306. package/templates/be/inbox/database-client.spec.ts.tpl +17 -18
  307. package/templates/be/inbox/database-client.ts.tpl +12 -11
  308. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  309. package/templates/be/inbox/event-index.ts.tpl +1 -0
  310. package/templates/be/inbox/events.json.tpl +10 -0
  311. package/templates/be/inbox/migration.sql.tpl +1 -0
  312. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  313. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  314. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  315. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  316. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  317. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  318. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  319. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  320. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  321. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  322. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  323. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  324. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  325. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  326. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  327. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  328. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  329. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  330. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  331. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  332. package/templates/be/inbox/service.spec.ts.tpl +42 -9
  333. package/templates/be/inbox/service.ts.tpl +23 -8
  334. package/templates/be/inbox/webhook-door.spec.ts.tpl +1 -1
  335. package/templates/be/inbox/webhook-door.ts.tpl +2 -2
  336. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  337. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  338. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  339. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  340. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  341. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  342. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  343. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  344. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  345. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  346. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  347. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  348. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  349. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  350. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  351. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  352. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  353. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  354. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  355. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  356. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  357. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  358. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  359. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  360. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  362. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  363. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  364. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  365. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  366. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  367. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  368. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  369. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  370. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  371. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  372. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  373. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  374. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  375. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  376. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  377. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  378. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  379. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  380. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  381. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  382. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  383. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  384. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  385. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  386. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  387. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  388. package/templates/be/skeleton/apps/__app__/src/app.module.ts +11 -9
  389. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  390. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  391. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +2 -0
  392. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  393. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  394. package/templates/be/skeleton/apps/__app__/src/main.ts +15 -1
  395. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  396. package/templates/be/skeleton/apps/cli/src/cli.options.ts +6 -14
  397. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  398. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  399. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  400. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  401. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  402. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  403. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  404. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  405. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  406. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  407. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  408. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  409. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  410. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  411. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  412. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  413. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  414. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  415. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  416. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  417. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  418. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  419. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  420. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  421. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  422. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  423. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  424. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  425. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  426. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  427. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  428. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  429. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  430. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  431. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  432. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  433. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  434. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  435. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  436. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  437. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  438. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  439. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  440. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  441. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  442. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  443. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  444. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  445. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  446. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  447. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  448. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  449. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  450. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  451. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  452. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  453. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  454. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  455. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  456. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  457. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  458. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  459. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  460. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  461. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  462. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  463. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  464. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  465. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  466. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  467. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  468. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  469. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +14 -18
  470. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  471. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  472. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  473. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +2 -0
  474. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  475. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  476. package/templates/be/skeleton-lite/apps/api/src/main.ts +2 -3
  477. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  478. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  479. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  480. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  481. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  482. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  483. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  484. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  485. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  486. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  487. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  488. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  489. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  490. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  491. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  492. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  493. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  494. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  495. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  496. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  497. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  498. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  499. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  500. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  501. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  502. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  503. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  504. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  505. package/templates/be/table/migration.sql.tpl +1 -0
  506. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  507. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  508. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  509. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  510. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  511. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  512. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  513. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  514. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  515. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  516. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  517. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  518. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  519. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  520. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  521. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  522. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  523. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  524. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  525. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  526. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  527. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  528. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  529. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  530. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  531. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  532. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  533. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  534. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  535. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  536. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  537. package/upgrade/check.mjs +3 -1
  538. package/upgrade/index.mjs +54 -41
@@ -0,0 +1,367 @@
1
+ import { validateArgs } from '../../packages/cli/src/validate-args.mjs';
2
+ import { isAlternation, isPlaceholder, tokenise } from '../lib/command-tokens.mjs';
3
+
4
+ // The one owner of "a documented command exists": it finds every `starci <group> <verb> ...` a text shows to an agent or owner
5
+ // (extractCommands) and judges it the way the dispatcher would (checkCommand): group and verb exist, every flag is declared, enum
6
+ // values are valid, required flags and positionals are present unless the text is a fragment (a bare verb, a line that ends in an
7
+ // ellipsis, a flag mentioned on its own). The prose-commands self-check (check-prose-commands.mjs, R230) and the doc-command specs
8
+ // are callers: they choose the files and the reasons they enforce, never a second extractor.
9
+
10
+ const IDENTIFIER = /^[a-z][a-z0-9-]*$/;
11
+ // a sentence that quotes a command only to say it is gone is not an instruction to run it
12
+ const RETIRED_SENTENCE = /^[^.;]*?\b(?:is|was|are|were)\s+(?:now\s+)?(?:gone|removed|retired|dropped)\b/;
13
+ // `starci history guard: refused ...`: a log label names no command
14
+ const LOG_LABEL = /^[\w-]+(?:\s+[\w-]+)?:(?:\s|$)/;
15
+ const ASKED = /\b(?:run|Run|execute|Execute)\s*$/;
16
+ const LIST_BULLET = /^\s*(?:[-*>$]\s*)*$/;
17
+ const NOT_A_COMMAND_HEAD = new Set(['help', '--help', '-h', '--version', 'completion']);
18
+ const SEPARATORS = new Set(['|', '||', '&&', ';', '>', '>>', '<', '&', '2>', '2>&1', '#', '\\', 'then', 'do', '-', '—', '–', '→', '←', '=>', '->', '-->', '=', '+', '/', 'and', 'or', 'to']);
19
+ const WORD_VALUE = /^[\w-]+$/;
20
+ const PROMPT_ASSIGNMENT = /^[A-Za-z_]+=\S*\s+/;
21
+ const OPERATORS = ['&&', '||', ';', '|', '('];
22
+ const KEYWORDS = ['then', 'do', 'sudo', 'exec'];
23
+
24
+ // a flag family (`--until-*`, `--until-<type>`, `--until-...`) names no single flag
25
+ const isEllipsis = (t) => t === '...' || t === '…' || t.endsWith('...') || /^--[\w-]*(-\*|-<[^>]*>|-)$/.test(t) || /^[<[]?(options?|flags?|args?|arguments?|rest|more|etc)[>\]]?$/i.test(t);
26
+ const isOpaque = (t) => isPlaceholder(t) || isEllipsis(t);
27
+ const alternatives = (t) => (!/[<{$]/.test(t) && (t.includes('|') || (/^[a-z]/.test(t) && t.includes('/'))) ? t.split(/[|/]/) : [t]);
28
+ const inner = (t) => t.replace(/^<|>$/g, '');
29
+
30
+ // ---- extraction -----------------------------------------------------------------------------------------------
31
+
32
+ const COMMAND_AT = /(?<![\w@./:-])(?:npx\s+starci|npm\s+run\s+starci(?:\s+--silent)?(?:\s+--)?|starci)(?![\w@./-])(?=\s|$)/g;
33
+ const FENCE = /^\s*(?:[-*>|]\s*)?```/;
34
+ const SCRIPT_COMMENT = /^\s*(?:\/\/|\*|\/\*)/;
35
+ const CONTINUED = /\\\s*$/;
36
+ const SCRIPT_LITERAL = /(['"`])((?:\\.|(?!\1).)*)\1/g;
37
+ const SPELLING_FIELD = /\bspelling:\s*$/;
38
+
39
+ // the text before a `starci` is a shell prompt and environment assignments only
40
+ function startsAtPrompt(before) {
41
+ let rest = before.trimStart();
42
+ if (rest.startsWith('PS>')) rest = rest.slice(3).trimStart();
43
+ else if (rest.startsWith('$') || rest.startsWith('>')) rest = rest.slice(1).trimStart();
44
+ while (PROMPT_ASSIGNMENT.test(rest)) rest = rest.replace(PROMPT_ASSIGNMENT, '');
45
+ return rest === '';
46
+ }
47
+
48
+ const isWordChar = (ch) => ch !== undefined && /\w/.test(ch);
49
+
50
+ // the text before a `starci` ends in a shell operator or a command keyword
51
+ function endsAtOperator(before) {
52
+ const head = before.trimEnd();
53
+ if (OPERATORS.some((op) => head.endsWith(op))) return true;
54
+ return KEYWORDS.some((word) => head.endsWith(word) && !isWordChar(head[head.length - word.length - 1]));
55
+ }
56
+
57
+ const isCommandPosition = (before) => startsAtPrompt(before) || endsAtOperator(before);
58
+
59
+ // {body, end} of the code span that opens at `from` (a run of backticks closed by a run of the same length), or null
60
+ function spanAt(line, from) {
61
+ let run = 0;
62
+ while (line[from + run] === '`') run += 1;
63
+ for (let n = run; n >= 1; n -= 1) {
64
+ const close = line.indexOf('`'.repeat(n), from + n);
65
+ if (close > from + n) return { body: line.slice(from + n, close), end: close + n };
66
+ }
67
+ return null;
68
+ }
69
+
70
+ function spansOf(line) {
71
+ const spans = [];
72
+ let masked = '';
73
+ let i = 0;
74
+ while (i < line.length) {
75
+ const span = line[i] === '`' ? spanAt(line, i) : null;
76
+ if (span === null) {
77
+ masked += line[i];
78
+ i += 1;
79
+ } else {
80
+ spans.push(span.body);
81
+ masked += ' '.repeat(span.end - i);
82
+ i = span.end;
83
+ }
84
+ }
85
+ return { spans, masked };
86
+ }
87
+
88
+ // Escapes of the host language (JSON/YAML/JS string literals) are not part of the command.
89
+ const unescape = (s) => s.replace(/\\[nt]/g, ' ').replace(/\\(["'`])/g, '$1').replace(/\\$/, '');
90
+
91
+ // the line without its trailing ` # comment`
92
+ function withoutComment(code) {
93
+ for (let i = code.indexOf('#'); i >= 0; i = code.indexOf('#', i + 1)) {
94
+ if (i > 0 && /\s/.test(code[i - 1]) && /\s/.test(code[i + 1] ?? '')) {
95
+ let start = i - 1;
96
+ while (start > 0 && /\s/.test(code[start - 1])) start -= 1;
97
+ return code.slice(0, start);
98
+ }
99
+ }
100
+ return code;
101
+ }
102
+
103
+ // every `starci` of `body` as an occurrence {line, mode, preceded, text}; `modeOf(before)` names the mode
104
+ function occurrencesIn(body, line, modeOf, scan = body) {
105
+ return [...scan.matchAll(COMMAND_AT)].map((m) => {
106
+ const before = body.slice(0, m.index);
107
+ return { line, mode: modeOf(before), preceded: before, text: unescape(body.slice(m.index + m[0].length)) };
108
+ });
109
+ }
110
+
111
+ // a fenced line is a command where starci stands in command position; elsewhere it is a diagram or a sentence
112
+ const fencedMode = (before) => (isCommandPosition(before) ? 'bounded' : 'prose');
113
+
114
+ // the fenced line at `index` with its `\`-continued lines joined: {code, last}
115
+ function joinContinuations(lines, index) {
116
+ let code = lines[index];
117
+ let last = index;
118
+ while (CONTINUED.test(code) && last + 1 < lines.length && !/^\s*```/.test(lines[last + 1])) {
119
+ last += 1;
120
+ code = `${code.replace(CONTINUED, ' ')}${lines[last].trimStart()}`;
121
+ }
122
+ return { code, last };
123
+ }
124
+
125
+ // the string literals of a script line, a retired spelling (`spelling: '...'`) kept so the dispatcher can refuse it excluded
126
+ const scriptLiterals = (line) => [...line.matchAll(SCRIPT_LITERAL)].filter((m) => !SPELLING_FIELD.test(line.slice(0, m.index))).map((m) => m[2]);
127
+
128
+ function lineOccurrences(line, lineNo, kind) {
129
+ const found = [];
130
+ for (const segment of kind === 'script' ? scriptLiterals(line) : [line]) {
131
+ const { spans, masked } = spansOf(segment);
132
+ for (const span of spans) found.push(...occurrencesIn(span, lineNo, () => 'bounded'));
133
+ found.push(...occurrencesIn(masked, lineNo, () => 'prose'));
134
+ }
135
+ return found;
136
+ }
137
+
138
+ /**
139
+ * Every starci command occurrence in one text.
140
+ * `bounded` occurrences (inside a code span, a fenced line, or a script string literal span) run to the end of that
141
+ * span; `prose` occurrences inside running text only keep the flags and values that follow the verb.
142
+ */
143
+ export function extractCommands(text, { kind = 'text', skipLine = null } = {}) {
144
+ const found = [];
145
+ const lines = text.split(/\r?\n/);
146
+ let fenced = false;
147
+ let index = 0;
148
+ while (index < lines.length) {
149
+ const line = lines[index];
150
+ if (kind !== 'script' && FENCE.test(line)) fenced = !fenced;
151
+ else if (!skipLine?.(line) && fenced) {
152
+ const { code, last } = joinContinuations(lines, index);
153
+ found.push(...occurrencesIn(code, index + 1, fencedMode, withoutComment(code)));
154
+ index = last;
155
+ } else if (!skipLine?.(line) && (kind !== 'script' || !SCRIPT_COMMENT.test(line))) {
156
+ found.push(...lineOccurrences(line, index + 1, kind));
157
+ }
158
+ index += 1;
159
+ }
160
+ return found;
161
+ }
162
+
163
+ // ---- validation -----------------------------------------------------------------------------------------------
164
+
165
+ const sampleFor = (flag) => {
166
+ if (flag.type === 'enum') return flag.enum[0];
167
+ if (flag.type === 'number') return '1';
168
+ if (flag.type === 'boolean') return 'true';
169
+ return 'x';
170
+ };
171
+
172
+ const globalFlagsBefore = (tokens, globals) => {
173
+ let i = 0;
174
+ while (i < tokens.length && tokens[i].text.startsWith('--')) {
175
+ const g = globals.get(tokens[i].text.slice(2).split('=')[0]);
176
+ if (!g) break;
177
+ i += g.type === 'boolean' || tokens[i].text.includes('=') ? 1 : 2;
178
+ }
179
+ return i;
180
+ };
181
+
182
+ function commandTokens(occurrence) {
183
+ const tokens = tokenise(occurrence.text.split(/\s{3,}/)[0].trim());
184
+ const cut = tokens.findIndex((t) => SEPARATORS.has(t.text) || /^\d?>/.test(t.text) || t.text === 'starci');
185
+ return cut >= 0 ? tokens.slice(0, cut) : tokens;
186
+ }
187
+
188
+ const isAsked = (occurrence, strictProse) => strictProse || ASKED.test(occurrence.preceded) || LIST_BULLET.test(occurrence.preceded);
189
+
190
+ // the findings of the group word(s) when they end the check ([] ends it silently), or null when the verb is next
191
+ function groupStop(occurrence, groupAlts, unknownGroups, lenient) {
192
+ if (!groupAlts.every((g) => IDENTIFIER.test(g))) return [];
193
+ if (unknownGroups.length > 0 && (lenient || LOG_LABEL.test(occurrence.text.trim()))) return [];
194
+ return unknownGroups.length > 0 ? unknownGroups.map((g) => `unknown group "${g}"`) : null;
195
+ }
196
+
197
+ // {stop: findings} when the occurrence ends before its flags, else the resolved call
198
+ function resolveCall(occurrence, catalog, strictProse) {
199
+ const bounded = occurrence.mode === 'bounded';
200
+ const tokens = commandTokens(occurrence);
201
+ const globals = new Map(catalog.global.map((f) => [f.name, f]));
202
+ const i = globalFlagsBefore(tokens, globals);
203
+ const head = tokens[i];
204
+ if (!head || NOT_A_COMMAND_HEAD.has(head.text) || isOpaque(head.text)) return { stop: [] };
205
+ const groupAlts = alternatives(head.text);
206
+ const lenient = !bounded && !isAsked(occurrence, strictProse);
207
+ const stopped = groupStop(occurrence, groupAlts, groupAlts.filter((g) => !catalog.groups[g]), lenient);
208
+ if (stopped) return { stop: stopped };
209
+ const group = catalog.groups[groupAlts[0]];
210
+ const verbToken = tokens[i + 1];
211
+ if (!verbToken || verbToken.text.startsWith('-') || isOpaque(verbToken.text)) return { stop: [] };
212
+ const verbAlts = alternatives(verbToken.text);
213
+ const unknownVerbs = verbAlts.filter((v) => !group.verbs[v]);
214
+ if (lenient && unknownVerbs.length === verbAlts.length) return { stop: [] };
215
+ if (unknownVerbs.length > 0) return { stop: unknownVerbs.map((v) => `unknown verb "${groupAlts[0]} ${v}"`) };
216
+ return { groupAlts, verbAlts, group, rest: tokens.slice(i + 2), globals, bounded };
217
+ }
218
+
219
+ // alternatives of groups or verbs: only the flag names are provable
220
+ function alternativeErrors(call) {
221
+ const errors = [];
222
+ for (const t of call.rest) {
223
+ if (!t.text.startsWith('--')) continue;
224
+ for (const n of alternatives(t.text).map((a) => a.replace(/^--/, '').split('=')[0])) {
225
+ const some = call.verbAlts.some((v) => call.group.verbs[v].flags?.some((f) => f.name === n)) || call.globals.has(n);
226
+ if (!some) errors.push(`--${n} is not a flag of ${call.groupAlts[0]} ${call.verbAlts.join('|')}`);
227
+ }
228
+ }
229
+ return errors;
230
+ }
231
+
232
+ const enumChoices = (literal, value) => {
233
+ if (literal !== null) return alternatives(literal);
234
+ return value !== null && isAlternation(value) ? inner(value).split('|') : [];
235
+ };
236
+
237
+ function pushFlagValue(st, flag, name, value) {
238
+ const literal = value === null || isPlaceholder(value) ? null : value;
239
+ if (flag.type !== 'enum') {
240
+ st.argv.push(`--${name}`, flag.type === 'number' || literal === null ? sampleFor(flag) : literal);
241
+ return;
242
+ }
243
+ for (const c of enumChoices(literal, value)) {
244
+ if (WORD_VALUE.test(c) && !flag.enum.includes(c)) st.errors.push(`--${name} expects one of: ${flag.enum.join(', ')} (got ${c})`);
245
+ }
246
+ st.argv.push(`--${name}`, flag.enum[0]);
247
+ }
248
+
249
+ // reads the flag at rest[k] (with its value) into `st`; returns the index of the last token it used
250
+ function readFlag(st, rest, k, ctx) {
251
+ const alts = alternatives(rest[k].text).map((a) => a.replace(/^--/, ''));
252
+ const eq = alts[0].indexOf('=');
253
+ const name = eq >= 0 ? alts[0].slice(0, eq) : alts[0];
254
+ const unknown = alts.map((a) => a.split('=')[0]).filter((an) => !ctx.declared.has(an));
255
+ for (const an of unknown) st.errors.push(`unknown option --${an}`);
256
+ if (unknown.length > 0) return k;
257
+ const flag = ctx.declared.get(name);
258
+ if (flag.type === 'boolean') {
259
+ st.argv.push(`--${name}`);
260
+ return k;
261
+ }
262
+ let value = eq >= 0 ? alts[0].slice(eq + 1) : undefined;
263
+ let last = k;
264
+ if (value === undefined) {
265
+ const next = rest[k + 1];
266
+ const takesNext = next && !next.text.startsWith('--') && !isEllipsis(next.text);
267
+ value = takesNext ? next.text : null;
268
+ if (takesNext) last = k + 1;
269
+ }
270
+ if (value !== null) st.valued = true;
271
+ pushFlagValue(st, flag, name, value);
272
+ return last;
273
+ }
274
+
275
+ // the argv entry (and findings) of one positional token against its schema entry
276
+ function pushPositional(st, text, entry) {
277
+ const alts = isAlternation(text) ? inner(text).split('|') : null;
278
+ if (entry?.enum && alts) {
279
+ for (const c of alts) {
280
+ if (WORD_VALUE.test(c) && !entry.enum.includes(c)) st.errors.push(`${entry.name} expects one of: ${entry.enum.join(', ')} (got ${c})`);
281
+ }
282
+ st.argv.push(entry.enum[0]);
283
+ } else if (isPlaceholder(text) || text.includes('|')) st.argv.push(entry?.enum ? entry.enum[0] : 'x');
284
+ else st.argv.push(text);
285
+ }
286
+
287
+ // reads the positional token `t` into `st`; false when the command ends before it
288
+ function readPositional(st, t, ctx) {
289
+ if (!ctx.bounded && !/^<.*>$/.test(t.text)) return false;
290
+ if (t.text.startsWith('-') && t.text.length > 1) {
291
+ st.errors.push(`unknown option ${t.text}`);
292
+ return true;
293
+ }
294
+ const entry = ctx.schema[Math.min(st.position, ctx.schema.length - 1)];
295
+ st.position += 1;
296
+ st.valued = true;
297
+ pushPositional(st, t.text, entry);
298
+ return true;
299
+ }
300
+
301
+ function readArguments(rest, ctx) {
302
+ const st = { argv: [], errors: [], valued: false, fragment: rest.length === 0, position: 0 };
303
+ let k = 0;
304
+ while (k < rest.length) {
305
+ const t = rest[k];
306
+ if (isEllipsis(t.text)) {
307
+ st.fragment = true;
308
+ if (!ctx.bounded) break;
309
+ } else if (t.text === '--') {
310
+ st.fragment = true;
311
+ break;
312
+ } else if (t.text.startsWith('--')) k = readFlag(st, rest, k, ctx);
313
+ else if (!readPositional(st, t, ctx)) break;
314
+ k += 1;
315
+ }
316
+ return st;
317
+ }
318
+
319
+ // A mention that is not a whole call (prose, no value given, an ellipsis) is only held to the flags it names.
320
+ function supplyMissing(st, ctx, error) {
321
+ const missingFlag = /^missing required option --(.+)$/.exec(error);
322
+ if (missingFlag) {
323
+ st.argv.push(`--${missingFlag[1]}`, sampleFor(ctx.declared.get(missingFlag[1])));
324
+ return true;
325
+ }
326
+ const missingPositional = /^missing required positional (.+)$/.exec(error);
327
+ if (!missingPositional) return false;
328
+ st.argv.push(ctx.schema.find((s) => s.name === missingPositional[1])?.enum?.[0] ?? 'x');
329
+ return true;
330
+ }
331
+
332
+ function proveCall(st, ctx, catalog) {
333
+ const whole = ctx.bounded && st.valued && !st.fragment;
334
+ for (let attempt = 0; attempt < 30; attempt += 1) {
335
+ const result = validateArgs(st.argv, ctx.verb, catalog.global);
336
+ if (result.ok) return [];
337
+ if (whole || !supplyMissing(st, ctx, result.error)) return [result.error];
338
+ }
339
+ return [];
340
+ }
341
+
342
+ function checkSingle(call, catalog) {
343
+ const verbName = call.verbAlts[0];
344
+ const verb = { ...call.group.verbs[verbName], group: call.groupAlts[0], verb: verbName };
345
+ const declared = new Map([...catalog.global, ...(verb.flags ?? [])].map((f) => [f.name, f]));
346
+ const ctx = { bounded: call.bounded, verb, declared, schema: verb.positional ?? [] };
347
+ const st = readArguments(call.rest, ctx);
348
+ return st.errors.length > 0 ? st.errors : proveCall(st, ctx, catalog);
349
+ }
350
+
351
+ /**
352
+ * Returns [] when the command is a valid call (or a fragment of one), else the reasons it is not.
353
+ * `strictProse` holds a mention inside running text to the same group and verb as a span.
354
+ */
355
+ export function checkCommand(occurrence, catalog, { strictProse = false } = {}) {
356
+ if (RETIRED_SENTENCE.test(occurrence.text)) return [];
357
+ const call = resolveCall(occurrence, catalog, strictProse);
358
+ if (call.stop) return call.stop;
359
+ if (call.verbAlts.length > 1 || call.groupAlts.length > 1) return alternativeErrors(call);
360
+ return checkSingle(call, catalog);
361
+ }
362
+
363
+ /** The command table of a catalog loaded from modules/cli/commands (scripts/cli/catalog.mjs loadCatalog) in the shape checkCommand reads. */
364
+ export const commandCatalogOf = (loaded) => ({
365
+ global: loaded.global.flags,
366
+ groups: Object.fromEntries(loaded.groups.map((group) => [group.group, { verbs: Object.fromEntries(group.verbs.map((verb) => [verb.verb, verb])) }])),
367
+ });
@@ -1,7 +1,7 @@
1
1
  // edition.mjs - the one edition filter shared by the HFS slot resolver, declaration validation, rule catalog and
2
2
  // edition-specific rule parameters. Full is the default; lite changes data views, never the checks that consume them.
3
- // Lite is the full architecture deployed without self-hosted infrastructure (owner ruling 2026-10-12): its overlays swap
4
- // adapters and remove the infra and e2e world, and every slot keeps its own `tests`.
3
+ // Lite uses provider adapters and declares Docker Swarm production infrastructure (owner:lite-production-swarm, ruling:R212/case-4).
4
+ // Its overlays exclude the e2e world, and every slot keeps its own `tests`.
5
5
  import { isPlainObject } from '../../engine/plain-object.mjs';
6
6
  import { EDITIONS } from './manifest-shape.mjs';
7
7
 
@@ -0,0 +1,158 @@
1
+ // Runtime examples keep names only; checks receive disposable copies with fresh sealed members.
2
+ import fs from 'node:fs';
3
+ import { safeRemove } from '../api/fs/safe-remove.mjs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { runProgram } from '../api/process/run-program.mjs';
7
+ import { init as initGit } from '../api/git/init.mjs';
8
+ import { add as addGit } from '../api/git/add.mjs';
9
+ import { stringifyYaml } from '../../engine/yaml.mjs';
10
+ import { isMain } from '../lib/is-main.mjs';
11
+ import { readEnv } from '../lib/env.mjs';
12
+ import { withoutGitLocalEnv } from '../lib/git.mjs';
13
+ import { resolveRealTool } from '../api/process/resolve-real-tool.mjs';
14
+ import { generateAppIdentity } from './secret-bootstrap-crypto.mjs';
15
+ import { declaredSealedMembers, materializeDeclaredPlaceholders } from './sealed-members.mjs';
16
+
17
+ const SKIPPED = new Set(['.git', '.claude', '.secrets', '.aws', '.codex', '.agents', 'node_modules', '.next', '.turbo', 'dist', 'coverage']);
18
+ const PATH_RULE = String.raw`\.starcistacks[/\\][^/\\]+[/\\]secrets[/\\][^/\\]+\.enc$`;
19
+
20
+ function writePublicRecipient(repoRoot, recipient) {
21
+ fs.writeFileSync(path.join(repoRoot, '.sops.yaml'), stringifyYaml({
22
+ creation_rules: [{ path_regex: PATH_RULE, age: recipient }],
23
+ }));
24
+ const stacks = path.join(repoRoot, '.starcistacks');
25
+ for (const entry of fs.readdirSync(stacks, { withFileTypes: true })) {
26
+ if (!entry.isDirectory()) continue;
27
+ const file = path.join(stacks, entry.name, 'backup.recipient');
28
+ if (fs.existsSync(file)) fs.writeFileSync(file, `${recipient}\n`);
29
+ }
30
+ }
31
+
32
+ /** Generate genuine sealed members; the fresh private identity is confined to os.tmpdir and removed before return. */
33
+ export async function materializeExample(repoRoot, { createIdentity = generateAppIdentity, sops } = {}) {
34
+ const declared = declaredSealedMembers(repoRoot);
35
+ if (!declared.length) return { members: [] };
36
+ if (declared.some(({ env, slug }) => fs.existsSync(path.join(repoRoot, '.starcistacks', env, 'secrets', slug + '.enc'))))
37
+ throw new Error('example materialization requires a disposable copy without existing sealed members');
38
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'starci-example-age-'));
39
+ try {
40
+ const selected = createIdentity();
41
+ try {
42
+ fs.writeFileSync(path.join(directory, 'identity.txt'), selected.identity, { mode: 0o600, flag: 'wx' });
43
+ } finally {
44
+ selected.identity.fill(0);
45
+ }
46
+ writePublicRecipient(repoRoot, selected.recipient);
47
+ return materializeDeclaredPlaceholders(repoRoot, { recipient: selected.recipient, sops });
48
+ } finally {
49
+ safeRemove(directory, { hold: () => null });
50
+ }
51
+ }
52
+
53
+ function trackExample(repoRoot) {
54
+ const env = withoutGitLocalEnv(process.env);
55
+ if (!initGit(repoRoot, { env }).ok) throw new Error('could not initialize the isolated example git tree');
56
+ const staged = addGit(['-A', '--', '.'], { cwd: repoRoot, env });
57
+ if (staged.error || staged.status !== 0) throw new Error('could not stage the isolated example git tree');
58
+ }
59
+
60
+ function safeExampleSource(source, { preserveCoverage = false } = {}) {
61
+ const name = path.basename(source);
62
+ const stat = fs.lstatSync(source);
63
+ if (stat.isSymbolicLink()) return false;
64
+ if (SKIPPED.has(name) && !(preserveCoverage && name === 'coverage')) return false;
65
+ if (name.endsWith('.enc') || /\.(?:agekey|pem|key)$/iu.test(name)) return false;
66
+ if (['secret.env', 'secrets.env', 'keys.txt'].includes(name)) return false;
67
+ if (name === '.env.example' || name.endsWith('.env.example')) return true;
68
+ if (name === '.env' || name.startsWith('.env.') || name.endsWith('.env')) return false;
69
+ if (stat.isDirectory()) return true;
70
+ const parts = source.split(path.sep);
71
+ return !parts.includes('secrets') || ['.gitignore', 'README.md'].includes(name);
72
+ }
73
+
74
+ function pruneEmptyDirectories(directory) {
75
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
76
+ if (entry.isDirectory()) pruneEmptyDirectories(path.join(directory, entry.name));
77
+ }
78
+ if (!fs.readdirSync(directory).length) fs.rmdirSync(directory);
79
+ }
80
+
81
+ /** Await callback(tempExamplePath), return its result, and remove the os.tmpdir-based copy even on failure. */
82
+ export async function withMaterializedExample(repoRoot, exampleRelativePath, callback, { materialize = materializeExample, linkDependencies = false, preserveCoverage = false } = {}) {
83
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'starci-example-'));
84
+ const source = path.resolve(repoRoot, exampleRelativePath);
85
+ const app = path.join(directory, 'examples', path.basename(source));
86
+ try {
87
+ fs.writeFileSync(path.join(directory, 'hfs.json'), JSON.stringify({ kind: 'runtime' }));
88
+ fs.cpSync(source, app, {
89
+ recursive: true,
90
+ filter: (file) => safeExampleSource(file, { preserveCoverage }),
91
+ });
92
+ pruneEmptyDirectories(app);
93
+ if (linkDependencies) linkExampleDependencies(source, app);
94
+ await materialize(app);
95
+ trackExample(app);
96
+ return await callback(app);
97
+ } finally {
98
+ safeRemove(directory, { hold: () => null });
99
+ }
100
+ }
101
+
102
+ // Runtime example command execution shares the same isolated materialization as the source check.
103
+ function linkExampleDependencies(source, target) {
104
+ for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
105
+ const from = path.join(source, entry.name);
106
+ const to = path.join(target, entry.name);
107
+ if (entry.name === 'node_modules') {
108
+ fs.mkdirSync(target, { recursive: true });
109
+ fs.symlinkSync(from, to, process.platform === 'win32' ? 'junction' : 'dir');
110
+ } else if (entry.isDirectory() && safeExampleSource(from)) {
111
+ linkExampleDependencies(from, to);
112
+ }
113
+ }
114
+ }
115
+
116
+ function commandLaunch(command, args) {
117
+ const npm = /^(npm|npx)(?:\.cmd)?$/iu.exec(command);
118
+ if (process.platform !== 'win32' || !npm) return { file: command, args };
119
+ const entry = npm[1].toLowerCase() + '-cli.js';
120
+ const resolved = resolveRealTool(npm[1]);
121
+ const npmExecPath = readEnv('npm_execpath');
122
+ const candidates = [
123
+ npmExecPath && path.join(path.dirname(npmExecPath), entry),
124
+ resolved && path.join(path.dirname(resolved), 'node_modules', 'npm', 'bin', entry),
125
+ path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', entry),
126
+ ];
127
+ const cli = candidates.find((file) => file && fs.existsSync(file));
128
+ if (!cli) throw new Error('the example runner could not locate the installed npm CLI entry point');
129
+ return { file: process.execPath, args: [cli, ...args] };
130
+ }
131
+
132
+ function commandEnvironment() {
133
+ const env = withoutGitLocalEnv(process.env);
134
+ return Object.fromEntries(Object.entries(env).filter(([name]) => !/^SOPS_AGE_KEY(?:_FILE)?$/iu.test(name)));
135
+ }
136
+
137
+ /** Run an installed app command in a sealed temporary copy, returning its exit code and preserving report artifacts. */
138
+ async function runMaterializedCommand(repoRoot, command, args = [], { run = runProgram } = {}) {
139
+ return withMaterializedExample(repoRoot, '.', (app) => {
140
+ const launch = commandLaunch(command, args);
141
+ const result = run(launch.file, launch.args, { cwd: app, env: commandEnvironment(), stdio: 'inherit', windowsHide: true });
142
+ const reports = path.join(app, 'reports');
143
+ if (fs.existsSync(reports)) fs.cpSync(reports, path.join(repoRoot, 'reports'), { recursive: true });
144
+ if (result.error) throw new Error('the example check command could not start');
145
+ return result.status ?? 1;
146
+ }, { linkDependencies: true, preserveCoverage: true });
147
+ }
148
+
149
+ async function main(argv) {
150
+ if (argv[0] === '--run' && argv[1] && argv[2] === '--' && argv[3])
151
+ return runMaterializedCommand(path.resolve(argv[1]), argv[3], argv.slice(4));
152
+ if (argv.length !== 1) throw new Error('usage: example-secrets.mjs <disposable-copy> | --run <example> -- <command> [args]');
153
+ const result = await materializeExample(path.resolve(argv[0]));
154
+ process.stdout.write(`example sealed members generated: ${result.members.length}\n`);
155
+ return 0;
156
+ }
157
+
158
+ if (isMain(import.meta.url)) process.exitCode = await main(process.argv.slice(2));