@geekmidas/cli 0.10.0 → 0.13.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 (146) hide show
  1. package/README.md +525 -0
  2. package/dist/bundler-B1qy9b-j.cjs +112 -0
  3. package/dist/bundler-B1qy9b-j.cjs.map +1 -0
  4. package/dist/bundler-DskIqW2t.mjs +111 -0
  5. package/dist/bundler-DskIqW2t.mjs.map +1 -0
  6. package/dist/{config-C9aXOHBe.cjs → config-AmInkU7k.cjs} +8 -8
  7. package/dist/config-AmInkU7k.cjs.map +1 -0
  8. package/dist/{config-BrkUalUh.mjs → config-DYULeEv8.mjs} +3 -3
  9. package/dist/config-DYULeEv8.mjs.map +1 -0
  10. package/dist/config.cjs +1 -1
  11. package/dist/config.d.cts +1 -1
  12. package/dist/config.d.mts +1 -1
  13. package/dist/config.mjs +1 -1
  14. package/dist/encryption-C8H-38Yy.mjs +42 -0
  15. package/dist/encryption-C8H-38Yy.mjs.map +1 -0
  16. package/dist/encryption-Dyf_r1h-.cjs +44 -0
  17. package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
  18. package/dist/index.cjs +2123 -179
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.mjs +2141 -192
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/{openapi-CZLI4QTr.mjs → openapi-BfFlOBCG.mjs} +801 -38
  23. package/dist/openapi-BfFlOBCG.mjs.map +1 -0
  24. package/dist/{openapi-BeHLKcwP.cjs → openapi-Bt_1FDpT.cjs} +794 -31
  25. package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
  26. package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
  27. package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
  28. package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
  29. package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
  30. package/dist/openapi-react-query.cjs +1 -1
  31. package/dist/openapi-react-query.d.cts.map +1 -1
  32. package/dist/openapi-react-query.d.mts.map +1 -1
  33. package/dist/openapi-react-query.mjs +1 -1
  34. package/dist/openapi.cjs +2 -2
  35. package/dist/openapi.d.cts +1 -1
  36. package/dist/openapi.d.cts.map +1 -1
  37. package/dist/openapi.d.mts +1 -1
  38. package/dist/openapi.d.mts.map +1 -1
  39. package/dist/openapi.mjs +2 -2
  40. package/dist/storage-BOOpAF8N.cjs +5 -0
  41. package/dist/storage-Bj1E26lU.cjs +187 -0
  42. package/dist/storage-Bj1E26lU.cjs.map +1 -0
  43. package/dist/storage-kSxTjkNb.mjs +133 -0
  44. package/dist/storage-kSxTjkNb.mjs.map +1 -0
  45. package/dist/storage-tgZSUnKl.mjs +3 -0
  46. package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
  47. package/dist/types-BR0M2v_c.d.mts.map +1 -0
  48. package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
  49. package/dist/types-BhkZc-vm.d.cts.map +1 -0
  50. package/examples/cron-example.ts +27 -27
  51. package/examples/env.ts +27 -27
  52. package/examples/function-example.ts +31 -31
  53. package/examples/gkm.config.json +20 -20
  54. package/examples/gkm.config.ts +8 -8
  55. package/examples/gkm.minimal.config.json +5 -5
  56. package/examples/gkm.production.config.json +25 -25
  57. package/examples/logger.ts +2 -2
  58. package/package.json +6 -6
  59. package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
  60. package/src/__tests__/config.spec.ts +55 -55
  61. package/src/__tests__/loadEnvFiles.spec.ts +93 -93
  62. package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
  63. package/src/__tests__/openapi-react-query.spec.ts +497 -497
  64. package/src/__tests__/openapi.spec.ts +428 -428
  65. package/src/__tests__/test-helpers.ts +76 -76
  66. package/src/auth/__tests__/credentials.spec.ts +204 -0
  67. package/src/auth/__tests__/index.spec.ts +168 -0
  68. package/src/auth/credentials.ts +187 -0
  69. package/src/auth/index.ts +226 -0
  70. package/src/build/__tests__/bundler.spec.ts +444 -0
  71. package/src/build/__tests__/index-new.spec.ts +474 -474
  72. package/src/build/__tests__/manifests.spec.ts +333 -333
  73. package/src/build/bundler.ts +210 -0
  74. package/src/build/endpoint-analyzer.ts +236 -0
  75. package/src/build/handler-templates.ts +1253 -0
  76. package/src/build/index.ts +260 -179
  77. package/src/build/manifests.ts +52 -52
  78. package/src/build/providerResolver.ts +145 -145
  79. package/src/build/types.ts +64 -43
  80. package/src/config.ts +39 -39
  81. package/src/deploy/__tests__/docker.spec.ts +111 -0
  82. package/src/deploy/__tests__/dokploy.spec.ts +245 -0
  83. package/src/deploy/__tests__/init.spec.ts +662 -0
  84. package/src/deploy/docker.ts +128 -0
  85. package/src/deploy/dokploy.ts +204 -0
  86. package/src/deploy/index.ts +136 -0
  87. package/src/deploy/init.ts +484 -0
  88. package/src/deploy/types.ts +48 -0
  89. package/src/dev/__tests__/index.spec.ts +266 -266
  90. package/src/dev/index.ts +647 -601
  91. package/src/docker/__tests__/compose.spec.ts +531 -0
  92. package/src/docker/__tests__/templates.spec.ts +280 -0
  93. package/src/docker/compose.ts +273 -0
  94. package/src/docker/index.ts +230 -0
  95. package/src/docker/templates.ts +446 -0
  96. package/src/generators/CronGenerator.ts +72 -72
  97. package/src/generators/EndpointGenerator.ts +699 -398
  98. package/src/generators/FunctionGenerator.ts +84 -84
  99. package/src/generators/Generator.ts +72 -72
  100. package/src/generators/OpenApiTsGenerator.ts +577 -577
  101. package/src/generators/SubscriberGenerator.ts +124 -124
  102. package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
  103. package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
  104. package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
  105. package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
  106. package/src/generators/index.ts +4 -4
  107. package/src/index.ts +623 -201
  108. package/src/init/__tests__/generators.spec.ts +334 -334
  109. package/src/init/__tests__/init.spec.ts +332 -332
  110. package/src/init/__tests__/utils.spec.ts +89 -89
  111. package/src/init/generators/config.ts +175 -175
  112. package/src/init/generators/docker.ts +41 -41
  113. package/src/init/generators/env.ts +72 -72
  114. package/src/init/generators/index.ts +1 -1
  115. package/src/init/generators/models.ts +64 -64
  116. package/src/init/generators/monorepo.ts +161 -161
  117. package/src/init/generators/package.ts +71 -71
  118. package/src/init/generators/source.ts +6 -6
  119. package/src/init/index.ts +203 -208
  120. package/src/init/templates/api.ts +115 -115
  121. package/src/init/templates/index.ts +75 -75
  122. package/src/init/templates/minimal.ts +98 -98
  123. package/src/init/templates/serverless.ts +89 -89
  124. package/src/init/templates/worker.ts +98 -98
  125. package/src/init/utils.ts +54 -56
  126. package/src/openapi-react-query.ts +194 -194
  127. package/src/openapi.ts +63 -63
  128. package/src/secrets/__tests__/encryption.spec.ts +226 -0
  129. package/src/secrets/__tests__/generator.spec.ts +319 -0
  130. package/src/secrets/__tests__/index.spec.ts +91 -0
  131. package/src/secrets/__tests__/storage.spec.ts +611 -0
  132. package/src/secrets/encryption.ts +91 -0
  133. package/src/secrets/generator.ts +164 -0
  134. package/src/secrets/index.ts +383 -0
  135. package/src/secrets/storage.ts +192 -0
  136. package/src/secrets/types.ts +53 -0
  137. package/src/types.ts +295 -176
  138. package/tsdown.config.ts +11 -8
  139. package/dist/config-BrkUalUh.mjs.map +0 -1
  140. package/dist/config-C9aXOHBe.cjs.map +0 -1
  141. package/dist/openapi-BeHLKcwP.cjs.map +0 -1
  142. package/dist/openapi-CZLI4QTr.mjs.map +0 -1
  143. package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
  144. package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
  145. package/dist/types-DXgiA1sF.d.mts.map +0 -1
  146. package/dist/types-b-vwGpqc.d.cts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,13 +1,17 @@
1
1
  #!/usr/bin/env -S npx tsx
2
- import { loadConfig, parseModuleConfig } from "./config-BrkUalUh.mjs";
3
- import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-CZLI4QTr.mjs";
4
- import { generateReactQueryCommand } from "./openapi-react-query-CcciaVu5.mjs";
5
- import { join, relative } from "path";
2
+ import { loadConfig, parseModuleConfig } from "./config-DYULeEv8.mjs";
3
+ import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-BfFlOBCG.mjs";
4
+ import { generateReactQueryCommand } from "./openapi-react-query-B6XTeGqS.mjs";
5
+ import { maskPassword, readStageSecrets, secretsExist, setCustomSecret, writeStageSecrets } from "./storage-kSxTjkNb.mjs";
6
+ import { createRequire } from "node:module";
7
+ import { existsSync, mkdirSync } from "node:fs";
8
+ import { dirname, join, parse, relative, resolve } from "node:path";
6
9
  import { Command } from "commander";
7
- import { mkdir, writeFile } from "node:fs/promises";
8
- import { dirname, join as join$1, relative as relative$1, resolve } from "node:path";
10
+ import { stdin, stdout } from "node:process";
11
+ import * as readline from "node:readline/promises";
12
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
13
+ import { homedir } from "node:os";
9
14
  import { execSync, spawn } from "node:child_process";
10
- import { existsSync } from "node:fs";
11
15
  import { createServer } from "node:net";
12
16
  import chokidar from "chokidar";
13
17
  import { config } from "dotenv";
@@ -16,10 +20,15 @@ import { Cron } from "@geekmidas/constructs/crons";
16
20
  import { Function } from "@geekmidas/constructs/functions";
17
21
  import { Subscriber } from "@geekmidas/constructs/subscribers";
18
22
  import prompts from "prompts";
23
+ import { randomBytes } from "node:crypto";
19
24
 
25
+ //#region rolldown:runtime
26
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
27
+
28
+ //#endregion
20
29
  //#region package.json
21
30
  var name = "@geekmidas/cli";
22
- var version = "0.10.0";
31
+ var version = "0.13.0";
23
32
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
24
33
  var private$1 = false;
25
34
  var type = "module";
@@ -99,6 +108,227 @@ var package_default = {
99
108
  peerDependenciesMeta
100
109
  };
101
110
 
111
+ //#endregion
112
+ //#region src/auth/credentials.ts
113
+ /**
114
+ * Get the path to the credentials directory
115
+ */
116
+ function getCredentialsDir(options) {
117
+ const root = options?.root ?? homedir();
118
+ return join(root, ".gkm");
119
+ }
120
+ /**
121
+ * Get the path to the credentials file
122
+ */
123
+ function getCredentialsPath(options) {
124
+ return join(getCredentialsDir(options), "credentials.json");
125
+ }
126
+ /**
127
+ * Ensure the credentials directory exists
128
+ */
129
+ function ensureCredentialsDir(options) {
130
+ const dir = getCredentialsDir(options);
131
+ if (!existsSync(dir)) mkdirSync(dir, {
132
+ recursive: true,
133
+ mode: 448
134
+ });
135
+ }
136
+ /**
137
+ * Read stored credentials from disk
138
+ */
139
+ async function readCredentials(options) {
140
+ const path = getCredentialsPath(options);
141
+ if (!existsSync(path)) return {};
142
+ try {
143
+ const content = await readFile(path, "utf-8");
144
+ return JSON.parse(content);
145
+ } catch {
146
+ return {};
147
+ }
148
+ }
149
+ /**
150
+ * Write credentials to disk
151
+ */
152
+ async function writeCredentials(credentials, options) {
153
+ ensureCredentialsDir(options);
154
+ const path = getCredentialsPath(options);
155
+ await writeFile(path, JSON.stringify(credentials, null, 2), { mode: 384 });
156
+ }
157
+ /**
158
+ * Store Dokploy credentials
159
+ */
160
+ async function storeDokployCredentials(token, endpoint, options) {
161
+ const credentials = await readCredentials(options);
162
+ credentials.dokploy = {
163
+ token,
164
+ endpoint,
165
+ storedAt: (/* @__PURE__ */ new Date()).toISOString()
166
+ };
167
+ await writeCredentials(credentials, options);
168
+ }
169
+ /**
170
+ * Get stored Dokploy credentials
171
+ */
172
+ async function getDokployCredentials(options) {
173
+ const credentials = await readCredentials(options);
174
+ if (!credentials.dokploy) return null;
175
+ return {
176
+ token: credentials.dokploy.token,
177
+ endpoint: credentials.dokploy.endpoint
178
+ };
179
+ }
180
+ /**
181
+ * Remove Dokploy credentials
182
+ */
183
+ async function removeDokployCredentials(options) {
184
+ const credentials = await readCredentials(options);
185
+ if (!credentials.dokploy) return false;
186
+ delete credentials.dokploy;
187
+ await writeCredentials(credentials, options);
188
+ return true;
189
+ }
190
+ /**
191
+ * Get Dokploy API token, checking stored credentials first, then environment
192
+ */
193
+ async function getDokployToken(options) {
194
+ const envToken = process.env.DOKPLOY_API_TOKEN;
195
+ if (envToken) return envToken;
196
+ const stored = await getDokployCredentials(options);
197
+ if (stored) return stored.token;
198
+ return null;
199
+ }
200
+
201
+ //#endregion
202
+ //#region src/auth/index.ts
203
+ const logger$9 = console;
204
+ /**
205
+ * Validate Dokploy token by making a test API call
206
+ */
207
+ async function validateDokployToken(endpoint, token) {
208
+ try {
209
+ const response = await fetch(`${endpoint}/api/project.all`, {
210
+ method: "GET",
211
+ headers: {
212
+ "Content-Type": "application/json",
213
+ Authorization: `Bearer ${token}`
214
+ }
215
+ });
216
+ return response.ok;
217
+ } catch {
218
+ return false;
219
+ }
220
+ }
221
+ /**
222
+ * Prompt for input (handles both TTY and non-TTY)
223
+ */
224
+ async function prompt(message, hidden = false) {
225
+ if (!process.stdin.isTTY) throw new Error("Interactive input required. Please provide --token option.");
226
+ const rl = readline.createInterface({
227
+ input: stdin,
228
+ output: stdout
229
+ });
230
+ try {
231
+ if (hidden) {
232
+ process.stdout.write(message);
233
+ return new Promise((resolve$1) => {
234
+ let value = "";
235
+ const onData = (char) => {
236
+ const c = char.toString();
237
+ if (c === "\n" || c === "\r") {
238
+ process.stdin.removeListener("data", onData);
239
+ process.stdin.setRawMode(false);
240
+ process.stdout.write("\n");
241
+ resolve$1(value);
242
+ } else if (c === "") process.exit(1);
243
+ else if (c === "" || c === "\b") {
244
+ if (value.length > 0) value = value.slice(0, -1);
245
+ } else value += c;
246
+ };
247
+ process.stdin.setRawMode(true);
248
+ process.stdin.resume();
249
+ process.stdin.on("data", onData);
250
+ });
251
+ } else return await rl.question(message);
252
+ } finally {
253
+ rl.close();
254
+ }
255
+ }
256
+ /**
257
+ * Login to a service
258
+ */
259
+ async function loginCommand(options) {
260
+ const { service, token: providedToken, endpoint: providedEndpoint } = options;
261
+ if (service === "dokploy") {
262
+ logger$9.log("\n🔐 Logging in to Dokploy...\n");
263
+ let endpoint = providedEndpoint;
264
+ if (!endpoint) endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
265
+ endpoint = endpoint.replace(/\/$/, "");
266
+ try {
267
+ new URL(endpoint);
268
+ } catch {
269
+ logger$9.error("Invalid URL format");
270
+ process.exit(1);
271
+ }
272
+ let token = providedToken;
273
+ if (!token) {
274
+ logger$9.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
275
+ token = await prompt("API Token: ", true);
276
+ }
277
+ if (!token) {
278
+ logger$9.error("Token is required");
279
+ process.exit(1);
280
+ }
281
+ logger$9.log("\nValidating credentials...");
282
+ const isValid = await validateDokployToken(endpoint, token);
283
+ if (!isValid) {
284
+ logger$9.error("\n✗ Invalid credentials. Please check your token and try again.");
285
+ process.exit(1);
286
+ }
287
+ await storeDokployCredentials(token, endpoint);
288
+ logger$9.log("\n✓ Successfully logged in to Dokploy!");
289
+ logger$9.log(` Endpoint: ${endpoint}`);
290
+ logger$9.log(` Credentials stored in: ${getCredentialsPath()}`);
291
+ logger$9.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
292
+ }
293
+ }
294
+ /**
295
+ * Logout from a service
296
+ */
297
+ async function logoutCommand(options) {
298
+ const { service = "dokploy" } = options;
299
+ if (service === "all") {
300
+ const dokployRemoved = await removeDokployCredentials();
301
+ if (dokployRemoved) logger$9.log("\n✓ Logged out from all services");
302
+ else logger$9.log("\nNo stored credentials found");
303
+ return;
304
+ }
305
+ if (service === "dokploy") {
306
+ const removed = await removeDokployCredentials();
307
+ if (removed) logger$9.log("\n✓ Logged out from Dokploy");
308
+ else logger$9.log("\nNo Dokploy credentials found");
309
+ }
310
+ }
311
+ /**
312
+ * Show current login status
313
+ */
314
+ async function whoamiCommand() {
315
+ logger$9.log("\n📋 Current credentials:\n");
316
+ const dokploy = await getDokployCredentials();
317
+ if (dokploy) {
318
+ logger$9.log(" Dokploy:");
319
+ logger$9.log(` Endpoint: ${dokploy.endpoint}`);
320
+ logger$9.log(` Token: ${maskToken(dokploy.token)}`);
321
+ } else logger$9.log(" Dokploy: Not logged in");
322
+ logger$9.log(`\n Credentials file: ${getCredentialsPath()}`);
323
+ }
324
+ /**
325
+ * Mask a token for display
326
+ */
327
+ function maskToken(token) {
328
+ if (token.length <= 8) return "****";
329
+ return `${token.slice(0, 4)}...${token.slice(-4)}`;
330
+ }
331
+
102
332
  //#endregion
