@starci/hfs 5.0.8 → 5.2.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 (339) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +10 -1
  3. package/cache/import-graph.mjs +6 -61
  4. package/cache/lint-family.d.mts +10 -1
  5. package/cache/lint-family.mjs +12 -5
  6. package/codemod/directive-first.d.mts +48 -0
  7. package/codemod/directive-first.mjs +136 -0
  8. package/codemod/explicit-surface-edit.d.mts +14 -0
  9. package/codemod/explicit-surface-edit.mjs +57 -0
  10. package/codemod/explicit-surface-names.d.mts +30 -0
  11. package/codemod/explicit-surface-names.mjs +135 -0
  12. package/codemod/explicit-surface-render.d.mts +29 -0
  13. package/codemod/explicit-surface-render.mjs +197 -0
  14. package/codemod/explicit-surface.d.mts +50 -0
  15. package/codemod/explicit-surface.mjs +374 -0
  16. package/codemod/main.d.mts +7 -5
  17. package/codemod/main.mjs +37 -16
  18. package/codemod/source-tree.d.mts +8 -0
  19. package/codemod/source-tree.mjs +23 -0
  20. package/db/postgres.mjs +9 -1
  21. package/lint/eslint-list.cjs +29 -0
  22. package/lint/run.d.mts +19 -5
  23. package/lint/run.mjs +47 -14
  24. package/package.json +1 -1
  25. package/runtime/knowledge/hfs/canon-pins.yaml +31 -10
  26. package/runtime/knowledge/hfs/fe-stack.yaml +7 -7
  27. package/runtime/knowledge/hfs/fork-images.yaml +7 -0
  28. package/runtime/knowledge/hfs/rules.yaml +107 -8
  29. package/runtime/knowledge/hfs/slots.yaml +75 -24
  30. package/runtime/modules/kernel/failure-codes.yaml +62 -0
  31. package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
  32. package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
  33. package/runtime/scripts/hfs/architecture/test-world-files.d.mts +4 -3
  34. package/runtime/scripts/hfs/architecture/test-world-files.mjs +26 -7
  35. package/runtime/scripts/hfs/architecture/transport-owner.d.mts +1 -2
  36. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -6
  37. package/runtime/scripts/hfs/explicit-surface-rule.d.mts +58 -0
  38. package/runtime/scripts/hfs/explicit-surface-rule.mjs +59 -0
  39. package/runtime/scripts/hfs/manifest-shape.d.mts +1 -0
  40. package/runtime/scripts/hfs/manifest-shape.mjs +3 -1
  41. package/runtime/scripts/hfs/rules/a15-findings.mjs +5 -0
  42. package/runtime/scripts/hfs/rules/stacks-local.d.mts +7 -0
  43. package/runtime/scripts/hfs/rules/stacks-local.mjs +71 -0
  44. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +6 -1
  45. package/runtime/scripts/hfs/rules/stacks-service-images.d.mts +12 -0
  46. package/runtime/scripts/hfs/rules/stacks-service-images.mjs +120 -0
  47. package/runtime/scripts/hfs/secret-dev-init.d.mts +11 -0
  48. package/runtime/scripts/hfs/secret-dev-init.mjs +79 -0
  49. package/runtime/scripts/hfs/secret.mjs +10 -7
  50. package/runtime/scripts/hfs/slots.mjs +1 -1
  51. package/runtime/scripts/hfs/stack-local-config.d.mts +101 -0
  52. package/runtime/scripts/hfs/stack-local-config.mjs +320 -0
  53. package/runtime/scripts/hfs/stacks-gate.mjs +1 -1
  54. package/runtime/scripts/lib/declaration-surface.d.mts +35 -0
  55. package/runtime/scripts/lib/declaration-surface.mjs +112 -0
  56. package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
  57. package/runtime/scripts/lib/swarm-stack.mjs +2 -2
  58. package/scaffold/add-module.d.mts +16 -0
  59. package/scaffold/add-module.mjs +232 -0
  60. package/scaffold/add-queue-lite.mjs +49 -0
  61. package/scaffold/add.mjs +5 -0
  62. package/scaffold/app.mjs +7 -7
  63. package/scaffold/module-registry.d.mts +19 -0
  64. package/scaffold/module-registry.mjs +84 -0
  65. package/src/main.mjs +23 -2
  66. package/stack/cli.mjs +6 -1
  67. package/stack/context.d.mts +15 -0
  68. package/stack/context.mjs +1 -0
  69. package/stack/local-cli.d.mts +25 -0
  70. package/stack/local-cli.mjs +89 -0
  71. package/stack/local-config.d.mts +17 -0
  72. package/stack/local-config.mjs +37 -0
  73. package/stack/local-custody.d.mts +24 -0
  74. package/stack/local-custody.mjs +109 -0
  75. package/stack/local-doctor.d.mts +6 -0
  76. package/stack/local-doctor.mjs +49 -0
  77. package/stack/local-down.d.mts +8 -0
  78. package/stack/local-down.mjs +20 -0
  79. package/stack/local-host.d.mts +42 -0
  80. package/stack/local-host.mjs +163 -0
  81. package/stack/local-logs.d.mts +8 -0
  82. package/stack/local-logs.mjs +32 -0
  83. package/stack/local-ports.d.mts +7 -0
  84. package/stack/local-ports.mjs +39 -0
  85. package/stack/local-prepare.d.mts +22 -0
  86. package/stack/local-prepare.mjs +62 -0
  87. package/stack/local-run.d.mts +71 -0
  88. package/stack/local-run.mjs +200 -0
  89. package/stack/local-state.d.mts +35 -0
  90. package/stack/local-state.mjs +29 -0
  91. package/stack/local-status.d.mts +6 -0
  92. package/stack/local-status.mjs +36 -0
  93. package/stack/local-up.d.mts +11 -0
  94. package/stack/local-up.mjs +166 -0
  95. package/stack/local-verify.d.mts +6 -0
  96. package/stack/local-verify.mjs +21 -0
  97. package/sync/index.d.mts +13 -12
  98. package/sync/index.mjs +83 -28
  99. package/templates/app/module/jest.config.js.tpl +8 -0
  100. package/templates/app/module/src/index.ts.tpl +2 -0
  101. package/templates/app/module/src/manifest/declaration.spec.ts.tpl +11 -0
  102. package/templates/app/module/src/manifest/declaration.ts.tpl +13 -0
  103. package/templates/app/module/src/manifest/index.ts.tpl +1 -0
  104. package/templates/app/module/src/public/index.ts.tpl +2 -0
  105. package/templates/app/module/src/template/__name__.template.spec.ts.tpl +13 -0
  106. package/templates/app/module/src/template/__name__.template.ts.tpl +41 -0
  107. package/templates/app/module/src/template/index.ts.tpl +1 -0
  108. package/templates/app/module/src/workbench/components/__pascal__WorkbenchView.tsx.tpl +16 -0
  109. package/templates/app/module/src/workbench/index.ts.tpl +2 -0
  110. package/templates/app/package-scripts/package.json +1 -1
  111. package/templates/app/package-scripts-lite/package.json +1 -1
  112. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +76 -70
  113. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +23 -13
  114. package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +25 -0
  115. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -26
  116. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +1 -0
  117. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +2 -1
  118. package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +57 -14
  119. package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +11 -0
  120. package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.handler.ts +23 -0
  121. package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.query.ts +12 -0
  122. package/templates/be/auth-lite/src/features/api/auth/application/google-begin.command.ts +11 -0
  123. package/templates/be/auth-lite/src/features/api/auth/application/google-begin.handler.ts +22 -0
  124. package/templates/be/auth-lite/src/features/api/auth/application/google-complete.command.ts +12 -0
  125. package/templates/be/auth-lite/src/features/api/auth/application/google-complete.handler.ts +23 -0
  126. package/templates/be/auth-lite/src/features/api/auth/application/login.command.ts +12 -0
  127. package/templates/be/auth-lite/src/features/api/auth/application/login.handler.ts +23 -0
  128. package/templates/be/auth-lite/src/features/api/auth/application/logout.command.ts +11 -0
  129. package/templates/be/auth-lite/src/features/api/auth/application/logout.handler.ts +22 -0
  130. package/templates/be/auth-lite/src/features/api/auth/application/refresh.command.ts +12 -0
  131. package/templates/be/auth-lite/src/features/api/auth/application/refresh.handler.ts +23 -0
  132. package/templates/be/auth-lite/src/features/api/auth/application/session.command.ts +12 -0
  133. package/templates/be/auth-lite/src/features/api/auth/application/session.handler.ts +23 -0
  134. package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +13 -0
  135. package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +16 -0
  136. package/templates/be/auth-lite/src/features/api/auth/auth.options.ts +3 -0
  137. package/templates/be/auth-lite/src/features/api/auth/index.ts +2 -0
  138. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +13 -0
  139. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module.ts +9 -0
  140. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.options.ts +3 -0
  141. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +90 -0
  142. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/google-start.request.ts +17 -0
  143. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/login.request.ts +16 -0
  144. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +46 -0
  145. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +28 -0
  146. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +145 -0
  147. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +95 -0
  148. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +38 -0
  149. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +54 -0
  150. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts +18 -0
  151. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +34 -0
  152. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-session.entity.ts +30 -0
  153. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +17 -0
  154. package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +86 -0
  155. package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +45 -0
  156. package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +97 -0
  157. package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +81 -0
  158. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +78 -0
  159. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +87 -0
  160. package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +44 -0
  161. package/templates/be/inbox/delivery-recorded.event.ts.tpl +2 -2
  162. package/templates/be/inbox/entities.index.ts.tpl +1 -1
  163. package/templates/be/inbox/module-definition.ts.tpl +8 -2
  164. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +10 -2
  165. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +8 -2
  166. package/templates/be/patterns/api/feature.module-definition.ts.tpl +9 -3
  167. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +9 -3
  168. package/templates/be/patterns/cli/group.module-definition.ts.tpl +9 -3
  169. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +8 -3
  170. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +3 -2
  171. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -3
  172. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +8 -3
  173. package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +3 -2
  174. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +7 -6
  175. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +9 -3
  176. package/templates/be/patterns/jobs/job.processor.ts.tpl +1 -1
  177. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +9 -3
  178. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +8 -3
  179. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +3 -2
  180. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +5 -4
  181. package/templates/be/patterns/projections/persistence.connection.ts.tpl +3 -2
  182. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +8 -3
  183. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +3 -2
  184. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -3
  185. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +8 -3
  186. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +9 -3
  187. package/templates/be/patterns/reactors/consumer.ts.tpl +1 -1
  188. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +9 -3
  189. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +9 -3
  190. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +9 -3
  191. package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +9 -3
  192. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +9 -3
  193. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +1 -1
  194. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +1 -1
  195. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +9 -3
  196. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +9 -3
  197. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +9 -3
  198. package/templates/be/platform/graphql/graphql.module-definition.ts +8 -3
  199. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +9 -3
  200. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +9 -3
  201. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +9 -3
  202. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +9 -3
  203. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +9 -3
  204. package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +8 -3
  205. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +8 -3
  206. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +8 -3
  207. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +3 -2
  208. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +2 -1
  209. package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +8 -3
  210. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +8 -3
  211. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +8 -3
  212. package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -0
  213. package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +10 -4
  214. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +8 -4
  215. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +10 -4
  216. package/templates/be/skeleton/src/modules/platform/http-security/persistence/connection.ts +3 -2
  217. package/templates/be/skeleton/src/modules/platform/http-security/persistence/rate-limit.sql.ts +3 -2
  218. package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +10 -4
  219. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +8 -3
  220. package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +8 -3
  221. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +8 -3
  222. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +6 -5
  223. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +3 -2
  224. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +10 -5
  225. package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +34 -0
  226. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -2
  227. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +47 -2
  228. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -0
  229. package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +24 -0
  230. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +3 -3
  231. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +3 -1
  232. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +8 -4
  233. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +12 -0
  234. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts +29 -0
  235. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +6 -31
  236. package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +18 -9
  237. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +35 -11
  238. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.spec.ts +44 -8
  239. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +40 -18
  240. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +18 -6
  241. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.spec.ts +73 -0
  242. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.ts +26 -0
  243. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +24 -53
  244. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.registration.ts +31 -0
  245. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +1 -1
  246. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +1 -1
  247. package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +2 -1
  248. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
  249. package/templates/be/skeleton-lite/src/modules/platform/http-security/persistence/entities/index.ts +1 -1
  250. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
  251. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +7 -1
  252. package/templates/be/table/api/feature.module-definition.ts.tpl +9 -3
  253. package/templates/be/table/api/graphql.module-definition.ts.tpl +9 -3
  254. package/templates/be/table/entities.index.ts.tpl +1 -1
  255. package/templates/be/table/module-definition.ts.tpl +8 -3
  256. package/templates/be/test-world/e2e/serves-once-migrated.e2e-spec.ts.tpl +9 -9
  257. package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +4 -3
  258. package/templates/be/test-world/world/test-world.config.ts.tpl +8 -1
  259. package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
  260. package/templates/fe/common/app-layout.tsx.partial +1 -1
  261. package/templates/fe/common/site-shell.tsx.partial +3 -3
  262. package/templates/fe/skeleton/apps/__app__/src/components/providers/AppProviders/index.tsx +33 -10
  263. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/index.ts +1 -0
  264. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +2 -1
  265. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignIn.ts +11 -3
  266. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +8 -2
  267. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useViewerId.ts +11 -0
  268. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +2 -1
  269. package/templates/fe/skeleton/apps/__app__/src/modules/toast/hooks.ts +1 -1
  270. package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +18 -12
  271. package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
  272. package/templates/fe/skeleton/apps/__app__/src/redux/hooks.ts +3 -2
  273. package/templates/fe/skeleton/apps/__app__/src/redux/index.ts +1 -0
  274. package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +12 -4
  275. package/templates/fe/skeleton/apps/__app__/src/redux/store.ts +8 -3
  276. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +4 -3
  277. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +1 -1
  278. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +2 -1
  279. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +2 -1
  280. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +2 -1
  281. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +2 -1
  282. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -1
  283. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +4 -3
  284. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +2 -2
  285. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +2 -1
  286. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +2 -1
  287. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +2 -1
  288. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +2 -1
  289. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -1
  290. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +20 -2
  291. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +1 -1
  292. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +5 -1
  293. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +4 -7
  294. package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/index.tsx +2 -3
  295. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +4 -3
  296. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +4 -7
  297. package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/index.tsx +1 -2
  298. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +2 -2
  299. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +2 -1
  300. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +2 -1
  301. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +2 -1
  302. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +3 -1
  303. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +5 -1
  304. package/templates/fe/table/graphql/useMutateSwr.ts.tpl +2 -1
  305. package/templates/fe/table/graphql/useQuerySwr.ts.tpl +2 -1
  306. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  307. package/templates/fe/transport/graphql/clients/clients/no-auth.ts +1 -1
  308. package/templates/fe/transport/graphql/clients/links/attach-access-token.ts +1 -1
  309. package/templates/fe/transport/graphql/clients/links/error.ts +1 -1
  310. package/templates/fe/transport/graphql/clients/links/http.ts +1 -1
  311. package/templates/fe/transport/graphql/clients/links/locale.ts +1 -1
  312. package/templates/fe/transport/graphql/clients/links/refresh-token.ts +1 -1
  313. package/templates/fe/transport/graphql/clients/links/retry.ts +1 -1
  314. package/templates/fe/transport/graphql/clients/links/timeout.ts +1 -1
  315. package/templates/fe/units/page/index.tsx.tpl +3 -2
  316. package/templates/fe/units/slot/SlotView.tsx.tpl +1 -1
  317. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +0 -16
  318. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +0 -11
  319. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +0 -7
  320. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +0 -6
  321. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +0 -8
  322. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +0 -6
  323. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +0 -186
  324. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +0 -22
  325. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +0 -25
  326. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts +0 -104
  327. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +0 -34
  328. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.spec.ts +0 -28
  329. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +0 -11
  330. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +0 -88
  331. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +0 -69
  332. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +0 -37
  333. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -36
  334. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +0 -63
  335. package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/classNames.ts +0 -4
  336. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +0 -30
  337. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +0 -50
  338. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +0 -31
  339. package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/classNames.ts +0 -4
