@geekmidas/cli 0.46.0 → 0.48.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 (35) hide show
  1. package/dist/config.d.cts +1 -1
  2. package/dist/config.d.mts +1 -1
  3. package/dist/{dokploy-api-D8a0eQQB.cjs → dokploy-api-BDLu0qWi.cjs} +12 -1
  4. package/dist/dokploy-api-BDLu0qWi.cjs.map +1 -0
  5. package/dist/dokploy-api-BN3V57z1.mjs +3 -0
  6. package/dist/dokploy-api-BdCKjFDA.cjs +3 -0
  7. package/dist/{dokploy-api-b6usLLKk.mjs → dokploy-api-DvzIDxTj.mjs} +12 -1
  8. package/dist/dokploy-api-DvzIDxTj.mjs.map +1 -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 +685 -249
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.mjs +685 -249
  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 +3 -3
  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 +398 -0
  25. package/src/deploy/dokploy-api.ts +12 -0
  26. package/src/deploy/index.ts +108 -35
  27. package/src/docker/templates.ts +10 -14
  28. package/src/workspace/types.ts +64 -1
  29. package/tsconfig.tsbuildinfo +1 -1
  30. package/dist/dokploy-api-C1JgU9Vr.mjs +0 -3
  31. package/dist/dokploy-api-Cpq_tLSz.cjs +0 -3
  32. package/dist/dokploy-api-D8a0eQQB.cjs.map +0 -1
  33. package/dist/dokploy-api-b6usLLKk.mjs.map +0 -1
  34. package/dist/index-BtnjoghR.d.mts.map +0 -1
  35. 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-DvzIDxTj.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-BN3V57z1.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,
@@ -2802,8 +2828,8 @@ ENV NODE_ENV=production
2802
2828
  ENV PORT=${port}
2803
2829
 
2804
2830
  # Health check
2805
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
2806
- CMD wget -q --spider http://localhost:${port}${healthCheckPath} || exit 1
2831
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
2832
+ CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
2807
2833
 
2808
2834
  # Switch to non-root user
2809
2835
  USER hono
@@ -2885,8 +2911,8 @@ COPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./
2885
2911
  ENV NODE_ENV=production
2886
2912
  ENV PORT=${port}
2887
2913
 
2888
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
2889
- CMD wget -q --spider http://localhost:${port}${healthCheckPath} || exit 1
2914
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
2915
+ CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
2890
2916
 
2891
2917
  USER hono
2892
2918
 
@@ -2922,8 +2948,8 @@ ENV NODE_ENV=production
2922
2948
  ENV PORT=${port}
2923
2949
 
2924
2950
  # Health check
2925
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
2926
- CMD wget -q --spider http://localhost:${port}${healthCheckPath} || exit 1
2951
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
2952
+ CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
2927
2953
 
2928
2954
  # Switch to non-root user
2929
2955
  USER hono
@@ -3115,10 +3141,6 @@ COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/standalone ./
3115
3141
  COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/static ./${appPath}/.next/static
3116
3142
  COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/public ./${appPath}/public
3117
3143
 
3118
- # Health check
3119
- HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
3120
- CMD wget -q --spider http://localhost:${port}/ || exit 1
3121
-
3122
3144
  USER nextjs
3123
3145
 
3124
3146
  EXPOSE ${port}
@@ -3211,8 +3233,8 @@ COPY --from=builder --chown=hono:nodejs /app/${appPath}/.gkm/server/dist/server.
3211
3233
  ENV NODE_ENV=production
3212
3234
  ENV PORT=${port}
3213
3235
 
3214
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
3215
- CMD wget -q --spider http://localhost:${port}${healthCheckPath} || exit 1
3236
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
3237
+ CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
3216
3238
 
3217
3239
  USER hono
3218
3240
 
@@ -3321,8 +3343,8 @@ COPY --from=builder --chown=app:nodejs /app/${appPath}/dist/index.mjs ./
3321
3343
  ENV NODE_ENV=production
3322
3344
  ENV PORT=${port}
3323
3345
 
3324
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
3325
- CMD wget -q --spider http://localhost:${port}${healthCheckPath} || exit 1
3346
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
3347
+ CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
3326
3348
 
3327
3349
  USER app
3328
3350
 
@@ -3335,7 +3357,7 @@ CMD ["node", "index.mjs"]
3335
3357
 
3336
3358
  //#endregion
3337
3359
  //#region src/docker/index.ts
