@geekmidas/cli 1.9.0 → 1.10.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 (111) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +42 -6
  3. package/dist/{HostingerProvider-CEsQbmpY.cjs → HostingerProvider-5KYmwoK2.cjs} +1 -1
  4. package/dist/{HostingerProvider-CEsQbmpY.cjs.map → HostingerProvider-5KYmwoK2.cjs.map} +1 -1
  5. package/dist/{HostingerProvider-DkahM5AP.mjs → HostingerProvider-ANWchdiK.mjs} +1 -1
  6. package/dist/{HostingerProvider-DkahM5AP.mjs.map → HostingerProvider-ANWchdiK.mjs.map} +1 -1
  7. package/dist/{LocalStateProvider-Roi202l7.cjs → LocalStateProvider-CLifRC0Y.cjs} +1 -1
  8. package/dist/{LocalStateProvider-Roi202l7.cjs.map → LocalStateProvider-CLifRC0Y.cjs.map} +1 -1
  9. package/dist/{LocalStateProvider-DXIwWb7k.mjs → LocalStateProvider-Dp0KkRcw.mjs} +1 -1
  10. package/dist/{LocalStateProvider-DXIwWb7k.mjs.map → LocalStateProvider-Dp0KkRcw.mjs.map} +1 -1
  11. package/dist/{Route53Provider-Ckq_n5Be.mjs → Route53Provider-QoPgcXxn.mjs} +1 -1
  12. package/dist/{Route53Provider-Ckq_n5Be.mjs.map → Route53Provider-QoPgcXxn.mjs.map} +1 -1
  13. package/dist/{Route53Provider-BqXeHzuc.cjs → Route53Provider-owQQ4pn6.cjs} +1 -1
  14. package/dist/{Route53Provider-BqXeHzuc.cjs.map → Route53Provider-owQQ4pn6.cjs.map} +1 -1
  15. package/dist/{SSMStateProvider-BReQA5re.cjs → SSMStateProvider-CT8tjl9o.cjs} +1 -1
  16. package/dist/{SSMStateProvider-BReQA5re.cjs.map → SSMStateProvider-CT8tjl9o.cjs.map} +1 -1
  17. package/dist/{SSMStateProvider-wddd0_-d.mjs → SSMStateProvider-CksOTB8M.mjs} +1 -1
  18. package/dist/{SSMStateProvider-wddd0_-d.mjs.map → SSMStateProvider-CksOTB8M.mjs.map} +1 -1
  19. package/dist/{backup-provisioner-BAExdDtc.mjs → backup-provisioner-BEXoHTuC.mjs} +1 -1
  20. package/dist/{backup-provisioner-BAExdDtc.mjs.map → backup-provisioner-BEXoHTuC.mjs.map} +1 -1
  21. package/dist/{backup-provisioner-C8VK63I-.cjs → backup-provisioner-C4noe75O.cjs} +1 -1
  22. package/dist/{backup-provisioner-C8VK63I-.cjs.map → backup-provisioner-C4noe75O.cjs.map} +1 -1
  23. package/dist/{bundler-BxHyDhdt.mjs → bundler-DQYjKFPm.mjs} +1 -1
  24. package/dist/{bundler-BxHyDhdt.mjs.map → bundler-DQYjKFPm.mjs.map} +1 -1
  25. package/dist/{bundler-CuMIfXw5.cjs → bundler-NpfYPBUo.cjs} +1 -1
  26. package/dist/{bundler-CuMIfXw5.cjs.map → bundler-NpfYPBUo.cjs.map} +1 -1
  27. package/dist/{config-6JHOwLCx.cjs → config-D3ORuiUs.cjs} +2 -2
  28. package/dist/{config-6JHOwLCx.cjs.map → config-D3ORuiUs.cjs.map} +1 -1
  29. package/dist/{config-DxASSNjr.mjs → config-jsRYHOHU.mjs} +2 -2
  30. package/dist/{config-DxASSNjr.mjs.map → config-jsRYHOHU.mjs.map} +1 -1
  31. package/dist/config.cjs +2 -2
  32. package/dist/config.d.cts +2 -2
  33. package/dist/config.d.mts +2 -2
  34. package/dist/config.mjs +2 -2
  35. package/dist/fullstack-secrets-COWz084x.cjs +238 -0
  36. package/dist/fullstack-secrets-COWz084x.cjs.map +1 -0
  37. package/dist/fullstack-secrets-UZAFWuH4.mjs +202 -0
  38. package/dist/fullstack-secrets-UZAFWuH4.mjs.map +1 -0
  39. package/dist/{index-BVNXOydm.d.mts → index-3n-giNaw.d.mts} +18 -6
  40. package/dist/index-3n-giNaw.d.mts.map +1 -0
  41. package/dist/{index-Cyk2rTyj.d.cts → index-CiEOtKEX.d.cts} +18 -6
  42. package/dist/index-CiEOtKEX.d.cts.map +1 -0
  43. package/dist/index.cjs +322 -433
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.mjs +306 -417
  46. package/dist/index.mjs.map +1 -1
  47. package/dist/{openapi-CnvwSRDU.cjs → openapi-BYxAWwok.cjs} +178 -32
  48. package/dist/openapi-BYxAWwok.cjs.map +1 -0
  49. package/dist/{openapi-BYlyAbH3.mjs → openapi-DenF-okj.mjs} +148 -32
  50. package/dist/openapi-DenF-okj.mjs.map +1 -0
  51. package/dist/{openapi-react-query-DaTMSPD5.mjs → openapi-react-query-C4UdILaI.mjs} +1 -1
  52. package/dist/{openapi-react-query-DaTMSPD5.mjs.map → openapi-react-query-C4UdILaI.mjs.map} +1 -1
  53. package/dist/{openapi-react-query-BeXvk-wa.cjs → openapi-react-query-DYbBq-WJ.cjs} +1 -1
  54. package/dist/{openapi-react-query-BeXvk-wa.cjs.map → openapi-react-query-DYbBq-WJ.cjs.map} +1 -1
  55. package/dist/openapi-react-query.cjs +1 -1
  56. package/dist/openapi-react-query.mjs +1 -1
  57. package/dist/openapi.cjs +3 -3
  58. package/dist/openapi.d.cts +1 -1
  59. package/dist/openapi.d.cts.map +1 -1
  60. package/dist/openapi.d.mts +1 -1
  61. package/dist/openapi.d.mts.map +1 -1
  62. package/dist/openapi.mjs +3 -3
  63. package/dist/reconcile-7yarEvmK.cjs +36 -0
  64. package/dist/reconcile-7yarEvmK.cjs.map +1 -0
  65. package/dist/reconcile-D2WCDQue.mjs +36 -0
  66. package/dist/reconcile-D2WCDQue.mjs.map +1 -0
  67. package/dist/{sync-BnqNNc6O.mjs → sync-6FoT41G3.mjs} +1 -1
  68. package/dist/{sync-CHfhmXF3.mjs → sync-CbeKrnQV.mjs} +1 -1
  69. package/dist/{sync-CHfhmXF3.mjs.map → sync-CbeKrnQV.mjs.map} +1 -1
  70. package/dist/{sync-BOS0jKLn.cjs → sync-DdkKaHqP.cjs} +1 -1
  71. package/dist/{sync-BOS0jKLn.cjs.map → sync-DdkKaHqP.cjs.map} +1 -1
  72. package/dist/sync-RsnjXYwG.cjs +4 -0
  73. package/dist/{types-eTlj5f2M.d.mts → types-C7QJJl9f.d.cts} +6 -2
  74. package/dist/types-C7QJJl9f.d.cts.map +1 -0
  75. package/dist/{types-l53qUmGt.d.cts → types-Iqsq_FIG.d.mts} +6 -2
  76. package/dist/types-Iqsq_FIG.d.mts.map +1 -0
  77. package/dist/workspace/index.cjs +1 -1
  78. package/dist/workspace/index.d.cts +2 -2
  79. package/dist/workspace/index.d.mts +2 -2
  80. package/dist/workspace/index.mjs +1 -1
  81. package/dist/{workspace-D2ocAlpl.cjs → workspace-4SP3Gx4Y.cjs} +11 -3
  82. package/dist/{workspace-D2ocAlpl.cjs.map → workspace-4SP3Gx4Y.cjs.map} +1 -1
  83. package/dist/{workspace-9IQIjwkQ.mjs → workspace-D4z4A4cq.mjs} +11 -3
  84. package/dist/{workspace-9IQIjwkQ.mjs.map → workspace-D4z4A4cq.mjs.map} +1 -1
  85. package/package.json +2 -2
  86. package/src/build/__tests__/manifests.spec.ts +171 -0
  87. package/src/build/__tests__/partitions.spec.ts +110 -0
  88. package/src/build/index.ts +58 -15
  89. package/src/build/manifests.ts +153 -32
  90. package/src/build/partitions.ts +58 -0
  91. package/src/deploy/sniffer.ts +6 -1
  92. package/src/dev/__tests__/index.spec.ts +49 -0
  93. package/src/dev/index.ts +84 -63
  94. package/src/generators/Generator.ts +27 -7
  95. package/src/generators/OpenApiTsGenerator.ts +4 -4
  96. package/src/index.ts +79 -1
  97. package/src/init/versions.ts +4 -4
  98. package/src/openapi.ts +2 -1
  99. package/src/secrets/__tests__/reconcile.spec.ts +123 -0
  100. package/src/secrets/reconcile.ts +53 -0
  101. package/src/setup/fullstack-secrets.ts +2 -0
  102. package/src/types.ts +17 -1
  103. package/src/workspace/client-generator.ts +6 -3
  104. package/src/workspace/schema.ts +13 -3
  105. package/dist/index-BVNXOydm.d.mts.map +0 -1
  106. package/dist/index-Cyk2rTyj.d.cts.map +0 -1
  107. package/dist/openapi-BYlyAbH3.mjs.map +0 -1
  108. package/dist/openapi-CnvwSRDU.cjs.map +0 -1
  109. package/dist/sync-BxFB34zW.cjs +0 -4
  110. package/dist/types-eTlj5f2M.d.mts.map +0 -1
  111. package/dist/types-l53qUmGt.d.cts.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  const require_chunk = require('./chunk-CUT6urMc.cjs');
