nuxt-openapi-hyperfetch 0.1.0-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 (109) hide show
  1. package/.editorconfig +26 -0
  2. package/.prettierignore +17 -0
  3. package/.prettierrc.json +12 -0
  4. package/CONTRIBUTING.md +292 -0
  5. package/INSTRUCTIONS.md +327 -0
  6. package/LICENSE +202 -0
  7. package/README.md +202 -0
  8. package/dist/cli/config.d.ts +57 -0
  9. package/dist/cli/config.js +85 -0
  10. package/dist/cli/logger.d.ts +44 -0
  11. package/dist/cli/logger.js +58 -0
  12. package/dist/cli/logo.d.ts +6 -0
  13. package/dist/cli/logo.js +21 -0
  14. package/dist/cli/messages.d.ts +65 -0
  15. package/dist/cli/messages.js +86 -0
  16. package/dist/cli/prompts.d.ts +30 -0
  17. package/dist/cli/prompts.js +118 -0
  18. package/dist/cli/types.d.ts +43 -0
  19. package/dist/cli/types.js +4 -0
  20. package/dist/cli/utils.d.ts +26 -0
  21. package/dist/cli/utils.js +45 -0
  22. package/dist/generate.d.ts +6 -0
  23. package/dist/generate.js +48 -0
  24. package/dist/generators/nuxt-server/bff-templates.d.ts +25 -0
  25. package/dist/generators/nuxt-server/bff-templates.js +737 -0
  26. package/dist/generators/nuxt-server/generator.d.ts +7 -0
  27. package/dist/generators/nuxt-server/generator.js +206 -0
  28. package/dist/generators/nuxt-server/parser.d.ts +5 -0
  29. package/dist/generators/nuxt-server/parser.js +5 -0
  30. package/dist/generators/nuxt-server/templates.d.ts +35 -0
  31. package/dist/generators/nuxt-server/templates.js +412 -0
  32. package/dist/generators/nuxt-server/types.d.ts +5 -0
  33. package/dist/generators/nuxt-server/types.js +5 -0
  34. package/dist/generators/shared/parsers/heyapi-parser.d.ts +11 -0
  35. package/dist/generators/shared/parsers/heyapi-parser.js +248 -0
  36. package/dist/generators/shared/parsers/official-parser.d.ts +5 -0
  37. package/dist/generators/shared/parsers/official-parser.js +5 -0
  38. package/dist/generators/shared/runtime/apiHelpers.d.ts +183 -0
  39. package/dist/generators/shared/runtime/apiHelpers.js +268 -0
  40. package/dist/generators/shared/templates/api-callbacks-plugin.d.ts +178 -0
  41. package/dist/generators/shared/templates/api-callbacks-plugin.js +338 -0
  42. package/dist/generators/shared/types.d.ts +25 -0
  43. package/dist/generators/shared/types.js +4 -0
  44. package/dist/generators/tanstack-query/generator.d.ts +5 -0
  45. package/dist/generators/tanstack-query/generator.js +11 -0
  46. package/dist/generators/use-async-data/generator.d.ts +5 -0
  47. package/dist/generators/use-async-data/generator.js +156 -0
  48. package/dist/generators/use-async-data/parser.d.ts +5 -0
  49. package/dist/generators/use-async-data/parser.js +5 -0
  50. package/dist/generators/use-async-data/runtime/useApiAsyncData.d.ts +38 -0
  51. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +122 -0
  52. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.d.ts +54 -0
  53. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +126 -0
  54. package/dist/generators/use-async-data/templates.d.ts +20 -0
  55. package/dist/generators/use-async-data/templates.js +191 -0
  56. package/dist/generators/use-async-data/types.d.ts +4 -0
  57. package/dist/generators/use-async-data/types.js +4 -0
  58. package/dist/generators/use-fetch/generator.d.ts +5 -0
  59. package/dist/generators/use-fetch/generator.js +131 -0
  60. package/dist/generators/use-fetch/parser.d.ts +9 -0
  61. package/dist/generators/use-fetch/parser.js +282 -0
  62. package/dist/generators/use-fetch/runtime/useApiRequest.d.ts +46 -0
  63. package/dist/generators/use-fetch/runtime/useApiRequest.js +158 -0
  64. package/dist/generators/use-fetch/templates.d.ts +16 -0
  65. package/dist/generators/use-fetch/templates.js +169 -0
  66. package/dist/generators/use-fetch/types.d.ts +5 -0
  67. package/dist/generators/use-fetch/types.js +5 -0
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.js +213 -0
  70. package/docs/API-REFERENCE.md +887 -0
  71. package/docs/ARCHITECTURE.md +649 -0
  72. package/docs/DEVELOPMENT.md +918 -0
  73. package/docs/QUICK-START.md +323 -0
  74. package/docs/README.md +155 -0
  75. package/docs/TROUBLESHOOTING.md +881 -0
  76. package/eslint.config.js +72 -0
  77. package/package.json +65 -0
  78. package/src/cli/config.ts +140 -0
  79. package/src/cli/logger.ts +66 -0
  80. package/src/cli/logo.ts +25 -0
  81. package/src/cli/messages.ts +97 -0
  82. package/src/cli/prompts.ts +143 -0
  83. package/src/cli/types.ts +50 -0
  84. package/src/cli/utils.ts +49 -0
  85. package/src/generate.ts +57 -0
  86. package/src/generators/nuxt-server/bff-templates.ts +754 -0
  87. package/src/generators/nuxt-server/generator.ts +270 -0
  88. package/src/generators/nuxt-server/parser.ts +5 -0
  89. package/src/generators/nuxt-server/templates.ts +483 -0
  90. package/src/generators/nuxt-server/types.ts +5 -0
  91. package/src/generators/shared/parsers/heyapi-parser.ts +307 -0
  92. package/src/generators/shared/parsers/official-parser.ts +5 -0
  93. package/src/generators/shared/runtime/apiHelpers.ts +466 -0
  94. package/src/generators/shared/templates/api-callbacks-plugin.ts +352 -0
  95. package/src/generators/shared/types.ts +27 -0
  96. package/src/generators/tanstack-query/generator.ts +11 -0
  97. package/src/generators/use-async-data/generator.ts +204 -0
  98. package/src/generators/use-async-data/parser.ts +5 -0
  99. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +220 -0
  100. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +236 -0
  101. package/src/generators/use-async-data/templates.ts +250 -0
  102. package/src/generators/use-async-data/types.ts +4 -0
  103. package/src/generators/use-fetch/generator.ts +169 -0
  104. package/src/generators/use-fetch/parser.ts +341 -0
  105. package/src/generators/use-fetch/runtime/useApiRequest.ts +223 -0
  106. package/src/generators/use-fetch/templates.ts +214 -0
  107. package/src/generators/use-fetch/types.ts +5 -0
  108. package/src/index.ts +265 -0
  109. package/tsconfig.json +15 -0
