@geekmidas/cli 0.28.0 → 0.30.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 (40) hide show
  1. package/dist/{config-BhryDQEq.cjs → config-BAE9LFC1.cjs} +2 -2
  2. package/dist/{config-BhryDQEq.cjs.map → config-BAE9LFC1.cjs.map} +1 -1
  3. package/dist/{config-C9bdq0l-.mjs → config-BC5n1a2D.mjs} +2 -2
  4. package/dist/{config-C9bdq0l-.mjs.map → config-BC5n1a2D.mjs.map} +1 -1
  5. package/dist/config.cjs +2 -2
  6. package/dist/config.d.cts +1 -1
  7. package/dist/config.d.mts +1 -1
  8. package/dist/config.mjs +2 -2
  9. package/dist/{index-CWN-bgrO.d.mts → index-C7TkoYmt.d.mts} +5 -1
  10. package/dist/index-C7TkoYmt.d.mts.map +1 -0
  11. package/dist/{index-DEWYvYvg.d.cts → index-CpchsC9w.d.cts} +5 -1
  12. package/dist/index-CpchsC9w.d.cts.map +1 -0
  13. package/dist/index.cjs +139 -46
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.mjs +139 -46
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/{openapi-BCEFhkLh.mjs → openapi-CjYeF-Tg.mjs} +2 -2
  18. package/dist/{openapi-BCEFhkLh.mjs.map → openapi-CjYeF-Tg.mjs.map} +1 -1
  19. package/dist/{openapi-D82bBqG7.cjs → openapi-a-e3Y8WA.cjs} +2 -2
  20. package/dist/{openapi-D82bBqG7.cjs.map → openapi-a-e3Y8WA.cjs.map} +1 -1
  21. package/dist/openapi.cjs +3 -3
  22. package/dist/openapi.mjs +3 -3
  23. package/dist/workspace/index.cjs +1 -1
  24. package/dist/workspace/index.d.cts +1 -1
  25. package/dist/workspace/index.d.mts +1 -1
  26. package/dist/workspace/index.mjs +1 -1
  27. package/dist/{workspace-DQjmv9lk.mjs → workspace-DFJ3sWfY.mjs} +19 -3
  28. package/dist/{workspace-DQjmv9lk.mjs.map → workspace-DFJ3sWfY.mjs.map} +1 -1
  29. package/dist/{workspace-CiZBOjf9.cjs → workspace-My0A4IRO.cjs} +19 -3
  30. package/dist/{workspace-CiZBOjf9.cjs.map → workspace-My0A4IRO.cjs.map} +1 -1
  31. package/package.json +3 -3
  32. package/src/dev/__tests__/index.spec.ts +223 -0
  33. package/src/dev/index.ts +83 -4
  34. package/src/init/__tests__/generators.spec.ts +17 -9
  35. package/src/init/generators/web.ts +86 -37
  36. package/src/workspace/__tests__/schema.spec.ts +114 -0
  37. package/src/workspace/schema.ts +23 -1
  38. package/tsconfig.tsbuildinfo +1 -1
  39. package/dist/index-CWN-bgrO.d.mts.map +0 -1
  40. package/dist/index-DEWYvYvg.d.cts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env -S npx tsx
2
- import { __require, getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, isDeployTargetSupported } from "./workspace-DQjmv9lk.mjs";
3
- import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig } from "./config-C9bdq0l-.mjs";
4
- import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, OpenApiTsGenerator, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-BCEFhkLh.mjs";
2
+ import { __require, getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, isDeployTargetSupported } from "./workspace-DFJ3sWfY.mjs";
3
+ import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig } from "./config-BC5n1a2D.mjs";
4
+ import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, OpenApiTsGenerator, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-CjYeF-Tg.mjs";
5
5
  import { getKeyPath, maskPassword, readStageSecrets, secretsExist, setCustomSecret, toEmbeddableSecrets, writeStageSecrets } from "./storage-Dhst7BhI.mjs";
6
6
  import { DokployApi } from "./dokploy-api-B9qR2Yn1.mjs";
7
7
  import { generateReactQueryCommand } from "./openapi-react-query-5rSortLH.mjs";
@@ -26,7 +26,7 @@ import prompts from "prompts";
26
26
 
27
27
  //#region package.json
28
28
  var name = "@geekmidas/cli";
29
- var version = "0.28.0";
29
+ var version = "0.30.0";
30
30
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
31
31
  var private$1 = false;
32
32
  var type = "module";
