@geekmidas/cli 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +42 -6
  3. package/dist/{config-6JHOwLCx.cjs → config-D3ORuiUs.cjs} +2 -2
  4. package/dist/{config-6JHOwLCx.cjs.map → config-D3ORuiUs.cjs.map} +1 -1
  5. package/dist/{config-DxASSNjr.mjs → config-jsRYHOHU.mjs} +2 -2
  6. package/dist/{config-DxASSNjr.mjs.map → config-jsRYHOHU.mjs.map} +1 -1
  7. package/dist/config.cjs +2 -2
  8. package/dist/config.d.cts +2 -2
  9. package/dist/config.d.mts +2 -2
  10. package/dist/config.mjs +2 -2
  11. package/dist/{index-Bt2kX0-R.d.mts → index-3n-giNaw.d.mts} +18 -6
  12. package/dist/index-3n-giNaw.d.mts.map +1 -0
  13. package/dist/{index-Cyk2rTyj.d.cts → index-CiEOtKEX.d.cts} +18 -6
  14. package/dist/index-CiEOtKEX.d.cts.map +1 -0
  15. package/dist/index.cjs +182 -158
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.mjs +179 -155
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/{openapi-CnvwSRDU.cjs → openapi-BYxAWwok.cjs} +178 -32
  20. package/dist/openapi-BYxAWwok.cjs.map +1 -0
  21. package/dist/{openapi-BYlyAbH3.mjs → openapi-DenF-okj.mjs} +148 -32
  22. package/dist/openapi-DenF-okj.mjs.map +1 -0
  23. package/dist/openapi.cjs +3 -3
  24. package/dist/openapi.d.cts +1 -1
  25. package/dist/openapi.d.cts.map +1 -1
  26. package/dist/openapi.d.mts +1 -1
  27. package/dist/openapi.d.mts.map +1 -1
  28. package/dist/openapi.mjs +3 -3
  29. package/dist/{types-l53qUmGt.d.cts → types-C7QJJl9f.d.cts} +6 -2
  30. package/dist/types-C7QJJl9f.d.cts.map +1 -0
  31. package/dist/{types-wXMIMOyK.d.mts → types-Iqsq_FIG.d.mts} +6 -2
  32. package/dist/types-Iqsq_FIG.d.mts.map +1 -0
  33. package/dist/workspace/index.cjs +1 -1
  34. package/dist/workspace/index.d.cts +2 -2
  35. package/dist/workspace/index.d.mts +2 -2
  36. package/dist/workspace/index.mjs +1 -1
  37. package/dist/{workspace-D2ocAlpl.cjs → workspace-4SP3Gx4Y.cjs} +11 -3
  38. package/dist/{workspace-D2ocAlpl.cjs.map → workspace-4SP3Gx4Y.cjs.map} +1 -1
  39. package/dist/{workspace-9IQIjwkQ.mjs → workspace-D4z4A4cq.mjs} +11 -3
  40. package/dist/{workspace-9IQIjwkQ.mjs.map → workspace-D4z4A4cq.mjs.map} +1 -1
  41. package/package.json +4 -4
  42. package/src/build/__tests__/manifests.spec.ts +171 -0
  43. package/src/build/__tests__/partitions.spec.ts +110 -0
  44. package/src/build/index.ts +58 -15
  45. package/src/build/manifests.ts +153 -32
  46. package/src/build/partitions.ts +58 -0
  47. package/src/deploy/sniffer.ts +6 -1
  48. package/src/generators/Generator.ts +27 -7
  49. package/src/generators/OpenApiTsGenerator.ts +4 -4
  50. package/src/openapi.ts +2 -1
  51. package/src/types.ts +17 -1
  52. package/src/workspace/client-generator.ts +6 -3
  53. package/src/workspace/schema.ts +13 -3
  54. package/dist/index-Bt2kX0-R.d.mts.map +0 -1
  55. package/dist/index-Cyk2rTyj.d.cts.map +0 -1
  56. package/dist/openapi-BYlyAbH3.mjs.map +0 -1
  57. package/dist/openapi-CnvwSRDU.cjs.map +0 -1
  58. package/dist/types-l53qUmGt.d.cts.map +0 -1
  59. package/dist/types-wXMIMOyK.d.mts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  import { __require } from "./chunk-Duj1WY3L.mjs";
