@geekmidas/cli 10.0.0-alpha.76 → 10.0.0-alpha.78

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 (133) hide show
  1. package/dist/{LegacyStateStore-dyq07_Ir.mjs → LegacyStateStore-Ceq7A_75.mjs} +2 -2
  2. package/dist/{LegacyStateStore-dyq07_Ir.mjs.map → LegacyStateStore-Ceq7A_75.mjs.map} +1 -1
  3. package/dist/{LegacyStateStore-DwngfJxr.cjs → LegacyStateStore-Z-EKUGSX.cjs} +2 -2
  4. package/dist/{LegacyStateStore-DwngfJxr.cjs.map → LegacyStateStore-Z-EKUGSX.cjs.map} +1 -1
  5. package/dist/{LocalStateStore-B0NCde8Z.cjs → LocalStateStore-Bocb8dmZ.cjs} +2 -2
  6. package/dist/{LocalStateStore-B0NCde8Z.cjs.map → LocalStateStore-Bocb8dmZ.cjs.map} +1 -1
  7. package/dist/{LocalStateStore-DLxCCatI.mjs → LocalStateStore-C72bwpG2.mjs} +2 -2
  8. package/dist/{LocalStateStore-DLxCCatI.mjs.map → LocalStateStore-C72bwpG2.mjs.map} +1 -1
  9. package/dist/{S3StateStore-BOenb-3o.cjs → S3StateStore-35YfcdL_.cjs} +2 -2
  10. package/dist/{S3StateStore-BOenb-3o.cjs.map → S3StateStore-35YfcdL_.cjs.map} +1 -1
  11. package/dist/{S3StateStore-CGK_4hlo.mjs → S3StateStore-C9upSW5o.mjs} +2 -2
  12. package/dist/{S3StateStore-CGK_4hlo.mjs.map → S3StateStore-C9upSW5o.mjs.map} +1 -1
  13. package/dist/{SSMStateStore-DYeD__7p.cjs → SSMStateStore-W-Ps22AQ.cjs} +2 -2
  14. package/dist/{SSMStateStore-DYeD__7p.cjs.map → SSMStateStore-W-Ps22AQ.cjs.map} +1 -1
  15. package/dist/{SSMStateStore-B_5_RqY-.mjs → SSMStateStore-dOmFO6g1.mjs} +2 -2
  16. package/dist/{SSMStateStore-B_5_RqY-.mjs.map → SSMStateStore-dOmFO6g1.mjs.map} +1 -1
  17. package/dist/{add-j2yI3cbS.cjs → add-BvOICikp.cjs} +8 -8
  18. package/dist/{add-j2yI3cbS.cjs.map → add-BvOICikp.cjs.map} +1 -1
  19. package/dist/{add-B7IMXzou.mjs → add-D2XKszYP.mjs} +3 -3
  20. package/dist/{add-B7IMXzou.mjs.map → add-D2XKszYP.mjs.map} +1 -1
  21. package/dist/{bundler-Cf6OgaOe.cjs → bundler-C6T-1OLT.cjs} +5 -5
  22. package/dist/{bundler-Cf6OgaOe.cjs.map → bundler-C6T-1OLT.cjs.map} +1 -1
  23. package/dist/{bundler-DO6bHhnb.mjs → bundler-CtHzKqnZ.mjs} +5 -5
  24. package/dist/{bundler-DO6bHhnb.mjs.map → bundler-CtHzKqnZ.mjs.map} +1 -1
  25. package/dist/{cli-BCPOHmg9.mjs → cli-BgPyhx1i.mjs} +3 -3
  26. package/dist/{cli-BCPOHmg9.mjs.map → cli-BgPyhx1i.mjs.map} +1 -1
  27. package/dist/{cli-CNanUB7a.cjs → cli-K6Vj_pPW.cjs} +4 -4
  28. package/dist/{cli-CNanUB7a.cjs.map → cli-K6Vj_pPW.cjs.map} +1 -1
  29. package/dist/{compose-h_6oNnHC.cjs → compose-B7MC19eC.cjs} +668 -110
  30. package/dist/compose-B7MC19eC.cjs.map +1 -0
  31. package/dist/{compose-Dq4VCtu5.mjs → compose-DByK353T.mjs} +606 -66
  32. package/dist/compose-DByK353T.mjs.map +1 -0
  33. package/dist/{config-CqAvPP-f.d.mts → config-Bql0AmJt.d.mts} +3 -3
  34. package/dist/{config-CWyhLSY1.d.cts.map → config-Bql0AmJt.d.mts.map} +1 -1
  35. package/dist/{config-CWyhLSY1.d.cts → config-DlNpHK8p.d.cts} +3 -3
  36. package/dist/{config-CqAvPP-f.d.mts.map → config-DlNpHK8p.d.cts.map} +1 -1
  37. package/dist/config.cjs +1 -1
  38. package/dist/config.d.cts +3 -3
  39. package/dist/config.d.mts +3 -3
  40. package/dist/config.mjs +1 -1
  41. package/dist/deploy/public.cjs +12 -12
  42. package/dist/deploy/public.d.cts +3 -3
  43. package/dist/deploy/public.d.cts.map +1 -1
  44. package/dist/deploy/public.d.mts +3 -3
  45. package/dist/deploy/public.d.mts.map +1 -1
  46. package/dist/deploy/public.mjs +6 -6
  47. package/dist/{deploy-CVXEx7Nm.mjs → deploy-BerP7ppB.mjs} +5 -5
  48. package/dist/{deploy-CVXEx7Nm.mjs.map → deploy-BerP7ppB.mjs.map} +1 -1
  49. package/dist/{deploy-CT3R9de9.cjs → deploy-Dt42xiQA.cjs} +11 -11
  50. package/dist/{deploy-CT3R9de9.cjs.map → deploy-Dt42xiQA.cjs.map} +1 -1
  51. package/dist/{dokploy-eSGmlhhj.cjs → dokploy-NG9UrVrP.cjs} +2 -2
  52. package/dist/{dokploy-eSGmlhhj.cjs.map → dokploy-NG9UrVrP.cjs.map} +1 -1
  53. package/dist/{dokploy-CYm2ozFs.mjs → dokploy-ssZQRK2C.mjs} +2 -2
  54. package/dist/{dokploy-CYm2ozFs.mjs.map → dokploy-ssZQRK2C.mjs.map} +1 -1
  55. package/dist/{encryption-vS3Qzw73.cjs → encryption-CbMI24fI.cjs} +6 -6
  56. package/dist/{encryption-vS3Qzw73.cjs.map → encryption-CbMI24fI.cjs.map} +1 -1
  57. package/dist/{encryption-DC0rsR1W.mjs → encryption-De6MN0O5.mjs} +3 -3
  58. package/dist/{encryption-DC0rsR1W.mjs.map → encryption-De6MN0O5.mjs.map} +1 -1
  59. package/dist/{engine-C0N2d1i5.cjs → engine-CoCsNoW_.cjs} +73 -68
  60. package/dist/{engine-C0N2d1i5.cjs.map → engine-CoCsNoW_.cjs.map} +1 -1
  61. package/dist/{engine-BOO5TWZd.mjs → engine-DOjGIgl4.mjs} +9 -4
  62. package/dist/{engine-BOO5TWZd.mjs.map → engine-DOjGIgl4.mjs.map} +1 -1
  63. package/dist/{github-CEaFDL6t.mjs → github-CwX9z89E.mjs} +3 -3
  64. package/dist/{github-CEaFDL6t.mjs.map → github-CwX9z89E.mjs.map} +1 -1
  65. package/dist/{github-ClkdCOL9.cjs → github-D9vGqdLk.cjs} +7 -7
  66. package/dist/{github-ClkdCOL9.cjs.map → github-D9vGqdLk.cjs.map} +1 -1
  67. package/dist/{index-DOxmDKqv.d.mts → index-BXO0vlme.d.mts} +2 -2
  68. package/dist/{index-Ccu0f_2m.d.cts.map → index-BXO0vlme.d.mts.map} +1 -1
  69. package/dist/{index-Ccu0f_2m.d.cts → index-Rs0Fua-q.d.cts} +2 -2
  70. package/dist/{index-DOxmDKqv.d.mts.map → index-Rs0Fua-q.d.cts.map} +1 -1
  71. package/dist/index.cjs +180 -219
  72. package/dist/index.cjs.map +1 -1
  73. package/dist/index.mjs +108 -147
  74. package/dist/index.mjs.map +1 -1
  75. package/dist/{migrate-BRbYtFXG.cjs → migrate-B4GBvCNY.cjs} +5 -5
  76. package/dist/{migrate-BRbYtFXG.cjs.map → migrate-B4GBvCNY.cjs.map} +1 -1
  77. package/dist/{migrate-D3NAT7Ns.mjs → migrate-BQ4wVKq_.mjs} +3 -3
  78. package/dist/{migrate-D3NAT7Ns.mjs.map → migrate-BQ4wVKq_.mjs.map} +1 -1
  79. package/dist/{migrate-DrO2p9Rl.mjs → migrate-CNKaQl3K.mjs} +3 -3
  80. package/dist/{migrate-DrO2p9Rl.mjs.map → migrate-CNKaQl3K.mjs.map} +1 -1
  81. package/dist/{migrate-D5_-ylI1.cjs → migrate-Cgoy58Sh.cjs} +8 -8
  82. package/dist/{migrate-D5_-ylI1.cjs.map → migrate-Cgoy58Sh.cjs.map} +1 -1
  83. package/dist/openapi.cjs +1 -1
  84. package/dist/openapi.d.cts +1 -1
  85. package/dist/openapi.d.mts +1 -1
  86. package/dist/openapi.mjs +1 -1
  87. package/dist/reconcile/public.d.cts +1 -1
  88. package/dist/reconcile/public.d.mts +1 -1
  89. package/dist/{resolve-Fa1Ii0BK.cjs → resolve-CEaqRBzY.cjs} +3 -3
  90. package/dist/{resolve-Fa1Ii0BK.cjs.map → resolve-CEaqRBzY.cjs.map} +1 -1
  91. package/dist/{resolve-C1muFos5.mjs → resolve-mvaystiE.mjs} +3 -3
  92. package/dist/{resolve-C1muFos5.mjs.map → resolve-mvaystiE.mjs.map} +1 -1
  93. package/dist/{rollback-C9N5lKVf.cjs → rollback-F3P-rA-r.cjs} +7 -7
  94. package/dist/{rollback-C9N5lKVf.cjs.map → rollback-F3P-rA-r.cjs.map} +1 -1
  95. package/dist/{rollback-DeQzbRqM.mjs → rollback-S2C-Y9pB.mjs} +5 -5
  96. package/dist/{rollback-DeQzbRqM.mjs.map → rollback-S2C-Y9pB.mjs.map} +1 -1
  97. package/dist/{runtime-Cm7QA53H.d.cts → runtime-D9AxGzrw.d.cts} +2 -2
  98. package/dist/{runtime-BSvzPqXW.d.mts.map → runtime-D9AxGzrw.d.cts.map} +1 -1
  99. package/dist/{runtime-BSvzPqXW.d.mts → runtime-Dflki3Vs.d.mts} +2 -2
  100. package/dist/{runtime-Cm7QA53H.d.cts.map → runtime-Dflki3Vs.d.mts.map} +1 -1
  101. package/dist/{sniffer-Y6GQOCXB.cjs → sniffer-BjG-1mlD.cjs} +6 -6
  102. package/dist/{sniffer-Y6GQOCXB.cjs.map → sniffer-BjG-1mlD.cjs.map} +1 -1
  103. package/dist/{sniffer-DR8AlRCk.mjs → sniffer-ByWtH28i.mjs} +6 -6
  104. package/dist/{sniffer-DR8AlRCk.mjs.map → sniffer-ByWtH28i.mjs.map} +1 -1
  105. package/dist/{state-C2qi0iQw.cjs → stale-BsiPJI7Y.cjs} +111 -25
  106. package/dist/stale-BsiPJI7Y.cjs.map +1 -0
  107. package/dist/{state-CUecOCNJ.mjs → stale-C5FqIVc8.mjs} +106 -26
  108. package/dist/stale-C5FqIVc8.mjs.map +1 -0
  109. package/dist/target/public.cjs +1 -1
  110. package/dist/target/public.d.cts +2 -2
  111. package/dist/target/public.d.mts +2 -2
  112. package/dist/target/public.mjs +1 -1
  113. package/dist/{types-BdptGBFS.d.cts → types-ByY0zi3n.d.cts} +42 -3
  114. package/dist/types-ByY0zi3n.d.cts.map +1 -0
  115. package/dist/{types-OxE0OAn2.d.mts → types-DKxkXsKC.d.mts} +42 -3
  116. package/dist/types-DKxkXsKC.d.mts.map +1 -0
  117. package/dist/workspace/index.cjs +1 -1
  118. package/dist/workspace/index.d.cts +2 -2
  119. package/dist/workspace/index.d.mts +2 -2
  120. package/dist/workspace/index.mjs +1 -1
  121. package/dist/{workspace-QUTqYmVy.mjs → workspace-BBdqmkch.mjs} +114 -17
  122. package/dist/workspace-BBdqmkch.mjs.map +1 -0
  123. package/dist/{workspace-DOm81oUo.cjs → workspace-Bp8FEkF-.cjs} +130 -15
  124. package/dist/workspace-Bp8FEkF-.cjs.map +1 -0
  125. package/package.json +15 -15
  126. package/dist/compose-Dq4VCtu5.mjs.map +0 -1
  127. package/dist/compose-h_6oNnHC.cjs.map +0 -1
  128. package/dist/state-C2qi0iQw.cjs.map +0 -1
  129. package/dist/state-CUecOCNJ.mjs.map +0 -1
  130. package/dist/types-BdptGBFS.d.cts.map +0 -1
  131. package/dist/types-OxE0OAn2.d.mts.map +0 -1
  132. package/dist/workspace-DOm81oUo.cjs.map +0 -1
  133. package/dist/workspace-QUTqYmVy.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"sniffer-DR8AlRCk.mjs","names":[],"sources":["../src/secrets/encryption.ts","../src/deploy/events.ts","../src/deploy/StateProvider.ts","../src/deploy/StateStore.ts","../src/target/dokploy/dokploy-api.ts","../src/workspace/client-generator.ts","../src/deploy/sniffer.ts"],"sourcesContent":["import {\n\tcreateCipheriv,\n\tcreateDecipheriv,\n\tcreateHash,\n\trandomBytes,\n} from 'node:crypto';\nimport type { EmbeddableSecrets, EncryptedPayload } from './types';\n\n/** AES-256-GCM configuration */\nconst ALGORITHM = 'aes-256-gcm';\nconst KEY_LENGTH = 32; // 256 bits\nconst IV_LENGTH = 12; // 96 bits for GCM\nconst AUTH_TAG_LENGTH = 16; // 128 bits\n\n/**\n * Encrypt secrets for embedding in a bundle.\n * Uses AES-256-GCM with a randomly generated ephemeral key.\n *\n * @param secrets - Key-value pairs to encrypt\n * @returns Encrypted payload with ephemeral master key\n */\nexport function encryptSecrets(secrets: EmbeddableSecrets): EncryptedPayload {\n\t// Generate ephemeral key and IV\n\tconst masterKey = randomBytes(KEY_LENGTH);\n\tconst iv = randomBytes(IV_LENGTH);\n\n\t// Serialize secrets to JSON\n\tconst plaintext = JSON.stringify(secrets);\n\n\t// Encrypt\n\tconst cipher = createCipheriv(ALGORITHM, masterKey, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(plaintext, 'utf-8'),\n\t\tcipher.final(),\n\t]);\n\n\t// Get auth tag\n\tconst authTag = cipher.getAuthTag();\n\n\t// Combine ciphertext + auth tag\n\tconst combined = Buffer.concat([ciphertext, authTag]);\n\n\treturn {\n\t\tencrypted: combined.toString('base64'),\n\t\tiv: iv.toString('hex'),\n\t\tmasterKey: masterKey.toString('hex'),\n\t};\n}\n\n/**\n * Decrypt secrets from an encrypted payload.\n * Used at runtime to decrypt embedded credentials.\n *\n * @param encrypted - Base64 encoded ciphertext + auth tag\n * @param iv - Hex encoded IV\n * @param masterKey - Hex encoded master key\n * @returns Decrypted secrets\n */\nexport function decryptSecrets(\n\tencrypted: string,\n\tiv: string,\n\tmasterKey: string,\n): EmbeddableSecrets {\n\t// Decode inputs\n\tconst key = Buffer.from(masterKey, 'hex');\n\tconst ivBuffer = Buffer.from(iv, 'hex');\n\tconst combined = Buffer.from(encrypted, 'base64');\n\n\t// Split ciphertext and auth tag\n\tconst ciphertext = combined.subarray(0, -AUTH_TAG_LENGTH);\n\tconst authTag = combined.subarray(-AUTH_TAG_LENGTH);\n\n\t// Decrypt\n\tconst decipher = createDecipheriv(ALGORITHM, key, ivBuffer);\n\tdecipher.setAuthTag(authTag);\n\n\tconst plaintext = Buffer.concat([\n\t\tdecipher.update(ciphertext),\n\t\tdecipher.final(),\n\t]);\n\n\treturn JSON.parse(plaintext.toString('utf-8')) as EmbeddableSecrets;\n}\n\n/**\n * Generate the define options for tsdown/esbuild.\n * These will be injected at build time.\n */\nexport function generateDefineOptions(\n\tpayload: EncryptedPayload,\n): Record<string, string> {\n\treturn {\n\t\t__GKM_ENCRYPTED_CREDENTIALS__: JSON.stringify(payload.encrypted),\n\t\t__GKM_CREDENTIALS_IV__: JSON.stringify(payload.iv),\n\t};\n}\n\n/**\n * A short, one-way name for a master key: the first 8 hex characters of its\n * SHA-256.\n *\n * Printed where the key itself used to be, so a person can tell which key a\n * build used — and match it against the one they hold — without the output\n * carrying a value that decrypts every secret of the stage.\n */\nexport function keyFingerprint(masterKey: string): string {\n\treturn createHash('sha256').update(masterKey).digest('hex').slice(0, 8);\n}\n","/**\n * What a deploy reports while it runs.\n *\n * Plain JSON objects, discriminated by `type`: `gkm deploy --json` writes them\n * one per line, and a host can forward them to a UI or a log pipeline without\n * a serialiser of its own. Nothing in an event is a class instance, a function\n * or a secret — an error travels as its name and message, a credential never\n * travels at all.\n *\n * Every target reports through this one union, so a UI renders a Dokploy\n * deploy and a plugin's the same way.\n *\n * The union only grows: a new event is a new `type`, a new field is optional,\n * and nothing that exists changes meaning. A consumer ignores types it does\n * not know. That is why the events carry no version number — the CLI that\n * writes a stream and the program that reads it agree by that rule rather\n * than by negotiating one.\n */\n\nimport type { DeployResult } from './types';\n\n/**\n * The stretch of a deploy an event belongs to.\n *\n * - `validate`: the config, the target, the stage, the apps, the lock, the\n * stage's secrets and what each app reads from its environment\n * - `plan`: a dry run's lookups — what would be created and what reused\n * - `provision`: what the apps run on — for Dokploy the project, environment\n * and registry — and the declared constructs\n * - `build`: each app's artifact, built without changing anything live\n * - `release`: the artifacts put live. Dokploy applies the stage's\n * migrations, then builds and pushes each image here, beside its\n * application, because a site's build args are resolved per app — and\n * checks each backend before any site is released\n * - `verify`: what was released answers — for Dokploy, each site's DNS\n * records and health\n * - `rollback`: the previous release restored, after `release` or `verify`\n * failed, on a target that can\n */\nexport type DeployPhase =\n\t| 'validate'\n\t| 'plan'\n\t| 'provision'\n\t| 'build'\n\t| 'release'\n\t| 'verify'\n\t| 'rollback';\n\n/** An error, as an event carries it. */\nexport interface DeployEventError {\n\t/** The error's class name — `MissingCredential`, `StateLocked`. */\n\tname: string;\n\tmessage: string;\n}\n\n/**\n * How a resource came to be used:\n * - `recorded`: the id the stage's state holds still resolves\n * - `resumed`: a run that died after creating it is picked up\n * - `found`: it existed and was looked up\n * - `created`: this run created it\n */\nexport type ResourceVia = 'recorded' | 'resumed' | 'found' | 'created';\n\n/** One resource a deploy touches, planned or applied. */\nexport interface ResourceChange {\n\t/** `<type>:<name>`, unique within the stage — `application:api`. */\n\tkey: string;\n\t/** `project`, `environment`, `registry`, `application`, `domain`, `image`, or a construct's kind. */\n\tresourceType: string;\n\t/**\n\t * For a plan: whether the run would create it, reuse what exists, or\n\t * ensure it (look it up, and create it if missing). For an applied change:\n\t * what was done.\n\t */\n\taction: 'create' | 'reuse' | 'ensure' | 'build';\n\t/** The target's id, once there is one. */\n\tid?: string;\n}\n\nexport type DeployEvent =\n\t| {\n\t\t\ttype: 'deploy.started';\n\t\t\tstage: string;\n\t\t\t/** The target the run deploys through, by the name it was given. */\n\t\t\ttarget: string;\n\t\t\t/** `<namespace>/<project>`. */\n\t\t\tidentity: string;\n\t\t\ttag: string;\n\t\t\t/** The apps this run deploys, in order. */\n\t\t\tapps: string[];\n\t\t\tdryRun: boolean;\n\t }\n\t| { type: 'phase.started'; phase: DeployPhase }\n\t| { type: 'phase.finished'; phase: DeployPhase }\n\t/** A phase threw; `deploy.failed` follows (after a rollback, if any). */\n\t| { type: 'phase.failed'; phase: DeployPhase; error: DeployEventError }\n\t/**\n\t * A progress line, exactly as `gkm deploy` prints it — leading newline and\n\t * indentation included, so a terminal renderer prints `message` as is.\n\t */\n\t| { type: 'log'; level: 'info' | 'warn' | 'error'; message: string }\n\t| { type: 'app.skipped'; app: string; reason: string }\n\t| ({ type: 'resource.planned' } & ResourceChange)\n\t| ({ type: 'resource.applied'; via: ResourceVia } & ResourceChange & {\n\t\t\t\tid: string;\n\t\t\t})\n\t| {\n\t\t\ttype: 'artifact.built';\n\t\t\tapp: string;\n\t\t\timageRef: string;\n\t\t\t/** The registry's digest, `sha256:…`, once pushed. */\n\t\t\tdigest?: string;\n\t }\n\t| {\n\t\t\ttype: 'app.deployed';\n\t\t\tapp: string;\n\t\t\tapplicationId: string;\n\t\t\timageRef: string;\n\t\t\turl: string;\n\t }\n\t| { type: 'app.failed'; app: string; error: DeployEventError }\n\t/** One check of whether a released app answers. */\n\t| {\n\t\t\ttype: 'health.checked';\n\t\t\tapp: string;\n\t\t\t/** What was asked: a URL, or the target's own status check. */\n\t\t\turl: string;\n\t\t\thealthy: boolean;\n\t\t\t/** The HTTP status, when the check was a request. */\n\t\t\tstatus?: number;\n\t\t\t/** 1 for the first check of the app in this run. */\n\t\t\tattempt: number;\n\t }\n\t/**\n\t * A deployed stage runs a dev service — `--allow-dev-services` — in place\n\t * of real mail or object storage: Mailpit delivers no mail, MinIO keeps\n\t * every object on one container's disk. A warning, every run.\n\t */\n\t| {\n\t\t\ttype: 'dev-service.used';\n\t\t\tservice: 'minio' | 'mailpit';\n\t\t\tstage: string;\n\t\t\t/** The constructs it stands in for. */\n\t\t\tconstructs: string[];\n\t }\n\t/**\n\t * The stack's log UI (`deploy.compose.logs`) is up and answering: where\n\t * it is and how it is reached — through an SSH tunnel to a loopback\n\t * port, or publicly on a host that allows only some addresses.\n\t */\n\t| {\n\t\t\ttype: 'logs.ready';\n\t\t\tservice: 'openobserve';\n\t\t\taccess: 'tunnel' | 'public';\n\t\t\t/** Where a browser opens it: the tunnel's end, or its public host. */\n\t\t\turl: string;\n\t\t\t/** The loopback port it is published on, for a tunnel. */\n\t\t\tport?: number;\n\t\t\t/** The addresses a public host answers. */\n\t\t\tallow?: string[];\n\t\t\temail: string;\n\t }\n\t| { type: 'deploy.finished'; result: DeployResult }\n\t| { type: 'deploy.failed'; error: DeployEventError };\n\n/** Every `type` a deploy event can have. */\nexport type DeployEventType = DeployEvent['type'];\n\n/** An error as an event carries it: its name and message, nothing else. */\nexport function eventError(error: unknown): DeployEventError {\n\tif (error instanceof Error) {\n\t\treturn { name: error.name, message: error.message };\n\t}\n\treturn { name: 'Error', message: String(error) };\n}\n","/**\n * State configuration, and the `StateProvider` interface a custom backend can\n * implement.\n *\n * Deploy state is read and written through a `StateStore` (`StateStore.ts`):\n * locks, versions and resource records. A custom `StateProvider` — read and\n * write a whole state, nothing else — still works behind `LegacyStateStore`,\n * which warns that it cannot lock.\n */\n\nimport type { StateStore } from './StateStore';\nimport type { DokployStageState } from './state';\n\n/**\n * Interface for deployment state storage providers.\n *\n * Implementations must handle:\n * - Reading state for a stage (returns null if not found)\n * - Writing state for a stage (creates or updates)\n */\nexport interface StateProvider {\n\t/**\n\t * Read deployment state for a stage.\n\t *\n\t * @param stage - The deployment stage (e.g., 'development', 'production')\n\t * @returns The state object or null if not found\n\t */\n\tread(stage: string): Promise<DokployStageState | null>;\n\n\t/**\n\t * Write deployment state for a stage.\n\t *\n\t * @param stage - The deployment stage\n\t * @param state - The state object to persist\n\t */\n\twrite(stage: string, state: DokployStageState): Promise<void>;\n}\n\n/**\n * Valid AWS regions.\n */\nexport type AwsRegion =\n\t| 'us-east-1'\n\t| 'us-east-2'\n\t| 'us-west-1'\n\t| 'us-west-2'\n\t| 'af-south-1'\n\t| 'ap-east-1'\n\t| 'ap-south-1'\n\t| 'ap-south-2'\n\t| 'ap-southeast-1'\n\t| 'ap-southeast-2'\n\t| 'ap-southeast-3'\n\t| 'ap-southeast-4'\n\t| 'ap-northeast-1'\n\t| 'ap-northeast-2'\n\t| 'ap-northeast-3'\n\t| 'ca-central-1'\n\t| 'eu-central-1'\n\t| 'eu-central-2'\n\t| 'eu-west-1'\n\t| 'eu-west-2'\n\t| 'eu-west-3'\n\t| 'eu-south-1'\n\t| 'eu-south-2'\n\t| 'eu-north-1'\n\t| 'me-south-1'\n\t| 'me-central-1'\n\t| 'sa-east-1';\n\n/**\n * Local state provider config.\n */\nexport interface LocalStateConfig {\n\tprovider: 'local';\n}\n\n/**\n * SSM state provider config (requires region).\n */\nexport interface SSMStateConfig {\n\tprovider: 'ssm';\n\t/** AWS region (required for SSM provider) */\n\tregion: AwsRegion;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n}\n\n/**\n * S3 state config: one object per stage, written with conditional puts\n * (`If-Match` / `If-None-Match`), and a lock object beside it.\n *\n * Keys: `<prefix>/<workspace name>/<stage>/state.json` and `lock.json`.\n */\nexport interface S3StateConfig {\n\tprovider: 's3';\n\t/** Bucket the state lives in. It must already exist. */\n\tbucket: string;\n\t/** AWS region of the bucket */\n\tregion: AwsRegion;\n\t/** Key prefix inside the bucket (default: `gkm`) */\n\tprefix?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n}\n\n/**\n * Custom state provider config.\n */\nexport interface CustomStateConfig {\n\t/**\n\t * Custom implementation. A `StateStore` is used as is; a `StateProvider`\n\t * still works, but cannot lock — deploy warns `StateStoreWithoutLocking`.\n\t */\n\tprovider: StateProvider | StateStore;\n}\n\n/**\n * State configuration types.\n */\nexport type StateConfig =\n\t| LocalStateConfig\n\t| SSMStateConfig\n\t| S3StateConfig\n\t| CustomStateConfig;\n\n/**\n * Check if value is a StateProvider implementation.\n */\nexport function isStateProvider(value: unknown): value is StateProvider {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\ttypeof (value as StateProvider).read === 'function' &&\n\t\ttypeof (value as StateProvider).write === 'function'\n\t);\n}\n\nexport interface CreateStateStoreConfig {\n\t/** State config from workspace */\n\tconfig?: StateConfig;\n\t/** Workspace root directory (for local provider) */\n\tworkspaceRoot: string;\n\t/** Workspace name (for SSM parameter path) */\n\tworkspaceName: string;\n}\n","/**\n * Deploy state store.\n *\n * `StateProvider` reads and writes a whole stage at once, with nothing to stop\n * two runs of the same stage from overwriting each other and nowhere to record\n * a resource the moment it exists. A `StateStore` adds the three things a\n * deploy needs to be safe against crashes and concurrent runs:\n *\n * - `lock(stage)`: one run per stage. A second gets `StateLocked`, naming the\n * holder, instead of racing the first.\n * - Versioned `read` and conditional `write`: a write names the version it was\n * based on, and raises `StateVersionConflict` when the stored state moved on\n * underneath it, so nothing is lost silently even without the lock.\n * - Per-resource records: `putResource` journals one resource at a time, so a\n * run that dies half way leaves behind the ids of everything it created.\n *\n * Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A\n * custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it\n * working but cannot lock.\n */\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport { hostname, userInfo } from 'node:os';\nimport {\n\ttype CreateStateStoreConfig,\n\tisStateProvider,\n\ttype S3StateConfig,\n\ttype SSMStateConfig,\n} from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/** Opaque: a file hash, an SSM parameter version or an S3 ETag. */\nexport type StateVersion = string;\n\n/**\n * `pending` is written before the call that creates the resource, `ready`\n * once the target has returned its id. A run that finds a `pending` record\n * knows a previous run may have created the resource and should look for it\n * before creating another.\n */\nexport type ResourceStatus = 'pending' | 'ready';\n\nexport interface ResourceRecord {\n\t/** Unique within the stage, `<type>:<name>` — e.g. `application:api`. */\n\tkey: string;\n\t/** What kind of resource it is — e.g. `application`, `postgres`. */\n\ttype: string;\n\t/** The id the target gave it. Absent while `pending`. */\n\tid?: string;\n\tstatus: ResourceStatus;\n\t/** Whatever the target needs to adopt the resource on a later run. */\n\tdata?: Record<string, unknown>;\n\tupdatedAt: string;\n}\n\n/** A resource as a caller hands it to `putResource`; the store stamps it. */\nexport type ResourceInput = Omit<ResourceRecord, 'updatedAt'>;\n\nexport interface StoredStageState {\n\tstate: DokployStageState;\n\tresources: Record<string, ResourceRecord>;\n\t/** Pass back as `expectedVersion` to write on top of exactly this read. */\n\tversion: StateVersion;\n}\n\nexport interface WriteOptions {\n\t/**\n\t * The version this write is based on — `null` to create state that must\n\t * not exist yet.\n\t */\n\texpectedVersion: StateVersion | null;\n}\n\nexport interface ResourceWriteOptions {\n\t/**\n\t * Omitted, the record is applied on top of whatever is stored now (still\n\t * conditionally, so a concurrent write raises rather than being lost).\n\t */\n\texpectedVersion?: StateVersion;\n}\n\nexport interface LockHolder {\n\t/** Unique per acquisition, so a release never removes someone else's lock. */\n\tid: string;\n\towner: string;\n\thost: string;\n\tpid: number;\n\tacquiredAt: string;\n\t/** What the holder is doing, e.g. `deploy`. */\n\toperation?: string;\n}\n\nexport interface LockOptions {\n\toperation?: string;\n}\n\nexport interface StateLock {\n\treadonly stage: string;\n\treadonly holder: LockHolder;\n\t/** Removes the lock if it is still this one; safe to call twice. */\n\trelease(): Promise<void>;\n}\n\nexport interface StateStore {\n\t/** Takes the stage's lock, or raises `StateLocked` naming its holder. */\n\tlock(stage: string, options?: LockOptions): Promise<StateLock>;\n\t/**\n\t * Removes the stage's lock whoever holds it, for a run that crashed with\n\t * it held. Returns the holder it removed, or `null` if there was none.\n\t */\n\tforceUnlock(stage: string): Promise<LockHolder | null>;\n\tread(stage: string): Promise<StoredStageState | null>;\n\t/** Replaces the stage's state, keeping its resource records. */\n\twrite(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion>;\n\tputResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion>;\n\tdeleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion>;\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class StateLocked extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly holder: LockHolder | null,\n\t\treadonly location: string,\n\t) {\n\t\tconst by = holder\n\t\t\t? `${holder.owner}@${holder.host} (pid ${holder.pid}) since ${holder.acquiredAt}${holder.operation ? ` for '${holder.operation}'` : ''}`\n\t\t\t: 'a run that did not record who it was';\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' is locked by ${by} (${location}). ` +\n\t\t\t\t`Wait for that run to finish; if it crashed, release the lock with ` +\n\t\t\t\t`\\`gkm state:unlock --stage ${stage}\\`.`,\n\t\t);\n\t\tthis.name = 'StateLocked';\n\t}\n}\n\nexport class StateVersionConflict extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly expectedVersion: StateVersion | null,\n\t\treadonly actualVersion: StateVersion | null,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' changed since it was read ` +\n\t\t\t\t`(expected ${expectedVersion ? `version ${expectedVersion}` : 'no state'}, ` +\n\t\t\t\t`found ${actualVersion ? `version ${actualVersion}` : 'no state'}). ` +\n\t\t\t\t`Another run wrote it: read the state again and re-apply the change, ` +\n\t\t\t\t`and hold the stage's lock so runs take turns.`,\n\t\t);\n\t\tthis.name = 'StateVersionConflict';\n\t}\n}\n\nexport class StageStateMissing extends Error {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`There is no deploy state for stage '${stage}' to record a resource in. ` +\n\t\t\t\t`Write the stage's state first (write with expectedVersion: null).`,\n\t\t);\n\t\tthis.name = 'StageStateMissing';\n\t}\n}\n\nexport class StateUnreadable extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly location: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' at ${location} is not valid JSON. ` +\n\t\t\t\t`Restore it from a backup (or the provider's history) rather than ` +\n\t\t\t\t`deploying without it — a deploy without state recreates every resource.`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'StateUnreadable';\n\t}\n}\n\nexport class StateSchemaTooNew extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly schemaVersion: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' has schema version ${String(schemaVersion)}, ` +\n\t\t\t\t`newer than this @geekmidas/cli understands (${STATE_SCHEMA_VERSION}). ` +\n\t\t\t\t`Upgrade @geekmidas/cli to the version that wrote it.`,\n\t\t);\n\t\tthis.name = 'StateSchemaTooNew';\n\t}\n}\n\n/**\n * Not thrown: emitted (once per store) when a custom `StateProvider` stands\n * in for a store, so a deploy that cannot be protected from a concurrent one\n * says so.\n */\nexport class StateStoreWithoutLocking extends Error {\n\tconstructor(readonly provider: string) {\n\t\tsuper(\n\t\t\t`The custom state provider '${provider}' has no lock and no versions, ` +\n\t\t\t\t`so two deploys of the same stage can still overwrite each other. ` +\n\t\t\t\t`Implement StateStore (lock, versioned read, conditional write) or ` +\n\t\t\t\t`use the 'local', 'ssm' or 's3' state provider.`,\n\t\t);\n\t\tthis.name = 'StateStoreWithoutLocking';\n\t}\n}\n\nexport class StateStoreNeedsWorkspaceName extends Error {\n\tconstructor(readonly provider: string) {\n\t\tsuper(\n\t\t\t`The '${provider}' state provider keys state by workspace name. ` +\n\t\t\t\t`Set \"name\" in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'StateStoreNeedsWorkspaceName';\n\t}\n}\n\nexport class UnknownStateProvider extends Error {\n\tconstructor(readonly config: unknown) {\n\t\tsuper(\n\t\t\t`Unknown state provider ${JSON.stringify(config)}. Use 'local', 'ssm', ` +\n\t\t\t\t`'s3', or an object implementing StateStore or StateProvider.`,\n\t\t);\n\t\tthis.name = 'UnknownStateProvider';\n\t}\n}\n\n/** A lock or write that could not get at the state backend in time. */\nexport class StateStoreBusy extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly location: string,\n\t) {\n\t\tsuper(\n\t\t\t`Timed out waiting to write deploy state for stage '${stage}' (${location}). ` +\n\t\t\t\t`If no other gkm process is running, remove ${location}.`,\n\t\t);\n\t\tthis.name = 'StateStoreBusy';\n\t}\n}\n\n// ============================================================================\n// Document format\n// ============================================================================\n\nexport const STATE_SCHEMA_VERSION = 2;\n\n/**\n * What a store persists. v1 was `DokployStageState` on its own; v2 wraps it\n * so resource records and a write serial can sit beside it. The serial\n * changes every write, so content-addressed versions (a file hash, an S3\n * ETag) never repeat — a writer holding a version from two writes ago cannot\n * match by accident.\n */\nexport interface StateDocument {\n\tschemaVersion: typeof STATE_SCHEMA_VERSION;\n\tstage: string;\n\tserial: number;\n\tstate: DokployStageState;\n\tresources: Record<string, ResourceRecord>;\n\tupdatedAt: string;\n}\n\nexport type DecodedDocument =\n\t| { schemaVersion: 2; document: StateDocument }\n\t| { schemaVersion: 1; state: DokployStageState };\n\nexport function decodeDocument(\n\tstage: string,\n\tbody: string,\n\tlocation: string,\n): DecodedDocument {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(body);\n\t} catch (error) {\n\t\tthrow new StateUnreadable(stage, location, error);\n\t}\n\n\tif (typeof parsed !== 'object' || parsed === null) {\n\t\tthrow new StateUnreadable(stage, location, parsed);\n\t}\n\n\tconst schemaVersion = (parsed as { schemaVersion?: unknown }).schemaVersion;\n\tif (schemaVersion === undefined) {\n\t\treturn { schemaVersion: 1, state: parsed as DokployStageState };\n\t}\n\tif (schemaVersion === STATE_SCHEMA_VERSION) {\n\t\treturn { schemaVersion: 2, document: parsed as StateDocument };\n\t}\n\tthrow new StateSchemaTooNew(stage, schemaVersion);\n}\n\nexport function encodeDocument(document: StateDocument): string {\n\treturn JSON.stringify(document, null, 2);\n}\n\n/**\n * v1 state as a v2 document. The ids v1 kept are seeded as `ready` records,\n * so the first journaled deploy after an upgrade adopts them rather than\n * treating them as unknown.\n */\nexport function migrateV1(\n\tstage: string,\n\tstate: DokployStageState,\n): StateDocument {\n\tconst now = new Date().toISOString();\n\tconst resources: Record<string, ResourceRecord> = {};\n\tconst seed = (type: string, name: string | null, id: string | undefined) => {\n\t\tif (!id) return;\n\t\tconst key = name ? `${type}:${name}` : type;\n\t\tresources[key] = { key, type, id, status: 'ready', updatedAt: now };\n\t};\n\n\tseed('project', null, state.projectId);\n\tseed('environment', null, state.environmentId);\n\tfor (const [app, id] of Object.entries(state.applications ?? {})) {\n\t\tseed('application', app, id);\n\t}\n\tseed('postgres', null, state.services?.postgresId);\n\tseed('redis', null, state.services?.redisId);\n\tseed('backup-destination', null, state.backups?.destinationId);\n\n\treturn {\n\t\tschemaVersion: STATE_SCHEMA_VERSION,\n\t\tstage,\n\t\tserial: 1,\n\t\tstate,\n\t\tresources,\n\t\tupdatedAt: now,\n\t};\n}\n\nexport function newLockHolder(options?: LockOptions): LockHolder {\n\tlet owner = 'unknown';\n\ttry {\n\t\towner = userInfo().username;\n\t} catch {\n\t\t// No passwd entry (a container running as an arbitrary uid).\n\t}\n\treturn {\n\t\tid: randomUUID(),\n\t\towner,\n\t\thost: hostname(),\n\t\tpid: process.pid,\n\t\tacquiredAt: new Date().toISOString(),\n\t\toperation: options?.operation,\n\t};\n}\n\nexport function contentVersion(body: string): StateVersion {\n\treturn createHash('sha256').update(body).digest('hex').slice(0, 16);\n}\n\n// ============================================================================\n// Shared implementation\n// ============================================================================\n\nexport interface RawState {\n\tbody: string;\n\tversion: StateVersion;\n}\n\n/**\n * A store over a backend that can read and conditionally write one blob per\n * stage, and create a lock object only if it does not exist. Migration and\n * resource records are the same for every such backend, so they live here.\n */\nexport abstract class DocumentStateStore implements StateStore {\n\t/** Where the stage's state lives, for messages. */\n\tprotected abstract location(stage: string): string;\n\tprotected abstract readRaw(stage: string): Promise<RawState | null>;\n\t/**\n\t * Writes only if the stored version is `expectedVersion` (`null`: only if\n\t * nothing is stored), else raises `StateVersionConflict`.\n\t */\n\tprotected abstract writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion>;\n\t/** Keeps the v1 body as it was. Never replaces an existing backup. */\n\tprotected abstract writeV1Backup(stage: string, body: string): Promise<void>;\n\t/** Creates the lock only if absent, else raises `StateLocked`. */\n\tprotected abstract createLock(\n\t\tstage: string,\n\t\tholder: LockHolder,\n\t): Promise<void>;\n\tprotected abstract readLock(stage: string): Promise<LockHolder | null>;\n\t/** Removes the lock — only if it is `holder`'s, when one is given. */\n\tprotected abstract removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void>;\n\n\tasync lock(stage: string, options?: LockOptions): Promise<StateLock> {\n\t\tconst holder = newLockHolder(options);\n\t\tawait this.createLock(stage, holder);\n\t\tlet released = false;\n\t\treturn {\n\t\t\tstage,\n\t\t\tholder,\n\t\t\trelease: async () => {\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tawait this.removeLock(stage, holder);\n\t\t\t},\n\t\t};\n\t}\n\n\tasync forceUnlock(stage: string): Promise<LockHolder | null> {\n\t\tconst holder = await this.readLock(stage);\n\t\tawait this.removeLock(stage, null);\n\t\treturn holder;\n\t}\n\n\tasync read(stage: string): Promise<StoredStageState | null> {\n\t\tconst current = await this.readDocument(stage);\n\t\tif (!current) return null;\n\t\treturn {\n\t\t\tstate: current.document.state,\n\t\t\tresources: current.document.resources,\n\t\t\tversion: current.version,\n\t\t};\n\t}\n\n\tasync write(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion> {\n\t\tif (options.expectedVersion === null) {\n\t\t\tconst document: StateDocument = {\n\t\t\t\tschemaVersion: STATE_SCHEMA_VERSION,\n\t\t\t\tstage,\n\t\t\t\tserial: 1,\n\t\t\t\tstate,\n\t\t\t\tresources: {},\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\treturn this.writeRaw(stage, encodeDocument(document), null);\n\t\t}\n\t\treturn this.mutate(stage, options.expectedVersion, (document) => ({\n\t\t\t...document,\n\t\t\tstate,\n\t\t}));\n\t}\n\n\tasync putResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\treturn this.mutate(stage, options?.expectedVersion, (document) => ({\n\t\t\t...document,\n\t\t\tresources: {\n\t\t\t\t...document.resources,\n\t\t\t\t[record.key]: { ...record, updatedAt: new Date().toISOString() },\n\t\t\t},\n\t\t}));\n\t}\n\n\tasync deleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\treturn this.mutate(stage, options?.expectedVersion, (document) => {\n\t\t\tconst { [key]: _removed, ...resources } = document.resources;\n\t\t\treturn { ...document, resources };\n\t\t});\n\t}\n\n\tprivate async mutate(\n\t\tstage: string,\n\t\texpectedVersion: StateVersion | undefined,\n\t\tchange: (document: StateDocument) => StateDocument,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.readDocument(stage);\n\t\tif (!current) {\n\t\t\tif (expectedVersion !== undefined) {\n\t\t\t\tthrow new StateVersionConflict(stage, expectedVersion, null);\n\t\t\t}\n\t\t\tthrow new StageStateMissing(stage);\n\t\t}\n\t\tif (expectedVersion !== undefined && current.version !== expectedVersion) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, current.version);\n\t\t}\n\n\t\tconst next = change(current.document);\n\t\tnext.serial = current.document.serial + 1;\n\t\tnext.updatedAt = new Date().toISOString();\n\t\treturn this.writeRaw(stage, encodeDocument(next), current.version);\n\t}\n\n\t/**\n\t * Reads the stage's document, migrating v1 in place on the way. The\n\t * migration is itself a conditional write: if another run migrated (or\n\t * wrote) first, its result is read instead.\n\t */\n\tprivate async readDocument(\n\t\tstage: string,\n\t\tattempt = 0,\n\t): Promise<{ document: StateDocument; version: StateVersion } | null> {\n\t\tconst raw = await this.readRaw(stage);\n\t\tif (!raw) return null;\n\n\t\tconst decoded = decodeDocument(stage, raw.body, this.location(stage));\n\t\tif (decoded.schemaVersion === 2) {\n\t\t\treturn { document: decoded.document, version: raw.version };\n\t\t}\n\n\t\tawait this.writeV1Backup(stage, raw.body);\n\t\tconst document = migrateV1(stage, decoded.state);\n\t\ttry {\n\t\t\tconst version = await this.writeRaw(\n\t\t\t\tstage,\n\t\t\t\tencodeDocument(document),\n\t\t\t\traw.version,\n\t\t\t);\n\t\t\treturn { document, version };\n\t\t} catch (error) {\n\t\t\tif (error instanceof StateVersionConflict && attempt < 2) {\n\t\t\t\treturn this.readDocument(stage, attempt + 1);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Resolution\n// ============================================================================\n\nexport function isStateStore(value: unknown): value is StateStore {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\ttypeof (value as StateStore).lock === 'function' &&\n\t\ttypeof (value as StateStore).read === 'function' &&\n\t\ttypeof (value as StateStore).write === 'function' &&\n\t\ttypeof (value as StateStore).putResource === 'function'\n\t);\n}\n\nexport interface CreateStateStoreOptions extends CreateStateStoreConfig {\n\t/** Where `StateStoreWithoutLocking` goes; `process.emitWarning` by default. */\n\twarn?: (warning: StateStoreWithoutLocking) => void;\n}\n\n/**\n * The store for a workspace's `state` config:\n *\n * - none / `'local'`: `LocalStateStore` under `.gkm/`\n * - `'ssm'`: `SSMStateStore`, with no local cache — SSM is read directly, so\n * a stale local copy can never win over it\n * - `'s3'`: `S3StateStore`\n * - a `StateStore` object: used as is\n * - a `StateProvider` object: wrapped by `LegacyStateStore`\n */\nexport async function createStateStore(\n\toptions: CreateStateStoreOptions,\n): Promise<StateStore> {\n\tconst { config, workspaceRoot, workspaceName } = options;\n\n\tif (!config || config.provider === 'local') {\n\t\tconst { LocalStateStore } = await import('./LocalStateStore');\n\t\treturn new LocalStateStore(workspaceRoot);\n\t}\n\n\tif (isStateStore(config.provider)) {\n\t\treturn config.provider;\n\t}\n\n\tif (isStateProvider(config.provider)) {\n\t\tconst { LegacyStateStore } = await import('./LegacyStateStore');\n\t\treturn new LegacyStateStore(config.provider, { warn: options.warn });\n\t}\n\n\tif (config.provider === 'ssm') {\n\t\tif (!workspaceName) throw new StateStoreNeedsWorkspaceName('ssm');\n\t\tconst { SSMStateStore } = await import('./SSMStateStore');\n\t\tconst ssm = config as SSMStateConfig;\n\t\treturn SSMStateStore.create({\n\t\t\tworkspaceName,\n\t\t\tregion: ssm.region,\n\t\t\tprofile: ssm.profile,\n\t\t});\n\t}\n\n\tif (config.provider === 's3') {\n\t\tif (!workspaceName) throw new StateStoreNeedsWorkspaceName('s3');\n\t\tconst { S3StateStore } = await import('./S3StateStore');\n\t\tconst s3 = config as S3StateConfig;\n\t\treturn S3StateStore.create({\n\t\t\tworkspaceName,\n\t\t\tbucket: s3.bucket,\n\t\t\tprefix: s3.prefix,\n\t\t\tregion: s3.region,\n\t\t\tprofile: s3.profile,\n\t\t});\n\t}\n\n\tthrow new UnknownStateProvider(config);\n}\n","/**\n * Centralized Dokploy API client\n *\n * Handles authentication, error handling, and provides typed methods for all Dokploy API endpoints.\n */\n\nexport interface DokployApiOptions {\n\t/** Dokploy server URL (e.g., https://dokploy.example.com) */\n\tbaseUrl: string;\n\t/** API token for authentication */\n\ttoken: string;\n\t/**\n\t * How long one request may take, in milliseconds, before it is abandoned\n\t * with `DokployRequestTimedOut`. Defaults to 30 seconds.\n\t */\n\ttimeoutMs?: number;\n\t/**\n\t * Aborts every request, and any retry still waiting to run. A request it\n\t * stops rejects with the signal's reason.\n\t */\n\tsignal?: AbortSignal;\n}\n\n/** Per request, so a stalled server fails the deploy, not the CI job. */\nexport const DEFAULT_DOKPLOY_TIMEOUT_MS = 30_000;\n\n/**\n * Dokploy accepted the connection but did not answer in time.\n *\n * Not retried: a request that arrived and stalled may still be acted on, and\n * sending it again could repeat a deploy or a create the server is already\n * doing.\n */\nexport class DokployRequestTimedOut extends Error {\n\tconstructor(\n\t\treadonly baseUrl: string,\n\t\treadonly endpoint: string,\n\t\treadonly timeoutMs: number,\n\t) {\n\t\tsuper(\n\t\t\t`Dokploy at ${baseUrl} did not answer ${endpoint} within ${timeoutMs}ms. ` +\n\t\t\t\t'Check that the Dokploy server is healthy, or raise `timeoutMs` if this call is known to be slow.',\n\t\t);\n\t\tthis.name = 'DokployRequestTimedOut';\n\t}\n}\n\n/**\n * Waits `ms` between retries, or rejects with the signal's reason the moment\n * it aborts — a caller who gave up should not sit through the backoff.\n */\nfunction pause(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(signal.reason);\n\t\t\treturn;\n\t\t}\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timer);\n\t\t\treject(signal?.reason);\n\t\t};\n\t\tconst timer = setTimeout(() => {\n\t\t\tsignal?.removeEventListener('abort', onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tsignal?.addEventListener('abort', onAbort, { once: true });\n\t});\n}\n\n/** How long a deployment may take to finish before a deploy gives up on it. */\nexport const DEFAULT_DEPLOYMENT_TIMEOUT_MS = 10 * 60_000;\n\nexport interface WaitForDeploymentOptions {\n\t/**\n\t * Only a deployment created after this counts — by Dokploy's clock, as\n\t * `latestDeploymentAt` read it before `application.deploy`.\n\t */\n\tsince: Date;\n\t/** How long to wait. Defaults to 10 minutes. */\n\ttimeoutMs?: number;\n\tsignal?: AbortSignal;\n\t/** The first wait between polls; it doubles up to `maxIntervalMs`. */\n\tintervalMs?: number;\n\tmaxIntervalMs?: number;\n}\n\n/** A deployment Dokploy ran and reported as failed. */\nexport class DeploymentFailed extends Error {\n\tconstructor(\n\t\treadonly applicationId: string,\n\t\treadonly deployment: DokployDeployment,\n\t) {\n\t\tsuper(\n\t\t\t`Dokploy's deployment ${deployment.deploymentId} of application ${applicationId} ended in \"${deployment.status}\"` +\n\t\t\t\t`${deployment.errorMessage ? `: ${deployment.errorMessage}` : ''}. ` +\n\t\t\t\t'Its log in Dokploy says why — usually an image the server could not pull, or a container that exited on start.',\n\t\t);\n\t\tthis.name = 'DeploymentFailed';\n\t}\n}\n\n/** A deployment that had not finished when the deploy stopped waiting. */\nexport class DeploymentTimedOut extends Error {\n\tconstructor(\n\t\treadonly applicationId: string,\n\t\treadonly timeoutMs: number,\n\t\t/** The last status seen, if a deployment had appeared at all. */\n\t\treadonly lastStatus: DokployDeploymentStatus | undefined,\n\t) {\n\t\tsuper(\n\t\t\t`Application ${applicationId} did not finish deploying within ${Math.round(timeoutMs / 1000)}s ` +\n\t\t\t\t`(${lastStatus ? `last status \"${lastStatus}\"` : 'no deployment started'}). ` +\n\t\t\t\t'Check its deployment log in Dokploy, or raise deploy.dokploy.verify.deploymentTimeoutMs for an image that is slow to pull.',\n\t\t);\n\t\tthis.name = 'DeploymentTimedOut';\n\t}\n}\n\nexport interface DokployErrorResponse {\n\tmessage?: string;\n\tissues?: Array<{ message: string }>;\n}\n\nexport class DokployApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic issues?: Array<{ message: string }>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'DokployApiError';\n\t}\n}\n\n/**\n * Dokploy API client\n */\nexport class DokployApi {\n\tprivate baseUrl: string;\n\tprivate token: string;\n\tprivate timeoutMs: number;\n\tprivate signal: AbortSignal | undefined;\n\n\tconstructor(options: DokployApiOptions) {\n\t\tthis.baseUrl = options.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n\t\tthis.token = options.token;\n\t\tthis.timeoutMs = options.timeoutMs ?? DEFAULT_DOKPLOY_TIMEOUT_MS;\n\t\tthis.signal = options.signal;\n\t}\n\n\t/**\n\t * Make a GET request to the Dokploy API\n\t */\n\tasync get<T>(endpoint: string): Promise<T> {\n\t\treturn this.request<T>('GET', endpoint);\n\t}\n\n\t/**\n\t * Make a POST request to the Dokploy API\n\t */\n\tasync post<T>(endpoint: string, body?: Record<string, unknown>): Promise<T> {\n\t\treturn this.request<T>('POST', endpoint, body);\n\t}\n\n\t/**\n\t * Make a request to the Dokploy API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: Record<string, unknown>,\n\t): Promise<T> {\n\t\tconst url = `${this.baseUrl}/api/${endpoint}`;\n\n\t\t// Retried, because the *transport* fails in one specific way here: a\n\t\t// deploy pushes an image for several minutes, and the first call after\n\t\t// that finds a keep-alive socket the server has since closed. `fetch`\n\t\t// reports it as a bare `fetch failed` with no status, which aborted a\n\t\t// deploy that had already built and pushed everything.\n\t\t//\n\t\t// Only connection failures. A response — including a 4xx — means the\n\t\t// request arrived, and repeating it would be repeating a decision the\n\t\t// server already made.\n\t\t//\n\t\t// Each attempt gets its own deadline: a server that accepts the\n\t\t// connection and never answers would otherwise hold the deploy — and a\n\t\t// CI job — until the runner's own limit.\n\t\tlet response: Response | undefined;\n\t\tlet timeout: AbortSignal | undefined;\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 1; attempt <= 3; attempt++) {\n\t\t\tthis.signal?.throwIfAborted();\n\t\t\ttimeout = AbortSignal.timeout(this.timeoutMs);\n\t\t\ttry {\n\t\t\t\tresponse = await fetch(url, {\n\t\t\t\t\tmethod,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t'x-api-key': this.token,\n\t\t\t\t\t},\n\t\t\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t\t\t\tsignal: this.signal\n\t\t\t\t\t\t? AbortSignal.any([this.signal, timeout])\n\t\t\t\t\t\t: timeout,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t} catch (error) {\n\t\t\t\t// An abort is a decision, not a dropped connection: never retried.\n\t\t\t\tconst stopped = this.stopped(timeout, endpoint);\n\t\t\t\tif (stopped) throw stopped;\n\t\t\t\tlastError = error;\n\t\t\t\tif (attempt === 3) break;\n\t\t\t\tawait pause(attempt * 750, this.signal);\n\t\t\t}\n\t\t}\n\n\t\tif (!response || !timeout) {\n\t\t\tconst detail =\n\t\t\t\tlastError instanceof Error ? lastError.message : String(lastError);\n\t\t\tthrow new DokployApiError(\n\t\t\t\t`Could not reach Dokploy at ${this.baseUrl} (${endpoint}): ${detail}`,\n\t\t\t\t0,\n\t\t\t\t'Network error',\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\treturn await this.read<T>(response);\n\t\t} catch (error) {\n\t\t\t// The deadline covers the body too: headers can arrive and the body\n\t\t\t// stall just the same.\n\t\t\tthrow this.stopped(timeout, endpoint) ?? error;\n\t\t}\n\t}\n\n\t/**\n\t * What a request cut short should reject with: the caller's own reason\n\t * when they aborted, `DokployRequestTimedOut` when the deadline passed,\n\t * nothing when it was neither.\n\t */\n\tprivate stopped(timeout: AbortSignal, endpoint: string): unknown {\n\t\tif (this.signal?.aborted) return this.signal.reason;\n\t\tif (timeout.aborted) {\n\t\t\treturn new DokployRequestTimedOut(this.baseUrl, endpoint, this.timeoutMs);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Turns a response into its JSON body, or a `DokployApiError`. */\n\tprivate async read<T>(response: Response): Promise<T> {\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Dokploy API error: ${response.status} ${response.statusText}`;\n\t\t\tlet issues: Array<{ message: string }> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as DokployErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Dokploy API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\tif (errorBody.issues?.length) {\n\t\t\t\t\tissues = errorBody.issues;\n\t\t\t\t\terrorMessage += `\\n Issues: ${errorBody.issues.map((i) => i.message).join(', ')}`;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new DokployApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\tissues,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses (204 No Content or empty body)\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Every external port published on this server, across every project.\n\t *\n\t * Dokploy knows what it has bound; guessing does not. A port that looks free\n\t * from here can be held by a service in a project this deploy has never\n\t * heard of — which surfaces as `Port 5432 is already in use by container\n\t * \"…\"`, naming something the caller cannot see.\n\t *\n\t * `project.all` is a summary, so each project is fetched: the per-service\n\t * `externalPort` only appears on `project.one`.\n\t */\n\tasync publishedPorts(): Promise<Set<number>> {\n\t\tconst ports = new Set<number>();\n\t\tconst projects = await this.listProjects();\n\n\t\tfor (const project of projects) {\n\t\t\tconst details = await this.getProject(project.projectId);\n\n\t\t\tfor (const environment of details.environments ?? []) {\n\t\t\t\tconst resources = [\n\t\t\t\t\t...(environment.postgres ?? []),\n\t\t\t\t\t...(environment.redis ?? []),\n\t\t\t\t\t...(environment.applications ?? []),\n\t\t\t\t] as { externalPort?: number | null }[];\n\n\t\t\t\tfor (const resource of resources) {\n\t\t\t\t\tif (typeof resource.externalPort === 'number') {\n\t\t\t\t\t\tports.add(resource.externalPort);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn ports;\n\t}\n\n\t/**\n\t * Validate the API token by making a test request\n\t */\n\tasync validateToken(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.get('project.all');\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ============================================\n\t// Project endpoints\n\t// ============================================\n\n\t/**\n\t * List all projects\n\t */\n\tasync listProjects(): Promise<DokployProject[]> {\n\t\treturn this.get<DokployProject[]>('project.all');\n\t}\n\n\t/**\n\t * Get a single project by ID\n\t */\n\tasync getProject(projectId: string): Promise<DokployProjectDetails> {\n\t\treturn this.get<DokployProjectDetails>(\n\t\t\t`project.one?projectId=${projectId}`,\n\t\t);\n\t}\n\n\t/**\n\t * Create a new project\n\t */\n\tasync createProject(\n\t\tname: string,\n\t\tdescription?: string,\n\t): Promise<{ project: DokployProject; environment: DokployEnvironment }> {\n\t\treturn this.post<{\n\t\t\tproject: DokployProject;\n\t\t\tenvironment: DokployEnvironment;\n\t\t}>('project.create', {\n\t\t\tname,\n\t\t\tdescription: description ?? `Created by gkm CLI`,\n\t\t});\n\t}\n\n\t/**\n\t * Change a project's name or description.\n\t *\n\t * The name is sent with the description because `project.update` validates\n\t * the whole project on some Dokploy versions, and a missing name fails it.\n\t */\n\tasync updateProject(\n\t\tprojectId: string,\n\t\tupdates: { name: string; description: string },\n\t): Promise<void> {\n\t\tawait this.post('project.update', { projectId, ...updates });\n\t}\n\n\t/**\n\t * Delete a project and all its resources\n\t */\n\tasync deleteProject(projectId: string): Promise<void> {\n\t\tawait this.post('project.remove', { projectId });\n\t}\n\n\t// ============================================\n\t// Environment endpoints\n\t// ============================================\n\n\t/**\n\t * Create an environment in a project\n\t */\n\tasync createEnvironment(\n\t\tprojectId: string,\n\t\tname: string,\n\t\tdescription?: string,\n\t): Promise<DokployEnvironment> {\n\t\treturn this.post<DokployEnvironment>('environment.create', {\n\t\t\tprojectId,\n\t\t\tname,\n\t\t\tdescription: description ?? `${name} environment`,\n\t\t});\n\t}\n\n\t// ============================================\n\t// Application endpoints\n\t// ============================================\n\n\t/**\n\t * List all applications in a project\n\t */\n\tasync listApplications(projectId: string): Promise<DokployApplication[]> {\n\t\treturn this.listInProject(projectId, (env) => env.applications);\n\t}\n\n\t/**\n\t * Everything of one kind in a project, across its environments.\n\t *\n\t * From `project.one`, because the per-kind `*.all?projectId=` endpoints\n\t * return **404** on current Dokploy: resources live under environments now.\n\t * Those calls used to be wrapped in `catch { return [] }`, which turned \"I\n\t * could not ask\" into \"there are none\" — and for a find-or-create that is\n\t * the worst available answer. Every deploy created a second application, a\n\t * second Postgres and a second Redis, silently, for as long as the project\n\t * existed.\n\t *\n\t * A failure here is not swallowed: creating a duplicate is worse than\n\t * stopping.\n\t */\n\tprivate async listInProject<T>(\n\t\tprojectId: string,\n\t\tpick: (environment: DokployEnvironment) => T[] | undefined,\n\t): Promise<T[]> {\n\t\tconst project = await this.getProject(projectId);\n\n\t\t// Tagged with the environment they came from: `project.one` nests them,\n\t\t// and a caller filtering by environment has nothing to filter on\n\t\t// otherwise.\n\t\treturn (project.environments ?? []).flatMap((environment) =>\n\t\t\t(pick(environment) ?? []).map((resource) => ({\n\t\t\t\t...resource,\n\t\t\t\tenvironmentId: environment.environmentId,\n\t\t\t})),\n\t\t);\n\t}\n\n\t/**\n\t * Find an application by name in a project\n\t */\n\tasync findApplicationByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t\t/**\n\t\t * The environment to look in.\n\t\t *\n\t\t * A project has one application list per environment, and the same name\n\t\t * legitimately exists in each — `prod` and `staging` both run `api`.\n\t\t * Searching across all of them finds whichever came back first and\n\t\t * deploys a stage's image into another stage's application.\n\t\t */\n\t\tenvironmentId?: string,\n\t): Promise<DokployApplication | undefined> {\n\t\tconst applications = await this.listApplications(projectId);\n\t\tconst normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\n\t\treturn applications.find(\n\t\t\t(app) =>\n\t\t\t\t(!environmentId || app.environmentId === environmentId) &&\n\t\t\t\t(app.name === name || app.appName === normalizedName),\n\t\t);\n\t}\n\n\t/**\n\t * Create a new application\n\t */\n\tasync createApplication(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t): Promise<DokployApplication> {\n\t\treturn this.post<DokployApplication>('application.create', {\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\tappName: name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),\n\t\t});\n\t}\n\n\t/**\n\t * Find or create an application by name\n\t */\n\tasync findOrCreateApplication(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t): Promise<{ application: DokployApplication; created: boolean }> {\n\t\tconst existing = await this.findApplicationByName(\n\t\t\tprojectId,\n\t\t\tname,\n\t\t\tenvironmentId,\n\t\t);\n\t\tif (existing) {\n\t\t\treturn { application: existing, created: false };\n\t\t}\n\t\tconst application = await this.createApplication(\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t);\n\t\treturn { application, created: true };\n\t}\n\n\t/**\n\t * Get an application by ID\n\t */\n\tasync getApplication(\n\t\tapplicationId: string,\n\t): Promise<DokployApplication | null> {\n\t\ttry {\n\t\t\treturn await this.get<DokployApplication>(\n\t\t\t\t`application.one?applicationId=${applicationId}`,\n\t\t\t);\n\t\t} catch {\n\t\t\t// Application not found\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Update an application\n\t */\n\tasync updateApplication(\n\t\tapplicationId: string,\n\t\tupdates: Partial<DokployApplicationUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('application.update', {\n\t\t\tapplicationId,\n\t\t\t...updates,\n\t\t});\n\t}\n\n\t/**\n\t * Save environment variables for an application\n\t */\n\tasync saveApplicationEnv(\n\t\tapplicationId: string,\n\t\tenv: string,\n\t\toptions?: { buildArgs?: string; buildSecrets?: string },\n\t): Promise<void> {\n\t\t// `buildArgs`, `buildSecrets` and `createEnvFile` are `nonoptional` on\n\t\t// current Dokploy, the same way the docker provider's credentials are:\n\t\t// they must be present, and null is how \"none\" is spelled. Omitting them\n\t\t// is rejected with three anonymous validation errors that name no field.\n\t\tawait this.post('application.saveEnvironment', {\n\t\t\tapplicationId,\n\t\t\tenv,\n\t\t\tbuildArgs: options?.buildArgs ?? null,\n\t\t\tbuildSecrets: options?.buildSecrets ?? null,\n\t\t\tcreateEnvFile: false,\n\t\t});\n\t}\n\n\t/**\n\t * Configure application to use Docker provider (pull from registry)\n\t *\n\t * For private registries, either:\n\t * - Use `registryId` if the registry is configured in Dokploy\n\t * - Or provide `username`, `password`, and `registryUrl` directly\n\t */\n\tasync saveDockerProvider(\n\t\tapplicationId: string,\n\t\tdockerImage: string,\n\t\toptions?: {\n\t\t\t/** Registry ID in Dokploy (for pre-configured registries) */\n\t\t\tregistryId?: string;\n\t\t\t/** Registry username (for direct auth) */\n\t\t\tusername?: string;\n\t\t\t/** Registry password (for direct auth) */\n\t\t\tpassword?: string;\n\t\t\t/** Registry URL (for direct auth, e.g., ghcr.io) */\n\t\t\tregistryUrl?: string;\n\t\t},\n\t): Promise<void> {\n\t\t// All five are `nonoptional` on current Dokploy — including the three\n\t\t// credential fields, which are meaningless when a registry is selected by\n\t\t// id. Omitting them is rejected; sending them as null is accepted. So\n\t\t// they are always present and null is how \"this registry needs none\" is\n\t\t// spelled.\n\t\tawait this.post('application.saveDockerProvider', {\n\t\t\tapplicationId,\n\t\t\tdockerImage,\n\t\t\t// Spread first: `registryId` selects a stored registry and is the\n\t\t\t// usual case, and dropping it silently deploys an image nothing can\n\t\t\t// pull. The three below are then filled in rather than replaced.\n\t\t\t...options,\n\t\t\tusername: options?.username ?? null,\n\t\t\tpassword: options?.password ?? null,\n\t\t\tregistryUrl: options?.registryUrl ?? null,\n\t\t});\n\t}\n\n\t/**\n\t * Deploy an application\n\t */\n\tasync deployApplication(applicationId: string): Promise<void> {\n\t\tawait this.post('application.deploy', { applicationId });\n\t}\n\n\t/**\n\t * An application's deployments, newest first.\n\t *\n\t * Sorted here rather than trusted to arrive sorted: which deployment is\n\t * the latest is the whole question `waitForDeployment` asks.\n\t */\n\tasync listDeployments(applicationId: string): Promise<DokployDeployment[]> {\n\t\tconst deployments = await this.get<DokployDeployment[]>(\n\t\t\t`deployment.all?applicationId=${encodeURIComponent(applicationId)}`,\n\t\t);\n\t\treturn [...deployments].sort(\n\t\t\t(a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt),\n\t\t);\n\t}\n\n\t/**\n\t * When the application's latest deployment was created, by Dokploy's own\n\t * clock — what `waitForDeployment`'s `since` is measured against. Epoch\n\t * when it has none.\n\t */\n\tasync latestDeploymentAt(applicationId: string): Promise<Date> {\n\t\tconst [latest] = await this.listDeployments(applicationId);\n\t\treturn new Date(latest ? Date.parse(latest.createdAt) : 0);\n\t}\n\n\t/**\n\t * Wait for the deployment `application.deploy` queued to finish.\n\t *\n\t * `application.deploy` only queues one: it answers before the image is\n\t * pulled, let alone running, so a deploy that stopped there reported\n\t * success for containers that had not started. This polls the latest\n\t * deployment created after `since` — Dokploy's clock, from\n\t * `latestDeploymentAt` before deploying, so a laptop's skew cannot pick an\n\t * older one — and the application's own status, backing off from\n\t * `intervalMs` to `maxIntervalMs`.\n\t *\n\t * @throws {DeploymentFailed} when the deployment ends in `error` or is\n\t * cancelled, or the application reports `error` once it has finished.\n\t * @throws {DeploymentTimedOut} when it has not finished within `timeoutMs`.\n\t */\n\tasync waitForDeployment(\n\t\tapplicationId: string,\n\t\toptions: WaitForDeploymentOptions,\n\t): Promise<DokployDeployment> {\n\t\tconst {\n\t\t\tsince,\n\t\t\ttimeoutMs = DEFAULT_DEPLOYMENT_TIMEOUT_MS,\n\t\t\tsignal,\n\t\t\tintervalMs = 2_000,\n\t\t\tmaxIntervalMs = 15_000,\n\t\t} = options;\n\t\tconst after = since.getTime();\n\t\tconst deadline = Date.now() + timeoutMs;\n\t\tlet interval = intervalMs;\n\t\tlet last: DokployDeployment | undefined;\n\n\t\twhile (true) {\n\t\t\tsignal?.throwIfAborted();\n\t\t\tconst deployment = (await this.listDeployments(applicationId)).find(\n\t\t\t\t(d) => Date.parse(d.createdAt) > after,\n\t\t\t);\n\t\t\tlast = deployment ?? last;\n\n\t\t\tif (deployment) {\n\t\t\t\tif (\n\t\t\t\t\tdeployment.status === 'error' ||\n\t\t\t\t\tdeployment.status === 'cancelled'\n\t\t\t\t) {\n\t\t\t\t\tthrow new DeploymentFailed(applicationId, deployment);\n\t\t\t\t}\n\t\t\t\tif (deployment.status === 'done') {\n\t\t\t\t\tconst { applicationStatus } =\n\t\t\t\t\t\tawait this.get<DokployApplicationStatus>(\n\t\t\t\t\t\t\t`application.one?applicationId=${encodeURIComponent(applicationId)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\tif (applicationStatus === 'error') {\n\t\t\t\t\t\tthrow new DeploymentFailed(applicationId, deployment);\n\t\t\t\t\t}\n\t\t\t\t\t// `running` is Dokploy still settling the service after the\n\t\t\t\t\t// deployment's own record closed; anything else is finished.\n\t\t\t\t\tif (applicationStatus !== 'running') return deployment;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst left = deadline - Date.now();\n\t\t\tif (left <= 0) {\n\t\t\t\tthrow new DeploymentTimedOut(applicationId, timeoutMs, last?.status);\n\t\t\t}\n\t\t\tawait pause(Math.min(interval, left), signal);\n\t\t\tinterval = Math.min(interval * 2, maxIntervalMs);\n\t\t}\n\t}\n\n\t/**\n\t * Delete an application\n\t */\n\tasync deleteApplication(applicationId: string): Promise<void> {\n\t\tawait this.post('application.remove', { applicationId });\n\t}\n\n\t// ============================================\n\t// Registry endpoints\n\t// ============================================\n\n\t/**\n\t * List all registries\n\t */\n\tasync listRegistries(): Promise<DokployRegistry[]> {\n\t\treturn this.get<DokployRegistry[]>('registry.all');\n\t}\n\n\t/**\n\t * Create a new registry\n\t */\n\tasync createRegistry(\n\t\tregistryName: string,\n\t\tregistryUrl: string,\n\t\tusername: string,\n\t\tpassword: string,\n\t\toptions?: {\n\t\t\timagePrefix?: string;\n\t\t},\n\t): Promise<DokployRegistry> {\n\t\treturn this.post<DokployRegistry>('registry.create', {\n\t\t\tregistryName,\n\t\t\tregistryUrl,\n\t\t\tusername,\n\t\t\tpassword,\n\t\t\timagePrefix: options?.imagePrefix,\n\t\t});\n\t}\n\n\t/**\n\t * Get a registry by ID\n\t */\n\tasync getRegistry(registryId: string): Promise<DokployRegistry> {\n\t\treturn this.get<DokployRegistry>(`registry.one?registryId=${registryId}`);\n\t}\n\n\t/**\n\t * Update a registry\n\t */\n\tasync updateRegistry(\n\t\tregistryId: string,\n\t\tupdates: Partial<{\n\t\t\tregistryName: string;\n\t\t\tregistryUrl: string;\n\t\t\tusername: string;\n\t\t\tpassword: string;\n\t\t\timagePrefix: string;\n\t\t}>,\n\t): Promise<void> {\n\t\tawait this.post('registry.update', { registryId, ...updates });\n\t}\n\n\t/**\n\t * Delete a registry\n\t */\n\tasync deleteRegistry(registryId: string): Promise<void> {\n\t\tawait this.post('registry.remove', { registryId });\n\t}\n\n\t// ============================================\n\t// Postgres endpoints\n\t// ============================================\n\n\t/**\n\t * List all Postgres databases in a project\n\t */\n\tasync listPostgres(projectId: string): Promise<DokployPostgres[]> {\n\t\treturn this.listInProject(projectId, (env) => env.postgres);\n\t}\n\n\t/**\n\t * Find a Postgres database by name in a project\n\t */\n\tasync findPostgresByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t): Promise<DokployPostgres | undefined> {\n\t\tconst databases = await this.listPostgres(projectId);\n\t\tconst normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\t\tconst match = databases.find(\n\t\t\t(db) => db.name === name || db.appName === normalizedName,\n\t\t);\n\n\t\tif (!match) return undefined;\n\n\t\t// Hydrated, because the listing is a *summary*: `project.one` nests these\n\t\t// with `postgresId`, `name`, `appName` and status, and none of\n\t\t// `databaseUser`, `databasePassword` or `databaseName`.\n\t\t//\n\t\t// Returning the summary meant a *found* cluster came back with undefined\n\t\t// credentials — so the URL built from it named database `undefined`, and\n\t\t// the DDL connection authenticated with nothing and retried thirty times.\n\t\t// A created one was fine, which is why this only appeared on the second\n\t\t// deploy.\n\t\treturn this.getPostgres(match.postgresId);\n\t}\n\n\t/**\n\t * Create a new Postgres database\n\t */\n\tasync createPostgres(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tappName?: string;\n\t\t\tdatabaseName?: string;\n\t\t\tdatabaseUser?: string;\n\t\t\tdatabasePassword?: string;\n\t\t\tdockerImage?: string;\n\t\t\tdescription?: string;\n\t\t},\n\t): Promise<DokployPostgres> {\n\t\treturn this.post<DokployPostgres>('postgres.create', {\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\tappName:\n\t\t\t\toptions?.appName ?? name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),\n\t\t\tdatabaseName: options?.databaseName,\n\t\t\tdatabaseUser: options?.databaseUser ?? 'postgres',\n\t\t\tdatabasePassword: options?.databasePassword,\n\t\t\tdockerImage: options?.dockerImage ?? 'postgres:18',\n\t\t\tdescription: options?.description ?? `Postgres database for ${name}`,\n\t\t});\n\t}\n\n\t/**\n\t * Find or create a Postgres database by name\n\t */\n\tasync findOrCreatePostgres(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tdatabaseName?: string;\n\t\t\tdatabasePassword?: string;\n\t\t},\n\t): Promise<{ postgres: DokployPostgres; created: boolean }> {\n\t\tconst existing = await this.findPostgresByName(projectId, name);\n\t\tif (existing) {\n\t\t\treturn { postgres: existing, created: false };\n\t\t}\n\t\tconst postgres = await this.createPostgres(\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\toptions,\n\t\t);\n\t\treturn { postgres, created: true };\n\t}\n\n\t/**\n\t * Get a Postgres database by ID\n\t */\n\tasync getPostgres(postgresId: string): Promise<DokployPostgres> {\n\t\treturn this.get<DokployPostgres>(`postgres.one?postgresId=${postgresId}`);\n\t}\n\n\t/**\n\t * Deploy a Postgres database\n\t */\n\tasync deployPostgres(postgresId: string): Promise<void> {\n\t\tawait this.post('postgres.deploy', { postgresId });\n\t}\n\n\t/**\n\t * Save environment variables for Postgres\n\t */\n\tasync savePostgresEnv(postgresId: string, env: string): Promise<void> {\n\t\tawait this.post('postgres.saveEnvironment', { postgresId, env });\n\t}\n\n\t/**\n\t * Set external port for Postgres (for external access)\n\t */\n\tasync savePostgresExternalPort(\n\t\tpostgresId: string,\n\t\texternalPort: number | null,\n\t): Promise<void> {\n\t\tawait this.post('postgres.saveExternalPort', { postgresId, externalPort });\n\t}\n\n\t/**\n\t * Update Postgres configuration\n\t */\n\tasync updatePostgres(\n\t\tpostgresId: string,\n\t\tupdates: Partial<DokployPostgresUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('postgres.update', { postgresId, ...updates });\n\t}\n\n\t/**\n\t * Delete a Postgres database\n\t */\n\tasync deletePostgres(postgresId: string): Promise<void> {\n\t\tawait this.post('postgres.remove', { postgresId });\n\t}\n\n\t// ============================================\n\t// Compose endpoints\n\t// ============================================\n\n\t/**\n\t * The compose stacks in a project.\n\t *\n\t * Dokploy has first-class primitives for Postgres and Redis and none for\n\t * MinIO, so an object store is a compose stack — the one kind whose contents\n\t * this target writes rather than configures.\n\t */\n\tasync listCompose(projectId: string): Promise<DokployCompose[]> {\n\t\treturn this.listInProject(projectId, (env) => env.compose);\n\t}\n\n\t/** A compose stack by name, hydrated — `project.one` returns summaries. */\n\tasync findComposeByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t): Promise<DokployCompose | undefined> {\n\t\tconst summary = (await this.listCompose(projectId)).find(\n\t\t\t(compose) => compose.name === name,\n\t\t);\n\n\t\treturn summary ? this.getCompose(summary.composeId) : undefined;\n\t}\n\n\tasync getCompose(composeId: string): Promise<DokployCompose> {\n\t\treturn this.get<DokployCompose>(`compose.one?composeId=${composeId}`);\n\t}\n\n\t/**\n\t * A compose stack carrying the file this target wrote.\n\t *\n\t * Created empty and then updated, because `compose.create` takes no file:\n\t * it defaults to `sourceType: 'github'` and expects a repository. `raw` is\n\t * what makes the file ours.\n\t */\n\tasync findOrCreateCompose(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\tcomposeFile: string,\n\t): Promise<{ compose: DokployCompose; created: boolean }> {\n\t\tconst existing = await this.findComposeByName(projectId, name);\n\n\t\tconst compose =\n\t\t\texisting ??\n\t\t\t(await this.post<DokployCompose>('compose.create', {\n\t\t\t\tname,\n\t\t\t\tenvironmentId,\n\t\t\t\tcomposeType: 'docker-compose',\n\t\t\t}));\n\n\t\t// Written every time, not only on create: the file is derived from the\n\t\t// manifest, so a redeploy after a declaration changed has to carry the\n\t\t// change. Dokploy redeploys only what differs.\n\t\tawait this.post('compose.update', {\n\t\t\tcomposeId: compose.composeId,\n\t\t\tsourceType: 'raw',\n\t\t\tcomposeFile,\n\t\t});\n\n\t\treturn { compose, created: !existing };\n\t}\n\n\tasync deployCompose(composeId: string): Promise<void> {\n\t\tawait this.post('compose.deploy', { composeId });\n\t}\n\n\tasync deleteCompose(composeId: string): Promise<void> {\n\t\tawait this.post('compose.delete', { composeId, deleteVolumes: false });\n\t}\n\n\t// ============================================\n\t// Redis endpoints\n\t// ============================================\n\n\t/**\n\t * List all Redis instances in a project\n\t */\n\tasync listRedis(projectId: string): Promise<DokployRedis[]> {\n\t\treturn this.listInProject(projectId, (env) => env.redis);\n\t}\n\n\t/**\n\t * Find a Redis instance by name in a project\n\t */\n\tasync findRedisByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t): Promise<DokployRedis | undefined> {\n\t\tconst instances = await this.listRedis(projectId);\n\t\tconst normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\t\tconst match = instances.find(\n\t\t\t(redis) => redis.name === name || redis.appName === normalizedName,\n\t\t);\n\n\t\t// Hydrated for the same reason the Postgres lookup is: the nested listing\n\t\t// carries no credentials, so a found instance would resolve a URL with an\n\t\t// undefined password in it.\n\t\treturn match ? this.getRedis(match.redisId) : undefined;\n\t}\n\n\t/**\n\t * Create a new Redis instance\n\t */\n\tasync createRedis(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tappName?: string;\n\t\t\tdatabasePassword?: string;\n\t\t\tdockerImage?: string;\n\t\t\tdescription?: string;\n\t\t},\n\t): Promise<DokployRedis> {\n\t\treturn this.post<DokployRedis>('redis.create', {\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\tappName:\n\t\t\t\toptions?.appName ?? name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),\n\t\t\tdatabasePassword: options?.databasePassword,\n\t\t\tdockerImage: options?.dockerImage ?? 'redis:8',\n\t\t\tdescription: options?.description ?? `Redis instance for ${name}`,\n\t\t});\n\t}\n\n\t/**\n\t * Find or create a Redis instance by name\n\t */\n\tasync findOrCreateRedis(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tdatabasePassword?: string;\n\t\t},\n\t): Promise<{ redis: DokployRedis; created: boolean }> {\n\t\tconst existing = await this.findRedisByName(projectId, name);\n\t\tif (existing) {\n\t\t\treturn { redis: existing, created: false };\n\t\t}\n\t\tconst redis = await this.createRedis(\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\toptions,\n\t\t);\n\t\treturn { redis, created: true };\n\t}\n\n\t/**\n\t * Get a Redis instance by ID\n\t */\n\tasync getRedis(redisId: string): Promise<DokployRedis> {\n\t\treturn this.get<DokployRedis>(`redis.one?redisId=${redisId}`);\n\t}\n\n\t/**\n\t * Deploy a Redis instance\n\t */\n\tasync deployRedis(redisId: string): Promise<void> {\n\t\tawait this.post('redis.deploy', { redisId });\n\t}\n\n\t/**\n\t * Save environment variables for Redis\n\t */\n\tasync saveRedisEnv(redisId: string, env: string): Promise<void> {\n\t\tawait this.post('redis.saveEnvironment', { redisId, env });\n\t}\n\n\t/**\n\t * Set external port for Redis (for external access)\n\t */\n\tasync saveRedisExternalPort(\n\t\tredisId: string,\n\t\texternalPort: number | null,\n\t): Promise<void> {\n\t\tawait this.post('redis.saveExternalPort', { redisId, externalPort });\n\t}\n\n\t/**\n\t * Update Redis configuration\n\t */\n\tasync updateRedis(\n\t\tredisId: string,\n\t\tupdates: Partial<DokployRedisUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('redis.update', { redisId, ...updates });\n\t}\n\n\t/**\n\t * Delete a Redis instance\n\t */\n\tasync deleteRedis(redisId: string): Promise<void> {\n\t\tawait this.post('redis.remove', { redisId });\n\t}\n\n\t// ============================================\n\t// Domain endpoints\n\t// ============================================\n\n\t/**\n\t * Create a new domain for an application\n\t */\n\tasync createDomain(options: DokployDomainCreate): Promise<DokployDomain> {\n\t\treturn this.post<DokployDomain>(\n\t\t\t'domain.create',\n\t\t\toptions as unknown as Record<string, unknown>,\n\t\t);\n\t}\n\n\t/**\n\t * Update an existing domain\n\t */\n\tasync updateDomain(\n\t\tdomainId: string,\n\t\tupdates: Partial<DokployDomainCreate>,\n\t): Promise<void> {\n\t\tawait this.post('domain.update', { domainId, ...updates });\n\t}\n\n\t/**\n\t * Delete a domain\n\t */\n\tasync deleteDomain(domainId: string): Promise<void> {\n\t\tawait this.post('domain.delete', { domainId });\n\t}\n\n\t/**\n\t * Get a domain by ID\n\t */\n\tasync getDomain(domainId: string): Promise<DokployDomain> {\n\t\treturn this.get<DokployDomain>(`domain.one?domainId=${domainId}`);\n\t}\n\n\t/**\n\t * Get all domains for an application\n\t */\n\tasync getDomainsByApplicationId(\n\t\tapplicationId: string,\n\t): Promise<DokployDomain[]> {\n\t\treturn this.get<DokployDomain[]>(\n\t\t\t`domain.byApplicationId?applicationId=${applicationId}`,\n\t\t);\n\t}\n\n\t/**\n\t * Validate a domain and trigger SSL certificate generation\n\t *\n\t * This should be called after DNS records are created and propagated.\n\t * It triggers Let's Encrypt certificate generation for HTTPS domains.\n\t *\n\t * @param domain - The domain hostname to validate (e.g., 'api.example.com')\n\t */\n\tasync validateDomain(\n\t\tdomain: string,\n\t): Promise<{ isValid: boolean; resolvedIp: string }> {\n\t\treturn this.post<{ isValid: boolean; resolvedIp: string }>(\n\t\t\t'domain.validateDomain',\n\t\t\t{ domain },\n\t\t);\n\t}\n\n\t/**\n\t * Auto-generate a domain name for an application\n\t */\n\tasync generateDomain(\n\t\tappName: string,\n\t\tserverId?: string,\n\t): Promise<{ domain: string }> {\n\t\treturn this.post<{ domain: string }>('domain.generateDomain', {\n\t\t\tappName,\n\t\t\tserverId,\n\t\t});\n\t}\n\n\t// ============================================\n\t// Destination endpoints (backup storage)\n\t// ============================================\n\n\t/**\n\t * List all backup destinations\n\t */\n\tasync listDestinations(): Promise<DokployDestination[]> {\n\t\treturn this.get<DokployDestination[]>('destination.all');\n\t}\n\n\t/**\n\t * Get a destination by ID\n\t */\n\tasync getDestination(destinationId: string): Promise<DokployDestination> {\n\t\treturn this.get<DokployDestination>(\n\t\t\t`destination.one?destinationId=${destinationId}`,\n\t\t);\n\t}\n\n\t/**\n\t * Create a new S3 backup destination\n\t */\n\tasync createDestination(\n\t\toptions: DokployDestinationCreate,\n\t): Promise<DokployDestination> {\n\t\treturn this.post<DokployDestination>('destination.create', { ...options });\n\t}\n\n\t/**\n\t * Find a destination by name\n\t */\n\tasync findDestinationByName(\n\t\tname: string,\n\t): Promise<DokployDestination | undefined> {\n\t\tconst destinations = await this.listDestinations();\n\t\treturn destinations.find((d) => d.name === name);\n\t}\n\n\t/**\n\t * Find or create a destination by name\n\t */\n\tasync findOrCreateDestination(\n\t\tname: string,\n\t\toptions: Omit<DokployDestinationCreate, 'name'>,\n\t): Promise<{ destination: DokployDestination; created: boolean }> {\n\t\tconst existing = await this.findDestinationByName(name);\n\t\tif (existing) {\n\t\t\treturn { destination: existing, created: false };\n\t\t}\n\t\tconst destination = await this.createDestination({ name, ...options });\n\t\treturn { destination, created: true };\n\t}\n\n\t/**\n\t * Update a destination\n\t */\n\tasync updateDestination(\n\t\tdestinationId: string,\n\t\tupdates: Partial<DokployDestinationCreate>,\n\t): Promise<void> {\n\t\tawait this.post('destination.update', { destinationId, ...updates });\n\t}\n\n\t/**\n\t * Delete a destination\n\t */\n\tasync deleteDestination(destinationId: string): Promise<void> {\n\t\tawait this.post('destination.remove', { destinationId });\n\t}\n\n\t/**\n\t * Test connection to a destination\n\t */\n\tasync testDestinationConnection(\n\t\tdestinationId: string,\n\t): Promise<{ success: boolean }> {\n\t\treturn this.post<{ success: boolean }>('destination.testConnection', {\n\t\t\tdestinationId,\n\t\t});\n\t}\n\n\t// ============================================\n\t// Backup endpoints (scheduled backups)\n\t// ============================================\n\n\t/**\n\t * Create a backup schedule for postgres\n\t */\n\tasync createPostgresBackup(\n\t\toptions: DokployBackupCreate,\n\t): Promise<DokployBackup> {\n\t\treturn this.post<DokployBackup>('backup.create', {\n\t\t\t...options,\n\t\t\tdatabaseType: 'postgres',\n\t\t});\n\t}\n\n\t/**\n\t * List backups for a postgres database\n\t */\n\tasync listPostgresBackups(postgresId: string): Promise<DokployBackup[]> {\n\t\treturn this.get<DokployBackup[]>(\n\t\t\t`backup.all?postgresId=${postgresId}&databaseType=postgres`,\n\t\t);\n\t}\n\n\t/**\n\t * Get a backup by ID\n\t */\n\tasync getBackup(backupId: string): Promise<DokployBackup> {\n\t\treturn this.get<DokployBackup>(`backup.one?backupId=${backupId}`);\n\t}\n\n\t/**\n\t * Update a backup schedule\n\t */\n\tasync updateBackup(\n\t\tbackupId: string,\n\t\tupdates: Partial<DokployBackupUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('backup.update', { backupId, ...updates });\n\t}\n\n\t/**\n\t * Delete a backup schedule\n\t */\n\tasync deleteBackup(backupId: string): Promise<void> {\n\t\tawait this.post('backup.remove', { backupId });\n\t}\n\n\t/**\n\t * Trigger a manual backup run\n\t */\n\tasync runBackupManually(backupId: string): Promise<void> {\n\t\tawait this.post('backup.manualBackup', { backupId });\n\t}\n}\n\n// ============================================\n// Type definitions for Dokploy API responses\n// ============================================\n\nexport interface DokployProject {\n\tprojectId: string;\n\tname: string;\n\tdescription: string | null;\n\tcreatedAt?: string;\n\tadminId?: string;\n}\n\nexport interface DokployEnvironment {\n\tenvironmentId: string;\n\tname: string;\n\tdescription: string | null;\n\t/**\n\t * What lives in this environment.\n\t *\n\t * `project.one` is the only endpoint that answers this. The per-kind\n\t * `*.all?projectId=` endpoints return 404 on current Dokploy — resources\n\t * moved under environments — which is why they are no longer asked.\n\t */\n\tapplications?: DokployApplication[];\n\tpostgres?: DokployPostgres[];\n\tcompose?: DokployCompose[];\n\tredis?: DokployRedis[];\n}\n\nexport interface DokployProjectDetails extends DokployProject {\n\tenvironments: DokployEnvironment[];\n}\n\nexport interface DokployApplication {\n\tapplicationId: string;\n\tname: string;\n\tappName: string;\n\tprojectId: string;\n\tenvironmentId?: string;\n}\n\n/** Where an application is, as `application.one` reports it. */\nexport interface DokployApplicationStatus {\n\tapplicationStatus: 'idle' | 'running' | 'done' | 'error';\n}\n\nexport type DokployDeploymentStatus =\n\t| 'running'\n\t| 'done'\n\t| 'error'\n\t| 'cancelled';\n\n/** One run of `application.deploy`, as `deployment.all` lists it. */\nexport interface DokployDeployment {\n\tdeploymentId: string;\n\tstatus: DokployDeploymentStatus;\n\t/** ISO timestamp, by Dokploy's clock. */\n\tcreatedAt: string;\n\ttitle?: string;\n\terrorMessage?: string | null;\n}\n\nexport interface DokployApplicationUpdate {\n\tregistryId: string;\n\tdockerImage: string;\n\tsourceType: 'docker';\n}\n\nexport interface DokployRegistry {\n\tregistryId: string;\n\tregistryName: string;\n\tregistryUrl: string;\n\tusername: string;\n\timagePrefix: string | null;\n}\n\nexport interface DokployPostgres {\n\tpostgresId: string;\n\tname: string;\n\tappName: string;\n\tdatabaseName: string;\n\tdatabaseUser: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string | null;\n\tprojectId: string;\n\tenvironmentId: string;\n\tapplicationStatus: 'idle' | 'running' | 'done' | 'error';\n\texternalPort: number | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployPostgresUpdate {\n\tname: string;\n\tappName: string;\n\tdatabaseName: string;\n\tdatabaseUser: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string;\n}\n\nexport interface DokployRedis {\n\tredisId: string;\n\tname: string;\n\tappName: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string | null;\n\tprojectId: string;\n\tenvironmentId: string;\n\tapplicationStatus: 'idle' | 'running' | 'done' | 'error';\n\texternalPort: number | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployRedisUpdate {\n\tname: string;\n\tappName: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string;\n}\n\nexport type DokployCertificateType = 'letsencrypt' | 'none' | 'custom';\n/** A Dokploy compose stack. */\nexport interface DokployCompose {\n\tcomposeId: string;\n\tname: string;\n\t/** Dokploy's own generated name, which prefixes the stack's containers. */\n\tappName: string;\n\tcomposeFile?: string;\n\tcomposeStatus?: string;\n}\n\nexport type DokployDomainType = 'application' | 'compose' | 'preview';\n\nexport interface DokployDomainCreate {\n\t/** Domain hostname (e.g., 'api.example.com') */\n\thost: string;\n\t/** URL path (optional, e.g., '/api') */\n\tpath?: string | null;\n\t/** Container port to route to (1-65535) */\n\tport?: number | null;\n\t/** Enable HTTPS */\n\thttps?: boolean;\n\t/** Associated application ID */\n\tapplicationId?: string | null;\n\t/** Certificate type for HTTPS */\n\tcertificateType?: DokployCertificateType;\n\t/** Custom certificate resolver name */\n\tcustomCertResolver?: string | null;\n\t/** Docker Compose service ID */\n\tcomposeId?: string | null;\n\t/** Service name for compose */\n\tserviceName?: string | null;\n\t/** Domain type */\n\tdomainType?: DokployDomainType | null;\n\t/** Preview deployment ID */\n\tpreviewDeploymentId?: string | null;\n\t/** Internal routing path */\n\tinternalPath?: string | null;\n\t/** Strip path from forwarded requests */\n\tstripPath?: boolean;\n}\n\nexport interface DokployDomain extends DokployDomainCreate {\n\tdomainId: string;\n\tcreatedAt?: string;\n}\n\n// ============================================\n// Destination types (backup storage)\n// ============================================\n\nexport interface DokployDestination {\n\tdestinationId: string;\n\tname: string;\n\taccessKey: string;\n\tbucket: string;\n\tregion: string;\n\tendpoint: string | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployDestinationCreate {\n\t/** User-friendly name for the destination */\n\tname: string;\n\t/** S3 access key ID */\n\taccessKey: string;\n\t/** S3 secret access key */\n\tsecretAccessKey: string;\n\t/** S3 bucket name */\n\tbucket: string;\n\t/** AWS region (e.g., 'us-east-1') */\n\tregion: string;\n\t/** Optional endpoint for S3-compatible services */\n\tendpoint?: string;\n}\n\n// ============================================\n// Backup types (scheduled backups)\n// ============================================\n\nexport type DokployDatabaseType = 'postgres' | 'mysql' | 'mariadb' | 'mongo';\n\nexport interface DokployBackup {\n\tbackupId: string;\n\tschedule: string;\n\tprefix: string;\n\tenabled: boolean;\n\tdestinationId: string;\n\tpostgresId?: string;\n\tdatabaseType: DokployDatabaseType;\n\tkeepLatestCount: number | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployBackupCreate {\n\t/** Cron schedule (e.g., '0 2 * * *' for 2 AM daily) */\n\tschedule: string;\n\t/** Backup file prefix (e.g., 'production/postgres') */\n\tprefix: string;\n\t/** Destination ID for backup storage */\n\tdestinationId: string;\n\t/** Database name to backup */\n\tdatabase: string;\n\t/** Postgres instance ID */\n\tpostgresId: string;\n\t/** Enable/disable backup (default: true) */\n\tenabled?: boolean;\n\t/** Number of backups to keep (default: keep all) */\n\tkeepLatestCount?: number;\n}\n\nexport interface DokployBackupUpdate {\n\tschedule?: string;\n\tprefix?: string;\n\tenabled?: boolean;\n\tkeepLatestCount?: number;\n}\n\n/**\n * Create a Dokploy API client from stored credentials or environment\n */\nexport async function createDokployApi(\n\tendpoint?: string,\n): Promise<DokployApi | null> {\n\tconst { getDokployCredentials } = await import('../../auth/credentials');\n\n\t// Try environment variable first\n\tconst envToken = process.env.DOKPLOY_API_TOKEN;\n\tconst envEndpoint = endpoint || process.env.DOKPLOY_ENDPOINT;\n\n\tif (envToken && envEndpoint) {\n\t\treturn new DokployApi({ baseUrl: envEndpoint, token: envToken });\n\t}\n\n\t// Fall back to stored credentials\n\tconst creds = await getDokployCredentials();\n\tif (creds) {\n\t\treturn new DokployApi({\n\t\t\tbaseUrl: endpoint || creds.endpoint,\n\t\t\ttoken: creds.token,\n\t\t});\n\t}\n\n\treturn null;\n}\n","import { join } from 'node:path';\nimport type { Routes } from '../types.js';\nimport type { NormalizedWorkspace } from './types.js';\n\n/**\n * Normalize routes to an array of patterns.\n * Handles a glob or a list of them.\n * @internal Exported for use in dev command\n */\nexport function normalizeRoutes(routes: Routes | undefined): string[] {\n\tif (!routes) return [];\n\treturn Array.isArray(routes) ? routes : [routes];\n}\n\n/**\n * Get the first routes pattern as a string (for simple cases).\n * @internal Exported for use in dev command\n */\nexport function getFirstRoute(\n\troutes: string | string[] | undefined,\n): string | null {\n\tconst normalized = normalizeRoutes(routes);\n\treturn normalized[0] || null;\n}\n\n/**\n * Check if a file path matches endpoint patterns that could affect OpenAPI schema.\n * Returns true for changes that should trigger client regeneration.\n */\nexport function shouldRegenerateClient(\n\tfilePath: string,\n\troutesPattern: string,\n): boolean {\n\t// Normalize path separators\n\tconst normalizedPath = filePath.replace(/\\\\/g, '/');\n\tconst normalizedPattern = routesPattern.replace(/\\\\/g, '/');\n\n\t// Check if the file matches the routes pattern\n\t// This is a simple check - the file should be within the routes directory\n\tconst patternDir = normalizedPattern.split('*')[0] || '';\n\n\tif (!normalizedPath.includes(patternDir.replace('./', ''))) {\n\t\treturn false;\n\t}\n\n\t// Check file extension - only TypeScript endpoint files\n\tif (!normalizedPath.endsWith('.ts') && !normalizedPath.endsWith('.tsx')) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/**\n * Get backend apps that a web/mobile client depends on.\n */\nexport function getBackendDependencies(\n\tworkspace: NormalizedWorkspace,\n\tclientAppName: string,\n): string[] {\n\tconst clientApp = workspace.apps[clientAppName];\n\tif (!clientApp || (clientApp.type !== 'web' && clientApp.type !== 'mobile')) {\n\t\treturn [];\n\t}\n\n\treturn clientApp.dependencies.filter((dep) => {\n\t\tconst depApp = workspace.apps[dep];\n\t\treturn depApp?.type === 'backend' && depApp.routes;\n\t});\n}\n\n/**\n * Get web/mobile apps that depend on a backend app.\n */\nexport function getDependentFrontends(\n\tworkspace: NormalizedWorkspace,\n\tbackendAppName: string,\n): string[] {\n\tconst dependentApps: string[] = [];\n\n\tfor (const [appName, app] of Object.entries(workspace.apps)) {\n\t\tif (\n\t\t\t(app.type === 'web' || app.type === 'mobile') &&\n\t\t\tapp.dependencies.includes(backendAppName)\n\t\t) {\n\t\t\tdependentApps.push(appName);\n\t\t}\n\t}\n\n\treturn dependentApps;\n}\n\n/**\n * Get the path to a backend's OpenAPI spec file.\n */\nexport function getBackendOpenApiPath(\n\tworkspace: NormalizedWorkspace,\n\tbackendAppName: string,\n): string | null {\n\tconst app = workspace.apps[backendAppName];\n\tif (!app || app.type !== 'backend') {\n\t\treturn null;\n\t}\n\n\treturn join(workspace.root, app.path, '.gkm', 'openapi.ts');\n}\n","import { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { SniffResult } from '@geekmidas/envkit/sniffer';\nimport { output } from '../output.js';\nimport { LocalSandbox } from '../sandbox/local.js';\nimport { activeSandbox, type Sandbox } from '../sandbox/sandbox.js';\nimport { nodeWithTsx } from '../sandbox/worker.js';\nimport { normalizeRoutes } from '../workspace/client-generator.js';\nimport { getPublicEnvPrefix } from '../workspace/publicEnv.js';\nimport type { NormalizedAppConfig } from '../workspace/types.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * How long one sniff may run. Importing an entry, a config or a module of\n * routes takes a second or two; one that is still going after this is\n * waiting on something — a server it started, a connection, a prompt — and\n * would otherwise hold the deploy, and its lock, until someone noticed.\n */\nexport const SNIFF_TIMEOUT_MS = 30_000;\n\n/**\n * Resolve the path to a sniffer helper file.\n * Handles both dev (.ts with tsx) and production (.mjs from dist).\n *\n * In production: sniffer.ts is bundled into dist/index.mjs, but sniffer helper\n * files are output to dist/deploy/ as standalone modules for subprocess loading.\n *\n * In development: All files are in src/deploy/ and loaded via tsx.\n */\nfunction resolveSnifferFile(baseName: string): string {\n\t// Try deploy/ subdirectory first (production: bundled code is at dist/index.mjs,\n\t// but sniffer files are at dist/deploy/)\n\tconst deployMjsPath = resolve(__dirname, 'deploy', `${baseName}.mjs`);\n\tif (existsSync(deployMjsPath)) {\n\t\treturn deployMjsPath;\n\t}\n\n\t// Try same directory .mjs (production: if running from dist/deploy/ directly)\n\tconst mjsPath = resolve(__dirname, `${baseName}.mjs`);\n\tif (existsSync(mjsPath)) {\n\t\treturn mjsPath;\n\t}\n\n\t// Try same directory .ts (development with tsx: all files in src/deploy/)\n\tconst tsPath = resolve(__dirname, `${baseName}.ts`);\n\tif (existsSync(tsPath)) {\n\t\treturn tsPath;\n\t}\n\n\t// Fallback to .ts (will error if neither exists)\n\treturn tsPath;\n}\n\n// Re-export SniffResult for consumers\nexport type { SniffResult } from '@geekmidas/envkit/sniffer';\n\n/**\n * Result of sniffing an app's environment requirements.\n */\nexport interface SniffedEnvironment {\n\tappName: string;\n\t/**\n\t * All required environment variable names.\n\t * When `SniffAppOptions.markOptional` is true, optional variables are\n\t * suffixed with `?` (e.g. `PORT?`).\n\t */\n\trequiredEnvVars: string[];\n\t/**\n\t * Subset of variables that are optional (accessed via `.optional()` or\n\t * `.default()`). Always populated regardless of `markOptional`.\n\t */\n\toptionalEnvVars: string[];\n}\n\n/**\n * Options for sniffing an app's environment.\n */\nexport interface SniffAppOptions {\n\t/** Whether to log warnings for errors encountered during sniffing. Defaults to true. */\n\tlogWarnings?: boolean;\n\t/**\n\t * When true, optional variables (those with `.optional()` or `.default()`)\n\t * are suffixed with `?` in `requiredEnvVars` (e.g. `PORT?`).\n\t * Defaults to false.\n\t */\n\tmarkOptional?: boolean;\n\t/**\n\t * Where the app's code runs while it is sniffed. Defaults to the run's\n\t * own (`withSandbox`), else a {@link LocalSandbox} on the workspace —\n\t * either way never this process, and never with its environment.\n\t */\n\tsandbox?: Sandbox;\n\t/** How long each sniff may run. Defaults to {@link SNIFF_TIMEOUT_MS}. */\n\ttimeoutMs?: number;\n}\n\n/** Where a sniff runs, and for how long. */\ninterface SniffRun {\n\tsandbox?: Sandbox;\n\ttimeoutMs?: number;\n}\n\n/**\n * Run a sniffer worker in the sandbox, with the sandbox's environment and\n * nothing of the deploy's: the deploy's Dokploy token, registry login and AWS\n * keys stay where they are. A worker that outlives the timeout is killed, and\n * comes back as the error the caller reports.\n */\nasync function runSniffer(\n\trun: SniffRun,\n\tworkspacePath: string,\n\tcwd: string,\n\tnodeArgs: string[],\n): Promise<\n\t{ stdout: string; stderr: string; code: number | null } | { error: Error }\n> {\n\tconst sandbox =\n\t\trun.sandbox ??\n\t\tactiveSandbox() ??\n\t\tnew LocalSandbox({ root: resolve(workspacePath) });\n\n\ttry {\n\t\tconst result = await sandbox.exec('node', nodeArgs, {\n\t\t\tcwd,\n\t\t\tenv: { ...sandbox.env },\n\t\t\ttimeoutMs: run.timeoutMs ?? SNIFF_TIMEOUT_MS,\n\t\t\toutput: 'capture',\n\t\t});\n\t\treturn {\n\t\t\tstdout: result.stdout,\n\t\t\tstderr: result.stderr,\n\t\t\tcode: result.exitCode,\n\t\t};\n\t} catch (error) {\n\t\treturn {\n\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t};\n\t}\n}\n\n/** The worker's JSON answer: the last object on stdout naming `envVars`. */\nfunction snifferAnswer(stdout: string):\n\t| {\n\t\t\tenvVars?: string[];\n\t\t\toptionalEnvVars?: string[];\n\t\t\tunhandledRejections?: string[];\n\t\t\twarnings?: string[];\n\t\t\terror?: string | null;\n\t }\n\t| undefined {\n\ttry {\n\t\tconst jsonMatch = stdout.match(/\\{[^{}]*\"envVars\"[^{}]*\\}[^{]*$/);\n\t\treturn jsonMatch ? JSON.parse(jsonMatch[0]) : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Get required environment variables for an app.\n *\n * Detection strategy (in order):\n * 1. Frontend apps: Returns empty (no server secrets)\n * 2. Apps with `requiredEnv`: Uses explicit list from config\n * 3. Entry apps: Imports entry file in subprocess to capture config.parse() calls\n * 4. Route-based apps: Loads route files and calls getEnvironment() on each construct\n * 5. Apps with `envParser` (no routes): Runs SnifferEnvironmentParser to detect usage\n * 6. Apps with neither: Returns empty\n *\n * This function handles \"fire and forget\" async operations gracefully,\n * capturing errors and unhandled rejections without failing the build.\n *\n * @param app - The normalized app configuration\n * @param appName - The name of the app\n * @param workspacePath - Absolute path to the workspace root\n * @param options - Optional configuration for sniffing behavior\n * @returns The sniffed environment with required variables\n */\nexport async function sniffAppEnvironment(\n\tapp: NormalizedAppConfig,\n\tappName: string,\n\tworkspacePath: string,\n\toptions: SniffAppOptions = {},\n): Promise<SniffedEnvironment> {\n\tconst { logWarnings = true, markOptional = false } = options;\n\tconst run: SniffRun = {\n\t\t...(options.sandbox ? { sandbox: options.sandbox } : {}),\n\t\t...(options.timeoutMs ? { timeoutMs: options.timeoutMs } : {}),\n\t};\n\n\t// 1. Frontend apps - handle dependencies and config sniffing\n\tif (app.type === 'web' || app.type === 'mobile') {\n\t\t// Auto-generate {prefix}{DEP}_URL from dependencies, where prefix matches\n\t\t// the framework's public-var convention (NEXT_PUBLIC_, VITE_, ...).\n\t\t// For frameworks without a prefix (e.g. Remix), no dep var is required.\n\t\tconst publicPrefix = getPublicEnvPrefix(app.framework);\n\t\tconst depVars = publicPrefix\n\t\t\t? (app.dependencies ?? []).map(\n\t\t\t\t\t(dep) => `${publicPrefix}${dep.toUpperCase()}_URL`,\n\t\t\t\t)\n\t\t\t: [];\n\n\t\t// If config specified, sniff by importing the file(s)\n\t\t// The file calls .parse() at module load, which triggers sniffer to capture vars\n\t\tif (app.config) {\n\t\t\tconst sniffedVars: string[] = [];\n\t\t\tconst sniffedOptional: string[] = [];\n\n\t\t\t// Collect config paths to sniff\n\t\t\tconst configPaths: string[] = [];\n\t\t\tif (app.config.client) configPaths.push(app.config.client);\n\t\t\tif (app.config.server) configPaths.push(app.config.server);\n\n\t\t\t// Sniff each config file\n\t\t\tfor (const configPath of configPaths) {\n\t\t\t\tconst result = await sniffEntryFile(\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tapp.path,\n\t\t\t\t\tworkspacePath,\n\t\t\t\t\trun,\n\t\t\t\t);\n\n\t\t\t\tif (logWarnings && result.error) {\n\t\t\t\t\toutput.warn(\n\t\t\t\t\t\t`[sniffer] ${appName}: Config file \"${configPath}\" threw error during sniffing (env vars still captured): ${result.error.message}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tsniffedVars.push(...result.envVars);\n\t\t\t\tsniffedOptional.push(...result.optionalEnvVars);\n\t\t\t}\n\n\t\t\tconst optionalEnvVars = [...new Set(sniffedOptional)];\n\t\t\t// Combine: dependency vars + sniffed vars (deduplicated)\n\t\t\tconst allVars = applyMarkOptional(\n\t\t\t\t[...new Set([...depVars, ...sniffedVars])],\n\t\t\t\toptionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t);\n\t\t\treturn { appName, requiredEnvVars: allVars, optionalEnvVars };\n\t\t}\n\n\t\treturn { appName, requiredEnvVars: depVars, optionalEnvVars: [] };\n\t}\n\n\t// 2. Entry apps - import entry file in subprocess to trigger config.parse()\n\tif (app.entry) {\n\t\tconst result = await sniffEntryFile(\n\t\t\tapp.entry,\n\t\t\tapp.path,\n\t\t\tworkspacePath,\n\t\t\trun,\n\t\t);\n\n\t\tif (logWarnings && result.error) {\n\t\t\toutput.warn(\n\t\t\t\t`[sniffer] ${appName}: Entry file threw error during sniffing (env vars still captured): ${result.error.message}`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tappName,\n\t\t\trequiredEnvVars: applyMarkOptional(\n\t\t\t\tresult.envVars,\n\t\t\t\tresult.optionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t),\n\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t};\n\t}\n\n\t// 4. Route-based apps - load routes and call getEnvironment() on each construct\n\tif (app.routes) {\n\t\tconst result = await sniffRouteFiles(\n\t\t\tnormalizeRoutes(app.routes),\n\t\t\tapp.path,\n\t\t\tworkspacePath,\n\t\t\trun,\n\t\t);\n\n\t\tif (logWarnings && result.error) {\n\t\t\toutput.warn(\n\t\t\t\t`[sniffer] ${appName}: Route sniffing threw error (env vars still captured): ${result.error.message}`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tappName,\n\t\t\trequiredEnvVars: applyMarkOptional(\n\t\t\t\tresult.envVars,\n\t\t\t\tresult.optionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t),\n\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t};\n\t}\n\n\t// 5. Apps with envParser but no routes - run sniffer to detect env var usage\n\tif (app.envParser) {\n\t\tconst result = await sniffEnvParser(\n\t\t\tapp.envParser,\n\t\t\tapp.path,\n\t\t\tworkspacePath,\n\t\t\trun,\n\t\t);\n\n\t\t// Log any issues for debugging\n\t\tif (logWarnings) {\n\t\t\tif (result.error) {\n\t\t\t\toutput.warn(\n\t\t\t\t\t`[sniffer] ${appName}: envParser threw error during sniffing (env vars still captured): ${result.error.message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (result.unhandledRejections.length > 0) {\n\t\t\t\toutput.warn(\n\t\t\t\t\t`[sniffer] ${appName}: Fire-and-forget rejections during sniffing (suppressed): ${result.unhandledRejections.map((e) => e.message).join(', ')}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tappName,\n\t\t\trequiredEnvVars: applyMarkOptional(\n\t\t\t\tresult.envVars,\n\t\t\t\tresult.optionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t),\n\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t};\n\t}\n\n\t// No env detection method available\n\treturn { appName, requiredEnvVars: [], optionalEnvVars: [] };\n}\n\n/**\n * Apply the `?` suffix to optional variables in an env var list.\n * Only modifies the list when `markOptional` is true and there are optional vars.\n */\nfunction applyMarkOptional(\n\tenvVars: string[],\n\toptionalEnvVars: string[],\n\tmarkOptional: boolean,\n): string[] {\n\tif (!markOptional || optionalEnvVars.length === 0) return envVars;\n\tconst optionalSet = new Set(optionalEnvVars);\n\treturn envVars.map((v) => (optionalSet.has(v) ? `${v}?` : v));\n}\n\n/**\n * Result from sniffing an entry file.\n */\ninterface EntrySniffResult {\n\tenvVars: string[];\n\toptionalEnvVars: string[];\n\terror?: Error;\n}\n\n/**\n * Sniff an entry file by importing it in a subprocess.\n *\n * Entry apps call `config.parse()` at module load time. To capture which\n * env vars are accessed, we:\n * 1. Spawn a subprocess with a module loader hook\n * 2. The loader intercepts `@geekmidas/envkit` and replaces EnvironmentParser\n * with SnifferEnvironmentParser\n * 3. Import the entry file (triggers config.parse())\n * 4. Capture and return the accessed env var names\n *\n * This approach provides process isolation - each app is sniffed in its own\n * subprocess, preventing module cache pollution.\n *\n * @param entryPath - Relative path to the entry file (e.g., './src/index.ts')\n * @param appPath - The app's path relative to workspace (e.g., 'apps/auth')\n * @param workspacePath - Absolute path to workspace root\n * @returns EntrySniffResult with env vars and optional error\n */\nasync function sniffEntryFile(\n\tentryPath: string,\n\tappPath: string,\n\tworkspacePath: string,\n\trun: SniffRun = {},\n): Promise<EntrySniffResult> {\n\tconst fullEntryPath = resolve(workspacePath, appPath, entryPath);\n\tconst loaderPath = resolveSnifferFile('sniffer-loader');\n\tconst workerPath = resolveSnifferFile('sniffer-worker');\n\n\t// tsx first — each `.tsx` compiled by its own tsconfig's JSX settings\n\t// rather than whatever the app's tsconfig includes — then the loader that\n\t// swaps in the sniffing EnvironmentParser.\n\tconst ran = await runSniffer(\n\t\trun,\n\t\tworkspacePath,\n\t\tresolve(workspacePath, appPath),\n\t\tnodeWithTsx(workerPath, [fullEntryPath], [loaderPath]),\n\t);\n\tif ('error' in ran)\n\t\treturn { envVars: [], optionalEnvVars: [], error: ran.error };\n\n\tconst answer = snifferAnswer(ran.stdout);\n\tif (answer) {\n\t\treturn {\n\t\t\tenvVars: answer.envVars || [],\n\t\t\toptionalEnvVars: answer.optionalEnvVars || [],\n\t\t\terror: answer.error ? new Error(answer.error) : undefined,\n\t\t};\n\t}\n\n\t// If we couldn't parse the output, return empty with error info\n\treturn {\n\t\tenvVars: [],\n\t\toptionalEnvVars: [],\n\t\terror: new Error(\n\t\t\t`Failed to sniff entry file (exit code ${ran.code}): ${ran.stderr || ran.stdout || 'No output'}`,\n\t\t),\n\t};\n}\n\n/**\n * Sniff route files by loading constructs and calling getEnvironment().\n *\n * Route-based apps have endpoints, functions, crons, and subscribers that\n * use services. Each service's register() method accesses environment variables.\n *\n * This runs in a subprocess with tsx loader to properly handle TypeScript\n * compilation and path alias resolution (e.g., `src/...` imports).\n *\n * @param routes - Glob pattern(s) for route files\n * @param appPath - The app's path relative to workspace (e.g., 'apps/api')\n * @param workspacePath - Absolute path to workspace root\n * @returns EntrySniffResult with env vars and optional error\n */\nasync function sniffRouteFiles(\n\troutes: string | string[],\n\tappPath: string,\n\tworkspacePath: string,\n\trun: SniffRun = {},\n): Promise<EntrySniffResult> {\n\tconst fullAppPath = resolve(workspacePath, appPath);\n\tconst workerPath = resolveSnifferFile('sniffer-routes-worker');\n\n\t// Convert array of patterns to first pattern (worker handles glob internally)\n\tconst routesArray = Array.isArray(routes) ? routes : [routes];\n\tconst pattern = routesArray[0];\n\tif (!pattern) {\n\t\treturn {\n\t\t\tenvVars: [],\n\t\t\toptionalEnvVars: [],\n\t\t\terror: new Error('No route patterns provided'),\n\t\t};\n\t}\n\n\tconst ran = await runSniffer(\n\t\trun,\n\t\tworkspacePath,\n\t\tfullAppPath,\n\t\tnodeWithTsx(workerPath, [fullAppPath, pattern]),\n\t);\n\tif ('error' in ran)\n\t\treturn { envVars: [], optionalEnvVars: [], error: ran.error };\n\n\t// Log any stderr output (import errors, etc.)\n\tif (ran.stderr) {\n\t\tran.stderr\n\t\t\t.split('\\n')\n\t\t\t.filter((line) => line.trim())\n\t\t\t.forEach((line) => output.warn(line));\n\t}\n\n\tconst answer = snifferAnswer(ran.stdout);\n\tif (answer) {\n\t\treturn {\n\t\t\tenvVars: answer.envVars || [],\n\t\t\toptionalEnvVars: answer.optionalEnvVars || [],\n\t\t\terror: answer.error ? new Error(answer.error) : undefined,\n\t\t};\n\t}\n\n\t// If we couldn't parse the output, return empty with error info\n\treturn {\n\t\tenvVars: [],\n\t\toptionalEnvVars: [],\n\t\terror: new Error(\n\t\t\t`Failed to sniff route files (exit code ${ran.code}): ${ran.stderr || ran.stdout || 'No output'}`,\n\t\t),\n\t};\n}\n\n/**\n * Run the SnifferEnvironmentParser on an envParser module to detect\n * which environment variables it accesses.\n *\n * In a subprocess, like the entry and route sniffers: the module is the\n * project's code, and it was imported into the deploy itself before — with\n * every credential the deploy held in reach. Fire-and-forget rejections are\n * collected there with the shared `sniffWithFireAndForget`.\n *\n * @param envParserPath - The envParser config (e.g., './src/config/env#envParser')\n * @param appPath - The app's path relative to workspace\n * @param workspacePath - Absolute path to workspace root\n * @returns SniffResult with env vars and any errors encountered\n */\nasync function sniffEnvParser(\n\tenvParserPath: string,\n\tappPath: string,\n\tworkspacePath: string,\n\trun: SniffRun = {},\n): Promise<SniffResult> {\n\t// Parse the envParser path: './src/config/env#envParser' or './src/config/env'\n\tconst [modulePath, exportName = 'default'] = envParserPath.split('#');\n\tif (!modulePath) {\n\t\treturn { envVars: [], optionalEnvVars: [], unhandledRejections: [] };\n\t}\n\n\tconst appRoot = resolve(workspacePath, appPath);\n\tconst fullPath = resolve(appRoot, modulePath);\n\tconst workerPath = resolveSnifferFile('sniffer-envparser-worker');\n\n\tconst ran = await runSniffer(\n\t\trun,\n\t\tworkspacePath,\n\t\tappRoot,\n\t\tnodeWithTsx(workerPath, [fullPath, exportName]),\n\t);\n\tif ('error' in ran) {\n\t\treturn {\n\t\t\tenvVars: [],\n\t\t\toptionalEnvVars: [],\n\t\t\tunhandledRejections: [],\n\t\t\terror: ran.error,\n\t\t};\n\t}\n\n\tconst answer = snifferAnswer(ran.stdout);\n\tif (!answer) {\n\t\treturn {\n\t\t\tenvVars: [],\n\t\t\toptionalEnvVars: [],\n\t\t\tunhandledRejections: [],\n\t\t\terror: new Error(\n\t\t\t\t`Failed to sniff envParser (exit code ${ran.code}): ${ran.stderr || ran.stdout || 'No output'}`,\n\t\t\t),\n\t\t};\n\t}\n\n\tfor (const warning of answer.warnings ?? []) output.warn(warning);\n\n\treturn {\n\t\tenvVars: answer.envVars ?? [],\n\t\toptionalEnvVars: answer.optionalEnvVars ?? [],\n\t\tunhandledRejections: (answer.unhandledRejections ?? []).map(\n\t\t\t(message) => new Error(message),\n\t\t),\n\t\t...(answer.error ? { error: new Error(answer.error) } : {}),\n\t};\n}\n\n/**\n * Sniff environment requirements for multiple apps.\n *\n * @param apps - Map of app name to app config\n * @param workspacePath - Absolute path to workspace root\n * @param options - Optional configuration for sniffing behavior\n * @returns Map of app name to sniffed environment\n */\nexport async function sniffAllApps(\n\tapps: Record<string, NormalizedAppConfig>,\n\tworkspacePath: string,\n\toptions: SniffAppOptions = {},\n): Promise<Map<string, SniffedEnvironment>> {\n\tconst results = new Map<string, SniffedEnvironment>();\n\n\tfor (const [appName, app] of Object.entries(apps)) {\n\t\tconst sniffed = await sniffAppEnvironment(\n\t\t\tapp,\n\t\t\tappName,\n\t\t\tworkspacePath,\n\t\t\toptions,\n\t\t);\n\t\tresults.set(appName, sniffed);\n\t}\n\n\treturn results;\n}\n\n// Export for testing\nexport {\n\tsniffEnvParser as _sniffEnvParser,\n\tsniffEntryFile as _sniffEntryFile,\n\tsniffRouteFiles as _sniffRouteFiles,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"sniffer-ByWtH28i.mjs","names":[],"sources":["../src/secrets/encryption.ts","../src/deploy/events.ts","../src/deploy/StateProvider.ts","../src/deploy/StateStore.ts","../src/target/dokploy/dokploy-api.ts","../src/workspace/client-generator.ts","../src/deploy/sniffer.ts"],"sourcesContent":["import {\n\tcreateCipheriv,\n\tcreateDecipheriv,\n\tcreateHash,\n\trandomBytes,\n} from 'node:crypto';\nimport type { EmbeddableSecrets, EncryptedPayload } from './types';\n\n/** AES-256-GCM configuration */\nconst ALGORITHM = 'aes-256-gcm';\nconst KEY_LENGTH = 32; // 256 bits\nconst IV_LENGTH = 12; // 96 bits for GCM\nconst AUTH_TAG_LENGTH = 16; // 128 bits\n\n/**\n * Encrypt secrets for embedding in a bundle.\n * Uses AES-256-GCM with a randomly generated ephemeral key.\n *\n * @param secrets - Key-value pairs to encrypt\n * @returns Encrypted payload with ephemeral master key\n */\nexport function encryptSecrets(secrets: EmbeddableSecrets): EncryptedPayload {\n\t// Generate ephemeral key and IV\n\tconst masterKey = randomBytes(KEY_LENGTH);\n\tconst iv = randomBytes(IV_LENGTH);\n\n\t// Serialize secrets to JSON\n\tconst plaintext = JSON.stringify(secrets);\n\n\t// Encrypt\n\tconst cipher = createCipheriv(ALGORITHM, masterKey, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(plaintext, 'utf-8'),\n\t\tcipher.final(),\n\t]);\n\n\t// Get auth tag\n\tconst authTag = cipher.getAuthTag();\n\n\t// Combine ciphertext + auth tag\n\tconst combined = Buffer.concat([ciphertext, authTag]);\n\n\treturn {\n\t\tencrypted: combined.toString('base64'),\n\t\tiv: iv.toString('hex'),\n\t\tmasterKey: masterKey.toString('hex'),\n\t};\n}\n\n/**\n * Decrypt secrets from an encrypted payload.\n * Used at runtime to decrypt embedded credentials.\n *\n * @param encrypted - Base64 encoded ciphertext + auth tag\n * @param iv - Hex encoded IV\n * @param masterKey - Hex encoded master key\n * @returns Decrypted secrets\n */\nexport function decryptSecrets(\n\tencrypted: string,\n\tiv: string,\n\tmasterKey: string,\n): EmbeddableSecrets {\n\t// Decode inputs\n\tconst key = Buffer.from(masterKey, 'hex');\n\tconst ivBuffer = Buffer.from(iv, 'hex');\n\tconst combined = Buffer.from(encrypted, 'base64');\n\n\t// Split ciphertext and auth tag\n\tconst ciphertext = combined.subarray(0, -AUTH_TAG_LENGTH);\n\tconst authTag = combined.subarray(-AUTH_TAG_LENGTH);\n\n\t// Decrypt\n\tconst decipher = createDecipheriv(ALGORITHM, key, ivBuffer);\n\tdecipher.setAuthTag(authTag);\n\n\tconst plaintext = Buffer.concat([\n\t\tdecipher.update(ciphertext),\n\t\tdecipher.final(),\n\t]);\n\n\treturn JSON.parse(plaintext.toString('utf-8')) as EmbeddableSecrets;\n}\n\n/**\n * Generate the define options for tsdown/esbuild.\n * These will be injected at build time.\n */\nexport function generateDefineOptions(\n\tpayload: EncryptedPayload,\n): Record<string, string> {\n\treturn {\n\t\t__GKM_ENCRYPTED_CREDENTIALS__: JSON.stringify(payload.encrypted),\n\t\t__GKM_CREDENTIALS_IV__: JSON.stringify(payload.iv),\n\t};\n}\n\n/**\n * A short, one-way name for a master key: the first 8 hex characters of its\n * SHA-256.\n *\n * Printed where the key itself used to be, so a person can tell which key a\n * build used — and match it against the one they hold — without the output\n * carrying a value that decrypts every secret of the stage.\n */\nexport function keyFingerprint(masterKey: string): string {\n\treturn createHash('sha256').update(masterKey).digest('hex').slice(0, 8);\n}\n","/**\n * What a deploy reports while it runs.\n *\n * Plain JSON objects, discriminated by `type`: `gkm deploy --json` writes them\n * one per line, and a host can forward them to a UI or a log pipeline without\n * a serialiser of its own. Nothing in an event is a class instance, a function\n * or a secret — an error travels as its name and message, a credential never\n * travels at all.\n *\n * Every target reports through this one union, so a UI renders a Dokploy\n * deploy and a plugin's the same way.\n *\n * The union only grows: a new event is a new `type`, a new field is optional,\n * and nothing that exists changes meaning. A consumer ignores types it does\n * not know. That is why the events carry no version number — the CLI that\n * writes a stream and the program that reads it agree by that rule rather\n * than by negotiating one.\n */\n\nimport type { DeployResult } from './types';\n\n/**\n * The stretch of a deploy an event belongs to.\n *\n * - `validate`: the config, the target, the stage, the apps, the lock, the\n * stage's secrets and what each app reads from its environment\n * - `plan`: a dry run's lookups — what would be created and what reused\n * - `provision`: what the apps run on — for Dokploy the project, environment\n * and registry — and the declared constructs\n * - `build`: each app's artifact, built without changing anything live\n * - `release`: the artifacts put live. Dokploy applies the stage's\n * migrations, then builds and pushes each image here, beside its\n * application, because a site's build args are resolved per app — and\n * checks each backend before any site is released\n * - `verify`: what was released answers — for Dokploy, each site's DNS\n * records and health\n * - `rollback`: the previous release restored, after `release` or `verify`\n * failed, on a target that can\n */\nexport type DeployPhase =\n\t| 'validate'\n\t| 'plan'\n\t| 'provision'\n\t| 'build'\n\t| 'release'\n\t| 'verify'\n\t| 'rollback';\n\n/** An error, as an event carries it. */\nexport interface DeployEventError {\n\t/** The error's class name — `MissingCredential`, `StateLocked`. */\n\tname: string;\n\tmessage: string;\n}\n\n/**\n * How a resource came to be used:\n * - `recorded`: the id the stage's state holds still resolves\n * - `resumed`: a run that died after creating it is picked up\n * - `found`: it existed and was looked up\n * - `created`: this run created it\n */\nexport type ResourceVia = 'recorded' | 'resumed' | 'found' | 'created';\n\n/** One resource a deploy touches, planned or applied. */\nexport interface ResourceChange {\n\t/** `<type>:<name>`, unique within the stage — `application:api`. */\n\tkey: string;\n\t/** `project`, `environment`, `registry`, `application`, `domain`, `image`, or a construct's kind. */\n\tresourceType: string;\n\t/**\n\t * For a plan: whether the run would create it, reuse what exists, or\n\t * ensure it (look it up, and create it if missing). For an applied change:\n\t * what was done.\n\t */\n\taction: 'create' | 'reuse' | 'ensure' | 'build';\n\t/** The target's id, once there is one. */\n\tid?: string;\n}\n\nexport type DeployEvent =\n\t| {\n\t\t\ttype: 'deploy.started';\n\t\t\tstage: string;\n\t\t\t/** The target the run deploys through, by the name it was given. */\n\t\t\ttarget: string;\n\t\t\t/** `<namespace>/<project>`. */\n\t\t\tidentity: string;\n\t\t\ttag: string;\n\t\t\t/** The apps this run deploys, in order. */\n\t\t\tapps: string[];\n\t\t\tdryRun: boolean;\n\t }\n\t| { type: 'phase.started'; phase: DeployPhase }\n\t| { type: 'phase.finished'; phase: DeployPhase }\n\t/** A phase threw; `deploy.failed` follows (after a rollback, if any). */\n\t| { type: 'phase.failed'; phase: DeployPhase; error: DeployEventError }\n\t/**\n\t * A progress line, exactly as `gkm deploy` prints it — leading newline and\n\t * indentation included, so a terminal renderer prints `message` as is.\n\t */\n\t| { type: 'log'; level: 'info' | 'warn' | 'error'; message: string }\n\t| { type: 'app.skipped'; app: string; reason: string }\n\t| ({ type: 'resource.planned' } & ResourceChange)\n\t| ({ type: 'resource.applied'; via: ResourceVia } & ResourceChange & {\n\t\t\t\tid: string;\n\t\t\t})\n\t| {\n\t\t\ttype: 'artifact.built';\n\t\t\tapp: string;\n\t\t\timageRef: string;\n\t\t\t/** The registry's digest, `sha256:…`, once pushed. */\n\t\t\tdigest?: string;\n\t }\n\t| {\n\t\t\ttype: 'app.deployed';\n\t\t\tapp: string;\n\t\t\tapplicationId: string;\n\t\t\timageRef: string;\n\t\t\turl: string;\n\t }\n\t| { type: 'app.failed'; app: string; error: DeployEventError }\n\t/** One check of whether a released app answers. */\n\t| {\n\t\t\ttype: 'health.checked';\n\t\t\tapp: string;\n\t\t\t/** What was asked: a URL, or the target's own status check. */\n\t\t\turl: string;\n\t\t\thealthy: boolean;\n\t\t\t/** The HTTP status, when the check was a request. */\n\t\t\tstatus?: number;\n\t\t\t/** 1 for the first check of the app in this run. */\n\t\t\tattempt: number;\n\t }\n\t/**\n\t * A deployed stage runs a dev service — `--allow-dev-services` — in place\n\t * of real mail or object storage: Mailpit delivers no mail, MinIO keeps\n\t * every object on one container's disk. A warning, every run.\n\t */\n\t| {\n\t\t\ttype: 'dev-service.used';\n\t\t\tservice: 'minio' | 'mailpit';\n\t\t\tstage: string;\n\t\t\t/** The constructs it stands in for. */\n\t\t\tconstructs: string[];\n\t }\n\t/**\n\t * The stack's log UI (`deploy.compose.logs`) is up and answering: where\n\t * it is and how it is reached — through an SSH tunnel to a loopback\n\t * port, or publicly on a host that allows only some addresses.\n\t */\n\t| {\n\t\t\ttype: 'logs.ready';\n\t\t\tservice: 'openobserve';\n\t\t\taccess: 'tunnel' | 'public';\n\t\t\t/** Where a browser opens it: the tunnel's end, or its public host. */\n\t\t\turl: string;\n\t\t\t/** The loopback port it is published on, for a tunnel. */\n\t\t\tport?: number;\n\t\t\t/** The addresses a public host answers. */\n\t\t\tallow?: string[];\n\t\t\temail: string;\n\t }\n\t| { type: 'deploy.finished'; result: DeployResult }\n\t| { type: 'deploy.failed'; error: DeployEventError };\n\n/** Every `type` a deploy event can have. */\nexport type DeployEventType = DeployEvent['type'];\n\n/** An error as an event carries it: its name and message, nothing else. */\nexport function eventError(error: unknown): DeployEventError {\n\tif (error instanceof Error) {\n\t\treturn { name: error.name, message: error.message };\n\t}\n\treturn { name: 'Error', message: String(error) };\n}\n","/**\n * State configuration, and the `StateProvider` interface a custom backend can\n * implement.\n *\n * Deploy state is read and written through a `StateStore` (`StateStore.ts`):\n * locks, versions and resource records. A custom `StateProvider` — read and\n * write a whole state, nothing else — still works behind `LegacyStateStore`,\n * which warns that it cannot lock.\n */\n\nimport type { StateStore } from './StateStore';\nimport type { DokployStageState } from './state';\n\n/**\n * Interface for deployment state storage providers.\n *\n * Implementations must handle:\n * - Reading state for a stage (returns null if not found)\n * - Writing state for a stage (creates or updates)\n */\nexport interface StateProvider {\n\t/**\n\t * Read deployment state for a stage.\n\t *\n\t * @param stage - The deployment stage (e.g., 'development', 'production')\n\t * @returns The state object or null if not found\n\t */\n\tread(stage: string): Promise<DokployStageState | null>;\n\n\t/**\n\t * Write deployment state for a stage.\n\t *\n\t * @param stage - The deployment stage\n\t * @param state - The state object to persist\n\t */\n\twrite(stage: string, state: DokployStageState): Promise<void>;\n}\n\n/**\n * Valid AWS regions.\n */\nexport type AwsRegion =\n\t| 'us-east-1'\n\t| 'us-east-2'\n\t| 'us-west-1'\n\t| 'us-west-2'\n\t| 'af-south-1'\n\t| 'ap-east-1'\n\t| 'ap-south-1'\n\t| 'ap-south-2'\n\t| 'ap-southeast-1'\n\t| 'ap-southeast-2'\n\t| 'ap-southeast-3'\n\t| 'ap-southeast-4'\n\t| 'ap-northeast-1'\n\t| 'ap-northeast-2'\n\t| 'ap-northeast-3'\n\t| 'ca-central-1'\n\t| 'eu-central-1'\n\t| 'eu-central-2'\n\t| 'eu-west-1'\n\t| 'eu-west-2'\n\t| 'eu-west-3'\n\t| 'eu-south-1'\n\t| 'eu-south-2'\n\t| 'eu-north-1'\n\t| 'me-south-1'\n\t| 'me-central-1'\n\t| 'sa-east-1';\n\n/**\n * Local state provider config.\n */\nexport interface LocalStateConfig {\n\tprovider: 'local';\n}\n\n/**\n * SSM state provider config (requires region).\n */\nexport interface SSMStateConfig {\n\tprovider: 'ssm';\n\t/** AWS region (required for SSM provider) */\n\tregion: AwsRegion;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n}\n\n/**\n * S3 state config: one object per stage, written with conditional puts\n * (`If-Match` / `If-None-Match`), and a lock object beside it.\n *\n * Keys: `<prefix>/<workspace name>/<stage>/state.json` and `lock.json`.\n */\nexport interface S3StateConfig {\n\tprovider: 's3';\n\t/** Bucket the state lives in. It must already exist. */\n\tbucket: string;\n\t/** AWS region of the bucket */\n\tregion: AwsRegion;\n\t/** Key prefix inside the bucket (default: `gkm`) */\n\tprefix?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n}\n\n/**\n * Custom state provider config.\n */\nexport interface CustomStateConfig {\n\t/**\n\t * Custom implementation. A `StateStore` is used as is; a `StateProvider`\n\t * still works, but cannot lock — deploy warns `StateStoreWithoutLocking`.\n\t */\n\tprovider: StateProvider | StateStore;\n}\n\n/**\n * State configuration types.\n */\nexport type StateConfig =\n\t| LocalStateConfig\n\t| SSMStateConfig\n\t| S3StateConfig\n\t| CustomStateConfig;\n\n/**\n * Check if value is a StateProvider implementation.\n */\nexport function isStateProvider(value: unknown): value is StateProvider {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\ttypeof (value as StateProvider).read === 'function' &&\n\t\ttypeof (value as StateProvider).write === 'function'\n\t);\n}\n\nexport interface CreateStateStoreConfig {\n\t/** State config from workspace */\n\tconfig?: StateConfig;\n\t/** Workspace root directory (for local provider) */\n\tworkspaceRoot: string;\n\t/** Workspace name (for SSM parameter path) */\n\tworkspaceName: string;\n}\n","/**\n * Deploy state store.\n *\n * `StateProvider` reads and writes a whole stage at once, with nothing to stop\n * two runs of the same stage from overwriting each other and nowhere to record\n * a resource the moment it exists. A `StateStore` adds the three things a\n * deploy needs to be safe against crashes and concurrent runs:\n *\n * - `lock(stage)`: one run per stage. A second gets `StateLocked`, naming the\n * holder, instead of racing the first.\n * - Versioned `read` and conditional `write`: a write names the version it was\n * based on, and raises `StateVersionConflict` when the stored state moved on\n * underneath it, so nothing is lost silently even without the lock.\n * - Per-resource records: `putResource` journals one resource at a time, so a\n * run that dies half way leaves behind the ids of everything it created.\n *\n * Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A\n * custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it\n * working but cannot lock.\n */\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport { hostname, userInfo } from 'node:os';\nimport {\n\ttype CreateStateStoreConfig,\n\tisStateProvider,\n\ttype S3StateConfig,\n\ttype SSMStateConfig,\n} from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/** Opaque: a file hash, an SSM parameter version or an S3 ETag. */\nexport type StateVersion = string;\n\n/**\n * `pending` is written before the call that creates the resource, `ready`\n * once the target has returned its id. A run that finds a `pending` record\n * knows a previous run may have created the resource and should look for it\n * before creating another.\n */\nexport type ResourceStatus = 'pending' | 'ready';\n\nexport interface ResourceRecord {\n\t/** Unique within the stage, `<type>:<name>` — e.g. `application:api`. */\n\tkey: string;\n\t/** What kind of resource it is — e.g. `application`, `postgres`. */\n\ttype: string;\n\t/** The id the target gave it. Absent while `pending`. */\n\tid?: string;\n\tstatus: ResourceStatus;\n\t/** Whatever the target needs to adopt the resource on a later run. */\n\tdata?: Record<string, unknown>;\n\tupdatedAt: string;\n}\n\n/** A resource as a caller hands it to `putResource`; the store stamps it. */\nexport type ResourceInput = Omit<ResourceRecord, 'updatedAt'>;\n\nexport interface StoredStageState {\n\tstate: DokployStageState;\n\tresources: Record<string, ResourceRecord>;\n\t/** Pass back as `expectedVersion` to write on top of exactly this read. */\n\tversion: StateVersion;\n}\n\nexport interface WriteOptions {\n\t/**\n\t * The version this write is based on — `null` to create state that must\n\t * not exist yet.\n\t */\n\texpectedVersion: StateVersion | null;\n}\n\nexport interface ResourceWriteOptions {\n\t/**\n\t * Omitted, the record is applied on top of whatever is stored now (still\n\t * conditionally, so a concurrent write raises rather than being lost).\n\t */\n\texpectedVersion?: StateVersion;\n}\n\nexport interface LockHolder {\n\t/** Unique per acquisition, so a release never removes someone else's lock. */\n\tid: string;\n\towner: string;\n\thost: string;\n\tpid: number;\n\tacquiredAt: string;\n\t/** What the holder is doing, e.g. `deploy`. */\n\toperation?: string;\n}\n\nexport interface LockOptions {\n\toperation?: string;\n}\n\nexport interface StateLock {\n\treadonly stage: string;\n\treadonly holder: LockHolder;\n\t/** Removes the lock if it is still this one; safe to call twice. */\n\trelease(): Promise<void>;\n}\n\nexport interface StateStore {\n\t/** Takes the stage's lock, or raises `StateLocked` naming its holder. */\n\tlock(stage: string, options?: LockOptions): Promise<StateLock>;\n\t/**\n\t * Removes the stage's lock whoever holds it, for a run that crashed with\n\t * it held. Returns the holder it removed, or `null` if there was none.\n\t */\n\tforceUnlock(stage: string): Promise<LockHolder | null>;\n\tread(stage: string): Promise<StoredStageState | null>;\n\t/** Replaces the stage's state, keeping its resource records. */\n\twrite(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion>;\n\tputResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion>;\n\tdeleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion>;\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class StateLocked extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly holder: LockHolder | null,\n\t\treadonly location: string,\n\t) {\n\t\tconst by = holder\n\t\t\t? `${holder.owner}@${holder.host} (pid ${holder.pid}) since ${holder.acquiredAt}${holder.operation ? ` for '${holder.operation}'` : ''}`\n\t\t\t: 'a run that did not record who it was';\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' is locked by ${by} (${location}). ` +\n\t\t\t\t`Wait for that run to finish; if it crashed, release the lock with ` +\n\t\t\t\t`\\`gkm state:unlock --stage ${stage}\\`.`,\n\t\t);\n\t\tthis.name = 'StateLocked';\n\t}\n}\n\nexport class StateVersionConflict extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly expectedVersion: StateVersion | null,\n\t\treadonly actualVersion: StateVersion | null,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' changed since it was read ` +\n\t\t\t\t`(expected ${expectedVersion ? `version ${expectedVersion}` : 'no state'}, ` +\n\t\t\t\t`found ${actualVersion ? `version ${actualVersion}` : 'no state'}). ` +\n\t\t\t\t`Another run wrote it: read the state again and re-apply the change, ` +\n\t\t\t\t`and hold the stage's lock so runs take turns.`,\n\t\t);\n\t\tthis.name = 'StateVersionConflict';\n\t}\n}\n\nexport class StageStateMissing extends Error {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`There is no deploy state for stage '${stage}' to record a resource in. ` +\n\t\t\t\t`Write the stage's state first (write with expectedVersion: null).`,\n\t\t);\n\t\tthis.name = 'StageStateMissing';\n\t}\n}\n\nexport class StateUnreadable extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly location: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' at ${location} is not valid JSON. ` +\n\t\t\t\t`Restore it from a backup (or the provider's history) rather than ` +\n\t\t\t\t`deploying without it — a deploy without state recreates every resource.`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'StateUnreadable';\n\t}\n}\n\nexport class StateSchemaTooNew extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly schemaVersion: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' has schema version ${String(schemaVersion)}, ` +\n\t\t\t\t`newer than this @geekmidas/cli understands (${STATE_SCHEMA_VERSION}). ` +\n\t\t\t\t`Upgrade @geekmidas/cli to the version that wrote it.`,\n\t\t);\n\t\tthis.name = 'StateSchemaTooNew';\n\t}\n}\n\n/**\n * Not thrown: emitted (once per store) when a custom `StateProvider` stands\n * in for a store, so a deploy that cannot be protected from a concurrent one\n * says so.\n */\nexport class StateStoreWithoutLocking extends Error {\n\tconstructor(readonly provider: string) {\n\t\tsuper(\n\t\t\t`The custom state provider '${provider}' has no lock and no versions, ` +\n\t\t\t\t`so two deploys of the same stage can still overwrite each other. ` +\n\t\t\t\t`Implement StateStore (lock, versioned read, conditional write) or ` +\n\t\t\t\t`use the 'local', 'ssm' or 's3' state provider.`,\n\t\t);\n\t\tthis.name = 'StateStoreWithoutLocking';\n\t}\n}\n\nexport class StateStoreNeedsWorkspaceName extends Error {\n\tconstructor(readonly provider: string) {\n\t\tsuper(\n\t\t\t`The '${provider}' state provider keys state by workspace name. ` +\n\t\t\t\t`Set \"name\" in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'StateStoreNeedsWorkspaceName';\n\t}\n}\n\nexport class UnknownStateProvider extends Error {\n\tconstructor(readonly config: unknown) {\n\t\tsuper(\n\t\t\t`Unknown state provider ${JSON.stringify(config)}. Use 'local', 'ssm', ` +\n\t\t\t\t`'s3', or an object implementing StateStore or StateProvider.`,\n\t\t);\n\t\tthis.name = 'UnknownStateProvider';\n\t}\n}\n\n/** A lock or write that could not get at the state backend in time. */\nexport class StateStoreBusy extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly location: string,\n\t) {\n\t\tsuper(\n\t\t\t`Timed out waiting to write deploy state for stage '${stage}' (${location}). ` +\n\t\t\t\t`If no other gkm process is running, remove ${location}.`,\n\t\t);\n\t\tthis.name = 'StateStoreBusy';\n\t}\n}\n\n// ============================================================================\n// Document format\n// ============================================================================\n\nexport const STATE_SCHEMA_VERSION = 2;\n\n/**\n * What a store persists. v1 was `DokployStageState` on its own; v2 wraps it\n * so resource records and a write serial can sit beside it. The serial\n * changes every write, so content-addressed versions (a file hash, an S3\n * ETag) never repeat — a writer holding a version from two writes ago cannot\n * match by accident.\n */\nexport interface StateDocument {\n\tschemaVersion: typeof STATE_SCHEMA_VERSION;\n\tstage: string;\n\tserial: number;\n\tstate: DokployStageState;\n\tresources: Record<string, ResourceRecord>;\n\tupdatedAt: string;\n}\n\nexport type DecodedDocument =\n\t| { schemaVersion: 2; document: StateDocument }\n\t| { schemaVersion: 1; state: DokployStageState };\n\nexport function decodeDocument(\n\tstage: string,\n\tbody: string,\n\tlocation: string,\n): DecodedDocument {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(body);\n\t} catch (error) {\n\t\tthrow new StateUnreadable(stage, location, error);\n\t}\n\n\tif (typeof parsed !== 'object' || parsed === null) {\n\t\tthrow new StateUnreadable(stage, location, parsed);\n\t}\n\n\tconst schemaVersion = (parsed as { schemaVersion?: unknown }).schemaVersion;\n\tif (schemaVersion === undefined) {\n\t\treturn { schemaVersion: 1, state: parsed as DokployStageState };\n\t}\n\tif (schemaVersion === STATE_SCHEMA_VERSION) {\n\t\treturn { schemaVersion: 2, document: parsed as StateDocument };\n\t}\n\tthrow new StateSchemaTooNew(stage, schemaVersion);\n}\n\nexport function encodeDocument(document: StateDocument): string {\n\treturn JSON.stringify(document, null, 2);\n}\n\n/**\n * v1 state as a v2 document. The ids v1 kept are seeded as `ready` records,\n * so the first journaled deploy after an upgrade adopts them rather than\n * treating them as unknown.\n */\nexport function migrateV1(\n\tstage: string,\n\tstate: DokployStageState,\n): StateDocument {\n\tconst now = new Date().toISOString();\n\tconst resources: Record<string, ResourceRecord> = {};\n\tconst seed = (type: string, name: string | null, id: string | undefined) => {\n\t\tif (!id) return;\n\t\tconst key = name ? `${type}:${name}` : type;\n\t\tresources[key] = { key, type, id, status: 'ready', updatedAt: now };\n\t};\n\n\tseed('project', null, state.projectId);\n\tseed('environment', null, state.environmentId);\n\tfor (const [app, id] of Object.entries(state.applications ?? {})) {\n\t\tseed('application', app, id);\n\t}\n\tseed('postgres', null, state.services?.postgresId);\n\tseed('redis', null, state.services?.redisId);\n\tseed('backup-destination', null, state.backups?.destinationId);\n\n\treturn {\n\t\tschemaVersion: STATE_SCHEMA_VERSION,\n\t\tstage,\n\t\tserial: 1,\n\t\tstate,\n\t\tresources,\n\t\tupdatedAt: now,\n\t};\n}\n\nexport function newLockHolder(options?: LockOptions): LockHolder {\n\tlet owner = 'unknown';\n\ttry {\n\t\towner = userInfo().username;\n\t} catch {\n\t\t// No passwd entry (a container running as an arbitrary uid).\n\t}\n\treturn {\n\t\tid: randomUUID(),\n\t\towner,\n\t\thost: hostname(),\n\t\tpid: process.pid,\n\t\tacquiredAt: new Date().toISOString(),\n\t\toperation: options?.operation,\n\t};\n}\n\nexport function contentVersion(body: string): StateVersion {\n\treturn createHash('sha256').update(body).digest('hex').slice(0, 16);\n}\n\n// ============================================================================\n// Shared implementation\n// ============================================================================\n\nexport interface RawState {\n\tbody: string;\n\tversion: StateVersion;\n}\n\n/**\n * A store over a backend that can read and conditionally write one blob per\n * stage, and create a lock object only if it does not exist. Migration and\n * resource records are the same for every such backend, so they live here.\n */\nexport abstract class DocumentStateStore implements StateStore {\n\t/** Where the stage's state lives, for messages. */\n\tprotected abstract location(stage: string): string;\n\tprotected abstract readRaw(stage: string): Promise<RawState | null>;\n\t/**\n\t * Writes only if the stored version is `expectedVersion` (`null`: only if\n\t * nothing is stored), else raises `StateVersionConflict`.\n\t */\n\tprotected abstract writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion>;\n\t/** Keeps the v1 body as it was. Never replaces an existing backup. */\n\tprotected abstract writeV1Backup(stage: string, body: string): Promise<void>;\n\t/** Creates the lock only if absent, else raises `StateLocked`. */\n\tprotected abstract createLock(\n\t\tstage: string,\n\t\tholder: LockHolder,\n\t): Promise<void>;\n\tprotected abstract readLock(stage: string): Promise<LockHolder | null>;\n\t/** Removes the lock — only if it is `holder`'s, when one is given. */\n\tprotected abstract removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void>;\n\n\tasync lock(stage: string, options?: LockOptions): Promise<StateLock> {\n\t\tconst holder = newLockHolder(options);\n\t\tawait this.createLock(stage, holder);\n\t\tlet released = false;\n\t\treturn {\n\t\t\tstage,\n\t\t\tholder,\n\t\t\trelease: async () => {\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tawait this.removeLock(stage, holder);\n\t\t\t},\n\t\t};\n\t}\n\n\tasync forceUnlock(stage: string): Promise<LockHolder | null> {\n\t\tconst holder = await this.readLock(stage);\n\t\tawait this.removeLock(stage, null);\n\t\treturn holder;\n\t}\n\n\tasync read(stage: string): Promise<StoredStageState | null> {\n\t\tconst current = await this.readDocument(stage);\n\t\tif (!current) return null;\n\t\treturn {\n\t\t\tstate: current.document.state,\n\t\t\tresources: current.document.resources,\n\t\t\tversion: current.version,\n\t\t};\n\t}\n\n\tasync write(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion> {\n\t\tif (options.expectedVersion === null) {\n\t\t\tconst document: StateDocument = {\n\t\t\t\tschemaVersion: STATE_SCHEMA_VERSION,\n\t\t\t\tstage,\n\t\t\t\tserial: 1,\n\t\t\t\tstate,\n\t\t\t\tresources: {},\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\treturn this.writeRaw(stage, encodeDocument(document), null);\n\t\t}\n\t\treturn this.mutate(stage, options.expectedVersion, (document) => ({\n\t\t\t...document,\n\t\t\tstate,\n\t\t}));\n\t}\n\n\tasync putResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\treturn this.mutate(stage, options?.expectedVersion, (document) => ({\n\t\t\t...document,\n\t\t\tresources: {\n\t\t\t\t...document.resources,\n\t\t\t\t[record.key]: { ...record, updatedAt: new Date().toISOString() },\n\t\t\t},\n\t\t}));\n\t}\n\n\tasync deleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\treturn this.mutate(stage, options?.expectedVersion, (document) => {\n\t\t\tconst { [key]: _removed, ...resources } = document.resources;\n\t\t\treturn { ...document, resources };\n\t\t});\n\t}\n\n\tprivate async mutate(\n\t\tstage: string,\n\t\texpectedVersion: StateVersion | undefined,\n\t\tchange: (document: StateDocument) => StateDocument,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.readDocument(stage);\n\t\tif (!current) {\n\t\t\tif (expectedVersion !== undefined) {\n\t\t\t\tthrow new StateVersionConflict(stage, expectedVersion, null);\n\t\t\t}\n\t\t\tthrow new StageStateMissing(stage);\n\t\t}\n\t\tif (expectedVersion !== undefined && current.version !== expectedVersion) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, current.version);\n\t\t}\n\n\t\tconst next = change(current.document);\n\t\tnext.serial = current.document.serial + 1;\n\t\tnext.updatedAt = new Date().toISOString();\n\t\treturn this.writeRaw(stage, encodeDocument(next), current.version);\n\t}\n\n\t/**\n\t * Reads the stage's document, migrating v1 in place on the way. The\n\t * migration is itself a conditional write: if another run migrated (or\n\t * wrote) first, its result is read instead.\n\t */\n\tprivate async readDocument(\n\t\tstage: string,\n\t\tattempt = 0,\n\t): Promise<{ document: StateDocument; version: StateVersion } | null> {\n\t\tconst raw = await this.readRaw(stage);\n\t\tif (!raw) return null;\n\n\t\tconst decoded = decodeDocument(stage, raw.body, this.location(stage));\n\t\tif (decoded.schemaVersion === 2) {\n\t\t\treturn { document: decoded.document, version: raw.version };\n\t\t}\n\n\t\tawait this.writeV1Backup(stage, raw.body);\n\t\tconst document = migrateV1(stage, decoded.state);\n\t\ttry {\n\t\t\tconst version = await this.writeRaw(\n\t\t\t\tstage,\n\t\t\t\tencodeDocument(document),\n\t\t\t\traw.version,\n\t\t\t);\n\t\t\treturn { document, version };\n\t\t} catch (error) {\n\t\t\tif (error instanceof StateVersionConflict && attempt < 2) {\n\t\t\t\treturn this.readDocument(stage, attempt + 1);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Resolution\n// ============================================================================\n\nexport function isStateStore(value: unknown): value is StateStore {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\ttypeof (value as StateStore).lock === 'function' &&\n\t\ttypeof (value as StateStore).read === 'function' &&\n\t\ttypeof (value as StateStore).write === 'function' &&\n\t\ttypeof (value as StateStore).putResource === 'function'\n\t);\n}\n\nexport interface CreateStateStoreOptions extends CreateStateStoreConfig {\n\t/** Where `StateStoreWithoutLocking` goes; `process.emitWarning` by default. */\n\twarn?: (warning: StateStoreWithoutLocking) => void;\n}\n\n/**\n * The store for a workspace's `state` config:\n *\n * - none / `'local'`: `LocalStateStore` under `.gkm/`\n * - `'ssm'`: `SSMStateStore`, with no local cache — SSM is read directly, so\n * a stale local copy can never win over it\n * - `'s3'`: `S3StateStore`\n * - a `StateStore` object: used as is\n * - a `StateProvider` object: wrapped by `LegacyStateStore`\n */\nexport async function createStateStore(\n\toptions: CreateStateStoreOptions,\n): Promise<StateStore> {\n\tconst { config, workspaceRoot, workspaceName } = options;\n\n\tif (!config || config.provider === 'local') {\n\t\tconst { LocalStateStore } = await import('./LocalStateStore');\n\t\treturn new LocalStateStore(workspaceRoot);\n\t}\n\n\tif (isStateStore(config.provider)) {\n\t\treturn config.provider;\n\t}\n\n\tif (isStateProvider(config.provider)) {\n\t\tconst { LegacyStateStore } = await import('./LegacyStateStore');\n\t\treturn new LegacyStateStore(config.provider, { warn: options.warn });\n\t}\n\n\tif (config.provider === 'ssm') {\n\t\tif (!workspaceName) throw new StateStoreNeedsWorkspaceName('ssm');\n\t\tconst { SSMStateStore } = await import('./SSMStateStore');\n\t\tconst ssm = config as SSMStateConfig;\n\t\treturn SSMStateStore.create({\n\t\t\tworkspaceName,\n\t\t\tregion: ssm.region,\n\t\t\tprofile: ssm.profile,\n\t\t});\n\t}\n\n\tif (config.provider === 's3') {\n\t\tif (!workspaceName) throw new StateStoreNeedsWorkspaceName('s3');\n\t\tconst { S3StateStore } = await import('./S3StateStore');\n\t\tconst s3 = config as S3StateConfig;\n\t\treturn S3StateStore.create({\n\t\t\tworkspaceName,\n\t\t\tbucket: s3.bucket,\n\t\t\tprefix: s3.prefix,\n\t\t\tregion: s3.region,\n\t\t\tprofile: s3.profile,\n\t\t});\n\t}\n\n\tthrow new UnknownStateProvider(config);\n}\n","/**\n * Centralized Dokploy API client\n *\n * Handles authentication, error handling, and provides typed methods for all Dokploy API endpoints.\n */\n\nexport interface DokployApiOptions {\n\t/** Dokploy server URL (e.g., https://dokploy.example.com) */\n\tbaseUrl: string;\n\t/** API token for authentication */\n\ttoken: string;\n\t/**\n\t * How long one request may take, in milliseconds, before it is abandoned\n\t * with `DokployRequestTimedOut`. Defaults to 30 seconds.\n\t */\n\ttimeoutMs?: number;\n\t/**\n\t * Aborts every request, and any retry still waiting to run. A request it\n\t * stops rejects with the signal's reason.\n\t */\n\tsignal?: AbortSignal;\n}\n\n/** Per request, so a stalled server fails the deploy, not the CI job. */\nexport const DEFAULT_DOKPLOY_TIMEOUT_MS = 30_000;\n\n/**\n * Dokploy accepted the connection but did not answer in time.\n *\n * Not retried: a request that arrived and stalled may still be acted on, and\n * sending it again could repeat a deploy or a create the server is already\n * doing.\n */\nexport class DokployRequestTimedOut extends Error {\n\tconstructor(\n\t\treadonly baseUrl: string,\n\t\treadonly endpoint: string,\n\t\treadonly timeoutMs: number,\n\t) {\n\t\tsuper(\n\t\t\t`Dokploy at ${baseUrl} did not answer ${endpoint} within ${timeoutMs}ms. ` +\n\t\t\t\t'Check that the Dokploy server is healthy, or raise `timeoutMs` if this call is known to be slow.',\n\t\t);\n\t\tthis.name = 'DokployRequestTimedOut';\n\t}\n}\n\n/**\n * Waits `ms` between retries, or rejects with the signal's reason the moment\n * it aborts — a caller who gave up should not sit through the backoff.\n */\nfunction pause(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(signal.reason);\n\t\t\treturn;\n\t\t}\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timer);\n\t\t\treject(signal?.reason);\n\t\t};\n\t\tconst timer = setTimeout(() => {\n\t\t\tsignal?.removeEventListener('abort', onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tsignal?.addEventListener('abort', onAbort, { once: true });\n\t});\n}\n\n/** How long a deployment may take to finish before a deploy gives up on it. */\nexport const DEFAULT_DEPLOYMENT_TIMEOUT_MS = 10 * 60_000;\n\nexport interface WaitForDeploymentOptions {\n\t/**\n\t * Only a deployment created after this counts — by Dokploy's clock, as\n\t * `latestDeploymentAt` read it before `application.deploy`.\n\t */\n\tsince: Date;\n\t/** How long to wait. Defaults to 10 minutes. */\n\ttimeoutMs?: number;\n\tsignal?: AbortSignal;\n\t/** The first wait between polls; it doubles up to `maxIntervalMs`. */\n\tintervalMs?: number;\n\tmaxIntervalMs?: number;\n}\n\n/** A deployment Dokploy ran and reported as failed. */\nexport class DeploymentFailed extends Error {\n\tconstructor(\n\t\treadonly applicationId: string,\n\t\treadonly deployment: DokployDeployment,\n\t) {\n\t\tsuper(\n\t\t\t`Dokploy's deployment ${deployment.deploymentId} of application ${applicationId} ended in \"${deployment.status}\"` +\n\t\t\t\t`${deployment.errorMessage ? `: ${deployment.errorMessage}` : ''}. ` +\n\t\t\t\t'Its log in Dokploy says why — usually an image the server could not pull, or a container that exited on start.',\n\t\t);\n\t\tthis.name = 'DeploymentFailed';\n\t}\n}\n\n/** A deployment that had not finished when the deploy stopped waiting. */\nexport class DeploymentTimedOut extends Error {\n\tconstructor(\n\t\treadonly applicationId: string,\n\t\treadonly timeoutMs: number,\n\t\t/** The last status seen, if a deployment had appeared at all. */\n\t\treadonly lastStatus: DokployDeploymentStatus | undefined,\n\t) {\n\t\tsuper(\n\t\t\t`Application ${applicationId} did not finish deploying within ${Math.round(timeoutMs / 1000)}s ` +\n\t\t\t\t`(${lastStatus ? `last status \"${lastStatus}\"` : 'no deployment started'}). ` +\n\t\t\t\t'Check its deployment log in Dokploy, or raise deploy.dokploy.verify.deploymentTimeoutMs for an image that is slow to pull.',\n\t\t);\n\t\tthis.name = 'DeploymentTimedOut';\n\t}\n}\n\nexport interface DokployErrorResponse {\n\tmessage?: string;\n\tissues?: Array<{ message: string }>;\n}\n\nexport class DokployApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic issues?: Array<{ message: string }>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'DokployApiError';\n\t}\n}\n\n/**\n * Dokploy API client\n */\nexport class DokployApi {\n\tprivate baseUrl: string;\n\tprivate token: string;\n\tprivate timeoutMs: number;\n\tprivate signal: AbortSignal | undefined;\n\n\tconstructor(options: DokployApiOptions) {\n\t\tthis.baseUrl = options.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n\t\tthis.token = options.token;\n\t\tthis.timeoutMs = options.timeoutMs ?? DEFAULT_DOKPLOY_TIMEOUT_MS;\n\t\tthis.signal = options.signal;\n\t}\n\n\t/**\n\t * Make a GET request to the Dokploy API\n\t */\n\tasync get<T>(endpoint: string): Promise<T> {\n\t\treturn this.request<T>('GET', endpoint);\n\t}\n\n\t/**\n\t * Make a POST request to the Dokploy API\n\t */\n\tasync post<T>(endpoint: string, body?: Record<string, unknown>): Promise<T> {\n\t\treturn this.request<T>('POST', endpoint, body);\n\t}\n\n\t/**\n\t * Make a request to the Dokploy API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: Record<string, unknown>,\n\t): Promise<T> {\n\t\tconst url = `${this.baseUrl}/api/${endpoint}`;\n\n\t\t// Retried, because the *transport* fails in one specific way here: a\n\t\t// deploy pushes an image for several minutes, and the first call after\n\t\t// that finds a keep-alive socket the server has since closed. `fetch`\n\t\t// reports it as a bare `fetch failed` with no status, which aborted a\n\t\t// deploy that had already built and pushed everything.\n\t\t//\n\t\t// Only connection failures. A response — including a 4xx — means the\n\t\t// request arrived, and repeating it would be repeating a decision the\n\t\t// server already made.\n\t\t//\n\t\t// Each attempt gets its own deadline: a server that accepts the\n\t\t// connection and never answers would otherwise hold the deploy — and a\n\t\t// CI job — until the runner's own limit.\n\t\tlet response: Response | undefined;\n\t\tlet timeout: AbortSignal | undefined;\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 1; attempt <= 3; attempt++) {\n\t\t\tthis.signal?.throwIfAborted();\n\t\t\ttimeout = AbortSignal.timeout(this.timeoutMs);\n\t\t\ttry {\n\t\t\t\tresponse = await fetch(url, {\n\t\t\t\t\tmethod,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t'x-api-key': this.token,\n\t\t\t\t\t},\n\t\t\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t\t\t\tsignal: this.signal\n\t\t\t\t\t\t? AbortSignal.any([this.signal, timeout])\n\t\t\t\t\t\t: timeout,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t} catch (error) {\n\t\t\t\t// An abort is a decision, not a dropped connection: never retried.\n\t\t\t\tconst stopped = this.stopped(timeout, endpoint);\n\t\t\t\tif (stopped) throw stopped;\n\t\t\t\tlastError = error;\n\t\t\t\tif (attempt === 3) break;\n\t\t\t\tawait pause(attempt * 750, this.signal);\n\t\t\t}\n\t\t}\n\n\t\tif (!response || !timeout) {\n\t\t\tconst detail =\n\t\t\t\tlastError instanceof Error ? lastError.message : String(lastError);\n\t\t\tthrow new DokployApiError(\n\t\t\t\t`Could not reach Dokploy at ${this.baseUrl} (${endpoint}): ${detail}`,\n\t\t\t\t0,\n\t\t\t\t'Network error',\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\treturn await this.read<T>(response);\n\t\t} catch (error) {\n\t\t\t// The deadline covers the body too: headers can arrive and the body\n\t\t\t// stall just the same.\n\t\t\tthrow this.stopped(timeout, endpoint) ?? error;\n\t\t}\n\t}\n\n\t/**\n\t * What a request cut short should reject with: the caller's own reason\n\t * when they aborted, `DokployRequestTimedOut` when the deadline passed,\n\t * nothing when it was neither.\n\t */\n\tprivate stopped(timeout: AbortSignal, endpoint: string): unknown {\n\t\tif (this.signal?.aborted) return this.signal.reason;\n\t\tif (timeout.aborted) {\n\t\t\treturn new DokployRequestTimedOut(this.baseUrl, endpoint, this.timeoutMs);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Turns a response into its JSON body, or a `DokployApiError`. */\n\tprivate async read<T>(response: Response): Promise<T> {\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Dokploy API error: ${response.status} ${response.statusText}`;\n\t\t\tlet issues: Array<{ message: string }> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as DokployErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Dokploy API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\tif (errorBody.issues?.length) {\n\t\t\t\t\tissues = errorBody.issues;\n\t\t\t\t\terrorMessage += `\\n Issues: ${errorBody.issues.map((i) => i.message).join(', ')}`;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new DokployApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\tissues,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses (204 No Content or empty body)\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Every external port published on this server, across every project.\n\t *\n\t * Dokploy knows what it has bound; guessing does not. A port that looks free\n\t * from here can be held by a service in a project this deploy has never\n\t * heard of — which surfaces as `Port 5432 is already in use by container\n\t * \"…\"`, naming something the caller cannot see.\n\t *\n\t * `project.all` is a summary, so each project is fetched: the per-service\n\t * `externalPort` only appears on `project.one`.\n\t */\n\tasync publishedPorts(): Promise<Set<number>> {\n\t\tconst ports = new Set<number>();\n\t\tconst projects = await this.listProjects();\n\n\t\tfor (const project of projects) {\n\t\t\tconst details = await this.getProject(project.projectId);\n\n\t\t\tfor (const environment of details.environments ?? []) {\n\t\t\t\tconst resources = [\n\t\t\t\t\t...(environment.postgres ?? []),\n\t\t\t\t\t...(environment.redis ?? []),\n\t\t\t\t\t...(environment.applications ?? []),\n\t\t\t\t] as { externalPort?: number | null }[];\n\n\t\t\t\tfor (const resource of resources) {\n\t\t\t\t\tif (typeof resource.externalPort === 'number') {\n\t\t\t\t\t\tports.add(resource.externalPort);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn ports;\n\t}\n\n\t/**\n\t * Validate the API token by making a test request\n\t */\n\tasync validateToken(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.get('project.all');\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ============================================\n\t// Project endpoints\n\t// ============================================\n\n\t/**\n\t * List all projects\n\t */\n\tasync listProjects(): Promise<DokployProject[]> {\n\t\treturn this.get<DokployProject[]>('project.all');\n\t}\n\n\t/**\n\t * Get a single project by ID\n\t */\n\tasync getProject(projectId: string): Promise<DokployProjectDetails> {\n\t\treturn this.get<DokployProjectDetails>(\n\t\t\t`project.one?projectId=${projectId}`,\n\t\t);\n\t}\n\n\t/**\n\t * Create a new project\n\t */\n\tasync createProject(\n\t\tname: string,\n\t\tdescription?: string,\n\t): Promise<{ project: DokployProject; environment: DokployEnvironment }> {\n\t\treturn this.post<{\n\t\t\tproject: DokployProject;\n\t\t\tenvironment: DokployEnvironment;\n\t\t}>('project.create', {\n\t\t\tname,\n\t\t\tdescription: description ?? `Created by gkm CLI`,\n\t\t});\n\t}\n\n\t/**\n\t * Change a project's name or description.\n\t *\n\t * The name is sent with the description because `project.update` validates\n\t * the whole project on some Dokploy versions, and a missing name fails it.\n\t */\n\tasync updateProject(\n\t\tprojectId: string,\n\t\tupdates: { name: string; description: string },\n\t): Promise<void> {\n\t\tawait this.post('project.update', { projectId, ...updates });\n\t}\n\n\t/**\n\t * Delete a project and all its resources\n\t */\n\tasync deleteProject(projectId: string): Promise<void> {\n\t\tawait this.post('project.remove', { projectId });\n\t}\n\n\t// ============================================\n\t// Environment endpoints\n\t// ============================================\n\n\t/**\n\t * Create an environment in a project\n\t */\n\tasync createEnvironment(\n\t\tprojectId: string,\n\t\tname: string,\n\t\tdescription?: string,\n\t): Promise<DokployEnvironment> {\n\t\treturn this.post<DokployEnvironment>('environment.create', {\n\t\t\tprojectId,\n\t\t\tname,\n\t\t\tdescription: description ?? `${name} environment`,\n\t\t});\n\t}\n\n\t// ============================================\n\t// Application endpoints\n\t// ============================================\n\n\t/**\n\t * List all applications in a project\n\t */\n\tasync listApplications(projectId: string): Promise<DokployApplication[]> {\n\t\treturn this.listInProject(projectId, (env) => env.applications);\n\t}\n\n\t/**\n\t * Everything of one kind in a project, across its environments.\n\t *\n\t * From `project.one`, because the per-kind `*.all?projectId=` endpoints\n\t * return **404** on current Dokploy: resources live under environments now.\n\t * Those calls used to be wrapped in `catch { return [] }`, which turned \"I\n\t * could not ask\" into \"there are none\" — and for a find-or-create that is\n\t * the worst available answer. Every deploy created a second application, a\n\t * second Postgres and a second Redis, silently, for as long as the project\n\t * existed.\n\t *\n\t * A failure here is not swallowed: creating a duplicate is worse than\n\t * stopping.\n\t */\n\tprivate async listInProject<T>(\n\t\tprojectId: string,\n\t\tpick: (environment: DokployEnvironment) => T[] | undefined,\n\t): Promise<T[]> {\n\t\tconst project = await this.getProject(projectId);\n\n\t\t// Tagged with the environment they came from: `project.one` nests them,\n\t\t// and a caller filtering by environment has nothing to filter on\n\t\t// otherwise.\n\t\treturn (project.environments ?? []).flatMap((environment) =>\n\t\t\t(pick(environment) ?? []).map((resource) => ({\n\t\t\t\t...resource,\n\t\t\t\tenvironmentId: environment.environmentId,\n\t\t\t})),\n\t\t);\n\t}\n\n\t/**\n\t * Find an application by name in a project\n\t */\n\tasync findApplicationByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t\t/**\n\t\t * The environment to look in.\n\t\t *\n\t\t * A project has one application list per environment, and the same name\n\t\t * legitimately exists in each — `prod` and `staging` both run `api`.\n\t\t * Searching across all of them finds whichever came back first and\n\t\t * deploys a stage's image into another stage's application.\n\t\t */\n\t\tenvironmentId?: string,\n\t): Promise<DokployApplication | undefined> {\n\t\tconst applications = await this.listApplications(projectId);\n\t\tconst normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\n\t\treturn applications.find(\n\t\t\t(app) =>\n\t\t\t\t(!environmentId || app.environmentId === environmentId) &&\n\t\t\t\t(app.name === name || app.appName === normalizedName),\n\t\t);\n\t}\n\n\t/**\n\t * Create a new application\n\t */\n\tasync createApplication(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t): Promise<DokployApplication> {\n\t\treturn this.post<DokployApplication>('application.create', {\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\tappName: name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),\n\t\t});\n\t}\n\n\t/**\n\t * Find or create an application by name\n\t */\n\tasync findOrCreateApplication(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t): Promise<{ application: DokployApplication; created: boolean }> {\n\t\tconst existing = await this.findApplicationByName(\n\t\t\tprojectId,\n\t\t\tname,\n\t\t\tenvironmentId,\n\t\t);\n\t\tif (existing) {\n\t\t\treturn { application: existing, created: false };\n\t\t}\n\t\tconst application = await this.createApplication(\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t);\n\t\treturn { application, created: true };\n\t}\n\n\t/**\n\t * Get an application by ID\n\t */\n\tasync getApplication(\n\t\tapplicationId: string,\n\t): Promise<DokployApplication | null> {\n\t\ttry {\n\t\t\treturn await this.get<DokployApplication>(\n\t\t\t\t`application.one?applicationId=${applicationId}`,\n\t\t\t);\n\t\t} catch {\n\t\t\t// Application not found\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Update an application\n\t */\n\tasync updateApplication(\n\t\tapplicationId: string,\n\t\tupdates: Partial<DokployApplicationUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('application.update', {\n\t\t\tapplicationId,\n\t\t\t...updates,\n\t\t});\n\t}\n\n\t/**\n\t * Save environment variables for an application\n\t */\n\tasync saveApplicationEnv(\n\t\tapplicationId: string,\n\t\tenv: string,\n\t\toptions?: { buildArgs?: string; buildSecrets?: string },\n\t): Promise<void> {\n\t\t// `buildArgs`, `buildSecrets` and `createEnvFile` are `nonoptional` on\n\t\t// current Dokploy, the same way the docker provider's credentials are:\n\t\t// they must be present, and null is how \"none\" is spelled. Omitting them\n\t\t// is rejected with three anonymous validation errors that name no field.\n\t\tawait this.post('application.saveEnvironment', {\n\t\t\tapplicationId,\n\t\t\tenv,\n\t\t\tbuildArgs: options?.buildArgs ?? null,\n\t\t\tbuildSecrets: options?.buildSecrets ?? null,\n\t\t\tcreateEnvFile: false,\n\t\t});\n\t}\n\n\t/**\n\t * Configure application to use Docker provider (pull from registry)\n\t *\n\t * For private registries, either:\n\t * - Use `registryId` if the registry is configured in Dokploy\n\t * - Or provide `username`, `password`, and `registryUrl` directly\n\t */\n\tasync saveDockerProvider(\n\t\tapplicationId: string,\n\t\tdockerImage: string,\n\t\toptions?: {\n\t\t\t/** Registry ID in Dokploy (for pre-configured registries) */\n\t\t\tregistryId?: string;\n\t\t\t/** Registry username (for direct auth) */\n\t\t\tusername?: string;\n\t\t\t/** Registry password (for direct auth) */\n\t\t\tpassword?: string;\n\t\t\t/** Registry URL (for direct auth, e.g., ghcr.io) */\n\t\t\tregistryUrl?: string;\n\t\t},\n\t): Promise<void> {\n\t\t// All five are `nonoptional` on current Dokploy — including the three\n\t\t// credential fields, which are meaningless when a registry is selected by\n\t\t// id. Omitting them is rejected; sending them as null is accepted. So\n\t\t// they are always present and null is how \"this registry needs none\" is\n\t\t// spelled.\n\t\tawait this.post('application.saveDockerProvider', {\n\t\t\tapplicationId,\n\t\t\tdockerImage,\n\t\t\t// Spread first: `registryId` selects a stored registry and is the\n\t\t\t// usual case, and dropping it silently deploys an image nothing can\n\t\t\t// pull. The three below are then filled in rather than replaced.\n\t\t\t...options,\n\t\t\tusername: options?.username ?? null,\n\t\t\tpassword: options?.password ?? null,\n\t\t\tregistryUrl: options?.registryUrl ?? null,\n\t\t});\n\t}\n\n\t/**\n\t * Deploy an application\n\t */\n\tasync deployApplication(applicationId: string): Promise<void> {\n\t\tawait this.post('application.deploy', { applicationId });\n\t}\n\n\t/**\n\t * An application's deployments, newest first.\n\t *\n\t * Sorted here rather than trusted to arrive sorted: which deployment is\n\t * the latest is the whole question `waitForDeployment` asks.\n\t */\n\tasync listDeployments(applicationId: string): Promise<DokployDeployment[]> {\n\t\tconst deployments = await this.get<DokployDeployment[]>(\n\t\t\t`deployment.all?applicationId=${encodeURIComponent(applicationId)}`,\n\t\t);\n\t\treturn [...deployments].sort(\n\t\t\t(a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt),\n\t\t);\n\t}\n\n\t/**\n\t * When the application's latest deployment was created, by Dokploy's own\n\t * clock — what `waitForDeployment`'s `since` is measured against. Epoch\n\t * when it has none.\n\t */\n\tasync latestDeploymentAt(applicationId: string): Promise<Date> {\n\t\tconst [latest] = await this.listDeployments(applicationId);\n\t\treturn new Date(latest ? Date.parse(latest.createdAt) : 0);\n\t}\n\n\t/**\n\t * Wait for the deployment `application.deploy` queued to finish.\n\t *\n\t * `application.deploy` only queues one: it answers before the image is\n\t * pulled, let alone running, so a deploy that stopped there reported\n\t * success for containers that had not started. This polls the latest\n\t * deployment created after `since` — Dokploy's clock, from\n\t * `latestDeploymentAt` before deploying, so a laptop's skew cannot pick an\n\t * older one — and the application's own status, backing off from\n\t * `intervalMs` to `maxIntervalMs`.\n\t *\n\t * @throws {DeploymentFailed} when the deployment ends in `error` or is\n\t * cancelled, or the application reports `error` once it has finished.\n\t * @throws {DeploymentTimedOut} when it has not finished within `timeoutMs`.\n\t */\n\tasync waitForDeployment(\n\t\tapplicationId: string,\n\t\toptions: WaitForDeploymentOptions,\n\t): Promise<DokployDeployment> {\n\t\tconst {\n\t\t\tsince,\n\t\t\ttimeoutMs = DEFAULT_DEPLOYMENT_TIMEOUT_MS,\n\t\t\tsignal,\n\t\t\tintervalMs = 2_000,\n\t\t\tmaxIntervalMs = 15_000,\n\t\t} = options;\n\t\tconst after = since.getTime();\n\t\tconst deadline = Date.now() + timeoutMs;\n\t\tlet interval = intervalMs;\n\t\tlet last: DokployDeployment | undefined;\n\n\t\twhile (true) {\n\t\t\tsignal?.throwIfAborted();\n\t\t\tconst deployment = (await this.listDeployments(applicationId)).find(\n\t\t\t\t(d) => Date.parse(d.createdAt) > after,\n\t\t\t);\n\t\t\tlast = deployment ?? last;\n\n\t\t\tif (deployment) {\n\t\t\t\tif (\n\t\t\t\t\tdeployment.status === 'error' ||\n\t\t\t\t\tdeployment.status === 'cancelled'\n\t\t\t\t) {\n\t\t\t\t\tthrow new DeploymentFailed(applicationId, deployment);\n\t\t\t\t}\n\t\t\t\tif (deployment.status === 'done') {\n\t\t\t\t\tconst { applicationStatus } =\n\t\t\t\t\t\tawait this.get<DokployApplicationStatus>(\n\t\t\t\t\t\t\t`application.one?applicationId=${encodeURIComponent(applicationId)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\tif (applicationStatus === 'error') {\n\t\t\t\t\t\tthrow new DeploymentFailed(applicationId, deployment);\n\t\t\t\t\t}\n\t\t\t\t\t// `running` is Dokploy still settling the service after the\n\t\t\t\t\t// deployment's own record closed; anything else is finished.\n\t\t\t\t\tif (applicationStatus !== 'running') return deployment;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst left = deadline - Date.now();\n\t\t\tif (left <= 0) {\n\t\t\t\tthrow new DeploymentTimedOut(applicationId, timeoutMs, last?.status);\n\t\t\t}\n\t\t\tawait pause(Math.min(interval, left), signal);\n\t\t\tinterval = Math.min(interval * 2, maxIntervalMs);\n\t\t}\n\t}\n\n\t/**\n\t * Delete an application\n\t */\n\tasync deleteApplication(applicationId: string): Promise<void> {\n\t\tawait this.post('application.remove', { applicationId });\n\t}\n\n\t// ============================================\n\t// Registry endpoints\n\t// ============================================\n\n\t/**\n\t * List all registries\n\t */\n\tasync listRegistries(): Promise<DokployRegistry[]> {\n\t\treturn this.get<DokployRegistry[]>('registry.all');\n\t}\n\n\t/**\n\t * Create a new registry\n\t */\n\tasync createRegistry(\n\t\tregistryName: string,\n\t\tregistryUrl: string,\n\t\tusername: string,\n\t\tpassword: string,\n\t\toptions?: {\n\t\t\timagePrefix?: string;\n\t\t},\n\t): Promise<DokployRegistry> {\n\t\treturn this.post<DokployRegistry>('registry.create', {\n\t\t\tregistryName,\n\t\t\tregistryUrl,\n\t\t\tusername,\n\t\t\tpassword,\n\t\t\timagePrefix: options?.imagePrefix,\n\t\t});\n\t}\n\n\t/**\n\t * Get a registry by ID\n\t */\n\tasync getRegistry(registryId: string): Promise<DokployRegistry> {\n\t\treturn this.get<DokployRegistry>(`registry.one?registryId=${registryId}`);\n\t}\n\n\t/**\n\t * Update a registry\n\t */\n\tasync updateRegistry(\n\t\tregistryId: string,\n\t\tupdates: Partial<{\n\t\t\tregistryName: string;\n\t\t\tregistryUrl: string;\n\t\t\tusername: string;\n\t\t\tpassword: string;\n\t\t\timagePrefix: string;\n\t\t}>,\n\t): Promise<void> {\n\t\tawait this.post('registry.update', { registryId, ...updates });\n\t}\n\n\t/**\n\t * Delete a registry\n\t */\n\tasync deleteRegistry(registryId: string): Promise<void> {\n\t\tawait this.post('registry.remove', { registryId });\n\t}\n\n\t// ============================================\n\t// Postgres endpoints\n\t// ============================================\n\n\t/**\n\t * List all Postgres databases in a project\n\t */\n\tasync listPostgres(projectId: string): Promise<DokployPostgres[]> {\n\t\treturn this.listInProject(projectId, (env) => env.postgres);\n\t}\n\n\t/**\n\t * Find a Postgres database by name in a project\n\t */\n\tasync findPostgresByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t): Promise<DokployPostgres | undefined> {\n\t\tconst databases = await this.listPostgres(projectId);\n\t\tconst normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\t\tconst match = databases.find(\n\t\t\t(db) => db.name === name || db.appName === normalizedName,\n\t\t);\n\n\t\tif (!match) return undefined;\n\n\t\t// Hydrated, because the listing is a *summary*: `project.one` nests these\n\t\t// with `postgresId`, `name`, `appName` and status, and none of\n\t\t// `databaseUser`, `databasePassword` or `databaseName`.\n\t\t//\n\t\t// Returning the summary meant a *found* cluster came back with undefined\n\t\t// credentials — so the URL built from it named database `undefined`, and\n\t\t// the DDL connection authenticated with nothing and retried thirty times.\n\t\t// A created one was fine, which is why this only appeared on the second\n\t\t// deploy.\n\t\treturn this.getPostgres(match.postgresId);\n\t}\n\n\t/**\n\t * Create a new Postgres database\n\t */\n\tasync createPostgres(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tappName?: string;\n\t\t\tdatabaseName?: string;\n\t\t\tdatabaseUser?: string;\n\t\t\tdatabasePassword?: string;\n\t\t\tdockerImage?: string;\n\t\t\tdescription?: string;\n\t\t},\n\t): Promise<DokployPostgres> {\n\t\treturn this.post<DokployPostgres>('postgres.create', {\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\tappName:\n\t\t\t\toptions?.appName ?? name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),\n\t\t\tdatabaseName: options?.databaseName,\n\t\t\tdatabaseUser: options?.databaseUser ?? 'postgres',\n\t\t\tdatabasePassword: options?.databasePassword,\n\t\t\tdockerImage: options?.dockerImage ?? 'postgres:18',\n\t\t\tdescription: options?.description ?? `Postgres database for ${name}`,\n\t\t});\n\t}\n\n\t/**\n\t * Find or create a Postgres database by name\n\t */\n\tasync findOrCreatePostgres(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tdatabaseName?: string;\n\t\t\tdatabasePassword?: string;\n\t\t},\n\t): Promise<{ postgres: DokployPostgres; created: boolean }> {\n\t\tconst existing = await this.findPostgresByName(projectId, name);\n\t\tif (existing) {\n\t\t\treturn { postgres: existing, created: false };\n\t\t}\n\t\tconst postgres = await this.createPostgres(\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\toptions,\n\t\t);\n\t\treturn { postgres, created: true };\n\t}\n\n\t/**\n\t * Get a Postgres database by ID\n\t */\n\tasync getPostgres(postgresId: string): Promise<DokployPostgres> {\n\t\treturn this.get<DokployPostgres>(`postgres.one?postgresId=${postgresId}`);\n\t}\n\n\t/**\n\t * Deploy a Postgres database\n\t */\n\tasync deployPostgres(postgresId: string): Promise<void> {\n\t\tawait this.post('postgres.deploy', { postgresId });\n\t}\n\n\t/**\n\t * Save environment variables for Postgres\n\t */\n\tasync savePostgresEnv(postgresId: string, env: string): Promise<void> {\n\t\tawait this.post('postgres.saveEnvironment', { postgresId, env });\n\t}\n\n\t/**\n\t * Set external port for Postgres (for external access)\n\t */\n\tasync savePostgresExternalPort(\n\t\tpostgresId: string,\n\t\texternalPort: number | null,\n\t): Promise<void> {\n\t\tawait this.post('postgres.saveExternalPort', { postgresId, externalPort });\n\t}\n\n\t/**\n\t * Update Postgres configuration\n\t */\n\tasync updatePostgres(\n\t\tpostgresId: string,\n\t\tupdates: Partial<DokployPostgresUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('postgres.update', { postgresId, ...updates });\n\t}\n\n\t/**\n\t * Delete a Postgres database\n\t */\n\tasync deletePostgres(postgresId: string): Promise<void> {\n\t\tawait this.post('postgres.remove', { postgresId });\n\t}\n\n\t// ============================================\n\t// Compose endpoints\n\t// ============================================\n\n\t/**\n\t * The compose stacks in a project.\n\t *\n\t * Dokploy has first-class primitives for Postgres and Redis and none for\n\t * MinIO, so an object store is a compose stack — the one kind whose contents\n\t * this target writes rather than configures.\n\t */\n\tasync listCompose(projectId: string): Promise<DokployCompose[]> {\n\t\treturn this.listInProject(projectId, (env) => env.compose);\n\t}\n\n\t/** A compose stack by name, hydrated — `project.one` returns summaries. */\n\tasync findComposeByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t): Promise<DokployCompose | undefined> {\n\t\tconst summary = (await this.listCompose(projectId)).find(\n\t\t\t(compose) => compose.name === name,\n\t\t);\n\n\t\treturn summary ? this.getCompose(summary.composeId) : undefined;\n\t}\n\n\tasync getCompose(composeId: string): Promise<DokployCompose> {\n\t\treturn this.get<DokployCompose>(`compose.one?composeId=${composeId}`);\n\t}\n\n\t/**\n\t * A compose stack carrying the file this target wrote.\n\t *\n\t * Created empty and then updated, because `compose.create` takes no file:\n\t * it defaults to `sourceType: 'github'` and expects a repository. `raw` is\n\t * what makes the file ours.\n\t */\n\tasync findOrCreateCompose(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\tcomposeFile: string,\n\t): Promise<{ compose: DokployCompose; created: boolean }> {\n\t\tconst existing = await this.findComposeByName(projectId, name);\n\n\t\tconst compose =\n\t\t\texisting ??\n\t\t\t(await this.post<DokployCompose>('compose.create', {\n\t\t\t\tname,\n\t\t\t\tenvironmentId,\n\t\t\t\tcomposeType: 'docker-compose',\n\t\t\t}));\n\n\t\t// Written every time, not only on create: the file is derived from the\n\t\t// manifest, so a redeploy after a declaration changed has to carry the\n\t\t// change. Dokploy redeploys only what differs.\n\t\tawait this.post('compose.update', {\n\t\t\tcomposeId: compose.composeId,\n\t\t\tsourceType: 'raw',\n\t\t\tcomposeFile,\n\t\t});\n\n\t\treturn { compose, created: !existing };\n\t}\n\n\tasync deployCompose(composeId: string): Promise<void> {\n\t\tawait this.post('compose.deploy', { composeId });\n\t}\n\n\tasync deleteCompose(composeId: string): Promise<void> {\n\t\tawait this.post('compose.delete', { composeId, deleteVolumes: false });\n\t}\n\n\t// ============================================\n\t// Redis endpoints\n\t// ============================================\n\n\t/**\n\t * List all Redis instances in a project\n\t */\n\tasync listRedis(projectId: string): Promise<DokployRedis[]> {\n\t\treturn this.listInProject(projectId, (env) => env.redis);\n\t}\n\n\t/**\n\t * Find a Redis instance by name in a project\n\t */\n\tasync findRedisByName(\n\t\tprojectId: string,\n\t\tname: string,\n\t): Promise<DokployRedis | undefined> {\n\t\tconst instances = await this.listRedis(projectId);\n\t\tconst normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\t\tconst match = instances.find(\n\t\t\t(redis) => redis.name === name || redis.appName === normalizedName,\n\t\t);\n\n\t\t// Hydrated for the same reason the Postgres lookup is: the nested listing\n\t\t// carries no credentials, so a found instance would resolve a URL with an\n\t\t// undefined password in it.\n\t\treturn match ? this.getRedis(match.redisId) : undefined;\n\t}\n\n\t/**\n\t * Create a new Redis instance\n\t */\n\tasync createRedis(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tappName?: string;\n\t\t\tdatabasePassword?: string;\n\t\t\tdockerImage?: string;\n\t\t\tdescription?: string;\n\t\t},\n\t): Promise<DokployRedis> {\n\t\treturn this.post<DokployRedis>('redis.create', {\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\tappName:\n\t\t\t\toptions?.appName ?? name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),\n\t\t\tdatabasePassword: options?.databasePassword,\n\t\t\tdockerImage: options?.dockerImage ?? 'redis:8',\n\t\t\tdescription: options?.description ?? `Redis instance for ${name}`,\n\t\t});\n\t}\n\n\t/**\n\t * Find or create a Redis instance by name\n\t */\n\tasync findOrCreateRedis(\n\t\tname: string,\n\t\tprojectId: string,\n\t\tenvironmentId: string,\n\t\toptions?: {\n\t\t\tdatabasePassword?: string;\n\t\t},\n\t): Promise<{ redis: DokployRedis; created: boolean }> {\n\t\tconst existing = await this.findRedisByName(projectId, name);\n\t\tif (existing) {\n\t\t\treturn { redis: existing, created: false };\n\t\t}\n\t\tconst redis = await this.createRedis(\n\t\t\tname,\n\t\t\tprojectId,\n\t\t\tenvironmentId,\n\t\t\toptions,\n\t\t);\n\t\treturn { redis, created: true };\n\t}\n\n\t/**\n\t * Get a Redis instance by ID\n\t */\n\tasync getRedis(redisId: string): Promise<DokployRedis> {\n\t\treturn this.get<DokployRedis>(`redis.one?redisId=${redisId}`);\n\t}\n\n\t/**\n\t * Deploy a Redis instance\n\t */\n\tasync deployRedis(redisId: string): Promise<void> {\n\t\tawait this.post('redis.deploy', { redisId });\n\t}\n\n\t/**\n\t * Save environment variables for Redis\n\t */\n\tasync saveRedisEnv(redisId: string, env: string): Promise<void> {\n\t\tawait this.post('redis.saveEnvironment', { redisId, env });\n\t}\n\n\t/**\n\t * Set external port for Redis (for external access)\n\t */\n\tasync saveRedisExternalPort(\n\t\tredisId: string,\n\t\texternalPort: number | null,\n\t): Promise<void> {\n\t\tawait this.post('redis.saveExternalPort', { redisId, externalPort });\n\t}\n\n\t/**\n\t * Update Redis configuration\n\t */\n\tasync updateRedis(\n\t\tredisId: string,\n\t\tupdates: Partial<DokployRedisUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('redis.update', { redisId, ...updates });\n\t}\n\n\t/**\n\t * Delete a Redis instance\n\t */\n\tasync deleteRedis(redisId: string): Promise<void> {\n\t\tawait this.post('redis.remove', { redisId });\n\t}\n\n\t// ============================================\n\t// Domain endpoints\n\t// ============================================\n\n\t/**\n\t * Create a new domain for an application\n\t */\n\tasync createDomain(options: DokployDomainCreate): Promise<DokployDomain> {\n\t\treturn this.post<DokployDomain>(\n\t\t\t'domain.create',\n\t\t\toptions as unknown as Record<string, unknown>,\n\t\t);\n\t}\n\n\t/**\n\t * Update an existing domain\n\t */\n\tasync updateDomain(\n\t\tdomainId: string,\n\t\tupdates: Partial<DokployDomainCreate>,\n\t): Promise<void> {\n\t\tawait this.post('domain.update', { domainId, ...updates });\n\t}\n\n\t/**\n\t * Delete a domain\n\t */\n\tasync deleteDomain(domainId: string): Promise<void> {\n\t\tawait this.post('domain.delete', { domainId });\n\t}\n\n\t/**\n\t * Get a domain by ID\n\t */\n\tasync getDomain(domainId: string): Promise<DokployDomain> {\n\t\treturn this.get<DokployDomain>(`domain.one?domainId=${domainId}`);\n\t}\n\n\t/**\n\t * Get all domains for an application\n\t */\n\tasync getDomainsByApplicationId(\n\t\tapplicationId: string,\n\t): Promise<DokployDomain[]> {\n\t\treturn this.get<DokployDomain[]>(\n\t\t\t`domain.byApplicationId?applicationId=${applicationId}`,\n\t\t);\n\t}\n\n\t/**\n\t * Validate a domain and trigger SSL certificate generation\n\t *\n\t * This should be called after DNS records are created and propagated.\n\t * It triggers Let's Encrypt certificate generation for HTTPS domains.\n\t *\n\t * @param domain - The domain hostname to validate (e.g., 'api.example.com')\n\t */\n\tasync validateDomain(\n\t\tdomain: string,\n\t): Promise<{ isValid: boolean; resolvedIp: string }> {\n\t\treturn this.post<{ isValid: boolean; resolvedIp: string }>(\n\t\t\t'domain.validateDomain',\n\t\t\t{ domain },\n\t\t);\n\t}\n\n\t/**\n\t * Auto-generate a domain name for an application\n\t */\n\tasync generateDomain(\n\t\tappName: string,\n\t\tserverId?: string,\n\t): Promise<{ domain: string }> {\n\t\treturn this.post<{ domain: string }>('domain.generateDomain', {\n\t\t\tappName,\n\t\t\tserverId,\n\t\t});\n\t}\n\n\t// ============================================\n\t// Destination endpoints (backup storage)\n\t// ============================================\n\n\t/**\n\t * List all backup destinations\n\t */\n\tasync listDestinations(): Promise<DokployDestination[]> {\n\t\treturn this.get<DokployDestination[]>('destination.all');\n\t}\n\n\t/**\n\t * Get a destination by ID\n\t */\n\tasync getDestination(destinationId: string): Promise<DokployDestination> {\n\t\treturn this.get<DokployDestination>(\n\t\t\t`destination.one?destinationId=${destinationId}`,\n\t\t);\n\t}\n\n\t/**\n\t * Create a new S3 backup destination\n\t */\n\tasync createDestination(\n\t\toptions: DokployDestinationCreate,\n\t): Promise<DokployDestination> {\n\t\treturn this.post<DokployDestination>('destination.create', { ...options });\n\t}\n\n\t/**\n\t * Find a destination by name\n\t */\n\tasync findDestinationByName(\n\t\tname: string,\n\t): Promise<DokployDestination | undefined> {\n\t\tconst destinations = await this.listDestinations();\n\t\treturn destinations.find((d) => d.name === name);\n\t}\n\n\t/**\n\t * Find or create a destination by name\n\t */\n\tasync findOrCreateDestination(\n\t\tname: string,\n\t\toptions: Omit<DokployDestinationCreate, 'name'>,\n\t): Promise<{ destination: DokployDestination; created: boolean }> {\n\t\tconst existing = await this.findDestinationByName(name);\n\t\tif (existing) {\n\t\t\treturn { destination: existing, created: false };\n\t\t}\n\t\tconst destination = await this.createDestination({ name, ...options });\n\t\treturn { destination, created: true };\n\t}\n\n\t/**\n\t * Update a destination\n\t */\n\tasync updateDestination(\n\t\tdestinationId: string,\n\t\tupdates: Partial<DokployDestinationCreate>,\n\t): Promise<void> {\n\t\tawait this.post('destination.update', { destinationId, ...updates });\n\t}\n\n\t/**\n\t * Delete a destination\n\t */\n\tasync deleteDestination(destinationId: string): Promise<void> {\n\t\tawait this.post('destination.remove', { destinationId });\n\t}\n\n\t/**\n\t * Test connection to a destination\n\t */\n\tasync testDestinationConnection(\n\t\tdestinationId: string,\n\t): Promise<{ success: boolean }> {\n\t\treturn this.post<{ success: boolean }>('destination.testConnection', {\n\t\t\tdestinationId,\n\t\t});\n\t}\n\n\t// ============================================\n\t// Backup endpoints (scheduled backups)\n\t// ============================================\n\n\t/**\n\t * Create a backup schedule for postgres\n\t */\n\tasync createPostgresBackup(\n\t\toptions: DokployBackupCreate,\n\t): Promise<DokployBackup> {\n\t\treturn this.post<DokployBackup>('backup.create', {\n\t\t\t...options,\n\t\t\tdatabaseType: 'postgres',\n\t\t});\n\t}\n\n\t/**\n\t * List backups for a postgres database\n\t */\n\tasync listPostgresBackups(postgresId: string): Promise<DokployBackup[]> {\n\t\treturn this.get<DokployBackup[]>(\n\t\t\t`backup.all?postgresId=${postgresId}&databaseType=postgres`,\n\t\t);\n\t}\n\n\t/**\n\t * Get a backup by ID\n\t */\n\tasync getBackup(backupId: string): Promise<DokployBackup> {\n\t\treturn this.get<DokployBackup>(`backup.one?backupId=${backupId}`);\n\t}\n\n\t/**\n\t * Update a backup schedule\n\t */\n\tasync updateBackup(\n\t\tbackupId: string,\n\t\tupdates: Partial<DokployBackupUpdate>,\n\t): Promise<void> {\n\t\tawait this.post('backup.update', { backupId, ...updates });\n\t}\n\n\t/**\n\t * Delete a backup schedule\n\t */\n\tasync deleteBackup(backupId: string): Promise<void> {\n\t\tawait this.post('backup.remove', { backupId });\n\t}\n\n\t/**\n\t * Trigger a manual backup run\n\t */\n\tasync runBackupManually(backupId: string): Promise<void> {\n\t\tawait this.post('backup.manualBackup', { backupId });\n\t}\n}\n\n// ============================================\n// Type definitions for Dokploy API responses\n// ============================================\n\nexport interface DokployProject {\n\tprojectId: string;\n\tname: string;\n\tdescription: string | null;\n\tcreatedAt?: string;\n\tadminId?: string;\n}\n\nexport interface DokployEnvironment {\n\tenvironmentId: string;\n\tname: string;\n\tdescription: string | null;\n\t/**\n\t * What lives in this environment.\n\t *\n\t * `project.one` is the only endpoint that answers this. The per-kind\n\t * `*.all?projectId=` endpoints return 404 on current Dokploy — resources\n\t * moved under environments — which is why they are no longer asked.\n\t */\n\tapplications?: DokployApplication[];\n\tpostgres?: DokployPostgres[];\n\tcompose?: DokployCompose[];\n\tredis?: DokployRedis[];\n}\n\nexport interface DokployProjectDetails extends DokployProject {\n\tenvironments: DokployEnvironment[];\n}\n\nexport interface DokployApplication {\n\tapplicationId: string;\n\tname: string;\n\tappName: string;\n\tprojectId: string;\n\tenvironmentId?: string;\n}\n\n/** Where an application is, as `application.one` reports it. */\nexport interface DokployApplicationStatus {\n\tapplicationStatus: 'idle' | 'running' | 'done' | 'error';\n}\n\nexport type DokployDeploymentStatus =\n\t| 'running'\n\t| 'done'\n\t| 'error'\n\t| 'cancelled';\n\n/** One run of `application.deploy`, as `deployment.all` lists it. */\nexport interface DokployDeployment {\n\tdeploymentId: string;\n\tstatus: DokployDeploymentStatus;\n\t/** ISO timestamp, by Dokploy's clock. */\n\tcreatedAt: string;\n\ttitle?: string;\n\terrorMessage?: string | null;\n}\n\nexport interface DokployApplicationUpdate {\n\tregistryId: string;\n\tdockerImage: string;\n\tsourceType: 'docker';\n}\n\nexport interface DokployRegistry {\n\tregistryId: string;\n\tregistryName: string;\n\tregistryUrl: string;\n\tusername: string;\n\timagePrefix: string | null;\n}\n\nexport interface DokployPostgres {\n\tpostgresId: string;\n\tname: string;\n\tappName: string;\n\tdatabaseName: string;\n\tdatabaseUser: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string | null;\n\tprojectId: string;\n\tenvironmentId: string;\n\tapplicationStatus: 'idle' | 'running' | 'done' | 'error';\n\texternalPort: number | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployPostgresUpdate {\n\tname: string;\n\tappName: string;\n\tdatabaseName: string;\n\tdatabaseUser: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string;\n}\n\nexport interface DokployRedis {\n\tredisId: string;\n\tname: string;\n\tappName: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string | null;\n\tprojectId: string;\n\tenvironmentId: string;\n\tapplicationStatus: 'idle' | 'running' | 'done' | 'error';\n\texternalPort: number | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployRedisUpdate {\n\tname: string;\n\tappName: string;\n\tdatabasePassword: string;\n\tdockerImage: string;\n\tdescription: string;\n}\n\nexport type DokployCertificateType = 'letsencrypt' | 'none' | 'custom';\n/** A Dokploy compose stack. */\nexport interface DokployCompose {\n\tcomposeId: string;\n\tname: string;\n\t/** Dokploy's own generated name, which prefixes the stack's containers. */\n\tappName: string;\n\tcomposeFile?: string;\n\tcomposeStatus?: string;\n}\n\nexport type DokployDomainType = 'application' | 'compose' | 'preview';\n\nexport interface DokployDomainCreate {\n\t/** Domain hostname (e.g., 'api.example.com') */\n\thost: string;\n\t/** URL path (optional, e.g., '/api') */\n\tpath?: string | null;\n\t/** Container port to route to (1-65535) */\n\tport?: number | null;\n\t/** Enable HTTPS */\n\thttps?: boolean;\n\t/** Associated application ID */\n\tapplicationId?: string | null;\n\t/** Certificate type for HTTPS */\n\tcertificateType?: DokployCertificateType;\n\t/** Custom certificate resolver name */\n\tcustomCertResolver?: string | null;\n\t/** Docker Compose service ID */\n\tcomposeId?: string | null;\n\t/** Service name for compose */\n\tserviceName?: string | null;\n\t/** Domain type */\n\tdomainType?: DokployDomainType | null;\n\t/** Preview deployment ID */\n\tpreviewDeploymentId?: string | null;\n\t/** Internal routing path */\n\tinternalPath?: string | null;\n\t/** Strip path from forwarded requests */\n\tstripPath?: boolean;\n}\n\nexport interface DokployDomain extends DokployDomainCreate {\n\tdomainId: string;\n\tcreatedAt?: string;\n}\n\n// ============================================\n// Destination types (backup storage)\n// ============================================\n\nexport interface DokployDestination {\n\tdestinationId: string;\n\tname: string;\n\taccessKey: string;\n\tbucket: string;\n\tregion: string;\n\tendpoint: string | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployDestinationCreate {\n\t/** User-friendly name for the destination */\n\tname: string;\n\t/** S3 access key ID */\n\taccessKey: string;\n\t/** S3 secret access key */\n\tsecretAccessKey: string;\n\t/** S3 bucket name */\n\tbucket: string;\n\t/** AWS region (e.g., 'us-east-1') */\n\tregion: string;\n\t/** Optional endpoint for S3-compatible services */\n\tendpoint?: string;\n}\n\n// ============================================\n// Backup types (scheduled backups)\n// ============================================\n\nexport type DokployDatabaseType = 'postgres' | 'mysql' | 'mariadb' | 'mongo';\n\nexport interface DokployBackup {\n\tbackupId: string;\n\tschedule: string;\n\tprefix: string;\n\tenabled: boolean;\n\tdestinationId: string;\n\tpostgresId?: string;\n\tdatabaseType: DokployDatabaseType;\n\tkeepLatestCount: number | null;\n\tcreatedAt?: string;\n}\n\nexport interface DokployBackupCreate {\n\t/** Cron schedule (e.g., '0 2 * * *' for 2 AM daily) */\n\tschedule: string;\n\t/** Backup file prefix (e.g., 'production/postgres') */\n\tprefix: string;\n\t/** Destination ID for backup storage */\n\tdestinationId: string;\n\t/** Database name to backup */\n\tdatabase: string;\n\t/** Postgres instance ID */\n\tpostgresId: string;\n\t/** Enable/disable backup (default: true) */\n\tenabled?: boolean;\n\t/** Number of backups to keep (default: keep all) */\n\tkeepLatestCount?: number;\n}\n\nexport interface DokployBackupUpdate {\n\tschedule?: string;\n\tprefix?: string;\n\tenabled?: boolean;\n\tkeepLatestCount?: number;\n}\n\n/**\n * Create a Dokploy API client from stored credentials or environment\n */\nexport async function createDokployApi(\n\tendpoint?: string,\n): Promise<DokployApi | null> {\n\tconst { getDokployCredentials } = await import('../../auth/credentials');\n\n\t// Try environment variable first\n\tconst envToken = process.env.DOKPLOY_API_TOKEN;\n\tconst envEndpoint = endpoint || process.env.DOKPLOY_ENDPOINT;\n\n\tif (envToken && envEndpoint) {\n\t\treturn new DokployApi({ baseUrl: envEndpoint, token: envToken });\n\t}\n\n\t// Fall back to stored credentials\n\tconst creds = await getDokployCredentials();\n\tif (creds) {\n\t\treturn new DokployApi({\n\t\t\tbaseUrl: endpoint || creds.endpoint,\n\t\t\ttoken: creds.token,\n\t\t});\n\t}\n\n\treturn null;\n}\n","import { join } from 'node:path';\nimport type { Routes } from '../types.js';\nimport type { NormalizedWorkspace } from './types.js';\n\n/**\n * Normalize routes to an array of patterns.\n * Handles a glob or a list of them.\n * @internal Exported for use in dev command\n */\nexport function normalizeRoutes(routes: Routes | undefined): string[] {\n\tif (!routes) return [];\n\treturn Array.isArray(routes) ? routes : [routes];\n}\n\n/**\n * Get the first routes pattern as a string (for simple cases).\n * @internal Exported for use in dev command\n */\nexport function getFirstRoute(\n\troutes: string | string[] | undefined,\n): string | null {\n\tconst normalized = normalizeRoutes(routes);\n\treturn normalized[0] || null;\n}\n\n/**\n * Check if a file path matches endpoint patterns that could affect OpenAPI schema.\n * Returns true for changes that should trigger client regeneration.\n */\nexport function shouldRegenerateClient(\n\tfilePath: string,\n\troutesPattern: string,\n): boolean {\n\t// Normalize path separators\n\tconst normalizedPath = filePath.replace(/\\\\/g, '/');\n\tconst normalizedPattern = routesPattern.replace(/\\\\/g, '/');\n\n\t// Check if the file matches the routes pattern\n\t// This is a simple check - the file should be within the routes directory\n\tconst patternDir = normalizedPattern.split('*')[0] || '';\n\n\tif (!normalizedPath.includes(patternDir.replace('./', ''))) {\n\t\treturn false;\n\t}\n\n\t// Check file extension - only TypeScript endpoint files\n\tif (!normalizedPath.endsWith('.ts') && !normalizedPath.endsWith('.tsx')) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/**\n * Get backend apps that a web/mobile client depends on.\n */\nexport function getBackendDependencies(\n\tworkspace: NormalizedWorkspace,\n\tclientAppName: string,\n): string[] {\n\tconst clientApp = workspace.apps[clientAppName];\n\tif (!clientApp || (clientApp.type !== 'web' && clientApp.type !== 'mobile')) {\n\t\treturn [];\n\t}\n\n\treturn clientApp.dependencies.filter((dep) => {\n\t\tconst depApp = workspace.apps[dep];\n\t\treturn depApp?.type === 'backend' && depApp.routes;\n\t});\n}\n\n/**\n * Get web/mobile apps that depend on a backend app.\n */\nexport function getDependentFrontends(\n\tworkspace: NormalizedWorkspace,\n\tbackendAppName: string,\n): string[] {\n\tconst dependentApps: string[] = [];\n\n\tfor (const [appName, app] of Object.entries(workspace.apps)) {\n\t\tif (\n\t\t\t(app.type === 'web' || app.type === 'mobile') &&\n\t\t\tapp.dependencies.includes(backendAppName)\n\t\t) {\n\t\t\tdependentApps.push(appName);\n\t\t}\n\t}\n\n\treturn dependentApps;\n}\n\n/**\n * Get the path to a backend's OpenAPI spec file.\n */\nexport function getBackendOpenApiPath(\n\tworkspace: NormalizedWorkspace,\n\tbackendAppName: string,\n): string | null {\n\tconst app = workspace.apps[backendAppName];\n\tif (!app || app.type !== 'backend') {\n\t\treturn null;\n\t}\n\n\treturn join(workspace.root, app.path, '.gkm', 'openapi.ts');\n}\n","import { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { SniffResult } from '@geekmidas/envkit/sniffer';\nimport { output } from '../output.js';\nimport { LocalSandbox } from '../sandbox/local.js';\nimport { activeSandbox, type Sandbox } from '../sandbox/sandbox.js';\nimport { nodeWithTsx } from '../sandbox/worker.js';\nimport { normalizeRoutes } from '../workspace/client-generator.js';\nimport { getPublicEnvPrefix } from '../workspace/publicEnv.js';\nimport type { NormalizedAppConfig } from '../workspace/types.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * How long one sniff may run. Importing an entry, a config or a module of\n * routes takes a second or two; one that is still going after this is\n * waiting on something — a server it started, a connection, a prompt — and\n * would otherwise hold the deploy, and its lock, until someone noticed.\n */\nexport const SNIFF_TIMEOUT_MS = 30_000;\n\n/**\n * Resolve the path to a sniffer helper file.\n * Handles both dev (.ts with tsx) and production (.mjs from dist).\n *\n * In production: sniffer.ts is bundled into dist/index.mjs, but sniffer helper\n * files are output to dist/deploy/ as standalone modules for subprocess loading.\n *\n * In development: All files are in src/deploy/ and loaded via tsx.\n */\nfunction resolveSnifferFile(baseName: string): string {\n\t// Try deploy/ subdirectory first (production: bundled code is at dist/index.mjs,\n\t// but sniffer files are at dist/deploy/)\n\tconst deployMjsPath = resolve(__dirname, 'deploy', `${baseName}.mjs`);\n\tif (existsSync(deployMjsPath)) {\n\t\treturn deployMjsPath;\n\t}\n\n\t// Try same directory .mjs (production: if running from dist/deploy/ directly)\n\tconst mjsPath = resolve(__dirname, `${baseName}.mjs`);\n\tif (existsSync(mjsPath)) {\n\t\treturn mjsPath;\n\t}\n\n\t// Try same directory .ts (development with tsx: all files in src/deploy/)\n\tconst tsPath = resolve(__dirname, `${baseName}.ts`);\n\tif (existsSync(tsPath)) {\n\t\treturn tsPath;\n\t}\n\n\t// Fallback to .ts (will error if neither exists)\n\treturn tsPath;\n}\n\n// Re-export SniffResult for consumers\nexport type { SniffResult } from '@geekmidas/envkit/sniffer';\n\n/**\n * Result of sniffing an app's environment requirements.\n */\nexport interface SniffedEnvironment {\n\tappName: string;\n\t/**\n\t * All required environment variable names.\n\t * When `SniffAppOptions.markOptional` is true, optional variables are\n\t * suffixed with `?` (e.g. `PORT?`).\n\t */\n\trequiredEnvVars: string[];\n\t/**\n\t * Subset of variables that are optional (accessed via `.optional()` or\n\t * `.default()`). Always populated regardless of `markOptional`.\n\t */\n\toptionalEnvVars: string[];\n}\n\n/**\n * Options for sniffing an app's environment.\n */\nexport interface SniffAppOptions {\n\t/** Whether to log warnings for errors encountered during sniffing. Defaults to true. */\n\tlogWarnings?: boolean;\n\t/**\n\t * When true, optional variables (those with `.optional()` or `.default()`)\n\t * are suffixed with `?` in `requiredEnvVars` (e.g. `PORT?`).\n\t * Defaults to false.\n\t */\n\tmarkOptional?: boolean;\n\t/**\n\t * Where the app's code runs while it is sniffed. Defaults to the run's\n\t * own (`withSandbox`), else a {@link LocalSandbox} on the workspace —\n\t * either way never this process, and never with its environment.\n\t */\n\tsandbox?: Sandbox;\n\t/** How long each sniff may run. Defaults to {@link SNIFF_TIMEOUT_MS}. */\n\ttimeoutMs?: number;\n}\n\n/** Where a sniff runs, and for how long. */\ninterface SniffRun {\n\tsandbox?: Sandbox;\n\ttimeoutMs?: number;\n}\n\n/**\n * Run a sniffer worker in the sandbox, with the sandbox's environment and\n * nothing of the deploy's: the deploy's Dokploy token, registry login and AWS\n * keys stay where they are. A worker that outlives the timeout is killed, and\n * comes back as the error the caller reports.\n */\nasync function runSniffer(\n\trun: SniffRun,\n\tworkspacePath: string,\n\tcwd: string,\n\tnodeArgs: string[],\n): Promise<\n\t{ stdout: string; stderr: string; code: number | null } | { error: Error }\n> {\n\tconst sandbox =\n\t\trun.sandbox ??\n\t\tactiveSandbox() ??\n\t\tnew LocalSandbox({ root: resolve(workspacePath) });\n\n\ttry {\n\t\tconst result = await sandbox.exec('node', nodeArgs, {\n\t\t\tcwd,\n\t\t\tenv: { ...sandbox.env },\n\t\t\ttimeoutMs: run.timeoutMs ?? SNIFF_TIMEOUT_MS,\n\t\t\toutput: 'capture',\n\t\t});\n\t\treturn {\n\t\t\tstdout: result.stdout,\n\t\t\tstderr: result.stderr,\n\t\t\tcode: result.exitCode,\n\t\t};\n\t} catch (error) {\n\t\treturn {\n\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t};\n\t}\n}\n\n/** The worker's JSON answer: the last object on stdout naming `envVars`. */\nfunction snifferAnswer(stdout: string):\n\t| {\n\t\t\tenvVars?: string[];\n\t\t\toptionalEnvVars?: string[];\n\t\t\tunhandledRejections?: string[];\n\t\t\twarnings?: string[];\n\t\t\terror?: string | null;\n\t }\n\t| undefined {\n\ttry {\n\t\tconst jsonMatch = stdout.match(/\\{[^{}]*\"envVars\"[^{}]*\\}[^{]*$/);\n\t\treturn jsonMatch ? JSON.parse(jsonMatch[0]) : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Get required environment variables for an app.\n *\n * Detection strategy (in order):\n * 1. Frontend apps: Returns empty (no server secrets)\n * 2. Apps with `requiredEnv`: Uses explicit list from config\n * 3. Entry apps: Imports entry file in subprocess to capture config.parse() calls\n * 4. Route-based apps: Loads route files and calls getEnvironment() on each construct\n * 5. Apps with `envParser` (no routes): Runs SnifferEnvironmentParser to detect usage\n * 6. Apps with neither: Returns empty\n *\n * This function handles \"fire and forget\" async operations gracefully,\n * capturing errors and unhandled rejections without failing the build.\n *\n * @param app - The normalized app configuration\n * @param appName - The name of the app\n * @param workspacePath - Absolute path to the workspace root\n * @param options - Optional configuration for sniffing behavior\n * @returns The sniffed environment with required variables\n */\nexport async function sniffAppEnvironment(\n\tapp: NormalizedAppConfig,\n\tappName: string,\n\tworkspacePath: string,\n\toptions: SniffAppOptions = {},\n): Promise<SniffedEnvironment> {\n\tconst { logWarnings = true, markOptional = false } = options;\n\tconst run: SniffRun = {\n\t\t...(options.sandbox ? { sandbox: options.sandbox } : {}),\n\t\t...(options.timeoutMs ? { timeoutMs: options.timeoutMs } : {}),\n\t};\n\n\t// 1. Frontend apps - handle dependencies and config sniffing\n\tif (app.type === 'web' || app.type === 'mobile') {\n\t\t// Auto-generate {prefix}{DEP}_URL from dependencies, where prefix matches\n\t\t// the framework's public-var convention (NEXT_PUBLIC_, VITE_, ...).\n\t\t// For frameworks without a prefix (e.g. Remix), no dep var is required.\n\t\tconst publicPrefix = getPublicEnvPrefix(app.framework);\n\t\tconst depVars = publicPrefix\n\t\t\t? (app.dependencies ?? []).map(\n\t\t\t\t\t(dep) => `${publicPrefix}${dep.toUpperCase()}_URL`,\n\t\t\t\t)\n\t\t\t: [];\n\n\t\t// If config specified, sniff by importing the file(s)\n\t\t// The file calls .parse() at module load, which triggers sniffer to capture vars\n\t\tif (app.config) {\n\t\t\tconst sniffedVars: string[] = [];\n\t\t\tconst sniffedOptional: string[] = [];\n\n\t\t\t// Collect config paths to sniff\n\t\t\tconst configPaths: string[] = [];\n\t\t\tif (app.config.client) configPaths.push(app.config.client);\n\t\t\tif (app.config.server) configPaths.push(app.config.server);\n\n\t\t\t// Sniff each config file\n\t\t\tfor (const configPath of configPaths) {\n\t\t\t\tconst result = await sniffEntryFile(\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tapp.path,\n\t\t\t\t\tworkspacePath,\n\t\t\t\t\trun,\n\t\t\t\t);\n\n\t\t\t\tif (logWarnings && result.error) {\n\t\t\t\t\toutput.warn(\n\t\t\t\t\t\t`[sniffer] ${appName}: Config file \"${configPath}\" threw error during sniffing (env vars still captured): ${result.error.message}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tsniffedVars.push(...result.envVars);\n\t\t\t\tsniffedOptional.push(...result.optionalEnvVars);\n\t\t\t}\n\n\t\t\tconst optionalEnvVars = [...new Set(sniffedOptional)];\n\t\t\t// Combine: dependency vars + sniffed vars (deduplicated)\n\t\t\tconst allVars = applyMarkOptional(\n\t\t\t\t[...new Set([...depVars, ...sniffedVars])],\n\t\t\t\toptionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t);\n\t\t\treturn { appName, requiredEnvVars: allVars, optionalEnvVars };\n\t\t}\n\n\t\treturn { appName, requiredEnvVars: depVars, optionalEnvVars: [] };\n\t}\n\n\t// 2. Entry apps - import entry file in subprocess to trigger config.parse()\n\tif (app.entry) {\n\t\tconst result = await sniffEntryFile(\n\t\t\tapp.entry,\n\t\t\tapp.path,\n\t\t\tworkspacePath,\n\t\t\trun,\n\t\t);\n\n\t\tif (logWarnings && result.error) {\n\t\t\toutput.warn(\n\t\t\t\t`[sniffer] ${appName}: Entry file threw error during sniffing (env vars still captured): ${result.error.message}`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tappName,\n\t\t\trequiredEnvVars: applyMarkOptional(\n\t\t\t\tresult.envVars,\n\t\t\t\tresult.optionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t),\n\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t};\n\t}\n\n\t// 4. Route-based apps - load routes and call getEnvironment() on each construct\n\tif (app.routes) {\n\t\tconst result = await sniffRouteFiles(\n\t\t\tnormalizeRoutes(app.routes),\n\t\t\tapp.path,\n\t\t\tworkspacePath,\n\t\t\trun,\n\t\t);\n\n\t\tif (logWarnings && result.error) {\n\t\t\toutput.warn(\n\t\t\t\t`[sniffer] ${appName}: Route sniffing threw error (env vars still captured): ${result.error.message}`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tappName,\n\t\t\trequiredEnvVars: applyMarkOptional(\n\t\t\t\tresult.envVars,\n\t\t\t\tresult.optionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t),\n\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t};\n\t}\n\n\t// 5. Apps with envParser but no routes - run sniffer to detect env var usage\n\tif (app.envParser) {\n\t\tconst result = await sniffEnvParser(\n\t\t\tapp.envParser,\n\t\t\tapp.path,\n\t\t\tworkspacePath,\n\t\t\trun,\n\t\t);\n\n\t\t// Log any issues for debugging\n\t\tif (logWarnings) {\n\t\t\tif (result.error) {\n\t\t\t\toutput.warn(\n\t\t\t\t\t`[sniffer] ${appName}: envParser threw error during sniffing (env vars still captured): ${result.error.message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (result.unhandledRejections.length > 0) {\n\t\t\t\toutput.warn(\n\t\t\t\t\t`[sniffer] ${appName}: Fire-and-forget rejections during sniffing (suppressed): ${result.unhandledRejections.map((e) => e.message).join(', ')}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tappName,\n\t\t\trequiredEnvVars: applyMarkOptional(\n\t\t\t\tresult.envVars,\n\t\t\t\tresult.optionalEnvVars,\n\t\t\t\tmarkOptional,\n\t\t\t),\n\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t};\n\t}\n\n\t// No env detection method available\n\treturn { appName, requiredEnvVars: [], optionalEnvVars: [] };\n}\n\n/**\n * Apply the `?` suffix to optional variables in an env var list.\n * Only modifies the list when `markOptional` is true and there are optional vars.\n */\nfunction applyMarkOptional(\n\tenvVars: string[],\n\toptionalEnvVars: string[],\n\tmarkOptional: boolean,\n): string[] {\n\tif (!markOptional || optionalEnvVars.length === 0) return envVars;\n\tconst optionalSet = new Set(optionalEnvVars);\n\treturn envVars.map((v) => (optionalSet.has(v) ? `${v}?` : v));\n}\n\n/**\n * Result from sniffing an entry file.\n */\ninterface EntrySniffResult {\n\tenvVars: string[];\n\toptionalEnvVars: string[];\n\terror?: Error;\n}\n\n/**\n * Sniff an entry file by importing it in a subprocess.\n *\n * Entry apps call `config.parse()` at module load time. To capture which\n * env vars are accessed, we:\n * 1. Spawn a subprocess with a module loader hook\n * 2. The loader intercepts `@geekmidas/envkit` and replaces EnvironmentParser\n * with SnifferEnvironmentParser\n * 3. Import the entry file (triggers config.parse())\n * 4. Capture and return the accessed env var names\n *\n * This approach provides process isolation - each app is sniffed in its own\n * subprocess, preventing module cache pollution.\n *\n * @param entryPath - Relative path to the entry file (e.g., './src/index.ts')\n * @param appPath - The app's path relative to workspace (e.g., 'apps/auth')\n * @param workspacePath - Absolute path to workspace root\n * @returns EntrySniffResult with env vars and optional error\n */\nasync function sniffEntryFile(\n\tentryPath: string,\n\tappPath: string,\n\tworkspacePath: string,\n\trun: SniffRun = {},\n): Promise<EntrySniffResult> {\n\tconst fullEntryPath = resolve(workspacePath, appPath, entryPath);\n\tconst loaderPath = resolveSnifferFile('sniffer-loader');\n\tconst workerPath = resolveSnifferFile('sniffer-worker');\n\n\t// tsx first — each `.tsx` compiled by its own tsconfig's JSX settings\n\t// rather than whatever the app's tsconfig includes — then the loader that\n\t// swaps in the sniffing EnvironmentParser.\n\tconst ran = await runSniffer(\n\t\trun,\n\t\tworkspacePath,\n\t\tresolve(workspacePath, appPath),\n\t\tnodeWithTsx(workerPath, [fullEntryPath], [loaderPath]),\n\t);\n\tif ('error' in ran)\n\t\treturn { envVars: [], optionalEnvVars: [], error: ran.error };\n\n\tconst answer = snifferAnswer(ran.stdout);\n\tif (answer) {\n\t\treturn {\n\t\t\tenvVars: answer.envVars || [],\n\t\t\toptionalEnvVars: answer.optionalEnvVars || [],\n\t\t\terror: answer.error ? new Error(answer.error) : undefined,\n\t\t};\n\t}\n\n\t// If we couldn't parse the output, return empty with error info\n\treturn {\n\t\tenvVars: [],\n\t\toptionalEnvVars: [],\n\t\terror: new Error(\n\t\t\t`Failed to sniff entry file (exit code ${ran.code}): ${ran.stderr || ran.stdout || 'No output'}`,\n\t\t),\n\t};\n}\n\n/**\n * Sniff route files by loading constructs and calling getEnvironment().\n *\n * Route-based apps have endpoints, functions, crons, and subscribers that\n * use services. Each service's register() method accesses environment variables.\n *\n * This runs in a subprocess with tsx loader to properly handle TypeScript\n * compilation and path alias resolution (e.g., `src/...` imports).\n *\n * @param routes - Glob pattern(s) for route files\n * @param appPath - The app's path relative to workspace (e.g., 'apps/api')\n * @param workspacePath - Absolute path to workspace root\n * @returns EntrySniffResult with env vars and optional error\n */\nasync function sniffRouteFiles(\n\troutes: string | string[],\n\tappPath: string,\n\tworkspacePath: string,\n\trun: SniffRun = {},\n): Promise<EntrySniffResult> {\n\tconst fullAppPath = resolve(workspacePath, appPath);\n\tconst workerPath = resolveSnifferFile('sniffer-routes-worker');\n\n\t// Convert array of patterns to first pattern (worker handles glob internally)\n\tconst routesArray = Array.isArray(routes) ? routes : [routes];\n\tconst pattern = routesArray[0];\n\tif (!pattern) {\n\t\treturn {\n\t\t\tenvVars: [],\n\t\t\toptionalEnvVars: [],\n\t\t\terror: new Error('No route patterns provided'),\n\t\t};\n\t}\n\n\tconst ran = await runSniffer(\n\t\trun,\n\t\tworkspacePath,\n\t\tfullAppPath,\n\t\tnodeWithTsx(workerPath, [fullAppPath, pattern]),\n\t);\n\tif ('error' in ran)\n\t\treturn { envVars: [], optionalEnvVars: [], error: ran.error };\n\n\t// Log any stderr output (import errors, etc.)\n\tif (ran.stderr) {\n\t\tran.stderr\n\t\t\t.split('\\n')\n\t\t\t.filter((line) => line.trim())\n\t\t\t.forEach((line) => output.warn(line));\n\t}\n\n\tconst answer = snifferAnswer(ran.stdout);\n\tif (answer) {\n\t\treturn {\n\t\t\tenvVars: answer.envVars || [],\n\t\t\toptionalEnvVars: answer.optionalEnvVars || [],\n\t\t\terror: answer.error ? new Error(answer.error) : undefined,\n\t\t};\n\t}\n\n\t// If we couldn't parse the output, return empty with error info\n\treturn {\n\t\tenvVars: [],\n\t\toptionalEnvVars: [],\n\t\terror: new Error(\n\t\t\t`Failed to sniff route files (exit code ${ran.code}): ${ran.stderr || ran.stdout || 'No output'}`,\n\t\t),\n\t};\n}\n\n/**\n * Run the SnifferEnvironmentParser on an envParser module to detect\n * which environment variables it accesses.\n *\n * In a subprocess, like the entry and route sniffers: the module is the\n * project's code, and it was imported into the deploy itself before — with\n * every credential the deploy held in reach. Fire-and-forget rejections are\n * collected there with the shared `sniffWithFireAndForget`.\n *\n * @param envParserPath - The envParser config (e.g., './src/config/env#envParser')\n * @param appPath - The app's path relative to workspace\n * @param workspacePath - Absolute path to workspace root\n * @returns SniffResult with env vars and any errors encountered\n */\nasync function sniffEnvParser(\n\tenvParserPath: string,\n\tappPath: string,\n\tworkspacePath: string,\n\trun: SniffRun = {},\n): Promise<SniffResult> {\n\t// Parse the envParser path: './src/config/env#envParser' or './src/config/env'\n\tconst [modulePath, exportName = 'default'] = envParserPath.split('#');\n\tif (!modulePath) {\n\t\treturn { envVars: [], optionalEnvVars: [], unhandledRejections: [] };\n\t}\n\n\tconst appRoot = resolve(workspacePath, appPath);\n\tconst fullPath = resolve(appRoot, modulePath);\n\tconst workerPath = resolveSnifferFile('sniffer-envparser-worker');\n\n\tconst ran = await runSniffer(\n\t\trun,\n\t\tworkspacePath,\n\t\tappRoot,\n\t\tnodeWithTsx(workerPath, [fullPath, exportName]),\n\t);\n\tif ('error' in ran) {\n\t\treturn {\n\t\t\tenvVars: [],\n\t\t\toptionalEnvVars: [],\n\t\t\tunhandledRejections: [],\n\t\t\terror: ran.error,\n\t\t};\n\t}\n\n\tconst answer = snifferAnswer(ran.stdout);\n\tif (!answer) {\n\t\treturn {\n\t\t\tenvVars: [],\n\t\t\toptionalEnvVars: [],\n\t\t\tunhandledRejections: [],\n\t\t\terror: new Error(\n\t\t\t\t`Failed to sniff envParser (exit code ${ran.code}): ${ran.stderr || ran.stdout || 'No output'}`,\n\t\t\t),\n\t\t};\n\t}\n\n\tfor (const warning of answer.warnings ?? []) output.warn(warning);\n\n\treturn {\n\t\tenvVars: answer.envVars ?? [],\n\t\toptionalEnvVars: answer.optionalEnvVars ?? [],\n\t\tunhandledRejections: (answer.unhandledRejections ?? []).map(\n\t\t\t(message) => new Error(message),\n\t\t),\n\t\t...(answer.error ? { error: new Error(answer.error) } : {}),\n\t};\n}\n\n/**\n * Sniff environment requirements for multiple apps.\n *\n * @param apps - Map of app name to app config\n * @param workspacePath - Absolute path to workspace root\n * @param options - Optional configuration for sniffing behavior\n * @returns Map of app name to sniffed environment\n */\nexport async function sniffAllApps(\n\tapps: Record<string, NormalizedAppConfig>,\n\tworkspacePath: string,\n\toptions: SniffAppOptions = {},\n): Promise<Map<string, SniffedEnvironment>> {\n\tconst results = new Map<string, SniffedEnvironment>();\n\n\tfor (const [appName, app] of Object.entries(apps)) {\n\t\tconst sniffed = await sniffAppEnvironment(\n\t\t\tapp,\n\t\t\tappName,\n\t\t\tworkspacePath,\n\t\t\toptions,\n\t\t);\n\t\tresults.set(appName, sniffed);\n\t}\n\n\treturn results;\n}\n\n// Export for testing\nexport {\n\tsniffEnvParser as _sniffEnvParser,\n\tsniffEntryFile as _sniffEntryFile,\n\tsniffRouteFiles as _sniffRouteFiles,\n};\n"],"mappings":"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"}