@geekmidas/cli 10.0.0-alpha.45 → 10.0.0-alpha.47

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 (88) hide show
  1. package/README.md +144 -183
  2. package/dist/{OpenApiTsGenerator-B4Kj_qNU.cjs → OpenApiTsGenerator-BN0PIZxd.cjs} +17 -71
  3. package/dist/OpenApiTsGenerator-BN0PIZxd.cjs.map +1 -0
  4. package/dist/{OpenApiTsGenerator-CEh3EKN3.mjs → OpenApiTsGenerator-BzIGy8KE.mjs} +17 -71
  5. package/dist/OpenApiTsGenerator-BzIGy8KE.mjs.map +1 -0
  6. package/dist/backends-B9V2XD2M.cjs +82 -0
  7. package/dist/backends-B9V2XD2M.cjs.map +1 -0
  8. package/dist/backends-CSuLl0YP.mjs +59 -0
  9. package/dist/backends-CSuLl0YP.mjs.map +1 -0
  10. package/dist/{config-CJ-PAb7M.cjs → config-CsYSyUya.cjs} +3 -3
  11. package/dist/{config-CJ-PAb7M.cjs.map → config-CsYSyUya.cjs.map} +1 -1
  12. package/dist/{config-5watB1D0.mjs → config-D8J5rrTb.mjs} +3 -3
  13. package/dist/{config-5watB1D0.mjs.map → config-D8J5rrTb.mjs.map} +1 -1
  14. package/dist/config.cjs +2 -2
  15. package/dist/config.d.cts +2 -2
  16. package/dist/config.d.mts +2 -2
  17. package/dist/config.mjs +2 -2
  18. package/dist/{discover-BLhf7V_J.mjs → discover-Cf2jAcgF.mjs} +4 -56
  19. package/dist/discover-Cf2jAcgF.mjs.map +1 -0
  20. package/dist/{discover-B0f_kuNN.cjs → discover-DXNviAh-.cjs} +3 -79
  21. package/dist/discover-DXNviAh-.cjs.map +1 -0
  22. package/dist/{emit-Bzq7JsMP.mjs → emit-C1e8qD08.mjs} +12 -43
  23. package/dist/emit-C1e8qD08.mjs.map +1 -0
  24. package/dist/{emit-BdoPFjZf.cjs → emit-Dmc1nvdS.cjs} +11 -54
  25. package/dist/emit-Dmc1nvdS.cjs.map +1 -0
  26. package/dist/encryption-C7OGMve_.cjs +91 -0
  27. package/dist/encryption-C7OGMve_.cjs.map +1 -0
  28. package/dist/encryption-DyQnPsZN.mjs +90 -0
  29. package/dist/encryption-DyQnPsZN.mjs.map +1 -0
  30. package/dist/{github-D5MthUlR.mjs → github-BfpadQWQ.mjs} +3 -3
  31. package/dist/{github-D5MthUlR.mjs.map → github-BfpadQWQ.mjs.map} +1 -1
  32. package/dist/{github-BAr8WGkR.cjs → github-BrI1tv6u.cjs} +3 -3
  33. package/dist/{github-BAr8WGkR.cjs.map → github-BrI1tv6u.cjs.map} +1 -1
  34. package/dist/{index-B3PeGOQp.d.cts → index-BQhJXpaQ.d.mts} +2 -2
  35. package/dist/{index-B3PeGOQp.d.cts.map → index-BQhJXpaQ.d.mts.map} +1 -1
  36. package/dist/{index-BOyaGcCm.d.mts → index-DHJePE0y.d.cts} +2 -2
  37. package/dist/{index-BOyaGcCm.d.mts.map → index-DHJePE0y.d.cts.map} +1 -1
  38. package/dist/index.cjs +444 -670
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.mjs +442 -668
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/{migrate-PJi4b1ga.cjs → migrate-BgEY9pdP.cjs} +5 -5
  43. package/dist/{migrate-PJi4b1ga.cjs.map → migrate-BgEY9pdP.cjs.map} +1 -1
  44. package/dist/{migrate-BRklCm7b.mjs → migrate-DhxTIA0z.mjs} +5 -5
  45. package/dist/{migrate-BRklCm7b.mjs.map → migrate-DhxTIA0z.mjs.map} +1 -1
  46. package/dist/openapi.cjs +2 -2
  47. package/dist/openapi.d.cts +1 -1
  48. package/dist/openapi.d.mts +1 -1
  49. package/dist/openapi.mjs +2 -2
  50. package/dist/{ready-ZqOuNvKD.mjs → ready-WfT-eWlm.mjs} +50 -8
  51. package/dist/ready-WfT-eWlm.mjs.map +1 -0
  52. package/dist/{ready-3RfHxdA0.cjs → ready-lr2Fm-ep.cjs} +68 -8
  53. package/dist/ready-lr2Fm-ep.cjs.map +1 -0
  54. package/dist/reconcile/public.cjs +2 -4
  55. package/dist/reconcile/public.d.cts +6 -10
  56. package/dist/reconcile/public.d.cts.map +1 -1
  57. package/dist/reconcile/public.d.mts +6 -10
  58. package/dist/reconcile/public.d.mts.map +1 -1
  59. package/dist/reconcile/public.mjs +3 -3
  60. package/dist/{types-0R124hrY.d.mts → types-BbrNu83u.d.mts} +14 -109
  61. package/dist/{types-0R124hrY.d.mts.map → types-BbrNu83u.d.mts.map} +1 -1
  62. package/dist/{types-DfdYe31u.d.cts → types-DNqEsctD.d.cts} +14 -109
  63. package/dist/{types-DfdYe31u.d.cts.map → types-DNqEsctD.d.cts.map} +1 -1
  64. package/dist/vitest.cjs +1 -1
  65. package/dist/vitest.mjs +1 -1
  66. package/dist/workspace/index.cjs +1 -1
  67. package/dist/workspace/index.d.cts +2 -2
  68. package/dist/workspace/index.d.mts +2 -2
  69. package/dist/workspace/index.mjs +1 -1
  70. package/dist/{workspace-RYSYtHP7.cjs → workspace-DEibcoBe.cjs} +11 -25
  71. package/dist/{workspace-RYSYtHP7.cjs.map → workspace-DEibcoBe.cjs.map} +1 -1
  72. package/dist/{workspace-DOmIhiau.mjs → workspace-DNzWgM1J.mjs} +11 -25
  73. package/dist/{workspace-DOmIhiau.mjs.map → workspace-DNzWgM1J.mjs.map} +1 -1
  74. package/dist/{workspace-BKytVCzb.mjs → workspace-cb6U29EL.mjs} +59 -81
  75. package/dist/workspace-cb6U29EL.mjs.map +1 -0
  76. package/dist/{workspace-dV6f-3vv.cjs → workspace-vafrzZTC.cjs} +65 -105
  77. package/dist/workspace-vafrzZTC.cjs.map +1 -0
  78. package/package.json +14 -14
  79. package/dist/OpenApiTsGenerator-B4Kj_qNU.cjs.map +0 -1
  80. package/dist/OpenApiTsGenerator-CEh3EKN3.mjs.map +0 -1
  81. package/dist/discover-B0f_kuNN.cjs.map +0 -1
  82. package/dist/discover-BLhf7V_J.mjs.map +0 -1
  83. package/dist/emit-BdoPFjZf.cjs.map +0 -1
  84. package/dist/emit-Bzq7JsMP.mjs.map +0 -1
  85. package/dist/ready-3RfHxdA0.cjs.map +0 -1
  86. package/dist/ready-ZqOuNvKD.mjs.map +0 -1
  87. package/dist/workspace-BKytVCzb.mjs.map +0 -1
  88. package/dist/workspace-dV6f-3vv.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
4
- const require_discover = require("./discover-B0f_kuNN.cjs");
5
- const require_config = require("./config-CJ-PAb7M.cjs");
6
- const require_workspace = require("./workspace-RYSYtHP7.cjs");
4
+ const require_discover = require("./discover-DXNviAh-.cjs");
5
+ const require_config = require("./config-CsYSyUya.cjs");
6
+ const require_workspace = require("./workspace-DEibcoBe.cjs");
7
7
  const require_storage = require("./storage-CSGoC3bK.cjs");
8
8
  const require_store = require("./store-DQgxT_WR.cjs");
9
- const require_workspace$1 = require("./workspace-dV6f-3vv.cjs");
10
- const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-B4Kj_qNU.cjs");
9
+ const require_workspace$1 = require("./workspace-vafrzZTC.cjs");
10
+ const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-BN0PIZxd.cjs");
11
11
  const require_openapi = require("./openapi.cjs");
12
- const require_ready = require("./ready-3RfHxdA0.cjs");
13
- const require_emit = require("./emit-BdoPFjZf.cjs");
12
+ const require_ready = require("./ready-lr2Fm-ep.cjs");
13
+ const require_backends = require("./backends-B9V2XD2M.cjs");
14
+ const require_emit = require("./emit-Dmc1nvdS.cjs");
14
15
  let commander = require("commander");
15
16
  let node_process = require("node:process");
16
17
  let node_readline_promises = require("node:readline/promises");
@@ -28,6 +29,7 @@ let chokidar = require("chokidar");
28
29
  chokidar = require_rolldown_runtime.__toESM(chokidar, 1);
29
30
  let node_crypto = require("node:crypto");
30
31
  let node_url = require("node:url");
32
+ let _geekmidas_constructs_encryption = require("@geekmidas/constructs/encryption");
31
33
  let _geekmidas_db_pg_roles = require("@geekmidas/db/pg/roles");
32
34
  let pg = require("pg");
33
35
  let yaml = require("yaml");
@@ -46,7 +48,7 @@ let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
46
48
  let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
47
49
  let node_dns_promises = require("node:dns/promises");
48
50
  //#region package.json
49
- var version = "10.0.0-alpha.45";
51
+ var version = "10.0.0-alpha.47";
50
52
  //#endregion
51
53
  //#region src/auth/credentials.ts
52
54
  /**
@@ -180,7 +182,7 @@ async function getHostingerToken(options) {
180
182
  }
181
183
  //#endregion
182
184
  //#region src/auth/index.ts
183
- const logger$13 = console;
185
+ const logger$12 = console;
184
186
  /**
185
187
  * Validate Dokploy token by making a test API call
186
188
  */
@@ -247,52 +249,52 @@ async function prompt$1(message, hidden = false) {
247
249
  async function loginCommand(options) {
248
250
  const { provider, token: providedToken, endpoint: providedEndpoint } = options;
249
251
  if (provider === "dokploy") {
250
- logger$13.log("\n🔐 Logging in to Dokploy...\n");
252
+ logger$12.log("\n🔐 Logging in to Dokploy...\n");
251
253
  let endpoint = providedEndpoint;
252
254
  if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
253
255
  endpoint = endpoint.replace(/\/$/, "");
254
256
  try {
255
257
  new URL(endpoint);
256
258
  } catch {
257
- logger$13.error("Invalid URL format");
259
+ logger$12.error("Invalid URL format");
258
260
  process.exit(1);
259
261
  }
260
262
  let token = providedToken;
261
263
  if (!token) {
262
- logger$13.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
264
+ logger$12.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
263
265
  token = await prompt$1("API Token: ", true);
264
266
  }
265
267
  if (!token) {
266
- logger$13.error("Token is required");
268
+ logger$12.error("Token is required");
267
269
  process.exit(1);
268
270
  }
269
- logger$13.log("\nValidating credentials...");
271
+ logger$12.log("\nValidating credentials...");
270
272
  if (!await validateDokployToken(endpoint, token)) {
271
- logger$13.error("\n✗ Invalid credentials. Please check your token and try again.");
273
+ logger$12.error("\n✗ Invalid credentials. Please check your token and try again.");
272
274
  process.exit(1);
273
275
  }
274
276
  await storeDokployCredentials(token, endpoint);
275
- logger$13.log("\n✓ Successfully logged in to Dokploy!");
276
- logger$13.log(` Endpoint: ${endpoint}`);
277
- logger$13.log(` Credentials stored in: ${getCredentialsPath()}`);
278
- logger$13.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
277
+ logger$12.log("\n✓ Successfully logged in to Dokploy!");
278
+ logger$12.log(` Endpoint: ${endpoint}`);
279
+ logger$12.log(` Credentials stored in: ${getCredentialsPath()}`);
280
+ logger$12.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
279
281
  }
280
282
  if (provider === "hostinger") {
281
- logger$13.log("\n🔐 Logging in to Hostinger...\n");
283
+ logger$12.log("\n🔐 Logging in to Hostinger...\n");
282
284
  let token = providedToken;
283
285
  if (!token) {
284
- logger$13.log("\nGenerate a token at: https://hpanel.hostinger.com/profile/api\n");
286
+ logger$12.log("\nGenerate a token at: https://hpanel.hostinger.com/profile/api\n");
285
287
  token = await prompt$1("API Token: ", true);
286
288
  }
287
289
  if (!token) {
288
- logger$13.error("Token is required");
290
+ logger$12.error("Token is required");
289
291
  process.exit(1);
290
292
  }
291
293
  await storeHostingerToken(token);
292
- logger$13.log("\n✓ Hostinger token stored.");
293
- logger$13.log(` Credentials stored in: ${getCredentialsPath()}`);
294
- logger$13.log(" Not validated here: every DNS endpoint is scoped to a domain, so the\n first deploy that touches DNS is where a bad token reports itself.");
295
- logger$13.log("\nYou can now use it as a DNS provider without setting HOSTINGER_API_TOKEN.");
294
+ logger$12.log("\n✓ Hostinger token stored.");
295
+ logger$12.log(` Credentials stored in: ${getCredentialsPath()}`);
296
+ logger$12.log(" Not validated here: every DNS endpoint is scoped to a domain, so the\n first deploy that touches DNS is where a bad token reports itself.");
297
+ logger$12.log("\nYou can now use it as a DNS provider without setting HOSTINGER_API_TOKEN.");
296
298
  }
297
299
  }
298
300
  /**
@@ -301,27 +303,27 @@ async function loginCommand(options) {
301
303
  async function logoutCommand(options) {
302
304
  const { provider = "dokploy" } = options;
303
305
  if (provider === "all") {
304
- if (await removeDokployCredentials()) logger$13.log("\n✓ Logged out from all services");
305
- else logger$13.log("\nNo stored credentials found");
306
+ if (await removeDokployCredentials()) logger$12.log("\n✓ Logged out from all services");
307
+ else logger$12.log("\nNo stored credentials found");
306
308
  return;
307
309
  }
308
310
  if (provider === "dokploy") {
309
- if (await removeDokployCredentials()) logger$13.log("\n✓ Logged out from Dokploy");
310
- else logger$13.log("\nNo Dokploy credentials found");
311
+ if (await removeDokployCredentials()) logger$12.log("\n✓ Logged out from Dokploy");
312
+ else logger$12.log("\nNo Dokploy credentials found");
311
313
  }
312
314
  }
313
315
  /**
314
316
  * Show current login status
315
317
  */
316
318
  async function whoamiCommand() {
317
- logger$13.log("\n📋 Current credentials:\n");
319
+ logger$12.log("\n📋 Current credentials:\n");
318
320
  const dokploy = await getDokployCredentials();
319
321
  if (dokploy) {
320
- logger$13.log(" Dokploy:");
321
- logger$13.log(` Endpoint: ${dokploy.endpoint}`);
322
- logger$13.log(` Token: ${maskToken(dokploy.token)}`);
323
- } else logger$13.log(" Dokploy: Not logged in");
324
- logger$13.log(`\n Credentials file: ${getCredentialsPath()}`);
322
+ logger$12.log(" Dokploy:");
323
+ logger$12.log(` Endpoint: ${dokploy.endpoint}`);
324
+ logger$12.log(` Token: ${maskToken(dokploy.token)}`);
325
+ } else logger$12.log(" Dokploy: Not logged in");
326
+ logger$12.log(`\n Credentials file: ${getCredentialsPath()}`);
325
327
  }