103
333
  //#region src/build/providerResolver.ts
104
334
  /**
@@ -175,22 +405,22 @@ function isEnabled(config$1) {
175
405
  var CronGenerator = class extends ConstructGenerator {
176
406
  async build(context, constructs, outputDir, options) {
177
407
  const provider = options?.provider || "aws-lambda";
178
- const logger$3 = console;
408
+ const logger$10 = console;
179
409
  const cronInfos = [];
180
410
  if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
181
- const cronsDir = join$1(outputDir, "crons");
411
+ const cronsDir = join(outputDir, "crons");
182
412
  await mkdir(cronsDir, { recursive: true });
183
413
  for (const { key, construct, path } of constructs) {
184
414
  const handlerFile = await this.generateCronHandler(cronsDir, path.relative, key, context);
185
415
  cronInfos.push({
186
416
  name: key,
187
- handler: relative$1(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
417
+ handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
188
418
  schedule: construct.schedule || "rate(1 hour)",
189
419
  timeout: construct.timeout,
190
420
  memorySize: construct.memorySize,
191
421
  environment: await construct.getEnvironment()
192
422
  });
193
- logger$3.log(`Generated cron handler: ${key}`);
423
+ logger$10.log(`Generated cron handler: ${key}`);
194
424
  }
195
425
  return cronInfos;
196
426
  }
@@ -199,11 +429,11 @@ var CronGenerator = class extends ConstructGenerator {
199
429
  }
200
430
  async generateCronHandler(outputDir, sourceFile, exportName, context) {
201
431
  const handlerFileName = `${exportName}.ts`;
202
- const handlerPath = join$1(outputDir, handlerFileName);
203
- const relativePath = relative$1(dirname(handlerPath), sourceFile);
432
+ const handlerPath = join(outputDir, handlerFileName);
433
+ const relativePath = relative(dirname(handlerPath), sourceFile);
204
434
  const importPath = relativePath.replace(/\.ts$/, ".js");
205
- const relativeEnvParserPath = relative$1(dirname(handlerPath), context.envParserPath);
206
- const relativeLoggerPath = relative$1(dirname(handlerPath), context.loggerPath);
435
+ const relativeEnvParserPath = relative(dirname(handlerPath), context.envParserPath);
436
+ const relativeLoggerPath = relative(dirname(handlerPath), context.loggerPath);
207
437
  const content = `import { AWSScheduledFunction } from '@geekmidas/constructs/crons';
208
438
  import { ${exportName} } from '${importPath}';
209
439
  import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
@@ -226,31 +456,31 @@ var FunctionGenerator = class extends ConstructGenerator {
226
456
  }
227
457
  async build(context, constructs, outputDir, options) {
228
458
  const provider = options?.provider || "aws-lambda";
229
- const logger$3 = console;
459
+ const logger$10 = console;
230
460
  const functionInfos = [];
231
461
  if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
232
- const functionsDir = join$1(outputDir, "functions");
462
+ const functionsDir = join(outputDir, "functions");
233
463
  await mkdir(functionsDir, { recursive: true });
234
464
  for (const { key, construct, path } of constructs) {
235
465
  const handlerFile = await this.generateFunctionHandler(functionsDir, path.relative, key, context);
236
466
  functionInfos.push({
237
467
  name: key,
238
- handler: relative$1(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
468
+ handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
239
469
  timeout: construct.timeout,
240
470
  memorySize: construct.memorySize,
241
471
  environment: await construct.getEnvironment()
242
472
  });
243
- logger$3.log(`Generated function handler: ${key}`);
473
+ logger$10.log(`Generated function handler: ${key}`);
244
474
  }
245
475
  return functionInfos;
246
476
  }
247
477
  async generateFunctionHandler(outputDir, sourceFile, exportName, context) {
248
478
  const handlerFileName = `${exportName}.ts`;
249
- const handlerPath = join$1(outputDir, handlerFileName);
250
- const relativePath = relative$1(dirname(handlerPath), sourceFile);
479
+ const handlerPath = join(outputDir, handlerFileName);
480
+ const relativePath = relative(dirname(handlerPath), sourceFile);
251
481
  const importPath = relativePath.replace(/\.ts$/, ".js");
252
- const relativeEnvParserPath = relative$1(dirname(handlerPath), context.envParserPath);
253
- const relativeLoggerPath = relative$1(dirname(handlerPath), context.loggerPath);
482
+ const relativeEnvParserPath = relative(dirname(handlerPath), context.envParserPath);
483
+ const relativeLoggerPath = relative(dirname(handlerPath), context.loggerPath);
254
484
  const content = `import { AWSLambdaFunction } from '@geekmidas/constructs/functions';
255
485
  import { ${exportName} } from '${importPath}';
256
486
  import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
@@ -273,37 +503,37 @@ var SubscriberGenerator = class extends ConstructGenerator {
273
503
  }
274
504
  async build(context, constructs, outputDir, options) {
275
505
  const provider = options?.provider || "aws-lambda";
276
- const logger$3 = console;
506
+ const logger$10 = console;
277
507
  const subscriberInfos = [];
278
508
  if (provider === "server") {
279
509
  await this.generateServerSubscribersFile(outputDir, constructs);
280
- logger$3.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
510
+ logger$10.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
281
511
  return subscriberInfos;
282
512
  }
283
513
  if (constructs.length === 0) return subscriberInfos;
284
514
  if (provider !== "aws-lambda") return subscriberInfos;
285
- const subscribersDir = join$1(outputDir, "subscribers");
515
+ const subscribersDir = join(outputDir, "subscribers");
286
516
  await mkdir(subscribersDir, { recursive: true });
287
517
  for (const { key, construct, path } of constructs) {
288
518
  const handlerFile = await this.generateSubscriberHandler(subscribersDir, path.relative, key, construct, context);
289
519
  subscriberInfos.push({
290
520
  name: key,
291
- handler: relative$1(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
521
+ handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
292
522
  subscribedEvents: construct.subscribedEvents || [],
293
523
  timeout: construct.timeout,
294
524
  memorySize: construct.memorySize,
295
525
  environment: await construct.getEnvironment()
296
526
  });
297
- logger$3.log(`Generated subscriber handler: ${key}`);
527
+ logger$10.log(`Generated subscriber handler: ${key}`);
298
528
  }
299
529
  return subscriberInfos;
300
530
  }
301
531
  async generateSubscriberHandler(outputDir, sourceFile, exportName, _subscriber, context) {
302
532
  const handlerFileName = `${exportName}.ts`;
303
- const handlerPath = join$1(outputDir, handlerFileName);
304
- const relativePath = relative$1(dirname(handlerPath), sourceFile);
533
+ const handlerPath = join(outputDir, handlerFileName);
534
+ const relativePath = relative(dirname(handlerPath), sourceFile);
305
535
  const importPath = relativePath.replace(/\.ts$/, ".js");
306
- const relativeEnvParserPath = relative$1(dirname(handlerPath), context.envParserPath);
536
+ const relativeEnvParserPath = relative(dirname(handlerPath), context.envParserPath);
307
537
  const content = `import { AWSLambdaSubscriber } from '@geekmidas/constructs/aws';
308
538
  import { ${exportName} } from '${importPath}';
309
539
  import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
@@ -318,13 +548,13 @@ export const handler = adapter.handler;
318
548
  async generateServerSubscribersFile(outputDir, subscribers) {
319
549
  await mkdir(outputDir, { recursive: true });
320
550
  const subscribersFileName = "subscribers.ts";
321
- const subscribersPath = join$1(outputDir, subscribersFileName);
551
+ const subscribersPath = join(outputDir, subscribersFileName);
322
552
  const importsByFile = /* @__PURE__ */ new Map();
323
553
  for (const { path, key } of subscribers) {
324
- const relativePath = relative$1(dirname(subscribersPath), path.relative);
554
+ const relativePath = relative(dirname(subscribersPath), path.relative);
325
555
  const importPath = relativePath.replace(/\.ts$/, ".js");
326
556
  if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
327
- importsByFile.get(importPath).push(key);
557
+ importsByFile.get(importPath)?.push(key);
328
558
  }
329
559
  const imports = Array.from(importsByFile.entries()).map(([importPath, exports$1]) => `import { ${exports$1.join(", ")} } from '${importPath}';`).join("\n");
330
560
  const allExportNames = subscribers.map(({ key }) => key);
@@ -379,7 +609,7 @@ export async function setupSubscribers(
379
609
  return;
380
610
  }
381
611
 
382
- const serviceDiscovery = ServiceDiscovery.getInstance(logger, envParser);
612
+ const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
383
613
 
384
614
  // Create connection once, outside the loop (more efficient)
385
615
  // EventConnectionFactory automatically determines the right connection type
@@ -460,7 +690,7 @@ export async function setupSubscribers(
460
690
 
461
691
  //#endregion
462
692
  //#region src/dev/index.ts
463
- const logger$2 = console;
693
+ const logger$8 = console;
464
694
  /**
465
695
  * Load environment files
466
696
  * @internal Exported for testing
@@ -511,7 +741,7 @@ async function findAvailablePort(preferredPort, maxAttempts = 10) {
511
741
  for (let i = 0; i < maxAttempts; i++) {
512
742
  const port = preferredPort + i;
513
743
  if (await isPortAvailable(port)) return port;
514
- logger$2.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
744
+ logger$8.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
515
745
  }
516
746
  throw new Error(`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`);
517
747
  }
@@ -581,33 +811,61 @@ function normalizeHooksConfig(config$1) {
581
811
  const resolvedPath = resolve(process.cwd(), serverPath);
582
812
  return { serverHooksPath: resolvedPath };
583
813
  }
814
+ /**
815
+ * Normalize production configuration
816
+ * @internal Exported for testing
817
+ */
818
+ function normalizeProductionConfig(cliProduction, configProduction) {
819
+ if (!cliProduction) return void 0;
820
+ const config$1 = configProduction ?? {};
821
+ return {
822
+ enabled: true,
823
+ bundle: config$1.bundle ?? true,
824
+ minify: config$1.minify ?? true,
825
+ healthCheck: config$1.healthCheck ?? "/health",
826
+ gracefulShutdown: config$1.gracefulShutdown ?? true,
827
+ external: config$1.external ?? [],
828
+ subscribers: config$1.subscribers ?? "exclude",
829
+ openapi: config$1.openapi ?? false,
830
+ optimizedHandlers: config$1.optimizedHandlers ?? true
831
+ };
832
+ }
833
+ /**
834
+ * Get production config from GkmConfig
835
+ * @internal
836
+ */
837
+ function getProductionConfigFromGkm(config$1) {
838
+ const serverConfig = config$1.providers?.server;
839
+ if (typeof serverConfig === "object") return serverConfig.production;
840
+ return void 0;
841
+ }
584
842
  async function devCommand(options) {
585
843
  const defaultEnv = loadEnvFiles(".env");
586
- if (defaultEnv.loaded.length > 0) logger$2.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
844
+ if (defaultEnv.loaded.length > 0) logger$8.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
587
845
  const config$1 = await loadConfig();
588
846
  if (config$1.env) {
589
847
  const { loaded, missing } = loadEnvFiles(config$1.env);
590
- if (loaded.length > 0) logger$2.log(`📦 Loaded env: ${loaded.join(", ")}`);
591
- if (missing.length > 0) logger$2.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
848
+ if (loaded.length > 0) logger$8.log(`📦 Loaded env: ${loaded.join(", ")}`);
849
+ if (missing.length > 0) logger$8.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
592
850
  }
593
851
  const resolved = resolveProviders(config$1, { provider: "server" });
594
- logger$2.log("🚀 Starting development server...");
595
- logger$2.log(`Loading routes from: ${config$1.routes}`);
596
- if (config$1.functions) logger$2.log(`Loading functions from: ${config$1.functions}`);
597
- if (config$1.crons) logger$2.log(`Loading crons from: ${config$1.crons}`);
598
- if (config$1.subscribers) logger$2.log(`Loading subscribers from: ${config$1.subscribers}`);
599
- logger$2.log(`Using envParser: ${config$1.envParser}`);
852
+ logger$8.log("🚀 Starting development server...");
853
+ logger$8.log(`Loading routes from: ${config$1.routes}`);
854
+ if (config$1.functions) logger$8.log(`Loading functions from: ${config$1.functions}`);
855
+ if (config$1.crons) logger$8.log(`Loading crons from: ${config$1.crons}`);
856
+ if (config$1.subscribers) logger$8.log(`Loading subscribers from: ${config$1.subscribers}`);
857
+ logger$8.log(`Using envParser: ${config$1.envParser}`);
600
858
  const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config$1.envParser, "envParser");
601
859
  const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config$1.logger, "logger");
602
860
  const telescope = normalizeTelescopeConfig(config$1.telescope);
603
- if (telescope) logger$2.log(`🔭 Telescope enabled at ${telescope.path}`);
861
+ if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
604
862
  const studio = normalizeStudioConfig(config$1.studio);
605
- if (studio) logger$2.log(`🗄️ Studio enabled at ${studio.path}`);
863
+ if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
606
864
  const hooks = normalizeHooksConfig(config$1.hooks);
607
- if (hooks) logger$2.log(`🪝 Server hooks enabled from ${config$1.hooks?.server}`);
865
+ if (hooks) logger$8.log(`🪝 Server hooks enabled from ${config$1.hooks?.server}`);
608
866
  const openApiConfig = resolveOpenApiConfig(config$1);
609
867
  const enableOpenApi = openApiConfig.enabled || resolved.enableOpenApi;
610
- if (enableOpenApi) logger$2.log(`📄 OpenAPI output: ${OPENAPI_OUTPUT_PATH}`);
868
+ if (enableOpenApi) logger$8.log(`📄 OpenAPI output: ${OPENAPI_OUTPUT_PATH}`);
611
869
  const buildContext = {
612
870
  envParserPath,
613
871
  envParserImportPattern,
@@ -636,7 +894,7 @@ async function devCommand(options) {
636
894
  ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []
637
895
  ].flat().filter((p) => typeof p === "string");
638
896
  const normalizedPatterns = watchPatterns.map((p) => p.startsWith("./") ? p.slice(2) : p);
639
- logger$2.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
897
+ logger$8.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
640
898
  const resolvedFiles = await fg(normalizedPatterns, {
641
899
  cwd: process.cwd(),
642
900
  absolute: false,
@@ -646,32 +904,32 @@ async function devCommand(options) {
646
904
  const parts = f.split("/");
647
905
  return parts.slice(0, -1).join("/");
648
906
  }))];
649
- logger$2.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
907
+ logger$8.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
650
908
  const watcher = chokidar.watch([...resolvedFiles, ...dirsToWatch], {
651
- ignored: /(^|[\/\\])\../,
909
+ ignored: /(^|[/\\])\../,
652
910
  persistent: true,
653
911
  ignoreInitial: true,
654
912
  cwd: process.cwd()
655
913
  });
656
914
  watcher.on("ready", () => {
657
- logger$2.log("🔍 File watcher ready");
915
+ logger$8.log("🔍 File watcher ready");
658
916
  });
659
917
  watcher.on("error", (error) => {
660
- logger$2.error("❌ Watcher error:", error);
918
+ logger$8.error("❌ Watcher error:", error);
661
919
  });
662
920
  let rebuildTimeout = null;