3
- import { getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, isDeployTargetSupported } from "./workspace-9IQIjwkQ.mjs";
4
- import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, parseModuleConfig } from "./config-DxASSNjr.mjs";
3
+ import { getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, isDeployTargetSupported } from "./workspace-D4z4A4cq.mjs";
4
+ import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, parseModuleConfig } from "./config-jsRYHOHU.mjs";
5
5
  import { getCredentialsPath, getDokployCredentials, getDokployRegistryId, getDokployToken, removeDokployCredentials, storeDokployCredentials, storeDokployRegistryId } from "./credentials-s1kLcIzK.mjs";
6
- import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-BYlyAbH3.mjs";
6
+ import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, copyAllClients, copyClientToFrontends, generateOpenApi, getBackendOpenApiPath, isPartitionedRoutes, normalizeRoutes, openapiCommand, resolveOpenApiConfig } from "./openapi-DenF-okj.mjs";
7
7
  import { getKeyPath, maskPassword, readStageSecrets, secretsExist, setCustomSecret, toEmbeddableSecrets, writeStageSecrets } from "./storage-DmCbr6DI.mjs";
8
8
  import { DokployApi } from "./dokploy-api-2ldYoN3i.mjs";
9
9
  import { encryptSecrets } from "./encryption-BOH5M-f-.mjs";
@@ -35,7 +35,7 @@ import prompts from "prompts";
35
35
 
36
36
  //#region package.json
37
37
  var name = "@geekmidas/cli";
38
- var version = "1.9.0";
38
+ var version = "1.9.1";
39
39
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
40
40
  var private$1 = false;
41
41
  var type = "module";
