@geekmidas/cli 0.45.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/{config-C0b0jdmU.mjs → config-C3LSBNSl.mjs} +2 -2
  2. package/dist/{config-C0b0jdmU.mjs.map → config-C3LSBNSl.mjs.map} +1 -1
  3. package/dist/{config-xVZsRjN7.cjs → config-HYiM3iQJ.cjs} +2 -2
  4. package/dist/{config-xVZsRjN7.cjs.map → config-HYiM3iQJ.cjs.map} +1 -1
  5. package/dist/config.cjs +2 -2
  6. package/dist/config.d.cts +1 -1
  7. package/dist/config.d.mts +1 -1
  8. package/dist/config.mjs +2 -2
  9. package/dist/dokploy-api-4a6h35VY.cjs +3 -0
  10. package/dist/{dokploy-api-BdxOMH_V.cjs → dokploy-api-BnX2OxyF.cjs} +121 -1
  11. package/dist/dokploy-api-BnX2OxyF.cjs.map +1 -0
  12. package/dist/{dokploy-api-DWsqNjwP.mjs → dokploy-api-CMWlWq7-.mjs} +121 -1
  13. package/dist/dokploy-api-CMWlWq7-.mjs.map +1 -0
  14. package/dist/dokploy-api-DQvi9iZa.mjs +3 -0
  15. package/dist/{index-CXa3odEw.d.mts → index-A70abJ1m.d.mts} +598 -46
  16. package/dist/index-A70abJ1m.d.mts.map +1 -0
  17. package/dist/{index-E8Nu2Rxl.d.cts → index-pOA56MWT.d.cts} +598 -46
  18. package/dist/index-pOA56MWT.d.cts.map +1 -0
  19. package/dist/index.cjs +916 -357
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.mjs +916 -357
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/{openapi-D3pA6FfZ.mjs → openapi-C3C-BzIZ.mjs} +2 -2
  24. package/dist/{openapi-D3pA6FfZ.mjs.map → openapi-C3C-BzIZ.mjs.map} +1 -1
  25. package/dist/{openapi-DhcCtKzM.cjs → openapi-D7WwlpPF.cjs} +2 -2
  26. package/dist/{openapi-DhcCtKzM.cjs.map → openapi-D7WwlpPF.cjs.map} +1 -1
  27. package/dist/openapi.cjs +3 -3
  28. package/dist/openapi.mjs +3 -3
  29. package/dist/workspace/index.cjs +1 -1
  30. package/dist/workspace/index.d.cts +1 -1
  31. package/dist/workspace/index.d.mts +1 -1
  32. package/dist/workspace/index.mjs +1 -1
  33. package/dist/{workspace-BDAhr6Kb.cjs → workspace-CaVW6j2q.cjs} +10 -1
  34. package/dist/{workspace-BDAhr6Kb.cjs.map → workspace-CaVW6j2q.cjs.map} +1 -1
  35. package/dist/{workspace-D_6ZCaR_.mjs → workspace-DLFRaDc-.mjs} +10 -1
  36. package/dist/{workspace-D_6ZCaR_.mjs.map → workspace-DLFRaDc-.mjs.map} +1 -1
  37. package/package.json +3 -3
  38. package/src/auth/credentials.ts +66 -0
  39. package/src/deploy/dns/hostinger-api.ts +258 -0
  40. package/src/deploy/dns/index.ts +399 -0
  41. package/src/deploy/dokploy-api.ts +175 -0
  42. package/src/deploy/index.ts +389 -240
  43. package/src/deploy/state.ts +146 -0
  44. package/src/workspace/types.ts +629 -47
  45. package/tsconfig.tsbuildinfo +1 -1
  46. package/dist/dokploy-api-Bdmk5ImW.cjs +0 -3
  47. package/dist/dokploy-api-BdxOMH_V.cjs.map +0 -1
  48. package/dist/dokploy-api-DWsqNjwP.mjs.map +0 -1
  49. package/dist/dokploy-api-tZSZaHd9.mjs +0 -3
  50. package/dist/index-CXa3odEw.d.mts.map +0 -1
  51. package/dist/index-E8Nu2Rxl.d.cts.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  const require_chunk = require('./chunk-CUT6urMc.cjs');
