offbyt 1.0.0 โ 1.0.2
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/cli.js +117 -2
- package/industry-mode.json +22 -0
- package/lib/ir-integration.js +4 -4
- package/lib/modes/configBasedGenerator.js +27 -27
- package/lib/modes/connect.js +26 -25
- package/lib/modes/generateApi.js +217 -76
- package/lib/modes/interactiveSetup.js +28 -29
- package/lib/utils/cliFormatter.js +131 -0
- package/lib/utils/codeInjector.js +144 -26
- package/lib/utils/industryMode.js +419 -0
- package/lib/utils/postGenerationVerifier.js +256 -0
- package/lib/utils/resourceDetector.js +234 -169
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { connectFrontendBackend } from './lib/modes/connect.js';
|
|
|
9
9
|
import { runDoctor } from './lib/utils/doctor.js';
|
|
10
10
|
import { getInteractiveSetup, displaySetupSummary } from './lib/modes/interactiveSetup.js';
|
|
11
11
|
import { generateWithConfig } from './lib/modes/configBasedGenerator.js';
|
|
12
|
+
import { printBanner, printSection, printSuccess, printStep, printSummary, printFooter } from './lib/utils/cliFormatter.js';
|
|
12
13
|
|
|
13
14
|
const program = new Command();
|
|
14
15
|
|
|
@@ -21,6 +22,15 @@ function parsePort(value) {
|
|
|
21
22
|
return port;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
function parsePositiveInteger(value) {
|
|
26
|
+
const parsed = Number.parseInt(value, 10);
|
|
27
|
+
if (Number.isNaN(parsed) || parsed < 1) {
|
|
28
|
+
throw new Error('Value must be a positive integer');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
program
|
|
25
35
|
.name('offbyt')
|
|
26
36
|
.description('Hybrid Backend Generator - Offline + AI Powered')
|
|
@@ -32,8 +42,12 @@ program
|
|
|
32
42
|
.option('--no-auto-connect', 'Skip auto-connect after generation')
|
|
33
43
|
.option('--quick', 'Use default configuration (no questions)')
|
|
34
44
|
.option('--no-api-detect', 'Skip automatic API detection from frontend')
|
|
45
|
+
.option('--no-verify', 'Skip Phase-2 backend verification (startup + health check)')
|
|
46
|
+
.option('--verify-timeout <ms>', 'Health-check timeout in milliseconds', parsePositiveInteger, 45000)
|
|
47
|
+
.option('--strict-verify', 'Fail command if Phase-2 verification does not pass')
|
|
35
48
|
.action(async (projectPath, options) => {
|
|
36
49
|
try {
|
|
50
|
+
printBanner();
|
|
37
51
|
const workingPath = projectPath || process.cwd();
|
|
38
52
|
let config;
|
|
39
53
|
|
|
@@ -49,7 +63,8 @@ program
|
|
|
49
63
|
enableCaching: false,
|
|
50
64
|
enableLogging: true
|
|
51
65
|
};
|
|
52
|
-
|
|
66
|
+
printSection('Quick Mode: Using Default Configuration');
|
|
67
|
+
printSuccess('Configuration ready - MongoDB + Express + Auth enabled');
|
|
53
68
|
} else {
|
|
54
69
|
// Interactive setup
|
|
55
70
|
config = await getInteractiveSetup();
|
|
@@ -64,7 +79,7 @@ program
|
|
|
64
79
|
|
|
65
80
|
// AUTOMATIC: Smart API detection & generation
|
|
66
81
|
if (options.apiDetect !== false) {
|
|
67
|
-
console.log(chalk.cyan('\n\n
|
|
82
|
+
console.log(chalk.cyan('\n\n[PIPELINE] Running Smart API Detection...\n'));
|
|
68
83
|
const { generateSmartAPI } = await import('./lib/modes/generateApi.js');
|
|
69
84
|
try {
|
|
70
85
|
await generateSmartAPI(workingPath, { inject: true, config });
|
|
@@ -78,6 +93,48 @@ program
|
|
|
78
93
|
}
|
|
79
94
|
}
|
|
80
95
|
|
|
96
|
+
let shouldRunVerify = options.verify !== false;
|
|
97
|
+
if (options.verify !== false) {
|
|
98
|
+
const verifyChoice = await inquirer.prompt([
|
|
99
|
+
{
|
|
100
|
+
type: 'confirm',
|
|
101
|
+
name: 'runVerify',
|
|
102
|
+
message: 'Run post-generation health check (Phase-2)?',
|
|
103
|
+
default: true
|
|
104
|
+
}
|
|
105
|
+
]);
|
|
106
|
+
shouldRunVerify = verifyChoice.runVerify;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (shouldRunVerify) {
|
|
110
|
+
console.log(chalk.cyan('\n\n[VERIFY] Running Phase-2 verification...\n'));
|
|
111
|
+
const { verifyGeneratedBackend } = await import('./lib/utils/postGenerationVerifier.js');
|
|
112
|
+
const verification = await verifyGeneratedBackend(workingPath, {
|
|
113
|
+
timeoutMs: options.verifyTimeout,
|
|
114
|
+
maxAttempts: 2
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (verification.ok) {
|
|
118
|
+
console.log(chalk.green(`\n[OK] Phase-2 passed at http://localhost:${verification.port}${verification.healthEndpoint}\n`));
|
|
119
|
+
} else {
|
|
120
|
+
console.log(chalk.yellow('\n[WARN] Phase-2 verification did not fully pass.'));
|
|
121
|
+
console.log(chalk.yellow(` Reason: ${verification.issue}`));
|
|
122
|
+
if (verification.fixesApplied.length > 0) {
|
|
123
|
+
console.log(chalk.gray(' Auto-fixes attempted:'));
|
|
124
|
+
for (const fix of verification.fixesApplied) {
|
|
125
|
+
console.log(chalk.gray(` - ${fix}`));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
console.log(chalk.gray(' You can skip this step next time using --no-verify.\n'));
|
|
129
|
+
|
|
130
|
+
if (options.strictVerify) {
|
|
131
|
+
throw new Error(`Phase-2 verification failed: ${verification.issue}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
console.log(chalk.gray('\n[SKIP] Post-generation health check skipped by user choice.\n'));
|
|
136
|
+
}
|
|
137
|
+
|
|
81
138
|
// Auto-connect if enabled
|
|
82
139
|
if (options.autoConnect) {
|
|
83
140
|
console.log(chalk.cyan('Auto-connecting frontend & backend...\n'));
|
|
@@ -189,6 +246,64 @@ program
|
|
|
189
246
|
}
|
|
190
247
|
});
|
|
191
248
|
|
|
249
|
+
program
|
|
250
|
+
.command('industry-validate [path]')
|
|
251
|
+
.description('Run offline Industry Mode contract validation and print report')
|
|
252
|
+
.option('--json', 'Print JSON output')
|
|
253
|
+
.action(async (projectPath, options) => {
|
|
254
|
+
try {
|
|
255
|
+
const {
|
|
256
|
+
runIndustryContractCheck,
|
|
257
|
+
printIndustryReport
|
|
258
|
+
} = await import('./lib/utils/industryMode.js');
|
|
259
|
+
|
|
260
|
+
const report = await runIndustryContractCheck(projectPath || process.cwd());
|
|
261
|
+
if (options.json) {
|
|
262
|
+
console.log(JSON.stringify(report, null, 2));
|
|
263
|
+
} else {
|
|
264
|
+
printIndustryReport(report);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
process.exit(report.ok ? 0 : 1);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error(chalk.red('Error:', error.message));
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
program
|
|
275
|
+
.command('industry-smoke [path]')
|
|
276
|
+
.description('Run offline Industry Mode smoke checks and save report file')
|
|
277
|
+
.option('--timeout <ms>', 'Smoke timeout in milliseconds', parsePositiveInteger, 45000)
|
|
278
|
+
.option('--json', 'Print JSON output')
|
|
279
|
+
.action(async (projectPath, options) => {
|
|
280
|
+
try {
|
|
281
|
+
const {
|
|
282
|
+
runIndustrySmoke,
|
|
283
|
+
writeIndustryReport,
|
|
284
|
+
printIndustryReport
|
|
285
|
+
} = await import('./lib/utils/industryMode.js');
|
|
286
|
+
|
|
287
|
+
const workingPath = projectPath || process.cwd();
|
|
288
|
+
const report = await runIndustrySmoke(workingPath, {
|
|
289
|
+
timeoutMs: options.timeout
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const filePath = writeIndustryReport(workingPath, report);
|
|
293
|
+
if (options.json) {
|
|
294
|
+
console.log(JSON.stringify({ ...report, reportFile: filePath }, null, 2));
|
|
295
|
+
} else {
|
|
296
|
+
printIndustryReport(report);
|
|
297
|
+
console.log(chalk.cyan(`Report saved: ${filePath}`));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
process.exit(report.ok ? 0 : 1);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error(chalk.red('Error:', error.message));
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
192
307
|
program.parse(process.argv);
|
|
193
308
|
|
|
194
309
|
if (!process.argv.slice(2).length) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"requiredContract": [
|
|
3
|
+
"database",
|
|
4
|
+
"framework",
|
|
5
|
+
"auth",
|
|
6
|
+
"resources"
|
|
7
|
+
],
|
|
8
|
+
"minResources": 1,
|
|
9
|
+
"requireAuth": true,
|
|
10
|
+
"requiredSecurity": [
|
|
11
|
+
"validation",
|
|
12
|
+
"rateLimit",
|
|
13
|
+
"helmet",
|
|
14
|
+
"cors"
|
|
15
|
+
],
|
|
16
|
+
"smoke": {
|
|
17
|
+
"checkHealth": true,
|
|
18
|
+
"checkResourceList": true,
|
|
19
|
+
"maxResources": 10,
|
|
20
|
+
"timeoutMs": 45000
|
|
21
|
+
}
|
|
22
|
+
}
|
package/lib/ir-integration.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
/**
|
|
19
19
|
* Complete pipeline: Frontend รขโ โ IR รขโ โ Backend
|
|
20
20
|
*/
|
|
21
|
-
export async function
|
|
21
|
+
export async function offbytWithIR(frontendPath, outputPath, options = {}) {
|
|
22
22
|
console.log('\n' + '='.repeat(70));
|
|
23
23
|
console.log('รฐลธลกโฌ offbyt - IR-Based Backend Generation');
|
|
24
24
|
console.log('='.repeat(70) + '\n');
|
|
@@ -303,7 +303,7 @@ export async function quickGenerate(frontendPath, options = {}) {
|
|
|
303
303
|
const projectName = path.basename(frontendPath);
|
|
304
304
|
const outputPath = `./offbyt-${projectName}`;
|
|
305
305
|
|
|
306
|
-
return
|
|
306
|
+
return offbytWithIR(frontendPath, outputPath, {
|
|
307
307
|
projectName,
|
|
308
308
|
...options
|
|
309
309
|
});
|
|
@@ -331,7 +331,7 @@ export async function runFromCLI(args) {
|
|
|
331
331
|
const out = outputPath || `./offbyt-${path.basename(frontendPath)}`;
|
|
332
332
|
|
|
333
333
|
try {
|
|
334
|
-
await
|
|
334
|
+
await offbytWithIR(frontendPath, out, {
|
|
335
335
|
hasAuth: true,
|
|
336
336
|
projectName: path.basename(frontendPath)
|
|
337
337
|
});
|
|
@@ -345,5 +345,5 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
345
345
|
runFromCLI(process.argv.slice(2));
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
export default
|
|
348
|
+
export default offbytWithIR;
|
|
349
349
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
2
|
* Configuration-Based Backend Generator
|
|
3
3
|
* Generates production-level backend for selected tech stack
|
|
4
4
|
*/
|
|
@@ -21,61 +21,61 @@ export async function generateWithConfig(projectPath, config) {
|
|
|
21
21
|
// Step 1: Create structure
|
|
22
22
|
const step1 = ora('Creating backend structure...').start();
|
|
23
23
|
createBackendStructure(backendPath, config);
|
|
24
|
-
step1.succeed('
|
|
24
|
+
step1.succeed('Backend structure created');
|
|
25
25
|
|
|
26
26
|
// Step 2: Setup database
|
|
27
27
|
const step2 = ora('Setting up database configuration...').start();
|
|
28
28
|
setupDatabaseConfig(backendPath, config);
|
|
29
|
-
step2.succeed('
|
|
29
|
+
step2.succeed('Database configured');
|
|
30
30
|
|
|
31
31
|
// Step 3: Create middleware
|
|
32
32
|
const step3 = ora('Setting up middleware...').start();
|
|
33
33
|
setupMiddleware(backendPath, config);
|
|
34
|
-
step3.succeed('
|
|
34
|
+
step3.succeed('Middleware configured');
|
|
35
35
|
|
|
36
36
|
// Step 4: Create authentication (if enabled)
|
|
37
37
|
if (config.enableAuth) {
|
|
38
38
|
const step4 = ora('Setting up authentication system...').start();
|
|
39
39
|
setupAuthentication(backendPath, config);
|
|
40
|
-
step4.succeed('
|
|
40
|
+
step4.succeed('Authentication configured');
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// Step 5: Create main server
|
|
44
44
|
const step5 = ora('Creating main server file...').start();
|
|
45
45
|
createServerFile(backendPath, config);
|
|
46
|
-
step5.succeed('
|
|
46
|
+
step5.succeed('Server created');
|
|
47
47
|
|
|
48
48
|
// Step 6: Setup Socket.io (if enabled)
|
|
49
49
|
if (config.enableSocket) {
|
|
50
50
|
const step6 = ora('Setting up realtime sockets...').start();
|
|
51
51
|
setupSockets(backendPath, config);
|
|
52
|
-
step6.succeed('
|
|
52
|
+
step6.succeed('Sockets configured');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// Step 7: Create package.json
|
|
56
56
|
const step7 = ora('Creating package.json...').start();
|
|
57
57
|
createPackageJson(backendPath, config);
|
|
58
|
-
step7.succeed('
|
|
58
|
+
step7.succeed('Dependencies configured');
|
|
59
59
|
|
|
60
60
|
// Step 8: Create .env
|
|
61
61
|
const step8 = ora('Creating environment configuration...').start();
|
|
62
62
|
createEnvFile(backendPath, config);
|
|
63
|
-
step8.succeed('
|
|
63
|
+
step8.succeed('Environment files created (.env, .env.example, .gitignore)');
|
|
64
64
|
|
|
65
65
|
// Step 9: Scan frontend and generate detected resources (skip - will be done by smart API)
|
|
66
66
|
const step9 = ora('Skipping sample generation (using Smart API detection instead)...').start();
|
|
67
|
-
step9.succeed('
|
|
67
|
+
step9.succeed('Backend structure ready for Smart API generation');
|
|
68
68
|
|
|
69
69
|
// Step 10: Generate SQL files for SQL databases
|
|
70
70
|
if (['mysql', 'postgresql', 'sqlite'].includes(config.database)) {
|
|
71
71
|
const step10 = ora('Generating SQL scripts...').start();
|
|
72
|
-
step10.succeed(
|
|
72
|
+
step10.succeed(`SQL scripts created in backend/sql/ (ready for ${config.database.toUpperCase()})`);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
console.log(chalk.cyan('\n
|
|
76
|
-
console.log(chalk.cyan('
|
|
75
|
+
console.log(chalk.cyan('\n[OK] Backend structure created.\n'));
|
|
76
|
+
console.log(chalk.cyan('[OK] Smart API detection will run automatically next...\n'));
|
|
77
77
|
|
|
78
|
-
console.log(chalk.yellow('
|
|
78
|
+
console.log(chalk.yellow('Next steps:'));
|
|
79
79
|
console.log(chalk.yellow(` 1. cd ${projectPath}/backend`));
|
|
80
80
|
console.log(chalk.yellow(' 2. npm install'));
|
|
81
81
|
console.log(chalk.yellow(' 3. Review & update .env file:'));
|
|
@@ -95,10 +95,10 @@ export async function generateWithConfig(projectPath, config) {
|
|
|
95
95
|
console.log(chalk.yellow(' 4. npm run dev'));
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
console.log(chalk.cyan('\
|
|
98
|
+
console.log(chalk.cyan('\nTip: Never commit .env to git! Use .env.example instead.\n'));
|
|
99
99
|
|
|
100
100
|
} catch (error) {
|
|
101
|
-
console.error(chalk.red('
|
|
101
|
+
console.error(chalk.red('Error generating backend:'), error.message);
|
|
102
102
|
throw error;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -364,9 +364,9 @@ app.use(requestLogger);
|
|
|
364
364
|
|
|
365
365
|
// Database Connection
|
|
366
366
|
connectDatabase().then(() => {
|
|
367
|
-
console.log('
|
|
367
|
+
console.log('[OK] Database connected');
|
|
368
368
|
}).catch(error => {
|
|
369
|
-
console.error('
|
|
369
|
+
console.error('[ERR] Database connection failed:', error);
|
|
370
370
|
process.exit(1);
|
|
371
371
|
});
|
|
372
372
|
|
|
@@ -397,11 +397,11 @@ io.on('connection', (socket) => {
|
|
|
397
397
|
});
|
|
398
398
|
|
|
399
399
|
server.listen(PORT, () => {
|
|
400
|
-
console.log(
|
|
400
|
+
console.log(\`[OK] Server running on http://localhost:\${PORT}\`);
|
|
401
401
|
});`;
|
|
402
402
|
} else {
|
|
403
403
|
serverContent += `app.listen(PORT, () => {
|
|
404
|
-
console.log(
|
|
404
|
+
console.log(\`[OK] Server running on http://localhost:\${PORT}\`);
|
|
405
405
|
});`;
|
|
406
406
|
}
|
|
407
407
|
|
|
@@ -430,9 +430,9 @@ await fastify.register(fastifyCors, { origin: '*' });
|
|
|
430
430
|
|
|
431
431
|
// Database Connection
|
|
432
432
|
connectDatabase().then(() => {
|
|
433
|
-
fastify.log.info('
|
|
433
|
+
fastify.log.info('[OK] Database connected');
|
|
434
434
|
}).catch(error => {
|
|
435
|
-
fastify.log.error('
|
|
435
|
+
fastify.log.error('[ERR] Database connection failed:', error);
|
|
436
436
|
process.exit(1);
|
|
437
437
|
});
|
|
438
438
|
|
|
@@ -468,7 +468,7 @@ io.on('connection', (socket) => {
|
|
|
468
468
|
serverContent += `// Start server
|
|
469
469
|
try {
|
|
470
470
|
await fastify.listen({ port: PORT, host: '0.0.0.0' });
|
|
471
|
-
console.log(
|
|
471
|
+
console.log(\`[OK] Fastify server running on http://localhost:\${PORT}\`);
|
|
472
472
|
} catch (err) {
|
|
473
473
|
fastify.log.error(err);
|
|
474
474
|
process.exit(1);
|
|
@@ -535,7 +535,7 @@ async function bootstrap() {
|
|
|
535
535
|
});
|
|
536
536
|
});
|
|
537
537
|
` : ''}
|
|
538
|
-
console.log(
|
|
538
|
+
console.log(\`[OK] NestJS server running on http://localhost:\${PORT}\`);
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
bootstrap();`;
|
|
@@ -815,7 +815,7 @@ async function detectAndGenerateResources(projectPath, backendPath, config) {
|
|
|
815
815
|
generateRoute(backendPath, resourceName, resourceInfo, config, apiAnalysis);
|
|
816
816
|
generatedResources.push(resourceName);
|
|
817
817
|
} catch (e) {
|
|
818
|
-
console.warn(
|
|
818
|
+
console.warn(`[WARN] Skipped ${resourceName}:`, e.message);
|
|
819
819
|
}
|
|
820
820
|
});
|
|
821
821
|
|
|
@@ -847,7 +847,7 @@ async function detectAndGenerateResources(projectPath, backendPath, config) {
|
|
|
847
847
|
|
|
848
848
|
return { resources: generatedResources };
|
|
849
849
|
} catch (error) {
|
|
850
|
-
console.warn('
|
|
850
|
+
console.warn('[WARN] Auto-detection error:', error.message);
|
|
851
851
|
createSampleResources(backendPath, config);
|
|
852
852
|
return { resources: ['User (sample)'] };
|
|
853
853
|
}
|
|
@@ -2251,7 +2251,7 @@ npm run dev
|
|
|
2251
2251
|
## Environment Variables
|
|
2252
2252
|
See \`../.env\` for required environment variables.
|
|
2253
2253
|
|
|
2254
|
-
Generated by offbyt
|
|
2254
|
+
Generated by offbyt`;
|
|
2255
2255
|
|
|
2256
2256
|
fs.writeFileSync(path.join(backendPath, 'README.md'), readme);
|
|
2257
2257
|
}
|
package/lib/modes/connect.js
CHANGED
|
@@ -18,36 +18,38 @@ import ora from 'ora';
|
|
|
18
18
|
// ============================================================
|
|
19
19
|
export async function connectFrontendBackend(projectPath) {
|
|
20
20
|
try {
|
|
21
|
-
console.log(chalk.cyan('\n
|
|
22
|
-
console.log(chalk.
|
|
21
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
22
|
+
console.log(chalk.cyan('OFFBYT AUTO-CONNECTION ENGINE'));
|
|
23
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
24
|
+
console.log(chalk.gray('Scanner: Frontend -> Backend Connection Analysis\n'));
|
|
23
25
|
|
|
24
26
|
const backendPath = path.join(projectPath, 'backend');
|
|
25
27
|
|
|
26
28
|
// Verify project structure
|
|
27
29
|
if (!fs.existsSync(backendPath)) {
|
|
28
|
-
console.error(chalk.red('
|
|
30
|
+
console.error(chalk.red('Backend not found. Run "offbyt generate" first.\n'));
|
|
29
31
|
process.exit(1);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
// Step 1: Scan React components
|
|
33
35
|
const step1 = ora('Step 1/5: Scanning React components...').start();
|
|
34
36
|
const reactAnalysis = scanReactComponents(projectPath);
|
|
35
|
-
step1.succeed(
|
|
37
|
+
step1.succeed(`Found ${reactAnalysis.components.length} API components`);
|
|
36
38
|
|
|
37
39
|
// Step 2: Scan backend structure
|
|
38
40
|
const step2 = ora('Step 2/5: Analyzing backend routes...').start();
|
|
39
41
|
const backendAnalysis = scanBackendRoutes(backendPath);
|
|
40
|
-
step2.succeed(
|
|
42
|
+
step2.succeed(`Found ${backendAnalysis.routes.length} backend routes`);
|
|
41
43
|
|
|
42
44
|
// Step 3: Detect mismatches
|
|
43
45
|
const step3 = ora('Step 3/5: Analyzing mismatches...').start();
|
|
44
46
|
const mismatches = detectMismatches(reactAnalysis, backendAnalysis);
|
|
45
|
-
step3.succeed(
|
|
47
|
+
step3.succeed(`Detected ${mismatches.issues.length} issues`);
|
|
46
48
|
|
|
47
49
|
// Step 4: Auto-fix issues
|
|
48
50
|
const step4 = ora('Step 4/5: Auto-fixing components...').start();
|
|
49
51
|
const fixResults = applyAutoFixes(projectPath, backendPath, mismatches, reactAnalysis, backendAnalysis);
|
|
50
|
-
step4.succeed(
|
|
52
|
+
step4.succeed(`Fixed ${fixResults.fixed} issues`);
|
|
51
53
|
|
|
52
54
|
// Detect Vite
|
|
53
55
|
let isVite = false;
|
|
@@ -64,18 +66,18 @@ export async function connectFrontendBackend(projectPath) {
|
|
|
64
66
|
// Step 5: Create/Update .env
|
|
65
67
|
const step5 = ora('Step 5/5: Configuring environment...').start();
|
|
66
68
|
createFrontendEnv(projectPath, backendAnalysis.serverPort, isVite);
|
|
67
|
-
step5.succeed('
|
|
69
|
+
step5.succeed('.env file configured');
|
|
68
70
|
|
|
69
71
|
// Summary
|
|
70
|
-
console.log(chalk.green('\n
|
|
71
|
-
console.log(chalk.gray('
|
|
72
|
+
console.log(chalk.green('\n[OK] Auto-Connection Complete!\n'));
|
|
73
|
+
console.log(chalk.gray('------------------------------------------------------------'));
|
|
72
74
|
console.log(chalk.white(' Components Updated:'), reactAnalysis.components.length);
|
|
73
75
|
console.log(chalk.white(' Issues Fixed:'), fixResults.fixed);
|
|
74
76
|
console.log(chalk.white(' API URL Configured:'), `http://localhost:${backendAnalysis.serverPort}`);
|
|
75
|
-
console.log(chalk.gray('
|
|
77
|
+
console.log(chalk.gray('------------------------------------------------------------\n'));
|
|
76
78
|
|
|
77
79
|
if (mismatches.issues.length > 0) {
|
|
78
|
-
console.log(chalk.yellow('
|
|
80
|
+
console.log(chalk.yellow('[WARN] Issues Found (Auto-Fixed):\n'));
|
|
79
81
|
mismatches.issues.forEach((issue, i) => {
|
|
80
82
|
console.log(chalk.gray(` ${i+1}. ${issue.type}`));
|
|
81
83
|
console.log(chalk.gray(` File: ${issue.file}`));
|
|
@@ -83,13 +85,13 @@ export async function connectFrontendBackend(projectPath) {
|
|
|
83
85
|
});
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
console.log(chalk.cyan('
|
|
88
|
+
console.log(chalk.cyan('Ready to start:\n'));
|
|
87
89
|
console.log(chalk.white(' Frontend:', chalk.bold(`npm start`)));
|
|
88
90
|
console.log(chalk.white(' Backend:', chalk.bold(`npm start`)));
|
|
89
91
|
console.log(chalk.cyan('\n'));
|
|
90
92
|
|
|
91
93
|
} catch (error) {
|
|
92
|
-
console.error(chalk.red('
|
|
94
|
+
console.error(chalk.red('Connection Error:'), error.message);
|
|
93
95
|
process.exit(1);
|
|
94
96
|
}
|
|
95
97
|
}
|
|
@@ -152,7 +154,7 @@ function scanReactComponents(projectPath) {
|
|
|
152
154
|
});
|
|
153
155
|
}
|
|
154
156
|
} catch (error) {
|
|
155
|
-
console.warn(chalk.gray(`
|
|
157
|
+
console.warn(chalk.gray(` [WARN] Skipped: ${path.relative(projectPath, file)}`));
|
|
156
158
|
}
|
|
157
159
|
});
|
|
158
160
|
|
|
@@ -306,7 +308,7 @@ function scanBackendRoutes(backendPath) {
|
|
|
306
308
|
|
|
307
309
|
return { routes, models, serverPort };
|
|
308
310
|
} catch (error) {
|
|
309
|
-
console.warn(chalk.yellow(
|
|
311
|
+
console.warn(chalk.yellow(`[WARN] Backend scan error: ${error.message}`));
|
|
310
312
|
return { routes: [], models: [], serverPort: 5000 };
|
|
311
313
|
}
|
|
312
314
|
}
|
|
@@ -581,7 +583,7 @@ function applyAutoFixes(projectPath, backendPath, mismatches, reactAnalysis, bac
|
|
|
581
583
|
}
|
|
582
584
|
}
|
|
583
585
|
} catch (error) {
|
|
584
|
-
console.warn(chalk.gray(`
|
|
586
|
+
console.warn(chalk.gray(` [WARN] Could not fix: ${issue.file} - ${error.message}`));
|
|
585
587
|
}
|
|
586
588
|
});
|
|
587
589
|
|
|
@@ -613,10 +615,10 @@ function applyAutoFixes(projectPath, backendPath, mismatches, reactAnalysis, bac
|
|
|
613
615
|
function fixResponseParsing(content) {
|
|
614
616
|
// Fix common response parsing patterns
|
|
615
617
|
|
|
616
|
-
// Pattern 1: res.data.data.token
|
|
618
|
+
// Pattern 1: res.data.data.token รขโ โ res.data.token (if response is {data: {...}})
|
|
617
619
|
content = content.replace(/data\.data\.(\w+)/g, 'data.$1');
|
|
618
620
|
|
|
619
|
-
// Pattern 2: response.data.data.user
|
|
621
|
+
// Pattern 2: response.data.data.user รขโ โ response.data.user
|
|
620
622
|
content = content.replace(/response\.data\.data\.(\w+)/g, 'response.data.$1');
|
|
621
623
|
|
|
622
624
|
// Pattern 3: const x = await response.json();
|
|
@@ -639,7 +641,7 @@ function fixApiUrls(content, isVite = false) {
|
|
|
639
641
|
// Ensure API calls use environment variables for base URL
|
|
640
642
|
const API_URL = isVite ? 'import.meta.env.VITE_API_URL' : 'process.env.REACT_APP_API_URL';
|
|
641
643
|
|
|
642
|
-
// Pattern 1: fetch('/api/...
|
|
644
|
+
// Pattern 1: fetch('/api/... รขโ โ fetch(`/api/...
|
|
643
645
|
content = content.replace(
|
|
644
646
|
/fetch\s*\(\s*['"`](\/api\/[^'"`]+)['"`]/g,
|
|
645
647
|
`fetch(\`\${${API_URL}}$1\``
|
|
@@ -661,7 +663,7 @@ function fixApiUrls(content, isVite = false) {
|
|
|
661
663
|
`fetch(\`\${${API_URL}}$1\``
|
|
662
664
|
);
|
|
663
665
|
|
|
664
|
-
// Pattern 3: const url = '/api/...
|
|
666
|
+
// Pattern 3: const url = '/api/... รขโ โ const url = `/api/...
|
|
665
667
|
content = content.replace(
|
|
666
668
|
/(const|let|var)\s+(\w+)\s*=\s*['"`](\/api\/[^'"`]+)['"`]/g,
|
|
667
669
|
`$1 $2 = \`\${${API_URL}}$3\``
|
|
@@ -1020,9 +1022,9 @@ function escapeRegex(string) {
|
|
|
1020
1022
|
/**
|
|
1021
1023
|
* Parse API path to extract resource name and route path
|
|
1022
1024
|
* Examples:
|
|
1023
|
-
* /api/admin/dashboard
|
|
1024
|
-
* /api/user/invoices
|
|
1025
|
-
* /api/products/:id
|
|
1025
|
+
* /api/admin/dashboard รขโ โ {resourceName: 'admin', routePath: '/dashboard'}
|
|
1026
|
+
* /api/user/invoices รขโ โ {resourceName: 'user', routePath: '/invoices'}
|
|
1027
|
+
* /api/products/:id รขโ โ {resourceName: 'products', routePath: '/:id'}
|
|
1026
1028
|
*/
|
|
1027
1029
|
function parseApiPath(apiPath) {
|
|
1028
1030
|
const normalizedPath = normalizeRoutePath(apiPath);
|
|
@@ -1122,4 +1124,3 @@ router.${methodLower}('${cleanPath}', async (req, res) => {
|
|
|
1122
1124
|
}
|
|
1123
1125
|
});`;
|
|
1124
1126
|
}
|
|
1125
|
-
|