@geekmidas/cli 10.0.0-alpha.60 → 10.0.0-alpha.62

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 (151) hide show
  1. package/dist/{HostingerProvider-DgzKa_Tj.mjs → HostingerProvider-B92qH4dC.mjs} +2 -2
  2. package/dist/{HostingerProvider-DgzKa_Tj.mjs.map → HostingerProvider-B92qH4dC.mjs.map} +1 -1
  3. package/dist/{HostingerProvider-BTZd2u0f.cjs → HostingerProvider-DyD1Hin8.cjs} +2 -2
  4. package/dist/{HostingerProvider-BTZd2u0f.cjs.map → HostingerProvider-DyD1Hin8.cjs.map} +1 -1
  5. package/dist/{LegacyStateStore-CToa2hga.mjs → LegacyStateStore-A8d2GdT7.mjs} +2 -2
  6. package/dist/{LegacyStateStore-CToa2hga.mjs.map → LegacyStateStore-A8d2GdT7.mjs.map} +1 -1
  7. package/dist/{LegacyStateStore-CidlPT0N.cjs → LegacyStateStore-UHJEBLdn.cjs} +2 -2
  8. package/dist/{LegacyStateStore-CidlPT0N.cjs.map → LegacyStateStore-UHJEBLdn.cjs.map} +1 -1
  9. package/dist/{LocalStateStore-BFLiOocU.mjs → LocalStateStore-B51rnfgn.mjs} +2 -2
  10. package/dist/{LocalStateStore-BFLiOocU.mjs.map → LocalStateStore-B51rnfgn.mjs.map} +1 -1
  11. package/dist/{LocalStateStore-Dpuqka1N.cjs → LocalStateStore-COW3Ldwa.cjs} +2 -2
  12. package/dist/{LocalStateStore-Dpuqka1N.cjs.map → LocalStateStore-COW3Ldwa.cjs.map} +1 -1
  13. package/dist/{OpenApiTsGenerator-BTuUumsV.cjs → OpenApiTsGenerator-BSrgw7B0.cjs} +31 -2
  14. package/dist/OpenApiTsGenerator-BSrgw7B0.cjs.map +1 -0
  15. package/dist/{OpenApiTsGenerator-CvVr1Ilz.mjs → OpenApiTsGenerator-q5B0z8Xg.mjs} +31 -2
  16. package/dist/OpenApiTsGenerator-q5B0z8Xg.mjs.map +1 -0
  17. package/dist/{S3StateStore-aaGkjcxO.mjs → S3StateStore-CJewPhgz.mjs} +2 -2
  18. package/dist/{S3StateStore-aaGkjcxO.mjs.map → S3StateStore-CJewPhgz.mjs.map} +1 -1
  19. package/dist/{S3StateStore-cnm813Xn.cjs → S3StateStore-WUJGdRNL.cjs} +2 -2
  20. package/dist/{S3StateStore-cnm813Xn.cjs.map → S3StateStore-WUJGdRNL.cjs.map} +1 -1
  21. package/dist/{SSMStateStore-DwaGprVX.cjs → SSMStateStore-Zejt42V8.cjs} +2 -2
  22. package/dist/{SSMStateStore-DwaGprVX.cjs.map → SSMStateStore-Zejt42V8.cjs.map} +1 -1
  23. package/dist/{SSMStateStore-BllaJKGo.mjs → SSMStateStore-iK5UKp9d.mjs} +2 -2
  24. package/dist/{SSMStateStore-BllaJKGo.mjs.map → SSMStateStore-iK5UKp9d.mjs.map} +1 -1
  25. package/dist/{bundler-CzXZPSBJ.cjs → bundler-CfH9frOS.cjs} +2 -2
  26. package/dist/{bundler-CzXZPSBJ.cjs.map → bundler-CfH9frOS.cjs.map} +1 -1
  27. package/dist/{bundler-DJVR4QlW.mjs → bundler-DN5r4x5b.mjs} +2 -2
  28. package/dist/{bundler-DJVR4QlW.mjs.map → bundler-DN5r4x5b.mjs.map} +1 -1
  29. package/dist/{cli-D_qTl20Z.cjs → cli-DOAUp80L.cjs} +12 -16
  30. package/dist/cli-DOAUp80L.cjs.map +1 -0
  31. package/dist/{cli-DzR1hpum.mjs → cli-DYwxmfsW.mjs} +10 -14
  32. package/dist/cli-DYwxmfsW.mjs.map +1 -0
  33. package/dist/{config-DXnkCEq0.cjs → config-BYh9S0cZ.cjs} +2 -2
  34. package/dist/config-BYh9S0cZ.cjs.map +1 -0
  35. package/dist/{config-C5-EEdmr.mjs → config-Bf4-bROQ.mjs} +2 -2
  36. package/dist/config-Bf4-bROQ.mjs.map +1 -0
  37. package/dist/config.cjs +2 -2
  38. package/dist/config.d.cts +2 -2
  39. package/dist/config.d.mts +2 -2
  40. package/dist/config.mjs +2 -2
  41. package/dist/define-CHBIEc5j.mjs +94 -0
  42. package/dist/define-CHBIEc5j.mjs.map +1 -0
  43. package/dist/define-Cv_QvDrg.cjs +141 -0
  44. package/dist/define-Cv_QvDrg.cjs.map +1 -0
  45. package/dist/deploy/public.cjs +28 -13
  46. package/dist/deploy/public.d.cts +38 -315
  47. package/dist/deploy/public.d.cts.map +1 -1
  48. package/dist/deploy/public.d.mts +38 -315
  49. package/dist/deploy/public.d.mts.map +1 -1
  50. package/dist/deploy/public.mjs +8 -3
  51. package/dist/{deploy-D6Q09OkA.mjs → deploy-B7jvUORo.mjs} +155 -682
  52. package/dist/deploy-B7jvUORo.mjs.map +1 -0
  53. package/dist/deploy-Cg9fae7a.mjs +133 -0
  54. package/dist/deploy-Cg9fae7a.mjs.map +1 -0
  55. package/dist/deploy-D8Qr-Xto.cjs +138 -0
  56. package/dist/deploy-D8Qr-Xto.cjs.map +1 -0
  57. package/dist/{deploy-0fBVeWO-.cjs → deploy-DSaCYvcO.cjs} +182 -727
  58. package/dist/deploy-DSaCYvcO.cjs.map +1 -0
  59. package/dist/dokploy-CD9PY-3D.mjs +32 -0
  60. package/dist/dokploy-CD9PY-3D.mjs.map +1 -0
  61. package/dist/dokploy-Ey2foJLv.cjs +32 -0
  62. package/dist/dokploy-Ey2foJLv.cjs.map +1 -0
  63. package/dist/{encryption-aDA2AzVx.mjs → encryption-CVwvGbsL.mjs} +4 -4
  64. package/dist/{encryption-aDA2AzVx.mjs.map → encryption-CVwvGbsL.mjs.map} +1 -1
  65. package/dist/{encryption-CML4UaPi.cjs → encryption-ChBk3hTj.cjs} +4 -4
  66. package/dist/{encryption-CML4UaPi.cjs.map → encryption-ChBk3hTj.cjs.map} +1 -1
  67. package/dist/{github-DmCj2u95.mjs → github-BCB3_LBX.mjs} +3 -3
  68. package/dist/{github-DmCj2u95.mjs.map → github-BCB3_LBX.mjs.map} +1 -1
  69. package/dist/{github-Cq57poPj.cjs → github-OUgPDlqg.cjs} +3 -3
  70. package/dist/{github-Cq57poPj.cjs.map → github-OUgPDlqg.cjs.map} +1 -1
  71. package/dist/{index-UiWCaGzU.d.cts → index-BtXDZQuF.d.cts} +2 -2
  72. package/dist/{index-Da1-oa9W.d.mts.map → index-BtXDZQuF.d.cts.map} +1 -1
  73. package/dist/{index-Da1-oa9W.d.mts → index-DrKzpHl-.d.mts} +2 -2
  74. package/dist/{index-UiWCaGzU.d.cts.map → index-DrKzpHl-.d.mts.map} +1 -1
  75. package/dist/index.cjs +1728 -146
  76. package/dist/index.cjs.map +1 -1
  77. package/dist/index.mjs +1719 -153
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/migrate-BoHMqdCu.mjs +265 -0
  80. package/dist/migrate-BoHMqdCu.mjs.map +1 -0
  81. package/dist/migrate-dnaFMvOm.cjs +270 -0
  82. package/dist/migrate-dnaFMvOm.cjs.map +1 -0
  83. package/dist/openapi.cjs +2 -2
  84. package/dist/openapi.d.cts +1 -1
  85. package/dist/openapi.d.mts +1 -1
  86. package/dist/openapi.mjs +2 -2
  87. package/dist/{ready-CIHpnUc5.mjs → ready-DQIy21W5.mjs} +2 -2
  88. package/dist/{ready-CIHpnUc5.mjs.map → ready-DQIy21W5.mjs.map} +1 -1
  89. package/dist/{ready-CCLIAkwR.cjs → ready-XlNx-Gbo.cjs} +2 -2
  90. package/dist/{ready-CCLIAkwR.cjs.map → ready-XlNx-Gbo.cjs.map} +1 -1
  91. package/dist/reconcile/public.d.cts +1 -1
  92. package/dist/reconcile/public.d.mts +1 -1
  93. package/dist/resolve-CFCIkm7m.mjs +170 -0
  94. package/dist/resolve-CFCIkm7m.mjs.map +1 -0
  95. package/dist/resolve-wbv66HjD.cjs +217 -0
  96. package/dist/resolve-wbv66HjD.cjs.map +1 -0
  97. package/dist/runtime-CMwtq0AL.cjs +163 -0
  98. package/dist/runtime-CMwtq0AL.cjs.map +1 -0
  99. package/dist/runtime-CXPlkZzs.d.mts +90 -0
  100. package/dist/runtime-CXPlkZzs.d.mts.map +1 -0
  101. package/dist/runtime-DbakJS8u.mjs +134 -0
  102. package/dist/runtime-DbakJS8u.mjs.map +1 -0
  103. package/dist/runtime-Dn_n-29S.d.cts +90 -0
  104. package/dist/runtime-Dn_n-29S.d.cts.map +1 -0
  105. package/dist/{sniffer-DIv12ncU.cjs → sniffer-BsfPa-GA.cjs} +2581 -2170
  106. package/dist/sniffer-BsfPa-GA.cjs.map +1 -0
  107. package/dist/{sniffer-BM8fpCas.mjs → sniffer-DHE2Nafd.mjs} +2428 -2185
  108. package/dist/sniffer-DHE2Nafd.mjs.map +1 -0
  109. package/dist/target/public.cjs +15 -0
  110. package/dist/target/public.d.cts +28 -0
  111. package/dist/target/public.d.cts.map +1 -0
  112. package/dist/target/public.d.mts +28 -0
  113. package/dist/target/public.d.mts.map +1 -0
  114. package/dist/target/public.mjs +4 -0
  115. package/dist/terminal-6oL0vANs.cjs +576 -0
  116. package/dist/terminal-6oL0vANs.cjs.map +1 -0
  117. package/dist/terminal-ByZNjuig.mjs +517 -0
  118. package/dist/terminal-ByZNjuig.mjs.map +1 -0
  119. package/dist/{types-efNLu9xu.d.cts → types-CfLKGfC_.d.mts} +624 -34
  120. package/dist/types-CfLKGfC_.d.mts.map +1 -0
  121. package/dist/{types-Bp1mVxG0.d.mts → types-DIA7Li-J.d.cts} +626 -34
  122. package/dist/types-DIA7Li-J.d.cts.map +1 -0
  123. package/dist/vitest.cjs +1 -1
  124. package/dist/vitest.mjs +1 -1
  125. package/dist/workspace/index.cjs +1 -1
  126. package/dist/workspace/index.d.cts +3 -3
  127. package/dist/workspace/index.d.mts +3 -3
  128. package/dist/workspace/index.mjs +1 -1
  129. package/dist/{workspace-B64Xk1Mb.cjs → workspace-B6zQO6RY.cjs} +229 -23
  130. package/dist/workspace-B6zQO6RY.cjs.map +1 -0
  131. package/dist/{workspace-Bp1xVaOz.mjs → workspace-BFe57es7.mjs} +229 -23
  132. package/dist/workspace-BFe57es7.mjs.map +1 -0
  133. package/package.json +23 -13
  134. package/dist/OpenApiTsGenerator-BTuUumsV.cjs.map +0 -1
  135. package/dist/OpenApiTsGenerator-CvVr1Ilz.mjs.map +0 -1
  136. package/dist/cli-D_qTl20Z.cjs.map +0 -1
  137. package/dist/cli-DzR1hpum.mjs.map +0 -1
  138. package/dist/config-C5-EEdmr.mjs.map +0 -1
  139. package/dist/config-DXnkCEq0.cjs.map +0 -1
  140. package/dist/deploy-0fBVeWO-.cjs.map +0 -1
  141. package/dist/deploy-D6Q09OkA.mjs.map +0 -1
  142. package/dist/migrate-BTqIflwc.cjs +0 -743
  143. package/dist/migrate-BTqIflwc.cjs.map +0 -1
  144. package/dist/migrate-B_6KofUo.mjs +0 -736
  145. package/dist/migrate-B_6KofUo.mjs.map +0 -1
  146. package/dist/sniffer-BM8fpCas.mjs.map +0 -1
  147. package/dist/sniffer-DIv12ncU.cjs.map +0 -1
  148. package/dist/types-Bp1mVxG0.d.mts.map +0 -1
  149. package/dist/types-efNLu9xu.d.cts.map +0 -1
  150. package/dist/workspace-B64Xk1Mb.cjs.map +0 -1
  151. package/dist/workspace-Bp1xVaOz.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,32 +1,34 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
3
- import { $ as loginCommand, B as providerOf, C as run$1, D as constructGlobs, E as backendsOf$1, F as quoteIdentifier, G as withOwningTsconfigJsx, H as describeServices, I as pgClient, J as findAvailablePort, K as createCredentialsPreload, M as forgetState, O as derivedContainers, P as postgresDatabaseNames, Q as prepareEntryCredentials, R as cacheBackendFor, S as keyFingerprint, T as FAKE_ENV, U as fakeKey, W as primaryPortKey, X as loadEnvFiles, Y as isPortAvailable, Z as loadSecretsForApp, a as dockerCommand, at as getDokployToken, et as logoutCommand, g as createStateStore, it as getDokployRegistryId, j as LOCAL_CA_PATH, k as reconcileWorkspace, n as sniffAppEnvironment, nt as whoamiCommand, q as createEntryWrapper, rt as getDokployCredentials, v as DokployApi } from "./sniffer-BM8fpCas.mjs";
3
+ import { $ as quoteIdentifier, At as getDokployCredentials, B as run$1, Ct as loadEnvFiles, Dt as logoutCommand, Et as loginCommand, G as derivedContainers, H as FAKE_ENV, I as withGeneratedSecrets, J as LOCAL_CA_PATH, K as reconcileWorkspace, Mt as getDokployToken, P as createStateStore, Q as postgresStatements, St as isPortAvailable, Tt as prepareEntryCredentials, U as backendsOf$1, V as runOutput, W as constructGlobs, X as applyPostgres, Y as forgetState, Z as postgresDatabaseNames, _t as primaryPortKey, at as localRolePassword, bt as createEntryWrapper, c as dockerCommand, ct as hostFor, d as generateDockerignore, dt as providerOf, et as composeFor, f as generateSlimDockerfile, g as createEmptyState, gt as portKeys, h as resolveHost, ht as fakeKey, it as envFor, jt as getDokployRegistryId, kt as whoamiCommand, l as siteDockerfile, lt as cacheBackendFor, m as isMainFrontendApp, mt as describeServices, n as sniffAppEnvironment, nt as appEnvKeys, ot as planFor, p as validateImageRef, pt as DEFAULT_IMAGES, r as DokployApi, rt as networkEnv, s as appPackageName$1, tt as pgClient, u as detectPackageManager$2, vt as withOwningTsconfigJsx, wt as loadSecretsForApp, xt as findAvailablePort, yt as createCredentialsPreload, z as keyFingerprint } from "./sniffer-DHE2Nafd.mjs";
4
4
  import { i as ConstructGenerator, t as discover } from "./discover-FrYvzkeC.mjs";
