@geekmidas/cli 1.2.2 → 1.3.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 (90) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{CachedStateProvider-DVyKfaMm.mjs → CachedStateProvider-BDq5WqSy.mjs} +1 -1
  3. package/dist/{CachedStateProvider-DVyKfaMm.mjs.map → CachedStateProvider-BDq5WqSy.mjs.map} +1 -1
  4. package/dist/CachedStateProvider-CI61keQ1.mjs +3 -0
  5. package/dist/{HostingerProvider-DqUq6e9i.mjs → HostingerProvider-B9N-TKbp.mjs} +2 -2
  6. package/dist/{HostingerProvider-DqUq6e9i.mjs.map → HostingerProvider-B9N-TKbp.mjs.map} +1 -1
  7. package/dist/{LocalStateProvider-DxoSaWUV.mjs → LocalStateProvider-BDm7ZqJo.mjs} +1 -1
  8. package/dist/{LocalStateProvider-DxoSaWUV.mjs.map → LocalStateProvider-BDm7ZqJo.mjs.map} +1 -1
  9. package/dist/{Route53Provider-KUAX3vz9.mjs → Route53Provider-DOWmFnwN.mjs} +2 -2
  10. package/dist/{Route53Provider-KUAX3vz9.mjs.map → Route53Provider-DOWmFnwN.mjs.map} +1 -1
  11. package/dist/{Route53Provider-CpRIqu69.cjs → Route53Provider-xrWuBXih.cjs} +2 -2
  12. package/dist/{Route53Provider-CpRIqu69.cjs.map → Route53Provider-xrWuBXih.cjs.map} +1 -1
  13. package/dist/{SSMStateProvider-D79o_JjM.cjs → SSMStateProvider-DGrqYll0.cjs} +8 -4
  14. package/dist/SSMStateProvider-DGrqYll0.cjs.map +1 -0
  15. package/dist/{SSMStateProvider-BjCi_58g.mjs → SSMStateProvider-DT0WV-E_.mjs} +9 -4
  16. package/dist/SSMStateProvider-DT0WV-E_.mjs.map +1 -0
  17. package/dist/{bundler-BqTN5Dj5.mjs → bundler-DgXsOSxc.mjs} +3 -3
  18. package/dist/{bundler-BqTN5Dj5.mjs.map → bundler-DgXsOSxc.mjs.map} +1 -1
  19. package/dist/chunk-Duj1WY3L.mjs +7 -0
  20. package/dist/{config-BQ4a36Rq.mjs → config-C1bidhvG.mjs} +2 -2
  21. package/dist/{config-BQ4a36Rq.mjs.map → config-C1bidhvG.mjs.map} +1 -1
  22. package/dist/{config-Bayob8pB.cjs → config-C1dM7aZb.cjs} +2 -2
  23. package/dist/{config-Bayob8pB.cjs.map → config-C1dM7aZb.cjs.map} +1 -1
  24. package/dist/config.cjs +2 -2
  25. package/dist/config.d.cts +1 -1
  26. package/dist/config.d.mts +2 -2
  27. package/dist/config.mjs +2 -2
  28. package/dist/{credentials-DT1dSxIx.mjs → credentials-s1kLcIzK.mjs} +1 -1
  29. package/dist/{credentials-DT1dSxIx.mjs.map → credentials-s1kLcIzK.mjs.map} +1 -1
  30. package/dist/deploy/sniffer-routes-worker.cjs +65 -0
  31. package/dist/deploy/sniffer-routes-worker.cjs.map +1 -0
  32. package/dist/deploy/sniffer-routes-worker.d.cts +1 -0
  33. package/dist/deploy/sniffer-routes-worker.d.mts +1 -0
  34. package/dist/deploy/sniffer-routes-worker.mjs +64 -0
  35. package/dist/deploy/sniffer-routes-worker.mjs.map +1 -0
  36. package/dist/dokploy-api-DSJYNx88.mjs +3 -0
  37. package/dist/{dokploy-api-7k3t7_zd.mjs → dokploy-api-z0833e7r.mjs} +1 -1
  38. package/dist/{dokploy-api-7k3t7_zd.mjs.map → dokploy-api-z0833e7r.mjs.map} +1 -1
  39. package/dist/{encryption-JtMsiGNp.mjs → encryption-BOH5M-f-.mjs} +1 -1
  40. package/dist/{encryption-JtMsiGNp.mjs.map → encryption-BOH5M-f-.mjs.map} +1 -1
  41. package/dist/encryption-a9TNMWav.mjs +3 -0
  42. package/dist/{index-Bi9vGQJy.d.mts → index-DvpWzLD7.d.mts} +5 -2
  43. package/dist/index-DvpWzLD7.d.mts.map +1 -0
  44. package/dist/{index-CufAAnge.d.cts → index-DzmZ6SUW.d.cts} +4 -1
  45. package/dist/index-DzmZ6SUW.d.cts.map +1 -0
  46. package/dist/index.cjs +100 -170
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.mjs +116 -185
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/{openapi-BZ4Qik9w.mjs → openapi-9k6a6VA4.mjs} +3 -3
  51. package/dist/{openapi-BZ4Qik9w.mjs.map → openapi-9k6a6VA4.mjs.map} +1 -1
  52. package/dist/{openapi-CzfnHlhG.cjs → openapi-Dcja4e1C.cjs} +2 -8
  53. package/dist/{openapi-CzfnHlhG.cjs.map → openapi-Dcja4e1C.cjs.map} +1 -1
  54. package/dist/{openapi-react-query-DGEkD39r.mjs → openapi-react-query-DaTMSPD5.mjs} +1 -1
  55. package/dist/{openapi-react-query-DGEkD39r.mjs.map → openapi-react-query-DaTMSPD5.mjs.map} +1 -1
  56. package/dist/openapi-react-query.mjs +1 -1
  57. package/dist/openapi.cjs +3 -3
  58. package/dist/openapi.d.mts +1 -1
  59. package/dist/openapi.mjs +3 -3
  60. package/dist/{storage-BMW6yLu3.mjs → storage-DmCbr6DI.mjs} +1 -1
  61. package/dist/{storage-BMW6yLu3.mjs.map → storage-DmCbr6DI.mjs.map} +1 -1
  62. package/dist/{storage-D8XzjVaO.mjs → storage-Dx_jZbq6.mjs} +1 -1
  63. package/dist/{types-BldpmqQX.d.mts → types-B9UZ7fOG.d.mts} +1 -1
  64. package/dist/{types-BldpmqQX.d.mts.map → types-B9UZ7fOG.d.mts.map} +1 -1
  65. package/dist/workspace/index.cjs +1 -1
  66. package/dist/workspace/index.d.cts +1 -1
  67. package/dist/workspace/index.d.mts +2 -2
  68. package/dist/workspace/index.mjs +1 -1
  69. package/dist/{workspace-CASoZOjs.mjs → workspace-Cb_I7oCJ.mjs} +5 -8
  70. package/dist/{workspace-CASoZOjs.mjs.map → workspace-Cb_I7oCJ.mjs.map} +1 -1
  71. package/dist/{workspace-BMJE18LV.cjs → workspace-CeFgIDC-.cjs} +3 -2
  72. package/dist/{workspace-BMJE18LV.cjs.map → workspace-CeFgIDC-.cjs.map} +1 -1
  73. package/package.json +2 -2
  74. package/src/deploy/SSMStateProvider.ts +14 -3
  75. package/src/deploy/StateProvider.ts +5 -1
  76. package/src/deploy/__tests__/SSMStateProvider.spec.ts +12 -0
  77. package/src/deploy/__tests__/createStateProvider.spec.ts +10 -0
  78. package/src/dev/index.ts +69 -106
  79. package/src/init/generators/web.ts +6 -2
  80. package/src/workspace/__tests__/client-generator.spec.ts +330 -301
  81. package/src/workspace/client-generator.ts +139 -199
  82. package/src/workspace/schema.ts +2 -0
  83. package/tsdown.config.ts +1 -0
  84. package/dist/CachedStateProvider-OiFUGr7p.mjs +0 -3
  85. package/dist/SSMStateProvider-BjCi_58g.mjs.map +0 -1
  86. package/dist/SSMStateProvider-D79o_JjM.cjs.map +0 -1
  87. package/dist/dokploy-api-CHa8G51l.mjs +0 -3
  88. package/dist/encryption-UUmaWAmz.mjs +0 -3
  89. package/dist/index-Bi9vGQJy.d.mts.map +0 -1
  90. package/dist/index-CufAAnge.d.cts.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/cli",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
