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.
- package/README.md +2 -0
- package/cli/index.js +2 -0
- package/cli.js +206 -0
- package/core/detector/detectAxios.js +107 -0
- package/core/detector/detectFetch.js +148 -0
- package/core/detector/detectForms.js +55 -0
- package/core/detector/detectSocket.js +341 -0
- package/core/generator/generateControllers.js +17 -0
- package/core/generator/generateModels.js +25 -0
- package/core/generator/generateRoutes.js +17 -0
- package/core/generator/generateServer.js +18 -0
- package/core/generator/generateSocket.js +160 -0
- package/core/index.js +14 -0
- package/core/ir/IRTypes.js +25 -0
- package/core/ir/buildIR.js +83 -0
- package/core/parser/parseJS.js +26 -0
- package/core/parser/parseTS.js +27 -0
- package/core/rules/relationRules.js +38 -0
- package/core/rules/resourceRules.js +32 -0
- package/core/rules/schemaInference.js +26 -0
- package/core/scanner/scanProject.js +58 -0
- package/deploy/cloudflare.js +41 -0
- package/deploy/cloudflareWorker.js +122 -0
- package/deploy/connect.js +198 -0
- package/deploy/flyio.js +51 -0
- package/deploy/index.js +322 -0
- package/deploy/netlify.js +29 -0
- package/deploy/railway.js +215 -0
- package/deploy/render.js +195 -0
- package/deploy/utils.js +383 -0
- package/deploy/vercel.js +29 -0
- package/index.js +18 -0
- package/lib/generator/advancedCrudGenerator.js +475 -0
- package/lib/generator/crudCodeGenerator.js +486 -0
- package/lib/generator/irBasedGenerator.js +360 -0
- package/lib/ir-builder/index.js +16 -0
- package/lib/ir-builder/irBuilder.js +330 -0
- package/lib/ir-builder/rulesEngine.js +353 -0
- package/lib/ir-builder/templateEngine.js +193 -0
- package/lib/ir-builder/templates/index.js +14 -0
- package/lib/ir-builder/templates/model.template.js +47 -0
- package/lib/ir-builder/templates/routes-generic.template.js +66 -0
- package/lib/ir-builder/templates/routes-user.template.js +105 -0
- package/lib/ir-builder/templates/routes.template.js +102 -0
- package/lib/ir-builder/templates/validation.template.js +15 -0
- package/lib/ir-integration.js +349 -0
- package/lib/modes/benchmark.js +162 -0
- package/lib/modes/configBasedGenerator.js +2258 -0
- package/lib/modes/connect.js +1125 -0
- package/lib/modes/doctorAi.js +172 -0
- package/lib/modes/generateApi.js +435 -0
- package/lib/modes/interactiveSetup.js +548 -0
- package/lib/modes/offline.clean.js +14 -0
- package/lib/modes/offline.enhanced.js +787 -0
- package/lib/modes/offline.js +295 -0
- package/lib/modes/offline.v2.js +13 -0
- package/lib/modes/sync.js +629 -0
- package/lib/scanner/apiEndpointExtractor.js +387 -0
- package/lib/scanner/authPatternDetector.js +54 -0
- package/lib/scanner/frontendScanner.js +642 -0
- package/lib/utils/apiClientGenerator.js +242 -0
- package/lib/utils/apiScanner.js +95 -0
- package/lib/utils/codeInjector.js +350 -0
- package/lib/utils/doctor.js +381 -0
- package/lib/utils/envGenerator.js +36 -0
- package/lib/utils/loadTester.js +61 -0
- package/lib/utils/performanceAnalyzer.js +298 -0
- package/lib/utils/resourceDetector.js +281 -0
- package/package.json +20 -0
- package/templates/.env.template +31 -0
- package/templates/advanced.model.template.js +201 -0
- package/templates/advanced.route.template.js +341 -0
- package/templates/auth.middleware.template.js +87 -0
- package/templates/auth.routes.template.js +238 -0
- package/templates/auth.user.model.template.js +78 -0
- package/templates/cache.middleware.js +34 -0
- package/templates/chat.models.template.js +260 -0
- package/templates/chat.routes.template.js +478 -0
- package/templates/compression.middleware.js +19 -0
- package/templates/database.config.js +74 -0
- package/templates/errorHandler.middleware.js +54 -0
- package/templates/express/controller.ejs +26 -0
- package/templates/express/model.ejs +9 -0
- package/templates/express/route.ejs +18 -0
- package/templates/express/server.ejs +16 -0
- package/templates/frontend.env.template +14 -0
- package/templates/model.template.js +86 -0
- package/templates/package.production.json +51 -0
- package/templates/package.template.json +41 -0
- package/templates/pagination.utility.js +110 -0
- package/templates/production.server.template.js +233 -0
- package/templates/rateLimiter.middleware.js +36 -0
- package/templates/requestLogger.middleware.js +19 -0
- package/templates/response.helper.js +179 -0
- package/templates/route.template.js +130 -0
- package/templates/security.middleware.js +78 -0
- package/templates/server.template.js +91 -0
- package/templates/socket.server.template.js +433 -0
- package/templates/utils.helper.js +157 -0
- package/templates/validation.middleware.js +63 -0
- package/templates/validation.schema.js +128 -0
- package/utils/fileWriter.js +15 -0
- package/utils/logger.js +18 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Routes Template - With Auth
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const ROUTES_USER_TEMPLATE = `import express from 'express';
|
|
6
|
+
import User from '../models/User.model.js';
|
|
7
|
+
import bcryptjs from 'bcryptjs';
|
|
8
|
+
import jwt from 'jsonwebtoken';
|
|
9
|
+
|
|
10
|
+
const router = express.Router();
|
|
11
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-key';
|
|
12
|
+
const JWT_EXPIRE = process.env.JWT_EXPIRE || process.env.JWT_EXPIRES_IN || '7d';
|
|
13
|
+
|
|
14
|
+
// POST signup
|
|
15
|
+
router.post('/signup', async (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
const { username, email, password, firstName, lastName, role } = req.body;
|
|
18
|
+
const existing = await User.findOne({ email });
|
|
19
|
+
if (existing) return res.status(400).json({ success: false, error: 'Email already registered' });
|
|
20
|
+
const hashedPassword = await bcryptjs.hash(password, 10);
|
|
21
|
+
const userRole = role || 'student';
|
|
22
|
+
const data = new User({ username, email, password: hashedPassword, firstName, lastName, role: userRole });
|
|
23
|
+
await data.save();
|
|
24
|
+
const token = jwt.sign({ id: data._id, email: data.email }, JWT_SECRET, { expiresIn: JWT_EXPIRE });
|
|
25
|
+
res.status(201).json({ success: true, message: 'User created', data: { id: data._id, email: data.email, firstName: data.firstName, lastName: data.lastName, role: userRole }, token });
|
|
26
|
+
} catch (error) {
|
|
27
|
+
res.status(400).json({ success: false, error: error.message });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// POST login
|
|
32
|
+
router.post('/login', async (req, res) => {
|
|
33
|
+
try {
|
|
34
|
+
const { email, password } = req.body;
|
|
35
|
+
const user = await User.findOne({ email });
|
|
36
|
+
if (!user) return res.status(401).json({ success: false, error: 'Invalid credentials' });
|
|
37
|
+
const isValid = await bcryptjs.compare(password, user.password);
|
|
38
|
+
if (!isValid) return res.status(401).json({ success: false, error: 'Invalid credentials' });
|
|
39
|
+
const token = jwt.sign({ id: user._id, email: user.email }, JWT_SECRET, { expiresIn: JWT_EXPIRE });
|
|
40
|
+
res.json({ success: true, message: 'Login successful', data: { id: user._id, email: user.email, firstName: user.firstName, lastName: user.lastName, role: user.role }, token });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
res.status(500).json({ success: false, error: error.message });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// GET all users
|
|
47
|
+
router.get('/', async (req, res) => {
|
|
48
|
+
try {
|
|
49
|
+
const { skip = 0, limit = 100 } = req.query;
|
|
50
|
+
const data = await User.find({ isDeleted: false }).skip(parseInt(skip)).limit(parseInt(limit));
|
|
51
|
+
const total = await User.countDocuments({ isDeleted: false });
|
|
52
|
+
res.json({ success: true, data, total, skip: parseInt(skip), limit: parseInt(limit) });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
res.status(500).json({ success: false, error: error.message });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// GET single user
|
|
59
|
+
router.get('/:id', async (req, res) => {
|
|
60
|
+
try {
|
|
61
|
+
const data = await User.findById(req.params.id);
|
|
62
|
+
if (!data) return res.status(404).json({ success: false, error: 'Not found' });
|
|
63
|
+
res.json({ success: true, data });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
res.status(500).json({ success: false, error: error.message });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// POST create user
|
|
70
|
+
router.post('/', async (req, res) => {
|
|
71
|
+
try {
|
|
72
|
+
const data = new User(req.body);
|
|
73
|
+
if (data.password) {
|
|
74
|
+
data.password = await bcryptjs.hash(data.password, 10);
|
|
75
|
+
}
|
|
76
|
+
await data.save();
|
|
77
|
+
res.status(201).json({ success: true, data, message: 'Created successfully' });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
res.status(400).json({ success: false, error: error.message });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// PUT update user
|
|
84
|
+
router.put('/:id', async (req, res) => {
|
|
85
|
+
try {
|
|
86
|
+
const data = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
|
87
|
+
if (!data) return res.status(404).json({ success: false, error: 'Not found' });
|
|
88
|
+
res.json({ success: true, data, message: 'Updated successfully' });
|
|
89
|
+
} catch (error) {
|
|
90
|
+
res.status(400).json({ success: false, error: error.message });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// DELETE user
|
|
95
|
+
router.delete('/:id', async (req, res) => {
|
|
96
|
+
try {
|
|
97
|
+
await User.findByIdAndUpdate(req.params.id, { isDeleted: true });
|
|
98
|
+
res.json({ success: true, message: 'Deleted successfully' });
|
|
99
|
+
} catch (error) {
|
|
100
|
+
res.status(500).json({ success: false, error: error.message });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export default router;
|
|
105
|
+
`;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express Routes Template
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const ROUTES_TEMPLATE = `import express from 'express';
|
|
6
|
+
import <%= capitalize(resource.singular) %> from '../models/<%= capitalize(resource.singular) %>.model.js';
|
|
7
|
+
import bcryptjs from 'bcryptjs';
|
|
8
|
+
import jwt from 'jsonwebtoken';
|
|
9
|
+
|
|
10
|
+
const router = express.Router();
|
|
11
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-key';
|
|
12
|
+
const JWT_EXPIRE = process.env.JWT_EXPIRE || process.env.JWT_EXPIRES_IN || '7d';
|
|
13
|
+
|
|
14
|
+
// POST signup (for users resource only)
|
|
15
|
+
router.post('/signup', async (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
const { username, email, password, firstName, lastName } = req.body;
|
|
18
|
+
const existing = await <%= capitalize(resource.singular) %>.findOne({ email });
|
|
19
|
+
if (existing) return res.status(400).json({ success: false, error: 'Email already registered' });
|
|
20
|
+
const hashedPassword = await bcryptjs.hash(password, 10);
|
|
21
|
+
const data = new <%= capitalize(resource.singular) %>({ username, email, password: hashedPassword, firstName, lastName, role: 'student' });
|
|
22
|
+
await data.save();
|
|
23
|
+
const token = jwt.sign({ id: data._id, email: data.email }, JWT_SECRET, { expiresIn: JWT_EXPIRE });
|
|
24
|
+
res.status(201).json({ success: true, message: 'User created', data: { id: data._id, email: data.email, firstName: data.firstName, lastName: data.lastName }, token });
|
|
25
|
+
} catch (error) {
|
|
26
|
+
res.status(400).json({ success: false, error: error.message });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// POST login (for users resource only)
|
|
31
|
+
router.post('/login', async (req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const { email, password } = req.body;
|
|
34
|
+
const user = await <%= capitalize(resource.singular) %>.findOne({ email });
|
|
35
|
+
if (!user) return res.status(401).json({ success: false, error: 'Invalid credentials' });
|
|
36
|
+
const isValid = await bcryptjs.compare(password, user.password);
|
|
37
|
+
if (!isValid) return res.status(401).json({ success: false, error: 'Invalid credentials' });
|
|
38
|
+
const token = jwt.sign({ id: user._id, email: user.email }, JWT_SECRET, { expiresIn: JWT_EXPIRE });
|
|
39
|
+
res.json({ success: true, message: 'Login successful', data: { id: user._id, email: user.email, firstName: user.firstName, lastName: user.lastName }, token });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
res.status(500).json({ success: false, error: error.message });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// GET all <%= resource.plural %>
|
|
46
|
+
router.get('/', async (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
const { skip = 0, limit = 100 } = req.query;
|
|
49
|
+
const data = await <%= capitalize(resource.singular) %>.find({ isDeleted: false }).skip(parseInt(skip)).limit(parseInt(limit)).populate('user').populate('event').populate('club').populate('organizer').populate('admin').populate('members');
|
|
50
|
+
const total = await <%= capitalize(resource.singular) %>.countDocuments({ isDeleted: false });
|
|
51
|
+
res.json({ success: true, data, total, skip: parseInt(skip), limit: parseInt(limit) });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
res.status(500).json({ success: false, error: error.message });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// GET single <%= resource.singular %>
|
|
58
|
+
router.get('/:id', async (req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
const data = await <%= capitalize(resource.singular) %>.findById(req.params.id).populate('user').populate('event').populate('club').populate('organizer').populate('admin').populate('members');
|
|
61
|
+
if (!data) return res.status(404).json({ success: false, error: 'Not found' });
|
|
62
|
+
res.json({ success: true, data });
|
|
63
|
+
} catch (error) {
|
|
64
|
+
res.status(500).json({ success: false, error: error.message });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// POST create <%= resource.singular %>
|
|
69
|
+
router.post('/', async (req, res) => {
|
|
70
|
+
try {
|
|
71
|
+
const data = new <%= capitalize(resource.singular) %>(req.body);
|
|
72
|
+
await data.save();
|
|
73
|
+
await data.populate('user').populate('event').populate('club').populate('organizer').populate('admin').populate('members');
|
|
74
|
+
res.status(201).json({ success: true, data, message: 'Created successfully' });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
res.status(400).json({ success: false, error: error.message });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// PUT update <%= resource.singular %>
|
|
81
|
+
router.put('/:id', async (req, res) => {
|
|
82
|
+
try {
|
|
83
|
+
const data = await <%= capitalize(resource.singular) %>.findByIdAndUpdate(req.params.id, req.body, { new: true }).populate('user').populate('event').populate('club').populate('organizer').populate('admin').populate('members');
|
|
84
|
+
if (!data) return res.status(404).json({ success: false, error: 'Not found' });
|
|
85
|
+
res.json({ success: true, data, message: 'Updated successfully' });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
res.status(400).json({ success: false, error: error.message });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// DELETE <%= resource.singular %>
|
|
92
|
+
router.delete('/:id', async (req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
await <%= capitalize(resource.singular) %>.findByIdAndUpdate(req.params.id, { isDeleted: true });
|
|
95
|
+
res.json({ success: true, message: 'Deleted successfully' });
|
|
96
|
+
} catch (error) {
|
|
97
|
+
res.status(500).json({ success: false, error: error.message });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
export default router;
|
|
102
|
+
`;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Schema Template
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const VALIDATION_TEMPLATE = `import Joi from 'joi';
|
|
6
|
+
|
|
7
|
+
const validate<%= capitalize(resource.singular) %> = {
|
|
8
|
+
create: Joi.object({
|
|
9
|
+
name: Joi.string().required(),
|
|
10
|
+
email: Joi.string().email().optional()
|
|
11
|
+
})
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default validate<%= capitalize(resource.singular) %>;
|
|
15
|
+
`;
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IR Integration - Connects Scanner → IR → Generator
|
|
3
|
+
*
|
|
4
|
+
* Professional pipeline: Scan → Build IR → Generate Code
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { scanFrontendCode } from './scanner/frontendScanner.js';
|
|
10
|
+
import { buildIR, validateIR, printIR } from './ir-builder/irBuilder.js';
|
|
11
|
+
import {
|
|
12
|
+
generateBackendFromScanner,
|
|
13
|
+
writeGeneratedFiles,
|
|
14
|
+
generateBackendScaffold,
|
|
15
|
+
printGenerationSummary
|
|
16
|
+
} from './generator/irBasedGenerator.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Complete pipeline: Frontend → IR → Backend
|
|
20
|
+
*/
|
|
21
|
+
export async function offbyteWithIR(frontendPath, outputPath, options = {}) {
|
|
22
|
+
console.log('\n' + '='.repeat(70));
|
|
23
|
+
console.log('🚀 offbyt - IR-Based Backend Generation');
|
|
24
|
+
console.log('='.repeat(70) + '\n');
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Step 1: Scan frontend
|
|
28
|
+
console.log('📱 STEP 1: Scanning Frontend Code\n');
|
|
29
|
+
const detectedApis = scanFrontendCode(frontendPath);
|
|
30
|
+
|
|
31
|
+
if (detectedApis.length === 0) {
|
|
32
|
+
console.warn('âš ï¸ No APIs detected. Make sure frontend has API calls.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(`✅ Detected ${detectedApis.length} API calls\n`);
|
|
37
|
+
|
|
38
|
+
// Step 2: Save detected APIs
|
|
39
|
+
const apisPath = path.join(outputPath, 'detected-apis.json');
|
|
40
|
+
if (!fs.existsSync(path.dirname(apisPath))) {
|
|
41
|
+
fs.mkdirSync(path.dirname(apisPath), { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
fs.writeFileSync(apisPath, JSON.stringify(detectedApis, null, 2));
|
|
44
|
+
console.log(`💾 Saved API detections to: detectedapis.json\n`);
|
|
45
|
+
|
|
46
|
+
// Step 3: Build IR
|
|
47
|
+
console.log('🧠STEP 2: Building Intermediate Representation (IR)\n');
|
|
48
|
+
const ir = buildIR(detectedApis, {
|
|
49
|
+
hasAuth: options.hasAuth ?? true,
|
|
50
|
+
dbType: options.dbType ?? 'mongodb',
|
|
51
|
+
apiVersion: options.apiVersion ?? 'v1',
|
|
52
|
+
projectName: options.projectName || path.basename(frontendPath)
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Validate IR
|
|
56
|
+
const validation = validateIR(ir);
|
|
57
|
+
if (!validation.valid) {
|
|
58
|
+
console.error('⌠IR Validation Failed:');
|
|
59
|
+
validation.errors.forEach(err => console.error(` - ${err}`));
|
|
60
|
+
throw new Error('Invalid IR structure');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Save IR
|
|
64
|
+
const irPath = path.join(outputPath, 'project.ir.json');
|
|
65
|
+
fs.writeFileSync(irPath, JSON.stringify(ir, null, 2));
|
|
66
|
+
console.log(`💾 Saved IR to: project.ir.json`);
|
|
67
|
+
|
|
68
|
+
console.log(`✅ IR built with ${ir.resources.length} resources\n`);
|
|
69
|
+
|
|
70
|
+
// Step 4: Generate backend
|
|
71
|
+
console.log('🔨 STEP 3: Generating Backend Code\n');
|
|
72
|
+
const result = await generateBackendFromScanner(detectedApis, {
|
|
73
|
+
hasAuth: options.hasAuth ?? true,
|
|
74
|
+
dbType: options.dbType ?? 'mongodb'
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Step 5: Write files
|
|
78
|
+
console.log('📠STEP 4: Writing Generated Files\n');
|
|
79
|
+
|
|
80
|
+
// Create models, routes, validations
|
|
81
|
+
const backendDir = path.join(outputPath, 'generated');
|
|
82
|
+
await writeGeneratedFiles(result.generated, backendDir);
|
|
83
|
+
|
|
84
|
+
// Create scaffold (server.js, package.json, etc.)
|
|
85
|
+
await generateBackendScaffold(result.ir, backendDir);
|
|
86
|
+
|
|
87
|
+
// Create README
|
|
88
|
+
createReadme(backendDir, ir, options);
|
|
89
|
+
|
|
90
|
+
// Step 6: Summary
|
|
91
|
+
printGenerationSummary(result);
|
|
92
|
+
|
|
93
|
+
// Additional info
|
|
94
|
+
console.log('📊 IR Structure Breakdown:\n');
|
|
95
|
+
ir.resources.forEach(resource => {
|
|
96
|
+
console.log(` 📦 ${resource.singular}:`);
|
|
97
|
+
console.log(` Fields: ${resource.fields.map(f => f.name).join(', ')}`);
|
|
98
|
+
console.log(` Routes: ${resource.routes.join(', ')}`);
|
|
99
|
+
if (resource.validations && Object.keys(resource.validations).length > 0) {
|
|
100
|
+
console.log(` Validations: ${Object.keys(resource.validations).join(', ')}`);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
console.log('\n' + '='.repeat(70));
|
|
105
|
+
console.log('✨ GENERATION COMPLETE!');
|
|
106
|
+
console.log('='.repeat(70) + '\n');
|
|
107
|
+
|
|
108
|
+
console.log('📂 Generated Structure:');
|
|
109
|
+
console.log(` ${backendDir}/`);
|
|
110
|
+
console.log(` ├── models/ (Mongoose models)`);
|
|
111
|
+
console.log(` ├── routes/ (Express routes)`);
|
|
112
|
+
console.log(` ├── validations/ (Input validators)`);
|
|
113
|
+
console.log(` ├── middleware/ (Middleware handlers)`);
|
|
114
|
+
console.log(` ├── config/ (Database config)`);
|
|
115
|
+
console.log(` ├── server.js (Express server)`);
|
|
116
|
+
console.log(` ├── package.json (Dependencies)`);
|
|
117
|
+
console.log(` ├── .env.example (Environment variables)`);
|
|
118
|
+
console.log(` ├── README.md (Documentation)`);
|
|
119
|
+
console.log(` └── ir-schemas/ (IR JSON files)\n`);
|
|
120
|
+
|
|
121
|
+
console.log('🚀 Next Steps:');
|
|
122
|
+
console.log(` 1. cd ${backendDir}`);
|
|
123
|
+
console.log(` 2. npm install`);
|
|
124
|
+
console.log(` 3. Configure .env file`);
|
|
125
|
+
console.log(` 4. npm run dev\n`);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
ir,
|
|
130
|
+
generatedDir: backendDir,
|
|
131
|
+
stats: {
|
|
132
|
+
resourcesGenerated: ir.resources.length,
|
|
133
|
+
endpointsGenerated: ir.resources.reduce((sum, r) => sum + r.endpoints.length, 0),
|
|
134
|
+
fieldsDefinedGenerated: ir.resources.reduce((sum, r) => sum + r.fields.length, 0)
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('\n⌠Generation Failed:');
|
|
140
|
+
console.error(` ${error.message}\n`);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create README for generated project
|
|
147
|
+
*/
|
|
148
|
+
function createReadme(outputDir, ir, options) {
|
|
149
|
+
const resources = ir.resources.map(r =>
|
|
150
|
+
`- **${r.singular}** (\`${r.plural}\`): ${r.fields.map(f => f.name).join(', ')}`
|
|
151
|
+
).join('\n');
|
|
152
|
+
|
|
153
|
+
const readme = `# Generated Backend
|
|
154
|
+
|
|
155
|
+
Auto-generated with **offbyt IR Architecture**
|
|
156
|
+
|
|
157
|
+
Generated on: ${new Date().toLocaleString()}
|
|
158
|
+
|
|
159
|
+
## 📦 Resources
|
|
160
|
+
|
|
161
|
+
${resources}
|
|
162
|
+
|
|
163
|
+
## 🚀 Getting Started
|
|
164
|
+
|
|
165
|
+
\`\`\`bash
|
|
166
|
+
npm install
|
|
167
|
+
\`\`\`
|
|
168
|
+
|
|
169
|
+
### Configuration
|
|
170
|
+
|
|
171
|
+
Create a \`.env\` file (copy from \`.env.example\`):
|
|
172
|
+
|
|
173
|
+
\`\`\`env
|
|
174
|
+
MONGODB_URI=mongodb://localhost:27017/offbyt-${ir.settings.apiVersion}
|
|
175
|
+
PORT=3000
|
|
176
|
+
NODE_ENV=development
|
|
177
|
+
JWT_SECRET=your-secret-key
|
|
178
|
+
\`\`\`
|
|
179
|
+
|
|
180
|
+
### Development
|
|
181
|
+
|
|
182
|
+
\`\`\`bash
|
|
183
|
+
npm run dev
|
|
184
|
+
\`\`\`
|
|
185
|
+
|
|
186
|
+
Server will start on \`http://localhost:3000\`
|
|
187
|
+
|
|
188
|
+
## 📚 API Documentation
|
|
189
|
+
|
|
190
|
+
### Base URL
|
|
191
|
+
|
|
192
|
+
\`/api/${ir.settings.apiVersion}\`
|
|
193
|
+
|
|
194
|
+
### Endpoints
|
|
195
|
+
|
|
196
|
+
${ir.resources.map(r => {
|
|
197
|
+
const endpoints = r.endpoints.map(e => {
|
|
198
|
+
const method = e.method;
|
|
199
|
+
const path = `/api/` + r.plural + (method === 'GET' && e.path.includes(':id') ? '/:id' : '');
|
|
200
|
+
return '- `' + method + ' ' + path + '`';
|
|
201
|
+
}).join('\n');
|
|
202
|
+
return '#### ' + r.singular + '\n\n' + endpoints;
|
|
203
|
+
}).join('\n\n')}
|
|
204
|
+
|
|
205
|
+
## 🔒 Authentication
|
|
206
|
+
|
|
207
|
+
${ir.settings.hasAuth ? 'Authentication is enabled. Include JWT token in Authorization header.' : 'No authentication configured.'}
|
|
208
|
+
|
|
209
|
+
## 📂 Project Structure
|
|
210
|
+
|
|
211
|
+
\`\`\`
|
|
212
|
+
.
|
|
213
|
+
├── models/ Mongoose schemas
|
|
214
|
+
├── routes/ Express route handlers
|
|
215
|
+
├── validations/ Input validation schemas
|
|
216
|
+
├── middleware/ Custom middleware
|
|
217
|
+
├── config/ Database & environment config
|
|
218
|
+
├── server.js Express server entry point
|
|
219
|
+
└── package.json Dependencies
|
|
220
|
+
\`\`\`
|
|
221
|
+
|
|
222
|
+
## 🔧 Customization
|
|
223
|
+
|
|
224
|
+
Generated files are ready for customization:
|
|
225
|
+
|
|
226
|
+
1. **Add Business Logic**: Edit files in \`models/\` and \`routes/\`
|
|
227
|
+
2. **Extend Validation**: Modify validation files
|
|
228
|
+
3. **Add Middleware**: Create new middleware in \`middleware/\`
|
|
229
|
+
4. **Database Hooks**: Add Mongoose hooks in model files
|
|
230
|
+
|
|
231
|
+
## 🧪 Testing
|
|
232
|
+
|
|
233
|
+
\`\`\`bash
|
|
234
|
+
npm test
|
|
235
|
+
\`\`\`
|
|
236
|
+
|
|
237
|
+
## 📠Environment Variables
|
|
238
|
+
|
|
239
|
+
See \`.env.example\` for all configuration options.
|
|
240
|
+
|
|
241
|
+
## 🚀 Deployment
|
|
242
|
+
|
|
243
|
+
### Using Docker
|
|
244
|
+
|
|
245
|
+
Create \`Dockerfile\`:
|
|
246
|
+
|
|
247
|
+
\`\`\`dockerfile
|
|
248
|
+
FROM node:18-alpine
|
|
249
|
+
|
|
250
|
+
WORKDIR /app
|
|
251
|
+
|
|
252
|
+
COPY package*.json ./
|
|
253
|
+
RUN npm ci --only=production
|
|
254
|
+
|
|
255
|
+
COPY . .
|
|
256
|
+
|
|
257
|
+
EXPOSE 3000
|
|
258
|
+
CMD ["npm", "start"]
|
|
259
|
+
\`\`\`
|
|
260
|
+
|
|
261
|
+
Build and run:
|
|
262
|
+
|
|
263
|
+
\`\`\`bash
|
|
264
|
+
docker build -t offbyt-app .
|
|
265
|
+
docker run -p 3000:3000 offbyt-app
|
|
266
|
+
\`\`\`
|
|
267
|
+
|
|
268
|
+
### Using Node.js
|
|
269
|
+
|
|
270
|
+
\`\`\`bash
|
|
271
|
+
npm install
|
|
272
|
+
NODE_ENV=production npm start
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
## 📊 Tech Stack
|
|
276
|
+
|
|
277
|
+
- **Runtime**: Node.js 18+
|
|
278
|
+
- **Framework**: Express.js
|
|
279
|
+
- **Database**: MongoDB + Mongoose
|
|
280
|
+
- **Validation**: Joi
|
|
281
|
+
- **Logger**: Built-in request logger
|
|
282
|
+
|
|
283
|
+
## 📄 License
|
|
284
|
+
|
|
285
|
+
Generated by offbyt IR Architecture
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
**Generated by**: offbyt
|
|
290
|
+
**Generation Date**: ${new Date().toISOString()}
|
|
291
|
+
**IR Version**: ${ir.version}
|
|
292
|
+
`;
|
|
293
|
+
|
|
294
|
+
const readmePath = path.join(outputDir, 'README.md');
|
|
295
|
+
fs.writeFileSync(readmePath, readme);
|
|
296
|
+
console.log(` ✅ ${path.relative(process.cwd(), readmePath)}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Quick start: Directory → Full Backend
|
|
301
|
+
*/
|
|
302
|
+
export async function quickGenerate(frontendPath, options = {}) {
|
|
303
|
+
const projectName = path.basename(frontendPath);
|
|
304
|
+
const outputPath = `./offbyt-${projectName}`;
|
|
305
|
+
|
|
306
|
+
return offbyteWithIR(frontendPath, outputPath, {
|
|
307
|
+
projectName,
|
|
308
|
+
...options
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* CLI Helper
|
|
314
|
+
*/
|
|
315
|
+
export async function runFromCLI(args) {
|
|
316
|
+
const [command, frontendPath, outputPath] = args;
|
|
317
|
+
|
|
318
|
+
if (!frontendPath) {
|
|
319
|
+
console.log('\n📖 Usage: node ir-integration.js <frontend-path> [output-path]\n');
|
|
320
|
+
console.log('Examples:');
|
|
321
|
+
console.log(' node ir-integration.js ./my-app');
|
|
322
|
+
console.log(' node ir-integration.js ./frontend ./generated-backend\n');
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!fs.existsSync(frontendPath)) {
|
|
327
|
+
console.error(`⌠Frontend path not found: ${frontendPath}`);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const out = outputPath || `./offbyt-${path.basename(frontendPath)}`;
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
await offbyteWithIR(frontendPath, out, {
|
|
335
|
+
hasAuth: true,
|
|
336
|
+
projectName: path.basename(frontendPath)
|
|
337
|
+
});
|
|
338
|
+
} catch (error) {
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Run if called directly
|
|
344
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
345
|
+
runFromCLI(process.argv.slice(2));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export default offbyteWithIR;
|
|
349
|
+
|