3338
- const logger$5 = console;
3360
+ const logger$6 = console;
3339
3361
  /**
3340
3362
  * Docker command implementation
3341
3363
  * Generates Dockerfile, docker-compose.yml, and related files
@@ -3346,7 +3368,7 @@ const logger$5 = console;
3346
3368
  async function dockerCommand(options) {
3347
3369
  const loadedConfig = await loadWorkspaceConfig();
3348
3370
  if (loadedConfig.type === "workspace") {
3349
- logger$5.log("📦 Detected workspace configuration");
3371
+ logger$6.log("📦 Detected workspace configuration");
3350
3372
  return workspaceDockerCommand(loadedConfig.workspace, options);
3351
3373
  }
3352
3374
  const config$1 = await loadConfig();
@@ -3367,14 +3389,14 @@ async function dockerCommand(options) {
3367
3389
  let useTurbo = options.turbo ?? false;
3368
3390
  if (inMonorepo && !useSlim) if (hasTurbo) {
3369
3391
  useTurbo = true;
3370
- logger$5.log(" Detected monorepo with turbo.json - using turbo prune");
3392
+ logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
3371
3393
  } 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
3394
  let turboPackage = options.turboPackage ?? dockerConfig.imageName;
3373
3395
  if (useTurbo && !options.turboPackage) try {
3374
3396
  const pkg$1 = __require(`${process.cwd()}/package.json`);
3375
3397
  if (pkg$1.name) {
3376
3398
  turboPackage = pkg$1.name;
3377
- logger$5.log(` Turbo package: ${turboPackage}`);
3399
+ logger$6.log(` Turbo package: ${turboPackage}`);
3378
3400
  }
3379
3401
  } catch {}
3380
3402
  const templateOptions = {
@@ -3391,7 +3413,7 @@ async function dockerCommand(options) {
3391
3413
  const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
3392
3414
  const dockerfilePath = join(dockerDir, "Dockerfile");
3393
3415
  await writeFile(dockerfilePath, dockerfile);
3394
- logger$5.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
3416
+ logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
3395
3417
  const composeOptions = {
3396
3418
  imageName: dockerConfig.imageName,
3397
3419
  registry: options.registry ?? dockerConfig.registry,
@@ -3403,15 +3425,15 @@ async function dockerCommand(options) {
3403
3425
  const dockerCompose = hasServices ? generateDockerCompose(composeOptions) : generateMinimalDockerCompose(composeOptions);
3404
3426
  const composePath = join(dockerDir, "docker-compose.yml");
3405
3427
  await writeFile(composePath, dockerCompose);
3406
- logger$5.log("Generated: .gkm/docker/docker-compose.yml");
3428
+ logger$6.log("Generated: .gkm/docker/docker-compose.yml");
3407
3429
  const dockerignore = generateDockerignore();
3408
3430
  const dockerignorePath = join(process.cwd(), ".dockerignore");
3409
3431
  await writeFile(dockerignorePath, dockerignore);
3410
- logger$5.log("Generated: .dockerignore (project root)");
3432
+ logger$6.log("Generated: .dockerignore (project root)");
3411
3433
  const entrypoint = generateDockerEntrypoint();
3412
3434
  const entrypointPath = join(dockerDir, "docker-entrypoint.sh");
3413
3435
  await writeFile(entrypointPath, entrypoint);
3414
- logger$5.log("Generated: .gkm/docker/docker-entrypoint.sh");
3436
+ logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
3415
3437
  const result = {
3416
3438
  dockerfile: dockerfilePath,
3417
3439
  dockerCompose: composePath,
@@ -3430,13 +3452,13 @@ async function dockerCommand(options) {
3430
3452
  function ensureLockfile(cwd) {
3431
3453
  const lockfilePath = findLockfilePath(cwd);
3432
3454
  if (!lockfilePath) {
3433
- logger$5.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
3455
+ logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
3434
3456
  return null;
3435
3457
  }
3436
3458
  const lockfileName = basename(lockfilePath);
3437
3459
  const localLockfile = join(cwd, lockfileName);
3438
3460
  if (lockfilePath === localLockfile) return null;
3439
- logger$5.log(` Copying ${lockfileName} from monorepo root...`);
3461
+ logger$6.log(` Copying ${lockfileName} from monorepo root...`);
3440
3462
  copyFileSync(lockfilePath, localLockfile);
3441
3463
  return () => {
3442
3464
  try {
@@ -3452,7 +3474,7 @@ async function buildDockerImage(imageName, options) {
3452
3474
  const tag = options.tag ?? "latest";
3453
3475
  const registry = options.registry;
3454
3476
  const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
3455
- logger$5.log(`\n🐳 Building Docker image: ${fullImageName}`);
3477
+ logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
3456
3478
  const cwd = process.cwd();
3457
3479
  const cleanup = ensureLockfile(cwd);
3458
3480
  try {
@@ -3464,7 +3486,7 @@ async function buildDockerImage(imageName, options) {
3464
3486
  DOCKER_BUILDKIT: "1"
3465
3487
  }
3466
3488
  });
3467
- logger$5.log(`✅ Docker image built: ${fullImageName}`);
3489
+ logger$6.log(`✅ Docker image built: ${fullImageName}`);
3468
3490
  } catch (error) {
3469
3491
  throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3470
3492
  } finally {
@@ -3479,13 +3501,13 @@ async function pushDockerImage(imageName, options) {
3479
3501
  const registry = options.registry;
3480
3502
  if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
3481
3503
  const fullImageName = `${registry}/${imageName}:${tag}`;
3482
- logger$5.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
3504
+ logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
3483
3505
  try {
3484
3506
  execSync(`docker push ${fullImageName}`, {
3485
3507
  cwd: process.cwd(),
3486
3508
  stdio: "inherit"
3487
3509
  });
3488
- logger$5.log(`✅ Docker image pushed: ${fullImageName}`);
3510
+ logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
3489
3511
  } catch (error) {
3490
3512
  throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3491
3513
  }
@@ -3511,11 +3533,11 @@ function getAppPackageName(appPath) {
3511
3533
  async function workspaceDockerCommand(workspace, options) {
3512
3534
  const results = [];
3513
3535
  const apps = Object.entries(workspace.apps);
3514
- logger$5.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
3536
+ logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
3515
3537
  const dockerDir = join(workspace.root, ".gkm", "docker");
3516
3538
  await mkdir(dockerDir, { recursive: true });
3517
3539
  const packageManager = detectPackageManager$1(workspace.root);
3518
- logger$5.log(` Package manager: ${packageManager}`);
3540
+ logger$6.log(` Package manager: ${packageManager}`);
3519
3541
  for (const [appName, app] of apps) {
3520
3542
  const appPath = app.path;
3521
3543
  const fullAppPath = join(workspace.root, appPath);
@@ -3523,7 +3545,7 @@ async function workspaceDockerCommand(workspace, options) {
3523
3545
  const imageName = appName;
3524
3546
  const hasEntry = !!app.entry;
3525
3547
  const buildType = hasEntry ? "entry" : app.type;
3526
- logger$5.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
3548
+ logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
3527
3549
  let dockerfile;
3528
3550
  if (app.type === "frontend") dockerfile = generateNextjsDockerfile({
3529
3551
  imageName,
@@ -3554,7 +3576,7 @@ async function workspaceDockerCommand(workspace, options) {
3554
3576
  });
3555
3577
  const dockerfilePath = join(dockerDir, `Dockerfile.${appName}`);
3556
3578
  await writeFile(dockerfilePath, dockerfile);
3557
- logger$5.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
3579
+ logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
3558
3580
  results.push({
3559
3581
  appName,
3560
3582
  type: app.type,
@@ -3565,19 +3587,19 @@ async function workspaceDockerCommand(workspace, options) {
3565
3587
  const dockerignore = generateDockerignore();
3566
3588
  const dockerignorePath = join(workspace.root, ".dockerignore");
3567
3589
  await writeFile(dockerignorePath, dockerignore);
3568
- logger$5.log(`\n Generated: .dockerignore (workspace root)`);
3590
+ logger$6.log(`\n Generated: .dockerignore (workspace root)`);
3569
3591
  const dockerCompose = generateWorkspaceCompose(workspace, { registry: options.registry });
3570
3592
  const composePath = join(dockerDir, "docker-compose.yml");
3571
3593
  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:");
3594
+ logger$6.log(` Generated: .gkm/docker/docker-compose.yml`);
3595
+ logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
3596
+ logger$6.log("\n📋 Build commands:");
3575
3597
  for (const result of results) {
3576
3598
  const icon = result.type === "backend" ? "⚙️" : "🌐";
3577
- logger$5.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
3599
+ logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
3578
3600
  }
3579
- logger$5.log("\n📋 Run all services:");
3580
- logger$5.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
3601
+ logger$6.log("\n📋 Run all services:");
3602
+ logger$6.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
3581
3603
  return {
3582
3604
  apps: results,
3583
3605
  dockerCompose: composePath,
@@ -3617,7 +3639,7 @@ function getAppNameFromPackageJson() {
3617
3639
  } catch {}
3618
3640
  return void 0;
3619
3641
  }
3620
- const logger$4 = console;
3642
+ const logger$5 = console;
3621
3643
  /**
3622
3644
  * Get the full image reference
3623
3645
  */