3
- const require_workspace = require('./workspace-BDAhr6Kb.cjs');
4
- const require_config = require('./config-xVZsRjN7.cjs');
5
- const require_openapi = require('./openapi-DhcCtKzM.cjs');
3
+ const require_workspace = require('./workspace-CaVW6j2q.cjs');
4
+ const require_config = require('./config-HYiM3iQJ.cjs');
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-BdxOMH_V.cjs');
7
+ const require_dokploy_api = require('./dokploy-api-BnX2OxyF.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.44.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-Bdmk5ImW.cjs"));
257
+ const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-4a6h35VY.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,
@@ -3336,7 +3362,7 @@ CMD ["node", "index.mjs"]
3336
3362
 
3337
3363
  //#endregion
3338
3364
  //#region src/docker/index.ts
3339
- const logger$5 = console;
3365
+ const logger$6 = console;
3340
3366
  /**
3341
3367
  * Docker command implementation
3342
3368
  * Generates Dockerfile, docker-compose.yml, and related files
@@ -3347,7 +3373,7 @@ const logger$5 = console;
3347
3373
  async function dockerCommand(options) {
3348
3374
  const loadedConfig = await require_config.loadWorkspaceConfig();
3349
3375
  if (loadedConfig.type === "workspace") {
3350
- logger$5.log("📦 Detected workspace configuration");
3376
+ logger$6.log("📦 Detected workspace configuration");
3351
3377
  return workspaceDockerCommand(loadedConfig.workspace, options);
3352
3378
  }
3353
3379
  const config = await require_config.loadConfig();
@@ -3368,14 +3394,14 @@ async function dockerCommand(options) {
3368
3394
  let useTurbo = options.turbo ?? false;
3369
3395
  if (inMonorepo && !useSlim) if (hasTurbo) {
3370
3396
  useTurbo = true;
3371
- logger$5.log(" Detected monorepo with turbo.json - using turbo prune");
3397
+ logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
3372
3398
  } 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
3399
  let turboPackage = options.turboPackage ?? dockerConfig.imageName;
3374
3400
  if (useTurbo && !options.turboPackage) try {
3375
3401
  const pkg$1 = require(`${process.cwd()}/package.json`);
3376
3402
  if (pkg$1.name) {
3377
3403
  turboPackage = pkg$1.name;
3378
- logger$5.log(` Turbo package: ${turboPackage}`);
3404
+ logger$6.log(` Turbo package: ${turboPackage}`);
3379
3405
  }
3380
3406
  } catch {}
3381
3407
  const templateOptions = {
@@ -3392,7 +3418,7 @@ async function dockerCommand(options) {
3392
3418
  const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
3393
3419
  const dockerfilePath = (0, node_path.join)(dockerDir, "Dockerfile");
3394
3420
  await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
3395
- logger$5.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
3421
+ logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
3396
3422
  const composeOptions = {
3397
3423
  imageName: dockerConfig.imageName,
3398
3424
  registry: options.registry ?? dockerConfig.registry,
@@ -3404,15 +3430,15 @@ async function dockerCommand(options) {
3404
3430
  const dockerCompose = hasServices ? generateDockerCompose(composeOptions) : generateMinimalDockerCompose(composeOptions);
3405
3431
  const composePath = (0, node_path.join)(dockerDir, "docker-compose.yml");
3406
3432
  await (0, node_fs_promises.writeFile)(composePath, dockerCompose);
3407
- logger$5.log("Generated: .gkm/docker/docker-compose.yml");
3433
+ logger$6.log("Generated: .gkm/docker/docker-compose.yml");
3408
3434
  const dockerignore = generateDockerignore();
3409
3435
  const dockerignorePath = (0, node_path.join)(process.cwd(), ".dockerignore");
3410
3436
  await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
3411
- logger$5.log("Generated: .dockerignore (project root)");
3437
+ logger$6.log("Generated: .dockerignore (project root)");
3412
3438
  const entrypoint = generateDockerEntrypoint();
3413
3439
  const entrypointPath = (0, node_path.join)(dockerDir, "docker-entrypoint.sh");
3414
3440
  await (0, node_fs_promises.writeFile)(entrypointPath, entrypoint);
3415
- logger$5.log("Generated: .gkm/docker/docker-entrypoint.sh");
3441
+ logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
3416
3442
  const result = {
3417
3443
  dockerfile: dockerfilePath,
3418
3444
  dockerCompose: composePath,
@@ -3431,13 +3457,13 @@ async function dockerCommand(options) {
3431
3457
  function ensureLockfile(cwd) {
3432
3458
  const lockfilePath = findLockfilePath(cwd);
3433
3459
  if (!lockfilePath) {
3434
- logger$5.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
3460
+ logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
3435
3461
  return null;
3436
3462
  }
3437
3463
  const lockfileName = (0, node_path.basename)(lockfilePath);
3438
3464
  const localLockfile = (0, node_path.join)(cwd, lockfileName);
3439
3465
  if (lockfilePath === localLockfile) return null;
3440
- logger$5.log(` Copying ${lockfileName} from monorepo root...`);
3466
+ logger$6.log(` Copying ${lockfileName} from monorepo root...`);
3441
3467
  (0, node_fs.copyFileSync)(lockfilePath, localLockfile);
3442
3468
  return () => {
3443
3469
  try {
@@ -3453,7 +3479,7 @@ async function buildDockerImage(imageName, options) {
3453
3479
  const tag = options.tag ?? "latest";
3454
3480
  const registry = options.registry;
3455
3481
  const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
3456
- logger$5.log(`\n🐳 Building Docker image: ${fullImageName}`);
3482
+ logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
3457
3483
  const cwd = process.cwd();
3458
3484
  const cleanup = ensureLockfile(cwd);
3459
3485
  try {
@@ -3465,7 +3491,7 @@ async function buildDockerImage(imageName, options) {
3465
3491
  DOCKER_BUILDKIT: "1"
3466
3492
  }
3467
3493
  });
3468
- logger$5.log(`✅ Docker image built: ${fullImageName}`);
3494
+ logger$6.log(`✅ Docker image built: ${fullImageName}`);
3469
3495
  } catch (error) {
3470
3496
  throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3471
3497
  } finally {
@@ -3480,13 +3506,13 @@ async function pushDockerImage(imageName, options) {
3480
3506
  const registry = options.registry;
3481
3507
  if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
3482
3508
  const fullImageName = `${registry}/${imageName}:${tag}`;
3483
- logger$5.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
3509
+ logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
3484
3510
  try {
3485
3511
  (0, node_child_process.execSync)(`docker push ${fullImageName}`, {
3486
3512
  cwd: process.cwd(),
3487
3513
  stdio: "inherit"
3488
3514
  });
3489
- logger$5.log(`✅ Docker image pushed: ${fullImageName}`);
3515
+ logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
3490
3516
  } catch (error) {
3491
3517
  throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3492
3518
  }
@@ -3512,11 +3538,11 @@ function getAppPackageName(appPath) {
3512
3538
  async function workspaceDockerCommand(workspace, options) {
3513
3539
  const results = [];
3514
3540
  const apps = Object.entries(workspace.apps);
3515
- logger$5.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
3541
+ logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
3516
3542
  const dockerDir = (0, node_path.join)(workspace.root, ".gkm", "docker");
3517
3543
  await (0, node_fs_promises.mkdir)(dockerDir, { recursive: true });
3518
3544
  const packageManager = detectPackageManager$1(workspace.root);
3519
- logger$5.log(` Package manager: ${packageManager}`);
3545
+ logger$6.log(` Package manager: ${packageManager}`);
3520
3546
  for (const [appName, app] of apps) {
3521
3547
  const appPath = app.path;
3522
3548
  const fullAppPath = (0, node_path.join)(workspace.root, appPath);
@@ -3524,7 +3550,7 @@ async function workspaceDockerCommand(workspace, options) {
3524
3550
  const imageName = appName;
3525
3551
  const hasEntry = !!app.entry;
3526
3552
  const buildType = hasEntry ? "entry" : app.type;
3527
- logger$5.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
3553
+ logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
3528
3554
  let dockerfile;
3529
3555
  if (app.type === "frontend") dockerfile = generateNextjsDockerfile({
3530
3556
  imageName,
@@ -3555,7 +3581,7 @@ async function workspaceDockerCommand(workspace, options) {
3555
3581
  });
3556
3582
  const dockerfilePath = (0, node_path.join)(dockerDir, `Dockerfile.${appName}`);
3557
3583
  await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
3558
- logger$5.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
3584
+ logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
3559
3585
  results.push({
3560
3586
  appName,
3561
3587
  type: app.type,
@@ -3566,19 +3592,19 @@ async function workspaceDockerCommand(workspace, options) {
3566
3592
  const dockerignore = generateDockerignore();
3567
3593
  const dockerignorePath = (0, node_path.join)(workspace.root, ".dockerignore");
3568
3594
  await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
3569
- logger$5.log(`\n Generated: .dockerignore (workspace root)`);
3595
+ logger$6.log(`\n Generated: .dockerignore (workspace root)`);
3570
3596
  const dockerCompose = generateWorkspaceCompose(workspace, { registry: options.registry });
3571
3597
  const composePath = (0, node_path.join)(dockerDir, "docker-compose.yml");
3572
3598
  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:");
3599
+ logger$6.log(` Generated: .gkm/docker/docker-compose.yml`);
3600
+ logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
3601
+ logger$6.log("\n📋 Build commands:");
3576
3602
  for (const result of results) {
3577
3603
  const icon = result.type === "backend" ? "⚙️" : "🌐";
3578
- logger$5.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
3604
+ logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
3579
3605
  }
3580
- logger$5.log("\n📋 Run all services:");
3581
- logger$5.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
3606
+ logger$6.log("\n📋 Run all services:");
3607
+ logger$6.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
3582
3608
  return {
3583
3609
  apps: results,
3584
3610
  dockerCompose: composePath,
@@ -3618,7 +3644,7 @@ function getAppNameFromPackageJson() {
3618
3644
  } catch {}
3619
3645
  return void 0;
3620
3646
  }
3621
- const logger$4 = console;
3647
+ const logger$5 = console;
3622
3648
  /**
3623
3649
  * Get the full image reference
3624
3650
  */
@@ -3633,18 +3659,18 @@ function getImageRef(registry, imageName, tag) {
3633
3659
  * @param buildArgs - Build arguments to pass to docker build
3634
3660
  */
3635
3661
  async function buildImage(imageRef, appName, buildArgs) {
3636
- logger$4.log(`\n🔨 Building Docker image: ${imageRef}`);
3662
+ logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
3637
3663
  const cwd = process.cwd();
3638
3664
  const lockfilePath = findLockfilePath(cwd);
3639
3665
  const lockfileDir = lockfilePath ? (0, node_path.dirname)(lockfilePath) : cwd;
3640
3666
  const inMonorepo = lockfileDir !== cwd;
3641
- if (appName || inMonorepo) logger$4.log(" Generating Dockerfile for monorepo (turbo prune)...");
3642
- else logger$4.log(" Generating Dockerfile...");
3667
+ if (appName || inMonorepo) logger$5.log(" Generating Dockerfile for monorepo (turbo prune)...");
3668
+ else logger$5.log(" Generating Dockerfile...");
3643
3669
  await dockerCommand({});
3644
3670
  const dockerfileSuffix = appName ? `.${appName}` : "";
3645
3671
  const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
3646
3672
  const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
3647
- if (buildCwd !== cwd) logger$4.log(` Building from workspace root: ${buildCwd}`);
3673
+ if (buildCwd !== cwd) logger$5.log(` Building from workspace root: ${buildCwd}`);
3648
3674
  const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
3649
3675
  try {
3650
3676
  const cmd = [
@@ -3663,7 +3689,7 @@ async function buildImage(imageRef, appName, buildArgs) {
3663
3689
  DOCKER_BUILDKIT: "1"
3664
3690
  }
3665
3691
  });
3666
- logger$4.log(`✅ Image built: ${imageRef}`);
3692
+ logger$5.log(`✅ Image built: ${imageRef}`);
3667
3693
  } catch (error) {
3668
3694
  throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3669
3695
  }
@@ -3672,13 +3698,13 @@ async function buildImage(imageRef, appName, buildArgs) {
3672
3698
  * Push Docker image to registry
3673
3699
  */
3674
3700
  async function pushImage(imageRef) {
3675
- logger$4.log(`\n☁️ Pushing image: ${imageRef}`);
3701
+ logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
3676
3702
  try {
3677
3703
  (0, node_child_process.execSync)(`docker push ${imageRef}`, {
3678
3704
  cwd: process.cwd(),
3679
3705
  stdio: "inherit"
3680
3706
  });
3681
- logger$4.log(`✅ Image pushed: ${imageRef}`);
3707
+ logger$5.log(`✅ Image pushed: ${imageRef}`);
3682
3708
  } catch (error) {
3683
3709
  throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
3684
3710
  }
@@ -3691,17 +3717,17 @@ async function deployDocker(options) {
3691
3717
  const imageName = config.imageName;
3692
3718
  const imageRef = getImageRef(config.registry, imageName, tag);
3693
3719
  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");
3720
+ if (!skipPush) if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
3695
3721
  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}`);
3722
+ logger$5.log("\n✅ Docker deployment ready!");
3723
+ logger$5.log(`\n📋 Deployment details:`);
3724
+ logger$5.log(` Image: ${imageRef}`);
3725
+ logger$5.log(` Stage: ${stage}`);
3700
3726
  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}`);
3727
+ logger$5.log(`\n🔐 Deploy with this environment variable:`);
3728
+ logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
3729
+ logger$5.log("\n Example docker run:");
3730
+ logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
3705
3731
  }
3706
3732
  return {
3707
3733
  imageRef,
@@ -3728,7 +3754,7 @@ function resolveDockerConfig(config) {
3728
3754
 
3729
3755
  //#endregion
3730
3756
  //#region src/deploy/dokploy.ts
3731
- const logger$3 = console;
3757
+ const logger$4 = console;
3732
3758
  /**
3733
3759
  * Get the Dokploy API token from stored credentials or environment
3734
3760
  */
@@ -3752,25 +3778,25 @@ async function createApi$1(endpoint) {
3752
3778
  */
3753
3779
  async function deployDokploy(options) {
3754
3780
  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}`);
3781
+ logger$4.log(`\n🎯 Deploying to Dokploy...`);
3782
+ logger$4.log(` Endpoint: ${config.endpoint}`);
3783
+ logger$4.log(` Application: ${config.applicationId}`);
3758
3784
  const api = await createApi$1(config.endpoint);
3759
- logger$3.log(` Configuring Docker image: ${imageRef}`);
3785
+ logger$4.log(` Configuring Docker image: ${imageRef}`);
3760
3786
  const registryOptions = {};
3761
3787
  if (config.registryId) {
3762
3788
  registryOptions.registryId = config.registryId;
3763
- logger$3.log(` Using Dokploy registry: ${config.registryId}`);
3789
+ logger$4.log(` Using Dokploy registry: ${config.registryId}`);
3764
3790
  } else {
3765
3791
  const storedRegistryId = await getDokployRegistryId();
3766
3792
  if (storedRegistryId) {
3767
3793
  registryOptions.registryId = storedRegistryId;
3768
- logger$3.log(` Using stored Dokploy registry: ${storedRegistryId}`);
3794
+ logger$4.log(` Using stored Dokploy registry: ${storedRegistryId}`);
3769
3795
  } else if (config.registryCredentials) {
3770
3796
  registryOptions.username = config.registryCredentials.username;
3771
3797
  registryOptions.password = config.registryCredentials.password;
3772
3798
  registryOptions.registryUrl = config.registryCredentials.registryUrl;
3773
- logger$3.log(` Using registry credentials for: ${config.registryCredentials.registryUrl}`);
3799
+ logger$4.log(` Using registry credentials for: ${config.registryCredentials.registryUrl}`);
3774
3800
  } else {
3775
3801
  const username = process.env.DOCKER_REGISTRY_USERNAME;
3776
3802
  const password = process.env.DOCKER_REGISTRY_PASSWORD;
@@ -3779,31 +3805,31 @@ async function deployDokploy(options) {
3779
3805
  registryOptions.username = username;
3780
3806
  registryOptions.password = password;
3781
3807
  registryOptions.registryUrl = registryUrl;
3782
- logger$3.log(` Using registry credentials from environment`);
3808
+ logger$4.log(` Using registry credentials from environment`);
3783
3809
  }
3784
3810
  }
3785
3811
  }
3786
3812
  await api.saveDockerProvider(config.applicationId, imageRef, registryOptions);
3787
- logger$3.log(" ✓ Docker provider configured");
3813
+ logger$4.log(" ✓ Docker provider configured");
3788
3814
  const envVars = {};
3789
3815
  if (masterKey) envVars.GKM_MASTER_KEY = masterKey;
3790
3816
  if (Object.keys(envVars).length > 0) {
3791
- logger$3.log(" Updating environment variables...");
3817
+ logger$4.log(" Updating environment variables...");
3792
3818
  const envString = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
3793
3819
  await api.saveApplicationEnv(config.applicationId, envString);
3794
- logger$3.log(" ✓ Environment variables updated");
3820
+ logger$4.log(" ✓ Environment variables updated");
3795
3821
  }
3796
- logger$3.log(" Triggering deployment...");
3822
+ logger$4.log(" Triggering deployment...");
3797
3823
  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`);
3824
+ logger$4.log(" ✓ Deployment triggered");
3825
+ logger$4.log("\n✅ Dokploy deployment initiated!");
3826
+ logger$4.log(`\n📋 Deployment details:`);
3827
+ logger$4.log(` Image: ${imageRef}`);
3828
+ logger$4.log(` Stage: ${stage}`);
3829
+ logger$4.log(` Application ID: ${config.applicationId}`);
3830
+ if (masterKey) logger$4.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
3805
3831
  const deploymentUrl = `${config.endpoint}/project/${config.projectId}`;
3806
- logger$3.log(`\n🔗 View deployment: ${deploymentUrl}`);
3832
+ logger$4.log(`\n🔗 View deployment: ${deploymentUrl}`);
3807
3833
  return {
3808
3834
  imageRef,
3809
3835
  masterKey,
@@ -3811,6 +3837,473 @@ async function deployDokploy(options) {
3811
3837
  };
3812
3838
  }
3813
3839
 
3840
+ //#endregion
3841
+ //#region src/deploy/state.ts
3842
+ /**
3843
+ * Get the state file path for a stage
3844
+ */
3845
+ function getStateFilePath(workspaceRoot, stage) {
3846
+ return (0, node_path.join)(workspaceRoot, ".gkm", `deploy-${stage}.json`);
3847
+ }
3848
+ /**
3849
+ * Read the deploy state for a stage
3850
+ * Returns null if state file doesn't exist
3851
+ */
3852
+ async function readStageState(workspaceRoot, stage) {
3853
+ const filePath = getStateFilePath(workspaceRoot, stage);
3854
+ try {
3855
+ const content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
3856
+ return JSON.parse(content);
3857
+ } catch (error) {
3858
+ if (error.code === "ENOENT") return null;
3859
+ console.warn(`Warning: Could not read deploy state: ${error}`);
3860
+ return null;
3861
+ }
3862
+ }
3863
+ /**
3864
+ * Write the deploy state for a stage
3865
+ */
3866
+ async function writeStageState(workspaceRoot, stage, state) {
3867
+ const filePath = getStateFilePath(workspaceRoot, stage);
3868
+ const dir = (0, node_path.join)(workspaceRoot, ".gkm");
3869
+ await (0, node_fs_promises.mkdir)(dir, { recursive: true });
3870
+ state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
3871
+ await (0, node_fs_promises.writeFile)(filePath, JSON.stringify(state, null, 2));
3872
+ }
3873
+ /**
3874
+ * Create a new empty state for a stage
3875
+ */
3876
+ function createEmptyState(stage, environmentId) {
3877
+ return {
3878
+ provider: "dokploy",
3879
+ stage,
3880
+ environmentId,
3881
+ applications: {},
3882
+ services: {},
3883
+ lastDeployedAt: (/* @__PURE__ */ new Date()).toISOString()
3884
+ };
3885
+ }
3886
+ /**
3887
+ * Get application ID from state
3888
+ */
3889
+ function getApplicationId(state, appName) {
3890
+ return state?.applications[appName];
3891
+ }
3892
+ /**
3893
+ * Set application ID in state (mutates state)
3894
+ */
3895
+ function setApplicationId(state, appName, applicationId) {
3896
+ state.applications[appName] = applicationId;
3897
+ }
3898
+ /**
3899
+ * Get postgres ID from state
3900
+ */
3901
+ function getPostgresId(state) {
3902
+ return state?.services.postgresId;
3903
+ }
3904
+ /**
3905
+ * Set postgres ID in state (mutates state)
3906
+ */
3907
+ function setPostgresId(state, postgresId) {
3908
+ state.services.postgresId = postgresId;
3909
+ }
3910
+ /**
3911
+ * Get redis ID from state
3912
+ */
3913
+ function getRedisId(state) {
3914
+ return state?.services.redisId;
3915
+ }
3916
+ /**
3917
+ * Set redis ID in state (mutates state)
3918
+ */
3919
+ function setRedisId(state, redisId) {
3920
+ state.services.redisId = redisId;
3921
+ }
3922
+
3923
+ //#endregion
3924
+ //#region src/deploy/dns/hostinger-api.ts
3925
+ /**
3926
+ * Hostinger DNS API client
3927
+ *
3928
+ * API Documentation: https://developers.hostinger.com/
3929
+ * Authentication: Bearer token from hpanel.hostinger.com/profile/api
3930
+ */
3931
+ const HOSTINGER_API_BASE = "https://api.hostinger.com";
3932
+ /**
3933
+ * Hostinger API error
3934
+ */
3935
+ var HostingerApiError = class extends Error {
3936
+ constructor(message, status, statusText, errors) {
3937
+ super(message);
3938
+ this.status = status;
3939
+ this.statusText = statusText;
3940
+ this.errors = errors;
3941
+ this.name = "HostingerApiError";
3942
+ }
3943
+ };
3944
+ /**
3945
+ * Hostinger DNS API client
3946
+ *
3947
+ * @example
3948
+ * ```ts
3949
+ * const api = new HostingerApi(token);
3950
+ *
3951
+ * // Get all records for a domain
3952
+ * const records = await api.getRecords('traflabs.io');
3953
+ *
3954
+ * // Create/update records
3955
+ * await api.upsertRecords('traflabs.io', [
3956
+ * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
3957
+ * ]);
3958
+ * ```
3959
+ */
3960
+ var HostingerApi = class {
3961
+ token;
3962
+ constructor(token) {
3963
+ this.token = token;
3964
+ }
3965
+ /**
3966
+ * Make a request to the Hostinger API
3967
+ */
3968
+ async request(method, endpoint, body) {
3969
+ const url = `${HOSTINGER_API_BASE}${endpoint}`;
3970
+ const response = await fetch(url, {
3971
+ method,
3972
+ headers: {
3973
+ "Content-Type": "application/json",
3974
+ Authorization: `Bearer ${this.token}`
3975
+ },
3976
+ body: body ? JSON.stringify(body) : void 0
3977
+ });
3978
+ if (!response.ok) {
3979
+ let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
3980
+ let errors;
3981
+ try {
3982
+ const errorBody = await response.json();
3983
+ if (errorBody.message) errorMessage = `Hostinger API error: ${errorBody.message}`;
3984
+ errors = errorBody.errors;
3985
+ } catch {}
3986
+ throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
3987
+ }
3988
+ const text = await response.text();
3989
+ if (!text || text.trim() === "") return void 0;
3990
+ return JSON.parse(text);
3991
+ }
3992
+ /**
3993
+ * Get all DNS records for a domain
3994
+ *
3995
+ * @param domain - Root domain (e.g., 'traflabs.io')
3996
+ */
3997
+ async getRecords(domain) {
3998
+ const response = await this.request("GET", `/api/dns/v1/zones/${domain}`);
3999
+ return response.data || [];
4000
+ }
4001
+ /**
4002
+ * Create or update DNS records
4003
+ *
4004
+ * @param domain - Root domain (e.g., 'traflabs.io')
4005
+ * @param records - Records to create/update
4006
+ * @param overwrite - If true, replaces all existing records. If false, merges with existing.
4007
+ */
4008
+ async upsertRecords(domain, records, overwrite = false) {
4009
+ await this.request("PUT", `/api/dns/v1/zones/${domain}`, {
4010
+ overwrite,
4011
+ zone: records
4012
+ });
4013
+ }
4014
+ /**
4015
+ * Validate DNS records before applying
4016
+ *
4017
+ * @param domain - Root domain (e.g., 'traflabs.io')
4018
+ * @param records - Records to validate
4019
+ * @returns true if valid, throws if invalid
4020
+ */
4021
+ async validateRecords(domain, records) {
4022
+ await this.request("POST", `/api/dns/v1/zones/${domain}/validate`, {
4023
+ overwrite: false,
4024
+ zone: records
4025
+ });
4026
+ return true;
4027
+ }
4028
+ /**
4029
+ * Delete specific DNS records
4030
+ *
4031
+ * @param domain - Root domain (e.g., 'traflabs.io')
4032
+ * @param filters - Filters to match records for deletion
4033
+ */
4034
+ async deleteRecords(domain, filters) {
4035
+ await this.request("DELETE", `/api/dns/v1/zones/${domain}`, { filters });
4036
+ }
4037
+ /**
4038
+ * Check if a specific record exists
4039
+ *
4040
+ * @param domain - Root domain (e.g., 'traflabs.io')
4041
+ * @param name - Subdomain name (e.g., 'api.joemoer')
4042
+ * @param type - Record type (e.g., 'A')
4043
+ */
4044
+ async recordExists(domain, name$1, type$1 = "A") {
4045
+ const records = await this.getRecords(domain);
4046
+ return records.some((r) => r.name === name$1 && r.type === type$1);
4047
+ }
4048
+ /**
4049
+ * Create a single A record if it doesn't exist
4050
+ *
4051
+ * @param domain - Root domain (e.g., 'traflabs.io')
4052
+ * @param subdomain - Subdomain name (e.g., 'api.joemoer')
4053
+ * @param ip - IP address to point to
4054
+ * @param ttl - TTL in seconds (default: 300)
4055
+ * @returns true if created, false if already exists
4056
+ */
4057
+ async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
4058
+ const exists = await this.recordExists(domain, subdomain, "A");
4059
+ if (exists) return false;
4060
+ await this.upsertRecords(domain, [{
4061
+ name: subdomain,
4062
+ type: "A",
4063
+ ttl,
4064
+ records: [ip]
4065
+ }]);
4066
+ return true;
4067
+ }
4068
+ };
4069
+
4070
+ //#endregion
4071
+ //#region src/deploy/dns/index.ts
4072
+ const logger$3 = console;
4073
+ /**
4074
+ * Resolve IP address from a hostname
4075
+ */
4076
+ async function resolveHostnameToIp(hostname) {
4077
+ try {
4078
+ const addresses = await (0, node_dns_promises.lookup)(hostname, { family: 4 });
4079
+ return addresses.address;
4080
+ } catch (error) {
4081
+ throw new Error(`Failed to resolve IP for ${hostname}: ${error instanceof Error ? error.message : "Unknown error"}`);
4082
+ }
4083
+ }
4084
+ /**
4085
+ * Extract subdomain from full hostname relative to root domain
4086
+ *
4087
+ * @example
4088
+ * extractSubdomain('api.joemoer.traflabs.io', 'traflabs.io') => 'api.joemoer'
4089
+ * extractSubdomain('joemoer.traflabs.io', 'traflabs.io') => 'joemoer'
4090
+ */
4091
+ function extractSubdomain(hostname, rootDomain) {
4092
+ if (!hostname.endsWith(rootDomain)) throw new Error(`Hostname ${hostname} is not under root domain ${rootDomain}`);
4093
+ const subdomain = hostname.slice(0, -(rootDomain.length + 1));
4094
+ return subdomain || "@";
4095
+ }
4096
+ /**
4097
+ * Generate required DNS records for a deployment
4098
+ */
4099
+ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
4100
+ const records = [];
4101
+ for (const [appName, hostname] of appHostnames) {
4102
+ const subdomain = extractSubdomain(hostname, rootDomain);
4103
+ records.push({
4104
+ hostname,
4105
+ subdomain,
4106
+ type: "A",
4107
+ value: serverIp,
4108
+ appName
4109
+ });
4110
+ }
4111
+ return records;
4112
+ }
4113
+ /**
4114
+ * Print DNS records table
4115
+ */
4116
+ function printDnsRecordsTable(records, rootDomain) {
4117
+ logger$3.log("\n 📋 DNS Records for " + rootDomain + ":");
4118
+ logger$3.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
4119
+ logger$3.log(" │ Subdomain │ Type │ Value │ Status │");
4120
+ logger$3.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
4121
+ for (const record of records) {
4122
+ const subdomain = record.subdomain.padEnd(35);
4123
+ const type$1 = record.type.padEnd(4);
4124
+ const value = record.value.padEnd(15);
4125
+ let status;
4126
+ if (record.error) status = "✗";
4127
+ else if (record.created) status = "✓ new";
4128
+ else if (record.existed) status = "✓";
4129
+ else status = "?";
4130
+ logger$3.log(` │ ${subdomain} │ ${type$1} │ ${value} │ ${status.padEnd(6)} │`);
4131
+ }
4132
+ logger$3.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
4133
+ }
4134
+ /**
4135
+ * Print DNS records in a simple format for manual setup
4136
+ */
4137
+ function printDnsRecordsSimple(records, rootDomain) {
4138
+ logger$3.log("\n 📋 Required DNS Records:");
4139
+ logger$3.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
4140
+ for (const record of records) logger$3.log(` ${record.subdomain} → ${record.value} (A record)`);
4141
+ logger$3.log("");
4142
+ }
4143
+ /**
4144
+ * Prompt for input (reuse from deploy/index.ts pattern)
4145
+ */
4146
+ async function promptForToken(message) {
4147
+ const { stdin, stdout } = await import("node:process");
4148
+ const readline = await import("node:readline/promises");
4149
+ if (!stdin.isTTY) throw new Error("Interactive input required for Hostinger token.");
4150
+ stdout.write(message);
4151
+ return new Promise((resolve$3) => {
4152
+ let value = "";
4153
+ const onData = (char) => {
4154
+ const c = char.toString();
4155
+ if (c === "\n" || c === "\r") {
4156
+ stdin.setRawMode(false);
4157
+ stdin.pause();
4158
+ stdin.removeListener("data", onData);
4159
+ stdout.write("\n");
4160
+ resolve$3(value);
4161
+ } else if (c === "") {
4162
+ stdin.setRawMode(false);
4163
+ stdin.pause();
4164
+ stdout.write("\n");
4165
+ process.exit(1);
4166
+ } else if (c === "" || c === "\b") {
4167
+ if (value.length > 0) value = value.slice(0, -1);
4168
+ } else value += c;
4169
+ };
4170
+ stdin.setRawMode(true);
4171
+ stdin.resume();
4172
+ stdin.on("data", onData);
4173
+ });
4174
+ }
4175
+ /**
4176
+ * Create DNS records using the configured provider
4177
+ */
4178
+ async function createDnsRecords(records, dnsConfig) {
4179
+ const { provider, domain: rootDomain, ttl = 300 } = dnsConfig;
4180
+ if (provider === "manual") return records.map((r) => ({
4181
+ ...r,
4182
+ created: false,
4183
+ existed: false
4184
+ }));
4185
+ if (provider === "hostinger") return createHostingerRecords(records, rootDomain, ttl);
4186
+ if (provider === "cloudflare") {
4187
+ logger$3.log(" ⚠ Cloudflare DNS integration not yet implemented");
4188
+ return records.map((r) => ({
4189
+ ...r,
4190
+ error: "Cloudflare not implemented"
4191
+ }));
4192
+ }
4193
+ return records;
4194
+ }
4195
+ /**
4196
+ * Create DNS records at Hostinger
4197
+ */
4198
+ async function createHostingerRecords(records, rootDomain, ttl) {
4199
+ let token = await getHostingerToken();
4200
+ if (!token) {
4201
+ logger$3.log("\n 📋 Hostinger API token not found.");
4202
+ logger$3.log(" Get your token from: https://hpanel.hostinger.com/profile/api\n");
4203
+ try {
4204
+ token = await promptForToken(" Hostinger API Token: ");
4205
+ await storeHostingerToken(token);
4206
+ logger$3.log(" ✓ Token saved");
4207
+ } catch {
4208
+ logger$3.log(" ⚠ Could not get token, skipping DNS creation");
4209
+ return records.map((r) => ({
4210
+ ...r,
4211
+ error: "No API token"
4212
+ }));
4213
+ }
4214
+ }
4215
+ const api = new HostingerApi(token);
4216
+ const results = [];
4217
+ let existingRecords = [];
4218
+ try {
4219
+ existingRecords = await api.getRecords(rootDomain);
4220
+ } catch (error) {
4221
+ const message = error instanceof Error ? error.message : "Unknown error";
4222
+ logger$3.log(` ⚠ Failed to fetch existing DNS records: ${message}`);
4223
+ return records.map((r) => ({
4224
+ ...r,
4225
+ error: message
4226
+ }));
4227
+ }
4228
+ for (const record of records) {
4229
+ const existing = existingRecords.find((r) => r.name === record.subdomain && r.type === "A");
4230
+ if (existing) {
4231
+ results.push({
4232
+ ...record,
4233
+ existed: true,
4234
+ created: false
4235
+ });
4236
+ continue;
4237
+ }
4238
+ try {
4239
+ await api.upsertRecords(rootDomain, [{
4240
+ name: record.subdomain,
4241
+ type: "A",
4242
+ ttl,
4243
+ records: [record.value]
4244
+ }]);
4245
+ results.push({
4246
+ ...record,
4247
+ created: true,
4248
+ existed: false
4249
+ });
4250
+ } catch (error) {
4251
+ const message = error instanceof Error ? error.message : "Unknown error";
4252
+ results.push({
4253
+ ...record,
4254
+ error: message
4255
+ });
4256
+ }
4257
+ }
4258
+ return results;
4259
+ }
4260
+ /**
4261
+ * Main DNS orchestration function for deployments
4262
+ */
4263
+ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint) {
4264
+ if (!dnsConfig) return null;
4265
+ const { domain: rootDomain, autoCreate = true } = dnsConfig;
4266
+ logger$3.log("\n🌐 Setting up DNS records...");
4267
+ let serverIp;
4268
+ try {
4269
+ const endpointUrl = new URL(dokployEndpoint);
4270
+ serverIp = await resolveHostnameToIp(endpointUrl.hostname);
4271
+ logger$3.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
4272
+ } catch (error) {
4273
+ const message = error instanceof Error ? error.message : "Unknown error";
4274
+ logger$3.log(` ⚠ Failed to resolve server IP: ${message}`);
4275
+ return null;
4276
+ }
4277
+ const requiredRecords = generateRequiredRecords(appHostnames, rootDomain, serverIp);
4278
+ if (requiredRecords.length === 0) {
4279
+ logger$3.log(" No DNS records needed");
4280
+ return {
4281
+ records: [],
4282
+ success: true,
4283
+ serverIp
4284
+ };
4285
+ }
4286
+ let finalRecords;
4287
+ if (autoCreate && dnsConfig.provider !== "manual") {
4288
+ logger$3.log(` Creating DNS records at ${dnsConfig.provider}...`);
4289
+ finalRecords = await createDnsRecords(requiredRecords, dnsConfig);
4290
+ const created = finalRecords.filter((r) => r.created).length;
4291
+ const existed = finalRecords.filter((r) => r.existed).length;
4292
+ const failed = finalRecords.filter((r) => r.error).length;
4293
+ if (created > 0) logger$3.log(` ✓ Created ${created} DNS record(s)`);
4294
+ if (existed > 0) logger$3.log(` ✓ ${existed} record(s) already exist`);
4295
+ if (failed > 0) logger$3.log(` ⚠ ${failed} record(s) failed`);
4296
+ } else finalRecords = requiredRecords;
4297
+ printDnsRecordsTable(finalRecords, rootDomain);
4298
+ const hasFailures = finalRecords.some((r) => r.error);
4299
+ if (dnsConfig.provider === "manual" || hasFailures) printDnsRecordsSimple(finalRecords.filter((r) => !r.created && !r.existed), rootDomain);
4300
+ return {
4301
+ records: finalRecords,
4302
+ success: !hasFailures,
4303
+ serverIp
4304
+ };
4305
+ }
4306
+
3814
4307
  //#endregion
3815
4308
  //#region src/deploy/domain.ts
3816
4309
  /**
@@ -4315,24 +4808,39 @@ async function prompt(message, hidden = false) {
4315
4808
  * Provision docker compose services in Dokploy
4316
4809
  * @internal Exported for testing
4317
4810
  */
4318
- async function provisionServices(api, projectId, environmentId, appName, services, existingUrls) {
4811
+ async function provisionServices(api, projectId, environmentId, appName, services, existingServiceIds) {
4319
4812
  logger$1.log(`\n🔍 provisionServices called: services=${JSON.stringify(services)}, envId=${environmentId}`);
4320
4813
  if (!services || !environmentId) {
4321
4814
  logger$1.log(" Skipping: no services or no environmentId");
4322
4815
  return void 0;
4323
4816
  }
4324
4817
  const serviceUrls = {};
4325
- if (services.postgres) if (existingUrls?.DATABASE_URL) logger$1.log("\n🐘 PostgreSQL: Already configured (skipping)");
4326
- else {
4327
- logger$1.log("\n🐘 Provisioning PostgreSQL...");
4328
- const postgresName = `${appName}-db`;
4818
+ const serviceIds = {};
4819
+ if (services.postgres) {
4820
+ logger$1.log("\n🐘 Checking PostgreSQL...");
4821
+ const postgresName = "db";
4329
4822
  try {
4330
- const { randomBytes: randomBytes$1 } = await import("node:crypto");
4331
- const databasePassword = randomBytes$1(16).toString("hex");
4332
- const postgres = await api.createPostgres(postgresName, projectId, environmentId, { databasePassword });
4333
- logger$1.log(` Created PostgreSQL: ${postgres.postgresId}`);
4334
- await api.deployPostgres(postgres.postgresId);
4335
- logger$1.log(" ✓ PostgreSQL deployed");
4823
+ let postgres = null;
4824
+ let created = false;
4825
+ if (existingServiceIds?.postgresId) {
4826
+ logger$1.log(` Using cached ID: ${existingServiceIds.postgresId}`);
4827
+ postgres = await api.getPostgres(existingServiceIds.postgresId);
4828
+ if (postgres) logger$1.log(` ✓ PostgreSQL found: ${postgres.postgresId}`);
4829
+ else logger$1.log(` ⚠ Cached ID invalid, will create new`);
4830
+ }
4831
+ if (!postgres) {
4832
+ const { randomBytes: randomBytes$1 } = await import("node:crypto");
4833
+ const databasePassword = randomBytes$1(16).toString("hex");
4834
+ const result = await api.findOrCreatePostgres(postgresName, projectId, environmentId, { databasePassword });
4835
+ postgres = result.postgres;
4836
+ created = result.created;
4837
+ if (created) {
4838
+ logger$1.log(` ✓ Created PostgreSQL: ${postgres.postgresId}`);
4839
+ await api.deployPostgres(postgres.postgresId);
4840
+ logger$1.log(" ✓ PostgreSQL deployed");
4841
+ } else logger$1.log(` ✓ PostgreSQL already exists: ${postgres.postgresId}`);
4842
+ }
4843
+ serviceIds.postgresId = postgres.postgresId;
4336
4844
  serviceUrls.DATABASE_HOST = postgres.appName;
4337
4845
  serviceUrls.DATABASE_PORT = "5432";
4338
4846
  serviceUrls.DATABASE_NAME = postgres.databaseName;
@@ -4342,21 +4850,34 @@ async function provisionServices(api, projectId, environmentId, appName, service
4342
4850
  logger$1.log(` ✓ Database credentials configured`);
4343
4851
  } catch (error) {
4344
4852
  const message = error instanceof Error ? error.message : "Unknown error";
4345
- if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` PostgreSQL already exists`);
4346
- else logger$1.log(` ⚠ Failed to provision PostgreSQL: ${message}`);
4853
+ logger$1.log(` Failed to provision PostgreSQL: ${message}`);
4347
4854
  }
4348
4855
  }
4349
- if (services.redis) if (existingUrls?.REDIS_URL) logger$1.log("\n🔴 Redis: Already configured (skipping)");
4350
- else {
4351
- logger$1.log("\n🔴 Provisioning Redis...");
4352
- const redisName = `${appName}-cache`;
4856
+ if (services.redis) {
4857
+ logger$1.log("\n🔴 Checking Redis...");
4858
+ const redisName = "cache";
4353
4859
  try {
4354
- const { randomBytes: randomBytes$1 } = await import("node:crypto");
4355
- const databasePassword = randomBytes$1(16).toString("hex");
4356
- const redis = await api.createRedis(redisName, projectId, environmentId, { databasePassword });
4357
- logger$1.log(` Created Redis: ${redis.redisId}`);
4358
- await api.deployRedis(redis.redisId);
4359
- logger$1.log(" ✓ Redis deployed");
4860
+ let redis = null;
4861
+ let created = false;
4862
+ if (existingServiceIds?.redisId) {
4863
+ logger$1.log(` Using cached ID: ${existingServiceIds.redisId}`);
4864
+ redis = await api.getRedis(existingServiceIds.redisId);
4865
+ if (redis) logger$1.log(` ✓ Redis found: ${redis.redisId}`);
4866
+ else logger$1.log(` ⚠ Cached ID invalid, will create new`);
4867
+ }
4868
+ if (!redis) {
4869
+ const { randomBytes: randomBytes$1 } = await import("node:crypto");
4870
+ const databasePassword = randomBytes$1(16).toString("hex");
4871
+ const result = await api.findOrCreateRedis(redisName, projectId, environmentId, { databasePassword });
4872
+ redis = result.redis;
4873
+ created = result.created;
4874
+ if (created) {
4875
+ logger$1.log(` ✓ Created Redis: ${redis.redisId}`);
4876
+ await api.deployRedis(redis.redisId);
4877
+ logger$1.log(" ✓ Redis deployed");
4878
+ } else logger$1.log(` ✓ Redis already exists: ${redis.redisId}`);
4879
+ }
4880
+ serviceIds.redisId = redis.redisId;
4360
4881
  serviceUrls.REDIS_HOST = redis.appName;
4361
4882
  serviceUrls.REDIS_PORT = "6379";
4362
4883
  if (redis.databasePassword) serviceUrls.REDIS_PASSWORD = redis.databasePassword;
@@ -4365,11 +4886,13 @@ async function provisionServices(api, projectId, environmentId, appName, service
4365
4886
  logger$1.log(` ✓ Redis credentials configured`);
4366
4887
  } catch (error) {
4367
4888
  const message = error instanceof Error ? error.message : "Unknown error";
4368
- if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` Redis already exists`);
4369
- else logger$1.log(` ⚠ Failed to provision Redis: ${message}`);
4889
+ logger$1.log(` Failed to provision Redis: ${message}`);
4370
4890
  }
4371
4891
  }
4372
- return Object.keys(serviceUrls).length > 0 ? serviceUrls : void 0;
4892
+ return Object.keys(serviceUrls).length > 0 ? {
4893
+ serviceUrls,
4894
+ serviceIds
4895
+ } : void 0;
4373
4896
  }
4374
4897
  /**
4375
4898
  * Ensure Dokploy is fully configured, recovering/creating resources as needed
@@ -4424,7 +4947,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
4424
4947
  }
4425
4948
  const environmentId$1 = environment.environmentId;
4426
4949
  logger$1.log(` Services config: ${JSON.stringify(services)}, envId: ${environmentId$1}`);
4427
- const serviceUrls$1 = await provisionServices(api, existingConfig.projectId, environmentId$1, dockerConfig.appName, services, existingUrls);
4950
+ const provisionResult$1 = await provisionServices(api, existingConfig.projectId, environmentId$1, dockerConfig.appName, services, void 0);
4428
4951
  return {
4429
4952
  config: {
4430
4953
  endpoint: existingConfig.endpoint,
@@ -4433,7 +4956,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
4433
4956
  registry: existingConfig.registry,
4434
4957
  registryId: storedRegistryId ?? void 0
4435
4958
  },
4436
- serviceUrls: serviceUrls$1
4959
+ serviceUrls: provisionResult$1?.serviceUrls
4437
4960
  };
4438
4961
  } catch {
4439
4962
  logger$1.log("⚠ Project not found, will recover...");
@@ -4540,10 +5063,10 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
4540
5063
  logger$1.log(` Project: ${project.projectId}`);
4541
5064
  logger$1.log(` Application: ${applicationId}`);
4542
5065
  if (registryId) logger$1.log(` Registry: ${registryId}`);
4543
- const serviceUrls = await provisionServices(api, project.projectId, environmentId, dockerConfig.appName, services, existingUrls);
5066
+ const provisionResult = await provisionServices(api, project.projectId, environmentId, dockerConfig.appName, services, void 0);
4544
5067
  return {
4545
5068
  config: dokployConfig,
4546
- serviceUrls
5069
+ serviceUrls: provisionResult?.serviceUrls
4547
5070
  };
4548
5071
  }
4549
5072
  /**
@@ -4662,6 +5185,18 @@ async function workspaceDeployCommand(workspace, options) {
4662
5185
  } else environmentId = result.environment.environmentId;
4663
5186
  logger$1.log(` ✓ Created project: ${project.projectId}`);
4664
5187
  }
5188
+ logger$1.log("\n📋 Loading deploy state...");
5189
+ let state = await readStageState(workspace.root, stage);
5190
+ if (state) {
5191
+ logger$1.log(` Found existing state for stage "${stage}"`);
5192
+ if (state.environmentId !== environmentId) {
5193
+ logger$1.log(` ⚠ Environment ID changed, updating state`);
5194
+ state.environmentId = environmentId;
5195
+ }
5196
+ } else {
5197
+ logger$1.log(` Creating new state for stage "${stage}"`);
5198
+ state = createEmptyState(stage, environmentId);
5199
+ }
4665
5200
  logger$1.log("\n🐳 Checking registry...");
4666
5201
  let registryId = await getDokployRegistryId();
4667
5202
  const registry = workspace.deploy.dokploy?.registry;
@@ -4697,33 +5232,45 @@ async function workspaceDeployCommand(workspace, options) {
4697
5232
  };
4698
5233
  if (dockerServices.postgres || dockerServices.redis) {
4699
5234
  logger$1.log("\n🔧 Provisioning infrastructure services...");
4700
- const existingUrls = stageSecrets ? {
4701
- DATABASE_URL: stageSecrets.urls?.DATABASE_URL,
4702
- REDIS_URL: stageSecrets.urls?.REDIS_URL
4703
- } : void 0;
4704
- await provisionServices(api, project.projectId, environmentId, workspace.name, dockerServices, existingUrls);
5235
+ const existingServiceIds = {
5236
+ postgresId: getPostgresId(state),
5237
+ redisId: getRedisId(state)
5238
+ };
5239
+ const provisionResult = await provisionServices(api, project.projectId, environmentId, workspace.name, dockerServices, existingServiceIds);
5240
+ if (provisionResult?.serviceIds) {
5241
+ if (provisionResult.serviceIds.postgresId) setPostgresId(state, provisionResult.serviceIds.postgresId);
5242
+ if (provisionResult.serviceIds.redisId) setRedisId(state, provisionResult.serviceIds.redisId);
5243
+ }
4705
5244
  }
4706
5245
  const backendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "backend");
4707
5246
  const frontendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "frontend");
4708
5247
  const publicUrls = {};
4709
5248
  const results = [];
4710
5249
  const dokployConfig = workspace.deploy.dokploy;
5250
+ const appHostnames = /* @__PURE__ */ new Map();
5251
+ const appDomainIds = /* @__PURE__ */ new Map();
4711
5252
  if (backendApps.length > 0) {
4712
5253
  logger$1.log("\n📦 PHASE 1: Deploying backend applications...");
4713
5254
  for (const appName of backendApps) {
4714
5255
  const app = workspace.apps[appName];
4715
5256
  logger$1.log(`\n ⚙️ Deploying ${appName}...`);
4716
5257
  try {
4717
- const dokployAppName = `${workspace.name}-${appName}`;
4718
- let application;
4719
- try {
4720
- application = await api.createApplication(dokployAppName, project.projectId, environmentId);
4721
- logger$1.log(` Created application: ${application.applicationId}`);
4722
- } catch (error) {
4723
- const message = error instanceof Error ? error.message : "Unknown error";
4724
- if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` Application already exists`);
4725
- else throw error;
5258
+ const dokployAppName = appName;
5259
+ let application = null;
5260
+ const cachedAppId = getApplicationId(state, appName);
5261
+ if (cachedAppId) {
5262
+ logger$1.log(` Using cached ID: ${cachedAppId}`);
5263
+ application = await api.getApplication(cachedAppId);
5264
+ if (application) logger$1.log(` ✓ Application found: ${application.applicationId}`);
5265
+ else logger$1.log(` Cached ID invalid, will create new`);
4726
5266
  }
5267
+ if (!application) {
5268
+ const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
5269
+ application = result.application;
5270
+ if (result.created) logger$1.log(` Created application: ${application.applicationId}`);
5271
+ else logger$1.log(` Found existing application: ${application.applicationId}`);
5272
+ }
5273
+ setApplicationId(state, appName, application.applicationId);
4727
5274
  const appSecrets = encryptedSecrets.get(appName);
4728
5275
  const buildArgs = [];
4729
5276
  if (appSecrets && appSecrets.secretCount > 0) {
@@ -4747,47 +5294,42 @@ async function workspaceDeployCommand(workspace, options) {
4747
5294
  });
4748
5295
  const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
4749
5296
  if (appSecrets && appSecrets.masterKey) envVars.push(`GKM_MASTER_KEY=${appSecrets.masterKey}`);
4750
- if (application) {
4751
- await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
4752
- await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
4753
- logger$1.log(` Deploying to Dokploy...`);
4754
- await api.deployApplication(application.applicationId);
4755
- try {
4756
- const host = resolveHost(appName, app, stage, dokployConfig, false);
4757
- await api.createDomain({
4758
- host,
4759
- port: app.port,
4760
- https: true,
4761
- certificateType: "letsencrypt",
4762
- applicationId: application.applicationId
4763
- });
4764
- const publicUrl = `https://${host}`;
4765
- publicUrls[appName] = publicUrl;
4766
- logger$1.log(` ✓ Domain: ${publicUrl}`);
4767
- } catch (domainError) {
4768
- const host = resolveHost(appName, app, stage, dokployConfig, false);
4769
- publicUrls[appName] = `https://${host}`;
4770
- logger$1.log(` ℹ Domain already configured: https://${host}`);
4771
- }
4772
- results.push({
4773
- appName,
4774
- type: app.type,
4775
- success: true,
4776
- applicationId: application.applicationId,
4777
- imageRef
4778
- });
4779
- logger$1.log(` ✓ ${appName} deployed successfully`);
4780
- } else {
4781
- const host = resolveHost(appName, app, stage, dokployConfig, false);
4782
- publicUrls[appName] = `https://${host}`;
4783
- results.push({
4784
- appName,
4785
- type: app.type,
4786
- success: true,
4787
- imageRef
5297
+ await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
5298
+ await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
5299
+ logger$1.log(` Deploying to Dokploy...`);
5300
+ await api.deployApplication(application.applicationId);
5301
+ const backendHost = resolveHost(appName, app, stage, dokployConfig, false);
5302
+ try {
5303
+ const domain = await api.createDomain({
5304
+ host: backendHost,
5305
+ port: app.port,
5306
+ https: true,
5307
+ certificateType: "letsencrypt",
5308
+ applicationId: application.applicationId
4788
5309
  });
4789
- logger$1.log(` ✓ ${appName} image pushed (app already exists)`);
5310
+ appHostnames.set(appName, backendHost);
5311
+ appDomainIds.set(appName, domain.domainId);
5312
+ const publicUrl = `https://${backendHost}`;
5313
+ publicUrls[appName] = publicUrl;
5314
+ logger$1.log(` ✓ Domain: ${publicUrl}`);
5315
+ } catch (domainError) {
5316
+ appHostnames.set(appName, backendHost);
5317
+ try {
5318
+ const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
5319
+ const matchingDomain = existingDomains.find((d) => d.host === backendHost);
5320
+ if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
5321
+ } catch {}
5322
+ publicUrls[appName] = `https://${backendHost}`;
5323
+ logger$1.log(` ℹ Domain already configured: https://${backendHost}`);
4790
5324
  }
5325
+ results.push({
5326
+ appName,
5327
+ type: app.type,
5328
+ success: true,
5329
+ applicationId: application.applicationId,
5330
+ imageRef
5331
+ });
5332
+ logger$1.log(` ✓ ${appName} deployed successfully`);
4791
5333
  } catch (error) {
4792
5334
  const message = error instanceof Error ? error.message : "Unknown error";
4793
5335
  logger$1.log(` ✗ Failed to deploy ${appName}: ${message}`);
@@ -4807,16 +5349,22 @@ async function workspaceDeployCommand(workspace, options) {
4807
5349
  const app = workspace.apps[appName];
4808
5350
  logger$1.log(`\n 🌐 Deploying ${appName}...`);
4809
5351
  try {
4810
- const dokployAppName = `${workspace.name}-${appName}`;
4811
- let application;
4812
- try {
4813
- application = await api.createApplication(dokployAppName, project.projectId, environmentId);
4814
- logger$1.log(` Created application: ${application.applicationId}`);
4815
- } catch (error) {
4816
- const message = error instanceof Error ? error.message : "Unknown error";
4817
- if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` Application already exists`);
4818
- else throw error;
5352
+ const dokployAppName = appName;
5353
+ let application = null;
5354
+ const cachedAppId = getApplicationId(state, appName);
5355
+ if (cachedAppId) {
5356
+ logger$1.log(` Using cached ID: ${cachedAppId}`);
5357
+ application = await api.getApplication(cachedAppId);
5358
+ if (application) logger$1.log(` ✓ Application found: ${application.applicationId}`);
5359
+ else logger$1.log(` Cached ID invalid, will create new`);
4819
5360
  }
5361
+ if (!application) {
5362
+ const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
5363
+ application = result.application;
5364
+ if (result.created) logger$1.log(` Created application: ${application.applicationId}`);
5365
+ else logger$1.log(` Found existing application: ${application.applicationId}`);
5366
+ }
5367
+ setApplicationId(state, appName, application.applicationId);
4820
5368
  const buildArgs = generatePublicUrlBuildArgs(app, publicUrls);
4821
5369
  if (buildArgs.length > 0) logger$1.log(` Public URLs: ${buildArgs.join(", ")}`);
4822
5370
  const imageName = `${workspace.name}-${appName}`;
@@ -4835,49 +5383,43 @@ async function workspaceDeployCommand(workspace, options) {
4835
5383
  publicUrlArgs: getPublicUrlArgNames(app)
4836
5384
  });
4837
5385
  const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
4838
- if (application) {
4839
- await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
4840
- await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
4841
- logger$1.log(` Deploying to Dokploy...`);
4842
- await api.deployApplication(application.applicationId);
4843
- const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
4844
- try {
4845
- const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
4846
- await api.createDomain({
4847
- host,
4848
- port: app.port,
4849
- https: true,
4850
- certificateType: "letsencrypt",
4851
- applicationId: application.applicationId
4852
- });
4853
- const publicUrl = `https://${host}`;
4854
- publicUrls[appName] = publicUrl;
4855
- logger$1.log(` ✓ Domain: ${publicUrl}`);
4856
- } catch (domainError) {
4857
- const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
4858
- publicUrls[appName] = `https://${host}`;
4859
- logger$1.log(` ℹ Domain already configured: https://${host}`);
4860
- }
4861
- results.push({
4862
- appName,
4863
- type: app.type,
4864
- success: true,
4865
- applicationId: application.applicationId,
4866
- imageRef
4867
- });
4868
- logger$1.log(` ✓ ${appName} deployed successfully`);
4869
- } else {
4870
- const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
4871
- const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
4872
- publicUrls[appName] = `https://${host}`;
4873
- results.push({
4874
- appName,
4875
- type: app.type,
4876
- success: true,
4877
- imageRef
5386
+ await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
5387
+ await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
5388
+ logger$1.log(` Deploying to Dokploy...`);
5389
+ await api.deployApplication(application.applicationId);
5390
+ const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
5391
+ const frontendHost = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
5392
+ try {
5393
+ const domain = await api.createDomain({
5394
+ host: frontendHost,
5395
+ port: app.port,
5396
+ https: true,
5397
+ certificateType: "letsencrypt",
5398
+ applicationId: application.applicationId
4878
5399
  });
4879
- logger$1.log(` ✓ ${appName} image pushed (app already exists)`);
5400
+ appHostnames.set(appName, frontendHost);
5401
+ appDomainIds.set(appName, domain.domainId);
5402
+ const publicUrl = `https://${frontendHost}`;
5403
+ publicUrls[appName] = publicUrl;
5404
+ logger$1.log(` ✓ Domain: ${publicUrl}`);
5405
+ } catch (domainError) {
5406
+ appHostnames.set(appName, frontendHost);
5407
+ try {
5408
+ const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
5409
+ const matchingDomain = existingDomains.find((d) => d.host === frontendHost);
5410
+ if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
5411
+ } catch {}
5412
+ publicUrls[appName] = `https://${frontendHost}`;
5413
+ logger$1.log(` ℹ Domain already configured: https://${frontendHost}`);
4880
5414
  }
5415
+ results.push({
5416
+ appName,
5417
+ type: app.type,
5418
+ success: true,
5419
+ applicationId: application.applicationId,
5420
+ imageRef
5421
+ });
5422
+ logger$1.log(` ✓ ${appName} deployed successfully`);
4881
5423
  } catch (error) {
4882
5424
  const message = error instanceof Error ? error.message : "Unknown error";
4883
5425
  logger$1.log(` ✗ Failed to deploy ${appName}: ${message}`);
@@ -4890,6 +5432,23 @@ async function workspaceDeployCommand(workspace, options) {
4890
5432
  }
4891
5433
  }
4892
5434
  }
5435
+ logger$1.log("\n📋 Saving deploy state...");
5436
+ await writeStageState(workspace.root, stage, state);
5437
+ logger$1.log(` ✓ State saved to .gkm/deploy-${stage}.json`);
5438
+ const dnsConfig = workspace.deploy.dns;
5439
+ if (dnsConfig && appHostnames.size > 0) {
5440
+ const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
5441
+ if (dnsResult?.success && appDomainIds.size > 0) {
5442
+ logger$1.log("\n🔒 Validating domains for SSL certificates...");
5443
+ for (const [appName, domainId] of appDomainIds) try {
5444
+ await api.validateDomain(domainId);
5445
+ logger$1.log(` ✓ ${appName}: SSL validation triggered`);
5446
+ } catch (validationError) {
5447
+ const message = validationError instanceof Error ? validationError.message : "Unknown error";
5448
+ logger$1.log(` ⚠ ${appName}: SSL validation failed - ${message}`);
5449
+ }
5450
+ }
5451
+ }
4893
5452
  const successCount = results.filter((r) => r.success).length;
4894
5453
  const failedCount = results.filter((r) => !r.success).length;
4895
5454
  logger$1.log(`\n${"─".repeat(50)}`);