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,172 @@
1
+ import { spawn } from 'child_process';
2
+ import chalk from 'chalk';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import Groq from 'groq-sdk';
6
+ import ora from 'ora';
7
+
8
+ export async function runDoctorAi(projectPath, options) {
9
+ // Fast custom .env loader without dotenv dependency
10
+ try {
11
+ const envContent = await fs.readFile(path.join(projectPath, '.env'), 'utf-8');
12
+ envContent.split('\n').forEach(line => {
13
+ const match = line.match(/^\s*([\w]+)\s*=\s*(.*)?\s*$/);
14
+ if (match) {
15
+ let [_, key, value = ''] = match;
16
+ value = value.replace(/^['"]|['"]$/g, ''); // Remove quotes
17
+ process.env[key] = process.env[key] || value; // Don't override existing env vars
18
+ }
19
+ });
20
+ } catch (err) { /* ignore if no .env */ }
21
+
22
+ const cmd = options.cmd || 'npm run dev';
23
+
24
+ const groq = process.env.GROQ_API_KEY ? new Groq({ apiKey: process.env.GROQ_API_KEY }) : null;
25
+
26
+ if (!groq) {
27
+ console.warn(chalk.yellow('⚠️ GROQ_API_KEY is not set in environment variables.'));
28
+ console.warn(chalk.yellow('AI analysis will not be available. Please set GROQ_API_KEY to enable doctor-ai.'));
29
+ console.info(chalk.cyan('You can get an API key from https://console.groq.com/keys'));
30
+ process.exit(1);
31
+ }
32
+
33
+ console.log(chalk.cyan(`\n🩺 Starting Doctor AI... Monitoring command: ${chalk.bold(cmd)}`));
34
+ console.log(chalk.gray(`Watching directory: ${projectPath}\n`));
35
+
36
+ // Split command
37
+ const [command, ...args] = cmd.split(' ');
38
+ // Use shell true to handle npm run dev properly on windows/linux
39
+ const child = spawn(command, args, { cwd: projectPath, shell: true });
40
+
41
+ let errorBuffer = '';
42
+ let analysisInProgress = false;
43
+
44
+ child.stdout.on('data', (data) => {
45
+ process.stdout.write(data);
46
+ const str = data.toString();
47
+ if (str.toLowerCase().includes('error')) {
48
+ errorBuffer += str;
49
+ triggerAnalysis();
50
+ }
51
+ });
52
+
53
+ child.stderr.on('data', (data) => {
54
+ process.stderr.write(data);
55
+ errorBuffer += data.toString();
56
+ triggerAnalysis();
57
+ });
58
+
59
+ child.on('close', (code) => {
60
+ console.log(chalk.gray(`\nChild process exited with code ${code}`));
61
+ });
62
+
63
+ // Debounce the analysis so we don't trigger it 100 times for one stack trace
64
+ let timeoutId = null;
65
+ function triggerAnalysis() {
66
+ if (analysisInProgress) return;
67
+
68
+ if (timeoutId) clearTimeout(timeoutId);
69
+ timeoutId = setTimeout(async () => {
70
+ if (errorBuffer.trim().length > 0 && !analysisInProgress) {
71
+ await analyzeError(errorBuffer);
72
+ errorBuffer = ''; // reset buffer after analysis
73
+ }
74
+ }, 2000); // Wait 2 seconds for stack trace to settle
75
+ }
76
+
77
+ async function analyzeError(errorText) {
78
+ analysisInProgress = true;
79
+ const spinner = ora('AI is analyzing the error...').start();
80
+
81
+ try {
82
+ // 1. Extract potential files from error text
83
+ const filePathsToRead = extractFilesFromError(errorText, projectPath);
84
+ let fileContents = '';
85
+
86
+ for (const file of filePathsToRead) {
87
+ try {
88
+ const content = await fs.readFile(file, 'utf-8');
89
+ fileContents += `\n--- File: ${path.relative(projectPath, file)} ---\n\`\`\`javascript\n${content}\n\`\`\`\n`;
90
+ } catch (err) {
91
+ // ignore files that couldn't be read
92
+ }
93
+ }
94
+
95
+ // 2. Call Groq
96
+ const prompt = `
97
+ You are an expert Backend Developer and AI debugging assistant.
98
+ The user's application crashed or produced an error.
99
+ Please analyze the following error trace and the involved file codes to determine the root cause and provide a solution.
100
+
101
+ ERROR TRACE:
102
+ ${errorText}
103
+
104
+ INVOLVED FILES:
105
+ ${fileContents}
106
+
107
+ Please provide:
108
+ 1. **Root Cause**: Why did the error happen?
109
+ 2. **Solution**: What exactly needs to be changed? Show the code replacements clearly.
110
+ 3. **Prevention**: How can this be prevented in the future?
111
+
112
+ Format the output cleanly in Markdown so it can be saved as a report.
113
+ `;
114
+
115
+ const completion = await groq.chat.completions.create({
116
+ messages: [
117
+ {
118
+ role: "system",
119
+ content: "You are a helpful, expert AI debugging assistant. Respond in clear markdown."
120
+ },
121
+ {
122
+ role: "user",
123
+ content: prompt
124
+ }
125
+ ],
126
+ model: "llama-3.3-70b-versatile",
127
+ temperature: 0.2,
128
+ });
129
+
130
+ const aiResponse = completion.choices[0]?.message?.content || 'No completion generated.';
131
+
132
+ // 3. Write to report
133
+ const reportPath = path.join(process.cwd(), 'AI_DEBUG_REPORT.md'); // Write to root where CLI was called
134
+
135
+ const reportContent = `# 🩺 Doctor AI Debug Report
136
+ _Generated on ${new Date().toLocaleString()}_
137
+
138
+ ## Monitored Command
139
+ \`${cmd}\`
140
+
141
+ ` + aiResponse;
142
+
143
+ await fs.writeFile(reportPath, reportContent);
144
+ spinner.succeed(chalk.green(`Analysis complete! Report saved to ${chalk.bold('AI_DEBUG_REPORT.md')}`));
145
+
146
+ } catch (err) {
147
+ spinner.fail(chalk.red('AI Analysis failed.'));
148
+ console.error(chalk.red(err.message));
149
+ } finally {
150
+ analysisInProgress = false;
151
+ }
152
+ }
153
+
154
+ function extractFilesFromError(errorText, basePath) {
155
+ // Look for file paths like /path/to/file.js:line:col or C:\path\to\file.js:line:col
156
+ // specifically we look for .js, .jsx, .ts, .tsx files
157
+ const regex = /(?:[a-zA-Z]:[\\/]|[\.\/\\]+)[a-zA-Z0-9_\-\/\\]+\.(js|jsx|ts|tsx)/g;
158
+ const matches = errorText.match(regex) || [];
159
+
160
+ // Deduplicate and resolve absolute paths
161
+ const uniqueFiles = new Set();
162
+ matches.forEach(match => {
163
+ // Ignore node internal files like internal/modules/... or node_modules
164
+ if (match.includes('node_modules') || match.startsWith('internal/')) return;
165
+
166
+ const absPath = path.resolve(basePath, match.trim());
167
+ uniqueFiles.add(absPath);
168
+ });
169
+
170
+ return Array.from(uniqueFiles);
171
+ }
172
+ }
@@ -0,0 +1,435 @@
1
+ /**
2
+ * Smart API Generation Mode
3
+ * Detects resources from frontend patterns and generates full-stack APIs
4
+ */
5
+
6
+ import path from 'path';
7
+ import fs from 'fs';
8
+ import chalk from 'chalk';
9
+ import ora from 'ora';
10
+ import { fileURLToPath } from 'url';
11
+ import { detectResourcesFromFrontend } from '../utils/resourceDetector.js';
12
+ import { generateApiClients } from '../utils/apiClientGenerator.js';
13
+ import { injectApiCalls } from '../utils/codeInjector.js';
14
+ import { generateAdvancedCrudModel, generateAdvancedCrudRoutes } from '../generator/advancedCrudGenerator.js';
15
+ import { generateSQLFiles } from './configBasedGenerator.js';
16
+
17
+ /**
18
+ * Main Smart API Generation Function
19
+ */
20
+ export async function generateSmartAPI(projectPath, options = {}) {
21
+ // Extract config from options (passed from cli.js)
22
+ const config = options.config || { database: 'mongodb' };
23
+ const isSQL = ['postgresql', 'mysql', 'sqlite'].includes(config.database);
24
+
25
+ console.log(chalk.cyan('\n🎯 offbyt Smart API Generation\n'));
26
+ console.log(chalk.gray('Detecting resources from your frontend...\n'));
27
+ if (isSQL) {
28
+ console.log(chalk.blue(`ðŸ"¦ Using ${config.database.toUpperCase()} database\n`));
29
+ }
30
+
31
+ const backendPath = path.join(projectPath, 'backend');
32
+
33
+ // ========================================================
34
+ // STEP 1: DETECT RESOURCES FROM FRONTEND
35
+ // ========================================================
36
+ const step1 = ora('Step 1/6: Scanning frontend for data patterns...').start();
37
+ const resources = detectResourcesFromFrontend(projectPath);
38
+
39
+ if (resources.length === 0) {
40
+ step1.fail(chalk.yellow('No resources detected in frontend'));
41
+ console.log(chalk.gray('\nMake sure your frontend has state variables like:'));
42
+ console.log(chalk.gray(' const [products, setProducts] = useState([])'));
43
+ console.log(chalk.gray(' const [users, setUsers] = useState([])'));
44
+ return;
45
+ }
46
+
47
+ step1.succeed(chalk.green(`✅ Detected ${resources.length} resources`));
48
+
49
+ console.log(chalk.cyan('\n📦 Detected Resources:\n'));
50
+ for (const resource of resources) {
51
+ console.log(chalk.white(` • ${resource.name}`) + chalk.gray(` (${resource.singular})`));
52
+ if (resource.fields.length > 0) {
53
+ console.log(chalk.gray(` Fields: ${resource.fields.join(', ')}`));
54
+ }
55
+ }
56
+ console.log('');
57
+
58
+ // ========================================================
59
+ // STEP 2: CREATE BACKEND STRUCTURE
60
+ // ========================================================
61
+ const step2 = ora('Step 2/6: Creating backend structure...').start();
62
+
63
+ const backendExists = fs.existsSync(backendPath);
64
+ createBackendStructure(backendPath, projectPath);
65
+ ensureValidationMiddleware(backendPath);
66
+ if (backendExists) {
67
+ step2.succeed('✅ Backend structure exists and required utility files ensured');
68
+ } else {
69
+ step2.succeed('✅ Backend structure created');
70
+ }
71
+
72
+ // ========================================================
73
+ // STEP 3: GENERATE BACKEND MODELS
74
+ // ========================================================
75
+ const step3 = ora('Step 3/6: Generating backend models...').start();
76
+ const modelsDir = path.join(backendPath, 'models');
77
+ let modelCount = 0;
78
+
79
+ for (const resource of resources) {
80
+ const modelName = capitalize(resource.singular);
81
+ const modelFile = path.join(modelsDir, `${modelName}.js`);
82
+
83
+ // Only create if doesn't exist
84
+ if (!fs.existsSync(modelFile)) {
85
+ const modelCode = generateAdvancedCrudModel(
86
+ resource.name,
87
+ resource.fields,
88
+ false,
89
+ []
90
+ );
91
+ fs.writeFileSync(modelFile, modelCode, 'utf8');
92
+ modelCount++;
93
+ }
94
+ }
95
+
96
+ step3.succeed(`✅ Generated ${modelCount} models`);
97
+
98
+ // ========================================================
99
+ // STEP 4: GENERATE BACKEND ROUTES
100
+ // ========================================================
101
+ const step4 = ora('Step 4/6: Generating backend routes...').start();
102
+ const routesDir = path.join(backendPath, 'routes');
103
+ let routeCount = 0;
104
+
105
+ for (const resource of resources) {
106
+ const routeFile = path.join(routesDir, `${resource.name}.routes.js`);
107
+
108
+ // Only create if doesn't exist
109
+ if (!fs.existsSync(routeFile)) {
110
+ const routeCode = generateAdvancedCrudRoutes(
111
+ resource.name,
112
+ resource.fields,
113
+ false,
114
+ [],
115
+ []
116
+ );
117
+ fs.writeFileSync(routeFile, routeCode, 'utf8');
118
+ routeCount++;
119
+ }
120
+ }
121
+
122
+ step4.succeed(`✅ Generated ${routeCount} route files`);
123
+
124
+ // ========================================================
125
+ // STEP 4.5: GENERATE SQL FILES (for SQL databases only)
126
+ // ========================================================
127
+ if (isSQL && resources.length > 0) {
128
+ const sqlStep = ora('Generating SQL schema and seed files...').start();
129
+ try {
130
+ // Convert resources array to Map format expected by generateSQLFiles
131
+ const resourcesMap = new Map();
132
+ for (const resource of resources) {
133
+ resourcesMap.set(resource.name, {
134
+ fields: resource.fields || [],
135
+ hasAuth: false
136
+ });
137
+ }
138
+
139
+ generateSQLFiles(backendPath, resourcesMap, config, { relationships: [] });
140
+ sqlStep.succeed(chalk.green('✅ SQL files generated in backend/sql/'));
141
+ console.log(chalk.gray(' • 01_schema.sql'));
142
+ console.log(chalk.gray(' • 02_crud_operations.sql'));
143
+ console.log(chalk.gray(' • 03_relationships_joins.sql'));
144
+ console.log(chalk.gray(' • 04_seed_data.sql\n'));
145
+ } catch (error) {
146
+ sqlStep.fail(`Failed to generate SQL files: ${error.message}`);
147
+ console.error(chalk.red(error.stack));
148
+ }
149
+ }
150
+
151
+ // ========================================================
152
+ // STEP 5: GENERATE FRONTEND API CLIENTS
153
+ // ========================================================
154
+ const step5 = ora('Step 5/6: Generating frontend API clients...').start();
155
+
156
+ try {
157
+ const generatedClients = generateApiClients(projectPath, resources);
158
+ step5.succeed(`✅ Generated ${generatedClients.length} API client files`);
159
+
160
+ console.log(chalk.cyan('\n📁 Generated API Clients:\n'));
161
+ for (const file of generatedClients) {
162
+ console.log(chalk.gray(` • ${file}`));
163
+ }
164
+ console.log('');
165
+ } catch (error) {
166
+ step5.fail(`Failed to generate API clients: ${error.message}`);
167
+ }
168
+
169
+ // ========================================================
170
+ // STEP 6: INJECT API CALLS IN FRONTEND (Optional)
171
+ // ========================================================
172
+ if (options.inject !== false) {
173
+ const step6 = ora('Step 6/6: Injecting API calls in frontend...').start();
174
+
175
+ try {
176
+ const injectedFiles = injectApiCalls(projectPath, resources);
177
+
178
+ if (injectedFiles.length > 0) {
179
+ step6.succeed(`✅ Injected API calls in ${injectedFiles.length} files`);
180
+
181
+ console.log(chalk.cyan('\n💉 Modified Files:\n'));
182
+ for (const file of injectedFiles.slice(0, 10)) {
183
+ console.log(chalk.gray(` • ${file}`));
184
+ }
185
+ if (injectedFiles.length > 10) {
186
+ console.log(chalk.gray(` ... and ${injectedFiles.length - 10} more`));
187
+ }
188
+ console.log('');
189
+ } else {
190
+ step6.succeed('✅ No injection needed (files already up-to-date)');
191
+ }
192
+ } catch (error) {
193
+ step6.warn(`Skipped injection: ${error.message}`);
194
+ }
195
+ } else {
196
+ console.log(chalk.gray(' ⏭️ Skipped frontend injection (--no-inject)\n'));
197
+ }
198
+
199
+ // ========================================================
200
+ // STEP 7: UPDATE SERVER.JS
201
+ // ========================================================
202
+ const serverFile = path.join(backendPath, 'server.js');
203
+ if (fs.existsSync(serverFile)) {
204
+ updateServerWithRoutes(serverFile, resources);
205
+ } else {
206
+ createServerFile(backendPath, resources);
207
+ }
208
+
209
+ // ========================================================
210
+ // SUMMARY
211
+ // ========================================================
212
+ console.log(chalk.green('\n✅ Smart API Generation Complete!\n'));
213
+
214
+ console.log(chalk.cyan('📋 Generated APIs:\n'));
215
+ for (const resource of resources) {
216
+ console.log(chalk.white(` ${resource.name}:`));
217
+ console.log(chalk.gray(` GET /api/${resource.name}`));
218
+ console.log(chalk.gray(` GET /api/${resource.name}/:id`));
219
+ console.log(chalk.gray(` POST /api/${resource.name}`));
220
+ console.log(chalk.gray(` PUT /api/${resource.name}/:id`));
221
+ console.log(chalk.gray(` DELETE /api/${resource.name}/:id`));
222
+ }
223
+
224
+ console.log(chalk.cyan('\n🚀 Next Steps:\n'));
225
+ console.log(chalk.yellow(' 1. Update backend with new APIs:'));
226
+ console.log(chalk.green(' backendify sync\n'));
227
+ console.log(chalk.gray(' This will generate backend routes & models for your new APIs\n'));
228
+ console.log(chalk.yellow(' 2. Start your servers:'));
229
+ console.log(chalk.gray(' • Start MongoDB: mongod'));
230
+ console.log(chalk.gray(' • Start backend: cd backend && npm run dev'));
231
+ console.log(chalk.gray(' • Start frontend: npm start'));
232
+ console.log(chalk.green('\n Your frontend and backend will be fully connected! 🎉\n'));
233
+ }
234
+
235
+ /**
236
+ * Create backend structure
237
+ */
238
+ function createBackendStructure(backendPath, projectPath) {
239
+ const dirs = ['routes', 'models', 'middleware', 'config', 'utils'];
240
+ for (const dir of dirs) {
241
+ const dirPath = path.join(backendPath, dir);
242
+ if (!fs.existsSync(dirPath)) {
243
+ fs.mkdirSync(dirPath, { recursive: true });
244
+ }
245
+ }
246
+
247
+ // Get templates directory path
248
+ const __filename = fileURLToPath(import.meta.url);
249
+ const __dirname = path.dirname(__filename);
250
+ const templatesDir = path.join(__dirname, '..', '..', 'templates');
251
+
252
+ // Copy pagination utility
253
+ const paginationTemplate = path.join(templatesDir, 'pagination.utility.js');
254
+ const paginationDest = path.join(backendPath, 'utils', 'pagination.js');
255
+ if (fs.existsSync(paginationTemplate) && !fs.existsSync(paginationDest)) {
256
+ fs.copyFileSync(paginationTemplate, paginationDest);
257
+ }
258
+
259
+ // Copy response helper
260
+ const helperTemplate = path.join(templatesDir, 'utils.helper.js');
261
+ const helperDest = path.join(backendPath, 'utils', 'helper.js');
262
+ if (fs.existsSync(helperTemplate) && !fs.existsSync(helperDest)) {
263
+ fs.copyFileSync(helperTemplate, helperDest);
264
+ }
265
+
266
+ // Copy validation schema
267
+ const validationTemplate = path.join(templatesDir, 'validation.schema.js');
268
+ const validationDest = path.join(backendPath, 'middleware', 'validation.js');
269
+ if (fs.existsSync(validationTemplate) && !fs.existsSync(validationDest)) {
270
+ fs.copyFileSync(validationTemplate, validationDest);
271
+ }
272
+
273
+ // Create .env file from template
274
+ const envTemplate = path.join(templatesDir, '.env.template');
275
+ const envDest = path.join(backendPath, '.env');
276
+ if (fs.existsSync(envTemplate) && !fs.existsSync(envDest)) {
277
+ let envContent = fs.readFileSync(envTemplate, 'utf8');
278
+ // Replace DB_NAME placeholder with project name
279
+ const dbName = path.basename(projectPath).toLowerCase().replace(/[^a-z0-9]/g, '-');
280
+ envContent = envContent.replace(/<DB_NAME>/g, dbName);
281
+ fs.writeFileSync(envDest, envContent, 'utf8');
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Ensure validation middleware has validateErrors export expected by generated routes.
287
+ */
288
+ function ensureValidationMiddleware(backendPath) {
289
+ const validationFile = path.join(backendPath, 'middleware', 'validation.js');
290
+ if (!fs.existsSync(validationFile)) return;
291
+
292
+ const content = fs.readFileSync(validationFile, 'utf8');
293
+ if (content.includes('export const validateErrors')) return;
294
+
295
+ let updated = content;
296
+ if (!updated.includes("from 'express-validator'") && !updated.includes('from "express-validator"')) {
297
+ updated = `import { validationResult } from 'express-validator';\n\n` + updated;
298
+ }
299
+
300
+ const validateErrorsBlock = `export const validateErrors = (req, res, next) => {\n const errors = validationResult(req);\n if (!errors.isEmpty()) {\n return res.status(400).json({\n success: false,\n errors: errors.array()\n });\n }\n next();\n};\n\n`;
301
+
302
+ fs.writeFileSync(validationFile, validateErrorsBlock + updated, 'utf8');
303
+ }
304
+
305
+ /**
306
+ * Update server.js with new routes
307
+ */
308
+ function updateServerWithRoutes(serverFile, resources) {
309
+ let content = fs.readFileSync(serverFile, 'utf8');
310
+ let modified = false;
311
+
312
+ for (const resource of resources) {
313
+ const routeImport = `import ${resource.name}Routes from './routes/${resource.name}.routes.js';`;
314
+ const routeUse = `app.use('/api/${resource.name}', ${resource.name}Routes);`;
315
+
316
+ // Add import if not present
317
+ if (!content.includes(`from './routes/${resource.name}.routes.js'`)) {
318
+ // Find last import
319
+ const lastImportIndex = content.lastIndexOf('import ');
320
+ const endOfLine = content.indexOf('\n', lastImportIndex);
321
+ content = content.slice(0, endOfLine + 1) + routeImport + '\n' + content.slice(endOfLine + 1);
322
+ modified = true;
323
+ }
324
+
325
+ // Add route use if not present
326
+ if (!content.includes(`app.use('/api/${resource.name}'`)) {
327
+ // Find where routes are defined (after middleware, before server/app listen)
328
+ const appListenIndex = content.indexOf('app.listen');
329
+ const serverListenIndex = content.indexOf('server.listen');
330
+ const listenCandidates = [appListenIndex, serverListenIndex].filter(idx => idx !== -1);
331
+ const listenIndex = listenCandidates.length > 0 ? Math.min(...listenCandidates) : -1;
332
+
333
+ if (listenIndex !== -1) {
334
+ content = content.slice(0, listenIndex) + routeUse + '\n\n' + content.slice(listenIndex);
335
+ modified = true;
336
+ }
337
+ }
338
+ }
339
+
340
+ if (modified) {
341
+ fs.writeFileSync(serverFile, content, 'utf8');
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Create basic server.js
347
+ */
348
+ function createServerFile(backendPath, resources) {
349
+ let serverCode = `import express from 'express';
350
+ import cors from 'cors';
351
+ import mongoose from 'mongoose';
352
+ import dotenv from 'dotenv';
353
+
354
+ dotenv.config();
355
+
356
+ const app = express();
357
+
358
+ // Middleware
359
+ app.use(cors());
360
+ app.use(express.json());
361
+ app.use(express.urlencoded({ extended: true }));
362
+
363
+ // MongoDB Connection
364
+ mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/offbyt', {
365
+ useNewUrlParser: true,
366
+ useUnifiedTopology: true
367
+ }).then(() => {
368
+ console.log('✅ MongoDB Connected');
369
+ }).catch((err) => {
370
+ console.error('❌ MongoDB Connection Failed:', err.message);
371
+ });
372
+
373
+ `;
374
+
375
+ // Add route imports
376
+ for (const resource of resources) {
377
+ serverCode += `import ${resource.name}Routes from './routes/${resource.name}.routes.js';\n`;
378
+ }
379
+
380
+ serverCode += '\n// Routes\n';
381
+
382
+ // Add route uses
383
+ for (const resource of resources) {
384
+ serverCode += `app.use('/api/${resource.name}', ${resource.name}Routes);\n`;
385
+ }
386
+
387
+ serverCode += `
388
+ // Health check
389
+ app.get('/health', (req, res) => {
390
+ res.json({ status: 'ok', message: 'Server is running' });
391
+ });
392
+
393
+ // Start server
394
+ const PORT = process.env.PORT || 5000;
395
+ app.listen(PORT, () => {
396
+ console.log(\`🚀 Server running on http://localhost:\${PORT}\`);
397
+ });
398
+ `;
399
+
400
+ fs.writeFileSync(path.join(backendPath, 'server.js'), serverCode, 'utf8');
401
+
402
+ // Create package.json
403
+ const packageJson = {
404
+ name: 'backend',
405
+ version: '1.0.0',
406
+ type: 'module',
407
+ scripts: {
408
+ dev: 'node server.js',
409
+ start: 'node server.js'
410
+ },
411
+ dependencies: {
412
+ express: '^4.18.0',
413
+ mongoose: '^7.0.0',
414
+ cors: '^2.8.5',
415
+ dotenv: '^16.0.0',
416
+ 'express-validator': '^7.0.0'
417
+ }
418
+ };
419
+
420
+ fs.writeFileSync(
421
+ path.join(backendPath, 'package.json'),
422
+ JSON.stringify(packageJson, null, 2),
423
+ 'utf8'
424
+ );
425
+ }
426
+
427
+ /**
428
+ * Capitalize first letter
429
+ */
430
+ function capitalize(str) {
431
+ return str.charAt(0).toUpperCase() + str.slice(1);
432
+ }
433
+
434
+ export default { generateSmartAPI };
435
+