@geekmidas/cli 10.0.0-alpha.12 → 10.0.0-alpha.121

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 (517) hide show
  1. package/README.md +310 -377
  2. package/bin/adjacent-tsconfig.mjs +46 -8
  3. package/bin/gkm.mjs +11 -3
  4. package/bin/owning-tsconfig-jsx-register.mjs +18 -0
  5. package/bin/owning-tsconfig-jsx.mjs +108 -0
  6. package/dist/EndpointGenerator-BDa6pPgE.cjs +1090 -0
  7. package/dist/EndpointGenerator-BDa6pPgE.cjs.map +1 -0
  8. package/dist/EndpointGenerator-CP_z_E3e.mjs +1031 -0
  9. package/dist/EndpointGenerator-CP_z_E3e.mjs.map +1 -0
  10. package/dist/GoDaddyProvider-3C8U_tVu.cjs +388 -0
  11. package/dist/GoDaddyProvider-3C8U_tVu.cjs.map +1 -0
  12. package/dist/GoDaddyProvider-D0MQoOM4.mjs +388 -0
  13. package/dist/GoDaddyProvider-D0MQoOM4.mjs.map +1 -0
  14. package/dist/{HostingerProvider-BcyFRbqV.mjs → HostingerProvider-BNpS9aRJ.mjs} +29 -25
  15. package/dist/HostingerProvider-BNpS9aRJ.mjs.map +1 -0
  16. package/dist/{HostingerProvider-BOvsgATO.cjs → HostingerProvider-DR1iws1P.cjs} +29 -25
  17. package/dist/HostingerProvider-DR1iws1P.cjs.map +1 -0
  18. package/dist/LegacyStateStore-CRi7TosX.mjs +124 -0
  19. package/dist/LegacyStateStore-CRi7TosX.mjs.map +1 -0
  20. package/dist/LegacyStateStore-DHsoQGOo.cjs +124 -0
  21. package/dist/LegacyStateStore-DHsoQGOo.cjs.map +1 -0
  22. package/dist/LocalStateStore-BQXTkeoS.cjs +178 -0
  23. package/dist/LocalStateStore-BQXTkeoS.cjs.map +1 -0
  24. package/dist/LocalStateStore-Dkth0jXX.mjs +167 -0
  25. package/dist/LocalStateStore-Dkth0jXX.mjs.map +1 -0
  26. package/dist/OpenApiTsGenerator-CsrukCE-.mjs +536 -0
  27. package/dist/OpenApiTsGenerator-CsrukCE-.mjs.map +1 -0
  28. package/dist/OpenApiTsGenerator-FNhY7Ssz.cjs +541 -0
  29. package/dist/OpenApiTsGenerator-FNhY7Ssz.cjs.map +1 -0
  30. package/dist/{Route53Provider-CH9XH7bz.cjs → Route53Provider-Bllus2NR.cjs} +21 -22
  31. package/dist/Route53Provider-Bllus2NR.cjs.map +1 -0
  32. package/dist/{Route53Provider-C0xmer8U.mjs → Route53Provider-QXxjkZO1.mjs} +13 -13
  33. package/dist/Route53Provider-QXxjkZO1.mjs.map +1 -0
  34. package/dist/S3StateStore-DGKZgyKz.cjs +222 -0
  35. package/dist/S3StateStore-DGKZgyKz.cjs.map +1 -0
  36. package/dist/S3StateStore-pZZnLLBd.mjs +223 -0
  37. package/dist/S3StateStore-pZZnLLBd.mjs.map +1 -0
  38. package/dist/SSMStateStore-CZRMERDA.mjs +205 -0
  39. package/dist/SSMStateStore-CZRMERDA.mjs.map +1 -0
  40. package/dist/SSMStateStore-DzkyjQ5D.cjs +204 -0
  41. package/dist/SSMStateStore-DzkyjQ5D.cjs.map +1 -0
  42. package/dist/SubscriberGenerator-C8CPxrCK.cjs +592 -0
  43. package/dist/SubscriberGenerator-C8CPxrCK.cjs.map +1 -0
  44. package/dist/SubscriberGenerator-y3pncae_.mjs +533 -0
  45. package/dist/SubscriberGenerator-y3pncae_.mjs.map +1 -0
  46. package/dist/add-C0kXOsWS.mjs +717 -0
  47. package/dist/add-C0kXOsWS.mjs.map +1 -0
  48. package/dist/add-Df8cmz0H.cjs +719 -0
  49. package/dist/add-Df8cmz0H.cjs.map +1 -0
  50. package/dist/appPorts-BoXMwolc.mjs +249 -0
  51. package/dist/appPorts-BoXMwolc.mjs.map +1 -0
  52. package/dist/appPorts-IW-OZmRi.cjs +296 -0
  53. package/dist/appPorts-IW-OZmRi.cjs.map +1 -0
  54. package/dist/aws-8QQ8n-t6.mjs +89 -0
  55. package/dist/aws-8QQ8n-t6.mjs.map +1 -0
  56. package/dist/aws-DY9NqQIT.cjs +100 -0
  57. package/dist/aws-DY9NqQIT.cjs.map +1 -0
  58. package/dist/awsStore-Bx5nCFnS.mjs +121 -0
  59. package/dist/awsStore-Bx5nCFnS.mjs.map +1 -0
  60. package/dist/awsStore-u3ehhMoY.cjs +138 -0
  61. package/dist/awsStore-u3ehhMoY.cjs.map +1 -0
  62. package/dist/{backup-provisioner-DXXg6FIL.mjs → backup-provisioner-C8DnCmxK.mjs} +11 -8
  63. package/dist/backup-provisioner-C8DnCmxK.mjs.map +1 -0
  64. package/dist/{backup-provisioner-cE2PuCTb.cjs → backup-provisioner-oDEsPj02.cjs} +22 -20
  65. package/dist/backup-provisioner-oDEsPj02.cjs.map +1 -0
  66. package/dist/backups/runner.cjs +519 -0
  67. package/dist/backups/runner.cjs.map +1 -0
  68. package/dist/backups/runner.d.cts +102 -0
  69. package/dist/backups/runner.d.cts.map +1 -0
  70. package/dist/backups/runner.d.mts +102 -0
  71. package/dist/backups/runner.d.mts.map +1 -0
  72. package/dist/backups/runner.mjs +506 -0
  73. package/dist/backups/runner.mjs.map +1 -0
  74. package/dist/{bundler-Bux2iF-v.mjs → bundler-B-I4Du99.mjs} +35 -16
  75. package/dist/bundler-B-I4Du99.mjs.map +1 -0
  76. package/dist/{bundler-BMekgx7a.cjs → bundler-Dx_grUmc.cjs} +37 -19
  77. package/dist/bundler-Dx_grUmc.cjs.map +1 -0
  78. package/dist/cli-Cd9exatJ.cjs +71 -0
  79. package/dist/cli-Cd9exatJ.cjs.map +1 -0
  80. package/dist/cli-DL7GxGAe.mjs +71 -0
  81. package/dist/cli-DL7GxGAe.mjs.map +1 -0
  82. package/dist/compose-C43c03WZ.mjs +3284 -0
  83. package/dist/compose-C43c03WZ.mjs.map +1 -0
  84. package/dist/compose-Mdgea4Yt.cjs +3427 -0
  85. package/dist/compose-Mdgea4Yt.cjs.map +1 -0
  86. package/dist/config-D48erO2j.d.mts +269 -0
  87. package/dist/config-D48erO2j.d.mts.map +1 -0
  88. package/dist/config-DHDtsQrB.d.cts +269 -0
  89. package/dist/config-DHDtsQrB.d.cts.map +1 -0
  90. package/dist/config.cjs +421 -11
  91. package/dist/config.cjs.map +1 -0
  92. package/dist/config.d.cts +4 -120
  93. package/dist/config.d.mts +4 -120
  94. package/dist/config.mjs +405 -4
  95. package/dist/config.mjs.map +1 -0
  96. package/dist/credentialSchemas-B67WJOOm.cjs +272 -0
  97. package/dist/credentialSchemas-B67WJOOm.cjs.map +1 -0
  98. package/dist/credentialSchemas-DDOLRCLF.mjs +237 -0
  99. package/dist/credentialSchemas-DDOLRCLF.mjs.map +1 -0
  100. package/dist/credentials-B5N5yKk6.mjs +267 -0
  101. package/dist/credentials-B5N5yKk6.mjs.map +1 -0
  102. package/dist/credentials-Zqr3QQYB.cjs +350 -0
  103. package/dist/credentials-Zqr3QQYB.cjs.map +1 -0
  104. package/dist/databases-BPOgTn1u.mjs +522 -0
  105. package/dist/databases-BPOgTn1u.mjs.map +1 -0
  106. package/dist/databases-fOaoBInm.cjs +625 -0
  107. package/dist/databases-fOaoBInm.cjs.map +1 -0
  108. package/dist/define-6poNqm5O.mjs +106 -0
  109. package/dist/define-6poNqm5O.mjs.map +1 -0
  110. package/dist/define-crmMcXFS.cjs +153 -0
  111. package/dist/define-crmMcXFS.cjs.map +1 -0
  112. package/dist/deploy/public.cjs +212 -0
  113. package/dist/deploy/public.cjs.map +1 -0
  114. package/dist/deploy/public.d.cts +654 -0
  115. package/dist/deploy/public.d.cts.map +1 -0
  116. package/dist/deploy/public.d.mts +654 -0
  117. package/dist/deploy/public.d.mts.map +1 -0
  118. package/dist/deploy/public.mjs +145 -0
  119. package/dist/deploy/public.mjs.map +1 -0
  120. package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
  121. package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
  122. package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
  123. package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
  124. package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
  125. package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
  126. package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
  127. package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
  128. package/dist/deploy/sniffer-envparser-worker.cjs +54 -0
  129. package/dist/deploy/sniffer-envparser-worker.cjs.map +1 -0
  130. package/dist/deploy/sniffer-envparser-worker.d.cts +1 -0
  131. package/dist/deploy/sniffer-envparser-worker.d.mts +1 -0
  132. package/dist/deploy/sniffer-envparser-worker.mjs +55 -0
  133. package/dist/deploy/sniffer-envparser-worker.mjs.map +1 -0
  134. package/dist/deploy/sniffer-hooks.cjs +13 -7
  135. package/dist/deploy/sniffer-hooks.cjs.map +1 -1
  136. package/dist/deploy/sniffer-hooks.d.cts +1 -2
  137. package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
  138. package/dist/deploy/sniffer-hooks.d.mts +1 -2
  139. package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
  140. package/dist/deploy/sniffer-hooks.mjs +10 -3
  141. package/dist/deploy/sniffer-hooks.mjs.map +1 -1
  142. package/dist/deploy/sniffer-loader.cjs +16 -7
  143. package/dist/deploy/sniffer-loader.cjs.map +1 -1
  144. package/dist/deploy/sniffer-loader.d.cts +1 -1
  145. package/dist/deploy/sniffer-loader.d.mts +1 -1
  146. package/dist/deploy/sniffer-loader.mjs +14 -3
  147. package/dist/deploy/sniffer-loader.mjs.map +1 -1
  148. package/dist/deploy/sniffer-routes-worker.cjs +11 -11
  149. package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
  150. package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
  151. package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
  152. package/dist/deploy/sniffer-routes-worker.mjs +4 -4
  153. package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
  154. package/dist/deploy/sniffer-worker.cjs +15 -6
  155. package/dist/deploy/sniffer-worker.cjs.map +1 -1
  156. package/dist/deploy/sniffer-worker.d.cts +1 -3
  157. package/dist/deploy/sniffer-worker.d.cts.map +1 -1
  158. package/dist/deploy/sniffer-worker.d.mts +1 -3
  159. package/dist/deploy/sniffer-worker.d.mts.map +1 -1
  160. package/dist/deploy/sniffer-worker.mjs +15 -4
  161. package/dist/deploy/sniffer-worker.mjs.map +1 -1
  162. package/dist/deploy-BMM2hx-K.cjs +2401 -0
  163. package/dist/deploy-BMM2hx-K.cjs.map +1 -0
  164. package/dist/deploy-D5YhwDng.mjs +2328 -0
  165. package/dist/deploy-D5YhwDng.mjs.map +1 -0
  166. package/dist/derive-B4vDpzgP.mjs +150 -0
  167. package/dist/derive-B4vDpzgP.mjs.map +1 -0
  168. package/dist/derive-DAbo49sm.cjs +161 -0
  169. package/dist/derive-DAbo49sm.cjs.map +1 -0
  170. package/dist/discover-C5roJuc9.mjs +560 -0
  171. package/dist/discover-C5roJuc9.mjs.map +1 -0
  172. package/dist/discover-CSW_BMWa.d.cts +125 -0
  173. package/dist/discover-CSW_BMWa.d.cts.map +1 -0
  174. package/dist/discover-DozUKs8f.cjs +639 -0
  175. package/dist/discover-DozUKs8f.cjs.map +1 -0
  176. package/dist/discover-xtT7fgWg.d.mts +125 -0
  177. package/dist/discover-xtT7fgWg.d.mts.map +1 -0
  178. package/dist/dns-BIYf20p7.cjs +1057 -0
  179. package/dist/dns-BIYf20p7.cjs.map +1 -0
  180. package/dist/dns-agf2VCdV.mjs +926 -0
  181. package/dist/dns-agf2VCdV.mjs.map +1 -0
  182. package/dist/dokploy-2p4PK6ej.mjs +38 -0
  183. package/dist/dokploy-2p4PK6ej.mjs.map +1 -0
  184. package/dist/dokploy-BANMTroy.cjs +38 -0
  185. package/dist/dokploy-BANMTroy.cjs.map +1 -0
  186. package/dist/drivers-BjGDYfnC.mjs +195 -0
  187. package/dist/drivers-BjGDYfnC.mjs.map +1 -0
  188. package/dist/drivers-DbjiYpW6.cjs +224 -0
  189. package/dist/drivers-DbjiYpW6.cjs.map +1 -0
  190. package/dist/eas-CNi16SoO.cjs +337 -0
  191. package/dist/eas-CNi16SoO.cjs.map +1 -0
  192. package/dist/eas-iT2JDHCh.mjs +334 -0
  193. package/dist/eas-iT2JDHCh.mjs.map +1 -0
  194. package/dist/{emit-CIlvnDvV.mjs → emit-C1e8qD08.mjs} +13 -26
  195. package/dist/emit-C1e8qD08.mjs.map +1 -0
  196. package/dist/{emit-CUD1BWls.cjs → emit-Dmc1nvdS.cjs} +31 -57
  197. package/dist/emit-Dmc1nvdS.cjs.map +1 -0
  198. package/dist/encryption-DkXVbyZc.mjs +89 -0
  199. package/dist/encryption-DkXVbyZc.mjs.map +1 -0
  200. package/dist/encryption-d2lLtkYx.cjs +90 -0
  201. package/dist/encryption-d2lLtkYx.cjs.map +1 -0
  202. package/dist/engine-CatKW6E-.mjs +3089 -0
  203. package/dist/engine-CatKW6E-.mjs.map +1 -0
  204. package/dist/engine-DlEHexED.cjs +3202 -0
  205. package/dist/engine-DlEHexED.cjs.map +1 -0
  206. package/dist/errors-D8Gwp5kU.mjs +23 -0
  207. package/dist/errors-D8Gwp5kU.mjs.map +1 -0
  208. package/dist/errors-DMC4Umsb.cjs +34 -0
  209. package/dist/errors-DMC4Umsb.cjs.map +1 -0
  210. package/dist/github-BAeaC1Pl.cjs +833 -0
  211. package/dist/github-BAeaC1Pl.cjs.map +1 -0
  212. package/dist/github-DErsqKGN.mjs +832 -0
  213. package/dist/github-DErsqKGN.mjs.map +1 -0
  214. package/dist/googlePlay-BVcfFWRO.cjs +931 -0
  215. package/dist/googlePlay-BVcfFWRO.cjs.map +1 -0
  216. package/dist/googlePlay-BXJ5ZjMK.mjs +931 -0
  217. package/dist/googlePlay-BXJ5ZjMK.mjs.map +1 -0
  218. package/dist/identity-CBXr4wNi.mjs +129 -0
  219. package/dist/identity-CBXr4wNi.mjs.map +1 -0
  220. package/dist/identity-CbLfgQNT.cjs +182 -0
  221. package/dist/identity-CbLfgQNT.cjs.map +1 -0
  222. package/dist/{index-BzJ7QJ9c.d.mts → index-8OfpOtSI.d.cts} +39 -20
  223. package/dist/index-8OfpOtSI.d.cts.map +1 -0
  224. package/dist/{index-CfMjtdVC.d.cts → index-Dpji6Bxq.d.mts} +39 -20
  225. package/dist/index-Dpji6Bxq.d.mts.map +1 -0
  226. package/dist/index.cjs +14291 -11898
  227. package/dist/index.cjs.map +1 -1
  228. package/dist/index.d.cts +1 -1
  229. package/dist/index.d.mts +1 -1
  230. package/dist/index.mjs +13938 -11560
  231. package/dist/index.mjs.map +1 -1
  232. package/dist/local-BwC2v2Y8.mjs +224 -0
  233. package/dist/local-BwC2v2Y8.mjs.map +1 -0
  234. package/dist/local-DNhnkugT.cjs +241 -0
  235. package/dist/local-DNhnkugT.cjs.map +1 -0
  236. package/dist/migrate-Bb1MmGHl.mjs +181 -0
  237. package/dist/migrate-Bb1MmGHl.mjs.map +1 -0
  238. package/dist/migrate-D79GI0XM.cjs +271 -0
  239. package/dist/migrate-D79GI0XM.cjs.map +1 -0
  240. package/dist/migrate-L4a273dY.cjs +181 -0
  241. package/dist/migrate-L4a273dY.cjs.map +1 -0
  242. package/dist/migrate-s4QizIoM.mjs +266 -0
  243. package/dist/migrate-s4QizIoM.mjs.map +1 -0
  244. package/dist/openapi.cjs +214 -10
  245. package/dist/openapi.cjs.map +1 -0
  246. package/dist/openapi.d.cts +38 -17
  247. package/dist/openapi.d.cts.map +1 -1
  248. package/dist/openapi.d.mts +38 -17
  249. package/dist/openapi.d.mts.map +1 -1
  250. package/dist/openapi.mjs +208 -5
  251. package/dist/openapi.mjs.map +1 -0
  252. package/dist/projectBucket-BBbQ1lHF.cjs +263 -0
  253. package/dist/projectBucket-BBbQ1lHF.cjs.map +1 -0
  254. package/dist/projectBucket-C_DN4WzF.mjs +222 -0
  255. package/dist/projectBucket-C_DN4WzF.mjs.map +1 -0
  256. package/dist/provider-BqpjG9QM.cjs +57 -0
  257. package/dist/provider-BqpjG9QM.cjs.map +1 -0
  258. package/dist/provider-CEOKk8NS.mjs +46 -0
  259. package/dist/provider-CEOKk8NS.mjs.map +1 -0
  260. package/dist/publish-CcTlmJT6.mjs +123 -0
  261. package/dist/publish-CcTlmJT6.mjs.map +1 -0
  262. package/dist/publish-ydscCYsW.cjs +124 -0
  263. package/dist/publish-ydscCYsW.cjs.map +1 -0
  264. package/dist/ready-BFFP838V.cjs +404 -0
  265. package/dist/ready-BFFP838V.cjs.map +1 -0
  266. package/dist/ready-BOfogqzr.mjs +375 -0
  267. package/dist/ready-BOfogqzr.mjs.map +1 -0
  268. package/dist/reconcile/public.cjs +3 -5
  269. package/dist/reconcile/public.d.cts +11 -87
  270. package/dist/reconcile/public.d.cts.map +1 -1
  271. package/dist/reconcile/public.d.mts +11 -87
  272. package/dist/reconcile/public.d.mts.map +1 -1
  273. package/dist/reconcile/public.mjs +3 -4
  274. package/dist/{reconcile-BI0tWkwz.mjs → reconcile--I7V4Klj.mjs} +4 -7
  275. package/dist/reconcile--I7V4Klj.mjs.map +1 -0
  276. package/dist/{reconcile-Bhw5fmKu.cjs → reconcile-CuCQBa5-.cjs} +5 -8
  277. package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
  278. package/dist/resolve-BLp2trep.mjs +263 -0
  279. package/dist/resolve-BLp2trep.mjs.map +1 -0
  280. package/dist/resolve-BgL-brF9.cjs +316 -0
  281. package/dist/resolve-BgL-brF9.cjs.map +1 -0
  282. package/dist/rollback-2cyLXEH1.cjs +165 -0
  283. package/dist/rollback-2cyLXEH1.cjs.map +1 -0
  284. package/dist/rollback-DwqZ8zzj.mjs +136 -0
  285. package/dist/rollback-DwqZ8zzj.mjs.map +1 -0
  286. package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
  287. package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
  288. package/dist/run-BJLIerMM.mjs +133 -0
  289. package/dist/run-BJLIerMM.mjs.map +1 -0
  290. package/dist/run-fVZ-rLnc.cjs +174 -0
  291. package/dist/run-fVZ-rLnc.cjs.map +1 -0
  292. package/dist/runtime-ACdfK59n.d.cts +82 -0
  293. package/dist/runtime-ACdfK59n.d.cts.map +1 -0
  294. package/dist/runtime-YyQuekxi.d.mts +82 -0
  295. package/dist/runtime-YyQuekxi.d.mts.map +1 -0
  296. package/dist/s3-CrPoA89T.cjs +215 -0
  297. package/dist/s3-CrPoA89T.cjs.map +1 -0
  298. package/dist/s3-DQXZdmCM.mjs +215 -0
  299. package/dist/s3-DQXZdmCM.mjs.map +1 -0
  300. package/dist/sandbox/config-worker.cjs +43 -0
  301. package/dist/sandbox/config-worker.cjs.map +1 -0
  302. package/dist/sandbox/config-worker.d.cts +1 -0
  303. package/dist/sandbox/config-worker.d.mts +1 -0
  304. package/dist/sandbox/config-worker.mjs +44 -0
  305. package/dist/sandbox/config-worker.mjs.map +1 -0
  306. package/dist/sandbox/credentials-worker.cjs +54 -0
  307. package/dist/sandbox/credentials-worker.cjs.map +1 -0
  308. package/dist/sandbox/credentials-worker.d.cts +1 -0
  309. package/dist/sandbox/credentials-worker.d.mts +1 -0
  310. package/dist/sandbox/credentials-worker.mjs +55 -0
  311. package/dist/sandbox/credentials-worker.mjs.map +1 -0
  312. package/dist/sandbox/discover-worker.cjs +50 -0
  313. package/dist/sandbox/discover-worker.cjs.map +1 -0
  314. package/dist/sandbox/discover-worker.d.cts +1 -0
  315. package/dist/sandbox/discover-worker.d.mts +1 -0
  316. package/dist/sandbox/discover-worker.mjs +51 -0
  317. package/dist/sandbox/discover-worker.mjs.map +1 -0
  318. package/dist/sandbox/migrate-worker.cjs +75 -0
  319. package/dist/sandbox/migrate-worker.cjs.map +1 -0
  320. package/dist/sandbox/migrate-worker.d.cts +1 -0
  321. package/dist/sandbox/migrate-worker.d.mts +1 -0
  322. package/dist/sandbox/migrate-worker.mjs +76 -0
  323. package/dist/sandbox/migrate-worker.mjs.map +1 -0
  324. package/dist/sandbox-C4g1eG31.d.mts +104 -0
  325. package/dist/sandbox-C4g1eG31.d.mts.map +1 -0
  326. package/dist/sandbox-CHv3OYXg.cjs +165 -0
  327. package/dist/sandbox-CHv3OYXg.cjs.map +1 -0
  328. package/dist/sandbox-CbVB7uOD.mjs +118 -0
  329. package/dist/sandbox-CbVB7uOD.mjs.map +1 -0
  330. package/dist/sandbox-CdKgQ3XU.d.cts +104 -0
  331. package/dist/sandbox-CdKgQ3XU.d.cts.map +1 -0
  332. package/dist/{workspace-DPV7FtAv.cjs → schemas-BCwKKA42.mjs} +958 -1816
  333. package/dist/schemas-BCwKKA42.mjs.map +1 -0
  334. package/dist/{workspace-CtnZnWsa.mjs → schemas-BQ_v0lEX.cjs} +1048 -1626
  335. package/dist/schemas-BQ_v0lEX.cjs.map +1 -0
  336. package/dist/secretsManager-CQORubp9.mjs +97 -0
  337. package/dist/secretsManager-CQORubp9.mjs.map +1 -0
  338. package/dist/secretsManager-RAGcEDX1.cjs +108 -0
  339. package/dist/secretsManager-RAGcEDX1.cjs.map +1 -0
  340. package/dist/seeds-C2w4UqVP.cjs +9347 -0
  341. package/dist/seeds-C2w4UqVP.cjs.map +1 -0
  342. package/dist/seeds-Cwtv-U9-.mjs +8350 -0
  343. package/dist/seeds-Cwtv-U9-.mjs.map +1 -0
  344. package/dist/serverSetup-BdFOD93s.cjs +631 -0
  345. package/dist/serverSetup-BdFOD93s.cjs.map +1 -0
  346. package/dist/serverSetup-utKsoyGH.mjs +631 -0
  347. package/dist/serverSetup-utKsoyGH.mjs.map +1 -0
  348. package/dist/sniffer-8C4q6Szd.mjs +1825 -0
  349. package/dist/sniffer-8C4q6Szd.mjs.map +1 -0
  350. package/dist/sniffer-CF7wNxMo.cjs +1998 -0
  351. package/dist/sniffer-CF7wNxMo.cjs.map +1 -0
  352. package/dist/sst-B-Uz4cvm.cjs +464 -0
  353. package/dist/sst-B-Uz4cvm.cjs.map +1 -0
  354. package/dist/sst-C1pxbFEd.mjs +441 -0
  355. package/dist/sst-C1pxbFEd.mjs.map +1 -0
  356. package/dist/stage-BvM_u2z4.mjs +302 -0
  357. package/dist/stage-BvM_u2z4.mjs.map +1 -0
  358. package/dist/stage-DEQJGW3r.cjs +313 -0
  359. package/dist/stage-DEQJGW3r.cjs.map +1 -0
  360. package/dist/stages-Cb9rqRbN.mjs +190 -0
  361. package/dist/stages-Cb9rqRbN.mjs.map +1 -0
  362. package/dist/stages-D4SnG5yl.cjs +190 -0
  363. package/dist/stages-D4SnG5yl.cjs.map +1 -0
  364. package/dist/target/public.cjs +15 -0
  365. package/dist/target/public.d.cts +28 -0
  366. package/dist/target/public.d.cts.map +1 -0
  367. package/dist/target/public.d.mts +28 -0
  368. package/dist/target/public.d.mts.map +1 -0
  369. package/dist/target/public.mjs +4 -0
  370. package/dist/terminal-CW_dWeTe.mjs +85 -0
  371. package/dist/terminal-CW_dWeTe.mjs.map +1 -0
  372. package/dist/terminal-DJlqcW_U.cjs +85 -0
  373. package/dist/terminal-DJlqcW_U.cjs.map +1 -0
  374. package/dist/types-B6UXlEkf.d.cts +7650 -0
  375. package/dist/types-B6UXlEkf.d.cts.map +1 -0
  376. package/dist/types-HqVPddRK.d.mts +7649 -0
  377. package/dist/types-HqVPddRK.d.mts.map +1 -0
  378. package/dist/utils-D2Nk1Nv2.cjs +205 -0
  379. package/dist/utils-D2Nk1Nv2.cjs.map +1 -0
  380. package/dist/utils-D_fxUqvU.mjs +156 -0
  381. package/dist/utils-D_fxUqvU.mjs.map +1 -0
  382. package/dist/vitest.cjs +65 -0
  383. package/dist/vitest.cjs.map +1 -0
  384. package/dist/vitest.d.cts +29 -0
  385. package/dist/vitest.d.cts.map +1 -0
  386. package/dist/vitest.d.mts +29 -0
  387. package/dist/vitest.d.mts.map +1 -0
  388. package/dist/vitest.mjs +65 -0
  389. package/dist/vitest.mjs.map +1 -0
  390. package/dist/workerRuntime-BXfhbe13.cjs +108 -0
  391. package/dist/workerRuntime-BXfhbe13.cjs.map +1 -0
  392. package/dist/workerRuntime-CsOyBRx2.mjs +79 -0
  393. package/dist/workerRuntime-CsOyBRx2.mjs.map +1 -0
  394. package/dist/workspace/index.cjs +8 -4
  395. package/dist/workspace/index.d.cts +3 -3
  396. package/dist/workspace/index.d.mts +3 -3
  397. package/dist/workspace/index.mjs +2 -3
  398. package/dist/workspace-BwqQnool.cjs +2806 -0
  399. package/dist/workspace-BwqQnool.cjs.map +1 -0
  400. package/dist/workspace-D1QbNd55.mjs +2394 -0
  401. package/dist/workspace-D1QbNd55.mjs.map +1 -0
  402. package/package.json +54 -22
  403. package/dist/CachedStateProvider-BEBl2wQT.cjs +0 -60
  404. package/dist/CachedStateProvider-BEBl2wQT.cjs.map +0 -1
  405. package/dist/CachedStateProvider-C-8wtlto.mjs +0 -3
  406. package/dist/CachedStateProvider-Co4n5Wvs.mjs +0 -54
  407. package/dist/CachedStateProvider-Co4n5Wvs.mjs.map +0 -1
  408. package/dist/CachedStateProvider-DqhGpzsm.cjs +0 -3
  409. package/dist/HostingerProvider-BOvsgATO.cjs.map +0 -1
  410. package/dist/HostingerProvider-BcyFRbqV.mjs.map +0 -1
  411. package/dist/LocalStateProvider-Bq-0C0sg.mjs +0 -42
  412. package/dist/LocalStateProvider-Bq-0C0sg.mjs.map +0 -1
  413. package/dist/LocalStateProvider-yjXZXZDM.cjs +0 -43
  414. package/dist/LocalStateProvider-yjXZXZDM.cjs.map +0 -1
  415. package/dist/Route53Provider-C0xmer8U.mjs.map +0 -1
  416. package/dist/Route53Provider-CH9XH7bz.cjs.map +0 -1
  417. package/dist/SSMStateProvider-CXtNgPfy.cjs +0 -66
  418. package/dist/SSMStateProvider-CXtNgPfy.cjs.map +0 -1
  419. package/dist/SSMStateProvider-DOZifZfE.mjs +0 -66
  420. package/dist/SSMStateProvider-DOZifZfE.mjs.map +0 -1
  421. package/dist/backup-provisioner-DXXg6FIL.mjs.map +0 -1
  422. package/dist/backup-provisioner-cE2PuCTb.cjs.map +0 -1
  423. package/dist/bundler-BMekgx7a.cjs.map +0 -1
  424. package/dist/bundler-Bux2iF-v.mjs.map +0 -1
  425. package/dist/chunk-Duj1WY3L.mjs +0 -7
  426. package/dist/config-DTsVjKZD.mjs +0 -402
  427. package/dist/config-DTsVjKZD.mjs.map +0 -1
  428. package/dist/config-RYR9j6EF.cjs +0 -462
  429. package/dist/config-RYR9j6EF.cjs.map +0 -1
  430. package/dist/config.d.cts.map +0 -1
  431. package/dist/config.d.mts.map +0 -1
  432. package/dist/credentials-B5zRHdAl.mjs +0 -141
  433. package/dist/credentials-B5zRHdAl.mjs.map +0 -1
  434. package/dist/credentials-BapzjyV5.cjs +0 -195
  435. package/dist/credentials-BapzjyV5.cjs.map +0 -1
  436. package/dist/credentials-DQRvPz2S.cjs +0 -2769
  437. package/dist/credentials-DQRvPz2S.cjs.map +0 -1
  438. package/dist/credentials-jMJC63qs.mjs +0 -2625
  439. package/dist/credentials-jMJC63qs.mjs.map +0 -1
  440. package/dist/discover-BYHQwQqu.mjs +0 -242
  441. package/dist/discover-BYHQwQqu.mjs.map +0 -1
  442. package/dist/discover-DmW9Bscc.cjs +0 -290
  443. package/dist/discover-DmW9Bscc.cjs.map +0 -1
  444. package/dist/dokploy-api-5cK2InBm.cjs +0 -3
  445. package/dist/dokploy-api-D2Ny4Jrc.mjs +0 -3
  446. package/dist/dokploy-api-pdaBryoY.mjs +0 -706
  447. package/dist/dokploy-api-pdaBryoY.mjs.map +0 -1
  448. package/dist/dokploy-api-zqKe9lHH.cjs +0 -718
  449. package/dist/dokploy-api-zqKe9lHH.cjs.map +0 -1
  450. package/dist/emit-CIlvnDvV.mjs.map +0 -1
  451. package/dist/emit-CUD1BWls.cjs.map +0 -1
  452. package/dist/encryption-D2pL3tHH.cjs +0 -54
  453. package/dist/encryption-D2pL3tHH.cjs.map +0 -1
  454. package/dist/encryption-YDsXXi6w.mjs +0 -3
  455. package/dist/encryption-_4zwjOAR.mjs +0 -42
  456. package/dist/encryption-_4zwjOAR.mjs.map +0 -1
  457. package/dist/encryption-sQ2Oxx0d.cjs +0 -4
  458. package/dist/fullstack-secrets-B6KBHf-g.mjs +0 -87
  459. package/dist/fullstack-secrets-B6KBHf-g.mjs.map +0 -1
  460. package/dist/fullstack-secrets-DL8v0y4D.cjs +0 -99
  461. package/dist/fullstack-secrets-DL8v0y4D.cjs.map +0 -1
  462. package/dist/generator-CNzBw7g5.cjs +0 -289
  463. package/dist/generator-CNzBw7g5.cjs.map +0 -1
  464. package/dist/generator-VE2JrFlY.mjs +0 -253
  465. package/dist/generator-VE2JrFlY.mjs.map +0 -1
  466. package/dist/github-BThvbf2W.cjs +0 -197
  467. package/dist/github-BThvbf2W.cjs.map +0 -1
  468. package/dist/github-DpMZurEK.mjs +0 -196
  469. package/dist/github-DpMZurEK.mjs.map +0 -1
  470. package/dist/index-BzJ7QJ9c.d.mts.map +0 -1
  471. package/dist/index-CfMjtdVC.d.cts.map +0 -1
  472. package/dist/keystore-C9rint2E.cjs +0 -101
  473. package/dist/keystore-C9rint2E.cjs.map +0 -1
  474. package/dist/keystore-TxeYM7an.mjs +0 -83
  475. package/dist/keystore-TxeYM7an.mjs.map +0 -1
  476. package/dist/openapi-Bs3-5q_r.mjs +0 -2021
  477. package/dist/openapi-Bs3-5q_r.mjs.map +0 -1
  478. package/dist/openapi-DWLTsYLL.cjs +0 -2075
  479. package/dist/openapi-DWLTsYLL.cjs.map +0 -1
  480. package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
  481. package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
  482. package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
  483. package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
  484. package/dist/openapi-react-query.cjs +0 -4
  485. package/dist/openapi-react-query.d.cts +0 -11
  486. package/dist/openapi-react-query.d.cts.map +0 -1
  487. package/dist/openapi-react-query.d.mts +0 -11
  488. package/dist/openapi-react-query.d.mts.map +0 -1
  489. package/dist/openapi-react-query.mjs +0 -4
  490. package/dist/reconcile-BI0tWkwz.mjs.map +0 -1
  491. package/dist/reconcile-Bhw5fmKu.cjs.map +0 -1
  492. package/dist/setup-BmLoNrDX.mjs +0 -12
  493. package/dist/setup-BrR1_xGy.cjs +0 -12
  494. package/dist/setup-BsNR2LBG.cjs +0 -467
  495. package/dist/setup-BsNR2LBG.cjs.map +0 -1
  496. package/dist/setup-D5-JMknk.mjs +0 -437
  497. package/dist/setup-D5-JMknk.mjs.map +0 -1
  498. package/dist/storage-B020InPW.mjs +0 -222
  499. package/dist/storage-B020InPW.mjs.map +0 -1
  500. package/dist/storage-CSNplaJn.cjs +0 -282
  501. package/dist/storage-CSNplaJn.cjs.map +0 -1
  502. package/dist/storage-DGBzMLZa.mjs +0 -4
  503. package/dist/storage-DlVol_oj.cjs +0 -8
  504. package/dist/sync-0ILCb-1S.cjs +0 -93
  505. package/dist/sync-0ILCb-1S.cjs.map +0 -1
  506. package/dist/sync-CzHXiR9c.mjs +0 -76
  507. package/dist/sync-CzHXiR9c.mjs.map +0 -1
  508. package/dist/sync-DYENfOt4.cjs +0 -4
  509. package/dist/sync-HAau6h_R.mjs +0 -3
  510. package/dist/types-DVIx8I1N.d.mts +0 -1950
  511. package/dist/types-DVIx8I1N.d.mts.map +0 -1
  512. package/dist/types-PMP_qXRY.d.cts +0 -1950
  513. package/dist/types-PMP_qXRY.d.cts.map +0 -1
  514. package/dist/workspace-Cji6lE8E.cjs +0 -10
  515. package/dist/workspace-CkJAtRIX.mjs +0 -9
  516. package/dist/workspace-CtnZnWsa.mjs.map +0 -1
  517. package/dist/workspace-DPV7FtAv.cjs.map +0 -1
