@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.mjs CHANGED
@@ -1,21 +1,22 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
3
- import { c as isPartitionedRoutes, i as ConstructGenerator, t as discover } from "./discover-BLhf7V_J.mjs";
4
- import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, p as derivedApps, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-5watB1D0.mjs";
5
- import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-DOmIhiau.mjs";
3
+ import { i as ConstructGenerator, t as discover } from "./discover-Cf2jAcgF.mjs";
4
+ import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, p as derivedApps, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-D8J5rrTb.mjs";
5
+ import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-DNzWgM1J.mjs";
6
6
  import { a as withCustomSecret, i as toEmbeddableSecrets, n as maskPassword, t as initStageSecrets } from "./storage-BzaUxmqe.mjs";
7
7
  import { a as getKeyPath, i as FileSecretsStore, n as secretsStoreFor } from "./store-DeGATCS-.mjs";
8
- import { C as createEntryWrapper, D as loadSecretsForApp, E as loadEnvFiles, O as prepareEntryCredentials, S as createCredentialsPreload, T as isPortAvailable, _ as primaryPortKey, a as reconcileWorkspace, b as providerOf, c as LOCAL_CA_PATH, d as postgresDatabaseNames, f as quoteIdentifier, g as fakeKey, h as rootSite, i as derivedContainers, l as forgetState, m as resourceName, n as backendsOf, p as pgClient, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as cacheBackendFor, w as findAvailablePort, x as storageBackendFor, y as eventsBackendFor } from "./workspace-BKytVCzb.mjs";
9
- import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-CEh3EKN3.mjs";
8
+ import { C as loadEnvFiles, S as isPortAvailable, T as prepareEntryCredentials, _ as fakeKey, a as reconcileWorkspace, b as createEntryWrapper, c as LOCAL_CA_PATH, d as postgresDatabaseNames, f as quoteIdentifier, g as describeServices, h as rootSite, i as derivedContainers, l as forgetState, m as resourceName, n as backendsOf$1, p as pgClient, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as primaryPortKey, w as loadSecretsForApp, x as findAvailablePort, y as createCredentialsPreload } from "./workspace-cb6U29EL.mjs";
9
+ import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-BzIGy8KE.mjs";
10
10
  import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
11
- import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-ZqOuNvKD.mjs";
12
- import { i as withRoutes, r as withCompute } from "./emit-Bzq7JsMP.mjs";
11
+ import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-WfT-eWlm.mjs";
12
+ import { i as storageBackendFor, n as eventsBackendFor, r as providerOf, t as cacheBackendFor } from "./backends-CSuLl0YP.mjs";
13
+ import { n as withCompute, r as withRoutes, t as manifestModule } from "./emit-C1e8qD08.mjs";
13
14
  import { createRequire } from "node:module";
14
15
  import { Command } from "commander";
15
16
  import { stdin, stdout } from "node:process";
16
17
  import * as readline from "node:readline/promises";
17
18
  import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
18
- import { mkdir, readFile, writeFile } from "node:fs/promises";
19
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
19
20
  import { homedir } from "node:os";
