@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,23 +1,21 @@
1
1
  import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
2
- import { s as run$1 } from "./local-CWR4Y_kW.mjs";
3
- import { d as output, r as discover } from "./discover-LMrqsWSU.mjs";
2
+ import { s as run$1, u as gkmHome } from "./local-CWR4Y_kW.mjs";
3
+ import { d as output, r as discover } from "./discover-BFoJWKGD.mjs";
4
4
  import { t as appKey } from "./derive-B4vDpzgP.mjs";
5
- import { O as TEST_STAGE, h as getPublicEnvPrefix, n as allConstructGlobs } from "./workspace-C06_pW4g.mjs";
5
+ import { O as TEST_STAGE, h as getPublicEnvPrefix, n as allConstructGlobs } from "./workspace-Be6uBhlF.mjs";
6
6
  import { ConfigNotFound, NotInAnApp, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig } from "./config.mjs";
7
- import { i as toEmbeddableSecrets } from "./storage-BzaUxmqe.mjs";
8
- import { i as FileSecretsStore, n as secretsStoreFor } from "./store-CLiHnqOY.mjs";
9
- import { c as runtimeOf } from "./resolve-DLkIXTct.mjs";
7
+ import { c as runtimeOf } from "./resolve-Db0tNlrW.mjs";
10
8
  import { i as DEFAULT_STORAGE, n as DEFAULT_CACHE, r as DEFAULT_EVENTS$1, t as readFakes } from "./fakes-C9RiT6XG.mjs";
11
9
  import { basename, dirname, join, parse, resolve } from "node:path";
