@geekmidas/cli 10.0.0-alpha.94 → 10.0.0-alpha.96

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 (206) hide show
  1. package/dist/{GoDaddyProvider-BRZUWD8z.mjs → GoDaddyProvider-BsUKjGcg.mjs} +3 -3
  2. package/dist/{GoDaddyProvider-BRZUWD8z.mjs.map → GoDaddyProvider-BsUKjGcg.mjs.map} +1 -1
  3. package/dist/{GoDaddyProvider-DzguxU_a.cjs → GoDaddyProvider-DyHXHDHe.cjs} +3 -3
  4. package/dist/{GoDaddyProvider-DzguxU_a.cjs.map → GoDaddyProvider-DyHXHDHe.cjs.map} +1 -1
  5. package/dist/{HostingerProvider-Cjh_2Ml6.mjs → HostingerProvider-BNpS9aRJ.mjs} +2 -2
  6. package/dist/{HostingerProvider-Cjh_2Ml6.mjs.map → HostingerProvider-BNpS9aRJ.mjs.map} +1 -1
  7. package/dist/{HostingerProvider-OdQmKXE3.cjs → HostingerProvider-DR1iws1P.cjs} +2 -2
  8. package/dist/{HostingerProvider-OdQmKXE3.cjs.map → HostingerProvider-DR1iws1P.cjs.map} +1 -1
  9. package/dist/{LegacyStateStore-BQZb6-j2.mjs → LegacyStateStore-B6SOqRFi.mjs} +3 -3
  10. package/dist/{LegacyStateStore-BQZb6-j2.mjs.map → LegacyStateStore-B6SOqRFi.mjs.map} +1 -1
  11. package/dist/{LegacyStateStore-D50dgUPc.cjs → LegacyStateStore-BnMcONE0.cjs} +3 -3
  12. package/dist/{LegacyStateStore-D50dgUPc.cjs.map → LegacyStateStore-BnMcONE0.cjs.map} +1 -1
  13. package/dist/{LocalStateStore-BSaS2535.mjs → LocalStateStore-6CrsZzqI.mjs} +2 -2
  14. package/dist/{LocalStateStore-BSaS2535.mjs.map → LocalStateStore-6CrsZzqI.mjs.map} +1 -1
  15. package/dist/{LocalStateStore-EFBUjuMX.cjs → LocalStateStore-pd4bbdJJ.cjs} +2 -2
  16. package/dist/{LocalStateStore-EFBUjuMX.cjs.map → LocalStateStore-pd4bbdJJ.cjs.map} +1 -1
  17. package/dist/{S3StateStore-C5mv8To3.cjs → S3StateStore-CoGk6ZKU.cjs} +2 -2
  18. package/dist/{S3StateStore-C5mv8To3.cjs.map → S3StateStore-CoGk6ZKU.cjs.map} +1 -1
  19. package/dist/{S3StateStore-CrFq4UZU.mjs → S3StateStore-DP_7Q4yb.mjs} +2 -2
  20. package/dist/{S3StateStore-CrFq4UZU.mjs.map → S3StateStore-DP_7Q4yb.mjs.map} +1 -1
  21. package/dist/{SSMStateStore-D3Z6mZnA.mjs → SSMStateStore-Bo6_KDYY.mjs} +2 -2
  22. package/dist/{SSMStateStore-D3Z6mZnA.mjs.map → SSMStateStore-Bo6_KDYY.mjs.map} +1 -1
  23. package/dist/{SSMStateStore-DuZCH7_v.cjs → SSMStateStore-D9s4_Do3.cjs} +2 -2
  24. package/dist/{SSMStateStore-DuZCH7_v.cjs.map → SSMStateStore-D9s4_Do3.cjs.map} +1 -1
  25. package/dist/{add-C85isXFl.mjs → add-BQS2QZHp.mjs} +22 -51
  26. package/dist/add-BQS2QZHp.mjs.map +1 -0
  27. package/dist/{add-DsVDdvon.cjs → add-CjURJ-vI.cjs} +23 -52
  28. package/dist/add-CjURJ-vI.cjs.map +1 -0
  29. package/dist/{bundler-BSHog8CH.mjs → bundler-BMM3UsGm.mjs} +5 -5
  30. package/dist/{bundler-BSHog8CH.mjs.map → bundler-BMM3UsGm.mjs.map} +1 -1
  31. package/dist/{bundler-BEgUKc4j.cjs → bundler-DmLJQ7AY.cjs} +5 -5
  32. package/dist/{bundler-BEgUKc4j.cjs.map → bundler-DmLJQ7AY.cjs.map} +1 -1
  33. package/dist/{cli-BvBdv660.cjs → cli-C2c-jOVK.cjs} +4 -4
  34. package/dist/{cli-BvBdv660.cjs.map → cli-C2c-jOVK.cjs.map} +1 -1
  35. package/dist/{cli-BsFenyaf.mjs → cli-Ce6NgaQF.mjs} +4 -4
  36. package/dist/{cli-BsFenyaf.mjs.map → cli-Ce6NgaQF.mjs.map} +1 -1
  37. package/dist/{compose-D2RFYd_f.mjs → compose-BJDPFBAN.mjs} +43 -47
  38. package/dist/compose-BJDPFBAN.mjs.map +1 -0
  39. package/dist/{compose-Cd09gW-n.cjs → compose-ETFKqkTu.cjs} +60 -64
  40. package/dist/compose-ETFKqkTu.cjs.map +1 -0
  41. package/dist/{config-Ngesg4O8.d.mts → config-4yirvxS8.d.cts} +4 -4
  42. package/dist/{config-DvHNMoOp.d.cts.map → config-4yirvxS8.d.cts.map} +1 -1
  43. package/dist/{config-DvHNMoOp.d.cts → config-C8NPCn1l.d.mts} +4 -4
  44. package/dist/{config-Ngesg4O8.d.mts.map → config-C8NPCn1l.d.mts.map} +1 -1
  45. package/dist/config.cjs +1 -1
  46. package/dist/config.d.cts +3 -3
  47. package/dist/config.d.mts +3 -3
  48. package/dist/config.mjs +1 -1
  49. package/dist/{credentials-DpfnUvIR.mjs → credentials-B5N5yKk6.mjs} +4 -132
  50. package/dist/credentials-B5N5yKk6.mjs.map +1 -0
  51. package/dist/{credentials-D7XJypNd.cjs → credentials-Zqr3QQYB.cjs} +3 -173
  52. package/dist/credentials-Zqr3QQYB.cjs.map +1 -0
  53. package/dist/deploy/public.cjs +16 -15
  54. package/dist/deploy/public.cjs.map +1 -1
  55. package/dist/deploy/public.d.cts +5 -5
  56. package/dist/deploy/public.d.cts.map +1 -1
  57. package/dist/deploy/public.d.mts +5 -5
  58. package/dist/deploy/public.d.mts.map +1 -1
  59. package/dist/deploy/public.mjs +11 -10
  60. package/dist/deploy/public.mjs.map +1 -1
  61. package/dist/deploy-BdN0ym2i.cjs +2057 -0
  62. package/dist/deploy-BdN0ym2i.cjs.map +1 -0
  63. package/dist/deploy-DktQl7ti.mjs +2008 -0
  64. package/dist/deploy-DktQl7ti.mjs.map +1 -0
  65. package/dist/{discover-CTzOSGUF.d.cts → discover-BtFofyQB.d.cts} +2 -2
  66. package/dist/{discover-CTzOSGUF.d.cts.map → discover-BtFofyQB.d.cts.map} +1 -1
  67. package/dist/{discover-CihAtaoj.d.mts → discover-D-MdXZgm.d.mts} +2 -2
  68. package/dist/{discover-CihAtaoj.d.mts.map → discover-D-MdXZgm.d.mts.map} +1 -1
  69. package/dist/dns-BjHECDO7.mjs +44 -0
  70. package/dist/dns-BjHECDO7.mjs.map +1 -0
  71. package/dist/dns-C-X6PLkV.cjs +663 -0
  72. package/dist/dns-C-X6PLkV.cjs.map +1 -0
  73. package/dist/dns-C2EeR6n2.mjs +610 -0
  74. package/dist/dns-C2EeR6n2.mjs.map +1 -0
  75. package/dist/dns-ClI7-d5g.cjs +55 -0
  76. package/dist/dns-ClI7-d5g.cjs.map +1 -0
  77. package/dist/{dokploy-CV7GcobZ.cjs → dokploy-CD_Bc4EV.cjs} +2 -2
  78. package/dist/{dokploy-CV7GcobZ.cjs.map → dokploy-CD_Bc4EV.cjs.map} +1 -1
  79. package/dist/{dokploy-D_aat2Xj.mjs → dokploy-CxtkS5hl.mjs} +2 -2
  80. package/dist/{dokploy-D_aat2Xj.mjs.map → dokploy-CxtkS5hl.mjs.map} +1 -1
  81. package/dist/{encryption-_4qKqtTx.mjs → encryption-BgQd4f2S.mjs} +3 -3
  82. package/dist/{encryption-_4qKqtTx.mjs.map → encryption-BgQd4f2S.mjs.map} +1 -1
  83. package/dist/{encryption-DFcivJaO.cjs → encryption-CqVfz-m7.cjs} +3 -3
  84. package/dist/{encryption-DFcivJaO.cjs.map → encryption-CqVfz-m7.cjs.map} +1 -1
  85. package/dist/{engine-Bv-LZpmj.cjs → engine-OVVKtPmW.cjs} +18 -36
  86. package/dist/engine-OVVKtPmW.cjs.map +1 -0
  87. package/dist/{engine-DPMHThkg.mjs → engine-YlXzlTUl.mjs} +14 -32
  88. package/dist/{engine-DPMHThkg.mjs.map → engine-YlXzlTUl.mjs.map} +1 -1
  89. package/dist/{github-Br42NMrW.cjs → github-CsMloeSL.cjs} +9 -7
  90. package/dist/{github-Br42NMrW.cjs.map → github-CsMloeSL.cjs.map} +1 -1
  91. package/dist/{github-xsDT19im.mjs → github-DuuvqI3Z.mjs} +5 -3
  92. package/dist/{github-xsDT19im.mjs.map → github-DuuvqI3Z.mjs.map} +1 -1
  93. package/dist/{index-CwLvFqtH.d.cts → index-5eL8rM6H.d.mts} +2 -2
  94. package/dist/{index-BlpcClwM.d.mts.map → index-5eL8rM6H.d.mts.map} +1 -1
  95. package/dist/{index-BlpcClwM.d.mts → index-Dz-gyjm_.d.cts} +2 -2
  96. package/dist/{index-CwLvFqtH.d.cts.map → index-Dz-gyjm_.d.cts.map} +1 -1
  97. package/dist/index.cjs +54 -53
  98. package/dist/index.cjs.map +1 -1
  99. package/dist/index.mjs +52 -51
  100. package/dist/index.mjs.map +1 -1
  101. package/dist/{local-B7Zv1NoA.mjs → local-BwC2v2Y8.mjs} +2 -2
  102. package/dist/{local-B7Zv1NoA.mjs.map → local-BwC2v2Y8.mjs.map} +1 -1
  103. package/dist/{local-OPC9BCZt.cjs → local-DNhnkugT.cjs} +3 -3
  104. package/dist/{local-OPC9BCZt.cjs.map → local-DNhnkugT.cjs.map} +1 -1
  105. package/dist/{migrate-D5jUz20_.cjs → migrate-BAmJeVq1.cjs} +3 -3
  106. package/dist/{migrate-D5jUz20_.cjs.map → migrate-BAmJeVq1.cjs.map} +1 -1
  107. package/dist/{migrate-SkWJotn5.cjs → migrate-BdkoNdIu.cjs} +3 -3
  108. package/dist/{migrate-SkWJotn5.cjs.map → migrate-BdkoNdIu.cjs.map} +1 -1
  109. package/dist/{migrate-DhvFPGLi.mjs → migrate-CB8B6W9y.mjs} +3 -3
  110. package/dist/{migrate-DhvFPGLi.mjs.map → migrate-CB8B6W9y.mjs.map} +1 -1
  111. package/dist/{migrate-B7evHv7O.mjs → migrate-CsPFHgTs.mjs} +3 -3
  112. package/dist/{migrate-B7evHv7O.mjs.map → migrate-CsPFHgTs.mjs.map} +1 -1
  113. package/dist/openapi.cjs +1 -1
  114. package/dist/openapi.d.cts +1 -1
  115. package/dist/openapi.d.mts +1 -1
  116. package/dist/openapi.mjs +1 -1
  117. package/dist/reconcile/public.d.cts +2 -2
  118. package/dist/reconcile/public.d.mts +2 -2
  119. package/dist/{resolve-CFEOtRsl.cjs → resolve-BDatNOoG.cjs} +4 -4
  120. package/dist/{resolve-CFEOtRsl.cjs.map → resolve-BDatNOoG.cjs.map} +1 -1
  121. package/dist/{resolve-Dyd1S2wn.mjs → resolve-CUne0qOm.mjs} +4 -4
  122. package/dist/{resolve-Dyd1S2wn.mjs.map → resolve-CUne0qOm.mjs.map} +1 -1
  123. package/dist/{rollback-NroNbGC0.mjs → rollback-CCdEEEcF.mjs} +7 -8
  124. package/dist/{rollback-NroNbGC0.mjs.map → rollback-CCdEEEcF.mjs.map} +1 -1
  125. package/dist/{rollback-D720SFhV.cjs → rollback-CuG3DkTY.cjs} +8 -9
  126. package/dist/{rollback-D720SFhV.cjs.map → rollback-CuG3DkTY.cjs.map} +1 -1
  127. package/dist/run-BJLIerMM.mjs +133 -0
  128. package/dist/run-BJLIerMM.mjs.map +1 -0
  129. package/dist/run-fVZ-rLnc.cjs +174 -0
  130. package/dist/run-fVZ-rLnc.cjs.map +1 -0
  131. package/dist/{runtime-VktAo_8B.d.cts → runtime-DUQdM-mW.d.cts} +2 -2
  132. package/dist/{runtime-BRCq6dQ7.d.mts.map → runtime-DUQdM-mW.d.cts.map} +1 -1
  133. package/dist/{runtime-BRCq6dQ7.d.mts → runtime-Vr6mleo2.d.mts} +2 -2
  134. package/dist/{runtime-VktAo_8B.d.cts.map → runtime-Vr6mleo2.d.mts.map} +1 -1
  135. package/dist/{sandbox-D1gd0iDz.d.cts → sandbox-BbJmh2rI.d.cts} +2 -2
  136. package/dist/{sandbox-D1gd0iDz.d.cts.map → sandbox-BbJmh2rI.d.cts.map} +1 -1
  137. package/dist/{sandbox-zHnRLCj0.d.mts → sandbox-DGOsMPMf.d.mts} +2 -2
  138. package/dist/{sandbox-zHnRLCj0.d.mts.map → sandbox-DGOsMPMf.d.mts.map} +1 -1
  139. package/dist/{secrets-DAvi_O9g.cjs → secrets-C5qROlbp.cjs} +3 -3
  140. package/dist/{secrets-DAvi_O9g.cjs.map → secrets-C5qROlbp.cjs.map} +1 -1
  141. package/dist/{secrets-CwLauUB_.mjs → secrets-ZikTIxEm.mjs} +3 -3
  142. package/dist/{secrets-CwLauUB_.mjs.map → secrets-ZikTIxEm.mjs.map} +1 -1
  143. package/dist/{seeds-BCg8X36S.cjs → seeds-BjXW-Krf.cjs} +193 -946
  144. package/dist/seeds-BjXW-Krf.cjs.map +1 -0
  145. package/dist/{seeds-CTxTZa-R.mjs → seeds-CbJSne1A.mjs} +156 -825
  146. package/dist/seeds-CbJSne1A.mjs.map +1 -0
  147. package/dist/{sniffer-DCcuzHz_.mjs → sniffer-DQGBYY8i.mjs} +8 -8
  148. package/dist/{sniffer-DCcuzHz_.mjs.map → sniffer-DQGBYY8i.mjs.map} +1 -1
  149. package/dist/{sniffer-C0z3NB2A.cjs → sniffer-NPD1dyab.cjs} +8 -8
  150. package/dist/{sniffer-C0z3NB2A.cjs.map → sniffer-NPD1dyab.cjs.map} +1 -1
  151. package/dist/{sst-DRDSDwOk.mjs → sst-CGdjHn2A.mjs} +4 -3
  152. package/dist/{sst-DRDSDwOk.mjs.map → sst-CGdjHn2A.mjs.map} +1 -1
  153. package/dist/{sst-DKNH243z.cjs → sst-DGuHDxVU.cjs} +5 -4
  154. package/dist/{sst-DKNH243z.cjs.map → sst-DGuHDxVU.cjs.map} +1 -1
  155. package/dist/{stack-NrdH5ngX.mjs → stack-BFq3K-_z.mjs} +14 -52
  156. package/dist/stack-BFq3K-_z.mjs.map +1 -0
  157. package/dist/{stack-B3fPDTBg.cjs → stack-DOVvEd1T.cjs} +14 -52
  158. package/dist/stack-DOVvEd1T.cjs.map +1 -0
  159. package/dist/{stages-veC4TcQe.mjs → stages-BEn2ArJZ.mjs} +4 -3
  160. package/dist/{stages-veC4TcQe.mjs.map → stages-BEn2ArJZ.mjs.map} +1 -1
  161. package/dist/{stages-CkqP1N4S.cjs → stages-Cw6urZyJ.cjs} +5 -4
  162. package/dist/{stages-CkqP1N4S.cjs.map → stages-Cw6urZyJ.cjs.map} +1 -1
  163. package/dist/target/public.cjs +1 -1
  164. package/dist/target/public.d.cts +2 -2
  165. package/dist/target/public.d.mts +2 -2
  166. package/dist/target/public.mjs +1 -1
  167. package/dist/{terminal-CkUYuAPd.cjs → terminal-CPHuH4S1.cjs} +2 -2
  168. package/dist/{terminal-CkUYuAPd.cjs.map → terminal-CPHuH4S1.cjs.map} +1 -1
  169. package/dist/{terminal-DAHs2Zko.mjs → terminal-iNmAXXjs.mjs} +2 -2
  170. package/dist/{terminal-DAHs2Zko.mjs.map → terminal-iNmAXXjs.mjs.map} +1 -1
  171. package/dist/{types-DMoaFrjg.d.mts → types-bkqVscdN.d.mts} +12 -3
  172. package/dist/types-bkqVscdN.d.mts.map +1 -0
  173. package/dist/{types-DXyaSmqu.d.cts → types-dipJRmKV.d.cts} +12 -3
  174. package/dist/types-dipJRmKV.d.cts.map +1 -0
  175. package/dist/workspace/index.cjs +1 -1
  176. package/dist/workspace/index.d.cts +2 -2
  177. package/dist/workspace/index.d.mts +2 -2
  178. package/dist/workspace/index.mjs +1 -1
  179. package/dist/{workspace-DWK1PTXP.mjs → workspace-CqFUjObk.mjs} +54 -2
  180. package/dist/workspace-CqFUjObk.mjs.map +1 -0
  181. package/dist/{workspace-cSyi5nb1.cjs → workspace-DDkm4y6D.cjs} +71 -1
  182. package/dist/workspace-DDkm4y6D.cjs.map +1 -0
  183. package/package.json +15 -15
  184. package/dist/add-C85isXFl.mjs.map +0 -1
  185. package/dist/add-DsVDdvon.cjs.map +0 -1
  186. package/dist/compose-Cd09gW-n.cjs.map +0 -1
  187. package/dist/compose-D2RFYd_f.mjs.map +0 -1
  188. package/dist/credentials-D7XJypNd.cjs.map +0 -1
  189. package/dist/credentials-DpfnUvIR.mjs.map +0 -1
  190. package/dist/deploy-C911Akkn.cjs +0 -670
  191. package/dist/deploy-C911Akkn.cjs.map +0 -1
  192. package/dist/deploy-CeJ5D0xn.mjs +0 -641
  193. package/dist/deploy-CeJ5D0xn.mjs.map +0 -1
  194. package/dist/engine-Bv-LZpmj.cjs.map +0 -1
  195. package/dist/providers-BFeP_mYw.cjs +0 -1098
  196. package/dist/providers-BFeP_mYw.cjs.map +0 -1
  197. package/dist/providers-sTj-BG0S.mjs +0 -1079
  198. package/dist/providers-sTj-BG0S.mjs.map +0 -1
  199. package/dist/seeds-BCg8X36S.cjs.map +0 -1
  200. package/dist/seeds-CTxTZa-R.mjs.map +0 -1
  201. package/dist/stack-B3fPDTBg.cjs.map +0 -1
  202. package/dist/stack-NrdH5ngX.mjs.map +0 -1
  203. package/dist/types-DMoaFrjg.d.mts.map +0 -1
  204. package/dist/types-DXyaSmqu.d.cts.map +0 -1
  205. package/dist/workspace-DWK1PTXP.mjs.map +0 -1
  206. package/dist/workspace-cSyi5nb1.cjs.map +0 -1