3
- const require_workspace = require('./workspace-D2ocAlpl.cjs');
4
- const require_config = require('./config-6JHOwLCx.cjs');
3
+ const require_workspace = require('./workspace-4SP3Gx4Y.cjs');
4
+ const require_config = require('./config-D3ORuiUs.cjs');
5
5
  const require_credentials = require('./credentials-C8DWtnMY.cjs');
6
- const require_openapi = require('./openapi-CnvwSRDU.cjs');
6
+ const require_openapi = require('./openapi-BYxAWwok.cjs');
7
7
  const require_storage = require('./storage-CoCNe0Pt.cjs');
8
8
  const require_dokploy_api = require('./dokploy-api-DLgvEQlr.cjs');
9
9
  const require_encryption = require('./encryption-BE0UOb8j.cjs');
10
10
  const require_CachedStateProvider = require('./CachedStateProvider-D73dCqfH.cjs');
11
- const require_openapi_react_query = require('./openapi-react-query-BeXvk-wa.cjs');
12
- const require_sync = require('./sync-BOS0jKLn.cjs');
11
+ const require_fullstack_secrets = require('./fullstack-secrets-COWz084x.cjs');
12
+ const require_openapi_react_query = require('./openapi-react-query-DYbBq-WJ.cjs');
13
+ const require_sync = require('./sync-DdkKaHqP.cjs');
13
14
  const node_fs = require_chunk.__toESM(require("node:fs"));
14
15
  const node_path = require_chunk.__toESM(require("node:path"));
15
16
  const commander = require_chunk.__toESM(require("commander"));
@@ -34,7 +35,7 @@ const prompts = require_chunk.__toESM(require("prompts"));
34
35
 
35
36
  //#region package.json
36
37
  var name = "@geekmidas/cli";
37
- var version = "1.8.0";
38
+ var version = "1.9.1";
38
39
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
39
40
  var private$1 = false;
40
41
  var type = "module";
