@geekmidas/cli 10.0.0-alpha.64 → 10.0.0-alpha.65

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/README.md +1 -1
  2. package/dist/{HostingerProvider-Bx2lHHvu.cjs → HostingerProvider-Dlmbfass.cjs} +3 -3
  3. package/dist/{HostingerProvider-Bx2lHHvu.cjs.map → HostingerProvider-Dlmbfass.cjs.map} +1 -1
  4. package/dist/{HostingerProvider-Cd4aS5Kj.mjs → HostingerProvider-x9gQTAGZ.mjs} +2 -2
  5. package/dist/{HostingerProvider-Cd4aS5Kj.mjs.map → HostingerProvider-x9gQTAGZ.mjs.map} +1 -1
  6. package/dist/{LegacyStateStore-C2abV49N.cjs → LegacyStateStore-BGoo__sO.cjs} +2 -2
  7. package/dist/{LegacyStateStore-C2abV49N.cjs.map → LegacyStateStore-BGoo__sO.cjs.map} +1 -1
  8. package/dist/{LegacyStateStore-DIgkGaUP.mjs → LegacyStateStore-BUu1nQ5u.mjs} +2 -2
  9. package/dist/{LegacyStateStore-DIgkGaUP.mjs.map → LegacyStateStore-BUu1nQ5u.mjs.map} +1 -1
  10. package/dist/{LocalStateStore-DiJqKyPy.cjs → LocalStateStore-93urXyQe.cjs} +2 -2
  11. package/dist/{LocalStateStore-DiJqKyPy.cjs.map → LocalStateStore-93urXyQe.cjs.map} +1 -1
  12. package/dist/{LocalStateStore-Cy1ZBW7U.mjs → LocalStateStore-BRhMQRuy.mjs} +2 -2
  13. package/dist/{LocalStateStore-Cy1ZBW7U.mjs.map → LocalStateStore-BRhMQRuy.mjs.map} +1 -1
  14. package/dist/{S3StateStore-Ds0V1Lgm.cjs → S3StateStore-F4dxw6kS.cjs} +2 -2
  15. package/dist/{S3StateStore-Ds0V1Lgm.cjs.map → S3StateStore-F4dxw6kS.cjs.map} +1 -1
  16. package/dist/{S3StateStore-D3x6ivBL.mjs → S3StateStore-pw1GhWwt.mjs} +2 -2
  17. package/dist/{S3StateStore-D3x6ivBL.mjs.map → S3StateStore-pw1GhWwt.mjs.map} +1 -1
  18. package/dist/{SSMStateStore-CLipvx47.cjs → SSMStateStore-Bt9acuAF.cjs} +2 -2
  19. package/dist/{SSMStateStore-CLipvx47.cjs.map → SSMStateStore-Bt9acuAF.cjs.map} +1 -1
  20. package/dist/{SSMStateStore-DfF174xQ.mjs → SSMStateStore-Dmj7zzFO.mjs} +2 -2
  21. package/dist/{SSMStateStore-DfF174xQ.mjs.map → SSMStateStore-Dmj7zzFO.mjs.map} +1 -1
  22. package/dist/{bundler-C8uJf324.cjs → bundler-D6MlIfBT.cjs} +3 -3
  23. package/dist/{bundler-C8uJf324.cjs.map → bundler-D6MlIfBT.cjs.map} +1 -1
  24. package/dist/{bundler-DYgm9xsd.mjs → bundler-D_14JLZF.mjs} +3 -3
  25. package/dist/{bundler-DYgm9xsd.mjs.map → bundler-D_14JLZF.mjs.map} +1 -1
  26. package/dist/{cli-D3jofJ6j.cjs → cli-BfH1deAT.cjs} +6 -6
  27. package/dist/{cli-D3jofJ6j.cjs.map → cli-BfH1deAT.cjs.map} +1 -1
  28. package/dist/{cli-Bogz-zEj.mjs → cli-P56vvrDQ.mjs} +5 -5
  29. package/dist/{cli-Bogz-zEj.mjs.map → cli-P56vvrDQ.mjs.map} +1 -1
  30. package/dist/compose-391oEKj1.cjs +1982 -0
  31. package/dist/compose-391oEKj1.cjs.map +1 -0
  32. package/dist/compose-DprE3ASm.mjs +1862 -0
  33. package/dist/compose-DprE3ASm.mjs.map +1 -0
  34. package/dist/{config-DqNRiQOp.d.mts → config-BonvTW8p.d.mts} +3 -3
  35. package/dist/{config-Be-4_I6H.d.cts.map → config-BonvTW8p.d.mts.map} +1 -1
  36. package/dist/{config-Be-4_I6H.d.cts → config-V_X_BMpm.d.cts} +3 -3
  37. package/dist/{config-DqNRiQOp.d.mts.map → config-V_X_BMpm.d.cts.map} +1 -1
  38. package/dist/config.cjs +1 -1
  39. package/dist/config.d.cts +3 -3
  40. package/dist/config.d.mts +3 -3
  41. package/dist/config.mjs +1 -1
  42. package/dist/{credentials-B70vmLcm.cjs → credentials-Bzbg23n8.cjs} +176 -3
  43. package/dist/credentials-Bzbg23n8.cjs.map +1 -0
  44. package/dist/{credentials-s0wMeZBU.mjs → credentials-Dq1MnWQf.mjs} +128 -3
  45. package/dist/credentials-Dq1MnWQf.mjs.map +1 -0
  46. package/dist/{define-CYs0PEfZ.mjs → define-DR8xGYfL.mjs} +5 -1
  47. package/dist/define-DR8xGYfL.mjs.map +1 -0
  48. package/dist/{define-zUsP72ym.cjs → define-DzRz8FgH.cjs} +5 -1
  49. package/dist/define-DzRz8FgH.cjs.map +1 -0
  50. package/dist/deploy/public.cjs +18 -18
  51. package/dist/deploy/public.cjs.map +1 -1
  52. package/dist/deploy/public.d.cts +7 -4
  53. package/dist/deploy/public.d.cts.map +1 -1
  54. package/dist/deploy/public.d.mts +7 -4
  55. package/dist/deploy/public.d.mts.map +1 -1
  56. package/dist/deploy/public.mjs +11 -11
  57. package/dist/{deploy-Dc2LBJaK.mjs → deploy-9MBG_QLd.mjs} +8 -7
  58. package/dist/deploy-9MBG_QLd.mjs.map +1 -0
  59. package/dist/{deploy-yUcR2OlW.cjs → deploy-B80idPUZ.cjs} +11 -10
  60. package/dist/deploy-B80idPUZ.cjs.map +1 -0
  61. package/dist/{deploy-ZRXIPp1c.mjs → deploy-CZ5nlpsu.mjs} +13 -119
  62. package/dist/deploy-CZ5nlpsu.mjs.map +1 -0
  63. package/dist/{deploy-55rJkdKz.cjs → deploy-Cy8igpQF.cjs} +39 -145
  64. package/dist/deploy-Cy8igpQF.cjs.map +1 -0
  65. package/dist/{dokploy-CepdXuMl.mjs → dokploy-BdtBePjp.mjs} +3 -3
  66. package/dist/{dokploy-CepdXuMl.mjs.map → dokploy-BdtBePjp.mjs.map} +1 -1
  67. package/dist/{dokploy-D1d37rvM.cjs → dokploy-iFY8MY-L.cjs} +3 -3
  68. package/dist/{dokploy-D1d37rvM.cjs.map → dokploy-iFY8MY-L.cjs.map} +1 -1
  69. package/dist/{encryption-C9X98u2F.cjs → encryption-B1wJwgae.cjs} +5 -5
  70. package/dist/{encryption-C9X98u2F.cjs.map → encryption-B1wJwgae.cjs.map} +1 -1
  71. package/dist/{encryption-CKHjMpCr.mjs → encryption-DF_hgIpl.mjs} +4 -4
  72. package/dist/{encryption-CKHjMpCr.mjs.map → encryption-DF_hgIpl.mjs.map} +1 -1
  73. package/dist/{github-D-byNI7T.mjs → github-B0YYE98x.mjs} +3 -3
  74. package/dist/{github-D-byNI7T.mjs.map → github-B0YYE98x.mjs.map} +1 -1
  75. package/dist/{github-Dd3tMa1u.cjs → github-CYcmTdFs.cjs} +3 -3
  76. package/dist/{github-Dd3tMa1u.cjs.map → github-CYcmTdFs.cjs.map} +1 -1
  77. package/dist/{index-CfE9Y885.d.cts → index-DPUAGJaK.d.cts} +16 -3
  78. package/dist/index-DPUAGJaK.d.cts.map +1 -0
  79. package/dist/{index-CJ2qsSpp.d.mts → index-DwKD_8lt.d.mts} +16 -3
  80. package/dist/index-DwKD_8lt.d.mts.map +1 -0
  81. package/dist/index.cjs +233 -1667
  82. package/dist/index.cjs.map +1 -1
  83. package/dist/index.mjs +196 -1614
  84. package/dist/index.mjs.map +1 -1
  85. package/dist/{local-C7wnrcYK.cjs → local-BdH4XN0E.cjs} +1 -178
  86. package/dist/local-BdH4XN0E.cjs.map +1 -0
  87. package/dist/{local-Cd8ZYQG3.mjs → local-CWR4Y_kW.mjs} +3 -126
  88. package/dist/local-CWR4Y_kW.mjs.map +1 -0
  89. package/dist/{migrate-B4gngTLe.mjs → migrate-B2qL5wj4.mjs} +6 -5
  90. package/dist/{migrate-B4gngTLe.mjs.map → migrate-B2qL5wj4.mjs.map} +1 -1
  91. package/dist/{migrate-tx9-P9uz.cjs → migrate-q4pPAXtO.cjs} +19 -18
  92. package/dist/{migrate-tx9-P9uz.cjs.map → migrate-q4pPAXtO.cjs.map} +1 -1
  93. package/dist/openapi.d.cts +1 -1
  94. package/dist/openapi.d.mts +1 -1
  95. package/dist/provider-C9_-45d5.cjs +56 -0
  96. package/dist/provider-C9_-45d5.cjs.map +1 -0
  97. package/dist/provider-q4lsYzYG.mjs +45 -0
  98. package/dist/provider-q4lsYzYG.mjs.map +1 -0
  99. package/dist/reconcile/public.d.cts +1 -1
  100. package/dist/reconcile/public.d.mts +1 -1
  101. package/dist/resolve-DLkIXTct.mjs +262 -0
  102. package/dist/resolve-DLkIXTct.mjs.map +1 -0
  103. package/dist/resolve-N_i4XNd-.cjs +315 -0
  104. package/dist/resolve-N_i4XNd-.cjs.map +1 -0
  105. package/dist/{runtime-CJEYK4Gz.d.mts → runtime-BnD7Nn96.d.cts} +2 -2
  106. package/dist/{runtime-CJEYK4Gz.d.mts.map → runtime-BnD7Nn96.d.cts.map} +1 -1
  107. package/dist/{runtime-Cjq1jlVX.d.cts → runtime-j-tmFL65.d.mts} +2 -2
  108. package/dist/{runtime-Cjq1jlVX.d.cts.map → runtime-j-tmFL65.d.mts.map} +1 -1
  109. package/dist/sniffer-BSwaL-dX.cjs +2236 -0
  110. package/dist/sniffer-BSwaL-dX.cjs.map +1 -0
  111. package/dist/sniffer-BZ-OJO4Y.mjs +2068 -0
  112. package/dist/sniffer-BZ-OJO4Y.mjs.map +1 -0
  113. package/dist/{sst-Cper040I.mjs → sst-B2HbrDR3.mjs} +4 -4
  114. package/dist/{sst-Cper040I.mjs.map → sst-B2HbrDR3.mjs.map} +1 -1
  115. package/dist/{sst-BdALKEas.cjs → sst-DcfsVH6x.cjs} +4 -4
  116. package/dist/{sst-BdALKEas.cjs.map → sst-DcfsVH6x.cjs.map} +1 -1
  117. package/dist/{sniffer-49KPbDgq.mjs → state-B8YwmUZU.mjs} +878 -2465
  118. package/dist/state-B8YwmUZU.mjs.map +1 -0
  119. package/dist/{sniffer-ByhbEW5A.cjs → state-DF26e_lo.cjs} +811 -2525
  120. package/dist/state-DF26e_lo.cjs.map +1 -0
  121. package/dist/{store-D_2ByRtW.cjs → store-CJF1Tqkq.cjs} +2 -2
  122. package/dist/{store-D_2ByRtW.cjs.map → store-CJF1Tqkq.cjs.map} +1 -1
  123. package/dist/{store-BaaonhkJ.mjs → store-CLiHnqOY.mjs} +2 -2
  124. package/dist/{store-BaaonhkJ.mjs.map → store-CLiHnqOY.mjs.map} +1 -1
  125. package/dist/target/public.cjs +6 -6
  126. package/dist/target/public.d.cts +2 -2
  127. package/dist/target/public.d.mts +2 -2
  128. package/dist/target/public.mjs +3 -3
  129. package/dist/terminal-Bs5E1oxm.cjs +88 -0
  130. package/dist/terminal-Bs5E1oxm.cjs.map +1 -0
  131. package/dist/terminal-CfbiYUbZ.mjs +84 -0
  132. package/dist/terminal-CfbiYUbZ.mjs.map +1 -0
  133. package/dist/{types-B_bEQvb9.d.mts → types-BCzMWBb6.d.mts} +43 -11
  134. package/dist/types-BCzMWBb6.d.mts.map +1 -0
  135. package/dist/{types-DkrDOMxw.d.cts → types-ByMWkXKP.d.cts} +43 -11
  136. package/dist/types-ByMWkXKP.d.cts.map +1 -0
  137. package/dist/workspace/index.cjs +3 -1
  138. package/dist/workspace/index.d.cts +3 -3
  139. package/dist/workspace/index.d.mts +3 -3
  140. package/dist/workspace/index.mjs +2 -2
  141. package/dist/{workspace-BwgWJK7h.cjs → workspace-BewAHZIo.cjs} +38 -3
  142. package/dist/workspace-BewAHZIo.cjs.map +1 -0
  143. package/dist/{workspace-Bwit4q9O.mjs → workspace-C06_pW4g.mjs} +27 -4
  144. package/dist/workspace-C06_pW4g.mjs.map +1 -0
  145. package/package.json +13 -13
  146. package/dist/credentials-B70vmLcm.cjs.map +0 -1
  147. package/dist/credentials-s0wMeZBU.mjs.map +0 -1
  148. package/dist/define-CYs0PEfZ.mjs.map +0 -1
  149. package/dist/define-zUsP72ym.cjs.map +0 -1
  150. package/dist/deploy-55rJkdKz.cjs.map +0 -1
  151. package/dist/deploy-Dc2LBJaK.mjs.map +0 -1
  152. package/dist/deploy-ZRXIPp1c.mjs.map +0 -1
  153. package/dist/deploy-yUcR2OlW.cjs.map +0 -1
  154. package/dist/index-CJ2qsSpp.d.mts.map +0 -1
  155. package/dist/index-CfE9Y885.d.cts.map +0 -1
  156. package/dist/local-C7wnrcYK.cjs.map +0 -1
  157. package/dist/local-Cd8ZYQG3.mjs.map +0 -1
  158. package/dist/resolve-BXsNQjMR.cjs +0 -220
  159. package/dist/resolve-BXsNQjMR.cjs.map +0 -1
  160. package/dist/resolve-DZ2IJ0qY.mjs +0 -173
  161. package/dist/resolve-DZ2IJ0qY.mjs.map +0 -1
  162. package/dist/runtime-BGNDrHzh.cjs +0 -163
  163. package/dist/runtime-BGNDrHzh.cjs.map +0 -1
  164. package/dist/runtime-Cmn-FeYO.mjs +0 -134
  165. package/dist/runtime-Cmn-FeYO.mjs.map +0 -1
  166. package/dist/sniffer-49KPbDgq.mjs.map +0 -1
  167. package/dist/sniffer-ByhbEW5A.cjs.map +0 -1
  168. package/dist/terminal-Bdv--jhY.cjs +0 -481
  169. package/dist/terminal-Bdv--jhY.cjs.map +0 -1
  170. package/dist/terminal-D7Ntz6WE.mjs +0 -440
  171. package/dist/terminal-D7Ntz6WE.mjs.map +0 -1
  172. package/dist/types-B_bEQvb9.d.mts.map +0 -1
  173. package/dist/types-DkrDOMxw.d.cts.map +0 -1
  174. package/dist/workspace-BwgWJK7h.cjs.map +0 -1
  175. package/dist/workspace-Bwit4q9O.mjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,21 +1,24 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
4
- const require_sniffer = require("./sniffer-ByhbEW5A.cjs");
5
- const require_local = require("./local-C7wnrcYK.cjs");
4
+ const require_sniffer = require("./sniffer-BSwaL-dX.cjs");
5
+ const require_local = require("./local-BdH4XN0E.cjs");
6
+ const require_credentials = require("./credentials-Bzbg23n8.cjs");
6
7
  const require_discover = require("./discover-DHdECueV.cjs");
7
8
  const require_derive = require("./derive-DAbo49sm.cjs");
8
- const require_workspace = require("./workspace-BwgWJK7h.cjs");
9
+ const require_workspace = require("./workspace-BewAHZIo.cjs");
9
10
  const require_config = require("./config.cjs");
10
11
  const require_storage = require("./storage-CSGoC3bK.cjs");
11
- const require_store = require("./store-D_2ByRtW.cjs");
12
+ const require_store = require("./store-CJF1Tqkq.cjs");
13
+ const require_state = require("./state-DF26e_lo.cjs");
12
14
  const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-BWuDRHe9.cjs");
13
15
  const require_openapi = require("./openapi.cjs");
14
- const require_fakes = require("./fakes-zW-jNAbQ.cjs");
15
16
  const require_appPorts = require("./appPorts-Cu79hJF6.cjs");
16
17
  const require_ready = require("./ready-brEhh68f.cjs");
17
18
  const require_emit = require("./emit-Dmc1nvdS.cjs");
18
- const require_LocalStateStore = require("./LocalStateStore-DiJqKyPy.cjs");
19
+ const require_deploy = require("./deploy-B80idPUZ.cjs");
20
+ const require_compose = require("./compose-391oEKj1.cjs");
21
+ const require_LocalStateStore = require("./LocalStateStore-93urXyQe.cjs");
19
22
  const require_utils = require("./utils-D2Nk1Nv2.cjs");
20
23
  let node_path = require("node:path");
21
24
  let commander = require("commander");
@@ -33,8 +36,6 @@ let chokidar = require("chokidar");
33
36
  chokidar = require_rolldown_runtime.__toESM(chokidar, 1);
34
37
  let node_net = require("node:net");
35
38
  let node_crypto = require("node:crypto");
36
- let pg = require("pg");
37
- pg = require_rolldown_runtime.__toESM(pg, 1);
38
39
  let yaml = require("yaml");
39
40
  let prompts = require("prompts");
40
41
  prompts = require_rolldown_runtime.__toESM(prompts, 1);
@@ -50,7 +51,7 @@ let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions")
50
51
  let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
