nuxt-openapi-hyperfetch 0.2.7-alpha.1 → 0.3.0-beta

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 (68) hide show
  1. package/.editorconfig +26 -26
  2. package/.prettierignore +17 -17
  3. package/CONTRIBUTING.md +291 -291
  4. package/INSTRUCTIONS.md +327 -327
  5. package/LICENSE +202 -202
  6. package/README.md +309 -231
  7. package/dist/cli/config.d.ts +9 -2
  8. package/dist/cli/config.js +1 -1
  9. package/dist/cli/logo.js +5 -5
  10. package/dist/cli/messages.d.ts +1 -0
  11. package/dist/cli/messages.js +2 -0
  12. package/dist/cli/prompts.d.ts +5 -0
  13. package/dist/cli/prompts.js +12 -0
  14. package/dist/cli/types.d.ts +1 -1
  15. package/dist/generators/components/connector-generator/templates.js +68 -19
  16. package/dist/generators/shared/runtime/useFormConnector.js +8 -1
  17. package/dist/generators/shared/runtime/useListConnector.js +13 -6
  18. package/dist/generators/use-async-data/generator.js +4 -0
  19. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +4 -4
  20. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +4 -4
  21. package/dist/generators/use-async-data/templates.js +17 -17
  22. package/dist/generators/use-fetch/generator.js +4 -0
  23. package/dist/generators/use-fetch/templates.js +14 -14
  24. package/dist/index.js +40 -27
  25. package/dist/module/index.js +19 -0
  26. package/dist/module/types.d.ts +7 -0
  27. package/docs/API-REFERENCE.md +886 -886
  28. package/docs/generated-components.md +615 -615
  29. package/docs/headless-composables-ui.md +569 -569
  30. package/eslint.config.js +85 -85
  31. package/package.json +1 -1
  32. package/src/cli/config.ts +147 -140
  33. package/src/cli/logger.ts +124 -124
  34. package/src/cli/logo.ts +25 -25
  35. package/src/cli/messages.ts +4 -0
  36. package/src/cli/prompts.ts +14 -1
  37. package/src/cli/types.ts +50 -50
  38. package/src/generators/components/connector-generator/generator.ts +138 -138
  39. package/src/generators/components/connector-generator/templates.ts +307 -254
  40. package/src/generators/components/connector-generator/types.ts +34 -34
  41. package/src/generators/components/schema-analyzer/index.ts +44 -44
  42. package/src/generators/components/schema-analyzer/intent-detector.ts +187 -187
  43. package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -96
  44. package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -166
  45. package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -268
  46. package/src/generators/components/schema-analyzer/types.ts +177 -177
  47. package/src/generators/nuxt-server/generator.ts +272 -272
  48. package/src/generators/shared/runtime/apiHelpers.ts +535 -535
  49. package/src/generators/shared/runtime/pagination.ts +323 -323
  50. package/src/generators/shared/runtime/useDeleteConnector.ts +109 -109
  51. package/src/generators/shared/runtime/useDetailConnector.ts +64 -64
  52. package/src/generators/shared/runtime/useFormConnector.ts +147 -139
  53. package/src/generators/shared/runtime/useListConnector.ts +158 -148
  54. package/src/generators/shared/runtime/zod-error-merger.ts +119 -119
  55. package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -399
  56. package/src/generators/shared/templates/api-pagination-plugin.ts +158 -158
  57. package/src/generators/use-async-data/generator.ts +213 -205
  58. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -329
  59. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -324
  60. package/src/generators/use-async-data/templates.ts +257 -257
  61. package/src/generators/use-fetch/generator.ts +178 -170
  62. package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -354
  63. package/src/generators/use-fetch/templates.ts +214 -214
  64. package/src/index.ts +306 -303
  65. package/src/module/index.ts +158 -133
  66. package/src/module/types.ts +39 -31
  67. package/dist/generators/tanstack-query/generator.d.ts +0 -5
  68. package/dist/generators/tanstack-query/generator.js +0 -11
