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

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 (175) hide show
  1. package/dist/{GoDaddyProvider-D0MQoOM4.mjs → GoDaddyProvider-BlB5nJky.mjs} +2 -2
  2. package/dist/{GoDaddyProvider-D0MQoOM4.mjs.map → GoDaddyProvider-BlB5nJky.mjs.map} +1 -1
  3. package/dist/{GoDaddyProvider-3C8U_tVu.cjs → GoDaddyProvider-Dubq-pCc.cjs} +2 -2
  4. package/dist/{GoDaddyProvider-3C8U_tVu.cjs.map → GoDaddyProvider-Dubq-pCc.cjs.map} +1 -1
  5. package/dist/{LegacyStateStore-DHsoQGOo.cjs → LegacyStateStore-ClvUC8qy.cjs} +3 -3
  6. package/dist/{LegacyStateStore-DHsoQGOo.cjs.map → LegacyStateStore-ClvUC8qy.cjs.map} +1 -1
  7. package/dist/{LegacyStateStore-CRi7TosX.mjs → LegacyStateStore-DGALQGZu.mjs} +3 -3
  8. package/dist/{LegacyStateStore-CRi7TosX.mjs.map → LegacyStateStore-DGALQGZu.mjs.map} +1 -1
  9. package/dist/{LocalStateStore-BQXTkeoS.cjs → LocalStateStore-43kSYNpw.cjs} +2 -2
  10. package/dist/{LocalStateStore-BQXTkeoS.cjs.map → LocalStateStore-43kSYNpw.cjs.map} +1 -1
  11. package/dist/{LocalStateStore-Dkth0jXX.mjs → LocalStateStore-CBYUzVR5.mjs} +2 -2
  12. package/dist/{LocalStateStore-Dkth0jXX.mjs.map → LocalStateStore-CBYUzVR5.mjs.map} +1 -1
  13. package/dist/{S3StateStore-pZZnLLBd.mjs → S3StateStore-DLHjDYYP.mjs} +2 -2
  14. package/dist/{S3StateStore-pZZnLLBd.mjs.map → S3StateStore-DLHjDYYP.mjs.map} +1 -1
  15. package/dist/{S3StateStore-DGKZgyKz.cjs → S3StateStore-ahE7hSNK.cjs} +2 -2
  16. package/dist/{S3StateStore-DGKZgyKz.cjs.map → S3StateStore-ahE7hSNK.cjs.map} +1 -1
  17. package/dist/{SSMStateStore-CZRMERDA.mjs → SSMStateStore-j5RuMkch.mjs} +2 -2
  18. package/dist/{SSMStateStore-CZRMERDA.mjs.map → SSMStateStore-j5RuMkch.mjs.map} +1 -1
  19. package/dist/{SSMStateStore-DzkyjQ5D.cjs → SSMStateStore-qIzIuazA.cjs} +2 -2
  20. package/dist/{SSMStateStore-DzkyjQ5D.cjs.map → SSMStateStore-qIzIuazA.cjs.map} +1 -1
  21. package/dist/{add-C0kXOsWS.mjs → add-Bm-C56lH.mjs} +4 -4
  22. package/dist/{add-C0kXOsWS.mjs.map → add-Bm-C56lH.mjs.map} +1 -1
  23. package/dist/{add-Df8cmz0H.cjs → add-C2CvXrYw.cjs} +4 -4
  24. package/dist/{add-Df8cmz0H.cjs.map → add-C2CvXrYw.cjs.map} +1 -1
  25. package/dist/{bundler-Dx_grUmc.cjs → bundler-Dk7tAvAX.cjs} +5 -5
  26. package/dist/{bundler-Dx_grUmc.cjs.map → bundler-Dk7tAvAX.cjs.map} +1 -1
  27. package/dist/{bundler-B-I4Du99.mjs → bundler-DnkFt4mn.mjs} +5 -5
  28. package/dist/{bundler-B-I4Du99.mjs.map → bundler-DnkFt4mn.mjs.map} +1 -1
  29. package/dist/{cli-DL7GxGAe.mjs → cli-CVeHFXNE.mjs} +2 -2
  30. package/dist/{cli-DL7GxGAe.mjs.map → cli-CVeHFXNE.mjs.map} +1 -1
  31. package/dist/{cli-Cd9exatJ.cjs → cli-Di464j1O.cjs} +2 -2
  32. package/dist/{cli-Cd9exatJ.cjs.map → cli-Di464j1O.cjs.map} +1 -1
  33. package/dist/{compose-Mdgea4Yt.cjs → compose-ChVvb3xM.cjs} +65 -13
  34. package/dist/compose-ChVvb3xM.cjs.map +1 -0
  35. package/dist/{compose-C43c03WZ.mjs → compose-cHkF---f.mjs} +65 -13
  36. package/dist/compose-cHkF---f.mjs.map +1 -0
  37. package/dist/{config-D48erO2j.d.mts → config-B3iz9BtK.d.cts} +4 -4
  38. package/dist/{config-D48erO2j.d.mts.map → config-B3iz9BtK.d.cts.map} +1 -1
  39. package/dist/{config-DHDtsQrB.d.cts → config-DrOX29sT.d.mts} +4 -4
  40. package/dist/{config-DHDtsQrB.d.cts.map → config-DrOX29sT.d.mts.map} +1 -1
  41. package/dist/config.cjs +1 -1
  42. package/dist/config.d.cts +3 -3
  43. package/dist/config.d.mts +3 -3
  44. package/dist/config.mjs +1 -1
  45. package/dist/deploy/public.cjs +7 -7
  46. package/dist/deploy/public.d.cts +5 -5
  47. package/dist/deploy/public.d.cts.map +1 -1
  48. package/dist/deploy/public.d.mts +5 -5
  49. package/dist/deploy/public.d.mts.map +1 -1
  50. package/dist/deploy/public.mjs +7 -7
  51. package/dist/{deploy-BMM2hx-K.cjs → deploy-BffR_uEN.cjs} +7 -6
  52. package/dist/{deploy-BMM2hx-K.cjs.map → deploy-BffR_uEN.cjs.map} +1 -1
  53. package/dist/{deploy-D5YhwDng.mjs → deploy-DO4djCHb.mjs} +7 -6
  54. package/dist/{deploy-D5YhwDng.mjs.map → deploy-DO4djCHb.mjs.map} +1 -1
  55. package/dist/{discover-CSW_BMWa.d.cts → discover-C08meHhu.d.mts} +2 -2
  56. package/dist/{discover-CSW_BMWa.d.cts.map → discover-C08meHhu.d.mts.map} +1 -1
  57. package/dist/{discover-xtT7fgWg.d.mts → discover-DipvfWpK.d.cts} +2 -2
  58. package/dist/{discover-xtT7fgWg.d.mts.map → discover-DipvfWpK.d.cts.map} +1 -1
  59. package/dist/{dns-BIYf20p7.cjs → dns-Bmnuttja.cjs} +346 -27
  60. package/dist/dns-Bmnuttja.cjs.map +1 -0
  61. package/dist/{dns-agf2VCdV.mjs → dns-DFvO5a53.mjs} +317 -30
  62. package/dist/dns-DFvO5a53.mjs.map +1 -0
  63. package/dist/{dokploy-2p4PK6ej.mjs → dokploy-BcZm93Cc.mjs} +2 -2
  64. package/dist/{dokploy-2p4PK6ej.mjs.map → dokploy-BcZm93Cc.mjs.map} +1 -1
  65. package/dist/{dokploy-BANMTroy.cjs → dokploy-CsdvfH3t.cjs} +2 -2
  66. package/dist/{dokploy-BANMTroy.cjs.map → dokploy-CsdvfH3t.cjs.map} +1 -1
  67. package/dist/{eas-iT2JDHCh.mjs → eas-CBShP20v.mjs} +2 -2
  68. package/dist/{eas-iT2JDHCh.mjs.map → eas-CBShP20v.mjs.map} +1 -1
  69. package/dist/{eas-CNi16SoO.cjs → eas-CN_cUXth.cjs} +2 -2
  70. package/dist/{eas-CNi16SoO.cjs.map → eas-CN_cUXth.cjs.map} +1 -1
  71. package/dist/{encryption-DkXVbyZc.mjs → encryption-CITa6dXL.mjs} +3 -3
  72. package/dist/{encryption-DkXVbyZc.mjs.map → encryption-CITa6dXL.mjs.map} +1 -1
  73. package/dist/{encryption-d2lLtkYx.cjs → encryption-CPDM49Hi.cjs} +3 -3
  74. package/dist/{encryption-d2lLtkYx.cjs.map → encryption-CPDM49Hi.cjs.map} +1 -1
  75. package/dist/{engine-CatKW6E-.mjs → engine-CTnHmHjz.mjs} +4 -4
  76. package/dist/{engine-CatKW6E-.mjs.map → engine-CTnHmHjz.mjs.map} +1 -1
  77. package/dist/{engine-DlEHexED.cjs → engine-D-jfMVj-.cjs} +4 -4
  78. package/dist/{engine-DlEHexED.cjs.map → engine-D-jfMVj-.cjs.map} +1 -1
  79. package/dist/{github-DErsqKGN.mjs → github-9RlA7EjN.mjs} +5 -5
  80. package/dist/{github-DErsqKGN.mjs.map → github-9RlA7EjN.mjs.map} +1 -1
  81. package/dist/{github-BAeaC1Pl.cjs → github-D56PSK39.cjs} +5 -5
  82. package/dist/{github-BAeaC1Pl.cjs.map → github-D56PSK39.cjs.map} +1 -1
  83. package/dist/{googlePlay-BXJ5ZjMK.mjs → googlePlay-COi5LXON.mjs} +3 -3
  84. package/dist/{googlePlay-BXJ5ZjMK.mjs.map → googlePlay-COi5LXON.mjs.map} +1 -1
  85. package/dist/{googlePlay-BVcfFWRO.cjs → googlePlay-SX_jQeJf.cjs} +3 -3
  86. package/dist/{googlePlay-BVcfFWRO.cjs.map → googlePlay-SX_jQeJf.cjs.map} +1 -1
  87. package/dist/{index-8OfpOtSI.d.cts → index-DcyRA5Rf.d.mts} +2 -2
  88. package/dist/{index-8OfpOtSI.d.cts.map → index-DcyRA5Rf.d.mts.map} +1 -1
  89. package/dist/{index-Dpji6Bxq.d.mts → index-DjLSMgAL.d.cts} +2 -2
  90. package/dist/{index-Dpji6Bxq.d.mts.map → index-DjLSMgAL.d.cts.map} +1 -1
  91. package/dist/index.cjs +53 -53
  92. package/dist/index.cjs.map +1 -1
  93. package/dist/index.mjs +53 -53
  94. package/dist/index.mjs.map +1 -1
  95. package/dist/{migrate-s4QizIoM.mjs → migrate-0J6gsfrQ.mjs} +3 -3
  96. package/dist/{migrate-s4QizIoM.mjs.map → migrate-0J6gsfrQ.mjs.map} +1 -1
  97. package/dist/{migrate-L4a273dY.cjs → migrate-BCz1lkh_.cjs} +3 -3
  98. package/dist/{migrate-L4a273dY.cjs.map → migrate-BCz1lkh_.cjs.map} +1 -1
  99. package/dist/{migrate-D79GI0XM.cjs → migrate-CrtxXeGY.cjs} +3 -3
  100. package/dist/{migrate-D79GI0XM.cjs.map → migrate-CrtxXeGY.cjs.map} +1 -1
  101. package/dist/{migrate-Bb1MmGHl.mjs → migrate-DJa_Dlsl.mjs} +3 -3
  102. package/dist/{migrate-Bb1MmGHl.mjs.map → migrate-DJa_Dlsl.mjs.map} +1 -1
  103. package/dist/openapi.cjs +1 -1
  104. package/dist/openapi.d.cts +1 -1
  105. package/dist/openapi.d.mts +1 -1
  106. package/dist/openapi.mjs +1 -1
  107. package/dist/{publish-CcTlmJT6.mjs → publish-CuQlOW5p.mjs} +2 -2
  108. package/dist/{publish-CcTlmJT6.mjs.map → publish-CuQlOW5p.mjs.map} +1 -1
  109. package/dist/{publish-ydscCYsW.cjs → publish-qSpnvmdX.cjs} +2 -2
  110. package/dist/{publish-ydscCYsW.cjs.map → publish-qSpnvmdX.cjs.map} +1 -1
  111. package/dist/reconcile/public.d.cts +2 -2
  112. package/dist/reconcile/public.d.mts +2 -2
  113. package/dist/{resolve-BgL-brF9.cjs → resolve-AsfohXd1.cjs} +3 -3
  114. package/dist/{resolve-BgL-brF9.cjs.map → resolve-AsfohXd1.cjs.map} +1 -1
  115. package/dist/{resolve-BLp2trep.mjs → resolve-j8hUcbGz.mjs} +3 -3
  116. package/dist/{resolve-BLp2trep.mjs.map → resolve-j8hUcbGz.mjs.map} +1 -1
  117. package/dist/{rollback-2cyLXEH1.cjs → rollback-Bxq_URgL.cjs} +5 -5
  118. package/dist/{rollback-2cyLXEH1.cjs.map → rollback-Bxq_URgL.cjs.map} +1 -1
  119. package/dist/{rollback-DwqZ8zzj.mjs → rollback-JgbAY43C.mjs} +5 -5
  120. package/dist/{rollback-DwqZ8zzj.mjs.map → rollback-JgbAY43C.mjs.map} +1 -1
  121. package/dist/{runtime-YyQuekxi.d.mts → runtime-DIZTeGJV.d.cts} +2 -2
  122. package/dist/{runtime-ACdfK59n.d.cts.map → runtime-DIZTeGJV.d.cts.map} +1 -1
  123. package/dist/{runtime-ACdfK59n.d.cts → runtime-WMLJY-db.d.mts} +2 -2
  124. package/dist/{runtime-YyQuekxi.d.mts.map → runtime-WMLJY-db.d.mts.map} +1 -1
  125. package/dist/{s3-DQXZdmCM.mjs → s3-B4FTQQkV.mjs} +2 -2
  126. package/dist/{s3-DQXZdmCM.mjs.map → s3-B4FTQQkV.mjs.map} +1 -1
  127. package/dist/{s3-CrPoA89T.cjs → s3-DwKLXwUT.cjs} +2 -2
  128. package/dist/{s3-CrPoA89T.cjs.map → s3-DwKLXwUT.cjs.map} +1 -1
  129. package/dist/{sandbox-CdKgQ3XU.d.cts → sandbox-Co1y2CmF.d.mts} +2 -2
  130. package/dist/{sandbox-C4g1eG31.d.mts.map → sandbox-Co1y2CmF.d.mts.map} +1 -1
  131. package/dist/{sandbox-C4g1eG31.d.mts → sandbox-DvNqr6od.d.cts} +2 -2
  132. package/dist/{sandbox-CdKgQ3XU.d.cts.map → sandbox-DvNqr6od.d.cts.map} +1 -1
  133. package/dist/{seeds-C2w4UqVP.cjs → seeds-BWC-qjSI.cjs} +11 -5
  134. package/dist/{seeds-C2w4UqVP.cjs.map → seeds-BWC-qjSI.cjs.map} +1 -1
  135. package/dist/{seeds-Cwtv-U9-.mjs → seeds-Dv78P9LS.mjs} +6 -6
  136. package/dist/{seeds-Cwtv-U9-.mjs.map → seeds-Dv78P9LS.mjs.map} +1 -1
  137. package/dist/{serverSetup-BdFOD93s.cjs → serverSetup-CtV80a5M.cjs} +6 -6
  138. package/dist/{serverSetup-BdFOD93s.cjs.map → serverSetup-CtV80a5M.cjs.map} +1 -1
  139. package/dist/{serverSetup-utKsoyGH.mjs → serverSetup-DB6sKwAy.mjs} +6 -6
  140. package/dist/{serverSetup-utKsoyGH.mjs.map → serverSetup-DB6sKwAy.mjs.map} +1 -1
  141. package/dist/{sniffer-CF7wNxMo.cjs → sniffer-ctpwBBkT.cjs} +7 -7
  142. package/dist/{sniffer-CF7wNxMo.cjs.map → sniffer-ctpwBBkT.cjs.map} +1 -1
  143. package/dist/{sniffer-8C4q6Szd.mjs → sniffer-qDFUQmWP.mjs} +7 -7
  144. package/dist/{sniffer-8C4q6Szd.mjs.map → sniffer-qDFUQmWP.mjs.map} +1 -1
  145. package/dist/{stage-BvM_u2z4.mjs → stage-C7lf36az.mjs} +5 -5
  146. package/dist/{stage-BvM_u2z4.mjs.map → stage-C7lf36az.mjs.map} +1 -1
  147. package/dist/{stage-DEQJGW3r.cjs → stage-DzALYUwY.cjs} +5 -5
  148. package/dist/{stage-DEQJGW3r.cjs.map → stage-DzALYUwY.cjs.map} +1 -1
  149. package/dist/{stages-D4SnG5yl.cjs → stages-B7mCWI_R.cjs} +2 -2
  150. package/dist/{stages-D4SnG5yl.cjs.map → stages-B7mCWI_R.cjs.map} +1 -1
  151. package/dist/{stages-Cb9rqRbN.mjs → stages-DSltTW2d.mjs} +2 -2
  152. package/dist/{stages-Cb9rqRbN.mjs.map → stages-DSltTW2d.mjs.map} +1 -1
  153. package/dist/target/public.cjs +1 -1
  154. package/dist/target/public.d.cts +2 -2
  155. package/dist/target/public.d.mts +2 -2
  156. package/dist/target/public.mjs +1 -1
  157. package/dist/{types-B6UXlEkf.d.cts → types-C8gXOLWL.d.mts} +22 -3
  158. package/dist/{types-HqVPddRK.d.mts.map → types-C8gXOLWL.d.mts.map} +1 -1
  159. package/dist/{types-HqVPddRK.d.mts → types-Q2SC_ly2.d.cts} +23 -2
  160. package/dist/{types-B6UXlEkf.d.cts.map → types-Q2SC_ly2.d.cts.map} +1 -1
  161. package/dist/workspace/index.cjs +1 -1
  162. package/dist/workspace/index.d.cts +2 -2
  163. package/dist/workspace/index.d.mts +2 -2
  164. package/dist/workspace/index.mjs +1 -1
  165. package/dist/{workspace-BwqQnool.cjs → workspace-C82GLPnf.cjs} +8 -2
  166. package/dist/workspace-C82GLPnf.cjs.map +1 -0
  167. package/dist/{workspace-D1QbNd55.mjs → workspace-DUnnVjqC.mjs} +8 -2
  168. package/dist/workspace-DUnnVjqC.mjs.map +1 -0
  169. package/package.json +17 -16
  170. package/dist/compose-C43c03WZ.mjs.map +0 -1
  171. package/dist/compose-Mdgea4Yt.cjs.map +0 -1
  172. package/dist/dns-BIYf20p7.cjs.map +0 -1
  173. package/dist/dns-agf2VCdV.mjs.map +0 -1
  174. package/dist/workspace-BwqQnool.cjs.map +0 -1
  175. package/dist/workspace-D1QbNd55.mjs.map +0 -1
