@geekmidas/cli 0.46.0 → 0.47.0

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 (32) hide show
  1. package/dist/config.d.cts +1 -1
  2. package/dist/config.d.mts +1 -1
  3. package/dist/dokploy-api-4a6h35VY.cjs +3 -0
  4. package/dist/{dokploy-api-D8a0eQQB.cjs → dokploy-api-BnX2OxyF.cjs} +12 -1
  5. package/dist/{dokploy-api-D8a0eQQB.cjs.map → dokploy-api-BnX2OxyF.cjs.map} +1 -1
  6. package/dist/{dokploy-api-b6usLLKk.mjs → dokploy-api-CMWlWq7-.mjs} +12 -1
  7. package/dist/{dokploy-api-b6usLLKk.mjs.map → dokploy-api-CMWlWq7-.mjs.map} +1 -1
  8. package/dist/dokploy-api-DQvi9iZa.mjs +3 -0
  9. package/dist/{index-BtnjoghR.d.mts → index-A70abJ1m.d.mts} +60 -2
  10. package/dist/index-A70abJ1m.d.mts.map +1 -0
  11. package/dist/{index-c89X2mi2.d.cts → index-pOA56MWT.d.cts} +60 -2
  12. package/dist/index-pOA56MWT.d.cts.map +1 -0
  13. package/dist/index.cjs +675 -235
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.mjs +675 -235
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/workspace/index.d.cts +1 -1
  18. package/dist/workspace/index.d.mts +1 -1
  19. package/dist/workspace-CaVW6j2q.cjs.map +1 -1
  20. package/dist/workspace-DLFRaDc-.mjs.map +1 -1
  21. package/package.json +2 -2
  22. package/src/auth/credentials.ts +66 -0
  23. package/src/deploy/dns/hostinger-api.ts +258 -0
  24. package/src/deploy/dns/index.ts +399 -0
  25. package/src/deploy/dokploy-api.ts +12 -0
  26. package/src/deploy/index.ts +104 -35
  27. package/src/workspace/types.ts +64 -1
  28. package/tsconfig.tsbuildinfo +1 -1
  29. package/dist/dokploy-api-C1JgU9Vr.mjs +0 -3
  30. package/dist/dokploy-api-Cpq_tLSz.cjs +0 -3
  31. package/dist/index-BtnjoghR.d.mts.map +0 -1
  32. package/dist/index-c89X2mi2.d.cts.map +0 -1
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import { __require, getAppBuildOrder, getDependencyEnvVars, getDeployTargetError
3
3
  import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig } from "./config-C3LSBNSl.mjs";
4
4
  import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, OpenApiTsGenerator, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-C3C-BzIZ.mjs";
5
5
  import { getKeyPath, maskPassword, readStageSecrets, secretsExist, setCustomSecret, toEmbeddableSecrets, writeStageSecrets } from "./storage-Dhst7BhI.mjs";
6
- import { DokployApi } from "./dokploy-api-b6usLLKk.mjs";
6
+ import { DokployApi } from "./dokploy-api-CMWlWq7-.mjs";
7
7
  import { encryptSecrets } from "./encryption-BC4MAODn.mjs";
8
8
  import { generateReactQueryCommand } from "./openapi-react-query-ZoP9DPbY.mjs";
9
9
  import { createRequire } from "node:module";
@@ -23,12 +23,13 @@ import { Cron } from "@geekmidas/constructs/crons";
23
23
  import { Function } from "@geekmidas/constructs/functions";
24
24
  import { Subscriber } from "@geekmidas/constructs/subscribers";
25
25
  import { createHash, randomBytes } from "node:crypto";
26
+ import { lookup } from "node:dns/promises";
26
27
  import { pathToFileURL } from "node:url";
27
28
  import prompts from "prompts";
28
29
 
29
30
  //#region package.json
30
31
  var name = "@geekmidas/cli";
31
- var version = "0.46.0";
32
+ var version = "0.47.0";
32
33
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
33
34
  var private$1 = false;
34
35
  var type = "module";
@@ -219,15 +220,40 @@ async function getDokployRegistryId(options) {
219
220
  const stored = await getDokployCredentials(options);
220
221
  return stored?.registryId ?? void 0;
221
222
  }
223
+ /**
224
+ * Store Hostinger API token
225
+ *
226
+ * @param token - API token from hpanel.hostinger.com/profile/api
227
+ */
228
+ async function storeHostingerToken(token, options) {
229
+ const credentials = await readCredentials(options);
230
+ credentials.hostinger = {
231
+ token,
232
+ storedAt: (/* @__PURE__ */ new Date()).toISOString()
233
+ };
234
+ await writeCredentials(credentials, options);
235
+ }
236
+ /**
237
+ * Get stored Hostinger API token
238
+ *
239
+ * Checks environment variable first (HOSTINGER_API_TOKEN),
240
+ * then falls back to stored credentials.
241
+ */
242
+ async function getHostingerToken(options) {
243
+ const envToken = process.env.HOSTINGER_API_TOKEN;
244
+ if (envToken) return envToken;
245
+ const credentials = await readCredentials(options);
246
+ return credentials.hostinger?.token ?? null;
247
+ }
222
248
 
223
249
  //#endregion
224
250
  //#region src/auth/index.ts
225
- const logger$10 = console;
251
+ const logger$11 = console;
226
252
  /**
227
253
  * Validate Dokploy token by making a test API call
228
254
  */