@@ -1,272 +1,272 @@
1
- import * as path from 'path';
2
- import fs from 'fs-extra';
3
- import { format } from 'prettier';
4
- import {
5
- getApiFiles as getApiFilesOfficial,
6
- parseApiFile as parseApiFileOfficial,
7
- } from './parser.js';
8
- import {
9
- getApiFiles as getApiFilesHeyApi,
10
- parseApiFile as parseApiFileHeyApi,
11
- } from '../shared/parsers/heyapi-parser.js';
12
- import {
13
- generateServerRouteFile,
14
- generateRouteFilePath,
15
- generateRoutesIndexFile,
16
- } from './templates.js';
17
- import {
18
- generateAuthContextStub,
19
- generateAuthTypesStub,
20
- generateTransformerStub,
21
- generateTransformerExamples,
22
- generateBffReadme,
23
- } from './bff-templates.js';
24
- import type { MethodInfo } from './types.js';
25
- import { type Logger, createClackLogger } from '../../cli/logger.js';
26
-
27
- /**
28
- * Main function to generate Nuxt Server Routes
29
- */
30
- export async function generateNuxtServerRoutes(
31
- inputDir: string,
32
- serverRoutePath: string,
33
- options?: {
34
- enableBff?: boolean;
35
- backend?: string;
36
- },
37
- logger: Logger = createClackLogger()
38
- ): Promise<void> {
39
- const mainSpinner = logger.spinner();
40
-
41
- // Select parser based on chosen backend
42
- const getApiFiles = options?.backend === 'heyapi' ? getApiFilesHeyApi : getApiFilesOfficial;
43
- const parseApiFile = options?.backend === 'heyapi' ? parseApiFileHeyApi : parseApiFileOfficial;
44
-
45
- const enableBff = options?.enableBff ?? false;
46
-
47
- if (enableBff) {
48
- logger.log.info('BFF Mode: Enabled (transformers + auth)');
49
- }
50
-
51
- // 1. Get all API files
52
- mainSpinner.start('Scanning API files');
53
- const apiFiles = getApiFiles(inputDir);
54
- mainSpinner.stop(`Found ${apiFiles.length} API file(s)`);
55
-
56
- if (apiFiles.length === 0) {
57
- throw new Error('No API files found in the input directory');
58
- }
59
-
60
- // 2. Parse each API file
61
- mainSpinner.start('Parsing API files');
62
- const allMethods: MethodInfo[] = [];
63
-
64
- for (const file of apiFiles) {
65
- const fileName = path.basename(file);
66
- try {
67
- const apiInfo = parseApiFile(file);
68
- allMethods.push(...apiInfo.methods);
69
- } catch (error) {
70
- logger.log.error(`Error parsing ${fileName}: ${String(error)}`);
71
- }
72
- }
73
-
74
- mainSpinner.stop(`Found ${allMethods.length} routes to generate`);
75
-
76
- if (allMethods.length === 0) {
77
- logger.log.warn('No methods found to generate');
78
- return;
79
- }
80
-
81
- // 3. Clean and create output directory
82
- mainSpinner.start('Preparing output directory');
83
- await fs.emptyDir(serverRoutePath);
84
- mainSpinner.stop('Output directory ready');
85
-
86
- // 4. Generate BFF structure if enabled
87
- if (enableBff) {
88
- await generateBffStructure(allMethods, serverRoutePath, inputDir, logger);
89
- }
90
-
91
- // 5. Calculate relative import path from server routes to APIs
92
- const relativePath = calculateRelativeImportPath(serverRoutePath, inputDir);
93
-
94
- // 6. Generate each server route
95
- mainSpinner.start('Generating server routes');
96
- let successCount = 0;
97
- let errorCount = 0;
98
-
99
- for (const method of allMethods) {
100
- try {
101
- // Extract resource name from path
102
- const resource = extractResourceFromPath(method.path);
103
-
104
- const code = generateServerRouteFile(method, relativePath, {
105
- enableBff: enableBff,
106
- resource: resource,
107
- });
108
- const formattedCode = await formatCode(code, logger);
109
- const routeFilePath = generateRouteFilePath(method);
110
- const fullPath = path.join(serverRoutePath, routeFilePath);
111
-
112
- // Ensure directory exists
113
- await fs.ensureDir(path.dirname(fullPath));
114
-
115
- await fs.writeFile(fullPath, formattedCode, 'utf-8');
116
- successCount++;
117
- } catch (error) {
118
- logger.log.error(`Error generating ${method.path} [${method.httpMethod}]: ${String(error)}`);
119
- errorCount++;
120
- }
121
- }
122
- mainSpinner.stop(`Generated ${successCount} server routes`);
123
-
124
- // 7. Generate configuration files
125
- mainSpinner.start('Generating configuration files');
126
-
127
- // Generate routes index (documentation)
128
- const routesIndexCode = generateRoutesIndexFile(allMethods);
129
- const formattedRoutesIndex = await formatCode(routesIndexCode, logger);
130
- await fs.writeFile(path.join(serverRoutePath, '_routes.ts'), formattedRoutesIndex, 'utf-8');
131
- mainSpinner.stop('Configuration files generated');
132
-
133
- // 8. Summary and Next Steps
134
- if (errorCount > 0) {
135
- logger.log.warn(`Completed with ${errorCount} error(s)`);
136
- }
137
- logger.log.success(`Generated ${successCount} server route(s) in ${serverRoutePath}`);
138
-
139
- // Build next steps message
140
- let nextSteps = '1. Configure API_BASE_URL and API_SECRET in your .env\n';
141
- nextSteps += '2. Update nuxt.config.ts with runtimeConfig (add apiBaseUrl and apiSecret)';
142
-
143
- if (enableBff) {
144
- nextSteps += '\n3. Implement authentication in server/auth/context.ts';
145
- nextSteps += '\n4. Add business logic to transformers in server/bff/transformers/';
146
- nextSteps += '\n5. See server/bff/README.md for BFF documentation';
147
- nextSteps += '\n6. Start your Nuxt dev server and test the routes';
148
- } else {
149
- nextSteps += '\n3. Start your Nuxt dev server and test the routes';
150
- }
151
-
152
- logger.note(nextSteps, 'Next steps');
153
- }
154
-
155
- /**
156
- * Calculate relative import path from server routes to APIs
157
- */
158
- function calculateRelativeImportPath(serverRoutePath: string, inputDir: string): string {
159
- // Use Nuxt's ~ alias (project root) so the path is stable regardless of serverRoutePath depth
160
- const projectRoot = process.cwd();
161
- const relativeInputDir = path.relative(projectRoot, path.resolve(inputDir));
162
- // Convert Windows paths to Unix-style
163
- return '~/' + relativeInputDir.replace(/\\/g, '/');
164
- }
165
-
166
- /**
167
- * Format code with Prettier
168
- */
169
- async function formatCode(code: string, logger: Logger): Promise<string> {
170
- try {
171
- return await format(code, {
172
- parser: 'typescript',
173
- semi: false,
174
- singleQuote: true,
175
- trailingComma: 'es5',
176
- printWidth: 100,
177
- });
178
- } catch {
179
- logger.log.warn('Prettier formatting failed, using unformatted code');
180
- return code;
181
- }
182
- }
183
-
184
- /**
185
- * Generate BFF structure (auth + transformers)
186
- */
187
- async function generateBffStructure(
188
- allMethods: MethodInfo[],
189
- serverRoutePath: string,
190
- inputDir: string,
191
- logger: Logger
192
- ): Promise<void> {
193
- const bffSpinner = logger.spinner();
194
- bffSpinner.start('Generating BFF structure (auth + transformers)');
195
-
196
- const serverRoot = path.dirname(serverRoutePath);
197
-
198
- // 1. Generate auth files (only if they don't exist)
199
- const authDir = path.join(serverRoot, 'auth');
200
- await fs.ensureDir(authDir);
201
-
202
- const authContextPath = path.join(authDir, 'context.ts');
203
- if (!fs.existsSync(authContextPath)) {
204
- const authContextCode = generateAuthContextStub();
205
- const formattedAuthContext = await formatCode(authContextCode, logger);
206
- await fs.writeFile(authContextPath, formattedAuthContext, 'utf-8');
207
- }
208
-
209
- const authTypesPath = path.join(authDir, 'types.ts');
210
- if (!fs.existsSync(authTypesPath)) {
211
- const authTypesCode = generateAuthTypesStub();
212
- const formattedAuthTypes = await formatCode(authTypesCode, logger);
213
- await fs.writeFile(authTypesPath, formattedAuthTypes, 'utf-8');
214
- }
215
-
216
- // 2. Generate transformer stubs (only if they don't exist)
217
- const bffDir = path.join(serverRoot, 'bff');
218
- const transformersDir = path.join(bffDir, 'transformers');
219
- await fs.ensureDir(transformersDir);
220
-
221
- // Group methods by resource
222
- const methodsByResource = new Map<string, MethodInfo[]>();
223
- for (const method of allMethods) {
224
- const resource = extractResourceFromPath(method.path);
225
- if (!methodsByResource.has(resource)) {
226
- methodsByResource.set(resource, []);
227
- }
228
- methodsByResource.get(resource)!.push(method);
229
- }
230
-
231
- // Generate transformer for each resource
232
- for (const [resource, methods] of methodsByResource.entries()) {
233
- const transformerPath = path.join(transformersDir, `${resource}.ts`);
234
- if (!fs.existsSync(transformerPath)) {
235
- const transformerCode = generateTransformerStub(resource, methods, inputDir);
236
- const formattedTransformer = await formatCode(transformerCode, logger);
237
- await fs.writeFile(transformerPath, formattedTransformer, 'utf-8');
238
- }
239
- }
240
-
241
- // 3. Generate examples file (always regenerated)
242
- const examplesPath = path.join(bffDir, '_transformers.example.ts');
243
- const examplesCode = generateTransformerExamples();
244
- const formattedExamples = await formatCode(examplesCode, logger);
245
- await fs.writeFile(examplesPath, formattedExamples, 'utf-8');
246
-
247
- // 4. Generate BFF README (always regenerated)
248
- const bffReadmePath = path.join(bffDir, 'README.md');
249
- const bffReadmeCode = generateBffReadme();
250
- await fs.writeFile(bffReadmePath, bffReadmeCode, 'utf-8');
251
-
252
- bffSpinner.stop('BFF structure generated');
253
- }
254
-
255
- /**
256
- * Extract resource name from API path
257
- * Examples:
258
- * /pet -> pet
259
- * /pet/{id} -> pet
260
- * /store/inventory -> store
261
- * /user/login -> user
262
- */
263
- function extractResourceFromPath(path: string): string {
264
- // Remove leading slash
265
- const cleanPath = path.startsWith('/') ? path.substring(1) : path;
266
-
267
- // Get first segment
268
- const firstSegment = cleanPath.split('/')[0];
269
-
270
- // Remove any path params
271
- return firstSegment.replace(/\{[^}]+\}/g, '').toLowerCase();
272
- }
1
+ import * as path from 'path';
2
+ import fs from 'fs-extra';
3
+ import { format } from 'prettier';
4
+ import {
5
+ getApiFiles as getApiFilesOfficial,
6
+ parseApiFile as parseApiFileOfficial,
7
+ } from './parser.js';
8
+ import {
9
+ getApiFiles as getApiFilesHeyApi,
10
+ parseApiFile as parseApiFileHeyApi,
11
+ } from '../shared/parsers/heyapi-parser.js';
12
+ import {
13
+ generateServerRouteFile,
14
+ generateRouteFilePath,
15
+ generateRoutesIndexFile,
16
+ } from './templates.js';
17
+ import {
18
+ generateAuthContextStub,
19
+ generateAuthTypesStub,
20
+ generateTransformerStub,
21
+ generateTransformerExamples,
22
+ generateBffReadme,
23
+ } from './bff-templates.js';
24
+ import type { MethodInfo } from './types.js';
25
+ import { type Logger, createClackLogger } from '../../cli/logger.js';
26
+
27
+ /**
28
+ * Main function to generate Nuxt Server Routes
29
+ */
30
+ export async function generateNuxtServerRoutes(
31
+ inputDir: string,
32
+ serverRoutePath: string,
33
+ options?: {
34
+ enableBff?: boolean;
35
+ backend?: string;
36
+ },
37
+ logger: Logger = createClackLogger()
38
+ ): Promise<void> {
39
+ const mainSpinner = logger.spinner();
40
+
41
+ // Select parser based on chosen backend
42
+ const getApiFiles = options?.backend === 'heyapi' ? getApiFilesHeyApi : getApiFilesOfficial;
43
+ const parseApiFile = options?.backend === 'heyapi' ? parseApiFileHeyApi : parseApiFileOfficial;
44
+
45
+ const enableBff = options?.enableBff ?? false;
46
+
47
+ if (enableBff) {
48
+ logger.log.info('BFF Mode: Enabled (transformers + auth)');
49
+ }
50
+
51
+ // 1. Get all API files
52
+ mainSpinner.start('Scanning API files');
53
+ const apiFiles = getApiFiles(inputDir);
54
+ mainSpinner.stop(`Found ${apiFiles.length} API file(s)`);
55
+
56
+ if (apiFiles.length === 0) {
57
+ throw new Error('No API files found in the input directory');
58
+ }
59
+
60
+ // 2. Parse each API file
61
+ mainSpinner.start('Parsing API files');
62
+ const allMethods: MethodInfo[] = [];
63
+
64
+ for (const file of apiFiles) {
65
+ const fileName = path.basename(file);
66
+ try {
67
+ const apiInfo = parseApiFile(file);
68
+ allMethods.push(...apiInfo.methods);
69
+ } catch (error) {
70
+ logger.log.error(`Error parsing ${fileName}: ${String(error)}`);
71
+ }
72
+ }
73
+
74
+ mainSpinner.stop(`Found ${allMethods.length} routes to generate`);
75
+
76
+ if (allMethods.length === 0) {
77
+ logger.log.warn('No methods found to generate');
78
+ return;
79
+ }
80
+
81
+ // 3. Clean and create output directory
82
+ mainSpinner.start('Preparing output directory');
83
+ await fs.emptyDir(serverRoutePath);
84
+ mainSpinner.stop('Output directory ready');
85
+
86
+ // 4. Generate BFF structure if enabled
87
+ if (enableBff) {
88
+ await generateBffStructure(allMethods, serverRoutePath, inputDir, logger);
89
+ }
90
+
91
+ // 5. Calculate relative import path from server routes to APIs
92
+ const relativePath = calculateRelativeImportPath(serverRoutePath, inputDir);
93
+
94
+ // 6. Generate each server route
95
+ mainSpinner.start('Generating server routes');
96
+ let successCount = 0;
97
+ let errorCount = 0;
98
+
99
+ for (const method of allMethods) {
100
+ try {
101
+ // Extract resource name from path
102
+ const resource = extractResourceFromPath(method.path);
103
+
104
+ const code = generateServerRouteFile(method, relativePath, {
105
+ enableBff: enableBff,
106
+ resource: resource,
107
+ });
108
+ const formattedCode = await formatCode(code, logger);
109
+ const routeFilePath = generateRouteFilePath(method);
110
+ const fullPath = path.join(serverRoutePath, routeFilePath);
111
+
112
+ // Ensure directory exists
113
+ await fs.ensureDir(path.dirname(fullPath));
114
+
115
+ await fs.writeFile(fullPath, formattedCode, 'utf-8');
116
+ successCount++;
117
+ } catch (error) {
118
+ logger.log.error(`Error generating ${method.path} [${method.httpMethod}]: ${String(error)}`);
119
+ errorCount++;
120
+ }
121
+ }
122
+ mainSpinner.stop(`Generated ${successCount} server routes`);
123
+
124
+ // 7. Generate configuration files
125
+ mainSpinner.start('Generating configuration files');
126
+
127
+ // Generate routes index (documentation)
128
+ const routesIndexCode = generateRoutesIndexFile(allMethods);
129
+ const formattedRoutesIndex = await formatCode(routesIndexCode, logger);
130
+ await fs.writeFile(path.join(serverRoutePath, '_routes.ts'), formattedRoutesIndex, 'utf-8');
131
+ mainSpinner.stop('Configuration files generated');
132
+
133
+ // 8. Summary and Next Steps
134
+ if (errorCount > 0) {
135
+ logger.log.warn(`Completed with ${errorCount} error(s)`);
136
+ }
137
+ logger.log.success(`Generated ${successCount} server route(s) in ${serverRoutePath}`);
138
+
139
+ // Build next steps message
140
+ let nextSteps = '1. Configure API_BASE_URL and API_SECRET in your .env\n';
141
+ nextSteps += '2. Update nuxt.config.ts with runtimeConfig (add apiBaseUrl and apiSecret)';
142
+
143
+ if (enableBff) {
144
+ nextSteps += '\n3. Implement authentication in server/auth/context.ts';
145
+ nextSteps += '\n4. Add business logic to transformers in server/bff/transformers/';
146
+ nextSteps += '\n5. See server/bff/README.md for BFF documentation';
147
+ nextSteps += '\n6. Start your Nuxt dev server and test the routes';
148
+ } else {
149
+ nextSteps += '\n3. Start your Nuxt dev server and test the routes';
150
+ }
151
+
152
+ logger.note(nextSteps, 'Next steps');
153
+ }
154
+
155
+ /**
156
+ * Calculate relative import path from server routes to APIs
157
+ */
158
+ function calculateRelativeImportPath(serverRoutePath: string, inputDir: string): string {
159
+ // Use Nuxt's ~ alias (project root) so the path is stable regardless of serverRoutePath depth
160
+ const projectRoot = process.cwd();
161
+ const relativeInputDir = path.relative(projectRoot, path.resolve(inputDir));
162
+ // Convert Windows paths to Unix-style
163
+ return '~/' + relativeInputDir.replace(/\\/g, '/');
164
+ }
165
+
166
+ /**
167
+ * Format code with Prettier
168
+ */
169
+ async function formatCode(code: string, logger: Logger): Promise<string> {
170
+ try {
171
+ return await format(code, {
172
+ parser: 'typescript',
173
+ semi: false,
174
+ singleQuote: true,
175
+ trailingComma: 'es5',
176
+ printWidth: 100,
177
+ });
178
+ } catch {
179
+ logger.log.warn('Prettier formatting failed, using unformatted code');
180
+ return code;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Generate BFF structure (auth + transformers)
186
+ */
187
+ async function generateBffStructure(
188
+ allMethods: MethodInfo[],
189
+ serverRoutePath: string,
190
+ inputDir: string,
191
+ logger: Logger
192
+ ): Promise<void> {
193
+ const bffSpinner = logger.spinner();
194
+ bffSpinner.start('Generating BFF structure (auth + transformers)');
195
+
196
+ const serverRoot = path.dirname(serverRoutePath);
197
+
198
+ // 1. Generate auth files (only if they don't exist)
199
+ const authDir = path.join(serverRoot, 'auth');
200
+ await fs.ensureDir(authDir);
201
+
202
+ const authContextPath = path.join(authDir, 'context.ts');
203
+ if (!fs.existsSync(authContextPath)) {
204
+ const authContextCode = generateAuthContextStub();
205
+ const formattedAuthContext = await formatCode(authContextCode, logger);
206
+ await fs.writeFile(authContextPath, formattedAuthContext, 'utf-8');
207
+ }
208
+
209
+ const authTypesPath = path.join(authDir, 'types.ts');
210
+ if (!fs.existsSync(authTypesPath)) {
211
+ const authTypesCode = generateAuthTypesStub();
212
+ const formattedAuthTypes = await formatCode(authTypesCode, logger);
213
+ await fs.writeFile(authTypesPath, formattedAuthTypes, 'utf-8');
214
+ }
215
+
216
+ // 2. Generate transformer stubs (only if they don't exist)
217
+ const bffDir = path.join(serverRoot, 'bff');
218
+ const transformersDir = path.join(bffDir, 'transformers');
219
+ await fs.ensureDir(transformersDir);
220
+
221
+ // Group methods by resource
222
+ const methodsByResource = new Map<string, MethodInfo[]>();
223
+ for (const method of allMethods) {
224
+ const resource = extractResourceFromPath(method.path);
225
+ if (!methodsByResource.has(resource)) {
226
+ methodsByResource.set(resource, []);
227
+ }
228
+ methodsByResource.get(resource)!.push(method);
229
+ }
230
+
231
+ // Generate transformer for each resource
232
+ for (const [resource, methods] of methodsByResource.entries()) {
233
+ const transformerPath = path.join(transformersDir, `${resource}.ts`);
234
+ if (!fs.existsSync(transformerPath)) {
235
+ const transformerCode = generateTransformerStub(resource, methods, inputDir);
236
+ const formattedTransformer = await formatCode(transformerCode, logger);
237
+ await fs.writeFile(transformerPath, formattedTransformer, 'utf-8');
238
+ }
239
+ }
240
+
241
+ // 3. Generate examples file (always regenerated)
242
+ const examplesPath = path.join(bffDir, '_transformers.example.ts');
243
+ const examplesCode = generateTransformerExamples();
244
+ const formattedExamples = await formatCode(examplesCode, logger);
245
+ await fs.writeFile(examplesPath, formattedExamples, 'utf-8');
246
+
247
+ // 4. Generate BFF README (always regenerated)
248
+ const bffReadmePath = path.join(bffDir, 'README.md');
249
+ const bffReadmeCode = generateBffReadme();
250
+ await fs.writeFile(bffReadmePath, bffReadmeCode, 'utf-8');
251
+
252
+ bffSpinner.stop('BFF structure generated');
253
+ }
254
+
255
+ /**
256
+ * Extract resource name from API path
257
+ * Examples:
258
+ * /pet -> pet
259
+ * /pet/{id} -> pet
260
+ * /store/inventory -> store
261
+ * /user/login -> user
262
+ */
263
+ function extractResourceFromPath(path: string): string {
264
+ // Remove leading slash
265
+ const cleanPath = path.startsWith('/') ? path.substring(1) : path;
266
+
267
+ // Get first segment
268
+ const firstSegment = cleanPath.split('/')[0];
269
+
270
+ // Remove any path params
271
+ return firstSegment.replace(/\{[^}]+\}/g, '').toLowerCase();
272
+ }