12
- import { copyFileSync, existsSync, readFileSync, unlinkSync } from "node:fs";
13
- import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
14
- import { networkInterfaces } from "node:os";
10
+ import { constants, copyFileSync, existsSync, readFileSync, unlinkSync } from "node:fs";
11
+ import { chmod, copyFile, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
12
+ import { homedir, networkInterfaces } from "node:os";
15
13
  import { promisify } from "node:util";
16
- import { DEFAULT_POSTGRES_VERSION, cacheTable, cookieDomain, dependenciesOf, dependentsOf, externalApiUrl, kebabCase, mobileOrigins, provideKey, providedKeyFor, provisionOrder, publicEnvFor, schemeBase } from "@geekmidas/manifest";
14
+ import { DEFAULT_POSTGRES_VERSION, cacheTable, cookieDomain, dependenciesOf, dependentsOf, externalApiUrl, kebabCase, mobileOrigins, provideKey, providedKeyFor, provisionOrder, publicEnvFor, schemeBase, scopedName } from "@geekmidas/manifest";
17
15
  import { execFile, spawn } from "node:child_process";
18
16
  import { createServer } from "node:net";
19
17
  import { config } from "dotenv";
20
- import { createHash, randomBytes } from "node:crypto";
18
+ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
21
19
  import { formatKeyring, generateKeyring } from "@geekmidas/constructs/encryption";
22
20
  import { ownerRole, readerRole, roleStatements } from "@geekmidas/db/pg/roles";
23
21
  import * as snsUrl from "@geekmidas/events/sns/url";
@@ -28,6 +26,530 @@ import { CreateQueueCommand, GetQueueUrlCommand, SQSClient } from "@aws-sdk/clie
28
26
  import { Client } from "pg";
29
27
  import { stringify } from "yaml";
30
28
  import { cacheTableStatements } from "@geekmidas/cache/postgres";
29
+ //#region src/secrets/storage.ts
30
+ var storage_exports = /* @__PURE__ */ __exportAll({
31
+ initStageSecrets: () => initStageSecrets,
32
+ maskPassword: () => maskPassword,
33
+ toEmbeddableSecrets: () => toEmbeddableSecrets,
34
+ validateEnvironmentVariables: () => validateEnvironmentVariables,
35
+ withCustomSecret: () => withCustomSecret
36
+ });
37
+ /**
38
+ * Initialize an empty StageSecrets object for a stage.
39
+ */
40
+ function initStageSecrets(stage) {
41
+ const now = (/* @__PURE__ */ new Date()).toISOString();
42
+ return {
43
+ stage,
44
+ createdAt: now,
45
+ updatedAt: now,
46
+ services: {},
47
+ urls: {},
48
+ custom: {}
49
+ };
50
+ }
51
+ /**
52
+ * Convert StageSecrets to embeddable format (flat key-value pairs).
53
+ * This is what gets encrypted and embedded in the bundle.
54
+ */
55
+ function toEmbeddableSecrets(secrets) {
56
+ return {
57
+ ...secrets.urls,
58
+ ...secrets.custom,
59
+ ...secrets.services.postgres && {
60
+ POSTGRES_USER: secrets.services.postgres.username,
61
+ POSTGRES_PASSWORD: secrets.services.postgres.password,
62
+ POSTGRES_DB: secrets.services.postgres.database ?? "app",
63
+ POSTGRES_HOST: secrets.services.postgres.host,
64
+ POSTGRES_PORT: String(secrets.services.postgres.port)
65
+ },
66
+ ...secrets.services.redis && {
67
+ REDIS_PASSWORD: secrets.services.redis.password,
68
+ REDIS_HOST: secrets.services.redis.host,
69
+ REDIS_PORT: String(secrets.services.redis.port)
70
+ },
71
+ ...secrets.services.minio && {
72
+ STORAGE_ACCESS_KEY_ID: secrets.services.minio.username,
73
+ STORAGE_SECRET_ACCESS_KEY: secrets.services.minio.password,
74
+ STORAGE_BUCKET: secrets.services.minio.bucket ?? "app",
75
+ STORAGE_REGION: "eu-west-1",
76
+ STORAGE_FORCE_PATH_STYLE: "true"
77
+ },
78
+ ...secrets.services.mailpit && {
79
+ SMTP_HOST: secrets.services.mailpit.host,
80
+ SMTP_PORT: String(secrets.services.mailpit.port),
81
+ SMTP_USER: secrets.services.mailpit.username,
82
+ SMTP_PASS: secrets.services.mailpit.password,
83
+ SMTP_SECURE: "false",
84
+ MAIL_FROM: "noreply@localhost"
85
+ },
86
+ ...secrets.services.localstack && {
87
+ AWS_ACCESS_KEY_ID: secrets.services.localstack.accessKeyId ?? secrets.services.localstack.username,
88
+ AWS_SECRET_ACCESS_KEY: secrets.services.localstack.password,
89
+ AWS_REGION: secrets.services.localstack.region ?? "us-east-1",
90
+ AWS_ENDPOINT_URL: `http://${secrets.services.localstack.host}:${secrets.services.localstack.port}`
91
+ },
92
+ ...secrets.services.pgboss && {
93
+ PGBOSS_DB_USER: secrets.services.pgboss.username,
94
+ PGBOSS_DB_PASSWORD: secrets.services.pgboss.password
95
+ }
96
+ };
97
+ }
98
+ /** The same secrets with one custom value set, and `updatedAt` moved on. */
99
+ function withCustomSecret(secrets, key, value) {
100
+ return {
101
+ ...secrets,
102
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
103
+ custom: {
104
+ ...secrets.custom,
105
+ [key]: value
106
+ }
107
+ };
108
+ }
109
+ /**
110
+ * Mask a password for display (show first 4 and last 2 chars).
111
+ */
112
+ function maskPassword(password) {
113
+ if (password.length <= 8) return "********";
114
+ return `${password.slice(0, 4)}${"*".repeat(password.length - 6)}${password.slice(-2)}`;
115
+ }
116
+ /**
117
+ * Validate that all required environment variables are present in secrets.
118
+ *
119
+ * @param requiredVars - Array of environment variable names required by the application
120
+ * @param secrets - Stage secrets to validate against
121
+ * @returns Validation result with missing and provided variables
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];
126
+ * const secrets = await secretsStoreFor(workspace, 'production').then((s) => s.read('production'));
127
+ * const result = validateEnvironmentVariables(required, secrets);
128
+ *
129
+ * if (!result.valid) {
130
+ * console.error(`Missing environment variables: ${result.missing.join(', ')}`);
131
+ * }
132
+ * ```
133
+ */
134
+ function validateEnvironmentVariables(requiredVars, secrets) {
135
+ const embeddable = toEmbeddableSecrets(secrets);
136
+ const availableVars = new Set(Object.keys(embeddable));
137
+ const missing = [];
138
+ const provided = [];
139
+ for (const varName of requiredVars) if (availableVars.has(varName)) provided.push(varName);
140
+ else missing.push(varName);
141
+ return {
142
+ valid: missing.length === 0,
143
+ missing: missing.sort(),
144
+ provided: provided.sort(),
145
+ required: [...requiredVars].sort()
146
+ };
147
+ }
148
+ //#endregion
149
+ //#region src/deploy/identity.ts
150
+ /**
151
+ * Who a deploy is, on a target it shares with other workspaces.
152
+ *
153
+ * A Dokploy server is one flat namespace of projects, images and Docker
154
+ * service names. Deploys used to identify everything they made by the
155
+ * workspace name alone, so two workspaces called `shop` — or `Shop` and
156
+ * `shop` — deploying to one server landed in the same project, pushed over
157
+ * each other's images and redeployed each other's applications. A
158
+ * `DeployIdentity` is what a deploy names and claims its resources by instead.
159
+ */
160
+ /** A namespace that cannot be part of a Dokploy name or an image path. */
161
+ var DeployNamespaceInvalid = class extends Error {
162
+ namespace;
163
+ constructor(namespace) {
164
+ super(`deploy.namespace '${namespace}' is not usable as a name: use lowercase letters, digits and single '-' between them (e.g. 'acme' or 'acme-platform') in gkm.config.ts.`);
165
+ this.namespace = namespace;
166
+ this.name = "DeployNamespaceInvalid";
167
+ }
168
+ };
169
+ /** A workspace name with nothing a target can name a resource by. */
170
+ var DeployProjectNameInvalid = class extends Error {
171
+ workspaceName;
172
+ constructor(workspaceName) {
173
+ super(`The workspace name '${workspaceName}' has no letters or digits to name deployed resources by. Set "name" in gkm.config.ts.`);
174
+ this.workspaceName = workspaceName;
175
+ this.name = "DeployProjectNameInvalid";
176
+ }
177
+ };
178
+ const NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
179
+ /**
180
+ * Lowercase `[a-z0-9-]`, runs of anything else collapsed to one `-`: valid as
181
+ * a Docker image path component, a Docker service name and a Dokploy name.
182
+ */
183
+ function slug(value) {
184
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
185
+ }
186
+ /** The namespace a workspace is in when it does not name one. */
187
+ function defaultNamespace(workspaceName) {
188
+ return slug(kebabCase(workspaceName));
189
+ }
190
+ function deployIdentity(workspace, stage) {
191
+ const project = slug(workspace.name);
192
+ if (!project) throw new DeployProjectNameInvalid(workspace.name);
193
+ const fallback = defaultNamespace(workspace.name);
194
+ const namespace = workspace.deploy?.namespace ?? fallback;
195
+ if (!NAME.test(namespace)) throw new DeployNamespaceInvalid(namespace);
196
+ return {
197
+ namespace,
198
+ project,
199
+ stage,
200
+ key: `${namespace}/${project}`,
201
+ scope: namespace === fallback ? project : `${namespace}-${project}`
202
+ };
203
+ }
204
+ /**
205
+ * The token a Dokploy project's description carries to say which identity
206
+ * created it. A project with the right name and no marker belongs to someone
207
+ * else, and a deploy never adopts it.
208
+ */
209
+ function ownershipMarker(identity) {
210
+ return `gkm:${identity.key}`;
211
+ }
212
+ /** Whether a project description carries `identity`'s marker. */
213
+ function isOwnedBy(description, identity) {
214
+ const marker = ownershipMarker(identity);
215
+ return (description ?? "").split(/\s+/).includes(marker);
216
+ }
217
+ /** Any `gkm:` marker a description carries — whoever's it is. */
218
+ function markerOf(description) {
219
+ return (description ?? "").split(/\s+/).find((t) => t.startsWith("gkm:"));
220
+ }
221
+ /** What the Dokploy project is called. */
222
+ function projectName(identity) {
223
+ return identity.scope;
224
+ }
225
+ /**
226
+ * The repository an app's image is pushed to, below the registry:
227
+ * `<namespace>/<project>-<app>`.
228
+ *
229
+ * The namespace is a path segment of its own so that a registry that maps
230
+ * paths to owners (an organisation's packages, a Harbor project) can grant it
231
+ * as one; the app is joined to the project so the two read as one name.
232
+ */
233
+ function imageName(identity, app) {
234
+ return `${identity.namespace}/${identity.project}-${slug(kebabCase(app))}`;
235
+ }
236
+ /** The full image ref an app is built, pushed and deployed as. */
237
+ function imageRef(identity, app, registry, tag) {
238
+ const name = imageName(identity, app);
239
+ return registry ? `${registry.replace(/\/+$/, "")}/${name}:${tag}` : `${name}:${tag}`;
240
+ }
241
+ /**
242
+ * What one application is called on the target.
243
+ *
244
+ * The same rule the constructs use, through the same `scopedName`, scoped by
245
+ * the stage and the identity: the application beside
246
+ * `production-kitchen-sink-database` is `production-kitchen-sink-api`. A
247
+ * Dokploy application's name is also its Docker service name, which is unique
248
+ * on the whole server, so the namespace has to be in it too — `acme`'s and
249
+ * `globex`'s `shop` would otherwise both run `production-shop-api`.
250
+ *
251
+ * The app id is dropped when it repeats the project, so a project named for its
252
+ * one application is `production-kitchen-sink` rather than
253
+ * `production-kitchen-sink-kitchen-sink`.
254
+ */
255
+ function applicationName(identity, app) {
256
+ return slug(app) === identity.project ? `${identity.stage}-${identity.scope}` : scopedName([identity.stage, identity.scope], app);
257
+ }
258
+ //#endregion
259
+ //#region src/secrets/keystore.ts
260
+ /**
261
+ * The keys that open each stage's encrypted secrets file, kept outside the
262
+ * project in the CLI's home.
263
+ *
264
+ * Keys were kept at `~/.gkm/<folder>/<stage>.key`, named after the project
265
+ * directory — so two checkouts in folders with the same name (`~/work/api`
266
+ * and `~/oss/api`) shared one key, and the second `gkm secrets:init`
267
+ * overwrote the first project's. They are now kept under the project's
268
+ * identity, `<home>/keys/<namespace>/<project>/<stage>.key`, the same
269
+ * `<namespace>/<project>` a deploy claims its Dokploy project by. A key still
270
+ * at the old place is copied to the new one the first time it is read; the
271
+ * old file stays, so an older CLI on the same machine keeps working.
272
+ */
273
+ /** Key length for AES-256 encryption */
274
+ const KEY_LENGTH = 32;
275
+ /** A keystore project key that is not `<namespace>/<project>`. */
276
+ var KeystoreProjectInvalid = class extends Error {
277
+ key;
278
+ constructor(key) {
279
+ super(`'${key}' is not a keystore project: expected '<namespace>/<project>' in lowercase letters, digits and '-', as deployIdentity() produces. Pass the workspace's identity key.`);
280
+ this.key = key;
281
+ this.name = "KeystoreProjectInvalid";
282
+ }
283
+ };
284
+ /** A stage has no key, here or at the place keys used to be kept. */
285
+ var KeyNotFound = class extends Error {
286
+ stage;
287
+ project;
288
+ path;
289
+ constructor(stage, project, path) {
290
+ super(`Encryption key not found for stage "${stage}" in project "${project}". Expected key at: ${path}. Run \`gkm secrets:init --stage ${stage}\` to create one, or copy the stage's key there.`);
291
+ this.stage = stage;
292
+ this.project = project;
293
+ this.path = path;
294
+ this.name = "KeyNotFound";
295
+ }
296
+ };
297
+ const PROJECT_KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*$/;
298
+ /**
299
+ * The keystore key of a workspace: its deploy identity without the stage, so
300
+ * a project's keys and its Dokploy project are named by the same thing.
301
+ */
302
+ function projectKey(workspace) {
303
+ return deployIdentity(workspace, "").key;
304
+ }
305
+ /** The keystore project of a loaded workspace. */
306
+ function keystoreProject(workspace, home) {
307
+ const key = projectKey(workspace);
308
+ const unscoped = projectKey({ name: workspace.name });
309
+ return {
310
+ key,
311
+ legacy: [.../* @__PURE__ */ new Set([folderName(workspace.root), workspace.name])].filter((name) => !!name),
312
+ ...unscoped !== key ? { aliases: [unscoped] } : {},
313
+ ...home ? { home } : {}
314
+ };
315
+ }
316
+ /**
317
+ * Whether `name` stays inside the home it is joined to. A workspace name was
318
+ * joined as is — `@acme/shop` is `~/.gkm/@acme/shop` — but never upwards.
319
+ */
320
+ function isLegacyName(name) {
321
+ return name.split(/[\\/]/).every((part) => part !== ".." && part !== "");
322
+ }
323
+ /** The last segment of a path, as the old keystore named projects. */
324
+ function folderName(path) {
325
+ const name = path.split(/[\\/]/).filter(Boolean).at(-1);
326
+ return name && name !== "." && name !== ".." ? name : void 0;
327
+ }
328
+ /**
329
+ * Get the keystore directory for a project:
330
+ * `<home>/keys/<namespace>/<project>`.
331
+ */
332
+ function getKeystoreDir(project) {
333
+ if (!PROJECT_KEY.test(project.key)) throw new KeystoreProjectInvalid(project.key);
334
+ const [namespace, name] = project.key.split("/");
335
+ return join(project.home ?? gkmHome(), "keys", namespace, name);
336
+ }
337
+ /** Get the path to a stage's encryption key. */
338
+ function getKeyPath(stage, project) {
339
+ return join(getKeystoreDir(project), `${stage}.key`);
340
+ }
341
+ /**
342
+ * Where else a stage's key may be, most likely first: under an alias of the
343
+ * project, then where keys were kept before they were keyed by identity —
344
+ * under the configured home, then under `~/.gkm`, which is where every CLI
345
+ * before `GKM_HOME` wrote.
346
+ */
347
+ function legacyKeyPaths(stage, project) {
348
+ const aliases = (project.aliases ?? []).map((key) => getKeyPath(stage, {
349
+ key,
350
+ home: project.home
351
+ }));
352
+ const homes = [.../* @__PURE__ */ new Set([project.home ?? gkmHome(), join(homedir(), ".gkm")])];
353
+ const names = (project.legacy ?? []).filter(isLegacyName);
354
+ return [...aliases, ...names.flatMap((name) => homes.map((home) => join(home, name, `${stage}.key`)))];
355
+ }
356
+ /**
357
+ * Generate a new encryption key for a stage, stored owner-only at
358
+ * `<home>/keys/<namespace>/<project>/<stage>.key`.
359
+ */
360
+ async function generateKey(stage, project, flag = "w") {
361
+ const keyDir = getKeystoreDir(project);
362
+ const keyPath = getKeyPath(stage, project);
363
+ await mkdir(keyDir, {
364
+ recursive: true,
365
+ mode: 448
366
+ });
367
+ const key = randomBytes(KEY_LENGTH).toString("hex");
368
+ await writeFile(keyPath, key, {
369
+ mode: 384,
370
+ encoding: "utf-8",
371
+ flag
372
+ });
373
+ await chmod(keyPath, 384);
374
+ return key;
375
+ }
376
+ /**
377
+ * Copy a key from where it used to be kept to where it is kept now.
378
+ *
379
+ * Copied, not moved: an older CLI on the same machine still reads the old
380
+ * place. Exclusive, so two processes migrating at once cannot overwrite a key
381
+ * one of them already put there.
382
+ */
383
+ async function migrateLegacyKey(stage, project) {
384
+ const legacy = legacyKeyPaths(stage, project).find((path) => existsSync(path));
385
+ if (!legacy) return false;
386
+ const keyPath = getKeyPath(stage, project);
387
+ await mkdir(getKeystoreDir(project), {
388
+ recursive: true,
389
+ mode: 448
390
+ });
391
+ try {
392
+ await copyFile(legacy, keyPath, constants.COPYFILE_EXCL);
393
+ } catch (error) {
394
+ if (error.code !== "EEXIST") throw error;
395
+ }
396
+ await chmod(keyPath, 384);
397
+ return true;
398
+ }
399
+ /**
400
+ * Read an encryption key for a stage, or `null` when there is none — after
401
+ * copying it over from where keys used to be kept, if it is still there.
402
+ */
403
+ async function readKey(stage, project) {
404
+ const keyPath = getKeyPath(stage, project);
405
+ if (!existsSync(keyPath) && !await migrateLegacyKey(stage, project)) return null;
406
+ return (await readFile(keyPath, "utf-8")).trim();
407
+ }
408
+ /**
409
+ * Read an encryption key for a stage, throwing {@link KeyNotFound} if there
410
+ * is none.
411
+ */
412
+ async function requireKey(stage, project) {
413
+ const key = await readKey(stage, project);
414
+ if (!key) throw new KeyNotFound(stage, project.key, getKeyPath(stage, project));
415
+ return key;
416
+ }
417
+ /**
418
+ * Get or create a key for a stage.
419
+ * If the key already exists, it is returned. Otherwise, a new key is generated.
420
+ */
421
+ async function getOrCreateKey(stage, project) {
422
+ const existingKey = await readKey(stage, project);
423
+ if (existingKey) return existingKey;
424
+ try {
425
+ return await generateKey(stage, project, "wx");
426
+ } catch (error) {
427
+ if (error.code !== "EEXIST") throw error;
428
+ return requireKey(stage, project);
429
+ }
430
+ }
431
+ //#endregion
432
+ //#region src/secrets/file.ts
433
+ /** Where the files live, relative to the project root. */
434
+ const SECRETS_DIR = ".gkm/secrets";
435
+ /** AES-256-GCM configuration */
436
+ const ALGORITHM = "aes-256-gcm";
437
+ const IV_LENGTH = 12;
438
+ /**
439
+ * A stage's secrets as an encrypted file on this machine:
440
+ * `.gkm/secrets/<stage>.json`, with its key in the CLI's home at
441
+ * `keys/<namespace>/<project>/<stage>.key`.
442
+ *
443
+ * The local stage's store, always — its secrets belong to the machine running
444
+ * `gkm dev` — and a deployed stage's when `secrets.store` is `'file'`.
445
+ */
446
+ var FileSecretsStore = class {
447
+ root;
448
+ name = "file";
449
+ project;
450
+ /**
451
+ * @param project - Whose keys: a workspace's `keystoreProject()`. Outside a
452
+ * workspace there is no config to name the project, so it is named the
453
+ * way a workspace without `name` is — its package name, else its folder.
454
+ */
455
+ constructor(root, project) {
456
+ this.root = root;
457
+ this.project = project ?? {
458
+ key: projectKey({ name: packageName(root) ?? basename(root) }),
459
+ legacy: [basename(root)]
460
+ };
461
+ }
462
+ /** The file a stage's secrets are kept in. */
463
+ path(stage) {
464
+ return join(this.root, SECRETS_DIR, `${stage}.json`);
465
+ }
466
+ async read(stage) {
467
+ const path = this.path(stage);
468
+ if (!existsSync(path)) return null;
469
+ const data = JSON.parse(await readFile(path, "utf-8"));
470
+ if (!(data.version === 1 && data.encrypted && data.iv)) return data;
471
+ const key = await readKey(stage, this.project);
472
+ if (!key) throw new MissingSecretsKey(stage, this.project.key, getKeyPath(stage, this.project));
473
+ return decrypt(data, key);
474
+ }
475
+ async write(stage, secrets) {
476
+ await mkdir(join(this.root, SECRETS_DIR), { recursive: true });
477
+ const key = await getOrCreateKey(stage, this.project);
478
+ await writeFile(this.path(stage), JSON.stringify(encrypt(secrets, key), null, 2), "utf-8");
479
+ }
480
+ };
481
+ /** A stage's secrets file exists, and the key that opens it does not. */
482
+ var MissingSecretsKey = class extends Error {
483
+ stage;
484
+ project;
485
+ path;
486
+ constructor(stage, project, path) {
487
+ super(`Decryption key not found for stage "${stage}". Expected key at: ${path}. Copy the stage's key there from whoever created it, or set secrets.store to a store this machine can reach.`);
488
+ this.stage = stage;
489
+ this.project = project;
490
+ this.path = path;
491
+ this.name = "MissingSecretsKey";
492
+ }
493
+ };
494
+ /** A project's package name without its scope, as a workspace name reads it. */
495
+ function packageName(root) {
496
+ try {
497
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
498
+ return typeof pkg.name === "string" ? pkg.name.replace(/^@[^/]+\//, "") : void 0;
499
+ } catch {
500
+ return;
501
+ }
502
+ }
503
+ function encrypt(secrets, keyHex) {
504
+ const key = Buffer.from(keyHex, "hex");
505
+ const iv = randomBytes(IV_LENGTH);
506
+ const cipher = createCipheriv(ALGORITHM, key, iv);
507
+ const ciphertext = Buffer.concat([cipher.update(JSON.stringify(secrets), "utf-8"), cipher.final()]);
508
+ return {
509
+ version: 1,
510
+ encrypted: Buffer.concat([ciphertext, cipher.getAuthTag()]).toString("base64"),
511
+ iv: iv.toString("hex")
512
+ };
513
+ }
514
+ function decrypt(data, keyHex) {
515
+ const key = Buffer.from(keyHex, "hex");
516
+ const combined = Buffer.from(data.encrypted, "base64");
517
+ const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(data.iv, "hex"));
518
+ decipher.setAuthTag(combined.subarray(-16));
519
+ const plaintext = Buffer.concat([decipher.update(combined.subarray(0, -16)), decipher.final()]);
520
+ return JSON.parse(plaintext.toString("utf-8"));
521
+ }
522
+ //#endregion
523
+ //#region src/secrets/store.ts
524
+ var store_exports = /* @__PURE__ */ __exportAll({
525
+ FileSecretsStore: () => FileSecretsStore,
526
+ isRemoteStore: () => isRemoteStore,
527
+ secretsStoreFor: () => secretsStoreFor
528
+ });
529
+ /**
530
+ * The store a stage's secrets are kept in — what every command reads and
531
+ * writes, for the stage it acts on.
532
+ *
533
+ * The local stage is always the file: its secrets belong to the machine
534
+ * running `gkm dev`. A deployed stage uses `secrets.store`.
535
+ */
536
+ async function secretsStoreFor(workspace, stage, options = {}) {
537
+ const configured = workspace.secrets.store ?? "file";
538
+ if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root, keystoreProject(workspace, options.home));
539
+ if (configured.provider !== "ssm") return configured.provider;
540
+ const { AwsSecretsStore } = await import("./aws-DojzQnLd.mjs");
541
+ return new AwsSecretsStore({
542
+ project: workspace.name,
543
+ region: configured.region,
544
+ ...options.profile ? { profile: options.profile } : {}
545
+ });
546
+ }
547
+ /** Whether a stage's secrets live somewhere other than this machine. */
548
+ function isRemoteStore(workspace, stage) {
549
+ const configured = workspace.secrets.store ?? "file";
550
+ return stage !== workspace.stages.local && configured !== "file";
551
+ }
552
+ //#endregion
31
553
  //#region src/credentials/index.ts