663
921
  watcher.on("change", async (path) => {
664
- logger$2.log(`📝 File changed: ${path}`);
922
+ logger$8.log(`📝 File changed: ${path}`);
665
923
  if (rebuildTimeout) clearTimeout(rebuildTimeout);
666
924
  rebuildTimeout = setTimeout(async () => {
667
925
  try {
668
- logger$2.log("🔄 Rebuilding...");
926
+ logger$8.log("🔄 Rebuilding...");
669
927
  await buildServer(config$1, buildContext, resolved.providers[0], enableOpenApi);
670
928
  if (enableOpenApi) await generateOpenApi(config$1, { silent: true });
671
- logger$2.log("✅ Rebuild complete, restarting server...");
929
+ logger$8.log("✅ Rebuild complete, restarting server...");
672
930
  await devServer.restart();
673
931
  } catch (error) {
674
- logger$2.error("❌ Rebuild failed:", error.message);
932
+ logger$8.error("❌ Rebuild failed:", error.message);
675
933
  }
676
934
  }, 300);
677
935
  });
@@ -679,9 +937,9 @@ async function devCommand(options) {
679
937
  const shutdown = () => {
680
938
  if (isShuttingDown) return;
681
939
  isShuttingDown = true;
682
- logger$2.log("\n🛑 Shutting down...");
940
+ logger$8.log("\n🛑 Shutting down...");
683
941
  Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
684
- logger$2.error("Error during shutdown:", err);
942
+ logger$8.error("Error during shutdown:", err);
685
943
  }).finally(() => {
686
944
  process.exit(0);
687
945
  });
@@ -700,7 +958,7 @@ async function buildServer(config$1, context, provider, enableOpenApi) {
700
958
  config$1.crons ? cronGenerator.load(config$1.crons) : [],
701
959
  config$1.subscribers ? subscriberGenerator.load(config$1.subscribers) : []
702
960
  ]);
703
- const outputDir = join$1(process.cwd(), ".gkm", provider);
961
+ const outputDir = join(process.cwd(), ".gkm", provider);
704
962
  await mkdir(outputDir, { recursive: true });
