@geekmidas/cli 10.0.0-alpha.26 → 10.0.0-alpha.28

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-D5RcGmVM.mjs} +2 -2
  4. package/dist/{bundler-mwH7WOb1.mjs.map → bundler-D5RcGmVM.mjs.map} +1 -1
  5. package/dist/{config-Dn9DW2Fz.cjs → config-BTFLHfRb.cjs} +25 -4
  6. package/dist/{config-Dn9DW2Fz.cjs.map → config-BTFLHfRb.cjs.map} +1 -1
  7. package/dist/{config-DBGpRTUy.mjs → config-Dqxa-_Ge.mjs} +20 -5
  8. package/dist/{config-DBGpRTUy.mjs.map → config-Dqxa-_Ge.mjs.map} +1 -1
  9. package/dist/config.cjs +3 -2
  10. package/dist/config.d.cts +12 -1
  11. package/dist/config.d.cts.map +1 -1
  12. package/dist/config.d.mts +12 -1
  13. package/dist/config.d.mts.map +1 -1
  14. package/dist/config.mjs +3 -3
  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-D58lt7RR.cjs → github-BUCXEl2v.cjs} +6 -6
  21. package/dist/{github-D58lt7RR.cjs.map → github-BUCXEl2v.cjs.map} +1 -1
  22. package/dist/{github-D5SBlOx5.mjs → github-zkzu-ka2.mjs} +4 -4
  23. package/dist/{github-D5SBlOx5.mjs.map → github-zkzu-ka2.mjs.map} +1 -1
  24. package/dist/{index-RCUizGxL.d.cts → index-BPN7kghQ.d.cts} +2 -12
  25. package/dist/{index-B4H4XF7A.d.mts.map → index-BPN7kghQ.d.cts.map} +1 -1
  26. package/dist/{index-B4H4XF7A.d.mts → index-BqWKWRm0.d.mts} +2 -12
  27. package/dist/{index-RCUizGxL.d.cts.map → index-BqWKWRm0.d.mts.map} +1 -1
  28. package/dist/index.cjs +805 -790
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.mjs +754 -741
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/{openapi-DHQlstv6.mjs → openapi-D-Lk5Hr5.mjs} +3 -3
  33. package/dist/{openapi-DHQlstv6.mjs.map → openapi-D-Lk5Hr5.mjs.map} +1 -1
  34. package/dist/{openapi-BAKQLho-.cjs → openapi-K4H5tHzA.cjs} +3 -3
  35. package/dist/{openapi-BAKQLho-.cjs.map → openapi-K4H5tHzA.cjs.map} +1 -1
  36. package/dist/openapi.cjs +1 -1
  37. package/dist/openapi.mjs +1 -1
  38. package/dist/reconcile/public.cjs +1 -1
  39. package/dist/reconcile/public.d.cts +12 -0
  40. package/dist/reconcile/public.d.cts.map +1 -1
  41. package/dist/reconcile/public.d.mts +12 -0
  42. package/dist/reconcile/public.d.mts.map +1 -1
  43. package/dist/reconcile/public.mjs +1 -1
  44. package/dist/{generator-LfZU7NlU.mjs → reconcile-DhbCvh4K.mjs} +111 -2
  45. package/dist/reconcile-DhbCvh4K.mjs.map +1 -0
  46. package/dist/{generator-Wdfp0jCS.cjs → reconcile-szmJw_Sb.cjs} +130 -3
  47. package/dist/reconcile-szmJw_Sb.cjs.map +1 -0
  48. package/dist/workspace/index.cjs +1 -2
  49. package/dist/workspace/index.d.cts +2 -2
  50. package/dist/workspace/index.d.mts +2 -2
  51. package/dist/workspace/index.mjs +2 -2
  52. package/dist/{workspace-CRD9YinV.cjs → workspace-Bav3TVrP.cjs} +47 -78
  53. package/dist/workspace-Bav3TVrP.cjs.map +1 -0
  54. package/dist/{credentials-fQSAe4Nb.cjs → workspace-Cv48QwPK.cjs} +347 -516
  55. package/dist/workspace-Cv48QwPK.cjs.map +1 -0
  56. package/dist/{workspace-Bu3xNCej.mjs → workspace-T4IVGSoB.mjs} +48 -73
  57. package/dist/workspace-T4IVGSoB.mjs.map +1 -0
  58. package/dist/{credentials-DI9kJcaP.mjs → workspace-cBobQVoA.mjs} +349 -500
  59. package/dist/workspace-cBobQVoA.mjs.map +1 -0
  60. package/package.json +14 -14
  61. package/dist/credentials-DI9kJcaP.mjs.map +0 -1
  62. package/dist/credentials-fQSAe4Nb.cjs.map +0 -1
  63. package/dist/generator-LfZU7NlU.mjs.map +0 -1
  64. package/dist/generator-Wdfp0jCS.cjs.map +0 -1
  65. package/dist/reconcile-gb1lz1Dk.mjs +0 -113
  66. package/dist/reconcile-gb1lz1Dk.mjs.map +0 -1
  67. package/dist/reconcile-hx_vBvux.cjs +0 -136
  68. package/dist/reconcile-hx_vBvux.cjs.map +0 -1
  69. package/dist/transfer-CAJs_vhu.mjs +0 -122
  70. package/dist/transfer-CAJs_vhu.mjs.map +0 -1
  71. package/dist/transfer-D2qLFXsw.cjs +0 -151
  72. package/dist/transfer-D2qLFXsw.cjs.map +0 -1
  73. package/dist/workspace-Bu3xNCej.mjs.map +0 -1
  74. package/dist/workspace-CRD9YinV.cjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,15 +1,13 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
3
- import { i as DEFAULT_CACHE, r as ConstructGenerator, s as isPartitionedRoutes, t as discover } from "./discover-0fhCbnk0.mjs";
4
- import { i as loadConfig, l as derivedApps, n as getAppNameFromCwd, o as loadWorkspaceConfig, r as loadAppConfig, s as parseModuleConfig } from "./config-DBGpRTUy.mjs";
5
- import { A as stripPublicPrefix, C as InvalidStages, D as stageProblems, E as deployedProblems, T as assertDeployedStage, a as getAppBuildOrder, k as getPublicEnvPrefix, o as getAppGkmConfig, s as getDependencyEnvVars, t as allConstructGlobs, v as getDeployTargetError, w as TEST_STAGE, y as isDeployTargetSupported } from "./workspace-Bu3xNCej.mjs";
6
- import { C as providerOf, S as eventsBackendFor, _ as COMPOSE_PATH, a as loadEnvFiles, b as resourceName, c as resolveServicePorts, d as backendsOf, f as constructGlobs, h as usesConstructs, i as isPortAvailable, l as rewriteUrlsWithPorts, m as reconcileWorkspace, n as createEntryWrapper, o as loadSecretsForApp, p as derivedContainers, r as findAvailablePort, s as prepareEntryCredentials, t as createCredentialsPreload, u as startWorkspaceServices, v as LOCAL_CA_PATH, w as storageBackendFor, x as cacheBackendFor, y as applyPostgres } from "./credentials-DI9kJcaP.mjs";
7
- import { a as generateServiceCredentials, i as generateSecurePassword, n as generateConnectionUrls, o as rotateServicePassword, t as createStageSecrets } from "./generator-LfZU7NlU.mjs";
3
+ import { i as DEFAULT_CACHE, r as ConstructGenerator, s as isPartitionedRoutes, t as discover } from "./discover-CXGlQHm1.mjs";
4
+ import { a as loadConfig, c as parseModuleConfig, i as loadAppConfig, l as appKey, r as getAppNameFromCwd, s as loadWorkspaceConfig, u as derivedApps } from "./config-Dqxa-_Ge.mjs";
5
+ import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-T4IVGSoB.mjs";
8
6
  import { c as getKeyPath, i as setCustomSecret, n as readStageSecrets, o as toEmbeddableSecrets, r as secretsExist, s as writeStageSecrets, t as maskPassword } from "./storage-TP0x118M.mjs";
9
- import { a as openapiCommand, c as EndpointGenerator, l as runtimeFor, n as generateOpenApi, o as resolveOpenApiConfig, s as OpenApiTsGenerator, t as OPENAPI_OUTPUT_PATH$1 } from "./openapi-DHQlstv6.mjs";
7
+ import { _ as findAvailablePort, b as loadSecretsForApp, c as applyPostgres, d as cacheBackendFor, f as eventsBackendFor, g as createEntryWrapper, h as createCredentialsPreload, i as reconcileWorkspace, l as resourceName, m as storageBackendFor, n as constructGlobs, o as COMPOSE_PATH, p as providerOf, r as derivedContainers, s as LOCAL_CA_PATH, t as backendsOf, u as rootSite, v as isPortAvailable, x as prepareEntryCredentials, y as loadEnvFiles } from "./workspace-cBobQVoA.mjs";
8
+ import { a as openapiCommand, c as EndpointGenerator, l as runtimeFor, n as generateOpenApi, o as resolveOpenApiConfig, s as OpenApiTsGenerator } from "./openapi-D-Lk5Hr5.mjs";
10
9
  import { i as withRoutes, r as withCompute } from "./emit-Bzq7JsMP.mjs";
11
- import { i as writeDockerEnvFromSecrets, r as generateFullstackCustomSecrets } from "./reconcile-gb1lz1Dk.mjs";
12
- import { a as isRemoteStore, n as pullStageSecrets, r as pushStageSecrets, t as NoStoredSecrets } from "./transfer-CAJs_vhu.mjs";
10
+ import { a as createStageSecrets, c as generateServiceCredentials, i as writeDockerEnvFromSecrets, l as rotateServicePassword, o as generateConnectionUrls, r as generateFullstackCustomSecrets, s as generateSecurePassword, t as reconcileMissingSecrets } from "./reconcile-DhbCvh4K.mjs";
13
11
  import { createRequire } from "node:module";
14
12
  import { Command } from "commander";
15
13
  import { stdin, stdout } from "node:process";
@@ -22,24 +20,24 @@ import { execFile, execSync, spawn } from "node:child_process";
22
20
  import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, kebabCase, provideKey, provisionOrder, scopedName, serviceKey } from "@geekmidas/manifest";
23
21
  import fg from "fast-glob";
24
22
  import chokidar from "chokidar";
25
- import { parse as parse$1, parseDocument } from "yaml";
26
23
  import { createCipheriv, createHash, randomBytes } from "node:crypto";
24
+ import { fileURLToPath, pathToFileURL } from "node:url";
27
25
  import { ownerRole, readerRole, roleStatements } from "@geekmidas/db/pg/roles";
28
26
  import { Client } from "pg";
27
+ import { parse as parse$1, parseDocument } from "yaml";
29
28
  import { promisify } from "node:util";
30
29
  import { cacheTableStatements } from "@geekmidas/cache/postgres";
31
- import { fileURLToPath, pathToFileURL } from "node:url";
30
+ import prompts from "prompts";
32
31
  import { Cron } from "@geekmidas/constructs/crons";
33
32
  import { Function } from "@geekmidas/constructs/functions";
34
33
  import { Queue } from "@geekmidas/constructs/queue";
35
34
  import { Subscriber } from "@geekmidas/constructs/subscribers";
36
35
  import { Topic } from "@geekmidas/constructs/topic";
37
36
  import { lookup } from "node:dns/promises";
38
- import prompts from "prompts";
39
37
  import { EnvironmentParser } from "@geekmidas/envkit";
40
38
  import { serviceContext } from "@geekmidas/services";
41
39
  //#region package.json
42
- var version = "10.0.0-alpha.26";
40
+ var version = "10.0.0-alpha.28";
43
41
  //#endregion
44
42
  //#region src/auth/credentials.ts
45
43
  /**
@@ -406,8 +404,182 @@ function normalizeRoutes(routes) {
406
404
  return Array.isArray(routes) ? routes : [routes];
407
405
  }
408
406
  //#endregion
409
- //#region src/exec/index.ts
407
+ //#region src/trust/index.ts
408
+ /**
409
+ * Installing the local edge's certificate authority into the OS trust store.
410
+ *
411
+ * `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test
412
+ * suite, anything under `gkm exec` — because reconcile injects it. A browser
413
+ * reads none of that: it asks the operating system, so until the root is in the
414
+ * system store a local `https://` address is "not secure", which is true and
415
+ * unhelpful.
416
+ *
417
+ * A separate command rather than part of `gkm setup`, for one reason: this
418
+ * needs `sudo`, and a setup that silently escalates on every run is a worse
419
+ * trade than one that tells you what to type. Caddy's own `caddy trust` draws
420
+ * the line in the same place.
421
+ */
410
422
  const logger$12 = console;
423
+ const run = promisify(execFile);
424
+ /**
425
+ * How each platform is told about a new authority.
426
+ *
427
+ * Separate rather than shelled through one script because the failure modes
428
+ * differ and the message should name the real one: macOS refuses without an
429
+ * admin password, Debian needs the file in a specific directory before its
430
+ * updater will look at it.
431
+ */
432
+ function installation(platform, certificate) {
433
+ if (platform === "darwin") return {
434
+ describe: "the System keychain",
435
+ steps: [["sudo", [
436
+ "security",
437
+ "add-trusted-cert",
438
+ "-d",
439
+ "-r",
440
+ "trustRoot",
441
+ "-k",
442
+ "/Library/Keychains/System.keychain",
443
+ certificate
444
+ ]]]
445
+ };
446
+ if (platform === "linux") {
447
+ const debian = existsSync("/usr/local/share/ca-certificates");
448
+ return {
449
+ describe: debian ? "the system CA bundle" : "the system trust anchors",
450
+ steps: debian ? [["sudo", [
451
+ "cp",
452
+ certificate,
453
+ "/usr/local/share/ca-certificates/gkm-local-ca.crt"
454
+ ]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
455
+ "cp",
456
+ certificate,
457
+ "/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
458
+ ]], ["sudo", ["update-ca-trust"]]]
459
+ };
460
+ }
461
+ throw new UnsupportedPlatform(platform, certificate);
462
+ }
463
+ /**
464
+ * Trust the certificate authority this project's edge issues from.
465
+ *
466
+ * Idempotent in effect: installing a root that is already trusted replaces the
467
+ * same entry, so running it twice is not an error.
468
+ */
469
+ async function trustCommand(options = {}) {
470
+ const { workspace } = await loadWorkspaceConfig();
471
+ const certificate = join(workspace.root, LOCAL_CA_PATH);
472
+ if (!existsSync(certificate)) throw new NoLocalAuthority(certificate);
473
+ const { describe, steps } = installation(process.platform, certificate);
474
+ logger$12.log(`\n🔐 Trusting the local authority in ${describe}`);
475
+ logger$12.log(` ${certificate}\n`);
476
+ if (options.dryRun) {
477
+ for (const [command, args] of steps) logger$12.log(` ${command} ${args.join(" ")}`);
478
+ return;
479
+ }
480
+ logger$12.log(" This needs an admin password.\n");
481
+ for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
482
+ logger$12.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
483
+ }
484
+ /**
485
+ * Trust the authority if this machine lacks it and its owner agrees.
486
+ *
487
+ * A machine's answer, not the project's: whether a laptop installs a root
488
+ * certificate is its owner's call, so the decision is `--yes`, then what this
489
+ * machine previously answered, then a prompt. Never a silent `sudo` — that is
490
+ * the one thing a setup command should not do on somebody's behalf.
491
+ *
492
+ * Silent and free when the machine already trusts it, which is what makes it
493
+ * acceptable to call on every `gkm setup`.
494
+ */
495
+ async function ensureTrusted(root, url, options = {}) {
496
+ if (await isTrusted(url)) return;
497
+ if (!options.assumeYes) {
498
+ if (!process.stdin.isTTY) {
499
+ logger$12.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
500
+ return;
501
+ }
502
+ if (await declined(root)) return;
503
+ const { install } = await prompts({
504
+ type: "confirm",
505
+ name: "install",
506
+ message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
507
+ initial: false
508
+ });
509
+ if (!install) {
510
+ await recordDeclined(root);
511
+ logger$12.log(" Skipped. Run \"gkm trust\" later.");
512
+ return;
513
+ }
514
+ }
515
+ await trustCommand();
516
+ }
517
+ /** Where a declined answer is remembered, so setup asks once and not again. */
518
+ const TRUST_STATE = ".gkm/trust.json";
519
+ async function declined(root) {
520
+ try {
521
+ const raw = await readFile(join(root, TRUST_STATE), "utf-8");
522
+ return JSON.parse(raw).declined === true;
523
+ } catch {
524
+ return false;
525
+ }
526
+ }
527
+ async function recordDeclined(root) {
528
+ const path = join(root, TRUST_STATE);
529
+ await mkdir(dirname(path), { recursive: true });
530
+ await writeFile(path, `${JSON.stringify({ declined: true }, null, 2)}\n`);
531
+ }
532
+ /**
533
+ * Whether this machine already trusts the local authority.
534
+ *
535
+ * Asked by connecting rather than by reading a keychain: what matters is
536
+ * whether a client with no special configuration verifies the edge, and that is
537
+ * one platform-neutral question instead of three platform-specific ones. It is
538
+ * also the exact thing a browser is about to do.
539
+ *
540
+ * A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and
541
+ * this process almost certainly has it set — asking in-process would answer
542
+ * "yes" for everyone.
543
+ */
544
+ async function isTrusted(url) {
545
+ const { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;
546
+ try {
547
+ await run(process.execPath, [
548
+ "--use-system-ca",
549
+ "-e",
550
+ `fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`
551
+ ], {
552
+ env,
553
+ timeout: 15e3
554
+ });
555
+ return true;
556
+ } catch {
557
+ return false;
558
+ }
559
+ }
560
+ /** The edge has not run, so there is no authority to trust yet. */
561
+ var NoLocalAuthority = class extends Error {
562
+ path;
563
+ constructor(path) {
564
+ 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.`);
565
+ this.path = path;
566
+ this.name = "NoLocalAuthority";
567
+ }
568
+ };
569
+ /** A platform with no trust store this knows how to write to. */
570
+ var UnsupportedPlatform = class extends Error {
571
+ platform;
572
+ certificate;
573
+ constructor(platform, certificate) {
574
+ 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.`);
575
+ this.platform = platform;
576
+ this.certificate = certificate;
577
+ this.name = "UnsupportedPlatform";
578
+ }
579
+ };
580
+ //#endregion
581
+ //#region src/exec/index.ts
582
+ const logger$11 = console;
411
583
  /** `gkm exec` was given nothing to run. */