@@ -133,7 +133,7 @@ var package_default = {
133
133
 
134
134
  //#endregion
135
135
  //#region src/auth/index.ts
136
- const logger$13 = console;
136
+ const logger$12 = console;
137
137
  /**
138
138
  * Validate Dokploy token by making a test API call
139
139
  */
@@ -201,36 +201,36 @@ async function prompt$1(message, hidden = false) {
201
201
  async function loginCommand(options) {
202
202
  const { service, token: providedToken, endpoint: providedEndpoint } = options;
203
203
  if (service === "dokploy") {
204
- logger$13.log("\n🔐 Logging in to Dokploy...\n");
204
+ logger$12.log("\n🔐 Logging in to Dokploy...\n");
205
205
  let endpoint = providedEndpoint;
206
206
  if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
207
207
  endpoint = endpoint.replace(/\/$/, "");
208
208
  try {
209
209
  new URL(endpoint);
210
210
  } catch {
211
- logger$13.error("Invalid URL format");
211
+ logger$12.error("Invalid URL format");
212
212
  process.exit(1);
213
213
  }
214
214
  let token = providedToken;
215
215
  if (!token) {
216
- logger$13.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
216
+ logger$12.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
217
217
  token = await prompt$1("API Token: ", true);
218
218
  }
219
219
  if (!token) {
220
- logger$13.error("Token is required");
220
+ logger$12.error("Token is required");
221
221
  process.exit(1);
222
222
  }
223
- logger$13.log("\nValidating credentials...");
223
+ logger$12.log("\nValidating credentials...");
224
224
  const isValid = await validateDokployToken(endpoint, token);
225
225
  if (!isValid) {
226
- logger$13.error("\n✗ Invalid credentials. Please check your token and try again.");
226
+ logger$12.error("\n✗ Invalid credentials. Please check your token and try again.");
227
227
  process.exit(1);
228
228
  }
229
229
  await storeDokployCredentials(token, endpoint);
230
- logger$13.log("\n✓ Successfully logged in to Dokploy!");
231
- logger$13.log(` Endpoint: ${endpoint}`);
232
- logger$13.log(` Credentials stored in: ${getCredentialsPath()}`);
233
- logger$13.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
230
+ logger$12.log("\n✓ Successfully logged in to Dokploy!");
231
+ logger$12.log(` Endpoint: ${endpoint}`);
232
+ logger$12.log(` Credentials stored in: ${getCredentialsPath()}`);
233
+ logger$12.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
234
234
  }
235
235
  }
236
236
  /**
@@ -240,28 +240,28 @@ async function logoutCommand(options) {
240
240
  const { service = "dokploy" } = options;
241
241
  if (service === "all") {
242
242
  const dokployRemoved = await removeDokployCredentials();
243
- if (dokployRemoved) logger$13.log("\n✓ Logged out from all services");
244
- else logger$13.log("\nNo stored credentials found");
243
+ if (dokployRemoved) logger$12.log("\n✓ Logged out from all services");
244
+ else logger$12.log("\nNo stored credentials found");
245
245
  return;
246
246
  }
247
247
  if (service === "dokploy") {
248
248
  const removed = await removeDokployCredentials();
249
- if (removed) logger$13.log("\n✓ Logged out from Dokploy");
250
- else logger$13.log("\nNo Dokploy credentials found");
249
+ if (removed) logger$12.log("\n✓ Logged out from Dokploy");
250
+ else logger$12.log("\nNo Dokploy credentials found");
251
251
  }
252
252
  }
253
253
  /**
254
254
  * Show current login status
255
255
  */
256
256
  async function whoamiCommand() {
257
- logger$13.log("\n📋 Current credentials:\n");
257
+ logger$12.log("\n📋 Current credentials:\n");
258
258
  const dokploy = await getDokployCredentials();
259
259
  if (dokploy) {
260
- logger$13.log(" Dokploy:");
261
- logger$13.log(` Endpoint: ${dokploy.endpoint}`);
262
- logger$13.log(` Token: ${maskToken(dokploy.token)}`);
263
- } else logger$13.log(" Dokploy: Not logged in");
264
- logger$13.log(`\n Credentials file: ${getCredentialsPath()}`);
260
+ logger$12.log(" Dokploy:");
261
+ logger$12.log(` Endpoint: ${dokploy.endpoint}`);
262
+ logger$12.log(` Token: ${maskToken(dokploy.token)}`);
263
+ } else logger$12.log(" Dokploy: Not logged in");
264
+ logger$12.log(`\n Credentials file: ${getCredentialsPath()}`);
265
265
  }
266
266
  /**
267
267
  * Mask a token for display
@@ -347,7 +347,7 @@ function isEnabled(config$1) {
347
347
  var CronGenerator = class extends ConstructGenerator {
348
348
  async build(context, constructs, outputDir, options) {
349
349
  const provider = options?.provider || "aws-lambda";
350
- const logger$14 = console;
350
+ const logger$13 = console;
351
351
  const cronInfos = [];
352
352
  if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
353
353
  const cronsDir = join(outputDir, "crons");
@@ -362,7 +362,7 @@ var CronGenerator = class extends ConstructGenerator {
362
362
  memorySize: construct.memorySize,
363
363
  environment: await construct.getEnvironment()
364
364
  });
365
- logger$14.log(`Generated cron handler: ${key}`);
365
+ logger$13.log(`Generated cron handler: ${key}`);
366
366
  }
367
367
  return cronInfos;
368
368
  }
@@ -398,7 +398,7 @@ var FunctionGenerator = class extends ConstructGenerator {
398
398
  }
399
399
  async build(context, constructs, outputDir, options) {
400
400
  const provider = options?.provider || "aws-lambda";
401
- const logger$14 = console;
401
+ const logger$13 = console;
402
402
  const functionInfos = [];
403
403
  if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
404
404
  const functionsDir = join(outputDir, "functions");
@@ -412,7 +412,7 @@ var FunctionGenerator = class extends ConstructGenerator {
412
412
  memorySize: construct.memorySize,
413
413
  environment: await construct.getEnvironment()
414
414
  });
415
- logger$14.log(`Generated function handler: ${key}`);
415
+ logger$13.log(`Generated function handler: ${key}`);
416
416
  }
417
417
  return functionInfos;
418
418
  }
@@ -445,11 +445,11 @@ var SubscriberGenerator = class extends ConstructGenerator {
445
445
  }
446
446
  async build(context, constructs, outputDir, options) {
447
447
  const provider = options?.provider || "aws-lambda";
448
- const logger$14 = console;
448
+ const logger$13 = console;
449
449
  const subscriberInfos = [];
450
450
  if (provider === "server") {
451
451
  await this.generateServerSubscribersFile(outputDir, constructs);
452
- logger$14.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
452
+ logger$13.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
453
453
  return subscriberInfos;
454
454
  }
455
455
  if (constructs.length === 0) return subscriberInfos;
@@ -466,7 +466,7 @@ var SubscriberGenerator = class extends ConstructGenerator {
466
466
  memorySize: construct.memorySize,
467
467
  environment: await construct.getEnvironment()
468
468
  });
469
- logger$14.log(`Generated subscriber handler: ${key}`);
469
+ logger$13.log(`Generated subscriber handler: ${key}`);
470
470
  }
471
471
  return subscriberInfos;
472
472
  }
@@ -631,98 +631,6 @@ export async function setupSubscribers(
631
631
  }
632
632
  };
633
633
 
634
- //#endregion
635
- //#region src/workspace/client-generator.ts
636
- const logger$12 = console;
637
- /**
638
- * Get frontend apps that depend on a backend app.
639
- */
640
- function getDependentFrontends(workspace, backendAppName) {
641
- const dependentApps = [];
642
- for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "frontend" && app.dependencies.includes(backendAppName)) dependentApps.push(appName);
643
- return dependentApps;
644
- }
645
- /**
646
- * Get the path to a backend's OpenAPI spec file.
647
- */
648
- function getBackendOpenApiPath(workspace, backendAppName) {
649
- const app = workspace.apps[backendAppName];
650
- if (!app || app.type !== "backend") return null;
651
- return join(workspace.root, app.path, ".gkm", "openapi.ts");
652
- }
653
- /**
654
- * Count endpoints in an OpenAPI spec content.
655
- */
656
- function countEndpoints(content) {
657
- const endpointMatches = content.match(/'(GET|POST|PUT|PATCH|DELETE)\s+\/[^']+'/g);
658
- return endpointMatches?.length ?? 0;
659
- }
660
- /**
661
- * Copy the OpenAPI client from a backend to all dependent frontend apps.
662
- * Called when the backend's .gkm/openapi.ts file changes.
663
- */
664
- async function copyClientToFrontends(workspace, backendAppName, options = {}) {
665
- const log = options.silent ? () => {} : logger$12.log.bind(logger$12);
666
- const results = [];
667
- const backendApp = workspace.apps[backendAppName];
668
- if (!backendApp || backendApp.type !== "backend") return results;
669
- const openApiPath = join(workspace.root, backendApp.path, ".gkm", "openapi.ts");
670
- if (!existsSync(openApiPath)) return results;
671
- const content = await readFile(openApiPath, "utf-8");
672
- const endpointCount = countEndpoints(content);
673
- const dependentFrontends = getDependentFrontends(workspace, backendAppName);
674
- for (const frontendAppName of dependentFrontends) {
675
- const frontendApp = workspace.apps[frontendAppName];
676
- if (!frontendApp || frontendApp.type !== "frontend") continue;
677
- const clientOutput = frontendApp.client?.output;
678
- if (!clientOutput) continue;
679
- const result = {
680
- frontendApp: frontendAppName,
681
- backendApp: backendAppName,
682
- outputPath: "",
683
- endpointCount,
684
- success: false
685
- };
686
- try {
687
- const frontendPath = join(workspace.root, frontendApp.path);
688
- const outputDir = join(frontendPath, clientOutput);
689
- await mkdir(outputDir, { recursive: true });
690
- const fileName = `${backendAppName}.ts`;
691
- const outputPath = join(outputDir, fileName);
692
- const backendRelPath = relative(dirname(outputPath), join(workspace.root, backendApp.path));
693
- const clientContent = `/**
694
- * Auto-generated API client for ${backendAppName}
695
- * Generated from: ${backendRelPath}
696
- *
697
- * DO NOT EDIT - This file is automatically regenerated when backend schemas change.
698
- */
699
-
700
- ${content}
701
- `;
702
- await writeFile(outputPath, clientContent);
703
- result.outputPath = outputPath;
704
- result.success = true;
705
- log(`📦 Copied client to ${frontendAppName} from ${backendAppName} (${endpointCount} endpoints)`);
706
- } catch (error) {
707
- result.error = error.message;
708
- }
709
- results.push(result);
710
- }
711
- return results;
712
- }
713
- /**
714
- * Copy clients from all backends to their dependent frontends.
715
- * Useful for initial setup or force refresh.
716
- */
717
- async function copyAllClients(workspace, options = {}) {
718
- const allResults = [];
719
- for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "backend" && app.routes) {
720
- const results = await copyClientToFrontends(workspace, appName, options);
721
- allResults.push(...results);
722
- }
723
- return allResults;
724
- }
725
-
726
634
  //#endregion