@@ -1,13 +1,15 @@
1
1
  import { t as GkmError } from "./errors-D8Gwp5kU.mjs";
2
- import { dt as checkStageBackups, ft as SERVER_IPV4_KEY, mt as cnameTarget, pt as SERVER_IPV6_KEY } from "./workspace-D1QbNd55.mjs";
3
- import { D as DnsRecordsUnreadable, Kn as postgresImage, O as createDnsProvider, d as extractSubdomain, f as findRootDomain, kn as localRolePassword, ln as quoteIdentifier, p as normalizeDnsConfig, pn as postgresRoleSlug } from "./seeds-Cwtv-U9-.mjs";
2
+ import { dt as checkStageBackups, ft as SERVER_IPV4_KEY, mt as cnameTarget, pt as SERVER_IPV6_KEY } from "./workspace-DUnnVjqC.mjs";
3
+ import { An as localRolePassword, O as DnsRecordsUnreadable, f as extractSubdomain, k as createDnsProvider, m as normalizeDnsConfig, mn as postgresRoleSlug, p as findRootDomain, qn as postgresImage, un as quoteIdentifier } from "./seeds-Dv78P9LS.mjs";
4
+ import { t as appKey } from "./derive-B4vDpzgP.mjs";
4
5
  import { createRequire } from "node:module";
