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