@geekmidas/cli 10.0.0-alpha.27 → 10.0.0-alpha.29

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 (74) hide show
  1. package/dist/{HostingerProvider-BY26bWwB.mjs → HostingerProvider-Btxpto71.mjs} +2 -2
  2. package/dist/{HostingerProvider-BY26bWwB.mjs.map → HostingerProvider-Btxpto71.mjs.map} +1 -1
  3. package/dist/{bundler-mwH7WOb1.mjs → bundler-7iaO93G8.mjs} +3 -3
  4. package/dist/{bundler-mwH7WOb1.mjs.map → bundler-7iaO93G8.mjs.map} +1 -1
  5. package/dist/{bundler-mjqGAvu1.cjs → bundler-CYF5mWi9.cjs} +2 -2
  6. package/dist/{bundler-mjqGAvu1.cjs.map → bundler-CYF5mWi9.cjs.map} +1 -1
  7. package/dist/{config-7R_-WyHq.cjs → config-BTFLHfRb.cjs} +2 -2
  8. package/dist/{config-7R_-WyHq.cjs.map → config-BTFLHfRb.cjs.map} +1 -1
  9. package/dist/{config-DXrrYvob.mjs → config-Dqxa-_Ge.mjs} +2 -2
  10. package/dist/{config-DXrrYvob.mjs.map → config-Dqxa-_Ge.mjs.map} +1 -1
  11. package/dist/config.cjs +1 -1
  12. package/dist/config.d.cts +2 -2
  13. package/dist/config.d.mts +2 -2
  14. package/dist/config.mjs +1 -1
  15. package/dist/deploy/sniffer-loader.cjs +1 -1
  16. package/dist/{discover-B_HtYJCr.cjs → discover-BTh87DOO.cjs} +24 -2
  17. package/dist/{discover-B_HtYJCr.cjs.map → discover-BTh87DOO.cjs.map} +1 -1
  18. package/dist/{discover-0fhCbnk0.mjs → discover-CXGlQHm1.mjs} +24 -2
  19. package/dist/{discover-0fhCbnk0.mjs.map → discover-CXGlQHm1.mjs.map} +1 -1
  20. package/dist/{github-Ba5kXEJo.mjs → github-DPpNfzu4.mjs} +4 -4
  21. package/dist/{github-Ba5kXEJo.mjs.map → github-DPpNfzu4.mjs.map} +1 -1
  22. package/dist/{github-CQflOKKV.cjs → github-ZL3RcUVD.cjs} +6 -6
  23. package/dist/{github-CQflOKKV.cjs.map → github-ZL3RcUVD.cjs.map} +1 -1
  24. package/dist/{index-BqWKWRm0.d.mts → index-p89EzILa.d.mts} +2 -2
  25. package/dist/{index-BPN7kghQ.d.cts.map → index-p89EzILa.d.mts.map} +1 -1
  26. package/dist/{index-BPN7kghQ.d.cts → index-rU9u761a.d.cts} +2 -2
  27. package/dist/{index-BqWKWRm0.d.mts.map → index-rU9u761a.d.cts.map} +1 -1
  28. package/dist/index.cjs +901 -822
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.mjs +894 -817
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/{openapi-3NLVvtz-.mjs → openapi-D-Lk5Hr5.mjs} +3 -3
  33. package/dist/{openapi-3NLVvtz-.mjs.map → openapi-D-Lk5Hr5.mjs.map} +1 -1
  34. package/dist/{openapi-rhJdy2GC.cjs → openapi-K4H5tHzA.cjs} +3 -3
  35. package/dist/{openapi-rhJdy2GC.cjs.map → openapi-K4H5tHzA.cjs.map} +1 -1
  36. package/dist/openapi.cjs +1 -1
  37. package/dist/openapi.d.cts +1 -1
  38. package/dist/openapi.d.mts +1 -1
  39. package/dist/openapi.mjs +1 -1
  40. package/dist/reconcile/public.cjs +1 -1
  41. package/dist/reconcile/public.d.cts +13 -1
  42. package/dist/reconcile/public.d.cts.map +1 -1
  43. package/dist/reconcile/public.d.mts +13 -1
  44. package/dist/reconcile/public.d.mts.map +1 -1
  45. package/dist/reconcile/public.mjs +1 -1
  46. package/dist/{reconcile-szmJw_Sb.cjs → reconcile-BxZTV2vl.cjs} +5 -82
  47. package/dist/reconcile-BxZTV2vl.cjs.map +1 -0
  48. package/dist/{reconcile-DhbCvh4K.mjs → reconcile-yNq6Shau.mjs} +5 -82
  49. package/dist/reconcile-yNq6Shau.mjs.map +1 -0
  50. package/dist/{storage-TP0x118M.mjs → storage-BjTLjDDp.mjs} +1 -8
  51. package/dist/{storage-TP0x118M.mjs.map → storage-BjTLjDDp.mjs.map} +1 -1
  52. package/dist/{storage-DgypfSD6.cjs → storage-C2JC31V9.cjs} +1 -8
  53. package/dist/{storage-DgypfSD6.cjs.map → storage-C2JC31V9.cjs.map} +1 -1
  54. package/dist/{types-7R81hu0i.d.mts → types-CzK6hsXP.d.mts} +1 -17
  55. package/dist/types-CzK6hsXP.d.mts.map +1 -0
  56. package/dist/{types-N7c2O2m7.d.cts → types-DOOAevjG.d.cts} +1 -17
  57. package/dist/types-DOOAevjG.d.cts.map +1 -0
  58. package/dist/workspace/index.d.cts +2 -2
  59. package/dist/workspace/index.d.mts +2 -2
  60. package/dist/{workspace-DgeVgxmm.cjs → workspace-B3ApLW6p.cjs} +129 -10
  61. package/dist/workspace-B3ApLW6p.cjs.map +1 -0
  62. package/dist/{workspace-CUmR_k4J.mjs → workspace-DnHW8ooI.mjs} +125 -12
  63. package/dist/workspace-DnHW8ooI.mjs.map +1 -0
  64. package/package.json +14 -14
  65. package/dist/reconcile-DhbCvh4K.mjs.map +0 -1
  66. package/dist/reconcile-szmJw_Sb.cjs.map +0 -1
  67. package/dist/transfer-BV00TxZv.mjs +0 -122
  68. package/dist/transfer-BV00TxZv.mjs.map +0 -1
  69. package/dist/transfer-BYIn7z8m.cjs +0 -151
  70. package/dist/transfer-BYIn7z8m.cjs.map +0 -1
  71. package/dist/types-7R81hu0i.d.mts.map +0 -1
  72. package/dist/types-N7c2O2m7.d.cts.map +0 -1
  73. package/dist/workspace-CUmR_k4J.mjs.map +0 -1
  74. package/dist/workspace-DgeVgxmm.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
4
- const require_discover = require("./discover-B_HtYJCr.cjs");
5
- const require_config = require("./config-7R_-WyHq.cjs");
4
+ const require_discover = require("./discover-BTh87DOO.cjs");
5
+ const require_config = require("./config-BTFLHfRb.cjs");
6
6
  const require_workspace = require("./workspace-Bav3TVrP.cjs");
7
- const require_storage = require("./storage-DgypfSD6.cjs");
8
- const require_workspace$1 = require("./workspace-DgeVgxmm.cjs");
9
- const require_openapi = require("./openapi-rhJdy2GC.cjs");
7
+ const require_storage = require("./storage-C2JC31V9.cjs");
8
+ const require_workspace$1 = require("./workspace-B3ApLW6p.cjs");
9
+ const require_openapi = require("./openapi-K4H5tHzA.cjs");
10
10
  const require_emit = require("./emit-BdoPFjZf.cjs");
11
- const require_reconcile = require("./reconcile-szmJw_Sb.cjs");
12
- const require_transfer = require("./transfer-BYIn7z8m.cjs");
11
+ const require_reconcile = require("./reconcile-BxZTV2vl.cjs");
13
12
  let commander = require("commander");
14
13
  let node_process = require("node:process");
15
14
  let node_readline_promises = require("node:readline/promises");
@@ -22,6 +21,7 @@ let node_child_process = require("node:child_process");
22
21
  let _geekmidas_manifest = require("@geekmidas/manifest");
23
22
  let fast_glob = require("fast-glob");
24
23
  fast_glob = require_rolldown_runtime.__toESM(fast_glob, 1);
24
+ let node_module = require("node:module");
25
25
  let chokidar = require("chokidar");
26
26
  chokidar = require_rolldown_runtime.__toESM(chokidar, 1);
27
27
  let node_crypto = require("node:crypto");
@@ -31,19 +31,18 @@ let pg = require("pg");
31
31
  let yaml = require("yaml");
32
32
  let node_util = require("node:util");
33
33
  let _geekmidas_cache_postgres = require("@geekmidas/cache/postgres");
34
+ let prompts = require("prompts");
35
+ prompts = require_rolldown_runtime.__toESM(prompts, 1);
34
36
  let _geekmidas_constructs_crons = require("@geekmidas/constructs/crons");
35
37
  let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions");
36
38
  let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
37
39
  let _geekmidas_constructs_subscribers = require("@geekmidas/constructs/subscribers");
38
40
  let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
39
41
  let node_dns_promises = require("node:dns/promises");
40
- let node_module = require("node:module");
41
- let prompts = require("prompts");
42
- prompts = require_rolldown_runtime.__toESM(prompts, 1);
43
42
  let _geekmidas_envkit = require("@geekmidas/envkit");
44
43
  let _geekmidas_services = require("@geekmidas/services");
45
44
  //#region package.json
46
- var version = "10.0.0-alpha.27";
45
+ var version = "10.0.0-alpha.29";
47
46
  //#endregion
48
47
  //#region src/auth/credentials.ts
49
48
  /**
@@ -410,8 +409,182 @@ function normalizeRoutes(routes) {
410
409
  return Array.isArray(routes) ? routes : [routes];
411
410
  }
412
411
  //#endregion
413
- //#region src/exec/index.ts
412
+ //#region src/trust/index.ts
413
+ /**
414
+ * Installing the local edge's certificate authority into the OS trust store.
415
+ *
416
+ * `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test
417
+ * suite, anything under `gkm exec` — because reconcile injects it. A browser
418
+ * reads none of that: it asks the operating system, so until the root is in the
419
+ * system store a local `https://` address is "not secure", which is true and
420
+ * unhelpful.
421
+ *
422
+ * A separate command rather than part of `gkm setup`, for one reason: this
423
+ * needs `sudo`, and a setup that silently escalates on every run is a worse
424
+ * trade than one that tells you what to type. Caddy's own `caddy trust` draws
425
+ * the line in the same place.
426
+ */
414
427
  const logger$12 = console;
