atlas-pipeline-mcp 1.0.25 → 1.0.27
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.
- package/README.md +50 -4
- package/dist/mcp.js +182 -0
- package/dist/mcp.js.map +1 -1
- package/dist/tools/animation-studio.d.ts +83 -0
- package/dist/tools/animation-studio.d.ts.map +1 -0
- package/dist/tools/animation-studio.js +1064 -0
- package/dist/tools/animation-studio.js.map +1 -0
- package/dist/tools/api-integration-helper.d.ts +141 -0
- package/dist/tools/api-integration-helper.d.ts.map +1 -0
- package/dist/tools/api-integration-helper.js +907 -0
- package/dist/tools/api-integration-helper.js.map +1 -0
- package/dist/tools/css-architecture-wizard.d.ts +86 -0
- package/dist/tools/css-architecture-wizard.d.ts.map +1 -0
- package/dist/tools/css-architecture-wizard.js +790 -0
- package/dist/tools/css-architecture-wizard.js.map +1 -0
- package/dist/tools/frontend-performance-doctor.d.ts +108 -0
- package/dist/tools/frontend-performance-doctor.d.ts.map +1 -0
- package/dist/tools/frontend-performance-doctor.js +731 -0
- package/dist/tools/frontend-performance-doctor.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Integration Helper
|
|
3
|
+
*
|
|
4
|
+
* Helps frontend developers integrate with backend APIs:
|
|
5
|
+
* - Generates TypeScript types from API responses
|
|
6
|
+
* - Creates React Query / SWR / TanStack Query hooks
|
|
7
|
+
* - Generates mock data for testing
|
|
8
|
+
* - Creates error handling wrappers
|
|
9
|
+
* - Builds API client with interceptors
|
|
10
|
+
* - Generates OpenAPI types
|
|
11
|
+
* - Creates Zod validation schemas
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { logger } from '../utils.js';
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Validation Schema
|
|
17
|
+
// ============================================================================
|
|
18
|
+
export const APIEndpointSchema = z.object({
|
|
19
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
|
|
20
|
+
path: z.string().min(1),
|
|
21
|
+
description: z.string().optional(),
|
|
22
|
+
requestBody: z.record(z.any()).optional(),
|
|
23
|
+
responseBody: z.record(z.any()),
|
|
24
|
+
headers: z.record(z.string()).optional(),
|
|
25
|
+
queryParams: z.record(z.string()).optional()
|
|
26
|
+
});
|
|
27
|
+
export const APIIntegrationRequestSchema = z.object({
|
|
28
|
+
endpoints: z.array(APIEndpointSchema).min(1),
|
|
29
|
+
baseUrl: z.string().url(),
|
|
30
|
+
library: z.enum(['react-query', 'swr', 'axios', 'fetch', 'tanstack-query']),
|
|
31
|
+
generateTypes: z.boolean().optional().default(true),
|
|
32
|
+
generateMocks: z.boolean().optional().default(true),
|
|
33
|
+
generateZodSchemas: z.boolean().optional().default(true),
|
|
34
|
+
authType: z.enum(['bearer', 'api-key', 'basic', 'oauth2', 'none']).optional().default('bearer'),
|
|
35
|
+
framework: z.enum(['react', 'vue', 'svelte', 'next']).optional().default('react')
|
|
36
|
+
});
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Type Inference Helpers
|
|
39
|
+
// ============================================================================
|
|
40
|
+
function inferTypeFromValue(value, key = '') {
|
|
41
|
+
if (value === null)
|
|
42
|
+
return 'null';
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
return 'undefined';
|
|
45
|
+
const type = typeof value;
|
|
46
|
+
if (type === 'string') {
|
|
47
|
+
// Check for common patterns
|
|
48
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(value))
|
|
49
|
+
return 'string'; // Date ISO string
|
|
50
|
+
if (/^[a-f0-9-]{36}$/i.test(value))
|
|
51
|
+
return 'string'; // UUID
|
|
52
|
+
if (value.includes('@'))
|
|
53
|
+
return 'string'; // Email
|
|
54
|
+
return 'string';
|
|
55
|
+
}
|
|
56
|
+
if (type === 'number') {
|
|
57
|
+
return Number.isInteger(value) ? 'number' : 'number';
|
|
58
|
+
}
|
|
59
|
+
if (type === 'boolean')
|
|
60
|
+
return 'boolean';
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
if (value.length === 0)
|
|
63
|
+
return 'unknown[]';
|
|
64
|
+
const itemType = inferTypeFromValue(value[0], key);
|
|
65
|
+
return `${itemType}[]`;
|
|
66
|
+
}
|
|
67
|
+
if (type === 'object') {
|
|
68
|
+
return 'object'; // Will be expanded later
|
|
69
|
+
}
|
|
70
|
+
return 'unknown';
|
|
71
|
+
}
|
|
72
|
+
function generateTypeFromObject(obj, name, depth = 0) {
|
|
73
|
+
const indent = ' '.repeat(depth);
|
|
74
|
+
const lines = [];
|
|
75
|
+
lines.push(`${indent}export interface ${name} {`);
|
|
76
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
77
|
+
const isOptional = key.endsWith('?') || value === null;
|
|
78
|
+
const cleanKey = key.replace('?', '');
|
|
79
|
+
const optionalMark = isOptional ? '?' : '';
|
|
80
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
81
|
+
// Nested object - generate inline or reference
|
|
82
|
+
const nestedType = `${name}${toPascalCase(cleanKey)}`;
|
|
83
|
+
lines.push(`${indent} ${cleanKey}${optionalMark}: ${nestedType};`);
|
|
84
|
+
}
|
|
85
|
+
else if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object') {
|
|
86
|
+
// Array of objects
|
|
87
|
+
const itemType = `${name}${toPascalCase(cleanKey)}Item`;
|
|
88
|
+
lines.push(`${indent} ${cleanKey}${optionalMark}: ${itemType}[];`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const inferredType = inferTypeFromValue(value, cleanKey);
|
|
92
|
+
lines.push(`${indent} ${cleanKey}${optionalMark}: ${inferredType};`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
lines.push(`${indent}}`);
|
|
96
|
+
// Generate nested interfaces
|
|
97
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
98
|
+
const cleanKey = key.replace('?', '');
|
|
99
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
100
|
+
lines.push('');
|
|
101
|
+
lines.push(generateTypeFromObject(value, `${name}${toPascalCase(cleanKey)}`, depth));
|
|
102
|
+
}
|
|
103
|
+
else if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object') {
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push(generateTypeFromObject(value[0], `${name}${toPascalCase(cleanKey)}Item`, depth));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return lines.join('\n');
|
|
109
|
+
}
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// Main Functions
|
|
112
|
+
// ============================================================================
|
|
113
|
+
export async function generateAPIIntegration(request) {
|
|
114
|
+
const validated = APIIntegrationRequestSchema.parse(request);
|
|
115
|
+
logger.info(`Generating API integration for ${validated.endpoints.length} endpoints`);
|
|
116
|
+
// Generate TypeScript types
|
|
117
|
+
const types = generateTypes(validated.endpoints);
|
|
118
|
+
// Generate API client
|
|
119
|
+
const apiClient = generateAPIClient(validated);
|
|
120
|
+
// Generate hooks based on library
|
|
121
|
+
const hooks = generateHooks(validated);
|
|
122
|
+
// Generate error handling
|
|
123
|
+
const errorHandling = generateErrorHandling(validated);
|
|
124
|
+
// Generate mocks if requested
|
|
125
|
+
const mocks = validated.generateMocks ? generateMocks(validated.endpoints) : undefined;
|
|
126
|
+
// Generate Zod schemas if requested
|
|
127
|
+
const zodSchemas = validated.generateZodSchemas ? generateZodSchemas(validated.endpoints) : undefined;
|
|
128
|
+
// Generate usage examples
|
|
129
|
+
const usageExamples = generateUsageExamples(validated);
|
|
130
|
+
// Generate testing guide
|
|
131
|
+
const testingGuide = generateTestingGuide(validated);
|
|
132
|
+
return {
|
|
133
|
+
types,
|
|
134
|
+
hooks,
|
|
135
|
+
apiClient,
|
|
136
|
+
mocks,
|
|
137
|
+
zodSchemas,
|
|
138
|
+
errorHandling,
|
|
139
|
+
usageExamples,
|
|
140
|
+
testingGuide
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// ============================================================================
|
|
144
|
+
// Generator Functions
|
|
145
|
+
// ============================================================================
|
|
146
|
+
function generateTypes(endpoints) {
|
|
147
|
+
const types = [];
|
|
148
|
+
types.push(`// ============================================================================`);
|
|
149
|
+
types.push(`// API Types - Auto-generated`);
|
|
150
|
+
types.push(`// ============================================================================`);
|
|
151
|
+
types.push('');
|
|
152
|
+
for (const endpoint of endpoints) {
|
|
153
|
+
const pathName = pathToName(endpoint.path);
|
|
154
|
+
// Request type (for POST, PUT, PATCH)
|
|
155
|
+
if (endpoint.requestBody && ['POST', 'PUT', 'PATCH'].includes(endpoint.method)) {
|
|
156
|
+
types.push(generateTypeFromObject(endpoint.requestBody, `${pathName}Request`));
|
|
157
|
+
types.push('');
|
|
158
|
+
}
|
|
159
|
+
// Response type
|
|
160
|
+
types.push(generateTypeFromObject(endpoint.responseBody, `${pathName}Response`));
|
|
161
|
+
types.push('');
|
|
162
|
+
// Query params type
|
|
163
|
+
if (endpoint.queryParams) {
|
|
164
|
+
const queryInterface = Object.entries(endpoint.queryParams)
|
|
165
|
+
.map(([key, desc]) => ` ${key}?: string; // ${desc}`)
|
|
166
|
+
.join('\n');
|
|
167
|
+
types.push(`export interface ${pathName}QueryParams {\n${queryInterface}\n}`);
|
|
168
|
+
types.push('');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Add common types
|
|
172
|
+
types.push(`// Common Types`);
|
|
173
|
+
types.push(`export interface APIError {
|
|
174
|
+
message: string;
|
|
175
|
+
code: string;
|
|
176
|
+
status: number;
|
|
177
|
+
details?: Record<string, string[]>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface PaginatedResponse<T> {
|
|
181
|
+
data: T[];
|
|
182
|
+
pagination: {
|
|
183
|
+
page: number;
|
|
184
|
+
limit: number;
|
|
185
|
+
total: number;
|
|
186
|
+
totalPages: number;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface APIResponse<T> {
|
|
191
|
+
data: T;
|
|
192
|
+
message?: string;
|
|
193
|
+
success: boolean;
|
|
194
|
+
}`);
|
|
195
|
+
return types.join('\n');
|
|
196
|
+
}
|
|
197
|
+
function generateAPIClient(request) {
|
|
198
|
+
const { baseUrl, authType } = request;
|
|
199
|
+
return `// ============================================================================
|
|
200
|
+
// API Client - Auto-generated
|
|
201
|
+
// ============================================================================
|
|
202
|
+
|
|
203
|
+
const API_BASE_URL = '${baseUrl}';
|
|
204
|
+
|
|
205
|
+
// Token storage (use secure storage in production)
|
|
206
|
+
let authToken: string | null = null;
|
|
207
|
+
|
|
208
|
+
export const setAuthToken = (token: string) => {
|
|
209
|
+
authToken = token;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const clearAuthToken = () => {
|
|
213
|
+
authToken = null;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Request interceptor
|
|
217
|
+
const getHeaders = (customHeaders?: Record<string, string>): HeadersInit => {
|
|
218
|
+
const headers: Record<string, string> = {
|
|
219
|
+
'Content-Type': 'application/json',
|
|
220
|
+
...customHeaders,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
${authType === 'bearer' ? `if (authToken) {
|
|
224
|
+
headers['Authorization'] = \`Bearer \${authToken}\`;
|
|
225
|
+
}` : ''}
|
|
226
|
+
${authType === 'api-key' ? `if (authToken) {
|
|
227
|
+
headers['X-API-Key'] = authToken;
|
|
228
|
+
}` : ''}
|
|
229
|
+
|
|
230
|
+
return headers;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Response interceptor
|
|
234
|
+
const handleResponse = async <T>(response: Response): Promise<T> => {
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
const error = await response.json().catch(() => ({
|
|
237
|
+
message: response.statusText,
|
|
238
|
+
code: 'UNKNOWN_ERROR',
|
|
239
|
+
status: response.status,
|
|
240
|
+
}));
|
|
241
|
+
|
|
242
|
+
// Handle specific error codes
|
|
243
|
+
if (response.status === 401) {
|
|
244
|
+
// Token expired - trigger refresh or logout
|
|
245
|
+
clearAuthToken();
|
|
246
|
+
window.dispatchEvent(new CustomEvent('auth:expired'));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return response.json();
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Generic request function
|
|
256
|
+
export async function apiRequest<T>(
|
|
257
|
+
endpoint: string,
|
|
258
|
+
options: RequestInit = {}
|
|
259
|
+
): Promise<T> {
|
|
260
|
+
const url = \`\${API_BASE_URL}\${endpoint}\`;
|
|
261
|
+
|
|
262
|
+
const response = await fetch(url, {
|
|
263
|
+
...options,
|
|
264
|
+
headers: getHeaders(options.headers as Record<string, string>),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return handleResponse<T>(response);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Convenience methods
|
|
271
|
+
export const api = {
|
|
272
|
+
get: <T>(endpoint: string, params?: Record<string, string>) => {
|
|
273
|
+
const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
|
|
274
|
+
return apiRequest<T>(\`\${endpoint}\${queryString}\`, { method: 'GET' });
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
post: <T>(endpoint: string, data?: unknown) => {
|
|
278
|
+
return apiRequest<T>(endpoint, {
|
|
279
|
+
method: 'POST',
|
|
280
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
281
|
+
});
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
put: <T>(endpoint: string, data?: unknown) => {
|
|
285
|
+
return apiRequest<T>(endpoint, {
|
|
286
|
+
method: 'PUT',
|
|
287
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
288
|
+
});
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
patch: <T>(endpoint: string, data?: unknown) => {
|
|
292
|
+
return apiRequest<T>(endpoint, {
|
|
293
|
+
method: 'PATCH',
|
|
294
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
295
|
+
});
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
delete: <T>(endpoint: string) => {
|
|
299
|
+
return apiRequest<T>(endpoint, { method: 'DELETE' });
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export default api;`;
|
|
304
|
+
}
|
|
305
|
+
function generateHooks(request) {
|
|
306
|
+
const { library, endpoints } = request;
|
|
307
|
+
switch (library) {
|
|
308
|
+
case 'react-query':
|
|
309
|
+
case 'tanstack-query':
|
|
310
|
+
return generateTanStackQueryHooks(endpoints);
|
|
311
|
+
case 'swr':
|
|
312
|
+
return generateSWRHooks(endpoints);
|
|
313
|
+
default:
|
|
314
|
+
return generateFetchHooks(endpoints);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function generateTanStackQueryHooks(endpoints) {
|
|
318
|
+
const hooks = [];
|
|
319
|
+
hooks.push(`// ============================================================================`);
|
|
320
|
+
hooks.push(`// React Query / TanStack Query Hooks - Auto-generated`);
|
|
321
|
+
hooks.push(`// ============================================================================`);
|
|
322
|
+
hooks.push('');
|
|
323
|
+
hooks.push(`import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';`);
|
|
324
|
+
hooks.push(`import { api } from './api-client';`);
|
|
325
|
+
hooks.push(`import type { ${endpoints.map(e => `${pathToName(e.path)}Response`).join(', ')} } from './types';`);
|
|
326
|
+
hooks.push('');
|
|
327
|
+
hooks.push(`// Query Keys`);
|
|
328
|
+
hooks.push(`export const queryKeys = {`);
|
|
329
|
+
for (const endpoint of endpoints) {
|
|
330
|
+
const name = pathToName(endpoint.path);
|
|
331
|
+
const keyName = toCamelCase(name);
|
|
332
|
+
hooks.push(` ${keyName}: ['${keyName}'] as const,`);
|
|
333
|
+
}
|
|
334
|
+
hooks.push(`};`);
|
|
335
|
+
hooks.push('');
|
|
336
|
+
for (const endpoint of endpoints) {
|
|
337
|
+
const name = pathToName(endpoint.path);
|
|
338
|
+
const hookName = toCamelCase(name);
|
|
339
|
+
if (endpoint.method === 'GET') {
|
|
340
|
+
hooks.push(`// ${endpoint.description || `Fetch ${name}`}
|
|
341
|
+
export function use${name}(${hasPathParams(endpoint.path) ? 'id: string, ' : ''}options?: { enabled?: boolean }) {
|
|
342
|
+
return useQuery({
|
|
343
|
+
queryKey: [...queryKeys.${hookName}${hasPathParams(endpoint.path) ? ', id' : ''}],
|
|
344
|
+
queryFn: () => api.get<${name}Response>('${endpoint.path}'${hasPathParams(endpoint.path) ? '.replace(":id", id)' : ''}),
|
|
345
|
+
...options,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
`);
|
|
349
|
+
}
|
|
350
|
+
else if (['POST', 'PUT', 'PATCH'].includes(endpoint.method)) {
|
|
351
|
+
const inputType = endpoint.requestBody ? `${name}Request` : 'unknown';
|
|
352
|
+
hooks.push(`// ${endpoint.description || `${endpoint.method} ${name}`}
|
|
353
|
+
export function use${name}Mutation() {
|
|
354
|
+
const queryClient = useQueryClient();
|
|
355
|
+
|
|
356
|
+
return useMutation({
|
|
357
|
+
mutationFn: (data: ${inputType}) =>
|
|
358
|
+
api.${endpoint.method.toLowerCase()}<${name}Response>('${endpoint.path}', data),
|
|
359
|
+
onSuccess: () => {
|
|
360
|
+
// Invalidate related queries
|
|
361
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.${hookName} });
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
`);
|
|
366
|
+
}
|
|
367
|
+
else if (endpoint.method === 'DELETE') {
|
|
368
|
+
hooks.push(`// ${endpoint.description || `Delete ${name}`}
|
|
369
|
+
export function useDelete${name}() {
|
|
370
|
+
const queryClient = useQueryClient();
|
|
371
|
+
|
|
372
|
+
return useMutation({
|
|
373
|
+
mutationFn: (id: string) =>
|
|
374
|
+
api.delete<${name}Response>(\`${endpoint.path.replace(':id', '${id}')}\`),
|
|
375
|
+
onSuccess: () => {
|
|
376
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.${hookName} });
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return hooks.join('\n');
|
|
384
|
+
}
|
|
385
|
+
function generateSWRHooks(endpoints) {
|
|
386
|
+
const hooks = [];
|
|
387
|
+
hooks.push(`// ============================================================================`);
|
|
388
|
+
hooks.push(`// SWR Hooks - Auto-generated`);
|
|
389
|
+
hooks.push(`// ============================================================================`);
|
|
390
|
+
hooks.push('');
|
|
391
|
+
hooks.push(`import useSWR from 'swr';`);
|
|
392
|
+
hooks.push(`import useSWRMutation from 'swr/mutation';`);
|
|
393
|
+
hooks.push(`import { api } from './api-client';`);
|
|
394
|
+
hooks.push('');
|
|
395
|
+
hooks.push(`// SWR Fetcher`);
|
|
396
|
+
hooks.push(`const fetcher = <T>(url: string): Promise<T> => api.get<T>(url);`);
|
|
397
|
+
hooks.push('');
|
|
398
|
+
for (const endpoint of endpoints) {
|
|
399
|
+
const name = pathToName(endpoint.path);
|
|
400
|
+
const hookName = toCamelCase(name);
|
|
401
|
+
if (endpoint.method === 'GET') {
|
|
402
|
+
hooks.push(`// ${endpoint.description || `Fetch ${name}`}
|
|
403
|
+
export function use${name}(${hasPathParams(endpoint.path) ? 'id: string' : ''}) {
|
|
404
|
+
return useSWR<${name}Response>(
|
|
405
|
+
${hasPathParams(endpoint.path) ? `id ? \`${endpoint.path.replace(':id', '${id}')}\` : null` : `'${endpoint.path}'`},
|
|
406
|
+
fetcher,
|
|
407
|
+
{
|
|
408
|
+
revalidateOnFocus: false,
|
|
409
|
+
dedupingInterval: 5000,
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
`);
|
|
414
|
+
}
|
|
415
|
+
else if (['POST', 'PUT', 'PATCH'].includes(endpoint.method)) {
|
|
416
|
+
hooks.push(`// ${endpoint.description || `${endpoint.method} ${name}`}
|
|
417
|
+
export function use${name}Mutation() {
|
|
418
|
+
return useSWRMutation(
|
|
419
|
+
'${endpoint.path}',
|
|
420
|
+
(url: string, { arg }: { arg: ${name}Request }) =>
|
|
421
|
+
api.${endpoint.method.toLowerCase()}<${name}Response>(url, arg)
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return hooks.join('\n');
|
|
428
|
+
}
|
|
429
|
+
function generateFetchHooks(endpoints) {
|
|
430
|
+
const hooks = [];
|
|
431
|
+
hooks.push(`// ============================================================================`);
|
|
432
|
+
hooks.push(`// Custom Fetch Hooks - Auto-generated`);
|
|
433
|
+
hooks.push(`// ============================================================================`);
|
|
434
|
+
hooks.push('');
|
|
435
|
+
hooks.push(`import { useState, useEffect, useCallback } from 'react';`);
|
|
436
|
+
hooks.push(`import { api } from './api-client';`);
|
|
437
|
+
hooks.push('');
|
|
438
|
+
hooks.push(`// Generic fetch hook
|
|
439
|
+
interface UseFetchResult<T> {
|
|
440
|
+
data: T | null;
|
|
441
|
+
loading: boolean;
|
|
442
|
+
error: Error | null;
|
|
443
|
+
refetch: () => void;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function useFetch<T>(fetchFn: () => Promise<T>): UseFetchResult<T> {
|
|
447
|
+
const [data, setData] = useState<T | null>(null);
|
|
448
|
+
const [loading, setLoading] = useState(true);
|
|
449
|
+
const [error, setError] = useState<Error | null>(null);
|
|
450
|
+
|
|
451
|
+
const fetch = useCallback(async () => {
|
|
452
|
+
setLoading(true);
|
|
453
|
+
setError(null);
|
|
454
|
+
try {
|
|
455
|
+
const result = await fetchFn();
|
|
456
|
+
setData(result);
|
|
457
|
+
} catch (err) {
|
|
458
|
+
setError(err instanceof Error ? err : new Error('Unknown error'));
|
|
459
|
+
} finally {
|
|
460
|
+
setLoading(false);
|
|
461
|
+
}
|
|
462
|
+
}, [fetchFn]);
|
|
463
|
+
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
fetch();
|
|
466
|
+
}, [fetch]);
|
|
467
|
+
|
|
468
|
+
return { data, loading, error, refetch: fetch };
|
|
469
|
+
}
|
|
470
|
+
`);
|
|
471
|
+
for (const endpoint of endpoints) {
|
|
472
|
+
const name = pathToName(endpoint.path);
|
|
473
|
+
if (endpoint.method === 'GET') {
|
|
474
|
+
hooks.push(`// ${endpoint.description || `Fetch ${name}`}
|
|
475
|
+
export function use${name}(${hasPathParams(endpoint.path) ? 'id: string' : ''}) {
|
|
476
|
+
return useFetch(() => api.get<${name}Response>('${endpoint.path}'${hasPathParams(endpoint.path) ? '.replace(":id", id)' : ''}));
|
|
477
|
+
}
|
|
478
|
+
`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return hooks.join('\n');
|
|
482
|
+
}
|
|
483
|
+
function generateErrorHandling(request) {
|
|
484
|
+
return `// ============================================================================
|
|
485
|
+
// Error Handling - Auto-generated
|
|
486
|
+
// ============================================================================
|
|
487
|
+
|
|
488
|
+
import { toast } from 'sonner'; // or your preferred toast library
|
|
489
|
+
|
|
490
|
+
export interface APIError {
|
|
491
|
+
message: string;
|
|
492
|
+
code: string;
|
|
493
|
+
status: number;
|
|
494
|
+
details?: Record<string, string[]>;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Error codes mapping
|
|
498
|
+
const ERROR_MESSAGES: Record<string, string> = {
|
|
499
|
+
NETWORK_ERROR: 'Unable to connect. Please check your internet connection.',
|
|
500
|
+
UNAUTHORIZED: 'Your session has expired. Please log in again.',
|
|
501
|
+
FORBIDDEN: 'You don\\'t have permission to perform this action.',
|
|
502
|
+
NOT_FOUND: 'The requested resource was not found.',
|
|
503
|
+
VALIDATION_ERROR: 'Please check your input and try again.',
|
|
504
|
+
RATE_LIMITED: 'Too many requests. Please wait a moment.',
|
|
505
|
+
SERVER_ERROR: 'Something went wrong. Please try again later.',
|
|
506
|
+
UNKNOWN_ERROR: 'An unexpected error occurred.',
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// Error handler
|
|
510
|
+
export function handleAPIError(error: APIError): void {
|
|
511
|
+
const message = ERROR_MESSAGES[error.code] || error.message || ERROR_MESSAGES.UNKNOWN_ERROR;
|
|
512
|
+
|
|
513
|
+
// Show toast notification
|
|
514
|
+
toast.error(message);
|
|
515
|
+
|
|
516
|
+
// Log for debugging (remove in production)
|
|
517
|
+
console.error('[API Error]', {
|
|
518
|
+
code: error.code,
|
|
519
|
+
message: error.message,
|
|
520
|
+
status: error.status,
|
|
521
|
+
details: error.details,
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// Track error analytics
|
|
525
|
+
// analytics.track('api_error', error);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// React Query error handler
|
|
529
|
+
export const queryErrorHandler = (error: unknown) => {
|
|
530
|
+
if (isAPIError(error)) {
|
|
531
|
+
handleAPIError(error);
|
|
532
|
+
} else if (error instanceof Error) {
|
|
533
|
+
handleAPIError({
|
|
534
|
+
message: error.message,
|
|
535
|
+
code: 'UNKNOWN_ERROR',
|
|
536
|
+
status: 0,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
// Type guard
|
|
542
|
+
function isAPIError(error: unknown): error is APIError {
|
|
543
|
+
return (
|
|
544
|
+
typeof error === 'object' &&
|
|
545
|
+
error !== null &&
|
|
546
|
+
'message' in error &&
|
|
547
|
+
'code' in error
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Retry logic
|
|
552
|
+
export const retryCondition = (failureCount: number, error: unknown): boolean => {
|
|
553
|
+
// Don't retry on 4xx errors
|
|
554
|
+
if (isAPIError(error) && error.status >= 400 && error.status < 500) {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
// Retry up to 3 times for server errors
|
|
558
|
+
return failureCount < 3;
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// React Query default options with error handling
|
|
562
|
+
export const queryClientConfig = {
|
|
563
|
+
defaultOptions: {
|
|
564
|
+
queries: {
|
|
565
|
+
retry: retryCondition,
|
|
566
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
567
|
+
gcTime: 30 * 60 * 1000, // 30 minutes (formerly cacheTime)
|
|
568
|
+
},
|
|
569
|
+
mutations: {
|
|
570
|
+
retry: false,
|
|
571
|
+
onError: queryErrorHandler,
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
};`;
|
|
575
|
+
}
|
|
576
|
+
function generateMocks(endpoints) {
|
|
577
|
+
const mocks = [];
|
|
578
|
+
mocks.push(`// ============================================================================`);
|
|
579
|
+
mocks.push(`// Mock Data - Auto-generated`);
|
|
580
|
+
mocks.push(`// ============================================================================`);
|
|
581
|
+
mocks.push('');
|
|
582
|
+
mocks.push(`import { http, HttpResponse } from 'msw';`);
|
|
583
|
+
mocks.push('');
|
|
584
|
+
mocks.push(`// Mock Data Generators`);
|
|
585
|
+
mocks.push(`const generateId = () => Math.random().toString(36).substr(2, 9);`);
|
|
586
|
+
mocks.push(`const generateDate = () => new Date().toISOString();`);
|
|
587
|
+
mocks.push('');
|
|
588
|
+
// Generate mock data for each endpoint
|
|
589
|
+
for (const endpoint of endpoints) {
|
|
590
|
+
const name = pathToName(endpoint.path);
|
|
591
|
+
const mockData = generateMockData(endpoint.responseBody, name);
|
|
592
|
+
mocks.push(`export const mock${name} = ${mockData};`);
|
|
593
|
+
mocks.push('');
|
|
594
|
+
}
|
|
595
|
+
// Generate MSW handlers
|
|
596
|
+
mocks.push(`// MSW Handlers`);
|
|
597
|
+
mocks.push(`export const handlers = [`);
|
|
598
|
+
for (const endpoint of endpoints) {
|
|
599
|
+
const name = pathToName(endpoint.path);
|
|
600
|
+
const method = endpoint.method.toLowerCase();
|
|
601
|
+
const path = endpoint.path.replace(/:(\w+)/g, ':$1'); // MSW path format
|
|
602
|
+
mocks.push(` http.${method}('${path}', () => {
|
|
603
|
+
return HttpResponse.json(mock${name});
|
|
604
|
+
}),`);
|
|
605
|
+
}
|
|
606
|
+
mocks.push(`];`);
|
|
607
|
+
mocks.push('');
|
|
608
|
+
// Add MSW setup
|
|
609
|
+
mocks.push(`// MSW Setup (add to your test setup file)
|
|
610
|
+
/*
|
|
611
|
+
import { setupServer } from 'msw/node';
|
|
612
|
+
import { handlers } from './mocks';
|
|
613
|
+
|
|
614
|
+
export const server = setupServer(...handlers);
|
|
615
|
+
|
|
616
|
+
beforeAll(() => server.listen());
|
|
617
|
+
afterEach(() => server.resetHandlers());
|
|
618
|
+
afterAll(() => server.close());
|
|
619
|
+
*/`);
|
|
620
|
+
return mocks.join('\n');
|
|
621
|
+
}
|
|
622
|
+
function generateMockData(obj, prefix) {
|
|
623
|
+
const mockObj = {};
|
|
624
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
625
|
+
if (typeof value === 'string') {
|
|
626
|
+
if (key.toLowerCase().includes('id')) {
|
|
627
|
+
mockObj[key] = 'mock-id-123';
|
|
628
|
+
}
|
|
629
|
+
else if (key.toLowerCase().includes('email')) {
|
|
630
|
+
mockObj[key] = 'user@example.com';
|
|
631
|
+
}
|
|
632
|
+
else if (key.toLowerCase().includes('name')) {
|
|
633
|
+
mockObj[key] = 'John Doe';
|
|
634
|
+
}
|
|
635
|
+
else if (key.toLowerCase().includes('date') || key.toLowerCase().includes('at')) {
|
|
636
|
+
mockObj[key] = new Date().toISOString();
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
mockObj[key] = `Mock ${key}`;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
else if (typeof value === 'number') {
|
|
643
|
+
mockObj[key] = 42;
|
|
644
|
+
}
|
|
645
|
+
else if (typeof value === 'boolean') {
|
|
646
|
+
mockObj[key] = true;
|
|
647
|
+
}
|
|
648
|
+
else if (Array.isArray(value)) {
|
|
649
|
+
mockObj[key] = value.length > 0 ? [generateMockData(value[0], key)] : [];
|
|
650
|
+
}
|
|
651
|
+
else if (typeof value === 'object' && value !== null) {
|
|
652
|
+
mockObj[key] = JSON.parse(generateMockData(value, key));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return JSON.stringify(mockObj, null, 2);
|
|
656
|
+
}
|
|
657
|
+
function generateZodSchemas(endpoints) {
|
|
658
|
+
const schemas = [];
|
|
659
|
+
schemas.push(`// ============================================================================`);
|
|
660
|
+
schemas.push(`// Zod Validation Schemas - Auto-generated`);
|
|
661
|
+
schemas.push(`// ============================================================================`);
|
|
662
|
+
schemas.push('');
|
|
663
|
+
schemas.push(`import { z } from 'zod';`);
|
|
664
|
+
schemas.push('');
|
|
665
|
+
for (const endpoint of endpoints) {
|
|
666
|
+
const name = pathToName(endpoint.path);
|
|
667
|
+
// Response schema
|
|
668
|
+
schemas.push(`export const ${name}ResponseSchema = ${generateZodSchema(endpoint.responseBody)};`);
|
|
669
|
+
schemas.push(`export type ${name}Response = z.infer<typeof ${name}ResponseSchema>;`);
|
|
670
|
+
schemas.push('');
|
|
671
|
+
// Request schema (for mutations)
|
|
672
|
+
if (endpoint.requestBody && ['POST', 'PUT', 'PATCH'].includes(endpoint.method)) {
|
|
673
|
+
schemas.push(`export const ${name}RequestSchema = ${generateZodSchema(endpoint.requestBody)};`);
|
|
674
|
+
schemas.push(`export type ${name}Request = z.infer<typeof ${name}RequestSchema>;`);
|
|
675
|
+
schemas.push('');
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
// Add validation helper
|
|
679
|
+
schemas.push(`// Validation helper
|
|
680
|
+
export function validateResponse<T>(schema: z.ZodSchema<T>, data: unknown): T {
|
|
681
|
+
const result = schema.safeParse(data);
|
|
682
|
+
if (!result.success) {
|
|
683
|
+
console.error('Validation error:', result.error.format());
|
|
684
|
+
throw new Error('Invalid API response');
|
|
685
|
+
}
|
|
686
|
+
return result.data;
|
|
687
|
+
}`);
|
|
688
|
+
return schemas.join('\n');
|
|
689
|
+
}
|
|
690
|
+
function generateZodSchema(obj) {
|
|
691
|
+
const fields = [];
|
|
692
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
693
|
+
const isOptional = key.endsWith('?');
|
|
694
|
+
const cleanKey = key.replace('?', '');
|
|
695
|
+
let zodType;
|
|
696
|
+
if (typeof value === 'string') {
|
|
697
|
+
zodType = 'z.string()';
|
|
698
|
+
}
|
|
699
|
+
else if (typeof value === 'number') {
|
|
700
|
+
zodType = 'z.number()';
|
|
701
|
+
}
|
|
702
|
+
else if (typeof value === 'boolean') {
|
|
703
|
+
zodType = 'z.boolean()';
|
|
704
|
+
}
|
|
705
|
+
else if (Array.isArray(value)) {
|
|
706
|
+
if (value.length > 0 && typeof value[0] === 'object') {
|
|
707
|
+
zodType = `z.array(${generateZodSchema(value[0])})`;
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
zodType = 'z.array(z.unknown())';
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
else if (typeof value === 'object' && value !== null) {
|
|
714
|
+
zodType = generateZodSchema(value);
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
zodType = 'z.unknown()';
|
|
718
|
+
}
|
|
719
|
+
if (isOptional) {
|
|
720
|
+
zodType += '.optional()';
|
|
721
|
+
}
|
|
722
|
+
fields.push(` ${cleanKey}: ${zodType}`);
|
|
723
|
+
}
|
|
724
|
+
return `z.object({\n${fields.join(',\n')}\n})`;
|
|
725
|
+
}
|
|
726
|
+
function generateUsageExamples(request) {
|
|
727
|
+
const { library, endpoints, framework } = request;
|
|
728
|
+
const example = endpoints[0];
|
|
729
|
+
if (!example) {
|
|
730
|
+
return '// No endpoints provided for usage examples';
|
|
731
|
+
}
|
|
732
|
+
const name = pathToName(example.path);
|
|
733
|
+
if (library === 'react-query' || library === 'tanstack-query') {
|
|
734
|
+
return `// ============================================================================
|
|
735
|
+
// Usage Examples - React Query / TanStack Query
|
|
736
|
+
// ============================================================================
|
|
737
|
+
|
|
738
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
739
|
+
import { use${name} } from './hooks';
|
|
740
|
+
|
|
741
|
+
// 1. Setup Query Client
|
|
742
|
+
const queryClient = new QueryClient(queryClientConfig);
|
|
743
|
+
|
|
744
|
+
function App() {
|
|
745
|
+
return (
|
|
746
|
+
<QueryClientProvider client={queryClient}>
|
|
747
|
+
<YourApp />
|
|
748
|
+
</QueryClientProvider>
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// 2. Use in Component
|
|
753
|
+
function ${name}List() {
|
|
754
|
+
const { data, isLoading, error, refetch } = use${name}();
|
|
755
|
+
|
|
756
|
+
if (isLoading) return <div>Loading...</div>;
|
|
757
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
758
|
+
|
|
759
|
+
return (
|
|
760
|
+
<div>
|
|
761
|
+
{/* Render your data */}
|
|
762
|
+
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
763
|
+
<button onClick={() => refetch()}>Refresh</button>
|
|
764
|
+
</div>
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// 3. Mutations
|
|
769
|
+
function Create${name}Form() {
|
|
770
|
+
const mutation = use${name}Mutation();
|
|
771
|
+
|
|
772
|
+
const handleSubmit = (data) => {
|
|
773
|
+
mutation.mutate(data, {
|
|
774
|
+
onSuccess: () => {
|
|
775
|
+
toast.success('Created successfully!');
|
|
776
|
+
},
|
|
777
|
+
onError: (error) => {
|
|
778
|
+
handleAPIError(error);
|
|
779
|
+
},
|
|
780
|
+
});
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
return (
|
|
784
|
+
<form onSubmit={handleSubmit}>
|
|
785
|
+
{/* Your form fields */}
|
|
786
|
+
<button type="submit" disabled={mutation.isPending}>
|
|
787
|
+
{mutation.isPending ? 'Saving...' : 'Save'}
|
|
788
|
+
</button>
|
|
789
|
+
</form>
|
|
790
|
+
);
|
|
791
|
+
}`;
|
|
792
|
+
}
|
|
793
|
+
return `// See the generated hooks file for usage examples`;
|
|
794
|
+
}
|
|
795
|
+
function generateTestingGuide(request) {
|
|
796
|
+
return `// ============================================================================
|
|
797
|
+
// Testing Guide - Auto-generated
|
|
798
|
+
// ============================================================================
|
|
799
|
+
|
|
800
|
+
/*
|
|
801
|
+
## 1. Setup MSW (Mock Service Worker)
|
|
802
|
+
|
|
803
|
+
npm install msw --save-dev
|
|
804
|
+
|
|
805
|
+
Create src/mocks/handlers.ts (use the generated mocks file)
|
|
806
|
+
Create src/mocks/browser.ts for browser
|
|
807
|
+
Create src/mocks/server.ts for tests
|
|
808
|
+
|
|
809
|
+
## 2. Testing Hooks
|
|
810
|
+
|
|
811
|
+
\`\`\`typescript
|
|
812
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
813
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
814
|
+
import { useUsers } from './hooks';
|
|
815
|
+
|
|
816
|
+
const createWrapper = () => {
|
|
817
|
+
const queryClient = new QueryClient({
|
|
818
|
+
defaultOptions: {
|
|
819
|
+
queries: { retry: false },
|
|
820
|
+
},
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
return ({ children }) => (
|
|
824
|
+
<QueryClientProvider client={queryClient}>
|
|
825
|
+
{children}
|
|
826
|
+
</QueryClientProvider>
|
|
827
|
+
);
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
describe('useUsers', () => {
|
|
831
|
+
it('fetches users successfully', async () => {
|
|
832
|
+
const { result } = renderHook(() => useUsers(), {
|
|
833
|
+
wrapper: createWrapper(),
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
await waitFor(() => {
|
|
837
|
+
expect(result.current.isSuccess).toBe(true);
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
expect(result.current.data).toBeDefined();
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it('handles errors', async () => {
|
|
844
|
+
// Override handler for this test
|
|
845
|
+
server.use(
|
|
846
|
+
http.get('/users', () => {
|
|
847
|
+
return HttpResponse.json(
|
|
848
|
+
{ message: 'Not found' },
|
|
849
|
+
{ status: 404 }
|
|
850
|
+
);
|
|
851
|
+
})
|
|
852
|
+
);
|
|
853
|
+
|
|
854
|
+
const { result } = renderHook(() => useUsers(), {
|
|
855
|
+
wrapper: createWrapper(),
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
await waitFor(() => {
|
|
859
|
+
expect(result.current.isError).toBe(true);
|
|
860
|
+
});
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
\`\`\`
|
|
864
|
+
|
|
865
|
+
## 3. Integration Testing
|
|
866
|
+
|
|
867
|
+
Test the full flow including API client, hooks, and components together.
|
|
868
|
+
|
|
869
|
+
## 4. E2E Testing with Playwright
|
|
870
|
+
|
|
871
|
+
\`\`\`typescript
|
|
872
|
+
import { test, expect } from '@playwright/test';
|
|
873
|
+
|
|
874
|
+
test('loads and displays data', async ({ page }) => {
|
|
875
|
+
await page.goto('/');
|
|
876
|
+
await expect(page.getByText('Loading...')).toBeVisible();
|
|
877
|
+
await expect(page.getByText('John Doe')).toBeVisible();
|
|
878
|
+
});
|
|
879
|
+
\`\`\`
|
|
880
|
+
*/`;
|
|
881
|
+
}
|
|
882
|
+
// ============================================================================
|
|
883
|
+
// Utility Functions
|
|
884
|
+
// ============================================================================
|
|
885
|
+
function pathToName(path) {
|
|
886
|
+
return path
|
|
887
|
+
.replace(/^\//, '')
|
|
888
|
+
.replace(/\/:?\w+/g, '')
|
|
889
|
+
.split(/[-_/]/)
|
|
890
|
+
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
|
|
891
|
+
.join('');
|
|
892
|
+
}
|
|
893
|
+
function toPascalCase(str) {
|
|
894
|
+
return str
|
|
895
|
+
.split(/[-_\s]+/)
|
|
896
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
897
|
+
.join('');
|
|
898
|
+
}
|
|
899
|
+
function toCamelCase(str) {
|
|
900
|
+
const pascal = toPascalCase(str);
|
|
901
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
902
|
+
}
|
|
903
|
+
function hasPathParams(path) {
|
|
904
|
+
return path.includes(':');
|
|
905
|
+
}
|
|
906
|
+
export default { generateAPIIntegration };
|
|
907
|
+
//# sourceMappingURL=api-integration-helper.js.map
|