@@ -0,0 +1,320 @@
1
+ // stack-local-config.mjs - reads and judges `environments.<env>.local`, the declaration of a dev environment (runtime docker-compose) that
2
+ // `starci app stack up|down|doctor|prepare|status|logs|verify --env <env>` runs (PROJECT-STACKS-6). It holds names, ports, argv lists and
3
+ // shapes only, never a value: custody keys are references into .starcistacks/<env>/secrets (PROJECT-STACKS-5). The authority for the fields is
4
+ // this module; the same normaliser serves `starci app check` (rule R340) and the verbs, so a block the check passes is a block the verbs run.
5
+ import path from 'node:path';
6
+ import { isPlainObject as plain } from '../../engine/plain-object.mjs';
7
+ import { closedFields, collector, patternField } from './stack-config-kit.mjs';
8
+ export const LOCAL_CONFIG_CODE = 'STACKS_LOCAL_CONFIG';
9
+ const NAME = /^[a-z][a-z0-9-]{0,40}$/u;
10
+ const KEY = /^[A-Z][A-Z0-9_]{0,63}$/u;
11
+ const PROJECT = /^[a-z0-9][a-z0-9_-]{0,62}$/u;
12
+ const NAME_PATTERN = /^[a-z][a-z0-9-]{0,40}$/u;
13
+ const SQL_NAME = /^[a-z][a-z0-9_]{0,62}$/u;
14
+ /** The generators `starci app secret init --env <env>` knows (PROJECT-STACKS-5: dev-only values, created once, never replaced). */
15
+ export const GENERATORS = Object.freeze(['hex', 'base64', 'password', 'uuid', 'rsa-pem', 'ec-pem', 'ed25519-pem']);
16
+ const TOP = ['project', 'ports', 'services', 'database', 'composeEnv', 'grants', 'dirs', 'custody', 'setup', 'seed', 'processes', 'ready', 'verify', 'origins', 'resources'];
17
+ const COMMAND = ['name', 'argv', 'cwd', 'heavy', 'skipIfExists', 'env', 'files'];
18
+ const SEED = [...COMMAND, 'after'];
19
+ const PROCESS = [...COMMAND, 'port', 'health'];
20
+ const text = (value) => typeof value === 'string' && value.trim() !== '';
21
+ /** A relative path inside the app root: no drive, no `..`, forward slashes. */
22
+ function relative(c, at, value) {
23
+ if (typeof value !== 'string' || value === '' || path.posix.isAbsolute(value) || /^[A-Za-z]:/u.test(value) || value.includes('\\') || path.posix.normalize(value).split('/').includes('..')) {
24
+ c.problem(at, 'must be a relative path inside the app root');
25
+ return '.';
26
+ }
27
+ return value;
28
+ }
29
+ function ref(c, at, value) {
30
+ if (!closedFields(c, at, value, ['member', 'key'], 'local'))
31
+ return { member: '', key: '' };
32
+ const entry = value;
33
+ patternField(c, `${at}.member`, entry.member, /^[a-z0-9][a-z0-9-]*$/u);
34
+ patternField(c, `${at}.key`, entry.key, KEY);
35
+ return { member: String(entry.member), key: String(entry.key) };
36
+ }
37
+ function strings(c, at, value, pattern = null) {
38
+ if (value === undefined)
39
+ return [];
40
+ if (!Array.isArray(value) || value.some((item) => !text(item))) {
41
+ c.problem(at, 'must be a list of non-empty strings');
42
+ return [];
43
+ }
44
+ if (pattern)
45
+ value.forEach((item, index) => patternField(c, `${at}[${index}]`, item, pattern));
46
+ return value;
47
+ }
48
+ function command(c, at, value, allowed, names) {
49
+ const empty = { name: '', argv: [], cwd: '.', heavy: 'none', skipIfExists: null, env: {}, files: [], after: 'migrate' };
50
+ if (!closedFields(c, at, value, allowed, 'local'))
51
+ return empty;
52
+ const entry = value;
53
+ const name = String(entry.name ?? '');
54
+ if (patternField(c, `${at}.name`, entry.name, NAME)) {
55
+ if (names.has(name))
56
+ c.problem(`${at}.name`, `${name} is declared twice`);
57
+ names.add(name);
58
+ }
59
+ const argv = strings(c, `${at}.argv`, entry.argv);
60
+ if (argv.length === 0)
61
+ c.problem(`${at}.argv`, 'must hold the command and its arguments (an argv list, never a shell line)');
62
+ const heavy = entry.heavy ?? 'none';
63
+ if (!['install', 'build', 'none'].includes(String(heavy)))
64
+ c.problem(`${at}.heavy`, 'must be install, build or none (the heavy-command governor class of the command)');
65
+ let env = {};
66
+ if (entry.env !== undefined) {
67
+ if (!plain(entry.env))
68
+ c.problem(`${at}.env`, 'must be a mapping of non-secret values');
69
+ else {
70
+ env = entry.env;
71
+ for (const [key, item] of Object.entries(env)) {
72
+ patternField(c, `${at}.env.${key}`, key, KEY);
73
+ if (typeof item !== 'string')
74
+ c.problem(`${at}.env.${key}`, 'must be a string');
75
+ }
76
+ }
77
+ }
78
+ return {
79
+ name, argv, cwd: entry.cwd === undefined ? '.' : relative(c, `${at}.cwd`, entry.cwd), heavy: heavy,
80
+ skipIfExists: entry.skipIfExists === undefined ? null : relative(c, `${at}.skipIfExists`, entry.skipIfExists), env,
81
+ files: strings(c, `${at}.files`, entry.files, KEY), after: afterOf(c, at, entry.after),
82
+ };
83
+ }
84
+ function afterOf(c, at, value) {
85
+ if (value === undefined)
86
+ return 'migrate';
87
+ if (value !== 'migrate' && value !== 'ready')
88
+ c.problem(`${at}.after`, 'must be migrate or ready');
89
+ return value === 'ready' ? 'ready' : 'migrate';
90
+ }
91
+ function probe(c, at, value) {
92
+ const empty = { name: '', url: '', method: 'GET', status: 200, contains: null };
93
+ if (!closedFields(c, at, value, ['name', 'url', 'method', 'status', 'contains'], 'local'))
94
+ return empty;
95
+ const entry = value;
96
+ patternField(c, `${at}.name`, entry.name, NAME);
97
+ const url = String(entry.url ?? '');
98
+ if (!/^http:\/\/(localhost|127\.0\.0\.1|[a-z0-9-]+\.localhost)(:\$\{port\.[a-z][a-z0-9-]*\}|:[0-9]+)?(\/.*)?$/u.test(url))
99
+ c.problem(`${at}.url`, 'must be an http URL on the loopback (localhost, 127.0.0.1 or *.localhost), its port a literal or ${port.<name>}');
100
+ const method = String(entry.method ?? 'GET');
101
+ if (!['GET', 'HEAD', 'POST'].includes(method))
102
+ c.problem(`${at}.method`, 'must be GET, HEAD or POST');
103
+ const status = entry.status === undefined ? 200 : entry.status;
104
+ if (!Number.isInteger(status) || status < 100 || status > 599)
105
+ c.problem(`${at}.status`, 'must be an HTTP status');
106
+ return { name: String(entry.name ?? ''), url, method, status: status, contains: text(entry.contains) ? entry.contains : null };
107
+ }
108
+ function portsOf(c, value) {
109
+ const ports = {};
110
+ if (!plain(value) || Object.keys(value).length === 0) {
111
+ c.problem('ports', 'must name at least one port: the host ports the environment publishes or listens on');
112
+ return ports;
113
+ }
114
+ const seen = new Map();
115
+ for (const [name, port] of Object.entries(value)) {
116
+ patternField(c, `ports.${name}`, name, NAME);
117
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) {
118
+ c.problem(`ports.${name}`, 'must be a port from 1024 to 65535');
119
+ continue;
120
+ }
121
+ if (seen.has(port))
122
+ c.problem(`ports.${name}`, `is the port of ${seen.get(port)} too`);
123
+ seen.set(port, name);
124
+ ports[name] = port;
125
+ }
126
+ return ports;
127
+ }
128
+ function servicesOf(c, value, ports) {
129
+ if (!Array.isArray(value) || value.length === 0) {
130
+ c.problem('services', 'must list the compose services `up` starts, dependencies first');
131
+ return [];
132
+ }
133
+ return value.map((item, index) => {
134
+ const at = `services[${index}]`;
135
+ if (!closedFields(c, at, item, ['name', 'wait', 'port'], 'local'))
136
+ return { name: '', wait: 'started', port: null };
137
+ const entry = item;
138
+ patternField(c, `${at}.name`, entry.name, NAME);
139
+ const wait = String(entry.wait ?? 'healthy');
140
+ if (!['healthy', 'started', 'tcp'].includes(wait))
141
+ c.problem(`${at}.wait`, 'must be healthy (the compose healthcheck), tcp (the port accepts) or started');
142
+ const port = entry.port === undefined ? null : String(entry.port);
143
+ if (wait === 'tcp' && (port === null || !Object.hasOwn(ports, port)))
144
+ c.problem(`${at}.port`, 'must name a declared port when wait is tcp');
145
+ if (port !== null && !Object.hasOwn(ports, port))
146
+ c.problem(`${at}.port`, `${port} is not a declared port`);
147
+ return { name: String(entry.name ?? ''), wait: wait, port };
148
+ });
149
+ }
150
+ function databaseOf(c, value, ports, services, role) {
151
+ if (value === undefined)
152
+ return null;
153
+ if (!closedFields(c, 'database', value, ['service', 'port', 'name', 'role', 'urlEnv', 'urlAs', 'adminUser', 'password', 'adminPassword', 'appRole', 'appPassword'], 'local'))
154
+ return null;
155
+ const entry = value;
156
+ patternField(c, 'database.service', entry.service, NAME);
157
+ if (!services.some((service) => service.name === entry.service))
158
+ c.problem('database.service', 'must be one of the started services');
159
+ if (!Object.hasOwn(ports, String(entry.port)))
160
+ c.problem('database.port', 'must name a declared port');
161
+ const own = entry.role === undefined ? role : String(entry.role);
162
+ patternField(c, 'database.role', own, SQL_NAME);
163
+ const name = entry.name === undefined ? own : String(entry.name);
164
+ patternField(c, 'database.name', name, SQL_NAME);
165
+ patternField(c, 'database.urlEnv', entry.urlEnv, KEY);
166
+ const urlAs = String(entry.urlAs ?? 'file');
167
+ if (!['env', 'file'].includes(urlAs))
168
+ c.problem('database.urlAs', 'must be env (the URL in the process environment) or file (<KEY>_FILE, preferred)');
169
+ const adminUser = String(entry.adminUser ?? 'postgres');
170
+ patternField(c, 'database.adminUser', adminUser, SQL_NAME);
171
+ if (own === adminUser || own === 'postgres')
172
+ c.problem('database.role', 'must be a role of this environment alone, never the superuser and never a role another environment uses');
173
+ const appRole = entry.appRole === undefined ? null : String(entry.appRole);
174
+ if (appRole !== null)
175
+ patternField(c, 'database.appRole', appRole, SQL_NAME);
176
+ if ((appRole === null) !== (entry.appPassword === undefined))
177
+ c.problem('database.appPassword', 'is declared with appRole, both or neither: the app role the migrations create gets its own password per environment');
178
+ if (appRole !== null && (appRole === own || appRole === adminUser))
179
+ c.problem('database.appRole', 'must be a role other than the owner role and the superuser');
180
+ return {
181
+ appRole, appPassword: entry.appPassword === undefined ? null : ref(c, 'database.appPassword', entry.appPassword),
182
+ service: String(entry.service), port: String(entry.port), name, role: own, urlEnv: String(entry.urlEnv), urlAs: urlAs, adminUser,
183
+ password: ref(c, 'database.password', entry.password), adminPassword: ref(c, 'database.adminPassword', entry.adminPassword),
184
+ };
185
+ }
186
+ function custodyOf(c, value) {
187
+ if (value === undefined)
188
+ return [];
189
+ if (!Array.isArray(value)) {
190
+ c.problem('custody', 'must be a list of generated dev values');
191
+ return [];
192
+ }
193
+ return value.map((item, index) => {
194
+ const at = `custody[${index}]`;
195
+ if (!closedFields(c, at, item, ['member', 'key', 'kind', 'bytes', 'publicKey'], 'local'))
196
+ return { member: '', key: '', kind: 'hex', bytes: 32, publicKey: null };
197
+ const entry = item;
198
+ const base = ref(c, at, { member: entry.member, key: entry.key });
199
+ if (!GENERATORS.includes(String(entry.kind)))
200
+ c.problem(`${at}.kind`, `must be one of ${GENERATORS.join(', ')}`);
201
+ const bytes = entry.bytes === undefined ? 32 : entry.bytes;
202
+ if (!Number.isInteger(bytes) || bytes < 8 || bytes > 1024)
203
+ c.problem(`${at}.bytes`, 'must be a whole byte count from 8 to 1024');
204
+ if (entry.publicKey !== undefined) {
205
+ if (entry.kind === 'rsa-pem' || entry.kind === 'ec-pem' || entry.kind === 'ed25519-pem')
206
+ patternField(c, `${at}.publicKey`, entry.publicKey, KEY);
207
+ else
208
+ c.problem(`${at}.publicKey`, 'is only for kind rsa-pem, ec-pem or ed25519-pem');
209
+ }
210
+ return { ...base, kind: String(entry.kind), bytes: bytes, publicKey: text(entry.publicKey) ? entry.publicKey : null };
211
+ });
212
+ }
213
+ function listOf(c, at, value, each) {
214
+ if (value === undefined)
215
+ return [];
216
+ if (!Array.isArray(value)) {
217
+ c.problem(at, 'must be a list');
218
+ return [];
219
+ }
220
+ return value.map((item, index) => each(item, index));
221
+ }
222
+ function referencedPorts(c, block) {
223
+ const known = new Set(Object.keys(block.ports));
224
+ const check = (at, value) => {
225
+ for (const match of value.matchAll(/\$\{port\.([a-z][a-z0-9-]*)\}/gu))
226
+ if (!known.has(match[1]))
227
+ c.problem(at, `names the undeclared port ${match[1]}`);
228
+ };
229
+ for (const item of [...block.ready, ...block.verify])
230
+ check(`url of ${item.name}`, item.url);
231
+ for (const origin of block.origins)
232
+ check(`origin ${origin.name}`, origin.url);
233
+ for (const run of [...block.setup, ...block.seed, ...block.processes]) {
234
+ for (const part of run.argv)
235
+ check(`argv of ${run.name}`, part);
236
+ for (const value of Object.values(run.env))
237
+ check(`env of ${run.name}`, value);
238
+ }
239
+ for (const process of block.processes) {
240
+ if (process.port !== null && !known.has(process.port))
241
+ c.problem(`processes.${process.name}.port`, `${process.port} is not a declared port`);
242
+ if (process.health)
243
+ check(`health of ${process.name}`, process.health.url);
244
+ }
245
+ }
246
+ /** The normalised `local` block, or the problems that stop it. `project` and `env` name the defaults (`<project>-<env>`, the role). */
247
+ export function normalizeLocal(raw, { project, env }) {
248
+ const c = collector();
249
+ if (!closedFields(c, 'local', raw, TOP, 'local'))
250
+ return { config: null, problems: c.problems };
251
+ const entry = raw;
252
+ const compose = String(entry.project ?? `${project}-${env}`).toLowerCase().replaceAll(/[^a-z0-9_-]+/gu, '-');
253
+ patternField(c, 'project', compose, PROJECT);
254
+ const ports = portsOf(c, entry.ports);
255
+ const services = servicesOf(c, entry.services, ports);
256
+ const role = `${project}_${env}`.toLowerCase().replaceAll(/[^a-z0-9]+/gu, '_').replace(/^[^a-z]+/u, 'a');
257
+ const names = new Set();
258
+ const block = {
259
+ project: compose, ports, services,
260
+ database: databaseOf(c, entry.database, ports, services, role),
261
+ composeEnv: listOf(c, 'composeEnv', entry.composeEnv, (item, index) => {
262
+ const at = `composeEnv[${index}]`;
263
+ if (!closedFields(c, at, item, ['name', 'member', 'key'], 'local'))
264
+ return { name: '', member: '', key: '' };
265
+ const value = item;
266
+ patternField(c, `${at}.name`, value.name, KEY);
267
+ return { name: String(value.name), ...ref(c, at, { member: value.member, key: value.key }) };
268
+ }),
269
+ grants: listOf(c, 'grants', entry.grants, (item, index) => {
270
+ const at = `grants[${index}]`;
271
+ if (!closedFields(c, at, item, ['name', 'keys'], 'local'))
272
+ return { name: '', keys: [] };
273
+ const value = item;
274
+ patternField(c, `${at}.name`, value.name, NAME);
275
+ const keys = strings(c, `${at}.keys`, value.keys, KEY);
276
+ if (keys.length === 0)
277
+ c.problem(`${at}.keys`, 'must list the keys the service reads');
278
+ return { name: String(value.name), keys };
279
+ }),
280
+ dirs: strings(c, 'dirs', entry.dirs, NAME_PATTERN),
281
+ custody: custodyOf(c, entry.custody),
282
+ setup: listOf(c, 'setup', entry.setup, (item, index) => command(c, `setup[${index}]`, item, COMMAND, names)),
283
+ seed: listOf(c, 'seed', entry.seed, (item, index) => command(c, `seed[${index}]`, item, SEED, names)),
284
+ processes: listOf(c, 'processes', entry.processes, (item, index) => {
285
+ const at = `processes[${index}]`;
286
+ const base = command(c, at, item, PROCESS, names);
287
+ const value = plain(item) ? item : {};
288
+ return { ...base, port: value.port === undefined ? null : String(value.port), health: value.health === undefined ? null : probe(c, `${at}.health`, { name: base.name || 'health', ...value.health }) };
289
+ }),
290
+ ready: listOf(c, 'ready', entry.ready, (item, index) => probe(c, `ready[${index}]`, item)),
291
+ verify: listOf(c, 'verify', entry.verify, (item, index) => probe(c, `verify[${index}]`, item)),
292
+ origins: listOf(c, 'origins', entry.origins, (item, index) => {
293
+ const at = `origins[${index}]`;
294
+ if (!closedFields(c, at, item, ['name', 'url'], 'local'))
295
+ return { name: '', url: '' };
296
+ const value = item;
297
+ patternField(c, `${at}.name`, value.name, NAME);
298
+ return { name: String(value.name), url: String(value.url ?? '') };
299
+ }),
300
+ resources: { diskGb: 10, ramMb: 4096 },
301
+ };
302
+ if (entry.resources !== undefined && closedFields(c, 'resources', entry.resources, ['diskGb', 'ramMb'], 'local')) {
303
+ const value = entry.resources;
304
+ for (const [key, floor] of [['diskGb', 1], ['ramMb', 512]]) {
305
+ if (value[key] === undefined)
306
+ continue;
307
+ if (!Number.isInteger(value[key]) || value[key] < floor)
308
+ c.problem(`resources.${key}`, `must be a whole number from ${floor}`);
309
+ else
310
+ block.resources[key] = value[key];
311
+ }
312
+ }
313
+ referencedPorts(c, block);
314
+ const generated = new Set(block.custody.flatMap((item) => [`${item.member}/${item.key}`, ...(item.publicKey ? [`${item.member}/${item.publicKey}`] : [])]));
315
+ if (generated.size < block.custody.length)
316
+ c.problem('custody', 'declares a key twice');
317
+ return c.problems.length === 0 ? { config: block, problems: [] } : { config: null, problems: c.problems };
318
+ }
319
+ /** Replaces `${port.<name>}` with the chosen host port. */
320
+ export const expandPorts = (value, ports) => value.replaceAll(/\$\{port\.([a-z][a-z0-9-]*)\}/gu, (_whole, name) => String(ports[name] ?? ''));
@@ -535,7 +535,7 @@ const checkStacksManifest = (manifest, model, { stacksRoot, environment, root, r
535
535
  const lite = repo.edition === 'lite';
536
536
  checkStacksTree({ stacksRoot, manifest, environmentNames, add, lite });
537
537
  checkKubernetesDeclaration(manifest, add);
538
- for (const issue of swarmInventoryIssues({ components: manifest.components, lite }))
538
+ for (const issue of swarmInventoryIssues({ components: manifest.components, lite, swarm: new Set(Object.values(object(manifest.environments) ? manifest.environments : {}).filter((spec) => spec?.runtime === 'docker-swarm').flatMap((spec) => Object.keys(object(spec.components) ? spec.components : {}))) }))
539
539
  add('HFS_STACKS_SHAPE', issue.at, issue.message);
540
540
  checkSelectedEnvironment({ environment, environmentNames, environments, stacksRoot, ids, root, model, manifest, lite, repo, add });
541
541
  };
@@ -0,0 +1,35 @@
1
+ import type { TypeScript } from './ts-ast.mjs';
2
+ /** What the declaration emit cannot write without reading other files, and where: one export (or one part of it) the author must annotate. */
3
+ export interface SurfaceProblem {
4
+ /** The TypeScript diagnostic code (9007: function without a return type, 9010: variable without a type, ...). */
5
+ code: number;
6
+ /** The offset and length of the offending span in the file's text. */
7
+ start: number;
8
+ length: number;
9
+ /** The name of the export (or member) the span sits in, or `default export`. */
10
+ name: string;
11
+ /** What to annotate: `return type`, `variable type`, `parameter type`, `property type`, `accessor type`, `destructured export` or `exported value`. */
12
+ annotate: string;
13
+ /** TypeScript's own reason, one sentence. */
14
+ reason: string;
15
+ }
16
+ /** The declaration emit of one file: its text (the digest of the type surface) and the exports it could not write explicitly. */
17
+ export interface DeclarationSurface {
18
+ outputText: string;
19
+ problems: SurfaceProblem[];
20
+ }
21
+ /** A script file (`.js`, `.mjs`, `.cjs`, `.jsx`): it has no declaration emit and stands for its content. */
22
+ export declare const SCRIPT: RegExp;
23
+ /** A declaration file: it is its own surface. */
24
+ export declare const DECLARATION: RegExp;
25
+ /** A TypeScript source file whose surface the declaration emit writes. */
26
+ export declare const TYPESCRIPT: RegExp;
27
+ /** Whether `file` is a TypeScript source (not a declaration file) the declaration emit reads. */
28
+ export declare const isEmittableSource: (file: string) => boolean;
29
+ /**
30
+ * The declaration emit of one TypeScript source and the exports it could not write explicitly (a type inferred from bodies and other files), or null
31
+ * when the compiler has no `transpileDeclaration` (before TypeScript 5.5). A declaration whose only diagnostics are components returning JSX has the
32
+ * surface its emit shows. `module` is the side's `module` compiler option, which the emit reads. A throw from the compiler propagates: the caller
33
+ * decides what an unreadable file means.
34
+ */
35
+ export declare function declarationSurface(ts: TypeScript, file: string, text: string, module?: number): DeclarationSurface | null;
@@ -0,0 +1,112 @@
1
+ /** A script file (`.js`, `.mjs`, `.cjs`, `.jsx`): it has no declaration emit and stands for its content. */
2
+ export const SCRIPT = /\.(?:[cm]?jsx?)$/;
3
+ /** A declaration file: it is its own surface. */
4
+ export const DECLARATION = /\.d\.[cm]?ts$/;
5
+ /** A TypeScript source file whose surface the declaration emit writes. */
6
+ export const TYPESCRIPT = /\.(?:[cm]?tsx?)$/;
7
+ /** Whether `file` is a TypeScript source (not a declaration file) the declaration emit reads. */
8
+ export const isEmittableSource = (file) => TYPESCRIPT.test(file) && !DECLARATION.test(file);
9
+ /** Whether `expression` is a JSX element, fragment, `null` or `undefined`, or a conditional of those: its type does not depend on any other file. */
10
+ function isJsxValue(ts, expression) {
11
+ if (expression === undefined)
12
+ return false;
13
+ let node = expression;
14
+ while (ts.isParenthesizedExpression(node))
15
+ node = node.expression;
16
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node) || node.kind === ts.SyntaxKind.NullKeyword)
17
+ return true;
18
+ if (ts.isIdentifier(node))
19
+ return node.text === 'undefined';
20
+ return ts.isConditionalExpression(node) && isJsxValue(ts, node.whenTrue) && isJsxValue(ts, node.whenFalse);
21
+ }
22
+ /**
23
+ * Whether the function (or method) the declaration diagnostic at `start` points at has the type `JSX.Element | null | undefined` whatever its body
24
+ * reads: not async, not a generator, and every `return` of its own (and its concise body) a JSX value. A React component written without a return
25
+ * type is the common case; its inferred type is one the JSX namespace of the installed packages gives, which a lockfile change dirties as a whole.
26
+ */
27
+ function returnsOnlyJsx(ts, source, start) {
28
+ let found;
29
+ const find = (node) => {
30
+ if (found !== undefined || start < node.getFullStart() || start >= node.end)
31
+ return;
32
+ const named = (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) && node.name !== undefined && node.name.getStart(source) === start;
33
+ if (named || ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && node.getStart(source) === start)) {
34
+ found = node;
35
+ return;
36
+ }
37
+ ts.forEachChild(node, find);
38
+ };
39
+ find(source);
40
+ const fn = found;
41
+ if (fn === undefined || fn.type !== undefined || fn.body === undefined)
42
+ return false;
43
+ if (fn.asteriskToken !== undefined || (ts.getCombinedModifierFlags(fn) & ts.ModifierFlags.Async) !== 0)
44
+ return false;
45
+ if (!ts.isBlock(fn.body))
46
+ return isJsxValue(ts, fn.body);
47
+ let only = true;
48
+ const visit = (node) => {
49
+ if (!only || ts.isFunctionLike(node) || ts.isClassLike(node))
50
+ return;
51
+ if (ts.isReturnStatement(node) && node.expression !== undefined && !isJsxValue(ts, node.expression))
52
+ only = false;
53
+ ts.forEachChild(node, visit);
54
+ };
55
+ ts.forEachChild(fn.body, visit);
56
+ return only;
57
+ }
58
+ /** What to annotate for each isolated-declaration diagnostic code; any other code asks for the exported value's type. */
59
+ const ANNOTATE = new Map([
60
+ [9007, 'return type'], [9008, 'return type'], [9009, 'accessor type'], [9010, 'variable type'], [9011, 'parameter type'], [9012, 'property type'],
61
+ [9019, 'destructured export'], [9025, 'parameter type'],
62
+ ]);
63
+ /** The declaration kinds whose name labels the export a diagnostic sits in, innermost first. */
64
+ function nameOf(ts, source, start) {
65
+ let innermost = source;
66
+ const descend = (node) => {
67
+ if (start < node.getFullStart() || start >= node.end)
68
+ return;
69
+ innermost = node;
70
+ ts.forEachChild(node, descend);
71
+ };
72
+ ts.forEachChild(source, descend);
73
+ for (let node = innermost; node !== undefined && node !== source; node = node.parent) {
74
+ if (ts.isExportAssignment(node))
75
+ return 'default export';
76
+ if (ts.isBindingElement(node) && ts.isIdentifier(node.name))
77
+ return node.name.text;
78
+ if ((ts.isVariableDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isMethodDeclaration(node) || ts.isPropertyDeclaration(node) || ts.isPropertyAssignment(node) || ts.isGetAccessorDeclaration(node)) && node.name !== undefined && ts.isIdentifier(node.name))
79
+ return node.name.text;
80
+ if (ts.isFunctionDeclaration(node) && (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Default) !== 0)
81
+ return 'default export';
82
+ }
83
+ return 'default export';
84
+ }
85
+ /**
86
+ * The declaration emit of one TypeScript source and the exports it could not write explicitly (a type inferred from bodies and other files), or null
87
+ * when the compiler has no `transpileDeclaration` (before TypeScript 5.5). A declaration whose only diagnostics are components returning JSX has the
88
+ * surface its emit shows. `module` is the side's `module` compiler option, which the emit reads. A throw from the compiler propagates: the caller
89
+ * decides what an unreadable file means.
90
+ */
91
+ export function declarationSurface(ts, file, text, module) {
92
+ const emit = ts.transpileDeclaration;
93
+ if (emit === undefined)
94
+ return null;
95
+ const declared = emit.call(ts, text, { compilerOptions: { jsx: ts.JsxEmit.Preserve, target: ts.ScriptTarget.Latest, ...(module === undefined ? {} : { module }) }, fileName: file, reportDiagnostics: true });
96
+ const diagnostics = (declared.diagnostics ?? []);
97
+ if (diagnostics.length === 0)
98
+ return { outputText: declared.outputText, problems: [] };
99
+ const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, file.endsWith('x') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
100
+ const problems = [];
101
+ for (const diagnostic of diagnostics) {
102
+ const exempt = (diagnostic.code === 9007 || diagnostic.code === 9008) && diagnostic.start !== undefined && returnsOnlyJsx(ts, source, diagnostic.start);
103
+ if (exempt)
104
+ continue;
105
+ const start = diagnostic.start ?? 0;
106
+ problems.push({
107
+ code: diagnostic.code, start, length: diagnostic.length ?? 0, name: nameOf(ts, source, start), annotate: ANNOTATE.get(diagnostic.code) ?? 'exported value',
108
+ reason: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n').split('\n')[0] ?? '',
109
+ });
110
+ }
111
+ return { outputText: declared.outputText, problems };
112
+ }
@@ -10,9 +10,10 @@ export declare function composeIncludePaths(doc: Mapping): {
10
10
  errors: string[];
11
11
  };