51
52
  let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
52
53
  //#region package.json
53
- var version = "10.0.0-alpha.64";
54
+ var version = "10.0.0-alpha.65";
54
55
  //#endregion
55
56
  //#region src/trust/index.ts
56
57
  /**
@@ -67,7 +68,7 @@ var version = "10.0.0-alpha.64";
67
68
  * worse trade than one that tells you what to type. Caddy's own `caddy trust`
68
69
  * draws the line in the same place.
69
70
  */
70
- const logger$8 = console;
71
+ const logger$7 = console;
71
72
  const run = (0, node_util.promisify)(node_child_process.execFile);
72
73
  /**
73
74
  * How each platform is told about a new authority.
@@ -116,18 +117,18 @@ function installation(platform, certificate) {
116
117
  */
117
118
  async function trustCommand(options = {}) {
118
119
  const { workspace } = await require_config.loadWorkspaceConfig();
119
- const certificate = (0, node_path.join)(workspace.root, require_sniffer.LOCAL_CA_PATH);
120
+ const certificate = (0, node_path.join)(workspace.root, require_state.LOCAL_CA_PATH);
120
121
  if (!(0, node_fs.existsSync)(certificate)) throw new NoLocalAuthority(certificate);
121
122
  const { describe, asks, steps } = installation(process.platform, certificate);
122
- logger$8.log(`\n🔐 Trusting the local authority in ${describe}`);
123
- logger$8.log(` ${certificate}\n`);
123
+ logger$7.log(`\n🔐 Trusting the local authority in ${describe}`);
124
+ logger$7.log(` ${certificate}\n`);
124
125
  if (options.dryRun) {
125
- for (const [command, args] of steps) logger$8.log(` ${command} ${args.join(" ")}`);
126
+ for (const [command, args] of steps) logger$7.log(` ${command} ${args.join(" ")}`);
126
127
  return;
127
128
  }
128
- logger$8.log(` ${asks}\n`);
129
+ logger$7.log(` ${asks}\n`);
129
130
  for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
130
- logger$8.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
131
+ logger$7.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
131
132
  }
132
133
  /**
133
134
  * Trust the authority if this machine lacks it and its owner agrees.
@@ -144,7 +145,7 @@ async function ensureTrusted(root, url, options = {}) {
144
145
  if (await isTrusted(url)) return;
145
146
  if (!options.assumeYes) {
146
147
  if (!process.stdin.isTTY) {
147
- logger$8.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
148
+ logger$7.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
148
149
  return;
149
150
  }
150
151
  if (await declined(root)) return;
@@ -156,7 +157,7 @@ async function ensureTrusted(root, url, options = {}) {
156
157
  });
157
158
  if (!install) {
158
159
  await recordDeclined(root);
159
- logger$8.log(" Skipped. Run \"gkm trust\" later.");
160
+ logger$7.log(" Skipped. Run \"gkm trust\" later.");
160
161
  return;
161
162
  }
162
163
  }
@@ -1300,7 +1301,7 @@ async function serveFakes(fakes, ports) {
1300
1301
  const served = [];
1301
1302
  for (const [id, local] of Object.entries(fakes)) {
1302
1303
  if (local.image) continue;
1303
- const port = ports[require_sniffer.fakeKey(id)];
1304
+ const port = ports[require_state.fakeKey(id)];
1304
1305
  if (port === void 0) continue;
1305
1306
  const { handler } = (await import((0, node_url.pathToFileURL)(local.file).href)).default;
1306
1307
  const server = serve({
@@ -1321,7 +1322,7 @@ function closeFakes(served) {
1321
1322
  }
1322
1323
  //#endregion
1323
1324
  //#region src/exec/index.ts
1324
- const logger$7 = console;
1325
+ const logger$6 = console;
1325
1326
  /** `gkm exec` was given nothing to run. */