@@ -3632,18 +3654,18 @@ function getImageRef(registry, imageName, tag) {
3632
3654
  * @param buildArgs - Build arguments to pass to docker build
3633
3655
  */
3634
3656
  async function buildImage(imageRef, appName, buildArgs) {
3635
- logger$4.log(`\n🔨 Building Docker image: ${imageRef}`);
3657
+ logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
3636
3658
  const cwd = process.cwd();
3637
3659
  const lockfilePath = findLockfilePath(cwd);
3638
3660
  const lockfileDir = lockfilePath ? dirname(lockfilePath) : cwd;
3639
3661
  const inMonorepo = lockfileDir !== cwd;
3640
- if (appName || inMonorepo) logger$4.log(" Generating Dockerfile for monorepo (turbo prune)...");
3641
- else logger$4.log(" Generating Dockerfile...");
3662
+ if (appName || inMonorepo) logger$5.log(" Generating Dockerfile for monorepo (turbo prune)...");
3663
+ else logger$5.log(" Generating Dockerfile...");
3642
3664
  await dockerCommand({});
3643
3665
  const dockerfileSuffix = appName ? `.${appName}` : "";
3644
3666
  const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
3645
3667
  const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
3646
- if (buildCwd !== cwd) logger$4.log(` Building from workspace root: ${buildCwd}`);
3668
+ if (buildCwd !== cwd) logger$5.log(` Building from workspace root: ${buildCwd}`);
3647
3669
  const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
3648
3670
  try {
3649
3671
  const cmd = [
@@ -3662,7 +3684,7 @@ async function buildImage(imageRef, appName, buildArgs) {
3662
3684
  DOCKER_BUILDKIT: "1"
3663
3685
  }
3664
3686
  });
3665
- logger$4.log(`✅ Image built: ${imageRef}`);
3687
+ logger$5.log(`✅ Image built: ${imageRef}`);
3666
3688
  } catch (error) {
3667
3689
  throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3668
3690
  }
@@ -3671,13 +3693,13 @@ async function buildImage(imageRef, appName, buildArgs) {
3671
3693
  * Push Docker image to registry
3672
3694
  */
3673
3695
  async function pushImage(imageRef) {
3674
- logger$4.log(`\n☁️ Pushing image: ${imageRef}`);
3696
+ logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
3675
3697
  try {
3676
3698
  execSync(`docker push ${imageRef}`, {
3677
3699
  cwd: process.cwd(),
3678
3700
  stdio: "inherit"
3679
3701
  });
3680
- logger$4.log(`✅ Image pushed: ${imageRef}`);
3702
+ logger$5.log(`✅ Image pushed: ${imageRef}`);
3681
3703
  } catch (error) {
3682
3704
  throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3683
3705
  }
@@ -3690,17 +3712,17 @@ async function deployDocker(options) {
3690
3712
  const imageName = config$1.imageName;
3691
3713
  const imageRef = getImageRef(config$1.registry, imageName, tag);
3692
3714
  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");
3715
+ 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
3716
  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}`);
3717
+ logger$5.log("\n✅ Docker deployment ready!");
3718
+ logger$5.log(`\n📋 Deployment details:`);
3719
+ logger$5.log(` Image: ${imageRef}`);
3720
+ logger$5.log(` Stage: ${stage}`);
3699
3721
  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}`);
3722
+ logger$5.log(`\n🔐 Deploy with this environment variable:`);
3723
+ logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
3724
+ logger$5.log("\n Example docker run:");
3725
+ logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
3704
3726
  }
3705
3727
  return {
3706
3728
  imageRef,
@@ -3727,7 +3749,7 @@ function resolveDockerConfig(config$1) {
3727
3749
 
3728
3750
  //#endregion
3729
3751
  //#region src/deploy/dokploy.ts
3730
- const logger$3 = console;
3752
+ const logger$4 = console;
3731
3753
  /**
3732
3754
  * Get the Dokploy API token from stored credentials or environment
3733
3755
  */
@@ -3751,25 +3773,25 @@ async function createApi$1(endpoint) {
3751
3773
  */
3752
3774
  async function deployDokploy(options) {
3753
3775
  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}`);
3776
+ logger$4.log(`\n🎯 Deploying to Dokploy...`);
3777
+ logger$4.log(` Endpoint: ${config$1.endpoint}`);
3778
+ logger$4.log(` Application: ${config$1.applicationId}`);
3757
3779
  const api = await createApi$1(config$1.endpoint);
3758
- logger$3.log(` Configuring Docker image: ${imageRef}`);
3780
+ logger$4.log(` Configuring Docker image: ${imageRef}`);
3759
3781
  const registryOptions = {};
3760
3782
  if (config$1.registryId) {
3761
3783
  registryOptions.registryId = config$1.registryId;
3762
- logger$3.log(` Using Dokploy registry: ${config$1.registryId}`);
3784
+ logger$4.log(` Using Dokploy registry: ${config$1.registryId}`);
3763
3785
  } else {
3764
3786
  const storedRegistryId = await getDokployRegistryId();
3765
3787
  if (storedRegistryId) {
3766
3788
  registryOptions.registryId = storedRegistryId;
3767
- logger$3.log(` Using stored Dokploy registry: ${storedRegistryId}`);
3789
+ logger$4.log(` Using stored Dokploy registry: ${storedRegistryId}`);
3768
3790
  } else if (config$1.registryCredentials) {
3769
3791
  registryOptions.username = config$1.registryCredentials.username;
3770
3792
  registryOptions.password = config$1.registryCredentials.password;
3771
3793
  registryOptions.registryUrl = config$1.registryCredentials.registryUrl;
3772
- logger$3.log(` Using registry credentials for: ${config$1.registryCredentials.registryUrl}`);
3794
+ logger$4.log(` Using registry credentials for: ${config$1.registryCredentials.registryUrl}`);
3773
3795
  } else {
3774
3796
  const username = process.env.DOCKER_REGISTRY_USERNAME;
3775
3797
  const password = process.env.DOCKER_REGISTRY_PASSWORD;
@@ -3778,31 +3800,31 @@ async function deployDokploy(options) {
3778
3800
  registryOptions.username = username;
3779
3801
  registryOptions.password = password;
3780
3802
  registryOptions.registryUrl = registryUrl;
3781
- logger$3.log(` Using registry credentials from environment`);
3803
+ logger$4.log(` Using registry credentials from environment`);
3782
3804
  }
3783
3805
  }
3784
3806
  }
3785
3807
  await api.saveDockerProvider(config$1.applicationId, imageRef, registryOptions);
3786
- logger$3.log(" ✓ Docker provider configured");
3808
+ logger$4.log(" ✓ Docker provider configured");
3787
3809
  const envVars = {};
3788
3810
  if (masterKey) envVars.GKM_MASTER_KEY = masterKey;
3789
3811
  if (Object.keys(envVars).length > 0) {
3790
- logger$3.log(" Updating environment variables...");
3812
+ logger$4.log(" Updating environment variables...");
3791
3813
  const envString = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
3792
3814
  await api.saveApplicationEnv(config$1.applicationId, envString);
3793
- logger$3.log(" ✓ Environment variables updated");
3815
+ logger$4.log(" ✓ Environment variables updated");
3794
3816
  }
3795
- logger$3.log(" Triggering deployment...");
3817
+ logger$4.log(" Triggering deployment...");
3796
3818
  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`);
3819
+ logger$4.log(" ✓ Deployment triggered");
3820
+ logger$4.log("\n✅ Dokploy deployment initiated!");
3821
+ logger$4.log(`\n📋 Deployment details:`);
3822
+ logger$4.log(` Image: ${imageRef}`);
3823
+ logger$4.log(` Stage: ${stage}`);
3824
+ logger$4.log(` Application ID: ${config$1.applicationId}`);
3825
+ if (masterKey) logger$4.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
3804
3826
  const deploymentUrl = `${config$1.endpoint}/project/${config$1.projectId}`;
3805
- logger$3.log(`\n🔗 View deployment: ${deploymentUrl}`);
3827
+ logger$4.log(`\n🔗 View deployment: ${deploymentUrl}`);
3806
3828
  return {
3807
3829
  imageRef,
3808
3830
  masterKey,
@@ -3893,6 +3915,389 @@ function setRedisId(state, redisId) {
3893
3915
  state.services.redisId = redisId;
3894
3916
  }
3895
3917
 
3918
+ //#endregion
3919
+ //#region src/deploy/dns/hostinger-api.ts
3920
+ /**
3921
+ * Hostinger DNS API client
3922
+ *
3923
+ * API Documentation: https://developers.hostinger.com/
3924
+ * Authentication: Bearer token from hpanel.hostinger.com/profile/api
3925
+ */
3926
+ const HOSTINGER_API_BASE = "https://developers.hostinger.com";
3927
+ /**
3928
+ * Hostinger API error
3929
+ */
3930
+ var HostingerApiError = class extends Error {
3931
+ constructor(message, status, statusText, errors) {
3932
+ super(message);
3933
+ this.status = status;
3934
+ this.statusText = statusText;
3935
+ this.errors = errors;
3936
+ this.name = "HostingerApiError";
3937
+ }
3938
+ };
3939
+ /**
3940
+ * Hostinger DNS API client
3941
+ *
3942
+ * @example
3943
+ * ```ts
3944
+ * const api = new HostingerApi(token);
3945
+ *
3946
+ * // Get all records for a domain
3947
+ * const records = await api.getRecords('traflabs.io');
3948
+ *
3949
+ * // Create/update records
3950
+ * await api.upsertRecords('traflabs.io', [
3951
+ * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
3952
+ * ]);
3953
+ * ```
3954
+ */
3955
+ var HostingerApi = class {
3956
+ token;
3957
+ constructor(token) {
3958
+ this.token = token;
3959
+ }
3960
+ /**
3961
+ * Make a request to the Hostinger API
3962
+ */
3963
+ async request(method, endpoint, body) {
3964
+ const url = `${HOSTINGER_API_BASE}${endpoint}`;
3965
+ const response = await fetch(url, {
3966
+ method,
3967
+ headers: {
3968
+ "Content-Type": "application/json",
3969
+ Authorization: `Bearer ${this.token}`
3970
+ },
3971
+ body: body ? JSON.stringify(body) : void 0
3972
+ });
3973
+ if (!response.ok) {
3974
+ let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
3975
+ let errors;
3976
+ try {
3977
+ const errorBody = await response.json();
3978
+ if (errorBody.message) errorMessage = `Hostinger API error: ${errorBody.message}`;
3979
+ errors = errorBody.errors;
3980
+ } catch {}
3981
+ throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
3982
+ }
3983
+ const text = await response.text();
3984
+ if (!text || text.trim() === "") return void 0;
3985
+ return JSON.parse(text);
3986
+ }
3987
+ /**
3988
+ * Get all DNS records for a domain
3989
+ *
3990
+ * @param domain - Root domain (e.g., 'traflabs.io')
3991
+ */
3992
+ async getRecords(domain) {
3993
+ const response = await this.request("GET", `/api/dns/v1/zones/${domain}`);
3994
+ return response.data || [];
3995
+ }
3996
+ /**
3997
+ * Create or update DNS records
3998
+ *
3999
+ * @param domain - Root domain (e.g., 'traflabs.io')
4000
+ * @param records - Records to create/update
4001
+ * @param overwrite - If true, replaces all existing records. If false, merges with existing.
4002
+ */
4003
+ async upsertRecords(domain, records, overwrite = false) {
4004
+ await this.request("PUT", `/api/dns/v1/zones/${domain}`, {
4005
+ overwrite,
4006
+ zone: records
4007
+ });
4008
+ }
4009
+ /**
4010
+ * Validate DNS records before applying
4011
+ *
4012
+ * @param domain - Root domain (e.g., 'traflabs.io')
4013
+ * @param records - Records to validate
4014
+ * @returns true if valid, throws if invalid
4015
+ */
4016
+ async validateRecords(domain, records) {
4017
+ await this.request("POST", `/api/dns/v1/zones/${domain}/validate`, {
4018
+ overwrite: false,
4019
+ zone: records
4020
+ });
4021
+ return true;
4022
+ }
4023
+ /**
4024
+ * Delete specific DNS records
4025
+ *
4026
+ * @param domain - Root domain (e.g., 'traflabs.io')
4027
+ * @param filters - Filters to match records for deletion
4028
+ */
4029
+ async deleteRecords(domain, filters) {
4030
+ await this.request("DELETE", `/api/dns/v1/zones/${domain}`, { filters });
4031
+ }
4032
+ /**
4033
+ * Check if a specific record exists
4034
+ *
4035
+ * @param domain - Root domain (e.g., 'traflabs.io')
4036
+ * @param name - Subdomain name (e.g., 'api.joemoer')
4037
+ * @param type - Record type (e.g., 'A')
4038
+ */
4039
+ async recordExists(domain, name$1, type$1 = "A") {
4040
+ const records = await this.getRecords(domain);
4041
+ return records.some((r) => r.name === name$1 && r.type === type$1);
4042
+ }
4043
+ /**
4044
+ * Create a single A record if it doesn't exist
4045
+ *
4046
+ * @param domain - Root domain (e.g., 'traflabs.io')
4047
+ * @param subdomain - Subdomain name (e.g., 'api.joemoer')
4048
+ * @param ip - IP address to point to
4049
+ * @param ttl - TTL in seconds (default: 300)
4050
+ * @returns true if created, false if already exists
4051
+ */
4052
+ async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
4053
+ const exists = await this.recordExists(domain, subdomain, "A");
4054
+ if (exists) return false;
4055
+ await this.upsertRecords(domain, [{
4056
+ name: subdomain,
4057
+ type: "A",
4058
+ ttl,
4059
+ records: [{ content: ip }]
4060
+ }]);
4061
+ return true;
4062
+ }
4063
+ };
4064
+
4065
+ //#endregion
4066
+ //#region src/deploy/dns/index.ts
4067
+ const logger$3 = console;
4068
+ /**
4069
+ * Resolve IP address from a hostname
4070
+ */
4071
+ async function resolveHostnameToIp(hostname) {
4072
+ try {
4073
+ const addresses = await lookup(hostname, { family: 4 });
4074
+ return addresses.address;
4075
+ } catch (error) {
4076
+ throw new Error(`Failed to resolve IP for ${hostname}: ${error instanceof Error ? error.message : "Unknown error"}`);
4077
+ }
4078
+ }
4079
+ /**
4080
+ * Extract subdomain from full hostname relative to root domain
4081
+ *
4082
+ * @example
4083
+ * extractSubdomain('api.joemoer.traflabs.io', 'traflabs.io') => 'api.joemoer'
4084
+ * extractSubdomain('joemoer.traflabs.io', 'traflabs.io') => 'joemoer'
4085
+ */
4086
+ function extractSubdomain(hostname, rootDomain) {
4087
+ if (!hostname.endsWith(rootDomain)) throw new Error(`Hostname ${hostname} is not under root domain ${rootDomain}`);
4088
+ const subdomain = hostname.slice(0, -(rootDomain.length + 1));
4089
+ return subdomain || "@";
4090
+ }
4091
+ /**
4092
+ * Generate required DNS records for a deployment
4093
+ */
4094
+ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
4095
+ const records = [];
4096
+ for (const [appName, hostname] of appHostnames) {
4097
+ const subdomain = extractSubdomain(hostname, rootDomain);
4098
+ records.push({
4099
+ hostname,
4100
+ subdomain,
4101
+ type: "A",
4102
+ value: serverIp,
4103
+ appName
4104
+ });
4105
+ }
4106
+ return records;
4107
+ }
4108
+ /**
4109
+ * Print DNS records table
4110
+ */
4111
+ function printDnsRecordsTable(records, rootDomain) {
4112
+ logger$3.log("\n 📋 DNS Records for " + rootDomain + ":");
4113
+ logger$3.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
4114
+ logger$3.log(" │ Subdomain │ Type │ Value │ Status │");
4115
+ logger$3.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
4116
+ for (const record of records) {
4117
+ const subdomain = record.subdomain.padEnd(35);
4118
+ const type$1 = record.type.padEnd(4);
4119
+ const value = record.value.padEnd(15);
4120
+ let status;
4121
+ if (record.error) status = "✗";
4122
+ else if (record.created) status = "✓ new";
4123
+ else if (record.existed) status = "✓";
4124
+ else status = "?";
4125
+ logger$3.log(` │ ${subdomain} │ ${type$1} │ ${value} │ ${status.padEnd(6)} │`);
4126
+ }
4127
+ logger$3.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
4128
+ }
4129
+ /**
4130
+ * Print DNS records in a simple format for manual setup
4131
+ */
4132
+ function printDnsRecordsSimple(records, rootDomain) {
4133
+ logger$3.log("\n 📋 Required DNS Records:");
4134
+ logger$3.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
4135
+ for (const record of records) logger$3.log(` ${record.subdomain} → ${record.value} (A record)`);
4136
+ logger$3.log("");
4137
+ }
4138
+ /**
4139
+ * Prompt for input (reuse from deploy/index.ts pattern)
4140
+ */
4141
+ async function promptForToken(message) {
4142
+ const { stdin: stdin$1, stdout: stdout$1 } = await import("node:process");
4143
+ if (!stdin$1.isTTY) throw new Error("Interactive input required for Hostinger token.");
4144
+ stdout$1.write(message);
4145
+ return new Promise((resolve$1) => {
4146
+ let value = "";
4147
+ const onData = (char) => {
4148
+ const c = char.toString();
4149
+ if (c === "\n" || c === "\r") {
4150
+ stdin$1.setRawMode(false);
4151
+ stdin$1.pause();
4152
+ stdin$1.removeListener("data", onData);
4153
+ stdout$1.write("\n");
4154
+ resolve$1(value);
4155
+ } else if (c === "") {
4156
+ stdin$1.setRawMode(false);
4157
+ stdin$1.pause();
4158
+ stdout$1.write("\n");
4159
+ process.exit(1);
4160
+ } else if (c === "" || c === "\b") {
4161
+ if (value.length > 0) value = value.slice(0, -1);
4162
+ } else value += c;
4163
+ };
4164
+ stdin$1.setRawMode(true);
4165
+ stdin$1.resume();
4166
+ stdin$1.on("data", onData);
4167
+ });
4168
+ }
4169
+ /**
4170
+ * Create DNS records using the configured provider
4171
+ */
4172
+ async function createDnsRecords(records, dnsConfig) {
4173
+ const { provider, domain: rootDomain, ttl = 300 } = dnsConfig;
4174
+ if (provider === "manual") return records.map((r) => ({
4175
+ ...r,
4176
+ created: false,
4177
+ existed: false
4178
+ }));
4179
+ if (provider === "hostinger") return createHostingerRecords(records, rootDomain, ttl);
4180
+ if (provider === "cloudflare") {
4181
+ logger$3.log(" ⚠ Cloudflare DNS integration not yet implemented");
4182
+ return records.map((r) => ({
4183
+ ...r,
4184
+ error: "Cloudflare not implemented"
4185
+ }));
4186
+ }
4187
+ return records;
4188
+ }
4189
+ /**
4190
+ * Create DNS records at Hostinger
4191
+ */
4192
+ async function createHostingerRecords(records, rootDomain, ttl) {
4193
+ let token = await getHostingerToken();
4194
+ if (!token) {
4195
+ logger$3.log("\n 📋 Hostinger API token not found.");
4196
+ logger$3.log(" Get your token from: https://hpanel.hostinger.com/profile/api\n");
4197
+ try {
4198
+ token = await promptForToken(" Hostinger API Token: ");
4199
+ await storeHostingerToken(token);
4200
+ logger$3.log(" ✓ Token saved");
4201
+ } catch {
4202
+ logger$3.log(" ⚠ Could not get token, skipping DNS creation");
4203
+ return records.map((r) => ({
4204
+ ...r,
4205
+ error: "No API token"
4206
+ }));
4207
+ }
4208
+ }
4209
+ const api = new HostingerApi(token);
4210
+ const results = [];
4211
+ let existingRecords = [];
4212
+ try {
4213
+ existingRecords = await api.getRecords(rootDomain);
4214
+ } catch (error) {
4215
+ const message = error instanceof Error ? error.message : "Unknown error";
4216
+ logger$3.log(` ⚠ Failed to fetch existing DNS records: ${message}`);
4217
+ return records.map((r) => ({
4218
+ ...r,
4219
+ error: message
4220
+ }));
4221
+ }
4222
+ for (const record of records) {
4223
+ const existing = existingRecords.find((r) => r.name === record.subdomain && r.type === "A");
4224
+ if (existing) {
4225
+ results.push({
4226
+ ...record,
4227
+ existed: true,
4228
+ created: false
4229
+ });
4230
+ continue;
4231
+ }
4232
+ try {
4233
+ await api.upsertRecords(rootDomain, [{
4234
+ name: record.subdomain,
4235
+ type: "A",
4236
+ ttl,
4237
+ records: [{ content: record.value }]
4238
+ }]);
4239
+ results.push({
4240
+ ...record,
4241
+ created: true,
4242
+ existed: false
4243
+ });
4244
+ } catch (error) {
4245
+ const message = error instanceof Error ? error.message : "Unknown error";
4246
+ results.push({
4247
+ ...record,
4248
+ error: message
4249
+ });
4250
+ }
4251
+ }
4252
+ return results;
4253
+ }
4254
+ /**
4255
+ * Main DNS orchestration function for deployments
4256
+ */
4257
+ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint) {
4258
+ if (!dnsConfig) return null;
4259
+ const { domain: rootDomain, autoCreate = true } = dnsConfig;
4260
+ logger$3.log("\n🌐 Setting up DNS records...");
4261
+ let serverIp;
4262
+ try {
4263
+ const endpointUrl = new URL(dokployEndpoint);
4264
+ serverIp = await resolveHostnameToIp(endpointUrl.hostname);
4265
+ logger$3.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
4266
+ } catch (error) {
4267
+ const message = error instanceof Error ? error.message : "Unknown error";
4268
+ logger$3.log(` ⚠ Failed to resolve server IP: ${message}`);
4269
+ return null;
4270
+ }
4271
+ const requiredRecords = generateRequiredRecords(appHostnames, rootDomain, serverIp);
4272
+ if (requiredRecords.length === 0) {
4273
+ logger$3.log(" No DNS records needed");
4274
+ return {
4275
+ records: [],
4276
+ success: true,
4277
+ serverIp
4278
+ };
4279
+ }
4280
+ let finalRecords;
4281
+ if (autoCreate && dnsConfig.provider !== "manual") {
4282
+ logger$3.log(` Creating DNS records at ${dnsConfig.provider}...`);
4283
+ finalRecords = await createDnsRecords(requiredRecords, dnsConfig);
4284
+ const created = finalRecords.filter((r) => r.created).length;
4285
+ const existed = finalRecords.filter((r) => r.existed).length;
4286
+ const failed = finalRecords.filter((r) => r.error).length;
4287
+ if (created > 0) logger$3.log(` ✓ Created ${created} DNS record(s)`);
4288
+ if (existed > 0) logger$3.log(` ✓ ${existed} record(s) already exist`);
4289
+ if (failed > 0) logger$3.log(` ⚠ ${failed} record(s) failed`);
4290
+ } else finalRecords = requiredRecords;
4291
+ printDnsRecordsTable(finalRecords, rootDomain);
4292
+ const hasFailures = finalRecords.some((r) => r.error);
4293
+ if (dnsConfig.provider === "manual" || hasFailures) printDnsRecordsSimple(finalRecords.filter((r) => !r.created && !r.existed), rootDomain);
4294
+ return {
4295
+ records: finalRecords,
4296
+ success: !hasFailures,
4297
+ serverIp
4298
+ };
4299
+ }
4300
+
3896
4301
  //#endregion
3897
4302
  //#region src/deploy/domain.ts
3898
4303
  /**
@@ -4836,6 +5241,8 @@ async function workspaceDeployCommand(workspace, options) {
4836
5241
  const publicUrls = {};
4837
5242
  const results = [];
4838
5243
  const dokployConfig = workspace.deploy.dokploy;
5244
+ const appHostnames = /* @__PURE__ */ new Map();
5245
+ const appDomainIds = /* @__PURE__ */ new Map();
4839
5246
  if (backendApps.length > 0) {
4840
5247
  logger$1.log("\n📦 PHASE 1: Deploying backend applications...");
4841
5248
  for (const appName of backendApps) {
@@ -4885,22 +5292,29 @@ async function workspaceDeployCommand(workspace, options) {
4885
5292
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
4886
5293
  logger$1.log(` Deploying to Dokploy...`);
4887
5294
  await api.deployApplication(application.applicationId);
5295
+ const backendHost = resolveHost(appName, app, stage, dokployConfig, false);
4888
5296
  try {
4889
- const host = resolveHost(appName, app, stage, dokployConfig, false);
4890
- await api.createDomain({
4891
- host,
5297
+ const domain = await api.createDomain({
5298
+ host: backendHost,
4892
5299
  port: app.port,
4893
5300
  https: true,
4894
5301
  certificateType: "letsencrypt",
4895
5302
  applicationId: application.applicationId
4896
5303
  });
4897
- const publicUrl = `https://${host}`;
5304
+ appHostnames.set(appName, backendHost);
5305
+ appDomainIds.set(appName, domain.domainId);
5306
+ const publicUrl = `https://${backendHost}`;
4898
5307
  publicUrls[appName] = publicUrl;
4899
5308
  logger$1.log(` ✓ Domain: ${publicUrl}`);
4900
5309
  } 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}`);
5310
+ appHostnames.set(appName, backendHost);
5311
+ try {
5312
+ const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
5313
+ const matchingDomain = existingDomains.find((d) => d.host === backendHost);
5314
+ if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
5315
+ } catch {}
5316
+ publicUrls[appName] = `https://${backendHost}`;
5317
+ logger$1.log(` ℹ Domain already configured: https://${backendHost}`);
4904
5318
  }
4905
5319
  results.push({
4906
5320
  appName,
@@ -4968,22 +5382,29 @@ async function workspaceDeployCommand(workspace, options) {
4968
5382
  logger$1.log(` Deploying to Dokploy...`);
4969
5383
  await api.deployApplication(application.applicationId);
4970
5384
  const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
5385
+ const frontendHost = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
4971
5386
  try {
4972
- const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
4973
- await api.createDomain({
4974
- host,
5387
+ const domain = await api.createDomain({
5388
+ host: frontendHost,
4975
5389
  port: app.port,
4976
5390
  https: true,
4977
5391
  certificateType: "letsencrypt",
4978
5392
  applicationId: application.applicationId
4979
5393
  });
4980
- const publicUrl = `https://${host}`;
5394
+ appHostnames.set(appName, frontendHost);
5395
+ appDomainIds.set(appName, domain.domainId);
5396
+ const publicUrl = `https://${frontendHost}`;
4981
5397
  publicUrls[appName] = publicUrl;
4982
5398
  logger$1.log(` ✓ Domain: ${publicUrl}`);
4983
5399
  } 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}`);
5400
+ appHostnames.set(appName, frontendHost);
5401
+ try {
5402
+ const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
5403
+ const matchingDomain = existingDomains.find((d) => d.host === frontendHost);
5404
+ if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
5405
+ } catch {}
5406
+ publicUrls[appName] = `https://${frontendHost}`;
5407
+ logger$1.log(` ℹ Domain already configured: https://${frontendHost}`);
4987
5408
  }
4988
5409
  results.push({
4989
5410
  appName,
@@ -5008,6 +5429,21 @@ async function workspaceDeployCommand(workspace, options) {
5008
5429
  logger$1.log("\n📋 Saving deploy state...");
5009
5430
  await writeStageState(workspace.root, stage, state);
5010
5431
  logger$1.log(` ✓ State saved to .gkm/deploy-${stage}.json`);
5432
+ const dnsConfig = workspace.deploy.dns;
5433
+ if (dnsConfig && appHostnames.size > 0) {
5434
+ const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
5435
+ if (dnsResult?.success && appHostnames.size > 0) {
5436
+ logger$1.log("\n🔒 Validating domains for SSL certificates...");
5437
+ for (const [appName, hostname] of appHostnames) try {
5438
+ const result = await api.validateDomain(hostname);
5439
+ if (result.isValid) logger$1.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
5440
+ else logger$1.log(` ⚠ ${appName}: ${hostname} not valid`);
5441
+ } catch (validationError) {
5442
+ const message = validationError instanceof Error ? validationError.message : "Unknown error";
5443
+ logger$1.log(` ⚠ ${appName}: validation failed - ${message}`);
5444
+ }
5445
+ }
5446
+ }
5011
5447
  const successCount = results.filter((r) => r.success).length;
5012
5448
  const failedCount = results.filter((r) => !r.success).length;
5013
5449
  logger$1.log(`\n${"─".repeat(50)}`);