@geekmidas/cli 10.0.0-alpha.82 → 10.0.0-alpha.84

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 (169) hide show
  1. package/dist/{EndpointGenerator-DtnIGafh.mjs → EndpointGenerator-CVc6Oi1Y.mjs} +185 -82
  2. package/dist/EndpointGenerator-CVc6Oi1Y.mjs.map +1 -0
  3. package/dist/{EndpointGenerator-cKlnllIu.cjs → EndpointGenerator-CwLjBCFE.cjs} +203 -82
  4. package/dist/EndpointGenerator-CwLjBCFE.cjs.map +1 -0
  5. package/dist/{LegacyStateStore-Ceq7A_75.mjs → LegacyStateStore-CTsUFLNQ.mjs} +2 -2
  6. package/dist/{LegacyStateStore-Ceq7A_75.mjs.map → LegacyStateStore-CTsUFLNQ.mjs.map} +1 -1
  7. package/dist/{LegacyStateStore-Z-EKUGSX.cjs → LegacyStateStore-EDT2TUSe.cjs} +2 -2
  8. package/dist/{LegacyStateStore-Z-EKUGSX.cjs.map → LegacyStateStore-EDT2TUSe.cjs.map} +1 -1
  9. package/dist/{LocalStateStore-Bocb8dmZ.cjs → LocalStateStore-CdpzTvPn.cjs} +2 -2
  10. package/dist/{LocalStateStore-Bocb8dmZ.cjs.map → LocalStateStore-CdpzTvPn.cjs.map} +1 -1
  11. package/dist/{LocalStateStore-C72bwpG2.mjs → LocalStateStore-D1VfxRuK.mjs} +2 -2
  12. package/dist/{LocalStateStore-C72bwpG2.mjs.map → LocalStateStore-D1VfxRuK.mjs.map} +1 -1
  13. package/dist/{OpenApiTsGenerator-CdUM6Hvv.cjs → OpenApiTsGenerator-B--HV-sW.cjs} +2 -2
  14. package/dist/{OpenApiTsGenerator-CdUM6Hvv.cjs.map → OpenApiTsGenerator-B--HV-sW.cjs.map} +1 -1
  15. package/dist/{OpenApiTsGenerator-ByRPMbBN.mjs → OpenApiTsGenerator-CkvsPdgg.mjs} +2 -2
  16. package/dist/{OpenApiTsGenerator-ByRPMbBN.mjs.map → OpenApiTsGenerator-CkvsPdgg.mjs.map} +1 -1
  17. package/dist/{S3StateStore-35YfcdL_.cjs → S3StateStore-BPM74zwv.cjs} +2 -2
  18. package/dist/{S3StateStore-35YfcdL_.cjs.map → S3StateStore-BPM74zwv.cjs.map} +1 -1
  19. package/dist/{S3StateStore-C9upSW5o.mjs → S3StateStore-DVZ1YuGJ.mjs} +2 -2
  20. package/dist/{S3StateStore-C9upSW5o.mjs.map → S3StateStore-DVZ1YuGJ.mjs.map} +1 -1
  21. package/dist/{SSMStateStore-dOmFO6g1.mjs → SSMStateStore-DLvnGyyl.mjs} +2 -2
  22. package/dist/{SSMStateStore-dOmFO6g1.mjs.map → SSMStateStore-DLvnGyyl.mjs.map} +1 -1
  23. package/dist/{SSMStateStore-W-Ps22AQ.cjs → SSMStateStore-qerm5c36.cjs} +2 -2
  24. package/dist/{SSMStateStore-W-Ps22AQ.cjs.map → SSMStateStore-qerm5c36.cjs.map} +1 -1
  25. package/dist/{SubscriberGenerator-DQZeGkDW.mjs → SubscriberGenerator-BPqQf8fB.mjs} +2 -2
  26. package/dist/{SubscriberGenerator-DQZeGkDW.mjs.map → SubscriberGenerator-BPqQf8fB.mjs.map} +1 -1
  27. package/dist/{SubscriberGenerator-CYriX5BV.cjs → SubscriberGenerator-BVn7cQ-2.cjs} +2 -2
  28. package/dist/{SubscriberGenerator-CYriX5BV.cjs.map → SubscriberGenerator-BVn7cQ-2.cjs.map} +1 -1
  29. package/dist/{add-1T3y6E4M.cjs → add-BQ8v1ZII.cjs} +3 -3
  30. package/dist/{add-1T3y6E4M.cjs.map → add-BQ8v1ZII.cjs.map} +1 -1
  31. package/dist/{add-DnYHJOap.mjs → add-DbnzC7Kl.mjs} +3 -3
  32. package/dist/{add-DnYHJOap.mjs.map → add-DbnzC7Kl.mjs.map} +1 -1
  33. package/dist/{bundler-DlKdxCGh.mjs → bundler-D4030Afw.mjs} +5 -5
  34. package/dist/{bundler-DlKdxCGh.mjs.map → bundler-D4030Afw.mjs.map} +1 -1
  35. package/dist/{bundler-YNvLdvlh.cjs → bundler-DjrRffko.cjs} +5 -5
  36. package/dist/{bundler-YNvLdvlh.cjs.map → bundler-DjrRffko.cjs.map} +1 -1
  37. package/dist/{cli-CWee1-wU.cjs → cli-D8YQAYUX.cjs} +3 -3
  38. package/dist/{cli-CWee1-wU.cjs.map → cli-D8YQAYUX.cjs.map} +1 -1
  39. package/dist/{cli-qMszGvDu.mjs → cli-DwTa9d_8.mjs} +3 -3
  40. package/dist/{cli-qMszGvDu.mjs.map → cli-DwTa9d_8.mjs.map} +1 -1
  41. package/dist/{compose-D4sD4Lgr.mjs → compose-C5wz8mf9.mjs} +64 -24
  42. package/dist/compose-C5wz8mf9.mjs.map +1 -0
  43. package/dist/{compose-qnDyPJNp.cjs → compose-DqWh1TaO.cjs} +64 -24
  44. package/dist/compose-DqWh1TaO.cjs.map +1 -0
  45. package/dist/{config-Mr4rcXFC.d.cts → config-BzIfP8a-.d.cts} +16 -3
  46. package/dist/config-BzIfP8a-.d.cts.map +1 -0
  47. package/dist/{config-L_6NivCp.d.mts → config-DhdzmndX.d.mts} +16 -3
  48. package/dist/config-DhdzmndX.d.mts.map +1 -0
  49. package/dist/config.cjs +1 -1
  50. package/dist/config.d.cts +3 -3
  51. package/dist/config.d.mts +3 -3
  52. package/dist/config.mjs +1 -1
  53. package/dist/deploy/public.cjs +6 -6
  54. package/dist/deploy/public.d.cts +3 -3
  55. package/dist/deploy/public.d.cts.map +1 -1
  56. package/dist/deploy/public.d.mts +3 -3
  57. package/dist/deploy/public.d.mts.map +1 -1
  58. package/dist/deploy/public.mjs +6 -6
  59. package/dist/{deploy-C00DAKOt.mjs → deploy-BwIKGxWH.mjs} +14 -5
  60. package/dist/deploy-BwIKGxWH.mjs.map +1 -0
  61. package/dist/{deploy-CXP2fXvK.cjs → deploy-C8sWg81_.cjs} +14 -5
  62. package/dist/deploy-C8sWg81_.cjs.map +1 -0
  63. package/dist/{dokploy-BHcSmghb.cjs → dokploy-BkRxJbfi.cjs} +2 -2
  64. package/dist/{dokploy-BHcSmghb.cjs.map → dokploy-BkRxJbfi.cjs.map} +1 -1
  65. package/dist/{dokploy-B3zoCmJb.mjs → dokploy-ijjms1-h.mjs} +2 -2
  66. package/dist/{dokploy-B3zoCmJb.mjs.map → dokploy-ijjms1-h.mjs.map} +1 -1
  67. package/dist/{drivers-B4RP2B2O.mjs → drivers-DTZzxeIl.mjs} +2 -2
  68. package/dist/{drivers-B4RP2B2O.mjs.map → drivers-DTZzxeIl.mjs.map} +1 -1
  69. package/dist/{drivers-CJx76mcb.cjs → drivers-LYSdQDmd.cjs} +2 -2
  70. package/dist/{drivers-CJx76mcb.cjs.map → drivers-LYSdQDmd.cjs.map} +1 -1
  71. package/dist/{encryption-DPFljXtM.mjs → encryption-BRcBAmjL.mjs} +3 -3
  72. package/dist/{encryption-DPFljXtM.mjs.map → encryption-BRcBAmjL.mjs.map} +1 -1
  73. package/dist/{encryption-DLin89I_.cjs → encryption-g6PzwwfL.cjs} +3 -3
  74. package/dist/{encryption-DLin89I_.cjs.map → encryption-g6PzwwfL.cjs.map} +1 -1
  75. package/dist/{engine-CgzgIg-_.mjs → engine-BP0CZ5fh.mjs} +33 -13
  76. package/dist/engine-BP0CZ5fh.mjs.map +1 -0
  77. package/dist/{engine-BS3iX9Mu.cjs → engine-DLEJsZ3i.cjs} +33 -13
  78. package/dist/engine-DLEJsZ3i.cjs.map +1 -0
  79. package/dist/{github-D8KVmNRg.cjs → github-B8YjwdLK.cjs} +3 -3
  80. package/dist/{github-D8KVmNRg.cjs.map → github-B8YjwdLK.cjs.map} +1 -1
  81. package/dist/{github-DL4zwJsR.mjs → github-Zs-dVJbR.mjs} +3 -3
  82. package/dist/{github-DL4zwJsR.mjs.map → github-Zs-dVJbR.mjs.map} +1 -1
  83. package/dist/{index-C0UmP_lg.d.mts → index-D1ODeM9s.d.cts} +7 -3
  84. package/dist/index-D1ODeM9s.d.cts.map +1 -0
  85. package/dist/{index-DTEPwLml.d.cts → index-f77HdqN5.d.mts} +7 -3
  86. package/dist/index-f77HdqN5.d.mts.map +1 -0
  87. package/dist/index.cjs +553 -134
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.mjs +553 -134
  90. package/dist/index.mjs.map +1 -1
  91. package/dist/{migrate-DIjG8-QR.cjs → migrate-BfZN1S32.cjs} +3 -3
  92. package/dist/{migrate-DIjG8-QR.cjs.map → migrate-BfZN1S32.cjs.map} +1 -1
  93. package/dist/{migrate-CCejlgU0.mjs → migrate-CdXcX0XR.mjs} +3 -3
  94. package/dist/{migrate-CCejlgU0.mjs.map → migrate-CdXcX0XR.mjs.map} +1 -1
  95. package/dist/{migrate-Cd3kcI7o.mjs → migrate-DOMU4uKe.mjs} +3 -3
  96. package/dist/{migrate-Cd3kcI7o.mjs.map → migrate-DOMU4uKe.mjs.map} +1 -1
  97. package/dist/{migrate-BdKhIJgT.cjs → migrate-FoL_mZIz.cjs} +3 -3
  98. package/dist/{migrate-BdKhIJgT.cjs.map → migrate-FoL_mZIz.cjs.map} +1 -1
  99. package/dist/openapi.cjs +3 -3
  100. package/dist/openapi.d.cts +1 -1
  101. package/dist/openapi.d.mts +1 -1
  102. package/dist/openapi.mjs +3 -3
  103. package/dist/{ready-4kx2pAhm.cjs → ready-BpNbORRB.cjs} +5 -5
  104. package/dist/{ready-4kx2pAhm.cjs.map → ready-BpNbORRB.cjs.map} +1 -1
  105. package/dist/{ready-DhZbZtFw.mjs → ready-b6vm0-W-.mjs} +5 -5
  106. package/dist/{ready-DhZbZtFw.mjs.map → ready-b6vm0-W-.mjs.map} +1 -1
  107. package/dist/reconcile/public.d.cts +1 -1
  108. package/dist/reconcile/public.d.mts +1 -1
  109. package/dist/{resolve-ApSUK_HB.cjs → resolve-T2I-5FcT.cjs} +3 -3
  110. package/dist/{resolve-ApSUK_HB.cjs.map → resolve-T2I-5FcT.cjs.map} +1 -1
  111. package/dist/{resolve-DY9TaVeV.mjs → resolve-r7PAs6ng.mjs} +3 -3
  112. package/dist/{resolve-DY9TaVeV.mjs.map → resolve-r7PAs6ng.mjs.map} +1 -1
  113. package/dist/{rollback-DXU6cr7q.mjs → rollback-BSdxnW3X.mjs} +5 -5
  114. package/dist/{rollback-DXU6cr7q.mjs.map → rollback-BSdxnW3X.mjs.map} +1 -1
  115. package/dist/{rollback-CdGhCk1d.cjs → rollback-CPSDjGse.cjs} +5 -5
  116. package/dist/{rollback-CdGhCk1d.cjs.map → rollback-CPSDjGse.cjs.map} +1 -1
  117. package/dist/{runtime-ekxfPNM-.d.mts → runtime-CNu2C2hB.d.mts} +2 -2
  118. package/dist/{runtime-D6A-C6zh.d.cts.map → runtime-CNu2C2hB.d.mts.map} +1 -1
  119. package/dist/{runtime-D6A-C6zh.d.cts → runtime-DMLgL26v.d.cts} +2 -2
  120. package/dist/{runtime-ekxfPNM-.d.mts.map → runtime-DMLgL26v.d.cts.map} +1 -1
  121. package/dist/{seeds-DbyqaXW4.mjs → seeds-BK8caVMm.mjs} +1707 -1612
  122. package/dist/seeds-BK8caVMm.mjs.map +1 -0
  123. package/dist/{seeds-BLzrSwEr.cjs → seeds-DQ0FG57Z.cjs} +2558 -2421
  124. package/dist/seeds-DQ0FG57Z.cjs.map +1 -0
  125. package/dist/{sniffer-ByWtH28i.mjs → sniffer-CqE_KTTp.mjs} +6 -6
  126. package/dist/{sniffer-ByWtH28i.mjs.map → sniffer-CqE_KTTp.mjs.map} +1 -1
  127. package/dist/{sniffer-BjG-1mlD.cjs → sniffer-hSf5mRgV.cjs} +6 -6
  128. package/dist/{sniffer-BjG-1mlD.cjs.map → sniffer-hSf5mRgV.cjs.map} +1 -1
  129. package/dist/stages-CS1sjcRb.cjs +174 -0
  130. package/dist/stages-CS1sjcRb.cjs.map +1 -0
  131. package/dist/stages-CSXt7N2b.mjs +174 -0
  132. package/dist/stages-CSXt7N2b.mjs.map +1 -0
  133. package/dist/target/public.cjs +1 -1
  134. package/dist/target/public.d.cts +2 -2
  135. package/dist/target/public.d.mts +2 -2
  136. package/dist/target/public.mjs +1 -1
  137. package/dist/{types-DZtj9jeI.d.cts → types-BZn-8ssD.d.mts} +75 -22
  138. package/dist/types-BZn-8ssD.d.mts.map +1 -0
  139. package/dist/{types-BzCavjGt.d.mts → types-DiZj6hqp.d.cts} +76 -21
  140. package/dist/types-DiZj6hqp.d.cts.map +1 -0
  141. package/dist/vitest.cjs +1 -1
  142. package/dist/vitest.mjs +1 -1
  143. package/dist/workspace/index.cjs +2 -1
  144. package/dist/workspace/index.d.cts +3 -3
  145. package/dist/workspace/index.d.mts +3 -3
  146. package/dist/workspace/index.mjs +2 -2
  147. package/dist/{workspace-Bp8FEkF-.cjs → workspace-Bic_R3tz.cjs} +345 -99
  148. package/dist/workspace-Bic_R3tz.cjs.map +1 -0
  149. package/dist/{workspace-BBdqmkch.mjs → workspace-Drz4jsvy.mjs} +314 -98
  150. package/dist/workspace-Drz4jsvy.mjs.map +1 -0
  151. package/package.json +15 -15
  152. package/dist/EndpointGenerator-DtnIGafh.mjs.map +0 -1
  153. package/dist/EndpointGenerator-cKlnllIu.cjs.map +0 -1
  154. package/dist/compose-D4sD4Lgr.mjs.map +0 -1
  155. package/dist/compose-qnDyPJNp.cjs.map +0 -1
  156. package/dist/config-L_6NivCp.d.mts.map +0 -1
  157. package/dist/config-Mr4rcXFC.d.cts.map +0 -1
  158. package/dist/deploy-C00DAKOt.mjs.map +0 -1
  159. package/dist/deploy-CXP2fXvK.cjs.map +0 -1
  160. package/dist/engine-BS3iX9Mu.cjs.map +0 -1
  161. package/dist/engine-CgzgIg-_.mjs.map +0 -1
  162. package/dist/index-C0UmP_lg.d.mts.map +0 -1
  163. package/dist/index-DTEPwLml.d.cts.map +0 -1
  164. package/dist/seeds-BLzrSwEr.cjs.map +0 -1
  165. package/dist/seeds-DbyqaXW4.mjs.map +0 -1
  166. package/dist/types-BzCavjGt.d.mts.map +0 -1
  167. package/dist/types-DZtj9jeI.d.cts.map +0 -1
  168. package/dist/workspace-BBdqmkch.mjs.map +0 -1
  169. package/dist/workspace-Bp8FEkF-.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -5,22 +5,22 @@ const require_run = require("./run-fVZ-rLnc.cjs");
