nuxt-openapi-hyperfetch 0.2.7-alpha.1 → 0.2.8-alpha.1

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 (60) 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 +231 -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 +12 -12
  16. package/dist/generators/use-async-data/templates.js +17 -17
  17. package/dist/generators/use-fetch/templates.js +14 -14
  18. package/dist/index.js +39 -27
  19. package/dist/module/index.js +19 -0
  20. package/dist/module/types.d.ts +7 -0
  21. package/docs/API-REFERENCE.md +886 -886
  22. package/docs/generated-components.md +615 -615
  23. package/docs/headless-composables-ui.md +569 -569
  24. package/eslint.config.js +85 -85
  25. package/package.json +1 -1
  26. package/src/cli/config.ts +147 -140
  27. package/src/cli/logger.ts +124 -124
  28. package/src/cli/logo.ts +25 -25
  29. package/src/cli/messages.ts +4 -0
  30. package/src/cli/prompts.ts +14 -1
  31. package/src/cli/types.ts +50 -50
  32. package/src/generators/components/connector-generator/generator.ts +138 -138
  33. package/src/generators/components/connector-generator/templates.ts +254 -254
  34. package/src/generators/components/connector-generator/types.ts +34 -34
  35. package/src/generators/components/schema-analyzer/index.ts +44 -44
  36. package/src/generators/components/schema-analyzer/intent-detector.ts +187 -187
  37. package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -96
  38. package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -166
  39. package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -268
  40. package/src/generators/components/schema-analyzer/types.ts +177 -177
  41. package/src/generators/nuxt-server/generator.ts +272 -272
  42. package/src/generators/shared/runtime/apiHelpers.ts +535 -535
  43. package/src/generators/shared/runtime/pagination.ts +323 -323
  44. package/src/generators/shared/runtime/useDeleteConnector.ts +109 -109
  45. package/src/generators/shared/runtime/useDetailConnector.ts +64 -64
  46. package/src/generators/shared/runtime/useFormConnector.ts +139 -139
  47. package/src/generators/shared/runtime/useListConnector.ts +148 -148
  48. package/src/generators/shared/runtime/zod-error-merger.ts +119 -119
  49. package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -399
  50. package/src/generators/shared/templates/api-pagination-plugin.ts +158 -158
  51. package/src/generators/use-async-data/generator.ts +205 -205
  52. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -329
  53. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -324
  54. package/src/generators/use-async-data/templates.ts +257 -257
  55. package/src/generators/use-fetch/generator.ts +170 -170
  56. package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -354
  57. package/src/generators/use-fetch/templates.ts +214 -214
  58. package/src/index.ts +305 -303
  59. package/src/module/index.ts +158 -133
  60. package/src/module/types.ts +39 -31
@@ -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
+ }