5
5
  "private": false,
6
6
  "type": "module",
@@ -54,8 +54,8 @@
54
54
  "prompts": "~2.4.2",
55
55
  "tsx": "~4.20.3",
56
56
  "@geekmidas/constructs": "~1.0.0",
57
- "@geekmidas/errors": "~1.0.0",
58
57
  "@geekmidas/envkit": "~1.0.0",
58
+ "@geekmidas/errors": "~1.0.0",
59
59
  "@geekmidas/logger": "~1.0.0",
60
60
  "@geekmidas/schema": "~1.0.0"
61
61
  },
@@ -22,6 +22,8 @@ export interface SSMStateProviderOptions {
22
22
  workspaceName: string;
23
23
  /** AWS region */
24
24
  region?: AwsRegion;
25
+ /** AWS profile name (optional - uses default credential chain if not provided) */
26
+ profile?: string;
25
27
  /** AWS credentials (optional - uses default credential chain if not provided) */
26
28
  credentials?: SSMClientConfig['credentials'];
27
29
  /** Custom endpoint (for LocalStack or other S3-compatible services) */
@@ -44,12 +46,21 @@ export class SSMStateProvider implements StateProvider {
44
46
  * Create an SSMStateProvider with a new SSMClient.
45
47
  */
46
48
  static create(options: SSMStateProviderOptions): SSMStateProvider {
47
- const client = new SSMClient({
49
+ const clientConfig: SSMClientConfig = {
48
50
  region: options.region,
49
- credentials: options.credentials,
50
51
  endpoint: options.endpoint,
51
- });
52
+ };
52
53
 
54
+ // Use profile credentials if specified, otherwise use provided credentials or default chain
55
+ if (options.profile) {
56
+ // Dynamic import to avoid requiring @aws-sdk/credential-providers when not using profiles
57
+ const { fromIni } = require('@aws-sdk/credential-providers');
58
+ clientConfig.credentials = fromIni({ profile: options.profile });
59
+ } else if (options.credentials) {
60
+ clientConfig.credentials = options.credentials;
61
+ }
62
+
63
+ const client = new SSMClient(clientConfig);
53
64
  return new SSMStateProvider(options.workspaceName, client);
54
65
  }
55
66
 
@@ -79,6 +79,8 @@ export interface SSMStateConfig {
79
79
  provider: 'ssm';
80
80
  /** AWS region (required for SSM provider) */
81
81
  region: AwsRegion;
82
+ /** AWS profile name (optional - uses default credential chain if not provided) */
83
+ profile?: string;
82
84
  }
83
85
 
84
86
  /**
@@ -157,10 +159,12 @@ export async function createStateProvider(
157
159
  const { SSMStateProvider } = await import('./SSMStateProvider');
158
160
  const { CachedStateProvider } = await import('./CachedStateProvider');
159
161
 
162
+ const ssmConfig = config as SSMStateConfig;
160
163
  const local = new LocalStateProvider(workspaceRoot);
161
164
  const ssm = SSMStateProvider.create({
162
165
  workspaceName,
163
- region: (config as SSMStateConfig).region,
166
+ region: ssmConfig.region,
167
+ profile: ssmConfig.profile,
164
168
  });
165
169
 
166
170
  return new CachedStateProvider(ssm, local);
@@ -75,6 +75,18 @@ describe('SSMStateProvider', () => {
75
75
  expect(provider).toBeInstanceOf(SSMStateProvider);
76
76
  expect(provider.workspaceName).toBe('my-workspace');
77
77
  });
78
+
79
+ it('should create provider with profile option', () => {
80
+ const provider = SSMStateProvider.create({
81
+ workspaceName: 'my-workspace',
82
+ region: 'us-west-2',
83
+ profile: 'my-profile',
84
+ endpoint: LOCALSTACK_ENDPOINT,
85
+ });
86
+
87
+ expect(provider).toBeInstanceOf(SSMStateProvider);
88
+ expect(provider.workspaceName).toBe('my-workspace');
89
+ });
78
90
  });
79
91
 
80
92
  describe('read', () => {
@@ -93,6 +93,16 @@ describe('createStateProvider', () => {
93
93
 
94
94
  expect(provider).toBeInstanceOf(CachedStateProvider);
95
95
  });
96
+
97
+ it('should create CachedStateProvider for ssm config with profile', async () => {
98
+ const provider = await createStateProvider({
99
+ config: { provider: 'ssm', region: 'us-east-1', profile: 'my-profile' },
100
+ workspaceRoot: testDir,
101
+ workspaceName: 'test-workspace',
102
+ });
103
+
104
+ expect(provider).toBeInstanceOf(CachedStateProvider);
105
+ });
96
106
  });
97
107
 
98
108
  describe('custom provider', () => {
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) {
@@ -90,17 +90,21 @@ export default nextConfig;
90
90
  name: 'next',
91
91
  },
92
92
  ],
93
+ baseUrl: '.',
93
94
  paths: {
94
- '~/*': ['./src/*'],
95
+ '~/*': ['./src/*', '../../packages/ui/src/*'],
95
96
  [`${modelsPackage}`]: ['../../packages/models/src'],
96
97
  [`${modelsPackage}/*`]: ['../../packages/models/src/*'],
97
98
  [`${uiPackage}`]: ['../../packages/ui/src'],
98
99
  [`${uiPackage}/*`]: ['../../packages/ui/src/*'],
99
100
  },
100
- baseUrl: '.',
101
101
  },
102
102
  include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
103
103
  exclude: ['node_modules'],
104
+ references: [
105
+ { path: '../../packages/ui' },
106
+ { path: '../../packages/models' },
107
+ ],
104
108
  };
105
109
 
106
110
  // Query client singleton for browser, fresh instance for server