@geekmidas/cli 1.2.1 → 1.2.3

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.
package/src/dev/index.ts CHANGED
@@ -46,10 +46,9 @@ import type {
46
46
  TelescopeConfig,
47
47
  } from '../types';
48
48
  import {
49
- generateAllClients,
50
- generateClientForFrontend,
51
- getDependentFrontends,
52
- normalizeRoutes,
49
+ copyAllClients,
50
+ copyClientToFrontends,
51
+ getBackendOpenApiPath,
53
52
  } from '../workspace/client-generator.js';
54
53
  import {
55
54
  getAppBuildOrder,
@@ -964,12 +963,12 @@ async function workspaceDevCommand(
964
963
  logger.log('✅ Frontend apps validated');
965
964
  }
966
965
 
967
- // Generate initial clients for frontends with backend dependencies
968
- if (frontendApps.length > 0) {
969
- const clientResults = await generateAllClients(workspace, { force: true });
970
- const generatedCount = clientResults.filter((r) => r.generated).length;
971
- if (generatedCount > 0) {
972
- logger.log(`\n📦 Generated ${generatedCount} API client(s)`);
966
+ // Copy initial clients from backends to frontends
967
+ if (frontendApps.length > 0 && backendApps.length > 0) {
968
+ const clientResults = await copyAllClients(workspace);
969
+ const copiedCount = clientResults.filter((r) => r.success).length;
970
+ if (copiedCount > 0) {
971
+ logger.log(`\n📦 Copied ${copiedCount} API client(s)`);
973
972
  }
974
973
  }
975
974
 
@@ -1058,117 +1057,81 @@ async function workspaceDevCommand(
1058
1057
  env: turboEnv,
1059
1058
  });
1060
1059
 
1061
- // Set up file watcher for backend endpoint changes (smart client regeneration)
1062
- let endpointWatcher: ReturnType<typeof chokidar.watch> | null = null;
1060
+ // Set up file watcher for backend .gkm/openapi.ts changes (auto-copy to frontends)
1061
+ let openApiWatcher: ReturnType<typeof chokidar.watch> | null = null;
1063
1062
 
1064
1063
  if (frontendApps.length > 0 && backendApps.length > 0) {
1065
- // Collect all backend route patterns to watch
1066
- const watchPatterns: string[] = [];
1067
- const backendRouteMap = new Map<string, string[]>(); // routes pattern -> backend app names
1068
-
1069
- for (const [appName, app] of backendApps) {
1070
- const routePatterns = normalizeRoutes(app.routes);
1071
- for (const routePattern of routePatterns) {
1072
- const fullPattern = join(workspace.root, app.path, routePattern);
1073
- watchPatterns.push(fullPattern);
1074
-
1075
- // Map pattern to app name for change detection
1076
- const patternKey = join(app.path, routePattern);
1077
- const existing = backendRouteMap.get(patternKey) || [];
1078
- backendRouteMap.set(patternKey, [...existing, appName]);
1064
+ // Collect all backend openapi.ts file paths to watch
1065
+ const openApiPaths: { path: string; appName: string }[] = [];
1066
+
1067
+ for (const [appName] of backendApps) {
1068
+ const openApiPath = getBackendOpenApiPath(workspace, appName);
1069
+ if (openApiPath) {
1070
+ openApiPaths.push({ path: openApiPath, appName });
1079
1071
  }
1080
1072
  }
1081
1073
 
1082
- if (watchPatterns.length > 0) {
1083
- // Resolve glob patterns to files
1084
- const resolvedFiles = await fg(watchPatterns, {
1085
- cwd: workspace.root,
1086
- absolute: true,
1087
- onlyFiles: true,
1088
- });
1074
+ if (openApiPaths.length > 0) {
1075
+ logger.log(
1076
+ `\n👀 Watching ${openApiPaths.length} backend OpenAPI spec(s) for changes`,
1077
+ );
1089
1078
 
1090
- if (resolvedFiles.length > 0) {
1091
- logger.log(
1092
- `\n👀 Watching ${resolvedFiles.length} endpoint file(s) for schema changes`,
1093
- );
1079
+ // Create a map for quick lookup of app name from path
1080
+ const pathToApp = new Map(openApiPaths.map((p) => [p.path, p.appName]));
1094
1081
 
1095
- endpointWatcher = chokidar.watch(resolvedFiles, {
1096
- ignored: /(^|[/\\])\../,
1082
+ openApiWatcher = chokidar.watch(
1083
+ openApiPaths.map((p) => p.path),
1084
+ {
1097
1085
  persistent: true,
1098
1086
  ignoreInitial: true,
1099
- });
1100
-
1101
- let regenerateTimeout: NodeJS.Timeout | null = null;
1087
+ // Watch parent directory too since file may not exist yet
1088
+ depth: 0,
1089
+ },
1090
+ );
1102
1091
 
1103
- endpointWatcher.on('change', async (changedPath) => {
1104
- // Debounce regeneration
1105
- if (regenerateTimeout) {
1106
- clearTimeout(regenerateTimeout);
1107
- }
1092
+ let copyTimeout: NodeJS.Timeout | null = null;
1108
1093
 
1109
- regenerateTimeout = setTimeout(async () => {
1110
- // Find which backend app this file belongs to
1111
- const changedBackends: string[] = [];
1112
-
1113
- for (const [appName, app] of backendApps) {
1114
- const routePatterns = normalizeRoutes(app.routes);
1115
- for (const routePattern of routePatterns) {
1116
- const routesDir = join(
1117
- workspace.root,
1118
- app.path,
1119
- routePattern.split('*')[0] || '',
1120
- );
1121
- if (changedPath.startsWith(routesDir.replace(/\/$/, ''))) {
1122
- changedBackends.push(appName);
1123
- break; // Found a match, no need to check other patterns
1124
- }
1125
- }
1126
- }
1127
-
1128
- if (changedBackends.length === 0) {
1129
- return;
1130
- }
1094
+ const handleChange = async (changedPath: string) => {
1095
+ // Debounce to handle rapid changes
1096
+ if (copyTimeout) {
1097
+ clearTimeout(copyTimeout);
1098
+ }
1131
1099
 
1132
- // Find frontends that depend on changed backends
1133
- const affectedFrontends = new Set<string>();
1134
- for (const backend of changedBackends) {
1135
- const dependents = getDependentFrontends(workspace, backend);
1136
- for (const frontend of dependents) {
1137
- affectedFrontends.add(frontend);
1138
- }
1139
- }
1100
+ copyTimeout = setTimeout(async () => {
1101
+ const backendAppName = pathToApp.get(changedPath);
1102
+ if (!backendAppName) {
1103
+ return;
1104
+ }
1140
1105
 
1141
- if (affectedFrontends.size === 0) {
1142
- return;
1143
- }
1106
+ logger.log(`\n🔄 OpenAPI spec changed for ${backendAppName}`);
1144
1107
 
1145
- // Regenerate clients for affected frontends
1146
- logger.log(
1147
- `\n🔄 Detected schema change in ${changedBackends.join(', ')}`,
1108
+ try {
1109
+ const results = await copyClientToFrontends(
1110
+ workspace,
1111
+ backendAppName,
1112
+ { silent: true },
1148
1113
  );
1149
-
1150
- for (const frontend of affectedFrontends) {
1151
- try {
1152
- const results = await generateClientForFrontend(
1153
- workspace,
1154
- frontend,
1114
+ for (const result of results) {
1115
+ if (result.success) {
1116
+ logger.log(
1117
+ ` 📦 Copied client to ${result.frontendApp} (${result.endpointCount} endpoints)`,
1155
1118
  );
1156
- for (const result of results) {
1157
- if (result.generated) {
1158
- logger.log(
1159
- ` 📦 Regenerated client for ${result.frontendApp} (${result.endpointCount} endpoints)`,
1160
- );
1161
- }
1162
- }
1163
- } catch (error) {
1119
+ } else if (result.error) {
1164
1120
  logger.error(
1165
- ` ❌ Failed to regenerate client for ${frontend}: ${(error as Error).message}`,
1121
+ ` ❌ Failed to copy client to ${result.frontendApp}: ${result.error}`,
1166
1122
  );
1167
1123
  }
1168
1124
  }
1169
- }, 500); // 500ms debounce
1170
- });
1171
- }
1125
+ } catch (error) {
1126
+ logger.error(
1127
+ ` ❌ Failed to copy clients: ${(error as Error).message}`,
1128
+ );
1129
+ }
1130
+ }, 200); // 200ms debounce
1131
+ };
1132
+
1133
+ openApiWatcher.on('change', handleChange);
1134
+ openApiWatcher.on('add', handleChange);
1172
1135
  }
1173
1136
  }
1174
1137
 
@@ -1180,9 +1143,9 @@ async function workspaceDevCommand(
1180
1143
 
1181
1144
  logger.log('\n🛑 Shutting down workspace...');
1182
1145
 
1183
- // Close endpoint watcher
1184
- if (endpointWatcher) {
1185
- endpointWatcher.close().catch(() => {});
1146
+ // Close OpenAPI watcher
1147
+ if (openApiWatcher) {
1148
+ openApiWatcher.close().catch(() => {});
1186
1149
  }
1187
1150
 
1188
1151
  // Kill turbo process
@@ -1214,8 +1177,8 @@ async function workspaceDevCommand(
1214
1177
 
1215
1178
  turboProcess.on('exit', (code) => {
1216
1179
  // Close watcher on exit
1217
- if (endpointWatcher) {
1218
- endpointWatcher.close().catch(() => {});
1180
+ if (openApiWatcher) {
1181
+ openApiWatcher.close().catch(() => {});
1219
1182
  }
1220
1183
 
1221
1184
  if (code !== null && code !== 0) {
@@ -1294,11 +1257,12 @@ function generateCredentialsInjection(secretsJsonPath: string): string {
1294
1257
  return `import { Credentials } from '@geekmidas/envkit/credentials';
1295
1258
  import { existsSync, readFileSync } from 'node:fs';
1296
1259
 
1297
- // Inject dev secrets into Credentials
1260
+ // Inject dev secrets into Credentials and process.env
1298
1261
  const secretsPath = '${secretsJsonPath}';
1299
1262
  if (existsSync(secretsPath)) {
1300
1263
  const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
1301
1264
  Object.assign(Credentials, secrets);
1265
+ Object.assign(process.env, secrets);
1302
1266
  // Debug: uncomment to verify preload is running
1303
1267
  // console.log('[gkm preload] Injected', Object.keys(secrets).length, 'credentials');
1304
1268
  }
@@ -722,7 +722,6 @@ export function createApi(options: CreateApiOptions) {
722
722
  `;
723
723
 
724
724
  return `// Auto-generated by @geekmidas/cli - DO NOT EDIT
725
- // Generated: ${new Date().toISOString()}
726
725
 
727
726
  // ============================================================
728
727
  // Security Scheme Type
@@ -317,7 +317,7 @@ export { Button, buttonVariants };
317
317
 
318
318
  // src/components/ui/button/button.stories.tsx
319
319
  const buttonStories = `import type { Meta, StoryObj } from '@storybook/react';
320
- import { Button } from '.';
320
+ import { Button } from '~/components/ui/button';
321
321
 
322
322
  const meta: Meta<typeof Button> = {
323
323
  title: 'Components/Button',
@@ -488,7 +488,7 @@ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
488
488
 
489
489
  // src/components/ui/input/input.stories.tsx
490
490
  const inputStories = `import type { Meta, StoryObj } from '@storybook/react';
491
- import { Input } from '.';
491
+ import { Input } from '~/components/ui/input';
492
492
 
493
493
  const meta: Meta<typeof Input> = {
494
494
  title: 'Components/Input',
@@ -544,7 +544,7 @@ export const WithValue: Story = {
544
544
 
545
545
  // src/components/ui/card/card.stories.tsx
546
546
  const cardStories = `import type { Meta, StoryObj } from '@storybook/react';
547
- import { Button } from '../button';
547
+ import { Button } from '~/components/ui/button';
548
548
  import {
549
549
  Card,
550
550
  CardContent,
@@ -552,8 +552,8 @@ import {
552
552
  CardFooter,
553
553
  CardHeader,
554
554
  CardTitle,
555
- } from '.';
556
- import { Input } from '../input';
555
+ } from '~/components/ui/card';
556
+ import { Input } from '~/components/ui/input';
557
557
 
558
558
  const meta: Meta<typeof Card> = {
559
559
  title: 'Components/Card',
@@ -635,8 +635,8 @@ export { Label };
635
635
 
636
636
  // src/components/ui/label/label.stories.tsx
637
637
  const labelStories = `import type { Meta, StoryObj } from '@storybook/react';
638
- import { Input } from '../input';
639
- import { Label } from '.';
638
+ import { Input } from '~/components/ui/input';
639
+ import { Label } from '~/components/ui/label';
640
640
 
641
641
  const meta: Meta<typeof Label> = {
642
642
  title: 'Components/Label',
@@ -715,7 +715,7 @@ export { Badge, badgeVariants };
715
715
 
716
716
  // src/components/ui/badge/badge.stories.tsx
717
717
  const badgeStories = `import type { Meta, StoryObj } from '@storybook/react';
718
- import { Badge } from '.';
718
+ import { Badge } from '~/components/ui/badge';
719
719
 
720
720
  const meta: Meta<typeof Badge> = {
721
721
  title: 'Components/Badge',
@@ -795,7 +795,7 @@ export { Separator };
795
795
 
796
796
  // src/components/ui/separator/separator.stories.tsx
797
797
  const separatorStories = `import type { Meta, StoryObj } from '@storybook/react';
798
- import { Separator } from '.';
798
+ import { Separator } from '~/components/ui/separator';
799
799
 
800
800
  const meta: Meta<typeof Separator> = {
801
801
  title: 'Components/Separator',
@@ -904,11 +904,11 @@ export { Tabs, TabsList, TabsTrigger, TabsContent };
904
904
 
905
905
  // src/components/ui/tabs/tabs.stories.tsx
906
906
  const tabsStories = `import type { Meta, StoryObj } from '@storybook/react';
907
- import { Tabs, TabsContent, TabsList, TabsTrigger } from '.';
908
- import { Button } from '../button';
909
- import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../card';
910
- import { Input } from '../input';
911
- import { Label } from '../label';
907
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '~/components/ui/tabs';
908
+ import { Button } from '~/components/ui/button';
909
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '~/components/ui/card';
910
+ import { Input } from '~/components/ui/input';
911
+ import { Label } from '~/components/ui/label';
912
912
 
913
913
  const meta: Meta<typeof Tabs> = {
914
914
  title: 'Components/Tabs',
@@ -1012,8 +1012,8 @@ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
1012
1012
 
1013
1013
  // src/components/ui/tooltip/tooltip.stories.tsx
1014
1014
  const tooltipStories = `import type { Meta, StoryObj } from '@storybook/react';
1015
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '.';
1016
- import { Button } from '../button';
1015
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui/tooltip';
1016
+ import { Button } from '~/components/ui/button';
1017
1017
 
1018
1018
  const meta: Meta<typeof Tooltip> = {
1019
1019
  title: 'Components/Tooltip',
@@ -1217,10 +1217,10 @@ import {
1217
1217
  DialogHeader,
1218
1218
  DialogTitle,
1219
1219
  DialogTrigger,
1220
- } from '.';
1221
- import { Button } from '../button';
1222
- import { Input } from '../input';
1223
- import { Label } from '../label';
1220
+ } from '~/components/ui/dialog';
1221
+ import { Button } from '~/components/ui/button';
1222
+ import { Input } from '~/components/ui/input';
1223
+ import { Label } from '~/components/ui/label';
1224
1224
 
1225
1225
  const meta: Meta<typeof Dialog> = {
1226
1226
  title: 'Components/Dialog',
package/src/openapi.ts CHANGED
@@ -27,8 +27,9 @@ export function resolveOpenApiConfig(
27
27
  }
28
28
 
29
29
  if (config.openapi === true || config.openapi === undefined) {
30
+ // Enable by default when not explicitly set (undefined) or explicitly true
30
31
  return {
31
- enabled: config.openapi === true,
32
+ enabled: true,
32
33
  title: 'API Documentation',
33
34
  version: '1.0.0',
34
35
  description: 'Auto-generated API documentation from endpoints',