@@ -132,7 +133,7 @@ var package_default = {
132
133
 
133
134
  //#endregion
134
135
  //#region src/auth/index.ts
135
- const logger$13 = console;
136
+ const logger$12 = console;
136
137
  /**
137
138
  * Validate Dokploy token by making a test API call
138
139
  */
@@ -200,36 +201,36 @@ async function prompt$1(message, hidden = false) {
200
201
  async function loginCommand(options) {
201
202
  const { service, token: providedToken, endpoint: providedEndpoint } = options;
202
203
  if (service === "dokploy") {
203
- logger$13.log("\n🔐 Logging in to Dokploy...\n");
204
+ logger$12.log("\n🔐 Logging in to Dokploy...\n");
204
205
  let endpoint = providedEndpoint;
205
206
  if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
206
207
  endpoint = endpoint.replace(/\/$/, "");
207
208
  try {
208
209
  new URL(endpoint);
209
210
  } catch {
210
- logger$13.error("Invalid URL format");
211
+ logger$12.error("Invalid URL format");
211
212
  process.exit(1);
212
213
  }
213
214
  let token = providedToken;
214
215
  if (!token) {
215
- logger$13.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
216
+ logger$12.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
216
217
  token = await prompt$1("API Token: ", true);
217
218
  }
218
219
  if (!token) {
219
- logger$13.error("Token is required");
220
+ logger$12.error("Token is required");
220
221
  process.exit(1);
221
222
  }
222
- logger$13.log("\nValidating credentials...");
223
+ logger$12.log("\nValidating credentials...");
223
224
  const isValid = await validateDokployToken(endpoint, token);
224
225
  if (!isValid) {
225
- logger$13.error("\n✗ Invalid credentials. Please check your token and try again.");
226
+ logger$12.error("\n✗ Invalid credentials. Please check your token and try again.");
226
227
  process.exit(1);
227
228
  }
228
229
  await require_credentials.storeDokployCredentials(token, endpoint);
229
- logger$13.log("\n✓ Successfully logged in to Dokploy!");
230
- logger$13.log(` Endpoint: ${endpoint}`);
231
- logger$13.log(` Credentials stored in: ${require_credentials.getCredentialsPath()}`);
232
- logger$13.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
230
+ logger$12.log("\n✓ Successfully logged in to Dokploy!");
231
+ logger$12.log(` Endpoint: ${endpoint}`);
232
+ logger$12.log(` Credentials stored in: ${require_credentials.getCredentialsPath()}`);
233
+ logger$12.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
233
234
  }
234
235
  }
235
236
  /**
@@ -239,28 +240,28 @@ async function logoutCommand(options) {
239
240
  const { service = "dokploy" } = options;
240
241
  if (service === "all") {
241
242
  const dokployRemoved = await require_credentials.removeDokployCredentials();
242
- if (dokployRemoved) logger$13.log("\n✓ Logged out from all services");
243
- else logger$13.log("\nNo stored credentials found");
243
+ if (dokployRemoved) logger$12.log("\n✓ Logged out from all services");
244
+ else logger$12.log("\nNo stored credentials found");
244
245
  return;
245
246
  }
246
247
  if (service === "dokploy") {
247
248
  const removed = await require_credentials.removeDokployCredentials();
248
- if (removed) logger$13.log("\n✓ Logged out from Dokploy");
249
- else logger$13.log("\nNo Dokploy credentials found");
249
+ if (removed) logger$12.log("\n✓ Logged out from Dokploy");
250
+ else logger$12.log("\nNo Dokploy credentials found");
250
251
  }
251
252
  }
252
253
  /**
253
254
  * Show current login status
254
255
  */
255
256
  async function whoamiCommand() {
256
- logger$13.log("\n📋 Current credentials:\n");
257
+ logger$12.log("\n📋 Current credentials:\n");
257
258
  const dokploy = await require_credentials.getDokployCredentials();
258
259
  if (dokploy) {
259
- logger$13.log(" Dokploy:");
260
- logger$13.log(` Endpoint: ${dokploy.endpoint}`);
261
- logger$13.log(` Token: ${maskToken(dokploy.token)}`);
262
- } else logger$13.log(" Dokploy: Not logged in");
263
- logger$13.log(`\n Credentials file: ${require_credentials.getCredentialsPath()}`);
260
+ logger$12.log(" Dokploy:");
261
+ logger$12.log(` Endpoint: ${dokploy.endpoint}`);
262
+ logger$12.log(` Token: ${maskToken(dokploy.token)}`);
263
+ } else logger$12.log(" Dokploy: Not logged in");
264
+ logger$12.log(`\n Credentials file: ${require_credentials.getCredentialsPath()}`);
264
265
  }
265
266
  /**
266
267
  * Mask a token for display
@@ -346,7 +347,7 @@ function isEnabled(config) {
346
347
  var CronGenerator = class extends require_openapi.ConstructGenerator {
347
348
  async build(context, constructs, outputDir, options) {
348
349
  const provider = options?.provider || "aws-lambda";
349
- const logger$14 = console;
350
+ const logger$13 = console;
350
351
  const cronInfos = [];
351
352
  if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
352
353
  const cronsDir = (0, node_path.join)(outputDir, "crons");
@@ -361,7 +362,7 @@ var CronGenerator = class extends require_openapi.ConstructGenerator {
361
362
  memorySize: construct.memorySize,
362
363
  environment: await construct.getEnvironment()
363
364
  });
364
- logger$14.log(`Generated cron handler: ${key}`);
365
+ logger$13.log(`Generated cron handler: ${key}`);
365
366
  }
366
367
  return cronInfos;
367
368
  }
@@ -397,7 +398,7 @@ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
397
398
  }
398
399
  async build(context, constructs, outputDir, options) {
399
400
  const provider = options?.provider || "aws-lambda";
400
- const logger$14 = console;
401
+ const logger$13 = console;
401
402
  const functionInfos = [];
402
403
  if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
403
404
  const functionsDir = (0, node_path.join)(outputDir, "functions");
@@ -411,7 +412,7 @@ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
411
412
  memorySize: construct.memorySize,
412
413
  environment: await construct.getEnvironment()
413
414
  });
414
- logger$14.log(`Generated function handler: ${key}`);
415
+ logger$13.log(`Generated function handler: ${key}`);
415
416
  }
416
417
  return functionInfos;
417
418
  }
@@ -444,11 +445,11 @@ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
444
445
  }
445
446
  async build(context, constructs, outputDir, options) {
446
447
  const provider = options?.provider || "aws-lambda";
447
- const logger$14 = console;
448
+ const logger$13 = console;
448
449
  const subscriberInfos = [];
449
450
  if (provider === "server") {
450
451
  await this.generateServerSubscribersFile(outputDir, constructs);
451
- logger$14.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
452
+ logger$13.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
452
453
  return subscriberInfos;
453
454
  }
454
455
  if (constructs.length === 0) return subscriberInfos;
@@ -465,7 +466,7 @@ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
465
466
  memorySize: construct.memorySize,
466
467
  environment: await construct.getEnvironment()
467
468
  });
468
- logger$14.log(`Generated subscriber handler: ${key}`);
469
+ logger$13.log(`Generated subscriber handler: ${key}`);
469
470
  }
470
471
  return subscriberInfos;
471
472
  }
@@ -630,98 +631,6 @@ export async function setupSubscribers(
630
631
  }
631
632
  };
632
633
 
633
- //#endregion
634
- //#region src/workspace/client-generator.ts
635
- const logger$12 = console;
636
- /**
637
- * Get frontend apps that depend on a backend app.
638
- */
639
- function getDependentFrontends(workspace, backendAppName) {
640
- const dependentApps = [];
641
- for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "frontend" && app.dependencies.includes(backendAppName)) dependentApps.push(appName);
642
- return dependentApps;
643
- }
644
- /**
645
- * Get the path to a backend's OpenAPI spec file.
646
- */
647
- function getBackendOpenApiPath(workspace, backendAppName) {
648
- const app = workspace.apps[backendAppName];
649
- if (!app || app.type !== "backend") return null;
650
- return (0, node_path.join)(workspace.root, app.path, ".gkm", "openapi.ts");
651
- }
652
- /**
653
- * Count endpoints in an OpenAPI spec content.
654
- */
655
- function countEndpoints(content) {
656
- const endpointMatches = content.match(/'(GET|POST|PUT|PATCH|DELETE)\s+\/[^']+'/g);
657
- return endpointMatches?.length ?? 0;
658
- }
659
- /**
660
- * Copy the OpenAPI client from a backend to all dependent frontend apps.
661
- * Called when the backend's .gkm/openapi.ts file changes.
662
- */
663
- async function copyClientToFrontends(workspace, backendAppName, options = {}) {
664
- const log = options.silent ? () => {} : logger$12.log.bind(logger$12);
665
- const results = [];
666
- const backendApp = workspace.apps[backendAppName];
667
- if (!backendApp || backendApp.type !== "backend") return results;
668
- const openApiPath = (0, node_path.join)(workspace.root, backendApp.path, ".gkm", "openapi.ts");
669
- if (!(0, node_fs.existsSync)(openApiPath)) return results;
670
- const content = await (0, node_fs_promises.readFile)(openApiPath, "utf-8");
671
- const endpointCount = countEndpoints(content);
672
- const dependentFrontends = getDependentFrontends(workspace, backendAppName);
673
- for (const frontendAppName of dependentFrontends) {
674
- const frontendApp = workspace.apps[frontendAppName];
675
- if (!frontendApp || frontendApp.type !== "frontend") continue;
676
- const clientOutput = frontendApp.client?.output;
677
- if (!clientOutput) continue;
678
- const result = {
679
- frontendApp: frontendAppName,
680
- backendApp: backendAppName,
681
- outputPath: "",
682
- endpointCount,
683
- success: false
684
- };
685
- try {
686
- const frontendPath = (0, node_path.join)(workspace.root, frontendApp.path);
687
- const outputDir = (0, node_path.join)(frontendPath, clientOutput);
688
- await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
689
- const fileName = `${backendAppName}.ts`;
690
- const outputPath = (0, node_path.join)(outputDir, fileName);
691
- const backendRelPath = (0, node_path.relative)((0, node_path.dirname)(outputPath), (0, node_path.join)(workspace.root, backendApp.path));
692
- const clientContent = `/**
693
- * Auto-generated API client for ${backendAppName}
694
- * Generated from: ${backendRelPath}
695
- *
696
- * DO NOT EDIT - This file is automatically regenerated when backend schemas change.
697
- */
698
-
699
- ${content}
700
- `;
701
- await (0, node_fs_promises.writeFile)(outputPath, clientContent);
702
- result.outputPath = outputPath;
703
- result.success = true;
704
- log(`📦 Copied client to ${frontendAppName} from ${backendAppName} (${endpointCount} endpoints)`);
705
- } catch (error) {
706
- result.error = error.message;
707
- }
708
- results.push(result);
709
- }
710
- return results;
711
- }
712
- /**
713
- * Copy clients from all backends to their dependent frontends.
714
- * Useful for initial setup or force refresh.
715
- */
716
- async function copyAllClients(workspace, options = {}) {
717
- const allResults = [];
718
- for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "backend" && app.routes) {
719
- const results = await copyClientToFrontends(workspace, appName, options);
720
- allResults.push(...results);
721
- }
722
- return allResults;
723
- }
724
-
725
634
  //#endregion
726
635
  //#region src/dev/index.ts
727
636
  const logger$11 = console;
@@ -1374,7 +1283,7 @@ async function workspaceDevCommand(workspace, options) {
1374
1283
  logger$11.log("✅ Frontend apps validated");
1375
1284
  }
1376
1285
  if (frontendApps.length > 0 && backendApps.length > 0) {
1377
- const clientResults = await copyAllClients(workspace);
1286
+ const clientResults = await require_openapi.copyAllClients(workspace);
1378
1287
  const copiedCount = clientResults.filter((r) => r.success).length;
1379
1288
  if (copiedCount > 0) logger$11.log(`\n📦 Copied ${copiedCount} API client(s)`);
1380
1289
  }