412
584
  var NoCommandSpecified = class extends Error {
413
585
  constructor() {
@@ -430,20 +602,17 @@ async function execCommand(commandArgs, options = {}) {
430
602
  const cwd = options.cwd ?? process.cwd();
431
603
  if (!commandArgs[0]) throw new NoCommandSpecified();
432
604
  const defaultEnv = loadEnvFiles(".env");
433
- if (defaultEnv.loaded.length > 0) logger$12.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
434
- const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({
435
- cwd,
436
- resolveDockerPorts: "readonly"
437
- });
438
- if (appName) logger$12.log(`📦 App: ${appName}`);
605
+ if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
606
+ const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
607
+ if (appName) logger$11.log(`📦 App: ${appName}`);
439
608
  const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
440
- if (secretCount > 0) logger$12.log(`🔐 Loaded ${secretCount} secret(s)`);
609
+ if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
441
610
  const preloadDir = join(cwd, ".gkm");
442
611
  await mkdir(preloadDir, { recursive: true });
443
612
  const preloadPath = join(preloadDir, "credentials-preload.mjs");
444
613
  await createCredentialsPreload(preloadPath, secretsJsonPath);
445
614
  const [cmd, ...args] = commandArgs;
446
- logger$12.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
615
+ logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
447
616
  const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
448
617
  const child = spawn(cmd, args, {
449
618
  cwd,
@@ -457,7 +626,7 @@ async function execCommand(commandArgs, options = {}) {
457
626
  const exitCode = await new Promise((resolve) => {
458
627
  child.on("close", (code) => resolve(code ?? 0));
459
628
  child.on("error", (error) => {
460
- logger$12.error(`Failed to run command: ${error.message}`);
629
+ logger$11.error(`Failed to run command: ${error.message}`);
461
630
  resolve(1);
462
631
  });
463
632
  });
@@ -465,7 +634,7 @@ async function execCommand(commandArgs, options = {}) {
465
634
  }
466
635
  //#endregion
467
636
  //#region src/dev/index.ts
468
- const logger$11 = console;
637
+ const logger$10 = console;
469
638
  /**
470
639
  * Normalize telescope configuration
471
640
  * @internal Exported for testing
@@ -559,7 +728,7 @@ function getProductionConfigFromGkm(config) {
559
728
  async function devCommand(options) {
560
729
  if (options.entry) return entryDevCommand(options);
561
730
  const defaultEnv = loadEnvFiles(".env");
562
- if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
731
+ if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
563
732
  const appName = getAppNameFromCwd();
564
733
  let config;
565
734
  let appRoot = process.cwd();
@@ -575,9 +744,8 @@ async function devCommand(options) {
575
744
  workspaceAppName = appConfig.appName;
576
745
  workspaceAppPort = appConfig.app.port;
577
746
  workspace = appConfig.workspace;
578
- logger$11.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
579
747
  if (appConfig.app.entry) {
580
- logger$11.log(`📄 Using entry point: ${appConfig.app.entry}`);
748
+ logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
581
749
  return entryDevCommand({
582
750
  ...options,
583
751
  entry: appConfig.app.entry,
@@ -587,38 +755,27 @@ async function devCommand(options) {
587
755
  }
588
756
  } catch {
589
757
  const loadedConfig = await loadWorkspaceConfig();
590
- if (loadedConfig.type === "workspace") {
591
- logger$11.log("📦 Detected workspace configuration");
592
- return workspaceDevCommand(loadedConfig.workspace, options);
593
- }
758
+ if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
594
759
  config = loadedConfig.raw;
760
+ workspace = loadedConfig.workspace;
595
761
  }
596
762
  else {
597
763
  const loadedConfig = await loadWorkspaceConfig();
598
- if (loadedConfig.type === "workspace") {
599
- logger$11.log("📦 Detected workspace configuration");
600
- return workspaceDevCommand(loadedConfig.workspace, options);
601
- }
764
+ if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
602
765
  config = loadedConfig.raw;
603
766
  workspace = loadedConfig.workspace;
604
767
  }
605
768
  if (config.env) {
606
769
  const { loaded, missing } = loadEnvFiles(config.env, appRoot);
607
- if (loaded.length > 0) logger$11.log(`📦 Loaded env: ${loaded.join(", ")}`);
608
- if (missing.length > 0) logger$11.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
770
+ if (loaded.length > 0) logger$10.log(`📦 Loaded env: ${loaded.join(", ")}`);
771
+ if (missing.length > 0) logger$10.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
609
772
  }
610
773
  const resolved = resolveProviders(config, { provider: "server" });
611
- logger$11.log("🚀 Starting development server...");
612
- logger$11.log(`Loading constructs from: ${config.constructs}`);
613
774
  const telescope = normalizeTelescopeConfig(config.telescope);
614
- if (telescope) logger$11.log(`🔭 Telescope enabled at ${telescope.path}`);
615
775
  const studio = normalizeStudioConfig(config.studio);
616
- if (studio) logger$11.log(`🗄️ Studio enabled at ${studio.path}`);
617
776
  const hooks = normalizeHooksConfig(config.hooks, appRoot);
618
- if (hooks) logger$11.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
619
777
  const enableOpenApi = resolveOpenApiConfig(config).enabled || resolved.enableOpenApi;
620
- if (enableOpenApi) logger$11.log(`📄 OpenAPI output: ${OPENAPI_OUTPUT_PATH$1}`);
621
- const build = (bustCache = false) => buildApp({
778
+ const build = (bustCache = false) => quietly(() => buildApp({
622
779
  config,
623
780
  workspaceRoot: workspace?.root ?? secretsRoot,
624
781
  appRoot,
@@ -631,29 +788,21 @@ async function devCommand(options) {
631
788
  skipBundle: true,
632
789
  bustCache,
633
790
  serveEmpty: true
634
- });
791
+ }));
635
792
  const initial = await build();
636
793
  const runtime = config.runtime ?? "node";
637
794
  let secretsJsonPath;
795
+ let publicUrl;
638
796
  const appSecrets = await loadSecretsForApp(secretsRoot, config.stages.local, workspaceAppName);
639
- if (workspace && usesConstructs(workspace)) {
797
+ if (workspace) {
640
798
  const reconciled = await reconcileWorkspace(workspace, { stage: workspace.stages.local });
641
799
  if (reconciled.changed && reconciled.plan.containers.length > 0) {
642
- logger$11.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
643
- for (const [container, address] of Object.entries(reconciled.addresses)) logger$11.log(` ${container}: ${address}`);
800
+ logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
801
+ for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
644
802
  }
645
803
  Object.assign(appSecrets, reconciled.env);
646
- } else {
647
- const resolvedPorts = await resolveServicePorts(secretsRoot);
648
- if (Object.keys(resolvedPorts.ports).length > 0) {
649
- const rewritten = rewriteUrlsWithPorts(appSecrets, resolvedPorts);
650
- Object.assign(appSecrets, rewritten);
651
- logger$11.log(`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
652
- }
653
- }
654
- if (workspace && workspaceAppName) {
655
- const depEnv = getDependencyEnvVars(workspace, workspaceAppName);
656
- for (const [key, value] of Object.entries(depEnv)) appSecrets[key] ??= value;
804
+ const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && appKey(r.id) === workspaceAppName);
805
+ publicUrl = own ? reconciled.env[own.envKey] : void 0;
657
806
  }
658
807
  if (Object.keys(appSecrets).length > 0) {
659
808
  const secretsDir = join(secretsRoot, ".gkm");
@@ -661,13 +810,13 @@ async function devCommand(options) {
661
810
  const secretsFileName = workspaceAppName ? `dev-secrets-${workspaceAppName}.json` : "dev-secrets.json";
662
811
  secretsJsonPath = join(secretsDir, secretsFileName);
663
812
  await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
664
- logger$11.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
665
813
  }
666
814
  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);
815
+ devServer.label = workspaceAppName ?? "server";
816
+ devServer.publicUrl = publicUrl;
667
817
  await devServer.start();
668
818
  const hooksFile = (config.hooks?.server?.split("#"))?.[0];
669
819
  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);
670
- logger$11.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
671
820
  const resolvedFiles = await fg(normalizedPatterns, {
672
821
  cwd: appRoot,
673
822
  absolute: false,
@@ -676,34 +825,28 @@ async function devCommand(options) {
676
825
  const dirsToWatch = [...new Set(resolvedFiles.map((f) => {
677
826
  return f.split("/").slice(0, -1).join("/");
678
827
  }))];
679
- logger$11.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
680
828
  const watcher = chokidar.watch([...resolvedFiles, ...dirsToWatch], {
681
829
  ignored: /(^|[/\\])\../,
682
830
  persistent: true,
683
831
  ignoreInitial: true,
684
832
  cwd: appRoot
685
833
  });
686
- watcher.on("ready", () => {
687
- logger$11.log("🔍 File watcher ready");
688
- });
689
834
  watcher.on("error", (error) => {
690
- logger$11.error("❌ Watcher error:", error);
835
+ logger$10.error("❌ Watcher error:", error);
691
836
  });
692
837
  let rebuildTimeout = null;
693
838
  watcher.on("change", async (path) => {
694
- logger$11.log(`📝 File changed: ${path}`);
839
+ logger$10.log(`🔄 ${path} changed — rebuilding`);
695
840
  if (rebuildTimeout) clearTimeout(rebuildTimeout);
696
841
  rebuildTimeout = setTimeout(async () => {
697
842
  try {
698
- logger$11.log("🔄 Rebuilding...");
699
843
  const rebuilt = await build(true);
700
- logger$11.log("✅ Rebuild complete, restarting server...");
701
844
  devServer.selfServing = rebuilt.selfServing !== void 0;
702
845
  await devServer.restart();
703
846
  } catch (error) {
704
847
  const err = error;
705
- logger$11.error(`❌ Rebuild failed: ${err.message}`);
706
- if (err.stack) logger$11.error(err.stack);
848
+ logger$10.error(`❌ Rebuild failed: ${err.message}`);
849
+ if (err.stack) logger$10.error(err.stack);
707
850
  }
708
851
  }, 300);
709
852
  });
@@ -711,9 +854,9 @@ async function devCommand(options) {
711
854
  const shutdown = () => {
712
855
  if (isShuttingDown) return;
713
856
  isShuttingDown = true;
714
- logger$11.log("\n🛑 Shutting down...");
857
+ logger$10.log("\n🛑 Shutting down...");
715
858
  Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
716
- logger$11.error("Error during shutdown:", err);
859
+ logger$10.error("Error during shutdown:", err);
717
860
  }).finally(() => {
718
861
  process.exit(0);
719
862
  });
@@ -722,19 +865,6 @@ async function devCommand(options) {
722
865
  process.on("SIGTERM", shutdown);
723
866
  }
724
867
  /**
725
- * Generate all dependency environment variables for all apps.
726
- * Returns a flat object with all {APP_NAME}_URL variables.
727
- * @internal Exported for testing
728
- */
729
- function generateAllDependencyEnvVars(workspace, urlPrefix = "http://localhost") {
730
- const env = {};
731
- for (const appName of Object.keys(workspace.apps)) {
732
- const appEnv = getDependencyEnvVars(workspace, appName, urlPrefix);
733
- Object.assign(env, appEnv);
734
- }
735
- return env;
736
- }
737
- /**
738
868
  * Check for port conflicts across all apps.
739
869
  * Returns list of conflicts if any ports are duplicated.
740
870
  * @internal Exported for testing
@@ -912,12 +1042,8 @@ async function loadDevSecrets(workspace) {
912
1042
  const stage = workspace.stages.local;
913
1043
  if (secretsExist(stage, workspace.root)) {
914
1044
  const secrets = await readStageSecrets(stage, workspace.root);
915
- if (secrets) {
916
- logger$11.log(`🔐 Loading secrets from stage: ${stage}`);
917
- return toEmbeddableSecrets(secrets);
918
- }
1045
+ if (secrets) return toEmbeddableSecrets(secrets);
919
1046
  }
920
- logger$11.warn(`⚠️ Secrets enabled but no "${stage}" secrets found. Run "gkm setup" to initialize the local stage`);
921
1047
  return {};
922
1048
  }
923
1049
  /**
@@ -931,66 +1057,43 @@ async function loadDevSecrets(workspace) {
931
1057
  */
932
1058
  async function workspaceDevCommand(workspace, options) {
933
1059
  const appCount = Object.keys(workspace.apps).length;
934
- const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
935
1060
  const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
936
- const mobileApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "mobile");
937
- logger$11.log(`\n🚀 Starting workspace: ${workspace.name}`);
938
- const counts = [`${backendApps.length} backend`, `${frontendApps.length} web`];
939
- if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
940
- logger$11.log(` ${counts.join(", ")} app(s)`);
941
1061
  const conflicts = checkPortConflicts(workspace);
942
1062
  if (conflicts.length > 0) {
943
- for (const conflict of conflicts) logger$11.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1063
+ for (const conflict of conflicts) logger$10.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
944
1064
  throw new Error("Port conflicts detected. Please assign unique ports to each app.");
945
1065
  }
946
1066
  if (frontendApps.length > 0) {
947
- logger$11.log("\n🔍 Validating frontend apps...");
948
1067
  const validationResults = await validateFrontendApps(workspace);
949
1068
  let hasErrors = false;
950
1069
  for (const result of validationResults) {
951
1070
  if (!result.valid) {
952
1071
  hasErrors = true;
953
- logger$11.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
954
- for (const error of result.errors) logger$11.error(` • ${error}`);
1072
+ logger$10.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1073
+ for (const error of result.errors) logger$10.error(` • ${error}`);
955
1074
  }
956
- for (const warning of result.warnings) logger$11.warn(` ⚠️ ${result.appName}: ${warning}`);
1075
+ for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
957
1076
  }
958
1077
  if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
959
- logger$11.log("✅ Frontend apps validated");
960
1078
  }
961
1079
  const rawSecrets = await loadDevSecrets(workspace);
962
- let secretsEnv;
1080
+ const reconciled = await reconcileWorkspace(workspace, { stage: workspace.stages.local });
1081
+ if (reconciled.changed && reconciled.plan.containers.length > 0) {
1082
+ logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
1083
+ for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
1084
+ }
1085
+ const secretsEnv = {
1086
+ ...rawSecrets,
1087
+ ...reconciled.env
1088
+ };
963
1089
  const appUrls = {};
964
- if (usesConstructs(workspace)) {
965
- const reconciled = await reconcileWorkspace(workspace, { stage: workspace.stages.local });
966
- if (reconciled.changed && reconciled.plan.containers.length > 0) {
967
- logger$11.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
968
- for (const [container, address] of Object.entries(reconciled.addresses)) logger$11.log(` ${container}: ${address}`);
969
- }
970
- secretsEnv = {
971
- ...rawSecrets,
972
- ...reconciled.env
973
- };
974
- for (const resource of reconciled.plan.resources) {
975
- if (resource.kind !== "rest-api" && resource.kind !== "site") continue;
976
- const address = reconciled.env[resource.envKey];
977
- if (address) appUrls[resource.id] = address;
978
- }
979
- } else {
980
- const resolvedPorts = await resolveServicePorts(workspace.root);
981
- await startWorkspaceServices(workspace, resolvedPorts.dockerEnv, rawSecrets);
982
- secretsEnv = rewriteUrlsWithPorts(rawSecrets, resolvedPorts);
983
- }
984
- if (Object.keys(secretsEnv).length > 0) logger$11.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
985
- if (Object.keys(appUrls).length > 0) {
986
- logger$11.log("🔒 App URLs:");
987
- for (const [id, address] of Object.entries(appUrls)) logger$11.log(` ${id}: ${address}`);
988
- }
989
- const dependencyEnv = Object.fromEntries(Object.entries(generateAllDependencyEnvVars(workspace)).filter(([key]) => secretsEnv[key] === void 0));
990
- if (Object.keys(dependencyEnv).length > 0) {
991
- logger$11.log("📡 Dependency URLs:");
992
- for (const [key, value] of Object.entries(dependencyEnv)) logger$11.log(` ${key}=${value}`);
1090
+ for (const resource of reconciled.plan.resources) {
1091
+ if (resource.kind !== "rest-api" && resource.kind !== "site") continue;
1092
+ const address = reconciled.env[resource.envKey];
1093
+ if (address) appUrls[resource.id] = address;
993
1094
  }
1095
+ const firstUrl = Object.values(appUrls).find((url) => url.startsWith("https://"));
1096
+ if (firstUrl) await ensureTrusted(workspace.root, firstUrl);
994
1097
  let turboFilter = [];
995
1098
  if (options.app) {
996
1099
  if (!workspace.apps[options.app]) {
@@ -998,25 +1101,23 @@ async function workspaceDevCommand(workspace, options) {
998
1101
  throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
999
1102
  }
1000
1103
  turboFilter = ["--filter", appPackageName(workspace, options.app) ?? options.app];
1001
- logger$11.log(`\n🎯 Running single app: ${options.app}`);
1002
- } else if (options.filter) {
1003
- turboFilter = ["--filter", options.filter];
1004
- logger$11.log(`\n🔍 Using filter: ${options.filter}`);
1005
- } else {
1104
+ } else if (options.filter) turboFilter = ["--filter", options.filter];
1105
+ else {
1006
1106
  const { filters, unpackaged } = turboFilters(workspace);
1007
1107
  turboFilter = filters.flatMap((name) => ["--filter", name]);
1008
- if (unpackaged.length > 0) logger$11.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
1009
- logger$11.log(`\n🎯 Running all ${appCount} apps`);
1108
+ if (unpackaged.length > 0) logger$10.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
1010
1109
  }
1011
1110
  const buildOrder = getAppBuildOrder(workspace);
1012
- logger$11.log("\n📋 Apps (in dependency order):");
1111
+ const width = Math.max(...buildOrder.map((name) => name.length));
1112
+ logger$10.log(`\n${workspace.name}: ${appCount} app(s)`);
1013
1113
  for (const appName of buildOrder) {
1014
1114
  const app = workspace.apps[appName];
1015
1115
  if (!app) continue;
1016
- const deps = app.dependencies.length > 0 ? ` (depends on: ${app.dependencies.join(", ")})` : "";
1017
- const icon = app.type === "backend" ? "🔧" : app.type === "mobile" ? "📱" : "🌐";
1018
- logger$11.log(` ${icon} ${appName} → http://localhost:${app.port}${deps}`);
1116
+ const local = `http://localhost:${app.port}`;
1117
+ const url = Object.entries(appUrls).find(([id]) => appKey(id) === appName)?.[1];
1118
+ logger$10.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
1019
1119
  }
1120
+ logger$10.log("");
1020
1121
  const configFiles = [
1021
1122
  "gkm.config.ts",
1022
1123
  "gkm.config.js",
@@ -1033,7 +1134,6 @@ async function workspaceDevCommand(workspace, options) {
1033
1134
  const turboEnv = {
1034
1135
  ...process.env,
1035
1136
  ...secretsEnv,
1036
- ...dependencyEnv,
1037
1137
  NODE_ENV: "development",
1038
1138
  ...configPath ? { GKM_CONFIG_PATH: configPath } : {}
1039
1139
  };
@@ -1048,7 +1148,6 @@ async function workspaceDevCommand(workspace, options) {
1048
1148
  });
1049
1149
  }
1050
1150
  if (held.length > 0) throw new WorkspacePortsInUse(held);
1051
- logger$11.log("\n🏃 Starting turbo run dev...\n");
1052
1151
  const turboProcess = spawn("pnpm", [
1053
1152
  "turbo",
1054
1153
  "run",
@@ -1064,7 +1163,7 @@ async function workspaceDevCommand(workspace, options) {
1064
1163
  const shutdown = () => {
1065
1164
  if (isShuttingDown) return;
1066
1165
  isShuttingDown = true;
1067
- logger$11.log("\n🛑 Shutting down workspace...");
1166
+ logger$10.log("\n🛑 Shutting down workspace...");
1068
1167
  const pid = turboProcess.pid;
1069
1168
  if (pid) try {
1070
1169
  process.kill(-pid, "SIGTERM");
@@ -1088,7 +1187,7 @@ async function workspaceDevCommand(workspace, options) {
1088
1187
  process.on("SIGTERM", shutdown);
1089
1188
  return new Promise((resolve, reject) => {
1090
1189
  turboProcess.on("error", (error) => {
1091
- logger$11.error("❌ Turbo error:", error);
1190
+ logger$10.error("❌ Turbo error:", error);
1092
1191
  reject(error);
1093
1192
  });
1094
1193
  turboProcess.on("exit", (code) => {
@@ -1107,11 +1206,11 @@ async function entryDevCommand(options) {
1107
1206
  const entryPath = resolve(process.cwd(), entry);
1108
1207
  if (!existsSync(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
1109
1208
  const defaultEnv = loadEnvFiles(".env");
1110
- if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1209
+ if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1111
1210
  const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
1112
- if (appName) logger$11.log(`📦 App: ${appName} (port ${resolvedPort})`);
1113
- logger$11.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1114
- if (Object.keys(credentials).length > 1) logger$11.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
1211
+ if (appName) logger$10.log(`📦 App: ${appName} (port ${resolvedPort})`);
1212
+ logger$10.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1213
+ if (Object.keys(credentials).length > 1) logger$10.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
1115
1214
  const wrapperDir = join(process.cwd(), ".gkm");
1116
1215
  await mkdir(wrapperDir, { recursive: true });
1117
1216
  const wrapperPath = join(wrapperDir, "entry-wrapper.ts");
@@ -1122,7 +1221,7 @@ async function entryDevCommand(options) {
1122
1221
  const shutdown = () => {
1123
1222
  if (isShuttingDown) return;
1124
1223
  isShuttingDown = true;
1125
- logger$11.log("\n🛑 Shutting down...");
1224
+ logger$10.log("\n🛑 Shutting down...");
1126
1225
  runner.stop();
1127
1226
  process.exit(0);
1128
1227
  };
@@ -1158,14 +1257,14 @@ var EntryRunner = class {
1158
1257
  });
1159
1258
  let restartTimeout = null;
1160
1259
  this.watcher.on("change", (path) => {
1161
- logger$11.log(`📝 File changed: ${path}`);
1260
+ logger$10.log(`📝 File changed: ${path}`);
1162
1261
  if (restartTimeout) clearTimeout(restartTimeout);
1163
1262
  restartTimeout = setTimeout(async () => {
1164
- logger$11.log("🔄 Restarting...");
1263
+ logger$10.log("🔄 Restarting...");
1165
1264
  await this.restart();
1166
1265
  }, 300);
1167
1266
  });
1168
- logger$11.log(`👀 Watching for changes in: ${watchDir}`);
1267
+ logger$10.log(`👀 Watching for changes in: ${watchDir}`);
1169
1268
  }
1170
1269
  }
1171
1270
  async runProcess() {
@@ -1173,24 +1272,24 @@ var EntryRunner = class {
1173
1272
  ...process.env,
1174
1273
  PORT: String(this.port)
1175
1274
  };
1176
- this.childProcess = spawn("npx", ["tsx", this.wrapperPath], {
1275
+ this.childProcess = spawn(...tsxCommand(dirname(this.wrapperPath), [this.wrapperPath]), {
1177
1276
  stdio: "inherit",
1178
1277
  env,
1179
1278
  detached: true
1180
1279
  });
1181
1280
  this.isRunning = true;
1182
1281
  this.childProcess.on("error", (error) => {
1183
- logger$11.error("❌ Process error:", error);
1282
+ logger$10.error("❌ Process error:", error);
1184
1283
  });
1185
1284
  this.childProcess.on("exit", (code) => {
1186
- if (code !== null && code !== 0 && code !== 143) logger$11.error(`❌ Process exited with code ${code}`);
1285
+ if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
1187
1286
  this.isRunning = false;
1188
1287
  });
1189
1288
  await new Promise((resolve) => setTimeout(resolve, 500));
1190
1289
  if (this.isRunning) {
1191
- logger$11.log("");
1192
- logger$11.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
1193
- logger$11.log("");
1290
+ logger$10.log("");
1291
+ logger$10.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
1292
+ logger$10.log("");
1194
1293
  }
1195
1294
  }
1196
1295
  async restart() {
@@ -1276,7 +1375,6 @@ ${credentialsInjection}${parentWatch}const port = process.argv.includes('--port'
1276
1375
  const { app } = await import('${appImportPath}');
1277
1376
 
1278
1377
  ${serveCode.replace(/^ {4}/gm, "")}
1279
- console.log(\`Server started on port \${port}\`);
1280
1378
  `;
1281
1379
  return `#!/usr/bin/env node
1282
1380
  /**
@@ -1305,6 +1403,31 @@ start({
1305
1403
  });
1306
1404
  `;
1307
1405
  }
1406
+ /** Run `fn` with `console.log` muted — warnings and errors still print. */
1407
+ async function quietly(fn) {
1408
+ const log = console.log;
1409
+ console.log = () => {};
1410
+ try {
1411
+ return await fn();
1412
+ } finally {
1413
+ console.log = log;
1414
+ }
1415
+ }
1416
+ /**
1417
+ * How to run a TypeScript entry with the app's own tsx.
1418
+ *
1419
+ * Straight through node rather than `npx tsx`: npx reads the developer's npm
1420
+ * config on every start and warned about each key it did not recognise — nine
1421
+ * lines per app, per restart, about nothing gkm does.
1422
+ */
1423
+ function tsxCommand(appRoot, args) {
1424
+ try {
1425
+ const cli = createRequire(join(appRoot, "package.json")).resolve("tsx/cli");
1426
+ return [process.execPath, [cli, ...args]];
1427
+ } catch {
1428
+ return ["npx", ["tsx", ...args]];
1429
+ }
1430
+ }
1308
1431
  /** A port this app must serve on is held by something else. */
1309
1432
  var DevPortInUse = class extends Error {
1310
1433
  port;
@@ -1371,6 +1494,13 @@ var DevServer = class {
1371
1494
  this.selfServing = selfServing;
1372
1495
  this.actualPort = requestedPort;
1373
1496
  }
1497
+ /** What the ready line calls this app. */
1498
+ label = "server";
1499
+ /**
1500
+ * Where the app is reached — its HTTPS address behind the edge, when it has
1501
+ * one. The local port is only what the edge forwards to.
1502
+ */
1503
+ publicUrl;
1374
1504
  async start() {
1375
1505
  this.startTime = Date.now();
1376
1506
  if (this.isRunning) await this.stop();
@@ -1379,17 +1509,15 @@ var DevServer = class {
1379
1509
  this.actualPort = this.requestedPort;
1380
1510
  } else {
1381
1511
  this.actualPort = await findAvailablePort(this.requestedPort);
1382
- if (this.actualPort !== this.requestedPort) logger$11.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
1512
+ if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
1383
1513
  }
1384
1514
  const serverEntryPath = join(this.appRoot, ".gkm", this.provider, "server.ts");
1385
1515
  await this.createServerEntry();
1386
- logger$11.log(`\n⏳ Starting server...`);
1387
- this.serverProcess = spawn("npx", [
1388
- "tsx",
1516
+ this.serverProcess = spawn(...tsxCommand(this.appRoot, [
1389
1517
  serverEntryPath,
1390
1518
  "--port",
1391
1519
  this.actualPort.toString()
1392
- ], {
1520
+ ]), {
1393
1521
  stdio: "inherit",
1394
1522
  env: {
1395
1523
  ...process.env,
@@ -1400,29 +1528,24 @@ var DevServer = class {
1400
1528
  });
1401
1529
  this.isRunning = true;
1402
1530
  this.serverProcess.on("error", (error) => {
1403
- logger$11.error("❌ Server error:", error);
1531
+ logger$10.error("❌ Server error:", error);
1404
1532
  });
1405
1533
  this.serverProcess.on("exit", (code, signal) => {
1406
- if (code !== null && code !== 0 && signal !== "SIGTERM") logger$11.error(`❌ Server exited with code ${code}`);
1534
+ if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
1407
1535
  this.isRunning = false;
1408
1536
  });
1409
1537
  await new Promise((resolve) => setTimeout(resolve, 1e3));
1410
1538
  if (this.isRunning) {
1411
- const base = `http://localhost:${this.actualPort}`;
1412
- const lines = [` Local: ${base}`];
1413
- if (this.enableOpenApi) lines.push(` API Docs: ${base}/__docs`);
1414
- if (this.telescope) lines.push(` Telescope: ${base}${this.telescope.path}`);
1415
- if (this.studio) lines.push(` Studio: ${base}${this.studio.path}`);
1416
- const maxLen = Math.max(...lines.map((l) => l.length));
1417
- const pad = (s) => s.padEnd(maxLen);
1418
- const border = "─".repeat(maxLen + 2);
1419
- logger$11.log("");
1420
- logger$11.log(` \x1b[32m✓ Ready\x1b[0m in ${((Date.now() - this.startTime) / 1e3).toFixed(1)}s`);
1421
- logger$11.log("");
1422
- logger$11.log(` ┌${border}┐`);
1423
- for (const line of lines) logger$11.log(` │ ${pad(line)} │`);
1424
- logger$11.log(` └${border}┘`);
1425
- logger$11.log("");
1539
+ const local = `http://localhost:${this.actualPort}`;
1540
+ const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
1541
+ const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
1542
+ logger$10.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
1543
+ const tools = this.selfServing ? [] : [
1544
+ ...this.enableOpenApi ? ["docs /__docs"] : [],
1545
+ ...this.telescope ? [`telescope ${this.telescope.path}`] : [],
1546
+ ...this.studio ? [`studio ${this.studio.path}`] : []
1547
+ ];
1548
+ if (tools.length > 0) logger$10.log(` ${tools.join(" · ")}`);
1426
1549
  }
1427
1550
  }
1428
1551
  async stop() {
@@ -2237,7 +2360,7 @@ var TopicGenerator = class extends ConstructGenerator {
2237
2360
  };
2238
2361
  //#endregion
2239
2362
  //#region src/build/manifests.ts
2240
- const logger$10 = console;
2363
+ const logger$9 = console;
2241
2364
  function isPartitioned(field) {
2242
2365
  return !Array.isArray(field);
2243
2366
  }
@@ -2334,8 +2457,8 @@ export type RoutePath = Route['path'];
2334
2457
  `;
2335
2458
  const manifestPath = join(manifestDir, "aws.ts");
2336
2459
  await writeFile(manifestPath, content);
2337
- 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`);
2338
- logger$10.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2460
+ 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`);
2461
+ logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2339
2462
  }
2340
2463
  async function generateServerManifest(outputDir, appInfo, routes, subscribers, queues = [], topics = [], constructs = {}, backends = {}) {
2341
2464
  const manifestDir = join(outputDir, "manifest");
@@ -2390,8 +2513,8 @@ export type RoutePath = Route['path'];
2390
2513
  `;
2391
2514
  const manifestPath = join(manifestDir, "server.ts");
2392
2515
  await writeFile(manifestPath, content);
2393
- logger$10.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
2394
- logger$10.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2516
+ logger$9.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
2517
+ logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2395
2518
  }
2396
2519
  /**
2397
2520
  * Filter out 'ALL' method routes from a manifest field (flat or partitioned).
@@ -2596,7 +2719,7 @@ export default app;
2596
2719
  }
2597
2720
  //#endregion
2598
2721
  //#region src/build/index.ts
2599
- const logger$9 = console;
2722
+ const logger$8 = console;
2600
2723
  /**
2601
2724
  * What a build run at the workspace root reads.
2602
2725
  *
@@ -2621,7 +2744,7 @@ async function buildCommand(options) {
2621
2744
  const isAtWorkspaceRoot = cwd === workspaceRoot;
2622
2745
  const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => resolve(workspaceRoot, app.path) !== workspaceRoot);
2623
2746
  if (isAtWorkspaceRoot && someAppIsElsewhere) {
2624
- logger$9.log("📦 Detected workspace configuration");
2747
+ logger$8.log("📦 Detected workspace configuration");
2625
2748
  return workspaceBuildCommand(loadedConfig.workspace, options);
2626
2749
  }
2627
2750
  }
@@ -2630,15 +2753,15 @@ async function buildCommand(options) {
2630
2753
  const cacheBackend = cacheBackendFor(providerOf(loadedConfig.workspace));
2631
2754
  const productionConfigFromGkm = getProductionConfigFromGkm(config);
2632
2755
  const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
2633
- if (production) logger$9.log(`🏭 Building for PRODUCTION`);
2634
- logger$9.log(`Building with providers: ${resolved.providers.join(", ")}`);
2635
- logger$9.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
2756
+ if (production) logger$8.log(`🏭 Building for PRODUCTION`);
2757
+ logger$8.log(`Building with providers: ${resolved.providers.join(", ")}`);
2758
+ logger$8.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
2636
2759
  const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
2637
- if (telescope) logger$9.log(`🔭 Telescope enabled at ${telescope.path}`);
2760
+ if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
2638
2761
  const studio = production ? void 0 : normalizeStudioConfig(config.studio);
2639
- if (studio) logger$9.log(`🗄️ Studio enabled at ${studio.path}`);
2762
+ if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
2640
2763
  const hooks = normalizeHooksConfig(config.hooks);
2641
- if (hooks) logger$9.log(`🪝 Server hooks enabled`);
2764
+ if (hooks) logger$8.log(`🪝 Server hooks enabled`);
2642
2765
  return buildApp({
2643
2766
  config,
2644
2767
  workspaceRoot: loadedConfig.workspace.root,
@@ -2681,7 +2804,7 @@ async function buildApp(input) {
2681
2804
  });
2682
2805
  if (selfServing) {
2683
2806
  await writeSurfaceEntry(join(appRoot, ".gkm", "server"), selfServing.source);
2684
- logger$9.log(`Generated a server for ${selfServing.id} from its own declaration`);
2807
+ logger$8.log(`Generated a server for ${selfServing.id} from its own declaration`);
2685
2808
  return { selfServing: selfServing.id };
2686
2809
  }
2687
2810
  const derived = ownersContext({
@@ -2721,14 +2844,14 @@ async function buildApp(input) {
2721
2844
  topicGenerator.load(code, appRoot, bustCache)
2722
2845
  ]);
2723
2846
  const allEndpoints = servedBy(loadedEndpoints, derived.surface);
2724
- logger$9.log(`Found ${allEndpoints.length} endpoints`);
2725
- logger$9.log(`Found ${allFunctions.length} functions`);
2726
- logger$9.log(`Found ${allCrons.length} crons`);
2727
- logger$9.log(`Found ${allSubscribers.length} subscribers`);
2728
- logger$9.log(`Found ${allQueues.length} queues`);
2729
- logger$9.log(`Found ${allTopics.length} topics`);
2847
+ logger$8.log(`Found ${allEndpoints.length} endpoints`);
2848
+ logger$8.log(`Found ${allFunctions.length} functions`);
2849
+ logger$8.log(`Found ${allCrons.length} crons`);
2850
+ logger$8.log(`Found ${allSubscribers.length} subscribers`);
2851
+ logger$8.log(`Found ${allQueues.length} queues`);
2852
+ logger$8.log(`Found ${allTopics.length} topics`);
2730
2853
  if (!input.serveEmpty && allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
2731
- logger$9.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
2854
+ logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
2732
2855
  return {};
2733
2856
  }
2734
2857
  const rootOutputDir = join(appRoot, ".gkm");
@@ -2745,7 +2868,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
2745
2868
  const rootOutputDir = join(appRoot, ".gkm");
2746
2869
  const outputDir = join(rootOutputDir, provider);
2747
2870
  await mkdir(outputDir, { recursive: true });
2748
- logger$9.log(`\nGenerating handlers for provider: ${provider}`);
2871
+ logger$8.log(`\nGenerating handlers for provider: ${provider}`);
2749
2872
  const [routes, functionInfos, cronInfos, subscriberInfos, queueInfos, topicInfos] = await Promise.all([
2750
2873
  endpointGenerator.build(context, endpoints, outputDir, {
2751
2874
  provider,
@@ -2757,7 +2880,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
2757
2880
  queueGenerator.build(context, queues, outputDir, { provider }),
2758
2881
  topicGenerator.build(context, topics, outputDir, { provider })
2759
2882
  ]);
2760
- logger$9.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
2883
+ logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
2761
2884
  const manifestRoutes = assembleManifestField(routes, endpoints);
2762
2885
  const manifestFunctions = assembleManifestField(functionInfos, functions);
2763
2886
  const manifestCrons = assembleManifestField(cronInfos, crons);
@@ -2777,8 +2900,8 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
2777
2900
  }, serverRouteField, manifestSubscribers, manifestQueues, manifestTopics);
2778
2901
  let masterKey;
2779
2902
  if (context.production?.bundle && !skipBundle) {
2780
- logger$9.log(`\n📦 Bundling production server...`);
2781
- const { bundleServer } = await import("./bundler-mwH7WOb1.mjs");
2903
+ logger$8.log(`\n📦 Bundling production server...`);
2904
+ const { bundleServer } = await import("./bundler-D5RcGmVM.mjs");
2782
2905
  const allConstructs = [
2783
2906
  ...endpoints.map((e) => e.construct),
2784
2907
  ...functions.map((f) => f.construct),
@@ -2795,10 +2918,10 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
2795
2918
  stage,
2796
2919
  constructs: allConstructs
2797
2920
  })).masterKey;
2798
- logger$9.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
2921
+ logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
2799
2922
  if (masterKey) {
2800
- logger$9.log(`\n🔐 Secrets encrypted for deployment`);
2801
- logger$9.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
2923
+ logger$8.log(`\n🔐 Secrets encrypted for deployment`);
2924
+ logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
2802
2925
  }
2803
2926
  }
2804
2927
  return { masterKey };
@@ -2885,19 +3008,19 @@ async function workspaceBuildCommand(workspace, options) {
2885
3008
  const apps = Object.entries(workspace.apps);
2886
3009
  const backendApps = apps.filter(([, app]) => app.type === "backend");
2887
3010
  const frontendApps = apps.filter(([, app]) => app.type === "web");
2888
- logger$9.log(`\n🏗️ Building workspace: ${workspace.name}`);
2889
- logger$9.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
2890
- logger$9.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
2891
- if (options.production) logger$9.log(` 🏭 Production mode enabled`);
3011
+ logger$8.log(`\n🏗️ Building workspace: ${workspace.name}`);
3012
+ logger$8.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
3013
+ logger$8.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
3014
+ if (options.production) logger$8.log(` 🏭 Production mode enabled`);
2892
3015
  const buildOrder = getAppBuildOrder(workspace);
2893
- logger$9.log(` Build order: ${buildOrder.join(" → ")}`);
3016
+ logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
2894
3017
  const pm = detectPackageManager$2();
2895
- logger$9.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
3018
+ logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
2896
3019
  try {
2897
3020
  const { filters, unpackaged } = turboFilters(workspace);
2898
3021
  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.`);
2899
3022
  const turboCommand = getTurboCommand(pm, filters);
2900
- logger$9.log(`Running: ${turboCommand}`);
3023
+ logger$8.log(`Running: ${turboCommand}`);
2901
3024
  await new Promise((resolve, reject) => {
2902
3025
  const child = spawn(turboCommand, {
2903
3026
  shell: true,
@@ -2925,15 +3048,15 @@ async function workspaceBuildCommand(workspace, options) {
2925
3048
  outputPath
2926
3049
  });
2927
3050
  }
2928
- logger$9.log(`\n✅ Workspace build complete!`);
2929
- logger$9.log(`\n📋 Build Summary:`);
3051
+ logger$8.log(`\n✅ Workspace build complete!`);
3052
+ logger$8.log(`\n📋 Build Summary:`);
2930
3053
  for (const result of results) {
2931
3054
  const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
2932
- logger$9.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
3055
+ logger$8.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
2933
3056
  }
2934
3057
  } catch (error) {
2935
3058
  const errorMessage = error instanceof Error ? error.message : "Build failed";
2936
- logger$9.log(`\n❌ Build failed: ${errorMessage}`);
3059
+ logger$8.log(`\n❌ Build failed: ${errorMessage}`);
2937
3060
  for (const [appName, app] of apps) results.push({
2938
3061
  appName,
2939
3062
  type: app.type,
@@ -3567,10 +3690,6 @@ var UnprovisionableBucket = class extends Error {
3567
3690
  */
3568
3691
  /**
3569
3692
  * Provision everything the workspace declares, and return what it resolved.
3570
- *
3571
- * Nothing happens for a project that has not adopted the model: `usesConstructs`
3572
- * is the same hard switch reconcile reads, so an existing deploy is untouched
3573
- * until it declares something.
3574
3693
  */
3575
3694
  async function provisionDeclared(options) {
3576
3695
  const { workspace } = options;
@@ -3580,7 +3699,6 @@ async function provisionDeclared(options) {
3580
3699
  provisioned: {},
3581
3700
  clusters: {}
3582
3701
  };
3583
- if (!usesConstructs(workspace)) return empty;
3584
3702
  const manifest = options.manifest ?? await discover({
3585
3703
  patterns: constructGlobs(workspace),
3586
3704
  cwd: workspace.root
@@ -3794,7 +3912,7 @@ async function createDnsProvider(options) {
3794
3912
  if (isDnsProvider(config.provider)) return config.provider;
3795
3913
  const provider = config.provider;
3796
3914
  if (provider === "hostinger") {
3797
- const { HostingerProvider } = await import("./HostingerProvider-BY26bWwB.mjs");
3915
+ const { HostingerProvider } = await import("./HostingerProvider-Btxpto71.mjs");
3798
3916
  return new HostingerProvider();
3799
3917
  }
3800
3918
  if (provider === "route53") {
@@ -3816,7 +3934,7 @@ async function createDnsProvider(options) {
3816
3934
  *
3817
3935
  * Handles automatic DNS record creation for deployed applications.
3818
3936
  */
3819
- const logger$8 = console;
3937
+ const logger$7 = console;
3820
3938
  /**
3821
3939
  * Check if DNS config is legacy format (single domain with `domain` property)
3822
3940
  */
@@ -3853,7 +3971,7 @@ function groupHostnamesByDomain(appHostnames, dnsConfig) {
3853
3971
  for (const [appName, hostname] of appHostnames) {
3854
3972
  const rootDomain = findRootDomain(hostname, dnsConfig);
3855
3973
  if (!rootDomain) {
3856
- logger$8.log(` ⚠ No DNS config found for hostname: ${hostname}`);
3974
+ logger$7.log(` ⚠ No DNS config found for hostname: ${hostname}`);
3857
3975
  continue;
3858
3976
  }
3859
3977
  if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
@@ -3903,10 +4021,10 @@ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
3903
4021
  * Print DNS records table
3904
4022
  */
3905
4023
  function printDnsRecordsTable(records, rootDomain) {
3906
- logger$8.log(`\n 📋 DNS Records for ${rootDomain}:`);
3907
- logger$8.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
3908
- logger$8.log(" │ Subdomain │ Type │ Value │ Status │");
3909
- logger$8.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
4024
+ logger$7.log(`\n 📋 DNS Records for ${rootDomain}:`);
4025
+ logger$7.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
4026
+ logger$7.log(" │ Subdomain │ Type │ Value │ Status │");
4027
+ logger$7.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
3910
4028
  for (const record of records) {
3911
4029
  const subdomain = record.subdomain.padEnd(35);
3912
4030
  const type = record.type.padEnd(4);
@@ -3916,18 +4034,18 @@ function printDnsRecordsTable(records, rootDomain) {
3916
4034
  else if (record.created) status = "✓ new";
3917
4035
  else if (record.existed) status = "✓";
3918
4036
  else status = "?";
3919
- logger$8.log(` │ ${subdomain} │ ${type} │ ${value} │ ${status.padEnd(6)} │`);
4037
+ logger$7.log(` │ ${subdomain} │ ${type} │ ${value} │ ${status.padEnd(6)} │`);
3920
4038
  }
3921
- logger$8.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
4039
+ logger$7.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
3922
4040
  }
3923
4041
  /**
3924
4042
  * Print DNS records in a simple format for manual setup
3925
4043
  */
3926
4044
  function printDnsRecordsSimple(records, rootDomain) {
3927
- logger$8.log("\n 📋 Required DNS Records:");
3928
- logger$8.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
3929
- for (const record of records) logger$8.log(` ${record.subdomain} → ${record.value} (A record)`);
3930
- logger$8.log("");
4045
+ logger$7.log("\n 📋 Required DNS Records:");
4046
+ logger$7.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
4047
+ for (const record of records) logger$7.log(` ${record.subdomain} → ${record.value} (A record)`);
4048
+ logger$7.log("");
3931
4049
  }
3932
4050
  /**
3933
4051
  * Create DNS records for a single domain using its configured provider
@@ -3939,7 +4057,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
3939
4057
  provider = await createDnsProvider({ config: providerConfig });
3940
4058
  } catch (error) {
3941
4059
  const message = error instanceof Error ? error.message : "Unknown error";
3942
- logger$8.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
4060
+ logger$7.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
3943
4061
  return records.map((r) => ({
3944
4062
  ...r,
3945
4063
  error: message
@@ -3993,7 +4111,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
3993
4111
  }
3994
4112
  } catch (error) {
3995
4113
  const message = error instanceof Error ? error.message : "Unknown error";
3996
- logger$8.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
4114
+ logger$7.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
3997
4115
  return records.map((r) => ({
3998
4116
  hostname: r.hostname,
3999
4117
  subdomain: r.subdomain,
@@ -4020,20 +4138,20 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
4020
4138
  async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
4021
4139
  if (!dnsConfig) return null;
4022
4140
  const normalizedConfig = normalizeDnsConfig(dnsConfig);
4023
- logger$8.log("\n🌐 Setting up DNS records...");
4141
+ logger$7.log("\n🌐 Setting up DNS records...");
4024
4142
  let serverIp;
4025
4143
  try {
4026
4144
  const endpointUrl = new URL(dokployEndpoint);
4027
4145
  serverIp = await resolveHostnameToIp(endpointUrl.hostname);
4028
- logger$8.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
4146
+ logger$7.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
4029
4147
  } catch (error) {
4030
4148
  const message = error instanceof Error ? error.message : "Unknown error";
4031
- logger$8.log(` ⚠ Failed to resolve server IP: ${message}`);
4149
+ logger$7.log(` ⚠ Failed to resolve server IP: ${message}`);
4032
4150
  return null;
4033
4151
  }
4034
4152
  const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
4035
4153
  if (groupedHostnames.size === 0) {
4036
- logger$8.log(" No DNS records needed (no hostnames match configured domains)");
4154
+ logger$7.log(" No DNS records needed (no hostnames match configured domains)");
4037
4155
  return {
4038
4156
  records: [],
4039
4157
  success: true,
@@ -4045,22 +4163,22 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
4045
4163
  for (const [rootDomain, domainHostnames] of groupedHostnames) {
4046
4164
  const providerConfig = normalizedConfig[rootDomain];
4047
4165
  if (!providerConfig) {
4048
- logger$8.log(` ⚠ No provider config for ${rootDomain}`);
4166
+ logger$7.log(` ⚠ No provider config for ${rootDomain}`);
4049
4167
  continue;
4050
4168
  }
4051
4169
  const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
4052
4170
  const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
4053
4171
  if (requiredRecords.length === 0) continue;
4054
- logger$8.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
4172
+ logger$7.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
4055
4173
  const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
4056
4174
  allRecords.push(...domainRecords);
4057
4175
  const created = domainRecords.filter((r) => r.created).length;
4058
4176
  const existed = domainRecords.filter((r) => r.existed).length;
4059
4177
  const failed = domainRecords.filter((r) => r.error).length;
4060
- if (created > 0) logger$8.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
4061
- if (existed > 0) logger$8.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
4178
+ if (created > 0) logger$7.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
4179
+ if (existed > 0) logger$7.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
4062
4180
  if (failed > 0) {
4063
- logger$8.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
4181
+ logger$7.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
4064
4182
  hasFailures = true;
4065
4183
  }
4066
4184
  if (state) {
@@ -4097,10 +4215,10 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
4097
4215
  */
4098
4216
  async function verifyDnsRecords(appHostnames, serverIp, state) {
4099
4217
  const results = [];
4100
- logger$8.log("\n🔍 Verifying DNS records...");
4218
+ logger$7.log("\n🔍 Verifying DNS records...");
4101
4219
  for (const [appName, hostname] of appHostnames) {
4102
4220
  if (isDnsVerified(state, hostname, serverIp)) {
4103
- logger$8.log(` ✓ ${hostname} (previously verified)`);
4221
+ logger$7.log(` ✓ ${hostname} (previously verified)`);
4104
4222
  results.push({
4105
4223
  hostname,
4106
4224
  appName,
@@ -4114,7 +4232,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
4114
4232
  const resolvedIp = await resolveHostnameToIp(hostname);
4115
4233
  if (resolvedIp === serverIp) {
4116
4234
  setDnsVerification(state, hostname, serverIp);
4117
- logger$8.log(` ✓ ${hostname} → ${resolvedIp}`);
4235
+ logger$7.log(` ✓ ${hostname} → ${resolvedIp}`);
4118
4236
  results.push({
4119
4237
  hostname,
4120
4238
  appName,
@@ -4123,7 +4241,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
4123
4241
  expectedIp: serverIp
4124
4242
  });
4125
4243
  } else {
4126
- logger$8.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
4244
+ logger$7.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
4127
4245
  results.push({
4128
4246
  hostname,
4129
4247
  appName,
@@ -4134,7 +4252,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
4134
4252
  }
4135
4253
  } catch (error) {
4136
4254
  const message = error instanceof Error ? error.message : "Unknown error";
4137
- logger$8.log(` ⚠ ${hostname} DNS not propagated (${message})`);
4255
+ logger$7.log(` ⚠ ${hostname} DNS not propagated (${message})`);
4138
4256
  results.push({
4139
4257
  hostname,
4140
4258
  appName,
@@ -4148,9 +4266,9 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
4148
4266
  const skipped = results.filter((r) => r.skipped).length;
4149
4267
  const pending = results.filter((r) => !r.verified).length;
4150
4268
  if (pending > 0) {
4151
- logger$8.log(`\n ${verified} verified, ${pending} pending propagation`);
4152
- logger$8.log(" DNS changes may take 5-30 minutes to propagate");
4153
- } else if (skipped > 0) logger$8.log(` ${verified} verified (${skipped} from cache)`);
4269
+ logger$7.log(`\n ${verified} verified, ${pending} pending propagation`);
4270
+ logger$7.log(" DNS changes may take 5-30 minutes to propagate");
4271
+ } else if (skipped > 0) logger$7.log(` ${verified} verified (${skipped} from cache)`);
4154
4272
  return results;
4155
4273
  }
4156
4274
  //#endregion
@@ -4183,43 +4301,19 @@ function resolveHost(appName, app, stage, dokployConfig, isMainFrontend) {
4183
4301
  return `${appName}.${baseDomain}`;
4184
4302
  }
4185
4303
  /**
4186
- * Which site the base domain points at.
4187
- *
4188
- * Three rules, in order, and the last one is the point:
4189
- *
4190
- * 1. **One site** — it is the root. Unambiguous, so nothing has to be said.
4191
- * 2. **Named `web`** — the convention wins, because people already rely on it.
4192
- * 3. **Declared `root: true`** — the site says so itself.
4193
- *
4194
- * Anything else throws. It used to take the *first* `type: 'web'` it iterated,
4195
- * and that object is built from the manifest, which is built in glob traversal
4196
- * order — so two sites with neither named `web` meant renaming a file could
4197
- * move the production root domain, silently. The same shape as
4198
- * `CacheIsAmbiguous`: unambiguous with one and arbitrary with two, so two is an
4199
- * error rather than a coin toss.
4304
+ * Whether this app is the site the base domain points at — see `rootSite`,
4305
+ * which the local edge asks too.
4200
4306
  *
4201
4307
  * @throws {AmbiguousRootSite} when several sites could be the root and none says it is
4202
4308
  */
4203
4309
  function isMainFrontendApp(appName, app, allApps) {
4204
4310
  if (app.type !== "web") return false;
4205
- const sites = Object.entries(allApps).filter(([, a]) => a.type === "web");
4206
- if (sites.length === 1) return true;
4207
- if (sites.filter(([name]) => name === "web").length > 0) return appName === "web";
4208
- const declared = sites.filter(([, a]) => a.root === true);
4209
- if (declared.length === 1) return declared[0][0] === appName;
4210
- throw new AmbiguousRootSite(sites.map(([name]) => name), declared.length > 1);
4211
- }
4212
- /** Several sites could hold the base domain, and none of them says it does. */
4213
- var AmbiguousRootSite = class extends Error {
4214
- sites;
4215
- tooMany;
4216
- constructor(sites, tooMany = false) {
4217
- 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.`);
4218
- this.sites = sites;
4219
- this.tooMany = tooMany;
4220
- this.name = "AmbiguousRootSite";
4221
- }
4222
- };
4311
+ const sites = Object.entries(allApps).filter(([, a]) => a.type === "web").map(([name, a]) => ({
4312
+ name,
4313
+ root: a.root
4314
+ }));
4315
+ return rootSite(sites) === appName;
4316
+ }
4223
4317
  /**
4224
4318
  * Get public URL arg names from app dependencies.
4225
4319
  *
@@ -5082,7 +5176,7 @@ CMD ["nginx", "-g", "daemon off;"]
5082
5176
  }
5083
5177
  //#endregion
5084
5178
  //#region src/docker/index.ts
5085
- const logger$7 = console;
5179
+ const logger$6 = console;
5086
5180
  /**
5087
5181
  * Docker command implementation
5088
5182
  * Generates Dockerfile, docker-compose.yml, and related files
@@ -5093,7 +5187,7 @@ const logger$7 = console;
5093
5187
  async function dockerCommand(options) {
5094
5188
  const loadedConfig = await loadWorkspaceConfig();
5095
5189
  if (loadedConfig.type === "workspace") {
5096
- logger$7.log("📦 Detected workspace configuration");
5190
+ logger$6.log("📦 Detected workspace configuration");
5097
5191
  return workspaceDockerCommand(loadedConfig.workspace);
5098
5192
  }
5099
5193
  const config = await loadConfig();
@@ -5113,7 +5207,7 @@ async function dockerCommand(options) {
5113
5207
  if (inMonorepo && !useSlim) {
5114
5208
  if (hasTurbo) {
5115
5209
  useTurbo = true;
5116
- logger$7.log(" Detected monorepo with turbo.json - using turbo prune");
5210
+ logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
5117
5211
  } 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");
5118
5212
  }
5119
5213
  let turboPackage = options.turboPackage ?? dockerConfig.imageName;
@@ -5121,7 +5215,7 @@ async function dockerCommand(options) {
5121
5215
  const pkg = __require(`${process.cwd()}/package.json`);
5122
5216
  if (pkg.name) {
5123
5217
  turboPackage = pkg.name;
5124
- logger$7.log(` Turbo package: ${turboPackage}`);
5218
+ logger$6.log(` Turbo package: ${turboPackage}`);
5125
5219
  }
5126
5220
  } catch {}
5127
5221
  const templateOptions = {
@@ -5138,16 +5232,16 @@ async function dockerCommand(options) {
5138
5232
  const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
5139
5233
  const dockerfilePath = join(dockerDir, "Dockerfile");
5140
5234
  await writeFile(dockerfilePath, dockerfile);
5141
- logger$7.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
5235
+ logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
5142
5236
  const composePath = await writeConstructsCompose(loadedConfig.workspace);
5143
5237
  const dockerignore = generateDockerignore();
5144
5238
  const dockerignorePath = join(process.cwd(), ".dockerignore");
5145
5239
  await writeFile(dockerignorePath, dockerignore);
5146
- logger$7.log("Generated: .dockerignore (project root)");
5240
+ logger$6.log("Generated: .dockerignore (project root)");
5147
5241
  const entrypoint = generateDockerEntrypoint();
5148
5242
  const entrypointPath = join(dockerDir, "docker-entrypoint.sh");
5149
5243
  await writeFile(entrypointPath, entrypoint);
5150
- logger$7.log("Generated: .gkm/docker/docker-entrypoint.sh");
5244
+ logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
5151
5245
  const result = {
5152
5246
  dockerfile: dockerfilePath,
5153
5247
  dockerCompose: composePath,
@@ -5166,13 +5260,13 @@ async function dockerCommand(options) {
5166
5260
  function ensureLockfile(cwd) {
5167
5261
  const lockfilePath = findLockfilePath(cwd);
5168
5262
  if (!lockfilePath) {
5169
- logger$7.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
5263
+ logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
5170
5264
  return null;
5171
5265
  }
5172
5266
  const lockfileName = basename(lockfilePath);
5173
5267
  const localLockfile = join(cwd, lockfileName);
5174
5268
  if (lockfilePath === localLockfile) return null;
5175
- logger$7.log(` Copying ${lockfileName} from monorepo root...`);
5269
+ logger$6.log(` Copying ${lockfileName} from monorepo root...`);
5176
5270
  copyFileSync(lockfilePath, localLockfile);
5177
5271
  return () => {
5178
5272
  try {
@@ -5188,7 +5282,7 @@ async function buildDockerImage(imageName, options) {
5188
5282
  const tag = options.tag ?? "latest";
5189
5283
  const registry = options.registry;
5190
5284
  const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
5191
- logger$7.log(`\n🐳 Building Docker image: ${fullImageName}`);
5285
+ logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
5192
5286
  const cwd = process.cwd();
5193
5287
  const cleanup = ensureLockfile(cwd);
5194
5288
  try {
@@ -5200,7 +5294,7 @@ async function buildDockerImage(imageName, options) {
5200
5294
  DOCKER_BUILDKIT: "1"
5201
5295
  }
5202
5296
  });
5203
- logger$7.log(`✅ Docker image built: ${fullImageName}`);
5297
+ logger$6.log(`✅ Docker image built: ${fullImageName}`);
5204
5298
  } catch (error) {
5205
5299
  throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
5206
5300
  } finally {
@@ -5215,13 +5309,13 @@ async function pushDockerImage(imageName, options) {
5215
5309
  const registry = options.registry;
5216
5310
  if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
5217
5311
  const fullImageName = `${registry}/${imageName}:${tag}`;
5218
- logger$7.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
5312
+ logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
5219
5313
  try {
5220
5314
  execSync(`docker push ${fullImageName}`, {
5221
5315
  cwd: process.cwd(),
5222
5316
  stdio: "inherit"
5223
5317
  });
5224
- logger$7.log(`✅ Docker image pushed: ${fullImageName}`);
5318
+ logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
5225
5319
  } catch (error) {
5226
5320
  throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
5227
5321
  }
@@ -5246,21 +5340,21 @@ function getAppPackageName(appPath) {
5246
5340
  async function workspaceDockerCommand(workspace) {
5247
5341
  const results = [];
5248
5342
  const apps = Object.entries(workspace.apps);
5249
- logger$7.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
5343
+ logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
5250
5344
  const dockerDir = join(workspace.root, ".gkm", "docker");
5251
5345
  await mkdir(dockerDir, { recursive: true });
5252
5346
  const packageManager = detectPackageManager$1(workspace.root);
5253
- logger$7.log(` Package manager: ${packageManager}`);
5347
+ logger$6.log(` Package manager: ${packageManager}`);
5254
5348
  for (const [appName, app] of apps) {
5255
5349
  if (app.type === "mobile") {
5256
- logger$7.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
5350
+ logger$6.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
5257
5351
  continue;
5258
5352
  }
5259
5353
  const appPath = app.path;
5260
5354
  const turboPackage = getAppPackageName(join(workspace.root, appPath)) ?? appName;
5261
5355
  const imageName = appName;
5262
5356
  const buildType = !!app.entry ? "entry" : app.type;
5263
- logger$7.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
5357
+ logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
5264
5358
  let dockerfile;
5265
5359
  if (app.type === "web") {
5266
5360
  const publicUrlArgs = getPublicUrlArgNames(app);
@@ -5304,7 +5398,7 @@ async function workspaceDockerCommand(workspace) {
5304
5398
  });
5305
5399
  const dockerfilePath = join(dockerDir, `Dockerfile.${appName}`);
5306
5400
  await writeFile(dockerfilePath, dockerfile);
5307
- logger$7.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
5401
+ logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
5308
5402
  results.push({
5309
5403
  appName,
5310
5404
  type: app.type,
@@ -5315,13 +5409,13 @@ async function workspaceDockerCommand(workspace) {
5315
5409
  const dockerignore = generateDockerignore();
5316
5410
  const dockerignorePath = join(workspace.root, ".dockerignore");
5317
5411
  await writeFile(dockerignorePath, dockerignore);
5318
- logger$7.log(`\n Generated: .dockerignore (workspace root)`);
5412
+ logger$6.log(`\n Generated: .dockerignore (workspace root)`);
5319
5413
  const composePath = await writeConstructsCompose(workspace);
5320
- logger$7.log(`\n✅ Generated ${results.length} Dockerfile(s) + ${COMPOSE_PATH}`);
5321
- logger$7.log("\n📋 Build commands:");
5414
+ logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + ${COMPOSE_PATH}`);
5415
+ logger$6.log("\n📋 Build commands:");
5322
5416
  for (const result of results) {
5323
5417
  const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
5324
- logger$7.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
5418
+ logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
5325
5419
  }
5326
5420
  printRunInstructions(workspace);
5327
5421
  return {
@@ -5342,18 +5436,18 @@ async function writeConstructsCompose(workspace) {
5342
5436
  stage: workspace.stages.local,
5343
5437
  start: false
5344
5438
  });
5345
- logger$7.log(` Generated: ${COMPOSE_PATH} (project root)`);
5439
+ logger$6.log(` Generated: ${COMPOSE_PATH} (project root)`);
5346
5440
  return join(workspace.root, COMPOSE_PATH);
5347
5441
  }
5348
5442
  function printRunInstructions(workspace) {
5349
5443
  const project = existsSync(join(workspace.root, "docker-compose.yml")) ? " -f docker-compose.yml" : "";
5350
- logger$7.log("\n📋 Run everything (the apps sit behind the \"apps\" profile):");
5351
- logger$7.log(" gkm setup # starts the containers and creates what they hold");
5352
- logger$7.log(` docker compose -f ${COMPOSE_PATH}${project} --profile apps up --build`);
5444
+ logger$6.log("\n📋 Run everything (the apps sit behind the \"apps\" profile):");
5445
+ logger$6.log(" gkm setup # starts the containers and creates what they hold");
5446
+ logger$6.log(` docker compose -f ${COMPOSE_PATH}${project} --profile apps up --build`);
5353
5447
  }
5354
5448
  //#endregion
5355
5449
  //#region src/deploy/docker.ts
5356
- const logger$6 = console;
5450
+ const logger$5 = console;
5357
5451
  /**
5358
5452
  * Get the full image reference
5359
5453
  */
@@ -5368,9 +5462,9 @@ function getImageRef(registry, imageName, tag) {
5368
5462
  * @param buildArgs - Build arguments to pass to docker build
5369
5463
  */
5370
5464
  async function buildImage(imageRef, _appName, buildArgs) {
5371
- logger$6.log(`\n🔨 Building Docker image: ${imageRef}`);
5465
+ logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
5372
5466
  const cwd = process.cwd();
5373
- logger$6.log(" Generating Dockerfile for the pre-built bundle...");
5467
+ logger$5.log(" Generating Dockerfile for the pre-built bundle...");
5374
5468
  await dockerCommand({ slim: true });
5375
5469
  const dockerfilePath = join(cwd, ".gkm", "docker", "Dockerfile");
5376
5470
  const buildCwd = cwd;
@@ -5392,7 +5486,7 @@ async function buildImage(imageRef, _appName, buildArgs) {
5392
5486
  DOCKER_BUILDKIT: "1"
5393
5487
  }
5394
5488
  });
5395
- logger$6.log(`✅ Image built: ${imageRef}`);
5489
+ logger$5.log(`✅ Image built: ${imageRef}`);
5396
5490
  } catch (error) {
5397
5491
  throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
5398
5492
  }
@@ -5401,13 +5495,13 @@ async function buildImage(imageRef, _appName, buildArgs) {
5401
5495
  * Push Docker image to registry
5402
5496
  */
5403
5497
  async function pushImage(imageRef) {
5404
- logger$6.log(`\n☁️ Pushing image: ${imageRef}`);
5498
+ logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
5405
5499
  try {
5406
5500
  execSync(`docker push ${imageRef}`, {
5407
5501
  cwd: process.cwd(),
5408
5502
  stdio: "inherit"
5409
5503
  });
5410
- logger$6.log(`✅ Image pushed: ${imageRef}`);
5504
+ logger$5.log(`✅ Image pushed: ${imageRef}`);
5411
5505
  } catch (error) {
5412
5506
  throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
5413
5507
  }
@@ -5421,18 +5515,18 @@ async function deployDocker(options) {
5421
5515
  const imageRef = getImageRef(config.registry, imageName, tag);
5422
5516
  await buildImage(imageRef, config.appName, buildArgs);
5423
5517
  if (!skipPush) {
5424
- if (!config.registry) logger$6.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
5518
+ if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
5425
5519
  else await pushImage(imageRef);
5426
5520
  }
5427
- logger$6.log("\n✅ Docker deployment ready!");
5428
- logger$6.log(`\n📋 Deployment details:`);
5429
- logger$6.log(` Image: ${imageRef}`);
5430
- logger$6.log(` Stage: ${stage}`);
5521
+ logger$5.log("\n✅ Docker deployment ready!");
5522
+ logger$5.log(`\n📋 Deployment details:`);
5523
+ logger$5.log(` Image: ${imageRef}`);
5524
+ logger$5.log(` Stage: ${stage}`);
5431
5525
  if (masterKey) {
5432
- logger$6.log(`\n🔐 Deploy with this environment variable:`);
5433
- logger$6.log(` GKM_MASTER_KEY=${masterKey}`);
5434
- logger$6.log("\n Example docker run:");
5435
- logger$6.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
5526
+ logger$5.log(`\n🔐 Deploy with this environment variable:`);
5527
+ logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
5528
+ logger$5.log("\n Example docker run:");
5529
+ logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
5436
5530
  }
5437
5531
  return {
5438
5532
  imageRef,
@@ -6811,7 +6905,7 @@ async function sniffAllApps(apps, workspacePath, options = {}) {
6811
6905
  *
6812
6906
  * @module deploy
6813
6907
  */
6814
- const logger$5 = console;
6908
+ const logger$4 = console;
6815
6909
  /**
6816
6910
  * Prompt for input
6817
6911
  */
@@ -6885,7 +6979,7 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
6885
6979
  return;
6886
6980
  } catch {
6887
6981
  if (i < maxRetries - 1) {
6888
- logger$5.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
6982
+ logger$4.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
6889
6983
  await new Promise((r) => setTimeout(r, retryIntervalMs));
6890
6984
  }
6891
6985
  }
@@ -6928,10 +7022,10 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
6928
7022
  break;
6929
7023
  } catch (error) {
6930
7024
  if (!(error instanceof Error ? error.message : String(error)).includes("already in use")) throw error;
6931
- logger$5.log(` Port ${candidate} is taken; trying another...`);
7025
+ logger$4.log(` Port ${candidate} is taken; trying another...`);
6932
7026
  }
6933
7027
  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.`);
6934
- logger$5.log(` Publishing ${postgres.appName} on ${externalPort}...`);
7028
+ logger$4.log(` Publishing ${postgres.appName} on ${externalPort}...`);
6935
7029
  }
6936
7030
  await api.deployPostgres(postgres.postgresId);
6937
7031
  await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName);
@@ -6955,7 +7049,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
6955
7049
  const unpublish = async () => {
6956
7050
  if (!opened) return;
6957
7051
  await api.savePostgresExternalPort(postgres.postgresId, null).then(() => api.deployPostgres(postgres.postgresId)).catch(() => {
6958
- logger$5.log(` ⚠ Could not close external port ${externalPort} on ${postgres.appName} — close it in Dokploy.`);
7052
+ logger$4.log(` ⚠ Could not close external port ${externalPort} on ${postgres.appName} — close it in Dokploy.`);
6959
7053
  });
6960
7054
  };
6961
7055
  let lastError;
@@ -6966,7 +7060,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
6966
7060
  lastError = error;
6967
7061
  if (attempt === 3) break;
6968
7062
  const message = error instanceof Error ? error.message : String(error);
6969
- logger$5.log(` ⏳ Cluster still settling (${message}); retrying (${attempt}/2)...`);
7063
+ logger$4.log(` ⏳ Cluster still settling (${message}); retrying (${attempt}/2)...`);
6970
7064
  await new Promise((resolve) => setTimeout(resolve, 1e4));
6971
7065
  await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName).catch(() => {});
6972
7066
  }
@@ -7041,91 +7135,91 @@ async function workspaceDeployCommand(configured, options) {
7041
7135
  apps: units
7042
7136
  } : configured;
7043
7137
  if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
7044
- logger$5.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
7045
- logger$5.log(` Stage: ${stage}`);
7138
+ logger$4.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
7139
+ logger$4.log(` Stage: ${stage}`);
7046
7140
  const imageTag = tag ?? generateTag(stage);
7047
- logger$5.log(` Tag: ${imageTag}`);
7141
+ logger$4.log(` Tag: ${imageTag}`);
7048
7142
  const buildOrder = getAppBuildOrder(workspace);
7049
7143
  let appsToDeployNames = buildOrder;
7050
7144
  if (selectedApps && selectedApps.length > 0) {
7051
7145
  const invalidApps = selectedApps.filter((name) => !workspace.apps[name]);
7052
7146
  if (invalidApps.length > 0) throw new Error(`Unknown apps: ${invalidApps.join(", ")}\nAvailable apps: ${Object.keys(workspace.apps).join(", ")}`);
7053
7147
  appsToDeployNames = buildOrder.filter((name) => selectedApps.includes(name));
7054
- logger$5.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
7055
- } else logger$5.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
7148
+ logger$4.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
7149
+ } else logger$4.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
7056
7150
  const dokployApps = appsToDeployNames.filter((name) => {
7057
7151
  const target = workspace.apps[name].resolvedDeployTarget;
7058
7152
  if (!isDeployTargetSupported(target)) {
7059
- logger$5.log(` ⚠️ Skipping ${name}: ${getDeployTargetError(target, name)}`);
7153
+ logger$4.log(` ⚠️ Skipping ${name}: ${getDeployTargetError(target, name)}`);
7060
7154
  return false;
7061
7155
  }
7062
7156
  return true;
7063
7157
  });
7064
7158
  if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
7065
7159
  appsToDeployNames = dokployApps;
7066
- logger$5.log("\n🔐 Loading secrets and analyzing environment requirements...");
7160
+ logger$4.log("\n🔐 Loading secrets and analyzing environment requirements...");
7067
7161
  const stageSecrets = await readStageSecrets(stage, workspace.root);
7068
7162
  if (!stageSecrets) {
7069
- logger$5.log(` ⚠️ No secrets found for stage "${stage}"`);
7070
- logger$5.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
7163
+ logger$4.log(` ⚠️ No secrets found for stage "${stage}"`);
7164
+ logger$4.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
7071
7165
  }
7072
7166
  const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
7073
7167
  const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
7074
7168
  if (stageSecrets) {
7075
7169
  const report = generateSecretsReport(encryptedSecrets, sniffedApps);
7076
- if (report.appsWithSecrets.length > 0) logger$5.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
7077
- if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$5.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
7170
+ if (report.appsWithSecrets.length > 0) logger$4.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
7171
+ if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$4.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
7078
7172
  }
7079
7173
  let creds = await getDokployCredentials();
7080
7174
  if (!creds) {
7081
- logger$5.log("\n📋 Dokploy credentials not found. Let's set them up.");
7175
+ logger$4.log("\n📋 Dokploy credentials not found. Let's set them up.");
7082
7176
  const normalizedEndpoint = (await prompt("Dokploy URL (e.g., https://dokploy.example.com): ")).replace(/\/$/, "");
7083
7177
  try {
7084
7178
  new URL(normalizedEndpoint);
7085
7179
  } catch {
7086
7180
  throw new Error("Invalid URL format");
7087
7181
  }
7088
- logger$5.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
7182
+ logger$4.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
7089
7183
  const token = await prompt("API Token: ", true);
7090
- logger$5.log("\nValidating credentials...");
7184
+ logger$4.log("\nValidating credentials...");
7091
7185
  if (!await validateDokployToken(normalizedEndpoint, token)) throw new Error("Invalid credentials. Please check your token.");
7092
7186
  await storeDokployCredentials(token, normalizedEndpoint);
7093
7187
  creds = {
7094
7188
  token,
7095
7189
  endpoint: normalizedEndpoint
7096
7190
  };
7097
- logger$5.log("✓ Credentials saved");
7191
+ logger$4.log("✓ Credentials saved");
7098
7192
  }
7099
7193
  const api = new DokployApi({
7100
7194
  baseUrl: creds.endpoint,
7101
7195
  token: creds.token
7102
7196
  });
7103
- logger$5.log("\n📁 Setting up Dokploy project...");
7197
+ logger$4.log("\n📁 Setting up Dokploy project...");
7104
7198
  const projectName = workspace.name;
7105
7199
  let project = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
7106
7200
  let environmentId;
7107
7201
  if (project) {
7108
- logger$5.log(` Found existing project: ${project.name}`);
7202
+ logger$4.log(` Found existing project: ${project.name}`);
7109
7203
  const matchingEnv = ((await api.getProject(project.projectId)).environments ?? []).find((e) => e.name.toLowerCase() === stage.toLowerCase());
7110
7204
  if (matchingEnv) {
7111
7205
  environmentId = matchingEnv.environmentId;
7112
- logger$5.log(` Using environment: ${matchingEnv.name}`);
7206
+ logger$4.log(` Using environment: ${matchingEnv.name}`);
7113
7207
  } else {
7114
- logger$5.log(` Creating "${stage}" environment...`);
7208
+ logger$4.log(` Creating "${stage}" environment...`);
7115
7209
  environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
7116
- logger$5.log(` ✓ Created environment: ${stage}`);
7210
+ logger$4.log(` ✓ Created environment: ${stage}`);
7117
7211
  }
7118
7212
  } else {
7119
- logger$5.log(` Creating project: ${projectName}`);
7213
+ logger$4.log(` Creating project: ${projectName}`);
7120
7214
  const result = await api.createProject(projectName);
7121
7215
  project = result.project;
7122
7216
  if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
7123
- logger$5.log(` Creating "${stage}" environment...`);
7217
+ logger$4.log(` Creating "${stage}" environment...`);
7124
7218
  environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
7125
7219
  } else environmentId = result.environment.environmentId;
7126
- logger$5.log(` ✓ Created project: ${project.projectId}`);
7220
+ logger$4.log(` ✓ Created project: ${project.projectId}`);
7127
7221
  }
7128
- logger$5.log("\n📋 Loading deploy state...");
7222
+ logger$4.log("\n📋 Loading deploy state...");
7129
7223
  const stateProvider = await createStateProvider({
7130
7224
  config: workspace.state,
7131
7225
  workspaceRoot: workspace.root,
@@ -7133,27 +7227,27 @@ async function workspaceDeployCommand(configured, options) {
7133
7227
  });
7134
7228
  let state = await stateProvider.read(stage);
7135
7229
  if (state) {
7136
- logger$5.log(` Found existing state for stage "${stage}"`);
7230
+ logger$4.log(` Found existing state for stage "${stage}"`);
7137
7231
  if (state.projectId !== project.projectId) {
7138
- logger$5.log(` ⚠ Project ID changed, updating state`);
7232
+ logger$4.log(` ⚠ Project ID changed, updating state`);
7139
7233
  state.projectId = project.projectId;
7140
7234
  }
7141
7235
  if (state.environmentId !== environmentId) {
7142
- logger$5.log(` ⚠ Environment ID changed, updating state`);
7236
+ logger$4.log(` ⚠ Environment ID changed, updating state`);
7143
7237
  state.environmentId = environmentId;
7144
7238
  }
7145
7239
  } else {
7146
- logger$5.log(` Creating new state for stage "${stage}"`);
7240
+ logger$4.log(` Creating new state for stage "${stage}"`);
7147
7241
  state = createEmptyState(stage, project.projectId, environmentId);
7148
7242
  }
7149
- logger$5.log("\n🐳 Checking registry...");
7243
+ logger$4.log("\n🐳 Checking registry...");
7150
7244
  let registryId = await getDokployRegistryId();
7151
7245
  const registry = workspace.deploy.dokploy?.registry;
7152
7246
  if (registryId) try {
7153
7247
  const reg = await api.getRegistry(registryId);
7154
- logger$5.log(` Using registry: ${reg.registryName}`);
7248
+ logger$4.log(` Using registry: ${reg.registryName}`);
7155
7249
  } catch {
7156
- logger$5.log(" ⚠ Stored registry not found, clearing...");
7250
+ logger$4.log(" ⚠ Stored registry not found, clearing...");
7157
7251
  registryId = void 0;
7158
7252
  await storeDokployRegistryId("");
7159
7253
  }
@@ -7162,26 +7256,26 @@ async function workspaceDeployCommand(configured, options) {
7162
7256
  if (registries.length > 0) {
7163
7257
  registryId = registries[0].registryId;
7164
7258
  await storeDokployRegistryId(registryId);
7165
- logger$5.log(` Using registry: ${registries[0].registryName}`);
7259
+ logger$4.log(` Using registry: ${registries[0].registryName}`);
7166
7260
  } else if (registry) {
7167
- logger$5.log(" No registries found in Dokploy. Let's create one.");
7168
- logger$5.log(` Registry URL: ${registry}`);
7261
+ logger$4.log(" No registries found in Dokploy. Let's create one.");
7262
+ logger$4.log(` Registry URL: ${registry}`);
7169
7263
  const username = await prompt("Registry username: ");
7170
7264
  const password = await prompt("Registry password/token: ", true);
7171
7265
  registryId = (await api.createRegistry("Default Registry", registry, username, password)).registryId;
7172
7266
  await storeDokployRegistryId(registryId);
7173
- logger$5.log(` ✓ Registry created: ${registryId}`);
7174
- } else logger$5.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
7267
+ logger$4.log(` ✓ Registry created: ${registryId}`);
7268
+ } else logger$4.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
7175
7269
  }
7176
7270
  const skippedMobileApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "mobile");
7177
- if (skippedMobileApps.length > 0) logger$5.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
7271
+ if (skippedMobileApps.length > 0) logger$4.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
7178
7272
  const backendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "backend");
7179
7273
  const frontendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "web");
7180
7274
  const dokployConfig = workspace.deploy.dokploy;
7181
7275
  let declaredEnv = {};
7182
7276
  let declaredClusters = {};
7183
7277
  {
7184
- logger$5.log("\n📦 Provisioning declared constructs...");
7278
+ logger$4.log("\n📦 Provisioning declared constructs...");
7185
7279
  const appUrls = {};
7186
7280
  for (const appName of backendApps) {
7187
7281
  const app = workspace.apps[appName];
@@ -7199,20 +7293,20 @@ async function workspaceDeployCommand(configured, options) {
7199
7293
  });
7200
7294
  declaredEnv = declared.env;
7201
7295
  declaredClusters = declared.clusters;
7202
- if (Object.keys(declaredEnv).length > 0) logger$5.log(` 🔌 Resolved ${Object.keys(declaredEnv).length} declared URL(s)`);
7296
+ if (Object.keys(declaredEnv).length > 0) logger$4.log(` 🔌 Resolved ${Object.keys(declaredEnv).length} declared URL(s)`);
7203
7297
  if (declared.statements.length > 0) {
7204
7298
  const serverHostname = getServerHostname(creds.endpoint);
7205
7299
  for (const [databaseName, cluster] of Object.entries(declared.clusters)) {
7206
7300
  const statements = declared.statements.filter((statement) => statement.database === databaseName);
7207
7301
  if (statements.length === 0) continue;
7208
7302
  const created = await applyDeclaredStatements(api, cluster, serverHostname, statements);
7209
- logger$5.log(` 🗄️ ${databaseName}: applied ${statements.length} statement(s), ${created} new`);
7303
+ logger$4.log(` 🗄️ ${databaseName}: applied ${statements.length} statement(s), ${created} new`);
7210
7304
  }
7211
7305
  }
7212
7306
  }
7213
7307
  const backupCluster = Object.values(declaredClusters)[0];
7214
7308
  if (workspace.deploy?.backups && backupCluster) {
7215
- logger$5.log("\n💾 Provisioning backup destination...");
7309
+ logger$4.log("\n💾 Provisioning backup destination...");
7216
7310
  const { provisionBackupDestination } = await import("./backup-provisioner-DIVCbfee.mjs");
7217
7311
  const backupState = await provisionBackupDestination({
7218
7312
  api,
@@ -7221,13 +7315,13 @@ async function workspaceDeployCommand(configured, options) {
7221
7315
  stage,
7222
7316
  config: workspace.deploy.backups,
7223
7317
  existingState: getBackupState(state),
7224
- logger: logger$5
7318
+ logger: logger$4
7225
7319
  });
7226
7320
  setBackupState(state, backupState);
7227
7321
  if (!backupState.postgresBackupId) {
7228
7322
  const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
7229
7323
  const backupRetention = workspace.deploy.backups.retention ?? 30;
7230
- logger$5.log(" Creating postgres backup schedule...");
7324
+ logger$4.log(" Creating postgres backup schedule...");
7231
7325
  const backup = await api.createPostgresBackup({
7232
7326
  schedule: backupSchedule,
7233
7327
  prefix: `${stage}/postgres`,
@@ -7238,8 +7332,8 @@ async function workspaceDeployCommand(configured, options) {
7238
7332
  keepLatestCount: backupRetention
7239
7333
  });
7240
7334
  setPostgresBackupId(state, backup.backupId);
7241
- logger$5.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
7242
- } else logger$5.log(" ✓ Using existing postgres backup schedule");
7335
+ logger$4.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
7336
+ } else logger$4.log(" ✓ Using existing postgres backup schedule");
7243
7337
  }
7244
7338
  const publicUrls = {};
7245
7339
  const results = [];
@@ -7252,25 +7346,25 @@ async function workspaceDeployCommand(configured, options) {
7252
7346
  frontendUrls.push(`https://${hostname}`);
7253
7347
  }
7254
7348
  if (backendApps.length > 0) {
7255
- logger$5.log("\n📦 PHASE 1: Deploying backend applications...");
7349
+ logger$4.log("\n📦 PHASE 1: Deploying backend applications...");
7256
7350
  for (const appName of backendApps) {
7257
7351
  const app = workspace.apps[appName];
7258
- logger$5.log(`\n ⚙️ Deploying ${appName}...`);
7352
+ logger$4.log(`\n ⚙️ Deploying ${appName}...`);
7259
7353
  try {
7260
7354
  const dokployAppName = applicationName(stage, workspace.name, appName);
7261
7355
  let application = null;
7262
7356
  const cachedAppId = getApplicationId(state, appName);
7263
7357
  if (cachedAppId) {
7264
- logger$5.log(` Using cached ID: ${cachedAppId}`);
7358
+ logger$4.log(` Using cached ID: ${cachedAppId}`);
7265
7359
  application = await api.getApplication(cachedAppId);
7266
- if (application) logger$5.log(` ✓ Application found: ${application.applicationId}`);
7267
- else logger$5.log(` ⚠ Cached ID invalid, will create new`);
7360
+ if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
7361
+ else logger$4.log(` ⚠ Cached ID invalid, will create new`);
7268
7362
  }
7269
7363
  if (!application) {
7270
7364
  const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
7271
7365
  application = result.application;
7272
- if (result.created) logger$5.log(` Created application: ${application.applicationId}`);
7273
- else logger$5.log(` Found existing application: ${application.applicationId}`);
7366
+ if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
7367
+ else logger$4.log(` Found existing application: ${application.applicationId}`);
7274
7368
  }
7275
7369
  setApplicationId(state, appName, application.applicationId);
7276
7370
  const appSecrets = encryptedSecrets.get(appName);
@@ -7278,11 +7372,11 @@ async function workspaceDeployCommand(configured, options) {
7278
7372
  if (appSecrets && appSecrets.secretCount > 0) {
7279
7373
  buildArgs.push(`GKM_ENCRYPTED_CREDENTIALS=${appSecrets.payload.encrypted}`);
7280
7374
  buildArgs.push(`GKM_CREDENTIALS_IV=${appSecrets.payload.iv}`);
7281
- logger$5.log(` Encrypted ${appSecrets.secretCount} secrets`);
7375
+ logger$4.log(` Encrypted ${appSecrets.secretCount} secrets`);
7282
7376
  }
7283
7377
  const imageName = `${workspace.name}-${appName}`;
7284
7378
  const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
7285
- logger$5.log(` Building Docker image: ${imageRef}`);
7379
+ logger$4.log(` Building Docker image: ${imageRef}`);
7286
7380
  await deployDocker({
7287
7381
  stage,
7288
7382
  tag: imageTag,
@@ -7323,17 +7417,17 @@ async function workspaceDeployCommand(configured, options) {
7323
7417
  ...declaredEnv
7324
7418
  };
7325
7419
  const envVars = Object.entries(withDeclared).map(([key, value]) => `${key}=${value}`);
7326
- if (Object.keys(withDeclared).length > 0) logger$5.log(` Resolved ${Object.keys(withDeclared).length} env vars: ${Object.keys(withDeclared).sort().join(", ")}`);
7420
+ if (Object.keys(withDeclared).length > 0) logger$4.log(` Resolved ${Object.keys(withDeclared).length} env vars: ${Object.keys(withDeclared).sort().join(", ")}`);
7327
7421
  await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
7328
7422
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
7329
- logger$5.log(` Deploying to Dokploy...`);
7423
+ logger$4.log(` Deploying to Dokploy...`);
7330
7424
  await api.deployApplication(application.applicationId);
7331
7425
  const existingDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === backendHost);
7332
7426
  if (existingDomain) {
7333
7427
  appHostnames.set(appName, backendHost);
7334
7428
  appDomainIds.set(appName, existingDomain.domainId);
7335
7429
  publicUrls[appName] = `https://${backendHost}`;
7336
- logger$5.log(` ✓ Domain: https://${backendHost} (existing)`);
7430
+ logger$4.log(` ✓ Domain: https://${backendHost} (existing)`);
7337
7431
  } else try {
7338
7432
  const domain = await api.createDomain({
7339
7433
  host: backendHost,
@@ -7345,10 +7439,10 @@ async function workspaceDeployCommand(configured, options) {
7345
7439
  appHostnames.set(appName, backendHost);
7346
7440
  appDomainIds.set(appName, domain.domainId);
7347
7441
  publicUrls[appName] = `https://${backendHost}`;
7348
- logger$5.log(` ✓ Domain: https://${backendHost} (created)`);
7442
+ logger$4.log(` ✓ Domain: https://${backendHost} (created)`);
7349
7443
  } catch (domainError) {
7350
7444
  const message = domainError instanceof Error ? domainError.message : "Unknown error";
7351
- logger$5.log(` ⚠ Domain creation failed: ${message}`);
7445
+ logger$4.log(` ⚠ Domain creation failed: ${message}`);
7352
7446
  appHostnames.set(appName, backendHost);
7353
7447
  publicUrls[appName] = `https://${backendHost}`;
7354
7448
  }
@@ -7359,10 +7453,10 @@ async function workspaceDeployCommand(configured, options) {
7359
7453
  applicationId: application.applicationId,
7360
7454
  imageRef
7361
7455
  });
7362
- logger$5.log(` ✓ ${appName} deployed successfully`);
7456
+ logger$4.log(` ✓ ${appName} deployed successfully`);
7363
7457
  } catch (error) {
7364
7458
  const message = error instanceof Error ? error.message : "Unknown error";
7365
- logger$5.log(` ✗ Failed to deploy ${appName}: ${message}`);
7459
+ logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
7366
7460
  results.push({
7367
7461
  appName,
7368
7462
  type: app.type,
@@ -7374,25 +7468,25 @@ async function workspaceDeployCommand(configured, options) {
7374
7468
  }
7375
7469
  }
7376
7470
  if (frontendApps.length > 0) {
7377
- logger$5.log("\n🌐 PHASE 2: Deploying frontend applications...");
7471
+ logger$4.log("\n🌐 PHASE 2: Deploying frontend applications...");
7378
7472
  for (const appName of frontendApps) {
7379
7473
  const app = workspace.apps[appName];
7380
- logger$5.log(`\n 🌐 Deploying ${appName}...`);
7474
+ logger$4.log(`\n 🌐 Deploying ${appName}...`);
7381
7475
  try {
7382
7476
  const dokployAppName = applicationName(stage, workspace.name, appName);
7383
7477
  let application = null;
7384
7478
  const cachedAppId = getApplicationId(state, appName);
7385
7479
  if (cachedAppId) {
7386
- logger$5.log(` Using cached ID: ${cachedAppId}`);
7480
+ logger$4.log(` Using cached ID: ${cachedAppId}`);
7387
7481
  application = await api.getApplication(cachedAppId);
7388
- if (application) logger$5.log(` ✓ Application found: ${application.applicationId}`);
7389
- else logger$5.log(` ⚠ Cached ID invalid, will create new`);
7482
+ if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
7483
+ else logger$4.log(` ⚠ Cached ID invalid, will create new`);
7390
7484
  }
7391
7485
  if (!application) {
7392
7486
  const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
7393
7487
  application = result.application;
7394
- if (result.created) logger$5.log(` Created application: ${application.applicationId}`);
7395
- else logger$5.log(` Found existing application: ${application.applicationId}`);
7488
+ if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
7489
+ else logger$4.log(` Found existing application: ${application.applicationId}`);
7396
7490
  }
7397
7491
  setApplicationId(state, appName, application.applicationId);
7398
7492
  const dependencyUrls = {};
@@ -7412,7 +7506,7 @@ async function workspaceDeployCommand(configured, options) {
7412
7506
  };
7413
7507
  const { valid, missing, resolved } = validateEnvVars(requiredOf(sniffedApps.get(appName)), envContext);
7414
7508
  if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
7415
- if (Object.keys(resolved).length > 0) logger$5.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
7509
+ if (Object.keys(resolved).length > 0) logger$4.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
7416
7510
  const publicPrefix = getPublicEnvPrefix(app.framework);
7417
7511
  const buildArgs = [];
7418
7512
  const publicUrlArgNames = [];
@@ -7422,10 +7516,10 @@ async function workspaceDeployCommand(configured, options) {
7422
7516
  publicUrlArgNames.push(key);
7423
7517
  }
7424
7518
  }
7425
- if (buildArgs.length > 0) logger$5.log(` Build args: ${publicUrlArgNames.join(", ")}`);
7519
+ if (buildArgs.length > 0) logger$4.log(` Build args: ${publicUrlArgNames.join(", ")}`);
7426
7520
  const imageName = `${workspace.name}-${appName}`;
7427
7521
  const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
7428
- logger$5.log(` Building Docker image: ${imageRef}`);
7522
+ logger$4.log(` Building Docker image: ${imageRef}`);
7429
7523
  await deployDocker({
7430
7524
  stage,
7431
7525
  tag: imageTag,
@@ -7446,14 +7540,14 @@ async function workspaceDeployCommand(configured, options) {
7446
7540
  for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
7447
7541
  await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
7448
7542
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
7449
- logger$5.log(` Deploying to Dokploy...`);
7543
+ logger$4.log(` Deploying to Dokploy...`);
7450
7544
  await api.deployApplication(application.applicationId);
7451
7545
  const existingFrontendDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === frontendHost);
7452
7546
  if (existingFrontendDomain) {
7453
7547
  appHostnames.set(appName, frontendHost);
7454
7548
  appDomainIds.set(appName, existingFrontendDomain.domainId);
7455
7549
  publicUrls[appName] = `https://${frontendHost}`;
7456
- logger$5.log(` ✓ Domain: https://${frontendHost} (existing)`);
7550
+ logger$4.log(` ✓ Domain: https://${frontendHost} (existing)`);
7457
7551
  } else try {
7458
7552
  const domain = await api.createDomain({
7459
7553
  host: frontendHost,
@@ -7465,10 +7559,10 @@ async function workspaceDeployCommand(configured, options) {
7465
7559
  appHostnames.set(appName, frontendHost);
7466
7560
  appDomainIds.set(appName, domain.domainId);
7467
7561
  publicUrls[appName] = `https://${frontendHost}`;
7468
- logger$5.log(` ✓ Domain: https://${frontendHost} (created)`);
7562
+ logger$4.log(` ✓ Domain: https://${frontendHost} (created)`);
7469
7563
  } catch (domainError) {
7470
7564
  const message = domainError instanceof Error ? domainError.message : "Unknown error";
7471
- logger$5.log(` ⚠ Domain creation failed: ${message}`);
7565
+ logger$4.log(` ⚠ Domain creation failed: ${message}`);
7472
7566
  appHostnames.set(appName, frontendHost);
7473
7567
  publicUrls[appName] = `https://${frontendHost}`;
7474
7568
  }
@@ -7479,10 +7573,10 @@ async function workspaceDeployCommand(configured, options) {
7479
7573
  applicationId: application.applicationId,
7480
7574
  imageRef
7481
7575
  });
7482
- logger$5.log(` ✓ ${appName} deployed successfully`);
7576
+ logger$4.log(` ✓ ${appName} deployed successfully`);
7483
7577
  } catch (error) {
7484
7578
  const message = error instanceof Error ? error.message : "Unknown error";
7485
- logger$5.log(` ✗ Failed to deploy ${appName}: ${message}`);
7579
+ logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
7486
7580
  results.push({
7487
7581
  appName,
7488
7582
  type: app.type,
@@ -7492,9 +7586,9 @@ async function workspaceDeployCommand(configured, options) {
7492
7586
  }
7493
7587
  }
7494
7588
  }
7495
- logger$5.log("\n📋 Saving deploy state...");
7589
+ logger$4.log("\n📋 Saving deploy state...");
7496
7590
  await stateProvider.write(stage, state);
7497
- logger$5.log(" ✓ State saved");
7591
+ logger$4.log(" ✓ State saved");
7498
7592
  const dnsConfig = workspace.deploy.dns;
7499
7593
  if (dnsConfig && appHostnames.size > 0) {
7500
7594
  const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
@@ -7503,27 +7597,27 @@ async function workspaceDeployCommand(configured, options) {
7503
7597
  await stateProvider.write(stage, state);
7504
7598
  }
7505
7599
  if (dnsResult?.success && appHostnames.size > 0) {
7506
- logger$5.log("\n🔒 Validating domains for SSL certificates...");
7600
+ logger$4.log("\n🔒 Validating domains for SSL certificates...");
7507
7601
  for (const [appName, hostname] of appHostnames) try {
7508
7602
  const result = await api.validateDomain(hostname);
7509
- if (result.isValid) logger$5.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
7510
- else logger$5.log(` ⚠ ${appName}: ${hostname} not valid`);
7603
+ if (result.isValid) logger$4.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
7604
+ else logger$4.log(` ⚠ ${appName}: ${hostname} not valid`);
7511
7605
  } catch (validationError) {
7512
7606
  const message = validationError instanceof Error ? validationError.message : "Unknown error";
7513
- logger$5.log(` ⚠ ${appName}: validation failed - ${message}`);
7607
+ logger$4.log(` ⚠ ${appName}: validation failed - ${message}`);
7514
7608
  }
7515
7609
  }
7516
7610
  }
7517
7611
  const successCount = results.filter((r) => r.success).length;
7518
7612
  const failedCount = results.filter((r) => !r.success).length;
7519
- logger$5.log(`\n${"─".repeat(50)}`);
7520
- logger$5.log(`\n✅ Workspace deployment complete!`);
7521
- logger$5.log(` Project: ${project.projectId}`);
7522
- logger$5.log(` Successful: ${successCount}`);
7523
- if (failedCount > 0) logger$5.log(` Failed: ${failedCount}`);
7613
+ logger$4.log(`\n${"─".repeat(50)}`);
7614
+ logger$4.log(`\n✅ Workspace deployment complete!`);
7615
+ logger$4.log(` Project: ${project.projectId}`);
7616
+ logger$4.log(` Successful: ${successCount}`);
7617
+ if (failedCount > 0) logger$4.log(` Failed: ${failedCount}`);
7524
7618
  if (Object.keys(publicUrls).length > 0) {
7525
- logger$5.log("\n 📡 Deployed URLs:");
7526
- for (const [name, url] of Object.entries(publicUrls)) logger$5.log(` ${name}: ${url}`);
7619
+ logger$4.log("\n 📡 Deployed URLs:");
7620
+ for (const [name, url] of Object.entries(publicUrls)) logger$4.log(` ${name}: ${url}`);
7527
7621
  }
7528
7622
  return {
7529
7623
  apps: results,
@@ -7542,7 +7636,7 @@ async function deployCommand(options) {
7542
7636
  }
7543
7637
  //#endregion
7544
7638
  //#region src/deploy/init.ts
7545
- const logger$4 = console;
7639
+ const logger$3 = console;
7546
7640
  /**
7547
7641
  * Get the Dokploy API token from stored credentials or environment
7548
7642
  */
@@ -7588,28 +7682,28 @@ function unsafeToRewrite(content) {
7588
7682
  async function updateConfig(config, cwd = process.cwd()) {
7589
7683
  const configPath = join(cwd, "gkm.config.ts");
7590
7684
  if (!existsSync(configPath)) {
7591
- logger$4.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
7592
- logger$4.log(` providers: {`);
7593
- logger$4.log(` dokploy: {`);
7594
- logger$4.log(` endpoint: '${config.endpoint}',`);
7595
- logger$4.log(` projectId: '${config.projectId}',`);
7596
- logger$4.log(` applicationId: '${config.applicationId}',`);
7597
- logger$4.log(` },`);
7598
- logger$4.log(` },`);
7685
+ logger$3.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
7686
+ logger$3.log(` providers: {`);
7687
+ logger$3.log(` dokploy: {`);
7688
+ logger$3.log(` endpoint: '${config.endpoint}',`);
7689
+ logger$3.log(` projectId: '${config.projectId}',`);
7690
+ logger$3.log(` applicationId: '${config.applicationId}',`);
7691
+ logger$3.log(` },`);
7692
+ logger$3.log(` },`);
7599
7693
  return;
7600
7694
  }
7601
7695
  const content = await readFile(configPath, "utf-8");
7602
7696
  if (unsafeToRewrite(content)) {
7603
- 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.");
7604
- logger$4.log(`\n Discovered, if you want them written down:`);
7605
- logger$4.log(` projectId: '${config.projectId}'`);
7606
- logger$4.log(` applicationId: '${config.applicationId}'`);
7607
- if (config.registryId) logger$4.log(` registryId: '${config.registryId}'`);
7697
+ 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.");
7698
+ logger$3.log(`\n Discovered, if you want them written down:`);
7699
+ logger$3.log(` projectId: '${config.projectId}'`);
7700
+ logger$3.log(` applicationId: '${config.applicationId}'`);
7701
+ if (config.registryId) logger$3.log(` registryId: '${config.registryId}'`);
7608
7702
  return;
7609
7703
  }
7610
7704
  if (content.includes("dokploy:") && content.includes("applicationId:")) {
7611
- logger$4.log("\n Dokploy config already exists in gkm.config.ts");
7612
- logger$4.log(" Updating with new values...");
7705
+ logger$3.log("\n Dokploy config already exists in gkm.config.ts");
7706
+ logger$3.log(" Updating with new values...");
7613
7707
  }
7614
7708
  const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
7615
7709
  const dokployConfigStr = `dokploy: {
@@ -7627,7 +7721,7 @@ async function updateConfig(config, cwd = process.cwd()) {
7627
7721
  },
7628
7722
  });`);
7629
7723
  await writeFile(configPath, newContent);
7630
- logger$4.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
7724
+ logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
7631
7725
  }
7632
7726
  /**
7633
7727
  * Initialize Dokploy deployment configuration
@@ -7636,22 +7730,22 @@ async function deployInitCommand(options) {
7636
7730
  const { projectName, appName, projectId: existingProjectId, registryId } = options;
7637
7731
  const endpoint = await getEndpoint(options.endpoint);
7638
7732
  const api = await createApi(endpoint);
7639
- logger$4.log(`\n🚀 Initializing Dokploy deployment...`);
7640
- logger$4.log(` Endpoint: ${endpoint}`);
7733
+ logger$3.log(`\n🚀 Initializing Dokploy deployment...`);
7734
+ logger$3.log(` Endpoint: ${endpoint}`);
7641
7735
  let projectId;
7642
7736
  if (existingProjectId) {
7643
7737
  projectId = existingProjectId;
7644
- logger$4.log(`\n📁 Using existing project: ${projectId}`);
7738
+ logger$3.log(`\n📁 Using existing project: ${projectId}`);
7645
7739
  } else {
7646
- logger$4.log(`\n📁 Looking for project: ${projectName}`);
7740
+ logger$3.log(`\n📁 Looking for project: ${projectName}`);
7647
7741
  const existingProject = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
7648
7742
  if (existingProject) {
7649
7743
  projectId = existingProject.projectId;
7650
- logger$4.log(` Found existing project: ${projectId}`);
7744
+ logger$3.log(` Found existing project: ${projectId}`);
7651
7745
  } else {
7652
- logger$4.log(` Creating new project...`);
7746
+ logger$3.log(` Creating new project...`);
7653
7747
  projectId = (await api.createProject(projectName)).project.projectId;
7654
- logger$4.log(` ✓ Created project: ${projectId}`);
7748
+ logger$3.log(` ✓ Created project: ${projectId}`);
7655
7749
  }
7656
7750
  }
7657
7751
  const project = await api.getProject(projectId);
@@ -7659,22 +7753,22 @@ async function deployInitCommand(options) {
7659
7753
  const firstEnv = project.environments?.[0];
7660
7754
  if (firstEnv) environmentId = firstEnv.environmentId;
7661
7755
  else {
7662
- logger$4.log(` Creating production environment...`);
7756
+ logger$3.log(` Creating production environment...`);
7663
7757
  environmentId = (await api.createEnvironment(projectId, "production")).environmentId;
7664
7758
  }
7665
- logger$4.log(`\n📦 Creating application: ${appName}`);
7759
+ logger$3.log(`\n📦 Creating application: ${appName}`);
7666
7760
  const application = await api.createApplication(appName, projectId, environmentId);
7667
- logger$4.log(` ✓ Created application: ${application.applicationId}`);
7761
+ logger$3.log(` ✓ Created application: ${application.applicationId}`);
7668
7762
  if (registryId) {
7669
- logger$4.log(`\n🔧 Configuring registry: ${registryId}`);
7763
+ logger$3.log(`\n🔧 Configuring registry: ${registryId}`);
7670
7764
  await api.updateApplication(application.applicationId, { registryId });
7671
- logger$4.log(` ✓ Registry configured`);
7765
+ logger$3.log(` ✓ Registry configured`);
7672
7766
  } else try {
7673
7767
  const registries = await api.listRegistries();
7674
7768
  if (registries.length > 0) {
7675
- logger$4.log(`\n📋 Available registries:`);
7676
- for (const reg of registries) logger$4.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
7677
- logger$4.log(`\n To use a registry, run with --registry-id <id>`);
7769
+ logger$3.log(`\n📋 Available registries:`);
7770
+ for (const reg of registries) logger$3.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
7771
+ logger$3.log(`\n To use a registry, run with --registry-id <id>`);
7678
7772
  }
7679
7773
  } catch {}
7680
7774
  const config = {
@@ -7683,14 +7777,14 @@ async function deployInitCommand(options) {
7683
7777
  applicationId: application.applicationId
7684
7778
  };
7685
7779
  await updateConfig(config);
7686
- logger$4.log(`\n✅ Dokploy deployment initialized!`);
7687
- logger$4.log(`\n📋 Configuration:`);
7688
- logger$4.log(` Project ID: ${projectId}`);
7689
- logger$4.log(` Application ID: ${application.applicationId}`);
7690
- logger$4.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
7691
- logger$4.log(`\n📝 Next steps:`);
7692
- logger$4.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
7693
- logger$4.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
7780
+ logger$3.log(`\n✅ Dokploy deployment initialized!`);
7781
+ logger$3.log(`\n📋 Configuration:`);
7782
+ logger$3.log(` Project ID: ${projectId}`);
7783
+ logger$3.log(` Application ID: ${application.applicationId}`);
7784
+ logger$3.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
7785
+ logger$3.log(`\n📝 Next steps:`);
7786
+ logger$3.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
7787
+ logger$3.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
7694
7788
  return config;
7695
7789
  }
7696
7790
  /**
@@ -7701,31 +7795,31 @@ async function deployListCommand(options) {
7701
7795
  const api = await createApi(endpoint);
7702
7796
  const { resource } = options;
7703
7797
  if (resource === "projects") {
7704
- logger$4.log(`\n📁 Projects in ${endpoint}:`);
7798
+ logger$3.log(`\n📁 Projects in ${endpoint}:`);
7705
7799
  const projects = await api.listProjects();
7706
7800
  if (projects.length === 0) {
7707
- logger$4.log(" No projects found");
7801
+ logger$3.log(" No projects found");
7708
7802
  return;
7709
7803
  }
7710
7804
  for (const project of projects) {
7711
- logger$4.log(`\n ${project.name} (${project.projectId})`);
7712
- if (project.description) logger$4.log(` ${project.description}`);
7805
+ logger$3.log(`\n ${project.name} (${project.projectId})`);
7806
+ if (project.description) logger$3.log(` ${project.description}`);
7713
7807
  }
7714
7808
  } else if (resource === "registries") {
7715
- logger$4.log(`\n🐳 Registries in ${endpoint}:`);
7809
+ logger$3.log(`\n🐳 Registries in ${endpoint}:`);
7716
7810
  const registries = await api.listRegistries();
7717
7811
  if (registries.length === 0) {
7718
- logger$4.log(" No registries configured");
7719
- logger$4.log(" Run \"gkm registry:setup\" to configure a registry");
7812
+ logger$3.log(" No registries configured");
7813
+ logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
7720
7814
  return;
7721
7815
  }
7722
7816
  const storedRegistryId = await getDokployRegistryId();
7723
7817
  for (const registry of registries) {
7724
7818
  const marker = registry.registryId === storedRegistryId ? " (default)" : "";
7725
- logger$4.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
7726
- logger$4.log(` URL: ${registry.registryUrl}`);
7727
- logger$4.log(` Username: ${registry.username}`);
7728
- if (registry.imagePrefix) logger$4.log(` Prefix: ${registry.imagePrefix}`);
7819
+ logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
7820
+ logger$3.log(` URL: ${registry.registryUrl}`);
7821
+ logger$3.log(` Username: ${registry.username}`);
7822
+ if (registry.imagePrefix) logger$3.log(` Prefix: ${registry.imagePrefix}`);
7729
7823
  }
7730
7824
  }
7731
7825
  }
@@ -8586,26 +8680,26 @@ function loadPackageJson() {
8586
8680
  * Run: pnpm --filter @geekmidas/cli sync-versions
8587
8681
  */
8588
8682
  const GEEKMIDAS_VERSIONS = {
8589
- "@geekmidas/audit": "~10.0.0-alpha.26",
8590
- "@geekmidas/auth": "~10.0.0-alpha.26",
8591
- "@geekmidas/cache": "~10.0.0-alpha.26",
8592
- "@geekmidas/client": "~10.0.0-alpha.26",
8593
- "@geekmidas/cloud": "~10.0.0-alpha.26",
8594
- "@geekmidas/constructs": "~10.0.0-alpha.26",
8595
- "@geekmidas/db": "~10.0.0-alpha.26",
8596
- "@geekmidas/emailkit": "~10.0.0-alpha.26",
8597
- "@geekmidas/envkit": "~10.0.0-alpha.26",
8598
- "@geekmidas/errors": "~10.0.0-alpha.26",
8599
- "@geekmidas/events": "~10.0.0-alpha.26",
8600
- "@geekmidas/logger": "~10.0.0-alpha.26",
8601
- "@geekmidas/manifest": "~10.0.0-alpha.26",
8602
- "@geekmidas/rate-limit": "~10.0.0-alpha.26",
8603
- "@geekmidas/schema": "~10.0.0-alpha.26",
8604
- "@geekmidas/services": "~10.0.0-alpha.26",
8605
- "@geekmidas/storage": "~10.0.0-alpha.26",
8606
- "@geekmidas/studio": "~10.0.0-alpha.26",
8607
- "@geekmidas/telescope": "~10.0.0-alpha.26",
8608
- "@geekmidas/testkit": "~10.0.0-alpha.26",
8683
+ "@geekmidas/audit": "~10.0.0-alpha.28",
8684
+ "@geekmidas/auth": "~10.0.0-alpha.28",
8685
+ "@geekmidas/cache": "~10.0.0-alpha.28",
8686
+ "@geekmidas/client": "~10.0.0-alpha.28",
8687
+ "@geekmidas/cloud": "~10.0.0-alpha.28",
8688
+ "@geekmidas/constructs": "~10.0.0-alpha.28",
8689
+ "@geekmidas/db": "~10.0.0-alpha.28",
8690
+ "@geekmidas/emailkit": "~10.0.0-alpha.28",
8691
+ "@geekmidas/envkit": "~10.0.0-alpha.28",
8692
+ "@geekmidas/errors": "~10.0.0-alpha.28",
8693
+ "@geekmidas/events": "~10.0.0-alpha.28",
8694
+ "@geekmidas/logger": "~10.0.0-alpha.28",
8695
+ "@geekmidas/manifest": "~10.0.0-alpha.28",
8696
+ "@geekmidas/rate-limit": "~10.0.0-alpha.28",
8697
+ "@geekmidas/schema": "~10.0.0-alpha.28",
8698
+ "@geekmidas/services": "~10.0.0-alpha.28",
8699
+ "@geekmidas/storage": "~10.0.0-alpha.28",
8700
+ "@geekmidas/studio": "~10.0.0-alpha.28",
8701
+ "@geekmidas/telescope": "~10.0.0-alpha.28",
8702
+ "@geekmidas/testkit": "~10.0.0-alpha.28",
8609
8703
  "@geekmidas/cli": `~${loadPackageJson().version}`
8610
8704
  };
8611
8705
  //#endregion
@@ -14684,7 +14778,7 @@ var NotAnAwsRegion = class extends Error {
14684
14778
  };
14685
14779
  //#endregion
14686
14780
  //#region src/secrets/index.ts
14687
- const logger$3 = console;
14781
+ const logger$2 = console;
14688
14782
  /**
14689
14783
  * Initialize secrets for a stage.
14690
14784
  * Generates secure random passwords for configured services.
@@ -14692,7 +14786,7 @@ const logger$3 = console;
14692
14786
  async function secretsInitCommand(options) {
14693
14787
  const { stage, force } = options;
14694
14788
  if (!force && secretsExist(stage)) {
14695
- logger$3.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
14789
+ logger$2.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
14696
14790
  process.exit(1);
14697
14791
  }
14698
14792
  let projectName;
@@ -14702,21 +14796,21 @@ async function secretsInitCommand(options) {
14702
14796
  projectName = loaded.workspace.name;
14703
14797
  if (Object.keys(loaded.workspace.apps).length > 1) {
14704
14798
  workspaceSecrets = generateFullstackCustomSecrets(loaded.workspace);
14705
- logger$3.log(" Detected workspace mode — generating per-app secrets");
14799
+ logger$2.log(" Detected workspace mode — generating per-app secrets");
14706
14800
  }
14707
14801
  } catch {}
14708
14802
  const secrets = createStageSecrets(stage, [], { projectName });
14709
14803
  if (workspaceSecrets) secrets.custom = workspaceSecrets;
14710
14804
  await writeStageSecrets(secrets);
14711
- logger$3.log(`\n✓ Secrets initialized for stage "${stage}"`);
14712
- logger$3.log(` Location: .gkm/secrets/${stage}.json`);
14713
- if (secrets.urls.DATABASE_URL) logger$3.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
14714
- if (secrets.urls.REDIS_URL) logger$3.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
14715
- if (secrets.urls.RABBITMQ_URL) logger$3.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
14716
- if (secrets.urls.STORAGE_ENDPOINT) logger$3.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
14717
- if (Object.keys(secrets.custom).length > 0) logger$3.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
14718
- logger$3.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
14719
- logger$3.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
14805
+ logger$2.log(`\n✓ Secrets initialized for stage "${stage}"`);
14806
+ logger$2.log(` Location: .gkm/secrets/${stage}.json`);
14807
+ if (secrets.urls.DATABASE_URL) logger$2.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
14808
+ if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
14809
+ if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
14810
+ if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
14811
+ if (Object.keys(secrets.custom).length > 0) logger$2.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
14812
+ logger$2.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
14813
+ logger$2.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
14720
14814
  }
14721
14815
  /**
14722
14816
  * Read all data from stdin.
@@ -14735,21 +14829,21 @@ async function secretsSetCommand(key, value, options) {
14735
14829
  let secretValue = value;
14736
14830
  if (!secretValue) {
14737
14831
  if (process.stdin.isTTY) {
14738
- logger$3.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
14739
- logger$3.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
14832
+ logger$2.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
14833
+ logger$2.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
14740
14834
  process.exit(1);
14741
14835
  }
14742
14836
  secretValue = await readStdin();
14743
14837
  if (!secretValue) {
14744
- logger$3.error("No value received from stdin");
14838
+ logger$2.error("No value received from stdin");
14745
14839
  process.exit(1);
14746
14840
  }
14747
14841
  }
14748
14842
  try {
14749
14843
  await setCustomSecret(stage, key, secretValue);
14750
- logger$3.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
14844
+ logger$2.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
14751
14845
  } catch (error) {
14752
- logger$3.error(error instanceof Error ? error.message : "Failed to set secret");
14846
+ logger$2.error(error instanceof Error ? error.message : "Failed to set secret");
14753
14847
  process.exit(1);
14754
14848
  }
14755
14849
  }
@@ -14760,33 +14854,33 @@ async function secretsShowCommand(options) {
14760
14854
  const { stage, reveal } = options;
14761
14855
  const secrets = await readStageSecrets(stage);
14762
14856
  if (!secrets) {
14763
- logger$3.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14857
+ logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14764
14858
  process.exit(1);
14765
14859
  }
14766
- logger$3.log(`\nSecrets for stage "${stage}":`);
14767
- logger$3.log(` Created: ${secrets.createdAt}`);
14768
- logger$3.log(` Updated: ${secrets.updatedAt}`);
14769
- logger$3.log("\nService Credentials:");
14860
+ logger$2.log(`\nSecrets for stage "${stage}":`);
14861
+ logger$2.log(` Created: ${secrets.createdAt}`);
14862
+ logger$2.log(` Updated: ${secrets.updatedAt}`);
14863
+ logger$2.log("\nService Credentials:");
14770
14864
  for (const [service, creds] of Object.entries(secrets.services)) if (creds) {
14771
- logger$3.log(`\n ${service}:`);
14772
- logger$3.log(` host: ${creds.host}`);
14773
- logger$3.log(` port: ${creds.port}`);
14774
- logger$3.log(` username: ${creds.username}`);
14775
- logger$3.log(` password: ${reveal ? creds.password : maskPassword(creds.password)}`);
14776
- if (creds.database) logger$3.log(` database: ${creds.database}`);
14777
- if (creds.vhost) logger$3.log(` vhost: ${creds.vhost}`);
14778
- if (creds.bucket) logger$3.log(` bucket: ${creds.bucket}`);
14779
- }
14780
- logger$3.log("\nConnection URLs:");
14781
- if (secrets.urls.DATABASE_URL) logger$3.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
14782
- if (secrets.urls.REDIS_URL) logger$3.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
14783
- if (secrets.urls.RABBITMQ_URL) logger$3.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
14784
- if (secrets.urls.STORAGE_ENDPOINT) logger$3.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
14865
+ logger$2.log(`\n ${service}:`);
14866
+ logger$2.log(` host: ${creds.host}`);
14867
+ logger$2.log(` port: ${creds.port}`);
14868
+ logger$2.log(` username: ${creds.username}`);
14869
+ logger$2.log(` password: ${reveal ? creds.password : maskPassword(creds.password)}`);
14870
+ if (creds.database) logger$2.log(` database: ${creds.database}`);
14871
+ if (creds.vhost) logger$2.log(` vhost: ${creds.vhost}`);
14872
+ if (creds.bucket) logger$2.log(` bucket: ${creds.bucket}`);
14873
+ }
14874
+ logger$2.log("\nConnection URLs:");
14875
+ if (secrets.urls.DATABASE_URL) logger$2.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
14876
+ if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
14877
+ if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
14878
+ if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
14785
14879
  if (Object.keys(secrets.custom).length > 0) {
14786
- logger$3.log("\nCustom Secrets:");
14787
- for (const [key, value] of Object.entries(secrets.custom)) logger$3.log(` ${key}: ${reveal ? value : maskPassword(value)}`);
14880
+ logger$2.log("\nCustom Secrets:");
14881
+ for (const [key, value] of Object.entries(secrets.custom)) logger$2.log(` ${key}: ${reveal ? value : maskPassword(value)}`);
14788
14882
  }
14789
- if (!reveal) logger$3.log("\nUse --reveal to show actual values");
14883
+ if (!reveal) logger$2.log("\nUse --reveal to show actual values");
14790
14884
  }
14791
14885
  /**
14792
14886
  * Rotate passwords for services.
@@ -14795,25 +14889,25 @@ async function secretsRotateCommand(options) {
14795
14889
  const { stage, service } = options;
14796
14890
  const secrets = await readStageSecrets(stage);
14797
14891
  if (!secrets) {
14798
- logger$3.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14892
+ logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14799
14893
  process.exit(1);
14800
14894
  }
14801
14895
  if (service) {
14802
14896
  if (!secrets.services[service]) {
14803
- logger$3.error(`Service "${service}" not configured in stage "${stage}"`);
14897
+ logger$2.error(`Service "${service}" not configured in stage "${stage}"`);
14804
14898
  process.exit(1);
14805
14899
  }
14806
14900
  const updated = rotateServicePassword(secrets, service);
14807
14901
  await writeStageSecrets(updated);
14808
- logger$3.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
14902
+ logger$2.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
14809
14903
  } else {
14810
14904
  let updated = secrets;
14811
14905
  const services = Object.keys(secrets.services);
14812
14906
  for (const svc of services) updated = rotateServicePassword(updated, svc);
14813
14907
  await writeStageSecrets(updated);
14814
- logger$3.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
14908
+ logger$2.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
14815
14909
  }
14816
- logger$3.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
14910
+ logger$2.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
14817
14911
  }
14818
14912
  /**
14819
14913
  * Import secrets from a JSON file.
@@ -14821,7 +14915,7 @@ async function secretsRotateCommand(options) {
14821
14915
  async function secretsImportCommand(file, options) {
14822
14916
  const { stage, merge = true } = options;
14823
14917
  if (!existsSync(file)) {
14824
- logger$3.error(`File not found: ${file}`);
14918
+ logger$2.error(`File not found: ${file}`);
14825
14919
  process.exit(1);
14826
14920
  }
14827
14921
  let importedSecrets;
@@ -14831,12 +14925,12 @@ async function secretsImportCommand(file, options) {
14831
14925
  if (typeof importedSecrets !== "object" || importedSecrets === null) throw new Error("JSON must be an object");
14832
14926
  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}`);
14833
14927
  } catch (error) {
14834
- logger$3.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
14928
+ logger$2.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
14835
14929
  process.exit(1);
14836
14930
  }
14837
14931
  const secrets = await readStageSecrets(stage);
14838
14932
  if (!secrets) {
14839
- logger$3.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14933
+ logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
14840
14934
  process.exit(1);
14841
14935
  }
14842
14936
  const updatedCustom = merge ? {
@@ -14851,10 +14945,10 @@ async function secretsImportCommand(file, options) {
14851
14945
  await writeStageSecrets(updated);
14852
14946
  const importedCount = Object.keys(importedSecrets).length;
14853
14947
  const totalCount = Object.keys(updatedCustom).length;
14854
- logger$3.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
14855
- if (merge && totalCount > importedCount) logger$3.log(` Total custom secrets: ${totalCount}`);
14856
- logger$3.log("\n Imported keys:");
14857
- for (const key of Object.keys(importedSecrets)) logger$3.log(` - ${key}`);
14948
+ logger$2.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
14949
+ if (merge && totalCount > importedCount) logger$2.log(` Total custom secrets: ${totalCount}`);
14950
+ logger$2.log("\n Imported keys:");
14951
+ for (const key of Object.keys(importedSecrets)) logger$2.log(` - ${key}`);
14858
14952
  }
14859
14953
  /**
14860
14954
  * Mask password in a URL for display.
@@ -14869,175 +14963,120 @@ function maskUrl(url) {
14869
14963
  }
14870
14964
  }
14871
14965
  //#endregion
14872
- //#region src/trust/index.ts
14873
- /**
14874
- * Installing the local edge's certificate authority into the OS trust store.
14875
- *
14876
- * `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test
14877
- * suite, anything under `gkm exec` — because reconcile injects it. A browser
14878
- * reads none of that: it asks the operating system, so until the root is in the
14879
- * system store a local `https://` address is "not secure", which is true and
14880
- * unhelpful.
14881
- *
14882
- * A separate command rather than part of `gkm setup`, for one reason: this
14883
- * needs `sudo`, and a setup that silently escalates on every run is a worse
14884
- * trade than one that tells you what to type. Caddy's own `caddy trust` draws
14885
- * the line in the same place.
14886
- */
14887
- const logger$2 = console;
14888
- const run = promisify(execFile);
14889
- /**
14890
- * How each platform is told about a new authority.
14891
- *
14892
- * Separate rather than shelled through one script because the failure modes
14893
- * differ and the message should name the real one: macOS refuses without an
14894
- * admin password, Debian needs the file in a specific directory before its
14895
- * updater will look at it.
14896
- */
14897
- function installation(platform, certificate) {
14898
- if (platform === "darwin") return {
14899
- describe: "the System keychain",
14900
- steps: [["sudo", [
14901
- "security",
14902
- "add-trusted-cert",
14903
- "-d",
14904
- "-r",
14905
- "trustRoot",
14906
- "-k",
14907
- "/Library/Keychains/System.keychain",
14908
- certificate
14909
- ]]]
14910
- };
14911
- if (platform === "linux") {
14912
- const debian = existsSync("/usr/local/share/ca-certificates");
14913
- return {
14914
- describe: debian ? "the system CA bundle" : "the system trust anchors",
14915
- steps: debian ? [["sudo", [
14916
- "cp",
14917
- certificate,
14918
- "/usr/local/share/ca-certificates/gkm-local-ca.crt"
14919
- ]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
14920
- "cp",
14921
- certificate,
14922
- "/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
14923
- ]], ["sudo", ["update-ca-trust"]]]
14924
- };
14966
+ //#region src/secrets/store.ts
14967
+ /** The encrypted file under `.gkm/secrets/`, and its key under `~/.gkm/`. */
14968
+ var FileSecretsStore = class {
14969
+ root;
14970
+ constructor(root) {
14971
+ this.root = root;
14925
14972
  }
14926
- throw new UnsupportedPlatform(platform, certificate);
14927
- }
14928
- /**
14929
- * Trust the certificate authority this project's edge issues from.
14930
- *
14931
- * Idempotent in effect: installing a root that is already trusted replaces the
14932
- * same entry, so running it twice is not an error.
14933
- */
14934
- async function trustCommand(options = {}) {
14935
- const { workspace } = await loadWorkspaceConfig();
14936
- const certificate = join(workspace.root, LOCAL_CA_PATH);
14937
- if (!existsSync(certificate)) throw new NoLocalAuthority(certificate);
14938
- const { describe, steps } = installation(process.platform, certificate);
14939
- logger$2.log(`\n🔐 Trusting the local authority in ${describe}`);
14940
- logger$2.log(` ${certificate}\n`);
14941
- if (options.dryRun) {
14942
- for (const [command, args] of steps) logger$2.log(` ${command} ${args.join(" ")}`);
14943
- return;
14973
+ pull(stage) {
14974
+ return readStageSecrets(stage, this.root);
14944
14975
  }
14945
- logger$2.log(" This needs an admin password.\n");
14946
- for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
14947
- logger$2.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
14948
- }
14976
+ push(_stage, secrets) {
14977
+ return writeStageSecrets(secrets, this.root);
14978
+ }
14979
+ };
14949
14980
  /**
14950
- * Trust the authority if this machine lacks it and its owner agrees.
14981
+ * The store a stage's secrets live in.
14951
14982
  *
14952
- * A machine's answer, not the project's: whether a laptop installs a root
14953
- * certificate is its owner's call, so the decision is `--yes`, then what this
14954
- * machine previously answered, then a prompt. Never a silent `sudo` — that is
14955
- * the one thing a setup command should not do on somebody's behalf.
14956
- *
14957
- * Silent and free when the machine already trusts it, which is what makes it
14958
- * acceptable to call on every `gkm setup`.
14959
- */
14960
- async function ensureTrusted(root, url, options = {}) {
14961
- if (await isTrusted(url)) return;
14962
- if (!options.assumeYes) {
14963
- if (!process.stdin.isTTY) {
14964
- logger$2.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
14965
- return;
14966
- }
14967
- if (await declined(root)) return;
14968
- const { install } = await prompts({
14969
- type: "confirm",
14970
- name: "install",
14971
- message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
14972
- initial: false
14973
- });
14974
- if (!install) {
14975
- await recordDeclined(root);
14976
- logger$2.log(" Skipped. Run \"gkm trust\" later.");
14977
- return;
14978
- }
14979
- }
14980
- await trustCommand();
14981
- }
14982
- /** Where a declined answer is remembered, so setup asks once and not again. */
14983
- const TRUST_STATE = ".gkm/trust.json";
14984
- async function declined(root) {
14985
- try {
14986
- const raw = await readFile(join(root, TRUST_STATE), "utf-8");
14987
- return JSON.parse(raw).declined === true;
14988
- } catch {
14989
- return false;
14990
- }
14983
+ * The local stage is always the file: its secrets belong to the machine
14984
+ * running `gkm dev`. A deployed stage uses `secrets.store`.
14985
+ */
14986
+ async function secretsStoreFor(workspace, stage, options = {}) {
14987
+ const configured = workspace.secrets.store ?? "file";
14988
+ if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root);
14989
+ if (configured.provider !== "ssm") return configured.provider;
14990
+ const { SsmSecretsStore } = await import("./ssm-BKN1Cg4z.mjs");
14991
+ return new SsmSecretsStore({
14992
+ project: workspace.name,
14993
+ region: configured.region,
14994
+ ...options.profile ? { profile: options.profile } : {}
14995
+ });
14991
14996
  }
14992
- async function recordDeclined(root) {
14993
- const path = join(root, TRUST_STATE);
14994
- await mkdir(dirname(path), { recursive: true });
14995
- await writeFile(path, `${JSON.stringify({ declined: true }, null, 2)}\n`);
14997
+ /** Whether a stage's secrets live somewhere other than this machine. */
14998
+ function isRemoteStore(workspace, stage) {
14999
+ const configured = workspace.secrets.store ?? "file";
15000
+ return stage !== workspace.stages.local && configured !== "file";
14996
15001
  }
15002
+ //#endregion
15003
+ //#region src/secrets/transfer.ts
14997
15004
  /**
14998
- * Whether this machine already trusts the local authority.
15005
+ * Moving a deployed stage's secrets between this machine and its store.
14999
15006
  *
15000
- * Asked by connecting rather than by reading a keychain: what matters is
15001
- * whether a client with no special configuration verifies the edge, and that is
15002
- * one platform-neutral question instead of three platform-specific ones. It is
15003
- * also the exact thing a browser is about to do.
15004
- *
15005
- * A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and
15006
- * this process almost certainly has it set — asking in-process would answer
15007
- * "yes" for everyone.
15008
- */
15009
- async function isTrusted(url) {
15010
- const { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;
15011
- try {
15012
- await run(process.execPath, ["-e", `fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`], {
15013
- env,
15014
- timeout: 15e3
15015
- });
15016
- return true;
15017
- } catch {
15018
- return false;
15007
+ * `push` sends the encrypted local copy to the stage's store; `pull` brings it
15008
+ * back and writes the local copy a deploy reads — generating a key for it if
15009
+ * the machine has none, which is what a fresh CI runner needs. Both reconcile
15010
+ * the keys the workspace now derives, so a store never lags a new construct.
15011
+ */
15012
+ var transfer_exports = /* @__PURE__ */ __exportAll({
15013
+ NoLocalSecrets: () => NoLocalSecrets,
15014
+ NoRemoteSecretsStore: () => NoRemoteSecretsStore,
15015
+ NoStoredSecrets: () => NoStoredSecrets,
15016
+ pullStageSecrets: () => pullStageSecrets,
15017
+ pushStageSecrets: () => pushStageSecrets
15018
+ });
15019
+ /** A push or pull for a stage with nowhere to push or pull from. */
15020
+ var NoRemoteSecretsStore = class extends Error {
15021
+ stage;
15022
+ constructor(stage) {
15023
+ 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.`);
15024
+ this.stage = stage;
15025
+ this.name = "NoRemoteSecretsStore";
15019
15026
  }
15020
- }
15021
- /** The edge has not run, so there is no authority to trust yet. */
15022
- var NoLocalAuthority = class extends Error {
15023
- path;
15024
- constructor(path) {
15025
- 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.`);
15026
- this.path = path;
15027
- this.name = "NoLocalAuthority";
15027
+ };
15028
+ /** A push for a stage whose secrets this machine does not have. */
15029
+ var NoLocalSecrets = class extends Error {
15030
+ stage;
15031
+ constructor(stage) {
15032
+ super(`No secrets for stage "${stage}" on this machine. Run gkm secrets:init --stage ${stage} first.`);
15033
+ this.stage = stage;
15034
+ this.name = "NoLocalSecrets";
15028
15035
  }
15029
15036
  };
15030
- /** A platform with no trust store this knows how to write to. */
15031
- var UnsupportedPlatform = class extends Error {
15032
- platform;
15033
- certificate;
15034
- constructor(platform, certificate) {
15035
- 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.`);
15036
- this.platform = platform;
15037
- this.certificate = certificate;
15038
- this.name = "UnsupportedPlatform";
15037
+ /** A pull for a stage the store holds nothing for. */
15038
+ var NoStoredSecrets = class extends Error {
15039
+ stage;
15040
+ constructor(stage) {
15041
+ super(`The store holds no secrets for stage "${stage}". Push them first: gkm secrets:push --stage ${stage}.`);
15042
+ this.stage = stage;
15043
+ this.name = "NoStoredSecrets";
15039
15044
  }
15040
15045
  };
15046
+ async function reconcile(secrets, workspace, stage) {
15047
+ const { derivedContainers } = await import("./workspace-cBobQVoA.mjs").then((n) => n.a);
15048
+ const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
15049
+ return result ? {
15050
+ secrets: result.secrets,
15051
+ addedKeys: result.addedKeys
15052
+ } : {
15053
+ secrets,
15054
+ addedKeys: []
15055
+ };
15056
+ }
15057
+ function remoteOnly(workspace, stage) {
15058
+ assertDeployedStage(workspace.stages, stage);
15059
+ if (!isRemoteStore(workspace, stage)) throw new NoRemoteSecretsStore(stage);
15060
+ }
15061
+ /** Send this machine's secrets for a deployed stage to its store. */
15062
+ async function pushStageSecrets(workspace, stage, options = {}) {
15063
+ remoteOnly(workspace, stage);
15064
+ const local = await readStageSecrets(stage, workspace.root);
15065
+ if (!local) throw new NoLocalSecrets(stage);
15066
+ const result = await reconcile(local, workspace, stage);
15067
+ if (result.addedKeys.length) await writeStageSecrets(result.secrets, workspace.root);
15068
+ await (await secretsStoreFor(workspace, stage, options)).push(stage, result.secrets);
15069
+ return result;
15070
+ }
15071
+ /** Bring a deployed stage's secrets from its store into the local copy. */
15072
+ async function pullStageSecrets(workspace, stage, options = {}) {
15073
+ remoteOnly(workspace, stage);
15074
+ const stored = await (await secretsStoreFor(workspace, stage, options)).pull(stage);
15075
+ if (!stored) throw new NoStoredSecrets(stage);
15076
+ const result = await reconcile(stored, workspace, stage);
15077
+ await writeStageSecrets(result.secrets, workspace.root);
15078
+ return result;
15079
+ }
15041
15080
  //#endregion
15042
15081
  //#region src/setup/index.ts
15043
15082
  var setup_exports = /* @__PURE__ */ __exportAll({
@@ -15106,8 +15145,7 @@ async function reconcileLocal(workspace, stage, options) {
15106
15145
  }
15107
15146
  logger$1.log(`🐳 Services: ${result.plan.containers.join(", ")}`);
15108
15147
  for (const [container, address] of Object.entries(result.addresses)) logger$1.log(` ${container}: ${address}`);
15109
- const served = result.plan.resources.find((r) => r.kind === "file-server");
15110
- const url = served ? result.env[served.envKey] : void 0;
15148
+ 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://"));
15111
15149
  if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
15112
15150
  }
15113
15151
  /**
@@ -15551,7 +15589,6 @@ async function testCommand(options = {}) {
15551
15589
  stage,
15552
15590
  startDocker: !options.prepare,
15553
15591
  secretsFileName: "test-secrets.json",
15554
- resolveDockerPorts: "full",
15555
15592
  reconcileStage: "test"
15556
15593
  });
15557
15594
  let finalCredentials = { ...result.credentials };
@@ -15565,14 +15602,13 @@ async function testCommand(options = {}) {
15565
15602
  console.log(` 🔍 Sniffed ${sniffed.requiredEnvVars.length} required env var(s)` + (result.declaredKeys.length > 0 ? `, kept ${result.declaredKeys.length} declared` : ""));
15566
15603
  }
15567
15604
  }
15568
- finalCredentials = rewriteDatabaseUrlForTests(finalCredentials);
15569
15605
  console.log("");
15570
15606
  await writeFile(result.secretsJsonPath, JSON.stringify(finalCredentials, null, 2));
15571
15607
  const gkmDir = join(cwd, ".gkm");
15572
15608
  const preloadPath = join(gkmDir, "test-credentials-preload.ts");
15573
15609
  await createCredentialsPreload(preloadPath, result.secretsJsonPath);
15574
15610
  const workspace = await loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
15575
- const manifestPath = workspace && usesConstructs(workspace) ? await writeTestHarness({
15611
+ const manifestPath = workspace ? await writeTestHarness({
15576
15612
  root: workspace.root,
15577
15613
  targets: [workspace.root, ...Object.values(workspace.apps).map((app) => isAbsolute(app.path) ? app.path : join(workspace.root, app.path))],
15578
15614
  patterns: constructGlobs(workspace),
@@ -15616,29 +15652,6 @@ async function testCommand(options = {}) {
15616
15652
  });
15617
15653
  });
15618
15654
  }
15619
- const TEST_DB_SUFFIX = "_test";
15620
- /**
15621
- * Rewrite DATABASE_URL to point to a separate test database.
15622
- * Appends `_test` to the database name (e.g., `app` -> `app_test`).
15623
- * @internal Exported for testing
15624
- */
15625
- function rewriteDatabaseUrlForTests(env) {
15626
- const result = { ...env };
15627
- for (const key of Object.keys(result)) {
15628
- if (!key.includes("DATABASE_URL")) continue;
15629
- const value = result[key];
15630
- try {
15631
- const url = new URL(value);
15632
- const dbName = url.pathname.slice(1);
15633
- if (dbName && !dbName.endsWith(TEST_DB_SUFFIX)) {
15634
- url.pathname = `/${dbName}${TEST_DB_SUFFIX}`;
15635
- result[key] = url.toString();
15636
- console.log(` 🧪 ${key}: using test database "${dbName}${TEST_DB_SUFFIX}"`);
15637
- }
15638
- } catch {}
15639
- }
15640
- return result;
15641
- }
15642
15655
  /** `gkm test` outside a project that declares stages, with none named. */
15643
15656
  var NoStageToTest = class extends Error {
15644
15657
  constructor() {
@@ -16179,7 +16192,7 @@ program.command("secrets:push").description("Send a deployed stage's secrets to
16179
16192
  const globalOptions = program.opts();
16180
16193
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16181
16194
  const { loadWorkspaceConfig } = await import("./config.mjs");
16182
- const { pushStageSecrets } = await import("./transfer-CAJs_vhu.mjs").then((n) => n.i);
16195
+ const { pushStageSecrets } = await Promise.resolve().then(() => transfer_exports);
16183
16196
  const { workspace } = await loadWorkspaceConfig();
16184
16197
  const { addedKeys } = await pushStageSecrets(workspace, options.stage, { profile: options.profile });
16185
16198
  for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
@@ -16194,7 +16207,7 @@ program.command("secrets:pull").description("Bring a deployed stage's secrets fr
16194
16207
  const globalOptions = program.opts();
16195
16208
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16196
16209
  const { loadWorkspaceConfig } = await import("./config.mjs");
16197
- const { pullStageSecrets } = await import("./transfer-CAJs_vhu.mjs").then((n) => n.i);
16210
+ const { pullStageSecrets } = await Promise.resolve().then(() => transfer_exports);
16198
16211
  const { workspace } = await loadWorkspaceConfig();
16199
16212
  const { addedKeys } = await pullStageSecrets(workspace, options.stage, { profile: options.profile });
16200
16213
  for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
@@ -16209,7 +16222,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16209
16222
  const globalOptions = program.opts();
16210
16223
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16211
16224
  const { loadWorkspaceConfig } = await import("./config.mjs");
16212
- const { reconcileMissingSecrets } = await import("./reconcile-gb1lz1Dk.mjs").then((n) => n.n);
16225
+ const { reconcileMissingSecrets } = await import("./reconcile-DhbCvh4K.mjs").then((n) => n.n);
16213
16226
  const { readStageSecrets, writeStageSecrets } = await import("./storage-TP0x118M.mjs").then((n) => n.a);
16214
16227
  const { workspace } = await loadWorkspaceConfig();
16215
16228
  const stage = options.stage ?? workspace.stages.local;
@@ -16218,7 +16231,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16218
16231
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
16219
16232
  process.exit(1);
16220
16233
  }
16221
- const { derivedContainers } = await import("./credentials-DI9kJcaP.mjs").then((n) => n.g);
16234
+ const { derivedContainers } = await import("./workspace-cBobQVoA.mjs").then((n) => n.a);
16222
16235
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
16223
16236
  if (!result) {
16224
16237
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -16277,7 +16290,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
16277
16290
  try {
16278
16291
  const globalOptions = program.opts();
16279
16292
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16280
- const { deployGithubCommand } = await import("./github-D5SBlOx5.mjs");
16293
+ const { deployGithubCommand } = await import("./github-zkzu-ka2.mjs");
16281
16294
  await deployGithubCommand(options);
16282
16295
  } catch (error) {
16283
16296
  console.error(formatError(error));
@@ -16402,6 +16415,6 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
16402
16415
  });
16403
16416
  program.parse();
16404
16417
  //#endregion
16405
- export { getHostingerToken as n, encryption_exports as t };
16418
+ export { getHostingerToken as i, isRemoteStore as n, encryption_exports as r, pushStageSecrets as t };
16406
16419
 
16407
16420
  //# sourceMappingURL=index.mjs.map