@geekmidas/cli 10.0.0-alpha.92 → 10.0.0-alpha.93

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 (152) hide show
  1. package/dist/{GoDaddyProvider-CJwg2r_9.cjs → GoDaddyProvider-BZb-V14e.cjs} +2 -2
  2. package/dist/{GoDaddyProvider-CJwg2r_9.cjs.map → GoDaddyProvider-BZb-V14e.cjs.map} +1 -1
  3. package/dist/{GoDaddyProvider-BqYGk9XT.mjs → GoDaddyProvider-Mi3vptSR.mjs} +2 -2
  4. package/dist/{GoDaddyProvider-BqYGk9XT.mjs.map → GoDaddyProvider-Mi3vptSR.mjs.map} +1 -1
  5. package/dist/{LegacyStateStore-CeEwayMB.cjs → LegacyStateStore-BlYXQQSE.cjs} +44 -10
  6. package/dist/LegacyStateStore-BlYXQQSE.cjs.map +1 -0
  7. package/dist/LegacyStateStore-CtwO7fwy.mjs +123 -0
  8. package/dist/LegacyStateStore-CtwO7fwy.mjs.map +1 -0
  9. package/dist/{LocalStateStore-Y8XHdoCR.mjs → LocalStateStore-D40_SDmJ.mjs} +2 -2
  10. package/dist/{LocalStateStore-Y8XHdoCR.mjs.map → LocalStateStore-D40_SDmJ.mjs.map} +1 -1
  11. package/dist/{LocalStateStore-BMfseje2.cjs → LocalStateStore-oX_zxj9V.cjs} +2 -2
  12. package/dist/{LocalStateStore-BMfseje2.cjs.map → LocalStateStore-oX_zxj9V.cjs.map} +1 -1
  13. package/dist/{S3StateStore-BfoY4MvD.mjs → S3StateStore-BzS0IeSt.mjs} +2 -2
  14. package/dist/{S3StateStore-BfoY4MvD.mjs.map → S3StateStore-BzS0IeSt.mjs.map} +1 -1
  15. package/dist/{S3StateStore-BmMt2HhW.cjs → S3StateStore-DeI7jtvi.cjs} +2 -2
  16. package/dist/{S3StateStore-BmMt2HhW.cjs.map → S3StateStore-DeI7jtvi.cjs.map} +1 -1
  17. package/dist/{SSMStateStore-D9K62wM4.cjs → SSMStateStore-CaCtrzFL.cjs} +2 -2
  18. package/dist/{SSMStateStore-D9K62wM4.cjs.map → SSMStateStore-CaCtrzFL.cjs.map} +1 -1
  19. package/dist/{SSMStateStore-Bdyw_0Jj.mjs → SSMStateStore-k_Ew7TWx.mjs} +2 -2
  20. package/dist/{SSMStateStore-Bdyw_0Jj.mjs.map → SSMStateStore-k_Ew7TWx.mjs.map} +1 -1
  21. package/dist/{add-2Opn2VVE.mjs → add-CP1podDH.mjs} +2 -3
  22. package/dist/{add-2Opn2VVE.mjs.map → add-CP1podDH.mjs.map} +1 -1
  23. package/dist/{add-oN0b18HN.cjs → add-VcyJnsPX.cjs} +3 -4
  24. package/dist/{add-oN0b18HN.cjs.map → add-VcyJnsPX.cjs.map} +1 -1
  25. package/dist/{bundler-C-AdesaG.mjs → bundler-BWrh-AJH.mjs} +3 -3
  26. package/dist/{bundler-C-AdesaG.mjs.map → bundler-BWrh-AJH.mjs.map} +1 -1
  27. package/dist/{bundler-CXQDrhzN.cjs → bundler-Dfsr6KXU.cjs} +3 -3
  28. package/dist/{bundler-CXQDrhzN.cjs.map → bundler-Dfsr6KXU.cjs.map} +1 -1
  29. package/dist/{cli-BjhaVObS.mjs → cli-B8otnZfX.mjs} +4 -4
  30. package/dist/{cli-BjhaVObS.mjs.map → cli-B8otnZfX.mjs.map} +1 -1
  31. package/dist/{cli-BRZFYvdb.cjs → cli-DL19w4OE.cjs} +2 -2
  32. package/dist/{cli-BRZFYvdb.cjs.map → cli-DL19w4OE.cjs.map} +1 -1
  33. package/dist/compose-BHVKLluX.cjs +1523 -0
  34. package/dist/compose-BHVKLluX.cjs.map +1 -0
  35. package/dist/compose-DrEB_4F-.mjs +1476 -0
  36. package/dist/compose-DrEB_4F-.mjs.map +1 -0
  37. package/dist/{config-BJo8Wggp.d.mts → config-CHX5GOo3.d.mts} +4 -4
  38. package/dist/{config-BJo8Wggp.d.mts.map → config-CHX5GOo3.d.mts.map} +1 -1
  39. package/dist/{config-C9zx7KsX.d.cts → config-DCCcrOSo.d.cts} +4 -4
  40. package/dist/{config-C9zx7KsX.d.cts.map → config-DCCcrOSo.d.cts.map} +1 -1
  41. package/dist/config.d.cts +3 -3
  42. package/dist/config.d.mts +3 -3
  43. package/dist/deploy/public.cjs +6 -5
  44. package/dist/deploy/public.d.cts +6 -6
  45. package/dist/deploy/public.d.cts.map +1 -1
  46. package/dist/deploy/public.d.mts +6 -6
  47. package/dist/deploy/public.d.mts.map +1 -1
  48. package/dist/deploy/public.mjs +6 -6
  49. package/dist/{deploy-6ToUHJmM.cjs → deploy-C3Mhnmus.cjs} +4 -3
  50. package/dist/deploy-C3Mhnmus.cjs.map +1 -0
  51. package/dist/{deploy-yuJHlkY0.mjs → deploy-DYIyIUYd.mjs} +4 -3
  52. package/dist/deploy-DYIyIUYd.mjs.map +1 -0
  53. package/dist/{discover-CRMhvHG9.d.cts → discover-DqwlBVyY.d.mts} +2 -2
  54. package/dist/{discover-CRMhvHG9.d.cts.map → discover-DqwlBVyY.d.mts.map} +1 -1
  55. package/dist/{discover-G76EQ4vY.d.mts → discover-vk2r5giK.d.cts} +2 -2
  56. package/dist/{discover-G76EQ4vY.d.mts.map → discover-vk2r5giK.d.cts.map} +1 -1
  57. package/dist/{dokploy-BvbJ_A4Q.mjs → dokploy-o4I6vb9W.mjs} +2 -2
  58. package/dist/{dokploy-BvbJ_A4Q.mjs.map → dokploy-o4I6vb9W.mjs.map} +1 -1
  59. package/dist/{dokploy-Bvvfj2qe.cjs → dokploy-rivb1RFt.cjs} +2 -2
  60. package/dist/{dokploy-Bvvfj2qe.cjs.map → dokploy-rivb1RFt.cjs.map} +1 -1
  61. package/dist/{encryption-C9Jm9dfn.mjs → encryption-B6ilrT7p.mjs} +2 -2
  62. package/dist/{encryption-C9Jm9dfn.mjs.map → encryption-B6ilrT7p.mjs.map} +1 -1
  63. package/dist/{encryption-CgW167LO.cjs → encryption-D7zmwzWz.cjs} +2 -2
  64. package/dist/{encryption-CgW167LO.cjs.map → encryption-D7zmwzWz.cjs.map} +1 -1
  65. package/dist/{engine-Dj7ZKO8H.cjs → engine-Cfx8fEZU.cjs} +6 -5
  66. package/dist/engine-Cfx8fEZU.cjs.map +1 -0
  67. package/dist/{engine-C8Ye9MUm.mjs → engine-DjEcovm-.mjs} +6 -5
  68. package/dist/engine-DjEcovm-.mjs.map +1 -0
  69. package/dist/{github-B1k42hIt.mjs → github-CzBbs43W.mjs} +2 -2
  70. package/dist/{github-B1k42hIt.mjs.map → github-CzBbs43W.mjs.map} +1 -1
  71. package/dist/{github-BD051KZZ.cjs → github-zneTMvnO.cjs} +2 -2
  72. package/dist/{github-BD051KZZ.cjs.map → github-zneTMvnO.cjs.map} +1 -1
  73. package/dist/{index-D1pHwqkK.d.mts → index-BiYU7cUl.d.mts} +2 -2
  74. package/dist/{index-B5uuwx-Y.d.cts.map → index-BiYU7cUl.d.mts.map} +1 -1
  75. package/dist/{index-B5uuwx-Y.d.cts → index-Dm6V9bwh.d.cts} +2 -2
  76. package/dist/{index-D1pHwqkK.d.mts.map → index-Dm6V9bwh.d.cts.map} +1 -1
  77. package/dist/index.cjs +143 -184
  78. package/dist/index.cjs.map +1 -1
  79. package/dist/index.mjs +141 -181
  80. package/dist/index.mjs.map +1 -1
  81. package/dist/{migrate-IZsYyZ5F.mjs → migrate-BaC4w0sb.mjs} +2 -2
  82. package/dist/{migrate-IZsYyZ5F.mjs.map → migrate-BaC4w0sb.mjs.map} +1 -1
  83. package/dist/{migrate-D3VVsiHp.cjs → migrate-C8wTpakT.cjs} +2 -2
  84. package/dist/{migrate-D3VVsiHp.cjs.map → migrate-C8wTpakT.cjs.map} +1 -1
  85. package/dist/{migrate-BSbZ7-9n.mjs → migrate-CsBZKALY.mjs} +3 -3
  86. package/dist/{migrate-BSbZ7-9n.mjs.map → migrate-CsBZKALY.mjs.map} +1 -1
  87. package/dist/{migrate-f6JDjyCp.cjs → migrate-DFjbGnSr.cjs} +2 -2
  88. package/dist/{migrate-f6JDjyCp.cjs.map → migrate-DFjbGnSr.cjs.map} +1 -1
  89. package/dist/openapi.d.cts +1 -1
  90. package/dist/openapi.d.mts +1 -1
  91. package/dist/reconcile/public.d.cts +2 -2
  92. package/dist/reconcile/public.d.mts +2 -2
  93. package/dist/{reconcile-Dh1Jk7WE.mjs → reconcile--I7V4Klj.mjs} +2 -2
  94. package/dist/{reconcile-Dh1Jk7WE.mjs.map → reconcile--I7V4Klj.mjs.map} +1 -1
  95. package/dist/{resolve-Be4bF1w4.mjs → resolve-Ccjlpnd9.mjs} +3 -3
  96. package/dist/{resolve-Be4bF1w4.mjs.map → resolve-Ccjlpnd9.mjs.map} +1 -1
  97. package/dist/{resolve-DUp65SnJ.cjs → resolve-g8htuR80.cjs} +3 -3
  98. package/dist/{resolve-DUp65SnJ.cjs.map → resolve-g8htuR80.cjs.map} +1 -1
  99. package/dist/{rollback-DDHtd0GJ.cjs → rollback--ympG9ss.cjs} +5 -4
  100. package/dist/rollback--ympG9ss.cjs.map +1 -0
  101. package/dist/{rollback-D1d-fW0r.mjs → rollback-Drbg0OeR.mjs} +5 -4
  102. package/dist/rollback-Drbg0OeR.mjs.map +1 -0
  103. package/dist/{runtime-Es1V4e9M.d.mts → runtime-D_J3ExJn.d.cts} +2 -2
  104. package/dist/{runtime-DDrHlYe5.d.cts.map → runtime-D_J3ExJn.d.cts.map} +1 -1
  105. package/dist/{runtime-DDrHlYe5.d.cts → runtime-_wEVtshH.d.mts} +2 -2
  106. package/dist/{runtime-Es1V4e9M.d.mts.map → runtime-_wEVtshH.d.mts.map} +1 -1
  107. package/dist/{sandbox-C3WsTa5m.d.mts → sandbox-CPJq1Kia.d.mts} +2 -2
  108. package/dist/{sandbox-C3WsTa5m.d.mts.map → sandbox-CPJq1Kia.d.mts.map} +1 -1
  109. package/dist/{sandbox-MaxSeU92.d.cts → sandbox-JwuPAl7I.d.cts} +2 -2
  110. package/dist/{sandbox-MaxSeU92.d.cts.map → sandbox-JwuPAl7I.d.cts.map} +1 -1
  111. package/dist/secrets-DJteijZo.cjs +47 -0
  112. package/dist/secrets-DJteijZo.cjs.map +1 -0
  113. package/dist/secrets-DbPmAS4k.mjs +36 -0
  114. package/dist/secrets-DbPmAS4k.mjs.map +1 -0
  115. package/dist/{seeds-CvGQJnPO.mjs → seeds-BSL55F6q.mjs} +897 -59
  116. package/dist/seeds-BSL55F6q.mjs.map +1 -0
  117. package/dist/{seeds-BKoJYsE7.cjs → seeds-c-ByX9je.cjs} +1014 -80
  118. package/dist/seeds-c-ByX9je.cjs.map +1 -0
  119. package/dist/stack-1PeLJS2j.cjs +1346 -0
  120. package/dist/stack-1PeLJS2j.cjs.map +1 -0
  121. package/dist/stack-Co4uZkAo.mjs +1239 -0
  122. package/dist/stack-Co4uZkAo.mjs.map +1 -0
  123. package/dist/target/public.cjs +1 -1
  124. package/dist/target/public.d.cts +2 -2
  125. package/dist/target/public.d.mts +2 -2
  126. package/dist/target/public.mjs +1 -1
  127. package/dist/{terminal-Dtd6BZX9.mjs → terminal-C4XRdwnU.mjs} +2 -2
  128. package/dist/{terminal-Dtd6BZX9.mjs.map → terminal-C4XRdwnU.mjs.map} +1 -1
  129. package/dist/{types-Bk9st2cd.d.cts → types-B6lmD3en.d.mts} +77 -26
  130. package/dist/types-B6lmD3en.d.mts.map +1 -0
  131. package/dist/{types-B3Gqs175.d.mts → types-BibpiHKB.d.cts} +78 -25
  132. package/dist/types-BibpiHKB.d.cts.map +1 -0
  133. package/dist/workspace/index.d.cts +2 -2
  134. package/dist/workspace/index.d.mts +2 -2
  135. package/package.json +15 -15
  136. package/dist/LegacyStateStore-CeEwayMB.cjs.map +0 -1
  137. package/dist/LegacyStateStore-DdJ1bCRV.mjs +0 -89
  138. package/dist/LegacyStateStore-DdJ1bCRV.mjs.map +0 -1
  139. package/dist/compose-CTdVeeVk.mjs +0 -3151
  140. package/dist/compose-CTdVeeVk.mjs.map +0 -1
  141. package/dist/compose-fnYmPk_c.cjs +0 -3276
  142. package/dist/compose-fnYmPk_c.cjs.map +0 -1
  143. package/dist/deploy-6ToUHJmM.cjs.map +0 -1
  144. package/dist/deploy-yuJHlkY0.mjs.map +0 -1
  145. package/dist/engine-C8Ye9MUm.mjs.map +0 -1
  146. package/dist/engine-Dj7ZKO8H.cjs.map +0 -1
  147. package/dist/rollback-D1d-fW0r.mjs.map +0 -1
  148. package/dist/rollback-DDHtd0GJ.cjs.map +0 -1
  149. package/dist/seeds-BKoJYsE7.cjs.map +0 -1
  150. package/dist/seeds-CvGQJnPO.mjs.map +0 -1
  151. package/dist/types-B3Gqs175.d.mts.map +0 -1
  152. package/dist/types-Bk9st2cd.d.cts.map +0 -1