5
- import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-C5-EEdmr.mjs";
6
- import { E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, k as stageProblems, o as getAppGkmConfig, t as allConstructGlobs } from "./workspace-Bp1xVaOz.mjs";
7
- import { a as withCustomSecret, i as toEmbeddableSecrets, n as maskPassword } from "./storage-BzaUxmqe.mjs";
8
- import { a as getKeyPath, i as FileSecretsStore, n as secretsStoreFor, s as projectKey } from "./store-BS8A7NcA.mjs";
9
- import { i as telemetryFor, n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-CvVr1Ilz.mjs";
5
+ import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-Bf4-bROQ.mjs";
6
+ import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, k as stageProblems, o as getAppGkmConfig, t as allConstructGlobs } from "./workspace-BFe57es7.mjs";
7
+ import { a as withCustomSecret, i as toEmbeddableSecrets, n as maskPassword, t as initStageSecrets } from "./storage-BzaUxmqe.mjs";
8
+ import { a as getKeyPath, f as imageRef, i as FileSecretsStore, n as secretsStoreFor, s as projectKey, u as deployIdentity } from "./store-BS8A7NcA.mjs";
9
+ import { i as telemetryFor, n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-q5B0z8Xg.mjs";
10
10
  import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
11
+ import { n as DEFAULT_CACHE, r as DEFAULT_EVENTS } from "./fakes-C9RiT6XG.mjs";
11
12
  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";
12
- import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-CIHpnUc5.mjs";
13
+ import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-DQIy21W5.mjs";
13
14
  import { n as withCompute, r as withRoutes, t as manifestModule } from "./emit-C1e8qD08.mjs";
14
- import { t as LocalStateStore } from "./LocalStateStore-BFLiOocU.mjs";
15
+ import { t as LocalStateStore } from "./LocalStateStore-B51rnfgn.mjs";
15
16
  import { createRequire } from "node:module";
16
- import { basename, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
17
+ import { basename, dirname, extname, isAbsolute, join, parse, relative, resolve } from "node:path";
17
18
  import { Command } from "commander";
18
19
  import { existsSync, linkSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
19
- import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
20
+ import { appendFile, chmod, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
20
21
  import { homedir } from "node:os";
21
22
  import { promisify } from "node:util";
22
- import { canonicalId, kebabCase, migrationFolder, provideKey, schemeBase, serviceKey } from "@geekmidas/manifest";
23
+ import { DATABASE_FOLDERS, MIGRATIONS_ROOT, canonicalId, kebabCase, migrationFolder, migrationTargets, provideKey, provisionOrder, publicEnvFor, schemeBase, serviceKey } from "@geekmidas/manifest";
23
24
  import fg from "fast-glob";
24
25
  import { execFile, execSync, spawn } from "node:child_process";
25
26
  import chokidar from "chokidar";
26
27
  import { connect } from "node:net";
27
28
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
28
29
  import { pathToFileURL } from "node:url";
29
- import { parse as parse$1, parseDocument } from "yaml";
30
+ import pg from "pg";
31
+ import { parse as parse$1, parseDocument, stringify } from "yaml";
30
32
  import prompts from "prompts";
31
33
  import { createServer as createServer$1 } from "node:http";
32
34
  import { Readable } from "node:stream";
@@ -39,7 +41,7 @@ import { Function } from "@geekmidas/constructs/functions";
39
41
  import { Queue } from "@geekmidas/constructs/queue";
40
42
  import { Topic } from "@geekmidas/constructs/topic";
41
43
  //#region package.json
42
- var version = "10.0.0-alpha.60";
44
+ var version = "10.0.0-alpha.62";
43
45
  //#endregion
44
46
  //#region src/trust/index.ts
45
47
  /**
@@ -56,7 +58,7 @@ var version = "10.0.0-alpha.60";
56
58
  * worse trade than one that tells you what to type. Caddy's own `caddy trust`
57
59
  * draws the line in the same place.
58
60
  */
59
- const logger$7 = console;
61
+ const logger$8 = console;
60
62
  const run = promisify(execFile);
61
63
  /**
62
64
  * How each platform is told about a new authority.
@@ -108,15 +110,15 @@ async function trustCommand(options = {}) {
108
110
  const certificate = join(workspace.root, LOCAL_CA_PATH);
109
111
  if (!existsSync(certificate)) throw new NoLocalAuthority(certificate);
110
112
  const { describe, asks, steps } = installation(process.platform, certificate);
111
- logger$7.log(`\n🔐 Trusting the local authority in ${describe}`);
112
- logger$7.log(` ${certificate}\n`);
113
+ logger$8.log(`\n🔐 Trusting the local authority in ${describe}`);
114
+ logger$8.log(` ${certificate}\n`);
113
115
  if (options.dryRun) {
114
- for (const [command, args] of steps) logger$7.log(` ${command} ${args.join(" ")}`);
116
+ for (const [command, args] of steps) logger$8.log(` ${command} ${args.join(" ")}`);
115
117
  return;
116
118
  }
117
- logger$7.log(` ${asks}\n`);
119
+ logger$8.log(` ${asks}\n`);
118
120
  for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
119
- logger$7.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
121
+ logger$8.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
120
122
  }
121
123
  /**
122
124
  * Trust the authority if this machine lacks it and its owner agrees.
@@ -133,7 +135,7 @@ async function ensureTrusted(root, url, options = {}) {
133
135
  if (await isTrusted(url)) return;
134
136
  if (!options.assumeYes) {
135
137
  if (!process.stdin.isTTY) {
136
- logger$7.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
138
+ logger$8.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
137
139
  return;
138
140
  }
139
141
  if (await declined(root)) return;
@@ -145,7 +147,7 @@ async function ensureTrusted(root, url, options = {}) {
145
147
  });
146
148
  if (!install) {
147
149
  await recordDeclined(root);
148
- logger$7.log(" Skipped. Run \"gkm trust\" later.");
150
+ logger$8.log(" Skipped. Run \"gkm trust\" later.");
149
151
  return;
150
152
  }
151
153
  }
@@ -1310,7 +1312,7 @@ function closeFakes(served) {
1310
1312
  }
1311
1313
  //#endregion
1312
1314
  //#region src/exec/index.ts
1313
- const logger$6 = console;
1315
+ const logger$7 = console;
1314
1316
  /** `gkm exec` was given nothing to run. */
1315
1317
  var NoCommandSpecified = class extends Error {
1316
1318
  constructor() {
@@ -1333,17 +1335,17 @@ async function execCommand(commandArgs, options = {}) {
1333
1335
  const cwd = options.cwd ?? process.cwd();
1334
1336
  if (!commandArgs[0]) throw new NoCommandSpecified();
1335
1337
  const defaultEnv = loadEnvFiles(".env");
1336
- if (defaultEnv.loaded.length > 0) logger$6.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1338
+ if (defaultEnv.loaded.length > 0) logger$7.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1337
1339
  const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
1338
- if (appName) logger$6.log(`📦 App: ${appName}`);
1340
+ if (appName) logger$7.log(`📦 App: ${appName}`);
1339
1341
  const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
1340
- if (secretCount > 0) logger$6.log(`🔐 Loaded ${secretCount} secret(s)`);
1342
+ if (secretCount > 0) logger$7.log(`🔐 Loaded ${secretCount} secret(s)`);
1341
1343
  const preloadDir = join(cwd, ".gkm");
1342
1344
  await mkdir(preloadDir, { recursive: true });
1343
1345
  const preloadPath = join(preloadDir, "credentials-preload.mjs");
1344
1346
  await createCredentialsPreload(preloadPath, secretsJsonPath);
1345
1347
  const [cmd, ...args] = commandArgs;
1346
- logger$6.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
1348
+ logger$7.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
1347
1349
  const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
1348
1350
  const child = spawn(cmd, args, {
1349
1351
  cwd,
@@ -1357,7 +1359,7 @@ async function execCommand(commandArgs, options = {}) {
1357
1359
  const exitCode = await new Promise((resolve) => {
1358
1360
  child.on("close", (code) => resolve(code ?? 0));
1359
1361
  child.on("error", (error) => {
1360
- logger$6.error(`Failed to run command: ${error.message}`);
1362
+ logger$7.error(`Failed to run command: ${error.message}`);
1361
1363
  resolve(1);
1362
1364
  });
1363
1365
  });
@@ -1365,7 +1367,7 @@ async function execCommand(commandArgs, options = {}) {
1365
1367
  }
1366
1368
  //#endregion
1367
1369
  //#region src/dev/index.ts
1368
- const logger$5 = console;
1370
+ const logger$6 = console;
1369
1371
  /**
1370
1372
  * Normalize telescope configuration
1371
1373
  * @internal Exported for testing
@@ -1435,13 +1437,13 @@ async function devCommand(options) {
1435
1437
  if (options.subscribers === false) process.env[SUBSCRIBERS_ENV] = "off";
1436
1438
  if (options.entry) return entryDevCommand(options);
1437
1439
  const defaultEnv = loadEnvFiles(".env");
1438
- if (defaultEnv.loaded.length > 0) logger$5.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1440
+ if (defaultEnv.loaded.length > 0) logger$6.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1439
1441
  const appConfig = await loadAppConfig().catch((error) => {
1440
1442
  if (error instanceof NotInAnApp || error instanceof NotABackendApp) return;
1441
1443
  throw error;
1442
1444
  });
1443
1445
  if (!appConfig) {
1444
- logger$5.log("🔎 Reading gkm.config.ts and the constructs");
1446
+ logger$6.log("🔎 Reading gkm.config.ts and the constructs");
1445
1447
  const { workspace: everything } = await loadWorkspaceConfig();
1446
1448
  return workspaceDevCommand(everything, options);
1447
1449
  }
@@ -1454,12 +1456,12 @@ async function devCommand(options) {
1454
1456
  holder: holderOf
1455
1457
  });
1456
1458
  if (assigned.running.length > 0) throw new WorkspacePortsInUse(assigned.running);
1457
- for (const { app, from, to, holder } of assigned.moved) logger$5.log(`↪️ ${app}: ${from} is held by ${describeHolder(holder)}, which is not this workspace's — using ${to}`);
1459
+ for (const { app, from, to, holder } of assigned.moved) logger$6.log(`↪️ ${app}: ${from} is held by ${describeHolder(holder)}, which is not this workspace's — using ${to}`);
1458
1460
  const workspace = withAppPorts(appConfig.workspace, assigned.ports);
1459
1461
  const workspaceAppPort = assigned.ports[workspaceAppName] ?? appConfig.app.port;
1460
1462
  process.env[APP_TAG_ENV] = appTag(appConfig.workspaceRoot, workspaceAppName);
1461
1463
  if (appConfig.app.entry) {
1462
- logger$5.log(`📄 Using entry point: ${appConfig.app.entry}`);
1464
+ logger$6.log(`📄 Using entry point: ${appConfig.app.entry}`);
1463
1465
  return entryDevCommand({
1464
1466
  ...options,
1465
1467
  entry: appConfig.app.entry,
@@ -1469,8 +1471,8 @@ async function devCommand(options) {
1469
1471
  }
1470
1472
  if (config.env) {
1471
1473
  const { loaded, missing } = loadEnvFiles(config.env, appRoot);
1472
- if (loaded.length > 0) logger$5.log(`📦 Loaded env: ${loaded.join(", ")}`);
1473
- if (missing.length > 0) logger$5.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
1474
+ if (loaded.length > 0) logger$6.log(`📦 Loaded env: ${loaded.join(", ")}`);
1475
+ if (missing.length > 0) logger$6.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
1474
1476
  }
1475
1477
  const telescope = normalizeTelescopeConfig(config.telescope);
1476
1478
  const hooks = normalizeHooksConfig(config.hooks, appRoot);
@@ -1489,7 +1491,7 @@ async function devCommand(options) {
1489
1491
  bustCache,
1490
1492
  serveEmpty: true
1491
1493
  }));
1492
- logger$5.log(`🔨 Building ${workspaceAppName ?? "the app"}`);
1494
+ logger$6.log(`🔨 Building ${workspaceAppName ?? "the app"}`);
1493
1495
  const initial = await build();
1494
1496
  const runtime = config.runtime ?? "node";
1495
1497
  let secretsJsonPath;
@@ -1500,11 +1502,11 @@ async function devCommand(options) {
1500
1502
  const reconciled = await reconcileWorkspace(workspace, {
1501
1503
  stage: workspace.stages.local,
1502
1504
  ...options.fake ? { fake: true } : {},
1503
- progress: (message) => logger$5.log(message)
1505
+ progress: (message) => logger$6.log(message)
1504
1506
  });
1505
1507
  if (reconciled.services.length > 0) {
1506
- logger$5.log("🐳 Services");
1507
- for (const line of describeServices(reconciled.services)) logger$5.log(line);
1508
+ logger$6.log("🐳 Services");
1509
+ for (const line of describeServices(reconciled.services)) logger$6.log(line);
1508
1510
  }
1509
1511
  Object.assign(appSecrets, reconciled.env);
1510
1512
  const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && appKey(r.id) === workspaceAppName);
@@ -1571,11 +1573,11 @@ async function devCommand(options) {
1571
1573
  cwd: appRoot
1572
1574
  });
1573
1575
  watcher.on("error", (error) => {
1574
- logger$5.error("❌ Watcher error:", error);
1576
+ logger$6.error("❌ Watcher error:", error);
1575
1577
  });
1576
1578
  let rebuildTimeout = null;
1577
1579
  watcher.on("change", async (path) => {
1578
- logger$5.log(`🔄 ${path} changed — rebuilding`);
1580
+ logger$6.log(`🔄 ${path} changed — rebuilding`);
1579
1581
  if (rebuildTimeout) clearTimeout(rebuildTimeout);
1580
1582
  rebuildTimeout = setTimeout(async () => {
1581
1583
  try {
@@ -1584,8 +1586,8 @@ async function devCommand(options) {
1584
1586
  await devServer.restart();
1585
1587
  } catch (error) {
1586
1588
  const err = error;
1587
- logger$5.error(`❌ Rebuild failed: ${err.message}`);
1588
- if (err.stack) logger$5.error(err.stack);
1589
+ logger$6.error(`❌ Rebuild failed: ${err.message}`);
1590
+ if (err.stack) logger$6.error(err.stack);
1589
1591
  }
1590
1592
  }, 300);
1591
1593
  });
@@ -1593,13 +1595,13 @@ async function devCommand(options) {
1593
1595
  const shutdown = () => {
1594
1596
  if (isShuttingDown) return;
1595
1597
  isShuttingDown = true;
1596
- logger$5.log("\n🛑 Shutting down...");
1598
+ logger$6.log("\n🛑 Shutting down...");
1597
1599
  Promise.all([
1598
1600
  watcher.close(),
1599
1601
  devServer.stop(),
1600
1602
  discovery.leave()
1601
1603
  ]).catch((err) => {
1602
- logger$5.error("Error during shutdown:", err);
1604
+ logger$6.error("Error during shutdown:", err);
1603
1605
  }).finally(() => {
1604
1606
  process.exit(0);
1605
1607
  });
@@ -1803,13 +1805,13 @@ async function workspaceDevCommand(configured, options) {
1803
1805
  holder: holderOf
1804
1806
  });
1805
1807
  if (assigned.running.length > 0) throw new WorkspacePortsInUse(assigned.running);
1806
- for (const { app, from, to, holder } of assigned.moved) logger$5.log(`↪️ ${app}: ${from} is held by ${describeHolder(holder)}, which is not this workspace's — using ${to}`);
1808
+ for (const { app, from, to, holder } of assigned.moved) logger$6.log(`↪️ ${app}: ${from} is held by ${describeHolder(holder)}, which is not this workspace's — using ${to}`);
1807
1809
  const workspace = withAppPorts(configured, assigned.ports);
1808
1810
  const appCount = Object.keys(workspace.apps).length;
1809
1811
  const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
1810
1812
  const conflicts = checkPortConflicts(workspace);
1811
1813
  if (conflicts.length > 0) {
1812
- for (const conflict of conflicts) logger$5.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1814
+ for (const conflict of conflicts) logger$6.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1813
1815
  throw new Error("Port conflicts detected. Please assign unique ports to each app.");
1814
1816
  }
1815
1817
  if (frontendApps.length > 0) {
@@ -1818,10 +1820,10 @@ async function workspaceDevCommand(configured, options) {
1818
1820
  for (const result of validationResults) {
1819
1821
  if (!result.valid) {
1820
1822
  hasErrors = true;
1821
- logger$5.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1822
- for (const error of result.errors) logger$5.error(` • ${error}`);
1823
+ logger$6.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1824
+ for (const error of result.errors) logger$6.error(` • ${error}`);
1823
1825
  }
1824
- for (const warning of result.warnings) logger$5.warn(` ⚠️ ${result.appName}: ${warning}`);
1826
+ for (const warning of result.warnings) logger$6.warn(` ⚠️ ${result.appName}: ${warning}`);
1825
1827
  }
1826
1828
  if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
1827
1829
  }
@@ -1829,24 +1831,24 @@ async function workspaceDevCommand(configured, options) {
1829
1831
  const reconciled = await reconcileWorkspace(workspace, {
1830
1832
  stage: workspace.stages.local,
1831
1833
  ...options.fake ? { fake: true } : {},
1832
- progress: (message) => logger$5.log(message)
1834
+ progress: (message) => logger$6.log(message)
1833
1835
  });
1834
1836
  if (reconciled.services.length > 0) {
1835
- logger$5.log("🐳 Services");
1836
- for (const line of describeServices(reconciled.services)) logger$5.log(line);
1837
+ logger$6.log("🐳 Services");
1838
+ for (const line of describeServices(reconciled.services)) logger$6.log(line);
1837
1839
  }
1838
1840
  const secretsEnv = {
1839
1841
  ...rawSecrets,
1840
1842
  ...reconciled.env
1841
1843
  };
1842
1844
  const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
1843
- for (const { id, port } of fakes) logger$5.log(`🎭 ${id} fake: http://localhost:${port}`);
1844
- logger$5.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
1845
+ for (const { id, port } of fakes) logger$6.log(`🎭 ${id} fake: http://localhost:${port}`);
1846
+ logger$6.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
1845
1847
  if (options.migrate || options.seed) {
1846
- const { prepareDevDatabases } = await import("./migrate-B_6KofUo.mjs");
1848
+ const { prepareDevDatabases } = await import("./migrate-BoHMqdCu.mjs");
1847
1849
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1848
- } else await import("./migrate-B_6KofUo.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1849
- logger$5.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1850
+ } else await import("./migrate-BoHMqdCu.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1851
+ logger$6.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1850
1852
  });
1851
1853
  const appUrls = {};
1852
1854
  for (const resource of reconciled.plan.resources) {
@@ -1867,11 +1869,11 @@ async function workspaceDevCommand(configured, options) {
1867
1869
  else {
1868
1870
  const { filters, unpackaged } = turboFilters(workspace);
1869
1871
  turboFilter = filters.flatMap((name) => ["--filter", name]);
1870
- if (unpackaged.length > 0) logger$5.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
1872
+ if (unpackaged.length > 0) logger$6.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
1871
1873
  }
1872
1874
  const buildOrder = getAppBuildOrder(workspace);
1873
1875
  const width = Math.max(...buildOrder.map((name) => name.length));
1874
- logger$5.log(`\n${workspace.name}: ${appCount} app(s)`);
1876
+ logger$6.log(`\n${workspace.name}: ${appCount} app(s)`);
1875
1877
  for (const appName of buildOrder) {
1876
1878
  const app = workspace.apps[appName];
1877
1879
  if (!app) continue;
@@ -1879,14 +1881,14 @@ async function workspaceDevCommand(configured, options) {
1879
1881
  const own = reconciled.plan.resources.find((r) => r.kind === "mobile-app" && appKey(r.id) === appName);
1880
1882
  const scheme = own ? reconciled.env[own.envKey] : void 0;
1881
1883
  const device = Object.entries(reconciled.env).find(([key]) => key.endsWith("_DEVICE_URL"))?.[1];
1882
- logger$5.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
1884
+ logger$6.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
1883
1885
  continue;
1884
1886
  }
1885
1887
  const local = `http://localhost:${app.port}`;
1886
1888
  const url = Object.entries(appUrls).find(([id]) => appKey(id) === appName)?.[1];
1887
- logger$5.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
1889
+ logger$6.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
1888
1890
  }
1889
- logger$5.log("");
1891
+ logger$6.log("");
1890
1892
  const configFiles = [
1891
1893
  "gkm.config.ts",
1892
1894
  "gkm.config.js",
@@ -1934,7 +1936,7 @@ async function workspaceDevCommand(configured, options) {
1934
1936
  ...options.fake ? { [FAKE_ENV]: "1" } : {},
1935
1937
  [DISCOVERY_QUIET_ENV]: "1"
1936
1938
  };
1937
- logger$5.log(`🚀 Starting ${appCount} app(s) — each builds before it answers, and says so when it is ready`);
1939
+ logger$6.log(`🚀 Starting ${appCount} app(s) — each builds before it answers, and says so when it is ready`);
1938
1940
  const turboProcess = spawn("pnpm", [
1939
1941
  "turbo",
1940
1942
  "run",
@@ -1950,7 +1952,7 @@ async function workspaceDevCommand(configured, options) {
1950
1952
  const shutdown = () => {
1951
1953
  if (isShuttingDown) return;
1952
1954
  isShuttingDown = true;
1953
- logger$5.log("\n🛑 Shutting down workspace...");
1955
+ logger$6.log("\n🛑 Shutting down workspace...");
1954
1956
  closeFakes(fakes);
1955
1957
  discovery.leave();
1956
1958
  const pid = turboProcess.pid;
@@ -1976,7 +1978,7 @@ async function workspaceDevCommand(configured, options) {
1976
1978
  process.on("SIGTERM", shutdown);
1977
1979
  return new Promise((resolve, reject) => {
1978
1980
  turboProcess.on("error", (error) => {
1979
- logger$5.error("❌ Turbo error:", error);
1981
+ logger$6.error("❌ Turbo error:", error);
1980
1982
  reject(error);
1981
1983
  });
1982
1984
  turboProcess.on("exit", (code) => {
@@ -1995,11 +1997,11 @@ async function entryDevCommand(options) {
1995
1997
  const entryPath = resolve(process.cwd(), entry);
1996
1998
  if (!existsSync(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
1997
1999
  const defaultEnv = loadEnvFiles(".env");
1998
- if (defaultEnv.loaded.length > 0) logger$5.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
2000
+ if (defaultEnv.loaded.length > 0) logger$6.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1999
2001
  const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
2000
- if (appName) logger$5.log(`📦 App: ${appName} (port ${resolvedPort})`);
2001
- logger$5.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
2002
- if (Object.keys(credentials).length > 1) logger$5.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
2002
+ if (appName) logger$6.log(`📦 App: ${appName} (port ${resolvedPort})`);
2003
+ logger$6.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
2004
+ if (Object.keys(credentials).length > 1) logger$6.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
2003
2005
  const wrapperDir = join(process.cwd(), ".gkm");
2004
2006
  await mkdir(wrapperDir, { recursive: true });
2005
2007
  const wrapperPath = join(wrapperDir, "entry-wrapper.ts");
@@ -2010,7 +2012,7 @@ async function entryDevCommand(options) {
2010
2012
  const shutdown = () => {
2011
2013
  if (isShuttingDown) return;
2012
2014
  isShuttingDown = true;
2013
- logger$5.log("\n🛑 Shutting down...");
2015
+ logger$6.log("\n🛑 Shutting down...");
2014
2016
  runner.stop();
2015
2017
  process.exit(0);
2016
2018
  };
@@ -2046,14 +2048,14 @@ var EntryRunner = class {
2046
2048
  });
2047
2049
  let restartTimeout = null;
2048
2050
  this.watcher.on("change", (path) => {
2049
- logger$5.log(`📝 File changed: ${path}`);
2051
+ logger$6.log(`📝 File changed: ${path}`);
2050
2052
  if (restartTimeout) clearTimeout(restartTimeout);
2051
2053
  restartTimeout = setTimeout(async () => {
2052
- logger$5.log("🔄 Restarting...");
2054
+ logger$6.log("🔄 Restarting...");
2053
2055
  await this.restart();
2054
2056
  }, 300);
2055
2057
  });
2056
- logger$5.log(`👀 Watching for changes in: ${watchDir}`);
2058
+ logger$6.log(`👀 Watching for changes in: ${watchDir}`);
2057
2059
  }
2058
2060
  }
2059
2061
  async runProcess() {
@@ -2069,17 +2071,17 @@ var EntryRunner = class {
2069
2071
  });
2070
2072
  this.isRunning = true;
2071
2073
  this.childProcess.on("error", (error) => {
2072
- logger$5.error("❌ Process error:", error);
2074
+ logger$6.error("❌ Process error:", error);
2073
2075
  });
2074
2076
  this.childProcess.on("exit", (code) => {
2075
- if (code !== null && code !== 0 && code !== 143) logger$5.error(`❌ Process exited with code ${code}`);
2077
+ if (code !== null && code !== 0 && code !== 143) logger$6.error(`❌ Process exited with code ${code}`);
2076
2078
  this.isRunning = false;
2077
2079
  });
2078
2080
  await new Promise((resolve) => setTimeout(resolve, 500));
2079
2081
  if (this.isRunning) {
2080
- logger$5.log("");
2081
- logger$5.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
2082
- logger$5.log("");
2082
+ logger$6.log("");
2083
+ logger$6.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
2084
+ logger$6.log("");
2083
2085
  }
2084
2086
  }
2085
2087
  async restart() {
@@ -2198,7 +2200,7 @@ start({
2198
2200
  * lets a console read what this machine is running.
2199
2201
  */
2200
2202
  function announce(discovery) {
2201
- logger$5.log(`🧭 Discovery: ${discovery.connectUrl}`);
2203
+ logger$6.log(`🧭 Discovery: ${discovery.connectUrl}`);
2202
2204
  }
2203
2205
  /** Run `fn` with `console.log` muted — warnings and errors still print. */
2204
2206
  async function quietly(fn) {
@@ -2316,7 +2318,7 @@ var DevServer = class {
2316
2318
  this.actualPort = this.requestedPort;
2317
2319
  } else {
2318
2320
  this.actualPort = await findAvailablePort(this.requestedPort);
2319
- if (this.actualPort !== this.requestedPort) logger$5.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
2321
+ if (this.actualPort !== this.requestedPort) logger$6.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
2320
2322
  }
2321
2323
  const serverEntryPath = join(this.appRoot, ".gkm", "server", "server.ts");
2322
2324
  await this.createServerEntry();
@@ -2336,11 +2338,11 @@ var DevServer = class {
2336
2338
  });
2337
2339
  this.isRunning = true;
2338
2340
  this.serverProcess.on("error", (error) => {
2339
- logger$5.error("❌ Server error:", error);
2341
+ logger$6.error("❌ Server error:", error);
2340
2342
  });
2341
2343
  const child = this.serverProcess;
2342
2344
  child.on("exit", (code, signal) => {
2343
- if (code !== null && code !== 0 && signal !== "SIGTERM") logger$5.error(`❌ Server exited with code ${code}`);
2345
+ if (code !== null && code !== 0 && signal !== "SIGTERM") logger$6.error(`❌ Server exited with code ${code}`);
2344
2346
  this.isRunning = false;
2345
2347
  if (this.serverProcess === child) this.onStatus?.("stopped");
2346
2348
  });
@@ -2349,13 +2351,13 @@ var DevServer = class {
2349
2351
  const local = `http://localhost:${this.actualPort}`;
2350
2352
  const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
2351
2353
  const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
2352
- logger$5.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
2354
+ logger$6.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
2353
2355
  const tools = this.selfServing ? [] : [
2354
2356
  ...this.enableOpenApi ? ["docs /__docs"] : [],
2355
2357
  ...this.telescope ? [`telescope ${this.telescope.path}`] : [],
2356
2358
  ...this.databaseApi ? [`db ${this.databaseApi}`] : []
2357
2359
  ];
2358
- if (tools.length > 0) logger$5.log(` ${tools.join(" · ")}`);
2360
+ if (tools.length > 0) logger$6.log(` ${tools.join(" · ")}`);
2359
2361
  this.onStatus?.("ready");
2360
2362
  }
2361
2363
  }
@@ -2995,9 +2997,63 @@ export const { app } = await surface.server({ envParser, context: snifferContext
2995
2997
  export default app;
2996
2998
  `);
2997
2999
  }
3000
+ /**
3001
+ * The production server for a surface that serves itself.
3002
+ *
3003
+ * `app.ts` exports the construct's own Hono app and starts nothing, which is
3004
+ * what `gkm dev` wants: its server mounts the app beside its tools. An image
3005
+ * needs a process, so a production build starts one here — the health check
3006
+ * every image is probed on, the surface behind it, and the same bounded drain
3007
+ * on SIGTERM the generated route servers have. Without it an auth server had
3008
+ * no bundle to put in an image at all.
3009
+ */
3010
+ async function writeSurfaceServer(outputDir, options) {
3011
+ await mkdir(outputDir, { recursive: true });
3012
+ const shutdown = options.gracefulShutdown ? `
3013
+ // Stop taking requests, let in-flight ones finish, then close what the
3014
+ // construct opened — bounded by GKM_SHUTDOWN_TIMEOUT_MS, under Docker's 10s
3015
+ // stop timeout, so the process exits on its own terms.
3016
+ let shuttingDown = false;
3017
+ const shutdown = async () => {
3018
+ if (shuttingDown) return;
3019
+ shuttingDown = true;
3020
+ const deadline = Number(process.env.GKM_SHUTDOWN_TIMEOUT_MS) || 8000;
3021
+ setTimeout(() => process.exit(1), deadline).unref();
3022
+ await new Promise<void>((resolve) => server.close(() => resolve()));
3023
+ await runShutdownHooks((error) => console.error(error));
3024
+ process.exit(0);
3025
+ };
3026
+
3027
+ process.on('SIGTERM', shutdown);
3028
+ process.on('SIGINT', shutdown);
3029
+ ` : "";
3030
+ const path = join(outputDir, "server.ts");
3031
+ await writeFile(path, `#!/usr/bin/env node
3032
+ /**
3033
+ * Production server for a surface that serves itself.
3034
+ * Generated by 'gkm build --production'.
3035
+ */
3036
+ import { serve } from '@hono/node-server';
3037
+ import { Hono } from 'hono';
3038
+ ${options.gracefulShutdown ? "import { runShutdownHooks } from '@geekmidas/constructs';\n" : ""}import { app as surface } from './app.js';
3039
+
3040
+ const port = Number(process.env.PORT) || 3000;
3041
+
3042
+ const app = new Hono();
3043
+ // First, so a probe is answered without reaching the surface's own routes.
3044
+ app.get(${JSON.stringify(options.healthCheck)}, (c) =>
3045
+ c.json({ status: 'ok', timestamp: Date.now() }),
3046
+ );
3047
+ app.route('/', surface);
3048
+
3049
+ const server = serve({ fetch: app.fetch, port });
3050
+ console.log(JSON.stringify({ msg: 'Production server started', port }));
3051
+ ${shutdown}`);
3052
+ return path;
3053
+ }
2998
3054
  //#endregion
2999
3055
  //#region src/build/index.ts
3000
- const logger$4 = console;
3056
+ const logger$5 = console;
3001
3057
  /**
3002
3058
  * Where `gkm build --stage` leaves the key its bundle's secrets were encrypted
3003
3059
  * with, beside the bundle in `.gkm/server/` and readable by its owner alone.
@@ -3045,7 +3101,7 @@ async function buildCommand(options) {
3045
3101
  const isAtWorkspaceRoot = cwd === workspaceRoot;
3046
3102
  const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => resolve(workspaceRoot, app.path) !== workspaceRoot);
3047
3103
  if (isAtWorkspaceRoot && someAppIsElsewhere) {
3048
- logger$4.log("📦 Detected workspace configuration");
3104
+ logger$5.log("📦 Detected workspace configuration");
3049
3105
  return workspaceBuildCommand(loadedConfig.workspace, options);
3050
3106
  }
3051
3107
  }
@@ -3091,13 +3147,13 @@ async function buildOneApp(input) {
3091
3147
  const { config, options, workspace, appRoot, target } = input;
3092
3148
  const cacheBackend = cacheBackendFor(providerOf(workspace));
3093
3149
  const production = normalizeProductionConfig(options.production ?? false);
3094
- if (production) logger$4.log(`🏭 Building for PRODUCTION`);
3095
- logger$4.log(`Building for ${target}`);
3096
- logger$4.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
3150
+ if (production) logger$5.log(`🏭 Building for PRODUCTION`);
3151
+ logger$5.log(`Building for ${target}`);
3152
+ logger$5.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
3097
3153
  const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
3098
- if (telescope) logger$4.log(`🔭 Telescope enabled at ${telescope.path}`);
3154
+ if (telescope) logger$5.log(`🔭 Telescope enabled at ${telescope.path}`);
3099
3155
  const hooks = normalizeHooksConfig(config.hooks, appRoot);
3100
- if (hooks) logger$4.log(`🪝 Server hooks enabled`);
3156
+ if (hooks) logger$5.log(`🪝 Server hooks enabled`);
3101
3157
  return buildApp({
3102
3158
  config,
3103
3159
  workspaceRoot: workspace.root,
@@ -3139,7 +3195,7 @@ async function writeManifest(input) {
3139
3195
  });
3140
3196
  const path = join(manifestDir, `${target}.ts`);
3141
3197
  await writeFile(path, manifestModule(folded, backends));
3142
- logger$4.log(`Manifest: ${relative(process.cwd(), path)}`);
3198
+ logger$5.log(`Manifest: ${relative(process.cwd(), path)}`);
3143
3199
  }
3144
3200
  /**
3145
3201
  * Turn one app's constructs into its generated entry.
@@ -3167,7 +3223,21 @@ async function buildApp(input) {
3167
3223
  });
3168
3224
  if (selfServing) {
3169
3225
  await writeSurfaceEntry(join(appRoot, ".gkm", "server"), selfServing.source);
3170
- logger$4.log(`Generated a server for ${selfServing.id} from its own declaration`);
3226
+ logger$5.log(`Generated a server for ${selfServing.id} from its own declaration`);
3227
+ if (production?.bundle && !input.skipBundle) {
3228
+ const outputDir = join(appRoot, ".gkm", "server");
3229
+ const entryPoint = await writeSurfaceServer(outputDir, production);
3230
+ const { bundleServer } = await import("./bundler-DN5r4x5b.mjs");
3231
+ await bundleServer({
3232
+ entryPoint,
3233
+ outputDir: join(outputDir, "dist"),
3234
+ minify: production.minify,
3235
+ sourcemap: false,
3236
+ external: production.external,
3237
+ ...input.stage ? { stage: input.stage } : {}
3238
+ });
3239
+ logger$5.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
3240
+ }
3171
3241
  return { selfServing: selfServing.id };
3172
3242
  }
3173
3243
  const derived = ownersContext({
@@ -3212,14 +3282,14 @@ async function buildApp(input) {
3212
3282
  topicGenerator.load(code, appRoot, bustCache)
3213
3283
  ]);
3214
3284
  const allEndpoints = servedBy(loadedEndpoints, derived.surface);
3215
- logger$4.log(`Found ${allEndpoints.length} endpoints`);
3216
- logger$4.log(`Found ${allFunctions.length} functions`);
3217
- logger$4.log(`Found ${allCrons.length} crons`);
3218
- logger$4.log(`Found ${allSubscribers.length} subscribers`);
3219
- logger$4.log(`Found ${allQueues.length} queues`);
3220
- logger$4.log(`Found ${allTopics.length} topics`);
3285
+ logger$5.log(`Found ${allEndpoints.length} endpoints`);
3286
+ logger$5.log(`Found ${allFunctions.length} functions`);
3287
+ logger$5.log(`Found ${allCrons.length} crons`);
3288
+ logger$5.log(`Found ${allSubscribers.length} subscribers`);
3289
+ logger$5.log(`Found ${allQueues.length} queues`);
3290
+ logger$5.log(`Found ${allTopics.length} topics`);
3221
3291
  if (!input.serveEmpty && allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
3222
- logger$4.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
3292
+ logger$5.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
3223
3293
  return {};
3224
3294
  }
3225
3295
  if (production?.subscribers === "include" && derived.surface) {
@@ -3232,7 +3302,7 @@ async function buildApp(input) {
3232
3302
  [allQueues.length, "queue consumer"],
3233
3303
  [allSubscribers.length, "subscriber"]
3234
3304
  ].filter(([n]) => n > 0).map(([n, what]) => `${n} ${what}${n === 1 ? "" : "s"}`);
3235
- if (left.length > 0) logger$4.log(`Serving ${derived.surface.id} only: leaving out ${left.join(", ")} — they run in a Worker's image`);
3305
+ if (left.length > 0) logger$5.log(`Serving ${derived.surface.id} only: leaving out ${left.join(", ")} — they run in a Worker's image`);
3236
3306
  }
3237
3307
  const result = await buildForTarget(target, buildContext, appRoot, workspaceRoot, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics, enableOpenApi, input.skipBundle ?? false, input.stage);
3238
3308
  await generateOpenApi(allEndpoints.map(({ construct }) => construct), {
@@ -3255,7 +3325,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3255
3325
  force: true
3256
3326
  });
3257
3327
  await mkdir(outputDir, { recursive: true });
3258
- logger$4.log(`\nGenerating handlers for ${target}`);
3328
+ logger$5.log(`\nGenerating handlers for ${target}`);
3259
3329
  const options = {
3260
3330
  target,
3261
3331
  root: workspaceRoot
@@ -3271,7 +3341,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3271
3341
  queueGenerator.build(context, queues, outputDir, options),
3272
3342
  topicGenerator.build(context, topics, outputDir, options)
3273
3343
  ]);
3274
- logger$4.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${target}`);
3344
+ logger$5.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${target}`);
3275
3345
  const fields = {
3276
3346
  ...context.surface ? { surface: context.surface.id } : {},
3277
3347
  functions: functionInfos,
@@ -3294,8 +3364,8 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3294
3364
  };
3295
3365
  let masterKey;
3296
3366
  if (context.production?.bundle && !skipBundle) {
3297
- logger$4.log(`\n📦 Bundling production server...`);
3298
- const { bundleServer } = await import("./bundler-DJVR4QlW.mjs");
3367
+ logger$5.log(`\n📦 Bundling production server...`);
3368
+ const { bundleServer } = await import("./bundler-DN5r4x5b.mjs");
3299
3369
  const allConstructs = [
3300
3370
  ...endpoints.map((e) => e.construct),
3301
3371
  ...functions.map((f) => f.construct),
@@ -3312,13 +3382,13 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3312
3382
  stage,
3313
3383
  constructs: allConstructs
3314
3384
  })).masterKey;
3315
- logger$4.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
3385
+ logger$5.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
3316
3386
  if (masterKey) {
3317
3387
  const keyPath = join(outputDir, MASTER_KEY_FILE);
3318
3388
  await writeFile(keyPath, `${masterKey}\n`, { mode: 384 });
3319
3389
  await chmod(keyPath, 384);
3320
- logger$4.log(`\n🔐 Secrets encrypted for deployment`);
3321
- logger$4.log(` Run with GKM_MASTER_KEY set to the key in ${relative(appRoot, keyPath)} (fingerprint ${keyFingerprint(masterKey)})`);
3390
+ logger$5.log(`\n🔐 Secrets encrypted for deployment`);
3391
+ logger$5.log(` Run with GKM_MASTER_KEY set to the key in ${relative(appRoot, keyPath)} (fingerprint ${keyFingerprint(masterKey)})`);
3322
3392
  }
3323
3393
  }
3324
3394
  return {
@@ -3419,12 +3489,12 @@ async function workspaceBuildCommand(workspace, options) {
3419
3489
  const apps = Object.entries(workspace.apps);
3420
3490
  const backendApps = apps.filter(([, app]) => app.type === "backend");
3421
3491
  const frontendApps = apps.filter(([, app]) => app.type === "web");
3422
- logger$4.log(`\n🏗️ Building workspace: ${workspace.name}`);
3423
- logger$4.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
3424
- logger$4.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
3425
- if (options.production) logger$4.log(` 🏭 Production mode enabled`);
3492
+ logger$5.log(`\n🏗️ Building workspace: ${workspace.name}`);
3493
+ logger$5.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
3494
+ logger$5.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
3495
+ if (options.production) logger$5.log(` 🏭 Production mode enabled`);
3426
3496
  const buildOrder = getAppBuildOrder(workspace);
3427
- logger$4.log(` Build order: ${buildOrder.join(" → ")}`);
3497
+ logger$5.log(` Build order: ${buildOrder.join(" → ")}`);
3428
3498
  try {
3429
3499
  const target = options.provider ?? providerOf(workspace);
3430
3500
  const builds = [];
@@ -3432,7 +3502,7 @@ async function workspaceBuildCommand(workspace, options) {
3432
3502
  const app = workspace.apps[appName];
3433
3503
  const config = getAppGkmConfig(workspace, appName);
3434
3504
  if (!app || app.type !== "backend" || !config) continue;
3435
- logger$4.log(`\n⚙️ ${appName}`);
3505
+ logger$5.log(`\n⚙️ ${appName}`);
3436
3506
  const output = await buildOneApp({
3437
3507
  config,
3438
3508
  options,
@@ -3457,8 +3527,8 @@ async function workspaceBuildCommand(workspace, options) {
3457
3527
  const pm = detectPackageManager$1();
3458
3528
  const [turboBin, turboArgs] = getTurboArgs(pm, filters, { only: true });
3459
3529
  if (filters.length > 0) {
3460
- logger$4.log(`\n📦 Using ${pm} with Turbo for the other apps...\n`);
3461
- logger$4.log(`Running: ${[turboBin, ...turboArgs].join(" ")}`);
3530
+ logger$5.log(`\n📦 Using ${pm} with Turbo for the other apps...\n`);
3531
+ logger$5.log(`Running: ${[turboBin, ...turboArgs].join(" ")}`);
3462
3532
  await run$1(turboBin, turboArgs, {
3463
3533
  cwd: workspace.root,
3464
3534
  env: {
@@ -3476,15 +3546,15 @@ async function workspaceBuildCommand(workspace, options) {
3476
3546
  outputPath
3477
3547
  });
3478
3548
  }
3479
- logger$4.log(`\n✅ Workspace build complete!`);
3480
- logger$4.log(`\n📋 Build Summary:`);
3549
+ logger$5.log(`\n✅ Workspace build complete!`);
3550
+ logger$5.log(`\n📋 Build Summary:`);
3481
3551
  for (const result of results) {
3482
3552
  const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
3483
- logger$4.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
3553
+ logger$5.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
3484
3554
  }
3485
3555
  } catch (error) {
3486
3556
  const errorMessage = error instanceof Error ? error.message : "Build failed";
3487
- logger$4.log(`\n❌ Build failed: ${errorMessage}`);
3557
+ logger$5.log(`\n❌ Build failed: ${errorMessage}`);
3488
3558
  for (const [appName, app] of apps) results.push({
3489
3559
  appName,
3490
3560
  type: app.type,
@@ -3513,6 +3583,1491 @@ function getAppOutputPath(workspace, _appName, app) {
3513
3583
  function formatRoutes(routes) {
3514
3584
  return Array.isArray(routes) ? routes.join(", ") : routes;
3515
3585
  }
3586
+ //#endregion
3587
+ //#region src/migrate/provider.ts
3588
+ /**
3589
+ * One construct's migrations and seeds, read from its folders.
3590
+ *
3591
+ * Two kinds of file, told apart by extension rather than by configuration: a
3592
+ * script exports `up` (and may export `down`) — or `seed(db, { stage })`, in
3593
+ * `seeds/` — and a
3594
+ * `.sql` file is run whole. The second is what makes a folder independent of
3595
+ * the client the app uses — Better Auth's schema is SQL whatever the
3596
+ * application queries with.
3597
+ */
3598
+ const SCRIPTS = /* @__PURE__ */ new Set([
3599
+ ".ts",
3600
+ ".mts",
3601
+ ".js",
3602
+ ".mjs"
3603
+ ]);
3604
+ /** Two files in one folder that would be one migration. */
3605
+ var DuplicateMigration = class extends Error {
3606
+ folder;
3607
+ migration;
3608
+ constructor(folder, migration) {
3609
+ super(`${folder} has two migrations named '${migration}' — a script and a .sql file with the same name. A migration is recorded by name, so only one of them could ever run; rename or remove the other.`);
3610
+ this.folder = folder;
3611
+ this.migration = migration;
3612
+ this.name = "DuplicateMigration";
3613
+ }
3614
+ };
3615
+ /** A script in a migrations folder that exports no `up`. */
3616
+ var MigrationHasNoUp = class extends Error {
3617
+ file;
3618
+ constructor(file) {
3619
+ super(`${file} is in a migrations folder but exports no \`up\` function. Export \`async function up(db: Kysely<unknown>)\`, or move the file out of the folder if it is not a migration.`);
3620
+ this.file = file;
3621
+ this.name = "MigrationHasNoUp";
3622
+ }
3623
+ };
3624
+ /** A script in a seeds folder that exports no `seed`. */
3625
+ var SeedHasNoSeed = class extends Error {
3626
+ file;
3627
+ constructor(file) {
3628
+ super(`${file} is in a seeds folder but exports no \`seed\` function. Export \`async function seed(db, { stage })\`, or move the file out of the folder if it is not a seed.`);
3629
+ this.file = file;
3630
+ this.name = "SeedHasNoSeed";
3631
+ }
3632
+ };
3633
+ /**
3634
+ * The migration files in a folder, by name, without reading them.
3635
+ *
3636
+ * Also what `gkm dev` compares against a construct's history to report what
3637
+ * is pending, which is why it is separate from loading them.
3638
+ */
3639
+ async function migrationFiles(folder) {
3640
+ const entries = await readdir(folder, { withFileTypes: true }).catch((error) => {
3641
+ if (error.code === "ENOENT") return [];
3642
+ throw error;
3643
+ });
3644
+ const files = /* @__PURE__ */ new Map();
3645
+ for (const entry of entries) {
3646
+ if (!entry.isFile() || !isMigration(entry.name)) continue;
3647
+ const name = entry.name.slice(0, -extname(entry.name).length);
3648
+ if (files.has(name)) throw new DuplicateMigration(folder, name);
3649
+ files.set(name, join(folder, entry.name));
3650
+ }
3651
+ return files;
3652
+ }
3653
+ /** A Kysely provider over one construct's folder. */
3654
+ function folderProvider(folder, raw) {
3655
+ return { async getMigrations() {
3656
+ const migrations = {};
3657
+ for (const [name, file] of await migrationFiles(folder)) migrations[name] = extname(file) === ".sql" ? await sqlMigration(file, raw) : await scriptMigration(file);
3658
+ return migrations;
3659
+ } };
3660
+ }
3661
+ /**
3662
+ * The seeds in a folder, loaded, in name order.
3663
+ *
3664
+ * Every one runs on every pass — there is no history to consult — so the order
3665
+ * is the only thing between two seeds where one reads what another writes.
3666
+ * Name them to sort that way.
3667
+ */
3668
+ async function loadSeeds(folder, raw) {
3669
+ const files = [...await migrationFiles(folder)].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
3670
+ const seeds = [];
3671
+ for (const [name, file] of files) seeds.push({
3672
+ name,
3673
+ run: extname(file) === ".sql" ? (await sqlMigration(file, raw)).up : await scriptSeed(file)
3674
+ });
3675
+ return seeds;
3676
+ }
3677
+ /**
3678
+ * A seed script, imported as it is on disk now.
3679
+ *
3680
+ * Keyed by its modification time: a seed is run again after it is edited, and
3681
+ * a process that has imported it once would otherwise run the old one.
3682
+ */
3683
+ async function scriptSeed(file) {
3684
+ const url = pathToFileURL(file);
3685
+ url.searchParams.set("mtime", String((await stat(file)).mtimeMs));
3686
+ const module = await import(url.href);
3687
+ if (typeof module.seed !== "function") throw new SeedHasNoSeed(file);
3688
+ return module.seed;
3689
+ }
3690
+ function isMigration(file) {
3691
+ if (file.endsWith(".d.ts")) return false;
3692
+ if (/\.(spec|test)\.[cm]?[jt]s$/.test(file)) return false;
3693
+ const extension = extname(file);
3694
+ return extension === ".sql" || SCRIPTS.has(extension);
3695
+ }
3696
+ async function scriptMigration(file) {
3697
+ const module = await import(pathToFileURL(file).href);
3698
+ if (typeof module.up !== "function") throw new MigrationHasNoUp(file);
3699
+ return {
3700
+ up: module.up,
3701
+ ...typeof module.down === "function" ? { down: module.down } : {}
3702
+ };
3703
+ }
3704
+ /**
3705
+ * A `.sql` file, run whole.
3706
+ *
3707
+ * One query rather than split on `;`: with no parameters, Postgres takes
3708
+ * several statements in one message, and splitting by hand breaks on the first
3709
+ * `;` inside a string or a function body. There is no `down` — a generated
3710
+ * schema is rolled back by a migration that says how, not by guessing.
3711
+ */
3712
+ async function sqlMigration(file, raw) {
3713
+ const statements = await readFile(file, "utf-8");
3714
+ return { up: async (db) => {
3715
+ await raw.raw(statements).execute(db);
3716
+ } };
3717
+ }
3718
+ //#endregion
3719
+ //#region src/migrate/databases.ts
3720
+ /**
3721
+ * Bring each construct's schema up to its migrations folder.
3722
+ *
3723
+ * Which constructs, in what order, from which folder, is `migrationTargets`'
3724
+ * answer and nobody else's. What is decided here is only how one target is
3725
+ * run: as its owner, in its own schema, with a history of its own.
3726
+ */
3727
+ /** A folder under `db/` that names no construct. */
3728
+ var UnknownMigrationFolder = class extends Error {
3729
+ folder;
3730
+ known;
3731
+ constructor(folder, known) {
3732
+ super(`${folder} names no database construct, so its migrations would never run. ${known.length > 0 ? `The folders that do: ${known.join(", ")}.` : "This project declares no database."} Rename the folder after the construct it belongs to.`);
3733
+ this.folder = folder;
3734
+ this.known = known;
3735
+ this.name = "UnknownMigrationFolder";
3736
+ }
3737
+ };
3738
+ /** A construct was asked for by a name that takes no migrations. */
3739
+ var NoSuchMigrationTarget = class extends Error {
3740
+ construct;
3741
+ known;
3742
+ constructor(construct, known) {
3743
+ super(`'${construct}' is not a database construct. ` + (known.length > 0 ? `The ones with migrations: ${known.join(", ")}.` : "This project declares no database."));
3744
+ this.construct = construct;
3745
+ this.known = known;
3746
+ this.name = "NoSuchMigrationTarget";
3747
+ }
3748
+ };
3749
+ /**
3750
+ * A migration left directly in a construct's folder, where nothing reads it.
3751
+ *
3752
+ * Migrations moved into `migrations/` so seeds could sit beside them. A file
3753
+ * still at the old level would otherwise never run, and nothing would say so.
3754
+ */
3755
+ var MigrationsOutsideFolder = class extends Error {
3756
+ folder;
3757
+ files;
3758
+ constructor(folder, files) {
3759
+ super(`${folder} has ${files.join(", ")} directly inside it, where nothing runs them. Migrations live in ${folder}/migrations/ and seeds in ${folder}/seeds/ — move ${files.length === 1 ? "it" : "them"} there.`);
3760
+ this.folder = folder;
3761
+ this.files = files;
3762
+ this.name = "MigrationsOutsideFolder";
3763
+ }
3764
+ };
3765
+ /** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */
3766
+ var UnknownDatabaseFolder = class extends Error {
3767
+ folder;
3768
+ constructor(folder) {
3769
+ super(`${folder} is neither migrations/ nor seeds/, so nothing in it would ever run. A construct's folder holds those two; rename or remove it.`);
3770
+ this.folder = folder;
3771
+ this.name = "UnknownDatabaseFolder";
3772
+ }
3773
+ };
3774
+ /** A seed failed; its own writes are rolled back, the ones before it stay. */
3775
+ var SeedFailed = class extends Error {
3776
+ construct;
3777
+ seed;
3778
+ cause;
3779
+ constructor(construct, seed, cause) {
3780
+ super(`${construct}: seed '${seed}' failed — ${cause instanceof Error ? cause.message : String(cause)}. Its writes were rolled back; fix it and run it again.`);
3781
+ this.construct = construct;
3782
+ this.seed = seed;
3783
+ this.cause = cause;
3784
+ this.name = "SeedFailed";
3785
+ }
3786
+ };
3787
+ /** A construct with roles has no owner URL to migrate with. */
3788
+ var NoOwnerCredential = class extends Error {
3789
+ construct;
3790
+ key;
3791
+ constructor(construct, key) {
3792
+ super(`${construct} has no ${key} to migrate with. Migrations run as the owner role — the runtime one cannot create a table — and nothing resolved its URL. Is the stage reconciled?`);
3793
+ this.construct = construct;
3794
+ this.key = key;
3795
+ this.name = "NoOwnerCredential";
3796
+ }
3797
+ };
3798
+ /** A migration failed; the ones before it in the run stay applied. */
3799
+ var MigrationFailed = class extends Error {
3800
+ construct;
3801
+ migration;
3802
+ cause;
3803
+ constructor(construct, migration, cause) {
3804
+ super(`${construct}: migration ${migration ? `'${migration}' ` : ""}failed — ${cause instanceof Error ? cause.message : String(cause)}`);
3805
+ this.construct = construct;
3806
+ this.migration = migration;
3807
+ this.cause = cause;
3808
+ this.name = "MigrationFailed";
3809
+ }
3810
+ };
3811
+ /** The project's Kysely could not be found from where a database is declared. */
3812
+ var KyselyNotFound = class extends Error {
3813
+ from;
3814
+ constructor(from) {
3815
+ super(`Could not resolve 'kysely' from ${from}. Migrations run with the project's own Kysely — install it where the database is declared.`);
3816
+ this.from = from;
3817
+ this.name = "KyselyNotFound";
3818
+ }
3819
+ };
3820
+ /** Apply every pending migration, construct by construct, parents first. */
3821
+ async function migrateDatabases(options) {
3822
+ const runs = [];
3823
+ for (const target of await targetsFor(options)) {
3824
+ if ((await migrationFiles(join(options.root, target.migrations))).size === 0) {
3825
+ runs.push({
3826
+ target,
3827
+ applied: []
3828
+ });
3829
+ continue;
3830
+ }
3831
+ const applied = await withMigrator(options, target, async (migrator) => {
3832
+ const { error, results = [] } = await migrator.migrateToLatest();
3833
+ if (error) {
3834
+ const failed = results.find((result) => result.status === "Error");
3835
+ throw new MigrationFailed(target.id, failed?.migrationName, error);
3836
+ }
3837
+ return results.filter((result) => result.status === "Success").map((result) => result.migrationName);
3838
+ });
3839
+ runs.push({
3840
+ target,
3841
+ applied
3842
+ });
3843
+ }
3844
+ return runs;
3845
+ }
3846
+ /**
3847
+ * Run every construct's seeds, parents first — each one, every time, on every
3848
+ * stage, production included. Each is handed the stage, so a seed decides
3849
+ * what belongs where.
3850
+ *
3851
+ * Seeds are reference data kept up to date by being run again, so there is no
3852
+ * history: a seed is an upsert, and a changed one is applied by the next run.
3853
+ * Each runs in its own transaction, as the construct's owner, so a failure
3854
+ * leaves nothing half-written. Assumes the schema is migrated —
3855
+ * {@link migrateAndSeed} is what makes that true.
3856
+ */
3857
+ async function seedDatabases(options) {
3858
+ const runs = [];
3859
+ for (const target of await targetsFor(options)) {
3860
+ const folder = join(options.root, target.seeds);
3861
+ if ((await migrationFiles(folder)).size === 0) {
3862
+ runs.push({
3863
+ target,
3864
+ seeded: []
3865
+ });
3866
+ continue;
3867
+ }
3868
+ const seeded = await withOwner(options, target, async (db, kysely) => {
3869
+ const names = [];
3870
+ for (const seed of await loadSeeds(folder, kysely.sql)) {
3871
+ try {
3872
+ await db.transaction().execute((trx) => seed.run(trx, { stage: options.stage }));
3873
+ } catch (error) {
3874
+ throw new SeedFailed(target.id, seed.name, error);
3875
+ }
3876
+ names.push(seed.name);
3877
+ }
3878
+ return names;
3879
+ });
3880
+ runs.push({
3881
+ target,
3882
+ seeded
3883
+ });
3884
+ }
3885
+ return runs;
3886
+ }
3887
+ /**
3888
+ * Migrate, then seed: a seed only ever runs against the schema it was written
3889
+ * for.
3890
+ *
3891
+ * With `only`, the constructs it lives in are migrated too — a tenant's seed
3892
+ * cannot run before the database that owns its schema exists — and only it is
3893
+ * seeded.
3894
+ */
3895
+ async function migrateAndSeed(options) {
3896
+ const migrations = [];
3897
+ for (const target of await withParents(options)) migrations.push(...await migrateDatabases({
3898
+ ...options,
3899
+ only: target.id
3900
+ }));
3901
+ return {
3902
+ migrations,
3903
+ seeds: await seedDatabases(options)
3904
+ };
3905
+ }
3906
+ /** The targets `only` names, after the ones it lives in; all without it. */
3907
+ async function withParents(options) {
3908
+ const chosen = await targetsFor(options);
3909
+ if (!options.only) return chosen;
3910
+ const all = migrationTargets(options.manifest);
3911
+ const ids = /* @__PURE__ */ new Set();
3912
+ for (const target of chosen) {
3913
+ let current = target;
3914
+ while (current) {
3915
+ ids.add(current.id);
3916
+ const parent = current.of;
3917
+ current = parent ? all.find((t) => t.id === parent) : void 0;
3918
+ }
3919
+ }
3920
+ return all.filter((target) => ids.has(target.id));
3921
+ }
3922
+ /**
3923
+ * What each construct's folder has that its history does not — without
3924
+ * applying any of it. What `gkm dev` reports.
3925
+ */
3926
+ async function pendingMigrations(options) {
3927
+ const found = [];
3928
+ for (const target of await targetsFor(options)) {
3929
+ if ((await migrationFiles(join(options.root, target.migrations))).size === 0) continue;
3930
+ const pending = await withMigrator(options, target, async (migrator) => (await migrator.getMigrations()).filter((migration) => migration.executedAt === void 0).map((migration) => migration.name));
3931
+ if (pending.length > 0) found.push({
3932
+ target,
3933
+ pending
3934
+ });
3935
+ }
3936
+ return found;
3937
+ }
3938
+ /**
3939
+ * The targets this run covers, with every folder under `db/` accounted for.
3940
+ *
3941
+ * A folder that names no construct is an error rather than something to skip:
3942
+ * a typo in its name is otherwise a migration that silently never runs.
3943
+ */
3944
+ async function targetsFor(options) {
3945
+ const targets = migrationTargets(options.manifest);
3946
+ const folders = targets.map((target) => target.folder);
3947
+ for (const folder of await foldersIn(join(options.root, MIGRATIONS_ROOT))) {
3948
+ const relative = `${MIGRATIONS_ROOT}/${folder}`;
3949
+ if (!folders.includes(relative)) throw new UnknownMigrationFolder(relative, folders);
3950
+ await checkLayout(options.root, relative);
3951
+ }
3952
+ if (!options.only) return targets;
3953
+ const id = canonicalId(options.only);
3954
+ const chosen = targets.filter((target) => target.id === id);
3955
+ if (chosen.length === 0) throw new NoSuchMigrationTarget(options.only, targets.map((target) => target.id));
3956
+ return chosen;
3957
+ }
3958
+ /**
3959
+ * A construct's folder holds `migrations/` and `seeds/`, and nothing else —
3960
+ * anything beside them is a file or folder nothing would ever run.
3961
+ */
3962
+ async function checkLayout(root, folder) {
3963
+ const entries = await readdir(join(root, folder), { withFileTypes: true });
3964
+ const loose = entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name);
3965
+ if (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);
3966
+ for (const entry of entries) if (entry.isDirectory() && !DATABASE_FOLDERS.includes(entry.name)) throw new UnknownDatabaseFolder(`${folder}/${entry.name}`);
3967
+ }
3968
+ async function foldersIn(dir) {
3969
+ return (await readdir(dir, { withFileTypes: true }).catch((error) => {
3970
+ if (error.code === "ENOENT") return [];
3971
+ throw error;
3972
+ })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
3973
+ }
3974
+ /**
3975
+ * A migrator for one target, connected as its owner.
3976
+ *
3977
+ * No `migrationTableSchema`: the owner role's `search_path` is pinned to the
3978
+ * construct's schema, so the history lands there — `app.kysely_migration` for
3979
+ * the database, `auth_database.kysely_migration` for its tenant — and two
3980
+ * constructs in one Postgres never share one.
3981
+ *
3982
+ * Unordered migrations are allowed. Files are timestamped, so a branch merged
3983
+ * after another's migration ran carries an older one; refusing it would make
3984
+ * every such merge a manual repair.
3985
+ */
3986
+ async function withMigrator(options, target, run) {
3987
+ return withOwner(options, target, (db, { Migrator, sql }) => run(new Migrator({
3988
+ db,
3989
+ provider: folderProvider(join(options.root, target.migrations), sql),
3990
+ allowUnorderedMigrations: true
3991
+ })));
3992
+ }
3993
+ /**
3994
+ * One target's database, connected as its owner and closed afterwards — what
3995
+ * migrations and seeds both run as.
3996
+ */
3997
+ async function withOwner(options, target, run) {
3998
+ const kysely = await kyselyFrom(options.sources?.[target.id]?.file ?? (target.of ? options.sources?.[target.of]?.file : void 0) ?? join(options.root, "package.json"));
3999
+ const db = new kysely.Kysely({ dialect: new kysely.PostgresDialect({ pool: new pg.Pool(poolConfig(ownerUrl(options, target))) }) });
4000
+ try {
4001
+ return await run(db, kysely);
4002
+ } finally {
4003
+ await db.destroy();
4004
+ }
4005
+ }
4006
+ /**
4007
+ * The owner's URL, with no fallback to the runtime one.
4008
+ *
4009
+ * The runtime role can create nothing, so falling back would only move the
4010
+ * failure to the first `CREATE TABLE` and make it read like a permissions
4011
+ * bug. The one exception is a database declared `roles: false`, where there is
4012
+ * no owner — both URLs are the master credential by design.
4013
+ */
4014
+ function ownerUrl(options, target) {
4015
+ const key = provideKey(target.id, "ownerUrl");
4016
+ const owner = options.env[key];
4017
+ if (owner) return owner;
4018
+ const database = options.manifest[target.of ?? target.id];
4019
+ if (database?.kind === "database" && database.roles === false) {
4020
+ const url = options.env[provideKey(target.id, "url")];
4021
+ if (url) return url;
4022
+ }
4023
+ throw new NoOwnerCredential(target.id, key);
4024
+ }
4025
+ /**
4026
+ * Pool settings for a URL, with `?search_path=` as the startup option Postgres
4027
+ * reads. Only a `roles: false` URL carries it — a role pins its own.
4028
+ */
4029
+ function poolConfig(url) {
4030
+ const parsed = new URL(url);
4031
+ const searchPath = parsed.searchParams.get("search_path");
4032
+ if (!searchPath) return { connectionString: url };
4033
+ parsed.searchParams.delete("search_path");
4034
+ return {
4035
+ connectionString: parsed.toString(),
4036
+ options: `-c search_path=${searchPath}`
4037
+ };
4038
+ }
4039
+ /** Kysely as the project resolves it from `from`. */
4040
+ async function kyselyFrom(from) {
4041
+ const require = createRequire(from);
4042
+ try {
4043
+ const [main, migration] = await Promise.all([import(pathToFileURL(require.resolve("kysely")).href), import(pathToFileURL(require.resolve("kysely/migration")).href)]);
4044
+ return {
4045
+ Kysely: main.Kysely,
4046
+ PostgresDialect: main.PostgresDialect,
4047
+ sql: main.sql,
4048
+ Migrator: migration.Migrator
4049
+ };
4050
+ } catch {
4051
+ throw new KyselyNotFound(from);
4052
+ }
4053
+ }
4054
+ //#endregion
4055
+ //#region src/compose/docker.ts
4056
+ /**
4057
+ * The Docker operations `gkm compose` needs, behind an interface.
4058
+ *
4059
+ * Small on purpose, like reconcile's: the rules about *when* each runs — the
4060
+ * registry is asked before anything is pulled, nothing is started for a tag
4061
+ * that is missing — are asserted against a fake, and this file is only the
4062
+ * wiring to the real CLI.
4063
+ *
4064
+ * Every call is an argument array. Image refs, tags and paths come from the
4065
+ * workspace, and none of them reaches a shell.
4066
+ */
4067
+ /** How long a stack has to pass its health checks. */
4068
+ const WAIT_TIMEOUT_S = 180;
4069
+ /** `docker compose port` printed nothing a port can be read from. */
4070
+ var ServicePortUnknown = class extends Error {
4071
+ service;
4072
+ inside;
4073
+ output;
4074
+ constructor(service, inside, output) {
4075
+ super(`'${service}' publishes no host port for ${inside} (docker said: ${output.trim() || "nothing"}). Is it running? \`docker compose ps\` shows the stack.`);
4076
+ this.service = service;
4077
+ this.inside = inside;
4078
+ this.output = output;
4079
+ this.name = "ServicePortUnknown";
4080
+ }
4081
+ };
4082
+ function compose(stack, args) {
4083
+ return [
4084
+ "compose",
4085
+ "-p",
4086
+ stack.project,
4087
+ "-f",
4088
+ stack.file,
4089
+ ...args
4090
+ ];
4091
+ }
4092
+ /** Run a program and keep everything it said, whatever it exits with. */
4093
+ function capture(command, args, timeoutMs = 6e4) {
4094
+ return new Promise((resolve, reject) => {
4095
+ const child = spawn(command, [...args], {
4096
+ stdio: [
4097
+ "ignore",
4098
+ "pipe",
4099
+ "pipe"
4100
+ ],
4101
+ shell: false
4102
+ });
4103
+ let stdout = "";
4104
+ let stderr = "";
4105
+ child.stdout.on("data", (chunk) => {
4106
+ stdout += chunk.toString();
4107
+ });
4108
+ child.stderr.on("data", (chunk) => {
4109
+ stderr += chunk.toString();
4110
+ });
4111
+ const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
4112
+ timer.unref?.();
4113
+ child.on("error", (error) => {
4114
+ clearTimeout(timer);
4115
+ reject(error);
4116
+ });
4117
+ child.on("close", (code) => {
4118
+ clearTimeout(timer);
4119
+ resolve({
4120
+ code,
4121
+ stdout,
4122
+ stderr
4123
+ });
4124
+ });
4125
+ });
4126
+ }
4127
+ /**
4128
+ * Whether a failed `docker manifest inspect` means the tag is not there, as
4129
+ * opposed to the registry not answering.
4130
+ *
4131
+ * Read off the message because that is all the CLI gives. Anything not
4132
+ * recognisably "no such tag" is treated as the registry being unreachable:
4133
+ * a registry that refused credentials must not be reported as a tag that was
4134
+ * never pushed, or the fix offered would be the wrong one.
4135
+ */
4136
+ function isMissingManifest(output) {
4137
+ return /no such manifest|manifest unknown|manifest for .* not found|not found: manifest/i.test(output);
4138
+ }
4139
+ const dockerCompose = {
4140
+ async lookup(ref) {
4141
+ const { code, stdout, stderr } = await capture("docker", [
4142
+ "manifest",
4143
+ "inspect",
4144
+ ref
4145
+ ]);
4146
+ if (code === 0) return {
4147
+ ref,
4148
+ status: "found"
4149
+ };
4150
+ const output = `${stderr}\n${stdout}`.trim();
4151
+ return isMissingManifest(output) ? {
4152
+ ref,
4153
+ status: "missing"
4154
+ } : {
4155
+ ref,
4156
+ status: "unreachable",
4157
+ detail: output
4158
+ };
4159
+ },
4160
+ async build(stack, services) {
4161
+ await run$1("docker", compose(stack, ["build", ...services]), {
4162
+ cwd: stack.cwd,
4163
+ env: {
4164
+ ...process.env,
4165
+ DOCKER_BUILDKIT: "1"
4166
+ }
4167
+ });
4168
+ },
4169
+ async pull(stack, services) {
4170
+ await run$1("docker", compose(stack, ["pull", ...services]), { cwd: stack.cwd });
4171
+ },
4172
+ async up(stack, services) {
4173
+ await run$1("docker", compose(stack, [
4174
+ "up",
4175
+ "-d",
4176
+ "--wait",
4177
+ `--wait-timeout=${WAIT_TIMEOUT_S}`,
4178
+ ...services ? [] : ["--remove-orphans"],
4179
+ ...services ?? []
4180
+ ]), { cwd: stack.cwd });
4181
+ },
4182
+ async down(stack) {
4183
+ await run$1("docker", compose(stack, ["down"]), { cwd: stack.cwd });
4184
+ },
4185
+ async port(stack, service, inside) {
4186
+ const output = await runOutput("docker", compose(stack, [
4187
+ "port",
4188
+ service,
4189
+ String(inside)
4190
+ ]), { cwd: stack.cwd });
4191
+ const port = Number(output.trim().split(":").pop());
4192
+ if (!Number.isInteger(port) || port <= 0) throw new ServicePortUnknown(service, inside, output);
4193
+ return port;
4194
+ },
4195
+ async copyOut(stack, service, from, to) {
4196
+ await mkdir(dirname(to), { recursive: true });
4197
+ await run$1("docker", compose(stack, [
4198
+ "cp",
4199
+ `${service}:${from}`,
4200
+ to
4201
+ ]), { cwd: stack.cwd });
4202
+ },
4203
+ async digest(ref) {
4204
+ const { code, stdout } = await capture("docker", [
4205
+ "image",
4206
+ "inspect",
4207
+ "--format={{json .RepoDigests}} {{.Id}}",
4208
+ ref
4209
+ ]);
4210
+ if (code !== 0) return void 0;
4211
+ const [json = "[]", id] = stdout.trim().split(" ");
4212
+ const repository = ref.split("@")[0].replace(/:[\w][\w.-]*$/, "");
4213
+ try {
4214
+ const digests = JSON.parse(json);
4215
+ const match = Array.isArray(digests) ? digests.find((entry) => typeof entry === "string" && entry.startsWith(`${repository}@`)) : void 0;
4216
+ if (match) return match.slice(repository.length + 1);
4217
+ } catch {}
4218
+ return id || void 0;
4219
+ }
4220
+ };
4221
+ //#endregion
4222
+ //#region src/compose/images.ts
4223
+ /**
4224
+ * The tag a site's image carries for a release.
4225
+ *
4226
+ * A site's public URLs are inlined when it is built, so one bundle serves one
4227
+ * stage: `v1.4.0-production` and `v1.4.0-staging` are two images of the same
4228
+ * commit. A backend reads its URLs at runtime, so one image serves every stage
4229
+ * and its tag is the release's alone.
4230
+ */
4231
+ function siteTag(tag, stage) {
4232
+ return `${tag}-${stage}`;
4233
+ }
4234
+ /** Images a tag names that the registry does not have. */
4235
+ var ImageTagNotFound = class extends Error {
4236
+ tag;
4237
+ refs;
4238
+ constructor(tag, refs) {
4239
+ super(`The registry has no image for ${refs.length === 1 ? "this app" : `${refs.length} apps`} at '${tag}':\n` + refs.map((ref) => ` - ${ref}`).join("\n") + `\nNothing was pulled or started. Push '${tag}' for every app from CI, pick a tag that exists, or run without --tag to build from this checkout.`);
4240
+ this.tag = tag;
4241
+ this.refs = refs;
4242
+ this.name = "ImageTagNotFound";
4243
+ }
4244
+ };
4245
+ /** The registry did not answer, or did not let this machine in. */
4246
+ var RegistryUnreachable = class extends Error {
4247
+ ref;
4248
+ detail;
4249
+ constructor(ref, detail) {
4250
+ super(`Could not ask the registry about ${ref}: ${detail || "no answer"}\nNothing was pulled or started. Check the registry is reachable and that this machine is logged in to it (\`docker login <registry>\`).`);
4251
+ this.ref = ref;
4252
+ this.detail = detail;
4253
+ this.name = "RegistryUnreachable";
4254
+ }
4255
+ };
4256
+ /**
4257
+ * Assert every image exists, before anything else happens.
4258
+ *
4259
+ * Asked all at once and reported all at once: a release missing two images is
4260
+ * one problem with two names, and finding the second only after fixing the
4261
+ * first is a slower way to learn the same thing.
4262
+ *
4263
+ * @throws {RegistryUnreachable} when the registry cannot say
4264
+ * @throws {ImageTagNotFound} listing every missing image
4265
+ */
4266
+ async function assertImagesExist(docker, tag, images) {
4267
+ const lookups = await Promise.all(images.map((image) => docker.lookup(image.ref)));
4268
+ const unreachable = lookups.find((lookup) => lookup.status === "unreachable");
4269
+ if (unreachable?.status === "unreachable") throw new RegistryUnreachable(unreachable.ref, unreachable.detail);
4270
+ const missing = lookups.filter((lookup) => lookup.status === "missing").map((lookup) => lookup.ref);
4271
+ if (missing.length > 0) throw new ImageTagNotFound(tag, missing);
4272
+ }
4273
+ //#endregion
4274
+ //#region src/compose/caddyfile.ts
4275
+ /** The Caddyfile for a stack's edge. */
4276
+ function edgeCaddyfile(sites, options) {
4277
+ const global = options.local ? `{
4278
+ # No ACME and no public DNS: the CA is Caddy's own and the names resolve
4279
+ # to this machine.
4280
+ local_certs
4281
+ auto_https disable_redirects
4282
+ }` : options.email ? `{
4283
+ email ${options.email}
4284
+ }` : "";
4285
+ const blocks = sites.map(({ host, upstream }) => `https://${host} {${options.local ? `
4286
+ tls internal
4287
+ ` : ""}
4288
+ # Host and X-Forwarded-* are passed through as Caddy does by default, so
4289
+ # an app builds redirects, cookies and links on the address a caller used.
4290
+ reverse_proxy ${upstream} {
4291
+ # Never buffer: a streamed response — server-sent events, a Next.js
4292
+ # RSC payload — reaches the client as each chunk is written.
4293
+ flush_interval -1
4294
+ }
4295
+ }`);
4296
+ return `${HEADER}${global ? `${global}\n\n` : ""}${blocks.join("\n\n")}\n`;
4297
+ }
4298
+ const HEADER = `# Generated by gkm compose from the construct manifest — do not edit.
4299
+ #
4300
+ # One host per API and site, each in front of its own container.
4301
+ `;
4302
+ //#endregion
4303
+ //#region src/compose/stack.ts
4304
+ /**
4305
+ * The stack a workspace runs as for one stage — pure.
4306
+ *
4307
+ * Everything `gkm compose` writes is decided here, as data: the compose file,
4308
+ * the Caddyfile, one env file per backend, and the image each app runs. The
4309
+ * command around it reads the workspace, asks the registry, writes the files
4310
+ * and starts Docker; none of those decide anything, so all of it can be
4311
+ * asserted without a daemon.
4312
+ *
4313
+ * The values come from the same derivations every other target uses. The
4314
+ * plan is reconcile's `planFor`, the keys an app reads are `appEnvKeys`, and
4315
+ * every URL is `envFor`'s — asked twice, once with each app at its public
4316
+ * address and once on the compose network, because a browser and a sibling
4317
+ * service reach the same API by different names.
4318
+ */
4319
+ /** Where a stack's files are written, relative to the workspace root. */
4320
+ function stackDir(stage) {
4321
+ return `.gkm/compose/${stage}`;
4322
+ }
4323
+ /**
4324
+ * The compose project a stage's stack runs as.
4325
+ *
4326
+ * The deploy identity's scope, so two workspaces with one name in different
4327
+ * namespaces never share containers or volumes — and the stage, so a
4328
+ * machine can run two stages side by side.
4329
+ */
4330
+ function composeProject(identity) {
4331
+ return `${identity.scope}-${identity.stage}`;
4332
+ }
4333
+ /** The ports the edge is published on, and the variables that move them. */
4334
+ const EDGE_PORT_ENV = {
4335
+ https: "GKM_COMPOSE_HTTPS_PORT",
4336
+ http: "GKM_COMPOSE_HTTP_PORT"
4337
+ };
4338
+ /**
4339
+ * Containers this target does not run.
4340
+ *
4341
+ * Object storage is the stage's own — an externally hosted bucket whose URL
4342
+ * the stage's secrets hold — and so is a deployed stage's mail. The AWS
4343
+ * emulator never applies: a stack is a server target, whose events are
4344
+ * pg-boss.
4345
+ */
4346
+ const NOT_RUN = {
4347
+ minio: true,
4348
+ localstack: true,
4349
+ caddy: true
4350
+ };
4351
+ /** Kinds whose `<ID>_URL` a stage must be given, because nothing here runs them. */
4352
+ const EXTERNAL_KINDS = {
4353
+ objects: true,
4354
+ "file-server": true
4355
+ };
4356
+ /** The S3 client's credentials, read beside a bucket's URL when it has them. */
4357
+ const STORAGE_KEYS = [
4358
+ "AWS_ACCESS_KEY_ID",
4359
+ "AWS_SECRET_ACCESS_KEY",
4360
+ "AWS_REGION"
4361
+ ];
4362
+ /** A bucket the stack needs and the stage was not given. */
4363
+ var BucketNotConfigured = class extends Error {
4364
+ app;
4365
+ id;
4366
+ key;
4367
+ stage;
4368
+ constructor(app, id, key, stage) {
4369
+ super(`'${app}' uses the bucket '${id}', and gkm compose runs no object storage: the stage needs an externally hosted bucket. Set its URL in the stage's secrets: gkm secrets:set ${key} 's3://…' --stage ${stage}`);
4370
+ this.app = app;
4371
+ this.id = id;
4372
+ this.key = key;
4373
+ this.stage = stage;
4374
+ this.name = "BucketNotConfigured";
4375
+ }
4376
+ };
4377
+ /** A value only the stage's secrets can hold, and they do not. */
4378
+ var StageSecretMissing = class extends Error {
4379
+ app;
4380
+ key;
4381
+ stage;
4382
+ constructor(app, key, stage) {
4383
+ super(`'${app}' reads ${key}, and the stage '${stage}' has none. Nothing in the stack can derive it; set it in the stage's secrets: gkm secrets:set ${key} '…' --stage ${stage}`);
4384
+ this.app = app;
4385
+ this.key = key;
4386
+ this.stage = stage;
4387
+ this.name = "StageSecretMissing";
4388
+ }
4389
+ };
4390
+ /** A deployed stage's secrets with no seed to derive its passwords from. */
4391
+ var StageSeedMissing = class extends Error {
4392
+ stage;
4393
+ constructor(stage) {
4394
+ super(`The stage '${stage}' has no seed in its secrets, and a deployed stage's database passwords are derived from it. Run gkm compose without --dry-run once, or gkm deploy, to generate it.`);
4395
+ this.stage = stage;
4396
+ this.name = "StageSeedMissing";
4397
+ }
4398
+ };
4399
+ /** A workspace with no API and no site to run. */
4400
+ var NothingToCompose = class extends Error {
4401
+ root;
4402
+ constructor(root) {
4403
+ super(`${root} declares no RestApi and no site, so a stack would run nothing. Workers and mobile apps are not run by gkm compose yet.`);
4404
+ this.root = root;
4405
+ this.name = "NothingToCompose";
4406
+ }
4407
+ };
4408
+ /** A value that cannot be written to an env file a line at a time. */
4409
+ var EnvValueMultiline = class extends Error {
4410
+ app;
4411
+ key;
4412
+ constructor(app, key) {
4413
+ super(`${key} for '${app}' spans more than one line, and an env file holds one value per line. Store it on one line — base64, or JSON — and decode it where it is read.`);
4414
+ this.app = app;
4415
+ this.key = key;
4416
+ this.name = "EnvValueMultiline";
4417
+ }
4418
+ };
4419
+ /** The services a stack's apps wait on and the edge waits on in turn. */
4420
+ const HEALTHY = { condition: "service_healthy" };
4421
+ /**
4422
+ * An app's health check: its own server, asked on 127.0.0.1 — not
4423
+ * `localhost`, which resolves to `::1` first in an Alpine container while a
4424
+ * server may listen on IPv4 alone.
4425
+ */
4426
+ function probe(port, path) {
4427
+ return {
4428
+ test: [
4429
+ "CMD",
4430
+ "wget",
4431
+ "-q",
4432
+ "-O",
4433
+ "/dev/null",
4434
+ `http://127.0.0.1:${port}${path}`
4435
+ ],
4436
+ interval: "5s",
4437
+ timeout: "3s",
4438
+ retries: 20,
4439
+ start_period: "10s"
4440
+ };
4441
+ }
4442
+ /** The stack for one stage. */
4443
+ function composeStack(input) {
4444
+ const { workspace, manifest, stage, identity } = input;
4445
+ const local = stage === workspace.stages.local;
4446
+ const project = composeProject(identity);
4447
+ const custom = input.secrets?.custom ?? {};
4448
+ const https = input.ports?.https ?? 443;
4449
+ const http = input.ports?.http ?? 80;
4450
+ const plan = planFor(manifest, stage, provisionOrder(manifest), {
4451
+ localStage: workspace.stages.local,
4452
+ events: DEFAULT_EVENTS.server,
4453
+ cache: DEFAULT_CACHE.server,
4454
+ edge: false
4455
+ });
4456
+ const byId = new Map(plan.resources.map((r) => [r.id, r]));
4457
+ const seed = local ? void 0 : input.secrets?.seed;
4458
+ if (!local && !seed) throw new StageSeedMissing(stage);
4459
+ const credential = {
4460
+ master: local ? "geekmidas" : localRolePassword(workspace.name, plan, "master", seed),
4461
+ ...seed ? { seed } : {}
4462
+ };
4463
+ const infra = plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => local || c !== "mailpit").sort();
4464
+ const apps = stackApps(workspace, manifest, plan, {
4465
+ local,
4466
+ stage,
4467
+ https,
4468
+ identity,
4469
+ images: input.images
4470
+ });
4471
+ if (apps.length === 0) throw new NothingToCompose(workspace.root);
4472
+ const derivation = {
4473
+ project: workspace.name,
4474
+ master: credential.master,
4475
+ ...credential.seed ? { seed: credential.seed } : {}
4476
+ };
4477
+ const outside = networkEnv(plan, {
4478
+ ...derivation,
4479
+ addresses: Object.fromEntries(apps.map((app) => [app.id, app.url]))
4480
+ });
4481
+ const inside = networkEnv(plan, {
4482
+ ...derivation,
4483
+ addresses: Object.fromEntries(apps.map((app) => [app.id, `http://${app.name}:${app.port}`]))
4484
+ });
4485
+ const owners = /* @__PURE__ */ new Map();
4486
+ for (const resource of plan.resources) {
4487
+ owners.set(resource.envKey, resource);
4488
+ if (resource.kind === "email") {
4489
+ owners.set(provideKey(resource.id, "from"), resource);
4490
+ owners.set(provideKey(resource.id, "inboxUrl"), resource);
4491
+ }
4492
+ if (resource.kind === "external-api") owners.set(provideKey(resource.id, "credentials"), resource);
4493
+ }
4494
+ const surfaceUrls = new Map(plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site").map((r) => [r.envKey, r.id]));
4495
+ const appById = new Map(apps.map((app) => [app.id, app]));
4496
+ /**
4497
+ * What a key resolves to for one app, from the stage's secrets where only
4498
+ * they can hold it, and the derivations otherwise.
4499
+ */
4500
+ const valueFor = (app, key, owner) => {
4501
+ if (owner && EXTERNAL_KINDS[owner.kind] && key === owner.envKey) {
4502
+ const value = custom[key];
4503
+ if (value === void 0) throw new BucketNotConfigured(app.name, owner.id, key, stage);
4504
+ return value;
4505
+ }
4506
+ if (owner && suppliedBy(owner, key, local)) {
4507
+ const value = custom[key];
4508
+ if (value === void 0) throw new StageSecretMissing(app.name, key, stage);
4509
+ return value;
4510
+ }
4511
+ if (custom[key] !== void 0) return custom[key];
4512
+ const surface = surfaceUrls.get(key);
4513
+ if (app.kind === "rest-api" && surface && surface !== app.id) return inside[key];
4514
+ return outside[key];
4515
+ };
4516
+ for (const app of apps) {
4517
+ if (app.kind === "site") {
4518
+ const site = manifest[app.id];
4519
+ if (site?.kind !== "site") continue;
4520
+ const args = {};
4521
+ for (const [key, source] of Object.entries(publicEnvFor(site, manifest))) {
4522
+ const value = valueFor(app, source, owners.get(source));
4523
+ if (value !== void 0) args[key] = value;
4524
+ }
4525
+ if (app.build && Object.keys(args).length > 0) app.build.args = args;
4526
+ continue;
4527
+ }
4528
+ const keys = appEnvKeys(manifest, app.name, input.runnables) ?? /* @__PURE__ */ new Set();
4529
+ const values = {};
4530
+ for (const key of [...keys].sort()) {
4531
+ const owner = owners.get(key) ?? storageOwner(key, plan);
4532
+ if (!local && owner?.kind === "email" && key.endsWith("_INBOX_URL")) continue;
4533
+ if (STORAGE_KEYS.includes(key)) {
4534
+ if (custom[key] !== void 0) values[key] = custom[key];
4535
+ continue;
4536
+ }
4537
+ const value = valueFor(app, key, owner);
4538
+ if (value !== void 0) values[key] = value;
4539
+ }
4540
+ const trusted = provideKey(app.id, "trustedOrigins");
4541
+ if (keys.has(trusted)) {
4542
+ const internal = (byId.get(app.id)?.callers ?? []).map((caller) => appById.get(caller)).filter((caller) => caller?.kind === "rest-api").map((caller) => `http://${caller.name}:${caller.port}`);
4543
+ const origins = [...(values[trusted] ?? "").split(",").filter(Boolean), ...internal];
4544
+ values[trusted] = [...new Set(origins)].join(",");
4545
+ }
4546
+ app.env = {
4547
+ ...values,
4548
+ NODE_ENV: "production",
4549
+ PORT: String(app.port),
4550
+ STAGE: stage
4551
+ };
4552
+ for (const [key, value] of Object.entries(app.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(app.name, key);
4553
+ }
4554
+ const compose = stackFile({
4555
+ project,
4556
+ plan,
4557
+ infra,
4558
+ apps,
4559
+ master: credential.master,
4560
+ https,
4561
+ http
4562
+ });
4563
+ const caddyfile = edgeCaddyfile(apps.map((app) => ({
4564
+ host: app.host,
4565
+ upstream: `${app.name}:${app.port}`
4566
+ })), { local });
4567
+ const dockerfiles = {};
4568
+ for (const app of apps) {
4569
+ if (!app.build) continue;
4570
+ dockerfiles[app.build.dockerfile] = app.kind === "site" ? siteDockerfile(app.name, workspace.apps[app.name], {
4571
+ turboPackage: input.packages?.[app.name] ?? app.name,
4572
+ packageManager: input.packageManager ?? "pnpm",
4573
+ manifest
4574
+ }) : generateSlimDockerfile({
4575
+ imageName: app.name,
4576
+ baseImage: "node:22-alpine",
4577
+ port: app.port,
4578
+ healthCheckPath: "/health",
4579
+ prebuilt: true,
4580
+ packageManager: input.packageManager ?? "pnpm",
4581
+ bundle: `${app.path}/.gkm/server/dist/server.mjs`
4582
+ });
4583
+ }
4584
+ return {
4585
+ project,
4586
+ stage,
4587
+ local,
4588
+ apps,
4589
+ infra,
4590
+ compose,
4591
+ caddyfile,
4592
+ dockerfiles,
4593
+ plan,
4594
+ credential
4595
+ };
4596
+ /** Whether only the stage's secrets can supply `key` to `owner`. */
4597
+ function suppliedBy(owner, key, isLocal) {
4598
+ if (owner.kind === "credential") return true;
4599
+ if (owner.kind === "external-api") return key === provideKey(owner.id, "credentials");
4600
+ if (isLocal) return false;
4601
+ if (owner.kind === "secret" || owner.kind === "encryption") return true;
4602
+ return owner.kind === "email" && !key.endsWith("_INBOX_URL");
4603
+ }
4604
+ }
4605
+ /** Which bucket an S3 credential key belongs to — the first one declared. */
4606
+ function storageOwner(key, plan) {
4607
+ if (!STORAGE_KEYS.includes(key)) return void 0;
4608
+ return plan.resources.find((r) => r.kind === "objects");
4609
+ }
4610
+ /** The apps a stack runs: every API and every site, at its stage address. */
4611
+ function stackApps(workspace, manifest, plan, options) {
4612
+ const apps = [];
4613
+ const resources = new Map(plan.resources.map((r) => [r.id, r]));
4614
+ for (const [id, declaration] of Object.entries(manifest)) {
4615
+ if (declaration.kind !== "rest-api" && declaration.kind !== "site") continue;
4616
+ const name = appKey(id);
4617
+ const app = workspace.apps[name];
4618
+ const resource = resources.get(id);
4619
+ if (!app || !resource || !app.port) continue;
4620
+ const host = hostOf(name, app, workspace, resource, options);
4621
+ const tag = declaration.kind === "site" ? siteTag(options.images.tag, options.stage) : options.images.tag;
4622
+ const ref = validateImageRef(imageRef(options.identity, name, options.images.registry, tag));
4623
+ apps.push({
4624
+ app: name,
4625
+ name,
4626
+ id,
4627
+ kind: declaration.kind,
4628
+ path: app.path,
4629
+ port: app.port,
4630
+ host,
4631
+ url: `https://${host}${options.local && options.https !== 443 ? `:${options.https}` : ""}`,
4632
+ ref,
4633
+ tag,
4634
+ ...options.images.mode === "build" ? { build: { dockerfile: `${stackDir(options.stage)}/Dockerfile.${name}` } } : {}
4635
+ });
4636
+ }
4637
+ return apps.sort((a, b) => a.name.localeCompare(b.name));
4638
+ }
4639
+ /**
4640
+ * The host an app answers on for a stage: the `gkm dev` edge's
4641
+ * `*.localhost` names for the local stage, and the stage's domain — by the
4642
+ * same rule a deploy follows — for any other.
4643
+ */
4644
+ function hostOf(name, app, workspace, resource, options) {
4645
+ if (options.local) return hostFor(resource, workspace.name);
4646
+ return resolveHost(name, app, options.stage, workspace.deploy?.domains, isMainFrontendApp(name, app, workspace.apps));
4647
+ }
4648
+ /** The compose document: the infrastructure, the apps, and the edge. */
4649
+ function stackFile(options) {
4650
+ const { project, plan, infra, apps } = options;
4651
+ const derived = composeFor({
4652
+ ...plan,
4653
+ containers: [...infra]
4654
+ }, {
4655
+ project,
4656
+ ports: Object.fromEntries(portKeys(infra, plan.fakes).map((key) => [key, 0]))
4657
+ });
4658
+ const services = {};
4659
+ const volumes = {
4660
+ ...derived.volumes,
4661
+ "caddy-data": {},
4662
+ "caddy-config": {}
4663
+ };
4664
+ for (const name of infra) {
4665
+ const { ports: _ports, depends_on, profiles: _profiles, ...service } = derived.services[name];
4666
+ services[name] = {
4667
+ ...service,
4668
+ ...depends_on?.length ? { depends_on: Object.fromEntries(depends_on.map((dep) => [dep, HEALTHY])) } : {}
4669
+ };
4670
+ }
4671
+ const postgres = services.postgres;
4672
+ if (postgres) {
4673
+ postgres.environment = {
4674
+ ...postgres.environment,
4675
+ POSTGRES_PASSWORD: options.master
4676
+ };
4677
+ postgres.ports = ["127.0.0.1::5432"];
4678
+ }
4679
+ for (const app of apps) services[app.name] = {
4680
+ image: app.ref,
4681
+ ...app.build ? { build: {
4682
+ context: "../../..",
4683
+ dockerfile: app.build.dockerfile,
4684
+ ...app.build.args ? { args: app.build.args } : {}
4685
+ } } : {},
4686
+ restart: "unless-stopped",
4687
+ ...app.kind === "rest-api" ? {
4688
+ env_file: [{
4689
+ path: `./${app.name}.env`,
4690
+ format: "raw"
4691
+ }],
4692
+ ...infra.length ? { depends_on: Object.fromEntries(infra.map((name) => [name, HEALTHY])) } : {}
4693
+ } : {},
4694
+ healthcheck: probe(app.port, app.kind === "site" ? "/" : "/health")
4695
+ };
4696
+ services.caddy = {
4697
+ image: DEFAULT_IMAGES.caddy,
4698
+ restart: "unless-stopped",
4699
+ ports: [`${options.https}:443`, `${options.http}:80`],
4700
+ volumes: [
4701
+ "./Caddyfile:/etc/caddy/Caddyfile:ro",
4702
+ "caddy-data:/data",
4703
+ "caddy-config:/config"
4704
+ ],
4705
+ depends_on: Object.fromEntries(apps.map((app) => [app.name, HEALTHY])),
4706
+ healthcheck: {
4707
+ test: [
4708
+ "CMD",
4709
+ "caddy",
4710
+ "version"
4711
+ ],
4712
+ interval: "10s",
4713
+ timeout: "5s",
4714
+ retries: 5
4715
+ }
4716
+ };
4717
+ return {
4718
+ name: project,
4719
+ services,
4720
+ volumes
4721
+ };
4722
+ }
4723
+ /** Render an app's env file: one `KEY=value` per line, read raw by compose. */
4724
+ function envFile(env) {
4725
+ return `${Object.entries(env).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`).join("\n")}\n`;
4726
+ }
4727
+ //#endregion
4728
+ //#region src/compose/index.ts
4729
+ /**
4730
+ * `gkm compose` — a workspace's APIs and sites for one stage, as one Docker
4731
+ * Compose stack behind Caddy.
4732
+ *
4733
+ * The order is the point of this file, and the rules it keeps are these:
4734
+ *
4735
+ * - A tag is checked before anything happens. Every app's image is asked of
4736
+ * the registry first; one missing image stops the run naming them all, and
4737
+ * nothing is written, pulled or started.
4738
+ * - The databases exist before the apps do. Postgres comes up alone, its
4739
+ * databases, roles and grants are created and its migrations applied from
4740
+ * this machine, and only then do the apps start — so an app never boots
4741
+ * against a schema that is not there yet.
4742
+ * - What a stage runs is recorded. Each app's tag and the digest it resolved
4743
+ * to are written to the stage's state, so "what is this stage running" has
4744
+ * an exact answer.
4745
+ *
4746
+ * Docker, the registry, the build and the clock are injected, so the rules are
4747
+ * asserted without a daemon; the defaults are the real ones.
4748
+ */
4749
+ const logger$4 = console;
4750
+ /** `--build` and `--pull` together ask for two different things. */
4751
+ var ComposeModeConflict = class extends Error {
4752
+ constructor() {
4753
+ super("--build and --pull ask for opposite things: build images from this checkout, or pull a tag CI pushed. Pass one of them.");
4754
+ this.name = "ComposeModeConflict";
4755
+ }
4756
+ };
4757
+ /** A build with no commit to tag its images with. */
4758
+ var NoGitRevision = class extends Error {
4759
+ root;
4760
+ constructor(root) {
4761
+ super(`Images built here are tagged with the commit they were built from, and ${root} is not in a git repository with a commit. Commit, or pass --tag to name the images yourself.`);
4762
+ this.root = root;
4763
+ this.name = "NoGitRevision";
4764
+ }
4765
+ };
4766
+ /** A port variable that does not hold a port. */
4767
+ var EdgePortInvalid = class extends Error {
4768
+ variable;
4769
+ value;
4770
+ constructor(variable, value) {
4771
+ super(`${variable}=${value} is not a port. Set it to a number between 1 and 65535, or unset it for the default.`);
4772
+ this.variable = variable;
4773
+ this.value = value;
4774
+ this.name = "EdgePortInvalid";
4775
+ }
4776
+ };
4777
+ /** The commit HEAD is at, short — and `-dirty` when the tree has changes. */
4778
+ async function gitRevision(root) {
4779
+ let sha;
4780
+ try {
4781
+ sha = (await runOutput("git", [
4782
+ "rev-parse",
4783
+ "--short",
4784
+ "HEAD"
4785
+ ], { cwd: root })).trim();
4786
+ } catch {
4787
+ throw new NoGitRevision(root);
4788
+ }
4789
+ if (!sha) throw new NoGitRevision(root);
4790
+ return (await runOutput("git", ["status", "--porcelain"], { cwd: root }).catch(() => "")).trim() ? `${sha}-dirty` : sha;
4791
+ }
4792
+ /**
4793
+ * Bundle a backend with the gkm that is running — the same build a deploy
4794
+ * makes, so the image holds one file and no dependencies.
4795
+ */
4796
+ async function bundleWithThisCli(appRoot) {
4797
+ const entry = process.argv[1];
4798
+ if (!entry) return;
4799
+ await run$1(process.execPath, [
4800
+ ...process.execArgv,
4801
+ entry,
4802
+ "build",
4803
+ "--provider",
4804
+ "server",
4805
+ "--production"
4806
+ ], { cwd: appRoot });
4807
+ }
4808
+ const defaultDeps = {
4809
+ docker: dockerCompose,
4810
+ bundle: bundleWithThisCli,
4811
+ revision: gitRevision
4812
+ };
4813
+ /** The edge's ports, from the environment — 443 and 80 when unset. */
4814
+ function edgePorts(env = process.env) {
4815
+ const read = (variable, fallback) => {
4816
+ const value = env[variable];
4817
+ if (value === void 0 || value === "") return fallback;
4818
+ const port = Number(value);
4819
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new EdgePortInvalid(variable, value);
4820
+ return port;
4821
+ };
4822
+ return {
4823
+ https: read(EDGE_PORT_ENV.https, 443),
4824
+ http: read(EDGE_PORT_ENV.http, 80)
4825
+ };
4826
+ }
4827
+ /** `gkm compose`. */
4828
+ async function composeCommand(options = {}, deps = {}) {
4829
+ const { docker, bundle, revision } = {
4830
+ ...defaultDeps,
4831
+ ...deps
4832
+ };
4833
+ const env = deps.env ?? process.env;
4834
+ if (options.build && options.pull) throw new ComposeModeConflict();
4835
+ const { workspace } = await loadWorkspaceConfig(options.cwd);
4836
+ const root = workspace.root;
4837
+ const stage = options.stage ?? workspace.stages.local;
4838
+ if (!(stage === workspace.stages.local)) assertDeployedStage(workspace.stages, stage);
4839
+ const identity = deployIdentity(workspace, stage);
4840
+ const dir = join(root, stackDir(stage));
4841
+ const file = join(dir, "docker-compose.yml");
4842
+ const ref = {
4843
+ project: composeProject(identity),
4844
+ file,
4845
+ cwd: root
4846
+ };
4847
+ if (options.down) {
4848
+ await docker.down(ref);
4849
+ logger$4.log(`🛑 Stopped ${ref.project}. Its volumes are kept.`);
4850
+ return;
4851
+ }
4852
+ const sources = {};
4853
+ const runnables = {};
4854
+ const manifest = await discover({
4855
+ patterns: constructGlobs(workspace),
4856
+ cwd: root,
4857
+ sources,
4858
+ runnables
4859
+ });
4860
+ const mode = options.build || !options.pull && !options.tag ? "build" : "pull";
4861
+ const tag = options.tag ?? (mode === "pull" ? "latest" : await revision(root));
4862
+ const { secrets, keep } = await stageSecrets(workspace, manifest, stage);
4863
+ const packageManager = detectPackageManager$2(root);
4864
+ const stack = composeStack({
4865
+ workspace,
4866
+ manifest,
4867
+ runnables,
4868
+ stage,
4869
+ identity,
4870
+ images: {
4871
+ mode,
4872
+ tag,
4873
+ ...workspace.deploy?.dokploy?.registry ? { registry: workspace.deploy.dokploy.registry } : {}
4874
+ },
4875
+ secrets,
4876
+ ports: edgePorts(env),
4877
+ packageManager,
4878
+ packages: Object.fromEntries(Object.entries(workspace.apps).map(([name, app]) => [name, appPackageName$1(root, name, app.path)]))
4879
+ });
4880
+ if (mode === "pull" && !options.dryRun) await assertImagesExist(docker, tag, stack.apps);
4881
+ if (options.dryRun) keep.skip();
4882
+ else await keep.write();
4883
+ const files = await writeStack(root, dir, stack);
4884
+ printPlan(stack, mode, files);
4885
+ if (options.dryRun) return {
4886
+ stack,
4887
+ files
4888
+ };
4889
+ const store = deps.state ? await deps.state(workspace) : await createStateStore({
4890
+ config: workspace.state,
4891
+ workspaceRoot: root,
4892
+ workspaceName: workspace.name
4893
+ });
4894
+ const lock = await store.lock(stage, { operation: "compose" });
4895
+ try {
4896
+ const apps = stack.apps.map((app) => app.name);
4897
+ if (mode === "build") {
4898
+ for (const app of stack.apps.filter((a) => a.kind === "rest-api")) {
4899
+ logger$4.log(`\n📦 Bundling ${app.name}…`);
4900
+ await bundle(join(root, app.path));
4901
+ }
4902
+ logger$4.log("\n🐳 Building images…");
4903
+ await docker.build(ref, apps);
4904
+ } else {
4905
+ logger$4.log(`\n🐳 Pulling ${tag}…`);
4906
+ await docker.pull(ref, apps);
4907
+ }
4908
+ if (stack.infra.length > 0) {
4909
+ logger$4.log(`\n🗄️ Starting ${stack.infra.join(", ")}…`);
4910
+ await docker.up(ref, stack.infra);
4911
+ }
4912
+ if (stack.infra.includes("postgres")) await prepareDatabases(stack, workspace, manifest, sources, {
4913
+ docker,
4914
+ ref,
4915
+ sql: deps.sql ?? pgClient,
4916
+ migrate: deps.migrate ?? migrateDatabases
4917
+ });
4918
+ logger$4.log("\n🚀 Starting the stack…");
4919
+ await docker.up(ref);
4920
+ if (stack.local) await docker.copyOut(ref, "caddy", "/data/caddy/pki/authorities/local/root.crt", join(dir, "caddy-root.crt")).catch(() => {});
4921
+ const images = await recordImages(store, stack, docker, mode);
4922
+ logger$4.log(`\n✅ ${ref.project} is running:`);
4923
+ for (const app of stack.apps) logger$4.log(` ${app.name.padEnd(12)} ${app.url}`);
4924
+ if (stack.local) logger$4.log(`\n🔐 Certificates are from Caddy's local CA: NODE_EXTRA_CA_CERTS=${join(stackDir(stage), "caddy-root.crt")}`);
4925
+ return {
4926
+ stack,
4927
+ files,
4928
+ images
4929
+ };
4930
+ } finally {
4931
+ await lock.release();
4932
+ }
4933
+ }
4934
+ /**
4935
+ * The stage's secrets — and, for a deployed stage, everything it generates
4936
+ * once: its seed, and each declared secret and keyring. Written back before
4937
+ * anything is derived from them, so the next run derives the same passwords.
4938
+ */
4939
+ async function stageSecrets(workspace, manifest, stage) {
4940
+ const store = await secretsStoreFor(workspace, stage);
4941
+ const stored = await store.read(stage);
4942
+ const nothing = {
4943
+ write: async () => {},
4944
+ skip: () => {}
4945
+ };
4946
+ if (stage === workspace.stages.local) return {
4947
+ secrets: stored,
4948
+ keep: nothing
4949
+ };
4950
+ const { secrets, generated } = withGeneratedSecrets(stored ?? initStageSecrets(stage), manifest);
4951
+ if (generated.length === 0) return {
4952
+ secrets,
4953
+ keep: nothing
4954
+ };
4955
+ return {
4956
+ secrets,
4957
+ keep: {
4958
+ async write() {
4959
+ await store.write(stage, secrets);
4960
+ logger$4.log(`🔑 Generated for "${stage}" (${store.name}): ${generated.join(", ")}`);
4961
+ },
4962
+ skip() {
4963
+ logger$4.log(`🔑 "${stage}" has no ${generated.join(", ")} yet; this dry run used values it did not keep.`);
4964
+ }
4965
+ }
4966
+ };
4967
+ }
4968
+ /** Write the stack's files: owner-only, since the env files hold secrets. */
4969
+ async function writeStack(root, dir, stack) {
4970
+ await mkdir(dir, {
4971
+ recursive: true,
4972
+ mode: 448
4973
+ });
4974
+ await chmod(dir, 448);
4975
+ const files = [];
4976
+ const write = async (path, content, mode = 420) => {
4977
+ await mkdir(dirname(path), { recursive: true });
4978
+ await writeFile(path, content, { mode });
4979
+ await chmod(path, mode);
4980
+ files.push(path);
4981
+ };
4982
+ await write(join(dir, "docker-compose.yml"), composeYaml(stack));
4983
+ await write(join(dir, "Caddyfile"), stack.caddyfile);
4984
+ for (const app of stack.apps) if (app.env) await write(join(dir, `${app.name}.env`), envFile(app.env), 384);
4985
+ for (const [path, content] of Object.entries(stack.dockerfiles)) await write(join(root, path), content);
4986
+ await ignoreStacks(root);
4987
+ return files;
4988
+ }
4989
+ /** Make sure the build context leaves `.gkm/compose` out. */
4990
+ async function ignoreStacks(root) {
4991
+ const path = join(root, ".dockerignore");
4992
+ if (!existsSync(path)) {
4993
+ await writeFile(path, generateDockerignore());
4994
+ return;
4995
+ }
4996
+ const current = await readFile(path, "utf-8");
4997
+ if (/^\/?\.gkm\/compose\/?$|^\/?\.gkm\/?$/m.test(current)) return;
4998
+ await appendFile(path, `${current.endsWith("\n") ? "" : "\n"}\n# gkm compose's stacks: each app's env file holds its stage's secrets\n.gkm/compose\n`);
4999
+ }
5000
+ function composeYaml(stack) {
5001
+ return `# Generated by gkm compose from the construct manifest — do not edit.
5002
+ # The ${stack.stage} stage's APIs and sites behind one Caddy. Each backend
5003
+ # reads exactly the keys in its own env file beside this one.
5004
+ #
5005
+ # gkm compose${stack.local ? "" : ` --stage ${stack.stage}`} start or update it
5006
+ # gkm compose${stack.local ? "" : ` --stage ${stack.stage}`} --down stop it
5007
+ ${stringify(stack.compose, {
5008
+ lineWidth: 0,
5009
+ aliasDuplicateObjects: false
5010
+ })}`;
5011
+ }
5012
+ /** Create what the plan names in the stack's Postgres, then migrate it. */
5013
+ async function prepareDatabases(stack, workspace, manifest, sources, deps) {
5014
+ const port = await deps.docker.port(deps.ref, "postgres", 5432);
5015
+ const statements = postgresStatements(stack.plan, workspace.name, stack.credential.seed);
5016
+ if (statements.length > 0) {
5017
+ logger$4.log("🗄️ Creating databases, roles and grants…");
5018
+ await applyPostgres(deps.sql(port, stack.credential.master), statements);
5019
+ }
5020
+ const env = envFor(stack.plan, {
5021
+ ports: { [primaryPortKey("postgres")]: port },
5022
+ project: workspace.name,
5023
+ master: stack.credential.master,
5024
+ ...stack.credential.seed ? { seed: stack.credential.seed } : {}
5025
+ });
5026
+ const runs = await deps.migrate({
5027
+ root: workspace.root,
5028
+ manifest,
5029
+ sources,
5030
+ env
5031
+ });
5032
+ for (const { target, applied } of runs) if (applied.length > 0) logger$4.log(`🗄️ ${target.migrations}: applied ${applied.length}`);
5033
+ }
5034
+ /**
5035
+ * Record each app's image in the stage's state: the tag it ran and the digest
5036
+ * the tag resolved to — or, for an image built here and never pushed, its
5037
+ * content id.
5038
+ */
5039
+ async function recordImages(store, stack, docker, mode) {
5040
+ if (!await store.read(stack.stage)) await store.write(stack.stage, createEmptyState(stack.stage, "", ""), { expectedVersion: null });
5041
+ const images = {};
5042
+ for (const app of stack.apps) {
5043
+ const digest = await docker.digest(app.ref);
5044
+ images[app.name] = {
5045
+ ref: app.ref,
5046
+ tag: app.tag,
5047
+ ...digest ? { digest } : {}
5048
+ };
5049
+ await store.putResource(stack.stage, {
5050
+ key: `compose:${app.name}`,
5051
+ type: "compose-image",
5052
+ status: "ready",
5053
+ ...digest ? { id: digest } : {},
5054
+ data: {
5055
+ project: stack.project,
5056
+ ref: app.ref,
5057
+ tag: app.tag,
5058
+ source: mode,
5059
+ ...digest ? { digest } : {}
5060
+ }
5061
+ });
5062
+ }
5063
+ return images;
5064
+ }
5065
+ function printPlan(stack, mode, files) {
5066
+ logger$4.log(`\n🧱 ${stack.project} — stage ${stack.stage}`);
5067
+ for (const app of stack.apps) logger$4.log(` ${app.name.padEnd(12)} ${app.url.padEnd(40)} ${mode === "build" ? "build" : "pull"} ${app.ref}`);
5068
+ if (stack.infra.length > 0) logger$4.log(` infrastructure: ${stack.infra.join(", ")}`);
5069
+ logger$4.log(`\n📝 Wrote ${files.length} file(s) under ${stackDir(stack.stage)}/`);
5070
+ }
3516
5071
  /**
3517
5072
  * Format a fatal error for display.
3518
5073
  * Always includes the full stack trace since these are process-ending errors.
@@ -3620,7 +5175,7 @@ async function deployInitCommand(options) {
3620
5175
  logger$3.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
3621
5176
  logger$3.log(`\n📝 Next steps:`);
3622
5177
  logger$3.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
3623
- logger$3.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
5178
+ logger$3.log(` 2. Deploy: gkm deploy --stage production`);
3624
5179
  return config;
3625
5180
  }
3626
5181
  /**
@@ -4918,25 +6473,25 @@ function loadPackageJson() {
4918
6473
  * Run: pnpm --filter @geekmidas/cli sync-versions
4919
6474
  */
4920
6475
  const GEEKMIDAS_VERSIONS = {
4921
- "@geekmidas/audit": "~10.0.0-alpha.60",
4922
- "@geekmidas/auth": "~10.0.0-alpha.60",
4923
- "@geekmidas/cache": "~10.0.0-alpha.60",
4924
- "@geekmidas/client": "~10.0.0-alpha.60",
4925
- "@geekmidas/cloud": "~10.0.0-alpha.60",
4926
- "@geekmidas/constructs": "~10.0.0-alpha.60",
4927
- "@geekmidas/db": "~10.0.0-alpha.60",
4928
- "@geekmidas/emailkit": "~10.0.0-alpha.60",
4929
- "@geekmidas/envkit": "~10.0.0-alpha.60",
4930
- "@geekmidas/errors": "~10.0.0-alpha.60",
4931
- "@geekmidas/events": "~10.0.0-alpha.60",
4932
- "@geekmidas/logger": "~10.0.0-alpha.60",
4933
- "@geekmidas/manifest": "~10.0.0-alpha.60",
4934
- "@geekmidas/rate-limit": "~10.0.0-alpha.60",
4935
- "@geekmidas/schema": "~10.0.0-alpha.60",
4936
- "@geekmidas/services": "~10.0.0-alpha.60",
4937
- "@geekmidas/storage": "~10.0.0-alpha.60",
4938
- "@geekmidas/telescope": "~10.0.0-alpha.60",
4939
- "@geekmidas/testkit": "~10.0.0-alpha.60",
6476
+ "@geekmidas/audit": "~10.0.0-alpha.62",
6477
+ "@geekmidas/auth": "~10.0.0-alpha.62",
6478
+ "@geekmidas/cache": "~10.0.0-alpha.62",
6479
+ "@geekmidas/client": "~10.0.0-alpha.62",
6480
+ "@geekmidas/cloud": "~10.0.0-alpha.62",
6481
+ "@geekmidas/constructs": "~10.0.0-alpha.62",
6482
+ "@geekmidas/db": "~10.0.0-alpha.62",
6483
+ "@geekmidas/emailkit": "~10.0.0-alpha.62",
6484
+ "@geekmidas/envkit": "~10.0.0-alpha.62",
6485
+ "@geekmidas/errors": "~10.0.0-alpha.62",
6486
+ "@geekmidas/events": "~10.0.0-alpha.62",
6487
+ "@geekmidas/logger": "~10.0.0-alpha.62",
6488
+ "@geekmidas/manifest": "~10.0.0-alpha.62",
6489
+ "@geekmidas/rate-limit": "~10.0.0-alpha.62",
6490
+ "@geekmidas/schema": "~10.0.0-alpha.62",
6491
+ "@geekmidas/services": "~10.0.0-alpha.62",
6492
+ "@geekmidas/storage": "~10.0.0-alpha.62",
6493
+ "@geekmidas/telescope": "~10.0.0-alpha.62",
6494
+ "@geekmidas/testkit": "~10.0.0-alpha.62",
4940
6495
  "@geekmidas/cli": `~${loadPackageJson().version}`
4941
6496
  };
4942
6497
  //#endregion
@@ -6466,7 +8021,7 @@ const perStage = (options, command) => Object.fromEntries(options.stages.deploye
6466
8021
  function deployPackage(options) {
6467
8022
  switch (options.deployTarget) {
6468
8023
  case "dokploy": return {
6469
- scripts: perStage(options, (stage) => `gkm deploy --provider dokploy --stage ${stage}`),
8024
+ scripts: perStage(options, (stage) => `gkm deploy --stage ${stage}`),
6470
8025
  dependencies: {},
6471
8026
  devDependencies: {}
6472
8027
  };
@@ -10837,7 +12392,7 @@ function testDatabases(reconciled) {
10837
12392
  * starts without the reference data the app needs.
10838
12393
  */
10839
12394
  async function migrateTestStage(workspace, env) {
10840
- const { migrateAndSeed } = await import("./migrate-B_6KofUo.mjs");
12395
+ const { migrateAndSeed } = await import("./migrate-BoHMqdCu.mjs");
10841
12396
  const { discover } = await import("./discover-FrYvzkeC.mjs").then((n) => n.n);
10842
12397
  const sources = {};
10843
12398
  const manifest = await discover({
@@ -11268,7 +12823,7 @@ program.command("migrate").description("Apply each database construct's migratio
11268
12823
  try {
11269
12824
  const globalOptions = program.opts();
11270
12825
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11271
- const { migrateCommand } = await import("./migrate-B_6KofUo.mjs");
12826
+ const { migrateCommand } = await import("./migrate-BoHMqdCu.mjs");
11272
12827
  await migrateCommand({
11273
12828
  ...options,
11274
12829
  ...construct ? { construct } : {}
@@ -11282,7 +12837,7 @@ program.command("seed").description("Migrate, then run each database construct's
11282
12837
  try {
11283
12838
  const globalOptions = program.opts();
11284
12839
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11285
- const { seedCommand } = await import("./migrate-B_6KofUo.mjs");
12840
+ const { seedCommand } = await import("./migrate-BoHMqdCu.mjs");
11286
12841
  await seedCommand({
11287
12842
  ...options,
11288
12843
  ...construct ? { construct } : {}
@@ -11296,7 +12851,7 @@ program.command("migration").description("Write a construct's next migration: an
11296
12851
  try {
11297
12852
  const globalOptions = program.opts();
11298
12853
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11299
- const { migrationCommand } = await import("./migrate-B_6KofUo.mjs");
12854
+ const { migrationCommand } = await import("./migrate-BoHMqdCu.mjs");
11300
12855
  await migrationCommand({
11301
12856
  construct,
11302
12857
  ...name ? { name } : {}
@@ -11341,6 +12896,16 @@ program.command("docker").description("Generate Docker deployment files").option
11341
12896
  process.exit(1);
11342
12897
  }
11343
12898
  });
12899
+ program.command("compose").description("Run the workspace's APIs and sites for a stage as one Docker Compose stack behind Caddy").option("--stage <stage>", "Stage to run (default: the local stage)").option("--tag <tag>", "Run the images CI pushed at this tag (sites: <tag>-<stage>); nothing is built").option("--build", "Build images from this checkout, tagged with the commit").option("--pull", "Pull images (at --tag, or latest) rather than build them").option("--dry-run", "Write the files and print the plan; start nothing").option("--down", "Stop the stage's stack (its volumes are kept)").action(async (options) => {
12900
+ try {
12901
+ const globalOptions = program.opts();
12902
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12903
+ await composeCommand(options);
12904
+ } catch (error) {
12905
+ console.error(formatError(error));
12906
+ process.exit(1);
12907
+ }
12908
+ });
11344
12909
  program.command("prepack").description("Generate Docker files for production deployment").option("--build", "Build Docker image after generating files").option("--push", "Push image to registry after building").option("--tag <tag>", "Image tag", "latest").option("--registry <registry>", "Container registry URL").option("--slim", "Build locally first, then use slim Dockerfile").option("--skip-bundle", "Skip bundling step (only with --slim)").option("--turbo", "Use turbo prune for monorepo optimization").option("--turbo-package <name>", "Package name for turbo prune").action(async (options) => {
11345
12910
  try {
11346
12911
  const globalOptions = program.opts();
@@ -11394,7 +12959,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
11394
12959
  try {
11395
12960
  const globalOptions = program.opts();
11396
12961
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11397
- const { encryptionRotateCommand } = await import("./encryption-aDA2AzVx.mjs");
12962
+ const { encryptionRotateCommand } = await import("./encryption-CVwvGbsL.mjs");
11398
12963
  await encryptionRotateCommand(construct, options);
11399
12964
  } catch (error) {
11400
12965
  console.error(formatError(error));
@@ -11405,7 +12970,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
11405
12970
  try {
11406
12971
  const globalOptions = program.opts();
11407
12972
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11408
- const { encryptionRetireCommand } = await import("./encryption-aDA2AzVx.mjs");
12973
+ const { encryptionRetireCommand } = await import("./encryption-CVwvGbsL.mjs");
11409
12974
  await encryptionRetireCommand(construct, key, options);
11410
12975
  } catch (error) {
11411
12976
  console.error(formatError(error));
@@ -11457,7 +13022,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
11457
13022
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
11458
13023
  process.exit(1);
11459
13024
  }
11460
- const { derivedContainers } = await import("./sniffer-BM8fpCas.mjs").then((n) => n.A);
13025
+ const { derivedContainers } = await import("./sniffer-DHE2Nafd.mjs").then((n) => n.q);
11461
13026
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
11462
13027
  if (!result) {
11463
13028
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -11471,12 +13036,13 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
11471
13036
  process.exit(1);
11472
13037
  }
11473
13038
  });
11474
- program.command("deploy").description("Deploy application to a provider").requiredOption("--provider <provider>", "Deploy provider (docker, dokploy, aws-lambda)").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").option("--tag <tag>", "Image tag (default: stage-timestamp)").option("--skip-push", "Skip pushing image to registry").option("--skip-build", "Skip build step (use existing build)").option("--json", "Write events as JSON lines instead of progress; never prompts").option("--dry-run", "Show what would be created or reused; change, build and push nothing").action(async (options) => {
11475
- const { deployCli } = await import("./cli-DzR1hpum.mjs");
13039
+ program.command("deploy").description("Deploy a stage through its target").option("--target <name>", "Deploy target: dokploy, or one named in deploy.targets (default: deploy.default)").option("--provider <provider>", "[DEPRECATED] Use --target instead").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").option("--tag <tag>", "Image tag (default: stage-timestamp)").option("--skip-push", "Skip pushing image to registry").option("--skip-build", "Skip build step (use existing build)").option("--json", "Write events as JSON lines instead of progress; never prompts").option("--dry-run", "Show what would be created or reused; change, build and push nothing").action(async (options) => {
13040
+ const { deployCli } = await import("./cli-DYwxmfsW.mjs");
11476
13041
  const globalOptions = program.opts();
11477
13042
  const code = await deployCli({
11478
13043
  cwd: resolve(globalOptions.cwd ?? process.cwd()),
11479
- provider: options.provider,
13044
+ ...options.target ? { target: options.target } : {},
13045
+ ...options.provider ? { provider: options.provider } : {},
11480
13046
  stage: options.stage,
11481
13047
  ...options.tag ? { tag: options.tag } : {},
11482
13048
  ...options.json ? { json: true } : {},
@@ -11504,7 +13070,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
11504
13070
  try {
11505
13071
  const globalOptions = program.opts();
11506
13072
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11507
- const { deployGithubCommand } = await import("./github-DmCj2u95.mjs");
13073
+ const { deployGithubCommand } = await import("./github-BCB3_LBX.mjs");
11508
13074
  await deployGithubCommand(options);
11509
13075
  } catch (error) {
11510
13076
  console.error(formatError(error));
@@ -11639,6 +13205,6 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
11639
13205
  });
11640
13206
  program.parse();
11641
13207
  //#endregion
11642
- export { formatError as i, slug as n, stamp as r, generateFullstackCustomSecrets as t };
13208
+ export { DuplicateMigration as _, KyselyNotFound as a, NoOwnerCredential as c, UnknownDatabaseFolder as d, UnknownMigrationFolder as f, seedDatabases as g, pendingMigrations as h, formatError as i, NoSuchMigrationTarget as l, migrateDatabases as m, slug as n, MigrationFailed as o, migrateAndSeed as p, stamp as r, MigrationsOutsideFolder as s, generateFullstackCustomSecrets as t, SeedFailed as u, MigrationHasNoUp as v, SeedHasNoSeed as y };
11643
13209
 
11644
13210
  //# sourceMappingURL=index.mjs.map