428
+ const run = (0, node_util.promisify)(node_child_process.execFile);
429
+ /**
430
+ * How each platform is told about a new authority.
431
+ *
432
+ * Separate rather than shelled through one script because the failure modes
433
+ * differ and the message should name the real one: macOS refuses without an
434
+ * admin password, Debian needs the file in a specific directory before its
435
+ * updater will look at it.
436
+ */
437
+ function installation(platform, certificate) {
438
+ if (platform === "darwin") return {
439
+ describe: "the System keychain",
440
+ steps: [["sudo", [
441
+ "security",
442
+ "add-trusted-cert",
443
+ "-d",
444
+ "-r",
445
+ "trustRoot",
446
+ "-k",
447
+ "/Library/Keychains/System.keychain",
448
+ certificate
449
+ ]]]
450
+ };
451
+ if (platform === "linux") {
452
+ const debian = (0, node_fs.existsSync)("/usr/local/share/ca-certificates");
453
+ return {
454
+ describe: debian ? "the system CA bundle" : "the system trust anchors",
455
+ steps: debian ? [["sudo", [
456
+ "cp",
457
+ certificate,
458
+ "/usr/local/share/ca-certificates/gkm-local-ca.crt"
459
+ ]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
460
+ "cp",
461
+ certificate,
462
+ "/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
463
+ ]], ["sudo", ["update-ca-trust"]]]
464
+ };
465
+ }
466
+ throw new UnsupportedPlatform(platform, certificate);
467
+ }
468
+ /**
469
+ * Trust the certificate authority this project's edge issues from.
470
+ *
471
+ * Idempotent in effect: installing a root that is already trusted replaces the
472
+ * same entry, so running it twice is not an error.
473
+ */
474
+ async function trustCommand(options = {}) {
475
+ const { workspace } = await require_config.loadWorkspaceConfig();
476
+ const certificate = (0, node_path.join)(workspace.root, require_workspace$1.LOCAL_CA_PATH);
477
+ if (!(0, node_fs.existsSync)(certificate)) throw new NoLocalAuthority(certificate);
478
+ const { describe, steps } = installation(process.platform, certificate);
479
+ logger$12.log(`\n🔐 Trusting the local authority in ${describe}`);
480
+ logger$12.log(` ${certificate}\n`);
481
+ if (options.dryRun) {
482
+ for (const [command, args] of steps) logger$12.log(` ${command} ${args.join(" ")}`);
483
+ return;
484
+ }
485
+ logger$12.log(" This needs an admin password.\n");
486
+ for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
487
+ logger$12.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
488
+ }
489
+ /**
490
+ * Trust the authority if this machine lacks it and its owner agrees.
491
+ *
492
+ * A machine's answer, not the project's: whether a laptop installs a root
493
+ * certificate is its owner's call, so the decision is `--yes`, then what this
494
+ * machine previously answered, then a prompt. Never a silent `sudo` — that is
495
+ * the one thing a setup command should not do on somebody's behalf.
496
+ *
497
+ * Silent and free when the machine already trusts it, which is what makes it
498
+ * acceptable to call on every `gkm setup`.
499
+ */
500
+ async function ensureTrusted(root, url, options = {}) {
501
+ if (await isTrusted(url)) return;
502
+ if (!options.assumeYes) {
503
+ if (!process.stdin.isTTY) {
504
+ logger$12.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
505
+ return;
506
+ }
507
+ if (await declined(root)) return;
508
+ const { install } = await (0, prompts.default)({
509
+ type: "confirm",
510
+ name: "install",
511
+ message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
512
+ initial: false
513
+ });
514
+ if (!install) {
515
+ await recordDeclined(root);
516
+ logger$12.log(" Skipped. Run \"gkm trust\" later.");
517
+ return;
518
+ }
519
+ }
520
+ await trustCommand();
521
+ }
522
+ /** Where a declined answer is remembered, so setup asks once and not again. */
523
+ const TRUST_STATE = ".gkm/trust.json";
524
+ async function declined(root) {
525
+ try {
526
+ const raw = await (0, node_fs_promises.readFile)((0, node_path.join)(root, TRUST_STATE), "utf-8");
527
+ return JSON.parse(raw).declined === true;
528
+ } catch {
529
+ return false;
530
+ }
531
+ }
532
+ async function recordDeclined(root) {
533
+ const path = (0, node_path.join)(root, TRUST_STATE);
534
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
535
+ await (0, node_fs_promises.writeFile)(path, `${JSON.stringify({ declined: true }, null, 2)}\n`);
536
+ }
537
+ /**
538
+ * Whether this machine already trusts the local authority.
539
+ *
540
+ * Asked by connecting rather than by reading a keychain: what matters is
541
+ * whether a client with no special configuration verifies the edge, and that is
542
+ * one platform-neutral question instead of three platform-specific ones. It is
543
+ * also the exact thing a browser is about to do.
544
+ *
545
+ * A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and
546
+ * this process almost certainly has it set — asking in-process would answer
547
+ * "yes" for everyone.
548
+ */
549
+ async function isTrusted(url) {
550
+ const { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;
551
+ try {
552
+ await run(process.execPath, [
553
+ "--use-system-ca",
554
+ "-e",
555
+ `fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`
556
+ ], {
557
+ env,
558
+ timeout: 15e3
559
+ });
560
+ return true;
561
+ } catch {
562
+ return false;
563
+ }
564
+ }
565
+ /** The edge has not run, so there is no authority to trust yet. */
566
+ var NoLocalAuthority = class extends Error {
567
+ path;
568
+ constructor(path) {
569
+ super(`No local certificate authority at ${path}. It is generated the first time the edge starts, so run "gkm setup" (or "gkm dev") once — and if this project declares no file server, nothing needs one.`);
570
+ this.path = path;
571
+ this.name = "NoLocalAuthority";
572
+ }
573
+ };
574
+ /** A platform with no trust store this knows how to write to. */
575
+ var UnsupportedPlatform = class extends Error {
576
+ platform;
577
+ certificate;
578
+ constructor(platform, certificate) {
579
+ super(`gkm does not know how to install a certificate authority on ${platform}. The root is at ${certificate} — add it to the trust store by hand, or point a client at it with NODE_EXTRA_CA_CERTS.`);
580
+ this.platform = platform;
581
+ this.certificate = certificate;
582
+ this.name = "UnsupportedPlatform";
583
+ }
584
+ };
585
+ //#endregion
586
+ //#region src/exec/index.ts
587
+ const logger$11 = console;
415
588
  /** `gkm exec` was given nothing to run. */
416
589
  var NoCommandSpecified = class extends Error {
417
590
  constructor() {
@@ -434,17 +607,17 @@ async function execCommand(commandArgs, options = {}) {
434
607
  const cwd = options.cwd ?? process.cwd();
435
608
  if (!commandArgs[0]) throw new NoCommandSpecified();
436
609
  const defaultEnv = require_workspace$1.loadEnvFiles(".env");
437
- if (defaultEnv.loaded.length > 0) logger$12.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
610
+ if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
438
611
  const { credentials, secretsJsonPath, appName } = await require_workspace$1.prepareEntryCredentials({ cwd });
439
- if (appName) logger$12.log(`📦 App: ${appName}`);
612
+ if (appName) logger$11.log(`📦 App: ${appName}`);
440
613
  const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
441
- if (secretCount > 0) logger$12.log(`🔐 Loaded ${secretCount} secret(s)`);
614
+ if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
442
615
  const preloadDir = (0, node_path.join)(cwd, ".gkm");
443
616
  await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
444
617
  const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.mjs");
445
618
  await require_workspace$1.createCredentialsPreload(preloadPath, secretsJsonPath);
446
619
  const [cmd, ...args] = commandArgs;
447
- logger$12.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
620
+ logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
448
621
  const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
449
622
  const child = (0, node_child_process.spawn)(cmd, args, {
450
623
  cwd,
@@ -458,7 +631,7 @@ async function execCommand(commandArgs, options = {}) {
458
631
  const exitCode = await new Promise((resolve) => {
459
632
  child.on("close", (code) => resolve(code ?? 0));
460
633
  child.on("error", (error) => {
461
- logger$12.error(`Failed to run command: ${error.message}`);
634
+ logger$11.error(`Failed to run command: ${error.message}`);
462
635
  resolve(1);
463
636
  });
464
637
  });
@@ -466,7 +639,7 @@ async function execCommand(commandArgs, options = {}) {
466
639
  }
467
640
  //#endregion
468
641
  //#region src/dev/index.ts
469
- const logger$11 = console;
642
+ const logger$10 = console;
470
643
  /**
471
644
  * Normalize telescope configuration
472
645
  * @internal Exported for testing
@@ -560,7 +733,7 @@ function getProductionConfigFromGkm(config) {
560
733
  async function devCommand(options) {
561
734
  if (options.entry) return entryDevCommand(options);
562
735
  const defaultEnv = require_workspace$1.loadEnvFiles(".env");
563
- if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
736
+ if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
564
737
  const appName = require_config.getAppNameFromCwd();
565
738
  let config;
566
739
  let appRoot = process.cwd();
@@ -576,9 +749,8 @@ async function devCommand(options) {
576
749
  workspaceAppName = appConfig.appName;
577
750
  workspaceAppPort = appConfig.app.port;
578
751
  workspace = appConfig.workspace;
579
- logger$11.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
580
752
  if (appConfig.app.entry) {
581
- logger$11.log(`📄 Using entry point: ${appConfig.app.entry}`);
753
+ logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
582
754
  return entryDevCommand({
583
755
  ...options,
584
756
  entry: appConfig.app.entry,
@@ -588,39 +760,27 @@ async function devCommand(options) {
588
760
  }
589
761
  } catch {
590
762
  const loadedConfig = await require_config.loadWorkspaceConfig();
591
- if (loadedConfig.type === "workspace") {
592
- logger$11.log("📦 Detected workspace configuration");
593
- return workspaceDevCommand(loadedConfig.workspace, options);
594
- }
763
+ if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
595
764
  config = loadedConfig.raw;
596
765
  workspace = loadedConfig.workspace;
597
766
  }
598
767
  else {
599
768
  const loadedConfig = await require_config.loadWorkspaceConfig();
600
- if (loadedConfig.type === "workspace") {
601
- logger$11.log("📦 Detected workspace configuration");
602
- return workspaceDevCommand(loadedConfig.workspace, options);
603
- }
769
+ if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
604
770
  config = loadedConfig.raw;
605
771
  workspace = loadedConfig.workspace;
606
772
  }
607
773
  if (config.env) {
608
774
  const { loaded, missing } = require_workspace$1.loadEnvFiles(config.env, appRoot);
609
- if (loaded.length > 0) logger$11.log(`📦 Loaded env: ${loaded.join(", ")}`);
610
- if (missing.length > 0) logger$11.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
775
+ if (loaded.length > 0) logger$10.log(`📦 Loaded env: ${loaded.join(", ")}`);
776
+ if (missing.length > 0) logger$10.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
611
777
  }
612
778
  const resolved = resolveProviders(config, { provider: "server" });
613
- logger$11.log("🚀 Starting development server...");
614
- logger$11.log(`Loading constructs from: ${config.constructs}`);
615
779
  const telescope = normalizeTelescopeConfig(config.telescope);
616
- if (telescope) logger$11.log(`🔭 Telescope enabled at ${telescope.path}`);
617
780
  const studio = normalizeStudioConfig(config.studio);
618
- if (studio) logger$11.log(`🗄️ Studio enabled at ${studio.path}`);
619
781
  const hooks = normalizeHooksConfig(config.hooks, appRoot);
620
- if (hooks) logger$11.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
621
782
  const enableOpenApi = require_openapi.resolveOpenApiConfig(config).enabled || resolved.enableOpenApi;
622
- if (enableOpenApi) logger$11.log(`📄 OpenAPI output: ${require_openapi.OPENAPI_OUTPUT_PATH}`);
623
- const build = (bustCache = false) => buildApp({
783
+ const build = (bustCache = false) => quietly(() => buildApp({
624
784
  config,
625
785
  workspaceRoot: workspace?.root ?? secretsRoot,
626
786
  appRoot,
@@ -633,18 +793,21 @@ async function devCommand(options) {
633
793
  skipBundle: true,
634
794
  bustCache,
635
795
  serveEmpty: true
636
- });
796
+ }));
637
797
  const initial = await build();
638
798
  const runtime = config.runtime ?? "node";
639
799
  let secretsJsonPath;
800
+ let publicUrl;
640
801
  const appSecrets = await require_workspace$1.loadSecretsForApp(secretsRoot, config.stages.local, workspaceAppName);
641
802
  if (workspace) {
642
803
  const reconciled = await require_workspace$1.reconcileWorkspace(workspace, { stage: workspace.stages.local });
643
804
  if (reconciled.changed && reconciled.plan.containers.length > 0) {
644
- logger$11.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
645
- for (const [container, address] of Object.entries(reconciled.addresses)) logger$11.log(` ${container}: ${address}`);
805
+ logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
806
+ for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
646
807
  }
647
808
  Object.assign(appSecrets, reconciled.env);
809
+ const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && require_config.appKey(r.id) === workspaceAppName);
810
+ publicUrl = own ? reconciled.env[own.envKey] : void 0;
648
811
  }
649
812
  if (Object.keys(appSecrets).length > 0) {
650
813
  const secretsDir = (0, node_path.join)(secretsRoot, ".gkm");
@@ -652,13 +815,13 @@ async function devCommand(options) {
652
815
  const secretsFileName = workspaceAppName ? `dev-secrets-${workspaceAppName}.json` : "dev-secrets.json";
653
816
  secretsJsonPath = (0, node_path.join)(secretsDir, secretsFileName);
654
817
  await (0, node_fs_promises.writeFile)(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
655
- logger$11.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
656
818
  }
657
819
  const devServer = new DevServer(resolved.providers[0], options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? workspaceAppPort !== void 0, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath, initial.selfServing !== void 0);
820
+ devServer.label = workspaceAppName ?? "server";
821
+ devServer.publicUrl = publicUrl;
658
822
  await devServer.start();
659
823
  const hooksFile = (config.hooks?.server?.split("#"))?.[0];
660
824
  const normalizedPatterns = [config.constructs, ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []].flat().filter((p) => typeof p === "string").map((p) => p.startsWith("./") ? p.slice(2) : p);
661
- logger$11.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
662
825
  const resolvedFiles = await (0, fast_glob.default)(normalizedPatterns, {
663
826
  cwd: appRoot,
664
827
  absolute: false,
@@ -667,34 +830,28 @@ async function devCommand(options) {
667
830
  const dirsToWatch = [...new Set(resolvedFiles.map((f) => {
668
831
  return f.split("/").slice(0, -1).join("/");
669
832
  }))];
670
- logger$11.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
671
833
  const watcher = chokidar.default.watch([...resolvedFiles, ...dirsToWatch], {
672
834
  ignored: /(^|[/\\])\../,
673
835
  persistent: true,
674
836
  ignoreInitial: true,
675
837
  cwd: appRoot
676
838
  });
677
- watcher.on("ready", () => {
678
- logger$11.log("🔍 File watcher ready");
679
- });
680
839
  watcher.on("error", (error) => {
681
- logger$11.error("❌ Watcher error:", error);
840
+ logger$10.error("❌ Watcher error:", error);
682
841
  });
683
842
  let rebuildTimeout = null;
684
843
  watcher.on("change", async (path) => {
685
- logger$11.log(`📝 File changed: ${path}`);
844
+ logger$10.log(`🔄 ${path} changed — rebuilding`);
686
845
  if (rebuildTimeout) clearTimeout(rebuildTimeout);
687
846
  rebuildTimeout = setTimeout(async () => {
688
847
  try {
689
- logger$11.log("🔄 Rebuilding...");
690
848
  const rebuilt = await build(true);
691
- logger$11.log("✅ Rebuild complete, restarting server...");
692
849
  devServer.selfServing = rebuilt.selfServing !== void 0;
693
850
  await devServer.restart();
694
851
  } catch (error) {
695
852
  const err = error;
696
- logger$11.error(`❌ Rebuild failed: ${err.message}`);
697
- if (err.stack) logger$11.error(err.stack);
853
+ logger$10.error(`❌ Rebuild failed: ${err.message}`);
854
+ if (err.stack) logger$10.error(err.stack);
698
855
  }
699
856
  }, 300);
700
857
  });
@@ -702,9 +859,9 @@ async function devCommand(options) {
702
859
  const shutdown = () => {
703
860
  if (isShuttingDown) return;
704
861
  isShuttingDown = true;
705
- logger$11.log("\n🛑 Shutting down...");
862
+ logger$10.log("\n🛑 Shutting down...");
706
863
  Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
707
- logger$11.error("Error during shutdown:", err);
864
+ logger$10.error("Error during shutdown:", err);
708
865
  }).finally(() => {
709
866
  process.exit(0);
710
867
  });
@@ -890,12 +1047,8 @@ async function loadDevSecrets(workspace) {
890
1047
  const stage = workspace.stages.local;
891
1048
  if (require_storage.secretsExist(stage, workspace.root)) {
892
1049
  const secrets = await require_storage.readStageSecrets(stage, workspace.root);
893
- if (secrets) {
894
- logger$11.log(`🔐 Loading secrets from stage: ${stage}`);
895
- return require_storage.toEmbeddableSecrets(secrets);
896
- }
1050
+ if (secrets) return require_storage.toEmbeddableSecrets(secrets);
897
1051
  }
898
- logger$11.warn(`⚠️ Secrets enabled but no "${stage}" secrets found. Run "gkm setup" to initialize the local stage`);
899
1052
  return {};
900
1053
  }
901
1054
  /**
@@ -909,38 +1062,30 @@ async function loadDevSecrets(workspace) {
909
1062
  */
910
1063
  async function workspaceDevCommand(workspace, options) {
911
1064
  const appCount = Object.keys(workspace.apps).length;
912
- const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
913
1065
  const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
914
- const mobileApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "mobile");
915
- logger$11.log(`\n🚀 Starting workspace: ${workspace.name}`);
916
- const counts = [`${backendApps.length} backend`, `${frontendApps.length} web`];
917
- if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
918
- logger$11.log(` ${counts.join(", ")} app(s)`);
919
1066
  const conflicts = checkPortConflicts(workspace);
920
1067
  if (conflicts.length > 0) {
921
- for (const conflict of conflicts) logger$11.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1068
+ for (const conflict of conflicts) logger$10.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
922
1069
  throw new Error("Port conflicts detected. Please assign unique ports to each app.");
923
1070
  }
924
1071
  if (frontendApps.length > 0) {
925
- logger$11.log("\n🔍 Validating frontend apps...");
926
1072
  const validationResults = await validateFrontendApps(workspace);
927
1073
  let hasErrors = false;
928
1074
  for (const result of validationResults) {
929
1075
  if (!result.valid) {
930
1076
  hasErrors = true;
931
- logger$11.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
932
- for (const error of result.errors) logger$11.error(` • ${error}`);
1077
+ logger$10.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1078
+ for (const error of result.errors) logger$10.error(` • ${error}`);
933
1079
  }
934
- for (const warning of result.warnings) logger$11.warn(` ⚠️ ${result.appName}: ${warning}`);
1080
+ for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
935
1081
  }
936
1082
  if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
937
- logger$11.log("✅ Frontend apps validated");
938
1083
  }
939
1084
  const rawSecrets = await loadDevSecrets(workspace);
940
1085
  const reconciled = await require_workspace$1.reconcileWorkspace(workspace, { stage: workspace.stages.local });
941
1086
  if (reconciled.changed && reconciled.plan.containers.length > 0) {
942
- logger$11.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
943
- for (const [container, address] of Object.entries(reconciled.addresses)) logger$11.log(` ${container}: ${address}`);
1087
+ logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
1088
+ for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
944
1089
  }
945
1090
  const secretsEnv = {
946
1091
  ...rawSecrets,
@@ -952,11 +1097,8 @@ async function workspaceDevCommand(workspace, options) {
952
1097
  const address = reconciled.env[resource.envKey];
953
1098
  if (address) appUrls[resource.id] = address;
954
1099
  }
955
- if (Object.keys(secretsEnv).length > 0) logger$11.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
956
- if (Object.keys(appUrls).length > 0) {
957
- logger$11.log("🔒 App URLs:");
958
- for (const [id, address] of Object.entries(appUrls)) logger$11.log(` ${id}: ${address}`);
959
- }
1100
+ const firstUrl = Object.values(appUrls).find((url) => url.startsWith("https://"));
1101
+ if (firstUrl) await ensureTrusted(workspace.root, firstUrl);
960
1102
  let turboFilter = [];
961
1103
  if (options.app) {
962
1104
  if (!workspace.apps[options.app]) {
@@ -964,25 +1106,23 @@ async function workspaceDevCommand(workspace, options) {
964
1106
  throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
965
1107
  }
966
1108
  turboFilter = ["--filter", appPackageName(workspace, options.app) ?? options.app];
967
- logger$11.log(`\n🎯 Running single app: ${options.app}`);
968
- } else if (options.filter) {
969
- turboFilter = ["--filter", options.filter];
970
- logger$11.log(`\n🔍 Using filter: ${options.filter}`);
971
- } else {
1109
+ } else if (options.filter) turboFilter = ["--filter", options.filter];
1110
+ else {
972
1111
  const { filters, unpackaged } = turboFilters(workspace);
973
1112
  turboFilter = filters.flatMap((name) => ["--filter", name]);
974
- if (unpackaged.length > 0) logger$11.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
975
- logger$11.log(`\n🎯 Running all ${appCount} apps`);
1113
+ if (unpackaged.length > 0) logger$10.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
976
1114
  }
977
1115
  const buildOrder = require_workspace.getAppBuildOrder(workspace);
978
- logger$11.log("\n📋 Apps (in dependency order):");
1116
+ const width = Math.max(...buildOrder.map((name) => name.length));
1117
+ logger$10.log(`\n${workspace.name}: ${appCount} app(s)`);
979
1118
  for (const appName of buildOrder) {
980
1119
  const app = workspace.apps[appName];
981
1120
  if (!app) continue;
982
- const deps = app.dependencies.length > 0 ? ` (depends on: ${app.dependencies.join(", ")})` : "";
983
- const icon = app.type === "backend" ? "🔧" : app.type === "mobile" ? "📱" : "🌐";
984
- logger$11.log(` ${icon} ${appName} → http://localhost:${app.port}${deps}`);
1121
+ const local = `http://localhost:${app.port}`;
1122
+ const url = Object.entries(appUrls).find(([id]) => require_config.appKey(id) === appName)?.[1];
1123
+ logger$10.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
985
1124
  }
1125
+ logger$10.log("");
986
1126
  const configFiles = [
987
1127
  "gkm.config.ts",
988
1128
  "gkm.config.js",
@@ -1013,7 +1153,6 @@ async function workspaceDevCommand(workspace, options) {
1013
1153
  });
1014
1154
  }
1015
1155
  if (held.length > 0) throw new WorkspacePortsInUse(held);
1016
- logger$11.log("\n🏃 Starting turbo run dev...\n");
1017
1156
  const turboProcess = (0, node_child_process.spawn)("pnpm", [
1018
1157
  "turbo",
1019
1158
  "run",
@@ -1029,7 +1168,7 @@ async function workspaceDevCommand(workspace, options) {
1029
1168
  const shutdown = () => {
1030
1169
  if (isShuttingDown) return;
1031
1170
  isShuttingDown = true;
1032
- logger$11.log("\n🛑 Shutting down workspace...");
1171
+ logger$10.log("\n🛑 Shutting down workspace...");
1033
1172
  const pid = turboProcess.pid;
1034
1173
  if (pid) try {
1035
1174
  process.kill(-pid, "SIGTERM");
@@ -1053,7 +1192,7 @@ async function workspaceDevCommand(workspace, options) {
1053
1192
  process.on("SIGTERM", shutdown);
1054
1193
  return new Promise((resolve, reject) => {
1055
1194
  turboProcess.on("error", (error) => {
1056
- logger$11.error("❌ Turbo error:", error);
1195
+ logger$10.error("❌ Turbo error:", error);
1057
1196
  reject(error);
1058
1197
  });
1059
1198
  turboProcess.on("exit", (code) => {
@@ -1072,11 +1211,11 @@ async function entryDevCommand(options) {
1072
1211
  const entryPath = (0, node_path.resolve)(process.cwd(), entry);
1073
1212
  if (!(0, node_fs.existsSync)(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
1074
1213
  const defaultEnv = require_workspace$1.loadEnvFiles(".env");
1075
- if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1214
+ if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1076
1215
  const { credentials, resolvedPort, secretsJsonPath, appName } = await require_workspace$1.prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
1077
- if (appName) logger$11.log(`📦 App: ${appName} (port ${resolvedPort})`);
1078
- logger$11.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1079
- if (Object.keys(credentials).length > 1) logger$11.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
1216
+ if (appName) logger$10.log(`📦 App: ${appName} (port ${resolvedPort})`);
1217
+ logger$10.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1218
+ if (Object.keys(credentials).length > 1) logger$10.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
1080
1219
  const wrapperDir = (0, node_path.join)(process.cwd(), ".gkm");
1081
1220
  await (0, node_fs_promises.mkdir)(wrapperDir, { recursive: true });
1082
1221
  const wrapperPath = (0, node_path.join)(wrapperDir, "entry-wrapper.ts");
@@ -1087,7 +1226,7 @@ async function entryDevCommand(options) {
1087
1226
  const shutdown = () => {
1088
1227
  if (isShuttingDown) return;
1089
1228
  isShuttingDown = true;
1090
- logger$11.log("\n🛑 Shutting down...");
1229
+ logger$10.log("\n🛑 Shutting down...");
1091
1230
  runner.stop();
1092
1231
  process.exit(0);
1093
1232
  };
@@ -1123,14 +1262,14 @@ var EntryRunner = class {
1123
1262
  });
1124
1263
  let restartTimeout = null;
1125
1264
  this.watcher.on("change", (path) => {
1126
- logger$11.log(`📝 File changed: ${path}`);
1265
+ logger$10.log(`📝 File changed: ${path}`);
1127
1266
  if (restartTimeout) clearTimeout(restartTimeout);
1128
1267
  restartTimeout = setTimeout(async () => {
1129
- logger$11.log("🔄 Restarting...");
1268
+ logger$10.log("🔄 Restarting...");
1130
1269
  await this.restart();
1131
1270
  }, 300);
1132
1271
  });
1133
- logger$11.log(`👀 Watching for changes in: ${watchDir}`);
1272
+ logger$10.log(`👀 Watching for changes in: ${watchDir}`);
1134
1273
  }
1135
1274
  }
1136
1275
  async runProcess() {
@@ -1138,24 +1277,24 @@ var EntryRunner = class {
1138
1277
  ...process.env,
1139
1278
  PORT: String(this.port)
1140
1279
  };
1141
- this.childProcess = (0, node_child_process.spawn)("npx", ["tsx", this.wrapperPath], {
1280
+ this.childProcess = (0, node_child_process.spawn)(...tsxCommand((0, node_path.dirname)(this.wrapperPath), [this.wrapperPath]), {
1142
1281
  stdio: "inherit",
1143
1282
  env,
1144
1283
  detached: true
1145
1284
  });
1146
1285
  this.isRunning = true;
1147
1286
  this.childProcess.on("error", (error) => {
1148
- logger$11.error("❌ Process error:", error);
1287
+ logger$10.error("❌ Process error:", error);
1149
1288
  });
1150
1289
  this.childProcess.on("exit", (code) => {
1151
- if (code !== null && code !== 0 && code !== 143) logger$11.error(`❌ Process exited with code ${code}`);
1290
+ if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
1152
1291
  this.isRunning = false;
1153
1292
  });
1154
1293
  await new Promise((resolve) => setTimeout(resolve, 500));
1155
1294
  if (this.isRunning) {
1156
- logger$11.log("");
1157
- logger$11.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
1158
- logger$11.log("");
1295
+ logger$10.log("");
1296
+ logger$10.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
1297
+ logger$10.log("");
1159
1298
  }
1160
1299
  }
1161
1300
  async restart() {
@@ -1241,7 +1380,6 @@ ${credentialsInjection}${parentWatch}const port = process.argv.includes('--port'
1241
1380
  const { app } = await import('${appImportPath}');
1242
1381
 
1243
1382
  ${serveCode.replace(/^ {4}/gm, "")}
1244
- console.log(\`Server started on port \${port}\`);
1245
1383
  `;
1246
1384
  return `#!/usr/bin/env node
1247
1385
  /**
@@ -1270,6 +1408,31 @@ start({
1270
1408
  });
1271
1409
  `;
1272
1410
  }
1411
+ /** Run `fn` with `console.log` muted — warnings and errors still print. */
1412
+ async function quietly(fn) {
1413
+ const log = console.log;
1414
+ console.log = () => {};
1415
+ try {
1416
+ return await fn();
1417
+ } finally {
1418
+ console.log = log;
1419
+ }
1420
+ }
1421
+ /**
1422
+ * How to run a TypeScript entry with the app's own tsx.
1423
+ *
1424
+ * Straight through node rather than `npx tsx`: npx reads the developer's npm
1425
+ * config on every start and warned about each key it did not recognise — nine
1426
+ * lines per app, per restart, about nothing gkm does.
1427
+ */
1428
+ function tsxCommand(appRoot, args) {
1429
+ try {
1430
+ const cli = (0, node_module.createRequire)((0, node_path.join)(appRoot, "package.json")).resolve("tsx/cli");
1431
+ return [process.execPath, [cli, ...args]];
1432
+ } catch {
1433
+ return ["npx", ["tsx", ...args]];
1434
+ }
1435
+ }
1273
1436
  /** A port this app must serve on is held by something else. */
1274
1437
  var DevPortInUse = class extends Error {
1275
1438
  port;
@@ -1336,6 +1499,13 @@ var DevServer = class {
1336
1499
  this.selfServing = selfServing;
1337
1500
  this.actualPort = requestedPort;
1338
1501
  }
1502
+ /** What the ready line calls this app. */
1503
+ label = "server";
1504
+ /**
1505
+ * Where the app is reached — its HTTPS address behind the edge, when it has
1506
+ * one. The local port is only what the edge forwards to.
1507
+ */
1508
+ publicUrl;
1339
1509
  async start() {
1340
1510
  this.startTime = Date.now();
1341
1511
  if (this.isRunning) await this.stop();
@@ -1344,17 +1514,15 @@ var DevServer = class {
1344
1514
  this.actualPort = this.requestedPort;
1345
1515
  } else {
1346
1516
  this.actualPort = await require_workspace$1.findAvailablePort(this.requestedPort);
1347
- if (this.actualPort !== this.requestedPort) logger$11.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
1517
+ if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
1348
1518
  }
1349
1519
  const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts");
1350
1520
  await this.createServerEntry();
1351
- logger$11.log(`\n⏳ Starting server...`);
1352
- this.serverProcess = (0, node_child_process.spawn)("npx", [
1353
- "tsx",
1521
+ this.serverProcess = (0, node_child_process.spawn)(...tsxCommand(this.appRoot, [
1354
1522
  serverEntryPath,
1355
1523
  "--port",
1356
1524
  this.actualPort.toString()
1357
- ], {
1525
+ ]), {
1358
1526
  stdio: "inherit",
1359
1527
  env: {
1360
1528
  ...process.env,
@@ -1365,29 +1533,24 @@ var DevServer = class {
1365
1533
  });
1366
1534
  this.isRunning = true;
1367
1535
  this.serverProcess.on("error", (error) => {
1368
- logger$11.error("❌ Server error:", error);
1536
+ logger$10.error("❌ Server error:", error);
1369
1537
  });
1370
1538
  this.serverProcess.on("exit", (code, signal) => {
1371
- if (code !== null && code !== 0 && signal !== "SIGTERM") logger$11.error(`❌ Server exited with code ${code}`);
1539
+ if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
1372
1540
  this.isRunning = false;
1373
1541
  });
1374
1542
  await new Promise((resolve) => setTimeout(resolve, 1e3));
1375
1543
  if (this.isRunning) {
1376
- const base = `http://localhost:${this.actualPort}`;
1377
- const lines = [` Local: ${base}`];
1378
- if (this.enableOpenApi) lines.push(` API Docs: ${base}/__docs`);
1379
- if (this.telescope) lines.push(` Telescope: ${base}${this.telescope.path}`);
1380
- if (this.studio) lines.push(` Studio: ${base}${this.studio.path}`);
1381
- const maxLen = Math.max(...lines.map((l) => l.length));
1382
- const pad = (s) => s.padEnd(maxLen);
1383
- const border = "─".repeat(maxLen + 2);
1384
- logger$11.log("");
1385
- logger$11.log(` \x1b[32m✓ Ready\x1b[0m in ${((Date.now() - this.startTime) / 1e3).toFixed(1)}s`);
1386
- logger$11.log("");
1387
- logger$11.log(` ┌${border}┐`);
1388
- for (const line of lines) logger$11.log(` │ ${pad(line)} │`);
1389
- logger$11.log(` └${border}┘`);
1390
- logger$11.log("");
1544
+ const local = `http://localhost:${this.actualPort}`;
1545
+ const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
1546
+ const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
1547
+ logger$10.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
1548
+ const tools = this.selfServing ? [] : [
1549
+ ...this.enableOpenApi ? ["docs /__docs"] : [],
1550
+ ...this.telescope ? [`telescope ${this.telescope.path}`] : [],
1551
+ ...this.studio ? [`studio ${this.studio.path}`] : []
1552
+ ];
1553
+ if (tools.length > 0) logger$10.log(` ${tools.join(" · ")}`);
1391
1554
  }
1392
1555
  }
1393
1556
  async stop() {
@@ -2202,7 +2365,7 @@ var TopicGenerator = class extends require_discover.ConstructGenerator {
2202
2365
  };
2203
2366
  //#endregion
2204
2367
  //#region src/build/manifests.ts
2205
- const logger$10 = console;
2368
+ const logger$9 = console;
2206
2369
  function isPartitioned(field) {
2207
2370
  return !Array.isArray(field);
2208
2371
  }
@@ -2299,8 +2462,8 @@ export type RoutePath = Route['path'];
2299
2462
  `;
2300
2463
  const manifestPath = (0, node_path.join)(manifestDir, "aws.ts");
2301
2464
  await (0, node_fs_promises.writeFile)(manifestPath, content);
2302
- logger$10.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers, ${countItems(queues)} queues, ${countItems(topics)} topics`);
2303
- logger$10.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
2465
+ logger$9.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers, ${countItems(queues)} queues, ${countItems(topics)} topics`);
2466
+ logger$9.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
2304
2467
  }
2305
2468
  async function generateServerManifest(outputDir, appInfo, routes, subscribers, queues = [], topics = [], constructs = {}, backends = {}) {
2306
2469
  const manifestDir = (0, node_path.join)(outputDir, "manifest");
@@ -2355,8 +2518,8 @@ export type RoutePath = Route['path'];
2355
2518
  `;
2356
2519
  const manifestPath = (0, node_path.join)(manifestDir, "server.ts");
2357
2520
  await (0, node_fs_promises.writeFile)(manifestPath, content);
2358
- logger$10.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
2359
- logger$10.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
2521
+ logger$9.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
2522
+ logger$9.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
2360
2523
  }
2361
2524
  /**
2362
2525
  * Filter out 'ALL' method routes from a manifest field (flat or partitioned).
@@ -2561,7 +2724,7 @@ export default app;
2561
2724
  }
2562
2725
  //#endregion
2563
2726
  //#region src/build/index.ts
2564
- const logger$9 = console;
2727
+ const logger$8 = console;
2565
2728
  /**
2566
2729
  * What a build run at the workspace root reads.
2567
2730
  *
@@ -2586,7 +2749,7 @@ async function buildCommand(options) {
2586
2749
  const isAtWorkspaceRoot = cwd === workspaceRoot;
2587
2750
  const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => (0, node_path.resolve)(workspaceRoot, app.path) !== workspaceRoot);
2588
2751
  if (isAtWorkspaceRoot && someAppIsElsewhere) {
2589
- logger$9.log("📦 Detected workspace configuration");
2752
+ logger$8.log("📦 Detected workspace configuration");
2590
2753
  return workspaceBuildCommand(loadedConfig.workspace, options);
2591
2754
  }
2592
2755
  }
@@ -2595,15 +2758,15 @@ async function buildCommand(options) {
2595
2758
  const cacheBackend = require_workspace$1.cacheBackendFor(require_workspace$1.providerOf(loadedConfig.workspace));
2596
2759
  const productionConfigFromGkm = getProductionConfigFromGkm(config);
2597
2760
  const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
2598
- if (production) logger$9.log(`🏭 Building for PRODUCTION`);
2599
- logger$9.log(`Building with providers: ${resolved.providers.join(", ")}`);
2600
- logger$9.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
2761
+ if (production) logger$8.log(`🏭 Building for PRODUCTION`);
2762
+ logger$8.log(`Building with providers: ${resolved.providers.join(", ")}`);
2763
+ logger$8.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
2601
2764
  const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
2602
- if (telescope) logger$9.log(`🔭 Telescope enabled at ${telescope.path}`);
2765
+ if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
2603
2766
  const studio = production ? void 0 : normalizeStudioConfig(config.studio);
2604
- if (studio) logger$9.log(`🗄️ Studio enabled at ${studio.path}`);
2767
+ if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
2605
2768
  const hooks = normalizeHooksConfig(config.hooks);
2606
- if (hooks) logger$9.log(`🪝 Server hooks enabled`);
2769
+ if (hooks) logger$8.log(`🪝 Server hooks enabled`);
2607
2770
  return buildApp({
2608
2771
  config,
2609
2772
  workspaceRoot: loadedConfig.workspace.root,
@@ -2646,7 +2809,7 @@ async function buildApp(input) {
2646
2809
  });
2647
2810
  if (selfServing) {
2648
2811
  await writeSurfaceEntry((0, node_path.join)(appRoot, ".gkm", "server"), selfServing.source);
2649
- logger$9.log(`Generated a server for ${selfServing.id} from its own declaration`);
2812
+ logger$8.log(`Generated a server for ${selfServing.id} from its own declaration`);
2650
2813
  return { selfServing: selfServing.id };
2651
2814
  }
2652
2815
  const derived = ownersContext({
@@ -2686,14 +2849,14 @@ async function buildApp(input) {
2686
2849
  topicGenerator.load(code, appRoot, bustCache)
2687
2850
  ]);
2688
2851
  const allEndpoints = servedBy(loadedEndpoints, derived.surface);
2689
- logger$9.log(`Found ${allEndpoints.length} endpoints`);
2690
- logger$9.log(`Found ${allFunctions.length} functions`);
2691
- logger$9.log(`Found ${allCrons.length} crons`);
2692
- logger$9.log(`Found ${allSubscribers.length} subscribers`);
2693
- logger$9.log(`Found ${allQueues.length} queues`);
2694
- logger$9.log(`Found ${allTopics.length} topics`);
2852
+ logger$8.log(`Found ${allEndpoints.length} endpoints`);
2853
+ logger$8.log(`Found ${allFunctions.length} functions`);
2854
+ logger$8.log(`Found ${allCrons.length} crons`);
2855
+ logger$8.log(`Found ${allSubscribers.length} subscribers`);
2856
+ logger$8.log(`Found ${allQueues.length} queues`);
2857
+ logger$8.log(`Found ${allTopics.length} topics`);
2695
2858
  if (!input.serveEmpty && allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
2696
- logger$9.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
2859
+ logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
2697
2860
  return {};
2698
2861
  }
2699
2862
  const rootOutputDir = (0, node_path.join)(appRoot, ".gkm");
@@ -2710,7 +2873,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
2710
2873
  const rootOutputDir = (0, node_path.join)(appRoot, ".gkm");
2711
2874
  const outputDir = (0, node_path.join)(rootOutputDir, provider);
2712
2875
  await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
2713
- logger$9.log(`\nGenerating handlers for provider: ${provider}`);
2876
+ logger$8.log(`\nGenerating handlers for provider: ${provider}`);
2714
2877
  const [routes, functionInfos, cronInfos, subscriberInfos, queueInfos, topicInfos] = await Promise.all([
2715
2878
  endpointGenerator.build(context, endpoints, outputDir, {
2716
2879
  provider,
@@ -2722,7 +2885,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
2722
2885
  queueGenerator.build(context, queues, outputDir, { provider }),
2723
2886
  topicGenerator.build(context, topics, outputDir, { provider })
2724
2887
  ]);
2725
- logger$9.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
2888
+ logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
2726
2889
  const manifestRoutes = assembleManifestField(routes, endpoints);
2727
2890
  const manifestFunctions = assembleManifestField(functionInfos, functions);
2728
2891
  const manifestCrons = assembleManifestField(cronInfos, crons);
@@ -2742,8 +2905,8 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
2742
2905
  }, serverRouteField, manifestSubscribers, manifestQueues, manifestTopics);
2743
2906
  let masterKey;
2744
2907
  if (context.production?.bundle && !skipBundle) {
2745
- logger$9.log(`\n📦 Bundling production server...`);
2746
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-mjqGAvu1.cjs"));
2908
+ logger$8.log(`\n📦 Bundling production server...`);
2909
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-CYF5mWi9.cjs"));
2747
2910
  const allConstructs = [
2748
2911
  ...endpoints.map((e) => e.construct),
2749
2912
  ...functions.map((f) => f.construct),
@@ -2760,10 +2923,10 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
2760
2923
  stage,
2761
2924
  constructs: allConstructs
2762
2925
  })).masterKey;
2763
- logger$9.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
2926
+ logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
2764
2927
  if (masterKey) {
2765
- logger$9.log(`\n🔐 Secrets encrypted for deployment`);
2766
- logger$9.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
2928
+ logger$8.log(`\n🔐 Secrets encrypted for deployment`);
2929
+ logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
2767
2930
  }
2768
2931
  }
2769
2932
  return { masterKey };
@@ -2850,19 +3013,19 @@ async function workspaceBuildCommand(workspace, options) {
2850
3013
  const apps = Object.entries(workspace.apps);
2851
3014
  const backendApps = apps.filter(([, app]) => app.type === "backend");
2852
3015
  const frontendApps = apps.filter(([, app]) => app.type === "web");
2853
- logger$9.log(`\n🏗️ Building workspace: ${workspace.name}`);
2854
- logger$9.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
2855
- logger$9.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
2856
- if (options.production) logger$9.log(` 🏭 Production mode enabled`);
3016
+ logger$8.log(`\n🏗️ Building workspace: ${workspace.name}`);
3017
+ logger$8.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
3018
+ logger$8.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
3019
+ if (options.production) logger$8.log(` 🏭 Production mode enabled`);
2857
3020
  const buildOrder = require_workspace.getAppBuildOrder(workspace);
2858
- logger$9.log(` Build order: ${buildOrder.join(" → ")}`);
3021
+ logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
2859
3022
  const pm = detectPackageManager$2();
2860
- logger$9.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
3023
+ logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
2861
3024
  try {
2862
3025
  const { filters, unpackaged } = turboFilters(workspace);
2863
3026
  if (unpackaged.length > 0) throw new Error(`No package.json for workspace app(s): ${unpackaged.join(", ")}. Each app needs one — turbo builds packages, and gkm reads the name back to know which app it is building.`);
2864
3027
  const turboCommand = getTurboCommand(pm, filters);
2865
- logger$9.log(`Running: ${turboCommand}`);
3028
+ logger$8.log(`Running: ${turboCommand}`);
2866
3029
  await new Promise((resolve, reject) => {
2867
3030
  const child = (0, node_child_process.spawn)(turboCommand, {
2868
3031
  shell: true,
@@ -2890,15 +3053,15 @@ async function workspaceBuildCommand(workspace, options) {
2890
3053
  outputPath
2891
3054
  });
2892
3055
  }
2893
- logger$9.log(`\n✅ Workspace build complete!`);
2894
- logger$9.log(`\n📋 Build Summary:`);
3056
+ logger$8.log(`\n✅ Workspace build complete!`);
3057
+ logger$8.log(`\n📋 Build Summary:`);
2895
3058
  for (const result of results) {
2896
3059
  const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
2897
- logger$9.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
3060
+ logger$8.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
2898
3061
  }
2899
3062
  } catch (error) {
2900
3063
  const errorMessage = error instanceof Error ? error.message : "Build failed";
2901
- logger$9.log(`\n❌ Build failed: ${errorMessage}`);
3064
+ logger$8.log(`\n❌ Build failed: ${errorMessage}`);
2902
3065
  for (const [appName, app] of apps) results.push({
2903
3066
  appName,
2904
3067
  type: app.type,
@@ -3782,7 +3945,7 @@ async function createDnsProvider(options) {
3782
3945
  *
3783
3946
  * Handles automatic DNS record creation for deployed applications.
3784
3947
  */
3785
- const logger$8 = console;
3948
+ const logger$7 = console;
3786
3949
  /**
3787
3950
  * Check if DNS config is legacy format (single domain with `domain` property)
3788
3951
  */
@@ -3819,7 +3982,7 @@ function groupHostnamesByDomain(appHostnames, dnsConfig) {
3819
3982
  for (const [appName, hostname] of appHostnames) {
3820
3983
  const rootDomain = findRootDomain(hostname, dnsConfig);
3821
3984
  if (!rootDomain) {
3822
- logger$8.log(` ⚠ No DNS config found for hostname: ${hostname}`);
3985
+ logger$7.log(` ⚠ No DNS config found for hostname: ${hostname}`);
3823
3986
  continue;
3824
3987
  }
3825
3988
  if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
@@ -3869,10 +4032,10 @@ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
3869
4032
  * Print DNS records table
3870
4033
  */
3871
4034
  function printDnsRecordsTable(records, rootDomain) {
3872
- logger$8.log(`\n 📋 DNS Records for ${rootDomain}:`);
3873
- logger$8.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
3874
- logger$8.log(" │ Subdomain │ Type │ Value │ Status │");
3875
- logger$8.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
4035
+ logger$7.log(`\n 📋 DNS Records for ${rootDomain}:`);
4036
+ logger$7.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
4037
+ logger$7.log(" │ Subdomain │ Type │ Value │ Status │");
4038
+ logger$7.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
3876
4039
  for (const record of records) {
3877
4040
  const subdomain = record.subdomain.padEnd(35);
3878
4041
  const type = record.type.padEnd(4);
@@ -3882,18 +4045,18 @@ function printDnsRecordsTable(records, rootDomain) {
3882
4045
  else if (record.created) status = "✓ new";
3883
4046
  else if (record.existed) status = "✓";
3884
4047
  else status = "?";
3885
- logger$8.log(` │ ${subdomain} │ ${type} │ ${value} │ ${status.padEnd(6)} │`);
4048
+ logger$7.log(` │ ${subdomain} │ ${type} │ ${value} │ ${status.padEnd(6)} │`);
3886
4049
  }
3887
- logger$8.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
4050
+ logger$7.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
3888
4051
  }
3889
4052
  /**
3890
4053
  * Print DNS records in a simple format for manual setup
3891
4054
  */
3892
4055
  function printDnsRecordsSimple(records, rootDomain) {
3893
- logger$8.log("\n 📋 Required DNS Records:");
3894
- logger$8.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
3895
- for (const record of records) logger$8.log(` ${record.subdomain} → ${record.value} (A record)`);
3896
- logger$8.log("");
4056
+ logger$7.log("\n 📋 Required DNS Records:");
4057
+ logger$7.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
4058
+ for (const record of records) logger$7.log(` ${record.subdomain} → ${record.value} (A record)`);
4059
+ logger$7.log("");
3897
4060
  }
3898
4061
  /**
3899
4062
  * Create DNS records for a single domain using its configured provider
@@ -3905,7 +4068,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
3905
4068
  provider = await createDnsProvider({ config: providerConfig });
3906
4069
  } catch (error) {
3907
4070
  const message = error instanceof Error ? error.message : "Unknown error";
3908
- logger$8.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
4071
+ logger$7.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
3909
4072
  return records.map((r) => ({
3910
4073
  ...r,
3911
4074
  error: message
@@ -3959,7 +4122,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
3959
4122
  }
3960
4123
  } catch (error) {
3961
4124
  const message = error instanceof Error ? error.message : "Unknown error";
3962
- logger$8.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
4125
+ logger$7.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
3963
4126
  return records.map((r) => ({
3964
4127
  hostname: r.hostname,
3965
4128
  subdomain: r.subdomain,
@@ -3986,20 +4149,20 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
3986
4149
  async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
3987
4150
  if (!dnsConfig) return null;
3988
4151
  const normalizedConfig = normalizeDnsConfig(dnsConfig);
3989
- logger$8.log("\n🌐 Setting up DNS records...");
4152
+ logger$7.log("\n🌐 Setting up DNS records...");
3990
4153
  let serverIp;
3991
4154
  try {
3992
4155
  const endpointUrl = new URL(dokployEndpoint);
3993
4156
  serverIp = await resolveHostnameToIp(endpointUrl.hostname);
3994
- logger$8.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
4157
+ logger$7.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
3995
4158
  } catch (error) {
3996
4159
  const message = error instanceof Error ? error.message : "Unknown error";
3997
- logger$8.log(` ⚠ Failed to resolve server IP: ${message}`);
4160
+ logger$7.log(` ⚠ Failed to resolve server IP: ${message}`);
3998
4161
  return null;
3999
4162
  }
4000
4163
  const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
4001
4164
  if (groupedHostnames.size === 0) {
4002
- logger$8.log(" No DNS records needed (no hostnames match configured domains)");
4165
+ logger$7.log(" No DNS records needed (no hostnames match configured domains)");
4003
4166
  return {
4004
4167
  records: [],
4005
4168
  success: true,
@@ -4011,22 +4174,22 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
4011
4174
  for (const [rootDomain, domainHostnames] of groupedHostnames) {
4012
4175
  const providerConfig = normalizedConfig[rootDomain];
4013
4176
  if (!providerConfig) {
4014
- logger$8.log(` ⚠ No provider config for ${rootDomain}`);
4177
+ logger$7.log(` ⚠ No provider config for ${rootDomain}`);
4015
4178
  continue;
4016
4179
  }
4017
4180
  const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
4018
4181
  const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
4019
4182
  if (requiredRecords.length === 0) continue;
4020
- logger$8.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
4183
+ logger$7.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
4021
4184
  const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
4022
4185
  allRecords.push(...domainRecords);
4023
4186
  const created = domainRecords.filter((r) => r.created).length;
4024
4187
  const existed = domainRecords.filter((r) => r.existed).length;
4025
4188
  const failed = domainRecords.filter((r) => r.error).length;
4026
- if (created > 0) logger$8.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
4027
- if (existed > 0) logger$8.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
4189
+ if (created > 0) logger$7.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
4190
+ if (existed > 0) logger$7.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
4028
4191
  if (failed > 0) {
4029
- logger$8.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
4192
+ logger$7.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
4030
4193
  hasFailures = true;
4031
4194
  }
4032
4195
  if (state) {
@@ -4063,10 +4226,10 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
4063
4226
  */
4064
4227
  async function verifyDnsRecords(appHostnames, serverIp, state) {
4065
4228
  const results = [];
4066
- logger$8.log("\n🔍 Verifying DNS records...");
4229
+ logger$7.log("\n🔍 Verifying DNS records...");
4067
4230
  for (const [appName, hostname] of appHostnames) {
4068
4231
  if (isDnsVerified(state, hostname, serverIp)) {
4069
- logger$8.log(` ✓ ${hostname} (previously verified)`);
4232
+ logger$7.log(` ✓ ${hostname} (previously verified)`);
4070
4233
  results.push({
4071
4234
  hostname,
4072
4235
  appName,
@@ -4080,7 +4243,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
4080
4243
  const resolvedIp = await resolveHostnameToIp(hostname);
4081
4244
  if (resolvedIp === serverIp) {
4082
4245
  setDnsVerification(state, hostname, serverIp);
4083
- logger$8.log(` ✓ ${hostname} → ${resolvedIp}`);
4246
+ logger$7.log(` ✓ ${hostname} → ${resolvedIp}`);
4084
4247
  results.push({
4085
4248
  hostname,
4086
4249
  appName,
@@ -4089,7 +4252,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
4089
4252
  expectedIp: serverIp
4090
4253
  });
4091
4254
  } else {
4092
- logger$8.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
4255
+ logger$7.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
4093
4256
  results.push({
4094
4257
  hostname,
4095
4258
  appName,
@@ -4100,7 +4263,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
4100
4263
  }
4101
4264
  } catch (error) {
4102
4265
  const message = error instanceof Error ? error.message : "Unknown error";
4103
- logger$8.log(` ⚠ ${hostname} DNS not propagated (${message})`);
4266
+ logger$7.log(` ⚠ ${hostname} DNS not propagated (${message})`);
4104
4267
  results.push({
4105
4268
  hostname,
4106
4269
  appName,
@@ -4114,9 +4277,9 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
4114
4277
  const skipped = results.filter((r) => r.skipped).length;
4115
4278
  const pending = results.filter((r) => !r.verified).length;
4116
4279
  if (pending > 0) {
4117
- logger$8.log(`\n ${verified} verified, ${pending} pending propagation`);
4118
- logger$8.log(" DNS changes may take 5-30 minutes to propagate");
4119
- } else if (skipped > 0) logger$8.log(` ${verified} verified (${skipped} from cache)`);
4280
+ logger$7.log(`\n ${verified} verified, ${pending} pending propagation`);
4281
+ logger$7.log(" DNS changes may take 5-30 minutes to propagate");
4282
+ } else if (skipped > 0) logger$7.log(` ${verified} verified (${skipped} from cache)`);
4120
4283
  return results;
4121
4284
  }
4122
4285
  //#endregion
@@ -4149,43 +4312,19 @@ function resolveHost(appName, app, stage, dokployConfig, isMainFrontend) {
4149
4312
  return `${appName}.${baseDomain}`;
4150
4313
  }
4151
4314
  /**
4152
- * Which site the base domain points at.
4153
- *
4154
- * Three rules, in order, and the last one is the point:
4155
- *
4156
- * 1. **One site** — it is the root. Unambiguous, so nothing has to be said.
4157
- * 2. **Named `web`** — the convention wins, because people already rely on it.
4158
- * 3. **Declared `root: true`** — the site says so itself.
4159
- *
4160
- * Anything else throws. It used to take the *first* `type: 'web'` it iterated,
4161
- * and that object is built from the manifest, which is built in glob traversal
4162
- * order — so two sites with neither named `web` meant renaming a file could
4163
- * move the production root domain, silently. The same shape as
4164
- * `CacheIsAmbiguous`: unambiguous with one and arbitrary with two, so two is an
4165
- * error rather than a coin toss.
4315
+ * Whether this app is the site the base domain points at — see `rootSite`,
4316
+ * which the local edge asks too.
4166
4317
  *
4167
4318
  * @throws {AmbiguousRootSite} when several sites could be the root and none says it is
4168
4319
  */
4169
4320
  function isMainFrontendApp(appName, app, allApps) {
4170
4321
  if (app.type !== "web") return false;
4171
- const sites = Object.entries(allApps).filter(([, a]) => a.type === "web");
4172
- if (sites.length === 1) return true;
4173
- if (sites.filter(([name]) => name === "web").length > 0) return appName === "web";
4174
- const declared = sites.filter(([, a]) => a.root === true);
4175
- if (declared.length === 1) return declared[0][0] === appName;
4176
- throw new AmbiguousRootSite(sites.map(([name]) => name), declared.length > 1);
4177
- }
4178
- /** Several sites could hold the base domain, and none of them says it does. */
4179
- var AmbiguousRootSite = class extends Error {
4180
- sites;
4181
- tooMany;
4182
- constructor(sites, tooMany = false) {
4183
- super(tooMany ? `More than one site declares \`root: true\` — ${sites.join(", ")}. The base domain points at one of them.` : `${sites.length} sites and nothing says which holds the base domain: ${sites.join(", ")}. Name one of them \`web\`, or declare \`root: true\` on the one the base domain points at. It used to be whichever the glob reached first, which is not a thing to decide a production hostname.`);
4184
- this.sites = sites;
4185
- this.tooMany = tooMany;
4186
- this.name = "AmbiguousRootSite";
4187
- }
4188
- };
4322
+ const sites = Object.entries(allApps).filter(([, a]) => a.type === "web").map(([name, a]) => ({
4323
+ name,
4324
+ root: a.root
4325
+ }));
4326
+ return require_workspace$1.rootSite(sites) === appName;
4327
+ }
4189
4328
  /**
4190
4329
  * Get public URL arg names from app dependencies.
4191
4330
  *
@@ -5048,7 +5187,7 @@ CMD ["nginx", "-g", "daemon off;"]
5048
5187
  }
5049
5188
  //#endregion
5050
5189
  //#region src/docker/index.ts
5051
- const logger$7 = console;
5190
+ const logger$6 = console;
5052
5191
  /**
5053
5192
  * Docker command implementation
5054
5193
  * Generates Dockerfile, docker-compose.yml, and related files
@@ -5059,7 +5198,7 @@ const logger$7 = console;
5059
5198
  async function dockerCommand(options) {
5060
5199
  const loadedConfig = await require_config.loadWorkspaceConfig();
5061
5200
  if (loadedConfig.type === "workspace") {
5062
- logger$7.log("📦 Detected workspace configuration");
5201
+ logger$6.log("📦 Detected workspace configuration");
5063
5202
  return workspaceDockerCommand(loadedConfig.workspace);
5064
5203
  }
5065
5204
  const config = await require_config.loadConfig();
@@ -5079,7 +5218,7 @@ async function dockerCommand(options) {
5079
5218
  if (inMonorepo && !useSlim) {
5080
5219
  if (hasTurbo) {
5081
5220
  useTurbo = true;
5082
- logger$7.log(" Detected monorepo with turbo.json - using turbo prune");
5221
+ logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
5083
5222
  } else throw new Error("Monorepo detected but turbo.json not found.\n\nDocker builds in monorepos require Turborepo for proper dependency isolation.\n\nTo fix this:\n 1. Install turbo: pnpm add -Dw turbo\n 2. Create turbo.json in your monorepo root\n 3. Run this command again\n\nSee: https://turbo.build/repo/docs/guides/tools/docker");
5084
5223
  }
5085
5224
  let turboPackage = options.turboPackage ?? dockerConfig.imageName;
@@ -5087,7 +5226,7 @@ async function dockerCommand(options) {
5087
5226
  const pkg = require(`${process.cwd()}/package.json`);
5088
5227
  if (pkg.name) {
5089
5228
  turboPackage = pkg.name;
5090
- logger$7.log(` Turbo package: ${turboPackage}`);
5229
+ logger$6.log(` Turbo package: ${turboPackage}`);
5091
5230
  }
5092
5231
  } catch {}
5093
5232
  const templateOptions = {
@@ -5104,16 +5243,16 @@ async function dockerCommand(options) {
5104
5243
  const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
5105
5244
  const dockerfilePath = (0, node_path.join)(dockerDir, "Dockerfile");
5106
5245
  await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
5107
- logger$7.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
5246
+ logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
5108
5247
  const composePath = await writeConstructsCompose(loadedConfig.workspace);
5109
5248
  const dockerignore = generateDockerignore();
5110
5249
  const dockerignorePath = (0, node_path.join)(process.cwd(), ".dockerignore");
5111
5250
  await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
5112
- logger$7.log("Generated: .dockerignore (project root)");
5251
+ logger$6.log("Generated: .dockerignore (project root)");
5113
5252
  const entrypoint = generateDockerEntrypoint();
5114
5253
  const entrypointPath = (0, node_path.join)(dockerDir, "docker-entrypoint.sh");
5115
5254
  await (0, node_fs_promises.writeFile)(entrypointPath, entrypoint);
5116
- logger$7.log("Generated: .gkm/docker/docker-entrypoint.sh");
5255
+ logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
5117
5256
  const result = {
5118
5257
  dockerfile: dockerfilePath,
5119
5258
  dockerCompose: composePath,
@@ -5132,13 +5271,13 @@ async function dockerCommand(options) {
5132
5271
  function ensureLockfile(cwd) {
5133
5272
  const lockfilePath = findLockfilePath(cwd);
5134
5273
  if (!lockfilePath) {
5135
- logger$7.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
5274
+ logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
5136
5275
  return null;
5137
5276
  }
5138
5277
  const lockfileName = (0, node_path.basename)(lockfilePath);
5139
5278
  const localLockfile = (0, node_path.join)(cwd, lockfileName);
5140
5279
  if (lockfilePath === localLockfile) return null;
5141
- logger$7.log(` Copying ${lockfileName} from monorepo root...`);
5280
+ logger$6.log(` Copying ${lockfileName} from monorepo root...`);
5142
5281
  (0, node_fs.copyFileSync)(lockfilePath, localLockfile);
5143
5282
  return () => {
5144
5283
  try {
@@ -5154,7 +5293,7 @@ async function buildDockerImage(imageName, options) {
5154
5293
  const tag = options.tag ?? "latest";
5155
5294
  const registry = options.registry;
5156
5295
  const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
5157
- logger$7.log(`\n🐳 Building Docker image: ${fullImageName}`);
5296
+ logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
5158
5297
  const cwd = process.cwd();
5159
5298
  const cleanup = ensureLockfile(cwd);
5160
5299
  try {
@@ -5166,7 +5305,7 @@ async function buildDockerImage(imageName, options) {
5166
5305
  DOCKER_BUILDKIT: "1"
5167
5306
  }
5168
5307
  });
5169
- logger$7.log(`✅ Docker image built: ${fullImageName}`);
5308
+ logger$6.log(`✅ Docker image built: ${fullImageName}`);
5170
5309
  } catch (error) {
5171
5310
  throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
5172
5311
  } finally {
@@ -5181,13 +5320,13 @@ async function pushDockerImage(imageName, options) {
5181
5320
  const registry = options.registry;
5182
5321
  if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
5183
5322
  const fullImageName = `${registry}/${imageName}:${tag}`;
5184
- logger$7.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
5323
+ logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
5185
5324
  try {
5186
5325
  (0, node_child_process.execSync)(`docker push ${fullImageName}`, {
5187
5326
  cwd: process.cwd(),
5188
5327
  stdio: "inherit"
5189
5328
  });
5190
- logger$7.log(`✅ Docker image pushed: ${fullImageName}`);
5329
+ logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
5191
5330
  } catch (error) {
5192
5331
  throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
5193
5332
  }
@@ -5212,21 +5351,21 @@ function getAppPackageName(appPath) {
5212
5351
  async function workspaceDockerCommand(workspace) {
5213
5352
  const results = [];
5214
5353
  const apps = Object.entries(workspace.apps);
5215
- logger$7.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
5354
+ logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
5216
5355
  const dockerDir = (0, node_path.join)(workspace.root, ".gkm", "docker");
5217
5356
  await (0, node_fs_promises.mkdir)(dockerDir, { recursive: true });
5218
5357
  const packageManager = detectPackageManager$1(workspace.root);
5219
- logger$7.log(` Package manager: ${packageManager}`);
5358
+ logger$6.log(` Package manager: ${packageManager}`);
5220
5359
  for (const [appName, app] of apps) {
5221
5360
  if (app.type === "mobile") {
5222
- logger$7.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
5361
+ logger$6.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
5223
5362
  continue;
5224
5363
  }
5225
5364
  const appPath = app.path;
5226
5365
  const turboPackage = getAppPackageName((0, node_path.join)(workspace.root, appPath)) ?? appName;
5227
5366
  const imageName = appName;
5228
5367
  const buildType = !!app.entry ? "entry" : app.type;
5229
- logger$7.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
5368
+ logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
5230
5369
  let dockerfile;
5231
5370
  if (app.type === "web") {
5232
5371
  const publicUrlArgs = getPublicUrlArgNames(app);
@@ -5270,7 +5409,7 @@ async function workspaceDockerCommand(workspace) {
5270
5409
  });
5271
5410
  const dockerfilePath = (0, node_path.join)(dockerDir, `Dockerfile.${appName}`);
5272
5411
  await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
5273
- logger$7.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
5412
+ logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
5274
5413
  results.push({
5275
5414
  appName,
5276
5415
  type: app.type,
@@ -5281,13 +5420,13 @@ async function workspaceDockerCommand(workspace) {
5281
5420
  const dockerignore = generateDockerignore();
5282
5421
  const dockerignorePath = (0, node_path.join)(workspace.root, ".dockerignore");
5283
5422
  await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
5284
- logger$7.log(`\n Generated: .dockerignore (workspace root)`);
5423
+ logger$6.log(`\n Generated: .dockerignore (workspace root)`);
5285
5424
  const composePath = await writeConstructsCompose(workspace);
5286
- logger$7.log(`\n✅ Generated ${results.length} Dockerfile(s) + ${require_workspace$1.COMPOSE_PATH}`);
5287
- logger$7.log("\n📋 Build commands:");
5425
+ logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + ${require_workspace$1.COMPOSE_PATH}`);
5426
+ logger$6.log("\n📋 Build commands:");
5288
5427
  for (const result of results) {
5289
5428
  const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
5290
- logger$7.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
5429
+ logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
5291
5430
  }
5292
5431
  printRunInstructions(workspace);
5293
5432
  return {
@@ -5308,18 +5447,18 @@ async function writeConstructsCompose(workspace) {
5308
5447
  stage: workspace.stages.local,
5309
5448
  start: false
5310
5449
  });
5311
- logger$7.log(` Generated: ${require_workspace$1.COMPOSE_PATH} (project root)`);
5450
+ logger$6.log(` Generated: ${require_workspace$1.COMPOSE_PATH} (project root)`);
5312
5451
  return (0, node_path.join)(workspace.root, require_workspace$1.COMPOSE_PATH);
5313
5452
  }
5314
5453
  function printRunInstructions(workspace) {
5315
5454
  const project = (0, node_fs.existsSync)((0, node_path.join)(workspace.root, "docker-compose.yml")) ? " -f docker-compose.yml" : "";
5316
- logger$7.log("\n📋 Run everything (the apps sit behind the \"apps\" profile):");
5317
- logger$7.log(" gkm setup # starts the containers and creates what they hold");
5318
- logger$7.log(` docker compose -f ${require_workspace$1.COMPOSE_PATH}${project} --profile apps up --build`);
5455
+ logger$6.log("\n📋 Run everything (the apps sit behind the \"apps\" profile):");
5456
+ logger$6.log(" gkm setup # starts the containers and creates what they hold");
5457
+ logger$6.log(` docker compose -f ${require_workspace$1.COMPOSE_PATH}${project} --profile apps up --build`);
5319
5458
  }
5320
5459
  //#endregion
5321
5460
  //#region src/deploy/docker.ts
5322
- const logger$6 = console;
5461
+ const logger$5 = console;
5323
5462
  /**
5324
5463
  * Get the full image reference
5325
5464
  */
@@ -5334,9 +5473,9 @@ function getImageRef(registry, imageName, tag) {
5334
5473
  * @param buildArgs - Build arguments to pass to docker build
5335
5474
  */
5336
5475
  async function buildImage(imageRef, _appName, buildArgs) {
5337
- logger$6.log(`\n🔨 Building Docker image: ${imageRef}`);
5476
+ logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
5338
5477
  const cwd = process.cwd();
5339
- logger$6.log(" Generating Dockerfile for the pre-built bundle...");
5478
+ logger$5.log(" Generating Dockerfile for the pre-built bundle...");
5340
5479
  await dockerCommand({ slim: true });
5341
5480
  const dockerfilePath = (0, node_path.join)(cwd, ".gkm", "docker", "Dockerfile");
5342
5481
  const buildCwd = cwd;
@@ -5358,7 +5497,7 @@ async function buildImage(imageRef, _appName, buildArgs) {
5358
5497
  DOCKER_BUILDKIT: "1"
5359
5498
  }
5360
5499
  });
5361
- logger$6.log(`✅ Image built: ${imageRef}`);
5500
+ logger$5.log(`✅ Image built: ${imageRef}`);
5362
5501
  } catch (error) {
5363
5502
  throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
5364
5503
  }
@@ -5367,13 +5506,13 @@ async function buildImage(imageRef, _appName, buildArgs) {
5367
5506
  * Push Docker image to registry
5368
5507
  */
5369
5508
  async function pushImage(imageRef) {
5370
- logger$6.log(`\n☁️ Pushing image: ${imageRef}`);
5509
+ logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
5371
5510
  try {
5372
5511
  (0, node_child_process.execSync)(`docker push ${imageRef}`, {
5373
5512
  cwd: process.cwd(),
5374
5513
  stdio: "inherit"
5375
5514
  });
5376
- logger$6.log(`✅ Image pushed: ${imageRef}`);
5515
+ logger$5.log(`✅ Image pushed: ${imageRef}`);
5377
5516
  } catch (error) {
5378
5517
  throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
5379
5518
  }
@@ -5387,18 +5526,18 @@ async function deployDocker(options) {
5387
5526
  const imageRef = getImageRef(config.registry, imageName, tag);
5388
5527
  await buildImage(imageRef, config.appName, buildArgs);
5389
5528
  if (!skipPush) {
5390
- if (!config.registry) logger$6.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
5529
+ if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
5391
5530
  else await pushImage(imageRef);
5392
5531
  }
5393
- logger$6.log("\n✅ Docker deployment ready!");
5394
- logger$6.log(`\n📋 Deployment details:`);
5395
- logger$6.log(` Image: ${imageRef}`);
5396
- logger$6.log(` Stage: ${stage}`);
5532
+ logger$5.log("\n✅ Docker deployment ready!");
5533
+ logger$5.log(`\n📋 Deployment details:`);
5534
+ logger$5.log(` Image: ${imageRef}`);
5535
+ logger$5.log(` Stage: ${stage}`);
5397
5536
  if (masterKey) {
5398
- logger$6.log(`\n🔐 Deploy with this environment variable:`);
5399
- logger$6.log(` GKM_MASTER_KEY=${masterKey}`);
5400
- logger$6.log("\n Example docker run:");
5401
- logger$6.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
5537
+ logger$5.log(`\n🔐 Deploy with this environment variable:`);
5538
+ logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
5539
+ logger$5.log("\n Example docker run:");
5540
+ logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
5402
5541
  }
5403
5542
  return {
5404
5543
  imageRef,
@@ -6777,7 +6916,7 @@ async function sniffAllApps(apps, workspacePath, options = {}) {
6777
6916
  *
6778
6917
  * @module deploy
6779
6918
  */
6780
- const logger$5 = console;
6919
+ const logger$4 = console;
6781
6920
  /**
6782
6921
  * Prompt for input
6783
6922
  */
@@ -6851,7 +6990,7 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
6851
6990
  return;
6852
6991
  } catch {
6853
6992
  if (i < maxRetries - 1) {
6854
- logger$5.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
6993
+ logger$4.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
6855
6994
  await new Promise((r) => setTimeout(r, retryIntervalMs));
6856
6995
  }
6857
6996
  }
@@ -6894,10 +7033,10 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
6894
7033
  break;
6895
7034
  } catch (error) {
6896
7035
  if (!(error instanceof Error ? error.message : String(error)).includes("already in use")) throw error;
6897
- logger$5.log(` Port ${candidate} is taken; trying another...`);
7036
+ logger$4.log(` Port ${candidate} is taken; trying another...`);
6898
7037
  }
6899
7038
  if (externalPort === void 0) throw new Error(`Could not publish a port for ${postgres.appName}. In use on this server: ${[...taken].sort((a, b) => a - b).join(", ") || "none reported"}. The role DDL needs to reach the cluster from here.`);
6900
- logger$5.log(` Publishing ${postgres.appName} on ${externalPort}...`);
7039
+ logger$4.log(` Publishing ${postgres.appName} on ${externalPort}...`);
6901
7040
  }
6902
7041
  await api.deployPostgres(postgres.postgresId);
6903
7042
  await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName);
@@ -6921,7 +7060,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
6921
7060
  const unpublish = async () => {
6922
7061
  if (!opened) return;
6923
7062
  await api.savePostgresExternalPort(postgres.postgresId, null).then(() => api.deployPostgres(postgres.postgresId)).catch(() => {
6924
- logger$5.log(` ⚠ Could not close external port ${externalPort} on ${postgres.appName} — close it in Dokploy.`);
7063
+ logger$4.log(` ⚠ Could not close external port ${externalPort} on ${postgres.appName} — close it in Dokploy.`);
6925
7064
  });
6926
7065
  };
6927
7066
  let lastError;
@@ -6932,7 +7071,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
6932
7071
  lastError = error;
6933
7072
  if (attempt === 3) break;
6934
7073
  const message = error instanceof Error ? error.message : String(error);
6935
- logger$5.log(` ⏳ Cluster still settling (${message}); retrying (${attempt}/2)...`);
7074
+ logger$4.log(` ⏳ Cluster still settling (${message}); retrying (${attempt}/2)...`);
6936
7075
  await new Promise((resolve) => setTimeout(resolve, 1e4));
6937
7076
  await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName).catch(() => {});
6938
7077
  }
@@ -7007,91 +7146,91 @@ async function workspaceDeployCommand(configured, options) {
7007
7146
  apps: units
7008
7147
  } : configured;
7009
7148
  if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
7010
- logger$5.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
7011
- logger$5.log(` Stage: ${stage}`);
7149
+ logger$4.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
7150
+ logger$4.log(` Stage: ${stage}`);
7012
7151
  const imageTag = tag ?? generateTag(stage);
7013
- logger$5.log(` Tag: ${imageTag}`);
7152
+ logger$4.log(` Tag: ${imageTag}`);
7014
7153
  const buildOrder = require_workspace.getAppBuildOrder(workspace);
7015
7154
  let appsToDeployNames = buildOrder;
7016
7155
  if (selectedApps && selectedApps.length > 0) {
7017
7156
  const invalidApps = selectedApps.filter((name) => !workspace.apps[name]);
7018
7157
  if (invalidApps.length > 0) throw new Error(`Unknown apps: ${invalidApps.join(", ")}\nAvailable apps: ${Object.keys(workspace.apps).join(", ")}`);
7019
7158
  appsToDeployNames = buildOrder.filter((name) => selectedApps.includes(name));
7020
- logger$5.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
7021
- } else logger$5.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
7159
+ logger$4.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
7160
+ } else logger$4.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
7022
7161
  const dokployApps = appsToDeployNames.filter((name) => {
7023
7162
  const target = workspace.apps[name].resolvedDeployTarget;
7024
7163
  if (!require_workspace.isDeployTargetSupported(target)) {
7025
- logger$5.log(` ⚠️ Skipping ${name}: ${require_workspace.getDeployTargetError(target, name)}`);
7164
+ logger$4.log(` ⚠️ Skipping ${name}: ${require_workspace.getDeployTargetError(target, name)}`);
7026
7165
  return false;
7027
7166
  }
7028
7167
  return true;
7029
7168
  });
7030
7169
  if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
7031
7170
  appsToDeployNames = dokployApps;
7032
- logger$5.log("\n🔐 Loading secrets and analyzing environment requirements...");
7171
+ logger$4.log("\n🔐 Loading secrets and analyzing environment requirements...");
7033
7172
  const stageSecrets = await require_storage.readStageSecrets(stage, workspace.root);
7034
7173
  if (!stageSecrets) {
7035
- logger$5.log(` ⚠️ No secrets found for stage "${stage}"`);
7036
- logger$5.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
7174
+ logger$4.log(` ⚠️ No secrets found for stage "${stage}"`);
7175
+ logger$4.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
7037
7176
  }
7038
7177
  const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
7039
7178
  const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
7040
7179
  if (stageSecrets) {
7041
7180
  const report = generateSecretsReport(encryptedSecrets, sniffedApps);
7042
- if (report.appsWithSecrets.length > 0) logger$5.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
7043
- if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$5.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
7181
+ if (report.appsWithSecrets.length > 0) logger$4.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
7182
+ if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$4.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
7044
7183
  }
7045
7184
  let creds = await getDokployCredentials();
7046
7185
  if (!creds) {
7047
- logger$5.log("\n📋 Dokploy credentials not found. Let's set them up.");
7186
+ logger$4.log("\n📋 Dokploy credentials not found. Let's set them up.");
7048
7187
  const normalizedEndpoint = (await prompt("Dokploy URL (e.g., https://dokploy.example.com): ")).replace(/\/$/, "");
7049
7188
  try {
7050
7189
  new URL(normalizedEndpoint);
7051
7190
  } catch {
7052
7191
  throw new Error("Invalid URL format");
7053
7192
  }
7054
- logger$5.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
7193
+ logger$4.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
7055
7194
  const token = await prompt("API Token: ", true);
7056
- logger$5.log("\nValidating credentials...");
7195
+ logger$4.log("\nValidating credentials...");
7057
7196
  if (!await validateDokployToken(normalizedEndpoint, token)) throw new Error("Invalid credentials. Please check your token.");
7058
7197
  await storeDokployCredentials(token, normalizedEndpoint);
7059
7198
  creds = {
7060
7199
  token,
7061
7200
  endpoint: normalizedEndpoint
7062
7201
  };
7063
- logger$5.log("✓ Credentials saved");
7202
+ logger$4.log("✓ Credentials saved");
7064
7203
  }
7065
7204
  const api = new DokployApi({
7066
7205
  baseUrl: creds.endpoint,
7067
7206
  token: creds.token
7068
7207
  });
7069
- logger$5.log("\n📁 Setting up Dokploy project...");
7208
+ logger$4.log("\n📁 Setting up Dokploy project...");
7070
7209
  const projectName = workspace.name;
7071
7210
  let project = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
7072
7211
  let environmentId;
7073
7212
  if (project) {
7074
- logger$5.log(` Found existing project: ${project.name}`);
7213
+ logger$4.log(` Found existing project: ${project.name}`);
7075
7214
  const matchingEnv = ((await api.getProject(project.projectId)).environments ?? []).find((e) => e.name.toLowerCase() === stage.toLowerCase());
7076
7215
  if (matchingEnv) {
7077
7216
  environmentId = matchingEnv.environmentId;
7078
- logger$5.log(` Using environment: ${matchingEnv.name}`);
7217
+ logger$4.log(` Using environment: ${matchingEnv.name}`);
7079
7218
  } else {
7080
- logger$5.log(` Creating "${stage}" environment...`);
7219
+ logger$4.log(` Creating "${stage}" environment...`);
7081
7220
  environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
7082
- logger$5.log(` ✓ Created environment: ${stage}`);
7221
+ logger$4.log(` ✓ Created environment: ${stage}`);
7083
7222
  }
7084
7223
  } else {
7085
- logger$5.log(` Creating project: ${projectName}`);
7224
+ logger$4.log(` Creating project: ${projectName}`);
7086
7225
  const result = await api.createProject(projectName);
7087
7226
  project = result.project;
7088
7227
  if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
7089
- logger$5.log(` Creating "${stage}" environment...`);
7228
+ logger$4.log(` Creating "${stage}" environment...`);
7090
7229
  environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
7091
7230
  } else environmentId = result.environment.environmentId;
7092
- logger$5.log(` ✓ Created project: ${project.projectId}`);
7231
+ logger$4.log(` ✓ Created project: ${project.projectId}`);
7093
7232
  }
7094
- logger$5.log("\n📋 Loading deploy state...");
7233
+ logger$4.log("\n📋 Loading deploy state...");
7095
7234
  const stateProvider = await createStateProvider({
7096
7235
  config: workspace.state,
7097
7236
  workspaceRoot: workspace.root,
@@ -7099,27 +7238,27 @@ async function workspaceDeployCommand(configured, options) {
7099
7238
  });
7100
7239
  let state = await stateProvider.read(stage);
7101
7240
  if (state) {
7102
- logger$5.log(` Found existing state for stage "${stage}"`);
7241
+ logger$4.log(` Found existing state for stage "${stage}"`);
7103
7242
  if (state.projectId !== project.projectId) {
7104
- logger$5.log(` ⚠ Project ID changed, updating state`);
7243
+ logger$4.log(` ⚠ Project ID changed, updating state`);
7105
7244
  state.projectId = project.projectId;
7106
7245
  }
7107
7246
  if (state.environmentId !== environmentId) {
7108
- logger$5.log(` ⚠ Environment ID changed, updating state`);
7247
+ logger$4.log(` ⚠ Environment ID changed, updating state`);
7109
7248
  state.environmentId = environmentId;
7110
7249
  }
7111
7250
  } else {
7112
- logger$5.log(` Creating new state for stage "${stage}"`);
7251
+ logger$4.log(` Creating new state for stage "${stage}"`);
7113
7252
  state = createEmptyState(stage, project.projectId, environmentId);
7114
7253
  }
7115
- logger$5.log("\n🐳 Checking registry...");
7254
+ logger$4.log("\n🐳 Checking registry...");
7116
7255
  let registryId = await getDokployRegistryId();
7117
7256
  const registry = workspace.deploy.dokploy?.registry;
7118
7257
  if (registryId) try {
7119
7258
  const reg = await api.getRegistry(registryId);
7120
- logger$5.log(` Using registry: ${reg.registryName}`);
7259
+ logger$4.log(` Using registry: ${reg.registryName}`);
7121
7260
  } catch {
7122
- logger$5.log(" ⚠ Stored registry not found, clearing...");
7261
+ logger$4.log(" ⚠ Stored registry not found, clearing...");
7123
7262
  registryId = void 0;
7124
7263
  await storeDokployRegistryId("");
7125
7264
  }
@@ -7128,26 +7267,26 @@ async function workspaceDeployCommand(configured, options) {
7128
7267
  if (registries.length > 0) {
7129
7268
  registryId = registries[0].registryId;
7130
7269
  await storeDokployRegistryId(registryId);
7131
- logger$5.log(` Using registry: ${registries[0].registryName}`);
7270
+ logger$4.log(` Using registry: ${registries[0].registryName}`);
7132
7271
  } else if (registry) {
7133
- logger$5.log(" No registries found in Dokploy. Let's create one.");
7134
- logger$5.log(` Registry URL: ${registry}`);
7272
+ logger$4.log(" No registries found in Dokploy. Let's create one.");
7273
+ logger$4.log(` Registry URL: ${registry}`);
7135
7274
  const username = await prompt("Registry username: ");
7136
7275
  const password = await prompt("Registry password/token: ", true);
7137
7276
  registryId = (await api.createRegistry("Default Registry", registry, username, password)).registryId;
7138
7277
  await storeDokployRegistryId(registryId);
7139
- logger$5.log(` ✓ Registry created: ${registryId}`);
7140
- } else logger$5.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
7278
+ logger$4.log(` ✓ Registry created: ${registryId}`);
7279
+ } else logger$4.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
7141
7280
  }
7142
7281
  const skippedMobileApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "mobile");
7143
- if (skippedMobileApps.length > 0) logger$5.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
7282
+ if (skippedMobileApps.length > 0) logger$4.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
7144
7283
  const backendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "backend");
7145
7284
  const frontendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "web");
7146
7285
  const dokployConfig = workspace.deploy.dokploy;
7147
7286
  let declaredEnv = {};
7148
7287
  let declaredClusters = {};
7149
7288
  {
7150
- logger$5.log("\n📦 Provisioning declared constructs...");
7289
+ logger$4.log("\n📦 Provisioning declared constructs...");
7151
7290
  const appUrls = {};
7152
7291
  for (const appName of backendApps) {
7153
7292
  const app = workspace.apps[appName];
@@ -7165,20 +7304,20 @@ async function workspaceDeployCommand(configured, options) {
7165
7304
  });
7166
7305
  declaredEnv = declared.env;
7167
7306
  declaredClusters = declared.clusters;
7168
- if (Object.keys(declaredEnv).length > 0) logger$5.log(` 🔌 Resolved ${Object.keys(declaredEnv).length} declared URL(s)`);
7307
+ if (Object.keys(declaredEnv).length > 0) logger$4.log(` 🔌 Resolved ${Object.keys(declaredEnv).length} declared URL(s)`);
7169
7308
  if (declared.statements.length > 0) {
7170
7309
  const serverHostname = getServerHostname(creds.endpoint);
7171
7310
  for (const [databaseName, cluster] of Object.entries(declared.clusters)) {
7172
7311
  const statements = declared.statements.filter((statement) => statement.database === databaseName);
7173
7312
  if (statements.length === 0) continue;
7174
7313
  const created = await applyDeclaredStatements(api, cluster, serverHostname, statements);
7175
- logger$5.log(` 🗄️ ${databaseName}: applied ${statements.length} statement(s), ${created} new`);
7314
+ logger$4.log(` 🗄️ ${databaseName}: applied ${statements.length} statement(s), ${created} new`);
7176
7315
  }
7177
7316
  }
7178
7317
  }
7179
7318
  const backupCluster = Object.values(declaredClusters)[0];
7180
7319
  if (workspace.deploy?.backups && backupCluster) {
7181
- logger$5.log("\n💾 Provisioning backup destination...");
7320
+ logger$4.log("\n💾 Provisioning backup destination...");
7182
7321
  const { provisionBackupDestination } = await Promise.resolve().then(() => require("./backup-provisioner-BmhIO_Yr.cjs"));
7183
7322
  const backupState = await provisionBackupDestination({
7184
7323
  api,
@@ -7187,13 +7326,13 @@ async function workspaceDeployCommand(configured, options) {
7187
7326
  stage,
7188
7327
  config: workspace.deploy.backups,
7189
7328
  existingState: getBackupState(state),
7190
- logger: logger$5
7329
+ logger: logger$4
7191
7330
  });
7192
7331
  setBackupState(state, backupState);
7193
7332
  if (!backupState.postgresBackupId) {
7194
7333
  const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
7195
7334
  const backupRetention = workspace.deploy.backups.retention ?? 30;
7196
- logger$5.log(" Creating postgres backup schedule...");
7335
+ logger$4.log(" Creating postgres backup schedule...");
7197
7336
  const backup = await api.createPostgresBackup({
7198
7337
  schedule: backupSchedule,
7199
7338
  prefix: `${stage}/postgres`,
@@ -7204,8 +7343,8 @@ async function workspaceDeployCommand(configured, options) {
7204
7343
  keepLatestCount: backupRetention
7205
7344
  });
7206
7345
  setPostgresBackupId(state, backup.backupId);
7207
- logger$5.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
7208
- } else logger$5.log(" ✓ Using existing postgres backup schedule");
7346
+ logger$4.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
7347
+ } else logger$4.log(" ✓ Using existing postgres backup schedule");
7209
7348
  }
7210
7349
  const publicUrls = {};
7211
7350
  const results = [];
@@ -7218,25 +7357,25 @@ async function workspaceDeployCommand(configured, options) {
7218
7357
  frontendUrls.push(`https://${hostname}`);
7219
7358
  }
7220
7359
  if (backendApps.length > 0) {
7221
- logger$5.log("\n📦 PHASE 1: Deploying backend applications...");
7360
+ logger$4.log("\n📦 PHASE 1: Deploying backend applications...");
7222
7361
  for (const appName of backendApps) {
7223
7362
  const app = workspace.apps[appName];
7224
- logger$5.log(`\n ⚙️ Deploying ${appName}...`);
7363
+ logger$4.log(`\n ⚙️ Deploying ${appName}...`);
7225
7364
  try {
7226
7365
  const dokployAppName = applicationName(stage, workspace.name, appName);
7227
7366
  let application = null;
7228
7367
  const cachedAppId = getApplicationId(state, appName);
7229
7368
  if (cachedAppId) {
7230
- logger$5.log(` Using cached ID: ${cachedAppId}`);
7369
+ logger$4.log(` Using cached ID: ${cachedAppId}`);
7231
7370
  application = await api.getApplication(cachedAppId);
7232
- if (application) logger$5.log(` ✓ Application found: ${application.applicationId}`);
7233
- else logger$5.log(` ⚠ Cached ID invalid, will create new`);
7371
+ if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
7372
+ else logger$4.log(` ⚠ Cached ID invalid, will create new`);
7234
7373
  }
7235
7374
  if (!application) {
7236
7375
  const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
7237
7376
  application = result.application;
7238
- if (result.created) logger$5.log(` Created application: ${application.applicationId}`);
7239
- else logger$5.log(` Found existing application: ${application.applicationId}`);
7377
+ if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
7378
+ else logger$4.log(` Found existing application: ${application.applicationId}`);
7240
7379
  }
7241
7380
  setApplicationId(state, appName, application.applicationId);
7242
7381
  const appSecrets = encryptedSecrets.get(appName);
@@ -7244,11 +7383,11 @@ async function workspaceDeployCommand(configured, options) {
7244
7383
  if (appSecrets && appSecrets.secretCount > 0) {
7245
7384
  buildArgs.push(`GKM_ENCRYPTED_CREDENTIALS=${appSecrets.payload.encrypted}`);
7246
7385
  buildArgs.push(`GKM_CREDENTIALS_IV=${appSecrets.payload.iv}`);
7247
- logger$5.log(` Encrypted ${appSecrets.secretCount} secrets`);
7386
+ logger$4.log(` Encrypted ${appSecrets.secretCount} secrets`);
7248
7387
  }
7249
7388
  const imageName = `${workspace.name}-${appName}`;
7250
7389
  const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
7251
- logger$5.log(` Building Docker image: ${imageRef}`);
7390
+ logger$4.log(` Building Docker image: ${imageRef}`);
7252
7391
  await deployDocker({
7253
7392
  stage,
7254
7393
  tag: imageTag,
@@ -7289,17 +7428,17 @@ async function workspaceDeployCommand(configured, options) {
7289
7428
  ...declaredEnv
7290
7429
  };
7291
7430
  const envVars = Object.entries(withDeclared).map(([key, value]) => `${key}=${value}`);
7292
- if (Object.keys(withDeclared).length > 0) logger$5.log(` Resolved ${Object.keys(withDeclared).length} env vars: ${Object.keys(withDeclared).sort().join(", ")}`);
7431
+ if (Object.keys(withDeclared).length > 0) logger$4.log(` Resolved ${Object.keys(withDeclared).length} env vars: ${Object.keys(withDeclared).sort().join(", ")}`);
7293
7432
  await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
7294
7433
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
7295
- logger$5.log(` Deploying to Dokploy...`);
7434
+ logger$4.log(` Deploying to Dokploy...`);
7296
7435
  await api.deployApplication(application.applicationId);
7297
7436
  const existingDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === backendHost);
7298
7437
  if (existingDomain) {
7299
7438
  appHostnames.set(appName, backendHost);
7300
7439
  appDomainIds.set(appName, existingDomain.domainId);
7301
7440
  publicUrls[appName] = `https://${backendHost}`;
7302
- logger$5.log(` ✓ Domain: https://${backendHost} (existing)`);
7441
+ logger$4.log(` ✓ Domain: https://${backendHost} (existing)`);
7303
7442
  } else try {
7304
7443
  const domain = await api.createDomain({
7305
7444
  host: backendHost,
@@ -7311,10 +7450,10 @@ async function workspaceDeployCommand(configured, options) {
7311
7450
  appHostnames.set(appName, backendHost);
7312
7451
  appDomainIds.set(appName, domain.domainId);
7313
7452
  publicUrls[appName] = `https://${backendHost}`;
7314
- logger$5.log(` ✓ Domain: https://${backendHost} (created)`);
7453
+ logger$4.log(` ✓ Domain: https://${backendHost} (created)`);
7315
7454
  } catch (domainError) {
7316
7455
  const message = domainError instanceof Error ? domainError.message : "Unknown error";
7317
- logger$5.log(` ⚠ Domain creation failed: ${message}`);
7456
+ logger$4.log(` ⚠ Domain creation failed: ${message}`);
7318
7457
  appHostnames.set(appName, backendHost);
7319
7458
  publicUrls[appName] = `https://${backendHost}`;
7320
7459
  }
@@ -7325,10 +7464,10 @@ async function workspaceDeployCommand(configured, options) {
7325
7464
  applicationId: application.applicationId,
7326
7465
  imageRef
7327
7466
  });
7328
- logger$5.log(` ✓ ${appName} deployed successfully`);
7467
+ logger$4.log(` ✓ ${appName} deployed successfully`);
7329
7468
  } catch (error) {
7330
7469
  const message = error instanceof Error ? error.message : "Unknown error";
7331
- logger$5.log(` ✗ Failed to deploy ${appName}: ${message}`);
7470
+ logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
7332
7471
  results.push({
7333
7472
  appName,
7334
7473
  type: app.type,
@@ -7340,25 +7479,25 @@ async function workspaceDeployCommand(configured, options) {
7340
7479
  }
7341
7480
  }
7342
7481
  if (frontendApps.length > 0) {
7343
- logger$5.log("\n🌐 PHASE 2: Deploying frontend applications...");
7482
+ logger$4.log("\n🌐 PHASE 2: Deploying frontend applications...");
7344
7483
  for (const appName of frontendApps) {
7345
7484
  const app = workspace.apps[appName];
7346
- logger$5.log(`\n 🌐 Deploying ${appName}...`);
7485
+ logger$4.log(`\n 🌐 Deploying ${appName}...`);
7347
7486
  try {
7348
7487
  const dokployAppName = applicationName(stage, workspace.name, appName);
7349
7488
  let application = null;
7350
7489
  const cachedAppId = getApplicationId(state, appName);
7351
7490
  if (cachedAppId) {
7352
- logger$5.log(` Using cached ID: ${cachedAppId}`);
7491
+ logger$4.log(` Using cached ID: ${cachedAppId}`);
7353
7492
  application = await api.getApplication(cachedAppId);
7354
- if (application) logger$5.log(` ✓ Application found: ${application.applicationId}`);
7355
- else logger$5.log(` ⚠ Cached ID invalid, will create new`);
7493
+ if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
7494
+ else logger$4.log(` ⚠ Cached ID invalid, will create new`);
7356
7495
  }
7357
7496
  if (!application) {
7358
7497
  const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
7359
7498
  application = result.application;
7360
- if (result.created) logger$5.log(` Created application: ${application.applicationId}`);
7361
- else logger$5.log(` Found existing application: ${application.applicationId}`);
7499
+ if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
7500
+ else logger$4.log(` Found existing application: ${application.applicationId}`);
7362
7501
  }
7363
7502
  setApplicationId(state, appName, application.applicationId);
7364
7503
  const dependencyUrls = {};
@@ -7378,7 +7517,7 @@ async function workspaceDeployCommand(configured, options) {
7378
7517
  };
7379
7518
  const { valid, missing, resolved } = validateEnvVars(requiredOf(sniffedApps.get(appName)), envContext);
7380
7519
  if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
7381
- if (Object.keys(resolved).length > 0) logger$5.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
7520
+ if (Object.keys(resolved).length > 0) logger$4.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
7382
7521
  const publicPrefix = require_workspace.getPublicEnvPrefix(app.framework);
7383
7522
  const buildArgs = [];
7384
7523
  const publicUrlArgNames = [];
@@ -7388,10 +7527,10 @@ async function workspaceDeployCommand(configured, options) {
7388
7527
  publicUrlArgNames.push(key);
7389
7528
  }
7390
7529
  }
7391
- if (buildArgs.length > 0) logger$5.log(` Build args: ${publicUrlArgNames.join(", ")}`);
7530
+ if (buildArgs.length > 0) logger$4.log(` Build args: ${publicUrlArgNames.join(", ")}`);
7392
7531
  const imageName = `${workspace.name}-${appName}`;
7393
7532
  const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
7394
- logger$5.log(` Building Docker image: ${imageRef}`);
7533
+ logger$4.log(` Building Docker image: ${imageRef}`);
7395
7534
  await deployDocker({
7396
7535
  stage,
7397
7536
  tag: imageTag,
@@ -7412,14 +7551,14 @@ async function workspaceDeployCommand(configured, options) {
7412
7551
  for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
7413
7552
  await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
7414
7553
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
7415
- logger$5.log(` Deploying to Dokploy...`);
7554
+ logger$4.log(` Deploying to Dokploy...`);
7416
7555
  await api.deployApplication(application.applicationId);
7417
7556
  const existingFrontendDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === frontendHost);
7418
7557
  if (existingFrontendDomain) {
7419
7558
  appHostnames.set(appName, frontendHost);
7420
7559
  appDomainIds.set(appName, existingFrontendDomain.domainId);
7421
7560
  publicUrls[appName] = `https://${frontendHost}`;
7422
- logger$5.log(` ✓ Domain: https://${frontendHost} (existing)`);
7561
+ logger$4.log(` ✓ Domain: https://${frontendHost} (existing)`);
7423
7562
  } else try {
7424
7563
  const domain = await api.createDomain({
7425
7564
  host: frontendHost,
@@ -7431,10 +7570,10 @@ async function workspaceDeployCommand(configured, options) {
7431
7570
  appHostnames.set(appName, frontendHost);
7432
7571
  appDomainIds.set(appName, domain.domainId);
7433
7572
  publicUrls[appName] = `https://${frontendHost}`;
7434
- logger$5.log(` ✓ Domain: https://${frontendHost} (created)`);
7573
+ logger$4.log(` ✓ Domain: https://${frontendHost} (created)`);
7435
7574
  } catch (domainError) {
7436
7575
  const message = domainError instanceof Error ? domainError.message : "Unknown error";
7437
- logger$5.log(` ⚠ Domain creation failed: ${message}`);
7576
+ logger$4.log(` ⚠ Domain creation failed: ${message}`);
7438
7577
  appHostnames.set(appName, frontendHost);
7439
7578
  publicUrls[appName] = `https://${frontendHost}`;
7440
7579
  }
@@ -7445,10 +7584,10 @@ async function workspaceDeployCommand(configured, options) {
7445
7584
  applicationId: application.applicationId,
7446
7585
  imageRef
7447
7586
  });
7448
- logger$5.log(` ✓ ${appName} deployed successfully`);
7587
+ logger$4.log(` ✓ ${appName} deployed successfully`);
7449
7588
  } catch (error) {
7450
7589
  const message = error instanceof Error ? error.message : "Unknown error";
7451
- logger$5.log(` ✗ Failed to deploy ${appName}: ${message}`);
7590
+ logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
7452
7591
  results.push({
7453
7592
  appName,
7454
7593
  type: app.type,
@@ -7458,9 +7597,9 @@ async function workspaceDeployCommand(configured, options) {
7458
7597
  }
7459
7598
  }
7460
7599
  }
7461
- logger$5.log("\n📋 Saving deploy state...");
7600
+ logger$4.log("\n📋 Saving deploy state...");
7462
7601
  await stateProvider.write(stage, state);
7463
- logger$5.log(" ✓ State saved");
7602
+ logger$4.log(" ✓ State saved");
7464
7603
  const dnsConfig = workspace.deploy.dns;
7465
7604
  if (dnsConfig && appHostnames.size > 0) {
7466
7605
  const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
@@ -7469,27 +7608,27 @@ async function workspaceDeployCommand(configured, options) {
7469
7608
  await stateProvider.write(stage, state);
7470
7609
  }
7471
7610
  if (dnsResult?.success && appHostnames.size > 0) {
7472
- logger$5.log("\n🔒 Validating domains for SSL certificates...");
7611
+ logger$4.log("\n🔒 Validating domains for SSL certificates...");
7473
7612
  for (const [appName, hostname] of appHostnames) try {
7474
7613
  const result = await api.validateDomain(hostname);
7475
- if (result.isValid) logger$5.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
7476
- else logger$5.log(` ⚠ ${appName}: ${hostname} not valid`);
7614
+ if (result.isValid) logger$4.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
7615
+ else logger$4.log(` ⚠ ${appName}: ${hostname} not valid`);
7477
7616
  } catch (validationError) {
7478
7617
  const message = validationError instanceof Error ? validationError.message : "Unknown error";
7479
- logger$5.log(` ⚠ ${appName}: validation failed - ${message}`);
7618
+ logger$4.log(` ⚠ ${appName}: validation failed - ${message}`);
7480
7619
  }
7481
7620
  }
7482
7621
  }
7483
7622
  const successCount = results.filter((r) => r.success).length;
7484
7623
  const failedCount = results.filter((r) => !r.success).length;
7485
- logger$5.log(`\n${"─".repeat(50)}`);
7486
- logger$5.log(`\n✅ Workspace deployment complete!`);
7487
- logger$5.log(` Project: ${project.projectId}`);
7488
- logger$5.log(` Successful: ${successCount}`);
7489
- if (failedCount > 0) logger$5.log(` Failed: ${failedCount}`);
7624
+ logger$4.log(`\n${"─".repeat(50)}`);
7625
+ logger$4.log(`\n✅ Workspace deployment complete!`);
7626
+ logger$4.log(` Project: ${project.projectId}`);
7627
+ logger$4.log(` Successful: ${successCount}`);
7628
+ if (failedCount > 0) logger$4.log(` Failed: ${failedCount}`);
7490
7629
  if (Object.keys(publicUrls).length > 0) {
7491
- logger$5.log("\n 📡 Deployed URLs:");
7492
- for (const [name, url] of Object.entries(publicUrls)) logger$5.log(` ${name}: ${url}`);
7630
+ logger$4.log("\n 📡 Deployed URLs:");
7631
+ for (const [name, url] of Object.entries(publicUrls)) logger$4.log(` ${name}: ${url}`);
7493
7632
  }
7494
7633
  return {
7495
7634
  apps: results,
@@ -7508,7 +7647,7 @@ async function deployCommand(options) {
7508
7647
  }
7509
7648
  //#endregion
7510
7649
  //#region src/deploy/init.ts
7511
- const logger$4 = console;
7650
+ const logger$3 = console;
7512
7651
  /**
7513
7652
  * Get the Dokploy API token from stored credentials or environment
7514
7653
  */
@@ -7554,28 +7693,28 @@ function unsafeToRewrite(content) {
7554
7693
  async function updateConfig(config, cwd = process.cwd()) {
7555
7694
  const configPath = (0, node_path.join)(cwd, "gkm.config.ts");
7556
7695
  if (!(0, node_fs.existsSync)(configPath)) {
7557
- logger$4.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
7558
- logger$4.log(` providers: {`);
7559
- logger$4.log(` dokploy: {`);
7560
- logger$4.log(` endpoint: '${config.endpoint}',`);
7561
- logger$4.log(` projectId: '${config.projectId}',`);
7562
- logger$4.log(` applicationId: '${config.applicationId}',`);
7563
- logger$4.log(` },`);
7564
- logger$4.log(` },`);
7696
+ logger$3.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
7697
+ logger$3.log(` providers: {`);
7698
+ logger$3.log(` dokploy: {`);
7699
+ logger$3.log(` endpoint: '${config.endpoint}',`);
7700
+ logger$3.log(` projectId: '${config.projectId}',`);
7701
+ logger$3.log(` applicationId: '${config.applicationId}',`);
7702
+ logger$3.log(` },`);
7703
+ logger$3.log(` },`);
7565
7704
  return;
7566
7705
  }
7567
7706
  const content = await (0, node_fs_promises.readFile)(configPath, "utf-8");
7568
7707
  if (unsafeToRewrite(content)) {
7569
- logger$4.warn("\n gkm.config.ts holds a dokploy block this cannot safely rewrite —\n it contains an expression or a nested object. Leaving it alone.");
7570
- logger$4.log(`\n Discovered, if you want them written down:`);
7571
- logger$4.log(` projectId: '${config.projectId}'`);
7572
- logger$4.log(` applicationId: '${config.applicationId}'`);
7573
- if (config.registryId) logger$4.log(` registryId: '${config.registryId}'`);
7708
+ logger$3.warn("\n gkm.config.ts holds a dokploy block this cannot safely rewrite —\n it contains an expression or a nested object. Leaving it alone.");
7709
+ logger$3.log(`\n Discovered, if you want them written down:`);
7710
+ logger$3.log(` projectId: '${config.projectId}'`);
7711
+ logger$3.log(` applicationId: '${config.applicationId}'`);
7712
+ if (config.registryId) logger$3.log(` registryId: '${config.registryId}'`);
7574
7713
  return;
7575
7714
  }
7576
7715
  if (content.includes("dokploy:") && content.includes("applicationId:")) {
7577
- logger$4.log("\n Dokploy config already exists in gkm.config.ts");
7578
- logger$4.log(" Updating with new values...");
7716
+ logger$3.log("\n Dokploy config already exists in gkm.config.ts");
7717
+ logger$3.log(" Updating with new values...");
7579
7718
  }
7580
7719
  const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
7581
7720
  const dokployConfigStr = `dokploy: {
@@ -7593,7 +7732,7 @@ async function updateConfig(config, cwd = process.cwd()) {
7593
7732
  },
7594
7733
  });`);
7595
7734
  await (0, node_fs_promises.writeFile)(configPath, newContent);
7596
- logger$4.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
7735
+ logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
7597
7736
  }
7598
7737
  /**
7599
7738
  * Initialize Dokploy deployment configuration
@@ -7602,22 +7741,22 @@ async function deployInitCommand(options) {
7602
7741
  const { projectName, appName, projectId: existingProjectId, registryId } = options;
7603
7742
  const endpoint = await getEndpoint(options.endpoint);
7604
7743
  const api = await createApi(endpoint);
7605
- logger$4.log(`\n🚀 Initializing Dokploy deployment...`);
7606
- logger$4.log(` Endpoint: ${endpoint}`);
7744
+ logger$3.log(`\n🚀 Initializing Dokploy deployment...`);
7745
+ logger$3.log(` Endpoint: ${endpoint}`);
7607
7746
  let projectId;
7608
7747
  if (existingProjectId) {
7609
7748
  projectId = existingProjectId;
7610
- logger$4.log(`\n📁 Using existing project: ${projectId}`);
7749
+ logger$3.log(`\n📁 Using existing project: ${projectId}`);
7611
7750
  } else {
7612
- logger$4.log(`\n📁 Looking for project: ${projectName}`);
7751
+ logger$3.log(`\n📁 Looking for project: ${projectName}`);
7613
7752
  const existingProject = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
7614
7753
  if (existingProject) {
7615
7754
  projectId = existingProject.projectId;
7616
- logger$4.log(` Found existing project: ${projectId}`);
7755
+ logger$3.log(` Found existing project: ${projectId}`);
7617
7756
  } else {
7618
- logger$4.log(` Creating new project...`);
7757
+ logger$3.log(` Creating new project...`);
7619
7758
  projectId = (await api.createProject(projectName)).project.projectId;
7620
- logger$4.log(` ✓ Created project: ${projectId}`);
7759
+ logger$3.log(` ✓ Created project: ${projectId}`);
7621
7760
  }
7622
7761
  }
7623
7762
  const project = await api.getProject(projectId);
@@ -7625,22 +7764,22 @@ async function deployInitCommand(options) {
7625
7764
  const firstEnv = project.environments?.[0];
7626
7765
  if (firstEnv) environmentId = firstEnv.environmentId;
7627
7766
  else {
7628
- logger$4.log(` Creating production environment...`);
7767
+ logger$3.log(` Creating production environment...`);
7629
7768
  environmentId = (await api.createEnvironment(projectId, "production")).environmentId;
7630
7769
  }
7631
- logger$4.log(`\n📦 Creating application: ${appName}`);
7770
+ logger$3.log(`\n📦 Creating application: ${appName}`);
7632
7771
  const application = await api.createApplication(appName, projectId, environmentId);
7633
- logger$4.log(` ✓ Created application: ${application.applicationId}`);
7772
+ logger$3.log(` ✓ Created application: ${application.applicationId}`);
7634
7773
  if (registryId) {
7635
- logger$4.log(`\n🔧 Configuring registry: ${registryId}`);
7774
+ logger$3.log(`\n🔧 Configuring registry: ${registryId}`);
7636
7775
  await api.updateApplication(application.applicationId, { registryId });
7637
- logger$4.log(` ✓ Registry configured`);
7776
+ logger$3.log(` ✓ Registry configured`);
7638
7777
  } else try {
7639
7778
  const registries = await api.listRegistries();
7640
7779
  if (registries.length > 0) {
7641
- logger$4.log(`\n📋 Available registries:`);
7642
- for (const reg of registries) logger$4.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
7643
- logger$4.log(`\n To use a registry, run with --registry-id <id>`);
7780
+ logger$3.log(`\n📋 Available registries:`);
7781
+ for (const reg of registries) logger$3.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
7782
+ logger$3.log(`\n To use a registry, run with --registry-id <id>`);
7644
7783
  }
7645
7784
  } catch {}
7646
7785
  const config = {
@@ -7649,14 +7788,14 @@ async function deployInitCommand(options) {
7649
7788
  applicationId: application.applicationId
7650
7789
  };
7651
7790
  await updateConfig(config);
7652
- logger$4.log(`\n✅ Dokploy deployment initialized!`);
7653
- logger$4.log(`\n📋 Configuration:`);
7654
- logger$4.log(` Project ID: ${projectId}`);
7655
- logger$4.log(` Application ID: ${application.applicationId}`);
7656
- logger$4.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
7657
- logger$4.log(`\n📝 Next steps:`);
7658
- logger$4.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
7659
- logger$4.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
7791
+ logger$3.log(`\n✅ Dokploy deployment initialized!`);
7792
+ logger$3.log(`\n📋 Configuration:`);
7793
+ logger$3.log(` Project ID: ${projectId}`);
7794
+ logger$3.log(` Application ID: ${application.applicationId}`);
7795
+ logger$3.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
7796
+ logger$3.log(`\n📝 Next steps:`);
7797
+ logger$3.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
7798
+ logger$3.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
7660
7799
  return config;
7661
7800
  }
7662
7801
  /**
@@ -7667,31 +7806,31 @@ async function deployListCommand(options) {
7667
7806
  const api = await createApi(endpoint);
7668
7807
  const { resource } = options;
7669
7808
  if (resource === "projects") {
7670
- logger$4.log(`\n📁 Projects in ${endpoint}:`);
7809
+ logger$3.log(`\n📁 Projects in ${endpoint}:`);
7671
7810
  const projects = await api.listProjects();
7672
7811
  if (projects.length === 0) {
7673
- logger$4.log(" No projects found");
7812
+ logger$3.log(" No projects found");
7674
7813
  return;
7675
7814
  }
7676
7815
  for (const project of projects) {
7677
- logger$4.log(`\n ${project.name} (${project.projectId})`);
7678
- if (project.description) logger$4.log(` ${project.description}`);
7816
+ logger$3.log(`\n ${project.name} (${project.projectId})`);
7817
+ if (project.description) logger$3.log(` ${project.description}`);
7679
7818
  }
7680
7819
  } else if (resource === "registries") {
7681
- logger$4.log(`\n🐳 Registries in ${endpoint}:`);
7820
+ logger$3.log(`\n🐳 Registries in ${endpoint}:`);
7682
7821
  const registries = await api.listRegistries();
7683
7822
  if (registries.length === 0) {
7684
- logger$4.log(" No registries configured");
7685
- logger$4.log(" Run \"gkm registry:setup\" to configure a registry");
7823
+ logger$3.log(" No registries configured");
7824
+ logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
7686
7825
  return;
7687
7826
  }
7688
7827
  const storedRegistryId = await getDokployRegistryId();
7689
7828
  for (const registry of registries) {
7690
7829
  const marker = registry.registryId === storedRegistryId ? " (default)" : "";
7691
- logger$4.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
7692
- logger$4.log(` URL: ${registry.registryUrl}`);
7693
- logger$4.log(` Username: ${registry.username}`);
7694
- if (registry.imagePrefix) logger$4.log(` Prefix: ${registry.imagePrefix}`);
7830
+ logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
7831
+ logger$3.log(` URL: ${registry.registryUrl}`);
7832
+ logger$3.log(` Username: ${registry.username}`);
7833
+ if (registry.imagePrefix) logger$3.log(` Prefix: ${registry.imagePrefix}`);
7695
7834
  }
7696
7835
  }
7697
7836
  }
@@ -7993,15 +8132,20 @@ function databaseFiles() {
7993
8132
  return [{
7994
8133
  path: "src/constructs/database.ts",
7995
8134
  content: `import { KyselyDatabase } from '@geekmidas/constructs/database/kysely';
7996
- import type { Generated } from 'kysely';
8135
+ import { CamelCasePlugin, type Generated } from 'kysely';
7997
8136
 
7998
- /** Your database schema. Add tables here. */
8137
+ /**
8138
+ * Your database schema. Add tables here.
8139
+ *
8140
+ * camelCase, because of the plugin below: the columns are snake_case in
8141
+ * Postgres (\`created_at\`), and Kysely maps between the two.
8142
+ */
7999
8143
  export interface Database {
8000
8144
  users: {
8001
8145
  id: Generated<string>;
8002
8146
  name: string;
8003
8147
  email: string;
8004
- created_at: Generated<Date>;
8148
+ createdAt: Generated<Date>;
8005
8149
  };
8006
8150
  }
8007
8151
 
@@ -8016,7 +8160,11 @@ export interface Database {
8016
8160
  * inference, so passing only \`Database\` would leave the name at \`string\`
8017
8161
  * and widen the service key away from \`${db.service}\`.
8018
8162
  */
8019
- export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}');
8163
+ export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}', {
8164
+ // Yours to keep, change or remove. Every connection this construct opens —
8165
+ // every endpoint, the tests — uses it.
8166
+ plugins: [new CamelCasePlugin()],
8167
+ });
8020
8168
  `
8021
8169
  }, usersMigration()];
8022
8170
  }
@@ -8191,7 +8339,7 @@ environment and fail in a way that looks like a config bug. Go through
8191
8339
  \`gkm exec --\`, which is what injects them.`);
8192
8340
  if (!isWorker) sections.push(endpointSection(options));
8193
8341
  if (isWorker || isServerless) sections.push(backgroundSection(template));
8194
- if (database) sections.push(databaseSection());
8342
+ if (database) sections.push(databaseSection(), queriesSection());
8195
8343
  sections.push(`## Environment
8196
8344
 
8197
8345
  Environment is parsed once, through \`envkit\`, and never read from
@@ -8378,6 +8526,88 @@ export const monthly = router
8378
8526
  Queries go through Kysely. Never open your own connection pool — the one the
8379
8527
  construct provides is the one that gets credentials, pooling and shutdown.`;
8380
8528
  }
8529
+ function queriesSection() {
8530
+ return `## Writing queries and relations
8531
+
8532
+ **Names: snake_case in Postgres, camelCase in TypeScript.** Kysely's
8533
+ \`CamelCasePlugin\` does the mapping, and it is set where you can see it — on
8534
+ the database construct in \`constructs/database.ts\`:
8535
+
8536
+ \`\`\`typescript
8537
+ export const database = new KyselyDatabase<Database, 'Database'>('Database', {
8538
+ plugins: [new CamelCasePlugin()],
8539
+ });
8540
+ \`\`\`
8541
+
8542
+ It is the project's plugin, not the toolbox's: keep it, change it, or remove it,
8543
+ but do it there. It applies to every connection the construct opens: every
8544
+ endpoint, and the tests, which connect through the construct.
8545
+
8546
+ - The \`Database\` interface is camelCase: \`userId\`, \`createdAt\`.
8547
+ - Migrations are the SQL, so they spell columns in snake_case.
8548
+ - A raw \`sql\` fragment is not rewritten. Refer to columns with
8549
+ \`sql.ref('users.createdAt')\` or \`eb.ref(…)\`, never inside the string.
8550
+ - A schema tenant takes its own options. Better Auth's tenant gets no plugin —
8551
+ its tables are its own.
8552
+
8553
+ **Types.** \`Generated<T>\` for every column the database fills in (ids,
8554
+ defaults, timestamps). Derive row types with \`Selectable<Database['users']>\`,
8555
+ \`Insertable<…>\` and \`Updateable<…>\` rather than writing them again.
8556
+
8557
+ **Relations are foreign keys.** Every \`*_id\` column references its parent,
8558
+ with its delete rule written out:
8559
+
8560
+ \`\`\`typescript
8561
+ .addColumn('user_id', 'uuid', (col) =>
8562
+ col.notNull().references('users.id').onDelete('cascade'),
8563
+ )
8564
+ \`\`\`
8565
+
8566
+ - \`restrict\` by default.
8567
+ - \`cascade\` only for rows that belong to the parent — a user's notifications.
8568
+ - \`set null\` for an optional link, on a nullable column.
8569
+ - Index every foreign key column. Postgres does not do it for you.
8570
+
8571
+ **Reading related rows: one query, nested.** \`jsonArrayFrom\` and
8572
+ \`jsonObjectFrom\` from \`kysely/helpers/postgres\` return children already in
8573
+ the shape of the response:
8574
+
8575
+ \`\`\`typescript
8576
+ import { jsonArrayFrom } from 'kysely/helpers/postgres';
8577
+
8578
+ const user = await db
8579
+ .selectFrom('users')
8580
+ .select((eb) => [
8581
+ 'users.id',
8582
+ 'users.name',
8583
+ jsonArrayFrom(
8584
+ eb
8585
+ .selectFrom('notifications')
8586
+ .select(['notifications.id', 'notifications.body', 'notifications.createdAt'])
8587
+ .whereRef('notifications.userId', '=', 'users.id')
8588
+ .orderBy('notifications.createdAt', 'desc'),
8589
+ ).as('notifications'),
8590
+ ])
8591
+ .where('users.id', '=', id)
8592
+ .executeTakeFirstOrThrow();
8593
+ \`\`\`
8594
+
8595
+ - Never query once per row in a loop.
8596
+ - Use joins to filter, or for a flat result — not to build nested ones by hand.
8597
+ - Qualify columns (\`users.id\`) once a query touches more than one table.
8598
+ - Select the columns the response needs. \`selectAll()\` only returns a whole
8599
+ row.
8600
+ - Nested rows come back as JSON: a timestamp inside \`jsonArrayFrom\` is a
8601
+ string, not a \`Date\`. Say so in the output schema (\`z.coerce.date()\`).
8602
+
8603
+ **Writes that touch several tables run in one transaction.** Use
8604
+ \`withTransaction(db, async (trx) => …)\` from \`@geekmidas/db/kysely\`: it joins
8605
+ the transaction \`db\` already is — an endpoint with an auditor, a test —
8606
+ instead of opening a second one.
8607
+
8608
+ **Lists paginate by cursor**, with \`paginatedSearch\` from
8609
+ \`@geekmidas/db/kysely/pagination\`, not by offset.`;
8610
+ }
8381
8611
  function conventionsSection() {
8382
8612
  return `## Conventions
8383
8613
 
@@ -8552,26 +8782,26 @@ function loadPackageJson() {
8552
8782
  * Run: pnpm --filter @geekmidas/cli sync-versions
8553
8783
  */
8554
8784
  const GEEKMIDAS_VERSIONS = {
8555
- "@geekmidas/audit": "~10.0.0-alpha.27",
8556
- "@geekmidas/auth": "~10.0.0-alpha.27",
8557
- "@geekmidas/cache": "~10.0.0-alpha.27",
8558
- "@geekmidas/client": "~10.0.0-alpha.27",
8559
- "@geekmidas/cloud": "~10.0.0-alpha.27",
8560
- "@geekmidas/constructs": "~10.0.0-alpha.27",
8561
- "@geekmidas/db": "~10.0.0-alpha.27",
8562
- "@geekmidas/emailkit": "~10.0.0-alpha.27",
8563
- "@geekmidas/envkit": "~10.0.0-alpha.27",
8564
- "@geekmidas/errors": "~10.0.0-alpha.27",
8565
- "@geekmidas/events": "~10.0.0-alpha.27",
8566
- "@geekmidas/logger": "~10.0.0-alpha.27",
8567
- "@geekmidas/manifest": "~10.0.0-alpha.27",
8568
- "@geekmidas/rate-limit": "~10.0.0-alpha.27",
8569
- "@geekmidas/schema": "~10.0.0-alpha.27",
8570
- "@geekmidas/services": "~10.0.0-alpha.27",
8571
- "@geekmidas/storage": "~10.0.0-alpha.27",
8572
- "@geekmidas/studio": "~10.0.0-alpha.27",
8573
- "@geekmidas/telescope": "~10.0.0-alpha.27",
8574
- "@geekmidas/testkit": "~10.0.0-alpha.27",
8785
+ "@geekmidas/audit": "~10.0.0-alpha.29",
8786
+ "@geekmidas/auth": "~10.0.0-alpha.29",
8787
+ "@geekmidas/cache": "~10.0.0-alpha.29",
8788
+ "@geekmidas/client": "~10.0.0-alpha.29",
8789
+ "@geekmidas/cloud": "~10.0.0-alpha.29",
8790
+ "@geekmidas/constructs": "~10.0.0-alpha.29",
8791
+ "@geekmidas/db": "~10.0.0-alpha.29",
8792
+ "@geekmidas/emailkit": "~10.0.0-alpha.29",
8793
+ "@geekmidas/envkit": "~10.0.0-alpha.29",
8794
+ "@geekmidas/errors": "~10.0.0-alpha.29",
8795
+ "@geekmidas/events": "~10.0.0-alpha.29",
8796
+ "@geekmidas/logger": "~10.0.0-alpha.29",
8797
+ "@geekmidas/manifest": "~10.0.0-alpha.29",
8798
+ "@geekmidas/rate-limit": "~10.0.0-alpha.29",
8799
+ "@geekmidas/schema": "~10.0.0-alpha.29",
8800
+ "@geekmidas/services": "~10.0.0-alpha.29",
8801
+ "@geekmidas/storage": "~10.0.0-alpha.29",
8802
+ "@geekmidas/studio": "~10.0.0-alpha.29",
8803
+ "@geekmidas/telescope": "~10.0.0-alpha.29",
8804
+ "@geekmidas/testkit": "~10.0.0-alpha.29",
8575
8805
  "@geekmidas/cli": `~${loadPackageJson().version}`
8576
8806
  };
8577
8807
  //#endregion
@@ -10509,15 +10739,20 @@ export const logger = createLogger();
10509
10739
  files.push({
10510
10740
  path: "constructs/database.ts",
10511
10741
  content: `import { KyselyDatabase } from '@geekmidas/constructs/database/kysely';
10512
- import type { Generated } from 'kysely';
10742
+ import { CamelCasePlugin, type Generated } from 'kysely';
10513
10743
 
10514
- /** Your database schema. Add tables here. */
10744
+ /**
10745
+ * Your database schema. Add tables here.
10746
+ *
10747
+ * camelCase, because of the plugin below: the columns are snake_case in
10748
+ * Postgres (\`created_at\`), and Kysely maps between the two.
10749
+ */
10515
10750
  export interface Database {
10516
10751
  users: {
10517
10752
  id: Generated<string>;
10518
10753
  name: string;
10519
10754
  email: string;
10520
- created_at: Generated<Date>;
10755
+ createdAt: Generated<Date>;
10521
10756
  };
10522
10757
  }
10523
10758
 
@@ -10528,14 +10763,19 @@ export interface Database {
10528
10763
  * \`${db.urlKey}\` all derive from this line — which is why nothing lists
10529
10764
  * \`postgres\` anywhere.
10530
10765
  */
10531
- export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}');
10766
+ export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}', {
10767
+ // Yours to keep, change or remove. Every connection this construct opens —
10768
+ // every endpoint, the tests — uses it.
10769
+ plugins: [new CamelCasePlugin()],
10770
+ });
10532
10771
 
10533
10772
  /**
10534
10773
  * The auth server's own schema in that same Postgres, with its own role.
10535
10774
  *
10536
10775
  * A schema tenant rather than a second database: one container, and a role
10537
10776
  * whose \`search_path\` is pinned, so Better Auth's tables cannot collide with
10538
- * the application's.
10777
+ * the application's. It takes its own options: no plugin, because Better
10778
+ * Auth names its own columns.
10539
10779
  */
10540
10780
  export const authDb = database.schema<Record<string, never>, 'AuthDb'>('AuthDb');
10541
10781
  `
@@ -10863,54 +11103,11 @@ export const getUserEndpoint = router
10863
11103
  }
10864
11104
  ];
10865
11105
  if (options.monorepo) {
10866
- files.push({
10867
- path: "src/services/auth.ts",
10868
- content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
10869
-
10870
- export interface Session {
10871
- user: {
10872
- id: string;
10873
- email: string;
10874
- name: string;
10875
- };
10876
- }
10877
-
10878
- export interface AuthClient {
10879
- getSession: (cookie: string) => Promise<Session | null>;
10880
- }
10881
-
10882
- export const authService = {
10883
- serviceName: 'auth' as const,
10884
- async register({ envParser, context }: ServiceRegisterOptions) {
10885
- const logger = context.getLogger();
10886
-
10887
- const config = envParser
10888
- .create((get) => ({
10889
- url: get('AUTH_URL').string(),
10890
- }))
10891
- .parse();
10892
-
10893
- logger.info({ authUrl: config.url }, 'Auth service configured');
10894
-
10895
- return {
10896
- getSession: async (cookie: string): Promise<Session | null> => {
10897
- const res = await fetch(\`\${config.url}/api/auth/get-session\`, {
10898
- headers: { cookie },
10899
- });
10900
- if (!res.ok) return null;
10901
- return (await res.json()) as Session | null;
10902
- },
10903
- };
10904
- },
10905
- } satisfies Service<'auth', AuthClient>;
10906
- `
10907
- });
10908
11106
  files.push({
10909
11107
  path: "src/router.ts",
10910
11108
  content: `import { UnauthorizedError } from '@geekmidas/errors';
10911
11109
  import { api } from '${constructsImport("api")}';${options.constructs.database ? `
10912
11110
  import { database } from '${constructsImport("database")}';` : ""}
10913
- import { authService, type Session } from './services/auth.ts';
10914
11111
 
10915
11112
  /**
10916
11113
  * The shared endpoint factory — no session required.
@@ -10921,15 +11118,17 @@ import { authService, type Session } from './services/auth.ts';
10921
11118
  */
10922
11119
  export const router = api${options.constructs.database ? ".database(database)" : ""};
10923
11120
 
10924
- // The auth client available, but the session not enforced.
10925
- export const r = router.services([authService]);
10926
-
10927
- // Requires an active session — throws when there is none.
10928
- export const sessionRouter = r.session<Session>(async ({ services, header }) => {
10929
- const cookie = header('cookie') || '';
10930
- const session = await services.auth.getSession(cookie);
11121
+ /**
11122
+ * Requires an active session — throws when there is none.
11123
+ *
11124
+ * \`auth\` is the construct named in \`.auth()\` on the API, bound to this
11125
+ * request: it asks the auth server whose cookie this is. Handlers built from
11126
+ * this router get whatever this returns as \`session\`.
11127
+ */
11128
+ export const sessionRouter = router.session(async ({ auth }) => {
11129
+ const session = await auth.getSession();
10931
11130
 
10932
- if (!session?.user) {
11131
+ if (!session) {
10933
11132
  throw new UnauthorizedError('No active session');
10934
11133
  }
10935
11134
 
@@ -11016,46 +11215,6 @@ export const email = new Email('${mail.id}', { templates: {} });
11016
11215
  * schema and reached by its role, whatever the target.
11017
11216
  */
11018
11217
  export const cache = new Cache('${kv.id}');
11019
- `
11020
- });
11021
- if (options.constructs.database && !declares) files.push({
11022
- path: "src/services/database.ts",
11023
- content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
11024
- import { Kysely, PostgresDialect } from 'kysely';
11025
- import pg from 'pg';
11026
-
11027
- // Define your database schema
11028
- export interface Database {
11029
- users: {
11030
- id: string;
11031
- name: string;
11032
- email: string;
11033
- created_at: Date;
11034
- };
11035
- }
11036
-
11037
- export const databaseService = {
11038
- serviceName: 'database' as const,
11039
- async register({ envParser, context }: ServiceRegisterOptions) {
11040
- const logger = context.getLogger();
11041
- logger.info('Connecting to database');
11042
-
11043
- const config = envParser
11044
- .create((get) => ({
11045
- url: get('DATABASE_URL').string(),
11046
- }))
11047
- .parse();
11048
-
11049
- const db = new Kysely<Database>({
11050
- dialect: new PostgresDialect({
11051
- pool: new pg.Pool({ connectionString: config.url }),
11052
- }),
11053
- });
11054
-
11055
- logger.info('Database connection established');
11056
- return db;
11057
- },
11058
- } satisfies Service<'database', Kysely<Database>>;
11059
11218
  `
11060
11219
  });
11061
11220
  if (options.telescope) files.push({
@@ -11074,17 +11233,19 @@ export const telescope = new Telescope({
11074
11233
  content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
11075
11234
  import { Kysely, PostgresDialect } from 'kysely';
11076
11235
  import pg from 'pg';
11077
- import type { Database } from '${src(declares ? "constructs/database.ts" : "services/database.ts")}';
11236
+ import { type Database, database } from '${declares ? src("constructs/database.ts") : constructsImport("database")}';
11078
11237
  import { envParser } from '${src("config/env.ts")}';
11079
11238
 
11080
11239
  const studioConfig = envParser
11081
11240
  .create((get) => ({
11082
- databaseUrl: get('${declares ? db.urlKey : "DATABASE_URL"}').string(),
11241
+ databaseUrl: get('${db.urlKey}').string(),
11083
11242
  }))
11084
11243
  .parse();
11085
11244
 
11086
- // Create a Kysely instance for Studio
11245
+ // Studio's own client, built the way the construct builds its own — the same
11246
+ // plugins — so the browser shows what the app's queries see.
11087
11247
  const db = new Kysely<Database>({
11248
+ ...database.clientConfig,
11088
11249
  dialect: new PostgresDialect({
11089
11250
  pool: new pg.Pool({ connectionString: studioConfig.databaseUrl }),
11090
11251
  }),
@@ -11439,7 +11600,8 @@ const workerTemplate = {
11439
11600
  "@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
11440
11601
  "@geekmidas/events": GEEKMIDAS_VERSIONS["@geekmidas/events"],
11441
11602
  "@geekmidas/logger": GEEKMIDAS_VERSIONS["@geekmidas/logger"],
11442
- "@geekmidas/schema": GEEKMIDAS_VERSIONS["@geekmidas/schema"]
11603
+ "@geekmidas/schema": GEEKMIDAS_VERSIONS["@geekmidas/schema"],
11604
+ zod: DEPENDENCY_VERSIONS.zod
11443
11605
  },
11444
11606
  devDependencies: {
11445
11607
  "@biomejs/biome": DEPENDENCY_VERSIONS["@biomejs/biome"],
@@ -11498,51 +11660,34 @@ export const config = envParser
11498
11660
  content: loggerContent
11499
11661
  },
11500
11662
  {
11501
- path: "src/events/types.ts",
11502
- content: `import type { PublishableMessage } from '@geekmidas/events';
11503
-
11504
- // Define your event types here
11505
- export type AppEvents =
11506
- | PublishableMessage<'user.created', { userId: string; email: string }>
11507
- | PublishableMessage<'user.updated', { userId: string; changes: Record<string, unknown> }>
11508
- | PublishableMessage<'order.placed', { orderId: string; userId: string; total: number }>;
11509
- `
11510
- },
11511
- {
11512
- path: "src/events/publisher.ts",
11513
- content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
11514
- import { Publisher, type EventPublisher } from '@geekmidas/events';
11515
- import type { AppEvents } from './types.ts';
11516
-
11517
- export const eventsPublisherService = {
11518
- serviceName: 'events' as const,
11519
- async register({ envParser, context }: ServiceRegisterOptions) {
11520
- const logger = context.getLogger();
11521
- logger.info('Connecting to message broker');
11522
-
11523
- const config = envParser
11524
- .create((get) => ({
11525
- url: get('RABBITMQ_URL').string().default('amqp://localhost:5672'),
11526
- }))
11527
- .parse();
11528
-
11529
- const publisher = await Publisher.fromConnectionString<AppEvents>(
11530
- \`rabbitmq://\${config.url.replace('amqp://', '')}?exchange=events\`
11531
- );
11663
+ path: "src/constructs/topics.ts",
11664
+ content: `import { t } from '@geekmidas/constructs/topic';
11665
+ import { z } from 'zod';
11532
11666
 
11533
- logger.info('Message broker connection established');
11534
- return publisher;
11535
- },
11536
- } satisfies Service<'events', EventPublisher<AppEvents>>;
11667
+ /**
11668
+ * The \`users\` topic: pub/sub fan-out, any number of subscribers.
11669
+ *
11670
+ * The event map is the contract. It types \`users.publisher\` and every
11671
+ * subscriber that binds with \`.topic(users)\`, so an event cannot be published
11672
+ * in one shape and read in another.
11673
+ */
11674
+ export const users = t.topic('users').events({
11675
+ 'user.created': z.object({ userId: z.string(), email: z.email() }),
11676
+ 'user.updated': z.object({
11677
+ userId: z.string(),
11678
+ changes: z.record(z.string(), z.unknown()),
11679
+ }),
11680
+ });
11537
11681
  `
11538
11682
  },
11539
11683
  {
11540
11684
  path: "src/subscribers/user-events.ts",
11541
- content: `import { worker } from '~/constructs/worker.ts';
11542
- import { eventsPublisherService } from '~/events/publisher.ts';
11685
+ content: `import { users } from '~/constructs/topics.ts';
11686
+ import { worker } from '~/constructs/worker.ts';
11543
11687
 
11688
+ // Bound to the topic, not handed its publisher: a subscriber consumes.
11544
11689
  export const userEventsSubscriber = worker
11545
- .publisher(eventsPublisherService)
11690
+ .topic(users)
11546
11691
  .subscribe(['user.created', 'user.updated'])
11547
11692
  .handle(async ({ events, logger }) => {
11548
11693
  // A batch, not a single event. Both transports deliver in batches, so
@@ -11848,7 +11993,7 @@ function generateSourceFiles(options, template) {
11848
11993
  function generateTestFiles(options, _template) {
11849
11994
  if (!options.constructs.database) return [];
11850
11995
  const db = databaseFor();
11851
- const schema = !options.monorepo ? "../src/constructs/database.ts" : "~/services/database.ts";
11996
+ const schema = !options.monorepo ? "../src/constructs/database.ts" : `@${options.name}/constructs/database.ts`;
11852
11997
  const runtimeUrl = db.urlKey;
11853
11998
  const ownerUrl = db.ownerUrlKey;
11854
11999
  return [
@@ -11877,21 +12022,13 @@ export default defineConfig({
11877
12022
  {
11878
12023
  path: "test/config.ts",
11879
12024
  content: `import { it as itVitest } from 'vitest';
11880
- import { Kysely, PostgresDialect } from 'kysely';
11881
- import pg from 'pg';
11882
12025
  import { wrapVitestKyselyTransaction } from '@geekmidas/testkit/kysely';
11883
- import type { Database } from '${schema}';
11884
-
11885
- const db = new Kysely<Database>({
11886
- dialect: new PostgresDialect({
11887
- pool: new pg.Pool({ connectionString: process.env.${runtimeUrl} }),
11888
- }),
11889
- });
12026
+ import { type Database, database } from '${schema}';
11890
12027
 
11891
12028
  export const it = wrapVitestKyselyTransaction<Database>(itVitest, {
11892
- // A function, or a construct: an instance is taken for a construct and has
11893
- // no service to register.
11894
- connection: () => db,
12029
+ // The construct itself, so the tests connect the way the app does — with
12030
+ // the plugins it was given, \`CamelCasePlugin\` included.
12031
+ connection: database,
11895
12032
  });
11896
12033
  `
11897
12034
  },
@@ -11965,7 +12102,7 @@ export const usersBuilder = KyselyFactory.createBuilder<Database, 'users'>(
11965
12102
  id: faker.string.uuid(),
11966
12103
  name: faker.person.fullName(),
11967
12104
  email: faker.internet.email(),
11968
- created_at: new Date(),
12105
+ createdAt: new Date(),
11969
12106
  }),
11970
12107
  );
11971
12108
  `
@@ -14650,7 +14787,7 @@ var NotAnAwsRegion = class extends Error {
14650
14787
  };
14651
14788
  //#endregion
14652
14789
  //#region src/secrets/index.ts
14653
- const logger$3 = console;
14790
+ const logger$2 = console;
14654
14791
  /**
14655
14792
  * Initialize secrets for a stage.
14656
14793
  * Generates secure random passwords for configured services.
@@ -14658,7 +14795,7 @@ const logger$3 = console;
14658
14795
  async function secretsInitCommand(options) {
14659
14796
  const { stage, force } = options;
14660
14797
  if (!force && require_storage.secretsExist(stage)) {
14661
- logger$3.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
14798
+ logger$2.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
14662
14799
  process.exit(1);
14663
14800
  }
14664
14801
  let projectName;
@@ -14668,21 +14805,20 @@ async function secretsInitCommand(options) {
14668
14805
  projectName = loaded.workspace.name;
14669
14806
  if (Object.keys(loaded.workspace.apps).length > 1) {
14670
14807
  workspaceSecrets = require_reconcile.generateFullstackCustomSecrets(loaded.workspace);
14671
- logger$3.log(" Detected workspace mode — generating per-app secrets");
14808
+ logger$2.log(" Detected workspace mode — generating per-app secrets");
14672
14809
  }
14673
14810
  } catch {}
14674
14811
  const secrets = require_reconcile.createStageSecrets(stage, [], { projectName });
14675
14812
  if (workspaceSecrets) secrets.custom = workspaceSecrets;
14676
14813
  await require_storage.writeStageSecrets(secrets);
14677
- logger$3.log(`\n✓ Secrets initialized for stage "${stage}"`);
14678
- logger$3.log(` Location: .gkm/secrets/${stage}.json`);
14679
- if (secrets.urls.DATABASE_URL) logger$3.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
14680
- if (secrets.urls.REDIS_URL) logger$3.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
14681
- if (secrets.urls.RABBITMQ_URL) logger$3.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
14682
- if (secrets.urls.STORAGE_ENDPOINT) logger$3.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
14683
- if (Object.keys(secrets.custom).length > 0) logger$3.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
14684
- logger$3.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
14685
- logger$3.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
14814
+ logger$2.log(`\n✓ Secrets initialized for stage "${stage}"`);
14815
+ logger$2.log(` Location: .gkm/secrets/${stage}.json`);
14816
+ if (secrets.urls.DATABASE_URL) logger$2.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
14817
+ if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
14818
+ if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
14819
+ if (Object.keys(secrets.custom).length > 0) logger$2.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
14820
+ logger$2.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
14821
+ logger$2.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
14686
14822
  }
14687
14823
  /**
14688
14824
  * Read all data from stdin.
@@ -14701,21 +14837,21 @@ async function secretsSetCommand(key, value, options) {
14701
14837
  let secretValue = value;
14702
14838
  if (!secretValue) {
14703
14839
  if (process.stdin.isTTY) {
14704
- logger$3.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
14705
- logger$3.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
14840
+ logger$2.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
14841
+ logger$2.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
14706
14842
  process.exit(1);
14707
14843
  }
14708
14844
  secretValue = await readStdin();
14709
14845
  if (!secretValue) {
14710
- logger$3.error("No value received from stdin");
14846
+ logger$2.error("No value received from stdin");
14711
14847
  process.exit(1);
14712
14848
  }
14713
14849
  }
14714
14850
  try {
14715
14851
  await require_storage.setCustomSecret(stage, key, secretValue);
14716
- logger$3.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
14852
+ logger$2.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
14717
14853
  } catch (error) {
14718
- logger$3.error(error instanceof Error ? error.message : "Failed to set secret");
14854
+ logger$2.error(error instanceof Error ? error.message : "Failed to set secret");
14719
14855
  process.exit(1);
14720
14856
  }
14721
14857
  }
@@ -14726,33 +14862,31 @@ async function secretsShowCommand(options) {
14726
14862
  const { stage, reveal } = options;
14727
14863
  const secrets = await require_storage.readStageSecrets(stage);
14728
14864
  if (!secrets) {
14729
- logger$3.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14865
+ logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14730
14866
  process.exit(1);
14731
14867
  }
14732
- logger$3.log(`\nSecrets for stage "${stage}":`);
14733
- logger$3.log(` Created: ${secrets.createdAt}`);
14734
- logger$3.log(` Updated: ${secrets.updatedAt}`);
14735
- logger$3.log("\nService Credentials:");
14868
+ logger$2.log(`\nSecrets for stage "${stage}":`);
14869
+ logger$2.log(` Created: ${secrets.createdAt}`);
14870
+ logger$2.log(` Updated: ${secrets.updatedAt}`);
14871
+ logger$2.log("\nService Credentials:");
14736
14872
  for (const [service, creds] of Object.entries(secrets.services)) if (creds) {
14737
- logger$3.log(`\n ${service}:`);
14738
- logger$3.log(` host: ${creds.host}`);
14739
- logger$3.log(` port: ${creds.port}`);
14740
- logger$3.log(` username: ${creds.username}`);
14741
- logger$3.log(` password: ${reveal ? creds.password : require_storage.maskPassword(creds.password)}`);
14742
- if (creds.database) logger$3.log(` database: ${creds.database}`);
14743
- if (creds.vhost) logger$3.log(` vhost: ${creds.vhost}`);
14744
- if (creds.bucket) logger$3.log(` bucket: ${creds.bucket}`);
14745
- }
14746
- logger$3.log("\nConnection URLs:");
14747
- if (secrets.urls.DATABASE_URL) logger$3.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
14748
- if (secrets.urls.REDIS_URL) logger$3.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
14749
- if (secrets.urls.RABBITMQ_URL) logger$3.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
14750
- if (secrets.urls.STORAGE_ENDPOINT) logger$3.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
14873
+ logger$2.log(`\n ${service}:`);
14874
+ logger$2.log(` host: ${creds.host}`);
14875
+ logger$2.log(` port: ${creds.port}`);
14876
+ logger$2.log(` username: ${creds.username}`);
14877
+ logger$2.log(` password: ${reveal ? creds.password : require_storage.maskPassword(creds.password)}`);
14878
+ if (creds.database) logger$2.log(` database: ${creds.database}`);
14879
+ if (creds.bucket) logger$2.log(` bucket: ${creds.bucket}`);
14880
+ }
14881
+ logger$2.log("\nConnection URLs:");
14882
+ if (secrets.urls.DATABASE_URL) logger$2.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
14883
+ if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
14884
+ if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
14751
14885
  if (Object.keys(secrets.custom).length > 0) {
14752
- logger$3.log("\nCustom Secrets:");
14753
- for (const [key, value] of Object.entries(secrets.custom)) logger$3.log(` ${key}: ${reveal ? value : require_storage.maskPassword(value)}`);
14886
+ logger$2.log("\nCustom Secrets:");
14887
+ for (const [key, value] of Object.entries(secrets.custom)) logger$2.log(` ${key}: ${reveal ? value : require_storage.maskPassword(value)}`);
14754
14888
  }
14755
- if (!reveal) logger$3.log("\nUse --reveal to show actual values");
14889
+ if (!reveal) logger$2.log("\nUse --reveal to show actual values");
14756
14890
  }
14757
14891
  /**
14758
14892
  * Rotate passwords for services.
@@ -14761,25 +14895,25 @@ async function secretsRotateCommand(options) {
14761
14895
  const { stage, service } = options;
14762
14896
  const secrets = await require_storage.readStageSecrets(stage);
14763
14897
  if (!secrets) {
14764
- logger$3.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14898
+ logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14765
14899
  process.exit(1);
14766
14900
  }
14767
14901
  if (service) {
14768
14902
  if (!secrets.services[service]) {
14769
- logger$3.error(`Service "${service}" not configured in stage "${stage}"`);
14903
+ logger$2.error(`Service "${service}" not configured in stage "${stage}"`);
14770
14904
  process.exit(1);
14771
14905
  }
14772
14906
  const updated = require_reconcile.rotateServicePassword(secrets, service);
14773
14907
  await require_storage.writeStageSecrets(updated);
14774
- logger$3.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
14908
+ logger$2.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
14775
14909
  } else {
14776
14910
  let updated = secrets;
14777
14911
  const services = Object.keys(secrets.services);
14778
14912
  for (const svc of services) updated = require_reconcile.rotateServicePassword(updated, svc);
14779
14913
  await require_storage.writeStageSecrets(updated);
14780
- logger$3.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
14914
+ logger$2.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
14781
14915
  }
14782
- logger$3.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
14916
+ logger$2.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
14783
14917
  }
14784
14918
  /**
14785
14919
  * Import secrets from a JSON file.
@@ -14787,7 +14921,7 @@ async function secretsRotateCommand(options) {
14787
14921
  async function secretsImportCommand(file, options) {
14788
14922
  const { stage, merge = true } = options;
14789
14923
  if (!(0, node_fs.existsSync)(file)) {
14790
- logger$3.error(`File not found: ${file}`);
14924
+ logger$2.error(`File not found: ${file}`);
14791
14925
  process.exit(1);
14792
14926
  }
14793
14927
  let importedSecrets;
@@ -14797,12 +14931,12 @@ async function secretsImportCommand(file, options) {
14797
14931
  if (typeof importedSecrets !== "object" || importedSecrets === null) throw new Error("JSON must be an object");
14798
14932
  for (const [key, value] of Object.entries(importedSecrets)) if (typeof value !== "string") throw new Error(`Value for "${key}" must be a string, got ${typeof value}`);
14799
14933
  } catch (error) {
14800
- logger$3.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
14934
+ logger$2.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
14801
14935
  process.exit(1);
14802
14936
  }
14803
14937
  const secrets = await require_storage.readStageSecrets(stage);
14804
14938
  if (!secrets) {
14805
- logger$3.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14939
+ logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14806
14940
  process.exit(1);
14807
14941
  }
14808
14942
  const updatedCustom = merge ? {
@@ -14817,10 +14951,10 @@ async function secretsImportCommand(file, options) {
14817
14951
  await require_storage.writeStageSecrets(updated);
14818
14952
  const importedCount = Object.keys(importedSecrets).length;
14819
14953
  const totalCount = Object.keys(updatedCustom).length;
14820
- logger$3.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
14821
- if (merge && totalCount > importedCount) logger$3.log(` Total custom secrets: ${totalCount}`);
14822
- logger$3.log("\n Imported keys:");
14823
- for (const key of Object.keys(importedSecrets)) logger$3.log(` - ${key}`);
14954
+ logger$2.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
14955
+ if (merge && totalCount > importedCount) logger$2.log(` Total custom secrets: ${totalCount}`);
14956
+ logger$2.log("\n Imported keys:");
14957
+ for (const key of Object.keys(importedSecrets)) logger$2.log(` - ${key}`);
14824
14958
  }
14825
14959
  /**
14826
14960
  * Mask password in a URL for display.
@@ -14835,175 +14969,120 @@ function maskUrl(url) {
14835
14969
  }
14836
14970
  }
14837
14971
  //#endregion
14838
- //#region src/trust/index.ts
14839
- /**
14840
- * Installing the local edge's certificate authority into the OS trust store.
14841
- *
14842
- * `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test
14843
- * suite, anything under `gkm exec` — because reconcile injects it. A browser
14844
- * reads none of that: it asks the operating system, so until the root is in the
14845
- * system store a local `https://` address is "not secure", which is true and
14846
- * unhelpful.
14847
- *
14848
- * A separate command rather than part of `gkm setup`, for one reason: this
14849
- * needs `sudo`, and a setup that silently escalates on every run is a worse
14850
- * trade than one that tells you what to type. Caddy's own `caddy trust` draws
14851
- * the line in the same place.
14852
- */
14853
- const logger$2 = console;
14854
- const run = (0, node_util.promisify)(node_child_process.execFile);
14855
- /**
14856
- * How each platform is told about a new authority.
14857
- *
14858
- * Separate rather than shelled through one script because the failure modes
14859
- * differ and the message should name the real one: macOS refuses without an
14860
- * admin password, Debian needs the file in a specific directory before its
14861
- * updater will look at it.
14862
- */
14863
- function installation(platform, certificate) {
14864
- if (platform === "darwin") return {
14865
- describe: "the System keychain",
14866
- steps: [["sudo", [
14867
- "security",
14868
- "add-trusted-cert",
14869
- "-d",
14870
- "-r",
14871
- "trustRoot",
14872
- "-k",
14873
- "/Library/Keychains/System.keychain",
14874
- certificate
14875
- ]]]
14876
- };
14877
- if (platform === "linux") {
14878
- const debian = (0, node_fs.existsSync)("/usr/local/share/ca-certificates");
14879
- return {
14880
- describe: debian ? "the system CA bundle" : "the system trust anchors",
14881
- steps: debian ? [["sudo", [
14882
- "cp",
14883
- certificate,
14884
- "/usr/local/share/ca-certificates/gkm-local-ca.crt"
14885
- ]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
14886
- "cp",
14887
- certificate,
14888
- "/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
14889
- ]], ["sudo", ["update-ca-trust"]]]
14890
- };
14972
+ //#region src/secrets/store.ts
14973
+ /** The encrypted file under `.gkm/secrets/`, and its key under `~/.gkm/`. */
14974
+ var FileSecretsStore = class {
14975
+ root;
14976
+ constructor(root) {
14977
+ this.root = root;
14891
14978
  }
14892
- throw new UnsupportedPlatform(platform, certificate);
14893
- }
14894
- /**
14895
- * Trust the certificate authority this project's edge issues from.
14896
- *
14897
- * Idempotent in effect: installing a root that is already trusted replaces the
14898
- * same entry, so running it twice is not an error.
14899
- */
14900
- async function trustCommand(options = {}) {
14901
- const { workspace } = await require_config.loadWorkspaceConfig();
14902
- const certificate = (0, node_path.join)(workspace.root, require_workspace$1.LOCAL_CA_PATH);
14903
- if (!(0, node_fs.existsSync)(certificate)) throw new NoLocalAuthority(certificate);
14904
- const { describe, steps } = installation(process.platform, certificate);
14905
- logger$2.log(`\n🔐 Trusting the local authority in ${describe}`);
14906
- logger$2.log(` ${certificate}\n`);
14907
- if (options.dryRun) {
14908
- for (const [command, args] of steps) logger$2.log(` ${command} ${args.join(" ")}`);
14909
- return;
14979
+ pull(stage) {
14980
+ return require_storage.readStageSecrets(stage, this.root);
14910
14981
  }
14911
- logger$2.log(" This needs an admin password.\n");
14912
- for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
14913
- logger$2.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
14914
- }
14982
+ push(_stage, secrets) {
14983
+ return require_storage.writeStageSecrets(secrets, this.root);
14984
+ }
14985
+ };
14915
14986
  /**
14916
- * Trust the authority if this machine lacks it and its owner agrees.
14917
- *
14918
- * A machine's answer, not the project's: whether a laptop installs a root
14919
- * certificate is its owner's call, so the decision is `--yes`, then what this
14920
- * machine previously answered, then a prompt. Never a silent `sudo` — that is
14921
- * the one thing a setup command should not do on somebody's behalf.
14987
+ * The store a stage's secrets live in.
14922
14988
  *
14923
- * Silent and free when the machine already trusts it, which is what makes it
14924
- * acceptable to call on every `gkm setup`.
14925
- */
14926
- async function ensureTrusted(root, url, options = {}) {
14927
- if (await isTrusted(url)) return;
14928
- if (!options.assumeYes) {
14929
- if (!process.stdin.isTTY) {
14930
- logger$2.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
14931
- return;
14932
- }
14933
- if (await declined(root)) return;
14934
- const { install } = await (0, prompts.default)({
14935
- type: "confirm",
14936
- name: "install",
14937
- message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
14938
- initial: false
14939
- });
14940
- if (!install) {
14941
- await recordDeclined(root);
14942
- logger$2.log(" Skipped. Run \"gkm trust\" later.");
14943
- return;
14944
- }
14945
- }
14946
- await trustCommand();
14947
- }
14948
- /** Where a declined answer is remembered, so setup asks once and not again. */
14949
- const TRUST_STATE = ".gkm/trust.json";
14950
- async function declined(root) {
14951
- try {
14952
- const raw = await (0, node_fs_promises.readFile)((0, node_path.join)(root, TRUST_STATE), "utf-8");
14953
- return JSON.parse(raw).declined === true;
14954
- } catch {
14955
- return false;
14956
- }
14989
+ * The local stage is always the file: its secrets belong to the machine
14990
+ * running `gkm dev`. A deployed stage uses `secrets.store`.
14991
+ */
14992
+ async function secretsStoreFor(workspace, stage, options = {}) {
14993
+ const configured = workspace.secrets.store ?? "file";
14994
+ if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root);
14995
+ if (configured.provider !== "ssm") return configured.provider;
14996
+ const { SsmSecretsStore } = await Promise.resolve().then(() => require("./ssm-FxeSAt35.cjs"));
14997
+ return new SsmSecretsStore({
14998
+ project: workspace.name,
14999
+ region: configured.region,
15000
+ ...options.profile ? { profile: options.profile } : {}
15001
+ });
14957
15002
  }
14958
- async function recordDeclined(root) {
14959
- const path = (0, node_path.join)(root, TRUST_STATE);
14960
- await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
14961
- await (0, node_fs_promises.writeFile)(path, `${JSON.stringify({ declined: true }, null, 2)}\n`);
15003
+ /** Whether a stage's secrets live somewhere other than this machine. */
15004
+ function isRemoteStore(workspace, stage) {
15005
+ const configured = workspace.secrets.store ?? "file";
15006
+ return stage !== workspace.stages.local && configured !== "file";
14962
15007
  }
15008
+ //#endregion
15009
+ //#region src/secrets/transfer.ts
14963
15010
  /**
14964
- * Whether this machine already trusts the local authority.
14965
- *
14966
- * Asked by connecting rather than by reading a keychain: what matters is
14967
- * whether a client with no special configuration verifies the edge, and that is
14968
- * one platform-neutral question instead of three platform-specific ones. It is
14969
- * also the exact thing a browser is about to do.
15011
+ * Moving a deployed stage's secrets between this machine and its store.
14970
15012
  *
14971
- * A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and
14972
- * this process almost certainly has it set — asking in-process would answer
14973
- * "yes" for everyone.
14974
- */
14975
- async function isTrusted(url) {
14976
- const { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;
14977
- try {
14978
- await run(process.execPath, ["-e", `fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`], {
14979
- env,
14980
- timeout: 15e3
14981
- });
14982
- return true;
14983
- } catch {
14984
- return false;
15013
+ * `push` sends the encrypted local copy to the stage's store; `pull` brings it
15014
+ * back and writes the local copy a deploy reads — generating a key for it if
15015
+ * the machine has none, which is what a fresh CI runner needs. Both reconcile
15016
+ * the keys the workspace now derives, so a store never lags a new construct.
15017
+ */
15018
+ var transfer_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
15019
+ NoLocalSecrets: () => NoLocalSecrets,
15020
+ NoRemoteSecretsStore: () => NoRemoteSecretsStore,
15021
+ NoStoredSecrets: () => NoStoredSecrets,
15022
+ pullStageSecrets: () => pullStageSecrets,
15023
+ pushStageSecrets: () => pushStageSecrets
15024
+ });
15025
+ /** A push or pull for a stage with nowhere to push or pull from. */
15026
+ var NoRemoteSecretsStore = class extends Error {
15027
+ stage;
15028
+ constructor(stage) {
15029
+ super(`Stage "${stage}" keeps its secrets in the local file, so there is nothing to push or pull. Set secrets.store in gkm.config.ts (e.g. { provider: 'ssm', region: 'eu-west-1' }) for deployed stages.`);
15030
+ this.stage = stage;
15031
+ this.name = "NoRemoteSecretsStore";
14985
15032
  }
14986
- }
14987
- /** The edge has not run, so there is no authority to trust yet. */
14988
- var NoLocalAuthority = class extends Error {
14989
- path;
14990
- constructor(path) {
14991
- super(`No local certificate authority at ${path}. It is generated the first time the edge starts, so run "gkm setup" (or "gkm dev") once — and if this project declares no file server, nothing needs one.`);
14992
- this.path = path;
14993
- this.name = "NoLocalAuthority";
15033
+ };
15034
+ /** A push for a stage whose secrets this machine does not have. */
15035
+ var NoLocalSecrets = class extends Error {
15036
+ stage;
15037
+ constructor(stage) {
15038
+ super(`No secrets for stage "${stage}" on this machine. Run gkm secrets:init --stage ${stage} first.`);
15039
+ this.stage = stage;
15040
+ this.name = "NoLocalSecrets";
14994
15041
  }
14995
15042
  };
14996
- /** A platform with no trust store this knows how to write to. */
14997
- var UnsupportedPlatform = class extends Error {
14998
- platform;
14999
- certificate;
15000
- constructor(platform, certificate) {
15001
- super(`gkm does not know how to install a certificate authority on ${platform}. The root is at ${certificate} — add it to the trust store by hand, or point a client at it with NODE_EXTRA_CA_CERTS.`);
15002
- this.platform = platform;
15003
- this.certificate = certificate;
15004
- this.name = "UnsupportedPlatform";
15043
+ /** A pull for a stage the store holds nothing for. */
15044
+ var NoStoredSecrets = class extends Error {
15045
+ stage;
15046
+ constructor(stage) {
15047
+ super(`The store holds no secrets for stage "${stage}". Push them first: gkm secrets:push --stage ${stage}.`);
15048
+ this.stage = stage;
15049
+ this.name = "NoStoredSecrets";
15005
15050
  }
15006
15051
  };
15052
+ async function reconcile(secrets, workspace, stage) {
15053
+ const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-B3ApLW6p.cjs")).then((n) => n.workspace_exports);
15054
+ const result = require_reconcile.reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
15055
+ return result ? {
15056
+ secrets: result.secrets,
15057
+ addedKeys: result.addedKeys
15058
+ } : {
15059
+ secrets,
15060
+ addedKeys: []
15061
+ };
15062
+ }
15063
+ function remoteOnly(workspace, stage) {
15064
+ require_workspace.assertDeployedStage(workspace.stages, stage);
15065
+ if (!isRemoteStore(workspace, stage)) throw new NoRemoteSecretsStore(stage);
15066
+ }
15067
+ /** Send this machine's secrets for a deployed stage to its store. */
15068
+ async function pushStageSecrets(workspace, stage, options = {}) {
15069
+ remoteOnly(workspace, stage);
15070
+ const local = await require_storage.readStageSecrets(stage, workspace.root);
15071
+ if (!local) throw new NoLocalSecrets(stage);
15072
+ const result = await reconcile(local, workspace, stage);
15073
+ if (result.addedKeys.length) await require_storage.writeStageSecrets(result.secrets, workspace.root);
15074
+ await (await secretsStoreFor(workspace, stage, options)).push(stage, result.secrets);
15075
+ return result;
15076
+ }
15077
+ /** Bring a deployed stage's secrets from its store into the local copy. */
15078
+ async function pullStageSecrets(workspace, stage, options = {}) {
15079
+ remoteOnly(workspace, stage);
15080
+ const stored = await (await secretsStoreFor(workspace, stage, options)).pull(stage);
15081
+ if (!stored) throw new NoStoredSecrets(stage);
15082
+ const result = await reconcile(stored, workspace, stage);
15083
+ await require_storage.writeStageSecrets(result.secrets, workspace.root);
15084
+ return result;
15085
+ }
15007
15086
  //#endregion
15008
15087
  //#region src/setup/index.ts
15009
15088
  var setup_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
@@ -15072,8 +15151,7 @@ async function reconcileLocal(workspace, stage, options) {
15072
15151
  }
15073
15152
  logger$1.log(`🐳 Services: ${result.plan.containers.join(", ")}`);
15074
15153
  for (const [container, address] of Object.entries(result.addresses)) logger$1.log(` ${container}: ${address}`);
15075
- const served = result.plan.resources.find((r) => r.kind === "file-server");
15076
- const url = served ? result.env[served.envKey] : void 0;
15154
+ const url = result.plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site" || r.kind === "file-server").map((r) => result.env[r.envKey]).find((address) => address?.startsWith("https://"));
15077
15155
  if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
15078
15156
  }
15079
15157
  /**
@@ -15098,14 +15176,14 @@ async function resolveSecrets(stage, workspace, options) {
15098
15176
  return reconciled ?? secrets;
15099
15177
  }
15100
15178
  }
15101
- if (require_transfer.isRemoteStore(workspace, stage)) {
15179
+ if (isRemoteStore(workspace, stage)) {
15102
15180
  logger$1.log("☁️ Checking the secrets store...");
15103
15181
  try {
15104
- const { secrets } = await require_transfer.pullStageSecrets(workspace, stage);
15182
+ const { secrets } = await pullStageSecrets(workspace, stage);
15105
15183
  logger$1.log("✅ Pulled secrets from the store");
15106
15184
  return secrets;
15107
15185
  } catch (error) {
15108
- if (error instanceof require_transfer.NoStoredSecrets) logger$1.log(" The store holds none yet");
15186
+ if (error instanceof NoStoredSecrets) logger$1.log(" The store holds none yet");
15109
15187
  else logger$1.warn(`⚠️ Could not pull from the store: ${error.message}`);
15110
15188
  }
15111
15189
  }
@@ -15132,8 +15210,7 @@ function credentialedServices(containers) {
15132
15210
  redis: "redis",
15133
15211
  minio: "minio",
15134
15212
  mailpit: "mailpit",
15135
- localstack: "localstack",
15136
- rabbitmq: "rabbitmq"
15213
+ localstack: "localstack"
15137
15214
  };
15138
15215
  return [...new Set(containers.map((container) => byContainer[container]).filter((name) => Boolean(name)))];
15139
15216
  }
@@ -15153,7 +15230,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
15153
15230
  [name]: creds
15154
15231
  }
15155
15232
  };
15156
- result.urls = require_reconcile.generateConnectionUrls(result.services, result.eventsBackend);
15233
+ result.urls = require_reconcile.generateConnectionUrls(result.services);
15157
15234
  logger$1.log(` 🔄 Adding missing service credentials: ${name}`);
15158
15235
  changed = true;
15159
15236
  }
@@ -15171,7 +15248,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
15171
15248
  }
15172
15249
  }
15173
15250
  };
15174
- result.urls = require_reconcile.generateConnectionUrls(result.services, result.eventsBackend);
15251
+ result.urls = require_reconcile.generateConnectionUrls(result.services);
15175
15252
  logger$1.log(" 🔄 Adding missing service credentials: pgboss");
15176
15253
  changed = true;
15177
15254
  }
@@ -15239,7 +15316,7 @@ async function generateFreshSecrets(stage, workspace, options) {
15239
15316
  const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_workspace$1.derivedContainers(workspace, stage));
15240
15317
  await require_storage.writeStageSecrets(secrets, workspace.root);
15241
15318
  logger$1.log(` Secrets written to .gkm/secrets/${stage}.json`);
15242
- if (require_transfer.isRemoteStore(workspace, stage) && !options.yes) {
15319
+ if (isRemoteStore(workspace, stage) && !options.yes) {
15243
15320
  const { shouldPush } = await (0, prompts.default)({
15244
15321
  type: "confirm",
15245
15322
  name: "shouldPush",
@@ -15247,7 +15324,7 @@ async function generateFreshSecrets(stage, workspace, options) {
15247
15324
  initial: true
15248
15325
  });
15249
15326
  if (shouldPush) try {
15250
- await require_transfer.pushStageSecrets(workspace, stage);
15327
+ await pushStageSecrets(workspace, stage);
15251
15328
  logger$1.log("☁️ Secrets pushed to the store");
15252
15329
  } catch (error) {
15253
15330
  logger$1.warn(`⚠️ Could not push to the store: ${error.message}`);
@@ -16095,7 +16172,7 @@ program.command("secrets:show").description("Show secrets for a stage").required
16095
16172
  process.exit(1);
16096
16173
  }
16097
16174
  });
16098
- program.command("secrets:rotate").description("Rotate service passwords").requiredOption("--stage <stage>", "Stage name").option("--service <service>", "Specific service to rotate (postgres, redis, rabbitmq)").action(async (options) => {
16175
+ program.command("secrets:rotate").description("Rotate service passwords").requiredOption("--stage <stage>", "Stage name").option("--service <service>", "Specific service to rotate (postgres, redis, minio, …)").action(async (options) => {
16099
16176
  try {
16100
16177
  const globalOptions = program.opts();
16101
16178
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
@@ -16120,7 +16197,7 @@ program.command("secrets:push").description("Send a deployed stage's secrets to
16120
16197
  const globalOptions = program.opts();
16121
16198
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16122
16199
  const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
16123
- const { pushStageSecrets } = await Promise.resolve().then(() => require("./transfer-BYIn7z8m.cjs")).then((n) => n.transfer_exports);
16200
+ const { pushStageSecrets } = await Promise.resolve().then(() => transfer_exports);
16124
16201
  const { workspace } = await loadWorkspaceConfig();
16125
16202
  const { addedKeys } = await pushStageSecrets(workspace, options.stage, { profile: options.profile });
16126
16203
  for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
@@ -16135,7 +16212,7 @@ program.command("secrets:pull").description("Bring a deployed stage's secrets fr
16135
16212
  const globalOptions = program.opts();
16136
16213
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16137
16214
  const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
16138
- const { pullStageSecrets } = await Promise.resolve().then(() => require("./transfer-BYIn7z8m.cjs")).then((n) => n.transfer_exports);
16215
+ const { pullStageSecrets } = await Promise.resolve().then(() => transfer_exports);
16139
16216
  const { workspace } = await loadWorkspaceConfig();
16140
16217
  const { addedKeys } = await pullStageSecrets(workspace, options.stage, { profile: options.profile });
16141
16218
  for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
@@ -16150,8 +16227,8 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16150
16227
  const globalOptions = program.opts();
16151
16228
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16152
16229
  const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
16153
- const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-szmJw_Sb.cjs")).then((n) => n.reconcile_exports);
16154
- const { readStageSecrets, writeStageSecrets } = await Promise.resolve().then(() => require("./storage-DgypfSD6.cjs")).then((n) => n.storage_exports);
16230
+ const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-BxZTV2vl.cjs")).then((n) => n.reconcile_exports);
16231
+ const { readStageSecrets, writeStageSecrets } = await Promise.resolve().then(() => require("./storage-C2JC31V9.cjs")).then((n) => n.storage_exports);
16155
16232
  const { workspace } = await loadWorkspaceConfig();
16156
16233
  const stage = options.stage ?? workspace.stages.local;
16157
16234
  const secrets = await readStageSecrets(stage, workspace.root);
@@ -16159,7 +16236,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16159
16236
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
16160
16237
  process.exit(1);
16161
16238
  }
16162
- const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-DgeVgxmm.cjs")).then((n) => n.workspace_exports);
16239
+ const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-B3ApLW6p.cjs")).then((n) => n.workspace_exports);
16163
16240
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
16164
16241
  if (!result) {
16165
16242
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -16218,7 +16295,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
16218
16295
  try {
16219
16296
  const globalOptions = program.opts();
16220
16297
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16221
- const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-CQflOKKV.cjs"));
16298
+ const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-ZL3RcUVD.cjs"));
16222
16299
  await deployGithubCommand(options);
16223
16300
  } catch (error) {
16224
16301
  console.error(formatError(error));
@@ -16350,5 +16427,7 @@ Object.defineProperty(exports, "encryption_exports", {
16350
16427
  }
16351
16428
  });
16352
16429
  exports.getHostingerToken = getHostingerToken;
16430
+ exports.isRemoteStore = isRemoteStore;
16431
+ exports.pushStageSecrets = pushStageSecrets;
16353
16432
 
16354
16433
  //# sourceMappingURL=index.cjs.map