@geekmidas/cli 1.9.1 → 1.10.1

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 (60) hide show
  1. package/CHANGELOG.md +24 -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 +189 -165
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.mjs +186 -162
  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/init/versions.ts +7 -7
  51. package/src/openapi.ts +2 -1
  52. package/src/types.ts +17 -1
  53. package/src/workspace/client-generator.ts +6 -3
  54. package/src/workspace/schema.ts +13 -3
  55. package/dist/index-Bt2kX0-R.d.mts.map +0 -1
  56. package/dist/index-Cyk2rTyj.d.cts.map +0 -1
  57. package/dist/openapi-BYlyAbH3.mjs.map +0 -1
  58. package/dist/openapi-CnvwSRDU.cjs.map +0 -1
  59. package/dist/types-l53qUmGt.d.cts.map +0 -1
  60. package/dist/types-wXMIMOyK.d.mts.map +0 -1
@@ -1,11 +1,18 @@
1
- import { loadWorkspaceConfig } from "./config-DxASSNjr.mjs";
1
+ import { loadWorkspaceConfig } from "./config-jsRYHOHU.mjs";
2
+ import { existsSync } from "node:fs";
2
3
  import { dirname, join, relative } from "node:path";
3
- import { mkdir, writeFile } from "node:fs/promises";
4
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
5
  import fg from "fast-glob";
5
6
  import kebabCase from "lodash.kebabcase";
6
7
  import { Endpoint } from "@geekmidas/constructs/endpoints";
7
8
  import { StandardSchemaJsonSchema, getSchemaMetadata } from "@geekmidas/schema/conversion";
8
9
 
10
+ //#region src/types.ts
11
+ function isPartitionedRoutes(routes) {
12
+ return typeof routes === "object" && routes !== null && !Array.isArray(routes) && "paths" in routes;
13
+ }
14
+
15
+ //#endregion
9
16
  //#region src/generators/Generator.ts
