offbyt 1.0.0

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 (103) hide show
  1. package/README.md +2 -0
  2. package/cli/index.js +2 -0
  3. package/cli.js +206 -0
  4. package/core/detector/detectAxios.js +107 -0
  5. package/core/detector/detectFetch.js +148 -0
  6. package/core/detector/detectForms.js +55 -0
  7. package/core/detector/detectSocket.js +341 -0
  8. package/core/generator/generateControllers.js +17 -0
  9. package/core/generator/generateModels.js +25 -0
  10. package/core/generator/generateRoutes.js +17 -0
  11. package/core/generator/generateServer.js +18 -0
  12. package/core/generator/generateSocket.js +160 -0
  13. package/core/index.js +14 -0
  14. package/core/ir/IRTypes.js +25 -0
  15. package/core/ir/buildIR.js +83 -0
  16. package/core/parser/parseJS.js +26 -0
  17. package/core/parser/parseTS.js +27 -0
  18. package/core/rules/relationRules.js +38 -0
  19. package/core/rules/resourceRules.js +32 -0
  20. package/core/rules/schemaInference.js +26 -0
  21. package/core/scanner/scanProject.js +58 -0
  22. package/deploy/cloudflare.js +41 -0
  23. package/deploy/cloudflareWorker.js +122 -0
  24. package/deploy/connect.js +198 -0
  25. package/deploy/flyio.js +51 -0
  26. package/deploy/index.js +322 -0
  27. package/deploy/netlify.js +29 -0
  28. package/deploy/railway.js +215 -0
  29. package/deploy/render.js +195 -0
  30. package/deploy/utils.js +383 -0
  31. package/deploy/vercel.js +29 -0
  32. package/index.js +18 -0
  33. package/lib/generator/advancedCrudGenerator.js +475 -0
  34. package/lib/generator/crudCodeGenerator.js +486 -0
  35. package/lib/generator/irBasedGenerator.js +360 -0
  36. package/lib/ir-builder/index.js +16 -0
  37. package/lib/ir-builder/irBuilder.js +330 -0
  38. package/lib/ir-builder/rulesEngine.js +353 -0
  39. package/lib/ir-builder/templateEngine.js +193 -0
  40. package/lib/ir-builder/templates/index.js +14 -0
  41. package/lib/ir-builder/templates/model.template.js +47 -0
  42. package/lib/ir-builder/templates/routes-generic.template.js +66 -0
  43. package/lib/ir-builder/templates/routes-user.template.js +105 -0
  44. package/lib/ir-builder/templates/routes.template.js +102 -0
  45. package/lib/ir-builder/templates/validation.template.js +15 -0
  46. package/lib/ir-integration.js +349 -0
  47. package/lib/modes/benchmark.js +162 -0
  48. package/lib/modes/configBasedGenerator.js +2258 -0
  49. package/lib/modes/connect.js +1125 -0
  50. package/lib/modes/doctorAi.js +172 -0
  51. package/lib/modes/generateApi.js +435 -0
  52. package/lib/modes/interactiveSetup.js +548 -0
  53. package/lib/modes/offline.clean.js +14 -0
  54. package/lib/modes/offline.enhanced.js +787 -0
  55. package/lib/modes/offline.js +295 -0
  56. package/lib/modes/offline.v2.js +13 -0
  57. package/lib/modes/sync.js +629 -0
  58. package/lib/scanner/apiEndpointExtractor.js +387 -0
  59. package/lib/scanner/authPatternDetector.js +54 -0
  60. package/lib/scanner/frontendScanner.js +642 -0
  61. package/lib/utils/apiClientGenerator.js +242 -0
  62. package/lib/utils/apiScanner.js +95 -0
  63. package/lib/utils/codeInjector.js +350 -0
  64. package/lib/utils/doctor.js +381 -0
  65. package/lib/utils/envGenerator.js +36 -0
  66. package/lib/utils/loadTester.js +61 -0
  67. package/lib/utils/performanceAnalyzer.js +298 -0
  68. package/lib/utils/resourceDetector.js +281 -0
  69. package/package.json +20 -0
  70. package/templates/.env.template +31 -0
  71. package/templates/advanced.model.template.js +201 -0
  72. package/templates/advanced.route.template.js +341 -0
  73. package/templates/auth.middleware.template.js +87 -0
  74. package/templates/auth.routes.template.js +238 -0
  75. package/templates/auth.user.model.template.js +78 -0
  76. package/templates/cache.middleware.js +34 -0
  77. package/templates/chat.models.template.js +260 -0
  78. package/templates/chat.routes.template.js +478 -0
  79. package/templates/compression.middleware.js +19 -0
  80. package/templates/database.config.js +74 -0
  81. package/templates/errorHandler.middleware.js +54 -0
  82. package/templates/express/controller.ejs +26 -0
  83. package/templates/express/model.ejs +9 -0
  84. package/templates/express/route.ejs +18 -0
  85. package/templates/express/server.ejs +16 -0
  86. package/templates/frontend.env.template +14 -0
  87. package/templates/model.template.js +86 -0
  88. package/templates/package.production.json +51 -0
  89. package/templates/package.template.json +41 -0
  90. package/templates/pagination.utility.js +110 -0
  91. package/templates/production.server.template.js +233 -0
  92. package/templates/rateLimiter.middleware.js +36 -0
  93. package/templates/requestLogger.middleware.js +19 -0
  94. package/templates/response.helper.js +179 -0
  95. package/templates/route.template.js +130 -0
  96. package/templates/security.middleware.js +78 -0
  97. package/templates/server.template.js +91 -0
  98. package/templates/socket.server.template.js +433 -0
  99. package/templates/utils.helper.js +157 -0
  100. package/templates/validation.middleware.js +63 -0
  101. package/templates/validation.schema.js +128 -0
  102. package/utils/fileWriter.js +15 -0
  103. package/utils/logger.js +18 -0