20
21
  import { basename, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
21
22
  import { execFile, execSync, spawn } from "node:child_process";
@@ -24,6 +25,7 @@ import fg from "fast-glob";
24
25
  import chokidar from "chokidar";
25
26
  import { createCipheriv, createHash, randomBytes } from "node:crypto";
26
27
  import { fileURLToPath, pathToFileURL } from "node:url";
28
+ import { generateKeyring } from "@geekmidas/constructs/encryption";
27
29
  import { ownerRole, readerRole, roleStatements } from "@geekmidas/db/pg/roles";
28
30
  import { Client } from "pg";
29
31
  import { parse as parse$1, parseDocument } from "yaml";
@@ -40,7 +42,7 @@ import { Queue } from "@geekmidas/constructs/queue";
40
42
  import { Topic } from "@geekmidas/constructs/topic";
41
43
  import { lookup } from "node:dns/promises";
42
44
  //#region package.json
43
- var version = "10.0.0-alpha.45";
45
+ var version = "10.0.0-alpha.47";
44
46
  //#endregion
45
47
  //#region src/auth/credentials.ts
46
48
  /**
@@ -174,7 +176,7 @@ async function getHostingerToken(options) {
174
176
  }
175
177
  //#endregion
176
178
  //#region src/auth/index.ts
177
- const logger$13 = console;
179
+ const logger$12 = console;
178
180
  /**
179
181
  * Validate Dokploy token by making a test API call
180
182
  */
@@ -241,52 +243,52 @@ async function prompt$1(message, hidden = false) {
241
243
  async function loginCommand(options) {
242
244
  const { provider, token: providedToken, endpoint: providedEndpoint } = options;
243
245
  if (provider === "dokploy") {
244
- logger$13.log("\n🔐 Logging in to Dokploy...\n");
246
+ logger$12.log("\n🔐 Logging in to Dokploy...\n");
245
247
  let endpoint = providedEndpoint;
246
248
  if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
247
249
  endpoint = endpoint.replace(/\/$/, "");
248
250
  try {
249
251
  new URL(endpoint);
250
252
  } catch {
251
- logger$13.error("Invalid URL format");
253
+ logger$12.error("Invalid URL format");
252
254
  process.exit(1);
253
255
  }
254
256
  let token = providedToken;
255
257
  if (!token) {
256
- logger$13.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
258
+ logger$12.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
257
259
  token = await prompt$1("API Token: ", true);
258
260
  }
259
261
  if (!token) {
260
- logger$13.error("Token is required");
262
+ logger$12.error("Token is required");
261
263
  process.exit(1);
262
264
  }
263
- logger$13.log("\nValidating credentials...");
265
+ logger$12.log("\nValidating credentials...");
264
266
  if (!await validateDokployToken(endpoint, token)) {
265
- logger$13.error("\n✗ Invalid credentials. Please check your token and try again.");
267
+ logger$12.error("\n✗ Invalid credentials. Please check your token and try again.");
266
268
  process.exit(1);
267
269
  }
268
270
  await storeDokployCredentials(token, endpoint);
269
- logger$13.log("\n✓ Successfully logged in to Dokploy!");
270
- logger$13.log(` Endpoint: ${endpoint}`);
271
- logger$13.log(` Credentials stored in: ${getCredentialsPath()}`);
272
- logger$13.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
271
+ logger$12.log("\n✓ Successfully logged in to Dokploy!");
272
+ logger$12.log(` Endpoint: ${endpoint}`);
273
+ logger$12.log(` Credentials stored in: ${getCredentialsPath()}`);
274
+ logger$12.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
273
275
  }
274
276
  if (provider === "hostinger") {
275
- logger$13.log("\n🔐 Logging in to Hostinger...\n");
277
+ logger$12.log("\n🔐 Logging in to Hostinger...\n");
276
278
  let token = providedToken;
277
279
  if (!token) {
278
- logger$13.log("\nGenerate a token at: https://hpanel.hostinger.com/profile/api\n");
280
+ logger$12.log("\nGenerate a token at: https://hpanel.hostinger.com/profile/api\n");
279
281
  token = await prompt$1("API Token: ", true);
280
282
  }
281
283
  if (!token) {
282
- logger$13.error("Token is required");
284
+ logger$12.error("Token is required");
283
285
  process.exit(1);
284
286
  }
285
287
  await storeHostingerToken(token);
286
- logger$13.log("\n✓ Hostinger token stored.");
287
- logger$13.log(` Credentials stored in: ${getCredentialsPath()}`);
288
- 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.");
289
- logger$13.log("\nYou can now use it as a DNS provider without setting HOSTINGER_API_TOKEN.");
288
+ logger$12.log("\n✓ Hostinger token stored.");
289
+ logger$12.log(` Credentials stored in: ${getCredentialsPath()}`);
290
+ 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.");
291
+ logger$12.log("\nYou can now use it as a DNS provider without setting HOSTINGER_API_TOKEN.");
290
292
  }
291
293
  }
292
294
  /**
@@ -295,27 +297,27 @@ async function loginCommand(options) {
295
297
  async function logoutCommand(options) {
296
298
  const { provider = "dokploy" } = options;
297
299
  if (provider === "all") {
298
- if (await removeDokployCredentials()) logger$13.log("\n✓ Logged out from all services");
299
- else logger$13.log("\nNo stored credentials found");
300
+ if (await removeDokployCredentials()) logger$12.log("\n✓ Logged out from all services");
301
+ else logger$12.log("\nNo stored credentials found");
300
302
  return;
301
303
  }
302
304
  if (provider === "dokploy") {
303
- if (await removeDokployCredentials()) logger$13.log("\n✓ Logged out from Dokploy");
304
- else logger$13.log("\nNo Dokploy credentials found");
305
+ if (await removeDokployCredentials()) logger$12.log("\n✓ Logged out from Dokploy");
306
+ else logger$12.log("\nNo Dokploy credentials found");
305
307
  }
306
308
  }
307
309
  /**
308
310
  * Show current login status
309
311
  */
310
312
  async function whoamiCommand() {
311
- logger$13.log("\n📋 Current credentials:\n");
313
+ logger$12.log("\n📋 Current credentials:\n");
312
314
  const dokploy = await getDokployCredentials();
313
315
  if (dokploy) {
314
- logger$13.log(" Dokploy:");
315
- logger$13.log(` Endpoint: ${dokploy.endpoint}`);
316
- logger$13.log(` Token: ${maskToken(dokploy.token)}`);
317
- } else logger$13.log(" Dokploy: Not logged in");
318
- logger$13.log(`\n Credentials file: ${getCredentialsPath()}`);
316
+ logger$12.log(" Dokploy:");
317
+ logger$12.log(` Endpoint: ${dokploy.endpoint}`);
318
+ logger$12.log(` Token: ${maskToken(dokploy.token)}`);
319
+ } else logger$12.log(" Dokploy: Not logged in");
320
+ logger$12.log(`\n Credentials file: ${getCredentialsPath()}`);
319
321
  }
320
322
  /**
321
323
  * Mask a token for display
@@ -325,85 +327,14 @@ function maskToken(token) {
325
327
  return `${token.slice(0, 4)}...${token.slice(-4)}`;
326
328
  }
327
329
  //#endregion
328
- //#region src/build/providerResolver.ts
329
- /**
330
- * Resolves provider configuration from the new simplified system
331
- * to the internal legacy format for backward compatibility
332
- */
333
- function resolveProviders(config, options) {
334
- const providers = [];
335
- let enableOpenApi = options.enableOpenApi || false;
336
- if (options.providers) return {
337
- providers: options.providers,
338
- enableOpenApi
339
- };
340
- if (options.provider) {
341
- const resolvedProviders = resolveMainProvider(options.provider, config.providers);
342
- providers.push(...resolvedProviders.providers);
343
- enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
344
- } else if (config.providers) {
345
- const resolvedProviders = resolveAllConfiguredProviders(config.providers);
346
- providers.push(...resolvedProviders.providers);
347
- enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
348
- } else providers.push("aws-apigatewayv2", "aws-lambda");
349
- return {
350
- providers: [...new Set(providers)],
351
- enableOpenApi
352
- };
353
- }
354
- function resolveMainProvider(mainProvider, providersConfig) {
355
- const providers = [];
356
- let enableOpenApi = false;
357
- if (mainProvider === "aws") {
358
- const awsConfig = providersConfig?.aws;
359
- if (awsConfig?.apiGateway) {
360
- if (isEnabled(awsConfig.apiGateway.v1)) providers.push("aws-apigatewayv1");
361
- if (isEnabled(awsConfig.apiGateway.v2)) providers.push("aws-apigatewayv2");
362
- } else providers.push("aws-apigatewayv2");
363
- if (awsConfig?.lambda) {
364
- if (isEnabled(awsConfig.lambda.functions) || isEnabled(awsConfig.lambda.crons)) providers.push("aws-lambda");
365
- } else providers.push("aws-lambda");
366
- } else if (mainProvider === "server") {
367
- providers.push("server");
368
- const serverConfig = providersConfig?.server;
369
- if (typeof serverConfig === "object" && serverConfig?.enableOpenApi) enableOpenApi = true;
370
- }
371
- return {
372
- providers,
373
- enableOpenApi
374
- };
375
- }
376
- function resolveAllConfiguredProviders(providersConfig) {
377
- const providers = [];
378
- let enableOpenApi = false;
379
- if (providersConfig.aws) {
380
- const awsProviders = resolveMainProvider("aws", providersConfig);
381
- providers.push(...awsProviders.providers);
382
- }
383
- if (providersConfig.server && isEnabled(providersConfig.server)) {
384
- providers.push("server");
385
- if (typeof providersConfig.server === "object" && providersConfig.server.enableOpenApi) enableOpenApi = true;
386
- }
387
- return {
388
- providers,
389
- enableOpenApi
390
- };
391
- }
392
- function isEnabled(config) {
393
- if (config === void 0) return false;
394
- if (typeof config === "boolean") return config;
395
- return config.enabled !== false;
396
- }
397
- //#endregion
398
330
  //#region src/workspace/client-generator.ts
399
331
  /**
400
332
  * Normalize routes to an array of patterns.
401
- * Handles string, string[], and PartitionedRoutes (extracts paths).
333
+ * Handles a glob or a list of them.
402
334
  * @internal Exported for use in dev command
403
335
  */
404
336
  function normalizeRoutes(routes) {
405
337
  if (!routes) return [];
406
- if (isPartitionedRoutes(routes)) return Array.isArray(routes.paths) ? routes.paths : [routes.paths];
407
338
  return Array.isArray(routes) ? routes : [routes];
408
339
  }
409
340
  //#endregion
@@ -422,7 +353,7 @@ function normalizeRoutes(routes) {
422
353
  * trade than one that tells you what to type. Caddy's own `caddy trust` draws
423
354
  * the line in the same place.
424
355
  */
425
- const logger$12 = console;
356
+ const logger$11 = console;
426
357
  const run = promisify(execFile);
427
358
  /**
428
359
  * How each platform is told about a new authority.
@@ -474,15 +405,15 @@ async function trustCommand(options = {}) {
474
405
  const certificate = join(workspace.root, LOCAL_CA_PATH);
475
406
  if (!existsSync(certificate)) throw new NoLocalAuthority(certificate);
476
407
  const { describe, steps } = installation(process.platform, certificate);
477
- logger$12.log(`\n🔐 Trusting the local authority in ${describe}`);
478
- logger$12.log(` ${certificate}\n`);
408
+ logger$11.log(`\n🔐 Trusting the local authority in ${describe}`);
409
+ logger$11.log(` ${certificate}\n`);
479
410
  if (options.dryRun) {
480
- for (const [command, args] of steps) logger$12.log(` ${command} ${args.join(" ")}`);
411
+ for (const [command, args] of steps) logger$11.log(` ${command} ${args.join(" ")}`);
481
412
  return;
482
413
  }
483
- logger$12.log(" This needs an admin password.\n");
414
+ logger$11.log(" This needs an admin password.\n");
484
415
  for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
485
- logger$12.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
416
+ logger$11.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
486
417
  }
487
418
  /**
488
419
  * Trust the authority if this machine lacks it and its owner agrees.
@@ -499,7 +430,7 @@ async function ensureTrusted(root, url, options = {}) {
499
430
  if (await isTrusted(url)) return;
500
431
  if (!options.assumeYes) {
501
432
  if (!process.stdin.isTTY) {
502
- logger$12.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
433
+ logger$11.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
503
434
  return;
504
435
  }
505
436
  if (await declined(root)) return;
@@ -511,7 +442,7 @@ async function ensureTrusted(root, url, options = {}) {
511
442
  });
512
443
  if (!install) {
513
444
  await recordDeclined(root);
514
- logger$12.log(" Skipped. Run \"gkm trust\" later.");
445
+ logger$11.log(" Skipped. Run \"gkm trust\" later.");
515
446
  return;
516
447
  }
517
448
  }
@@ -1061,7 +992,7 @@ function closeFakes(served) {
1061
992
  }
1062
993
  //#endregion
1063
994
  //#region src/exec/index.ts
1064
- const logger$11 = console;
995
+ const logger$10 = console;
1065
996
  /** `gkm exec` was given nothing to run. */
1066
997
  var NoCommandSpecified = class extends Error {
1067
998
  constructor() {
@@ -1084,17 +1015,17 @@ async function execCommand(commandArgs, options = {}) {
1084
1015
  const cwd = options.cwd ?? process.cwd();
1085
1016
  if (!commandArgs[0]) throw new NoCommandSpecified();
1086
1017
  const defaultEnv = loadEnvFiles(".env");
1087
- if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1018
+ if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1088
1019
  const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
1089
- if (appName) logger$11.log(`📦 App: ${appName}`);
1020
+ if (appName) logger$10.log(`📦 App: ${appName}`);
1090
1021
  const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
1091
- if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
1022
+ if (secretCount > 0) logger$10.log(`🔐 Loaded ${secretCount} secret(s)`);
1092
1023
  const preloadDir = join(cwd, ".gkm");
1093
1024
  await mkdir(preloadDir, { recursive: true });
1094
1025
  const preloadPath = join(preloadDir, "credentials-preload.mjs");
1095
1026
  await createCredentialsPreload(preloadPath, secretsJsonPath);
1096
1027
  const [cmd, ...args] = commandArgs;
1097
- logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
1028
+ logger$10.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
1098
1029
  const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
1099
1030
  const child = spawn(cmd, args, {
1100
1031
  cwd,
@@ -1108,7 +1039,7 @@ async function execCommand(commandArgs, options = {}) {
1108
1039
  const exitCode = await new Promise((resolve) => {
1109
1040
  child.on("close", (code) => resolve(code ?? 0));
1110
1041
  child.on("error", (error) => {
1111
- logger$11.error(`Failed to run command: ${error.message}`);
1042
+ logger$10.error(`Failed to run command: ${error.message}`);
1112
1043
  resolve(1);
1113
1044
  });
1114
1045
  });
@@ -1116,7 +1047,7 @@ async function execCommand(commandArgs, options = {}) {
1116
1047
  }
1117
1048
  //#endregion
1118
1049
  //#region src/dev/index.ts
1119
- const logger$10 = console;
1050
+ const logger$9 = console;
1120
1051
  /**
1121
1052
  * Normalize telescope configuration
1122
1053
  * @internal Exported for testing
@@ -1181,39 +1112,36 @@ function normalizeHooksConfig(config, cwd = process.cwd()) {
1181
1112
  return { serverHooksPath: resolve(cwd, serverPath) };
1182
1113
  }
1183
1114
  /**
1184
- * Normalize production configuration
1115
+ * What a `--production` build is.
1116
+ *
1117
+ * A server runs its worker's background work itself — queues polled, crons
1118
+ * scheduled, subscribers drained — so the production entry wires all three.
1119
+ * The build writes their files on every server build, empty when there is
1120
+ * nothing, so including them costs an app without any nothing.
1121
+ *
1185
1122
  * @internal Exported for testing
1186
1123
  */
1187
- function normalizeProductionConfig(cliProduction, configProduction) {
1124
+ function normalizeProductionConfig(cliProduction) {
1188
1125
  if (!cliProduction) return;
1189
- const config = configProduction ?? {};
1190
1126
  return {
1191
1127
  enabled: true,
1192
- bundle: config.bundle ?? true,
1193
- minify: config.minify ?? true,
1194
- healthCheck: config.healthCheck ?? "/health",
1195
- gracefulShutdown: config.gracefulShutdown ?? true,
1196
- external: config.external ?? [],
1197
- subscribers: config.subscribers ?? "exclude",
1198
- openapi: config.openapi ?? false,
1199
- optimizedHandlers: config.optimizedHandlers ?? true
1128
+ bundle: true,
1129
+ minify: true,
1130
+ healthCheck: "/health",
1131
+ gracefulShutdown: true,
1132
+ external: [],
1133
+ subscribers: "include",
1134
+ openapi: false,
1135
+ optimizedHandlers: true
1200
1136
  };
1201
1137
  }
1202
- /**
1203
- * Get production config from GkmConfig
1204
- * @internal
1205
- */
1206
- function getProductionConfigFromGkm(config) {
1207
- const serverConfig = config.providers?.server;
1208
- if (typeof serverConfig === "object") return serverConfig.production;
1209
- }
1210
1138
  /** Read by the generated server: no subscriber is subscribed or polled. */
1211
1139
  const SUBSCRIBERS_ENV = "GKM_SUBSCRIBERS";
1212
1140
  async function devCommand(options) {
1213
1141
  if (options.subscribers === false) process.env[SUBSCRIBERS_ENV] = "off";
1214
1142
  if (options.entry) return entryDevCommand(options);
1215
1143
  const defaultEnv = loadEnvFiles(".env");
1216
- if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1144
+ if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1217
1145
  const appConfig = await loadAppConfig().catch((error) => {
1218
1146
  if (error instanceof NotInAnApp || error instanceof NotABackendApp) return;
1219
1147
  throw error;
@@ -1229,7 +1157,7 @@ async function devCommand(options) {
1229
1157
  const workspaceAppPort = appConfig.app.port;
1230
1158
  const workspace = appConfig.workspace;
1231
1159
  if (appConfig.app.entry) {
1232
- logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
1160
+ logger$9.log(`📄 Using entry point: ${appConfig.app.entry}`);
1233
1161
  return entryDevCommand({
1234
1162
  ...options,
1235
1163
  entry: appConfig.app.entry,
@@ -1239,19 +1167,18 @@ async function devCommand(options) {
1239
1167
  }
1240
1168
  if (config.env) {
1241
1169
  const { loaded, missing } = loadEnvFiles(config.env, appRoot);
1242
- if (loaded.length > 0) logger$10.log(`📦 Loaded env: ${loaded.join(", ")}`);
1243
- if (missing.length > 0) logger$10.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
1170
+ if (loaded.length > 0) logger$9.log(`📦 Loaded env: ${loaded.join(", ")}`);
1171
+ if (missing.length > 0) logger$9.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
1244
1172
  }
1245
- const resolved = resolveProviders(config, { provider: "server" });
1246
1173
  const telescope = normalizeTelescopeConfig(config.telescope);
1247
1174
  const studio = normalizeStudioConfig(config.studio);
1248
1175
  const hooks = normalizeHooksConfig(config.hooks, appRoot);
1249
- const enableOpenApi = resolveOpenApiConfig(config).enabled || resolved.enableOpenApi;
1176
+ const enableOpenApi = resolveOpenApiConfig(config).enabled;
1250
1177
  const build = (bustCache = false) => quietly(() => buildApp({
1251
1178
  config,
1252
1179
  workspaceRoot: workspace?.root ?? secretsRoot,
1253
1180
  appRoot,
1254
- providers: ["server"],
1181
+ target: "server",
1255
1182
  enableOpenApi,
1256
1183
  cacheBackend: cacheBackendFor(providerOf(workspace ?? config)),
1257
1184
  telescope,
@@ -1271,9 +1198,9 @@ async function devCommand(options) {
1271
1198
  stage: workspace.stages.local,
1272
1199
  ...options.fake ? { fake: true } : {}
1273
1200
  });
1274
- if (reconciled.changed && reconciled.plan.containers.length > 0) {
1275
- logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
1276
- for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
1201
+ if (reconciled.services.length > 0) {
1202
+ logger$9.log("🐳 Services");
1203
+ for (const line of describeServices(reconciled.services)) logger$9.log(line);
1277
1204
  }
1278
1205
  Object.assign(appSecrets, reconciled.env);
1279
1206
  const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && appKey(r.id) === workspaceAppName);
@@ -1286,7 +1213,7 @@ async function devCommand(options) {
1286
1213
  secretsJsonPath = join(secretsDir, secretsFileName);
1287
1214
  await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
1288
1215
  }
1289
- 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);
1216
+ const devServer = new DevServer(options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? workspaceAppPort !== void 0, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath, initial.selfServing !== void 0);
1290
1217
  devServer.label = workspaceAppName ?? "server";
1291
1218
  devServer.publicUrl = publicUrl;
1292
1219
  await devServer.start();
@@ -1307,11 +1234,11 @@ async function devCommand(options) {
1307
1234
  cwd: appRoot
1308
1235
  });
1309
1236
  watcher.on("error", (error) => {
1310
- logger$10.error("❌ Watcher error:", error);
1237
+ logger$9.error("❌ Watcher error:", error);
1311
1238
  });
1312
1239
  let rebuildTimeout = null;
1313
1240
  watcher.on("change", async (path) => {
1314
- logger$10.log(`🔄 ${path} changed — rebuilding`);
1241
+ logger$9.log(`🔄 ${path} changed — rebuilding`);
1315
1242
  if (rebuildTimeout) clearTimeout(rebuildTimeout);
1316
1243
  rebuildTimeout = setTimeout(async () => {
1317
1244
  try {
@@ -1320,8 +1247,8 @@ async function devCommand(options) {
1320
1247
  await devServer.restart();
1321
1248
  } catch (error) {
1322
1249
  const err = error;
1323
- logger$10.error(`❌ Rebuild failed: ${err.message}`);
1324
- if (err.stack) logger$10.error(err.stack);
1250
+ logger$9.error(`❌ Rebuild failed: ${err.message}`);
1251
+ if (err.stack) logger$9.error(err.stack);
1325
1252
  }
1326
1253
  }, 300);
1327
1254
  });
@@ -1329,9 +1256,9 @@ async function devCommand(options) {
1329
1256
  const shutdown = () => {
1330
1257
  if (isShuttingDown) return;
1331
1258
  isShuttingDown = true;
1332
- logger$10.log("\n🛑 Shutting down...");
1259
+ logger$9.log("\n🛑 Shutting down...");
1333
1260
  Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
1334
- logger$10.error("Error during shutdown:", err);
1261
+ logger$9.error("Error during shutdown:", err);
1335
1262
  }).finally(() => {
1336
1263
  process.exit(0);
1337
1264
  });
@@ -1533,7 +1460,7 @@ async function workspaceDevCommand(workspace, options) {
1533
1460
  const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
1534
1461
  const conflicts = checkPortConflicts(workspace);
1535
1462
  if (conflicts.length > 0) {
1536
- for (const conflict of conflicts) logger$10.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1463
+ for (const conflict of conflicts) logger$9.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
1537
1464
  throw new Error("Port conflicts detected. Please assign unique ports to each app.");
1538
1465
  }
1539
1466
  if (frontendApps.length > 0) {
@@ -1542,10 +1469,10 @@ async function workspaceDevCommand(workspace, options) {
1542
1469
  for (const result of validationResults) {
1543
1470
  if (!result.valid) {
1544
1471
  hasErrors = true;
1545
- logger$10.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1546
- for (const error of result.errors) logger$10.error(` • ${error}`);
1472
+ logger$9.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
1473
+ for (const error of result.errors) logger$9.error(` • ${error}`);
1547
1474
  }
1548
- for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
1475
+ for (const warning of result.warnings) logger$9.warn(` ⚠️ ${result.appName}: ${warning}`);
1549
1476
  }
1550
1477
  if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
1551
1478
  }
@@ -1554,21 +1481,21 @@ async function workspaceDevCommand(workspace, options) {
1554
1481
  stage: workspace.stages.local,
1555
1482
  ...options.fake ? { fake: true } : {}
1556
1483
  });
1557
- if (reconciled.changed && reconciled.plan.containers.length > 0) {
1558
- logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
1559
- for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
1484
+ if (reconciled.services.length > 0) {
1485
+ logger$9.log("🐳 Services");
1486
+ for (const line of describeServices(reconciled.services)) logger$9.log(line);
1560
1487
  }
1561
1488
  const secretsEnv = {
1562
1489
  ...rawSecrets,
1563
1490
  ...reconciled.env
1564
1491
  };
1565
1492
  const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
1566
- for (const { id, port } of fakes) logger$10.log(`🎭 ${id} fake: http://localhost:${port}`);
1493
+ for (const { id, port } of fakes) logger$9.log(`🎭 ${id} fake: http://localhost:${port}`);
1567
1494
  if (options.migrate || options.seed) {
1568
- const { prepareDevDatabases } = await import("./migrate-BRklCm7b.mjs");
1495
+ const { prepareDevDatabases } = await import("./migrate-DhxTIA0z.mjs");
1569
1496
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1570
- } else await import("./migrate-BRklCm7b.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1571
- logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1497
+ } else await import("./migrate-DhxTIA0z.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1498
+ logger$9.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1572
1499
  });
1573
1500
  const appUrls = {};
1574
1501
  for (const resource of reconciled.plan.resources) {
@@ -1589,11 +1516,11 @@ async function workspaceDevCommand(workspace, options) {
1589
1516
  else {
1590
1517
  const { filters, unpackaged } = turboFilters(workspace);
1591
1518
  turboFilter = filters.flatMap((name) => ["--filter", name]);
1592
- if (unpackaged.length > 0) logger$10.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
1519
+ if (unpackaged.length > 0) logger$9.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
1593
1520
  }
1594
1521
  const buildOrder = getAppBuildOrder(workspace);
1595
1522
  const width = Math.max(...buildOrder.map((name) => name.length));
1596
- logger$10.log(`\n${workspace.name}: ${appCount} app(s)`);
1523
+ logger$9.log(`\n${workspace.name}: ${appCount} app(s)`);
1597
1524
  for (const appName of buildOrder) {
1598
1525
  const app = workspace.apps[appName];
1599
1526
  if (!app) continue;
@@ -1601,14 +1528,14 @@ async function workspaceDevCommand(workspace, options) {
1601
1528
  const own = reconciled.plan.resources.find((r) => r.kind === "mobile-app" && appKey(r.id) === appName);
1602
1529
  const scheme = own ? reconciled.env[own.envKey] : void 0;
1603
1530
  const device = Object.entries(reconciled.env).find(([key]) => key.endsWith("_DEVICE_URL"))?.[1];
1604
- logger$10.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
1531
+ logger$9.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
1605
1532
  continue;
1606
1533
  }
1607
1534
  const local = `http://localhost:${app.port}`;
1608
1535
  const url = Object.entries(appUrls).find(([id]) => appKey(id) === appName)?.[1];
1609
- logger$10.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
1536
+ logger$9.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
1610
1537
  }
1611
- logger$10.log("");
1538
+ logger$9.log("");
1612
1539
  const configFiles = [
1613
1540
  "gkm.config.ts",
1614
1541
  "gkm.config.js",
@@ -1655,7 +1582,7 @@ async function workspaceDevCommand(workspace, options) {
1655
1582
  const shutdown = () => {
1656
1583
  if (isShuttingDown) return;
1657
1584
  isShuttingDown = true;
1658
- logger$10.log("\n🛑 Shutting down workspace...");
1585
+ logger$9.log("\n🛑 Shutting down workspace...");
1659
1586
  closeFakes(fakes);
1660
1587
  const pid = turboProcess.pid;
1661
1588
  if (pid) try {
@@ -1680,7 +1607,7 @@ async function workspaceDevCommand(workspace, options) {
1680
1607
  process.on("SIGTERM", shutdown);
1681
1608
  return new Promise((resolve, reject) => {
1682
1609
  turboProcess.on("error", (error) => {
1683
- logger$10.error("❌ Turbo error:", error);
1610
+ logger$9.error("❌ Turbo error:", error);
1684
1611
  reject(error);
1685
1612
  });
1686
1613
  turboProcess.on("exit", (code) => {
@@ -1699,11 +1626,11 @@ async function entryDevCommand(options) {
1699
1626
  const entryPath = resolve(process.cwd(), entry);
1700
1627
  if (!existsSync(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
1701
1628
  const defaultEnv = loadEnvFiles(".env");
1702
- if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1629
+ if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1703
1630
  const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
1704
- if (appName) logger$10.log(`📦 App: ${appName} (port ${resolvedPort})`);
1705
- logger$10.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1706
- if (Object.keys(credentials).length > 1) logger$10.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
1631
+ if (appName) logger$9.log(`📦 App: ${appName} (port ${resolvedPort})`);
1632
+ logger$9.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1633
+ if (Object.keys(credentials).length > 1) logger$9.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
1707
1634
  const wrapperDir = join(process.cwd(), ".gkm");
1708
1635
  await mkdir(wrapperDir, { recursive: true });
1709
1636
  const wrapperPath = join(wrapperDir, "entry-wrapper.ts");
@@ -1714,7 +1641,7 @@ async function entryDevCommand(options) {
1714
1641
  const shutdown = () => {
1715
1642
  if (isShuttingDown) return;
1716
1643
  isShuttingDown = true;
1717
- logger$10.log("\n🛑 Shutting down...");
1644
+ logger$9.log("\n🛑 Shutting down...");
1718
1645
  runner.stop();
1719
1646
  process.exit(0);
1720
1647
  };
@@ -1750,14 +1677,14 @@ var EntryRunner = class {
1750
1677
  });
1751
1678
  let restartTimeout = null;
1752
1679
  this.watcher.on("change", (path) => {
1753
- logger$10.log(`📝 File changed: ${path}`);
1680
+ logger$9.log(`📝 File changed: ${path}`);
1754
1681
  if (restartTimeout) clearTimeout(restartTimeout);
1755
1682
  restartTimeout = setTimeout(async () => {
1756
- logger$10.log("🔄 Restarting...");
1683
+ logger$9.log("🔄 Restarting...");
1757
1684
  await this.restart();
1758
1685
  }, 300);
1759
1686
  });
1760
- logger$10.log(`👀 Watching for changes in: ${watchDir}`);
1687
+ logger$9.log(`👀 Watching for changes in: ${watchDir}`);
1761
1688
  }
1762
1689
  }
1763
1690
  async runProcess() {
@@ -1772,17 +1699,17 @@ var EntryRunner = class {
1772
1699
  });
1773
1700
  this.isRunning = true;
1774
1701
  this.childProcess.on("error", (error) => {
1775
- logger$10.error("❌ Process error:", error);
1702
+ logger$9.error("❌ Process error:", error);
1776
1703
  });
1777
1704
  this.childProcess.on("exit", (code) => {
1778
- if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
1705
+ if (code !== null && code !== 0 && code !== 143) logger$9.error(`❌ Process exited with code ${code}`);
1779
1706
  this.isRunning = false;
1780
1707
  });
1781
1708
  await new Promise((resolve) => setTimeout(resolve, 500));
1782
1709
  if (this.isRunning) {
1783
- logger$10.log("");
1784
- logger$10.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
1785
- logger$10.log("");
1710
+ logger$9.log("");
1711
+ logger$9.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
1712
+ logger$9.log("");
1786
1713
  }
1787
1714
  }
1788
1715
  async restart() {
@@ -1960,7 +1887,6 @@ function listenerOn(port) {
1960
1887
  }
1961
1888
  }
1962
1889
  var DevServer = class {
1963
- provider;
1964
1890
  requestedPort;
1965
1891
  portExplicit;
1966
1892
  enableOpenApi;
@@ -1974,8 +1900,7 @@ var DevServer = class {
1974
1900
  isRunning = false;
1975
1901
  actualPort;
1976
1902
  startTime = Date.now();
1977
- constructor(provider, requestedPort, portExplicit, enableOpenApi, telescope, studio, runtime = "node", appRoot = process.cwd(), secretsJsonPath, selfServing = false) {
1978
- this.provider = provider;
1903
+ constructor(requestedPort, portExplicit, enableOpenApi, telescope, studio, runtime = "node", appRoot = process.cwd(), secretsJsonPath, selfServing = false) {
1979
1904
  this.requestedPort = requestedPort;
1980
1905
  this.portExplicit = portExplicit;
1981
1906
  this.enableOpenApi = enableOpenApi;
@@ -2002,9 +1927,9 @@ var DevServer = class {
2002
1927
  this.actualPort = this.requestedPort;
2003
1928
  } else {
2004
1929
  this.actualPort = await findAvailablePort(this.requestedPort);
2005
- if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
1930
+ if (this.actualPort !== this.requestedPort) logger$9.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
2006
1931
  }
2007
- const serverEntryPath = join(this.appRoot, ".gkm", this.provider, "server.ts");
1932
+ const serverEntryPath = join(this.appRoot, ".gkm", "server", "server.ts");
2008
1933
  await this.createServerEntry();
2009
1934
  this.serverProcess = spawn(...tsxCommand(this.appRoot, [
2010
1935
  serverEntryPath,
@@ -2021,10 +1946,10 @@ var DevServer = class {
2021
1946
  });
2022
1947
  this.isRunning = true;
2023
1948
  this.serverProcess.on("error", (error) => {
2024
- logger$10.error("❌ Server error:", error);
1949
+ logger$9.error("❌ Server error:", error);
2025
1950
  });
2026
1951
  this.serverProcess.on("exit", (code, signal) => {
2027
- if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
1952
+ if (code !== null && code !== 0 && signal !== "SIGTERM") logger$9.error(`❌ Server exited with code ${code}`);
2028
1953
  this.isRunning = false;
2029
1954
  });
2030
1955
  await new Promise((resolve) => setTimeout(resolve, 1e3));
@@ -2032,13 +1957,13 @@ var DevServer = class {
2032
1957
  const local = `http://localhost:${this.actualPort}`;
2033
1958
  const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
2034
1959
  const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
2035
- logger$10.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
1960
+ logger$9.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
2036
1961
  const tools = this.selfServing ? [] : [
2037
1962
  ...this.enableOpenApi ? ["docs /__docs"] : [],
2038
1963
  ...this.telescope ? [`telescope ${this.telescope.path}`] : [],
2039
1964
  ...this.studio ? [`studio ${this.studio.path}`] : []
2040
1965
  ];
2041
- if (tools.length > 0) logger$10.log(` ${tools.join(" · ")}`);
1966
+ if (tools.length > 0) logger$9.log(` ${tools.join(" · ")}`);
2042
1967
  }
2043
1968
  }
2044
1969
  async stop() {
@@ -2076,7 +2001,7 @@ var DevServer = class {
2076
2001
  }
2077
2002
  async createServerEntry() {
2078
2003
  const { writeFile: fsWriteFile } = await import("node:fs/promises");
2079
- await fsWriteFile(join(this.appRoot, ".gkm", this.provider, "server.ts"), generateServerEntryContent({
2004
+ await fsWriteFile(join(this.appRoot, ".gkm", "server", "server.ts"), generateServerEntryContent({
2080
2005
  secretsJsonPath: this.secretsJsonPath,
2081
2006
  runtime: this.runtime,
2082
2007
  enableOpenApi: this.enableOpenApi,
@@ -2088,22 +2013,22 @@ var DevServer = class {
2088
2013
  //#region src/generators/CronGenerator.ts
2089
2014
  var CronGenerator = class extends ConstructGenerator {
2090
2015
  async build(context, constructs, outputDir, options) {
2091
- const provider = options?.provider || "aws-lambda";
2016
+ const target = options?.target ?? "aws";
2092
2017
  const logger = console;
2093
2018
  const cronInfos = [];
2094
- if (provider === "server") {
2019
+ if (target === "server") {
2095
2020
  await this.generateServerCronsFile(outputDir, constructs);
2096
2021
  logger.log(`Generated server crons file with ${constructs.length} crons`);
2097
2022
  return cronInfos;
2098
2023
  }
2099
- if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
2024
+ if (constructs.length === 0) return cronInfos;
2100
2025
  const cronsDir = join(outputDir, "crons");
2101
2026
  await mkdir(cronsDir, { recursive: true });
2102
2027
  for (const { key, construct, path } of constructs) {
2103
2028
  const handlerFile = await this.generateCronHandler(cronsDir, path.relative, key, context, construct.owner);
2104
2029
  cronInfos.push({
2105
2030
  name: key,
2106
- handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2031
+ handler: relative(options?.root ?? process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2107
2032
  schedule: construct.schedule || "rate(1 hour)",
2108
2033
  timeout: construct.timeout,
2109
2034
  memorySize: construct.memorySize,
@@ -2293,17 +2218,17 @@ var FunctionGenerator = class extends ConstructGenerator {
2293
2218
  return Function.isFunction(value);
2294
2219
  }
2295
2220
  async build(context, constructs, outputDir, options) {
2296
- const provider = options?.provider || "aws-lambda";
2221
+ const target = options?.target ?? "aws";
2297
2222
  const logger = console;
2298
2223
  const functionInfos = [];
2299
- if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
2224
+ if (constructs.length === 0 || target === "server") return functionInfos;
2300
2225
  const functionsDir = join(outputDir, "functions");
2301
2226
  await mkdir(functionsDir, { recursive: true });
2302
2227
  for (const { key, construct, path } of constructs) {
2303
2228
  const handlerFile = await this.generateFunctionHandler(functionsDir, path.relative, key, context, construct.owner);
2304
2229
  functionInfos.push({
2305
2230
  name: key,
2306
- handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2231
+ handler: relative(options?.root ?? process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2307
2232
  timeout: construct.timeout,
2308
2233
  memorySize: construct.memorySize,
2309
2234
  environment: await construct.getEnvironment({ markOptional: context.markOptional }),
@@ -2347,22 +2272,22 @@ var QueueGenerator = class extends ConstructGenerator {
2347
2272
  return Queue.isQueue(value);
2348
2273
  }
2349
2274
  async build(context, constructs, outputDir, options) {
2350
- const provider = options?.provider || "aws-lambda";
2275
+ const target = options?.target ?? "aws";
2351
2276
  const logger = console;
2352
2277
  const queueInfos = [];
2353
- if (provider === "server") {
2278
+ if (target === "server") {
2354
2279
  await this.generateServerQueuesFile(outputDir, constructs);
2355
2280
  logger.log(`Generated server queues file with ${constructs.length} queues (polling mode)`);
2356
2281
  return queueInfos;
2357
2282
  }
2358
- if (constructs.length === 0 || provider !== "aws-lambda") return queueInfos;
2283
+ if (constructs.length === 0) return queueInfos;
2359
2284
  const queuesDir = join(outputDir, "queues");
2360
2285
  await mkdir(queuesDir, { recursive: true });
2361
2286
  for (const { key, construct, path } of constructs) {
2362
2287
  const handlerFile = await this.generateQueueHandler(queuesDir, path.relative, key, context, construct.owner);
2363
2288
  queueInfos.push({
2364
2289
  name: construct.name,
2365
- handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2290
+ handler: relative(options?.root ?? process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2366
2291
  batchSize: construct.batchSize,
2367
2292
  fifo: construct.fifo,
2368
2293
  timeout: construct.timeout,
@@ -2528,217 +2453,6 @@ var TopicGenerator = class extends ConstructGenerator {
2528
2453
  }
2529
2454
  };
2530
2455
  //#endregion
2531
- //#region src/build/manifests.ts
2532
- const logger$9 = console;
2533
- function isPartitioned(field) {
2534
- return !Array.isArray(field);
2535
- }
2536
- /**
2537
- * Serialize a manifest field to a TypeScript string.
2538
- * Flat arrays serialize as JSON arrays, partitioned fields as objects of arrays.
2539
- */
2540
- function serializeField(field, indent = 2) {
2541
- if (Array.isArray(field)) return JSON.stringify(field, null, indent);
2542
- return `{\n${Object.entries(field).map(([key, value]) => ` ${JSON.stringify(key)}: ${JSON.stringify(value, null, indent)}`).join(",\n")},\n }`;
2543
- }
2544
- /**
2545
- * Count total items across a manifest field (flat or partitioned).
2546
- */
2547
- function countItems(field) {
2548
- if (Array.isArray(field)) return field.length;
2549
- return Object.values(field).reduce((sum, arr) => sum + arr.length, 0);
2550
- }
2551
- /**
2552
- * Generate derived types for a construct field.
2553
- * @param fieldName - The field name in the manifest (e.g., 'routes')
2554
- * @param typeName - The exported type name (e.g., 'Route')
2555
- * @param partitioned - Whether this field is partitioned
2556
- */
2557
- function generateDerivedType(fieldName, typeName, partitioned) {
2558
- if (partitioned) {
2559
- const partitionTypeName = `${typeName}Partition`;
2560
- return [`export type ${partitionTypeName} = keyof typeof manifest.${fieldName};`, `export type ${typeName}<P extends ${partitionTypeName} = ${partitionTypeName}> = (typeof manifest.${fieldName})[P][number];`].join("\n");
2561
- }
2562
- return `export type ${typeName} = (typeof manifest.${fieldName})[number];`;
2563
- }
2564
- /** A manifest field as a flat list, whether or not it is partitioned. */
2565
- function flatten(field) {
2566
- return Array.isArray(field) ? field : Object.values(field).flat();
2567
- }
2568
- async function generateAwsManifest(outputDir, routes, functions, crons, subscribers, queues = [], topics = [], constructs = {}, backends = {}) {
2569
- const manifestDir = join(outputDir, "manifest");
2570
- await mkdir(manifestDir, { recursive: true });
2571
- const awsRoutes = filterAllRoutes(routes);
2572
- const routesPartitioned = isPartitioned(awsRoutes);
2573
- const functionsPartitioned = isPartitioned(functions);
2574
- const cronsPartitioned = isPartitioned(crons);
2575
- const subscribersPartitioned = isPartitioned(subscribers);
2576
- const queuesPartitioned = isPartitioned(queues);
2577
- const topicsPartitioned = isPartitioned(topics);
2578
- const awsConstructs = withCompute(withRoutes(constructs, flatten(awsRoutes), { perRoute: true }), {
2579
- functions: flatten(functions),
2580
- crons: flatten(crons),
2581
- queues: flatten(queues),
2582
- subscribers: flatten(subscribers)
2583
- });
2584
- const content = `export const manifest = {
2585
- routes: ${serializeField(awsRoutes)},
2586
- functions: ${serializeField(functions)},
2587
- crons: ${serializeField(crons)},
2588
- subscribers: ${serializeField(subscribers)},
2589
- queues: ${serializeField(queues)},
2590
- topics: ${serializeField(topics)},
2591
- } as const;
2592
-
2593
- /**
2594
- * What the application declares, as opposed to what this build generated.
2595
- *
2596
- * Discovery imports application code, so it runs once here rather than in every
2597
- * consumer — a deploy config calling it would evaluate the whole runtime graph
2598
- * inside its own toolchain.
2599
- */
2600
- export const constructs = ${JSON.stringify(awsConstructs, null, 2)} as const;
2601
-
2602
- /**
2603
- * Where the backends that are config rather than declaration resolved to.
2604
- *
2605
- * Recorded because they were answered twice: once by the build, which registers
2606
- * exactly one cache driver, and once by a deploy, which told the provisioner
2607
- * something else — leaving the running code with a URL it had no driver for.
2608
- */
2609
- export const backends = ${JSON.stringify(backends, null, 2)} as const;
2610
-
2611
- // Derived types
2612
- ${generateDerivedType("routes", "Route", routesPartitioned)}
2613
- ${generateDerivedType("functions", "Function", functionsPartitioned)}
2614
- ${generateDerivedType("crons", "Cron", cronsPartitioned)}
2615
- ${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
2616
- ${generateDerivedType("queues", "Queue", queuesPartitioned)}
2617
- ${generateDerivedType("topics", "Topic", topicsPartitioned)}
2618
-
2619
- export type ConstructId = keyof typeof constructs;
2620
- export type Construct<Id extends ConstructId> = (typeof constructs)[Id];
2621
-
2622
- // Useful union types
2623
- export type Authorizer = Route['authorizer'];
2624
- export type HttpMethod = Route['method'];
2625
- export type RoutePath = Route['path'];
2626
- `;
2627
- const manifestPath = join(manifestDir, "aws.ts");
2628
- await writeFile(manifestPath, content);
2629
- 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`);
2630
- logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2631
- }
2632
- async function generateServerManifest(outputDir, appInfo, routes, subscribers, queues = [], topics = [], constructs = {}, backends = {}) {
2633
- const manifestDir = join(outputDir, "manifest");
2634
- await mkdir(manifestDir, { recursive: true });
2635
- const serverRoutes = mapRouteMetadata(filterAllRoutes(routes));
2636
- const serverSubscribers = mapSubscriberMetadata(subscribers);
2637
- const serverQueues = mapQueueMetadata(queues);
2638
- const serverTopics = mapTopicMetadata(topics);
2639
- const routesPartitioned = isPartitioned(serverRoutes);
2640
- const subscribersPartitioned = isPartitioned(serverSubscribers);
2641
- const queuesPartitioned = isPartitioned(serverQueues);
2642
- const topicsPartitioned = isPartitioned(serverTopics);
2643
- const content = `export const manifest = {
2644
- app: ${JSON.stringify(appInfo, null, 2)},
2645
- routes: ${serializeField(serverRoutes)},
2646
- subscribers: ${serializeField(serverSubscribers)},
2647
- queues: ${serializeField(serverQueues)},
2648
- topics: ${serializeField(serverTopics)},
2649
- } as const;
2650
-
2651
- /**
2652
- * What the application declares, as opposed to what this build generated.
2653
- *
2654
- * Discovery imports application code, so it runs once in the build rather than
2655
- * in every consumer — a deploy config calling it would evaluate the whole
2656
- * runtime graph inside its own toolchain.
2657
- */
2658
- export const constructs = ${JSON.stringify(constructs, null, 2)} as const;
2659
-
2660
- /**
2661
- * Where the backends that are config rather than declaration resolved to.
2662
- *
2663
- * Recorded because they were answered twice: once by the build, which registers
2664
- * exactly one cache driver, and once by a deploy, which told the provisioner
2665
- * something else — leaving the running code with a URL it had no driver for.
2666
- */
2667
- export const backends = ${JSON.stringify(backends, null, 2)} as const;
2668
-
2669
- // Derived types
2670
- ${generateDerivedType("routes", "Route", routesPartitioned)}
2671
- ${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
2672
- ${generateDerivedType("queues", "Queue", queuesPartitioned)}
2673
- ${generateDerivedType("topics", "Topic", topicsPartitioned)}
2674
-
2675
- export type ConstructId = keyof typeof constructs;
2676
- export type Construct<Id extends ConstructId> = (typeof constructs)[Id];
2677
-
2678
- // Useful union types
2679
- export type Authorizer = Route['authorizer'];
2680
- export type HttpMethod = Route['method'];
2681
- export type RoutePath = Route['path'];
2682
- `;
2683
- const manifestPath = join(manifestDir, "server.ts");
2684
- await writeFile(manifestPath, content);
2685
- logger$9.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
2686
- logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2687
- }
2688
- /**
2689
- * Filter out 'ALL' method routes from a manifest field (flat or partitioned).
2690
- */
2691
- function filterAllRoutes(routes) {
2692
- if (Array.isArray(routes)) return routes.filter((r) => r.method !== "ALL");
2693
- const result = {};
2694
- for (const [partition, partitionRoutes] of Object.entries(routes)) result[partition] = partitionRoutes.filter((r) => r.method !== "ALL");
2695
- return result;
2696
- }
2697
- /**
2698
- * Map routes to server metadata (path, method, authorizer only).
2699
- */
2700
- function mapRouteMetadata(routes) {
2701
- const mapFn = (r) => ({
2702
- path: r.path,
2703
- method: r.method,
2704
- authorizer: r.authorizer
2705
- });
2706
- if (Array.isArray(routes)) return routes.map(mapFn);
2707
- const result = {};
2708
- for (const [partition, partitionRoutes] of Object.entries(routes)) result[partition] = partitionRoutes.map(mapFn);
2709
- return result;
2710
- }
2711
- /**
2712
- * Map subscribers to server metadata (name, subscribedEvents only).
2713
- */
2714
- function mapSubscriberMetadata(subscribers) {
2715
- const mapFn = (s) => ({
2716
- name: s.name,
2717
- subscribedEvents: s.subscribedEvents
2718
- });
2719
- if (Array.isArray(subscribers)) return subscribers.map(mapFn);
2720
- const result = {};
2721
- for (const [partition, partitionSubs] of Object.entries(subscribers)) result[partition] = partitionSubs.map(mapFn);
2722
- return result;
2723
- }
2724
- function mapQueueMetadata(queues) {
2725
- const mapFn = (q) => ({ name: q.name });
2726
- if (Array.isArray(queues)) return queues.map(mapFn);
2727
- const result = {};
2728
- for (const [partition, partitionQueues] of Object.entries(queues)) result[partition] = partitionQueues.map(mapFn);
2729
- return result;
2730
- }
2731
- function mapTopicMetadata(topics) {
2732
- const mapFn = (t) => ({
2733
- name: t.name,
2734
- events: t.events
2735
- });
2736
- if (Array.isArray(topics)) return topics.map(mapFn);
2737
- const result = {};
2738
- for (const [partition, partitionTopics] of Object.entries(topics)) result[partition] = partitionTopics.map(mapFn);
2739
- return result;
2740
- }
2741
- //#endregion
2742
2456
  //#region src/build/owners.ts
2743
2457
  /**
2744
2458
  * What a generated entry imports its runtime from, read off the discovered
@@ -2808,27 +2522,6 @@ function restApis(declared) {
2808
2522
  function servedSurface(declared, workspaceRoot, appRoot) {
2809
2523
  return restApis(declared).find((d) => resolve(workspaceRoot, d.path) === resolve(appRoot));
2810
2524
  }
2811
- /**
2812
- * Check if any construct across the given arrays has a non-undefined partition.
2813
- * When true, the manifest should use the partitioned shape for that construct type.
2814
- */
2815
- function hasPartitions(constructs) {
2816
- return constructs.some((c) => c.partition !== void 0);
2817
- }
2818
- /**
2819
- * Group an info array by partition, using the partition values from
2820
- * the corresponding construct array. Both arrays must be the same length
2821
- * and in the same order.
2822
- */
2823
- function groupInfosByPartition(infos, constructs) {
2824
- const groups = {};
2825
- for (let i = 0; i < infos.length; i++) {
2826
- const partition = constructs[i]?.partition ?? "default";
2827
- if (!groups[partition]) groups[partition] = [];
2828
- groups[partition].push(infos[i]);
2829
- }
2830
- return groups;
2831
- }
2832
2525
  //#endregion
2833
2526
  //#region src/build/surfaceEntry.ts
2834
2527
  /** Whether a construct serves itself, asked of the construct. */
@@ -2905,6 +2598,24 @@ function rootGkmConfig(workspace) {
2905
2598
  constructs: allConstructGlobs(workspace)
2906
2599
  };
2907
2600
  }
2601
+ /** Directories under `.gkm/` that the removed per-provider builds wrote. */
2602
+ const SUPERSEDED_OUTPUT = [
2603
+ "aws-apigatewayv1",
2604
+ "aws-apigatewayv2",
2605
+ "aws-lambda"
2606
+ ];
2607
+ /** Whether `--provider` named something a build can target. */
2608
+ function isMainProvider(value) {
2609
+ return value === "aws" || value === "server";
2610
+ }
2611
+ var UnknownBuildProvider = class extends Error {
2612
+ provider;
2613
+ constructor(provider) {
2614
+ 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.`);
2615
+ this.provider = provider;
2616
+ this.name = "UnknownBuildProvider";
2617
+ }
2618
+ };
2908
2619
  async function buildCommand(options) {
2909
2620
  const loadedConfig = await loadWorkspaceConfig();
2910
2621
  if (loadedConfig.type === "workspace") {
@@ -2918,25 +2629,62 @@ async function buildCommand(options) {
2918
2629
  }
2919
2630
  }
2920
2631
  const config = loadedConfig.type !== "workspace" ? await loadConfig() : resolve(process.cwd()) === resolve(loadedConfig.workspace.root) ? rootGkmConfig(loadedConfig.workspace) : (await loadAppConfig()).gkmConfig;
2921
- const resolved = resolveProviders(config, options);
2922
- const cacheBackend = cacheBackendFor(providerOf(loadedConfig.workspace));
2923
- const productionConfigFromGkm = getProductionConfigFromGkm(config);
2924
- const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
2632
+ const target = options.provider ?? providerOf(loadedConfig.workspace);
2633
+ const workspaceRoot = loadedConfig.workspace.root;
2634
+ const appRoot = process.cwd();
2635
+ const output = await buildOneApp({
2636
+ config,
2637
+ options,
2638
+ workspace: loadedConfig.workspace,
2639
+ appRoot,
2640
+ target
2641
+ });
2642
+ if (resolve(appRoot) === resolve(workspaceRoot)) await writeManifest({
2643
+ workspaceRoot,
2644
+ target,
2645
+ builds: output.built ? [output.built] : [],
2646
+ constructs: await declaredIn(config, workspaceRoot),
2647
+ backends: backendsOf(loadedConfig.workspace)
2648
+ });
2649
+ return output;
2650
+ }
2651
+ /** What the workspace declares, read through a config's globs. */
2652
+ async function declaredIn(config, cwd) {
2653
+ return discover({
2654
+ patterns: config.constructs,
2655
+ cwd
2656
+ });
2657
+ }
2658
+ /**
2659
+ * The backend choices the manifest records — answered by the deploy target,
2660
+ * the same place reconcile and the build's drivers read them.
2661
+ */
2662
+ function backendsOf(workspace) {
2663
+ return {
2664
+ cache: cacheBackendFor(providerOf(workspace)),
2665
+ email: "ses"
2666
+ };
2667
+ }
2668
+ /** One app's build, from its config and the command's options. */
2669
+ async function buildOneApp(input) {
2670
+ const { config, options, workspace, appRoot, target } = input;
2671
+ const cacheBackend = cacheBackendFor(providerOf(workspace));
2672
+ const production = normalizeProductionConfig(options.production ?? false);
2925
2673
  if (production) logger$8.log(`🏭 Building for PRODUCTION`);
2926
- logger$8.log(`Building with providers: ${resolved.providers.join(", ")}`);
2674
+ logger$8.log(`Building for ${target}`);
2927
2675
  logger$8.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
2928
2676
  const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
2929
2677
  if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
2930
2678
  const studio = production ? void 0 : normalizeStudioConfig(config.studio);
2931
2679
  if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
2932
- const hooks = normalizeHooksConfig(config.hooks);
2680
+ const hooks = normalizeHooksConfig(config.hooks, appRoot);
2933
2681
  if (hooks) logger$8.log(`🪝 Server hooks enabled`);
2934
2682
  return buildApp({
2935
2683
  config,
2936
- workspaceRoot: loadedConfig.workspace.root,
2937
- appRoot: process.cwd(),
2938
- providers: resolved.providers,
2939
- enableOpenApi: resolved.enableOpenApi,
2684
+ workspaceRoot: workspace.root,
2685
+ appRoot,
2686
+ target,
2687
+ enableOpenApi: options.enableOpenApi ?? false,
2940
2688
  cacheBackend,
2941
2689
  production,
2942
2690
  telescope,
@@ -2948,6 +2696,33 @@ async function buildCommand(options) {
2948
2696
  });
2949
2697
  }
2950
2698
  /**
2699
+ * Write the application's manifest: every declared construct, with what each
2700
+ * app generated folded into it.
2701
+ *
2702
+ * The manifest belongs to the whole application, so only the root writes it —
2703
+ * once, after every app has built, from the constructs the workspace declares.
2704
+ * An app's own build generates its handlers and nothing else.
2705
+ */
2706
+ async function writeManifest(input) {
2707
+ const { workspaceRoot, target, builds, constructs, backends } = input;
2708
+ const manifestDir = join(workspaceRoot, ".gkm", "manifest");
2709
+ await mkdir(manifestDir, { recursive: true });
2710
+ await rm(join(manifestDir, `${target === "aws" ? "server" : "aws"}.ts`), { force: true });
2711
+ const withServed = builds.reduce((manifest, build) => withRoutes(manifest, build.routes.filter((route) => route.method !== "ALL"), {
2712
+ perRoute: true,
2713
+ ...build.surface ? { surface: build.surface } : {}
2714
+ }), constructs);
2715
+ const folded = withCompute(withServed, {
2716
+ functions: builds.flatMap((build) => build.functions),
2717
+ crons: builds.flatMap((build) => build.crons),
2718
+ queues: builds.flatMap((build) => build.queues),
2719
+ subscribers: builds.flatMap((build) => build.subscribers)
2720
+ });
2721
+ const path = join(manifestDir, `${target}.ts`);
2722
+ await writeFile(path, manifestModule(folded, backends));
2723
+ logger$8.log(`Manifest: ${relative(process.cwd(), path)}`);
2724
+ }
2725
+ /**
2951
2726
  * Turn one app's constructs into its generated entry.
2952
2727
  *
2953
2728
  * `gkm build` and `gkm dev` both run this. They used to be two copies of it,
@@ -2956,8 +2731,8 @@ async function buildCommand(options) {
2956
2731
  * dev did not, and failed on exactly the apps the build had learned to handle.
2957
2732
  */
2958
2733
  async function buildApp(input) {
2959
- const { config, workspaceRoot, appRoot, providers, enableOpenApi, cacheBackend, production, telescope, studio, hooks, bustCache = false } = input;
2960
- const constructGlobs = typeof config.constructs === "string" || Array.isArray(config.constructs) ? config.constructs : void 0;
2734
+ const { config, workspaceRoot, appRoot, target, enableOpenApi, cacheBackend, production, telescope, studio, hooks, bustCache = false } = input;
2735
+ const constructGlobs = config.constructs;
2961
2736
  const constructSources = {};
2962
2737
  const declared = constructGlobs ? await discover({
2963
2738
  patterns: constructGlobs,
@@ -3023,50 +2798,58 @@ async function buildApp(input) {
3023
2798
  logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
3024
2799
  return {};
3025
2800
  }
3026
- const rootOutputDir = join(appRoot, ".gkm");
3027
- await mkdir(rootOutputDir, { recursive: true });
3028
- let result = {};
3029
- for (const provider of providers) {
3030
- 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);
3031
- if (providerResult.masterKey) result = providerResult;
3032
- }
2801
+ 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);
3033
2802
  await generateOpenApi(allEndpoints.map(({ construct }) => construct), { openapi: config.openapi });
3034
2803
  return result;
3035
2804
  }
3036
- async function buildForProvider(provider, context, appRoot, discovered, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, endpoints, functions, crons, subscribers, queues, topics, enableOpenApi, skipBundle, stage) {
3037
- const rootOutputDir = join(appRoot, ".gkm");
3038
- const outputDir = join(rootOutputDir, provider);
2805
+ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, endpoints, functions, crons, subscribers, queues, topics, enableOpenApi, skipBundle, stage) {
2806
+ const outputDir = join(appRoot, ".gkm", target);
2807
+ await Promise.all([...SUPERSEDED_OUTPUT.map((dir) => join(appRoot, ".gkm", dir)), ...appRoot === workspaceRoot ? [] : [join(appRoot, ".gkm", "manifest")]].map((dir) => rm(dir, {
2808
+ recursive: true,
2809
+ force: true
2810
+ })));
2811
+ if (target === "aws") await rm(outputDir, {
2812
+ recursive: true,
2813
+ force: true
2814
+ });
3039
2815
  await mkdir(outputDir, { recursive: true });
3040
- logger$8.log(`\nGenerating handlers for provider: ${provider}`);
2816
+ logger$8.log(`\nGenerating handlers for ${target}`);
2817
+ const options = {
2818
+ target,
2819
+ root: workspaceRoot
2820
+ };
3041
2821
  const [routes, functionInfos, cronInfos, subscriberInfos, queueInfos, topicInfos] = await Promise.all([
3042
2822
  endpointGenerator.build(context, endpoints, outputDir, {
3043
- provider,
2823
+ ...options,
3044
2824
  enableOpenApi
3045
2825
  }),
3046
- functionGenerator.build(context, functions, outputDir, { provider }),
3047
- cronGenerator.build(context, crons, outputDir, { provider }),
3048
- subscriberGenerator.build(context, subscribers, outputDir, { provider }),
3049
- queueGenerator.build(context, queues, outputDir, { provider }),
3050
- topicGenerator.build(context, topics, outputDir, { provider })
2826
+ functionGenerator.build(context, functions, outputDir, options),
2827
+ cronGenerator.build(context, crons, outputDir, options),
2828
+ subscriberGenerator.build(context, subscribers, outputDir, options),
2829
+ queueGenerator.build(context, queues, outputDir, options),
2830
+ topicGenerator.build(context, topics, outputDir, options)
3051
2831
  ]);
3052
- logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
3053
- const manifestRoutes = assembleManifestField(routes, endpoints);
3054
- const manifestFunctions = assembleManifestField(functionInfos, functions);
3055
- const manifestCrons = assembleManifestField(cronInfos, crons);
3056
- const manifestQueues = assembleManifestField(queueInfos, queues);
3057
- const manifestTopics = assembleManifestField(topicInfos, topics);
3058
- const manifestSubscribers = assembleManifestField(subscriberInfos, subscribers);
3059
- if (provider === "server") {
3060
- const serverRouteField = assembleManifestField(await Promise.all(endpoints.map(async ({ construct }) => ({
3061
- path: construct._path,
3062
- method: construct.method,
3063
- handler: "",
3064
- authorizer: construct.authorizer?.name ?? "none"
3065
- }))), endpoints);
3066
- await generateServerManifest(rootOutputDir, {
3067
- handler: relative(appRoot, join(outputDir, "app.ts")),
3068
- endpoints: relative(appRoot, join(outputDir, "endpoints.ts"))
3069
- }, serverRouteField, manifestSubscribers, manifestQueues, manifestTopics);
2832
+ logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${target}`);
2833
+ const fields = {
2834
+ ...context.surface ? { surface: context.surface.id } : {},
2835
+ functions: functionInfos,
2836
+ crons: cronInfos,
2837
+ queues: queueInfos,
2838
+ subscribers: subscriberInfos
2839
+ };
2840
+ if (target === "server") {
2841
+ const handler = relative(workspaceRoot, join(outputDir, "app.ts"));
2842
+ const built = {
2843
+ ...fields,
2844
+ routes: await Promise.all(endpoints.map(async ({ construct }) => ({
2845
+ path: construct._path,
2846
+ method: construct.method,
2847
+ handler,
2848
+ environment: await construct.getEnvironment({ markOptional: context.markOptional }),
2849
+ dependencies: construct.constructs,
2850
+ authorizer: construct.authorizer?.name ?? "none"
2851
+ })))
2852
+ };
3070
2853
  let masterKey;
3071
2854
  if (context.production?.bundle && !skipBundle) {
3072
2855
  logger$8.log(`\n📦 Bundling production server...`);
@@ -3093,12 +2876,15 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
3093
2876
  logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
3094
2877
  }
3095
2878
  }
3096
- return { masterKey };
3097
- } else await generateAwsManifest(rootOutputDir, manifestRoutes, manifestFunctions, manifestCrons, manifestSubscribers, manifestQueues, manifestTopics, discovered, {
3098
- cache: context.cacheBackend,
3099
- email: context.emailBackend
3100
- });
3101
- return {};
2879
+ return {
2880
+ masterKey,
2881
+ built
2882
+ };
2883
+ }
2884
+ return { built: {
2885
+ ...fields,
2886
+ routes
2887
+ } };
3102
2888
  }
3103
2889
  /**
3104
2890
  * Detect available package manager.
@@ -3120,8 +2906,8 @@ function detectPackageManager$2() {
3120
2906
  *
3121
2907
  * @internal Exported for testing
3122
2908
  */
3123
- function getTurboCommand(pm, filters = []) {
3124
- const filterArgs = (typeof filters === "string" ? [filters] : filters).map((f) => ` --filter=${f}`).join("");
2909
+ function getTurboCommand(pm, filters = [], { only = false } = {}) {
2910
+ const filterArgs = (typeof filters === "string" ? [filters] : filters).map((f) => ` --filter=${f}`).join("") + (only ? " --only" : "");
3125
2911
  switch (pm) {
3126
2912
  case "pnpm": return `pnpm exec turbo run build${filterArgs}`;
3127
2913
  case "yarn": return `yarn turbo run build${filterArgs}`;
@@ -3141,10 +2927,11 @@ function getTurboCommand(pm, filters = []) {
3141
2927
  *
3142
2928
  * @internal Exported for testing
3143
2929
  */
3144
- function turboFilters(workspace) {
2930
+ function turboFilters(workspace, { exclude } = {}) {
3145
2931
  const filters = [];
3146
2932
  const unpackaged = [];
3147
- for (const appName of Object.keys(workspace.apps)) {
2933
+ for (const [appName, app] of Object.entries(workspace.apps)) {
2934
+ if (app.type === exclude) continue;
3148
2935
  const name = appPackageName(workspace, appName);
3149
2936
  if (name) filters.push(name);
3150
2937
  else unpackaged.push(appName);
@@ -3183,31 +2970,59 @@ async function workspaceBuildCommand(workspace, options) {
3183
2970
  if (options.production) logger$8.log(` 🏭 Production mode enabled`);
3184
2971
  const buildOrder = getAppBuildOrder(workspace);
3185
2972
  logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
3186
- const pm = detectPackageManager$2();
3187
- logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
3188
2973
  try {
3189
- const { filters, unpackaged } = turboFilters(workspace);
3190
- 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.`);
3191
- const turboCommand = getTurboCommand(pm, filters);
3192
- logger$8.log(`Running: ${turboCommand}`);
3193
- await new Promise((resolve, reject) => {
3194
- const child = spawn(turboCommand, {
3195
- shell: true,
3196
- cwd: workspace.root,
3197
- stdio: "inherit",
3198
- env: {
3199
- ...process.env,
3200
- NODE_ENV: options.production ? "production" : "development"
3201
- }
3202
- });
3203
- child.on("close", (code) => {
3204
- if (code === 0) resolve();
3205
- else reject(/* @__PURE__ */ new Error(`Turbo build failed with exit code ${code}`));
3206
- });
3207
- child.on("error", (err) => {
3208
- reject(err);
2974
+ const target = options.provider ?? providerOf(workspace);
2975
+ const builds = [];
2976
+ for (const appName of buildOrder) {
2977
+ const app = workspace.apps[appName];
2978
+ const config = getAppGkmConfig(workspace, appName);
2979
+ if (!app || app.type !== "backend" || !config) continue;
2980
+ logger$8.log(`\n⚙️ ${appName}`);
2981
+ const output = await buildOneApp({
2982
+ config,
2983
+ options,
2984
+ workspace,
2985
+ appRoot: join(workspace.root, app.path),
2986
+ target
3209
2987
  });
2988
+ if (output.built) builds.push(output.built);
2989
+ }
2990
+ await writeManifest({
2991
+ workspaceRoot: workspace.root,
2992
+ target,
2993
+ builds,
2994
+ constructs: await discover({
2995
+ patterns: allConstructGlobs(workspace),
2996
+ cwd: workspace.root
2997
+ }),
2998
+ backends: backendsOf(workspace)
3210
2999
  });
3000
+ const { filters, unpackaged } = turboFilters(workspace, { exclude: "backend" });
3001
+ 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.`);
3002
+ const pm = detectPackageManager$2();
3003
+ const turboCommand = getTurboCommand(pm, filters, { only: true });
3004
+ if (filters.length > 0) {
3005
+ logger$8.log(`\n📦 Using ${pm} with Turbo for the other apps...\n`);
3006
+ logger$8.log(`Running: ${turboCommand}`);
3007
+ await new Promise((resolve, reject) => {
3008
+ const child = spawn(turboCommand, {
3009
+ shell: true,
3010
+ cwd: workspace.root,
3011
+ stdio: "inherit",
3012
+ env: {
3013
+ ...process.env,
3014
+ NODE_ENV: options.production ? "production" : "development"
3015
+ }
3016
+ });
3017
+ child.on("close", (code) => {
3018
+ if (code === 0) resolve();
3019
+ else reject(/* @__PURE__ */ new Error(`Turbo build failed with exit code ${code}`));
3020
+ });
3021
+ child.on("error", (err) => {
3022
+ reject(err);
3023
+ });
3024
+ });
3025
+ }
3211
3026
  for (const [appName, app] of apps) {
3212
3027
  const outputPath = getAppOutputPath(workspace, appName, app);
3213
3028
  results.push({
@@ -3250,23 +3065,11 @@ function getAppOutputPath(workspace, _appName, app) {
3250
3065
  }
3251
3066
  return join(appPath, ".gkm");
3252
3067
  }
3253
- /**
3254
- * Format routes for logging, handling PartitionedRoutes.
3255
- */
3068
+ /** Format routes for logging. */
3256
3069
  function formatRoutes(routes) {
3257
- if (isPartitionedRoutes(routes)) return `${Array.isArray(routes.paths) ? routes.paths.join(", ") : routes.paths} (partitioned)`;
3258
3070
  return Array.isArray(routes) ? routes.join(", ") : routes;
3259
3071
  }
3260
3072
  /**
3261
- * Assemble a ManifestField from build infos and constructs.
3262
- * If any construct has a partition, returns a Record<string, T[]>.
3263
- * Otherwise, returns a flat T[].
3264
- */
3265
- function assembleManifestField(infos, constructs) {
3266
- if (!hasPartitions(constructs)) return infos;
3267
- return groupInfosByPartition(infos, constructs);
3268
- }
3269
- /**
3270
3073
  * Format a fatal error for display.
3271
3074
  * Always includes the full stack trace since these are process-ending errors.
3272
3075
  */
@@ -3569,6 +3372,19 @@ const PROVISIONERS = {
3569
3372
  return { provides: { [key]: supplied(declaration.id, key, context) } };
3570
3373
  },
3571
3374
  /**
3375
+ * The keyring the stage keeps in its secrets.
3376
+ *
3377
+ * Generated on the first deploy (`withGeneratedSecrets`) and rotated only
3378
+ * by `gkm encryption:rotate`, so a redeploy never changes a key and nothing
3379
+ * written becomes unreadable. Nothing is created in Dokploy: the process
3380
+ * holds the keyring.
3381
+ */
3382
+ encryption: async (declaration, context) => {
3383
+ if (declaration.kind !== "encryption") throw new WrongKind(declaration.kind);
3384
+ const key = provideKey(declaration.id, "url");
3385
+ return { provides: { [key]: supplied(declaration.id, key, context) } };
3386
+ },
3387
+ /**
3572
3388
  * A third party's credentials, as the stage was given them.
3573
3389
  *
3574
3390
  * Nothing is created and nothing derived: the value was issued by somebody
@@ -5412,9 +5228,8 @@ async function dockerCommand(options) {
5412
5228
  logger$6.log("📦 Detected workspace configuration");
5413
5229
  return workspaceDockerCommand(loadedConfig.workspace);
5414
5230
  }
5415
- const config = await loadConfig();
5416
- const dockerConfig = resolveDockerConfig(config);
5417
- const healthCheckPath = (typeof config.providers?.server === "object" ? config.providers.server : void 0)?.production?.healthCheck ?? "/health";
5231
+ const dockerConfig = resolveDockerConfig(await loadConfig());
5232
+ const healthCheckPath = "/health";
5418
5233
  const useSlim = options.slim === true;
5419
5234
  if (useSlim) {
5420
5235
  const distDir = join(process.cwd(), ".gkm", "server", "dist");
@@ -6573,6 +6388,8 @@ function validateEnvVars(requiredVars, context) {
6573
6388
  * who could read the repo.
6574
6389
  * - **Each `secret` construct's value** — an auth server's signing secret —
6575
6390
  * random, rather than derived.
6391
+ * - **Each `encryption` construct's keyring** — one random key and an index
6392
+ * key. Later keys are added by `gkm encryption:rotate`, never by a deploy.
6576
6393
  *
6577
6394
  * Both are generated on the first deploy that needs them and written to the
6578
6395
  * stage's store, so every later deploy reads the same values: sessions survive
@@ -6588,10 +6405,10 @@ function withGeneratedSecrets(secrets, manifest) {
6588
6405
  const generated = [];
6589
6406
  const custom = { ...secrets.custom };
6590
6407
  for (const declaration of Object.values(manifest)) {
6591
- if (declaration?.kind !== "secret") continue;
6408
+ if (declaration?.kind !== "secret" && declaration?.kind !== "encryption") continue;
6592
6409
  const key = declaration.provides?.[0];
6593
6410
  if (!key || custom[key]) continue;
6594
- custom[key] = random();
6411
+ custom[key] = declaration.kind === "encryption" ? generateKeyring() : random();
6595
6412
  generated.push(key);
6596
6413
  }
6597
6414
  const seed = secrets.seed ?? random();
@@ -7420,6 +7237,10 @@ async function workspaceDeployCommand(configured, options) {
7420
7237
  } else logger$4.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
7421
7238
  const dokployApps = appsToDeployNames.filter((name) => {
7422
7239
  const target = workspace.apps[name].resolvedDeployTarget;
7240
+ if (target === "sst") {
7241
+ logger$4.log(` ⚠️ Skipping ${name}: it deploys with SST — run \`gkm build && sst deploy --stage ${stage}\``);
7242
+ return false;
7243
+ }
7423
7244
  if (!isDeployTargetSupported(target)) {
7424
7245
  logger$4.log(` ⚠️ Skipping ${name}: ${getDeployTargetError(target, name)}`);
7425
7246
  return false;
@@ -7940,66 +7761,6 @@ async function createApi(endpoint) {
7940
7761
  });
7941
7762
  }
7942
7763
  /**
7943
- * Update gkm.config.ts with Dokploy configuration
7944
- */
7945
- /**
7946
- * Whether the file's `dokploy` block is more than a flat literal.
7947
- *
7948
- * A spread, a `process.env`, a ternary or a nested object all defeat the
7949
- * single-level regex below — the first two by being expressions it would
7950
- * stringify away, the last by ending the match at the wrong brace.
7951
- */
7952
- function unsafeToRewrite(content) {
7953
- const start = content.indexOf("dokploy:");
7954
- if (start === -1) return false;
7955
- const block = content.slice(start, content.indexOf("\n },", start) + 1);
7956
- return /\.\.\.|process\.env|\?\s|`|\{[^}]*\{/s.test(block);
7957
- }
7958
- async function updateConfig(config, cwd = process.cwd()) {
7959
- const configPath = join(cwd, "gkm.config.ts");
7960
- if (!existsSync(configPath)) {
7961
- logger$3.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
7962
- logger$3.log(` providers: {`);
7963
- logger$3.log(` dokploy: {`);
7964
- logger$3.log(` endpoint: '${config.endpoint}',`);
7965
- logger$3.log(` projectId: '${config.projectId}',`);
7966
- logger$3.log(` applicationId: '${config.applicationId}',`);
7967
- logger$3.log(` },`);
7968
- logger$3.log(` },`);
7969
- return;
7970
- }
7971
- const content = await readFile(configPath, "utf-8");
7972
- if (unsafeToRewrite(content)) {
7973
- 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.");
7974
- logger$3.log(`\n Discovered, if you want them written down:`);
7975
- logger$3.log(` projectId: '${config.projectId}'`);
7976
- logger$3.log(` applicationId: '${config.applicationId}'`);
7977
- if (config.registryId) logger$3.log(` registryId: '${config.registryId}'`);
7978
- return;
7979
- }
7980
- if (content.includes("dokploy:") && content.includes("applicationId:")) {
7981
- logger$3.log("\n Dokploy config already exists in gkm.config.ts");
7982
- logger$3.log(" Updating with new values...");
7983
- }
7984
- const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
7985
- const dokployConfigStr = `dokploy: {
7986
- endpoint: '${config.endpoint}',
7987
- projectId: '${config.projectId}',
7988
- applicationId: '${config.applicationId}',${registryLine}
7989
- }`;
7990
- let newContent;
7991
- if (content.includes("providers:")) {
7992
- if (content.includes("dokploy:")) newContent = content.replace(/dokploy:\s*\{[^}]*\}/s, dokployConfigStr);
7993
- else newContent = content.replace(/providers:\s*\{/, `providers: {\n\t\t${dokployConfigStr},`);
7994
- } else newContent = content.replace(/}\s*\)\s*;?\s*$/, `
7995
- providers: {
7996
- ${dokployConfigStr},
7997
- },
7998
- });`);
7999
- await writeFile(configPath, newContent);
8000
- logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
8001
- }
8002
- /**
8003
7764
  * Initialize Dokploy deployment configuration
8004
7765
  */
8005
7766
  async function deployInitCommand(options) {
@@ -8052,7 +7813,6 @@ async function deployInitCommand(options) {
8052
7813
  projectId,
8053
7814
  applicationId: application.applicationId
8054
7815
  };
8055
- await updateConfig(config);
8056
7816
  logger$3.log(`\n✅ Dokploy deployment initialized!`);
8057
7817
  logger$3.log(`\n📋 Configuration:`);
8058
7818
  logger$3.log(` Project ID: ${projectId}`);
@@ -9322,26 +9082,26 @@ function loadPackageJson() {
9322
9082
  * Run: pnpm --filter @geekmidas/cli sync-versions
9323
9083
  */
9324
9084
  const GEEKMIDAS_VERSIONS = {
9325
- "@geekmidas/audit": "~10.0.0-alpha.45",
9326
- "@geekmidas/auth": "~10.0.0-alpha.45",
9327
- "@geekmidas/cache": "~10.0.0-alpha.45",
9328
- "@geekmidas/client": "~10.0.0-alpha.45",
9329
- "@geekmidas/cloud": "~10.0.0-alpha.45",
9330
- "@geekmidas/constructs": "~10.0.0-alpha.45",
9331
- "@geekmidas/db": "~10.0.0-alpha.45",
9332
- "@geekmidas/emailkit": "~10.0.0-alpha.45",
9333
- "@geekmidas/envkit": "~10.0.0-alpha.45",
9334
- "@geekmidas/errors": "~10.0.0-alpha.45",
9335
- "@geekmidas/events": "~10.0.0-alpha.45",
9336
- "@geekmidas/logger": "~10.0.0-alpha.45",
9337
- "@geekmidas/manifest": "~10.0.0-alpha.45",
9338
- "@geekmidas/rate-limit": "~10.0.0-alpha.45",
9339
- "@geekmidas/schema": "~10.0.0-alpha.45",
9340
- "@geekmidas/services": "~10.0.0-alpha.45",
9341
- "@geekmidas/storage": "~10.0.0-alpha.45",
9342
- "@geekmidas/studio": "~10.0.0-alpha.45",
9343
- "@geekmidas/telescope": "~10.0.0-alpha.45",
9344
- "@geekmidas/testkit": "~10.0.0-alpha.45",
9085
+ "@geekmidas/audit": "~10.0.0-alpha.47",
9086
+ "@geekmidas/auth": "~10.0.0-alpha.47",
9087
+ "@geekmidas/cache": "~10.0.0-alpha.47",
9088
+ "@geekmidas/client": "~10.0.0-alpha.47",
9089
+ "@geekmidas/cloud": "~10.0.0-alpha.47",
9090
+ "@geekmidas/constructs": "~10.0.0-alpha.47",
9091
+ "@geekmidas/db": "~10.0.0-alpha.47",
9092
+ "@geekmidas/emailkit": "~10.0.0-alpha.47",
9093
+ "@geekmidas/envkit": "~10.0.0-alpha.47",
9094
+ "@geekmidas/errors": "~10.0.0-alpha.47",
9095
+ "@geekmidas/events": "~10.0.0-alpha.47",
9096
+ "@geekmidas/logger": "~10.0.0-alpha.47",
9097
+ "@geekmidas/manifest": "~10.0.0-alpha.47",
9098
+ "@geekmidas/rate-limit": "~10.0.0-alpha.47",
9099
+ "@geekmidas/schema": "~10.0.0-alpha.47",
9100
+ "@geekmidas/services": "~10.0.0-alpha.47",
9101
+ "@geekmidas/storage": "~10.0.0-alpha.47",
9102
+ "@geekmidas/studio": "~10.0.0-alpha.47",
9103
+ "@geekmidas/telescope": "~10.0.0-alpha.47",
9104
+ "@geekmidas/testkit": "~10.0.0-alpha.47",
9345
9105
  "@geekmidas/cli": `~${loadPackageJson().version}`
9346
9106
  };
9347
9107
  //#endregion
@@ -10868,7 +10628,7 @@ function deployPackage(options) {
10868
10628
  case "sst": {
10869
10629
  const v = GEEKMIDAS_VERSIONS;
10870
10630
  return {
10871
- scripts: perStage(options, (stage) => `gkm build --provider aws && sst deploy --stage ${stage}`),
10631
+ scripts: perStage(options, (stage) => `gkm build && sst deploy --stage ${stage}`),
10872
10632
  dependencies: {
10873
10633
  "@geekmidas/cloud": v["@geekmidas/cloud"],
10874
10634
  "@geekmidas/db": v["@geekmidas/db"],
@@ -10933,7 +10693,7 @@ export default $config({
10933
10693
 
10934
10694
  async run() {
10935
10695
  const { App, fromManifest, Stack } = await import('@geekmidas/cloud/sst');
10936
- // Written by \`gkm build --provider aws\`. Imported rather than discovered,
10696
+ // Written by \`gkm build\`. Imported rather than discovered,
10937
10697
  // so SST's toolchain never evaluates the application's own modules.
10938
10698
  const { backends, constructs } = await import('./.gkm/manifest/aws.js');
10939
10699
 
@@ -11171,8 +10931,11 @@ coverage/
11171
10931
  ${isSst ? "\n# SST\n.sst/\n" : ""}`;
11172
10932
  const tsConfig = {
11173
10933
  compilerOptions: {
10934
+ paths: {
10935
+ ...isFullstack ? { [`@${options.name}/constructs/*`]: ["./constructs/*"] } : {},
10936
+ [`@${options.name}/manifest`]: [`./.gkm/manifest/${isSst ? "aws" : "server"}.ts`]
10937
+ },
11174
10938
  ...isFullstack ? {
11175
- paths: { [`@${options.name}/constructs/*`]: ["./constructs/*"] },
11176
10939
  allowImportingTsExtensions: true,
11177
10940
  noEmit: true
11178
10941
  } : {},
@@ -11313,7 +11076,7 @@ export default defineWorkspace({
11313
11076
  // \`production-${options.name}-database\` on Dokploy and on AWS alike.
11314
11077
  name: '${options.name}',
11315
11078
  ${stagesBlock(options.stages)}
11316
-
11079
+ ${deployBlock(options)}
11317
11080
  // Every kind, in every app. A declared database is why a Postgres exists, a
11318
11081
  // declared bucket is why MinIO does, a declared topic is why a broker does —
11319
11082
  // none of it listed here. It is also where the apps come from: a
@@ -11331,6 +11094,18 @@ ${workspaceConstructsGlobs(options.routesStructure, dirname(options.apiPath)).ma
11331
11094
  `;
11332
11095
  }
11333
11096
  /**
11097
+ * Where it deploys, when that is not the default Dokploy: what a bare
11098
+ * `gkm build` builds for, and which backends a cache or a broker resolves to.
11099
+ */
11100
+ function deployBlock(options) {
11101
+ if (options.deployTarget !== "sst") return "";
11102
+ return `
11103
+ // Deployed with \`sst deploy\`, from the manifest \`gkm build\` writes to
11104
+ // \`.gkm/manifest/aws.ts\`. Each construct becomes a managed AWS resource.
11105
+ deploy: { default: 'sst' },
11106
+ `;
11107
+ }
11108
+ /**
11334
11109
  * Where a deployed stage's secrets live. On AWS that is SSM in the stage's own
11335
11110
  * account, which the deploy job reaches with the role it already assumed; a
11336
11111
  * server target keeps the encrypted local file, the default.
@@ -11356,7 +11131,7 @@ function secretsStore(options) {
11356
11131
  */
11357
11132
  function generateRootConstructs(options) {
11358
11133
  if (!options.monorepo || options.template !== "fullstack") return [];
11359
- const { name, frontendFramework } = options;
11134
+ const { frontendFramework } = options;
11360
11135
  const { cache, uploads, mail } = options.constructs;
11361
11136
  const db = databaseFor();
11362
11137
  const files = [];
@@ -12116,7 +11891,7 @@ const serverlessTemplate = {
12116
11891
  },
12117
11892
  scripts: {
12118
11893
  dev: "gkm dev",
12119
- build: "gkm build --provider aws-apigatewayv2",
11894
+ build: "gkm build --provider aws",
12120
11895
  test: "vitest",
12121
11896
  "test:once": "vitest run",
12122
11897
  typecheck: "tsc --noEmit",
@@ -12125,7 +11900,7 @@ const serverlessTemplate = {
12125
11900
  "fmt:check": "biome format ."
12126
11901
  },
12127
11902
  files: (options) => {
12128
- const { loggerType, routesStructure, name } = options;
11903
+ const { loggerType, routesStructure } = options;
12129
11904
  const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
12130
11905
 
12131
11906
  export const logger = createLogger();
@@ -12410,10 +12185,6 @@ export const telescope = new Telescope({
12410
12185
  */
12411
12186
  const OPENAPI_OUTPUT_PATH = "./.gkm/openapi/api.ts";
12412
12187
  /**
12413
- * Endpoints output path (generated by gkm dev/build)
12414
- */
12415
- const ENDPOINTS_OUTPUT_PATH = "./.gkm/server/endpoints.ts";
12416
- /**
12417
12188
  * All available templates
12418
12189
  */
12419
12190
  const templates = {
@@ -12626,10 +12397,7 @@ function generatePackageJson(options, template) {
12626
12397
  private: true,
12627
12398
  type: "module",
12628
12399
  ...!monorepo && options.packageManager === "pnpm" ? { packageManager: PNPM_VERSION } : {},
12629
- exports: {
12630
- "./client": OPENAPI_OUTPUT_PATH,
12631
- "./endpoints": ENDPOINTS_OUTPUT_PATH
12632
- },
12400
+ exports: { "./client": OPENAPI_OUTPUT_PATH },
12633
12401
  scripts,
12634
12402
  dependencies: sortObject(dependencies),
12635
12403
  devDependencies: sortObject(devDependencies)
@@ -15355,7 +15123,7 @@ function printNextSteps(projectName, options, pkgManager) {
15355
15123
  }
15356
15124
  if (options.deployTarget === "sst") {
15357
15125
  console.log("🚀 Deployment (AWS, through SST):");
15358
- for (const stage of deployed) console.log(` ${getRunCommand(pkgManager, `deploy:${stage}`)} # gkm build --provider aws, then sst deploy`);
15126
+ for (const stage of deployed) console.log(` ${getRunCommand(pkgManager, `deploy:${stage}`)} # gkm build, then sst deploy`);
15359
15127
  console.log(` Uses your AWS credentials, in ${options.region}.`);
15360
15128
  if (options.constructs.mail) console.log(" Set MAIL_FROM to a sender verified in SES first.");
15361
15129
  console.log("");
@@ -15742,8 +15510,8 @@ async function reconcileLocal(workspace, stage, options) {
15742
15510
  logger$1.log("🐳 Services already up to date");
15743
15511
  return;
15744
15512
  }
15745
- logger$1.log(`🐳 Services: ${result.plan.containers.join(", ")}`);
15746
- for (const [container, address] of Object.entries(result.addresses)) logger$1.log(` ${container}: ${address}`);
15513
+ logger$1.log("🐳 Services");
15514
+ for (const line of describeServices(result.services)) logger$1.log(line);
15747
15515
  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://"));
15748
15516
  if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
15749
15517
  }
@@ -16015,7 +15783,7 @@ async function testCommand(options = {}) {
16015
15783
  root: workspace.root,
16016
15784
  targets: [workspace.root, ...Object.values(workspace.apps).map((app) => isAbsolute(app.path) ? app.path : join(workspace.root, app.path))],
16017
15785
  patterns: constructGlobs(workspace),
16018
- cacheBackend: backendsOf(workspace).cache,
15786
+ cacheBackend: backendsOf$1(workspace).cache,
16019
15787
  stage: TEST_STAGE,
16020
15788
  env: finalCredentials,
16021
15789
  factories: join(workspace.root, workspace.test.factories ?? "test/factories")
@@ -16090,8 +15858,8 @@ function testDatabases(reconciled) {
16090
15858
  * starts without the reference data the app needs.
16091
15859
  */
16092
15860
  async function migrateTestStage(workspace, env) {
16093
- const { migrateAndSeed } = await import("./migrate-BRklCm7b.mjs");
16094
- const { discover } = await import("./discover-BLhf7V_J.mjs").then((n) => n.n);
15861
+ const { migrateAndSeed } = await import("./migrate-DhxTIA0z.mjs");
15862
+ const { discover } = await import("./discover-Cf2jAcgF.mjs").then((n) => n.n);
16095
15863
  const sources = {};
16096
15864
  const manifest = await discover({
16097
15865
  patterns: constructGlobs(workspace),
@@ -16456,29 +16224,13 @@ program.command("setup").description("Reconcile declared constructs — containe
16456
16224
  process.exit(1);
16457
16225
  }
16458
16226
  });
16459
- 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) => {
16227
+ 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) => {
16460
16228
  try {
16461
16229
  const globalOptions = program.opts();
16462
16230
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16463
- if (options.provider) {
16464
- if (!["aws", "server"].includes(options.provider)) process.exit(1);
16465
- await buildCommand({
16466
- provider: options.provider,
16467
- enableOpenApi: options.enableOpenapi || false,
16468
- production: options.production || false,
16469
- skipBundle: options.skipBundle || false,
16470
- stage: options.stage,
16471
- markOptional: options.markOptional || false
16472
- });
16473
- } else if (options.providers) await buildCommand({
16474
- providers: [...new Set(options.providers.split(",").map((p) => p.trim()))],
16475
- enableOpenApi: options.enableOpenapi || false,
16476
- production: options.production || false,
16477
- skipBundle: options.skipBundle || false,
16478
- stage: options.stage,
16479
- markOptional: options.markOptional || false
16480
- });
16481
- else await buildCommand({
16231
+ if (options.provider !== void 0 && !isMainProvider(options.provider)) throw new UnknownBuildProvider(options.provider);
16232
+ await buildCommand({
16233
+ ...options.provider ? { provider: options.provider } : {},
16482
16234
  enableOpenApi: options.enableOpenapi || false,
16483
16235
  production: options.production || false,
16484
16236
  skipBundle: options.skipBundle || false,
@@ -16537,7 +16289,7 @@ program.command("migrate").description("Apply each database construct's migratio
16537
16289
  try {
16538
16290
  const globalOptions = program.opts();
16539
16291
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16540
- const { migrateCommand } = await import("./migrate-BRklCm7b.mjs");
16292
+ const { migrateCommand } = await import("./migrate-DhxTIA0z.mjs");
16541
16293
  await migrateCommand({
16542
16294
  ...options,
16543
16295
  ...construct ? { construct } : {}
@@ -16551,7 +16303,7 @@ program.command("seed").description("Migrate, then run each database construct's
16551
16303
  try {
16552
16304
  const globalOptions = program.opts();
16553
16305
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16554
- const { seedCommand } = await import("./migrate-BRklCm7b.mjs");
16306
+ const { seedCommand } = await import("./migrate-DhxTIA0z.mjs");
16555
16307
  await seedCommand({
16556
16308
  ...options,
16557
16309
  ...construct ? { construct } : {}
@@ -16565,7 +16317,7 @@ program.command("migration").description("Write a construct's next migration: an
16565
16317
  try {
16566
16318
  const globalOptions = program.opts();
16567
16319
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16568
- const { migrationCommand } = await import("./migrate-BRklCm7b.mjs");
16320
+ const { migrationCommand } = await import("./migrate-DhxTIA0z.mjs");
16569
16321
  await migrationCommand({
16570
16322
  construct,
16571
16323
  ...name ? { name } : {}
@@ -16659,6 +16411,28 @@ program.command("secrets:set").description("Set a custom secret for a stage").ar
16659
16411
  process.exit(1);
16660
16412
  }
16661
16413
  });
16414
+ 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) => {
16415
+ try {
16416
+ const globalOptions = program.opts();
16417
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16418
+ const { encryptionRotateCommand } = await import("./encryption-DyQnPsZN.mjs");
16419
+ await encryptionRotateCommand(construct, options);
16420
+ } catch (error) {
16421
+ console.error(formatError(error));
16422
+ process.exit(1);
16423
+ }
16424
+ });
16425
+ 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) => {
16426
+ try {
16427
+ const globalOptions = program.opts();
16428
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16429
+ const { encryptionRetireCommand } = await import("./encryption-DyQnPsZN.mjs");
16430
+ await encryptionRetireCommand(construct, key, options);
16431
+ } catch (error) {
16432
+ console.error(formatError(error));
16433
+ process.exit(1);
16434
+ }
16435
+ });
16662
16436
  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) => {
16663
16437
  try {
16664
16438
  const globalOptions = program.opts();
@@ -16704,7 +16478,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16704
16478
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
16705
16479
  process.exit(1);
16706
16480
  }
16707
- const { derivedContainers } = await import("./workspace-BKytVCzb.mjs").then((n) => n.o);
16481
+ const { derivedContainers } = await import("./workspace-cb6U29EL.mjs").then((n) => n.o);
16708
16482
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
16709
16483
  if (!result) {
16710
16484
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -16763,7 +16537,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
16763
16537
  try {
16764
16538
  const globalOptions = program.opts();
16765
16539
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16766
- const { deployGithubCommand } = await import("./github-D5MthUlR.mjs");
16540
+ const { deployGithubCommand } = await import("./github-BfpadQWQ.mjs");
16767
16541
  await deployGithubCommand(options);
16768
16542
  } catch (error) {
16769
16543
  console.error(formatError(error));