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