@geekmidas/cli 10.0.0-alpha.65 → 10.0.0-alpha.66

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 (208) hide show
  1. package/README.md +14 -0
  2. package/dist/{HostingerProvider-Dlmbfass.cjs → HostingerProvider-BznqRC2S.cjs} +3 -3
  3. package/dist/HostingerProvider-BznqRC2S.cjs.map +1 -0
  4. package/dist/{HostingerProvider-x9gQTAGZ.mjs → HostingerProvider-CY_VpVud.mjs} +3 -3
  5. package/dist/HostingerProvider-CY_VpVud.mjs.map +1 -0
  6. package/dist/{LegacyStateStore-BGoo__sO.cjs → LegacyStateStore-BFcTT2QL.cjs} +2 -2
  7. package/dist/{LegacyStateStore-BGoo__sO.cjs.map → LegacyStateStore-BFcTT2QL.cjs.map} +1 -1
  8. package/dist/{LegacyStateStore-BUu1nQ5u.mjs → LegacyStateStore-CFheFo62.mjs} +2 -2
  9. package/dist/{LegacyStateStore-BUu1nQ5u.mjs.map → LegacyStateStore-CFheFo62.mjs.map} +1 -1
  10. package/dist/{LocalStateStore-93urXyQe.cjs → LocalStateStore-BVUexSvu.cjs} +2 -2
  11. package/dist/{LocalStateStore-93urXyQe.cjs.map → LocalStateStore-BVUexSvu.cjs.map} +1 -1
  12. package/dist/{LocalStateStore-BRhMQRuy.mjs → LocalStateStore-Bm3x9oOg.mjs} +2 -2
  13. package/dist/{LocalStateStore-BRhMQRuy.mjs.map → LocalStateStore-Bm3x9oOg.mjs.map} +1 -1
  14. package/dist/{OpenApiTsGenerator-BWuDRHe9.cjs → OpenApiTsGenerator-B2zrdyP4.cjs} +2 -2
  15. package/dist/{OpenApiTsGenerator-BWuDRHe9.cjs.map → OpenApiTsGenerator-B2zrdyP4.cjs.map} +1 -1
  16. package/dist/{OpenApiTsGenerator-C6v-IeHt.mjs → OpenApiTsGenerator-BmUAcR7d.mjs} +2 -2
  17. package/dist/{OpenApiTsGenerator-C6v-IeHt.mjs.map → OpenApiTsGenerator-BmUAcR7d.mjs.map} +1 -1
  18. package/dist/{Route53Provider-DVILBsvV.cjs → Route53Provider-Bllus2NR.cjs} +2 -2
  19. package/dist/Route53Provider-Bllus2NR.cjs.map +1 -0
  20. package/dist/{Route53Provider-Cvj40A8I.mjs → Route53Provider-QXxjkZO1.mjs} +2 -2
  21. package/dist/Route53Provider-QXxjkZO1.mjs.map +1 -0
  22. package/dist/{S3StateStore-pw1GhWwt.mjs → S3StateStore-BFHuctWM.mjs} +2 -2
  23. package/dist/{S3StateStore-pw1GhWwt.mjs.map → S3StateStore-BFHuctWM.mjs.map} +1 -1
  24. package/dist/{S3StateStore-F4dxw6kS.cjs → S3StateStore-CoXBhILd.cjs} +2 -2
  25. package/dist/{S3StateStore-F4dxw6kS.cjs.map → S3StateStore-CoXBhILd.cjs.map} +1 -1
  26. package/dist/{SSMStateStore-Bt9acuAF.cjs → SSMStateStore-BqCA9TP2.cjs} +2 -2
  27. package/dist/{SSMStateStore-Bt9acuAF.cjs.map → SSMStateStore-BqCA9TP2.cjs.map} +1 -1
  28. package/dist/{SSMStateStore-Dmj7zzFO.mjs → SSMStateStore-C1kvHScY.mjs} +2 -2
  29. package/dist/{SSMStateStore-Dmj7zzFO.mjs.map → SSMStateStore-C1kvHScY.mjs.map} +1 -1
  30. package/dist/{backup-provisioner-DIVCbfee.mjs → backup-provisioner-C8DnCmxK.mjs} +2 -2
  31. package/dist/backup-provisioner-C8DnCmxK.mjs.map +1 -0
  32. package/dist/{backup-provisioner-BmhIO_Yr.cjs → backup-provisioner-oDEsPj02.cjs} +2 -2
  33. package/dist/backup-provisioner-oDEsPj02.cjs.map +1 -0
  34. package/dist/{bundler-D_14JLZF.mjs → bundler-Dvaiud0h.mjs} +4 -4
  35. package/dist/{bundler-D_14JLZF.mjs.map → bundler-Dvaiud0h.mjs.map} +1 -1
  36. package/dist/{bundler-D6MlIfBT.cjs → bundler-orRqUJNZ.cjs} +4 -4
  37. package/dist/{bundler-D6MlIfBT.cjs.map → bundler-orRqUJNZ.cjs.map} +1 -1
  38. package/dist/{cli-P56vvrDQ.mjs → cli-CfHm0AO7.mjs} +5 -4
  39. package/dist/cli-CfHm0AO7.mjs.map +1 -0
  40. package/dist/{cli-BfH1deAT.cjs → cli-D1JkkOwg.cjs} +5 -4
  41. package/dist/cli-D1JkkOwg.cjs.map +1 -0
  42. package/dist/{compose-DprE3ASm.mjs → compose-CNQL8BnO.mjs} +18 -482
  43. package/dist/compose-CNQL8BnO.mjs.map +1 -0
  44. package/dist/{compose-391oEKj1.cjs → compose-jrLy7KGO.cjs} +16 -571
  45. package/dist/compose-jrLy7KGO.cjs.map +1 -0
  46. package/dist/{config-V_X_BMpm.d.cts → config-4XFxX1al.d.cts} +3 -3
  47. package/dist/{config-BonvTW8p.d.mts.map → config-4XFxX1al.d.cts.map} +1 -1
  48. package/dist/{config-BonvTW8p.d.mts → config-UtKzhpFb.d.mts} +3 -3
  49. package/dist/{config-V_X_BMpm.d.cts.map → config-UtKzhpFb.d.mts.map} +1 -1
  50. package/dist/config.cjs +3 -3
  51. package/dist/config.d.cts +3 -3
  52. package/dist/config.d.mts +3 -3
  53. package/dist/config.mjs +3 -3
  54. package/dist/databases-C2p5LGAp.cjs +419 -0
  55. package/dist/databases-C2p5LGAp.cjs.map +1 -0
  56. package/dist/databases-hUHTuwhx.mjs +346 -0
  57. package/dist/databases-hUHTuwhx.mjs.map +1 -0
  58. package/dist/deploy/public.cjs +26 -17
  59. package/dist/deploy/public.d.cts +136 -19
  60. package/dist/deploy/public.d.cts.map +1 -1
  61. package/dist/deploy/public.d.mts +136 -19
  62. package/dist/deploy/public.d.mts.map +1 -1
  63. package/dist/deploy/public.mjs +8 -8
  64. package/dist/deploy-B7aJi_Ah.cjs +511 -0
  65. package/dist/deploy-B7aJi_Ah.cjs.map +1 -0
  66. package/dist/deploy-DWn7YfIc.mjs +488 -0
  67. package/dist/deploy-DWn7YfIc.mjs.map +1 -0
  68. package/dist/{discover-LMrqsWSU.mjs → discover-BFoJWKGD.mjs} +4 -2
  69. package/dist/discover-BFoJWKGD.mjs.map +1 -0
  70. package/dist/{discover-DHdECueV.cjs → discover-OEfZ9OkA.cjs} +4 -2
  71. package/dist/discover-OEfZ9OkA.cjs.map +1 -0
  72. package/dist/dokploy-B4LcYG1E.cjs +38 -0
  73. package/dist/dokploy-B4LcYG1E.cjs.map +1 -0
  74. package/dist/dokploy-DalwLML5.mjs +38 -0
  75. package/dist/dokploy-DalwLML5.mjs.map +1 -0
  76. package/dist/{encryption-B1wJwgae.cjs → encryption-3KlGS1sl.cjs} +5 -7
  77. package/dist/{encryption-B1wJwgae.cjs.map → encryption-3KlGS1sl.cjs.map} +1 -1
  78. package/dist/{encryption-DF_hgIpl.mjs → encryption-CPfENwWO.mjs} +3 -5
  79. package/dist/{encryption-DF_hgIpl.mjs.map → encryption-CPfENwWO.mjs.map} +1 -1
  80. package/dist/{deploy-CZ5nlpsu.mjs → engine-CunvOG3s.mjs} +2100 -1733
  81. package/dist/engine-CunvOG3s.mjs.map +1 -0
  82. package/dist/{deploy-Cy8igpQF.cjs → engine-DR6KurWX.cjs} +2136 -1727
  83. package/dist/engine-DR6KurWX.cjs.map +1 -0
  84. package/dist/{github-CYcmTdFs.cjs → github-BJ5gh9bw.cjs} +7 -7
  85. package/dist/{github-CYcmTdFs.cjs.map → github-BJ5gh9bw.cjs.map} +1 -1
  86. package/dist/{github-B0YYE98x.mjs → github-MXA9v7Ge.mjs} +3 -3
  87. package/dist/{github-B0YYE98x.mjs.map → github-MXA9v7Ge.mjs.map} +1 -1
  88. package/dist/{index-DwKD_8lt.d.mts → index-CyApaHVe.d.cts} +2 -2
  89. package/dist/{index-DPUAGJaK.d.cts.map → index-CyApaHVe.d.cts.map} +1 -1
  90. package/dist/{index-DPUAGJaK.d.cts → index-DD1FfUHh.d.mts} +2 -2
  91. package/dist/{index-DwKD_8lt.d.mts.map → index-DD1FfUHh.d.mts.map} +1 -1
  92. package/dist/index.cjs +276 -68
  93. package/dist/index.cjs.map +1 -1
  94. package/dist/index.mjs +254 -50
  95. package/dist/index.mjs.map +1 -1
  96. package/dist/{migrate-B2qL5wj4.mjs → migrate-BJBcm4sG.mjs} +6 -6
  97. package/dist/{migrate-B2qL5wj4.mjs.map → migrate-BJBcm4sG.mjs.map} +1 -1
  98. package/dist/{migrate-q4pPAXtO.cjs → migrate-CF4J4GQS.cjs} +16 -16
  99. package/dist/{migrate-q4pPAXtO.cjs.map → migrate-CF4J4GQS.cjs.map} +1 -1
  100. package/dist/openapi.cjs +1 -1
  101. package/dist/openapi.d.cts +1 -1
  102. package/dist/openapi.d.mts +1 -1
  103. package/dist/openapi.mjs +1 -1
  104. package/dist/provider-B3Sv_a74.cjs +173 -0
  105. package/dist/provider-B3Sv_a74.cjs.map +1 -0
  106. package/dist/provider-D7rQzzzf.mjs +138 -0
  107. package/dist/provider-D7rQzzzf.mjs.map +1 -0
  108. package/dist/{ready-bQfu9GES.mjs → ready-2s3-CndS.mjs} +3 -3
  109. package/dist/{ready-bQfu9GES.mjs.map → ready-2s3-CndS.mjs.map} +1 -1
  110. package/dist/{ready-brEhh68f.cjs → ready-B6uHr4sm.cjs} +3 -3
  111. package/dist/{ready-brEhh68f.cjs.map → ready-B6uHr4sm.cjs.map} +1 -1
  112. package/dist/reconcile/public.cjs +1 -1
  113. package/dist/reconcile/public.d.cts +1 -1
  114. package/dist/reconcile/public.d.mts +1 -1
  115. package/dist/reconcile/public.mjs +1 -1
  116. package/dist/{resolve-N_i4XNd-.cjs → resolve-CVmch0wI.cjs} +3 -3
  117. package/dist/{resolve-N_i4XNd-.cjs.map → resolve-CVmch0wI.cjs.map} +1 -1
  118. package/dist/{resolve-DLkIXTct.mjs → resolve-Db0tNlrW.mjs} +3 -3
  119. package/dist/{resolve-DLkIXTct.mjs.map → resolve-Db0tNlrW.mjs.map} +1 -1
  120. package/dist/rollback-DvAo4OKS.mjs +133 -0
  121. package/dist/rollback-DvAo4OKS.mjs.map +1 -0
  122. package/dist/rollback-DxJK84hy.cjs +162 -0
  123. package/dist/rollback-DxJK84hy.cjs.map +1 -0
  124. package/dist/{runtime-BnD7Nn96.d.cts → runtime-BRqFOvPM.d.cts} +2 -2
  125. package/dist/{runtime-BnD7Nn96.d.cts.map → runtime-BRqFOvPM.d.cts.map} +1 -1
  126. package/dist/{runtime-j-tmFL65.d.mts → runtime-CEk5n4VH.d.mts} +2 -2
  127. package/dist/{runtime-j-tmFL65.d.mts.map → runtime-CEk5n4VH.d.mts.map} +1 -1
  128. package/dist/sandbox/discover-worker.cjs +1 -1
  129. package/dist/sandbox/discover-worker.mjs +1 -1
  130. package/dist/sandbox/migrate-worker.cjs +61 -0
  131. package/dist/sandbox/migrate-worker.cjs.map +1 -0
  132. package/dist/sandbox/migrate-worker.d.cts +1 -0
  133. package/dist/sandbox/migrate-worker.d.mts +1 -0
  134. package/dist/sandbox/migrate-worker.mjs +62 -0
  135. package/dist/sandbox/migrate-worker.mjs.map +1 -0
  136. package/dist/{schemas-D2LCF689.mjs → schemas-Cfhtpdo0.mjs} +10 -2
  137. package/dist/{schemas-Ckm_aBnF.cjs.map → schemas-Cfhtpdo0.mjs.map} +1 -1
  138. package/dist/{schemas-Ckm_aBnF.cjs → schemas-RUlKHu27.cjs} +15 -1
  139. package/dist/{schemas-D2LCF689.mjs.map → schemas-RUlKHu27.cjs.map} +1 -1
  140. package/dist/{sniffer-BSwaL-dX.cjs → sniffer-5xmrkh2a.cjs} +112 -606
  141. package/dist/sniffer-5xmrkh2a.cjs.map +1 -0
  142. package/dist/{sniffer-BZ-OJO4Y.mjs → sniffer-BAQhLEsI.mjs} +95 -546
  143. package/dist/sniffer-BAQhLEsI.mjs.map +1 -0
  144. package/dist/{state-DF26e_lo.cjs → state-Y6ATakMX.cjs} +744 -20
  145. package/dist/state-Y6ATakMX.cjs.map +1 -0
  146. package/dist/{state-B8YwmUZU.mjs → state-uuYqnRVO.mjs} +605 -19
  147. package/dist/state-uuYqnRVO.mjs.map +1 -0
  148. package/dist/target/public.cjs +1 -1
  149. package/dist/target/public.d.cts +2 -2
  150. package/dist/target/public.d.mts +2 -2
  151. package/dist/target/public.mjs +1 -1
  152. package/dist/{terminal-Bs5E1oxm.cjs → terminal-CHy_MdjG.cjs} +11 -15
  153. package/dist/{terminal-Bs5E1oxm.cjs.map → terminal-CHy_MdjG.cjs.map} +1 -1
  154. package/dist/{terminal-CfbiYUbZ.mjs → terminal-DUhcZ1ND.mjs} +4 -4
  155. package/dist/{terminal-CfbiYUbZ.mjs.map → terminal-DUhcZ1ND.mjs.map} +1 -1
  156. package/dist/{types-ByMWkXKP.d.cts → types-CSw2w69a.d.mts} +68 -10
  157. package/dist/types-CSw2w69a.d.mts.map +1 -0
  158. package/dist/{types-BCzMWBb6.d.mts → types-D5WIxRDP.d.cts} +69 -9
  159. package/dist/types-D5WIxRDP.d.cts.map +1 -0
  160. package/dist/vitest.cjs +1 -1
  161. package/dist/vitest.mjs +1 -1
  162. package/dist/workspace/index.cjs +1 -1
  163. package/dist/workspace/index.d.cts +2 -2
  164. package/dist/workspace/index.d.mts +2 -2
  165. package/dist/workspace/index.mjs +1 -1
  166. package/dist/{workspace-C06_pW4g.mjs → workspace-Be6uBhlF.mjs} +10 -3
  167. package/dist/workspace-Be6uBhlF.mjs.map +1 -0
  168. package/dist/{workspace-BewAHZIo.cjs → workspace-ON-bqiJz.cjs} +10 -3
  169. package/dist/workspace-ON-bqiJz.cjs.map +1 -0
  170. package/package.json +13 -13
  171. package/dist/HostingerProvider-Dlmbfass.cjs.map +0 -1
  172. package/dist/HostingerProvider-x9gQTAGZ.mjs.map +0 -1
  173. package/dist/Route53Provider-Cvj40A8I.mjs.map +0 -1
  174. package/dist/Route53Provider-DVILBsvV.cjs.map +0 -1
  175. package/dist/backup-provisioner-BmhIO_Yr.cjs.map +0 -1
  176. package/dist/backup-provisioner-DIVCbfee.mjs.map +0 -1
  177. package/dist/cli-BfH1deAT.cjs.map +0 -1
  178. package/dist/cli-P56vvrDQ.mjs.map +0 -1
  179. package/dist/compose-391oEKj1.cjs.map +0 -1
  180. package/dist/compose-DprE3ASm.mjs.map +0 -1
  181. package/dist/deploy-9MBG_QLd.mjs +0 -137
  182. package/dist/deploy-9MBG_QLd.mjs.map +0 -1
  183. package/dist/deploy-B80idPUZ.cjs +0 -142
  184. package/dist/deploy-B80idPUZ.cjs.map +0 -1
  185. package/dist/deploy-CZ5nlpsu.mjs.map +0 -1
  186. package/dist/deploy-Cy8igpQF.cjs.map +0 -1
  187. package/dist/discover-DHdECueV.cjs.map +0 -1
  188. package/dist/discover-LMrqsWSU.mjs.map +0 -1
  189. package/dist/dokploy-BdtBePjp.mjs +0 -32
  190. package/dist/dokploy-BdtBePjp.mjs.map +0 -1
  191. package/dist/dokploy-iFY8MY-L.cjs +0 -32
  192. package/dist/dokploy-iFY8MY-L.cjs.map +0 -1
  193. package/dist/sniffer-BSwaL-dX.cjs.map +0 -1
  194. package/dist/sniffer-BZ-OJO4Y.mjs.map +0 -1
  195. package/dist/state-B8YwmUZU.mjs.map +0 -1
  196. package/dist/state-DF26e_lo.cjs.map +0 -1
  197. package/dist/storage-BzaUxmqe.mjs +0 -124
  198. package/dist/storage-BzaUxmqe.mjs.map +0 -1
  199. package/dist/storage-CSGoC3bK.cjs +0 -152
  200. package/dist/storage-CSGoC3bK.cjs.map +0 -1
  201. package/dist/store-CJF1Tqkq.cjs +0 -510
  202. package/dist/store-CJF1Tqkq.cjs.map +0 -1
  203. package/dist/store-CLiHnqOY.mjs +0 -415
  204. package/dist/store-CLiHnqOY.mjs.map +0 -1
  205. package/dist/types-BCzMWBb6.d.mts.map +0 -1
  206. package/dist/types-ByMWkXKP.d.cts.map +0 -1
  207. package/dist/workspace-BewAHZIo.cjs.map +0 -1
  208. package/dist/workspace-C06_pW4g.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"HostingerProvider-Dlmbfass.cjs","names":["getHostingerToken"],"sources":["../src/deploy/dns/hostinger-api.ts","../src/deploy/dns/HostingerProvider.ts"],"sourcesContent":["/**\n * Hostinger DNS API client\n *\n * API Documentation: https://developers.hostinger.com/\n * Authentication: Bearer token from hpanel.hostinger.com/profile/api\n */\n\nconst HOSTINGER_API_BASE = 'https://developers.hostinger.com';\n\n/**\n * DNS record types supported by Hostinger\n */\nexport type DnsRecordType =\n\t| 'A'\n\t| 'AAAA'\n\t| 'CNAME'\n\t| 'MX'\n\t| 'TXT'\n\t| 'NS'\n\t| 'SRV'\n\t| 'CAA';\n\n/**\n * A single DNS record\n */\nexport interface DnsRecord {\n\t/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */\n\tname: string;\n\t/** Record type */\n\ttype: DnsRecordType;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** Record values */\n\trecords: Array<{ content: string }>;\n}\n\n/**\n * Filter for deleting specific records\n */\nexport interface DnsRecordFilter {\n\tname: string;\n\ttype: DnsRecordType;\n}\n\n/**\n * API error response\n */\nexport interface HostingerErrorResponse {\n\tmessage?: string;\n\terrors?: Record<string, string[]>;\n}\n\n/**\n * Hostinger API error\n */\nexport class HostingerApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic errors?: Record<string, string[]>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'HostingerApiError';\n\t}\n}\n\n/**\n * Hostinger DNS API client\n *\n * @example\n * ```ts\n * const api = new HostingerApi(token);\n *\n * // Get all records for a domain\n * const records = await api.getRecords('traflabs.io');\n *\n * // Create/update records\n * await api.upsertRecords('traflabs.io', [\n * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }\n * ]);\n * ```\n */\nexport class HostingerApi {\n\tprivate token: string;\n\n\tconstructor(token: string) {\n\t\tthis.token = token;\n\t}\n\n\t/**\n\t * Make a request to the Hostinger API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${HOSTINGER_API_BASE}${endpoint}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAuthorization: `Bearer ${this.token}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;\n\t\t\tlet errors: Record<string, string[]> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as HostingerErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Hostinger API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\terrors = errorBody.errors;\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new HostingerApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\terrors,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Get all DNS records for a domain\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\n\n\t\treturn response?.data ?? [];\n\t}\n\n\t/**\n\t * Create or update DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to create/update\n\t * @param overwrite - If true, replaces all existing records. If false, merges with existing.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t\toverwrite = false,\n\t): Promise<void> {\n\t\tawait this.request('PUT', `/api/dns/v1/zones/${domain}`, {\n\t\t\toverwrite,\n\t\t\tzone: records,\n\t\t});\n\t}\n\n\t/**\n\t * Validate DNS records before applying\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to validate\n\t * @returns true if valid, throws if invalid\n\t */\n\tasync validateRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t): Promise<boolean> {\n\t\tawait this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {\n\t\t\toverwrite: false,\n\t\t\tzone: records,\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * Delete specific DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param filters - Filters to match records for deletion\n\t */\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\tfilters: DnsRecordFilter[],\n\t): Promise<void> {\n\t\tawait this.request('DELETE', `/api/dns/v1/zones/${domain}`, {\n\t\t\tfilters,\n\t\t});\n\t}\n\n\t/**\n\t * Check if a specific record exists\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param name - Subdomain name (e.g., 'api.joemoer')\n\t * @param type - Record type (e.g., 'A')\n\t */\n\tasync recordExists(\n\t\tdomain: string,\n\t\tname: string,\n\t\ttype: DnsRecordType = 'A',\n\t): Promise<boolean> {\n\t\tconst records = await this.getRecords(domain);\n\t\treturn records.some((r) => r.name === name && r.type === type);\n\t}\n\n\t/**\n\t * Create a single A record if it doesn't exist\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param subdomain - Subdomain name (e.g., 'api.joemoer')\n\t * @param ip - IP address to point to\n\t * @param ttl - TTL in seconds (default: 300)\n\t * @returns true if created, false if already exists\n\t */\n\tasync createARecordIfNotExists(\n\t\tdomain: string,\n\t\tsubdomain: string,\n\t\tip: string,\n\t\tttl = 300,\n\t): Promise<boolean> {\n\t\tconst exists = await this.recordExists(domain, subdomain, 'A');\n\t\tif (exists) {\n\t\t\treturn false;\n\t\t}\n\n\t\tawait this.upsertRecords(domain, [\n\t\t\t{\n\t\t\t\tname: subdomain,\n\t\t\t\ttype: 'A',\n\t\t\t\tttl,\n\t\t\t\trecords: [{ content: ip }],\n\t\t\t},\n\t\t]);\n\n\t\treturn true;\n\t}\n}\n","/**\n * Hostinger DNS Provider\n *\n * Implements DnsProvider interface using the Hostinger DNS API.\n */\n\nimport { getHostingerToken } from '../../auth/credentials';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\nimport { HostingerApi } from './hostinger-api';\n\n/**\n * Hostinger DNS provider implementation.\n */\nexport class HostingerProvider implements DnsProvider {\n\treadonly name = 'hostinger';\n\tprivate api: HostingerApi | null = null;\n\n\t/**\n\t * Get or create the Hostinger API client.\n\t */\n\tprivate async getApi(): Promise<HostingerApi> {\n\t\tif (this.api) {\n\t\t\treturn this.api;\n\t\t}\n\n\t\tconst token = await getHostingerToken();\n\t\tif (!token) {\n\t\t\tthrow new Error(\n\t\t\t\t'Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api',\n\t\t\t);\n\t\t}\n\n\t\tthis.api = new HostingerApi(token);\n\t\treturn this.api;\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst api = await this.getApi();\n\t\tconst records = await api.getRecords(domain);\n\n\t\treturn records.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttype: r.type,\n\t\t\tttl: r.ttl,\n\t\t\tvalues: r.records.map((rec) => rec.content),\n\t\t}));\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to check what already exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\n\t\t\tconst existingValue = existing?.records?.[0]?.content;\n\n\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: false,\n\t\t\t\t\tunchanged: true,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Create or update the record\n\t\t\tawait api.upsertRecords(domain, [\n\t\t\t\t{\n\t\t\t\t\tname: record.name,\n\t\t\t\t\ttype: record.type,\n\t\t\t\t\tttl: record.ttl,\n\t\t\t\t\trecords: [{ content: record.value }],\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: !existing,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"HostingerProvider-x9gQTAGZ.mjs","names":[],"sources":["../src/deploy/dns/hostinger-api.ts","../src/deploy/dns/HostingerProvider.ts"],"sourcesContent":["/**\n * Hostinger DNS API client\n *\n * API Documentation: https://developers.hostinger.com/\n * Authentication: Bearer token from hpanel.hostinger.com/profile/api\n */\n\nconst HOSTINGER_API_BASE = 'https://developers.hostinger.com';\n\n/**\n * DNS record types supported by Hostinger\n */\nexport type DnsRecordType =\n\t| 'A'\n\t| 'AAAA'\n\t| 'CNAME'\n\t| 'MX'\n\t| 'TXT'\n\t| 'NS'\n\t| 'SRV'\n\t| 'CAA';\n\n/**\n * A single DNS record\n */\nexport interface DnsRecord {\n\t/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */\n\tname: string;\n\t/** Record type */\n\ttype: DnsRecordType;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** Record values */\n\trecords: Array<{ content: string }>;\n}\n\n/**\n * Filter for deleting specific records\n */\nexport interface DnsRecordFilter {\n\tname: string;\n\ttype: DnsRecordType;\n}\n\n/**\n * API error response\n */\nexport interface HostingerErrorResponse {\n\tmessage?: string;\n\terrors?: Record<string, string[]>;\n}\n\n/**\n * Hostinger API error\n */\nexport class HostingerApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic errors?: Record<string, string[]>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'HostingerApiError';\n\t}\n}\n\n/**\n * Hostinger DNS API client\n *\n * @example\n * ```ts\n * const api = new HostingerApi(token);\n *\n * // Get all records for a domain\n * const records = await api.getRecords('traflabs.io');\n *\n * // Create/update records\n * await api.upsertRecords('traflabs.io', [\n * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }\n * ]);\n * ```\n */\nexport class HostingerApi {\n\tprivate token: string;\n\n\tconstructor(token: string) {\n\t\tthis.token = token;\n\t}\n\n\t/**\n\t * Make a request to the Hostinger API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${HOSTINGER_API_BASE}${endpoint}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAuthorization: `Bearer ${this.token}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;\n\t\t\tlet errors: Record<string, string[]> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as HostingerErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Hostinger API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\terrors = errorBody.errors;\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new HostingerApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\terrors,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Get all DNS records for a domain\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\n\n\t\treturn response?.data ?? [];\n\t}\n\n\t/**\n\t * Create or update DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to create/update\n\t * @param overwrite - If true, replaces all existing records. If false, merges with existing.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t\toverwrite = false,\n\t): Promise<void> {\n\t\tawait this.request('PUT', `/api/dns/v1/zones/${domain}`, {\n\t\t\toverwrite,\n\t\t\tzone: records,\n\t\t});\n\t}\n\n\t/**\n\t * Validate DNS records before applying\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to validate\n\t * @returns true if valid, throws if invalid\n\t */\n\tasync validateRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t): Promise<boolean> {\n\t\tawait this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {\n\t\t\toverwrite: false,\n\t\t\tzone: records,\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * Delete specific DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param filters - Filters to match records for deletion\n\t */\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\tfilters: DnsRecordFilter[],\n\t): Promise<void> {\n\t\tawait this.request('DELETE', `/api/dns/v1/zones/${domain}`, {\n\t\t\tfilters,\n\t\t});\n\t}\n\n\t/**\n\t * Check if a specific record exists\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param name - Subdomain name (e.g., 'api.joemoer')\n\t * @param type - Record type (e.g., 'A')\n\t */\n\tasync recordExists(\n\t\tdomain: string,\n\t\tname: string,\n\t\ttype: DnsRecordType = 'A',\n\t): Promise<boolean> {\n\t\tconst records = await this.getRecords(domain);\n\t\treturn records.some((r) => r.name === name && r.type === type);\n\t}\n\n\t/**\n\t * Create a single A record if it doesn't exist\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param subdomain - Subdomain name (e.g., 'api.joemoer')\n\t * @param ip - IP address to point to\n\t * @param ttl - TTL in seconds (default: 300)\n\t * @returns true if created, false if already exists\n\t */\n\tasync createARecordIfNotExists(\n\t\tdomain: string,\n\t\tsubdomain: string,\n\t\tip: string,\n\t\tttl = 300,\n\t): Promise<boolean> {\n\t\tconst exists = await this.recordExists(domain, subdomain, 'A');\n\t\tif (exists) {\n\t\t\treturn false;\n\t\t}\n\n\t\tawait this.upsertRecords(domain, [\n\t\t\t{\n\t\t\t\tname: subdomain,\n\t\t\t\ttype: 'A',\n\t\t\t\tttl,\n\t\t\t\trecords: [{ content: ip }],\n\t\t\t},\n\t\t]);\n\n\t\treturn true;\n\t}\n}\n","/**\n * Hostinger DNS Provider\n *\n * Implements DnsProvider interface using the Hostinger DNS API.\n */\n\nimport { getHostingerToken } from '../../auth/credentials';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\nimport { HostingerApi } from './hostinger-api';\n\n/**\n * Hostinger DNS provider implementation.\n */\nexport class HostingerProvider implements DnsProvider {\n\treadonly name = 'hostinger';\n\tprivate api: HostingerApi | null = null;\n\n\t/**\n\t * Get or create the Hostinger API client.\n\t */\n\tprivate async getApi(): Promise<HostingerApi> {\n\t\tif (this.api) {\n\t\t\treturn this.api;\n\t\t}\n\n\t\tconst token = await getHostingerToken();\n\t\tif (!token) {\n\t\t\tthrow new Error(\n\t\t\t\t'Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api',\n\t\t\t);\n\t\t}\n\n\t\tthis.api = new HostingerApi(token);\n\t\treturn this.api;\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst api = await this.getApi();\n\t\tconst records = await api.getRecords(domain);\n\n\t\treturn records.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttype: r.type,\n\t\t\tttl: r.ttl,\n\t\t\tvalues: r.records.map((rec) => rec.content),\n\t\t}));\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to check what already exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\n\t\t\tconst existingValue = existing?.records?.[0]?.content;\n\n\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: false,\n\t\t\t\t\tunchanged: true,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Create or update the record\n\t\t\tawait api.upsertRecords(domain, [\n\t\t\t\t{\n\t\t\t\t\tname: record.name,\n\t\t\t\t\ttype: record.type,\n\t\t\t\t\tttl: record.ttl,\n\t\t\t\t\trecords: [{ content: record.value }],\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: !existing,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"Route53Provider-Cvj40A8I.mjs","names":[],"sources":["../src/deploy/dns/Route53Provider.ts"],"sourcesContent":["/**\n * Route53 DNS Provider\n *\n * Implements DnsProvider interface using AWS Route53.\n */\n\nimport {\n\tChangeResourceRecordSetsCommand,\n\tListHostedZonesByNameCommand,\n\tListResourceRecordSetsCommand,\n\tRoute53Client,\n\ttype RRType,\n} from '@aws-sdk/client-route-53';\nimport { fromIni } from '@aws-sdk/credential-providers';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tDnsRecordType,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\n\nexport interface Route53ProviderOptions {\n\t/** AWS region (optional - uses AWS_REGION env var if not provided) */\n\tregion?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** Hosted zone ID (optional - auto-detected from domain if not provided) */\n\thostedZoneId?: string;\n\t/** Custom endpoint for testing with localstack */\n\tendpoint?: string;\n}\n\n/**\n * Route53 DNS provider implementation.\n *\n * Uses AWS default credential chain for authentication.\n * Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.\n * Profile can be specified to use a named profile from ~/.aws/credentials.\n */\nexport class Route53Provider implements DnsProvider {\n\treadonly name = 'route53';\n\tprivate client: Route53Client;\n\tprivate hostedZoneId?: string;\n\tprivate hostedZoneCache: Map<string, string> = new Map();\n\n\tconstructor(options: Route53ProviderOptions = {}) {\n\t\t// Route53 is a global service, default to us-east-1 if no region specified\n\t\tconst region = options.region ?? process.env.AWS_REGION ?? 'us-east-1';\n\n\t\tthis.client = new Route53Client({\n\t\t\tregion,\n\t\t\t...(options.endpoint && { endpoint: options.endpoint }),\n\t\t\t...(options.profile && {\n\t\t\t\tcredentials: fromIni({ profile: options.profile }),\n\t\t\t}),\n\t\t});\n\t\tthis.hostedZoneId = options.hostedZoneId;\n\t}\n\n\t/**\n\t * Get the hosted zone ID for a domain.\n\t * Uses cache to avoid repeated API calls.\n\t */\n\tprivate async getHostedZoneId(domain: string): Promise<string> {\n\t\t// Use configured zone ID if provided\n\t\tif (this.hostedZoneId) {\n\t\t\treturn this.hostedZoneId;\n\t\t}\n\n\t\t// Check cache\n\t\tif (this.hostedZoneCache.has(domain)) {\n\t\t\treturn this.hostedZoneCache.get(domain)!;\n\t\t}\n\n\t\t// Auto-detect from domain\n\t\tconst command = new ListHostedZonesByNameCommand({\n\t\t\tDNSName: domain,\n\t\t\tMaxItems: 1,\n\t\t});\n\n\t\tconst response = await this.client.send(command);\n\t\tconst zones = response.HostedZones ?? [];\n\n\t\t// Find exact match (domain with trailing dot)\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst zone = zones.find((z) => z.Name === normalizedDomain);\n\n\t\tif (!zone?.Id) {\n\t\t\tthrow new Error(\n\t\t\t\t`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`,\n\t\t\t);\n\t\t}\n\n\t\t// Zone ID comes as \"/hostedzone/Z1234567890\" - extract just the ID\n\t\tconst zoneId = zone.Id.replace('/hostedzone/', '');\n\t\tthis.hostedZoneCache.set(domain, zoneId);\n\t\treturn zoneId;\n\t}\n\n\t/**\n\t * Convert Route53 record type to our DnsRecordType.\n\t * Excludes NS and SOA which are auto-managed by Route53 for the zone.\n\t */\n\tprivate toRecordType(type: string): DnsRecordType | null {\n\t\t// Exclude NS and SOA which are auto-managed zone records\n\t\tconst managedTypes = ['NS', 'SOA'];\n\t\tif (managedTypes.includes(type)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst validTypes: DnsRecordType[] = [\n\t\t\t'A',\n\t\t\t'AAAA',\n\t\t\t'CNAME',\n\t\t\t'MX',\n\t\t\t'TXT',\n\t\t\t'SRV',\n\t\t\t'CAA',\n\t\t];\n\t\treturn validTypes.includes(type as DnsRecordType)\n\t\t\t? (type as DnsRecordType)\n\t\t\t: null;\n\t}\n\n\t/**\n\t * Extract subdomain from full record name relative to domain.\n\t */\n\tprivate extractSubdomain(recordName: string, domain: string): string {\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst normalizedName = recordName.endsWith('.')\n\t\t\t? recordName\n\t\t\t: `${recordName}.`;\n\n\t\tif (normalizedName === normalizedDomain) {\n\t\t\treturn '@';\n\t\t}\n\n\t\t// Remove the domain suffix\n\t\tconst subdomain = normalizedName.replace(`.${normalizedDomain}`, '');\n\t\treturn subdomain.replace(/\\.$/, ''); // Remove trailing dot if any\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst records: DnsRecord[] = [];\n\n\t\tlet nextRecordName: string | undefined;\n\t\tlet nextRecordType: RRType | undefined;\n\n\t\t// Paginate through all records\n\t\tdo {\n\t\t\tconst command = new ListResourceRecordSetsCommand({\n\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\tStartRecordName: nextRecordName,\n\t\t\t\tStartRecordType: nextRecordType,\n\t\t\t\tMaxItems: 100,\n\t\t\t});\n\n\t\t\tconst response = await this.client.send(command);\n\n\t\t\tfor (const recordSet of response.ResourceRecordSets ?? []) {\n\t\t\t\tconst type = this.toRecordType(recordSet.Type ?? '');\n\t\t\t\tif (!type || !recordSet.Name) continue;\n\n\t\t\t\tconst values = (recordSet.ResourceRecords ?? [])\n\t\t\t\t\t.map((r) => r.Value)\n\t\t\t\t\t.filter((v): v is string => !!v);\n\n\t\t\t\trecords.push({\n\t\t\t\t\tname: this.extractSubdomain(recordSet.Name, domain),\n\t\t\t\t\ttype,\n\t\t\t\t\tttl: recordSet.TTL ?? 300,\n\t\t\t\t\tvalues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (response.IsTruncated) {\n\t\t\t\tnextRecordName = response.NextRecordName;\n\t\t\t\tnextRecordType = response.NextRecordType;\n\t\t\t} else {\n\t\t\t\tnextRecordName = undefined;\n\t\t\t}\n\t\t} while (nextRecordName);\n\n\t\treturn records;\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to determine if creating or updating\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tconst existingValue = existing?.values?.[0];\n\n\t\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tcreated: false,\n\t\t\t\t\t\tunchanged: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'UPSERT' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: record.ttl,\n\t\t\t\t\t\tResourceRecords: [{ Value: record.value }],\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: !existing,\n\t\t\t\t\tunchanged: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\tComment: 'Upsert by gkm deploy',\n\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tawait this.client.send(command);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to find the ones to delete\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tif (!existing) {\n\t\t\t\t\t// Record doesn't exist - already deleted\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'DELETE' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: existing.ttl,\n\t\t\t\t\t\tResourceRecords: existing.values.map((v) => ({ Value: v })),\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: true,\n\t\t\t\t\tnotFound: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\ttry {\n\t\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\t\tComment: 'Delete by gkm undeploy',\n\t\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tawait this.client.send(command);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Mark all records in this batch as failed\n\t\t\t\t\tfor (const result of results.slice(-changes.length)) {\n\t\t\t\t\t\tif (result.deleted) {\n\t\t\t\t\t\t\tresult.deleted = false;\n\t\t\t\t\t\t\tresult.error = String(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;AA0CA,IAAa,kBAAb,MAAoD;CACnD,OAAgB;CAChB;CACA;CACA,kCAA+C,IAAI,IAAI;CAEvD,YAAY,UAAkC,CAAC,GAAG;EAEjD,MAAM,SAAS,QAAQ,UAAU,QAAQ,IAAI,cAAc;EAE3D,KAAK,SAAS,IAAI,cAAc;GAC/B;GACA,GAAI,QAAQ,YAAY,EAAE,UAAU,QAAQ,SAAS;GACrD,GAAI,QAAQ,WAAW,EACtB,aAAa,QAAQ,EAAE,SAAS,QAAQ,QAAQ,CAAC,EAClD;EACD,CAAC;EACD,KAAK,eAAe,QAAQ;CAC7B;;;;;CAMA,MAAc,gBAAgB,QAAiC;EAE9D,IAAI,KAAK,cACR,OAAO,KAAK;EAIb,IAAI,KAAK,gBAAgB,IAAI,MAAM,GAClC,OAAO,KAAK,gBAAgB,IAAI,MAAM;EAIvC,MAAM,UAAU,IAAI,6BAA6B;GAChD,SAAS;GACT,UAAU;EACX,CAAC;EAGD,MAAM,SAAQ,MADS,KAAK,OAAO,KAAK,OAAO,EAAA,CACxB,eAAe,CAAC;EAGvC,MAAM,mBAAmB,OAAO,SAAS,GAAG,IAAI,SAAS,GAAG,OAAO;EACnE,MAAM,OAAO,MAAM,MAAM,MAAM,EAAE,SAAS,gBAAgB;EAE1D,IAAI,CAAC,MAAM,IACV,MAAM,IAAI,MACT,oCAAoC,OAAO,2DAC5C;EAID,MAAM,SAAS,KAAK,GAAG,QAAQ,gBAAgB,EAAE;EACjD,KAAK,gBAAgB,IAAI,QAAQ,MAAM;EACvC,OAAO;CACR;;;;;CAMA,aAAqB,MAAoC;EAGxD,IAAI,CADkB,MAAM,KACb,CAAC,CAAC,SAAS,IAAI,GAC7B,OAAO;EAYR,OAAO;GARN;GACA;GACA;GACA;GACA;GACA;GACA;EAEe,CAAC,CAAC,SAAS,IAAqB,IAC5C,OACD;CACJ;;;;CAKA,iBAAyB,YAAoB,QAAwB;EACpE,MAAM,mBAAmB,OAAO,SAAS,GAAG,IAAI,SAAS,GAAG,OAAO;EACnE,MAAM,iBAAiB,WAAW,SAAS,GAAG,IAC3C,aACA,GAAG,WAAW;EAEjB,IAAI,mBAAmB,kBACtB,OAAO;EAKR,OADkB,eAAe,QAAQ,IAAI,oBAAoB,EAClD,CAAC,CAAC,QAAQ,OAAO,EAAE;CACnC;CAEA,MAAM,WAAW,QAAsC;EACtD,MAAM,SAAS,MAAM,KAAK,gBAAgB,MAAM;EAChD,MAAM,UAAuB,CAAC;EAE9B,IAAI;EACJ,IAAI;EAGJ,GAAG;GACF,MAAM,UAAU,IAAI,8BAA8B;IACjD,cAAc;IACd,iBAAiB;IACjB,iBAAiB;IACjB,UAAU;GACX,CAAC;GAED,MAAM,WAAW,MAAM,KAAK,OAAO,KAAK,OAAO;GAE/C,KAAK,MAAM,aAAa,SAAS,sBAAsB,CAAC,GAAG;IAC1D,MAAM,OAAO,KAAK,aAAa,UAAU,QAAQ,EAAE;IACnD,IAAI,CAAC,QAAQ,CAAC,UAAU,MAAM;IAE9B,MAAM,UAAU,UAAU,mBAAmB,CAAC,EAAA,CAC5C,KAAK,MAAM,EAAE,KAAK,CAAC,CACnB,QAAQ,MAAmB,CAAC,CAAC,CAAC;IAEhC,QAAQ,KAAK;KACZ,MAAM,KAAK,iBAAiB,UAAU,MAAM,MAAM;KAClD;KACA,KAAK,UAAU,OAAO;KACtB;IACD,CAAC;GACF;GAEA,IAAI,SAAS,aAAa;IACzB,iBAAiB,SAAS;IAC1B,iBAAiB,SAAS;GAC3B,OACC,iBAAiB,KAAA;EAEnB,SAAS;EAET,OAAO;CACR;CAEA,MAAM,cACL,QACA,SAC0B;EAC1B,MAAM,SAAS,MAAM,KAAK,gBAAgB,MAAM;EAChD,MAAM,UAA0B,CAAC;EAGjC,MAAM,kBAAkB,MAAM,KAAK,WAAW,MAAM;EAGpD,MAAM,YAAY;EAClB,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;GACnD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,SAAS;GAC5C,MAAM,UAAU,CAAC;GAEjB,KAAK,MAAM,UAAU,OAAO;IAC3B,MAAM,WAAW,gBAAgB,MAC/B,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IACpD;IAEA,MAAM,gBAAgB,UAAU,SAAS;IAEzC,IAAI,YAAY,kBAAkB,OAAO,OAAO;KAE/C,QAAQ,KAAK;MACZ;MACA,SAAS;MACT,WAAW;KACZ,CAAC;KACD;IACD;IAGA,MAAM,aACL,OAAO,SAAS,MAAM,SAAS,GAAG,OAAO,KAAK,GAAG;IAElD,QAAQ,KAAK;KACZ,QAAQ;KACR,mBAAmB;MAClB,MAAM;MACN,MAAM,OAAO;MACb,KAAK,OAAO;MACZ,iBAAiB,CAAC,EAAE,OAAO,OAAO,MAAM,CAAC;KAC1C;IACD,CAAC;IAED,QAAQ,KAAK;KACZ;KACA,SAAS,CAAC;KACV,WAAW;IACZ,CAAC;GACF;GAGA,IAAI,QAAQ,SAAS,GAAG;IACvB,MAAM,UAAU,IAAI,gCAAgC;KACnD,cAAc;KACd,aAAa;MACZ,SAAS;MACT,SAAS;KACV;IACD,CAAC;IAED,MAAM,KAAK,OAAO,KAAK,OAAO;GAC/B;EACD;EAEA,OAAO;CACR;CAEA,MAAM,cACL,QACA,SAC0B;EAC1B,MAAM,SAAS,MAAM,KAAK,gBAAgB,MAAM;EAChD,MAAM,UAA0B,CAAC;EAGjC,MAAM,kBAAkB,MAAM,KAAK,WAAW,MAAM;EAGpD,MAAM,YAAY;EAClB,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;GACnD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,SAAS;GAC5C,MAAM,UAAU,CAAC;GAEjB,KAAK,MAAM,UAAU,OAAO;IAC3B,MAAM,WAAW,gBAAgB,MAC/B,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IACpD;IAEA,IAAI,CAAC,UAAU;KAEd,QAAQ,KAAK;MACZ;MACA,SAAS;MACT,UAAU;KACX,CAAC;KACD;IACD;IAGA,MAAM,aACL,OAAO,SAAS,MAAM,SAAS,GAAG,OAAO,KAAK,GAAG;IAElD,QAAQ,KAAK;KACZ,QAAQ;KACR,mBAAmB;MAClB,MAAM;MACN,MAAM,OAAO;MACb,KAAK,SAAS;MACd,iBAAiB,SAAS,OAAO,KAAK,OAAO,EAAE,OAAO,EAAE,EAAE;KAC3D;IACD,CAAC;IAED,QAAQ,KAAK;KACZ;KACA,SAAS;KACT,UAAU;IACX,CAAC;GACF;GAGA,IAAI,QAAQ,SAAS,GACpB,IAAI;IACH,MAAM,UAAU,IAAI,gCAAgC;KACnD,cAAc;KACd,aAAa;MACZ,SAAS;MACT,SAAS;KACV;IACD,CAAC;IAED,MAAM,KAAK,OAAO,KAAK,OAAO;GAC/B,SAAS,OAAO;IAEf,KAAK,MAAM,UAAU,QAAQ,MAAM,CAAC,QAAQ,MAAM,GACjD,IAAI,OAAO,SAAS;KACnB,OAAO,UAAU;KACjB,OAAO,QAAQ,OAAO,KAAK;IAC5B;GAEF;EAEF;EAEA,OAAO;CACR;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"Route53Provider-DVILBsvV.cjs","names":["Route53Client","fromIni","ListHostedZonesByNameCommand","ListResourceRecordSetsCommand","ChangeResourceRecordSetsCommand"],"sources":["../src/deploy/dns/Route53Provider.ts"],"sourcesContent":["/**\n * Route53 DNS Provider\n *\n * Implements DnsProvider interface using AWS Route53.\n */\n\nimport {\n\tChangeResourceRecordSetsCommand,\n\tListHostedZonesByNameCommand,\n\tListResourceRecordSetsCommand,\n\tRoute53Client,\n\ttype RRType,\n} from '@aws-sdk/client-route-53';\nimport { fromIni } from '@aws-sdk/credential-providers';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tDnsRecordType,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\n\nexport interface Route53ProviderOptions {\n\t/** AWS region (optional - uses AWS_REGION env var if not provided) */\n\tregion?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** Hosted zone ID (optional - auto-detected from domain if not provided) */\n\thostedZoneId?: string;\n\t/** Custom endpoint for testing with localstack */\n\tendpoint?: string;\n}\n\n/**\n * Route53 DNS provider implementation.\n *\n * Uses AWS default credential chain for authentication.\n * Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.\n * Profile can be specified to use a named profile from ~/.aws/credentials.\n */\nexport class Route53Provider implements DnsProvider {\n\treadonly name = 'route53';\n\tprivate client: Route53Client;\n\tprivate hostedZoneId?: string;\n\tprivate hostedZoneCache: Map<string, string> = new Map();\n\n\tconstructor(options: Route53ProviderOptions = {}) {\n\t\t// Route53 is a global service, default to us-east-1 if no region specified\n\t\tconst region = options.region ?? process.env.AWS_REGION ?? 'us-east-1';\n\n\t\tthis.client = new Route53Client({\n\t\t\tregion,\n\t\t\t...(options.endpoint && { endpoint: options.endpoint }),\n\t\t\t...(options.profile && {\n\t\t\t\tcredentials: fromIni({ profile: options.profile }),\n\t\t\t}),\n\t\t});\n\t\tthis.hostedZoneId = options.hostedZoneId;\n\t}\n\n\t/**\n\t * Get the hosted zone ID for a domain.\n\t * Uses cache to avoid repeated API calls.\n\t */\n\tprivate async getHostedZoneId(domain: string): Promise<string> {\n\t\t// Use configured zone ID if provided\n\t\tif (this.hostedZoneId) {\n\t\t\treturn this.hostedZoneId;\n\t\t}\n\n\t\t// Check cache\n\t\tif (this.hostedZoneCache.has(domain)) {\n\t\t\treturn this.hostedZoneCache.get(domain)!;\n\t\t}\n\n\t\t// Auto-detect from domain\n\t\tconst command = new ListHostedZonesByNameCommand({\n\t\t\tDNSName: domain,\n\t\t\tMaxItems: 1,\n\t\t});\n\n\t\tconst response = await this.client.send(command);\n\t\tconst zones = response.HostedZones ?? [];\n\n\t\t// Find exact match (domain with trailing dot)\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst zone = zones.find((z) => z.Name === normalizedDomain);\n\n\t\tif (!zone?.Id) {\n\t\t\tthrow new Error(\n\t\t\t\t`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`,\n\t\t\t);\n\t\t}\n\n\t\t// Zone ID comes as \"/hostedzone/Z1234567890\" - extract just the ID\n\t\tconst zoneId = zone.Id.replace('/hostedzone/', '');\n\t\tthis.hostedZoneCache.set(domain, zoneId);\n\t\treturn zoneId;\n\t}\n\n\t/**\n\t * Convert Route53 record type to our DnsRecordType.\n\t * Excludes NS and SOA which are auto-managed by Route53 for the zone.\n\t */\n\tprivate toRecordType(type: string): DnsRecordType | null {\n\t\t// Exclude NS and SOA which are auto-managed zone records\n\t\tconst managedTypes = ['NS', 'SOA'];\n\t\tif (managedTypes.includes(type)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst validTypes: DnsRecordType[] = [\n\t\t\t'A',\n\t\t\t'AAAA',\n\t\t\t'CNAME',\n\t\t\t'MX',\n\t\t\t'TXT',\n\t\t\t'SRV',\n\t\t\t'CAA',\n\t\t];\n\t\treturn validTypes.includes(type as DnsRecordType)\n\t\t\t? (type as DnsRecordType)\n\t\t\t: null;\n\t}\n\n\t/**\n\t * Extract subdomain from full record name relative to domain.\n\t */\n\tprivate extractSubdomain(recordName: string, domain: string): string {\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst normalizedName = recordName.endsWith('.')\n\t\t\t? recordName\n\t\t\t: `${recordName}.`;\n\n\t\tif (normalizedName === normalizedDomain) {\n\t\t\treturn '@';\n\t\t}\n\n\t\t// Remove the domain suffix\n\t\tconst subdomain = normalizedName.replace(`.${normalizedDomain}`, '');\n\t\treturn subdomain.replace(/\\.$/, ''); // Remove trailing dot if any\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst records: DnsRecord[] = [];\n\n\t\tlet nextRecordName: string | undefined;\n\t\tlet nextRecordType: RRType | undefined;\n\n\t\t// Paginate through all records\n\t\tdo {\n\t\t\tconst command = new ListResourceRecordSetsCommand({\n\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\tStartRecordName: nextRecordName,\n\t\t\t\tStartRecordType: nextRecordType,\n\t\t\t\tMaxItems: 100,\n\t\t\t});\n\n\t\t\tconst response = await this.client.send(command);\n\n\t\t\tfor (const recordSet of response.ResourceRecordSets ?? []) {\n\t\t\t\tconst type = this.toRecordType(recordSet.Type ?? '');\n\t\t\t\tif (!type || !recordSet.Name) continue;\n\n\t\t\t\tconst values = (recordSet.ResourceRecords ?? [])\n\t\t\t\t\t.map((r) => r.Value)\n\t\t\t\t\t.filter((v): v is string => !!v);\n\n\t\t\t\trecords.push({\n\t\t\t\t\tname: this.extractSubdomain(recordSet.Name, domain),\n\t\t\t\t\ttype,\n\t\t\t\t\tttl: recordSet.TTL ?? 300,\n\t\t\t\t\tvalues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (response.IsTruncated) {\n\t\t\t\tnextRecordName = response.NextRecordName;\n\t\t\t\tnextRecordType = response.NextRecordType;\n\t\t\t} else {\n\t\t\t\tnextRecordName = undefined;\n\t\t\t}\n\t\t} while (nextRecordName);\n\n\t\treturn records;\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to determine if creating or updating\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tconst existingValue = existing?.values?.[0];\n\n\t\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tcreated: false,\n\t\t\t\t\t\tunchanged: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'UPSERT' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: record.ttl,\n\t\t\t\t\t\tResourceRecords: [{ Value: record.value }],\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: !existing,\n\t\t\t\t\tunchanged: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\tComment: 'Upsert by gkm deploy',\n\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tawait this.client.send(command);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to find the ones to delete\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tif (!existing) {\n\t\t\t\t\t// Record doesn't exist - already deleted\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'DELETE' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: existing.ttl,\n\t\t\t\t\t\tResourceRecords: existing.values.map((v) => ({ Value: v })),\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: true,\n\t\t\t\t\tnotFound: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\ttry {\n\t\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\t\tComment: 'Delete by gkm undeploy',\n\t\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tawait this.client.send(command);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Mark all records in this batch as failed\n\t\t\t\t\tfor (const result of results.slice(-changes.length)) {\n\t\t\t\t\t\tif (result.deleted) {\n\t\t\t\t\t\t\tresult.deleted = false;\n\t\t\t\t\t\t\tresult.error = String(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"backup-provisioner-BmhIO_Yr.cjs","names":["S3Client","IAMClient","HeadBucketCommand","GetUserCommand","CreateBucketCommand","PutBucketVersioningCommand","CreateUserCommand","PutUserPolicyCommand","CreateAccessKeyCommand"],"sources":["../src/deploy/backup-provisioner.ts"],"sourcesContent":["/**\n * Backup Destination Provisioner\n *\n * Creates AWS resources (S3 bucket, IAM user, access keys) and configures\n * Dokploy backup destinations for database backups.\n */\n\nimport {\n\tCreateAccessKeyCommand,\n\tCreateUserCommand,\n\tGetUserCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tPutUserPolicyCommand,\n} from '@aws-sdk/client-iam';\nimport {\n\ttype BucketLocationConstraint,\n\tCreateBucketCommand,\n\tHeadBucketCommand,\n\tPutBucketVersioningCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { BackupsConfig } from '../workspace/types.js';\nimport type { DokployApi } from './dokploy-api.js';\nimport type { BackupState } from './state.js';\n\nexport interface ProvisionBackupOptions {\n\t/** Dokploy API client */\n\tapi: DokployApi;\n\t/** Dokploy project ID */\n\tprojectId: string;\n\t/** Workspace name (used for resource naming) */\n\tprojectName: string;\n\t/** Deploy stage (e.g., 'production', 'staging') */\n\tstage: string;\n\t/** Backup configuration */\n\tconfig: BackupsConfig;\n\t/** Existing backup state (if any) */\n\texistingState?: BackupState;\n\t/** Logger for progress output */\n\tlogger: { log: (msg: string) => void };\n\t/** AWS endpoint override (for testing with LocalStack) */\n\tawsEndpoint?: string;\n}\n\n/**\n * Generate a random suffix for unique resource names\n */\nfunction randomSuffix(): string {\n\treturn Math.random().toString(36).substring(2, 8);\n}\n\n/**\n * Sanitize a name for AWS resources (lowercase alphanumeric and hyphens)\n */\nfunction sanitizeName(name: string): string {\n\treturn name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n}\n\n/**\n * Create AWS clients with optional profile credentials\n */\nasync function createAwsClients(\n\tregion: string,\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<{ s3: S3Client; iam: IAMClient }> {\n\tconst config: S3ClientConfig & IAMClientConfig = { region };\n\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\n\t// Support custom endpoint for testing (e.g., LocalStack)\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\t(config as S3ClientConfig).forcePathStyle = true;\n\t\t// Use test credentials when endpoint is specified\n\t\tconfig.credentials = {\n\t\t\taccessKeyId: 'test',\n\t\t\tsecretAccessKey: 'test',\n\t\t};\n\t}\n\n\treturn {\n\t\ts3: new S3Client(config),\n\t\tiam: new IAMClient(config),\n\t};\n}\n\n/**\n * Check if an S3 bucket exists\n */\nasync function bucketExists(\n\ts3: S3Client,\n\tbucketName: string,\n): Promise<boolean> {\n\ttry {\n\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucketName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name === 'NotFound') {\n\t\t\treturn false;\n\t\t}\n\t\t// 403 means bucket exists but we don't have access\n\t\tif (\n\t\t\t(error as { $metadata?: { httpStatusCode?: number } }).$metadata\n\t\t\t\t?.httpStatusCode === 403\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if an IAM user exists\n */\nasync function userExists(iam: IAMClient, userName: string): Promise<boolean> {\n\ttry {\n\t\tawait iam.send(new GetUserCommand({ UserName: userName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tconst errorName = (error as { name?: string }).name;\n\t\t// AWS returns 'NoSuchEntity', LocalStack returns 'NoSuchEntityException'\n\t\tif (errorName === 'NoSuchEntity' || errorName === 'NoSuchEntityException') {\n\t\t\treturn false;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Provision backup destination for a deployment.\n *\n * Creates AWS resources (S3 bucket, IAM user) and Dokploy destination if needed.\n * Reuses existing resources from state when possible.\n */\nexport async function provisionBackupDestination(\n\toptions: ProvisionBackupOptions,\n): Promise<BackupState> {\n\tconst {\n\t\tapi,\n\t\tprojectName,\n\t\tstage,\n\t\tconfig,\n\t\texistingState,\n\t\tlogger,\n\t\tawsEndpoint,\n\t} = options;\n\n\t// If we have existing state, verify the Dokploy destination still exists\n\tif (existingState?.destinationId) {\n\t\ttry {\n\t\t\tawait api.getDestination(existingState.destinationId);\n\t\t\tlogger.log(' Using existing backup destination');\n\t\t\treturn existingState;\n\t\t} catch {\n\t\t\tlogger.log(' Existing destination not found, recreating...');\n\t\t}\n\t}\n\n\t// Create AWS clients\n\tconst aws = await createAwsClients(\n\t\tconfig.region,\n\t\tconfig.profile,\n\t\tawsEndpoint,\n\t);\n\tconst sanitizedProject = sanitizeName(projectName);\n\n\t// 1. Create or verify S3 bucket\n\tconst bucketName =\n\t\texistingState?.bucketName ??\n\t\t`${sanitizedProject}-${stage}-backups-${randomSuffix()}`;\n\n\tconst bucketAlreadyExists = await bucketExists(aws.s3, bucketName);\n\tif (!bucketAlreadyExists) {\n\t\tlogger.log(` Creating S3 bucket: ${bucketName}`);\n\n\t\t// CreateBucket needs LocationConstraint for non-us-east-1 regions\n\t\tconst createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t} = {\n\t\t\tBucket: bucketName,\n\t\t};\n\t\tif (config.region !== 'us-east-1') {\n\t\t\tcreateBucketParams.CreateBucketConfiguration = {\n\t\t\t\tLocationConstraint: config.region as BucketLocationConstraint,\n\t\t\t};\n\t\t}\n\n\t\tawait aws.s3.send(new CreateBucketCommand(createBucketParams));\n\n\t\t// Enable versioning for backup integrity\n\t\tawait aws.s3.send(\n\t\t\tnew PutBucketVersioningCommand({\n\t\t\t\tBucket: bucketName,\n\t\t\t\tVersioningConfiguration: { Status: 'Enabled' },\n\t\t\t}),\n\t\t);\n\t} else {\n\t\tlogger.log(` Using existing S3 bucket: ${bucketName}`);\n\t}\n\n\t// 2. Create or verify IAM user\n\tconst iamUserName =\n\t\texistingState?.iamUserName ?? `dokploy-backup-${sanitizedProject}-${stage}`;\n\n\tconst iamUserAlreadyExists = await userExists(aws.iam, iamUserName);\n\tif (!iamUserAlreadyExists) {\n\t\tlogger.log(` Creating IAM user: ${iamUserName}`);\n\t\tawait aws.iam.send(new CreateUserCommand({ UserName: iamUserName }));\n\t} else {\n\t\tlogger.log(` Using existing IAM user: ${iamUserName}`);\n\t}\n\n\t// 3. Attach bucket policy to IAM user\n\tconst policyDocument = {\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: [\n\t\t\t\t\t's3:GetObject',\n\t\t\t\t\t's3:PutObject',\n\t\t\t\t\t's3:DeleteObject',\n\t\t\t\t\t's3:ListBucket',\n\t\t\t\t\t's3:GetBucketLocation',\n\t\t\t\t],\n\t\t\t\tResource: [\n\t\t\t\t\t`arn:aws:s3:::${bucketName}`,\n\t\t\t\t\t`arn:aws:s3:::${bucketName}/*`,\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tlogger.log(' Updating IAM policy');\n\tawait aws.iam.send(\n\t\tnew PutUserPolicyCommand({\n\t\t\tUserName: iamUserName,\n\t\t\tPolicyName: 'DokployBackupAccess',\n\t\t\tPolicyDocument: JSON.stringify(policyDocument),\n\t\t}),\n\t);\n\n\t// 4. Create access key (or reuse existing if state has it and destination needs recreation)\n\tlet accessKeyId: string;\n\tlet secretAccessKey: string;\n\n\tif (existingState?.iamAccessKeyId && existingState?.iamSecretAccessKey) {\n\t\t// Reuse existing credentials\n\t\tlogger.log(' Using existing IAM access key');\n\t\taccessKeyId = existingState.iamAccessKeyId;\n\t\tsecretAccessKey = existingState.iamSecretAccessKey;\n\t} else {\n\t\t// Create new access key\n\t\tlogger.log(' Creating IAM access key');\n\t\tconst accessKeyResult = await aws.iam.send(\n\t\t\tnew CreateAccessKeyCommand({ UserName: iamUserName }),\n\t\t);\n\n\t\tif (!accessKeyResult.AccessKey) {\n\t\t\tthrow new Error('Failed to create IAM access key');\n\t\t}\n\n\t\taccessKeyId = accessKeyResult.AccessKey.AccessKeyId!;\n\t\tsecretAccessKey = accessKeyResult.AccessKey.SecretAccessKey!;\n\t}\n\n\t// 5. Create Dokploy destination\n\tconst destinationName = `${sanitizedProject}-${stage}-s3`;\n\tlogger.log(` Creating Dokploy destination: ${destinationName}`);\n\n\tconst { destination, created } = await api.findOrCreateDestination(\n\t\tdestinationName,\n\t\t{\n\t\t\taccessKey: accessKeyId,\n\t\t\tsecretAccessKey: secretAccessKey,\n\t\t\tbucket: bucketName,\n\t\t\tregion: config.region,\n\t\t},\n\t);\n\n\tif (created) {\n\t\tlogger.log(' ✓ Dokploy destination created');\n\t} else {\n\t\tlogger.log(' ✓ Using existing Dokploy destination');\n\t}\n\n\t// 6. Test connection\n\ttry {\n\t\tawait api.testDestinationConnection(destination.destinationId);\n\t\tlogger.log(' ✓ Destination connection verified');\n\t} catch (error) {\n\t\tlogger.log(\n\t\t\t` ⚠ Warning: Could not verify destination connection: ${error}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tbucketName,\n\t\tbucketArn: `arn:aws:s3:::${bucketName}`,\n\t\tiamUserName,\n\t\tiamAccessKeyId: accessKeyId,\n\t\tiamSecretAccessKey: secretAccessKey,\n\t\tdestinationId: destination.destinationId,\n\t\tregion: config.region,\n\t\tcreatedAt: existingState?.createdAt ?? new Date().toISOString(),\n\t};\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"backup-provisioner-DIVCbfee.mjs","names":[],"sources":["../src/deploy/backup-provisioner.ts"],"sourcesContent":["/**\n * Backup Destination Provisioner\n *\n * Creates AWS resources (S3 bucket, IAM user, access keys) and configures\n * Dokploy backup destinations for database backups.\n */\n\nimport {\n\tCreateAccessKeyCommand,\n\tCreateUserCommand,\n\tGetUserCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tPutUserPolicyCommand,\n} from '@aws-sdk/client-iam';\nimport {\n\ttype BucketLocationConstraint,\n\tCreateBucketCommand,\n\tHeadBucketCommand,\n\tPutBucketVersioningCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { BackupsConfig } from '../workspace/types.js';\nimport type { DokployApi } from './dokploy-api.js';\nimport type { BackupState } from './state.js';\n\nexport interface ProvisionBackupOptions {\n\t/** Dokploy API client */\n\tapi: DokployApi;\n\t/** Dokploy project ID */\n\tprojectId: string;\n\t/** Workspace name (used for resource naming) */\n\tprojectName: string;\n\t/** Deploy stage (e.g., 'production', 'staging') */\n\tstage: string;\n\t/** Backup configuration */\n\tconfig: BackupsConfig;\n\t/** Existing backup state (if any) */\n\texistingState?: BackupState;\n\t/** Logger for progress output */\n\tlogger: { log: (msg: string) => void };\n\t/** AWS endpoint override (for testing with LocalStack) */\n\tawsEndpoint?: string;\n}\n\n/**\n * Generate a random suffix for unique resource names\n */\nfunction randomSuffix(): string {\n\treturn Math.random().toString(36).substring(2, 8);\n}\n\n/**\n * Sanitize a name for AWS resources (lowercase alphanumeric and hyphens)\n */\nfunction sanitizeName(name: string): string {\n\treturn name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n}\n\n/**\n * Create AWS clients with optional profile credentials\n */\nasync function createAwsClients(\n\tregion: string,\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<{ s3: S3Client; iam: IAMClient }> {\n\tconst config: S3ClientConfig & IAMClientConfig = { region };\n\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\n\t// Support custom endpoint for testing (e.g., LocalStack)\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\t(config as S3ClientConfig).forcePathStyle = true;\n\t\t// Use test credentials when endpoint is specified\n\t\tconfig.credentials = {\n\t\t\taccessKeyId: 'test',\n\t\t\tsecretAccessKey: 'test',\n\t\t};\n\t}\n\n\treturn {\n\t\ts3: new S3Client(config),\n\t\tiam: new IAMClient(config),\n\t};\n}\n\n/**\n * Check if an S3 bucket exists\n */\nasync function bucketExists(\n\ts3: S3Client,\n\tbucketName: string,\n): Promise<boolean> {\n\ttry {\n\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucketName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name === 'NotFound') {\n\t\t\treturn false;\n\t\t}\n\t\t// 403 means bucket exists but we don't have access\n\t\tif (\n\t\t\t(error as { $metadata?: { httpStatusCode?: number } }).$metadata\n\t\t\t\t?.httpStatusCode === 403\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if an IAM user exists\n */\nasync function userExists(iam: IAMClient, userName: string): Promise<boolean> {\n\ttry {\n\t\tawait iam.send(new GetUserCommand({ UserName: userName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tconst errorName = (error as { name?: string }).name;\n\t\t// AWS returns 'NoSuchEntity', LocalStack returns 'NoSuchEntityException'\n\t\tif (errorName === 'NoSuchEntity' || errorName === 'NoSuchEntityException') {\n\t\t\treturn false;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Provision backup destination for a deployment.\n *\n * Creates AWS resources (S3 bucket, IAM user) and Dokploy destination if needed.\n * Reuses existing resources from state when possible.\n */\nexport async function provisionBackupDestination(\n\toptions: ProvisionBackupOptions,\n): Promise<BackupState> {\n\tconst {\n\t\tapi,\n\t\tprojectName,\n\t\tstage,\n\t\tconfig,\n\t\texistingState,\n\t\tlogger,\n\t\tawsEndpoint,\n\t} = options;\n\n\t// If we have existing state, verify the Dokploy destination still exists\n\tif (existingState?.destinationId) {\n\t\ttry {\n\t\t\tawait api.getDestination(existingState.destinationId);\n\t\t\tlogger.log(' Using existing backup destination');\n\t\t\treturn existingState;\n\t\t} catch {\n\t\t\tlogger.log(' Existing destination not found, recreating...');\n\t\t}\n\t}\n\n\t// Create AWS clients\n\tconst aws = await createAwsClients(\n\t\tconfig.region,\n\t\tconfig.profile,\n\t\tawsEndpoint,\n\t);\n\tconst sanitizedProject = sanitizeName(projectName);\n\n\t// 1. Create or verify S3 bucket\n\tconst bucketName =\n\t\texistingState?.bucketName ??\n\t\t`${sanitizedProject}-${stage}-backups-${randomSuffix()}`;\n\n\tconst bucketAlreadyExists = await bucketExists(aws.s3, bucketName);\n\tif (!bucketAlreadyExists) {\n\t\tlogger.log(` Creating S3 bucket: ${bucketName}`);\n\n\t\t// CreateBucket needs LocationConstraint for non-us-east-1 regions\n\t\tconst createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t} = {\n\t\t\tBucket: bucketName,\n\t\t};\n\t\tif (config.region !== 'us-east-1') {\n\t\t\tcreateBucketParams.CreateBucketConfiguration = {\n\t\t\t\tLocationConstraint: config.region as BucketLocationConstraint,\n\t\t\t};\n\t\t}\n\n\t\tawait aws.s3.send(new CreateBucketCommand(createBucketParams));\n\n\t\t// Enable versioning for backup integrity\n\t\tawait aws.s3.send(\n\t\t\tnew PutBucketVersioningCommand({\n\t\t\t\tBucket: bucketName,\n\t\t\t\tVersioningConfiguration: { Status: 'Enabled' },\n\t\t\t}),\n\t\t);\n\t} else {\n\t\tlogger.log(` Using existing S3 bucket: ${bucketName}`);\n\t}\n\n\t// 2. Create or verify IAM user\n\tconst iamUserName =\n\t\texistingState?.iamUserName ?? `dokploy-backup-${sanitizedProject}-${stage}`;\n\n\tconst iamUserAlreadyExists = await userExists(aws.iam, iamUserName);\n\tif (!iamUserAlreadyExists) {\n\t\tlogger.log(` Creating IAM user: ${iamUserName}`);\n\t\tawait aws.iam.send(new CreateUserCommand({ UserName: iamUserName }));\n\t} else {\n\t\tlogger.log(` Using existing IAM user: ${iamUserName}`);\n\t}\n\n\t// 3. Attach bucket policy to IAM user\n\tconst policyDocument = {\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: [\n\t\t\t\t\t's3:GetObject',\n\t\t\t\t\t's3:PutObject',\n\t\t\t\t\t's3:DeleteObject',\n\t\t\t\t\t's3:ListBucket',\n\t\t\t\t\t's3:GetBucketLocation',\n\t\t\t\t],\n\t\t\t\tResource: [\n\t\t\t\t\t`arn:aws:s3:::${bucketName}`,\n\t\t\t\t\t`arn:aws:s3:::${bucketName}/*`,\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tlogger.log(' Updating IAM policy');\n\tawait aws.iam.send(\n\t\tnew PutUserPolicyCommand({\n\t\t\tUserName: iamUserName,\n\t\t\tPolicyName: 'DokployBackupAccess',\n\t\t\tPolicyDocument: JSON.stringify(policyDocument),\n\t\t}),\n\t);\n\n\t// 4. Create access key (or reuse existing if state has it and destination needs recreation)\n\tlet accessKeyId: string;\n\tlet secretAccessKey: string;\n\n\tif (existingState?.iamAccessKeyId && existingState?.iamSecretAccessKey) {\n\t\t// Reuse existing credentials\n\t\tlogger.log(' Using existing IAM access key');\n\t\taccessKeyId = existingState.iamAccessKeyId;\n\t\tsecretAccessKey = existingState.iamSecretAccessKey;\n\t} else {\n\t\t// Create new access key\n\t\tlogger.log(' Creating IAM access key');\n\t\tconst accessKeyResult = await aws.iam.send(\n\t\t\tnew CreateAccessKeyCommand({ UserName: iamUserName }),\n\t\t);\n\n\t\tif (!accessKeyResult.AccessKey) {\n\t\t\tthrow new Error('Failed to create IAM access key');\n\t\t}\n\n\t\taccessKeyId = accessKeyResult.AccessKey.AccessKeyId!;\n\t\tsecretAccessKey = accessKeyResult.AccessKey.SecretAccessKey!;\n\t}\n\n\t// 5. Create Dokploy destination\n\tconst destinationName = `${sanitizedProject}-${stage}-s3`;\n\tlogger.log(` Creating Dokploy destination: ${destinationName}`);\n\n\tconst { destination, created } = await api.findOrCreateDestination(\n\t\tdestinationName,\n\t\t{\n\t\t\taccessKey: accessKeyId,\n\t\t\tsecretAccessKey: secretAccessKey,\n\t\t\tbucket: bucketName,\n\t\t\tregion: config.region,\n\t\t},\n\t);\n\n\tif (created) {\n\t\tlogger.log(' ✓ Dokploy destination created');\n\t} else {\n\t\tlogger.log(' ✓ Using existing Dokploy destination');\n\t}\n\n\t// 6. Test connection\n\ttry {\n\t\tawait api.testDestinationConnection(destination.destinationId);\n\t\tlogger.log(' ✓ Destination connection verified');\n\t} catch (error) {\n\t\tlogger.log(\n\t\t\t` ⚠ Warning: Could not verify destination connection: ${error}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tbucketName,\n\t\tbucketArn: `arn:aws:s3:::${bucketName}`,\n\t\tiamUserName,\n\t\tiamAccessKeyId: accessKeyId,\n\t\tiamSecretAccessKey: secretAccessKey,\n\t\tdestinationId: destination.destinationId,\n\t\tregion: config.region,\n\t\tcreatedAt: existingState?.createdAt ?? new Date().toISOString(),\n\t};\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli-BfH1deAT.cjs","names":["targetForProvider","formatError","deploy","storedCredentials","terminalCredentials"],"sources":["../src/deploy/cli.ts"],"sourcesContent":["/**\n * `gkm deploy`: the terminal around `deploy()`. CLI layer only.\n *\n * Everything that belongs to a person at a terminal lives here and nowhere\n * below it: prompting for a missing login (and storing it), printing progress,\n * and turning the outcome into an exit code. The deploy itself is the same\n * `deploy()` a host calls.\n */\n\nimport { formatError } from '../debug';\nimport { targetForProvider } from '../target/provider';\nimport { storedCredentials } from './credentials';\nimport { deploy } from './deploy';\nimport { terminalCredentials } from './terminal';\n\nexport interface DeployCliOptions {\n\t/** The project directory: `--cwd`, else where the command was run. */\n\tcwd: string;\n\t/** `--target`: where to deploy. Defaults to `deploy.default`. */\n\ttarget?: string;\n\t/** `--provider`, deprecated: `dokploy` means `--target dokploy`. */\n\tprovider?: string;\n\tstage: string;\n\ttag?: string;\n\t/**\n\t * Print each event as one JSON object per line on stdout, instead of the\n\t * progress lines. Nothing prompts: a missing credential fails the run.\n\t */\n\tjson?: boolean;\n\t/** Plan only: create, change, build and push nothing. */\n\tdryRun?: boolean;\n}\n\n/** Where the command writes. The process's own streams, outside tests. */\nexport interface DeployCliStreams {\n\tstdout: Pick<NodeJS.WriteStream, 'write'>;\n}\n\n/**\n * Run `gkm deploy` and resolve with its exit code: 0 when the run finished,\n * 1 when anything stopped it.\n */\nexport async function deployCli(\n\toptions: DeployCliOptions,\n\tstreams: DeployCliStreams = { stdout: process.stdout },\n): Promise<number> {\n\tlet target = options.target;\n\tif (options.provider !== undefined && target === undefined) {\n\t\ttry {\n\t\t\t// On stderr, so a `--json` run's stdout still carries only events.\n\t\t\ttarget = targetForProvider(options.provider, (message) =>\n\t\t\t\tconsole.warn(message),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(formatError(error));\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tconst input = {\n\t\tcwd: options.cwd,\n\t\tstage: options.stage,\n\t\t...(target ? { target } : {}),\n\t\t...(options.tag ? { tag: options.tag } : {}),\n\t\t...(options.dryRun ? { dryRun: true } : {}),\n\t};\n\n\tif (options.json) {\n\t\t// stdout carries the events and nothing else: docker's own output goes\n\t\t// to stderr, and nobody is asked anything — a prompt would be written\n\t\t// into the stream a program is parsing.\n\t\tconst run = deploy({\n\t\t\t...input,\n\t\t\tcredentials: storedCredentials(),\n\t\t\tchildOutput: 'stderr',\n\t\t});\n\t\tfor await (const event of run) {\n\t\t\tstreams.stdout.write(`${JSON.stringify(event)}\\n`);\n\t\t}\n\t\treturn run.result.then(\n\t\t\t() => 0,\n\t\t\t(error) => {\n\t\t\t\tconsole.error(formatError(error));\n\t\t\t\treturn 1;\n\t\t\t},\n\t\t);\n\t}\n\n\t// The lines arrive as they are written, rather than as events read back a\n\t// tick later, so they interleave with a prompt exactly as they always did.\n\tconst run = deploy({\n\t\t...input,\n\t\tcredentials: terminalCredentials(storedCredentials()),\n\t\tlogger: {\n\t\t\tinfo: (message) => console.log(message),\n\t\t\twarn: (message) => console.warn(message),\n\t\t\terror: (message) => console.error(message),\n\t\t},\n\t});\n\n\ttry {\n\t\tawait run.result;\n\t\treturn 0;\n\t} catch (error) {\n\t\tconsole.error(formatError(error));\n\t\treturn 1;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA0CA,eAAsB,UACrB,SACA,UAA4B,EAAE,QAAQ,QAAQ,OAAO,GACnC;CAClB,IAAI,SAAS,QAAQ;CACrB,IAAI,QAAQ,aAAa,KAAA,KAAa,WAAW,KAAA,GAChD,IAAI;EAEH,SAASA,iBAAAA,kBAAkB,QAAQ,WAAW,YAC7C,QAAQ,KAAK,OAAO,CACrB;CACD,SAAS,OAAO;EACf,QAAQ,MAAMC,cAAAA,YAAY,KAAK,CAAC;EAChC,OAAO;CACR;CAGD,MAAM,QAAQ;EACb,KAAK,QAAQ;EACb,OAAO,QAAQ;EACf,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC3B,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;EAC1C,GAAI,QAAQ,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC1C;CAEA,IAAI,QAAQ,MAAM;EAIjB,MAAM,MAAMC,eAAAA,OAAO;GAClB,GAAG;GACH,aAAaC,oBAAAA,kBAAkB;GAC/B,aAAa;EACd,CAAC;EACD,WAAW,MAAM,SAAS,KACzB,QAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,KAAK,EAAE,GAAG;EAElD,OAAO,IAAI,OAAO,WACX,IACL,UAAU;GACV,QAAQ,MAAMF,cAAAA,YAAY,KAAK,CAAC;GAChC,OAAO;EACR,CACD;CACD;CAIA,MAAM,MAAMC,eAAAA,OAAO;EAClB,GAAG;EACH,aAAaE,iBAAAA,oBAAoBD,oBAAAA,kBAAkB,CAAC;EACpD,QAAQ;GACP,OAAO,YAAY,QAAQ,IAAI,OAAO;GACtC,OAAO,YAAY,QAAQ,KAAK,OAAO;GACvC,QAAQ,YAAY,QAAQ,MAAM,OAAO;EAC1C;CACD,CAAC;CAED,IAAI;EACH,MAAM,IAAI;EACV,OAAO;CACR,SAAS,OAAO;EACf,QAAQ,MAAMF,cAAAA,YAAY,KAAK,CAAC;EAChC,OAAO;CACR;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli-P56vvrDQ.mjs","names":[],"sources":["../src/deploy/cli.ts"],"sourcesContent":["/**\n * `gkm deploy`: the terminal around `deploy()`. CLI layer only.\n *\n * Everything that belongs to a person at a terminal lives here and nowhere\n * below it: prompting for a missing login (and storing it), printing progress,\n * and turning the outcome into an exit code. The deploy itself is the same\n * `deploy()` a host calls.\n */\n\nimport { formatError } from '../debug';\nimport { targetForProvider } from '../target/provider';\nimport { storedCredentials } from './credentials';\nimport { deploy } from './deploy';\nimport { terminalCredentials } from './terminal';\n\nexport interface DeployCliOptions {\n\t/** The project directory: `--cwd`, else where the command was run. */\n\tcwd: string;\n\t/** `--target`: where to deploy. Defaults to `deploy.default`. */\n\ttarget?: string;\n\t/** `--provider`, deprecated: `dokploy` means `--target dokploy`. */\n\tprovider?: string;\n\tstage: string;\n\ttag?: string;\n\t/**\n\t * Print each event as one JSON object per line on stdout, instead of the\n\t * progress lines. Nothing prompts: a missing credential fails the run.\n\t */\n\tjson?: boolean;\n\t/** Plan only: create, change, build and push nothing. */\n\tdryRun?: boolean;\n}\n\n/** Where the command writes. The process's own streams, outside tests. */\nexport interface DeployCliStreams {\n\tstdout: Pick<NodeJS.WriteStream, 'write'>;\n}\n\n/**\n * Run `gkm deploy` and resolve with its exit code: 0 when the run finished,\n * 1 when anything stopped it.\n */\nexport async function deployCli(\n\toptions: DeployCliOptions,\n\tstreams: DeployCliStreams = { stdout: process.stdout },\n): Promise<number> {\n\tlet target = options.target;\n\tif (options.provider !== undefined && target === undefined) {\n\t\ttry {\n\t\t\t// On stderr, so a `--json` run's stdout still carries only events.\n\t\t\ttarget = targetForProvider(options.provider, (message) =>\n\t\t\t\tconsole.warn(message),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(formatError(error));\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tconst input = {\n\t\tcwd: options.cwd,\n\t\tstage: options.stage,\n\t\t...(target ? { target } : {}),\n\t\t...(options.tag ? { tag: options.tag } : {}),\n\t\t...(options.dryRun ? { dryRun: true } : {}),\n\t};\n\n\tif (options.json) {\n\t\t// stdout carries the events and nothing else: docker's own output goes\n\t\t// to stderr, and nobody is asked anything — a prompt would be written\n\t\t// into the stream a program is parsing.\n\t\tconst run = deploy({\n\t\t\t...input,\n\t\t\tcredentials: storedCredentials(),\n\t\t\tchildOutput: 'stderr',\n\t\t});\n\t\tfor await (const event of run) {\n\t\t\tstreams.stdout.write(`${JSON.stringify(event)}\\n`);\n\t\t}\n\t\treturn run.result.then(\n\t\t\t() => 0,\n\t\t\t(error) => {\n\t\t\t\tconsole.error(formatError(error));\n\t\t\t\treturn 1;\n\t\t\t},\n\t\t);\n\t}\n\n\t// The lines arrive as they are written, rather than as events read back a\n\t// tick later, so they interleave with a prompt exactly as they always did.\n\tconst run = deploy({\n\t\t...input,\n\t\tcredentials: terminalCredentials(storedCredentials()),\n\t\tlogger: {\n\t\t\tinfo: (message) => console.log(message),\n\t\t\twarn: (message) => console.warn(message),\n\t\t\terror: (message) => console.error(message),\n\t\t},\n\t});\n\n\ttry {\n\t\tawait run.result;\n\t\treturn 0;\n\t} catch (error) {\n\t\tconsole.error(formatError(error));\n\t\treturn 1;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA0CA,eAAsB,UACrB,SACA,UAA4B,EAAE,QAAQ,QAAQ,OAAO,GACnC;CAClB,IAAI,SAAS,QAAQ;CACrB,IAAI,QAAQ,aAAa,KAAA,KAAa,WAAW,KAAA,GAChD,IAAI;EAEH,SAAS,kBAAkB,QAAQ,WAAW,YAC7C,QAAQ,KAAK,OAAO,CACrB;CACD,SAAS,OAAO;EACf,QAAQ,MAAM,YAAY,KAAK,CAAC;EAChC,OAAO;CACR;CAGD,MAAM,QAAQ;EACb,KAAK,QAAQ;EACb,OAAO,QAAQ;EACf,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC3B,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;EAC1C,GAAI,QAAQ,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC1C;CAEA,IAAI,QAAQ,MAAM;EAIjB,MAAM,MAAM,OAAO;GAClB,GAAG;GACH,aAAa,kBAAkB;GAC/B,aAAa;EACd,CAAC;EACD,WAAW,MAAM,SAAS,KACzB,QAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,KAAK,EAAE,GAAG;EAElD,OAAO,IAAI,OAAO,WACX,IACL,UAAU;GACV,QAAQ,MAAM,YAAY,KAAK,CAAC;GAChC,OAAO;EACR,CACD;CACD;CAIA,MAAM,MAAM,OAAO;EAClB,GAAG;EACH,aAAa,oBAAoB,kBAAkB,CAAC;EACpD,QAAQ;GACP,OAAO,YAAY,QAAQ,IAAI,OAAO;GACtC,OAAO,YAAY,QAAQ,KAAK,OAAO;GACvC,QAAQ,YAAY,QAAQ,MAAM,OAAO;EAC1C;CACD,CAAC;CAED,IAAI;EACH,MAAM,IAAI;EACV,OAAO;CACR,SAAS,OAAO;EACf,QAAQ,MAAM,YAAY,KAAK,CAAC;EAChC,OAAO;CACR;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compose-391oEKj1.cjs","names":["spawn","run","runOutput","mkdir","dirname","planFor","provisionOrder","DEFAULT_EVENTS","DEFAULT_CACHE","localRolePassword","networkEnv","provideKey","publicEnvFor","appEnvKeys","siteDockerfile","generateSlimDockerfile","appKey","validateImageRef","imageRef","hostFor","resolveHost","isMainFrontendApp","composeFor","portKeys","DEFAULT_IMAGES","readdir","extname","join","pathToFileURL","stat","readFile","join","migrationTargets","MIGRATIONS_ROOT","canonicalId","readdir","DATABASE_FOLDERS","provideKey","require","createRequire","pathToFileURL","request","runOutput","dirname","fileURLToPath","join","existsSync","activeSandbox","LocalSandbox","pgClient","discover","constructGlobs","detectPackageManager","appPackageName","withGeneratedSecrets","initStageSecrets","postgresStatements","applyPostgres","envFor","primaryPortKey","DeployJournal","createEmptyState","readFile","mkdir","chmod","writeFile","generateDockerignore","appendFile","stringify","defineTarget"],"sources":["../src/compose/docker.ts","../src/compose/images.ts","../src/compose/caddyfile.ts","../src/compose/stack.ts","../src/migrate/provider.ts","../src/migrate/databases.ts","../src/target/compose/health.ts","../src/target/compose/phases.ts","../src/target/compose/index.ts"],"sourcesContent":["/**\n * The Docker operations `gkm compose` needs, behind an interface.\n *\n * Small on purpose, like reconcile's: the rules about *when* each runs — the\n * registry is asked before anything is pulled, nothing is started for a tag\n * that is missing — are asserted against a fake, and this file is only the\n * wiring to the real CLI.\n *\n * Every call is an argument array. Image refs, tags and paths come from the\n * workspace, and none of them reaches a shell.\n */\n\nimport { spawn } from 'node:child_process';\nimport { mkdir } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { type RunOptions, run, runOutput } from '../run';\n\n/** Where one stack lives: its compose project and the file that defines it. */\nexport interface StackRef {\n\tproject: string;\n\tfile: string;\n\t/** Where relative paths in the file resolve — the workspace root. */\n\tcwd: string;\n\t/**\n\t * Where docker's own output goes: the terminal (the default), stderr — so\n\t * a `--json` run's stdout carries only events — or nowhere.\n\t */\n\toutput?: 'inherit' | 'stderr' | 'ignore';\n\t/** Stops whatever docker is doing for the stack. */\n\tsignal?: AbortSignal;\n}\n\n/** `output` as `spawn` reads it. */\nconst STDIO: Record<NonNullable<StackRef['output']>, RunOptions['stdio']> = {\n\tinherit: 'inherit',\n\tstderr: ['ignore', 2, 2],\n\tignore: 'ignore',\n};\n\n/** How a stack's docker commands run: where it is, where output goes. */\nfunction runOptions(stack: StackRef, env?: NodeJS.ProcessEnv): RunOptions {\n\treturn {\n\t\tcwd: stack.cwd,\n\t\tstdio: STDIO[stack.output ?? 'inherit'],\n\t\t...(env ? { env } : {}),\n\t\t...(stack.signal ? { signal: stack.signal } : {}),\n\t};\n}\n\n/** What the registry said about one image ref. */\nexport type ImageLookup =\n\t| { ref: string; status: 'found' }\n\t| { ref: string; status: 'missing' }\n\t| { ref: string; status: 'unreachable'; detail: string };\n\nexport interface ComposeDocker {\n\t/** Ask the registry whether `ref` exists, without pulling it. */\n\tlookup(ref: string): Promise<ImageLookup>;\n\t/** Build every service with a `build:` entry. */\n\tbuild(stack: StackRef, services: readonly string[]): Promise<void>;\n\t/** Pull the named services' images. */\n\tpull(stack: StackRef, services: readonly string[]): Promise<void>;\n\t/**\n\t * Start services, detached, and wait for their health checks. With no\n\t * services named, the whole stack — and anything it no longer defines is\n\t * removed.\n\t */\n\tup(stack: StackRef, services?: readonly string[]): Promise<void>;\n\t/** Stop the stack. Its volumes are kept. */\n\tdown(stack: StackRef): Promise<void>;\n\t/** The host port a running service publishes for `inside`. */\n\tport(stack: StackRef, service: string, inside: number): Promise<number>;\n\t/** Copy a file out of a running service. */\n\tcopyOut(\n\t\tstack: StackRef,\n\t\tservice: string,\n\t\tfrom: string,\n\t\tto: string,\n\t): Promise<void>;\n\t/**\n\t * What an image resolved to: the registry digest a pulled image has, or\n\t * the content id of one built here and never pushed.\n\t */\n\tdigest(ref: string): Promise<string | undefined>;\n}\n\n/** How long a stack has to pass its health checks. */\nconst WAIT_TIMEOUT_S = 180;\n\n/** `docker compose port` printed nothing a port can be read from. */\nexport class ServicePortUnknown extends Error {\n\tconstructor(\n\t\treadonly service: string,\n\t\treadonly inside: number,\n\t\treadonly output: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${service}' publishes no host port for ${inside} (docker said: ${output.trim() || 'nothing'}). Is it running? \\`docker compose ps\\` shows the stack.`,\n\t\t);\n\t\tthis.name = 'ServicePortUnknown';\n\t}\n}\n\nfunction compose(stack: StackRef, args: readonly string[]): string[] {\n\treturn ['compose', '-p', stack.project, '-f', stack.file, ...args];\n}\n\n/** Run a program and keep everything it said, whatever it exits with. */\nfunction capture(\n\tcommand: string,\n\targs: readonly string[],\n\ttimeoutMs = 60_000,\n): Promise<{ code: number | null; stdout: string; stderr: string }> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst child = spawn(command, [...args], {\n\t\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\t\tshell: false,\n\t\t});\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\t\tchild.stdout.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr.on('data', (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\t\tconst timer = setTimeout(() => child.kill('SIGKILL'), timeoutMs);\n\t\ttimer.unref?.();\n\t\tchild.on('error', (error) => {\n\t\t\tclearTimeout(timer);\n\t\t\treject(error);\n\t\t});\n\t\tchild.on('close', (code) => {\n\t\t\tclearTimeout(timer);\n\t\t\tresolve({ code, stdout, stderr });\n\t\t});\n\t});\n}\n\n/**\n * Whether a failed `docker manifest inspect` means the tag is not there, as\n * opposed to the registry not answering.\n *\n * Read off the message because that is all the CLI gives. Anything not\n * recognisably \"no such tag\" is treated as the registry being unreachable:\n * a registry that refused credentials must not be reported as a tag that was\n * never pushed, or the fix offered would be the wrong one.\n */\nexport function isMissingManifest(output: string): boolean {\n\treturn /no such manifest|manifest unknown|manifest for .* not found|not found: manifest/i.test(\n\t\toutput,\n\t);\n}\n\nexport const dockerCompose: ComposeDocker = {\n\tasync lookup(ref) {\n\t\tconst { code, stdout, stderr } = await capture('docker', [\n\t\t\t'manifest',\n\t\t\t'inspect',\n\t\t\tref,\n\t\t]);\n\t\tif (code === 0) return { ref, status: 'found' };\n\n\t\tconst output = `${stderr}\\n${stdout}`.trim();\n\t\treturn isMissingManifest(output)\n\t\t\t? { ref, status: 'missing' }\n\t\t\t: { ref, status: 'unreachable', detail: output };\n\t},\n\n\tasync build(stack, services) {\n\t\tawait run(\n\t\t\t'docker',\n\t\t\tcompose(stack, ['build', ...services]),\n\t\t\trunOptions(stack, { ...process.env, DOCKER_BUILDKIT: '1' }),\n\t\t);\n\t},\n\n\tasync pull(stack, services) {\n\t\tawait run(\n\t\t\t'docker',\n\t\t\tcompose(stack, ['pull', ...services]),\n\t\t\trunOptions(stack),\n\t\t);\n\t},\n\n\tasync up(stack, services) {\n\t\tawait run(\n\t\t\t'docker',\n\t\t\tcompose(stack, [\n\t\t\t\t'up',\n\t\t\t\t'-d',\n\t\t\t\t'--wait',\n\t\t\t\t`--wait-timeout=${WAIT_TIMEOUT_S}`,\n\t\t\t\t// Only when the whole stack is asked for: starting the databases\n\t\t\t\t// first must not remove the apps an earlier run left running.\n\t\t\t\t...(services ? [] : ['--remove-orphans']),\n\t\t\t\t...(services ?? []),\n\t\t\t]),\n\t\t\trunOptions(stack),\n\t\t);\n\t},\n\n\tasync down(stack) {\n\t\tawait run('docker', compose(stack, ['down']), runOptions(stack));\n\t},\n\n\tasync port(stack, service, inside) {\n\t\tconst output = await runOutput(\n\t\t\t'docker',\n\t\t\tcompose(stack, ['port', service, String(inside)]),\n\t\t\t{ cwd: stack.cwd, ...(stack.signal ? { signal: stack.signal } : {}) },\n\t\t);\n\t\t// `127.0.0.1:54321` — the part after the last colon is the host port.\n\t\tconst port = Number(output.trim().split(':').pop());\n\t\tif (!Number.isInteger(port) || port <= 0) {\n\t\t\tthrow new ServicePortUnknown(service, inside, output);\n\t\t}\n\t\treturn port;\n\t},\n\n\tasync copyOut(stack, service, from, to) {\n\t\tawait mkdir(dirname(to), { recursive: true });\n\t\tawait run(\n\t\t\t'docker',\n\t\t\tcompose(stack, ['cp', `${service}:${from}`, to]),\n\t\t\trunOptions(stack),\n\t\t);\n\t},\n\n\tasync digest(ref) {\n\t\tconst { code, stdout } = await capture('docker', [\n\t\t\t'image',\n\t\t\t'inspect',\n\t\t\t'--format={{json .RepoDigests}} {{.Id}}',\n\t\t\tref,\n\t\t]);\n\t\tif (code !== 0) return undefined;\n\n\t\tconst [json = '[]', id] = stdout.trim().split(' ');\n\t\tconst repository = ref.split('@')[0]!.replace(/:[\\w][\\w.-]*$/, '');\n\t\ttry {\n\t\t\tconst digests = JSON.parse(json) as unknown;\n\t\t\tconst match = Array.isArray(digests)\n\t\t\t\t? digests.find(\n\t\t\t\t\t\t(entry): entry is string =>\n\t\t\t\t\t\t\ttypeof entry === 'string' && entry.startsWith(`${repository}@`),\n\t\t\t\t\t)\n\t\t\t\t: undefined;\n\t\t\tif (match) return match.slice(repository.length + 1);\n\t\t} catch {\n\t\t\t// Fall through to the content id.\n\t\t}\n\t\treturn id || undefined;\n\t},\n};\n","/**\n * Which image each app runs — and, for a tag, whether every one exists.\n *\n * A tag is a release's identity: `--tag=v1.4.0` means the images CI pushed as\n * v1.4.0, and nothing else. So a stack is never half rolled out: every app's\n * image is asked of the registry before anything is pulled or the running\n * stack is touched, and one missing image stops the whole run naming every\n * one that is missing. Nor is a missing image quietly built instead — a build\n * of whatever is checked out here is not the release the tag names.\n */\n\nimport type { ComposeDocker } from './docker';\n\n/** One app's image, as the stack runs it. */\nexport interface AppImage {\n\tapp: string;\n\tref: string;\n\ttag: string;\n}\n\n/**\n * The tag a site's image carries for a release.\n *\n * A site's public URLs are inlined when it is built, so one bundle serves one\n * stage: `v1.4.0-production` and `v1.4.0-staging` are two images of the same\n * commit. A backend reads its URLs at runtime, so one image serves every stage\n * and its tag is the release's alone.\n */\nexport function siteTag(tag: string, stage: string): string {\n\treturn `${tag}-${stage}`;\n}\n\n/** Images a tag names that the registry does not have. */\nexport class ImageTagNotFound extends Error {\n\tconstructor(\n\t\treadonly tag: string,\n\t\treadonly refs: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`The registry has no image for ${refs.length === 1 ? 'this app' : `${refs.length} apps`} at '${tag}':\\n` +\n\t\t\t\trefs.map((ref) => ` - ${ref}`).join('\\n') +\n\t\t\t\t`\\nNothing was pulled or started. Push '${tag}' for every app from CI, ` +\n\t\t\t\t`pick a tag that exists, or run without --tag to build from this checkout.`,\n\t\t);\n\t\tthis.name = 'ImageTagNotFound';\n\t}\n}\n\n/** The registry did not answer, or did not let this machine in. */\nexport class RegistryUnreachable extends Error {\n\tconstructor(\n\t\treadonly ref: string,\n\t\treadonly detail: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not ask the registry about ${ref}: ${detail || 'no answer'}\\n` +\n\t\t\t\t`Nothing was pulled or started. Check the registry is reachable and ` +\n\t\t\t\t`that this machine is logged in to it (\\`docker login <registry>\\`).`,\n\t\t);\n\t\tthis.name = 'RegistryUnreachable';\n\t}\n}\n\n/**\n * Assert every image exists, before anything else happens.\n *\n * Asked all at once and reported all at once: a release missing two images is\n * one problem with two names, and finding the second only after fixing the\n * first is a slower way to learn the same thing.\n *\n * @throws {RegistryUnreachable} when the registry cannot say\n * @throws {ImageTagNotFound} listing every missing image\n */\nexport async function assertImagesExist(\n\tdocker: Pick<ComposeDocker, 'lookup'>,\n\ttag: string,\n\timages: readonly AppImage[],\n): Promise<void> {\n\tconst lookups = await Promise.all(\n\t\timages.map((image) => docker.lookup(image.ref)),\n\t);\n\n\tconst unreachable = lookups.find((lookup) => lookup.status === 'unreachable');\n\tif (unreachable?.status === 'unreachable') {\n\t\tthrow new RegistryUnreachable(unreachable.ref, unreachable.detail);\n\t}\n\n\tconst missing = lookups\n\t\t.filter((lookup) => lookup.status === 'missing')\n\t\t.map((lookup) => lookup.ref);\n\tif (missing.length > 0) throw new ImageTagNotFound(tag, missing);\n}\n","/**\n * The edge in front of a compose stack: one Caddy, one host per app.\n *\n * Deployed, each host is the stage's real domain and Caddy gets it a\n * certificate from Let's Encrypt on first request — the same automatic HTTPS\n * it does anywhere, with nothing to configure but the name. Locally the hosts\n * are `*.localhost` and the certificates come from Caddy's own CA, as the\n * `gkm dev` edge does, so a local stack is `https://` like a deployed one.\n *\n * Pure: what the edge serves is data, and rendering it is one string.\n */\n\n/** One host on the edge and the app container behind it. */\nexport interface EdgeSite {\n\t/** e.g. `api.example.com` */\n\thost: string;\n\t/** The app on the compose network, e.g. `api:3000`. */\n\tupstream: string;\n}\n\nexport interface EdgeOptions {\n\t/** Certificates from Caddy's internal CA rather than from ACME. */\n\tlocal: boolean;\n\t/** Where ACME sends expiry notices, when someone wants them. */\n\temail?: string;\n}\n\n/** The Caddyfile for a stack's edge. */\nexport function edgeCaddyfile(\n\tsites: readonly EdgeSite[],\n\toptions: EdgeOptions,\n): string {\n\tconst global = options.local\n\t\t? `{\n\t# No ACME and no public DNS: the CA is Caddy's own and the names resolve\n\t# to this machine.\n\tlocal_certs\n\tauto_https disable_redirects\n}`\n\t\t: options.email\n\t\t\t? `{\n\temail ${options.email}\n}`\n\t\t\t: '';\n\n\tconst blocks = sites.map(\n\t\t({ host, upstream }) => `https://${host} {${\n\t\t\toptions.local\n\t\t\t\t? `\n\ttls internal\n`\n\t\t\t\t: ''\n\t\t}\n\t# Host and X-Forwarded-* are passed through as Caddy does by default, so\n\t# an app builds redirects, cookies and links on the address a caller used.\n\treverse_proxy ${upstream} {\n\t\t# Never buffer: a streamed response — server-sent events, a Next.js\n\t\t# RSC payload — reaches the client as each chunk is written.\n\t\tflush_interval -1\n\t}\n}`,\n\t);\n\n\t// No blank line under the header: `caddy fmt` wants the first block there.\n\treturn `${HEADER}${global ? `${global}\\n\\n` : ''}${blocks.join('\\n\\n')}\\n`;\n}\n\nconst HEADER = `# Generated by gkm compose from the construct manifest — do not edit.\n#\n# One host per API and site, each in front of its own container.\n`;\n","/**\n * The stack a workspace runs as for one stage — pure.\n *\n * Everything `gkm compose` writes is decided here, as data: the compose file,\n * the Caddyfile, one env file per backend, and the image each app runs. The\n * command around it reads the workspace, asks the registry, writes the files\n * and starts Docker; none of those decide anything, so all of it can be\n * asserted without a daemon.\n *\n * The values come from the same derivations every other target uses. The\n * plan is reconcile's `planFor`, the keys an app reads are `appEnvKeys`, and\n * every URL is `envFor`'s — asked twice, once with each app at its public\n * address and once on the compose network, because a browser and a sibling\n * service reach the same API by different names.\n */\n\nimport {\n\ttype ConstructManifest,\n\tprovideKey,\n\tprovisionOrder,\n\tpublicEnvFor,\n} from '@geekmidas/manifest';\nimport { isMainFrontendApp, resolveHost } from '../deploy/domain.js';\nimport { type DeployIdentity, imageRef } from '../deploy/identity.js';\nimport { validateImageRef } from '../docker/imageRef.js';\nimport { siteDockerfile } from '../docker/index.js';\nimport {\n\tgenerateSlimDockerfile,\n\ttype PackageManager,\n} from '../docker/templates.js';\nimport { appEnvKeys, networkEnv } from '../reconcile/apps.js';\nimport { hostFor } from '../reconcile/caddyfile.js';\nimport { type ComposeService, composeFor } from '../reconcile/compose.js';\nimport { DEFAULT_IMAGES, portKeys } from '../reconcile/containers.js';\nimport { localRolePassword } from '../reconcile/env.js';\nimport { type Plan, type PlannedResource, planFor } from '../reconcile/plan.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport { DEFAULT_CACHE, DEFAULT_EVENTS } from '../types.js';\nimport { appKey } from '../workspace/derive.js';\nimport type {\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from '../workspace/types.js';\nimport { edgeCaddyfile } from './caddyfile.js';\nimport { type AppImage, siteTag } from './images.js';\n\n/** Where a stack's files are written, relative to the workspace root. */\nexport function stackDir(stage: string): string {\n\treturn `.gkm/compose/${stage}`;\n}\n\n/**\n * The compose project a stage's stack runs as.\n *\n * The deploy identity's scope, so two workspaces with one name in different\n * namespaces never share containers or volumes — and the stage, so a\n * machine can run two stages side by side.\n */\nexport function composeProject(identity: DeployIdentity): string {\n\treturn `${identity.scope}-${identity.stage}`;\n}\n\n/** The ports the edge is published on, and the variables that move them. */\nexport const EDGE_PORT_ENV = {\n\thttps: 'GKM_COMPOSE_HTTPS_PORT',\n\thttp: 'GKM_COMPOSE_HTTP_PORT',\n} as const;\n\n/**\n * Containers this target does not run.\n *\n * Object storage is the stage's own — an externally hosted bucket whose URL\n * the stage's secrets hold — and so is a deployed stage's mail. The AWS\n * emulator never applies: a stack is a server target, whose events are\n * pg-boss.\n */\nconst NOT_RUN: Readonly<Record<string, true>> = {\n\tminio: true,\n\tlocalstack: true,\n\tcaddy: true,\n};\n\n/** Kinds whose `<ID>_URL` a stage must be given, because nothing here runs them. */\nconst EXTERNAL_KINDS: Readonly<Record<string, true>> = {\n\tobjects: true,\n\t'file-server': true,\n};\n\n/** The S3 client's credentials, read beside a bucket's URL when it has them. */\nconst STORAGE_KEYS = [\n\t'AWS_ACCESS_KEY_ID',\n\t'AWS_SECRET_ACCESS_KEY',\n\t'AWS_REGION',\n] as const;\n\n/** Each image's build, where the stack builds rather than pulls. */\nexport interface AppBuild {\n\t/** Relative to the workspace root, which is the build context. */\n\tdockerfile: string;\n\t/** A site's public URLs, inlined by its bundler. */\n\targs?: Record<string, string>;\n}\n\n/** One app in the stack. */\nexport interface StackApp extends AppImage {\n\t/** Its compose service name — the app key, `api`. */\n\tname: string;\n\t/** The construct it serves. */\n\tid: string;\n\tkind: 'rest-api' | 'site';\n\t/** Its directory, relative to the workspace root. */\n\tpath: string;\n\tport: number;\n\t/** The host the edge answers it on. */\n\thost: string;\n\t/** `https://<host>` — with the edge's port where it is not 443. */\n\turl: string;\n\tbuild?: AppBuild;\n\t/** A backend's environment, exactly the keys it reads. Absent for a site. */\n\tenv?: Record<string, string>;\n}\n\n/** A service as the stack's compose file defines it. */\nexport interface StackService {\n\timage: string;\n\tbuild?: {\n\t\tcontext: string;\n\t\tdockerfile: string;\n\t\targs?: Record<string, string>;\n\t};\n\trestart?: string;\n\tcommand?: string;\n\tenv_file?: { path: string; format: 'raw' }[];\n\tenvironment?: Record<string, string>;\n\tports?: string[];\n\tvolumes?: string[];\n\tdepends_on?: Record<string, { condition: 'service_healthy' }>;\n\thealthcheck?: ComposeService['healthcheck'];\n}\n\nexport interface StackFile {\n\tname: string;\n\tservices: Record<string, StackService>;\n\tvolumes: Record<string, Record<string, never>>;\n}\n\nexport interface ComposeStack {\n\tproject: string;\n\tstage: string;\n\t/** The project's local stage, served with Caddy's internal CA. */\n\tlocal: boolean;\n\tapps: StackApp[];\n\t/** The infrastructure containers, by compose service name. */\n\tinfra: string[];\n\tcompose: StackFile;\n\tcaddyfile: string;\n\t/** Generated Dockerfiles, by path relative to the workspace root. */\n\tdockerfiles: Record<string, string>;\n\t/** The plan the stack was derived from — what provisioning creates. */\n\tplan: Plan;\n\t/** The Postgres master's password, and the seed role passwords take. */\n\tcredential: { master: string; seed?: string };\n}\n\nexport interface StackInput {\n\tworkspace: NormalizedWorkspace;\n\tmanifest: ConstructManifest;\n\t/** Each owner's runnables' edges, from discovery. */\n\trunnables?: Readonly<Record<string, readonly string[]>>;\n\tstage: string;\n\tidentity: DeployIdentity;\n\timages: {\n\t\tmode: 'build' | 'pull';\n\t\t/** A release tag, or — building — the commit. */\n\t\ttag: string;\n\t\tregistry?: string;\n\t};\n\t/** The stage's secrets — set by hand, and what a deployed stage generated. */\n\tsecrets?: StageSecrets | null;\n\t/** The edge's published ports. 443 and 80 by default. */\n\tports?: { https?: number; http?: number };\n\t/** For builds: the package manager the site Dockerfiles install with. */\n\tpackageManager?: PackageManager;\n\t/** For builds: each app's package name, which turbo prunes by. */\n\tpackages?: Readonly<Record<string, string>>;\n}\n\n/** A bucket the stack needs and the stage was not given. */\nexport class BucketNotConfigured extends Error {\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly id: string,\n\t\treadonly key: string,\n\t\treadonly stage: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${app}' uses the bucket '${id}', and gkm compose runs no object ` +\n\t\t\t\t`storage: the stage needs an externally hosted bucket. Set its URL in ` +\n\t\t\t\t`the stage's secrets: gkm secrets:set ${key} 's3://…' --stage ${stage}`,\n\t\t);\n\t\tthis.name = 'BucketNotConfigured';\n\t}\n}\n\n/** A value only the stage's secrets can hold, and they do not. */\nexport class StageSecretMissing extends Error {\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly key: string,\n\t\treadonly stage: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${app}' reads ${key}, and the stage '${stage}' has none. Nothing in ` +\n\t\t\t\t`the stack can derive it; set it in the stage's secrets: ` +\n\t\t\t\t`gkm secrets:set ${key} '…' --stage ${stage}`,\n\t\t);\n\t\tthis.name = 'StageSecretMissing';\n\t}\n}\n\n/** A deployed stage's secrets with no seed to derive its passwords from. */\nexport class StageSeedMissing extends Error {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`The stage '${stage}' has no seed in its secrets, and a deployed stage's ` +\n\t\t\t\t`database passwords are derived from it. Run gkm compose without ` +\n\t\t\t\t`--dry-run once, or gkm deploy, to generate it.`,\n\t\t);\n\t\tthis.name = 'StageSeedMissing';\n\t}\n}\n\n/** A workspace with no API and no site to run. */\nexport class NothingToCompose extends Error {\n\tconstructor(readonly root: string) {\n\t\tsuper(\n\t\t\t`${root} declares no RestApi and no site, so a stack would run nothing. ` +\n\t\t\t\t`Workers and mobile apps are not run by gkm compose yet.`,\n\t\t);\n\t\tthis.name = 'NothingToCompose';\n\t}\n}\n\n/** A value that cannot be written to an env file a line at a time. */\nexport class EnvValueMultiline extends Error {\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`${key} for '${app}' spans more than one line, and an env file holds ` +\n\t\t\t\t`one value per line. Store it on one line — base64, or JSON — and ` +\n\t\t\t\t`decode it where it is read.`,\n\t\t);\n\t\tthis.name = 'EnvValueMultiline';\n\t}\n}\n\n/** The services a stack's apps wait on and the edge waits on in turn. */\nconst HEALTHY = { condition: 'service_healthy' } as const;\n\n/**\n * An app's health check: its own server, asked on 127.0.0.1 — not\n * `localhost`, which resolves to `::1` first in an Alpine container while a\n * server may listen on IPv4 alone.\n */\nfunction probe(port: number, path: string): StackService['healthcheck'] {\n\treturn {\n\t\ttest: [\n\t\t\t'CMD',\n\t\t\t'wget',\n\t\t\t'-q',\n\t\t\t'-O',\n\t\t\t'/dev/null',\n\t\t\t`http://127.0.0.1:${port}${path}`,\n\t\t],\n\t\tinterval: '5s',\n\t\ttimeout: '3s',\n\t\tretries: 20,\n\t\tstart_period: '10s',\n\t};\n}\n\n/** The stack for one stage. */\nexport function composeStack(input: StackInput): ComposeStack {\n\tconst { workspace, manifest, stage, identity } = input;\n\tconst local = stage === workspace.stages.local;\n\tconst project = composeProject(identity);\n\tconst custom = input.secrets?.custom ?? {};\n\tconst https = input.ports?.https ?? 443;\n\tconst http = input.ports?.http ?? 80;\n\n\t// A server target's backends, whatever `deploy.default` says: the stack is\n\t// one machine running containers, so the cache is a table in the declared\n\t// database and events are pg-boss beside it.\n\tconst plan = planFor(manifest, stage, provisionOrder(manifest), {\n\t\tlocalStage: workspace.stages.local,\n\t\tevents: DEFAULT_EVENTS.server,\n\t\tcache: DEFAULT_CACHE.server,\n\t\t// The stack brings its own edge; reconcile's would front the host.\n\t\tedge: false,\n\t});\n\tconst byId = new Map(plan.resources.map((r) => [r.id, r]));\n\n\tconst seed = local ? undefined : input.secrets?.seed;\n\tif (!local && !seed) throw new StageSeedMissing(stage);\n\tconst credential = {\n\t\tmaster: local\n\t\t\t? 'geekmidas'\n\t\t\t: localRolePassword(workspace.name, plan, 'master', seed),\n\t\t...(seed ? { seed } : {}),\n\t};\n\n\tconst infra = plan.containers\n\t\t.filter((c) => !NOT_RUN[c])\n\t\t// A deployed stage sends real mail, through whatever its MAIL_URL names.\n\t\t.filter((c) => local || c !== 'mailpit')\n\t\t.sort();\n\n\tconst apps = stackApps(workspace, manifest, plan, {\n\t\tlocal,\n\t\tstage,\n\t\thttps,\n\t\tidentity,\n\t\timages: input.images,\n\t});\n\tif (apps.length === 0) throw new NothingToCompose(workspace.root);\n\n\t// Every key the stage resolves, twice: with each app at its public address\n\t// — what a browser is handed, and what an app says it is — and with each on\n\t// the compose network, which is how one service reaches another.\n\tconst derivation = {\n\t\tproject: workspace.name,\n\t\tmaster: credential.master,\n\t\t...(credential.seed ? { seed: credential.seed } : {}),\n\t};\n\tconst outside = networkEnv(plan, {\n\t\t...derivation,\n\t\taddresses: Object.fromEntries(apps.map((app) => [app.id, app.url])),\n\t});\n\tconst inside = networkEnv(plan, {\n\t\t...derivation,\n\t\taddresses: Object.fromEntries(\n\t\t\tapps.map((app) => [app.id, `http://${app.name}:${app.port}`]),\n\t\t),\n\t});\n\n\t// Which construct each key belongs to, so a key is resolved by what it is\n\t// rather than by its spelling.\n\tconst owners = new Map<string, PlannedResource>();\n\tfor (const resource of plan.resources) {\n\t\towners.set(resource.envKey, resource);\n\t\tif (resource.kind === 'email') {\n\t\t\towners.set(provideKey(resource.id, 'from'), resource);\n\t\t\towners.set(provideKey(resource.id, 'inboxUrl'), resource);\n\t\t}\n\t\tif (resource.kind === 'external-api') {\n\t\t\towners.set(provideKey(resource.id, 'credentials'), resource);\n\t\t}\n\t}\n\tconst surfaceUrls = new Map(\n\t\tplan.resources\n\t\t\t.filter((r) => r.kind === 'rest-api' || r.kind === 'site')\n\t\t\t.map((r) => [r.envKey, r.id]),\n\t);\n\tconst appById = new Map(apps.map((app) => [app.id, app]));\n\n\t/**\n\t * What a key resolves to for one app, from the stage's secrets where only\n\t * they can hold it, and the derivations otherwise.\n\t */\n\tconst valueFor = (\n\t\tapp: StackApp,\n\t\tkey: string,\n\t\towner: PlannedResource | undefined,\n\t): string | undefined => {\n\t\tif (owner && EXTERNAL_KINDS[owner.kind] && key === owner.envKey) {\n\t\t\tconst value = custom[key];\n\t\t\tif (value === undefined) {\n\t\t\t\tthrow new BucketNotConfigured(app.name, owner.id, key, stage);\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\n\t\t// Supplied by somebody: a third party's credentials, and — deployed —\n\t\t// the mail server a stage sends through and the secrets it generated.\n\t\tif (owner && suppliedBy(owner, key, local)) {\n\t\t\tconst value = custom[key];\n\t\t\tif (value === undefined) {\n\t\t\t\tthrow new StageSecretMissing(app.name, key, stage);\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\n\t\t// Set by hand wins over derived: it is the stage saying what it is.\n\t\tif (custom[key] !== undefined) return custom[key];\n\n\t\t// A backend reaches a sibling surface on the compose network; its own\n\t\t// address is the public one, which is what it builds links on. A site\n\t\t// runs in a browser, which has only the public addresses.\n\t\tconst surface = surfaceUrls.get(key);\n\t\tif (app.kind === 'rest-api' && surface && surface !== app.id) {\n\t\t\treturn inside[key];\n\t\t}\n\n\t\treturn outside[key];\n\t};\n\n\tfor (const app of apps) {\n\t\tif (app.kind === 'site') {\n\t\t\tconst site = manifest[app.id];\n\t\t\tif (site?.kind !== 'site') continue;\n\n\t\t\tconst args: Record<string, string> = {};\n\t\t\tfor (const [key, source] of Object.entries(\n\t\t\t\tpublicEnvFor(site, manifest),\n\t\t\t)) {\n\t\t\t\tconst value = valueFor(app, source, owners.get(source));\n\t\t\t\tif (value !== undefined) args[key] = value;\n\t\t\t}\n\t\t\tif (app.build && Object.keys(args).length > 0) app.build.args = args;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst keys = appEnvKeys(manifest, app.name, input.runnables) ?? new Set();\n\t\tconst values: Record<string, string> = {};\n\t\tfor (const key of [...keys].sort()) {\n\t\t\tconst owner = owners.get(key) ?? storageOwner(key, plan);\n\t\t\t// A deployed stage has no inbox: Mailpit runs only locally.\n\t\t\tif (!local && owner?.kind === 'email' && key.endsWith('_INBOX_URL'))\n\t\t\t\tcontinue;\n\t\t\tif ((STORAGE_KEYS as readonly string[]).includes(key)) {\n\t\t\t\t// The bucket is the stage's own, so its credentials are too —\n\t\t\t\t// when it has any: on AWS an instance role may stand in for them.\n\t\t\t\tif (custom[key] !== undefined) values[key] = custom[key];\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst value = valueFor(app, key, owner);\n\t\t\tif (value !== undefined) values[key] = value;\n\t\t}\n\n\t\t// The origins Better Auth trusts are the browser's and, now, each\n\t\t// service that calls this surface across the compose network: its CSRF\n\t\t// check applies to every caller, and a sibling's request carries its\n\t\t// own origin, not a browser's.\n\t\tconst trusted = provideKey(app.id, 'trustedOrigins');\n\t\tif (keys.has(trusted)) {\n\t\t\tconst internal = (byId.get(app.id)?.callers ?? [])\n\t\t\t\t.map((caller) => appById.get(caller))\n\t\t\t\t.filter((caller): caller is StackApp => caller?.kind === 'rest-api')\n\t\t\t\t.map((caller) => `http://${caller.name}:${caller.port}`);\n\t\t\tconst origins = [\n\t\t\t\t...(values[trusted] ?? '').split(',').filter(Boolean),\n\t\t\t\t...internal,\n\t\t\t];\n\t\t\tvalues[trusted] = [...new Set(origins)].join(',');\n\t\t}\n\n\t\tapp.env = {\n\t\t\t...values,\n\t\t\tNODE_ENV: 'production',\n\t\t\tPORT: String(app.port),\n\t\t\tSTAGE: stage,\n\t\t};\n\n\t\tfor (const [key, value] of Object.entries(app.env)) {\n\t\t\tif (/[\\r\\n]/.test(value)) throw new EnvValueMultiline(app.name, key);\n\t\t}\n\t}\n\n\tconst compose = stackFile({\n\t\tproject,\n\t\tplan,\n\t\tinfra,\n\t\tapps,\n\t\tmaster: credential.master,\n\t\thttps,\n\t\thttp,\n\t});\n\n\tconst caddyfile = edgeCaddyfile(\n\t\tapps.map((app) => ({\n\t\t\thost: app.host,\n\t\t\tupstream: `${app.name}:${app.port}`,\n\t\t})),\n\t\t{ local },\n\t);\n\n\tconst dockerfiles: Record<string, string> = {};\n\tfor (const app of apps) {\n\t\tif (!app.build) continue;\n\t\tdockerfiles[app.build.dockerfile] =\n\t\t\tapp.kind === 'site'\n\t\t\t\t? siteDockerfile(app.name, workspace.apps[app.name]!, {\n\t\t\t\t\t\tturboPackage: input.packages?.[app.name] ?? app.name,\n\t\t\t\t\t\tpackageManager: input.packageManager ?? 'pnpm',\n\t\t\t\t\t\tmanifest,\n\t\t\t\t\t})\n\t\t\t\t: generateSlimDockerfile({\n\t\t\t\t\t\timageName: app.name,\n\t\t\t\t\t\tbaseImage: 'node:22-alpine',\n\t\t\t\t\t\tport: app.port,\n\t\t\t\t\t\thealthCheckPath: '/health',\n\t\t\t\t\t\tprebuilt: true,\n\t\t\t\t\t\tpackageManager: input.packageManager ?? 'pnpm',\n\t\t\t\t\t\tbundle: `${app.path}/.gkm/server/dist/server.mjs`,\n\t\t\t\t\t});\n\t}\n\n\treturn {\n\t\tproject,\n\t\tstage,\n\t\tlocal,\n\t\tapps,\n\t\tinfra,\n\t\tcompose,\n\t\tcaddyfile,\n\t\tdockerfiles,\n\t\tplan,\n\t\tcredential,\n\t};\n\n\t/** Whether only the stage's secrets can supply `key` to `owner`. */\n\tfunction suppliedBy(\n\t\towner: PlannedResource,\n\t\tkey: string,\n\t\tisLocal: boolean,\n\t): boolean {\n\t\tif (owner.kind === 'credential') return true;\n\t\tif (owner.kind === 'external-api')\n\t\t\treturn key === provideKey(owner.id, 'credentials');\n\t\tif (isLocal) return false;\n\t\t// Deployed, nothing here derives a secret: the stage generated each one\n\t\t// once, and every run reads the same value back.\n\t\tif (owner.kind === 'secret' || owner.kind === 'encryption') return true;\n\t\treturn owner.kind === 'email' && !key.endsWith('_INBOX_URL');\n\t}\n}\n\n/** Which bucket an S3 credential key belongs to — the first one declared. */\nfunction storageOwner(key: string, plan: Plan): PlannedResource | undefined {\n\tif (!(STORAGE_KEYS as readonly string[]).includes(key)) return undefined;\n\treturn plan.resources.find((r) => r.kind === 'objects');\n}\n\n/** The apps a stack runs: every API and every site, at its stage address. */\nfunction stackApps(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\tplan: Plan,\n\toptions: {\n\t\tlocal: boolean;\n\t\tstage: string;\n\t\thttps: number;\n\t\tidentity: DeployIdentity;\n\t\timages: StackInput['images'];\n\t},\n): StackApp[] {\n\tconst apps: StackApp[] = [];\n\tconst resources = new Map(plan.resources.map((r) => [r.id, r]));\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration.kind !== 'rest-api' && declaration.kind !== 'site')\n\t\t\tcontinue;\n\n\t\tconst name = appKey(id);\n\t\tconst app = workspace.apps[name];\n\t\tconst resource = resources.get(id);\n\t\tif (!app || !resource || !app.port) continue;\n\n\t\tconst host = hostOf(name, app, workspace, resource, options);\n\t\tconst site = declaration.kind === 'site';\n\t\tconst tag = site\n\t\t\t? siteTag(options.images.tag, options.stage)\n\t\t\t: options.images.tag;\n\t\tconst ref = validateImageRef(\n\t\t\timageRef(options.identity, name, options.images.registry, tag),\n\t\t);\n\n\t\tapps.push({\n\t\t\tapp: name,\n\t\t\tname,\n\t\t\tid,\n\t\t\tkind: declaration.kind,\n\t\t\tpath: app.path,\n\t\t\tport: app.port,\n\t\t\thost,\n\t\t\turl: `https://${host}${options.local && options.https !== 443 ? `:${options.https}` : ''}`,\n\t\t\tref,\n\t\t\ttag,\n\t\t\t...(options.images.mode === 'build'\n\t\t\t\t? {\n\t\t\t\t\t\tbuild: {\n\t\t\t\t\t\t\tdockerfile: `${stackDir(options.stage)}/Dockerfile.${name}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t});\n\t}\n\n\treturn apps.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * The host an app answers on for a stage: the `gkm dev` edge's\n * `*.localhost` names for the local stage, and the stage's domain — by the\n * same rule a deploy follows — for any other.\n */\nfunction hostOf(\n\tname: string,\n\tapp: NormalizedAppConfig,\n\tworkspace: NormalizedWorkspace,\n\tresource: PlannedResource,\n\toptions: { local: boolean; stage: string },\n): string {\n\tif (options.local) return hostFor(resource, workspace.name);\n\n\treturn resolveHost(\n\t\tname,\n\t\tapp,\n\t\toptions.stage,\n\t\tworkspace.deploy?.domains,\n\t\tisMainFrontendApp(name, app, workspace.apps),\n\t);\n}\n\n/** The compose document: the infrastructure, the apps, and the edge. */\nfunction stackFile(options: {\n\tproject: string;\n\tplan: Plan;\n\tinfra: readonly string[];\n\tapps: readonly StackApp[];\n\tmaster: string;\n\thttps: number;\n\thttp: number;\n}): StackFile {\n\tconst { project, plan, infra, apps } = options;\n\n\t// Reconcile's definitions — images, health checks, volumes — with nothing\n\t// published to the host. Ports are allocation's business there; here the\n\t// edge is the only way in.\n\tconst derived = composeFor(\n\t\t{ ...plan, containers: [...infra] },\n\t\t{\n\t\t\tproject,\n\t\t\tports: Object.fromEntries(\n\t\t\t\tportKeys(infra, plan.fakes).map((key) => [key, 0]),\n\t\t\t),\n\t\t},\n\t);\n\n\tconst services: Record<string, StackService> = {};\n\tconst volumes: Record<string, Record<string, never>> = {\n\t\t...derived.volumes,\n\t\t'caddy-data': {},\n\t\t'caddy-config': {},\n\t};\n\n\tfor (const name of infra) {\n\t\tconst {\n\t\t\tports: _ports,\n\t\t\tdepends_on,\n\t\t\tprofiles: _profiles,\n\t\t\t...service\n\t\t} = derived.services[name]!;\n\t\tservices[name] = {\n\t\t\t...service,\n\t\t\t...(depends_on?.length\n\t\t\t\t? {\n\t\t\t\t\t\tdepends_on: Object.fromEntries(\n\t\t\t\t\t\t\tdepends_on.map((dep) => [dep, HEALTHY]),\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tconst postgres = services.postgres;\n\tif (postgres) {\n\t\tpostgres.environment = {\n\t\t\t...postgres.environment,\n\t\t\tPOSTGRES_PASSWORD: options.master,\n\t\t};\n\t\t// Loopback only, on a port Docker picks: gkm creates the databases and\n\t\t// roles and runs the migrations from this machine, and nothing else\n\t\t// should reach the database from outside the stack.\n\t\tpostgres.ports = ['127.0.0.1::5432'];\n\t}\n\n\tfor (const app of apps) {\n\t\tservices[app.name] = {\n\t\t\timage: app.ref,\n\t\t\t...(app.build\n\t\t\t\t? {\n\t\t\t\t\t\tbuild: {\n\t\t\t\t\t\t\tcontext: '../../..',\n\t\t\t\t\t\t\tdockerfile: app.build.dockerfile,\n\t\t\t\t\t\t\t...(app.build.args ? { args: app.build.args } : {}),\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\trestart: 'unless-stopped',\n\t\t\t// A site is a bundle and reads nothing at runtime; a backend reads\n\t\t\t// exactly the keys in its own file.\n\t\t\t...(app.kind === 'rest-api'\n\t\t\t\t? {\n\t\t\t\t\t\tenv_file: [{ path: `./${app.name}.env`, format: 'raw' as const }],\n\t\t\t\t\t\t...(infra.length\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tdepends_on: Object.fromEntries(\n\t\t\t\t\t\t\t\t\t\tinfra.map((name) => [name, HEALTHY]),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\thealthcheck: probe(app.port, app.kind === 'site' ? '/' : '/health'),\n\t\t};\n\t}\n\n\tservices.caddy = {\n\t\timage: DEFAULT_IMAGES.caddy!,\n\t\trestart: 'unless-stopped',\n\t\tports: [`${options.https}:443`, `${options.http}:80`],\n\t\tvolumes: [\n\t\t\t'./Caddyfile:/etc/caddy/Caddyfile:ro',\n\t\t\t'caddy-data:/data',\n\t\t\t'caddy-config:/config',\n\t\t],\n\t\tdepends_on: Object.fromEntries(apps.map((app) => [app.name, HEALTHY])),\n\t\thealthcheck: {\n\t\t\ttest: ['CMD', 'caddy', 'version'],\n\t\t\tinterval: '10s',\n\t\t\ttimeout: '5s',\n\t\t\tretries: 5,\n\t\t},\n\t};\n\n\treturn { name: project, services, volumes };\n}\n\n/** Render an app's env file: one `KEY=value` per line, read raw by compose. */\nexport function envFile(env: Readonly<Record<string, string>>): string {\n\treturn `${Object.entries(env)\n\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t.map(([key, value]) => `${key}=${value}`)\n\t\t.join('\\n')}\\n`;\n}\n","/**\n * One construct's migrations and seeds, read from its folders.\n *\n * Two kinds of file, told apart by extension rather than by configuration: a\n * script exports `up` (and may export `down`) — or `seed(db, { stage })`, in\n * `seeds/` — and a\n * `.sql` file is run whole. The second is what makes a folder independent of\n * the client the app uses — Better Auth's schema is SQL whatever the\n * application queries with.\n */\n\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { Kysely, sql } from 'kysely';\nimport type { Migration, MigrationProvider } from 'kysely/migration';\n\nconst SCRIPTS = new Set(['.ts', '.mts', '.js', '.mjs']);\n\n/** Two files in one folder that would be one migration. */\nexport class DuplicateMigration extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly migration: string,\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has two migrations named '${migration}' — a script and a .sql file ` +\n\t\t\t\t`with the same name. A migration is recorded by name, so only one of ` +\n\t\t\t\t`them could ever run; rename or remove the other.`,\n\t\t);\n\t\tthis.name = 'DuplicateMigration';\n\t}\n}\n\n/** A script in a migrations folder that exports no `up`. */\nexport class MigrationHasNoUp extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a migrations folder but exports no \\`up\\` function. ` +\n\t\t\t\t`Export \\`async function up(db: Kysely<unknown>)\\`, or move the file ` +\n\t\t\t\t`out of the folder if it is not a migration.`,\n\t\t);\n\t\tthis.name = 'MigrationHasNoUp';\n\t}\n}\n\n/** A script in a seeds folder that exports no `seed`. */\nexport class SeedHasNoSeed extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a seeds folder but exports no \\`seed\\` function. ` +\n\t\t\t\t`Export \\`async function seed(db, { stage })\\`, or move the ` +\n\t\t\t\t`file out of the folder if it is not a seed.`,\n\t\t);\n\t\tthis.name = 'SeedHasNoSeed';\n\t}\n}\n\n/**\n * What a seed is handed beside its database.\n *\n * Seeds run on every stage, production included, so a seed that should write\n * something only somewhere — a demo tenant on `development`, never on\n * `production` — decides by the stage it is given.\n */\nexport interface SeedContext {\n\t/** The stage being seeded: the local one, `test`, or a deployed one. */\n\tstage: string;\n}\n\n/** One seed: what it is called, and what it does. */\nexport interface Seed {\n\tname: string;\n\trun: (db: Kysely<unknown>, context: SeedContext) => Promise<void>;\n}\n\n/**\n * The migration files in a folder, by name, without reading them.\n *\n * Also what `gkm dev` compares against a construct's history to report what\n * is pending, which is why it is separate from loading them.\n */\nexport async function migrationFiles(\n\tfolder: string,\n): Promise<Map<string, string>> {\n\tconst entries = await readdir(folder, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\tconst files = new Map<string, string>();\n\tfor (const entry of entries) {\n\t\tif (!entry.isFile() || !isMigration(entry.name)) continue;\n\n\t\tconst name = entry.name.slice(0, -extname(entry.name).length);\n\t\tif (files.has(name)) throw new DuplicateMigration(folder, name);\n\n\t\tfiles.set(name, join(folder, entry.name));\n\t}\n\n\treturn files;\n}\n\n/** A Kysely provider over one construct's folder. */\nexport function folderProvider(\n\tfolder: string,\n\traw: typeof sql,\n): MigrationProvider {\n\treturn {\n\t\tasync getMigrations() {\n\t\t\tconst migrations: Record<string, Migration> = {};\n\n\t\t\tfor (const [name, file] of await migrationFiles(folder)) {\n\t\t\t\tmigrations[name] =\n\t\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t\t? await sqlMigration(file, raw)\n\t\t\t\t\t\t: await scriptMigration(file);\n\t\t\t}\n\n\t\t\treturn migrations;\n\t\t},\n\t};\n}\n\n/**\n * The seeds in a folder, loaded, in name order.\n *\n * Every one runs on every pass — there is no history to consult — so the order\n * is the only thing between two seeds where one reads what another writes.\n * Name them to sort that way.\n */\nexport async function loadSeeds(\n\tfolder: string,\n\traw: typeof sql,\n): Promise<Seed[]> {\n\tconst files = [...(await migrationFiles(folder))].sort(([a], [b]) =>\n\t\ta < b ? -1 : a > b ? 1 : 0,\n\t);\n\n\tconst seeds: Seed[] = [];\n\tfor (const [name, file] of files) {\n\t\tseeds.push({\n\t\t\tname,\n\t\t\trun:\n\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t? (await sqlMigration(file, raw)).up\n\t\t\t\t\t: await scriptSeed(file),\n\t\t});\n\t}\n\n\treturn seeds;\n}\n\n/**\n * A seed script, imported as it is on disk now.\n *\n * Keyed by its modification time: a seed is run again after it is edited, and\n * a process that has imported it once would otherwise run the old one.\n */\nasync function scriptSeed(file: string): Promise<Seed['run']> {\n\tconst url = pathToFileURL(file);\n\turl.searchParams.set('mtime', String((await stat(file)).mtimeMs));\n\tconst module = (await import(url.href)) as { seed?: unknown };\n\tif (typeof module.seed !== 'function') throw new SeedHasNoSeed(file);\n\n\treturn module.seed as Seed['run'];\n}\n\nfunction isMigration(file: string): boolean {\n\tif (file.endsWith('.d.ts')) return false;\n\tif (/\\.(spec|test)\\.[cm]?[jt]s$/.test(file)) return false;\n\n\tconst extension = extname(file);\n\treturn extension === '.sql' || SCRIPTS.has(extension);\n}\n\nasync function scriptMigration(file: string): Promise<Migration> {\n\tconst module = (await import(pathToFileURL(file).href)) as Partial<Migration>;\n\tif (typeof module.up !== 'function') throw new MigrationHasNoUp(file);\n\n\treturn {\n\t\tup: module.up,\n\t\t...(typeof module.down === 'function' ? { down: module.down } : {}),\n\t};\n}\n\n/**\n * A `.sql` file, run whole.\n *\n * One query rather than split on `;`: with no parameters, Postgres takes\n * several statements in one message, and splitting by hand breaks on the first\n * `;` inside a string or a function body. There is no `down` — a generated\n * schema is rolled back by a migration that says how, not by guessing.\n */\nasync function sqlMigration(file: string, raw: typeof sql): Promise<Migration> {\n\tconst statements = await readFile(file, 'utf-8');\n\n\treturn {\n\t\tup: async (db: Kysely<unknown>) => {\n\t\t\tawait raw.raw(statements).execute(db);\n\t\t},\n\t};\n}\n","/**\n * Bring each construct's schema up to its migrations folder.\n *\n * Which constructs, in what order, from which folder, is `migrationTargets`'\n * answer and nobody else's. What is decided here is only how one target is\n * run: as its owner, in its own schema, with a history of its own.\n */\n\nimport { readdir } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\tDATABASE_FOLDERS,\n\tMIGRATIONS_ROOT,\n\ttype MigrationTarget,\n\tmigrationTargets,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Kysely } from 'kysely';\nimport type { Migrator } from 'kysely/migration';\nimport pg from 'pg';\nimport type { ConstructSource } from '../reconcile/discover';\nimport { folderProvider, loadSeeds, migrationFiles } from './provider';\n\nexport interface DatabasesOptions {\n\t/** The project root — where `db/` lives. */\n\troot: string;\n\tmanifest: ConstructManifest;\n\t/**\n\t * Where each construct was declared. A construct's migrations run with the\n\t * Kysely its own file resolves, which is the one its migrations import.\n\t */\n\tsources?: Readonly<Record<string, ConstructSource>>;\n\t/** The resolved environment: each construct's owner URL is read from it. */\n\tenv: Readonly<Record<string, string | undefined>>;\n\t/** One construct, by name. All of them when absent. */\n\tonly?: string;\n}\n\nexport interface MigrationRun {\n\ttarget: MigrationTarget;\n\t/** The migrations this run applied, in order. */\n\tapplied: string[];\n}\n\nexport interface SeedOptions extends DatabasesOptions {\n\t/** The stage being seeded, handed to every seed. */\n\tstage: string;\n}\n\nexport interface SeedRun {\n\ttarget: MigrationTarget;\n\t/** The seeds this run ran, in order — every one, every time. */\n\tseeded: string[];\n}\n\nexport interface PendingMigrations {\n\ttarget: MigrationTarget;\n\tpending: string[];\n}\n\n/** A folder under `db/` that names no construct. */\nexport class UnknownMigrationFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} names no database construct, so its migrations would never ` +\n\t\t\t\t`run. ${known.length > 0 ? `The folders that do: ${known.join(', ')}.` : 'This project declares no database.'} ` +\n\t\t\t\t`Rename the folder after the construct it belongs to.`,\n\t\t);\n\t\tthis.name = 'UnknownMigrationFolder';\n\t}\n}\n\n/** A construct was asked for by a name that takes no migrations. */\nexport class NoSuchMigrationTarget extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`'${construct}' is not a database construct. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `The ones with migrations: ${known.join(', ')}.`\n\t\t\t\t\t: 'This project declares no database.'),\n\t\t);\n\t\tthis.name = 'NoSuchMigrationTarget';\n\t}\n}\n\n/**\n * A migration left directly in a construct's folder, where nothing reads it.\n *\n * Migrations moved into `migrations/` so seeds could sit beside them. A file\n * still at the old level would otherwise never run, and nothing would say so.\n */\nexport class MigrationsOutsideFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly files: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has ${files.join(', ')} directly inside it, where nothing ` +\n\t\t\t\t`runs them. Migrations live in ${folder}/migrations/ and seeds in ` +\n\t\t\t\t`${folder}/seeds/ — move ${files.length === 1 ? 'it' : 'them'} there.`,\n\t\t);\n\t\tthis.name = 'MigrationsOutsideFolder';\n\t}\n}\n\n/** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */\nexport class UnknownDatabaseFolder extends Error {\n\tconstructor(readonly folder: string) {\n\t\tsuper(\n\t\t\t`${folder} is neither migrations/ nor seeds/, so nothing in it would ` +\n\t\t\t\t`ever run. A construct's folder holds those two; rename or remove it.`,\n\t\t);\n\t\tthis.name = 'UnknownDatabaseFolder';\n\t}\n}\n\n/** A seed failed; its own writes are rolled back, the ones before it stay. */\nexport class SeedFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly seed: string,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: seed '${seed}' failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}. ` +\n\t\t\t\t`Its writes were rolled back; fix it and run it again.`,\n\t\t);\n\t\tthis.name = 'SeedFailed';\n\t}\n}\n\n/** A construct with roles has no owner URL to migrate with. */\nexport class NoOwnerCredential extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`${construct} has no ${key} to migrate with. Migrations run as the owner ` +\n\t\t\t\t`role — the runtime one cannot create a table — and nothing ` +\n\t\t\t\t`resolved its URL. Is the stage reconciled?`,\n\t\t);\n\t\tthis.name = 'NoOwnerCredential';\n\t}\n}\n\n/** A migration failed; the ones before it in the run stay applied. */\nexport class MigrationFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly migration: string | undefined,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: migration ${migration ? `'${migration}' ` : ''}failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t);\n\t\tthis.name = 'MigrationFailed';\n\t}\n}\n\n/** The project's Kysely could not be found from where a database is declared. */\nexport class KyselyNotFound extends Error {\n\tconstructor(readonly from: string) {\n\t\tsuper(\n\t\t\t`Could not resolve 'kysely' from ${from}. Migrations run with the ` +\n\t\t\t\t`project's own Kysely — install it where the database is declared.`,\n\t\t);\n\t\tthis.name = 'KyselyNotFound';\n\t}\n}\n\n/** Apply every pending migration, construct by construct, parents first. */\nexport async function migrateDatabases(\n\toptions: DatabasesOptions,\n): Promise<MigrationRun[]> {\n\tconst runs: MigrationRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\t// No folder, or an empty one, is nothing to do — and no reason to\n\t\t// connect, which a construct nobody has written a migration for yet\n\t\t// should not need.\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) {\n\t\t\truns.push({ target, applied: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst applied = await withMigrator(options, target, async (migrator) => {\n\t\t\tconst { error, results = [] } = await migrator.migrateToLatest();\n\t\t\tif (error) {\n\t\t\t\tconst failed = results.find((result) => result.status === 'Error');\n\t\t\t\tthrow new MigrationFailed(target.id, failed?.migrationName, error);\n\t\t\t}\n\n\t\t\treturn results\n\t\t\t\t.filter((result) => result.status === 'Success')\n\t\t\t\t.map((result) => result.migrationName);\n\t\t});\n\n\t\truns.push({ target, applied });\n\t}\n\n\treturn runs;\n}\n\n/**\n * Run every construct's seeds, parents first — each one, every time, on every\n * stage, production included. Each is handed the stage, so a seed decides\n * what belongs where.\n *\n * Seeds are reference data kept up to date by being run again, so there is no\n * history: a seed is an upsert, and a changed one is applied by the next run.\n * Each runs in its own transaction, as the construct's owner, so a failure\n * leaves nothing half-written. Assumes the schema is migrated —\n * {@link migrateAndSeed} is what makes that true.\n */\nexport async function seedDatabases(options: SeedOptions): Promise<SeedRun[]> {\n\tconst runs: SeedRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst folder = join(options.root, target.seeds);\n\t\tif ((await migrationFiles(folder)).size === 0) {\n\t\t\truns.push({ target, seeded: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst seeded = await withOwner(options, target, async (db, kysely) => {\n\t\t\tconst names: string[] = [];\n\t\t\tfor (const seed of await loadSeeds(folder, kysely.sql)) {\n\t\t\t\ttry {\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.transaction()\n\t\t\t\t\t\t.execute((trx) => seed.run(trx, { stage: options.stage }));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow new SeedFailed(target.id, seed.name, error);\n\t\t\t\t}\n\t\t\t\tnames.push(seed.name);\n\t\t\t}\n\t\t\treturn names;\n\t\t});\n\n\t\truns.push({ target, seeded });\n\t}\n\n\treturn runs;\n}\n\n/**\n * Migrate, then seed: a seed only ever runs against the schema it was written\n * for.\n *\n * With `only`, the constructs it lives in are migrated too — a tenant's seed\n * cannot run before the database that owns its schema exists — and only it is\n * seeded.\n */\nexport async function migrateAndSeed(options: SeedOptions): Promise<{\n\tmigrations: MigrationRun[];\n\tseeds: SeedRun[];\n}> {\n\tconst migrations: MigrationRun[] = [];\n\tfor (const target of await withParents(options)) {\n\t\tmigrations.push(\n\t\t\t...(await migrateDatabases({ ...options, only: target.id })),\n\t\t);\n\t}\n\n\treturn { migrations, seeds: await seedDatabases(options) };\n}\n\n/** The targets `only` names, after the ones it lives in; all without it. */\nasync function withParents(\n\toptions: DatabasesOptions,\n): Promise<MigrationTarget[]> {\n\tconst chosen = await targetsFor(options);\n\tif (!options.only) return chosen;\n\n\tconst all = migrationTargets(options.manifest);\n\tconst ids = new Set<string>();\n\tfor (const target of chosen) {\n\t\tlet current: MigrationTarget | undefined = target;\n\t\twhile (current) {\n\t\t\tids.add(current.id);\n\t\t\tconst parent: string | undefined = current.of;\n\t\t\tcurrent = parent ? all.find((t) => t.id === parent) : undefined;\n\t\t}\n\t}\n\n\treturn all.filter((target) => ids.has(target.id));\n}\n\n/**\n * What each construct's folder has that its history does not — without\n * applying any of it. What `gkm dev` reports.\n */\nexport async function pendingMigrations(\n\toptions: DatabasesOptions,\n): Promise<PendingMigrations[]> {\n\tconst found: PendingMigrations[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) continue;\n\n\t\tconst pending = await withMigrator(options, target, async (migrator) =>\n\t\t\t(await migrator.getMigrations())\n\t\t\t\t.filter((migration) => migration.executedAt === undefined)\n\t\t\t\t.map((migration) => migration.name),\n\t\t);\n\n\t\tif (pending.length > 0) found.push({ target, pending });\n\t}\n\n\treturn found;\n}\n\n/**\n * The targets this run covers, with every folder under `db/` accounted for.\n *\n * A folder that names no construct is an error rather than something to skip:\n * a typo in its name is otherwise a migration that silently never runs.\n */\nasync function targetsFor(\n\toptions: DatabasesOptions,\n): Promise<MigrationTarget[]> {\n\tconst targets = migrationTargets(options.manifest);\n\tconst folders = targets.map((target) => target.folder);\n\n\tfor (const folder of await foldersIn(join(options.root, MIGRATIONS_ROOT))) {\n\t\tconst relative = `${MIGRATIONS_ROOT}/${folder}`;\n\t\tif (!folders.includes(relative)) {\n\t\t\tthrow new UnknownMigrationFolder(relative, folders);\n\t\t}\n\t\tawait checkLayout(options.root, relative);\n\t}\n\n\tif (!options.only) return targets;\n\n\tconst id = canonicalId(options.only);\n\tconst chosen = targets.filter((target) => target.id === id);\n\tif (chosen.length === 0) {\n\t\tthrow new NoSuchMigrationTarget(\n\t\t\toptions.only,\n\t\t\ttargets.map((target) => target.id),\n\t\t);\n\t}\n\n\treturn chosen;\n}\n\n/**\n * A construct's folder holds `migrations/` and `seeds/`, and nothing else —\n * anything beside them is a file or folder nothing would ever run.\n */\nasync function checkLayout(root: string, folder: string): Promise<void> {\n\tconst entries = await readdir(join(root, folder), { withFileTypes: true });\n\n\tconst loose = entries\n\t\t.filter((entry) => entry.isFile() && !entry.name.startsWith('.'))\n\t\t.map((entry) => entry.name);\n\tif (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);\n\n\tfor (const entry of entries) {\n\t\tif (\n\t\t\tentry.isDirectory() &&\n\t\t\t!(DATABASE_FOLDERS as readonly string[]).includes(entry.name)\n\t\t) {\n\t\t\tthrow new UnknownDatabaseFolder(`${folder}/${entry.name}`);\n\t\t}\n\t}\n}\n\nasync function foldersIn(dir: string): Promise<string[]> {\n\tconst entries = await readdir(dir, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\treturn entries\n\t\t.filter((entry) => entry.isDirectory())\n\t\t.map((entry) => entry.name);\n}\n\n/**\n * A migrator for one target, connected as its owner.\n *\n * No `migrationTableSchema`: the owner role's `search_path` is pinned to the\n * construct's schema, so the history lands there — `app.kysely_migration` for\n * the database, `auth_database.kysely_migration` for its tenant — and two\n * constructs in one Postgres never share one.\n *\n * Unordered migrations are allowed. Files are timestamped, so a branch merged\n * after another's migration ran carries an older one; refusing it would make\n * every such merge a manual repair.\n */\nasync function withMigrator<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (migrator: Migrator) => Promise<T>,\n): Promise<T> {\n\treturn withOwner(options, target, (db, { Migrator, sql }) =>\n\t\trun(\n\t\t\tnew Migrator({\n\t\t\t\tdb,\n\t\t\t\tprovider: folderProvider(join(options.root, target.migrations), sql),\n\t\t\t\tallowUnorderedMigrations: true,\n\t\t\t}),\n\t\t),\n\t);\n}\n\n/**\n * One target's database, connected as its owner and closed afterwards — what\n * migrations and seeds both run as.\n */\nasync function withOwner<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (\n\t\tdb: Kysely<unknown>,\n\t\tkysely: Awaited<ReturnType<typeof kyselyFrom>>,\n\t) => Promise<T>,\n): Promise<T> {\n\tconst from =\n\t\toptions.sources?.[target.id]?.file ??\n\t\t(target.of ? options.sources?.[target.of]?.file : undefined) ??\n\t\tjoin(options.root, 'package.json');\n\tconst kysely = await kyselyFrom(from);\n\n\tconst db: Kysely<unknown> = new kysely.Kysely<unknown>({\n\t\tdialect: new kysely.PostgresDialect({\n\t\t\tpool: new pg.Pool(poolConfig(ownerUrl(options, target))),\n\t\t}),\n\t});\n\n\ttry {\n\t\treturn await run(db, kysely);\n\t} finally {\n\t\tawait db.destroy();\n\t}\n}\n\n/**\n * The owner's URL, with no fallback to the runtime one.\n *\n * The runtime role can create nothing, so falling back would only move the\n * failure to the first `CREATE TABLE` and make it read like a permissions\n * bug. The one exception is a database declared `roles: false`, where there is\n * no owner — both URLs are the master credential by design.\n */\nfunction ownerUrl(options: DatabasesOptions, target: MigrationTarget): string {\n\tconst key = provideKey(target.id, 'ownerUrl');\n\tconst owner = options.env[key];\n\tif (owner) return owner;\n\n\tconst database = options.manifest[target.of ?? target.id];\n\tif (database?.kind === 'database' && database.roles === false) {\n\t\tconst url = options.env[provideKey(target.id, 'url')];\n\t\tif (url) return url;\n\t}\n\n\tthrow new NoOwnerCredential(target.id, key);\n}\n\n/**\n * Pool settings for a URL, with `?search_path=` as the startup option Postgres\n * reads. Only a `roles: false` URL carries it — a role pins its own.\n */\nfunction poolConfig(url: string): pg.PoolConfig {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tif (!searchPath) return { connectionString: url };\n\n\tparsed.searchParams.delete('search_path');\n\treturn {\n\t\tconnectionString: parsed.toString(),\n\t\toptions: `-c search_path=${searchPath}`,\n\t};\n}\n\n/** Kysely as the project resolves it from `from`. */\nasync function kyselyFrom(from: string) {\n\tconst require = createRequire(from);\n\n\ttry {\n\t\tconst [main, migration] = await Promise.all([\n\t\t\timport(pathToFileURL(require.resolve('kysely')).href),\n\t\t\timport(pathToFileURL(require.resolve('kysely/migration')).href),\n\t\t]);\n\n\t\treturn {\n\t\t\tKysely: main.Kysely as typeof import('kysely').Kysely,\n\t\t\tPostgresDialect:\n\t\t\t\tmain.PostgresDialect as typeof import('kysely').PostgresDialect,\n\t\t\tsql: main.sql as typeof import('kysely').sql,\n\t\t\tMigrator:\n\t\t\t\tmigration.Migrator as typeof import('kysely/migration').Migrator,\n\t\t};\n\t} catch {\n\t\tthrow new KyselyNotFound(from);\n\t}\n}\n","/**\n * Asking each app of a stack whether it answers — through Caddy, over HTTPS,\n * the way a browser reaches it.\n *\n * `docker compose up --wait` already knows each container passed its own\n * health check, asked on 127.0.0.1 inside it. What that cannot say is whether\n * the stack is reachable: whether the edge routes the host to the app, and\n * whether the certificate it presents is one a client accepts. So the check\n * goes through the edge, by hostname, and verifies the certificate.\n */\n\nimport { request } from 'node:https';\n\n/** One request to the edge. */\nexport interface HealthRequest {\n\t/** The app's public URL plus its health path. */\n\turl: string;\n\t/**\n\t * The certificate authority to verify against, PEM — Caddy's internal CA\n\t * on the local stage. The system's when absent.\n\t */\n\tca?: string;\n\t/**\n\t * Connect here instead of resolving the URL's host — the local edge, at\n\t * 127.0.0.1. The host is still sent as SNI and `Host`, so nothing depends\n\t * on how this machine resolves `*.localhost`.\n\t */\n\tconnectTo?: string;\n\ttimeoutMs: number;\n\tsignal?: AbortSignal;\n}\n\n/** Asks once, resolving with the HTTP status. Rejects when nothing answers. */\nexport type HealthProbe = (request: HealthRequest) => Promise<number>;\n\n/** A request that got no answer in time. */\nexport class HealthRequestTimedOut extends Error {\n\tconstructor(\n\t\treadonly url: string,\n\t\treadonly timeoutMs: number,\n\t) {\n\t\tsuper(\n\t\t\t`${url} did not answer within ${timeoutMs}ms. Check the stack is up with \\`docker compose ps\\`.`,\n\t\t);\n\t\tthis.name = 'HealthRequestTimedOut';\n\t}\n}\n\n/** The real probe: one HTTPS GET, the certificate verified. */\nexport const httpsProbe: HealthProbe = ({\n\turl,\n\tca,\n\tconnectTo,\n\ttimeoutMs,\n\tsignal,\n}) => {\n\tconst target = new URL(url);\n\treturn new Promise((resolve, reject) => {\n\t\tconst req = request(\n\t\t\t{\n\t\t\t\thost: connectTo ?? target.hostname,\n\t\t\t\tport: target.port || 443,\n\t\t\t\tservername: target.hostname,\n\t\t\t\tpath: `${target.pathname}${target.search}`,\n\t\t\t\tmethod: 'GET',\n\t\t\t\theaders: { host: target.host },\n\t\t\t\t...(ca ? { ca } : {}),\n\t\t\t\t...(signal ? { signal } : {}),\n\t\t\t\ttimeout: timeoutMs,\n\t\t\t},\n\t\t\t(res) => {\n\t\t\t\t// The status is the answer; the body is drained so the socket closes.\n\t\t\t\tres.resume();\n\t\t\t\tres.on('end', () => resolve(res.statusCode ?? 0));\n\t\t\t\tres.on('error', reject);\n\t\t\t},\n\t\t);\n\t\treq.on('timeout', () =>\n\t\t\treq.destroy(new HealthRequestTimedOut(url, timeoutMs)),\n\t\t);\n\t\treq.on('error', reject);\n\t\treq.end();\n\t});\n};\n\n/** Whether a status means the app answered as it should. */\nexport function isHealthy(status: number): boolean {\n\treturn status >= 200 && status < 400;\n}\n\n/** Apps that did not answer through the edge. */\nexport class ComposeAppsUnhealthy extends Error {\n\tconstructor(\n\t\treadonly project: string,\n\t\t/** Each app, and the last thing its check got: a status or an error. */\n\t\treadonly apps: readonly { app: string; url: string; last: string }[],\n\t) {\n\t\tsuper(\n\t\t\t`${project} started, but ${apps.length === 1 ? 'this app does' : 'these apps do'} not answer through Caddy:\\n` +\n\t\t\t\tapps.map(({ url, last }) => ` - ${url}: ${last}`).join('\\n') +\n\t\t\t\t`\\nSee why with \\`docker compose -p ${project} logs caddy ${apps.map((a) => a.app).join(' ')}\\`.`,\n\t\t);\n\t\tthis.name = 'ComposeAppsUnhealthy';\n\t}\n}\n","/**\n * The compose target's phases: a stage as one Docker Compose stack behind\n * Caddy, on the machine the deploy runs on.\n *\n * The order is the point of this file, and the rules it keeps are these:\n *\n * - A tag is checked before anything happens (`validate`). Every app's image\n * is asked of the registry first; one missing image stops the run naming\n * them all, and nothing is written, pulled or started.\n * - The databases exist before the apps do (`provision`). Postgres comes up\n * alone, its databases, roles and grants are created and its migrations\n * applied from this machine, and only then do the apps start — so an app\n * never boots against a schema that is not there yet.\n * - What a stage runs is recorded (`release`). Each app's image, the tag it\n * was released under and the digest it resolved to are written to the\n * stage's state, so \"what is this stage running\" has an exact answer.\n * - What was started answers (`verify`): each app is asked through Caddy,\n * over HTTPS, with the certificate verified.\n *\n * Docker, the registry, the bundler, Postgres and the health probe are\n * injected, so the rules are asserted without a daemon; the defaults are the\n * real ones.\n */\n\nimport { existsSync } from 'node:fs';\nimport {\n\tappendFile,\n\tchmod,\n\tmkdir,\n\treadFile,\n\twriteFile,\n} from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { ConstructManifest } from '@geekmidas/manifest';\nimport { stringify } from 'yaml';\nimport {\n\ttype ComposeDocker,\n\tdockerCompose,\n\ttype StackRef,\n} from '../../compose/docker';\nimport { assertImagesExist } from '../../compose/images';\nimport {\n\ttype ComposeStack,\n\tcomposeProject,\n\tcomposeStack,\n\tEDGE_PORT_ENV,\n\tenvFile,\n\ttype StackApp,\n\tstackDir,\n} from '../../compose/stack';\nimport type { ResourceChange } from '../../deploy/events';\nimport { withGeneratedSecrets } from '../../deploy/generated.js';\nimport { DeployJournal } from '../../deploy/journal';\nimport {\n\tcreateEmptyState,\n\ttype DeployedImage,\n\tsetDeployedImage,\n} from '../../deploy/state.js';\nimport type { DeployResult } from '../../deploy/types';\nimport { appPackageName } from '../../docker/index.js';\nimport {\n\tdetectPackageManager,\n\tgenerateDockerignore,\n} from '../../docker/templates.js';\nimport { migrateDatabases } from '../../migrate/databases.js';\nimport { pgClient } from '../../reconcile/clients.js';\nimport { primaryPortKey } from '../../reconcile/containers.js';\nimport { type ConstructSource, discover } from '../../reconcile/discover.js';\nimport { envFor } from '../../reconcile/env.js';\nimport {\n\tapplyPostgres,\n\tpostgresStatements,\n} from '../../reconcile/provision.js';\nimport { constructGlobs } from '../../reconcile/workspace.js';\nimport { runOutput } from '../../run';\nimport { LocalSandbox } from '../../sandbox/local';\nimport { activeSandbox } from '../../sandbox/sandbox';\nimport { initStageSecrets } from '../../secrets/storage.js';\nimport type { StageSecrets } from '../../secrets/types.js';\nimport type { NormalizedWorkspace } from '../../workspace/types.js';\nimport type { DeployPhaseContext } from '../types';\nimport {\n\tComposeAppsUnhealthy,\n\ttype HealthProbe,\n\thttpsProbe,\n\tisHealthy,\n} from './health';\n\n/** Every phase's context: the compose target takes no options. */\nexport type ComposeContext = DeployPhaseContext<undefined>;\n\n/** What the target needs from outside the process, so tests can stand in. */\nexport interface ComposeDeps {\n\tdocker: ComposeDocker;\n\t/**\n\t * Bundle one backend for production, in its own directory. By default\n\t * `gkm build --provider server --production`, run in the deploy's sandbox:\n\t * it imports the project's code.\n\t */\n\tbundle: (appRoot: string, ctx: ComposeContext) => Promise<void>;\n\t/** The commit a build is tagged with. */\n\trevision: (root: string) => Promise<string>;\n\t/** The Postgres client provisioning and migrations connect through. */\n\tsql: typeof pgClient;\n\t/** Apply the stage's migrations — `migrateDatabases` by default. */\n\tmigrate: typeof migrateDatabases;\n\t/** Ask one app, through the edge, whether it answers. */\n\tprobe: HealthProbe;\n\t/** How many times each app is asked before it counts as down. */\n\thealthAttempts: number;\n\t/** How long to wait between two asks of one app. */\n\thealthIntervalMs: number;\n\t/** Where the edge's ports are read from (`GKM_COMPOSE_HTTPS_PORT`, …). */\n\tenv: NodeJS.ProcessEnv;\n}\n\n/** One app's image, as the stage runs it. */\nexport interface ComposeImage {\n\tref: string;\n\ttag: string;\n\tdigest?: string;\n}\n\n/** A compose deploy in progress: what `validate` found, and what follows. */\nexport interface ComposeRun {\n\t/** Build the images from this checkout, or pull the ones a tag names. */\n\tmode: 'build' | 'pull';\n\tstack: ComposeStack;\n\t/** The stack's directory, absolute: `.gkm/compose/<stage>`. */\n\tdir: string;\n\tref: StackRef;\n\t/** The stage's secrets, with whatever a deployed stage generates once. */\n\tsecrets: StageSecrets | null;\n\t/** The names of what was generated, written back in `provision`. */\n\tgenerated: string[];\n\t/** Each app's image as the stage's state last recorded it. */\n\tprevious: Record<string, DeployedImage>;\n\t/** Every file written, absolute. */\n\tfiles: string[];\n\t/** Each app's image, once built or pulled. */\n\timages: Record<string, ComposeImage>;\n\t/** Every resource the run touched, or for a dry run would. */\n\tchanges: ResourceChange[];\n}\n\n/** A port variable that does not hold a port. */\nexport class EdgePortInvalid extends Error {\n\tconstructor(\n\t\treadonly variable: string,\n\t\treadonly value: string,\n\t) {\n\t\tsuper(\n\t\t\t`${variable}=${value} is not a port. Set it to a number between 1 and 65535, or unset it for the default.`,\n\t\t);\n\t\tthis.name = 'EdgePortInvalid';\n\t}\n}\n\n/** A build with no commit to tag its images with. */\nexport class NoGitRevision extends Error {\n\tconstructor(readonly root: string) {\n\t\tsuper(\n\t\t\t`Images built here are tagged with the commit they were built from, ` +\n\t\t\t\t`and ${root} is not in a git repository with a commit. Commit, or ` +\n\t\t\t\t`pass --tag to name the images yourself.`,\n\t\t);\n\t\tthis.name = 'NoGitRevision';\n\t}\n}\n\n/** A backend's production bundle did not build. */\nexport class BundleFailed extends Error {\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly exitCode: number | null,\n\t\treadonly stderr: string,\n\t) {\n\t\tsuper(\n\t\t\t`Bundling '${app}' for production failed (exit code ${exitCode}).` +\n\t\t\t\t`${stderr.trim() ? `\\n${stderr.trim().slice(-4000)}` : ''}\\n` +\n\t\t\t\t`Run \\`gkm build --provider server --production\\` in the app's directory to see it again.`,\n\t\t);\n\t\tthis.name = 'BundleFailed';\n\t}\n}\n\n/** The CLI's own entry, which the bundle step runs, is not where it should be. */\nexport class CliEntryNotFound extends Error {\n\tconstructor(readonly from: string) {\n\t\tsuper(\n\t\t\t`Could not find bin/gkm.mjs above ${from}, which bundling a backend runs. Reinstall @geekmidas/cli.`,\n\t\t);\n\t\tthis.name = 'CliEntryNotFound';\n\t}\n}\n\n/** The commit HEAD is at, short — and `-dirty` when the tree has changes. */\nexport async function gitRevision(root: string): Promise<string> {\n\tlet sha: string;\n\ttry {\n\t\tsha = (\n\t\t\tawait runOutput('git', ['rev-parse', '--short', 'HEAD'], { cwd: root })\n\t\t).trim();\n\t} catch {\n\t\tthrow new NoGitRevision(root);\n\t}\n\tif (!sha) throw new NoGitRevision(root);\n\n\t// An image built from uncommitted changes is not the commit's image, and a\n\t// tag that said it was would be the one thing worse than no tag.\n\tconst status = await runOutput('git', ['status', '--porcelain'], {\n\t\tcwd: root,\n\t}).catch(() => '');\n\treturn status.trim() ? `${sha}-dirty` : sha;\n}\n\n/** The edge's ports, from the environment — 443 and 80 when unset. */\nexport function edgePorts(env: NodeJS.ProcessEnv = process.env): {\n\thttps: number;\n\thttp: number;\n} {\n\tconst read = (variable: string, fallback: number) => {\n\t\tconst value = env[variable];\n\t\tif (value === undefined || value === '') return fallback;\n\t\tconst port = Number(value);\n\t\tif (!Number.isInteger(port) || port < 1 || port > 65_535) {\n\t\t\tthrow new EdgePortInvalid(variable, value);\n\t\t}\n\t\treturn port;\n\t};\n\treturn {\n\t\thttps: read(EDGE_PORT_ENV.https, 443),\n\t\thttp: read(EDGE_PORT_ENV.http, 80),\n\t};\n}\n\n/** `bin/gkm.mjs` of the CLI that is running. */\nfunction cliEntry(): string {\n\tconst from = dirname(fileURLToPath(import.meta.url));\n\tfor (let dir = from; dirname(dir) !== dir; dir = dirname(dir)) {\n\t\tconst entry = join(dir, 'bin', 'gkm.mjs');\n\t\tif (existsSync(entry)) return entry;\n\t}\n\tthrow new CliEntryNotFound(from);\n}\n\n/** A cold production bundle is seconds; a hung one must not hold the lock. */\nconst BUNDLE_TIMEOUT_MS = 10 * 60_000;\n\n/**\n * Bundle a backend with the gkm that is running — the same build a deploy\n * makes, so the image holds one file and no dependencies — in the run's\n * sandbox, because the build imports the project's code.\n */\nasync function bundleInSandbox(\n\tappRoot: string,\n\tctx: ComposeContext,\n): Promise<void> {\n\tconst sandbox = activeSandbox() ?? new LocalSandbox({ root: ctx.cwd });\n\tconst result = await sandbox.exec(\n\t\t'node',\n\t\t[cliEntry(), 'build', '--provider', 'server', '--production'],\n\t\t{\n\t\t\tcwd: appRoot,\n\t\t\tenv: sandbox.env,\n\t\t\ttimeoutMs: BUNDLE_TIMEOUT_MS,\n\t\t\toutput: ctx.childOutput,\n\t\t\tsignal: ctx.signal,\n\t\t},\n\t);\n\tif (result.exitCode !== 0) {\n\t\tthrow new BundleFailed(appRoot, result.exitCode, result.stderr);\n\t}\n}\n\n/**\n * The real ones. Each app is asked for three minutes before it counts as\n * down: a deployed stage's first ACME certificate can take that long.\n */\nexport const defaultDeps: ComposeDeps = {\n\tdocker: dockerCompose,\n\tbundle: bundleInSandbox,\n\trevision: gitRevision,\n\tsql: pgClient,\n\tmigrate: migrateDatabases,\n\tprobe: httpsProbe,\n\thealthAttempts: 90,\n\thealthIntervalMs: 2_000,\n\tenv: process.env,\n};\n\n// ============================================================================\n// validate\n// ============================================================================\n\n/**\n * The stack the stage would run, and — for a tag — the registry's word that\n * every image it names exists. Nothing is written: a missing image stops the\n * run here, before a file, a secret or a container is touched.\n */\nexport async function validateCompose(\n\tctx: ComposeContext,\n\tdeps: ComposeDeps,\n\tforced?: 'build' | 'pull',\n): Promise<ComposeRun> {\n\tconst { workspace, stage, identity, tag } = ctx;\n\tconst root = workspace.root;\n\tconst mode = forced ?? (ctx.tagGiven ? 'pull' : 'build');\n\n\t// The runnables' edges say which surface reaches which; the manifest the\n\t// run discovered has the declarations but not them.\n\tconst runnables: Record<string, string[]> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: root,\n\t\trunnables,\n\t});\n\n\tconst { secrets, generated } = await stageSecrets(ctx, manifest);\n\n\tconst stack = composeStack({\n\t\tworkspace,\n\t\tmanifest,\n\t\trunnables,\n\t\tstage,\n\t\tidentity,\n\t\timages: {\n\t\t\tmode,\n\t\t\ttag,\n\t\t\t...(workspace.deploy?.registry\n\t\t\t\t? { registry: workspace.deploy.registry }\n\t\t\t\t: {}),\n\t\t},\n\t\tsecrets,\n\t\tports: edgePorts(deps.env),\n\t\tpackageManager: detectPackageManager(root),\n\t\tpackages: Object.fromEntries(\n\t\t\tObject.entries(workspace.apps).map(([name, app]) => [\n\t\t\t\tname,\n\t\t\t\tappPackageName(root, name, app.path),\n\t\t\t]),\n\t\t),\n\t});\n\n\t// A release is all of its images or none of them. A dry run asks nothing,\n\t// so it can be run without a registry login.\n\tif (mode === 'pull' && !ctx.dryRun) {\n\t\tawait assertImagesExist(deps.docker, tag, stack.apps);\n\t}\n\n\tconst dir = join(root, stackDir(stage));\n\tconst previous = (await ctx.state.read(stage))?.state.images ?? {};\n\n\treturn {\n\t\tmode,\n\t\tstack,\n\t\tdir,\n\t\tref: {\n\t\t\tproject: composeProject(identity),\n\t\t\tfile: join(dir, 'docker-compose.yml'),\n\t\t\tcwd: root,\n\t\t\toutput: ctx.childOutput,\n\t\t\tsignal: ctx.signal,\n\t\t},\n\t\tsecrets,\n\t\tgenerated,\n\t\tprevious,\n\t\tfiles: [],\n\t\timages: {},\n\t\tchanges: [],\n\t};\n}\n\n/**\n * The stage's secrets — and, for a deployed stage, everything it generates\n * once: its seed, and each declared secret and keyring. Kept only once the\n * run goes ahead (`provision`), so a dry run, or a tag that is not there,\n * leaves the stage's secrets as it found them.\n */\nasync function stageSecrets(\n\tctx: ComposeContext,\n\tmanifest: ConstructManifest,\n): Promise<{ secrets: StageSecrets | null; generated: string[] }> {\n\tconst stored = await ctx.secrets.read();\n\tif (ctx.stage === ctx.workspace.stages.local) {\n\t\treturn { secrets: stored, generated: [] };\n\t}\n\n\tconst { secrets, generated } = withGeneratedSecrets(\n\t\tstored ?? initStageSecrets(ctx.stage),\n\t\tmanifest,\n\t);\n\t// Read through the store they are masked; made up here, they are not yet.\n\tif (secrets.seed) ctx.secrets.mask(secrets.seed);\n\tfor (const value of Object.values(secrets.custom ?? {})) {\n\t\tctx.secrets.mask(value);\n\t}\n\treturn { secrets, generated };\n}\n\n// ============================================================================\n// plan (dry run)\n// ============================================================================\n\n/**\n * A dry run: the stack's files, written so they can be read, and what a real\n * run would do — no image is built or pulled, no container touched, nothing\n * recorded and no secret kept.\n */\nexport async function planCompose(\n\tctx: ComposeContext,\n\trun: ComposeRun,\n): Promise<void> {\n\tif (run.generated.length > 0) {\n\t\tctx.logger.info(\n\t\t\t`🔑 \"${ctx.stage}\" has no ${run.generated.join(', ')} yet; this dry run used values it did not keep.`,\n\t\t);\n\t}\n\trun.files = await writeStack(ctx.cwd, run.dir, run.stack);\n\tprintPlan(ctx, run);\n\n\tconst planned = (change: ResourceChange) => {\n\t\trun.changes.push(change);\n\t\tctx.emit({ type: 'resource.planned', ...change });\n\t};\n\tfor (const service of run.stack.infra) {\n\t\tplanned({\n\t\t\tkey: `service:${service}`,\n\t\t\tresourceType: 'service',\n\t\t\taction: 'ensure',\n\t\t});\n\t}\n\tfor (const app of run.stack.apps) {\n\t\tplanned({\n\t\t\tkey: `image:${app.name}`,\n\t\t\tresourceType: 'image',\n\t\t\taction: run.mode === 'build' ? 'build' : 'reuse',\n\t\t\tid: app.ref,\n\t\t});\n\t\tplanned({\n\t\t\tkey: `service:${app.name}`,\n\t\t\tresourceType: 'service',\n\t\t\taction: run.previous[app.name] ? 'reuse' : 'create',\n\t\t});\n\t}\n\tplanned({ key: 'service:caddy', resourceType: 'service', action: 'ensure' });\n}\n\n// ============================================================================\n// provision\n// ============================================================================\n\n/**\n * What the apps run on: the stage's generated secrets kept, the stack's files\n * written, its infrastructure started, and its databases, roles, grants and\n * migrations applied.\n */\nexport async function provisionCompose(\n\tctx: ComposeContext,\n\trun: ComposeRun,\n\tdeps: ComposeDeps,\n): Promise<void> {\n\tconst { stack, ref } = run;\n\n\tif (run.generated.length > 0 && run.secrets) {\n\t\tawait ctx.secrets.write(run.secrets);\n\t\tctx.logger.info(\n\t\t\t`🔑 Generated for \"${ctx.stage}\" (${ctx.secrets.store}): ${run.generated.join(', ')}`,\n\t\t);\n\t}\n\n\trun.files = await writeStack(ctx.cwd, run.dir, stack);\n\tprintPlan(ctx, run);\n\n\tif (stack.infra.length > 0) {\n\t\tctx.logger.info(`\\n🗄️ Starting ${stack.infra.join(', ')}…`);\n\t\tawait deps.docker.up(ref, stack.infra);\n\t\tfor (const service of stack.infra) {\n\t\t\tapplied(ctx, run, {\n\t\t\t\tkey: `service:${service}`,\n\t\t\t\tresourceType: 'service',\n\t\t\t\taction: 'ensure',\n\t\t\t\tid: `${stack.project}/${service}`,\n\t\t\t\tvia: Object.keys(run.previous).length > 0 ? 'recorded' : 'created',\n\t\t\t});\n\t\t}\n\t}\n\n\tif (stack.infra.includes('postgres')) {\n\t\tawait prepareDatabases(ctx, run, deps);\n\t}\n}\n\n/** Create what the plan names in the stack's Postgres, then migrate it. */\nasync function prepareDatabases(\n\tctx: ComposeContext,\n\trun: ComposeRun,\n\tdeps: ComposeDeps,\n): Promise<void> {\n\tconst { stack } = run;\n\tconst workspace: NormalizedWorkspace = ctx.workspace;\n\tconst port = await deps.docker.port(run.ref, 'postgres', 5432);\n\n\tconst statements = postgresStatements(\n\t\tstack.plan,\n\t\tworkspace.name,\n\t\tstack.credential.seed,\n\t);\n\tif (statements.length > 0) {\n\t\tctx.logger.info('🗄️ Creating databases, roles and grants…');\n\t\tawait applyPostgres(deps.sql(port, stack.credential.master), statements);\n\t}\n\n\t// Migrations run here, with the project's own Kysely, the way `gkm\n\t// migrate` runs them — so the constructs themselves are needed, which\n\t// only an in-process discovery hands back.\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\t// The owner URLs, on the port published to this machine.\n\tconst env = envFor(stack.plan, {\n\t\tports: { [primaryPortKey('postgres')]: port },\n\t\tproject: workspace.name,\n\t\tmaster: stack.credential.master,\n\t\t...(stack.credential.seed ? { seed: stack.credential.seed } : {}),\n\t});\n\tconst runs = await deps.migrate({\n\t\troot: workspace.root,\n\t\tmanifest,\n\t\tsources,\n\t\tenv,\n\t});\n\tfor (const { target, applied } of runs) {\n\t\tif (applied.length > 0) {\n\t\t\tctx.logger.info(`🗄️ ${target.migrations}: applied ${applied.length}`);\n\t\t}\n\t}\n}\n\n// ============================================================================\n// build\n// ============================================================================\n\n/**\n * Each app's image: built from this checkout — every backend bundled first,\n * on the host, with this CLI — or pulled at the tag. Nothing live changes.\n */\nexport async function buildCompose(\n\tctx: ComposeContext,\n\trun: ComposeRun,\n\tdeps: ComposeDeps,\n): Promise<void> {\n\tconst { stack, ref } = run;\n\tconst apps = stack.apps.map((app) => app.name);\n\n\tif (run.mode === 'build') {\n\t\tfor (const app of stack.apps.filter((a) => a.kind === 'rest-api')) {\n\t\t\tctx.signal.throwIfAborted();\n\t\t\tctx.logger.info(`\\n📦 Bundling ${app.name}…`);\n\t\t\tawait deps.bundle(join(ctx.cwd, app.path), ctx);\n\t\t}\n\t\tctx.logger.info('\\n🐳 Building images…');\n\t\tawait deps.docker.build(ref, apps);\n\t} else {\n\t\tctx.logger.info(`\\n🐳 Pulling ${ctx.tag}…`);\n\t\tawait deps.docker.pull(ref, apps);\n\t}\n\n\t// What each tag resolved to: the registry's digest for a pulled image, or\n\t// — for one built here and never pushed — its content id.\n\tfor (const app of stack.apps) {\n\t\tconst digest = await deps.docker.digest(app.ref);\n\t\trun.images[app.name] = {\n\t\t\tref: app.ref,\n\t\t\ttag: app.tag,\n\t\t\t...(digest ? { digest } : {}),\n\t\t};\n\t\tctx.emit({\n\t\t\ttype: 'artifact.built',\n\t\t\tapp: app.name,\n\t\t\timageRef: app.ref,\n\t\t\t...(digest ? { digest } : {}),\n\t\t});\n\t\trun.changes.push({\n\t\t\tkey: `image:${app.name}`,\n\t\t\tresourceType: 'image',\n\t\t\taction: run.mode === 'build' ? 'build' : 'reuse',\n\t\t\tid: digest ?? app.ref,\n\t\t});\n\t}\n}\n\n// ============================================================================\n// release\n// ============================================================================\n\n/**\n * The whole stack up — anything it no longer defines removed — and each\n * app's image recorded in the stage's state.\n */\nexport async function releaseCompose(\n\tctx: ComposeContext,\n\trun: ComposeRun,\n\tdeps: ComposeDeps,\n): Promise<void> {\n\tconst { stack, ref } = run;\n\n\tctx.logger.info('\\n🚀 Starting the stack…');\n\tawait deps.docker.up(ref);\n\n\tif (stack.local) {\n\t\t// Caddy's CA is generated on its first start. Copied out so a process —\n\t\t// `verify` among them — can trust it without installing anything.\n\t\tawait deps.docker\n\t\t\t.copyOut(\n\t\t\t\tref,\n\t\t\t\t'caddy',\n\t\t\t\t'/data/caddy/pki/authorities/local/root.crt',\n\t\t\t\tcaFile(run),\n\t\t\t)\n\t\t\t.catch(() => {});\n\t}\n\n\tawait recordImages(ctx, run);\n\n\tfor (const app of stack.apps) {\n\t\tconst image = run.images[app.name];\n\t\tapplied(ctx, run, {\n\t\t\tkey: `service:${app.name}`,\n\t\t\tresourceType: 'service',\n\t\t\taction: 'ensure',\n\t\t\tid: `${stack.project}/${app.name}`,\n\t\t\tvia: run.previous[app.name] ? 'recorded' : 'created',\n\t\t});\n\t\tctx.emit({\n\t\t\ttype: 'app.deployed',\n\t\t\tapp: app.name,\n\t\t\tapplicationId: `${stack.project}/${app.name}`,\n\t\t\timageRef: image?.ref ?? app.ref,\n\t\t\turl: app.url,\n\t\t});\n\t}\n\n\tctx.logger.info(`\\n✅ ${stack.project} is running:`);\n\tfor (const app of stack.apps) {\n\t\tctx.logger.info(` ${app.name.padEnd(12)} ${app.url}`);\n\t}\n\tif (stack.local) {\n\t\tctx.logger.info(\n\t\t\t`\\n🔐 Certificates are from Caddy's local CA: NODE_EXTRA_CA_CERTS=${join(stackDir(ctx.stage), 'caddy-root.crt')}`,\n\t\t);\n\t}\n}\n\n/**\n * Each app's image in the stage's state — the same `images` a Dokploy deploy\n * records: the ref it runs, the tag it was released under, and the digest\n * the tag resolved to.\n */\nasync function recordImages(\n\tctx: ComposeContext,\n\trun: ComposeRun,\n): Promise<void> {\n\tconst journal = await DeployJournal.open(ctx.state, ctx.stage, () =>\n\t\tcreateEmptyState(ctx.stage, '', ''),\n\t);\n\tfor (const app of run.stack.apps) {\n\t\tconst image = run.images[app.name] ?? { ref: app.ref, tag: app.tag };\n\t\tsetDeployedImage(journal.state, app.name, image);\n\t}\n\tjournal.state.identity = ctx.identity.key;\n\tawait journal.save();\n}\n\n// ============================================================================\n// verify\n// ============================================================================\n\n/**\n * Each app asked through Caddy, over HTTPS, the certificate verified: an API\n * at `/health`, a site at `/`. On the local stage the edge is this machine and\n * its certificates are Caddy's internal CA's, so the CA `release` copied out\n * is the one trusted and the request goes to 127.0.0.1.\n */\nexport async function verifyCompose(\n\tctx: ComposeContext,\n\trun: ComposeRun,\n\tdeps: ComposeDeps,\n): Promise<void> {\n\tconst { stack } = run;\n\tconst ca = stack.local\n\t\t? await readFile(caFile(run), 'utf-8').catch(() => undefined)\n\t\t: undefined;\n\n\tctx.logger.info('\\n🩺 Checking each app through Caddy…');\n\tconst results = await Promise.all(\n\t\tstack.apps.map((app) =>\n\t\t\tcheckApp(ctx, app, deps, { ca, local: stack.local }),\n\t\t),\n\t);\n\n\tconst down = results\n\t\t.filter((result) => !result.healthy)\n\t\t.map(({ app, url, last }) => ({ app, url, last }));\n\tif (down.length > 0) throw new ComposeAppsUnhealthy(stack.project, down);\n}\n\nasync function checkApp(\n\tctx: ComposeContext,\n\tapp: StackApp,\n\tdeps: ComposeDeps,\n\toptions: { ca: string | undefined; local: boolean },\n): Promise<{ app: string; url: string; last: string; healthy: boolean }> {\n\tconst url = `${app.url}${app.kind === 'site' ? '/' : '/health'}`;\n\tlet last = 'not asked';\n\n\tfor (let attempt = 1; attempt <= deps.healthAttempts; attempt++) {\n\t\tctx.signal.throwIfAborted();\n\t\tlet status: number | undefined;\n\t\ttry {\n\t\t\tstatus = await deps.probe({\n\t\t\t\turl,\n\t\t\t\t...(options.ca ? { ca: options.ca } : {}),\n\t\t\t\t...(options.local ? { connectTo: '127.0.0.1' } : {}),\n\t\t\t\ttimeoutMs: 10_000,\n\t\t\t\tsignal: ctx.signal,\n\t\t\t});\n\t\t\tlast = `HTTP ${status}`;\n\t\t} catch (error) {\n\t\t\tlast = error instanceof Error ? error.message : String(error);\n\t\t}\n\n\t\tconst healthy = status !== undefined && isHealthy(status);\n\t\tctx.emit({\n\t\t\ttype: 'health.checked',\n\t\t\tapp: app.name,\n\t\t\turl,\n\t\t\thealthy,\n\t\t\t...(status !== undefined ? { status } : {}),\n\t\t\tattempt,\n\t\t});\n\t\tif (healthy) {\n\t\t\tctx.logger.info(` ✓ ${app.name.padEnd(12)} ${url} (${last})`);\n\t\t\treturn { app: app.name, url, last, healthy };\n\t\t}\n\t\tif (attempt < deps.healthAttempts) {\n\t\t\tawait new Promise((resolve) =>\n\t\t\t\tsetTimeout(resolve, deps.healthIntervalMs),\n\t\t\t);\n\t\t}\n\t}\n\n\tctx.logger.warn(` ✗ ${app.name.padEnd(12)} ${url} (${last})`);\n\treturn { app: app.name, url, last, healthy: false };\n}\n\n// ============================================================================\n// result\n// ============================================================================\n\nexport function composeResult(\n\tctx: ComposeContext,\n\trun: ComposeRun,\n): DeployResult {\n\tconst apps = run.stack.apps.map((app) => {\n\t\tconst image = run.images[app.name];\n\t\treturn {\n\t\t\tappName: app.name,\n\t\t\ttype: app.kind === 'site' ? ('web' as const) : ('backend' as const),\n\t\t\tsuccess: !ctx.dryRun,\n\t\t\tapplicationId: `${run.stack.project}/${app.name}`,\n\t\t\timageRef: image?.ref ?? app.ref,\n\t\t\t...(image?.digest ? { digest: image.digest } : {}),\n\t\t\turl: app.url,\n\t\t};\n\t});\n\treturn {\n\t\tapps,\n\t\tprojectId: run.stack.project,\n\t\tsuccessCount: ctx.dryRun ? 0 : apps.length,\n\t\tfailedCount: 0,\n\t\tstage: ctx.stage,\n\t\tidentity: ctx.identity.key,\n\t\ttag: ctx.tag,\n\t\tdryRun: ctx.dryRun,\n\t\tenvironmentId: '',\n\t\tskipped: [...ctx.skipped],\n\t\turls: Object.fromEntries(run.stack.apps.map((app) => [app.name, app.url])),\n\t\tchanges: run.changes,\n\t};\n}\n\n// ============================================================================\n// Files\n// ============================================================================\n\nfunction caFile(run: ComposeRun): string {\n\treturn join(run.dir, 'caddy-root.crt');\n}\n\nfunction applied(\n\tctx: ComposeContext,\n\trun: ComposeRun,\n\tchange: ResourceChange & {\n\t\tid: string;\n\t\tvia: 'recorded' | 'resumed' | 'found' | 'created';\n\t},\n): void {\n\tconst { via, ...rest } = change;\n\trun.changes.push(rest);\n\tctx.emit({ type: 'resource.applied', ...rest, via });\n}\n\n/** Write the stack's files: owner-only, since the env files hold secrets. */\nasync function writeStack(\n\troot: string,\n\tdir: string,\n\tstack: ComposeStack,\n): Promise<string[]> {\n\tawait mkdir(dir, { recursive: true, mode: 0o700 });\n\tawait chmod(dir, 0o700);\n\n\tconst files: string[] = [];\n\tconst write = async (path: string, content: string, mode = 0o644) => {\n\t\tawait mkdir(dirname(path), { recursive: true });\n\t\tawait writeFile(path, content, { mode });\n\t\tawait chmod(path, mode);\n\t\tfiles.push(path);\n\t};\n\n\tawait write(join(dir, 'docker-compose.yml'), composeYaml(stack));\n\tawait write(join(dir, 'Caddyfile'), stack.caddyfile);\n\tfor (const app of stack.apps) {\n\t\tif (app.env)\n\t\t\tawait write(join(dir, `${app.name}.env`), envFile(app.env), 0o600);\n\t}\n\tfor (const [path, content] of Object.entries(stack.dockerfiles)) {\n\t\tawait write(join(root, path), content);\n\t}\n\n\t// The build context is the workspace, and the env files are in it: the\n\t// ignore file is what keeps a stage's secrets out of every image's context.\n\tawait ignoreStacks(root);\n\n\treturn files;\n}\n\n/** Make sure the build context leaves `.gkm/compose` out. */\nasync function ignoreStacks(root: string): Promise<void> {\n\tconst path = join(root, '.dockerignore');\n\tif (!existsSync(path)) {\n\t\tawait writeFile(path, generateDockerignore());\n\t\treturn;\n\t}\n\tconst current = await readFile(path, 'utf-8');\n\tif (/^\\/?\\.gkm\\/compose\\/?$|^\\/?\\.gkm\\/?$/m.test(current)) return;\n\tawait appendFile(\n\t\tpath,\n\t\t`${current.endsWith('\\n') ? '' : '\\n'}\\n# gkm compose's stacks: each app's env file holds its stage's secrets\\n.gkm/compose\\n`,\n\t);\n}\n\nfunction composeYaml(stack: ComposeStack): string {\n\tconst stage = stack.local ? '' : ` --stage ${stack.stage}`;\n\treturn `# Generated by gkm compose from the construct manifest — do not edit.\n# The ${stack.stage} stage's APIs and sites behind one Caddy. Each backend\n# reads exactly the keys in its own env file beside this one.\n#\n# gkm compose${stage} start or update it\n# gkm compose${stage} --down stop it\n${stringify(stack.compose, { lineWidth: 0, aliasDuplicateObjects: false })}`;\n}\n\nfunction printPlan(ctx: ComposeContext, run: ComposeRun): void {\n\tconst { stack, mode, files } = run;\n\tctx.logger.info(`\\n🧱 ${stack.project} — stage ${stack.stage}`);\n\tfor (const app of stack.apps) {\n\t\tctx.logger.info(\n\t\t\t` ${app.name.padEnd(12)} ${app.url.padEnd(40)} ${mode} ${app.ref}`,\n\t\t);\n\t}\n\tif (stack.infra.length > 0) {\n\t\tctx.logger.info(` infrastructure: ${stack.infra.join(', ')}`);\n\t}\n\tctx.logger.info(\n\t\t`\\n📝 Wrote ${files.length} file(s) under ${stackDir(stack.stage)}/`,\n\t);\n}\n","/**\n * The built-in `compose` target: a stage as one Docker Compose stack behind\n * Caddy, on the machine that runs the deploy.\n *\n * ```bash\n * gkm deploy --target compose --stage production --tag v1.4.0 # what CI pushed\n * gkm deploy --target compose --stage production # built from here\n * gkm deploy --target compose --stage development # the local stage\n * ```\n *\n * - `validate`: the stack (`composeStack`, pure) and, for a tag, every image\n * looked up in the registry — `ImageTagNotFound` before anything changes\n * - `plan`: the stack's files and what a run would create or reuse\n * - `provision`: generated secrets kept, files written, infrastructure up,\n * databases, roles, grants and migrations\n * - `build`: each backend bundled on the host and every image built — or, for\n * a tag, pulled\n * - `release`: `up --wait --remove-orphans`, and each app's image, tag and\n * digest in the stage's state\n * - `verify`: each app asked through Caddy over HTTPS\n *\n * `gkm compose` is this target with a few more switches (`--build`, `--pull`,\n * `--down`), run through the same `deploy()`.\n */\n\nimport { defineTarget } from '../define';\nimport type { DeployTarget } from '../types';\nimport {\n\tbuildCompose,\n\ttype ComposeDeps,\n\ttype ComposeRun,\n\tcomposeResult,\n\tdefaultDeps,\n\tplanCompose,\n\tprovisionCompose,\n\treleaseCompose,\n\tvalidateCompose,\n\tverifyCompose,\n} from './phases';\n\nexport {\n\tComposeAppsUnhealthy,\n\ttype HealthProbe,\n\ttype HealthRequest,\n\tHealthRequestTimedOut,\n} from './health';\nexport {\n\tBundleFailed,\n\tCliEntryNotFound,\n\ttype ComposeDeps,\n\ttype ComposeImage,\n\ttype ComposeRun,\n\tEdgePortInvalid,\n\tedgePorts,\n\tgitRevision,\n\tNoGitRevision,\n} from './phases';\n\nexport interface ComposeTargetOptions extends Partial<ComposeDeps> {\n\t/**\n\t * Build or pull whatever the tag says. By default a given tag is pulled\n\t * and no tag builds from the checkout.\n\t */\n\tmode?: 'build' | 'pull';\n\t/** Handed the finished run — `gkm compose` prints and returns from it. */\n\treport?: (run: ComposeRun) => void;\n}\n\n/** The compose target, with its Docker, bundler and probe as given. */\nexport function composeTarget(\n\toptions: ComposeTargetOptions = {},\n): DeployTarget<undefined, ComposeRun> {\n\tconst { mode, report, ...given } = options;\n\tconst deps: ComposeDeps = { ...defaultDeps, ...given };\n\n\treturn defineTarget<undefined, ComposeRun>({\n\t\tname: 'compose',\n\t\truntime: 'server',\n\t\tcapabilities: {\n\t\t\trollback: false,\n\t\t\tmigrations: 'target',\n\t\t\timages: true,\n\t\t\t// The stack runs where the deploy does, so the local stage is one\n\t\t\t// it can run — the `gkm dev` hostnames, behind Caddy's own CA.\n\t\t\tlocalStage: true,\n\t\t},\n\t\t// Images are pulled with this machine's own `docker login`.\n\t\tcredentials: [],\n\t\t// Built from the checkout, an image is named after its commit.\n\t\ttag: ({ cwd }) => deps.revision(cwd),\n\t\tvalidate: (ctx) => validateCompose(ctx, deps, mode),\n\t\tplan: (ctx, run) => planCompose(ctx, run),\n\t\tprovision: (ctx, run) => provisionCompose(ctx, run, deps),\n\t\tbuild: (ctx, run) => buildCompose(ctx, run, deps),\n\t\trelease: (ctx, run) => releaseCompose(ctx, run, deps),\n\t\tverify: (ctx, run) => verifyCompose(ctx, run, deps),\n\t\tresult: (ctx, run) => {\n\t\t\treport?.(run);\n\t\t\treturn composeResult(ctx, run);\n\t\t},\n\t});\n}\n"],"mappings":"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"}