5
6
  import { dirname, join } from "node:path";
6
7
  import { existsSync, readFileSync } from "node:fs";
7
8
  import { isIPv4, isIPv6 } from "node:net";
8
- import { createHash } from "node:crypto";
9
- import { DEFAULT_POSTGRES_VERSION, kebabCase } from "@geekmidas/manifest";
9
+ import { createHash, randomBytes } from "node:crypto";
10
+ import { DEFAULT_POSTGRES_VERSION, environmentCase, kebabCase } from "@geekmidas/manifest";
10
11
  import { fileURLToPath } from "node:url";
12
+ import bcrypt from "bcryptjs";
11
13
  import { lookup } from "node:dns/promises";
12
14
  //#region src/providers/dns.ts
13
15
  /** Whether the workspace deploys through the compose target. */
@@ -319,6 +321,153 @@ function backupsRoleStatements(backups) {
319
321
  ];
320
322
  }
321
323
  //#endregion
324
+ //#region src/compose/sitePassword.ts
325
+ /**
326
+ * A deployed stage's sites behind a password — `deploy.sitePassword` — so a
327
+ * staging build is not public while it is being tried.
328
+ *
329
+ * The edge asks for it with HTTP basic auth, before a request reaches the
330
+ * site: the browser prompts, and nothing of the site loads until the stage's
331
+ * login is given. The user is the stage's name; the password is generated
332
+ * once per site and kept in the stage's secrets, where one set by hand wins.
333
+ * Only its bcrypt hash reaches the edge's config.
334
+ *
335
+ * Pure.
336
+ */
337
+ /** `deploy.sitePassword` names something that is not one of the stage's sites. */
338
+ var UnknownPasswordSite = class extends GkmError {
339
+ stage;
340
+ site;
341
+ sites;
342
+ constructor(stage, site, sites) {
343
+ super(`deploy.sitePassword.${stage} names '${site}', which is not a site of this workspace. ${sites.length > 0 ? `Its sites: ${sites.map((s) => `'${s}'`).join(", ")}.` : "It declares no site."} Name one of them, or set ${stage}: true for every site.`);
344
+ this.stage = stage;
345
+ this.site = site;
346
+ this.sites = sites;
347
+ this.name = "UnknownPasswordSite";
348
+ }
349
+ };
350
+ /** A site the stage puts behind a password, with none in the stage's secrets. */
351
+ var SitePasswordMissing = class extends GkmError {
352
+ stage;
353
+ key;
354
+ constructor(stage, key) {
355
+ super(`deploy.sitePassword puts a site of '${stage}' behind a password, and the stage has no ${key} in its secrets. Run gkm compose --stage ${stage} once to generate it, or set one: gkm secrets:set ${key} '…' --stage ${stage}`);
356
+ this.stage = stage;
357
+ this.key = key;
358
+ this.name = "SitePasswordMissing";
359
+ }
360
+ };
361
+ /**
362
+ * `deploy.sitePassword` names a stage that deploys through a target that
363
+ * cannot ask for a login. Refused rather than ignored: ignored, the stage
364
+ * would be public while its config says it is not.
365
+ */
366
+ var SitePasswordNeedsCompose = class extends GkmError {
367
+ stage;
368
+ target;
369
+ constructor(stage, target) {
370
+ super(`deploy.sitePassword puts '${stage}' behind a password, and it deploys through '${target}', which cannot ask for one yet — only compose does. Remove ${stage} from deploy.sitePassword, or deploy it with compose.`);
371
+ this.stage = stage;
372
+ this.target = target;
373
+ this.name = "SitePasswordNeedsCompose";
374
+ }
375
+ };
376
+ /**
377
+ * @throws {SitePasswordNeedsCompose} where `stage` asks for a site password
378
+ * and `target` is not compose
379
+ */
380
+ function assertSitePasswordTarget(workspace, stage, target) {
381
+ if (workspace.deploy?.sitePassword?.[stage] && target !== "compose") throw new SitePasswordNeedsCompose(stage, target);
382
+ }
383
+ /** The stage secret a site's password is kept in: `WEB_SITE_PASSWORD`. */
384
+ function sitePasswordKey(site) {
385
+ return `${environmentCase(site)}_SITE_PASSWORD`;
386
+ }
387
+ /** Every site the manifest declares, by app name. */
388
+ function siteNames(manifest) {
389
+ return Object.values(manifest).filter((declaration) => declaration.kind === "site").map((declaration) => appKey(declaration.id));
390
+ }
391
+ /**
392
+ * The sites `deploy.sitePassword` puts behind a password on `stage`, by app
393
+ * name: every one for `true`, the ones it names otherwise.
394
+ *
395
+ * @throws {UnknownPasswordSite} for a name that is not one of `sites`
396
+ */
397
+ function passwordSites(workspace, stage, sites) {
398
+ const choice = workspace.deploy?.sitePassword?.[stage];
399
+ if (!choice) return [];
400
+ if (choice === true) return [...sites];
401
+ const chosen = /* @__PURE__ */ new Set();
402
+ for (const name of choice) {
403
+ const site = appKey(name);
404
+ if (!sites.includes(site)) throw new UnknownPasswordSite(stage, name, sites);
405
+ chosen.add(site);
406
+ }
407
+ return [...chosen];
408
+ }
409
+ /** A password: 192 random bits, URL-safe, so it is typed or pasted as is. */
410
+ function generateSitePassword() {
411
+ return randomBytes(24).toString("base64url");
412
+ }
413
+ /**
414
+ * The stage's secrets with a generated password for each site, where it has
415
+ * none. Pure, like `withGeneratedSecrets`: the caller keeps the result once
416
+ * the run goes ahead.
417
+ */
418
+ function withSitePasswords(secrets, sites) {
419
+ const missing = sites.map(sitePasswordKey).filter((key) => !secrets.custom?.[key]);
420
+ if (missing.length === 0) return {
421
+ secrets,
422
+ generated: []
423
+ };
424
+ return {
425
+ secrets: {
426
+ ...secrets,
427
+ custom: {
428
+ ...secrets.custom,
429
+ ...Object.fromEntries(missing.map((key) => [key, generateSitePassword()]))
430
+ },
431
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
432
+ },
433
+ generated: missing
434
+ };
435
+ }
436
+ /**
437
+ * The login a site is served behind on `stage`.
438
+ *
439
+ * The hash's salt is derived from the password, so the same password always
440
+ * renders the same edge config: a deploy that changed nothing rewrites
441
+ * nothing, and the proxy is not reloaded for it. A per-hash random salt
442
+ * guards against a precomputed table of common passwords; a generated one is
443
+ * 192 random bits, which no table holds.
444
+ *
445
+ * @throws {SitePasswordMissing} where the stage's secrets hold none
446
+ */
447
+ function siteAuth(stage, site, custom) {
448
+ const key = sitePasswordKey(site);
449
+ const password = custom[key];
450
+ if (!password) throw new SitePasswordMissing(stage, key);
451
+ return {
452
+ user: stage,
453
+ password,
454
+ hash: bcrypt.hashSync(password, saltFor(password)),
455
+ key
456
+ };
457
+ }
458
+ /** bcrypt's own base-64 alphabet, in which a salt is written. */
459
+ const BCRYPT_ALPHABET = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
460
+ /**
461
+ * A bcrypt salt — `$2a$`, which Caddy and Traefik both read, at cost 10 —
462
+ * derived from the password.
463
+ */
464
+ function saltFor(password) {
465
+ const digest = createHash("sha256").update(password).digest();
466
+ let salt = "";
467
+ for (let i = 0; i < 22; i++) salt += BCRYPT_ALPHABET[digest[i] % 64];
468
+ return `$2a$10$${salt}`;
469
+ }
470
+ //#endregion
322
471
  //#region src/compose/dns.ts
