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