@@ -1016,10 +1016,14 @@ async function devCommand(options) {
1016
1016
  const appName = getAppNameFromCwd();
1017
1017
  let config$1;
1018
1018
  let appRoot = process.cwd();
1019
+ let secretsRoot = process.cwd();
1020
+ let workspaceAppName;
1019
1021
  if (appName) try {
1020
1022
  const appConfig = await loadAppConfig();
1021
1023
  config$1 = appConfig.gkmConfig;
1022
1024
  appRoot = appConfig.appRoot;
1025
+ secretsRoot = appConfig.workspaceRoot;
1026
+ workspaceAppName = appConfig.appName;
1023
1027
  logger$8.log(`📦 Running app: ${appConfig.appName}`);
1024
1028
  } catch {
1025
1029
  const loadedConfig = await loadWorkspaceConfig();
@@ -1072,7 +1076,16 @@ async function devCommand(options) {
1072
1076
  await buildServer(config$1, buildContext, resolved.providers[0], enableOpenApi, appRoot);
1073
1077
  if (enableOpenApi) await generateOpenApi(config$1);
1074
1078
  const runtime = config$1.runtime ?? "node";
1075
- const devServer = new DevServer(resolved.providers[0], options.port || 3e3, options.portExplicit ?? false, enableOpenApi, telescope, studio, runtime, appRoot);
1079
+ let secretsJsonPath;
1080
+ const appSecrets = await loadSecretsForApp(secretsRoot, workspaceAppName);
1081
+ if (Object.keys(appSecrets).length > 0) {
1082
+ const secretsDir = join(secretsRoot, ".gkm");
1083
+ await mkdir(secretsDir, { recursive: true });
1084
+ secretsJsonPath = join(secretsDir, "dev-secrets.json");
1085
+ await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
1086
+ logger$8.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
1087
+ }
1088
+ const devServer = new DevServer(resolved.providers[0], options.port || 3e3, options.portExplicit ?? false, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath);
1076
1089
  await devServer.start();
1077
1090
  const envParserFile = config$1.envParser.split("#")[0] ?? config$1.envParser;
1078
1091
  const loggerFile = config$1.logger.split("#")[0] ?? config$1.logger;
@@ -1244,6 +1257,31 @@ async function loadDevSecrets(workspace) {
1244
1257
  return {};
1245
1258
  }
1246
1259
  /**
1260
+ * Load secrets from a path for dev mode.
1261
+ * For single app: returns secrets as-is.
1262
+ * For workspace app: maps {APP}_DATABASE_URL → DATABASE_URL.
1263
+ * @internal Exported for testing
1264
+ */
1265
+ async function loadSecretsForApp(secretsRoot, appName) {
1266
+ const stages = ["dev", "development"];
1267
+ let secrets = {};
1268
+ for (const stage of stages) if (secretsExist(stage, secretsRoot)) {
1269
+ const stageSecrets = await readStageSecrets(stage, secretsRoot);
1270
+ if (stageSecrets) {
1271
+ logger$8.log(`🔐 Loading secrets from stage: ${stage}`);
1272
+ secrets = toEmbeddableSecrets(stageSecrets);
1273
+ break;
1274
+ }
1275
+ }
1276
+ if (Object.keys(secrets).length === 0) return {};
1277
+ if (!appName) return secrets;
1278
+ const prefix = appName.toUpperCase();
1279
+ const mapped = { ...secrets };
1280
+ const appDbUrl = secrets[`${prefix}_DATABASE_URL`];
1281
+ if (appDbUrl) mapped.DATABASE_URL = appDbUrl;
1282
+ return mapped;
1283
+ }
1284
+ /**
1247
1285
  * Start docker-compose services for the workspace.
1248
1286
  * @internal Exported for testing
1249
1287
  */
@@ -1488,7 +1526,7 @@ var DevServer = class {
1488
1526
  serverProcess = null;
1489
1527
  isRunning = false;
1490
1528
  actualPort;
1491
- constructor(provider, requestedPort, portExplicit, enableOpenApi, telescope, studio, runtime = "node", appRoot = process.cwd()) {
1529
+ constructor(provider, requestedPort, portExplicit, enableOpenApi, telescope, studio, runtime = "node", appRoot = process.cwd(), secretsJsonPath) {
1492
1530
  this.provider = provider;
1493
1531
  this.requestedPort = requestedPort;
1494
1532
  this.portExplicit = portExplicit;
@@ -1497,6 +1535,7 @@ var DevServer = class {
1497
1535
  this.studio = studio;
1498
1536
  this.runtime = runtime;
1499
1537
  this.appRoot = appRoot;
1538
+ this.secretsJsonPath = secretsJsonPath;
1500
1539
  this.actualPort = requestedPort;
1501
1540
  }
1502
1541
  async start() {
@@ -1575,10 +1614,20 @@ var DevServer = class {
1575
1614
  await this.start();
1576
1615
  }
1577
1616
  async createServerEntry() {
1578
- const { writeFile: writeFile$1 } = await import("node:fs/promises");
1617
+ const { writeFile: fsWriteFile } = await import("node:fs/promises");
1579
1618
  const { relative: relative$1, dirname: dirname$1 } = await import("node:path");
1580
1619
  const serverPath = join(this.appRoot, ".gkm", this.provider, "server.ts");
1581
1620
  const relativeAppPath = relative$1(dirname$1(serverPath), join(dirname$1(serverPath), "app.js"));
1621
+ const credentialsInjection = this.secretsJsonPath ? `import { Credentials } from '@geekmidas/envkit/credentials';
1622
+ import { existsSync, readFileSync } from 'node:fs';
1623
+
1624
+ // Inject dev secrets into Credentials (must happen before app import)
1625
+ const secretsPath = '${this.secretsJsonPath}';
1626
+ if (existsSync(secretsPath)) {
1627
+ Object.assign(Credentials, JSON.parse(readFileSync(secretsPath, 'utf-8')));
1628
+ }
1629
+
1630
+ ` : "";
1582
1631
  const serveCode = this.runtime === "bun" ? `Bun.serve({
1583
1632
  port,
1584
1633
  fetch: app.fetch,
@@ -1598,7 +1647,7 @@ var DevServer = class {
1598
1647
  * Development server entry point
1599
1648
  * This file is auto-generated by 'gkm dev'
1600
1649
  */
1601
- import { createApp } from './${relativeAppPath.startsWith(".") ? relativeAppPath : `./${relativeAppPath}`}';
1650
+ ${credentialsInjection}import { createApp } from './${relativeAppPath.startsWith(".") ? relativeAppPath : `./${relativeAppPath}`}';
1602
1651
 
1603
1652
  const port = process.argv.includes('--port')
1604
1653
  ? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
@@ -1618,7 +1667,7 @@ start({
1618
1667
  process.exit(1);
1619
1668
  });
1620
1669
  `;
1621
- await writeFile$1(serverPath, content);
1670
+ await fsWriteFile(serverPath, content);
1622
1671
  }
1623
1672
  };
1624
1673
 
@@ -6298,6 +6347,8 @@ function generateWebAppFiles(options) {
6298
6347
  },
6299
6348
  dependencies: {
6300
6349
  [modelsPackage]: "workspace:*",
6350
+ "@geekmidas/client": GEEKMIDAS_VERSIONS["@geekmidas/client"],
6351
+ "@tanstack/react-query": "~5.80.0",
6301
6352
  next: "~16.1.0",
6302
6353
  react: "~19.2.0",
6303
6354
  "react-dom": "~19.2.0"
@@ -6354,7 +6405,68 @@ export default nextConfig;
6354
6405
  ],
6355
6406
  exclude: ["node_modules"]
6356
6407
  };
6408
+ const providersTsx = `'use client';
6409
+
6410
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
6411
+ import { useState } from 'react';
6412
+
6413
+ export function Providers({ children }: { children: React.ReactNode }) {
6414
+ const [queryClient] = useState(
6415
+ () =>
6416
+ new QueryClient({
6417
+ defaultOptions: {
6418
+ queries: {
6419
+ staleTime: 60 * 1000,
6420
+ },
6421
+ },
6422
+ }),
6423
+ );
6424
+
6425
+ return (
6426
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
6427
+ );
6428
+ }
6429
+ `;
6430
+ const apiIndexTs = `import { TypedFetcher } from '@geekmidas/client/fetcher';
6431
+ import { createEndpointHooks } from '@geekmidas/client/endpoint-hooks';
6432
+
6433
+ // TODO: Run 'gkm openapi' to generate typed paths from your API
6434
+ // This is a placeholder that will be replaced by the generated openapi.ts
6435
+ interface paths {
6436
+ '/health': {
6437
+ get: {
6438
+ responses: {
6439
+ 200: {
6440
+ content: {
6441
+ 'application/json': { status: string; timestamp: string };
6442
+ };
6443
+ };
6444
+ };
6445
+ };
6446
+ };
6447
+ '/users': {
6448
+ get: {
6449
+ responses: {
6450
+ 200: {
6451
+ content: {
6452
+ 'application/json': { users: Array<{ id: string; name: string }> };
6453
+ };
6454
+ };
6455
+ };
6456
+ };
6457
+ };
6458
+ }
6459
+
6460
+ const baseURL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
6461
+
6462
+ const fetcher = new TypedFetcher<paths>({ baseURL });
6463
+
6464
+ const hooks = createEndpointHooks<paths>(fetcher.request.bind(fetcher));
6465
+
6466
+ export const api = Object.assign(fetcher.request.bind(fetcher), hooks);
6467
+ `;
6357
6468
  const layoutTsx = `import type { Metadata } from 'next';
6469
+ import { Providers } from './providers';
6358
6470
 
6359
6471
  export const metadata: Metadata = {
6360
6472
  title: '${options.name}',
@@ -6368,35 +6480,18 @@ export default function RootLayout({
6368
6480
  }) {
6369
6481
  return (
6370
6482
  <html lang="en">
6371
- <body>{children}</body>
6483
+ <body>
6484
+ <Providers>{children}</Providers>
6485
+ </body>
6372
6486
  </html>
6373
6487
  );
6374
6488
  }
6375
6489
  `;
6376
- const pageTsx = `import type { User } from '${modelsPackage}';
6490
+ const pageTsx = `import { api } from '@/api';
6377
6491
 
6378
6492
  export default async function Home() {
6379
- // Example: Fetch from API
6380
- const apiUrl = process.env.API_URL || 'http://localhost:3000';
6381
- let health = null;
6382
-
6383
- try {
6384
- const response = await fetch(\`\${apiUrl}/health\`, {
6385
- cache: 'no-store',
6386
- });
6387
- health = await response.json();
6388
- } catch (error) {
6389
- console.error('Failed to fetch health:', error);
6390
- }
6391
-
6392
- // Example: Type-safe model usage
6393
- const exampleUser: User = {
6394
- id: '123e4567-e89b-12d3-a456-426614174000',
6395
- email: 'user@example.com',
6396
- name: 'Example User',
6397
- createdAt: new Date(),
6398
- updatedAt: new Date(),
6399
- };
6493
+ // Type-safe API call using the generated client
6494
+ const health = await api('GET /health').catch(() => null);
6400
6495
 
6401
6496
  return (
6402
6497
  <main style={{ padding: '2rem', fontFamily: 'system-ui' }}>
@@ -6409,21 +6504,14 @@ export default async function Home() {
6409
6504
  {JSON.stringify(health, null, 2)}
6410
6505
  </pre>
6411
6506
  ) : (
6412
- <p>Unable to connect to API at {apiUrl}</p>
6507
+ <p>Unable to connect to API</p>
6413
6508
  )}
6414
6509
  </section>
6415
6510
 
6416
- <section style={{ marginTop: '2rem' }}>
6417
- <h2>Shared Models</h2>
6418
- <p>This user object is typed from @${options.name}/models:</p>
6419
- <pre style={{ background: '#f0f0f0', padding: '1rem', borderRadius: '8px' }}>
6420
- {JSON.stringify(exampleUser, null, 2)}
6421
- </pre>
6422
- </section>
6423
-
6424
6511
  <section style={{ marginTop: '2rem' }}>
6425
6512
  <h2>Next Steps</h2>
6426
6513
  <ul>
6514
+ <li>Run <code>gkm openapi</code> to generate typed API client</li>
6427
6515
  <li>Edit <code>apps/web/src/app/page.tsx</code> to customize this page</li>
6428
6516
  <li>Add API routes in <code>apps/api/src/endpoints/</code></li>
6429
6517
  <li>Define shared schemas in <code>packages/models/src/</code></li>
@@ -6433,11 +6521,8 @@ export default async function Home() {
6433
6521
  );
6434
6522
  }
6435
6523
  `;
6436
- const envLocal = `# API URL (injected automatically in workspace mode)
6437
- API_URL=http://localhost:3000
6438
-
6439
- # Other environment variables
6440
- # NEXT_PUBLIC_API_URL=http://localhost:3000
6524
+ const envLocal = `# API URL for client-side requests
6525
+ NEXT_PUBLIC_API_URL=http://localhost:3000
6441
6526
  `;
6442
6527
  const gitignore = `.next/
6443
6528
  node_modules/
@@ -6461,10 +6546,18 @@ node_modules/
6461
6546
  path: "apps/web/src/app/layout.tsx",
6462
6547
  content: layoutTsx
6463
6548
  },
6549
+ {
6550
+ path: "apps/web/src/app/providers.tsx",
6551
+ content: providersTsx
6552
+ },
6464
6553
  {
6465
6554
  path: "apps/web/src/app/page.tsx",
6466
6555
  content: pageTsx
6467
6556
  },
6557
+ {
6558
+ path: "apps/web/src/api/index.ts",
6559
+ content: apiIndexTs
6560
+ },
6468
6561
  {
6469
6562
  path: "apps/web/.env.local",
6470
6563
  content: envLocal