727
635
  //#region src/dev/index.ts
728
636
  const logger$11 = console;
@@ -1982,22 +1890,58 @@ async function execCommand(commandArgs, options = {}) {
1982
1890
  //#endregion
1983
1891
  //#region src/build/manifests.ts
1984
1892
  const logger$10 = console;
1893
+ function isPartitioned(field) {
1894
+ return !Array.isArray(field);
1895
+ }
1896
+ /**
1897
+ * Serialize a manifest field to a TypeScript string.
1898
+ * Flat arrays serialize as JSON arrays, partitioned fields as objects of arrays.
1899
+ */
1900
+ function serializeField(field, indent = 2) {
1901
+ if (Array.isArray(field)) return JSON.stringify(field, null, indent);
1902
+ const entries = Object.entries(field).map(([key, value]) => ` ${JSON.stringify(key)}: ${JSON.stringify(value, null, indent)}`).join(",\n");
1903
+ return `{\n${entries},\n }`;
1904
+ }
1905
+ /**
1906
+ * Count total items across a manifest field (flat or partitioned).
1907
+ */
1908
+ function countItems(field) {
1909
+ if (Array.isArray(field)) return field.length;
1910
+ return Object.values(field).reduce((sum, arr) => sum + arr.length, 0);
1911
+ }
1912
+ /**
1913
+ * Generate derived types for a construct field.
1914
+ * @param fieldName - The field name in the manifest (e.g., 'routes')
1915
+ * @param typeName - The exported type name (e.g., 'Route')
1916
+ * @param partitioned - Whether this field is partitioned
1917
+ */
1918
+ function generateDerivedType(fieldName, typeName, partitioned) {
1919
+ if (partitioned) {
1920
+ const partitionTypeName = `${typeName}Partition`;
1921
+ return [`export type ${partitionTypeName} = keyof typeof manifest.${fieldName};`, `export type ${typeName}<P extends ${partitionTypeName} = ${partitionTypeName}> = (typeof manifest.${fieldName})[P][number];`].join("\n");
1922
+ }
1923
+ return `export type ${typeName} = (typeof manifest.${fieldName})[number];`;
1924
+ }
1985
1925
  async function generateAwsManifest(outputDir, routes, functions, crons, subscribers) {
1986
1926
  const manifestDir = join(outputDir, "manifest");
1987
1927
  await mkdir(manifestDir, { recursive: true });
1988
- const awsRoutes = routes.filter((r) => r.method !== "ALL");
1928
+ const awsRoutes = filterAllRoutes(routes);
1929
+ const routesPartitioned = isPartitioned(awsRoutes);
1930
+ const functionsPartitioned = isPartitioned(functions);
1931
+ const cronsPartitioned = isPartitioned(crons);
1932
+ const subscribersPartitioned = isPartitioned(subscribers);
1989
1933
  const content = `export const manifest = {
1990
- routes: ${JSON.stringify(awsRoutes, null, 2)},
1991
- functions: ${JSON.stringify(functions, null, 2)},
1992
- crons: ${JSON.stringify(crons, null, 2)},
1993
- subscribers: ${JSON.stringify(subscribers, null, 2)},
1934
+ routes: ${serializeField(awsRoutes)},
1935
+ functions: ${serializeField(functions)},
1936
+ crons: ${serializeField(crons)},
1937
+ subscribers: ${serializeField(subscribers)},
1994
1938
  } as const;
1995
1939
 
1996
1940
  // Derived types
1997
- export type Route = (typeof manifest.routes)[number];
1998
- export type Function = (typeof manifest.functions)[number];
1999
- export type Cron = (typeof manifest.crons)[number];
2000
- export type Subscriber = (typeof manifest.subscribers)[number];
1941
+ ${generateDerivedType("routes", "Route", routesPartitioned)}
1942
+ ${generateDerivedType("functions", "Function", functionsPartitioned)}
1943
+ ${generateDerivedType("crons", "Cron", cronsPartitioned)}
1944
+ ${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
2001
1945
 
2002
1946
  // Useful union types
2003
1947
  export type Authorizer = Route['authorizer'];
@@ -2006,30 +1950,25 @@ export type RoutePath = Route['path'];
2006
1950
  `;
2007
1951
  const manifestPath = join(manifestDir, "aws.ts");
2008
1952
  await writeFile(manifestPath, content);
2009
- logger$10.log(`Generated AWS manifest with ${awsRoutes.length} routes, ${functions.length} functions, ${crons.length} crons, ${subscribers.length} subscribers`);
1953
+ logger$10.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers`);
2010
1954
  logger$10.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2011
1955
  }
2012
1956
  async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
2013
1957
  const manifestDir = join(outputDir, "manifest");
2014
1958
  await mkdir(manifestDir, { recursive: true });
2015
- const serverRoutes = routes.filter((r) => r.method !== "ALL").map((r) => ({
2016
- path: r.path,
2017
- method: r.method,
2018
- authorizer: r.authorizer
2019
- }));
2020
- const serverSubscribers = subscribers.map((s) => ({
2021
- name: s.name,
2022
- subscribedEvents: s.subscribedEvents
2023
- }));
1959
+ const serverRoutes = mapRouteMetadata(filterAllRoutes(routes));
1960
+ const serverSubscribers = mapSubscriberMetadata(subscribers);
1961
+ const routesPartitioned = isPartitioned(serverRoutes);
1962
+ const subscribersPartitioned = isPartitioned(serverSubscribers);
2024
1963
  const content = `export const manifest = {
2025
1964
  app: ${JSON.stringify(appInfo, null, 2)},
2026
- routes: ${JSON.stringify(serverRoutes, null, 2)},
2027
- subscribers: ${JSON.stringify(serverSubscribers, null, 2)},
1965
+ routes: ${serializeField(serverRoutes)},
1966
+ subscribers: ${serializeField(serverSubscribers)},
2028
1967
  } as const;
2029
1968
 
2030
1969
  // Derived types
2031
- export type Route = (typeof manifest.routes)[number];
2032
- export type Subscriber = (typeof manifest.subscribers)[number];
1970
+ ${generateDerivedType("routes", "Route", routesPartitioned)}
1971
+ ${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
2033
1972
 
2034
1973
  // Useful union types
2035
1974
  export type Authorizer = Route['authorizer'];
@@ -2038,9 +1977,70 @@ export type RoutePath = Route['path'];
2038
1977
  `;
2039
1978
  const manifestPath = join(manifestDir, "server.ts");
2040
1979
  await writeFile(manifestPath, content);
2041
- logger$10.log(`Generated server manifest with ${serverRoutes.length} routes, ${serverSubscribers.length} subscribers`);
1980
+ logger$10.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers`);
2042
1981
  logger$10.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
2043
1982
  }
1983
+ /**
1984
+ * Filter out 'ALL' method routes from a manifest field (flat or partitioned).
1985
+ */
1986
+ function filterAllRoutes(routes) {
1987
+ if (Array.isArray(routes)) return routes.filter((r) => r.method !== "ALL");
1988
+ const result = {};
1989
+ for (const [partition, partitionRoutes] of Object.entries(routes)) result[partition] = partitionRoutes.filter((r) => r.method !== "ALL");
1990
+ return result;
1991
+ }
1992
+ /**
1993
+ * Map routes to server metadata (path, method, authorizer only).
1994
+ */
1995
+ function mapRouteMetadata(routes) {
1996
+ const mapFn = (r) => ({
1997
+ path: r.path,
1998
+ method: r.method,
1999
+ authorizer: r.authorizer
2000
+ });
2001
+ if (Array.isArray(routes)) return routes.map(mapFn);
2002
+ const result = {};
2003
+ for (const [partition, partitionRoutes] of Object.entries(routes)) result[partition] = partitionRoutes.map(mapFn);
2004
+ return result;
2005
+ }
2006
+ /**
2007
+ * Map subscribers to server metadata (name, subscribedEvents only).
2008
+ */
2009
+ function mapSubscriberMetadata(subscribers) {
2010
+ const mapFn = (s) => ({
2011
+ name: s.name,
2012
+ subscribedEvents: s.subscribedEvents
2013
+ });
2014
+ if (Array.isArray(subscribers)) return subscribers.map(mapFn);
2015
+ const result = {};
2016
+ for (const [partition, partitionSubs] of Object.entries(subscribers)) result[partition] = partitionSubs.map(mapFn);
2017
+ return result;
2018
+ }
2019
+
2020
+ //#endregion
2021
+ //#region src/build/partitions.ts
2022
+ const DEFAULT_PARTITION = "default";
2023
+ /**
2024
+ * Check if any construct across the given arrays has a non-undefined partition.
2025
+ * When true, the manifest should use the partitioned shape for that construct type.
2026
+ */
2027
+ function hasPartitions(constructs) {
2028
+ return constructs.some((c) => c.partition !== void 0);
2029
+ }
2030
+ /**
2031
+ * Group an info array by partition, using the partition values from
2032
+ * the corresponding construct array. Both arrays must be the same length
2033
+ * and in the same order.
2034
+ */
2035
+ function groupInfosByPartition(infos, constructs) {
2036
+ const groups = {};
2037
+ for (let i = 0; i < infos.length; i++) {
2038
+ const partition = constructs[i]?.partition ?? DEFAULT_PARTITION;
2039
+ if (!groups[partition]) groups[partition] = [];
2040
+ groups[partition].push(infos[i]);
2041
+ }
2042
+ return groups;
2043
+ }
2044
2044
 
2045
2045
  //#endregion
2046
2046
  //#region src/build/index.ts
@@ -2062,10 +2062,10 @@ async function buildCommand(options) {
2062
2062
  const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
2063
2063
  if (production) logger$9.log(`🏭 Building for PRODUCTION`);
2064
2064
  logger$9.log(`Building with providers: ${resolved.providers.join(", ")}`);
2065
- logger$9.log(`Loading routes from: ${config$1.routes}`);
2066
- if (config$1.functions) logger$9.log(`Loading functions from: ${config$1.functions}`);
2067
- if (config$1.crons) logger$9.log(`Loading crons from: ${config$1.crons}`);
2068
- if (config$1.subscribers) logger$9.log(`Loading subscribers from: ${config$1.subscribers}`);
2065
+ logger$9.log(`Loading routes from: ${formatRoutes(config$1.routes)}`);
2066
+ if (config$1.functions) logger$9.log(`Loading functions from: ${formatRoutes(config$1.functions)}`);
2067
+ if (config$1.crons) logger$9.log(`Loading crons from: ${formatRoutes(config$1.crons)}`);
2068
+ if (config$1.subscribers) logger$9.log(`Loading subscribers from: ${formatRoutes(config$1.subscribers)}`);
2069
2069
  logger$9.log(`Using envParser: ${config$1.envParser}`);
2070
2070
  const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config$1.envParser, "envParser");
2071
2071
  const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config$1.logger, "logger");
