@geekmidas/cli 10.0.0-alpha.6 → 10.0.0-alpha.61

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 (303) hide show
  1. package/README.md +225 -268
  2. package/bin/adjacent-tsconfig.mjs +97 -0
  3. package/bin/gkm.mjs +16 -2
  4. package/bin/owning-tsconfig-jsx-register.mjs +18 -0
  5. package/bin/owning-tsconfig-jsx.mjs +108 -0
  6. package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-CgDaD8VR.cjs} +21 -22
  7. package/dist/HostingerProvider-CgDaD8VR.cjs.map +1 -0
  8. package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-DI_Pr2VE.mjs} +20 -21
  9. package/dist/HostingerProvider-DI_Pr2VE.mjs.map +1 -0
  10. package/dist/LegacyStateStore-C7vFAxbu.mjs +89 -0
  11. package/dist/LegacyStateStore-C7vFAxbu.mjs.map +1 -0
  12. package/dist/LegacyStateStore-JbBVFp_2.cjs +89 -0
  13. package/dist/LegacyStateStore-JbBVFp_2.cjs.map +1 -0
  14. package/dist/LocalStateStore-DHj_-CQB.cjs +176 -0
  15. package/dist/LocalStateStore-DHj_-CQB.cjs.map +1 -0
  16. package/dist/LocalStateStore-DfUwQsL8.mjs +165 -0
  17. package/dist/LocalStateStore-DfUwQsL8.mjs.map +1 -0
  18. package/dist/{openapi-D-dx9Akj.cjs → OpenApiTsGenerator-BSrgw7B0.cjs} +360 -470
  19. package/dist/OpenApiTsGenerator-BSrgw7B0.cjs.map +1 -0
  20. package/dist/{openapi-Bw8DWFWz.mjs → OpenApiTsGenerator-q5B0z8Xg.mjs} +325 -404
  21. package/dist/OpenApiTsGenerator-q5B0z8Xg.mjs.map +1 -0
  22. package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
  23. package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
  24. package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
  25. package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
  26. package/dist/S3StateStore-Dq6hHtzY.mjs +161 -0
  27. package/dist/S3StateStore-Dq6hHtzY.mjs.map +1 -0
  28. package/dist/S3StateStore-xqnpUrzy.cjs +160 -0
  29. package/dist/S3StateStore-xqnpUrzy.cjs.map +1 -0
  30. package/dist/SSMStateStore-5dBRwVlJ.cjs +153 -0
  31. package/dist/SSMStateStore-5dBRwVlJ.cjs.map +1 -0
  32. package/dist/SSMStateStore-SUmvAbLi.mjs +154 -0
  33. package/dist/SSMStateStore-SUmvAbLi.mjs.map +1 -0
  34. package/dist/appPorts-C9z9Pzn9.mjs +223 -0
  35. package/dist/appPorts-C9z9Pzn9.mjs.map +1 -0
  36. package/dist/appPorts-Cu79hJF6.cjs +264 -0
  37. package/dist/appPorts-Cu79hJF6.cjs.map +1 -0
  38. package/dist/aws-DkHdTV-p.cjs +61 -0
  39. package/dist/aws-DkHdTV-p.cjs.map +1 -0
  40. package/dist/aws-DojzQnLd.mjs +61 -0
  41. package/dist/aws-DojzQnLd.mjs.map +1 -0
  42. package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
  43. package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
  44. package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
  45. package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
  46. package/dist/{bundler-Be8Zg4Wd.mjs → bundler-B5VsKW-_.mjs} +18 -29
  47. package/dist/bundler-B5VsKW-_.mjs.map +1 -0
  48. package/dist/{bundler-BY1OmV1e.cjs → bundler-DXATpO1T.cjs} +20 -32
  49. package/dist/bundler-DXATpO1T.cjs.map +1 -0
  50. package/dist/cli-DEA_J3Sa.mjs +69 -0
  51. package/dist/cli-DEA_J3Sa.mjs.map +1 -0
  52. package/dist/cli-DQdrVkj2.cjs +69 -0
  53. package/dist/cli-DQdrVkj2.cjs.map +1 -0
  54. package/dist/{config-CxP7P-qG.mjs → config-C5-EEdmr.mjs} +144 -127
  55. package/dist/config-C5-EEdmr.mjs.map +1 -0
  56. package/dist/{config-CNz5KaSR.cjs → config-DXnkCEq0.cjs} +212 -184
  57. package/dist/config-DXnkCEq0.cjs.map +1 -0
  58. package/dist/config.cjs +9 -6
  59. package/dist/config.d.cts +124 -33
  60. package/dist/config.d.cts.map +1 -1
  61. package/dist/config.d.mts +124 -33
  62. package/dist/config.d.mts.map +1 -1
  63. package/dist/config.mjs +3 -5
  64. package/dist/deploy/public.cjs +22 -0
  65. package/dist/deploy/public.d.cts +459 -0
  66. package/dist/deploy/public.d.cts.map +1 -0
  67. package/dist/deploy/public.d.mts +459 -0
  68. package/dist/deploy/public.d.mts.map +1 -0
  69. package/dist/deploy/public.mjs +5 -0
  70. package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
  71. package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
  72. package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
  73. package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
  74. package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
  75. package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
  76. package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
  77. package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
  78. package/dist/deploy/sniffer-hooks.cjs +13 -7
  79. package/dist/deploy/sniffer-hooks.cjs.map +1 -1
  80. package/dist/deploy/sniffer-hooks.d.cts +1 -2
  81. package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
  82. package/dist/deploy/sniffer-hooks.d.mts +1 -2
  83. package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
  84. package/dist/deploy/sniffer-hooks.mjs +10 -3
  85. package/dist/deploy/sniffer-hooks.mjs.map +1 -1
  86. package/dist/deploy/sniffer-loader.cjs +16 -7
  87. package/dist/deploy/sniffer-loader.cjs.map +1 -1
  88. package/dist/deploy/sniffer-loader.d.cts +1 -1
  89. package/dist/deploy/sniffer-loader.d.mts +1 -1
  90. package/dist/deploy/sniffer-loader.mjs +14 -3
  91. package/dist/deploy/sniffer-loader.mjs.map +1 -1
  92. package/dist/deploy/sniffer-routes-worker.cjs +11 -11
  93. package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
  94. package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
  95. package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
  96. package/dist/deploy/sniffer-routes-worker.mjs +3 -3
  97. package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
  98. package/dist/deploy/sniffer-worker.cjs +15 -6
  99. package/dist/deploy/sniffer-worker.cjs.map +1 -1
  100. package/dist/deploy/sniffer-worker.d.cts +1 -3
  101. package/dist/deploy/sniffer-worker.d.cts.map +1 -1
  102. package/dist/deploy/sniffer-worker.d.mts +1 -3
  103. package/dist/deploy/sniffer-worker.d.mts.map +1 -1
  104. package/dist/deploy/sniffer-worker.mjs +15 -4
  105. package/dist/deploy/sniffer-worker.mjs.map +1 -1
  106. package/dist/deploy-BJcLmVTc.mjs +3282 -0
  107. package/dist/deploy-BJcLmVTc.mjs.map +1 -0
  108. package/dist/deploy-BbJdgQ2b.cjs +3365 -0
  109. package/dist/deploy-BbJdgQ2b.cjs.map +1 -0
  110. package/dist/{discover-DTmFeloL.cjs → discover-Bc-4eA_p.cjs} +131 -103
  111. package/dist/discover-Bc-4eA_p.cjs.map +1 -0
  112. package/dist/{discover-CAlFgYec.mjs → discover-FrYvzkeC.mjs} +91 -58
  113. package/dist/discover-FrYvzkeC.mjs.map +1 -0
  114. package/dist/{emit-CiZS_5Ub.mjs → emit-C1e8qD08.mjs} +13 -26
  115. package/dist/emit-C1e8qD08.mjs.map +1 -0
  116. package/dist/{emit-CbcRolsa.cjs → emit-Dmc1nvdS.cjs} +31 -57
  117. package/dist/emit-Dmc1nvdS.cjs.map +1 -0
  118. package/dist/encryption-DaA9gRXr.mjs +90 -0
  119. package/dist/encryption-DaA9gRXr.mjs.map +1 -0
  120. package/dist/encryption-ojzUo7-z.cjs +91 -0
  121. package/dist/encryption-ojzUo7-z.cjs.map +1 -0
  122. package/dist/fakes-C9RiT6XG.mjs +125 -0
  123. package/dist/fakes-C9RiT6XG.mjs.map +1 -0
  124. package/dist/fakes-zW-jNAbQ.cjs +148 -0
  125. package/dist/fakes-zW-jNAbQ.cjs.map +1 -0
  126. package/dist/github-Cq57poPj.cjs +213 -0
  127. package/dist/github-Cq57poPj.cjs.map +1 -0
  128. package/dist/github-DmCj2u95.mjs +213 -0
  129. package/dist/github-DmCj2u95.mjs.map +1 -0
  130. package/dist/home-DOzBaWaL.cjs +33 -0
  131. package/dist/home-DOzBaWaL.cjs.map +1 -0
  132. package/dist/home-GI7pJl1M.mjs +22 -0
  133. package/dist/home-GI7pJl1M.mjs.map +1 -0
  134. package/dist/{index-DEbaleny.d.mts → index-Da1-oa9W.d.mts} +4 -23
  135. package/dist/index-Da1-oa9W.d.mts.map +1 -0
  136. package/dist/{index-BGGdjueF.d.cts → index-UiWCaGzU.d.cts} +4 -23
  137. package/dist/index-UiWCaGzU.d.cts.map +1 -0
  138. package/dist/index.cjs +10712 -12248
  139. package/dist/index.cjs.map +1 -1
  140. package/dist/index.d.cts +1 -1
  141. package/dist/index.d.mts +1 -1
  142. package/dist/index.mjs +10480 -12040
  143. package/dist/index.mjs.map +1 -1
  144. package/dist/migrate-5_J5JySj.mjs +265 -0
  145. package/dist/migrate-5_J5JySj.mjs.map +1 -0
  146. package/dist/migrate-BScRpCEx.cjs +270 -0
  147. package/dist/migrate-BScRpCEx.cjs.map +1 -0
  148. package/dist/openapi.cjs +198 -10
  149. package/dist/openapi.cjs.map +1 -0
  150. package/dist/openapi.d.cts +21 -19
  151. package/dist/openapi.d.cts.map +1 -1
  152. package/dist/openapi.d.mts +21 -19
  153. package/dist/openapi.d.mts.map +1 -1
  154. package/dist/openapi.mjs +193 -5
  155. package/dist/openapi.mjs.map +1 -0
  156. package/dist/ready-DQIy21W5.mjs +760 -0
  157. package/dist/ready-DQIy21W5.mjs.map +1 -0
  158. package/dist/ready-XlNx-Gbo.cjs +807 -0
  159. package/dist/ready-XlNx-Gbo.cjs.map +1 -0
  160. package/dist/reconcile/public.cjs +3 -5
  161. package/dist/reconcile/public.d.cts +30 -33
  162. package/dist/reconcile/public.d.cts.map +1 -1
  163. package/dist/reconcile/public.d.mts +30 -33
  164. package/dist/reconcile/public.d.mts.map +1 -1
  165. package/dist/reconcile/public.mjs +3 -4
  166. package/dist/{reconcile-CFs8C_1a.mjs → reconcile--I7V4Klj.mjs} +4 -7
  167. package/dist/reconcile--I7V4Klj.mjs.map +1 -0
  168. package/dist/{reconcile-D0pimNiy.cjs → reconcile-CuCQBa5-.cjs} +5 -8
  169. package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
  170. package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
  171. package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
  172. package/dist/sniffer-CQmJnNJM.cjs +7115 -0
  173. package/dist/sniffer-CQmJnNJM.cjs.map +1 -0
  174. package/dist/sniffer-bdleks5l.mjs +6561 -0
  175. package/dist/sniffer-bdleks5l.mjs.map +1 -0
  176. package/dist/storage-BzaUxmqe.mjs +124 -0
  177. package/dist/storage-BzaUxmqe.mjs.map +1 -0
  178. package/dist/storage-CSGoC3bK.cjs +152 -0
  179. package/dist/storage-CSGoC3bK.cjs.map +1 -0
  180. package/dist/store-BS8A7NcA.mjs +415 -0
  181. package/dist/store-BS8A7NcA.mjs.map +1 -0
  182. package/dist/store-KvNQwYaB.cjs +510 -0
  183. package/dist/store-KvNQwYaB.cjs.map +1 -0
  184. package/dist/types-Bp1mVxG0.d.mts +6410 -0
  185. package/dist/types-Bp1mVxG0.d.mts.map +1 -0
  186. package/dist/types-efNLu9xu.d.cts +6411 -0
  187. package/dist/types-efNLu9xu.d.cts.map +1 -0
  188. package/dist/vitest.cjs +65 -0
  189. package/dist/vitest.cjs.map +1 -0
  190. package/dist/vitest.d.cts +29 -0
  191. package/dist/vitest.d.cts.map +1 -0
  192. package/dist/vitest.d.mts +29 -0
  193. package/dist/vitest.d.mts.map +1 -0
  194. package/dist/vitest.mjs +65 -0
  195. package/dist/vitest.mjs.map +1 -0
  196. package/dist/workspace/index.cjs +3 -4
  197. package/dist/workspace/index.d.cts +3 -3
  198. package/dist/workspace/index.d.mts +3 -3
  199. package/dist/workspace/index.mjs +2 -3
  200. package/dist/{workspace-LKiNyaDy.cjs → workspace-B64Xk1Mb.cjs} +1070 -1118
  201. package/dist/workspace-B64Xk1Mb.cjs.map +1 -0
  202. package/dist/{workspace-DIuZx-cc.mjs → workspace-Bp1xVaOz.mjs} +901 -972
  203. package/dist/workspace-Bp1xVaOz.mjs.map +1 -0
  204. package/package.json +38 -20
  205. package/dist/CachedStateProvider-BLo7zv7S.cjs +0 -3
  206. package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
  207. package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
  208. package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
  209. package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
  210. package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
  211. package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
  212. package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
  213. package/dist/LocalStateProvider-Bn6tS-oL.mjs +0 -42
  214. package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
  215. package/dist/LocalStateProvider-Db9SEjyH.cjs +0 -43
  216. package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
  217. package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
  218. package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
  219. package/dist/SSMStateProvider-BRt2py-A.mjs +0 -66
  220. package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
  221. package/dist/SSMStateProvider-DyX8s9HI.cjs +0 -66
  222. package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
  223. package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
  224. package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
  225. package/dist/bundler-BY1OmV1e.cjs.map +0 -1
  226. package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
  227. package/dist/chunk-Duj1WY3L.mjs +0 -7
  228. package/dist/config-CNz5KaSR.cjs.map +0 -1
  229. package/dist/config-CxP7P-qG.mjs.map +0 -1
  230. package/dist/credentials-B5zRHdAl.mjs +0 -141
  231. package/dist/credentials-B5zRHdAl.mjs.map +0 -1
  232. package/dist/credentials-BK3Z93MX.mjs +0 -2548
  233. package/dist/credentials-BK3Z93MX.mjs.map +0 -1
  234. package/dist/credentials-BapzjyV5.cjs +0 -195
  235. package/dist/credentials-BapzjyV5.cjs.map +0 -1
  236. package/dist/credentials-DzsXZzkx.cjs +0 -2680
  237. package/dist/credentials-DzsXZzkx.cjs.map +0 -1
  238. package/dist/discover-CAlFgYec.mjs.map +0 -1
  239. package/dist/discover-DTmFeloL.cjs.map +0 -1
  240. package/dist/dokploy-api-BS2xnJLZ.mjs +0 -3
  241. package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
  242. package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
  243. package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
  244. package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
  245. package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
  246. package/dist/emit-CbcRolsa.cjs.map +0 -1
  247. package/dist/emit-CiZS_5Ub.mjs.map +0 -1
  248. package/dist/encryption-C6jaU0hE.cjs +0 -54
  249. package/dist/encryption-C6jaU0hE.cjs.map +0 -1
  250. package/dist/encryption-CHzzeR8a.cjs +0 -4
  251. package/dist/encryption-CQUncu7v.mjs +0 -3
  252. package/dist/encryption-D-abyhZx.mjs +0 -42
  253. package/dist/encryption-D-abyhZx.mjs.map +0 -1
  254. package/dist/fullstack-secrets-C-GGohke.cjs +0 -111
  255. package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
  256. package/dist/fullstack-secrets-D1o9-G5l.mjs +0 -87
  257. package/dist/fullstack-secrets-D1o9-G5l.mjs.map +0 -1
  258. package/dist/generator-CNzBw7g5.cjs +0 -289
  259. package/dist/generator-CNzBw7g5.cjs.map +0 -1
  260. package/dist/generator-VE2JrFlY.mjs +0 -253
  261. package/dist/generator-VE2JrFlY.mjs.map +0 -1
  262. package/dist/index-BGGdjueF.d.cts.map +0 -1
  263. package/dist/index-DEbaleny.d.mts.map +0 -1
  264. package/dist/openapi-Bw8DWFWz.mjs.map +0 -1
  265. package/dist/openapi-D-dx9Akj.cjs.map +0 -1
  266. package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
  267. package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
  268. package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
  269. package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
  270. package/dist/openapi-react-query.cjs +0 -4
  271. package/dist/openapi-react-query.d.cts +0 -11
  272. package/dist/openapi-react-query.d.cts.map +0 -1
  273. package/dist/openapi-react-query.d.mts +0 -11
  274. package/dist/openapi-react-query.d.mts.map +0 -1
  275. package/dist/openapi-react-query.mjs +0 -4
  276. package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
  277. package/dist/reconcile-D0pimNiy.cjs.map +0 -1
  278. package/dist/setup-8IWPsRIN.cjs +0 -11
  279. package/dist/setup-BWMhbW-p.mjs +0 -443
  280. package/dist/setup-BWMhbW-p.mjs.map +0 -1
  281. package/dist/setup-D7L4BgBT.cjs +0 -473
  282. package/dist/setup-D7L4BgBT.cjs.map +0 -1
  283. package/dist/setup-DGIPd2fh.mjs +0 -11
  284. package/dist/storage-BJmzYxt8.cjs +0 -363
  285. package/dist/storage-BJmzYxt8.cjs.map +0 -1
  286. package/dist/storage-BM9hGpgv.cjs +0 -7
  287. package/dist/storage-Bi24WfoW.mjs +0 -297
  288. package/dist/storage-Bi24WfoW.mjs.map +0 -1
  289. package/dist/storage-CYlGwWTj.mjs +0 -3
  290. package/dist/sync-CmEuNZsB.mjs +0 -76
  291. package/dist/sync-CmEuNZsB.mjs.map +0 -1
  292. package/dist/sync-D306yOWu.cjs +0 -4
  293. package/dist/sync-SizaChmC.cjs +0 -93
  294. package/dist/sync-SizaChmC.cjs.map +0 -1
  295. package/dist/sync-hOqAWHR6.mjs +0 -3
  296. package/dist/types-BLSNdrk0.d.cts +0 -2176
  297. package/dist/types-BLSNdrk0.d.cts.map +0 -1
  298. package/dist/types-C3POXhw3.d.mts +0 -2176
  299. package/dist/types-C3POXhw3.d.mts.map +0 -1
  300. package/dist/workspace-CGaUAw5A.cjs +0 -9
  301. package/dist/workspace-CnTqIlB3.mjs +0 -8
  302. package/dist/workspace-DIuZx-cc.mjs.map +0 -1
  303. package/dist/workspace-LKiNyaDy.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sniffer-CQmJnNJM.cjs","names":["readline","join","gkmHome","existsSync","readFile","writeFile","logger","ask","logger","resolve","existsSync","createServer","readFile","join","mkdir","writeFile","toEmbeddableSecrets","dirname","loadWorkspaceAppInfo","NotInAnApp","ConfigNotFound","loadWorkspaceConfig","secretsStoreFor","FileSecretsStore","dirname","fileURLToPath","join","existsSync","pathToFileURL","DEFAULT_POSTGRES_VERSION","kebabCase","DEFAULT_CACHE","DEFAULT_STORAGE","DEFAULT_EVENTS","DEFAULT_CACHE","providedKeyFor","dependentsOf","publicEnvFor","appKey","LOCAL_USER","LOCAL_TOKEN","provideKey","cookieDomain","mobileOrigins","ownerRole","schemeBase","externalApiUrl","readerRole","cacheTable","snsUrl","sqsUrl","createHash","formatKeyring","provisionOrder","appKey","publicEnvFor","dependenciesOf","LOCAL_USER","Client","S3Client","HeadBucketCommand","CreateBucketCommand","GetBucketPolicyCommand","PutBucketPolicyCommand","SNSClient","SQSClient","GetTopicAttributesCommand","CreateTopicCommand","GetQueueUrlCommand","CreateQueueCommand","stringify","run","promisify","execFile","join","dirname","existsSync","spawn","mkdir","networkInterfaces","createHash","createServer","cacheTable","cacheTableStatements","ownerRole","readerRole","roleStatements","createHash","readFile","mkdir","dirname","writeFile","rm","join","provisionOrder","join","existsSync","TEST_STAGE","dirname","readdir","rm","readFile","mkdir","writeFile","allConstructGlobs","discover","provisionOrder","readFakes","appKey","spawn","randomBytes","createCipheriv","createHash","generateKeyring","randomBytes","userInfo","randomUUID","hostname","createHash","getPublicEnvPrefix","parse","existsSync","join","dirname","output","loadWorkspaceConfig","loadConfig","join","existsSync","mkdir","writeFile","basename","readFileSync","appKey","publicEnvFor","__filename","fileURLToPath","__dirname","dirname","createRequire","resolve","existsSync","getPublicEnvPrefix","spawn","output","pathToFileURL"],"sources":["../src/prompt.ts","../src/auth/credentials.ts","../src/auth/index.ts","../src/credentials/index.ts","../src/workspace/client-generator.ts","../src/owningTsconfigJsx.ts","../src/reconcile/containers.ts","../src/workspace/backends.ts","../src/reconcile/caddyfile.ts","../src/reconcile/emulator.ts","../src/workspace/rootSite.ts","../src/reconcile/plan.ts","../src/reconcile/env.ts","../src/reconcile/apps.ts","../src/reconcile/clients.ts","../src/reconcile/compose.ts","../src/reconcile/docker.ts","../src/reconcile/lan.ts","../src/reconcile/ports.ts","../src/reconcile/provision.ts","../src/reconcile/state.ts","../src/reconcile/index.ts","../src/reconcile/workspace.ts","../src/run.ts","../src/secrets/encryption.ts","../src/deploy/generated.ts","../src/deploy/StateProvider.ts","../src/deploy/StateStore.ts","../src/deploy/state.ts","../src/deploy/domain.ts","../src/docker/imageRef.ts","../src/docker/templates.ts","../src/docker/index.ts","../src/deploy/dokploy-api.ts","../src/deploy/sniffer.ts"],"sourcesContent":["/**\n * Asking the person at the terminal.\n *\n * CLI layer only: a command's action may prompt, nothing it calls may. A\n * library call (`deploy()`) that needs something it was not given raises a\n * named error instead, and the command decides whether to ask.\n */\n\nimport { stdin as input, stdout as output } from 'node:process';\nimport * as readline from 'node:readline/promises';\n\n/** A prompt was needed with no terminal to ask at. */\nexport class PromptNeedsTerminal extends Error {\n\tconstructor(\n\t\treadonly question: string,\n\t\t/** What to do instead — the flag or variable that answers it. */\n\t\treadonly instead: string,\n\t) {\n\t\tsuper(`Interactive input required. ${instead}`);\n\t\tthis.name = 'PromptNeedsTerminal';\n\t}\n}\n\n/** Whether there is a person at a terminal to ask. */\nexport function canPrompt(): boolean {\n\treturn process.stdin.isTTY === true;\n}\n\n/**\n * Ask `message` and resolve with the line typed — without echoing it when\n * `hidden`. Ctrl+C at a hidden prompt exits, as it would anywhere else in a\n * terminal program: raw mode has taken the signal away from the shell.\n */\nexport async function prompt(\n\tmessage: string,\n\toptions: { hidden?: boolean; instead: string },\n): Promise<string> {\n\tif (!canPrompt()) {\n\t\tthrow new PromptNeedsTerminal(message, options.instead);\n\t}\n\n\tif (options.hidden) {\n\t\t// For hidden input, use raw mode directly without readline\n\t\tprocess.stdout.write(message);\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlet value = '';\n\n\t\t\tconst cleanup = () => {\n\t\t\t\tprocess.stdin.setRawMode(false);\n\t\t\t\tprocess.stdin.pause();\n\t\t\t\tprocess.stdin.removeListener('data', onData);\n\t\t\t\tprocess.stdin.removeListener('error', onError);\n\t\t\t};\n\n\t\t\tconst onError = (err: Error) => {\n\t\t\t\tcleanup();\n\t\t\t\treject(err);\n\t\t\t};\n\n\t\t\tconst onData = (char: Buffer) => {\n\t\t\t\tconst c = char.toString();\n\n\t\t\t\tif (c === '\\n' || c === '\\r') {\n\t\t\t\t\tcleanup();\n\t\t\t\t\tprocess.stdout.write('\\n');\n\t\t\t\t\tresolve(value);\n\t\t\t\t} else if (c === '\\u0003') {\n\t\t\t\t\t// Ctrl+C\n\t\t\t\t\tcleanup();\n\t\t\t\t\tprocess.stdout.write('\\n');\n\t\t\t\t\tprocess.exit(1);\n\t\t\t\t} else if (c === '\\u007F' || c === '\\b') {\n\t\t\t\t\t// Backspace\n\t\t\t\t\tif (value.length > 0) {\n\t\t\t\t\t\tvalue = value.slice(0, -1);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tvalue += c;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tprocess.stdin.setRawMode(true);\n\t\t\tprocess.stdin.resume();\n\t\t\tprocess.stdin.on('data', onData);\n\t\t\tprocess.stdin.on('error', onError);\n\t\t});\n\t}\n\n\t// For visible input, use readline\n\tconst rl = readline.createInterface({ input, output });\n\ttry {\n\t\treturn await rl.question(message);\n\t} finally {\n\t\trl.close();\n\t}\n}\n","import { existsSync, mkdirSync } from 'node:fs';\nimport { readFile, unlink, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { gkmHome } from '../home';\n\n/**\n * Stored credentials for various services\n */\nexport interface StoredCredentials {\n\tdokploy?: {\n\t\t/** API token */\n\t\ttoken: string;\n\t\t/** Dokploy endpoint URL */\n\t\tendpoint: string;\n\t\t/** Registry ID in Dokploy (for Docker image pulls) */\n\t\tregistryId?: string;\n\t\t/** When the credentials were stored */\n\t\tstoredAt: string;\n\t};\n\thostinger?: {\n\t\t/** API token from hpanel.hostinger.com/profile/api */\n\t\ttoken: string;\n\t\t/** When the credentials were stored */\n\t\tstoredAt: string;\n\t};\n}\n\n/**\n * Options for credential operations\n */\nexport interface CredentialOptions {\n\t/**\n\t * A directory whose `.gkm` holds the credentials. Default: the CLI's home\n\t * (`GKM_HOME`, else `~/.gkm`).\n\t */\n\troot?: string;\n\t/** The CLI's home itself, holding `credentials.json`. Wins over `root`. */\n\thome?: string;\n}\n\n/**\n * Get the path to the credentials directory\n */\nexport function getCredentialsDir(options?: CredentialOptions): string {\n\tif (options?.home) return options.home;\n\treturn options?.root ? join(options.root, '.gkm') : gkmHome();\n}\n\n/**\n * Get the path to the credentials file\n */\nexport function getCredentialsPath(options?: CredentialOptions): string {\n\treturn join(getCredentialsDir(options), 'credentials.json');\n}\n\n/**\n * Ensure the credentials directory exists\n */\nfunction ensureCredentialsDir(options?: CredentialOptions): void {\n\tconst dir = getCredentialsDir(options);\n\tif (!existsSync(dir)) {\n\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t}\n}\n\n/**\n * Read stored credentials from disk\n */\nexport async function readCredentials(\n\toptions?: CredentialOptions,\n): Promise<StoredCredentials> {\n\tconst path = getCredentialsPath(options);\n\n\tif (!existsSync(path)) {\n\t\treturn {};\n\t}\n\n\ttry {\n\t\tconst content = await readFile(path, 'utf-8');\n\t\treturn JSON.parse(content) as StoredCredentials;\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n/**\n * Write credentials to disk\n */\nexport async function writeCredentials(\n\tcredentials: StoredCredentials,\n\toptions?: CredentialOptions,\n): Promise<void> {\n\tensureCredentialsDir(options);\n\tconst path = getCredentialsPath(options);\n\n\tawait writeFile(path, JSON.stringify(credentials, null, 2), {\n\t\tmode: 0o600, // Owner read/write only\n\t});\n}\n\n/**\n * Store Dokploy credentials\n */\nexport async function storeDokployCredentials(\n\ttoken: string,\n\tendpoint: string,\n\toptions?: CredentialOptions,\n): Promise<void> {\n\tconst credentials = await readCredentials(options);\n\n\tcredentials.dokploy = {\n\t\ttoken,\n\t\tendpoint,\n\t\tstoredAt: new Date().toISOString(),\n\t};\n\n\tawait writeCredentials(credentials, options);\n}\n\n/**\n * Get stored Dokploy credentials\n */\nexport async function getDokployCredentials(\n\toptions?: CredentialOptions,\n): Promise<{\n\ttoken: string;\n\tendpoint: string;\n\tregistryId?: string;\n} | null> {\n\tconst credentials = await readCredentials(options);\n\n\t// The environment first, and each half independently.\n\t//\n\t// `getDokployToken` has always read `DOKPLOY_API_TOKEN` while this — the\n\t// function the deploy path actually calls — read only the file. So a CI job\n\t// given the token as a secret had a token and nowhere to send it, and the\n\t// only way through was committing a credentials file or running an\n\t// interactive login on the runner.\n\t//\n\t// Read separately rather than as a pair because they arrive separately: a\n\t// token is a secret and belongs in the environment, while an endpoint is\n\t// configuration and usually belongs in `deploy.dokploy.endpoint`. Requiring\n\t// both from the same place is what made either one useless alone.\n\tconst token = process.env.DOKPLOY_API_TOKEN ?? credentials.dokploy?.token;\n\tconst endpoint =\n\t\tprocess.env.DOKPLOY_ENDPOINT ?? credentials.dokploy?.endpoint;\n\n\tif (!token || !endpoint) return null;\n\n\treturn {\n\t\ttoken,\n\t\tendpoint: endpoint.replace(/\\/$/, ''),\n\t\t...(credentials.dokploy?.registryId\n\t\t\t? { registryId: credentials.dokploy.registryId }\n\t\t\t: {}),\n\t};\n}\n\n/**\n * Remove Dokploy credentials\n */\nexport async function removeDokployCredentials(\n\toptions?: CredentialOptions,\n): Promise<boolean> {\n\tconst credentials = await readCredentials(options);\n\n\tif (!credentials.dokploy) {\n\t\treturn false;\n\t}\n\n\tdelete credentials.dokploy;\n\tawait writeCredentials(credentials, options);\n\treturn true;\n}\n\n/**\n * Remove all stored credentials\n */\nexport async function removeAllCredentials(\n\toptions?: CredentialOptions,\n): Promise<void> {\n\tconst path = getCredentialsPath(options);\n\n\tif (existsSync(path)) {\n\t\tawait unlink(path);\n\t}\n}\n\n/**\n * Get Dokploy API token, checking stored credentials first, then environment\n */\nexport async function getDokployToken(\n\toptions?: CredentialOptions,\n): Promise<string | null> {\n\t// First check environment variable (takes precedence)\n\tconst envToken = process.env.DOKPLOY_API_TOKEN;\n\tif (envToken) {\n\t\treturn envToken;\n\t}\n\n\t// Then check stored credentials\n\tconst stored = await getDokployCredentials(options);\n\tif (stored) {\n\t\treturn stored.token;\n\t}\n\n\treturn null;\n}\n\n/**\n * Get Dokploy endpoint from stored credentials\n */\nexport async function getDokployEndpoint(\n\toptions?: CredentialOptions,\n): Promise<string | null> {\n\tconst stored = await getDokployCredentials(options);\n\treturn stored?.endpoint ?? null;\n}\n\n/**\n * Store Dokploy registry ID\n */\nexport async function storeDokployRegistryId(\n\tregistryId: string,\n\toptions?: CredentialOptions,\n): Promise<void> {\n\tconst credentials = await readCredentials(options);\n\n\tif (!credentials.dokploy) {\n\t\tthrow new Error(\n\t\t\t'Dokploy credentials not found. Run \"gkm login --service dokploy\" first.',\n\t\t);\n\t}\n\n\tcredentials.dokploy.registryId = registryId;\n\tawait writeCredentials(credentials, options);\n}\n\n/**\n * Get Dokploy registry ID from stored credentials\n */\nexport async function getDokployRegistryId(\n\toptions?: CredentialOptions,\n): Promise<string | undefined> {\n\tconst stored = await getDokployCredentials(options);\n\treturn stored?.registryId ?? undefined;\n}\n\n// ============================================\n// Hostinger credentials\n// ============================================\n\n/**\n * Store Hostinger API token\n *\n * @param token - API token from hpanel.hostinger.com/profile/api\n */\nexport async function storeHostingerToken(\n\ttoken: string,\n\toptions?: CredentialOptions,\n): Promise<void> {\n\tconst credentials = await readCredentials(options);\n\n\tcredentials.hostinger = {\n\t\ttoken,\n\t\tstoredAt: new Date().toISOString(),\n\t};\n\n\tawait writeCredentials(credentials, options);\n}\n\n/**\n * Get stored Hostinger API token\n *\n * Checks environment variable first (HOSTINGER_API_TOKEN),\n * then falls back to stored credentials.\n */\nexport async function getHostingerToken(\n\toptions?: CredentialOptions,\n): Promise<string | null> {\n\t// First check environment variable (takes precedence)\n\tconst envToken = process.env.HOSTINGER_API_TOKEN;\n\tif (envToken) {\n\t\treturn envToken;\n\t}\n\n\t// Then check stored credentials\n\tconst credentials = await readCredentials(options);\n\treturn credentials.hostinger?.token ?? null;\n}\n\n/**\n * Remove Hostinger credentials\n */\nexport async function removeHostingerCredentials(\n\toptions?: CredentialOptions,\n): Promise<boolean> {\n\tconst credentials = await readCredentials(options);\n\n\tif (!credentials.hostinger) {\n\t\treturn false;\n\t}\n\n\tdelete credentials.hostinger;\n\tawait writeCredentials(credentials, options);\n\treturn true;\n}\n","import { prompt as ask } from '../prompt';\nimport {\n\tgetCredentialsPath,\n\tgetDokployCredentials,\n\tremoveDokployCredentials,\n\tstoreDokployCredentials,\n\tstoreHostingerToken,\n} from './credentials';\n\nconst logger = console;\n\nexport interface LoginOptions {\n\t/** Which provider's credentials to store — the same word `gkm deploy` uses. */\n\tprovider: 'dokploy' | 'hostinger';\n\t/** API token (if not provided, will prompt) */\n\ttoken?: string;\n\t/** Endpoint URL */\n\tendpoint?: string;\n}\n\nexport interface LogoutOptions {\n\t/** Whose credentials to remove — `all` clears every stored provider. */\n\tprovider?: 'dokploy' | 'all';\n}\n\n/**\n * Validate Dokploy token by making a test API call\n */\nexport async function validateDokployToken(\n\tendpoint: string,\n\ttoken: string,\n): Promise<boolean> {\n\tconst { DokployApi } = await import('../deploy/dokploy-api');\n\tconst api = new DokployApi({ baseUrl: endpoint, token });\n\treturn api.validateToken();\n}\n\n/** What answers a login prompt without a terminal. */\nconst LOGIN_INSTEAD = 'Please provide --token option.';\n\n/**\n * Login to a service\n */\nexport async function loginCommand(options: LoginOptions): Promise<void> {\n\tconst {\n\t\tprovider,\n\t\ttoken: providedToken,\n\t\tendpoint: providedEndpoint,\n\t} = options;\n\n\tif (provider === 'dokploy') {\n\t\tlogger.log('\\n🔐 Logging in to Dokploy...\\n');\n\n\t\t// Get endpoint\n\t\tlet endpoint = providedEndpoint;\n\t\tif (!endpoint) {\n\t\t\tendpoint = await ask(\n\t\t\t\t'Dokploy URL (e.g., https://dokploy.example.com): ',\n\t\t\t\t{\n\t\t\t\t\tinstead: LOGIN_INSTEAD,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\t// Normalize endpoint (remove trailing slash)\n\t\tendpoint = endpoint.replace(/\\/$/, '');\n\n\t\t// Validate endpoint format\n\t\ttry {\n\t\t\tnew URL(endpoint);\n\t\t} catch {\n\t\t\tlogger.error('Invalid URL format');\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\t// Get token\n\t\tlet token = providedToken;\n\t\tif (!token) {\n\t\t\tlogger.log(`\\nGenerate a token at: ${endpoint}/settings/profile\\n`);\n\t\t\ttoken = await ask('API Token: ', {\n\t\t\t\thidden: true,\n\t\t\t\tinstead: LOGIN_INSTEAD,\n\t\t\t});\n\t\t}\n\n\t\tif (!token) {\n\t\t\tlogger.error('Token is required');\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\t// Validate token\n\t\tlogger.log('\\nValidating credentials...');\n\t\tconst isValid = await validateDokployToken(endpoint, token);\n\n\t\tif (!isValid) {\n\t\t\tlogger.error(\n\t\t\t\t'\\n✗ Invalid credentials. Please check your token and try again.',\n\t\t\t);\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\t// Store credentials\n\t\tawait storeDokployCredentials(token, endpoint);\n\n\t\tlogger.log('\\n✓ Successfully logged in to Dokploy!');\n\t\tlogger.log(` Endpoint: ${endpoint}`);\n\t\tlogger.log(` Credentials stored in: ${getCredentialsPath()}`);\n\t\tlogger.log(\n\t\t\t'\\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.',\n\t\t);\n\t}\n\n\tif (provider === 'hostinger') {\n\t\t// The DNS provider has told people to run this since it was written, and\n\t\t// this branch did not exist — so the only way to supply the token was the\n\t\t// environment variable, which the message does not mention.\n\t\tlogger.log('\\n🔐 Logging in to Hostinger...\\n');\n\n\t\tlet token = providedToken;\n\t\tif (!token) {\n\t\t\tlogger.log(\n\t\t\t\t'\\nGenerate a token at: https://hpanel.hostinger.com/profile/api\\n',\n\t\t\t);\n\t\t\ttoken = await ask('API Token: ', {\n\t\t\t\thidden: true,\n\t\t\t\tinstead: LOGIN_INSTEAD,\n\t\t\t});\n\t\t}\n\n\t\tif (!token) {\n\t\t\tlogger.error('Token is required');\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\t// Stored without validating, and that is worth saying rather than hiding.\n\t\t// Hostinger's DNS API is zone-scoped — every endpoint takes a domain — so\n\t\t// there is nothing to call that means \"is this token good\" without\n\t\t// already knowing a domain this account owns. A check against a guessed\n\t\t// one would fail for two different reasons and report one.\n\t\tawait storeHostingerToken(token);\n\n\t\tlogger.log('\\n✓ Hostinger token stored.');\n\t\tlogger.log(` Credentials stored in: ${getCredentialsPath()}`);\n\t\tlogger.log(\n\t\t\t' Not validated here: every DNS endpoint is scoped to a domain, so the\\n' +\n\t\t\t\t' first deploy that touches DNS is where a bad token reports itself.',\n\t\t);\n\t\tlogger.log(\n\t\t\t'\\nYou can now use it as a DNS provider without setting HOSTINGER_API_TOKEN.',\n\t\t);\n\t}\n}\n\n/**\n * Logout from a service\n */\nexport async function logoutCommand(options: LogoutOptions): Promise<void> {\n\tconst { provider = 'dokploy' } = options;\n\n\tif (provider === 'all') {\n\t\tconst dokployRemoved = await removeDokployCredentials();\n\n\t\tif (dokployRemoved) {\n\t\t\tlogger.log('\\n✓ Logged out from all services');\n\t\t} else {\n\t\t\tlogger.log('\\nNo stored credentials found');\n\t\t}\n\t\treturn;\n\t}\n\n\tif (provider === 'dokploy') {\n\t\tconst removed = await removeDokployCredentials();\n\n\t\tif (removed) {\n\t\t\tlogger.log('\\n✓ Logged out from Dokploy');\n\t\t} else {\n\t\t\tlogger.log('\\nNo Dokploy credentials found');\n\t\t}\n\t}\n}\n\n/**\n * Show current login status\n */\nexport async function whoamiCommand(): Promise<void> {\n\tlogger.log('\\n📋 Current credentials:\\n');\n\n\tconst dokploy = await getDokployCredentials();\n\n\tif (dokploy) {\n\t\tlogger.log(' Dokploy:');\n\t\tlogger.log(` Endpoint: ${dokploy.endpoint}`);\n\t\tlogger.log(` Token: ${maskToken(dokploy.token)}`);\n\t} else {\n\t\tlogger.log(' Dokploy: Not logged in');\n\t}\n\n\tlogger.log(`\\n Credentials file: ${getCredentialsPath()}`);\n}\n\n/**\n * Mask a token for display\n */\nexport function maskToken(token: string): string {\n\tif (token.length <= 8) {\n\t\treturn '****';\n\t}\n\treturn `${token.slice(0, 4)}...${token.slice(-4)}`;\n}\n\n// Re-export credentials utilities for use in other modules\nexport {\n\tgetDokployCredentials,\n\tgetDokployEndpoint,\n\tgetDokployRegistryId,\n\tgetDokployToken,\n\tstoreDokployCredentials,\n\tstoreDokployRegistryId,\n} from './credentials';\n","import { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { createServer } from 'node:net';\nimport { dirname, join, resolve } from 'node:path';\nimport { config as dotenvConfig } from 'dotenv';\nimport {\n\tConfigNotFound,\n\tloadWorkspaceAppInfo,\n\tloadWorkspaceConfig,\n\tNotInAnApp,\n\ttype WorkspaceAppInfo,\n} from '../config';\nimport type { ReconcileResult } from '../reconcile/index.js';\nimport { toEmbeddableSecrets } from '../secrets/storage.js';\nimport {\n\tFileSecretsStore,\n\ttype SecretsStore,\n\tsecretsStoreFor,\n} from '../secrets/store.js';\nimport type { NormalizedWorkspace } from '../workspace/index.js';\n\nconst logger = console;\n\n// ---------------------------------------------------------------------------\n// Environment files\n// ---------------------------------------------------------------------------\n\n/**\n * Load environment files\n * @internal Exported for testing\n */\nexport function loadEnvFiles(\n\tenvConfig: string | string[] | undefined,\n\tcwd: string = process.cwd(),\n): { loaded: string[]; missing: string[] } {\n\tconst loaded: string[] = [];\n\tconst missing: string[] = [];\n\n\t// Normalize to array\n\tconst envFiles = envConfig\n\t\t? Array.isArray(envConfig)\n\t\t\t? envConfig\n\t\t\t: [envConfig]\n\t\t: ['.env'];\n\n\t// Load each env file in order (later files override earlier)\n\tfor (const envFile of envFiles) {\n\t\tconst envPath = resolve(cwd, envFile);\n\t\tif (existsSync(envPath)) {\n\t\t\tdotenvConfig({ path: envPath, override: true, quiet: true });\n\t\t\tloaded.push(envFile);\n\t\t} else if (envConfig) {\n\t\t\t// Only report as missing if explicitly configured\n\t\t\tmissing.push(envFile);\n\t\t}\n\t}\n\n\treturn { loaded, missing };\n}\n\n// ---------------------------------------------------------------------------\n// Port utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Check if a port is available\n * @internal Exported for testing\n */\nexport async function isPortAvailable(port: number): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst server = createServer();\n\n\t\tserver.once('error', (err: NodeJS.ErrnoException) => {\n\t\t\tif (err.code === 'EADDRINUSE') {\n\t\t\t\tresolve(false);\n\t\t\t} else {\n\t\t\t\tresolve(false);\n\t\t\t}\n\t\t});\n\n\t\tserver.once('listening', () => {\n\t\t\tserver.close();\n\t\t\tresolve(true);\n\t\t});\n\n\t\tserver.listen(port);\n\t});\n}\n\n/**\n * Find an available port starting from the preferred port\n * @internal Exported for testing\n */\nexport async function findAvailablePort(\n\tpreferredPort: number,\n\tmaxAttempts = 10,\n): Promise<number> {\n\tfor (let i = 0; i < maxAttempts; i++) {\n\t\tconst port = preferredPort + i;\n\t\tif (await isPortAvailable(port)) {\n\t\t\treturn port;\n\t\t}\n\t\tlogger.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);\n\t}\n\n\tthrow new Error(\n\t\t`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`,\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Docker Compose port mapping\n// ---------------------------------------------------------------------------\n\n/** Port state persisted to .gkm/ports.json, keyed by env var name. */\nexport type PortState = Record<string, number>;\n\nconst PORT_STATE_PATH = '.gkm/ports.json';\n\n/**\n * Load saved port state from .gkm/ports.json.\n * @internal Exported for testing\n */\nexport async function loadPortState(workspaceRoot: string): Promise<PortState> {\n\ttry {\n\t\tconst raw = await readFile(join(workspaceRoot, PORT_STATE_PATH), 'utf-8');\n\t\treturn JSON.parse(raw) as PortState;\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n/**\n * Save port state to .gkm/ports.json.\n * @internal Exported for testing\n */\nexport async function savePortState(\n\tworkspaceRoot: string,\n\tports: PortState,\n): Promise<void> {\n\tconst dir = join(workspaceRoot, '.gkm');\n\tawait mkdir(dir, { recursive: true });\n\tawait writeFile(\n\t\tjoin(workspaceRoot, PORT_STATE_PATH),\n\t\t`${JSON.stringify(ports, null, 2)}\\n`,\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// URL rewriting\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Docker Compose services\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Secrets loading\n// ---------------------------------------------------------------------------\n\n/**\n * Load and flatten secrets for an app from encrypted storage.\n * For workspace app: maps {APP}_DATABASE_URL → DATABASE_URL.\n * @internal Exported for testing\n */\nexport async function loadSecretsForApp(\n\tstore: SecretsStore,\n\tstage: string,\n\tappName?: string,\n): Promise<Record<string, string>> {\n\tlet secrets: Record<string, string> = {};\n\n\tconst stageSecrets = await store.read(stage);\n\tif (stageSecrets) {\n\t\tlogger.log(`🔐 Loading secrets from stage: ${stage}`);\n\t\tsecrets = toEmbeddableSecrets(stageSecrets);\n\t}\n\n\tif (Object.keys(secrets).length === 0) {\n\t\treturn {};\n\t}\n\n\t// Single app mode - no mapping needed\n\tif (!appName) {\n\t\treturn secrets;\n\t}\n\n\t// Workspace app mode - map {APP}_* to generic names\n\tconst prefix = appName.toUpperCase();\n\tconst mapped = { ...secrets };\n\n\t// Map {APP}_DATABASE_URL → DATABASE_URL\n\tconst appDbUrl = secrets[`${prefix}_DATABASE_URL`];\n\tif (appDbUrl) {\n\t\tmapped.DATABASE_URL = appDbUrl;\n\t}\n\n\treturn mapped;\n}\n\n/**\n * Walk up the directory tree to find the root containing .gkm/secrets/.\n * @internal Exported for testing\n */\nexport function findSecretsRoot(startDir: string): string {\n\tlet dir = startDir;\n\twhile (dir !== '/') {\n\t\tif (existsSync(join(dir, '.gkm', 'secrets'))) {\n\t\t\treturn dir;\n\t\t}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) break;\n\t\tdir = parent;\n\t}\n\treturn startDir;\n}\n\n// ---------------------------------------------------------------------------\n// Credentials preload / injection\n// ---------------------------------------------------------------------------\n\n/**\n * Generate the credentials injection code snippet.\n * This is the common logic used by both entry wrapper and exec preload.\n * @internal\n */\nfunction generateCredentialsInjection(secretsJsonPath: string): string {\n\treturn `import { existsSync, readFileSync } from 'node:fs';\n\n// Inject dev secrets via globalThis and process.env\n// Using globalThis.__gkm_credentials__ avoids CJS/ESM interop issues where\n// Object.assign on the Credentials export only mutates one module copy.\nconst secretsPath = '${secretsJsonPath}';\nif (existsSync(secretsPath)) {\n const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));\n globalThis.__gkm_credentials__ = secrets;\n Object.assign(process.env, secrets);\n}\n`;\n}\n\n/**\n * Create a preload script that injects secrets into Credentials.\n * Used by `gkm exec` to inject secrets before running any command.\n * @internal Exported for testing\n */\nexport async function createCredentialsPreload(\n\tpreloadPath: string,\n\tsecretsJsonPath: string,\n): Promise<void> {\n\tconst content = `/**\n * Credentials preload generated by 'gkm exec'\n * This file is loaded via NODE_OPTIONS=\"--import <path>\"\n */\n${generateCredentialsInjection(secretsJsonPath)}`;\n\n\tawait writeFile(preloadPath, content);\n}\n\n/**\n * Create a wrapper script that injects secrets before importing the entry file.\n * @internal Exported for testing\n */\nexport async function createEntryWrapper(\n\twrapperPath: string,\n\tentryPath: string,\n\tsecretsJsonPath?: string,\n): Promise<void> {\n\tconst credentialsInjection = secretsJsonPath\n\t\t? `${generateCredentialsInjection(secretsJsonPath)}\n`\n\t\t: '';\n\n\t// Use dynamic import() to ensure secrets are assigned before the entry file loads\n\t// Static imports are hoisted, so Object.assign would run after the entry file is loaded\n\tconst content = `#!/usr/bin/env node\n/**\n * Entry wrapper generated by 'gkm dev --entry'\n */\n${credentialsInjection}// Import and run the user's entry file (dynamic import ensures secrets load first)\nawait import('${entryPath}');\n`;\n\n\tawait writeFile(wrapperPath, content);\n}\n\n// ---------------------------------------------------------------------------\n// Prepare credentials (shared by dev, exec, test)\n// ---------------------------------------------------------------------------\n\n/**\n * Result of preparing credentials.\n */\nexport interface EntryCredentialsResult {\n\t/** Credentials to inject (secrets + PORT) */\n\tcredentials: Record<string, string>;\n\t/** Resolved port (from --port, workspace config, or default 3000) */\n\tresolvedPort: number;\n\t/** Path where credentials JSON was written */\n\tsecretsJsonPath: string;\n\t/** Resolved app name (if in workspace) */\n\tappName: string | undefined;\n\t/** Secrets root directory */\n\tsecretsRoot: string;\n\t/** Workspace app info (if in a workspace) */\n\tappInfo?: WorkspaceAppInfo;\n\t/**\n\t * The keys the manifest declared, as opposed to those a secret store held.\n\t *\n\t * Kept apart because they cannot be *sniffed*. A construct reads its own key\n\t * inside `@geekmidas/constructs`, not in application code, so a walk of the\n\t * app finds no `get('MAIL_URL')` to find — and a caller that filters the\n\t * environment down to what it sniffed would drop every URL the target just\n\t * resolved. `gkm test` did exactly that.\n\t */\n\tdeclaredKeys: string[];\n\t/** The reconcile just run, in a workspace: its plan and its ports. */\n\treconciled?: ReconcileResult;\n}\n\n/**\n * Prepare credentials for dev/exec/test modes.\n * Loads workspace config, secrets, resolves Docker ports, rewrites URLs,\n * injects PORT, dependency URLs, and writes credentials JSON.\n *\n * @param options.stage - The stage whose secrets to load. Default: the project's `stages.local`.\n * @param options.startDocker - Start Docker Compose services after port resolution. Default: false.\n * @param options.secretsFileName - Custom secrets JSON filename. Default: 'dev-secrets-{appName}.json' or 'dev-secrets.json'.\n * @internal Exported for testing\n */\nexport async function prepareEntryCredentials(options: {\n\texplicitPort?: number;\n\tcwd?: string;\n\t/** The stage whose secrets to load. Default: the project's `stages.local` */\n\tstage?: string;\n\t/** Start Docker Compose services after port resolution. Default: false */\n\tstartDocker?: boolean;\n\t/** Custom secrets JSON filename. Default: 'dev-secrets-{appName}.json' or 'dev-secrets.json' */\n\tsecretsFileName?: string;\n\t/**\n\t * The stage to reconcile the local target for.\n\t *\n\t * `gkm test` passes `test` and gets the same containers with suffixed\n\t * resources; left out, it is the project's local stage and they are\n\t * unsuffixed. Only read by workspaces that have adopted the constructs glob.\n\t */\n\treconcileStage?: string;\n}): Promise<EntryCredentialsResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\n\t// Try to get workspace app config for port and secrets\n\tlet workspaceAppPort: number | undefined;\n\tlet secretsRoot: string = cwd;\n\tlet appName: string | undefined;\n\tlet appInfo: WorkspaceAppInfo | undefined;\n\n\tlet workspace: NormalizedWorkspace | undefined;\n\n\ttry {\n\t\tappInfo = await loadWorkspaceAppInfo(cwd);\n\t\tworkspace = appInfo.workspace;\n\t\tworkspaceAppPort = appInfo.app.port;\n\t\tsecretsRoot = appInfo.workspaceRoot;\n\t\tappName = appInfo.appName;\n\t} catch (error) {\n\t\t// Anything but \"not in an app\" or \"not in a workspace\" is a real fault —\n\t\t// a config that does not load — and must not be mistaken for either.\n\t\tif (!(error instanceof NotInAnApp || error instanceof ConfigNotFound)) {\n\t\t\tthrow error;\n\t\t}\n\t\t// Not an app — but possibly the workspace root, which is where the\n\t\t// scaffold's own `pnpm test` runs `gkm test` from. Without the workspace\n\t\t// here that run skipped the reconcile below: no container started, and the\n\t\t// URLs were whatever the stored secrets said, on ports nothing listened on.\n\t\tworkspace = await loadWorkspaceConfig(cwd)\n\t\t\t.then((loaded) => loaded.workspace)\n\t\t\t.catch(() => undefined);\n\t\t// Otherwise not in a workspace at all (expected for non-gkm apps using\n\t\t// gkm exec) — use defaults.\n\t\t// No app here, so no per-app keys to map.\n\t\tsecretsRoot = workspace?.root ?? findSecretsRoot(cwd);\n\t}\n\n\t// The app's local port, by the rule `gkm dev` applies: moved off a port\n\t// another project holds, and kept. Never refused here — the command may be\n\t// a build or a script that binds nothing, so this app's own dev server\n\t// holding the port is no reason to stop it.\n\tif (appName && workspace && options.explicitPort === undefined) {\n\t\tconst { assignAppPorts, describeHolder, holderOf, withAppPorts } =\n\t\t\tawait import('../dev/appPorts.js');\n\t\tconst assigned = await assignAppPorts(workspace, [appName], {\n\t\t\tfree: isPortAvailable,\n\t\t\tholder: holderOf,\n\t\t});\n\t\tfor (const { app, from, to, holder } of assigned.moved) {\n\t\t\tlogger.log(\n\t\t\t\t`↪️ ${app}: ${from} is held by ${describeHolder(holder)}, which is not this workspace's — using ${to}`,\n\t\t\t);\n\t\t}\n\t\tworkspace = withAppPorts(workspace, assigned.ports);\n\t\tworkspaceAppPort = assigned.ports[appName] ?? workspaceAppPort;\n\t}\n\n\t// Tagged, so a later `gkm dev` can tell what this starts from another\n\t// project's process on the same port; the command inherits it.\n\tif (appName) {\n\t\tconst { APP_TAG_ENV, appTag } = await import('../dev/appPorts.js');\n\t\tprocess.env[APP_TAG_ENV] = appTag(secretsRoot, appName);\n\t}\n\n\t// Determine port: explicit --port > the app's local port > default 3000\n\tconst resolvedPort = options.explicitPort ?? workspaceAppPort ?? 3000;\n\n\t// Load secrets and inject PORT. Outside a workspace there are no declared\n\t// stages to read, so only a stage asked for by name is loaded.\n\tconst stage = options.stage ?? workspace?.stages.local;\n\t// The stage's own store; outside a workspace nothing names one but the file.\n\tconst store = stage\n\t\t? workspace\n\t\t\t? await secretsStoreFor(workspace, stage)\n\t\t\t: new FileSecretsStore(secretsRoot)\n\t\t: undefined;\n\tconst credentials =\n\t\tstore && stage ? await loadSecretsForApp(store, stage, appName) : {};\n\n\t// Always inject PORT into credentials so apps can read it\n\tcredentials.PORT = String(resolvedPort);\n\t// Expo reads its Metro port from here when `expo start` is given none, so\n\t// a mobile app runs where the workspace placed it — and where the `exp://`\n\t// origins its auth server trusts say it is.\n\tif (appInfo?.app.type === 'mobile') {\n\t\tcredentials.RCT_METRO_PORT = String(resolvedPort);\n\t}\n\n\tconst declaredKeys: string[] = [];\n\tlet reconciled: ReconcileResult | undefined;\n\n\t// Every address — containers, the edge, each app — comes from what the\n\t// workspace declares. Outside a workspace (a non-gkm app using `gkm exec`)\n\t// there is nothing to resolve, only the secrets above.\n\tif (workspace) {\n\t\tconst { reconcileWorkspace } = await import('../reconcile/workspace.js');\n\t\treconciled = await reconcileWorkspace(workspace, {\n\t\t\tstage: options.reconcileStage ?? workspace.stages.local,\n\t\t\tstart: options.startDocker ?? false,\n\t\t});\n\n\t\t// Declared URLs win over anything stored: the manifest is the statement\n\t\t// of what exists, and a stale secret naming an old port is exactly the\n\t\t// drift this replaces.\n\t\tObject.assign(credentials, reconciled.env);\n\t\tdeclaredKeys.push(...Object.keys(reconciled.env));\n\n\t\tif (Object.keys(reconciled.env).length > 0) {\n\t\t\tlogger.log(\n\t\t\t\t`🔌 Resolved ${Object.keys(reconciled.env).length} declared URL(s)`,\n\t\t\t);\n\t\t}\n\t}\n\n\t// Write secrets to temp JSON file (always write since we have PORT)\n\t// Use app-specific filename to avoid race conditions when running multiple apps via turbo\n\tconst secretsDir = join(secretsRoot, '.gkm');\n\tawait mkdir(secretsDir, { recursive: true });\n\tconst secretsFileName =\n\t\toptions.secretsFileName ??\n\t\t(appName ? `dev-secrets-${appName}.json` : 'dev-secrets.json');\n\tconst secretsJsonPath = join(secretsDir, secretsFileName);\n\tawait writeFile(secretsJsonPath, JSON.stringify(credentials, null, 2));\n\n\treturn {\n\t\tcredentials,\n\t\tresolvedPort,\n\t\tsecretsJsonPath,\n\t\tappName,\n\t\tsecretsRoot,\n\t\tappInfo,\n\t\tdeclaredKeys,\n\t\t...(reconciled ? { reconciled } : {}),\n\t};\n}\n","import { join } from 'node:path';\nimport type { Routes } from '../types.js';\nimport type { NormalizedWorkspace } from './types.js';\n\n/**\n * Normalize routes to an array of patterns.\n * Handles a glob or a list of them.\n * @internal Exported for use in dev command\n */\nexport function normalizeRoutes(routes: Routes | undefined): string[] {\n\tif (!routes) return [];\n\treturn Array.isArray(routes) ? routes : [routes];\n}\n\n/**\n * Get the first routes pattern as a string (for simple cases).\n * @internal Exported for use in dev command\n */\nexport function getFirstRoute(\n\troutes: string | string[] | undefined,\n): string | null {\n\tconst normalized = normalizeRoutes(routes);\n\treturn normalized[0] || null;\n}\n\n/**\n * Check if a file path matches endpoint patterns that could affect OpenAPI schema.\n * Returns true for changes that should trigger client regeneration.\n */\nexport function shouldRegenerateClient(\n\tfilePath: string,\n\troutesPattern: string,\n): boolean {\n\t// Normalize path separators\n\tconst normalizedPath = filePath.replace(/\\\\/g, '/');\n\tconst normalizedPattern = routesPattern.replace(/\\\\/g, '/');\n\n\t// Check if the file matches the routes pattern\n\t// This is a simple check - the file should be within the routes directory\n\tconst patternDir = normalizedPattern.split('*')[0] || '';\n\n\tif (!normalizedPath.includes(patternDir.replace('./', ''))) {\n\t\treturn false;\n\t}\n\n\t// Check file extension - only TypeScript endpoint files\n\tif (!normalizedPath.endsWith('.ts') && !normalizedPath.endsWith('.tsx')) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/**\n * Get backend apps that a web/mobile client depends on.\n */\nexport function getBackendDependencies(\n\tworkspace: NormalizedWorkspace,\n\tclientAppName: string,\n): string[] {\n\tconst clientApp = workspace.apps[clientAppName];\n\tif (!clientApp || (clientApp.type !== 'web' && clientApp.type !== 'mobile')) {\n\t\treturn [];\n\t}\n\n\treturn clientApp.dependencies.filter((dep) => {\n\t\tconst depApp = workspace.apps[dep];\n\t\treturn depApp?.type === 'backend' && depApp.routes;\n\t});\n}\n\n/**\n * Get web/mobile apps that depend on a backend app.\n */\nexport function getDependentFrontends(\n\tworkspace: NormalizedWorkspace,\n\tbackendAppName: string,\n): string[] {\n\tconst dependentApps: string[] = [];\n\n\tfor (const [appName, app] of Object.entries(workspace.apps)) {\n\t\tif (\n\t\t\t(app.type === 'web' || app.type === 'mobile') &&\n\t\t\tapp.dependencies.includes(backendAppName)\n\t\t) {\n\t\t\tdependentApps.push(appName);\n\t\t}\n\t}\n\n\treturn dependentApps;\n}\n\n/**\n * Get the path to a backend's OpenAPI spec file.\n */\nexport function getBackendOpenApiPath(\n\tworkspace: NormalizedWorkspace,\n\tbackendAppName: string,\n): string | null {\n\tconst app = workspace.apps[backendAppName];\n\tif (!app || app.type !== 'backend') {\n\t\treturn null;\n\t}\n\n\treturn join(workspace.root, app.path, '.gkm', 'openapi.ts');\n}\n","import { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\n\nconst REGISTER = 'owning-tsconfig-jsx-register.mjs';\n\n/** The CLI's `bin/` was not found above the module looking for it. */\nexport class OwningTsconfigJsxHookMissing extends Error {\n\tconstructor(readonly from: string) {\n\t\tsuper(\n\t\t\t`Could not find bin/${REGISTER} in any directory above '${from}'. ` +\n\t\t\t\t'It ships with @geekmidas/cli beside bin/gkm.mjs; reinstall the ' +\n\t\t\t\t'package if it is missing.',\n\t\t);\n\t\tthis.name = 'OwningTsconfigJsxHookMissing';\n\t}\n}\n\n/**\n * The CLI's `bin/owning-tsconfig-jsx-register.mjs`, as a file URL. Searched\n * upward because this module runs from `src/` in the repo and from a chunk in\n * `dist/` once built, and `bin/` sits beside both.\n */\nfunction registerUrl(): string {\n\tconst from = dirname(fileURLToPath(import.meta.url));\n\tfor (let dir = from; ; dir = dirname(dir)) {\n\t\tconst candidate = join(dir, 'bin', REGISTER);\n\t\tif (existsSync(candidate)) return pathToFileURL(candidate).href;\n\t\tif (dirname(dir) === dir) throw new OwningTsconfigJsxHookMissing(from);\n\t}\n}\n\n/**\n * `NODE_OPTIONS` for a process that runs app code through tsx, with the hook\n * that compiles each `.tsx` with its own tsconfig's JSX settings (see\n * `bin/owning-tsconfig-jsx.mjs`) put in front of every other `--import`, so it\n * is registered before tsx and reads the source on tsx's behalf.\n *\n * `bin/gkm.mjs` already starts the CLI this way, and its children inherit it;\n * this is for those that set `NODE_OPTIONS` themselves, or whose CLI was\n * started with tsx already imported.\n */\nexport function withOwningTsconfigJsx(nodeOptions = ''): string {\n\tif (nodeOptions.includes(REGISTER)) return nodeOptions;\n\treturn [`--import=${registerUrl()}`, nodeOptions].filter(Boolean).join(' ');\n}\n","import {\n\tDEFAULT_POSTGRES_VERSION,\n\tkebabCase,\n\ttype PostgresVersion,\n} from '@geekmidas/manifest';\n/**\n * What gkm knows about each container it can derive.\n *\n * Everything provider-specific about the local target lives here: the image, the\n * ports the image listens on, and whether its data is worth keeping. The plan\n * says *which* containers exist and never says any of this, which is the split\n * the design asks for — neutral facts from the declaration, provider detail\n * from here and from config.\n *\n * Shared by allocation and by the compose writer so the two cannot disagree\n * about how many ports a container needs.\n */\n\n/**\n * The Postgres image for a major version.\n *\n * One place that knows the tag shape, so a declared version reaches the\n * container without anyone restating `-alpine` beside it.\n */\nexport function postgresImage(version: PostgresVersion): string {\n\treturn `postgres:${version}-alpine`;\n}\n\n/**\n * The default image for each container.\n *\n * Pinned to a major rather than `latest` where the image publishes one, so a\n * rebuild does not silently move a developer's Postgres a version. Overridable\n * per project: a project needing `postgis/postgis` says so in config.\n */\nexport const DEFAULT_IMAGES: Readonly<Record<string, string>> = {\n\tpostgres: postgresImage(DEFAULT_POSTGRES_VERSION),\n\tminio: 'pgsty/minio:RELEASE.2026-08-04T00-00-00Z',\n\tmailpit: 'axllent/mailpit:latest',\n\tredis: 'redis:8-alpine',\n\t'redis-http': 'hiett/serverless-redis-http:latest',\n\trabbitmq: 'rabbitmq:4-management-alpine',\n\t// Pinned: 2.2.0 answers a KMS decrypt under the wrong encryption context\n\t// with UnknownError instead of InvalidCiphertextException.\n\tlocalstack: 'floci/floci:2.1.0',\n\t// The local edge: TLS with its own CA, and host-based routing onto the\n\t// bucket behind it. Not a CDN — what is missing locally is the *mapping*\n\t// and the certificate, not caching, and a caching proxy would add an\n\t// invalidation story no local stack needs.\n\tcaddy: 'caddy:2-alpine',\n};\n\n/** One port an image listens on. */\nexport interface ContainerPort {\n\t/**\n\t * The name this port is allocated and persisted under.\n\t *\n\t * Per port rather than per container so that adding a console port later\n\t * assigns a new one instead of shifting the port the app already connects to.\n\t */\n\tkey: string;\n\t/** The port inside the container, fixed by the image. */\n\tinside: number;\n\t/** What it is for, in the one line `gkm dev` and `gkm setup` print. */\n\tlabel: string;\n\t/** A page a person opens, so it prints as a link rather than an address. */\n\tweb?: true;\n}\n\n/**\n * The ports each container listens on.\n *\n * The first is always the one an app connects to; the rest are consoles and\n * management UIs, published because a bucket or an inbox you can look at is most\n * of why these are real containers rather than stubs.\n */\nconst PORTS: Readonly<Record<string, readonly ContainerPort[]>> = {\n\tpostgres: [{ key: 'postgres', inside: 5432, label: 'postgres' }],\n\tminio: [\n\t\t{ key: 'minio', inside: 9000, label: 'minio api' },\n\t\t{ key: 'minio-console', inside: 9001, label: 'minio console', web: true },\n\t],\n\tmailpit: [\n\t\t{ key: 'mailpit', inside: 1025, label: 'smtp' },\n\t\t{ key: 'mailpit-web', inside: 8025, label: 'mailpit inbox', web: true },\n\t],\n\tredis: [{ key: 'redis', inside: 6379, label: 'redis' }],\n\t'redis-http': [{ key: 'redis-http', inside: 80, label: 'cache' }],\n\trabbitmq: [\n\t\t{ key: 'rabbitmq', inside: 5672, label: 'amqp' },\n\t\t{\n\t\t\tkey: 'rabbitmq-management',\n\t\t\tinside: 15672,\n\t\t\tlabel: 'rabbitmq console',\n\t\t\tweb: true,\n\t\t},\n\t],\n\tlocalstack: [{ key: 'localstack', inside: 4566, label: 'localstack' }],\n\t// One port, and an assigned one rather than 443. The whole point of\n\t// allocation is that two projects run at once, and an edge that insists on\n\t// the privileged port puts that back — at the cost of a port in the URL,\n\t// which no cookie or CORS rule looks at.\n\tcaddy: [{ key: 'caddy', inside: 443, label: 'https edge' }],\n};\n\n/**\n * An external API's fake, as the local target runs it.\n *\n * Keyed by its port key, `<id>-fake`, which is also its container's name when\n * it has one — so `primaryPortKey` finds its port with no table to consult.\n * A module fake has no image: gkm serves it, on the port this key is assigned.\n */\nexport interface PlannedFake {\n\t/** The external API it stands in for. */\n\tid: string;\n\t/** The image to run, for a fake the provider publishes. */\n\timage?: string;\n\t/** The port that image listens on inside the container. */\n\tport?: number;\n}\n\n/** The port key — and container name, for an image — of an API's fake. */\nexport function fakeKey(id: string): string {\n\treturn `${kebabCase(id)}-fake`;\n}\n\n/**\n * The ports a container needs published. Empty for one gkm does not know.\n *\n * `fakes` answers for the containers no table can: an external API's fake\n * listens wherever its declaration says.\n */\nexport function portsOf(\n\tcontainer: string,\n\tfakes: Readonly<Record<string, PlannedFake>> = {},\n): readonly ContainerPort[] {\n\tconst fake = fakes[container];\n\tif (fake?.port !== undefined) {\n\t\treturn [{ key: container, inside: fake.port, label: `${fake.id} fake` }];\n\t}\n\n\treturn PORTS[container] ?? [];\n}\n\n/** One published port, as a person reads it. */\nexport interface ServiceAddress {\n\tcontainer: string;\n\tlabel: string;\n\t/** `http://localhost:<port>` for a page, `localhost:<port>` otherwise. */\n\taddress: string;\n}\n\n/**\n * Every port the containers publish — not only the one an app connects to.\n *\n * The consoles and the inbox are most of why these are real containers, and an\n * inbox nobody can find is an inbox nobody opens: a sign-in link gets fished\n * out of `docker ps` instead.\n */\nexport function serviceAddresses(\n\tcontainers: readonly string[],\n\tports: Readonly<Record<string, number>>,\n\tfakes: Readonly<Record<string, PlannedFake>> = {},\n): ServiceAddress[] {\n\treturn containers.flatMap((container) =>\n\t\tportsOf(container, fakes).flatMap((port) => {\n\t\t\tconst assigned = ports[port.key];\n\t\t\tif (assigned === undefined) return [];\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tcontainer,\n\t\t\t\t\tlabel: port.label,\n\t\t\t\t\taddress: port.web\n\t\t\t\t\t\t? `http://localhost:${assigned}`\n\t\t\t\t\t\t: `localhost:${assigned}`,\n\t\t\t\t},\n\t\t\t];\n\t\t}),\n\t);\n}\n\n/** The lines `gkm dev` and `gkm setup` print under the services heading. */\nexport function describeServices(\n\tservices: readonly ServiceAddress[],\n): string[] {\n\tconst width = Math.max(0, ...services.map(({ label }) => label.length));\n\treturn services.map(\n\t\t({ label, address }) => ` ${label.padEnd(width)} ${address}`,\n\t);\n}\n\n/** The key a container's primary port is allocated under. */\nexport function primaryPortKey(container: string): string {\n\treturn portsOf(container)[0]?.key ?? container;\n}\n\n/**\n * Every port key a set of containers needs, in a stable order.\n *\n * What allocation is handed: it assigns ports to names and knows nothing about\n * what listens on them.\n */\nexport function portKeys(\n\tcontainers: readonly string[],\n\tfakes: Readonly<Record<string, PlannedFake>> = {},\n): string[] {\n\tconst keys = [...containers]\n\t\t.sort()\n\t\t.flatMap((container) => portsOf(container, fakes).map((port) => port.key));\n\n\t// A module fake runs in no container, and still needs a port to answer on.\n\treturn [...new Set([...keys, ...Object.keys(fakes).sort()])];\n}\n\n/**\n * The named volume a container's data lives in.\n *\n * Mailpit deliberately has none: local mail is disposable, and an inbox that\n * survives days of dev is noise rather than state.\n */\nexport function volumeOf(container: string): string | undefined {\n\tconst volumes: Readonly<Record<string, string>> = {\n\t\tpostgres: 'postgres-data',\n\t\tminio: 'minio-data',\n\t\tredis: 'redis-data',\n\t\trabbitmq: 'rabbitmq-data',\n\t\tlocalstack: 'localstack-data',\n\t\t// Holds the CA it generated. Losing it means a new root on every start,\n\t\t// and a trust store full of dead authorities.\n\t\tcaddy: 'caddy-data',\n\t};\n\n\treturn volumes[container];\n}\n","/**\n * Which backend each declared kind resolves to — decided by where the project\n * deploys, and by nothing else.\n *\n * These used to be read out of a `services:` block, where one key answered two\n * questions: *does this exist* and *what backs it*. The first is the manifest's\n * — a declared `Cache` is why there is a cache — and the second is the deploy\n * target's, because the right cache for a Lambda is not the right cache for a\n * box already running Postgres. With both answered elsewhere there was nothing\n * left for the block to say, so it is gone and these read the target.\n *\n * Mail has no answer here at all: every provider speaks SMTP, so the provider\n * is whichever one the stage's mail URL points at.\n */\n\nimport {\n\ttype CacheBackend,\n\tDEFAULT_CACHE,\n\tDEFAULT_EVENTS,\n\tDEFAULT_STORAGE,\n\ttype EventsBackend,\n\ttype MainProvider,\n\ttype StorageBackend,\n} from '../types.js';\n\n/**\n * Which family of defaults a workspace's deploy target belongs to.\n *\n * One question, asked once: **does this target run containers the project\n * controls?** Dokploy and a bare server do, so a bucket or a cache can live\n * beside the app; AWS, Vercel and Cloudflare do not, so the default has to be\n * something managed. That is the whole of the distinction, and it is why\n * `MainProvider` is the right axis rather than the deploy target's own name —\n * three targets share one answer.\n *\n * An AWS deploy names `deploy.default: 'sst'`, because it goes through SST.\n */\nexport function providerOf(workspace: {\n\tdeploy?: { default?: string } | undefined;\n}): MainProvider {\n\tconst target = workspace.deploy?.default;\n\n\treturn target === 'dokploy' || target === 'server' ? 'server' : 'aws';\n}\n\n/**\n * The cache backend a target implies.\n *\n * The same answer locally and deployed: a local `gkm dev` for an AWS app still\n * uses the Upstash protocol, because a backend that differed between the two\n * would be worse than a slower one.\n */\nexport function cacheBackendFor(on: MainProvider): CacheBackend {\n\treturn DEFAULT_CACHE[on];\n}\n\n/** Where a declared bucket lives on a target. */\nexport function storageBackendFor(on: MainProvider): StorageBackend {\n\treturn DEFAULT_STORAGE[on];\n}\n\n/**\n * Which broker carries declared queues and topics on a target.\n *\n * Only consulted when something declares one; a project with no `Topic` or\n * `Queue` has no events at all, whatever the target.\n */\nexport function eventsBackendFor(on: MainProvider): EventsBackend {\n\treturn DEFAULT_EVENTS[on];\n}\n","/**\n * The Caddy configuration a plan implies.\n *\n * A file server is a *domain that serves a bucket*, and locally it had neither:\n * `UPLOADS_SERVER_URL` resolved to MinIO's path-style address, which works and\n * is not the deployed shape. MinIO's own virtual-host mode cannot fix that —\n * it reads the leading label *as the bucket name*, so it produces the right\n * shape only when the server's id and the bucket's happen to agree, and never\n * for a server fronting two buckets.\n *\n * So the mapping lives in front of MinIO instead: one host per file server,\n * rewriting the bucket in as a path prefix. That is the whole of it — no\n * caching, because caching is not what is missing locally, and a caching proxy\n * would add an invalidation story a local stack has no use for.\n *\n * What it buys beyond the shape is a certificate. Caddy issues one per host\n * from its own CA, so a local address is `https://` like the deployed one, and\n * everything downstream of that — `Secure` cookies, `SameSite`, a cookie domain\n * with more than one host under it — starts behaving the way it will in\n * production rather than the way `http://localhost` allows.\n *\n * Pure, like `composeFor`: what should exist is data, and rendering it is one\n * line at the edge.\n */\n\nimport type { Plan, PlannedResource } from './plan';\n\n/** One host on the edge, and what answers behind it. */\nexport interface CaddySite {\n\t/** e.g. `api.kitchen-sink.localhost` */\n\thost: string;\n\t/** Where the request is sent, e.g. `http://minio:9000`. */\n\tupstream: string;\n\t/**\n\t * A path to rewrite to, where the upstream expects a different one.\n\t *\n\t * The bucket prefix on an object store is the case. A surface and a site\n\t * want none: they are already serving the paths the client asked for.\n\t */\n\trewrite?: string;\n}\n\n/**\n * The kinds the edge serves.\n *\n * Everything that owns a *public address*. Deployed, each of these has a real\n * hostname and a certificate, so this is what makes the local shape the same\n * one rather than a near-enough one — and the application is indifferent\n * either way, because it reads whichever address was injected and never\n * composes one.\n *\n * Adding a kind here is the whole of adding it to the edge.\n */\nexport const EDGE_KINDS: Readonly<Record<string, true>> = {\n\t'file-server': true,\n\t'rest-api': true,\n\tsite: true,\n};\n\n/**\n * How the edge reaches the host, from inside its container.\n *\n * A surface and a site are served by processes `gkm dev` starts on the host,\n * not by containers — so the edge has to leave Docker's network to reach them.\n * Docker Desktop resolves this name already; on Linux the compose definition\n * maps it to the host gateway.\n */\nconst HOST_GATEWAY = 'host.docker.internal';\n\n/**\n * The suffix every local host shares.\n *\n * `.localhost` rather than a made-up TLD because it resolves to loopback\n * without touching `/etc/hosts` — macOS and systemd-resolved both answer for\n * subdomains of it, and Caddy issues internal certificates for it by default.\n */\nexport const LOCAL_TLD = 'localhost';\n\n/**\n * The sites a plan implies, one per file server.\n *\n * The leading label is the *server's* stage-scoped name rather than the\n * bucket's, which is the difference that matters: two servers over one bucket\n * are a legitimate arrangement — two cache behaviours, one origin — and naming\n * the host after the bucket would make them collide.\n */\nexport function sitesFor(\n\tplan: Plan,\n\tproject: string,\n\t/**\n\t * Where surfaces and sites answer on the host, keyed by construct id.\n\t *\n\t * Assigned by whatever starts them rather than published by a container,\n\t * which is why they arrive here instead of being read off a port.\n\t */\n\taddresses: Readonly<Record<string, string>> = {},\n): CaddySite[] {\n\tconst byId = new Map(plan.resources.map((r) => [r.id, r]));\n\tconst sites: CaddySite[] = [];\n\n\tfor (const resource of plan.resources) {\n\t\tif (!EDGE_KINDS[resource.kind]) continue;\n\n\t\tconst host = hostFor(resource, project);\n\n\t\tif (resource.kind === 'file-server') {\n\t\t\t// The bucket is a prefix on the origin, never part of the address: the\n\t\t\t// client asks for the key it stored.\n\t\t\tconst origin = resource.of ? byId.get(resource.of) : undefined;\n\t\t\tif (!origin) continue;\n\n\t\t\tsites.push({\n\t\t\t\thost,\n\t\t\t\tupstream: 'http://minio:9000',\n\t\t\t\trewrite: `/${origin.name}{uri}`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\t// A surface or a site: a process on the host, at an address something\n\t\t// else assigned. Without one there is nothing to route to — which is the\n\t\t// ordinary state before `gkm dev` has decided where things listen.\n\t\tconst address = addresses[resource.id];\n\t\tif (!address) continue;\n\n\t\tconst port = portOf(address);\n\t\tif (!port) continue;\n\n\t\tsites.push({ host, upstream: `http://${HOST_GATEWAY}:${port}` });\n\t}\n\n\treturn sites;\n}\n\n/** The port a local address answers on, defaulting by scheme. */\nfunction portOf(address: string): string | undefined {\n\ttry {\n\t\tconst { port, protocol } = new URL(address);\n\n\t\treturn port || (protocol === 'https:' ? '443' : '80');\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Where one file server answers, including the stage its name carries.\n *\n * The project is a label rather than a decoration: it is what keeps two\n * checkouts from answering on one address. Omitted when there is no project to\n * name, because an empty label makes `uploads..localhost` — a host nothing\n * resolves and no certificate can be issued for.\n */\nexport function hostFor(resource: PlannedResource, project: string): string {\n\t// The root site answers on the project's bare host, as it does on the base\n\t// domain deployed; everything else on a subdomain of it. A stage other than\n\t// the local one keeps a label — the stage — because one edge serves every\n\t// stage, and `gkm test` claiming the bare host would take it from `gkm dev`.\n\t// Only under a project: without one, the \"bare host\" is `localhost` itself,\n\t// which is nobody's domain to take.\n\tconst label =\n\t\tresource.root && project ? stageOf(resource) : labelOf(resource);\n\n\treturn [label, project, LOCAL_TLD].filter(Boolean).join('.');\n}\n\n/**\n * A resource's own label: its `subdomain` where it declared one — the label a\n * deploy uses too — with the stage it carries kept, so `gkm test` and `gkm dev`\n * still answer on different hosts.\n */\nfunction labelOf(resource: PlannedResource): string {\n\tif (!resource.subdomain) return resource.name;\n\tconst stage = stageOf(resource);\n\treturn stage ? `${resource.subdomain}-${stage}` : resource.subdomain;\n}\n\n/** The stage a resource's name carries, or nothing for the local stage's. */\nfunction stageOf(resource: PlannedResource): string | undefined {\n\tconst base = resource.id.toLowerCase();\n\treturn resource.name === base\n\t\t? undefined\n\t\t: resource.name.slice(base.length + 1);\n}\n\n/**\n * The static root config, which only ever imports.\n *\n * Written once and identical for every stage, because the *sites* are what\n * differ and they live in their own files. That split is not tidiness: one\n * container serves every stage — the same way one Postgres holds `orders` and\n * `orders_test` — so a single Caddyfile would mean `gkm test` deleting the\n * routes `gkm dev` is serving, and each command silently breaking the other.\n */\nexport function caddyfileRoot(): string {\n\treturn `${GENERATED}\n{\n\t# No ACME, no public DNS: the CA is local and so are the names.\n\tlocal_certs\n\tauto_https disable_redirects\n}\n\n# One file per stage, so two stages share this edge the way they already share\n# one Postgres and one MinIO.\nimport /etc/caddy/sites/*.caddy\n`;\n}\n\n/**\n * One stage's sites, as the file imported above.\n *\n * `rewrite` rather than a path prefix on the address: the bucket is the\n * origin's business, and the client asks for the key it stored.\n *\n * `header_up Host {upstream_hostport}` matters more than it looks. MinIO routes\n * and signs on the Host header, so forwarding the requested hostname would make\n * it look for a bucket named after the domain.\n */\nexport function toCaddyfile(sites: readonly CaddySite[]): string {\n\tif (sites.length === 0) {\n\t\treturn `${GENERATED}\n# No file server is declared in this stage, so it routes nothing.\n`;\n\t}\n\n\tconst blocks = sites.map(\n\t\t({ host, upstream, rewrite }) => `\nhttps://${host} {\n\ttls internal\n\n\treverse_proxy ${upstream} {${\n\t\trewrite\n\t\t\t? `\n\t\trewrite ${rewrite}\n\t\t# MinIO routes and signs on the Host header, so forwarding the requested\n\t\t# hostname would make it look for a bucket named after the domain.\n\t\theader_up Host {upstream_hostport}`\n\t\t\t: `\n\t\t# The application reads whatever address was injected, so it has to see\n\t\t# the one a caller used — that is what makes a redirect, a cookie domain\n\t\t# and a generated link point back here rather than at the upstream.\n\t\theader_up Host {host}\n\t\theader_up X-Forwarded-Proto {scheme}`\n\t}\n\t}\n}`,\n\t);\n\n\treturn `${GENERATED}${blocks.join('\\n')}\\n`;\n}\n\nconst GENERATED = `# Generated by gkm from the construct manifest — do not edit.\n#\n# One host per declared file server, in front of the object store that holds its\n# objects. This is the shape a file server has deployed — a domain serving a\n# bucket — which MinIO alone cannot produce: its virtual-host mode reads the\n# leading label as the bucket name.\n#\n# Certificates come from Caddy's own CA. \"gkm dev\" and \"gkm test\" export its\n# root and point Node at it, so nothing has to be installed to use these URLs\n# from code; a browser needs the root trusted once.\n`;\n","/**\n * The AWS emulator (floci, LocalStack-compatible) the `sns` backend runs on\n * locally — the facts the compose file, the env writer and the applier must\n * agree on.\n *\n * The emulator has one account and answers with deterministic ARNs and queue\n * URLs, which is what lets a connection string be composed before the topic\n * or queue it names has been created.\n */\n\n/** The emulator's single account. */\nexport const EMULATOR_ACCOUNT = '000000000000';\n\nexport const EMULATOR_REGION = 'us-east-1';\n\n/**\n * The emulator's credential. The `LSIA` prefix is what LocalStack required on\n * issued keys; floci does not care, and keeping it means a project that pins\n * the old image still works.\n */\nexport const EMULATOR_CREDENTIALS = {\n\taccessKeyId: 'LSIAQAAAAAAVNCBMPNSG',\n\tsecretAccessKey: 'geekmidas',\n} as const;\n\n/** The emulator's address from the host, on the port it was published on. */\nexport function emulatorEndpoint(port: number): string {\n\treturn `http://localhost:${port}`;\n}\n\nexport function emulatorTopicArn(name: string): string {\n\treturn `arn:aws:sns:${EMULATOR_REGION}:${EMULATOR_ACCOUNT}:${name}`;\n}\n\nexport function emulatorQueueUrl(name: string, port: number): string {\n\treturn `${emulatorEndpoint(port)}/${EMULATOR_ACCOUNT}/${name}`;\n}\n","/**\n * Which site the base domain points at — deployed and locally alike.\n *\n * Three rules, in order, and the last one is the point:\n *\n * 1. **One site** — it is the root. Unambiguous, so nothing has to be said.\n * 2. **Named `web`** — the convention wins, because people already rely on it.\n * 3. **Declared `root: true`** — the site says so itself.\n *\n * Anything else throws. It used to take the *first* site it iterated, and that\n * order came from the manifest, which is built in glob traversal order — so two\n * sites with neither named `web` meant renaming a file could move the root\n * domain, silently.\n *\n * One rule for both targets: deployed the root site gets the base domain, and\n * locally it gets the project's bare host behind the edge. Before this was\n * shared, the local edge put every site on a subdomain, so what a developer\n * browsed was not the shape production served.\n *\n * @returns the root site's name, or `undefined` when there are no sites\n * @throws {AmbiguousRootSite} when several sites could be the root and none says it is\n */\nexport function rootSite(\n\tsites: readonly { name: string; root?: boolean }[],\n): string | undefined {\n\tif (sites.length === 0) return undefined;\n\n\t// 1. The only site there is.\n\tif (sites.length === 1) return sites[0]!.name;\n\n\t// 2. The convention.\n\tif (sites.some(({ name }) => name === 'web')) return 'web';\n\n\t// 3. What a site declared about itself.\n\tconst declared = sites.filter(({ root }) => root === true);\n\tif (declared.length === 1) return declared[0]!.name;\n\n\tthrow new AmbiguousRootSite(\n\t\tsites.map(({ name }) => name),\n\t\tdeclared.length > 1,\n\t);\n}\n\n/** Several sites could hold the base domain, and none of them says it does. */\nexport class AmbiguousRootSite extends Error {\n\tconstructor(\n\t\treadonly sites: readonly string[],\n\t\treadonly tooMany = false,\n\t) {\n\t\tsuper(\n\t\t\ttooMany\n\t\t\t\t? `More than one site declares \\`root: true\\` — ${sites.join(', ')}. ` +\n\t\t\t\t\t\t`The base domain points at one of them.`\n\t\t\t\t: `${sites.length} sites and nothing says which holds the base ` +\n\t\t\t\t\t\t`domain: ${sites.join(', ')}. Name one of them \\`web\\`, or ` +\n\t\t\t\t\t\t`declare \\`root: true\\` on the one the base domain points at. ` +\n\t\t\t\t\t\t`It used to be whichever the glob reached first, which is not a ` +\n\t\t\t\t\t\t`thing to decide a hostname.`,\n\t\t);\n\t\tthis.name = 'AmbiguousRootSite';\n\t}\n}\n","/**\n * The reconcile plan — what a manifest wants to exist, for one stage.\n *\n * Pure. Turning the plan into running containers and provisioned resources is\n * the applier's job; deciding *what* should exist is this, so the decisions can\n * be asserted as data rather than by starting Docker.\n *\n * One plan serves every stage. `gkm dev` reconciles `development` and `gkm test`\n * reconciles `test`; they differ only in what the resources are called, never in\n * what infrastructure exists. Roles are cluster-scoped in Postgres while grants\n * are per-object, so one container and one role pair serve both.\n */\n\nimport {\n\ttype ConstructManifest,\n\ttype Declaration,\n\ttype DeclarationKind,\n\tdependentsOf,\n\ttype PostgresVersion,\n\tprovidedKeyFor,\n\tpublicEnvFor,\n} from '@geekmidas/manifest';\nimport { type CacheBackend, DEFAULT_CACHE, type EventsBackend } from '../types';\nimport { appKey } from '../workspace/derive.js';\nimport { rootSite } from '../workspace/rootSite.js';\nimport { EDGE_KINDS } from './caddyfile';\nimport { fakeKey, type PlannedFake } from './containers';\nimport type { LocalFake } from './fakes';\n\n/**\n * Which container serves a kind.\n *\n * Derived kinds map to the same container as their parent — a schema tenant\n * lives in its parent's Postgres — so the set is deduplicated rather than\n * counted.\n */\nconst CONTAINERS: Partial<Record<DeclarationKind, string>> = {\n\tdatabase: 'postgres',\n\t'database-reader': 'postgres',\n\t'database-schema': 'postgres',\n\tobjects: 'minio',\n\t// The server is not the bucket. Its own container is the local edge, which\n\t// is what lets it answer on a *host* rather than on a path under MinIO —\n\t// the shape it has deployed, and the one MinIO's virtual-host mode cannot\n\t// produce because that mode reads the leading label as the bucket name.\n\t//\n\t// MinIO still runs: it is implied by the bucket this server is declared\n\t// over, which is a parent it always has.\n\t'file-server': 'caddy',\n\t// Every mail backend speaks SMTP, so locally there is one answer whichever\n\t// one is selected — which is the same reason the declaration has no\n\t// `provider` field.\n\temail: 'mailpit',\n};\n\n/**\n * The container a cache backend needs locally.\n *\n * Each one matches the protocol its deployed form speaks, which is the property\n * worth having: a cache that behaves differently in the two places is worse than\n * a slower one.\n *\n * `db` is deliberately absent, the same way `pgboss` is absent from\n * `EVENT_CONTAINERS`: the cache is a table in a database the manifest already\n * declares, so mapping it here would start a container for a project that needs\n * none.\n */\nconst CACHE_CONTAINERS: Partial<Record<CacheBackend, string>> = {\n\t// The proxy, not the Redis behind it: the client speaks HTTP with a token\n\t// wherever it runs, which is what makes dev and prod the same client.\n\tupstash: 'redis-http',\n\t// The wire protocol, which is what ElastiCache offers — so dev speaks it too.\n\telasticache: 'redis',\n};\n\n/**\n * Kinds that resolve a value without anything running.\n *\n * A secret is the case: it has no address, so there is nothing to start and\n * nothing to connect to — but it still appears in the plan, because the target\n * still has to resolve it.\n */\nconst CONTAINERLESS: Partial<Record<DeclarationKind, true>> = {\n\tsecret: true,\n\t// A credential has no address either — and unlike a secret it has no value\n\t// this target can derive, because it was issued by somebody else.\n\tcredential: true,\n\t// An encryption key is a value too: a keyring the target derives, the way\n\t// it derives a secret.\n\tencryption: true,\n\t// A surface answers on the app's own port. It is the first kind whose\n\t// address belongs to something gkm starts rather than something Docker does.\n\t'rest-api': true,\n\t// A site is the second: it is served by its own framework's dev server, and\n\t// what the target resolves for it is only where that server answers.\n\tsite: true,\n\t// A mobile app resolves its scheme, which nothing has to be running for.\n\t'mobile-app': true,\n\t// An external API is answered by its fake. A module fake runs in no\n\t// container — gkm serves it — and an image's container is added per API,\n\t// since no kind-wide table can name it.\n\t'external-api': true,\n};\n\n/**\n * Containers that cannot run alone.\n *\n * The one case is the cache: `serverless-redis-http` is a proxy and needs the\n * Redis it proxies. Kept here rather than in the compose definition so the plan\n * -- which is what decides what must be running -- still names everything.\n */\nconst REQUIRES: Readonly<Record<string, readonly string[]>> = {\n\t'redis-http': ['redis'],\n};\n\n/**\n * The role each kind's one key plays, where it is not a URL.\n *\n * The twin of `ProvidesByKind` in `@geekmidas/manifest`: that is what a\n * construct declares, this is what the local target resolves, and both derive\n * the key from the same `provideKey` so they cannot disagree.\n */\nconst ROLES: Partial<Record<DeclarationKind, string>> = {\n\tqueue: 'publisherConnectionString',\n\ttopic: 'publisherConnectionString',\n\tcredential: 'credentials',\n\t'mobile-app': 'scheme',\n};\n\n/** The kinds whose container is the events backend's rather than their own. */\nconst EVENT_KINDS: Partial<Record<DeclarationKind, true>> = {\n\tqueue: true,\n\ttopic: true,\n};\n\n/**\n * Which kinds provision something *inside* their container.\n *\n * A database creates a database, a bucket creates a bucket; mail creates\n * nothing — Mailpit accepts whatever is sent to it and has no per-stage object\n * to name. Both still appear in the plan, because both still resolve a URL; the\n * flag is the difference between \"must be running\" and \"must be created\".\n */\nconst PROVISIONS: Partial<Record<DeclarationKind, true>> = {\n\tdatabase: true,\n\t'database-reader': true,\n\t'database-schema': true,\n\tobjects: true,\n\t// Only a cache that lives in a database creates anything — its table. The\n\t// other backends have nothing to create inside the container they use.\n\tcache: true,\n\t// The open patterns become a bucket policy on the origin — real\n\t// enforcement locally, from the same declaration the CDN behaviour comes\n\t// from deployed.\n\t'file-server': true,\n};\n// Queues and topics are absent deliberately: pg-boss creates its own schema and\n// tables on first connect, and a RabbitMQ exchange is declared by the client\n// that binds to it. Both still appear in the plan, because both still resolve a\n// connection string.\n\n/**\n * The container each events backend needs, where it needs one of its own.\n *\n * `pgboss` is deliberately absent rather than mapped to `postgres`: it is a\n * schema tenant in a database the manifest already declares, so mapping it here\n * would start a Postgres for a project that declared no database at all.\n *\n * This one comes from config rather than from a declaration because there are\n * no `topic` or `queue` kinds yet. When they land the mapping moves to\n * {@link CONTAINERS} keyed by kind, and this constant goes with them.\n */\nconst EVENT_CONTAINERS: Partial<Record<EventsBackend, string>> = {\n\tsns: 'localstack',\n\trabbitmq: 'rabbitmq',\n};\n\n/** The backend a project gets when it declares queues or topics and says nothing. */\nexport const DEFAULT_EVENTS: EventsBackend = 'pgboss';\n\n/**\n * How a resource of each kind separates its stage suffix.\n *\n * Postgres identifiers are snake_case and a hyphen would need quoting\n * everywhere; buckets and queues are kebab and disallow underscores in some\n * providers. The suffix is the same word either way.\n */\nconst SEPARATORS: Partial<Record<DeclarationKind, string>> = {\n\tdatabase: '_',\n\t'database-reader': '_',\n\t'database-schema': '_',\n\tobjects: '-',\n};\n\n/** One thing that must exist, and what it is called in this stage. */\nexport interface PlannedResource {\n\t/** The construct's canonical id, e.g. `Orders`. */\n\tid: string;\n\tkind: DeclarationKind;\n\t/** The container that serves it, e.g. `postgres`. Absent for a secret. */\n\tcontainer?: string;\n\t/** The stage-scoped name, e.g. `orders_test`. */\n\tname: string;\n\t/** The env key it resolves onto anything depending on it. */\n\tenvKey: string;\n\t/** The parent it derives from, for kinds that provision nothing themselves. */\n\tof?: string;\n\t/**\n\t * Whether anything has to be created inside the container for this.\n\t *\n\t * False for mail: Mailpit takes whatever is sent to it. The applier skips\n\t * these; the env writer still resolves their URL.\n\t */\n\tprovisions: boolean;\n\t/**\n\t * The site the base domain points at, by the rule a deploy uses\n\t * (`rootSite`) — so locally it answers on the project's bare host, the\n\t * shape production serves, rather than a subdomain of it.\n\t */\n\troot?: true;\n\t/**\n\t * The label a surface or site answers on, its declaration's `subdomain` —\n\t * the same one a deploy puts under the stage's domain.\n\t */\n\tsubdomain?: string;\n\t/** The schema a tenant pins on its roles' `search_path`. */\n\tschema?: string;\n\t/** For a cache in a database: the table entries are kept in. */\n\ttable?: string;\n\t/**\n\t * Whether to provision the owner/runtime role split.\n\t *\n\t * `false` is the documented downgrade — both URLs fall back to the cluster's\n\t * master credential — and it is a choice someone made rather than a default\n\t * they got, which is why it is carried through rather than inferred.\n\t */\n\troles?: boolean;\n\t/**\n\t * For a database: the engine major version to run.\n\t *\n\t * Carried like `schema` and `roles` — read off the declaration so the\n\t * container tag and the deployed engine come from one statement. They used\n\t * to come from two, and had drifted a major apart with nothing recording it.\n\t */\n\tversion?: PostgresVersion;\n\t/**\n\t * For a file server: the paths it serves without a signature.\n\t *\n\t * Carried into the plan because the local target enforces them for real — a\n\t * bucket policy with prefix resources, from the same declaration the CDN\n\t * behaviour comes from deployed. An open path that is open locally and shut\n\t * in production is the bug this avoids.\n\t */\n\topen?: readonly string[];\n\t/**\n\t * For an external API: the port key its fake answers on, and the\n\t * credentials it accepts — what a local stage is handed in place of the\n\t * provider and the stage's own.\n\t */\n\tfake?: { key: string; credentials: string };\n\t/**\n\t * For an external API with no fake in this plan: the URL it is called at,\n\t * as declared — one string, or one per stage name.\n\t */\n\turl?: string | Readonly<Record<string, string>>;\n\t/**\n\t * For a site: the keys its bundle needs, mapped to the key each value comes\n\t * from — `{ VITE_API_URL: 'API_URL' }`.\n\t *\n\t * One neutral name from the construct and one serialisation per framework.\n\t * The prefix is the whole of what varies, which is why the mapping is a\n\t * rename rather than a second derivation: `API_URL` is resolved once, by the\n\t * same code that resolves it for the server, and a site simply reads it\n\t * under the name its bundler will inline.\n\t */\n\tpublicEnv?: Record<string, string>;\n\t/** For a mobile app: the scheme it gave, when not the project's name. */\n\tscheme?: string;\n\t/**\n\t * The ids that depend on this one — the graph read backwards.\n\t *\n\t * Carried on surfaces, where it is the whole answer to \"who may call this\":\n\t * CORS origins, trusted origins, and the cookie domain are three readings of\n\t * this one list. Resolved to addresses by the target, because the manifest\n\t * knows which constructs call which and only the target knows where any of\n\t * them answer.\n\t */\n\tcallers?: string[];\n}\n\nexport interface Plan {\n\tstage: string;\n\t/**\n\t * The events backend this plan was built for.\n\t *\n\t * Carried on the plan rather than passed alongside it, because everything\n\t * downstream that composes a connection string needs it and reading it off\n\t * the plan is what keeps the two from disagreeing.\n\t */\n\tevents: EventsBackend;\n\t/** Where the cache lives, for everything downstream that composes its URL. */\n\tcache: CacheBackend;\n\t/**\n\t * A worker is declared and the broker is pg-boss, so the broker is needed\n\t * whether or not a queue or topic is.\n\t *\n\t * On a server a worker's crons are scheduled through the events broker —\n\t * the app's one pg-boss. Crons are not in the manifest (a worker does not\n\t * list them), so the worker is the signal. pg-boss is a schema in the\n\t * declared database, so this starts nothing and provisions nothing; it only\n\t * means the broker's connection strings are resolved.\n\t */\n\tworkerBroker?: true;\n\t/** Containers to start, deduplicated. */\n\tcontainers: string[];\n\t/**\n\t * Each external API's fake, by its port key — `<id>-fake`. An image fake's\n\t * key is also in `containers`; a module fake's is a port gkm serves on.\n\t */\n\tfakes?: Record<string, PlannedFake>;\n\t/**\n\t * Everything the stage resolves a URL for, parents before children.\n\t *\n\t * Not all of it is provisioned — see `PlannedResource.provisions`.\n\t */\n\tresources: PlannedResource[];\n}\n\n/** What the plan needs that the manifest cannot tell it. */\nexport interface PlanOptions {\n\t/**\n\t * The project's local stage (`stages.local`), whose resources carry no\n\t * suffix. Absent, every stage is suffixed — which is right for a deploy,\n\t * since a deployed stage is never the local one.\n\t */\n\tlocalStage?: string;\n\t/**\n\t * Whether the local edge fronts the addresses this plan resolves.\n\t *\n\t * On by default, because deployed every surface, site and file server has a\n\t * real hostname and a certificate, and the point of the local target is to\n\t * be the same shape rather than a near-enough one. Off falls back to\n\t * `http://localhost:<port>`.\n\t *\n\t * Config rather than declaration for the same reason `cache` is: the\n\t * application reads whichever address was injected and composes none, so it\n\t * cannot tell the difference.\n\t */\n\tedge?: boolean;\n\t/**\n\t * Where a declared cache lives, which decides both the container it needs\n\t * locally and the protocol its URL speaks.\n\t *\n\t * Config rather than declaration, because the same application code caches\n\t * into any of them — the same reason `events` is config.\n\t */\n\tcache?: CacheBackend;\n\t/**\n\t * The events backend, which selects a container of its own for everything\n\t * except pg-boss.\n\t *\n\t * Config rather than declaration only until `topic` and `queue` are kinds —\n\t * see {@link EVENT_CONTAINERS}.\n\t */\n\tevents?: EventsBackend;\n\t/**\n\t * Containers no construct implies — the escape hatch, and the whole of what\n\t * the workspace `services:` block becomes.\n\t *\n\t * A list of exceptions rather than a list you maintain: everything a\n\t * construct implies is already in the plan before this is read.\n\t */\n\textraContainers?: readonly string[];\n\t/**\n\t * Each external API's fake, by id — read from `test/fakes` by `readFakes`,\n\t * since the declaration never names one. An external API without one\n\t * resolves no URL.\n\t */\n\tfakes?: Readonly<Record<string, LocalFake>>;\n}\n\n/**\n * The stage-scoped name for a resource.\n *\n * The local stage carries no suffix, so `gkm dev` keeps using `orders` and a\n * developer's psql history, saved connections, and muscle memory survive a\n * change of stage name. Every other stage is suffixed.\n */\nexport function resourceName(\n\tid: string,\n\tkind: DeclarationKind,\n\tstage: string,\n\t/** The project's local stage, whose names are unsuffixed. */\n\tlocalStage?: string,\n): string {\n\tconst base = id.toLowerCase();\n\tif (stage === localStage) return base;\n\n\treturn `${base}${SEPARATORS[kind] ?? '-'}${stage}`;\n}\n\n/**\n * The container a kind needs, or `undefined` if it needs none.\n *\n * Queues and topics have no container of their own — they live in whichever\n * broker the project selected, which is why the backend is an argument here\n * rather than a second pass that adds containers after the fact.\n */\nexport function containerFor(\n\tkind: DeclarationKind,\n\tevents: EventsBackend = DEFAULT_EVENTS,\n\t// The target-aware choice is made upstream, in `reconcile/workspace.ts`,\n\t// where the workspace knows where it deploys. This fires only for a caller\n\t// that passed nothing at all.\n\tcache: CacheBackend = DEFAULT_CACHE.aws,\n\t/** Whether the declaration named a parent — see the `cache` branch. */\n\tderived = false,\n\t/** Whether the local edge exists — see the `file-server` branch. */\n\tedge = true,\n): string | undefined {\n\tif (EVENT_KINDS[kind]) {\n\t\t// pg-boss is deliberately absent from EVENT_CONTAINERS: it is a schema\n\t\t// tenant in a database the manifest already declares, so its container is\n\t\t// that database's.\n\t\treturn EVENT_CONTAINERS[events] ?? CONTAINERS.database;\n\t}\n\n\t// A file server answers on the edge, which is what gives it a host of its\n\t// own. With the edge off it falls back to the object store that holds its\n\t// objects — a path under MinIO, which works and is not the deployed shape.\n\tif (kind === 'file-server') return edge ? 'caddy' : CONTAINERS.objects;\n\n\t// The same shape: a cache in the database lives in the database's container.\n\tif (kind === 'cache') {\n\t\t// A cache that *named* a database is in it whatever the backend config\n\t\t// says — the declaration is the stronger statement, and config choosing\n\t\t// otherwise would move a cache the app said lives here.\n\t\tif (derived) return CONTAINERS.database;\n\n\t\treturn CACHE_CONTAINERS[cache] ?? CONTAINERS.database;\n\t}\n\n\treturn CONTAINERS[kind];\n}\n\n/**\n * Build the plan for a manifest and stage.\n *\n * `order` is the caller's provisioning order — parents before children — which\n * `provisionOrder` produces from the manifest. Passing it in keeps this\n * function free of the traversal and makes the ordering assertable on its own.\n */\nexport function planFor(\n\tmanifest: ConstructManifest,\n\tstage: string,\n\torder: readonly string[],\n\toptions: PlanOptions = {},\n): Plan {\n\tconst containers = new Set<string>();\n\tconst resources: PlannedResource[] = [];\n\tconst fakes: Record<string, PlannedFake> = {};\n\n\tconst events = options.events ?? DEFAULT_EVENTS;\n\tconst cache = options.cache ?? DEFAULT_CACHE.aws;\n\n\tfor (const id of order) {\n\t\tconst declaration: Declaration | undefined = manifest[id];\n\t\tif (!declaration) continue;\n\n\t\tconst local =\n\t\t\tdeclaration.kind === 'external-api' ? options.fakes?.[id] : undefined;\n\t\tconst fake = local && { key: fakeKey(id), ...local };\n\t\tif (fake) {\n\t\t\tfakes[fake.key] = {\n\t\t\t\tid,\n\t\t\t\t...(fake.image ? { image: fake.image, port: fake.port } : {}),\n\t\t\t};\n\t\t}\n\n\t\tconst container = fake?.image\n\t\t\t? fake.key\n\t\t\t: containerFor(\n\t\t\t\t\tdeclaration.kind,\n\t\t\t\t\tevents,\n\t\t\t\t\tcache,\n\t\t\t\t\t'of' in declaration && typeof declaration.of === 'string',\n\t\t\t\t\toptions.edge !== false,\n\t\t\t\t);\n\t\tif (!container && !CONTAINERLESS[declaration.kind]) continue;\n\n\t\tif (container) {\n\t\t\tcontainers.add(container);\n\t\t\tfor (const required of REQUIRES[container] ?? [])\n\t\t\t\tcontainers.add(required);\n\t\t}\n\n\t\tresources.push({\n\t\t\tid,\n\t\t\tkind: declaration.kind,\n\t\t\t...(container ? { container } : {}),\n\t\t\tname: resourceName(id, declaration.kind, stage, options.localStage),\n\t\t\t// Through the shared derivation: a secret's name *is* its key, and\n\t\t\t// this target and the deploy target must not answer that separately.\n\t\t\tenvKey: providedKeyFor(\n\t\t\t\tid,\n\t\t\t\tdeclaration.kind,\n\t\t\t\tROLES[declaration.kind] ?? 'url',\n\t\t\t),\n\t\t\tprovisions: PROVISIONS[declaration.kind] === true,\n\t\t\t// Only for surfaces. Every other kind is reached by a URL that says\n\t\t\t// nothing about who holds it, so a caller list would be a fact with\n\t\t\t// no reader.\n\t\t\t...(declaration.kind === 'rest-api'\n\t\t\t\t? { callers: dependentsOf(manifest, id) }\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'site'\n\t\t\t\t? { publicEnv: publicEnvFor(declaration, manifest) }\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'mobile-app'\n\t\t\t\t? {\n\t\t\t\t\t\tpublicEnv: publicEnvFor(declaration, manifest),\n\t\t\t\t\t\t...(declaration.scheme ? { scheme: declaration.scheme } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'file-server' && declaration.open?.length\n\t\t\t\t? { open: declaration.open }\n\t\t\t\t: {}),\n\t\t\t...((declaration.kind === 'rest-api' || declaration.kind === 'site') &&\n\t\t\tdeclaration.subdomain\n\t\t\t\t? { subdomain: declaration.subdomain }\n\t\t\t\t: {}),\n\t\t\t...(fake\n\t\t\t\t? { fake: { key: fake.key, credentials: fake.credentials } }\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'external-api' && !fake\n\t\t\t\t? { url: declaration.url }\n\t\t\t\t: {}),\n\t\t\t...('of' in declaration ? { of: declaration.of } : {}),\n\t\t\t...('schema' in declaration && declaration.schema\n\t\t\t\t? { schema: declaration.schema }\n\t\t\t\t: {}),\n\t\t\t...('version' in declaration && declaration.version\n\t\t\t\t? { version: declaration.version }\n\t\t\t\t: {}),\n\t\t\t...('roles' in declaration && declaration.roles === false\n\t\t\t\t? { roles: false }\n\t\t\t\t: {}),\n\t\t\t...('table' in declaration && declaration.table\n\t\t\t\t? { table: declaration.table }\n\t\t\t\t: {}),\n\t\t});\n\t}\n\n\t// The edge serves anything that owns a public address — a surface and a site\n\t// as much as a file server. Added here rather than in `containerFor` because\n\t// those two are containerless in every other sense: nothing runs them in\n\t// Docker, and what the edge does is put a hostname and a certificate in\n\t// front of a process on the host.\n\t//\n\t// `edge: false` opts out, and then every local address is\n\t// `http://localhost:<port>` as before. The application cannot tell: it reads\n\t// whichever address was injected and composes none.\n\tif (\n\t\toptions.edge !== false &&\n\t\tresources.some((r) => EDGE_KINDS[r.kind] === true)\n\t) {\n\t\tcontainers.add('caddy');\n\t}\n\n\tfor (const extra of options.extraContainers ?? []) containers.add(extra);\n\n\t// pg-boss lives in a declared database. Without one there is nothing for it\n\t// to live in, and starting a Postgres to hold only a queue would be the\n\t// container this design refuses to invent.\n\tif (\n\t\tevents === 'pgboss' &&\n\t\tresources.some((r) => EVENT_KINDS[r.kind]) &&\n\t\t!resources.some((r) => r.kind === 'database')\n\t) {\n\t\tthrow new PgBossNeedsDatabase(\n\t\t\tresources.filter((r) => EVENT_KINDS[r.kind]).map((r) => r.id),\n\t\t);\n\t}\n\n\t// A cache the deploy target placed in a database becomes an *edge*, here,\n\t// once.\n\t//\n\t// A server target's `'db'` cache names something that exists in this graph, which\n\t// makes it different in kind from `upstash` or `elasticache`: those name a\n\t// backend nothing declares, while this one names a database sitting right\n\t// there. So it is resolved to `of` — the same field `database.cache()` sets\n\t// — and every reader downstream follows an edge instead of re-deriving the\n\t// answer from a string.\n\t//\n\t// It also turns a silent guess into an error. \"The declared database\" is\n\t// unambiguous with one and arbitrary with two, and picking whichever came\n\t// first would put the cache in a database nobody chose.\n\t//\n\t// A cache that named its parent is untouched: the declaration is the\n\t// stronger statement, and its parent is checked by `assertDerivations`,\n\t// which names the database that is missing rather than reporting that some\n\t// database is.\n\tif (cache === 'db') {\n\t\tconst placeless = resources.filter((r) => r.kind === 'cache' && !r.of);\n\n\t\tif (placeless.length > 0) {\n\t\t\tconst databases = resources.filter((r) => r.kind === 'database');\n\t\t\tconst [only] = databases;\n\n\t\t\t// Starting a Postgres to hold only a cache would be the container this\n\t\t\t// design refuses to invent.\n\t\t\tif (!only) {\n\t\t\t\tthrow new CacheNeedsDatabase(\n\t\t\t\t\tresources.filter((r) => r.kind === 'cache').map((r) => r.id),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (databases.length > 1) {\n\t\t\t\tthrow new CacheIsAmbiguous(\n\t\t\t\t\tplaceless.map((r) => r.id),\n\t\t\t\t\tdatabases.map((r) => r.id),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tfor (const resource of placeless) resource.of = only.id;\n\t\t}\n\t}\n\n\t// The site the base domain points at: the same answer a deploy gives, so the\n\t// local edge serves it on the bare host.\n\tconst sites = resources.filter((r) => r.kind === 'site');\n\tconst root = rootSite(\n\t\tsites.map((r) => {\n\t\t\tconst declared = manifest[r.id];\n\t\t\treturn {\n\t\t\t\tname: appKey(r.id),\n\t\t\t\t...(declared?.kind === 'site' && declared.root ? { root: true } : {}),\n\t\t\t};\n\t\t}),\n\t);\n\tfor (const site of sites) {\n\t\tif (appKey(site.id) === root) site.root = true;\n\t}\n\n\tconst workerBroker =\n\t\tevents === 'pgboss' &&\n\t\tObject.values(manifest).some((d) => d?.kind === 'worker') &&\n\t\tresources.some((r) => r.kind === 'database');\n\n\treturn {\n\t\tstage,\n\t\tevents,\n\t\tcache,\n\t\tcontainers: [...containers],\n\t\tresources,\n\t\t...(Object.keys(fakes).length ? { fakes } : {}),\n\t\t...(workerBroker ? { workerBroker: true as const } : {}),\n\t};\n}\n\n/**\n * Queues or topics were declared with pg-boss and no database to hold them.\n *\n * Not a reconcile failure but a manifest one: the fix is a line of application\n * code either way — declare a database, or select a backend that brings its own\n * broker.\n */\nexport class CacheNeedsDatabase extends Error {\n\tconstructor(readonly ids: readonly string[]) {\n\t\tsuper(\n\t\t\t`A cache backed by the database needs a declared database, and none ` +\n\t\t\t\t`was declared, and on this deploy target that is where a cache lives. ` +\n\t\t\t\t`Declare one. Caches affected: ${ids.join(', ')}.`,\n\t\t);\n\t\tthis.name = 'CacheNeedsDatabase';\n\t}\n}\n\n/**\n * A `'db'` cache — a server target's default — in an app that declares more\n * than one database.\n *\n * The target says *a* database and the graph offers several, so there is no\n * answer to give — and the wrong kind of failure would be to pick one, since a\n * cache silently landing in the wrong database is a bug that surfaces as\n * missing rows much later. The fix is a stronger statement in application code:\n * `orders.cache('Sessions')` names the database, which is a fact about the\n * application rather than a deployment choice.\n */\nexport class CacheIsAmbiguous extends Error {\n\tconstructor(\n\t\treadonly ids: readonly string[],\n\t\treadonly databases: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`A cache backed by the database needs to know which one, and this app ` +\n\t\t\t\t`declares ${databases.length}: ${databases.join(', ')}. Declare the ` +\n\t\t\t\t`cache from its database — e.g. ${databases[0]}.cache('Sessions') — ` +\n\t\t\t\t`rather than as a bare new Cache(). Caches affected: ${ids.join(', ')}.`,\n\t\t);\n\t\tthis.name = 'CacheIsAmbiguous';\n\t}\n}\n\nexport class PgBossNeedsDatabase extends Error {\n\tconstructor(readonly ids: readonly string[]) {\n\t\tsuper(\n\t\t\t`pg-boss keeps its queues in a declared database, and none was declared. ` +\n\t\t\t\t`Declare one: on this deploy target, events are carried by pg-boss. ` +\n\t\t\t\t`Queues and topics affected: ${ids.join(', ')}.`,\n\t\t);\n\t\tthis.name = 'PgBossNeedsDatabase';\n\t}\n}\n","/**\n * The URLs a reconciled stage injects.\n *\n * One `<NAME>_URL` per construct, and the protocol picks the driver. This is the\n * seam between the target that writes a URL and the client that parses it, so\n * every component of it is read off the resource — never inherited from ambient\n * environment. A bucket can live in a different region than the function reading\n * it, and `AWS_REGION` in a Lambda is the *function's* region, so a URL that\n * omits `?region=` breaks cross-region silently at runtime.\n *\n * Locally that self-containment is what lets the port be whatever was free: the\n * app reads `ORDERS_URL` and never sees a host or a port.\n */\n\nimport { createHash } from 'node:crypto';\nimport { formatKeyring } from '@geekmidas/constructs/encryption';\nimport { ownerRole, readerRole } from '@geekmidas/db/pg/roles';\nimport * as snsUrl from '@geekmidas/events/sns/url';\nimport * as sqsUrl from '@geekmidas/events/sqs/url';\nimport {\n\tcacheTable,\n\tcookieDomain,\n\texternalApiUrl,\n\tmobileOrigins,\n\tprovideKey,\n\tschemeBase,\n} from '@geekmidas/manifest';\nimport { hostFor } from './caddyfile';\nimport { primaryPortKey } from './containers';\nimport {\n\tEMULATOR_CREDENTIALS,\n\tEMULATOR_REGION,\n\temulatorEndpoint,\n\temulatorQueueUrl,\n\temulatorTopicArn,\n} from './emulator';\nimport { PgBossNeedsDatabase, type Plan, type PlannedResource } from './plan';\nimport type { PortAssignments } from './ports';\n\n/**\n * The credential local containers are brought up with.\n *\n * The design's `roles: false` case — both URLs fall back to the cluster's master\n * credential. A deliberate downgrade, not a default: it holds until the role DDL\n * lands in `@geekmidas/db`, at which point the runtime role's URL replaces this\n * and the owner URL stops being reachable from any manifest edge.\n */\nconst LOCAL_USER = 'geekmidas';\n\n/** The host containers are published on. */\nconst LOCAL_HOST = 'localhost';\n\n/** The region local buckets claim, so the client has one to sign with. */\nconst LOCAL_REGION = 'us-east-1';\n\n/**\n * The credentials the S3 client resolves for MinIO.\n *\n * An `s3://` URL deliberately carries none: deployed, the SDK reads them from\n * the execution role, so a URL that embedded a key would be one more thing to\n * rotate and one more thing to leak into a log. Locally there is no role, and\n * the same chain reads these — which is why they are injected beside the URL\n * rather than written into it.\n */\nconst STORAGE_CREDENTIALS: Readonly<Record<string, string>> = {\n\tAWS_ACCESS_KEY_ID: LOCAL_USER,\n\tAWS_SECRET_ACCESS_KEY: LOCAL_USER,\n\tAWS_REGION: LOCAL_REGION,\n};\n\n/** The token the local cache proxy accepts. Matches the compose definition. */\nconst LOCAL_TOKEN = 'geekmidas';\n\n/** The port Mailpit's inbox — its web UI and HTTP API — is published under. */\nconst MAILPIT_INBOX_PORT = 'mailpit-web';\n\n/** The schema pg-boss keeps its tables in, inside the declared database. */\nconst PGBOSS_SCHEMA = 'pgboss';\n\n/** The exchange every local topic and queue shares on RabbitMQ. */\nconst RABBITMQ_EXCHANGE = 'geekmidas.events';\n\nexport interface EnvOptions {\n\t/** Assigned ports, keyed by port key. */\n\tports: PortAssignments;\n\t/**\n\t * The project, which seeds any secret this stage derives.\n\t *\n\t * Derived rather than random so it survives a restart -- a signing key that\n\t * changed on every `gkm dev` would invalidate every session you were in the\n\t * middle of -- and seeded by the project so two checkouts do not share one.\n\t */\n\tproject?: string;\n\t/**\n\t * Where each address-owning construct answers, keyed by id — surfaces and\n\t * sites, e.g. `{ Api: 'http://localhost:3000', Console: 'http://localhost:5173' }`.\n\t *\n\t * Not container addresses: `gkm dev` assigns these, which is why they arrive\n\t * here rather than being read off a published port. Keyed by id rather than\n\t * being one `surface` string because there is more than one address in a\n\t * workspace and the difference between them is exactly what CORS is about.\n\t */\n\taddresses?: Readonly<Record<string, string>>;\n\t/**\n\t * This machine's address on the local network — what a phone running the\n\t * app reaches it on. Only a local stage has one, and only a project with a\n\t * mobile app reads it: its `exp://` origins, and the address sign-in links\n\t * for the app are built on.\n\t */\n\tlanAddress?: string;\n\t/** Each mobile app's Metro port, by id — the exact port its `exp://` origins name. */\n\tmetroPorts?: Readonly<Record<string, number>>;\n\t/**\n\t * The domain mail is sent from locally.\n\t *\n\t * Stage config, exactly as it is deployed — the difference is only that here\n\t * nobody had to verify it.\n\t */\n\tmailFrom?: string;\n\t/**\n\t * The stage's random seed, salting every derived role password.\n\t *\n\t * Absent locally, where the cluster is on loopback and a password derived\n\t * from the project and stage alone is the convenience the local stage\n\t * wants. A stack that serves a deployed stage passes the seed its store\n\t * keeps, so its passwords are the ones a deploy derives — and are not\n\t * computable from the repo.\n\t */\n\tseed?: string;\n\t/**\n\t * The cluster master's password, where it is not the local one.\n\t *\n\t * pg-boss and a `roles: false` database connect as the master, so its\n\t * password is in their URLs; a deployed stage's is derived from its seed.\n\t */\n\tmaster?: string;\n}\n\n/**\n * The credentials a stage's URLs carry: the master's password and the seed\n * role passwords are salted with. Locally both are the fixed defaults.\n */\ninterface StageCredential {\n\tmaster: string;\n\tseed?: string;\n}\n\n/** The credential `options` describe, defaulting to the local one. */\nfunction credentialOf(options: { seed?: string; master?: string }) {\n\treturn {\n\t\tmaster: options.master ?? LOCAL_USER,\n\t\t...(options.seed ? { seed: options.seed } : {}),\n\t} satisfies StageCredential;\n}\n\n/**\n * Every env key a reconciled plan resolves, and its value.\n *\n * Keys come from the declaration rather than being re-derived, so what the build\n * publishes and what the app reads cannot drift.\n */\nexport function envFor(\n\tplan: Plan,\n\toptions: EnvOptions,\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tconst credential = credentialOf(options);\n\n\t// Resolved once, up front, because a surface's cookie domain and origin list\n\t// are derived from *other constructs'* addresses — and reading them as the\n\t// loop happened to reach them would make the answer depend on the order the\n\t// manifest was keyed in.\n\tconst resolved: Record<string, string> = {};\n\tfor (const resource of plan.resources) {\n\t\tconst url = urlFor(\n\t\t\tresource,\n\t\t\tplan,\n\t\t\toptions.ports,\n\t\t\toptions.project ?? '',\n\t\t\toptions.addresses,\n\t\t\tcredential,\n\t\t);\n\t\tif (url) resolved[resource.id] = url;\n\t}\n\n\tfor (const resource of plan.resources) {\n\t\tconst url = resolved[resource.id];\n\n\t\t// A surface carries the origins its callers may come from, and they are\n\t\t// its inbound edges — nothing more. Better Auth's CSRF check applies to\n\t\t// every caller and not only to browsers, so a sibling service calling it\n\t\t// is rejected unless its origin is listed; that this list is now the\n\t\t// graph rather than every app the workspace happens to run is what makes\n\t\t// it the same list deployed, where no workspace is watching.\n\t\tif (resource.kind === 'rest-api') {\n\t\t\tObject.assign(\n\t\t\t\tenv,\n\t\t\t\tsurfaceEnv(resource, url, resolved, {\n\t\t\t\t\tplan,\n\t\t\t\t\t...(options.addresses ? { addresses: options.addresses } : {}),\n\t\t\t\t\t...(options.lanAddress ? { lanAddress: options.lanAddress } : {}),\n\t\t\t\t\t...(options.metroPorts ? { metroPorts: options.metroPorts } : {}),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tif (url) env[resource.envKey] = url;\n\n\t\t// A database owns a second key, and it is deliberately not in `provides`:\n\t\t// the owner URL is what a migrator connects with, so no edge in any\n\t\t// manifest can name it and nothing can be granted DDL rights by mistake.\n\t\t// `gkm exec` injects it; a handler never sees it.\n\t\tif (isDatabase(resource.kind) && resource.roles !== false) {\n\t\t\tconst owner = ownerUrl(\n\t\t\t\tresource,\n\t\t\t\tplan,\n\t\t\t\toptions.ports,\n\t\t\t\toptions.project ?? '',\n\t\t\t\tcredential,\n\t\t\t);\n\t\t\tif (owner) env[provideKey(resource.id, 'ownerUrl')] = owner;\n\t\t}\n\n\t\t// An external API owns a second key. Faked, it is the fake's; otherwise\n\t\t// it is the stage's own, from its secrets like any value it was given.\n\t\tif (resource.kind === 'external-api' && url && resource.fake) {\n\t\t\tenv[provideKey(resource.id, 'credentials')] = resource.fake.credentials;\n\t\t}\n\n\t\t// Mail owns a second key. It is the sending identity, which is the one\n\t\t// thing about mail that differs per stage — so it travels with the URL\n\t\t// rather than being written into the construct.\n\t\tif (resource.kind === 'email' && url) {\n\t\t\tenv[provideKey(resource.id, 'from')] =\n\t\t\t\toptions.mailFrom ?? `noreply@${LOCAL_HOST}`;\n\n\t\t\t// And, locally, a third: where the mail it sent can be read back.\n\t\t\t// Mailpit answers SMTP on one port and its inbox on another; a feature\n\t\t\t// test signs in by opening the email that was actually sent, and this\n\t\t\t// is how it finds it. Deployed mail has no inbox, so nothing deployed\n\t\t\t// publishes this key.\n\t\t\tconst inbox = options.ports[MAILPIT_INBOX_PORT];\n\t\t\tif (inbox !== undefined) {\n\t\t\t\tenv[provideKey(resource.id, 'inboxUrl')] =\n\t\t\t\t\t`http://${LOCAL_HOST}:${inbox}`;\n\t\t\t}\n\t\t}\n\t}\n\n\t// After the loop, and deliberately: a site's keys are renames of values the\n\t// constructs it depends on resolved, so every source has to exist before any\n\t// of them can be read. Doing it inline would make the result depend on the\n\t// order the manifest happened to be keyed in.\n\tObject.assign(env, publicEnv(plan, env, options.addresses));\n\n\tObject.assign(env, brokerEnv(plan, options.ports, credential));\n\n\t// Only once a bucket actually resolved: an unresolvable plan resolves\n\t// nothing, and credentials for a container that is not running are noise.\n\tif (plan.resources.some((r) => r.kind === 'objects' && env[r.envKey])) {\n\t\tObject.assign(env, STORAGE_CREDENTIALS);\n\t}\n\n\treturn env;\n}\n\n/**\n * The values a site's bundler inlines, under the names it inlines them by.\n *\n * A rename and nothing more. `API_URL` was resolved once, by the same code that\n * resolved it for the server; a site reads the same value under `VITE_API_URL`\n * because that prefix is how its bundler is told to ship it. Nothing is derived\n * twice, so a site and its API cannot come to disagree about where the API is.\n *\n * A source that resolved to nothing is skipped rather than written empty: an\n * inlined empty string is a frontend that builds and then fails at runtime\n * against `http:///`, where a missing variable fails at build with the name of\n * the thing that is missing.\n */\nfunction publicEnv(\n\tplan: Plan,\n\tresolved: Record<string, string>,\n\t/** Where each surface answers on its own port — what a mobile app is given. */\n\taddresses: Readonly<Record<string, string>> = {},\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\t// Which construct each key belongs to, so a mobile app can be handed the\n\t// surface's own port rather than the edge's hostname.\n\tconst owners = new Map(plan.resources.map((r) => [r.envKey, r.id]));\n\n\tfor (const resource of plan.resources) {\n\t\tfor (const [key, source] of Object.entries(resource.publicEnv ?? {})) {\n\t\t\t// A phone cannot open `https://api-dev.shop.localhost` — the hostname\n\t\t\t// resolves only on this machine, and the edge routes by it, so no LAN\n\t\t\t// address can stand in for it. The surface's own port can: the app\n\t\t\t// swaps `localhost` for the host Metro was served from, and reaches it.\n\t\t\tconst direct =\n\t\t\t\tresource.kind === 'mobile-app'\n\t\t\t\t\t? addresses[owners.get(source) ?? '']\n\t\t\t\t\t: undefined;\n\t\t\tconst value = direct ?? resolved[source];\n\t\t\tif (value) env[key] = value;\n\t\t}\n\t}\n\n\treturn env;\n}\n\n/**\n * What a surface publishes beyond its own address: who may call it, and where\n * a cookie set by it is readable.\n *\n * Both are read off the same list — the constructs that declared an edge to\n * this surface — which is why neither appears in application code. A surface\n * that listed its own callers would be edited every time something new called\n * it, and the thing being edited is already recorded in the graph.\n *\n * An empty origin list is written, and a surface with no address writes nothing\n * at all. The two cases look alike and are not: \"nothing depends on this yet\" is\n * a real state a target should publish, while \"this surface has no address here\"\n * means it is not running in this stage, and keys belong with the address they\n * describe.\n */\nfunction surfaceEnv(\n\tresource: PlannedResource,\n\turl: string | undefined,\n\t/**\n\t * Every construct's *resolved* address, keyed by id — not the raw ones the\n\t * workspace assigned.\n\t *\n\t * The difference is the feature. Behind the edge a surface and its callers\n\t * are `api.shop.localhost` and `console.shop.localhost`, which share a\n\t * parent and therefore a cookie; the addresses they were assigned are\n\t * `localhost:3000` and `localhost:5173`, which are one host with two ports\n\t * and share nothing. Deriving from the wrong one produces a local cookie\n\t * model that is *different* from the deployed one rather than matching it.\n\t */\n\tresolved: Readonly<Record<string, string>> = {},\n\tlocal: {\n\t\tplan?: Plan;\n\t\taddresses?: Readonly<Record<string, string>>;\n\t\tlanAddress?: string;\n\t\tmetroPorts?: Readonly<Record<string, number>>;\n\t} = {},\n): Record<string, string> {\n\tif (!url) return {};\n\n\tconst origins = [\n\t\t...new Set(\n\t\t\t(resource.callers ?? [])\n\t\t\t\t.map((caller) => resolved[caller])\n\t\t\t\t.filter((address): address is string => Boolean(address))\n\t\t\t\t.map(originOf)\n\t\t\t\t.filter((origin): origin is string => Boolean(origin)),\n\t\t),\n\t].sort();\n\n\t// Its own address belongs in the cookie derivation but not in the origin\n\t// list: a surface does not need permission to call itself, and adding it\n\t// would make every surface trust every other one that shares a port.\n\tconst domain = cookieDomain([url, ...origins]);\n\n\t// A mobile caller is reached by its scheme, not an address, so it adds its\n\t// scheme — and, on this local stage, the `exp://` hosts Expo Go sends from —\n\t// rather than an origin read off a URL. After the cookie domain on purpose:\n\t// a scheme is not a host anything shares a cookie with.\n\tconst mobile = (resource.callers ?? [])\n\t\t.map((caller) =>\n\t\t\tlocal.plan?.resources.find(\n\t\t\t\t(r) => r.id === caller && r.kind === 'mobile-app',\n\t\t\t),\n\t\t)\n\t\t.filter((caller): caller is PlannedResource => Boolean(caller));\n\tconst hosts = [...(local.lanAddress ? [local.lanAddress] : []), LOCAL_HOST];\n\tconst schemes = mobile.flatMap((caller) => {\n\t\tconst scheme = resolved[caller.id];\n\t\tconst port = local.metroPorts?.[caller.id];\n\t\treturn scheme\n\t\t\t? mobileOrigins(scheme, { hosts, ...(port ? { port } : {}) })\n\t\t\t: [];\n\t});\n\n\t// Where a phone reaches this surface: its own port on the LAN address. What\n\t// the auth server builds a sign-in link on when the app asked for it — the\n\t// link is opened on the phone, which cannot resolve the edge's hostname.\n\tconst own = local.addresses?.[resource.id];\n\tconst device =\n\t\tmobile.length > 0 && local.lanAddress && own\n\t\t\t? own.replace(\n\t\t\t\t\t/\\/\\/(localhost|127\\.0\\.0\\.1)(?=[:/]|$)/,\n\t\t\t\t\t`//${local.lanAddress}`,\n\t\t\t\t)\n\t\t\t: undefined;\n\n\treturn {\n\t\t[provideKey(resource.id, 'trustedOrigins')]: [\n\t\t\t...origins,\n\t\t\t...new Set(schemes),\n\t\t].join(','),\n\t\t...(domain ? { [provideKey(resource.id, 'cookieDomain')]: domain } : {}),\n\t\t...(device ? { [provideKey(resource.id, 'deviceUrl')]: device } : {}),\n\t};\n}\n\n/** An address reduced to the origin a browser compares against. */\nfunction originOf(address: string): string | undefined {\n\ttry {\n\t\treturn new URL(address).origin;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * The runtime role of whatever a derived node hangs off.\n *\n * A reader's parent may itself be a schema tenant, so this follows `of` by one\n * hop rather than assuming the cluster — the reader of a tenant reads that\n * tenant's schema, not the database's.\n */\nfunction parentRole(resource: PlannedResource, plan: Plan): string {\n\tconst parent = plan.resources.find((r) => r.id === resource.of);\n\n\treturn parent ? localRole(parent) : resource.name;\n}\n\n/** Whether a kind connects to Postgres and therefore has an owner role. */\nfunction isDatabase(kind: PlannedResource['kind']): boolean {\n\treturn kind === 'database' || kind === 'database-schema';\n}\n\n/**\n * The URL a migrator connects with — the owner role, which can create, alter\n * and drop.\n *\n * Never in `provides`, which is the whole point. A handler that could reach this\n * could `DROP TABLE`, and the security property the role split exists for is\n * that it cannot: the value is injected by `gkm exec` for the migrate step and\n * by nothing else.\n */\nfunction ownerUrl(\n\tresource: PlannedResource,\n\tplan: Plan,\n\tports: PortAssignments,\n\tproject: string,\n\tcredential: StageCredential,\n): string | undefined {\n\tif (!resource.container) return undefined;\n\n\tconst port = ports[primaryPortKey(resource.container)];\n\tif (port === undefined) return undefined;\n\n\tconst owner = ownerRole(localRole(resource));\n\n\treturn postgres(port, rootDatabase(resource, plan), {\n\t\tuser: owner,\n\t\tpassword: localRolePassword(project, plan, owner, credential.seed),\n\t});\n}\n\n/**\n * The shared broker keys, when anything is published or consumed.\n *\n * A queue's own key is the producer's. These two are the *connection* itself,\n * which locally is one broker for every queue and topic in the project: the\n * generated pollers open a single connection and subscribe each worker by name\n * on it. Deployed there is no such thing — a Lambda is handed its own event\n * source — so this pair exists for the local target and says so.\n */\nfunction brokerEnv(\n\tplan: Plan,\n\tports: PortAssignments,\n\tcredential: StageCredential,\n): Record<string, string> {\n\tconst carrier = plan.resources.find(\n\t\t(r) => r.kind === 'queue' || r.kind === 'topic',\n\t);\n\n\tconst publisher = carrier\n\t\t? urlFor(carrier, plan, ports, '', {}, credential)\n\t\t: plan.workerBroker\n\t\t\t? workerBroker(plan, ports, credential)\n\t\t\t: undefined;\n\tif (!publisher) return {};\n\n\t// One broker address only where there is one broker: pg-boss and RabbitMQ.\n\t// On SNS every topic and queue is its own address, carried by its own\n\t// `<ID>_PUBLISHER_CONNECTION_STRING`, and no single string stands for them.\n\tif (plan.events === 'sns') return {};\n\n\treturn { EVENT_PUBLISHER_CONNECTION_STRING: publisher };\n}\n\n/**\n * The broker a worker schedules its crons through, when nothing else declared\n * one — pg-boss in the declared database, on that database's port.\n */\nfunction workerBroker(\n\tplan: Plan,\n\tports: PortAssignments,\n\tcredential: StageCredential,\n): string | undefined {\n\tconst database = plan.resources.find((r) => r.kind === 'database');\n\tif (!database?.container) return undefined;\n\n\tconst port = ports[primaryPortKey(database.container)];\n\treturn port === undefined ? undefined : broker(plan, port, credential);\n}\n\n/**\n * The URL for one planned resource.\n *\n * Every part of it comes from the resource and the port it was published on.\n */\nfunction urlFor(\n\tresource: PlannedResource,\n\tplan: Plan,\n\tports: PortAssignments,\n\tproject: string,\n\taddresses: Readonly<Record<string, string>> = {},\n\tcredential: StageCredential = { master: LOCAL_USER },\n): string | undefined {\n\t// A secret has no address, so there is no port to wait for.\n\tif (resource.kind === 'secret') return localSecret(project, plan, resource);\n\n\t// Nor has an encryption key: a keyring derived the way a secret is.\n\tif (resource.kind === 'encryption') {\n\t\treturn localKeyring(project, plan, resource);\n\t}\n\n\t// A mobile app resolves its scheme — the same on every stage.\n\tif (resource.kind === 'mobile-app') {\n\t\treturn schemeBase(project, resource.scheme);\n\t}\n\n\t// A credential resolves to nothing here, deliberately. A secret is derived\n\t// because the platform owns it; a credential was issued by a third party, so\n\t// inventing a value would produce a Stripe key that is not a Stripe key and\n\t// fail at the first call rather than at the first read. It comes from\n\t// `gkm secrets` or `.env` like any other supplied value, and the construct's\n\t// own schema is what reports it missing.\n\tif (resource.kind === 'credential') return undefined;\n\n\t// An external API is the provider, at its URL for this stage — unless the\n\t// plan was made with fakes (`gkm test`, `gkm dev --fake`), and then it is\n\t// its fake, on the port its key was assigned.\n\tif (resource.kind === 'external-api') {\n\t\tif (!resource.fake) {\n\t\t\treturn resource.url === undefined\n\t\t\t\t? undefined\n\t\t\t\t: externalApiUrl({ id: resource.id, url: resource.url }, plan.stage);\n\t\t}\n\t\tconst port = ports[resource.fake.key];\n\t\treturn port === undefined ? undefined : `http://${LOCAL_HOST}:${port}`;\n\t}\n\n\t// A surface answers on the app's own port, and a site on its dev server's —\n\t// both assigned by the workspace, neither published by a container.\n\t//\n\t// Behind the edge when there is one, which is what gives them a hostname and\n\t// a certificate rather than a port on localhost. The application cannot tell\n\t// the difference: it reads whichever address was injected and composes none,\n\t// so this is the target's decision alone.\n\tif (resource.kind === 'rest-api' || resource.kind === 'site') {\n\t\tconst address = addresses[resource.id];\n\t\tconst edge = plan.containers.includes('caddy')\n\t\t\t? ports[primaryPortKey('caddy')]\n\t\t\t: undefined;\n\n\t\treturn address && edge !== undefined\n\t\t\t? `https://${hostFor(resource, project)}:${edge}`\n\t\t\t: address;\n\t}\n\n\tif (!resource.container) return undefined;\n\n\tconst port = ports[primaryPortKey(resource.container)];\n\tif (port === undefined) return undefined;\n\n\tswitch (resource.kind) {\n\t\tcase 'database':\n\t\tcase 'database-schema':\n\t\tcase 'database-reader': {\n\t\t\t// A tenant and a reader both live in the *parent's* database; what\n\t\t\t// separates them is the role's grants and the `search_path` pinned on\n\t\t\t// that role, never a database of their own.\n\t\t\tconst database = rootDatabase(resource, plan);\n\n\t\t\t// The runtime role, which can read and write rows and create nothing.\n\t\t\t// `search_path` is pinned on the role by the DDL rather than carried\n\t\t\t// here, so a connection string cannot forget it — and forgetting it\n\t\t\t// looks like an empty database rather than an error.\n\t\t\t//\n\t\t\t// `roles: false` is the documented downgrade: both URLs fall back to\n\t\t\t// the cluster's master credential, and the schema goes back into the\n\t\t\t// URL because there is no role to pin it on.\n\t\t\tif (resource.roles === false) {\n\t\t\t\tconst schema = schemaOf(resource, plan);\n\n\t\t\t\tconst master = { user: LOCAL_USER, password: credential.master };\n\t\t\t\treturn schema\n\t\t\t\t\t? `${postgres(port, database, master)}?search_path=${schema}`\n\t\t\t\t\t: postgres(port, database, master);\n\t\t\t}\n\n\t\t\t// A reader connects as the read-only role on its parent, not as a role\n\t\t\t// of its own: read-only is enforced by the grants, which is what makes\n\t\t\t// falling back to the writer's endpoint safe where no replica exists.\n\t\t\tconst role =\n\t\t\t\tresource.kind === 'database-reader'\n\t\t\t\t\t? readerRole(parentRole(resource, plan))\n\t\t\t\t\t: localRole(resource);\n\n\t\t\treturn postgres(port, database, {\n\t\t\t\tuser: role,\n\t\t\t\tpassword: localRolePassword(project, plan, role, credential.seed),\n\t\t\t});\n\t\t}\n\n\t\tcase 'email':\n\t\t\t// The same scheme the deployed target writes; only host and credentials\n\t\t\t// differ, which is what lets the client never branch on provider.\n\t\t\treturn `smtp://${LOCAL_HOST}:${port}`;\n\n\t\tcase 'objects':\n\t\t\t// `?endpoint=` is what points the same S3 client at MinIO — the client\n\t\t\t// is identical, only the URL differs. Path-style addressing goes with\n\t\t\t// it: virtual-host style resolves `bucket.localhost`, which is not\n\t\t\t// MinIO and not anything.\n\t\t\treturn `s3://${resource.name}?region=${LOCAL_REGION}&endpoint=http://${LOCAL_HOST}:${port}&forcePathStyle=true`;\n\n\t\tcase 'file-server': {\n\t\t\t// A host of its own, over TLS — the shape it has deployed, and the one\n\t\t\t// MinIO alone cannot produce: its virtual-host mode reads the leading\n\t\t\t// label *as the bucket name*, so it matches only when the server's id\n\t\t\t// and the bucket's agree and never for a server fronting two buckets.\n\t\t\t// The local edge in front of it does the mapping instead, and issues\n\t\t\t// the certificate. An AWS emulator supplies neither: CloudFront\n\t\t\t// emulation is control plane only, and this is the data plane.\n\t\t\t//\n\t\t\t// The port is in the address because ports are assigned rather than\n\t\t\t// fixed, so two projects can run at once. Nothing that reads a\n\t\t\t// hostname — a cookie domain, a CORS origin — looks at it.\n\t\t\tconst origin = plan.resources.find((r) => r.id === resource.of);\n\t\t\tif (!origin) return undefined;\n\n\t\t\t// With the edge off, the honest local answer is the address that\n\t\t\t// works: a path under the object store. It is not the deployed shape,\n\t\t\t// and the application still cannot tell — it reads the key it was\n\t\t\t// given and never composes one.\n\t\t\treturn plan.containers.includes('caddy')\n\t\t\t\t? `https://${hostFor(resource, project)}:${port}`\n\t\t\t\t: `http://${LOCAL_HOST}:${port}/${origin.name}`;\n\t\t}\n\n\t\tcase 'cache':\n\t\t\t// The scheme is the backend, and the backend is the same one deployed —\n\t\t\t// which is what lets a driver registered at build time match the URL\n\t\t\t// resolved at run time.\n\t\t\t//\n\t\t\t// A cache in a database has no address of its own: no second host and\n\t\t\t// no second credential, just its parent's URL. Reached by following\n\t\t\t// `of` and nothing else — `planFor` has already resolved a\n\t\t\t// `cache: 'db'` backend into that edge, so there is one way a cache\n\t\t\t// lands in a database and one way to find out.\n\t\t\tif (resource.of) {\n\t\t\t\tconst parent = plan.resources.find((r) => r.id === resource.of);\n\t\t\t\tconst url = parent\n\t\t\t\t\t? urlFor(parent, plan, ports, project, addresses, credential)\n\t\t\t\t\t: undefined;\n\n\t\t\t\t// The parent's address plus the one thing that distinguishes this\n\t\t\t\t// cache from another in the same database: its table. Two caches on\n\t\t\t\t// one database resolve the same connection string, so without it a\n\t\t\t\t// client built from the URL cannot tell them apart.\n\t\t\t\treturn url\n\t\t\t\t\t? withTable(url, resource.table ?? cacheTable(resource.id))\n\t\t\t\t\t: undefined;\n\t\t\t}\n\n\t\t\tswitch (plan.cache) {\n\t\t\t\tcase 'elasticache':\n\t\t\t\t\t// The wire protocol, unauthenticated locally. Deployed it is\n\t\t\t\t\t// `rediss://` inside a VPC; the client is the same either way.\n\t\t\t\t\treturn `redis://${LOCAL_HOST}:${port}`;\n\n\t\t\t\tdefault:\n\t\t\t\t\t// The token in the userinfo, because an address and the credential\n\t\t\t\t\t// that opens it are one fact. Deployed the scheme is https and the\n\t\t\t\t\t// host is the provider's; nothing else differs.\n\t\t\t\t\treturn `http://:${LOCAL_TOKEN}@${LOCAL_HOST}:${port}`;\n\t\t\t}\n\n\t\tcase 'queue':\n\t\tcase 'topic':\n\t\t\treturn broker(plan, port, credential, resource);\n\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * The producer's connection string, composed from the backend the plan chose.\n *\n * The protocol is what picks the transport, so a producer never branches: the\n * same `.publish()` reaches pg-boss here and SQS deployed because the string it\n * was handed said so.\n */\nfunction broker(\n\tplan: Plan,\n\tport: number,\n\tcredential: StageCredential,\n\t/** The topic or queue — needed where each is its own address (SNS/SQS). */\n\tresource?: PlannedResource,\n): string {\n\tswitch (plan.events) {\n\t\tcase 'pgboss': {\n\t\t\t// A schema tenant of the database the app already declared, which is\n\t\t\t// why nothing here invents a Postgres of its own.\n\t\t\tconst database = plan.resources.find((r) => r.kind === 'database');\n\t\t\tif (!database) throw new PgBossNeedsDatabase([]);\n\n\t\t\treturn `pgboss://${LOCAL_USER}:${encodeURIComponent(credential.master)}@${LOCAL_HOST}:${port}/${database.name}?schema=${PGBOSS_SCHEMA}`;\n\t\t}\n\n\t\tcase 'rabbitmq':\n\t\t\t// The exchange is declared by whichever client connects first, so\n\t\t\t// naming it here is the whole of the setup.\n\t\t\treturn `rabbitmq://${LOCAL_USER}:${LOCAL_USER}@${LOCAL_HOST}:${port}?exchange=${RABBITMQ_EXCHANGE}`;\n\n\t\tcase 'sns': {\n\t\t\t// The emulator's ARNs and queue URLs are deterministic, so the address\n\t\t\t// is composed here and the topic or queue is created beside it by the\n\t\t\t// applier (`applyCarriers`). Credentials ride in the string: the\n\t\t\t// emulator's are not the developer's AWS profile, and must not be\n\t\t\t// picked up from it.\n\t\t\tif (!resource) throw new UnprovisionedEventsBackend('sns');\n\t\t\tconst endpoint = emulatorEndpoint(port);\n\t\t\tconst address =\n\t\t\t\tresource.kind === 'topic'\n\t\t\t\t\t? snsUrl.build({\n\t\t\t\t\t\t\ttopicArn: emulatorTopicArn(resource.name),\n\t\t\t\t\t\t\tregion: EMULATOR_REGION,\n\t\t\t\t\t\t\tendpoint,\n\t\t\t\t\t\t})\n\t\t\t\t\t: sqsUrl.build({\n\t\t\t\t\t\t\tqueueUrl: emulatorQueueUrl(resource.name, port),\n\t\t\t\t\t\t\tregion: EMULATOR_REGION,\n\t\t\t\t\t\t\tendpoint,\n\t\t\t\t\t\t});\n\t\t\tconst url = new URL(address);\n\t\t\turl.searchParams.set('accessKeyId', EMULATOR_CREDENTIALS.accessKeyId);\n\t\t\turl.searchParams.set(\n\t\t\t\t'secretAccessKey',\n\t\t\t\tEMULATOR_CREDENTIALS.secretAccessKey,\n\t\t\t);\n\t\t\treturn url.toString();\n\t\t}\n\t}\n}\n\n/** A local Postgres URL, master credential and all. */\nfunction postgres(\n\tport: number,\n\tdatabase: string,\n\tcredential: { user: string; password: string } = {\n\t\tuser: LOCAL_USER,\n\t\tpassword: LOCAL_USER,\n\t},\n): string {\n\treturn `postgres://${credential.user}:${encodeURIComponent(credential.password)}@${LOCAL_HOST}:${port}/${database}`;\n}\n\n/**\n * The role a handler connects as, for one database or tenant.\n *\n * The construct's id, lowercased and stage-scoped exactly as its database or\n * schema is — so `Orders` in the `test` stage connects as `orders_test`, and two\n * stages sharing one cluster cannot share a credential. Roles are cluster-scoped\n * in Postgres, which is why the suffix is not optional here the way it is for a\n * schema.\n */\nexport function localRole(resource: PlannedResource): string {\n\treturn resource.name;\n}\n\n/**\n * The password for a derived local role.\n *\n * Derived rather than random, for the same reason the local secret is: a\n * password that changed on every `gkm dev` would lock a developer out of the\n * data they had a moment ago. Seeded by the project so two checkouts do not\n * share one, and by the stage so `test` and `development` do not.\n *\n * Without a seed it is a *local* credential by construction — the cluster is\n * on loopback and everything it is derived from is in the repo. `gkm compose`\n * serving a deployed stage passes that stage's random seed, which is what\n * makes the same derivation a secret.\n */\nexport function localRolePassword(\n\tproject: string,\n\tplan: Plan,\n\trole: string,\n\t/**\n\t * A deployed stage's random seed. Given, the derivation is the one a\n\t * deploy makes — the seed first — so a stack serving that stage connects\n\t * with the passwords its roles were created with.\n\t */\n\tseed?: string,\n): string {\n\treturn createHash('sha256')\n\t\t.update(`${seed ? `${seed}:` : ''}${project}:${plan.stage}:role:${role}`)\n\t\t.digest('base64url')\n\t\t.slice(0, 32);\n}\n\n/**\n * A cache's address: its database's URL, carrying the table it reads.\n *\n * Appended rather than kept beside the URL as a second key, for the reason the\n * design gives everywhere else — an address and what it opens are one fact, and\n * two keys are one more thing to keep in step.\n */\nfunction withTable(url: string, table: string): string {\n\tconst parsed = new URL(url);\n\tparsed.searchParams.set('table', table);\n\n\treturn parsed.toString();\n}\n\n/**\n * The physical database a derived resource lives in.\n *\n * Walks `of` to the top rather than reading the immediate parent, because a\n * reader's parent may itself be a schema tenant — two hops from the database\n * that actually exists.\n */\nexport function rootDatabase(resource: PlannedResource, plan: Plan): string {\n\tconst byId = new Map(plan.resources.map((r) => [r.id, r]));\n\tlet current = resource;\n\n\twhile (current.of) {\n\t\tconst parent = byId.get(current.of);\n\t\tif (!parent) break;\n\t\tcurrent = parent;\n\t}\n\n\treturn current.name;\n}\n\n/** The schema a derived resource puts on its search path, if it has one. */\nfunction schemaOf(resource: PlannedResource, plan: Plan): string | undefined {\n\tconst byId = new Map(plan.resources.map((r) => [r.id, r]));\n\tlet current: PlannedResource | undefined = resource;\n\n\t// A reader has no schema of its own; it reads whatever its parent addresses.\n\twhile (current) {\n\t\tif (current.kind === 'database-schema') return current.schema;\n\t\tcurrent = current.of ? byId.get(current.of) : undefined;\n\t}\n\n\treturn undefined;\n}\n\n/**\n * A broker address asked for without the topic or queue it would name.\n *\n * On SNS each topic and queue is its own address, so there is no project-wide\n * one to hand out — a cron's schedule store, say, cannot live there.\n */\nexport class UnprovisionedEventsBackend extends Error {\n\tconstructor(readonly backend: string) {\n\t\tsuper(\n\t\t\t`'${backend}' has no single broker address: each topic and queue is ` +\n\t\t\t\t`addressed by its own ARN. Something asked for one without naming ` +\n\t\t\t\t`the topic or queue.`,\n\t\t);\n\t\tthis.name = 'UnprovisionedEventsBackend';\n\t}\n}\n\n/**\n * The keyring an `Encryption` construct resolves to locally.\n *\n * Derived like a secret — stable across restarts, distinct per project, stage\n * and construct, never written to disk — so what `gkm dev` encrypted yesterday\n * still opens today. One key, because nothing local is worth rotating: a\n * server stage's keyring is generated and rotated in its secrets instead.\n */\nfunction localKeyring(\n\tproject: string,\n\tplan: Plan,\n\tresource: PlannedResource,\n): string {\n\tconst derive = (purpose: string) =>\n\t\tcreateHash('sha256')\n\t\t\t.update(`${project}:${plan.stage}:${resource.id}:${purpose}`)\n\t\t\t.digest();\n\n\treturn formatKeyring({\n\t\tkeys: [{ id: 'k1', key: derive('k1') }],\n\t\tindex: derive('index'),\n\t});\n}\n\n/**\n * The value a secret resolves to locally.\n *\n * A hash of what identifies it, so it is stable, distinct per project, stage\n * and construct, and never written to disk as a literal. Deployed this is the\n * one kind whose value does *not* come from here — a secret manager generates\n * it once and the target reads it back.\n */\nfunction localSecret(\n\tproject: string,\n\tplan: Plan,\n\tresource: PlannedResource,\n): string {\n\treturn createHash('sha256')\n\t\t.update(`${project}:${plan.stage}:${resource.id}`)\n\t\t.digest('base64url')\n\t\t.slice(0, 43);\n}\n","/**\n * The apps, as services beside the containers their constructs derive.\n *\n * `gkm docker` used to write these by hand: every backend got\n * `DATABASE_URL=postgresql://…@postgres:5432/app` and `REDIS_URL=redis://redis`,\n * which was right only for a project whose single database happened to be\n * called `Database` — an `Orders` database publishes `ORDERS_URL`, a schema\n * tenant has a URL of its own, and buckets and mail were not wired at all.\n *\n * Now an app's environment is exactly what reconcile hands `gkm dev`: every key\n * the declared constructs provide, from the same derivation. The only\n * difference is where the addresses point — at the containers by service name\n * on the compose network, rather than at `localhost` on the ports published for\n * the host.\n *\n * Always from the *local* stage's plan, whichever stage is being reconciled:\n * `gkm test` rewrites the same file, and running the tests must not quietly\n * point these apps at the test databases.\n */\n\nimport {\n\ttype ConstructManifest,\n\tdependenciesOf,\n\tprovisionOrder,\n\tpublicEnvFor,\n} from '@geekmidas/manifest';\nimport { appKey } from '../workspace/derive.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { ComposeService } from './compose.js';\nimport { portKeys, portsOf } from './containers.js';\nimport { type EnvOptions, envFor } from './env.js';\nimport { type Plan, type PlanOptions, planFor } from './plan.js';\nimport { backendsOf, surfaceAddresses } from './workspace.js';\n\n/** The profile app services carry, so `gkm dev` starts only the containers. */\nexport const APPS_PROFILE = 'apps';\n\n/**\n * Where an app's image is built from.\n *\n * An app at the project root is a single-app project, whose `gkm docker`\n * writes `.gkm/docker/Dockerfile`; a workspace writes one per app.\n */\nexport function dockerfileOf(appName: string, path: string): string {\n\treturn path === '.'\n\t\t? '.gkm/docker/Dockerfile'\n\t\t: `.gkm/docker/Dockerfile.${appName}`;\n}\n\n/**\n * Every construct key, addressed across the compose network.\n *\n * `envFor` is asked with a placeholder port for every port key, so each\n * `localhost:<placeholder>` in its answer names exactly one container port —\n * two containers listening on the same port inside cannot be confused — and is\n * rewritten to `<container>:<inside>`.\n */\nexport function inNetworkEnv(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\t/**\n\t * External APIs' fakes. An image fake is a container on the network like\n\t * any other; a module fake is served by `gkm dev` on the host, which an app\n\t * container does not reach, so its URL is left as the host sees it.\n\t */\n\tfakes: PlanOptions['fakes'] = {},\n): Record<string, string> {\n\treturn networkEnv(localPlan(workspace, manifest, fakes), {\n\t\tproject: workspace.name,\n\t\taddresses: surfaceAddresses(\n\t\t\tworkspace,\n\t\t\tmanifest,\n\t\t\t(app, port) => `http://${app}:${port}`,\n\t\t),\n\t});\n}\n\n/**\n * The local stage's plan, which the app services are always written from.\n *\n * Without the edge: these apps reach each other by service name and a browser\n * reaches them on the ports this file publishes. Behind the edge every\n * surface resolved to its `*.localhost` host on the edge's port, which the\n * rewrite to the compose network then turned into an address nothing answers.\n */\nfunction localPlan(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\tfakes: PlanOptions['fakes'] = {},\n): Plan {\n\treturn planFor(manifest, workspace.stages.local, provisionOrder(manifest), {\n\t\tlocalStage: workspace.stages.local,\n\t\t...backendsOf(workspace),\n\t\tfakes,\n\t\tedge: false,\n\t});\n}\n\n/**\n * The values a site's bundler inlines, by the name it inlines them under —\n * `{ VITE_API_URL: 'http://localhost:3000' }`.\n *\n * Build arguments, not environment: a static bundle is finished when it is\n * built, so a value handed to its container at runtime reaches nothing. And\n * addresses a *browser* can open, since that is where the bundle runs — the\n * host's port for each app, never a name on the compose network.\n */\nexport function sitePublicArgs(\n\tmanifest: ConstructManifest,\n\tappName: string,\n\t/** Every key the stage resolves, with surfaces at their public addresses. */\n\tenv: Readonly<Record<string, string>>,\n): Record<string, string> {\n\tconst declaration = Object.entries(manifest).find(\n\t\t([id, d]) => d.kind === 'site' && appKey(id) === appName,\n\t)?.[1];\n\tif (declaration?.kind !== 'site') return {};\n\n\tconst args: Record<string, string> = {};\n\tfor (const key of Object.keys(publicEnvFor(declaration, manifest))) {\n\t\tconst value = env[key];\n\t\tif (value !== undefined) args[key] = value;\n\t}\n\treturn args;\n}\n\n/**\n * Every key a plan resolves, with each container addressed by its service\n * name on the compose network — `postgres:5432` rather than the port\n * published for the host.\n *\n * The addresses of surfaces and sites are the caller's: on the compose\n * network for a service another service calls, public for what a browser is\n * handed. `gkm compose` asks twice, once for each.\n */\nexport function networkEnv(\n\tplan: Plan,\n\toptions: Omit<EnvOptions, 'ports'>,\n): Record<string, string> {\n\tconst placeholders: Record<string, number> = {};\n\tconst targets = new Map<number, string>();\n\tlet next = 49_152;\n\tfor (const container of [...plan.containers].sort()) {\n\t\tfor (const port of portsOf(container, plan.fakes)) {\n\t\t\tplaceholders[port.key] = next;\n\t\t\ttargets.set(next, `${container}:${port.inside}`);\n\t\t\tnext += 1;\n\t\t}\n\t}\n\t// Every key allocation would have assigned, so envFor finds each one.\n\tfor (const key of portKeys(plan.containers, plan.fakes)) {\n\t\tplaceholders[key] ??= next++;\n\t}\n\n\tconst env = envFor(plan, { ...options, ports: placeholders });\n\n\tconst rewrite = (value: string) =>\n\t\tvalue.replace(/(?:localhost|127\\.0\\.0\\.1):(\\d+)/g, (match, port) => {\n\t\t\treturn targets.get(Number(port)) ?? match;\n\t\t});\n\n\treturn Object.fromEntries(\n\t\tObject.entries(env).map(([key, value]) => [key, rewrite(value)]),\n\t);\n}\n\n/**\n * The keys one app's environment may hold: what its own declaration provides\n * and requires, and what each construct it has an edge to provides.\n *\n * The edges, not the manifest. Every app used to be handed every key the\n * workspace resolved — a web app got the database's owner URL and the auth\n * server's signing secret, and the API got the auth server's database — which\n * is exactly what a declared edge exists to rule out. A deploy composes an\n * app's environment from its edges; so does this.\n *\n * One hop: a surface an app calls is reached over HTTP, so its URL is the\n * app's business and its database is not. A site also gets the public\n * variant of each key its bundle inlines (`NEXT_PUBLIC_API_URL`), and a surface\n * the edges of the workers whose crons and subscribers its server runs.\n *\n * `undefined` for an app no declaration describes, which gets nothing but its\n * port.\n */\nexport function appEnvKeys(\n\tmanifest: ConstructManifest,\n\tappName: string,\n\t/** Each owner's runnables' edges, from discovery — see `DiscoverOptions`. */\n\trunnables: Readonly<Record<string, readonly string[]>> = {},\n): Set<string> | undefined {\n\tconst entry = Object.entries(manifest).find(\n\t\t([id, d]) =>\n\t\t\t(d.kind === 'rest-api' || d.kind === 'site' || d.kind === 'mobile-app') &&\n\t\t\tappKey(id) === appName,\n\t);\n\tif (!entry) return undefined;\n\tconst [id, declaration] = entry;\n\n\tconst keys = new Set<string>([\n\t\t...(declaration.provides ?? []),\n\t\t...(declaration.requires ?? []),\n\t\t// A handler the surface declares itself — an auth server's wildcard —\n\t\t// states what it needs on the handler.\n\t\t...(declaration.kind === 'rest-api'\n\t\t\t? declaration.endpoints.flatMap((endpoint) => endpoint.requires ?? [])\n\t\t\t: []),\n\t]);\n\n\tconst edges = [\n\t\t...dependenciesOf(declaration).map((edge) => edge.target),\n\t\t// The endpoints the glob found, which the surface's node does not list.\n\t\t...(runnables[id] ?? []),\n\t];\n\t// The server the build generates — a surface whose endpoints the glob finds,\n\t// so it declares none — also runs the workers' crons and subscribers. One\n\t// that serves itself (an auth server's wildcard) runs only its own handler.\n\tif (declaration.kind === 'rest-api' && declaration.endpoints.length === 0) {\n\t\tconst runsWorkers = Object.values(manifest).some(\n\t\t\t(other) => other.kind === 'worker',\n\t\t);\n\t\tfor (const [otherId, other] of Object.entries(manifest)) {\n\t\t\tif (other.kind !== 'worker') continue;\n\t\t\tedges.push(\n\t\t\t\t...dependenciesOf(other).map((edge) => edge.target),\n\t\t\t\t...(runnables[otherId] ?? []),\n\t\t\t);\n\t\t\t// A server schedules its crons through the broker.\n\t\t\tkeys.add('EVENT_PUBLISHER_CONNECTION_STRING');\n\t\t}\n\t\t// Each consumer reaches the thing it consumes: a queue's consumer its\n\t\t// queue, a subscriber its topic. Which subscriber binds which topic is\n\t\t// only known once the build has found them, so a server that runs the\n\t\t// workers is handed every carrier's address.\n\t\tif (runsWorkers) {\n\t\t\tfor (const other of Object.values(manifest)) {\n\t\t\t\tif (other.kind !== 'queue' && other.kind !== 'topic') continue;\n\t\t\t\tfor (const key of other.provides ?? []) keys.add(key);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const id of edges) {\n\t\tconst target = manifest[id];\n\t\tif (!target) continue;\n\t\tfor (const key of target.provides ?? []) keys.add(key);\n\n\t\tif (target.kind === 'queue' || target.kind === 'topic') {\n\t\t\tkeys.add('EVENT_PUBLISHER_CONNECTION_STRING');\n\t\t}\n\t\t// The S3 client reads its credentials beside the URL, not in it. A file\n\t\t// server is reached by its URL alone.\n\t\tif (target.kind === 'objects') {\n\t\t\tkeys.add('AWS_ACCESS_KEY_ID');\n\t\t\tkeys.add('AWS_SECRET_ACCESS_KEY');\n\t\t\tkeys.add('AWS_REGION');\n\t\t}\n\t}\n\n\tif (declaration.kind === 'site' || declaration.kind === 'mobile-app') {\n\t\tfor (const key of Object.keys(publicEnvFor(declaration, manifest))) {\n\t\t\tkeys.add(key);\n\t\t}\n\t}\n\n\treturn keys;\n}\n\n/** One compose service per app the workspace runs, behind the apps profile. */\nexport function appServices(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\tcontainers: readonly string[],\n\trunnables: Readonly<Record<string, readonly string[]>> = {},\n\tfakes: PlanOptions['fakes'] = {},\n): Record<string, ComposeService> {\n\tconst plan = localPlan(workspace, manifest, fakes);\n\tconst env = networkEnv(plan, {\n\t\tproject: workspace.name,\n\t\taddresses: surfaceAddresses(\n\t\t\tworkspace,\n\t\t\tmanifest,\n\t\t\t(app, port) => `http://${app}:${port}`,\n\t\t),\n\t});\n\t// What a browser is handed: each app on the port this file publishes it on.\n\tconst browser = networkEnv(plan, {\n\t\tproject: workspace.name,\n\t\taddresses: surfaceAddresses(workspace, manifest),\n\t});\n\tconst services: Record<string, ComposeService> = {};\n\n\tfor (const [name, app] of Object.entries(workspace.apps)) {\n\t\t// A mobile app ships through its own toolchain; nothing here runs it.\n\t\tif (app.type === 'mobile' || !app.port) continue;\n\n\t\tconst key = appKey(name);\n\t\tconst health = app.type === 'web' ? '/' : '/health';\n\t\tconst dockerfile = dockerfileOf(name, app.path);\n\t\tconst ports = [`${app.port}:${app.port}`];\n\n\t\t// A site is a bundle: its public URLs are inlined when it is built, so\n\t\t// they are build arguments, and it reads nothing at runtime — no server\n\t\t// env, and nothing to wait for but its own server.\n\t\tif (app.type === 'web') {\n\t\t\tconst args = sitePublicArgs(manifest, name, browser);\n\t\t\tservices[key] = {\n\t\t\t\timage: `${key}:\\${TAG:-latest}`,\n\t\t\t\tbuild: {\n\t\t\t\t\tcontext: '.',\n\t\t\t\t\tdockerfile,\n\t\t\t\t\t...(Object.keys(args).length ? { args } : {}),\n\t\t\t\t},\n\t\t\t\tprofiles: [APPS_PROFILE],\n\t\t\t\tports,\n\t\t\t\thealthcheck: healthcheck(app.port, health),\n\t\t\t};\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst allowed = appEnvKeys(manifest, name, runnables);\n\t\tconst own = Object.fromEntries(\n\t\t\tObject.entries(env).filter(([envKey]) => allowed?.has(envKey)),\n\t\t);\n\n\t\tservices[key] = {\n\t\t\timage: `${key}:\\${TAG:-latest}`,\n\t\t\tbuild: { context: '.', dockerfile },\n\t\t\tprofiles: [APPS_PROFILE],\n\t\t\tports,\n\t\t\tenvironment: {\n\t\t\t\tNODE_ENV: 'production',\n\t\t\t\tPORT: String(app.port),\n\t\t\t\t...own,\n\t\t\t},\n\t\t\t// The edge fronts the host's browser; apps talk to each other directly.\n\t\t\t...(containers.some((c) => c !== 'caddy')\n\t\t\t\t? { depends_on: containers.filter((c) => c !== 'caddy') }\n\t\t\t\t: {}),\n\t\t\thealthcheck: healthcheck(app.port, health),\n\t\t};\n\t}\n\n\treturn services;\n}\n\n/** An app's health check: its own server answering on `path`. */\nexport function healthcheck(\n\tport: number,\n\tpath: string,\n): NonNullable<ComposeService['healthcheck']> {\n\treturn {\n\t\ttest: ['CMD', 'wget', '-q', '--spider', `http://localhost:${port}${path}`],\n\t\tinterval: '10s',\n\t\ttimeout: '5s',\n\t\tretries: 5,\n\t};\n}\n","/**\n * The real Postgres and MinIO clients the applier runs against.\n *\n * Kept apart from `provision.ts` so the rules there — what gets created, in what\n * order, and never dropping anything — are asserted against fakes, while the\n * drivers live here where there is nothing to assert but wiring.\n *\n * Both are constructed from the same local addresses the env writer composes,\n * so the applier cannot end up talking to a different container than the app.\n */\n\nimport {\n\tCreateBucketCommand,\n\tGetBucketPolicyCommand,\n\tHeadBucketCommand,\n\tPutBucketPolicyCommand,\n\tS3Client,\n} from '@aws-sdk/client-s3';\nimport {\n\tCreateTopicCommand,\n\tGetTopicAttributesCommand,\n\tSNSClient,\n} from '@aws-sdk/client-sns';\nimport {\n\tCreateQueueCommand,\n\tGetQueueUrlCommand,\n\tSQSClient,\n} from '@aws-sdk/client-sqs';\nimport { Client } from 'pg';\nimport {\n\tEMULATOR_CREDENTIALS,\n\tEMULATOR_REGION,\n\temulatorEndpoint,\n\temulatorTopicArn,\n} from './emulator';\nimport type { BucketClient, CarrierClient, SqlClient } from './provision';\n\n/** The credential local containers are brought up with. */\nconst LOCAL_USER = 'geekmidas';\n\n/**\n * A Postgres client that opens one connection per database it is asked about.\n *\n * `CREATE DATABASE` cannot run from a connection to the database being created,\n * and a schema must be created from inside its own database — so the database is\n * part of each query rather than fixed when the client is built.\n */\nexport function pgClient(\n\tport: number,\n\t/**\n\t * The master's password, where the cluster is not a local one — a\n\t * `gkm compose` stack serving a deployed stage derives its own.\n\t */\n\tpassword: string = LOCAL_USER,\n): SqlClient {\n\treturn {\n\t\tasync query(database, sql, values) {\n\t\t\tconst client = new Client({\n\t\t\t\thost: 'localhost',\n\t\t\t\tport,\n\t\t\t\tuser: LOCAL_USER,\n\t\t\t\tpassword,\n\t\t\t\t// The cluster's own database, which the image creates. Connecting to\n\t\t\t\t// it is what makes `CREATE DATABASE` possible at all.\n\t\t\t\tdatabase: database ?? LOCAL_USER,\n\t\t\t});\n\n\t\t\tawait client.connect();\n\t\t\ttry {\n\t\t\t\tconst result = await client.query(sql, values as never[]);\n\t\t\t\treturn result.rows;\n\t\t\t} finally {\n\t\t\t\tawait client.end();\n\t\t\t}\n\t\t},\n\t};\n}\n\n/** A bucket client pointed at the local MinIO. */\nexport function bucketClient(port: number): BucketClient {\n\tconst s3 = new S3Client({\n\t\tregion: 'us-east-1',\n\t\tendpoint: `http://localhost:${port}`,\n\t\t// MinIO serves buckets as paths, not as subdomains of the endpoint.\n\t\tforcePathStyle: true,\n\t\tcredentials: { accessKeyId: LOCAL_USER, secretAccessKey: LOCAL_USER },\n\t});\n\n\treturn {\n\t\tasync exists(bucket) {\n\t\t\ttry {\n\t\t\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucket }));\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\t// Missing, or not reachable. Creating is idempotent enough that\n\t\t\t\t// treating both as \"missing\" is safe.\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync create(bucket) {\n\t\t\tawait s3.send(new CreateBucketCommand({ Bucket: bucket }));\n\t\t},\n\n\t\tasync policy(bucket) {\n\t\t\ttry {\n\t\t\t\tconst result = await s3.send(\n\t\t\t\t\tnew GetBucketPolicyCommand({ Bucket: bucket }),\n\t\t\t\t);\n\t\t\t\treturn result.Policy;\n\t\t\t} catch {\n\t\t\t\t// No policy, or the bucket is not reachable. Both mean there is\n\t\t\t\t// nothing to compare against, and writing one is idempotent.\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\n\t\tasync setPolicy(bucket, policy) {\n\t\t\tawait s3.send(\n\t\t\t\tnew PutBucketPolicyCommand({ Bucket: bucket, Policy: policy }),\n\t\t\t);\n\t\t},\n\t};\n}\n\n/** A topic and queue client pointed at the local AWS emulator. */\nexport function carrierClient(port: number): CarrierClient {\n\tconst config = {\n\t\tregion: EMULATOR_REGION,\n\t\tendpoint: emulatorEndpoint(port),\n\t\tcredentials: EMULATOR_CREDENTIALS,\n\t};\n\tconst sns = new SNSClient(config);\n\tconst sqs = new SQSClient(config);\n\n\treturn {\n\t\tasync topicExists(name) {\n\t\t\ttry {\n\t\t\t\tawait sns.send(\n\t\t\t\t\tnew GetTopicAttributesCommand({ TopicArn: emulatorTopicArn(name) }),\n\t\t\t\t);\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\t// Missing, or not reachable; creating is idempotent either way.\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync createTopic(name) {\n\t\t\tawait sns.send(new CreateTopicCommand({ Name: name }));\n\t\t},\n\n\t\tasync queueExists(name) {\n\t\t\ttry {\n\t\t\t\tawait sqs.send(new GetQueueUrlCommand({ QueueName: name }));\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync createQueue(name) {\n\t\t\tawait sqs.send(new CreateQueueCommand({ QueueName: name }));\n\t\t},\n\t};\n}\n","/**\n * The compose document a plan implies.\n *\n * The third declaration of the same containers disappears here: today a\n * `services: ['postgres','minio']` block is kept in step with the code by hand,\n * which is the triplication this design exists to remove, in the one place you\n * touch every day. A `database` implies Postgres; the list is derived.\n *\n * Returns an object rather than a string for the same reason `plan` is pure —\n * what should exist is asserted as data, and turning it into YAML is one line at\n * the edge.\n *\n * What stays config is unchanged: images and versions are pinned explicitly, and\n * a container no construct implies is still declared by hand. Neutral facts come\n * from the declaration, provider detail from config.\n */\n\nimport { stringify } from 'yaml';\nimport { DEFAULT_IMAGES, portsOf, postgresImage, volumeOf } from './containers';\nimport { EMULATOR_CREDENTIALS, EMULATOR_REGION } from './emulator';\nimport type { Plan } from './plan';\nimport type { PortAssignments } from './ports';\n\n/** A compose service definition — as much of the schema as is emitted here. */\nexport interface ComposeService {\n\timage: string;\n\t/** An app is built from its generated Dockerfile; a container is pulled. */\n\tbuild?: {\n\t\tcontext: string;\n\t\tdockerfile: string;\n\t\t/** A site's public URLs, which its bundler inlines at build time. */\n\t\targs?: Record<string, string>;\n\t};\n\t/** Behind a profile, so starting the containers does not start the apps. */\n\tprofiles?: string[];\n\trestart?: string;\n\tentrypoint?: string;\n\tcommand?: string;\n\tports?: string[];\n\tenvironment?: Record<string, string>;\n\tvolumes?: string[];\n\tdepends_on?: string[];\n\thealthcheck?: {\n\t\ttest: string[];\n\t\tinterval: string;\n\t\ttimeout: string;\n\t\tretries: number;\n\t\tstart_period?: string;\n\t};\n\textra_hosts?: string[];\n}\n\nexport interface ComposeFile {\n\tname: string;\n\tservices: Record<string, ComposeService>;\n\tvolumes?: Record<string, Record<string, never>>;\n}\n\n/** The credential every derived container is brought up with locally. */\nconst LOCAL_USER = 'geekmidas';\n\n/**\n * The token the local cache accepts.\n *\n * Fixed rather than generated: it is only ever reachable on a port this project\n * published, and the URL that carries it is derived, so nothing is made safer\n * by making it unguessable and a stable one keeps `redis-cli` sessions working.\n */\nconst LOCAL_TOKEN = 'geekmidas';\n\nexport interface ComposeOptions {\n\t/** Names the compose project, which is what isolates it from other stacks. */\n\tproject: string;\n\t/** Ports assigned by `allocate`, keyed by port key — see `portKeys`. */\n\tports: PortAssignments;\n\t/** App services to write beside the containers — see `reconcile/apps.ts`. */\n\tapps?: Readonly<Record<string, ComposeService>>;\n}\n\n/**\n * Build the compose document for a plan.\n *\n * Every container is published on its assigned port, never on the image's\n * default: the point of allocation is that two projects can run at once, and\n * one container publishing 5432 puts that back.\n */\nexport function composeFor(plan: Plan, options: ComposeOptions): ComposeFile {\n\tconst services: Record<string, ComposeService> = {};\n\tconst volumes: Record<string, Record<string, never>> = {};\n\n\tfor (const container of [...plan.containers].sort()) {\n\t\t// A declared version beats the default image, so the container a\n\t\t// developer runs is the major the declaration names — and the same one a\n\t\t// deploy provisions. An image pin is the project's own\n\t\t// `docker-compose.yml`, merged over this file, and wins over both.\n\t\tconst declared =\n\t\t\tcontainer === 'postgres'\n\t\t\t\t? plan.resources.find((r) => r.version)?.version\n\t\t\t\t: undefined;\n\n\t\tconst fake = plan.fakes?.[container];\n\t\tconst image = declared\n\t\t\t? postgresImage(declared)\n\t\t\t: (fake?.image ?? DEFAULT_IMAGES[container]);\n\t\tif (!image) throw new UnknownContainer(container);\n\n\t\tconst published = publish(container, options.ports, plan.fakes);\n\t\tservices[container] = fake\n\t\t\t? // An external API's fake: the provider's image, as it ships. It has\n\t\t\t\t// no volume — a fake keeps nothing worth keeping — and no\n\t\t\t\t// healthcheck, so running is what ready means.\n\t\t\t\t{ image, restart: 'unless-stopped', ports: published }\n\t\t\t: define(container, image, published);\n\n\t\tconst volume = volumeOf(container);\n\t\tif (volume) volumes[volume] = {};\n\t}\n\n\treturn {\n\t\t// Compose derives a project name from the directory otherwise, so two\n\t\t// checkouts of the same repo would fight over one set of containers.\n\t\tname: options.project,\n\t\tservices: { ...services, ...options.apps },\n\t\t...(Object.keys(volumes).length ? { volumes } : {}),\n\t};\n}\n\n/** Render a compose document as the file it becomes. */\nexport function toYaml(file: ComposeFile): string {\n\treturn `# Generated by gkm from the construct manifest — do not edit.\n# Containers are derived from what the app declares. Image pins and anything\n# no construct implies go in your own docker-compose.yml, which is merged over\n# this file and wins:\n#\n# docker compose -f docker-compose.constructs.yml -f docker-compose.yml …\n#\n# \"gkm dev\" and \"gkm test\" start the containers. The apps sit behind the\n# \"apps\" profile: run \"gkm setup\", then\n# docker compose -f docker-compose.constructs.yml --profile apps up --build\n${stringify(file, { lineWidth: 0 })}`;\n}\n\n/** A container with no image, from config or from the defaults. */\nexport class UnknownContainer extends Error {\n\t/** The container that could not be resolved. */\n\treadonly container: string;\n\t/** The containers that have a default, so the caller can see the set. */\n\treadonly known: readonly string[] = Object.keys(DEFAULT_IMAGES).sort();\n\n\tconstructor(container: string) {\n\t\tsuper('No image is known for this container; pin one in config');\n\t\tthis.name = 'UnknownContainer';\n\t\tthis.container = container;\n\t}\n}\n\n/** One container's definition, published on the port it was assigned. */\nfunction define(\n\tcontainer: string,\n\timage: string,\n\tpublished: string[],\n): ComposeService {\n\tswitch (container) {\n\t\tcase 'postgres':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tenvironment: {\n\t\t\t\t\tPOSTGRES_USER: LOCAL_USER,\n\t\t\t\t\tPOSTGRES_PASSWORD: LOCAL_USER,\n\t\t\t\t\t// The cluster's own database. Every declared database is created\n\t\t\t\t\t// inside this cluster by the applier, not by the image.\n\t\t\t\t\tPOSTGRES_DB: LOCAL_USER,\n\t\t\t\t},\n\t\t\t\t// One mount at the parent, not at `data`: the 18 image keeps its\n\t\t\t\t// cluster in a version-named subdirectory and refuses to start when\n\t\t\t\t// something is mounted over the legacy path. Mounting the parent is\n\t\t\t\t// also what makes the next major bump a `pg_upgrade --link` rather\n\t\t\t\t// than a new volume.\n\t\t\t\tvolumes: ['postgres-data:/var/lib/postgresql'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD-SHELL', `pg_isready -U ${LOCAL_USER}`],\n\t\t\t\t\tinterval: '5s',\n\t\t\t\t\ttimeout: '3s',\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tstart_period: '10s',\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'minio':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tenvironment: {\n\t\t\t\t\tMINIO_ROOT_USER: LOCAL_USER,\n\t\t\t\t\tMINIO_ROOT_PASSWORD: LOCAL_USER,\n\t\t\t\t},\n\t\t\t\t// No bucket is created here: buckets are planned resources, and\n\t\t\t\t// creating them in the entrypoint would put their names back in the\n\t\t\t\t// compose file — the declaration this change removes.\n\t\t\t\tcommand: 'server --console-address \":9001\" /data',\n\t\t\t\tvolumes: ['minio-data:/data'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'mc', 'ready', 'local'],\n\t\t\t\t\tinterval: '10s',\n\t\t\t\t\ttimeout: '5s',\n\t\t\t\t\tretries: 5,\n\t\t\t\t\tstart_period: '10s',\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'caddy':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\t// Relative to this file, which is at the project root, so the\n\t\t\t\t// mount resolves into `.gkm/` wherever the project lives.\n\t\t\t\tvolumes: [\n\t\t\t\t\t'./.gkm/Caddyfile:/etc/caddy/Caddyfile:ro',\n\t\t\t\t\t// A directory, not a file: every stage writes its own routes into\n\t\t\t\t\t// it, so `gkm test` cannot delete what `gkm dev` is serving.\n\t\t\t\t\t'./.gkm/caddy-sites:/etc/caddy/sites:ro',\n\t\t\t\t\t'caddy-data:/data',\n\t\t\t\t],\n\t\t\t\t// A surface and a site are processes on the *host*, not containers,\n\t\t\t\t// so the edge has to leave Docker's network to reach them. Docker\n\t\t\t\t// Desktop resolves this name already; on Linux it needs saying.\n\t\t\t\textra_hosts: ['host.docker.internal:host-gateway'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'caddy', 'version'],\n\t\t\t\t\tinterval: '10s',\n\t\t\t\t\ttimeout: '5s',\n\t\t\t\t\tretries: 5,\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'mailpit':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tenvironment: {\n\t\t\t\t\t// Accept mail from anything on this network. The URL carries no\n\t\t\t\t\t// credentials locally, which is what the parser's optional `auth`\n\t\t\t\t\t// exists for.\n\t\t\t\t\tMP_SMTP_AUTH_ACCEPT_ANY: '1',\n\t\t\t\t\tMP_SMTP_AUTH_ALLOW_INSECURE: '1',\n\t\t\t\t},\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'wget', '-q', '--spider', 'http://localhost:8025'],\n\t\t\t\t\tinterval: '10s',\n\t\t\t\t\ttimeout: '5s',\n\t\t\t\t\tretries: 5,\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'redis':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tvolumes: ['redis-data:/data'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'redis-cli', 'ping'],\n\t\t\t\t\tinterval: '5s',\n\t\t\t\t\ttimeout: '3s',\n\t\t\t\t\tretries: 5,\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'redis-http':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tenvironment: {\n\t\t\t\t\tSRH_MODE: 'env',\n\t\t\t\t\tSRH_TOKEN: LOCAL_TOKEN,\n\t\t\t\t\t// The Redis beside it, by compose service name — the one address\n\t\t\t\t\t// in this file that is not published, because nothing outside the\n\t\t\t\t\t// network speaks to it.\n\t\t\t\t\tSRH_CONNECTION_STRING: 'redis://redis:6379',\n\t\t\t\t},\n\t\t\t\tdepends_on: ['redis'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\t// 127.0.0.1, not localhost: the proxy binds IPv4 only, and\n\t\t\t\t\t// `localhost` resolves to ::1 first inside the container — which\n\t\t\t\t\t// is a health check that never passes and a container that never\n\t\t\t\t\t// comes up.\n\t\t\t\t\ttest: [\n\t\t\t\t\t\t'CMD',\n\t\t\t\t\t\t'wget',\n\t\t\t\t\t\t'-q',\n\t\t\t\t\t\t'-O',\n\t\t\t\t\t\t'/dev/null',\n\t\t\t\t\t\t'http://127.0.0.1:80/',\n\t\t\t\t\t],\n\t\t\t\t\tinterval: '5s',\n\t\t\t\t\ttimeout: '3s',\n\t\t\t\t\tretries: 10,\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'rabbitmq':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tenvironment: {\n\t\t\t\t\tRABBITMQ_DEFAULT_USER: LOCAL_USER,\n\t\t\t\t\tRABBITMQ_DEFAULT_PASS: LOCAL_USER,\n\t\t\t\t},\n\t\t\t\tvolumes: ['rabbitmq-data:/var/lib/rabbitmq'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'rabbitmq-diagnostics', '-q', 'ping'],\n\t\t\t\t\tinterval: '10s',\n\t\t\t\t\ttimeout: '5s',\n\t\t\t\t\tretries: 5,\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'localstack':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\t// SNS pushes to subscribers on the host — the dev server — and only\n\t\t\t\t// Docker Desktop resolves host.docker.internal on its own. On Linux\n\t\t\t\t// the name has to be mapped, or every push is refused.\n\t\t\t\textra_hosts: ['host.docker.internal:host-gateway'],\n\t\t\t\tenvironment: {\n\t\t\t\t\tAWS_DEFAULT_REGION: EMULATOR_REGION,\n\t\t\t\t\tAWS_ACCESS_KEY_ID: EMULATOR_CREDENTIALS.accessKeyId,\n\t\t\t\t\tAWS_SECRET_ACCESS_KEY: EMULATOR_CREDENTIALS.secretAccessKey,\n\t\t\t\t},\n\t\t\t\tvolumes: ['localstack-data:/var/lib/localstack'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\t// Bash, not curl: the floci image ships no curl, so a curl check\n\t\t\t\t\t// never passes and `--wait` fails every start. A TCP connect is\n\t\t\t\t\t// the fact a waiter needs — the gateway binds the port once its\n\t\t\t\t\t// HTTP server is up — and the repo's own compose file does the\n\t\t\t\t\t// same.\n\t\t\t\t\ttest: ['CMD', 'bash', '-c', 'exec 3<>/dev/tcp/localhost/4566'],\n\t\t\t\t\tinterval: '10s',\n\t\t\t\t\ttimeout: '5s',\n\t\t\t\t\tretries: 5,\n\t\t\t\t\tstart_period: '10s',\n\t\t\t\t},\n\t\t\t};\n\n\t\tdefault:\n\t\t\tthrow new UnknownContainer(container);\n\t}\n}\n\n/**\n * The published port mappings for a container.\n *\n * Every port is looked up by its own key rather than being derived as an offset\n * from the container's first, so MinIO's console cannot land on whatever was\n * allocated to the next container.\n */\nfunction publish(\n\tcontainer: string,\n\tports: PortAssignments,\n\tfakes?: Plan['fakes'],\n): string[] {\n\treturn portsOf(container, fakes).map((port) => {\n\t\tconst assigned = ports[port.key];\n\t\tif (assigned === undefined) throw new UnassignedPort(port.key);\n\n\t\treturn `${assigned}:${port.inside}`;\n\t});\n}\n\n/** A planned port that allocation never assigned. */\nexport class UnassignedPort extends Error {\n\t/** The port key that went unassigned, e.g. `minio-console`. */\n\treadonly key: string;\n\n\tconstructor(key: string) {\n\t\tsuper('No port was assigned for this container port');\n\t\tthis.name = 'UnassignedPort';\n\t\tthis.key = key;\n\t}\n}\n","/**\n * The real Docker, behind the small interface reconcile needs.\n *\n * Shells out to `docker compose` rather than talking to the daemon socket: it is\n * what a developer runs by hand when something is wrong, so the two cannot\n * disagree, and it keeps Docker out of this package's dependencies.\n *\n * Every call is scoped to the generated compose file. Nothing here can touch a\n * container this project did not generate.\n */\n\nimport { execFile, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport type { Docker } from './index';\n\nconst run = promisify(execFile);\n\n/**\n * The generated file, then the project's own if it has one.\n *\n * Merged in that order so the project's wins — an `image:` there pins a\n * container this file derived, and a service there is added beside them.\n */\nexport function composeFiles(composePath: string): string[] {\n\tconst project = join(dirname(composePath), 'docker-compose.yml');\n\treturn ['-f', composePath, ...(existsSync(project) ? ['-f', project] : [])];\n}\n\n/** How long to wait for containers to pass their health checks. */\nconst HEALTH_TIMEOUT_MS = 120_000;\n\n/** `docker compose up` exited unsuccessfully; its own output said why. */\nexport class ComposeUpFailed extends Error {\n\tconstructor(\n\t\treadonly services: readonly string[],\n\t\treadonly code: number | null,\n\t) {\n\t\tsuper(\n\t\t\t`docker compose could not start ${services.join(', ')} (exit ${code ?? 'signal'}). Its output above names the container; \\`docker compose logs <service>\\` shows why it stopped.`,\n\t\t);\n\t\tthis.name = 'ComposeUpFailed';\n\t}\n}\n\n/** Run compose with its output on this terminal rather than captured. */\nfunction shown(args: readonly string[], services: readonly string[]) {\n\treturn new Promise<void>((resolve, reject) => {\n\t\tconst child = spawn('docker', args, {\n\t\t\tstdio: ['ignore', 'inherit', 'inherit'],\n\t\t});\n\t\tchild.on('error', reject);\n\t\tchild.on('close', (code) =>\n\t\t\tcode === 0 ? resolve() : reject(new ComposeUpFailed(services, code)),\n\t\t);\n\t});\n}\n\nexport const dockerCli: Docker = {\n\tasync publishedPort(composePath, service, inside) {\n\t\ttry {\n\t\t\tconst { stdout } = await run('docker', [\n\t\t\t\t'compose',\n\t\t\t\t...composeFiles(composePath),\n\t\t\t\t'port',\n\t\t\t\tservice,\n\t\t\t\tString(inside),\n\t\t\t]);\n\n\t\t\t// `0.0.0.0:54321` — the part after the last colon is the host port.\n\t\t\tconst port = Number(stdout.trim().split(':').pop());\n\n\t\t\treturn Number.isFinite(port) && port > 0 ? port : undefined;\n\t\t} catch {\n\t\t\t// Not running, or no such service. Both mean \"nothing to reuse\".\n\t\t\treturn undefined;\n\t\t}\n\t},\n\n\tasync up(composePath, services, options = {}) {\n\t\tconst args = [\n\t\t\t'compose',\n\t\t\t...composeFiles(composePath),\n\t\t\t'up',\n\t\t\t'-d',\n\t\t\t// Recreate anything whose definition moved — a changed image pin is\n\t\t\t// exactly the case the hash detects, and leaving the old container\n\t\t\t// running would make the reconcile a lie.\n\t\t\t'--remove-orphans',\n\t\t\t'--wait',\n\t\t\t`--wait-timeout=${Math.floor(HEALTH_TIMEOUT_MS / 1000)}`,\n\t\t\t...services,\n\t\t];\n\n\t\tif (options.show) return shown(args, services);\n\t\tawait run('docker', args);\n\t},\n\n\tasync copyOut(composePath, service, from, to) {\n\t\tawait mkdir(dirname(to), { recursive: true });\n\t\tawait run('docker', [\n\t\t\t'compose',\n\t\t\t...composeFiles(composePath),\n\t\t\t'cp',\n\t\t\t`${service}:${from}`,\n\t\t\tto,\n\t\t]);\n\t},\n\n\tasync reload(composePath, service) {\n\t\t// Caddy's own reload is graceful and validates first, so a config it\n\t\t// refuses leaves the previous one serving rather than dropping the edge.\n\t\tawait run('docker', [\n\t\t\t'compose',\n\t\t\t...composeFiles(composePath),\n\t\t\t'exec',\n\t\t\t'-T',\n\t\t\tservice,\n\t\t\t'caddy',\n\t\t\t'reload',\n\t\t\t'--config',\n\t\t\t'/etc/caddy/Caddyfile',\n\t\t]);\n\t},\n\n\tasync healthy(composePath, services) {\n\t\tif (services.length === 0) return true;\n\n\t\ttry {\n\t\t\tconst { stdout } = await run('docker', [\n\t\t\t\t'compose',\n\t\t\t\t...composeFiles(composePath),\n\t\t\t\t'ps',\n\t\t\t\t'--format',\n\t\t\t\t'json',\n\t\t\t\t...services,\n\t\t\t]);\n\n\t\t\tconst running = new Set<string>();\n\t\t\tfor (const line of stdout.trim().split('\\n').filter(Boolean)) {\n\t\t\t\tconst entry = JSON.parse(line) as {\n\t\t\t\t\tService?: string;\n\t\t\t\t\tState?: string;\n\t\t\t\t\tHealth?: string;\n\t\t\t\t};\n\n\t\t\t\t// A container with a health check must be passing it; one without\n\t\t\t\t// only has to be running, since there is nothing better to ask.\n\t\t\t\tconst ok =\n\t\t\t\t\tentry.State === 'running' &&\n\t\t\t\t\t(entry.Health === undefined ||\n\t\t\t\t\t\tentry.Health === '' ||\n\t\t\t\t\t\tentry.Health === 'healthy');\n\n\t\t\t\tif (ok && entry.Service) running.add(entry.Service);\n\t\t\t}\n\n\t\t\treturn services.every((service) => running.has(service));\n\t\t} catch {\n\t\t\t// No compose file yet, or no daemon. Either way, not healthy.\n\t\t\treturn false;\n\t\t}\n\t},\n};\n\nexport { HEALTH_TIMEOUT_MS };\n","/**\n * This machine's address on the local network — what a phone running the app\n * reaches it on.\n */\n\nimport { type NetworkInterfaceInfo, networkInterfaces } from 'node:os';\n\n/**\n * The first private IPv4 address of an interface that is up and not loopback.\n *\n * Private ranges only (`10/8`, `172.16/12`, `192.168/16`): a public address\n * would mean listening for a phone on the open internet, and a link-local one\n * (`169.254/16`) is what an interface has when it found no network at all.\n * Undefined when there is none — a laptop on no network still runs, its mobile\n * app reaching only a simulator.\n */\nexport function lanAddress(\n\tinterfaces: Record<\n\t\tstring,\n\t\tNetworkInterfaceInfo[] | undefined\n\t> = networkInterfaces(),\n): string | undefined {\n\tfor (const entries of Object.values(interfaces)) {\n\t\tfor (const entry of entries ?? []) {\n\t\t\tif (entry.family !== 'IPv4' || entry.internal) continue;\n\t\t\tif (isPrivate(entry.address)) return entry.address;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction isPrivate(address: string): boolean {\n\treturn (\n\t\t/^10\\./.test(address) ||\n\t\t/^192\\.168\\./.test(address) ||\n\t\t/^172\\.(1[6-9]|2\\d|3[01])\\./.test(address)\n\t);\n}\n","/**\n * Port allocation — why two projects can run at once.\n *\n * Publishing `5432:5432` means the second project on your machine cannot start.\n * Nothing solves that except giving each project its own ports, and what makes\n * it *possible* is the single-`<NAME>_URL` rule: an app reads `ORDERS_URL` and\n * never sees a host or a port, so reconcile is free to bind whatever is\n * available and compose the URL from what it bound. An app reading `DB_HOST` and\n * `DB_PORT` separately could not be moved this way.\n *\n * Assignments are **persisted, not recomputed**. Recomputing means adding a\n * construct renumbers its neighbours, and every running container, saved\n * connection string, and `psql` history goes stale at once.\n *\n * Pure, with the liveness probe injected — a collision with another project's\n * stack is a fact about the machine, and keeping it a parameter is what lets the\n * rules be asserted without binding a socket.\n */\n\nimport { createHash } from 'node:crypto';\nimport { createServer } from 'node:net';\n\n/** What was assigned, by container name. Persisted between runs. */\nexport type PortAssignments = Readonly<Record<string, number>>;\n\n/**\n * The window ports are drawn from.\n *\n * Above the registered range so nothing here needs privileges, and below the\n * ephemeral range both Linux (32768+) and macOS (49152+) allocate from — a port\n * the kernel might hand to an outgoing connection is one that is free when\n * checked and taken by the time it is bound.\n */\nconst WINDOW = { min: 20000, max: 32000 } as const;\n\n/**\n * How far apart two projects start.\n *\n * Wide enough that a project can add containers without walking into its\n * neighbour's block, narrow enough that the window holds many projects.\n */\nconst BLOCK = 50;\n\n/**\n * Where a project's ports start.\n *\n * Derived from the name rather than assigned sequentially so that a fresh clone\n * of the same project on the same machine lands on the same block — the first\n * run has nothing persisted to read, and hashing is what keeps that first run\n * from colliding with whatever else is already allocated.\n *\n * @example startingPort('toolbox') // stable for a given name\n */\nexport function startingPort(project: string): number {\n\tconst digest = createHash('sha256').update(project).digest();\n\tconst blocks = Math.floor((WINDOW.max - WINDOW.min) / BLOCK);\n\n\treturn WINDOW.min + (digest.readUInt32BE(0) % blocks) * BLOCK;\n}\n\n/**\n * Whether a port can be bound right now.\n *\n * Async because the only way to answer it is to try binding, and\n * `server.listen` is asynchronous — there is no synchronous form to wrap. A\n * probe that pretended otherwise would have to lie.\n */\nexport type PortProbe = (port: number) => Promise<boolean>;\n\n/**\n * Assign a port to every container that has none, keeping the ones that do.\n *\n * Existing assignments are returned untouched even when the container is no\n * longer in the plan: removing a construct must not renumber what is left, and\n * keeping the entry means re-adding it later restores the same port rather than\n * inventing a new one.\n *\n * @param probe - reports what is actually listening. A port assigned to this\n * project is skipped without probing, because on a converged reconcile our own\n * container is the thing listening on it.\n * @throws {NoPortAvailable} when the window is exhausted.\n */\nexport async function allocate(\n\tproject: string,\n\tcontainers: readonly string[],\n\texisting: PortAssignments = {},\n\tprobe: PortProbe = isPortFree,\n): Promise<PortAssignments> {\n\tconst assignments: Record<string, number> = { ...existing };\n\tconst taken = new Set(Object.values(assignments));\n\n\t// Sorted so the order containers happen to appear in the plan cannot change\n\t// which port each one gets.\n\tfor (const container of [...containers].sort()) {\n\t\tif (assignments[container] !== undefined) continue;\n\n\t\tconst port = await nextFree(startingPort(project), taken, probe);\n\t\tassignments[container] = port;\n\t\ttaken.add(port);\n\t}\n\n\treturn assignments;\n}\n\n/**\n * The ports to keep: what running containers are observed on, then what was\n * saved — minus any saved entry whose port something else already holds.\n *\n * Spreading one over the other let an observed port override its own key and\n * leave a *different* key still saved on the same number: two services on one\n * port, and whichever answered first won. Saved keys for containers the plan no\n * longer has — a backend switched and back — were the usual source. Observed\n * wins because it is what is actually bound; a dropped entry is simply\n * allocated again.\n */\nexport function keptPorts(\n\tsaved: PortAssignments = {},\n\tobserved: PortAssignments = {},\n): PortAssignments {\n\tconst kept: Record<string, number> = { ...observed };\n\tconst taken = new Set(Object.values(observed));\n\n\t// Sorted, so which of two colliding saved entries survives is stable.\n\tfor (const [key, port] of Object.entries(saved).sort(([a], [b]) =>\n\t\ta < b ? -1 : 1,\n\t)) {\n\t\tif (key in kept || taken.has(port)) continue;\n\t\tkept[key] = port;\n\t\ttaken.add(port);\n\t}\n\n\treturn kept;\n}\n\n/** The window was exhausted. */\nexport class NoPortAvailable extends Error {\n\t/** Where the search started. */\n\treadonly from: number;\n\t/** The window searched, so the caller can report what was full. */\n\treadonly window: { min: number; max: number } = WINDOW;\n\n\tconstructor(from: number) {\n\t\tsuper('No free port is available in the local port range');\n\t\tthis.name = 'NoPortAvailable';\n\t\tthis.from = from;\n\t}\n}\n\n/**\n * The first port at or after `from` that is neither ours nor in use.\n *\n * Wraps at the top of the window rather than stopping, so a project whose block\n * sits near the end is not the one that fails.\n */\nasync function nextFree(\n\tfrom: number,\n\ttaken: ReadonlySet<number>,\n\tprobe: PortProbe,\n): Promise<number> {\n\tconst span = WINDOW.max - WINDOW.min;\n\n\tfor (let offset = 0; offset < span; offset++) {\n\t\tconst port = WINDOW.min + ((from - WINDOW.min + offset) % span);\n\t\tif (taken.has(port)) continue;\n\t\tif (await probe(port)) return port;\n\t}\n\n\tthrow new NoPortAvailable(from);\n}\n\n/**\n * Whether nothing is listening on a port.\n *\n * Binds rather than connects: a refused connection says nothing about whether\n * the port can be *published*, which is the question Docker will ask. Binds on\n * `0.0.0.0` for the same reason — a port free on loopback can still be taken on\n * the address Docker publishes to.\n *\n * Inherently racy, and that is acceptable: Docker failing to bind stays the real\n * check. This exists so the common case surfaces as a message at reconcile\n * rather than as a connection failure ten minutes later.\n */\nexport function isPortFree(port: number): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst server = createServer();\n\n\t\tserver.once('error', () => resolve(false));\n\t\tserver.once('listening', () => server.close(() => resolve(true)));\n\t\tserver.listen(port, '0.0.0.0');\n\t});\n}\n","/**\n * Creating what the URLs name.\n *\n * Convergent and idempotent: every statement asks whether the thing exists\n * first, so reconciling repeatedly is free and a half-applied run recovers by\n * being run again. That property is what lets `gkm dev` do this on every start.\n *\n * The line the design draws is at data, not at side effects — this creates\n * databases, schemas, and buckets, and it never seeds, resets, or drops. A\n * developer who wants their data gone asks for it explicitly.\n *\n * Statement generation is separated from application for the reason the design\n * gives: the same DDL has to run in-process locally and from a provisioner in a\n * VPC, and one implementation is what stops \"works locally, fails deployed\".\n * When the role DDL lands in `@geekmidas/db` this delegates to it rather than\n * growing its own.\n */\n\nimport { cacheTableStatements } from '@geekmidas/cache/postgres';\nimport { ownerRole, readerRole, roleStatements } from '@geekmidas/db/pg/roles';\nimport { cacheTable } from '@geekmidas/manifest';\nimport { localRole, localRolePassword, rootDatabase } from './env';\nimport type { Plan, PlannedResource } from './plan';\n\n/** One thing to create, and how to tell whether it already exists. */\nexport interface Statement {\n\t/** The construct this is for, so a failure can name it. */\n\tid: string;\n\t/** What it does, in the line reconcile prints. */\n\tdescribe: string;\n\t/** The database to connect to. Absent means the cluster's default. */\n\tdatabase?: string;\n\t/**\n\t * Answers \"does this already exist\", parameterised.\n\t *\n\t * Absent for a statement that is idempotent in itself — a `GRANT` re-granting\n\t * what is already granted is a no-op, and checking would cost a round trip to\n\t * learn nothing. Those run every time and report unchanged.\n\t */\n\texists?: { sql: string; values: unknown[] };\n\t/**\n\t * The DDL, run only when `exists` returns nothing.\n\t *\n\t * Not parameterised, because identifiers cannot be — which is why\n\t * {@link quoteIdentifier} exists and why names are canonicalised long before\n\t * they reach here.\n\t */\n\tcreate: string;\n}\n\n/**\n * The Postgres work a plan implies.\n *\n * Databases before the schemas inside them, which `provisionOrder` already\n * guarantees by putting parents first.\n */\nexport function postgresStatements(\n\tplan: Plan,\n\t/**\n\t * Seeds the derived role passwords. The same project seeds the same\n\t * passwords, so a restart does not lock a developer out of their own data —\n\t * and two checkouts do not share a credential.\n\t */\n\tproject = '',\n\t/**\n\t * A deployed stage's random seed, salting the role passwords the way a\n\t * deploy does — see `localRolePassword`. Absent for the local stages.\n\t */\n\tseed?: string,\n): Statement[] {\n\tconst statements: Statement[] = [];\n\n\tfor (const resource of plan.resources) {\n\t\tif (!resource.provisions || resource.container !== 'postgres') continue;\n\n\t\tif (resource.kind === 'database') {\n\t\t\tstatements.push({\n\t\t\t\tid: resource.id,\n\t\t\t\tdescribe: `database ${resource.name}`,\n\t\t\t\texists: {\n\t\t\t\t\tsql: 'SELECT 1 FROM pg_database WHERE datname = $1',\n\t\t\t\t\tvalues: [resource.name],\n\t\t\t\t},\n\t\t\t\t// `CREATE DATABASE` cannot run inside a transaction, which is why\n\t\t\t\t// each statement is applied on its own rather than batched.\n\t\t\t\tcreate: `CREATE DATABASE ${quoteIdentifier(resource.name)}`,\n\t\t\t});\n\n\t\t\t// After the database, never before: roles are cluster-scoped but their\n\t\t\t// grants are not, so every statement after the first two has to run\n\t\t\t// against a database that exists.\n\t\t\tif (resource.schema) {\n\t\t\t\tstatements.push(\n\t\t\t\t\t...rolesFor(resource, resource.schema, plan, project, seed),\n\t\t\t\t);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (resource.kind === 'database-schema' && resource.schema) {\n\t\t\t// The schema *and* the roles that reach it, from one generator shared\n\t\t\t// with the deploy target — the same DDL has to run in-process here and\n\t\t\t// from a provisioner in a VPC, and one implementation is what stops\n\t\t\t// \"works locally, fails deployed\".\n\t\t\tstatements.push(\n\t\t\t\t...rolesFor(resource, resource.schema, plan, project, seed),\n\t\t\t);\n\t\t}\n\n\t\t// A cache that lives in a database is a table in it, and a table is DDL —\n\t\t// so the owner creates it, not the handler's role, which may not create\n\t\t// anything. The same reason the driver does not do it lazily.\n\t\t//\n\t\t// `of` is the whole test: `planFor` resolves a `cache: 'db'` backend into\n\t\t// that edge before anything reads the plan, so a cache lands in a\n\t\t// database exactly one way and this does not have to know the config\n\t\t// existed.\n\t\tconst cacheHome =\n\t\t\tresource.kind === 'cache' && resource.of\n\t\t\t\t? plan.resources.find((r) => r.id === resource.of)\n\t\t\t\t: undefined;\n\n\t\tif (cacheHome) {\n\t\t\tstatements.push(...cacheTableDdl(resource, cacheHome, plan));\n\t\t}\n\n\t\t// A reader provisions nothing: it is a set of grants on an endpoint that\n\t\t// already exists, and falling back to the writer where no replica exists\n\t\t// stays safe because read-only is enforced by the role.\n\t}\n\n\treturn statements;\n}\n\n/**\n * A cache's table, in the schema the role that reads it resolves names in.\n *\n * Two things have to line up, and neither is the table's contents. The driver\n * names the table unqualified and lets the connection's `search_path` place it\n * — which the role carries — so creating it from the master connection, whose\n * path is `public`, leaves a table the application cannot see and a\n * `relation \"cache\" does not exist` on the first request. And the default\n * privileges that make a tenant's tables reachable are granted *for the owner\n * role*, so nothing covers a table the master created.\n *\n * Hence: qualified with the tenant's schema, handed to its owner, and granted\n * explicitly — `roleStatements` grants what exists at the time it runs, and\n * this table does not exist yet.\n *\n * A database that opted out of roles needs none of it: everything connects as\n * the master, whose `search_path` finds `public`, and the table it creates\n * there is the table it reads.\n */\nfunction cacheTableDdl(\n\tcache: PlannedResource,\n\thome: PlannedResource,\n\tplan: Plan,\n): Statement[] {\n\tconst database = rootDatabase(home, plan);\n\tconst schema = home.roles === false ? undefined : home.schema;\n\t// The same default the URL carries — `cacheTable` is read by both, so the\n\t// table a client reads and the table a target creates cannot drift.\n\tconst name = cache.table ?? cacheTable(cache.id);\n\tconst table = schema ? `${schema}.${name}` : name;\n\n\tconst created = cacheTableStatements({ table }).map((statement) => ({\n\t\tid: cache.id,\n\t\tdescribe: statement.describe,\n\t\tdatabase,\n\t\t...(statement.exists ? { exists: statement.exists } : {}),\n\t\tcreate: statement.sql,\n\t}));\n\n\tif (!schema) return created;\n\n\tconst runtime = localRole(home);\n\tconst owner = ownerRole(runtime);\n\tconst qualified = table\n\t\t.split('.')\n\t\t.map((part) => quoteIdentifier(part))\n\t\t.join('.');\n\n\treturn [\n\t\t...created,\n\t\t{\n\t\t\tid: cache.id,\n\t\t\tdescribe: `cache table ${table} is owned by ${owner}`,\n\t\t\tdatabase,\n\t\t\tcreate: `ALTER TABLE ${qualified} OWNER TO ${quoteIdentifier(owner)}`,\n\t\t},\n\t\t{\n\t\t\tid: cache.id,\n\t\t\tdescribe: `${runtime} may read and write ${table}`,\n\t\t\tdatabase,\n\t\t\tcreate:\n\t\t\t\t`GRANT SELECT, INSERT, UPDATE, DELETE ON ${qualified} ` +\n\t\t\t\t`TO ${quoteIdentifier(runtime)}`,\n\t\t},\n\t];\n}\n\n/**\n * The roles and schema for one database or tenant.\n *\n * Skipped entirely when the construct declared `roles: false` — the documented\n * downgrade, where both URLs fall back to the cluster's master credential. It\n * is a choice someone made rather than a default they got.\n */\nfunction rolesFor(\n\tresource: PlannedResource,\n\tschema: string,\n\tplan: Plan,\n\tproject: string,\n\tseed?: string,\n): Statement[] {\n\tif (resource.roles === false) return [];\n\n\tconst runtime = localRole(resource);\n\tconst owner = ownerRole(runtime);\n\n\t// A reader role only where something reads through one. Creating roles\n\t// nothing connects as is noise in `\\du` and one more thing to explain.\n\tconst reader = plan.resources.some(\n\t\t(r) => r.kind === 'database-reader' && r.of === resource.id,\n\t)\n\t\t? readerRole(runtime)\n\t\t: undefined;\n\n\treturn roleStatements({\n\t\truntime,\n\t\towner,\n\t\t...(reader ? { reader } : {}),\n\t\tschema,\n\t\t// A database's owner may create in it — trusted extensions; a tenant's\n\t\t// stays confined to its schema.\n\t\t...(resource.kind === 'database'\n\t\t\t? { database: rootDatabase(resource, plan) }\n\t\t\t: {}),\n\t\tpasswords: {\n\t\t\truntime: localRolePassword(project, plan, runtime, seed),\n\t\t\towner: localRolePassword(project, plan, owner, seed),\n\t\t\t...(reader\n\t\t\t\t? { reader: localRolePassword(project, plan, reader, seed) }\n\t\t\t\t: {}),\n\t\t},\n\t}).map((statement) => ({\n\t\tid: resource.id,\n\t\tdescribe: statement.describe,\n\t\t// Inside the parent's database — a tenant is a schema, never a database\n\t\t// of its own. Roles are cluster-scoped, but the grants are not, so all of\n\t\t// it has to run against the database the objects live in.\n\t\tdatabase: rootDatabase(resource, plan),\n\t\t...(statement.exists ? { exists: statement.exists } : {}),\n\t\tcreate: statement.sql,\n\t}));\n}\n\n/** The buckets a plan implies. */\nexport function bucketNames(plan: Plan): string[] {\n\treturn plan.resources\n\t\t.filter((r) => r.provisions && r.kind === 'objects')\n\t\t.map((r) => r.name);\n}\n\n/**\n * Quote an identifier for use in DDL.\n *\n * Names reaching here are already canonical and stage-suffixed, so this is\n * defence rather than sanitisation — but DDL cannot be parameterised, and an\n * unquoted identifier is the one place a name could ever be more than a name.\n */\nexport function quoteIdentifier(name: string): string {\n\treturn `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/** What applying a statement did. */\nexport interface Applied {\n\tid: string;\n\tdescribe: string;\n\t/** False when it already existed — the converged case. */\n\tcreated: boolean;\n}\n\n/** The database operations the applier needs, so it can be run against a fake. */\nexport interface SqlClient {\n\t/** Run a query against one database, returning its rows. */\n\tquery(\n\t\tdatabase: string | undefined,\n\t\tsql: string,\n\t\tvalues?: unknown[],\n\t): Promise<unknown[]>;\n}\n\n/**\n * Apply the plan's Postgres statements.\n *\n * Each is checked before it is run, so this converges rather than erroring on a\n * second pass.\n */\nexport async function applyPostgres(\n\tclient: SqlClient,\n\tstatements: readonly Statement[],\n): Promise<Applied[]> {\n\tconst applied: Applied[] = [];\n\n\tfor (const statement of statements) {\n\t\t// No check means the statement is idempotent in itself — a GRANT\n\t\t// re-granting what is already granted is a no-op — so it runs and\n\t\t// reports unchanged, and a converged reconcile still says converged.\n\t\tif (!statement.exists) {\n\t\t\tawait client.query(statement.database, statement.create);\n\t\t\tapplied.push({\n\t\t\t\tid: statement.id,\n\t\t\t\tdescribe: statement.describe,\n\t\t\t\tcreated: false,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst rows = await client.query(\n\t\t\tstatement.database,\n\t\t\tstatement.exists.sql,\n\t\t\tstatement.exists.values,\n\t\t);\n\n\t\tif (rows.length > 0) {\n\t\t\tapplied.push({\n\t\t\t\tid: statement.id,\n\t\t\t\tdescribe: statement.describe,\n\t\t\t\tcreated: false,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tawait client.query(statement.database, statement.create);\n\t\tapplied.push({\n\t\t\tid: statement.id,\n\t\t\tdescribe: statement.describe,\n\t\t\tcreated: true,\n\t\t});\n\t}\n\n\treturn applied;\n}\n\n/**\n * What each bucket's open paths are, if anything serves it.\n *\n * Keyed by the *origin*, because that is where the policy lives: two servers\n * over one bucket contribute to one policy, so the patterns are unioned rather\n * than the last one winning.\n */\nexport function bucketPolicies(\n\tplan: Plan,\n): { bucket: string; open: string[] }[] {\n\tconst byBucket = new Map<string, Set<string>>();\n\n\tfor (const resource of plan.resources) {\n\t\tif (resource.kind !== 'file-server' || !resource.open?.length) continue;\n\n\t\tconst origin = plan.resources.find((r) => r.id === resource.of);\n\t\tif (!origin) continue;\n\n\t\tconst patterns = byBucket.get(origin.name) ?? new Set<string>();\n\t\tfor (const pattern of resource.open) patterns.add(pattern);\n\t\tbyBucket.set(origin.name, patterns);\n\t}\n\n\treturn [...byBucket]\n\t\t.map(([bucket, open]) => ({ bucket, open: [...open].sort() }))\n\t\t.sort((a, b) => a.bucket.localeCompare(b.bucket));\n}\n\n/**\n * One bucket's open paths as an S3 bucket policy.\n *\n * Anonymous `s3:GetObject` on the named prefixes and nothing else — which is\n * what \"open\" means everywhere: the server serves that path without a\n * signature, and the bucket stays private for everything else.\n *\n * One fidelity note, stated rather than discovered: an S3 policy resource's `*`\n * crosses `/`, so a single-star pattern is *wider* here than the construct's own\n * runtime check, which stops at a segment boundary. The construct is the\n * stricter of the two, so a key it refuses to serve unsigned is never one this\n * policy was relied on to refuse — but a key fetched directly, bypassing the\n * client, can be admitted by the policy where the client would have said no.\n * Prefer `**` where crossing segments is what you meant.\n */\nexport function bucketPolicy(bucket: string, open: readonly string[]): string {\n\treturn JSON.stringify({\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tSid: 'GkmOpenPaths',\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tPrincipal: { AWS: ['*'] },\n\t\t\t\tAction: ['s3:GetObject'],\n\t\t\t\tResource: open.map(\n\t\t\t\t\t(pattern) =>\n\t\t\t\t\t\t`arn:aws:s3:::${bucket}/${pattern.replace(/\\*\\*/g, '*')}`,\n\t\t\t\t),\n\t\t\t},\n\t\t],\n\t});\n}\n\n/**\n * The topics and queues to create on the AWS emulator.\n *\n * Only on the `sns` backend: pg-boss makes its own tables on first connect and\n * a RabbitMQ exchange is declared by the client, but an SNS topic and an SQS\n * queue must exist before anything can publish to them.\n */\nexport function carrierNames(plan: Plan): {\n\ttopics: string[];\n\tqueues: string[];\n} {\n\tif (plan.events !== 'sns') return { topics: [], queues: [] };\n\n\tconst named = (kind: 'topic' | 'queue') =>\n\t\tplan.resources.filter((r) => r.kind === kind).map((r) => r.name);\n\n\treturn { topics: named('topic'), queues: named('queue') };\n}\n\n/** The emulator operations the applier needs. Both creates are idempotent. */\nexport interface CarrierClient {\n\ttopicExists(name: string): Promise<boolean>;\n\tcreateTopic(name: string): Promise<void>;\n\tqueueExists(name: string): Promise<boolean>;\n\tcreateQueue(name: string): Promise<void>;\n}\n\n/** Create each topic and queue the plan names, skipping those that exist. */\nexport async function applyCarriers(\n\tclient: CarrierClient,\n\tcarriers: { topics: readonly string[]; queues: readonly string[] },\n): Promise<Applied[]> {\n\tconst applied: Applied[] = [];\n\n\tfor (const name of carriers.topics) {\n\t\tconst created = !(await client.topicExists(name));\n\t\tif (created) await client.createTopic(name);\n\t\tapplied.push({ id: name, describe: `topic ${name}`, created });\n\t}\n\tfor (const name of carriers.queues) {\n\t\tconst created = !(await client.queueExists(name));\n\t\tif (created) await client.createQueue(name);\n\t\tapplied.push({ id: name, describe: `queue ${name}`, created });\n\t}\n\n\treturn applied;\n}\n\n/** The Postgres databases the plan creates — each is a `CREATE DATABASE`. */\nexport function postgresDatabaseNames(plan: Plan): string[] {\n\treturn plan.resources\n\t\t.filter(\n\t\t\t(resource) =>\n\t\t\t\tresource.kind === 'database' &&\n\t\t\t\tresource.provisions &&\n\t\t\t\tresource.container === 'postgres',\n\t\t)\n\t\t.map((resource) => resource.name);\n}\n\n/**\n * Whether every one of these databases exists. A database dropped behind the\n * recorded state's back — by another checkout's test teardown, against the\n * same container — makes the state a lie, and only asking Postgres finds out.\n * Unreachable counts as missing: applying again is what finds out why.\n */\nexport async function databasesExist(\n\tclient: SqlClient,\n\tnames: readonly string[],\n): Promise<boolean> {\n\tif (names.length === 0) return true;\n\ttry {\n\t\tconst rows = (await client.query(\n\t\t\tundefined,\n\t\t\t'SELECT datname FROM pg_catalog.pg_database WHERE datname = ANY($1)',\n\t\t\t[names],\n\t\t)) as { datname: string }[];\n\t\treturn new Set(rows.map((row) => row.datname)).size === names.length;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** The object-storage operations the applier needs. */\nexport interface BucketClient {\n\texists(bucket: string): Promise<boolean>;\n\tcreate(bucket: string): Promise<void>;\n\t/** The bucket's current policy, or `undefined` where it has none. */\n\tpolicy(bucket: string): Promise<string | undefined>;\n\tsetPolicy(bucket: string, policy: string): Promise<void>;\n}\n\n/**\n * Apply each served bucket's open-path policy, skipping those already correct.\n *\n * Compared as parsed JSON rather than as text: the same policy re-serialised\n * with different key order is the same policy, and rewriting it every reconcile\n * would report a change on every start.\n */\nexport async function applyPolicies(\n\tclient: BucketClient,\n\tpolicies: readonly { bucket: string; open: string[] }[],\n): Promise<Applied[]> {\n\tconst applied: Applied[] = [];\n\n\tfor (const { bucket, open } of policies) {\n\t\tconst wanted = bucketPolicy(bucket, open);\n\t\tconst describe = `open paths on ${bucket}`;\n\n\t\tif (equivalent(await client.policy(bucket), wanted)) {\n\t\t\tapplied.push({ id: `${bucket}:policy`, describe, created: false });\n\t\t\tcontinue;\n\t\t}\n\n\t\tawait client.setPolicy(bucket, wanted);\n\t\tapplied.push({ id: `${bucket}:policy`, describe, created: true });\n\t}\n\n\treturn applied;\n}\n\n/** Whether two policy documents say the same thing. */\nfunction equivalent(current: string | undefined, wanted: string): boolean {\n\tif (!current) return false;\n\n\ttry {\n\t\treturn (\n\t\t\tJSON.stringify(JSON.parse(current)) === JSON.stringify(JSON.parse(wanted))\n\t\t);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** Create every bucket the plan names, skipping those that exist. */\nexport async function applyBuckets(\n\tclient: BucketClient,\n\tbuckets: readonly string[],\n): Promise<Applied[]> {\n\tconst applied: Applied[] = [];\n\n\tfor (const bucket of buckets) {\n\t\tif (await client.exists(bucket)) {\n\t\t\tapplied.push({\n\t\t\t\tid: bucket,\n\t\t\t\tdescribe: `bucket ${bucket}`,\n\t\t\t\tcreated: false,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tawait client.create(bucket);\n\t\tapplied.push({ id: bucket, describe: `bucket ${bucket}`, created: true });\n\t}\n\n\treturn applied;\n}\n","/**\n * What reconcile records, and the hash that lets it do nothing.\n *\n * Reconciling on every `gkm dev` is only acceptable if the converged case is\n * free, so the desired plan is hashed: matching hash plus healthy containers\n * means there is nothing to do. Without that, the automatic reconcile the design\n * asks for would tax every start.\n *\n * Almost nothing needs recording — roles are queried, migrations have their own\n * table, containers are remembered by Docker. Only ports and this hash have no\n * other owner.\n */\n\nimport { createHash } from 'node:crypto';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type { ComposeFile } from './compose';\nimport type { Plan } from './plan';\n\n/** What was true at the end of the last successful reconcile. */\nexport interface ReconcileState {\n\t/** The hash of the plan that was applied. */\n\thash: string;\n\t/** The stage it was applied for, so a mismatched file is ignored. */\n\tstage: string;\n}\n\nconst STATE_DIR = '.gkm/reconcile';\n\n/**\n * The hash of everything a reconcile would act on.\n *\n * Covers the compose document rather than the plan alone, because the plan is\n * unchanged when only an image pin moves — and a changed image is exactly a\n * container that must be recreated. `stringify` is stable here because\n * `composeFor` sorts its containers, which is what makes the hash meaningful.\n *\n * Deliberately a plain function over serialisable input: the deployed target\n * keys its provisioner invocation on the same idea, and the two must not be able\n * to disagree about what counts as a change.\n */\nexport function planHash(\n\tplan: Plan,\n\tcompose: ComposeFile,\n\t/**\n\t * Anything else generated from the plan that a container reads.\n\t *\n\t * The edge's Caddyfile is the case: a file server added or a bucket renamed\n\t * changes the routing without changing a single container, so a hash over\n\t * the compose document alone would report convergence and leave the old\n\t * routes serving.\n\t */\n\tgenerated: Readonly<Record<string, string>> = {},\n): string {\n\treturn createHash('sha256')\n\t\t.update(JSON.stringify({ stage: plan.stage, plan, compose, generated }))\n\t\t.digest('hex');\n}\n\n/** Read the recorded state, or nothing if this stage has never reconciled. */\nexport async function loadState(\n\troot: string,\n\tstage: string,\n): Promise<ReconcileState | undefined> {\n\ttry {\n\t\tconst raw = await readFile(statePath(root, stage), 'utf-8');\n\t\tconst state = JSON.parse(raw) as ReconcileState;\n\n\t\t// A file written for another stage is not this stage's state. Treating it\n\t\t// as one is how `gkm test` would skip work `gkm dev` did.\n\t\treturn state.stage === stage ? state : undefined;\n\t} catch {\n\t\t// Unreadable, absent, or corrupt all mean the same thing: reconcile. The\n\t\t// loop is convergent, so the cost of being wrong here is one slow start.\n\t\treturn undefined;\n\t}\n}\n\n/** Record what was applied, so the next run can skip it. */\nexport async function saveState(\n\troot: string,\n\tstate: ReconcileState,\n): Promise<void> {\n\tconst path = statePath(root, state.stage);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, `${JSON.stringify(state, null, 2)}\\n`);\n}\n\n/**\n * Forget what was applied, so the next reconcile of this stage converges from\n * nothing — creating again whatever was dropped underneath the record.\n */\nexport async function forgetState(root: string, stage: string): Promise<void> {\n\tawait rm(statePath(root, stage), { force: true });\n}\n\nfunction statePath(root: string, stage: string): string {\n\treturn join(root, STATE_DIR, `${stage}.json`);\n}\n","/**\n * Reconcile — the one convergent function `gkm dev`, `gkm test`, and `gkm setup`\n * all call.\n *\n * Three phases, and only this one has side effects: declaring is pure (`gkm\n * build` and the dev watcher), running reads `<NAME>_URL` and provisions\n * nothing. Reconcile computes the desired state, compares, and applies the\n * difference — which is what makes \"when does it run\" answerable with\n * *whenever, repeatedly*.\n *\n * It is safe to run automatically because its blast radius is entirely local:\n * this project's containers, this project's `.gkm/`. Nothing it does can reach a\n * cloud. The line is drawn at data, not at side effects — it allocates ports,\n * writes compose, starts containers, and creates roles; it never seeds, resets,\n * or drops.\n *\n * Docker is injected rather than imported so the rules above can be asserted\n * without a daemon. The default implementation is the real one.\n */\n\nimport { existsSync } from 'node:fs';\nimport { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { type ConstructManifest, provisionOrder } from '@geekmidas/manifest';\nimport type { CacheBackend, EventsBackend } from '../types';\nimport { TEST_STAGE } from '../workspace/stages';\nimport { caddyfileRoot, sitesFor, toCaddyfile } from './caddyfile';\nimport { bucketClient, carrierClient, pgClient } from './clients';\nimport {\n\ttype ComposeFile,\n\ttype ComposeService,\n\tcomposeFor,\n\ttoYaml,\n} from './compose';\nimport {\n\tportKeys,\n\tportsOf,\n\tprimaryPortKey,\n\ttype ServiceAddress,\n\tserviceAddresses,\n} from './containers';\nimport { dockerCli } from './docker';\nimport { envFor } from './env';\nimport type { LocalFake } from './fakes';\nimport { lanAddress } from './lan';\nimport { type Plan, type PlanOptions, planFor } from './plan';\nimport {\n\tallocate,\n\tisPortFree,\n\tkeptPorts,\n\ttype PortAssignments,\n\ttype PortProbe,\n} from './ports';\nimport {\n\ttype Applied,\n\tapplyBuckets,\n\tapplyCarriers,\n\tapplyPolicies,\n\tapplyPostgres,\n\ttype BucketClient,\n\tbucketNames,\n\tbucketPolicies,\n\ttype CarrierClient,\n\tcarrierNames,\n\tdatabasesExist,\n\tpostgresDatabaseNames,\n\tpostgresStatements,\n\ttype SqlClient,\n} from './provision';\nimport { loadState, planHash, saveState } from './state';\n\nexport type { ComposeFile } from './compose';\nexport type { Plan, PlannedResource } from './plan';\nexport type { PortAssignments } from './ports';\n\n/**\n * Where the generated compose file is written, relative to the project root.\n *\n * At the root, beside the project's own `docker-compose.yml`, because the two\n * are one stack: this file is everything the constructs imply and is never\n * edited, that one is anything they cannot express — an image pin, an extra\n * service — and is merged over it, so it wins.\n */\nexport const COMPOSE_PATH = 'docker-compose.constructs.yml';\n\n/** The project's own compose file, merged over the generated one if present. */\nexport const PROJECT_COMPOSE_PATH = 'docker-compose.yml';\n\n/**\n * The local edge's config, beside the compose file that mounts it.\n *\n * Relative in the compose volume, so it resolves against `.gkm/` wherever the\n * project lives.\n */\nexport const CADDYFILE_PATH = '.gkm/Caddyfile';\n\n/**\n * Where one stage's routes live, imported by the file above.\n *\n * Per stage because one edge serves every stage, the same way one Postgres\n * holds `orders` and `orders_test`. A single file would mean `gkm test`\n * deleting the routes `gkm dev` is serving.\n */\nexport const caddySitesPath = (stage: string) =>\n\t`.gkm/caddy-sites/${stage}.caddy`;\n\n/**\n * Where the local edge's root certificate is copied to.\n *\n * Exported so a process can be pointed at it without installing anything:\n * `NODE_EXTRA_CA_CERTS` is the whole of the trust story for Node, and it needs\n * no sudo. A browser is the case that still wants the root in the system store,\n * which is a one-time `caddy trust` rather than something a reconcile should do\n * on a developer's behalf.\n */\nexport const LOCAL_CA_PATH = '.gkm/caddy-root.crt';\n\n/** Where Caddy keeps the root of the CA it generated. */\nconst CADDY_ROOT_IN_CONTAINER = '/data/caddy/pki/authorities/local/root.crt';\n\n/**\n * The Docker operations reconcile needs.\n *\n * Small on purpose: everything else about containers Docker already remembers,\n * so there is no state here to keep in step with it.\n */\nexport interface Docker {\n\t/**\n\t * The host port a running container publishes for one of its internal ports,\n\t * or `undefined` if it is not running.\n\t *\n\t * Consulted before the saved assignments so that reconcile converges against\n\t * what is actually running rather than only against its own file — a\n\t * container someone started by hand is still the container serving the app.\n\t */\n\tpublishedPort(\n\t\tcomposePath: string,\n\t\tservice: string,\n\t\tinside: number,\n\t): Promise<number | undefined>;\n\t/**\n\t * Bring the named services up, detached, and wait for their health checks.\n\t *\n\t * `show` lets Compose draw its own progress — the pulls, the containers it\n\t * creates, each one turning healthy — rather than capturing it. A first\n\t * start pulls every image and can take minutes, and minutes of nothing\n\t * read as a hang.\n\t */\n\tup(\n\t\tcomposePath: string,\n\t\tservices: readonly string[],\n\t\toptions?: { show?: boolean },\n\t): Promise<void>;\n\t/** Whether every named service is running and passing its health check. */\n\thealthy(composePath: string, services: readonly string[]): Promise<boolean>;\n\t/**\n\t * Copy a file out of a running container.\n\t *\n\t * One case: the certificate authority the local edge generates. It lives in\n\t * the container's volume, and everything that has to *trust* it — Node, the\n\t * test suite, curl — lives outside.\n\t */\n\tcopyOut(\n\t\tcomposePath: string,\n\t\tservice: string,\n\t\tfrom: string,\n\t\tto: string,\n\t): Promise<void>;\n\t/**\n\t * Ask a running service to re-read its configuration.\n\t *\n\t * The local edge is the case: its routes are a mounted file, and a container\n\t * that is already up will not notice one changing.\n\t */\n\treload(composePath: string, service: string): Promise<void>;\n}\n\nexport interface ReconcileOptions {\n\t/** The project root — where `.gkm/` lives. */\n\troot: string;\n\t/**\n\t * Told each slow step as it starts — starting the containers, creating what\n\t * the constructs declare — and, given one, Docker shows its own progress.\n\t * Absent, reconcile is silent: `gkm test` and the converged case say\n\t * nothing.\n\t */\n\tprogress?: (message: string) => void;\n\t/**\n\t * The project name, which seeds port allocation and names the compose\n\t * project. Two checkouts of the same repo share it deliberately.\n\t */\n\tproject: string;\n\t/** The construct manifest — what the app declared. */\n\tmanifest: ConstructManifest;\n\t/** The stage being reconciled: the project's local stage, or `test`. */\n\tstage: string;\n\t/**\n\t * This machine's LAN address, for a mobile app's `exp://` origins and the\n\t * address its sign-in links are built on. Read from the network interfaces\n\t * when absent; `null` for none.\n\t */\n\tlanAddress?: string | null;\n\t/** Each mobile app's Metro port, by construct id. */\n\tmetroPorts?: Readonly<Record<string, number>>;\n\t/** The project's local stage, whose resources carry no suffix. */\n\tlocalStage?: string;\n\t/** The events backend, until `topic` and `queue` are kinds. */\n\tevents?: EventsBackend;\n\t/** Where a declared cache lives — see {@link CacheBackend}. */\n\tcache?: CacheBackend;\n\t/** Containers no construct implies — the config exceptions. */\n\textraContainers?: readonly string[];\n\t/** Each external API's fake, by id — see `readFakes`. */\n\tfakes?: PlanOptions['fakes'];\n\t/** Whether the local edge fronts surfaces, sites and file servers. */\n\tedge?: boolean;\n\t/**\n\t * The apps, as services beside the containers — see `reconcile/apps.ts`.\n\t * Handed the derived containers so an app can depend on them.\n\t */\n\tapps?: (containers: readonly string[]) => Record<string, ComposeService>;\n\t/** Ports assigned by previous runs, from `.gkm/ports.json`. */\n\tsaved?: PortAssignments;\n\t/** Start containers and wait for health. Off answers \"what would change\". */\n\tstart?: boolean;\n\t/**\n\t * Create the databases, schemas, and buckets the plan names.\n\t *\n\t * Requires the containers to be running, so it is skipped when `start` is\n\t * off. Never seeds, resets, or drops — the line is at data, not side effects.\n\t */\n\tprovision?: boolean;\n\t/** The address mail is sent from locally. */\n\tmailFrom?: string;\n\t/**\n\t * Where each address-owning construct answers, keyed by id.\n\t *\n\t * Surfaces and sites. Assigned by whatever starts them, which is why it\n\t * arrives here rather than being read off a published container port.\n\t */\n\taddresses?: Readonly<Record<string, string>>;\n\tdocker?: Docker;\n\tprobe?: PortProbe;\n\t/** Injected for tests; the defaults talk to the containers just started. */\n\tsql?: (port: number) => SqlClient;\n\tbuckets?: (port: number) => BucketClient;\n\tcarriers?: (port: number) => CarrierClient;\n}\n\nexport interface ReconcileResult {\n\tstage: string;\n\tplan: Plan;\n\tcompose: ComposeFile;\n\t/** Every assigned port, keyed by port key. Persist this. */\n\tports: PortAssignments;\n\t/** Every port the containers publish, labelled — consoles and inbox too. */\n\tservices: readonly ServiceAddress[];\n\t/** The `<NAME>_URL` values this stage resolves. */\n\tenv: Readonly<Record<string, string>>;\n\t/** What the applier created, or found already there. */\n\tprovisioned: Applied[];\n\t/**\n\t * Each external API's fake, by id. An image fake is already running with\n\t * the containers; an app fake is the caller's to serve — `gkm dev` does —\n\t * on `ports[fakeKey(id)]`.\n\t */\n\tfakes: Readonly<Record<string, LocalFake>>;\n\t/** The hash recorded for this state. */\n\thash: string;\n\t/**\n\t * Whether anything was applied. False is the fast path — the recorded hash\n\t * matched and the containers were healthy, so nothing was written or started.\n\t */\n\tchanged: boolean;\n}\n\n/**\n * Converge the local target on what the manifest declares.\n *\n * Cheap when there is nothing to do: the hash is compared before the compose\n * file is written or a container is touched, and a converged project pays one\n * hash and one health check.\n */\nexport async function reconcile(\n\toptions: ReconcileOptions,\n): Promise<ReconcileResult> {\n\tconst {\n\t\troot,\n\t\tproject,\n\t\tmanifest,\n\t\tstage,\n\t\tstart = true,\n\t\tprovision = true,\n\t\tdocker = dockerCli,\n\t\tprobe = isPortFree,\n\t\tsql = pgClient,\n\t\tbuckets = bucketClient,\n\t\tcarriers = carrierClient,\n\t} = options;\n\n\tconst plan = planFor(manifest, stage, provisionOrder(manifest), {\n\t\tlocalStage: options.localStage,\n\t\tevents: options.events,\n\t\tcache: options.cache,\n\t\textraContainers: options.extraContainers,\n\t\t...(options.fakes ? { fakes: options.fakes } : {}),\n\t\t...(options.edge === undefined ? {} : { edge: options.edge }),\n\t});\n\n\tconst composePath = join(root, COMPOSE_PATH);\n\n\t// What is already running wins over what was recorded: a container on a port\n\t// is the fact, and the file is only a memory of one.\n\tconst observed = await observedPorts(\n\t\tdocker,\n\t\tcomposePath,\n\t\tplan.containers,\n\t\tplan.fakes,\n\t);\n\tconst ports = await allocate(\n\t\tproject,\n\t\tportKeys(plan.containers, plan.fakes),\n\t\tkeptPorts(options.saved, observed),\n\t\tprobe,\n\t);\n\n\tconst compose = composeFor(plan, {\n\t\tproject,\n\t\tports,\n\t\t...(options.apps ? { apps: options.apps(plan.containers) } : {}),\n\t});\n\n\t// The edge's config is part of what \"converged\" means: a file server added or\n\t// a bucket renamed changes the routing without changing a container, and a\n\t// hash that ignored it would leave the old routes in place.\n\tconst caddyfile = toCaddyfile(sitesFor(plan, project, options.addresses));\n\t// And so is what provisioning runs. A toolbox upgrade that changes the role\n\t// DDL — a new grant — changes no container and no route, so a hash without\n\t// it reported convergence and the existing database never got the grant.\n\tconst postgres = postgresStatements(plan, project)\n\t\t.map((statement) => statement.create)\n\t\t.join(';\\n');\n\tconst hash = planHash(plan, compose, { caddyfile, postgres });\n\tconst services = serviceAddresses(plan.containers, ports, plan.fakes);\n\t// Only a project with a mobile app reads it, so only one looks for it.\n\tconst lan = plan.resources.some((r) => r.kind === 'mobile-app')\n\t\t? options.lanAddress === undefined\n\t\t\t? lanAddress()\n\t\t\t: (options.lanAddress ?? undefined)\n\t\t: undefined;\n\tconst env = envFor(plan, {\n\t\tports,\n\t\tproject,\n\t\t...(options.mailFrom ? { mailFrom: options.mailFrom } : {}),\n\t\t...(options.addresses ? { addresses: options.addresses } : {}),\n\t\t...(lan ? { lanAddress: lan } : {}),\n\t\t...(options.metroPorts ? { metroPorts: options.metroPorts } : {}),\n\t});\n\t// Pointed at whether or not it exists yet: the copy below fills it in, and\n\t// anything that reads the environment starts after this returns.\n\tif (plan.containers.includes('caddy')) {\n\t\tenv.NODE_EXTRA_CA_CERTS = join(root, LOCAL_CA_PATH);\n\t}\n\n\tconst result: ReconcileResult = {\n\t\tstage,\n\t\tplan,\n\t\tcompose,\n\t\tports,\n\t\tservices,\n\t\tenv,\n\t\tprovisioned: [],\n\t\tfakes: options.fakes ?? {},\n\t\thash,\n\t\tchanged: false,\n\t};\n\n\tconst recorded = await loadState(root, stage);\n\tconst postgresPort = ports[primaryPortKey('postgres')];\n\tconst converged =\n\t\trecorded?.hash === hash &&\n\t\t// The file is gitignored and derived; a fresh checkout with the state\n\t\t// still around must write it rather than trust a hash of nothing.\n\t\texistsSync(composePath) &&\n\t\t(!start || (await docker.healthy(composePath, plan.containers))) &&\n\t\t// Every checkout shares one Postgres, so another one's test teardown can\n\t\t// drop a database this checkout's state still records as created.\n\t\t(!start ||\n\t\t\t!provision ||\n\t\t\tpostgresPort === undefined ||\n\t\t\t(await databasesExist(sql(postgresPort), postgresDatabaseNames(plan))));\n\n\t// The fast path, and the reason reconciling on every start is acceptable.\n\tif (converged) return result;\n\n\tawait write(composePath, toYaml(compose));\n\n\t// Before the containers: Caddy reads these at startup, and mounting a file\n\t// that does not exist yet gets a directory instead.\n\tif (plan.containers.includes('caddy')) {\n\t\tawait write(join(root, CADDYFILE_PATH), caddyfileRoot());\n\t\tawait write(join(root, caddySitesPath(stage)), caddyfile);\n\t\tawait pruneCaddySites(root, [stage, options.localStage, TEST_STAGE]);\n\t}\n\n\tif (start && plan.containers.length > 0) {\n\t\toptions.progress?.(\n\t\t\t`🐳 Starting ${plan.containers.join(', ')} — the first start pulls each image, which can take a few minutes`,\n\t\t);\n\t\tawait docker.up(\n\t\t\tcomposePath,\n\t\t\tplan.containers,\n\t\t\toptions.progress ? { show: true } : {},\n\t\t);\n\t}\n\n\t// A running Caddy does not notice a changed import, so the routes this\n\t// reconcile just wrote are inert until it is told. Failing is not fatal: the\n\t// container may have only this second come up, in which case it already read\n\t// them.\n\tif (start && plan.containers.includes('caddy')) {\n\t\tawait docker.reload(composePath, 'caddy').catch(() => {});\n\t}\n\n\t// After `up`, because Caddy generates its CA on first start — and only then,\n\t// so copying earlier gets nothing.\n\tif (start && plan.containers.includes('caddy')) {\n\t\tawait docker\n\t\t\t.copyOut(\n\t\t\t\tcomposePath,\n\t\t\t\t'caddy',\n\t\t\t\tCADDY_ROOT_IN_CONTAINER,\n\t\t\t\tjoin(root, LOCAL_CA_PATH),\n\t\t\t)\n\t\t\t.catch(() => {\n\t\t\t\t// Not fatal. Without it, `https://` local addresses fail to verify\n\t\t\t\t// and say so clearly; taking the whole reconcile down would be a\n\t\t\t\t// worse trade for a developer who is not using them yet.\n\t\t\t});\n\t}\n\n\t// Only once the containers are up: there is nothing to create inside a\n\t// container that is not running.\n\tif (start && provision) {\n\t\toptions.progress?.(\n\t\t\t'🗄️ Creating what the constructs declare — databases, roles, buckets, topics',\n\t\t);\n\t}\n\tconst provisioned =\n\t\tstart && provision\n\t\t\t? await create(plan, ports, sql, buckets, carriers, project)\n\t\t\t: [];\n\n\tawait saveState(root, { hash, stage });\n\n\treturn { ...result, provisioned, changed: true };\n}\n\n/**\n * Create the databases, schemas, buckets, topics and queues the plan names.\n *\n * Idempotent throughout, so this runs on every non-converged reconcile rather\n * than being something to remember.\n */\nasync function create(\n\tplan: Plan,\n\tports: PortAssignments,\n\tsql: (port: number) => SqlClient,\n\tbuckets: (port: number) => BucketClient,\n\temulatorCarriers: (port: number) => CarrierClient,\n\t/** Seeds the derived role passwords — see `localRolePassword`. */\n\tproject: string,\n): Promise<Applied[]> {\n\tconst applied: Applied[] = [];\n\n\tconst postgresPort = ports[primaryPortKey('postgres')];\n\tconst statements = postgresStatements(plan, project);\n\tif (postgresPort !== undefined && statements.length > 0) {\n\t\tapplied.push(...(await applyPostgres(sql(postgresPort), statements)));\n\t}\n\n\tconst minioPort = ports[primaryPortKey('minio')];\n\tconst names = bucketNames(plan);\n\tconst policies = bucketPolicies(plan);\n\tif (minioPort !== undefined && (names.length > 0 || policies.length > 0)) {\n\t\tconst client = buckets(minioPort);\n\n\t\t// Buckets first: a policy names a bucket, and applying one to a bucket\n\t\t// that does not exist yet fails on the first reconcile of a new project.\n\t\tif (names.length > 0) applied.push(...(await applyBuckets(client, names)));\n\t\tif (policies.length > 0)\n\t\t\tapplied.push(...(await applyPolicies(client, policies)));\n\t}\n\n\tconst emulatorPort = ports[primaryPortKey('localstack')];\n\tconst carriers = carrierNames(plan);\n\tif (\n\t\temulatorPort !== undefined &&\n\t\tcarriers.topics.length + carriers.queues.length > 0\n\t) {\n\t\tapplied.push(\n\t\t\t...(await applyCarriers(emulatorCarriers(emulatorPort), carriers)),\n\t\t);\n\t}\n\n\treturn applied;\n}\n\n/**\n * The host ports of containers already running.\n *\n * Keyed the same way allocation keys them, so the two merge without either\n * knowing about the other.\n */\nasync function observedPorts(\n\tdocker: Docker,\n\tcomposePath: string,\n\tcontainers: readonly string[],\n\tfakes?: Plan['fakes'],\n): Promise<PortAssignments> {\n\tconst observed: Record<string, number> = {};\n\n\tfor (const container of containers) {\n\t\tfor (const port of portsOf(container, fakes)) {\n\t\t\tconst running = await docker.publishedPort(\n\t\t\t\tcomposePath,\n\t\t\t\tcontainer,\n\t\t\t\tport.inside,\n\t\t\t);\n\t\t\tif (running !== undefined) observed[port.key] = running;\n\t\t}\n\t}\n\n\treturn observed;\n}\n\n/** Write a file only when its content would change, so mtimes stay meaningful. */\n/**\n * Remove the routes of stages this project no longer reconciles.\n *\n * Only the local stage and `test` are ever written, so any other file is left\n * over from a stage that was renamed — `development` becoming `dev`. Caddy\n * imports every file, and the leftover declares the same hosts as its\n * replacement: it refuses the whole config as ambiguous, and every URL behind\n * the edge goes with it.\n */\nasync function pruneCaddySites(\n\troot: string,\n\tstages: ReadonlyArray<string | undefined>,\n): Promise<void> {\n\tconst dir = dirname(join(root, caddySitesPath(TEST_STAGE)));\n\tconst keep = new Set(stages.map((stage) => stage && `${stage}.caddy`));\n\n\tfor (const file of await readdir(dir)) {\n\t\tif (file.endsWith('.caddy') && !keep.has(file)) {\n\t\t\tawait rm(join(dir, file));\n\t\t}\n\t}\n}\n\nasync function write(path: string, content: string): Promise<void> {\n\ttry {\n\t\tif ((await readFile(path, 'utf-8')) === content) return;\n\t} catch {\n\t\t// Absent or unreadable — write it.\n\t}\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, content);\n}\n","/**\n * Workspace config → reconcile.\n *\n * Nothing here is read from a `services:` block — there is none. Which\n * containers exist comes from the manifest; which backend a cache or a broker\n * resolves to comes from the deploy target; and an image pin is the project's\n * own `docker-compose.yml`, merged over the generated file.\n *\n * Shared by `gkm setup`, `gkm dev`, and `gkm test` so all three converge on the\n * same state, differing only in the stage they pass.\n */\n\nimport { type ConstructManifest, provisionOrder } from '@geekmidas/manifest';\nimport { loadPortState, savePortState } from '../credentials/index.js';\nimport {\n\tcacheBackendFor,\n\teventsBackendFor,\n\tproviderOf,\n} from '../workspace/backends.js';\nimport { appKey } from '../workspace/derive.js';\nimport { allConstructGlobs } from '../workspace/index.js';\nimport { TEST_STAGE } from '../workspace/stages.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport { appServices } from './apps.js';\nimport { discover } from './discover.js';\nimport { readFakes } from './fakes.js';\nimport { type ReconcileResult, reconcile } from './index.js';\nimport { planFor } from './plan.js';\n\n/**\n * Every constructs glob in the workspace, resolved against its app.\n *\n * Absolute because discovery globs from a single cwd while apps live in\n * different directories.\n */\nexport function constructGlobs(workspace: NormalizedWorkspace): string[] {\n\treturn allConstructGlobs(workspace);\n}\n\n/** The backends a workspace's target implies, as the plan takes them. */\nexport function backendsOf(workspace: NormalizedWorkspace) {\n\tconst on = providerOf(workspace);\n\treturn { events: eventsBackendFor(on), cache: cacheBackendFor(on) };\n}\n\n/**\n * The containers a workspace's declarations imply, without reconciling.\n *\n * The same derivation `reconcileWorkspace` makes, available to callers that\n * need to know *what would run* without starting anything or writing ports —\n * `gkm setup` deciding which credentials to generate, `gkm docker` writing a\n * compose file. Those used to each read a boolean per service out of config,\n * which is how a container could exist because config asked for one rather than\n * because something declared it.\n */\nexport async function derivedContainers(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\tmanifest?: ConstructManifest,\n): Promise<readonly string[]> {\n\tconst found =\n\t\tmanifest ??\n\t\t(await discover({\n\t\t\tpatterns: constructGlobs(workspace),\n\t\t\tcwd: workspace.root,\n\t\t}));\n\n\t// No fakes: they run only for `gkm test` and `gkm dev --fake`, and the\n\t// commands asking this — `setup`, `secrets:*`, `docker` — are neither.\n\treturn planFor(found, stage, provisionOrder(found), {\n\t\tlocalStage: workspace.stages.local,\n\t\t...backendsOf(workspace),\n\t}).containers;\n}\n\nexport interface WorkspaceReconcileOptions {\n\tstage: string;\n\t/** Start containers and wait for health. */\n\tstart?: boolean;\n\t/** A manifest already in hand — the dev watcher has one; setup does not. */\n\tmanifest?: ConstructManifest;\n\t/**\n\t * Point every external API at its fake (`test/fakes/<id>.ts`) instead of\n\t * the provider — `gkm dev --fake`. Always so for the test stage; otherwise\n\t * an external API is the real one, at its URL for this stage.\n\t */\n\tfake?: boolean;\n\t/** Told each slow step as it starts — see `ReconcileOptions.progress`. */\n\tprogress?: (message: string) => void;\n}\n\n/**\n * Set by `gkm dev --fake` for the apps it starts. Each app's own `gkm dev`\n * reconciles too, and must answer the way the workspace did.\n */\nexport const FAKE_ENV = 'GKM_FAKE';\n\n/**\n * Whether external APIs are faked: always for `gkm test`, and for `gkm dev`\n * only when asked — by `--fake`, or by the workspace that started this app\n * with it. Never for anything acting on a deployed stage.\n */\nexport function fakesApply(\n\tstage: string,\n\tfake?: boolean,\n\tenv: NodeJS.ProcessEnv = process.env,\n): boolean {\n\treturn fake === true || stage === TEST_STAGE || env[FAKE_ENV] === '1';\n}\n\n/**\n * Reconcile a workspace for one stage.\n *\n * Ports are loaded and saved around the call rather than inside it, so the loop\n * stays a pure-ish function of its inputs and the store keeps its single owner.\n */\nexport async function reconcileWorkspace(\n\tworkspace: NormalizedWorkspace,\n\toptions: WorkspaceReconcileOptions,\n): Promise<ReconcileResult & { manifest: ConstructManifest }> {\n\t// Discovered here even when a manifest was handed in: the apps' services\n\t// are given the keys their runnables reach, and only discovery sees those.\n\tconst runnables: Record<string, string[]> = {};\n\tconst discovered = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\trunnables,\n\t});\n\tconst manifest = options.manifest ?? discovered;\n\tconst fakes = fakesApply(options.stage, options.fake)\n\t\t? await readFakes(workspace.root, manifest)\n\t\t: {};\n\n\tconst result = await reconcile({\n\t\troot: workspace.root,\n\t\tproject: workspace.name,\n\t\tmanifest,\n\t\tstage: options.stage,\n\t\tlocalStage: workspace.stages.local,\n\t\t...backendsOf(workspace),\n\t\tsaved: await loadPortState(workspace.root),\n\t\taddresses: surfaceAddresses(workspace, manifest),\n\t\tmetroPorts: metroPorts(workspace, manifest),\n\t\tfakes,\n\t\tapps: (containers) =>\n\t\t\tappServices(workspace, manifest, containers, runnables, fakes),\n\t\t...(options.start === undefined ? {} : { start: options.start }),\n\t\t...(options.progress ? { progress: options.progress } : {}),\n\t});\n\n\tawait savePortState(workspace.root, { ...result.ports });\n\n\t// With the manifest, so `gkm dev` can say what the workspace declares\n\t// without discovering it a second time.\n\treturn { ...result, manifest };\n}\n\n/**\n * Where each mobile app's Metro runs: its app's port, which `gkm exec` hands\n * Expo as `RCT_METRO_PORT`. Kept apart from the surfaces' addresses — a phone\n * reaches Metro by `exp://`, not through the edge.\n */\nexport function metroPorts(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n): Record<string, number> {\n\tconst ports: Record<string, number> = {};\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration.kind !== 'mobile-app') continue;\n\t\tconst port = workspace.apps[appKey(id)]?.port;\n\t\tif (port) ports[id] = port;\n\t}\n\treturn ports;\n}\n\n/**\n * Where each declared surface and site answers locally.\n *\n * The addresses come from the ports the workspace already assigns; which\n * construct sits at which one comes from the manifest. That split is the point:\n * this used to answer \"who may call whom\" by listing every app the workspace\n * runs, which is a different question that happened to give the same answer in\n * a single-repo workspace and no answer at all deployed. Now it answers only\n * \"where does this construct answer\", and the graph answers the rest.\n *\n * A site is matched to its app by `path`, because that is the one thing a site\n * declaration and a workspace app both name. Surfaces are matched to the\n * backend app: every `rest-api` in a process answers on that process's port, so\n * an app serving both its own API and an auth server publishes one address\n * twice — which is exactly what it does at runtime.\n */\nexport function surfaceAddresses(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\t/**\n\t * How an app is addressed: on the host by default, or — for the compose\n\t * apps — by service name on the compose network.\n\t */\n\tat: (app: string, port: number) => string = (_app, port) =>\n\t\tlocalAddress(port),\n): Record<string, string> {\n\tconst addresses: Record<string, string> = {};\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration.kind !== 'rest-api' && declaration.kind !== 'site')\n\t\t\tcontinue;\n\n\t\t// Every site and every surface is its own app, keyed by its id the way\n\t\t// the derivation keyed it.\n\t\tconst host = appKey(id);\n\t\tconst port = workspace.apps[host]?.port;\n\n\t\tif (port) addresses[id] = at(host, port);\n\t}\n\n\treturn addresses;\n}\n\n/** Where a local process answers, given the port the workspace gave it. */\nfunction localAddress(port: number): string {\n\treturn `http://localhost:${port}`;\n}\n","/**\n * Start a program with an argument array, never a shell string.\n *\n * `gkm build`, `gkm docker` and `gkm deploy` pass values that come from the\n * workspace — image refs, tags, build args, package names — to `docker` and\n * `turbo`. Joined into a string and handed to a shell, a package named\n * `x;curl …|sh` or a tag holding `$(…)` became part of the command line, which\n * matters most where the CLI builds a repository it didn't write: a CI job for a\n * fork, a shared build runner. Here every value reaches the program as one\n * `argv` element, whatever it contains.\n */\n\nimport { type SpawnOptions, spawn } from 'node:child_process';\n\n/** Long enough for a cold image build; short enough that a hung one ends. */\nexport const DEFAULT_TIMEOUT_MS = 30 * 60_000;\n\n/** How long a timed-out child has to exit on SIGTERM before it is killed. */\nconst KILL_GRACE_MS = 5_000;\n\nexport interface RunOptions {\n\tcwd?: string;\n\tenv?: NodeJS.ProcessEnv;\n\tstdio?: SpawnOptions['stdio'];\n\t/** Kill the child after this long. Defaults to {@link DEFAULT_TIMEOUT_MS}. */\n\ttimeoutMs?: number;\n\t/**\n\t * Kills the child when aborted, and rejects with the signal's reason — the\n\t * same as a stopped deploy's other calls.\n\t */\n\tsignal?: AbortSignal;\n}\n\n/** The command line as a person would read it — for messages, never to run. */\nfunction display(command: string, args: readonly string[]): string {\n\treturn [command, ...args].join(' ');\n}\n\n/** The program ran and exited unsuccessfully; its own output said why. */\nexport class CommandFailed extends Error {\n\tconstructor(\n\t\treadonly command: string,\n\t\treadonly args: readonly string[],\n\t\treadonly exitCode: number | null,\n\t\treadonly signal: NodeJS.Signals | null,\n\t) {\n\t\tsuper(\n\t\t\t`\\`${display(command, args)}\\` ${exitCode === null ? `was stopped by ${signal ?? 'a signal'}` : `exited with code ${exitCode}`}. Its output above says why; run it by hand to see it again.`,\n\t\t);\n\t\tthis.name = 'CommandFailed';\n\t}\n}\n\n/** The program ran past its timeout and was killed. */\nexport class CommandTimedOut extends Error {\n\tconstructor(\n\t\treadonly command: string,\n\t\treadonly args: readonly string[],\n\t\treadonly timeoutMs: number,\n\t) {\n\t\tsuper(\n\t\t\t`\\`${display(command, args)}\\` was still running after ${Math.round(timeoutMs / 1000)}s and was stopped. Check what it was waiting on — a registry, the Docker daemon, a prompt — and run it again.`,\n\t\t);\n\t\tthis.name = 'CommandTimedOut';\n\t}\n}\n\n/**\n * Run `command` with `args`, resolving when it exits 0.\n *\n * Rejects with {@link CommandFailed} on any other exit, {@link CommandTimedOut}\n * once `timeoutMs` passes (after the child is gone), and with the spawn error\n * itself when the program cannot be started at all — `ENOENT` already names it.\n */\nexport async function run(\n\tcommand: string,\n\targs: readonly string[],\n\toptions: RunOptions = {},\n): Promise<void> {\n\tawait spawnChild(command, args, options, false);\n}\n\n/**\n * {@link run}, resolving with what the program wrote to stdout — for a value\n * the CLI reads back, such as the digest `docker inspect` reports. stderr still\n * reaches the terminal, so a failure explains itself the same way.\n */\nexport function runOutput(\n\tcommand: string,\n\targs: readonly string[],\n\toptions: Omit<RunOptions, 'stdio'> = {},\n): Promise<string> {\n\treturn spawnChild(command, args, options, true);\n}\n\nfunction spawnChild(\n\tcommand: string,\n\targs: readonly string[],\n\toptions: RunOptions,\n\tcapture: boolean,\n): Promise<string> {\n\tconst { timeoutMs = DEFAULT_TIMEOUT_MS, ...spawnOptions } = options;\n\n\treturn new Promise<string>((resolve, reject) => {\n\t\tconst child = spawn(command, [...args], {\n\t\t\tstdio: 'inherit',\n\t\t\t...spawnOptions,\n\t\t\t...(capture ? { stdio: ['ignore', 'pipe', 'inherit'] } : {}),\n\t\t\tshell: false,\n\t\t});\n\n\t\tlet stdout = '';\n\t\tchild.stdout?.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\n\t\tlet timedOut = false;\n\t\tlet killer: NodeJS.Timeout | undefined;\n\t\tconst timer = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tchild.kill('SIGTERM');\n\t\t\t// A child that ignores SIGTERM would hold the deploy open forever.\n\t\t\tkiller = setTimeout(() => child.kill('SIGKILL'), KILL_GRACE_MS);\n\t\t\tkiller.unref?.();\n\t\t}, timeoutMs);\n\t\ttimer.unref?.();\n\n\t\tconst settle = () => {\n\t\t\tclearTimeout(timer);\n\t\t\tif (killer) clearTimeout(killer);\n\t\t};\n\n\t\tchild.on('error', (error) => {\n\t\t\tsettle();\n\t\t\t// Node reports an abort as its own `AbortError`; the caller asked\n\t\t\t// with a reason, and that is what it should get back.\n\t\t\treject(spawnOptions.signal?.aborted ? spawnOptions.signal.reason : error);\n\t\t});\n\n\t\tchild.on('close', (code: number | null, signal: NodeJS.Signals | null) => {\n\t\t\tsettle();\n\t\t\tif (spawnOptions.signal?.aborted) reject(spawnOptions.signal.reason);\n\t\t\telse if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));\n\t\t\telse if (code === 0) resolve(stdout);\n\t\t\telse reject(new CommandFailed(command, args, code, signal));\n\t\t});\n\t});\n}\n","import {\n\tcreateCipheriv,\n\tcreateDecipheriv,\n\tcreateHash,\n\trandomBytes,\n} from 'node:crypto';\nimport type { EmbeddableSecrets, EncryptedPayload } from './types';\n\n/** AES-256-GCM configuration */\nconst ALGORITHM = 'aes-256-gcm';\nconst KEY_LENGTH = 32; // 256 bits\nconst IV_LENGTH = 12; // 96 bits for GCM\nconst AUTH_TAG_LENGTH = 16; // 128 bits\n\n/**\n * Encrypt secrets for embedding in a bundle.\n * Uses AES-256-GCM with a randomly generated ephemeral key.\n *\n * @param secrets - Key-value pairs to encrypt\n * @returns Encrypted payload with ephemeral master key\n */\nexport function encryptSecrets(secrets: EmbeddableSecrets): EncryptedPayload {\n\t// Generate ephemeral key and IV\n\tconst masterKey = randomBytes(KEY_LENGTH);\n\tconst iv = randomBytes(IV_LENGTH);\n\n\t// Serialize secrets to JSON\n\tconst plaintext = JSON.stringify(secrets);\n\n\t// Encrypt\n\tconst cipher = createCipheriv(ALGORITHM, masterKey, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(plaintext, 'utf-8'),\n\t\tcipher.final(),\n\t]);\n\n\t// Get auth tag\n\tconst authTag = cipher.getAuthTag();\n\n\t// Combine ciphertext + auth tag\n\tconst combined = Buffer.concat([ciphertext, authTag]);\n\n\treturn {\n\t\tencrypted: combined.toString('base64'),\n\t\tiv: iv.toString('hex'),\n\t\tmasterKey: masterKey.toString('hex'),\n\t};\n}\n\n/**\n * Decrypt secrets from an encrypted payload.\n * Used at runtime to decrypt embedded credentials.\n *\n * @param encrypted - Base64 encoded ciphertext + auth tag\n * @param iv - Hex encoded IV\n * @param masterKey - Hex encoded master key\n * @returns Decrypted secrets\n */\nexport function decryptSecrets(\n\tencrypted: string,\n\tiv: string,\n\tmasterKey: string,\n): EmbeddableSecrets {\n\t// Decode inputs\n\tconst key = Buffer.from(masterKey, 'hex');\n\tconst ivBuffer = Buffer.from(iv, 'hex');\n\tconst combined = Buffer.from(encrypted, 'base64');\n\n\t// Split ciphertext and auth tag\n\tconst ciphertext = combined.subarray(0, -AUTH_TAG_LENGTH);\n\tconst authTag = combined.subarray(-AUTH_TAG_LENGTH);\n\n\t// Decrypt\n\tconst decipher = createDecipheriv(ALGORITHM, key, ivBuffer);\n\tdecipher.setAuthTag(authTag);\n\n\tconst plaintext = Buffer.concat([\n\t\tdecipher.update(ciphertext),\n\t\tdecipher.final(),\n\t]);\n\n\treturn JSON.parse(plaintext.toString('utf-8')) as EmbeddableSecrets;\n}\n\n/**\n * Generate the define options for tsdown/esbuild.\n * These will be injected at build time.\n */\nexport function generateDefineOptions(\n\tpayload: EncryptedPayload,\n): Record<string, string> {\n\treturn {\n\t\t__GKM_ENCRYPTED_CREDENTIALS__: JSON.stringify(payload.encrypted),\n\t\t__GKM_CREDENTIALS_IV__: JSON.stringify(payload.iv),\n\t};\n}\n\n/**\n * A short, one-way name for a master key: the first 8 hex characters of its\n * SHA-256.\n *\n * Printed where the key itself used to be, so a person can tell which key a\n * build used — and match it against the one they hold — without the output\n * carrying a value that decrypts every secret of the stage.\n */\nexport function keyFingerprint(masterKey: string): string {\n\treturn createHash('sha256').update(masterKey).digest('hex').slice(0, 8);\n}\n","/**\n * The values a deployed stage generates once and keeps: what nothing can\n * provision and nobody else issues.\n *\n * - **A seed**, which salts every password a deploy derives — a database's\n * master and roles, a bucket's root user. Derived from repo facts alone\n * (project, stage, construct id), those passwords were computable by anyone\n * who could read the repo.\n * - **Each `secret` construct's value** — an auth server's signing secret —\n * random, rather than derived.\n * - **Each `encryption` construct's keyring** — one random key and an index\n * key. Later keys are added by `gkm encryption:rotate`, never by a deploy.\n *\n * Both are generated on the first deploy that needs them and written to the\n * stage's store, so every later deploy reads the same values: sessions survive\n * a redeploy, and a role's password does not move. A value set by hand with\n * `gkm secrets:set` is kept, never replaced.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { generateKeyring } from '@geekmidas/constructs/encryption';\nimport type { ConstructManifest } from '@geekmidas/manifest';\nimport type { StageSecrets } from '../secrets/types.js';\n\nexport interface GeneratedSecrets {\n\t/** The stage's secrets with everything it generates present. */\n\tsecrets: StageSecrets;\n\t/** What was generated this time, by key — empty when nothing was missing. */\n\tgenerated: string[];\n}\n\n/**\n * The stage's secrets with its seed and every declared `secret`'s value\n * present, generating what is missing. Pure: the caller writes the result\n * back to the stage's store when `generated` is not empty.\n */\nexport function withGeneratedSecrets(\n\tsecrets: StageSecrets,\n\tmanifest: ConstructManifest,\n): GeneratedSecrets {\n\tconst generated: string[] = [];\n\tconst custom = { ...secrets.custom };\n\n\tfor (const declaration of Object.values(manifest)) {\n\t\tif (declaration?.kind !== 'secret' && declaration?.kind !== 'encryption') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst key = declaration.provides?.[0];\n\t\tif (!key || custom[key]) continue;\n\n\t\tcustom[key] =\n\t\t\tdeclaration.kind === 'encryption' ? generateKeyring() : random();\n\t\tgenerated.push(key);\n\t}\n\n\tconst seed = secrets.seed ?? random();\n\tif (!secrets.seed) generated.push('seed');\n\n\treturn {\n\t\tsecrets: generated.length\n\t\t\t? { ...secrets, seed, custom, updatedAt: new Date().toISOString() }\n\t\t\t: secrets,\n\t\tgenerated,\n\t};\n}\n\n/** 256 bits, URL-safe. */\nfunction random(): string {\n\treturn randomBytes(32).toString('base64url');\n}\n","/**\n * State configuration, and the `StateProvider` interface a custom backend can\n * implement.\n *\n * Deploy state is read and written through a `StateStore` (`StateStore.ts`):\n * locks, versions and resource records. A custom `StateProvider` — read and\n * write a whole state, nothing else — still works behind `LegacyStateStore`,\n * which warns that it cannot lock.\n */\n\nimport type { StateStore } from './StateStore';\nimport type { DokployStageState } from './state';\n\n/**\n * Interface for deployment state storage providers.\n *\n * Implementations must handle:\n * - Reading state for a stage (returns null if not found)\n * - Writing state for a stage (creates or updates)\n */\nexport interface StateProvider {\n\t/**\n\t * Read deployment state for a stage.\n\t *\n\t * @param stage - The deployment stage (e.g., 'development', 'production')\n\t * @returns The state object or null if not found\n\t */\n\tread(stage: string): Promise<DokployStageState | null>;\n\n\t/**\n\t * Write deployment state for a stage.\n\t *\n\t * @param stage - The deployment stage\n\t * @param state - The state object to persist\n\t */\n\twrite(stage: string, state: DokployStageState): Promise<void>;\n}\n\n/**\n * Valid AWS regions.\n */\nexport type AwsRegion =\n\t| 'us-east-1'\n\t| 'us-east-2'\n\t| 'us-west-1'\n\t| 'us-west-2'\n\t| 'af-south-1'\n\t| 'ap-east-1'\n\t| 'ap-south-1'\n\t| 'ap-south-2'\n\t| 'ap-southeast-1'\n\t| 'ap-southeast-2'\n\t| 'ap-southeast-3'\n\t| 'ap-southeast-4'\n\t| 'ap-northeast-1'\n\t| 'ap-northeast-2'\n\t| 'ap-northeast-3'\n\t| 'ca-central-1'\n\t| 'eu-central-1'\n\t| 'eu-central-2'\n\t| 'eu-west-1'\n\t| 'eu-west-2'\n\t| 'eu-west-3'\n\t| 'eu-south-1'\n\t| 'eu-south-2'\n\t| 'eu-north-1'\n\t| 'me-south-1'\n\t| 'me-central-1'\n\t| 'sa-east-1';\n\n/**\n * Local state provider config.\n */\nexport interface LocalStateConfig {\n\tprovider: 'local';\n}\n\n/**\n * SSM state provider config (requires region).\n */\nexport interface SSMStateConfig {\n\tprovider: 'ssm';\n\t/** AWS region (required for SSM provider) */\n\tregion: AwsRegion;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n}\n\n/**\n * S3 state config: one object per stage, written with conditional puts\n * (`If-Match` / `If-None-Match`), and a lock object beside it.\n *\n * Keys: `<prefix>/<workspace name>/<stage>/state.json` and `lock.json`.\n */\nexport interface S3StateConfig {\n\tprovider: 's3';\n\t/** Bucket the state lives in. It must already exist. */\n\tbucket: string;\n\t/** AWS region of the bucket */\n\tregion: AwsRegion;\n\t/** Key prefix inside the bucket (default: `gkm`) */\n\tprefix?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n}\n\n/**\n * Custom state provider config.\n */\nexport interface CustomStateConfig {\n\t/**\n\t * Custom implementation. A `StateStore` is used as is; a `StateProvider`\n\t * still works, but cannot lock — deploy warns `StateStoreWithoutLocking`.\n\t */\n\tprovider: StateProvider | StateStore;\n}\n\n/**\n * State configuration types.\n */\nexport type StateConfig =\n\t| LocalStateConfig\n\t| SSMStateConfig\n\t| S3StateConfig\n\t| CustomStateConfig;\n\n/**\n * Check if value is a StateProvider implementation.\n */\nexport function isStateProvider(value: unknown): value is StateProvider {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\ttypeof (value as StateProvider).read === 'function' &&\n\t\ttypeof (value as StateProvider).write === 'function'\n\t);\n}\n\nexport interface CreateStateStoreConfig {\n\t/** State config from workspace */\n\tconfig?: StateConfig;\n\t/** Workspace root directory (for local provider) */\n\tworkspaceRoot: string;\n\t/** Workspace name (for SSM parameter path) */\n\tworkspaceName: string;\n}\n","/**\n * Deploy state store.\n *\n * `StateProvider` reads and writes a whole stage at once, with nothing to stop\n * two runs of the same stage from overwriting each other and nowhere to record\n * a resource the moment it exists. A `StateStore` adds the three things a\n * deploy needs to be safe against crashes and concurrent runs:\n *\n * - `lock(stage)`: one run per stage. A second gets `StateLocked`, naming the\n * holder, instead of racing the first.\n * - Versioned `read` and conditional `write`: a write names the version it was\n * based on, and raises `StateVersionConflict` when the stored state moved on\n * underneath it, so nothing is lost silently even without the lock.\n * - Per-resource records: `putResource` journals one resource at a time, so a\n * run that dies half way leaves behind the ids of everything it created.\n *\n * Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A\n * custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it\n * working but cannot lock.\n */\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport { hostname, userInfo } from 'node:os';\nimport {\n\ttype CreateStateStoreConfig,\n\tisStateProvider,\n\ttype S3StateConfig,\n\ttype SSMStateConfig,\n} from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/** Opaque: a file hash, an SSM parameter version or an S3 ETag. */\nexport type StateVersion = string;\n\n/**\n * `pending` is written before the call that creates the resource, `ready`\n * once the target has returned its id. A run that finds a `pending` record\n * knows a previous run may have created the resource and should look for it\n * before creating another.\n */\nexport type ResourceStatus = 'pending' | 'ready';\n\nexport interface ResourceRecord {\n\t/** Unique within the stage, `<type>:<name>` — e.g. `application:api`. */\n\tkey: string;\n\t/** What kind of resource it is — e.g. `application`, `postgres`. */\n\ttype: string;\n\t/** The id the target gave it. Absent while `pending`. */\n\tid?: string;\n\tstatus: ResourceStatus;\n\t/** Whatever the target needs to adopt the resource on a later run. */\n\tdata?: Record<string, unknown>;\n\tupdatedAt: string;\n}\n\n/** A resource as a caller hands it to `putResource`; the store stamps it. */\nexport type ResourceInput = Omit<ResourceRecord, 'updatedAt'>;\n\nexport interface StoredStageState {\n\tstate: DokployStageState;\n\tresources: Record<string, ResourceRecord>;\n\t/** Pass back as `expectedVersion` to write on top of exactly this read. */\n\tversion: StateVersion;\n}\n\nexport interface WriteOptions {\n\t/**\n\t * The version this write is based on — `null` to create state that must\n\t * not exist yet.\n\t */\n\texpectedVersion: StateVersion | null;\n}\n\nexport interface ResourceWriteOptions {\n\t/**\n\t * Omitted, the record is applied on top of whatever is stored now (still\n\t * conditionally, so a concurrent write raises rather than being lost).\n\t */\n\texpectedVersion?: StateVersion;\n}\n\nexport interface LockHolder {\n\t/** Unique per acquisition, so a release never removes someone else's lock. */\n\tid: string;\n\towner: string;\n\thost: string;\n\tpid: number;\n\tacquiredAt: string;\n\t/** What the holder is doing, e.g. `deploy`. */\n\toperation?: string;\n}\n\nexport interface LockOptions {\n\toperation?: string;\n}\n\nexport interface StateLock {\n\treadonly stage: string;\n\treadonly holder: LockHolder;\n\t/** Removes the lock if it is still this one; safe to call twice. */\n\trelease(): Promise<void>;\n}\n\nexport interface StateStore {\n\t/** Takes the stage's lock, or raises `StateLocked` naming its holder. */\n\tlock(stage: string, options?: LockOptions): Promise<StateLock>;\n\t/**\n\t * Removes the stage's lock whoever holds it, for a run that crashed with\n\t * it held. Returns the holder it removed, or `null` if there was none.\n\t */\n\tforceUnlock(stage: string): Promise<LockHolder | null>;\n\tread(stage: string): Promise<StoredStageState | null>;\n\t/** Replaces the stage's state, keeping its resource records. */\n\twrite(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion>;\n\tputResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion>;\n\tdeleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion>;\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class StateLocked extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly holder: LockHolder | null,\n\t\treadonly location: string,\n\t) {\n\t\tconst by = holder\n\t\t\t? `${holder.owner}@${holder.host} (pid ${holder.pid}) since ${holder.acquiredAt}${holder.operation ? ` for '${holder.operation}'` : ''}`\n\t\t\t: 'a run that did not record who it was';\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' is locked by ${by} (${location}). ` +\n\t\t\t\t`Wait for that run to finish; if it crashed, release the lock with ` +\n\t\t\t\t`\\`gkm state:unlock --stage ${stage}\\`.`,\n\t\t);\n\t\tthis.name = 'StateLocked';\n\t}\n}\n\nexport class StateVersionConflict extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly expectedVersion: StateVersion | null,\n\t\treadonly actualVersion: StateVersion | null,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' changed since it was read ` +\n\t\t\t\t`(expected ${expectedVersion ? `version ${expectedVersion}` : 'no state'}, ` +\n\t\t\t\t`found ${actualVersion ? `version ${actualVersion}` : 'no state'}). ` +\n\t\t\t\t`Another run wrote it: read the state again and re-apply the change, ` +\n\t\t\t\t`and hold the stage's lock so runs take turns.`,\n\t\t);\n\t\tthis.name = 'StateVersionConflict';\n\t}\n}\n\nexport class StageStateMissing extends Error {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`There is no deploy state for stage '${stage}' to record a resource in. ` +\n\t\t\t\t`Write the stage's state first (write with expectedVersion: null).`,\n\t\t);\n\t\tthis.name = 'StageStateMissing';\n\t}\n}\n\nexport class StateUnreadable extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly location: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' at ${location} is not valid JSON. ` +\n\t\t\t\t`Restore it from a backup (or the provider's history) rather than ` +\n\t\t\t\t`deploying without it — a deploy without state recreates every resource.`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'StateUnreadable';\n\t}\n}\n\nexport class StateSchemaTooNew extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly schemaVersion: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' has schema version ${String(schemaVersion)}, ` +\n\t\t\t\t`newer than this @geekmidas/cli understands (${STATE_SCHEMA_VERSION}). ` +\n\t\t\t\t`Upgrade @geekmidas/cli to the version that wrote it.`,\n\t\t);\n\t\tthis.name = 'StateSchemaTooNew';\n\t}\n}\n\n/**\n * Not thrown: emitted (once per store) when a custom `StateProvider` stands\n * in for a store, so a deploy that cannot be protected from a concurrent one\n * says so.\n */\nexport class StateStoreWithoutLocking extends Error {\n\tconstructor(readonly provider: string) {\n\t\tsuper(\n\t\t\t`The custom state provider '${provider}' has no lock and no versions, ` +\n\t\t\t\t`so two deploys of the same stage can still overwrite each other. ` +\n\t\t\t\t`Implement StateStore (lock, versioned read, conditional write) or ` +\n\t\t\t\t`use the 'local', 'ssm' or 's3' state provider.`,\n\t\t);\n\t\tthis.name = 'StateStoreWithoutLocking';\n\t}\n}\n\nexport class StateStoreNeedsWorkspaceName extends Error {\n\tconstructor(readonly provider: string) {\n\t\tsuper(\n\t\t\t`The '${provider}' state provider keys state by workspace name. ` +\n\t\t\t\t`Set \"name\" in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'StateStoreNeedsWorkspaceName';\n\t}\n}\n\nexport class UnknownStateProvider extends Error {\n\tconstructor(readonly config: unknown) {\n\t\tsuper(\n\t\t\t`Unknown state provider ${JSON.stringify(config)}. Use 'local', 'ssm', ` +\n\t\t\t\t`'s3', or an object implementing StateStore or StateProvider.`,\n\t\t);\n\t\tthis.name = 'UnknownStateProvider';\n\t}\n}\n\n/** A lock or write that could not get at the state backend in time. */\nexport class StateStoreBusy extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly location: string,\n\t) {\n\t\tsuper(\n\t\t\t`Timed out waiting to write deploy state for stage '${stage}' (${location}). ` +\n\t\t\t\t`If no other gkm process is running, remove ${location}.`,\n\t\t);\n\t\tthis.name = 'StateStoreBusy';\n\t}\n}\n\n// ============================================================================\n// Document format\n// ============================================================================\n\nexport const STATE_SCHEMA_VERSION = 2;\n\n/**\n * What a store persists. v1 was `DokployStageState` on its own; v2 wraps it\n * so resource records and a write serial can sit beside it. The serial\n * changes every write, so content-addressed versions (a file hash, an S3\n * ETag) never repeat — a writer holding a version from two writes ago cannot\n * match by accident.\n */\nexport interface StateDocument {\n\tschemaVersion: typeof STATE_SCHEMA_VERSION;\n\tstage: string;\n\tserial: number;\n\tstate: DokployStageState;\n\tresources: Record<string, ResourceRecord>;\n\tupdatedAt: string;\n}\n\nexport type DecodedDocument =\n\t| { schemaVersion: 2; document: StateDocument }\n\t| { schemaVersion: 1; state: DokployStageState };\n\nexport function decodeDocument(\n\tstage: string,\n\tbody: string,\n\tlocation: string,\n): DecodedDocument {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(body);\n\t} catch (error) {\n\t\tthrow new StateUnreadable(stage, location, error);\n\t}\n\n\tif (typeof parsed !== 'object' || parsed === null) {\n\t\tthrow new StateUnreadable(stage, location, parsed);\n\t}\n\n\tconst schemaVersion = (parsed as { schemaVersion?: unknown }).schemaVersion;\n\tif (schemaVersion === undefined) {\n\t\treturn { schemaVersion: 1, state: parsed as DokployStageState };\n\t}\n\tif (schemaVersion === STATE_SCHEMA_VERSION) {\n\t\treturn { schemaVersion: 2, document: parsed as StateDocument };\n\t}\n\tthrow new StateSchemaTooNew(stage, schemaVersion);\n}\n\nexport function encodeDocument(document: StateDocument): string {\n\treturn JSON.stringify(document, null, 2);\n}\n\n/**\n * v1 state as a v2 document. The ids v1 kept are seeded as `ready` records,\n * so the first journaled deploy after an upgrade adopts them rather than\n * treating them as unknown.\n */\nexport function migrateV1(\n\tstage: string,\n\tstate: DokployStageState,\n): StateDocument {\n\tconst now = new Date().toISOString();\n\tconst resources: Record<string, ResourceRecord> = {};\n\tconst seed = (type: string, name: string | null, id: string | undefined) => {\n\t\tif (!id) return;\n\t\tconst key = name ? `${type}:${name}` : type;\n\t\tresources[key] = { key, type, id, status: 'ready', updatedAt: now };\n\t};\n\n\tseed('project', null, state.projectId);\n\tseed('environment', null, state.environmentId);\n\tfor (const [app, id] of Object.entries(state.applications ?? {})) {\n\t\tseed('application', app, id);\n\t}\n\tseed('postgres', null, state.services?.postgresId);\n\tseed('redis', null, state.services?.redisId);\n\tseed('backup-destination', null, state.backups?.destinationId);\n\n\treturn {\n\t\tschemaVersion: STATE_SCHEMA_VERSION,\n\t\tstage,\n\t\tserial: 1,\n\t\tstate,\n\t\tresources,\n\t\tupdatedAt: now,\n\t};\n}\n\nexport function newLockHolder(options?: LockOptions): LockHolder {\n\tlet owner = 'unknown';\n\ttry {\n\t\towner = userInfo().username;\n\t} catch {\n\t\t// No passwd entry (a container running as an arbitrary uid).\n\t}\n\treturn {\n\t\tid: randomUUID(),\n\t\towner,\n\t\thost: hostname(),\n\t\tpid: process.pid,\n\t\tacquiredAt: new Date().toISOString(),\n\t\toperation: options?.operation,\n\t};\n}\n\nexport function contentVersion(body: string): StateVersion {\n\treturn createHash('sha256').update(body).digest('hex').slice(0, 16);\n}\n\n// ============================================================================\n// Shared implementation\n// ============================================================================\n\nexport interface RawState {\n\tbody: string;\n\tversion: StateVersion;\n}\n\n/**\n * A store over a backend that can read and conditionally write one blob per\n * stage, and create a lock object only if it does not exist. Migration and\n * resource records are the same for every such backend, so they live here.\n */\nexport abstract class DocumentStateStore implements StateStore {\n\t/** Where the stage's state lives, for messages. */\n\tprotected abstract location(stage: string): string;\n\tprotected abstract readRaw(stage: string): Promise<RawState | null>;\n\t/**\n\t * Writes only if the stored version is `expectedVersion` (`null`: only if\n\t * nothing is stored), else raises `StateVersionConflict`.\n\t */\n\tprotected abstract writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion>;\n\t/** Keeps the v1 body as it was. Never replaces an existing backup. */\n\tprotected abstract writeV1Backup(stage: string, body: string): Promise<void>;\n\t/** Creates the lock only if absent, else raises `StateLocked`. */\n\tprotected abstract createLock(\n\t\tstage: string,\n\t\tholder: LockHolder,\n\t): Promise<void>;\n\tprotected abstract readLock(stage: string): Promise<LockHolder | null>;\n\t/** Removes the lock — only if it is `holder`'s, when one is given. */\n\tprotected abstract removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void>;\n\n\tasync lock(stage: string, options?: LockOptions): Promise<StateLock> {\n\t\tconst holder = newLockHolder(options);\n\t\tawait this.createLock(stage, holder);\n\t\tlet released = false;\n\t\treturn {\n\t\t\tstage,\n\t\t\tholder,\n\t\t\trelease: async () => {\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tawait this.removeLock(stage, holder);\n\t\t\t},\n\t\t};\n\t}\n\n\tasync forceUnlock(stage: string): Promise<LockHolder | null> {\n\t\tconst holder = await this.readLock(stage);\n\t\tawait this.removeLock(stage, null);\n\t\treturn holder;\n\t}\n\n\tasync read(stage: string): Promise<StoredStageState | null> {\n\t\tconst current = await this.readDocument(stage);\n\t\tif (!current) return null;\n\t\treturn {\n\t\t\tstate: current.document.state,\n\t\t\tresources: current.document.resources,\n\t\t\tversion: current.version,\n\t\t};\n\t}\n\n\tasync write(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion> {\n\t\tif (options.expectedVersion === null) {\n\t\t\tconst document: StateDocument = {\n\t\t\t\tschemaVersion: STATE_SCHEMA_VERSION,\n\t\t\t\tstage,\n\t\t\t\tserial: 1,\n\t\t\t\tstate,\n\t\t\t\tresources: {},\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\treturn this.writeRaw(stage, encodeDocument(document), null);\n\t\t}\n\t\treturn this.mutate(stage, options.expectedVersion, (document) => ({\n\t\t\t...document,\n\t\t\tstate,\n\t\t}));\n\t}\n\n\tasync putResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\treturn this.mutate(stage, options?.expectedVersion, (document) => ({\n\t\t\t...document,\n\t\t\tresources: {\n\t\t\t\t...document.resources,\n\t\t\t\t[record.key]: { ...record, updatedAt: new Date().toISOString() },\n\t\t\t},\n\t\t}));\n\t}\n\n\tasync deleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\treturn this.mutate(stage, options?.expectedVersion, (document) => {\n\t\t\tconst { [key]: _removed, ...resources } = document.resources;\n\t\t\treturn { ...document, resources };\n\t\t});\n\t}\n\n\tprivate async mutate(\n\t\tstage: string,\n\t\texpectedVersion: StateVersion | undefined,\n\t\tchange: (document: StateDocument) => StateDocument,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.readDocument(stage);\n\t\tif (!current) {\n\t\t\tif (expectedVersion !== undefined) {\n\t\t\t\tthrow new StateVersionConflict(stage, expectedVersion, null);\n\t\t\t}\n\t\t\tthrow new StageStateMissing(stage);\n\t\t}\n\t\tif (expectedVersion !== undefined && current.version !== expectedVersion) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, current.version);\n\t\t}\n\n\t\tconst next = change(current.document);\n\t\tnext.serial = current.document.serial + 1;\n\t\tnext.updatedAt = new Date().toISOString();\n\t\treturn this.writeRaw(stage, encodeDocument(next), current.version);\n\t}\n\n\t/**\n\t * Reads the stage's document, migrating v1 in place on the way. The\n\t * migration is itself a conditional write: if another run migrated (or\n\t * wrote) first, its result is read instead.\n\t */\n\tprivate async readDocument(\n\t\tstage: string,\n\t\tattempt = 0,\n\t): Promise<{ document: StateDocument; version: StateVersion } | null> {\n\t\tconst raw = await this.readRaw(stage);\n\t\tif (!raw) return null;\n\n\t\tconst decoded = decodeDocument(stage, raw.body, this.location(stage));\n\t\tif (decoded.schemaVersion === 2) {\n\t\t\treturn { document: decoded.document, version: raw.version };\n\t\t}\n\n\t\tawait this.writeV1Backup(stage, raw.body);\n\t\tconst document = migrateV1(stage, decoded.state);\n\t\ttry {\n\t\t\tconst version = await this.writeRaw(\n\t\t\t\tstage,\n\t\t\t\tencodeDocument(document),\n\t\t\t\traw.version,\n\t\t\t);\n\t\t\treturn { document, version };\n\t\t} catch (error) {\n\t\t\tif (error instanceof StateVersionConflict && attempt < 2) {\n\t\t\t\treturn this.readDocument(stage, attempt + 1);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Resolution\n// ============================================================================\n\nexport function isStateStore(value: unknown): value is StateStore {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\ttypeof (value as StateStore).lock === 'function' &&\n\t\ttypeof (value as StateStore).read === 'function' &&\n\t\ttypeof (value as StateStore).write === 'function' &&\n\t\ttypeof (value as StateStore).putResource === 'function'\n\t);\n}\n\nexport interface CreateStateStoreOptions extends CreateStateStoreConfig {\n\t/** Where `StateStoreWithoutLocking` goes; `process.emitWarning` by default. */\n\twarn?: (warning: StateStoreWithoutLocking) => void;\n}\n\n/**\n * The store for a workspace's `state` config:\n *\n * - none / `'local'`: `LocalStateStore` under `.gkm/`\n * - `'ssm'`: `SSMStateStore`, with no local cache — SSM is read directly, so\n * a stale local copy can never win over it\n * - `'s3'`: `S3StateStore`\n * - a `StateStore` object: used as is\n * - a `StateProvider` object: wrapped by `LegacyStateStore`\n */\nexport async function createStateStore(\n\toptions: CreateStateStoreOptions,\n): Promise<StateStore> {\n\tconst { config, workspaceRoot, workspaceName } = options;\n\n\tif (!config || config.provider === 'local') {\n\t\tconst { LocalStateStore } = await import('./LocalStateStore');\n\t\treturn new LocalStateStore(workspaceRoot);\n\t}\n\n\tif (isStateStore(config.provider)) {\n\t\treturn config.provider;\n\t}\n\n\tif (isStateProvider(config.provider)) {\n\t\tconst { LegacyStateStore } = await import('./LegacyStateStore');\n\t\treturn new LegacyStateStore(config.provider, { warn: options.warn });\n\t}\n\n\tif (config.provider === 'ssm') {\n\t\tif (!workspaceName) throw new StateStoreNeedsWorkspaceName('ssm');\n\t\tconst { SSMStateStore } = await import('./SSMStateStore');\n\t\tconst ssm = config as SSMStateConfig;\n\t\treturn SSMStateStore.create({\n\t\t\tworkspaceName,\n\t\t\tregion: ssm.region,\n\t\t\tprofile: ssm.profile,\n\t\t});\n\t}\n\n\tif (config.provider === 's3') {\n\t\tif (!workspaceName) throw new StateStoreNeedsWorkspaceName('s3');\n\t\tconst { S3StateStore } = await import('./S3StateStore');\n\t\tconst s3 = config as S3StateConfig;\n\t\treturn S3StateStore.create({\n\t\t\tworkspaceName,\n\t\t\tbucket: s3.bucket,\n\t\t\tprefix: s3.prefix,\n\t\t\tregion: s3.region,\n\t\t\tprofile: s3.profile,\n\t\t});\n\t}\n\n\tthrow new UnknownStateProvider(config);\n}\n","/**\n * Deploy state management for Dokploy deployments\n *\n * Stores resource IDs (applications, services) per stage to avoid\n * re-creating resources on subsequent deploys.\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { output } from '../output';\n\n/**\n * Per-app database credentials\n */\nexport interface AppDbCredentials {\n\tdbUser: string;\n\tdbPassword: string;\n}\n\n/**\n * DNS verification record for a hostname\n */\nexport interface DnsVerificationRecord {\n\tserverIp: string;\n\tverifiedAt: string;\n}\n\n/**\n * A DNS record that was created during deploy\n */\nexport interface CreatedDnsRecord {\n\t/** The domain this record belongs to (e.g., 'example.com') */\n\tdomain: string;\n\t/** Record name/subdomain (e.g., 'api' or '@' for root) */\n\tname: string;\n\t/** Record type (A, CNAME, etc.) */\n\ttype: string;\n\t/** Record value (IP address, hostname, etc.) */\n\tvalue: string;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** When this record was created */\n\tcreatedAt: string;\n}\n\n/**\n * Backup destination state\n */\nexport interface BackupState {\n\t/** S3 bucket name for backups */\n\tbucketName: string;\n\t/** S3 bucket ARN */\n\tbucketArn: string;\n\t/** IAM user name created for backup access */\n\tiamUserName: string;\n\t/** IAM access key ID */\n\tiamAccessKeyId: string;\n\t/** IAM secret access key */\n\tiamSecretAccessKey: string;\n\t/** Dokploy destination ID */\n\tdestinationId: string;\n\t/** Dokploy backup schedule ID for postgres (if configured) */\n\tpostgresBackupId?: string;\n\t/** AWS region where bucket was created */\n\tregion: string;\n\t/** Timestamp when backup was configured */\n\tcreatedAt: string;\n}\n\n/**\n * State for a single stage deployment\n */\nexport interface DokployStageState {\n\tprovider: 'dokploy';\n\tstage: string;\n\t/** Dokploy project ID - created on first deploy */\n\tprojectId: string;\n\tenvironmentId: string;\n\tapplications: Record<string, string>; // appName -> applicationId\n\tservices: {\n\t\tpostgresId?: string;\n\t\tredisId?: string;\n\t};\n\t/** Per-app database credentials for reuse on subsequent deploys */\n\tappCredentials?: Record<string, AppDbCredentials>;\n\t/** Auto-generated secrets per app (e.g., BETTER_AUTH_SECRET) */\n\tgeneratedSecrets?: Record<string, Record<string, string>>;\n\t/** DNS verification state per hostname */\n\tdnsVerified?: Record<string, DnsVerificationRecord>;\n\t/** DNS records created during deploy (keyed by \"name:type\", e.g., \"api:A\") */\n\tdnsRecords?: Record<string, CreatedDnsRecord>;\n\t/** Backup destination state */\n\tbackups?: BackupState;\n\t/**\n\t * The deploy identity's key (`<namespace>/<project>`) that last deployed\n\t * this stage — what the project's ownership marker says.\n\t */\n\tidentity?: string;\n\t/**\n\t * The Dokploy registry the stage's images are pulled through. Kept per\n\t * stage rather than once per machine, so a stage keeps the registry it was\n\t * deployed with whoever deploys it next.\n\t */\n\tregistryId?: string;\n\t/** Each app's image as last deployed, keyed by app name. */\n\timages?: Record<string, DeployedImage>;\n\tlastDeployedAt: string;\n}\n\n/** An image a stage runs: the ref it was pushed as, and what it resolved to. */\nexport interface DeployedImage {\n\tref: string;\n\t/** The registry's digest, `sha256:…` — absent if it could not be read. */\n\tdigest?: string;\n}\n\n/**\n * Get the state file path for a stage\n */\nfunction getStateFilePath(workspaceRoot: string, stage: string): string {\n\treturn join(workspaceRoot, '.gkm', `deploy-${stage}.json`);\n}\n\n/**\n * Read the deploy state for a stage\n * Returns null if state file doesn't exist\n */\nexport async function readStageState(\n\tworkspaceRoot: string,\n\tstage: string,\n): Promise<DokployStageState | null> {\n\tconst filePath = getStateFilePath(workspaceRoot, stage);\n\n\ttry {\n\t\tconst content = await readFile(filePath, 'utf-8');\n\t\treturn JSON.parse(content) as DokployStageState;\n\t} catch (error) {\n\t\t// File doesn't exist or is invalid - return null\n\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\treturn null;\n\t\t}\n\t\t// Log other errors but don't fail\n\t\toutput.warn(`Warning: Could not read deploy state: ${error}`);\n\t\treturn null;\n\t}\n}\n\n/**\n * Write the deploy state for a stage\n */\nexport async function writeStageState(\n\tworkspaceRoot: string,\n\tstage: string,\n\tstate: DokployStageState,\n): Promise<void> {\n\tconst filePath = getStateFilePath(workspaceRoot, stage);\n\tconst dir = join(workspaceRoot, '.gkm');\n\n\t// Ensure .gkm directory exists\n\tawait mkdir(dir, { recursive: true });\n\n\t// Update last deployed timestamp\n\tstate.lastDeployedAt = new Date().toISOString();\n\n\tawait writeFile(filePath, JSON.stringify(state, null, 2));\n}\n\n/**\n * Create a new empty state for a stage\n */\nexport function createEmptyState(\n\tstage: string,\n\tprojectId: string,\n\tenvironmentId: string,\n): DokployStageState {\n\treturn {\n\t\tprovider: 'dokploy',\n\t\tstage,\n\t\tprojectId,\n\t\tenvironmentId,\n\t\tapplications: {},\n\t\tservices: {},\n\t\tlastDeployedAt: new Date().toISOString(),\n\t};\n}\n\n/**\n * Get application ID from state\n */\nexport function getApplicationId(\n\tstate: DokployStageState | null,\n\tappName: string,\n): string | undefined {\n\treturn state?.applications[appName];\n}\n\n/**\n * Set application ID in state (mutates state)\n */\nexport function setApplicationId(\n\tstate: DokployStageState,\n\tappName: string,\n\tapplicationId: string,\n): void {\n\tstate.applications[appName] = applicationId;\n}\n\n/**\n * Record the image an app was deployed with (mutates state)\n */\nexport function setDeployedImage(\n\tstate: DokployStageState,\n\tappName: string,\n\timage: DeployedImage,\n): void {\n\tstate.images = { ...state.images, [appName]: image };\n}\n\n/**\n * Get postgres ID from state\n */\nexport function getPostgresId(\n\tstate: DokployStageState | null,\n): string | undefined {\n\treturn state?.services.postgresId;\n}\n\n/**\n * Set postgres ID in state (mutates state)\n */\nexport function setPostgresId(\n\tstate: DokployStageState,\n\tpostgresId: string,\n): void {\n\tstate.services.postgresId = postgresId;\n}\n\n/**\n * Get redis ID from state\n */\nexport function getRedisId(\n\tstate: DokployStageState | null,\n): string | undefined {\n\treturn state?.services.redisId;\n}\n\n/**\n * Set redis ID in state (mutates state)\n */\nexport function setRedisId(state: DokployStageState, redisId: string): void {\n\tstate.services.redisId = redisId;\n}\n\n/**\n * Get app credentials from state\n */\nexport function getAppCredentials(\n\tstate: DokployStageState | null,\n\tappName: string,\n): AppDbCredentials | undefined {\n\treturn state?.appCredentials?.[appName];\n}\n\n/**\n * Set app credentials in state (mutates state)\n */\nexport function setAppCredentials(\n\tstate: DokployStageState,\n\tappName: string,\n\tcredentials: AppDbCredentials,\n): void {\n\tif (!state.appCredentials) {\n\t\tstate.appCredentials = {};\n\t}\n\tstate.appCredentials[appName] = credentials;\n}\n\n/**\n * Get all app credentials from state\n */\nexport function getAllAppCredentials(\n\tstate: DokployStageState | null,\n): Record<string, AppDbCredentials> {\n\treturn state?.appCredentials ?? {};\n}\n\n// ============================================================================\n// Generated Secrets\n// ============================================================================\n\n/**\n * Get a generated secret for an app\n */\nexport function getGeneratedSecret(\n\tstate: DokployStageState | null,\n\tappName: string,\n\tsecretName: string,\n): string | undefined {\n\treturn state?.generatedSecrets?.[appName]?.[secretName];\n}\n\n/**\n * Set a generated secret for an app (mutates state)\n */\nexport function setGeneratedSecret(\n\tstate: DokployStageState,\n\tappName: string,\n\tsecretName: string,\n\tvalue: string,\n): void {\n\tif (!state.generatedSecrets) {\n\t\tstate.generatedSecrets = {};\n\t}\n\tif (!state.generatedSecrets[appName]) {\n\t\tstate.generatedSecrets[appName] = {};\n\t}\n\tstate.generatedSecrets[appName][secretName] = value;\n}\n\n/**\n * Get all generated secrets for an app\n */\nexport function getAppGeneratedSecrets(\n\tstate: DokployStageState | null,\n\tappName: string,\n): Record<string, string> {\n\treturn state?.generatedSecrets?.[appName] ?? {};\n}\n\n/**\n * Get all generated secrets from state\n */\nexport function getAllGeneratedSecrets(\n\tstate: DokployStageState | null,\n): Record<string, Record<string, string>> {\n\treturn state?.generatedSecrets ?? {};\n}\n\n// ============================================================================\n// DNS Verification\n// ============================================================================\n\n/**\n * Get DNS verification record for a hostname\n */\nexport function getDnsVerification(\n\tstate: DokployStageState | null,\n\thostname: string,\n): DnsVerificationRecord | undefined {\n\treturn state?.dnsVerified?.[hostname];\n}\n\n/**\n * Set DNS verification record for a hostname (mutates state)\n */\nexport function setDnsVerification(\n\tstate: DokployStageState,\n\thostname: string,\n\tserverIp: string,\n): void {\n\tif (!state.dnsVerified) {\n\t\tstate.dnsVerified = {};\n\t}\n\tstate.dnsVerified[hostname] = {\n\t\tserverIp,\n\t\tverifiedAt: new Date().toISOString(),\n\t};\n}\n\n/**\n * Check if a hostname is already verified with the given IP\n */\nexport function isDnsVerified(\n\tstate: DokployStageState | null,\n\thostname: string,\n\tserverIp: string,\n): boolean {\n\tconst record = state?.dnsVerified?.[hostname];\n\treturn record?.serverIp === serverIp;\n}\n\n/**\n * Get all DNS verification records from state\n */\nexport function getAllDnsVerifications(\n\tstate: DokployStageState | null,\n): Record<string, DnsVerificationRecord> {\n\treturn state?.dnsVerified ?? {};\n}\n\n// ============================================================================\n// DNS Records\n// ============================================================================\n\n/**\n * Get the key for a DNS record in state\n */\nfunction getDnsRecordKey(name: string, type: string): string {\n\treturn `${name}:${type}`;\n}\n\n/**\n * Get a created DNS record from state\n */\nexport function getDnsRecord(\n\tstate: DokployStageState | null,\n\tname: string,\n\ttype: string,\n): CreatedDnsRecord | undefined {\n\treturn state?.dnsRecords?.[getDnsRecordKey(name, type)];\n}\n\n/**\n * Set a created DNS record in state (mutates state)\n */\nexport function setDnsRecord(\n\tstate: DokployStageState,\n\trecord: Omit<CreatedDnsRecord, 'createdAt'>,\n): void {\n\tif (!state.dnsRecords) {\n\t\tstate.dnsRecords = {};\n\t}\n\tconst key = getDnsRecordKey(record.name, record.type);\n\tstate.dnsRecords[key] = {\n\t\t...record,\n\t\tcreatedAt: new Date().toISOString(),\n\t};\n}\n\n/**\n * Remove a DNS record from state (mutates state)\n */\nexport function removeDnsRecord(\n\tstate: DokployStageState,\n\tname: string,\n\ttype: string,\n): void {\n\tif (state.dnsRecords) {\n\t\tdelete state.dnsRecords[getDnsRecordKey(name, type)];\n\t}\n}\n\n/**\n * Get all created DNS records from state\n */\nexport function getAllDnsRecords(\n\tstate: DokployStageState | null,\n): CreatedDnsRecord[] {\n\tif (!state?.dnsRecords) {\n\t\treturn [];\n\t}\n\treturn Object.values(state.dnsRecords);\n}\n\n/**\n * Clear all DNS records from state (mutates state)\n */\nexport function clearDnsRecords(state: DokployStageState): void {\n\tstate.dnsRecords = {};\n\tstate.dnsVerified = {};\n}\n\n// ============================================================================\n// Backup State\n// ============================================================================\n\n/**\n * Get backup state from state\n */\nexport function getBackupState(\n\tstate: DokployStageState | null,\n): BackupState | undefined {\n\treturn state?.backups;\n}\n\n/**\n * Set backup state (mutates state)\n */\nexport function setBackupState(\n\tstate: DokployStageState,\n\tbackupState: BackupState,\n): void {\n\tstate.backups = backupState;\n}\n\n/**\n * Get backup destination ID from state\n */\nexport function getBackupDestinationId(\n\tstate: DokployStageState | null,\n): string | undefined {\n\treturn state?.backups?.destinationId;\n}\n\n/**\n * Get postgres backup ID from state\n */\nexport function getPostgresBackupId(\n\tstate: DokployStageState | null,\n): string | undefined {\n\treturn state?.backups?.postgresBackupId;\n}\n\n/**\n * Set postgres backup ID in state (mutates state)\n */\nexport function setPostgresBackupId(\n\tstate: DokployStageState,\n\tbackupId: string,\n): void {\n\tif (state.backups) {\n\t\tstate.backups.postgresBackupId = backupId;\n\t}\n}\n","import { getPublicEnvPrefix } from '../workspace/index.js';\nimport { rootSite } from '../workspace/rootSite.js';\nimport type { DomainsConfig, NormalizedAppConfig } from '../workspace/types.js';\n\n/** A stage was deployed that `deploy.domains` gives no domain. */\nexport class NoDomainForStage extends Error {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`No domain for stage \"${stage}\". Add it to gkm.config.ts: deploy: { domains: { ${stage}: 'example.com' } }.`,\n\t\t);\n\t\tthis.name = 'NoDomainForStage';\n\t}\n}\n\n/**\n * The hostname an app answers on for a stage.\n *\n * 1. An explicit `app.domain` — a string, or one per stage — wins.\n * 2. Otherwise from the stage's base domain in `deploy.domains`: the root site\n * answers on the domain itself, and every other app on\n * `{subdomain}.{domain}` — its declaration's `subdomain`, or its own name.\n *\n * @throws {NoDomainForStage} when `deploy.domains` names no domain for it\n */\nexport function resolveHost(\n\tappName: string,\n\tapp: NormalizedAppConfig,\n\tstage: string,\n\tdomains: DomainsConfig | undefined,\n\tisMainFrontend: boolean,\n): string {\n\tif (app.domain) {\n\t\tif (typeof app.domain === 'string') {\n\t\t\treturn app.domain;\n\t\t}\n\t\tif (app.domain[stage]) {\n\t\t\treturn app.domain[stage]!;\n\t\t}\n\t}\n\n\tconst baseDomain = domains?.[stage];\n\tif (!baseDomain) throw new NoDomainForStage(stage);\n\n\tif (isMainFrontend) return baseDomain;\n\n\treturn `${app.subdomain ?? appName}.${baseDomain}`;\n}\n\n/**\n * Whether this app is the site the base domain points at — see `rootSite`,\n * which the local edge asks too.\n *\n * @throws {AmbiguousRootSite} when several sites could be the root and none says it is\n */\nexport function isMainFrontendApp(\n\tappName: string,\n\tapp: NormalizedAppConfig,\n\tallApps: Record<string, NormalizedAppConfig>,\n): boolean {\n\tif (app.type !== 'web') return false;\n\n\tconst sites = Object.entries(allApps)\n\t\t.filter(([, a]) => a.type === 'web')\n\t\t.map(([name, a]) => ({ name, root: a.root }));\n\n\treturn rootSite(sites) === appName;\n}\n\nexport { AmbiguousRootSite } from '../workspace/rootSite.js';\n\n/**\n * Generate public URL build args for a web/mobile app based on its dependencies.\n *\n * The prefix is chosen by the app's framework (NEXT_PUBLIC_, VITE_,\n * EXPO_PUBLIC_, or none for Remix). Apps without a public prefix get no\n * build args — they should fetch URLs at runtime instead.\n *\n * @param app - The web/mobile app configuration\n * @param deployedUrls - Map of app name to deployed public URL\n * @returns Array of build args like 'NEXT_PUBLIC_API_URL=https://api.example.com'\n */\nexport function generatePublicUrlBuildArgs(\n\tapp: NormalizedAppConfig,\n\tdeployedUrls: Record<string, string>,\n): string[] {\n\tconst prefix = getPublicEnvPrefix(app.framework);\n\tif (!prefix) return [];\n\n\tconst buildArgs: string[] = [];\n\tfor (const dep of app.dependencies) {\n\t\tconst publicUrl = deployedUrls[dep];\n\t\tif (publicUrl) {\n\t\t\tbuildArgs.push(`${prefix}${dep.toUpperCase()}_URL=${publicUrl}`);\n\t\t}\n\t}\n\n\treturn buildArgs;\n}\n\n/**\n * Get public URL arg names from app dependencies.\n *\n * @param app - The web/mobile app configuration\n * @returns Array of arg names like 'NEXT_PUBLIC_API_URL', or [] for frameworks\n * without a public prefix (e.g. Remix).\n */\nexport function getPublicUrlArgNames(app: NormalizedAppConfig): string[] {\n\tconst prefix = getPublicEnvPrefix(app.framework);\n\tif (!prefix) return [];\n\treturn app.dependencies.map((dep) => `${prefix}${dep.toUpperCase()}_URL`);\n}\n","/**\n * An image reference, checked before it reaches `docker`.\n *\n * The parts come from the workspace — a registry in `gkm.config.ts`, an image\n * name from the project, a tag from `--tag` — and an argument array already\n * keeps them out of a shell. What it cannot do is stop `docker` reading a value\n * that starts with `-` as one of its own flags, so a ref is held to Docker's\n * own grammar (distribution/reference) and anything else is refused by name\n * before a build starts.\n */\n\nconst DOMAIN_COMPONENT = '(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])';\nconst DOMAIN = `${DOMAIN_COMPONENT}(?:\\\\.${DOMAIN_COMPONENT})*(?::[0-9]+)?`;\nconst PATH_COMPONENT = '[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*';\nconst TAG = '[\\\\w][\\\\w.-]{0,127}';\nconst DIGEST =\n\t'[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*:[0-9a-fA-F]{32,}';\n\nconst REFERENCE = new RegExp(\n\t`^(?:${DOMAIN}/)?${PATH_COMPONENT}(?:/${PATH_COMPONENT})*(?::${TAG})?(?:@${DIGEST})?$`,\n);\n\n/** Docker refuses a repository name longer than this. */\nconst MAX_NAME_LENGTH = 255;\n\n/** A value that is not an image reference was about to be built or pushed. */\nexport class ImageRefInvalid extends Error {\n\tconstructor(readonly ref: string) {\n\t\tsuper(\n\t\t\t`'${ref}' is not a valid image reference. An image is \\`[registry/]name[:tag]\\`: the name lowercase letters, digits and . _ - separators, the tag up to 128 of letters, digits, _ . and -, not starting with . or -. Check docker.registry in gkm.config.ts and the --tag passed.`,\n\t\t);\n\t\tthis.name = 'ImageRefInvalid';\n\t}\n}\n\n/** Throw {@link ImageRefInvalid} unless `ref` is a Docker image reference. */\nexport function validateImageRef(ref: string): string {\n\tconst name = ref.split('@')[0]!.replace(/:[\\w][\\w.-]*$/, '');\n\tif (!REFERENCE.test(ref) || name.length > MAX_NAME_LENGTH) {\n\t\tthrow new ImageRefInvalid(ref);\n\t}\n\treturn ref;\n}\n","import { existsSync } from 'node:fs';\nimport { dirname, join, parse } from 'node:path';\nimport type { DockerConfig, GkmConfig } from '../types';\n\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';\n\nexport interface DockerTemplateOptions {\n\timageName: string;\n\tbaseImage: string;\n\tport: number;\n\thealthCheckPath: string;\n\t/** Whether the build is pre-built (slim Dockerfile) or needs building */\n\tprebuilt: boolean;\n\t/** Detected package manager */\n\tpackageManager: PackageManager;\n}\n\nexport interface FrontendDockerfileOptions {\n\timageName: string;\n\tbaseImage: string;\n\tport: number;\n\t/** App path relative to workspace root */\n\tappPath: string;\n\t/** Package name for turbo prune */\n\tturboPackage: string;\n\t/** Detected package manager */\n\tpackageManager: PackageManager;\n\t/**\n\t * Public URL build args to include in the Dockerfile.\n\t * These will be declared as ARG and converted to ENV for Next.js build.\n\t * Example: ['NEXT_PUBLIC_API_URL', 'NEXT_PUBLIC_AUTH_URL']\n\t */\n\tpublicUrlArgs?: string[];\n}\n\nexport interface MultiStageDockerfileOptions extends DockerTemplateOptions {\n\t/** Enable turbo prune for monorepo optimization */\n\tturbo?: boolean;\n\t/** Package name for turbo prune (defaults to current directory name) */\n\tturboPackage?: string;\n}\n\nconst LOCKFILES: [string, PackageManager][] = [\n\t['pnpm-lock.yaml', 'pnpm'],\n\t['bun.lockb', 'bun'],\n\t['yarn.lock', 'yarn'],\n\t['package-lock.json', 'npm'],\n];\n\n/**\n * Detect package manager from lockfiles\n * Walks up the directory tree to find lockfile (for monorepos)\n */\nexport function detectPackageManager(\n\tcwd: string = process.cwd(),\n): PackageManager {\n\tlet dir = cwd;\n\tconst root = parse(dir).root;\n\n\t// Walk up the directory tree\n\twhile (dir !== root) {\n\t\tfor (const [lockfile, pm] of LOCKFILES) {\n\t\t\tif (existsSync(join(dir, lockfile))) {\n\t\t\t\treturn pm;\n\t\t\t}\n\t\t}\n\t\tdir = dirname(dir);\n\t}\n\n\t// Check root directory\n\tfor (const [lockfile, pm] of LOCKFILES) {\n\t\tif (existsSync(join(root, lockfile))) {\n\t\t\treturn pm;\n\t\t}\n\t}\n\n\treturn 'pnpm'; // default\n}\n\n/**\n * Find the lockfile path by walking up the directory tree\n * Returns the full path to the lockfile, or null if not found\n */\nexport function findLockfilePath(cwd: string = process.cwd()): string | null {\n\tlet dir = cwd;\n\tconst root = parse(dir).root;\n\n\t// Walk up the directory tree\n\twhile (dir !== root) {\n\t\tfor (const [lockfile] of LOCKFILES) {\n\t\t\tconst lockfilePath = join(dir, lockfile);\n\t\t\tif (existsSync(lockfilePath)) {\n\t\t\t\treturn lockfilePath;\n\t\t\t}\n\t\t}\n\t\tdir = dirname(dir);\n\t}\n\n\t// Check root directory\n\tfor (const [lockfile] of LOCKFILES) {\n\t\tconst lockfilePath = join(root, lockfile);\n\t\tif (existsSync(lockfilePath)) {\n\t\t\treturn lockfilePath;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Get the lockfile name for a package manager\n */\nexport function getLockfileName(pm: PackageManager): string {\n\tconst lockfileMap: Record<PackageManager, string> = {\n\t\tpnpm: 'pnpm-lock.yaml',\n\t\tnpm: 'package-lock.json',\n\t\tyarn: 'yarn.lock',\n\t\tbun: 'bun.lockb',\n\t};\n\treturn lockfileMap[pm];\n}\n\n/**\n * Check if we're in a monorepo (lockfile is in a parent directory)\n */\nexport function isMonorepo(cwd: string = process.cwd()): boolean {\n\tconst lockfilePath = findLockfilePath(cwd);\n\tif (!lockfilePath) {\n\t\treturn false;\n\t}\n\n\t// Check if lockfile is in a parent directory (not in cwd)\n\tconst lockfileDir = dirname(lockfilePath);\n\treturn lockfileDir !== cwd;\n}\n\n/**\n * Check if turbo.json exists (walks up directory tree)\n */\nexport function hasTurboConfig(cwd: string = process.cwd()): boolean {\n\tlet dir = cwd;\n\tconst root = parse(dir).root;\n\n\twhile (dir !== root) {\n\t\tif (existsSync(join(dir, 'turbo.json'))) {\n\t\t\treturn true;\n\t\t}\n\t\tdir = dirname(dir);\n\t}\n\n\treturn existsSync(join(root, 'turbo.json'));\n}\n\n/**\n * Get install command for turbo builds (without frozen lockfile)\n * Turbo prune creates a subset that may not perfectly match the lockfile\n */\nfunction getTurboInstallCmd(pm: PackageManager): string {\n\tconst commands: Record<PackageManager, string> = {\n\t\tpnpm: 'pnpm install',\n\t\tnpm: 'npm install',\n\t\tyarn: 'yarn install',\n\t\tbun: 'bun install',\n\t};\n\treturn commands[pm];\n}\n\n/**\n * Get package manager specific commands and paths\n */\nfunction getPmConfig(pm: PackageManager) {\n\tconst configs = {\n\t\tpnpm: {\n\t\t\tinstall: 'corepack enable && corepack prepare pnpm@latest --activate',\n\t\t\tlockfile: 'pnpm-lock.yaml',\n\t\t\tfetch: 'pnpm fetch',\n\t\t\tinstallCmd: 'pnpm install --frozen-lockfile --offline',\n\t\t\tcacheTarget: '/root/.local/share/pnpm/store',\n\t\t\tcacheId: 'pnpm',\n\t\t\trun: 'pnpm',\n\t\t\texec: 'pnpm exec',\n\t\t\tdlx: 'pnpm dlx',\n\t\t\taddGlobal: 'pnpm add -g',\n\t\t},\n\t\tnpm: {\n\t\t\tinstall: '', // npm comes with node\n\t\t\tlockfile: 'package-lock.json',\n\t\t\tfetch: '', // npm doesn't have fetch\n\t\t\tinstallCmd: 'npm ci',\n\t\t\tcacheTarget: '/root/.npm',\n\t\t\tcacheId: 'npm',\n\t\t\trun: 'npm run',\n\t\t\texec: 'npx',\n\t\t\tdlx: 'npx',\n\t\t\taddGlobal: 'npm install -g',\n\t\t},\n\t\tyarn: {\n\t\t\tinstall: 'corepack enable && corepack prepare yarn@stable --activate',\n\t\t\tlockfile: 'yarn.lock',\n\t\t\tfetch: '', // yarn doesn't have fetch\n\t\t\tinstallCmd: 'yarn install --frozen-lockfile',\n\t\t\tcacheTarget: '/root/.yarn/cache',\n\t\t\tcacheId: 'yarn',\n\t\t\trun: 'yarn',\n\t\t\texec: 'yarn exec',\n\t\t\tdlx: 'yarn dlx',\n\t\t\taddGlobal: 'yarn global add',\n\t\t},\n\t\tbun: {\n\t\t\tinstall: 'npm install -g bun',\n\t\t\tlockfile: 'bun.lockb',\n\t\t\tfetch: '', // bun doesn't have fetch\n\t\t\tinstallCmd: 'bun install --frozen-lockfile',\n\t\t\tcacheTarget: '/root/.bun/install/cache',\n\t\t\tcacheId: 'bun',\n\t\t\trun: 'bun run',\n\t\t\texec: 'bunx',\n\t\t\tdlx: 'bunx',\n\t\t\taddGlobal: 'bun add -g',\n\t\t},\n\t};\n\treturn configs[pm];\n}\n\n/**\n * Generate a multi-stage Dockerfile for building from source\n * Optimized for build speed with:\n * - BuildKit cache mounts for package manager store\n * - pnpm fetch for better layer caching (when using pnpm)\n * - Optional turbo prune for monorepos\n */\nexport function generateMultiStageDockerfile(\n\toptions: MultiStageDockerfileOptions,\n): string {\n\tconst {\n\t\tbaseImage,\n\t\tport,\n\t\thealthCheckPath,\n\t\tturbo,\n\t\tturboPackage,\n\t\tpackageManager,\n\t} = options;\n\n\tif (turbo) {\n\t\treturn generateTurboDockerfile({\n\t\t\t...options,\n\t\t\tturboPackage: turboPackage ?? 'api',\n\t\t});\n\t}\n\n\tconst pm = getPmConfig(packageManager);\n\tconst installPm = pm.install\n\t\t? `\\n# Install ${packageManager}\\nRUN ${pm.install}\\n`\n\t\t: '';\n\tconst hasFetch = packageManager === 'pnpm';\n\n\t// pnpm has fetch which allows better caching\n\tconst depsStage = hasFetch\n\t\t? `# Copy lockfile first for better caching\nCOPY ${pm.lockfile} ./\n\n# Fetch dependencies (downloads to virtual store, cached separately)\nRUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\\\\n ${pm.fetch}\n\n# Copy package.json after fetch\nCOPY package.json ./\n\n# Install from cache (fast - no network needed)\nRUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\\\\n ${pm.installCmd}`\n\t\t: `# Copy package files\nCOPY package.json ${pm.lockfile} ./\n\n# Install dependencies with cache\nRUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\\\\n ${pm.installCmd}`;\n\n\treturn `# syntax=docker/dockerfile:1\n# Stage 1: Dependencies\nFROM ${baseImage} AS deps\n\nWORKDIR /app\n${installPm}\n${depsStage}\n\n# Stage 2: Build\nFROM deps AS builder\n\nWORKDIR /app\n\n# Copy source (deps already installed)\nCOPY . .\n\n# Build any workspace packages the app depends on.\n#\n# A no-op for a project whose dependencies come from a registry, and required\n# for one whose CLI is a sibling package: \\`turbo prune\\` honours .gitignore, so a\n# workspace package's \\`dist\\` never reaches the image and its bin has nothing to\n# import. \\`--if-present\\` is what makes the same Dockerfile correct for both.\nRUN ${pm.run} --if-present build\n\n# Build production server using gkm\nRUN ${pm.exec} gkm build --provider server --production\n\n# Stage 3: Production\nFROM ${baseImage} AS runner\n\nWORKDIR /app\n\n# Install tini for proper signal handling as PID 1\nRUN apk add --no-cache tini\n\n# Create non-root user\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 hono\n\n# Copy bundled server\nCOPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./\n\n# Environment\nENV NODE_ENV=production\nENV PORT=${port}\n\n# Health check\nHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\\\\n CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1\n\n# Switch to non-root user\nUSER hono\n\nEXPOSE ${port}\n\n# Use tini as entrypoint to handle PID 1 responsibilities\nENTRYPOINT [\"/sbin/tini\", \"--\"]\nCMD [\"node\", \"server.mjs\"]\n`;\n}\n\n/**\n * Generate a Dockerfile optimized for Turbo monorepos\n * Uses turbo prune to create minimal Docker context\n */\nfunction generateTurboDockerfile(options: MultiStageDockerfileOptions): string {\n\tconst { baseImage, port, healthCheckPath, turboPackage, packageManager } =\n\t\toptions;\n\n\tconst pm = getPmConfig(packageManager);\n\tconst installPm = pm.install ? `RUN ${pm.install}` : '';\n\n\t// For turbo builds, we can't use --frozen-lockfile because turbo prune\n\t// creates a subset that may not perfectly match. Use relaxed install.\n\tconst turboInstallCmd = getTurboInstallCmd(packageManager);\n\n\t// Use pnpm dlx for pnpm (avoids global bin dir issues in Docker)\n\tconst turboCmd = packageManager === 'pnpm' ? 'pnpm dlx turbo' : 'npx turbo';\n\n\treturn `# syntax=docker/dockerfile:1\n# Stage 1: Prune monorepo\nFROM ${baseImage} AS pruner\n\nWORKDIR /app\n\n${installPm}\n\nCOPY . .\n\n# Prune to only include necessary packages\nRUN ${turboCmd} prune ${turboPackage} --docker\n\n# Stage 2: Install dependencies\nFROM ${baseImage} AS deps\n\nWORKDIR /app\n\n${installPm}\n\n# Copy pruned lockfile and package.jsons\nCOPY --from=pruner /app/out/${pm.lockfile} ./\nCOPY --from=pruner /app/out/json/ ./\n\n# Install dependencies (no frozen-lockfile since turbo prune creates a subset)\nRUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\\\\n ${turboInstallCmd}\n\n# Stage 3: Build\nFROM deps AS builder\n\nWORKDIR /app\n\n# Copy pruned source\nCOPY --from=pruner /app/out/full/ ./\n\n# Build any workspace packages the app depends on.\n#\n# A no-op for a project whose dependencies come from a registry, and required\n# for one whose CLI is a sibling package: \\`turbo prune\\` honours .gitignore, so a\n# workspace package's \\`dist\\` never reaches the image and its bin has nothing to\n# import. \\`--if-present\\` is what makes the same Dockerfile correct for both.\nRUN ${pm.run} --if-present build\n\n# Build production server using gkm\nRUN ${pm.exec} gkm build --provider server --production\n\n# Stage 4: Production\nFROM ${baseImage} AS runner\n\nWORKDIR /app\n\nRUN apk add --no-cache tini\n\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 hono\n\nCOPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./\n\nENV NODE_ENV=production\nENV PORT=${port}\n\nHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\\\\n CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1\n\nUSER hono\n\nEXPOSE ${port}\n\nENTRYPOINT [\"/sbin/tini\", \"--\"]\nCMD [\"node\", \"server.mjs\"]\n`;\n}\n\n/**\n * Generate a slim Dockerfile for pre-built bundles\n */\nexport function generateSlimDockerfile(\n\toptions: DockerTemplateOptions & {\n\t\t/**\n\t\t * The bundle, relative to the build context. An app in a workspace keeps\n\t\t * it under its own directory; the default is a single app's.\n\t\t */\n\t\tbundle?: string;\n\t},\n): string {\n\tconst {\n\t\tbaseImage,\n\t\tport,\n\t\thealthCheckPath,\n\t\tbundle = '.gkm/server/dist/server.mjs',\n\t} = options;\n\n\treturn `# Slim Dockerfile for pre-built production bundle\nFROM ${baseImage}\n\nWORKDIR /app\n\n# Install tini for proper signal handling as PID 1\n# Handles SIGTERM propagation and zombie process reaping\nRUN apk add --no-cache tini\n\n# Create non-root user\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 hono\n\n# Copy pre-built bundle\nCOPY ${bundle} ./\n\n# Environment\nENV NODE_ENV=production\nENV PORT=${port}\n\n# Health check\nHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\\\\n CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1\n\n# Switch to non-root user\nUSER hono\n\nEXPOSE ${port}\n\n# Use tini as entrypoint to handle PID 1 responsibilities\nENTRYPOINT [\"/sbin/tini\", \"--\"]\nCMD [\"node\", \"server.mjs\"]\n`;\n}\n\n/**\n * Generate .dockerignore file\n */\nexport function generateDockerignore(): string {\n\treturn `# Dependencies\nnode_modules\n.pnpm-store\n\n# Build output (except what we need)\n.gkm/aws*\n.gkm/server/*.ts\n!.gkm/server/dist\n\n# The key gkm build --stage encrypted secrets with: runtime env only, never\n# the build context\n.gkm/server/master.key\n\n# gkm compose's stacks: each app's env file holds its stage's secrets\n.gkm/compose\n\n# IDE and editor\n.idea\n.vscode\n*.swp\n*.swo\n\n# Git\n.git\n.gitignore\n\n# Logs\n*.log\nnpm-debug.log*\npnpm-debug.log*\n\n# Test files\n**/*.test.ts\n**/*.spec.ts\n**/__tests__\ncoverage\n\n# Documentation\ndocs\n*.md\n!README.md\n\n# Environment files (handle secrets separately)\n.env\n.env.*\n!.env.example\n\n# Docker files (don't copy recursively)\nDockerfile*\ndocker-compose*\n.dockerignore\n`;\n}\n\n/**\n * Generate docker-entrypoint.sh for custom startup logic\n */\nexport function generateDockerEntrypoint(): string {\n\treturn `#!/bin/sh\nset -e\n\n# Run any custom startup scripts here\n# Example: wait for database\n# until nc -z $DB_HOST $DB_PORT; do\n# echo \"Waiting for database...\"\n# sleep 1\n# done\n\n# Execute the main command\nexec \"$@\"\n`;\n}\n\n/**\n * Resolve Docker configuration from GkmConfig with defaults\n */\nexport function resolveDockerConfig(config: GkmConfig): Required<DockerConfig> {\n\tconst docker = config.docker ?? {};\n\n\treturn {\n\t\tregistry: docker.registry ?? '',\n\t\t// The project's name, from the config — what somebody types after\n\t\t// `docker pull` — never whatever the directory's package.json is called.\n\t\timageName: docker.imageName ?? config.name ?? 'api',\n\t\tbaseImage: docker.baseImage ?? 'node:22-alpine',\n\t\tport: docker.port ?? 3000,\n\t};\n}\n\n/**\n * Generate a Dockerfile for Next.js frontend apps using standalone output.\n * Uses turbo prune for monorepo optimization.\n * @internal Exported for testing\n */\nexport function generateNextjsDockerfile(\n\toptions: FrontendDockerfileOptions,\n): string {\n\tconst {\n\t\tbaseImage,\n\t\tport,\n\t\tappPath,\n\t\tturboPackage,\n\t\tpackageManager,\n\t\tpublicUrlArgs = ['NEXT_PUBLIC_API_URL', 'NEXT_PUBLIC_AUTH_URL'],\n\t} = options;\n\n\tconst pm = getPmConfig(packageManager);\n\tconst installPm = pm.install ? `RUN ${pm.install}` : '';\n\n\t// For turbo builds, we can't use --frozen-lockfile because turbo prune\n\t// creates a subset that may not perfectly match. Use relaxed install.\n\tconst turboInstallCmd = getTurboInstallCmd(packageManager);\n\n\t// Use pnpm dlx for pnpm (avoids global bin dir issues in Docker)\n\tconst turboCmd = packageManager === 'pnpm' ? 'pnpm dlx turbo' : 'npx turbo';\n\n\t// Generate ARG and ENV declarations for public URLs\n\tconst publicUrlArgDeclarations = publicUrlArgs\n\t\t.map((arg) => `ARG ${arg}=\"\"`)\n\t\t.join('\\n');\n\tconst publicUrlEnvDeclarations = publicUrlArgs\n\t\t.map((arg) => `ENV ${arg}=$${arg}`)\n\t\t.join('\\n');\n\n\treturn `# syntax=docker/dockerfile:1\n# Next.js standalone Dockerfile with turbo prune optimization\n\n# Stage 1: Prune monorepo\nFROM ${baseImage} AS pruner\n\nWORKDIR /app\n\n${installPm}\n\nCOPY . .\n\n# Prune to only include necessary packages\nRUN ${turboCmd} prune ${turboPackage} --docker\n\n# Stage 2: Install dependencies\nFROM ${baseImage} AS deps\n\nWORKDIR /app\n\n${installPm}\n\n# Copy pruned lockfile and package.jsons\nCOPY --from=pruner /app/out/${pm.lockfile} ./\nCOPY --from=pruner /app/out/json/ ./\n\n# Install dependencies\nRUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\\\\n ${turboInstallCmd}\n\n# Stage 3: Build\nFROM deps AS builder\n\nWORKDIR /app\n\n# Build-time args for public API URLs (populated by gkm deploy)\n# These get baked into the Next.js build as public environment variables\n${publicUrlArgDeclarations}\n\n# Convert ARGs to ENVs for Next.js build\n${publicUrlEnvDeclarations}\n\n# Copy pruned source\nCOPY --from=pruner /app/out/full/ ./\n\n# Copy workspace root configs for turbo builds (turbo prune doesn't include root configs)\n# Using wildcard to make it optional for single-app projects\nCOPY --from=pruner /app/tsconfig.* ./\n\n# Ensure public directory exists (may be empty for scaffolded projects)\nRUN mkdir -p ${appPath}/public\n\n# Set Next.js to produce standalone output\nENV NEXT_TELEMETRY_DISABLED=1\n\n# Build the application\nRUN ${turboCmd} run build --filter=${turboPackage}\n\n# Stage 4: Production\nFROM ${baseImage} AS runner\n\nWORKDIR /app\n\n# Install tini for proper signal handling\nRUN apk add --no-cache tini\n\n# Create non-root user\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 nextjs\n\n# Set environment\nENV NODE_ENV=production\nENV NEXT_TELEMETRY_DISABLED=1\nENV PORT=${port}\nENV HOSTNAME=\"0.0.0.0\"\n\n# Copy static files and standalone output\nCOPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/standalone ./\nCOPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/static ./${appPath}/.next/static\nCOPY --from=builder --chown=nextjs:nodejs /app/${appPath}/public ./${appPath}/public\n\nUSER nextjs\n\nEXPOSE ${port}\n\nENTRYPOINT [\"/sbin/tini\", \"--\"]\nCMD [\"node\", \"${appPath}/server.js\"]\n`;\n}\n\n/**\n * Generate a Dockerfile for backend apps in a workspace.\n * Uses turbo prune for monorepo optimization.\n * @internal Exported for testing\n */\nexport function generateBackendDockerfile(\n\toptions: FrontendDockerfileOptions & { healthCheckPath?: string },\n): string {\n\tconst {\n\t\tbaseImage,\n\t\tport,\n\t\tappPath,\n\t\tturboPackage,\n\t\tpackageManager,\n\t\thealthCheckPath = '/health',\n\t} = options;\n\n\tconst pm = getPmConfig(packageManager);\n\tconst installPm = pm.install ? `RUN ${pm.install}` : '';\n\tconst turboInstallCmd = getTurboInstallCmd(packageManager);\n\tconst turboCmd = packageManager === 'pnpm' ? 'pnpm dlx turbo' : 'npx turbo';\n\n\treturn `# syntax=docker/dockerfile:1\n# Backend Dockerfile with turbo prune optimization\n\n# Stage 1: Prune monorepo\nFROM ${baseImage} AS pruner\n\nWORKDIR /app\n\n${installPm}\n\nCOPY . .\n\n# Prune to only include necessary packages\nRUN ${turboCmd} prune ${turboPackage} --docker\n\n# Stage 2: Install dependencies\nFROM ${baseImage} AS deps\n\nWORKDIR /app\n\n${installPm}\n\n# Copy pruned lockfile and package.jsons\nCOPY --from=pruner /app/out/${pm.lockfile} ./\nCOPY --from=pruner /app/out/json/ ./\n\n# Install dependencies\nRUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\\\\n ${turboInstallCmd}\n\n# Stage 3: Build\nFROM deps AS builder\n\nWORKDIR /app\n\n# Copy pruned source\nCOPY --from=pruner /app/out/full/ ./\n\n# Copy workspace root configs for turbo builds (turbo prune doesn't include root configs)\n# Using wildcard to make it optional for single-app projects\nCOPY --from=pruner /app/gkm.config.* ./\nCOPY --from=pruner /app/tsconfig.* ./\n\n# Write encrypted credentials for gkm build to embed.\n# A build secret (\\`docker build --secret id=gkm_credentials,src=…\\`), not an\n# ARG: an ARG is recorded in the image history. Two lines, ciphertext then IV;\n# absent when the app has no secrets.\nRUN --mount=type=secret,id=gkm_credentials,required=false \\\n if [ -s /run/secrets/gkm_credentials ]; then \\\n mkdir -p ${appPath}/.gkm && \\\n sed -n 1p /run/secrets/gkm_credentials > ${appPath}/.gkm/credentials.enc && \\\n sed -n 2p /run/secrets/gkm_credentials > ${appPath}/.gkm/credentials.iv; \\\n fi\n\n# Build any workspace packages the app depends on.\n#\n# A no-op for a project whose dependencies come from a registry, and required\n# for one whose CLI is a sibling package: \\`turbo prune\\` honours .gitignore, so a\n# workspace package's \\`dist\\` never reaches the image and its bin has nothing to\n# import. \\`--if-present\\` is what makes the same Dockerfile correct for both.\nRUN ${pm.run} --if-present build\n\n# Build production server using gkm\nRUN cd ${appPath} && ${pm.exec} gkm build --provider server --production\n\n# Stage 4: Production\nFROM ${baseImage} AS runner\n\nWORKDIR /app\n\nRUN apk add --no-cache tini\n\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 hono\n\n# Copy bundled server\nCOPY --from=builder --chown=hono:nodejs /app/${appPath}/.gkm/server/dist/server.mjs ./\n\nENV NODE_ENV=production\nENV PORT=${port}\n\nHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\\\\n CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1\n\nUSER hono\n\nEXPOSE ${port}\n\nENTRYPOINT [\"/sbin/tini\", \"--\"]\nCMD [\"node\", \"server.mjs\"]\n`;\n}\n\n/**\n * Options for entry-based Dockerfile generation.\n */\nexport interface EntryDockerfileOptions {\n\timageName: string;\n\tbaseImage: string;\n\tport: number;\n\t/** App path relative to workspace root */\n\tappPath: string;\n\t/** Entry file path relative to app path (e.g., './src/index.ts') */\n\tentry: string;\n\t/** Package name for turbo prune */\n\tturboPackage: string;\n\t/** Detected package manager */\n\tpackageManager: PackageManager;\n\t/** Health check path (default: '/health') */\n\thealthCheckPath?: string;\n}\n\n/**\n * Generate a Dockerfile for apps with a custom entry point.\n * Uses esbuild to bundle the entry point into dist/index.mjs with all dependencies.\n * This is used for apps that don't use gkm routes (e.g., Better Auth servers).\n * @internal Exported for testing\n */\nexport function generateEntryDockerfile(\n\toptions: EntryDockerfileOptions,\n): string {\n\tconst {\n\t\tbaseImage,\n\t\tport,\n\t\tappPath,\n\t\tentry,\n\t\tturboPackage,\n\t\tpackageManager,\n\t\thealthCheckPath = '/health',\n\t} = options;\n\n\tconst pm = getPmConfig(packageManager);\n\tconst installPm = pm.install ? `RUN ${pm.install}` : '';\n\tconst turboInstallCmd = getTurboInstallCmd(packageManager);\n\tconst turboCmd = packageManager === 'pnpm' ? 'pnpm dlx turbo' : 'npx turbo';\n\n\treturn `# syntax=docker/dockerfile:1\n# Entry-based Dockerfile with turbo prune + tsdown bundling\n\n# Stage 1: Prune monorepo\nFROM ${baseImage} AS pruner\n\nWORKDIR /app\n\n${installPm}\n\nCOPY . .\n\n# Prune to only include necessary packages\nRUN ${turboCmd} prune ${turboPackage} --docker\n\n# Stage 2: Install dependencies\nFROM ${baseImage} AS deps\n\nWORKDIR /app\n\n${installPm}\n\n# Copy pruned lockfile and package.jsons\nCOPY --from=pruner /app/out/${pm.lockfile} ./\nCOPY --from=pruner /app/out/json/ ./\n\n# Install dependencies\nRUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\\\\n ${turboInstallCmd}\n\n# Stage 3: Build with tsdown\nFROM deps AS builder\n\nWORKDIR /app\n\n# Copy pruned source\nCOPY --from=pruner /app/out/full/ ./\n\n# Copy workspace root configs for turbo builds (turbo prune doesn't include root configs)\n# Using wildcard to make it optional for single-app projects\nCOPY --from=pruner /app/tsconfig.* ./\n\n# Bundle entry point with esbuild (outputs to dist/index.mjs)\n# Creates a fully standalone bundle with all dependencies included\n# Use define to embed credentials if present.\n# They arrive as a build secret (\\`docker build --secret id=gkm_credentials,src=…\\`),\n# not an ARG: an ARG is recorded in the image history. Two lines, ciphertext\n# then IV; absent when the app has no secrets.\nRUN --mount=type=secret,id=gkm_credentials,required=false \\\n cd ${appPath} && \\\n if [ -s /run/secrets/gkm_credentials ]; then \\\n CREDS=$(sed -n 1p /run/secrets/gkm_credentials) && \\\n IV=$(sed -n 2p /run/secrets/gkm_credentials) && \\\n npx esbuild ${entry} --bundle --platform=node --target=node22 --format=esm \\\n --outfile=dist/index.mjs --packages=bundle \\\n --banner:js='import { createRequire } from \"module\"; const require = createRequire(import.meta.url);' \\\n --define:__GKM_ENCRYPTED_CREDENTIALS__=\"'\\\\\"$CREDS\\\\\"'\" \\\n --define:__GKM_CREDENTIALS_IV__=\"'\\\\\"$IV\\\\\"'\"; \\\n else \\\n npx esbuild ${entry} --bundle --platform=node --target=node22 --format=esm \\\n --outfile=dist/index.mjs --packages=bundle \\\n --banner:js='import { createRequire } from \"module\"; const require = createRequire(import.meta.url);'; \\\n fi\n\n# Stage 4: Production\nFROM ${baseImage} AS runner\n\nWORKDIR /app\n\nRUN apk add --no-cache tini\n\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 app\n\n# Copy bundled output only (no node_modules needed - fully bundled)\nCOPY --from=builder --chown=app:nodejs /app/${appPath}/dist/index.mjs ./\n\nENV NODE_ENV=production\nENV PORT=${port}\n\nHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\\\\n CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1\n\nUSER app\n\nEXPOSE ${port}\n\nENTRYPOINT [\"/sbin/tini\", \"--\"]\nCMD [\"node\", \"index.mjs\"]\n`;\n}\n\n/**\n * Generate a Dockerfile for Node-based SSR web frameworks (TanStack Start,\n * Remix). Builds with turbo, then runs the framework's production start\n * script via `npm start`.\n *\n * Public URL build args are declared so framework bundlers (Vite, Vinxi)\n * can inline them at build time. Per-framework prefix is the caller's job.\n * @internal Exported for testing\n */\nexport function generateNodeWebDockerfile(\n\toptions: FrontendDockerfileOptions,\n): string {\n\tconst {\n\t\tbaseImage,\n\t\tport,\n\t\tappPath,\n\t\tturboPackage,\n\t\tpackageManager,\n\t\tpublicUrlArgs = [],\n\t} = options;\n\n\tconst pm = getPmConfig(packageManager);\n\tconst installPm = pm.install ? `RUN ${pm.install}` : '';\n\tconst turboInstallCmd = getTurboInstallCmd(packageManager);\n\tconst turboCmd = packageManager === 'pnpm' ? 'pnpm dlx turbo' : 'npx turbo';\n\tconst startCmd =\n\t\tpackageManager === 'pnpm'\n\t\t\t? 'pnpm start'\n\t\t\t: `${pm.lockfile.startsWith('package-lock') ? 'npm' : packageManager} start`;\n\n\tconst argDecls = publicUrlArgs.map((a) => `ARG ${a}=\"\"`).join('\\n');\n\tconst envDecls = publicUrlArgs.map((a) => `ENV ${a}=$${a}`).join('\\n');\n\n\treturn `# syntax=docker/dockerfile:1\n# Node SSR web Dockerfile (TanStack Start / Remix) with turbo prune\n\nFROM ${baseImage} AS pruner\nWORKDIR /app\n${installPm}\nCOPY . .\nRUN ${turboCmd} prune ${turboPackage} --docker\n\nFROM ${baseImage} AS deps\nWORKDIR /app\n${installPm}\nCOPY --from=pruner /app/out/${pm.lockfile} ./\nCOPY --from=pruner /app/out/json/ ./\nRUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\\\\n ${turboInstallCmd}\n\nFROM deps AS builder\nWORKDIR /app\n${argDecls}\n${envDecls}\nCOPY --from=pruner /app/out/full/ ./\nCOPY --from=pruner /app/tsconfig.* ./\nRUN ${turboCmd} run build --filter=${turboPackage}\n\nFROM ${baseImage} AS runner\nWORKDIR /app\nRUN apk add --no-cache tini\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 app\n\n# Copy the whole built workspace — frameworks differ on output paths\nCOPY --from=builder --chown=app:nodejs /app/ ./\n\nENV NODE_ENV=production\nENV PORT=${port}\nENV HOSTNAME=\"0.0.0.0\"\n\nUSER app\nEXPOSE ${port}\n\nENTRYPOINT [\"/sbin/tini\", \"--\"]\nCMD [\"sh\", \"-c\", \"cd ${appPath} && ${startCmd}\"]\n`;\n}\n\n/**\n * Generate a Dockerfile for Vite SPA apps. Builds the static bundle with\n * turbo, then serves it via nginx.\n * @internal Exported for testing\n */\nexport function generateViteStaticDockerfile(\n\toptions: FrontendDockerfileOptions,\n): string {\n\tconst {\n\t\tbaseImage,\n\t\tport,\n\t\tappPath,\n\t\tturboPackage,\n\t\tpackageManager,\n\t\tpublicUrlArgs = [],\n\t} = options;\n\n\tconst pm = getPmConfig(packageManager);\n\tconst installPm = pm.install ? `RUN ${pm.install}` : '';\n\tconst turboInstallCmd = getTurboInstallCmd(packageManager);\n\tconst turboCmd = packageManager === 'pnpm' ? 'pnpm dlx turbo' : 'npx turbo';\n\n\tconst argDecls = publicUrlArgs.map((a) => `ARG ${a}=\"\"`).join('\\n');\n\tconst envDecls = publicUrlArgs.map((a) => `ENV ${a}=$${a}`).join('\\n');\n\n\treturn `# syntax=docker/dockerfile:1\n# Vite SPA Dockerfile — builds static assets, serves via nginx\n\nFROM ${baseImage} AS pruner\nWORKDIR /app\n${installPm}\nCOPY . .\nRUN ${turboCmd} prune ${turboPackage} --docker\n\nFROM ${baseImage} AS deps\nWORKDIR /app\n${installPm}\nCOPY --from=pruner /app/out/${pm.lockfile} ./\nCOPY --from=pruner /app/out/json/ ./\nRUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\\\\n ${turboInstallCmd}\n\nFROM deps AS builder\nWORKDIR /app\n${argDecls}\n${envDecls}\nCOPY --from=pruner /app/out/full/ ./\nCOPY --from=pruner /app/tsconfig.* ./\nRUN ${turboCmd} run build --filter=${turboPackage}\n\nFROM nginx:alpine AS runner\nCOPY --from=builder /app/${appPath}/dist /usr/share/nginx/html\nRUN printf 'server {\\\\n listen ${port};\\\\n root /usr/share/nginx/html;\\\\n index index.html;\\\\n location / { try_files $uri $uri/ /index.html; }\\\\n}\\\\n' \\\\\n > /etc/nginx/conf.d/default.conf\nEXPOSE ${port}\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n`;\n}\n","import { copyFileSync, existsSync, readFileSync, unlinkSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport { type ConstructManifest, publicEnvFor } from '@geekmidas/manifest';\nimport { loadConfig, loadWorkspaceConfig } from '../config';\nimport { getPublicUrlArgNames } from '../deploy/domain.js';\nimport { output } from '../output';\nimport { COMPOSE_PATH } from '../reconcile/index.js';\nimport { reconcileWorkspace } from '../reconcile/workspace.js';\nimport { run } from '../run';\nimport { appKey } from '../workspace/derive.js';\nimport type {\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from '../workspace/types.js';\nimport { validateImageRef } from './imageRef';\nimport {\n\tdetectPackageManager,\n\tfindLockfilePath,\n\tgenerateBackendDockerfile,\n\tgenerateDockerEntrypoint,\n\tgenerateDockerignore,\n\tgenerateEntryDockerfile,\n\tgenerateMultiStageDockerfile,\n\tgenerateNextjsDockerfile,\n\tgenerateNodeWebDockerfile,\n\tgenerateSlimDockerfile,\n\tgenerateViteStaticDockerfile,\n\thasTurboConfig,\n\tisMonorepo,\n\ttype PackageManager,\n\tresolveDockerConfig,\n} from './templates';\n\nexport { ImageRefInvalid, validateImageRef } from './imageRef';\nexport {\n\tdetectPackageManager,\n\tfindLockfilePath,\n\thasTurboConfig,\n\tisMonorepo,\n} from './templates';\n\n// Through `output`, so a deploy run that generates a Dockerfile hears it.\nconst logger = output;\n\n/** `docker build` failed; what docker printed above says why. */\nexport class DockerBuildFailed extends Error {\n\tconstructor(\n\t\treadonly imageRef: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Failed to build Docker image: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'DockerBuildFailed';\n\t}\n}\n\n/** `docker push` failed — usually the registry login or its permissions. */\nexport class DockerPushFailed extends Error {\n\tconstructor(\n\t\treadonly imageRef: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Failed to push Docker image: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'DockerPushFailed';\n\t}\n}\n\n/** `gkm docker --push` was asked for with nowhere to push to. */\nexport class PushNeedsRegistry extends Error {\n\tconstructor(readonly imageName: string) {\n\t\tsuper(\n\t\t\t`Registry is required to push Docker image '${imageName}'. Use --registry or configure docker.registry in gkm.config.ts`,\n\t\t);\n\t\tthis.name = 'PushNeedsRegistry';\n\t}\n}\n\nexport interface DockerOptions {\n\t/** Build Docker image after generating files */\n\tbuild?: boolean;\n\t/** Push image to registry after building */\n\tpush?: boolean;\n\t/** Image tag (default: 'latest') */\n\ttag?: string;\n\t/** Container registry URL */\n\tregistry?: string;\n\t/** Use slim Dockerfile (requires pre-built bundle from `gkm build --production`) */\n\tslim?: boolean;\n\t/** Enable turbo prune for monorepo optimization */\n\tturbo?: boolean;\n\t/** Package name for turbo prune (defaults to package.json name) */\n\tturboPackage?: string;\n\t/**\n\t * The directory to generate for — an app's own, when a deploy builds it.\n\t * Defaults to the process's working directory.\n\t */\n\tcwd?: string;\n}\n\nexport interface DockerGeneratedFiles {\n\tdockerfile: string;\n\tdockerCompose: string;\n\tdockerignore: string;\n\tentrypoint: string;\n}\n\n/**\n * Docker command implementation\n * Generates Dockerfile, docker-compose.yml, and related files\n *\n * Default: Multi-stage Dockerfile that builds from source inside Docker\n * --slim: Slim Dockerfile that copies pre-built bundle (requires prior build)\n */\nexport async function dockerCommand(\n\toptions: DockerOptions,\n): Promise<DockerGeneratedFiles | WorkspaceDockerResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\n\t// Load config with workspace detection\n\tconst loadedConfig = await loadWorkspaceConfig(cwd);\n\n\t// Route to workspace docker mode for multi-app workspaces\n\tif (loadedConfig.type === 'workspace') {\n\t\tlogger.log('📦 Detected workspace configuration');\n\t\treturn workspaceDockerCommand(\n\t\t\tloadedConfig.workspace,\n\t\t\tloadedConfig.manifest,\n\t\t);\n\t}\n\n\t// Single-app mode - use existing logic\n\tconst config = await loadConfig(cwd);\n\tconst dockerConfig = resolveDockerConfig(config);\n\n\tconst healthCheckPath = '/health';\n\n\t// Determine Dockerfile type\n\t// Default: Multi-stage (builds inside Docker for reproducibility)\n\t// --slim: Requires pre-built bundle\n\tconst useSlim = options.slim === true;\n\n\tif (useSlim) {\n\t\t// Verify pre-built bundle exists for slim mode\n\t\tconst distDir = join(cwd, '.gkm', 'server', 'dist');\n\t\tconst hasBuild = existsSync(join(distDir, 'server.mjs'));\n\n\t\tif (!hasBuild) {\n\t\t\tthrow new Error(\n\t\t\t\t'Slim Dockerfile requires a pre-built bundle. Run `gkm build --provider server --production` first, or omit --slim to use multi-stage build.',\n\t\t\t);\n\t\t}\n\t}\n\n\t// Generate Docker files\n\tconst dockerDir = join(cwd, '.gkm', 'docker');\n\tawait mkdir(dockerDir, { recursive: true });\n\n\t// Detect package manager from lockfiles\n\tconst packageManager = detectPackageManager(cwd);\n\tconst inMonorepo = isMonorepo(cwd);\n\tconst hasTurbo = hasTurboConfig(cwd);\n\n\t// Auto-enable turbo for monorepos with turbo.json\n\tlet useTurbo = options.turbo ?? false;\n\tif (inMonorepo && !useSlim) {\n\t\tif (hasTurbo) {\n\t\t\tuseTurbo = true;\n\t\t\tlogger.log(' Detected monorepo with turbo.json - using turbo prune');\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t'Monorepo detected but turbo.json not found.\\n\\n' +\n\t\t\t\t\t'Docker builds in monorepos require Turborepo for proper dependency isolation.\\n\\n' +\n\t\t\t\t\t'To fix this:\\n' +\n\t\t\t\t\t' 1. Install turbo: pnpm add -Dw turbo\\n' +\n\t\t\t\t\t' 2. Create turbo.json in your monorepo root\\n' +\n\t\t\t\t\t' 3. Run this command again\\n\\n' +\n\t\t\t\t\t'See: https://turbo.build/repo/docs/guides/tools/docker',\n\t\t\t);\n\t\t}\n\t}\n\n\t// Get the actual package name from package.json for turbo prune\n\tlet turboPackage = options.turboPackage ?? dockerConfig.imageName;\n\tif (useTurbo && !options.turboPackage) {\n\t\ttry {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\t\t\tconst pkg = require(`${cwd}/package.json`);\n\t\t\tif (pkg.name) {\n\t\t\t\tturboPackage = pkg.name;\n\t\t\t\tlogger.log(` Turbo package: ${turboPackage}`);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall back to imageName\n\t\t}\n\t}\n\n\tconst templateOptions = {\n\t\timageName: dockerConfig.imageName,\n\t\tbaseImage: dockerConfig.baseImage,\n\t\tport: dockerConfig.port,\n\t\thealthCheckPath,\n\t\tprebuilt: useSlim,\n\t\tturbo: useTurbo,\n\t\tturboPackage,\n\t\tpackageManager,\n\t};\n\n\t// Generate Dockerfile\n\tconst dockerfile = useSlim\n\t\t? generateSlimDockerfile(templateOptions)\n\t\t: generateMultiStageDockerfile(templateOptions);\n\n\tconst dockerMode = useSlim ? 'slim' : useTurbo ? 'turbo' : 'multi-stage';\n\n\tconst dockerfilePath = join(dockerDir, 'Dockerfile');\n\tawait writeFile(dockerfilePath, dockerfile);\n\tlogger.log(\n\t\t`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`,\n\t);\n\n\tconst composePath = await writeConstructsCompose(loadedConfig.workspace);\n\n\t// Generate .dockerignore in project root (Docker looks for it there)\n\tconst dockerignore = generateDockerignore();\n\tconst dockerignorePath = join(cwd, '.dockerignore');\n\tawait writeFile(dockerignorePath, dockerignore);\n\tlogger.log('Generated: .dockerignore (project root)');\n\n\t// Generate docker-entrypoint.sh\n\tconst entrypoint = generateDockerEntrypoint();\n\tconst entrypointPath = join(dockerDir, 'docker-entrypoint.sh');\n\tawait writeFile(entrypointPath, entrypoint);\n\tlogger.log('Generated: .gkm/docker/docker-entrypoint.sh');\n\n\tconst result: DockerGeneratedFiles = {\n\t\tdockerfile: dockerfilePath,\n\t\tdockerCompose: composePath,\n\t\tdockerignore: dockerignorePath,\n\t\tentrypoint: entrypointPath,\n\t};\n\n\t// Build Docker image if requested\n\tif (options.build) {\n\t\tawait buildDockerImage(dockerConfig.imageName, options, cwd);\n\t}\n\n\t// Push Docker image if requested\n\tif (options.push) {\n\t\tawait pushDockerImage(dockerConfig.imageName, options, cwd);\n\t}\n\n\treturn result;\n}\n\n/**\n * Ensure lockfile exists in the build context\n * For monorepos, copies from workspace root if needed\n * Returns cleanup function if file was copied\n */\nfunction ensureLockfile(cwd: string): (() => void) | null {\n\tconst lockfilePath = findLockfilePath(cwd);\n\n\tif (!lockfilePath) {\n\t\tlogger.warn(\n\t\t\t'\\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.',\n\t\t);\n\t\treturn null;\n\t}\n\n\tconst lockfileName = basename(lockfilePath);\n\tconst localLockfile = join(cwd, lockfileName);\n\n\t// If lockfile exists locally (same directory), nothing to do\n\tif (lockfilePath === localLockfile) {\n\t\treturn null;\n\t}\n\n\tlogger.log(` Copying ${lockfileName} from monorepo root...`);\n\tcopyFileSync(lockfilePath, localLockfile);\n\n\t// Return cleanup function\n\treturn () => {\n\t\ttry {\n\t\t\tunlinkSync(localLockfile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors\n\t\t}\n\t};\n}\n\n/**\n * Build Docker image\n * Uses BuildKit for cache mount support\n */\nasync function buildDockerImage(\n\timageName: string,\n\toptions: DockerOptions,\n\tcwd: string,\n): Promise<void> {\n\tconst tag = options.tag ?? 'latest';\n\tconst registry = options.registry;\n\n\t// Before anything runs: a ref docker would misread is refused by name.\n\tconst fullImageName = validateImageRef(\n\t\tregistry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`,\n\t);\n\n\tlogger.log(`\\n🐳 Building Docker image: ${fullImageName}`);\n\n\t// Ensure lockfile exists (copy from monorepo root if needed)\n\tconst cleanup = ensureLockfile(cwd);\n\n\ttry {\n\t\t// An argument array, so nothing in the ref is read by a shell, and\n\t\t// `--tag=` so it cannot be read as a flag.\n\t\tawait run(\n\t\t\t'docker',\n\t\t\t['build', '--file=.gkm/docker/Dockerfile', `--tag=${fullImageName}`, '.'],\n\t\t\t{\n\t\t\t\tcwd,\n\t\t\t\t// BuildKit, for the templates' `RUN --mount=type=cache`.\n\t\t\t\tenv: { ...process.env, DOCKER_BUILDKIT: '1' },\n\t\t\t},\n\t\t);\n\t\tlogger.log(`✅ Docker image built: ${fullImageName}`);\n\t} catch (error) {\n\t\tthrow new DockerBuildFailed(fullImageName, error);\n\t} finally {\n\t\t// Clean up copied lockfile\n\t\tcleanup?.();\n\t}\n}\n\n/**\n * Push Docker image to registry\n */\nasync function pushDockerImage(\n\timageName: string,\n\toptions: DockerOptions,\n\tcwd: string,\n): Promise<void> {\n\tconst tag = options.tag ?? 'latest';\n\tconst registry = options.registry;\n\n\tif (!registry) {\n\t\tthrow new PushNeedsRegistry(imageName);\n\t}\n\n\tconst fullImageName = validateImageRef(`${registry}/${imageName}:${tag}`);\n\n\tlogger.log(`\\n🚀 Pushing Docker image: ${fullImageName}`);\n\n\ttry {\n\t\tawait run('docker', ['push', fullImageName], { cwd });\n\t\tlogger.log(`✅ Docker image pushed: ${fullImageName}`);\n\t} catch (error) {\n\t\tthrow new DockerPushFailed(fullImageName, error);\n\t}\n}\n\n/**\n * Result of generating Docker files for a single app in a workspace.\n */\nexport interface AppDockerResult {\n\tappName: string;\n\ttype: 'backend' | 'web' | 'mobile';\n\tdockerfile: string;\n\timageName: string;\n}\n\n/**\n * Result of workspace docker command.\n */\nexport interface WorkspaceDockerResult {\n\tapps: AppDockerResult[];\n\tdockerCompose: string;\n\tdockerignore: string;\n}\n\n/**\n * Get the package name from package.json in an app directory.\n */\nfunction getAppPackageName(appPath: string): string | undefined {\n\ttry {\n\t\tconst pkgPath = join(appPath, 'package.json');\n\t\tif (!existsSync(pkgPath)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst content = readFileSync(pkgPath, 'utf-8');\n\t\tconst pkg = JSON.parse(content);\n\t\treturn pkg.name;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Generate Dockerfiles for all apps in a workspace.\n * @internal Exported for testing\n */\nexport async function workspaceDockerCommand(\n\tworkspace: NormalizedWorkspace,\n\t/** What the workspace declares — where a site's public keys come from. */\n\tmanifest?: ConstructManifest,\n): Promise<WorkspaceDockerResult> {\n\tconst results: AppDockerResult[] = [];\n\tconst apps = Object.entries(workspace.apps);\n\n\tlogger.log(`\\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);\n\n\t// Create docker output directory\n\tconst dockerDir = join(workspace.root, '.gkm', 'docker');\n\tawait mkdir(dockerDir, { recursive: true });\n\n\t// Detect package manager\n\tconst packageManager = detectPackageManager(workspace.root);\n\tlogger.log(` Package manager: ${packageManager}`);\n\n\t// Generate Dockerfile for each app\n\tfor (const [appName, app] of apps) {\n\t\t// Mobile apps deploy via their own toolchain (e.g. EAS Build for Expo)\n\t\t// — no Docker image is produced.\n\t\tif (app.type === 'mobile') {\n\t\t\tlogger.log(\n\t\t\t\t`\\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst appPath = app.path;\n\t\tconst fullAppPath = join(workspace.root, appPath);\n\n\t\t// Get package name for turbo prune (use package.json name or app name)\n\t\tconst turboPackage = getAppPackageName(fullAppPath) ?? appName;\n\n\t\t// Determine image name\n\t\tconst imageName = appName;\n\n\t\tconst hasEntry = !!app.entry;\n\t\tconst buildType = hasEntry ? 'entry' : app.type;\n\t\tlogger.log(`\\n 📄 Generating Dockerfile for ${appName} (${buildType})`);\n\n\t\tlet dockerfile: string;\n\n\t\tif (app.type === 'web') {\n\t\t\tdockerfile = siteDockerfile(appName, app, {\n\t\t\t\tturboPackage,\n\t\t\t\tpackageManager,\n\t\t\t\t...(manifest ? { manifest } : {}),\n\t\t\t});\n\t\t} else if (app.entry) {\n\t\t\t// Backend with custom entry point - use tsdown bundling\n\t\t\tdockerfile = generateEntryDockerfile({\n\t\t\t\timageName,\n\t\t\t\tbaseImage: 'node:22-alpine',\n\t\t\t\tport: app.port,\n\t\t\t\tappPath,\n\t\t\t\tentry: app.entry,\n\t\t\t\tturboPackage,\n\t\t\t\tpackageManager,\n\t\t\t\thealthCheckPath: '/health',\n\t\t\t});\n\t\t} else {\n\t\t\t// Backend with gkm routes - use gkm build\n\t\t\tdockerfile = generateBackendDockerfile({\n\t\t\t\timageName,\n\t\t\t\tbaseImage: 'node:22-alpine',\n\t\t\t\tport: app.port,\n\t\t\t\tappPath,\n\t\t\t\tturboPackage,\n\t\t\t\tpackageManager,\n\t\t\t\thealthCheckPath: '/health',\n\t\t\t});\n\t\t}\n\n\t\t// Write Dockerfile with app-specific name\n\t\tconst dockerfilePath = join(dockerDir, `Dockerfile.${appName}`);\n\t\tawait writeFile(dockerfilePath, dockerfile);\n\t\tlogger.log(` Generated: .gkm/docker/Dockerfile.${appName}`);\n\n\t\tresults.push({\n\t\t\tappName,\n\t\t\ttype: app.type,\n\t\t\tdockerfile: dockerfilePath,\n\t\t\timageName,\n\t\t});\n\t}\n\n\t// Generate shared .dockerignore\n\tconst dockerignore = generateDockerignore();\n\tconst dockerignorePath = join(workspace.root, '.dockerignore');\n\tawait writeFile(dockerignorePath, dockerignore);\n\tlogger.log(`\\n Generated: .dockerignore (workspace root)`);\n\n\tconst composePath = await writeConstructsCompose(workspace);\n\n\t// Summary\n\tlogger.log(\n\t\t`\\n✅ Generated ${results.length} Dockerfile(s) + ${COMPOSE_PATH}`,\n\t);\n\tlogger.log('\\n📋 Build commands:');\n\tfor (const result of results) {\n\t\tconst icon =\n\t\t\tresult.type === 'backend' ? '⚙️' : result.type === 'mobile' ? '📱' : '🌐';\n\t\tlogger.log(\n\t\t\t` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`,\n\t\t);\n\t}\n\tprintRunInstructions(workspace);\n\n\treturn {\n\t\tapps: results,\n\t\tdockerCompose: composePath,\n\t\tdockerignore: dockerignorePath,\n\t};\n}\n\n/**\n * A site's Dockerfile, with an `ARG` for every key its bundler inlines.\n *\n * The keys come from the site's declaration where there is one — every\n * public key its edges give it, `VITE_UPLOADS_SERVER_URL` included — and an\n * `ARG` the Dockerfile does not declare is a build argument the build drops,\n * leaving the bundle with an empty URL.\n */\nexport function siteDockerfile(\n\tappName: string,\n\tapp: NormalizedAppConfig,\n\toptions: {\n\t\tturboPackage: string;\n\t\tpackageManager: PackageManager;\n\t\tmanifest?: ConstructManifest;\n\t},\n): string {\n\tconst declared = options.manifest\n\t\t? Object.entries(options.manifest).find(\n\t\t\t\t([id, d]) => d.kind === 'site' && appKey(id) === appName,\n\t\t\t)?.[1]\n\t\t: undefined;\n\tconst publicUrlArgs =\n\t\tdeclared?.kind === 'site' && options.manifest\n\t\t\t? Object.keys(publicEnvFor(declared, options.manifest))\n\t\t\t: getPublicUrlArgNames(app);\n\n\tconst webOpts = {\n\t\timageName: appName,\n\t\tbaseImage: 'node:22-alpine',\n\t\tport: app.port,\n\t\tappPath: app.path,\n\t\tturboPackage: options.turboPackage,\n\t\tpackageManager: options.packageManager,\n\t\tpublicUrlArgs,\n\t};\n\n\tswitch (app.framework) {\n\t\tcase 'vite':\n\t\t\treturn generateViteStaticDockerfile(webOpts);\n\t\tcase 'tanstack-start':\n\t\tcase 'remix':\n\t\t\treturn generateNodeWebDockerfile(webOpts);\n\t\tdefault:\n\t\t\t// nextjs (and any unspecified web framework — schema requires a valid\n\t\t\t// framework, so this is just a default).\n\t\t\treturn generateNextjsDockerfile(webOpts);\n\t}\n}\n\n/**\n * The package name turbo prunes an app by: its package.json's, or its name.\n */\nexport function appPackageName(root: string, appName: string, path: string) {\n\treturn getAppPackageName(join(root, path)) ?? appName;\n}\n\n/**\n * Write `docker-compose.constructs.yml` — the same file `gkm dev` writes, with\n * the apps beside the containers their constructs derive.\n *\n * Through reconcile rather than a second generator, so what `gkm docker`\n * describes and what `gkm dev` runs cannot disagree. Nothing is started.\n */\nasync function writeConstructsCompose(\n\tworkspace: NormalizedWorkspace,\n): Promise<string> {\n\tawait reconcileWorkspace(workspace, {\n\t\tstage: workspace.stages.local,\n\t\tstart: false,\n\t});\n\tlogger.log(` Generated: ${COMPOSE_PATH} (project root)`);\n\treturn join(workspace.root, COMPOSE_PATH);\n}\n\nfunction printRunInstructions(workspace: NormalizedWorkspace): void {\n\tconst project = existsSync(join(workspace.root, 'docker-compose.yml'))\n\t\t? ' -f docker-compose.yml'\n\t\t: '';\n\tlogger.log('\\n📋 Run everything (the apps sit behind the \"apps\" profile):');\n\tlogger.log(\n\t\t' gkm setup # starts the containers and creates what they hold',\n\t);\n\tlogger.log(\n\t\t` docker compose -f ${COMPOSE_PATH}${project} --profile apps up --build`,\n\t);\n}\n","/**\n * Centralized Dokploy API client\n *\n * Handles authentication, error handling, and provides typed methods for all Dokploy API endpoints.\n */\n\nexport interface DokployApiOptions {\n\t/** Dokploy server URL (e.g., https://dokploy.example.com) */\n\tbaseUrl: string;\n\t/** API token for authentication */\n\ttoken: string;\n\t/**\n\t * How long one request may take, in milliseconds, before it is abandoned\n\t * with `DokployRequestTimedOut`. Defaults to 30 seconds.\n\t */\n\ttimeoutMs?: number;\n\t/**\n\t * Aborts every request, and any retry still waiting to run. A request it\n\t * stops rejects with the signal's reason.\n\t */\n\tsignal?: AbortSignal;\n}\n\n/** Per request, so a stalled server fails the deploy, not the CI job. */\nexport const DEFAULT_DOKPLOY_TIMEOUT_MS = 30_000;\n\n/**\n * Dokploy accepted the connection but did not answer in time.\n *\n * Not retried: a request that arrived and stalled may still be acted on, and\n * sending it again could repeat a deploy or a create the server is already\n * doing.\n */\nexport class DokployRequestTimedOut extends Error {\n\tconstructor(\n\t\treadonly baseUrl: string,\n\t\treadonly endpoint: string,\n\t\treadonly timeoutMs: number,\n\t) {\n\t\tsuper(\n\t\t\t`Dokploy at ${baseUrl} did not answer ${endpoint} within ${timeoutMs}ms. ` +\n\t\t\t\t'Check that the Dokploy server is healthy, or raise `timeoutMs` if this call is known to be slow.',\n\t\t);\n\t\tthis.name = 'DokployRequestTimedOut';\n\t}\n}\n\n/**\n * Waits `ms` between retries, or rejects with the signal's reason the moment\n * it aborts — a caller who gave up should not sit through the backoff.\n */\nfunction pause(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(signal.reason);\n\t\t\treturn;\n\t\t}\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timer);\n\t\t\treject(signal?.reason);\n\t\t};\n\t\tconst timer = setTimeout(() => {\n\t\t\tsignal?.removeEventListener('abort', onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tsignal?.addEventListener('abort', onAbort, { once: true });\n\t});\n}\n\nexport interface DokployErrorResponse {\n\tmessage?: string;\n\tissues?: Array<{ message: string }>;\n}\n\nexport class DokployApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic issues?: Array<{ message: string }>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'DokployApiError';\n\t}\n}\n\n/**\n * Dokploy API client\n */\nexport class DokployApi {\n\tprivate baseUrl: string;\n\tprivate token: string;\n\tprivate timeoutMs: number;\n\tprivate signal: AbortSignal | undefined;\n\n\tconstructor(options: DokployApiOptions) {\n\t\tthis.baseUrl = options.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n\t\tthis.token = options.token;\n\t\tthis.timeoutMs = options.timeoutMs ?? DEFAULT_DOKPLOY_TIMEOUT_MS;\n\t\tthis.signal = options.signal;\n\t}\n\n\t/**\n\t * Make a GET request to the Dokploy API\n\t */\n\tasync get<T>(endpoint: string): Promise<T> {\n\t\treturn this.request<T>('GET', endpoint);\n\t}\n\n\t/**\n\t * Make a POST request to the Dokploy API\n\t */\n\tasync post<T>(endpoint: string, body?: Record<string, unknown>): Promise<T> {\n\t\treturn this.request<T>('POST', endpoint, body);\n\t}\n\n\t/**\n\t * Make a request to the Dokploy API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: Record<string, unknown>,\n\t): Promise<T> {\n\t\tconst url = `${this.baseUrl}/api/${endpoint}`;\n\n\t\t// Retried, because the *transport* fails in one specific way here: a\n\t\t// deploy pushes an image for several minutes, and the first call after\n\t\t// that finds a keep-alive socket the server has since closed. `fetch`\n\t\t// reports it as a bare `fetch failed` with no status, which aborted a\n\t\t// deploy that had already built and pushed everything.\n\t\t//\n\t\t// Only connection failures. A response — including a 4xx — means the\n\t\t// request arrived, and repeating it would be repeating a decision the\n\t\t// server already made.\n\t\t//\n\t\t// Each attempt gets its own deadline: a server that accepts the\n\t\t// connection and never answers would otherwise hold the deploy — and a\n\t\t// CI job — until the runner's own limit.\n\t\tlet response: Response | undefined;\n\t\tlet timeout: AbortSignal | undefined;\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 1; attempt <= 3; attempt++) {\n\t\t\tthis.signal?.throwIfAborted();\n\t\t\ttimeout = AbortSignal.timeout(this.timeoutMs);\n\t\t\ttry {\n\t\t\t\tresponse = await fetch(url, {\n\t\t\t\t\tmethod,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t'x-api-key': this.token,\n\t\t\t\t\t},\n\t\t\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t\t\t\tsignal: this.signal\n\t\t\t\t\t\t? AbortSignal.any([this.signal, timeout])\n\t\t\t\t\t\t: timeout,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t} catch (error) {\n\t\t\t\t// An abort is a decision, not a dropped connection: never retried.\n\t\t\t\tconst stopped = this.stopped(timeout, endpoint);\n\t\t\t\tif (stopped) throw stopped;\n\t\t\t\tlastError = error;\n\t\t\t\tif (attempt === 3) break;\n\t\t\t\tawait pause(attempt * 750, this.signal);\n\t\t\t}\n\t\t}\n\n\t\tif (!response || !timeout) {\n\t\t\tconst detail =\n\t\t\t\tlastError instanceof Error ? lastError.message : String(lastError);\n\t\t\tthrow new DokployApiError(\n\t\t\t\t`Could not reach Dokploy at ${this.baseUrl} (${endpoint}): ${detail}`,\n\t\t\t\t0,\n\t\t\t\t'Network error',\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\treturn await this.read<T>(response);\n\t\t} catch (error) {\n\t\t\t// The deadline covers the body too: headers can arrive and the body\n\t\t\t// stall just the same.\n\t\t\tthrow this.stopped(timeout, endpoint) ?? error;\n\t\t}\n\t}\n\n\t/**\n\t * What a request cut short should reject with: the caller's own reason\n\t * when they aborted, `DokployRequestTimedOut` when the deadline passed,\n\t * nothing when it was neither.\n\t */\n\tprivate stopped(timeout: AbortSignal, endpoint: string): unknown {\n\t\tif (this.signal?.aborted) return this.signal.reason;\n\t\tif (timeout.aborted) {\n\t\t\treturn new DokployRequestTimedOut(this.baseUrl, endpoint, this.timeoutMs);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Turns a response into its JSON body, or a `DokployApiError`. */\n\tprivate async read<T>(response: Response): Promise<T> {\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Dokploy API error: ${response.status} ${response.statusText}`;\n\t\t\tlet issues: Array<{ message: string }> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as DokployErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Dokploy API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\tif (errorBody.issues?.length) {\n\t\t\t\t\tissues = errorBody.issues;\n\t\t\t\t\terrorMessage += `\\n Issues: ${errorBody.issues.map((i) => i.message).join(', ')}`;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new DokployApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\tissues,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses (204 No Content or empty body)\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Every external port published on this server, across every project.\n\t *\n\t * Dokploy knows what it has bound; guessing does not. A port that looks free\n\t * from here can be held by a service in a project this deploy has never\n\t * heard of — which surfaces as `Port 5432 is already in use by container\n\t * \"…\"`, naming something the caller cannot see.\n\t *\n\t * `project.all` is a summary, so each project is fetched: the per-service\n\t * `externalPort` only appears on `project.one`.\n\t */\n\tasync publishedPorts(): Promise<Set<number>> {\n\t\tconst ports = new Set<number>();\n\t\tconst projects = await this.listProjects();\n\n\t\tfor (const project of projects) {\n\t\t\tconst details = await this.getProject(project.projectId);\n\n\t\t\tfor (const environment of details.environments ?? []) {\n\t\t\t\tconst resources = [\n\t\t\t\t\t...(environment.postgres ?? []),\n\t\t\t\t\t...(environment.redis ?? []),\n\t\t\t\t\t...(environment.applications ?? []),\n\t\t\t\t] as { externalPort?: number | null }[];\n\n\t\t\t\tfor (const resource of resources) {\n\t\t\t\t\tif (typeof resource.externalPort === 'number') {\n\t\t\t\t\t\tports.add(resource.externalPort);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn ports;\n\t}\n\n\t/**\n\t * Validate the API token by making a test request\n\t */\n\tasync validateToken(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.get('project.all');\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ============================================\n\t// Project endpoints\n\t// ============================================\n\n\t/**\n\t * List all projects\n\t */\n\tasync listProjects(): Promise<DokployProject[]> {\n\t\treturn this.get<DokployProject[]>('project.all');\n\t}\n\n\t/**\n\t * Get a single project by ID\n\t */\n\tasync getProject(projectId: string): Promise<DokployProjectDetails> {\n\t\treturn this.get<DokployProjectDetails>(\n\t\t\t`project.one?projectId=${projectId}`,\n\t\t);\n\t}\n\n\t/**\n\t * Create a new project\n\t */\n\tasync createProject(\n\t\tname: string,\n\t\tdescription?: string,\n\t): Promise<{ project: DokployProject; environment: DokployEnvironment }> {\n\t\treturn this.post<{\n\t\t\tproject: DokployProject;\n\t\t\tenvironment: DokployEnvironment;\n\t\t}>('project.create', {\n\t\t\tname,\n\t\t\tdescription: description ?? `Created by gkm CLI`,\n\t\t});\n\t}\n\n\t/**\n\t * Change a project's name or description.\n\t *\n\t * The name is sent with the description because `project.update` validates\n\t * the whole project on some Dokploy versions, and a missing name fails it.\n\t */\n\tasync updateProject(\n\t\tprojectId: string,\n\t\tupdates: { name: string; description: string },\n\t): Promise<void> {\n\t\tawait this.post('project.update', { projectId, ...updates });\n\t}\n\n\t/**\n\t * Delete a project and all its resources\n\t */\n\tasync deleteProject(projectId: string): Promise<void> {\n\t\tawait this.post('project.remove', { projectId });\n\t}\n\n\t// ============================================\n\t// Environment endpoints\n\t// ============================================\n\n\t/**\n\t * Create an environment in a project\n\t */\n\tasync createEnvironment(\n\t\tprojectId: string,\n\t\tname: string,\n\t\tdescription?: string,\n\t): Promise<DokployEnvironment> {\n\t\treturn this.post<DokployEnvironment>('environment.create', {\n\t\t\tprojectId,\n\t\t\tname,\n\t\t\tdescription: description ?? `${name} environment`,\n\t\t});\n\t}\n\n\t// ============================================\n\t// Application endpoints\n\t// ============================================\n\n\t/**\n\t * List all applications in a project\n\t */\n\tasync listApplications(projectId: string): Promise<DokployApplication[]> {\n\t\treturn this.listInProject(projectId, (env) => env.applications);\n\t}\n\n\t/**\n\t * Everything of one kind in a project, across its environments.\n\t *\n\t * From `project.one`, because the per-kind `*.all?projectId=` endpoints\n\t * return **404** on current Dokploy: resources live under environments now.\n\t * Those calls used to be wrapped in `catch { return [] }`, which turned \"I\n\t * could not ask\" into \"there are none\" — and for a find-or-create that is\n\t * the worst available answer. Every deploy created a second application, a\n\t * second Postgres and a second Redis, silently, for as long as the project\n\t * existed.\n\t *\n\t * A failure here is not swallowed: creating a duplicate is worse than\n\t * stopping.\n\t */\n\tprivate async listInProject<T>(\n\t\tprojectId: string,\n\t\tpick: (environment: DokployEnvironment) => T[] | undefined,\n\t): Promise<T[]> {\n\t\tconst project = await this.getProject(projectId);\n\n\t\t// Tagged with the environment they came from: `project.one` nests them,\n\t\t// and a caller filtering by environment has nothing to filter on\n\t\t// otherwise.\n\t\treturn (project.environments ?? []).flatMap((environment) =>\n\t\t\t(pick(environment) ?? []).map((resource) => ({\n\t\t\t\t...resource,\n\t\t\t\tenvironmentId: environment.environmentId,\n\t\t\t})),\n\t\t);\n\t}\n\n\t/**\n\t * Find an application by name in a project\n\t */\n\tasync findApplicationByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t\t/**\n\t\t * The environment to look in.\n\t\t *\n\t\t * A project has one application list per environment, and the same name\n\t\t * legitimately exists in each — `prod` and `staging` both run `api`.\n\t\t * Searching across all of them finds whichever came back first and\n\t\t * deploys a stage's image into another stage's application.\n\t\t */\n\t\tenvironmentId?: string,\n\t): Promise<DokployApplication | undefined> {\n\t\tconst applications = await this.listApplications(projectId);\n\t\tconst normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\n\t\treturn applications.find(\n\t\t\t(app) =>\n\t\t\t\t(!environmentId || app.environmentId === environmentId) &&\n\t\t\t\t(app.name === name || app.appName === normalizedName),\n\t\t);\n\t}\n\n\t/**\n\t * Create a new application\n\t */\n\tasync createApplication(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t): Promise<DokployApplication> {\n\t\treturn this.post<DokployApplication>('application.create', {\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\tappName: name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),\n\t\t});\n\t}\n\n\t/**\n\t * Find or create an application by name\n\t */\n\tasync findOrCreateApplication(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t): Promise<{ application: DokployApplication; created: boolean }> {\n\t\tconst existing = await this.findApplicationByName(\n\t\t\tprojectId,\n\t\t\tname,\n\t\t\tenvironmentId,\n\t\t);\n\t\tif (existing) {\n\t\t\treturn { application: existing, created: false };\n\t\t}\n\t\tconst application = await this.createApplication(\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t);\n\t\treturn { application, created: true };\n\t}\n\n\t/**\n\t * Get an application by ID\n\t */\n\tasync getApplication(\n\t\tapplicationId: string,\n\t): Promise<DokployApplication | null> {\n\t\ttry {\n\t\t\treturn await this.get<DokployApplication>(\n\t\t\t\t`application.one?applicationId=${applicationId}`,\n\t\t\t);\n\t\t} catch {\n\t\t\t// Application not found\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Update an application\n\t */\n\tasync updateApplication(\n\t\tapplicationId: string,\n\t\tupdates: Partial<DokployApplicationUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('application.update', {\n\t\t\tapplicationId,\n\t\t\t...updates,\n\t\t});\n\t}\n\n\t/**\n\t * Save environment variables for an application\n\t */\n\tasync saveApplicationEnv(\n\t\tapplicationId: string,\n\t\tenv: string,\n\t\toptions?: { buildArgs?: string; buildSecrets?: string },\n\t): Promise<void> {\n\t\t// `buildArgs`, `buildSecrets` and `createEnvFile` are `nonoptional` on\n\t\t// current Dokploy, the same way the docker provider's credentials are:\n\t\t// they must be present, and null is how \"none\" is spelled. Omitting them\n\t\t// is rejected with three anonymous validation errors that name no field.\n\t\tawait this.post('application.saveEnvironment', {\n\t\t\tapplicationId,\n\t\t\tenv,\n\t\t\tbuildArgs: options?.buildArgs ?? null,\n\t\t\tbuildSecrets: options?.buildSecrets ?? null,\n\t\t\tcreateEnvFile: false,\n\t\t});\n\t}\n\n\t/**\n\t * Configure application to use Docker provider (pull from registry)\n\t *\n\t * For private registries, either:\n\t * - Use `registryId` if the registry is configured in Dokploy\n\t * - Or provide `username`, `password`, and `registryUrl` directly\n\t */\n\tasync saveDockerProvider(\n\t\tapplicationId: string,\n\t\tdockerImage: string,\n\t\toptions?: {\n\t\t\t/** Registry ID in Dokploy (for pre-configured registries) */\n\t\t\tregistryId?: string;\n\t\t\t/** Registry username (for direct auth) */\n\t\t\tusername?: string;\n\t\t\t/** Registry password (for direct auth) */\n\t\t\tpassword?: string;\n\t\t\t/** Registry URL (for direct auth, e.g., ghcr.io) */\n\t\t\tregistryUrl?: string;\n\t\t},\n\t): Promise<void> {\n\t\t// All five are `nonoptional` on current Dokploy — including the three\n\t\t// credential fields, which are meaningless when a registry is selected by\n\t\t// id. Omitting them is rejected; sending them as null is accepted. So\n\t\t// they are always present and null is how \"this registry needs none\" is\n\t\t// spelled.\n\t\tawait this.post('application.saveDockerProvider', {\n\t\t\tapplicationId,\n\t\t\tdockerImage,\n\t\t\t// Spread first: `registryId` selects a stored registry and is the\n\t\t\t// usual case, and dropping it silently deploys an image nothing can\n\t\t\t// pull. The three below are then filled in rather than replaced.\n\t\t\t...options,\n\t\t\tusername: options?.username ?? null,\n\t\t\tpassword: options?.password ?? null,\n\t\t\tregistryUrl: options?.registryUrl ?? null,\n\t\t});\n\t}\n\n\t/**\n\t * Deploy an application\n\t */\n\tasync deployApplication(applicationId: string): Promise<void> {\n\t\tawait this.post('application.deploy', { applicationId });\n\t}\n\n\t/**\n\t * Delete an application\n\t */\n\tasync deleteApplication(applicationId: string): Promise<void> {\n\t\tawait this.post('application.remove', { applicationId });\n\t}\n\n\t// ============================================\n\t// Registry endpoints\n\t// ============================================\n\n\t/**\n\t * List all registries\n\t */\n\tasync listRegistries(): Promise<DokployRegistry[]> {\n\t\treturn this.get<DokployRegistry[]>('registry.all');\n\t}\n\n\t/**\n\t * Create a new registry\n\t */\n\tasync createRegistry(\n\t\tregistryName: string,\n\t\tregistryUrl: string,\n\t\tusername: string,\n\t\tpassword: string,\n\t\toptions?: {\n\t\t\timagePrefix?: string;\n\t\t},\n\t): Promise<DokployRegistry> {\n\t\treturn this.post<DokployRegistry>('registry.create', {\n\t\t\tregistryName,\n\t\t\tregistryUrl,\n\t\t\tusername,\n\t\t\tpassword,\n\t\t\timagePrefix: options?.imagePrefix,\n\t\t});\n\t}\n\n\t/**\n\t * Get a registry by ID\n\t */\n\tasync getRegistry(registryId: string): Promise<DokployRegistry> {\n\t\treturn this.get<DokployRegistry>(`registry.one?registryId=${registryId}`);\n\t}\n\n\t/**\n\t * Update a registry\n\t */\n\tasync updateRegistry(\n\t\tregistryId: string,\n\t\tupdates: Partial<{\n\t\t\tregistryName: string;\n\t\t\tregistryUrl: string;\n\t\t\tusername: string;\n\t\t\tpassword: string;\n\t\t\timagePrefix: string;\n\t\t}>,\n\t): Promise<void> {\n\t\tawait this.post('registry.update', { registryId, ...updates });\n\t}\n\n\t/**\n\t * Delete a registry\n\t */\n\tasync deleteRegistry(registryId: string): Promise<void> {\n\t\tawait this.post('registry.remove', { registryId });\n\t}\n\n\t// ============================================\n\t// Postgres endpoints\n\t// ============================================\n\n\t/**\n\t * List all Postgres databases in a project\n\t */\n\tasync listPostgres(projectId: string): Promise<DokployPostgres[]> {\n\t\treturn this.listInProject(projectId, (env) => env.postgres);\n\t}\n\n\t/**\n\t * Find a Postgres database by name in a project\n\t */\n\tasync findPostgresByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t): Promise<DokployPostgres | undefined> {\n\t\tconst databases = await this.listPostgres(projectId);\n\t\tconst normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\t\tconst match = databases.find(\n\t\t\t(db) => db.name === name || db.appName === normalizedName,\n\t\t);\n\n\t\tif (!match) return undefined;\n\n\t\t// Hydrated, because the listing is a *summary*: `project.one` nests these\n\t\t// with `postgresId`, `name`, `appName` and status, and none of\n\t\t// `databaseUser`, `databasePassword` or `databaseName`.\n\t\t//\n\t\t// Returning the summary meant a *found* cluster came back with undefined\n\t\t// credentials — so the URL built from it named database `undefined`, and\n\t\t// the DDL connection authenticated with nothing and retried thirty times.\n\t\t// A created one was fine, which is why this only appeared on the second\n\t\t// deploy.\n\t\treturn this.getPostgres(match.postgresId);\n\t}\n\n\t/**\n\t * Create a new Postgres database\n\t */\n\tasync createPostgres(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tappName?: string;\n\t\t\tdatabaseName?: string;\n\t\t\tdatabaseUser?: string;\n\t\t\tdatabasePassword?: string;\n\t\t\tdockerImage?: string;\n\t\t\tdescription?: string;\n\t\t},\n\t): Promise<DokployPostgres> {\n\t\treturn this.post<DokployPostgres>('postgres.create', {\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\tappName:\n\t\t\t\toptions?.appName ?? name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),\n\t\t\tdatabaseName: options?.databaseName,\n\t\t\tdatabaseUser: options?.databaseUser ?? 'postgres',\n\t\t\tdatabasePassword: options?.databasePassword,\n\t\t\tdockerImage: options?.dockerImage ?? 'postgres:18',\n\t\t\tdescription: options?.description ?? `Postgres database for ${name}`,\n\t\t});\n\t}\n\n\t/**\n\t * Find or create a Postgres database by name\n\t */\n\tasync findOrCreatePostgres(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tdatabaseName?: string;\n\t\t\tdatabasePassword?: string;\n\t\t},\n\t): Promise<{ postgres: DokployPostgres; created: boolean }> {\n\t\tconst existing = await this.findPostgresByName(projectId, name);\n\t\tif (existing) {\n\t\t\treturn { postgres: existing, created: false };\n\t\t}\n\t\tconst postgres = await this.createPostgres(\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\toptions,\n\t\t);\n\t\treturn { postgres, created: true };\n\t}\n\n\t/**\n\t * Get a Postgres database by ID\n\t */\n\tasync getPostgres(postgresId: string): Promise<DokployPostgres> {\n\t\treturn this.get<DokployPostgres>(`postgres.one?postgresId=${postgresId}`);\n\t}\n\n\t/**\n\t * Deploy a Postgres database\n\t */\n\tasync deployPostgres(postgresId: string): Promise<void> {\n\t\tawait this.post('postgres.deploy', { postgresId });\n\t}\n\n\t/**\n\t * Save environment variables for Postgres\n\t */\n\tasync savePostgresEnv(postgresId: string, env: string): Promise<void> {\n\t\tawait this.post('postgres.saveEnvironment', { postgresId, env });\n\t}\n\n\t/**\n\t * Set external port for Postgres (for external access)\n\t */\n\tasync savePostgresExternalPort(\n\t\tpostgresId: string,\n\t\texternalPort: number | null,\n\t): Promise<void> {\n\t\tawait this.post('postgres.saveExternalPort', { postgresId, externalPort });\n\t}\n\n\t/**\n\t * Update Postgres configuration\n\t */\n\tasync updatePostgres(\n\t\tpostgresId: string,\n\t\tupdates: Partial<DokployPostgresUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('postgres.update', { postgresId, ...updates });\n\t}\n\n\t/**\n\t * Delete a Postgres database\n\t */\n\tasync deletePostgres(postgresId: string): Promise<void> {\n\t\tawait this.post('postgres.remove', { postgresId });\n\t}\n\n\t// ============================================\n\t// Compose endpoints\n\t// ============================================\n\n\t/**\n\t * The compose stacks in a project.\n\t *\n\t * Dokploy has first-class primitives for Postgres and Redis and none for\n\t * MinIO, so an object store is a compose stack — the one kind whose contents\n\t * this target writes rather than configures.\n\t */\n\tasync listCompose(projectId: string): Promise<DokployCompose[]> {\n\t\treturn this.listInProject(projectId, (env) => env.compose);\n\t}\n\n\t/** A compose stack by name, hydrated — `project.one` returns summaries. */\n\tasync findComposeByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t): Promise<DokployCompose | undefined> {\n\t\tconst summary = (await this.listCompose(projectId)).find(\n\t\t\t(compose) => compose.name === name,\n\t\t);\n\n\t\treturn summary ? this.getCompose(summary.composeId) : undefined;\n\t}\n\n\tasync getCompose(composeId: string): Promise<DokployCompose> {\n\t\treturn this.get<DokployCompose>(`compose.one?composeId=${composeId}`);\n\t}\n\n\t/**\n\t * A compose stack carrying the file this target wrote.\n\t *\n\t * Created empty and then updated, because `compose.create` takes no file:\n\t * it defaults to `sourceType: 'github'` and expects a repository. `raw` is\n\t * what makes the file ours.\n\t */\n\tasync findOrCreateCompose(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\tcomposeFile: string,\n\t): Promise<{ compose: DokployCompose; created: boolean }> {\n\t\tconst existing = await this.findComposeByName(projectId, name);\n\n\t\tconst compose =\n\t\t\texisting ??\n\t\t\t(await this.post<DokployCompose>('compose.create', {\n\t\t\t\tname,\n\t\t\t\tenvironmentId,\n\t\t\t\tcomposeType: 'docker-compose',\n\t\t\t}));\n\n\t\t// Written every time, not only on create: the file is derived from the\n\t\t// manifest, so a redeploy after a declaration changed has to carry the\n\t\t// change. Dokploy redeploys only what differs.\n\t\tawait this.post('compose.update', {\n\t\t\tcomposeId: compose.composeId,\n\t\t\tsourceType: 'raw',\n\t\t\tcomposeFile,\n\t\t});\n\n\t\treturn { compose, created: !existing };\n\t}\n\n\tasync deployCompose(composeId: string): Promise<void> {\n\t\tawait this.post('compose.deploy', { composeId });\n\t}\n\n\tasync deleteCompose(composeId: string): Promise<void> {\n\t\tawait this.post('compose.delete', { composeId, deleteVolumes: false });\n\t}\n\n\t// ============================================\n\t// Redis endpoints\n\t// ============================================\n\n\t/**\n\t * List all Redis instances in a project\n\t */\n\tasync listRedis(projectId: string): Promise<DokployRedis[]> {\n\t\treturn this.listInProject(projectId, (env) => env.redis);\n\t}\n\n\t/**\n\t * Find a Redis instance by name in a project\n\t */\n\tasync findRedisByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t): Promise<DokployRedis | undefined> {\n\t\tconst instances = await this.listRedis(projectId);\n\t\tconst normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\t\tconst match = instances.find(\n\t\t\t(redis) => redis.name === name || redis.appName === normalizedName,\n\t\t);\n\n\t\t// Hydrated for the same reason the Postgres lookup is: the nested listing\n\t\t// carries no credentials, so a found instance would resolve a URL with an\n\t\t// undefined password in it.\n\t\treturn match ? this.getRedis(match.redisId) : undefined;\n\t}\n\n\t/**\n\t * Create a new Redis instance\n\t */\n\tasync createRedis(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tappName?: string;\n\t\t\tdatabasePassword?: string;\n\t\t\tdockerImage?: string;\n\t\t\tdescription?: string;\n\t\t},\n\t): Promise<DokployRedis> {\n\t\treturn this.post<DokployRedis>('redis.create', {\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\tappName:\n\t\t\t\toptions?.appName ?? name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),\n\t\t\tdatabasePassword: options?.databasePassword,\n\t\t\tdockerImage: options?.dockerImage ?? 'redis:8',\n\t\t\tdescription: options?.description ?? `Redis instance for ${name}`,\n\t\t});\n\t}\n\n\t/**\n\t * Find or create a Redis instance by name\n\t */\n\tasync findOrCreateRedis(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tdatabasePassword?: string;\n\t\t},\n\t): Promise<{ redis: DokployRedis; created: boolean }> {\n\t\tconst existing = await this.findRedisByName(projectId, name);\n\t\tif (existing) {\n\t\t\treturn { redis: existing, created: false };\n\t\t}\n\t\tconst redis = await this.createRedis(\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\toptions,\n\t\t);\n\t\treturn { redis, created: true };\n\t}\n\n\t/**\n\t * Get a Redis instance by ID\n\t */\n\tasync getRedis(redisId: string): Promise<DokployRedis> {\n\t\treturn this.get<DokployRedis>(`redis.one?redisId=${redisId}`);\n\t}\n\n\t/**\n\t * Deploy a Redis instance\n\t */\n\tasync deployRedis(redisId: string): Promise<void> {\n\t\tawait this.post('redis.deploy', { redisId });\n\t}\n\n\t/**\n\t * Save environment variables for Redis\n\t */\n\tasync saveRedisEnv(redisId: string, env: string): Promise<void> {\n\t\tawait this.post('redis.saveEnvironment', { redisId, env });\n\t}\n\n\t/**\n\t * Set external port for Redis (for external access)\n\t */\n\tasync saveRedisExternalPort(\n\t\tredisId: string,\n\t\texternalPort: number | null,\n\t): Promise<void> {\n\t\tawait this.post('redis.saveExternalPort', { redisId, externalPort });\n\t}\n\n\t/**\n\t * Update Redis configuration\n\t */\n\tasync updateRedis(\n\t\tredisId: string,\n\t\tupdates: Partial<DokployRedisUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('redis.update', { redisId, ...updates });\n\t}\n\n\t/**\n\t * Delete a Redis instance\n\t */\n\tasync deleteRedis(redisId: string): Promise<void> {\n\t\tawait this.post('redis.remove', { redisId });\n\t}\n\n\t// ============================================\n\t// Domain endpoints\n\t// ============================================\n\n\t/**\n\t * Create a new domain for an application\n\t */\n\tasync createDomain(options: DokployDomainCreate): Promise<DokployDomain> {\n\t\treturn this.post<DokployDomain>(\n\t\t\t'domain.create',\n\t\t\toptions as unknown as Record<string, unknown>,\n\t\t);\n\t}\n\n\t/**\n\t * Update an existing domain\n\t */\n\tasync updateDomain(\n\t\tdomainId: string,\n\t\tupdates: Partial<DokployDomainCreate>,\n\t): Promise<void> {\n\t\tawait this.post('domain.update', { domainId, ...updates });\n\t}\n\n\t/**\n\t * Delete a domain\n\t */\n\tasync deleteDomain(domainId: string): Promise<void> {\n\t\tawait this.post('domain.delete', { domainId });\n\t}\n\n\t/**\n\t * Get a domain by ID\n\t */\n\tasync getDomain(domainId: string): Promise<DokployDomain> {\n\t\treturn this.get<DokployDomain>(`domain.one?domainId=${domainId}`);\n\t}\n\n\t/**\n\t * Get all domains for an application\n\t */\n\tasync getDomainsByApplicationId(\n\t\tapplicationId: string,\n\t): Promise<DokployDomain[]> {\n\t\treturn this.get<DokployDomain[]>(\n\t\t\t`domain.byApplicationId?applicationId=${applicationId}`,\n\t\t);\n\t}\n\n\t/**\n\t * Validate a domain and trigger SSL certificate generation\n\t *\n\t * This should be called after DNS records are created and propagated.\n\t * It triggers Let's Encrypt certificate generation for HTTPS domains.\n\t *\n\t * @param domain - The domain hostname to validate (e.g., 'api.example.com')\n\t */\n\tasync validateDomain(\n\t\tdomain: string,\n\t): Promise<{ isValid: boolean; resolvedIp: string }> {\n\t\treturn this.post<{ isValid: boolean; resolvedIp: string }>(\n\t\t\t'domain.validateDomain',\n\t\t\t{ domain },\n\t\t);\n\t}\n\n\t/**\n\t * Auto-generate a domain name for an application\n\t */\n\tasync generateDomain(\n\t\tappName: string,\n\t\tserverId?: string,\n\t): Promise<{ domain: string }> {\n\t\treturn this.post<{ domain: string }>('domain.generateDomain', {\n\t\t\tappName,\n\t\t\tserverId,\n\t\t});\n\t}\n\n\t// ============================================\n\t// Destination endpoints (backup storage)\n\t// ============================================\n\n\t/**\n\t * List all backup destinations\n\t */\n\tasync listDestinations(): Promise<DokployDestination[]> {\n\t\treturn this.get<DokployDestination[]>('destination.all');\n\t}\n\n\t/**\n\t * Get a destination by ID\n\t */\n\tasync getDestination(destinationId: string): Promise<DokployDestination> {\n\t\treturn this.get<DokployDestination>(\n\t\t\t`destination.one?destinationId=${destinationId}`,\n\t\t);\n\t}\n\n\t/**\n\t * Create a new S3 backup destination\n\t */\n\tasync createDestination(\n\t\toptions: DokployDestinationCreate,\n\t): Promise<DokployDestination> {\n\t\treturn this.post<DokployDestination>('destination.create', { ...options });\n\t}\n\n\t/**\n\t * Find a destination by name\n\t */\n\tasync findDestinationByName(\n\t\tname: string,\n\t): Promise<DokployDestination | undefined> {\n\t\tconst destinations = await this.listDestinations();\n\t\treturn destinations.find((d) => d.name === name);\n\t}\n\n\t/**\n\t * Find or create a destination by name\n\t */\n\tasync findOrCreateDestination(\n\t\tname: string,\n\t\toptions: Omit<DokployDestinationCreate, 'name'>,\n\t): Promise<{ destination: DokployDestination; created: boolean }> {\n\t\tconst existing = await this.findDestinationByName(name);\n\t\tif (existing) {\n\t\t\treturn { destination: existing, created: false };\n\t\t}\n\t\tconst destination = await this.createDestination({ name, ...options });\n\t\treturn { destination, created: true };\n\t}\n\n\t/**\n\t * Update a destination\n\t */\n\tasync updateDestination(\n\t\tdestinationId: string,\n\t\tupdates: Partial<DokployDestinationCreate>,\n\t): Promise<void> {\n\t\tawait this.post('destination.update', { destinationId, ...updates });\n\t}\n\n\t/**\n\t * Delete a destination\n\t */\n\tasync deleteDestination(destinationId: string): Promise<void> {\n\t\tawait this.post('destination.remove', { destinationId });\n\t}\n\n\t/**\n\t * Test connection to a destination\n\t */\n\tasync testDestinationConnection(\n\t\tdestinationId: string,\n\t): Promise<{ success: boolean }> {\n\t\treturn this.post<{ success: boolean }>('destination.testConnection', {\n\t\t\tdestinationId,\n\t\t});\n\t}\n\n\t// ============================================\n\t// Backup endpoints (scheduled backups)\n\t// ============================================\n\n\t/**\n\t * Create a backup schedule for postgres\n\t */\n\tasync createPostgresBackup(\n\t\toptions: DokployBackupCreate,\n\t): Promise<DokployBackup> {\n\t\treturn this.post<DokployBackup>('backup.create', {\n\t\t\t...options,\n\t\t\tdatabaseType: 'postgres',\n\t\t});\n\t}\n\n\t/**\n\t * List backups for a postgres database\n\t */\n\tasync listPostgresBackups(postgresId: string): Promise<DokployBackup[]> {\n\t\treturn this.get<DokployBackup[]>(\n\t\t\t`backup.all?postgresId=${postgresId}&databaseType=postgres`,\n\t\t);\n\t}\n\n\t/**\n\t * Get a backup by ID\n\t */\n\tasync getBackup(backupId: string): Promise<DokployBackup> {\n\t\treturn this.get<DokployBackup>(`backup.one?backupId=${backupId}`);\n\t}\n\n\t/**\n\t * Update a backup schedule\n\t */\n\tasync updateBackup(\n\t\tbackupId: string,\n\t\tupdates: Partial<DokployBackupUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('backup.update', { backupId, ...updates });\n\t}\n\n\t/**\n\t * Delete a backup schedule\n\t */\n\tasync deleteBackup(backupId: string): Promise<void> {\n\t\tawait this.post('backup.remove', { backupId });\n\t}\n\n\t/**\n\t * Trigger a manual backup run\n\t */\n\tasync runBackupManually(backupId: string): Promise<void> {\n\t\tawait this.post('backup.manualBackup', { backupId });\n\t}\n}\n\n// ============================================\n// Type definitions for Dokploy API responses\n// ============================================\n\nexport interface DokployProject {\n\tprojectId: string;\n\tname: string;\n\tdescription: string | null;\n\tcreatedAt?: string;\n\tadminId?: string;\n}\n\nexport interface DokployEnvironment {\n\tenvironmentId: string;\n\tname: string;\n\tdescription: string | null;\n\t/**\n\t * What lives in this environment.\n\t *\n\t * `project.one` is the only endpoint that answers this. The per-kind\n\t * `*.all?projectId=` endpoints return 404 on current Dokploy — resources\n\t * moved under environments — which is why they are no longer asked.\n\t */\n\tapplications?: DokployApplication[];\n\tpostgres?: DokployPostgres[];\n\tcompose?: DokployCompose[];\n\tredis?: DokployRedis[];\n}\n\nexport interface DokployProjectDetails extends DokployProject {\n\tenvironments: DokployEnvironment[];\n}\n\nexport interface DokployApplication {\n\tapplicationId: string;\n\tname: string;\n\tappName: string;\n\tprojectId: string;\n\tenvironmentId?: string;\n}\n\nexport interface DokployApplicationUpdate {\n\tregistryId: string;\n\tdockerImage: string;\n\tsourceType: 'docker';\n}\n\nexport interface DokployRegistry {\n\tregistryId: string;\n\tregistryName: string;\n\tregistryUrl: string;\n\tusername: string;\n\timagePrefix: string | null;\n}\n\nexport interface DokployPostgres {\n\tpostgresId: string;\n\tname: string;\n\tappName: string;\n\tdatabaseName: string;\n\tdatabaseUser: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string | null;\n\tprojectId: string;\n\tenvironmentId: string;\n\tapplicationStatus: 'idle' | 'running' | 'done' | 'error';\n\texternalPort: number | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployPostgresUpdate {\n\tname: string;\n\tappName: string;\n\tdatabaseName: string;\n\tdatabaseUser: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string;\n}\n\nexport interface DokployRedis {\n\tredisId: string;\n\tname: string;\n\tappName: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string | null;\n\tprojectId: string;\n\tenvironmentId: string;\n\tapplicationStatus: 'idle' | 'running' | 'done' | 'error';\n\texternalPort: number | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployRedisUpdate {\n\tname: string;\n\tappName: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string;\n}\n\nexport type DokployCertificateType = 'letsencrypt' | 'none' | 'custom';\n/** A Dokploy compose stack. */\nexport interface DokployCompose {\n\tcomposeId: string;\n\tname: string;\n\t/** Dokploy's own generated name, which prefixes the stack's containers. */\n\tappName: string;\n\tcomposeFile?: string;\n\tcomposeStatus?: string;\n}\n\nexport type DokployDomainType = 'application' | 'compose' | 'preview';\n\nexport interface DokployDomainCreate {\n\t/** Domain hostname (e.g., 'api.example.com') */\n\thost: string;\n\t/** URL path (optional, e.g., '/api') */\n\tpath?: string | null;\n\t/** Container port to route to (1-65535) */\n\tport?: number | null;\n\t/** Enable HTTPS */\n\thttps?: boolean;\n\t/** Associated application ID */\n\tapplicationId?: string | null;\n\t/** Certificate type for HTTPS */\n\tcertificateType?: DokployCertificateType;\n\t/** Custom certificate resolver name */\n\tcustomCertResolver?: string | null;\n\t/** Docker Compose service ID */\n\tcomposeId?: string | null;\n\t/** Service name for compose */\n\tserviceName?: string | null;\n\t/** Domain type */\n\tdomainType?: DokployDomainType | null;\n\t/** Preview deployment ID */\n\tpreviewDeploymentId?: string | null;\n\t/** Internal routing path */\n\tinternalPath?: string | null;\n\t/** Strip path from forwarded requests */\n\tstripPath?: boolean;\n}\n\nexport interface DokployDomain extends DokployDomainCreate {\n\tdomainId: string;\n\tcreatedAt?: string;\n}\n\n// ============================================\n// Destination types (backup storage)\n// ============================================\n\nexport interface DokployDestination {\n\tdestinationId: string;\n\tname: string;\n\taccessKey: string;\n\tbucket: string;\n\tregion: string;\n\tendpoint: string | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployDestinationCreate {\n\t/** User-friendly name for the destination */\n\tname: string;\n\t/** S3 access key ID */\n\taccessKey: string;\n\t/** S3 secret access key */\n\tsecretAccessKey: string;\n\t/** S3 bucket name */\n\tbucket: string;\n\t/** AWS region (e.g., 'us-east-1') */\n\tregion: string;\n\t/** Optional endpoint for S3-compatible services */\n\tendpoint?: string;\n}\n\n// ============================================\n// Backup types (scheduled backups)\n// ============================================\n\nexport type DokployDatabaseType = 'postgres' | 'mysql' | 'mariadb' | 'mongo';\n\nexport interface DokployBackup {\n\tbackupId: string;\n\tschedule: string;\n\tprefix: string;\n\tenabled: boolean;\n\tdestinationId: string;\n\tpostgresId?: string;\n\tdatabaseType: DokployDatabaseType;\n\tkeepLatestCount: number | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployBackupCreate {\n\t/** Cron schedule (e.g., '0 2 * * *' for 2 AM daily) */\n\tschedule: string;\n\t/** Backup file prefix (e.g., 'production/postgres') */\n\tprefix: string;\n\t/** Destination ID for backup storage */\n\tdestinationId: string;\n\t/** Database name to backup */\n\tdatabase: string;\n\t/** Postgres instance ID */\n\tpostgresId: string;\n\t/** Enable/disable backup (default: true) */\n\tenabled?: boolean;\n\t/** Number of backups to keep (default: keep all) */\n\tkeepLatestCount?: number;\n}\n\nexport interface DokployBackupUpdate {\n\tschedule?: string;\n\tprefix?: string;\n\tenabled?: boolean;\n\tkeepLatestCount?: number;\n}\n\n/**\n * Create a Dokploy API client from stored credentials or environment\n */\nexport async function createDokployApi(\n\tendpoint?: string,\n): Promise<DokployApi | null> {\n\tconst { getDokployCredentials } = await import('../auth/credentials');\n\n\t// Try environment variable first\n\tconst envToken = process.env.DOKPLOY_API_TOKEN;\n\tconst envEndpoint = endpoint || process.env.DOKPLOY_ENDPOINT;\n\n\tif (envToken && envEndpoint) {\n\t\treturn new DokployApi({ baseUrl: envEndpoint, token: envToken });\n\t}\n\n\t// Fall back to stored credentials\n\tconst creds = await getDokployCredentials();\n\tif (creds) {\n\t\treturn new DokployApi({\n\t\t\tbaseUrl: endpoint || creds.endpoint,\n\t\t\ttoken: creds.token,\n\t\t});\n\t}\n\n\treturn null;\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { SniffResult } from '@geekmidas/envkit/sniffer';\nimport { output } from '../output.js';\nimport { withOwningTsconfigJsx } from '../owningTsconfigJsx.js';\nimport { normalizeRoutes } from '../workspace/client-generator.js';\nimport { getPublicEnvPrefix } from '../workspace/publicEnv.js';\nimport type { NormalizedAppConfig } from '../workspace/types.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Resolve the tsx package path from the CLI package's dependencies.\n * This ensures tsx is available regardless of whether the target project has it installed.\n */\nfunction resolveTsxPath(): string {\n\tconst require = createRequire(import.meta.url);\n\treturn require.resolve('tsx');\n}\n\n/**\n * Resolve the path to a sniffer helper file.\n * Handles both dev (.ts with tsx) and production (.mjs from dist).\n *\n * In production: sniffer.ts is bundled into dist/index.mjs, but sniffer helper\n * files are output to dist/deploy/ as standalone modules for subprocess loading.\n *\n * In development: All files are in src/deploy/ and loaded via tsx.\n */\nfunction resolveSnifferFile(baseName: string): string {\n\t// Try deploy/ subdirectory first (production: bundled code is at dist/index.mjs,\n\t// but sniffer files are at dist/deploy/)\n\tconst deployMjsPath = resolve(__dirname, 'deploy', `${baseName}.mjs`);\n\tif (existsSync(deployMjsPath)) {\n\t\treturn deployMjsPath;\n\t}\n\n\t// Try same directory .mjs (production: if running from dist/deploy/ directly)\n\tconst mjsPath = resolve(__dirname, `${baseName}.mjs`);\n\tif (existsSync(mjsPath)) {\n\t\treturn mjsPath;\n\t}\n\n\t// Try same directory .ts (development with tsx: all files in src/deploy/)\n\tconst tsPath = resolve(__dirname, `${baseName}.ts`);\n\tif (existsSync(tsPath)) {\n\t\treturn tsPath;\n\t}\n\n\t// Fallback to .ts (will error if neither exists)\n\treturn tsPath;\n}\n\n// Re-export SniffResult for consumers\nexport type { SniffResult } from '@geekmidas/envkit/sniffer';\n\n/**\n * Result of sniffing an app's environment requirements.\n */\nexport interface SniffedEnvironment {\n\tappName: string;\n\t/**\n\t * All required environment variable names.\n\t * When `SniffAppOptions.markOptional` is true, optional variables are\n\t * suffixed with `?` (e.g. `PORT?`).\n\t */\n\trequiredEnvVars: string[];\n\t/**\n\t * Subset of variables that are optional (accessed via `.optional()` or\n\t * `.default()`). Always populated regardless of `markOptional`.\n\t */\n\toptionalEnvVars: string[];\n}\n\n/**\n * Options for sniffing an app's environment.\n */\nexport interface SniffAppOptions {\n\t/** Whether to log warnings for errors encountered during sniffing. Defaults to true. */\n\tlogWarnings?: boolean;\n\t/**\n\t * When true, optional variables (those with `.optional()` or `.default()`)\n\t * are suffixed with `?` in `requiredEnvVars` (e.g. `PORT?`).\n\t * Defaults to false.\n\t */\n\tmarkOptional?: boolean;\n}\n\n/**\n * Get required environment variables for an app.\n *\n * Detection strategy (in order):\n * 1. Frontend apps: Returns empty (no server secrets)\n * 2. Apps with `requiredEnv`: Uses explicit list from config\n * 3. Entry apps: Imports entry file in subprocess to capture config.parse() calls\n * 4. Route-based apps: Loads route files and calls getEnvironment() on each construct\n * 5. Apps with `envParser` (no routes): Runs SnifferEnvironmentParser to detect usage\n * 6. Apps with neither: Returns empty\n *\n * This function handles \"fire and forget\" async operations gracefully,\n * capturing errors and unhandled rejections without failing the build.\n *\n * @param app - The normalized app configuration\n * @param appName - The name of the app\n * @param workspacePath - Absolute path to the workspace root\n * @param options - Optional configuration for sniffing behavior\n * @returns The sniffed environment with required variables\n */\nexport async function sniffAppEnvironment(\n\tapp: NormalizedAppConfig,\n\tappName: string,\n\tworkspacePath: string,\n\toptions: SniffAppOptions = {},\n): Promise<SniffedEnvironment> {\n\tconst { logWarnings = true, markOptional = false } = options;\n\n\t// 1. Frontend apps - handle dependencies and config sniffing\n\tif (app.type === 'web' || app.type === 'mobile') {\n\t\t// Auto-generate {prefix}{DEP}_URL from dependencies, where prefix matches\n\t\t// the framework's public-var convention (NEXT_PUBLIC_, VITE_, ...).\n\t\t// For frameworks without a prefix (e.g. Remix), no dep var is required.\n\t\tconst publicPrefix = getPublicEnvPrefix(app.framework);\n\t\tconst depVars = publicPrefix\n\t\t\t? (app.dependencies ?? []).map(\n\t\t\t\t\t(dep) => `${publicPrefix}${dep.toUpperCase()}_URL`,\n\t\t\t\t)\n\t\t\t: [];\n\n\t\t// If config specified, sniff by importing the file(s)\n\t\t// The file calls .parse() at module load, which triggers sniffer to capture vars\n\t\tif (app.config) {\n\t\t\tconst sniffedVars: string[] = [];\n\t\t\tconst sniffedOptional: string[] = [];\n\n\t\t\t// Collect config paths to sniff\n\t\t\tconst configPaths: string[] = [];\n\t\t\tif (app.config.client) configPaths.push(app.config.client);\n\t\t\tif (app.config.server) configPaths.push(app.config.server);\n\n\t\t\t// Sniff each config file\n\t\t\tfor (const configPath of configPaths) {\n\t\t\t\tconst result = await sniffEntryFile(\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tapp.path,\n\t\t\t\t\tworkspacePath,\n\t\t\t\t);\n\n\t\t\t\tif (logWarnings && result.error) {\n\t\t\t\t\toutput.warn(\n\t\t\t\t\t\t`[sniffer] ${appName}: Config file \"${configPath}\" threw error during sniffing (env vars still captured): ${result.error.message}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tsniffedVars.push(...result.envVars);\n\t\t\t\tsniffedOptional.push(...result.optionalEnvVars);\n\t\t\t}\n\n\t\t\tconst optionalEnvVars = [...new Set(sniffedOptional)];\n\t\t\t// Combine: dependency vars + sniffed vars (deduplicated)\n\t\t\tconst allVars = applyMarkOptional(\n\t\t\t\t[...new Set([...depVars, ...sniffedVars])],\n\t\t\t\toptionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t);\n\t\t\treturn { appName, requiredEnvVars: allVars, optionalEnvVars };\n\t\t}\n\n\t\treturn { appName, requiredEnvVars: depVars, optionalEnvVars: [] };\n\t}\n\n\t// 2. Entry apps - import entry file in subprocess to trigger config.parse()\n\tif (app.entry) {\n\t\tconst result = await sniffEntryFile(app.entry, app.path, workspacePath);\n\n\t\tif (logWarnings && result.error) {\n\t\t\toutput.warn(\n\t\t\t\t`[sniffer] ${appName}: Entry file threw error during sniffing (env vars still captured): ${result.error.message}`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tappName,\n\t\t\trequiredEnvVars: applyMarkOptional(\n\t\t\t\tresult.envVars,\n\t\t\t\tresult.optionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t),\n\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t};\n\t}\n\n\t// 4. Route-based apps - load routes and call getEnvironment() on each construct\n\tif (app.routes) {\n\t\tconst result = await sniffRouteFiles(\n\t\t\tnormalizeRoutes(app.routes),\n\t\t\tapp.path,\n\t\t\tworkspacePath,\n\t\t);\n\n\t\tif (logWarnings && result.error) {\n\t\t\toutput.warn(\n\t\t\t\t`[sniffer] ${appName}: Route sniffing threw error (env vars still captured): ${result.error.message}`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tappName,\n\t\t\trequiredEnvVars: applyMarkOptional(\n\t\t\t\tresult.envVars,\n\t\t\t\tresult.optionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t),\n\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t};\n\t}\n\n\t// 5. Apps with envParser but no routes - run sniffer to detect env var usage\n\tif (app.envParser) {\n\t\tconst result = await sniffEnvParser(app.envParser, app.path, workspacePath);\n\n\t\t// Log any issues for debugging\n\t\tif (logWarnings) {\n\t\t\tif (result.error) {\n\t\t\t\toutput.warn(\n\t\t\t\t\t`[sniffer] ${appName}: envParser threw error during sniffing (env vars still captured): ${result.error.message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (result.unhandledRejections.length > 0) {\n\t\t\t\toutput.warn(\n\t\t\t\t\t`[sniffer] ${appName}: Fire-and-forget rejections during sniffing (suppressed): ${result.unhandledRejections.map((e) => e.message).join(', ')}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tappName,\n\t\t\trequiredEnvVars: applyMarkOptional(\n\t\t\t\tresult.envVars,\n\t\t\t\tresult.optionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t),\n\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t};\n\t}\n\n\t// No env detection method available\n\treturn { appName, requiredEnvVars: [], optionalEnvVars: [] };\n}\n\n/**\n * Apply the `?` suffix to optional variables in an env var list.\n * Only modifies the list when `markOptional` is true and there are optional vars.\n */\nfunction applyMarkOptional(\n\tenvVars: string[],\n\toptionalEnvVars: string[],\n\tmarkOptional: boolean,\n): string[] {\n\tif (!markOptional || optionalEnvVars.length === 0) return envVars;\n\tconst optionalSet = new Set(optionalEnvVars);\n\treturn envVars.map((v) => (optionalSet.has(v) ? `${v}?` : v));\n}\n\n/**\n * Result from sniffing an entry file.\n */\ninterface EntrySniffResult {\n\tenvVars: string[];\n\toptionalEnvVars: string[];\n\terror?: Error;\n}\n\n/**\n * Sniff an entry file by importing it in a subprocess.\n *\n * Entry apps call `config.parse()` at module load time. To capture which\n * env vars are accessed, we:\n * 1. Spawn a subprocess with a module loader hook\n * 2. The loader intercepts `@geekmidas/envkit` and replaces EnvironmentParser\n * with SnifferEnvironmentParser\n * 3. Import the entry file (triggers config.parse())\n * 4. Capture and return the accessed env var names\n *\n * This approach provides process isolation - each app is sniffed in its own\n * subprocess, preventing module cache pollution.\n *\n * @param entryPath - Relative path to the entry file (e.g., './src/index.ts')\n * @param appPath - The app's path relative to workspace (e.g., 'apps/auth')\n * @param workspacePath - Absolute path to workspace root\n * @returns EntrySniffResult with env vars and optional error\n */\nasync function sniffEntryFile(\n\tentryPath: string,\n\tappPath: string,\n\tworkspacePath: string,\n): Promise<EntrySniffResult> {\n\tconst fullEntryPath = resolve(workspacePath, appPath, entryPath);\n\tconst loaderPath = resolveSnifferFile('sniffer-loader');\n\tconst workerPath = resolveSnifferFile('sniffer-worker');\n\n\treturn new Promise((resolvePromise) => {\n\t\tconst child = spawn(\n\t\t\t'node',\n\t\t\t['--import', loaderPath, workerPath, fullEntryPath],\n\t\t\t{\n\t\t\t\tcwd: resolve(workspacePath, appPath),\n\t\t\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\t\t\tenv: {\n\t\t\t\t\t...process.env,\n\t\t\t\t\t// Ensure tsx is available for TypeScript entry files, with each\n\t\t\t\t\t// `.tsx` compiled by its own tsconfig's JSX settings rather than\n\t\t\t\t\t// whatever the app's tsconfig includes.\n\t\t\t\t\tNODE_OPTIONS: withOwningTsconfigJsx('--import=tsx'),\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\n\t\tchild.stdout.on('data', (data) => {\n\t\t\tstdout += data.toString();\n\t\t});\n\n\t\tchild.stderr.on('data', (data) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\tchild.on('close', (code) => {\n\t\t\t// Try to parse the JSON output from the worker\n\t\t\ttry {\n\t\t\t\t// Find the last JSON object in stdout (worker may emit other output)\n\t\t\t\tconst jsonMatch = stdout.match(/\\{[^{}]*\"envVars\"[^{}]*\\}[^{]*$/);\n\t\t\t\tif (jsonMatch) {\n\t\t\t\t\tconst result = JSON.parse(jsonMatch[0]);\n\t\t\t\t\tresolvePromise({\n\t\t\t\t\t\tenvVars: result.envVars || [],\n\t\t\t\t\t\toptionalEnvVars: result.optionalEnvVars || [],\n\t\t\t\t\t\terror: result.error ? new Error(result.error) : undefined,\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// JSON parse failed\n\t\t\t}\n\n\t\t\t// If we couldn't parse the output, return empty with error info\n\t\t\tresolvePromise({\n\t\t\t\tenvVars: [],\n\t\t\t\toptionalEnvVars: [],\n\t\t\t\terror: new Error(\n\t\t\t\t\t`Failed to sniff entry file (exit code ${code}): ${stderr || stdout || 'No output'}`,\n\t\t\t\t),\n\t\t\t});\n\t\t});\n\n\t\tchild.on('error', (err) => {\n\t\t\tresolvePromise({\n\t\t\t\tenvVars: [],\n\t\t\t\toptionalEnvVars: [],\n\t\t\t\terror: err,\n\t\t\t});\n\t\t});\n\t});\n}\n\n/**\n * Sniff route files by loading constructs and calling getEnvironment().\n *\n * Route-based apps have endpoints, functions, crons, and subscribers that\n * use services. Each service's register() method accesses environment variables.\n *\n * This runs in a subprocess with tsx loader to properly handle TypeScript\n * compilation and path alias resolution (e.g., `src/...` imports).\n *\n * @param routes - Glob pattern(s) for route files\n * @param appPath - The app's path relative to workspace (e.g., 'apps/api')\n * @param workspacePath - Absolute path to workspace root\n * @returns EntrySniffResult with env vars and optional error\n */\nasync function sniffRouteFiles(\n\troutes: string | string[],\n\tappPath: string,\n\tworkspacePath: string,\n): Promise<EntrySniffResult> {\n\tconst fullAppPath = resolve(workspacePath, appPath);\n\tconst workerPath = resolveSnifferFile('sniffer-routes-worker');\n\tconst tsxPath = resolveTsxPath();\n\n\t// Convert array of patterns to first pattern (worker handles glob internally)\n\tconst routesArray = Array.isArray(routes) ? routes : [routes];\n\tconst pattern = routesArray[0];\n\tif (!pattern) {\n\t\treturn {\n\t\t\tenvVars: [],\n\t\t\toptionalEnvVars: [],\n\t\t\terror: new Error('No route patterns provided'),\n\t\t};\n\t}\n\n\treturn new Promise((resolvePromise) => {\n\t\tconst child = spawn(\n\t\t\t'node',\n\t\t\t['--import', tsxPath, workerPath, fullAppPath, pattern],\n\t\t\t{\n\t\t\t\tcwd: fullAppPath,\n\t\t\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\t\t\tenv: {\n\t\t\t\t\t...process.env,\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\n\t\tchild.stdout.on('data', (data) => {\n\t\t\tstdout += data.toString();\n\t\t});\n\n\t\tchild.stderr.on('data', (data) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\tchild.on('close', (code) => {\n\t\t\t// Log any stderr output (import errors, etc.)\n\t\t\tif (stderr) {\n\t\t\t\tstderr\n\t\t\t\t\t.split('\\n')\n\t\t\t\t\t.filter((line) => line.trim())\n\t\t\t\t\t.forEach((line) => output.warn(line));\n\t\t\t}\n\n\t\t\t// Try to parse the JSON output from the worker\n\t\t\ttry {\n\t\t\t\t// Find the last JSON object in stdout (worker may emit other output)\n\t\t\t\tconst jsonMatch = stdout.match(/\\{[^{}]*\"envVars\"[^{}]*\\}[^{]*$/);\n\t\t\t\tif (jsonMatch) {\n\t\t\t\t\tconst result = JSON.parse(jsonMatch[0]);\n\t\t\t\t\tresolvePromise({\n\t\t\t\t\t\tenvVars: result.envVars || [],\n\t\t\t\t\t\toptionalEnvVars: result.optionalEnvVars || [],\n\t\t\t\t\t\terror: result.error ? new Error(result.error) : undefined,\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// JSON parse failed\n\t\t\t}\n\n\t\t\t// If we couldn't parse the output, return empty with error info\n\t\t\tresolvePromise({\n\t\t\t\tenvVars: [],\n\t\t\t\toptionalEnvVars: [],\n\t\t\t\terror: new Error(\n\t\t\t\t\t`Failed to sniff route files (exit code ${code}): ${stderr || stdout || 'No output'}`,\n\t\t\t\t),\n\t\t\t});\n\t\t});\n\n\t\tchild.on('error', (err) => {\n\t\t\tresolvePromise({\n\t\t\t\tenvVars: [],\n\t\t\t\toptionalEnvVars: [],\n\t\t\t\terror: err,\n\t\t\t});\n\t\t});\n\t});\n}\n\n/**\n * Run the SnifferEnvironmentParser on an envParser module to detect\n * which environment variables it accesses.\n *\n * This function handles \"fire and forget\" async operations by using\n * the shared sniffWithFireAndForget utility from @geekmidas/envkit.\n *\n * @param envParserPath - The envParser config (e.g., './src/config/env#envParser')\n * @param appPath - The app's path relative to workspace\n * @param workspacePath - Absolute path to workspace root\n * @returns SniffResult with env vars and any errors encountered\n */\nasync function sniffEnvParser(\n\tenvParserPath: string,\n\tappPath: string,\n\tworkspacePath: string,\n): Promise<SniffResult> {\n\t// Parse the envParser path: './src/config/env#envParser' or './src/config/env'\n\tconst [modulePath, exportName = 'default'] = envParserPath.split('#');\n\tif (!modulePath) {\n\t\treturn { envVars: [], optionalEnvVars: [], unhandledRejections: [] };\n\t}\n\n\t// Resolve the full path to the module\n\tconst fullPath = resolve(workspacePath, appPath, modulePath);\n\n\t// Dynamically import the sniffer utilities\n\tlet SnifferEnvironmentParser: any;\n\tlet sniffWithFireAndForget: any;\n\ttry {\n\t\tconst envkitModule = await import('@geekmidas/envkit/sniffer');\n\t\tSnifferEnvironmentParser = envkitModule.SnifferEnvironmentParser;\n\t\tsniffWithFireAndForget = envkitModule.sniffWithFireAndForget;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\toutput.warn(\n\t\t\t`[sniffer] Failed to import SnifferEnvironmentParser: ${message}`,\n\t\t);\n\t\treturn { envVars: [], optionalEnvVars: [], unhandledRejections: [] };\n\t}\n\n\tconst sniffer = new SnifferEnvironmentParser();\n\n\treturn sniffWithFireAndForget(sniffer, async () => {\n\t\t// Import the envParser module\n\t\tconst moduleUrl = pathToFileURL(fullPath).href;\n\t\tconst module = await import(moduleUrl);\n\n\t\t// Get the envParser function\n\t\tconst envParser = module[exportName];\n\t\tif (typeof envParser !== 'function') {\n\t\t\toutput.warn(\n\t\t\t\t`[sniffer] Export \"${exportName}\" from \"${modulePath}\" is not a function`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// The envParser function typically creates and configures an EnvironmentParser.\n\t\t// We pass our sniffer which implements the same interface.\n\t\tconst result = envParser(sniffer);\n\n\t\t// If the result is a ConfigParser, call parse() to trigger env var access\n\t\tif (result && typeof result.parse === 'function') {\n\t\t\ttry {\n\t\t\t\tresult.parse();\n\t\t\t} catch {\n\t\t\t\t// Parsing may fail due to mock values, that's expected\n\t\t\t}\n\t\t}\n\t});\n}\n\n/**\n * Sniff environment requirements for multiple apps.\n *\n * @param apps - Map of app name to app config\n * @param workspacePath - Absolute path to workspace root\n * @param options - Optional configuration for sniffing behavior\n * @returns Map of app name to sniffed environment\n */\nexport async function sniffAllApps(\n\tapps: Record<string, NormalizedAppConfig>,\n\tworkspacePath: string,\n\toptions: SniffAppOptions = {},\n): Promise<Map<string, SniffedEnvironment>> {\n\tconst results = new Map<string, SniffedEnvironment>();\n\n\tfor (const [appName, app] of Object.entries(apps)) {\n\t\tconst sniffed = await sniffAppEnvironment(\n\t\t\tapp,\n\t\t\tappName,\n\t\t\tworkspacePath,\n\t\t\toptions,\n\t\t);\n\t\tresults.set(appName, sniffed);\n\t}\n\n\treturn results;\n}\n\n// Export for testing\nexport {\n\tsniffEnvParser as _sniffEnvParser,\n\tsniffEntryFile as _sniffEntryFile,\n\tsniffRouteFiles as _sniffRouteFiles,\n};\n"],"mappings":"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"}