@@ -1,13 +1,13 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
2
2
  import { t as GkmError } from "./errors-D8Gwp5kU.mjs";
3
- import { r as storedCredentials, s as run$1, x as gkmHome } from "./credentials-C4KII3PD.mjs";
3
+ import { r as storedCredentials, s as run$1, t as MissingCredential, x as gkmHome } from "./credentials-C4KII3PD.mjs";
4
4
  import { d as output, r as discover, u as ConstructGenerator } from "./discover-qjFapetL.mjs";
5
5
  import { t as appKey } from "./derive-B4vDpzgP.mjs";
6
- import { $ as RotationInProgress, B as LOGS_PORT, G as checkS3Config, H as provisioningOf, J as BucketNameUnavailable, K as AccessKeyLimit, Q as RecordedBucketUnreachable, R as stageSampleRate, U as stageProvider, W as S3_PROVISIONING, X as IamUserNotOwned, Y as BucketOwnedByAnotherProject, Z as ProvisionedBucketUnreachable, _ as PUBLIC_ENV_PREFIXES, a as allConstructGlobs, at as SERVER_IPV6_KEY, et as S3RegionRequired, it as SERVER_IPV4_KEY, j as TEST_STAGE, lt as UnknownSecretsStoreProvider, q as BucketKeySetElsewhere, st as isReservedStageKey, v as getPublicEnvPrefix, z as telemetryEnv } from "./workspace-Bzw7Zs82.mjs";
6
+ import { $ as RotationInProgress, B as LOGS_PORT, G as checkS3Config, H as provisioningOf, J as BucketNameUnavailable, K as AccessKeyLimit, Q as RecordedBucketUnreachable, R as stageSampleRate, U as stageProvider, W as S3_PROVISIONING, X as IamUserNotOwned, Y as BucketOwnedByAnotherProject, Z as ProvisionedBucketUnreachable, _ as PUBLIC_ENV_PREFIXES, a as allConstructGlobs, at as SERVER_IPV6_KEY, et as S3RegionRequired, it as SERVER_IPV4_KEY, j as TEST_STAGE, lt as UnknownSecretsStoreProvider, ot as cnameTarget, q as BucketKeySetElsewhere, st as isReservedStageKey, v as getPublicEnvPrefix, z as telemetryEnv } from "./workspace-Bzw7Zs82.mjs";
7
7
  import { ConfigNotFound, NotInAnApp, loadWorkspaceAppInfo, loadWorkspaceConfig } from "./config.mjs";
8
8
  import { a as telemetryArgs, c as startTelemetryCode, n as runtimeFor, u as writeTelemetryModule } from "./EndpointGenerator-Bor2tZCm.mjs";
9
9
  import { a as DEFAULT_STORAGE, i as DEFAULT_EVENTS$1, n as readFakes, o as holdsEveryCache, r as DEFAULT_CACHE, t as SubscriberGenerator } from "./SubscriberGenerator-CyN2VF5X.mjs";
10
- import { c as runtimeOf } from "./resolve-Be4bF1w4.mjs";
10
+ import { c as runtimeOf } from "./resolve-Ccjlpnd9.mjs";
11
11
  import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
12
12
  import { constants, existsSync, readFileSync } from "node:fs";