10
17
  var ConstructGenerator = class {
11
18
  static async build(context, outputDir, generator, patterns, options) {
@@ -13,8 +20,13 @@ var ConstructGenerator = class {
13
20
  return generator.build(context, constructs, outputDir, options);
14
21
  }
15
22
  async load(patterns, cwd = process.cwd(), bustCache = false) {
16
- const logger = console;
17
- const globPatterns = Array.isArray(patterns) ? patterns : patterns ? [patterns] : [];
23
+ const logger$1 = console;
24
+ let globPatterns;
25
+ let partitionFn;
26
+ if (isPartitionedRoutes(patterns)) {
27
+ globPatterns = Array.isArray(patterns.paths) ? patterns.paths : [patterns.paths];
28
+ partitionFn = patterns.partition;
29
+ } else globPatterns = Array.isArray(patterns) ? patterns : patterns ? [patterns] : [];
18
30
  const files = fg.stream(globPatterns, {
19
31
  cwd,
20
32
  absolute: true
@@ -24,6 +36,7 @@ var ConstructGenerator = class {
24
36
  const file = f.toString();
25
37
  const importPath = bustCache ? `${file}?t=${Date.now()}` : file;
26
38
  const module = await import(importPath);
39
+ const partition = partitionFn ? partitionFn(file) : void 0;
27
40
  for (const [key, construct] of Object.entries(module)) if (this.isConstruct(construct)) constructs.push({
28
41
  key,
29
42
  name: kebabCase(key),
@@ -31,10 +44,11 @@ var ConstructGenerator = class {
31
44
  path: {
32
45
  absolute: file,
33
46
  relative: relative(process.cwd(), file)
34
- }
47
+ },
48
+ partition
35
49
  });
36
50
  } catch (error) {
37
- logger.warn(`Failed to load ${f}:`, error.message);
51
+ logger$1.warn(`Failed to load ${f}:`, error.message);
38
52
  throw new Error("Failed to load constructs. Please check the logs for details.");
39
53
  }
40
54
  return constructs;
@@ -630,7 +644,7 @@ var EndpointGenerator = class extends ConstructGenerator {
630
644
  async build(context, constructs, outputDir, options) {
631
645
  const provider = options?.provider || "aws-apigatewayv2";
632
646
  const enableOpenApi = options?.enableOpenApi || false;
633
- const logger = console;
647
+ const logger$1 = console;
634
648
  const routes = [];
635
649
  if (constructs.length === 0) return routes;
636
650
  if (provider === "server") {
@@ -642,7 +656,7 @@ var EndpointGenerator = class extends ConstructGenerator {
642
656
  handler: relative(process.cwd(), appFile),
643
657
  authorizer: "none"
644
658
  });
645
- logger.log(`Generated server with ${constructs.length} endpoints${enableOpenApi ? " (OpenAPI enabled)" : ""}`);
659
+ logger$1.log(`Generated server with ${constructs.length} endpoints${enableOpenApi ? " (OpenAPI enabled)" : ""}`);
646
660
  } else if (provider === "aws-lambda") {
647
661
  const routesDir = join(outputDir, "routes");
648
662
  await mkdir(routesDir, { recursive: true });
@@ -658,7 +672,7 @@ var EndpointGenerator = class extends ConstructGenerator {
658
672
  authorizer: construct.authorizer?.name ?? "none"
659
673
  };
660
674
  routes.push(routeInfo);
661
- logger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
675
+ logger$1.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
662
676
  }
663
677
  } else for (const { key, construct, path } of constructs) {
664
678
  const handlerFile = await this.generateHandlerFile(outputDir, path.relative, key, provider, construct, context);
@@ -672,7 +686,7 @@ var EndpointGenerator = class extends ConstructGenerator {
672
686
  authorizer: construct.authorizer?.name ?? "none"
673
687
  };
674
688
  routes.push(routeInfo);
675
- logger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
689
+ logger$1.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
676
690
  }
677
691
  return routes;
678
692
  }
@@ -761,7 +775,7 @@ export async function setupEndpoints(
761
775
  * Generate optimized endpoints files with nested folder structure (per-endpoint files)
762
776
  */
763
777
  async generateOptimizedEndpointsFile(endpointsPath, endpoints, _endpointImports, _allExportNames) {
764
- const logger = console;
778
+ const logger$1 = console;
765
779
  const outputDir = dirname(endpointsPath);
766
780
  const endpointsDir = join(outputDir, "endpoints");
767
781
  await mkdir(join(endpointsDir, "minimal"), { recursive: true });
@@ -778,11 +792,11 @@ export async function setupEndpoints(
778
792
  };
779
793
  });
780
794
  const summary = summarizeAnalysis(analyses);
781
- logger.log(`\n📊 Endpoint Analysis:`);
782
- logger.log(` Total: ${summary.total} endpoints`);
783
- logger.log(` - Minimal (near-raw-Hono): ${summary.byTier.minimal} endpoints`);
784
- logger.log(` - Standard (auth/services): ${summary.byTier.standard} endpoints`);
785
- logger.log(` - Full (audits/rls/rate-limit): ${summary.byTier.full} endpoints`);
795
+ logger$1.log(`\n📊 Endpoint Analysis:`);
796
+ logger$1.log(` Total: ${summary.total} endpoints`);
797
+ logger$1.log(` - Minimal (near-raw-Hono): ${summary.byTier.minimal} endpoints`);
798
+ logger$1.log(` - Standard (auth/services): ${summary.byTier.standard} endpoints`);
799
+ logger$1.log(` - Full (audits/rls/rate-limit): ${summary.byTier.full} endpoints`);
786
800
  const files = generateEndpointFilesNested(analyses, endpointImports);
787
801
  for (const [filename, content] of Object.entries(files)) {
788
802
  const filePath = join(endpointsDir, filename);
@@ -791,7 +805,7 @@ export async function setupEndpoints(
791
805
  }
792
806
  const endpointFiles = Object.keys(files).filter((f) => !f.endsWith("index.ts") && !f.endsWith("validators.ts")).length;
793
807
  const indexFiles = Object.keys(files).filter((f) => f.endsWith("index.ts")).length;
794
- logger.log(` Generated ${endpointFiles} endpoint files + ${indexFiles} index files + validators.ts`);
808
+ logger$1.log(` Generated ${endpointFiles} endpoint files + ${indexFiles} index files + validators.ts`);
795
809
  return join(endpointsDir, "index.ts");
796
810
  }
797
811
  async generateAppFile(outputDir, context) {
@@ -1556,7 +1570,7 @@ export function createApi(options: CreateApiOptions) {
1556
1570
  // API Client Factory
1557
1571
  // ============================================================
1558
1572
 
1559
- import { TypedFetcher, type FetcherOptions } from '@geekmidas/client/fetcher';
1573
+ import { createTypedFetcher, type FetcherOptions } from '@geekmidas/client/fetcher';
1560
1574
  import { createEndpointHooks } from '@geekmidas/client/endpoint-hooks';
1561
1575
  import type { QueryClient } from '@tanstack/react-query';
1562
1576
 
@@ -1588,11 +1602,11 @@ export interface CreateApiOptions extends Omit<FetcherOptions, 'baseURL'> {
1588
1602
  */
1589
1603
  export function createApi(options: CreateApiOptions) {
1590
1604
  const { queryClient, ...fetcherOptions } = options;
1591
- const fetcher = new TypedFetcher<paths>(fetcherOptions);
1605
+ const request = createTypedFetcher<paths>(fetcherOptions);
1592
1606
 
1593
- const hooks = createEndpointHooks<paths>(fetcher.request.bind(fetcher), { queryClient });
1607
+ const hooks = createEndpointHooks<paths>(request, { queryClient });
1594
1608
 
1595
- return Object.assign(fetcher.request.bind(fetcher), hooks);
1609
+ return Object.assign(request, hooks);
1596
1610
  }
1597
1611
  `;
1598
1612
  return `// Auto-generated by @geekmidas/cli - DO NOT EDIT
@@ -1670,6 +1684,108 @@ ${createApiSection}
1670
1684
  }
1671
1685
  };
1672
1686
 
1687
+ //#endregion
1688
+ //#region src/workspace/client-generator.ts
1689
+ const logger = console;
1690
+ /**
1691
+ * Normalize routes to an array of patterns.
1692
+ * Handles string, string[], and PartitionedRoutes (extracts paths).
1693
+ * @internal Exported for use in dev command
1694
+ */
1695
+ function normalizeRoutes(routes) {
1696
+ if (!routes) return [];
1697
+ if (isPartitionedRoutes(routes)) return Array.isArray(routes.paths) ? routes.paths : [routes.paths];
1698
+ return Array.isArray(routes) ? routes : [routes];
1699
+ }
1700
+ /**
1701
+ * Get frontend apps that depend on a backend app.
1702
+ */
1703
+ function getDependentFrontends(workspace, backendAppName) {
1704
+ const dependentApps = [];
1705
+ for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "frontend" && app.dependencies.includes(backendAppName)) dependentApps.push(appName);
1706
+ return dependentApps;
1707
+ }
1708
+ /**
1709
+ * Get the path to a backend's OpenAPI spec file.
1710
+ */
1711
+ function getBackendOpenApiPath(workspace, backendAppName) {
1712
+ const app = workspace.apps[backendAppName];
1713
+ if (!app || app.type !== "backend") return null;
1714
+ return join(workspace.root, app.path, ".gkm", "openapi.ts");
1715
+ }
1716
+ /**
1717
+ * Count endpoints in an OpenAPI spec content.
1718
+ */
1719
+ function countEndpoints(content) {
1720
+ const endpointMatches = content.match(/'(GET|POST|PUT|PATCH|DELETE)\s+\/[^']+'/g);
1721
+ return endpointMatches?.length ?? 0;
1722
+ }
1723
+ /**
1724
+ * Copy the OpenAPI client from a backend to all dependent frontend apps.
1725
+ * Called when the backend's .gkm/openapi.ts file changes.
1726
+ */
1727
+ async function copyClientToFrontends(workspace, backendAppName, options = {}) {
1728
+ const log = options.silent ? () => {} : logger.log.bind(logger);
1729
+ const results = [];
1730
+ const backendApp = workspace.apps[backendAppName];
1731
+ if (!backendApp || backendApp.type !== "backend") return results;
1732
+ const openApiPath = join(workspace.root, backendApp.path, ".gkm", "openapi.ts");
1733
+ if (!existsSync(openApiPath)) return results;
1734
+ const content = await readFile(openApiPath, "utf-8");
1735
+ const endpointCount = countEndpoints(content);
1736
+ const dependentFrontends = getDependentFrontends(workspace, backendAppName);
1737
+ for (const frontendAppName of dependentFrontends) {
1738
+ const frontendApp = workspace.apps[frontendAppName];
1739
+ if (!frontendApp || frontendApp.type !== "frontend") continue;
1740
+ const clientOutput = frontendApp.client?.output;
1741
+ if (!clientOutput) continue;
1742
+ const result = {
1743
+ frontendApp: frontendAppName,
1744
+ backendApp: backendAppName,
1745
+ outputPath: "",
1746
+ endpointCount,
1747
+ success: false
1748
+ };
1749
+ try {
1750
+ const frontendPath = join(workspace.root, frontendApp.path);
1751
+ const outputDir = join(frontendPath, clientOutput);
1752
+ await mkdir(outputDir, { recursive: true });
1753
+ const fileName = `${backendAppName}.ts`;
1754
+ const outputPath = join(outputDir, fileName);
1755
+ const backendRelPath = relative(dirname(outputPath), join(workspace.root, backendApp.path));
1756
+ const clientContent = `/**
1757
+ * Auto-generated API client for ${backendAppName}
1758
+ * Generated from: ${backendRelPath}
1759
+ *
1760
+ * DO NOT EDIT - This file is automatically regenerated when backend schemas change.
1761
+ */
1762
+
1763
+ ${content}
1764
+ `;
1765
+ await writeFile(outputPath, clientContent);
1766
+ result.outputPath = outputPath;
1767
+ result.success = true;
1768
+ log(`📦 Copied client to ${frontendAppName} from ${backendAppName} (${endpointCount} endpoints)`);
1769
+ } catch (error) {
1770
+ result.error = error.message;
1771
+ }
1772
+ results.push(result);
1773
+ }
1774
+ return results;
1775
+ }
1776
+ /**
1777
+ * Copy clients from all backends to their dependent frontends.
1778
+ * Useful for initial setup or force refresh.
1779
+ */
1780
+ async function copyAllClients(workspace, options = {}) {
1781
+ const allResults = [];
1782
+ for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "backend" && app.routes) {
1783
+ const results = await copyClientToFrontends(workspace, appName, options);
1784
+ allResults.push(...results);
1785
+ }
1786
+ return allResults;
1787
+ }
1788
+
1673
1789
  //#endregion
1674
1790
  //#region src/openapi.ts
1675
1791
  /**
@@ -1699,13 +1815,13 @@ function resolveOpenApiConfig(config) {
1699
1815
  * @returns Object with output path and endpoint count, or null if disabled
1700
1816
  */
1701
1817
  async function generateOpenApi(config, options = {}) {
1702
- const logger = options.silent ? { log: () => {} } : console;
1818
+ const logger$1 = options.silent ? { log: () => {} } : console;
1703
1819
  const openApiConfig = resolveOpenApiConfig(config);
1704
1820
  if (!openApiConfig.enabled) return null;
1705
1821
  const endpointGenerator = new EndpointGenerator();
1706
1822
  const loadedEndpoints = await endpointGenerator.load(config.routes, void 0, options.bustCache);
1707
1823
  if (loadedEndpoints.length === 0) {
1708
- logger.log("No valid endpoints found for OpenAPI generation");
1824
+ logger$1.log("No valid endpoints found for OpenAPI generation");
1709
1825
  return null;
1710
1826
  }
1711
1827
  const endpoints = loadedEndpoints.map(({ construct }) => construct);
@@ -1718,34 +1834,34 @@ async function generateOpenApi(config, options = {}) {
1718
1834
  description: openApiConfig.description
1719
1835
  });
1720
1836
  await writeFile(outputPath, tsContent);
1721
- logger.log(`📄 OpenAPI client generated: ${OPENAPI_OUTPUT_PATH}`);
1837
+ logger$1.log(`📄 OpenAPI client generated: ${OPENAPI_OUTPUT_PATH}`);
1722
1838
  return {
1723
1839
  outputPath,
1724
1840
  endpointCount: loadedEndpoints.length
1725
1841
  };
1726
1842
  }
1727
1843
  async function openapiCommand(options = {}) {
1728
- const logger = console;
1844
+ const logger$1 = console;
1729
1845
  try {
1730
1846
  const loadedConfig = await loadWorkspaceConfig(options.cwd);
1731
1847
  if (loadedConfig.type === "single") {
1732
1848
  const config = loadedConfig.raw;
1733
1849
  if (!config.openapi) config.openapi = { enabled: true };
1734
1850
  const result = await generateOpenApi(config);
1735
- if (result) logger.log(`Found ${result.endpointCount} endpoints`);
1851
+ if (result) logger$1.log(`Found ${result.endpointCount} endpoints`);
1736
1852
  } else {
1737
1853
  const { workspace } = loadedConfig;
1738
1854
  const workspaceRoot = options.cwd || process.cwd();
1739
1855
  const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend" && (app.openapi === true || typeof app.openapi === "object" && app.openapi.enabled !== false));
1740
1856
  if (backendApps.length === 0) {
1741
- logger.log("No backend apps with OpenAPI enabled found");
1857
+ logger$1.log("No backend apps with OpenAPI enabled found");
1742
1858
  return;
1743
1859
  }
1744
1860
  const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "frontend" && app.client?.output);
1745
1861
  for (const [appName, app] of backendApps) {
1746
1862
  if (app.type !== "backend" || !app.routes) continue;
1747
1863
  const appPath = join(workspaceRoot, app.path);
1748
- const routes = Array.isArray(app.routes) ? app.routes : [app.routes];
1864
+ const routes = normalizeRoutes(app.routes);
1749
1865
  const routesGlob = routes.map((r) => join(appPath, r));
1750
1866
  const gkmConfig = {
1751
1867
  routes: routesGlob,
@@ -1758,7 +1874,7 @@ async function openapiCommand(options = {}) {
1758
1874
  const result = await generateOpenApi(gkmConfig, { silent: true });
1759
1875
  process.chdir(originalCwd);
1760
1876
  if (result) {
1761
- logger.log(`📄 [${appName}] Generated OpenAPI (${result.endpointCount} endpoints)`);
1877
+ logger$1.log(`📄 [${appName}] Generated OpenAPI (${result.endpointCount} endpoints)`);
1762
1878
  for (const [frontendName, frontendApp] of frontendApps) {
1763
1879
  if (frontendApp.type !== "frontend") continue;
1764
1880
  const dependsOnBackend = !frontendApp.dependencies || frontendApp.dependencies.includes(appName);
@@ -1769,7 +1885,7 @@ async function openapiCommand(options = {}) {
1769
1885
  const { readFile: readFile$1 } = await import("node:fs/promises");
1770
1886
  const content = await readFile$1(result.outputPath, "utf-8");
1771
1887
  await writeFile(clientOutputPath, content);
1772
- logger.log(` → [${frontendName}] ${frontendApp.client.output}/openapi.ts`);
1888
+ logger$1.log(` → [${frontendName}] ${frontendApp.client.output}/openapi.ts`);
1773
1889
  }
1774
1890
  }
1775
1891
  }
@@ -1781,5 +1897,5 @@ async function openapiCommand(options = {}) {
1781
1897
  }
1782
1898
 
1783
1899
  //#endregion
1784
- export { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig };
1785
- //# sourceMappingURL=openapi-BYlyAbH3.mjs.map
1900
+ export { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, copyAllClients, copyClientToFrontends, generateOpenApi, getBackendOpenApiPath, isPartitionedRoutes, normalizeRoutes, openapiCommand, resolveOpenApiConfig };
1901
+ //# sourceMappingURL=openapi-DenF-okj.mjs.map