@@ -1442,7 +1351,7 @@ async function workspaceDevCommand(workspace, options) {
1442
1351
  if (frontendApps.length > 0 && backendApps.length > 0) {
1443
1352
  const openApiPaths = [];
1444
1353
  for (const [appName] of backendApps) {
1445
- const openApiPath = getBackendOpenApiPath(workspace, appName);
1354
+ const openApiPath = require_openapi.getBackendOpenApiPath(workspace, appName);
1446
1355
  if (openApiPath) openApiPaths.push({
1447
1356
  path: openApiPath,
1448
1357
  appName
@@ -1464,7 +1373,7 @@ async function workspaceDevCommand(workspace, options) {
1464
1373
  if (!backendAppName) return;
1465
1374
  logger$11.log(`\n🔄 OpenAPI spec changed for ${backendAppName}`);
1466
1375
  try {
1467
- const results = await copyClientToFrontends(workspace, backendAppName, { silent: true });
1376
+ const results = await require_openapi.copyClientToFrontends(workspace, backendAppName, { silent: true });
1468
1377
  for (const result of results) if (result.success) logger$11.log(` 📦 Copied client to ${result.frontendApp} (${result.endpointCount} endpoints)`);
1469
1378
  else if (result.error) logger$11.error(` ❌ Failed to copy client to ${result.frontendApp}: ${result.error}`);
1470
1379
  } catch (error) {
@@ -1740,6 +1649,66 @@ var EntryRunner = class {
1740
1649
  }
1741
1650
  }
1742
1651
  };
1652
+ /**
1653
+ * Generate the content of the dev server entry file (server.ts).
1654
+ * Uses dynamic import for createApp so Credentials are populated
1655
+ * before any app modules evaluate.
1656
+ * @internal Exported for testing
1657
+ */
1658
+ function generateServerEntryContent(options) {
1659
+ const { secretsJsonPath, runtime = "node", enableOpenApi = false, appImportPath = "./app.js" } = options;
1660
+ const credentialsInjection = secretsJsonPath ? `import { Credentials } from '@geekmidas/envkit/credentials';
1661
+ import { existsSync, readFileSync } from 'node:fs';
1662
+
1663
+ // Inject dev secrets into Credentials (must happen before app import)
1664
+ const secretsPath = '${secretsJsonPath}';
1665
+ if (existsSync(secretsPath)) {
1666
+ Object.assign(Credentials, JSON.parse(readFileSync(secretsPath, 'utf-8')));
1667
+ }
1668
+
1669
+ ` : "";
1670
+ const serveCode = runtime === "bun" ? `Bun.serve({
1671
+ port,
1672
+ fetch: app.fetch,
1673
+ });` : `const { serve } = await import('@hono/node-server');
1674
+ const server = serve({
1675
+ fetch: app.fetch,
1676
+ port,
1677
+ });
1678
+ // Inject WebSocket support if available
1679
+ const injectWs = (app as any).__injectWebSocket;
1680
+ if (injectWs) {
1681
+ injectWs(server);
1682
+ console.log('🔌 Telescope real-time updates enabled');
1683
+ }`;
1684
+ return `#!/usr/bin/env node
1685
+ /**
1686
+ * Development server entry point
1687
+ * This file is auto-generated by 'gkm dev'
1688
+ */
1689
+ ${credentialsInjection}
1690
+ const port = process.argv.includes('--port')
1691
+ ? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
1692
+ : 3000;
1693
+
1694
+ // Dynamic import so Credentials are populated before env.ts evaluates
1695
+ const { createApp } = await import('${appImportPath}');
1696
+
1697
+ // createApp is async to support optional WebSocket setup
1698
+ const { app, start } = await createApp(undefined, ${enableOpenApi});
1699
+
1700
+ // Start the server
1701
+ start({
1702
+ port,
1703
+ serve: async (app, port) => {
1704
+ ${serveCode}
1705
+ },
1706
+ }).catch((error) => {
1707
+ console.error('Failed to start server:', error);
1708
+ process.exit(1);
1709
+ });
1710
+ `;
1711
+ }
1743
1712
  var DevServer = class {
1744
1713
  serverProcess = null;
1745
1714
  isRunning = false;
@@ -1833,58 +1802,12 @@ var DevServer = class {
1833
1802
  }
1834
1803
  async createServerEntry() {
1835
1804
  const { writeFile: fsWriteFile } = await import("node:fs/promises");
1836
- const { relative: relative$6, dirname: dirname$11 } = await import("node:path");
1837
1805
  const serverPath = (0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts");
1838
- const relativeAppPath = relative$6(dirname$11(serverPath), (0, node_path.join)(dirname$11(serverPath), "app.js"));
1839
- const credentialsInjection = this.secretsJsonPath ? `import { Credentials } from '@geekmidas/envkit/credentials';
1840
- import { existsSync, readFileSync } from 'node:fs';
1841
-
1842
- // Inject dev secrets into Credentials (must happen before app import)
1843
- const secretsPath = '${this.secretsJsonPath}';
1844
- if (existsSync(secretsPath)) {
1845
- Object.assign(Credentials, JSON.parse(readFileSync(secretsPath, 'utf-8')));
1846
- }
1847
-
1848
- ` : "";
1849
- const serveCode = this.runtime === "bun" ? `Bun.serve({
1850
- port,
1851
- fetch: app.fetch,
1852
- });` : `const { serve } = await import('@hono/node-server');
1853
- const server = serve({
1854
- fetch: app.fetch,
1855
- port,
1856
- });
1857
- // Inject WebSocket support if available
1858
- const injectWs = (app as any).__injectWebSocket;
1859
- if (injectWs) {
1860
- injectWs(server);
1861
- console.log('🔌 Telescope real-time updates enabled');
1862
- }`;
1863
- const content = `#!/usr/bin/env node
1864
- /**
1865
- * Development server entry point
1866
- * This file is auto-generated by 'gkm dev'
1867
- */
1868
- ${credentialsInjection}import { createApp } from './${relativeAppPath.startsWith(".") ? relativeAppPath : `./${relativeAppPath}`}';
1869
-
1870
- const port = process.argv.includes('--port')
1871
- ? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
1872
- : 3000;
1873
-
1874
- // createApp is async to support optional WebSocket setup
1875
- const { app, start } = await createApp(undefined, ${this.enableOpenApi});
1876
-
1877
- // Start the server
1878
- start({
1879
- port,
1880
- serve: async (app, port) => {
1881
- ${serveCode}
1882
- },
1883
- }).catch((error) => {
1884
- console.error('Failed to start server:', error);
1885
- process.exit(1);
1886
- });
1887
- `;
1806
+ const content = generateServerEntryContent({
1807
+ secretsJsonPath: this.secretsJsonPath,
1808
+ runtime: this.runtime,
1809
+ enableOpenApi: this.enableOpenApi
1810
+ });
1888
1811
  await fsWriteFile(serverPath, content);
1889
1812
  }
1890
1813
  };
@@ -1967,22 +1890,58 @@ async function execCommand(commandArgs, options = {}) {
1967
1890
  //#endregion
1968
1891
  //#region src/build/manifests.ts
1969
1892
  const logger$10 = console;
1893
+ function isPartitioned(field) {
1894
+ return !Array.isArray(field);
1895
+ }
1896
+ /**
1897
+ * Serialize a manifest field to a TypeScript string.
1898
+ * Flat arrays serialize as JSON arrays, partitioned fields as objects of arrays.
1899
+ */
1900
+ function serializeField(field, indent = 2) {
1901
+ if (Array.isArray(field)) return JSON.stringify(field, null, indent);
1902
+ const entries = Object.entries(field).map(([key, value]) => ` ${JSON.stringify(key)}: ${JSON.stringify(value, null, indent)}`).join(",\n");
1903
+ return `{\n${entries},\n }`;
1904
+ }
1905
+ /**
1906
+ * Count total items across a manifest field (flat or partitioned).
1907
+ */
1908
+ function countItems(field) {
1909
+ if (Array.isArray(field)) return field.length;
1910
+ return Object.values(field).reduce((sum, arr) => sum + arr.length, 0);
1911
+ }
1912
+ /**
1913
+ * Generate derived types for a construct field.
1914
+ * @param fieldName - The field name in the manifest (e.g., 'routes')
1915
+ * @param typeName - The exported type name (e.g., 'Route')
1916
+ * @param partitioned - Whether this field is partitioned
1917
+ */
1918
+ function generateDerivedType(fieldName, typeName, partitioned) {
1919
+ if (partitioned) {
1920
+ const partitionTypeName = `${typeName}Partition`;
1921
+ return [`export type ${partitionTypeName} = keyof typeof manifest.${fieldName};`, `export type ${typeName}<P extends ${partitionTypeName} = ${partitionTypeName}> = (typeof manifest.${fieldName})[P][number];`].join("\n");
1922
+ }
1923
+ return `export type ${typeName} = (typeof manifest.${fieldName})[number];`;
1924
+ }
1970
1925
  async function generateAwsManifest(outputDir, routes, functions, crons, subscribers) {
1971
1926
  const manifestDir = (0, node_path.join)(outputDir, "manifest");
1972
1927
  await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
1973
- const awsRoutes = routes.filter((r) => r.method !== "ALL");
1928
+ const awsRoutes = filterAllRoutes(routes);
1929
+ const routesPartitioned = isPartitioned(awsRoutes);
1930
+ const functionsPartitioned = isPartitioned(functions);
1931
+ const cronsPartitioned = isPartitioned(crons);
1932
+ const subscribersPartitioned = isPartitioned(subscribers);
1974
1933
  const content = `export const manifest = {
1975
- routes: ${JSON.stringify(awsRoutes, null, 2)},
1976
- functions: ${JSON.stringify(functions, null, 2)},
1977
- crons: ${JSON.stringify(crons, null, 2)},
1978
- subscribers: ${JSON.stringify(subscribers, null, 2)},
1934
+ routes: ${serializeField(awsRoutes)},
1935
+ functions: ${serializeField(functions)},
1936
+ crons: ${serializeField(crons)},
1937
+ subscribers: ${serializeField(subscribers)},
1979
1938
  } as const;
1980
1939
 
1981
1940
  // Derived types
1982
- export type Route = (typeof manifest.routes)[number];
1983
- export type Function = (typeof manifest.functions)[number];
1984
- export type Cron = (typeof manifest.crons)[number];
1985
- export type Subscriber = (typeof manifest.subscribers)[number];
1941
+ ${generateDerivedType("routes", "Route", routesPartitioned)}
1942
+ ${generateDerivedType("functions", "Function", functionsPartitioned)}
1943
+ ${generateDerivedType("crons", "Cron", cronsPartitioned)}
1944
+ ${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
1986
1945
 
1987
1946
  // Useful union types
1988
1947
  export type Authorizer = Route['authorizer'];
@@ -1991,30 +1950,25 @@ export type RoutePath = Route['path'];
1991
1950
  `;
1992
1951
  const manifestPath = (0, node_path.join)(manifestDir, "aws.ts");
1993
1952
  await (0, node_fs_promises.writeFile)(manifestPath, content);
1994
- logger$10.log(`Generated AWS manifest with ${awsRoutes.length} routes, ${functions.length} functions, ${crons.length} crons, ${subscribers.length} subscribers`);
1953
+ logger$10.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers`);
1995
1954
  logger$10.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
1996
1955
  }
1997
1956
  async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
1998
1957
  const manifestDir = (0, node_path.join)(outputDir, "manifest");
1999
1958
  await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
2000
- const serverRoutes = routes.filter((r) => r.method !== "ALL").map((r) => ({
2001
- path: r.path,
2002
- method: r.method,
2003
- authorizer: r.authorizer
2004
- }));
2005
- const serverSubscribers = subscribers.map((s) => ({
2006
- name: s.name,
2007
- subscribedEvents: s.subscribedEvents
2008
- }));
1959
+ const serverRoutes = mapRouteMetadata(filterAllRoutes(routes));
1960
+ const serverSubscribers = mapSubscriberMetadata(subscribers);
1961
+ const routesPartitioned = isPartitioned(serverRoutes);
1962
+ const subscribersPartitioned = isPartitioned(serverSubscribers);
2009
1963
  const content = `export const manifest = {
2010
1964
  app: ${JSON.stringify(appInfo, null, 2)},
2011
- routes: ${JSON.stringify(serverRoutes, null, 2)},
2012
- subscribers: ${JSON.stringify(serverSubscribers, null, 2)},
1965
+ routes: ${serializeField(serverRoutes)},
1966
+ subscribers: ${serializeField(serverSubscribers)},
2013
1967
  } as const;
2014
1968
 
2015
1969
  // Derived types
2016
- export type Route = (typeof manifest.routes)[number];
2017
- export type Subscriber = (typeof manifest.subscribers)[number];
1970
+ ${generateDerivedType("routes", "Route", routesPartitioned)}
1971
+ ${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
2018
1972
 
2019
1973
  // Useful union types
2020
1974
  export type Authorizer = Route['authorizer'];
@@ -2023,9 +1977,70 @@ export type RoutePath = Route['path'];
2023
1977
  `;
2024
1978
  const manifestPath = (0, node_path.join)(manifestDir, "server.ts");
2025
1979
  await (0, node_fs_promises.writeFile)(manifestPath, content);
2026
- logger$10.log(`Generated server manifest with ${serverRoutes.length} routes, ${serverSubscribers.length} subscribers`);
1980
+ logger$10.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers`);
2027
1981
  logger$10.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
2028
1982
  }
1983
+ /**
1984
+ * Filter out 'ALL' method routes from a manifest field (flat or partitioned).
1985
+ */
1986
+ function filterAllRoutes(routes) {
1987
+ if (Array.isArray(routes)) return routes.filter((r) => r.method !== "ALL");
1988
+ const result = {};
1989
+ for (const [partition, partitionRoutes] of Object.entries(routes)) result[partition] = partitionRoutes.filter((r) => r.method !== "ALL");
1990
+ return result;
1991
+ }
1992
+ /**
1993
+ * Map routes to server metadata (path, method, authorizer only).
1994
+ */
1995
+ function mapRouteMetadata(routes) {
1996
+ const mapFn = (r) => ({
1997
+ path: r.path,
1998
+ method: r.method,
1999
+ authorizer: r.authorizer
2000
+ });
2001
+ if (Array.isArray(routes)) return routes.map(mapFn);
2002
+ const result = {};
2003
+ for (const [partition, partitionRoutes] of Object.entries(routes)) result[partition] = partitionRoutes.map(mapFn);
2004
+ return result;
2005
+ }
2006
+ /**
2007
+ * Map subscribers to server metadata (name, subscribedEvents only).
2008
+ */
2009
+ function mapSubscriberMetadata(subscribers) {
2010
+ const mapFn = (s) => ({
2011
+ name: s.name,
2012
+ subscribedEvents: s.subscribedEvents
2013
+ });
2014
+ if (Array.isArray(subscribers)) return subscribers.map(mapFn);
2015
+ const result = {};
2016
+ for (const [partition, partitionSubs] of Object.entries(subscribers)) result[partition] = partitionSubs.map(mapFn);
2017
+ return result;
2018
+ }
2019
+
2020
+ //#endregion
2021
+ //#region src/build/partitions.ts
2022
+ const DEFAULT_PARTITION = "default";
2023
+ /**
2024
+ * Check if any construct across the given arrays has a non-undefined partition.
2025
+ * When true, the manifest should use the partitioned shape for that construct type.
2026
+ */
2027
+ function hasPartitions(constructs) {
2028
+ return constructs.some((c) => c.partition !== void 0);
2029
+ }
2030
+ /**
2031
+ * Group an info array by partition, using the partition values from
2032
+ * the corresponding construct array. Both arrays must be the same length
2033
+ * and in the same order.
2034
+ */
2035
+ function groupInfosByPartition(infos, constructs) {
2036
+ const groups = {};
2037
+ for (let i = 0; i < infos.length; i++) {
2038
+ const partition = constructs[i]?.partition ?? DEFAULT_PARTITION;
2039
+ if (!groups[partition]) groups[partition] = [];
2040
+ groups[partition].push(infos[i]);
2041
+ }
2042
+ return groups;
2043
+ }
2029
2044
 
2030
2045
  //#endregion
2031
2046
  //#region src/build/index.ts
@@ -2047,10 +2062,10 @@ async function buildCommand(options) {
2047
2062
  const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
2048
2063
  if (production) logger$9.log(`🏭 Building for PRODUCTION`);
2049
2064
  logger$9.log(`Building with providers: ${resolved.providers.join(", ")}`);
2050
- logger$9.log(`Loading routes from: ${config.routes}`);
2051
- if (config.functions) logger$9.log(`Loading functions from: ${config.functions}`);
2052
- if (config.crons) logger$9.log(`Loading crons from: ${config.crons}`);
2053
- if (config.subscribers) logger$9.log(`Loading subscribers from: ${config.subscribers}`);
2065
+ logger$9.log(`Loading routes from: ${formatRoutes(config.routes)}`);
2066
+ if (config.functions) logger$9.log(`Loading functions from: ${formatRoutes(config.functions)}`);
2067
+ if (config.crons) logger$9.log(`Loading crons from: ${formatRoutes(config.crons)}`);
2068
+ if (config.subscribers) logger$9.log(`Loading subscribers from: ${formatRoutes(config.subscribers)}`);
2054
2069
  logger$9.log(`Using envParser: ${config.envParser}`);
2055
2070
  const { path: envParserPath, importPattern: envParserImportPattern } = require_config.parseModuleConfig(config.envParser, "envParser");
2056
2071
  const { path: loggerPath, importPattern: loggerImportPattern } = require_config.parseModuleConfig(config.logger, "logger");
@@ -2122,6 +2137,10 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
2122
2137
  subscriberGenerator.build(context, subscribers, outputDir, { provider })
2123
2138
  ]);
2124
2139
  logger$9.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
2140
+ const manifestRoutes = assembleManifestField(routes, endpoints);
2141
+ const manifestFunctions = assembleManifestField(functionInfos, functions);
2142
+ const manifestCrons = assembleManifestField(cronInfos, crons);
2143
+ const manifestSubscribers = assembleManifestField(subscriberInfos, subscribers);
2125
2144
  if (provider === "server") {
2126
2145
  const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
2127
2146
  path: construct._path,
@@ -2129,15 +2148,16 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
2129
2148
  handler: "",
2130
2149
  authorizer: construct.authorizer?.name ?? "none"
2131
2150
  })));
2151
+ const serverRouteField = assembleManifestField(routeMetadata, endpoints);
2132
2152
  const appInfo = {
2133
2153
  handler: (0, node_path.relative)(process.cwd(), (0, node_path.join)(outputDir, "app.ts")),
2134
2154
  endpoints: (0, node_path.relative)(process.cwd(), (0, node_path.join)(outputDir, "endpoints.ts"))
2135
2155
  };
2136
- await generateServerManifest(rootOutputDir, appInfo, routeMetadata, subscriberInfos);
2156
+ await generateServerManifest(rootOutputDir, appInfo, serverRouteField, manifestSubscribers);
2137
2157
  let masterKey;
2138
2158
  if (context.production?.bundle && !skipBundle) {
2139
2159
  logger$9.log(`\n📦 Bundling production server...`);
2140
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-CuMIfXw5.cjs"));
2160
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-NpfYPBUo.cjs"));
2141
2161
  const allConstructs = [
2142
2162
  ...endpoints.map((e) => e.construct),
2143
2163
  ...functions.map((f) => f.construct),
@@ -2163,7 +2183,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
2163
2183
  }
2164
2184
  }
2165
2185
  return { masterKey };
2166
- } else await generateAwsManifest(rootOutputDir, routes, functionInfos, cronInfos, subscriberInfos);
2186
+ } else await generateAwsManifest(rootOutputDir, manifestRoutes, manifestFunctions, manifestCrons, manifestSubscribers);
2167
2187
  return {};
2168
2188
  }