1326
1327
  var NoCommandSpecified = class extends Error {
1327
1328
  constructor() {
@@ -1343,18 +1344,18 @@ var NoCommandSpecified = class extends Error {
1343
1344
  async function execCommand(commandArgs, options = {}) {
1344
1345
  const cwd = options.cwd ?? process.cwd();
1345
1346
  if (!commandArgs[0]) throw new NoCommandSpecified();
1346
- const defaultEnv = require_sniffer.loadEnvFiles(".env");
1347
- if (defaultEnv.loaded.length > 0) logger$7.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1348
- const { credentials, secretsJsonPath, appName } = await require_sniffer.prepareEntryCredentials({ cwd });
1349
- if (appName) logger$7.log(`📦 App: ${appName}`);
1347
+ const defaultEnv = require_state.loadEnvFiles(".env");
1348
+ if (defaultEnv.loaded.length > 0) logger$6.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1349
+ const { credentials, secretsJsonPath, appName } = await require_state.prepareEntryCredentials({ cwd });
1350
+ if (appName) logger$6.log(`📦 App: ${appName}`);
1350
1351
  const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
1351
- if (secretCount > 0) logger$7.log(`🔐 Loaded ${secretCount} secret(s)`);
1352
+ if (secretCount > 0) logger$6.log(`🔐 Loaded ${secretCount} secret(s)`);
1352
1353
  const preloadDir = (0, node_path.join)(cwd, ".gkm");
1353
1354
  await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
1354
1355
  const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.mjs");
1355
- await require_sniffer.createCredentialsPreload(preloadPath, secretsJsonPath);
1356
+ await require_state.createCredentialsPreload(preloadPath, secretsJsonPath);
1356
1357
  const [cmd, ...args] = commandArgs;
1357
- logger$7.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
1358
+ logger$6.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
1358
1359
  const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
1359
1360
  const child = (0, node_child_process.spawn)(cmd, args, {
1360
1361
  cwd,
@@ -1368,7 +1369,7 @@ async function execCommand(commandArgs, options = {}) {
1368
1369
  const exitCode = await new Promise((resolve) => {
1369
1370
  child.on("close", (code) => resolve(code ?? 0));
1370
1371
  child.on("error", (error) => {
1371
- logger$7.error(`Failed to run command: ${error.message}`);
1372
+ logger$6.error(`Failed to run command: ${error.message}`);
1372
1373
  resolve(1);
1373
1374
  });
1374
1375
  });
@@ -1376,7 +1377,7 @@ async function execCommand(commandArgs, options = {}) {
1376
1377
  }
1377
1378
  //#endregion
1378
1379
  //#region src/dev/index.ts
1379
- const logger$6 = console;
1380
+ const logger$5 = console;
1380
1381
  /**
1381
1382
  * Normalize telescope configuration
1382
1383
  * @internal Exported for testing
@@ -1445,14 +1446,14 @@ const SUBSCRIBERS_ENV = "GKM_SUBSCRIBERS";
1445
1446
  async function devCommand(options) {
1446
1447
  if (options.subscribers === false) process.env[SUBSCRIBERS_ENV] = "off";
1447
1448
  if (options.entry) return entryDevCommand(options);
1448
- const defaultEnv = require_sniffer.loadEnvFiles(".env");
1449
- if (defaultEnv.loaded.length > 0) logger$6.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1449
+ const defaultEnv = require_state.loadEnvFiles(".env");
1450
+ if (defaultEnv.loaded.length > 0) logger$5.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1450
1451
  const appConfig = await require_config.loadAppConfig().catch((error) => {
1451
1452
  if (error instanceof require_config.NotInAnApp || error instanceof require_config.NotABackendApp) return;
1452
1453
  throw error;
1453
1454
  });
1454
1455
  if (!appConfig) {
1455
- logger$6.log("🔎 Reading gkm.config.ts and the constructs");
1456
+ logger$5.log("🔎 Reading gkm.config.ts and the constructs");
1456
1457
  const { workspace: everything } = await require_config.loadWorkspaceConfig();
1457
1458
  return workspaceDevCommand(everything, options);
1458
1459
  }
@@ -1461,16 +1462,16 @@ async function devCommand(options) {
1461
1462
  const secretsRoot = appConfig.workspaceRoot;
1462
1463
  const workspaceAppName = appConfig.appName;
1463
1464
  const assigned = await require_appPorts.assignAppPorts(appConfig.workspace, [workspaceAppName], {
1464
- free: require_sniffer.isPortAvailable,
1465
+ free: require_state.isPortAvailable,
1465
1466
  holder: require_appPorts.holderOf
1466
1467
  });
1467
1468
  if (assigned.running.length > 0) throw new WorkspacePortsInUse(assigned.running);
1468
- for (const { app, from, to, holder } of assigned.moved) logger$6.log(`↪️ ${app}: ${from} is held by ${require_appPorts.describeHolder(holder)}, which is not this workspace's — using ${to}`);
1469
+ for (const { app, from, to, holder } of assigned.moved) logger$5.log(`↪️ ${app}: ${from} is held by ${require_appPorts.describeHolder(holder)}, which is not this workspace's — using ${to}`);
1469
1470
  const workspace = require_appPorts.withAppPorts(appConfig.workspace, assigned.ports);
1470
1471
  const workspaceAppPort = assigned.ports[workspaceAppName] ?? appConfig.app.port;
1471
1472
  process.env[require_appPorts.APP_TAG_ENV] = require_appPorts.appTag(appConfig.workspaceRoot, workspaceAppName);
1472
1473
  if (appConfig.app.entry) {
1473
- logger$6.log(`📄 Using entry point: ${appConfig.app.entry}`);
1474
+ logger$5.log(`📄 Using entry point: ${appConfig.app.entry}`);
1474
1475
  return entryDevCommand({
1475
1476
  ...options,
1476
1477
  entry: appConfig.app.entry,
@@ -1479,9 +1480,9 @@ async function devCommand(options) {
1479
1480
  });
1480
1481
  }
1481
1482
  if (config.env) {
1482
- const { loaded, missing } = require_sniffer.loadEnvFiles(config.env, appRoot);
1483
- if (loaded.length > 0) logger$6.log(`📦 Loaded env: ${loaded.join(", ")}`);
1484
- if (missing.length > 0) logger$6.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
1483
+ const { loaded, missing } = require_state.loadEnvFiles(config.env, appRoot);
1484
+ if (loaded.length > 0) logger$5.log(`📦 Loaded env: ${loaded.join(", ")}`);
1485
+ if (missing.length > 0) logger$5.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
1485
1486
  }
1486
1487
  const telescope = normalizeTelescopeConfig(config.telescope);
1487
1488
  const hooks = normalizeHooksConfig(config.hooks, appRoot);
@@ -1492,7 +1493,7 @@ async function devCommand(options) {
1492
1493
  appRoot,
1493
1494
  target: "server",
1494
1495
  enableOpenApi,
1495
- cacheBackend: require_sniffer.cacheBackendFor(require_sniffer.providerOf(workspace ?? config)),
1496
+ cacheBackend: require_state.cacheBackendFor(require_state.providerOf(workspace ?? config)),
1496
1497
  telescope,
1497
1498
  databaseApi: true,
1498
1499
  hooks,
@@ -1500,22 +1501,22 @@ async function devCommand(options) {
1500
1501
  bustCache,
1501
1502
  serveEmpty: true
1502
1503
  }));
1503
- logger$6.log(`🔨 Building ${workspaceAppName ?? "the app"}`);
1504
+ logger$5.log(`🔨 Building ${workspaceAppName ?? "the app"}`);
1504
1505
  const initial = await build();
1505
1506
  const runtime = config.runtime ?? "node";
1506
1507
  let secretsJsonPath;
1507
1508
  let publicUrl;
1508
1509
  let manifest;
1509
- const appSecrets = await require_sniffer.loadSecretsForApp(workspace ? await require_store.secretsStoreFor(workspace, config.stages.local) : new require_store.FileSecretsStore(secretsRoot), config.stages.local, workspaceAppName);
1510
+ const appSecrets = await require_state.loadSecretsForApp(workspace ? await require_store.secretsStoreFor(workspace, config.stages.local) : new require_store.FileSecretsStore(secretsRoot), config.stages.local, workspaceAppName);
1510
1511
  if (workspace) {
1511
- const reconciled = await require_sniffer.reconcileWorkspace(workspace, {
1512
+ const reconciled = await require_state.reconcileWorkspace(workspace, {
1512
1513
  stage: workspace.stages.local,
1513
1514
  ...options.fake ? { fake: true } : {},
1514
- progress: (message) => logger$6.log(message)
1515
+ progress: (message) => logger$5.log(message)
1515
1516
  });
1516
1517
  if (reconciled.services.length > 0) {
1517
- logger$6.log("🐳 Services");
1518
- for (const line of require_sniffer.describeServices(reconciled.services)) logger$6.log(line);
1518
+ logger$5.log("🐳 Services");
1519
+ for (const line of require_state.describeServices(reconciled.services)) logger$5.log(line);
1519
1520
  }
1520
1521
  Object.assign(appSecrets, reconciled.env);
1521
1522
  const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && require_derive.appKey(r.id) === workspaceAppName);
@@ -1582,11 +1583,11 @@ async function devCommand(options) {
1582
1583
  cwd: appRoot
1583
1584
  });
1584
1585
  watcher.on("error", (error) => {
1585
- logger$6.error("❌ Watcher error:", error);
1586
+ logger$5.error("❌ Watcher error:", error);
1586
1587
  });
1587
1588
  let rebuildTimeout = null;
1588
1589
  watcher.on("change", async (path) => {
1589
- logger$6.log(`🔄 ${path} changed — rebuilding`);
1590
+ logger$5.log(`🔄 ${path} changed — rebuilding`);
1590
1591
  if (rebuildTimeout) clearTimeout(rebuildTimeout);
1591
1592
  rebuildTimeout = setTimeout(async () => {
1592
1593
  try {
@@ -1595,8 +1596,8 @@ async function devCommand(options) {
1595
1596
  await devServer.restart();
1596
1597
  } catch (error) {
1597
1598
  const err = error;
1598
- logger$6.error(`❌ Rebuild failed: ${err.message}`);
1599
- if (err.stack) logger$6.error(err.stack);
1599
+ logger$5.error(`❌ Rebuild failed: ${err.message}`);
1600
+ if (err.stack) logger$5.error(err.stack);
1600
1601
  }
1601
1602
  }, 300);
1602
1603
  });
@@ -1604,13 +1605,13 @@ async function devCommand(options) {
1604
1605
  const shutdown = () => {
1605
1606
  if (isShuttingDown) return;
1606
1607
  isShuttingDown = true;
1607
- logger$6.log("\n🛑 Shutting down...");
1608
+ logger$5.log("\n🛑 Shutting down...");
1608
1609
  Promise.all([
1609
1610
  watcher.close(),
1610
1611
  devServer.stop(),
1611
1612
  discovery.leave()
1612
1613
  ]).catch((err) => {
1613
- logger$6.error("Error during shutdown:", err);
1614
+ logger$5.error("Error during shutdown:", err);
1614
1615
  }).finally(() => {
1615
1616
  process.exit(0);
1616
1617
  });
@@ -1810,17 +1811,17 @@ async function loadDevSecrets(workspace) {
1810
1811
  async function workspaceDevCommand(configured, options) {
1811
1812
  const scoped = options.app ? [options.app] : options.filter ? [] : Object.keys(configured.apps);
1812
1813
  const assigned = await require_appPorts.assignAppPorts(configured, scoped, {
1813
- free: require_sniffer.isPortAvailable,
1814
+ free: require_state.isPortAvailable,
1814
1815
  holder: require_appPorts.holderOf
1815
1816
  });
1816
1817
  if (assigned.running.length > 0) throw new WorkspacePortsInUse(assigned.running);
1817
- for (const { app, from, to, holder } of assigned.moved) logger$6.log(`↪️ ${app}: ${from} is held by ${require_appPorts.describeHolder(holder)}, which is not this workspace's — using ${to}`);
1818
+ for (const { app, from, to, holder } of assigned.moved) logger$5.log(`↪️ ${app}: ${from} is held by ${require_appPorts.describeHolder(holder)}, which is not this workspace's — using ${to}`);
1818
1819
  const workspace = require_appPorts.withAppPorts(configured, assigned.ports);
1819
1820
  const appCount = Object.keys(workspace.apps).length;
1820
1821
  const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
1821
1822
  const conflicts = checkPortConflicts(workspace);
1822
1823
  if (conflicts.length > 0) {
1823
- for (const conflict of conflicts) logger$6.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1824
+ for (const conflict of conflicts) logger$5.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1824
1825
  throw new Error("Port conflicts detected. Please assign unique ports to each app.");
1825
1826
  }
1826
1827
  if (frontendApps.length > 0) {
@@ -1829,35 +1830,35 @@ async function workspaceDevCommand(configured, options) {
1829
1830
  for (const result of validationResults) {
1830
1831
  if (!result.valid) {
1831
1832
  hasErrors = true;
1832
- logger$6.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1833
- for (const error of result.errors) logger$6.error(` • ${error}`);
1833
+ logger$5.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1834
+ for (const error of result.errors) logger$5.error(` • ${error}`);
1834
1835
  }
1835
- for (const warning of result.warnings) logger$6.warn(` ⚠️ ${result.appName}: ${warning}`);
1836
+ for (const warning of result.warnings) logger$5.warn(` ⚠️ ${result.appName}: ${warning}`);
1836
1837
  }
1837
1838
  if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
1838
1839
  }
1839
1840
  const rawSecrets = await loadDevSecrets(workspace);
1840
- const reconciled = await require_sniffer.reconcileWorkspace(workspace, {
1841
+ const reconciled = await require_state.reconcileWorkspace(workspace, {
1841
1842
  stage: workspace.stages.local,
1842
1843
  ...options.fake ? { fake: true } : {},
1843
- progress: (message) => logger$6.log(message)
1844
+ progress: (message) => logger$5.log(message)
1844
1845
  });
1845
1846
  if (reconciled.services.length > 0) {
1846
- logger$6.log("🐳 Services");
1847
- for (const line of require_sniffer.describeServices(reconciled.services)) logger$6.log(line);
1847
+ logger$5.log("🐳 Services");
1848
+ for (const line of require_state.describeServices(reconciled.services)) logger$5.log(line);
1848
1849
  }
1849
1850
  const secretsEnv = {
1850
1851
  ...rawSecrets,
1851
1852
  ...reconciled.env
1852
1853
  };
1853
1854
  const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
1854
- for (const { id, port } of fakes) logger$6.log(`🎭 ${id} fake: http://localhost:${port}`);
1855
- logger$6.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
1855
+ for (const { id, port } of fakes) logger$5.log(`🎭 ${id} fake: http://localhost:${port}`);
1856
+ logger$5.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
1856
1857
  if (options.migrate || options.seed) {
1857
- const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-tx9-P9uz.cjs"));
1858
+ const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs"));
1858
1859
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1859
- } else await Promise.resolve().then(() => require("./migrate-tx9-P9uz.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1860
- logger$6.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1860
+ } else await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1861
+ logger$5.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1861
1862
  });
1862
1863
  const appUrls = {};
1863
1864
  for (const resource of reconciled.plan.resources) {
@@ -1878,11 +1879,11 @@ async function workspaceDevCommand(configured, options) {
1878
1879
  else {
1879
1880
  const { filters, unpackaged } = turboFilters(workspace);
1880
1881
  turboFilter = filters.flatMap((name) => ["--filter", name]);
1881
- if (unpackaged.length > 0) logger$6.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
1882
+ if (unpackaged.length > 0) logger$5.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
1882
1883
  }
1883
1884
  const buildOrder = require_workspace.getAppBuildOrder(workspace);
1884
1885
  const width = Math.max(...buildOrder.map((name) => name.length));
1885
- logger$6.log(`\n${workspace.name}: ${appCount} app(s)`);
1886
+ logger$5.log(`\n${workspace.name}: ${appCount} app(s)`);
1886
1887
  for (const appName of buildOrder) {
1887
1888
  const app = workspace.apps[appName];
1888
1889
  if (!app) continue;
@@ -1890,14 +1891,14 @@ async function workspaceDevCommand(configured, options) {
1890
1891
  const own = reconciled.plan.resources.find((r) => r.kind === "mobile-app" && require_derive.appKey(r.id) === appName);
1891
1892
  const scheme = own ? reconciled.env[own.envKey] : void 0;
1892
1893
  const device = Object.entries(reconciled.env).find(([key]) => key.endsWith("_DEVICE_URL"))?.[1];
1893
- logger$6.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
1894
+ logger$5.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
1894
1895
  continue;
1895
1896
  }
1896
1897
  const local = `http://localhost:${app.port}`;
1897
1898
  const url = Object.entries(appUrls).find(([id]) => require_derive.appKey(id) === appName)?.[1];
1898
- logger$6.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
1899
+ logger$5.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
1899
1900
  }
1900
- logger$6.log("");
1901
+ logger$5.log("");
1901
1902
  const configFiles = [
1902
1903
  "gkm.config.ts",
1903
1904
  "gkm.config.js",
@@ -1942,10 +1943,10 @@ async function workspaceDevCommand(configured, options) {
1942
1943
  ...secretsEnv,
1943
1944
  NODE_ENV: "development",
1944
1945
  ...configPath ? { GKM_CONFIG_PATH: configPath } : {},
1945
- ...options.fake ? { [require_sniffer.FAKE_ENV]: "1" } : {},
1946
+ ...options.fake ? { [require_state.FAKE_ENV]: "1" } : {},
1946
1947
  [DISCOVERY_QUIET_ENV]: "1"
1947
1948
  };
1948
- logger$6.log(`🚀 Starting ${appCount} app(s) — each builds before it answers, and says so when it is ready`);
1949
+ logger$5.log(`🚀 Starting ${appCount} app(s) — each builds before it answers, and says so when it is ready`);
1949
1950
  const turboProcess = (0, node_child_process.spawn)("pnpm", [
1950
1951
  "turbo",
1951
1952
  "run",
@@ -1961,7 +1962,7 @@ async function workspaceDevCommand(configured, options) {
1961
1962
  const shutdown = () => {
1962
1963
  if (isShuttingDown) return;
1963
1964
  isShuttingDown = true;
1964
- logger$6.log("\n🛑 Shutting down workspace...");
1965
+ logger$5.log("\n🛑 Shutting down workspace...");
1965
1966
  closeFakes(fakes);
1966
1967
  discovery.leave();
1967
1968
  const pid = turboProcess.pid;
@@ -1987,7 +1988,7 @@ async function workspaceDevCommand(configured, options) {
1987
1988
  process.on("SIGTERM", shutdown);
1988
1989
  return new Promise((resolve, reject) => {
1989
1990
  turboProcess.on("error", (error) => {
1990
- logger$6.error("❌ Turbo error:", error);
1991
+ logger$5.error("❌ Turbo error:", error);
1991
1992
  reject(error);
1992
1993
  });
1993
1994
  turboProcess.on("exit", (code) => {
@@ -2005,23 +2006,23 @@ async function entryDevCommand(options) {
2005
2006
  if (!entry) throw new Error("--entry requires a file path");
2006
2007
  const entryPath = (0, node_path.resolve)(process.cwd(), entry);
2007
2008
  if (!(0, node_fs.existsSync)(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
2008
- const defaultEnv = require_sniffer.loadEnvFiles(".env");
2009
- if (defaultEnv.loaded.length > 0) logger$6.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
2010
- const { credentials, resolvedPort, secretsJsonPath, appName } = await require_sniffer.prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
2011
- if (appName) logger$6.log(`📦 App: ${appName} (port ${resolvedPort})`);
2012
- logger$6.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
2013
- if (Object.keys(credentials).length > 1) logger$6.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
2009
+ const defaultEnv = require_state.loadEnvFiles(".env");
2010
+ if (defaultEnv.loaded.length > 0) logger$5.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
2011
+ const { credentials, resolvedPort, secretsJsonPath, appName } = await require_state.prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
2012
+ if (appName) logger$5.log(`📦 App: ${appName} (port ${resolvedPort})`);
2013
+ logger$5.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
2014
+ if (Object.keys(credentials).length > 1) logger$5.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
2014
2015
  const wrapperDir = (0, node_path.join)(process.cwd(), ".gkm");
2015
2016
  await (0, node_fs_promises.mkdir)(wrapperDir, { recursive: true });
2016
2017
  const wrapperPath = (0, node_path.join)(wrapperDir, "entry-wrapper.ts");
2017
- await require_sniffer.createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
2018
+ await require_state.createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
2018
2019
  const runner = new EntryRunner(wrapperPath, entryPath, watch, resolvedPort);
2019
2020
  await runner.start();
2020
2021
  let isShuttingDown = false;
2021
2022
  const shutdown = () => {
2022
2023
  if (isShuttingDown) return;
2023
2024
  isShuttingDown = true;
2024
- logger$6.log("\n🛑 Shutting down...");
2025
+ logger$5.log("\n🛑 Shutting down...");
2025
2026
  runner.stop();
2026
2027
  process.exit(0);
2027
2028
  };
@@ -2057,14 +2058,14 @@ var EntryRunner = class {
2057
2058
  });
2058
2059
  let restartTimeout = null;
2059
2060
  this.watcher.on("change", (path) => {
2060
- logger$6.log(`📝 File changed: ${path}`);
2061
+ logger$5.log(`📝 File changed: ${path}`);
2061
2062
  if (restartTimeout) clearTimeout(restartTimeout);
2062
2063
  restartTimeout = setTimeout(async () => {
2063
- logger$6.log("🔄 Restarting...");
2064
+ logger$5.log("🔄 Restarting...");
2064
2065
  await this.restart();
2065
2066
  }, 300);
2066
2067
  });
2067
- logger$6.log(`👀 Watching for changes in: ${watchDir}`);
2068
+ logger$5.log(`👀 Watching for changes in: ${watchDir}`);
2068
2069
  }
2069
2070
  }
2070
2071
  async runProcess() {
@@ -2080,17 +2081,17 @@ var EntryRunner = class {
2080
2081
  });
2081
2082
  this.isRunning = true;
2082
2083
  this.childProcess.on("error", (error) => {
2083
- logger$6.error("❌ Process error:", error);
2084
+ logger$5.error("❌ Process error:", error);
2084
2085
  });
2085
2086
  this.childProcess.on("exit", (code) => {
2086
- if (code !== null && code !== 0 && code !== 143) logger$6.error(`❌ Process exited with code ${code}`);
2087
+ if (code !== null && code !== 0 && code !== 143) logger$5.error(`❌ Process exited with code ${code}`);
2087
2088
  this.isRunning = false;
2088
2089
  });
2089
2090
  await new Promise((resolve) => setTimeout(resolve, 500));
2090
2091
  if (this.isRunning) {
2091
- logger$6.log("");
2092
- logger$6.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
2093
- logger$6.log("");
2092
+ logger$5.log("");
2093
+ logger$5.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
2094
+ logger$5.log("");
2094
2095
  }
2095
2096
  }
2096
2097
  async restart() {
@@ -2209,7 +2210,7 @@ start({
2209
2210
  * lets a console read what this machine is running.
2210
2211
  */
2211
2212
  function announce(discovery) {
2212
- logger$6.log(`🧭 Discovery: ${discovery.connectUrl}`);
2213
+ logger$5.log(`🧭 Discovery: ${discovery.connectUrl}`);
2213
2214
  }
2214
2215
  /** Run `fn` with `console.log` muted — warnings and errors still print. */
2215
2216
  async function quietly(fn) {
@@ -2323,11 +2324,11 @@ var DevServer = class {
2323
2324
  this.startTime = Date.now();
2324
2325
  if (this.isRunning) await this.stop();
2325
2326
  if (this.portExplicit) {
2326
- if (!await require_sniffer.isPortAvailable(this.requestedPort)) throw new DevPortInUse(this.requestedPort, listenerOn(this.requestedPort));
2327
+ if (!await require_state.isPortAvailable(this.requestedPort)) throw new DevPortInUse(this.requestedPort, listenerOn(this.requestedPort));
2327
2328
  this.actualPort = this.requestedPort;
2328
2329
  } else {
2329
- this.actualPort = await require_sniffer.findAvailablePort(this.requestedPort);
2330
- if (this.actualPort !== this.requestedPort) logger$6.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
2330
+ this.actualPort = await require_state.findAvailablePort(this.requestedPort);
2331
+ if (this.actualPort !== this.requestedPort) logger$5.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
2331
2332
  }
2332
2333
  const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", "server", "server.ts");
2333
2334
  await this.createServerEntry();
@@ -2347,11 +2348,11 @@ var DevServer = class {
2347
2348
  });
2348
2349
  this.isRunning = true;
2349
2350
  this.serverProcess.on("error", (error) => {
2350
- logger$6.error("❌ Server error:", error);
2351
+ logger$5.error("❌ Server error:", error);
2351
2352
  });
2352
2353
  const child = this.serverProcess;
2353
2354
  child.on("exit", (code, signal) => {
2354
- if (code !== null && code !== 0 && signal !== "SIGTERM") logger$6.error(`❌ Server exited with code ${code}`);
2355
+ if (code !== null && code !== 0 && signal !== "SIGTERM") logger$5.error(`❌ Server exited with code ${code}`);
2355
2356
  this.isRunning = false;
2356
2357
  if (this.serverProcess === child) this.onStatus?.("stopped");
2357
2358
  });
@@ -2360,13 +2361,13 @@ var DevServer = class {
2360
2361
  const local = `http://localhost:${this.actualPort}`;
2361
2362
  const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
2362
2363
  const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
2363
- logger$6.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
2364
+ logger$5.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
2364
2365
  const tools = this.selfServing ? [] : [
2365
2366
  ...this.enableOpenApi ? ["docs /__docs"] : [],
2366
2367
  ...this.telescope ? [`telescope ${this.telescope.path}`] : [],
2367
2368
  ...this.databaseApi ? [`db ${this.databaseApi}`] : []
2368
2369
  ];
2369
- if (tools.length > 0) logger$6.log(` ${tools.join(" · ")}`);
2370
+ if (tools.length > 0) logger$5.log(` ${tools.join(" · ")}`);
2370
2371
  this.onStatus?.("ready");
2371
2372
  }
2372
2373
  }
@@ -2397,7 +2398,7 @@ var DevServer = class {
2397
2398
  await this.stop();
2398
2399
  let attempts = 0;
2399
2400
  while (attempts < 30) {
2400
- if (await require_sniffer.isPortAvailable(portToReuse)) break;
2401
+ if (await require_state.isPortAvailable(portToReuse)) break;
2401
2402
  await new Promise((resolve) => setTimeout(resolve, 100));
2402
2403
  attempts++;
2403
2404
  }
@@ -3062,7 +3063,7 @@ ${shutdown}`);
3062
3063
  }
3063
3064
  //#endregion
3064
3065
  //#region src/build/index.ts
3065
- const logger$5 = console;
3066
+ const logger$4 = console;
3066
3067
  /**
3067
3068
  * Where `gkm build --stage` leaves the key its bundle's secrets were encrypted
3068
3069
  * with, beside the bundle in `.gkm/server/` and readable by its owner alone.
@@ -3110,12 +3111,12 @@ async function buildCommand(options) {
3110
3111
  const isAtWorkspaceRoot = cwd === workspaceRoot;
3111
3112
  const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => (0, node_path.resolve)(workspaceRoot, app.path) !== workspaceRoot);
3112
3113
  if (isAtWorkspaceRoot && someAppIsElsewhere) {
3113
- logger$5.log("📦 Detected workspace configuration");
3114
+ logger$4.log("📦 Detected workspace configuration");
3114
3115
  return workspaceBuildCommand(loadedConfig.workspace, options);
3115
3116
  }
3116
3117
  }
3117
3118
  const config = loadedConfig.type !== "workspace" ? await require_config.loadConfig() : (0, node_path.resolve)(process.cwd()) === (0, node_path.resolve)(loadedConfig.workspace.root) ? rootGkmConfig(loadedConfig.workspace) : (await require_config.loadAppConfig()).gkmConfig;
3118
- const target = options.provider ?? require_sniffer.providerOf(loadedConfig.workspace);
3119
+ const target = options.provider ?? require_state.providerOf(loadedConfig.workspace);
3119
3120
  const workspaceRoot = loadedConfig.workspace.root;
3120
3121
  const appRoot = process.cwd();
3121
3122
  const output = await buildOneApp({
@@ -3147,22 +3148,22 @@ async function declaredIn(config, cwd) {
3147
3148
  */
3148
3149
  function backendsOf(workspace) {
3149
3150
  return {
3150
- cache: require_sniffer.cacheBackendFor(require_sniffer.providerOf(workspace)),
3151
+ cache: require_state.cacheBackendFor(require_state.providerOf(workspace)),
3151
3152
  email: "ses"
3152
3153
  };
3153
3154
  }
3154
3155
  /** One app's build, from its config and the command's options. */
3155
3156
  async function buildOneApp(input) {
3156
3157
  const { config, options, workspace, appRoot, target } = input;
3157
- const cacheBackend = require_sniffer.cacheBackendFor(require_sniffer.providerOf(workspace));
3158
+ const cacheBackend = require_state.cacheBackendFor(require_state.providerOf(workspace));
3158
3159
  const production = normalizeProductionConfig(options.production ?? false);
3159
- if (production) logger$5.log(`🏭 Building for PRODUCTION`);
3160
- logger$5.log(`Building for ${target}`);
3161
- logger$5.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
3160
+ if (production) logger$4.log(`🏭 Building for PRODUCTION`);
3161
+ logger$4.log(`Building for ${target}`);
3162
+ logger$4.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
3162
3163
  const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
3163
- if (telescope) logger$5.log(`🔭 Telescope enabled at ${telescope.path}`);
3164
+ if (telescope) logger$4.log(`🔭 Telescope enabled at ${telescope.path}`);
3164
3165
  const hooks = normalizeHooksConfig(config.hooks, appRoot);
3165
- if (hooks) logger$5.log(`🪝 Server hooks enabled`);
3166
+ if (hooks) logger$4.log(`🪝 Server hooks enabled`);
3166
3167
  return buildApp({
3167
3168
  config,
3168
3169
  workspaceRoot: workspace.root,
@@ -3204,7 +3205,7 @@ async function writeManifest(input) {
3204
3205
  });
3205
3206
  const path = (0, node_path.join)(manifestDir, `${target}.ts`);
3206
3207
  await (0, node_fs_promises.writeFile)(path, require_emit.manifestModule(folded, backends));
3207
- logger$5.log(`Manifest: ${(0, node_path.relative)(process.cwd(), path)}`);
3208
+ logger$4.log(`Manifest: ${(0, node_path.relative)(process.cwd(), path)}`);
3208
3209
  }
3209
3210
  /**
3210
3211
  * Turn one app's constructs into its generated entry.
@@ -3232,11 +3233,11 @@ async function buildApp(input) {
3232
3233
  });
3233
3234
  if (selfServing) {
3234
3235
  await writeSurfaceEntry((0, node_path.join)(appRoot, ".gkm", "server"), selfServing.source);
3235
- logger$5.log(`Generated a server for ${selfServing.id} from its own declaration`);
3236
+ logger$4.log(`Generated a server for ${selfServing.id} from its own declaration`);
3236
3237
  if (production?.bundle && !input.skipBundle) {
3237
3238
  const outputDir = (0, node_path.join)(appRoot, ".gkm", "server");
3238
3239
  const entryPoint = await writeSurfaceServer(outputDir, production);
3239
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-C8uJf324.cjs"));
3240
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-D6MlIfBT.cjs"));
3240
3241
  await bundleServer({
3241
3242
  entryPoint,
3242
3243
  outputDir: (0, node_path.join)(outputDir, "dist"),
@@ -3245,7 +3246,7 @@ async function buildApp(input) {
3245
3246
  external: production.external,
3246
3247
  ...input.stage ? { stage: input.stage } : {}
3247
3248
  });
3248
- logger$5.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
3249
+ logger$4.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
3249
3250
  }
3250
3251
  return { selfServing: selfServing.id };
3251
3252
  }
@@ -3291,14 +3292,14 @@ async function buildApp(input) {
3291
3292
  topicGenerator.load(code, appRoot, bustCache)
3292
3293
  ]);
3293
3294
  const allEndpoints = servedBy(loadedEndpoints, derived.surface);
3294
- logger$5.log(`Found ${allEndpoints.length} endpoints`);
3295
- logger$5.log(`Found ${allFunctions.length} functions`);
3296
- logger$5.log(`Found ${allCrons.length} crons`);
3297
- logger$5.log(`Found ${allSubscribers.length} subscribers`);
3298
- logger$5.log(`Found ${allQueues.length} queues`);
3299
- logger$5.log(`Found ${allTopics.length} topics`);
3295
+ logger$4.log(`Found ${allEndpoints.length} endpoints`);
3296
+ logger$4.log(`Found ${allFunctions.length} functions`);
3297
+ logger$4.log(`Found ${allCrons.length} crons`);
3298
+ logger$4.log(`Found ${allSubscribers.length} subscribers`);
3299
+ logger$4.log(`Found ${allQueues.length} queues`);
3300
+ logger$4.log(`Found ${allTopics.length} topics`);
3300
3301
  if (!input.serveEmpty && allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
3301
- logger$5.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
3302
+ logger$4.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
3302
3303
  return {};
3303
3304
  }
3304
3305
  if (production?.subscribers === "include" && derived.surface) {
@@ -3311,7 +3312,7 @@ async function buildApp(input) {
3311
3312
  [allQueues.length, "queue consumer"],
3312
3313
  [allSubscribers.length, "subscriber"]
3313
3314
  ].filter(([n]) => n > 0).map(([n, what]) => `${n} ${what}${n === 1 ? "" : "s"}`);
3314
- if (left.length > 0) logger$5.log(`Serving ${derived.surface.id} only: leaving out ${left.join(", ")} — they run in a Worker's image`);
3315
+ if (left.length > 0) logger$4.log(`Serving ${derived.surface.id} only: leaving out ${left.join(", ")} — they run in a Worker's image`);
3315
3316
  }
3316
3317
  const result = await buildForTarget(target, buildContext, appRoot, workspaceRoot, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics, enableOpenApi, input.skipBundle ?? false, input.stage);
3317
3318
  await require_openapi.generateOpenApi(allEndpoints.map(({ construct }) => construct), {
@@ -3334,7 +3335,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3334
3335
  force: true
3335
3336
  });
3336
3337
  await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
3337
- logger$5.log(`\nGenerating handlers for ${target}`);
3338
+ logger$4.log(`\nGenerating handlers for ${target}`);
3338
3339
  const options = {
3339
3340
  target,
3340
3341
  root: workspaceRoot
@@ -3350,7 +3351,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3350
3351
  queueGenerator.build(context, queues, outputDir, options),
3351
3352
  topicGenerator.build(context, topics, outputDir, options)
3352
3353
  ]);
3353
- logger$5.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${target}`);
3354
+ logger$4.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${target}`);
3354
3355
  const fields = {
3355
3356
  ...context.surface ? { surface: context.surface.id } : {},
3356
3357
  functions: functionInfos,
@@ -3373,8 +3374,8 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3373
3374
  };
3374
3375
  let masterKey;
3375
3376
  if (context.production?.bundle && !skipBundle) {
3376
- logger$5.log(`\n📦 Bundling production server...`);
3377
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-C8uJf324.cjs"));
3377
+ logger$4.log(`\n📦 Bundling production server...`);
3378
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-D6MlIfBT.cjs"));
3378
3379
  const allConstructs = [
3379
3380
  ...endpoints.map((e) => e.construct),
3380
3381
  ...functions.map((f) => f.construct),
@@ -3391,13 +3392,13 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3391
3392
  stage,
3392
3393
  constructs: allConstructs
3393
3394
  })).masterKey;
3394
- logger$5.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
3395
+ logger$4.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
3395
3396
  if (masterKey) {
3396
3397
  const keyPath = (0, node_path.join)(outputDir, MASTER_KEY_FILE);
3397
3398
  await (0, node_fs_promises.writeFile)(keyPath, `${masterKey}\n`, { mode: 384 });
3398
3399
  await (0, node_fs_promises.chmod)(keyPath, 384);
3399
- logger$5.log(`\n🔐 Secrets encrypted for deployment`);
3400
- logger$5.log(` Run with GKM_MASTER_KEY set to the key in ${(0, node_path.relative)(appRoot, keyPath)} (fingerprint ${require_sniffer.keyFingerprint(masterKey)})`);
3400
+ logger$4.log(`\n🔐 Secrets encrypted for deployment`);
3401
+ logger$4.log(` Run with GKM_MASTER_KEY set to the key in ${(0, node_path.relative)(appRoot, keyPath)} (fingerprint ${require_sniffer.keyFingerprint(masterKey)})`);
3401
3402
  }
3402
3403
  }
3403
3404
  return {
@@ -3498,20 +3499,20 @@ async function workspaceBuildCommand(workspace, options) {
3498
3499
  const apps = Object.entries(workspace.apps);
3499
3500
  const backendApps = apps.filter(([, app]) => app.type === "backend");
3500
3501
  const frontendApps = apps.filter(([, app]) => app.type === "web");
3501
- logger$5.log(`\n🏗️ Building workspace: ${workspace.name}`);
3502
- logger$5.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
3503
- logger$5.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
3504
- if (options.production) logger$5.log(` 🏭 Production mode enabled`);
3502
+ logger$4.log(`\n🏗️ Building workspace: ${workspace.name}`);
3503
+ logger$4.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
3504
+ logger$4.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
3505
+ if (options.production) logger$4.log(` 🏭 Production mode enabled`);
3505
3506
  const buildOrder = require_workspace.getAppBuildOrder(workspace);
3506
- logger$5.log(` Build order: ${buildOrder.join(" → ")}`);
3507
+ logger$4.log(` Build order: ${buildOrder.join(" → ")}`);
3507
3508
  try {
3508
- const target = options.provider ?? require_sniffer.providerOf(workspace);
3509
+ const target = options.provider ?? require_state.providerOf(workspace);
3509
3510
  const builds = [];
3510
3511
  for (const appName of buildOrder) {
3511
3512
  const app = workspace.apps[appName];
3512
3513
  const config = require_workspace.getAppGkmConfig(workspace, appName);
3513
3514
  if (!app || app.type !== "backend" || !config) continue;
3514
- logger$5.log(`\n⚙️ ${appName}`);
3515
+ logger$4.log(`\n⚙️ ${appName}`);
3515
3516
  const output = await buildOneApp({
3516
3517
  config,
3517
3518
  options,
@@ -3536,8 +3537,8 @@ async function workspaceBuildCommand(workspace, options) {
3536
3537
  const pm = detectPackageManager();
3537
3538
  const [turboBin, turboArgs] = getTurboArgs(pm, filters, { only: true });
3538
3539
  if (filters.length > 0) {
3539
- logger$5.log(`\n📦 Using ${pm} with Turbo for the other apps...\n`);
3540
- logger$5.log(`Running: ${[turboBin, ...turboArgs].join(" ")}`);
3540
+ logger$4.log(`\n📦 Using ${pm} with Turbo for the other apps...\n`);
3541
+ logger$4.log(`Running: ${[turboBin, ...turboArgs].join(" ")}`);
3541
3542
  const sandbox = options.sandbox ?? new require_local.LocalSandbox({
3542
3543
  root: workspace.root,
3543
3544
  passEnv: ["TURBO_TOKEN"]
@@ -3562,15 +3563,15 @@ async function workspaceBuildCommand(workspace, options) {
3562
3563
  outputPath
3563
3564
  });
3564
3565
  }
3565
- logger$5.log(`\n✅ Workspace build complete!`);
3566
- logger$5.log(`\n📋 Build Summary:`);
3566
+ logger$4.log(`\n✅ Workspace build complete!`);
3567
+ logger$4.log(`\n📋 Build Summary:`);
3567
3568
  for (const result of results) {
3568
3569
  const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
3569
- logger$5.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
3570
+ logger$4.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
3570
3571
  }
3571
3572
  } catch (error) {
3572
3573
  const errorMessage = error instanceof Error ? error.message : "Build failed";
3573
- logger$5.log(`\n❌ Build failed: ${errorMessage}`);
3574
+ logger$4.log(`\n❌ Build failed: ${errorMessage}`);
3574
3575
  for (const [appName, app] of apps) results.push({
3575
3576
  appName,
3576
3577
  type: app.type,
@@ -3600,1169 +3601,17 @@ function formatRoutes(routes) {
3600
3601
  return Array.isArray(routes) ? routes.join(", ") : routes;
3601
3602
  }
3602
3603
  //#endregion
3603
- //#region src/migrate/provider.ts
3604
- /**
3605
- * One construct's migrations and seeds, read from its folders.
3606
- *
3607
- * Two kinds of file, told apart by extension rather than by configuration: a
3608
- * script exports `up` (and may export `down`) — or `seed(db, { stage })`, in
3609
- * `seeds/` — and a
3610
- * `.sql` file is run whole. The second is what makes a folder independent of
3611
- * the client the app uses — Better Auth's schema is SQL whatever the
3612
- * application queries with.
3613
- */
3614
- const SCRIPTS = /* @__PURE__ */ new Set([
3615
- ".ts",
3616
- ".mts",
3617
- ".js",
3618
- ".mjs"
3619
- ]);
3620
- /** Two files in one folder that would be one migration. */
3621
- var DuplicateMigration = class extends Error {
3622
- folder;
3623
- migration;
3624
- constructor(folder, migration) {
3625
- super(`${folder} has two migrations named '${migration}' — a script and a .sql file with the same name. A migration is recorded by name, so only one of them could ever run; rename or remove the other.`);
3626
- this.folder = folder;
3627
- this.migration = migration;
3628
- this.name = "DuplicateMigration";
3629
- }
3630
- };
3631
- /** A script in a migrations folder that exports no `up`. */
3632
- var MigrationHasNoUp = class extends Error {
3633
- file;
3634
- constructor(file) {
3635
- super(`${file} is in a migrations folder but exports no \`up\` function. Export \`async function up(db: Kysely<unknown>)\`, or move the file out of the folder if it is not a migration.`);
3636
- this.file = file;
3637
- this.name = "MigrationHasNoUp";
3638
- }
3639
- };
3640
- /** A script in a seeds folder that exports no `seed`. */
3641
- var SeedHasNoSeed = class extends Error {
3642
- file;
3643
- constructor(file) {
3644
- super(`${file} is in a seeds folder but exports no \`seed\` function. Export \`async function seed(db, { stage })\`, or move the file out of the folder if it is not a seed.`);
3645
- this.file = file;
3646
- this.name = "SeedHasNoSeed";
3647
- }
3648
- };
3649
- /**
3650
- * The migration files in a folder, by name, without reading them.
3651
- *
3652
- * Also what `gkm dev` compares against a construct's history to report what
3653
- * is pending, which is why it is separate from loading them.
3654
- */
3655
- async function migrationFiles(folder) {
3656
- const entries = await (0, node_fs_promises.readdir)(folder, { withFileTypes: true }).catch((error) => {
3657
- if (error.code === "ENOENT") return [];
3658
- throw error;
3659
- });
3660
- const files = /* @__PURE__ */ new Map();
3661
- for (const entry of entries) {
3662
- if (!entry.isFile() || !isMigration(entry.name)) continue;
3663
- const name = entry.name.slice(0, -(0, node_path.extname)(entry.name).length);
3664
- if (files.has(name)) throw new DuplicateMigration(folder, name);
3665
- files.set(name, (0, node_path.join)(folder, entry.name));
3666
- }
3667
- return files;
3668
- }
3669
- /** A Kysely provider over one construct's folder. */
3670
- function folderProvider(folder, raw) {
3671
- return { async getMigrations() {
3672
- const migrations = {};
3673
- for (const [name, file] of await migrationFiles(folder)) migrations[name] = (0, node_path.extname)(file) === ".sql" ? await sqlMigration(file, raw) : await scriptMigration(file);
3674
- return migrations;
3675
- } };
3676
- }
3677
- /**
3678
- * The seeds in a folder, loaded, in name order.
3679
- *
3680
- * Every one runs on every pass — there is no history to consult — so the order
3681
- * is the only thing between two seeds where one reads what another writes.
3682
- * Name them to sort that way.
3683
- */
3684
- async function loadSeeds(folder, raw) {
3685
- const files = [...await migrationFiles(folder)].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
3686
- const seeds = [];
3687
- for (const [name, file] of files) seeds.push({
3688
- name,
3689
- run: (0, node_path.extname)(file) === ".sql" ? (await sqlMigration(file, raw)).up : await scriptSeed(file)
3690
- });
3691
- return seeds;
3692
- }
3693
- /**
3694
- * A seed script, imported as it is on disk now.
3695
- *
3696
- * Keyed by its modification time: a seed is run again after it is edited, and
3697
- * a process that has imported it once would otherwise run the old one.
3698
- */
3699
- async function scriptSeed(file) {
3700
- const url = (0, node_url.pathToFileURL)(file);
3701
- url.searchParams.set("mtime", String((await (0, node_fs_promises.stat)(file)).mtimeMs));
3702
- const module = await import(url.href);
3703
- if (typeof module.seed !== "function") throw new SeedHasNoSeed(file);
3704
- return module.seed;
3705
- }
3706
- function isMigration(file) {
3707
- if (file.endsWith(".d.ts")) return false;
3708
- if (/\.(spec|test)\.[cm]?[jt]s$/.test(file)) return false;
3709
- const extension = (0, node_path.extname)(file);
3710
- return extension === ".sql" || SCRIPTS.has(extension);
3711
- }
3712
- async function scriptMigration(file) {
3713
- const module = await import((0, node_url.pathToFileURL)(file).href);
3714
- if (typeof module.up !== "function") throw new MigrationHasNoUp(file);
3715
- return {
3716
- up: module.up,
3717
- ...typeof module.down === "function" ? { down: module.down } : {}
3718
- };
3719
- }
3720
- /**
3721
- * A `.sql` file, run whole.
3722
- *
3723
- * One query rather than split on `;`: with no parameters, Postgres takes
3724
- * several statements in one message, and splitting by hand breaks on the first
3725
- * `;` inside a string or a function body. There is no `down` — a generated
3726
- * schema is rolled back by a migration that says how, not by guessing.
3727
- */
3728
- async function sqlMigration(file, raw) {
3729
- const statements = await (0, node_fs_promises.readFile)(file, "utf-8");
3730
- return { up: async (db) => {
3731
- await raw.raw(statements).execute(db);
3732
- } };
3733
- }
3734
- //#endregion
3735
- //#region src/migrate/databases.ts
3736
- /**
3737
- * Bring each construct's schema up to its migrations folder.
3738
- *
3739
- * Which constructs, in what order, from which folder, is `migrationTargets`'
3740
- * answer and nobody else's. What is decided here is only how one target is
3741
- * run: as its owner, in its own schema, with a history of its own.
3742
- */
3743
- /** A folder under `db/` that names no construct. */
3744
- var UnknownMigrationFolder = class extends Error {
3745
- folder;
3746
- known;
3747
- constructor(folder, known) {
3748
- super(`${folder} names no database construct, so its migrations would never run. ${known.length > 0 ? `The folders that do: ${known.join(", ")}.` : "This project declares no database."} Rename the folder after the construct it belongs to.`);
3749
- this.folder = folder;
3750
- this.known = known;
3751
- this.name = "UnknownMigrationFolder";
3752
- }
3753
- };
3754
- /** A construct was asked for by a name that takes no migrations. */
3755
- var NoSuchMigrationTarget = class extends Error {
3756
- construct;
3757
- known;
3758
- constructor(construct, known) {
3759
- super(`'${construct}' is not a database construct. ` + (known.length > 0 ? `The ones with migrations: ${known.join(", ")}.` : "This project declares no database."));
3760
- this.construct = construct;
3761
- this.known = known;
3762
- this.name = "NoSuchMigrationTarget";
3763
- }
3764
- };
3765
- /**
3766
- * A migration left directly in a construct's folder, where nothing reads it.
3767
- *
3768
- * Migrations moved into `migrations/` so seeds could sit beside them. A file
3769
- * still at the old level would otherwise never run, and nothing would say so.
3770
- */
3771
- var MigrationsOutsideFolder = class extends Error {
3772
- folder;
3773
- files;
3774
- constructor(folder, files) {
3775
- super(`${folder} has ${files.join(", ")} directly inside it, where nothing runs them. Migrations live in ${folder}/migrations/ and seeds in ${folder}/seeds/ — move ${files.length === 1 ? "it" : "them"} there.`);
3776
- this.folder = folder;
3777
- this.files = files;
3778
- this.name = "MigrationsOutsideFolder";
3779
- }
3780
- };
3781
- /** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */
3782
- var UnknownDatabaseFolder = class extends Error {
3783
- folder;
3784
- constructor(folder) {
3785
- super(`${folder} is neither migrations/ nor seeds/, so nothing in it would ever run. A construct's folder holds those two; rename or remove it.`);
3786
- this.folder = folder;
3787
- this.name = "UnknownDatabaseFolder";
3788
- }
3789
- };
3790
- /** A seed failed; its own writes are rolled back, the ones before it stay. */
3791
- var SeedFailed = class extends Error {
3792
- construct;
3793
- seed;
3794
- cause;
3795
- constructor(construct, seed, cause) {
3796
- super(`${construct}: seed '${seed}' failed — ${cause instanceof Error ? cause.message : String(cause)}. Its writes were rolled back; fix it and run it again.`);
3797
- this.construct = construct;
3798
- this.seed = seed;
3799
- this.cause = cause;
3800
- this.name = "SeedFailed";
3801
- }
3802
- };
3803
- /** A construct with roles has no owner URL to migrate with. */
3804
- var NoOwnerCredential = class extends Error {
3805
- construct;
3806
- key;
3807
- constructor(construct, key) {
3808
- super(`${construct} has no ${key} to migrate with. Migrations run as the owner role — the runtime one cannot create a table — and nothing resolved its URL. Is the stage reconciled?`);
3809
- this.construct = construct;
3810
- this.key = key;
3811
- this.name = "NoOwnerCredential";
3812
- }
3813
- };
3814
- /** A migration failed; the ones before it in the run stay applied. */
3815
- var MigrationFailed = class extends Error {
3816
- construct;
3817
- migration;
3818
- cause;
3819
- constructor(construct, migration, cause) {
3820
- super(`${construct}: migration ${migration ? `'${migration}' ` : ""}failed — ${cause instanceof Error ? cause.message : String(cause)}`);
3821
- this.construct = construct;
3822
- this.migration = migration;
3823
- this.cause = cause;
3824
- this.name = "MigrationFailed";
3825
- }
3826
- };
3827
- /** The project's Kysely could not be found from where a database is declared. */
3828
- var KyselyNotFound = class extends Error {
3829
- from;
3830
- constructor(from) {
3831
- super(`Could not resolve 'kysely' from ${from}. Migrations run with the project's own Kysely — install it where the database is declared.`);
3832
- this.from = from;
3833
- this.name = "KyselyNotFound";
3834
- }
3835
- };
3836
- /** Apply every pending migration, construct by construct, parents first. */
3837
- async function migrateDatabases(options) {
3838
- const runs = [];
3839
- for (const target of await targetsFor(options)) {
3840
- if ((await migrationFiles((0, node_path.join)(options.root, target.migrations))).size === 0) {
3841
- runs.push({
3842
- target,
3843
- applied: []
3844
- });
3845
- continue;
3846
- }
3847
- const applied = await withMigrator(options, target, async (migrator) => {
3848
- const { error, results = [] } = await migrator.migrateToLatest();
3849
- if (error) {
3850
- const failed = results.find((result) => result.status === "Error");
3851
- throw new MigrationFailed(target.id, failed?.migrationName, error);
3852
- }
3853
- return results.filter((result) => result.status === "Success").map((result) => result.migrationName);
3854
- });
3855
- runs.push({
3856
- target,
3857
- applied
3858
- });
3859
- }
3860
- return runs;
3861
- }
3862
- /**
3863
- * Run every construct's seeds, parents first — each one, every time, on every
3864
- * stage, production included. Each is handed the stage, so a seed decides
3865
- * what belongs where.
3866
- *
3867
- * Seeds are reference data kept up to date by being run again, so there is no
3868
- * history: a seed is an upsert, and a changed one is applied by the next run.
3869
- * Each runs in its own transaction, as the construct's owner, so a failure
3870
- * leaves nothing half-written. Assumes the schema is migrated —
3871
- * {@link migrateAndSeed} is what makes that true.
3872
- */
3873
- async function seedDatabases(options) {
3874
- const runs = [];
3875
- for (const target of await targetsFor(options)) {
3876
- const folder = (0, node_path.join)(options.root, target.seeds);
3877
- if ((await migrationFiles(folder)).size === 0) {
3878
- runs.push({
3879
- target,
3880
- seeded: []
3881
- });
3882
- continue;
3883
- }
3884
- const seeded = await withOwner(options, target, async (db, kysely) => {
3885
- const names = [];
3886
- for (const seed of await loadSeeds(folder, kysely.sql)) {
3887
- try {
3888
- await db.transaction().execute((trx) => seed.run(trx, { stage: options.stage }));
3889
- } catch (error) {
3890
- throw new SeedFailed(target.id, seed.name, error);
3891
- }
3892
- names.push(seed.name);
3893
- }
3894
- return names;
3895
- });
3896
- runs.push({
3897
- target,
3898
- seeded
3899
- });
3900
- }
3901
- return runs;
3902
- }
3903
- /**
3904
- * Migrate, then seed: a seed only ever runs against the schema it was written
3905
- * for.
3906
- *
3907
- * With `only`, the constructs it lives in are migrated too — a tenant's seed
3908
- * cannot run before the database that owns its schema exists — and only it is
3909
- * seeded.
3910
- */
3911
- async function migrateAndSeed(options) {
3912
- const migrations = [];
3913
- for (const target of await withParents(options)) migrations.push(...await migrateDatabases({
3914
- ...options,
3915
- only: target.id
3916
- }));
3917
- return {
3918
- migrations,
3919
- seeds: await seedDatabases(options)
3920
- };
3921
- }
3922
- /** The targets `only` names, after the ones it lives in; all without it. */
3923
- async function withParents(options) {
3924
- const chosen = await targetsFor(options);
3925
- if (!options.only) return chosen;
3926
- const all = (0, _geekmidas_manifest.migrationTargets)(options.manifest);
3927
- const ids = /* @__PURE__ */ new Set();
3928
- for (const target of chosen) {
3929
- let current = target;
3930
- while (current) {
3931
- ids.add(current.id);
3932
- const parent = current.of;
3933
- current = parent ? all.find((t) => t.id === parent) : void 0;
3934
- }
3935
- }
3936
- return all.filter((target) => ids.has(target.id));
3937
- }
3938
- /**
3939
- * What each construct's folder has that its history does not — without
3940
- * applying any of it. What `gkm dev` reports.
3941
- */
3942
- async function pendingMigrations(options) {
3943
- const found = [];
3944
- for (const target of await targetsFor(options)) {
3945
- if ((await migrationFiles((0, node_path.join)(options.root, target.migrations))).size === 0) continue;
3946
- const pending = await withMigrator(options, target, async (migrator) => (await migrator.getMigrations()).filter((migration) => migration.executedAt === void 0).map((migration) => migration.name));
3947
- if (pending.length > 0) found.push({
3948
- target,
3949
- pending
3950
- });
3951
- }
3952
- return found;
3953
- }
3954
- /**
3955
- * The targets this run covers, with every folder under `db/` accounted for.
3956
- *
3957
- * A folder that names no construct is an error rather than something to skip:
3958
- * a typo in its name is otherwise a migration that silently never runs.
3959
- */
3960
- async function targetsFor(options) {
3961
- const targets = (0, _geekmidas_manifest.migrationTargets)(options.manifest);
3962
- const folders = targets.map((target) => target.folder);
3963
- for (const folder of await foldersIn((0, node_path.join)(options.root, _geekmidas_manifest.MIGRATIONS_ROOT))) {
3964
- const relative = `${_geekmidas_manifest.MIGRATIONS_ROOT}/${folder}`;
3965
- if (!folders.includes(relative)) throw new UnknownMigrationFolder(relative, folders);
3966
- await checkLayout(options.root, relative);
3967
- }
3968
- if (!options.only) return targets;
3969
- const id = (0, _geekmidas_manifest.canonicalId)(options.only);
3970
- const chosen = targets.filter((target) => target.id === id);
3971
- if (chosen.length === 0) throw new NoSuchMigrationTarget(options.only, targets.map((target) => target.id));
3972
- return chosen;
3973
- }
3974
- /**
3975
- * A construct's folder holds `migrations/` and `seeds/`, and nothing else —
3976
- * anything beside them is a file or folder nothing would ever run.
3977
- */
3978
- async function checkLayout(root, folder) {
3979
- const entries = await (0, node_fs_promises.readdir)((0, node_path.join)(root, folder), { withFileTypes: true });
3980
- const loose = entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name);
3981
- if (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);
3982
- for (const entry of entries) if (entry.isDirectory() && !_geekmidas_manifest.DATABASE_FOLDERS.includes(entry.name)) throw new UnknownDatabaseFolder(`${folder}/${entry.name}`);
3983
- }
3984
- async function foldersIn(dir) {
3985
- return (await (0, node_fs_promises.readdir)(dir, { withFileTypes: true }).catch((error) => {
3986
- if (error.code === "ENOENT") return [];
3987
- throw error;
3988
- })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
3989
- }
3990
- /**
3991
- * A migrator for one target, connected as its owner.
3992
- *
3993
- * No `migrationTableSchema`: the owner role's `search_path` is pinned to the
3994
- * construct's schema, so the history lands there — `app.kysely_migration` for
3995
- * the database, `auth_database.kysely_migration` for its tenant — and two
3996
- * constructs in one Postgres never share one.
3997
- *
3998
- * Unordered migrations are allowed. Files are timestamped, so a branch merged
3999
- * after another's migration ran carries an older one; refusing it would make
4000
- * every such merge a manual repair.
4001
- */
4002
- async function withMigrator(options, target, run) {
4003
- return withOwner(options, target, (db, { Migrator, sql }) => run(new Migrator({
4004
- db,
4005
- provider: folderProvider((0, node_path.join)(options.root, target.migrations), sql),
4006
- allowUnorderedMigrations: true
4007
- })));
4008
- }
4009
- /**
4010
- * One target's database, connected as its owner and closed afterwards — what
4011
- * migrations and seeds both run as.
4012
- */
4013
- async function withOwner(options, target, run) {
4014
- const kysely = await kyselyFrom(options.sources?.[target.id]?.file ?? (target.of ? options.sources?.[target.of]?.file : void 0) ?? (0, node_path.join)(options.root, "package.json"));
4015
- const db = new kysely.Kysely({ dialect: new kysely.PostgresDialect({ pool: new pg.default.Pool(poolConfig(ownerUrl(options, target))) }) });
4016
- try {
4017
- return await run(db, kysely);
4018
- } finally {
4019
- await db.destroy();
4020
- }
4021
- }
4022
- /**
4023
- * The owner's URL, with no fallback to the runtime one.
4024
- *
4025
- * The runtime role can create nothing, so falling back would only move the
4026
- * failure to the first `CREATE TABLE` and make it read like a permissions
4027
- * bug. The one exception is a database declared `roles: false`, where there is
4028
- * no owner — both URLs are the master credential by design.
4029
- */
4030
- function ownerUrl(options, target) {
4031
- const key = (0, _geekmidas_manifest.provideKey)(target.id, "ownerUrl");
4032
- const owner = options.env[key];
4033
- if (owner) return owner;
4034
- const database = options.manifest[target.of ?? target.id];
4035
- if (database?.kind === "database" && database.roles === false) {
4036
- const url = options.env[(0, _geekmidas_manifest.provideKey)(target.id, "url")];
4037
- if (url) return url;
4038
- }
4039
- throw new NoOwnerCredential(target.id, key);
4040
- }
4041
- /**
4042
- * Pool settings for a URL, with `?search_path=` as the startup option Postgres
4043
- * reads. Only a `roles: false` URL carries it — a role pins its own.
4044
- */
4045
- function poolConfig(url) {
4046
- const parsed = new URL(url);
4047
- const searchPath = parsed.searchParams.get("search_path");
4048
- if (!searchPath) return { connectionString: url };
4049
- parsed.searchParams.delete("search_path");
4050
- return {
4051
- connectionString: parsed.toString(),
4052
- options: `-c search_path=${searchPath}`
4053
- };
4054
- }
4055
- /** Kysely as the project resolves it from `from`. */
4056
- async function kyselyFrom(from) {
4057
- const require$2 = (0, node_module.createRequire)(from);
4058
- try {
4059
- const [main, migration] = await Promise.all([import((0, node_url.pathToFileURL)(require$2.resolve("kysely")).href), import((0, node_url.pathToFileURL)(require$2.resolve("kysely/migration")).href)]);
4060
- return {
4061
- Kysely: main.Kysely,
4062
- PostgresDialect: main.PostgresDialect,
4063
- sql: main.sql,
4064
- Migrator: migration.Migrator
4065
- };
4066
- } catch {
4067
- throw new KyselyNotFound(from);
4068
- }
4069
- }
4070
- //#endregion
4071
- //#region src/compose/docker.ts
4072
- /**
4073
- * The Docker operations `gkm compose` needs, behind an interface.
4074
- *
4075
- * Small on purpose, like reconcile's: the rules about *when* each runs — the
4076
- * registry is asked before anything is pulled, nothing is started for a tag
4077
- * that is missing — are asserted against a fake, and this file is only the
4078
- * wiring to the real CLI.
4079
- *
4080
- * Every call is an argument array. Image refs, tags and paths come from the
4081
- * workspace, and none of them reaches a shell.
4082
- */
4083
- /** How long a stack has to pass its health checks. */
4084
- const WAIT_TIMEOUT_S = 180;
4085
- /** `docker compose port` printed nothing a port can be read from. */
4086
- var ServicePortUnknown = class extends Error {
4087
- service;
4088
- inside;
4089
- output;
4090
- constructor(service, inside, output) {
4091
- super(`'${service}' publishes no host port for ${inside} (docker said: ${output.trim() || "nothing"}). Is it running? \`docker compose ps\` shows the stack.`);
4092
- this.service = service;
4093
- this.inside = inside;
4094
- this.output = output;
4095
- this.name = "ServicePortUnknown";
4096
- }
4097
- };
4098
- function compose(stack, args) {
4099
- return [
4100
- "compose",
4101
- "-p",
4102
- stack.project,
4103
- "-f",
4104
- stack.file,
4105
- ...args
4106
- ];
4107
- }
4108
- /** Run a program and keep everything it said, whatever it exits with. */
4109
- function capture(command, args, timeoutMs = 6e4) {
4110
- return new Promise((resolve, reject) => {
4111
- const child = (0, node_child_process.spawn)(command, [...args], {
4112
- stdio: [
4113
- "ignore",
4114
- "pipe",
4115
- "pipe"
4116
- ],
4117
- shell: false
4118
- });
4119
- let stdout = "";
4120
- let stderr = "";
4121
- child.stdout.on("data", (chunk) => {
4122
- stdout += chunk.toString();
4123
- });
4124
- child.stderr.on("data", (chunk) => {
4125
- stderr += chunk.toString();
4126
- });
4127
- const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
4128
- timer.unref?.();
4129
- child.on("error", (error) => {
4130
- clearTimeout(timer);
4131
- reject(error);
4132
- });
4133
- child.on("close", (code) => {
4134
- clearTimeout(timer);
4135
- resolve({
4136
- code,
4137
- stdout,
4138
- stderr
4139
- });
4140
- });
4141
- });
4142
- }
4143
- /**
4144
- * Whether a failed `docker manifest inspect` means the tag is not there, as
4145
- * opposed to the registry not answering.
4146
- *
4147
- * Read off the message because that is all the CLI gives. Anything not
4148
- * recognisably "no such tag" is treated as the registry being unreachable:
4149
- * a registry that refused credentials must not be reported as a tag that was
4150
- * never pushed, or the fix offered would be the wrong one.
4151
- */
4152
- function isMissingManifest(output) {
4153
- return /no such manifest|manifest unknown|manifest for .* not found|not found: manifest/i.test(output);
4154
- }
4155
- const dockerCompose = {
4156
- async lookup(ref) {
4157
- const { code, stdout, stderr } = await capture("docker", [
4158
- "manifest",
4159
- "inspect",
4160
- ref
4161
- ]);
4162
- if (code === 0) return {
4163
- ref,
4164
- status: "found"
4165
- };
4166
- const output = `${stderr}\n${stdout}`.trim();
4167
- return isMissingManifest(output) ? {
4168
- ref,
4169
- status: "missing"
4170
- } : {
4171
- ref,
4172
- status: "unreachable",
4173
- detail: output
4174
- };
4175
- },
4176
- async build(stack, services) {
4177
- await require_local.run("docker", compose(stack, ["build", ...services]), {
4178
- cwd: stack.cwd,
4179
- env: {
4180
- ...process.env,
4181
- DOCKER_BUILDKIT: "1"
4182
- }
4183
- });
4184
- },
4185
- async pull(stack, services) {
4186
- await require_local.run("docker", compose(stack, ["pull", ...services]), { cwd: stack.cwd });
4187
- },
4188
- async up(stack, services) {
4189
- await require_local.run("docker", compose(stack, [
4190
- "up",
4191
- "-d",
4192
- "--wait",
4193
- `--wait-timeout=${WAIT_TIMEOUT_S}`,
4194
- ...services ? [] : ["--remove-orphans"],
4195
- ...services ?? []
4196
- ]), { cwd: stack.cwd });
4197
- },
4198
- async down(stack) {
4199
- await require_local.run("docker", compose(stack, ["down"]), { cwd: stack.cwd });
4200
- },
4201
- async port(stack, service, inside) {
4202
- const output = await require_local.runOutput("docker", compose(stack, [
4203
- "port",
4204
- service,
4205
- String(inside)
4206
- ]), { cwd: stack.cwd });
4207
- const port = Number(output.trim().split(":").pop());
4208
- if (!Number.isInteger(port) || port <= 0) throw new ServicePortUnknown(service, inside, output);
4209
- return port;
4210
- },
4211
- async copyOut(stack, service, from, to) {
4212
- await (0, node_fs_promises.mkdir)((0, node_path.dirname)(to), { recursive: true });
4213
- await require_local.run("docker", compose(stack, [
4214
- "cp",
4215
- `${service}:${from}`,
4216
- to
4217
- ]), { cwd: stack.cwd });
4218
- },
4219
- async digest(ref) {
4220
- const { code, stdout } = await capture("docker", [
4221
- "image",
4222
- "inspect",
4223
- "--format={{json .RepoDigests}} {{.Id}}",
4224
- ref
4225
- ]);
4226
- if (code !== 0) return void 0;
4227
- const [json = "[]", id] = stdout.trim().split(" ");
4228
- const repository = ref.split("@")[0].replace(/:[\w][\w.-]*$/, "");
4229
- try {
4230
- const digests = JSON.parse(json);
4231
- const match = Array.isArray(digests) ? digests.find((entry) => typeof entry === "string" && entry.startsWith(`${repository}@`)) : void 0;
4232
- if (match) return match.slice(repository.length + 1);
4233
- } catch {}
4234
- return id || void 0;
4235
- }
4236
- };
4237
- //#endregion
4238
- //#region src/compose/images.ts
4239
- /**
4240
- * The tag a site's image carries for a release.
4241
- *
4242
- * A site's public URLs are inlined when it is built, so one bundle serves one
4243
- * stage: `v1.4.0-production` and `v1.4.0-staging` are two images of the same
4244
- * commit. A backend reads its URLs at runtime, so one image serves every stage
4245
- * and its tag is the release's alone.
4246
- */
4247
- function siteTag(tag, stage) {
4248
- return `${tag}-${stage}`;
4249
- }
4250
- /** Images a tag names that the registry does not have. */
4251
- var ImageTagNotFound = class extends Error {
4252
- tag;
4253
- refs;
4254
- constructor(tag, refs) {
4255
- super(`The registry has no image for ${refs.length === 1 ? "this app" : `${refs.length} apps`} at '${tag}':\n` + refs.map((ref) => ` - ${ref}`).join("\n") + `\nNothing was pulled or started. Push '${tag}' for every app from CI, pick a tag that exists, or run without --tag to build from this checkout.`);
4256
- this.tag = tag;
4257
- this.refs = refs;
4258
- this.name = "ImageTagNotFound";
4259
- }
4260
- };
4261
- /** The registry did not answer, or did not let this machine in. */
4262
- var RegistryUnreachable = class extends Error {
4263
- ref;
4264
- detail;
4265
- constructor(ref, detail) {
4266
- super(`Could not ask the registry about ${ref}: ${detail || "no answer"}\nNothing was pulled or started. Check the registry is reachable and that this machine is logged in to it (\`docker login <registry>\`).`);
4267
- this.ref = ref;
4268
- this.detail = detail;
4269
- this.name = "RegistryUnreachable";
4270
- }
4271
- };
4272
- /**
4273
- * Assert every image exists, before anything else happens.
4274
- *
4275
- * Asked all at once and reported all at once: a release missing two images is
4276
- * one problem with two names, and finding the second only after fixing the
4277
- * first is a slower way to learn the same thing.
4278
- *
4279
- * @throws {RegistryUnreachable} when the registry cannot say
4280
- * @throws {ImageTagNotFound} listing every missing image
4281
- */
4282
- async function assertImagesExist(docker, tag, images) {
4283
- const lookups = await Promise.all(images.map((image) => docker.lookup(image.ref)));
4284
- const unreachable = lookups.find((lookup) => lookup.status === "unreachable");
4285
- if (unreachable?.status === "unreachable") throw new RegistryUnreachable(unreachable.ref, unreachable.detail);
4286
- const missing = lookups.filter((lookup) => lookup.status === "missing").map((lookup) => lookup.ref);
4287
- if (missing.length > 0) throw new ImageTagNotFound(tag, missing);
4288
- }
4289
- //#endregion
4290
- //#region src/compose/caddyfile.ts
4291
- /** The Caddyfile for a stack's edge. */
4292
- function edgeCaddyfile(sites, options) {
4293
- const global = options.local ? `{
4294
- # No ACME and no public DNS: the CA is Caddy's own and the names resolve
4295
- # to this machine.
4296
- local_certs
4297
- auto_https disable_redirects
4298
- }` : options.email ? `{
4299
- email ${options.email}
4300
- }` : "";
4301
- const blocks = sites.map(({ host, upstream }) => `https://${host} {${options.local ? `
4302
- tls internal
4303
- ` : ""}
4304
- # Host and X-Forwarded-* are passed through as Caddy does by default, so
4305
- # an app builds redirects, cookies and links on the address a caller used.
4306
- reverse_proxy ${upstream} {
4307
- # Never buffer: a streamed response — server-sent events, a Next.js
4308
- # RSC payload — reaches the client as each chunk is written.
4309
- flush_interval -1
4310
- }
4311
- }`);
4312
- return `${HEADER}${global ? `${global}\n\n` : ""}${blocks.join("\n\n")}\n`;
4313
- }
4314
- const HEADER = `# Generated by gkm compose from the construct manifest — do not edit.
4315
- #
4316
- # One host per API and site, each in front of its own container.
4317
- `;
4318
- //#endregion
4319
- //#region src/compose/stack.ts
4320
- /**
4321
- * The stack a workspace runs as for one stage — pure.
4322
- *
4323
- * Everything `gkm compose` writes is decided here, as data: the compose file,
4324
- * the Caddyfile, one env file per backend, and the image each app runs. The
4325
- * command around it reads the workspace, asks the registry, writes the files
4326
- * and starts Docker; none of those decide anything, so all of it can be
4327
- * asserted without a daemon.
4328
- *
4329
- * The values come from the same derivations every other target uses. The
4330
- * plan is reconcile's `planFor`, the keys an app reads are `appEnvKeys`, and
4331
- * every URL is `envFor`'s — asked twice, once with each app at its public
4332
- * address and once on the compose network, because a browser and a sibling
4333
- * service reach the same API by different names.
4334
- */
4335
- /** Where a stack's files are written, relative to the workspace root. */
4336
- function stackDir(stage) {
4337
- return `.gkm/compose/${stage}`;
4338
- }
4339
- /**
4340
- * The compose project a stage's stack runs as.
4341
- *
4342
- * The deploy identity's scope, so two workspaces with one name in different
4343
- * namespaces never share containers or volumes — and the stage, so a
4344
- * machine can run two stages side by side.
4345
- */
4346
- function composeProject(identity) {
4347
- return `${identity.scope}-${identity.stage}`;
4348
- }
4349
- /** The ports the edge is published on, and the variables that move them. */
4350
- const EDGE_PORT_ENV = {
4351
- https: "GKM_COMPOSE_HTTPS_PORT",
4352
- http: "GKM_COMPOSE_HTTP_PORT"
4353
- };
4354
- /**
4355
- * Containers this target does not run.
4356
- *
4357
- * Object storage is the stage's own — an externally hosted bucket whose URL
4358
- * the stage's secrets hold — and so is a deployed stage's mail. The AWS
4359
- * emulator never applies: a stack is a server target, whose events are
4360
- * pg-boss.
4361
- */
4362
- const NOT_RUN = {
4363
- minio: true,
4364
- localstack: true,
4365
- caddy: true
4366
- };
4367
- /** Kinds whose `<ID>_URL` a stage must be given, because nothing here runs them. */
4368
- const EXTERNAL_KINDS = {
4369
- objects: true,
4370
- "file-server": true
4371
- };
4372
- /** The S3 client's credentials, read beside a bucket's URL when it has them. */
4373
- const STORAGE_KEYS = [
4374
- "AWS_ACCESS_KEY_ID",
4375
- "AWS_SECRET_ACCESS_KEY",
4376
- "AWS_REGION"
4377
- ];
4378
- /** A bucket the stack needs and the stage was not given. */
4379
- var BucketNotConfigured = class extends Error {
4380
- app;
4381
- id;
4382
- key;
4383
- stage;
4384
- constructor(app, id, key, stage) {
4385
- super(`'${app}' uses the bucket '${id}', and gkm compose runs no object storage: the stage needs an externally hosted bucket. Set its URL in the stage's secrets: gkm secrets:set ${key} 's3://…' --stage ${stage}`);
4386
- this.app = app;
4387
- this.id = id;
4388
- this.key = key;
4389
- this.stage = stage;
4390
- this.name = "BucketNotConfigured";
4391
- }
4392
- };
4393
- /** A value only the stage's secrets can hold, and they do not. */
4394
- var StageSecretMissing = class extends Error {
4395
- app;
4396
- key;
4397
- stage;
4398
- constructor(app, key, stage) {
4399
- super(`'${app}' reads ${key}, and the stage '${stage}' has none. Nothing in the stack can derive it; set it in the stage's secrets: gkm secrets:set ${key} '…' --stage ${stage}`);
4400
- this.app = app;
4401
- this.key = key;
4402
- this.stage = stage;
4403
- this.name = "StageSecretMissing";
4404
- }
4405
- };
4406
- /** A deployed stage's secrets with no seed to derive its passwords from. */
4407
- var StageSeedMissing = class extends Error {
4408
- stage;
4409
- constructor(stage) {
4410
- super(`The stage '${stage}' has no seed in its secrets, and a deployed stage's database passwords are derived from it. Run gkm compose without --dry-run once, or gkm deploy, to generate it.`);
4411
- this.stage = stage;
4412
- this.name = "StageSeedMissing";
4413
- }
4414
- };
4415
- /** A workspace with no API and no site to run. */
4416
- var NothingToCompose = class extends Error {
4417
- root;
4418
- constructor(root) {
4419
- super(`${root} declares no RestApi and no site, so a stack would run nothing. Workers and mobile apps are not run by gkm compose yet.`);
4420
- this.root = root;
4421
- this.name = "NothingToCompose";
4422
- }
4423
- };
4424
- /** A value that cannot be written to an env file a line at a time. */
4425
- var EnvValueMultiline = class extends Error {
4426
- app;
4427
- key;
4428
- constructor(app, key) {
4429
- super(`${key} for '${app}' spans more than one line, and an env file holds one value per line. Store it on one line — base64, or JSON — and decode it where it is read.`);
4430
- this.app = app;
4431
- this.key = key;
4432
- this.name = "EnvValueMultiline";
4433
- }
4434
- };
4435
- /** The services a stack's apps wait on and the edge waits on in turn. */
4436
- const HEALTHY = { condition: "service_healthy" };
4437
- /**
4438
- * An app's health check: its own server, asked on 127.0.0.1 — not
4439
- * `localhost`, which resolves to `::1` first in an Alpine container while a
4440
- * server may listen on IPv4 alone.
4441
- */
4442
- function probe(port, path) {
4443
- return {
4444
- test: [
4445
- "CMD",
4446
- "wget",
4447
- "-q",
4448
- "-O",
4449
- "/dev/null",
4450
- `http://127.0.0.1:${port}${path}`
4451
- ],
4452
- interval: "5s",
4453
- timeout: "3s",
4454
- retries: 20,
4455
- start_period: "10s"
4456
- };
4457
- }
4458
- /** The stack for one stage. */
4459
- function composeStack(input) {
4460
- const { workspace, manifest, stage, identity } = input;
4461
- const local = stage === workspace.stages.local;
4462
- const project = composeProject(identity);
4463
- const custom = input.secrets?.custom ?? {};
4464
- const https = input.ports?.https ?? 443;
4465
- const http = input.ports?.http ?? 80;
4466
- const plan = require_sniffer.planFor(manifest, stage, (0, _geekmidas_manifest.provisionOrder)(manifest), {
4467
- localStage: workspace.stages.local,
4468
- events: require_fakes.DEFAULT_EVENTS.server,
4469
- cache: require_fakes.DEFAULT_CACHE.server,
4470
- edge: false
4471
- });
4472
- const byId = new Map(plan.resources.map((r) => [r.id, r]));
4473
- const seed = local ? void 0 : input.secrets?.seed;
4474
- if (!local && !seed) throw new StageSeedMissing(stage);
4475
- const credential = {
4476
- master: local ? "geekmidas" : require_sniffer.localRolePassword(workspace.name, plan, "master", seed),
4477
- ...seed ? { seed } : {}
4478
- };
4479
- const infra = plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => local || c !== "mailpit").sort();
4480
- const apps = stackApps(workspace, manifest, plan, {
4481
- local,
4482
- stage,
4483
- https,
4484
- identity,
4485
- images: input.images
4486
- });
4487
- if (apps.length === 0) throw new NothingToCompose(workspace.root);
4488
- const derivation = {
4489
- project: workspace.name,
4490
- master: credential.master,
4491
- ...credential.seed ? { seed: credential.seed } : {}
4492
- };
4493
- const outside = require_sniffer.networkEnv(plan, {
4494
- ...derivation,
4495
- addresses: Object.fromEntries(apps.map((app) => [app.id, app.url]))
4496
- });
4497
- const inside = require_sniffer.networkEnv(plan, {
4498
- ...derivation,
4499
- addresses: Object.fromEntries(apps.map((app) => [app.id, `http://${app.name}:${app.port}`]))
4500
- });
4501
- const owners = /* @__PURE__ */ new Map();
4502
- for (const resource of plan.resources) {
4503
- owners.set(resource.envKey, resource);
4504
- if (resource.kind === "email") {
4505
- owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "from"), resource);
4506
- owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "inboxUrl"), resource);
4507
- }
4508
- if (resource.kind === "external-api") owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "credentials"), resource);
4509
- }
4510
- const surfaceUrls = new Map(plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site").map((r) => [r.envKey, r.id]));
4511
- const appById = new Map(apps.map((app) => [app.id, app]));
4512
- /**
4513
- * What a key resolves to for one app, from the stage's secrets where only
4514
- * they can hold it, and the derivations otherwise.
4515
- */
4516
- const valueFor = (app, key, owner) => {
4517
- if (owner && EXTERNAL_KINDS[owner.kind] && key === owner.envKey) {
4518
- const value = custom[key];
4519
- if (value === void 0) throw new BucketNotConfigured(app.name, owner.id, key, stage);
4520
- return value;
4521
- }
4522
- if (owner && suppliedBy(owner, key, local)) {
4523
- const value = custom[key];
4524
- if (value === void 0) throw new StageSecretMissing(app.name, key, stage);
4525
- return value;
4526
- }
4527
- if (custom[key] !== void 0) return custom[key];
4528
- const surface = surfaceUrls.get(key);
4529
- if (app.kind === "rest-api" && surface && surface !== app.id) return inside[key];
4530
- return outside[key];
4531
- };
4532
- for (const app of apps) {
4533
- if (app.kind === "site") {
4534
- const site = manifest[app.id];
4535
- if (site?.kind !== "site") continue;
4536
- const args = {};
4537
- for (const [key, source] of Object.entries((0, _geekmidas_manifest.publicEnvFor)(site, manifest))) {
4538
- const value = valueFor(app, source, owners.get(source));
4539
- if (value !== void 0) args[key] = value;
4540
- }
4541
- if (app.build && Object.keys(args).length > 0) app.build.args = args;
4542
- continue;
4543
- }
4544
- const keys = require_sniffer.appEnvKeys(manifest, app.name, input.runnables) ?? /* @__PURE__ */ new Set();
4545
- const values = {};
4546
- for (const key of [...keys].sort()) {
4547
- const owner = owners.get(key) ?? storageOwner(key, plan);
4548
- if (!local && owner?.kind === "email" && key.endsWith("_INBOX_URL")) continue;
4549
- if (STORAGE_KEYS.includes(key)) {
4550
- if (custom[key] !== void 0) values[key] = custom[key];
4551
- continue;
4552
- }
4553
- const value = valueFor(app, key, owner);
4554
- if (value !== void 0) values[key] = value;
4555
- }
4556
- const trusted = (0, _geekmidas_manifest.provideKey)(app.id, "trustedOrigins");
4557
- if (keys.has(trusted)) {
4558
- const internal = (byId.get(app.id)?.callers ?? []).map((caller) => appById.get(caller)).filter((caller) => caller?.kind === "rest-api").map((caller) => `http://${caller.name}:${caller.port}`);
4559
- const origins = [...(values[trusted] ?? "").split(",").filter(Boolean), ...internal];
4560
- values[trusted] = [...new Set(origins)].join(",");
4561
- }
4562
- app.env = {
4563
- ...values,
4564
- NODE_ENV: "production",
4565
- PORT: String(app.port),
4566
- STAGE: stage
4567
- };
4568
- for (const [key, value] of Object.entries(app.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(app.name, key);
4569
- }
4570
- const compose = stackFile({
4571
- project,
4572
- plan,
4573
- infra,
4574
- apps,
4575
- master: credential.master,
4576
- https,
4577
- http
4578
- });
4579
- const caddyfile = edgeCaddyfile(apps.map((app) => ({
4580
- host: app.host,
4581
- upstream: `${app.name}:${app.port}`
4582
- })), { local });
4583
- const dockerfiles = {};
4584
- for (const app of apps) {
4585
- if (!app.build) continue;
4586
- dockerfiles[app.build.dockerfile] = app.kind === "site" ? require_sniffer.siteDockerfile(app.name, workspace.apps[app.name], {
4587
- turboPackage: input.packages?.[app.name] ?? app.name,
4588
- packageManager: input.packageManager ?? "pnpm",
4589
- manifest
4590
- }) : require_sniffer.generateSlimDockerfile({
4591
- imageName: app.name,
4592
- baseImage: "node:22-alpine",
4593
- port: app.port,
4594
- healthCheckPath: "/health",
4595
- prebuilt: true,
4596
- packageManager: input.packageManager ?? "pnpm",
4597
- bundle: `${app.path}/.gkm/server/dist/server.mjs`
4598
- });
4599
- }
4600
- return {
4601
- project,
4602
- stage,
4603
- local,
4604
- apps,
4605
- infra,
4606
- compose,
4607
- caddyfile,
4608
- dockerfiles,
4609
- plan,
4610
- credential
4611
- };
4612
- /** Whether only the stage's secrets can supply `key` to `owner`. */
4613
- function suppliedBy(owner, key, isLocal) {
4614
- if (owner.kind === "credential") return true;
4615
- if (owner.kind === "external-api") return key === (0, _geekmidas_manifest.provideKey)(owner.id, "credentials");
4616
- if (isLocal) return false;
4617
- if (owner.kind === "secret" || owner.kind === "encryption") return true;
4618
- return owner.kind === "email" && !key.endsWith("_INBOX_URL");
4619
- }
4620
- }
4621
- /** Which bucket an S3 credential key belongs to — the first one declared. */
4622
- function storageOwner(key, plan) {
4623
- if (!STORAGE_KEYS.includes(key)) return void 0;
4624
- return plan.resources.find((r) => r.kind === "objects");
4625
- }
4626
- /** The apps a stack runs: every API and every site, at its stage address. */
4627
- function stackApps(workspace, manifest, plan, options) {
4628
- const apps = [];
4629
- const resources = new Map(plan.resources.map((r) => [r.id, r]));
4630
- for (const [id, declaration] of Object.entries(manifest)) {
4631
- if (declaration.kind !== "rest-api" && declaration.kind !== "site") continue;
4632
- const name = require_derive.appKey(id);
4633
- const app = workspace.apps[name];
4634
- const resource = resources.get(id);
4635
- if (!app || !resource || !app.port) continue;
4636
- const host = hostOf(name, app, workspace, resource, options);
4637
- const tag = declaration.kind === "site" ? siteTag(options.images.tag, options.stage) : options.images.tag;
4638
- const ref = require_sniffer.validateImageRef(require_store.imageRef(options.identity, name, options.images.registry, tag));
4639
- apps.push({
4640
- app: name,
4641
- name,
4642
- id,
4643
- kind: declaration.kind,
4644
- path: app.path,
4645
- port: app.port,
4646
- host,
4647
- url: `https://${host}${options.local && options.https !== 443 ? `:${options.https}` : ""}`,
4648
- ref,
4649
- tag,
4650
- ...options.images.mode === "build" ? { build: { dockerfile: `${stackDir(options.stage)}/Dockerfile.${name}` } } : {}
4651
- });
4652
- }
4653
- return apps.sort((a, b) => a.name.localeCompare(b.name));
4654
- }
4655
- /**
4656
- * The host an app answers on for a stage: the `gkm dev` edge's
4657
- * `*.localhost` names for the local stage, and the stage's domain — by the
4658
- * same rule a deploy follows — for any other.
4659
- */
4660
- function hostOf(name, app, workspace, resource, options) {
4661
- if (options.local) return require_sniffer.hostFor(resource, workspace.name);
4662
- return require_sniffer.resolveHost(name, app, options.stage, workspace.deploy?.domains, require_sniffer.isMainFrontendApp(name, app, workspace.apps));
4663
- }
4664
- /** The compose document: the infrastructure, the apps, and the edge. */
4665
- function stackFile(options) {
4666
- const { project, plan, infra, apps } = options;
4667
- const derived = require_sniffer.composeFor({
4668
- ...plan,
4669
- containers: [...infra]
4670
- }, {
4671
- project,
4672
- ports: Object.fromEntries(require_sniffer.portKeys(infra, plan.fakes).map((key) => [key, 0]))
4673
- });
4674
- const services = {};
4675
- const volumes = {
4676
- ...derived.volumes,
4677
- "caddy-data": {},
4678
- "caddy-config": {}
4679
- };
4680
- for (const name of infra) {
4681
- const { ports: _ports, depends_on, profiles: _profiles, ...service } = derived.services[name];
4682
- services[name] = {
4683
- ...service,
4684
- ...depends_on?.length ? { depends_on: Object.fromEntries(depends_on.map((dep) => [dep, HEALTHY])) } : {}
4685
- };
4686
- }
4687
- const postgres = services.postgres;
4688
- if (postgres) {
4689
- postgres.environment = {
4690
- ...postgres.environment,
4691
- POSTGRES_PASSWORD: options.master
4692
- };
4693
- postgres.ports = ["127.0.0.1::5432"];
4694
- }
4695
- for (const app of apps) services[app.name] = {
4696
- image: app.ref,
4697
- ...app.build ? { build: {
4698
- context: "../../..",
4699
- dockerfile: app.build.dockerfile,
4700
- ...app.build.args ? { args: app.build.args } : {}
4701
- } } : {},
4702
- restart: "unless-stopped",
4703
- ...app.kind === "rest-api" ? {
4704
- env_file: [{
4705
- path: `./${app.name}.env`,
4706
- format: "raw"
4707
- }],
4708
- ...infra.length ? { depends_on: Object.fromEntries(infra.map((name) => [name, HEALTHY])) } : {}
4709
- } : {},
4710
- healthcheck: probe(app.port, app.kind === "site" ? "/" : "/health")
4711
- };
4712
- services.caddy = {
4713
- image: require_sniffer.DEFAULT_IMAGES.caddy,
4714
- restart: "unless-stopped",
4715
- ports: [`${options.https}:443`, `${options.http}:80`],
4716
- volumes: [
4717
- "./Caddyfile:/etc/caddy/Caddyfile:ro",
4718
- "caddy-data:/data",
4719
- "caddy-config:/config"
4720
- ],
4721
- depends_on: Object.fromEntries(apps.map((app) => [app.name, HEALTHY])),
4722
- healthcheck: {
4723
- test: [
4724
- "CMD",
4725
- "caddy",
4726
- "version"
4727
- ],
4728
- interval: "10s",
4729
- timeout: "5s",
4730
- retries: 5
4731
- }
4732
- };
4733
- return {
4734
- name: project,
4735
- services,
4736
- volumes
4737
- };
4738
- }
4739
- /** Render an app's env file: one `KEY=value` per line, read raw by compose. */
4740
- function envFile(env) {
4741
- return `${Object.entries(env).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`).join("\n")}\n`;
4742
- }
4743
- //#endregion
4744
3604
  //#region src/compose/index.ts
4745
3605
  /**
4746
3606
  * `gkm compose` — a workspace's APIs and sites for one stage, as one Docker
4747
3607
  * Compose stack behind Caddy.
4748
3608
  *
4749
- * The order is the point of this file, and the rules it keeps are these:
4750
- *
4751
- * - A tag is checked before anything happens. Every app's image is asked of
4752
- * the registry first; one missing image stops the run naming them all, and
4753
- * nothing is written, pulled or started.
4754
- * - The databases exist before the apps do. Postgres comes up alone, its
4755
- * databases, roles and grants are created and its migrations applied from
4756
- * this machine, and only then do the apps start — so an app never boots
4757
- * against a schema that is not there yet.
4758
- * - What a stage runs is recorded. Each app's tag and the digest it resolved
4759
- * to are written to the stage's state, so "what is this stage running" has
4760
- * an exact answer.
4761
- *
4762
- * Docker, the registry, the build and the clock are injected, so the rules are
4763
- * asserted without a daemon; the defaults are the real ones.
3609
+ * A convenience over `gkm deploy --target compose`: the stage defaults to the
3610
+ * local one, `--build`/`--pull` choose the images whatever `--tag` says, and
3611
+ * `--down` stops the stack. Everything else — the order, the image check, the
3612
+ * state, the health checks — is the compose target's, run through the same
3613
+ * `deploy()` as any other target, so there is one way a stack comes up.
4764
3614
  */
4765
- const logger$4 = console;
4766
3615
  /** `--build` and `--pull` together ask for two different things. */
4767
3616
  var ComposeModeConflict = class extends Error {
4768
3617
  constructor() {
@@ -4770,320 +3619,52 @@ var ComposeModeConflict = class extends Error {
4770
3619
  this.name = "ComposeModeConflict";
4771
3620
  }
4772
3621
  };
4773
- /** A build with no commit to tag its images with. */
4774
- var NoGitRevision = class extends Error {
4775
- root;
4776
- constructor(root) {
4777
- super(`Images built here are tagged with the commit they were built from, and ${root} is not in a git repository with a commit. Commit, or pass --tag to name the images yourself.`);
4778
- this.root = root;
4779
- this.name = "NoGitRevision";
4780
- }
4781
- };
4782
- /** A port variable that does not hold a port. */
4783
- var EdgePortInvalid = class extends Error {
4784
- variable;
4785
- value;
4786
- constructor(variable, value) {
4787
- super(`${variable}=${value} is not a port. Set it to a number between 1 and 65535, or unset it for the default.`);
4788
- this.variable = variable;
4789
- this.value = value;
4790
- this.name = "EdgePortInvalid";
4791
- }
4792
- };
4793
- /** The commit HEAD is at, short — and `-dirty` when the tree has changes. */
4794
- async function gitRevision(root) {
4795
- let sha;
4796
- try {
4797
- sha = (await require_local.runOutput("git", [
4798
- "rev-parse",
4799
- "--short",
4800
- "HEAD"
4801
- ], { cwd: root })).trim();
4802
- } catch {
4803
- throw new NoGitRevision(root);
4804
- }
4805
- if (!sha) throw new NoGitRevision(root);
4806
- return (await require_local.runOutput("git", ["status", "--porcelain"], { cwd: root }).catch(() => "")).trim() ? `${sha}-dirty` : sha;
4807
- }
4808
- /**
4809
- * Bundle a backend with the gkm that is running — the same build a deploy
4810
- * makes, so the image holds one file and no dependencies.
4811
- */
4812
- async function bundleWithThisCli(appRoot) {
4813
- const entry = process.argv[1];
4814
- if (!entry) return;
4815
- await require_local.run(process.execPath, [
4816
- ...process.execArgv,
4817
- entry,
4818
- "build",
4819
- "--provider",
4820
- "server",
4821
- "--production"
4822
- ], { cwd: appRoot });
4823
- }
4824
- const defaultDeps = {
4825
- docker: dockerCompose,
4826
- bundle: bundleWithThisCli,
4827
- revision: gitRevision
4828
- };
4829
- /** The edge's ports, from the environment — 443 and 80 when unset. */
4830
- function edgePorts(env = process.env) {
4831
- const read = (variable, fallback) => {
4832
- const value = env[variable];
4833
- if (value === void 0 || value === "") return fallback;
4834
- const port = Number(value);
4835
- if (!Number.isInteger(port) || port < 1 || port > 65535) throw new EdgePortInvalid(variable, value);
4836
- return port;
4837
- };
4838
- return {
4839
- https: read(EDGE_PORT_ENV.https, 443),
4840
- http: read(EDGE_PORT_ENV.http, 80)
4841
- };
4842
- }
4843
3622
  /** `gkm compose`. */
4844
3623
  async function composeCommand(options = {}, deps = {}) {
4845
- const { docker, bundle, revision } = {
4846
- ...defaultDeps,
4847
- ...deps
4848
- };
4849
- const env = deps.env ?? process.env;
4850
3624
  if (options.build && options.pull) throw new ComposeModeConflict();
4851
- const { workspace } = await require_config.loadWorkspaceConfig(options.cwd);
4852
- const root = workspace.root;
3625
+ const cwd = (0, node_path.resolve)(options.cwd ?? process.cwd());
3626
+ const { workspace } = await require_config.loadWorkspaceConfig(cwd);
4853
3627
  const stage = options.stage ?? workspace.stages.local;
4854
- if (!(stage === workspace.stages.local)) require_workspace.assertDeployedStage(workspace.stages, stage);
4855
- const identity = require_store.deployIdentity(workspace, stage);
4856
- const dir = (0, node_path.join)(root, stackDir(stage));
4857
- const file = (0, node_path.join)(dir, "docker-compose.yml");
4858
- const ref = {
4859
- project: composeProject(identity),
4860
- file,
4861
- cwd: root
4862
- };
4863
3628
  if (options.down) {
4864
- await docker.down(ref);
4865
- logger$4.log(`🛑 Stopped ${ref.project}. Its volumes are kept.`);
3629
+ const ref = {
3630
+ project: require_compose.composeProject(require_store.deployIdentity(workspace, stage)),
3631
+ file: (0, node_path.join)(workspace.root, require_compose.stackDir(stage), "docker-compose.yml"),
3632
+ cwd: workspace.root
3633
+ };
3634
+ await (deps.docker ?? require_compose.dockerCompose).down(ref);
3635
+ console.log(`🛑 Stopped ${ref.project}. Its volumes are kept.`);
4866
3636
  return;
4867
3637
  }
4868
- const sources = {};
4869
- const runnables = {};
4870
- const manifest = await require_discover.discover({
4871
- patterns: require_sniffer.constructGlobs(workspace),
4872
- cwd: root,
4873
- sources,
4874
- runnables
4875
- });
4876
- const mode = options.build || !options.pull && !options.tag ? "build" : "pull";
4877
- const tag = options.tag ?? (mode === "pull" ? "latest" : await revision(root));
4878
- const { secrets, keep } = await stageSecrets(workspace, manifest, stage);
4879
- const packageManager = require_sniffer.detectPackageManager(root);
4880
- const stack = composeStack({
4881
- workspace,
4882
- manifest,
4883
- runnables,
3638
+ let finished;
3639
+ const mode = options.build ? "build" : options.pull ? "pull" : void 0;
3640
+ const tag = options.tag ?? (options.pull ? "latest" : void 0);
3641
+ const result = await require_deploy.deploy({
3642
+ cwd,
4884
3643
  stage,
4885
- identity,
4886
- images: {
4887
- mode,
4888
- tag,
4889
- ...workspace.deploy?.dokploy?.registry ? { registry: workspace.deploy.dokploy.registry } : {}
4890
- },
4891
- secrets,
4892
- ports: edgePorts(env),
4893
- packageManager,
4894
- packages: Object.fromEntries(Object.entries(workspace.apps).map(([name, app]) => [name, require_sniffer.appPackageName(root, name, app.path)]))
4895
- });
4896
- if (mode === "pull" && !options.dryRun) await assertImagesExist(docker, tag, stack.apps);
4897
- if (options.dryRun) keep.skip();
4898
- else await keep.write();
4899
- const files = await writeStack(root, dir, stack);
4900
- printPlan(stack, mode, files);
4901
- if (options.dryRun) return {
4902
- stack,
4903
- files
4904
- };
4905
- const store = deps.state ? await deps.state(workspace) : await require_sniffer.createStateStore({
4906
- config: workspace.state,
4907
- workspaceRoot: root,
4908
- workspaceName: workspace.name
4909
- });
4910
- const lock = await store.lock(stage, { operation: "compose" });
4911
- try {
4912
- const apps = stack.apps.map((app) => app.name);
4913
- if (mode === "build") {
4914
- for (const app of stack.apps.filter((a) => a.kind === "rest-api")) {
4915
- logger$4.log(`\n📦 Bundling ${app.name}…`);
4916
- await bundle((0, node_path.join)(root, app.path));
3644
+ target: "compose",
3645
+ targets: { compose: require_compose.composeTarget({
3646
+ ...deps,
3647
+ ...mode ? { mode } : {},
3648
+ report: (done) => {
3649
+ finished = done;
4917
3650
  }
4918
- logger$4.log("\n🐳 Building images…");
4919
- await docker.build(ref, apps);
4920
- } else {
4921
- logger$4.log(`\n🐳 Pulling ${tag}…`);
4922
- await docker.pull(ref, apps);
3651
+ }) },
3652
+ ...tag ? { tag } : {},
3653
+ ...options.dryRun ? { dryRun: true } : {},
3654
+ logger: {
3655
+ info: (message) => console.log(message),
3656
+ warn: (message) => console.warn(message),
3657
+ error: (message) => console.error(message)
4923
3658
  }
4924
- if (stack.infra.length > 0) {
4925
- logger$4.log(`\n🗄️ Starting ${stack.infra.join(", ")}…`);
4926
- await docker.up(ref, stack.infra);
4927
- }
4928
- if (stack.infra.includes("postgres")) await prepareDatabases(stack, workspace, manifest, sources, {
4929
- docker,
4930
- ref,
4931
- sql: deps.sql ?? require_sniffer.pgClient,
4932
- migrate: deps.migrate ?? migrateDatabases
4933
- });
4934
- logger$4.log("\n🚀 Starting the stack…");
4935
- await docker.up(ref);
4936
- if (stack.local) await docker.copyOut(ref, "caddy", "/data/caddy/pki/authorities/local/root.crt", (0, node_path.join)(dir, "caddy-root.crt")).catch(() => {});
4937
- const images = await recordImages(store, stack, docker, mode);
4938
- logger$4.log(`\n✅ ${ref.project} is running:`);
4939
- for (const app of stack.apps) logger$4.log(` ${app.name.padEnd(12)} ${app.url}`);
4940
- if (stack.local) logger$4.log(`\n🔐 Certificates are from Caddy's local CA: NODE_EXTRA_CA_CERTS=${(0, node_path.join)(stackDir(stage), "caddy-root.crt")}`);
4941
- return {
4942
- stack,
4943
- files,
4944
- images
4945
- };
4946
- } finally {
4947
- await lock.release();
4948
- }
4949
- }
4950
- /**
4951
- * The stage's secrets — and, for a deployed stage, everything it generates
4952
- * once: its seed, and each declared secret and keyring. Written back before
4953
- * anything is derived from them, so the next run derives the same passwords.
4954
- */
4955
- async function stageSecrets(workspace, manifest, stage) {
4956
- const store = await require_store.secretsStoreFor(workspace, stage);
4957
- const stored = await store.read(stage);
4958
- const nothing = {
4959
- write: async () => {},
4960
- skip: () => {}
4961
- };
4962
- if (stage === workspace.stages.local) return {
4963
- secrets: stored,
4964
- keep: nothing
4965
- };
4966
- const { secrets, generated } = require_sniffer.withGeneratedSecrets(stored ?? require_storage.initStageSecrets(stage), manifest);
4967
- if (generated.length === 0) return {
4968
- secrets,
4969
- keep: nothing
4970
- };
3659
+ }).result;
3660
+ if (!finished) return void 0;
4971
3661
  return {
4972
- secrets,
4973
- keep: {
4974
- async write() {
4975
- await store.write(stage, secrets);
4976
- logger$4.log(`🔑 Generated for "${stage}" (${store.name}): ${generated.join(", ")}`);
4977
- },
4978
- skip() {
4979
- logger$4.log(`🔑 "${stage}" has no ${generated.join(", ")} yet; this dry run used values it did not keep.`);
4980
- }
4981
- }
3662
+ stack: finished.stack,
3663
+ files: finished.files,
3664
+ ...options.dryRun ? {} : { images: finished.images },
3665
+ deploy: result
4982
3666
  };
4983
3667
  }
4984
- /** Write the stack's files: owner-only, since the env files hold secrets. */
4985
- async function writeStack(root, dir, stack) {
4986
- await (0, node_fs_promises.mkdir)(dir, {
4987
- recursive: true,
4988
- mode: 448
4989
- });
4990
- await (0, node_fs_promises.chmod)(dir, 448);
4991
- const files = [];
4992
- const write = async (path, content, mode = 420) => {
4993
- await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
4994
- await (0, node_fs_promises.writeFile)(path, content, { mode });
4995
- await (0, node_fs_promises.chmod)(path, mode);
4996
- files.push(path);
4997
- };
4998
- await write((0, node_path.join)(dir, "docker-compose.yml"), composeYaml(stack));
4999
- await write((0, node_path.join)(dir, "Caddyfile"), stack.caddyfile);
5000
- for (const app of stack.apps) if (app.env) await write((0, node_path.join)(dir, `${app.name}.env`), envFile(app.env), 384);
5001
- for (const [path, content] of Object.entries(stack.dockerfiles)) await write((0, node_path.join)(root, path), content);
5002
- await ignoreStacks(root);
5003
- return files;
5004
- }
5005
- /** Make sure the build context leaves `.gkm/compose` out. */
5006
- async function ignoreStacks(root) {
5007
- const path = (0, node_path.join)(root, ".dockerignore");
5008
- if (!(0, node_fs.existsSync)(path)) {
5009
- await (0, node_fs_promises.writeFile)(path, require_sniffer.generateDockerignore());
5010
- return;
5011
- }
5012
- const current = await (0, node_fs_promises.readFile)(path, "utf-8");
5013
- if (/^\/?\.gkm\/compose\/?$|^\/?\.gkm\/?$/m.test(current)) return;
5014
- await (0, node_fs_promises.appendFile)(path, `${current.endsWith("\n") ? "" : "\n"}\n# gkm compose's stacks: each app's env file holds its stage's secrets\n.gkm/compose\n`);
5015
- }
5016
- function composeYaml(stack) {
5017
- return `# Generated by gkm compose from the construct manifest — do not edit.
5018
- # The ${stack.stage} stage's APIs and sites behind one Caddy. Each backend
5019
- # reads exactly the keys in its own env file beside this one.
5020
- #
5021
- # gkm compose${stack.local ? "" : ` --stage ${stack.stage}`} start or update it
5022
- # gkm compose${stack.local ? "" : ` --stage ${stack.stage}`} --down stop it
5023
- ${(0, yaml.stringify)(stack.compose, {
5024
- lineWidth: 0,
5025
- aliasDuplicateObjects: false
5026
- })}`;
5027
- }
5028
- /** Create what the plan names in the stack's Postgres, then migrate it. */
5029
- async function prepareDatabases(stack, workspace, manifest, sources, deps) {
5030
- const port = await deps.docker.port(deps.ref, "postgres", 5432);
5031
- const statements = require_sniffer.postgresStatements(stack.plan, workspace.name, stack.credential.seed);
5032
- if (statements.length > 0) {
5033
- logger$4.log("🗄️ Creating databases, roles and grants…");
5034
- await require_sniffer.applyPostgres(deps.sql(port, stack.credential.master), statements);
5035
- }
5036
- const env = require_sniffer.envFor(stack.plan, {
5037
- ports: { [require_sniffer.primaryPortKey("postgres")]: port },
5038
- project: workspace.name,
5039
- master: stack.credential.master,
5040
- ...stack.credential.seed ? { seed: stack.credential.seed } : {}
5041
- });
5042
- const runs = await deps.migrate({
5043
- root: workspace.root,
5044
- manifest,
5045
- sources,
5046
- env
5047
- });
5048
- for (const { target, applied } of runs) if (applied.length > 0) logger$4.log(`🗄️ ${target.migrations}: applied ${applied.length}`);
5049
- }
5050
- /**
5051
- * Record each app's image in the stage's state: the tag it ran and the digest
5052
- * the tag resolved to — or, for an image built here and never pushed, its
5053
- * content id.
5054
- */
5055
- async function recordImages(store, stack, docker, mode) {
5056
- if (!await store.read(stack.stage)) await store.write(stack.stage, require_sniffer.createEmptyState(stack.stage, "", ""), { expectedVersion: null });
5057
- const images = {};
5058
- for (const app of stack.apps) {
5059
- const digest = await docker.digest(app.ref);
5060
- images[app.name] = {
5061
- ref: app.ref,
5062
- tag: app.tag,
5063
- ...digest ? { digest } : {}
5064
- };
5065
- await store.putResource(stack.stage, {
5066
- key: `compose:${app.name}`,
5067
- type: "compose-image",
5068
- status: "ready",
5069
- ...digest ? { id: digest } : {},
5070
- data: {
5071
- project: stack.project,
5072
- ref: app.ref,
5073
- tag: app.tag,
5074
- source: mode,
5075
- ...digest ? { digest } : {}
5076
- }
5077
- });
5078
- }
5079
- return images;
5080
- }
5081
- function printPlan(stack, mode, files) {
5082
- logger$4.log(`\n🧱 ${stack.project} — stage ${stack.stage}`);
5083
- for (const app of stack.apps) logger$4.log(` ${app.name.padEnd(12)} ${app.url.padEnd(40)} ${mode === "build" ? "build" : "pull"} ${app.ref}`);
5084
- if (stack.infra.length > 0) logger$4.log(` infrastructure: ${stack.infra.join(", ")}`);
5085
- logger$4.log(`\n📝 Wrote ${files.length} file(s) under ${stackDir(stack.stage)}/`);
5086
- }
5087
3668
  /**
5088
3669
  * Format a fatal error for display.
5089
3670
  * Always includes the full stack trace since these are process-ending errors.
@@ -5108,7 +3689,7 @@ const logger$3 = console;
5108
3689
  * Get the Dokploy API token from stored credentials or environment
5109
3690
  */
5110
3691
  async function getApiToken() {
5111
- const token = await require_local.getDokployToken();
3692
+ const token = await require_credentials.getDokployToken();
5112
3693
  if (!token) throw new Error("Dokploy credentials not found.\nRun \"gkm login --service dokploy\" to authenticate, or set DOKPLOY_API_TOKEN.");
5113
3694
  return token;
5114
3695
  }
@@ -5117,7 +3698,7 @@ async function getApiToken() {
5117
3698
  */
5118
3699
  async function getEndpoint(providedEndpoint) {
5119
3700
  if (providedEndpoint) return providedEndpoint;
5120
- const stored = await require_local.getDokployCredentials();
3701
+ const stored = await require_credentials.getDokployCredentials();
5121
3702
  if (stored) return stored.endpoint;
5122
3703
  throw new Error("Dokploy endpoint not specified.\nEither run \"gkm login --service dokploy\" first, or provide --endpoint.");
5123
3704
  }
@@ -5220,7 +3801,7 @@ async function deployListCommand(options) {
5220
3801
  logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
5221
3802
  return;
5222
3803
  }
5223
- const storedRegistryId = await require_local.getDokployRegistryId();
3804
+ const storedRegistryId = await require_credentials.getDokployRegistryId();
5224
3805
  for (const registry of registries) {
5225
3806
  const marker = registry.registryId === storedRegistryId ? " (default)" : "";
5226
3807
  logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
@@ -6489,25 +5070,25 @@ function loadPackageJson() {
6489
5070
  * Run: pnpm --filter @geekmidas/cli sync-versions
6490
5071
  */
6491
5072
  const GEEKMIDAS_VERSIONS = {
6492
- "@geekmidas/audit": "~10.0.0-alpha.64",
6493
- "@geekmidas/auth": "~10.0.0-alpha.64",
6494
- "@geekmidas/cache": "~10.0.0-alpha.64",
6495
- "@geekmidas/client": "~10.0.0-alpha.64",
6496
- "@geekmidas/cloud": "~10.0.0-alpha.64",
6497
- "@geekmidas/constructs": "~10.0.0-alpha.64",
6498
- "@geekmidas/db": "~10.0.0-alpha.64",
6499
- "@geekmidas/emailkit": "~10.0.0-alpha.64",
6500
- "@geekmidas/envkit": "~10.0.0-alpha.64",
6501
- "@geekmidas/errors": "~10.0.0-alpha.64",
6502
- "@geekmidas/events": "~10.0.0-alpha.64",
6503
- "@geekmidas/logger": "~10.0.0-alpha.64",
6504
- "@geekmidas/manifest": "~10.0.0-alpha.64",
6505
- "@geekmidas/rate-limit": "~10.0.0-alpha.64",
6506
- "@geekmidas/schema": "~10.0.0-alpha.64",
6507
- "@geekmidas/services": "~10.0.0-alpha.64",
6508
- "@geekmidas/storage": "~10.0.0-alpha.64",
6509
- "@geekmidas/telescope": "~10.0.0-alpha.64",
6510
- "@geekmidas/testkit": "~10.0.0-alpha.64",
5073
+ "@geekmidas/audit": "~10.0.0-alpha.65",
5074
+ "@geekmidas/auth": "~10.0.0-alpha.65",
5075
+ "@geekmidas/cache": "~10.0.0-alpha.65",
5076
+ "@geekmidas/client": "~10.0.0-alpha.65",
5077
+ "@geekmidas/cloud": "~10.0.0-alpha.65",
5078
+ "@geekmidas/constructs": "~10.0.0-alpha.65",
5079
+ "@geekmidas/db": "~10.0.0-alpha.65",
5080
+ "@geekmidas/emailkit": "~10.0.0-alpha.65",
5081
+ "@geekmidas/envkit": "~10.0.0-alpha.65",
5082
+ "@geekmidas/errors": "~10.0.0-alpha.65",
5083
+ "@geekmidas/events": "~10.0.0-alpha.65",
5084
+ "@geekmidas/logger": "~10.0.0-alpha.65",
5085
+ "@geekmidas/manifest": "~10.0.0-alpha.65",
5086
+ "@geekmidas/rate-limit": "~10.0.0-alpha.65",
5087
+ "@geekmidas/schema": "~10.0.0-alpha.65",
5088
+ "@geekmidas/services": "~10.0.0-alpha.65",
5089
+ "@geekmidas/storage": "~10.0.0-alpha.65",
5090
+ "@geekmidas/telescope": "~10.0.0-alpha.65",
5091
+ "@geekmidas/testkit": "~10.0.0-alpha.65",
6511
5092
  "@geekmidas/cli": `~${loadPackageJson().version}`
6512
5093
  };
6513
5094
  //#endregion
@@ -11894,7 +10475,7 @@ async function setupCommand(options = {}) {
11894
10475
  logger$1.error("❌ Failed to resolve secrets. Exiting.");
11895
10476
  process.exit(1);
11896
10477
  }
11897
- const containers = await require_sniffer.derivedContainers(workspace, stage);
10478
+ const containers = await require_state.derivedContainers(workspace, stage);
11898
10479
  if (isMultiApp && containers.includes("postgres")) {
11899
10480
  await writeDockerEnvFromSecrets(secrets, workspace.root);
11900
10481
  logger$1.log("📄 Generated docker/.env with database passwords");
@@ -11913,7 +10494,7 @@ async function setupCommand(options = {}) {
11913
10494
  * free and quiet.
11914
10495
  */
11915
10496
  async function reconcileLocal(workspace, stage, options) {
11916
- const result = await require_sniffer.reconcileWorkspace(workspace, { stage });
10497
+ const result = await require_state.reconcileWorkspace(workspace, { stage });
11917
10498
  if (result.plan.containers.length === 0) {
11918
10499
  logger$1.log("🐳 No containers declared");
11919
10500
  return;
@@ -11923,7 +10504,7 @@ async function reconcileLocal(workspace, stage, options) {
11923
10504
  return;
11924
10505
  }
11925
10506
  logger$1.log("🐳 Services");
11926
- for (const line of require_sniffer.describeServices(result.services)) logger$1.log(line);
10507
+ for (const line of require_state.describeServices(result.services)) logger$1.log(line);
11927
10508
  const url = result.plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site" || r.kind === "file-server").map((r) => result.env[r.envKey]).find((address) => address?.startsWith("https://"));
11928
10509
  if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
11929
10510
  }
@@ -11944,7 +10525,7 @@ async function resolveSecrets(stage, workspace, options) {
11944
10525
  const secrets = await store.read(stage);
11945
10526
  if (secrets) {
11946
10527
  logger$1.log("🔐 Using existing secrets");
11947
- const reconciled = reconcileSecrets(secrets, workspace, await require_sniffer.derivedContainers(workspace, stage));
10528
+ const reconciled = reconcileSecrets(secrets, workspace, await require_state.derivedContainers(workspace, stage));
11948
10529
  if (reconciled) await store.write(stage, reconciled);
11949
10530
  return reconciled ?? secrets;
11950
10531
  }
@@ -12066,7 +10647,7 @@ async function ensureStageSecrets(stage, cwd = process.cwd()) {
12066
10647
  const { workspace } = await require_config.loadWorkspaceConfig(cwd);
12067
10648
  const store = await require_store.secretsStoreFor(workspace, stage);
12068
10649
  if (await store.read(stage)) return false;
12069
- const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_sniffer.derivedContainers(workspace, stage));
10650
+ const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_state.derivedContainers(workspace, stage));
12070
10651
  await store.write(stage, secrets);
12071
10652
  return true;
12072
10653
  }
@@ -12074,7 +10655,7 @@ async function ensureStageSecrets(stage, cwd = process.cwd()) {
12074
10655
  * Generate fresh secrets for the workspace.
12075
10656
  */
12076
10657
  async function generateFreshSecrets(stage, workspace) {
12077
- const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_sniffer.derivedContainers(workspace, stage));
10658
+ const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_state.derivedContainers(workspace, stage));
12078
10659
  const store = await require_store.secretsStoreFor(workspace, stage);
12079
10660
  await store.write(stage, secrets);
12080
10661
  logger$1.log(` Secrets written to the "${stage}" store (${store.name})`);
@@ -12115,7 +10696,7 @@ function printSummary(workspace, stage) {
12115
10696
  * A no-op when nothing is recorded — no run yet, or one already torn down —
12116
10697
  * so running it twice, or before a setup, is harmless.
12117
10698
  */
12118
- async function dropTestDatabases(cwd, root, sql = require_sniffer.pgClient) {
10699
+ async function dropTestDatabases(cwd, root, sql = require_state.pgClient) {
12119
10700
  const readyPath = (0, node_path.join)(cwd, require_ready.TEST_READY_FILE);
12120
10701
  const ready = await readReady(readyPath);
12121
10702
  const recorded = ready?.databases;
@@ -12123,9 +10704,9 @@ async function dropTestDatabases(cwd, root, sql = require_sniffer.pgClient) {
12123
10704
  const client = sql(recorded.port);
12124
10705
  for (const name of recorded.names) {
12125
10706
  if (!name.endsWith(`_test`)) throw new NotATestDatabase(name);
12126
- await client.query(void 0, `DROP DATABASE IF EXISTS ${require_sniffer.quoteIdentifier(name)} WITH (FORCE)`);
10707
+ await client.query(void 0, `DROP DATABASE IF EXISTS ${require_state.quoteIdentifier(name)} WITH (FORCE)`);
12127
10708
  }
12128
- await require_sniffer.forgetState(root, require_workspace.TEST_STAGE);
10709
+ await require_state.forgetState(root, require_workspace.TEST_STAGE);
12129
10710
  const { databases: _dropped, ...rest } = ready;
12130
10711
  await (0, node_fs_promises.writeFile)(readyPath, JSON.stringify(rest, null, 2));
12131
10712
  return recorded.names;
@@ -12165,10 +10746,10 @@ async function testCommand(options = {}) {
12165
10746
  const { ensureStageSecrets } = await Promise.resolve().then(() => setup_exports);
12166
10747
  if (await ensureStageSecrets(stage, cwd)) console.log(` 🔐 Generated fresh ${stage} secrets (auto-setup)`);
12167
10748
  }
12168
- const defaultEnv = require_sniffer.loadEnvFiles(".env");
10749
+ const defaultEnv = require_state.loadEnvFiles(".env");
12169
10750
  if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
12170
10751
  if (!options.prepare) await teardown(cwd);
12171
- const result = await require_sniffer.prepareEntryCredentials({
10752
+ const result = await require_state.prepareEntryCredentials({
12172
10753
  stage,
12173
10754
  startDocker: !options.prepare,
12174
10755
  secretsFileName: "test-secrets.json",
@@ -12189,13 +10770,13 @@ async function testCommand(options = {}) {
12189
10770
  await (0, node_fs_promises.writeFile)(result.secretsJsonPath, JSON.stringify(finalCredentials, null, 2));
12190
10771
  const gkmDir = (0, node_path.join)(cwd, ".gkm");
12191
10772
  const preloadPath = (0, node_path.join)(gkmDir, "test-credentials-preload.ts");
12192
- await require_sniffer.createCredentialsPreload(preloadPath, result.secretsJsonPath);
10773
+ await require_state.createCredentialsPreload(preloadPath, result.secretsJsonPath);
12193
10774
  const workspace = await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
12194
10775
  const manifestPath = workspace ? await require_ready.writeTestHarness({
12195
10776
  root: workspace.root,
12196
10777
  targets: [workspace.root, ...Object.values(workspace.apps).map((app) => (0, node_path.isAbsolute)(app.path) ? app.path : (0, node_path.join)(workspace.root, app.path))],
12197
- patterns: require_sniffer.constructGlobs(workspace),
12198
- cacheBackend: require_sniffer.backendsOf(workspace).cache,
10778
+ patterns: require_state.constructGlobs(workspace),
10779
+ cacheBackend: require_state.backendsOf(workspace).cache,
12199
10780
  stage: require_workspace.TEST_STAGE,
12200
10781
  env: finalCredentials,
12201
10782
  factories: (0, node_path.join)(workspace.root, workspace.test.factories ?? "test/factories")
@@ -12258,8 +10839,8 @@ async function teardown(cwd) {
12258
10839
  * the plan provisions, on the port the shared container answers.
12259
10840
  */
12260
10841
  function testDatabases(reconciled) {
12261
- const port = reconciled?.ports[require_sniffer.primaryPortKey("postgres")];
12262
- const names = reconciled ? require_sniffer.postgresDatabaseNames(reconciled.plan) : [];
10842
+ const port = reconciled?.ports[require_state.primaryPortKey("postgres")];
10843
+ const names = reconciled ? require_state.postgresDatabaseNames(reconciled.plan) : [];
12263
10844
  return port !== void 0 && names.length > 0 ? { databases: {
12264
10845
  port,
12265
10846
  names
@@ -12270,11 +10851,11 @@ function testDatabases(reconciled) {
12270
10851
  * starts without the reference data the app needs.
12271
10852
  */
12272
10853
  async function migrateTestStage(workspace, env) {
12273
- const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-tx9-P9uz.cjs"));
10854
+ const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs"));
12274
10855
  const { discover } = await Promise.resolve().then(() => require("./discover-DHdECueV.cjs")).then((n) => n.discover_exports);
12275
10856
  const sources = {};
12276
10857
  const manifest = await discover({
12277
- patterns: require_sniffer.constructGlobs(workspace),
10858
+ patterns: require_state.constructGlobs(workspace),
12278
10859
  cwd: workspace.root,
12279
10860
  sources
12280
10861
  });
@@ -12701,7 +11282,7 @@ program.command("migrate").description("Apply each database construct's migratio
12701
11282
  try {
12702
11283
  const globalOptions = program.opts();
12703
11284
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12704
- const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-tx9-P9uz.cjs"));
11285
+ const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs"));
12705
11286
  await migrateCommand({
12706
11287
  ...options,
12707
11288
  ...construct ? { construct } : {}
@@ -12715,7 +11296,7 @@ program.command("seed").description("Migrate, then run each database construct's
12715
11296
  try {
12716
11297
  const globalOptions = program.opts();
12717
11298
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12718
- const { seedCommand } = await Promise.resolve().then(() => require("./migrate-tx9-P9uz.cjs"));
11299
+ const { seedCommand } = await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs"));
12719
11300
  await seedCommand({
12720
11301
  ...options,
12721
11302
  ...construct ? { construct } : {}
@@ -12729,7 +11310,7 @@ program.command("migration").description("Write a construct's next migration: an
12729
11310
  try {
12730
11311
  const globalOptions = program.opts();
12731
11312
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12732
- const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-tx9-P9uz.cjs"));
11313
+ const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs"));
12733
11314
  await migrationCommand({
12734
11315
  construct,
12735
11316
  ...name ? { name } : {}
@@ -12768,7 +11349,7 @@ program.command("docker").description("Generate Docker deployment files").option
12768
11349
  try {
12769
11350
  const globalOptions = program.opts();
12770
11351
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12771
- await require_sniffer.dockerCommand(options);
11352
+ await require_state.dockerCommand(options);
12772
11353
  } catch (error) {
12773
11354
  console.error(formatError(error));
12774
11355
  process.exit(1);
@@ -12793,7 +11374,7 @@ program.command("prepack").description("Generate Docker files for production dep
12793
11374
  production: true,
12794
11375
  skipBundle: options.skipBundle
12795
11376
  });
12796
- await require_sniffer.dockerCommand({
11377
+ await require_state.dockerCommand({
12797
11378
  build: options.build,
12798
11379
  push: options.push,
12799
11380
  tag: options.tag,
@@ -12837,7 +11418,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
12837
11418
  try {
12838
11419
  const globalOptions = program.opts();
12839
11420
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12840
- const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-C9X98u2F.cjs"));
11421
+ const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-B1wJwgae.cjs"));
12841
11422
  await encryptionRotateCommand(construct, options);
12842
11423
  } catch (error) {
12843
11424
  console.error(formatError(error));
@@ -12848,7 +11429,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
12848
11429
  try {
12849
11430
  const globalOptions = program.opts();
12850
11431
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12851
- const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-C9X98u2F.cjs"));
11432
+ const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-B1wJwgae.cjs"));
12852
11433
  await encryptionRetireCommand(construct, key, options);
12853
11434
  } catch (error) {
12854
11435
  console.error(formatError(error));
@@ -12891,7 +11472,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
12891
11472
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12892
11473
  const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
12893
11474
  const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-CuCQBa5-.cjs"));
12894
- const { secretsStoreFor } = await Promise.resolve().then(() => require("./store-D_2ByRtW.cjs")).then((n) => n.store_exports);
11475
+ const { secretsStoreFor } = await Promise.resolve().then(() => require("./store-CJF1Tqkq.cjs")).then((n) => n.store_exports);
12895
11476
  const { workspace } = await loadWorkspaceConfig();
12896
11477
  const stage = options.stage ?? workspace.stages.local;
12897
11478
  const store = await secretsStoreFor(workspace, stage);
@@ -12900,7 +11481,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
12900
11481
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
12901
11482
  process.exit(1);
12902
11483
  }
12903
- const { derivedContainers } = await Promise.resolve().then(() => require("./sniffer-ByhbEW5A.cjs")).then((n) => n.workspace_exports);
11484
+ const { derivedContainers } = await Promise.resolve().then(() => require("./state-DF26e_lo.cjs")).then((n) => n.workspace_exports);
12904
11485
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
12905
11486
  if (!result) {
12906
11487
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -12914,8 +11495,8 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
12914
11495
  process.exit(1);
12915
11496
  }
12916
11497
  });
12917
- program.command("deploy").description("Deploy a stage through its target").option("--target <name>", "Deploy target: dokploy, sst, or one named in deploy.targets (default: deploy.default)").option("--provider <provider>", "[DEPRECATED] Use --target instead").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").option("--tag <tag>", "Image tag (default: stage-timestamp)").option("--skip-push", "Skip pushing image to registry").option("--skip-build", "Skip build step (use existing build)").option("--json", "Write events as JSON lines instead of progress; never prompts").option("--dry-run", "Show what would be created or reused; change, build and push nothing").action(async (options) => {
12918
- const { deployCli } = await Promise.resolve().then(() => require("./cli-D3jofJ6j.cjs"));
11498
+ program.command("deploy").description("Deploy a stage through its target").option("--target <name>", "Deploy target: dokploy, compose, sst, or one named in deploy.targets (default: deploy.default)").option("--provider <provider>", "[DEPRECATED] Use --target instead").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").option("--tag <tag>", "Image tag (default: stage-timestamp; compose: the commit). Compose pulls a given tag").option("--skip-push", "Skip pushing image to registry").option("--skip-build", "Skip build step (use existing build)").option("--json", "Write events as JSON lines instead of progress; never prompts").option("--dry-run", "Show what would be created or reused; change, build and push nothing").action(async (options) => {
11499
+ const { deployCli } = await Promise.resolve().then(() => require("./cli-BfH1deAT.cjs"));
12919
11500
  const globalOptions = program.opts();
12920
11501
  const code = await deployCli({
12921
11502
  cwd: (0, node_path.resolve)(globalOptions.cwd ?? process.cwd()),
@@ -12948,7 +11529,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
12948
11529
  try {
12949
11530
  const globalOptions = program.opts();
12950
11531
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12951
- const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-Dd3tMa1u.cjs"));
11532
+ const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-CYcmTdFs.cjs"));
12952
11533
  await deployGithubCommand(options);
12953
11534
  } catch (error) {
12954
11535
  console.error(formatError(error));
@@ -13083,23 +11664,8 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
13083
11664
  });
13084
11665
  program.parse();
13085
11666
  //#endregion
13086
- exports.DuplicateMigration = DuplicateMigration;
13087
- exports.KyselyNotFound = KyselyNotFound;
13088
- exports.MigrationFailed = MigrationFailed;
13089
- exports.MigrationHasNoUp = MigrationHasNoUp;
13090
- exports.MigrationsOutsideFolder = MigrationsOutsideFolder;
13091
- exports.NoOwnerCredential = NoOwnerCredential;
13092
- exports.NoSuchMigrationTarget = NoSuchMigrationTarget;
13093
- exports.SeedFailed = SeedFailed;
13094
- exports.SeedHasNoSeed = SeedHasNoSeed;
13095
- exports.UnknownDatabaseFolder = UnknownDatabaseFolder;
13096
- exports.UnknownMigrationFolder = UnknownMigrationFolder;
13097
11667
  exports.formatError = formatError;
13098
11668
  exports.generateFullstackCustomSecrets = generateFullstackCustomSecrets;
13099
- exports.migrateAndSeed = migrateAndSeed;
13100
- exports.migrateDatabases = migrateDatabases;
13101
- exports.pendingMigrations = pendingMigrations;
13102
- exports.seedDatabases = seedDatabases;
13103
11669
  exports.slug = slug;
13104
11670
  exports.stamp = stamp;
13105
11671