705
963
  await Promise.all([
706
964
  endpointGenerator.build(context, allEndpoints, outputDir, {
@@ -734,11 +992,11 @@ var DevServer = class {
734
992
  this.actualPort = this.requestedPort;
735
993
  } else {
736
994
  this.actualPort = await findAvailablePort(this.requestedPort);
737
- if (this.actualPort !== this.requestedPort) logger$2.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
995
+ if (this.actualPort !== this.requestedPort) logger$8.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
738
996
  }
739
- const serverEntryPath = join$1(process.cwd(), ".gkm", this.provider, "server.ts");
997
+ const serverEntryPath = join(process.cwd(), ".gkm", this.provider, "server.ts");
740
998
  await this.createServerEntry();
741
- logger$2.log(`\n✨ Starting server on port ${this.actualPort}...`);
999
+ logger$8.log(`\n✨ Starting server on port ${this.actualPort}...`);
742
1000
  this.serverProcess = spawn("npx", [
743
1001
  "tsx",
744
1002
  serverEntryPath,
@@ -754,18 +1012,18 @@ var DevServer = class {
754
1012
  });
755
1013
  this.isRunning = true;
756
1014
  this.serverProcess.on("error", (error) => {
757
- logger$2.error("❌ Server error:", error);
1015
+ logger$8.error("❌ Server error:", error);
758
1016
  });
759
1017
  this.serverProcess.on("exit", (code, signal) => {
760
- if (code !== null && code !== 0 && signal !== "SIGTERM") logger$2.error(`❌ Server exited with code ${code}`);
1018
+ if (code !== null && code !== 0 && signal !== "SIGTERM") logger$8.error(`❌ Server exited with code ${code}`);
761
1019
  this.isRunning = false;
762
1020
  });
763
1021
  await new Promise((resolve$1) => setTimeout(resolve$1, 1e3));
764
1022
  if (this.isRunning) {
765
- logger$2.log(`\n🎉 Server running at http://localhost:${this.actualPort}`);
766
- if (this.enableOpenApi) logger$2.log(`📚 API Docs available at http://localhost:${this.actualPort}/__docs`);
767
- if (this.telescope) logger$2.log(`🔭 Telescope available at http://localhost:${this.actualPort}${this.telescope.path}`);
768
- if (this.studio) logger$2.log(`🗄️ Studio available at http://localhost:${this.actualPort}${this.studio.path}`);
1023
+ logger$8.log(`\n🎉 Server running at http://localhost:${this.actualPort}`);
1024
+ if (this.enableOpenApi) logger$8.log(`📚 API Docs available at http://localhost:${this.actualPort}/__docs`);
1025
+ if (this.telescope) logger$8.log(`🔭 Telescope available at http://localhost:${this.actualPort}${this.telescope.path}`);
1026
+ if (this.studio) logger$8.log(`🗄️ Studio available at http://localhost:${this.actualPort}${this.studio.path}`);
769
1027
  }
770
1028
  }
771
1029
  async stop() {
@@ -803,9 +1061,9 @@ var DevServer = class {
803
1061
  }
804
1062
  async createServerEntry() {
805
1063
  const { writeFile: writeFile$1 } = await import("node:fs/promises");
806
- const { relative: relative$2, dirname: dirname$1 } = await import("node:path");
807
- const serverPath = join$1(process.cwd(), ".gkm", this.provider, "server.ts");
808
- const relativeAppPath = relative$2(dirname$1(serverPath), join$1(dirname$1(serverPath), "app.js"));
1064
+ const { relative: relative$1, dirname: dirname$1 } = await import("node:path");
1065
+ const serverPath = join(process.cwd(), ".gkm", this.provider, "server.ts");
1066
+ const relativeAppPath = relative$1(dirname$1(serverPath), join(dirname$1(serverPath), "app.js"));
809
1067
  const serveCode = this.runtime === "bun" ? `Bun.serve({
810
1068
  port,
811
1069
  fetch: app.fetch,
@@ -825,7 +1083,7 @@ var DevServer = class {
825
1083
  * Development server entry point
826
1084
  * This file is auto-generated by 'gkm dev'
827
1085
  */
828
- import { createApp } from './${relativeAppPath.startsWith(".") ? relativeAppPath : "./" + relativeAppPath}';
1086
+ import { createApp } from './${relativeAppPath.startsWith(".") ? relativeAppPath : `./${relativeAppPath}`}';
829
1087
 
830
1088
  const port = process.argv.includes('--port')
831
1089
  ? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
@@ -851,7 +1109,7 @@ start({
851
1109
 
852
1110
  //#endregion
853
1111
  //#region src/build/manifests.ts
854
- const logger$1 = console;
1112
+ const logger$7 = console;
855
1113
  async function generateAwsManifest(outputDir, routes, functions, crons, subscribers) {
856
1114
  const manifestDir = join(outputDir, "manifest");
857
1115
  await mkdir(manifestDir, { recursive: true });
@@ -876,8 +1134,8 @@ export type RoutePath = Route['path'];
876
1134
  `;
877
1135
  const manifestPath = join(manifestDir, "aws.ts");
878
1136
  await writeFile(manifestPath, content);
879
- logger$1.log(`Generated AWS manifest with ${awsRoutes.length} routes, ${functions.length} functions, ${crons.length} crons, ${subscribers.length} subscribers`);
880
- logger$1.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
1137
+ logger$7.log(`Generated AWS manifest with ${awsRoutes.length} routes, ${functions.length} functions, ${crons.length} crons, ${subscribers.length} subscribers`);
1138
+ logger$7.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
881
1139
  }
882
1140
  async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
883
1141
  const manifestDir = join(outputDir, "manifest");
@@ -908,35 +1166,42 @@ export type RoutePath = Route['path'];
908
1166
  `;
909
1167
  const manifestPath = join(manifestDir, "server.ts");
910
1168
  await writeFile(manifestPath, content);
911
- logger$1.log(`Generated server manifest with ${serverRoutes.length} routes, ${serverSubscribers.length} subscribers`);
912
- logger$1.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
1169
+ logger$7.log(`Generated server manifest with ${serverRoutes.length} routes, ${serverSubscribers.length} subscribers`);
1170
+ logger$7.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
913
1171
  }
914
1172
 
915
1173
  //#endregion
916
1174
  //#region src/build/index.ts
917
- const logger = console;
1175
+ const logger$6 = console;
918
1176
  async function buildCommand(options) {
919
1177
  const config$1 = await loadConfig();
920
1178
  const resolved = resolveProviders(config$1, options);
921
- logger.log(`Building with providers: ${resolved.providers.join(", ")}`);
922
- logger.log(`Loading routes from: ${config$1.routes}`);
923
- if (config$1.functions) logger.log(`Loading functions from: ${config$1.functions}`);
924
- if (config$1.crons) logger.log(`Loading crons from: ${config$1.crons}`);
925
- if (config$1.subscribers) logger.log(`Loading subscribers from: ${config$1.subscribers}`);
926
- logger.log(`Using envParser: ${config$1.envParser}`);
1179
+ const productionConfigFromGkm = getProductionConfigFromGkm(config$1);
1180
+ const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
1181
+ if (production) logger$6.log(`🏭 Building for PRODUCTION`);
1182
+ logger$6.log(`Building with providers: ${resolved.providers.join(", ")}`);
1183
+ logger$6.log(`Loading routes from: ${config$1.routes}`);
1184
+ if (config$1.functions) logger$6.log(`Loading functions from: ${config$1.functions}`);
1185
+ if (config$1.crons) logger$6.log(`Loading crons from: ${config$1.crons}`);
1186
+ if (config$1.subscribers) logger$6.log(`Loading subscribers from: ${config$1.subscribers}`);
1187
+ logger$6.log(`Using envParser: ${config$1.envParser}`);
927
1188
  const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config$1.envParser, "envParser");
928
1189
  const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config$1.logger, "logger");
929
- const telescope = normalizeTelescopeConfig(config$1.telescope);
930
- if (telescope) logger.log(`🔭 Telescope enabled at ${telescope.path}`);
1190
+ const telescope = production ? void 0 : normalizeTelescopeConfig(config$1.telescope);
1191
+ if (telescope) logger$6.log(`🔭 Telescope enabled at ${telescope.path}`);
1192
+ const studio = production ? void 0 : normalizeStudioConfig(config$1.studio);
1193
+ if (studio) logger$6.log(`🗄️ Studio enabled at ${studio.path}`);
931
1194
  const hooks = normalizeHooksConfig(config$1.hooks);
932
- if (hooks) logger.log(`🪝 Server hooks enabled`);
1195
+ if (hooks) logger$6.log(`🪝 Server hooks enabled`);
933
1196
  const buildContext = {
934
1197
  envParserPath,
935
1198
  envParserImportPattern,
936
1199
  loggerPath,
937
1200
  loggerImportPattern,
938
1201
  telescope,
939
- hooks
1202
+ studio,
1203
+ hooks,
1204
+ production
940
1205
  };
941
1206
  const endpointGenerator = new EndpointGenerator();
942
1207
  const functionGenerator = new FunctionGenerator();
@@ -948,45 +1213,1250 @@ async function buildCommand(options) {
948
1213
  config$1.crons ? cronGenerator.load(config$1.crons) : [],
949
1214
  config$1.subscribers ? subscriberGenerator.load(config$1.subscribers) : []
950
1215
  ]);
951
- logger.log(`Found ${allEndpoints.length} endpoints`);
952
- logger.log(`Found ${allFunctions.length} functions`);
953
- logger.log(`Found ${allCrons.length} crons`);
954
- logger.log(`Found ${allSubscribers.length} subscribers`);
1216
+ logger$6.log(`Found ${allEndpoints.length} endpoints`);
1217
+ logger$6.log(`Found ${allFunctions.length} functions`);
1218
+ logger$6.log(`Found ${allCrons.length} crons`);
1219
+ logger$6.log(`Found ${allSubscribers.length} subscribers`);
955
1220
  if (allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0) {
956
- logger.log("No endpoints, functions, crons, or subscribers found to process");
1221
+ logger$6.log("No endpoints, functions, crons, or subscribers found to process");
1222
+ return {};
1223
+ }
1224
+ const rootOutputDir = join(process.cwd(), ".gkm");
1225
+ await mkdir(rootOutputDir, { recursive: true });
1226
+ let result = {};
1227
+ for (const provider of resolved.providers) {
1228
+ const providerResult = await buildForProvider(provider, buildContext, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, resolved.enableOpenApi, options.skipBundle ?? false, options.stage);
1229
+ if (providerResult.masterKey) result = providerResult;
1230
+ }
1231
+ return result;
1232
+ }
1233
+ async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, endpoints, functions, crons, subscribers, enableOpenApi, skipBundle, stage) {
1234
+ const outputDir = join(process.cwd(), ".gkm", provider);
1235
+ await mkdir(outputDir, { recursive: true });
1236
+ logger$6.log(`\nGenerating handlers for provider: ${provider}`);
1237
+ const [routes, functionInfos, cronInfos, subscriberInfos] = await Promise.all([
1238
+ endpointGenerator.build(context, endpoints, outputDir, {
1239
+ provider,
1240
+ enableOpenApi
1241
+ }),
1242
+ functionGenerator.build(context, functions, outputDir, { provider }),
1243
+ cronGenerator.build(context, crons, outputDir, { provider }),
1244
+ subscriberGenerator.build(context, subscribers, outputDir, { provider })
1245
+ ]);
1246
+ logger$6.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
1247
+ if (provider === "server") {
1248
+ const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
1249
+ path: construct._path,
1250
+ method: construct.method,
1251
+ handler: "",
1252
+ authorizer: construct.authorizer?.name ?? "none"
1253
+ })));
1254
+ const appInfo = {
1255
+ handler: relative(process.cwd(), join(outputDir, "app.ts")),
1256
+ endpoints: relative(process.cwd(), join(outputDir, "endpoints.ts"))
1257
+ };
1258
+ await generateServerManifest(rootOutputDir, appInfo, routeMetadata, subscriberInfos);
1259
+ let masterKey;
1260
+ if (context.production?.bundle && !skipBundle) {
1261
+ logger$6.log(`\n📦 Bundling production server...`);
1262
+ const { bundleServer } = await import("./bundler-DskIqW2t.mjs");
1263
+ const allConstructs = [
1264
+ ...endpoints.map((e) => e.construct),
1265
+ ...functions.map((f) => f.construct),
1266
+ ...crons.map((c) => c.construct),
1267
+ ...subscribers.map((s) => s.construct)
1268
+ ];
1269
+ const bundleResult = await bundleServer({
1270
+ entryPoint: join(outputDir, "server.ts"),
1271
+ outputDir: join(outputDir, "dist"),
1272
+ minify: context.production.minify,
1273
+ sourcemap: false,
1274
+ external: context.production.external,
1275
+ stage,
1276
+ constructs: allConstructs
1277
+ });
1278
+ masterKey = bundleResult.masterKey;
1279
+ logger$6.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
1280
+ if (masterKey) {
1281
+ logger$6.log(`\n🔐 Secrets encrypted for deployment`);
1282
+ logger$6.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
1283
+ }
1284
+ }
1285
+ return { masterKey };
1286
+ } else await generateAwsManifest(rootOutputDir, routes, functionInfos, cronInfos, subscriberInfos);
1287
+ return {};
1288
+ }
1289
+
1290
+ //#endregion
1291
+ //#region src/deploy/docker.ts
1292
+ const logger$5 = console;
1293
+ /**
1294
+ * Get the full image reference
1295
+ */
1296
+ function getImageRef(registry, imageName, tag) {
1297
+ if (registry) return `${registry}/${imageName}:${tag}`;
1298
+ return `${imageName}:${tag}`;
1299
+ }
1300
+ /**
1301
+ * Build Docker image
1302
+ */
1303
+ async function buildImage(imageRef) {
1304
+ logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
1305
+ try {
1306
+ execSync(`DOCKER_BUILDKIT=1 docker build -f .gkm/docker/Dockerfile -t ${imageRef} .`, {
1307
+ cwd: process.cwd(),
1308
+ stdio: "inherit",
1309
+ env: {
1310
+ ...process.env,
1311
+ DOCKER_BUILDKIT: "1"
1312
+ }
1313
+ });
1314
+ logger$5.log(`✅ Image built: ${imageRef}`);
1315
+ } catch (error) {
1316
+ throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
1317
+ }
1318
+ }
1319
+ /**
1320
+ * Push Docker image to registry
1321
+ */
1322
+ async function pushImage(imageRef) {
1323
+ logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
1324
+ try {
1325
+ execSync(`docker push ${imageRef}`, {
1326
+ cwd: process.cwd(),
1327
+ stdio: "inherit"
1328
+ });
1329
+ logger$5.log(`✅ Image pushed: ${imageRef}`);
1330
+ } catch (error) {
1331
+ throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
1332
+ }
1333
+ }
1334
+ /**
1335
+ * Deploy using Docker (build and optionally push image)
1336
+ */
1337
+ async function deployDocker(options) {
1338
+ const { stage, tag, skipPush, masterKey, config: config$1 } = options;
1339
+ const imageName = config$1.imageName ?? "app";
1340
+ const imageRef = getImageRef(config$1.registry, imageName, tag);
1341
+ await buildImage(imageRef);
1342
+ if (!skipPush) if (!config$1.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
1343
+ else await pushImage(imageRef);
1344
+ logger$5.log("\n✅ Docker deployment ready!");
1345
+ logger$5.log(`\n📋 Deployment details:`);
1346
+ logger$5.log(` Image: ${imageRef}`);
1347
+ logger$5.log(` Stage: ${stage}`);
1348
+ if (masterKey) {
1349
+ logger$5.log(`\n🔐 Deploy with this environment variable:`);
1350
+ logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
1351
+ logger$5.log("\n Example docker run:");
1352
+ logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
1353
+ }
1354
+ return {
1355
+ imageRef,
1356
+ masterKey
1357
+ };
1358
+ }
1359
+ /**
1360
+ * Resolve Docker deploy config from gkm config
1361
+ */
1362
+ function resolveDockerConfig$1(config$1) {
1363
+ return {
1364
+ registry: config$1.docker?.registry,
1365
+ imageName: config$1.docker?.imageName
1366
+ };
1367
+ }
1368
+
1369
+ //#endregion
1370
+ //#region src/deploy/dokploy.ts
1371
+ const logger$4 = console;
1372
+ /**
1373
+ * Get the Dokploy API token from stored credentials or environment
1374
+ */
1375
+ async function getApiToken$1() {
1376
+ const token = await getDokployToken();
1377
+ if (!token) throw new Error("Dokploy credentials not found.\nRun \"gkm login --service dokploy\" to authenticate, or set DOKPLOY_API_TOKEN.");
1378
+ return token;
1379
+ }
1380
+ /**
1381
+ * Make a request to the Dokploy API
1382
+ */
1383
+ async function dokployRequest$1(endpoint, baseUrl, token, body) {
1384
+ const url = `${baseUrl}/api/${endpoint}`;
1385
+ const response = await fetch(url, {
1386
+ method: "POST",
1387
+ headers: {
1388
+ "Content-Type": "application/json",
1389
+ Authorization: `Bearer ${token}`
1390
+ },
1391
+ body: JSON.stringify(body)
1392
+ });
1393
+ if (!response.ok) {
1394
+ let errorMessage = `Dokploy API error: ${response.status} ${response.statusText}`;
1395
+ try {
1396
+ const errorBody = await response.json();
1397
+ if (errorBody.message) errorMessage = `Dokploy API error: ${errorBody.message}`;
1398
+ if (errorBody.issues?.length) errorMessage += `\n Issues: ${errorBody.issues.map((i) => i.message).join(", ")}`;
1399
+ } catch {}
1400
+ throw new Error(errorMessage);
1401
+ }
1402
+ return response.json();
1403
+ }
1404
+ /**
1405
+ * Update application environment variables
1406
+ */
1407
+ async function updateEnvironment(baseUrl, token, applicationId, envVars) {
1408
+ logger$4.log(" Updating environment variables...");
1409
+ const envString = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
1410
+ await dokployRequest$1("application.update", baseUrl, token, {
1411
+ applicationId,
1412
+ env: envString
1413
+ });
1414
+ logger$4.log(" ✓ Environment variables updated");
1415
+ }
1416
+ /**
1417
+ * Trigger application deployment
1418
+ */
1419
+ async function triggerDeploy(baseUrl, token, applicationId) {
1420
+ logger$4.log(" Triggering deployment...");
1421
+ await dokployRequest$1("application.deploy", baseUrl, token, { applicationId });
1422
+ logger$4.log(" ✓ Deployment triggered");
1423
+ }
1424
+ /**
1425
+ * Deploy to Dokploy
1426
+ */
1427
+ async function deployDokploy(options) {
1428
+ const { stage, imageRef, masterKey, config: config$1 } = options;
1429
+ logger$4.log(`\n🎯 Deploying to Dokploy...`);
1430
+ logger$4.log(` Endpoint: ${config$1.endpoint}`);
1431
+ logger$4.log(` Application: ${config$1.applicationId}`);
1432
+ const token = await getApiToken$1();
1433
+ const envVars = {};
1434
+ if (masterKey) envVars.GKM_MASTER_KEY = masterKey;
1435
+ if (Object.keys(envVars).length > 0) await updateEnvironment(config$1.endpoint, token, config$1.applicationId, envVars);
1436
+ await triggerDeploy(config$1.endpoint, token, config$1.applicationId);
1437
+ logger$4.log("\n✅ Dokploy deployment initiated!");
1438
+ logger$4.log(`\n📋 Deployment details:`);
1439
+ logger$4.log(` Image: ${imageRef}`);
1440
+ logger$4.log(` Stage: ${stage}`);
1441
+ logger$4.log(` Application ID: ${config$1.applicationId}`);
1442
+ if (masterKey) logger$4.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
1443
+ const deploymentUrl = `${config$1.endpoint}/project/${config$1.projectId}`;
1444
+ logger$4.log(`\n🔗 View deployment: ${deploymentUrl}`);
1445
+ return {
1446
+ imageRef,
1447
+ masterKey,
1448
+ url: deploymentUrl
1449
+ };
1450
+ }
1451
+ /**
1452
+ * Validate Dokploy configuration
1453
+ */
1454
+ function validateDokployConfig(config$1) {
1455
+ if (!config$1) return false;
1456
+ const required = [
1457
+ "endpoint",
1458
+ "projectId",
1459
+ "applicationId"
1460
+ ];
1461
+ const missing = required.filter((key) => !config$1[key]);
1462
+ if (missing.length > 0) throw new Error(`Missing Dokploy configuration: ${missing.join(", ")}\nConfigure in gkm.config.ts:
1463
+ providers: {
1464
+ dokploy: {
1465
+ endpoint: 'https://dokploy.example.com',
1466
+ projectId: 'proj_xxx',
1467
+ applicationId: 'app_xxx',
1468
+ },
1469
+ }`);
1470
+ return true;
1471
+ }
1472
+
1473
+ //#endregion
1474
+ //#region src/deploy/index.ts
1475
+ const logger$3 = console;
1476
+ /**
1477
+ * Generate image tag from stage and timestamp
1478
+ */
1479
+ function generateTag(stage) {
1480
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
1481
+ return `${stage}-${timestamp}`;
1482
+ }
1483
+ /**
1484
+ * Main deploy command
1485
+ */
1486
+ async function deployCommand(options) {
1487
+ const { provider, stage, tag, skipPush, skipBuild } = options;
1488
+ logger$3.log(`\n🚀 Deploying to ${provider}...`);
1489
+ logger$3.log(` Stage: ${stage}`);
1490
+ const config$1 = await loadConfig();
1491
+ const imageTag = tag ?? generateTag(stage);
1492
+ logger$3.log(` Tag: ${imageTag}`);
1493
+ let masterKey;
1494
+ if (!skipBuild) {
1495
+ logger$3.log(`\n📦 Building for production...`);
1496
+ const buildResult = await buildCommand({
1497
+ provider: "server",
1498
+ production: true,
1499
+ stage
1500
+ });
1501
+ masterKey = buildResult.masterKey;
1502
+ } else logger$3.log(`\n⏭️ Skipping build (--skip-build)`);
1503
+ const dockerConfig = resolveDockerConfig$1(config$1);
1504
+ const imageName = dockerConfig.imageName ?? "app";
1505
+ const registry = dockerConfig.registry;
1506
+ const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
1507
+ let result;
1508
+ switch (provider) {
1509
+ case "docker": {
1510
+ result = await deployDocker({
1511
+ stage,
1512
+ tag: imageTag,
1513
+ skipPush,
1514
+ masterKey,
1515
+ config: dockerConfig
1516
+ });
1517
+ break;
1518
+ }
1519
+ case "dokploy": {
1520
+ const dokployConfigRaw = config$1.providers?.dokploy;
1521
+ if (typeof dokployConfigRaw === "boolean" || !dokployConfigRaw) throw new Error("Dokploy provider requires configuration.\nConfigure in gkm.config.ts:\n providers: {\n dokploy: {\n endpoint: 'https://dokploy.example.com',\n projectId: 'proj_xxx',\n applicationId: 'app_xxx',\n },\n }");
1522
+ validateDokployConfig(dokployConfigRaw);
1523
+ const dokployConfig = dokployConfigRaw;
1524
+ await deployDocker({
1525
+ stage,
1526
+ tag: imageTag,
1527
+ skipPush: false,
1528
+ masterKey,
1529
+ config: {
1530
+ registry: dokployConfig.registry ?? dockerConfig.registry,
1531
+ imageName: dockerConfig.imageName
1532
+ }
1533
+ });
1534
+ result = await deployDokploy({
1535
+ stage,
1536
+ tag: imageTag,
1537
+ imageRef,
1538
+ masterKey,
1539
+ config: dokployConfig
1540
+ });
1541
+ break;
1542
+ }
1543
+ case "aws-lambda": {
1544
+ logger$3.log("\n⚠️ AWS Lambda deployment is not yet implemented.");
1545
+ logger$3.log(" Use SST or AWS CDK for Lambda deployments.");
1546
+ result = {
1547
+ imageRef,
1548
+ masterKey
1549
+ };
1550
+ break;
1551
+ }
1552
+ default: throw new Error(`Unknown deploy provider: ${provider}\nSupported providers: docker, dokploy, aws-lambda`);
1553
+ }
1554
+ logger$3.log("\n✅ Deployment complete!");
1555
+ return result;
1556
+ }
1557
+
1558
+ //#endregion
1559
+ //#region src/deploy/init.ts
1560
+ const logger$2 = console;
1561
+ /**
1562
+ * Get the Dokploy API token from stored credentials or environment
1563
+ */
1564
+ async function getApiToken() {
1565
+ const token = await getDokployToken();
1566
+ if (!token) throw new Error("Dokploy credentials not found.\nRun \"gkm login --service dokploy\" to authenticate, or set DOKPLOY_API_TOKEN.");
1567
+ return token;
1568
+ }
1569
+ /**
1570
+ * Make a request to the Dokploy API
1571
+ */
1572
+ async function dokployRequest(method, endpoint, baseUrl, token, body) {
1573
+ const url = `${baseUrl}/api/${endpoint}`;
1574
+ const response = await fetch(url, {
1575
+ method,
1576
+ headers: {
1577
+ "Content-Type": "application/json",
1578
+ Authorization: `Bearer ${token}`
1579
+ },
1580
+ body: body ? JSON.stringify(body) : void 0
1581
+ });
1582
+ if (!response.ok) {
1583
+ let errorMessage = `Dokploy API error: ${response.status} ${response.statusText}`;
1584
+ try {
1585
+ const errorBody = await response.json();
1586
+ if (errorBody.message) errorMessage = `Dokploy API error: ${errorBody.message}`;
1587
+ } catch {}
1588
+ throw new Error(errorMessage);
1589
+ }
1590
+ const text = await response.text();
1591
+ if (!text) return {};
1592
+ return JSON.parse(text);
1593
+ }
1594
+ /**
1595
+ * Get all projects from Dokploy
1596
+ */
1597
+ async function getProjects(baseUrl, token) {
1598
+ return dokployRequest("GET", "project.all", baseUrl, token);
1599
+ }
1600
+ /**
1601
+ * Create a new project in Dokploy
1602
+ */
1603
+ async function createProject(baseUrl, token, name$1, description$1) {
1604
+ return dokployRequest("POST", "project.create", baseUrl, token, {
1605
+ name: name$1,
1606
+ description: description$1 || `Created by gkm CLI`
1607
+ });
1608
+ }
1609
+ /**
1610
+ * Get project by ID to get environment info
1611
+ */
1612
+ async function getProject(baseUrl, token, projectId) {
1613
+ return dokployRequest("POST", "project.one", baseUrl, token, { projectId });
1614
+ }
1615
+ /**
1616
+ * Create a new application in Dokploy
1617
+ */
1618
+ async function createApplication(baseUrl, token, name$1, projectId) {
1619
+ const project = await getProject(baseUrl, token, projectId);
1620
+ let environmentId;
1621
+ const firstEnv = project.environments?.[0];
1622
+ if (firstEnv) environmentId = firstEnv.environmentId;
1623
+ else {
1624
+ const env = await dokployRequest("POST", "environment.create", baseUrl, token, {
1625
+ projectId,
1626
+ name: "production",
1627
+ description: "Production environment"
1628
+ });
1629
+ environmentId = env.environmentId;
1630
+ }
1631
+ return dokployRequest("POST", "application.create", baseUrl, token, {
1632
+ name: name$1,
1633
+ projectId,
1634
+ environmentId
1635
+ });
1636
+ }
1637
+ /**
1638
+ * Configure application for Docker registry deployment
1639
+ */
1640
+ async function configureApplicationRegistry(baseUrl, token, applicationId, registryId) {
1641
+ await dokployRequest("POST", "application.update", baseUrl, token, {
1642
+ applicationId,
1643
+ registryId
1644
+ });
1645
+ }
1646
+ /**
1647
+ * Get available registries
1648
+ */
1649
+ async function getRegistries(baseUrl, token) {
1650
+ return dokployRequest("GET", "registry.all", baseUrl, token);
1651
+ }
1652
+ /**
1653
+ * Update gkm.config.ts with Dokploy configuration
1654
+ */
1655
+ async function updateConfig(config$1, cwd = process.cwd()) {
1656
+ const configPath = join(cwd, "gkm.config.ts");
1657
+ if (!existsSync(configPath)) {
1658
+ logger$2.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
1659
+ logger$2.log(` providers: {`);
1660
+ logger$2.log(` dokploy: {`);
1661
+ logger$2.log(` endpoint: '${config$1.endpoint}',`);
1662
+ logger$2.log(` projectId: '${config$1.projectId}',`);
1663
+ logger$2.log(` applicationId: '${config$1.applicationId}',`);
1664
+ logger$2.log(` },`);
1665
+ logger$2.log(` },`);
957
1666
  return;
958
1667
  }
959
- const rootOutputDir = join$1(process.cwd(), ".gkm");
960
- await mkdir(rootOutputDir, { recursive: true });
961
- for (const provider of resolved.providers) await buildForProvider(provider, buildContext, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, resolved.enableOpenApi);
1668
+ const content = await readFile(configPath, "utf-8");
1669
+ if (content.includes("dokploy:") && content.includes("applicationId:")) {
1670
+ logger$2.log("\n Dokploy config already exists in gkm.config.ts");
1671
+ logger$2.log(" Updating with new values...");
1672
+ }
1673
+ let newContent;
1674
+ if (content.includes("providers:")) if (content.includes("dokploy:")) newContent = content.replace(/dokploy:\s*\{[^}]*\}/, `dokploy: {
1675
+ endpoint: '${config$1.endpoint}',
1676
+ projectId: '${config$1.projectId}',
1677
+ applicationId: '${config$1.applicationId}',
1678
+ }`);
1679
+ else newContent = content.replace(/providers:\s*\{/, `providers: {
1680
+ dokploy: {
1681
+ endpoint: '${config$1.endpoint}',
1682
+ projectId: '${config$1.projectId}',
1683
+ applicationId: '${config$1.applicationId}',
1684
+ },`);
1685
+ else newContent = content.replace(/}\s*\)\s*;?\s*$/, `
1686
+ providers: {
1687
+ dokploy: {
1688
+ endpoint: '${config$1.endpoint}',
1689
+ projectId: '${config$1.projectId}',
1690
+ applicationId: '${config$1.applicationId}',
1691
+ },
1692
+ },
1693
+ });`);
1694
+ await writeFile(configPath, newContent);
1695
+ logger$2.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
1696
+ }
1697
+ /**
1698
+ * Initialize Dokploy deployment configuration
1699
+ */
1700
+ async function deployInitCommand(options) {
1701
+ const { projectName, appName, projectId: existingProjectId, registryId } = options;
1702
+ let endpoint = options.endpoint;
1703
+ if (!endpoint) {
1704
+ const stored = await getDokployCredentials();
1705
+ if (stored) endpoint = stored.endpoint;
1706
+ else throw new Error("Dokploy endpoint not specified.\nEither run \"gkm login --service dokploy\" first, or provide --endpoint.");
1707
+ }
1708
+ logger$2.log(`\n🚀 Initializing Dokploy deployment...`);
1709
+ logger$2.log(` Endpoint: ${endpoint}`);
1710
+ const token = await getApiToken();
1711
+ let projectId;
1712
+ if (existingProjectId) {
1713
+ projectId = existingProjectId;
1714
+ logger$2.log(`\n📁 Using existing project: ${projectId}`);
1715
+ } else {
1716
+ logger$2.log(`\n📁 Looking for project: ${projectName}`);
1717
+ const projects = await getProjects(endpoint, token);
1718
+ const existingProject = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
1719
+ if (existingProject) {
1720
+ projectId = existingProject.projectId;
1721
+ logger$2.log(` Found existing project: ${projectId}`);
1722
+ } else {
1723
+ logger$2.log(` Creating new project...`);
1724
+ const project = await createProject(endpoint, token, projectName);
1725
+ projectId = project.projectId;
1726
+ logger$2.log(` ✓ Created project: ${projectId}`);
1727
+ }
1728
+ }
1729
+ logger$2.log(`\n📦 Creating application: ${appName}`);
1730
+ const application = await createApplication(endpoint, token, appName, projectId);
1731
+ logger$2.log(` ✓ Created application: ${application.applicationId}`);
1732
+ if (registryId) {
1733
+ logger$2.log(`\n🔧 Configuring registry: ${registryId}`);
1734
+ await configureApplicationRegistry(endpoint, token, application.applicationId, registryId);
1735
+ logger$2.log(` ✓ Registry configured`);
1736
+ } else try {
1737
+ const registries = await getRegistries(endpoint, token);
1738
+ if (registries.length > 0) {
1739
+ logger$2.log(`\n📋 Available registries:`);
1740
+ for (const reg of registries) logger$2.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
1741
+ logger$2.log(`\n To use a registry, run with --registry-id <id>`);
1742
+ }
1743
+ } catch {}
1744
+ const config$1 = {
1745
+ endpoint,
1746
+ projectId,
1747
+ applicationId: application.applicationId
1748
+ };
1749
+ await updateConfig(config$1);
1750
+ logger$2.log(`\n✅ Dokploy deployment initialized!`);
1751
+ logger$2.log(`\n📋 Configuration:`);
1752
+ logger$2.log(` Project ID: ${projectId}`);
1753
+ logger$2.log(` Application ID: ${application.applicationId}`);
1754
+ logger$2.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
1755
+ logger$2.log(`\n📝 Next steps:`);
1756
+ logger$2.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
1757
+ logger$2.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
1758
+ return config$1;
1759
+ }
1760
+ /**
1761
+ * List available Dokploy resources
1762
+ */
1763
+ async function deployListCommand(options) {
1764
+ let endpoint = options.endpoint;
1765
+ if (!endpoint) {
1766
+ const stored = await getDokployCredentials();
1767
+ if (stored) endpoint = stored.endpoint;
1768
+ else throw new Error("Dokploy endpoint not specified.\nEither run \"gkm login --service dokploy\" first, or provide --endpoint.");
1769
+ }
1770
+ const { resource } = options;
1771
+ const token = await getApiToken();
1772
+ if (resource === "projects") {
1773
+ logger$2.log(`\n📁 Projects in ${endpoint}:`);
1774
+ const projects = await getProjects(endpoint, token);
1775
+ if (projects.length === 0) {
1776
+ logger$2.log(" No projects found");
1777
+ return;
1778
+ }
1779
+ for (const project of projects) {
1780
+ logger$2.log(`\n ${project.name} (${project.projectId})`);
1781
+ if (project.description) logger$2.log(` ${project.description}`);
1782
+ }
1783
+ } else if (resource === "registries") {
1784
+ logger$2.log(`\n🐳 Registries in ${endpoint}:`);
1785
+ const registries = await getRegistries(endpoint, token);
1786
+ if (registries.length === 0) {
1787
+ logger$2.log(" No registries configured");
1788
+ logger$2.log(" Add a registry in Dokploy: Settings > Docker Registry");
1789
+ return;
1790
+ }
1791
+ for (const registry of registries) {
1792
+ logger$2.log(`\n ${registry.registryName} (${registry.registryId})`);
1793
+ logger$2.log(` URL: ${registry.registryUrl}`);
1794
+ logger$2.log(` Username: ${registry.username}`);
1795
+ if (registry.imagePrefix) logger$2.log(` Prefix: ${registry.imagePrefix}`);
1796
+ }
1797
+ }
1798
+ }
1799
+
1800
+ //#endregion
1801
+ //#region src/docker/compose.ts
1802
+ /** Default Docker images for services */
1803
+ const DEFAULT_SERVICE_IMAGES = {
1804
+ postgres: "postgres",
1805
+ redis: "redis",
1806
+ rabbitmq: "rabbitmq"
1807
+ };
1808
+ /** Default Docker image versions for services */
1809
+ const DEFAULT_SERVICE_VERSIONS = {
1810
+ postgres: "16-alpine",
1811
+ redis: "7-alpine",
1812
+ rabbitmq: "3-management-alpine"
1813
+ };
1814
+ /** Get the default full image reference for a service */
1815
+ function getDefaultImage(serviceName) {
1816
+ return `${DEFAULT_SERVICE_IMAGES[serviceName]}:${DEFAULT_SERVICE_VERSIONS[serviceName]}`;
1817
+ }
1818
+ /** Normalize services config to a consistent format - returns Map of service name to full image reference */
1819
+ function normalizeServices(services) {
1820
+ const result = /* @__PURE__ */ new Map();
1821
+ if (Array.isArray(services)) for (const name$1 of services) result.set(name$1, getDefaultImage(name$1));
1822
+ else for (const [name$1, config$1] of Object.entries(services)) {
1823
+ const serviceName = name$1;
1824
+ if (config$1 === true) result.set(serviceName, getDefaultImage(serviceName));
1825
+ else if (config$1 && typeof config$1 === "object") {
1826
+ const serviceConfig = config$1;
1827
+ if (serviceConfig.image) result.set(serviceName, serviceConfig.image);
1828
+ else {
1829
+ const version$1 = serviceConfig.version ?? DEFAULT_SERVICE_VERSIONS[serviceName];
1830
+ result.set(serviceName, `${DEFAULT_SERVICE_IMAGES[serviceName]}:${version$1}`);
1831
+ }
1832
+ }
1833
+ }
1834
+ return result;
1835
+ }
1836
+ /**
1837
+ * Generate docker-compose.yml for production deployment
1838
+ */
1839
+ function generateDockerCompose(options) {
1840
+ const { imageName, registry, port, healthCheckPath, services } = options;
1841
+ const serviceMap = normalizeServices(services);
1842
+ const imageRef = registry ? `\${REGISTRY:-${registry}}/` : "";
1843
+ let yaml = `version: '3.8'
1844
+
1845
+ services:
1846
+ api:
1847
+ build:
1848
+ context: ../..
1849
+ dockerfile: .gkm/docker/Dockerfile
1850
+ image: ${imageRef}\${IMAGE_NAME:-${imageName}}:\${TAG:-latest}
1851
+ container_name: ${imageName}
1852
+ restart: unless-stopped
1853
+ ports:
1854
+ - "\${PORT:-${port}}:${port}"
1855
+ environment:
1856
+ - NODE_ENV=production
1857
+ `;
1858
+ if (serviceMap.has("postgres")) yaml += ` - DATABASE_URL=\${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/app}
1859
+ `;
1860
+ if (serviceMap.has("redis")) yaml += ` - REDIS_URL=\${REDIS_URL:-redis://redis:6379}
1861
+ `;
1862
+ if (serviceMap.has("rabbitmq")) yaml += ` - RABBITMQ_URL=\${RABBITMQ_URL:-amqp://rabbitmq:5672}
1863
+ `;
1864
+ yaml += ` healthcheck:
1865
+ test: ["CMD", "wget", "-q", "--spider", "http://localhost:${port}${healthCheckPath}"]
1866
+ interval: 30s
1867
+ timeout: 3s
1868
+ retries: 3
1869
+ `;
1870
+ if (serviceMap.size > 0) {
1871
+ yaml += ` depends_on:
1872
+ `;
1873
+ for (const serviceName of serviceMap.keys()) yaml += ` ${serviceName}:
1874
+ condition: service_healthy
1875
+ `;
1876
+ }
1877
+ yaml += ` networks:
1878
+ - app-network
1879
+ `;
1880
+ const postgresImage = serviceMap.get("postgres");
1881
+ if (postgresImage) yaml += `
1882
+ postgres:
1883
+ image: ${postgresImage}
1884
+ container_name: postgres
1885
+ restart: unless-stopped
1886
+ environment:
1887
+ POSTGRES_USER: \${POSTGRES_USER:-postgres}
1888
+ POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
1889
+ POSTGRES_DB: \${POSTGRES_DB:-app}
1890
+ volumes:
1891
+ - postgres_data:/var/lib/postgresql/data
1892
+ healthcheck:
1893
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
1894
+ interval: 5s
1895
+ timeout: 5s
1896
+ retries: 5
1897
+ networks:
1898
+ - app-network
1899
+ `;
1900
+ const redisImage = serviceMap.get("redis");
1901
+ if (redisImage) yaml += `
1902
+ redis:
1903
+ image: ${redisImage}
1904
+ container_name: redis
1905
+ restart: unless-stopped
1906
+ volumes:
1907
+ - redis_data:/data
1908
+ healthcheck:
1909
+ test: ["CMD", "redis-cli", "ping"]
1910
+ interval: 5s
1911
+ timeout: 5s
1912
+ retries: 5
1913
+ networks:
1914
+ - app-network
1915
+ `;
1916
+ const rabbitmqImage = serviceMap.get("rabbitmq");
1917
+ if (rabbitmqImage) yaml += `
1918
+ rabbitmq:
1919
+ image: ${rabbitmqImage}
1920
+ container_name: rabbitmq
1921
+ restart: unless-stopped
1922
+ environment:
1923
+ RABBITMQ_DEFAULT_USER: \${RABBITMQ_USER:-guest}
1924
+ RABBITMQ_DEFAULT_PASS: \${RABBITMQ_PASSWORD:-guest}
1925
+ ports:
1926
+ - "15672:15672" # Management UI
1927
+ volumes:
1928
+ - rabbitmq_data:/var/lib/rabbitmq
1929
+ healthcheck:
1930
+ test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
1931
+ interval: 10s
1932
+ timeout: 5s
1933
+ retries: 5
1934
+ networks:
1935
+ - app-network
1936
+ `;
1937
+ yaml += `
1938
+ volumes:
1939
+ `;
1940
+ if (serviceMap.has("postgres")) yaml += ` postgres_data:
1941
+ `;
1942
+ if (serviceMap.has("redis")) yaml += ` redis_data:
1943
+ `;
1944
+ if (serviceMap.has("rabbitmq")) yaml += ` rabbitmq_data:
1945
+ `;
1946
+ yaml += `
1947
+ networks:
1948
+ app-network:
1949
+ driver: bridge
1950
+ `;
1951
+ return yaml;
1952
+ }
1953
+ /**
1954
+ * Generate a minimal docker-compose.yml for API only
1955
+ */
1956
+ function generateMinimalDockerCompose(options) {
1957
+ const { imageName, registry, port, healthCheckPath } = options;
1958
+ const imageRef = registry ? `\${REGISTRY:-${registry}}/` : "";
1959
+ return `version: '3.8'
1960
+
1961
+ services:
1962
+ api:
1963
+ build:
1964
+ context: ../..
1965
+ dockerfile: .gkm/docker/Dockerfile
1966
+ image: ${imageRef}\${IMAGE_NAME:-${imageName}}:\${TAG:-latest}
1967
+ container_name: ${imageName}
1968
+ restart: unless-stopped
1969
+ ports:
1970
+ - "\${PORT:-${port}}:${port}"
1971
+ environment:
1972
+ - NODE_ENV=production
1973
+ healthcheck:
1974
+ test: ["CMD", "wget", "-q", "--spider", "http://localhost:${port}${healthCheckPath}"]
1975
+ interval: 30s
1976
+ timeout: 3s
1977
+ retries: 3
1978
+ networks:
1979
+ - app-network
1980
+
1981
+ networks:
1982
+ app-network:
1983
+ driver: bridge
1984
+ `;
1985
+ }
1986
+
1987
+ //#endregion
1988
+ //#region src/docker/templates.ts
1989
+ /**
1990
+ * Detect package manager from lockfiles
1991
+ * Walks up the directory tree to find lockfile (for monorepos)
1992
+ */
1993
+ function detectPackageManager$1(cwd = process.cwd()) {
1994
+ const lockfiles = [
1995
+ ["pnpm-lock.yaml", "pnpm"],
1996
+ ["bun.lockb", "bun"],
1997
+ ["yarn.lock", "yarn"],
1998
+ ["package-lock.json", "npm"]
1999
+ ];
2000
+ let dir = cwd;
2001
+ const root = parse(dir).root;
2002
+ while (dir !== root) {
2003
+ for (const [lockfile, pm] of lockfiles) if (existsSync(join(dir, lockfile))) return pm;
2004
+ dir = dirname(dir);
2005
+ }
2006
+ for (const [lockfile, pm] of lockfiles) if (existsSync(join(root, lockfile))) return pm;
2007
+ return "pnpm";
2008
+ }
2009
+ /**
2010
+ * Get package manager specific commands and paths
2011
+ */
2012
+ function getPmConfig(pm) {
2013
+ const configs = {
2014
+ pnpm: {
2015
+ install: "corepack enable && corepack prepare pnpm@latest --activate",
2016
+ lockfile: "pnpm-lock.yaml",
2017
+ fetch: "pnpm fetch",
2018
+ installCmd: "pnpm install --frozen-lockfile --offline",
2019
+ cacheTarget: "/root/.local/share/pnpm/store",
2020
+ cacheId: "pnpm",
2021
+ run: "pnpm",
2022
+ addGlobal: "pnpm add -g"
2023
+ },
2024
+ npm: {
2025
+ install: "",
2026
+ lockfile: "package-lock.json",
2027
+ fetch: "",
2028
+ installCmd: "npm ci",
2029
+ cacheTarget: "/root/.npm",
2030
+ cacheId: "npm",
2031
+ run: "npm run",
2032
+ addGlobal: "npm install -g"
2033
+ },
2034
+ yarn: {
2035
+ install: "corepack enable && corepack prepare yarn@stable --activate",
2036
+ lockfile: "yarn.lock",
2037
+ fetch: "",
2038
+ installCmd: "yarn install --frozen-lockfile",
2039
+ cacheTarget: "/root/.yarn/cache",
2040
+ cacheId: "yarn",
2041
+ run: "yarn",
2042
+ addGlobal: "yarn global add"
2043
+ },
2044
+ bun: {
2045
+ install: "npm install -g bun",
2046
+ lockfile: "bun.lockb",
2047
+ fetch: "",
2048
+ installCmd: "bun install --frozen-lockfile",
2049
+ cacheTarget: "/root/.bun/install/cache",
2050
+ cacheId: "bun",
2051
+ run: "bun run",
2052
+ addGlobal: "bun add -g"
2053
+ }
2054
+ };
2055
+ return configs[pm];
2056
+ }
2057
+ /**
2058
+ * Generate a multi-stage Dockerfile for building from source
2059
+ * Optimized for build speed with:
2060
+ * - BuildKit cache mounts for package manager store
2061
+ * - pnpm fetch for better layer caching (when using pnpm)
2062
+ * - Optional turbo prune for monorepos
2063
+ */
2064
+ function generateMultiStageDockerfile(options) {
2065
+ const { baseImage, port, healthCheckPath, turbo, turboPackage, packageManager } = options;
2066
+ if (turbo) return generateTurboDockerfile({
2067
+ ...options,
2068
+ turboPackage: turboPackage ?? "api"
2069
+ });
2070
+ const pm = getPmConfig(packageManager);
2071
+ const installPm = pm.install ? `\n# Install ${packageManager}\nRUN ${pm.install}\n` : "";
2072
+ const hasFetch = packageManager === "pnpm";
2073
+ const depsStage = hasFetch ? `# Copy lockfile first for better caching
2074
+ COPY ${pm.lockfile} ./
2075
+
2076
+ # Fetch dependencies (downloads to virtual store, cached separately)
2077
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
2078
+ ${pm.fetch}
2079
+
2080
+ # Copy package.json after fetch
2081
+ COPY package.json ./
2082
+
2083
+ # Install from cache (fast - no network needed)
2084
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
2085
+ ${pm.installCmd}` : `# Copy package files
2086
+ COPY package.json ${pm.lockfile} ./
2087
+
2088
+ # Install dependencies with cache
2089
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
2090
+ ${pm.installCmd}`;
2091
+ return `# syntax=docker/dockerfile:1
2092
+ # Stage 1: Dependencies
2093
+ FROM ${baseImage} AS deps
2094
+
2095
+ WORKDIR /app
2096
+ ${installPm}
2097
+ ${depsStage}
2098
+
2099
+ # Stage 2: Build
2100
+ FROM deps AS builder
2101
+
2102
+ WORKDIR /app
2103
+
2104
+ # Copy source (deps already installed)
2105
+ COPY . .
2106
+
2107
+ # Build production server
2108
+ RUN ${pm.run} gkm build --provider server --production
2109
+
2110
+ # Stage 3: Production
2111
+ FROM ${baseImage} AS runner
2112
+
2113
+ WORKDIR /app
2114
+
2115
+ # Install tini for proper signal handling as PID 1
2116
+ RUN apk add --no-cache tini
2117
+
2118
+ # Create non-root user
2119
+ RUN addgroup --system --gid 1001 nodejs && \\
2120
+ adduser --system --uid 1001 hono
2121
+
2122
+ # Copy bundled server
2123
+ COPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./
2124
+
2125
+ # Environment
2126
+ ENV NODE_ENV=production
2127
+ ENV PORT=${port}
2128
+
2129
+ # Health check
2130
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
2131
+ CMD wget -q --spider http://localhost:${port}${healthCheckPath} || exit 1
2132
+
2133
+ # Switch to non-root user
2134
+ USER hono
2135
+
2136
+ EXPOSE ${port}
2137
+
2138
+ # Use tini as entrypoint to handle PID 1 responsibilities
2139
+ ENTRYPOINT ["/sbin/tini", "--"]
2140
+ CMD ["node", "server.mjs"]
2141
+ `;
2142
+ }
2143
+ /**
2144
+ * Generate a Dockerfile optimized for Turbo monorepos
2145
+ * Uses turbo prune to create minimal Docker context
2146
+ */
2147
+ function generateTurboDockerfile(options) {
2148
+ const { baseImage, port, healthCheckPath, turboPackage, packageManager } = options;
2149
+ const pm = getPmConfig(packageManager);
2150
+ const installPm = pm.install ? `RUN ${pm.install}` : "";
2151
+ const hasFetch = packageManager === "pnpm";
2152
+ const depsInstall = hasFetch ? `# Fetch and install from cache
2153
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
2154
+ ${pm.fetch}
2155
+
2156
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
2157
+ ${pm.installCmd}` : `# Install dependencies with cache
2158
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
2159
+ ${pm.installCmd}`;
2160
+ return `# syntax=docker/dockerfile:1
2161
+ # Stage 1: Prune monorepo
2162
+ FROM ${baseImage} AS pruner
2163
+
2164
+ WORKDIR /app
2165
+
2166
+ ${installPm}
2167
+ RUN ${pm.addGlobal} turbo
2168
+
2169
+ COPY . .
2170
+
2171
+ # Prune to only include necessary packages
2172
+ RUN turbo prune ${turboPackage} --docker
2173
+
2174
+ # Stage 2: Install dependencies
2175
+ FROM ${baseImage} AS deps
2176
+
2177
+ WORKDIR /app
2178
+
2179
+ ${installPm}
2180
+
2181
+ # Copy pruned lockfile and package.jsons
2182
+ COPY --from=pruner /app/out/${pm.lockfile} ./
2183
+ COPY --from=pruner /app/out/json/ ./
2184
+
2185
+ ${depsInstall}
2186
+
2187
+ # Stage 3: Build
2188
+ FROM deps AS builder
2189
+
2190
+ WORKDIR /app
2191
+
2192
+ # Copy pruned source
2193
+ COPY --from=pruner /app/out/full/ ./
2194
+
2195
+ # Build production server
2196
+ RUN ${pm.run} gkm build --provider server --production
2197
+
2198
+ # Stage 4: Production
2199
+ FROM ${baseImage} AS runner
2200
+
2201
+ WORKDIR /app
2202
+
2203
+ RUN apk add --no-cache tini
2204
+
2205
+ RUN addgroup --system --gid 1001 nodejs && \\
2206
+ adduser --system --uid 1001 hono
2207
+
2208
+ COPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./
2209
+
2210
+ ENV NODE_ENV=production
2211
+ ENV PORT=${port}
2212
+
2213
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
2214
+ CMD wget -q --spider http://localhost:${port}${healthCheckPath} || exit 1
2215
+
2216
+ USER hono
2217
+
2218
+ EXPOSE ${port}
2219
+
2220
+ ENTRYPOINT ["/sbin/tini", "--"]
2221
+ CMD ["node", "server.mjs"]
2222
+ `;
2223
+ }
2224
+ /**
2225
+ * Generate a slim Dockerfile for pre-built bundles
2226
+ */
2227
+ function generateSlimDockerfile(options) {
2228
+ const { baseImage, port, healthCheckPath } = options;
2229
+ return `# Slim Dockerfile for pre-built production bundle
2230
+ FROM ${baseImage}
2231
+
2232
+ WORKDIR /app
2233
+
2234
+ # Install tini for proper signal handling as PID 1
2235
+ # Handles SIGTERM propagation and zombie process reaping
2236
+ RUN apk add --no-cache tini
2237
+
2238
+ # Create non-root user
2239
+ RUN addgroup --system --gid 1001 nodejs && \\
2240
+ adduser --system --uid 1001 hono
2241
+
2242
+ # Copy pre-built bundle
2243
+ COPY .gkm/server/dist/server.mjs ./
2244
+
2245
+ # Environment
2246
+ ENV NODE_ENV=production
2247
+ ENV PORT=${port}
2248
+
2249
+ # Health check
2250
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
2251
+ CMD wget -q --spider http://localhost:${port}${healthCheckPath} || exit 1
2252
+
2253
+ # Switch to non-root user
2254
+ USER hono
2255
+
2256
+ EXPOSE ${port}
2257
+
2258
+ # Use tini as entrypoint to handle PID 1 responsibilities
2259
+ ENTRYPOINT ["/sbin/tini", "--"]
2260
+ CMD ["node", "server.mjs"]
2261
+ `;
2262
+ }
2263
+ /**
2264
+ * Generate .dockerignore file
2265
+ */
2266
+ function generateDockerignore() {
2267
+ return `# Dependencies
2268
+ node_modules
2269
+ .pnpm-store
2270
+
2271
+ # Build output (except what we need)
2272
+ .gkm/aws*
2273
+ .gkm/server/*.ts
2274
+ !.gkm/server/dist
2275
+
2276
+ # IDE and editor
2277
+ .idea
2278
+ .vscode
2279
+ *.swp
2280
+ *.swo
2281
+
2282
+ # Git
2283
+ .git
2284
+ .gitignore
2285
+
2286
+ # Logs
2287
+ *.log
2288
+ npm-debug.log*
2289
+ pnpm-debug.log*
2290
+
2291
+ # Test files
2292
+ **/*.test.ts
2293
+ **/*.spec.ts
2294
+ **/__tests__
2295
+ coverage
2296
+
2297
+ # Documentation
2298
+ docs
2299
+ *.md
2300
+ !README.md
2301
+
2302
+ # Environment files (handle secrets separately)
2303
+ .env
2304
+ .env.*
2305
+ !.env.example
2306
+
2307
+ # Docker files (don't copy recursively)
2308
+ Dockerfile*
2309
+ docker-compose*
2310
+ .dockerignore
2311
+ `;
2312
+ }
2313
+ /**
2314
+ * Generate docker-entrypoint.sh for custom startup logic
2315
+ */
2316
+ function generateDockerEntrypoint() {
2317
+ return `#!/bin/sh
2318
+ set -e
2319
+
2320
+ # Run any custom startup scripts here
2321
+ # Example: wait for database
2322
+ # until nc -z $DB_HOST $DB_PORT; do
2323
+ # echo "Waiting for database..."
2324
+ # sleep 1
2325
+ # done
2326
+
2327
+ # Execute the main command
2328
+ exec "$@"
2329
+ `;
2330
+ }
2331
+ /**
2332
+ * Resolve Docker configuration from GkmConfig with defaults
2333
+ */
2334
+ function resolveDockerConfig(config$1) {
2335
+ const docker = config$1.docker ?? {};
2336
+ let defaultImageName = "api";
2337
+ try {
2338
+ const pkg = __require(`${process.cwd()}/package.json`);
2339
+ if (pkg.name) defaultImageName = pkg.name.replace(/^@[^/]+\//, "");
2340
+ } catch {}
2341
+ return {
2342
+ registry: docker.registry ?? "",
2343
+ imageName: docker.imageName ?? defaultImageName,
2344
+ baseImage: docker.baseImage ?? "node:22-alpine",
2345
+ port: docker.port ?? 3e3,
2346
+ compose: docker.compose
2347
+ };
2348
+ }
2349
+
2350
+ //#endregion
2351
+ //#region src/docker/index.ts
2352
+ const logger$1 = console;
2353
+ /**
2354
+ * Docker command implementation
2355
+ * Generates Dockerfile, docker-compose.yml, and related files
2356
+ *
2357
+ * Default: Multi-stage Dockerfile that builds from source inside Docker
2358
+ * --slim: Slim Dockerfile that copies pre-built bundle (requires prior build)
2359
+ */
2360
+ async function dockerCommand(options) {
2361
+ const config$1 = await loadConfig();
2362
+ const dockerConfig = resolveDockerConfig(config$1);
2363
+ const serverConfig = typeof config$1.providers?.server === "object" ? config$1.providers.server : void 0;
2364
+ const healthCheckPath = serverConfig?.production?.healthCheck ?? "/health";
2365
+ const useSlim = options.slim === true;
2366
+ if (useSlim) {
2367
+ const distDir = join(process.cwd(), ".gkm", "server", "dist");
2368
+ const hasBuild = existsSync(join(distDir, "server.mjs"));
2369
+ if (!hasBuild) throw new Error("Slim Dockerfile requires a pre-built bundle. Run `gkm build --provider server --production` first, or omit --slim to use multi-stage build.");
2370
+ }
2371
+ const dockerDir = join(process.cwd(), ".gkm", "docker");
2372
+ await mkdir(dockerDir, { recursive: true });
2373
+ const packageManager = detectPackageManager$1();
2374
+ const templateOptions = {
2375
+ imageName: dockerConfig.imageName,
2376
+ baseImage: dockerConfig.baseImage,
2377
+ port: dockerConfig.port,
2378
+ healthCheckPath,
2379
+ prebuilt: useSlim,
2380
+ turbo: options.turbo,
2381
+ turboPackage: options.turboPackage ?? dockerConfig.imageName,
2382
+ packageManager
2383
+ };
2384
+ const dockerfile = useSlim ? generateSlimDockerfile(templateOptions) : generateMultiStageDockerfile(templateOptions);
2385
+ const dockerMode = useSlim ? "slim" : options.turbo ? "turbo" : "multi-stage";
2386
+ const dockerfilePath = join(dockerDir, "Dockerfile");
2387
+ await writeFile(dockerfilePath, dockerfile);
2388
+ logger$1.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
2389
+ const composeOptions = {
2390
+ imageName: dockerConfig.imageName,
2391
+ registry: options.registry ?? dockerConfig.registry,
2392
+ port: dockerConfig.port,
2393
+ healthCheckPath,
2394
+ services: dockerConfig.compose?.services ?? {}
2395
+ };
2396
+ const hasServices = Array.isArray(composeOptions.services) ? composeOptions.services.length > 0 : Object.keys(composeOptions.services).length > 0;
2397
+ const dockerCompose = hasServices ? generateDockerCompose(composeOptions) : generateMinimalDockerCompose(composeOptions);
2398
+ const composePath = join(dockerDir, "docker-compose.yml");
2399
+ await writeFile(composePath, dockerCompose);
2400
+ logger$1.log("Generated: .gkm/docker/docker-compose.yml");
2401
+ const dockerignore = generateDockerignore();
2402
+ const dockerignorePath = join(process.cwd(), ".dockerignore");
2403
+ await writeFile(dockerignorePath, dockerignore);
2404
+ logger$1.log("Generated: .dockerignore (project root)");
2405
+ const entrypoint = generateDockerEntrypoint();
2406
+ const entrypointPath = join(dockerDir, "docker-entrypoint.sh");
2407
+ await writeFile(entrypointPath, entrypoint);
2408
+ logger$1.log("Generated: .gkm/docker/docker-entrypoint.sh");
2409
+ const result = {
2410
+ dockerfile: dockerfilePath,
2411
+ dockerCompose: composePath,
2412
+ dockerignore: dockerignorePath,
2413
+ entrypoint: entrypointPath
2414
+ };
2415
+ if (options.build) await buildDockerImage(dockerConfig.imageName, options);
2416
+ if (options.push) await pushDockerImage(dockerConfig.imageName, options);
2417
+ return result;
2418
+ }
2419
+ /**
2420
+ * Build Docker image
2421
+ * Uses BuildKit for cache mount support
2422
+ */
2423
+ async function buildDockerImage(imageName, options) {
2424
+ const tag = options.tag ?? "latest";
2425
+ const registry = options.registry;
2426
+ const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
2427
+ logger$1.log(`\n🐳 Building Docker image: ${fullImageName}`);
2428
+ try {
2429
+ execSync(`DOCKER_BUILDKIT=1 docker build -f .gkm/docker/Dockerfile -t ${fullImageName} .`, {
2430
+ cwd: process.cwd(),
2431
+ stdio: "inherit",
2432
+ env: {
2433
+ ...process.env,
2434
+ DOCKER_BUILDKIT: "1"
2435
+ }
2436
+ });
2437
+ logger$1.log(`✅ Docker image built: ${fullImageName}`);
2438
+ } catch (error) {
2439
+ throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
2440
+ }
962
2441
  }
963
- async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, endpoints, functions, crons, subscribers, enableOpenApi) {
964
- const outputDir = join$1(process.cwd(), ".gkm", provider);
965
- await mkdir(outputDir, { recursive: true });
966
- logger.log(`\nGenerating handlers for provider: ${provider}`);
967
- const [routes, functionInfos, cronInfos, subscriberInfos] = await Promise.all([
968
- endpointGenerator.build(context, endpoints, outputDir, {
969
- provider,
970
- enableOpenApi
971
- }),
972
- functionGenerator.build(context, functions, outputDir, { provider }),
973
- cronGenerator.build(context, crons, outputDir, { provider }),
974
- subscriberGenerator.build(context, subscribers, outputDir, { provider })
975
- ]);
976
- logger.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
977
- if (provider === "server") {
978
- const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
979
- path: construct._path,
980
- method: construct.method,
981
- handler: "",
982
- authorizer: construct.authorizer?.name ?? "none"
983
- })));
984
- const appInfo = {
985
- handler: relative$1(process.cwd(), join$1(outputDir, "app.ts")),
986
- endpoints: relative$1(process.cwd(), join$1(outputDir, "endpoints.ts"))
987
- };
988
- await generateServerManifest(rootOutputDir, appInfo, routeMetadata, subscriberInfos);
989
- } else await generateAwsManifest(rootOutputDir, routes, functionInfos, cronInfos, subscriberInfos);
2442
+ /**
2443
+ * Push Docker image to registry
2444
+ */
2445
+ async function pushDockerImage(imageName, options) {
2446
+ const tag = options.tag ?? "latest";
2447
+ const registry = options.registry;
2448
+ if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
2449
+ const fullImageName = `${registry}/${imageName}:${tag}`;
2450
+ logger$1.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
2451
+ try {
2452
+ execSync(`docker push ${fullImageName}`, {
2453
+ cwd: process.cwd(),
2454
+ stdio: "inherit"
2455
+ });
2456
+ logger$1.log(`✅ Docker image pushed: ${fullImageName}`);
2457
+ } catch (error) {
2458
+ throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
2459
+ }
990
2460
  }
991
2461
 
992
2462
  //#endregion
@@ -1064,7 +2534,7 @@ export default defineConfig({
1064
2534
  content: gkmConfig
1065
2535
  }, {
1066
2536
  path: "tsconfig.json",
1067
- content: JSON.stringify(tsConfig, null, 2) + "\n"
2537
+ content: `${JSON.stringify(tsConfig, null, 2)}\n`
1068
2538
  }];
1069
2539
  const biomeConfig = {
1070
2540
  $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
@@ -1138,15 +2608,15 @@ export default defineConfig({
1138
2608
  },
1139
2609
  {
1140
2610
  path: "tsconfig.json",
1141
- content: JSON.stringify(tsConfig, null, 2) + "\n"
2611
+ content: `${JSON.stringify(tsConfig, null, 2)}\n`
1142
2612
  },
1143
2613
  {
1144
2614
  path: "biome.json",
1145
- content: JSON.stringify(biomeConfig, null, 2) + "\n"
2615
+ content: `${JSON.stringify(biomeConfig, null, 2)}\n`
1146
2616
  },
1147
2617
  {
1148
2618
  path: "turbo.json",
1149
- content: JSON.stringify(turboConfig, null, 2) + "\n"
2619
+ content: `${JSON.stringify(turboConfig, null, 2)}\n`
1150
2620
  }
1151
2621
  ];
1152
2622
  }
@@ -1498,11 +2968,11 @@ export type UpdateUser = z.infer<typeof updateUserSchema>;
1498
2968
  return [
1499
2969
  {
1500
2970
  path: "packages/models/package.json",
1501
- content: JSON.stringify(packageJson, null, 2) + "\n"
2971
+ content: `${JSON.stringify(packageJson, null, 2)}\n`
1502
2972
  },
1503
2973
  {
1504
2974
  path: "packages/models/tsconfig.json",
1505
- content: JSON.stringify(tsConfig, null, 2) + "\n"
2975
+ content: `${JSON.stringify(tsConfig, null, 2)}\n`
1506
2976
  },
1507
2977
  {
1508
2978
  path: "packages/models/src/index.ts",
@@ -1668,7 +3138,7 @@ coverage/
1668
3138
  return [
1669
3139
  {
1670
3140
  path: "package.json",
1671
- content: JSON.stringify(rootPackageJson, null, 2) + "\n"
3141
+ content: `${JSON.stringify(rootPackageJson, null, 2)}\n`
1672
3142
  },
1673
3143
  {
1674
3144
  path: "pnpm-workspace.yaml",
@@ -1676,15 +3146,15 @@ coverage/
1676
3146
  },
1677
3147
  {
1678
3148
  path: "tsconfig.json",
1679
- content: JSON.stringify(tsConfig, null, 2) + "\n"
3149
+ content: `${JSON.stringify(tsConfig, null, 2)}\n`
1680
3150
  },
1681
3151
  {
1682
3152
  path: "biome.json",
1683
- content: JSON.stringify(biomeConfig, null, 2) + "\n"
3153
+ content: `${JSON.stringify(biomeConfig, null, 2)}\n`
1684
3154
  },
1685
3155
  {
1686
3156
  path: "turbo.json",
1687
- content: JSON.stringify(turboConfig, null, 2) + "\n"
3157
+ content: `${JSON.stringify(turboConfig, null, 2)}\n`
1688
3158
  },
1689
3159
  {
1690
3160
  path: ".gitignore",
@@ -2405,19 +3875,19 @@ function generatePackageJson(options, template) {
2405
3875
  if (studio) dependencies$1["@geekmidas/studio"] = "workspace:*";
2406
3876
  if (database) {
2407
3877
  dependencies$1["@geekmidas/db"] = "workspace:*";
2408
- dependencies$1["kysely"] = "~0.28.2";
2409
- dependencies$1["pg"] = "~8.16.0";
3878
+ dependencies$1.kysely = "~0.28.2";
3879
+ dependencies$1.pg = "~8.16.0";
2410
3880
  devDependencies$1["@types/pg"] = "~8.15.0";
2411
3881
  }
2412
- dependencies$1["zod"] = "~4.1.0";
3882
+ dependencies$1.zod = "~4.1.0";
2413
3883
  if (monorepo) {
2414
3884
  delete devDependencies$1["@biomejs/biome"];
2415
- delete devDependencies$1["turbo"];
2416
- delete scripts$1["lint"];
2417
- delete scripts$1["fmt"];
3885
+ delete devDependencies$1.turbo;
3886
+ delete scripts$1.lint;
3887
+ delete scripts$1.fmt;
2418
3888
  delete scripts$1["fmt:check"];
2419
3889
  dependencies$1[`@${name$1}/models`] = "workspace:*";
2420
- delete dependencies$1["zod"];
3890
+ delete dependencies$1.zod;
2421
3891
  }
2422
3892
  const sortObject = (obj) => Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
2423
3893
  let packageName = name$1;
@@ -2441,7 +3911,7 @@ function generatePackageJson(options, template) {
2441
3911
  };
2442
3912
  return [{
2443
3913
  path: "package.json",
2444
- content: JSON.stringify(packageJson, null, 2) + "\n"
3914
+ content: `${JSON.stringify(packageJson, null, 2)}\n`
2445
3915
  }];
2446
3916
  }
2447
3917
 
@@ -2460,10 +3930,10 @@ function generateSourceFiles(options, template) {
2460
3930
  * Detect the package manager being used based on lockfiles or npm_config_user_agent
2461
3931
  */
2462
3932
  function detectPackageManager(cwd = process.cwd()) {
2463
- if (existsSync(join$1(cwd, "pnpm-lock.yaml"))) return "pnpm";
2464
- if (existsSync(join$1(cwd, "yarn.lock"))) return "yarn";
2465
- if (existsSync(join$1(cwd, "bun.lockb"))) return "bun";
2466
- if (existsSync(join$1(cwd, "package-lock.json"))) return "npm";
3933
+ if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
3934
+ if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
3935
+ if (existsSync(join(cwd, "bun.lockb"))) return "bun";
3936
+ if (existsSync(join(cwd, "package-lock.json"))) return "npm";
2467
3937
  const userAgent = process.env.npm_config_user_agent || "";
2468
3938
  if (userAgent.includes("pnpm")) return "pnpm";
2469
3939
  if (userAgent.includes("yarn")) return "yarn";
@@ -2489,7 +3959,7 @@ function validateProjectName(name$1) {
2489
3959
  * Check if a directory already exists at the target path
2490
3960
  */
2491
3961
  function checkDirectoryExists(name$1, cwd = process.cwd()) {
2492
- const targetPath = join$1(cwd, name$1);
3962
+ const targetPath = join(cwd, name$1);
2493
3963
  if (existsSync(targetPath)) return `Directory "${name$1}" already exists`;
2494
3964
  return true;
2495
3965
  }
@@ -2501,7 +3971,6 @@ function getInstallCommand(pkgManager) {
2501
3971
  case "pnpm": return "pnpm install";
2502
3972
  case "yarn": return "yarn";
2503
3973
  case "bun": return "bun install";
2504
- case "npm":
2505
3974
  default: return "npm install";
2506
3975
  }
2507
3976
  }
@@ -2513,7 +3982,6 @@ function getRunCommand(pkgManager, script) {
2513
3982
  case "pnpm": return `pnpm ${script}`;
2514
3983
  case "yarn": return `yarn ${script}`;
2515
3984
  case "bun": return `bun run ${script}`;
2516
- case "npm":
2517
3985
  default: return `npm run ${script}`;
2518
3986
  }
2519
3987
  }
@@ -2597,21 +4065,12 @@ async function initCommand(projectName, options = {}) {
2597
4065
  }
2598
4066
  ], { onCancel });
2599
4067
  const name$1 = projectName || answers.name;
2600
- if (!name$1) {
2601
- console.error(" Error: Project name is required\n");
2602
- process.exit(1);
2603
- }
4068
+ if (!name$1) process.exit(1);
2604
4069
  if (projectName) {
2605
4070
  const nameValid = validateProjectName(projectName);
2606
- if (nameValid !== true) {
2607
- console.error(` Error: ${nameValid}\n`);
2608
- process.exit(1);
2609
- }
4071
+ if (nameValid !== true) process.exit(1);
2610
4072
  const dirValid = checkDirectoryExists(projectName, cwd);
2611
- if (dirValid !== true) {
2612
- console.error(` Error: ${dirValid}\n`);
2613
- process.exit(1);
2614
- }
4073
+ if (dirValid !== true) process.exit(1);
2615
4074
  }
2616
4075
  const monorepo = options.monorepo ?? (options.yes ? false : answers.monorepo ?? false);
2617
4076
  const database = options.yes ? true : answers.database ?? true;
@@ -2626,12 +4085,12 @@ async function initCommand(projectName, options = {}) {
2626
4085
  monorepo,
2627
4086
  apiPath: monorepo ? options.apiPath ?? answers.apiPath ?? "apps/api" : ""
2628
4087
  };
2629
- const targetDir = join$1(cwd, name$1);
4088
+ const targetDir = join(cwd, name$1);
2630
4089
  const template = getTemplate(templateOptions.template);
2631
4090
  const isMonorepo = templateOptions.monorepo;
2632
4091
  const apiPath = templateOptions.apiPath;
2633
4092
  await mkdir(targetDir, { recursive: true });
2634
- const appDir = isMonorepo ? join$1(targetDir, apiPath) : targetDir;
4093
+ const appDir = isMonorepo ? join(targetDir, apiPath) : targetDir;
2635
4094
  if (isMonorepo) await mkdir(appDir, { recursive: true });
2636
4095
  const appFiles = [
2637
4096
  ...generatePackageJson(templateOptions, template),
@@ -2642,13 +4101,13 @@ async function initCommand(projectName, options = {}) {
2642
4101
  ];
2643
4102
  const rootFiles = [...generateMonorepoFiles(templateOptions, template), ...generateModelsPackage(templateOptions)];
2644
4103
  for (const { path, content } of rootFiles) {
2645
- const fullPath = join$1(targetDir, path);
4104
+ const fullPath = join(targetDir, path);
2646
4105
  await mkdir(dirname(fullPath), { recursive: true });
2647
4106
  await writeFile(fullPath, content);
2648
4107
  }
2649
4108
  for (const { path, content } of appFiles) {
2650
- const fullPath = join$1(appDir, path);
2651
- const displayPath = isMonorepo ? `${apiPath}/${path}` : path;
4109
+ const fullPath = join(appDir, path);
4110
+ const _displayPath = isMonorepo ? `${apiPath}/${path}` : path;
2652
4111
  await mkdir(dirname(fullPath), { recursive: true });
2653
4112
  await writeFile(fullPath, content);
2654
4113
  }
@@ -2658,9 +4117,7 @@ async function initCommand(projectName, options = {}) {
2658
4117
  cwd: targetDir,
2659
4118
  stdio: "inherit"
2660
4119
  });
2661
- } catch {
2662
- console.error("\n Warning: Failed to install dependencies.");
2663
- }
4120
+ } catch {}
2664
4121
  try {
2665
4122
  execSync("npx @biomejs/biome format --write --unsafe .", {
2666
4123
  cwd: targetDir,
@@ -2668,7 +4125,310 @@ async function initCommand(projectName, options = {}) {
2668
4125
  });
2669
4126
  } catch {}
2670
4127
  }
2671
- const devCommand$1 = getRunCommand(pkgManager, "dev");
4128
+ const _devCommand = getRunCommand(pkgManager, "dev");
4129
+ }
4130
+
4131
+ //#endregion
4132
+ //#region src/secrets/generator.ts
4133
+ /**
4134
+ * Generate a secure random password using URL-safe base64 characters.
4135
+ * @param length Password length (default: 32)
4136
+ */
4137
+ function generateSecurePassword(length = 32) {
4138
+ return randomBytes(Math.ceil(length * 3 / 4)).toString("base64url").slice(0, length);
4139
+ }
4140
+ /** Default service configurations */
4141
+ const SERVICE_DEFAULTS = {
4142
+ postgres: {
4143
+ host: "postgres",
4144
+ port: 5432,
4145
+ username: "app",
4146
+ database: "app"
4147
+ },
4148
+ redis: {
4149
+ host: "redis",
4150
+ port: 6379,
4151
+ username: "default"
4152
+ },
4153
+ rabbitmq: {
4154
+ host: "rabbitmq",
4155
+ port: 5672,
4156
+ username: "app",
4157
+ vhost: "/"
4158
+ }
4159
+ };
4160
+ /**
4161
+ * Generate credentials for a specific service.
4162
+ */
4163
+ function generateServiceCredentials(service) {
4164
+ const defaults = SERVICE_DEFAULTS[service];
4165
+ return {
4166
+ ...defaults,
4167
+ password: generateSecurePassword()
4168
+ };
4169
+ }
4170
+ /**
4171
+ * Generate credentials for multiple services.
4172
+ */
4173
+ function generateServicesCredentials(services) {
4174
+ const result = {};
4175
+ for (const service of services) result[service] = generateServiceCredentials(service);
4176
+ return result;
4177
+ }
4178
+ /**
4179
+ * Generate connection URL for PostgreSQL.
4180
+ */
4181
+ function generatePostgresUrl(creds) {
4182
+ const { username, password, host, port, database } = creds;
4183
+ return `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
4184
+ }
4185
+ /**
4186
+ * Generate connection URL for Redis.
4187
+ */
4188
+ function generateRedisUrl(creds) {
4189
+ const { password, host, port } = creds;
4190
+ return `redis://:${encodeURIComponent(password)}@${host}:${port}`;
4191
+ }
4192
+ /**
4193
+ * Generate connection URL for RabbitMQ.
4194
+ */
4195
+ function generateRabbitmqUrl(creds) {
4196
+ const { username, password, host, port, vhost } = creds;
4197
+ const encodedVhost = encodeURIComponent(vhost ?? "/");
4198
+ return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;
4199
+ }
4200
+ /**
4201
+ * Generate connection URLs from service credentials.
4202
+ */
4203
+ function generateConnectionUrls(services) {
4204
+ const urls = {};
4205
+ if (services.postgres) urls.DATABASE_URL = generatePostgresUrl(services.postgres);
4206
+ if (services.redis) urls.REDIS_URL = generateRedisUrl(services.redis);
4207
+ if (services.rabbitmq) urls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);
4208
+ return urls;
4209
+ }
4210
+ /**
4211
+ * Create a new StageSecrets object with generated credentials.
4212
+ */
4213
+ function createStageSecrets(stage, services) {
4214
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4215
+ const serviceCredentials = generateServicesCredentials(services);
4216
+ const urls = generateConnectionUrls(serviceCredentials);
4217
+ return {
4218
+ stage,
4219
+ createdAt: now,
4220
+ updatedAt: now,
4221
+ services: serviceCredentials,
4222
+ urls,
4223
+ custom: {}
4224
+ };
4225
+ }
4226
+ /**
4227
+ * Rotate password for a specific service.
4228
+ */
4229
+ function rotateServicePassword(secrets, service) {
4230
+ const currentCreds = secrets.services[service];
4231
+ if (!currentCreds) throw new Error(`Service "${service}" not configured in secrets`);
4232
+ const newCreds = {
4233
+ ...currentCreds,
4234
+ password: generateSecurePassword()
4235
+ };
4236
+ const newServices = {
4237
+ ...secrets.services,
4238
+ [service]: newCreds
4239
+ };
4240
+ return {
4241
+ ...secrets,
4242
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4243
+ services: newServices,
4244
+ urls: generateConnectionUrls(newServices)
4245
+ };
4246
+ }
4247
+
4248
+ //#endregion
4249
+ //#region src/secrets/index.ts
4250
+ const logger = console;
4251
+ /**
4252
+ * Extract service names from compose config.
4253
+ */
4254
+ function getServicesFromConfig(services) {
4255
+ if (!services) return [];
4256
+ if (Array.isArray(services)) return services;
4257
+ return Object.entries(services).filter(([, config$1]) => config$1).map(([name$1]) => name$1);
4258
+ }
4259
+ /**
4260
+ * Initialize secrets for a stage.
4261
+ * Generates secure random passwords for configured services.
4262
+ */
4263
+ async function secretsInitCommand(options) {
4264
+ const { stage, force } = options;
4265
+ if (!force && secretsExist(stage)) {
4266
+ logger.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
4267
+ process.exit(1);
4268
+ }
4269
+ const config$1 = await loadConfig();
4270
+ const services = getServicesFromConfig(config$1.docker?.compose?.services);
4271
+ if (services.length === 0) logger.warn("No services configured in docker.compose.services. Creating secrets with empty services.");
4272
+ const secrets = createStageSecrets(stage, services);
4273
+ await writeStageSecrets(secrets);
4274
+ logger.log(`\n✓ Secrets initialized for stage "${stage}"`);
4275
+ logger.log(` Location: .gkm/secrets/${stage}.json`);
4276
+ logger.log("\n Generated credentials for:");
4277
+ for (const service of services) logger.log(` - ${service}`);
4278
+ if (secrets.urls.DATABASE_URL) logger.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
4279
+ if (secrets.urls.REDIS_URL) logger.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
4280
+ if (secrets.urls.RABBITMQ_URL) logger.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
4281
+ logger.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
4282
+ logger.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
4283
+ }
4284
+ /**
4285
+ * Read all data from stdin.
4286
+ */
4287
+ async function readStdin() {
4288
+ const chunks = [];
4289
+ for await (const chunk of process.stdin) chunks.push(chunk);
4290
+ return Buffer.concat(chunks).toString("utf-8").trim();
4291
+ }
4292
+ /**
4293
+ * Set a custom secret.
4294
+ * If value is not provided, reads from stdin.
4295
+ */
4296
+ async function secretsSetCommand(key, value, options) {
4297
+ const { stage } = options;
4298
+ let secretValue = value;
4299
+ if (!secretValue) {
4300
+ if (process.stdin.isTTY) {
4301
+ logger.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
4302
+ logger.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
4303
+ process.exit(1);
4304
+ }
4305
+ secretValue = await readStdin();
4306
+ if (!secretValue) {
4307
+ logger.error("No value received from stdin");
4308
+ process.exit(1);
4309
+ }
4310
+ }
4311
+ try {
4312
+ await setCustomSecret(stage, key, secretValue);
4313
+ logger.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
4314
+ } catch (error) {
4315
+ logger.error(error instanceof Error ? error.message : "Failed to set secret");
4316
+ process.exit(1);
4317
+ }
4318
+ }
4319
+ /**
4320
+ * Show secrets for a stage.
4321
+ */
4322
+ async function secretsShowCommand(options) {
4323
+ const { stage, reveal } = options;
4324
+ const secrets = await readStageSecrets(stage);
4325
+ if (!secrets) {
4326
+ logger.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
4327
+ process.exit(1);
4328
+ }
4329
+ logger.log(`\nSecrets for stage "${stage}":`);
4330
+ logger.log(` Created: ${secrets.createdAt}`);
4331
+ logger.log(` Updated: ${secrets.updatedAt}`);
4332
+ logger.log("\nService Credentials:");
4333
+ for (const [service, creds] of Object.entries(secrets.services)) if (creds) {
4334
+ logger.log(`\n ${service}:`);
4335
+ logger.log(` host: ${creds.host}`);
4336
+ logger.log(` port: ${creds.port}`);
4337
+ logger.log(` username: ${creds.username}`);
4338
+ logger.log(` password: ${reveal ? creds.password : maskPassword(creds.password)}`);
4339
+ if (creds.database) logger.log(` database: ${creds.database}`);
4340
+ if (creds.vhost) logger.log(` vhost: ${creds.vhost}`);
4341
+ }
4342
+ logger.log("\nConnection URLs:");
4343
+ if (secrets.urls.DATABASE_URL) logger.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
4344
+ if (secrets.urls.REDIS_URL) logger.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
4345
+ if (secrets.urls.RABBITMQ_URL) logger.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
4346
+ const customKeys = Object.keys(secrets.custom);
4347
+ if (customKeys.length > 0) {
4348
+ logger.log("\nCustom Secrets:");
4349
+ for (const [key, value] of Object.entries(secrets.custom)) logger.log(` ${key}: ${reveal ? value : maskPassword(value)}`);
4350
+ }
4351
+ if (!reveal) logger.log("\nUse --reveal to show actual values");
4352
+ }
4353
+ /**
4354
+ * Rotate passwords for services.
4355
+ */
4356
+ async function secretsRotateCommand(options) {
4357
+ const { stage, service } = options;
4358
+ const secrets = await readStageSecrets(stage);
4359
+ if (!secrets) {
4360
+ logger.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
4361
+ process.exit(1);
4362
+ }
4363
+ if (service) {
4364
+ if (!secrets.services[service]) {
4365
+ logger.error(`Service "${service}" not configured in stage "${stage}"`);
4366
+ process.exit(1);
4367
+ }
4368
+ const updated = rotateServicePassword(secrets, service);
4369
+ await writeStageSecrets(updated);
4370
+ logger.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
4371
+ } else {
4372
+ let updated = secrets;
4373
+ const services = Object.keys(secrets.services);
4374
+ for (const svc of services) updated = rotateServicePassword(updated, svc);
4375
+ await writeStageSecrets(updated);
4376
+ logger.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
4377
+ }
4378
+ logger.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
4379
+ }
4380
+ /**
4381
+ * Import secrets from a JSON file.
4382
+ */
4383
+ async function secretsImportCommand(file, options) {
4384
+ const { stage, merge = true } = options;
4385
+ if (!existsSync(file)) {
4386
+ logger.error(`File not found: ${file}`);
4387
+ process.exit(1);
4388
+ }
4389
+ let importedSecrets;
4390
+ try {
4391
+ const content = await readFile(file, "utf-8");
4392
+ importedSecrets = JSON.parse(content);
4393
+ if (typeof importedSecrets !== "object" || importedSecrets === null) throw new Error("JSON must be an object");
4394
+ for (const [key, value] of Object.entries(importedSecrets)) if (typeof value !== "string") throw new Error(`Value for "${key}" must be a string, got ${typeof value}`);
4395
+ } catch (error) {
4396
+ logger.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
4397
+ process.exit(1);
4398
+ }
4399
+ const secrets = await readStageSecrets(stage);
4400
+ if (!secrets) {
4401
+ logger.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
4402
+ process.exit(1);
4403
+ }
4404
+ const updatedCustom = merge ? {
4405
+ ...secrets.custom,
4406
+ ...importedSecrets
4407
+ } : importedSecrets;
4408
+ const updated = {
4409
+ ...secrets,
4410
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4411
+ custom: updatedCustom
4412
+ };
4413
+ await writeStageSecrets(updated);
4414
+ const importedCount = Object.keys(importedSecrets).length;
4415
+ const totalCount = Object.keys(updatedCustom).length;
4416
+ logger.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
4417
+ if (merge && totalCount > importedCount) logger.log(` Total custom secrets: ${totalCount}`);
4418
+ logger.log("\n Imported keys:");
4419
+ for (const key of Object.keys(importedSecrets)) logger.log(` - ${key}`);
4420
+ }
4421
+ /**
4422
+ * Mask password in a URL for display.
4423
+ */
4424
+ function maskUrl(url) {
4425
+ try {
4426
+ const parsed = new URL(url);
4427
+ if (parsed.password) parsed.password = maskPassword(parsed.password);
4428
+ return parsed.toString();
4429
+ } catch {
4430
+ return url;
4431
+ }
2672
4432
  }
2673
4433
 
2674
4434
  //#endregion
@@ -2680,34 +4440,39 @@ program.command("init").description("Scaffold a new project").argument("[name]",
2680
4440
  const globalOptions = program.opts();
2681
4441
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
2682
4442
  await initCommand(name$1, options);
2683
- } catch (error) {
2684
- console.error("Init failed:", error.message);
4443
+ } catch (_error) {
2685
4444
  process.exit(1);
2686
4445
  }
2687
4446
  });
