@geekmidas/cli 10.0.0-alpha.0 → 10.0.0-alpha.10

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 (76) hide show
  1. package/README.md +12 -15
  2. package/bin/adjacent-tsconfig.mjs +59 -0
  3. package/bin/gkm.mjs +7 -1
  4. package/dist/{config-DbPE1KXF.cjs → config-BZ1F0WuA.cjs} +7 -80
  5. package/dist/config-BZ1F0WuA.cjs.map +1 -0
  6. package/dist/{config-D8o6I_7Z.mjs → config-DVVph9yz.mjs} +9 -76
  7. package/dist/config-DVVph9yz.mjs.map +1 -0
  8. package/dist/config.cjs +2 -2
  9. package/dist/config.d.cts +2 -2
  10. package/dist/config.d.mts +2 -2
  11. package/dist/config.mjs +2 -2
  12. package/dist/{credentials-K961ThG3.cjs → credentials-Xamqe_NJ.cjs} +20 -19
  13. package/dist/credentials-Xamqe_NJ.cjs.map +1 -0
  14. package/dist/{credentials-Dh0Q6HzY.mjs → credentials-dP8vRVRv.mjs} +20 -19
  15. package/dist/credentials-dP8vRVRv.mjs.map +1 -0
  16. package/dist/discover-CAlFgYec.mjs.map +1 -1
  17. package/dist/discover-DTmFeloL.cjs.map +1 -1
  18. package/dist/{index-BThUzEIp.d.mts → index-C6e-ULLx.d.mts} +2 -2
  19. package/dist/index-C6e-ULLx.d.mts.map +1 -0
  20. package/dist/{index-CHB4tSzX.d.cts → index-CR5urhWX.d.cts} +2 -2
  21. package/dist/index-CR5urhWX.d.cts.map +1 -0
  22. package/dist/index.cjs +2154 -859
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.mjs +2156 -861
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/{openapi-CljONd_M.cjs → openapi-SjsgARie.cjs} +127 -50
  27. package/dist/openapi-SjsgARie.cjs.map +1 -0
  28. package/dist/{openapi-CgpMWMmM.mjs → openapi-Xx0A7Wg0.mjs} +109 -50
  29. package/dist/openapi-Xx0A7Wg0.mjs.map +1 -0
  30. package/dist/openapi.cjs +5 -3
  31. package/dist/openapi.d.cts +46 -10
  32. package/dist/openapi.d.cts.map +1 -1
  33. package/dist/openapi.d.mts +46 -10
  34. package/dist/openapi.d.mts.map +1 -1
  35. package/dist/openapi.mjs +4 -4
  36. package/dist/reconcile/public.d.cts +1 -1
  37. package/dist/reconcile/public.d.mts +1 -1
  38. package/dist/{setup-DSutDAlt.mjs → setup-BA8ciTPU.mjs} +4 -4
  39. package/dist/setup-BsZvZP6f.cjs +11 -0
  40. package/dist/{setup-DOsYueeQ.mjs → setup-C8KOx8_8.mjs} +5 -5
  41. package/dist/setup-C8KOx8_8.mjs.map +1 -0
  42. package/dist/{setup-CJiQM2pX.cjs → setup-hC6hI86f.cjs} +5 -5
  43. package/dist/setup-hC6hI86f.cjs.map +1 -0
  44. package/dist/{types-C-arnQ1r.d.mts → types-6pkp2RRj.d.mts} +46 -10
  45. package/dist/types-6pkp2RRj.d.mts.map +1 -0
  46. package/dist/{types-abJQuk7m.d.cts → types-Y3fKQSAK.d.cts} +46 -10
  47. package/dist/types-Y3fKQSAK.d.cts.map +1 -0
  48. package/dist/workspace/index.cjs +1 -1
  49. package/dist/workspace/index.d.cts +2 -2
  50. package/dist/workspace/index.d.mts +2 -2
  51. package/dist/workspace/index.mjs +1 -1
  52. package/dist/workspace-BzrG_2SI.mjs +8 -0
  53. package/dist/workspace-C6VJQVtG.cjs +6845 -0
  54. package/dist/workspace-C6VJQVtG.cjs.map +1 -0
  55. package/dist/{workspace-8acupeaI.cjs → workspace-XXtNhqJl.cjs} +3 -3
  56. package/dist/workspace-zoqRbihZ.mjs +6678 -0
  57. package/dist/workspace-zoqRbihZ.mjs.map +1 -0
  58. package/package.json +33 -32
  59. package/dist/config-D8o6I_7Z.mjs.map +0 -1
  60. package/dist/config-DbPE1KXF.cjs.map +0 -1
  61. package/dist/credentials-Dh0Q6HzY.mjs.map +0 -1
  62. package/dist/credentials-K961ThG3.cjs.map +0 -1
  63. package/dist/index-BThUzEIp.d.mts.map +0 -1
  64. package/dist/index-CHB4tSzX.d.cts.map +0 -1
  65. package/dist/openapi-CgpMWMmM.mjs.map +0 -1
  66. package/dist/openapi-CljONd_M.cjs.map +0 -1
  67. package/dist/setup-CJiQM2pX.cjs.map +0 -1
  68. package/dist/setup-DMtU3p41.cjs +0 -11
  69. package/dist/setup-DOsYueeQ.mjs.map +0 -1
  70. package/dist/types-C-arnQ1r.d.mts.map +0 -1
  71. package/dist/types-abJQuk7m.d.cts.map +0 -1
  72. package/dist/workspace-7FqprxuJ.mjs +0 -4323
  73. package/dist/workspace-7FqprxuJ.mjs.map +0 -1
  74. package/dist/workspace-XvqO9jIv.cjs +0 -4472
  75. package/dist/workspace-XvqO9jIv.cjs.map +0 -1
  76. package/dist/workspace-yP5AHnrn.mjs +0 -8