@@ -0,0 +1,1125 @@
1
+ /**
2
+ * Auto-Connection Module
3
+ * Automatically connects generated backend with React frontend
4
+ * - Scans React components for API calls and field names
5
+ * - Scans backend routes
6
+ * - Detects mismatches (URLs, field names, response structure)
7
+ * - Auto-fixes components and creates .env
8
+ * - 100% Offline mode - No AI, No Network
9
+ */
10
+
11
+ import path from 'path';
12
+ import fs from 'fs';
13
+ import chalk from 'chalk';
14
+ import ora from 'ora';
15
+
16
+ // ============================================================
17
+ // MAIN CONNECT FUNCTION
18
+ // ============================================================
19
+ export async function connectFrontendBackend(projectPath) {
20
+ try {
21
+ console.log(chalk.cyan('\n🔗 offbyt Auto-Connection Engine\n'));
22
+ console.log(chalk.gray('Scanner: Frontend → Backend Connection Analysis\n'));
23
+
24
+ const backendPath = path.join(projectPath, 'backend');
25
+
26
+ // Verify project structure
27
+ if (!fs.existsSync(backendPath)) {
28
+ console.error(chalk.red('❌ Backend not found. Run "offbyt generate" first.\n'));
29
+ process.exit(1);
30
+ }
31
+
32
+ // Step 1: Scan React components
33
+ const step1 = ora('Step 1/5: Scanning React components...').start();
34
+ const reactAnalysis = scanReactComponents(projectPath);
35
+ step1.succeed(`✅ Found ${reactAnalysis.components.length} API components`);
36
+
37
+ // Step 2: Scan backend structure
38
+ const step2 = ora('Step 2/5: Analyzing backend routes...').start();
39
+ const backendAnalysis = scanBackendRoutes(backendPath);
40
+ step2.succeed(`✅ Found ${backendAnalysis.routes.length} backend routes`);
41
+
42
+ // Step 3: Detect mismatches
43
+ const step3 = ora('Step 3/5: Analyzing mismatches...').start();
44
+ const mismatches = detectMismatches(reactAnalysis, backendAnalysis);
45
+ step3.succeed(`✅ Detected ${mismatches.issues.length} issues`);
46
+
47
+ // Step 4: Auto-fix issues
48
+ const step4 = ora('Step 4/5: Auto-fixing components...').start();
49
+ const fixResults = applyAutoFixes(projectPath, backendPath, mismatches, reactAnalysis, backendAnalysis);
50
+ step4.succeed(`✅ Fixed ${fixResults.fixed} issues`);
51
+
52
+ // Detect Vite
53
+ let isVite = false;
54
+ try {
55
+ const pkgPath = path.join(projectPath, 'package.json');
56
+ if (fs.existsSync(pkgPath)) {
57
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
58
+ if (pkg.dependencies?.vite || pkg.devDependencies?.vite) {
59
+ isVite = true;
60
+ }
61
+ }
62
+ } catch(e) {}
63
+
64
+ // Step 5: Create/Update .env
65
+ const step5 = ora('Step 5/5: Configuring environment...').start();
66
+ createFrontendEnv(projectPath, backendAnalysis.serverPort, isVite);
67
+ step5.succeed('✅ .env file configured');
68
+
69
+ // Summary
70
+ console.log(chalk.green('\n✅ Auto-Connection Complete!\n'));
71
+ console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
72
+ console.log(chalk.white(' Components Updated:'), reactAnalysis.components.length);
73
+ console.log(chalk.white(' Issues Fixed:'), fixResults.fixed);
74
+ console.log(chalk.white(' API URL Configured:'), `http://localhost:${backendAnalysis.serverPort}`);
75
+ console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
76
+
77
+ if (mismatches.issues.length > 0) {
78
+ console.log(chalk.yellow('⚠️ Issues Found (Auto-Fixed):\n'));
79
+ mismatches.issues.forEach((issue, i) => {
80
+ console.log(chalk.gray(` ${i+1}. ${issue.type}`));
81
+ console.log(chalk.gray(` File: ${issue.file}`));
82
+ console.log(chalk.gray(` Details: ${issue.detail}\n`));
83
+ });
84
+ }
85
+
86
+ console.log(chalk.cyan('🚀 Ready to start:\n'));
87
+ console.log(chalk.white(' Frontend:', chalk.bold(`npm start`)));
88
+ console.log(chalk.white(' Backend:', chalk.bold(`npm start`)));
89
+ console.log(chalk.cyan('\n'));
90
+
91
+ } catch (error) {
92
+ console.error(chalk.red('❌ Connection Error:', error.message));
93
+ process.exit(1);
94
+ }
95
+ }
96
+
97
+ // ============================================================
98
+ // SCANNERS
99
+ // ============================================================
100
+
101
+ function scanReactComponents(projectPath) {
102
+ const components = [];
103
+ const candidateDirs = [
104
+ path.join(projectPath, 'src'),
105
+ path.join(projectPath, 'public'),
106
+ path.join(projectPath, 'frontend'),
107
+ path.join(projectPath, 'client')
108
+ ];
109
+
110
+ let files = [];
111
+
112
+ for (const dir of candidateDirs) {
113
+ if (fs.existsSync(dir)) {
114
+ files.push(...getAllFilesRecursive(dir));
115
+ }
116
+ }
117
+
118
+ files.push(...getTopLevelFiles(projectPath));
119
+
120
+ files = Array.from(new Set(files)).filter(file =>
121
+ file.endsWith('.jsx') ||
122
+ file.endsWith('.js') ||
123
+ file.endsWith('.ts') ||
124
+ file.endsWith('.tsx') ||
125
+ file.endsWith('.html')
126
+ );
127
+
128
+ if (files.length === 0) {
129
+ return { components: [] };
130
+ }
131
+
132
+ files.forEach(file => {
133
+ try {
134
+ const content = fs.readFileSync(file, 'utf8');
135
+
136
+ // Extract API calls
137
+ const apiCalls = extractApiCalls(content);
138
+
139
+ // Extract form field names (inputs, setState, etc)
140
+ const fieldNames = extractFormFields(content);
141
+
142
+ // Extract response parsing patterns
143
+ const responsePatterns = extractResponsePatterns(content);
144
+
145
+ if (apiCalls.length > 0 || fieldNames.length > 0) {
146
+ components.push({
147
+ file: path.relative(projectPath, file),
148
+ apiCalls,
149
+ fieldNames,
150
+ responsePatterns,
151
+ content
152
+ });
153
+ }
154
+ } catch (error) {
155
+ console.warn(chalk.gray(` ⚠️ Skipped: ${path.relative(projectPath, file)}`));
156
+ }
157
+ });
158
+
159
+ return { components };
160
+ }
161
+
162
+ function scanBackendRoutes(backendPath) {
163
+ const routes = [];
164
+ let serverPort = 5000;
165
+
166
+ try {
167
+ // Get port from server.js or main.ts (NestJS)
168
+ const serverFile = path.join(backendPath, 'server.js');
169
+ const mainFile = path.join(backendPath, 'main.ts');
170
+
171
+ if (fs.existsSync(serverFile)) {
172
+ const serverContent = fs.readFileSync(serverFile, 'utf8');
173
+ const portMatch = serverContent.match(/PORT["\s]*=\s*['"]?(\d+)['"]?/);
174
+ if (portMatch) serverPort = parseInt(portMatch[1]);
175
+
176
+ const portEnvMatch = serverContent.match(/process\.env\.PORT\s*\|\|\s*(\d+)/);
177
+ if (portEnvMatch) serverPort = parseInt(portEnvMatch[1]);
178
+ }
179
+ else if (fs.existsSync(mainFile)) {
180
+ const mainContent = fs.readFileSync(mainFile, 'utf8');
181
+ const portMatch = mainContent.match(/PORT["\s]*=\s*['"]?(\d+)['"]?/);
182
+ if (portMatch) serverPort = parseInt(portMatch[1]);
183
+
184
+ const portEnvMatch = mainContent.match(/process\.env\.PORT\s*\|\|\s*(\d+)/);
185
+ if (portEnvMatch) serverPort = parseInt(portEnvMatch[1]);
186
+ }
187
+
188
+ // Scan routes
189
+ const routesDir = path.join(backendPath, 'routes');
190
+ if (fs.existsSync(routesDir)) {
191
+ const routeFiles = fs.readdirSync(routesDir).filter(f => f.endsWith('.js') || f.endsWith('.ts'));
192
+
193
+ routeFiles.forEach(file => {
194
+ const filePath = path.join(routesDir, file);
195
+ const content = fs.readFileSync(filePath, 'utf8');
196
+ const resourceName = file.replace(/\.routes\.(js|ts)$/i, '').replace(/\.(js|ts)$/i, '');
197
+
198
+ // Extract route definitions (supports multiline route declarations)
199
+ const routeRegex = /(?:router|app|fastify)\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
200
+ let routeMatch;
201
+ while ((routeMatch = routeRegex.exec(content)) !== null) {
202
+ const method = routeMatch[1].toUpperCase();
203
+ const routePath = routeMatch[2];
204
+
205
+ routes.push({
206
+ method,
207
+ path: routePath,
208
+ file,
209
+ fullPath: buildApiRoutePath(resourceName, routePath)
210
+ });
211
+ }
212
+ });
213
+ }
214
+
215
+ // Scan NestJS controllers (TypeScript with decorators)
216
+ const controllersDir = path.join(backendPath, 'controllers');
217
+ if (fs.existsSync(controllersDir)) {
218
+ const controllerFiles = fs.readdirSync(controllersDir).filter(f => f.endsWith('.controller.ts'));
219
+
220
+ controllerFiles.forEach(file => {
221
+ const filePath = path.join(controllersDir, file);
222
+ const content = fs.readFileSync(filePath, 'utf8');
223
+
224
+ // Extract @Controller('resource') decorator
225
+ const controllerMatch = content.match(/@Controller\(['"`]([^'"`]+)['"`]\)/);
226
+ const baseRoute = controllerMatch ? controllerMatch[1] : '';
227
+
228
+ // Extract @Get(), @Post(), @Put(), @Delete(), @Patch() decorators
229
+ const methodRegex = /@(Get|Post|Put|Delete|Patch)\s*\(\s*['"`]?([^'"`\)]*?)['"`]?\s*\)/gi;
230
+ let methodMatch;
231
+
232
+ while ((methodMatch = methodRegex.exec(content)) !== null) {
233
+ const method = methodMatch[1].toUpperCase();
234
+ const routePath = methodMatch[2] || '';
235
+
236
+ // Build full path: /api/{baseRoute}/{routePath}
237
+ let fullPath = `/api/` + baseRoute;
238
+ if (routePath) {
239
+ // Handle :id parameters
240
+ const cleanPath = routePath.replace(/^\//, '');
241
+ fullPath += '/' + cleanPath;
242
+ }
243
+
244
+ routes.push({
245
+ method,
246
+ path: routePath || '/',
247
+ file,
248
+ fullPath: fullPath
249
+ });
250
+ }
251
+ });
252
+ }
253
+
254
+ // Scan models for field names
255
+ const models = [];
256
+ const modelsDir = path.join(backendPath, 'models');
257
+ if (fs.existsSync(modelsDir)) {
258
+ const modelFiles = fs.readdirSync(modelsDir).filter(f => f.endsWith('.js') || f.endsWith('.ts'));
259
+
260
+ modelFiles.forEach(file => {
261
+ const filePath = path.join(modelsDir, file);
262
+ const content = fs.readFileSync(filePath, 'utf8');
263
+ const fields = extractMongooseFields(content);
264
+
265
+ models.push({
266
+ name: file.replace(/\.(js|ts)$/, ''),
267
+ file: file,
268
+ fields: fields
269
+ });
270
+ });
271
+ }
272
+
273
+ // Scan NestJS entities (TypeORM)
274
+ const entitiesDir = path.join(backendPath, 'entities');
275
+ if (fs.existsSync(entitiesDir)) {
276
+ const entityFiles = fs.readdirSync(entitiesDir).filter(f => f.endsWith('.entity.ts'));
277
+
278
+ entityFiles.forEach(file => {
279
+ const filePath = path.join(entitiesDir, file);
280
+ const content = fs.readFileSync(filePath, 'utf8');
281
+
282
+ // Extract fields from @Column() decorators
283
+ const fieldRegex = /@Column\([^)]*\)\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g;
284
+ const fields = [];
285
+ let fieldMatch;
286
+
287
+ while ((fieldMatch = fieldRegex.exec(content)) !== null) {
288
+ fields.push(fieldMatch[1]);
289
+ }
290
+
291
+ // Also extract @PrimaryGeneratedColumn
292
+ if (content.includes('@PrimaryGeneratedColumn')) {
293
+ const idMatch = content.match(/@PrimaryGeneratedColumn\([^)]*\)\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/);
294
+ if (idMatch && !fields.includes(idMatch[1])) {
295
+ fields.unshift(idMatch[1]); // Add id at the beginning
296
+ }
297
+ }
298
+
299
+ models.push({
300
+ name: file.replace('.entity.ts', ''),
301
+ file: file,
302
+ fields: fields
303
+ });
304
+ });
305
+ }
306
+
307
+ return { routes, models, serverPort };
308
+ } catch (error) {
309
+ console.warn(chalk.yellow(`⚠️ Backend scan error: ${error.message}`));
310
+ return { routes: [], models: [], serverPort: 5000 };
311
+ }
312
+ }
313
+
314
+ // ============================================================
315
+ // MISMATCH DETECTION
316
+ // ============================================================
317
+
318
+ function detectMismatches(reactAnalysis, backendAnalysis) {
319
+ const issues = [];
320
+
321
+ reactAnalysis.components.forEach(component => {
322
+ // Check 1: API URLs (should use environment variable)
323
+ component.apiCalls.forEach(call => {
324
+ // If URL is hardcoded /api/... without env variable, flag it
325
+ if (call.url.startsWith(`/api`) && !call.usesEnvVar) {
326
+ issues.push({
327
+ type: 'Hardcoded API URL',
328
+ file: component.file,
329
+ detail: `URL "${call.url}" should use environment variable`,
330
+ fix: 'url_format',
331
+ component: component.file,
332
+ apiCall: call
333
+ });
334
+ }
335
+ // If URL doesn't have env variable and not http/https
336
+ else if (!call.usesEnvVar && !call.url.includes('http')) {
337
+ if (!call.url.startsWith('/')) {
338
+ issues.push({
339
+ type: 'Invalid API URL Format',
340
+ file: component.file,
341
+ detail: `URL should start with / or use env variable: ${call.url}`,
342
+ fix: 'url_format',
343
+ component: component.file,
344
+ apiCall: call
345
+ });
346
+ }
347
+ }
348
+
349
+ // Check 2: Route exists in backend
350
+ const cleanUrl = call.url
351
+ .replace(/`\$\{process\.env\.REACT_APP_API_URL\}/g, '')
352
+ .replace(/`\$\{import\.meta\.env\.VITE_API_URL\}/g, '')
353
+ .replace(/`/g, '');
354
+
355
+ const apiPath = cleanUrl.startsWith(`/api`)
356
+ ? cleanUrl
357
+ : `/api${cleanUrl.startsWith('/') ? '' : '/'}${cleanUrl}`;
358
+
359
+ const normalizedTarget = canonicalRouteMatchPath(apiPath);
360
+ const routeExists = backendAnalysis.routes.some(route =>
361
+ canonicalRouteMatchPath(route.fullPath) === normalizedTarget
362
+ );
363
+
364
+ if (!routeExists && !isAuthUrl(apiPath)) {
365
+ issues.push({
366
+ type: 'Missing Backend Route',
367
+ file: component.file,
368
+ detail: `API endpoint "${apiPath}" not found in backend routes`,
369
+ fix: 'missing_route',
370
+ component: component.file,
371
+ apiPath: apiPath,
372
+ method: call.method || 'GET'
373
+ });
374
+ }
375
+ });
376
+
377
+ // Check 3: Field name mismatches
378
+ component.fieldNames.forEach(field => {
379
+ const backendHasField = backendAnalysis.models.some(model =>
380
+ model.fields.some(f => f.name === field.name)
381
+ );
382
+
383
+ if (!backendHasField && field.name !== 'password' && field.name !== 'email') {
384
+ // Try fuzzy matching (e.g., fullname vs name)
385
+ const similarField = backendAnalysis.models.flatMap(m => m.fields)
386
+ .find(f => isSimilarField(f.name, field.name));
387
+
388
+ if (similarField) {
389
+ issues.push({
390
+ type: 'Field Name Mismatch',
391
+ file: component.file,
392
+ detail: `"${field.name}" should be "${similarField.name}"`,
393
+ fix: 'field_name',
394
+ component: component.file,
395
+ oldField: field.name,
396
+ newField: similarField.name
397
+ });
398
+ }
399
+ }
400
+ });
401
+
402
+ // Check 4: Response parsing patterns
403
+ if (component.responsePatterns.length > 0) {
404
+ // Infer expected structure from response patterns
405
+ component.responsePatterns.forEach(pattern => {
406
+ if (pattern.accessPath.includes('[0]') || pattern.accessPath.includes('.data.data')) {
407
+ issues.push({
408
+ type: 'Response Structure Issue',
409
+ file: component.file,
410
+ detail: `Possible nested response structure: ${pattern.accessPath}`,
411
+ fix: 'response_structure',
412
+ component: component.file,
413
+ pattern
414
+ });
415
+ }
416
+ });
417
+ }
418
+ });
419
+
420
+ return { issues };
421
+ }
422
+
423
+ /**
424
+ * Helper functions for route matching
425
+ */
426
+ function normalizeRoutePath(path) {
427
+ if (!path) return '/';
428
+
429
+ let normalized = String(path)
430
+ .trim()
431
+ .replace(/["'`]/g, '')
432
+ .replace(/\$\{[^}]+\}/g, ':id')
433
+ .replace(/\/+/g, '/');
434
+
435
+ if (!normalized.startsWith('/')) normalized = `/${normalized}`;
436
+ normalized = normalized.toLowerCase();
437
+
438
+ if (normalized.length > 1) {
439
+ normalized = normalized.replace(/\/+$/g, '');
440
+ }
441
+
442
+ return normalized || '/';
443
+ }
444
+
445
+ function isAuthUrl(path) {
446
+ return normalizeRoutePath(path).includes('/auth/');
447
+ }
448
+
449
+ function canonicalRouteMatchPath(routePath) {
450
+ return normalizeRoutePath(routePath)
451
+ .replace(/\/:([^/]+)/g, '/:id')
452
+ .replace(/\/(\d+)(?=\/|$)/g, '/:id')
453
+ .replace(/\/([0-9a-f]{24})(?=\/|$)/gi, '/:id');
454
+ }
455
+
456
+ function buildApiRoutePath(resourceName, routePath) {
457
+ const cleanResource = resourceName.replace(/\.routes$/i, '');
458
+ const normalizedRoute = normalizeRoutePath(routePath);
459
+
460
+ if (normalizedRoute.startsWith(`/api/`)) {
461
+ return normalizedRoute;
462
+ }
463
+
464
+ if (normalizedRoute === '/') {
465
+ return normalizeRoutePath(`/api/${cleanResource}`);
466
+ }
467
+ return normalizeRoutePath(`/api/${cleanResource}${normalizedRoute}`);
468
+ }
469
+
470
+
471
+
472
+ // ============================================================
473
+ // AUTO-FIXES
474
+ // ============================================================
475
+
476
+ function applyAutoFixes(projectPath, backendPath, mismatches, reactAnalysis, backendAnalysis) {
477
+ let fixedCount = 0;
478
+
479
+ // Detect Vite
480
+ let isVite = false;
481
+ try {
482
+ const pkgPath = path.join(projectPath, 'package.json');
483
+ if (fs.existsSync(pkgPath)) {
484
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
485
+ if (pkg.dependencies?.vite || pkg.devDependencies?.vite) {
486
+ isVite = true;
487
+ }
488
+ }
489
+ } catch(e) {}
490
+
491
+ mismatches.issues.forEach(issue => {
492
+ try {
493
+ if (issue.fix === 'missing_route') {
494
+ // Auto-generate missing routes
495
+ const { resourceName, routePath, method } = parseApiPath(issue.apiPath);
496
+ const routeFile = path.join(backendPath, 'routes', `${resourceName}.routes.js`);
497
+
498
+ if (resourceName && fs.existsSync(routeFile) && !isUnsafeAutoRoute(routePath)) {
499
+ let content = fs.readFileSync(routeFile, 'utf8');
500
+
501
+ const isExpressStyleRouteFile =
502
+ /express\.Router\(/i.test(content) ||
503
+ /const\s+router\s*=\s*express\.Router\(/i.test(content) ||
504
+ /export\s+default\s+router\s*;/i.test(content);
505
+
506
+ // Only inject auto-routes into Express router files.
507
+ if (!isExpressStyleRouteFile) {
508
+ return;
509
+ }
510
+
511
+ const methodLower = (method || 'GET').toLowerCase();
512
+ const alreadyExistsRegex = new RegExp(
513
+ `router\\.${methodLower}\\s*\\(\\s*['"]${escapeRegex(routePath)}['"]`,
514
+ 'i'
515
+ );
516
+
517
+ if (!alreadyExistsRegex.test(content)) {
518
+ const newRoute = generateMissingRoute(routePath, method, resourceName);
519
+
520
+ if (newRoute) {
521
+ // Insert new route before the export statement
522
+ if (/export\s+default\s+router\s*;/.test(content)) {
523
+ content = content.replace(/export\s+default\s+router\s*;/, `${newRoute}\n\nexport default router;`);
524
+ } else {
525
+ content = `${content}\n${newRoute}\n`;
526
+ }
527
+
528
+ fs.writeFileSync(routeFile, content, 'utf8');
529
+ fixedCount++;
530
+ }
531
+ }
532
+ }
533
+ }
534
+
535
+ if (issue.fix === 'field_name') {
536
+ // Fix field name in component
537
+ const filePath = path.join(projectPath, issue.component);
538
+ if (fs.existsSync(filePath)) {
539
+ let content = fs.readFileSync(filePath, 'utf8');
540
+ const regex = new RegExp(`\\b${escapeRegex(issue.oldField)}\\b`, 'g');
541
+
542
+ // Only replace in API payloads and form context, not in comments
543
+ content = content.replace(regex, (match) => {
544
+ // Simple heuristic: if surrounded by quotes or brackets, it's likely a data field
545
+ return match;
546
+ });
547
+
548
+ // More precise: only replace in specific contexts (object literals, formData)
549
+ content = replaceFieldNameInContext(content, issue.oldField, issue.newField);
550
+
551
+ fs.writeFileSync(filePath, content, 'utf8');
552
+ fixedCount++;
553
+ }
554
+ }
555
+
556
+ if (issue.fix === 'response_structure') {
557
+ // Fix response parsing
558
+ const filePath = path.join(projectPath, issue.component);
559
+ if (fs.existsSync(filePath)) {
560
+ let content = fs.readFileSync(filePath, 'utf8');
561
+
562
+ // Auto-detect and fix common response structure issues
563
+ content = fixPartialUpdateMethods(content);
564
+ content = fixResponseParsing(content);
565
+
566
+ fs.writeFileSync(filePath, content, 'utf8');
567
+ fixedCount++;
568
+ }
569
+ }
570
+
571
+ if (issue.fix === 'url_format') {
572
+ // Fix API URL format
573
+ const filePath = path.join(projectPath, issue.component);
574
+ if (fs.existsSync(filePath)) {
575
+ let content = fs.readFileSync(filePath, 'utf8');
576
+ content = fixApiUrls(content, isVite);
577
+ content = fixPartialUpdateMethods(content);
578
+ content = fixResponseParsing(content);
579
+ fs.writeFileSync(filePath, content, 'utf8');
580
+ fixedCount++;
581
+ }
582
+ }
583
+ } catch (error) {
584
+ console.warn(chalk.gray(` ⚠️ Could not fix: ${issue.file} - ${error.message}`));
585
+ }
586
+ });
587
+
588
+ // Safety pass: normalize partial status updates from PUT -> PATCH
589
+ // This protects generated frontends that toggle completed/status fields.
590
+ const componentFiles = new Set(
591
+ (reactAnalysis.components || [])
592
+ .map((component) => path.join(projectPath, component.file))
593
+ .filter(Boolean)
594
+ );
595
+
596
+ componentFiles.forEach((filePath) => {
597
+ try {
598
+ if (!fs.existsSync(filePath)) return;
599
+ const original = fs.readFileSync(filePath, 'utf8');
600
+ const updated = fixPartialUpdateMethods(original);
601
+ if (updated !== original) {
602
+ fs.writeFileSync(filePath, updated, 'utf8');
603
+ fixedCount++;
604
+ }
605
+ } catch {
606
+ // Ignore file-level rewrite failures
607
+ }
608
+ });
609
+
610
+ return { fixed: fixedCount };
611
+ }
612
+
613
+ function fixResponseParsing(content) {
614
+ // Fix common response parsing patterns
615
+
616
+ // Pattern 1: res.data.data.token → res.data.token (if response is {data: {...}})
617
+ content = content.replace(/data\.data\.(\w+)/g, 'data.$1');
618
+
619
+ // Pattern 2: response.data.data.user → response.data.user
620
+ content = content.replace(/response\.data\.data\.(\w+)/g, 'response.data.$1');
621
+
622
+ // Pattern 3: const x = await response.json();
623
+ // Works for both raw API arrays and wrapped payloads like { success, data }
624
+ content = content.replace(
625
+ /const\s+(\w+)\s*=\s*await\s+(\w+)\.json\(\);/g,
626
+ (fullMatch, variableName, responseName) => {
627
+ if (variableName.endsWith('Response')) {
628
+ return fullMatch;
629
+ }
630
+
631
+ return `const ${variableName}Response = await ${responseName}.json();\n const ${variableName} = Array.isArray(${variableName}Response) ? ${variableName}Response : (${variableName}Response.data ?? ${variableName}Response);`;
632
+ }
633
+ );
634
+
635
+ return content;
636
+ }
637
+
638
+ function fixApiUrls(content, isVite = false) {
639
+ // Ensure API calls use environment variables for base URL
640
+ const API_URL = isVite ? 'import.meta.env.VITE_API_URL' : 'process.env.REACT_APP_API_URL';
641
+
642
+ // Pattern 1: fetch('/api/... → fetch(`/api/...
643
+ content = content.replace(
644
+ /fetch\s*\(\s*['"`](\/api\/[^'"`]+)['"`]/g,
645
+ `fetch(\`\${${API_URL}}$1\``
646
+ );
647
+
648
+ // Pattern 2: Template literals with /api at start: fetch(`/api/products/${id}`)
649
+ content = content.replace(
650
+ /fetch\s*\(\s*`(\/api\/[^`]+)`/g,
651
+ (match, url) => {
652
+ // If already has env variable, skip
653
+ if (url.includes('$') || match.includes(API_URL)) return match;
654
+ return `fetch(\`\${${API_URL}}${url}\``;
655
+ }
656
+ );
657
+
658
+ // Pattern 2b: fetch(`${API_BASE}/api/...`) -> fetch(`/api/...`)
659
+ content = content.replace(
660
+ /fetch\s*\(\s*`\$\{[^}]+\}(\/api\/[^`]+)`/g,
661
+ `fetch(\`\${${API_URL}}$1\``
662
+ );
663
+
664
+ // Pattern 3: const url = '/api/... → const url = `/api/...
665
+ content = content.replace(
666
+ /(const|let|var)\s+(\w+)\s*=\s*['"`](\/api\/[^'"`]+)['"`]/g,
667
+ `$1 $2 = \`\${${API_URL}}$3\``
668
+ );
669
+
670
+ // Pattern 3b: const url = `${API_BASE}/api/...` -> const url = `/api/...`
671
+ content = content.replace(
672
+ /(const|let|var)\s+(\w+)\s*=\s*`\$\{[^}]+\}(\/api\/[^`]+)`/g,
673
+ `$1 $2 = \`\${${API_URL}}$3\``
674
+ );
675
+
676
+ // Pattern 4: axios calls and custom instances
677
+ content = content.replace(
678
+ /\b(axios|api|client|http|request)\.(get|post|put|delete|patch)\s*\(\s*['"`](\/api\/[^'"`]+)['"`]/g,
679
+ `$1.$2(\`\${${API_URL}}$3\``
680
+ );
681
+
682
+ // Pattern 4b: axios.get(`${API_BASE}/api/...`) -> axios.get(`/api/...`)
683
+ content = content.replace(
684
+ /\b(axios|api|client|http|request)\.(get|post|put|delete|patch)\s*\(\s*`\$\{[^}]+\}(\/api\/[^`]+)`/g,
685
+ `$1.$2(\`\${${API_URL}}$3\``
686
+ );
687
+
688
+ return content;
689
+ }
690
+
691
+ function fixPartialUpdateMethods(content) {
692
+ // Convert fetch PUT calls to PATCH when payload appears to be status-toggle only
693
+ content = content.replace(/fetch\s*\(\s*[\s\S]*?\{[\s\S]*?\}\s*\)/g, (callExpression) => {
694
+ if (!/method\s*:\s*['"`]PUT['"`]/i.test(callExpression)) return callExpression;
695
+
696
+ const payloadMatches = [...callExpression.matchAll(/body\s*:\s*JSON\.stringify\s*\(\s*(\{[\s\S]*?\})\s*\)/gi)];
697
+ if (payloadMatches.length === 0) return callExpression;
698
+
699
+ const shouldPatch = payloadMatches.some((match) => isPartialStatusPayload(match[1]));
700
+ if (!shouldPatch) return callExpression;
701
+
702
+ return callExpression.replace(/method\s*:\s*['"`]PUT['"`]/i, "method: 'PATCH'");
703
+ });
704
+
705
+ // Convert axios-like .put(url, payload) to .patch(url, payload) for partial status payloads
706
+ content = content.replace(
707
+ /\b(axios|api|client|http|request)\.put\s*\(\s*([^,]+)\s*,\s*(\{[\s\S]*?\})\s*(,\s*[\s\S]*?)?\)/g,
708
+ (match, clientName, urlArg, payload, trailing = '') => {
709
+ if (!isPartialStatusPayload(payload)) return match;
710
+ return `${clientName}.patch(${urlArg}, ${payload}${trailing})`;
711
+ }
712
+ );
713
+
714
+ return content;
715
+ }
716
+
717
+ function isPartialStatusPayload(payloadText = '') {
718
+ const statusLikeKeys = ['completed', 'isCompleted', 'done', 'isDone', 'status', 'state', 'isActive', 'active', 'archived'];
719
+ const fullUpdateKeys = ['title', 'name', 'description', 'email', 'password', 'phone', 'address', 'price', 'amount', 'content'];
720
+
721
+ const hasStatusLikeKey = statusLikeKeys.some((key) =>
722
+ new RegExp(`\\b${escapeRegex(key)}\\b\\s*:`, 'i').test(payloadText)
723
+ );
724
+
725
+ const hasFullUpdateKey = fullUpdateKeys.some((key) =>
726
+ new RegExp(`\\b${escapeRegex(key)}\\b\\s*:`, 'i').test(payloadText)
727
+ );
728
+
729
+ return hasStatusLikeKey && !hasFullUpdateKey;
730
+ }
731
+
732
+ function replaceFieldNameInContext(content, oldField, newField) {
733
+ // Replace field names only in appropriate contexts
734
+
735
+ // In object literals: {"oldField": value}
736
+ content = content.replace(
737
+ new RegExp(`['"](${escapeRegex(oldField)})['"]\\s*:`, 'g'),
738
+ `"${newField}":`
739
+ );
740
+
741
+ // In FormData: formData.append('oldField', ...)
742
+ content = content.replace(
743
+ new RegExp(`\\bappend\\s*\\(['"](${escapeRegex(oldField)})['"]`, 'g'),
744
+ `append('${newField}'`
745
+ );
746
+
747
+ // In object shorthand: {oldField}
748
+ content = content.replace(
749
+ new RegExp(`\\b${escapeRegex(oldField)}\\b(?=\\s*[,}])`, 'g'),
750
+ newField
751
+ );
752
+
753
+ return content;
754
+ }
755
+
756
+ function createFrontendEnv(projectPath, serverPort, isVite = false) {
757
+ const envPath = path.join(projectPath, '.env');
758
+ const envVar = isVite ? 'VITE_API_URL' : 'REACT_APP_API_URL';
759
+ const envContent = `${envVar}=http://localhost:${serverPort}\n`;
760
+
761
+ fs.writeFileSync(envPath, envContent, 'utf8');
762
+ }
763
+
764
+ // ============================================================
765
+ // HELPER FUNCTIONS
766
+ // ============================================================
767
+
768
+ function getAllFilesRecursive(dirPath) {
769
+ let files = [];
770
+
771
+ try {
772
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
773
+
774
+ entries.forEach(entry => {
775
+ const fullPath = path.join(dirPath, entry.name);
776
+
777
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
778
+ files = [...files, ...getAllFilesRecursive(fullPath)];
779
+ } else if (entry.isFile()) {
780
+ files.push(fullPath);
781
+ }
782
+ });
783
+ } catch (error) {
784
+ // Silently ignore permission errors
785
+ }
786
+
787
+ return files;
788
+ }
789
+
790
+ function getTopLevelFiles(dirPath) {
791
+ try {
792
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
793
+ return entries
794
+ .filter(entry => entry.isFile())
795
+ .map(entry => path.join(dirPath, entry.name));
796
+ } catch {
797
+ return [];
798
+ }
799
+ }
800
+
801
+ function extractApiCalls(content) {
802
+ const calls = [];
803
+ const isProcessEnvUrl = (raw) => /process\.env\.REACT_APP_API_URL|import\.meta\.env\.VITE_API_URL/.test(raw);
804
+
805
+ // Pattern 1: fetch with string literal (relative or absolute URL)
806
+ const fetchRegex = /fetch\s*\(\s*['"]([^'"\n]+)['"]/g;
807
+ let match;
808
+
809
+ while ((match = fetchRegex.exec(content)) !== null) {
810
+ const rawUrl = match[1];
811
+ const normalizedUrl = normalizeApiPath(rawUrl);
812
+ if (!normalizedUrl) continue;
813
+
814
+ calls.push({
815
+ type: 'fetch',
816
+ url: normalizedUrl,
817
+ usesEnvVar: isProcessEnvUrl(rawUrl)
818
+ });
819
+ }
820
+
821
+ // Pattern 2: fetch with template literal: fetch(`${API_BASE}/api/products/${id}`)
822
+ const fetchTemplateRegex = /fetch\s*\(\s*`([^`]+)`/g;
823
+
824
+ while ((match = fetchTemplateRegex.exec(content)) !== null) {
825
+ const rawUrl = match[1];
826
+ // Clean ${...} variables to :id
827
+ const cleanUrl = rawUrl.replace(/\$\{[^}]+\}/g, ':id');
828
+ const normalizedUrl = normalizeApiPath(cleanUrl);
829
+ if (!normalizedUrl) continue;
830
+
831
+ calls.push({
832
+ type: 'fetch',
833
+ url: normalizedUrl,
834
+ usesEnvVar: isProcessEnvUrl(rawUrl),
835
+ hasTemplate: true
836
+ });
837
+ }
838
+
839
+ // Pattern 3: const url = `/api/...` OR `/api/...`
840
+ const urlVarRegex = /(?:const|let|var)\s+(\w+)\s*=\s*['"`]([^'"`\n]+)['"`]/g;
841
+ const urlVars = new Map();
842
+
843
+ while ((match = urlVarRegex.exec(content)) !== null) {
844
+ const rawUrl = match[2];
845
+ const normalizedUrl = normalizeApiPath(rawUrl);
846
+ if (!normalizedUrl) continue;
847
+
848
+ urlVars.set(match[1], {
849
+ url: normalizedUrl,
850
+ usesEnvVar: isProcessEnvUrl(rawUrl)
851
+ });
852
+ }
853
+
854
+ // Find fetch(variableName)
855
+ const fetchVarRegex = /fetch\s*\(\s*(\w+)\s*[,)]/g;
856
+
857
+ while ((match = fetchVarRegex.exec(content)) !== null) {
858
+ const varName = match[1];
859
+ const urlMeta = urlVars.get(varName);
860
+
861
+ if (urlMeta) {
862
+ calls.push({
863
+ type: 'fetch',
864
+ url: urlMeta.url,
865
+ usesEnvVar: urlMeta.usesEnvVar
866
+ });
867
+ }
868
+ }
869
+
870
+ // Pattern 4: axios calls and custom instances (relative or absolute URL)
871
+ const axiosRegex = /\b(?:axios|api|client|http|request)\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`\n]+)['"`]/g;
872
+
873
+ while ((match = axiosRegex.exec(content)) !== null) {
874
+ const rawUrl = match[2];
875
+ const normalizedUrl = normalizeApiPath(rawUrl);
876
+ if (!normalizedUrl) continue;
877
+
878
+ calls.push({
879
+ type: 'axios',
880
+ method: match[1],
881
+ url: normalizedUrl,
882
+ usesEnvVar: isProcessEnvUrl(rawUrl)
883
+ });
884
+ }
885
+
886
+ // Pattern 4.5: axios calls with template literals: axios.get(`/api/products/${id}`)
887
+ const axiosTemplateRegex = /\b(?:axios|api|client|http|request)\.(get|post|put|delete|patch)\s*\(\s*`([^`]+)`/g;
888
+
889
+ while ((match = axiosTemplateRegex.exec(content)) !== null) {
890
+ const rawUrl = match[2];
891
+ // Clean ${...} variables to :id
892
+ const cleanUrl = rawUrl.replace(/\$\{[^}]+\}/g, ':id');
893
+ const normalizedUrl = normalizeApiPath(cleanUrl);
894
+ if (!normalizedUrl) continue;
895
+
896
+ calls.push({
897
+ type: 'axios',
898
+ method: match[1],
899
+ url: normalizedUrl,
900
+ usesEnvVar: isProcessEnvUrl(rawUrl),
901
+ hasTemplate: true
902
+ });
903
+ }
904
+
905
+ return calls;
906
+ }
907
+
908
+ function normalizeApiPath(rawUrl) {
909
+ if (!rawUrl) return null;
910
+
911
+ const clean = rawUrl
912
+ .trim()
913
+ .replace(/\$\{process\.env\.REACT_APP_API_URL\}|\$\{import\.meta\.env\.VITE_API_URL\}/g, '')
914
+ .replace(/\$\{[^}]+\}/g, ':id');
915
+
916
+ if (!clean) return null;
917
+
918
+ // Relative API paths
919
+ if (clean.startsWith(`/api/`)) return normalizeRoutePath(clean);
920
+
921
+ // Template/absolute paths that include /api/
922
+ const apiIndex = clean.indexOf(`/api/`);
923
+ if (apiIndex >= 0) {
924
+ return normalizeRoutePath(clean.slice(apiIndex));
925
+ }
926
+
927
+ return null;
928
+ }
929
+
930
+ function extractFormFields(content) {
931
+ const fields = [];
932
+
933
+ // Pattern 1: setState({field: ...})
934
+ const setStateRegex = /\{(\w+):\s*[^}]*\}/g;
935
+ let match;
936
+
937
+ while ((match = setStateRegex.exec(content)) !== null) {
938
+ const fieldName = match[1];
939
+ if (!['data', 'error', 'loading', 'response'].includes(fieldName)) {
940
+ fields.push({ name: fieldName });
941
+ }
942
+ }
943
+
944
+ // Pattern 2: formData.append('field', ...)
945
+ const formDataRegex = /append\s*\(\s*['"`](\w+)['"`]/g;
946
+
947
+ while ((match = formDataRegex.exec(content)) !== null) {
948
+ fields.push({ name: match[1] });
949
+ }
950
+
951
+ // Pattern 3: Object destructuring {name, email, ...}
952
+ const destructRegex = /const\s*\{([^}]+)\}\s*=/g;
953
+
954
+ while ((match = destructRegex.exec(content)) !== null) {
955
+ const vars = match[1].split(',').map(v => v.trim().split(':')[0]);
956
+ vars.forEach(v => {
957
+ if (v && !['useState', 'useEffect'].includes(v)) {
958
+ fields.push({ name: v });
959
+ }
960
+ });
961
+ }
962
+
963
+ // Deduplicate
964
+ return [...new Map(fields.map(f => [f.name, f])).values()];
965
+ }
966
+
967
+ function extractResponsePatterns(content) {
968
+ const patterns = [];
969
+
970
+ // Match response access patterns: response.data.user, data.data.token, etc.
971
+ const accessPatterns = /(\w+|response)(\.\w+)+/g;
972
+ let match;
973
+
974
+ while ((match = accessPatterns.exec(content)) !== null) {
975
+ patterns.push({
976
+ accessPath: match[0]
977
+ });
978
+ }
979
+
980
+ return patterns;
981
+ }
982
+
983
+ function extractMongooseFields(content) {
984
+ const fields = [];
985
+
986
+ // Match Mongoose schema fields like: name: { type: String }
987
+ const fieldRegex = /(\w+)\s*:\s*\{?\s*type\s*:\s*(String|Number|Boolean|Date)|(\w+)\s*:\s*(String|Number|Boolean|Date)/g;
988
+ let match;
989
+
990
+ while ((match = fieldRegex.exec(content)) !== null) {
991
+ const fieldName = match[1] || match[3];
992
+ if (fieldName) {
993
+ fields.push({ name: fieldName });
994
+ }
995
+ }
996
+
997
+ // Remove duplicates
998
+ return [...new Map(fields.map(f => [f.name, f])).values()];
999
+ }
1000
+
1001
+ function isSimilarField(field1, field2) {
1002
+ // Check for common patterns
1003
+ const patterns = [
1004
+ { old: 'fullname', new: 'name' },
1005
+ { old: 'firstname', new: 'name' },
1006
+ { old: 'last_name', new: 'lastname' },
1007
+ { old: 'phone_number', new: 'phone' },
1008
+ { old: 'user_email', new: 'email' }
1009
+ ];
1010
+
1011
+ return patterns.some(p =>
1012
+ (field1.toLowerCase() === p.old && field2.toLowerCase() === p.new) ||
1013
+ (field1.toLowerCase() === p.new && field2.toLowerCase() === p.old)
1014
+ );
1015
+ }
1016
+
1017
+ function escapeRegex(string) {
1018
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1019
+ }
1020
+ /**
1021
+ * Parse API path to extract resource name and route path
1022
+ * Examples:
1023
+ * /api/admin/dashboard → {resourceName: 'admin', routePath: '/dashboard'}
1024
+ * /api/user/invoices → {resourceName: 'user', routePath: '/invoices'}
1025
+ * /api/products/:id → {resourceName: 'products', routePath: '/:id'}
1026
+ */
1027
+ function parseApiPath(apiPath) {
1028
+ const normalizedPath = normalizeRoutePath(apiPath);
1029
+ if (!normalizedPath.startsWith(`/api/`)) {
1030
+ return { resourceName: null, routePath: null, method: 'GET' };
1031
+ }
1032
+
1033
+ const parts = normalizedPath.replace(/^\/api\//, '').split('/').filter(Boolean);
1034
+ const resourceName = parts[0] || null;
1035
+ const routePath = '/' + parts.slice(1).join('/');
1036
+ const method = 'GET'; // Default method
1037
+
1038
+ return { resourceName, routePath: routePath === '/' ? '/' : routePath, method };
1039
+ }
1040
+
1041
+ function isUnsafeAutoRoute(routePath) {
1042
+ const normalized = normalizeRoutePath(routePath);
1043
+ return normalized.includes(':') || normalized.includes('*') || normalized.includes('?') || normalized.includes('$');
1044
+ }
1045
+
1046
+ /**
1047
+ * Generate a missing route dynamically
1048
+ */
1049
+ function generateMissingRoute(routePath, method, resourceName) {
1050
+ if (!routePath) return '';
1051
+
1052
+ const methodLower = method.toLowerCase();
1053
+ const cleanPath = routePath === '/' ? '/' : routePath;
1054
+
1055
+ let responseLogic = `
1056
+ // TODO: Implement ${routePath} logic
1057
+ const result = {};
1058
+
1059
+ res.status(200).json({
1060
+ success: true,
1061
+ data: result
1062
+ });`;
1063
+
1064
+ // Detect common patterns
1065
+ if (routePath.includes('dashboard') || routePath.includes('stats')) {
1066
+ responseLogic = `
1067
+ // Dashboard/Stats endpoint
1068
+ const stats = {
1069
+ total: 0,
1070
+ active: 0,
1071
+ revenue: 0
1072
+ };
1073
+
1074
+ res.status(200).json({
1075
+ success: true,
1076
+ stats: stats
1077
+ });`;
1078
+ } else if (routePath.includes('analytics') || routePath.includes('reports')) {
1079
+ responseLogic = `
1080
+ // Analytics endpoint
1081
+ const analytics = {
1082
+ data: [],
1083
+ summary: {}
1084
+ };
1085
+
1086
+ res.status(200).json({
1087
+ success: true,
1088
+ analytics: analytics
1089
+ });`;
1090
+ } else if (routePath.includes('list') || routePath.includes('all')) {
1091
+ responseLogic = `
1092
+ // List endpoint
1093
+ const items = [];
1094
+
1095
+ res.status(200).json({
1096
+ success: true,
1097
+ count: items.length,
1098
+ items: items
1099
+ });`;
1100
+ } else if (method === 'POST' || method === 'PUT') {
1101
+ responseLogic = `
1102
+ // Update/Create endpoint
1103
+ const result = { ...req.body, _id: new Date().getTime() };
1104
+
1105
+ res.status(${method === 'POST' ? '201' : '200'}).json({
1106
+ success: true,
1107
+ message: '${routePath} processed successfully',
1108
+ data: result
1109
+ });`;
1110
+ }
1111
+
1112
+ return `
1113
+ // Auto-generated route for ${routePath}
1114
+ router.${methodLower}('${cleanPath}', async (req, res) => {
1115
+ try {${responseLogic}
1116
+ } catch (error) {
1117
+ res.status(500).json({
1118
+ success: false,
1119
+ message: 'Error processing ${routePath}',
1120
+ error: error.message
1121
+ });
1122
+ }
1123
+ });`;
1124
+ }
1125
+