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

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 (113) hide show
  1. package/dist/{LegacyStateStore-Ceq7A_75.mjs → LegacyStateStore-DFO3aJgs.mjs} +2 -2
  2. package/dist/{LegacyStateStore-Ceq7A_75.mjs.map → LegacyStateStore-DFO3aJgs.mjs.map} +1 -1
  3. package/dist/{LegacyStateStore-Z-EKUGSX.cjs → LegacyStateStore-iUWVxrXU.cjs} +2 -2
  4. package/dist/{LegacyStateStore-Z-EKUGSX.cjs.map → LegacyStateStore-iUWVxrXU.cjs.map} +1 -1
  5. package/dist/{LocalStateStore-Bocb8dmZ.cjs → LocalStateStore-BsFHdSeA.cjs} +2 -2
  6. package/dist/{LocalStateStore-Bocb8dmZ.cjs.map → LocalStateStore-BsFHdSeA.cjs.map} +1 -1
  7. package/dist/{LocalStateStore-C72bwpG2.mjs → LocalStateStore-DIfuh0Z1.mjs} +2 -2
  8. package/dist/{LocalStateStore-C72bwpG2.mjs.map → LocalStateStore-DIfuh0Z1.mjs.map} +1 -1
  9. package/dist/{S3StateStore-C9upSW5o.mjs → S3StateStore-DR2f78Dj.mjs} +2 -2
  10. package/dist/{S3StateStore-C9upSW5o.mjs.map → S3StateStore-DR2f78Dj.mjs.map} +1 -1
  11. package/dist/{S3StateStore-35YfcdL_.cjs → S3StateStore-ytvpIiv8.cjs} +2 -2
  12. package/dist/{S3StateStore-35YfcdL_.cjs.map → S3StateStore-ytvpIiv8.cjs.map} +1 -1
  13. package/dist/{SSMStateStore-dOmFO6g1.mjs → SSMStateStore-BZ3XL0Mr.mjs} +2 -2
  14. package/dist/{SSMStateStore-dOmFO6g1.mjs.map → SSMStateStore-BZ3XL0Mr.mjs.map} +1 -1
  15. package/dist/{SSMStateStore-W-Ps22AQ.cjs → SSMStateStore-C1JruCD9.cjs} +2 -2
  16. package/dist/{SSMStateStore-W-Ps22AQ.cjs.map → SSMStateStore-C1JruCD9.cjs.map} +1 -1
  17. package/dist/{add-1T3y6E4M.cjs → add-Bwddb4KD.cjs} +3 -3
  18. package/dist/{add-1T3y6E4M.cjs.map → add-Bwddb4KD.cjs.map} +1 -1
  19. package/dist/{add-DnYHJOap.mjs → add-DkBEZ5_s.mjs} +3 -3
  20. package/dist/{add-DnYHJOap.mjs.map → add-DkBEZ5_s.mjs.map} +1 -1
  21. package/dist/{bundler-DlKdxCGh.mjs → bundler-B8TGsunu.mjs} +5 -5
  22. package/dist/{bundler-DlKdxCGh.mjs.map → bundler-B8TGsunu.mjs.map} +1 -1
  23. package/dist/{bundler-YNvLdvlh.cjs → bundler-BkH1kPEX.cjs} +5 -5
  24. package/dist/{bundler-YNvLdvlh.cjs.map → bundler-BkH1kPEX.cjs.map} +1 -1
  25. package/dist/{cli-qMszGvDu.mjs → cli-BgDd4cZB.mjs} +3 -3
  26. package/dist/{cli-qMszGvDu.mjs.map → cli-BgDd4cZB.mjs.map} +1 -1
  27. package/dist/{cli-CWee1-wU.cjs → cli-DO7DQqCq.cjs} +3 -3
  28. package/dist/{cli-CWee1-wU.cjs.map → cli-DO7DQqCq.cjs.map} +1 -1
  29. package/dist/{compose-D4sD4Lgr.mjs → compose-BPJ9bUKp.mjs} +3 -3
  30. package/dist/{compose-D4sD4Lgr.mjs.map → compose-BPJ9bUKp.mjs.map} +1 -1
  31. package/dist/{compose-qnDyPJNp.cjs → compose-CGyxYe-h.cjs} +3 -3
  32. package/dist/{compose-qnDyPJNp.cjs.map → compose-CGyxYe-h.cjs.map} +1 -1
  33. package/dist/{config-L_6NivCp.d.mts → config-BBFiH4qk.d.mts} +2 -2
  34. package/dist/{config-L_6NivCp.d.mts.map → config-BBFiH4qk.d.mts.map} +1 -1
  35. package/dist/{config-Mr4rcXFC.d.cts → config-DMdzdO5u.d.cts} +2 -2
  36. package/dist/{config-Mr4rcXFC.d.cts.map → config-DMdzdO5u.d.cts.map} +1 -1
  37. package/dist/config.cjs +1 -1
  38. package/dist/config.d.cts +2 -2
  39. package/dist/config.d.mts +2 -2
  40. package/dist/config.mjs +1 -1
  41. package/dist/deploy/public.cjs +6 -6
  42. package/dist/deploy/public.d.cts +1 -1
  43. package/dist/deploy/public.d.mts +1 -1
  44. package/dist/deploy/public.mjs +6 -6
  45. package/dist/{deploy-C00DAKOt.mjs → deploy-0XpZDYAr.mjs} +5 -5
  46. package/dist/{deploy-C00DAKOt.mjs.map → deploy-0XpZDYAr.mjs.map} +1 -1
  47. package/dist/{deploy-CXP2fXvK.cjs → deploy-BlZx6J5x.cjs} +5 -5
  48. package/dist/{deploy-CXP2fXvK.cjs.map → deploy-BlZx6J5x.cjs.map} +1 -1
  49. package/dist/{dokploy-B3zoCmJb.mjs → dokploy-Crh4uDmP.mjs} +2 -2
  50. package/dist/{dokploy-B3zoCmJb.mjs.map → dokploy-Crh4uDmP.mjs.map} +1 -1
  51. package/dist/{dokploy-BHcSmghb.cjs → dokploy-rjXmkuwH.cjs} +2 -2
  52. package/dist/{dokploy-BHcSmghb.cjs.map → dokploy-rjXmkuwH.cjs.map} +1 -1
  53. package/dist/{encryption-DPFljXtM.mjs → encryption-0eo9aY_H.mjs} +3 -3
  54. package/dist/{encryption-DPFljXtM.mjs.map → encryption-0eo9aY_H.mjs.map} +1 -1
  55. package/dist/{encryption-DLin89I_.cjs → encryption-CxTfRx4h.cjs} +3 -3
  56. package/dist/{encryption-DLin89I_.cjs.map → encryption-CxTfRx4h.cjs.map} +1 -1
  57. package/dist/{engine-BS3iX9Mu.cjs → engine-CMzSebbs.cjs} +4 -4
  58. package/dist/{engine-BS3iX9Mu.cjs.map → engine-CMzSebbs.cjs.map} +1 -1
  59. package/dist/{engine-CgzgIg-_.mjs → engine-DxWswfXe.mjs} +4 -4
  60. package/dist/{engine-CgzgIg-_.mjs.map → engine-DxWswfXe.mjs.map} +1 -1
  61. package/dist/{github-D8KVmNRg.cjs → github-Dukt5cJe.cjs} +3 -3
  62. package/dist/{github-D8KVmNRg.cjs.map → github-Dukt5cJe.cjs.map} +1 -1
  63. package/dist/{github-DL4zwJsR.mjs → github-HOfOpFI_.mjs} +3 -3
  64. package/dist/{github-DL4zwJsR.mjs.map → github-HOfOpFI_.mjs.map} +1 -1
  65. package/dist/{index-DTEPwLml.d.cts → index-DFrbhRwg.d.cts} +6 -2
  66. package/dist/{index-C0UmP_lg.d.mts.map → index-DFrbhRwg.d.cts.map} +1 -1
  67. package/dist/{index-C0UmP_lg.d.mts → index-Wuqpf316.d.mts} +6 -2
  68. package/dist/{index-DTEPwLml.d.cts.map → index-Wuqpf316.d.mts.map} +1 -1
  69. package/dist/index.cjs +427 -111
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.mjs +427 -111
  72. package/dist/index.mjs.map +1 -1
  73. package/dist/{migrate-CCejlgU0.mjs → migrate-1dGEzuJ-.mjs} +3 -3
  74. package/dist/{migrate-CCejlgU0.mjs.map → migrate-1dGEzuJ-.mjs.map} +1 -1
  75. package/dist/{migrate-BdKhIJgT.cjs → migrate-Bemd4tkz.cjs} +3 -3
  76. package/dist/{migrate-BdKhIJgT.cjs.map → migrate-Bemd4tkz.cjs.map} +1 -1
  77. package/dist/{migrate-DIjG8-QR.cjs → migrate-BgPWVnAZ.cjs} +3 -3
  78. package/dist/{migrate-DIjG8-QR.cjs.map → migrate-BgPWVnAZ.cjs.map} +1 -1
  79. package/dist/{migrate-Cd3kcI7o.mjs → migrate-C14NNbFx.mjs} +3 -3
  80. package/dist/{migrate-Cd3kcI7o.mjs.map → migrate-C14NNbFx.mjs.map} +1 -1
  81. package/dist/openapi.cjs +1 -1
  82. package/dist/openapi.mjs +1 -1
  83. package/dist/{resolve-ApSUK_HB.cjs → resolve-DCTcD823.cjs} +3 -3
  84. package/dist/{resolve-ApSUK_HB.cjs.map → resolve-DCTcD823.cjs.map} +1 -1
  85. package/dist/{resolve-DY9TaVeV.mjs → resolve-y1PkSZrF.mjs} +3 -3
  86. package/dist/{resolve-DY9TaVeV.mjs.map → resolve-y1PkSZrF.mjs.map} +1 -1
  87. package/dist/{rollback-CdGhCk1d.cjs → rollback-BVQFDjxr.cjs} +5 -5
  88. package/dist/{rollback-CdGhCk1d.cjs.map → rollback-BVQFDjxr.cjs.map} +1 -1
  89. package/dist/{rollback-DXU6cr7q.mjs → rollback-CwVhG_xa.mjs} +5 -5
  90. package/dist/{rollback-DXU6cr7q.mjs.map → rollback-CwVhG_xa.mjs.map} +1 -1
  91. package/dist/{seeds-DbyqaXW4.mjs → seeds-BoKQ74Od.mjs} +3 -3
  92. package/dist/{seeds-DbyqaXW4.mjs.map → seeds-BoKQ74Od.mjs.map} +1 -1
  93. package/dist/{seeds-BLzrSwEr.cjs → seeds-Ccju3zGl.cjs} +3 -3
  94. package/dist/{seeds-BLzrSwEr.cjs.map → seeds-Ccju3zGl.cjs.map} +1 -1
  95. package/dist/{sniffer-ByWtH28i.mjs → sniffer-6oYI-MpT.mjs} +6 -6
  96. package/dist/{sniffer-ByWtH28i.mjs.map → sniffer-6oYI-MpT.mjs.map} +1 -1
  97. package/dist/{sniffer-BjG-1mlD.cjs → sniffer-DeyYI9Iv.cjs} +6 -6
  98. package/dist/{sniffer-BjG-1mlD.cjs.map → sniffer-DeyYI9Iv.cjs.map} +1 -1
  99. package/dist/stages-CHF9RFog.mjs +174 -0
  100. package/dist/stages-CHF9RFog.mjs.map +1 -0
  101. package/dist/stages-CwrYVX9u.cjs +174 -0
  102. package/dist/stages-CwrYVX9u.cjs.map +1 -0
  103. package/dist/target/public.cjs +1 -1
  104. package/dist/target/public.mjs +1 -1
  105. package/dist/workspace/index.cjs +2 -1
  106. package/dist/workspace/index.d.cts +2 -2
  107. package/dist/workspace/index.d.mts +2 -2
  108. package/dist/workspace/index.mjs +2 -2
  109. package/dist/{workspace-Bp8FEkF-.cjs → workspace-4i6Jwsoa.cjs} +22 -2
  110. package/dist/{workspace-Bp8FEkF-.cjs.map → workspace-4i6Jwsoa.cjs.map} +1 -1
  111. package/dist/{workspace-BBdqmkch.mjs → workspace-DD9mtPy8.mjs} +11 -3
  112. package/dist/{workspace-BBdqmkch.mjs.map → workspace-DD9mtPy8.mjs.map} +1 -1
  113. package/package.json +15 -15