5
5
  const require_credentials = require("./credentials-BVs7mrtw.cjs");
6
6
  const require_discover = require("./discover-D1It5r_h.cjs");
7
7
  const require_derive = require("./derive-DAbo49sm.cjs");
8
- const require_workspace = require("./workspace-Bp8FEkF-.cjs");
8
+ const require_workspace = require("./workspace-Bic_R3tz.cjs");
9
9
  const require_config = require("./config.cjs");
10
- const require_seeds = require("./seeds-BLzrSwEr.cjs");
11
- const require_EndpointGenerator = require("./EndpointGenerator-cKlnllIu.cjs");
10
+ const require_seeds = require("./seeds-DQ0FG57Z.cjs");
11
+ const require_EndpointGenerator = require("./EndpointGenerator-CwLjBCFE.cjs");
12
12
  const require_openapi = require("./openapi.cjs");
13
- const require_SubscriberGenerator = require("./SubscriberGenerator-CYriX5BV.cjs");
13
+ const require_SubscriberGenerator = require("./SubscriberGenerator-BVn7cQ-2.cjs");
14
14
  const require_appPorts = require("./appPorts-Cu79hJF6.cjs");
15
- const require_drivers = require("./drivers-CJx76mcb.cjs");
15
+ const require_drivers = require("./drivers-LYSdQDmd.cjs");
16
16
  const require_emit = require("./emit-Dmc1nvdS.cjs");
17
- const require_sniffer = require("./sniffer-BjG-1mlD.cjs");
18
- const require_deploy = require("./deploy-CXP2fXvK.cjs");
19
- const require_compose = require("./compose-qnDyPJNp.cjs");
17
+ const require_sniffer = require("./sniffer-hSf5mRgV.cjs");
18
+ const require_deploy = require("./deploy-C8sWg81_.cjs");
19
+ const require_compose = require("./compose-DqWh1TaO.cjs");
20
20
  const require_credentialSchemas = require("./credentialSchemas-DCR4Wd1W.cjs");
21
- const require_LocalStateStore = require("./LocalStateStore-Bocb8dmZ.cjs");
21
+ const require_LocalStateStore = require("./LocalStateStore-CdpzTvPn.cjs");
22
22
  const require_utils = require("./utils-D2Nk1Nv2.cjs");
23
- const require_ready = require("./ready-4kx2pAhm.cjs");
23
+ const require_ready = require("./ready-BpNbORRB.cjs");
24
24
  let node_path = require("node:path");
25
25
  let commander = require("commander");
26
26
  let node_process = require("node:process");
@@ -53,7 +53,7 @@ crypto = require_rolldown_runtime.__toESM(crypto, 1);
53
53
  let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions");
54
54
  let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
55
55
  //#region package.json
56
- var version = "10.0.0-alpha.82";
56
+ var version = "10.0.0-alpha.84";
57
57
  //#endregion
58
58
  //#region src/prompt.ts
59
59
  /**
@@ -136,7 +136,7 @@ const logger$8 = console;
136
136
  * Validate Dokploy token by making a test API call
137
137
  */