326
328
  /**
327
329
  * Mask a token for display
@@ -331,85 +333,14 @@ function maskToken(token) {
331
333
  return `${token.slice(0, 4)}...${token.slice(-4)}`;
332
334
  }
333
335
  //#endregion
334
- //#region src/build/providerResolver.ts
335
- /**
336
- * Resolves provider configuration from the new simplified system
337
- * to the internal legacy format for backward compatibility
338
- */
339
- function resolveProviders(config, options) {
340
- const providers = [];
341
- let enableOpenApi = options.enableOpenApi || false;
342
- if (options.providers) return {
343
- providers: options.providers,
344
- enableOpenApi
345
- };
346
- if (options.provider) {
347
- const resolvedProviders = resolveMainProvider(options.provider, config.providers);
348
- providers.push(...resolvedProviders.providers);
349
- enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
350
- } else if (config.providers) {
351
- const resolvedProviders = resolveAllConfiguredProviders(config.providers);
352
- providers.push(...resolvedProviders.providers);
353
- enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
354
- } else providers.push("aws-apigatewayv2", "aws-lambda");
355
- return {
356
- providers: [...new Set(providers)],
357
- enableOpenApi
358
- };
359
- }
360
- function resolveMainProvider(mainProvider, providersConfig) {
361
- const providers = [];
362
- let enableOpenApi = false;
363
- if (mainProvider === "aws") {
364
- const awsConfig = providersConfig?.aws;
365
- if (awsConfig?.apiGateway) {
366
- if (isEnabled(awsConfig.apiGateway.v1)) providers.push("aws-apigatewayv1");
367
- if (isEnabled(awsConfig.apiGateway.v2)) providers.push("aws-apigatewayv2");
368
- } else providers.push("aws-apigatewayv2");
369
- if (awsConfig?.lambda) {
370
- if (isEnabled(awsConfig.lambda.functions) || isEnabled(awsConfig.lambda.crons)) providers.push("aws-lambda");
371
- } else providers.push("aws-lambda");
372
- } else if (mainProvider === "server") {
373
- providers.push("server");
374
- const serverConfig = providersConfig?.server;
375
- if (typeof serverConfig === "object" && serverConfig?.enableOpenApi) enableOpenApi = true;
376
- }
377
- return {
378
- providers,
379
- enableOpenApi
380
- };
381
- }
382
- function resolveAllConfiguredProviders(providersConfig) {
383
- const providers = [];
384
- let enableOpenApi = false;
385
- if (providersConfig.aws) {
386
- const awsProviders = resolveMainProvider("aws", providersConfig);
387
- providers.push(...awsProviders.providers);
388
- }
389
- if (providersConfig.server && isEnabled(providersConfig.server)) {
390
- providers.push("server");
391
- if (typeof providersConfig.server === "object" && providersConfig.server.enableOpenApi) enableOpenApi = true;
392
- }
393
- return {
394
- providers,
395
- enableOpenApi
396
- };
397
- }
398
- function isEnabled(config) {
399
- if (config === void 0) return false;
400
- if (typeof config === "boolean") return config;
401
- return config.enabled !== false;
402
- }
403
- //#endregion
404
336
  //#region src/workspace/client-generator.ts
405
337
  /**
406
338
  * Normalize routes to an array of patterns.
407
- * Handles string, string[], and PartitionedRoutes (extracts paths).
339
+ * Handles a glob or a list of them.
408
340
  * @internal Exported for use in dev command
409
341
  */
410
342
  function normalizeRoutes(routes) {
411
343
  if (!routes) return [];
412
- if (require_discover.isPartitionedRoutes(routes)) return Array.isArray(routes.paths) ? routes.paths : [routes.paths];
413
344
  return Array.isArray(routes) ? routes : [routes];
414
345
  }
415
346
  //#endregion