12
12
  /** Inventory rules are independent of a selected environment or rendered deployment model. */
13
- export declare function swarmInventoryIssues({ components, lite }: {
13
+ export declare function swarmInventoryIssues({ components, lite, swarm }: {
14
14
  components: unknown;
15
15
  lite?: boolean;
16
+ swarm?: ReadonlySet<string>;
16
17
  }): SwarmIssue[];
17
18
  /** Validate the same deployment model in HFS and the deployment evidence gate. */
18
19
  export declare function swarmModelIssues({ model, components, bindings, lite }: {
@@ -49,12 +49,12 @@ function jobDeclarationIssues(id, component, components, add) {
49
49
  add(`components.${id}.schedule`, 'replicated-job must declare its cron schedule');
50
50
  }
51
51
  /** Inventory rules are independent of a selected environment or rendered deployment model. */
52
- export function swarmInventoryIssues({ components, lite = false }) {
52
+ export function swarmInventoryIssues({ components, lite = false, swarm }) {
53
53
  const errors = [], catalog = mapping(components);
54
54
  const add = (at, message) => { errors.push({ at, message }); };
55
55
  for (const [id, entry] of Object.entries(catalog)) {
56
56
  const component = mapping(entry);
57
- if (lite && present(component.image) && BACKING_IMAGE.test(repositoryOf(component.image)))
57
+ if (lite && (swarm === undefined || swarm.has(id)) && present(component.image) && BACKING_IMAGE.test(repositoryOf(component.image)))
58
58
  add(`components.${id}.image`, 'lite runs no backing image; its db component runs the image .starcistacks/images/db/Dockerfile builds, storage is the external S3 endpoint, identity the API');
59
59
  jobDeclarationIssues(id, component, catalog, add);
60
60
  }
@@ -0,0 +1,16 @@
1
+ /** What a module verb answers: every app-relative path it wrote, rewrote or deleted. */
2
+ export interface ModuleResult {
3
+ readonly created: string[];
4
+ readonly updated: string[];
5
+ readonly removed: string[];
6
+ }
7
+ /** Adds the module package `name`, registers it and re-renders the registry. Refused when the app has no agent SDK package or the module exists. */
8
+ export declare function addModule({ root, name }: {
9
+ root: string;
10
+ name: string;
11
+ }): ModuleResult;
12
+ /** Removes the module package `name`, its registrations and its entry in the registry; the last module takes the registry package with it. */
13
+ export declare function removeModule({ root, name }: {
14
+ root: string;
15
+ name: string;
16
+ }): ModuleResult;