138
138
  async function validateDokployToken(endpoint, token) {
139
- const { DokployApi } = await Promise.resolve().then(() => require("./sniffer-BjG-1mlD.cjs")).then((n) => n.dokploy_api_exports);
139
+ const { DokployApi } = await Promise.resolve().then(() => require("./sniffer-hSf5mRgV.cjs")).then((n) => n.dokploy_api_exports);
140
140
  return new DokployApi({
141
141
  baseUrl: endpoint,
142
142
  token
@@ -536,10 +536,9 @@ var NoLocalCredentials = class extends Error {
536
536
  /** The logins of every service the workspace last published. */
537
537
  function devCredentials(workspace, credentials, ports) {
538
538
  const containers = SIGNED_IN.filter((container) => require_seeds.portsOf(container).some((port) => ports[port.key] !== void 0));
539
- const logs = require_workspace.resolveLogs(workspace.deploy?.compose?.logs);
540
- const local = logs ? {
541
- url: logs.public ? `https://logs.${workspace.name}.localhost` : `http://localhost:${logs.port}`,
542
- via: `gkm compose --stage ${workspace.stages.local}`
539
+ const local = ports.openobserve !== void 0 ? {
540
+ url: `http://localhost:${ports.openobserve}`,
541
+ via: "gkm dev"
543
542
  } : void 0;
544
543
  return {
545
544
  workspace: workspace.name,
@@ -759,6 +758,7 @@ async function collect(records) {
759
758
  root,
760
759
  stage: lead.workspace.stage,
761
760
  manifest: group.find((r) => r.manifest)?.manifest ?? null,
761
+ telemetry: group.find((r) => r.telemetry)?.telemetry ?? null,
762
762
  apps: discovered,
763
763
  allowedOrigins: [...new Set(group.flatMap((r) => r.allowedOrigins))]
764
764
  });
@@ -1103,6 +1103,7 @@ var DiscoverySession = class DiscoverySession {
1103
1103
  workspace: options.workspace,
1104
1104
  allowedOrigins: [...options.allowedOrigins ?? []],
1105
1105
  ...options.manifest ? { manifest: options.manifest } : {},
1106
+ ...options.telemetry ? { telemetry: options.telemetry } : {},
1106
1107
  apps: options.apps
1107
1108
  };
1108
1109
  writeRecord(registry, record);
@@ -1856,7 +1857,8 @@ async function devCommand(options) {
1856
1857
  hooks,
1857
1858
  skipBundle: true,
1858
1859
  bustCache,
1859
- serveEmpty: true
1860
+ serveEmpty: true,
1861
+ workspaceName: workspace.name
1860
1862
  }));
1861
1863
  logger$5.log(`🔨 Building ${workspaceAppName ?? "the app"}`);
1862
1864
  const initial = await build();
@@ -1864,6 +1866,7 @@ async function devCommand(options) {
1864
1866
  let secretsJsonPath;
1865
1867
  let publicUrl;
1866
1868
  let manifest;
1869
+ let telemetryUi;
1867
1870
  const appSecrets = await require_seeds.loadSecretsForApp(workspace ? await require_seeds.secretsStoreFor(workspace, config.stages.local) : new require_seeds.FileSecretsStore(secretsRoot), config.stages.local);
1868
1871
  if (workspace) {
1869
1872
  const reconciled = await require_seeds.reconcileWorkspace(workspace, {
@@ -1875,10 +1878,25 @@ async function devCommand(options) {
1875
1878
  logger$5.log("🐳 Services");
1876
1879
  for (const line of require_seeds.describeServices(reconciled.services)) logger$5.log(line);
1877
1880
  }
1881
+ if (!process.env["GKM_DISCOVERY_QUIET"] && reconciled.credentials && reconciled.ports) {
1882
+ const { services: logins } = devCredentials(workspace, reconciled.credentials, reconciled.ports);
1883
+ if (logins.length > 0) {
1884
+ logger$5.log("🔑 Logins (again any time: gkm dev:credentials)");
1885
+ for (const line of describeLogins(logins)) logger$5.log(line);
1886
+ }
1887
+ }
1878
1888
  Object.assign(appSecrets, reconciled.env);
1889
+ const scoped = require_seeds.scopeTelemetryEnv(appSecrets, {
1890
+ uses: reconciled.manifest !== void 0 && require_seeds.appTelemetry(reconciled.manifest, workspaceAppName) !== void 0,
1891
+ serviceName: workspaceAppName,
1892
+ telemetry: reconciled.env
1893
+ });
1894
+ for (const key of Object.keys(appSecrets)) if (!(key in scoped)) delete appSecrets[key];
1895
+ Object.assign(appSecrets, scoped);
1879
1896
  const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && require_derive.appKey(r.id) === workspaceAppName);
1880
1897
  publicUrl = own ? reconciled.env[own.envKey] : void 0;
1881
1898
  manifest = reconciled.manifest;
1899
+ telemetryUi = telemetryUiOf(reconciled.services);
1882
1900
  }
1883
1901
  if (Object.keys(appSecrets).length > 0) {
1884
1902
  const secretsDir = (0, node_path.join)(secretsRoot, ".gkm");
@@ -1901,6 +1919,7 @@ async function devCommand(options) {
1901
1919
  },
1902
1920
  allowedOrigins: workspace.dev?.allowedOrigins ?? [],
1903
1921
  ...manifest ? { manifest } : {},
1922
+ ...telemetryUi ? { telemetry: telemetryUi } : {},
1904
1923
  apps: [{
1905
1924
  name: workspaceAppName,
1906
1925
  type: "backend",
@@ -2209,17 +2228,20 @@ async function workspaceDevCommand(configured, options) {
2209
2228
  logger$5.log("🔑 Logins (again any time: gkm dev:credentials)");
2210
2229
  for (const line of describeLogins(logins)) logger$5.log(line);
2211
2230
  }
2212
- const secretsEnv = {
2231
+ const secretsEnv = require_seeds.scopeTelemetryEnv({
2213
2232
  ...rawSecrets,
2214
2233
  ...reconciled.env
2215
- };
2234
+ }, {
2235
+ uses: false,
2236
+ serviceName: workspace.name
2237
+ });
2216
2238
  const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
2217
2239
  for (const { id, port } of fakes) logger$5.log(`🎭 ${id} fake: http://localhost:${port}`);
2218
2240
  logger$5.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
2219
2241
  if (options.migrate || options.seed) {
2220
- const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-DIjG8-QR.cjs"));
2242
+ const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-BfZN1S32.cjs"));
2221
2243
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
2222
- } else await Promise.resolve().then(() => require("./migrate-DIjG8-QR.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
2244
+ } else await Promise.resolve().then(() => require("./migrate-BfZN1S32.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
2223
2245
  logger$5.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
2224
2246
  });
2225
2247
  const appUrls = {};
@@ -2274,6 +2296,7 @@ async function workspaceDevCommand(configured, options) {
2274
2296
  break;
2275
2297
  }
2276
2298
  }
2299
+ const workspaceTelemetry = telemetryUiOf(reconciled.services);
2277
2300
  const discovery = await joinDiscovery({
2278
2301
  role: "workspace",
2279
2302
  port: discoveryPort(workspace.dev),
@@ -2284,6 +2307,7 @@ async function workspaceDevCommand(configured, options) {
2284
2307
  },
2285
2308
  allowedOrigins: workspace.dev?.allowedOrigins ?? [],
2286
2309
  ...reconciled.manifest ? { manifest: reconciled.manifest } : {},
2310
+ ...workspaceTelemetry ? { telemetry: workspaceTelemetry } : {},
2287
2311
  apps: buildOrder.flatMap((name) => {
2288
2312
  const app = workspace.apps[name];
2289
2313
  if (!app) return [];
@@ -2481,6 +2505,18 @@ var EntryRunner = class {
2481
2505
  }
2482
2506
  };
2483
2507
  /**
2508
+ * Where the local OpenObserve is, and who signs in to it, when reconcile runs
2509
+ * one — for the discovery endpoint, which is told no password.
2510
+ */
2511
+ function telemetryUiOf(services) {
2512
+ const ui = services.find((s) => s.container === require_seeds.TELEMETRY_CONTAINER);
2513
+ if (!ui) return void 0;
2514
+ return {
2515
+ url: ui.address,
2516
+ email: require_seeds.LOCAL_LOGS_EMAIL
2517
+ };
2518
+ }
2519
+ /**
2484
2520
  * Generate the content of the dev server entry file (server.ts).
2485
2521
  * Uses dynamic import for createApp so Credentials are populated
2486
2522
  * before any app modules evaluate.
@@ -2526,6 +2562,7 @@ if (existsSync(secretsPath)) {
2526
2562
  injectWs(server);
2527
2563
  console.log('🔌 Telescope real-time updates enabled');
2528
2564
  }`;
2565
+ const telemetry = require_EndpointGenerator.startTelemetryCode(["/health", "/ready"]);
2529
2566
  if (selfServing) return `#!/usr/bin/env node
2530
2567
  /**
2531
2568
  * Development server entry point for a surface that serves itself
@@ -2535,8 +2572,17 @@ ${credentialsInjection}${parentWatch}const port = process.argv.includes('--port'
2535
2572
  ? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
2536
2573
  : 3000;
2537
2574
 
2575
+ ${telemetry}
2538
2576
  // Dynamic import so Credentials are populated before the surface evaluates
2539
- const { app } = await import('${appImportPath}');
2577
+ const { app: surface } = await import('${appImportPath}');
2578
+
2579
+ // Behind the request spans when there are any, as the production server is.
2580
+ const { Hono } = await import('hono');
2581
+ const app = requestSpans ? new Hono() : surface;
2582
+ if (requestSpans) {
2583
+ ${require_EndpointGenerator.mountRequestSpansCode("app").trim().replace(/\n/g, "\n ")}
2584
+ app.route('/', surface);
2585
+ }
2540
2586
 
2541
2587
  ${serveCode.replace(/^ {4}/gm, "")}
2542
2588
  `;
@@ -2549,11 +2595,16 @@ ${credentialsInjection}${parentWatch}const port = process.argv.includes('--port'
2549
2595
  ? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
2550
2596
  : 3000;
2551
2597
 
2598
+ ${telemetry}
2552
2599
  // Dynamic import so Credentials are populated before env.ts evaluates
2553
2600
  const { createApp } = await import('${appImportPath}');
2554
2601
 
2602
+ // The span middleware first, as the production server mounts it.
2603
+ const { Hono } = await import('hono');
2604
+ const root = new Hono();
2605
+ ${require_EndpointGenerator.mountRequestSpansCode("root")}
2555
2606
  // createApp is async to support optional WebSocket setup
2556
- const { app, start } = await createApp(undefined, ${enableOpenApi});
2607
+ const { app, start } = await createApp(root, ${enableOpenApi});
2557
2608
 
2558
2609
  // Start the server
2559
2610
  start({
@@ -2995,6 +3046,7 @@ export default app;
2995
3046
  */
2996
3047
  async function writeSurfaceServer(outputDir, options) {
2997
3048
  await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
3049
+ await require_EndpointGenerator.writeTelemetryModule(outputDir, options.telemetry);
2998
3050
  const shutdown = options.gracefulShutdown ? `
2999
3051
  // Stop taking requests, let in-flight ones finish, then close what the
3000
3052
  // construct opened — bounded by GKM_SHUTDOWN_TIMEOUT_MS, under Docker's 10s
@@ -3021,12 +3073,14 @@ process.on('SIGINT', shutdown);
3021
3073
  */
3022
3074
  import { serve } from '@hono/node-server';
3023
3075
  import { Hono } from 'hono';
3024
- ${options.gracefulShutdown ? "import { runShutdownHooks } from '@geekmidas/constructs';\n" : ""}import { app as surface } from './app.js';
3076
+ ${options.gracefulShutdown ? "import { runShutdownHooks } from '@geekmidas/constructs';\n" : ""}
3077
+ ${require_EndpointGenerator.startTelemetryCode([options.healthCheck])}
3078
+ const { app: surface } = await import('./app.js');
3025
3079
 
3026
3080
  const port = Number(process.env.PORT) || 3000;
3027
3081
 
3028
3082
  const app = new Hono();
3029
- // First, so a probe is answered without reaching the surface's own routes.
3083
+ ${require_EndpointGenerator.mountRequestSpansCode("app")}// First, so a probe is answered without reaching the surface's own routes.
3030
3084
  app.get(${JSON.stringify(options.healthCheck)}, (c) =>
3031
3085
  c.json({ status: 'ok', timestamp: Date.now() }),
3032
3086
  );
@@ -3223,11 +3277,23 @@ async function buildApp(input) {
3223
3277
  });
3224
3278
  if (selfServing) {
3225
3279
  await writeSurfaceEntry((0, node_path.join)(appRoot, ".gkm", "server"), selfServing.source);
3280
+ const telemetry = require_EndpointGenerator.telemetryFor({
3281
+ node: require_seeds.telemetryOf(declared, selfServing.id),
3282
+ appRoot,
3283
+ app: appNameOf(input),
3284
+ cwd: workspaceRoot,
3285
+ serviceName: selfServing.id,
3286
+ ...input.workspaceName && { workspaceName: input.workspaceName }
3287
+ });
3288
+ await require_EndpointGenerator.writeTelemetryModule((0, node_path.join)(appRoot, ".gkm", "server"), telemetry);
3226
3289
  logger$4.log(`Generated a server for ${selfServing.id} from its own declaration`);
3227
3290
  if (production?.bundle && !input.skipBundle) {
3228
3291
  const outputDir = (0, node_path.join)(appRoot, ".gkm", "server");
3229
- const entryPoint = await writeSurfaceServer(outputDir, production);
3230
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-YNvLdvlh.cjs"));
3292
+ const entryPoint = await writeSurfaceServer(outputDir, {
3293
+ ...production,
3294
+ ...telemetry ? { telemetry } : {}
3295
+ });
3296
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-DjrRffko.cjs"));
3231
3297
  await bundleServer({
3232
3298
  entryPoint,
3233
3299
  outputDir: (0, node_path.join)(outputDir, "dist"),
@@ -3262,12 +3328,7 @@ async function buildApp(input) {
3262
3328
  events: eventsBackends
3263
3329
  }),
3264
3330
  eventsBackends,
3265
- markOptional: input.markOptional ?? false,
3266
- ...production && { telemetry: require_EndpointGenerator.telemetryFor({
3267
- appRoot,
3268
- surfaceId: derived.surface?.id,
3269
- workspaceName: input.workspaceName
3270
- }) }
3331
+ markOptional: input.markOptional ?? false
3271
3332
  };
3272
3333
  const endpointGenerator = new require_EndpointGenerator.EndpointGenerator();
3273
3334
  const functionGenerator = new FunctionGenerator();
@@ -3285,6 +3346,15 @@ async function buildApp(input) {
3285
3346
  topicGenerator.load(code, appRoot, bustCache)
3286
3347
  ]);
3287
3348
  const allEndpoints = servedBy(loadedEndpoints, derived.surface);
3349
+ if (target === "server") buildContext.telemetry = require_EndpointGenerator.telemetryFor({
3350
+ node: derived.surface ? require_seeds.telemetryOf(declared, derived.surface.id) : void 0,
3351
+ appRoot,
3352
+ app: appNameOf(input),
3353
+ cwd: workspaceRoot,
3354
+ serviceName: derived.surface?.id ?? (0, node_path.basename)(appRoot),
3355
+ ...input.workspaceName && { workspaceName: input.workspaceName },
3356
+ routes: require_EndpointGenerator.routeTelemetry(allEndpoints.map(({ construct }) => construct))
3357
+ });
3288
3358
  logger$4.log(`Found ${allEndpoints.length} endpoints`);
3289
3359
  logger$4.log(`Found ${allFunctions.length} functions`);
3290
3360
  logger$4.log(`Found ${allCrons.length} crons`);
@@ -3312,6 +3382,14 @@ async function buildApp(input) {
3312
3382
  const appName = Object.entries(input.workspace.apps).find(([, app]) => (0, node_path.resolve)(input.workspace.root, app.path) === (0, node_path.resolve)(appRoot))?.[0];
3313
3383
  await buildWorkers({
3314
3384
  context: buildContext,
3385
+ telemetry: (workerId) => require_EndpointGenerator.telemetryFor({
3386
+ node: require_seeds.telemetryOf(declared, workerId),
3387
+ appRoot,
3388
+ app: appNameOf(input),
3389
+ cwd: workspaceRoot,
3390
+ serviceName: workerId,
3391
+ ...input.workspaceName && { workspaceName: input.workspaceName }
3392
+ }),
3315
3393
  workers: require_seeds.workerUnits(input.workspace, declared, background).filter((unit) => unit.app === appName),
3316
3394
  serverDir: (0, node_path.join)(appRoot, ".gkm", "server"),
3317
3395
  crons: allCrons,
@@ -3323,7 +3401,14 @@ async function buildApp(input) {
3323
3401
  }
3324
3402
  await require_openapi.generateOpenApi(allEndpoints.map(({ construct }) => construct), {
3325
3403
  openapi: config.openapi,
3326
- root: workspaceRoot
3404
+ root: workspaceRoot,
3405
+ ...derived.surface ? clientTelemetry({
3406
+ manifest: declared,
3407
+ surfaceId: derived.surface.id,
3408
+ telemetry: input.workspace?.deploy?.telemetry,
3409
+ stage: input.stage,
3410
+ localStage: input.workspace?.stages.local ?? config.stages?.local
3411
+ }) : {}
3327
3412
  });
3328
3413
  return {
3329
3414
  ...result,
@@ -3341,9 +3426,11 @@ async function buildWorkers(input) {
3341
3426
  const crons = owned(input.crons);
3342
3427
  const queues = owned(input.queues);
3343
3428
  const subscribers = owned(input.subscribers);
3429
+ const telemetry = input.telemetry(worker.id);
3344
3430
  const entryPoint = await generator.build({
3345
3431
  context: input.context,
3346
3432
  workerId: worker.id,
3433
+ ...telemetry ? { telemetry } : {},
3347
3434
  outputDir: require_seeds.workerEntryDir(input.serverDir, worker.id),
3348
3435
  crons,
3349
3436
  queues,
@@ -3351,7 +3438,7 @@ async function buildWorkers(input) {
3351
3438
  });
3352
3439
  logger$4.log(`Generated worker ${worker.id}: ${crons.length} crons, ${queues.length} queues, ${subscribers.length} subscribers`);
3353
3440
  if (!input.bundle) continue;
3354
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-YNvLdvlh.cjs"));
3441
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-DjrRffko.cjs"));
3355
3442
  const outfile = require_seeds.workerBundleName(worker.id);
3356
3443
  await bundleServer({
3357
3444
  entryPoint,
@@ -3370,6 +3457,22 @@ async function buildWorkers(input) {
3370
3457
  logger$4.log(`✅ Bundle complete: .gkm/server/dist/${outfile}`);
3371
3458
  }
3372
3459
  }
3460
+ /**
3461
+ * Whether the surface's generated client propagates trace context, and at
3462
+ * what rate: on when a site that calls it has an edge to a `Telemetry`
3463
+ * construct, at the stage's sample rate — every trace locally, and on a build
3464
+ * for no stage. Off for a stage that sends no telemetry.
3465
+ */
3466
+ function clientTelemetry(options) {
3467
+ if (!require_seeds.surfaceClientTraced(options.manifest, options.surfaceId)) return {};
3468
+ const local = !options.stage || options.stage === options.localStage;
3469
+ const sampleRate = require_workspace.stageSampleRate(options.telemetry, options.stage ?? "", local);
3470
+ return sampleRate === void 0 ? {} : { telemetry: { sampleRate } };
3471
+ }
3472
+ /** The app being built, by its workspace name or its directory's. */
3473
+ function appNameOf(input) {
3474
+ return (input.workspace ? Object.entries(input.workspace.apps).find(([, app]) => (0, node_path.resolve)(input.workspace.root, app.path) === (0, node_path.resolve)(input.appRoot))?.[0] : void 0) ?? (0, node_path.basename)(input.appRoot);
3475
+ }
3373
3476
  async function buildForTarget(target, context, appRoot, workspaceRoot, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, endpoints, functions, crons, subscribers, queues, topics, enableOpenApi, skipBundle, stage) {
3374
3477
  const outputDir = (0, node_path.join)(appRoot, ".gkm", target);
3375
3478
  await Promise.all([...SUPERSEDED_OUTPUT.map((dir) => (0, node_path.join)(appRoot, ".gkm", dir)), ...appRoot === workspaceRoot ? [] : [(0, node_path.join)(appRoot, ".gkm", "manifest")]].map((dir) => (0, node_fs_promises.rm)(dir, {
@@ -3421,7 +3524,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3421
3524
  let masterKey;
3422
3525
  if (context.production?.bundle && !skipBundle) {
3423
3526
  logger$4.log(`\n📦 Bundling production server...`);
3424
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-YNvLdvlh.cjs"));
3527
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-DjrRffko.cjs"));
3425
3528
  const allConstructs = [
3426
3529
  ...endpoints.map((e) => e.construct),
3427
3530
  ...functions.map((f) => f.construct),
@@ -5108,12 +5211,12 @@ const PNPM_VERSION = "pnpm@10.13.1";
5108
5211
  const BIOME_SCHEMA = `https://biomejs.dev/schemas/${DEPENDENCY_VERSIONS["@biomejs/biome"].replace(/^[~^]/, "")}/schema.json`;
5109
5212
  //#endregion
5110
5213
  //#region src/init/versions.ts
5111
- const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
5214
+ const require$2 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
5112
5215
  function loadPackageJson() {
5113
5216
  try {
5114
- return require$1("../package.json");
5217
+ return require$2("../package.json");
5115
5218
  } catch {
5116
- return require$1("../../package.json");
5219
+ return require$2("../../package.json");
5117
5220
  }
5118
5221
  }
5119
5222
  /**
@@ -5123,25 +5226,25 @@ function loadPackageJson() {
5123
5226
  * Run: pnpm --filter @geekmidas/cli sync-versions
5124
5227
  */
5125
5228
  const GEEKMIDAS_VERSIONS = {
5126
- "@geekmidas/audit": "~10.0.0-alpha.82",
5127
- "@geekmidas/auth": "~10.0.0-alpha.82",
5128
- "@geekmidas/cache": "~10.0.0-alpha.82",
5129
- "@geekmidas/client": "~10.0.0-alpha.82",
5130
- "@geekmidas/cloud": "~10.0.0-alpha.82",
5131
- "@geekmidas/constructs": "~10.0.0-alpha.82",
5132
- "@geekmidas/db": "~10.0.0-alpha.82",
5133
- "@geekmidas/emailkit": "~10.0.0-alpha.82",
5134
- "@geekmidas/envkit": "~10.0.0-alpha.82",
5135
- "@geekmidas/errors": "~10.0.0-alpha.82",
5136
- "@geekmidas/events": "~10.0.0-alpha.82",
5137
- "@geekmidas/logger": "~10.0.0-alpha.82",
5138
- "@geekmidas/manifest": "~10.0.0-alpha.82",
5139
- "@geekmidas/rate-limit": "~10.0.0-alpha.82",
5140
- "@geekmidas/schema": "~10.0.0-alpha.82",
5141
- "@geekmidas/services": "~10.0.0-alpha.82",
5142
- "@geekmidas/storage": "~10.0.0-alpha.82",
5143
- "@geekmidas/telescope": "~10.0.0-alpha.82",
5144
- "@geekmidas/testkit": "~10.0.0-alpha.82",
5229
+ "@geekmidas/audit": "~10.0.0-alpha.84",
5230
+ "@geekmidas/auth": "~10.0.0-alpha.84",
5231
+ "@geekmidas/cache": "~10.0.0-alpha.84",
5232
+ "@geekmidas/client": "~10.0.0-alpha.84",
5233
+ "@geekmidas/cloud": "~10.0.0-alpha.84",
5234
+ "@geekmidas/constructs": "~10.0.0-alpha.84",
5235
+ "@geekmidas/db": "~10.0.0-alpha.84",
5236
+ "@geekmidas/emailkit": "~10.0.0-alpha.84",
5237
+ "@geekmidas/envkit": "~10.0.0-alpha.84",
5238
+ "@geekmidas/errors": "~10.0.0-alpha.84",
5239
+ "@geekmidas/events": "~10.0.0-alpha.84",
5240
+ "@geekmidas/logger": "~10.0.0-alpha.84",
5241
+ "@geekmidas/manifest": "~10.0.0-alpha.84",
5242
+ "@geekmidas/rate-limit": "~10.0.0-alpha.84",
5243
+ "@geekmidas/schema": "~10.0.0-alpha.84",
5244
+ "@geekmidas/services": "~10.0.0-alpha.84",
5245
+ "@geekmidas/storage": "~10.0.0-alpha.84",
5246
+ "@geekmidas/telescope": "~10.0.0-alpha.84",
5247
+ "@geekmidas/testkit": "~10.0.0-alpha.84",
5145
5248
  "@geekmidas/cli": `~${loadPackageJson().version}`
5146
5249
  };
5147
5250
  //#endregion
@@ -5514,6 +5617,38 @@ coverage/
5514
5617
  return files;
5515
5618
  }
5516
5619
  //#endregion
5620
+ //#region src/init/stagesAction.ts
5621
+ /**
5622
+ * How a scaffolded workflow names the `stages` action.
5623
+ *
5624
+ * A published CLI pins it to the commit it was released from: the release
5625
+ * build is given that commit as `GKM_RELEASE_COMMIT`, which the build writes
5626
+ * into the bundle (`define` in tsdown.config.ts). The action at that commit is
5627
+ * the one released beside this CLI, and a commit SHA cannot be moved under a
5628
+ * project the way a branch or a tag can.
5629
+ *
5630
+ * A CLI built anywhere else — a checkout, a local build — knows no released
5631
+ * commit, and names `main`.
5632
+ */
5633
+ const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
5634
+ function cliVersion() {
5635
+ try {
5636
+ return require$1("../package.json").version;
5637
+ } catch {
5638
+ return require$1("../../package.json").version;
5639
+ }
5640
+ }
5641
+ /** The action, as a workflow's `uses:` names it before the `@`. */
5642
+ const STAGES_ACTION = "geekmidas/toolbox/actions/stages";
5643
+ const COMMIT = /^[0-9a-f]{40}$/;
5644
+ /**
5645
+ * The `uses:` value for the stages action, with a comment saying which CLI
5646
+ * release the pinned commit is.
5647
+ */
5648
+ function stagesActionUses(commit = "1a6631b7e6dc71b7dd7b7afb8ca5b82225454826", version = cliVersion()) {
5649
+ return commit && COMMIT.test(commit) ? `${STAGES_ACTION}@${commit} # @geekmidas/cli ${version}` : `${STAGES_ACTION}@main # pin to a commit SHA: see the action's README`;
5650
+ }
5651
+ //#endregion
5517
5652
  //#region src/init/generators/github.ts
5518
5653
  /**
5519
5654
  * The GitHub workflows a scaffold ships with.
@@ -5523,11 +5658,14 @@ coverage/
5523
5658
  * release, labelled from their titles.
5524
5659
  * - `deploy.yml` — when a deploy target was picked: a push to main deploys the
5525
5660
  * stages that are not protected, publishing the drafted release deploys the
5526
- * protected ones.
5661
+ * protected ones, and a manual run deploys the one stage it names.
5527
5662
  *
5528
- * The deploy workflow names no stage. It reads `stages` from gkm.config.ts at
5529
- * run time, so the config stays the one place stages are declared and a
5530
- * workflow cannot fall out of step with it.
5663
+ * The deploy workflow names no stage. Its first job runs the
5664
+ * `geekmidas/toolbox/actions/stages` action, which asks the project's own
5665
+ * `gkm stages --github-output` which stages the run builds and deploys — so
5666
+ * the config stays the one place stages are declared, the rules for each event
5667
+ * are tested code in the CLI, and a workflow cannot fall out of step with
5668
+ * either.
5531
5669
  */
5532
5670
  /** How each package manager is set up, installs, and runs a script. */
5533
5671
  const TOOLING = {
@@ -5713,46 +5851,21 @@ template: |
5713
5851
  **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
5714
5852
  `;
5715
5853
  }
5716
- function deploy(options) {
5717
- const pm = tooling(options);
5718
- const sst = options.deployTarget === "sst";
5719
- const credentials = sst ? `
5720
- # An IAM role GitHub assumes through OIDC — no long-lived AWS keys. Set
5721
- # AWS_ROLE_ARN as a variable on each environment.
5722
- - uses: aws-actions/configure-aws-credentials@v4
5723
- with:
5724
- role-to-assume: \${{ vars.AWS_ROLE_ARN }}
5725
- aws-region: ${options.region}
5726
- ` : "";
5727
- const keyDir = `~/.gkm/keys/${require_seeds.projectKey({ name: options.name })}`;
5728
- const stageSecrets = sst ? credentials : `
5729
- # The stage's secrets are kept in the encrypted .gkm/secrets/<stage>.json,
5730
- # and each environment holds its key as GKM_SECRETS_KEY. .gkm/ is
5731
- # gitignored, so a checkout has the key but not the file: set
5732
- # secrets.store in gkm.config.ts to a store CI can reach before deploying
5733
- # from here.
5734
- # Owner-only, as the CLI writes its own keys. The stage reaches the shell
5735
- # as a variable, never pasted into the script: on a manual run it is
5736
- # whatever was typed.
5737
- - name: Stage secrets key
5738
- run: |
5739
- mkdir -p -m 700 ${keyDir}
5740
- (umask 077 && printf '%s' "$KEY" > ${keyDir}/"$STAGE".key)
5741
- env:
5742
- KEY: \${{ secrets.GKM_SECRETS_KEY }}
5743
- STAGE: \${{ matrix.stage }}
5744
- `;
5745
- const deployEnv = sst ? "" : `
5746
- DOKPLOY_API_TOKEN: \${{ secrets.DOKPLOY_API_TOKEN }}
5747
- DOKPLOY_ENDPOINT: \${{ vars.DOKPLOY_ENDPOINT }}`;
5854
+ /**
5855
+ * What every deploy workflow starts with: its triggers, and the `stages` job,
5856
+ * which asks the project's own gkm (through the stages action) which stages
5857
+ * this run builds and deploys. Nothing in a workflow names a stage.
5858
+ */
5859
+ function deployHeader(build) {
5748
5860
  return `name: Deploy
5749
5861
 
5750
5862
  # Which stages exist, and which are protected, is read from \`stages\` in
5751
- # gkm.config.ts — nothing here names one:
5863
+ # gkm.config.ts by the project's own gkm, through the stages action — nothing
5864
+ # here names one:
5752
5865
  #
5753
- # push to main every deployed stage that is not protected
5866
+ # push to main ${build ? "builds every deployed stage, deploys those not protected" : "every deployed stage that is not protected"}
5754
5867
  # release published the protected stages (publish the drafted release)
5755
- # run workflow the one stage you name
5868
+ # run workflow the one stage you name, at the ref you name
5756
5869
  on:
5757
5870
  push:
5758
5871
  branches: [main]
@@ -5763,17 +5876,27 @@ on:
5763
5876
  stage:
5764
5877
  description: A deployed stage from gkm.config.ts
5765
5878
  required: true
5879
+ type: string
5880
+ ref:
5881
+ description: The commit, tag or branch to deploy (default the latest on main)
5882
+ required: false
5883
+ type: string
5766
5884
 
5767
5885
  permissions:
5768
5886
  contents: read
5769
- id-token: write
5770
-
5771
- jobs:
5772
- stages:
5887
+ `;
5888
+ }
5889
+ function stagesJob(options) {
5890
+ const pm = tooling(options);
5891
+ return ` stages:
5773
5892
  name: Pick stages
5774
5893
  runs-on: ubuntu-latest
5775
5894
  outputs:
5776
- stages: \${{ steps.pick.outputs.stages }}
5895
+ build: \${{ steps.stages.outputs.build }}
5896
+ deploy: \${{ steps.stages.outputs.deploy }}
5897
+ has-build: \${{ steps.stages.outputs.has-build }}
5898
+ has-deploy: \${{ steps.stages.outputs.has-deploy }}
5899
+ aws-region: \${{ steps.stages.outputs.aws-region }}
5777
5900
  steps:
5778
5901
  - uses: actions/checkout@v4
5779
5902
 
@@ -5781,44 +5904,286 @@ ${pm.setup}
5781
5904
  - name: Install
5782
5905
  run: ${pm.install}
5783
5906
 
5907
+ # Runs \`gkm stages --github-output\`: the rules for each event are the
5908
+ # installed CLI's, and a manual run naming a stage gkm.config.ts does not
5909
+ # deploy fails here.
5784
5910
  - name: Read stages from gkm.config.ts
5785
- id: pick
5911
+ id: stages
5912
+ uses: ${stagesActionUses()}
5913
+ with:
5914
+ stage: \${{ inputs.stage }}
5915
+ `;
5916
+ }
5917
+ /**
5918
+ * The registry login a compose build pushes with: GitHub's own token on
5919
+ * ghcr.io, otherwise a username variable and a password secret.
5920
+ */
5921
+ function registryLogin(registry) {
5922
+ const host = registry.split("/")[0];
5923
+ const ghcr = host === "ghcr.io";
5924
+ return ` - uses: docker/login-action@v3
5925
+ with:
5926
+ registry: ${host}
5927
+ username: ${ghcr ? "${{ github.actor }}" : "${{ vars.REGISTRY_USERNAME }}"}
5928
+ password: ${ghcr ? "${{ secrets.GITHUB_TOKEN }}" : "${{ secrets.REGISTRY_PASSWORD }}"}
5929
+ `;
5930
+ }
5931
+ /**
5932
+ * The compose deploy: images are built and pushed by the runner, where the
5933
+ * code is, and pulled by the server, where the stage runs.
5934
+ *
5935
+ * - `build` (on a push): every deployed stage's images, tagged with the
5936
+ * commit, and their digests kept as the artifact `digests-<stage>`.
5937
+ * - `deploy`: the commit the event names — a release's tag, a manual run's
5938
+ * `ref` — then that commit's push build, whose digests pin each image. Over
5939
+ * SSH, with a pinned host key, the server checks the commit out and runs
5940
+ * `gkm compose` on exactly those images.
5941
+ */
5942
+ function composeDeploy(options) {
5943
+ const pm = tooling(options);
5944
+ const registry = options.registry ?? "";
5945
+ return `${deployHeader(true)}
5946
+ jobs:
5947
+ ${stagesJob(options)}
5948
+ build:
5949
+ name: Build \${{ matrix.stage }}
5950
+ needs: stages
5951
+ if: needs.stages.outputs.has-build == 'true'
5952
+ runs-on: ubuntu-latest
5953
+ strategy:
5954
+ fail-fast: false
5955
+ matrix:
5956
+ stage: \${{ fromJSON(needs.stages.outputs.build) }}
5957
+ # One GitHub environment per stage: its secrets, variables and approvals.
5958
+ environment: \${{ matrix.stage }}
5959
+ permissions:
5960
+ contents: read
5961
+ packages: write
5962
+ id-token: write
5963
+ steps:
5964
+ - uses: actions/checkout@v4
5965
+
5966
+ ${pm.setup}
5967
+ - name: Install
5968
+ run: ${pm.install}
5969
+
5970
+ ${registryLogin(registry)}
5971
+ # A site's public URLs are built into it, so the build reads the stage's
5972
+ # secrets store. With secrets.store on SSM or Secrets Manager the job
5973
+ # assumes the stage's role (AWS_ROLE_ARN, set by gkm deploy:github);
5974
+ # with the default 'file' store there is no role to assume.
5975
+ - name: Assume the stage's AWS role
5976
+ if: needs.stages.outputs.aws-region != ''
5977
+ uses: aws-actions/configure-aws-credentials@v4
5978
+ with:
5979
+ role-to-assume: \${{ vars.AWS_ROLE_ARN }}
5980
+ aws-region: \${{ needs.stages.outputs.aws-region }}
5981
+
5982
+ # Builds and pushes every image the stage runs, tagged with the commit,
5983
+ # and starts nothing.
5984
+ - name: Build and push
5985
+ run: ${pm.exec} gkm compose --stage "$STAGE" --build --push --tag "$SHA" --digests-file digests.json
5986
+ env:
5987
+ STAGE: \${{ matrix.stage }}
5988
+ SHA: \${{ github.sha }}
5989
+
5990
+ # What each tag pointed at when it was pushed. A deploy runs these
5991
+ # digests, so a tag moved since cannot change what is released.
5992
+ - uses: actions/upload-artifact@v4
5993
+ with:
5994
+ name: digests-\${{ matrix.stage }}
5995
+ path: digests.json
5996
+ retention-days: 90
5997
+
5998
+ deploy:
5999
+ name: Deploy \${{ matrix.stage }}
6000
+ needs: [stages, build]
6001
+ # The build is skipped on a release or a manual run: deploy then anyway,
6002
+ # but never after a failed build.
6003
+ if: \${{ !cancelled() && !failure() && needs.stages.outputs.has-deploy == 'true' }}
6004
+ runs-on: ubuntu-latest
6005
+ strategy:
6006
+ fail-fast: false
6007
+ matrix:
6008
+ stage: \${{ fromJSON(needs.stages.outputs.deploy) }}
6009
+ environment: \${{ matrix.stage }}
6010
+ concurrency:
6011
+ group: deploy-\${{ matrix.stage }}
6012
+ cancel-in-progress: false
6013
+ permissions:
6014
+ contents: read
6015
+ actions: read
6016
+ steps:
6017
+ # A release deploys its tag's commit, never its target_commitish — the
6018
+ # branch it was drafted against, which has moved on since.
6019
+ - name: Resolve the commit
6020
+ id: commit
6021
+ run: |
6022
+ case "$EVENT" in
6023
+ release) ref="tags/$TAG" ;;
6024
+ workflow_dispatch) ref="\${REF:-$SHA}" ;;
6025
+ *) ref="$SHA" ;;
6026
+ esac
6027
+ sha=$(gh api "repos/$REPO/commits/$ref" --jq .sha)
6028
+ echo "Deploying $sha"
6029
+ echo "sha=$sha" >> "$GITHUB_OUTPUT"
6030
+ env:
6031
+ GH_TOKEN: \${{ github.token }}
6032
+ REPO: \${{ github.repository }}
6033
+ EVENT: \${{ github.event_name }}
6034
+ TAG: \${{ github.event.release.tag_name }}
6035
+ REF: \${{ inputs.ref }}
6036
+ SHA: \${{ github.sha }}
6037
+
6038
+ # The push run of this workflow that built the commit for this stage:
6039
+ # this run on a push, the newest one with the stage's digests otherwise.
6040
+ - name: Find the commit's push build
6041
+ id: build
5786
6042
  run: |
5787
- ${pm.exec} tsx -e "
5788
- import config from './gkm.config.ts';
5789
- const { deployed, protected: kept = [] } = config.stages;
5790
- const event = process.env.EVENT;
5791
- if (event === 'workflow_dispatch' && !deployed.includes(process.env.STAGE)) {
5792
- console.error('Not a deployed stage: ' + process.env.STAGE + ' (deployed: ' + deployed.join(', ') + ')');
5793
- process.exit(1);
5794
- }
5795
- const stages =
5796
- event === 'workflow_dispatch' ? [process.env.STAGE]
5797
- : event === 'release' ? kept
5798
- : deployed.filter((stage) => !kept.includes(stage));
5799
- console.log('stages=' + JSON.stringify(stages));
5800
- " >> "$GITHUB_OUTPUT"
6043
+ if [ "$EVENT" = push ]; then
6044
+ echo "run-id=$RUN_ID" >> "$GITHUB_OUTPUT"
6045
+ exit 0
6046
+ fi
6047
+ workflow=\${WORKFLOW_REF%%@*}
6048
+ workflow=\${workflow##*/}
6049
+ for id in $(gh run list --repo "$REPO" --workflow "$workflow" --event push --commit "$SHA" --limit 20 --json databaseId --jq '.[].databaseId'); do
6050
+ found=$(gh api "repos/$REPO/actions/runs/$id/artifacts?name=$ARTIFACT" --jq '[.artifacts[] | select(.expired | not)] | length')
6051
+ if [ "$found" -gt 0 ]; then
6052
+ echo "run-id=$id" >> "$GITHUB_OUTPUT"
6053
+ exit 0
6054
+ fi
6055
+ done
6056
+ echo "run-id=" >> "$GITHUB_OUTPUT"
5801
6057
  env:
6058
+ GH_TOKEN: \${{ github.token }}
6059
+ REPO: \${{ github.repository }}
5802
6060
  EVENT: \${{ github.event_name }}
5803
- STAGE: \${{ inputs.stage }}
6061
+ RUN_ID: \${{ github.run_id }}
6062
+ WORKFLOW_REF: \${{ github.workflow_ref }}
6063
+ SHA: \${{ steps.commit.outputs.sha }}
6064
+ ARTIFACT: digests-\${{ matrix.stage }}
6065
+
6066
+ - name: Download digests-\${{ matrix.stage }}
6067
+ if: steps.build.outputs.run-id != ''
6068
+ uses: actions/download-artifact@v4
6069
+ with:
6070
+ name: digests-\${{ matrix.stage }}
6071
+ run-id: \${{ steps.build.outputs.run-id }}
6072
+ github-token: \${{ github.token }}
5804
6073
 
6074
+ - name: No digests — deploying by tag
6075
+ if: steps.build.outputs.run-id == ''
6076
+ run: |
6077
+ echo "::warning title=$STAGE deploys by tag, not digest::No push build of $SHA kept digests-$STAGE (never built, or older than 90 days). The server runs whatever the tag $SHA points at in the registry now, which anyone who can push there could have replaced."
6078
+ env:
6079
+ STAGE: \${{ matrix.stage }}
6080
+ SHA: \${{ steps.commit.outputs.sha }}
6081
+
6082
+ # Each environment holds DEPLOY_SSH_KEY (secret) and DEPLOY_KNOWN_HOSTS,
6083
+ # DEPLOY_HOST, DEPLOY_USER and DEPLOY_PATH (variables): the server's
6084
+ # host key is pinned, never accepted on first sight. DEPLOY_PATH is a
6085
+ # clone of this repository, with a docker login to ${registry.split("/")[0]}
6086
+ # and the stage's secrets.
6087
+ #
6088
+ # Every value reaches the server as an argument to a quoted heredoc —
6089
+ # nothing is pasted into the script.
6090
+ - name: Deploy on the server
6091
+ run: |
6092
+ mkdir -p ~/.ssh && chmod 700 ~/.ssh
6093
+ (umask 077 && printf '%s\\n' "$SSH_KEY" > ~/.ssh/deploy_key)
6094
+ printf '%s\\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts
6095
+ digests=""
6096
+ if [ -f digests.json ]; then digests=$(base64 -w0 < digests.json); fi
6097
+ ssh -i ~/.ssh/deploy_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes \\
6098
+ "$DEPLOY_USER@$DEPLOY_HOST" \\
6099
+ "bash -s -- $(printf '%q ' "$STAGE" "$SHA" "$DEPLOY_PATH" "$digests")" <<'REMOTE'
6100
+ set -euo pipefail
6101
+ stage=$1 sha=$2 dir=$3 digests=$4
6102
+ cd "$dir"
6103
+ git fetch --quiet origin
6104
+ git checkout --quiet --detach "$sha"
6105
+ ${pm.install}
6106
+ args=(--stage "$stage" --tag "$sha")
6107
+ if [ -n "$digests" ]; then
6108
+ mkdir -p .gkm
6109
+ printf '%s' "$digests" | base64 -d > ".gkm/digests-$stage.json"
6110
+ args+=(--digests-file ".gkm/digests-$stage.json")
6111
+ fi
6112
+ ${pm.exec} gkm compose "\${args[@]}"
6113
+ REMOTE
6114
+ env:
6115
+ SSH_KEY: \${{ secrets.DEPLOY_SSH_KEY }}
6116
+ KNOWN_HOSTS: \${{ vars.DEPLOY_KNOWN_HOSTS }}
6117
+ DEPLOY_HOST: \${{ vars.DEPLOY_HOST }}
6118
+ DEPLOY_USER: \${{ vars.DEPLOY_USER }}
6119
+ DEPLOY_PATH: \${{ vars.DEPLOY_PATH }}
6120
+ STAGE: \${{ matrix.stage }}
6121
+ SHA: \${{ steps.commit.outputs.sha }}
6122
+ `;
6123
+ }
6124
+ /**
6125
+ * Dokploy and SST: `gkm deploy` builds, releases and health-checks a stage in
6126
+ * one step, from a checkout, so there is no separate build job — the stages
6127
+ * job's `deploy` list is the matrix, and `build` goes unused.
6128
+ */
6129
+ function targetDeploy(options) {
6130
+ const pm = tooling(options);
6131
+ const sst = options.deployTarget === "sst";
6132
+ const credentials = sst ? `
6133
+ # An IAM role GitHub assumes through OIDC — no long-lived AWS keys. Set
6134
+ # AWS_ROLE_ARN as a variable on each environment.
6135
+ - uses: aws-actions/configure-aws-credentials@v4
6136
+ with:
6137
+ role-to-assume: \${{ vars.AWS_ROLE_ARN }}
6138
+ aws-region: ${options.region}
6139
+ ` : "";
6140
+ const keyDir = `~/.gkm/keys/${require_seeds.projectKey({ name: options.name })}`;
6141
+ const stageSecrets = sst ? credentials : `
6142
+ # The stage's secrets are kept in the encrypted .gkm/secrets/<stage>.json,
6143
+ # and each environment holds its key as GKM_SECRETS_KEY. .gkm/ is
6144
+ # gitignored, so a checkout has the key but not the file: set
6145
+ # secrets.store in gkm.config.ts to a store CI can reach before deploying
6146
+ # from here.
6147
+ # Owner-only, as the CLI writes its own keys. The stage reaches the shell
6148
+ # as a variable, never pasted into the script: on a manual run it is
6149
+ # whatever was typed.
6150
+ - name: Stage secrets key
6151
+ run: |
6152
+ mkdir -p ${keyDir} && chmod 700 ${keyDir}
6153
+ (umask 077 && printf '%s' "$KEY" > ${keyDir}/"$STAGE".key)
6154
+ env:
6155
+ KEY: \${{ secrets.GKM_SECRETS_KEY }}
6156
+ STAGE: \${{ matrix.stage }}
6157
+ `;
6158
+ const deployEnv = sst ? "" : `
6159
+ DOKPLOY_API_TOKEN: \${{ secrets.DOKPLOY_API_TOKEN }}
6160
+ DOKPLOY_ENDPOINT: \${{ vars.DOKPLOY_ENDPOINT }}`;
6161
+ return `${deployHeader(false)}
6162
+ jobs:
6163
+ ${stagesJob(options)}
5805
6164
  deploy:
5806
6165
  name: Deploy \${{ matrix.stage }}
5807
6166
  needs: stages
5808
- if: needs.stages.outputs.stages != '[]'
6167
+ if: needs.stages.outputs.has-deploy == 'true'
5809
6168
  runs-on: ubuntu-latest
5810
6169
  strategy:
5811
6170
  fail-fast: false
5812
6171
  matrix:
5813
- stage: \${{ fromJSON(needs.stages.outputs.stages) }}
6172
+ stage: \${{ fromJSON(needs.stages.outputs.deploy) }}
5814
6173
  # One GitHub environment per stage: its secrets and variables, and any
5815
6174
  # approval rule you put on a protected one.
5816
6175
  environment: \${{ matrix.stage }}
5817
6176
  concurrency:
5818
6177
  group: deploy-\${{ matrix.stage }}
5819
6178
  cancel-in-progress: false
6179
+ permissions:
6180
+ contents: read${sst ? "\n id-token: write" : ""}
5820
6181
  steps:
6182
+ # The event's own commit — a release's tag, the push — or the ref a
6183
+ # manual run names.
5821
6184
  - uses: actions/checkout@v4
6185
+ with:
6186
+ ref: \${{ inputs.ref }}
5822
6187
 
5823
6188
  ${pm.setup}
5824
6189
  - name: Install
@@ -5832,6 +6197,9 @@ ${stageSecrets}
5832
6197
  STAGE: \${{ matrix.stage }}${deployEnv}
5833
6198
  `;
5834
6199
  }
6200
+ function deploy(options) {
6201
+ return options.deployTarget === "compose" ? composeDeploy(options) : targetDeploy(options);
6202
+ }
5835
6203
  function generateGithubFiles(options) {
5836
6204
  const files = [
5837
6205
  {
@@ -6672,6 +7040,11 @@ export type ListUsersResponse = z.infer<typeof ListUsersResponseSchema>;
6672
7040
  const perStage = (options, command) => Object.fromEntries(options.stages.deployed.map((stage) => [`deploy:${stage}`, command(stage)]));
6673
7041
  function deployPackage(options) {
6674
7042
  switch (options.deployTarget) {
7043
+ case "compose": return {
7044
+ scripts: perStage(options, (stage) => `gkm compose --stage ${stage}`),
7045
+ dependencies: {},
7046
+ devDependencies: {}
7047
+ };
6675
7048
  case "dokploy": return {
6676
7049
  scripts: perStage(options, (stage) => `gkm deploy --stage ${stage}`),
6677
7050
  dependencies: {},
@@ -7181,6 +7554,13 @@ ${workspaceConstructsGlobs(options.routesStructure, (0, node_path.dirname)(optio
7181
7554
  * `gkm build` builds for, and which backends a cache or a broker resolves to.
7182
7555
  */
7183
7556
  function deployBlock(options) {
7557
+ if (options.deployTarget === "compose") return `
7558
+ // \`gkm compose --stage <stage>\` runs a stage as one Docker Compose stack on
7559
+ // the machine it runs on. CI builds every image and pushes it here; the
7560
+ // server pulls exactly those. On ghcr.io the owner is the repository's —
7561
+ // the user or organisation whose packages the workflow's token can write.
7562
+ deploy: { default: 'compose', registry: '${options.registry}' },
7563
+ `;
7184
7564
  if (options.deployTarget !== "sst") return "";
7185
7565
  return `
7186
7566
  // \`gkm deploy\` runs \`sst deploy\` on the manifest \`gkm build\` writes to
@@ -9757,6 +10137,11 @@ const deployTargetChoices = [
9757
10137
  value: "dokploy",
9758
10138
  description: "Deploy to Dokploy (Docker-based hosting)"
9759
10139
  },
10140
+ {
10141
+ title: "Server (Docker Compose)",
10142
+ value: "compose",
10143
+ description: "One Docker Compose stack per stage on your own server; CI builds the images"
10144
+ },
9760
10145
  {
9761
10146
  title: "AWS (SST)",
9762
10147
  value: "sst",
@@ -9894,6 +10279,13 @@ async function initCommand(projectName, options = {}) {
9894
10279
  message: "AWS region (e.g. eu-west-1):",
9895
10280
  validate: (value) => AWS_REGION.test(value.trim()) || "An AWS region, like eu-west-1"
9896
10281
  },
10282
+ {
10283
+ type: (_prev, values) => !options.yes && !options.registry && (options.deploy ?? values.deployTarget) === "compose" ? "text" : null,
10284
+ name: "registry",
10285
+ message: "Container registry CI pushes to (e.g. ghcr.io/<your GitHub owner>):",
10286
+ initial: (_prev, values) => defaultRegistry(projectName || options.name || values.name),
10287
+ validate: (value) => value.trim().length > 0 || "A registry, like ghcr.io/acme"
10288
+ },
9897
10289
  {
9898
10290
  type: options.yes || options.stages ? null : "text",
9899
10291
  name: "deployedStages",
@@ -9995,6 +10387,7 @@ async function initCommand(projectName, options = {}) {
9995
10387
  packageManager: pkgManager,
9996
10388
  deployTarget,
9997
10389
  stages: resolveStages(options, answers),
10390
+ ...deployTarget === "compose" ? { registry: options.registry ?? answers.registry?.trim() ?? defaultRegistry(name) } : {},
9998
10391
  ...deployTarget === "sst" ? { region: options.region ?? (options.yes ? "eu-west-1" : answers.region?.trim()) } : {},
9999
10392
  constructs,
10000
10393
  frontendFramework
@@ -10166,6 +10559,13 @@ function printNextSteps(projectName, options, pkgManager) {
10166
10559
  for (const stage of deployed) console.log(` ${require_utils.getRunCommand(pkgManager, `deploy:${stage}`)}`);
10167
10560
  console.log("");
10168
10561
  }
10562
+ if (options.deployTarget === "compose") {
10563
+ console.log("🚀 Deployment (Docker Compose, on your server):");
10564
+ console.log(` Images are pushed to ${options.registry}; .github/workflows/deploy.yml builds them`);
10565
+ console.log(" and runs gkm compose on the server over SSH. See the comments in it for the");
10566
+ console.log(" environment variables and secrets each stage needs.");
10567
+ console.log("");
10568
+ }
10169
10569
  if (options.deployTarget === "sst") {
10170
10570
  console.log("🚀 Deployment (AWS, through SST):");
10171
10571
  for (const stage of deployed) console.log(` ${require_utils.getRunCommand(pkgManager, `deploy:${stage}`)} # gkm deploy: build, sst deploy, health checks`);
@@ -10205,6 +10605,14 @@ var UnknownDeployTarget = class extends Error {
10205
10605
  this.name = "UnknownDeployTarget";
10206
10606
  }
10207
10607
  };
10608
+ /**
10609
+ * Where a compose deploy's images go when nobody said: GitHub's registry,
10610
+ * under an owner named after the project. The generated config says to change
10611
+ * it to the repository's owner, whose packages the workflow's token can write.
10612
+ */
10613
+ function defaultRegistry(name) {
10614
+ return `ghcr.io/${(name ?? "my-org").toLowerCase()}`;
10615
+ }
10208
10616
  /** `--region` that is not shaped like one, e.g. `europe` for `eu-west-1`. */
10209
10617
  var NotAnAwsRegion = class extends Error {
10210
10618
  region;
@@ -10909,7 +11317,7 @@ function testDatabases(reconciled) {
10909
11317
  * starts without the reference data the app needs.
10910
11318
  */
10911
11319
  async function migrateTestStage(workspace, env) {
10912
- const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-DIjG8-QR.cjs"));
11320
+ const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-BfZN1S32.cjs"));
10913
11321
  const { discover } = await Promise.resolve().then(() => require("./discover-D1It5r_h.cjs")).then((n) => n.discover_exports);
10914
11322
  const sources = {};
10915
11323
  const manifest = await discover({
@@ -11245,7 +11653,7 @@ const program = new commander.Command();
11245
11653
  program.name("gkm").description("GeekMidas backend framework CLI").version(version).option("--cwd <path>", "Change working directory").option("--debug", "Enable debug mode (verbose errors with stack traces)").hook("preAction", () => {
11246
11654
  if (program.opts().debug);
11247
11655
  });
11248
- program.command("init").description("Scaffold a new project").argument("[name]", "Project name").option("--template <template>", "Project template (minimal, api, serverless, worker)").option("--skip-install", "Skip dependency installation", false).option("-y, --yes", "Skip prompts, use defaults", false).option("--monorepo", "Setup as monorepo with packages/models", false).option("--api-path <path>", "API app path in monorepo (default: apps/api)").option("--pm <manager>", "Package manager (pnpm, npm, yarn, bun)").option("--deploy <target>", "Where it deploys (dokploy, sst, none)").option("--region <region>", "AWS region for an SST deploy (e.g. eu-west-1)").option("--stages <names>", "Deployed stages, comma-separated (e.g. staging,prod)").option("--protected-stage <name>", "Which deployed stage is production").option("--local-stage <name>", "What gkm dev runs as (e.g. dev)").action(async (name, options) => {
11656
+ program.command("init").description("Scaffold a new project").argument("[name]", "Project name").option("--template <template>", "Project template (minimal, api, serverless, worker)").option("--skip-install", "Skip dependency installation", false).option("-y, --yes", "Skip prompts, use defaults", false).option("--monorepo", "Setup as monorepo with packages/models", false).option("--api-path <path>", "API app path in monorepo (default: apps/api)").option("--pm <manager>", "Package manager (pnpm, npm, yarn, bun)").option("--deploy <target>", "Where it deploys (dokploy, compose, sst, none)").option("--region <region>", "AWS region for an SST deploy (e.g. eu-west-1)").option("--registry <registry>", "Container registry for a compose deploy (e.g. ghcr.io/acme)").option("--stages <names>", "Deployed stages, comma-separated (e.g. staging,prod)").option("--protected-stage <name>", "Which deployed stage is production").option("--local-stage <name>", "What gkm dev runs as (e.g. dev)").action(async (name, options) => {
11249
11657
  try {
11250
11658
  const globalOptions = program.opts();
11251
11659
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
@@ -11353,7 +11761,7 @@ program.command("migrate").description("Apply each database construct's migratio
11353
11761
  try {
11354
11762
  const globalOptions = program.opts();
11355
11763
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11356
- const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-DIjG8-QR.cjs"));
11764
+ const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-BfZN1S32.cjs"));
11357
11765
  await migrateCommand({
11358
11766
  ...options,
11359
11767
  ...construct ? { construct } : {}
@@ -11367,7 +11775,7 @@ program.command("seed").description("Migrate, then run each database construct's
11367
11775
  try {
11368
11776
  const globalOptions = program.opts();
11369
11777
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11370
- const { seedCommand } = await Promise.resolve().then(() => require("./migrate-DIjG8-QR.cjs"));
11778
+ const { seedCommand } = await Promise.resolve().then(() => require("./migrate-BfZN1S32.cjs"));
11371
11779
  await seedCommand({
11372
11780
  ...options,
11373
11781
  ...construct ? { construct } : {}
@@ -11381,7 +11789,7 @@ program.command("migration").description("Write a construct's next migration: an
11381
11789
  try {
11382
11790
  const globalOptions = program.opts();
11383
11791
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11384
- const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-DIjG8-QR.cjs"));
11792
+ const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-BfZN1S32.cjs"));
11385
11793
  await migrationCommand({
11386
11794
  construct,
11387
11795
  ...name ? { name } : {}
@@ -11489,7 +11897,7 @@ program.command("secrets:add").description("Build the keys a stage must be given
11489
11897
  try {
11490
11898
  const globalOptions = program.opts();
11491
11899
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11492
- const { secretsAddCommand } = await Promise.resolve().then(() => require("./add-1T3y6E4M.cjs"));
11900
+ const { secretsAddCommand } = await Promise.resolve().then(() => require("./add-BQ8v1ZII.cjs"));
11493
11901
  await secretsAddCommand(options);
11494
11902
  } catch (error) {
11495
11903
  console.error(formatError(error));
@@ -11500,7 +11908,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
11500
11908
  try {
11501
11909
  const globalOptions = program.opts();
11502
11910
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11503
- const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-DLin89I_.cjs"));
11911
+ const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-g6PzwwfL.cjs"));
11504
11912
  await encryptionRotateCommand(construct, options);
11505
11913
  } catch (error) {
11506
11914
  console.error(formatError(error));
@@ -11511,7 +11919,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
11511
11919
  try {
11512
11920
  const globalOptions = program.opts();
11513
11921
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11514
- const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-DLin89I_.cjs"));
11922
+ const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-g6PzwwfL.cjs"));
11515
11923
  await encryptionRetireCommand(construct, key, options);
11516
11924
  } catch (error) {
11517
11925
  console.error(formatError(error));
@@ -11554,7 +11962,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
11554
11962
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11555
11963
  const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
11556
11964
  const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-CuCQBa5-.cjs"));
11557
- const { secretsStoreFor } = await Promise.resolve().then(() => require("./seeds-BLzrSwEr.cjs")).then((n) => n.store_exports);
11965
+ const { secretsStoreFor } = await Promise.resolve().then(() => require("./seeds-DQ0FG57Z.cjs")).then((n) => n.store_exports);
11558
11966
  const { workspace } = await loadWorkspaceConfig();
11559
11967
  const stage = options.stage ?? workspace.stages.local;
11560
11968
  const store = await secretsStoreFor(workspace, stage);
@@ -11563,7 +11971,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
11563
11971
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
11564
11972
  process.exit(1);
11565
11973
  }
11566
- const { derivedContainers } = await Promise.resolve().then(() => require("./seeds-BLzrSwEr.cjs")).then((n) => n.workspace_exports);
11974
+ const { derivedContainers } = await Promise.resolve().then(() => require("./seeds-DQ0FG57Z.cjs")).then((n) => n.workspace_exports);
11567
11975
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
11568
11976
  if (!result) {
11569
11977
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -11581,7 +11989,7 @@ program.command("secrets:migrate").description("Copy a deployed stage's secrets
11581
11989
  try {
11582
11990
  const globalOptions = program.opts();
11583
11991
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11584
- const { secretsMigrateCommand } = await Promise.resolve().then(() => require("./migrate-BdKhIJgT.cjs"));
11992
+ const { secretsMigrateCommand } = await Promise.resolve().then(() => require("./migrate-FoL_mZIz.cjs"));
11585
11993
  await secretsMigrateCommand(options);
11586
11994
  } catch (error) {
11587
11995
  console.error(formatError(error));
@@ -11589,7 +11997,7 @@ program.command("secrets:migrate").description("Copy a deployed stage's secrets
11589
11997
  }
11590
11998
  });
11591
11999
  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").option("--atomic", "If the release fails, roll back every app it released, not only the failed ones").option("--allow-dev-services <list>", "Server targets: run MinIO and/or Mailpit (minio,mailpit) for mail and buckets the stage's secrets don't configure. Not production-grade").action(async (options) => {
11592
- const { deployCli } = await Promise.resolve().then(() => require("./cli-CWee1-wU.cjs"));
12000
+ const { deployCli } = await Promise.resolve().then(() => require("./cli-D8YQAYUX.cjs"));
11593
12001
  const globalOptions = program.opts();
11594
12002
  const code = await deployCli({
11595
12003
  cwd: (0, node_path.resolve)(globalOptions.cwd ?? process.cwd()),
@@ -11606,7 +12014,7 @@ program.command("deploy").description("Deploy a stage through its target").optio
11606
12014
  });
11607
12015
  program.command("deploy:rollback").description("Put a stage's app back on its previous release (Dokploy): one app with --app, every app with --atomic").requiredOption("--stage <stage>", "A deployed stage").option("--app <app>", "The app to roll back").option("--atomic", "Roll back every app that has an earlier release").action(async (options) => {
11608
12016
  try {
11609
- const { rollbackStage } = await Promise.resolve().then(() => require("./rollback-CdGhCk1d.cjs")).then((n) => n.rollback_exports);
12017
+ const { rollbackStage } = await Promise.resolve().then(() => require("./rollback-CPSDjGse.cjs")).then((n) => n.rollback_exports);
11610
12018
  const { terminalCredentials } = await Promise.resolve().then(() => require("./terminal-BL5E0xbR.cjs"));
11611
12019
  const globalOptions = program.opts();
11612
12020
  await rollbackStage({
@@ -11642,13 +12050,24 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
11642
12050
  try {
11643
12051
  const globalOptions = program.opts();
11644
12052
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11645
- const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-D8KVmNRg.cjs"));
12053
+ const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-B8YjwdLK.cjs"));
11646
12054
  await deployGithubCommand(options);
11647
12055
  } catch (error) {
11648
12056
  console.error(formatError(error));
11649
12057
  process.exit(1);
11650
12058
  }
11651
12059
  });
12060
+ program.command("stages").description("The workspace's stages from gkm.config.ts: local, deployed and protected").option("--json", "Print them as JSON").option("--github-output", "Write the stages, and which this workflow run builds and deploys, to $GITHUB_OUTPUT").option("--event <name>", "The event to plan for (default: $GITHUB_EVENT_NAME)").option("--stage <stage>", "The stage a workflow_dispatch deploys").action(async (options) => {
12061
+ try {
12062
+ const globalOptions = program.opts();
12063
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12064
+ const { stagesCommand } = await Promise.resolve().then(() => require("./stages-CS1sjcRb.cjs"));
12065
+ await stagesCommand(options);
12066
+ } catch (error) {
12067
+ console.error(formatError(error));
12068
+ process.exit(1);
12069
+ }
12070
+ });
11652
12071
  program.command("deploy:list").description("List Dokploy resources (projects, registries)").option("--endpoint <url>", "Dokploy server URL (uses stored credentials if logged in)").option("--projects", "List projects").option("--registries", "List registries").action(async (options) => {
11653
12072
  try {
11654
12073
  const globalOptions = program.opts();