@@ -1,1098 +0,0 @@
1
- const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
2
- const require_errors = require("./errors-DMC4Umsb.cjs");
3
- const require_credentials = require("./credentials-D7XJypNd.cjs");
4
- const require_derive = require("./derive-DAbo49sm.cjs");
5
- const require_workspace = require("./workspace-cSyi5nb1.cjs");
6
- const require_seeds = require("./seeds-BCg8X36S.cjs");
7
- let _geekmidas_manifest = require("@geekmidas/manifest");
8
- let _geekmidas_storage_s3_url = require("@geekmidas/storage/s3-url");
9
- _geekmidas_storage_s3_url = require_rolldown_runtime.__toESM(_geekmidas_storage_s3_url, 1);
10
- //#region src/deploy/journal.ts
11
- var DeployJournal = class DeployJournal {
12
- store;
13
- stage;
14
- state;
15
- resources;
16
- version;
17
- existed;
18
- constructor(store, stage, state, resources, version, existed) {
19
- this.store = store;
20
- this.stage = stage;
21
- this.state = state;
22
- this.resources = resources;
23
- this.version = version;
24
- this.existed = existed;
25
- }
26
- static async open(store, stage, initial, adopt = (stored) => stored) {
27
- const stored = await store.read(stage);
28
- if (stored) return new DeployJournal(store, stage, adopt(stored.state), stored.resources, stored.version, true);
29
- const state = initial();
30
- const version = await store.write(stage, state, { expectedVersion: null });
31
- return new DeployJournal(store, stage, state, {}, version, false);
32
- }
33
- record(key) {
34
- return this.resources[key];
35
- }
36
- /** The keys a previous run marked `pending` and never saw created. */
37
- unfinished() {
38
- return Object.values(this.resources).filter((record) => record.status === "pending").map((record) => record.key);
39
- }
40
- /** Records that `entry` is about to be created. */
41
- async pending(entry) {
42
- await this.put({
43
- ...entry,
44
- status: "pending"
45
- });
46
- }
47
- /**
48
- * Records `entry` as existing with `id`; a no-op if it already is, with
49
- * the same data.
50
- */
51
- async ready(entry, id) {
52
- const current = this.resources[entry.key];
53
- if (current?.status === "ready" && current.id === id && JSON.stringify(current.data) === JSON.stringify(entry.data)) return;
54
- await this.put({
55
- ...entry,
56
- status: "ready",
57
- id
58
- });
59
- }
60
- /**
61
- * The resource `entry` names: by its recorded id, else by looking it up,
62
- * else created — with `pending` written before the create and `ready`
63
- * after, so a run that dies in between leaves a record the next run
64
- * resolves by looking the resource up rather than creating another.
65
- */
66
- async ensure(entry, steps) {
67
- const record = this.resources[entry.key];
68
- let staleId;
69
- if (record?.status === "ready" && record.id && steps.get) {
70
- const known = await steps.get(record.id);
71
- if (known) {
72
- await this.ready(entry, steps.id(known));
73
- return {
74
- resource: known,
75
- via: "recorded"
76
- };
77
- }
78
- staleId = record.id;
79
- }
80
- const existing = await steps.find();
81
- if (existing) {
82
- await this.ready(entry, steps.id(existing));
83
- return {
84
- resource: existing,
85
- via: record?.status === "pending" ? "resumed" : "found",
86
- ...staleId ? { staleId } : {}
87
- };
88
- }
89
- await this.pending(entry);
90
- const created = await steps.create();
91
- await this.ready(entry, steps.id(created));
92
- return {
93
- resource: created,
94
- via: "created",
95
- ...staleId ? { staleId } : {}
96
- };
97
- }
98
- /** Removes `key`'s record — a resource that no longer exists. */
99
- async forget(key) {
100
- if (!(key in this.resources)) return;
101
- this.version = await this.store.deleteResource(this.stage, key, { expectedVersion: this.version });
102
- const { [key]: _removed, ...resources } = this.resources;
103
- this.resources = resources;
104
- }
105
- /** Every record, as the journal last wrote or read it. */
106
- records() {
107
- return this.resources;
108
- }
109
- /** Writes the stage's state, keeping its resource records. */
110
- async save() {
111
- this.state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
112
- this.version = await this.store.write(this.stage, this.state, { expectedVersion: this.version });
113
- }
114
- async put(record) {
115
- this.version = await this.store.putResource(this.stage, record, { expectedVersion: this.version });
116
- this.resources = {
117
- ...this.resources,
118
- [record.key]: {
119
- ...record,
120
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
121
- }
122
- };
123
- }
124
- };
125
- //#endregion
126
- //#region src/providers/aws.ts
127
- /**
128
- * The AWS account a provider provisions in — the stage's, found the way the
129
- * SSM and Secrets Manager stores find it.
130
- *
131
- * 1. `--profile`, resolved on its own: exported keys left over from another
132
- * account never win over a profile someone named.
133
- * 2. `AWS_PROFILE`, else `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` — the
134
- * deploy's own `aws` credential kind (`storedCredentials`).
135
- * 3. The SDK's default chain: `~/.aws` default profile, SSO, a role. In CI,
136
- * whatever `aws-actions/configure-aws-credentials` exported.
137
- *
138
- * None of them: the provider has no provisioning credentials, and the stage
139
- * is treated as `external`.
140
- */
141
- async function awsProvisioningCredentials(lookup) {
142
- const region = lookup.env.AWS_REGION ?? lookup.env.AWS_DEFAULT_REGION;
143
- const where = region ? { region } : {};
144
- if (lookup.profile) return {
145
- profile: lookup.profile,
146
- ...where
147
- };
148
- const stored = await require_credentials.storedCredentials({
149
- env: lookup.env,
150
- ...lookup.home ? { home: lookup.home } : {}
151
- }).get({
152
- kind: "aws",
153
- stage: lookup.stage
154
- });
155
- if (stored) return stored;
156
- try {
157
- const { fromNodeProviderChain } = await import("@aws-sdk/credential-providers");
158
- await fromNodeProviderChain({
159
- timeout: 1e3,
160
- maxRetries: 0
161
- })();
162
- return {
163
- chain: true,
164
- ...where
165
- };
166
- } catch {
167
- return;
168
- }
169
- }
170
- /**
171
- * An S3-compatible endpoint the environment points the SDK at — the AWS
172
- * emulator in tests. The SDK reads it itself; it is read here too, because
173
- * an emulator takes path-style requests, and a key written for one has to
174
- * say where it is.
175
- */
176
- function awsEndpoint(env, service) {
177
- return env[`AWS_ENDPOINT_URL_${service}`] ?? env.AWS_ENDPOINT_URL;
178
- }
179
- /** An SDK client's config for a provisioning credential. */
180
- async function awsClientConfig(credential, region) {
181
- if ("profile" in credential) {
182
- const { fromIni } = await import("@aws-sdk/credential-providers");
183
- return {
184
- region,
185
- credentials: fromIni({ profile: credential.profile })
186
- };
187
- }
188
- if ("accessKeyId" in credential) return {
189
- region,
190
- credentials: {
191
- accessKeyId: credential.accessKeyId,
192
- secretAccessKey: credential.secretAccessKey,
193
- ...credential.sessionToken ? { sessionToken: credential.sessionToken } : {}
194
- }
195
- };
196
- return { region };
197
- }
198
- //#endregion
199
- //#region src/providers/s3/origins.ts
200
- /**
201
- * Which browser origins may call a bucket: the stage's sites that depend on
202
- * an API using it.
203
- *
204
- * The same reading of the graph an API's own CORS origins are — its callers —
205
- * taken one step further: a site that calls an API which hands it a presigned
206
- * upload URL is a site whose browser PUTs to the bucket. A site that reads the
207
- * bucket (or a file server over it) itself is one too. Nothing is written
208
- * down: add a site that calls the API and the next provision run allows it.
209
- */
210
- /** The keys a reader of this bucket holds: its URL, and each server's over it. */
211
- function bucketKeys(manifest, bucket) {
212
- return [(0, _geekmidas_manifest.provideKey)(bucket, "url"), ...Object.entries(manifest).filter(([, d]) => d.kind === "file-server" && d.of === bucket).map(([id]) => (0, _geekmidas_manifest.provideKey)(id, "url"))];
213
- }
214
- function bucketOrigins(input) {
215
- const { manifest, workspace, stage } = input;
216
- const keys = bucketKeys(manifest, input.bucket);
217
- const readers = Object.entries(manifest).filter(([, d]) => d.kind === "rest-api" || d.kind === "site").filter(([id]) => {
218
- const env = require_seeds.appEnvKeys(manifest, require_derive.appKey(id), input.runnables);
219
- return env !== void 0 && keys.some((key) => env.has(key));
220
- });
221
- const sites = /* @__PURE__ */ new Set();
222
- for (const [id, declaration] of readers) {
223
- if (declaration.kind === "site") {
224
- sites.add(id);
225
- continue;
226
- }
227
- for (const caller of (0, _geekmidas_manifest.dependentsOf)(manifest, id)) if (manifest[caller]?.kind === "site") sites.add(caller);
228
- }
229
- const origins = /* @__PURE__ */ new Set();
230
- const unresolved = [];
231
- for (const id of [...sites].sort()) {
232
- const name = require_derive.appKey(id);
233
- const app = workspace.apps[name];
234
- if (!app) {
235
- unresolved.push(name);
236
- continue;
237
- }
238
- try {
239
- const host = require_seeds.resolveHost(name, app, stage, workspace.domains, require_seeds.isMainFrontendApp(name, app, workspace.apps));
240
- origins.add(`https://${host}`);
241
- } catch (error) {
242
- if (!(error instanceof require_seeds.NoDomainForStage)) throw error;
243
- unresolved.push(name);
244
- }
245
- }
246
- return {
247
- origins: [...origins].sort(),
248
- unresolved
249
- };
250
- }
251
- //#endregion
252
- //#region src/providers/s3/policy.ts
253
- const bucketArn = (bucket) => `arn:aws:s3:::${bucket}`;
254
- /**
255
- * What the app's key may do: objects in this bucket, and listing it — and
256
- * nothing else in the account.
257
- *
258
- * Multipart uploads are in it because a presigned upload of a large file is
259
- * one, and an abandoned one is cleaned up by the app. Reading old versions is
260
- * added only when the bucket keeps them.
261
- */
262
- function userPolicy(bucket, options = {}) {
263
- return {
264
- Version: "2012-10-17",
265
- Statement: [{
266
- Sid: "GkmObjects",
267
- Effect: "Allow",
268
- Action: [
269
- "s3:GetObject",
270
- "s3:PutObject",
271
- "s3:DeleteObject",
272
- "s3:AbortMultipartUpload",
273
- "s3:ListMultipartUploadParts",
274
- ...options.versioning ? ["s3:GetObjectVersion"] : []
275
- ],
276
- Resource: `${bucketArn(bucket)}/*`
277
- }, {
278
- Sid: "GkmBucket",
279
- Effect: "Allow",
280
- Action: [
281
- "s3:ListBucket",
282
- "s3:GetBucketLocation",
283
- "s3:ListBucketMultipartUploads"
284
- ],
285
- Resource: bucketArn(bucket)
286
- }]
287
- };
288
- }
289
- /**
290
- * One open pattern as the resource it is public on. A `**` is S3's `*`; a
291
- * single `*` is too, which crosses `/` where the construct's own check does
292
- * not — the construct is the stricter of the two (see `bucketPolicy` in
293
- * reconcile, which the local MinIO is given the same way).
294
- */
295
- function openResource(bucket, pattern) {
296
- const path = pattern.replace(/^\/+/, "").replace(/\*\*/g, "*");
297
- return `${bucketArn(bucket)}/${path}`;
298
- }
299
- /**
300
- * The bucket's own policy: every request refused unless it came over TLS,
301
- * and — only when a file server opens paths on it — anonymous `s3:GetObject`
302
- * on exactly those prefixes.
303
- */
304
- function bucketPolicy(bucket, open = []) {
305
- const statements = [{
306
- Sid: "GkmDenyInsecureTransport",
307
- Effect: "Deny",
308
- Principal: "*",
309
- Action: "s3:*",
310
- Resource: [bucketArn(bucket), `${bucketArn(bucket)}/*`],
311
- Condition: { Bool: { "aws:SecureTransport": "false" } }
312
- }];
313
- if (open.length > 0) statements.push({
314
- Sid: "GkmOpenPaths",
315
- Effect: "Allow",
316
- Principal: "*",
317
- Action: "s3:GetObject",
318
- Resource: [...new Set(open.map((p) => openResource(bucket, p)))].sort()
319
- });
320
- return {
321
- Version: "2012-10-17",
322
- Statement: statements
323
- };
324
- }
325
- /**
326
- * Block Public Access. All four flags on, unless the bucket has open paths:
327
- * then a *policy* may make it public (`BlockPublicPolicy`,
328
- * `RestrictPublicBuckets` off), and ACLs still may not.
329
- */
330
- function publicAccessBlock(open) {
331
- return {
332
- BlockPublicAcls: true,
333
- IgnorePublicAcls: true,
334
- BlockPublicPolicy: !open,
335
- RestrictPublicBuckets: !open
336
- };
337
- }
338
- function corsRule(origins) {
339
- return {
340
- AllowedMethods: [
341
- "GET",
342
- "HEAD",
343
- "POST",
344
- "PUT"
345
- ],
346
- AllowedOrigins: [...new Set(origins)].sort(),
347
- AllowedHeaders: ["*"],
348
- ExposeHeaders: ["ETag"],
349
- MaxAgeSeconds: 3e3
350
- };
351
- }
352
- /**
353
- * A policy document in one canonical form — keys sorted, a lone string a
354
- * one-element list, lists sorted — so one AWS reformatted reads as the same
355
- * document rather than as drift.
356
- */
357
- function canonicalPolicy(value) {
358
- return JSON.stringify(canonical(value));
359
- }
360
- function canonical(value, key) {
361
- if (Array.isArray(value)) {
362
- const items = value.map((v) => canonical(v));
363
- return key === "Statement" ? items : items.map((v) => JSON.stringify(v)).sort();
364
- }
365
- if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((k) => {
366
- const v = value[k];
367
- return [k, canonical(k === "Action" || k === "Resource" || k === "AWS" ? Array.isArray(v) ? v : [v] : v, k)];
368
- }));
369
- return value;
370
- }
371
- //#endregion
372
- //#region src/providers/s3/index.ts
373
- /** The tags gkm finds its own buckets and users by. */
374
- const TAG_PROJECT = "gkm:project";
375
- const TAG_STAGE = "gkm:stage";
376
- const TAG_CONSTRUCT = "gkm:construct";
377
- function bucketConstructs(manifest) {
378
- return Object.entries(manifest).filter(([, d]) => d.kind === "objects").map(([id, d]) => ({
379
- id,
380
- versioned: d.versioned === true,
381
- servers: Object.entries(manifest).filter(([, s]) => s.kind === "file-server" && s.of === id).map(([sid, s]) => ({
382
- id: sid,
383
- open: s.open ?? []
384
- })).sort((a, b) => a.id.localeCompare(b.id))
385
- })).sort((a, b) => a.id.localeCompare(b.id));
386
- }
387
- /**
388
- * The region the stage's buckets are created in: the entry's, the secrets
389
- * store's when it is in AWS, the credential's, `AWS_REGION`.
390
- */
391
- function s3Region(ctx, env = process.env) {
392
- const store = ctx.workspace.secrets?.store;
393
- const storeRegion = store && typeof store === "object" && "region" in store ? store.region : void 0;
394
- const region = ctx.config.region ?? storeRegion ?? ctx.credential?.region ?? env.AWS_REGION ?? env.AWS_DEFAULT_REGION;
395
- if (!region) throw new require_workspace.S3RegionRequired(ctx.stage);
396
- return region;
397
- }
398
- /**
399
- * The address a file server over `bucket` serves on: the bucket's regional
400
- * virtual-host endpoint, which is what `FileServer.url(key)` appends a key to
401
- * — `https://<bucket>.s3.<region>.amazonaws.com/<key>`. Against an emulator,
402
- * its path-style address.
403
- */
404
- function bucketPublicUrl(bucket, region, endpoint) {
405
- if (endpoint) return `${endpoint.replace(/\/+$/, "")}/${bucket}`;
406
- return `https://${bucket}.s3.${region}.amazonaws.com`;
407
- }
408
- /** What an AWS SDK error is called, whichever field carries it. */
409
- function errorName(error) {
410
- const e = error;
411
- return e?.Code ?? e?.name ?? "";
412
- }
413
- /** A read whose "there is none" answer is one of `missing`. */
414
- async function readOr(read, missing) {
415
- try {
416
- return await read();
417
- } catch (error) {
418
- if (missing.includes(errorName(error))) return void 0;
419
- throw error;
420
- }
421
- }
422
- const defaultClients = async (credential, region) => {
423
- const S3 = await import("@aws-sdk/client-s3");
424
- const IAM = await import("@aws-sdk/client-iam");
425
- const config = await awsClientConfig(credential, region);
426
- const endpoint = awsEndpoint(process.env, "S3");
427
- return {
428
- S3,
429
- IAM,
430
- s3: new S3.S3Client({
431
- ...config,
432
- ...endpoint ? {
433
- endpoint,
434
- forcePathStyle: true
435
- } : {}
436
- }),
437
- iam: new IAM.IAMClient(config),
438
- region,
439
- ...endpoint ? { endpoint } : {}
440
- };
441
- };
442
- /** For tests: what makes the clients. */
443
- let makeClients = defaultClients;
444
- async function ensureS3(ctx) {
445
- const region = s3Region(ctx);
446
- const clients = await makeClients(ctx.credential, region);
447
- for (const construct of bucketConstructs(ctx.manifest)) await ensureBucket(ctx, clients, construct);
448
- }
449
- /** The keys gkm tags a bucket and a user with. */
450
- function identityTags(ctx, id) {
451
- return [
452
- {
453
- Key: TAG_PROJECT,
454
- Value: ctx.identity.key
455
- },
456
- {
457
- Key: TAG_STAGE,
458
- Value: ctx.stage
459
- },
460
- {
461
- Key: TAG_CONSTRUCT,
462
- Value: id
463
- }
464
- ];
465
- }
466
- async function ensureBucket(ctx, clients, construct) {
467
- const { id } = construct;
468
- const nameInput = {
469
- scope: ctx.identity.scope,
470
- stage: ctx.stage,
471
- id: require_derive.appKey(id)
472
- };
473
- const versioning = ctx.config.versioning ?? construct.versioned;
474
- const open = [...new Set(construct.servers.flatMap((s) => s.open))].sort();
475
- const { bucket, exists } = await ensureBucketName(ctx, clients, id, require_seeds.bucketName(nameInput));
476
- await ensureBucketSettings(ctx, clients, {
477
- id,
478
- bucket,
479
- exists,
480
- versioning,
481
- open
482
- });
483
- await ensureAccessKey(ctx, clients, {
484
- id,
485
- bucket,
486
- ...await ensureUser(ctx, clients, id, require_seeds.iamUserName(nameInput), userPolicy(bucket, { versioning }))
487
- });
488
- const publicUrl = bucketPublicUrl(bucket, clients.region, clients.endpoint);
489
- for (const server of construct.servers) {
490
- const key = (0, _geekmidas_manifest.provideKey)(server.id, "url");
491
- const entry = {
492
- key: `file-server:${server.id}`,
493
- type: "url"
494
- };
495
- const recorded = ctx.state.record(entry.key)?.id;
496
- const current = ctx.secrets[key];
497
- if (current === publicUrl) {
498
- await ctx.state.ready(entry, publicUrl);
499
- continue;
500
- }
501
- if (current !== void 0 && current !== recorded) {
502
- ctx.log(` ${key} is set to ${current} by you, and kept — the bucket's own address is ${publicUrl}`);
503
- continue;
504
- }
505
- await ctx.change({
506
- construct: server.id,
507
- resource: key,
508
- change: `set to ${publicUrl}`
509
- }, async () => {
510
- await ctx.writeSecrets({ [key]: publicUrl });
511
- await ctx.state.ready(entry, publicUrl);
512
- });
513
- }
514
- }
515
- /**
516
- * The bucket's name: the recorded one, else the good name, else the good
517
- * name with a random suffix — up to {@link SUFFIX_ATTEMPTS} times.
518
- */
519
- async function ensureBucketName(ctx, clients, id, good) {
520
- const { S3, s3, region } = clients;
521
- const key = `s3-bucket:${id}`;
522
- const record = ctx.state.record(key);
523
- const entry = (name) => ({
524
- key,
525
- type: "s3-bucket",
526
- data: {
527
- name,
528
- region
529
- }
530
- });
531
- /** Whether this account holds `name`: yes, no, or someone else does. */
532
- const probe = async (name) => {
533
- try {
534
- await s3.send(new S3.HeadBucketCommand({ Bucket: name }));
535
- return "ours";
536
- } catch (error) {
537
- const name = errorName(error);
538
- if (name === "NotFound" || name === "NoSuchBucket") return "none";
539
- if (name === "Forbidden" || name === "AccessDenied") return "taken";
540
- throw error;
541
- }
542
- };
543
- const create = async (name) => {
544
- if (ctx.dryRun) {
545
- const found = await probe(name);
546
- if (found === "none") {
547
- await ctx.change({
548
- construct: id,
549
- resource: `bucket ${name}`,
550
- change: `create in ${region}`
551
- }, async () => {});
552
- return "created";
553
- }
554
- return found;
555
- }
556
- await ctx.state.pending(entry(name));
557
- try {
558
- await s3.send(new S3.CreateBucketCommand({
559
- Bucket: name,
560
- ...region === "us-east-1" ? {} : { CreateBucketConfiguration: { LocationConstraint: region } }
561
- }));
562
- } catch (error) {
563
- const code = errorName(error);
564
- if (code === "BucketAlreadyOwnedByYou") return "ours";
565
- if (code === "BucketAlreadyExists") return "taken";
566
- throw error;
567
- }
568
- await ctx.change({
569
- construct: id,
570
- resource: `bucket ${name}`,
571
- change: `create in ${region}`
572
- }, async () => {});
573
- return "created";
574
- };
575
- const adopt = async (name) => {
576
- const owner = (await readOr(() => s3.send(new S3.GetBucketTaggingCommand({ Bucket: name })), ["NoSuchTagSet", "NoSuchTagSetError"]))?.TagSet?.find((t) => t.Key === TAG_PROJECT)?.Value;
577
- if (owner && owner !== ctx.identity.key) throw new require_workspace.BucketOwnedByAnotherProject(name, owner, ctx.identity.key);
578
- };
579
- if (record?.status === "ready" && record.id) {
580
- const found = await probe(record.id);
581
- if (found === "ours") return {
582
- bucket: record.id,
583
- exists: true
584
- };
585
- if (found === "taken") throw new require_workspace.RecordedBucketUnreachable(id, record.id);
586
- const made = await create(record.id);
587
- if (made === "taken") throw new require_workspace.RecordedBucketUnreachable(id, record.id);
588
- if (!ctx.dryRun) await ctx.state.ready(entry(record.id), record.id);
589
- return {
590
- bucket: record.id,
591
- exists: made === "ours"
592
- };
593
- }
594
- const pending = record?.data?.name;
595
- const names = [
596
- ...typeof pending === "string" && pending !== good ? [pending] : [],
597
- good,
598
- ...Array.from({ length: 5 }, () => require_seeds.suffixedBucketName(good, require_seeds.randomSuffix()))
599
- ];
600
- const tried = [];
601
- for (const name of names) {
602
- tried.push(name);
603
- const made = await create(name);
604
- if (made === "taken") continue;
605
- if (made === "ours") await adopt(name);
606
- if (!ctx.dryRun) await ctx.state.ready(entry(name), name);
607
- if (name !== good) ctx.log(` '${good}' is taken by another account; '${id}' is '${name}', recorded in the stage's state`);
608
- return {
609
- bucket: name,
610
- exists: made === "ours"
611
- };
612
- }
613
- throw new require_workspace.BucketNameUnavailable(id, tried.filter((n) => n !== pending));
614
- }
615
- /** Tags, Block Public Access, encryption, policy, versioning and CORS. */
616
- async function ensureBucketSettings(ctx, clients, input) {
617
- const { S3, s3 } = clients;
618
- const { id, bucket } = input;
619
- const Bucket = bucket;
620
- const fresh = ctx.dryRun && !input.exists;
621
- const read = async (fn, missing) => fresh ? void 0 : readOr(fn, missing);
622
- const set = (change, apply) => ctx.change({
623
- construct: id,
624
- resource: `bucket ${bucket}`,
625
- change
626
- }, () => apply().then(() => void 0));
627
- const tagged = await read(() => s3.send(new S3.GetBucketTaggingCommand({ Bucket })), ["NoSuchTagSet", "NoSuchTagSetError"]);
628
- const ours = identityTags(ctx, id);
629
- const others = (tagged?.TagSet ?? []).filter((t) => !ours.some((o) => o.Key === t.Key));
630
- if (ours.some((o) => !tagged?.TagSet?.some((t) => t.Key === o.Key && t.Value === o.Value))) await set("tag", () => s3.send(new S3.PutBucketTaggingCommand({
631
- Bucket,
632
- Tagging: { TagSet: [...others, ...ours].map((t) => ({
633
- Key: t.Key,
634
- Value: t.Value
635
- })) }
636
- })));
637
- const isOpen = input.open.length > 0;
638
- const block = publicAccessBlock(isOpen);
639
- const ensureBlock = async () => {
640
- const now = (await read(() => s3.send(new S3.GetPublicAccessBlockCommand({ Bucket })), ["NoSuchPublicAccessBlockConfiguration"]))?.PublicAccessBlockConfiguration;
641
- if (!(now && Object.keys(block).every((k) => (now[k] ?? false) === block[k]))) await set(isOpen ? "block public access (ACLs; the policy opens the open paths)" : "block all public access", () => s3.send(new S3.PutPublicAccessBlockCommand({
642
- Bucket,
643
- PublicAccessBlockConfiguration: block
644
- })));
645
- };
646
- const policy = bucketPolicy(bucket, input.open);
647
- const ensurePolicy = async () => {
648
- const current = await read(() => s3.send(new S3.GetBucketPolicyCommand({ Bucket })), ["NoSuchBucketPolicy"]);
649
- if (!(current?.Policy !== void 0 && canonicalPolicy(JSON.parse(current.Policy)) === canonicalPolicy(policy))) await set(isOpen ? `policy: TLS only, public read on ${input.open.join(", ")}` : "policy: TLS only", () => s3.send(new S3.PutBucketPolicyCommand({
650
- Bucket,
651
- Policy: JSON.stringify(policy)
652
- })));
653
- };
654
- if (isOpen) {
655
- await ensureBlock();
656
- await ensurePolicy();
657
- } else {
658
- await ensurePolicy();
659
- await ensureBlock();
660
- }
661
- if ((await read(() => s3.send(new S3.GetBucketEncryptionCommand({ Bucket })), ["ServerSideEncryptionConfigurationNotFoundError"]))?.ServerSideEncryptionConfiguration?.Rules?.[0]?.ApplyServerSideEncryptionByDefault?.SSEAlgorithm !== "AES256" && !fresh) await set("encrypt by default (SSE-S3)", () => s3.send(new S3.PutBucketEncryptionCommand({
662
- Bucket,
663
- ServerSideEncryptionConfiguration: { Rules: [{ ApplyServerSideEncryptionByDefault: { SSEAlgorithm: "AES256" } }] }
664
- })));
665
- const status = (await read(() => s3.send(new S3.GetBucketVersioningCommand({ Bucket })), []))?.Status;
666
- if (input.versioning && status !== "Enabled") await set("enable versioning", () => s3.send(new S3.PutBucketVersioningCommand({
667
- Bucket,
668
- VersioningConfiguration: { Status: "Enabled" }
669
- })));
670
- else if (!input.versioning && status === "Enabled") await set("suspend versioning", () => s3.send(new S3.PutBucketVersioningCommand({
671
- Bucket,
672
- VersioningConfiguration: { Status: "Suspended" }
673
- })));
674
- const { origins, unresolved } = bucketOrigins({
675
- workspace: ctx.workspace,
676
- manifest: ctx.manifest,
677
- runnables: ctx.runnables,
678
- stage: ctx.stage,
679
- bucket: id
680
- });
681
- if (unresolved.length > 0) ctx.log(` ${unresolved.join(", ")} ${unresolved.length === 1 ? "has" : "have"} no address on '${ctx.stage}' (domains.${ctx.stage}), so ${unresolved.length === 1 ? "is" : "are"} not in '${bucket}''s CORS`);
682
- if (origins.length === 0) return;
683
- const rule = corsRule(origins);
684
- const cors = await read(() => s3.send(new S3.GetBucketCorsCommand({ Bucket })), ["NoSuchCORSConfiguration"]);
685
- if (!(cors?.CORSRules?.length === 1 && canonicalCors(cors.CORSRules[0]) === canonicalCors(rule))) await set(`CORS for ${origins.join(", ")}`, () => s3.send(new S3.PutBucketCorsCommand({
686
- Bucket,
687
- CORSConfiguration: { CORSRules: [rule] }
688
- })));
689
- }
690
- function canonicalCors(rule) {
691
- const sorted = (list) => [...list ?? []].sort();
692
- return JSON.stringify({
693
- AllowedMethods: sorted(rule.AllowedMethods),
694
- AllowedOrigins: sorted(rule.AllowedOrigins),
695
- AllowedHeaders: sorted(rule.AllowedHeaders),
696
- ExposeHeaders: sorted(rule.ExposeHeaders),
697
- MaxAgeSeconds: rule.MaxAgeSeconds ?? null
698
- });
699
- }
700
- /** The bucket's IAM user and its inline policy. */
701
- async function ensureUser(ctx, clients, id, user, policy) {
702
- const { IAM, iam } = clients;
703
- const entry = {
704
- key: `iam-user:${id}`,
705
- type: "iam-user"
706
- };
707
- const found = await readOr(() => iam.send(new IAM.GetUserCommand({ UserName: user })), ["NoSuchEntity", "NoSuchEntityException"]);
708
- if (found?.User) {
709
- const tags = await iam.send(new IAM.ListUserTagsCommand({ UserName: user }));
710
- const tag = (key) => tags.Tags?.find((t) => t.Key === key)?.Value;
711
- if (found.User.Path !== "/gkm/" || tag("gkm:project") !== ctx.identity.key || tag("gkm:stage") !== ctx.stage) throw new require_workspace.IamUserNotOwned(user, ctx.identity.key, ctx.stage);
712
- await ctx.state.ready(entry, user);
713
- } else await ctx.change({
714
- construct: id,
715
- resource: `IAM user ${user}`,
716
- change: `create under ${require_seeds.IAM_USER_PATH}`
717
- }, async () => {
718
- await ctx.state.pending(entry);
719
- await iam.send(new IAM.CreateUserCommand({
720
- UserName: user,
721
- Path: require_seeds.IAM_USER_PATH,
722
- Tags: identityTags(ctx, id)
723
- }));
724
- await ctx.state.ready(entry, user);
725
- });
726
- const exists = Boolean(found?.User) || !ctx.dryRun;
727
- const current = exists ? await readOr(() => iam.send(new IAM.GetUserPolicyCommand({
728
- UserName: user,
729
- PolicyName: require_seeds.IAM_POLICY_NAME
730
- })), ["NoSuchEntity", "NoSuchEntityException"]) : void 0;
731
- const document = current?.PolicyDocument ? JSON.parse(decodePolicy(current.PolicyDocument)) : void 0;
732
- if (!document || canonicalPolicy(document) !== canonicalPolicy(policy)) await ctx.change({
733
- construct: id,
734
- resource: `IAM user ${user}`,
735
- change: `policy ${require_seeds.IAM_POLICY_NAME}: this bucket only`
736
- }, async () => {
737
- await iam.send(new IAM.PutUserPolicyCommand({
738
- UserName: user,
739
- PolicyName: require_seeds.IAM_POLICY_NAME,
740
- PolicyDocument: JSON.stringify(policy)
741
- }));
742
- });
743
- return {
744
- user,
745
- exists
746
- };
747
- }
748
- /** IAM returns a policy document URL-encoded; an emulator may not. */
749
- function decodePolicy(document) {
750
- try {
751
- return decodeURIComponent(document);
752
- } catch {
753
- return document;
754
- }
755
- }
756
- /**
757
- * The user's access key, in the stage's secrets as the bucket's URL.
758
- *
759
- * - First run: one key, written into the stage's secrets.
760
- * - `--rotate-keys`: a second key, written in place of the first; the first
761
- * stays active until the stage has deployed with the second, and the next
762
- * run after that deploy — or `--retire-old-keys` — deletes it.
763
- * - A key the secrets no longer hold (a store emptied by hand): replaced the
764
- * same way, since its secret cannot be read back.
765
- */
766
- async function ensureAccessKey(ctx, clients, input) {
767
- const { IAM, iam } = clients;
768
- const { id, bucket, user } = input;
769
- const urlKey = (0, _geekmidas_manifest.provideKey)(id, "url");
770
- const entryKey = `iam-access-key:${id}`;
771
- const record = ctx.state.record(entryKey);
772
- const given = ctx.secrets[urlKey];
773
- let inUrl;
774
- if (given !== void 0) {
775
- let parsed;
776
- try {
777
- parsed = _geekmidas_storage_s3_url.parse(given);
778
- } catch {
779
- parsed = void 0;
780
- }
781
- if (parsed?.bucket !== bucket) throw new require_workspace.BucketKeySetElsewhere(urlKey, bucket, parsed?.bucket ?? given.replace(/\/\/[^@]*@/, "//"), ctx.stage);
782
- inUrl = parsed.accessKeyId;
783
- }
784
- const listed = input.exists ? (await iam.send(new IAM.ListAccessKeysCommand({ UserName: user }))).AccessKeyMetadata?.map((k) => k.AccessKeyId) ?? [] : [];
785
- let current = record?.status === "ready" ? record.id : void 0;
786
- let previous = typeof record?.data?.previous === "string" ? record.data.previous : void 0;
787
- const rotatedAt = typeof record?.data?.rotatedAt === "string" ? record.data.rotatedAt : void 0;
788
- const entry = (data = {}) => ({
789
- key: entryKey,
790
- type: "iam-access-key",
791
- data: {
792
- user,
793
- ...data
794
- }
795
- });
796
- if (previous && current) {
797
- const deployed = rotatedAt !== void 0 && ctx.state.lastDeployedAt !== void 0 && ctx.state.lastDeployedAt > rotatedAt;
798
- if (ctx.retireOldKeys || deployed) {
799
- const old = previous;
800
- const keep = current;
801
- await ctx.change({
802
- construct: id,
803
- resource: `IAM user ${user}`,
804
- change: `delete the rotated-out access key ${old}`
805
- }, async () => {
806
- if (listed.includes(old)) await iam.send(new IAM.DeleteAccessKeyCommand({
807
- UserName: user,
808
- AccessKeyId: old
809
- }));
810
- await ctx.state.ready(entry(), keep);
811
- });
812
- previous = void 0;
813
- } else ctx.log(` ${id}: the old access key ${previous} stays active until '${ctx.stage}' is deployed with the new one; the next deploy after that deletes it (or pass --retire-old-keys)`);
814
- }
815
- if (!current && inUrl && listed.includes(inUrl)) {
816
- await ctx.state.ready(entry(), inUrl);
817
- current = inUrl;
818
- }
819
- if (current !== void 0 && listed.includes(current) && inUrl === current && !ctx.rotateKeys) return;
820
- if (ctx.rotateKeys && previous) throw new require_workspace.RotationInProgress(id, previous, ctx.stage);
821
- const outgoing = current && listed.includes(current) ? current : void 0;
822
- if (listed.length >= 2) throw new require_workspace.AccessKeyLimit(user, listed);
823
- const why = !current ? "create an access key" : ctx.rotateKeys ? "rotate: issue a new access key" : "issue a new access key (the secrets no longer hold the recorded one)";
824
- await ctx.change({
825
- construct: id,
826
- resource: `IAM user ${user}`,
827
- change: why
828
- }, async () => {
829
- await ctx.state.pending(entry(outgoing ? { previous: outgoing } : {}));
830
- const key = (await iam.send(new IAM.CreateAccessKeyCommand({ UserName: user }))).AccessKey;
831
- await ctx.writeSecrets({ [urlKey]: _geekmidas_storage_s3_url.build({
832
- bucket,
833
- region: clients.region,
834
- ...clients.endpoint ? {
835
- endpoint: clients.endpoint,
836
- forcePathStyle: true
837
- } : {},
838
- accessKeyId: key.AccessKeyId,
839
- secretAccessKey: key.SecretAccessKey
840
- }) });
841
- await ctx.state.ready(entry(outgoing ? {
842
- previous: outgoing,
843
- rotatedAt: (/* @__PURE__ */ new Date()).toISOString()
844
- } : {}), key.AccessKeyId);
845
- });
846
- if (outgoing) ctx.log(` ${urlKey} holds the new key. This deploy moves '${ctx.stage}' onto it; the next deploy after it deletes the old key ${outgoing}`);
847
- }
848
- /**
849
- * Each provisioned bucket answers `HeadBucket` to the key in the stage's
850
- * secrets — the one the apps will use. A bucket whose URL carries no key is
851
- * not one this provider wrote, and the deploy's own check covers a URL that
852
- * is missing.
853
- */
854
- async function verifyS3(ctx) {
855
- const S3 = await import("@aws-sdk/client-s3");
856
- for (const { id } of bucketConstructs(ctx.manifest)) {
857
- const url = ctx.secrets[(0, _geekmidas_manifest.provideKey)(id, "url")];
858
- if (url === void 0) continue;
859
- const address = _geekmidas_storage_s3_url.parse(url);
860
- if (!address.accessKeyId || !address.secretAccessKey) continue;
861
- const client = new S3.S3Client({
862
- region: address.region ?? ctx.config.region ?? "us-east-1",
863
- credentials: {
864
- accessKeyId: address.accessKeyId,
865
- secretAccessKey: address.secretAccessKey
866
- },
867
- ...address.endpoint ? { endpoint: address.endpoint } : {},
868
- ...address.forcePathStyle ? { forcePathStyle: true } : {}
869
- });
870
- try {
871
- await client.send(new S3.HeadBucketCommand({ Bucket: address.bucket }));
872
- } catch (error) {
873
- const name = errorName(error);
874
- throw new require_workspace.ProvisionedBucketUnreachable(id, address.bucket, ctx.stage, name === "NotFound" ? "it does not exist" : name === "Forbidden" ? "the key is refused" : name || String(error));
875
- } finally {
876
- client.destroy();
877
- }
878
- }
879
- }
880
- //#endregion
881
- //#region src/providers/registry.ts
882
- /**
883
- * The providers gkm ships, by kind and name — registered the way a deploy
884
- * target's built-ins are (`target/builtins.ts`). A provider from a package is
885
- * one more entry here once there is a loading story for it.
886
- */
887
- const BUILTIN_PROVIDERS = { objects: { s3: {
888
- kind: "objects",
889
- name: "s3",
890
- check: require_workspace.checkS3Config,
891
- provisioning: require_workspace.S3_PROVISIONING,
892
- credentials: awsProvisioningCredentials,
893
- runtimeKeys(manifest) {
894
- return bucketConstructs(manifest).flatMap((b) => [(0, _geekmidas_manifest.provideKey)(b.id, "url"), ...b.servers.map((s) => (0, _geekmidas_manifest.provideKey)(s.id, "url"))]);
895
- },
896
- ensure: ensureS3,
897
- verify: verifyS3
898
- } } };
899
- /** The provider `kind` names `name`, if gkm has one. */
900
- function builtinProvider(kind, name) {
901
- return Object.hasOwn(BUILTIN_PROVIDERS[kind], name) ? BUILTIN_PROVIDERS[kind][name] : void 0;
902
- }
903
- //#endregion
904
- //#region src/providers/index.ts
905
- /** The constructs of a kind: `objects` is every bucket. */
906
- function constructsOf(manifest, kind) {
907
- return Object.entries(manifest).filter(([, d]) => d.kind === kind).map(([id]) => id).sort();
908
- }
909
- /**
910
- * What every deploy runs: each provider's `verify()` — the cheap check that
911
- * what it created is still there and the stage's key reaches it. Nothing on
912
- * the local stage, and nothing for a kind on `external`.
913
- *
914
- * @throws {StageProviderDisabled} for a declared kind the stage has none of
915
- */
916
- async function verifyStageProviders(input) {
917
- require_seeds.assertStageProvidersEnabled(input.workspace, input.manifest, input.stage);
918
- const verified = [];
919
- for (const kind of require_seeds.PROVIDER_KINDS) {
920
- if (constructsOf(input.manifest, kind).length === 0) continue;
921
- const choice = require_workspace.stageProvider(input.workspace, kind, input.stage);
922
- if (choice.mode !== "provider") continue;
923
- const provider = builtinProvider(kind, choice.name);
924
- await provider.verify({
925
- workspace: input.workspace,
926
- manifest: input.manifest,
927
- stage: input.stage,
928
- config: choice.config,
929
- secrets: input.secrets
930
- });
931
- verified.push(`${kind}: ${provider.name}`);
932
- }
933
- return verified;
934
- }
935
- /**
936
- * A stage whose provider creates its resources, and no credentials to create
937
- * them with. Raised before anything is created, built or started.
938
- */
939
- var ProviderCredentialsMissing = class extends require_errors.GkmError {
940
- stage;
941
- kind;
942
- provider;
943
- ids;
944
- env;
945
- describe;
946
- login;
947
- constructor(stage, kind, provider, ids, env, describe, login) {
948
- super(`deploy.${kind}.${stage} is ${provider}, so the deploy creates what ${ids.join(", ")} ${ids.length === 1 ? "is" : "are"} backed by on '${stage}' and writes ${ids.length === 1 ? "its key" : "their keys"} into the stage's secrets — and it found no ${provider} credentials to do it with. On this machine, supply ${describe}` + (login ? `, or run gkm login --provider ${login}` : "") + `. In CI, the step that runs the deploy (or gkm deploy --resources-only) needs them: the '${stage}' environment's role (AWS_ROLE_ARN, from gkm deploy:github), assumed before that step. With the keys set by hand instead, set deploy.${kind}.${stage} to 'external'.`);
949
- this.stage = stage;
950
- this.kind = kind;
951
- this.provider = provider;
952
- this.ids = ids;
953
- this.env = env;
954
- this.describe = describe;
955
- this.login = login;
956
- this.name = "ProviderCredentialsMissing";
957
- }
958
- };
959
- /**
960
- * `ensure()` for every provider the stage configures — find or create, fix
961
- * drift, record in the stage's state, write the runtime keys into its
962
- * secrets. Run by the deploy, which holds the stage's lock. A provider with
963
- * no provisioning credentials stops the run with
964
- * {@link ProviderCredentialsMissing} before anything is created.
965
- */
966
- async function provisionStage(options) {
967
- const { workspace, stage, manifest } = options;
968
- const dryRun = options.dryRun === true;
969
- const env = options.env ?? process.env;
970
- const log = options.log ?? ((line) => console.log(line));
971
- const runnables = options.runnables ?? {};
972
- const reports = [];
973
- const configured = [];
974
- for (const kind of require_seeds.PROVIDER_KINDS) {
975
- const choice = require_workspace.stageProvider(workspace, kind, stage);
976
- if (choice.mode !== "provider") {
977
- reports.push({
978
- kind,
979
- mode: choice.mode,
980
- actions: [],
981
- planned: []
982
- });
983
- continue;
984
- }
985
- const provider = builtinProvider(kind, choice.name);
986
- const report = {
987
- kind,
988
- mode: "provider",
989
- provider: provider.name,
990
- actions: [],
991
- planned: []
992
- };
993
- reports.push(report);
994
- const ids = constructsOf(manifest, kind);
995
- if (ids.length === 0) continue;
996
- const credential = await provider.credentials({
997
- stage,
998
- env,
999
- ...options.profile ? { profile: options.profile } : {},
1000
- ...options.home ? { home: options.home } : {}
1001
- });
1002
- if (credential === void 0) throw new ProviderCredentialsMissing(stage, kind, provider.name, ids, provider.provisioning.env, provider.provisioning.describe, provider.provisioning.login);
1003
- configured.push({
1004
- kind,
1005
- provider,
1006
- config: choice.config,
1007
- credential,
1008
- report
1009
- });
1010
- }
1011
- if (configured.length === 0) return reports;
1012
- const identity = require_seeds.deployIdentity(workspace, stage);
1013
- let secrets = (await options.secrets.read())?.custom ?? {};
1014
- for (const entry of configured) entry.report.planned = entry.provider.runtimeKeys(manifest).filter((key) => secrets[key] === void 0);
1015
- const state = dryRun ? await readOnlyState(options.state, stage) : journalState(await DeployJournal.open(options.state, stage, () => require_seeds.deploysWithCompose(workspace) ? require_seeds.createComposeState(stage) : require_seeds.createEmptyState(stage, "", "")));
1016
- const writeSecrets = async (values) => {
1017
- if (dryRun) return;
1018
- const stored = await options.secrets.read() ?? require_seeds.initStageSecrets(stage);
1019
- const next = {
1020
- ...stored,
1021
- custom: {
1022
- ...stored.custom,
1023
- ...values
1024
- },
1025
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1026
- };
1027
- await options.secrets.write(next);
1028
- secrets = next.custom;
1029
- };
1030
- for (const entry of configured) {
1031
- log(`☁️ ${entry.kind}: ${entry.provider.name}${dryRun ? " (dry run)" : ""}`);
1032
- await entry.provider.ensure({
1033
- workspace,
1034
- manifest,
1035
- runnables,
1036
- stage,
1037
- identity,
1038
- config: entry.config,
1039
- credential: entry.credential,
1040
- get secrets() {
1041
- return secrets;
1042
- },
1043
- state,
1044
- dryRun,
1045
- rotateKeys: options.rotateKeys === true,
1046
- retireOldKeys: options.retireOldKeys === true,
1047
- async change(action, apply) {
1048
- if (!dryRun) await apply();
1049
- entry.report.actions.push(action);
1050
- log(` ${dryRun ? "would " : ""}${action.change} — ${action.resource}`);
1051
- },
1052
- writeSecrets,
1053
- log
1054
- });
1055
- if (entry.report.actions.length === 0) log(" up to date");
1056
- }
1057
- return reports;
1058
- }
1059
- /** The journal as `ensure()` sees it. */
1060
- function journalState(journal) {
1061
- return {
1062
- record: (key) => journal.record(key),
1063
- pending: (entry) => journal.pending(entry),
1064
- ready: (entry, id) => journal.ready(entry, id),
1065
- lastDeployedAt: journal.existed ? journal.state.lastDeployedAt : void 0
1066
- };
1067
- }
1068
- /** What a dry run reads, and the writes it does not make. */
1069
- async function readOnlyState(store, stage) {
1070
- const stored = await store.read(stage);
1071
- return {
1072
- record: (key) => stored?.resources[key],
1073
- pending: async () => {},
1074
- ready: async () => {},
1075
- ...stored ? { lastDeployedAt: stored.state.lastDeployedAt } : {}
1076
- };
1077
- }
1078
- //#endregion
1079
- Object.defineProperty(exports, "DeployJournal", {
1080
- enumerable: true,
1081
- get: function() {
1082
- return DeployJournal;
1083
- }
1084
- });
1085
- Object.defineProperty(exports, "provisionStage", {
1086
- enumerable: true,
1087
- get: function() {
1088
- return provisionStage;
1089
- }
1090
- });
1091
- Object.defineProperty(exports, "verifyStageProviders", {
1092
- enumerable: true,
1093
- get: function() {
1094
- return verifyStageProviders;
1095
- }
1096
- });
1097
-
1098
- //# sourceMappingURL=providers-BFeP_mYw.cjs.map