2169
2189
  /**
@@ -2261,6 +2281,25 @@ function getAppOutputPath(workspace, _appName, app) {
2261
2281
  if (app.type === "frontend") return (0, node_path.join)(appPath, ".next");
2262
2282
  else return (0, node_path.join)(appPath, ".gkm");
2263
2283
  }
2284
+ /**
2285
+ * Format routes for logging, handling PartitionedRoutes.
2286
+ */
2287
+ function formatRoutes(routes) {
2288
+ if (require_openapi.isPartitionedRoutes(routes)) {
2289
+ const paths = Array.isArray(routes.paths) ? routes.paths.join(", ") : routes.paths;
2290
+ return `${paths} (partitioned)`;
2291
+ }
2292
+ return Array.isArray(routes) ? routes.join(", ") : routes;
2293
+ }
2294
+ /**
2295
+ * Assemble a ManifestField from build infos and constructs.
2296
+ * If any construct has a partition, returns a Record<string, T[]>.
2297
+ * Otherwise, returns a flat T[].
2298
+ */
2299
+ function assembleManifestField(infos, constructs) {
2300
+ if (!hasPartitions(constructs)) return infos;
2301
+ return groupInfosByPartition(infos, constructs);
2302
+ }
2264
2303
 
2265
2304
  //#endregion