@@ -1,6 +1,7 @@
1
1
  import { ConstructGenerator, isPartitionedRoutes } from "./discover-CAlFgYec.mjs";
2
- import { loadWorkspaceConfig } from "./config-D8o6I_7Z.mjs";
3
- import { dirname, join, relative } from "node:path";
2
+ import { loadWorkspaceConfig } from "./config-DVVph9yz.mjs";
3
+ import { dirname, join, relative, resolve } from "node:path";
4
+ import { kebabCase } from "@geekmidas/manifest";
4
5
  import { mkdir, writeFile } from "node:fs/promises";
5
6
  import { spawn } from "node:child_process";
6
7
  import { Endpoint } from "@geekmidas/constructs/endpoints";
@@ -602,24 +603,21 @@ export async function setupEndpoints(
602
603
  *
603
604
  * Falls back to the configured paths for an app that declares no surface.
604
605
  */
605
- function runtimeFor(context, fromDir) {
606
- const module = context.surface?.module;
606
+ function runtimeFor(context, fromDir, owner) {
607
+ const module = owner ? context.owners?.[owner] ?? context.surface?.module : context.surface?.module;
607
608
  if (module) {
608
609
  const specifier = importSpecifier(fromDir, module.specifier);
609
610
  return {
610
611
  imports: `import { ${module.exportName} as __surface } from '${specifier}';`,
611
612
  bindings: [
612
- "// What this surface was declared with. Objects, not module paths:",
613
- "// the entry imports the surface, so there is nothing to print.",
613
+ "// What this construct was declared with. Objects, not module paths:",
614
+ "// the entry imports the owner, so there is nothing to print.",
614
615
  "const envParser = __surface.envParser;",
615
616
  "const logger = __surface.logger;"
616
617
  ].join("\n")
617
618
  };
618
619
  }
619
- return {
620
- imports: [`import ${context.envParserImportPattern} from '${importSpecifier(fromDir, context.envParserPath)}';`, `import ${context.loggerImportPattern} from '${importSpecifier(fromDir, context.loggerPath)}';`].join("\n"),
621
- bindings: ""
622
- };
620
+ throw new Error(`No owner to take a logger and an environment parser from${owner ? ` for "${owner}"` : ""}. Build it from a \`RestApi\` or a \`Worker\`.`);
623
621
  }
624
622
  /** A relative specifier ESM will accept: always prefixed, never bare. */
625
623
  function importSpecifier(fromDir, target) {
@@ -992,6 +990,7 @@ import type { Hono as HonoType } from 'hono';
992
990
  import { setupEndpoints } from './endpoints.js';
993
991
  import { setupSubscribers } from './subscribers.js';
994
992
  import { setupQueues } from './queues.js';
993
+ import { setupCrons } from './crons.js';
995
994
  ${runtime.imports}
996
995
  ${telescopeImports}
997
996
  ${studioImports}
@@ -1079,6 +1078,12 @@ ${afterSetupCall}
1079
1078
  logger.error({ error }, 'Failed to start queue workers');
1080
1079
  });
1081
1080
 
1081
+ // Schedule this app's crons. Caught like the others: a scheduler that
1082
+ // cannot start is not a reason for the HTTP server not to.
1083
+ await setupCrons(envParser, logger).catch((error) => {
1084
+ logger.error({ error }, 'Failed to schedule crons');
1085
+ });
1086
+
1082
1087
  logger.info({ port }, 'Starting server');
1083
1088
 
1084
1089
  // Start HTTP server using provided serve function
@@ -1149,8 +1154,7 @@ export const handler = ${exportName};
1149
1154
  async generateProductionAppFile(outputDir, context) {
1150
1155
  const appFileName = "app.ts";
1151
1156
  const appPath = join(outputDir, appFileName);
1152
- const relativeLoggerPath = relative(dirname(appPath), context.loggerPath);
1153
- const relativeEnvParserPath = relative(dirname(appPath), context.envParserPath);
1157
+ const runtime = runtimeFor(context, dirname(appPath));
1154
1158
  const production = context.production;
1155
1159
  const healthCheckPath = production.healthCheck;
1156
1160
  const enableGracefulShutdown = production.gracefulShutdown;
@@ -1186,9 +1190,16 @@ export const handler = ${exportName};
1186
1190
  await setupQueues(envParser, logger).catch((error) => {
1187
1191
  logger.error({ error }, 'Failed to start queue workers');
1188
1192
  });
1193
+
1194
+ // Schedule this app's crons. Caught like the others: a scheduler that
1195
+ // cannot start is not a reason for the HTTP server not to.
1196
+ await setupCrons(envParser, logger).catch((error) => {
1197
+ logger.error({ error }, 'Failed to schedule crons');
1198
+ });
1189
1199
  ` : "";
1190
1200
  const subscriberImport = includeSubscribers ? `import { setupSubscribers } from './subscribers.js';
1191
- import { setupQueues } from './queues.js';` : "";
1201
+ import { setupQueues } from './queues.js';
1202
+ import { setupCrons } from './crons.js';` : "";
1192
1203
  const gracefulShutdownCode = enableGracefulShutdown ? `
1193
1204
  // Graceful shutdown handling
1194
1205
  let isShuttingDown = false;
@@ -1218,8 +1229,8 @@ import { Hono } from 'hono';
1218
1229
  import type { Hono as HonoType } from 'hono';
1219
1230
  import { setupEndpoints } from '${endpointsImportPath}';
1220
1231
  ${subscriberImport}
1221
- import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
1222
- import ${context.loggerImportPattern} from '${relativeLoggerPath}';
1232
+ ${runtime.imports}
1233
+ ${runtime.bindings}
1223
1234
  ${hooksImports}
1224
1235
  ${cors.imports}
1225
1236
  ${context.storageDrivers?.imports ?? ""}
@@ -1505,6 +1516,11 @@ var OpenApiTsGenerator = class {
1505
1516
  }
1506
1517
  }
1507
1518
  const { $defs,...schemaWithoutDefs } = jsonSchema;
1519
+ const ref = schemaWithoutDefs.$ref;
1520
+ if (ref && Object.keys(schemaWithoutDefs).every((k) => k === "$ref" || k === "$schema")) {
1521
+ const target = ref.split("/").pop();
1522
+ return target === name ? null : `export type ${name} = ${target};`;
1523
+ }
1508
1524
  return this.jsonSchemaToInterface(schemaWithoutDefs, name);
1509
1525
  } catch {
1510
1526
  return null;
@@ -1822,6 +1838,16 @@ function normalizeRoutes(routes) {
1822
1838
  */
1823
1839
  const OPENAPI_OUTPUT_PATH = "./.gkm/openapi.ts";
1824
1840
  /**
1841
+ * Where a surface's generated client lands.
1842
+ *
1843
+ * Named from the surface, so `new RestApi('Webhooks')` writes
1844
+ * `.gkm/openapi/webhooks.ts` — the same kebab form that gives it its container
1845
+ * name and its `WEBHOOKS_URL`.
1846
+ */
1847
+ function openApiPathFor(surfaceId) {
1848
+ return `./.gkm/openapi/${kebabCase(surfaceId)}.ts`;
1849
+ }
1850
+ /**
1825
1851
  * Resolve OpenAPI config from GkmConfig
1826
1852
  */
1827
1853
  function resolveOpenApiConfig(config) {
@@ -1840,33 +1866,66 @@ function resolveOpenApiConfig(config) {
1840
1866
  };
1841
1867
  }
1842
1868
  /**
1843
- * Generate OpenAPI spec from endpoints
1844
- * @returns Object with output path and endpoint count, or null if disabled
1869
+ * Load endpoints from a glob, then generate.
1870
+ *
1871
+ * For the callers that have no endpoints in hand — `gkm openapi`, and `gkm dev`
1872
+ * regenerating on reload. The glob is the app's derived one — the conventional
1873
+ * directories under it — not a `routes` field anybody wrote.
1874
+ */
1875
+ async function generateOpenApiFrom(routes, options = {}) {
1876
+ const loaded = await new EndpointGenerator().load(routes, void 0, options.bustCache);
1877
+ return generateOpenApi(loaded.map(({ construct }) => construct), options);
1878
+ }
1879
+ /**
1880
+ * Generate an OpenAPI spec for each surface these endpoints name.
1881
+ *
1882
+ * Takes the endpoints, not a glob to find them with. The build has already
1883
+ * loaded every one of them to generate handlers, so globbing again was a second
1884
+ * discovery pass over the same files — and the reason this function needed a
1885
+ * `routes` to be told where to look, which is how a retired config field stayed
1886
+ * load-bearing.
1887
+ *
1888
+ * @returns What was written, or null if disabled or there were none
1845
1889
  */
1846
- async function generateOpenApi(config, options = {}) {
1890
+ async function generateOpenApi(endpoints, options = {}) {
1847
1891
  const logger = options.silent ? { log: () => {} } : console;
1848
- const openApiConfig = resolveOpenApiConfig(config);
1892
+ const openApiConfig = resolveOpenApiConfig({ openapi: options.openapi });
1849
1893
  if (!openApiConfig.enabled) return null;
1850
- const endpointGenerator = new EndpointGenerator();
1851
- const loadedEndpoints = await endpointGenerator.load(config.routes, void 0, options.bustCache);
1852
- if (loadedEndpoints.length === 0) {
1894
+ if (endpoints.length === 0) {
1853
1895
  logger.log("No valid endpoints found for OpenAPI generation");
1854
1896
  return null;
1855
1897
  }
1856
- const endpoints = loadedEndpoints.map(({ construct }) => construct);
1857
- const outputPath = join(process.cwd(), OPENAPI_OUTPUT_PATH);
1858
- await mkdir(dirname(outputPath), { recursive: true });
1898
+ const bySurface = /* @__PURE__ */ new Map();
1899
+ for (const endpoint of endpoints) {
1900
+ const owner = endpoint.owner ?? endpoint.surface?.id;
1901
+ if (!owner) continue;
1902
+ const existing = bySurface.get(owner);
1903
+ if (existing) existing.push(endpoint);
1904
+ else bySurface.set(owner, [endpoint]);
1905
+ }
1906
+ if (bySurface.size === 0) {
1907
+ logger.log("No endpoints named a surface, so no OpenAPI was generated");
1908
+ return null;
1909
+ }
1859
1910
  const tsGenerator = new OpenApiTsGenerator();
1860
- const tsContent = await tsGenerator.generate(endpoints, {
1861
- title: openApiConfig.title,
1862
- version: openApiConfig.version,
1863
- description: openApiConfig.description
1864
- });
1865
- await writeFile(outputPath, tsContent);
1866
- logger.log(`📄 OpenAPI client generated: ${OPENAPI_OUTPUT_PATH}`);
1911
+ const written = [];
1912
+ for (const [surfaceId, surfaceEndpoints] of bySurface) {
1913
+ const relative$1 = openApiPathFor(surfaceId);
1914
+ const outputPath = join(process.cwd(), relative$1);
1915
+ await mkdir(dirname(outputPath), { recursive: true });
1916
+ const tsContent = await tsGenerator.generate(surfaceEndpoints, {
1917
+ title: openApiConfig.title ?? surfaceId,
1918
+ version: openApiConfig.version,
1919
+ description: openApiConfig.description
1920
+ });
1921
+ await writeFile(outputPath, tsContent);
1922
+ written.push(relative$1);
1923
+ logger.log(`📄 OpenAPI client generated: ${relative$1} (${surfaceEndpoints.length} endpoints)`);
1924
+ }
1867
1925
  return {
1868
- outputPath,
1869
- endpointCount: loadedEndpoints.length
1926
+ outputPath: join(process.cwd(), written[0]),
1927
+ outputPaths: written.map((r) => join(process.cwd(), r)),
1928
+ endpointCount: endpoints.length
1870
1929
  };
1871
1930
  }
1872
1931
  async function openapiCommand(options = {}) {
@@ -1876,12 +1935,12 @@ async function openapiCommand(options = {}) {
1876
1935
  if (loadedConfig.type === "single") {
1877
1936
  const config = loadedConfig.raw;
1878
1937
  if (!config.openapi) config.openapi = { enabled: true };
1879
- const result = await generateOpenApi(config);
1938
+ const result = await generateOpenApiFrom(config.constructs, { openapi: config.openapi });
1880
1939
  if (result) logger.log(`Found ${result.endpointCount} endpoints`);
1881
1940
  } else {
1882
1941
  const { workspace } = loadedConfig;
1883
1942
  const workspaceRoot = workspace.root;
1884
- const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend" && app.routes !== void 0 && app.openapi !== false && (typeof app.openapi !== "object" || app.openapi.enabled !== false));
1943
+ const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend" && app.openapi !== false && (typeof app.openapi !== "object" || app.openapi.enabled !== false));
1885
1944
  if (backendApps.length === 0) {
1886
1945
  logger.log("No backend apps with OpenAPI enabled found");
1887
1946
  return;
@@ -1895,8 +1954,12 @@ async function openapiCommand(options = {}) {
1895
1954
  return;
1896
1955
  }
1897
1956
  for (const [appName, app] of backendApps) {
1898
- if (app.type !== "backend" || !app.routes) continue;
1899
1957
  const appPath = join(workspaceRoot, app.path);
1958
+ if (resolve(appPath) === resolve(workspaceRoot)) {
1959
+ const result = await generateOpenApiForApp(workspaceRoot, appName, app, false);
1960
+ if (result) logger.log(`Found ${result.endpointCount} endpoints`);
1961
+ continue;
1962
+ }
1900
1963
  await runOpenApiInSubprocess(appPath, appName);
1901
1964
  }
1902
1965
  }
@@ -1909,18 +1972,14 @@ async function openapiCommand(options = {}) {
1909
1972
  * Runs in-process. The caller is responsible for ensuring `process.cwd()`
1910
1973
  * is the app's directory so tsx loads the app's tsconfig path aliases.
1911
1974
  */
1912
- async function generateOpenApiForApp(workspaceRoot, _appName, app) {
1913
- if (app.type !== "backend" || !app.routes) return null;
1975
+ async function generateOpenApiForApp(workspaceRoot, _appName, app, silent = true) {
1976
+ if (app.type !== "backend") return null;
1914
1977
  const appPath = join(workspaceRoot, app.path);
1915
- const routes = normalizeRoutes(app.routes);
1916
- const routesGlob = routes.map((r) => join(appPath, r));
1917
- const gkmConfig = {
1918
- routes: routesGlob,
1919
- envParser: app.envParser || "",
1920
- logger: app.logger || "",
1921
- openapi: app.openapi
1922
- };
1923
- return generateOpenApi(gkmConfig, { silent: true });
1978
+ const globs = [join(appPath, "**/*.ts")];
1979
+ return generateOpenApiFrom(globs, {
1980
+ openapi: app.openapi ?? { enabled: true },
1981
+ silent
1982
+ });
1924
1983
  }
1925
1984
  /**
1926
1985
  * Resolve the gkm bin path. Tests can override via `GKM_BIN_PATH` env var
@@ -1958,5 +2017,5 @@ async function runOpenApiInSubprocess(appCwd, appName) {
1958
2017
  }
1959
2018
 
1960
2019
  //#endregion
1961
- export { EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, normalizeRoutes, openapiCommand, resolveOpenApiConfig };
1962
- //# sourceMappingURL=openapi-CgpMWMmM.mjs.map
2020
+ export { EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, generateOpenApiFrom, normalizeRoutes, openApiPathFor, openapiCommand, resolveOpenApiConfig, runtimeFor };
2021
+ //# sourceMappingURL=openapi-Xx0A7Wg0.mjs.map