2688
- program.command("build").description("Build handlers from endpoints, functions, and crons").option("--provider <provider>", "Target provider for generated handlers (aws, server)").option("--providers <providers>", "[DEPRECATED] Use --provider instead. Target providers for generated handlers (comma-separated)").option("--enable-openapi", "Enable OpenAPI documentation generation for server builds").action(async (options) => {
4447
+ program.command("build").description("Build handlers from endpoints, functions, and crons").option("--provider <provider>", "Target provider for generated handlers (aws, server)").option("--providers <providers>", "[DEPRECATED] Use --provider instead. Target providers for generated handlers (comma-separated)").option("--enable-openapi", "Enable OpenAPI documentation generation for server builds").option("--production", "Build for production (no dev tools, bundled output)").option("--skip-bundle", "Skip bundling step in production build").option("--stage <stage>", "Inject encrypted secrets for deployment stage").action(async (options) => {
2689
4448
  try {
2690
4449
  const globalOptions = program.opts();
2691
4450
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
2692
4451
  if (options.provider) {
2693
- if (!["aws", "server"].includes(options.provider)) {
2694
- console.error(`Invalid provider: ${options.provider}. Must be 'aws' or 'server'.`);
2695
- process.exit(1);
2696
- }
4452
+ if (!["aws", "server"].includes(options.provider)) process.exit(1);
2697
4453
  await buildCommand({
2698
4454
  provider: options.provider,
2699
- enableOpenApi: options.enableOpenapi || false
4455
+ enableOpenApi: options.enableOpenapi || false,
4456
+ production: options.production || false,
4457
+ skipBundle: options.skipBundle || false,
4458
+ stage: options.stage
2700
4459
  });
2701
4460
  } else if (options.providers) {
2702
- console.warn("⚠️ --providers flag is deprecated. Use --provider instead.");
2703
4461
  const providerList = [...new Set(options.providers.split(",").map((p) => p.trim()))];
2704
4462
  await buildCommand({
2705
4463
  providers: providerList,
2706
- enableOpenApi: options.enableOpenapi || false
4464
+ enableOpenApi: options.enableOpenapi || false,
4465
+ production: options.production || false,
4466
+ skipBundle: options.skipBundle || false,
4467
+ stage: options.stage
2707
4468
  });
2708
- } else await buildCommand({ enableOpenApi: options.enableOpenapi || false });
2709
- } catch (error) {
2710
- console.error("Build failed:", error.message);
4469
+ } else await buildCommand({
4470
+ enableOpenApi: options.enableOpenapi || false,
4471
+ production: options.production || false,
4472
+ skipBundle: options.skipBundle || false,
4473
+ stage: options.stage
4474
+ });
4475
+ } catch (_error) {
2711
4476
  process.exit(1);
2712
4477
  }
2713
4478
  });
@@ -2716,12 +4481,11 @@ program.command("dev").description("Start development server with automatic relo
2716
4481
  const globalOptions = program.opts();
2717
4482
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
2718
4483
  await devCommand({
2719
- port: options.port ? Number.parseInt(options.port) : 3e3,
4484
+ port: options.port ? Number.parseInt(options.port, 10) : 3e3,
2720
4485
  portExplicit: !!options.port,
2721
4486
  enableOpenApi: options.enableOpenapi ?? true
2722
4487
  });
2723
- } catch (error) {
2724
- console.error("Dev server failed:", error.message);
4488
+ } catch (_error) {
2725
4489
  process.exit(1);
2726
4490
  }
2727
4491
  });