@@ -2137,6 +2137,10 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
2137
2137
  subscriberGenerator.build(context, subscribers, outputDir, { provider })
2138
2138
  ]);
2139
2139
  logger$9.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
2140
+ const manifestRoutes = assembleManifestField(routes, endpoints);
2141
+ const manifestFunctions = assembleManifestField(functionInfos, functions);
2142
+ const manifestCrons = assembleManifestField(cronInfos, crons);
2143
+ const manifestSubscribers = assembleManifestField(subscriberInfos, subscribers);
2140
2144
  if (provider === "server") {
2141
2145
  const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
2142
2146
  path: construct._path,
@@ -2144,11 +2148,12 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
2144
2148
  handler: "",
2145
2149
  authorizer: construct.authorizer?.name ?? "none"
2146
2150
  })));
2151
+ const serverRouteField = assembleManifestField(routeMetadata, endpoints);
2147
2152
  const appInfo = {
2148
2153
  handler: relative(process.cwd(), join(outputDir, "app.ts")),
2149
2154
  endpoints: relative(process.cwd(), join(outputDir, "endpoints.ts"))
2150
2155
  };
2151
- await generateServerManifest(rootOutputDir, appInfo, routeMetadata, subscriberInfos);
2156
+ await generateServerManifest(rootOutputDir, appInfo, serverRouteField, manifestSubscribers);
2152
2157
  let masterKey;