229
255
  async function validateDokployToken(endpoint, token) {
230
- const { DokployApi: DokployApi$1 } = await import("./dokploy-api-C1JgU9Vr.mjs");
256
+ const { DokployApi: DokployApi$1 } = await import("./dokploy-api-DQvi9iZa.mjs");
231
257
  const api = new DokployApi$1({
232
258
  baseUrl: endpoint,
233
259
  token
@@ -290,36 +316,36 @@ async function prompt$1(message, hidden = false) {
290
316
  async function loginCommand(options) {
291
317
  const { service, token: providedToken, endpoint: providedEndpoint } = options;
292
318
  if (service === "dokploy") {
293
- logger$10.log("\n🔐 Logging in to Dokploy...\n");
319
+ logger$11.log("\n🔐 Logging in to Dokploy...\n");
294
320
  let endpoint = providedEndpoint;
295
321
  if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
296
322
  endpoint = endpoint.replace(/\/$/, "");
297
323
  try {
298
324
  new URL(endpoint);
299
325
  } catch {
300
- logger$10.error("Invalid URL format");
326
+ logger$11.error("Invalid URL format");
301
327
  process.exit(1);
302
328
  }
303
329
  let token = providedToken;
304
330
  if (!token) {
305
- logger$10.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
331
+ logger$11.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
306
332
  token = await prompt$1("API Token: ", true);
307
333
  }
308
334
  if (!token) {
309
- logger$10.error("Token is required");
335
+ logger$11.error("Token is required");
310
336
  process.exit(1);
311
337
  }
312
- logger$10.log("\nValidating credentials...");
338
+ logger$11.log("\nValidating credentials...");
313
339
  const isValid = await validateDokployToken(endpoint, token);
314
340
  if (!isValid) {
315
- logger$10.error("\n✗ Invalid credentials. Please check your token and try again.");
341
+ logger$11.error("\n✗ Invalid credentials. Please check your token and try again.");
316
342
  process.exit(1);
317
343
  }
318
344
  await storeDokployCredentials(token, endpoint);
319
- logger$10.log("\n✓ Successfully logged in to Dokploy!");
320
- logger$10.log(` Endpoint: ${endpoint}`);
321
- logger$10.log(` Credentials stored in: ${getCredentialsPath()}`);
322
- logger$10.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
345
+ logger$11.log("\n✓ Successfully logged in to Dokploy!");
346
+ logger$11.log(` Endpoint: ${endpoint}`);
347
+ logger$11.log(` Credentials stored in: ${getCredentialsPath()}`);
348
+ logger$11.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
323
349
  }
324
350
  }
325
351
  /**
@@ -329,28 +355,28 @@ async function logoutCommand(options) {
329
355
  const { service = "dokploy" } = options;
330
356
  if (service === "all") {
331
357
  const dokployRemoved = await removeDokployCredentials();
332
- if (dokployRemoved) logger$10.log("\n✓ Logged out from all services");
333
- else logger$10.log("\nNo stored credentials found");
358
+ if (dokployRemoved) logger$11.log("\n✓ Logged out from all services");
359
+ else logger$11.log("\nNo stored credentials found");
334
360
  return;
335
361
  }
336
362
  if (service === "dokploy") {
337
363
  const removed = await removeDokployCredentials();
338
- if (removed) logger$10.log("\n✓ Logged out from Dokploy");
339
- else logger$10.log("\nNo Dokploy credentials found");
364
+ if (removed) logger$11.log("\n✓ Logged out from Dokploy");
365
+ else logger$11.log("\nNo Dokploy credentials found");
340
366
  }
341
367
  }
342
368
  /**
343
369
  * Show current login status
344
370
  */
345
371
  async function whoamiCommand() {
346
- logger$10.log("\n📋 Current credentials:\n");
372
+ logger$11.log("\n📋 Current credentials:\n");
347
373
  const dokploy = await getDokployCredentials();
348
374
  if (dokploy) {
349
- logger$10.log(" Dokploy:");
350
- logger$10.log(` Endpoint: ${dokploy.endpoint}`);
351
- logger$10.log(` Token: ${maskToken(dokploy.token)}`);
352
- } else logger$10.log(" Dokploy: Not logged in");
353
- logger$10.log(`\n Credentials file: ${getCredentialsPath()}`);
375
+ logger$11.log(" Dokploy:");
376
+ logger$11.log(` Endpoint: ${dokploy.endpoint}`);
377
+ logger$11.log(` Token: ${maskToken(dokploy.token)}`);
378
+ } else logger$11.log(" Dokploy: Not logged in");
379
+ logger$11.log(`\n Credentials file: ${getCredentialsPath()}`);
354
380
  }
355
381
  /**
356
382
  * Mask a token for display
@@ -436,7 +462,7 @@ function isEnabled(config$1) {
436
462
  var CronGenerator = class extends ConstructGenerator {
437
463
  async build(context, constructs, outputDir, options) {
438
464
  const provider = options?.provider || "aws-lambda";
439
- const logger$11 = console;
465
+ const logger$12 = console;
440
466
  const cronInfos = [];
441
467
  if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
442
468
  const cronsDir = join(outputDir, "crons");
@@ -451,7 +477,7 @@ var CronGenerator = class extends ConstructGenerator {
451
477
  memorySize: construct.memorySize,
452
478
  environment: await construct.getEnvironment()
453
479
  });
454
- logger$11.log(`Generated cron handler: ${key}`);
480
+ logger$12.log(`Generated cron handler: ${key}`);
455
481
  }
456
482
  return cronInfos;
457
483
  }
@@ -487,7 +513,7 @@ var FunctionGenerator = class extends ConstructGenerator {
487
513
  }
488
514
  async build(context, constructs, outputDir, options) {
489
515
  const provider = options?.provider || "aws-lambda";
490
- const logger$11 = console;
516
+ const logger$12 = console;
491
517
  const functionInfos = [];
492
518
  if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
493
519
  const functionsDir = join(outputDir, "functions");
@@ -501,7 +527,7 @@ var FunctionGenerator = class extends ConstructGenerator {
501
527
  memorySize: construct.memorySize,
502
528
  environment: await construct.getEnvironment()
503
529
  });
504
- logger$11.log(`Generated function handler: ${key}`);
530
+ logger$12.log(`Generated function handler: ${key}`);
505
531
  }
506
532
  return functionInfos;
507
533
  }
@@ -534,11 +560,11 @@ var SubscriberGenerator = class extends ConstructGenerator {
534
560
  }
535
561
  async build(context, constructs, outputDir, options) {
536
562
  const provider = options?.provider || "aws-lambda";
537
- const logger$11 = console;
563
+ const logger$12 = console;
538
564
  const subscriberInfos = [];
539
565
  if (provider === "server") {
540
566
  await this.generateServerSubscribersFile(outputDir, constructs);
541
- logger$11.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
567
+ logger$12.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
542
568
  return subscriberInfos;
543
569
  }
544
570
  if (constructs.length === 0) return subscriberInfos;
@@ -555,7 +581,7 @@ var SubscriberGenerator = class extends ConstructGenerator {
555
581
  memorySize: construct.memorySize,
556
582
  environment: await construct.getEnvironment()
557
583
  });
558
- logger$11.log(`Generated subscriber handler: ${key}`);
584
+ logger$12.log(`Generated subscriber handler: ${key}`);
559
585
  }
560
586
  return subscriberInfos;
561
587
  }
@@ -721,7 +747,7 @@ export async function setupSubscribers(
721
747
 
722
748
  //#endregion
723
749
  //#region src/workspace/client-generator.ts
724
- const logger$9 = console;
750
+ const logger$10 = console;
725
751
  /**
726
752
  * Cache of OpenAPI spec hashes to detect changes.
727
753
  */
@@ -841,7 +867,7 @@ ${spec.content}
841
867
  * Generate clients for all frontend apps in the workspace.
842
868
  */
843
869
  async function generateAllClients(workspace, options = {}) {
844
- const log = options.silent ? () => {} : logger$9.log.bind(logger$9);
870
+ const log = options.silent ? () => {} : logger$10.log.bind(logger$10);
845
871
  const allResults = [];
846
872
  for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "frontend" && app.dependencies.length > 0) {
847
873
  const results = await generateClientForFrontend(workspace, appName, { force: options.force });
@@ -863,7 +889,7 @@ function getDependentFrontends(workspace, backendAppName) {
863
889
 
864
890
  //#endregion
865
891
  //#region src/dev/index.ts
866
- const logger$8 = console;
892
+ const logger$9 = console;
867
893
  /**
868
894
  * Load environment files
869
895
  * @internal Exported for testing
@@ -914,7 +940,7 @@ async function findAvailablePort(preferredPort, maxAttempts = 10) {
914
940
  for (let i = 0; i < maxAttempts; i++) {
915
941
  const port = preferredPort + i;
916
942
  if (await isPortAvailable(port)) return port;
917
- logger$8.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
943
+ logger$9.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
918
944
  }
919
945
  throw new Error(`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`);
920
946
  }
@@ -1015,7 +1041,7 @@ function getProductionConfigFromGkm(config$1) {
1015
1041
  async function devCommand(options) {
1016
1042
  if (options.entry) return entryDevCommand(options);
1017
1043
  const defaultEnv = loadEnvFiles(".env");
1018
- if (defaultEnv.loaded.length > 0) logger$8.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1044
+ if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1019
1045
  const appName = getAppNameFromCwd();
1020
1046
  let config$1;
1021
1047
  let appRoot = process.cwd();
@@ -1029,9 +1055,9 @@ async function devCommand(options) {
1029
1055
  secretsRoot = appConfig.workspaceRoot;
1030
1056
  workspaceAppName = appConfig.appName;
1031
1057
  workspaceAppPort = appConfig.app.port;
1032
- logger$8.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
1058
+ logger$9.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
1033
1059
  if (appConfig.app.entry) {
1034
- logger$8.log(`📄 Using entry point: ${appConfig.app.entry}`);
1060
+ logger$9.log(`📄 Using entry point: ${appConfig.app.entry}`);
1035
1061
  return entryDevCommand({
1036
1062
  ...options,
1037
1063
  entry: appConfig.app.entry,
@@ -1042,7 +1068,7 @@ async function devCommand(options) {
1042
1068
  } catch {
1043
1069
  const loadedConfig = await loadWorkspaceConfig();
1044
1070
  if (loadedConfig.type === "workspace") {
1045
- logger$8.log("📦 Detected workspace configuration");
1071
+ logger$9.log("📦 Detected workspace configuration");
1046
1072
  return workspaceDevCommand(loadedConfig.workspace, options);
1047
1073
  }
1048
1074
  config$1 = loadedConfig.raw;
@@ -1050,34 +1076,34 @@ async function devCommand(options) {
1050
1076
  else {
1051
1077
  const loadedConfig = await loadWorkspaceConfig();
1052
1078
  if (loadedConfig.type === "workspace") {
1053
- logger$8.log("📦 Detected workspace configuration");
1079
+ logger$9.log("📦 Detected workspace configuration");
1054
1080
  return workspaceDevCommand(loadedConfig.workspace, options);
1055
1081
  }
1056
1082
  config$1 = loadedConfig.raw;
1057
1083
  }
1058
1084
  if (config$1.env) {
1059
1085
  const { loaded, missing } = loadEnvFiles(config$1.env, appRoot);
1060
- if (loaded.length > 0) logger$8.log(`📦 Loaded env: ${loaded.join(", ")}`);
1061
- if (missing.length > 0) logger$8.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
1086
+ if (loaded.length > 0) logger$9.log(`📦 Loaded env: ${loaded.join(", ")}`);
1087
+ if (missing.length > 0) logger$9.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
1062
1088
  }
1063
1089
  const resolved = resolveProviders(config$1, { provider: "server" });
1064
- logger$8.log("🚀 Starting development server...");
1065
- logger$8.log(`Loading routes from: ${config$1.routes}`);
1066
- if (config$1.functions) logger$8.log(`Loading functions from: ${config$1.functions}`);
1067
- if (config$1.crons) logger$8.log(`Loading crons from: ${config$1.crons}`);
1068
- if (config$1.subscribers) logger$8.log(`Loading subscribers from: ${config$1.subscribers}`);
1069
- logger$8.log(`Using envParser: ${config$1.envParser}`);
1090
+ logger$9.log("🚀 Starting development server...");
1091
+ logger$9.log(`Loading routes from: ${config$1.routes}`);
1092
+ if (config$1.functions) logger$9.log(`Loading functions from: ${config$1.functions}`);
1093
+ if (config$1.crons) logger$9.log(`Loading crons from: ${config$1.crons}`);
1094
+ if (config$1.subscribers) logger$9.log(`Loading subscribers from: ${config$1.subscribers}`);
1095
+ logger$9.log(`Using envParser: ${config$1.envParser}`);
1070
1096
  const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config$1.envParser, "envParser");
1071
1097
  const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config$1.logger, "logger");
1072
1098
  const telescope = normalizeTelescopeConfig(config$1.telescope);
1073
- if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
1099
+ if (telescope) logger$9.log(`🔭 Telescope enabled at ${telescope.path}`);
1074
1100
  const studio = normalizeStudioConfig(config$1.studio);
1075
- if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
1101
+ if (studio) logger$9.log(`🗄️ Studio enabled at ${studio.path}`);
1076
1102
  const hooks = normalizeHooksConfig(config$1.hooks, appRoot);
1077
- if (hooks) logger$8.log(`🪝 Server hooks enabled from ${config$1.hooks?.server}`);
1103
+ if (hooks) logger$9.log(`🪝 Server hooks enabled from ${config$1.hooks?.server}`);
1078
1104
  const openApiConfig = resolveOpenApiConfig(config$1);
1079
1105
  const enableOpenApi = openApiConfig.enabled || resolved.enableOpenApi;
1080
- if (enableOpenApi) logger$8.log(`📄 OpenAPI output: ${OPENAPI_OUTPUT_PATH}`);
1106
+ if (enableOpenApi) logger$9.log(`📄 OpenAPI output: ${OPENAPI_OUTPUT_PATH}`);
1081
1107
  const buildContext = {
1082
1108
  envParserPath,
1083
1109
  envParserImportPattern,
@@ -1097,7 +1123,7 @@ async function devCommand(options) {
1097
1123
  await mkdir(secretsDir, { recursive: true });
1098
1124
  secretsJsonPath = join(secretsDir, "dev-secrets.json");
1099
1125
  await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
1100
- logger$8.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
1126
+ logger$9.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
1101
1127
  }
1102
1128
  const devServer = new DevServer(resolved.providers[0], options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? false, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath);
1103
1129
  await devServer.start();
@@ -1115,7 +1141,7 @@ async function devCommand(options) {
1115
1141
  ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []
1116
1142
  ].flat().filter((p) => typeof p === "string");
1117
1143
  const normalizedPatterns = watchPatterns.map((p) => p.startsWith("./") ? p.slice(2) : p);
1118
- logger$8.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
1144
+ logger$9.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
1119
1145
  const resolvedFiles = await fg(normalizedPatterns, {
1120
1146
  cwd: appRoot,
1121
1147
  absolute: false,
@@ -1125,7 +1151,7 @@ async function devCommand(options) {
1125
1151
  const parts = f.split("/");
1126
1152
  return parts.slice(0, -1).join("/");
1127
1153
  }))];
1128
- logger$8.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
1154
+ logger$9.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
1129
1155
  const watcher = chokidar.watch([...resolvedFiles, ...dirsToWatch], {
1130
1156
  ignored: /(^|[/\\])\../,
1131
1157
  persistent: true,
@@ -1133,24 +1159,24 @@ async function devCommand(options) {
1133
1159
  cwd: appRoot
1134
1160
  });
1135
1161
  watcher.on("ready", () => {
1136
- logger$8.log("🔍 File watcher ready");
1162
+ logger$9.log("🔍 File watcher ready");
1137
1163
  });
1138
1164
  watcher.on("error", (error) => {
1139
- logger$8.error("❌ Watcher error:", error);
1165
+ logger$9.error("❌ Watcher error:", error);
1140
1166
  });
1141
1167
  let rebuildTimeout = null;
1142
1168
  watcher.on("change", async (path) => {
1143
- logger$8.log(`📝 File changed: ${path}`);
1169
+ logger$9.log(`📝 File changed: ${path}`);
1144
1170
  if (rebuildTimeout) clearTimeout(rebuildTimeout);
1145
1171
  rebuildTimeout = setTimeout(async () => {
1146
1172
  try {
1147
- logger$8.log("🔄 Rebuilding...");
1173
+ logger$9.log("🔄 Rebuilding...");
1148
1174
  await buildServer(config$1, buildContext, resolved.providers[0], enableOpenApi, appRoot);
1149
1175
  if (enableOpenApi) await generateOpenApi(config$1, { silent: true });
1150
- logger$8.log("✅ Rebuild complete, restarting server...");
1176
+ logger$9.log("✅ Rebuild complete, restarting server...");
1151
1177
  await devServer.restart();
1152
1178
  } catch (error) {
1153
- logger$8.error("❌ Rebuild failed:", error.message);
1179
+ logger$9.error("❌ Rebuild failed:", error.message);
1154
1180
  }
1155
1181
  }, 300);
1156
1182
  });
@@ -1158,9 +1184,9 @@ async function devCommand(options) {
1158
1184
  const shutdown = () => {
1159
1185
  if (isShuttingDown) return;
1160
1186
  isShuttingDown = true;
1161
- logger$8.log("\n🛑 Shutting down...");
1187
+ logger$9.log("\n🛑 Shutting down...");
1162
1188
  Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
1163
- logger$8.error("Error during shutdown:", err);
1189
+ logger$9.error("Error during shutdown:", err);
1164
1190
  }).finally(() => {
1165
1191
  process.exit(0);
1166
1192
  });
@@ -1263,11 +1289,11 @@ async function loadDevSecrets(workspace) {
1263
1289
  for (const stage of stages) if (secretsExist(stage, workspace.root)) {
1264
1290
  const secrets = await readStageSecrets(stage, workspace.root);
1265
1291
  if (secrets) {
1266
- logger$8.log(`🔐 Loading secrets from stage: ${stage}`);
1292
+ logger$9.log(`🔐 Loading secrets from stage: ${stage}`);
1267
1293
  return toEmbeddableSecrets(secrets);
1268
1294
  }
1269
1295
  }
1270
- logger$8.warn("⚠️ Secrets enabled but no dev/development secrets found. Run \"gkm secrets:init --stage dev\"");
1296
+ logger$9.warn("⚠️ Secrets enabled but no dev/development secrets found. Run \"gkm secrets:init --stage dev\"");
1271
1297
  return {};
1272
1298
  }
1273
1299
  /**
@@ -1282,7 +1308,7 @@ async function loadSecretsForApp(secretsRoot, appName) {
1282
1308
  for (const stage of stages) if (secretsExist(stage, secretsRoot)) {
1283
1309
  const stageSecrets = await readStageSecrets(stage, secretsRoot);
1284
1310
  if (stageSecrets) {
1285
- logger$8.log(`🔐 Loading secrets from stage: ${stage}`);
1311
+ logger$9.log(`🔐 Loading secrets from stage: ${stage}`);
1286
1312
  secrets = toEmbeddableSecrets(stageSecrets);
1287
1313
  break;
1288
1314
  }
@@ -1307,20 +1333,20 @@ async function startWorkspaceServices(workspace) {
1307
1333
  if (services.cache) servicesToStart.push("redis");
1308
1334
  if (services.mail) servicesToStart.push("mailpit");
1309
1335
  if (servicesToStart.length === 0) return;
1310
- logger$8.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
1336
+ logger$9.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
1311
1337
  try {
1312
1338
  const composeFile = join(workspace.root, "docker-compose.yml");
1313
1339
  if (!existsSync(composeFile)) {
1314
- logger$8.warn("⚠️ No docker-compose.yml found. Services will not be started.");
1340
+ logger$9.warn("⚠️ No docker-compose.yml found. Services will not be started.");
1315
1341
  return;
1316
1342
  }
1317
1343
  execSync(`docker compose up -d ${servicesToStart.join(" ")}`, {
1318
1344
  cwd: workspace.root,
1319
1345
  stdio: "inherit"
1320
1346
  });
1321
- logger$8.log("✅ Services started");
1347
+ logger$9.log("✅ Services started");
1322
1348
  } catch (error) {
1323
- logger$8.error("❌ Failed to start services:", error.message);
1349
+ logger$9.error("❌ Failed to start services:", error.message);
1324
1350
  throw error;
1325
1351
  }
1326
1352
  }
@@ -1337,40 +1363,40 @@ async function workspaceDevCommand(workspace, options) {
1337
1363
  const appCount = Object.keys(workspace.apps).length;
1338
1364
  const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
1339
1365
  const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "frontend");
1340
- logger$8.log(`\n🚀 Starting workspace: ${workspace.name}`);
1341
- logger$8.log(` ${backendApps.length} backend app(s), ${frontendApps.length} frontend app(s)`);
1366
+ logger$9.log(`\n🚀 Starting workspace: ${workspace.name}`);
1367
+ logger$9.log(` ${backendApps.length} backend app(s), ${frontendApps.length} frontend app(s)`);
1342
1368
  const conflicts = checkPortConflicts(workspace);
1343
1369
  if (conflicts.length > 0) {
1344
- for (const conflict of conflicts) logger$8.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1370
+ for (const conflict of conflicts) logger$9.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1345
1371
  throw new Error("Port conflicts detected. Please assign unique ports to each app.");
1346
1372
  }
1347
1373
  if (frontendApps.length > 0) {
1348
- logger$8.log("\n🔍 Validating frontend apps...");
1374
+ logger$9.log("\n🔍 Validating frontend apps...");
1349
1375
  const validationResults = await validateFrontendApps(workspace);
1350
1376
  let hasErrors = false;
1351
1377
  for (const result of validationResults) {
1352
1378
  if (!result.valid) {
1353
1379
  hasErrors = true;
1354
- logger$8.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1355
- for (const error of result.errors) logger$8.error(` • ${error}`);
1380
+ logger$9.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1381
+ for (const error of result.errors) logger$9.error(` • ${error}`);
1356
1382
  }
1357
- for (const warning of result.warnings) logger$8.warn(` ⚠️ ${result.appName}: ${warning}`);
1383
+ for (const warning of result.warnings) logger$9.warn(` ⚠️ ${result.appName}: ${warning}`);
1358
1384
  }
1359
1385
  if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
1360
- logger$8.log("✅ Frontend apps validated");
1386
+ logger$9.log("✅ Frontend apps validated");
1361
1387
  }
1362
1388
  if (frontendApps.length > 0) {
1363
1389
  const clientResults = await generateAllClients(workspace, { force: true });
1364
1390
  const generatedCount = clientResults.filter((r) => r.generated).length;
1365
- if (generatedCount > 0) logger$8.log(`\n📦 Generated ${generatedCount} API client(s)`);
1391
+ if (generatedCount > 0) logger$9.log(`\n📦 Generated ${generatedCount} API client(s)`);
1366
1392
  }
1367
1393
  await startWorkspaceServices(workspace);
1368
1394
  const secretsEnv = await loadDevSecrets(workspace);
1369
- if (Object.keys(secretsEnv).length > 0) logger$8.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
1395
+ if (Object.keys(secretsEnv).length > 0) logger$9.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
1370
1396
  const dependencyEnv = generateAllDependencyEnvVars(workspace);
1371
1397
  if (Object.keys(dependencyEnv).length > 0) {
1372
- logger$8.log("📡 Dependency URLs:");
1373
- for (const [key, value] of Object.entries(dependencyEnv)) logger$8.log(` ${key}=${value}`);
1398
+ logger$9.log("📡 Dependency URLs:");
1399
+ for (const [key, value] of Object.entries(dependencyEnv)) logger$9.log(` ${key}=${value}`);
1374
1400
  }
1375
1401
  let turboFilter = [];
1376
1402
  if (options.app) {
@@ -1379,18 +1405,18 @@ async function workspaceDevCommand(workspace, options) {
1379
1405
  throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
1380
1406
  }
1381
1407
  turboFilter = ["--filter", options.app];
1382
- logger$8.log(`\n🎯 Running single app: ${options.app}`);
1408
+ logger$9.log(`\n🎯 Running single app: ${options.app}`);
1383
1409
  } else if (options.filter) {
1384
1410
  turboFilter = ["--filter", options.filter];
1385
- logger$8.log(`\n🔍 Using filter: ${options.filter}`);
1386
- } else logger$8.log(`\n🎯 Running all ${appCount} apps`);
1411
+ logger$9.log(`\n🔍 Using filter: ${options.filter}`);
1412
+ } else logger$9.log(`\n🎯 Running all ${appCount} apps`);
1387
1413
  const buildOrder = getAppBuildOrder(workspace);
1388
- logger$8.log("\n📋 Apps (in dependency order):");
1414
+ logger$9.log("\n📋 Apps (in dependency order):");
1389
1415
  for (const appName of buildOrder) {
1390
1416
  const app = workspace.apps[appName];
1391
1417
  if (!app) continue;
1392
1418
  const deps = app.dependencies.length > 0 ? ` (depends on: ${app.dependencies.join(", ")})` : "";
1393
- logger$8.log(` ${app.type === "backend" ? "🔧" : "🌐"} ${appName} → http://localhost:${app.port}${deps}`);
1419
+ logger$9.log(` ${app.type === "backend" ? "🔧" : "🌐"} ${appName} → http://localhost:${app.port}${deps}`);
1394
1420
  }
1395
1421
  const configFiles = [
1396
1422
  "gkm.config.ts",
@@ -1412,7 +1438,7 @@ async function workspaceDevCommand(workspace, options) {
1412
1438
  NODE_ENV: "development",
1413
1439
  ...configPath ? { GKM_CONFIG_PATH: configPath } : {}
1414
1440
  };
1415
- logger$8.log("\n🏃 Starting turbo run dev...\n");
1441
+ logger$9.log("\n🏃 Starting turbo run dev...\n");
1416
1442
  const turboProcess = spawn("pnpm", [
1417
1443
  "turbo",
1418
1444
  "run",
@@ -1444,7 +1470,7 @@ async function workspaceDevCommand(workspace, options) {
1444
1470
  onlyFiles: true
1445
1471
  });
1446
1472
  if (resolvedFiles.length > 0) {
1447
- logger$8.log(`\n👀 Watching ${resolvedFiles.length} endpoint file(s) for schema changes`);
1473
+ logger$9.log(`\n👀 Watching ${resolvedFiles.length} endpoint file(s) for schema changes`);
1448
1474
  endpointWatcher = chokidar.watch(resolvedFiles, {
1449
1475
  ignored: /(^|[/\\])\../,
1450
1476
  persistent: true,
@@ -1472,12 +1498,12 @@ async function workspaceDevCommand(workspace, options) {
1472
1498
  for (const frontend of dependents) affectedFrontends.add(frontend);
1473
1499
  }
1474
1500
  if (affectedFrontends.size === 0) return;
1475
- logger$8.log(`\n🔄 Detected schema change in ${changedBackends.join(", ")}`);
1501
+ logger$9.log(`\n🔄 Detected schema change in ${changedBackends.join(", ")}`);
1476
1502
  for (const frontend of affectedFrontends) try {
1477
1503
  const results = await generateClientForFrontend(workspace, frontend);
1478
- for (const result of results) if (result.generated) logger$8.log(` 📦 Regenerated client for ${result.frontendApp} (${result.endpointCount} endpoints)`);
1504
+ for (const result of results) if (result.generated) logger$9.log(` 📦 Regenerated client for ${result.frontendApp} (${result.endpointCount} endpoints)`);
1479
1505
  } catch (error) {
1480
- logger$8.error(` ❌ Failed to regenerate client for ${frontend}: ${error.message}`);
1506
+ logger$9.error(` ❌ Failed to regenerate client for ${frontend}: ${error.message}`);
1481
1507
  }
1482
1508
  }, 500);
1483
1509
  });
@@ -1488,7 +1514,7 @@ async function workspaceDevCommand(workspace, options) {
1488
1514
  const shutdown = () => {
1489
1515
  if (isShuttingDown) return;
1490
1516
  isShuttingDown = true;
1491
- logger$8.log("\n🛑 Shutting down workspace...");
1517
+ logger$9.log("\n🛑 Shutting down workspace...");
1492
1518
  if (endpointWatcher) endpointWatcher.close().catch(() => {});
1493
1519
  if (turboProcess.pid) try {
1494
1520
  process.kill(-turboProcess.pid, "SIGTERM");
@@ -1503,7 +1529,7 @@ async function workspaceDevCommand(workspace, options) {
1503
1529
  process.on("SIGTERM", shutdown);
1504
1530
  return new Promise((resolve$1, reject) => {
1505
1531
  turboProcess.on("error", (error) => {
1506
- logger$8.error("❌ Turbo error:", error);
1532
+ logger$9.error("❌ Turbo error:", error);
1507
1533
  reject(error);
1508
1534
  });
1509
1535
  turboProcess.on("exit", (code) => {
@@ -1615,7 +1641,7 @@ async function prepareEntryCredentials(options) {
1615
1641
  secretsRoot = appConfig.workspaceRoot;
1616
1642
  appName = appConfig.appName;
1617
1643
  } catch (error) {
1618
- logger$8.log(`⚠️ Could not load workspace config: ${error.message}`);
1644
+ logger$9.log(`⚠️ Could not load workspace config: ${error.message}`);
1619
1645
  secretsRoot = findSecretsRoot(cwd);
1620
1646
  appName = getAppNameFromCwd(cwd) ?? void 0;
1621
1647
  }
@@ -1645,11 +1671,11 @@ async function entryDevCommand(options) {
1645
1671
  const entryPath = resolve(process.cwd(), entry);
1646
1672
  if (!existsSync(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
1647
1673
  const defaultEnv = loadEnvFiles(".env");
1648
- if (defaultEnv.loaded.length > 0) logger$8.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1674
+ if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1649
1675
  const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
1650
- if (appName) logger$8.log(`📦 App: ${appName} (port ${resolvedPort})`);
1651
- logger$8.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1652
- if (Object.keys(credentials).length > 1) logger$8.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
1676
+ if (appName) logger$9.log(`📦 App: ${appName} (port ${resolvedPort})`);
1677
+ logger$9.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1678
+ if (Object.keys(credentials).length > 1) logger$9.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
1653
1679
  const wrapperDir = join(process.cwd(), ".gkm");
1654
1680
  await mkdir(wrapperDir, { recursive: true });
1655
1681
  const wrapperPath = join(wrapperDir, "entry-wrapper.ts");
@@ -1660,7 +1686,7 @@ async function entryDevCommand(options) {
1660
1686
  const shutdown = () => {
1661
1687
  if (isShuttingDown) return;
1662
1688
  isShuttingDown = true;
1663
- logger$8.log("\n🛑 Shutting down...");
1689
+ logger$9.log("\n🛑 Shutting down...");
1664
1690
  runner.stop();
1665
1691
  process.exit(0);
1666
1692
  };
@@ -1692,14 +1718,14 @@ var EntryRunner = class {
1692
1718
  });
1693
1719
  let restartTimeout = null;
1694
1720
  this.watcher.on("change", (path) => {
1695
- logger$8.log(`📝 File changed: ${path}`);
1721
+ logger$9.log(`📝 File changed: ${path}`);
1696
1722
  if (restartTimeout) clearTimeout(restartTimeout);
1697
1723
  restartTimeout = setTimeout(async () => {
1698
- logger$8.log("🔄 Restarting...");
1724
+ logger$9.log("🔄 Restarting...");
1699
1725
  await this.restart();
1700
1726
  }, 300);
1701
1727
  });
1702
- logger$8.log(`👀 Watching for changes in: ${watchDir}`);
1728
+ logger$9.log(`👀 Watching for changes in: ${watchDir}`);
1703
1729
  }
1704
1730
  }
1705
1731
  async runProcess() {
@@ -1714,14 +1740,14 @@ var EntryRunner = class {
1714
1740
  });
1715
1741
  this.isRunning = true;
1716
1742
  this.childProcess.on("error", (error) => {
1717
- logger$8.error("❌ Process error:", error);
1743
+ logger$9.error("❌ Process error:", error);
1718
1744
  });
1719
1745
  this.childProcess.on("exit", (code) => {
1720
- if (code !== null && code !== 0 && code !== 143) logger$8.error(`❌ Process exited with code ${code}`);
1746
+ if (code !== null && code !== 0 && code !== 143) logger$9.error(`❌ Process exited with code ${code}`);
1721
1747
  this.isRunning = false;
1722
1748
  });
1723
1749
  await new Promise((resolve$1) => setTimeout(resolve$1, 500));
1724
- if (this.isRunning) logger$8.log(`\n🎉 Running at http://localhost:${this.port}`);
1750
+ if (this.isRunning) logger$9.log(`\n🎉 Running at http://localhost:${this.port}`);
1725
1751
  }
1726
1752
  async restart() {
1727
1753
  this.stopProcess();
@@ -1771,11 +1797,11 @@ var DevServer = class {
1771
1797
  this.actualPort = this.requestedPort;
1772
1798
  } else {
1773
1799
  this.actualPort = await findAvailablePort(this.requestedPort);
1774
- if (this.actualPort !== this.requestedPort) logger$8.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
1800
+ if (this.actualPort !== this.requestedPort) logger$9.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
1775
1801
  }
1776
1802
  const serverEntryPath = join(this.appRoot, ".gkm", this.provider, "server.ts");
1777
1803
  await this.createServerEntry();
1778
- logger$8.log(`\n✨ Starting server on port ${this.actualPort}...`);
1804
+ logger$9.log(`\n✨ Starting server on port ${this.actualPort}...`);
1779
1805
  this.serverProcess = spawn("npx", [
1780
1806
  "tsx",
1781
1807
  serverEntryPath,
@@ -1791,18 +1817,18 @@ var DevServer = class {
1791
1817
  });
1792
1818
  this.isRunning = true;
1793
1819
  this.serverProcess.on("error", (error) => {
1794
- logger$8.error("❌ Server error:", error);
1820
+ logger$9.error("❌ Server error:", error);
1795
1821
  });
1796
1822
  this.serverProcess.on("exit", (code, signal) => {
1797
- if (code !== null && code !== 0 && signal !== "SIGTERM") logger$8.error(`❌ Server exited with code ${code}`);
1823
+ if (code !== null && code !== 0 && signal !== "SIGTERM") logger$9.error(`❌ Server exited with code ${code}`);
1798
1824
  this.isRunning = false;
1799
1825
  });
1800
1826
  await new Promise((resolve$1) => setTimeout(resolve$1, 1e3));
1801
1827
  if (this.isRunning) {
1802
- logger$8.log(`\n🎉 Server running at http://localhost:${this.actualPort}`);
1803
- if (this.enableOpenApi) logger$8.log(`📚 API Docs available at http://localhost:${this.actualPort}/__docs`);
1804
- if (this.telescope) logger$8.log(`🔭 Telescope available at http://localhost:${this.actualPort}${this.telescope.path}`);
1805
- if (this.studio) logger$8.log(`🗄️ Studio available at http://localhost:${this.actualPort}${this.studio.path}`);
1828
+ logger$9.log(`\n🎉 Server running at http://localhost:${this.actualPort}`);
1829
+ if (this.enableOpenApi) logger$9.log(`📚 API Docs available at http://localhost:${this.actualPort}/__docs`);
1830
+ if (this.telescope) logger$9.log(`🔭 Telescope available at http://localhost:${this.actualPort}${this.telescope.path}`);
1831
+ if (this.studio) logger$9.log(`🗄️ Studio available at http://localhost:${this.actualPort}${this.studio.path}`);
1806
1832
  }
1807
1833
  }
1808
1834
  async stop() {
@@ -1910,18 +1936,18 @@ async function execCommand(commandArgs, options = {}) {
1910
1936
  const cwd = options.cwd ?? process.cwd();
1911
1937
  if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
1912
1938
  const defaultEnv = loadEnvFiles(".env");
1913
- if (defaultEnv.loaded.length > 0) logger$8.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1939
+ if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1914
1940
  const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
1915
- if (appName) logger$8.log(`📦 App: ${appName}`);
1941
+ if (appName) logger$9.log(`📦 App: ${appName}`);
1916
1942
  const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
1917
- if (secretCount > 0) logger$8.log(`🔐 Loaded ${secretCount} secret(s)`);
1943
+ if (secretCount > 0) logger$9.log(`🔐 Loaded ${secretCount} secret(s)`);
1918
1944
  const preloadDir = join(cwd, ".gkm");
1919
1945
  await mkdir(preloadDir, { recursive: true });
1920
1946
  const preloadPath = join(preloadDir, "credentials-preload.ts");
1921
1947
  await createCredentialsPreload(preloadPath, secretsJsonPath);
1922
1948
  const [cmd, ...args] = commandArgs;
1923
1949
  if (!cmd) throw new Error("No command specified");
1924
- logger$8.log(`🚀 Running: ${commandArgs.join(" ")}`);
1950
+ logger$9.log(`🚀 Running: ${commandArgs.join(" ")}`);
1925
1951
  const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
1926
1952
  const tsxImport = "--import tsx";
1927
1953
  const preloadImport = `--import ${preloadPath}`;
@@ -1942,7 +1968,7 @@ async function execCommand(commandArgs, options = {}) {
1942
1968
  const exitCode = await new Promise((resolve$1) => {
1943
1969
  child.on("close", (code) => resolve$1(code ?? 0));
1944
1970
  child.on("error", (error) => {
1945
- logger$8.error(`Failed to run command: ${error.message}`);
1971
+ logger$9.error(`Failed to run command: ${error.message}`);
1946
1972
  resolve$1(1);
1947
1973
  });
1948
1974
  });
@@ -1951,7 +1977,7 @@ async function execCommand(commandArgs, options = {}) {
1951
1977
 
1952
1978
  //#endregion
1953
1979
  //#region src/build/manifests.ts
1954
- const logger$7 = console;
1980
+ const logger$8 = console;
1955
1981
  async function generateAwsManifest(outputDir, routes, functions, crons, subscribers) {
1956
1982
  const manifestDir = join(outputDir, "manifest");
1957
1983
  await mkdir(manifestDir, { recursive: true });
@@ -1976,8 +2002,8 @@ export type RoutePath = Route['path'];
1976
2002
  `;
1977
2003
  const manifestPath = join(manifestDir, "aws.ts");
1978
2004
  await writeFile(manifestPath, content);
1979
- logger$7.log(`Generated AWS manifest with ${awsRoutes.length} routes, ${functions.length} functions, ${crons.length} crons, ${subscribers.length} subscribers`);
1980
- logger$7.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2005
+ logger$8.log(`Generated AWS manifest with ${awsRoutes.length} routes, ${functions.length} functions, ${crons.length} crons, ${subscribers.length} subscribers`);
2006
+ logger$8.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
1981
2007
  }
1982
2008
  async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
1983
2009
  const manifestDir = join(outputDir, "manifest");
@@ -2008,13 +2034,13 @@ export type RoutePath = Route['path'];
2008
2034
  `;
2009
2035
  const manifestPath = join(manifestDir, "server.ts");
2010
2036
  await writeFile(manifestPath, content);
2011
- logger$7.log(`Generated server manifest with ${serverRoutes.length} routes, ${serverSubscribers.length} subscribers`);
2012
- logger$7.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2037
+ logger$8.log(`Generated server manifest with ${serverRoutes.length} routes, ${serverSubscribers.length} subscribers`);
2038
+ logger$8.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2013
2039
  }
2014
2040
 
2015
2041
  //#endregion
2016
2042
  //#region src/build/index.ts
2017
- const logger$6 = console;
2043
+ const logger$7 = console;
2018
2044
  async function buildCommand(options) {
2019
2045
  const loadedConfig = await loadWorkspaceConfig();
2020
2046
  if (loadedConfig.type === "workspace") {
@@ -2022,7 +2048,7 @@ async function buildCommand(options) {
2022
2048
  const workspaceRoot = resolve(loadedConfig.workspace.root);
2023
2049
  const isAtWorkspaceRoot = cwd === workspaceRoot;
2024
2050
  if (isAtWorkspaceRoot) {
2025
- logger$6.log("📦 Detected workspace configuration");
2051
+ logger$7.log("📦 Detected workspace configuration");
2026
2052
  return workspaceBuildCommand(loadedConfig.workspace, options);
2027
2053
  }
2028
2054
  }
@@ -2030,21 +2056,21 @@ async function buildCommand(options) {
2030
2056
  const resolved = resolveProviders(config$1, options);
2031
2057
  const productionConfigFromGkm = getProductionConfigFromGkm(config$1);
2032
2058
  const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
2033
- if (production) logger$6.log(`🏭 Building for PRODUCTION`);
2034
- logger$6.log(`Building with providers: ${resolved.providers.join(", ")}`);
2035
- logger$6.log(`Loading routes from: ${config$1.routes}`);
2036
- if (config$1.functions) logger$6.log(`Loading functions from: ${config$1.functions}`);
2037
- if (config$1.crons) logger$6.log(`Loading crons from: ${config$1.crons}`);
2038
- if (config$1.subscribers) logger$6.log(`Loading subscribers from: ${config$1.subscribers}`);
2039
- logger$6.log(`Using envParser: ${config$1.envParser}`);
2059
+ if (production) logger$7.log(`🏭 Building for PRODUCTION`);
2060
+ logger$7.log(`Building with providers: ${resolved.providers.join(", ")}`);
2061
+ logger$7.log(`Loading routes from: ${config$1.routes}`);
2062
+ if (config$1.functions) logger$7.log(`Loading functions from: ${config$1.functions}`);
2063
+ if (config$1.crons) logger$7.log(`Loading crons from: ${config$1.crons}`);
2064
+ if (config$1.subscribers) logger$7.log(`Loading subscribers from: ${config$1.subscribers}`);
2065
+ logger$7.log(`Using envParser: ${config$1.envParser}`);
2040
2066
  const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config$1.envParser, "envParser");
2041
2067
  const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config$1.logger, "logger");
2042
2068
  const telescope = production ? void 0 : normalizeTelescopeConfig(config$1.telescope);
2043
- if (telescope) logger$6.log(`🔭 Telescope enabled at ${telescope.path}`);
2069
+ if (telescope) logger$7.log(`🔭 Telescope enabled at ${telescope.path}`);
2044
2070
  const studio = production ? void 0 : normalizeStudioConfig(config$1.studio);
2045
- if (studio) logger$6.log(`🗄️ Studio enabled at ${studio.path}`);
2071
+ if (studio) logger$7.log(`🗄️ Studio enabled at ${studio.path}`);
2046
2072
  const hooks = normalizeHooksConfig(config$1.hooks);
2047
- if (hooks) logger$6.log(`🪝 Server hooks enabled`);
2073
+ if (hooks) logger$7.log(`🪝 Server hooks enabled`);
2048
2074
  const services = config$1.docker?.compose?.services;
2049
2075
  const dockerServices = services ? Array.isArray(services) ? {
2050
2076
  postgres: services.includes("postgres"),
@@ -2076,12 +2102,12 @@ async function buildCommand(options) {
2076
2102
  config$1.crons ? cronGenerator.load(config$1.crons) : [],
2077
2103
  config$1.subscribers ? subscriberGenerator.load(config$1.subscribers) : []
2078
2104
  ]);
2079
- logger$6.log(`Found ${allEndpoints.length} endpoints`);
2080
- logger$6.log(`Found ${allFunctions.length} functions`);
2081
- logger$6.log(`Found ${allCrons.length} crons`);
2082
- logger$6.log(`Found ${allSubscribers.length} subscribers`);
2105
+ logger$7.log(`Found ${allEndpoints.length} endpoints`);
2106
+ logger$7.log(`Found ${allFunctions.length} functions`);
2107
+ logger$7.log(`Found ${allCrons.length} crons`);
2108
+ logger$7.log(`Found ${allSubscribers.length} subscribers`);
2083
2109
  if (allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0) {
2084
- logger$6.log("No endpoints, functions, crons, or subscribers found to process");
2110
+ logger$7.log("No endpoints, functions, crons, or subscribers found to process");
2085
2111
  return {};
2086
2112
  }
2087
2113
  const rootOutputDir = join(process.cwd(), ".gkm");
@@ -2096,7 +2122,7 @@ async function buildCommand(options) {
2096
2122
  async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, endpoints, functions, crons, subscribers, enableOpenApi, skipBundle, stage) {
2097
2123
  const outputDir = join(process.cwd(), ".gkm", provider);
2098
2124
  await mkdir(outputDir, { recursive: true });
2099
- logger$6.log(`\nGenerating handlers for provider: ${provider}`);
2125
+ logger$7.log(`\nGenerating handlers for provider: ${provider}`);
2100
2126
  const [routes, functionInfos, cronInfos, subscriberInfos] = await Promise.all([
2101
2127
  endpointGenerator.build(context, endpoints, outputDir, {
2102
2128
  provider,
@@ -2106,7 +2132,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
2106
2132
  cronGenerator.build(context, crons, outputDir, { provider }),
2107
2133
  subscriberGenerator.build(context, subscribers, outputDir, { provider })
2108
2134
  ]);
2109
- logger$6.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
2135
+ logger$7.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
2110
2136
  if (provider === "server") {
2111
2137
  const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
2112
2138
  path: construct._path,
@@ -2121,7 +2147,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
2121
2147
  await generateServerManifest(rootOutputDir, appInfo, routeMetadata, subscriberInfos);
2122
2148
  let masterKey;
2123
2149
  if (context.production?.bundle && !skipBundle) {
2124
- logger$6.log(`\n📦 Bundling production server...`);
2150
+ logger$7.log(`\n📦 Bundling production server...`);
2125
2151
  const { bundleServer } = await import("./bundler-DGry2vaR.mjs");
2126
2152
  const allConstructs = [
2127
2153
  ...endpoints.map((e) => e.construct),
@@ -2141,10 +2167,10 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
2141
2167
  dockerServices
2142
2168
  });
2143
2169
  masterKey = bundleResult.masterKey;
2144
- logger$6.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
2170
+ logger$7.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
2145
2171
  if (masterKey) {
2146
- logger$6.log(`\n🔐 Secrets encrypted for deployment`);
2147
- logger$6.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
2172
+ logger$7.log(`\n🔐 Secrets encrypted for deployment`);
2173
+ logger$7.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
2148
2174
  }
2149
2175
  }
2150
2176
  return { masterKey };
@@ -2181,17 +2207,17 @@ async function workspaceBuildCommand(workspace, options) {
2181
2207
  const apps = Object.entries(workspace.apps);
2182
2208
  const backendApps = apps.filter(([, app]) => app.type === "backend");
2183
2209
  const frontendApps = apps.filter(([, app]) => app.type === "frontend");
2184
- logger$6.log(`\n🏗️ Building workspace: ${workspace.name}`);
2185
- logger$6.log(` Backend apps: ${backendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
2186
- logger$6.log(` Frontend apps: ${frontendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
2187
- if (options.production) logger$6.log(` 🏭 Production mode enabled`);
2210
+ logger$7.log(`\n🏗️ Building workspace: ${workspace.name}`);
2211
+ logger$7.log(` Backend apps: ${backendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
2212
+ logger$7.log(` Frontend apps: ${frontendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
2213
+ if (options.production) logger$7.log(` 🏭 Production mode enabled`);
2188
2214
  const buildOrder = getAppBuildOrder(workspace);
2189
- logger$6.log(` Build order: ${buildOrder.join(" → ")}`);
2215
+ logger$7.log(` Build order: ${buildOrder.join(" → ")}`);
2190
2216
  const pm = detectPackageManager$2();
2191
- logger$6.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
2217
+ logger$7.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
2192
2218
  try {
2193
2219
  const turboCommand = getTurboCommand(pm);
2194
- logger$6.log(`Running: ${turboCommand}`);
2220
+ logger$7.log(`Running: ${turboCommand}`);
2195
2221
  await new Promise((resolve$1, reject) => {
2196
2222
  const child = spawn(turboCommand, {
2197
2223
  shell: true,
@@ -2219,15 +2245,15 @@ async function workspaceBuildCommand(workspace, options) {
2219
2245
  outputPath
2220
2246
  });
2221
2247
  }
2222
- logger$6.log(`\n✅ Workspace build complete!`);
2223
- logger$6.log(`\n📋 Build Summary:`);
2248
+ logger$7.log(`\n✅ Workspace build complete!`);
2249
+ logger$7.log(`\n📋 Build Summary:`);
2224
2250
  for (const result of results) {
2225
2251
  const icon = result.type === "backend" ? "⚙️" : "🌐";
2226
- logger$6.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
2252
+ logger$7.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
2227
2253
  }
2228
2254
  } catch (error) {
2229
2255
  const errorMessage = error instanceof Error ? error.message : "Build failed";
2230
- logger$6.log(`\n❌ Build failed: ${errorMessage}`);
2256
+ logger$7.log(`\n❌ Build failed: ${errorMessage}`);
2231
2257
  for (const [appName, app] of apps) results.push({
2232
2258
  appName,
2233
2259
  type: app.type,
@@ -3335,7 +3361,7 @@ CMD ["node", "index.mjs"]
3335
3361
 
3336
3362
  //#endregion
3337
3363
  //#region src/docker/index.ts
3338
- const logger$5 = console;
3364
+ const logger$6 = console;
3339
3365
  /**
3340
3366
  * Docker command implementation
3341
3367
  * Generates Dockerfile, docker-compose.yml, and related files
@@ -3346,7 +3372,7 @@ const logger$5 = console;
3346
3372
  async function dockerCommand(options) {
3347
3373
  const loadedConfig = await loadWorkspaceConfig();
3348
3374
  if (loadedConfig.type === "workspace") {
3349
- logger$5.log("📦 Detected workspace configuration");
3375
+ logger$6.log("📦 Detected workspace configuration");
3350
3376
  return workspaceDockerCommand(loadedConfig.workspace, options);
3351
3377
  }
3352
3378
  const config$1 = await loadConfig();
@@ -3367,14 +3393,14 @@ async function dockerCommand(options) {
3367
3393
  let useTurbo = options.turbo ?? false;
3368
3394
  if (inMonorepo && !useSlim) if (hasTurbo) {
3369
3395
  useTurbo = true;
3370
- logger$5.log(" Detected monorepo with turbo.json - using turbo prune");
3396
+ logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
3371
3397
  } 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");
3372
3398
  let turboPackage = options.turboPackage ?? dockerConfig.imageName;
3373
3399
  if (useTurbo && !options.turboPackage) try {
3374
3400
  const pkg$1 = __require(`${process.cwd()}/package.json`);
3375
3401
  if (pkg$1.name) {
3376
3402
  turboPackage = pkg$1.name;
3377
- logger$5.log(` Turbo package: ${turboPackage}`);
3403
+ logger$6.log(` Turbo package: ${turboPackage}`);
3378
3404
  }
3379
3405
  } catch {}
3380
3406
  const templateOptions = {
@@ -3391,7 +3417,7 @@ async function dockerCommand(options) {
3391
3417
  const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
3392
3418
  const dockerfilePath = join(dockerDir, "Dockerfile");
3393
3419
  await writeFile(dockerfilePath, dockerfile);
3394
- logger$5.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
3420
+ logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
3395
3421
  const composeOptions = {
3396
3422
  imageName: dockerConfig.imageName,
3397
3423
  registry: options.registry ?? dockerConfig.registry,
@@ -3403,15 +3429,15 @@ async function dockerCommand(options) {
3403
3429
  const dockerCompose = hasServices ? generateDockerCompose(composeOptions) : generateMinimalDockerCompose(composeOptions);
3404
3430
  const composePath = join(dockerDir, "docker-compose.yml");
3405
3431
  await writeFile(composePath, dockerCompose);
3406
- logger$5.log("Generated: .gkm/docker/docker-compose.yml");
3432
+ logger$6.log("Generated: .gkm/docker/docker-compose.yml");
3407
3433
  const dockerignore = generateDockerignore();
3408
3434
  const dockerignorePath = join(process.cwd(), ".dockerignore");
3409
3435
  await writeFile(dockerignorePath, dockerignore);
3410
- logger$5.log("Generated: .dockerignore (project root)");
3436
+ logger$6.log("Generated: .dockerignore (project root)");
3411
3437
  const entrypoint = generateDockerEntrypoint();
3412
3438
  const entrypointPath = join(dockerDir, "docker-entrypoint.sh");
3413
3439
  await writeFile(entrypointPath, entrypoint);
3414
- logger$5.log("Generated: .gkm/docker/docker-entrypoint.sh");
3440
+ logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
3415
3441
  const result = {
3416
3442
  dockerfile: dockerfilePath,
3417
3443
  dockerCompose: composePath,
@@ -3430,13 +3456,13 @@ async function dockerCommand(options) {
3430
3456
  function ensureLockfile(cwd) {
3431
3457
  const lockfilePath = findLockfilePath(cwd);
3432
3458
  if (!lockfilePath) {
3433
- logger$5.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
3459
+ logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
3434
3460
  return null;
3435
3461
  }
3436
3462
  const lockfileName = basename(lockfilePath);
3437
3463
  const localLockfile = join(cwd, lockfileName);
3438
3464
  if (lockfilePath === localLockfile) return null;
3439
- logger$5.log(` Copying ${lockfileName} from monorepo root...`);
3465
+ logger$6.log(` Copying ${lockfileName} from monorepo root...`);
3440
3466
  copyFileSync(lockfilePath, localLockfile);
3441
3467
  return () => {
3442
3468
  try {
@@ -3452,7 +3478,7 @@ async function buildDockerImage(imageName, options) {
3452
3478
  const tag = options.tag ?? "latest";
3453
3479
  const registry = options.registry;
3454
3480
  const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
3455
- logger$5.log(`\n🐳 Building Docker image: ${fullImageName}`);
3481
+ logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
3456
3482
  const cwd = process.cwd();
3457
3483
  const cleanup = ensureLockfile(cwd);
3458
3484
  try {
@@ -3464,7 +3490,7 @@ async function buildDockerImage(imageName, options) {
3464
3490
  DOCKER_BUILDKIT: "1"
3465
3491
  }
3466
3492
  });
3467
- logger$5.log(`✅ Docker image built: ${fullImageName}`);
3493
+ logger$6.log(`✅ Docker image built: ${fullImageName}`);
3468
3494
  } catch (error) {
3469
3495
  throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3470
3496
  } finally {
@@ -3479,13 +3505,13 @@ async function pushDockerImage(imageName, options) {
3479
3505
  const registry = options.registry;
3480
3506
  if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
3481
3507
  const fullImageName = `${registry}/${imageName}:${tag}`;
3482
- logger$5.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
3508
+ logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
3483
3509
  try {
3484
3510
  execSync(`docker push ${fullImageName}`, {
3485
3511
  cwd: process.cwd(),
3486
3512
  stdio: "inherit"
3487
3513
  });
3488
- logger$5.log(`✅ Docker image pushed: ${fullImageName}`);
3514
+ logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
3489
3515
  } catch (error) {
3490
3516
  throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3491
3517
  }
@@ -3511,11 +3537,11 @@ function getAppPackageName(appPath) {
3511
3537
  async function workspaceDockerCommand(workspace, options) {
3512
3538
  const results = [];
3513
3539
  const apps = Object.entries(workspace.apps);
3514
- logger$5.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
3540
+ logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
3515
3541
  const dockerDir = join(workspace.root, ".gkm", "docker");
3516
3542
  await mkdir(dockerDir, { recursive: true });
3517
3543
  const packageManager = detectPackageManager$1(workspace.root);
3518
- logger$5.log(` Package manager: ${packageManager}`);
3544
+ logger$6.log(` Package manager: ${packageManager}`);
3519
3545
  for (const [appName, app] of apps) {
3520
3546
  const appPath = app.path;
3521
3547
  const fullAppPath = join(workspace.root, appPath);
@@ -3523,7 +3549,7 @@ async function workspaceDockerCommand(workspace, options) {
3523
3549
  const imageName = appName;
3524
3550
  const hasEntry = !!app.entry;
3525
3551
  const buildType = hasEntry ? "entry" : app.type;
3526
- logger$5.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
3552
+ logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
3527
3553
  let dockerfile;
3528
3554
  if (app.type === "frontend") dockerfile = generateNextjsDockerfile({
3529
3555
  imageName,
@@ -3554,7 +3580,7 @@ async function workspaceDockerCommand(workspace, options) {
3554
3580
  });
3555
3581
  const dockerfilePath = join(dockerDir, `Dockerfile.${appName}`);
3556
3582
  await writeFile(dockerfilePath, dockerfile);
3557
- logger$5.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
3583
+ logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
3558
3584
  results.push({
3559
3585
  appName,
3560
3586
  type: app.type,
@@ -3565,19 +3591,19 @@ async function workspaceDockerCommand(workspace, options) {
3565
3591
  const dockerignore = generateDockerignore();
3566
3592
  const dockerignorePath = join(workspace.root, ".dockerignore");
3567
3593
  await writeFile(dockerignorePath, dockerignore);
3568
- logger$5.log(`\n Generated: .dockerignore (workspace root)`);
3594
+ logger$6.log(`\n Generated: .dockerignore (workspace root)`);
3569
3595
  const dockerCompose = generateWorkspaceCompose(workspace, { registry: options.registry });
3570
3596
  const composePath = join(dockerDir, "docker-compose.yml");
3571
3597
  await writeFile(composePath, dockerCompose);
3572
- logger$5.log(` Generated: .gkm/docker/docker-compose.yml`);
3573
- logger$5.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
3574
- logger$5.log("\n📋 Build commands:");
3598
+ logger$6.log(` Generated: .gkm/docker/docker-compose.yml`);
3599
+ logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
3600
+ logger$6.log("\n📋 Build commands:");
3575
3601
  for (const result of results) {
3576
3602
  const icon = result.type === "backend" ? "⚙️" : "🌐";
3577
- logger$5.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
3603
+ logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
3578
3604
  }
3579
- logger$5.log("\n📋 Run all services:");
3580
- logger$5.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
3605
+ logger$6.log("\n📋 Run all services:");
3606
+ logger$6.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
3581
3607
  return {
3582
3608
  apps: results,
3583
3609
  dockerCompose: composePath,
@@ -3617,7 +3643,7 @@ function getAppNameFromPackageJson() {
3617
3643
  } catch {}
3618
3644
  return void 0;
3619
3645
  }
3620
- const logger$4 = console;
3646
+ const logger$5 = console;
3621
3647
  /**
3622
3648
  * Get the full image reference
3623
3649
  */
@@ -3632,18 +3658,18 @@ function getImageRef(registry, imageName, tag) {
3632
3658
  * @param buildArgs - Build arguments to pass to docker build
3633
3659
  */
3634
3660
  async function buildImage(imageRef, appName, buildArgs) {
3635
- logger$4.log(`\n🔨 Building Docker image: ${imageRef}`);
3661
+ logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
3636
3662
  const cwd = process.cwd();
3637
3663
  const lockfilePath = findLockfilePath(cwd);
3638
3664
  const lockfileDir = lockfilePath ? dirname(lockfilePath) : cwd;
3639
3665
  const inMonorepo = lockfileDir !== cwd;
3640
- if (appName || inMonorepo) logger$4.log(" Generating Dockerfile for monorepo (turbo prune)...");
3641
- else logger$4.log(" Generating Dockerfile...");
3666
+ if (appName || inMonorepo) logger$5.log(" Generating Dockerfile for monorepo (turbo prune)...");
3667
+ else logger$5.log(" Generating Dockerfile...");
3642
3668
  await dockerCommand({});
3643
3669
  const dockerfileSuffix = appName ? `.${appName}` : "";
3644
3670
  const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
3645
3671
  const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
3646
- if (buildCwd !== cwd) logger$4.log(` Building from workspace root: ${buildCwd}`);
3672
+ if (buildCwd !== cwd) logger$5.log(` Building from workspace root: ${buildCwd}`);
3647
3673
  const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
3648
3674
  try {
3649
3675
  const cmd = [
@@ -3662,7 +3688,7 @@ async function buildImage(imageRef, appName, buildArgs) {
3662
3688
  DOCKER_BUILDKIT: "1"
3663
3689
  }
3664
3690
  });
3665
- logger$4.log(`✅ Image built: ${imageRef}`);
3691
+ logger$5.log(`✅ Image built: ${imageRef}`);
3666
3692
  } catch (error) {
3667
3693
  throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3668
3694
  }
@@ -3671,13 +3697,13 @@ async function buildImage(imageRef, appName, buildArgs) {
3671
3697
  * Push Docker image to registry
3672
3698
  */
3673
3699
  async function pushImage(imageRef) {
3674
- logger$4.log(`\n☁️ Pushing image: ${imageRef}`);
3700
+ logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
3675
3701
  try {
3676
3702
  execSync(`docker push ${imageRef}`, {
3677
3703
  cwd: process.cwd(),
3678
3704
  stdio: "inherit"
3679
3705
  });
3680
- logger$4.log(`✅ Image pushed: ${imageRef}`);
3706
+ logger$5.log(`✅ Image pushed: ${imageRef}`);
3681
3707
  } catch (error) {
3682
3708
  throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3683
3709
  }
@@ -3690,17 +3716,17 @@ async function deployDocker(options) {
3690
3716
  const imageName = config$1.imageName;
3691
3717
  const imageRef = getImageRef(config$1.registry, imageName, tag);
3692
3718
  await buildImage(imageRef, config$1.appName, buildArgs);
3693
- if (!skipPush) if (!config$1.registry) logger$4.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
3719
+ if (!skipPush) if (!config$1.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
3694
3720
  else await pushImage(imageRef);
3695
- logger$4.log("\n✅ Docker deployment ready!");
3696
- logger$4.log(`\n📋 Deployment details:`);
3697
- logger$4.log(` Image: ${imageRef}`);
3698
- logger$4.log(` Stage: ${stage}`);
3721
+ logger$5.log("\n✅ Docker deployment ready!");
3722
+ logger$5.log(`\n📋 Deployment details:`);
3723
+ logger$5.log(` Image: ${imageRef}`);
3724
+ logger$5.log(` Stage: ${stage}`);
3699
3725
  if (masterKey) {
3700
- logger$4.log(`\n🔐 Deploy with this environment variable:`);
3701
- logger$4.log(` GKM_MASTER_KEY=${masterKey}`);
3702
- logger$4.log("\n Example docker run:");
3703
- logger$4.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
3726
+ logger$5.log(`\n🔐 Deploy with this environment variable:`);
3727
+ logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
3728
+ logger$5.log("\n Example docker run:");
3729
+ logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
3704
3730
  }
3705
3731
  return {
3706
3732
  imageRef,
@@ -3727,7 +3753,7 @@ function resolveDockerConfig(config$1) {
3727
3753
 
3728
3754
  //#endregion
3729
3755
  //#region src/deploy/dokploy.ts
3730
- const logger$3 = console;
3756
+ const logger$4 = console;
3731
3757
  /**
3732
3758
  * Get the Dokploy API token from stored credentials or environment
3733
3759
  */
@@ -3751,25 +3777,25 @@ async function createApi$1(endpoint) {
3751
3777
  */
3752
3778
  async function deployDokploy(options) {
3753
3779
  const { stage, imageRef, masterKey, config: config$1 } = options;
3754
- logger$3.log(`\n🎯 Deploying to Dokploy...`);
3755
- logger$3.log(` Endpoint: ${config$1.endpoint}`);
3756
- logger$3.log(` Application: ${config$1.applicationId}`);
3780
+ logger$4.log(`\n🎯 Deploying to Dokploy...`);
3781
+ logger$4.log(` Endpoint: ${config$1.endpoint}`);
3782
+ logger$4.log(` Application: ${config$1.applicationId}`);
3757
3783
  const api = await createApi$1(config$1.endpoint);
3758
- logger$3.log(` Configuring Docker image: ${imageRef}`);
3784
+ logger$4.log(` Configuring Docker image: ${imageRef}`);
3759
3785
  const registryOptions = {};
3760
3786
  if (config$1.registryId) {
3761
3787
  registryOptions.registryId = config$1.registryId;
3762
- logger$3.log(` Using Dokploy registry: ${config$1.registryId}`);
3788
+ logger$4.log(` Using Dokploy registry: ${config$1.registryId}`);
3763
3789
  } else {
3764
3790
  const storedRegistryId = await getDokployRegistryId();
3765
3791
  if (storedRegistryId) {
3766
3792
  registryOptions.registryId = storedRegistryId;
3767
- logger$3.log(` Using stored Dokploy registry: ${storedRegistryId}`);
3793
+ logger$4.log(` Using stored Dokploy registry: ${storedRegistryId}`);
3768
3794
  } else if (config$1.registryCredentials) {
3769
3795
  registryOptions.username = config$1.registryCredentials.username;
3770
3796
  registryOptions.password = config$1.registryCredentials.password;
3771
3797
  registryOptions.registryUrl = config$1.registryCredentials.registryUrl;
3772
- logger$3.log(` Using registry credentials for: ${config$1.registryCredentials.registryUrl}`);
3798
+ logger$4.log(` Using registry credentials for: ${config$1.registryCredentials.registryUrl}`);
3773
3799
  } else {
3774
3800
  const username = process.env.DOCKER_REGISTRY_USERNAME;
3775
3801
  const password = process.env.DOCKER_REGISTRY_PASSWORD;
@@ -3778,31 +3804,31 @@ async function deployDokploy(options) {
3778
3804
  registryOptions.username = username;
3779
3805
  registryOptions.password = password;
3780
3806
  registryOptions.registryUrl = registryUrl;
3781
- logger$3.log(` Using registry credentials from environment`);
3807
+ logger$4.log(` Using registry credentials from environment`);
3782
3808
  }
3783
3809
  }
3784
3810
  }
3785
3811
  await api.saveDockerProvider(config$1.applicationId, imageRef, registryOptions);
3786
- logger$3.log(" ✓ Docker provider configured");
3812
+ logger$4.log(" ✓ Docker provider configured");
3787
3813
  const envVars = {};
3788
3814
  if (masterKey) envVars.GKM_MASTER_KEY = masterKey;
3789
3815
  if (Object.keys(envVars).length > 0) {
3790
- logger$3.log(" Updating environment variables...");
3816
+ logger$4.log(" Updating environment variables...");
3791
3817
  const envString = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
3792
3818
  await api.saveApplicationEnv(config$1.applicationId, envString);
3793
- logger$3.log(" ✓ Environment variables updated");
3819
+ logger$4.log(" ✓ Environment variables updated");
3794
3820
  }
3795
- logger$3.log(" Triggering deployment...");
3821
+ logger$4.log(" Triggering deployment...");
3796
3822
  await api.deployApplication(config$1.applicationId);
3797
- logger$3.log(" ✓ Deployment triggered");
3798
- logger$3.log("\n✅ Dokploy deployment initiated!");
3799
- logger$3.log(`\n📋 Deployment details:`);
3800
- logger$3.log(` Image: ${imageRef}`);
3801
- logger$3.log(` Stage: ${stage}`);
3802
- logger$3.log(` Application ID: ${config$1.applicationId}`);
3803
- if (masterKey) logger$3.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
3823
+ logger$4.log(" ✓ Deployment triggered");
3824
+ logger$4.log("\n✅ Dokploy deployment initiated!");
3825
+ logger$4.log(`\n📋 Deployment details:`);
3826
+ logger$4.log(` Image: ${imageRef}`);
3827
+ logger$4.log(` Stage: ${stage}`);
3828
+ logger$4.log(` Application ID: ${config$1.applicationId}`);
3829
+ if (masterKey) logger$4.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
3804
3830
  const deploymentUrl = `${config$1.endpoint}/project/${config$1.projectId}`;
3805
- logger$3.log(`\n🔗 View deployment: ${deploymentUrl}`);
3831
+ logger$4.log(`\n🔗 View deployment: ${deploymentUrl}`);
3806
3832
  return {
3807
3833
  imageRef,
3808
3834
  masterKey,
@@ -3893,6 +3919,390 @@ function setRedisId(state, redisId) {
3893
3919
  state.services.redisId = redisId;
3894
3920
  }
3895
3921
 
3922
+ //#endregion
3923
+ //#region src/deploy/dns/hostinger-api.ts
3924
+ /**
3925
+ * Hostinger DNS API client
3926
+ *
3927
+ * API Documentation: https://developers.hostinger.com/
3928
+ * Authentication: Bearer token from hpanel.hostinger.com/profile/api
3929
+ */
3930
+ const HOSTINGER_API_BASE = "https://api.hostinger.com";
3931
+ /**
3932
+ * Hostinger API error
3933
+ */
3934
+ var HostingerApiError = class extends Error {
3935
+ constructor(message, status, statusText, errors) {
3936
+ super(message);
3937
+ this.status = status;
3938
+ this.statusText = statusText;
3939
+ this.errors = errors;
3940
+ this.name = "HostingerApiError";
3941
+ }
3942
+ };
3943
+ /**
3944
+ * Hostinger DNS API client
3945
+ *
3946
+ * @example
3947
+ * ```ts
3948
+ * const api = new HostingerApi(token);
3949
+ *
3950
+ * // Get all records for a domain
3951
+ * const records = await api.getRecords('traflabs.io');
3952
+ *
3953
+ * // Create/update records
3954
+ * await api.upsertRecords('traflabs.io', [
3955
+ * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
3956
+ * ]);
3957
+ * ```
3958
+ */
3959
+ var HostingerApi = class {
3960
+ token;
3961
+ constructor(token) {
3962
+ this.token = token;
3963
+ }
3964
+ /**
3965
+ * Make a request to the Hostinger API
3966
+ */
3967
+ async request(method, endpoint, body) {
3968
+ const url = `${HOSTINGER_API_BASE}${endpoint}`;
3969
+ const response = await fetch(url, {
3970
+ method,
3971
+ headers: {
3972
+ "Content-Type": "application/json",
3973
+ Authorization: `Bearer ${this.token}`
3974
+ },
3975
+ body: body ? JSON.stringify(body) : void 0
3976
+ });
3977
+ if (!response.ok) {
3978
+ let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
3979
+ let errors;
3980
+ try {
3981
+ const errorBody = await response.json();
3982
+ if (errorBody.message) errorMessage = `Hostinger API error: ${errorBody.message}`;
3983
+ errors = errorBody.errors;
3984
+ } catch {}
3985
+ throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
3986
+ }
3987
+ const text = await response.text();
3988
+ if (!text || text.trim() === "") return void 0;
3989
+ return JSON.parse(text);
3990
+ }
3991
+ /**
3992
+ * Get all DNS records for a domain
3993
+ *
3994
+ * @param domain - Root domain (e.g., 'traflabs.io')
3995
+ */
3996
+ async getRecords(domain) {
3997
+ const response = await this.request("GET", `/api/dns/v1/zones/${domain}`);
3998
+ return response.data || [];
3999
+ }
4000
+ /**
4001
+ * Create or update DNS records
4002
+ *
4003
+ * @param domain - Root domain (e.g., 'traflabs.io')
4004
+ * @param records - Records to create/update
4005
+ * @param overwrite - If true, replaces all existing records. If false, merges with existing.
4006
+ */
4007
+ async upsertRecords(domain, records, overwrite = false) {
4008
+ await this.request("PUT", `/api/dns/v1/zones/${domain}`, {
4009
+ overwrite,
4010
+ zone: records
4011
+ });
4012
+ }
4013
+ /**
4014
+ * Validate DNS records before applying
4015
+ *
4016
+ * @param domain - Root domain (e.g., 'traflabs.io')
4017
+ * @param records - Records to validate
4018
+ * @returns true if valid, throws if invalid
4019
+ */
4020
+ async validateRecords(domain, records) {
4021
+ await this.request("POST", `/api/dns/v1/zones/${domain}/validate`, {
4022
+ overwrite: false,
4023
+ zone: records
4024
+ });
4025
+ return true;
4026
+ }
4027
+ /**
4028
+ * Delete specific DNS records
4029
+ *
4030
+ * @param domain - Root domain (e.g., 'traflabs.io')
4031
+ * @param filters - Filters to match records for deletion
4032
+ */
4033
+ async deleteRecords(domain, filters) {
4034
+ await this.request("DELETE", `/api/dns/v1/zones/${domain}`, { filters });
4035
+ }
4036
+ /**
4037
+ * Check if a specific record exists
4038
+ *
4039
+ * @param domain - Root domain (e.g., 'traflabs.io')
4040
+ * @param name - Subdomain name (e.g., 'api.joemoer')
4041
+ * @param type - Record type (e.g., 'A')
4042
+ */
4043
+ async recordExists(domain, name$1, type$1 = "A") {
4044
+ const records = await this.getRecords(domain);
4045
+ return records.some((r) => r.name === name$1 && r.type === type$1);
4046
+ }
4047
+ /**
4048
+ * Create a single A record if it doesn't exist
4049
+ *
4050
+ * @param domain - Root domain (e.g., 'traflabs.io')
4051
+ * @param subdomain - Subdomain name (e.g., 'api.joemoer')
4052
+ * @param ip - IP address to point to
4053
+ * @param ttl - TTL in seconds (default: 300)
4054
+ * @returns true if created, false if already exists
4055
+ */
4056
+ async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
4057
+ const exists = await this.recordExists(domain, subdomain, "A");
4058
+ if (exists) return false;
4059
+ await this.upsertRecords(domain, [{
4060
+ name: subdomain,
4061
+ type: "A",
4062
+ ttl,
4063
+ records: [ip]
4064
+ }]);
4065
+ return true;
4066
+ }
4067
+ };
4068
+
4069
+ //#endregion
4070
+ //#region src/deploy/dns/index.ts
4071
+ const logger$3 = console;
4072
+ /**
4073
+ * Resolve IP address from a hostname
4074
+ */
4075
+ async function resolveHostnameToIp(hostname) {
4076
+ try {
4077
+ const addresses = await lookup(hostname, { family: 4 });
4078
+ return addresses.address;
4079
+ } catch (error) {
4080
+ throw new Error(`Failed to resolve IP for ${hostname}: ${error instanceof Error ? error.message : "Unknown error"}`);
4081
+ }
4082
+ }
4083
+ /**
4084
+ * Extract subdomain from full hostname relative to root domain
4085
+ *
4086
+ * @example
4087
+ * extractSubdomain('api.joemoer.traflabs.io', 'traflabs.io') => 'api.joemoer'
4088
+ * extractSubdomain('joemoer.traflabs.io', 'traflabs.io') => 'joemoer'
4089
+ */
4090
+ function extractSubdomain(hostname, rootDomain) {
4091
+ if (!hostname.endsWith(rootDomain)) throw new Error(`Hostname ${hostname} is not under root domain ${rootDomain}`);
4092
+ const subdomain = hostname.slice(0, -(rootDomain.length + 1));
4093
+ return subdomain || "@";
4094
+ }
4095
+ /**
4096
+ * Generate required DNS records for a deployment
4097
+ */
4098
+ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
4099
+ const records = [];
4100
+ for (const [appName, hostname] of appHostnames) {
4101
+ const subdomain = extractSubdomain(hostname, rootDomain);
4102
+ records.push({
4103
+ hostname,
4104
+ subdomain,
4105
+ type: "A",
4106
+ value: serverIp,
4107
+ appName
4108
+ });
4109
+ }
4110
+ return records;
4111
+ }
4112
+ /**
4113
+ * Print DNS records table
4114
+ */
4115
+ function printDnsRecordsTable(records, rootDomain) {
4116
+ logger$3.log("\n 📋 DNS Records for " + rootDomain + ":");
4117
+ logger$3.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
4118
+ logger$3.log(" │ Subdomain │ Type │ Value │ Status │");
4119
+ logger$3.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
4120
+ for (const record of records) {
4121
+ const subdomain = record.subdomain.padEnd(35);
4122
+ const type$1 = record.type.padEnd(4);
4123
+ const value = record.value.padEnd(15);
4124
+ let status;
4125
+ if (record.error) status = "✗";
4126
+ else if (record.created) status = "✓ new";
4127
+ else if (record.existed) status = "✓";
4128
+ else status = "?";
4129
+ logger$3.log(` │ ${subdomain} │ ${type$1} │ ${value} │ ${status.padEnd(6)} │`);
4130
+ }
4131
+ logger$3.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
4132
+ }
4133
+ /**
4134
+ * Print DNS records in a simple format for manual setup
4135
+ */
4136
+ function printDnsRecordsSimple(records, rootDomain) {
4137
+ logger$3.log("\n 📋 Required DNS Records:");
4138
+ logger$3.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
4139
+ for (const record of records) logger$3.log(` ${record.subdomain} → ${record.value} (A record)`);
4140
+ logger$3.log("");
4141
+ }
4142
+ /**
4143
+ * Prompt for input (reuse from deploy/index.ts pattern)
4144
+ */
4145
+ async function promptForToken(message) {
4146
+ const { stdin: stdin$1, stdout: stdout$1 } = await import("node:process");
4147
+ const readline$1 = await import("node:readline/promises");
4148
+ if (!stdin$1.isTTY) throw new Error("Interactive input required for Hostinger token.");
4149
+ stdout$1.write(message);
4150
+ return new Promise((resolve$1) => {
4151
+ let value = "";
4152
+ const onData = (char) => {
4153
+ const c = char.toString();
4154
+ if (c === "\n" || c === "\r") {
4155
+ stdin$1.setRawMode(false);
4156
+ stdin$1.pause();
4157
+ stdin$1.removeListener("data", onData);
4158
+ stdout$1.write("\n");
4159
+ resolve$1(value);
4160
+ } else if (c === "") {
4161
+ stdin$1.setRawMode(false);
4162
+ stdin$1.pause();
4163
+ stdout$1.write("\n");
4164
+ process.exit(1);
4165
+ } else if (c === "" || c === "\b") {
4166
+ if (value.length > 0) value = value.slice(0, -1);
4167
+ } else value += c;
4168
+ };
4169
+ stdin$1.setRawMode(true);
4170
+ stdin$1.resume();
4171
+ stdin$1.on("data", onData);
4172
+ });
4173
+ }
4174
+ /**
4175
+ * Create DNS records using the configured provider
4176
+ */
4177
+ async function createDnsRecords(records, dnsConfig) {
4178
+ const { provider, domain: rootDomain, ttl = 300 } = dnsConfig;
4179
+ if (provider === "manual") return records.map((r) => ({
4180
+ ...r,
4181
+ created: false,
4182
+ existed: false
4183
+ }));
4184
+ if (provider === "hostinger") return createHostingerRecords(records, rootDomain, ttl);
4185
+ if (provider === "cloudflare") {
4186
+ logger$3.log(" ⚠ Cloudflare DNS integration not yet implemented");
4187
+ return records.map((r) => ({
4188
+ ...r,
4189
+ error: "Cloudflare not implemented"
4190
+ }));
4191
+ }
4192
+ return records;
4193
+ }
4194
+ /**
4195
+ * Create DNS records at Hostinger
4196
+ */
4197
+ async function createHostingerRecords(records, rootDomain, ttl) {
4198
+ let token = await getHostingerToken();
4199
+ if (!token) {
4200
+ logger$3.log("\n 📋 Hostinger API token not found.");
4201
+ logger$3.log(" Get your token from: https://hpanel.hostinger.com/profile/api\n");
4202
+ try {
4203
+ token = await promptForToken(" Hostinger API Token: ");
4204
+ await storeHostingerToken(token);
4205
+ logger$3.log(" ✓ Token saved");
4206
+ } catch {
4207
+ logger$3.log(" ⚠ Could not get token, skipping DNS creation");
4208
+ return records.map((r) => ({
4209
+ ...r,
4210
+ error: "No API token"
4211
+ }));
4212
+ }
4213
+ }
4214
+ const api = new HostingerApi(token);
4215
+ const results = [];
4216
+ let existingRecords = [];
4217
+ try {
4218
+ existingRecords = await api.getRecords(rootDomain);
4219
+ } catch (error) {
4220
+ const message = error instanceof Error ? error.message : "Unknown error";
4221
+ logger$3.log(` ⚠ Failed to fetch existing DNS records: ${message}`);
4222
+ return records.map((r) => ({
4223
+ ...r,
4224
+ error: message
4225
+ }));
4226
+ }
4227
+ for (const record of records) {
4228
+ const existing = existingRecords.find((r) => r.name === record.subdomain && r.type === "A");
4229
+ if (existing) {
4230
+ results.push({
4231
+ ...record,
4232
+ existed: true,
4233
+ created: false
4234
+ });
4235
+ continue;
4236
+ }
4237
+ try {
4238
+ await api.upsertRecords(rootDomain, [{
4239
+ name: record.subdomain,
4240
+ type: "A",
4241
+ ttl,
4242
+ records: [record.value]
4243
+ }]);
4244
+ results.push({
4245
+ ...record,
4246
+ created: true,
4247
+ existed: false
4248
+ });
4249
+ } catch (error) {
4250
+ const message = error instanceof Error ? error.message : "Unknown error";
4251
+ results.push({
4252
+ ...record,
4253
+ error: message
4254
+ });
4255
+ }
4256
+ }
4257
+ return results;
4258
+ }
4259
+ /**
4260
+ * Main DNS orchestration function for deployments
4261
+ */
4262
+ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint) {
4263
+ if (!dnsConfig) return null;
4264
+ const { domain: rootDomain, autoCreate = true } = dnsConfig;
4265
+ logger$3.log("\n🌐 Setting up DNS records...");
4266
+ let serverIp;
4267
+ try {
4268
+ const endpointUrl = new URL(dokployEndpoint);
4269
+ serverIp = await resolveHostnameToIp(endpointUrl.hostname);
4270
+ logger$3.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
4271
+ } catch (error) {
4272
+ const message = error instanceof Error ? error.message : "Unknown error";
4273
+ logger$3.log(` ⚠ Failed to resolve server IP: ${message}`);
4274
+ return null;
4275
+ }
4276
+ const requiredRecords = generateRequiredRecords(appHostnames, rootDomain, serverIp);
4277
+ if (requiredRecords.length === 0) {
4278
+ logger$3.log(" No DNS records needed");
4279
+ return {
4280
+ records: [],
4281
+ success: true,
4282
+ serverIp
4283
+ };
4284
+ }
4285
+ let finalRecords;
4286
+ if (autoCreate && dnsConfig.provider !== "manual") {
4287
+ logger$3.log(` Creating DNS records at ${dnsConfig.provider}...`);
4288
+ finalRecords = await createDnsRecords(requiredRecords, dnsConfig);
4289
+ const created = finalRecords.filter((r) => r.created).length;
4290
+ const existed = finalRecords.filter((r) => r.existed).length;
4291
+ const failed = finalRecords.filter((r) => r.error).length;
4292
+ if (created > 0) logger$3.log(` ✓ Created ${created} DNS record(s)`);
4293
+ if (existed > 0) logger$3.log(` ✓ ${existed} record(s) already exist`);
4294
+ if (failed > 0) logger$3.log(` ⚠ ${failed} record(s) failed`);
4295
+ } else finalRecords = requiredRecords;
4296
+ printDnsRecordsTable(finalRecords, rootDomain);
4297
+ const hasFailures = finalRecords.some((r) => r.error);
4298
+ if (dnsConfig.provider === "manual" || hasFailures) printDnsRecordsSimple(finalRecords.filter((r) => !r.created && !r.existed), rootDomain);
4299
+ return {
4300
+ records: finalRecords,
4301
+ success: !hasFailures,
4302
+ serverIp
4303
+ };
4304
+ }
4305
+
3896
4306
  //#endregion
3897
4307
  //#region src/deploy/domain.ts
3898
4308
  /**
@@ -4836,6 +5246,8 @@ async function workspaceDeployCommand(workspace, options) {
4836
5246
  const publicUrls = {};
4837
5247
  const results = [];
4838
5248
  const dokployConfig = workspace.deploy.dokploy;
5249
+ const appHostnames = /* @__PURE__ */ new Map();
5250
+ const appDomainIds = /* @__PURE__ */ new Map();
4839
5251
  if (backendApps.length > 0) {
4840
5252
  logger$1.log("\n📦 PHASE 1: Deploying backend applications...");
4841
5253
  for (const appName of backendApps) {
@@ -4885,22 +5297,29 @@ async function workspaceDeployCommand(workspace, options) {
4885
5297
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
4886
5298
  logger$1.log(` Deploying to Dokploy...`);
4887
5299
  await api.deployApplication(application.applicationId);
5300
+ const backendHost = resolveHost(appName, app, stage, dokployConfig, false);
4888
5301
  try {
4889
- const host = resolveHost(appName, app, stage, dokployConfig, false);
4890
- await api.createDomain({
4891
- host,
5302
+ const domain = await api.createDomain({
5303
+ host: backendHost,
4892
5304
  port: app.port,
4893
5305
  https: true,
4894
5306
  certificateType: "letsencrypt",
4895
5307
  applicationId: application.applicationId
4896
5308
  });
4897
- const publicUrl = `https://${host}`;
5309
+ appHostnames.set(appName, backendHost);
5310
+ appDomainIds.set(appName, domain.domainId);
5311
+ const publicUrl = `https://${backendHost}`;
4898
5312
  publicUrls[appName] = publicUrl;
4899
5313
  logger$1.log(` ✓ Domain: ${publicUrl}`);
4900
5314
  } catch (domainError) {
4901
- const host = resolveHost(appName, app, stage, dokployConfig, false);
4902
- publicUrls[appName] = `https://${host}`;
4903
- logger$1.log(` ℹ Domain already configured: https://${host}`);
5315
+ appHostnames.set(appName, backendHost);
5316
+ try {
5317
+ const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
5318
+ const matchingDomain = existingDomains.find((d) => d.host === backendHost);
5319
+ if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
5320
+ } catch {}
5321
+ publicUrls[appName] = `https://${backendHost}`;
5322
+ logger$1.log(` ℹ Domain already configured: https://${backendHost}`);
4904
5323
  }
4905
5324
  results.push({
4906
5325
  appName,
@@ -4968,22 +5387,29 @@ async function workspaceDeployCommand(workspace, options) {
4968
5387
  logger$1.log(` Deploying to Dokploy...`);
4969
5388
  await api.deployApplication(application.applicationId);
4970
5389
  const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
5390
+ const frontendHost = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
4971
5391
  try {
4972
- const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
4973
- await api.createDomain({
4974
- host,
5392
+ const domain = await api.createDomain({
5393
+ host: frontendHost,
4975
5394
  port: app.port,
4976
5395
  https: true,
4977
5396
  certificateType: "letsencrypt",
4978
5397
  applicationId: application.applicationId
4979
5398
  });
4980
- const publicUrl = `https://${host}`;
5399
+ appHostnames.set(appName, frontendHost);
5400
+ appDomainIds.set(appName, domain.domainId);
5401
+ const publicUrl = `https://${frontendHost}`;
4981
5402
  publicUrls[appName] = publicUrl;
4982
5403
  logger$1.log(` ✓ Domain: ${publicUrl}`);
4983
5404
  } catch (domainError) {
4984
- const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
4985
- publicUrls[appName] = `https://${host}`;
4986
- logger$1.log(` ℹ Domain already configured: https://${host}`);
5405
+ appHostnames.set(appName, frontendHost);
5406
+ try {
5407
+ const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
5408
+ const matchingDomain = existingDomains.find((d) => d.host === frontendHost);
5409
+ if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
5410
+ } catch {}
5411
+ publicUrls[appName] = `https://${frontendHost}`;
5412
+ logger$1.log(` ℹ Domain already configured: https://${frontendHost}`);
4987
5413
  }
4988
5414
  results.push({
4989
5415
  appName,
@@ -5008,6 +5434,20 @@ async function workspaceDeployCommand(workspace, options) {
5008
5434
  logger$1.log("\n📋 Saving deploy state...");
5009
5435
  await writeStageState(workspace.root, stage, state);
5010
5436
  logger$1.log(` ✓ State saved to .gkm/deploy-${stage}.json`);
5437
+ const dnsConfig = workspace.deploy.dns;
5438
+ if (dnsConfig && appHostnames.size > 0) {
5439
+ const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
5440
+ if (dnsResult?.success && appDomainIds.size > 0) {
5441
+ logger$1.log("\n🔒 Validating domains for SSL certificates...");
5442
+ for (const [appName, domainId] of appDomainIds) try {
5443
+ await api.validateDomain(domainId);
5444
+ logger$1.log(` ✓ ${appName}: SSL validation triggered`);
5445
+ } catch (validationError) {
5446
+ const message = validationError instanceof Error ? validationError.message : "Unknown error";
5447
+ logger$1.log(` ⚠ ${appName}: SSL validation failed - ${message}`);
5448
+ }
5449
+ }
5450
+ }
5011
5451
  const successCount = results.filter((r) => r.success).length;
5012
5452
  const failedCount = results.filter((r) => !r.success).length;
5013
5453
  logger$1.log(`\n${"─".repeat(50)}`);