323
472
  /**
324
473
  * A compose stage's DNS: the records its public hosts need, and the check that
@@ -341,10 +490,17 @@ function backupsRoleStatements(backups) {
341
490
  * resolver. One that is not the server stops the deploy with
342
491
  * {@link HostNotPointingAtServer}, before Caddy or Traefik ask Let's Encrypt
343
492
  * for a certificate it cannot get — and use up the rate limit trying.
344
- * - **The step** (`stageDns`): all three, as every deploy runs them — the plan applied through each provider,
345
- * what was written read back from the provider, and only the hosts no
346
- * provider wrote resolved publicly. A brand-new name is never looked up
347
- * before its record exists, so no resolver caches it as missing.
493
+ * - **What gkm wrote before** (`diffRecordedDns`, pure): the plan against the
494
+ * records the stage's state says gkm wrote — added, kept, and stale: a
495
+ * host the stage no longer serves, after a domain or an app changed.
496
+ * - **The prune** (`pruneStackDns`): each stale record deleted through its
497
+ * domain's provider, if it still holds the value gkm wrote. One someone
498
+ * pointed elsewhere is left alone; one no provider can delete is printed.
499
+ * - **The step** (`stageDns`): all of it, as every deploy runs it — the plan applied through each provider,
500
+ * what was written read back from the provider, the stale records pruned,
501
+ * and only the hosts no provider wrote resolved publicly. A brand-new name
502
+ * is never looked up before its record exists, so no resolver caches it as
503
+ * missing.
348
504
  */