@@ -428,7 +359,7 @@ function normalizeRoutes(routes) {
428
359
  * trade than one that tells you what to type. Caddy's own `caddy trust` draws
429
360
  * the line in the same place.
430
361
  */
431
- const logger$12 = console;
362
+ const logger$11 = console;
432
363
  const run = (0, node_util.promisify)(node_child_process.execFile);
433
364
  /**
434
365
  * How each platform is told about a new authority.
@@ -480,15 +411,15 @@ async function trustCommand(options = {}) {
480
411
  const certificate = (0, node_path.join)(workspace.root, require_workspace$1.LOCAL_CA_PATH);
481
412
  if (!(0, node_fs.existsSync)(certificate)) throw new NoLocalAuthority(certificate);
482
413
  const { describe, steps } = installation(process.platform, certificate);
483
- logger$12.log(`\n🔐 Trusting the local authority in ${describe}`);
484
- logger$12.log(` ${certificate}\n`);
414
+ logger$11.log(`\n🔐 Trusting the local authority in ${describe}`);
415
+ logger$11.log(` ${certificate}\n`);
485
416
  if (options.dryRun) {
486
- for (const [command, args] of steps) logger$12.log(` ${command} ${args.join(" ")}`);
417
+ for (const [command, args] of steps) logger$11.log(` ${command} ${args.join(" ")}`);
487
418
  return;
488
419
  }
489
- logger$12.log(" This needs an admin password.\n");
420
+ logger$11.log(" This needs an admin password.\n");
490
421
  for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
491
- logger$12.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
422
+ logger$11.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
492
423
  }
493
424
  /**
494
425
  * Trust the authority if this machine lacks it and its owner agrees.
@@ -505,7 +436,7 @@ async function ensureTrusted(root, url, options = {}) {
505
436
  if (await isTrusted(url)) return;
506
437
  if (!options.assumeYes) {
507
438
  if (!process.stdin.isTTY) {
508
- logger$12.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
439
+ logger$11.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
509
440
  return;
510
441
  }
511
442
  if (await declined(root)) return;
@@ -517,7 +448,7 @@ async function ensureTrusted(root, url, options = {}) {
517
448
  });
518
449
  if (!install) {
519
450
  await recordDeclined(root);
520
- logger$12.log(" Skipped. Run \"gkm trust\" later.");
451
+ logger$11.log(" Skipped. Run \"gkm trust\" later.");
521
452
  return;
522
453
  }
523
454
  }
@@ -1067,7 +998,7 @@ function closeFakes(served) {
1067
998
  }
1068
999
  //#endregion
1069
1000
  //#region src/exec/index.ts
1070
- const logger$11 = console;
1001
+ const logger$10 = console;
1071
1002
  /** `gkm exec` was given nothing to run. */
1072
1003
  var NoCommandSpecified = class extends Error {
1073
1004
  constructor() {
@@ -1090,17 +1021,17 @@ async function execCommand(commandArgs, options = {}) {
1090
1021
  const cwd = options.cwd ?? process.cwd();
1091
1022
  if (!commandArgs[0]) throw new NoCommandSpecified();
1092
1023
  const defaultEnv = require_workspace$1.loadEnvFiles(".env");
1093
- if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1024
+ if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1094
1025
  const { credentials, secretsJsonPath, appName } = await require_workspace$1.prepareEntryCredentials({ cwd });
1095
- if (appName) logger$11.log(`📦 App: ${appName}`);
1026
+ if (appName) logger$10.log(`📦 App: ${appName}`);
1096
1027
  const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
1097
- if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
1028
+ if (secretCount > 0) logger$10.log(`🔐 Loaded ${secretCount} secret(s)`);
1098
1029
  const preloadDir = (0, node_path.join)(cwd, ".gkm");
1099
1030
  await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
1100
1031
  const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.mjs");
1101
1032
  await require_workspace$1.createCredentialsPreload(preloadPath, secretsJsonPath);
1102
1033
  const [cmd, ...args] = commandArgs;
1103
- logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
1034
+ logger$10.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
1104
1035
  const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
1105
1036
  const child = (0, node_child_process.spawn)(cmd, args, {
1106
1037
  cwd,
@@ -1114,7 +1045,7 @@ async function execCommand(commandArgs, options = {}) {
1114
1045
  const exitCode = await new Promise((resolve) => {
1115
1046
  child.on("close", (code) => resolve(code ?? 0));
1116
1047
  child.on("error", (error) => {
1117
- logger$11.error(`Failed to run command: ${error.message}`);
1048
+ logger$10.error(`Failed to run command: ${error.message}`);
1118
1049
  resolve(1);
1119
1050
  });
1120
1051
  });
@@ -1122,7 +1053,7 @@ async function execCommand(commandArgs, options = {}) {
1122
1053
  }
1123
1054
  //#endregion
1124
1055
  //#region src/dev/index.ts
1125
- const logger$10 = console;
1056
+ const logger$9 = console;
1126
1057
  /**
1127
1058
  * Normalize telescope configuration
1128
1059
  * @internal Exported for testing
@@ -1187,39 +1118,36 @@ function normalizeHooksConfig(config, cwd = process.cwd()) {
1187
1118
  return { serverHooksPath: (0, node_path.resolve)(cwd, serverPath) };
1188
1119
  }
1189
1120
  /**
1190
- * Normalize production configuration
1121
+ * What a `--production` build is.
1122
+ *
1123
+ * A server runs its worker's background work itself — queues polled, crons
1124
+ * scheduled, subscribers drained — so the production entry wires all three.
1125
+ * The build writes their files on every server build, empty when there is
1126
+ * nothing, so including them costs an app without any nothing.
1127
+ *
1191
1128
  * @internal Exported for testing
1192
1129
  */
1193
- function normalizeProductionConfig(cliProduction, configProduction) {
1130
+ function normalizeProductionConfig(cliProduction) {
1194
1131
  if (!cliProduction) return;
1195
- const config = configProduction ?? {};
1196
1132
  return {
1197
1133
  enabled: true,
1198
- bundle: config.bundle ?? true,
1199
- minify: config.minify ?? true,
1200
- healthCheck: config.healthCheck ?? "/health",
1201
- gracefulShutdown: config.gracefulShutdown ?? true,
1202
- external: config.external ?? [],
1203
- subscribers: config.subscribers ?? "exclude",
1204
- openapi: config.openapi ?? false,
1205
- optimizedHandlers: config.optimizedHandlers ?? true
1134
+ bundle: true,
1135
+ minify: true,
1136
+ healthCheck: "/health",
1137
+ gracefulShutdown: true,
1138
+ external: [],
1139
+ subscribers: "include",
1140
+ openapi: false,
1141
+ optimizedHandlers: true
1206
1142
  };
1207
1143
  }
1208
- /**
1209
- * Get production config from GkmConfig
1210
- * @internal
1211
- */
1212
- function getProductionConfigFromGkm(config) {
1213
- const serverConfig = config.providers?.server;
1214
- if (typeof serverConfig === "object") return serverConfig.production;
1215
- }
1216
1144
  /** Read by the generated server: no subscriber is subscribed or polled. */
1217
1145
  const SUBSCRIBERS_ENV = "GKM_SUBSCRIBERS";
1218
1146
  async function devCommand(options) {
1219
1147
  if (options.subscribers === false) process.env[SUBSCRIBERS_ENV] = "off";
1220
1148
  if (options.entry) return entryDevCommand(options);
1221
1149
  const defaultEnv = require_workspace$1.loadEnvFiles(".env");
1222
- if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1150
+ if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1223
1151
  const appConfig = await require_config.loadAppConfig().catch((error) => {
1224
1152
  if (error instanceof require_config.NotInAnApp || error instanceof require_config.NotABackendApp) return;
1225
1153
  throw error;
@@ -1235,7 +1163,7 @@ async function devCommand(options) {
1235
1163
  const workspaceAppPort = appConfig.app.port;
1236
1164
  const workspace = appConfig.workspace;
1237
1165
  if (appConfig.app.entry) {
1238
- logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
1166
+ logger$9.log(`📄 Using entry point: ${appConfig.app.entry}`);
1239
1167
  return entryDevCommand({
1240
1168
  ...options,
1241
1169
  entry: appConfig.app.entry,
@@ -1245,21 +1173,20 @@ async function devCommand(options) {
1245
1173
  }
1246
1174
  if (config.env) {
1247
1175
  const { loaded, missing } = require_workspace$1.loadEnvFiles(config.env, appRoot);
1248
- if (loaded.length > 0) logger$10.log(`📦 Loaded env: ${loaded.join(", ")}`);
1249
- if (missing.length > 0) logger$10.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
1176
+ if (loaded.length > 0) logger$9.log(`📦 Loaded env: ${loaded.join(", ")}`);
1177
+ if (missing.length > 0) logger$9.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
1250
1178
  }
1251
- const resolved = resolveProviders(config, { provider: "server" });
1252
1179
  const telescope = normalizeTelescopeConfig(config.telescope);
1253
1180
  const studio = normalizeStudioConfig(config.studio);
1254
1181
  const hooks = normalizeHooksConfig(config.hooks, appRoot);
1255
- const enableOpenApi = require_openapi.resolveOpenApiConfig(config).enabled || resolved.enableOpenApi;
1182
+ const enableOpenApi = require_openapi.resolveOpenApiConfig(config).enabled;
1256
1183
  const build = (bustCache = false) => quietly(() => buildApp({
1257
1184
  config,
1258
1185
  workspaceRoot: workspace?.root ?? secretsRoot,
1259
1186
  appRoot,
1260
- providers: ["server"],
1187
+ target: "server",
1261
1188
  enableOpenApi,
1262
- cacheBackend: require_workspace$1.cacheBackendFor(require_workspace$1.providerOf(workspace ?? config)),
1189
+ cacheBackend: require_backends.cacheBackendFor(require_backends.providerOf(workspace ?? config)),
1263
1190
  telescope,
1264
1191
  studio,
1265
1192
  hooks,
@@ -1277,9 +1204,9 @@ async function devCommand(options) {
1277
1204
  stage: workspace.stages.local,
1278
1205
  ...options.fake ? { fake: true } : {}
1279
1206
  });
1280
- if (reconciled.changed && reconciled.plan.containers.length > 0) {
1281
- logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
1282
- for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
1207
+ if (reconciled.services.length > 0) {
1208
+ logger$9.log("🐳 Services");
1209
+ for (const line of require_workspace$1.describeServices(reconciled.services)) logger$9.log(line);
1283
1210
  }
1284
1211
  Object.assign(appSecrets, reconciled.env);
1285
1212
  const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && require_config.appKey(r.id) === workspaceAppName);
@@ -1292,7 +1219,7 @@ async function devCommand(options) {
1292
1219
  secretsJsonPath = (0, node_path.join)(secretsDir, secretsFileName);
1293
1220
  await (0, node_fs_promises.writeFile)(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
1294
1221
  }
1295
- const devServer = new DevServer(resolved.providers[0], options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? workspaceAppPort !== void 0, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath, initial.selfServing !== void 0);
1222
+ const devServer = new DevServer(options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? workspaceAppPort !== void 0, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath, initial.selfServing !== void 0);
1296
1223
  devServer.label = workspaceAppName ?? "server";
1297
1224
  devServer.publicUrl = publicUrl;
1298
1225
  await devServer.start();
@@ -1313,11 +1240,11 @@ async function devCommand(options) {
1313
1240
  cwd: appRoot
1314
1241
  });
1315
1242
  watcher.on("error", (error) => {
1316
- logger$10.error("❌ Watcher error:", error);
1243
+ logger$9.error("❌ Watcher error:", error);
1317
1244
  });
1318
1245
  let rebuildTimeout = null;
1319
1246
  watcher.on("change", async (path) => {
1320
- logger$10.log(`🔄 ${path} changed — rebuilding`);
1247
+ logger$9.log(`🔄 ${path} changed — rebuilding`);
1321
1248
  if (rebuildTimeout) clearTimeout(rebuildTimeout);
1322
1249
  rebuildTimeout = setTimeout(async () => {
1323
1250
  try {
@@ -1326,8 +1253,8 @@ async function devCommand(options) {
1326
1253
  await devServer.restart();
1327
1254
  } catch (error) {
1328
1255
  const err = error;
1329
- logger$10.error(`❌ Rebuild failed: ${err.message}`);
1330
- if (err.stack) logger$10.error(err.stack);
1256
+ logger$9.error(`❌ Rebuild failed: ${err.message}`);
1257
+ if (err.stack) logger$9.error(err.stack);
1331
1258
  }
1332
1259
  }, 300);
1333
1260
  });
@@ -1335,9 +1262,9 @@ async function devCommand(options) {
1335
1262
  const shutdown = () => {
1336
1263
  if (isShuttingDown) return;
1337
1264
  isShuttingDown = true;
1338
- logger$10.log("\n🛑 Shutting down...");
1265
+ logger$9.log("\n🛑 Shutting down...");
1339
1266
  Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
1340
- logger$10.error("Error during shutdown:", err);
1267
+ logger$9.error("Error during shutdown:", err);
1341
1268
  }).finally(() => {
1342
1269
  process.exit(0);
1343
1270
  });
@@ -1539,7 +1466,7 @@ async function workspaceDevCommand(workspace, options) {
1539
1466
  const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
1540
1467
  const conflicts = checkPortConflicts(workspace);
1541
1468
  if (conflicts.length > 0) {
1542
- for (const conflict of conflicts) logger$10.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1469
+ for (const conflict of conflicts) logger$9.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1543
1470
  throw new Error("Port conflicts detected. Please assign unique ports to each app.");
1544
1471
  }
1545
1472
  if (frontendApps.length > 0) {
@@ -1548,10 +1475,10 @@ async function workspaceDevCommand(workspace, options) {
1548
1475
  for (const result of validationResults) {
1549
1476
  if (!result.valid) {
1550
1477
  hasErrors = true;
1551
- logger$10.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1552
- for (const error of result.errors) logger$10.error(` • ${error}`);
1478
+ logger$9.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1479
+ for (const error of result.errors) logger$9.error(` • ${error}`);
1553
1480
  }
1554
- for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
1481
+ for (const warning of result.warnings) logger$9.warn(` ⚠️ ${result.appName}: ${warning}`);
1555
1482
  }
1556
1483
  if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
1557
1484
  }
@@ -1560,21 +1487,21 @@ async function workspaceDevCommand(workspace, options) {
1560
1487
  stage: workspace.stages.local,
1561
1488
  ...options.fake ? { fake: true } : {}
1562
1489
  });
1563
- if (reconciled.changed && reconciled.plan.containers.length > 0) {
1564
- logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
1565
- for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
1490
+ if (reconciled.services.length > 0) {
1491
+ logger$9.log("🐳 Services");
1492
+ for (const line of require_workspace$1.describeServices(reconciled.services)) logger$9.log(line);
1566
1493
  }
1567
1494
  const secretsEnv = {
1568
1495
  ...rawSecrets,
1569
1496
  ...reconciled.env
1570
1497
  };
1571
1498
  const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
1572
- for (const { id, port } of fakes) logger$10.log(`🎭 ${id} fake: http://localhost:${port}`);
1499
+ for (const { id, port } of fakes) logger$9.log(`🎭 ${id} fake: http://localhost:${port}`);
1573
1500
  if (options.migrate || options.seed) {
1574
- const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-PJi4b1ga.cjs"));
1501
+ const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-BgEY9pdP.cjs"));
1575
1502
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1576
- } else await Promise.resolve().then(() => require("./migrate-PJi4b1ga.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1577
- logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1503
+ } else await Promise.resolve().then(() => require("./migrate-BgEY9pdP.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1504
+ logger$9.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1578
1505
  });
1579
1506
  const appUrls = {};
1580
1507
  for (const resource of reconciled.plan.resources) {
@@ -1595,11 +1522,11 @@ async function workspaceDevCommand(workspace, options) {
1595
1522
  else {
1596
1523
  const { filters, unpackaged } = turboFilters(workspace);
1597
1524
  turboFilter = filters.flatMap((name) => ["--filter", name]);
1598
- if (unpackaged.length > 0) logger$10.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
1525
+ if (unpackaged.length > 0) logger$9.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
1599
1526
  }
1600
1527
  const buildOrder = require_workspace.getAppBuildOrder(workspace);
1601
1528
  const width = Math.max(...buildOrder.map((name) => name.length));
1602
- logger$10.log(`\n${workspace.name}: ${appCount} app(s)`);
1529
+ logger$9.log(`\n${workspace.name}: ${appCount} app(s)`);
1603
1530
  for (const appName of buildOrder) {
1604
1531
  const app = workspace.apps[appName];
1605
1532
  if (!app) continue;
@@ -1607,14 +1534,14 @@ async function workspaceDevCommand(workspace, options) {
1607
1534
  const own = reconciled.plan.resources.find((r) => r.kind === "mobile-app" && require_config.appKey(r.id) === appName);
1608
1535
  const scheme = own ? reconciled.env[own.envKey] : void 0;
1609
1536
  const device = Object.entries(reconciled.env).find(([key]) => key.endsWith("_DEVICE_URL"))?.[1];
1610
- logger$10.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
1537
+ logger$9.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
1611
1538
  continue;
1612
1539
  }
1613
1540
  const local = `http://localhost:${app.port}`;
1614
1541
  const url = Object.entries(appUrls).find(([id]) => require_config.appKey(id) === appName)?.[1];
1615
- logger$10.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
1542
+ logger$9.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
1616
1543
  }
1617
- logger$10.log("");
1544
+ logger$9.log("");
1618
1545
  const configFiles = [
1619
1546
  "gkm.config.ts",
1620
1547
  "gkm.config.js",
@@ -1661,7 +1588,7 @@ async function workspaceDevCommand(workspace, options) {
1661
1588
  const shutdown = () => {
1662
1589
  if (isShuttingDown) return;
1663
1590
  isShuttingDown = true;
1664
- logger$10.log("\n🛑 Shutting down workspace...");
1591
+ logger$9.log("\n🛑 Shutting down workspace...");
1665
1592
  closeFakes(fakes);
1666
1593
  const pid = turboProcess.pid;
1667
1594
  if (pid) try {
@@ -1686,7 +1613,7 @@ async function workspaceDevCommand(workspace, options) {
1686
1613
  process.on("SIGTERM", shutdown);
1687
1614
  return new Promise((resolve, reject) => {
1688
1615
  turboProcess.on("error", (error) => {
1689
- logger$10.error("❌ Turbo error:", error);
1616
+ logger$9.error("❌ Turbo error:", error);
1690
1617
  reject(error);
1691
1618
  });
1692
1619
  turboProcess.on("exit", (code) => {
@@ -1705,11 +1632,11 @@ async function entryDevCommand(options) {
1705
1632
  const entryPath = (0, node_path.resolve)(process.cwd(), entry);
1706
1633
  if (!(0, node_fs.existsSync)(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
1707
1634
  const defaultEnv = require_workspace$1.loadEnvFiles(".env");
1708
- if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1635
+ if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1709
1636
  const { credentials, resolvedPort, secretsJsonPath, appName } = await require_workspace$1.prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
1710
- if (appName) logger$10.log(`📦 App: ${appName} (port ${resolvedPort})`);
1711
- logger$10.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1712
- if (Object.keys(credentials).length > 1) logger$10.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
1637
+ if (appName) logger$9.log(`📦 App: ${appName} (port ${resolvedPort})`);
1638
+ logger$9.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1639
+ if (Object.keys(credentials).length > 1) logger$9.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
1713
1640
  const wrapperDir = (0, node_path.join)(process.cwd(), ".gkm");
1714
1641
  await (0, node_fs_promises.mkdir)(wrapperDir, { recursive: true });
1715
1642
  const wrapperPath = (0, node_path.join)(wrapperDir, "entry-wrapper.ts");
@@ -1720,7 +1647,7 @@ async function entryDevCommand(options) {
1720
1647
  const shutdown = () => {
1721
1648
  if (isShuttingDown) return;
1722
1649
  isShuttingDown = true;
1723
- logger$10.log("\n🛑 Shutting down...");
1650
+ logger$9.log("\n🛑 Shutting down...");
1724
1651
  runner.stop();
1725
1652
  process.exit(0);
1726
1653
  };
@@ -1756,14 +1683,14 @@ var EntryRunner = class {
1756
1683
  });
1757
1684
  let restartTimeout = null;
1758
1685
  this.watcher.on("change", (path) => {
1759
- logger$10.log(`📝 File changed: ${path}`);
1686
+ logger$9.log(`📝 File changed: ${path}`);
1760
1687
  if (restartTimeout) clearTimeout(restartTimeout);
1761
1688
  restartTimeout = setTimeout(async () => {
1762
- logger$10.log("🔄 Restarting...");
1689
+ logger$9.log("🔄 Restarting...");
1763
1690
  await this.restart();
1764
1691
  }, 300);
1765
1692
  });
1766
- logger$10.log(`👀 Watching for changes in: ${watchDir}`);
1693
+ logger$9.log(`👀 Watching for changes in: ${watchDir}`);
1767
1694
  }
1768
1695
  }
1769
1696
  async runProcess() {
@@ -1778,17 +1705,17 @@ var EntryRunner = class {
1778
1705
  });
1779
1706
  this.isRunning = true;
1780
1707
  this.childProcess.on("error", (error) => {
1781
- logger$10.error("❌ Process error:", error);
1708
+ logger$9.error("❌ Process error:", error);
1782
1709
  });
1783
1710
  this.childProcess.on("exit", (code) => {
1784
- if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
1711
+ if (code !== null && code !== 0 && code !== 143) logger$9.error(`❌ Process exited with code ${code}`);
1785
1712
  this.isRunning = false;
1786
1713
  });
1787
1714
  await new Promise((resolve) => setTimeout(resolve, 500));
1788
1715
  if (this.isRunning) {
1789
- logger$10.log("");
1790
- logger$10.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
1791
- logger$10.log("");
1716
+ logger$9.log("");
1717
+ logger$9.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
1718
+ logger$9.log("");
1792
1719
  }
1793
1720
  }
1794
1721
  async restart() {
@@ -1966,7 +1893,6 @@ function listenerOn(port) {
1966
1893
  }
1967
1894
  }
1968
1895
  var DevServer = class {
1969
- provider;
1970
1896
  requestedPort;
1971
1897
  portExplicit;
1972
1898
  enableOpenApi;
@@ -1980,8 +1906,7 @@ var DevServer = class {
1980
1906
  isRunning = false;
1981
1907
  actualPort;
1982
1908
  startTime = Date.now();
1983
- constructor(provider, requestedPort, portExplicit, enableOpenApi, telescope, studio, runtime = "node", appRoot = process.cwd(), secretsJsonPath, selfServing = false) {
1984
- this.provider = provider;
1909
+ constructor(requestedPort, portExplicit, enableOpenApi, telescope, studio, runtime = "node", appRoot = process.cwd(), secretsJsonPath, selfServing = false) {
1985
1910
  this.requestedPort = requestedPort;
1986
1911
  this.portExplicit = portExplicit;
1987
1912
  this.enableOpenApi = enableOpenApi;
@@ -2008,9 +1933,9 @@ var DevServer = class {
2008
1933
  this.actualPort = this.requestedPort;
2009
1934
  } else {
2010
1935
  this.actualPort = await require_workspace$1.findAvailablePort(this.requestedPort);
2011
- if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
1936
+ if (this.actualPort !== this.requestedPort) logger$9.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
2012
1937
  }
2013
- const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts");
1938
+ const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", "server", "server.ts");
2014
1939
  await this.createServerEntry();
2015
1940
  this.serverProcess = (0, node_child_process.spawn)(...tsxCommand(this.appRoot, [
2016
1941
  serverEntryPath,
@@ -2027,10 +1952,10 @@ var DevServer = class {
2027
1952
  });
2028
1953
  this.isRunning = true;
2029
1954
  this.serverProcess.on("error", (error) => {
2030
- logger$10.error("❌ Server error:", error);
1955
+ logger$9.error("❌ Server error:", error);
2031
1956
  });
2032
1957
  this.serverProcess.on("exit", (code, signal) => {
2033
- if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
1958
+ if (code !== null && code !== 0 && signal !== "SIGTERM") logger$9.error(`❌ Server exited with code ${code}`);
2034
1959
  this.isRunning = false;
2035
1960
  });
2036
1961
  await new Promise((resolve) => setTimeout(resolve, 1e3));
@@ -2038,13 +1963,13 @@ var DevServer = class {
2038
1963
  const local = `http://localhost:${this.actualPort}`;
2039
1964
  const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
2040
1965
  const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
2041
- logger$10.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
1966
+ logger$9.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
2042
1967
  const tools = this.selfServing ? [] : [
2043
1968
  ...this.enableOpenApi ? ["docs /__docs"] : [],
2044
1969
  ...this.telescope ? [`telescope ${this.telescope.path}`] : [],
2045
1970
  ...this.studio ? [`studio ${this.studio.path}`] : []
2046
1971
  ];
2047
- if (tools.length > 0) logger$10.log(` ${tools.join(" · ")}`);
1972
+ if (tools.length > 0) logger$9.log(` ${tools.join(" · ")}`);
2048
1973
  }
2049
1974
  }
2050
1975
  async stop() {
@@ -2082,7 +2007,7 @@ var DevServer = class {
2082
2007
  }
2083
2008
  async createServerEntry() {
2084
2009
  const { writeFile: fsWriteFile } = await import("node:fs/promises");
2085
- await fsWriteFile((0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts"), generateServerEntryContent({
2010
+ await fsWriteFile((0, node_path.join)(this.appRoot, ".gkm", "server", "server.ts"), generateServerEntryContent({
2086
2011
  secretsJsonPath: this.secretsJsonPath,
2087
2012
  runtime: this.runtime,
2088
2013
  enableOpenApi: this.enableOpenApi,
@@ -2094,22 +2019,22 @@ var DevServer = class {
2094
2019
  //#region src/generators/CronGenerator.ts
2095
2020
  var CronGenerator = class extends require_discover.ConstructGenerator {
2096
2021
  async build(context, constructs, outputDir, options) {
2097
- const provider = options?.provider || "aws-lambda";
2022
+ const target = options?.target ?? "aws";
2098
2023
  const logger = console;
2099
2024
  const cronInfos = [];
2100
- if (provider === "server") {
2025
+ if (target === "server") {
2101
2026
  await this.generateServerCronsFile(outputDir, constructs);
2102
2027
  logger.log(`Generated server crons file with ${constructs.length} crons`);
2103
2028
  return cronInfos;
2104
2029
  }
2105
- if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
2030
+ if (constructs.length === 0) return cronInfos;
2106
2031
  const cronsDir = (0, node_path.join)(outputDir, "crons");
2107
2032
  await (0, node_fs_promises.mkdir)(cronsDir, { recursive: true });
2108
2033
  for (const { key, construct, path } of constructs) {
2109
2034
  const handlerFile = await this.generateCronHandler(cronsDir, path.relative, key, context, construct.owner);
2110
2035
  cronInfos.push({
2111
2036
  name: key,
2112
- handler: (0, node_path.relative)(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2037
+ handler: (0, node_path.relative)(options?.root ?? process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2113
2038
  schedule: construct.schedule || "rate(1 hour)",
2114
2039
  timeout: construct.timeout,
2115
2040
  memorySize: construct.memorySize,
@@ -2299,17 +2224,17 @@ var FunctionGenerator = class extends require_discover.ConstructGenerator {
2299
2224
  return _geekmidas_constructs_functions.Function.isFunction(value);
2300
2225
  }
2301
2226
  async build(context, constructs, outputDir, options) {
2302
- const provider = options?.provider || "aws-lambda";
2227
+ const target = options?.target ?? "aws";
2303
2228
  const logger = console;
2304
2229
  const functionInfos = [];
2305
- if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
2230
+ if (constructs.length === 0 || target === "server") return functionInfos;
2306
2231
  const functionsDir = (0, node_path.join)(outputDir, "functions");
2307
2232
  await (0, node_fs_promises.mkdir)(functionsDir, { recursive: true });
2308
2233
  for (const { key, construct, path } of constructs) {
2309
2234
  const handlerFile = await this.generateFunctionHandler(functionsDir, path.relative, key, context, construct.owner);
2310
2235
  functionInfos.push({
2311
2236
  name: key,
2312
- handler: (0, node_path.relative)(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2237
+ handler: (0, node_path.relative)(options?.root ?? process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2313
2238
  timeout: construct.timeout,
2314
2239
  memorySize: construct.memorySize,
2315
2240
  environment: await construct.getEnvironment({ markOptional: context.markOptional }),
@@ -2353,22 +2278,22 @@ var QueueGenerator = class extends require_discover.ConstructGenerator {
2353
2278
  return _geekmidas_constructs_queue.Queue.isQueue(value);
2354
2279
  }
2355
2280
  async build(context, constructs, outputDir, options) {
2356
- const provider = options?.provider || "aws-lambda";
2281
+ const target = options?.target ?? "aws";
2357
2282
  const logger = console;
2358
2283
  const queueInfos = [];
2359
- if (provider === "server") {
2284
+ if (target === "server") {
2360
2285
  await this.generateServerQueuesFile(outputDir, constructs);
2361
2286
  logger.log(`Generated server queues file with ${constructs.length} queues (polling mode)`);
2362
2287
  return queueInfos;
2363
2288
  }
2364
- if (constructs.length === 0 || provider !== "aws-lambda") return queueInfos;
2289
+ if (constructs.length === 0) return queueInfos;
2365
2290
  const queuesDir = (0, node_path.join)(outputDir, "queues");
2366
2291
  await (0, node_fs_promises.mkdir)(queuesDir, { recursive: true });
2367
2292
  for (const { key, construct, path } of constructs) {
2368
2293
  const handlerFile = await this.generateQueueHandler(queuesDir, path.relative, key, context, construct.owner);
2369
2294
  queueInfos.push({
2370
2295
  name: construct.name,
2371
- handler: (0, node_path.relative)(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2296
+ handler: (0, node_path.relative)(options?.root ?? process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2372
2297
  batchSize: construct.batchSize,
2373
2298
  fifo: construct.fifo,
2374
2299
  timeout: construct.timeout,
@@ -2534,217 +2459,6 @@ var TopicGenerator = class extends require_discover.ConstructGenerator {
2534
2459
  }
2535
2460
  };
2536
2461
  //#endregion
2537
- //#region src/build/manifests.ts
2538
- const logger$9 = console;
2539
- function isPartitioned(field) {
2540
- return !Array.isArray(field);
2541
- }
2542
- /**
2543
- * Serialize a manifest field to a TypeScript string.
2544
- * Flat arrays serialize as JSON arrays, partitioned fields as objects of arrays.
2545
- */
2546
- function serializeField(field, indent = 2) {
2547
- if (Array.isArray(field)) return JSON.stringify(field, null, indent);
2548
- return `{\n${Object.entries(field).map(([key, value]) => ` ${JSON.stringify(key)}: ${JSON.stringify(value, null, indent)}`).join(",\n")},\n }`;
2549
- }
2550
- /**
2551
- * Count total items across a manifest field (flat or partitioned).
2552
- */
2553
- function countItems(field) {
2554
- if (Array.isArray(field)) return field.length;
2555
- return Object.values(field).reduce((sum, arr) => sum + arr.length, 0);
2556
- }
2557
- /**
2558
- * Generate derived types for a construct field.
2559
- * @param fieldName - The field name in the manifest (e.g., 'routes')
2560
- * @param typeName - The exported type name (e.g., 'Route')
2561
- * @param partitioned - Whether this field is partitioned
2562
- */
2563
- function generateDerivedType(fieldName, typeName, partitioned) {
2564
- if (partitioned) {
2565
- const partitionTypeName = `${typeName}Partition`;
2566
- return [`export type ${partitionTypeName} = keyof typeof manifest.${fieldName};`, `export type ${typeName}<P extends ${partitionTypeName} = ${partitionTypeName}> = (typeof manifest.${fieldName})[P][number];`].join("\n");
2567
- }
2568
- return `export type ${typeName} = (typeof manifest.${fieldName})[number];`;
2569
- }
2570
- /** A manifest field as a flat list, whether or not it is partitioned. */
2571
- function flatten(field) {
2572
- return Array.isArray(field) ? field : Object.values(field).flat();
2573
- }
2574
- async function generateAwsManifest(outputDir, routes, functions, crons, subscribers, queues = [], topics = [], constructs = {}, backends = {}) {
2575
- const manifestDir = (0, node_path.join)(outputDir, "manifest");
2576
- await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
2577
- const awsRoutes = filterAllRoutes(routes);
2578
- const routesPartitioned = isPartitioned(awsRoutes);
2579
- const functionsPartitioned = isPartitioned(functions);
2580
- const cronsPartitioned = isPartitioned(crons);
2581
- const subscribersPartitioned = isPartitioned(subscribers);
2582
- const queuesPartitioned = isPartitioned(queues);
2583
- const topicsPartitioned = isPartitioned(topics);
2584
- const awsConstructs = require_emit.withCompute(require_emit.withRoutes(constructs, flatten(awsRoutes), { perRoute: true }), {
2585
- functions: flatten(functions),
2586
- crons: flatten(crons),
2587
- queues: flatten(queues),
2588
- subscribers: flatten(subscribers)
2589
- });
2590
- const content = `export const manifest = {
2591
- routes: ${serializeField(awsRoutes)},
2592
- functions: ${serializeField(functions)},
2593
- crons: ${serializeField(crons)},
2594
- subscribers: ${serializeField(subscribers)},
2595
- queues: ${serializeField(queues)},
2596
- topics: ${serializeField(topics)},
2597
- } as const;
2598
-
2599
- /**
2600
- * What the application declares, as opposed to what this build generated.
2601
- *
2602
- * Discovery imports application code, so it runs once here rather than in every
2603
- * consumer — a deploy config calling it would evaluate the whole runtime graph
2604
- * inside its own toolchain.
2605
- */
2606
- export const constructs = ${JSON.stringify(awsConstructs, null, 2)} as const;
2607
-
2608
- /**
2609
- * Where the backends that are config rather than declaration resolved to.
2610
- *
2611
- * Recorded because they were answered twice: once by the build, which registers
2612
- * exactly one cache driver, and once by a deploy, which told the provisioner
2613
- * something else — leaving the running code with a URL it had no driver for.
2614
- */
2615
- export const backends = ${JSON.stringify(backends, null, 2)} as const;
2616
-
2617
- // Derived types
2618
- ${generateDerivedType("routes", "Route", routesPartitioned)}
2619
- ${generateDerivedType("functions", "Function", functionsPartitioned)}
2620
- ${generateDerivedType("crons", "Cron", cronsPartitioned)}
2621
- ${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
2622
- ${generateDerivedType("queues", "Queue", queuesPartitioned)}
2623
- ${generateDerivedType("topics", "Topic", topicsPartitioned)}
2624
-
2625
- export type ConstructId = keyof typeof constructs;
2626
- export type Construct<Id extends ConstructId> = (typeof constructs)[Id];
2627
-
2628
- // Useful union types
2629
- export type Authorizer = Route['authorizer'];
2630
- export type HttpMethod = Route['method'];
2631
- export type RoutePath = Route['path'];
2632
- `;
2633
- const manifestPath = (0, node_path.join)(manifestDir, "aws.ts");
2634
- await (0, node_fs_promises.writeFile)(manifestPath, content);
2635
- logger$9.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers, ${countItems(queues)} queues, ${countItems(topics)} topics`);
2636
- logger$9.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
2637
- }
2638
- async function generateServerManifest(outputDir, appInfo, routes, subscribers, queues = [], topics = [], constructs = {}, backends = {}) {
2639
- const manifestDir = (0, node_path.join)(outputDir, "manifest");
2640
- await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
2641
- const serverRoutes = mapRouteMetadata(filterAllRoutes(routes));
2642
- const serverSubscribers = mapSubscriberMetadata(subscribers);
2643
- const serverQueues = mapQueueMetadata(queues);
2644
- const serverTopics = mapTopicMetadata(topics);
2645
- const routesPartitioned = isPartitioned(serverRoutes);
2646
- const subscribersPartitioned = isPartitioned(serverSubscribers);
2647
- const queuesPartitioned = isPartitioned(serverQueues);
2648
- const topicsPartitioned = isPartitioned(serverTopics);
2649
- const content = `export const manifest = {
2650
- app: ${JSON.stringify(appInfo, null, 2)},
2651
- routes: ${serializeField(serverRoutes)},
2652
- subscribers: ${serializeField(serverSubscribers)},
2653
- queues: ${serializeField(serverQueues)},
2654
- topics: ${serializeField(serverTopics)},
2655
- } as const;
2656
-
2657
- /**
2658
- * What the application declares, as opposed to what this build generated.
2659
- *
2660
- * Discovery imports application code, so it runs once in the build rather than
2661
- * in every consumer — a deploy config calling it would evaluate the whole
2662
- * runtime graph inside its own toolchain.
2663
- */
2664
- export const constructs = ${JSON.stringify(constructs, null, 2)} as const;
2665
-
2666
- /**
2667
- * Where the backends that are config rather than declaration resolved to.
2668
- *
2669
- * Recorded because they were answered twice: once by the build, which registers
2670
- * exactly one cache driver, and once by a deploy, which told the provisioner
2671
- * something else — leaving the running code with a URL it had no driver for.
2672
- */
2673
- export const backends = ${JSON.stringify(backends, null, 2)} as const;
2674
-
2675
- // Derived types
2676
- ${generateDerivedType("routes", "Route", routesPartitioned)}
2677
- ${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
2678
- ${generateDerivedType("queues", "Queue", queuesPartitioned)}
2679
- ${generateDerivedType("topics", "Topic", topicsPartitioned)}
2680
-
2681
- export type ConstructId = keyof typeof constructs;
2682
- export type Construct<Id extends ConstructId> = (typeof constructs)[Id];
2683
-
2684
- // Useful union types
2685
- export type Authorizer = Route['authorizer'];
2686
- export type HttpMethod = Route['method'];
2687
- export type RoutePath = Route['path'];
2688
- `;
2689
- const manifestPath = (0, node_path.join)(manifestDir, "server.ts");
2690
- await (0, node_fs_promises.writeFile)(manifestPath, content);
2691
- logger$9.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
2692
- logger$9.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
2693
- }
2694
- /**
2695
- * Filter out 'ALL' method routes from a manifest field (flat or partitioned).
2696
- */
2697
- function filterAllRoutes(routes) {
2698
- if (Array.isArray(routes)) return routes.filter((r) => r.method !== "ALL");
2699
- const result = {};
2700
- for (const [partition, partitionRoutes] of Object.entries(routes)) result[partition] = partitionRoutes.filter((r) => r.method !== "ALL");
2701
- return result;
2702
- }
2703
- /**
2704
- * Map routes to server metadata (path, method, authorizer only).
2705
- */
2706
- function mapRouteMetadata(routes) {
2707
- const mapFn = (r) => ({
2708
- path: r.path,
2709
- method: r.method,
2710
- authorizer: r.authorizer
2711
- });
2712
- if (Array.isArray(routes)) return routes.map(mapFn);
2713
- const result = {};
2714
- for (const [partition, partitionRoutes] of Object.entries(routes)) result[partition] = partitionRoutes.map(mapFn);
2715
- return result;
2716
- }
2717
- /**
2718
- * Map subscribers to server metadata (name, subscribedEvents only).
2719
- */
2720
- function mapSubscriberMetadata(subscribers) {
2721
- const mapFn = (s) => ({
2722
- name: s.name,
2723
- subscribedEvents: s.subscribedEvents
2724
- });
2725
- if (Array.isArray(subscribers)) return subscribers.map(mapFn);
2726
- const result = {};
2727
- for (const [partition, partitionSubs] of Object.entries(subscribers)) result[partition] = partitionSubs.map(mapFn);
2728
- return result;
2729
- }
2730
- function mapQueueMetadata(queues) {
2731
- const mapFn = (q) => ({ name: q.name });
2732
- if (Array.isArray(queues)) return queues.map(mapFn);
2733
- const result = {};
2734
- for (const [partition, partitionQueues] of Object.entries(queues)) result[partition] = partitionQueues.map(mapFn);
2735
- return result;
2736
- }
2737
- function mapTopicMetadata(topics) {
2738
- const mapFn = (t) => ({
2739
- name: t.name,
2740
- events: t.events
2741
- });
2742
- if (Array.isArray(topics)) return topics.map(mapFn);
2743
- const result = {};
2744
- for (const [partition, partitionTopics] of Object.entries(topics)) result[partition] = partitionTopics.map(mapFn);
2745
- return result;
2746
- }
2747
- //#endregion
2748
2462
  //#region src/build/owners.ts
2749
2463
  /**
2750
2464
  * What a generated entry imports its runtime from, read off the discovered
@@ -2814,27 +2528,6 @@ function restApis(declared) {
2814
2528
  function servedSurface(declared, workspaceRoot, appRoot) {
2815
2529
  return restApis(declared).find((d) => (0, node_path.resolve)(workspaceRoot, d.path) === (0, node_path.resolve)(appRoot));
2816
2530
  }
2817
- /**
2818
- * Check if any construct across the given arrays has a non-undefined partition.
2819
- * When true, the manifest should use the partitioned shape for that construct type.
2820
- */
2821
- function hasPartitions(constructs) {
2822
- return constructs.some((c) => c.partition !== void 0);
2823
- }
2824
- /**
2825
- * Group an info array by partition, using the partition values from
2826
- * the corresponding construct array. Both arrays must be the same length
2827
- * and in the same order.
2828
- */
2829
- function groupInfosByPartition(infos, constructs) {
2830
- const groups = {};
2831
- for (let i = 0; i < infos.length; i++) {
2832
- const partition = constructs[i]?.partition ?? "default";
2833
- if (!groups[partition]) groups[partition] = [];
2834
- groups[partition].push(infos[i]);
2835
- }
2836
- return groups;
2837
- }
2838
2531
  //#endregion
2839
2532
  //#region src/build/surfaceEntry.ts
2840
2533
  /** Whether a construct serves itself, asked of the construct. */
@@ -2911,6 +2604,24 @@ function rootGkmConfig(workspace) {
2911
2604
  constructs: require_workspace.allConstructGlobs(workspace)
2912
2605
  };
2913
2606
  }
2607
+ /** Directories under `.gkm/` that the removed per-provider builds wrote. */
2608
+ const SUPERSEDED_OUTPUT = [
2609
+ "aws-apigatewayv1",
2610
+ "aws-apigatewayv2",
2611
+ "aws-lambda"
2612
+ ];
2613
+ /** Whether `--provider` named something a build can target. */
2614
+ function isMainProvider(value) {
2615
+ return value === "aws" || value === "server";
2616
+ }
2617
+ var UnknownBuildProvider = class extends Error {
2618
+ provider;
2619
+ constructor(provider) {
2620
+ super(`'${provider}' is not a build target. Pass --provider aws or --provider server, or leave it out to build for where gkm.config.ts deploys.`);
2621
+ this.provider = provider;
2622
+ this.name = "UnknownBuildProvider";
2623
+ }
2624
+ };
2914
2625
  async function buildCommand(options) {
2915
2626
  const loadedConfig = await require_config.loadWorkspaceConfig();
2916
2627
  if (loadedConfig.type === "workspace") {
@@ -2924,25 +2635,62 @@ async function buildCommand(options) {
2924
2635
  }
2925
2636
  }
2926
2637
  const config = loadedConfig.type !== "workspace" ? await require_config.loadConfig() : (0, node_path.resolve)(process.cwd()) === (0, node_path.resolve)(loadedConfig.workspace.root) ? rootGkmConfig(loadedConfig.workspace) : (await require_config.loadAppConfig()).gkmConfig;
2927
- const resolved = resolveProviders(config, options);
2928
- const cacheBackend = require_workspace$1.cacheBackendFor(require_workspace$1.providerOf(loadedConfig.workspace));
2929
- const productionConfigFromGkm = getProductionConfigFromGkm(config);
2930
- const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
2638
+ const target = options.provider ?? require_backends.providerOf(loadedConfig.workspace);
2639
+ const workspaceRoot = loadedConfig.workspace.root;
2640
+ const appRoot = process.cwd();
2641
+ const output = await buildOneApp({
2642
+ config,
2643
+ options,
2644
+ workspace: loadedConfig.workspace,
2645
+ appRoot,
2646
+ target
2647
+ });
2648
+ if ((0, node_path.resolve)(appRoot) === (0, node_path.resolve)(workspaceRoot)) await writeManifest({
2649
+ workspaceRoot,
2650
+ target,
2651
+ builds: output.built ? [output.built] : [],
2652
+ constructs: await declaredIn(config, workspaceRoot),
2653
+ backends: backendsOf(loadedConfig.workspace)
2654
+ });
2655
+ return output;
2656
+ }
2657
+ /** What the workspace declares, read through a config's globs. */
2658
+ async function declaredIn(config, cwd) {
2659
+ return require_discover.discover({
2660
+ patterns: config.constructs,
2661
+ cwd
2662
+ });
2663
+ }
2664
+ /**
2665
+ * The backend choices the manifest records — answered by the deploy target,
2666
+ * the same place reconcile and the build's drivers read them.
2667
+ */
2668
+ function backendsOf(workspace) {
2669
+ return {
2670
+ cache: require_backends.cacheBackendFor(require_backends.providerOf(workspace)),
2671
+ email: "ses"
2672
+ };
2673
+ }
2674
+ /** One app's build, from its config and the command's options. */
2675
+ async function buildOneApp(input) {
2676
+ const { config, options, workspace, appRoot, target } = input;
2677
+ const cacheBackend = require_backends.cacheBackendFor(require_backends.providerOf(workspace));
2678
+ const production = normalizeProductionConfig(options.production ?? false);
2931
2679
  if (production) logger$8.log(`🏭 Building for PRODUCTION`);
2932
- logger$8.log(`Building with providers: ${resolved.providers.join(", ")}`);
2680
+ logger$8.log(`Building for ${target}`);
2933
2681
  logger$8.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
2934
2682
  const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
2935
2683
  if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
2936
2684
  const studio = production ? void 0 : normalizeStudioConfig(config.studio);
2937
2685
  if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
2938
- const hooks = normalizeHooksConfig(config.hooks);
2686
+ const hooks = normalizeHooksConfig(config.hooks, appRoot);
2939
2687
  if (hooks) logger$8.log(`🪝 Server hooks enabled`);
2940
2688
  return buildApp({
2941
2689
  config,
2942
- workspaceRoot: loadedConfig.workspace.root,
2943
- appRoot: process.cwd(),
2944
- providers: resolved.providers,
2945
- enableOpenApi: resolved.enableOpenApi,
2690
+ workspaceRoot: workspace.root,
2691
+ appRoot,
2692
+ target,
2693
+ enableOpenApi: options.enableOpenApi ?? false,
2946
2694
  cacheBackend,
2947
2695
  production,
2948
2696
  telescope,
@@ -2954,6 +2702,33 @@ async function buildCommand(options) {
2954
2702
  });
2955
2703
  }
2956
2704
  /**
2705
+ * Write the application's manifest: every declared construct, with what each
2706
+ * app generated folded into it.
2707
+ *
2708
+ * The manifest belongs to the whole application, so only the root writes it —
2709
+ * once, after every app has built, from the constructs the workspace declares.
2710
+ * An app's own build generates its handlers and nothing else.
2711
+ */
2712
+ async function writeManifest(input) {
2713
+ const { workspaceRoot, target, builds, constructs, backends } = input;
2714
+ const manifestDir = (0, node_path.join)(workspaceRoot, ".gkm", "manifest");
2715
+ await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
2716
+ await (0, node_fs_promises.rm)((0, node_path.join)(manifestDir, `${target === "aws" ? "server" : "aws"}.ts`), { force: true });
2717
+ const withServed = builds.reduce((manifest, build) => require_emit.withRoutes(manifest, build.routes.filter((route) => route.method !== "ALL"), {
2718
+ perRoute: true,
2719
+ ...build.surface ? { surface: build.surface } : {}
2720
+ }), constructs);
2721
+ const folded = require_emit.withCompute(withServed, {
2722
+ functions: builds.flatMap((build) => build.functions),
2723
+ crons: builds.flatMap((build) => build.crons),
2724
+ queues: builds.flatMap((build) => build.queues),
2725
+ subscribers: builds.flatMap((build) => build.subscribers)
2726
+ });
2727
+ const path = (0, node_path.join)(manifestDir, `${target}.ts`);
2728
+ await (0, node_fs_promises.writeFile)(path, require_emit.manifestModule(folded, backends));
2729
+ logger$8.log(`Manifest: ${(0, node_path.relative)(process.cwd(), path)}`);
2730
+ }
2731
+ /**
2957
2732
  * Turn one app's constructs into its generated entry.
2958
2733
  *
2959
2734
  * `gkm build` and `gkm dev` both run this. They used to be two copies of it,
@@ -2962,8 +2737,8 @@ async function buildCommand(options) {
2962
2737
  * dev did not, and failed on exactly the apps the build had learned to handle.
2963
2738
  */
2964
2739
  async function buildApp(input) {
2965
- const { config, workspaceRoot, appRoot, providers, enableOpenApi, cacheBackend, production, telescope, studio, hooks, bustCache = false } = input;
2966
- const constructGlobs = typeof config.constructs === "string" || Array.isArray(config.constructs) ? config.constructs : void 0;
2740
+ const { config, workspaceRoot, appRoot, target, enableOpenApi, cacheBackend, production, telescope, studio, hooks, bustCache = false } = input;
2741
+ const constructGlobs = config.constructs;
2967
2742
  const constructSources = {};
2968
2743
  const declared = constructGlobs ? await require_discover.discover({
2969
2744
  patterns: constructGlobs,
@@ -3029,50 +2804,58 @@ async function buildApp(input) {
3029
2804
  logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
3030
2805
  return {};
3031
2806
  }
3032
- const rootOutputDir = (0, node_path.join)(appRoot, ".gkm");
3033
- await (0, node_fs_promises.mkdir)(rootOutputDir, { recursive: true });
3034
- let result = {};
3035
- for (const provider of providers) {
3036
- const providerResult = await buildForProvider(provider, buildContext, appRoot, declared, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics, enableOpenApi, input.skipBundle ?? false, input.stage);
3037
- if (providerResult.masterKey) result = providerResult;
3038
- }
2807
+ const result = await buildForTarget(target, buildContext, appRoot, workspaceRoot, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics, enableOpenApi, input.skipBundle ?? false, input.stage);
3039
2808
  await require_openapi.generateOpenApi(allEndpoints.map(({ construct }) => construct), { openapi: config.openapi });
3040
2809
  return result;
3041
2810
  }
3042
- async function buildForProvider(provider, context, appRoot, discovered, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, endpoints, functions, crons, subscribers, queues, topics, enableOpenApi, skipBundle, stage) {
3043
- const rootOutputDir = (0, node_path.join)(appRoot, ".gkm");
3044
- const outputDir = (0, node_path.join)(rootOutputDir, provider);
2811
+ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, endpoints, functions, crons, subscribers, queues, topics, enableOpenApi, skipBundle, stage) {
2812
+ const outputDir = (0, node_path.join)(appRoot, ".gkm", target);
2813
+ await Promise.all([...SUPERSEDED_OUTPUT.map((dir) => (0, node_path.join)(appRoot, ".gkm", dir)), ...appRoot === workspaceRoot ? [] : [(0, node_path.join)(appRoot, ".gkm", "manifest")]].map((dir) => (0, node_fs_promises.rm)(dir, {
2814
+ recursive: true,
2815
+ force: true
2816
+ })));
2817
+ if (target === "aws") await (0, node_fs_promises.rm)(outputDir, {
2818
+ recursive: true,
2819
+ force: true
2820
+ });
3045
2821
  await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
3046
- logger$8.log(`\nGenerating handlers for provider: ${provider}`);
2822
+ logger$8.log(`\nGenerating handlers for ${target}`);
2823
+ const options = {
2824
+ target,
2825
+ root: workspaceRoot
2826
+ };
3047
2827
  const [routes, functionInfos, cronInfos, subscriberInfos, queueInfos, topicInfos] = await Promise.all([
3048
2828
  endpointGenerator.build(context, endpoints, outputDir, {
3049
- provider,
2829
+ ...options,
3050
2830
  enableOpenApi
3051
2831
  }),
3052
- functionGenerator.build(context, functions, outputDir, { provider }),
3053
- cronGenerator.build(context, crons, outputDir, { provider }),
3054
- subscriberGenerator.build(context, subscribers, outputDir, { provider }),
3055
- queueGenerator.build(context, queues, outputDir, { provider }),
3056
- topicGenerator.build(context, topics, outputDir, { provider })
2832
+ functionGenerator.build(context, functions, outputDir, options),
2833
+ cronGenerator.build(context, crons, outputDir, options),
2834
+ subscriberGenerator.build(context, subscribers, outputDir, options),
2835
+ queueGenerator.build(context, queues, outputDir, options),
2836
+ topicGenerator.build(context, topics, outputDir, options)
3057
2837
  ]);
3058
- logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
3059
- const manifestRoutes = assembleManifestField(routes, endpoints);
3060
- const manifestFunctions = assembleManifestField(functionInfos, functions);
3061
- const manifestCrons = assembleManifestField(cronInfos, crons);
3062
- const manifestQueues = assembleManifestField(queueInfos, queues);
3063
- const manifestTopics = assembleManifestField(topicInfos, topics);
3064
- const manifestSubscribers = assembleManifestField(subscriberInfos, subscribers);
3065
- if (provider === "server") {
3066
- const serverRouteField = assembleManifestField(await Promise.all(endpoints.map(async ({ construct }) => ({
3067
- path: construct._path,
3068
- method: construct.method,
3069
- handler: "",
3070
- authorizer: construct.authorizer?.name ?? "none"
3071
- }))), endpoints);
3072
- await generateServerManifest(rootOutputDir, {
3073
- handler: (0, node_path.relative)(appRoot, (0, node_path.join)(outputDir, "app.ts")),
3074
- endpoints: (0, node_path.relative)(appRoot, (0, node_path.join)(outputDir, "endpoints.ts"))
3075
- }, serverRouteField, manifestSubscribers, manifestQueues, manifestTopics);
2838
+ logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${target}`);
2839
+ const fields = {
2840
+ ...context.surface ? { surface: context.surface.id } : {},
2841
+ functions: functionInfos,
2842
+ crons: cronInfos,
2843
+ queues: queueInfos,
2844
+ subscribers: subscriberInfos
2845
+ };
2846
+ if (target === "server") {
2847
+ const handler = (0, node_path.relative)(workspaceRoot, (0, node_path.join)(outputDir, "app.ts"));
2848
+ const built = {
2849
+ ...fields,
2850
+ routes: await Promise.all(endpoints.map(async ({ construct }) => ({
2851
+ path: construct._path,
2852
+ method: construct.method,
2853
+ handler,
2854
+ environment: await construct.getEnvironment({ markOptional: context.markOptional }),
2855
+ dependencies: construct.constructs,
2856
+ authorizer: construct.authorizer?.name ?? "none"
2857
+ })))
2858
+ };
3076
2859
  let masterKey;
3077
2860
  if (context.production?.bundle && !skipBundle) {
3078
2861
  logger$8.log(`\n📦 Bundling production server...`);
@@ -3099,12 +2882,15 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
3099
2882
  logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
3100
2883
  }
3101
2884
  }
3102
- return { masterKey };
3103
- } else await generateAwsManifest(rootOutputDir, manifestRoutes, manifestFunctions, manifestCrons, manifestSubscribers, manifestQueues, manifestTopics, discovered, {
3104
- cache: context.cacheBackend,
3105
- email: context.emailBackend
3106
- });
3107
- return {};
2885
+ return {
2886
+ masterKey,
2887
+ built
2888
+ };
2889
+ }
2890
+ return { built: {
2891
+ ...fields,
2892
+ routes
2893
+ } };
3108
2894
  }
3109
2895
  /**
3110
2896
  * Detect available package manager.
@@ -3126,8 +2912,8 @@ function detectPackageManager$2() {
3126
2912
  *
3127
2913
  * @internal Exported for testing
3128
2914
  */
3129
- function getTurboCommand(pm, filters = []) {
3130
- const filterArgs = (typeof filters === "string" ? [filters] : filters).map((f) => ` --filter=${f}`).join("");
2915
+ function getTurboCommand(pm, filters = [], { only = false } = {}) {
2916
+ const filterArgs = (typeof filters === "string" ? [filters] : filters).map((f) => ` --filter=${f}`).join("") + (only ? " --only" : "");
3131
2917
  switch (pm) {
3132
2918
  case "pnpm": return `pnpm exec turbo run build${filterArgs}`;
3133
2919
  case "yarn": return `yarn turbo run build${filterArgs}`;
@@ -3147,10 +2933,11 @@ function getTurboCommand(pm, filters = []) {
3147
2933
  *
3148
2934
  * @internal Exported for testing
3149
2935
  */
3150
- function turboFilters(workspace) {
2936
+ function turboFilters(workspace, { exclude } = {}) {
3151
2937
  const filters = [];
3152
2938
  const unpackaged = [];
3153
- for (const appName of Object.keys(workspace.apps)) {
2939
+ for (const [appName, app] of Object.entries(workspace.apps)) {
2940
+ if (app.type === exclude) continue;
3154
2941
  const name = appPackageName(workspace, appName);
3155
2942
  if (name) filters.push(name);
3156
2943
  else unpackaged.push(appName);
@@ -3189,31 +2976,59 @@ async function workspaceBuildCommand(workspace, options) {
3189
2976
  if (options.production) logger$8.log(` 🏭 Production mode enabled`);
3190
2977
  const buildOrder = require_workspace.getAppBuildOrder(workspace);
3191
2978
  logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
3192
- const pm = detectPackageManager$2();
3193
- logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
3194
2979
  try {
3195
- const { filters, unpackaged } = turboFilters(workspace);
3196
- if (unpackaged.length > 0) throw new Error(`No package.json for workspace app(s): ${unpackaged.join(", ")}. Each app needs one — turbo builds packages, and gkm reads the name back to know which app it is building.`);
3197
- const turboCommand = getTurboCommand(pm, filters);
3198
- logger$8.log(`Running: ${turboCommand}`);
3199
- await new Promise((resolve, reject) => {
3200
- const child = (0, node_child_process.spawn)(turboCommand, {
3201
- shell: true,
3202
- cwd: workspace.root,
3203
- stdio: "inherit",
3204
- env: {
3205
- ...process.env,
3206
- NODE_ENV: options.production ? "production" : "development"
3207
- }
3208
- });
3209
- child.on("close", (code) => {
3210
- if (code === 0) resolve();
3211
- else reject(/* @__PURE__ */ new Error(`Turbo build failed with exit code ${code}`));
3212
- });
3213
- child.on("error", (err) => {
3214
- reject(err);
2980
+ const target = options.provider ?? require_backends.providerOf(workspace);
2981
+ const builds = [];
2982
+ for (const appName of buildOrder) {
2983
+ const app = workspace.apps[appName];
2984
+ const config = require_workspace.getAppGkmConfig(workspace, appName);
2985
+ if (!app || app.type !== "backend" || !config) continue;
2986
+ logger$8.log(`\n⚙️ ${appName}`);
2987
+ const output = await buildOneApp({
2988
+ config,
2989
+ options,
2990
+ workspace,
2991
+ appRoot: (0, node_path.join)(workspace.root, app.path),
2992
+ target
3215
2993
  });
2994
+ if (output.built) builds.push(output.built);
2995
+ }
2996
+ await writeManifest({
2997
+ workspaceRoot: workspace.root,
2998
+ target,
2999
+ builds,
3000
+ constructs: await require_discover.discover({
3001
+ patterns: require_workspace.allConstructGlobs(workspace),
3002
+ cwd: workspace.root
3003
+ }),
3004
+ backends: backendsOf(workspace)
3216
3005
  });
3006
+ const { filters, unpackaged } = turboFilters(workspace, { exclude: "backend" });
3007
+ if (unpackaged.length > 0) throw new Error(`No package.json for workspace app(s): ${unpackaged.join(", ")}. Each app needs one — turbo builds packages, and gkm reads the name back to know which app it is building.`);
3008
+ const pm = detectPackageManager$2();
3009
+ const turboCommand = getTurboCommand(pm, filters, { only: true });
3010
+ if (filters.length > 0) {
3011
+ logger$8.log(`\n📦 Using ${pm} with Turbo for the other apps...\n`);
3012
+ logger$8.log(`Running: ${turboCommand}`);
3013
+ await new Promise((resolve, reject) => {
3014
+ const child = (0, node_child_process.spawn)(turboCommand, {
3015
+ shell: true,
3016
+ cwd: workspace.root,
3017
+ stdio: "inherit",
3018
+ env: {
3019
+ ...process.env,
3020
+ NODE_ENV: options.production ? "production" : "development"
3021
+ }
3022
+ });
3023
+ child.on("close", (code) => {
3024
+ if (code === 0) resolve();
3025
+ else reject(/* @__PURE__ */ new Error(`Turbo build failed with exit code ${code}`));
3026
+ });
3027
+ child.on("error", (err) => {
3028
+ reject(err);
3029
+ });
3030
+ });
3031
+ }
3217
3032
  for (const [appName, app] of apps) {
3218
3033
  const outputPath = getAppOutputPath(workspace, appName, app);
3219
3034
  results.push({
@@ -3256,23 +3071,11 @@ function getAppOutputPath(workspace, _appName, app) {
3256
3071
  }
3257
3072
  return (0, node_path.join)(appPath, ".gkm");
3258
3073
  }
3259
- /**
3260
- * Format routes for logging, handling PartitionedRoutes.
3261
- */
3074
+ /** Format routes for logging. */
3262
3075
  function formatRoutes(routes) {
3263
- if (require_discover.isPartitionedRoutes(routes)) return `${Array.isArray(routes.paths) ? routes.paths.join(", ") : routes.paths} (partitioned)`;
3264
3076
  return Array.isArray(routes) ? routes.join(", ") : routes;
3265
3077
  }
3266
3078
  /**
3267
- * Assemble a ManifestField from build infos and constructs.
3268
- * If any construct has a partition, returns a Record<string, T[]>.
3269
- * Otherwise, returns a flat T[].
3270
- */
3271
- function assembleManifestField(infos, constructs) {
3272
- if (!hasPartitions(constructs)) return infos;
3273
- return groupInfosByPartition(infos, constructs);
3274
- }
3275
- /**
3276
3079
  * Format a fatal error for display.
3277
3080
  * Always includes the full stack trace since these are process-ending errors.
3278
3081
  */
@@ -3575,6 +3378,19 @@ const PROVISIONERS = {
3575
3378
  return { provides: { [key]: supplied(declaration.id, key, context) } };
3576
3379
  },
3577
3380
  /**
3381
+ * The keyring the stage keeps in its secrets.
3382
+ *
3383
+ * Generated on the first deploy (`withGeneratedSecrets`) and rotated only
3384
+ * by `gkm encryption:rotate`, so a redeploy never changes a key and nothing
3385
+ * written becomes unreadable. Nothing is created in Dokploy: the process
3386
+ * holds the keyring.
3387
+ */
3388
+ encryption: async (declaration, context) => {
3389
+ if (declaration.kind !== "encryption") throw new WrongKind(declaration.kind);
3390
+ const key = (0, _geekmidas_manifest.provideKey)(declaration.id, "url");
3391
+ return { provides: { [key]: supplied(declaration.id, key, context) } };
3392
+ },
3393
+ /**
3578
3394
  * A third party's credentials, as the stage was given them.
3579
3395
  *
3580
3396
  * Nothing is created and nothing derived: the value was issued by somebody
@@ -3944,9 +3760,9 @@ async function provisionDeclared(options) {
3944
3760
  environmentId: options.environmentId,
3945
3761
  stage: options.stage,
3946
3762
  project: workspace.name,
3947
- cache: require_workspace$1.cacheBackendFor(require_workspace$1.providerOf(workspace)),
3948
- events: require_workspace$1.eventsBackendFor(require_workspace$1.providerOf(workspace)),
3949
- storage: require_workspace$1.storageBackendFor(require_workspace$1.providerOf(workspace)),
3763
+ cache: require_backends.cacheBackendFor(require_backends.providerOf(workspace)),
3764
+ events: require_backends.eventsBackendFor(require_backends.providerOf(workspace)),
3765
+ storage: require_backends.storageBackendFor(require_backends.providerOf(workspace)),
3950
3766
  addresses: surfaceAddresses(workspace, manifest, options.appUrls),
3951
3767
  seed: options.seed,
3952
3768
  ...options.supplied ? { supplied: options.supplied } : {},
@@ -5424,9 +5240,8 @@ async function dockerCommand(options) {
5424
5240
  logger$6.log("📦 Detected workspace configuration");
5425
5241
  return workspaceDockerCommand(loadedConfig.workspace);
5426
5242
  }
5427
- const config = await require_config.loadConfig();
5428
- const dockerConfig = resolveDockerConfig(config);
5429
- const healthCheckPath = (typeof config.providers?.server === "object" ? config.providers.server : void 0)?.production?.healthCheck ?? "/health";
5243
+ const dockerConfig = resolveDockerConfig(await require_config.loadConfig());
5244
+ const healthCheckPath = "/health";
5430
5245
  const useSlim = options.slim === true;
5431
5246
  if (useSlim) {
5432
5247
  const distDir = (0, node_path.join)(process.cwd(), ".gkm", "server", "dist");
@@ -6585,6 +6400,8 @@ function validateEnvVars(requiredVars, context) {
6585
6400
  * who could read the repo.
6586
6401
  * - **Each `secret` construct's value** — an auth server's signing secret —
6587
6402
  * random, rather than derived.
6403
+ * - **Each `encryption` construct's keyring** — one random key and an index
6404
+ * key. Later keys are added by `gkm encryption:rotate`, never by a deploy.
6588
6405
  *
6589
6406
  * Both are generated on the first deploy that needs them and written to the
6590
6407
  * stage's store, so every later deploy reads the same values: sessions survive
@@ -6600,10 +6417,10 @@ function withGeneratedSecrets(secrets, manifest) {
6600
6417
  const generated = [];
6601
6418
  const custom = { ...secrets.custom };
6602
6419
  for (const declaration of Object.values(manifest)) {
6603
- if (declaration?.kind !== "secret") continue;
6420
+ if (declaration?.kind !== "secret" && declaration?.kind !== "encryption") continue;
6604
6421
  const key = declaration.provides?.[0];
6605
6422
  if (!key || custom[key]) continue;
6606
- custom[key] = random();
6423
+ custom[key] = declaration.kind === "encryption" ? (0, _geekmidas_constructs_encryption.generateKeyring)() : random();
6607
6424
  generated.push(key);
6608
6425
  }
6609
6426
  const seed = secrets.seed ?? random();
@@ -7432,6 +7249,10 @@ async function workspaceDeployCommand(configured, options) {
7432
7249
  } else logger$4.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
7433
7250
  const dokployApps = appsToDeployNames.filter((name) => {
7434
7251
  const target = workspace.apps[name].resolvedDeployTarget;
7252
+ if (target === "sst") {
7253
+ logger$4.log(` ⚠️ Skipping ${name}: it deploys with SST — run \`gkm build && sst deploy --stage ${stage}\``);
7254
+ return false;
7255
+ }
7435
7256
  if (!require_workspace.isDeployTargetSupported(target)) {
7436
7257
  logger$4.log(` ⚠️ Skipping ${name}: ${require_workspace.getDeployTargetError(target, name)}`);
7437
7258
  return false;
@@ -7952,66 +7773,6 @@ async function createApi(endpoint) {
7952
7773
  });
7953
7774
  }
7954
7775
  /**
7955
- * Update gkm.config.ts with Dokploy configuration
7956
- */
7957
- /**
7958
- * Whether the file's `dokploy` block is more than a flat literal.
7959
- *
7960
- * A spread, a `process.env`, a ternary or a nested object all defeat the
7961
- * single-level regex below — the first two by being expressions it would
7962
- * stringify away, the last by ending the match at the wrong brace.
7963
- */
7964
- function unsafeToRewrite(content) {
7965
- const start = content.indexOf("dokploy:");
7966
- if (start === -1) return false;
7967
- const block = content.slice(start, content.indexOf("\n },", start) + 1);
7968
- return /\.\.\.|process\.env|\?\s|`|\{[^}]*\{/s.test(block);
7969
- }
7970
- async function updateConfig(config, cwd = process.cwd()) {
7971
- const configPath = (0, node_path.join)(cwd, "gkm.config.ts");
7972
- if (!(0, node_fs.existsSync)(configPath)) {
7973
- logger$3.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
7974
- logger$3.log(` providers: {`);
7975
- logger$3.log(` dokploy: {`);
7976
- logger$3.log(` endpoint: '${config.endpoint}',`);
7977
- logger$3.log(` projectId: '${config.projectId}',`);
7978
- logger$3.log(` applicationId: '${config.applicationId}',`);
7979
- logger$3.log(` },`);
7980
- logger$3.log(` },`);
7981
- return;
7982
- }
7983
- const content = await (0, node_fs_promises.readFile)(configPath, "utf-8");
7984
- if (unsafeToRewrite(content)) {
7985
- logger$3.warn("\n gkm.config.ts holds a dokploy block this cannot safely rewrite —\n it contains an expression or a nested object. Leaving it alone.");
7986
- logger$3.log(`\n Discovered, if you want them written down:`);
7987
- logger$3.log(` projectId: '${config.projectId}'`);
7988
- logger$3.log(` applicationId: '${config.applicationId}'`);
7989
- if (config.registryId) logger$3.log(` registryId: '${config.registryId}'`);
7990
- return;
7991
- }
7992
- if (content.includes("dokploy:") && content.includes("applicationId:")) {
7993
- logger$3.log("\n Dokploy config already exists in gkm.config.ts");
7994
- logger$3.log(" Updating with new values...");
7995
- }
7996
- const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
7997
- const dokployConfigStr = `dokploy: {
7998
- endpoint: '${config.endpoint}',
7999
- projectId: '${config.projectId}',
8000
- applicationId: '${config.applicationId}',${registryLine}
8001
- }`;
8002
- let newContent;
8003
- if (content.includes("providers:")) {
8004
- if (content.includes("dokploy:")) newContent = content.replace(/dokploy:\s*\{[^}]*\}/s, dokployConfigStr);
8005
- else newContent = content.replace(/providers:\s*\{/, `providers: {\n\t\t${dokployConfigStr},`);
8006
- } else newContent = content.replace(/}\s*\)\s*;?\s*$/, `
8007
- providers: {
8008
- ${dokployConfigStr},
8009
- },
8010
- });`);
8011
- await (0, node_fs_promises.writeFile)(configPath, newContent);
8012
- logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
8013
- }
8014
- /**
8015
7776
  * Initialize Dokploy deployment configuration
8016
7777
  */
8017
7778
  async function deployInitCommand(options) {
@@ -8064,7 +7825,6 @@ async function deployInitCommand(options) {
8064
7825
  projectId,
8065
7826
  applicationId: application.applicationId
8066
7827
  };
8067
- await updateConfig(config);
8068
7828
  logger$3.log(`\n✅ Dokploy deployment initialized!`);
8069
7829
  logger$3.log(`\n📋 Configuration:`);
8070
7830
  logger$3.log(` Project ID: ${projectId}`);
@@ -9334,26 +9094,26 @@ function loadPackageJson() {
9334
9094
  * Run: pnpm --filter @geekmidas/cli sync-versions
9335
9095
  */
9336
9096
  const GEEKMIDAS_VERSIONS = {
9337
- "@geekmidas/audit": "~10.0.0-alpha.45",
9338
- "@geekmidas/auth": "~10.0.0-alpha.45",
9339
- "@geekmidas/cache": "~10.0.0-alpha.45",
9340
- "@geekmidas/client": "~10.0.0-alpha.45",
9341
- "@geekmidas/cloud": "~10.0.0-alpha.45",
9342
- "@geekmidas/constructs": "~10.0.0-alpha.45",
9343
- "@geekmidas/db": "~10.0.0-alpha.45",
9344
- "@geekmidas/emailkit": "~10.0.0-alpha.45",
9345
- "@geekmidas/envkit": "~10.0.0-alpha.45",
9346
- "@geekmidas/errors": "~10.0.0-alpha.45",
9347
- "@geekmidas/events": "~10.0.0-alpha.45",
9348
- "@geekmidas/logger": "~10.0.0-alpha.45",
9349
- "@geekmidas/manifest": "~10.0.0-alpha.45",
9350
- "@geekmidas/rate-limit": "~10.0.0-alpha.45",
9351
- "@geekmidas/schema": "~10.0.0-alpha.45",
9352
- "@geekmidas/services": "~10.0.0-alpha.45",
9353
- "@geekmidas/storage": "~10.0.0-alpha.45",
9354
- "@geekmidas/studio": "~10.0.0-alpha.45",
9355
- "@geekmidas/telescope": "~10.0.0-alpha.45",
9356
- "@geekmidas/testkit": "~10.0.0-alpha.45",
9097
+ "@geekmidas/audit": "~10.0.0-alpha.47",
9098
+ "@geekmidas/auth": "~10.0.0-alpha.47",
9099
+ "@geekmidas/cache": "~10.0.0-alpha.47",
9100
+ "@geekmidas/client": "~10.0.0-alpha.47",
9101
+ "@geekmidas/cloud": "~10.0.0-alpha.47",
9102
+ "@geekmidas/constructs": "~10.0.0-alpha.47",
9103
+ "@geekmidas/db": "~10.0.0-alpha.47",
9104
+ "@geekmidas/emailkit": "~10.0.0-alpha.47",
9105
+ "@geekmidas/envkit": "~10.0.0-alpha.47",
9106
+ "@geekmidas/errors": "~10.0.0-alpha.47",
9107
+ "@geekmidas/events": "~10.0.0-alpha.47",
9108
+ "@geekmidas/logger": "~10.0.0-alpha.47",
9109
+ "@geekmidas/manifest": "~10.0.0-alpha.47",
9110
+ "@geekmidas/rate-limit": "~10.0.0-alpha.47",
9111
+ "@geekmidas/schema": "~10.0.0-alpha.47",
9112
+ "@geekmidas/services": "~10.0.0-alpha.47",
9113
+ "@geekmidas/storage": "~10.0.0-alpha.47",
9114
+ "@geekmidas/studio": "~10.0.0-alpha.47",
9115
+ "@geekmidas/telescope": "~10.0.0-alpha.47",
9116
+ "@geekmidas/testkit": "~10.0.0-alpha.47",
9357
9117
  "@geekmidas/cli": `~${loadPackageJson().version}`
9358
9118
  };
9359
9119
  //#endregion
@@ -10880,7 +10640,7 @@ function deployPackage(options) {
10880
10640
  case "sst": {
10881
10641
  const v = GEEKMIDAS_VERSIONS;
10882
10642
  return {
10883
- scripts: perStage(options, (stage) => `gkm build --provider aws && sst deploy --stage ${stage}`),
10643
+ scripts: perStage(options, (stage) => `gkm build && sst deploy --stage ${stage}`),
10884
10644
  dependencies: {
10885
10645
  "@geekmidas/cloud": v["@geekmidas/cloud"],
10886
10646
  "@geekmidas/db": v["@geekmidas/db"],
@@ -10945,7 +10705,7 @@ export default $config({
10945
10705
 
10946
10706
  async run() {
10947
10707
  const { App, fromManifest, Stack } = await import('@geekmidas/cloud/sst');
10948
- // Written by \`gkm build --provider aws\`. Imported rather than discovered,
10708
+ // Written by \`gkm build\`. Imported rather than discovered,
10949
10709
  // so SST's toolchain never evaluates the application's own modules.
10950
10710
  const { backends, constructs } = await import('./.gkm/manifest/aws.js');
10951
10711
 
@@ -11183,8 +10943,11 @@ coverage/
11183
10943
  ${isSst ? "\n# SST\n.sst/\n" : ""}`;
11184
10944
  const tsConfig = {
11185
10945
  compilerOptions: {
10946
+ paths: {
10947
+ ...isFullstack ? { [`@${options.name}/constructs/*`]: ["./constructs/*"] } : {},
10948
+ [`@${options.name}/manifest`]: [`./.gkm/manifest/${isSst ? "aws" : "server"}.ts`]
10949
+ },
11186
10950
  ...isFullstack ? {
11187
- paths: { [`@${options.name}/constructs/*`]: ["./constructs/*"] },
11188
10951
  allowImportingTsExtensions: true,
11189
10952
  noEmit: true
11190
10953
  } : {},
@@ -11325,7 +11088,7 @@ export default defineWorkspace({
11325
11088
  // \`production-${options.name}-database\` on Dokploy and on AWS alike.
11326
11089
  name: '${options.name}',
11327
11090
  ${stagesBlock(options.stages)}
11328
-
11091
+ ${deployBlock(options)}
11329
11092
  // Every kind, in every app. A declared database is why a Postgres exists, a
11330
11093
  // declared bucket is why MinIO does, a declared topic is why a broker does —
11331
11094
  // none of it listed here. It is also where the apps come from: a
@@ -11343,6 +11106,18 @@ ${workspaceConstructsGlobs(options.routesStructure, (0, node_path.dirname)(optio
11343
11106
  `;
11344
11107
  }
11345
11108
  /**
11109
+ * Where it deploys, when that is not the default Dokploy: what a bare
11110
+ * `gkm build` builds for, and which backends a cache or a broker resolves to.
11111
+ */
11112
+ function deployBlock(options) {
11113
+ if (options.deployTarget !== "sst") return "";
11114
+ return `
11115
+ // Deployed with \`sst deploy\`, from the manifest \`gkm build\` writes to
11116
+ // \`.gkm/manifest/aws.ts\`. Each construct becomes a managed AWS resource.
11117
+ deploy: { default: 'sst' },
11118
+ `;
11119
+ }
11120
+ /**
11346
11121
  * Where a deployed stage's secrets live. On AWS that is SSM in the stage's own
11347
11122
  * account, which the deploy job reaches with the role it already assumed; a
11348
11123
  * server target keeps the encrypted local file, the default.
@@ -11368,7 +11143,7 @@ function secretsStore(options) {
11368
11143
  */
11369
11144
  function generateRootConstructs(options) {
11370
11145
  if (!options.monorepo || options.template !== "fullstack") return [];
11371
- const { name, frontendFramework } = options;
11146
+ const { frontendFramework } = options;
11372
11147
  const { cache, uploads, mail } = options.constructs;
11373
11148
  const db = databaseFor();
11374
11149
  const files = [];
@@ -12128,7 +11903,7 @@ const serverlessTemplate = {
12128
11903
  },
12129
11904
  scripts: {
12130
11905
  dev: "gkm dev",
12131
- build: "gkm build --provider aws-apigatewayv2",
11906
+ build: "gkm build --provider aws",
12132
11907
  test: "vitest",
12133
11908
  "test:once": "vitest run",
12134
11909
  typecheck: "tsc --noEmit",
@@ -12137,7 +11912,7 @@ const serverlessTemplate = {
12137
11912
  "fmt:check": "biome format ."
12138
11913
  },
12139
11914
  files: (options) => {
12140
- const { loggerType, routesStructure, name } = options;
11915
+ const { loggerType, routesStructure } = options;
12141
11916
  const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
12142
11917
 
12143
11918
  export const logger = createLogger();
@@ -12422,10 +12197,6 @@ export const telescope = new Telescope({
12422
12197
  */
12423
12198
  const OPENAPI_OUTPUT_PATH = "./.gkm/openapi/api.ts";
12424
12199
  /**
12425
- * Endpoints output path (generated by gkm dev/build)
12426
- */
12427
- const ENDPOINTS_OUTPUT_PATH = "./.gkm/server/endpoints.ts";
12428
- /**
12429
12200
  * All available templates
12430
12201
  */
12431
12202
  const templates = {
@@ -12638,10 +12409,7 @@ function generatePackageJson(options, template) {
12638
12409
  private: true,
12639
12410
  type: "module",
12640
12411
  ...!monorepo && options.packageManager === "pnpm" ? { packageManager: PNPM_VERSION } : {},
12641
- exports: {
12642
- "./client": OPENAPI_OUTPUT_PATH,
12643
- "./endpoints": ENDPOINTS_OUTPUT_PATH
12644
- },
12412
+ exports: { "./client": OPENAPI_OUTPUT_PATH },
12645
12413
  scripts,
12646
12414
  dependencies: sortObject(dependencies),
12647
12415
  devDependencies: sortObject(devDependencies)
@@ -15367,7 +15135,7 @@ function printNextSteps(projectName, options, pkgManager) {
15367
15135
  }
15368
15136
  if (options.deployTarget === "sst") {
15369
15137
  console.log("🚀 Deployment (AWS, through SST):");
15370
- for (const stage of deployed) console.log(` ${getRunCommand(pkgManager, `deploy:${stage}`)} # gkm build --provider aws, then sst deploy`);
15138
+ for (const stage of deployed) console.log(` ${getRunCommand(pkgManager, `deploy:${stage}`)} # gkm build, then sst deploy`);
15371
15139
  console.log(` Uses your AWS credentials, in ${options.region}.`);
15372
15140
  if (options.constructs.mail) console.log(" Set MAIL_FROM to a sender verified in SES first.");
15373
15141
  console.log("");
@@ -15754,8 +15522,8 @@ async function reconcileLocal(workspace, stage, options) {
15754
15522
  logger$1.log("🐳 Services already up to date");
15755
15523
  return;
15756
15524
  }
15757
- logger$1.log(`🐳 Services: ${result.plan.containers.join(", ")}`);
15758
- for (const [container, address] of Object.entries(result.addresses)) logger$1.log(` ${container}: ${address}`);
15525
+ logger$1.log("🐳 Services");
15526
+ for (const line of require_workspace$1.describeServices(result.services)) logger$1.log(line);
15759
15527
  const url = result.plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site" || r.kind === "file-server").map((r) => result.env[r.envKey]).find((address) => address?.startsWith("https://"));
15760
15528
  if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
15761
15529
  }
@@ -16102,8 +15870,8 @@ function testDatabases(reconciled) {
16102
15870
  * starts without the reference data the app needs.
16103
15871
  */
16104
15872
  async function migrateTestStage(workspace, env) {
16105
- const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-PJi4b1ga.cjs"));
16106
- const { discover } = await Promise.resolve().then(() => require("./discover-B0f_kuNN.cjs")).then((n) => n.discover_exports);
15873
+ const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-BgEY9pdP.cjs"));
15874
+ const { discover } = await Promise.resolve().then(() => require("./discover-DXNviAh-.cjs")).then((n) => n.discover_exports);
16107
15875
  const sources = {};
16108
15876
  const manifest = await discover({
16109
15877
  patterns: require_workspace$1.constructGlobs(workspace),
@@ -16468,29 +16236,13 @@ program.command("setup").description("Reconcile declared constructs — containe
16468
16236
  process.exit(1);
16469
16237
  }
16470
16238
  });
16471
- program.command("build").description("Build handlers and the manifest from declared constructs").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").option("--mark-optional", "Suffix optional env vars with ? in manifest envVars field (e.g. PORT?)").action(async (options) => {
16239
+ program.command("build").description("Build handlers and the manifest from declared constructs").option("--provider <provider>", "Build for aws or server (default: where gkm.config.ts deploys)").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").option("--mark-optional", "Suffix optional env vars with ? in manifest envVars field (e.g. PORT?)").action(async (options) => {
16472
16240
  try {
16473
16241
  const globalOptions = program.opts();
16474
16242
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16475
- if (options.provider) {
16476
- if (!["aws", "server"].includes(options.provider)) process.exit(1);
16477
- await buildCommand({
16478
- provider: options.provider,
16479
- enableOpenApi: options.enableOpenapi || false,
16480
- production: options.production || false,
16481
- skipBundle: options.skipBundle || false,
16482
- stage: options.stage,
16483
- markOptional: options.markOptional || false
16484
- });
16485
- } else if (options.providers) await buildCommand({
16486
- providers: [...new Set(options.providers.split(",").map((p) => p.trim()))],
16487
- enableOpenApi: options.enableOpenapi || false,
16488
- production: options.production || false,
16489
- skipBundle: options.skipBundle || false,
16490
- stage: options.stage,
16491
- markOptional: options.markOptional || false
16492
- });
16493
- else await buildCommand({
16243
+ if (options.provider !== void 0 && !isMainProvider(options.provider)) throw new UnknownBuildProvider(options.provider);
16244
+ await buildCommand({
16245
+ ...options.provider ? { provider: options.provider } : {},
16494
16246
  enableOpenApi: options.enableOpenapi || false,
16495
16247
  production: options.production || false,
16496
16248
  skipBundle: options.skipBundle || false,
@@ -16549,7 +16301,7 @@ program.command("migrate").description("Apply each database construct's migratio
16549
16301
  try {
16550
16302
  const globalOptions = program.opts();
16551
16303
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16552
- const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-PJi4b1ga.cjs"));
16304
+ const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-BgEY9pdP.cjs"));
16553
16305
  await migrateCommand({
16554
16306
  ...options,
16555
16307
  ...construct ? { construct } : {}
@@ -16563,7 +16315,7 @@ program.command("seed").description("Migrate, then run each database construct's
16563
16315
  try {
16564
16316
  const globalOptions = program.opts();
16565
16317
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16566
- const { seedCommand } = await Promise.resolve().then(() => require("./migrate-PJi4b1ga.cjs"));
16318
+ const { seedCommand } = await Promise.resolve().then(() => require("./migrate-BgEY9pdP.cjs"));
16567
16319
  await seedCommand({
16568
16320
  ...options,
16569
16321
  ...construct ? { construct } : {}
@@ -16577,7 +16329,7 @@ program.command("migration").description("Write a construct's next migration: an
16577
16329
  try {
16578
16330
  const globalOptions = program.opts();
16579
16331
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16580
- const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-PJi4b1ga.cjs"));
16332
+ const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-BgEY9pdP.cjs"));
16581
16333
  await migrationCommand({
16582
16334
  construct,
16583
16335
  ...name ? { name } : {}
@@ -16671,6 +16423,28 @@ program.command("secrets:set").description("Set a custom secret for a stage").ar
16671
16423
  process.exit(1);
16672
16424
  }
16673
16425
  });
16426
+ program.command("encryption:rotate").description("Add a new current key to an Encryption construct's keyring on a server stage").argument("<construct>", "The Encryption construct (e.g. Pii)").requiredOption("--stage <stage>", "A deployed stage").action(async (construct, options) => {
16427
+ try {
16428
+ const globalOptions = program.opts();
16429
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16430
+ const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-C7OGMve_.cjs"));
16431
+ await encryptionRotateCommand(construct, options);
16432
+ } catch (error) {
16433
+ console.error(formatError(error));
16434
+ process.exit(1);
16435
+ }
16436
+ });
16437
+ program.command("encryption:retire").description("Remove an old key from an Encryption construct's keyring, once nothing is under it").argument("<construct>", "The Encryption construct (e.g. Pii)").argument("<key>", "The key to retire (e.g. k1)").requiredOption("--stage <stage>", "A deployed stage").action(async (construct, key, options) => {
16438
+ try {
16439
+ const globalOptions = program.opts();
16440
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16441
+ const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-C7OGMve_.cjs"));
16442
+ await encryptionRetireCommand(construct, key, options);
16443
+ } catch (error) {
16444
+ console.error(formatError(error));
16445
+ process.exit(1);
16446
+ }
16447
+ });
16674
16448
  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) => {
16675
16449
  try {
16676
16450
  const globalOptions = program.opts();
@@ -16716,7 +16490,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16716
16490
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
16717
16491
  process.exit(1);
16718
16492
  }
16719
- const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-dV6f-3vv.cjs")).then((n) => n.workspace_exports);
16493
+ const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-vafrzZTC.cjs")).then((n) => n.workspace_exports);
16720
16494
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
16721
16495
  if (!result) {
16722
16496
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -16775,7 +16549,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
16775
16549
  try {
16776
16550
  const globalOptions = program.opts();
16777
16551
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16778
- const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-BAr8WGkR.cjs"));
16552
+ const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-BrI1tv6u.cjs"));
16779
16553
  await deployGithubCommand(options);
16780
16554
  } catch (error) {
16781
16555
  console.error(formatError(error));