2153
2158
  if (context.production?.bundle && !skipBundle) {
2154
2159
  logger$9.log(`\n📦 Bundling production server...`);
@@ -2178,7 +2183,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
2178
2183
  }
2179
2184
  }
2180
2185
  return { masterKey };
2181
- } else await generateAwsManifest(rootOutputDir, routes, functionInfos, cronInfos, subscriberInfos);
2186
+ } else await generateAwsManifest(rootOutputDir, manifestRoutes, manifestFunctions, manifestCrons, manifestSubscribers);
2182
2187
  return {};
2183
2188
  }
2184
2189
  /**
@@ -2276,6 +2281,25 @@ function getAppOutputPath(workspace, _appName, app) {
2276
2281
  if (app.type === "frontend") return join(appPath, ".next");
2277
2282
  else return join(appPath, ".gkm");
2278
2283
  }
2284
+ /**
2285
+ * Format routes for logging, handling PartitionedRoutes.
2286
+ */
2287
+ function formatRoutes(routes) {
2288
+ if (isPartitionedRoutes(routes)) {
2289
+ const paths = Array.isArray(routes.paths) ? routes.paths.join(", ") : routes.paths;
2290
+ return `${paths} (partitioned)`;
2291
+ }
2292
+ return Array.isArray(routes) ? routes.join(", ") : routes;
2293
+ }
2294
+ /**
2295
+ * Assemble a ManifestField from build infos and constructs.
2296
+ * If any construct has a partition, returns a Record<string, T[]>.
2297
+ * Otherwise, returns a flat T[].
2298
+ */
2299
+ function assembleManifestField(infos, constructs) {
2300
+ if (!hasPartitions(constructs)) return infos;
2301
+ return groupInfosByPartition(infos, constructs);
2302
+ }
2279
2303
 
2280
2304
  //#endregion
2281
2305
  //#region src/deploy/state.ts
@@ -4890,7 +4914,7 @@ async function sniffAppEnvironment(app, appName, workspacePath, options = {}) {
4890
4914
  };
4891
4915
  }
4892
4916
  if (app.routes) {
4893
- const result = await sniffRouteFiles(app.routes, app.path, workspacePath);
4917
+ const result = await sniffRouteFiles(normalizeRoutes(app.routes), app.path, workspacePath);
4894
4918
  if (logWarnings && result.error) console.warn(`[sniffer] ${appName}: Route sniffing threw error (env vars still captured): ${result.error.message}`);
4895
4919
  return {
4896
4920
  appName,