@@ -2745,8 +4509,7 @@ program.command("openapi").description("Generate OpenAPI specification from endp
2745
4509
  const globalOptions = program.opts();
2746
4510
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
2747
4511
  await openapiCommand({});
2748
- } catch (error) {
2749
- console.error("OpenAPI generation failed:", error.message);
4512
+ } catch (_error) {
2750
4513
  process.exit(1);
2751
4514
  }
2752
4515
  });
@@ -2755,8 +4518,194 @@ program.command("generate:react-query").description("Generate React Query hooks
2755
4518
  const globalOptions = program.opts();
2756
4519
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
2757
4520
  await generateReactQueryCommand(options);
4521
+ } catch (_error) {
4522
+ process.exit(1);
4523
+ }
4524
+ });
4525
+ program.command("docker").description("Generate Docker deployment files").option("--build", "Build Docker image after generating files").option("--push", "Push image to registry after building").option("--tag <tag>", "Image tag", "latest").option("--registry <registry>", "Container registry URL").option("--slim", "Use slim Dockerfile (assumes pre-built bundle exists)").option("--turbo", "Use turbo prune for monorepo optimization").option("--turbo-package <name>", "Package name for turbo prune").action(async (options) => {
4526
+ try {
4527
+ const globalOptions = program.opts();
4528
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4529
+ await dockerCommand(options);
4530
+ } catch (_error) {
4531
+ process.exit(1);
4532
+ }
4533
+ });
4534
+ program.command("prepack").description("Generate Docker files for production deployment").option("--build", "Build Docker image after generating files").option("--push", "Push image to registry after building").option("--tag <tag>", "Image tag", "latest").option("--registry <registry>", "Container registry URL").option("--slim", "Build locally first, then use slim Dockerfile").option("--skip-bundle", "Skip bundling step (only with --slim)").option("--turbo", "Use turbo prune for monorepo optimization").option("--turbo-package <name>", "Package name for turbo prune").action(async (options) => {
4535
+ try {
4536
+ const globalOptions = program.opts();
4537
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4538
+ if (options.slim) await buildCommand({
4539
+ provider: "server",
4540
+ production: true,
4541
+ skipBundle: options.skipBundle
4542
+ });
4543
+ await dockerCommand({
4544
+ build: options.build,
4545
+ push: options.push,
4546
+ tag: options.tag,
4547
+ registry: options.registry,
4548
+ slim: options.slim,
4549
+ turbo: options.turbo,
4550
+ turboPackage: options.turboPackage
4551
+ });
4552
+ if (options.slim) {}
4553
+ if (options.build) {
4554
+ const tag = options.tag ?? "latest";
4555
+ const registry = options.registry;
4556
+ const _imageRef = registry ? `${registry}/api:${tag}` : `api:${tag}`;
4557
+ }
4558
+ } catch (_error) {
4559
+ process.exit(1);
4560
+ }
4561
+ });
4562
+ program.command("secrets:init").description("Initialize secrets for a deployment stage").requiredOption("--stage <stage>", "Stage name (e.g., production, staging)").option("--force", "Overwrite existing secrets").action(async (options) => {
4563
+ try {
4564
+ const globalOptions = program.opts();
4565
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4566
+ await secretsInitCommand(options);
4567
+ } catch (_error) {
4568
+ process.exit(1);
4569
+ }
4570
+ });
4571
+ program.command("secrets:set").description("Set a custom secret for a stage").argument("<key>", "Secret key (e.g., API_KEY)").argument("[value]", "Secret value (reads from stdin if omitted)").requiredOption("--stage <stage>", "Stage name").action(async (key, value, options) => {
4572
+ try {
4573
+ const globalOptions = program.opts();
4574
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4575
+ await secretsSetCommand(key, value, options);
4576
+ } catch (_error) {
4577
+ process.exit(1);
4578
+ }
4579
+ });
4580
+ program.command("secrets:show").description("Show secrets for a stage").requiredOption("--stage <stage>", "Stage name").option("--reveal", "Show actual secret values (not masked)").action(async (options) => {
4581
+ try {
4582
+ const globalOptions = program.opts();
4583
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4584
+ await secretsShowCommand(options);
4585
+ } catch (_error) {
4586
+ process.exit(1);
4587
+ }
4588
+ });
4589
+ program.command("secrets:rotate").description("Rotate service passwords").requiredOption("--stage <stage>", "Stage name").option("--service <service>", "Specific service to rotate (postgres, redis, rabbitmq)").action(async (options) => {
4590
+ try {
4591
+ const globalOptions = program.opts();
4592
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4593
+ await secretsRotateCommand(options);
4594
+ } catch (_error) {
4595
+ process.exit(1);
4596
+ }
4597
+ });
4598
+ program.command("secrets:import").description("Import secrets from a JSON file").argument("<file>", "JSON file path (e.g., secrets.json)").requiredOption("--stage <stage>", "Stage name").option("--no-merge", "Replace all custom secrets instead of merging").action(async (file, options) => {
4599
+ try {
4600
+ const globalOptions = program.opts();
4601
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4602
+ await secretsImportCommand(file, options);
4603
+ } catch (_error) {
4604
+ process.exit(1);
4605
+ }
4606
+ });
4607
+ 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) => {
4608
+ try {
4609
+ const globalOptions = program.opts();
4610
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4611
+ const validProviders = [
4612
+ "docker",
4613
+ "dokploy",
4614
+ "aws-lambda"
4615
+ ];
4616
+ if (!validProviders.includes(options.provider)) {
4617
+ console.error(`Invalid provider: ${options.provider}\nValid providers: ${validProviders.join(", ")}`);
4618
+ process.exit(1);
4619
+ }
4620
+ await deployCommand({
4621
+ provider: options.provider,
4622
+ stage: options.stage,
4623
+ tag: options.tag,
4624
+ skipPush: options.skipPush,
4625
+ skipBuild: options.skipBuild
4626
+ });
4627
+ } catch (_error) {
4628
+ process.exit(1);
4629
+ }
4630
+ });
4631
+ program.command("deploy:init").description("Initialize Dokploy deployment (create project and application)").option("--endpoint <url>", "Dokploy server URL (uses stored credentials if logged in)").requiredOption("--project <name>", "Project name (creates if not exists)").requiredOption("--app <name>", "Application name").option("--project-id <id>", "Use existing project ID instead of creating").option("--registry-id <id>", "Configure registry for the application").action(async (options) => {
4632
+ try {
4633
+ const globalOptions = program.opts();
4634
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4635
+ await deployInitCommand({
4636
+ endpoint: options.endpoint,
4637
+ projectName: options.project,
4638
+ appName: options.app,
4639
+ projectId: options.projectId,
4640
+ registryId: options.registryId
4641
+ });
4642
+ } catch (error) {
4643
+ console.error(error instanceof Error ? error.message : "Failed to initialize deployment");
4644
+ process.exit(1);
4645
+ }
4646
+ });
4647
+ program.command("deploy:list").description("List Dokploy resources (projects, registries)").option("--endpoint <url>", "Dokploy server URL (uses stored credentials if logged in)").option("--projects", "List projects").option("--registries", "List registries").action(async (options) => {
4648
+ try {
4649
+ const globalOptions = program.opts();
4650
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4651
+ if (options.projects) await deployListCommand({
4652
+ endpoint: options.endpoint,
4653
+ resource: "projects"
4654
+ });
4655
+ if (options.registries) await deployListCommand({
4656
+ endpoint: options.endpoint,
4657
+ resource: "registries"
4658
+ });
4659
+ if (!options.projects && !options.registries) {
4660
+ await deployListCommand({
4661
+ endpoint: options.endpoint,
4662
+ resource: "projects"
4663
+ });
4664
+ await deployListCommand({
4665
+ endpoint: options.endpoint,
4666
+ resource: "registries"
4667
+ });
4668
+ }
4669
+ } catch (error) {
4670
+ console.error(error instanceof Error ? error.message : "Failed to list resources");
4671
+ process.exit(1);
4672
+ }
4673
+ });
4674
+ program.command("login").description("Authenticate with a deployment service").option("--service <service>", "Service to login to (dokploy)", "dokploy").option("--token <token>", "API token (will prompt if not provided)").option("--endpoint <url>", "Service endpoint URL").action(async (options) => {
4675
+ try {
4676
+ const globalOptions = program.opts();
4677
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4678
+ if (options.service !== "dokploy") {
4679
+ console.error(`Unknown service: ${options.service}. Supported: dokploy`);
4680
+ process.exit(1);
4681
+ }
4682
+ await loginCommand({
4683
+ service: options.service,
4684
+ token: options.token,
4685
+ endpoint: options.endpoint
4686
+ });
4687
+ } catch (error) {
4688
+ console.error(error instanceof Error ? error.message : "Failed to login");
4689
+ process.exit(1);
4690
+ }
4691
+ });
4692
+ program.command("logout").description("Remove stored credentials").option("--service <service>", "Service to logout from (dokploy, all)", "dokploy").action(async (options) => {
4693
+ try {
4694
+ const globalOptions = program.opts();
4695
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4696
+ await logoutCommand({ service: options.service });
4697
+ } catch (error) {
4698
+ console.error(error instanceof Error ? error.message : "Failed to logout");
4699
+ process.exit(1);
4700
+ }
4701
+ });
4702
+ program.command("whoami").description("Show current authentication status").action(async () => {
4703
+ try {
4704
+ const globalOptions = program.opts();
4705
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
4706
+ await whoamiCommand();
2758
4707
  } catch (error) {
2759
- console.error("React Query generation failed:", error.message);
4708
+ console.error(error instanceof Error ? error.message : "Failed to get status");
2760
4709
  process.exit(1);
2761
4710
  }
2762
4711
  });