@geekmidas/cli 10.0.0-alpha.11 → 10.0.0-alpha.13

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 (242) hide show
  1. package/README.md +13 -40
  2. package/dist/{HostingerProvider-BcyFRbqV.mjs → HostingerProvider-BY26bWwB.mjs} +20 -21
  3. package/dist/HostingerProvider-BY26bWwB.mjs.map +1 -0
  4. package/dist/{HostingerProvider-BOvsgATO.cjs → HostingerProvider-BZFGYYVA.cjs} +21 -22
  5. package/dist/HostingerProvider-BZFGYYVA.cjs.map +1 -0
  6. package/dist/{LocalStateProvider-yjXZXZDM.cjs → LocalStateProvider-7HR6SS2t.cjs} +11 -6
  7. package/dist/LocalStateProvider-7HR6SS2t.cjs.map +1 -0
  8. package/dist/{LocalStateProvider-Bq-0C0sg.mjs → LocalStateProvider-D7QyJoLM.mjs} +10 -4
  9. package/dist/LocalStateProvider-D7QyJoLM.mjs.map +1 -0
  10. package/dist/{Route53Provider-C0xmer8U.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
  11. package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
  12. package/dist/{Route53Provider-CH9XH7bz.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
  13. package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
  14. package/dist/{SSMStateProvider-DOZifZfE.mjs → SSMStateProvider-B0Hu1MKZ.mjs} +13 -4
  15. package/dist/SSMStateProvider-B0Hu1MKZ.mjs.map +1 -0
  16. package/dist/{SSMStateProvider-CXtNgPfy.cjs → SSMStateProvider-VrQ-yeqd.cjs} +17 -9
  17. package/dist/SSMStateProvider-VrQ-yeqd.cjs.map +1 -0
  18. package/dist/{backup-provisioner-cE2PuCTb.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
  19. package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
  20. package/dist/{backup-provisioner-DXXg6FIL.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
  21. package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
  22. package/dist/{bundler-CaTGtiaO.cjs → bundler-BeNDZdHQ.cjs} +10 -28
  23. package/dist/bundler-BeNDZdHQ.cjs.map +1 -0
  24. package/dist/{bundler-S3ek7ea-.mjs → bundler-BodTL4kf.mjs} +9 -26
  25. package/dist/bundler-BodTL4kf.mjs.map +1 -0
  26. package/dist/{config-HhDpJmLO.cjs → config-Q1rvKHvw.cjs} +74 -69
  27. package/dist/config-Q1rvKHvw.cjs.map +1 -0
  28. package/dist/{config-B0L2tvvC.mjs → config-pqGD3hF9.mjs} +26 -14
  29. package/dist/config-pqGD3hF9.mjs.map +1 -0
  30. package/dist/config.cjs +4 -5
  31. package/dist/config.d.cts +14 -17
  32. package/dist/config.d.cts.map +1 -1
  33. package/dist/config.d.mts +14 -17
  34. package/dist/config.d.mts.map +1 -1
  35. package/dist/config.mjs +3 -5
  36. package/dist/{credentials-CZrys9C5.mjs → credentials-BekkKtY_.mjs} +1331 -1100
  37. package/dist/credentials-BekkKtY_.mjs.map +1 -0
  38. package/dist/{credentials-B2Pyh3bJ.cjs → credentials-Q6F0uqJq.cjs} +1468 -1232
  39. package/dist/credentials-Q6F0uqJq.cjs.map +1 -0
  40. package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
  41. package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
  42. package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
  43. package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
  44. package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
  45. package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
  46. package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
  47. package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
  48. package/dist/deploy/sniffer-hooks.cjs +13 -7
  49. package/dist/deploy/sniffer-hooks.cjs.map +1 -1
  50. package/dist/deploy/sniffer-hooks.d.cts +1 -2
  51. package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
  52. package/dist/deploy/sniffer-hooks.d.mts +1 -2
  53. package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
  54. package/dist/deploy/sniffer-hooks.mjs +9 -2
  55. package/dist/deploy/sniffer-hooks.mjs.map +1 -1
  56. package/dist/deploy/sniffer-loader.cjs +16 -7
  57. package/dist/deploy/sniffer-loader.cjs.map +1 -1
  58. package/dist/deploy/sniffer-loader.d.cts +1 -1
  59. package/dist/deploy/sniffer-loader.d.mts +1 -1
  60. package/dist/deploy/sniffer-loader.mjs +13 -2
  61. package/dist/deploy/sniffer-loader.mjs.map +1 -1
  62. package/dist/deploy/sniffer-routes-worker.cjs +11 -11
  63. package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
  64. package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
  65. package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
  66. package/dist/deploy/sniffer-routes-worker.mjs +2 -2
  67. package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
  68. package/dist/deploy/sniffer-worker.cjs +15 -6
  69. package/dist/deploy/sniffer-worker.cjs.map +1 -1
  70. package/dist/deploy/sniffer-worker.d.cts +1 -3
  71. package/dist/deploy/sniffer-worker.d.cts.map +1 -1
  72. package/dist/deploy/sniffer-worker.d.mts +1 -3
  73. package/dist/deploy/sniffer-worker.d.mts.map +1 -1
  74. package/dist/deploy/sniffer-worker.mjs +15 -4
  75. package/dist/deploy/sniffer-worker.mjs.map +1 -1
  76. package/dist/{discover-DTmFeloL.cjs → discover-BERGFg8v.cjs} +81 -64
  77. package/dist/discover-BERGFg8v.cjs.map +1 -0
  78. package/dist/{discover-CAlFgYec.mjs → discover-CK-n3w6b.mjs} +35 -20
  79. package/dist/discover-CK-n3w6b.mjs.map +1 -0
  80. package/dist/{emit-CUD1BWls.cjs → emit-BdoPFjZf.cjs} +53 -36
  81. package/dist/emit-BdoPFjZf.cjs.map +1 -0
  82. package/dist/{emit-CIlvnDvV.mjs → emit-Bzq7JsMP.mjs} +24 -6
  83. package/dist/emit-Bzq7JsMP.mjs.map +1 -0
  84. package/dist/{fullstack-secrets-METaRLS1.mjs → fullstack-secrets-BET8gqwK.mjs} +9 -15
  85. package/dist/fullstack-secrets-BET8gqwK.mjs.map +1 -0
  86. package/dist/{fullstack-secrets-D6MyHhQP.cjs → fullstack-secrets-DXsWO-5m.cjs} +19 -38
  87. package/dist/fullstack-secrets-DXsWO-5m.cjs.map +1 -0
  88. package/dist/{generator-VE2JrFlY.mjs → generator-LfZU7NlU.mjs} +8 -14
  89. package/dist/generator-LfZU7NlU.mjs.map +1 -0
  90. package/dist/{generator-CNzBw7g5.cjs → generator-Wdfp0jCS.cjs} +38 -45
  91. package/dist/generator-Wdfp0jCS.cjs.map +1 -0
  92. package/dist/{github-BrvaPkFf.cjs → github-BfflNPVe.cjs} +36 -30
  93. package/dist/{github-BrvaPkFf.cjs.map → github-BfflNPVe.cjs.map} +1 -1
  94. package/dist/{github-CrpsXSfV.mjs → github-CRwnxKmD.mjs} +27 -20
  95. package/dist/github-CRwnxKmD.mjs.map +1 -0
  96. package/dist/{index-CR5urhWX.d.cts → index-Cd-qeOam.d.mts} +3 -12
  97. package/dist/index-Cd-qeOam.d.mts.map +1 -0
  98. package/dist/{index-Ayxg_DQY.d.mts → index-D-iAVHx-.d.cts} +3 -12
  99. package/dist/index-D-iAVHx-.d.cts.map +1 -0
  100. package/dist/index.cjs +3320 -2961
  101. package/dist/index.cjs.map +1 -1
  102. package/dist/index.d.cts +1 -1
  103. package/dist/index.d.mts +1 -1
  104. package/dist/index.mjs +3200 -2858
  105. package/dist/index.mjs.map +1 -1
  106. package/dist/{keystore-TxeYM7an.mjs → keystore-B4PRlbgE.mjs} +5 -7
  107. package/dist/keystore-B4PRlbgE.mjs.map +1 -0
  108. package/dist/{keystore-C9rint2E.cjs → keystore-DgGRj2Yy.cjs} +23 -26
  109. package/dist/keystore-DgGRj2Yy.cjs.map +1 -0
  110. package/dist/{openapi-DaKlTKxz.cjs → openapi-Dcbme-Us.cjs} +134 -171
  111. package/dist/openapi-Dcbme-Us.cjs.map +1 -0
  112. package/dist/openapi-react-query.cjs +156 -2
  113. package/dist/openapi-react-query.cjs.map +1 -0
  114. package/dist/openapi-react-query.d.cts +1 -3
  115. package/dist/openapi-react-query.d.cts.map +1 -1
  116. package/dist/openapi-react-query.d.mts +1 -3
  117. package/dist/openapi-react-query.d.mts.map +1 -1
  118. package/dist/openapi-react-query.mjs +156 -2
  119. package/dist/openapi-react-query.mjs.map +1 -0
  120. package/dist/{openapi-y1d7Kalt.mjs → openapi-vy4JzhUv.mjs} +76 -106
  121. package/dist/openapi-vy4JzhUv.mjs.map +1 -0
  122. package/dist/openapi.cjs +3 -6
  123. package/dist/openapi.d.cts +8 -11
  124. package/dist/openapi.d.cts.map +1 -1
  125. package/dist/openapi.d.mts +8 -11
  126. package/dist/openapi.d.mts.map +1 -1
  127. package/dist/openapi.mjs +2 -6
  128. package/dist/reconcile/public.cjs +4 -4
  129. package/dist/reconcile/public.d.cts +8 -26
  130. package/dist/reconcile/public.d.cts.map +1 -1
  131. package/dist/reconcile/public.d.mts +8 -26
  132. package/dist/reconcile/public.d.mts.map +1 -1
  133. package/dist/reconcile/public.mjs +3 -4
  134. package/dist/{reconcile-CYMhFSYy.cjs → reconcile-Dd2iyILf.cjs} +4 -7
  135. package/dist/reconcile-Dd2iyILf.cjs.map +1 -0
  136. package/dist/{reconcile-C13ERt1U.mjs → reconcile-DhQ00DpU.mjs} +4 -7
  137. package/dist/reconcile-DhQ00DpU.mjs.map +1 -0
  138. package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
  139. package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
  140. package/dist/{storage-CSNplaJn.cjs → storage-C-30BD1e.cjs} +59 -69
  141. package/dist/storage-C-30BD1e.cjs.map +1 -0
  142. package/dist/{storage-B020InPW.mjs → storage-P_b3w7uj.mjs} +22 -13
  143. package/dist/storage-P_b3w7uj.mjs.map +1 -0
  144. package/dist/{sync-CzHXiR9c.mjs → sync-B3xxXtJT.mjs} +18 -6
  145. package/dist/sync-B3xxXtJT.mjs.map +1 -0
  146. package/dist/{sync-0ILCb-1S.cjs → sync-D7uTIIEi.cjs} +43 -25
  147. package/dist/sync-D7uTIIEi.cjs.map +1 -0
  148. package/dist/types-BGo5TaNE.d.mts +6164 -0
  149. package/dist/types-BGo5TaNE.d.mts.map +1 -0
  150. package/dist/types-C5ZNpUad.d.cts +6165 -0
  151. package/dist/types-C5ZNpUad.d.cts.map +1 -0
  152. package/dist/workspace/index.cjs +3 -3
  153. package/dist/workspace/index.d.cts +3 -3
  154. package/dist/workspace/index.d.mts +3 -3
  155. package/dist/workspace/index.mjs +2 -3
  156. package/dist/{workspace-DaEX7FEg.mjs → workspace-CgwmXEtc.mjs} +725 -871
  157. package/dist/workspace-CgwmXEtc.mjs.map +1 -0
  158. package/dist/{workspace-C-a3G_U0.cjs → workspace-mFpI_ky_.cjs} +894 -1041
  159. package/dist/workspace-mFpI_ky_.cjs.map +1 -0
  160. package/package.json +14 -14
  161. package/dist/CachedStateProvider-BEBl2wQT.cjs +0 -60
  162. package/dist/CachedStateProvider-BEBl2wQT.cjs.map +0 -1
  163. package/dist/CachedStateProvider-C-8wtlto.mjs +0 -3
  164. package/dist/CachedStateProvider-Co4n5Wvs.mjs +0 -54
  165. package/dist/CachedStateProvider-Co4n5Wvs.mjs.map +0 -1
  166. package/dist/CachedStateProvider-DqhGpzsm.cjs +0 -3
  167. package/dist/HostingerProvider-BOvsgATO.cjs.map +0 -1
  168. package/dist/HostingerProvider-BcyFRbqV.mjs.map +0 -1
  169. package/dist/LocalStateProvider-Bq-0C0sg.mjs.map +0 -1
  170. package/dist/LocalStateProvider-yjXZXZDM.cjs.map +0 -1
  171. package/dist/Route53Provider-C0xmer8U.mjs.map +0 -1
  172. package/dist/Route53Provider-CH9XH7bz.cjs.map +0 -1
  173. package/dist/SSMStateProvider-CXtNgPfy.cjs.map +0 -1
  174. package/dist/SSMStateProvider-DOZifZfE.mjs.map +0 -1
  175. package/dist/backup-provisioner-DXXg6FIL.mjs.map +0 -1
  176. package/dist/backup-provisioner-cE2PuCTb.cjs.map +0 -1
  177. package/dist/bundler-CaTGtiaO.cjs.map +0 -1
  178. package/dist/bundler-S3ek7ea-.mjs.map +0 -1
  179. package/dist/chunk-Duj1WY3L.mjs +0 -7
  180. package/dist/config-B0L2tvvC.mjs.map +0 -1
  181. package/dist/config-HhDpJmLO.cjs.map +0 -1
  182. package/dist/credentials-B2Pyh3bJ.cjs.map +0 -1
  183. package/dist/credentials-B5zRHdAl.mjs +0 -141
  184. package/dist/credentials-B5zRHdAl.mjs.map +0 -1
  185. package/dist/credentials-BapzjyV5.cjs +0 -195
  186. package/dist/credentials-BapzjyV5.cjs.map +0 -1
  187. package/dist/credentials-CZrys9C5.mjs.map +0 -1
  188. package/dist/discover-CAlFgYec.mjs.map +0 -1
  189. package/dist/discover-DTmFeloL.cjs.map +0 -1
  190. package/dist/dokploy-api-5cK2InBm.cjs +0 -3
  191. package/dist/dokploy-api-D2Ny4Jrc.mjs +0 -3
  192. package/dist/dokploy-api-pdaBryoY.mjs +0 -706
  193. package/dist/dokploy-api-pdaBryoY.mjs.map +0 -1
  194. package/dist/dokploy-api-zqKe9lHH.cjs +0 -718
  195. package/dist/dokploy-api-zqKe9lHH.cjs.map +0 -1
  196. package/dist/emit-CIlvnDvV.mjs.map +0 -1
  197. package/dist/emit-CUD1BWls.cjs.map +0 -1
  198. package/dist/encryption-D2pL3tHH.cjs +0 -54
  199. package/dist/encryption-D2pL3tHH.cjs.map +0 -1
  200. package/dist/encryption-YDsXXi6w.mjs +0 -3
  201. package/dist/encryption-_4zwjOAR.mjs +0 -42
  202. package/dist/encryption-_4zwjOAR.mjs.map +0 -1
  203. package/dist/encryption-sQ2Oxx0d.cjs +0 -4
  204. package/dist/fullstack-secrets-D6MyHhQP.cjs.map +0 -1
  205. package/dist/fullstack-secrets-METaRLS1.mjs.map +0 -1
  206. package/dist/generator-CNzBw7g5.cjs.map +0 -1
  207. package/dist/generator-VE2JrFlY.mjs.map +0 -1
  208. package/dist/github-CrpsXSfV.mjs.map +0 -1
  209. package/dist/index-Ayxg_DQY.d.mts.map +0 -1
  210. package/dist/index-CR5urhWX.d.cts.map +0 -1
  211. package/dist/keystore-C9rint2E.cjs.map +0 -1
  212. package/dist/keystore-TxeYM7an.mjs.map +0 -1
  213. package/dist/openapi-DaKlTKxz.cjs.map +0 -1
  214. package/dist/openapi-react-query-BKH334Ug.cjs +0 -173
  215. package/dist/openapi-react-query-BKH334Ug.cjs.map +0 -1
  216. package/dist/openapi-react-query-DoR5CmzQ.mjs +0 -167
  217. package/dist/openapi-react-query-DoR5CmzQ.mjs.map +0 -1
  218. package/dist/openapi-y1d7Kalt.mjs.map +0 -1
  219. package/dist/reconcile-C13ERt1U.mjs.map +0 -1
  220. package/dist/reconcile-CYMhFSYy.cjs.map +0 -1
  221. package/dist/setup-BWgMs4gg.mjs +0 -12
  222. package/dist/setup-DOv92pIu.cjs +0 -473
  223. package/dist/setup-DOv92pIu.cjs.map +0 -1
  224. package/dist/setup-DQo_h_yK.mjs +0 -443
  225. package/dist/setup-DQo_h_yK.mjs.map +0 -1
  226. package/dist/setup-lCx_DcUo.cjs +0 -12
  227. package/dist/storage-B020InPW.mjs.map +0 -1
  228. package/dist/storage-CSNplaJn.cjs.map +0 -1
  229. package/dist/storage-DGBzMLZa.mjs +0 -4
  230. package/dist/storage-DlVol_oj.cjs +0 -8
  231. package/dist/sync-0ILCb-1S.cjs.map +0 -1
  232. package/dist/sync-CzHXiR9c.mjs.map +0 -1
  233. package/dist/sync-DYENfOt4.cjs +0 -4
  234. package/dist/sync-HAau6h_R.mjs +0 -3
  235. package/dist/types-CL7jPFM2.d.mts +0 -2220
  236. package/dist/types-CL7jPFM2.d.mts.map +0 -1
  237. package/dist/types-Y3fKQSAK.d.cts +0 -2220
  238. package/dist/types-Y3fKQSAK.d.cts.map +0 -1
  239. package/dist/workspace-C-a3G_U0.cjs.map +0 -1
  240. package/dist/workspace-DQxxs7Jk.cjs +0 -10
  241. package/dist/workspace-DaEX7FEg.mjs.map +0 -1
  242. package/dist/workspace-DbI580qS.mjs +0 -9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit-BdoPFjZf.cjs","names":["provideKey","canonicalId","join","mkdir","dirname","writeFile"],"sources":["../src/reconcile/emit.ts"],"sourcesContent":["/**\n * The manifest, written out as a module.\n *\n * Discovery imports the application's own code — that is how it finds\n * constructs — so anything calling it evaluates the whole runtime graph: React\n * email templates, database clients, modules that read env at import time. That\n * is fine inside `gkm dev`, which is already running the app. It is not fine\n * inside a deploy config, where it fails on the first thing the deploy toolchain\n * does not expect and would keep failing on the next.\n *\n * So discovery happens once, in the build, and everything downstream reads what\n * it wrote. The same split `RestApi` makes by naming a routes glob rather than\n * importing route modules.\n *\n * A module rather than JSON, because a manifest is not just data to this\n * codebase — `as const satisfies ConstructManifest` is what keeps every id,\n * kind and provided key a *literal*, so `IdsOf`, `ProvidedKeys` and the rest\n * can select out of it. `JSON.parse` returns `any` and gives all of that up.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type {\n\tConstructManifest,\n\tCronInfo,\n\tDeclaration,\n\tDependency,\n\tFunctionInfo,\n\tQueueInfo,\n\tRestApiEndpoint,\n\tRouteInfo,\n\tSubscriberInfo,\n} from '@geekmidas/manifest';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport type { CacheBackend, EmailBackend } from '../types.js';\n\n/** Where the build writes it, relative to the app root. */\nexport const MANIFEST_PATH = '.gkm/manifest.ts';\n\n/**\n * Fold the build's generated routes into the surface that serves them.\n *\n * The routes already exist: `gkm build` generates one handler per endpoint and\n * records its path, method and environment. What was missing is that the\n * *construct* manifest never learned about them, so a `rest-api` node said\n * `endpoints: []` while five handlers sat in `.gkm/`. Two documents describing\n * the same routes, one of them wrong.\n *\n * Merging here rather than making the construct name a glob or import its\n * endpoints: the build is the thing that already walked the filesystem and\n * already knows where it wrote each handler. Asking the declaration to restate\n * any of that is how the two come to disagree.\n *\n * Attribution is by exclusion — routes go to the surface that declared none of\n * its own. A surface with static endpoints (an auth server's single wildcard)\n * keeps them. That is exact while an app has one API of its own, and needs a\n * real answer the day it has two; the alternative today would be inventing one\n * before anything needs it.\n *\n * **Only from a provider that generates one handler per route.** The `server`\n * provider generates a single catch-all — `ALL *` pointing at the Hono app that\n * routes in-process — which is a true description of that build and a false\n * description of the application. Merging it wrote `endpoints: [{ method:\n * 'ALL', path: '*' }]` into a manifest for an app with five routes, and made the\n * document's contents depend on which `--provider` ran last. A manifest must not\n * do that.\n */\nexport function withRoutes(\n\tmanifest: ConstructManifest,\n\troutes: readonly RouteInfo[],\n\toptions: { perRoute: boolean },\n): ConstructManifest {\n\tif (routes.length === 0 || !options.perRoute) return manifest;\n\n\tconst target = Object.entries(manifest).find(\n\t\t([, declaration]) =>\n\t\t\tdeclaration.kind === 'rest-api' && declaration.endpoints.length === 0,\n\t);\n\n\tif (!target) return manifest;\n\n\tconst [id, declaration] = target;\n\tif (declaration.kind !== 'rest-api') return manifest;\n\n\treturn {\n\t\t...manifest,\n\t\t[id]: {\n\t\t\t...declaration,\n\t\t\tendpoints: routes.map((route) => asEndpoint(manifest, id, route)),\n\t\t},\n\t};\n}\n\n/**\n * Fold in the compute the build generated: functions, crons, queue workers and\n * topic subscribers.\n *\n * All four are named by their *export*, the same way an endpoint's handler is —\n * `CronGenerator` uses the export key as the name — so none of them can declare\n * themselves and all of them are the build's to add. That is why a `Function`\n * has no `declare()` and never will.\n *\n * A worker and a subscriber **nest** inside the queue or topic that triggers\n * them, because position carries the trigger. A function and a cron are\n * top-level, because nothing triggers them but a caller or a clock.\n */\nexport function withCompute(\n\tmanifest: ConstructManifest,\n\tcompute: {\n\t\tfunctions?: readonly FunctionInfo[];\n\t\tcrons?: readonly CronInfo[];\n\t\tqueues?: readonly QueueInfo[];\n\t\tsubscribers?: readonly SubscriberInfo[];\n\t},\n): ConstructManifest {\n\tconst next: Record<string, Declaration> = { ...manifest };\n\n\tfor (const fn of compute.functions ?? []) {\n\t\tnext[fn.name] = {\n\t\t\tkind: 'function',\n\t\t\tid: fn.name,\n\t\t\thandler: fn.handler,\n\t\t\tdependencies: edgesTo(manifest, fn.dependencies),\n\t\t\tprovides: [provideKey(fn.name, 'url')],\n\t\t};\n\t}\n\n\tfor (const cron of compute.crons ?? []) {\n\t\tnext[cron.name] = {\n\t\t\tkind: 'cron',\n\t\t\tid: cron.name,\n\t\t\thandler: cron.handler,\n\t\t\tschedule: cron.schedule,\n\t\t\tdependencies: edgesTo(manifest, cron.dependencies),\n\t\t};\n\t}\n\n\t// A worker drains exactly one queue, so it is matched by the queue it names\n\t// rather than by anything about the handler.\n\tfor (const worker of compute.queues ?? []) {\n\t\tconst queue = next[canonicalId(worker.name)];\n\t\tif (queue?.kind !== 'queue') continue;\n\n\t\tnext[queue.id] = {\n\t\t\t...queue,\n\t\t\tworker: {\n\t\t\t\tid: `${queue.id}Worker`,\n\t\t\t\thandler: worker.handler,\n\t\t\t\tdependencies: edgesTo(manifest, worker.dependencies),\n\t\t\t},\n\t\t};\n\t}\n\n\tfor (const subscriber of compute.subscribers ?? []) {\n\t\tconst topicId = subscriber.topic && canonicalId(subscriber.topic);\n\t\tconst topic = topicId ? next[topicId] : undefined;\n\t\tif (topic?.kind !== 'topic') continue;\n\n\t\tnext[topic.id] = {\n\t\t\t...topic,\n\t\t\tsubscribers: [\n\t\t\t\t...topic.subscribers,\n\t\t\t\t{\n\t\t\t\t\tid: subscriber.name,\n\t\t\t\t\thandler: subscriber.handler,\n\t\t\t\t\tevents: subscriber.subscribedEvents,\n\t\t\t\t\tdependencies: edgesTo(manifest, subscriber.dependencies),\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\treturn next;\n}\n\n/**\n * Construct ids as manifest edges.\n *\n * A `Dependency` carries the target's kind as well as its id, and the kind is\n * read off the manifest rather than recorded beside the edge — the same rule\n * `edgeTo` follows, for the same reason: a hand-written kind is one fact stated\n * twice, and the copy is what goes stale.\n *\n * An id that names nothing is dropped rather than thrown on. A handler can\n * depend on a construct that declares no node of its own — a lifted `Service`\n * is exactly that — and a build is the wrong place to refuse it; what a target\n * needs from this list is the subset it can actually grant.\n */\nfunction edgesTo(\n\tmanifest: ConstructManifest,\n\tids: readonly string[] = [],\n): Dependency[] {\n\treturn ids.flatMap((target) => {\n\t\tconst declaration = manifest[target];\n\n\t\treturn declaration ? [{ target, kind: declaration.kind }] : [];\n\t});\n}\n\n/** One generated route as the manifest's shape. */\nfunction asEndpoint(\n\tmanifest: ConstructManifest,\n\tsurface: string,\n\troute: RouteInfo,\n): RestApiEndpoint {\n\treturn {\n\t\t// The handler path is already unique per route and is what a target needs\n\t\t// to point a function at, so it is also the natural id.\n\t\tid: `${surface}${route.method}${route.path}`,\n\t\thandler: route.handler,\n\t\tmethod: route.method,\n\t\tpath: route.path,\n\t\t// The edges the handler declared, which is what a target reads to grant\n\t\t// this one function access to exactly those constructs. `environment` is\n\t\t// the same edges' shadow — the keys read — and cannot be turned back into\n\t\t// them, which is why the ids are carried rather than inferred.\n\t\tdependencies: edgesTo(manifest, route.dependencies),\n\t\t...(route.authorizer ? { authorizer: route.authorizer } : {}),\n\t};\n}\n\n/**\n * The manifest as TypeScript source.\n *\n * Pure, so what gets written can be asserted without touching a filesystem.\n */\nexport function manifestModule(\n\tmanifest: ConstructManifest,\n\tbackends: Backends = {},\n): string {\n\treturn `${HEADER}\nexport const manifest = ${serialise(manifest)} as const satisfies ConstructManifest;\n\n/**\n * Where the backends that are *config* rather than declaration resolved to.\n *\n * Recorded because they were being answered twice: once by the build, which\n * registers exactly one cache driver, and once by a deploy config, which told\n * the provisioner something else. The result was a deployed URL the running code\n * had no driver for — two documents disagreeing about one fact, which is what\n * this manifest exists to stop.\n */\nexport const backends = ${serialise(backends)} as const;\n\n// Derived types\nexport type Ids = IdsOf<typeof manifest>;\nexport type Construct<Id extends Ids> = DeclarationOf<typeof manifest, Id>;\nexport type Kind = Construct<Ids>['kind'];\n\n// Useful union types\nexport type ProvidedKeys = AllProvidedKeys<typeof manifest>;\nexport type Surfaces = IdsOfKind<typeof manifest, 'rest-api'>;\nexport type CacheBackend = (typeof backends)['cache'];\nexport type EmailBackend = (typeof backends)['email'];\n`;\n}\n\n/** Write it where the deploy config expects to import it from. */\nexport async function writeManifestModule(\n\tmanifest: ConstructManifest,\n\tappRoot: string,\n\tbackends: Backends = {},\n): Promise<string> {\n\tconst path = join(appRoot, MANIFEST_PATH);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, manifestModule(manifest, backends), 'utf8');\n\n\treturn path;\n}\n\n/** The choices that are deployment config rather than declaration. */\nexport interface Backends {\n\tcache?: CacheBackend;\n\temail?: EmailBackend;\n}\n\nconst HEADER = `// Generated by \\`gkm build\\`. Do not edit.\n//\n// Discovery imports application code, so it runs once here rather than in every\n// consumer — a deploy config that discovered for itself would evaluate the whole\n// runtime graph inside its own toolchain. This is the result, as literals.\n\nimport type {\n\tAllProvidedKeys,\n\tConstructManifest,\n\tDeclarationOf,\n\tIdsOf,\n\tIdsOfKind,\n} from '@geekmidas/manifest';\n`;\n\n/**\n * JSON, re-indented with tabs and with keys left unquoted where they can be.\n *\n * Cosmetic, and worth it: this file is read by people trying to work out what\n * their app declares, and a wall of quoted keys reads as output rather than as\n * something anyone is expected to understand.\n */\nfunction serialise(value: object): string {\n\treturn JSON.stringify(value, null, '\\t')\n\t\t.replace(/^(\\t*)\"([A-Za-z_$][\\w$]*)\":/gm, '$1$2:')\n\t\t.replace(/\"/g, \"'\");\n}\n"],"mappings":"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"}
@@ -1,8 +1,26 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
1
2
  import { dirname, join } from "node:path";
2
3
  import { canonicalId, provideKey } from "@geekmidas/manifest";
3
- import { mkdir, writeFile } from "node:fs/promises";
4
-
5
4
  //#region src/reconcile/emit.ts
5
+ /**
6
+ * The manifest, written out as a module.
7
+ *
8
+ * Discovery imports the application's own code — that is how it finds
9
+ * constructs — so anything calling it evaluates the whole runtime graph: React
10
+ * email templates, database clients, modules that read env at import time. That
11
+ * is fine inside `gkm dev`, which is already running the app. It is not fine
12
+ * inside a deploy config, where it fails on the first thing the deploy toolchain
13
+ * does not expect and would keep failing on the next.
14
+ *
15
+ * So discovery happens once, in the build, and everything downstream reads what
16
+ * it wrote. The same split `RestApi` makes by naming a routes glob rather than
17
+ * importing route modules.
18
+ *
19
+ * A module rather than JSON, because a manifest is not just data to this
20
+ * codebase — `as const satisfies ConstructManifest` is what keeps every id,
21
+ * kind and provided key a *literal*, so `IdsOf`, `ProvidedKeys` and the rest
22
+ * can select out of it. `JSON.parse` returns `any` and gives all of that up.
23
+ */
6
24
  /** Where the build writes it, relative to the app root. */
7
25
  const MANIFEST_PATH = ".gkm/manifest.ts";
8
26
  /**
@@ -35,7 +53,7 @@ const MANIFEST_PATH = ".gkm/manifest.ts";
35
53
  */
36
54
  function withRoutes(manifest, routes, options) {
37
55
  if (routes.length === 0 || !options.perRoute) return manifest;
38
- const target = Object.entries(manifest).find(([, declaration$1]) => declaration$1.kind === "rest-api" && declaration$1.endpoints.length === 0);
56
+ const target = Object.entries(manifest).find(([, declaration]) => declaration.kind === "rest-api" && declaration.endpoints.length === 0);
39
57
  if (!target) return manifest;
40
58
  const [id, declaration] = target;
41
59
  if (declaration.kind !== "rest-api") return manifest;
@@ -200,7 +218,7 @@ import type {
200
218
  function serialise(value) {
201
219
  return JSON.stringify(value, null, " ").replace(/^(\t*)"([A-Za-z_$][\w$]*)":/gm, "$1$2:").replace(/"/g, "'");
202
220
  }
203
-
204
221
  //#endregion
205
- export { MANIFEST_PATH, manifestModule, withCompute, withRoutes, writeManifestModule };
206
- //# sourceMappingURL=emit-CIlvnDvV.mjs.map
222
+ export { writeManifestModule as a, withRoutes as i, manifestModule as n, withCompute as r, MANIFEST_PATH as t };
223
+
224
+ //# sourceMappingURL=emit-Bzq7JsMP.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit-Bzq7JsMP.mjs","names":[],"sources":["../src/reconcile/emit.ts"],"sourcesContent":["/**\n * The manifest, written out as a module.\n *\n * Discovery imports the application's own code — that is how it finds\n * constructs — so anything calling it evaluates the whole runtime graph: React\n * email templates, database clients, modules that read env at import time. That\n * is fine inside `gkm dev`, which is already running the app. It is not fine\n * inside a deploy config, where it fails on the first thing the deploy toolchain\n * does not expect and would keep failing on the next.\n *\n * So discovery happens once, in the build, and everything downstream reads what\n * it wrote. The same split `RestApi` makes by naming a routes glob rather than\n * importing route modules.\n *\n * A module rather than JSON, because a manifest is not just data to this\n * codebase — `as const satisfies ConstructManifest` is what keeps every id,\n * kind and provided key a *literal*, so `IdsOf`, `ProvidedKeys` and the rest\n * can select out of it. `JSON.parse` returns `any` and gives all of that up.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type {\n\tConstructManifest,\n\tCronInfo,\n\tDeclaration,\n\tDependency,\n\tFunctionInfo,\n\tQueueInfo,\n\tRestApiEndpoint,\n\tRouteInfo,\n\tSubscriberInfo,\n} from '@geekmidas/manifest';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport type { CacheBackend, EmailBackend } from '../types.js';\n\n/** Where the build writes it, relative to the app root. */\nexport const MANIFEST_PATH = '.gkm/manifest.ts';\n\n/**\n * Fold the build's generated routes into the surface that serves them.\n *\n * The routes already exist: `gkm build` generates one handler per endpoint and\n * records its path, method and environment. What was missing is that the\n * *construct* manifest never learned about them, so a `rest-api` node said\n * `endpoints: []` while five handlers sat in `.gkm/`. Two documents describing\n * the same routes, one of them wrong.\n *\n * Merging here rather than making the construct name a glob or import its\n * endpoints: the build is the thing that already walked the filesystem and\n * already knows where it wrote each handler. Asking the declaration to restate\n * any of that is how the two come to disagree.\n *\n * Attribution is by exclusion — routes go to the surface that declared none of\n * its own. A surface with static endpoints (an auth server's single wildcard)\n * keeps them. That is exact while an app has one API of its own, and needs a\n * real answer the day it has two; the alternative today would be inventing one\n * before anything needs it.\n *\n * **Only from a provider that generates one handler per route.** The `server`\n * provider generates a single catch-all — `ALL *` pointing at the Hono app that\n * routes in-process — which is a true description of that build and a false\n * description of the application. Merging it wrote `endpoints: [{ method:\n * 'ALL', path: '*' }]` into a manifest for an app with five routes, and made the\n * document's contents depend on which `--provider` ran last. A manifest must not\n * do that.\n */\nexport function withRoutes(\n\tmanifest: ConstructManifest,\n\troutes: readonly RouteInfo[],\n\toptions: { perRoute: boolean },\n): ConstructManifest {\n\tif (routes.length === 0 || !options.perRoute) return manifest;\n\n\tconst target = Object.entries(manifest).find(\n\t\t([, declaration]) =>\n\t\t\tdeclaration.kind === 'rest-api' && declaration.endpoints.length === 0,\n\t);\n\n\tif (!target) return manifest;\n\n\tconst [id, declaration] = target;\n\tif (declaration.kind !== 'rest-api') return manifest;\n\n\treturn {\n\t\t...manifest,\n\t\t[id]: {\n\t\t\t...declaration,\n\t\t\tendpoints: routes.map((route) => asEndpoint(manifest, id, route)),\n\t\t},\n\t};\n}\n\n/**\n * Fold in the compute the build generated: functions, crons, queue workers and\n * topic subscribers.\n *\n * All four are named by their *export*, the same way an endpoint's handler is —\n * `CronGenerator` uses the export key as the name — so none of them can declare\n * themselves and all of them are the build's to add. That is why a `Function`\n * has no `declare()` and never will.\n *\n * A worker and a subscriber **nest** inside the queue or topic that triggers\n * them, because position carries the trigger. A function and a cron are\n * top-level, because nothing triggers them but a caller or a clock.\n */\nexport function withCompute(\n\tmanifest: ConstructManifest,\n\tcompute: {\n\t\tfunctions?: readonly FunctionInfo[];\n\t\tcrons?: readonly CronInfo[];\n\t\tqueues?: readonly QueueInfo[];\n\t\tsubscribers?: readonly SubscriberInfo[];\n\t},\n): ConstructManifest {\n\tconst next: Record<string, Declaration> = { ...manifest };\n\n\tfor (const fn of compute.functions ?? []) {\n\t\tnext[fn.name] = {\n\t\t\tkind: 'function',\n\t\t\tid: fn.name,\n\t\t\thandler: fn.handler,\n\t\t\tdependencies: edgesTo(manifest, fn.dependencies),\n\t\t\tprovides: [provideKey(fn.name, 'url')],\n\t\t};\n\t}\n\n\tfor (const cron of compute.crons ?? []) {\n\t\tnext[cron.name] = {\n\t\t\tkind: 'cron',\n\t\t\tid: cron.name,\n\t\t\thandler: cron.handler,\n\t\t\tschedule: cron.schedule,\n\t\t\tdependencies: edgesTo(manifest, cron.dependencies),\n\t\t};\n\t}\n\n\t// A worker drains exactly one queue, so it is matched by the queue it names\n\t// rather than by anything about the handler.\n\tfor (const worker of compute.queues ?? []) {\n\t\tconst queue = next[canonicalId(worker.name)];\n\t\tif (queue?.kind !== 'queue') continue;\n\n\t\tnext[queue.id] = {\n\t\t\t...queue,\n\t\t\tworker: {\n\t\t\t\tid: `${queue.id}Worker`,\n\t\t\t\thandler: worker.handler,\n\t\t\t\tdependencies: edgesTo(manifest, worker.dependencies),\n\t\t\t},\n\t\t};\n\t}\n\n\tfor (const subscriber of compute.subscribers ?? []) {\n\t\tconst topicId = subscriber.topic && canonicalId(subscriber.topic);\n\t\tconst topic = topicId ? next[topicId] : undefined;\n\t\tif (topic?.kind !== 'topic') continue;\n\n\t\tnext[topic.id] = {\n\t\t\t...topic,\n\t\t\tsubscribers: [\n\t\t\t\t...topic.subscribers,\n\t\t\t\t{\n\t\t\t\t\tid: subscriber.name,\n\t\t\t\t\thandler: subscriber.handler,\n\t\t\t\t\tevents: subscriber.subscribedEvents,\n\t\t\t\t\tdependencies: edgesTo(manifest, subscriber.dependencies),\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\treturn next;\n}\n\n/**\n * Construct ids as manifest edges.\n *\n * A `Dependency` carries the target's kind as well as its id, and the kind is\n * read off the manifest rather than recorded beside the edge — the same rule\n * `edgeTo` follows, for the same reason: a hand-written kind is one fact stated\n * twice, and the copy is what goes stale.\n *\n * An id that names nothing is dropped rather than thrown on. A handler can\n * depend on a construct that declares no node of its own — a lifted `Service`\n * is exactly that — and a build is the wrong place to refuse it; what a target\n * needs from this list is the subset it can actually grant.\n */\nfunction edgesTo(\n\tmanifest: ConstructManifest,\n\tids: readonly string[] = [],\n): Dependency[] {\n\treturn ids.flatMap((target) => {\n\t\tconst declaration = manifest[target];\n\n\t\treturn declaration ? [{ target, kind: declaration.kind }] : [];\n\t});\n}\n\n/** One generated route as the manifest's shape. */\nfunction asEndpoint(\n\tmanifest: ConstructManifest,\n\tsurface: string,\n\troute: RouteInfo,\n): RestApiEndpoint {\n\treturn {\n\t\t// The handler path is already unique per route and is what a target needs\n\t\t// to point a function at, so it is also the natural id.\n\t\tid: `${surface}${route.method}${route.path}`,\n\t\thandler: route.handler,\n\t\tmethod: route.method,\n\t\tpath: route.path,\n\t\t// The edges the handler declared, which is what a target reads to grant\n\t\t// this one function access to exactly those constructs. `environment` is\n\t\t// the same edges' shadow — the keys read — and cannot be turned back into\n\t\t// them, which is why the ids are carried rather than inferred.\n\t\tdependencies: edgesTo(manifest, route.dependencies),\n\t\t...(route.authorizer ? { authorizer: route.authorizer } : {}),\n\t};\n}\n\n/**\n * The manifest as TypeScript source.\n *\n * Pure, so what gets written can be asserted without touching a filesystem.\n */\nexport function manifestModule(\n\tmanifest: ConstructManifest,\n\tbackends: Backends = {},\n): string {\n\treturn `${HEADER}\nexport const manifest = ${serialise(manifest)} as const satisfies ConstructManifest;\n\n/**\n * Where the backends that are *config* rather than declaration resolved to.\n *\n * Recorded because they were being answered twice: once by the build, which\n * registers exactly one cache driver, and once by a deploy config, which told\n * the provisioner something else. The result was a deployed URL the running code\n * had no driver for — two documents disagreeing about one fact, which is what\n * this manifest exists to stop.\n */\nexport const backends = ${serialise(backends)} as const;\n\n// Derived types\nexport type Ids = IdsOf<typeof manifest>;\nexport type Construct<Id extends Ids> = DeclarationOf<typeof manifest, Id>;\nexport type Kind = Construct<Ids>['kind'];\n\n// Useful union types\nexport type ProvidedKeys = AllProvidedKeys<typeof manifest>;\nexport type Surfaces = IdsOfKind<typeof manifest, 'rest-api'>;\nexport type CacheBackend = (typeof backends)['cache'];\nexport type EmailBackend = (typeof backends)['email'];\n`;\n}\n\n/** Write it where the deploy config expects to import it from. */\nexport async function writeManifestModule(\n\tmanifest: ConstructManifest,\n\tappRoot: string,\n\tbackends: Backends = {},\n): Promise<string> {\n\tconst path = join(appRoot, MANIFEST_PATH);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, manifestModule(manifest, backends), 'utf8');\n\n\treturn path;\n}\n\n/** The choices that are deployment config rather than declaration. */\nexport interface Backends {\n\tcache?: CacheBackend;\n\temail?: EmailBackend;\n}\n\nconst HEADER = `// Generated by \\`gkm build\\`. Do not edit.\n//\n// Discovery imports application code, so it runs once here rather than in every\n// consumer — a deploy config that discovered for itself would evaluate the whole\n// runtime graph inside its own toolchain. This is the result, as literals.\n\nimport type {\n\tAllProvidedKeys,\n\tConstructManifest,\n\tDeclarationOf,\n\tIdsOf,\n\tIdsOfKind,\n} from '@geekmidas/manifest';\n`;\n\n/**\n * JSON, re-indented with tabs and with keys left unquoted where they can be.\n *\n * Cosmetic, and worth it: this file is read by people trying to work out what\n * their app declares, and a wall of quoted keys reads as output rather than as\n * something anyone is expected to understand.\n */\nfunction serialise(value: object): string {\n\treturn JSON.stringify(value, null, '\\t')\n\t\t.replace(/^(\\t*)\"([A-Za-z_$][\\w$]*)\":/gm, '$1$2:')\n\t\t.replace(/\"/g, \"'\");\n}\n"],"mappings":"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"}
@@ -1,7 +1,6 @@
1
- import { generateSecurePassword } from "./generator-VE2JrFlY.mjs";
2
- import { dirname, join } from "node:path";
1
+ import { i as generateSecurePassword } from "./generator-LfZU7NlU.mjs";
3
2
  import { mkdir, writeFile } from "node:fs/promises";
4
-
3
+ import { dirname, join } from "node:path";
5
4
  //#region src/setup/fullstack-secrets.ts
6
5
  /**
7
6
  * Generate a secure random password for database users.
@@ -15,9 +14,7 @@ function generateDbPassword() {
15
14
  * All apps connect to the same database, but use different users/schemas.
16
15
  */
17
16
  function generateDbUrl(appName, password, projectName, host = "localhost", port = 5432) {
18
- const userName = appName.replace(/-/g, "_");
19
- const dbName = `${projectName.replace(/-/g, "_")}_dev`;
20
- return `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;
17
+ return `postgresql://${appName.replace(/-/g, "_")}:${password}@${host}:${port}/${`${projectName.replace(/-/g, "_")}_dev`}`;
21
18
  }
22
19
  /**
23
20
  * Generate fullstack-aware custom secrets for a workspace.
@@ -40,8 +37,8 @@ function generateFullstackCustomSecrets(workspace, containers = []) {
40
37
  for (const [appName, appConfig] of Object.entries(workspace.apps)) {
41
38
  if (appConfig.type === "web") {
42
39
  frontendPorts.push(appConfig.port);
43
- const upperName$1 = appName.toUpperCase();
44
- customs[`${upperName$1}_URL`] = `http://localhost:${appConfig.port}`;
40
+ const upperName = appName.toUpperCase();
41
+ customs[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;
45
42
  continue;
46
43
  }
47
44
  if (appConfig.type === "mobile") continue;
@@ -56,10 +53,7 @@ function generateFullstackCustomSecrets(workspace, containers = []) {
56
53
  customs.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;
57
54
  }
58
55
  }
59
- if (customs.BETTER_AUTH_SECRET) {
60
- const allPorts = Object.values(workspace.apps).map((a) => a.port);
61
- customs.BETTER_AUTH_TRUSTED_ORIGINS = allPorts.map((p) => `http://localhost:${p}`).join(",");
62
- }
56
+ if (customs.BETTER_AUTH_SECRET) customs.BETTER_AUTH_TRUSTED_ORIGINS = Object.values(workspace.apps).map((a) => a.port).map((p) => `http://localhost:${p}`).join(",");
63
57
  return customs;
64
58
  }
65
59
  /**
@@ -81,7 +75,7 @@ ${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join("\n")}
81
75
  await mkdir(dirname(envPath), { recursive: true });
82
76
  await writeFile(envPath, envContent);
83
77
  }
84
-
85
78
  //#endregion
86
- export { generateDbPassword, generateDbUrl, generateFullstackCustomSecrets, writeDockerEnvFromSecrets };
87
- //# sourceMappingURL=fullstack-secrets-METaRLS1.mjs.map
79
+ export { writeDockerEnvFromSecrets as n, generateFullstackCustomSecrets as t };
80
+
81
+ //# sourceMappingURL=fullstack-secrets-BET8gqwK.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fullstack-secrets-BET8gqwK.mjs","names":[],"sources":["../src/setup/fullstack-secrets.ts"],"sourcesContent":["import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { generateSecurePassword } from '../secrets/generator.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/**\n * Generate a secure random password for database users.\n * Uses a combination of timestamp and random bytes for uniqueness.\n */\nexport function generateDbPassword(): string {\n\treturn `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Generate database URL for an app.\n * All apps connect to the same database, but use different users/schemas.\n */\nexport function generateDbUrl(\n\tappName: string,\n\tpassword: string,\n\tprojectName: string,\n\thost = 'localhost',\n\tport = 5432,\n): string {\n\tconst userName = appName.replace(/-/g, '_');\n\tconst dbName = `${projectName.replace(/-/g, '_')}_dev`;\n\treturn `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;\n}\n\n/**\n * Generate fullstack-aware custom secrets for a workspace.\n *\n * Generates:\n * - Common secrets: NODE_ENV, PORT, LOG_LEVEL, JWT_SECRET\n * - Per-app database passwords and URLs for backend apps with db service\n * - Better-auth secrets for apps using the better-auth framework\n */\nexport function generateFullstackCustomSecrets(\n\tworkspace: NormalizedWorkspace,\n\tcontainers: readonly string[] = [],\n): Record<string, string> {\n\t// A Postgres exists because something declared a database, not because\n\t// config carried a flag saying so.\n\tconst hasDb = containers.includes('postgres');\n\tconst customs: Record<string, string> = {\n\t\tNODE_ENV: 'development',\n\t\tPORT: '3000',\n\t\tLOG_LEVEL: 'debug',\n\t\tJWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n\t};\n\n\tif (!hasDb) {\n\t\treturn customs;\n\t}\n\n\t// Collect all frontend ports for trusted origins\n\tconst frontendPorts: number[] = [];\n\n\tfor (const [appName, appConfig] of Object.entries(workspace.apps)) {\n\t\tif (appConfig.type === 'web') {\n\t\t\tfrontendPorts.push(appConfig.port);\n\t\t\tconst upperName = appName.toUpperCase();\n\t\t\tcustoms[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Mobile apps (Expo, etc.) don't run on a host port the backend\n\t\t// cares about for CORS, and don't need DB credentials.\n\t\tif (appConfig.type === 'mobile') {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Backend apps with database: generate per-app DB passwords and URLs\n\t\tconst password = generateDbPassword();\n\t\tconst upperName = appName.toUpperCase();\n\n\t\tcustoms[`${upperName}_DATABASE_URL`] = generateDbUrl(\n\t\t\tappName,\n\t\t\tpassword,\n\t\t\tworkspace.name,\n\t\t);\n\t\tcustoms[`${upperName}_DB_PASSWORD`] = password;\n\n\t\t// Better-auth framework secrets\n\t\tif (appConfig.framework === 'better-auth') {\n\t\t\tcustoms.AUTH_PORT = String(appConfig.port);\n\t\t\tcustoms.AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t\tcustoms.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;\n\t\t\tcustoms.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t}\n\t}\n\n\t// Generate trusted origins for better-auth (all app ports)\n\tif (customs.BETTER_AUTH_SECRET) {\n\t\tconst allPorts = Object.values(workspace.apps).map((a) => a.port);\n\t\tcustoms.BETTER_AUTH_TRUSTED_ORIGINS = allPorts\n\t\t\t.map((p) => `http://localhost:${p}`)\n\t\t\t.join(',');\n\t}\n\n\treturn customs;\n}\n\n/**\n * Extract *_DB_PASSWORD keys from secrets and write docker/.env.\n *\n * The docker/.env file contains database passwords that the PostgreSQL\n * init script reads to create per-app database users.\n */\nexport async function writeDockerEnvFromSecrets(\n\tsecrets: StageSecrets,\n\tworkspaceRoot: string,\n): Promise<void> {\n\tconst dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) =>\n\t\tkey.endsWith('_DB_PASSWORD'),\n\t);\n\n\t// Always include pgboss password when pgboss credentials exist\n\tif (\n\t\tsecrets.services.pgboss &&\n\t\t!dbPasswordEntries.some(([key]) => key === 'PGBOSS_DB_PASSWORD')\n\t) {\n\t\tdbPasswordEntries.push([\n\t\t\t'PGBOSS_DB_PASSWORD',\n\t\t\tsecrets.services.pgboss.password,\n\t\t]);\n\t}\n\n\tif (dbPasswordEntries.length === 0) {\n\t\treturn;\n\t}\n\n\tconst envContent = `# Auto-generated docker environment file\n# Contains database passwords for docker-compose postgres init\n# This file is gitignored - do not commit to version control\n${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join('\\n')}\n`;\n\n\tconst envPath = join(workspaceRoot, 'docker', '.env');\n\tawait mkdir(dirname(envPath), { recursive: true });\n\tawait writeFile(envPath, envContent);\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,qBAA6B;CAC5C,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;AAC7G;;;;;AAMA,SAAgB,cACf,SACA,UACA,aACA,OAAO,aACP,OAAO,MACE;CAGT,OAAO,gBAFU,QAAQ,QAAQ,MAAM,GAET,EAAE,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,GAD7C,YAAY,QAAQ,MAAM,GAAG,EAAE;AAElD;;;;;;;;;AAUA,SAAgB,+BACf,WACA,aAAgC,CAAC,GACR;CAGzB,MAAM,QAAQ,WAAW,SAAS,UAAU;CAC5C,MAAM,UAAkC;EACvC,UAAU;EACV,MAAM;EACN,WAAW;EACX,YAAY,OAAO,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;CACpE;CAEA,IAAI,CAAC,OACJ,OAAO;CAIR,MAAM,gBAA0B,CAAC;CAEjC,KAAK,MAAM,CAAC,SAAS,cAAc,OAAO,QAAQ,UAAU,IAAI,GAAG;EAClE,IAAI,UAAU,SAAS,OAAO;GAC7B,cAAc,KAAK,UAAU,IAAI;GACjC,MAAM,YAAY,QAAQ,YAAY;GACtC,QAAQ,GAAG,UAAU,SAAS,oBAAoB,UAAU;GAC5D;EACD;EAIA,IAAI,UAAU,SAAS,UACtB;EAID,MAAM,WAAW,mBAAmB;EACpC,MAAM,YAAY,QAAQ,YAAY;EAEtC,QAAQ,GAAG,UAAU,kBAAkB,cACtC,SACA,UACA,UAAU,IACX;EACA,QAAQ,GAAG,UAAU,iBAAiB;EAGtC,IAAI,UAAU,cAAc,eAAe;GAC1C,QAAQ,YAAY,OAAO,UAAU,IAAI;GACzC,QAAQ,WAAW,oBAAoB,UAAU;GACjD,QAAQ,qBAAqB,eAAe,KAAK,IAAI,EAAE,GAAG,uBAAuB,EAAE;GACnF,QAAQ,kBAAkB,oBAAoB,UAAU;EACzD;CACD;CAGA,IAAI,QAAQ,oBAEX,QAAQ,8BADS,OAAO,OAAO,UAAU,IAAI,CAAC,CAAC,KAAK,MAAM,EAAE,IACf,CAAC,CAC5C,KAAK,MAAM,oBAAoB,GAAG,CAAC,CACnC,KAAK,GAAG;CAGX,OAAO;AACR;;;;;;;AAQA,eAAsB,0BACrB,SACA,eACgB;CAChB,MAAM,oBAAoB,OAAO,QAAQ,QAAQ,MAAM,CAAC,CAAC,QAAQ,CAAC,SACjE,IAAI,SAAS,cAAc,CAC5B;CAGA,IACC,QAAQ,SAAS,UACjB,CAAC,kBAAkB,MAAM,CAAC,SAAS,QAAQ,oBAAoB,GAE/D,kBAAkB,KAAK,CACtB,sBACA,QAAQ,SAAS,OAAO,QACzB,CAAC;CAGF,IAAI,kBAAkB,WAAW,GAChC;CAGD,MAAM,aAAa;;;EAGlB,kBAAkB,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE;;CAGvE,MAAM,UAAU,KAAK,eAAe,UAAU,MAAM;CACpD,MAAM,MAAM,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,MAAM,UAAU,SAAS,UAAU;AACpC"}
@@ -1,8 +1,6 @@
1
- const require_chunk = require('./chunk-CUT6urMc.cjs');
2
- const require_generator = require('./generator-CNzBw7g5.cjs');
3
- const node_path = require_chunk.__toESM(require("node:path"));
4
- const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
5
-
1
+ const require_generator = require("./generator-Wdfp0jCS.cjs");
2
+ let node_fs_promises = require("node:fs/promises");
3
+ let node_path = require("node:path");
6
4
  //#region src/setup/fullstack-secrets.ts
7
5
  /**
8
6
  * Generate a secure random password for database users.
@@ -16,9 +14,7 @@ function generateDbPassword() {
16
14
  * All apps connect to the same database, but use different users/schemas.
17
15
  */
18
16
  function generateDbUrl(appName, password, projectName, host = "localhost", port = 5432) {
19
- const userName = appName.replace(/-/g, "_");
20
- const dbName = `${projectName.replace(/-/g, "_")}_dev`;
21
- return `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;
17
+ return `postgresql://${appName.replace(/-/g, "_")}:${password}@${host}:${port}/${`${projectName.replace(/-/g, "_")}_dev`}`;
22
18
  }
23
19
  /**
24
20
  * Generate fullstack-aware custom secrets for a workspace.
@@ -41,8 +37,8 @@ function generateFullstackCustomSecrets(workspace, containers = []) {
41
37
  for (const [appName, appConfig] of Object.entries(workspace.apps)) {
42
38
  if (appConfig.type === "web") {
43
39
  frontendPorts.push(appConfig.port);
44
- const upperName$1 = appName.toUpperCase();
45
- customs[`${upperName$1}_URL`] = `http://localhost:${appConfig.port}`;
40
+ const upperName = appName.toUpperCase();
41
+ customs[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;
46
42
  continue;
47
43
  }
48
44
  if (appConfig.type === "mobile") continue;
@@ -57,10 +53,7 @@ function generateFullstackCustomSecrets(workspace, containers = []) {
57
53
  customs.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;
58
54
  }
59
55
  }
60
- if (customs.BETTER_AUTH_SECRET) {
61
- const allPorts = Object.values(workspace.apps).map((a) => a.port);
62
- customs.BETTER_AUTH_TRUSTED_ORIGINS = allPorts.map((p) => `http://localhost:${p}`).join(",");
63
- }
56
+ if (customs.BETTER_AUTH_SECRET) customs.BETTER_AUTH_TRUSTED_ORIGINS = Object.values(workspace.apps).map((a) => a.port).map((p) => `http://localhost:${p}`).join(",");
64
57
  return customs;
65
58
  }
66
59
  /**
@@ -82,30 +75,18 @@ ${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join("\n")}
82
75
  await (0, node_fs_promises.mkdir)((0, node_path.dirname)(envPath), { recursive: true });
83
76
  await (0, node_fs_promises.writeFile)(envPath, envContent);
84
77
  }
85
-
86
78
  //#endregion
87
- Object.defineProperty(exports, 'generateDbPassword', {
88
- enumerable: true,
89
- get: function () {
90
- return generateDbPassword;
91
- }
92
- });
93
- Object.defineProperty(exports, 'generateDbUrl', {
94
- enumerable: true,
95
- get: function () {
96
- return generateDbUrl;
97
- }
98
- });
99
- Object.defineProperty(exports, 'generateFullstackCustomSecrets', {
100
- enumerable: true,
101
- get: function () {
102
- return generateFullstackCustomSecrets;
103
- }
79
+ Object.defineProperty(exports, "generateFullstackCustomSecrets", {
80
+ enumerable: true,
81
+ get: function() {
82
+ return generateFullstackCustomSecrets;
83
+ }
104
84
  });
105
- Object.defineProperty(exports, 'writeDockerEnvFromSecrets', {
106
- enumerable: true,
107
- get: function () {
108
- return writeDockerEnvFromSecrets;
109
- }
85
+ Object.defineProperty(exports, "writeDockerEnvFromSecrets", {
86
+ enumerable: true,
87
+ get: function() {
88
+ return writeDockerEnvFromSecrets;
89
+ }
110
90
  });
111
- //# sourceMappingURL=fullstack-secrets-D6MyHhQP.cjs.map
91
+
92
+ //# sourceMappingURL=fullstack-secrets-DXsWO-5m.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fullstack-secrets-DXsWO-5m.cjs","names":["generateSecurePassword","join","mkdir","dirname","writeFile"],"sources":["../src/setup/fullstack-secrets.ts"],"sourcesContent":["import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { generateSecurePassword } from '../secrets/generator.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/**\n * Generate a secure random password for database users.\n * Uses a combination of timestamp and random bytes for uniqueness.\n */\nexport function generateDbPassword(): string {\n\treturn `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Generate database URL for an app.\n * All apps connect to the same database, but use different users/schemas.\n */\nexport function generateDbUrl(\n\tappName: string,\n\tpassword: string,\n\tprojectName: string,\n\thost = 'localhost',\n\tport = 5432,\n): string {\n\tconst userName = appName.replace(/-/g, '_');\n\tconst dbName = `${projectName.replace(/-/g, '_')}_dev`;\n\treturn `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;\n}\n\n/**\n * Generate fullstack-aware custom secrets for a workspace.\n *\n * Generates:\n * - Common secrets: NODE_ENV, PORT, LOG_LEVEL, JWT_SECRET\n * - Per-app database passwords and URLs for backend apps with db service\n * - Better-auth secrets for apps using the better-auth framework\n */\nexport function generateFullstackCustomSecrets(\n\tworkspace: NormalizedWorkspace,\n\tcontainers: readonly string[] = [],\n): Record<string, string> {\n\t// A Postgres exists because something declared a database, not because\n\t// config carried a flag saying so.\n\tconst hasDb = containers.includes('postgres');\n\tconst customs: Record<string, string> = {\n\t\tNODE_ENV: 'development',\n\t\tPORT: '3000',\n\t\tLOG_LEVEL: 'debug',\n\t\tJWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n\t};\n\n\tif (!hasDb) {\n\t\treturn customs;\n\t}\n\n\t// Collect all frontend ports for trusted origins\n\tconst frontendPorts: number[] = [];\n\n\tfor (const [appName, appConfig] of Object.entries(workspace.apps)) {\n\t\tif (appConfig.type === 'web') {\n\t\t\tfrontendPorts.push(appConfig.port);\n\t\t\tconst upperName = appName.toUpperCase();\n\t\t\tcustoms[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Mobile apps (Expo, etc.) don't run on a host port the backend\n\t\t// cares about for CORS, and don't need DB credentials.\n\t\tif (appConfig.type === 'mobile') {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Backend apps with database: generate per-app DB passwords and URLs\n\t\tconst password = generateDbPassword();\n\t\tconst upperName = appName.toUpperCase();\n\n\t\tcustoms[`${upperName}_DATABASE_URL`] = generateDbUrl(\n\t\t\tappName,\n\t\t\tpassword,\n\t\t\tworkspace.name,\n\t\t);\n\t\tcustoms[`${upperName}_DB_PASSWORD`] = password;\n\n\t\t// Better-auth framework secrets\n\t\tif (appConfig.framework === 'better-auth') {\n\t\t\tcustoms.AUTH_PORT = String(appConfig.port);\n\t\t\tcustoms.AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t\tcustoms.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;\n\t\t\tcustoms.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t}\n\t}\n\n\t// Generate trusted origins for better-auth (all app ports)\n\tif (customs.BETTER_AUTH_SECRET) {\n\t\tconst allPorts = Object.values(workspace.apps).map((a) => a.port);\n\t\tcustoms.BETTER_AUTH_TRUSTED_ORIGINS = allPorts\n\t\t\t.map((p) => `http://localhost:${p}`)\n\t\t\t.join(',');\n\t}\n\n\treturn customs;\n}\n\n/**\n * Extract *_DB_PASSWORD keys from secrets and write docker/.env.\n *\n * The docker/.env file contains database passwords that the PostgreSQL\n * init script reads to create per-app database users.\n */\nexport async function writeDockerEnvFromSecrets(\n\tsecrets: StageSecrets,\n\tworkspaceRoot: string,\n): Promise<void> {\n\tconst dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) =>\n\t\tkey.endsWith('_DB_PASSWORD'),\n\t);\n\n\t// Always include pgboss password when pgboss credentials exist\n\tif (\n\t\tsecrets.services.pgboss &&\n\t\t!dbPasswordEntries.some(([key]) => key === 'PGBOSS_DB_PASSWORD')\n\t) {\n\t\tdbPasswordEntries.push([\n\t\t\t'PGBOSS_DB_PASSWORD',\n\t\t\tsecrets.services.pgboss.password,\n\t\t]);\n\t}\n\n\tif (dbPasswordEntries.length === 0) {\n\t\treturn;\n\t}\n\n\tconst envContent = `# Auto-generated docker environment file\n# Contains database passwords for docker-compose postgres init\n# This file is gitignored - do not commit to version control\n${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join('\\n')}\n`;\n\n\tconst envPath = join(workspaceRoot, 'docker', '.env');\n\tawait mkdir(dirname(envPath), { recursive: true });\n\tawait writeFile(envPath, envContent);\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,qBAA6B;CAC5C,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;AAC7G;;;;;AAMA,SAAgB,cACf,SACA,UACA,aACA,OAAO,aACP,OAAO,MACE;CAGT,OAAO,gBAFU,QAAQ,QAAQ,MAAM,GAET,EAAE,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,GAD7C,YAAY,QAAQ,MAAM,GAAG,EAAE;AAElD;;;;;;;;;AAUA,SAAgB,+BACf,WACA,aAAgC,CAAC,GACR;CAGzB,MAAM,QAAQ,WAAW,SAAS,UAAU;CAC5C,MAAM,UAAkC;EACvC,UAAU;EACV,MAAM;EACN,WAAW;EACX,YAAY,OAAO,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;CACpE;CAEA,IAAI,CAAC,OACJ,OAAO;CAIR,MAAM,gBAA0B,CAAC;CAEjC,KAAK,MAAM,CAAC,SAAS,cAAc,OAAO,QAAQ,UAAU,IAAI,GAAG;EAClE,IAAI,UAAU,SAAS,OAAO;GAC7B,cAAc,KAAK,UAAU,IAAI;GACjC,MAAM,YAAY,QAAQ,YAAY;GACtC,QAAQ,GAAG,UAAU,SAAS,oBAAoB,UAAU;GAC5D;EACD;EAIA,IAAI,UAAU,SAAS,UACtB;EAID,MAAM,WAAW,mBAAmB;EACpC,MAAM,YAAY,QAAQ,YAAY;EAEtC,QAAQ,GAAG,UAAU,kBAAkB,cACtC,SACA,UACA,UAAU,IACX;EACA,QAAQ,GAAG,UAAU,iBAAiB;EAGtC,IAAI,UAAU,cAAc,eAAe;GAC1C,QAAQ,YAAY,OAAO,UAAU,IAAI;GACzC,QAAQ,WAAW,oBAAoB,UAAU;GACjD,QAAQ,qBAAqB,eAAe,KAAK,IAAI,EAAE,GAAGA,kBAAAA,uBAAuB,EAAE;GACnF,QAAQ,kBAAkB,oBAAoB,UAAU;EACzD;CACD;CAGA,IAAI,QAAQ,oBAEX,QAAQ,8BADS,OAAO,OAAO,UAAU,IAAI,CAAC,CAAC,KAAK,MAAM,EAAE,IACf,CAAC,CAC5C,KAAK,MAAM,oBAAoB,GAAG,CAAC,CACnC,KAAK,GAAG;CAGX,OAAO;AACR;;;;;;;AAQA,eAAsB,0BACrB,SACA,eACgB;CAChB,MAAM,oBAAoB,OAAO,QAAQ,QAAQ,MAAM,CAAC,CAAC,QAAQ,CAAC,SACjE,IAAI,SAAS,cAAc,CAC5B;CAGA,IACC,QAAQ,SAAS,UACjB,CAAC,kBAAkB,MAAM,CAAC,SAAS,QAAQ,oBAAoB,GAE/D,kBAAkB,KAAK,CACtB,sBACA,QAAQ,SAAS,OAAO,QACzB,CAAC;CAGF,IAAI,kBAAkB,WAAW,GAChC;CAGD,MAAM,aAAa;;;EAGlB,kBAAkB,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE;;CAGvE,MAAM,WAAA,GAAUC,UAAAA,KAAAA,CAAK,eAAe,UAAU,MAAM;CACpD,OAAA,GAAMC,iBAAAA,MAAAA,EAAAA,GAAMC,UAAAA,QAAAA,CAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,OAAA,GAAMC,iBAAAA,UAAAA,CAAU,SAAS,UAAU;AACpC"}
@@ -1,5 +1,4 @@
1
1
  import { randomBytes } from "node:crypto";
2
-
3
2
  //#region src/secrets/generator.ts
4
3
  /**
5
4
  * Generate a secure random password using URL-safe base64 characters.
@@ -56,9 +55,8 @@ const PGBOSS_DEFAULTS = {
56
55
  * Generate credentials for a specific service.
57
56
  */
58
57
  function generateServiceCredentials(service) {
59
- const defaults = SERVICE_DEFAULTS[service];
60
58
  return {
61
- ...defaults,
59
+ ...SERVICE_DEFAULTS[service],
62
60
  password: generateSecurePassword()
63
61
  };
64
62
  }
@@ -89,8 +87,7 @@ function generateRedisUrl(creds) {
89
87
  */
90
88
  function generateRabbitmqUrl(creds) {
91
89
  const { username, password, host, port, vhost } = creds;
92
- const encodedVhost = encodeURIComponent(vhost ?? "/");
93
- return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;
90
+ return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodeURIComponent(vhost ?? "/")}`;
94
91
  }
95
92
  /**
96
93
  * Generate endpoint URL for MinIO (S3-compatible).
@@ -105,8 +102,7 @@ function generateMinioEndpoint(creds) {
105
102
  * @see https://docs.localstack.cloud/aws/capabilities/config/credentials/
106
103
  */
107
104
  function generateLocalStackAccessKeyId() {
108
- const suffix = randomBytes(12).toString("base64url").slice(0, 16);
109
- return `LSIA${suffix}`;
105
+ return `LSIA${randomBytes(12).toString("base64url").slice(0, 16)}`;
110
106
  }
111
107
  /**
112
108
  * Generate connection URL for pg-boss (uses PostgreSQL protocol).
@@ -181,9 +177,8 @@ function generateConnectionUrls(services, eventsBackend) {
181
177
  * Generate LocalStack service credentials with LSIA-prefixed access key.
182
178
  */
183
179
  function generateLocalStackCredentials() {
184
- const defaults = SERVICE_DEFAULTS.localstack;
185
180
  return {
186
- ...defaults,
181
+ ...SERVICE_DEFAULTS.localstack,
187
182
  password: generateSecurePassword(),
188
183
  accessKeyId: generateLocalStackAccessKeyId()
189
184
  };
@@ -215,14 +210,13 @@ function createStageSecrets(stage, services, options) {
215
210
  };
216
211
  const eventsBackend = options?.eventsBackend;
217
212
  if (eventsBackend === "sns") serviceCredentials.localstack = generateLocalStackCredentials();
218
- const urls = generateConnectionUrls(serviceCredentials, eventsBackend);
219
213
  return {
220
214
  stage,
221
215
  createdAt: now,
222
216
  updatedAt: now,
223
217
  eventsBackend,
224
218
  services: serviceCredentials,
225
- urls,
219
+ urls: generateConnectionUrls(serviceCredentials, eventsBackend),
226
220
  custom: {}
227
221
  };
228
222
  }
@@ -247,7 +241,7 @@ function rotateServicePassword(secrets, service) {
247
241
  urls: generateConnectionUrls(newServices, secrets.eventsBackend)
248
242
  };
249
243
  }
250
-
251
244
  //#endregion
252
- export { createStageSecrets, generateConnectionUrls, generatePgBossUrl, generateSecurePassword, generateServiceCredentials, rotateServicePassword };
253
- //# sourceMappingURL=generator-VE2JrFlY.mjs.map
245
+ export { generateServiceCredentials as a, generateSecurePassword as i, generateConnectionUrls as n, rotateServicePassword as o, generatePgBossUrl as r, createStageSecrets as t };
246
+
247
+ //# sourceMappingURL=generator-LfZU7NlU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator-LfZU7NlU.mjs","names":[],"sources":["../src/secrets/generator.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { ComposeServiceName, EventsBackend } from '../types';\nimport type { ServiceCredentials, StageSecrets } from './types';\n\n/**\n * Generate a secure random password using URL-safe base64 characters.\n * @param length Password length (default: 32)\n */\nexport function generateSecurePassword(length = 32): string {\n\treturn randomBytes(Math.ceil((length * 3) / 4))\n\t\t.toString('base64url')\n\t\t.slice(0, length);\n}\n\n/** Default service configurations (localhost for local dev via Docker port mapping) */\nconst SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n> = {\n\tpostgres: {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'app',\n\t\tdatabase: 'app',\n\t},\n\tredis: {\n\t\thost: 'localhost',\n\t\tport: 6379,\n\t\tusername: 'default',\n\t},\n\trabbitmq: {\n\t\thost: 'localhost',\n\t\tport: 5672,\n\t\tusername: 'app',\n\t\tvhost: '/',\n\t},\n\tminio: {\n\t\thost: 'localhost',\n\t\tport: 9000,\n\t\tusername: 'app',\n\t\tbucket: 'app',\n\t},\n\tmailpit: {\n\t\thost: 'localhost',\n\t\tport: 1025,\n\t\tusername: 'app',\n\t},\n\tlocalstack: {\n\t\thost: 'localhost',\n\t\tport: 4566,\n\t\tusername: 'localstack',\n\t\tregion: 'us-east-1',\n\t},\n};\n\n/** Default credentials for pgboss (not a Docker service, reuses postgres) */\nconst PGBOSS_DEFAULTS: Omit<ServiceCredentials, 'password'> = {\n\thost: 'localhost',\n\tport: 5432,\n\tusername: 'pgboss',\n\tdatabase: 'app',\n};\n\n/**\n * Generate credentials for a specific service.\n */\nexport function generateServiceCredentials(\n\tservice: ComposeServiceName,\n): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS[service];\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t};\n}\n\n/**\n * Generate credentials for multiple services.\n */\nexport function generateServicesCredentials(\n\tservices: ComposeServiceName[],\n): StageSecrets['services'] {\n\tconst result: StageSecrets['services'] = {};\n\n\tfor (const service of services) {\n\t\tresult[service] = generateServiceCredentials(service);\n\t}\n\n\treturn result;\n}\n\n/**\n * Generate connection URL for PostgreSQL.\n */\nexport function generatePostgresUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;\n}\n\n/**\n * Generate connection URL for Redis.\n */\nexport function generateRedisUrl(creds: ServiceCredentials): string {\n\tconst { password, host, port } = creds;\n\treturn `redis://:${encodeURIComponent(password)}@${host}:${port}`;\n}\n\n/**\n * Generate connection URL for RabbitMQ.\n */\nexport function generateRabbitmqUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, vhost } = creds;\n\tconst encodedVhost = encodeURIComponent(vhost ?? '/');\n\treturn `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;\n}\n\n/**\n * Generate endpoint URL for MinIO (S3-compatible).\n */\nexport function generateMinioEndpoint(creds: ServiceCredentials): string {\n\tconst { host, port } = creds;\n\treturn `http://${host}:${port}`;\n}\n\n/**\n * Generate a LocalStack-compatible access key ID.\n * Must start with 'LSIA' prefix and be at least 20 characters.\n * @see https://docs.localstack.cloud/aws/capabilities/config/credentials/\n */\nexport function generateLocalStackAccessKeyId(): string {\n\tconst suffix = randomBytes(12).toString('base64url').slice(0, 16);\n\treturn `LSIA${suffix}`;\n}\n\n/**\n * Generate connection URL for pg-boss (uses PostgreSQL protocol).\n * Format: pgboss://user:pass@host:port/db?schema=pgboss\n */\nexport function generatePgBossUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;\n}\n\n/**\n * Generate event connection strings based on the events backend.\n */\nexport function generateEventConnectionStrings(\n\teventsBackend: EventsBackend,\n\tservices: StageSecrets['services'],\n): { publisher: string; subscriber: string } {\n\tswitch (eventsBackend) {\n\t\tcase 'pgboss': {\n\t\t\tconst creds = services.pgboss;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('pgboss credentials required for pgboss events');\n\t\t\t}\n\t\t\tconst url = generatePgBossUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t\tcase 'sns': {\n\t\t\tconst creds = services.localstack;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('localstack credentials required for sns events');\n\t\t\t}\n\t\t\tconst endpoint = `http://${creds.host}:${creds.port}`;\n\t\t\tconst region = creds.region ?? 'us-east-1';\n\t\t\tconst accessKeyId = creds.accessKeyId ?? creds.username;\n\t\t\tconst secretKey = encodeURIComponent(creds.password);\n\t\t\treturn {\n\t\t\t\tpublisher: `sns://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t\tsubscriber: `sqs://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t};\n\t\t}\n\t\tcase 'rabbitmq': {\n\t\t\tconst creds = services.rabbitmq;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('rabbitmq credentials required for rabbitmq events');\n\t\t\t}\n\t\t\tconst url = generateRabbitmqUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t}\n}\n\n/**\n * Generate connection URLs from service credentials.\n */\nexport function generateConnectionUrls(\n\tservices: StageSecrets['services'],\n\teventsBackend?: EventsBackend,\n): StageSecrets['urls'] {\n\tconst urls: StageSecrets['urls'] = {};\n\n\tif (services.postgres) {\n\t\turls.DATABASE_URL = generatePostgresUrl(services.postgres);\n\t}\n\n\tif (services.redis) {\n\t\turls.REDIS_URL = generateRedisUrl(services.redis);\n\t}\n\n\tif (services.rabbitmq) {\n\t\turls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);\n\t}\n\n\tif (services.minio) {\n\t\turls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);\n\t}\n\n\tif (eventsBackend) {\n\t\tconst eventUrls = generateEventConnectionStrings(eventsBackend, services);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = eventUrls.publisher;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = eventUrls.subscriber;\n\t} else if (services.pgboss) {\n\t\t// Default to pgboss when credentials exist but no explicit events backend\n\t\tconst pgbossUrl = generatePgBossUrl(services.pgboss);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;\n\t}\n\n\tif (services.mailpit) {\n\t\turls.SMTP_HOST = services.mailpit.host;\n\t\turls.SMTP_PORT = String(services.mailpit.port);\n\t}\n\n\treturn urls;\n}\n\n/**\n * Generate LocalStack service credentials with LSIA-prefixed access key.\n */\nexport function generateLocalStackCredentials(): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS.localstack;\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t\taccessKeyId: generateLocalStackAccessKeyId(),\n\t};\n}\n\n/**\n * Create a new StageSecrets object with generated credentials.\n * @param stage - The deployment stage (e.g., 'development', 'production')\n * @param services - List of services to generate credentials for\n * @param options - Optional configuration\n * @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')\n * @param options.eventsBackend - Event backend type (pgboss, sns, rabbitmq)\n */\nexport function createStageSecrets(\n\tstage: string,\n\tservices: ComposeServiceName[],\n\toptions?: { projectName?: string; eventsBackend?: EventsBackend },\n): StageSecrets {\n\tconst now = new Date().toISOString();\n\tconst serviceCredentials = generateServicesCredentials(services);\n\n\t// Override service defaults with project-derived names if provided\n\tif (options?.projectName) {\n\t\tif (serviceCredentials.postgres) {\n\t\t\tserviceCredentials.postgres.database = `${options.projectName.replace(/-/g, '_')}_dev`;\n\t\t}\n\t\tif (serviceCredentials.minio) {\n\t\t\tserviceCredentials.minio.bucket = options.projectName;\n\t\t\tserviceCredentials.minio.username = options.projectName;\n\t\t}\n\t}\n\n\t// Always create pgboss credentials when postgres is available.\n\t// pgboss reuses postgres host/port/database but with a dedicated user,\n\t// so subscribers work out of the box without explicit events config.\n\tif (serviceCredentials.postgres && !serviceCredentials.pgboss) {\n\t\tserviceCredentials.pgboss = {\n\t\t\t...PGBOSS_DEFAULTS,\n\t\t\tpassword: generateSecurePassword(),\n\t\t\thost: serviceCredentials.postgres.host,\n\t\t\tport: serviceCredentials.postgres.port,\n\t\t\tdatabase: serviceCredentials.postgres.database,\n\t\t};\n\t}\n\n\t// Generate event-specific credentials\n\tconst eventsBackend = options?.eventsBackend;\n\tif (eventsBackend === 'sns') {\n\t\t// LocalStack credentials with LSIA-prefixed access key\n\t\tserviceCredentials.localstack = generateLocalStackCredentials();\n\t}\n\n\tconst urls = generateConnectionUrls(serviceCredentials, eventsBackend);\n\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\teventsBackend,\n\t\tservices: serviceCredentials,\n\t\turls,\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Rotate password for a specific service.\n */\nexport function rotateServicePassword(\n\tsecrets: StageSecrets,\n\tservice: ComposeServiceName,\n): StageSecrets {\n\tconst currentCreds = secrets.services[service];\n\tif (!currentCreds) {\n\t\tthrow new Error(`Service \"${service}\" not configured in secrets`);\n\t}\n\n\tconst newCreds: ServiceCredentials = {\n\t\t...currentCreds,\n\t\tpassword: generateSecurePassword(),\n\t};\n\n\tconst newServices = {\n\t\t...secrets.services,\n\t\t[service]: newCreds,\n\t};\n\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tservices: newServices,\n\t\turls: generateConnectionUrls(newServices, secrets.eventsBackend),\n\t};\n}\n"],"mappings":"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"}
@@ -1,6 +1,4 @@
1
- const require_chunk = require('./chunk-CUT6urMc.cjs');
2
- const node_crypto = require_chunk.__toESM(require("node:crypto"));
3
-
1
+ let node_crypto = require("node:crypto");
4
2
  //#region src/secrets/generator.ts
5
3
  /**
6
4
  * Generate a secure random password using URL-safe base64 characters.
@@ -57,9 +55,8 @@ const PGBOSS_DEFAULTS = {
57
55
  * Generate credentials for a specific service.
58
56
  */
59
57
  function generateServiceCredentials(service) {
60
- const defaults = SERVICE_DEFAULTS[service];
61
58
  return {
62
- ...defaults,
59
+ ...SERVICE_DEFAULTS[service],
63
60
  password: generateSecurePassword()
64
61
  };
65
62
  }
@@ -90,8 +87,7 @@ function generateRedisUrl(creds) {
90
87
  */
91
88
  function generateRabbitmqUrl(creds) {
92
89
  const { username, password, host, port, vhost } = creds;
93
- const encodedVhost = encodeURIComponent(vhost ?? "/");
94
- return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;
90
+ return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodeURIComponent(vhost ?? "/")}`;
95
91
  }
96
92
  /**
97
93
  * Generate endpoint URL for MinIO (S3-compatible).
@@ -106,8 +102,7 @@ function generateMinioEndpoint(creds) {
106
102
  * @see https://docs.localstack.cloud/aws/capabilities/config/credentials/
107
103
  */
108
104
  function generateLocalStackAccessKeyId() {
109
- const suffix = (0, node_crypto.randomBytes)(12).toString("base64url").slice(0, 16);
110
- return `LSIA${suffix}`;
105
+ return `LSIA${(0, node_crypto.randomBytes)(12).toString("base64url").slice(0, 16)}`;
111
106
  }
112
107
  /**
113
108
  * Generate connection URL for pg-boss (uses PostgreSQL protocol).
@@ -182,9 +177,8 @@ function generateConnectionUrls(services, eventsBackend) {
182
177
  * Generate LocalStack service credentials with LSIA-prefixed access key.
183
178
  */
184
179
  function generateLocalStackCredentials() {
185
- const defaults = SERVICE_DEFAULTS.localstack;
186
180
  return {
187
- ...defaults,
181
+ ...SERVICE_DEFAULTS.localstack,
188
182
  password: generateSecurePassword(),
189
183
  accessKeyId: generateLocalStackAccessKeyId()
190
184
  };
@@ -216,14 +210,13 @@ function createStageSecrets(stage, services, options) {
216
210
  };
217
211
  const eventsBackend = options?.eventsBackend;
218
212
  if (eventsBackend === "sns") serviceCredentials.localstack = generateLocalStackCredentials();
219
- const urls = generateConnectionUrls(serviceCredentials, eventsBackend);
220
213
  return {
221
214
  stage,
222
215
  createdAt: now,
223
216
  updatedAt: now,
224
217
  eventsBackend,
225
218
  services: serviceCredentials,
226
- urls,
219
+ urls: generateConnectionUrls(serviceCredentials, eventsBackend),
227
220
  custom: {}
228
221
  };
229
222
  }
@@ -248,42 +241,42 @@ function rotateServicePassword(secrets, service) {
248
241
  urls: generateConnectionUrls(newServices, secrets.eventsBackend)
249
242
  };
250
243
  }
251
-
252
244
  //#endregion
253
- Object.defineProperty(exports, 'createStageSecrets', {
254
- enumerable: true,
255
- get: function () {
256
- return createStageSecrets;
257
- }
245
+ Object.defineProperty(exports, "createStageSecrets", {
246
+ enumerable: true,
247
+ get: function() {
248
+ return createStageSecrets;
249
+ }
258
250
  });
259
- Object.defineProperty(exports, 'generateConnectionUrls', {
260
- enumerable: true,
261
- get: function () {
262
- return generateConnectionUrls;
263
- }
251
+ Object.defineProperty(exports, "generateConnectionUrls", {
252
+ enumerable: true,
253
+ get: function() {
254
+ return generateConnectionUrls;
255
+ }
264
256
  });
265
- Object.defineProperty(exports, 'generatePgBossUrl', {
266
- enumerable: true,
267
- get: function () {
268
- return generatePgBossUrl;
269
- }
257
+ Object.defineProperty(exports, "generatePgBossUrl", {
258
+ enumerable: true,
259
+ get: function() {
260
+ return generatePgBossUrl;
261
+ }
270
262
  });
271
- Object.defineProperty(exports, 'generateSecurePassword', {
272
- enumerable: true,
273
- get: function () {
274
- return generateSecurePassword;
275
- }
263
+ Object.defineProperty(exports, "generateSecurePassword", {
264
+ enumerable: true,
265
+ get: function() {
266
+ return generateSecurePassword;
267
+ }
276
268
  });
277
- Object.defineProperty(exports, 'generateServiceCredentials', {
278
- enumerable: true,
279
- get: function () {
280
- return generateServiceCredentials;
281
- }
269
+ Object.defineProperty(exports, "generateServiceCredentials", {
270
+ enumerable: true,
271
+ get: function() {
272
+ return generateServiceCredentials;
273
+ }
282
274
  });
283
- Object.defineProperty(exports, 'rotateServicePassword', {
284
- enumerable: true,
285
- get: function () {
286
- return rotateServicePassword;
287
- }
275
+ Object.defineProperty(exports, "rotateServicePassword", {
276
+ enumerable: true,
277
+ get: function() {
278
+ return rotateServicePassword;
279
+ }
288
280
  });
289
- //# sourceMappingURL=generator-CNzBw7g5.cjs.map
281
+
282
+ //# sourceMappingURL=generator-Wdfp0jCS.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator-Wdfp0jCS.cjs","names":["randomBytes"],"sources":["../src/secrets/generator.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { ComposeServiceName, EventsBackend } from '../types';\nimport type { ServiceCredentials, StageSecrets } from './types';\n\n/**\n * Generate a secure random password using URL-safe base64 characters.\n * @param length Password length (default: 32)\n */\nexport function generateSecurePassword(length = 32): string {\n\treturn randomBytes(Math.ceil((length * 3) / 4))\n\t\t.toString('base64url')\n\t\t.slice(0, length);\n}\n\n/** Default service configurations (localhost for local dev via Docker port mapping) */\nconst SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n> = {\n\tpostgres: {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'app',\n\t\tdatabase: 'app',\n\t},\n\tredis: {\n\t\thost: 'localhost',\n\t\tport: 6379,\n\t\tusername: 'default',\n\t},\n\trabbitmq: {\n\t\thost: 'localhost',\n\t\tport: 5672,\n\t\tusername: 'app',\n\t\tvhost: '/',\n\t},\n\tminio: {\n\t\thost: 'localhost',\n\t\tport: 9000,\n\t\tusername: 'app',\n\t\tbucket: 'app',\n\t},\n\tmailpit: {\n\t\thost: 'localhost',\n\t\tport: 1025,\n\t\tusername: 'app',\n\t},\n\tlocalstack: {\n\t\thost: 'localhost',\n\t\tport: 4566,\n\t\tusername: 'localstack',\n\t\tregion: 'us-east-1',\n\t},\n};\n\n/** Default credentials for pgboss (not a Docker service, reuses postgres) */\nconst PGBOSS_DEFAULTS: Omit<ServiceCredentials, 'password'> = {\n\thost: 'localhost',\n\tport: 5432,\n\tusername: 'pgboss',\n\tdatabase: 'app',\n};\n\n/**\n * Generate credentials for a specific service.\n */\nexport function generateServiceCredentials(\n\tservice: ComposeServiceName,\n): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS[service];\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t};\n}\n\n/**\n * Generate credentials for multiple services.\n */\nexport function generateServicesCredentials(\n\tservices: ComposeServiceName[],\n): StageSecrets['services'] {\n\tconst result: StageSecrets['services'] = {};\n\n\tfor (const service of services) {\n\t\tresult[service] = generateServiceCredentials(service);\n\t}\n\n\treturn result;\n}\n\n/**\n * Generate connection URL for PostgreSQL.\n */\nexport function generatePostgresUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;\n}\n\n/**\n * Generate connection URL for Redis.\n */\nexport function generateRedisUrl(creds: ServiceCredentials): string {\n\tconst { password, host, port } = creds;\n\treturn `redis://:${encodeURIComponent(password)}@${host}:${port}`;\n}\n\n/**\n * Generate connection URL for RabbitMQ.\n */\nexport function generateRabbitmqUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, vhost } = creds;\n\tconst encodedVhost = encodeURIComponent(vhost ?? '/');\n\treturn `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;\n}\n\n/**\n * Generate endpoint URL for MinIO (S3-compatible).\n */\nexport function generateMinioEndpoint(creds: ServiceCredentials): string {\n\tconst { host, port } = creds;\n\treturn `http://${host}:${port}`;\n}\n\n/**\n * Generate a LocalStack-compatible access key ID.\n * Must start with 'LSIA' prefix and be at least 20 characters.\n * @see https://docs.localstack.cloud/aws/capabilities/config/credentials/\n */\nexport function generateLocalStackAccessKeyId(): string {\n\tconst suffix = randomBytes(12).toString('base64url').slice(0, 16);\n\treturn `LSIA${suffix}`;\n}\n\n/**\n * Generate connection URL for pg-boss (uses PostgreSQL protocol).\n * Format: pgboss://user:pass@host:port/db?schema=pgboss\n */\nexport function generatePgBossUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;\n}\n\n/**\n * Generate event connection strings based on the events backend.\n */\nexport function generateEventConnectionStrings(\n\teventsBackend: EventsBackend,\n\tservices: StageSecrets['services'],\n): { publisher: string; subscriber: string } {\n\tswitch (eventsBackend) {\n\t\tcase 'pgboss': {\n\t\t\tconst creds = services.pgboss;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('pgboss credentials required for pgboss events');\n\t\t\t}\n\t\t\tconst url = generatePgBossUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t\tcase 'sns': {\n\t\t\tconst creds = services.localstack;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('localstack credentials required for sns events');\n\t\t\t}\n\t\t\tconst endpoint = `http://${creds.host}:${creds.port}`;\n\t\t\tconst region = creds.region ?? 'us-east-1';\n\t\t\tconst accessKeyId = creds.accessKeyId ?? creds.username;\n\t\t\tconst secretKey = encodeURIComponent(creds.password);\n\t\t\treturn {\n\t\t\t\tpublisher: `sns://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t\tsubscriber: `sqs://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t};\n\t\t}\n\t\tcase 'rabbitmq': {\n\t\t\tconst creds = services.rabbitmq;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('rabbitmq credentials required for rabbitmq events');\n\t\t\t}\n\t\t\tconst url = generateRabbitmqUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t}\n}\n\n/**\n * Generate connection URLs from service credentials.\n */\nexport function generateConnectionUrls(\n\tservices: StageSecrets['services'],\n\teventsBackend?: EventsBackend,\n): StageSecrets['urls'] {\n\tconst urls: StageSecrets['urls'] = {};\n\n\tif (services.postgres) {\n\t\turls.DATABASE_URL = generatePostgresUrl(services.postgres);\n\t}\n\n\tif (services.redis) {\n\t\turls.REDIS_URL = generateRedisUrl(services.redis);\n\t}\n\n\tif (services.rabbitmq) {\n\t\turls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);\n\t}\n\n\tif (services.minio) {\n\t\turls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);\n\t}\n\n\tif (eventsBackend) {\n\t\tconst eventUrls = generateEventConnectionStrings(eventsBackend, services);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = eventUrls.publisher;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = eventUrls.subscriber;\n\t} else if (services.pgboss) {\n\t\t// Default to pgboss when credentials exist but no explicit events backend\n\t\tconst pgbossUrl = generatePgBossUrl(services.pgboss);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;\n\t}\n\n\tif (services.mailpit) {\n\t\turls.SMTP_HOST = services.mailpit.host;\n\t\turls.SMTP_PORT = String(services.mailpit.port);\n\t}\n\n\treturn urls;\n}\n\n/**\n * Generate LocalStack service credentials with LSIA-prefixed access key.\n */\nexport function generateLocalStackCredentials(): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS.localstack;\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t\taccessKeyId: generateLocalStackAccessKeyId(),\n\t};\n}\n\n/**\n * Create a new StageSecrets object with generated credentials.\n * @param stage - The deployment stage (e.g., 'development', 'production')\n * @param services - List of services to generate credentials for\n * @param options - Optional configuration\n * @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')\n * @param options.eventsBackend - Event backend type (pgboss, sns, rabbitmq)\n */\nexport function createStageSecrets(\n\tstage: string,\n\tservices: ComposeServiceName[],\n\toptions?: { projectName?: string; eventsBackend?: EventsBackend },\n): StageSecrets {\n\tconst now = new Date().toISOString();\n\tconst serviceCredentials = generateServicesCredentials(services);\n\n\t// Override service defaults with project-derived names if provided\n\tif (options?.projectName) {\n\t\tif (serviceCredentials.postgres) {\n\t\t\tserviceCredentials.postgres.database = `${options.projectName.replace(/-/g, '_')}_dev`;\n\t\t}\n\t\tif (serviceCredentials.minio) {\n\t\t\tserviceCredentials.minio.bucket = options.projectName;\n\t\t\tserviceCredentials.minio.username = options.projectName;\n\t\t}\n\t}\n\n\t// Always create pgboss credentials when postgres is available.\n\t// pgboss reuses postgres host/port/database but with a dedicated user,\n\t// so subscribers work out of the box without explicit events config.\n\tif (serviceCredentials.postgres && !serviceCredentials.pgboss) {\n\t\tserviceCredentials.pgboss = {\n\t\t\t...PGBOSS_DEFAULTS,\n\t\t\tpassword: generateSecurePassword(),\n\t\t\thost: serviceCredentials.postgres.host,\n\t\t\tport: serviceCredentials.postgres.port,\n\t\t\tdatabase: serviceCredentials.postgres.database,\n\t\t};\n\t}\n\n\t// Generate event-specific credentials\n\tconst eventsBackend = options?.eventsBackend;\n\tif (eventsBackend === 'sns') {\n\t\t// LocalStack credentials with LSIA-prefixed access key\n\t\tserviceCredentials.localstack = generateLocalStackCredentials();\n\t}\n\n\tconst urls = generateConnectionUrls(serviceCredentials, eventsBackend);\n\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\teventsBackend,\n\t\tservices: serviceCredentials,\n\t\turls,\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Rotate password for a specific service.\n */\nexport function rotateServicePassword(\n\tsecrets: StageSecrets,\n\tservice: ComposeServiceName,\n): StageSecrets {\n\tconst currentCreds = secrets.services[service];\n\tif (!currentCreds) {\n\t\tthrow new Error(`Service \"${service}\" not configured in secrets`);\n\t}\n\n\tconst newCreds: ServiceCredentials = {\n\t\t...currentCreds,\n\t\tpassword: generateSecurePassword(),\n\t};\n\n\tconst newServices = {\n\t\t...secrets.services,\n\t\t[service]: newCreds,\n\t};\n\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tservices: newServices,\n\t\turls: generateConnectionUrls(newServices, secrets.eventsBackend),\n\t};\n}\n"],"mappings":"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"}