package/dist/index.mjs CHANGED
@@ -4,20 +4,20 @@ import { r as DEFAULT_TIMEOUT_MS, t as CommandFailed } from "./run-BJLIerMM.mjs"
4
4
  import { c as getDokployCredentials, f as removeDokployCredentials, i as LocalSandbox, l as getDokployRegistryId, m as storeHostingerToken, p as storeDokployCredentials, s as getCredentialsPath, u as getDokployToken } from "./credentials-pAtB70k3.mjs";
5
5
  import { l as withOwningTsconfigJsx, r as discover, u as ConstructGenerator } from "./discover-BPelzeGW.mjs";
6
6
  import { t as appKey } from "./derive-B4vDpzgP.mjs";
7
- import { A as deployedProblems, D as InvalidStages, I as resolveLogs, O as TEST_STAGE, c as getAppGkmConfig, j as stageProblems, n as allConstructGlobs, s as getAppBuildOrder } from "./workspace-BBdqmkch.mjs";
7
+ import { M as deployedProblems, N as stageProblems, O as InvalidStages, R as resolveLogs, c as getAppBuildOrder, k as TEST_STAGE, l as getAppGkmConfig, r as allConstructGlobs } from "./workspace-DD9mtPy8.mjs";
8
8
  import { ConfigNotFound, NotABackendApp, NotInAnApp, loadAppConfig, loadConfig, loadWorkspaceConfig, loadWorkspaceSettings, parseModuleConfig } from "./config.mjs";
9
- import { B as WorkerGenerator, Ct as postgresDatabaseNames, F as parseDevServices, Fn as withCustomSecret, G as FAKE_ENV, Gt as imageBuildCredentials, H as workerEntryDir, J as derivedContainers, Jt as eventsBackendFor, K as backendsOf$1, Kt as isImageBuild, Mn as maskPassword, Pn as toEmbeddableSecrets, Tt as quoteIdentifier, U as QueueGenerator, V as workerBundleName, W as CronGenerator, Y as reconcileWorkspace, Yt as providerOf, Zt as CLUSTER_DATABASE, _t as LOCAL_CA_PATH, an as primaryPortKey, cn as findAvailablePort, dn as loadPortState, et as readLocalCredentials, fn as loadSecretsForApp, hn as secretsStoreFor, in as portsOf, kt as pgClient, ln as isPortAvailable, nn as fakeKey, on as createCredentialsPreload, pn as prepareEntryCredentials, q as constructGlobs, qt as cacheBackendFor, sn as createEntryWrapper, tn as describeServices, un as loadEnvFiles, vn as FileSecretsStore, vt as forgetState, wn as deployIdentity, x as dockerCommand, xn as projectKey, yn as getKeyPath, z as workerUnits } from "./seeds-DbyqaXW4.mjs";
9
+ import { B as WorkerGenerator, Ct as postgresDatabaseNames, F as parseDevServices, Fn as withCustomSecret, G as FAKE_ENV, Gt as imageBuildCredentials, H as workerEntryDir, J as derivedContainers, Jt as eventsBackendFor, K as backendsOf$1, Kt as isImageBuild, Mn as maskPassword, Pn as toEmbeddableSecrets, Tt as quoteIdentifier, U as QueueGenerator, V as workerBundleName, W as CronGenerator, Y as reconcileWorkspace, Yt as providerOf, Zt as CLUSTER_DATABASE, _t as LOCAL_CA_PATH, an as primaryPortKey, cn as findAvailablePort, dn as loadPortState, et as readLocalCredentials, fn as loadSecretsForApp, hn as secretsStoreFor, in as portsOf, kt as pgClient, ln as isPortAvailable, nn as fakeKey, on as createCredentialsPreload, pn as prepareEntryCredentials, q as constructGlobs, qt as cacheBackendFor, sn as createEntryWrapper, tn as describeServices, un as loadEnvFiles, vn as FileSecretsStore, vt as forgetState, wn as deployIdentity, x as dockerCommand, xn as projectKey, yn as getKeyPath, z as workerUnits } from "./seeds-BoKQ74Od.mjs";
10
10
  import { a as parseTelemetryFlag, i as telemetryFor, n as runtimeFor, t as EndpointGenerator } from "./EndpointGenerator-DtnIGafh.mjs";