@@ -0,0 +1,3427 @@
1
+ const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
2
+ const require_errors = require("./errors-DMC4Umsb.cjs");
3
+ const require_run = require("./run-fVZ-rLnc.cjs");
4
+ const require_workspace = require("./workspace-BwqQnool.cjs");
5
+ const require_dns = require("./dns-BIYf20p7.cjs");
6
+ const require_define = require("./define-crmMcXFS.cjs");
7
+ const require_seeds = require("./seeds-C2w4UqVP.cjs");
8
+ const require_derive = require("./derive-DAbo49sm.cjs");
9
+ const require_SubscriberGenerator = require("./SubscriberGenerator-C8CPxrCK.cjs");
10
+ const require_identity = require("./identity-CbLfgQNT.cjs");
11
+ const require_discover = require("./discover-DozUKs8f.cjs");
12
+ const require_drivers = require("./drivers-DbjiYpW6.cjs");
13
+ const require_databases = require("./databases-fOaoBInm.cjs");
14
+ let node_path = require("node:path");
15
+ let node_fs = require("node:fs");
16
+ let node_fs_promises = require("node:fs/promises");
17
+ let node_os = require("node:os");
18
+ let node_child_process = require("node:child_process");
19
+ let node_net = require("node:net");
20
+ let node_crypto = require("node:crypto");
21
+ let _geekmidas_manifest = require("@geekmidas/manifest");
22
+ let yaml = require("yaml");
23
+ let node_https = require("node:https");
24
+ //#region src/compose/docker.ts
25
+ /**
26
+ * The Docker operations `gkm compose` needs, behind an interface.
27
+ *
28
+ * Small on purpose, like reconcile's: the rules about *when* each runs — the
29
+ * registry is asked before anything is pulled, nothing is started for a tag
30
+ * that is missing — are asserted against a fake, and this file is only the
31
+ * wiring to the real CLI.
32
+ *
33
+ * Every call is an argument array. Image refs, tags and paths come from the
34
+ * workspace, and none of them reaches a shell.
35
+ */
36
+ /** `output` as `spawn` reads it. */
37
+ const STDIO = {
38
+ inherit: "inherit",
39
+ stderr: [
40
+ "ignore",
41
+ 2,
42
+ 2
43
+ ],
44
+ ignore: "ignore"
45
+ };
46
+ /**
47
+ * The environment a docker command for `engine` runs with: this process's,
48
+ * with `DOCKER_HOST` pointing at the stack's engine when it is a server's.
49
+ */
50
+ function engineEnv(engine, extra = {}) {
51
+ if (!engine.host && Object.keys(extra).length === 0) return void 0;
52
+ return {
53
+ ...process.env,
54
+ ...extra,
55
+ ...engine.host ? { DOCKER_HOST: engine.host } : {}
56
+ };
57
+ }
58
+ /** How a stack's docker commands run: where it is, where output goes. */
59
+ function runOptions(stack, extra) {
60
+ const env = engineEnv(stack, extra);
61
+ return {
62
+ cwd: stack.cwd,
63
+ stdio: STDIO[stack.output ?? "inherit"],
64
+ ...env ? { env } : {},
65
+ ...stack.signal ? { signal: stack.signal } : {}
66
+ };
67
+ }
68
+ /** The network compose puts a project's services on. */
69
+ function projectNetwork(project) {
70
+ return `${project}_default`;
71
+ }
72
+ /**
73
+ * The host ports a `docker ps` Ports column publishes: `0.0.0.0:443->443/tcp,
74
+ * [::]:443->443/tcp, 0.0.0.0:9000-9001->9000-9001/tcp`.
75
+ */
76
+ function publishedPorts(column) {
77
+ const ports = /* @__PURE__ */ new Set();
78
+ for (const match of column.matchAll(/(?:[\d.]+|\[[^\]]*\]):(\d+)(?:-(\d+))?->/g)) {
79
+ const first = Number(match[1]);
80
+ const last = match[2] ? Number(match[2]) : first;
81
+ for (let port = first; port <= last; port++) ports.add(port);
82
+ }
83
+ return [...ports];
84
+ }
85
+ /**
86
+ * The host port a `docker ps` Ports column publishes for `inside`:
87
+ * `127.0.0.1:54321->5432/tcp` is 54321.
88
+ */
89
+ function publishedPort(column, inside) {
90
+ const match = new RegExp(`(?:[\\d.]+|\\[[^\\]]*\\]):(\\d+)->${inside}/tcp`).exec(column);
91
+ return match ? Number(match[1]) : void 0;
92
+ }
93
+ /** Each line `docker ps` printed, as the containers holding `port`. */
94
+ function portHolders(output, port) {
95
+ return output.split("\n").filter(Boolean).map((line) => line.split(" ")).filter(([, , , column = ""]) => publishedPorts(column).includes(port)).map(([container = "", project, service]) => ({
96
+ container,
97
+ ...project ? { project } : {},
98
+ ...service ? { service } : {}
99
+ }));
100
+ }
101
+ /** How long a stack has to pass its health checks. */
102
+ const WAIT_TIMEOUT_S = 180;
103
+ /** `docker` itself failed, before it could say anything about a service. */
104
+ var DockerUnreachable = class extends require_errors.GkmError {
105
+ engine;
106
+ stderr;
107
+ constructor(engine, stderr) {
108
+ super(`Could not ask Docker on ${engine} which ports its services publish (docker said: ${stderr.trim() || "nothing"}). Check that \`DOCKER_HOST=${engine} docker ps\` works from here.`);
109
+ this.engine = engine;
110
+ this.stderr = stderr;
111
+ this.name = "DockerUnreachable";
112
+ }
113
+ };
114
+ /** `docker compose port` printed nothing a port can be read from. */
115
+ var ServicePortUnknown = class extends require_errors.GkmError {
116
+ service;
117
+ inside;
118
+ output;
119
+ constructor(service, inside, output) {
120
+ super(`'${service}' publishes no host port for ${inside} (docker said: ${output.trim() || "nothing"}). Is it running? \`docker compose ps\` shows the stack.`);
121
+ this.service = service;
122
+ this.inside = inside;
123
+ this.output = output;
124
+ this.name = "ServicePortUnknown";
125
+ }
126
+ };
127
+ /**
128
+ * Whether `ref` names a registry on this machine's loopback — which the
129
+ * daemon pulls from and pushes to over plain HTTP by default, and `docker
130
+ * manifest inspect` reaches only when told `--insecure` (without it, a local
131
+ * registry's image is reported as no such manifest).
132
+ */
133
+ function isLoopbackRegistry(ref) {
134
+ const first = ref.split("/")[0];
135
+ if (!ref.includes("/") || !/[.:]|^localhost$/.test(first)) return false;
136
+ const host = first.replace(/:\d+$/, "");
137
+ return host === "localhost" || /^127(?:\.\d{1,3}){3}$/.test(host);
138
+ }
139
+ /** `docker push` finished without saying what digest the registry stored. */
140
+ var PushDigestUnknown = class extends require_errors.GkmError {
141
+ ref;
142
+ output;
143
+ constructor(ref, output) {
144
+ super(`Pushed ${ref}, but docker did not say which digest the registry stored (it said: ${output.trim().split("\n").slice(-3).join(" / ") || "nothing"}). Check the push with \`docker buildx imagetools inspect ${ref}\`.`);
145
+ this.ref = ref;
146
+ this.output = output;
147
+ this.name = "PushDigestUnknown";
148
+ }
149
+ };
150
+ /** The digest `docker push` reports last: `<tag>: digest: sha256:… size: …`. */
151
+ function pushedDigest(output) {
152
+ return [...output.matchAll(/digest: (sha256:[0-9a-f]{64})/g)].at(-1)?.[1];
153
+ }
154
+ function compose(stack, args) {
155
+ return [
156
+ "compose",
157
+ "-p",
158
+ stack.project,
159
+ "-f",
160
+ stack.file,
161
+ ...(stack.overrides ?? []).flatMap((file) => ["-f", file]),
162
+ ...args
163
+ ];
164
+ }
165
+ /** Run a program and keep everything it said, whatever it exits with. */
166
+ function capture(command, args, options = {}) {
167
+ const { timeoutMs = 6e4 } = options;
168
+ return new Promise((resolve, reject) => {
169
+ const child = (0, node_child_process.spawn)(command, [...args], {
170
+ stdio: [
171
+ options.input === void 0 ? "ignore" : "pipe",
172
+ "pipe",
173
+ "pipe"
174
+ ],
175
+ shell: false,
176
+ ...options.env ? { env: options.env } : {}
177
+ });
178
+ if (options.input !== void 0) child.stdin?.end(options.input);
179
+ let stdout = "";
180
+ let stderr = "";
181
+ child.stdout?.on("data", (chunk) => {
182
+ stdout += chunk.toString();
183
+ });
184
+ child.stderr?.on("data", (chunk) => {
185
+ stderr += chunk.toString();
186
+ });
187
+ const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
188
+ timer.unref?.();
189
+ child.on("error", (error) => {
190
+ clearTimeout(timer);
191
+ reject(error);
192
+ });
193
+ child.on("close", (code) => {
194
+ clearTimeout(timer);
195
+ resolve({
196
+ code,
197
+ stdout,
198
+ stderr
199
+ });
200
+ });
201
+ });
202
+ }
203
+ /**
204
+ * Whether a failed `docker manifest inspect` means the tag is not there, as
205
+ * opposed to the registry not answering.
206
+ *
207
+ * Read off the message because that is all the CLI gives. Anything not
208
+ * recognisably "no such tag" is treated as the registry being unreachable:
209
+ * a registry that refused credentials must not be reported as a tag that was
210
+ * never pushed, or the fix offered would be the wrong one.
211
+ */
212
+ function isMissingManifest(output) {
213
+ return /no such manifest|manifest unknown|manifest for .* not found|not found: manifest/i.test(output);
214
+ }
215
+ const dockerCompose = {
216
+ async lookup(ref) {
217
+ const { code, stdout, stderr } = await capture("docker", [
218
+ "manifest",
219
+ "inspect",
220
+ ...isLoopbackRegistry(ref) ? ["--insecure"] : [],
221
+ ref
222
+ ]);
223
+ if (code === 0) return {
224
+ ref,
225
+ status: "found"
226
+ };
227
+ const output = `${stderr}\n${stdout}`.trim();
228
+ return isMissingManifest(output) ? {
229
+ ref,
230
+ status: "missing"
231
+ } : {
232
+ ref,
233
+ status: "unreachable",
234
+ detail: output
235
+ };
236
+ },
237
+ async build(stack, services) {
238
+ await require_run.run("docker", compose(stack, ["build", ...services]), runOptions(stack, {
239
+ ...process.env,
240
+ DOCKER_BUILDKIT: "1"
241
+ }));
242
+ },
243
+ async pull(stack, services) {
244
+ await require_run.run("docker", compose(stack, ["pull", ...services]), runOptions(stack));
245
+ },
246
+ async up(stack, services) {
247
+ await require_run.run("docker", compose(stack, [
248
+ "up",
249
+ "-d",
250
+ "--wait",
251
+ `--wait-timeout=${WAIT_TIMEOUT_S}`,
252
+ ...services ? [] : ["--remove-orphans"],
253
+ ...services ?? []
254
+ ]), runOptions(stack));
255
+ },
256
+ async down(stack) {
257
+ await require_run.run("docker", compose(stack, ["down"]), runOptions(stack));
258
+ },
259
+ async port(stack, service, inside) {
260
+ const env = engineEnv(stack);
261
+ const output = await require_run.runOutput("docker", compose(stack, [
262
+ "port",
263
+ service,
264
+ String(inside)
265
+ ]), {
266
+ cwd: stack.cwd,
267
+ ...env ? { env } : {},
268
+ ...stack.signal ? { signal: stack.signal } : {}
269
+ });
270
+ const port = Number(output.trim().split(":").pop());
271
+ if (!Number.isInteger(port) || port <= 0) throw new ServicePortUnknown(service, inside, output);
272
+ return port;
273
+ },
274
+ async health(stack, service) {
275
+ const env = engineEnv(stack);
276
+ const { code, stdout } = await capture("docker", compose(stack, [
277
+ "ps",
278
+ "--format",
279
+ "{{.Health}}",
280
+ service
281
+ ]), env ? { env } : {});
282
+ if (code !== 0) return void 0;
283
+ return stdout.trim().split("\n")[0] || void 0;
284
+ },
285
+ async copyOut(stack, service, from, to) {
286
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(to), { recursive: true });
287
+ await require_run.run("docker", compose(stack, [
288
+ "cp",
289
+ `${service}:${from}`,
290
+ to
291
+ ]), runOptions(stack));
292
+ },
293
+ async push(stack, ref) {
294
+ const output = await require_run.runOutput("docker", ["push", ref], {
295
+ cwd: stack.cwd,
296
+ ...stack.signal ? { signal: stack.signal } : {}
297
+ });
298
+ const digest = pushedDigest(output);
299
+ if (!digest) throw new PushDigestUnknown(ref, output);
300
+ return digest;
301
+ },
302
+ async digest(engine, ref) {
303
+ const env = engineEnv(engine);
304
+ const { code, stdout } = await capture("docker", [
305
+ "image",
306
+ "inspect",
307
+ "--format={{json .RepoDigests}} {{.Id}}",
308
+ ref
309
+ ], env ? { env } : {});
310
+ if (code !== 0) return void 0;
311
+ const [json = "[]", id] = stdout.trim().split(" ");
312
+ const repository = ref.split("@")[0].replace(/:[\w][\w.-]*$/, "");
313
+ try {
314
+ const digests = JSON.parse(json);
315
+ const match = Array.isArray(digests) ? digests.find((entry) => typeof entry === "string" && entry.startsWith(`${repository}@`)) : void 0;
316
+ if (match) return match.slice(repository.length + 1);
317
+ } catch {}
318
+ return id || void 0;
319
+ },
320
+ async ensureNetwork(engine, name) {
321
+ const env = engineEnv(engine);
322
+ const options = env ? { env } : {};
323
+ const { code } = await capture("docker", [
324
+ "network",
325
+ "inspect",
326
+ name
327
+ ], options);
328
+ if (code === 0) return;
329
+ const created = await capture("docker", [
330
+ "network",
331
+ "create",
332
+ name
333
+ ], options);
334
+ if (created.code !== 0 && !/already exists/.test(created.stderr)) throw new NetworkCreateFailed(name, created.stderr);
335
+ },
336
+ async publishers(engine, port) {
337
+ const env = engineEnv(engine);
338
+ const { stdout } = await capture("docker", [
339
+ "ps",
340
+ "--format",
341
+ "{{.Names}} {{.Label \"com.docker.compose.project\"}} {{.Label \"com.docker.compose.service\"}} {{.Ports}}"
342
+ ], env ? { env } : {});
343
+ return portHolders(stdout, port);
344
+ },
345
+ async exec(stack, service, command, input) {
346
+ const env = engineEnv(stack);
347
+ const options = env ? { env } : {};
348
+ const { stdout: ids } = await capture("docker", [
349
+ "ps",
350
+ "-q",
351
+ "--filter",
352
+ `label=com.docker.compose.project=${stack.project}`,
353
+ "--filter",
354
+ `label=com.docker.compose.service=${service}`
355
+ ], options);
356
+ const container = ids.trim().split("\n")[0];
357
+ if (!container) return {
358
+ code: null,
359
+ stdout: "",
360
+ stderr: `${service} is not running`
361
+ };
362
+ return capture("docker", [
363
+ "exec",
364
+ ...input === void 0 ? [] : ["-i"],
365
+ container,
366
+ ...command
367
+ ], {
368
+ ...options,
369
+ ...input === void 0 ? {} : { input }
370
+ });
371
+ },
372
+ async container(engine, project, service) {
373
+ const env = engineEnv(engine);
374
+ const { stdout } = await capture("docker", [
375
+ "ps",
376
+ "--filter",
377
+ `label=com.docker.compose.project=${project}`,
378
+ "--filter",
379
+ `label=com.docker.compose.service=${service}`,
380
+ "--format",
381
+ "{{.ID}} {{.Image}}"
382
+ ], env ? { env } : {});
383
+ const [id, image] = stdout.trim().split("\n")[0]?.split(" ") ?? [];
384
+ return id && image ? {
385
+ id,
386
+ image
387
+ } : void 0;
388
+ },
389
+ async published(engine, project, service, inside) {
390
+ const env = engineEnv(engine);
391
+ const { code, stdout, stderr } = await capture("docker", [
392
+ "ps",
393
+ "--filter",
394
+ `label=com.docker.compose.project=${project}`,
395
+ "--filter",
396
+ `label=com.docker.compose.service=${service}`,
397
+ "--format",
398
+ "{{.Ports}}"
399
+ ], env ? { env } : {});
400
+ if (code !== 0) throw new DockerUnreachable(engine.host ?? "this machine", stderr);
401
+ return publishedPort(stdout, inside);
402
+ },
403
+ runOnce(engine, options) {
404
+ return dockerExit([
405
+ "run",
406
+ "--rm",
407
+ ...options.stdin ? ["-i"] : [],
408
+ "--network",
409
+ options.network,
410
+ ...Object.keys(options.env ?? {}).flatMap((name) => ["-e", name]),
411
+ "--entrypoint",
412
+ "",
413
+ options.image,
414
+ ...options.command
415
+ ], {
416
+ env: engineEnv(engine, { ...options.env }) ?? process.env,
417
+ ...options.stdin ? { stdin: options.stdin } : {},
418
+ ...options.output ? { output: options.output } : {}
419
+ });
420
+ }
421
+ };
422
+ /** Spawn `docker`, its output where `output` says; resolve with the exit code. */
423
+ function dockerExit(args, options = {}) {
424
+ const output = STDIO[options.output ?? "inherit"];
425
+ const out = Array.isArray(output) ? output : [
426
+ output,
427
+ output,
428
+ output
429
+ ];
430
+ return new Promise((resolve, reject) => {
431
+ const child = (0, node_child_process.spawn)("docker", [...args], {
432
+ stdio: [
433
+ options.stdin ? "pipe" : "ignore",
434
+ out[1],
435
+ out[2]
436
+ ],
437
+ env: options.env ?? process.env,
438
+ shell: false
439
+ });
440
+ if (options.stdin && child.stdin) {
441
+ options.stdin.on("error", (error) => child.stdin?.destroy(error));
442
+ child.stdin.on("error", () => {});
443
+ options.stdin.pipe(child.stdin);
444
+ }
445
+ child.on("error", reject);
446
+ child.on("close", (code) => resolve(code ?? 1));
447
+ });
448
+ }
449
+ /** `docker network create` failed. */
450
+ var NetworkCreateFailed = class extends require_errors.GkmError {
451
+ network;
452
+ output;
453
+ constructor(network, output) {
454
+ super(`Could not create the Docker network '${network}' (docker said: ${output.trim() || "nothing"}). Create it by hand with \`docker network create ${network}\` and run again.`);
455
+ this.network = network;
456
+ this.output = output;
457
+ this.name = "NetworkCreateFailed";
458
+ }
459
+ };
460
+ //#endregion
461
+ //#region src/compose/server.ts
462
+ /**
463
+ * Where a deployed compose stage runs: its server, reached over SSH.
464
+ *
465
+ * gkm never runs on the server. It runs where the deploy is started — a CI
466
+ * runner, a developer's machine — and drives the server's Docker engine over
467
+ * SSH (`DOCKER_HOST=ssh://user@host`), so the server holds Docker, an SSH
468
+ * login and nothing else: no gkm, no checkout, no cloud credentials.
469
+ *
470
+ * ```ts
471
+ * deploy: { compose: { server: { production: { user: 'deploy' } } } }
472
+ * ```
473
+ *
474
+ * The host is the stage's `GKM_SERVER_IPV4` secret — the address its DNS
475
+ * records already point at — unless `host` names another. The local stage is
476
+ * never given one: it runs on this machine's Docker.
477
+ *
478
+ * What the deploy needs from inside the server — Postgres and MinIO, published
479
+ * on the server's loopback alone — it reaches through an SSH tunnel on the same
480
+ * login, so neither is ever published publicly.
481
+ */
482
+ /** A deployed compose stage with nowhere to run. */
483
+ var ComposeServerMissing = class extends require_errors.GkmError {
484
+ stage;
485
+ missing;
486
+ constructor(stage, missing) {
487
+ super(missing === "config" ? `'${stage}' is a deployed stage, and gkm deploys it to its server's Docker over SSH — it never starts a deployed stage on this machine's Docker. Name the login in gkm.config.ts: deploy: { compose: { server: { ${stage}: { user: 'deploy' } } } } — the host is the stage's ${require_workspace.SERVER_IPV4_KEY} secret (gkm secrets:set ${require_workspace.SERVER_IPV4_KEY} '<ip>' --stage ${stage}), or set host there too.` : `deploy.compose.server.${stage} names no host, and '${stage}' has no ${require_workspace.SERVER_IPV4_KEY} secret to default to. Set it: gkm secrets:set ${require_workspace.SERVER_IPV4_KEY} '<ip>' --stage ${stage} — or set deploy.compose.server.${stage}.host.`);
488
+ this.stage = stage;
489
+ this.missing = missing;
490
+ this.name = "ComposeServerMissing";
491
+ }
492
+ };
493
+ /** SSH to the stage's server failed before anything was changed there. */
494
+ var ComposeServerUnreachable = class extends require_errors.GkmError {
495
+ target;
496
+ stderr;
497
+ constructor(target, stderr) {
498
+ super(`Could not reach Docker on ${target} over SSH (ssh said: ${stderr.trim() || "nothing"}). Check that \`ssh ${target} docker version\` works from here: the host key in ~/.ssh/known_hosts, the key in your agent or ~/.ssh/config, and the user in the server's docker group.`);
499
+ this.target = target;
500
+ this.stderr = stderr;
501
+ this.name = "ComposeServerUnreachable";
502
+ }
503
+ };
504
+ /** An SSH tunnel to a port on the server's loopback did not open. */
505
+ var ComposeTunnelFailed = class extends require_errors.GkmError {
506
+ target;
507
+ remotePort;
508
+ stderr;
509
+ constructor(target, remotePort, stderr) {
510
+ super(`Could not open an SSH tunnel to 127.0.0.1:${remotePort} on ${target} (ssh said: ${stderr.trim() || "nothing"}). The server's sshd must allow TCP forwarding for the deploy user (AllowTcpForwarding yes, the default, or local), and its key in ~/.ssh/authorized_keys must too: a key installed with \`restrict\` refuses forwarding unless it also says \`port-forwarding\` (\`restrict,port-forwarding ssh-ed25519 …\`).`);
511
+ this.target = target;
512
+ this.remotePort = remotePort;
513
+ this.stderr = stderr;
514
+ this.name = "ComposeTunnelFailed";
515
+ }
516
+ };
517
+ /**
518
+ * The stage's server: its login from `deploy.compose.server.<stage>`, its
519
+ * host from there or the stage's `GKM_SERVER_IPV4`. Undefined for the local
520
+ * stage, which runs on this machine.
521
+ *
522
+ * @throws {ComposeServerMissing} for a deployed stage with no login or host
523
+ */
524
+ function composeServer(options) {
525
+ const { stage } = options;
526
+ if (options.local) return void 0;
527
+ const config = options.config?.[stage];
528
+ if (!config) throw new ComposeServerMissing(stage, "config");
529
+ const host = config.host ?? options.custom?.["GKM_SERVER_IPV4"]?.trim();
530
+ if (!host) throw new ComposeServerMissing(stage, "host");
531
+ return {
532
+ user: config.user,
533
+ host,
534
+ port: config.port ?? 22
535
+ };
536
+ }
537
+ /** `user@host`, as a person types it after `ssh`. */
538
+ function sshTarget(server) {
539
+ return `${server.user}@${server.host}`;
540
+ }
541
+ /** The server's Docker engine, as `DOCKER_HOST` names it. */
542
+ function dockerHost(server) {
543
+ const host = server.host.includes(":") ? `[${server.host}]` : server.host;
544
+ return `ssh://${server.user}@${host}${server.port === 22 ? "" : `:${server.port}`}`;
545
+ }
546
+ /** The arguments every `ssh` gkm runs starts with: no prompt, ever. */
547
+ function sshArgs(server) {
548
+ return [
549
+ "-o",
550
+ "BatchMode=yes",
551
+ "-o",
552
+ "ConnectTimeout=20",
553
+ "-p",
554
+ String(server.port),
555
+ sshTarget(server)
556
+ ];
557
+ }
558
+ async function freeLocalPort() {
559
+ const server = (0, node_net.createServer)();
560
+ await new Promise((resolve, reject) => {
561
+ server.once("error", reject);
562
+ server.listen(0, "127.0.0.1", resolve);
563
+ });
564
+ const address = server.address();
565
+ await new Promise((resolve) => server.close(resolve));
566
+ if (!address || typeof address === "string") return 0;
567
+ return address.port;
568
+ }
569
+ function connects(port) {
570
+ return new Promise((resolve) => {
571
+ const socket = (0, node_net.createConnection)({
572
+ host: "127.0.0.1",
573
+ port
574
+ });
575
+ socket.once("connect", () => {
576
+ socket.destroy();
577
+ resolve(true);
578
+ });
579
+ socket.once("error", () => resolve(false));
580
+ });
581
+ }
582
+ /** The real one: the system's `ssh`, with the user's keys and known hosts. */
583
+ const sshAccess = {
584
+ check(server) {
585
+ return new Promise((resolve, reject) => {
586
+ const child = (0, node_child_process.spawn)("ssh", [
587
+ ...sshArgs(server),
588
+ "docker",
589
+ "version",
590
+ "--format",
591
+ "{{.Server.Version}}"
592
+ ], { stdio: [
593
+ "ignore",
594
+ "ignore",
595
+ "pipe"
596
+ ] });
597
+ let stderr = "";
598
+ child.stderr.on("data", (chunk) => {
599
+ stderr += chunk.toString();
600
+ });
601
+ child.on("error", (error) => reject(new ComposeServerUnreachable(sshTarget(server), error.message)));
602
+ child.on("close", (code) => code === 0 ? resolve() : reject(new ComposeServerUnreachable(sshTarget(server), stderr)));
603
+ });
604
+ },
605
+ async tunnel(server, remotePort) {
606
+ const port = await freeLocalPort();
607
+ const child = (0, node_child_process.spawn)("ssh", [
608
+ "-N",
609
+ "-o",
610
+ "ExitOnForwardFailure=yes",
611
+ "-o",
612
+ "ControlPath=none",
613
+ "-L",
614
+ `127.0.0.1:${port}:127.0.0.1:${remotePort}`,
615
+ ...sshArgs(server)
616
+ ], { stdio: [
617
+ "ignore",
618
+ "ignore",
619
+ "pipe"
620
+ ] });
621
+ let stderr = "";
622
+ let exited = false;
623
+ child.stderr.on("data", (chunk) => {
624
+ stderr += chunk.toString();
625
+ });
626
+ child.on("exit", () => {
627
+ exited = true;
628
+ });
629
+ child.on("error", (error) => {
630
+ exited = true;
631
+ stderr += error.message;
632
+ });
633
+ const close = async () => {
634
+ if (exited) return;
635
+ const gone = new Promise((resolve) => child.once("exit", resolve));
636
+ child.kill("SIGTERM");
637
+ await gone;
638
+ };
639
+ for (let attempt = 0; attempt < 150; attempt++) {
640
+ if (exited) break;
641
+ if (await connects(port)) return {
642
+ port,
643
+ close
644
+ };
645
+ await new Promise((resolve) => setTimeout(resolve, 200));
646
+ }
647
+ await close();
648
+ throw new ComposeTunnelFailed(sshTarget(server), remotePort, stderr);
649
+ }
650
+ };
651
+ //#endregion
652
+ //#region src/compose/caddyfile.ts
653
+ /**
654
+ * The stack's own edge, rendered for Caddy: one site block per route.
655
+ *
656
+ * Deployed, each host is the stage's real domain and Caddy gets it a
657
+ * certificate from Let's Encrypt on first request — the same automatic HTTPS
658
+ * it does anywhere, with nothing to configure but the name — or serves the
659
+ * stage's own certificate (`deploy.compose.tls`). Locally the hosts are
660
+ * `*.localhost` and the certificates come from Caddy's own CA, as the `gkm
661
+ * dev` edge does, so a local stack is `https://` like a deployed one.
662
+ *
663
+ * Pure: the routes are data (`routes.ts`), and rendering them is one string.
664
+ */
665
+ /** Where Caddy reads a stage's own certificate, inside its container. */
666
+ const CADDY_TLS_DIR = "/etc/caddy/tls";
667
+ /** An upstream on the stack's own network: its service name. */
668
+ function address({ service, port }) {
669
+ return `${service}:${port}`;
670
+ }
671
+ /** The Caddyfile for a stack's edge. */
672
+ function edgeCaddyfile(routes, options) {
673
+ const { tls } = options;
674
+ const global = tls.kind === "internal" ? `{
675
+ # No ACME and no public DNS: the CA is Caddy's own and the names resolve
676
+ # to this machine.
677
+ local_certs
678
+ auto_https disable_redirects
679
+ }` : options.email ? `{
680
+ email ${options.email}
681
+ }` : "";
682
+ const certificate = tls.kind === "internal" ? `
683
+ tls internal
684
+ ` : tls.kind === "files" ? `
685
+ tls ${tls.certFile} ${tls.keyFile}
686
+ ` : "";
687
+ const blocks = routes.map(({ host, upstreams, prefix, allow, paths, streaming }) => `https://${host} {${certificate}
688
+ # gkm's own header, which only a service of this stack may send: the auth
689
+ # server rate-limits by the client address it carries.
690
+ request_header -${_geekmidas_manifest.CLIENT_IP_HEADER}
691
+ ${allow ? `
692
+ # Only these addresses — the peer Caddy sees, never a header a client
693
+ # could set. Everything else is refused before it reaches the upstream.
694
+ @denied not remote_ip ${allow.join(" ")}
695
+ respond @denied 403
696
+ ` : ""}${paths ? `
697
+ # Only these paths answer. Everything else on this host is refused before
698
+ # it reaches the upstream.
699
+ @elsewhere not path ${paths.join(" ")}
700
+ respond @elsewhere 404
701
+ ` : ""}${prefix ? `
702
+ # A bucket, served at a host of its own: the bucket is a prefix on MinIO,
703
+ # which routes and signs on the Host header — so it is sent the upstream's.
704
+ reverse_proxy ${upstreams.map(address).join(" ")} {
705
+ rewrite ${prefix}{uri}
706
+ header_up Host {upstream_hostport}
707
+ }` : `
708
+ # Host and X-Forwarded-* are passed through as Caddy does by default, so
709
+ # an app builds redirects, cookies and links on the address a caller used.
710
+ reverse_proxy ${upstreams.map(address).join(" ")}${streaming ? ` {
711
+ # Never buffer: a streamed response — server-sent events, a Next.js
712
+ # RSC payload — reaches the client as each chunk is written.
713
+ flush_interval -1
714
+ }` : ""}`}
715
+ }`);
716
+ return `${HEADER}${global ? `${global}\n\n` : ""}${blocks.join("\n\n")}\n`;
717
+ }
718
+ const HEADER = `# Generated by gkm compose from the construct manifest — do not edit.
719
+ #
720
+ # One host per API and site, each in front of its own container, one per
721
+ # file server in front of the stack's MinIO, one per worker SNS pushes to,
722
+ # answering only those routes, and — when its logs are served publicly — one
723
+ # for OpenObserve, to the addresses it allows.
724
+ `;
725
+ //#endregion
726
+ //#region src/compose/images.ts
727
+ /**
728
+ * Which image each app runs — and, for a tag, whether every one exists.
729
+ *
730
+ * A tag is a release's identity: `--tag=v1.4.0` means the images CI pushed as
731
+ * v1.4.0, and nothing else. So a stack is never half rolled out: every app's
732
+ * image is asked of the registry before anything is pulled or the running
733
+ * stack is touched, and one missing image stops the whole run naming every
734
+ * one that is missing. Nor is a missing image quietly built instead — a build
735
+ * of whatever is checked out here is not the release the tag names.
736
+ */
737
+ /**
738
+ * The tag a site's image carries for a release.
739
+ *
740
+ * A site's public URLs are inlined when it is built, so one bundle serves one
741
+ * stage: `v1.4.0-production` and `v1.4.0-staging` are two images of the same
742
+ * commit. A backend reads its URLs at runtime, so one image serves every stage
743
+ * and its tag is the release's alone.
744
+ */
745
+ function siteTag(tag, stage) {
746
+ return `${tag}-${stage}`;
747
+ }
748
+ /** Images a tag names that the registry does not have. */
749
+ var ImageTagNotFound = class extends require_errors.GkmError {
750
+ tag;
751
+ refs;
752
+ constructor(tag, refs) {
753
+ super(`The registry has no image for ${refs.length === 1 ? "this app" : `${refs.length} apps`} at '${tag}':\n` + refs.map((ref) => ` - ${ref}`).join("\n") + `\nNothing was pulled or started. Push '${tag}' for every app from CI, pick a tag that exists, or run without --tag to build from this checkout.`);
754
+ this.tag = tag;
755
+ this.refs = refs;
756
+ this.name = "ImageTagNotFound";
757
+ }
758
+ };
759
+ /** The registry did not answer, or did not let this machine in. */
760
+ var RegistryUnreachable = class extends require_errors.GkmError {
761
+ ref;
762
+ detail;
763
+ constructor(ref, detail) {
764
+ super(`Could not ask the registry about ${ref}: ${detail || "no answer"}\nNothing was pulled or started. Check the registry is reachable and that this machine is logged in to it (\`docker login <registry>\`).`);
765
+ this.ref = ref;
766
+ this.detail = detail;
767
+ this.name = "RegistryUnreachable";
768
+ }
769
+ };
770
+ /**
771
+ * Assert every image exists, before anything else happens.
772
+ *
773
+ * Asked all at once and reported all at once: a release missing two images is
774
+ * one problem with two names, and finding the second only after fixing the
775
+ * first is a slower way to learn the same thing.
776
+ *
777
+ * @throws {RegistryUnreachable} when the registry cannot say
778
+ * @throws {ImageTagNotFound} listing every missing image
779
+ */
780
+ async function assertImagesExist(docker, tag, images) {
781
+ const lookups = await Promise.all(images.map((image) => docker.lookup(image.ref)));
782
+ const unreachable = lookups.find((lookup) => lookup.status === "unreachable");
783
+ if (unreachable?.status === "unreachable") throw new RegistryUnreachable(unreachable.ref, unreachable.detail);
784
+ const missing = lookups.filter((lookup) => lookup.status === "missing").map((lookup) => lookup.ref);
785
+ if (missing.length > 0) throw new ImageTagNotFound(tag, missing);
786
+ }
787
+ /**
788
+ * A push or a pull with no `deploy.registry` to push to or pull from.
789
+ *
790
+ * Without one an image is named `<namespace>/<project>-<app>:<tag>`, which
791
+ * Docker resolves to Docker Hub — so a release would be pushed to, or pulled
792
+ * from, somebody else's account. It is never a fallback.
793
+ */
794
+ var RegistryRequired = class extends require_errors.GkmError {
795
+ operation;
796
+ ref;
797
+ constructor(operation, ref) {
798
+ super(`${operation === "push" ? "Pushing" : "Pulling"} images needs a registry, and gkm.config.ts sets no deploy.registry: '${ref}' would be ${operation === "push" ? "pushed to" : "pulled from"} Docker Hub. Set deploy.registry (e.g. registry: 'ghcr.io/acme') in gkm.config.ts.` + (operation === "pull" ? " To run images built here instead, pass --build." : ""));
799
+ this.operation = operation;
800
+ this.ref = ref;
801
+ this.name = "RegistryRequired";
802
+ }
803
+ };
804
+ /** An image pinned to its digest: `<ref>@sha256:…`. */
805
+ function pinnedRef(ref, digest) {
806
+ return `${ref.split("@")[0]}@${digest}`;
807
+ }
808
+ /** A digests file with no entry for an app the stack runs. */
809
+ var ImageDigestMissing = class extends require_errors.GkmError {
810
+ file;
811
+ apps;
812
+ constructor(file, apps) {
813
+ super(`${file} pins no digest for ${apps.join(", ")}. Use the file the same release's \`gkm compose --build --push --digests-file\` wrote, or run without --digests-file to pull by tag.`);
814
+ this.file = file;
815
+ this.apps = apps;
816
+ this.name = "ImageDigestMissing";
817
+ }
818
+ };
819
+ /** A digests file entry that is not this app's image. */
820
+ var ImageDigestMismatch = class extends require_errors.GkmError {
821
+ app;
822
+ expected;
823
+ found;
824
+ constructor(app, expected, found) {
825
+ super(`The digests file pins '${app}' to ${found}, but this release runs ${expected}. It was written for another registry, project, stage or tag; use the file this stage's push of this tag wrote.`);
826
+ this.app = app;
827
+ this.expected = expected;
828
+ this.found = found;
829
+ this.name = "ImageDigestMismatch";
830
+ }
831
+ };
832
+ /** A digests file that is not a JSON object of app to pinned ref. */
833
+ var ImageDigestsInvalid = class extends require_errors.GkmError {
834
+ file;
835
+ detail;
836
+ constructor(file, detail) {
837
+ super(`${file} is not a digests file (${detail}). It should be the JSON object \`gkm compose --build --push --digests-file\` writes: { "api": "<registry>/<image>:<tag>@sha256:…" }.`);
838
+ this.file = file;
839
+ this.detail = detail;
840
+ this.name = "ImageDigestsInvalid";
841
+ }
842
+ };
843
+ /** Parse a digests file's content, checking its shape. */
844
+ function parseDigests(file, content) {
845
+ let parsed;
846
+ try {
847
+ parsed = JSON.parse(content);
848
+ } catch (error) {
849
+ throw new ImageDigestsInvalid(file, error instanceof Error ? error.message : String(error));
850
+ }
851
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new ImageDigestsInvalid(file, "not a JSON object");
852
+ for (const [app, value] of Object.entries(parsed)) if (typeof value !== "string" || !/@sha256:[0-9a-f]{64}$/.test(value)) throw new ImageDigestsInvalid(file, `'${app}' is not an <image>:<tag>@sha256:… ref`);
853
+ return parsed;
854
+ }
855
+ /**
856
+ * Each image pinned to the digest the file names for it.
857
+ *
858
+ * @throws {ImageDigestMissing} naming every app the file has no entry for
859
+ * @throws {ImageDigestMismatch} when an entry is another image or tag
860
+ */
861
+ function pinImages(file, digests, images) {
862
+ const missing = images.filter((image) => !digests[image.app]).map((image) => image.app);
863
+ if (missing.length > 0) throw new ImageDigestMissing(file, missing);
864
+ return images.map((image) => {
865
+ const pinned = digests[image.app];
866
+ if (pinned.split("@")[0] !== image.ref) throw new ImageDigestMismatch(image.app, image.ref, pinned);
867
+ return {
868
+ ...image,
869
+ ref: pinned
870
+ };
871
+ });
872
+ }
873
+ //#endregion
874
+ //#region src/compose/traefik.ts
875
+ /**
876
+ * The server's shared edge, rendered for Traefik: what the edge itself runs
877
+ * with, and what one stack registers with it.
878
+ *
879
+ * One Traefik per server owns 80 and 443, the ACME state and the redirect to
880
+ * HTTPS. It is configured through its file provider alone: each stack writes
881
+ * one file — its routers, services and middlewares, every name prefixed with
882
+ * its compose project — into a directory the edge watches, and removes it
883
+ * when it stops. There is no Docker socket: the edge never learns what runs
884
+ * on the server except from those files.
885
+ *
886
+ * The edge and the stacks meet on one external Docker network. Only a
887
+ * stack's public services join it, each under an alias prefixed with its
888
+ * project (`<project>-api`), because the network is shared: a bare service
889
+ * name there would be answered by every stack's `api`.
890
+ *
891
+ * Pure: the documents are data, and the edge's side effects are `edge.ts`'s.
892
+ */
893
+ /**
894
+ * The image, pinned: the edge is every stack's way in, so a new version is a
895
+ * change to this line, not whatever `latest` is on the day a server starts.
896
+ */
897
+ const TRAEFIK_IMAGE = "traefik:v3.7.13";
898
+ /** The edge's compose project, one per server. */
899
+ const EDGE_PROJECT = "gkm-edge";
900
+ /** The external network the edge and every stack's public services share. */
901
+ const EDGE_NETWORK = "gkm-edge";
902
+ /** The edge's compose service. */
903
+ const EDGE_SERVICE = "traefik";
904
+ /** Inside the edge's container: what it watches, and where certificates are. */
905
+ const EDGE_DYNAMIC_DIR = "/etc/gkm-edge/dynamic";
906
+ const EDGE_CERTS_DIR = "/etc/gkm-edge/certs";
907
+ /** The certificate resolver every deployed route without its own certificate uses. */
908
+ const ACME_RESOLVER = "letsencrypt";
909
+ /** The internal entrypoint the edge's health check asks; never published. */
910
+ const PING_PORT = 8082;
911
+ /** A stack's service, as the edge — and the stack itself — reach it. */
912
+ function edgeAlias(project, service) {
913
+ return `${project}-${service}`;
914
+ }
915
+ /** A stack's certificate, as the edge's container reads it. */
916
+ function edgeCertificatePaths(project) {
917
+ return {
918
+ certFile: `${EDGE_CERTS_DIR}/${project}.crt`,
919
+ keyFile: `${EDGE_CERTS_DIR}/${project}.key`
920
+ };
921
+ }
922
+ /**
923
+ * The edge's static configuration: the entrypoints, the redirect, ACME, and
924
+ * the directory it watches. No API and no dashboard — nothing about the edge
925
+ * is reachable but the routes stacks register.
926
+ */
927
+ function traefikStatic(ports) {
928
+ return {
929
+ global: {
930
+ checkNewVersion: false,
931
+ sendAnonymousUsage: false
932
+ },
933
+ entryPoints: {
934
+ web: {
935
+ address: ":80",
936
+ http: { redirections: { entryPoint: {
937
+ to: ports.https === 443 ? "websecure" : `:${ports.https}`,
938
+ scheme: "https",
939
+ permanent: true
940
+ } } }
941
+ },
942
+ websecure: { address: ":443" },
943
+ ping: { address: `:${PING_PORT}` }
944
+ },
945
+ ping: { entryPoint: "ping" },
946
+ providers: { file: {
947
+ directory: EDGE_DYNAMIC_DIR,
948
+ watch: true
949
+ } },
950
+ certificatesResolvers: { [ACME_RESOLVER]: { acme: {
951
+ storage: "/acme/acme.json",
952
+ httpChallenge: { entryPoint: "web" }
953
+ } } },
954
+ log: { level: "INFO" }
955
+ };
956
+ }
957
+ /** The edge's compose document. */
958
+ function edgeCompose(options) {
959
+ const { ports } = options;
960
+ return {
961
+ name: EDGE_PROJECT,
962
+ services: { [EDGE_SERVICE]: {
963
+ image: TRAEFIK_IMAGE,
964
+ restart: "unless-stopped",
965
+ ports: [`${ports.https}:443`, `${ports.http}:80`],
966
+ configs: [{
967
+ source: "traefik",
968
+ target: "/etc/traefik/traefik.yml"
969
+ }],
970
+ volumes: [
971
+ `dynamic:${EDGE_DYNAMIC_DIR}`,
972
+ `certs:${EDGE_CERTS_DIR}`,
973
+ "acme:/acme"
974
+ ],
975
+ labels: { "dev.geekmidas.edge.config": (0, node_crypto.createHash)("sha256").update(options.staticConfig).digest("hex").slice(0, 16) },
976
+ networks: ["edge"],
977
+ healthcheck: {
978
+ test: [
979
+ "CMD",
980
+ "traefik",
981
+ "healthcheck",
982
+ "--ping"
983
+ ],
984
+ interval: "10s",
985
+ timeout: "5s",
986
+ retries: 5
987
+ },
988
+ logging: options.logging
989
+ } },
990
+ configs: { traefik: { content: options.staticConfig } },
991
+ volumes: {
992
+ acme: {},
993
+ dynamic: {},
994
+ certs: {}
995
+ },
996
+ networks: { edge: {
997
+ name: EDGE_NETWORK,
998
+ external: true
999
+ } }
1000
+ };
1001
+ }
1002
+ /** A stack's dynamic configuration, as an object. */
1003
+ function traefikDynamic(routes, options) {
1004
+ const { project, tls } = options;
1005
+ const name = (route, suffix) => `${project}-${route.name}${suffix ? `-${suffix}` : ""}`;
1006
+ const url = ({ service, port }) => `http://${edgeAlias(project, service)}:${port}`;
1007
+ const routers = {};
1008
+ const services = {};
1009
+ const middlewares = {};
1010
+ const strip = `${project}-strip-gkm-headers`;
1011
+ if (routes.length > 0) middlewares[strip] = { headers: { customRequestHeaders: { [_geekmidas_manifest.CLIENT_IP_HEADER]: "" } } };
1012
+ for (const route of routes) {
1013
+ const chain = [strip];
1014
+ if (route.allow) {
1015
+ middlewares[name(route, "allow")] = { ipAllowList: { sourceRange: [...route.allow] } };
1016
+ chain.push(name(route, "allow"));
1017
+ }
1018
+ if (route.prefix) {
1019
+ middlewares[name(route, "prefix")] = { addPrefix: { prefix: route.prefix } };
1020
+ chain.push(name(route, "prefix"));
1021
+ }
1022
+ const paths = route.paths ? ` && (${route.paths.map((path) => `Path(\`${path}\`)`).join(" || ")})` : "";
1023
+ routers[name(route)] = {
1024
+ rule: `Host(\`${route.host}\`)${paths}`,
1025
+ entryPoints: ["websecure"],
1026
+ service: name(route),
1027
+ middlewares: chain,
1028
+ tls: tls.kind === "acme" ? { certResolver: ACME_RESOLVER } : {}
1029
+ };
1030
+ services[name(route)] = { loadBalancer: {
1031
+ servers: route.upstreams.map((upstream) => ({ url: url(upstream) })),
1032
+ ...route.prefix ? { passHostHeader: false } : {},
1033
+ ...route.streaming ? { responseForwarding: { flushInterval: "-1ms" } } : {},
1034
+ ...route.health ? { healthCheck: {
1035
+ path: route.health,
1036
+ interval: "5s",
1037
+ timeout: "3s"
1038
+ } } : {}
1039
+ } };
1040
+ }
1041
+ return {
1042
+ http: {
1043
+ routers,
1044
+ services,
1045
+ ...Object.keys(middlewares).length > 0 ? { middlewares } : {}
1046
+ },
1047
+ ...tls.kind === "files" ? { tls: { certificates: [{
1048
+ certFile: tls.certFile,
1049
+ keyFile: tls.keyFile
1050
+ }] } } : {}
1051
+ };
1052
+ }
1053
+ /** A stack's dynamic configuration file, as written to the edge's directory. */
1054
+ function traefikDynamicFile(routes, options) {
1055
+ return `# Generated by gkm compose for ${options.project} — do not edit.
1056
+ # The shared edge (${EDGE_PROJECT}) reads every file in this directory; this
1057
+ # one is removed by gkm compose --down.
1058
+ ${yaml$1(traefikDynamic(routes, options))}`;
1059
+ }
1060
+ /** The edge's static configuration file. */
1061
+ function traefikStaticFile(ports) {
1062
+ return `# Generated by gkm compose — the server's shared edge. Do not edit.
1063
+ ${yaml$1(traefikStatic(ports))}`;
1064
+ }
1065
+ /** The edge's compose file. */
1066
+ function edgeComposeFile(options) {
1067
+ return `# Generated by gkm compose — the server's shared edge. Do not edit.
1068
+ #
1069
+ # Started by the first stack that runs with proxy: 'traefik', and left
1070
+ # running by gkm compose --down. Stop it, once nothing uses it, with:
1071
+ #
1072
+ # docker compose -p ${EDGE_PROJECT} down
1073
+ ${yaml$1(edgeCompose(options))}`;
1074
+ }
1075
+ function yaml$1(value) {
1076
+ return (0, yaml.stringify)(value, {
1077
+ lineWidth: 0,
1078
+ aliasDuplicateObjects: false
1079
+ });
1080
+ }
1081
+ //#endregion
1082
+ //#region src/compose/stack.ts
1083
+ /**
1084
+ * The stack a workspace runs as for one stage — pure.
1085
+ *
1086
+ * Everything `gkm compose` writes is decided here, as data: the compose file,
1087
+ * the Caddyfile, one env file per backend, and the image each app runs. The
1088
+ * command around it reads the workspace, asks the registry, writes the files
1089
+ * and starts Docker; none of those decide anything, so all of it can be
1090
+ * asserted without a daemon.
1091
+ *
1092
+ * The values come from the same derivations every other target uses. The
1093
+ * plan is reconcile's `planFor`, the keys an app reads are `appEnvKeys`, and
1094
+ * every URL is `envFor`'s — asked twice, once with each app at its public
1095
+ * address and once on the compose network, because a browser and a sibling
1096
+ * service reach the same API by different names.
1097
+ */
1098
+ /** Where a stack's files are written, relative to the workspace root. */
1099
+ function stackDir(stage) {
1100
+ return `.gkm/compose/${stage}`;
1101
+ }
1102
+ /**
1103
+ * The compose project a stage's stack runs as.
1104
+ *
1105
+ * The deploy identity's scope, so two workspaces with one name in different
1106
+ * namespaces never share containers or volumes — and the stage, so a
1107
+ * machine can run two stages side by side.
1108
+ */
1109
+ function composeProject(identity) {
1110
+ return `${identity.scope}-${identity.stage}`;
1111
+ }
1112
+ /** The ports the edge is published on, and the variables that move them. */
1113
+ const EDGE_PORT_ENV = {
1114
+ https: "GKM_COMPOSE_HTTPS_PORT",
1115
+ http: "GKM_COMPOSE_HTTP_PORT"
1116
+ };
1117
+ /**
1118
+ * The variable that moves the local stage's OpenObserve off its loopback
1119
+ * port, 5080 — which the local stage takes from nowhere else, since it
1120
+ * ignores `deploy.telemetry`. Another stack, or another tool, may hold 5080.
1121
+ */
1122
+ const LOGS_PORT_ENV = "GKM_COMPOSE_LOGS_PORT";
1123
+ /**
1124
+ * Containers this target does not run.
1125
+ *
1126
+ * The AWS emulator never applies: a stack is a server target, whose events
1127
+ * are pg-boss. Reconcile's Caddy fronts `gkm dev`'s host processes; the stack
1128
+ * brings its own edge.
1129
+ *
1130
+ * MinIO and Mailpit run on the local stage always, and on a deployed stage
1131
+ * only where `--allow-dev-services` allowed them and its secrets configure no
1132
+ * real bucket or mail server — see `devServices.ts`.
1133
+ */
1134
+ const NOT_RUN = {
1135
+ localstack: true,
1136
+ caddy: true
1137
+ };
1138
+ /** The kinds a deployed stage takes from its secrets, or a dev service. */
1139
+ const SERVICE_KINDS = {
1140
+ email: "email",
1141
+ objects: "objects",
1142
+ "file-server": "file-server"
1143
+ };
1144
+ /** The region MinIO is addressed with. It has no regions; the SDK wants one. */
1145
+ const MINIO_REGION = "us-east-1";
1146
+ /** The S3 client's credentials, read beside a bucket's URL when it has them. */
1147
+ const STORAGE_KEYS = [
1148
+ "AWS_ACCESS_KEY_ID",
1149
+ "AWS_SECRET_ACCESS_KEY",
1150
+ "AWS_REGION"
1151
+ ];
1152
+ /**
1153
+ * Every service's Docker logs, rotated. The json-file driver keeps a
1154
+ * container's output in one file that grows until the disk is full — on a
1155
+ * small server, in weeks. Three files of 10 MB each per container is enough
1156
+ * to see what happened and never fills anything. A project's own override
1157
+ * file that sets `logging` wins over it.
1158
+ */
1159
+ const LOG_ROTATION = {
1160
+ driver: "json-file",
1161
+ options: {
1162
+ "max-size": "10m",
1163
+ "max-file": "3"
1164
+ }
1165
+ };
1166
+ /**
1167
+ * The stack with Caddy's files inline: its Caddyfile and, where the stage has
1168
+ * one, its own certificate — `configs` with their `content`, which compose
1169
+ * copies into the container through the engine's API. Nothing is mounted from
1170
+ * a path, so the stack runs the same on a server's engine, over SSH, as on
1171
+ * this machine's. Their hash is a label: a changed file is a changed service,
1172
+ * which `up` recreates (compose does not, for a config's content alone).
1173
+ */
1174
+ function withCaddyConfigs(compose, files) {
1175
+ const caddy = compose.services.caddy;
1176
+ if (!caddy) return compose;
1177
+ const configs = {
1178
+ caddyfile: { content: files.caddyfile },
1179
+ ...files.tls ? {
1180
+ "tls-cert": { content: files.tls.cert },
1181
+ "tls-key": { content: files.tls.key }
1182
+ } : {}
1183
+ };
1184
+ const hash = (0, node_crypto.createHash)("sha256");
1185
+ for (const [name, { content }] of Object.entries(configs)) hash.update(`${name}\0${content}\0`);
1186
+ return {
1187
+ ...compose,
1188
+ services: {
1189
+ ...compose.services,
1190
+ caddy: {
1191
+ ...caddy,
1192
+ configs: [{
1193
+ source: "caddyfile",
1194
+ target: "/etc/caddy/Caddyfile"
1195
+ }, ...files.tls ? [{
1196
+ source: "tls-cert",
1197
+ target: `${CADDY_TLS_DIR}/cert.pem`
1198
+ }, {
1199
+ source: "tls-key",
1200
+ target: `${CADDY_TLS_DIR}/key.pem`,
1201
+ mode: 256
1202
+ }] : []],
1203
+ labels: {
1204
+ ...caddy.labels,
1205
+ "dev.geekmidas.caddy.config": hash.digest("hex").slice(0, 16)
1206
+ }
1207
+ }
1208
+ },
1209
+ configs: {
1210
+ ...compose.configs,
1211
+ ...configs
1212
+ }
1213
+ };
1214
+ }
1215
+ /** The local stage's stack, without the logins `gkm dev` generates. */
1216
+ var LocalCredentialsMissing = class extends require_errors.GkmError {
1217
+ stage;
1218
+ constructor(stage) {
1219
+ super(`The local stage '${stage}' runs with this machine's generated logins, and none were passed. Load them with loadLocalCredentials(workspace) and pass them as localCredentials.`);
1220
+ this.stage = stage;
1221
+ this.name = "LocalCredentialsMissing";
1222
+ }
1223
+ };
1224
+ /**
1225
+ * A deployed stage's container logins, derived from its seed like its role
1226
+ * passwords — so every deploy of the stage computes the same ones, and no one
1227
+ * reading the repo can.
1228
+ */
1229
+ function derivedCredentials(project, plan, seed) {
1230
+ const derive = (purpose) => require_seeds.localRolePassword(project, plan, purpose, seed);
1231
+ return {
1232
+ postgres: {
1233
+ user: require_seeds.postgresSuperuser(project),
1234
+ password: derive("master")
1235
+ },
1236
+ minio: {
1237
+ user: `${project}-minio`,
1238
+ password: derive("minio")
1239
+ },
1240
+ rabbitmq: {
1241
+ user: require_seeds.LOCAL_RABBITMQ_USER,
1242
+ password: derive("rabbitmq")
1243
+ },
1244
+ redis: { password: derive("redis") },
1245
+ cacheToken: derive("cache-token"),
1246
+ emulator: {
1247
+ accessKeyId: require_seeds.EMULATOR_ACCESS_KEY_ID,
1248
+ secretAccessKey: derive("emulator")
1249
+ },
1250
+ logs: {
1251
+ email: `admin@${project}`,
1252
+ password: derive("logs")
1253
+ }
1254
+ };
1255
+ }
1256
+ /** A value only the stage's secrets can hold, and they do not. */
1257
+ var StageSecretMissing = class extends require_errors.GkmError {
1258
+ app;
1259
+ key;
1260
+ stage;
1261
+ constructor(app, key, stage) {
1262
+ super(`'${app}' reads ${key}, and the stage '${stage}' has none. Nothing in the stack can derive it; set it in the stage's secrets: gkm secrets:set ${key} '…' --stage ${stage}`);
1263
+ this.app = app;
1264
+ this.key = key;
1265
+ this.stage = stage;
1266
+ this.name = "StageSecretMissing";
1267
+ }
1268
+ };
1269
+ /** A deployed stage's secrets with no seed to derive its passwords from. */
1270
+ var StageSeedMissing = class extends require_errors.GkmError {
1271
+ stage;
1272
+ constructor(stage) {
1273
+ super(`The stage '${stage}' has no seed in its secrets, and a deployed stage's database passwords are derived from it. Run gkm compose --stage ${stage} without --dry-run once, or gkm deploy, to generate it.`);
1274
+ this.stage = stage;
1275
+ this.name = "StageSeedMissing";
1276
+ }
1277
+ };
1278
+ /** A workspace with no API and no site to run. */
1279
+ var NothingToCompose = class extends require_errors.GkmError {
1280
+ root;
1281
+ constructor(root) {
1282
+ super(`${root} declares no RestApi, no site and no Worker with work to do, so a stack would run nothing. Mobile apps ship through their own toolchain.`);
1283
+ this.root = root;
1284
+ this.name = "NothingToCompose";
1285
+ }
1286
+ };
1287
+ /** A value that cannot be written to an env file a line at a time. */
1288
+ var EnvValueMultiline = class extends require_errors.GkmError {
1289
+ app;
1290
+ key;
1291
+ constructor(app, key) {
1292
+ super(`${key} for '${app}' spans more than one line, and an env file holds one value per line. Store it on one line — base64, or JSON — and decode it where it is read.`);
1293
+ this.app = app;
1294
+ this.key = key;
1295
+ this.name = "EnvValueMultiline";
1296
+ }
1297
+ };
1298
+ /** The services a stack's apps wait on and the edge waits on in turn. */
1299
+ const HEALTHY = { condition: "service_healthy" };
1300
+ /**
1301
+ * An app's health check: its own server, asked on 127.0.0.1 — not
1302
+ * `localhost`, which resolves to `::1` first in an Alpine container while a
1303
+ * server may listen on IPv4 alone.
1304
+ */
1305
+ function probe(port, path) {
1306
+ return {
1307
+ test: [
1308
+ "CMD",
1309
+ "wget",
1310
+ "-q",
1311
+ "-O",
1312
+ "/dev/null",
1313
+ `http://127.0.0.1:${port}${path}`
1314
+ ],
1315
+ interval: "5s",
1316
+ timeout: "3s",
1317
+ retries: 20,
1318
+ start_period: "10s"
1319
+ };
1320
+ }
1321
+ /**
1322
+ * What a stage's stack provisions: reconcile's plan, with a server target's
1323
+ * backends whatever `deploy.default` says — the stack is one machine running
1324
+ * containers, so events are pg-boss beside the declared database, and every
1325
+ * cache, one declared from that database included, is in the stack's own
1326
+ * Redis.
1327
+ */
1328
+ function stackPlan(workspace, manifest, stage) {
1329
+ return require_seeds.planFor(manifest, stage, (0, _geekmidas_manifest.provisionOrder)(manifest), {
1330
+ localStage: workspace.stages.local,
1331
+ events: require_SubscriberGenerator.DEFAULT_EVENTS.server,
1332
+ cache: require_seeds.STACK_CACHE,
1333
+ edge: false
1334
+ });
1335
+ }
1336
+ /** The stack for one stage. */
1337
+ function composeStack(input) {
1338
+ const { workspace, manifest, stage, identity } = input;
1339
+ const local = stage === workspace.stages.local;
1340
+ const project = composeProject(identity);
1341
+ const custom = input.secrets?.custom ?? {};
1342
+ const https = input.ports?.https ?? 443;
1343
+ const http = input.ports?.http ?? 80;
1344
+ const proxy = require_workspace.proxyFor(workspace.deploy?.compose, stage, local);
1345
+ const certificate = local ? void 0 : require_workspace.certificateFor(workspace.deploy?.compose, stage, workspace.root);
1346
+ const plan = stackPlan(workspace, manifest, stage);
1347
+ const byId = new Map(plan.resources.map((r) => [r.id, r]));
1348
+ if (local && !input.localCredentials) throw new LocalCredentialsMissing(stage);
1349
+ const seed = local ? input.localCredentials.seed : input.secrets?.seed;
1350
+ if (!seed) throw new StageSeedMissing(stage);
1351
+ const containers = local ? input.localCredentials : derivedCredentials(workspace.name, plan, seed);
1352
+ const credential = {
1353
+ containers: {
1354
+ ...containers,
1355
+ minio: {
1356
+ user: custom.AWS_ACCESS_KEY_ID ?? containers.minio.user,
1357
+ password: custom.AWS_SECRET_ACCESS_KEY ?? containers.minio.password
1358
+ }
1359
+ },
1360
+ seed
1361
+ };
1362
+ const apps = stackApps(workspace, manifest, plan, {
1363
+ local,
1364
+ stage,
1365
+ https,
1366
+ identity,
1367
+ images: input.images
1368
+ });
1369
+ const workers = stackWorkers(require_seeds.workerUnits(workspace, manifest, input.background ?? {}), {
1370
+ stage,
1371
+ identity,
1372
+ images: input.images
1373
+ });
1374
+ if (apps.length === 0 && workers.length === 0) throw new NothingToCompose(workspace.root);
1375
+ const owners = /* @__PURE__ */ new Map();
1376
+ for (const resource of plan.resources) {
1377
+ owners.set(resource.envKey, resource);
1378
+ if (resource.kind === "email") {
1379
+ owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "from"), resource);
1380
+ owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "inboxUrl"), resource);
1381
+ }
1382
+ if (resource.kind === "external-api") owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "credentials"), resource);
1383
+ }
1384
+ const domain = workspace.domains?.[stage];
1385
+ const telemetry = require_workspace.resolveStageTelemetry({
1386
+ ...workspace.deploy?.telemetry ? { telemetry: workspace.deploy.telemetry } : {},
1387
+ stage,
1388
+ local,
1389
+ target: "compose",
1390
+ runtime: "server",
1391
+ selfHosted: true,
1392
+ used: require_seeds.usesTelemetry(manifest)
1393
+ });
1394
+ const logsBase = telemetry?.provider === "self-hosted" ? require_seeds.stackLogs({
1395
+ config: local && input.ports?.logs ? {
1396
+ ...telemetry.selfHosted,
1397
+ port: input.ports.logs
1398
+ } : telemetry.selfHosted,
1399
+ stage,
1400
+ local,
1401
+ project: workspace.name,
1402
+ ...domain ? { domain } : {},
1403
+ custom,
1404
+ https,
1405
+ ...local ? { localLogin: input.localCredentials.logs } : {}
1406
+ }) : void 0;
1407
+ const services = local ? {
1408
+ missing: [],
1409
+ minio: plan.resources.filter((r) => r.kind === "objects").map((r) => r.id),
1410
+ mailpit: plan.resources.filter((r) => r.kind === "email").map((r) => r.id)
1411
+ } : require_seeds.externalServices({
1412
+ stage,
1413
+ declarations: require_seeds.stageServiceDeclarations({
1414
+ manifest,
1415
+ ...input.runnables ? { runnables: input.runnables } : {}
1416
+ }),
1417
+ supplied: custom,
1418
+ allow: input.allowDevServices === true,
1419
+ ...domain ? { domain } : {},
1420
+ providers: require_seeds.stageProviderNotes(workspace, stage)
1421
+ });
1422
+ const redis = require_seeds.stackRedis({
1423
+ plan,
1424
+ stage,
1425
+ local,
1426
+ custom,
1427
+ ...local ? { localPassword: input.localCredentials.redis.password } : {}
1428
+ });
1429
+ const infra = [...plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => c !== require_seeds.LOGS_SERVICE).filter((c) => c !== "minio" || services.minio.length > 0).filter((c) => c !== "mailpit" || services.mailpit.length > 0).filter((c) => c !== "redis" || redis !== void 0), ...logsBase ? [require_seeds.LOGS_SERVICE] : []].sort();
1430
+ const storage = infra.includes("minio") ? (() => {
1431
+ const buckets = services.minio.map((id) => byId.get(id)?.name).filter((name) => Boolean(name)).sort();
1432
+ return {
1433
+ ...credential.containers.minio,
1434
+ buckets,
1435
+ policies: require_seeds.bucketPolicies(plan).filter((p) => buckets.includes(p.bucket)),
1436
+ lifecycles: require_seeds.bucketLifecycles(plan).filter((l) => buckets.includes(l.bucket))
1437
+ };
1438
+ })() : void 0;
1439
+ const fileServers = /* @__PURE__ */ new Map();
1440
+ if (storage) for (const resource of plan.resources) {
1441
+ if (resource.kind !== "file-server" || !resource.of) continue;
1442
+ if (!services.minio.includes(resource.of)) continue;
1443
+ if (custom[resource.envKey] !== void 0) continue;
1444
+ const bucket = byId.get(resource.of);
1445
+ if (!bucket) continue;
1446
+ let host;
1447
+ if (local) host = require_seeds.hostFor(resource, workspace.name);
1448
+ else {
1449
+ if (!domain) throw new require_seeds.NoDomainForStage(stage);
1450
+ host = `${resource.subdomain ?? require_derive.appKey(resource.id)}.${domain}`;
1451
+ }
1452
+ fileServers.set(resource.id, {
1453
+ url: `https://${host}${local && https !== 443 ? `:${https}` : ""}`,
1454
+ route: {
1455
+ name: `files-${require_derive.appKey(resource.id)}`,
1456
+ host,
1457
+ upstreams: [{
1458
+ service: "minio",
1459
+ port: 9e3
1460
+ }],
1461
+ streaming: false,
1462
+ prefix: `/${bucket.name}`
1463
+ }
1464
+ });
1465
+ }
1466
+ const edgeServices = new Set(proxy === "traefik" ? [
1467
+ ...apps.map((app) => app.name),
1468
+ ...fileServers.size > 0 ? ["minio"] : [],
1469
+ ...logsBase?.host && logsBase.public ? [require_seeds.LOGS_SERVICE] : []
1470
+ ] : []);
1471
+ const hostOf = (service) => edgeServices.has(service) ? edgeAlias(project, service) : service;
1472
+ const logs = logsBase && {
1473
+ ...logsBase,
1474
+ appEnv: {
1475
+ ...logsBase.appEnv,
1476
+ OTEL_EXPORTER_OTLP_ENDPOINT: logsBase.appEnv.OTEL_EXPORTER_OTLP_ENDPOINT.replace(`//openobserve:`, `//${hostOf("openobserve")}:`)
1477
+ }
1478
+ };
1479
+ const derivation = {
1480
+ project: workspace.name,
1481
+ credentials: credential.containers,
1482
+ seed: credential.seed,
1483
+ ...!local && domain ? { mailFrom: `noreply@${domain}` } : {}
1484
+ };
1485
+ const outside = require_seeds.networkEnv(plan, {
1486
+ ...derivation,
1487
+ addresses: Object.fromEntries(apps.map((app) => [app.id, app.url]))
1488
+ }, hostOf);
1489
+ const inside = require_seeds.networkEnv(plan, {
1490
+ ...derivation,
1491
+ addresses: Object.fromEntries(apps.map((app) => [app.id, `http://${hostOf(app.name)}:${app.port}`]))
1492
+ }, hostOf);
1493
+ const surfaceUrls = new Map(plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site").map((r) => [r.envKey, r.id]));
1494
+ const appById = new Map(apps.map((app) => [app.id, app]));
1495
+ /**
1496
+ * What a key resolves to for one app, from the stage's secrets where only
1497
+ * they can hold it, and the derivations otherwise.
1498
+ */
1499
+ const valueFor = (app, key, owner) => {
1500
+ if (require_workspace.isReservedStageKey(key)) return void 0;
1501
+ if (!local && owner?.kind === "email" && key.endsWith("_INBOX_URL")) return;
1502
+ if (owner && SERVICE_KINDS[owner.kind]) {
1503
+ if (custom[key] !== void 0) return custom[key];
1504
+ if (owner.kind === "file-server") return fileServers.get(owner.id)?.url;
1505
+ return outside[key];
1506
+ }
1507
+ if (owner && require_seeds.suppliedOnly(owner.kind, owner.id, key, local)) {
1508
+ const value = custom[key];
1509
+ if (value === void 0) throw new StageSecretMissing(app.name, key, stage);
1510
+ return value;
1511
+ }
1512
+ if (custom[key] !== void 0) return custom[key];
1513
+ if (owner?.kind === "cache") return redis?.urls[key];
1514
+ const surface = surfaceUrls.get(key);
1515
+ if (app.kind === "rest-api" && surface && surface !== app.id) return inside[key];
1516
+ return outside[key];
1517
+ };
1518
+ const telemetryValues = logs ? require_workspace.telemetryEnv({
1519
+ endpoint: logs.appEnv.OTEL_EXPORTER_OTLP_ENDPOINT,
1520
+ headers: logs.appEnv.OTEL_EXPORTER_OTLP_HEADERS
1521
+ }, telemetry?.sampleRate ?? 1) : telemetry?.provider === "otlp" ? require_workspace.otlpTelemetryEnv(telemetry) : void 0;
1522
+ /** Each key one backend process reads, resolved for this stage. */
1523
+ const valuesFor = (app, keys) => {
1524
+ const values = {};
1525
+ for (const key of [...keys].sort()) {
1526
+ if (require_seeds.isTelemetryKey(key)) continue;
1527
+ const owner = owners.get(key) ?? storageOwner(key, plan);
1528
+ if (STORAGE_KEYS.includes(key)) {
1529
+ const value = custom[key] ?? storageValue(key, storage);
1530
+ if (value !== void 0) values[key] = value;
1531
+ continue;
1532
+ }
1533
+ const value = valueFor(app, key, owner);
1534
+ if (value !== void 0) values[key] = value;
1535
+ }
1536
+ return values;
1537
+ };
1538
+ for (const app of apps) {
1539
+ if (app.kind === "site") {
1540
+ const site = manifest[app.id];
1541
+ if (site?.kind !== "site") continue;
1542
+ const args = require_seeds.publicBuildValues(site, manifest, (key) => valueFor(app, key, owners.get(key)), custom);
1543
+ if (app.build && Object.keys(args).length > 0) app.build.args = args;
1544
+ continue;
1545
+ }
1546
+ if (input.buildOnly) continue;
1547
+ const keys = require_seeds.appEnvKeys(manifest, app.name, input.runnables) ?? /* @__PURE__ */ new Set();
1548
+ const values = valuesFor(app, keys);
1549
+ const trusted = (0, _geekmidas_manifest.provideKey)(app.id, "trustedOrigins");
1550
+ if (keys.has(trusted)) {
1551
+ const internal = (byId.get(app.id)?.callers ?? []).map((caller) => appById.get(caller)).filter((caller) => caller?.kind === "rest-api").map((caller) => `http://${hostOf(caller.name)}:${caller.port}`);
1552
+ const origins = [...(values[trusted] ?? "").split(",").filter(Boolean), ...internal];
1553
+ values[trusted] = [...new Set(origins)].join(",");
1554
+ }
1555
+ app.env = {
1556
+ ...require_seeds.scopeTelemetryEnv(values, {
1557
+ uses: require_seeds.telemetryOf(manifest, app.id) !== void 0,
1558
+ serviceName: app.name,
1559
+ ...telemetryValues ? { telemetry: telemetryValues } : {}
1560
+ }),
1561
+ NODE_ENV: "production",
1562
+ PORT: String(app.port),
1563
+ STAGE: stage
1564
+ };
1565
+ for (const [key, value] of Object.entries(app.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(app.name, key);
1566
+ }
1567
+ const mobileApps = [];
1568
+ for (const declaration of Object.values(manifest)) {
1569
+ if (declaration.kind !== "mobile-app") continue;
1570
+ const app = {
1571
+ name: require_derive.appKey(declaration.id),
1572
+ id: declaration.id,
1573
+ kind: "site"
1574
+ };
1575
+ mobileApps.push({
1576
+ name: app.name,
1577
+ id: app.id,
1578
+ values: require_seeds.publicBuildValues(declaration, manifest, (key) => valueFor(app, key, owners.get(key)), custom)
1579
+ });
1580
+ }
1581
+ for (const worker of input.buildOnly ? [] : workers) {
1582
+ const keys = require_seeds.workerEnvKeys(manifest, worker.id, input.runnables) ?? /* @__PURE__ */ new Set();
1583
+ const resolved = valuesFor({
1584
+ name: worker.name,
1585
+ id: worker.id,
1586
+ kind: "rest-api"
1587
+ }, keys);
1588
+ const push = local ? void 0 : workerPush(worker, input.subscribers?.[worker.id] ?? [], resolved, {
1589
+ stage,
1590
+ ...domain ? { domain } : {}
1591
+ });
1592
+ if (push) {
1593
+ worker.push = push;
1594
+ if (proxy === "traefik") edgeServices.add(worker.name);
1595
+ }
1596
+ worker.env = {
1597
+ ...require_seeds.scopeTelemetryEnv(resolved, {
1598
+ uses: require_seeds.telemetryOf(manifest, worker.id) !== void 0,
1599
+ serviceName: worker.name,
1600
+ ...telemetryValues ? { telemetry: telemetryValues } : {}
1601
+ }),
1602
+ ...push ? { [SUBSCRIBER_PUSH_URL_KEY]: push.url } : {},
1603
+ NODE_ENV: "production",
1604
+ PORT: String(worker.port),
1605
+ STAGE: stage
1606
+ };
1607
+ for (const [key, value] of Object.entries(worker.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(worker.name, key);
1608
+ }
1609
+ const deployedTls = certificate ? {
1610
+ kind: "files",
1611
+ ...proxy === "caddy" ? {
1612
+ certFile: `${CADDY_TLS_DIR}/cert.pem`,
1613
+ keyFile: `${CADDY_TLS_DIR}/key.pem`
1614
+ } : edgeCertificatePaths(project)
1615
+ } : { kind: "acme" };
1616
+ const tls = local ? { kind: "internal" } : deployedTls;
1617
+ const backupsChoice = input.buildOnly ? void 0 : require_dns.stageBackups(workspace, manifest, stage);
1618
+ const backups = backupsChoice?.mode === "on" && infra.includes("postgres") ? require_dns.stackBackups({
1619
+ workspace,
1620
+ stage,
1621
+ project,
1622
+ plan,
1623
+ backups: backupsChoice.backups,
1624
+ custom,
1625
+ seed,
1626
+ superuser: credential.containers.postgres,
1627
+ ...telemetryValues ? { telemetry: telemetryValues } : {}
1628
+ }) : void 0;
1629
+ const layout = input.layout ?? defaultLayout(workspace.root);
1630
+ const compose = stackFile({
1631
+ project,
1632
+ plan,
1633
+ infra,
1634
+ apps,
1635
+ workers,
1636
+ credentials: credential.containers,
1637
+ ...storage ? { storage } : {},
1638
+ ...logs ? { logs } : {},
1639
+ ...redis ? { redis } : {},
1640
+ ...backups ? { backups } : {},
1641
+ https,
1642
+ http,
1643
+ composeDir: (0, node_path.join)(workspace.root, stackDir(stage)),
1644
+ buildRoot: layout.buildRoot,
1645
+ workspaceRoot: workspace.root,
1646
+ envFiles: !input.buildOnly,
1647
+ proxy,
1648
+ edge: new Map([...edgeServices].map((service) => [service, hostOf(service)]))
1649
+ });
1650
+ const routes = [
1651
+ ...apps.map((app) => ({
1652
+ name: app.name,
1653
+ host: app.host,
1654
+ upstreams: [{
1655
+ service: app.name,
1656
+ port: app.port
1657
+ }],
1658
+ streaming: true,
1659
+ health: app.kind === "site" ? "/" : "/health"
1660
+ })),
1661
+ ...[...fileServers.values()].map(({ route }) => route),
1662
+ ...workers.flatMap(({ name, port, push }) => push ? [{
1663
+ name,
1664
+ host: push.host,
1665
+ upstreams: [{
1666
+ service: name,
1667
+ port
1668
+ }],
1669
+ streaming: false,
1670
+ paths: push.paths,
1671
+ health: "/health"
1672
+ }] : []),
1673
+ ...logs?.host && logs.public ? [{
1674
+ name: require_seeds.LOGS_SERVICE,
1675
+ host: logs.host,
1676
+ upstreams: [{
1677
+ service: require_seeds.LOGS_SERVICE,
1678
+ port: require_workspace.LOGS_PORT
1679
+ }],
1680
+ streaming: true,
1681
+ allow: logs.public.allow
1682
+ }] : []
1683
+ ];
1684
+ assertHostsUnique(routes);
1685
+ const dockerfiles = {};
1686
+ for (const app of apps) {
1687
+ if (!app.build) continue;
1688
+ dockerfiles[app.build.dockerfile] = require_seeds.appDockerfile(app.name, workspace.apps[app.name], {
1689
+ layout,
1690
+ workspaceRoot: workspace.root,
1691
+ apps: workspace.apps,
1692
+ manifest,
1693
+ cache: require_seeds.STACK_CACHE,
1694
+ workspace,
1695
+ stage,
1696
+ ...app.kind === "site" && app.build.args ? { publicUrlArgs: Object.keys(app.build.args) } : {}
1697
+ });
1698
+ }
1699
+ for (const worker of workers) {
1700
+ if (!worker.build) continue;
1701
+ dockerfiles[worker.build.dockerfile] = require_seeds.workerDockerfile({
1702
+ id: worker.id,
1703
+ name: worker.name,
1704
+ app: worker.host
1705
+ }, workspace.apps[worker.host], {
1706
+ layout,
1707
+ workspaceRoot: workspace.root,
1708
+ cache: require_seeds.STACK_CACHE
1709
+ });
1710
+ }
1711
+ return {
1712
+ project,
1713
+ stage,
1714
+ local,
1715
+ apps,
1716
+ workers,
1717
+ mobileApps,
1718
+ infra,
1719
+ compose,
1720
+ proxy,
1721
+ routes,
1722
+ ...proxy === "caddy" ? { caddyfile: edgeCaddyfile(routes, { tls }) } : { traefik: traefikDynamicFile(routes, {
1723
+ project,
1724
+ tls: deployedTls
1725
+ }) },
1726
+ ...certificate ? { tls: certificate } : {},
1727
+ dockerfiles,
1728
+ plan,
1729
+ credential,
1730
+ ...storage ? { storage } : {},
1731
+ devServices: local ? [] : require_seeds.devServicesUsed(services),
1732
+ ...logs ? { logs } : {},
1733
+ ...telemetry ? { telemetry } : {},
1734
+ ...redis ? { redis } : {},
1735
+ ...backups ? { backups } : {}
1736
+ };
1737
+ }
1738
+ /** The stack's MinIO's value for one of the S3 client's keys. */
1739
+ function storageValue(key, storage) {
1740
+ if (!storage) return void 0;
1741
+ if (key === "AWS_ACCESS_KEY_ID") return storage.user;
1742
+ if (key === "AWS_SECRET_ACCESS_KEY") return storage.password;
1743
+ if (key === "AWS_REGION") return MINIO_REGION;
1744
+ }
1745
+ /** Which bucket an S3 credential key belongs to — the first one declared. */
1746
+ function storageOwner(key, plan) {
1747
+ if (!STORAGE_KEYS.includes(key)) return void 0;
1748
+ return plan.resources.find((r) => r.kind === "objects");
1749
+ }
1750
+ /** The apps a stack runs: every API and every site, at its stage address. */
1751
+ function stackApps(workspace, manifest, plan, options) {
1752
+ const apps = [];
1753
+ const resources = new Map(plan.resources.map((r) => [r.id, r]));
1754
+ for (const [id, declaration] of Object.entries(manifest)) {
1755
+ if (declaration.kind !== "rest-api" && declaration.kind !== "site") continue;
1756
+ const name = require_derive.appKey(id);
1757
+ const app = workspace.apps[name];
1758
+ const resource = resources.get(id);
1759
+ if (!app || !resource || !app.port) continue;
1760
+ const host = hostOf(name, app, workspace, resource, options);
1761
+ const tag = declaration.kind === "site" ? siteTag(options.images.tag, options.stage) : options.images.tag;
1762
+ const ref = require_seeds.validateImageRef(require_identity.imageRef(options.identity, name, options.images.registry, tag));
1763
+ apps.push({
1764
+ app: name,
1765
+ name,
1766
+ id,
1767
+ kind: declaration.kind,
1768
+ path: app.path,
1769
+ port: app.port,
1770
+ host,
1771
+ url: `https://${host}${options.local && options.https !== 443 ? `:${options.https}` : ""}`,
1772
+ ref,
1773
+ tag,
1774
+ ...options.images.mode === "build" ? { build: { dockerfile: `${stackDir(options.stage)}/Dockerfile.${name}` } } : {}
1775
+ });
1776
+ }
1777
+ return apps.sort((a, b) => a.name.localeCompare(b.name));
1778
+ }
1779
+ /** What the worker reads the public base of its push routes from. */
1780
+ const SUBSCRIBER_PUSH_URL_KEY = "GKM_SUBSCRIBER_PUSH_URL";
1781
+ /**
1782
+ * Where SNS pushes to a worker on a deployed stage, or nothing when it pushes
1783
+ * to none of its subscribers.
1784
+ *
1785
+ * A subscriber is pushed to when its topic's connection string, as this stage
1786
+ * resolved it for the worker, is an SNS one — the same test the worker's own
1787
+ * startup makes, so the edge exposes a route exactly when the worker mounts
1788
+ * it. Each such route, and nothing else the worker serves, answers on
1789
+ * `<worker>.<domain>`.
1790
+ */
1791
+ function workerPush(worker, subscribers, env, options) {
1792
+ const paths = subscribers.filter(({ topic }) => env[(0, _geekmidas_manifest.provideKey)(topic, "publisherConnectionString")]?.startsWith("sns:")).map(({ name }) => require_SubscriberGenerator.subscriberRoute(name)).sort();
1793
+ if (paths.length === 0) return void 0;
1794
+ if (!options.domain) throw new require_seeds.NoDomainForStage(options.stage);
1795
+ const host = `${worker.name}.${options.domain}`;
1796
+ return {
1797
+ host,
1798
+ url: `https://${host}`,
1799
+ paths: [...new Set(paths)]
1800
+ };
1801
+ }
1802
+ /** Two constructs the edge would answer on one host. */
1803
+ var EdgeHostTaken = class extends require_errors.GkmError {
1804
+ host;
1805
+ first;
1806
+ second;
1807
+ constructor(host, first, second) {
1808
+ super(`'${first}' and '${second}' would both answer on ${host}, and the edge can send a host to only one of them. Give one a subdomain of its own, or rename it.`);
1809
+ this.host = host;
1810
+ this.first = first;
1811
+ this.second = second;
1812
+ this.name = "EdgeHostTaken";
1813
+ }
1814
+ };
1815
+ /** Refuse a stack where one host would be routed to two services. */
1816
+ function assertHostsUnique(routes) {
1817
+ const claimed = /* @__PURE__ */ new Map();
1818
+ for (const { host, name } of routes) {
1819
+ const first = claimed.get(host);
1820
+ if (first !== void 0) throw new EdgeHostTaken(host, first, name);
1821
+ claimed.set(host, name);
1822
+ }
1823
+ }
1824
+ /** Each worker's image, and where it is built from. */
1825
+ function stackWorkers(units, options) {
1826
+ return units.map((unit) => ({
1827
+ app: unit.name,
1828
+ name: unit.name,
1829
+ id: unit.id,
1830
+ host: unit.app,
1831
+ port: require_seeds.WORKER_PORT,
1832
+ ref: require_seeds.validateImageRef(require_identity.imageRef(options.identity, unit.name, options.images.registry, options.images.tag)),
1833
+ tag: options.images.tag,
1834
+ ...options.images.mode === "build" ? { build: { dockerfile: `${stackDir(options.stage)}/Dockerfile.${unit.name}` } } : {}
1835
+ }));
1836
+ }
1837
+ /**
1838
+ * The host an app answers on for a stage: the `gkm dev` edge's
1839
+ * `*.localhost` names for the local stage, and the stage's domain — by the
1840
+ * same rule a deploy follows — for any other.
1841
+ */
1842
+ function hostOf(name, app, workspace, resource, options) {
1843
+ if (options.local) return require_seeds.hostFor(resource, workspace.name);
1844
+ return require_seeds.resolveHost(name, app, options.stage, workspace.domains, require_seeds.isMainFrontendApp(name, app, workspace.apps));
1845
+ }
1846
+ /** The compose document: the infrastructure, the apps, and the edge. */
1847
+ function stackFile(options) {
1848
+ const { project, plan, apps } = options;
1849
+ const infra = options.infra.filter((name) => name !== require_seeds.LOGS_SERVICE);
1850
+ const derived = require_seeds.composeFor({
1851
+ ...plan,
1852
+ containers: [...infra]
1853
+ }, {
1854
+ project,
1855
+ ports: Object.fromEntries(require_seeds.portKeys(infra, plan.fakes).map((key) => [key, 0])),
1856
+ credentials: options.credentials
1857
+ });
1858
+ const services = {};
1859
+ const volumes = {
1860
+ ...derived.volumes,
1861
+ ...options.proxy === "caddy" ? {
1862
+ "caddy-data": {},
1863
+ "caddy-config": {}
1864
+ } : {},
1865
+ ...options.logs ? { "openobserve-data": {} } : {}
1866
+ };
1867
+ for (const name of infra) {
1868
+ const { ports: _ports, depends_on, profiles: _profiles, ...service } = derived.services[name];
1869
+ services[name] = {
1870
+ ...service,
1871
+ ...depends_on?.length ? { depends_on: Object.fromEntries(depends_on.map((dep) => [dep, HEALTHY])) } : {}
1872
+ };
1873
+ }
1874
+ const postgres = services.postgres;
1875
+ if (postgres) postgres.ports = ["127.0.0.1::5432"];
1876
+ const minio = services.minio;
1877
+ if (minio && options.storage) {
1878
+ minio.environment = {
1879
+ ...minio.environment,
1880
+ MINIO_ROOT_USER: options.storage.user,
1881
+ MINIO_ROOT_PASSWORD: options.storage.password
1882
+ };
1883
+ minio.ports = ["127.0.0.1::9000"];
1884
+ }
1885
+ if (services["redis"] && options.redis) services[require_seeds.REDIS_SERVICE] = require_seeds.redisService(options.redis);
1886
+ if (options.logs) services[require_seeds.LOGS_SERVICE] = require_seeds.logsService(options.logs);
1887
+ if (options.backups) services[require_dns.BACKUPS_SERVICE] = require_dns.backupsService(options.backups, options.envFiles);
1888
+ for (const app of apps) services[app.name] = {
1889
+ image: app.ref,
1890
+ ...app.build ? { build: {
1891
+ ...require_seeds.composeBuildPaths({
1892
+ composeDir: options.composeDir,
1893
+ buildRoot: options.buildRoot,
1894
+ workspaceRoot: options.workspaceRoot,
1895
+ dockerfile: app.build.dockerfile
1896
+ }),
1897
+ ...app.build.args ? { args: app.build.args } : {}
1898
+ } } : {},
1899
+ restart: "unless-stopped",
1900
+ ...app.kind === "rest-api" ? {
1901
+ env_file: [{
1902
+ path: `./${app.name}.env`,
1903
+ format: "raw"
1904
+ }],
1905
+ ...infra.length ? { depends_on: Object.fromEntries(infra.map((name) => [name, HEALTHY])) } : {}
1906
+ } : {},
1907
+ healthcheck: probe(app.port, app.kind === "site" ? "/" : "/health")
1908
+ };
1909
+ for (const worker of options.workers) services[worker.name] = {
1910
+ image: worker.ref,
1911
+ ...worker.build ? { build: require_seeds.composeBuildPaths({
1912
+ composeDir: options.composeDir,
1913
+ buildRoot: options.buildRoot,
1914
+ workspaceRoot: options.workspaceRoot,
1915
+ dockerfile: worker.build.dockerfile
1916
+ }) } : {},
1917
+ restart: "unless-stopped",
1918
+ env_file: [{
1919
+ path: `./${worker.name}.env`,
1920
+ format: "raw"
1921
+ }],
1922
+ ...infra.length ? { depends_on: Object.fromEntries(infra.map((name) => [name, HEALTHY])) } : {},
1923
+ healthcheck: probe(worker.port, "/health")
1924
+ };
1925
+ if (options.proxy === "caddy") services.caddy = {
1926
+ image: require_seeds.DEFAULT_IMAGES.caddy,
1927
+ restart: "unless-stopped",
1928
+ ports: [`${options.https}:443`, `${options.http}:80`],
1929
+ volumes: ["caddy-data:/data", "caddy-config:/config"],
1930
+ depends_on: Object.fromEntries([...apps.map((app) => app.name), ...options.workers.filter((w) => w.push).map((w) => w.name)].map((name) => [name, HEALTHY])),
1931
+ healthcheck: {
1932
+ test: [
1933
+ "CMD",
1934
+ "caddy",
1935
+ "version"
1936
+ ],
1937
+ interval: "10s",
1938
+ timeout: "5s",
1939
+ retries: 5
1940
+ }
1941
+ };
1942
+ for (const [service, alias] of options.edge) {
1943
+ const definition = services[service];
1944
+ if (!definition) continue;
1945
+ definition.networks = {
1946
+ default: { aliases: [alias] },
1947
+ edge: { aliases: [alias] }
1948
+ };
1949
+ }
1950
+ for (const service of Object.values(services)) {
1951
+ if (!options.envFiles) delete service.env_file;
1952
+ service.logging = {
1953
+ driver: LOG_ROTATION.driver,
1954
+ options: { ...LOG_ROTATION.options }
1955
+ };
1956
+ }
1957
+ return {
1958
+ name: project,
1959
+ services,
1960
+ volumes,
1961
+ ...options.edge.size > 0 ? { networks: { edge: {
1962
+ name: EDGE_NETWORK,
1963
+ external: true
1964
+ } } } : {}
1965
+ };
1966
+ }
1967
+ /**
1968
+ * A build without a layout: the workspace is the build root, with pnpm.
1969
+ * What a caller that never looked at the filesystem gets.
1970
+ */
1971
+ function defaultLayout(root) {
1972
+ return {
1973
+ buildRoot: root,
1974
+ gkmRoot: ".",
1975
+ tools: {
1976
+ packageManager: "pnpm",
1977
+ turboVersion: require_seeds.TURBO_VERSION,
1978
+ monorepo: true
1979
+ },
1980
+ gkmPaths: ["gkm.config.*"]
1981
+ };
1982
+ }
1983
+ /** Render an app's env file: one `KEY=value` per line, read raw by compose. */
1984
+ function envFile(env) {
1985
+ return `${Object.entries(env).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`).join("\n")}\n`;
1986
+ }
1987
+ //#endregion
1988
+ //#region src/compose/edge.ts
1989
+ /**
1990
+ * The server's shared Traefik edge, in Docker: started when a stack needs it,
1991
+ * and each stack's routes written to — and removed from — the directory it
1992
+ * watches.
1993
+ *
1994
+ * Everything goes through the server's Docker engine, never its filesystem:
1995
+ * the edge's static configuration is handed to it inline, and the directories
1996
+ * it watches are volumes each stack writes into with `docker exec`. So the
1997
+ * deploy runs anywhere that reaches the engine — a CI runner over SSH — and
1998
+ * nothing it writes depends on a path on that machine.
1999
+ *
2000
+ * gkm-edge (compose project)
2001
+ * traefik.yml its static configuration (inline config)
2002
+ * dynamic/<project>.yml one per stack: routers, services, middlewares
2003
+ * certs/<project>.crt|.key a stack's own certificate, where it has one
2004
+ */
2005
+ /** The edge's compose project, as docker is asked about it. */
2006
+ function edgeRef(file, options = {}) {
2007
+ return {
2008
+ project: EDGE_PROJECT,
2009
+ file,
2010
+ cwd: (0, node_path.dirname)(file),
2011
+ ...options
2012
+ };
2013
+ }
2014
+ /** Writing to or removing from the edge's volumes failed. */
2015
+ var EdgeWriteFailed = class extends require_errors.GkmError {
2016
+ path;
2017
+ output;
2018
+ constructor(path, output) {
2019
+ super(`Could not write ${path} in the shared edge (${EDGE_PROJECT}): ${output.trim() || "it is not running"}. Check it with \`docker compose -p ${EDGE_PROJECT} ps\` on the server, and run gkm compose again.`);
2020
+ this.path = path;
2021
+ this.output = output;
2022
+ this.name = "EdgeWriteFailed";
2023
+ }
2024
+ };
2025
+ /**
2026
+ * Something other than the proxy a stack is set to holds the edge's ports.
2027
+ *
2028
+ * With `proxy: 'traefik'`, the stack's own Caddy — still running from before
2029
+ * the switch — or any other container; with `proxy: 'caddy'`, the shared
2030
+ * edge, or another stack's Caddy. Refused before anything is started, so the
2031
+ * stack is never left half on one proxy and half on the other.
2032
+ */
2033
+ var ComposeProxyClash = class extends require_errors.GkmError {
2034
+ project;
2035
+ proxy;
2036
+ port;
2037
+ holder;
2038
+ constructor(project, proxy, port, holder) {
2039
+ const by = holder.project ? `${holder.container} (compose project ${holder.project})` : holder.container;
2040
+ const fix = proxy === "traefik" ? holder.project === project && holder.service === "caddy" ? `That is this stack's own Caddy, from before it was switched to proxy: 'traefik'. Move it once: stop it (docker compose -p ${project} stop caddy), then run gkm compose again — the shared edge takes 80/443 and the stack registers with it.` : `The shared edge needs ports 80 and 443. Stop ${holder.container}${holder.project ? ` (docker compose -p ${holder.project} down, or its own gkm compose --down)` : ""}, or move the edge with GKM_COMPOSE_HTTPS_PORT/GKM_COMPOSE_HTTP_PORT.` : holder.project === "gkm-edge" ? `That is the server's shared Traefik edge, which other stacks register with. Set deploy.compose.proxy to 'traefik' for this stage so the stack registers with it too, or stop the edge once nothing uses it (docker compose -p ${EDGE_PROJECT} down).` : `Stop ${holder.container}, or publish this stack's Caddy elsewhere with GKM_COMPOSE_HTTPS_PORT/GKM_COMPOSE_HTTP_PORT.`;
2041
+ super(`${project} is set to proxy: '${proxy}', and port ${port} is held by ${by}. ${fix}`);
2042
+ this.project = project;
2043
+ this.proxy = proxy;
2044
+ this.port = port;
2045
+ this.holder = holder;
2046
+ this.name = "ComposeProxyClash";
2047
+ }
2048
+ };
2049
+ /**
2050
+ * Refuse a stack whose edge ports are held by anything but its own proxy:
2051
+ * the shared edge for `'traefik'`, the stack's own Caddy for `'caddy'`.
2052
+ */
2053
+ async function assertEdgePorts(docker, options) {
2054
+ const { project, proxy, ports } = options;
2055
+ for (const port of [ports.https, ports.http]) for (const holder of await docker.publishers(options.engine, port)) if (!(proxy === "traefik" ? holder.project === "gkm-edge" : holder.project === project && holder.service === "caddy")) throw new ComposeProxyClash(project, proxy, port, holder);
2056
+ }
2057
+ /**
2058
+ * Write a file into the edge's running container whole or not at all: a
2059
+ * temporary name, then a rename. Not `.yml`: the edge reads only `.yml`,
2060
+ * `.yaml` and `.toml` files, so it never sees one half written.
2061
+ */
2062
+ async function put(docker, edge, path, content, mode = "644") {
2063
+ const result = await docker.exec(edge, EDGE_SERVICE, [
2064
+ "sh",
2065
+ "-c",
2066
+ "set -e; t=\"$1.tmp\"; cat > \"$t\"; chmod \"$2\" \"$t\"; mv \"$t\" \"$1\"",
2067
+ "sh",
2068
+ path,
2069
+ mode
2070
+ ], content);
2071
+ if (result.code !== 0) throw new EdgeWriteFailed(path, `${result.stderr}${result.stdout}`);
2072
+ }
2073
+ /** Remove files from the edge's container; whether the first was there. */
2074
+ async function remove(docker, edge, paths) {
2075
+ return (await docker.exec(edge, EDGE_SERVICE, [
2076
+ "sh",
2077
+ "-c",
2078
+ "e=1; [ -f \"$1\" ] && e=0; rm -f \"$@\"; exit $e",
2079
+ "sh",
2080
+ ...paths
2081
+ ])).code === 0;
2082
+ }
2083
+ /**
2084
+ * The edge, up: its network and its container — started when it is not
2085
+ * running, and recreated when its configuration changed. Idempotent: on a
2086
+ * server where it already runs as configured, `up` changes nothing. Its
2087
+ * compose file is written to `file`, on this machine: compose reads it here.
2088
+ */
2089
+ async function ensureEdge(docker, options) {
2090
+ const { file, ports } = options;
2091
+ const content = edgeComposeFile({
2092
+ ports,
2093
+ staticConfig: traefikStaticFile(ports),
2094
+ logging: options.logging
2095
+ });
2096
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(file), { recursive: true });
2097
+ if (await (0, node_fs_promises.readFile)(file, "utf-8").catch(() => void 0) !== content) await (0, node_fs_promises.writeFile)(file, content);
2098
+ const ref = edgeRef(file, {
2099
+ ...options.host ? { host: options.host } : {},
2100
+ ...options.output ? { output: options.output } : {},
2101
+ ...options.signal ? { signal: options.signal } : {}
2102
+ });
2103
+ await docker.ensureNetwork(ref, EDGE_NETWORK);
2104
+ await docker.up(ref, [EDGE_SERVICE]);
2105
+ return [file];
2106
+ }
2107
+ /** A stack's routes, inside the edge's container. */
2108
+ function routesPath(project) {
2109
+ return `${EDGE_DYNAMIC_DIR}/${project}.yml`;
2110
+ }
2111
+ function certificatePaths(project) {
2112
+ return [`${EDGE_CERTS_DIR}/${project}.crt`, `${EDGE_CERTS_DIR}/${project}.key`];
2113
+ }
2114
+ /**
2115
+ * Register a stack with the edge: its certificate first, where it has one,
2116
+ * so the routes that need it never load without it — then its routes, in one
2117
+ * rename the edge picks up whole. The certificate is read here and written
2118
+ * through the engine. The paths written, inside the edge.
2119
+ */
2120
+ async function writeEdgeRoutes(docker, edge, project, routes, certificate) {
2121
+ const written = [];
2122
+ const [crt, key] = certificatePaths(project);
2123
+ if (certificate) {
2124
+ await put(docker, edge, crt, await (0, node_fs_promises.readFile)(certificate.certFile, "utf-8"));
2125
+ await put(docker, edge, key, await (0, node_fs_promises.readFile)(certificate.keyFile, "utf-8"), "600");
2126
+ written.push(crt, key);
2127
+ } else await remove(docker, edge, [crt, key]);
2128
+ await put(docker, edge, routesPath(project), routes);
2129
+ written.push(routesPath(project));
2130
+ return written;
2131
+ }
2132
+ /** What a stack's file holds for the moment between emptying and removal. */
2133
+ const UNREGISTERED = "# Unregistered by gkm compose --down.\n";
2134
+ /**
2135
+ * Unregister a stack: its routes, then its certificate. Whether there was
2136
+ * anything to remove — false, too, when the edge is not running.
2137
+ */
2138
+ async function removeEdgeRoutes(docker, edge, project) {
2139
+ const file = routesPath(project);
2140
+ if ((await docker.exec(edge, "traefik", [
2141
+ "test",
2142
+ "-f",
2143
+ file
2144
+ ])).code !== 0) return false;
2145
+ await put(docker, edge, file, UNREGISTERED);
2146
+ await remove(docker, edge, [file, ...certificatePaths(project)]);
2147
+ return true;
2148
+ }
2149
+ //#endregion
2150
+ //#region src/compose/secrets.ts
2151
+ function deployedStackSecrets(workspace, stage, stored, manifest) {
2152
+ const withSeed = require_seeds.withGeneratedSecrets(stored ?? require_seeds.initStageSecrets(stage), manifest);
2153
+ const withLogs = require_workspace.resolveStageTelemetry({
2154
+ ...workspace.deploy?.telemetry ? { telemetry: workspace.deploy.telemetry } : {},
2155
+ stage,
2156
+ local: false,
2157
+ target: "compose",
2158
+ runtime: "server",
2159
+ selfHosted: true,
2160
+ used: require_seeds.usesTelemetry(manifest)
2161
+ })?.provider === "self-hosted" ? require_seeds.withLogsPassword(withSeed.secrets) : {
2162
+ secrets: withSeed.secrets,
2163
+ generated: []
2164
+ };
2165
+ const withRedis = require_seeds.runsRedis(manifest, withLogs.secrets.custom ?? {}) ? require_seeds.withRedisPassword(withLogs.secrets) : {
2166
+ secrets: withLogs.secrets,
2167
+ generated: []
2168
+ };
2169
+ return {
2170
+ secrets: withRedis.secrets,
2171
+ generated: [
2172
+ ...withSeed.generated,
2173
+ ...withLogs.generated,
2174
+ ...withRedis.generated
2175
+ ]
2176
+ };
2177
+ }
2178
+ //#endregion
2179
+ //#region src/target/compose/health.ts
2180
+ /**
2181
+ * Asking each app of a stack whether it answers — through Caddy, over HTTPS,
2182
+ * the way a browser reaches it.
2183
+ *
2184
+ * `docker compose up --wait` already knows each container passed its own
2185
+ * health check, asked on 127.0.0.1 inside it. What that cannot say is whether
2186
+ * the stack is reachable: whether the edge routes the host to the app, and
2187
+ * whether the certificate it presents is one a client accepts. So the check
2188
+ * goes through the edge, by hostname, and verifies the certificate.
2189
+ */
2190
+ /** A request that got no answer in time. */
2191
+ var HealthRequestTimedOut = class extends require_errors.GkmError {
2192
+ url;
2193
+ timeoutMs;
2194
+ constructor(url, timeoutMs) {
2195
+ super(`${url} did not answer within ${timeoutMs}ms. Check the stack is up with \`docker compose ps\`.`);
2196
+ this.url = url;
2197
+ this.timeoutMs = timeoutMs;
2198
+ this.name = "HealthRequestTimedOut";
2199
+ }
2200
+ };
2201
+ /** The real probe: one HTTPS GET, the certificate verified. */
2202
+ const httpsProbe = ({ url, ca, connectTo, connectPort, timeoutMs, signal }) => {
2203
+ const target = new URL(url);
2204
+ return new Promise((resolve, reject) => {
2205
+ const req = (0, node_https.request)({
2206
+ host: connectTo ?? target.hostname,
2207
+ port: connectPort ?? (target.port || 443),
2208
+ servername: target.hostname,
2209
+ path: `${target.pathname}${target.search}`,
2210
+ method: "GET",
2211
+ headers: { host: target.host },
2212
+ ...ca ? { ca } : {},
2213
+ ...signal ? { signal } : {},
2214
+ timeout: timeoutMs
2215
+ }, (res) => {
2216
+ res.resume();
2217
+ res.on("end", () => resolve(res.statusCode ?? 0));
2218
+ res.on("error", reject);
2219
+ });
2220
+ req.on("timeout", () => req.destroy(new HealthRequestTimedOut(url, timeoutMs)));
2221
+ req.on("error", reject);
2222
+ req.end();
2223
+ });
2224
+ };
2225
+ /** Whether a status means the app answered as it should. */
2226
+ function isHealthy(status) {
2227
+ return status >= 200 && status < 400;
2228
+ }
2229
+ /** Apps that did not answer through the edge. */
2230
+ var ComposeAppsUnhealthy = class extends require_errors.GkmError {
2231
+ project;
2232
+ apps;
2233
+ proxy;
2234
+ constructor(project, apps, proxy = "caddy") {
2235
+ const names = apps.map((a) => a.app).join(" ");
2236
+ super(`${project} started, but ${apps.length === 1 ? "this app does" : "these apps do"} not answer through ${proxy === "caddy" ? "Caddy" : "the shared Traefik edge"}:\n` + apps.map(({ url, last }) => ` - ${url}: ${last}`).join("\n") + (proxy === "caddy" ? `\nSee why with \`docker compose -p ${project} logs caddy ${names}\`.` : `\nSee why with \`docker compose -p gkm-edge logs traefik\` and \`docker compose -p ${project} logs ${names}\`.`));
2237
+ this.project = project;
2238
+ this.apps = apps;
2239
+ this.proxy = proxy;
2240
+ this.name = "ComposeAppsUnhealthy";
2241
+ }
2242
+ };
2243
+ //#endregion
2244
+ //#region src/target/compose/phases.ts
2245
+ /**
2246
+ * The compose target's phases: a stage as one Docker Compose stack behind
2247
+ * Caddy — the local stage on this machine's Docker, a deployed stage on its
2248
+ * server's, driven over SSH (`compose/server.ts`) from wherever the deploy
2249
+ * runs.
2250
+ *
2251
+ * The order is the point of this file, and the rules it keeps are these:
2252
+ *
2253
+ * - A tag is checked before anything happens (`validate`). Every app's image
2254
+ * is asked of the registry first; one missing image stops the run naming
2255
+ * them all, and nothing is written, pulled or started.
2256
+ * - The databases exist before the apps do (`provision`). Postgres comes up
2257
+ * alone, its databases, roles and grants are created, its migrations
2258
+ * applied and its seeds run from this machine — through an SSH tunnel to a
2259
+ * server's loopback — and only then do the apps
2260
+ * start — so an app
2261
+ * never boots against a schema that is not there yet.
2262
+ * - What a stage runs is recorded (`release`). Each app's image, the tag it
2263
+ * was released under and the digest it resolved to are written to the
2264
+ * stage's state, so "what is this stage running" has an exact answer.
2265
+ * - What was started answers (`verify`): each app is asked through Caddy,
2266
+ * over HTTPS, with the certificate verified.
2267
+ *
2268
+ * Every image is built inside Docker from a pruned slice of the build root —
2269
+ * the same Dockerfiles `gkm docker` writes — so nothing is built on this
2270
+ * machine before `docker compose build`.
2271
+ *
2272
+ * Docker, the registry, Postgres and the health probe are
2273
+ * injected, so the rules are asserted without a daemon; the defaults are the
2274
+ * real ones.
2275
+ */
2276
+ /** `--push` without `--build`: there is nothing built here to push. */
2277
+ var ComposePushNeedsBuild = class extends require_errors.GkmError {
2278
+ constructor() {
2279
+ super("--push pushes the images this checkout builds, so it needs --build (and never --pull): gkm compose --stage <stage> --build --push --tag <tag>.");
2280
+ this.name = "ComposePushNeedsBuild";
2281
+ }
2282
+ };
2283
+ /** Digests to run by, on a run that pulls nothing. */
2284
+ var ComposePinNeedsPull = class extends require_errors.GkmError {
2285
+ constructor() {
2286
+ super("--digests-file pins the images a release pulls, so it needs --tag (or --pull) — or, with --build --push, it is where the pushed digests are written.");
2287
+ this.name = "ComposePinNeedsPull";
2288
+ }
2289
+ };
2290
+ /** A port variable that does not hold a port. */
2291
+ var EdgePortInvalid = class extends require_errors.GkmError {
2292
+ variable;
2293
+ value;
2294
+ constructor(variable, value) {
2295
+ super(`${variable}=${value} is not a port. Set it to a number between 1 and 65535, or unset it for the default.`);
2296
+ this.variable = variable;
2297
+ this.value = value;
2298
+ this.name = "EdgePortInvalid";
2299
+ }
2300
+ };
2301
+ /** A build with no commit to tag its images with. */
2302
+ var NoGitRevision = class extends require_errors.GkmError {
2303
+ root;
2304
+ constructor(root) {
2305
+ super(`Images built here are tagged with the commit they were built from, and ${root} is not in a git repository with a commit. Commit, or pass --tag to name the images yourself.`);
2306
+ this.root = root;
2307
+ this.name = "NoGitRevision";
2308
+ }
2309
+ };
2310
+ /**
2311
+ * The project's own file merged over a stage's stack: `docker-compose.<stage>.yml`
2312
+ * at the workspace root. What the generated file cannot know — a port bound
2313
+ * to a tailnet address, another log driver — goes there, and is never
2314
+ * overwritten.
2315
+ */
2316
+ function stackOverrideFile(root, stage) {
2317
+ return (0, node_path.join)(root, `docker-compose.${stage}.yml`);
2318
+ }
2319
+ /** The commit HEAD is at, short — and `-dirty` when the tree has changes. */
2320
+ async function gitRevision(root) {
2321
+ let sha;
2322
+ try {
2323
+ sha = (await require_run.runOutput("git", [
2324
+ "rev-parse",
2325
+ "--short",
2326
+ "HEAD"
2327
+ ], { cwd: root })).trim();
2328
+ } catch {
2329
+ throw new NoGitRevision(root);
2330
+ }
2331
+ if (!sha) throw new NoGitRevision(root);
2332
+ return (await require_run.runOutput("git", ["status", "--porcelain"], { cwd: root }).catch(() => "")).trim() ? `${sha}-dirty` : sha;
2333
+ }
2334
+ /** The edge's ports, from the environment — 443 and 80 when unset. */
2335
+ function edgePorts(env = process.env) {
2336
+ const read = (variable, fallback) => {
2337
+ const value = env[variable];
2338
+ if (value === void 0 || value === "") return fallback;
2339
+ const port = Number(value);
2340
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new EdgePortInvalid(variable, value);
2341
+ return port;
2342
+ };
2343
+ const logs = env["GKM_COMPOSE_LOGS_PORT"] ? read(LOGS_PORT_ENV, 0) : void 0;
2344
+ return {
2345
+ https: read(EDGE_PORT_ENV.https, 443),
2346
+ http: read(EDGE_PORT_ENV.http, 80),
2347
+ ...logs ? { logs } : {}
2348
+ };
2349
+ }
2350
+ /**
2351
+ * The real ones. Each app is asked for three minutes before it counts as
2352
+ * down: a deployed stage's first ACME certificate can take that long.
2353
+ */
2354
+ const defaultDeps = {
2355
+ docker: dockerCompose,
2356
+ revision: gitRevision,
2357
+ sql: require_seeds.pgClient,
2358
+ buckets: require_seeds.bucketClient,
2359
+ logins: require_seeds.ensurePostgresLogin,
2360
+ migrate: require_databases.migrateDatabases,
2361
+ seed: require_databases.seedDatabases,
2362
+ probe: httpsProbe,
2363
+ healthAttempts: 90,
2364
+ healthIntervalMs: 2e3,
2365
+ env: process.env,
2366
+ lookup: require_dns.systemLookup,
2367
+ server: sshAccess
2368
+ };
2369
+ /**
2370
+ * The stack the stage would run, and — for a tag — the registry's word that
2371
+ * every image it names exists. Nothing is written: a missing image stops the
2372
+ * run here, before a file, a secret or a container is touched.
2373
+ */
2374
+ async function validateCompose(ctx, deps, options = {}) {
2375
+ const { workspace, stage, identity, tag } = ctx;
2376
+ const root = workspace.root;
2377
+ const mode = options.mode ?? (ctx.tagGiven ? "pull" : "build");
2378
+ const push = options.push === true;
2379
+ if (push && mode !== "build") throw new ComposePushNeedsBuild();
2380
+ if (options.pin && mode !== "pull") throw new ComposePinNeedsPull();
2381
+ const registry = workspace.deploy?.registry;
2382
+ if (!registry && (push || mode === "pull")) throw new RegistryRequired(push ? "push" : "pull", require_identity.imageRef(identity, ctx.apps[0] ?? workspace.name, void 0, tag));
2383
+ const runnables = {};
2384
+ const background = {};
2385
+ const subscribers = {};
2386
+ const manifest = await require_discover.discover({
2387
+ patterns: require_seeds.constructGlobs(workspace),
2388
+ cwd: root,
2389
+ runnables,
2390
+ background,
2391
+ subscribers
2392
+ });
2393
+ const { secrets, generated } = await stageSecrets(ctx, manifest);
2394
+ const layout = mode === "build" ? require_seeds.imageLayout(workspace) : void 0;
2395
+ const composed = composeStack({
2396
+ workspace,
2397
+ manifest,
2398
+ runnables,
2399
+ background,
2400
+ subscribers,
2401
+ stage,
2402
+ identity,
2403
+ images: {
2404
+ mode,
2405
+ tag,
2406
+ ...registry ? { registry } : {}
2407
+ },
2408
+ secrets,
2409
+ ...stage === workspace.stages.local ? { localCredentials: (await require_seeds.loadLocalCredentials(workspace)).credentials } : {},
2410
+ allowDevServices: ctx.allowDevServices,
2411
+ ports: edgePorts(deps.env),
2412
+ ...layout ? { layout } : {},
2413
+ ...push ? { buildOnly: true } : {}
2414
+ });
2415
+ if (mode === "build") assertRedisClient(workspace, composed, manifest, runnables);
2416
+ const stack = options.pin ? withPinnedImages(composed, options.pin.file, options.pin.digests) : composed;
2417
+ if (mode === "pull" && !ctx.dryRun) await assertImagesExist(deps.docker, tag, [...stack.apps, ...stack.workers]);
2418
+ if (stack.redis && !stack.local) ctx.secrets.mask(stack.redis.password);
2419
+ if (stack.logs && (!stack.local || stack.logs.passwordFromSecrets)) {
2420
+ ctx.secrets.mask(stack.logs.password);
2421
+ ctx.secrets.mask(stack.logs.appEnv.OTEL_EXPORTER_OTLP_HEADERS);
2422
+ }
2423
+ if (!push && !stack.local) require_dns.requiredServerAddress(stage, workspace.domains?.[stage], secrets?.custom);
2424
+ const server = push ? void 0 : composeServer({
2425
+ stage,
2426
+ local: stack.local,
2427
+ config: workspace.deploy?.compose?.server,
2428
+ custom: secrets?.custom
2429
+ });
2430
+ const dir = (0, node_path.join)(root, stackDir(stage));
2431
+ const override = stackOverrideFile(root, stage);
2432
+ const overrides = (0, node_fs.existsSync)(override) ? [override] : [];
2433
+ if (overrides.length > 0) ctx.logger.info(`🧩 Merging docker-compose.${stage}.yml over the generated stack`);
2434
+ const recorded = push ? void 0 : await ctx.state.read(stage);
2435
+ const previous = Object.fromEntries(Object.entries(recorded?.state.releases ?? {}).map(([name, releases]) => {
2436
+ const { ref, tag, digest } = releases.current;
2437
+ return [name, {
2438
+ ref,
2439
+ ...tag ? { tag } : {},
2440
+ ...digest ? { digest } : {}
2441
+ }];
2442
+ }));
2443
+ return {
2444
+ mode,
2445
+ push,
2446
+ stack,
2447
+ ...server ? { server } : {},
2448
+ dir,
2449
+ ref: {
2450
+ project: composeProject(identity),
2451
+ file: (0, node_path.join)(dir, "docker-compose.yml"),
2452
+ ...overrides.length > 0 ? { overrides } : {},
2453
+ cwd: root,
2454
+ ...server ? { host: dockerHost(server) } : {},
2455
+ output: ctx.childOutput,
2456
+ signal: ctx.signal
2457
+ },
2458
+ secrets,
2459
+ generated,
2460
+ previous,
2461
+ files: [],
2462
+ images: {},
2463
+ changes: []
2464
+ };
2465
+ }
2466
+ /**
2467
+ * What only a compose deploy checks before it changes anything, once the
2468
+ * stage's own keys are: the stage's own certificate is there, and every
2469
+ * public host points at the stage's server — its records written where a
2470
+ * provider holds them, then confirmed — before Caddy or Traefik ask Let's
2471
+ * Encrypt for a certificate it could not issue. A build-only run never asks.
2472
+ */
2473
+ async function readyCompose(ctx, run, deps) {
2474
+ const { stack } = run;
2475
+ if (stack.tls) {
2476
+ for (const file of [stack.tls.certFile, stack.tls.keyFile]) if (!(0, node_fs.existsSync)(file)) throw new require_workspace.ComposeTlsFileMissing(ctx.stage, file);
2477
+ }
2478
+ if (stack.local) return;
2479
+ if (run.server && !ctx.dryRun && !run.push) await deps.server.check(run.server);
2480
+ await stageHostsDns(ctx, deps, require_dns.stackHosts(stack).filter((host) => !require_dns.isLocalHost(host)), run.secrets?.custom);
2481
+ }
2482
+ /** A stack whose caches are in Redis, built for an app that has no client. */
2483
+ var RedisClientMissing = class extends require_errors.GkmError {
2484
+ apps;
2485
+ constructor(apps) {
2486
+ super(`The stack keeps every cache in Redis, and ${apps.join(", ")} ${apps.length === 1 ? "does" : "do"} not depend on ioredis, the client the generated server reaches it with. Add it to the workspace: pnpm add ioredis`);
2487
+ this.apps = apps;
2488
+ this.name = "RedisClientMissing";
2489
+ }
2490
+ };
2491
+ /**
2492
+ * Every backend and worker image whose process reaches a cache can resolve
2493
+ * `ioredis` — listed by its app or a directory above it, the way Node and
2494
+ * the image's install find it. A process that reaches no cache registers no
2495
+ * cache driver, so it needs no client.
2496
+ */
2497
+ function assertRedisClient(workspace, stack, manifest, runnables) {
2498
+ if (!stack.plan.resources.some((r) => r.kind === "cache")) return;
2499
+ const cacheKeys = Object.values(manifest).filter((d) => d.kind === "cache").flatMap((d) => d.provides ?? []);
2500
+ const reachesCache = (keys) => cacheKeys.some((key) => keys?.has(key));
2501
+ const missing = [.../* @__PURE__ */ new Set([...stack.apps.filter((app) => app.kind === "rest-api" && reachesCache(require_seeds.appEnvKeys(manifest, app.name, runnables))).map((a) => a.name), ...stack.workers.filter((worker) => reachesCache(require_seeds.workerEnvKeys(manifest, worker.id, runnables))).map((worker) => worker.host)])].filter((name) => {
2502
+ const app = workspace.apps[name];
2503
+ return app && !require_drivers.installedFrom((0, node_path.join)(workspace.root, app.path), "ioredis");
2504
+ }).sort();
2505
+ if (missing.length > 0) throw new RedisClientMissing(missing);
2506
+ }
2507
+ /**
2508
+ * The deploy's DNS step, for a deployed stage: its server's address — required
2509
+ * where it serves a real domain — then its own hosts written through each
2510
+ * domain's provider in `dns` where they differ, read back from it, and
2511
+ * recorded in the stage's state; hosts no provider writes resolved with the
2512
+ * system resolver. A dry run prints the plan and writes nothing, and warns
2513
+ * rather than stops.
2514
+ *
2515
+ * @throws {ServerAddressMissing} when the stage has a domain and no address
2516
+ * @throws {ServerAddressInvalid} for a value that is not an address
2517
+ * @throws {HostNotPointingAtServer} when a host is not the server
2518
+ */
2519
+ async function stageHostsDns(ctx, deps, hosts, custom) {
2520
+ const { workspace, stage } = ctx;
2521
+ const server = require_dns.requiredServerAddress(stage, workspace.domains?.[stage], custom);
2522
+ if (hosts.length === 0) return;
2523
+ if (ctx.skipDns) {
2524
+ ctx.logger.info("🌐 DNS skipped (--skip-dns): no record is written or checked");
2525
+ return;
2526
+ }
2527
+ if (!server) {
2528
+ ctx.logger.info(`🌐 DNS skipped: no ${require_workspace.SERVER_IPV4_KEY} in '${stage}''s secrets — ${require_dns.serverAddressHint(stage)}`);
2529
+ return;
2530
+ }
2531
+ const log = (line) => ctx.logger.info(line);
2532
+ if (workspace.dns) log(`🌐 DNS for '${stage}' → ${server.ipv4}${server.ipv6 ? ` / ${server.ipv6}` : ""}${ctx.dryRun ? " (dry run — nothing is written)" : ""}`);
2533
+ try {
2534
+ const result = await require_dns.stageDns({
2535
+ stage,
2536
+ hosts,
2537
+ server,
2538
+ dns: workspace.dns,
2539
+ credentials: ctx.credentials,
2540
+ log,
2541
+ lookup: deps.lookup ?? require_dns.systemLookup,
2542
+ ...ctx.dryRun ? { dryRun: true } : {},
2543
+ ...deps.dnsProviderFor ? { providerFor: deps.dnsProviderFor } : {}
2544
+ });
2545
+ if (!ctx.dryRun) {
2546
+ const journal = await require_seeds.DeployJournal.open(ctx.state, stage, () => require_seeds.createComposeState(stage));
2547
+ const providers = new Map(result.plan.domains.map((d) => [d.domain, d.provider]));
2548
+ await require_seeds.recordDnsChanges(journal, result.changes, (domain) => providers.get(domain));
2549
+ }
2550
+ const parts = [result.confirmed.length > 0 ? `${result.confirmed.length} host${result.confirmed.length === 1 ? "" : "s"} confirmed by the DNS provider` : "", result.resolved.length > 0 ? `${result.resolved.length} host${result.resolved.length === 1 ? "" : "s"} resolve${result.resolved.length === 1 ? "s" : ""} to ${server.ipv4}` : ""].filter(Boolean);
2551
+ if (parts.length > 0) log(`🌐 ${parts.join(", ")}`);
2552
+ } catch (error) {
2553
+ if (ctx.dryRun && error instanceof require_dns.HostNotPointingAtServer) {
2554
+ ctx.logger.warn(`⚠ ${error.message}`);
2555
+ return;
2556
+ }
2557
+ throw error;
2558
+ }
2559
+ }
2560
+ /**
2561
+ * The stage's secrets — and, for a deployed stage, everything it generates
2562
+ * once: its seed, and each declared secret and keyring. Kept only once the
2563
+ * run goes ahead (`provision`), so a dry run, or a tag that is not there,
2564
+ * leaves the stage's secrets as it found them.
2565
+ */
2566
+ async function stageSecrets(ctx, manifest) {
2567
+ const stored = await ctx.secrets.read();
2568
+ if (ctx.stage === ctx.workspace.stages.local) return {
2569
+ secrets: stored,
2570
+ generated: []
2571
+ };
2572
+ const { secrets, generated } = deployedStackSecrets(ctx.workspace, ctx.stage, stored, manifest);
2573
+ if (secrets.seed) ctx.secrets.mask(secrets.seed);
2574
+ for (const [key, value] of Object.entries(secrets.custom ?? {})) if (!require_workspace.isReservedStageKey(key)) ctx.secrets.mask(value);
2575
+ return {
2576
+ secrets,
2577
+ generated
2578
+ };
2579
+ }
2580
+ /**
2581
+ * The stack with each app's and worker's image pinned to the digest the file
2582
+ * names: the refs it is checked, pulled and recorded by, and the image each
2583
+ * compose service runs.
2584
+ */
2585
+ function withPinnedImages(stack, file, digests) {
2586
+ const apps = pinImages(file, digests, stack.apps);
2587
+ const workers = pinImages(file, digests, stack.workers);
2588
+ const services = { ...stack.compose.services };
2589
+ for (const image of [...apps, ...workers]) services[image.name] = {
2590
+ ...services[image.name],
2591
+ image: image.ref
2592
+ };
2593
+ return {
2594
+ ...stack,
2595
+ apps,
2596
+ workers,
2597
+ compose: {
2598
+ ...stack.compose,
2599
+ services
2600
+ }
2601
+ };
2602
+ }
2603
+ /**
2604
+ * A dry run: the stack's files, written so they can be read, and what a real
2605
+ * run would do — no image is built or pulled, no container touched, nothing
2606
+ * recorded and no secret kept.
2607
+ */
2608
+ async function planCompose(ctx, run) {
2609
+ if (run.generated.length > 0) ctx.logger.info(`🔑 "${ctx.stage}" has no ${run.generated.join(", ")} yet; this dry run used values it did not keep.`);
2610
+ run.files = await writeStack(ctx.cwd, run.dir, run.stack);
2611
+ printPlan(ctx, run);
2612
+ if (!run.push && run.stack.infra.includes("postgres")) require_seeds.reportPlannedSeeds((await require_databases.plannedSeeds({
2613
+ root: ctx.workspace.root,
2614
+ manifest: ctx.manifest
2615
+ })).map(({ target, seeds }) => ({
2616
+ folder: target.seeds,
2617
+ seeds
2618
+ })), (line) => ctx.logger.info(line));
2619
+ const planned = (change) => {
2620
+ run.changes.push(change);
2621
+ ctx.emit({
2622
+ type: "resource.planned",
2623
+ ...change
2624
+ });
2625
+ };
2626
+ for (const service of run.push ? [] : run.stack.infra) planned({
2627
+ key: `service:${service}`,
2628
+ resourceType: "service",
2629
+ action: "ensure"
2630
+ });
2631
+ for (const app of run.stack.apps) {
2632
+ planned({
2633
+ key: `image:${app.name}`,
2634
+ resourceType: "image",
2635
+ action: run.mode === "build" ? "build" : "reuse",
2636
+ id: app.ref
2637
+ });
2638
+ if (run.push) continue;
2639
+ planned({
2640
+ key: `service:${app.name}`,
2641
+ resourceType: "service",
2642
+ action: run.previous[app.name] ? "reuse" : "create"
2643
+ });
2644
+ }
2645
+ for (const worker of run.stack.workers) {
2646
+ planned({
2647
+ key: `image:${worker.name}`,
2648
+ resourceType: "image",
2649
+ action: run.mode === "build" ? "build" : "reuse",
2650
+ id: worker.ref
2651
+ });
2652
+ if (run.push) continue;
2653
+ planned({
2654
+ key: `service:${worker.name}`,
2655
+ resourceType: "service",
2656
+ action: run.previous[worker.name] ? "reuse" : "create"
2657
+ });
2658
+ }
2659
+ if (!run.push) planned({
2660
+ key: run.stack.proxy === "caddy" ? "service:caddy" : `service:${EDGE_PROJECT}/${EDGE_SERVICE}`,
2661
+ resourceType: "service",
2662
+ action: "ensure"
2663
+ });
2664
+ }
2665
+ /**
2666
+ * What the apps run on: the stage's generated secrets kept, the stack's files
2667
+ * written, its infrastructure started, and its databases, roles, grants and
2668
+ * migrations applied.
2669
+ */
2670
+ async function provisionCompose(ctx, run, deps) {
2671
+ const { stack, ref } = run;
2672
+ if (run.push) return;
2673
+ if (run.generated.length > 0 && run.secrets) {
2674
+ await ctx.secrets.write(run.secrets);
2675
+ ctx.logger.info(`🔑 Generated for "${ctx.stage}" (${ctx.secrets.store}): ${run.generated.join(", ")}`);
2676
+ }
2677
+ const ports = edgePorts(deps.env);
2678
+ await assertEdgePorts(deps.docker, {
2679
+ project: stack.project,
2680
+ proxy: stack.proxy,
2681
+ ports,
2682
+ engine: ref
2683
+ });
2684
+ run.files = await writeStack(ctx.cwd, run.dir, stack);
2685
+ printPlan(ctx, run);
2686
+ if (stack.proxy === "traefik") {
2687
+ ctx.logger.info(`\n🌐 Starting the shared edge (${EDGE_PROJECT})…`);
2688
+ run.files.push(...await ensureEdge(deps.docker, {
2689
+ file: edgeFile(run),
2690
+ ports,
2691
+ ...ref.host ? { host: ref.host } : {},
2692
+ logging: {
2693
+ driver: LOG_ROTATION.driver,
2694
+ options: { ...LOG_ROTATION.options }
2695
+ },
2696
+ ...ref.output ? { output: ref.output } : {},
2697
+ ...ref.signal ? { signal: ref.signal } : {}
2698
+ }));
2699
+ applied(ctx, run, {
2700
+ key: `service:${EDGE_PROJECT}/${EDGE_SERVICE}`,
2701
+ resourceType: "service",
2702
+ action: "ensure",
2703
+ id: `${EDGE_PROJECT}/${EDGE_SERVICE}`,
2704
+ via: "found"
2705
+ });
2706
+ }
2707
+ if (stack.infra.length > 0) {
2708
+ ctx.logger.info(`\n🗄️ Starting ${stack.infra.join(", ")}…`);
2709
+ await deps.docker.up(ref, stack.infra);
2710
+ for (const service of stack.infra) applied(ctx, run, {
2711
+ key: `service:${service}`,
2712
+ resourceType: "service",
2713
+ action: "ensure",
2714
+ id: `${stack.project}/${service}`,
2715
+ via: Object.keys(run.previous).length > 0 ? "recorded" : "created"
2716
+ });
2717
+ }
2718
+ if (stack.infra.includes("postgres")) await prepareDatabases(ctx, run, deps);
2719
+ if (stack.storage && stack.infra.includes("minio")) await prepareBuckets(ctx, run, deps);
2720
+ }
2721
+ /**
2722
+ * Create each bucket the stack's MinIO serves, and the open paths of each
2723
+ * one a file server fronts — from this machine, on MinIO's loopback port,
2724
+ * the way `gkm dev` creates them.
2725
+ */
2726
+ async function prepareBuckets(ctx, run, deps) {
2727
+ const storage = run.stack.storage;
2728
+ if (storage.buckets.length === 0) return;
2729
+ const tunnel = await reach(run, deps, "minio", 9e3);
2730
+ let done;
2731
+ try {
2732
+ const client = deps.buckets(tunnel.port, storage);
2733
+ done = [
2734
+ ...await require_seeds.applyBuckets(client, storage.buckets),
2735
+ ...await require_seeds.applyPolicies(client, storage.policies),
2736
+ ...await require_seeds.applyLifecycles(client, storage.lifecycles)
2737
+ ];
2738
+ } finally {
2739
+ await tunnel.close();
2740
+ }
2741
+ const created = done.filter((entry) => entry.created).length;
2742
+ ctx.logger.info(`🪣 Buckets: ${storage.buckets.join(", ")}${created ? ` (${created} change(s))` : ""}`);
2743
+ }
2744
+ /**
2745
+ * Create what the plan names in the stack's Postgres, migrate it, then run
2746
+ * its seeds — every one, every time, as each construct's owner.
2747
+ */
2748
+ async function prepareDatabases(ctx, run, deps) {
2749
+ const tunnel = await reach(run, deps, "postgres", 5432);
2750
+ try {
2751
+ await migrateAndSeed(ctx, run, deps, tunnel.port);
2752
+ } finally {
2753
+ await tunnel.close();
2754
+ }
2755
+ }
2756
+ /**
2757
+ * A port a stack's service publishes on its engine's loopback, reachable
2758
+ * from this machine: the published port itself on the local stage, and on a
2759
+ * server an SSH tunnel to it over the deploy's own login — so Postgres and
2760
+ * MinIO are never published beyond the server's loopback.
2761
+ *
2762
+ * Migrations and seeds run here, in this process, with the project's own
2763
+ * Kysely — the way `gkm migrate` runs them — through that port. No image
2764
+ * needs to carry them, and nothing is run on the server but the stack.
2765
+ */
2766
+ async function reach(run, deps, service, inside) {
2767
+ const published = await deps.docker.port(run.ref, service, inside);
2768
+ if (!run.server) return {
2769
+ port: published,
2770
+ close: async () => {}
2771
+ };
2772
+ return deps.server.tunnel(run.server, published);
2773
+ }
2774
+ async function migrateAndSeed(ctx, run, deps, port) {
2775
+ const { stack } = run;
2776
+ const workspace = ctx.workspace;
2777
+ const superuser = stack.credential.containers.postgres;
2778
+ const login = await deps.logins({
2779
+ port,
2780
+ login: superuser,
2781
+ legacy: [{
2782
+ user: require_seeds.LEGACY_LOCAL_LOGIN.user,
2783
+ password: superuser.password
2784
+ }, ...stack.local ? [require_seeds.LEGACY_LOCAL_LOGIN] : []]
2785
+ });
2786
+ if (login.notice) ctx.logger.info(login.notice);
2787
+ const statements = [...require_seeds.postgresStatements(stack.plan, workspace.name, stack.credential.seed), ...stack.backups ? require_dns.backupsRoleStatements(stack.backups) : []];
2788
+ if (statements.length > 0) {
2789
+ ctx.logger.info("🗄️ Creating databases, roles and grants…");
2790
+ await require_seeds.applyPostgres(deps.sql(port, login.login), statements);
2791
+ }
2792
+ const sources = {};
2793
+ const manifest = await require_discover.discover({
2794
+ patterns: require_seeds.constructGlobs(workspace),
2795
+ cwd: workspace.root,
2796
+ sources
2797
+ });
2798
+ const env = require_seeds.envFor(stack.plan, {
2799
+ ports: { [require_seeds.primaryPortKey("postgres")]: port },
2800
+ project: workspace.name,
2801
+ credentials: {
2802
+ ...stack.credential.containers,
2803
+ postgres: login.login
2804
+ },
2805
+ seed: stack.credential.seed
2806
+ });
2807
+ const options = {
2808
+ root: workspace.root,
2809
+ manifest,
2810
+ sources,
2811
+ env
2812
+ };
2813
+ const runs = await deps.migrate(options);
2814
+ const seeds = await deps.seed({
2815
+ ...options,
2816
+ stage: ctx.stage
2817
+ }).catch((error) => {
2818
+ if (error instanceof require_databases.SeedFailed) throw new require_seeds.DeploySeedsFailed(ctx.stage, error.construct, error.seed, error);
2819
+ throw error;
2820
+ });
2821
+ require_seeds.reportDatabaseRuns(runs.map(({ target, applied }) => ({
2822
+ construct: target.id,
2823
+ folder: target.migrations,
2824
+ applied
2825
+ })), seeds.map(({ target, seeded }) => ({
2826
+ construct: target.id,
2827
+ folder: target.seeds,
2828
+ seeded
2829
+ })), (line) => ctx.logger.info(line), (event) => ctx.emit(event));
2830
+ }
2831
+ /**
2832
+ * Each app's image: built from this checkout — inside Docker, every one of
2833
+ * them, from a pruned slice of the build root — or pulled at the tag. Nothing
2834
+ * live changes.
2835
+ */
2836
+ async function buildCompose(ctx, run, deps) {
2837
+ const { stack, ref } = run;
2838
+ const images = [...stack.apps, ...stack.workers];
2839
+ const apps = images.map((image) => image.app);
2840
+ if (run.push) {
2841
+ run.files = await writeStack(ctx.cwd, run.dir, stack);
2842
+ printPlan(ctx, run);
2843
+ }
2844
+ if (run.mode === "build") {
2845
+ ctx.logger.info("\n🐳 Building images…");
2846
+ await deps.docker.build(ref, apps);
2847
+ } else {
2848
+ ctx.logger.info(`\n🐳 Pulling ${ctx.tag}…`);
2849
+ await deps.docker.pull(ref, apps);
2850
+ }
2851
+ if (run.push) ctx.logger.info("\n📤 Pushing images…");
2852
+ for (const image of images) {
2853
+ const digest = run.push ? await deps.docker.push(ref, image.ref) : await deps.docker.digest(ref, image.ref);
2854
+ if (run.push) ctx.logger.info(` ${image.app.padEnd(12)} ${image.ref}@${digest}`);
2855
+ run.images[image.app] = {
2856
+ ref: image.ref,
2857
+ tag: image.tag,
2858
+ ...digest ? { digest } : {}
2859
+ };
2860
+ ctx.emit({
2861
+ type: "artifact.built",
2862
+ app: image.app,
2863
+ imageRef: image.ref,
2864
+ ...digest ? { digest } : {}
2865
+ });
2866
+ run.changes.push({
2867
+ key: `image:${image.app}`,
2868
+ resourceType: "image",
2869
+ action: run.mode === "build" ? "build" : "reuse",
2870
+ id: digest ?? image.ref
2871
+ });
2872
+ }
2873
+ }
2874
+ /**
2875
+ * The whole stack up — anything it no longer defines removed — and each
2876
+ * app's image recorded in the stage's state.
2877
+ */
2878
+ async function releaseCompose(ctx, run, deps) {
2879
+ const { stack, ref } = run;
2880
+ if (run.push) return;
2881
+ ctx.logger.info("\n🚀 Starting the stack…");
2882
+ await deps.docker.up(ref);
2883
+ const edge = edgeRef(edgeFile(run), {
2884
+ ...ref.host ? { host: ref.host } : {},
2885
+ ...ref.output ? { output: ref.output } : {}
2886
+ });
2887
+ if (stack.proxy === "traefik" && stack.traefik) {
2888
+ const written = await writeEdgeRoutes(deps.docker, edge, stack.project, stack.traefik, stack.tls);
2889
+ ctx.logger.info(`🌐 Registered ${stack.routes.length} route(s) with ${EDGE_PROJECT}: ${written.at(-1)}`);
2890
+ } else if (!stack.local) await removeEdgeRoutes(deps.docker, edge, stack.project);
2891
+ if (stack.local) await deps.docker.copyOut(ref, "caddy", "/data/caddy/pki/authorities/local/root.crt", caFile(run)).catch(() => {});
2892
+ await recordImages(ctx, run);
2893
+ for (const app of stack.apps) {
2894
+ const image = run.images[app.name];
2895
+ applied(ctx, run, {
2896
+ key: `service:${app.name}`,
2897
+ resourceType: "service",
2898
+ action: "ensure",
2899
+ id: `${stack.project}/${app.name}`,
2900
+ via: run.previous[app.name] ? "recorded" : "created"
2901
+ });
2902
+ ctx.emit({
2903
+ type: "app.deployed",
2904
+ app: app.name,
2905
+ applicationId: `${stack.project}/${app.name}`,
2906
+ imageRef: image?.ref ?? app.ref,
2907
+ url: app.url
2908
+ });
2909
+ }
2910
+ for (const worker of stack.workers) applied(ctx, run, {
2911
+ key: `service:${worker.name}`,
2912
+ resourceType: "service",
2913
+ action: "ensure",
2914
+ id: `${stack.project}/${worker.name}`,
2915
+ via: run.previous[worker.name] ? "recorded" : "created"
2916
+ });
2917
+ ctx.logger.info(`\n✅ ${stack.project} is running:`);
2918
+ for (const app of stack.apps) ctx.logger.info(` ${app.name.padEnd(12)} ${app.url}`);
2919
+ for (const worker of stack.workers) ctx.logger.info(` ${worker.name.padEnd(12)} worker ${worker.id}`);
2920
+ if (stack.local) ctx.logger.info(`\n🔐 Certificates are from Caddy's local CA: NODE_EXTRA_CA_CERTS=${(0, node_path.join)(stackDir(ctx.stage), "caddy-root.crt")}`);
2921
+ }
2922
+ /**
2923
+ * Each app's image in the stage's state — the same `images` a Dokploy deploy
2924
+ * records: the ref it runs, the tag it was released under, and the digest
2925
+ * the tag resolved to.
2926
+ */
2927
+ async function recordImages(ctx, run) {
2928
+ const journal = await require_seeds.DeployJournal.open(ctx.state, ctx.stage, () => require_seeds.createComposeState(ctx.stage));
2929
+ const by = require_seeds.currentActor();
2930
+ for (const app of [...run.stack.apps, ...run.stack.workers]) {
2931
+ const image = run.images[app.app] ?? {
2932
+ ref: app.ref,
2933
+ tag: app.tag
2934
+ };
2935
+ require_seeds.recordRelease(journal.state, app.app, image, by);
2936
+ }
2937
+ journal.state.identity = ctx.identity.key;
2938
+ await journal.save();
2939
+ }
2940
+ /**
2941
+ * Each app asked through Caddy, over HTTPS, the certificate verified: an API
2942
+ * at `/health`, a site at `/`. On the local stage the edge is this machine and
2943
+ * its certificates are Caddy's internal CA's, so the CA `release` copied out
2944
+ * is the one trusted and the request goes to 127.0.0.1.
2945
+ */
2946
+ async function verifyCompose(ctx, run, deps) {
2947
+ const { stack } = run;
2948
+ if (run.push) return;
2949
+ const ca = stack.local ? await (0, node_fs_promises.readFile)(caFile(run), "utf-8").catch(() => void 0) : void 0;
2950
+ ctx.logger.info(`\n🩺 Checking each app through ${stack.proxy === "caddy" ? "Caddy" : "the shared edge"}…`);
2951
+ const edge = stack.local ? { connectTo: "127.0.0.1" } : run.server ? {
2952
+ connectTo: run.server.host,
2953
+ connectPort: edgePorts(deps.env).https
2954
+ } : void 0;
2955
+ const results = await Promise.all(stack.apps.map((app) => checkApp(ctx, app, deps, {
2956
+ ca,
2957
+ edge
2958
+ })));
2959
+ results.push(...await Promise.all(stack.workers.map((worker) => checkContainer(ctx, run, deps, worker.name))));
2960
+ if (stack.logs) results.push(await checkContainer(ctx, run, deps, require_seeds.LOGS_SERVICE));
2961
+ const down = results.filter((result) => !result.healthy).map(({ app, url, last }) => ({
2962
+ app,
2963
+ url,
2964
+ last
2965
+ }));
2966
+ if (down.length > 0) throw new ComposeAppsUnhealthy(stack.project, down, stack.proxy);
2967
+ if (stack.logs) reportLogs(ctx, stack.logs, stack.local, run.server);
2968
+ }
2969
+ /**
2970
+ * A container's own health check, as Docker reports it: a worker's, which
2971
+ * has no route through the edge, and OpenObserve's — by default it has no
2972
+ * host there, and served publicly it answers only the addresses it allows,
2973
+ * which need not include this one.
2974
+ */
2975
+ async function checkContainer(ctx, run, deps, service) {
2976
+ const url = `docker:${service}`;
2977
+ let last = "not asked";
2978
+ for (let attempt = 1; attempt <= deps.healthAttempts; attempt++) {
2979
+ ctx.signal.throwIfAborted();
2980
+ const health = await deps.docker.health(run.ref, service).catch((error) => error instanceof Error ? error.message : String(error));
2981
+ last = health ?? "not running";
2982
+ const healthy = health === "healthy";
2983
+ ctx.emit({
2984
+ type: "health.checked",
2985
+ app: service,
2986
+ url,
2987
+ healthy,
2988
+ attempt
2989
+ });
2990
+ if (healthy) {
2991
+ ctx.logger.info(` ✓ ${service.padEnd(12)} ${url} (${last})`);
2992
+ return {
2993
+ app: service,
2994
+ url,
2995
+ last,
2996
+ healthy
2997
+ };
2998
+ }
2999
+ if (attempt < deps.healthAttempts) await new Promise((resolve) => setTimeout(resolve, deps.healthIntervalMs));
3000
+ }
3001
+ ctx.logger.warn(` ✗ ${service.padEnd(12)} ${url} (${last})`);
3002
+ return {
3003
+ app: service,
3004
+ url,
3005
+ last,
3006
+ healthy: false
3007
+ };
3008
+ }
3009
+ /** How to open the logs, printed — and as an event, for a headless caller. */
3010
+ function reportLogs(ctx, logs, local, server) {
3011
+ const lines = require_seeds.logsAccess(logs, {
3012
+ stage: ctx.stage,
3013
+ local,
3014
+ user: server?.user ?? currentUser(),
3015
+ hostname: server?.host ?? (0, node_os.hostname)(),
3016
+ ...server ? { known: true } : {}
3017
+ });
3018
+ ctx.logger.info(`\n${lines.join("\n")}`);
3019
+ ctx.emit({
3020
+ type: "logs.ready",
3021
+ service: require_seeds.LOGS_SERVICE,
3022
+ access: logs.public ? "public" : "tunnel",
3023
+ url: logs.url,
3024
+ ...logs.public ? { allow: [...logs.public.allow] } : { port: logs.port },
3025
+ email: logs.email
3026
+ });
3027
+ }
3028
+ /** This machine's user — a guess at who would SSH in. */
3029
+ function currentUser() {
3030
+ try {
3031
+ return (0, node_os.userInfo)().username;
3032
+ } catch {
3033
+ return process.env.USER ?? "user";
3034
+ }
3035
+ }
3036
+ async function checkApp(ctx, app, deps, options) {
3037
+ const url = `${app.url}${app.kind === "site" ? "/" : "/health"}`;
3038
+ let last = "not asked";
3039
+ for (let attempt = 1; attempt <= deps.healthAttempts; attempt++) {
3040
+ ctx.signal.throwIfAborted();
3041
+ let status;
3042
+ try {
3043
+ status = await deps.probe({
3044
+ url,
3045
+ ...options.ca ? { ca: options.ca } : {},
3046
+ ...options.edge,
3047
+ timeoutMs: 1e4,
3048
+ signal: ctx.signal
3049
+ });
3050
+ last = `HTTP ${status}`;
3051
+ } catch (error) {
3052
+ last = error instanceof Error ? error.message : String(error);
3053
+ }
3054
+ const healthy = status !== void 0 && isHealthy(status);
3055
+ ctx.emit({
3056
+ type: "health.checked",
3057
+ app: app.name,
3058
+ url,
3059
+ healthy,
3060
+ ...status !== void 0 ? { status } : {},
3061
+ attempt
3062
+ });
3063
+ if (healthy) {
3064
+ ctx.logger.info(` ✓ ${app.name.padEnd(12)} ${url} (${last})`);
3065
+ return {
3066
+ app: app.name,
3067
+ url,
3068
+ last,
3069
+ healthy
3070
+ };
3071
+ }
3072
+ if (attempt < deps.healthAttempts) await new Promise((resolve) => setTimeout(resolve, deps.healthIntervalMs));
3073
+ }
3074
+ ctx.logger.warn(` ✗ ${app.name.padEnd(12)} ${url} (${last})`);
3075
+ return {
3076
+ app: app.name,
3077
+ url,
3078
+ last,
3079
+ healthy: false
3080
+ };
3081
+ }
3082
+ function composeResult(ctx, run) {
3083
+ const apps = [...run.stack.apps.map((app) => {
3084
+ const image = run.images[app.name];
3085
+ return {
3086
+ appName: app.name,
3087
+ type: app.kind === "site" ? "web" : "backend",
3088
+ success: !ctx.dryRun,
3089
+ applicationId: `${run.stack.project}/${app.name}`,
3090
+ imageRef: image?.ref ?? app.ref,
3091
+ ...image?.digest ? { digest: image.digest } : {},
3092
+ url: app.url
3093
+ };
3094
+ }), ...run.stack.workers.map((worker) => {
3095
+ const image = run.images[worker.name];
3096
+ return {
3097
+ appName: worker.name,
3098
+ type: "backend",
3099
+ success: !ctx.dryRun,
3100
+ applicationId: `${run.stack.project}/${worker.name}`,
3101
+ imageRef: image?.ref ?? worker.ref,
3102
+ ...image?.digest ? { digest: image.digest } : {}
3103
+ };
3104
+ })];
3105
+ return {
3106
+ apps,
3107
+ projectId: run.stack.project,
3108
+ successCount: ctx.dryRun ? 0 : apps.length,
3109
+ failedCount: 0,
3110
+ stage: ctx.stage,
3111
+ identity: ctx.identity.key,
3112
+ tag: ctx.tag,
3113
+ dryRun: ctx.dryRun,
3114
+ environmentId: "",
3115
+ skipped: [...ctx.skipped],
3116
+ urls: Object.fromEntries(run.stack.apps.map((app) => [app.name, app.url])),
3117
+ changes: run.changes
3118
+ };
3119
+ }
3120
+ function caFile(run) {
3121
+ return (0, node_path.join)(run.dir, "caddy-root.crt");
3122
+ }
3123
+ /** The shared edge's compose file, as this machine hands it to compose. */
3124
+ function edgeFile(run) {
3125
+ return (0, node_path.join)(run.dir, `${EDGE_PROJECT}.yml`);
3126
+ }
3127
+ function applied(ctx, run, change) {
3128
+ const { via, ...rest } = change;
3129
+ run.changes.push(rest);
3130
+ ctx.emit({
3131
+ type: "resource.applied",
3132
+ ...rest,
3133
+ via
3134
+ });
3135
+ }
3136
+ /** Write the stack's files: owner-only, since the env files hold secrets. */
3137
+ async function writeStack(root, dir, stack) {
3138
+ await (0, node_fs_promises.mkdir)(dir, {
3139
+ recursive: true,
3140
+ mode: 448
3141
+ });
3142
+ await (0, node_fs_promises.chmod)(dir, 448);
3143
+ const files = [];
3144
+ const write = async (path, content, mode = 420) => {
3145
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
3146
+ await (0, node_fs_promises.writeFile)(path, content, { mode });
3147
+ await (0, node_fs_promises.chmod)(path, mode);
3148
+ files.push(path);
3149
+ };
3150
+ const tls = stack.proxy === "caddy" && stack.tls ? {
3151
+ cert: await (0, node_fs_promises.readFile)(stack.tls.certFile, "utf-8"),
3152
+ key: await (0, node_fs_promises.readFile)(stack.tls.keyFile, "utf-8")
3153
+ } : void 0;
3154
+ const compose = stack.caddyfile !== void 0 ? withCaddyConfigs(stack.compose, {
3155
+ caddyfile: stack.caddyfile,
3156
+ ...tls ? { tls } : {}
3157
+ }) : stack.compose;
3158
+ await write((0, node_path.join)(dir, "docker-compose.yml"), composeYaml(stack, compose), tls ? 384 : 420);
3159
+ if (stack.caddyfile !== void 0) {
3160
+ await write((0, node_path.join)(dir, "Caddyfile"), stack.caddyfile);
3161
+ await (0, node_fs_promises.rm)((0, node_path.join)(dir, "traefik.yml"), { force: true });
3162
+ } else await (0, node_fs_promises.rm)((0, node_path.join)(dir, "Caddyfile"), { force: true });
3163
+ if (stack.traefik !== void 0) await write((0, node_path.join)(dir, "traefik.yml"), stack.traefik);
3164
+ await (0, node_fs_promises.rm)((0, node_path.join)(dir, "tls"), {
3165
+ recursive: true,
3166
+ force: true
3167
+ });
3168
+ for (const app of [...stack.apps, ...stack.workers]) if (app.env) await write((0, node_path.join)(dir, `${app.name}.env`), envFile(app.env), 384);
3169
+ if (stack.logs && stack.compose.services["openobserve"]?.env_file) await write((0, node_path.join)(dir, `${require_seeds.LOGS_SERVICE}.env`), envFile(stack.logs.env), 384);
3170
+ if (stack.redis && stack.compose.services["redis"]?.env_file) await write((0, node_path.join)(dir, `${require_seeds.REDIS_SERVICE}.env`), envFile(stack.redis.env), 384);
3171
+ if (stack.backups) {
3172
+ for (const [name, content] of Object.entries(stack.backups.files)) await write((0, node_path.join)(dir, require_dns.BACKUPS_SERVICE, name), content);
3173
+ if (stack.compose.services["backups"]?.env_file) await write((0, node_path.join)(dir, `${require_dns.BACKUPS_SERVICE}.env`), envFile(stack.backups.env), 384);
3174
+ } else {
3175
+ await (0, node_fs_promises.rm)((0, node_path.join)(dir, require_dns.BACKUPS_SERVICE), {
3176
+ recursive: true,
3177
+ force: true
3178
+ });
3179
+ await (0, node_fs_promises.rm)((0, node_path.join)(dir, `${require_dns.BACKUPS_SERVICE}.env`), { force: true });
3180
+ }
3181
+ for (const entry of await (0, node_fs_promises.readdir)(dir)) if (entry.endsWith(".credentials")) await (0, node_fs_promises.rm)((0, node_path.join)(dir, entry));
3182
+ for (const [path, content] of Object.entries(stack.dockerfiles)) await write((0, node_path.join)(root, path), content);
3183
+ await require_seeds.ensureDockerignore(require_seeds.findBuildRoot(root));
3184
+ return files;
3185
+ }
3186
+ function composeYaml(stack, compose) {
3187
+ const stage = ` --stage ${stack.stage}`;
3188
+ return `# Generated by gkm compose from the construct manifest — do not edit.
3189
+ # The ${stack.stage} stage's APIs and sites behind ${stack.proxy === "caddy" ? "one Caddy" : `the shared Traefik edge (${EDGE_PROJECT})`}, and its workers.
3190
+ # Each backend and worker reads exactly the keys in its own env file beside
3191
+ # this one.
3192
+ #
3193
+ # gkm compose${stage} start or update it
3194
+ # gkm compose${stage} --down stop it
3195
+ ${(0, yaml.stringify)(compose, {
3196
+ lineWidth: 0,
3197
+ aliasDuplicateObjects: false
3198
+ })}`;
3199
+ }
3200
+ function printPlan(ctx, run) {
3201
+ const { stack, mode, files } = run;
3202
+ ctx.logger.info(`\n🧱 ${stack.project} — stage ${stack.stage}`);
3203
+ if (run.server) ctx.logger.info(` → ${sshTarget(run.server)} (docker over ssh)`);
3204
+ for (const app of stack.apps) ctx.logger.info(` ${app.name.padEnd(12)} ${app.url.padEnd(40)} ${mode} ${app.ref}`);
3205
+ for (const worker of stack.workers) ctx.logger.info(` ${worker.name.padEnd(12)} ${`worker ${worker.id} (no route)`.padEnd(40)} ${mode} ${worker.ref}`);
3206
+ if (stack.infra.length > 0) ctx.logger.info(` infrastructure: ${stack.infra.join(", ")}`);
3207
+ if (stack.backups) ctx.logger.info(` ${require_dns.BACKUPS_SERVICE.padEnd(12)} ${stack.backups.databases.map((d) => d.file).join(", ")} — ${stack.backups.backups.describe}, to ${stack.backups.prefix}/`);
3208
+ ctx.logger.info(`\n📝 Wrote ${files.length} file(s) under ${stackDir(stack.stage)}/`);
3209
+ }
3210
+ //#endregion
3211
+ //#region src/target/compose/index.ts
3212
+ /**
3213
+ * The built-in `compose` target: a stage as one Docker Compose stack behind
3214
+ * Caddy — the local stage on the machine that runs the deploy, a deployed
3215
+ * stage on its server's Docker engine, over SSH (`deploy.compose.server`).
3216
+ *
3217
+ * ```bash
3218
+ * gkm deploy --target compose --stage production --tag v1.4.0 # what CI pushed
3219
+ * gkm deploy --target compose --stage production # built from here
3220
+ * gkm deploy --target compose --stage development # the local stage
3221
+ * ```
3222
+ *
3223
+ * - `validate`: the stack (`composeStack`, pure) and, for a tag, every image
3224
+ * looked up in the registry — `ImageTagNotFound` before anything changes
3225
+ * - `ready`: a deploy's own checks, after the stage's keys are — the stage's
3226
+ * certificate, its server's address, and every public host's DNS
3227
+ * - `plan`: the stack's files and what a run would create or reuse
3228
+ * - `provision`: generated secrets kept, files written, infrastructure up,
3229
+ * databases, roles, grants and migrations
3230
+ * - `build`: each backend bundled on the host and every image built — or, for
3231
+ * a tag, pulled
3232
+ * - `release`: `up --wait --remove-orphans`, and each app's image, tag and
3233
+ * digest in the stage's state
3234
+ * - `verify`: each app asked through Caddy over HTTPS
3235
+ *
3236
+ * With `push` (`gkm compose --build --push`), run with `buildOnly`, it builds
3237
+ * every image and pushes it to `deploy.registry`, and does nothing else: no
3238
+ * readiness check runs, so a stage whose keys the deploy has yet to write
3239
+ * still builds.
3240
+ *
3241
+ * `gkm compose` is this target with a few more switches (`--build`, `--pull`,
3242
+ * `--down`), run through the same `deploy()`.
3243
+ */
3244
+ var compose_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ composeTarget: () => composeTarget });
3245
+ /** The compose target, with its Docker, bundler and probe as given. */
3246
+ function composeTarget(options = {}) {
3247
+ const { mode, push, pin, report, ...given } = options;
3248
+ const deps = {
3249
+ ...defaultDeps,
3250
+ ...given
3251
+ };
3252
+ return require_define.defineTarget({
3253
+ name: "compose",
3254
+ runtime: "server",
3255
+ capabilities: {
3256
+ rollback: false,
3257
+ migrations: "target",
3258
+ images: true,
3259
+ localStage: true,
3260
+ selfHostedTelemetry: true
3261
+ },
3262
+ credentials: [],
3263
+ tag: ({ cwd }) => deps.revision(cwd),
3264
+ validate: (ctx) => validateCompose(ctx, deps, {
3265
+ ...mode ? { mode } : {},
3266
+ ...push ? { push } : {},
3267
+ ...pin ? { pin } : {}
3268
+ }),
3269
+ ready: (ctx, run) => readyCompose(ctx, run, deps),
3270
+ plan: (ctx, run) => planCompose(ctx, run),
3271
+ provision: (ctx, run) => provisionCompose(ctx, run, deps),
3272
+ build: (ctx, run) => buildCompose(ctx, run, deps),
3273
+ release: (ctx, run) => releaseCompose(ctx, run, deps),
3274
+ verify: (ctx, run) => verifyCompose(ctx, run, deps),
3275
+ result: (ctx, run) => {
3276
+ report?.(run);
3277
+ return composeResult(ctx, run);
3278
+ }
3279
+ });
3280
+ }
3281
+ //#endregion
3282
+ Object.defineProperty(exports, "ComposePinNeedsPull", {
3283
+ enumerable: true,
3284
+ get: function() {
3285
+ return ComposePinNeedsPull;
3286
+ }
3287
+ });
3288
+ Object.defineProperty(exports, "ComposePushNeedsBuild", {
3289
+ enumerable: true,
3290
+ get: function() {
3291
+ return ComposePushNeedsBuild;
3292
+ }
3293
+ });
3294
+ Object.defineProperty(exports, "EDGE_PROJECT", {
3295
+ enumerable: true,
3296
+ get: function() {
3297
+ return EDGE_PROJECT;
3298
+ }
3299
+ });
3300
+ Object.defineProperty(exports, "composeProject", {
3301
+ enumerable: true,
3302
+ get: function() {
3303
+ return composeProject;
3304
+ }
3305
+ });
3306
+ Object.defineProperty(exports, "composeServer", {
3307
+ enumerable: true,
3308
+ get: function() {
3309
+ return composeServer;
3310
+ }
3311
+ });
3312
+ Object.defineProperty(exports, "composeStack", {
3313
+ enumerable: true,
3314
+ get: function() {
3315
+ return composeStack;
3316
+ }
3317
+ });
3318
+ Object.defineProperty(exports, "composeTarget", {
3319
+ enumerable: true,
3320
+ get: function() {
3321
+ return composeTarget;
3322
+ }
3323
+ });
3324
+ Object.defineProperty(exports, "compose_exports", {
3325
+ enumerable: true,
3326
+ get: function() {
3327
+ return compose_exports;
3328
+ }
3329
+ });
3330
+ Object.defineProperty(exports, "deployedStackSecrets", {
3331
+ enumerable: true,
3332
+ get: function() {
3333
+ return deployedStackSecrets;
3334
+ }
3335
+ });
3336
+ Object.defineProperty(exports, "derivedCredentials", {
3337
+ enumerable: true,
3338
+ get: function() {
3339
+ return derivedCredentials;
3340
+ }
3341
+ });
3342
+ Object.defineProperty(exports, "dockerCompose", {
3343
+ enumerable: true,
3344
+ get: function() {
3345
+ return dockerCompose;
3346
+ }
3347
+ });
3348
+ Object.defineProperty(exports, "dockerHost", {
3349
+ enumerable: true,
3350
+ get: function() {
3351
+ return dockerHost;
3352
+ }
3353
+ });
3354
+ Object.defineProperty(exports, "edgeAlias", {
3355
+ enumerable: true,
3356
+ get: function() {
3357
+ return edgeAlias;
3358
+ }
3359
+ });
3360
+ Object.defineProperty(exports, "edgePorts", {
3361
+ enumerable: true,
3362
+ get: function() {
3363
+ return edgePorts;
3364
+ }
3365
+ });
3366
+ Object.defineProperty(exports, "edgeRef", {
3367
+ enumerable: true,
3368
+ get: function() {
3369
+ return edgeRef;
3370
+ }
3371
+ });
3372
+ Object.defineProperty(exports, "parseDigests", {
3373
+ enumerable: true,
3374
+ get: function() {
3375
+ return parseDigests;
3376
+ }
3377
+ });
3378
+ Object.defineProperty(exports, "pinnedRef", {
3379
+ enumerable: true,
3380
+ get: function() {
3381
+ return pinnedRef;
3382
+ }
3383
+ });
3384
+ Object.defineProperty(exports, "projectNetwork", {
3385
+ enumerable: true,
3386
+ get: function() {
3387
+ return projectNetwork;
3388
+ }
3389
+ });
3390
+ Object.defineProperty(exports, "removeEdgeRoutes", {
3391
+ enumerable: true,
3392
+ get: function() {
3393
+ return removeEdgeRoutes;
3394
+ }
3395
+ });
3396
+ Object.defineProperty(exports, "sshAccess", {
3397
+ enumerable: true,
3398
+ get: function() {
3399
+ return sshAccess;
3400
+ }
3401
+ });
3402
+ Object.defineProperty(exports, "sshTarget", {
3403
+ enumerable: true,
3404
+ get: function() {
3405
+ return sshTarget;
3406
+ }
3407
+ });
3408
+ Object.defineProperty(exports, "stackDir", {
3409
+ enumerable: true,
3410
+ get: function() {
3411
+ return stackDir;
3412
+ }
3413
+ });
3414
+ Object.defineProperty(exports, "stackOverrideFile", {
3415
+ enumerable: true,
3416
+ get: function() {
3417
+ return stackOverrideFile;
3418
+ }
3419
+ });
3420
+ Object.defineProperty(exports, "stackPlan", {
3421
+ enumerable: true,
3422
+ get: function() {
3423
+ return stackPlan;
3424
+ }
3425
+ });
3426
+
3427
+ //# sourceMappingURL=compose-Mdgea4Yt.cjs.map