@@ -0,0 +1,352 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Global API Callbacks Plugin
4
+ *
5
+ * ⚠️ IMPORTANT: This file is NEVER regenerated - your changes are safe!
6
+ *
7
+ * This plugin allows you to configure global callbacks for all API requests
8
+ * made with useFetch* and useAsyncData* composables.
9
+ *
10
+ * 📚 Three ways to control global callbacks:
11
+ *
12
+ * 1️⃣ OPTION 1: skipGlobalCallbacks (disable from the call)
13
+ * Skip global callbacks for specific requests
14
+ * Example:
15
+ * useFetchGetPets({ skipGlobalCallbacks: true })
16
+ * useFetchGetPets({ skipGlobalCallbacks: ['onSuccess'] })
17
+ *
18
+ * 2️⃣ OPTION 2: return false (disable from the plugin)
19
+ * Global callbacks can return false to prevent local callback execution
20
+ * Example:
21
+ * onError: (error) => {
22
+ * if (error.statusCode === 401) {
23
+ * navigateTo('/login');
24
+ * return false; // Don't execute local onError
25
+ * }
26
+ * }
27
+ *
28
+ * 3️⃣ OPTION 3: patterns (URL matching)
29
+ * Only apply callbacks to URLs matching specific patterns
30
+ * Example:
31
+ * patterns: ['/api/**', '/api/v2/*']
32
+ */
33
+
34
+ export default defineNuxtPlugin(() => {
35
+ // Uncomment and customize the callbacks you need
36
+ const globalCallbacks = {
37
+ // ========================================================================
38
+ // OPTION 3: URL Pattern Matching (OPTIONAL)
39
+ // ========================================================================
40
+ // Only apply global callbacks to URLs matching these patterns
41
+ // Use ** to match any path (including nested), * to match single segment
42
+ // If omitted or empty, callbacks apply to ALL requests
43
+ // patterns: ['/api/**'], // Only internal APIs
44
+ // patterns: ['/api/v1/**', '/api/v2/**'], // Multiple API versions
45
+ // patterns: ['**/public/**'], // All public endpoints
46
+ // ========================================================================
47
+ // onRequest: Called before every request
48
+ // ========================================================================
49
+ // Use cases:
50
+ // - Add authentication headers globally
51
+ // - Log all API calls
52
+ // - Add request timestamps
53
+ // - Modify request body/headers/params
54
+ // onRequest: (context) => {
55
+ // console.log(`[API] ${context.method} ${context.url}`);
56
+ //
57
+ // // Example 1: Add auth token to all requests
58
+ // // const token = useCookie('auth-token').value;
59
+ // // if (token) {
60
+ // // return {
61
+ // // headers: { 'Authorization': `Bearer ${token}` }
62
+ // // };
63
+ // // }
64
+ //
65
+ // // Example 2: Add request timestamp
66
+ // // return {
67
+ // // headers: { 'X-Request-Time': new Date().toISOString() }
68
+ // // };
69
+ //
70
+ // // Example 3: Block local onRequest (OPTION 2)
71
+ // // return false;
72
+ // },
73
+ // ========================================================================
74
+ // onSuccess: Called when request succeeds
75
+ // ========================================================================
76
+ // Use cases:
77
+ // - Show success notifications/toasts
78
+ // - Track successful operations
79
+ // - Update analytics
80
+ // - Cache responses
81
+ // onSuccess: (data, context) => {
82
+ // // Example 1: Success notification
83
+ // // const { $toast } = useNuxtApp();
84
+ // // $toast?.success('✅ Operation successful');
85
+ //
86
+ // // Example 2: Track analytics
87
+ // // trackEvent('api_success', { url: context?.url });
88
+ //
89
+ // // Example 3: Log response data (development only)
90
+ // // if (process.dev) {
91
+ // // console.log('API Response:', data);
92
+ // // }
93
+ //
94
+ // // Example 4: Cache specific responses
95
+ // // if (context?.url.includes('/api/config')) {
96
+ // // localStorage.setItem('app-config', JSON.stringify(data));
97
+ // // }
98
+ //
99
+ // // Example 5: Block local onSuccess for certain cases (OPTION 2)
100
+ // // if (data.status === 'pending_approval') {
101
+ // // $toast?.info('⏳ Awaiting approval');
102
+ // // return false; // Don't execute local onSuccess
103
+ // // }
104
+ // },
105
+ // ========================================================================
106
+ // onError: Called when request fails
107
+ // ========================================================================
108
+ // Use cases:
109
+ // - Handle authentication errors globally (401, 403)
110
+ // - Show error notifications
111
+ // - Log errors to monitoring service
112
+ // - Handle network errors
113
+ // - Retry logic
114
+ // onError: (error, context) => {
115
+ // console.error('[API Error]', error);
116
+ // const { $toast } = useNuxtApp();
117
+ //
118
+ // // Example 1: Handle authentication errors (OPTION 2)
119
+ // if (error.statusCode === 401) {
120
+ // $toast?.warning('⚠️ Session expired - redirecting to login...');
121
+ // navigateTo('/login');
122
+ // return false; // Don't execute local onError (avoiding duplicate messages)
123
+ // }
124
+ //
125
+ // // Example 2: Handle forbidden errors
126
+ // // if (error.statusCode === 403) {
127
+ // // $toast?.error('❌ Access denied');
128
+ // // navigateTo('/');
129
+ // // return false;
130
+ // // }
131
+ //
132
+ // // Example 3: Handle server errors
133
+ // // if (error.statusCode >= 500) {
134
+ // // $toast?.error('❌ Server error - please try again later');
135
+ // // // Log to monitoring service
136
+ // // // logErrorToSentry(error);
137
+ // // }
138
+ //
139
+ // // Example 4: Handle rate limiting
140
+ // // if (error.statusCode === 429) {
141
+ // // const retryAfter = error.data?.retryAfter || 60;
142
+ // // $toast?.warning(`⏳ Too many requests - retry in ${retryAfter}s`);
143
+ // // return false; // Don't show duplicate error in component
144
+ // // }
145
+ //
146
+ // // Example 5: Handle network errors
147
+ // // if (error.message === 'Network request failed') {
148
+ // // $toast?.error('❌ Network error - check your connection');
149
+ // // }
150
+ //
151
+ // // Example 6: Generic error notification
152
+ // // $toast?.error(`❌ ${error.message || 'An error occurred'}`);
153
+ //
154
+ // // Allow local onError to execute (return true or don't return)
155
+ // },
156
+ // ========================================================================
157
+ // onFinish: Called when request completes (success or error)
158
+ // ========================================================================
159
+ // Use cases:
160
+ // - Hide loading indicators
161
+ // - Track request completion
162
+ // - Clean up resources
163
+ // - Update request counters
164
+ // onFinish: (context) => {
165
+ // // Example 1: Track API call completion
166
+ // // const duration = Date.now() - context.startTime;
167
+ // // trackMetric('api_call_duration', duration, { url: context.url });
168
+ //
169
+ // // Example 2: Log request completion
170
+ // // console.log(
171
+ // // `[API] ${context.url} - ${context.success ? '✅ Success' : '❌ Failed'}`
172
+ // // );
173
+ //
174
+ // // Example 3: Update request counter
175
+ // // const store = useRequestStore();
176
+ // // store.decrementPendingRequests();
177
+ //
178
+ // // Example 4: Clean up global loading state
179
+ // // const { $loading } = useNuxtApp();
180
+ // // $loading?.hide();
181
+ // },
182
+ };
183
+
184
+ return {
185
+ provide: {
186
+ getGlobalApiCallbacks: () => globalCallbacks,
187
+ },
188
+ };
189
+ });
190
+
191
+ // ============================================================================
192
+ // USAGE EXAMPLES
193
+ // ============================================================================
194
+
195
+ /**
196
+ * Example 1: Use global callbacks (default behavior)
197
+ *
198
+ * const { data, error } = useFetchGetPets();
199
+ * // ✅ All global callbacks execute automatically
200
+ */
201
+
202
+ /**
203
+ * Example 2: Skip ALL global callbacks (OPTION 1)
204
+ *
205
+ * const { data, error } = useFetchGetPets({
206
+ * skipGlobalCallbacks: true,
207
+ * });
208
+ * // ❌ No global callbacks execute
209
+ */
210
+
211
+ /**
212
+ * Example 3: Skip SPECIFIC global callbacks (OPTION 1)
213
+ *
214
+ * const { data, error } = useFetchUpdatePet(id, pet, {
215
+ * skipGlobalCallbacks: ['onSuccess'], // Skip global onSuccess only
216
+ * onSuccess: (data) => {
217
+ * // Only this local callback executes
218
+ * console.log('Pet updated:', data);
219
+ * }
220
+ * });
221
+ * // ✅ Global onError still executes
222
+ * // ❌ Global onSuccess skipped
223
+ * // ✅ Local onSuccess executes
224
+ */
225
+
226
+ /**
227
+ * Example 4: Global callback prevents local execution (OPTION 2)
228
+ *
229
+ * // In plugin:
230
+ * onError: (error) => {
231
+ * if (error.statusCode === 401) {
232
+ * navigateTo('/login');
233
+ * return false; // Don't execute local onError
234
+ * }
235
+ * }
236
+ *
237
+ * // In component:
238
+ * const { data, error } = useFetchGetPets({
239
+ * onError: (error) => {
240
+ * // ❌ This won't execute for 401 errors (global returned false)
241
+ * // ✅ This executes for other errors (404, 500, etc.)
242
+ * console.error('Failed to load pets:', error);
243
+ * }
244
+ * });
245
+ */
246
+
247
+ /**
248
+ * Example 5: URL pattern matching (OPTION 3)
249
+ *
250
+ * // In plugin:
251
+ * patterns: ['/api/public/**']
252
+ *
253
+ * // In components:
254
+ * useFetchGetPets(); // URL: /api/public/pets ✅ Global callbacks execute
255
+ * useFetchGetUser(); // URL: /api/users/me ❌ Global callbacks skipped
256
+ * useFetchGetPublicConfig(); // URL: /api/public/config ✅ Global callbacks execute
257
+ */
258
+
259
+ /**
260
+ * Example 6: Combine all options
261
+ *
262
+ * // In plugin:
263
+ * patterns: ['/api/**'],
264
+ * onError: (error) => {
265
+ * if (error.statusCode === 401) return false;
266
+ * }
267
+ *
268
+ * // In component:
269
+ * const { data } = useFetchCreatePet(pet, {
270
+ * skipGlobalCallbacks: ['onSuccess'], // Skip global success toast
271
+ * onSuccess: (pet) => {
272
+ * // Show custom success message
273
+ * toast.success(`🐕 ${pet.name} added successfully!`);
274
+ * },
275
+ * onError: (error) => {
276
+ * // This won't execute for 401 (global returns false)
277
+ * // This executes for other errors
278
+ * console.error('Failed to create pet:', error);
279
+ * }
280
+ * });
281
+ */
282
+
283
+ // ============================================================================
284
+ // COMMON PATTERNS
285
+ // ============================================================================
286
+
287
+ /**
288
+ * Pattern 1: Toast notifications for all operations
289
+ *
290
+ * const globalCallbacks = {
291
+ * onSuccess: () => {
292
+ * useNuxtApp().$toast?.success('✅ Success');
293
+ * },
294
+ * onError: (error) => {
295
+ * if (error.statusCode === 401) {
296
+ * navigateTo('/login');
297
+ * return false;
298
+ * }
299
+ * useNuxtApp().$toast?.error(`❌ ${error.message}`);
300
+ * }
301
+ * };
302
+ */
303
+
304
+ /**
305
+ * Pattern 2: Authentication + logging
306
+ *
307
+ * const globalCallbacks = {
308
+ * onRequest: (context) => {
309
+ * console.log(`[API] ${context.method} ${context.url}`);
310
+ * const token = useCookie('auth-token').value;
311
+ * if (token) {
312
+ * return { headers: { 'Authorization': `Bearer ${token}` } };
313
+ * }
314
+ * },
315
+ * onError: (error) => {
316
+ * if (error.statusCode === 401) {
317
+ * useCookie('auth-token').value = null;
318
+ * navigateTo('/login');
319
+ * return false;
320
+ * }
321
+ * }
322
+ * };
323
+ */
324
+
325
+ /**
326
+ * Pattern 3: Analytics tracking
327
+ *
328
+ * const globalCallbacks = {
329
+ * onSuccess: (data, context) => {
330
+ * trackEvent('api_success', { endpoint: context?.url });
331
+ * },
332
+ * onError: (error, context) => {
333
+ * trackEvent('api_error', {
334
+ * endpoint: context?.url,
335
+ * statusCode: error.statusCode
336
+ * });
337
+ * }
338
+ * };
339
+ */
340
+
341
+ /**
342
+ * Pattern 4: Loading states
343
+ *
344
+ * const globalCallbacks = {
345
+ * onRequest: () => {
346
+ * useLoadingStore().increment();
347
+ * },
348
+ * onFinish: () => {
349
+ * useLoadingStore().decrement();
350
+ * }
351
+ * };
352
+ */
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shared types used by multiple generators
3
+ */
4
+
5
+ export interface MethodInfo {
6
+ name: string; // 'addPet'
7
+ composableName: string; // 'useFetchAddPet' or 'useAsyncDataAddPet'
8
+ requestType?: string; // 'AddPetRequest'
9
+ responseType: string; // 'Pet'
10
+ httpMethod: string; // 'POST' | 'GET' | 'PUT' | 'DELETE' | 'PATCH'
11
+ path: string; // '/pet' or '/pet/{petId}'
12
+ hasBody: boolean; // true if method uses body
13
+ bodyField?: string; // 'pet' (from params.pet)
14
+ hasQueryParams: boolean; // true if has query parameters
15
+ queryParams: string[]; // ['status', 'tags']
16
+ pathParams: string[]; // ['petId']
17
+ headers: Record<string, string>; // Default headers
18
+ description?: string; // Method description from comments
19
+ hasRawMethod: boolean; // true if xxxRaw method exists
20
+ rawMethodName?: string; // 'addPetRaw'
21
+ paramsShape?: 'flat' | 'nested'; // 'flat' for official generator, 'nested' for Hey API
22
+ }
23
+
24
+ export interface ApiClassInfo {
25
+ className: string; // 'PetApi'
26
+ methods: MethodInfo[];
27
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Placeholder for TanStack Query composables generator
3
+ * TODO: Implement TanStack Query generation
4
+ */
5
+ export function generateTanstackQueryComposables(inputDir: string, outputDir: string): void {
6
+ console.log('\n📦 @tanstack/vue-query generator\n');
7
+ console.log(` Input: ${inputDir}`);
8
+ console.log(` Output: ${outputDir}`);
9
+ console.log('\n⚠️ This generator is not yet implemented.');
10
+ console.log(' Coming soon!\n');
11
+ }
@@ -0,0 +1,204 @@
1
+ import * as path from 'path';
2
+ import fs from 'fs-extra';
3
+ import { fileURLToPath } from 'url';
4
+ import { format } from 'prettier';
5
+ import {
6
+ getApiFiles as getApiFilesOfficial,
7
+ parseApiFile as parseApiFileOfficial,
8
+ } from './parser.js';
9
+ import {
10
+ getApiFiles as getApiFilesHeyApi,
11
+ parseApiFile as parseApiFileHeyApi,
12
+ } from '../shared/parsers/heyapi-parser.js';
13
+ import {
14
+ generateComposableFile,
15
+ generateRawComposableFile,
16
+ generateIndexFile,
17
+ type GenerateOptions,
18
+ } from './templates.js';
19
+ import type { MethodInfo } from './types.js';
20
+ import { p, logSuccess, logError } from '../../cli/logger.js';
21
+
22
+ /**
23
+ * Main function to generate useAsyncData composables
24
+ */
25
+ export async function generateUseAsyncDataComposables(
26
+ inputDir: string,
27
+ outputDir: string,
28
+ options?: GenerateOptions
29
+ ): Promise<void> {
30
+ const mainSpinner = p.spinner();
31
+
32
+ // Select parser based on chosen backend
33
+ const getApiFiles = options?.backend === 'heyapi' ? getApiFilesHeyApi : getApiFilesOfficial;
34
+ const parseApiFile = options?.backend === 'heyapi' ? parseApiFileHeyApi : parseApiFileOfficial;
35
+
36
+ // 1. Get all API files
37
+ mainSpinner.start('Scanning API files');
38
+ const apiFiles = getApiFiles(inputDir);
39
+ mainSpinner.stop(`Found ${apiFiles.length} API file(s)`);
40
+
41
+ if (apiFiles.length === 0) {
42
+ throw new Error('No API files found in the input directory');
43
+ }
44
+
45
+ // 2. Parse each API file
46
+ mainSpinner.start('Parsing API files');
47
+ const allMethods: MethodInfo[] = [];
48
+
49
+ for (const file of apiFiles) {
50
+ const fileName = path.basename(file);
51
+ try {
52
+ const apiInfo = parseApiFile(file);
53
+ allMethods.push(...apiInfo.methods);
54
+ } catch (error) {
55
+ logError(`Error parsing ${fileName}: ${error}`);
56
+ }
57
+ }
58
+
59
+ mainSpinner.stop(`Found ${allMethods.length} methods to generate`);
60
+
61
+ if (allMethods.length === 0) {
62
+ p.log.warn('No methods found to generate');
63
+ return;
64
+ }
65
+
66
+ // 3. Clean and create output directories
67
+ mainSpinner.start('Preparing output directories');
68
+ const composablesDir = path.join(outputDir, 'composables');
69
+ const runtimeDir = path.join(outputDir, 'runtime');
70
+ const sharedRuntimeDir = path.join(outputDir, 'shared', 'runtime');
71
+ await fs.emptyDir(composablesDir);
72
+ await fs.ensureDir(runtimeDir);
73
+ await fs.ensureDir(sharedRuntimeDir);
74
+ mainSpinner.stop('Output directories ready');
75
+
76
+ // 4. Copy runtime helpers
77
+ mainSpinner.start('Copying runtime files');
78
+ // Derive __dirname equivalent for ESM
79
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
80
+ // When compiled, __dirname points to dist/generators/use-async-data/
81
+ // We need to go back to src/ to get the original .ts files
82
+
83
+ // Copy useApiAsyncData.ts
84
+ const runtimeSource = path.resolve(
85
+ __dirname,
86
+ '../../../src/generators/use-async-data/runtime/useApiAsyncData.ts'
87
+ );
88
+ const runtimeDest = path.join(runtimeDir, 'useApiAsyncData.ts');
89
+ await fs.copyFile(runtimeSource, runtimeDest);
90
+
91
+ // Copy useApiAsyncDataRaw.ts
92
+ const runtimeRawSource = path.resolve(
93
+ __dirname,
94
+ '../../../src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts'
95
+ );
96
+ const runtimeRawDest = path.join(runtimeDir, 'useApiAsyncDataRaw.ts');
97
+ await fs.copyFile(runtimeRawSource, runtimeRawDest);
98
+
99
+ // Copy shared apiHelpers.ts
100
+ const sharedHelpersSource = path.resolve(
101
+ __dirname,
102
+ '../../../src/generators/shared/runtime/apiHelpers.ts'
103
+ );
104
+ const sharedHelpersDest = path.join(sharedRuntimeDir, 'apiHelpers.ts');
105
+ await fs.copyFile(sharedHelpersSource, sharedHelpersDest);
106
+ mainSpinner.stop('Runtime files copied');
107
+
108
+ // 5. Calculate relative import path from composables to APIs
109
+ const relativePath = calculateRelativeImportPath(composablesDir, inputDir);
110
+
111
+ // 6. Generate each composable (normal + Raw if available)
112
+ mainSpinner.start('Generating composables');
113
+ let successCount = 0;
114
+ let errorCount = 0;
115
+ const generatedComposableNames: string[] = [];
116
+
117
+ for (const method of allMethods) {
118
+ // Generate normal version
119
+ try {
120
+ const code = generateComposableFile(method, relativePath, options);
121
+ const formattedCode = await formatCode(code);
122
+ const composableName = method.composableName.replace(/^useFetch/, 'useAsyncData');
123
+ const fileName = `${composableName}.ts`;
124
+ const filePath = path.join(composablesDir, fileName);
125
+
126
+ await fs.writeFile(filePath, formattedCode, 'utf-8');
127
+ generatedComposableNames.push(composableName);
128
+ successCount++;
129
+ } catch (error) {
130
+ logError(`Error generating ${method.composableName}: ${error}`);
131
+ errorCount++;
132
+ }
133
+
134
+ // Generate Raw version if available
135
+ if (method.hasRawMethod && method.rawMethodName) {
136
+ try {
137
+ const code = generateRawComposableFile(method, relativePath, options);
138
+ const formattedCode = await formatCode(code);
139
+ const composableName = `useAsyncData${method.rawMethodName.replace(/Raw$/, '')}Raw`;
140
+ const fileName = `${composableName}.ts`;
141
+ const filePath = path.join(composablesDir, fileName);
142
+
143
+ await fs.writeFile(filePath, formattedCode, 'utf-8');
144
+ generatedComposableNames.push(composableName);
145
+ successCount++;
146
+ } catch (error) {
147
+ logError(`Error generating ${method.composableName} (Raw): ${error}`);
148
+ errorCount++;
149
+ }
150
+ }
151
+ }
152
+
153
+ // 7. Generate index.ts
154
+ const indexCode = generateIndexFile(generatedComposableNames);
155
+ const formattedIndex = await formatCode(indexCode);
156
+ await fs.writeFile(path.join(outputDir, 'index.ts'), formattedIndex, 'utf-8');
157
+ mainSpinner.stop(`Generated ${successCount} composables`);
158
+
159
+ // 8. Summary
160
+ if (errorCount > 0) {
161
+ p.log.warn(`Completed with ${errorCount} error(s)`);
162
+ }
163
+ logSuccess(`Generated ${successCount} useAsyncData composable(s) in ${outputDir}`);
164
+ }
165
+
166
+ /**
167
+ * Calculate relative import path from composables to APIs
168
+ */
169
+ function calculateRelativeImportPath(composablesDir: string, inputDir: string): string {
170
+ // Import from the root index.ts which exports apis, models, and runtime
171
+ let relativePath = path.relative(composablesDir, inputDir);
172
+
173
+ // Convert Windows paths to Unix-style
174
+ relativePath = relativePath.replace(/\\/g, '/');
175
+
176
+ // Ensure it starts with './' or '../'
177
+ if (!relativePath.startsWith('.')) {
178
+ relativePath = './' + relativePath;
179
+ }
180
+
181
+ // Remove .ts extension and trailing /
182
+ relativePath = relativePath.replace(/\.ts$/, '').replace(/\/$/, '');
183
+
184
+ return relativePath;
185
+ }
186
+
187
+ /**
188
+ * Format code with Prettier
189
+ */
190
+ async function formatCode(code: string): Promise<string> {
191
+ try {
192
+ return await format(code, {
193
+ parser: 'typescript',
194
+ semi: true,
195
+ singleQuote: true,
196
+ trailingComma: 'es5',
197
+ printWidth: 80,
198
+ tabWidth: 2,
199
+ });
200
+ } catch {
201
+ p.log.warn('Could not format code with Prettier');
202
+ return code;
203
+ }
204
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Re-export all parser functionality from use-fetch
3
+ * The parsing logic is the same for both generators
4
+ */
5
+ export * from '../use-fetch/parser.js';