11
11
  import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
12
12
  import { s as isCacheBackend, t as SubscriberGenerator } from "./SubscriberGenerator-DQZeGkDW.mjs";
13
13
  import { a as describeHolder, i as assignAppPorts, o as holderOf, r as appTag, s as withAppPorts, t as APP_TAG_ENV } from "./appPorts-C9z9Pzn9.mjs";
14
14
  import { n as driversFor, r as eventsBackendsIn, t as cacheBackendsIn } from "./drivers-B4RP2B2O.mjs";
15
15
  import { n as withCompute, r as withRoutes, t as manifestModule } from "./emit-C1e8qD08.mjs";
16
- import { g as createStateStore, n as sniffAppEnvironment, o as DokployApi, x as keyFingerprint } from "./sniffer-ByWtH28i.mjs";
17
- import { t as deploy$1 } from "./deploy-C00DAKOt.mjs";
18
- import { a as stackOverrideFile, c as parseDigests, d as removeEdgeRoutes, f as EDGE_PROJECT, i as ComposePushNeedsBuild, l as pinnedRef, o as composeProject, p as dockerCompose, r as ComposePinNeedsPull, s as stackDir, t as composeTarget, u as edgeDir } from "./compose-D4sD4Lgr.mjs";
16
+ import { g as createStateStore, n as sniffAppEnvironment, o as DokployApi, x as keyFingerprint } from "./sniffer-6oYI-MpT.mjs";
17
+ import { t as deploy$1 } from "./deploy-0XpZDYAr.mjs";
18
+ import { a as stackOverrideFile, c as parseDigests, d as removeEdgeRoutes, f as EDGE_PROJECT, i as ComposePushNeedsBuild, l as pinnedRef, o as composeProject, p as dockerCompose, r as ComposePinNeedsPull, s as stackDir, t as composeTarget, u as edgeDir } from "./compose-BPJ9bUKp.mjs";
19
19
  import { o as loadCredentialSchemas, t as CredentialsInvalid } from "./credentialSchemas-BJqRJNK4.mjs";
20
- import { t as LocalStateStore } from "./LocalStateStore-C72bwpG2.mjs";
20
+ import { t as LocalStateStore } from "./LocalStateStore-DIfuh0Z1.mjs";
21
21
  import { a as getExecCommand, c as validateProjectName, i as findWorkspaceRoot, n as detectPackageManager$1, o as getInstallCommand, r as findWorkspacePackages, s as getRunCommand, t as checkDirectoryExists } from "./utils-D_fxUqvU.mjs";
22
22
  import { a as writeTestHarness, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, t as TEST_READY_ENV } from "./ready-DhZbZtFw.mjs";
23
23
  import { createRequire } from "node:module";
@@ -47,7 +47,7 @@ import crypto from "crypto";
47
47
  import { Function } from "@geekmidas/constructs/functions";
48
48
  import { Topic } from "@geekmidas/constructs/topic";
49
49
  //#region package.json
50
- var version = "10.0.0-alpha.82";
50
+ var version = "10.0.0-alpha.83";
51
51
  //#endregion
52
52
  //#region src/prompt.ts
53
53
  /**
@@ -130,7 +130,7 @@ const logger$8 = console;
130
130
  * Validate Dokploy token by making a test API call
131
131
  */