349
505
  /** The line that says how to set the server's address. */
350
506
  function serverAddressHint(stage) {
@@ -536,12 +692,23 @@ function printManual(plan, log, why) {
536
692
  /** The existing records of every planned name, for the types gkm manages. */
537
693
  async function existingRecords(provider, plan) {
538
694
  const names = [...new Set(plan.records.map((r) => r.name))];
539
- const wanted = names.flatMap((name) => MANAGED.map((type) => ({
695
+ return readRecords(provider, plan.domain, names.flatMap((name) => MANAGED.map((type) => ({
540
696
  name,
541
697
  type
542
- })));
543
- if (provider.readRecords) return provider.readRecords(plan.domain, wanted);
544
- return (await provider.getRecords(plan.domain)).filter((r) => names.includes(r.name) && MANAGED.includes(r.type));
698
+ }))));
699
+ }
700
+ /** Exactly these names and types, as the provider holds them. */
701
+ async function readRecords(provider, domain, wanted) {
702
+ if (provider.readRecords) return provider.readRecords(domain, wanted);
703
+ return (await provider.getRecords(domain)).filter((r) => wanted.some((w) => w.name === r.name && w.type === r.type));
704
+ }
705
+ /** The provider for a domain's config: `providerFor`, or the real one. */
706
+ function providerResolver(options) {
707
+ return options.providerFor ?? ((config) => createDnsProvider({
708
+ config,
709
+ ...options.credentials ? { godaddy: options.credentials } : {},
710
+ ...options.hostinger ? { hostinger: options.hostinger } : {}
711
+ }));
545
712
  }
546
713
  const same = (a, b) => a.trim().replace(/\.$/, "").toLowerCase() === b.trim().replace(/\.$/, "").toLowerCase();
547
714
  /**
@@ -631,11 +798,7 @@ function printChange(change, dryRun, log) {
631
798
  async function settleStackDns(plan, options = {}) {
632
799
  const dryRun = options.dryRun === true;
633
800
  const log = options.log ?? ((line) => console.log(line));
634
- const providerFor = options.providerFor ?? ((config) => createDnsProvider({
635
- config,
636
- ...options.credentials ? { godaddy: options.credentials } : {},
637
- ...options.hostinger ? { hostinger: options.hostinger } : {}
638
- }));
801
+ const providerFor = providerResolver(options);
639
802
  const changes = [];
640
803
  const confirmed = /* @__PURE__ */ new Set();
641
804
  const unconfirmed = /* @__PURE__ */ new Set();
@@ -725,6 +888,110 @@ async function settleStackDns(plan, options = {}) {
725
888
  unconfirmed: [...unconfirmed].filter((h) => !confirmed.has(h)).sort()
726
889
  };
727
890
  }
891
+ /** `api.example.com A` — a record's identity, whatever its value. */
892
+ const recordKey = (fqdn, type) => `${fqdn.trim().replace(/\.$/, "").toLowerCase()} ${type.toUpperCase()}`;
893
+ /**
894
+ * The plan against the records gkm wrote for the stage — pure. A record is
895
+ * its full name and its type: one the plan has at another value is kept (the
896
+ * apply updates it), and one whose name or type the plan no longer has is
897
+ * stale — a host the stage stopped serving when its domain or an app changed,
898
+ * or an AAAA once its server has no IPv6 address.
899
+ */
900
+ function diffRecordedDns(plan, recorded) {
901
+ const planned = plan.domains.flatMap((domain) => domain.records);
902
+ const wanted = new Set(planned.map((r) => recordKey(r.host, r.type)));
903
+ const written = new Set(recorded.map((r) => recordKey(r.fqdn, r.type)));
904
+ return {
905
+ added: planned.filter((r) => !written.has(recordKey(r.host, r.type))),
906
+ kept: planned.filter((r) => written.has(recordKey(r.host, r.type))),
907
+ stale: recorded.filter((r) => !wanted.has(recordKey(r.fqdn, r.type)))
908
+ };
909
+ }
910
+ /**
911
+ * Each stale record deleted through its domain's provider — only where the
912
+ * provider still holds exactly the value gkm wrote, so a name someone has
913
+ * since pointed elsewhere is never taken from them. Where no provider can
914
+ * read and delete it — a `manual` domain, one gone from `dns`, a key that
915
+ * can only write — it is printed for a person to remove. A dry run reads
916
+ * and prints, and deletes nothing.
917
+ */
918
+ async function pruneStackDns(stale, options) {
919
+ if (stale.length === 0) return [];
920
+ const dryRun = options.dryRun === true;
921
+ const log = options.log ?? ((line) => console.log(line));
922
+ const providerFor = providerResolver(options);
923
+ const dns = options.dns ? normalizeDnsConfig(options.dns) : {};
924
+ const byDomain = /* @__PURE__ */ new Map();
925
+ for (const record of stale) byDomain.set(record.domain, [...byDomain.get(record.domain) ?? [], record]);
926
+ log(` No longer served by '${options.stage}':`);
927
+ const pruned = [];
928
+ const byHand = (domain, records, why) => {
929
+ log(` ${domain}: ${why} — remove these records at its DNS host:`);
930
+ for (const record of records) {
931
+ log(` ${record.fqdn.padEnd(32)} ${record.type.padEnd(5)} ${record.value}`);
932
+ pruned.push({
933
+ record,
934
+ action: "manual"
935
+ });
936
+ }
937
+ };
938
+ for (const [domain, records] of byDomain) {
939
+ const config = dns[domain];
940
+ if (!config) {
941
+ byHand(domain, records, "no longer in dns");
942
+ continue;
943
+ }
944
+ const provider = providerName(config) === "manual" ? null : await providerFor(config);
945
+ if (!provider) {
946
+ byHand(domain, records, "manual");
947
+ continue;
948
+ }
949
+ let existing;
950
+ try {
951
+ existing = await readRecords(provider, domain, records.map((r) => ({
952
+ name: r.name,
953
+ type: r.type
954
+ })));
955
+ } catch (error) {
956
+ if (!(error instanceof DnsRecordsUnreadable)) throw error;
957
+ byHand(domain, records, `the ${provider.name} key cannot read its records, so none is deleted unchecked`);
958
+ continue;
959
+ }
960
+ const would = dryRun ? "would " : "";
961
+ const deletes = [];
962
+ for (const record of records) {
963
+ const host = record.fqdn.padEnd(32);
964
+ const type = record.type.padEnd(5);
965
+ const found = existing.find((r) => r.name === record.name && r.type === record.type)?.values ?? [];
966
+ if (found.length === 0) {
967
+ log(` · ${host} ${type} already gone`);
968
+ pruned.push({
969
+ record,
970
+ action: "gone"
971
+ });
972
+ } else if (found.length === 1 && same(found[0], record.value)) {
973
+ log(` - ${host} ${type} ${record.value} — ${would}delete`);
974
+ deletes.push({
975
+ record,
976
+ action: "delete"
977
+ });
978
+ } else {
979
+ log(` ⚠ ${host} ${type} ${found.join(", ")} — pointed elsewhere since gkm wrote ${record.value}; left as it is`);
980
+ pruned.push({
981
+ record,
982
+ action: "changed",
983
+ found
984
+ });
985
+ }
986
+ }
987
+ if (!dryRun && deletes.length > 0) await provider.deleteRecords(domain, deletes.map(({ record }) => ({
988
+ name: record.name,
989
+ type: record.type
990
+ })));
991
+ pruned.push(...deletes);
992
+ }
993
+ return pruned;
994
+ }
728
995
  /** The system resolver: what the server's own clients would be told. */
729
996
  const systemLookup = async (host) => {
730
997
  return (await lookup(host, { all: true })).map((a) => a.address);
@@ -867,8 +1134,9 @@ async function dnsCredentials(stage, domains, credentials) {
867
1134
  /**
868
1135
  * A stage's DNS, as every deploy runs it: the
869
1136
  * stage's own hosts planned, written through each domain's provider where
870
- * they differ, and read back from it. Only A, AAAA and CNAME records of
871
- * those names are touched; nothing is a wildcard, and no other name is read,
1137
+ * they differ, and read back from it — and each record gkm wrote for a host
1138
+ * the stage no longer serves found, for `prune` to delete. Only A, AAAA and CNAME records of those
1139
+ * names are touched; nothing is a wildcard, and no other name is read,
872
1140
  * written or deleted. What no provider confirms — a `manual` domain, a host
873
1141
  * under no domain in `dns`, a key that cannot read — is resolved publicly.
874
1142
  * A dry run prints the plan and writes nothing.
@@ -886,16 +1154,17 @@ async function stageDns(input) {
886
1154
  server,
887
1155
  dns: input.dns
888
1156
  });
889
- const tokens = await dnsCredentials(stage, plan.domains.map(({ domain, config }) => ({
1157
+ const recorded = diffRecordedDns(plan, input.recorded ?? []);
1158
+ const dns = input.dns ? normalizeDnsConfig(input.dns) : {};
1159
+ const staleDomains = [...new Set(recorded.stale.map((r) => r.domain))].filter((domain) => dns[domain] && !plan.domains.some((d) => d.domain === domain)).map((domain) => ({
1160
+ domain,
1161
+ config: dns[domain]
1162
+ }));
1163
+ const tokens = await dnsCredentials(stage, [...plan.domains.map(({ domain, config }) => ({
890
1164
  domain,
891
1165
  config
892
- })), input.credentials);
893
- const applied = !input.dns ? {
894
- changes: [],
895
- confirmed: [],
896
- unconfirmed: [...plan.uncovered]
897
- } : await settleStackDns(plan, {
898
- ...input.dryRun ? { dryRun: true } : { confirm: true },
1166
+ })), ...staleDomains], input.credentials);
1167
+ const providers = {
899
1168
  log,
900
1169
  credentials: {
901
1170
  ...input.godaddy ?? {},
@@ -904,6 +1173,22 @@ async function stageDns(input) {
904
1173
  },
905
1174
  ...tokens.hostinger ? { hostinger: tokens.hostinger } : {},
906
1175
  ...input.providerFor ? { providerFor: input.providerFor } : {}
1176
+ };
1177
+ const applied = !input.dns ? {
1178
+ changes: [],
1179
+ confirmed: [],
1180
+ unconfirmed: [...plan.uncovered]
1181
+ } : await settleStackDns(plan, {
1182
+ ...input.dryRun ? { dryRun: true } : { confirm: true },
1183
+ ...providers
1184
+ });
1185
+ const deleted = new Set(applied.changes.filter((c) => c.action === "delete").map((c) => recordKey(fullName(c.domain, c.name), c.type)));
1186
+ const stale = recorded.stale.filter((r) => !deleted.has(recordKey(r.fqdn, r.type)));
1187
+ const prune = () => pruneStackDns(stale, {
1188
+ stage,
1189
+ dns: input.dns,
1190
+ ...input.dryRun ? { dryRun: true } : {},
1191
+ ...providers
907
1192
  });
908
1193
  let resolved = [];
909
1194
  if (applied.unconfirmed.length > 0) resolved = await checkStackDns({
@@ -917,10 +1202,12 @@ async function stageDns(input) {
917
1202
  plan,
918
1203
  changes: applied.changes,
919
1204
  confirmed: applied.confirmed,
920
- resolved
1205
+ resolved,
1206
+ recorded,
1207
+ prune
921
1208
  };
922
1209
  }
923
1210
  //#endregion
924
- export { deploysWithCompose as S, backupExpiries as _, serverAddressHint as a, runTime as b, stageDnsDomains as c, BACKUPS_URL_KEY as d, RUNNER_PATH as f, backupDatabases as g, stackBackups as h, requiredServerAddress as i, systemLookup as l, backupsService as m, dnsCredentials as n, stackHosts as o, backupsRoleStatements as p, isLocalHost as r, stageDns as s, HostNotPointingAtServer as t, BACKUPS_SERVICE as u, backupFile as v, stageBackups as x, backupsPrefix as y };
1211
+ export { backupFile as C, deploysWithCompose as D, stageBackups as E, backupExpiries as S, runTime as T, RUNNER_PATH as _, serverAddressHint as a, stackBackups as b, stageDnsDomains as c, passwordSites as d, siteAuth as f, BACKUPS_URL_KEY as g, BACKUPS_SERVICE as h, requiredServerAddress as i, systemLookup as l, withSitePasswords as m, dnsCredentials as n, stackHosts as o, siteNames as p, isLocalHost as r, stageDns as s, HostNotPointingAtServer as t, assertSitePasswordTarget as u, backupsRoleStatements as v, backupsPrefix as w, backupDatabases as x, backupsService as y };
925
1212
 
926
- //# sourceMappingURL=dns-agf2VCdV.mjs.map
1213
+ //# sourceMappingURL=dns-DFvO5a53.mjs.map