32
554
  const logger$1 = console;
33
555
  /**
@@ -3158,7 +3680,7 @@ function localAddress(port) {
3158
3680
  return `http://localhost:${port}`;
3159
3681
  }
3160
3682
  //#endregion
3161
- //#region src/deploy/domain.ts
3683
+ //#region src/target/dokploy/domain.ts
3162
3684
  /** A stage was deployed that `deploy.domains` gives no domain. */
3163
3685
  var NoDomainForStage = class extends Error {
3164
3686
  stage;
@@ -3772,6 +4294,12 @@ USER nextjs
3772
4294
 
3773
4295
  EXPOSE ${port}
3774
4296
 
4297
+ # A site answers at its root; Docker, and Dokploy, read the container's
4298
+ # health from it rather than from the process merely running. 127.0.0.1, not
4299
+ # localhost: the server listens on IPv4, and localhost can resolve to ::1.
4300
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
4301
+ CMD wget -qO- http://127.0.0.1:${port}/ > /dev/null 2>&1 || exit 1
4302
+
3775
4303
  ENTRYPOINT ["/sbin/tini", "--"]
3776
4304
  CMD ["node", "${appPath}/server.js"]
3777
4305
  `;
@@ -4039,6 +4567,12 @@ ENV HOSTNAME="0.0.0.0"
4039
4567
  USER app
4040
4568
  EXPOSE ${port}
4041
4569
 
4570
+ # A site answers at its root; Docker, and Dokploy, read the container's
4571
+ # health from it rather than from the process merely running. 127.0.0.1, not
4572
+ # localhost: the server listens on IPv4, and localhost can resolve to ::1.
4573
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
4574
+ CMD wget -qO- http://127.0.0.1:${port}/ > /dev/null 2>&1 || exit 1
4575
+
4042
4576
  ENTRYPOINT ["/sbin/tini", "--"]
4043
4577
  CMD ["sh", "-c", "cd ${appPath} && ${startCmd}"]
4044
4578
  `;
@@ -4086,6 +4620,11 @@ COPY --from=builder /app/${appPath}/dist /usr/share/nginx/html
4086
4620
  RUN printf 'server {\\n listen ${port};\\n root /usr/share/nginx/html;\\n index index.html;\\n location / { try_files $uri $uri/ /index.html; }\\n}\\n' \\
4087
4621
  > /etc/nginx/conf.d/default.conf
4088
4622
  EXPOSE ${port}
4623
+ # A site answers at its root; Docker, and Dokploy, read the container's
4624
+ # health from it rather than from the process merely running. 127.0.0.1, not
4625
+ # localhost: the server listens on IPv4, and localhost can resolve to ::1.
4626
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
4627
+ CMD wget -qO- http://127.0.0.1:${port}/ > /dev/null 2>&1 || exit 1
4089
4628
  CMD ["nginx", "-g", "daemon off;"]
4090
4629
  `;
4091
4630
  }
@@ -4584,13 +5123,60 @@ function createEmptyState(stage, projectId, environmentId) {
4584
5123
  function setApplicationId(state, appName, applicationId) {
4585
5124
  state.applications[appName] = applicationId;
4586
5125
  }
5126
+ /** Two records naming the same image: by digest when both have one. */
5127
+ function sameImage(a, b) {
5128
+ return a.digest && b.digest ? a.digest === b.digest : a.ref === b.ref;
5129
+ }
5130
+ /** The newest release in `history` after `index` that a rollback may restore. */
5131
+ function restorable(history, index, current) {
5132
+ return history.slice(index + 1).find((entry) => !entry.rolledBack && !sameImage(entry, current));
5133
+ }
4587
5134
  /**
4588
- * Record the image an app was deployed with (mutates state)
5135
+ * Record that `image` went live for `appName` (mutates state): it becomes
5136
+ * `current`, and what was current becomes `previous`. Releasing what already
5137
+ * runs — a redeploy of the same digest — changes nothing.
4589
5138
  */
4590
- function setDeployedImage(state, appName, image) {
4591
- state.images = {
4592
- ...state.images,
4593
- [appName]: image
5139
+ function recordRelease(state, appName, image, releasedAt = /* @__PURE__ */ new Date()) {
5140
+ const releases = state.releases?.[appName];
5141
+ if (releases && sameImage(releases.current, image)) return;
5142
+ const entry = {
5143
+ ...image,
5144
+ releasedAt: releasedAt.toISOString()
5145
+ };
5146
+ const history = [entry, ...releases?.history ?? []].slice(0, 10);
5147
+ const previous = releases?.current;
5148
+ state.releases = {
5149
+ ...state.releases,
5150
+ [appName]: {
5151
+ current: entry,
5152
+ ...previous ? { previous } : {},
5153
+ history
5154
+ }
5155
+ };
5156
+ }
5157
+ /**
5158
+ * Record that `appName` was rolled back to `to` (mutates state): every
5159
+ * release newer than it is marked rolled back, so neither this rollback nor
5160
+ * a later one restores it, and `previous` moves to the release before `to`.
5161
+ */
5162
+ function recordRollback(state, appName, to) {
5163
+ const releases = state.releases?.[appName];
5164
+ if (!releases) return;
5165
+ const index = releases.history.findIndex((entry) => !entry.rolledBack && sameImage(entry, to));
5166
+ if (index === -1) return;
5167
+ const history = releases.history.map((entry, i) => i < index ? {
5168
+ ...entry,
5169
+ rolledBack: true
5170
+ } : entry);
5171
+ const current = history[index];
5172
+ const previous = restorable(history, index, current);
5173
+ state.releases = {
5174
+ ...state.releases,
5175
+ [appName]: {
5176
+ current,
5177
+ ...previous ? { previous } : {},
5178
+ history
5179
+ }
4594
5180
  };
4595
5181
  }
4596
5182
  /**
@@ -4659,6 +5245,6 @@ function setPostgresBackupId(state, backupId) {
4659
5245
  if (state.backups) state.backups.postgresBackupId = backupId;
4660
5246
  }
4661
5247
  //#endregion
4662
- export { portKeys as $, workspace_exports as A, networkEnv as B, isMainFrontendApp as C, constructGlobs as D, backendsOf as E, postgresStatements as F, hostFor as G, localRolePassword as H, quoteIdentifier as I, providerOf as J, cacheBackendFor as K, composeFor as L, forgetState as M, applyPostgres as N, derivedContainers as O, postgresDatabaseNames as P, fakeKey as Q, pgClient as R, validateImageRef as S, FAKE_ENV as T, planFor as U, envFor as V, resourceName as W, DEFAULT_IMAGES as X, storageBackendFor as Y, describeServices as Z, dockerCommand as _, setApplicationId as a, loadEnvFiles as at, generateDockerignore as b, setDnsRecord as c, setPostgresBackupId as d, primaryPortKey as et, DeployJournal as f, appPackageName as g, DockerPushFailed as h, isDnsVerified as i, isPortAvailable as it, LOCAL_CA_PATH as j, reconcileWorkspace as k, setDnsVerification as l, DockerBuildFailed as m, getBackupState as n, createEntryWrapper as nt, setBackupState as o, loadSecretsForApp as ot, withGeneratedSecrets as p, eventsBackendFor as q, getGeneratedSecret as r, findAvailablePort as rt, setDeployedImage as s, prepareEntryCredentials as st, createEmptyState as t, createCredentialsPreload as tt, setGeneratedSecret as u, siteDockerfile as v, resolveHost as w, generateSlimDockerfile as x, detectPackageManager as y, appEnvKeys as z };
5248
+ export { describeServices as $, reconcileWorkspace as A, withCustomSecret as At, appEnvKeys as B, validateImageRef as C, markerOf as Ct, backendsOf as D, maskPassword as Dt, FAKE_ENV as E, initStageSecrets as Et, postgresDatabaseNames as F, planFor as G, networkEnv as H, postgresStatements as I, cacheBackendFor as J, resourceName as K, quoteIdentifier as L, LOCAL_CA_PATH as M, forgetState as N, constructGlobs as O, storage_exports as Ot, applyPostgres as P, DEFAULT_IMAGES as Q, composeFor as R, generateSlimDockerfile as S, isOwnedBy as St, resolveHost as T, projectName as Tt, envFor as U, dockerfileOf as V, localRolePassword as W, providerOf as X, eventsBackendFor as Y, storageBackendFor as Z, appPackageName as _, readKey as _t, recordRelease as a, findAvailablePort as at, detectPackageManager as b, imageName as bt, setBackupState as c, loadSecretsForApp as ct, setGeneratedSecret as d, secretsStoreFor as dt, fakeKey as et, setPostgresBackupId as f, store_exports as ft, DockerPushFailed as g, projectKey as gt, DockerBuildFailed as h, keystoreProject as ht, isDnsVerified as i, createEntryWrapper as it, workspace_exports as j, derivedContainers as k, toEmbeddableSecrets as kt, setDnsRecord as l, prepareEntryCredentials as lt, withGeneratedSecrets as m, getKeyPath as mt, getBackupState as n, primaryPortKey as nt, recordRollback as o, isPortAvailable as ot, DeployJournal as p, FileSecretsStore as pt, hostFor as q, getGeneratedSecret as r, createCredentialsPreload as rt, setApplicationId as s, loadEnvFiles as st, createEmptyState as t, portKeys as tt, setDnsVerification as u, isRemoteStore as ut, dockerCommand as v, applicationName as vt, isMainFrontendApp as w, ownershipMarker as wt, generateDockerignore as x, imageRef as xt, siteDockerfile as y, deployIdentity as yt, pgClient as z };
4663
5249
 
4664
- //# sourceMappingURL=state-B8YwmUZU.mjs.map
5250
+ //# sourceMappingURL=state-uuYqnRVO.mjs.map