132
132
  async function validateDokployToken(endpoint, token) {
133
- const { DokployApi } = await import("./sniffer-ByWtH28i.mjs").then((n) => n.c);
133
+ const { DokployApi } = await import("./sniffer-6oYI-MpT.mjs").then((n) => n.c);
134
134
  return new DokployApi({
135
135
  baseUrl: endpoint,
136
136
  token
@@ -2211,9 +2211,9 @@ async function workspaceDevCommand(configured, options) {
2211
2211
  for (const { id, port } of fakes) logger$5.log(`🎭 ${id} fake: http://localhost:${port}`);
2212
2212
  logger$5.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
2213
2213
  if (options.migrate || options.seed) {
2214
- const { prepareDevDatabases } = await import("./migrate-CCejlgU0.mjs");
2214
+ const { prepareDevDatabases } = await import("./migrate-1dGEzuJ-.mjs");
2215
2215
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
2216
- } else await import("./migrate-CCejlgU0.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
2216
+ } else await import("./migrate-1dGEzuJ-.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
2217
2217
  logger$5.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
2218
2218
  });
2219
2219
  const appUrls = {};
@@ -3221,7 +3221,7 @@ async function buildApp(input) {
3221
3221
  if (production?.bundle && !input.skipBundle) {
3222
3222
  const outputDir = join(appRoot, ".gkm", "server");
3223
3223
  const entryPoint = await writeSurfaceServer(outputDir, production);
3224
- const { bundleServer } = await import("./bundler-DlKdxCGh.mjs");
3224
+ const { bundleServer } = await import("./bundler-B8TGsunu.mjs");
3225
3225
  await bundleServer({
3226
3226
  entryPoint,
3227
3227
  outputDir: join(outputDir, "dist"),
@@ -3345,7 +3345,7 @@ async function buildWorkers(input) {
3345
3345
  });
3346
3346
  logger$4.log(`Generated worker ${worker.id}: ${crons.length} crons, ${queues.length} queues, ${subscribers.length} subscribers`);
3347
3347
  if (!input.bundle) continue;
3348
- const { bundleServer } = await import("./bundler-DlKdxCGh.mjs");
3348
+ const { bundleServer } = await import("./bundler-B8TGsunu.mjs");
3349
3349
  const outfile = workerBundleName(worker.id);
3350
3350
  await bundleServer({
3351
3351
  entryPoint,
@@ -3415,7 +3415,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3415
3415
  let masterKey;
3416
3416
  if (context.production?.bundle && !skipBundle) {
3417
3417
  logger$4.log(`\n📦 Bundling production server...`);
3418
- const { bundleServer } = await import("./bundler-DlKdxCGh.mjs");
3418
+ const { bundleServer } = await import("./bundler-B8TGsunu.mjs");
3419
3419
  const allConstructs = [
3420
3420
  ...endpoints.map((e) => e.construct),
3421
3421
  ...functions.map((f) => f.construct),
@@ -5102,12 +5102,12 @@ const PNPM_VERSION = "pnpm@10.13.1";
5102
5102
  const BIOME_SCHEMA = `https://biomejs.dev/schemas/${DEPENDENCY_VERSIONS["@biomejs/biome"].replace(/^[~^]/, "")}/schema.json`;
5103
5103
  //#endregion
5104
5104
  //#region src/init/versions.ts
5105
- const require$1 = createRequire(import.meta.url);
5105
+ const require$2 = createRequire(import.meta.url);
5106
5106
  function loadPackageJson() {
5107
5107
  try {
5108
- return require$1("../package.json");
5108
+ return require$2("../package.json");
5109
5109
  } catch {
5110
- return require$1("../../package.json");
5110
+ return require$2("../../package.json");
5111
5111
  }
5112
5112
  }
5113
5113
  /**
@@ -5117,25 +5117,25 @@ function loadPackageJson() {
5117
5117
  * Run: pnpm --filter @geekmidas/cli sync-versions
5118
5118
  */
5119
5119
  const GEEKMIDAS_VERSIONS = {
5120
- "@geekmidas/audit": "~10.0.0-alpha.82",
5121
- "@geekmidas/auth": "~10.0.0-alpha.82",
5122
- "@geekmidas/cache": "~10.0.0-alpha.82",
5123
- "@geekmidas/client": "~10.0.0-alpha.82",
5124
- "@geekmidas/cloud": "~10.0.0-alpha.82",
5125
- "@geekmidas/constructs": "~10.0.0-alpha.82",
5126
- "@geekmidas/db": "~10.0.0-alpha.82",
5127
- "@geekmidas/emailkit": "~10.0.0-alpha.82",
5128
- "@geekmidas/envkit": "~10.0.0-alpha.82",
5129
- "@geekmidas/errors": "~10.0.0-alpha.82",
5130
- "@geekmidas/events": "~10.0.0-alpha.82",
5131
- "@geekmidas/logger": "~10.0.0-alpha.82",
5132
- "@geekmidas/manifest": "~10.0.0-alpha.82",
5133
- "@geekmidas/rate-limit": "~10.0.0-alpha.82",
5134
- "@geekmidas/schema": "~10.0.0-alpha.82",
5135
- "@geekmidas/services": "~10.0.0-alpha.82",
5136
- "@geekmidas/storage": "~10.0.0-alpha.82",
5137
- "@geekmidas/telescope": "~10.0.0-alpha.82",
5138
- "@geekmidas/testkit": "~10.0.0-alpha.82",
5120
+ "@geekmidas/audit": "~10.0.0-alpha.83",
5121
+ "@geekmidas/auth": "~10.0.0-alpha.83",
5122
+ "@geekmidas/cache": "~10.0.0-alpha.83",
5123
+ "@geekmidas/client": "~10.0.0-alpha.83",
5124
+ "@geekmidas/cloud": "~10.0.0-alpha.83",
5125
+ "@geekmidas/constructs": "~10.0.0-alpha.83",
5126
+ "@geekmidas/db": "~10.0.0-alpha.83",
5127
+ "@geekmidas/emailkit": "~10.0.0-alpha.83",
5128
+ "@geekmidas/envkit": "~10.0.0-alpha.83",
5129
+ "@geekmidas/errors": "~10.0.0-alpha.83",
5130
+ "@geekmidas/events": "~10.0.0-alpha.83",
5131
+ "@geekmidas/logger": "~10.0.0-alpha.83",
5132
+ "@geekmidas/manifest": "~10.0.0-alpha.83",
5133
+ "@geekmidas/rate-limit": "~10.0.0-alpha.83",
5134
+ "@geekmidas/schema": "~10.0.0-alpha.83",
5135
+ "@geekmidas/services": "~10.0.0-alpha.83",
5136
+ "@geekmidas/storage": "~10.0.0-alpha.83",
5137
+ "@geekmidas/telescope": "~10.0.0-alpha.83",
5138
+ "@geekmidas/testkit": "~10.0.0-alpha.83",
5139
5139
  "@geekmidas/cli": `~${loadPackageJson().version}`
5140
5140
  };
5141
5141
  //#endregion
@@ -5508,6 +5508,38 @@ coverage/
5508
5508
  return files;
5509
5509
  }
5510
5510
  //#endregion
5511
+ //#region src/init/stagesAction.ts
5512
+ /**
5513
+ * How a scaffolded workflow names the `stages` action.
5514
+ *
5515
+ * A published CLI pins it to the commit it was released from: the release
5516
+ * build is given that commit as `GKM_RELEASE_COMMIT`, which the build writes
5517
+ * into the bundle (`define` in tsdown.config.ts). The action at that commit is
5518
+ * the one released beside this CLI, and a commit SHA cannot be moved under a
5519
+ * project the way a branch or a tag can.
5520
+ *
5521
+ * A CLI built anywhere else — a checkout, a local build — knows no released
5522
+ * commit, and names `main`.
5523
+ */
5524
+ const require$1 = createRequire(import.meta.url);
5525
+ function cliVersion() {
5526
+ try {
5527
+ return require$1("../package.json").version;
5528
+ } catch {
5529
+ return require$1("../../package.json").version;
5530
+ }
5531
+ }
5532
+ /** The action, as a workflow's `uses:` names it before the `@`. */
5533
+ const STAGES_ACTION = "geekmidas/toolbox/actions/stages";
5534
+ const COMMIT = /^[0-9a-f]{40}$/;
5535
+ /**
5536
+ * The `uses:` value for the stages action, with a comment saying which CLI
5537
+ * release the pinned commit is.
5538
+ */
5539
+ function stagesActionUses(commit = "4dee60bc593ffdfbaa59bbf8d8fe9e3e64f23d41", version = cliVersion()) {
5540
+ return commit && COMMIT.test(commit) ? `${STAGES_ACTION}@${commit} # @geekmidas/cli ${version}` : `${STAGES_ACTION}@main # pin to a commit SHA: see the action's README`;
5541
+ }
5542
+ //#endregion
5511
5543
  //#region src/init/generators/github.ts
5512
5544
  /**
5513
5545
  * The GitHub workflows a scaffold ships with.
@@ -5517,11 +5549,14 @@ coverage/
5517
5549
  * release, labelled from their titles.
5518
5550
  * - `deploy.yml` — when a deploy target was picked: a push to main deploys the
5519
5551
  * stages that are not protected, publishing the drafted release deploys the
5520
- * protected ones.
5552
+ * protected ones, and a manual run deploys the one stage it names.
5521
5553
  *
5522
- * The deploy workflow names no stage. It reads `stages` from gkm.config.ts at
5523
- * run time, so the config stays the one place stages are declared and a
5524
- * workflow cannot fall out of step with it.
5554
+ * The deploy workflow names no stage. Its first job runs the
5555
+ * `geekmidas/toolbox/actions/stages` action, which asks the project's own
5556
+ * `gkm stages --github-output` which stages the run builds and deploys — so
5557
+ * the config stays the one place stages are declared, the rules for each event
5558
+ * are tested code in the CLI, and a workflow cannot fall out of step with
5559
+ * either.
5525
5560
  */
5526
5561
  /** How each package manager is set up, installs, and runs a script. */
5527
5562
  const TOOLING = {
@@ -5707,46 +5742,21 @@ template: |
5707
5742
  **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
5708
5743
  `;
5709
5744
  }
5710
- function deploy(options) {
5711
- const pm = tooling(options);
5712
- const sst = options.deployTarget === "sst";
5713
- const credentials = sst ? `
5714
- # An IAM role GitHub assumes through OIDC — no long-lived AWS keys. Set
5715
- # AWS_ROLE_ARN as a variable on each environment.
5716
- - uses: aws-actions/configure-aws-credentials@v4
5717
- with:
5718
- role-to-assume: \${{ vars.AWS_ROLE_ARN }}
5719
- aws-region: ${options.region}
5720
- ` : "";
5721
- const keyDir = `~/.gkm/keys/${projectKey({ name: options.name })}`;
5722
- const stageSecrets = sst ? credentials : `
5723
- # The stage's secrets are kept in the encrypted .gkm/secrets/<stage>.json,
5724
- # and each environment holds its key as GKM_SECRETS_KEY. .gkm/ is
5725
- # gitignored, so a checkout has the key but not the file: set
5726
- # secrets.store in gkm.config.ts to a store CI can reach before deploying
5727
- # from here.
5728
- # Owner-only, as the CLI writes its own keys. The stage reaches the shell
5729
- # as a variable, never pasted into the script: on a manual run it is
5730
- # whatever was typed.
5731
- - name: Stage secrets key
5732
- run: |
5733
- mkdir -p -m 700 ${keyDir}
5734
- (umask 077 && printf '%s' "$KEY" > ${keyDir}/"$STAGE".key)
5735
- env:
5736
- KEY: \${{ secrets.GKM_SECRETS_KEY }}
5737
- STAGE: \${{ matrix.stage }}
5738
- `;
5739
- const deployEnv = sst ? "" : `
5740
- DOKPLOY_API_TOKEN: \${{ secrets.DOKPLOY_API_TOKEN }}
5741
- DOKPLOY_ENDPOINT: \${{ vars.DOKPLOY_ENDPOINT }}`;
5745
+ /**
5746
+ * What every deploy workflow starts with: its triggers, and the `stages` job,
5747
+ * which asks the project's own gkm (through the stages action) which stages
5748
+ * this run builds and deploys. Nothing in a workflow names a stage.
5749
+ */
5750
+ function deployHeader(build) {
5742
5751
  return `name: Deploy
5743
5752
 
5744
5753
  # Which stages exist, and which are protected, is read from \`stages\` in
5745
- # gkm.config.ts — nothing here names one:
5754
+ # gkm.config.ts by the project's own gkm, through the stages action — nothing
5755
+ # here names one:
5746
5756
  #
5747
- # push to main every deployed stage that is not protected
5757
+ # push to main ${build ? "builds every deployed stage, deploys those not protected" : "every deployed stage that is not protected"}
5748
5758
  # release published the protected stages (publish the drafted release)
5749
- # run workflow the one stage you name
5759
+ # run workflow the one stage you name, at the ref you name
5750
5760
  on:
5751
5761
  push:
5752
5762
  branches: [main]
@@ -5757,17 +5767,27 @@ on:
5757
5767
  stage:
5758
5768
  description: A deployed stage from gkm.config.ts
5759
5769
  required: true
5770
+ type: string
5771
+ ref:
5772
+ description: The commit, tag or branch to deploy (default the latest on main)
5773
+ required: false
5774
+ type: string
5760
5775
 
5761
5776
  permissions:
5762
5777
  contents: read
5763
- id-token: write
5764
-
5765
- jobs:
5766
- stages:
5778
+ `;
5779
+ }
5780
+ function stagesJob(options) {
5781
+ const pm = tooling(options);
5782
+ return ` stages:
5767
5783
  name: Pick stages
5768
5784
  runs-on: ubuntu-latest
5769
5785
  outputs:
5770
- stages: \${{ steps.pick.outputs.stages }}
5786
+ build: \${{ steps.stages.outputs.build }}
5787
+ deploy: \${{ steps.stages.outputs.deploy }}
5788
+ has-build: \${{ steps.stages.outputs.has-build }}
5789
+ has-deploy: \${{ steps.stages.outputs.has-deploy }}
5790
+ aws-region: \${{ steps.stages.outputs.aws-region }}
5771
5791
  steps:
5772
5792
  - uses: actions/checkout@v4
5773
5793
 
@@ -5775,44 +5795,286 @@ ${pm.setup}
5775
5795
  - name: Install
5776
5796
  run: ${pm.install}
5777
5797
 
5798
+ # Runs \`gkm stages --github-output\`: the rules for each event are the
5799
+ # installed CLI's, and a manual run naming a stage gkm.config.ts does not
5800
+ # deploy fails here.
5778
5801
  - name: Read stages from gkm.config.ts
5779
- id: pick
5802
+ id: stages
5803
+ uses: ${stagesActionUses()}
5804
+ with:
5805
+ stage: \${{ inputs.stage }}
5806
+ `;
5807
+ }
5808
+ /**
5809
+ * The registry login a compose build pushes with: GitHub's own token on
5810
+ * ghcr.io, otherwise a username variable and a password secret.
5811
+ */
5812
+ function registryLogin(registry) {
5813
+ const host = registry.split("/")[0];
5814
+ const ghcr = host === "ghcr.io";
5815
+ return ` - uses: docker/login-action@v3
5816
+ with:
5817
+ registry: ${host}
5818
+ username: ${ghcr ? "${{ github.actor }}" : "${{ vars.REGISTRY_USERNAME }}"}
5819
+ password: ${ghcr ? "${{ secrets.GITHUB_TOKEN }}" : "${{ secrets.REGISTRY_PASSWORD }}"}
5820
+ `;
5821
+ }
5822
+ /**
5823
+ * The compose deploy: images are built and pushed by the runner, where the
5824
+ * code is, and pulled by the server, where the stage runs.
5825
+ *
5826
+ * - `build` (on a push): every deployed stage's images, tagged with the
5827
+ * commit, and their digests kept as the artifact `digests-<stage>`.
5828
+ * - `deploy`: the commit the event names — a release's tag, a manual run's
5829
+ * `ref` — then that commit's push build, whose digests pin each image. Over
5830
+ * SSH, with a pinned host key, the server checks the commit out and runs
5831
+ * `gkm compose` on exactly those images.
5832
+ */
5833
+ function composeDeploy(options) {
5834
+ const pm = tooling(options);
5835
+ const registry = options.registry ?? "";
5836
+ return `${deployHeader(true)}
5837
+ jobs:
5838
+ ${stagesJob(options)}
5839
+ build:
5840
+ name: Build \${{ matrix.stage }}
5841
+ needs: stages
5842
+ if: needs.stages.outputs.has-build == 'true'
5843
+ runs-on: ubuntu-latest
5844
+ strategy:
5845
+ fail-fast: false
5846
+ matrix:
5847
+ stage: \${{ fromJSON(needs.stages.outputs.build) }}
5848
+ # One GitHub environment per stage: its secrets, variables and approvals.
5849
+ environment: \${{ matrix.stage }}
5850
+ permissions:
5851
+ contents: read
5852
+ packages: write
5853
+ id-token: write
5854
+ steps:
5855
+ - uses: actions/checkout@v4
5856
+
5857
+ ${pm.setup}
5858
+ - name: Install
5859
+ run: ${pm.install}
5860
+
5861
+ ${registryLogin(registry)}
5862
+ # A site's public URLs are built into it, so the build reads the stage's
5863
+ # secrets store. With secrets.store on SSM or Secrets Manager the job
5864
+ # assumes the stage's role (AWS_ROLE_ARN, set by gkm deploy:github);
5865
+ # with the default 'file' store there is no role to assume.
5866
+ - name: Assume the stage's AWS role
5867
+ if: needs.stages.outputs.aws-region != ''
5868
+ uses: aws-actions/configure-aws-credentials@v4
5869
+ with:
5870
+ role-to-assume: \${{ vars.AWS_ROLE_ARN }}
5871
+ aws-region: \${{ needs.stages.outputs.aws-region }}
5872
+
5873
+ # Builds and pushes every image the stage runs, tagged with the commit,
5874
+ # and starts nothing.
5875
+ - name: Build and push
5876
+ run: ${pm.exec} gkm compose --stage "$STAGE" --build --push --tag "$SHA" --digests-file digests.json
5877
+ env:
5878
+ STAGE: \${{ matrix.stage }}
5879
+ SHA: \${{ github.sha }}
5880
+
5881
+ # What each tag pointed at when it was pushed. A deploy runs these
5882
+ # digests, so a tag moved since cannot change what is released.
5883
+ - uses: actions/upload-artifact@v4
5884
+ with:
5885
+ name: digests-\${{ matrix.stage }}
5886
+ path: digests.json
5887
+ retention-days: 90
5888
+
5889
+ deploy:
5890
+ name: Deploy \${{ matrix.stage }}
5891
+ needs: [stages, build]
5892
+ # The build is skipped on a release or a manual run: deploy then anyway,
5893
+ # but never after a failed build.
5894
+ if: \${{ !cancelled() && !failure() && needs.stages.outputs.has-deploy == 'true' }}
5895
+ runs-on: ubuntu-latest
5896
+ strategy:
5897
+ fail-fast: false
5898
+ matrix:
5899
+ stage: \${{ fromJSON(needs.stages.outputs.deploy) }}
5900
+ environment: \${{ matrix.stage }}
5901
+ concurrency:
5902
+ group: deploy-\${{ matrix.stage }}
5903
+ cancel-in-progress: false
5904
+ permissions:
5905
+ contents: read
5906
+ actions: read
5907
+ steps:
5908
+ # A release deploys its tag's commit, never its target_commitish — the
5909
+ # branch it was drafted against, which has moved on since.
5910
+ - name: Resolve the commit
5911
+ id: commit
5912
+ run: |
5913
+ case "$EVENT" in
5914
+ release) ref="tags/$TAG" ;;
5915
+ workflow_dispatch) ref="\${REF:-$SHA}" ;;
5916
+ *) ref="$SHA" ;;
5917
+ esac
5918
+ sha=$(gh api "repos/$REPO/commits/$ref" --jq .sha)
5919
+ echo "Deploying $sha"
5920
+ echo "sha=$sha" >> "$GITHUB_OUTPUT"
5921
+ env:
5922
+ GH_TOKEN: \${{ github.token }}
5923
+ REPO: \${{ github.repository }}
5924
+ EVENT: \${{ github.event_name }}
5925
+ TAG: \${{ github.event.release.tag_name }}
5926
+ REF: \${{ inputs.ref }}
5927
+ SHA: \${{ github.sha }}
5928
+
5929
+ # The push run of this workflow that built the commit for this stage:
5930
+ # this run on a push, the newest one with the stage's digests otherwise.
5931
+ - name: Find the commit's push build
5932
+ id: build
5780
5933
  run: |
5781
- ${pm.exec} tsx -e "
5782
- import config from './gkm.config.ts';
5783
- const { deployed, protected: kept = [] } = config.stages;
5784
- const event = process.env.EVENT;
5785
- if (event === 'workflow_dispatch' && !deployed.includes(process.env.STAGE)) {
5786
- console.error('Not a deployed stage: ' + process.env.STAGE + ' (deployed: ' + deployed.join(', ') + ')');
5787
- process.exit(1);
5788
- }
5789
- const stages =
5790
- event === 'workflow_dispatch' ? [process.env.STAGE]
5791
- : event === 'release' ? kept
5792
- : deployed.filter((stage) => !kept.includes(stage));
5793
- console.log('stages=' + JSON.stringify(stages));
5794
- " >> "$GITHUB_OUTPUT"
5934
+ if [ "$EVENT" = push ]; then
5935
+ echo "run-id=$RUN_ID" >> "$GITHUB_OUTPUT"
5936
+ exit 0
5937
+ fi
5938
+ workflow=\${WORKFLOW_REF%%@*}
5939
+ workflow=\${workflow##*/}
5940
+ for id in $(gh run list --repo "$REPO" --workflow "$workflow" --event push --commit "$SHA" --limit 20 --json databaseId --jq '.[].databaseId'); do
5941
+ found=$(gh api "repos/$REPO/actions/runs/$id/artifacts?name=$ARTIFACT" --jq '[.artifacts[] | select(.expired | not)] | length')
5942
+ if [ "$found" -gt 0 ]; then
5943
+ echo "run-id=$id" >> "$GITHUB_OUTPUT"
5944
+ exit 0
5945
+ fi
5946
+ done
5947
+ echo "run-id=" >> "$GITHUB_OUTPUT"
5795
5948
  env:
5949
+ GH_TOKEN: \${{ github.token }}
5950
+ REPO: \${{ github.repository }}
5796
5951
  EVENT: \${{ github.event_name }}
5797
- STAGE: \${{ inputs.stage }}
5952
+ RUN_ID: \${{ github.run_id }}
5953
+ WORKFLOW_REF: \${{ github.workflow_ref }}
5954
+ SHA: \${{ steps.commit.outputs.sha }}
5955
+ ARTIFACT: digests-\${{ matrix.stage }}
5956
+
5957
+ - name: Download digests-\${{ matrix.stage }}
5958
+ if: steps.build.outputs.run-id != ''
5959
+ uses: actions/download-artifact@v4
5960
+ with:
5961
+ name: digests-\${{ matrix.stage }}
5962
+ run-id: \${{ steps.build.outputs.run-id }}
5963
+ github-token: \${{ github.token }}
5798
5964
 
5965
+ - name: No digests — deploying by tag
5966
+ if: steps.build.outputs.run-id == ''
5967
+ run: |
5968
+ 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."
5969
+ env:
5970
+ STAGE: \${{ matrix.stage }}
5971
+ SHA: \${{ steps.commit.outputs.sha }}
5972
+
5973
+ # Each environment holds DEPLOY_SSH_KEY (secret) and DEPLOY_KNOWN_HOSTS,
5974
+ # DEPLOY_HOST, DEPLOY_USER and DEPLOY_PATH (variables): the server's
5975
+ # host key is pinned, never accepted on first sight. DEPLOY_PATH is a
5976
+ # clone of this repository, with a docker login to ${registry.split("/")[0]}
5977
+ # and the stage's secrets.
5978
+ #
5979
+ # Every value reaches the server as an argument to a quoted heredoc —
5980
+ # nothing is pasted into the script.
5981
+ - name: Deploy on the server
5982
+ run: |
5983
+ mkdir -p ~/.ssh && chmod 700 ~/.ssh
5984
+ (umask 077 && printf '%s\\n' "$SSH_KEY" > ~/.ssh/deploy_key)
5985
+ printf '%s\\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts
5986
+ digests=""
5987
+ if [ -f digests.json ]; then digests=$(base64 -w0 < digests.json); fi
5988
+ ssh -i ~/.ssh/deploy_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes \\
5989
+ "$DEPLOY_USER@$DEPLOY_HOST" \\
5990
+ "bash -s -- $(printf '%q ' "$STAGE" "$SHA" "$DEPLOY_PATH" "$digests")" <<'REMOTE'
5991
+ set -euo pipefail
5992
+ stage=$1 sha=$2 dir=$3 digests=$4
5993
+ cd "$dir"
5994
+ git fetch --quiet origin
5995
+ git checkout --quiet --detach "$sha"
5996
+ ${pm.install}
5997
+ args=(--stage "$stage" --tag "$sha")
5998
+ if [ -n "$digests" ]; then
5999
+ mkdir -p .gkm
6000
+ printf '%s' "$digests" | base64 -d > ".gkm/digests-$stage.json"
6001
+ args+=(--digests-file ".gkm/digests-$stage.json")
6002
+ fi
6003
+ ${pm.exec} gkm compose "\${args[@]}"
6004
+ REMOTE
6005
+ env:
6006
+ SSH_KEY: \${{ secrets.DEPLOY_SSH_KEY }}
6007
+ KNOWN_HOSTS: \${{ vars.DEPLOY_KNOWN_HOSTS }}
6008
+ DEPLOY_HOST: \${{ vars.DEPLOY_HOST }}
6009
+ DEPLOY_USER: \${{ vars.DEPLOY_USER }}
6010
+ DEPLOY_PATH: \${{ vars.DEPLOY_PATH }}
6011
+ STAGE: \${{ matrix.stage }}
6012
+ SHA: \${{ steps.commit.outputs.sha }}
6013
+ `;
6014
+ }
6015
+ /**
6016
+ * Dokploy and SST: `gkm deploy` builds, releases and health-checks a stage in
6017
+ * one step, from a checkout, so there is no separate build job — the stages
6018
+ * job's `deploy` list is the matrix, and `build` goes unused.
6019
+ */
6020
+ function targetDeploy(options) {
6021
+ const pm = tooling(options);
6022
+ const sst = options.deployTarget === "sst";
6023
+ const credentials = sst ? `
6024
+ # An IAM role GitHub assumes through OIDC — no long-lived AWS keys. Set
6025
+ # AWS_ROLE_ARN as a variable on each environment.
6026
+ - uses: aws-actions/configure-aws-credentials@v4
6027
+ with:
6028
+ role-to-assume: \${{ vars.AWS_ROLE_ARN }}
6029
+ aws-region: ${options.region}
6030
+ ` : "";
6031
+ const keyDir = `~/.gkm/keys/${projectKey({ name: options.name })}`;
6032
+ const stageSecrets = sst ? credentials : `
6033
+ # The stage's secrets are kept in the encrypted .gkm/secrets/<stage>.json,
6034
+ # and each environment holds its key as GKM_SECRETS_KEY. .gkm/ is
6035
+ # gitignored, so a checkout has the key but not the file: set
6036
+ # secrets.store in gkm.config.ts to a store CI can reach before deploying
6037
+ # from here.
6038
+ # Owner-only, as the CLI writes its own keys. The stage reaches the shell
6039
+ # as a variable, never pasted into the script: on a manual run it is
6040
+ # whatever was typed.
6041
+ - name: Stage secrets key
6042
+ run: |
6043
+ mkdir -p ${keyDir} && chmod 700 ${keyDir}
6044
+ (umask 077 && printf '%s' "$KEY" > ${keyDir}/"$STAGE".key)
6045
+ env:
6046
+ KEY: \${{ secrets.GKM_SECRETS_KEY }}
6047
+ STAGE: \${{ matrix.stage }}
6048
+ `;
6049
+ const deployEnv = sst ? "" : `
6050
+ DOKPLOY_API_TOKEN: \${{ secrets.DOKPLOY_API_TOKEN }}
6051
+ DOKPLOY_ENDPOINT: \${{ vars.DOKPLOY_ENDPOINT }}`;
6052
+ return `${deployHeader(false)}
6053
+ jobs:
6054
+ ${stagesJob(options)}
5799
6055
  deploy:
5800
6056
  name: Deploy \${{ matrix.stage }}
5801
6057
  needs: stages
5802
- if: needs.stages.outputs.stages != '[]'
6058
+ if: needs.stages.outputs.has-deploy == 'true'
5803
6059
  runs-on: ubuntu-latest
5804
6060
  strategy:
5805
6061
  fail-fast: false
5806
6062
  matrix:
5807
- stage: \${{ fromJSON(needs.stages.outputs.stages) }}
6063
+ stage: \${{ fromJSON(needs.stages.outputs.deploy) }}
5808
6064
  # One GitHub environment per stage: its secrets and variables, and any
5809
6065
  # approval rule you put on a protected one.
5810
6066
  environment: \${{ matrix.stage }}
5811
6067
  concurrency:
5812
6068
  group: deploy-\${{ matrix.stage }}
5813
6069
  cancel-in-progress: false
6070
+ permissions:
6071
+ contents: read${sst ? "\n id-token: write" : ""}
5814
6072
  steps:
6073
+ # The event's own commit — a release's tag, the push — or the ref a
6074
+ # manual run names.
5815
6075
  - uses: actions/checkout@v4
6076
+ with:
6077
+ ref: \${{ inputs.ref }}
5816
6078
 
5817
6079
  ${pm.setup}
5818
6080
  - name: Install
@@ -5826,6 +6088,9 @@ ${stageSecrets}
5826
6088
  STAGE: \${{ matrix.stage }}${deployEnv}
5827
6089
  `;
5828
6090
  }
6091
+ function deploy(options) {
6092
+ return options.deployTarget === "compose" ? composeDeploy(options) : targetDeploy(options);
6093
+ }
5829
6094
  function generateGithubFiles(options) {
5830
6095
  const files = [
5831
6096
  {
@@ -6666,6 +6931,11 @@ export type ListUsersResponse = z.infer<typeof ListUsersResponseSchema>;
6666
6931
  const perStage = (options, command) => Object.fromEntries(options.stages.deployed.map((stage) => [`deploy:${stage}`, command(stage)]));
6667
6932
  function deployPackage(options) {
6668
6933
  switch (options.deployTarget) {
6934
+ case "compose": return {
6935
+ scripts: perStage(options, (stage) => `gkm compose --stage ${stage}`),
6936
+ dependencies: {},
6937
+ devDependencies: {}
6938
+ };
6669
6939
  case "dokploy": return {
6670
6940
  scripts: perStage(options, (stage) => `gkm deploy --stage ${stage}`),
6671
6941
  dependencies: {},
@@ -7175,6 +7445,13 @@ ${workspaceConstructsGlobs(options.routesStructure, dirname(options.apiPath)).ma
7175
7445
  * `gkm build` builds for, and which backends a cache or a broker resolves to.
7176
7446
  */
7177
7447
  function deployBlock(options) {
7448
+ if (options.deployTarget === "compose") return `
7449
+ // \`gkm compose --stage <stage>\` runs a stage as one Docker Compose stack on
7450
+ // the machine it runs on. CI builds every image and pushes it here; the
7451
+ // server pulls exactly those. On ghcr.io the owner is the repository's —
7452
+ // the user or organisation whose packages the workflow's token can write.
7453
+ deploy: { default: 'compose', registry: '${options.registry}' },
7454
+ `;
7178
7455
  if (options.deployTarget !== "sst") return "";
7179
7456
  return `
7180
7457
  // \`gkm deploy\` runs \`sst deploy\` on the manifest \`gkm build\` writes to
@@ -9751,6 +10028,11 @@ const deployTargetChoices = [
9751
10028
  value: "dokploy",
9752
10029
  description: "Deploy to Dokploy (Docker-based hosting)"
9753
10030
  },
10031
+ {
10032
+ title: "Server (Docker Compose)",
10033
+ value: "compose",
10034
+ description: "One Docker Compose stack per stage on your own server; CI builds the images"
10035
+ },
9754
10036
  {
9755
10037
  title: "AWS (SST)",
9756
10038
  value: "sst",
@@ -9888,6 +10170,13 @@ async function initCommand(projectName, options = {}) {
9888
10170
  message: "AWS region (e.g. eu-west-1):",
9889
10171
  validate: (value) => AWS_REGION.test(value.trim()) || "An AWS region, like eu-west-1"
9890
10172
  },
10173
+ {
10174
+ type: (_prev, values) => !options.yes && !options.registry && (options.deploy ?? values.deployTarget) === "compose" ? "text" : null,
10175
+ name: "registry",
10176
+ message: "Container registry CI pushes to (e.g. ghcr.io/<your GitHub owner>):",
10177
+ initial: (_prev, values) => defaultRegistry(projectName || options.name || values.name),
10178
+ validate: (value) => value.trim().length > 0 || "A registry, like ghcr.io/acme"
10179
+ },
9891
10180
  {
9892
10181
  type: options.yes || options.stages ? null : "text",
9893
10182
  name: "deployedStages",
@@ -9989,6 +10278,7 @@ async function initCommand(projectName, options = {}) {
9989
10278
  packageManager: pkgManager,
9990
10279
  deployTarget,
9991
10280
  stages: resolveStages(options, answers),
10281
+ ...deployTarget === "compose" ? { registry: options.registry ?? answers.registry?.trim() ?? defaultRegistry(name) } : {},
9992
10282
  ...deployTarget === "sst" ? { region: options.region ?? (options.yes ? "eu-west-1" : answers.region?.trim()) } : {},
9993
10283
  constructs,
9994
10284
  frontendFramework
@@ -10160,6 +10450,13 @@ function printNextSteps(projectName, options, pkgManager) {
10160
10450
  for (const stage of deployed) console.log(` ${getRunCommand(pkgManager, `deploy:${stage}`)}`);
10161
10451
  console.log("");
10162
10452
  }
10453
+ if (options.deployTarget === "compose") {
10454
+ console.log("🚀 Deployment (Docker Compose, on your server):");
10455
+ console.log(` Images are pushed to ${options.registry}; .github/workflows/deploy.yml builds them`);
10456
+ console.log(" and runs gkm compose on the server over SSH. See the comments in it for the");
10457
+ console.log(" environment variables and secrets each stage needs.");
10458
+ console.log("");
10459
+ }
10163
10460
  if (options.deployTarget === "sst") {
10164
10461
  console.log("🚀 Deployment (AWS, through SST):");
10165
10462
  for (const stage of deployed) console.log(` ${getRunCommand(pkgManager, `deploy:${stage}`)} # gkm deploy: build, sst deploy, health checks`);
@@ -10199,6 +10496,14 @@ var UnknownDeployTarget = class extends Error {
10199
10496
  this.name = "UnknownDeployTarget";
10200
10497
  }
10201
10498
  };
10499
+ /**
10500
+ * Where a compose deploy's images go when nobody said: GitHub's registry,
10501
+ * under an owner named after the project. The generated config says to change
10502
+ * it to the repository's owner, whose packages the workflow's token can write.
10503
+ */
10504
+ function defaultRegistry(name) {
10505
+ return `ghcr.io/${(name ?? "my-org").toLowerCase()}`;
10506
+ }
10202
10507
  /** `--region` that is not shaped like one, e.g. `europe` for `eu-west-1`. */
10203
10508
  var NotAnAwsRegion = class extends Error {
10204
10509
  region;
@@ -10903,7 +11208,7 @@ function testDatabases(reconciled) {
10903
11208
  * starts without the reference data the app needs.
10904
11209
  */
10905
11210
  async function migrateTestStage(workspace, env) {
10906
- const { migrateAndSeed } = await import("./migrate-CCejlgU0.mjs");
11211
+ const { migrateAndSeed } = await import("./migrate-1dGEzuJ-.mjs");
10907
11212
  const { discover } = await import("./discover-BPelzeGW.mjs").then((n) => n.i);
10908
11213
  const sources = {};
10909
11214
  const manifest = await discover({
@@ -11239,7 +11544,7 @@ const program = new Command();
11239
11544
  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", () => {
11240
11545
  if (program.opts().debug);
11241
11546
  });
11242
- 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) => {
11547
+ 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) => {
11243
11548
  try {
11244
11549
  const globalOptions = program.opts();
11245
11550
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
@@ -11347,7 +11652,7 @@ program.command("migrate").description("Apply each database construct's migratio
11347
11652
  try {
11348
11653
  const globalOptions = program.opts();
11349
11654
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11350
- const { migrateCommand } = await import("./migrate-CCejlgU0.mjs");
11655
+ const { migrateCommand } = await import("./migrate-1dGEzuJ-.mjs");
11351
11656
  await migrateCommand({
11352
11657
  ...options,
11353
11658
  ...construct ? { construct } : {}
@@ -11361,7 +11666,7 @@ program.command("seed").description("Migrate, then run each database construct's
11361
11666
  try {
11362
11667
  const globalOptions = program.opts();
11363
11668
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11364
- const { seedCommand } = await import("./migrate-CCejlgU0.mjs");
11669
+ const { seedCommand } = await import("./migrate-1dGEzuJ-.mjs");
11365
11670
  await seedCommand({
11366
11671
  ...options,
11367
11672
  ...construct ? { construct } : {}
@@ -11375,7 +11680,7 @@ program.command("migration").description("Write a construct's next migration: an
11375
11680
  try {
11376
11681
  const globalOptions = program.opts();
11377
11682
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11378
- const { migrationCommand } = await import("./migrate-CCejlgU0.mjs");
11683
+ const { migrationCommand } = await import("./migrate-1dGEzuJ-.mjs");
11379
11684
  await migrationCommand({
11380
11685
  construct,
11381
11686
  ...name ? { name } : {}
@@ -11483,7 +11788,7 @@ program.command("secrets:add").description("Build the keys a stage must be given
11483
11788
  try {
11484
11789
  const globalOptions = program.opts();
11485
11790
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11486
- const { secretsAddCommand } = await import("./add-DnYHJOap.mjs");
11791
+ const { secretsAddCommand } = await import("./add-DkBEZ5_s.mjs");
11487
11792
  await secretsAddCommand(options);
11488
11793
  } catch (error) {
11489
11794
  console.error(formatError(error));
@@ -11494,7 +11799,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
11494
11799
  try {
11495
11800
  const globalOptions = program.opts();
11496
11801
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11497
- const { encryptionRotateCommand } = await import("./encryption-DPFljXtM.mjs");
11802
+ const { encryptionRotateCommand } = await import("./encryption-0eo9aY_H.mjs");
11498
11803
  await encryptionRotateCommand(construct, options);
11499
11804
  } catch (error) {
11500
11805
  console.error(formatError(error));
@@ -11505,7 +11810,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
11505
11810
  try {
11506
11811
  const globalOptions = program.opts();
11507
11812
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11508
- const { encryptionRetireCommand } = await import("./encryption-DPFljXtM.mjs");
11813
+ const { encryptionRetireCommand } = await import("./encryption-0eo9aY_H.mjs");
11509
11814
  await encryptionRetireCommand(construct, key, options);
11510
11815
  } catch (error) {
11511
11816
  console.error(formatError(error));
@@ -11548,7 +11853,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
11548
11853
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11549
11854
  const { loadWorkspaceConfig } = await import("./config.mjs");
11550
11855
  const { reconcileMissingSecrets } = await import("./reconcile--I7V4Klj.mjs");
11551
- const { secretsStoreFor } = await import("./seeds-DbyqaXW4.mjs").then((n) => n._n);
11856
+ const { secretsStoreFor } = await import("./seeds-BoKQ74Od.mjs").then((n) => n._n);
11552
11857
  const { workspace } = await loadWorkspaceConfig();
11553
11858
  const stage = options.stage ?? workspace.stages.local;
11554
11859
  const store = await secretsStoreFor(workspace, stage);
@@ -11557,7 +11862,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
11557
11862
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
11558
11863
  process.exit(1);
11559
11864
  }
11560
- const { derivedContainers } = await import("./seeds-DbyqaXW4.mjs").then((n) => n.X);
11865
+ const { derivedContainers } = await import("./seeds-BoKQ74Od.mjs").then((n) => n.X);
11561
11866
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
11562
11867
  if (!result) {
11563
11868
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -11575,7 +11880,7 @@ program.command("secrets:migrate").description("Copy a deployed stage's secrets
11575
11880
  try {
11576
11881
  const globalOptions = program.opts();
11577
11882
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11578
- const { secretsMigrateCommand } = await import("./migrate-Cd3kcI7o.mjs");
11883
+ const { secretsMigrateCommand } = await import("./migrate-C14NNbFx.mjs");
11579
11884
  await secretsMigrateCommand(options);
11580
11885
  } catch (error) {
11581
11886
  console.error(formatError(error));
@@ -11583,7 +11888,7 @@ program.command("secrets:migrate").description("Copy a deployed stage's secrets
11583
11888
  }
11584
11889
  });
11585
11890
  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) => {
11586
- const { deployCli } = await import("./cli-qMszGvDu.mjs");
11891
+ const { deployCli } = await import("./cli-BgDd4cZB.mjs");
11587
11892
  const globalOptions = program.opts();
11588
11893
  const code = await deployCli({
11589
11894
  cwd: resolve(globalOptions.cwd ?? process.cwd()),
@@ -11600,7 +11905,7 @@ program.command("deploy").description("Deploy a stage through its target").optio
11600
11905
  });
11601
11906
  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) => {
11602
11907
  try {
11603
- const { rollbackStage } = await import("./rollback-DXU6cr7q.mjs").then((n) => n.a);
11908
+ const { rollbackStage } = await import("./rollback-CwVhG_xa.mjs").then((n) => n.a);
11604
11909
  const { terminalCredentials } = await import("./terminal-BdvrmkaC.mjs");
11605
11910
  const globalOptions = program.opts();
11606
11911
  await rollbackStage({
@@ -11636,13 +11941,24 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
11636
11941
  try {
11637
11942
  const globalOptions = program.opts();
11638
11943
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11639
- const { deployGithubCommand } = await import("./github-DL4zwJsR.mjs");
11944
+ const { deployGithubCommand } = await import("./github-HOfOpFI_.mjs");
11640
11945
  await deployGithubCommand(options);
11641
11946
  } catch (error) {
11642
11947
  console.error(formatError(error));
11643
11948
  process.exit(1);
11644
11949
  }
11645
11950
  });
11951
+ 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) => {
11952
+ try {
11953
+ const globalOptions = program.opts();
11954
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11955
+ const { stagesCommand } = await import("./stages-CHF9RFog.mjs");
11956
+ await stagesCommand(options);
11957
+ } catch (error) {
11958
+ console.error(formatError(error));
11959
+ process.exit(1);
11960
+ }
11961
+ });
11646
11962
  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) => {
11647
11963
  try {
11648
11964
  const globalOptions = program.opts();