offbyt 1.0.0 โ 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +122 -7
- package/industry-mode.json +22 -0
- package/lib/modes/configBasedGenerator.js +38 -38
- package/lib/modes/connect.js +26 -25
- package/lib/modes/generateApi.js +218 -77
- package/lib/modes/interactiveSetup.js +37 -38
- 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,8 +22,17 @@ 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
|
-
.name('
|
|
35
|
+
.name('offbyte')
|
|
26
36
|
.description('Hybrid Backend Generator - Offline + AI Powered')
|
|
27
37
|
.version('1.0.0');
|
|
28
38
|
|
|
@@ -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,18 +246,76 @@ 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) {
|
|
195
310
|
program.outputHelp();
|
|
196
311
|
console.log(chalk.cyan('\nQuick Start:\n'));
|
|
197
312
|
console.log(chalk.white(' Option 1 (Recommended):'));
|
|
198
|
-
console.log(chalk.gray('
|
|
313
|
+
console.log(chalk.gray(' offbyte generate # Generate + Auto-connect\n'));
|
|
199
314
|
console.log(chalk.white(' Option 2 (Skip auto-connect):'));
|
|
200
|
-
console.log(chalk.gray('
|
|
315
|
+
console.log(chalk.gray(' offbyte generate --no-auto-connect # Generate only\n'));
|
|
201
316
|
console.log(chalk.white(' Option 3 (Just connect):'));
|
|
202
|
-
console.log(chalk.gray('
|
|
317
|
+
console.log(chalk.gray(' offbyte connect [path] # Auto-connect existing project\n'));
|
|
203
318
|
console.log(chalk.white(' Option 4 (Deploy live):'));
|
|
204
|
-
console.log(chalk.gray('
|
|
319
|
+
console.log(chalk.gray(' offbyte deploy [path] # Deploy + auto-connect URLs\n'));
|
|
205
320
|
}
|
|
206
321
|
|
|
@@ -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
|
+
}
|
|
@@ -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();`;
|
|
@@ -553,14 +553,14 @@ ${['postgresql', 'mysql', 'sqlite'].includes(config.database) ? `import { TypeOr
|
|
|
553
553
|
ConfigModule.forRoot({
|
|
554
554
|
isGlobal: true
|
|
555
555
|
}),
|
|
556
|
-
${config.database === 'mongodb' ? ` MongooseModule.forRoot(process.env.MONGODB_URI || 'mongodb://localhost:27017/
|
|
556
|
+
${config.database === 'mongodb' ? ` MongooseModule.forRoot(process.env.MONGODB_URI || 'mongodb://localhost:27017/offbyte'),` : ''}
|
|
557
557
|
${config.database === 'postgresql' ? ` TypeOrmModule.forRoot({
|
|
558
558
|
type: 'postgres',
|
|
559
559
|
host: process.env.DB_HOST || 'localhost',
|
|
560
560
|
port: parseInt(process.env.DB_PORT) || 5432,
|
|
561
561
|
username: process.env.DB_USER || 'postgres',
|
|
562
562
|
password: process.env.DB_PASSWORD || 'password',
|
|
563
|
-
database: process.env.DB_NAME || '
|
|
563
|
+
database: process.env.DB_NAME || 'offbyte',
|
|
564
564
|
autoLoadEntities: true,
|
|
565
565
|
synchronize: process.env.NODE_ENV === 'development'
|
|
566
566
|
}),` : ''}
|
|
@@ -570,7 +570,7 @@ ${config.database === 'mysql' ? ` TypeOrmModule.forRoot({
|
|
|
570
570
|
port: parseInt(process.env.DB_PORT) || 3306,
|
|
571
571
|
username: process.env.DB_USER || 'root',
|
|
572
572
|
password: process.env.DB_PASSWORD || 'password',
|
|
573
|
-
database: process.env.DB_NAME || '
|
|
573
|
+
database: process.env.DB_NAME || 'offbyte',
|
|
574
574
|
autoLoadEntities: true,
|
|
575
575
|
synchronize: process.env.NODE_ENV === 'development'
|
|
576
576
|
}),` : ''}
|
|
@@ -695,9 +695,9 @@ function createPackageJson(backendPath, config) {
|
|
|
695
695
|
}
|
|
696
696
|
|
|
697
697
|
const packageJson = {
|
|
698
|
-
name: '
|
|
698
|
+
name: 'offbyte-backend',
|
|
699
699
|
version: '1.0.0',
|
|
700
|
-
description: 'Production-ready backend generated by
|
|
700
|
+
description: 'Production-ready backend generated by offbyte',
|
|
701
701
|
type: config.framework === 'nestjs' ? 'commonjs' : 'module',
|
|
702
702
|
main: config.framework === 'nestjs' ? 'dist/main.js' : 'server.js',
|
|
703
703
|
scripts,
|
|
@@ -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
|
}
|
|
@@ -1652,7 +1652,7 @@ function generateSchemaSQL(resources, dbType, apiAnalysis = { relationships: []
|
|
|
1652
1652
|
const isMySQL = dbType === 'mysql';
|
|
1653
1653
|
|
|
1654
1654
|
let sql = `-- ============================================
|
|
1655
|
-
--
|
|
1655
|
+
-- offbyte ${dbType.toUpperCase()} Schema
|
|
1656
1656
|
-- Generated for ${dbType === 'mysql' ? 'MySQL' : dbType === 'postgresql' ? 'PostgreSQL' : 'SQLite'} Database
|
|
1657
1657
|
-- Auto-generated from detected frontend resources
|
|
1658
1658
|
-- ============================================
|
|
@@ -1921,7 +1921,7 @@ function generateConversationRelationships(dbType) {
|
|
|
1921
1921
|
|
|
1922
1922
|
function generateCRUDSQL(resources, dbType, apiAnalysis = { relationships: [] }) {
|
|
1923
1923
|
let sql = `-- ============================================\n`;
|
|
1924
|
-
sql += `--
|
|
1924
|
+
sql += `-- offbyte ${dbType.toUpperCase()} CRUD Operations\n`;
|
|
1925
1925
|
sql += `-- All Create, Read, Update, Delete Queries\n`;
|
|
1926
1926
|
sql += `-- Ready to use in your application\n`;
|
|
1927
1927
|
sql += `-- ============================================\n\n`;
|
|
@@ -2000,7 +2000,7 @@ function generateSampleValue(fieldName) {
|
|
|
2000
2000
|
|
|
2001
2001
|
function generateJoinSQL(resources, dbType, apiAnalysis = { relationships: [] }) {
|
|
2002
2002
|
let sql = `-- ============================================\n`;
|
|
2003
|
-
sql += `--
|
|
2003
|
+
sql += `-- offbyte ${dbType.toUpperCase()} Relationships & JOINs\n`;
|
|
2004
2004
|
sql += `-- Complex queries with table relationships\n`;
|
|
2005
2005
|
sql += `-- ============================================\n\n`;
|
|
2006
2006
|
|
|
@@ -2059,7 +2059,7 @@ function generateJoinSQL(resources, dbType, apiAnalysis = { relationships: [] })
|
|
|
2059
2059
|
|
|
2060
2060
|
function generateSeedSQL(resources, dbType, apiAnalysis = { relationships: [] }) {
|
|
2061
2061
|
let sql = `-- ============================================\n`;
|
|
2062
|
-
sql += `--
|
|
2062
|
+
sql += `-- offbyte ${dbType.toUpperCase()} Sample Data\n`;
|
|
2063
2063
|
sql += `-- Seed data for testing and development\n`;
|
|
2064
2064
|
sql += `-- ============================================\n\n`;
|
|
2065
2065
|
|
|
@@ -2182,7 +2182,7 @@ export const User = sequelize.define('User', {
|
|
|
2182
2182
|
const router = express.Router();
|
|
2183
2183
|
|
|
2184
2184
|
router.get('/', (req, res) => {
|
|
2185
|
-
res.json({ message: 'Welcome to
|
|
2185
|
+
res.json({ message: 'Welcome to offbyte API' });
|
|
2186
2186
|
});
|
|
2187
2187
|
|
|
2188
2188
|
export default router;`;
|
|
@@ -2211,9 +2211,9 @@ export const createUser = async (req, res) => {
|
|
|
2211
2211
|
fs.writeFileSync(path.join(backendPath, 'controllers', 'userController.js'), sampleController);
|
|
2212
2212
|
|
|
2213
2213
|
// README
|
|
2214
|
-
const readme = `#
|
|
2214
|
+
const readme = `# offbyte Generated Backend
|
|
2215
2215
|
|
|
2216
|
-
This is a production-ready backend generated by
|
|
2216
|
+
This is a production-ready backend generated by offbyte.
|
|
2217
2217
|
|
|
2218
2218
|
## Configuration
|
|
2219
2219
|
- Database: ${config.database}
|
|
@@ -2251,7 +2251,7 @@ npm run dev
|
|
|
2251
2251
|
## Environment Variables
|
|
2252
2252
|
See \`../.env\` for required environment variables.
|
|
2253
2253
|
|
|
2254
|
-
Generated by
|
|
2254
|
+
Generated by offbyte`;
|
|
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('OFFBYTE 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 "offbyte 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
|
-
|