2266
2305
  //#region src/deploy/state.ts
@@ -2416,11 +2455,11 @@ async function createDnsProvider(options) {
2416
2455
  if (isDnsProvider(config.provider)) return config.provider;
2417
2456
  const provider = config.provider;
2418
2457
  if (provider === "hostinger") {
2419
- const { HostingerProvider } = await Promise.resolve().then(() => require("./HostingerProvider-CEsQbmpY.cjs"));
2458
+ const { HostingerProvider } = await Promise.resolve().then(() => require("./HostingerProvider-5KYmwoK2.cjs"));
2420
2459
  return new HostingerProvider();
2421
2460
  }
2422
2461
  if (provider === "route53") {
2423
- const { Route53Provider } = await Promise.resolve().then(() => require("./Route53Provider-BqXeHzuc.cjs"));
2462
+ const { Route53Provider } = await Promise.resolve().then(() => require("./Route53Provider-owQQ4pn6.cjs"));
2424
2463
  const route53Config = config;
2425
2464
  return new Route53Provider({
2426
2465
  region: route53Config.region,
@@ -4668,19 +4707,19 @@ function isStateProvider(value) {
4668
4707
  async function createStateProvider(options) {
4669
4708
  const { config, workspaceRoot, workspaceName } = options;
4670
4709
  if (!config) {
4671
- const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-Roi202l7.cjs"));
4710
+ const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CLifRC0Y.cjs"));
4672
4711
  return new LocalStateProvider(workspaceRoot);
4673
4712
  }
4674
4713
  if (isStateProvider(config.provider)) return config.provider;
4675
4714
  const provider = config.provider;
4676
4715
  if (provider === "local") {
4677
- const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-Roi202l7.cjs"));
4716
+ const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CLifRC0Y.cjs"));
4678
4717
  return new LocalStateProvider(workspaceRoot);
4679
4718
  }
4680
4719
  if (provider === "ssm") {
4681
4720
  if (!workspaceName) throw new Error("Workspace name is required for SSM state provider. Set \"name\" in gkm.config.ts.");
4682
- const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-Roi202l7.cjs"));
4683
- const { SSMStateProvider } = await Promise.resolve().then(() => require("./SSMStateProvider-BReQA5re.cjs"));
4721
+ const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CLifRC0Y.cjs"));
4722
+ const { SSMStateProvider } = await Promise.resolve().then(() => require("./SSMStateProvider-CT8tjl9o.cjs"));
4684
4723
  const { CachedStateProvider: CachedStateProvider$1 } = await Promise.resolve().then(() => require("./CachedStateProvider-D_uISMmJ.cjs"));
4685
4724
  const ssmConfig = config;
4686
4725
  const local = new LocalStateProvider(workspaceRoot);
@@ -4875,7 +4914,7 @@ async function sniffAppEnvironment(app, appName, workspacePath, options = {}) {
4875
4914
  };
4876
4915
  }
4877
4916
  if (app.routes) {
4878
- const result = await sniffRouteFiles(app.routes, app.path, workspacePath);
4917
+ const result = await sniffRouteFiles(require_openapi.normalizeRoutes(app.routes), app.path, workspacePath);
4879
4918
  if (logWarnings && result.error) console.warn(`[sniffer] ${appName}: Route sniffing threw error (env vars still captured): ${result.error.message}`);
4880
4919
  return {
4881
4920
  appName,
@@ -5355,8 +5394,8 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
5355
5394
  else logger$3.log(` ⚠ Cached ID invalid, will create new`);
5356
5395
  }
5357
5396
  if (!redis) {
5358
- const { randomBytes: randomBytes$3 } = await import("node:crypto");
5359
- const databasePassword = randomBytes$3(16).toString("hex");
5397
+ const { randomBytes: randomBytes$2 } = await import("node:crypto");
5398
+ const databasePassword = randomBytes$2(16).toString("hex");
5360
5399
  const result = await api.findOrCreateRedis(redisName, projectId, environmentId, { databasePassword });
5361
5400
  redis = result.redis;
5362
5401
  created = result.created;
@@ -5780,7 +5819,7 @@ async function workspaceDeployCommand(workspace, options) {
5780
5819
  }
5781
5820
  if (workspace.deploy?.backups && provisionedPostgres) {
5782
5821
  logger$3.log("\n💾 Provisioning backup destination...");
5783
- const { provisionBackupDestination } = await Promise.resolve().then(() => require("./backup-provisioner-C8VK63I-.cjs"));
5822
+ const { provisionBackupDestination } = await Promise.resolve().then(() => require("./backup-provisioner-C4noe75O.cjs"));
5784
5823
  const backupState = await provisionBackupDestination({
5785
5824
  api,
5786
5825
  projectId: project.projectId,
@@ -6405,200 +6444,6 @@ function printStateDetails(state) {
6405
6444
  }
6406
6445
  }
6407
6446
 
6408
- //#endregion
6409
- //#region src/secrets/generator.ts
6410
- /**
6411
- * Generate a secure random password using URL-safe base64 characters.
6412
- * @param length Password length (default: 32)
6413
- */
6414
- function generateSecurePassword(length = 32) {
6415
- return (0, node_crypto.randomBytes)(Math.ceil(length * 3 / 4)).toString("base64url").slice(0, length);
6416
- }
6417
- /** Default service configurations */
6418
- const SERVICE_DEFAULTS = {
6419
- postgres: {
6420
- host: "postgres",
6421
- port: 5432,
6422
- username: "app",
6423
- database: "app"
6424
- },
6425
- redis: {
6426
- host: "redis",
6427
- port: 6379,
6428
- username: "default"
6429
- },
6430
- rabbitmq: {
6431
- host: "rabbitmq",
6432
- port: 5672,
6433
- username: "app",
6434
- vhost: "/"
6435
- }
6436
- };
6437
- /**
6438
- * Generate credentials for a specific service.
6439
- */
6440
- function generateServiceCredentials(service) {
6441
- const defaults = SERVICE_DEFAULTS[service];
6442
- return {
6443
- ...defaults,
6444
- password: generateSecurePassword()
6445
- };
6446
- }
6447
- /**
6448
- * Generate credentials for multiple services.
6449
- */
6450
- function generateServicesCredentials(services) {
6451
- const result = {};
6452
- for (const service of services) result[service] = generateServiceCredentials(service);
6453
- return result;
6454
- }
6455
- /**
6456
- * Generate connection URL for PostgreSQL.
6457
- */
6458
- function generatePostgresUrl(creds) {
6459
- const { username, password, host, port, database } = creds;
6460
- return `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
6461
- }
6462
- /**
6463
- * Generate connection URL for Redis.
6464
- */
6465
- function generateRedisUrl(creds) {
6466
- const { password, host, port } = creds;
6467
- return `redis://:${encodeURIComponent(password)}@${host}:${port}`;
6468
- }
6469
- /**
6470
- * Generate connection URL for RabbitMQ.
6471
- */
6472
- function generateRabbitmqUrl(creds) {
6473
- const { username, password, host, port, vhost } = creds;
6474
- const encodedVhost = encodeURIComponent(vhost ?? "/");
6475
- return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;
6476
- }
6477
- /**
6478
- * Generate connection URLs from service credentials.
6479
- */
6480
- function generateConnectionUrls(services) {
6481
- const urls = {};
6482
- if (services.postgres) urls.DATABASE_URL = generatePostgresUrl(services.postgres);
6483
- if (services.redis) urls.REDIS_URL = generateRedisUrl(services.redis);
6484
- if (services.rabbitmq) urls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);
6485
- return urls;
6486
- }
6487
- /**
6488
- * Create a new StageSecrets object with generated credentials.
6489
- */
6490
- function createStageSecrets(stage, services) {
6491
- const now = (/* @__PURE__ */ new Date()).toISOString();
6492
- const serviceCredentials = generateServicesCredentials(services);
6493
- const urls = generateConnectionUrls(serviceCredentials);
6494
- return {
6495
- stage,
6496
- createdAt: now,
6497
- updatedAt: now,
6498
- services: serviceCredentials,
6499
- urls,
6500
- custom: {}
6501
- };
6502
- }
6503
- /**
6504
- * Rotate password for a specific service.
6505
- */
6506
- function rotateServicePassword(secrets, service) {
6507
- const currentCreds = secrets.services[service];
6508
- if (!currentCreds) throw new Error(`Service "${service}" not configured in secrets`);
6509
- const newCreds = {
6510
- ...currentCreds,
6511
- password: generateSecurePassword()
6512
- };
6513
- const newServices = {
6514
- ...secrets.services,
6515
- [service]: newCreds
6516
- };
6517
- return {
6518
- ...secrets,
6519
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6520
- services: newServices,
6521
- urls: generateConnectionUrls(newServices)
6522
- };
6523
- }
6524
-
6525
- //#endregion
6526
- //#region src/setup/fullstack-secrets.ts
6527
- /**
6528
- * Generate a secure random password for database users.
6529
- * Uses a combination of timestamp and random bytes for uniqueness.
6530
- */
6531
- function generateDbPassword() {
6532
- return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
6533
- }
6534
- /**
6535
- * Generate database URL for an app.
6536
- * All apps connect to the same database, but use different users/schemas.
6537
- */
6538
- function generateDbUrl(appName, password, projectName, host = "localhost", port = 5432) {
6539
- const userName = appName.replace(/-/g, "_");
6540
- const dbName = `${projectName.replace(/-/g, "_")}_dev`;
6541
- return `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;
6542
- }
6543
- /**
6544
- * Generate fullstack-aware custom secrets for a workspace.
6545
- *
6546
- * Generates:
6547
- * - Common secrets: NODE_ENV, PORT, LOG_LEVEL, JWT_SECRET
6548
- * - Per-app database passwords and URLs for backend apps with db service
6549
- * - Better-auth secrets for apps using the better-auth framework
6550
- */
6551
- function generateFullstackCustomSecrets(workspace) {
6552
- const hasDb = !!workspace.services.db;
6553
- const customs = {
6554
- NODE_ENV: "development",
6555
- PORT: "3000",
6556
- LOG_LEVEL: "debug",
6557
- JWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`
6558
- };
6559
- if (!hasDb) return customs;
6560
- const frontendPorts = [];
6561
- for (const [appName, appConfig] of Object.entries(workspace.apps)) {
6562
- if (appConfig.type === "frontend") {
6563
- frontendPorts.push(appConfig.port);
6564
- continue;
6565
- }
6566
- const password = generateDbPassword();
6567
- const upperName = appName.toUpperCase();
6568
- customs[`${upperName}_DATABASE_URL`] = generateDbUrl(appName, password, workspace.name);
6569
- customs[`${upperName}_DB_PASSWORD`] = password;
6570
- if (appConfig.framework === "better-auth") {
6571
- customs.AUTH_PORT = String(appConfig.port);
6572
- customs.AUTH_URL = `http://localhost:${appConfig.port}`;
6573
- customs.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;
6574
- customs.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;
6575
- }
6576
- }
6577
- if (customs.BETTER_AUTH_SECRET) {
6578
- const allPorts = Object.values(workspace.apps).map((a) => a.port);
6579
- customs.BETTER_AUTH_TRUSTED_ORIGINS = allPorts.map((p) => `http://localhost:${p}`).join(",");
6580
- }
6581
- return customs;
6582
- }
6583
- /**
6584
- * Extract *_DB_PASSWORD keys from secrets and write docker/.env.
6585
- *
6586
- * The docker/.env file contains database passwords that the PostgreSQL
6587
- * init script reads to create per-app database users.
6588
- */
6589
- async function writeDockerEnvFromSecrets(secrets, workspaceRoot) {
6590
- const dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) => key.endsWith("_DB_PASSWORD"));
6591
- if (dbPasswordEntries.length === 0) return;
6592
- const envContent = `# Auto-generated docker environment file
6593
- # Contains database passwords for docker-compose postgres init
6594
- # This file is gitignored - do not commit to version control
6595
- ${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join("\n")}
6596
- `;
6597
- const envPath = (0, node_path.join)(workspaceRoot, "docker", ".env");
6598
- await (0, node_fs_promises.mkdir)((0, node_path.dirname)(envPath), { recursive: true });
6599
- await (0, node_fs_promises.writeFile)(envPath, envContent);
6600
- }
6601
-
6602
6447
  //#endregion
6603
6448
  //#region src/init/versions.ts
6604
6449
  const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
@@ -6624,14 +6469,14 @@ const GEEKMIDAS_VERSIONS = {
6624
6469
  "@geekmidas/audit": "~1.0.0",
6625
6470
  "@geekmidas/auth": "~1.0.0",
6626
6471
  "@geekmidas/cache": "~1.0.0",
6627
- "@geekmidas/client": "~2.0.0",
6472
+ "@geekmidas/client": "~3.0.0",
6628
6473
  "@geekmidas/cloud": "~1.0.0",
6629
- "@geekmidas/constructs": "~1.1.1",
6474
+ "@geekmidas/constructs": "~2.0.0",
6630
6475
  "@geekmidas/db": "~1.0.0",
6631
6476
  "@geekmidas/emailkit": "~1.0.0",
6632
6477
  "@geekmidas/envkit": "~1.0.3",
6633
6478
  "@geekmidas/errors": "~1.0.0",
6634
- "@geekmidas/events": "~1.0.0",
6479
+ "@geekmidas/events": "~1.1.0",
6635
6480
  "@geekmidas/logger": "~1.0.0",
6636
6481
  "@geekmidas/rate-limit": "~1.0.0",
6637
6482
  "@geekmidas/schema": "~1.0.0",
@@ -6639,7 +6484,7 @@ const GEEKMIDAS_VERSIONS = {
6639
6484
  "@geekmidas/storage": "~1.0.0",
6640
6485
  "@geekmidas/studio": "~1.0.0",
6641
6486
  "@geekmidas/telescope": "~1.0.0",
6642
- "@geekmidas/testkit": "~1.0.1",
6487
+ "@geekmidas/testkit": "~1.0.2",
6643
6488
  "@geekmidas/cli": CLI_VERSION
6644
6489
  };
6645
6490
 
@@ -10948,10 +10793,10 @@ async function initCommand(projectName, options = {}) {
10948
10793
  const dbApps = [];
10949
10794
  if (isFullstack && services.db) dbApps.push({
10950
10795
  name: "api",
10951
- password: generateDbPassword()
10796
+ password: require_fullstack_secrets.generateDbPassword()
10952
10797
  }, {
10953
10798
  name: "auth",
10954
- password: generateDbPassword()
10799
+ password: require_fullstack_secrets.generateDbPassword()
10955
10800
  });
10956
10801
  const appFiles = baseTemplate ? [
10957
10802
  ...generatePackageJson(templateOptions, baseTemplate),
@@ -11000,7 +10845,7 @@ async function initCommand(projectName, options = {}) {
11000
10845
  const secretServices = [];
11001
10846
  if (services.db) secretServices.push("postgres");
11002
10847
  if (services.cache) secretServices.push("redis");
11003
- const devSecrets = createStageSecrets("development", secretServices);
10848
+ const devSecrets = require_fullstack_secrets.createStageSecrets("development", secretServices);
11004
10849
  const customSecrets = {
11005
10850
  NODE_ENV: "development",
11006
10851
  PORT: "3000",
@@ -11010,7 +10855,7 @@ async function initCommand(projectName, options = {}) {
11010
10855
  if (isFullstack && dbApps.length > 0) {
11011
10856
  for (const app of dbApps) {
11012
10857
  const urlKey = `${app.name.toUpperCase()}_DATABASE_URL`;
11013
- customSecrets[urlKey] = generateDbUrl(app.name, app.password, name$1);
10858
+ customSecrets[urlKey] = require_fullstack_secrets.generateDbUrl(app.name, app.password, name$1);
11014
10859
  const passwordKey = `${app.name.toUpperCase()}_DB_PASSWORD`;
11015
10860
  customSecrets[passwordKey] = app.password;
11016
10861
  }
@@ -11137,12 +10982,12 @@ async function secretsInitCommand(options) {
11137
10982
  const config = await require_config.loadConfig();
11138
10983
  const services = getServicesFromConfig(config.docker?.compose?.services);
11139
10984
  if (services.length === 0) logger$2.warn("No services configured in docker.compose.services. Creating secrets with empty services.");
11140
- const secrets = createStageSecrets(stage, services);
10985
+ const secrets = require_fullstack_secrets.createStageSecrets(stage, services);
11141
10986
  try {
11142
10987
  const loaded = await require_config.loadWorkspaceConfig();
11143
10988
  const isMultiApp = Object.keys(loaded.workspace.apps).length > 1;
11144
10989
  if (isMultiApp) {
11145
- const customSecrets = generateFullstackCustomSecrets(loaded.workspace);
10990
+ const customSecrets = require_fullstack_secrets.generateFullstackCustomSecrets(loaded.workspace);
11146
10991
  secrets.custom = customSecrets;
11147
10992
  logger$2.log(" Detected workspace mode — generating per-app secrets");
11148
10993
  }
@@ -11243,13 +11088,13 @@ async function secretsRotateCommand(options) {
11243
11088
  logger$2.error(`Service "${service}" not configured in stage "${stage}"`);
11244
11089
  process.exit(1);
11245
11090
  }
11246
- const updated = rotateServicePassword(secrets, service);
11091
+ const updated = require_fullstack_secrets.rotateServicePassword(secrets, service);
11247
11092
  await require_storage.writeStageSecrets(updated);
11248
11093
  logger$2.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
11249
11094
  } else {
11250
11095
  let updated = secrets;
11251
11096
  const services = Object.keys(secrets.services);
11252
- for (const svc of services) updated = rotateServicePassword(updated, svc);
11097
+ for (const svc of services) updated = require_fullstack_secrets.rotateServicePassword(updated, svc);
11253
11098
  await require_storage.writeStageSecrets(updated);
11254
11099
  logger$2.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
11255
11100
  }
@@ -11342,7 +11187,7 @@ async function setupCommand(options = {}) {
11342
11187
  process.exit(1);
11343
11188
  }
11344
11189
  if (isMultiApp && workspace.services.db) {
11345
- await writeDockerEnvFromSecrets(secrets, workspace.root);
11190
+ await require_fullstack_secrets.writeDockerEnvFromSecrets(secrets, workspace.root);
11346
11191
  logger$1.log("📄 Generated docker/.env with database passwords");
11347
11192
  }
11348
11193
  if (!options.skipDocker) {
@@ -11396,10 +11241,10 @@ async function generateFreshSecrets(stage, workspace, options) {
11396
11241
  const serviceNames = [];
11397
11242
  if (workspace.services.db) serviceNames.push("postgres");
11398
11243
  if (workspace.services.cache) serviceNames.push("redis");
11399
- const secrets = createStageSecrets(stage, serviceNames);
11244
+ const secrets = require_fullstack_secrets.createStageSecrets(stage, serviceNames);
11400
11245
  const isMultiApp = Object.keys(workspace.apps).length > 1;
11401
11246
  if (isMultiApp) {
11402
- const customSecrets = generateFullstackCustomSecrets(workspace);
11247
+ const customSecrets = require_fullstack_secrets.generateFullstackCustomSecrets(workspace);
11403
11248
  secrets.custom = customSecrets;
11404
11249
  } else secrets.custom = {
11405
11250
  NODE_ENV: "development",
@@ -11943,8 +11788,19 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
11943
11788
  const globalOptions = program.opts();
11944
11789
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11945
11790
  const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
11946
- const { pushSecrets: pushSecrets$1 } = await Promise.resolve().then(() => require("./sync-BxFB34zW.cjs"));
11791
+ const { pushSecrets: pushSecrets$1 } = await Promise.resolve().then(() => require("./sync-RsnjXYwG.cjs"));
11792
+ const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-7yarEvmK.cjs"));
11793
+ const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-C7pmBq1u.cjs"));
11947
11794
  const { workspace } = await loadWorkspaceConfig$1();
11795
+ const secrets = await readStageSecrets$1(options.stage, workspace.root);
11796
+ if (secrets) {
11797
+ const result = reconcileMissingSecrets(secrets, workspace);
11798
+ if (result) {
11799
+ await writeStageSecrets$1(result.secrets, workspace.root);
11800
+ console.log(` Reconciled ${result.addedKeys.length} missing secret(s):`);
11801
+ for (const key of result.addedKeys) console.log(` + ${key}`);
11802
+ }
11803
+ }
11948
11804
  await pushSecrets$1(options.stage, workspace);
11949
11805
  console.log(`\n✓ Secrets pushed for stage "${options.stage}"`);
11950
11806
  } catch (error) {
@@ -11957,14 +11813,21 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
11957
11813
  const globalOptions = program.opts();
11958
11814
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11959
11815
  const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
11960
- const { pullSecrets: pullSecrets$1 } = await Promise.resolve().then(() => require("./sync-BxFB34zW.cjs"));
11816
+ const { pullSecrets: pullSecrets$1 } = await Promise.resolve().then(() => require("./sync-RsnjXYwG.cjs"));
11961
11817
  const { writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-C7pmBq1u.cjs"));
11818
+ const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-7yarEvmK.cjs"));
11962
11819
  const { workspace } = await loadWorkspaceConfig$1();
11963
- const secrets = await pullSecrets$1(options.stage, workspace);
11820
+ let secrets = await pullSecrets$1(options.stage, workspace);
11964
11821
  if (!secrets) {
11965
11822
  console.error(`No remote secrets found for stage "${options.stage}".`);
11966
11823
  process.exit(1);
11967
11824
  }
11825
+ const result = reconcileMissingSecrets(secrets, workspace);
11826
+ if (result) {
11827
+ secrets = result.secrets;
11828
+ console.log(` Reconciled ${result.addedKeys.length} missing secret(s):`);
11829
+ for (const key of result.addedKeys) console.log(` + ${key}`);
11830
+ }
11968
11831
  await writeStageSecrets$1(secrets, workspace.root);
11969
11832
  console.log(`\n✓ Secrets pulled for stage "${options.stage}"`);
11970
11833
  } catch (error) {
@@ -11972,6 +11835,32 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
11972
11835
  process.exit(1);
11973
11836
  }
11974
11837
  });
11838
+ program.command("secrets:reconcile").description("Backfill missing custom secrets from workspace config").option("--stage <stage>", "Stage name", "development").action(async (options) => {
11839
+ try {
11840
+ const globalOptions = program.opts();
11841
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11842
+ const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
11843
+ const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-7yarEvmK.cjs"));
11844
+ const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-C7pmBq1u.cjs"));
11845
+ const { workspace } = await loadWorkspaceConfig$1();
11846
+ const secrets = await readStageSecrets$1(options.stage, workspace.root);
11847
+ if (!secrets) {
11848
+ console.error(`No secrets found for stage "${options.stage}". Run "gkm secrets:init --stage ${options.stage}" first.`);
11849
+ process.exit(1);
11850
+ }
11851
+ const result = reconcileMissingSecrets(secrets, workspace);
11852
+ if (!result) {
11853
+ console.log(`\n✓ Secrets for stage "${options.stage}" are up-to-date`);
11854
+ return;
11855
+ }
11856
+ await writeStageSecrets$1(result.secrets, workspace.root);
11857
+ console.log(`\n✓ Reconciled ${result.addedKeys.length} missing secret(s) for stage "${options.stage}":`);
11858
+ for (const key of result.addedKeys) console.log(` + ${key}`);
11859
+ } catch (error) {
11860
+ console.error(error instanceof Error ? error.message : "Command failed");
11861
+ process.exit(1);
11862
+ }
11863
+ });
11975
11864
  program.command("deploy").description("Deploy application to a provider").requiredOption("--provider <provider>", "Deploy provider (docker, dokploy, aws-lambda)").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").option("--tag <tag>", "Image tag (default: stage-timestamp)").option("--skip-push", "Skip pushing image to registry").option("--skip-build", "Skip build step (use existing build)").action(async (options) => {
11976
11865
  try {
11977
11866
  const globalOptions = program.opts();