13
13
  import { appendFile, chmod, copyFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
@@ -16,7 +16,7 @@ import { promisify } from "node:util";
16
16
  import { Cron } from "@geekmidas/constructs/crons";
17
17
  import { Queue } from "@geekmidas/constructs/queue";
18
18
  import { CLIENT_IP_HEADER, DEFAULT_POSTGRES_VERSION, TELEMETRY_KEYS, cacheTable, cookieDomain, dependenciesOf, dependentsOf, externalApiUrl, kebabCase, mobileOrigins, provideKey, providedKeyFor, provisionOrder, publicEnvFor, schemeBase, scopedName } from "@geekmidas/manifest";
19
- import { createServer } from "node:net";
19
+ import { createServer, isIPv4, isIPv6 } from "node:net";
20
20
  import { execFile, spawn } from "node:child_process";
21
21
  import { createCipheriv, createDecipheriv, createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
22
22
  import { config } from "dotenv";
@@ -6947,6 +6947,126 @@ function reportDevServices(ctx, uses) {
6947
6947
  }
6948
6948
  }
6949
6949
  //#endregion
6950
+ //#region src/deploy/actor.ts
6951
+ /**
6952
+ * Who wrote a stage's deploy state: a GitHub Actions run, or a person at a
6953
+ * machine. Recorded on every state write (`updatedBy`), on every resource
6954
+ * record and on every release (`releasedBy`), so "who put this here" has an
6955
+ * answer that does not depend on anyone's memory.
6956
+ */
6957
+ /** Whether `env` is a CI job — GitHub Actions or any runner that sets `CI`. */
6958
+ function isCi(env = process.env) {
6959
+ return env.GITHUB_ACTIONS === "true" || env.CI === "true";
6960
+ }
6961
+ /** Who this process is writing as. */
6962
+ function currentActor(env = process.env) {
6963
+ if (env.GITHUB_ACTIONS === "true") {
6964
+ const server = env.GITHUB_SERVER_URL ?? "https://github.com";
6965
+ return {
6966
+ kind: "github",
6967
+ actor: env.GITHUB_ACTOR ?? "unknown",
6968
+ run: `${server}/${env.GITHUB_REPOSITORY ?? "unknown"}/actions/runs/${env.GITHUB_RUN_ID ?? "unknown"}`,
6969
+ ...env.GITHUB_WORKFLOW ? { workflow: env.GITHUB_WORKFLOW } : {}
6970
+ };
6971
+ }
6972
+ let user = "unknown";
6973
+ try {
6974
+ user = userInfo().username;
6975
+ } catch {}
6976
+ return {
6977
+ kind: "local",
6978
+ user,
6979
+ host: hostname()
6980
+ };
6981
+ }
6982
+ /** `octocat (deploy, https://github.com/…/runs/1)` or `ada@laptop`. */
6983
+ function describeActor(actor) {
6984
+ if (!actor) return "unknown";
6985
+ if (actor.kind === "local") return `${actor.user}@${actor.host}`;
6986
+ return `${actor.actor} (${actor.workflow ? `${actor.workflow}, ` : ""}${actor.run})`;
6987
+ }
6988
+ //#endregion
6989
+ //#region src/deploy/dnsResources.ts
6990
+ /** The resource type of a DNS record gkm wrote. */
6991
+ const DNS_RECORD_RESOURCE = "dns-record";
6992
+ /** A record's full name: `api.example.com`, or the domain for `@`. */
6993
+ function dnsRecordFqdn(domain, name) {
6994
+ return name === "@" || name === "" ? domain : `${name}.${domain}`;
6995
+ }
6996
+ /** `dns-record:<fqdn>:<type>` */
6997
+ function dnsResourceKey(fqdn, type) {
6998
+ return `${DNS_RECORD_RESOURCE}:${fqdn.toLowerCase()}:${type.toUpperCase()}`;
6999
+ }
7000
+ /** The resource record a written DNS record is kept as. */
7001
+ function dnsRecordResource(record) {
7002
+ const fqdn = dnsRecordFqdn(record.domain, record.name).toLowerCase();
7003
+ const type = record.type.toUpperCase();
7004
+ const data = {
7005
+ domain: record.domain,
7006
+ name: record.name,
7007
+ value: record.value,
7008
+ ttl: record.ttl,
7009
+ ...record.provider ? { provider: record.provider } : {}
7010
+ };
7011
+ return {
7012
+ key: dnsResourceKey(fqdn, type),
7013
+ type: DNS_RECORD_RESOURCE,
7014
+ id: `${fqdn} ${type}`,
7015
+ status: "ready",
7016
+ data
7017
+ };
7018
+ }
7019
+ /** Records that gkm wrote `record` — or updates the value it holds. */
7020
+ async function recordDnsResource(journal, record) {
7021
+ const { key, type, id, data } = dnsRecordResource(record);
7022
+ await journal.ready({
7023
+ key,
7024
+ type,
7025
+ ...data ? { data } : {}
7026
+ }, id);
7027
+ }
7028
+ /** Forgets a DNS record gkm deleted. */
7029
+ async function forgetDnsResource(journal, record) {
7030
+ await journal.forget(dnsResourceKey(dnsRecordFqdn(record.domain, record.name), record.type));
7031
+ }
7032
+ /**
7033
+ * The changes a DNS step made, kept: what it created, updated or wrote is
7034
+ * recorded, what it deleted is forgotten. A record it found already right is
7035
+ * recorded only if gkm wrote it before; one it left to a person is not.
7036
+ */
7037
+ async function recordDnsChanges(journal, changes, providerOf) {
7038
+ for (const change of changes) {
7039
+ if (change.action === "delete") {
7040
+ await forgetDnsResource(journal, change);
7041
+ continue;
7042
+ }
7043
+ const written = change.action === "create" || change.action === "update" || change.action === "write";
7044
+ const known = change.action === "unchanged" && journal.record(dnsResourceKey(dnsRecordFqdn(change.domain, change.name), change.type)) !== void 0;
7045
+ if (!written && !known || change.value === void 0) continue;
7046
+ const provider = providerOf(change.domain);
7047
+ await recordDnsResource(journal, {
7048
+ domain: change.domain,
7049
+ name: change.name,
7050
+ type: change.type,
7051
+ value: change.value,
7052
+ ttl: change.ttl ?? 0,
7053
+ ...provider ? { provider } : {}
7054
+ });
7055
+ }
7056
+ }
7057
+ /** Every DNS record gkm wrote for the stage, from its resource records. */
7058
+ function dnsResources(resources) {
7059
+ return Object.values(resources).filter((record) => record.type === DNS_RECORD_RESOURCE).map((record) => {
7060
+ const data = record.data ?? {};
7061
+ const [fqdn = "", type = ""] = (record.id ?? "").split(" ");
7062
+ return {
7063
+ ...data,
7064
+ type,
7065
+ fqdn
7066
+ };
7067
+ }).sort((a, b) => a.fqdn.localeCompare(b.fqdn) || a.type.localeCompare(b.type));
7068
+ }
7069
+ //#endregion
6950
7070
  //#region src/deploy/StateProvider.ts
6951
7071
  /**
6952
7072
  * Check if value is a StateProvider implementation.
@@ -6954,28 +7074,6 @@ function reportDevServices(ctx, uses) {
6954
7074
  function isStateProvider(value) {
6955
7075
  return typeof value === "object" && value !== null && typeof value.read === "function" && typeof value.write === "function";
6956
7076
  }
6957
- //#endregion
6958
- //#region src/deploy/StateStore.ts
6959
- /**
6960
- * Deploy state store.
6961
- *
6962
- * `StateProvider` reads and writes a whole stage at once, with nothing to stop
6963
- * two runs of the same stage from overwriting each other and nowhere to record
6964
- * a resource the moment it exists. A `StateStore` adds the three things a
6965
- * deploy needs to be safe against crashes and concurrent runs:
6966
- *
6967
- * - `lock(stage)`: one run per stage. A second gets `StateLocked`, naming the
6968
- * holder, instead of racing the first.
6969
- * - Versioned `read` and conditional `write`: a write names the version it was
6970
- * based on, and raises `StateVersionConflict` when the stored state moved on
6971
- * underneath it, so nothing is lost silently even without the lock.
6972
- * - Per-resource records: `putResource` journals one resource at a time, so a
6973
- * run that dies half way leaves behind the ids of everything it created.
6974
- *
6975
- * Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A
6976
- * custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it
6977
- * working but cannot lock.
6978
- */
6979
7077
  var StateLocked = class extends GkmError {
6980
7078
  stage;
6981
7079
  holder;
@@ -7023,7 +7121,7 @@ var StateSchemaTooNew = class extends GkmError {
7023
7121
  stage;
7024
7122
  schemaVersion;
7025
7123
  constructor(stage, schemaVersion) {
7026
- super(`Deploy state for stage '${stage}' has schema version ${String(schemaVersion)}, newer than this @geekmidas/cli understands (2). Upgrade @geekmidas/cli to the version that wrote it.`);
7124
+ super(`Deploy state for stage '${stage}' has schema version ${String(schemaVersion)}, newer than this @geekmidas/cli understands (3). Upgrade @geekmidas/cli to the version that wrote it.`);
7027
7125
  this.stage = stage;
7028
7126
  this.schemaVersion = schemaVersion;
7029
7127
  this.name = "StateSchemaTooNew";
@@ -7058,6 +7156,22 @@ var UnknownStateProvider = class extends GkmError {
7058
7156
  this.name = "UnknownStateProvider";
7059
7157
  }
7060
7158
  };
7159
+ /**
7160
+ * A deploy, setup or rollback of a stage in CI, with the stage's state kept on
7161
+ * the runner — which is discarded when the job ends.
7162
+ */
7163
+ var LocalStateInCi = class extends GkmError {
7164
+ stage;
7165
+ operation;
7166
+ region;
7167
+ constructor(stage, operation, region) {
7168
+ super(`\`gkm ${operation}\` would keep stage '${stage}'s deploy state in .gkm/ on this CI runner, and the runner is discarded when the job ends: the next run would start without it — no releases to roll back to, no record of what was created. Keep the state in AWS — in gkm.config.ts:\n\n state: { provider: 'ssm', region: '${region}' },\n\nthen move the state you already have, from the machine that holds it:\n\n gkm state:push --stage ${stage}\n`);
7169
+ this.stage = stage;
7170
+ this.operation = operation;
7171
+ this.region = region;
7172
+ this.name = "LocalStateInCi";
7173
+ }
7174
+ };
7061
7175
  /** A lock or write that could not get at the state backend in time. */
7062
7176
  var StateStoreBusy = class extends GkmError {
7063
7177
  stage;
@@ -7086,6 +7200,10 @@ function decodeDocument(stage, body, location) {
7086
7200
  schemaVersion: 2,
7087
7201
  document: parsed
7088
7202
  };
7203
+ if (schemaVersion === 3) return {
7204
+ schemaVersion: 3,
7205
+ document: parsed
7206
+ };
7089
7207
  throw new StateSchemaTooNew(stage, schemaVersion);
7090
7208
  }
7091
7209
  function encodeDocument(document) {
@@ -7125,6 +7243,79 @@ function migrateV1(stage, state) {
7125
7243
  updatedAt: now
7126
7244
  };
7127
7245
  }
7246
+ const isEmpty = (value) => !value || Object.keys(value).length === 0;
7247
+ /**
7248
+ * Whether a v2 state in the Dokploy shape was written by the compose target.
7249
+ *
7250
+ * Before v3 every target wrote the Dokploy shape, and compose filled none of
7251
+ * its Dokploy fields. So the rule is the shape's content, not the target the
7252
+ * workspace names now: a state whose every Dokploy-only field is empty — no
7253
+ * project or environment id, no application or service ids, no credentials,
7254
+ * generated secrets, backups or registry — holds nothing only Dokploy could
7255
+ * use, and reading it as compose loses nothing. One with any of them was
7256
+ * written by Dokploy and is kept exactly as it is.
7257
+ *
7258
+ * The configured target is not used: a document decodes the same wherever it
7259
+ * is read (`state:show`, `state:push`) without a workspace, a stage can be
7260
+ * moved between targets, and `--target` can override a run.
7261
+ */
7262
+ function writtenByCompose(state) {
7263
+ return !state.projectId && !state.environmentId && isEmpty(state.applications) && !state.services?.postgresId && !state.services?.redisId && isEmpty(state.appCredentials) && isEmpty(state.generatedSecrets) && !state.backups && !state.registryId;
7264
+ }
7265
+ /**
7266
+ * A v2 state and its records as v3 keeps them. A compose-written state
7267
+ * becomes the compose shape: its `dnsRecords` become `dns-record` resources,
7268
+ * and `dnsVerified` and the empty Dokploy fields go — the DNS check is made
7269
+ * on every deploy. A Dokploy state is returned as it is.
7270
+ */
7271
+ function upgradeV2State(state, resources) {
7272
+ if (!writtenByCompose(state)) return {
7273
+ state,
7274
+ resources
7275
+ };
7276
+ const compose = {
7277
+ provider: "compose",
7278
+ stage: state.stage,
7279
+ lastDeployedAt: state.lastDeployedAt,
7280
+ ...state.identity ? { identity: state.identity } : {},
7281
+ ...state.releases ? { releases: state.releases } : {}
7282
+ };
7283
+ const upgraded = { ...resources };
7284
+ for (const record of Object.values(state.dnsRecords ?? {})) {
7285
+ const resource = migratedDnsResource(record);
7286
+ upgraded[resource.key] = resource;
7287
+ }
7288
+ return {
7289
+ state: compose,
7290
+ resources: upgraded
7291
+ };
7292
+ }
7293
+ /** A v2 `dnsRecords` entry as the `dns-record` resource v3 keeps. */
7294
+ function migratedDnsResource(record) {
7295
+ return {
7296
+ ...dnsRecordResource({
7297
+ domain: record.domain,
7298
+ name: record.name,
7299
+ type: record.type,
7300
+ value: record.value,
7301
+ ttl: record.ttl
7302
+ }),
7303
+ updatedAt: record.createdAt
7304
+ };
7305
+ }
7306
+ /** A v2 document as v3 — not written until the next write. */
7307
+ function migrateV2(document) {
7308
+ const { state, resources } = upgradeV2State(document.state, document.resources);
7309
+ return {
7310
+ schemaVersion: 3,
7311
+ stage: document.stage,
7312
+ serial: document.serial,
7313
+ state,
7314
+ resources,
7315
+ updatedAt: document.updatedAt,
7316
+ history: []
7317
+ };
7318
+ }
7128
7319
  function newLockHolder(options) {
7129
7320
  let owner = "unknown";
7130
7321
  try {
@@ -7143,14 +7334,33 @@ function contentVersion(body) {
7143
7334
  return createHash("sha256").update(body).digest("hex").slice(0, 16);
7144
7335
  }
7145
7336
  /**
7337
+ * `history` with this write at its head. A run makes many writes — one per
7338
+ * resource it journals — so a run is one entry: a write by the run that
7339
+ * wrote the newest entry (the same lock) replaces it, and any other is
7340
+ * added. Without a lock, each write is its own entry.
7341
+ */
7342
+ function appendHistory(history, entry, sameRun) {
7343
+ return [entry, ...sameRun ? history.slice(1) : history].slice(0, 20);
7344
+ }
7345
+ /**
7146
7346
  * A store over a backend that can read and conditionally write one blob per
7147
- * stage, and create a lock object only if it does not exist. Migration and
7148
- * resource records are the same for every such backend, so they live here.
7347
+ * stage, and create a lock object only if it does not exist. Migration,
7348
+ * resource records and history are the same for every such backend, so they
7349
+ * live here.
7149
7350
  */
7150
7351
  var DocumentStateStore = class {
7352
+ /** The lock this store holds for each stage — what its writes are for. */
7353
+ held = /* @__PURE__ */ new Map();
7354
+ /** Per stage, the lock whose run wrote the newest history entry. */
7355
+ logged = /* @__PURE__ */ new Map();
7356
+ /** Who this store writes as. */
7357
+ actor() {
7358
+ return currentActor();
7359
+ }
7151
7360
  async lock(stage, options) {
7152
7361
  const holder = newLockHolder(options);
7153
7362
  await this.createLock(stage, holder);
7363
+ this.held.set(stage, holder);
7154
7364
  let released = false;
7155
7365
  return {
7156
7366
  stage,
@@ -7158,6 +7368,7 @@ var DocumentStateStore = class {
7158
7368
  release: async () => {
7159
7369
  if (released) return;
7160
7370
  released = true;
7371
+ if (this.held.get(stage)?.id === holder.id) this.held.delete(stage);
7161
7372
  await this.removeLock(stage, holder);
7162
7373
  }
7163
7374
  };
@@ -7173,19 +7384,21 @@ var DocumentStateStore = class {
7173
7384
  return {
7174
7385
  state: current.document.state,
7175
7386
  resources: current.document.resources,
7387
+ history: current.document.history,
7176
7388
  version: current.version
7177
7389
  };
7178
7390
  }
7179
7391
  async write(stage, state, options) {
7180
7392
  if (options.expectedVersion === null) {
7181
- const document = {
7182
- schemaVersion: 2,
7393
+ const document = this.stamp(stage, {
7394
+ schemaVersion: 3,
7183
7395
  stage,
7184
7396
  serial: 1,
7185
7397
  state,
7186
7398
  resources: {},
7187
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
7188
- };
7399
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7400
+ history: []
7401
+ });
7189
7402
  return this.writeRaw(stage, encodeDocument(document), null);
7190
7403
  }
7191
7404
  return this.mutate(stage, options.expectedVersion, (document) => ({
@@ -7200,7 +7413,8 @@ var DocumentStateStore = class {
7200
7413
  ...document.resources,
7201
7414
  [record.key]: {
7202
7415
  ...record,
7203
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
7416
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7417
+ updatedBy: this.actor()
7204
7418
  }
7205
7419
  }
7206
7420
  }));
@@ -7214,6 +7428,25 @@ var DocumentStateStore = class {
7214
7428
  };
7215
7429
  });
7216
7430
  }
7431
+ /** `document` as this write leaves it: who wrote it, in its history. */
7432
+ stamp(stage, document) {
7433
+ const by = this.actor();
7434
+ const holder = this.held.get(stage);
7435
+ const operation = holder?.operation ?? "write";
7436
+ const history = document.history ?? [];
7437
+ const sameRun = holder !== void 0 && this.logged.get(stage) === holder.id && history[0]?.operation === operation;
7438
+ if (holder) this.logged.set(stage, holder.id);
7439
+ return {
7440
+ ...document,
7441
+ updatedBy: by,
7442
+ history: appendHistory(history, {
7443
+ serial: document.serial,
7444
+ at: document.updatedAt,
7445
+ by,
7446
+ operation
7447
+ }, sameRun)
7448
+ };
7449
+ }
7217
7450
  async mutate(stage, expectedVersion, change) {
7218
7451
  const current = await this.readDocument(stage);
7219
7452
  if (!current) {
@@ -7222,25 +7455,31 @@ var DocumentStateStore = class {
7222
7455
  }
7223
7456
  if (expectedVersion !== void 0 && current.version !== expectedVersion) throw new StateVersionConflict(stage, expectedVersion, current.version);
7224
7457
  const next = change(current.document);
7458
+ next.schemaVersion = 3;
7225
7459
  next.serial = current.document.serial + 1;
7226
7460
  next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7227
- return this.writeRaw(stage, encodeDocument(next), current.version);
7461
+ return this.writeRaw(stage, encodeDocument(this.stamp(stage, next)), current.version);
7228
7462
  }
7229
7463
  /**
7230
- * Reads the stage's document, migrating v1 in place on the way. The
7231
- * migration is itself a conditional write: if another run migrated (or
7232
- * wrote) first, its result is read instead.
7464
+ * Reads the stage's document as v3. A v1 document is migrated in place on
7465
+ * the way — itself a conditional write: if another run migrated (or wrote)
7466
+ * first, its result is read instead. A v2 document is upgraded in memory
7467
+ * and written as v3 by the next write.
7233
7468
  */
7234
7469
  async readDocument(stage, attempt = 0) {
7235
7470
  const raw = await this.readRaw(stage);
7236
7471
  if (!raw) return null;
7237
7472
  const decoded = decodeDocument(stage, raw.body, this.location(stage));
7238
- if (decoded.schemaVersion === 2) return {
7473
+ if (decoded.schemaVersion === 3) return {
7239
7474
  document: decoded.document,
7240
7475
  version: raw.version
7241
7476
  };
7477
+ if (decoded.schemaVersion === 2) return {
7478
+ document: migrateV2(decoded.document),
7479
+ version: raw.version
7480
+ };
7242
7481
  await this.writeV1Backup(stage, raw.body);
7243
- const document = migrateV1(stage, decoded.state);
7482
+ const document = this.stamp(stage, migrateV2(migrateV1(stage, decoded.state)));
7244
7483
  try {
7245
7484
  return {
7246
7485
  document,
@@ -7252,6 +7491,22 @@ var DocumentStateStore = class {
7252
7491
  }
7253
7492
  }
7254
7493
  };
7494
+ /** Whether `config` keeps state on this machine — `local`, or none given. */
7495
+ function isLocalState(config) {
7496
+ return !config || config.provider === "local";
7497
+ }
7498
+ /**
7499
+ * Refuses, with `LocalStateInCi`, a run that would write a deployed stage's
7500
+ * state to a CI runner's disk — the local stage, which runs on the runner
7501
+ * itself, is exempt. Local use is untouched. The region the message
7502
+ * suggests is the secrets store's, when the workspace keeps them in AWS.
7503
+ */
7504
+ function assertStateOutlivesRun(workspace, stage, operation, env = process.env) {
7505
+ if (stage === workspace.stages?.local) return;
7506
+ if (!isCi(env) || !isLocalState(workspace.state)) return;
7507
+ const store = workspace.secrets?.store;
7508
+ throw new LocalStateInCi(stage, operation, typeof store === "object" && store && typeof store.region === "string" ? store.region : "<region>");
7509
+ }
7255
7510
  function isStateStore(value) {
7256
7511
  return typeof value === "object" && value !== null && typeof value.lock === "function" && typeof value.read === "function" && typeof value.write === "function" && typeof value.putResource === "function";
7257
7512
  }
@@ -7268,17 +7523,17 @@ function isStateStore(value) {
7268
7523
  async function createStateStore(options) {
7269
7524
  const { config, workspaceRoot, workspaceName } = options;
7270
7525
  if (!config || config.provider === "local") {
7271
- const { LocalStateStore } = await import("./LocalStateStore-Y8XHdoCR.mjs").then((n) => n.n);
7526
+ const { LocalStateStore } = await import("./LocalStateStore-D40_SDmJ.mjs").then((n) => n.n);
7272
7527
  return new LocalStateStore(workspaceRoot);
7273
7528
  }
7274
7529
  if (isStateStore(config.provider)) return config.provider;
7275
7530
  if (isStateProvider(config.provider)) {
7276
- const { LegacyStateStore } = await import("./LegacyStateStore-DdJ1bCRV.mjs");
7531
+ const { LegacyStateStore } = await import("./LegacyStateStore-CtwO7fwy.mjs");
7277
7532
  return new LegacyStateStore(config.provider, { warn: options.warn });
7278
7533
  }
7279
7534
  if (config.provider === "ssm") {
7280
7535
  if (!workspaceName) throw new StateStoreNeedsWorkspaceName("ssm");
7281
- const { SSMStateStore } = await import("./SSMStateStore-Bdyw_0Jj.mjs");
7536
+ const { SSMStateStore } = await import("./SSMStateStore-k_Ew7TWx.mjs");
7282
7537
  const ssm = config;
7283
7538
  return SSMStateStore.create({
7284
7539
  workspaceName,
@@ -7288,7 +7543,7 @@ async function createStateStore(options) {
7288
7543
  }
7289
7544
  if (config.provider === "s3") {
7290
7545
  if (!workspaceName) throw new StateStoreNeedsWorkspaceName("s3");
7291
- const { S3StateStore } = await import("./S3StateStore-BfoY4MvD.mjs");
7546
+ const { S3StateStore } = await import("./S3StateStore-BzS0IeSt.mjs");
7292
7547
  const s3 = config;
7293
7548
  return S3StateStore.create({
7294
7549
  workspaceName,
@@ -7336,7 +7591,7 @@ async function createDnsProvider(options) {
7336
7591
  });
7337
7592
  }
7338
7593
  if (provider === "godaddy") {
7339
- const { GoDaddyProvider } = await import("./GoDaddyProvider-BqYGk9XT.mjs");
7594
+ const { GoDaddyProvider } = await import("./GoDaddyProvider-Mi3vptSR.mjs");
7340
7595
  return new GoDaddyProvider(options.godaddy ?? {});
7341
7596
  }
7342
7597
  if (provider === "cloudflare") throw new DnsProviderNotImplemented("cloudflare");
@@ -7393,6 +7648,31 @@ function createEmptyState(stage, projectId, environmentId) {
7393
7648
  lastDeployedAt: (/* @__PURE__ */ new Date()).toISOString()
7394
7649
  };
7395
7650
  }
7651
+ /** A new compose stage's state. */
7652
+ function createComposeState(stage) {
7653
+ return {
7654
+ provider: "compose",
7655
+ stage,
7656
+ lastDeployedAt: (/* @__PURE__ */ new Date()).toISOString()
7657
+ };
7658
+ }
7659
+ /**
7660
+ * `state` as the Dokploy target keeps it. A stage whose state another target
7661
+ * wrote — one moved from compose to Dokploy — keeps its core (releases,
7662
+ * identity) and starts with no Dokploy ids, as a first deploy would.
7663
+ */
7664
+ function asDokployState(state) {
7665
+ if (state.provider === "dokploy") return state;
7666
+ const { provider: _provider, ...core } = state;
7667
+ return {
7668
+ ...core,
7669
+ provider: "dokploy",
7670
+ projectId: "",
7671
+ environmentId: "",
7672
+ applications: {},
7673
+ services: {}
7674
+ };
7675
+ }
7396
7676
  /**
7397
7677
  * Set application ID in state (mutates state)
7398
7678
  */
@@ -7412,12 +7692,13 @@ function restorable(history, index, current) {
7412
7692
  * `current`, and what was current becomes `previous`. Releasing what already
7413
7693
  * runs — a redeploy of the same digest — changes nothing.
7414
7694
  */
7415
- function recordRelease(state, appName, image, releasedAt = /* @__PURE__ */ new Date()) {
7695
+ function recordRelease(state, appName, image, by, releasedAt = /* @__PURE__ */ new Date()) {
7416
7696
  const releases = state.releases?.[appName];
7417
7697
  if (releases && sameImage(releases.current, image)) return;
7418
7698
  const entry = {
7419
7699
  ...image,
7420
- releasedAt: releasedAt.toISOString()
7700
+ releasedAt: releasedAt.toISOString(),
7701
+ releasedBy: by
7421
7702
  };
7422
7703
  const history = [entry, ...releases?.history ?? []].slice(0, 10);
7423
7704
  const previous = releases?.current;
@@ -7865,6 +8146,422 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
7865
8146
  return results;
7866
8147
  }
7867
8148
  //#endregion
8149
+ //#region src/compose/dns.ts
8150
+ /**
8151
+ * A compose stage's DNS: the records its public hosts need, and the check that
8152
+ * they resolve to its server before a certificate is asked for.
8153
+ *
8154
+ * - **The server** (`serverAddress`): the stage's own secrets hold its public
8155
+ * addresses — `GKM_SERVER_IPV4`, and `GKM_SERVER_IPV6` where it has one.
8156
+ * Reserved for gkm: never handed to an app.
8157
+ * - **The plan** (`planStackDns`, pure): every public host the stack serves —
8158
+ * its apex, each app, each file server, the public log UI — pointed at the
8159
+ * server. An A record per host (and an AAAA with an IPv6 address), or, where
8160
+ * the domain's `dns` entry says `records: { mode: 'cname', target }`, one A
8161
+ * record for the target and a CNAME to it for every other host but the
8162
+ * apex, which DNS forbids a CNAME at.
8163
+ * - **Applying it** (`applyStackDns`): through the domain's provider in `dns`,
8164
+ * a record set at a time. A record with the right value is left alone; one
8165
+ * with another value is replaced and the change printed. A `manual` domain
8166
+ * is printed and nothing else. A dry run reads, prints and writes nothing.
8167
+ * - **The check** (`checkStackDns`): each host resolved with the system
8168
+ * resolver. One that is not the server stops the deploy with
8169
+ * {@link HostNotPointingAtServer}, before Caddy or Traefik ask Let's Encrypt
8170
+ * for a certificate it cannot get — and use up the rate limit trying.
8171
+ */
8172
+ /** The line that says how to set the server's address. */
8173
+ function serverAddressHint(stage) {
8174
+ return `gkm secrets:set ${SERVER_IPV4_KEY} '<ip>' --stage ${stage}`;
8175
+ }
8176
+ /** A server address in the stage's secrets that is not an address. */
8177
+ var ServerAddressInvalid = class extends GkmError {
8178
+ stage;
8179
+ key;
8180
+ value;
8181
+ constructor(stage, key, value) {
8182
+ const ipv4 = key === SERVER_IPV4_KEY;
8183
+ super(`${key} in the '${stage}' stage's secrets is ${JSON.stringify(value)}, which is not an ${ipv4 ? "IPv4 address (like 203.0.113.10)" : "IPv6 address (like 2001:db8::10)"}. Set the server's public address: gkm secrets:set ${key} '<ip>' --stage ${stage}.`);
8184
+ this.stage = stage;
8185
+ this.key = key;
8186
+ this.value = value;
8187
+ this.name = "ServerAddressInvalid";
8188
+ }
8189
+ };
8190
+ /**
8191
+ * A compose stage that serves real domains with no server address: its hosts
8192
+ * cannot be pointed or checked, and a certificate would be asked for blind.
8193
+ */
8194
+ var ServerAddressMissing = class extends GkmError {
8195
+ stage;
8196
+ domain;
8197
+ constructor(stage, domain) {
8198
+ super(`The '${stage}' stage serves ${domain} and its secrets hold no ${SERVER_IPV4_KEY} — the public address of the server its stack runs on, which its hosts' DNS records point at and every deploy checks before asking for certificates. Set it: ${serverAddressHint(stage)} (and ${SERVER_IPV6_KEY} for an IPv6 address).`);
8199
+ this.stage = stage;
8200
+ this.domain = domain;
8201
+ this.name = "ServerAddressMissing";
8202
+ }
8203
+ };
8204
+ /**
8205
+ * The server's address from the stage's secrets, checked — `undefined` when
8206
+ * `GKM_SERVER_IPV4` is not set.
8207
+ *
8208
+ * @throws {ServerAddressInvalid} for a value that is not an address
8209
+ */
8210
+ function serverAddress(stage, custom) {
8211
+ const ipv4 = custom?.[SERVER_IPV4_KEY]?.trim();
8212
+ const ipv6 = custom?.[SERVER_IPV6_KEY]?.trim();
8213
+ if (ipv6 && !isIPv6(ipv6)) throw new ServerAddressInvalid(stage, SERVER_IPV6_KEY, ipv6);
8214
+ if (!ipv4) return void 0;
8215
+ if (!isIPv4(ipv4)) throw new ServerAddressInvalid(stage, SERVER_IPV4_KEY, ipv4);
8216
+ return {
8217
+ ipv4,
8218
+ ...ipv6 ? { ipv6 } : {}
8219
+ };
8220
+ }
8221
+ /**
8222
+ * A name that resolves to this machine by definition — `localhost` and every
8223
+ * `*.localhost` — and is in nobody's DNS.
8224
+ */
8225
+ function isLocalHost(host) {
8226
+ const name = host.toLowerCase().replace(/\.$/, "");
8227
+ return name === "localhost" || name.endsWith(".localhost");
8228
+ }
8229
+ /**
8230
+ * The server's address, required where the stage serves real domains — not
8231
+ * `*.localhost` ones, which point at this machine already.
8232
+ *
8233
+ * @throws {ServerAddressMissing} when the stage has a domain and no address
8234
+ * @throws {ServerAddressInvalid} for a value that is not an address
8235
+ */
8236
+ function requiredServerAddress(stage, domain, custom) {
8237
+ const server = serverAddress(stage, custom);
8238
+ if (!server && domain && !isLocalHost(domain)) throw new ServerAddressMissing(stage, domain);
8239
+ return server;
8240
+ }
8241
+ const MANAGED = [
8242
+ "A",
8243
+ "AAAA",
8244
+ "CNAME"
8245
+ ];
8246
+ /** Every public host the stack serves, once each, sorted. */
8247
+ function stackHosts(stack) {
8248
+ return [...new Set(stack.routes.map((route) => route.host))].sort();
8249
+ }
8250
+ /** The TTL a domain's records are written with. */
8251
+ function recordTtl(config) {
8252
+ const ttl = "ttl" in config ? config.ttl : void 0;
8253
+ if (ttl) return ttl;
8254
+ return config.provider === "godaddy" ? 600 : 300;
8255
+ }
8256
+ function providerName(config) {
8257
+ return typeof config.provider === "string" ? config.provider : "custom";
8258
+ }
8259
+ /** The records a stage's hosts need — pure. */
8260
+ function planStackDns(input) {
8261
+ const { stage, server } = input;
8262
+ const dns = input.dns ? normalizeDnsConfig(input.dns) : {};
8263
+ const records = /* @__PURE__ */ new Map();
8264
+ const uncovered = [];
8265
+ const add = (host, domain, type, value) => {
8266
+ const name = extractSubdomain(host, domain);
8267
+ records.set(`${domain} ${name} ${type}`, {
8268
+ host,
8269
+ domain,
8270
+ name,
8271
+ type,
8272
+ value,
8273
+ ttl: recordTtl(dns[domain])
8274
+ });
8275
+ };
8276
+ const pointAt = (host, domain) => {
8277
+ add(host, domain, "A", server.ipv4);
8278
+ if (server.ipv6) add(host, domain, "AAAA", server.ipv6);
8279
+ };
8280
+ for (const raw of input.hosts) {
8281
+ const host = raw.toLowerCase();
8282
+ const domain = findRootDomain(host, dns);
8283
+ if (!domain) {
8284
+ uncovered.push(host);
8285
+ continue;
8286
+ }
8287
+ const target = cnameTarget(dns[domain].records, stage);
8288
+ if (target) pointAt(target, domain);
8289
+ if (target && host !== domain && host !== target) add(host, domain, "CNAME", target);
8290
+ else pointAt(host, domain);
8291
+ }
8292
+ const domains = /* @__PURE__ */ new Map();
8293
+ for (const record of [...records.values()].sort((a, b) => a.domain.localeCompare(b.domain) || a.name.localeCompare(b.name) || a.type.localeCompare(b.type))) {
8294
+ const config = dns[record.domain];
8295
+ const plan = domains.get(record.domain) ?? {
8296
+ domain: record.domain,
8297
+ provider: providerName(config),
8298
+ config,
8299
+ records: []
8300
+ };
8301
+ plan.records.push(record);
8302
+ domains.set(record.domain, plan);
8303
+ }
8304
+ return {
8305
+ stage,
8306
+ server,
8307
+ domains: [...domains.values()],
8308
+ uncovered: uncovered.sort()
8309
+ };
8310
+ }
8311
+ /** `api.example.com A 203.0.113.10 (TTL 600)` */
8312
+ function describe(record) {
8313
+ return `${record.host.padEnd(32)} ${record.type.padEnd(5)} ${record.value} (TTL ${record.ttl})`;
8314
+ }
8315
+ function printManual(plan, log, why) {
8316
+ log(` ${plan.domain}: ${why} — create these records at its DNS host:`);
8317
+ for (const record of plan.records) log(` ${describe(record)}`);
8318
+ return plan.records.map((r) => ({
8319
+ domain: r.domain,
8320
+ name: r.name,
8321
+ type: r.type,
8322
+ action: "manual",
8323
+ value: r.value,
8324
+ ttl: r.ttl
8325
+ }));
8326
+ }
8327
+ /** The existing records of every planned name, for the types gkm manages. */
8328
+ async function existingRecords(provider, plan) {
8329
+ const names = [...new Set(plan.records.map((r) => r.name))];
8330
+ const wanted = names.flatMap((name) => MANAGED.map((type) => ({
8331
+ name,
8332
+ type
8333
+ })));
8334
+ if (provider.readRecords) return provider.readRecords(plan.domain, wanted);
8335
+ return (await provider.getRecords(plan.domain)).filter((r) => names.includes(r.name) && MANAGED.includes(r.type));
8336
+ }
8337
+ const same = (a, b) => a.trim().replace(/\.$/, "").toLowerCase() === b.trim().replace(/\.$/, "").toLowerCase();
8338
+ /**
8339
+ * What each domain's records need, compared with what is there: creates,
8340
+ * updates, and the deletes a change of mode needs — a name moving from an A
8341
+ * record to a CNAME, or back, cannot hold both. Nothing else is deleted.
8342
+ */
8343
+ function diffDomainDns(plan, existing) {
8344
+ const changes = [];
8345
+ const find = (name, type) => existing.find((r) => r.name === name && r.type === type);
8346
+ const planned = /* @__PURE__ */ new Map();
8347
+ for (const record of plan.records) {
8348
+ const types = planned.get(record.name) ?? /* @__PURE__ */ new Set();
8349
+ types.add(record.type);
8350
+ planned.set(record.name, types);
8351
+ }
8352
+ for (const [name, types] of planned) {
8353
+ const conflicting = types.has("CNAME") ? ["A", "AAAA"] : ["CNAME"];
8354
+ for (const type of conflicting) {
8355
+ const found = find(name, type);
8356
+ if (!found || found.values.length === 0) continue;
8357
+ changes.push({
8358
+ domain: plan.domain,
8359
+ name,
8360
+ type,
8361
+ action: "delete",
8362
+ previous: found.values.join(", ")
8363
+ });
8364
+ }
8365
+ }
8366
+ for (const record of plan.records) {
8367
+ const found = find(record.name, record.type);
8368
+ const base = {
8369
+ domain: plan.domain,
8370
+ name: record.name,
8371
+ type: record.type,
8372
+ value: record.value,
8373
+ ttl: record.ttl
8374
+ };
8375
+ if (!found || found.values.length === 0) changes.push({
8376
+ ...base,
8377
+ action: "create"
8378
+ });
8379
+ else if (found.values.length === 1 && same(found.values[0], record.value)) changes.push({
8380
+ ...base,
8381
+ action: "unchanged"
8382
+ });
8383
+ else changes.push({
8384
+ ...base,
8385
+ action: "update",
8386
+ previous: found.values.join(", ")
8387
+ });
8388
+ }
8389
+ return changes;
8390
+ }
8391
+ /** A host's full name from a domain and a relative name. */
8392
+ function fullName(domain, name) {
8393
+ return name === "@" ? domain : `${name}.${domain}`;
8394
+ }
8395
+ function printChange(change, dryRun, log) {
8396
+ const host = fullName(change.domain, change.name).padEnd(32);
8397
+ const type = change.type.padEnd(5);
8398
+ const would = dryRun ? "would " : "";
8399
+ switch (change.action) {
8400
+ case "unchanged":
8401
+ log(` ✓ ${host} ${type} ${change.value} (TTL ${change.ttl}) — up to date`);
8402
+ break;
8403
+ case "create":
8404
+ log(` + ${host} ${type} ${change.value} (TTL ${change.ttl}) — ${would}create`);
8405
+ break;
8406
+ case "update":
8407
+ log(` ~ ${host} ${type} ${change.previous} → ${change.value} (TTL ${change.ttl}) — ${would}update`);
8408
+ break;
8409
+ case "write":
8410
+ log(dryRun ? ` ? ${host} ${type} ${change.value} (TTL ${change.ttl}) — would write; current value unknown (key cannot read DNS)` : ` ! ${host} ${type} ${change.value} (TTL ${change.ttl}) — written (not diffed)`);
8411
+ break;
8412
+ case "delete": log(` - ${host} ${type} ${change.previous} — ${would}delete (a name cannot hold a CNAME and an A record)`);
8413
+ }
8414
+ }
8415
+ /**
8416
+ * Each domain's records, through its provider. Returns every change, the
8417
+ * unchanged included; a dry run makes none of them.
8418
+ */
8419
+ async function applyStackDns(plan, options = {}) {
8420
+ const dryRun = options.dryRun === true;
8421
+ const log = options.log ?? ((line) => console.log(line));
8422
+ const providerFor = options.providerFor ?? ((config) => createDnsProvider({
8423
+ config,
8424
+ ...options.credentials ? { godaddy: options.credentials } : {}
8425
+ }));
8426
+ const changes = [];
8427
+ for (const host of plan.uncovered) log(` ⚠ ${host} is under no domain in dns — point it at ${plan.server.ipv4} yourself`);
8428
+ for (const domain of plan.domains) {
8429
+ if (domain.provider === "manual") {
8430
+ changes.push(...printManual(domain, log, "manual"));
8431
+ continue;
8432
+ }
8433
+ const provider = await providerFor(domain.config);
8434
+ if (!provider) {
8435
+ changes.push(...printManual(domain, log, "manual"));
8436
+ continue;
8437
+ }
8438
+ log(` ${domain.domain} (${provider.name})${dryRun ? " — dry run" : ""}`);
8439
+ let existing;
8440
+ let diff;
8441
+ try {
8442
+ existing = await existingRecords(provider, domain);
8443
+ diff = diffDomainDns(domain, existing);
8444
+ } catch (error) {
8445
+ if (!(error instanceof DnsRecordsUnreadable)) throw error;
8446
+ log(` ℹ The ${provider.name} key cannot read ${domain.domain}'s DNS records, so changes cannot be diffed: every record is written (idempotent).`);
8447
+ existing = [];
8448
+ diff = domain.records.map((r) => ({
8449
+ domain: r.domain,
8450
+ name: r.name,
8451
+ type: r.type,
8452
+ action: "write",
8453
+ value: r.value,
8454
+ ttl: r.ttl
8455
+ }));
8456
+ }
8457
+ for (const change of diff) printChange(change, dryRun, log);
8458
+ if (!plan.server.ipv6) for (const name of new Set(domain.records.map((r) => r.name))) {
8459
+ const aaaa = existing.find((r) => r.name === name && r.type === "AAAA");
8460
+ const planned = domain.records.find((r) => r.name === name && r.type === "A");
8461
+ if (aaaa && planned && aaaa.values.length > 0) log(` ⚠ ${fullName(domain.domain, name)} also has AAAA ${aaaa.values.join(", ")}: set ${SERVER_IPV6_KEY} in the '${plan.stage}' stage's secrets to the server's IPv6 address, or remove that record, or IPv6 clients go elsewhere`);
8462
+ }
8463
+ changes.push(...diff);
8464
+ if (dryRun) continue;
8465
+ const deletes = diff.filter((c) => c.action === "delete");
8466
+ if (deletes.length > 0) await provider.deleteRecords(domain.domain, deletes.map((c) => ({
8467
+ name: c.name,
8468
+ type: c.type
8469
+ })));
8470
+ const writes = diff.filter((c) => c.action === "create" || c.action === "update" || c.action === "write");
8471
+ if (writes.length > 0) await provider.upsertRecords(domain.domain, writes.map((c) => ({
8472
+ name: c.name,
8473
+ type: c.type,
8474
+ value: c.value,
8475
+ ttl: c.ttl
8476
+ })));
8477
+ }
8478
+ return changes;
8479
+ }
8480
+ /** The system resolver: what the server's own clients would be told. */
8481
+ const systemLookup = async (host) => {
8482
+ return (await lookup(host, { all: true })).map((a) => a.address);
8483
+ };
8484
+ /** Hosts that do not resolve to the stage's server. */
8485
+ var HostNotPointingAtServer = class extends GkmError {
8486
+ stage;
8487
+ hosts;
8488
+ expected;
8489
+ fix;
8490
+ constructor(stage, hosts, expected, fix) {
8491
+ const want = expected.ipv6 ? `${expected.ipv4} (and ${expected.ipv6})` : expected.ipv4;
8492
+ const lines = hosts.map((h) => ` - ${h.host} → ${h.resolved.length > 0 ? h.resolved.join(", ") : `nothing${h.error ? ` (${h.error})` : ""}`}, expected ${want}`);
8493
+ super(`${hosts.length === 1 ? "A host" : `${hosts.length} hosts`} of '${stage}' do${hosts.length === 1 ? "es" : ""} not resolve to its server, so a certificate for ${hosts.length === 1 ? "it" : "them"} cannot be issued:\n${lines.join("\n")}\n${fix}\nRecords can take a few minutes to propagate. With a CDN or proxy in front of the server, pass --skip-dns-check.`);
8494
+ this.stage = stage;
8495
+ this.hosts = hosts;
8496
+ this.expected = expected;
8497
+ this.fix = fix;
8498
+ this.name = "HostNotPointingAtServer";
8499
+ }
8500
+ };
8501
+ /**
8502
+ * Every host resolved: each must resolve to the server's IPv4 address and to
8503
+ * nothing but the server's addresses.
8504
+ *
8505
+ * @throws {HostNotPointingAtServer} naming every host that does not
8506
+ */
8507
+ async function checkStackDns(input) {
8508
+ const lookup = input.lookup ?? systemLookup;
8509
+ const { server } = input;
8510
+ const ours = new Set([server.ipv4, server.ipv6].filter(Boolean).map((a) => a.toLowerCase()));
8511
+ const bad = [];
8512
+ const ok = [];
8513
+ for (const host of input.hosts) {
8514
+ let resolved;
8515
+ try {
8516
+ resolved = [...new Set(await lookup(host))];
8517
+ } catch (error) {
8518
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : error instanceof Error ? error.message : String(error);
8519
+ bad.push({
8520
+ host,
8521
+ resolved: [],
8522
+ error: code
8523
+ });
8524
+ continue;
8525
+ }
8526
+ if (resolved.some((a) => a.toLowerCase() === server.ipv4.toLowerCase()) && resolved.every((a) => ours.has(a.toLowerCase()))) ok.push(host);
8527
+ else bad.push({
8528
+ host,
8529
+ resolved
8530
+ });
8531
+ }
8532
+ if (bad.length > 0) throw new HostNotPointingAtServer(input.stage, bad, {
8533
+ ipv4: server.ipv4,
8534
+ ...server.ipv6 ? { ipv6: server.ipv6 } : {}
8535
+ }, dnsFix(input, bad));
8536
+ return ok;
8537
+ }
8538
+ /** How to fix it: `gkm setup`, or the records to create by hand. */
8539
+ function dnsFix(input, bad) {
8540
+ const hosts = bad.map((h) => h.host);
8541
+ let plan;
8542
+ try {
8543
+ plan = planStackDns({
8544
+ stage: input.stage,
8545
+ hosts,
8546
+ server: input.server,
8547
+ dns: input.dns
8548
+ });
8549
+ } catch {
8550
+ plan = {
8551
+ stage: input.stage,
8552
+ server: input.server,
8553
+ domains: [],
8554
+ uncovered: hosts
8555
+ };
8556
+ }
8557
+ const automatic = plan.domains.filter((d) => d.provider !== "manual");
8558
+ const byHand = [...plan.domains.filter((d) => d.provider === "manual").flatMap((d) => d.records.map(describe)), ...plan.uncovered.flatMap((host) => [`${host.padEnd(32)} A ${input.server.ipv4}`, ...input.server.ipv6 ? [`${host.padEnd(32)} AAAA ${input.server.ipv6}`] : []])];
8559
+ const lines = [];
8560
+ if (automatic.length > 0) lines.push(`Fix: gkm setup --stage ${input.stage} writes the records for ${automatic.map((d) => d.domain).join(", ")} (gkm setup --stage ${input.stage} --dry-run shows them first).`);
8561
+ if (byHand.length > 0) lines.push(`${automatic.length > 0 ? "And create" : "Fix: create"} these records at the DNS host:`, ...byHand.map((line) => ` ${line}`));
8562
+ return lines.join("\n");
8563
+ }
8564
+ //#endregion
7868
8565
  //#region src/docker/imageRef.ts
7869
8566
  /**
7870
8567
  * An image reference, checked before it reaches `docker`.
@@ -8257,14 +8954,9 @@ var DeployJournal = class DeployJournal {
8257
8954
  this.version = version;
8258
8955
  this.existed = existed;
8259
8956
  }
8260
- /**
8261
- * The stage's journal — its stored state, or `initial` written as the
8262
- * stage's first state, so there is somewhere to record the very first
8263
- * resource before it is created.
8264
- */
8265
- static async open(store, stage, initial) {
8957
+ static async open(store, stage, initial, adopt = (stored) => stored) {
8266
8958
  const stored = await store.read(stage);
8267
- if (stored) return new DeployJournal(store, stage, stored.state, stored.resources, stored.version, true);
8959
+ if (stored) return new DeployJournal(store, stage, adopt(stored.state), stored.resources, stored.version, true);
8268
8960
  const state = initial();
8269
8961
  const version = await store.write(stage, state, { expectedVersion: null });
8270
8962
  return new DeployJournal(store, stage, state, {}, version, false);
@@ -8283,10 +8975,13 @@ var DeployJournal = class DeployJournal {
8283
8975
  status: "pending"
8284
8976
  });
8285
8977
  }
8286
- /** Records `entry` as existing with `id`; a no-op if it already is. */
8978
+ /**
8979
+ * Records `entry` as existing with `id`; a no-op if it already is, with
8980
+ * the same data.
8981
+ */
8287
8982
  async ready(entry, id) {
8288
8983
  const current = this.resources[entry.key];
8289
- if (current?.status === "ready" && current.id === id) return;
8984
+ if (current?.status === "ready" && current.id === id && JSON.stringify(current.data) === JSON.stringify(entry.data)) return;
8290
8985
  await this.put({
8291
8986
  ...entry,
8292
8987
  status: "ready",
@@ -8331,6 +9026,17 @@ var DeployJournal = class DeployJournal {
8331
9026
  ...staleId ? { staleId } : {}
8332
9027
  };
8333
9028
  }
9029
+ /** Removes `key`'s record — a resource that no longer exists. */
9030
+ async forget(key) {
9031
+ if (!(key in this.resources)) return;
9032
+ this.version = await this.store.deleteResource(this.stage, key, { expectedVersion: this.version });
9033
+ const { [key]: _removed, ...resources } = this.resources;
9034
+ this.resources = resources;
9035
+ }
9036
+ /** Every record, as the journal last wrote or read it. */
9037
+ records() {
9038
+ return this.resources;
9039
+ }
8334
9040
  /** Writes the stage's state, keeping its resource records. */
8335
9041
  async save() {
8336
9042
  this.state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -8348,6 +9054,138 @@ var DeployJournal = class DeployJournal {
8348
9054
  }
8349
9055
  };
8350
9056
  //#endregion
9057
+ //#region src/providers/dns.ts
9058
+ /** Whether the workspace deploys through the compose target. */
9059
+ function deploysWithCompose(workspace) {
9060
+ return workspace.deploy?.default === "compose" || Object.values(workspace.apps).some((app) => app.resolvedDeployTarget === "compose");
9061
+ }
9062
+ /**
9063
+ * A compose stage that serves a domain has its server's address in its
9064
+ * secrets — checked before `gkm setup --stage` does anything else.
9065
+ *
9066
+ * @throws {ServerAddressMissing} for a compose stage with a domain and none
9067
+ * @throws {ServerAddressInvalid} for a value that is not an address
9068
+ */
9069
+ function assertStageServer(workspace, stage, secrets) {
9070
+ if (stage === workspace.stages.local) return void 0;
9071
+ if (!deploysWithCompose(workspace)) return serverAddress(stage, secrets?.custom);
9072
+ return requiredServerAddress(stage, workspace.domains?.[stage], secrets?.custom);
9073
+ }
9074
+ /**
9075
+ * The hosts a compose stage serves: its stack composed as a deploy would —
9076
+ * with the secrets it would generate made up in memory, never written — and
9077
+ * every route's host read off it.
9078
+ */
9079
+ async function composeStageHosts(options) {
9080
+ const { workspace, stage } = options;
9081
+ const runnables = options.runnables ?? {};
9082
+ const background = options.background ?? {};
9083
+ const manifest = options.manifest ?? await discover({
9084
+ patterns: constructGlobs(workspace),
9085
+ cwd: workspace.root,
9086
+ runnables,
9087
+ background
9088
+ });
9089
+ const stored = await (await secretsStoreFor(workspace, stage, {
9090
+ ...options.profile ? { profile: options.profile } : {},
9091
+ ...options.home ? { home: options.home } : {}
9092
+ })).read(stage);
9093
+ const { deployedStackSecrets } = await import("./secrets-DbPmAS4k.mjs").then((n) => n.n);
9094
+ const { composeStack } = await import("./stack-Co4uZkAo.mjs").then((n) => n.c);
9095
+ const { secrets } = deployedStackSecrets(workspace, stage, stored, manifest);
9096
+ return stackHosts(composeStack({
9097
+ workspace,
9098
+ manifest,
9099
+ runnables,
9100
+ background,
9101
+ stage,
9102
+ identity: deployIdentity(workspace, stage),
9103
+ images: {
9104
+ mode: "pull",
9105
+ tag: "dns",
9106
+ ...workspace.deploy?.registry ? { registry: workspace.deploy.registry } : {}
9107
+ },
9108
+ secrets,
9109
+ buildOnly: true,
9110
+ allowDevServices: true
9111
+ }));
9112
+ }
9113
+ /**
9114
+ * `GKM_SERVER_IPV4` from the stage's secrets and `dns`: each public host's
9115
+ * records, created or corrected. A dry run reads and prints, and writes
9116
+ * nothing.
9117
+ */
9118
+ async function provisionStageDns(options) {
9119
+ const { workspace, stage } = options;
9120
+ const log = options.log ?? ((line) => console.log(line));
9121
+ if (stage === workspace.stages.local || !workspace.dns || !deploysWithCompose(workspace)) return {
9122
+ mode: "none",
9123
+ hosts: [],
9124
+ changes: []
9125
+ };
9126
+ const server = serverAddress(stage, (await (await secretsStoreFor(workspace, stage, {
9127
+ ...options.profile ? { profile: options.profile } : {},
9128
+ ...options.home ? { home: options.home } : {}
9129
+ })).read(stage))?.custom);
9130
+ if (!server) {
9131
+ log(`\n🌐 DNS for '${stage}': no GKM_SERVER_IPV4 in its secrets, so no records — ${serverAddressHint(stage)}`);
9132
+ return {
9133
+ mode: "none",
9134
+ hosts: [],
9135
+ changes: []
9136
+ };
9137
+ }
9138
+ const hosts = [...options.hosts ?? await composeStageHosts({
9139
+ workspace,
9140
+ stage,
9141
+ ...options.profile ? { profile: options.profile } : {},
9142
+ ...options.home ? { home: options.home } : {},
9143
+ ...options.manifest ? { manifest: options.manifest } : {},
9144
+ ...options.runnables ? { runnables: options.runnables } : {},
9145
+ ...options.background ? { background: options.background } : {}
9146
+ })];
9147
+ log(`\n🌐 DNS for '${stage}' → ${server.ipv4}${server.ipv6 ? ` / ${server.ipv6}` : ""}${options.dryRun ? " (dry run — nothing is written)" : ""}`);
9148
+ const plan = planStackDns({
9149
+ stage,
9150
+ hosts,
9151
+ server,
9152
+ dns: workspace.dns
9153
+ });
9154
+ try {
9155
+ const changes = await applyStackDns(plan, {
9156
+ ...options.dryRun ? { dryRun: true } : {},
9157
+ log,
9158
+ credentials: {
9159
+ env: options.env ?? process.env,
9160
+ ...options.home ? { home: options.home } : {},
9161
+ log
9162
+ },
9163
+ ...options.providerFor ? { providerFor: options.providerFor } : {}
9164
+ });
9165
+ if (options.journal && !options.dryRun) {
9166
+ const providers = new Map(plan.domains.map((domain) => [domain.domain, domain.provider]));
9167
+ await recordDnsChanges(options.journal, changes, (domain) => providers.get(domain));
9168
+ }
9169
+ return {
9170
+ mode: "records",
9171
+ hosts,
9172
+ changes
9173
+ };
9174
+ } catch (error) {
9175
+ if (error instanceof MissingCredential) {
9176
+ log(` ⚠ ${error.message}`);
9177
+ log(" Until then, create these records yourself:");
9178
+ for (const domain of plan.domains) for (const r of domain.records) log(` ${r.host.padEnd(32)} ${r.type.padEnd(5)} ${r.value} (TTL ${r.ttl})`);
9179
+ return {
9180
+ mode: "records",
9181
+ hosts,
9182
+ changes: []
9183
+ };
9184
+ }
9185
+ throw error;
9186
+ }
9187
+ }
9188
+ //#endregion
8351
9189
  //#region src/providers/aws.ts
8352
9190
  /**
8353
9191
  * The AWS account a provider provisions in — the stage's, found the way the
@@ -9319,7 +10157,7 @@ async function provisionStage(options) {
9319
10157
  });
9320
10158
  const lock = dryRun ? void 0 : await stateStore.lock(stage, { operation: "provision" });
9321
10159
  try {
9322
- const state = dryRun ? await readOnlyState(stateStore, stage) : journalState(await DeployJournal.open(stateStore, stage, () => createEmptyState(stage, "", "")));
10160
+ const state = dryRun ? await readOnlyState(stateStore, stage) : journalState(await DeployJournal.open(stateStore, stage, () => deploysWithCompose(workspace) ? createComposeState(stage) : createEmptyState(stage, "", "")));
9323
10161
  const writeSecrets = async (values) => {
9324
10162
  if (dryRun) return;
9325
10163
  const stored = await secretsStore.read(stage) ?? initStageSecrets(stage);
@@ -9534,6 +10372,6 @@ function reportPlannedSeeds(planned, log) {
9534
10372
  for (const { folder, seeds } of planned) log(`🌱 ${folder}: would run ${seeds.length} (${seeds.join(", ")})`);
9535
10373
  }
9536
10374
  //#endregion
9537
- export { externalServices as $, LOCAL_LOGS_EMAIL as $n, isImageBuild as $t, setApplicationId as A, prepareEntryCredentials as An, applyPolicies as At, StateStoreWithoutLocking as B, applicationName as Bn, appEnvKeys as Bt, orchestrateDns as C, createCredentialsPreload as Cn, redisService as Ct, getGeneratedSecret as D, loadEnvFiles as Dn, LOCAL_CA_PATH as Dt, getBackupState as E, isPortAvailable as En, withRedisPassword as Et, createDnsProvider as F, FileSecretsStore as Fn, quoteIdentifier as Ft, AllowDevServicesTakesNoValue as G, markerOf as Gn, localRolePassword as Gt, contentVersion as H, imageName as Hn, networkEnv as Ht, DocumentStateStore as I, getKeyPath as In, LEGACY_LOCAL_LOGIN as It, ExternalServicesNotConfigured as J, initStageSecrets as Jn, TURBO_VERSION as Jt, DEV_SERVICES as K, ownershipMarker as Kn, composeBuildPaths as Kt, StageStateMissing as L, keystoreProject as Ln, ensurePostgresLogin as Lt, setGeneratedSecret as M, secretsStoreFor as Mn, bucketPolicies as Mt, setPostgresBackupId as N, storeFromConfig as Nn, postgresDatabaseNames as Nt, recordRelease as O, loadPortState as On, forgetState as Ot, DnsRecordsUnreadable as P, store_exports as Pn, postgresStatements as Pt, devServicesUsed as Q, withCustomSecret as Qn, imageBuildCredentials as Qt, StateLocked as R, projectKey as Rn, bucketClient as Rt, normalizeDnsConfig as S, primaryPortKey as Sn, STACK_CACHE as St, createEmptyState as T, findAvailablePort as Tn, stackRedis as Tt, createStateStore as U, imageRef as Un, workerEnvKeys as Ut, StateVersionConflict as V, deployIdentity as Vn, dockerfileOf as Vt, newLockHolder as W, isOwnedBy as Wn, envFor as Wt, assertDevServicesFlag as X, storage_exports as Xn, detectPackageManager as Xt, STORAGE_KEY_PAIR as Y, maskPassword as Yn, WORKER_PORT as Yt, assertExternalServices as Z, toEmbeddableSecrets as Zn, findBuildRoot as Zt, workerDockerfile as _, DEFAULT_IMAGES as _n, loadLocalCredentials as _t, provisionStage as a, composeFor as an, NoDomainForStage as ar, WorkerGenerator as at, extractSubdomain as b, portKeys as bn, withGeneratedSecrets as bt, StageProviderDisabled as c, planFor as cn, QueueGenerator as ct, stageProviderNotes as d, appTelemetry as dn, backendsOf as dt, cacheBackendFor as en, LOGS_SERVICE as er, manifestServiceDeclarations as et, DockerBuildFailed as f, isTelemetryKey as fn, constructGlobs as ft, ensureDockerignore as g, usesTelemetry as gn, LOCAL_RABBITMQ_USER as gt, dockerCommand as h, telemetryOf as hn, workspace_exports as ht, assertNoStaleSecrets as i, CLUSTER_DATABASE as in, withLogsPassword as ir, workerUnits as it, setBackupState as j, isRemoteStore as jn, applyPostgres as jt, recordRollback as k, loadSecretsForApp as kn, applyBuckets as kt, assertStageProvidersEnabled as l, resourceName as ln, CronGenerator as lt, appDockerfile as m, surfaceClientTraced as mn, reconcileWorkspace as mt, reportDatabaseRuns as n, providerOf as nn, logsService as nr, requiredStageKeys as nt, verifyStageProviders as o, EMULATOR_ACCESS_KEY_ID as on, isMainFrontendApp as or, workerBundleName as ot, DockerPushFailed as p, scopeTelemetryEnv as pn, derivedContainers as pt, DevServicesNeedServerTarget as q, projectName as qn, imageLayout as qt, reportPlannedSeeds as r, storageBackendFor as rn, stackLogs as rr, suppliedOnly as rt, DeployJournal as s, TELEMETRY_CONTAINER as sn, resolveHost as sr, workerEntryDir as st, DeploySeedsFailed as t, eventsBackendFor as tn, logsAccess as tr, reportDevServices as tt, provisionCommand as u, hostFor as un, FAKE_ENV as ut, workerDockerfileOf as v, describeServices as vn, postgresSuperuser as vt, verifyDnsRecords as w, createEntryWrapper as wn, runsRedis as wt, findRootDomain as x, portsOf as xn, REDIS_SERVICE as xt, validateImageRef as y, fakeKey as yn, readLocalCredentials as yt, StateStoreBusy as z, readKey as zn, pgClient as zt };
10375
+ export { contentVersion as $, projectKey as $n, bucketClient as $t, orchestrateDns as A, telemetryOf as An, workspace_exports as At, setBackupState as B, findAvailablePort as Bn, stackRedis as Bt, HostNotPointingAtServer as C, planFor as Cn, QueueGenerator as Ct, serverAddressHint as D, isTelemetryKey as Dn, constructGlobs as Dt, requiredServerAddress as E, appTelemetry as En, backendsOf as Et, getBackupState as F, portKeys as Fn, withGeneratedSecrets as Ft, LocalStateInCi as G, prepareEntryCredentials as Gn, applyPolicies as Gt, setPostgresBackupId as H, loadEnvFiles as Hn, LOCAL_CA_PATH as Ht, getGeneratedSecret as I, portsOf as In, REDIS_SERVICE as It, StateStoreBusy as J, storeFromConfig as Jn, postgresDatabaseNames as Jt, StageStateMissing as K, isRemoteStore as Kn, applyPostgres as Kt, recordRelease as L, primaryPortKey as Ln, STACK_CACHE as Lt, asDokployState as M, DEFAULT_IMAGES as Mn, loadLocalCredentials as Mt, createComposeState as N, describeServices as Nn, postgresSuperuser as Nt, stackHosts as O, scopeTelemetryEnv as On, derivedContainers as Ot, createEmptyState as P, fakeKey as Pn, readLocalCredentials as Pt, assertStateOutlivesRun as Q, keystoreProject as Qn, ensurePostgresLogin as Qt, recordRollback as R, createCredentialsPreload as Rn, redisService as Rt, validateImageRef as S, TELEMETRY_CONTAINER as Sn, resolveHost as Sr, workerEntryDir as St, isLocalHost as T, hostFor as Tn, FAKE_ENV as Tt, DnsRecordsUnreadable as U, loadPortState as Un, forgetState as Ut, setGeneratedSecret as V, isPortAvailable as Vn, withRedisPassword as Vt, DocumentStateStore as W, loadSecretsForApp as Wn, applyBuckets as Wt, StateVersionConflict as X, FileSecretsStore as Xn, quoteIdentifier as Xt, StateStoreWithoutLocking as Y, store_exports as Yn, postgresStatements as Yt, appendHistory as Z, getKeyPath as Zn, LEGACY_LOCAL_LOGIN as Zt, appDockerfile as _, providerOf as _n, logsService as _r, requiredStageKeys as _t, provisionStage as a, envFor as an, isOwnedBy as ar, describeActor as at, workerDockerfile as b, composeFor as bn, NoDomainForStage as br, WorkerGenerator as bt, deploysWithCompose as c, imageLayout as cn, projectName as cr, DevServicesNeedServerTarget as ct, StageProviderDisabled as d, detectPackageManager as dn, storage_exports as dr, assertDevServicesFlag as dt, pgClient as en, readKey as er, createStateStore as et, assertStageProvidersEnabled as f, findBuildRoot as fn, toEmbeddableSecrets as fr, assertExternalServices as ft, DockerPushFailed as g, eventsBackendFor as gn, logsAccess as gr, reportDevServices as gt, DockerBuildFailed as h, cacheBackendFor as hn, LOGS_SERVICE as hr, manifestServiceDeclarations as ht, assertNoStaleSecrets as i, workerEnvKeys as in, imageRef as ir, currentActor as it, verifyDnsRecords as j, usesTelemetry as jn, LOCAL_RABBITMQ_USER as jt, systemLookup as k, surfaceClientTraced as kn, reconcileWorkspace as kt, provisionStageDns as l, TURBO_VERSION as ln, initStageSecrets as lr, ExternalServicesNotConfigured as lt, stageProviderNotes as m, isImageBuild as mn, LOCAL_LOGS_EMAIL as mr, externalServices as mt, reportDatabaseRuns as n, dockerfileOf as nn, deployIdentity as nr, upgradeV2State as nt, verifyStageProviders as o, localRolePassword as on, markerOf as or, AllowDevServicesTakesNoValue as ot, provisionCommand as p, imageBuildCredentials as pn, withCustomSecret as pr, devServicesUsed as pt, StateLocked as q, secretsStoreFor as qn, bucketPolicies as qt, reportPlannedSeeds as r, networkEnv as rn, imageName as rr, dnsResources as rt, assertStageServer as s, composeBuildPaths as sn, ownershipMarker as sr, DEV_SERVICES as st, DeploySeedsFailed as t, appEnvKeys as tn, applicationName as tr, newLockHolder as tt, DeployJournal as u, WORKER_PORT as un, maskPassword as ur, STORAGE_KEY_PAIR as ut, dockerCommand as v, storageBackendFor as vn, stackLogs as vr, suppliedOnly as vt, checkStackDns as w, resourceName as wn, CronGenerator as wt, workerDockerfileOf as x, EMULATOR_ACCESS_KEY_ID as xn, isMainFrontendApp as xr, workerBundleName as xt, ensureDockerignore as y, CLUSTER_DATABASE as yn, withLogsPassword as yr, workerUnits as yt, setApplicationId as z, createEntryWrapper as zn, runsRedis as zt };
9538
10376
 
9539
- //# sourceMappingURL=seeds-CvGQJnPO.mjs.map
10377
+ //# sourceMappingURL=seeds-BSL55F6q.mjs.map