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,341 @@
|
|
|
1
|
+
import babelTraverse from '@babel/traverse';
|
|
2
|
+
|
|
3
|
+
const traverse = babelTraverse.default || babelTraverse;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Socket.io / WebSocket Pattern Detector
|
|
7
|
+
* Detects real-time communication patterns in frontend code
|
|
8
|
+
*
|
|
9
|
+
* Detects:
|
|
10
|
+
* - Socket.io client initialization
|
|
11
|
+
* - WebSocket connections
|
|
12
|
+
* - Chat UI components
|
|
13
|
+
* - Message sending/receiving
|
|
14
|
+
* - Presence/typing indicators
|
|
15
|
+
* - Room/channel patterns
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const CHAT_PATTERNS = {
|
|
19
|
+
socketIo: [
|
|
20
|
+
/io\(['"].*['"]\)/gi,
|
|
21
|
+
/socket\.io-client/gi,
|
|
22
|
+
/new\s+Socket/gi,
|
|
23
|
+
/socketIOClient/gi,
|
|
24
|
+
],
|
|
25
|
+
websocket: [
|
|
26
|
+
/new\s+WebSocket/gi,
|
|
27
|
+
/ws:\/\//gi,
|
|
28
|
+
/wss:\/\//gi,
|
|
29
|
+
],
|
|
30
|
+
events: [
|
|
31
|
+
/on\(['"]message/gi,
|
|
32
|
+
/emit\(['"]message/gi,
|
|
33
|
+
/on\(['"]chat/gi,
|
|
34
|
+
/emit\(['"]chat/gi,
|
|
35
|
+
/socket\.send/gi,
|
|
36
|
+
/socket\.on/gi,
|
|
37
|
+
/socket\.emit/gi,
|
|
38
|
+
],
|
|
39
|
+
chatUI: [
|
|
40
|
+
/sendMessage/gi,
|
|
41
|
+
/receiveMessage/gi,
|
|
42
|
+
/chatBox/gi,
|
|
43
|
+
/messageList/gi,
|
|
44
|
+
/conversation/gi,
|
|
45
|
+
/chat.*input/gi,
|
|
46
|
+
/message.*input/gi,
|
|
47
|
+
/typing.*indicator/gi,
|
|
48
|
+
],
|
|
49
|
+
presence: [
|
|
50
|
+
/user.*online/gi,
|
|
51
|
+
/user.*offline/gi,
|
|
52
|
+
/presence/gi,
|
|
53
|
+
/is.*typing/gi,
|
|
54
|
+
],
|
|
55
|
+
rooms: [
|
|
56
|
+
/join.*room/gi,
|
|
57
|
+
/leave.*room/gi,
|
|
58
|
+
/join.*channel/gi,
|
|
59
|
+
/room.*id/gi,
|
|
60
|
+
/channel.*id/gi,
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Main socket detection function
|
|
66
|
+
*/
|
|
67
|
+
export function detectSocket(ast, sourceCode) {
|
|
68
|
+
const detections = {
|
|
69
|
+
hasSocket: false,
|
|
70
|
+
hasChat: false,
|
|
71
|
+
socketType: null, // 'socket.io' | 'websocket' | null
|
|
72
|
+
events: [],
|
|
73
|
+
rooms: false,
|
|
74
|
+
presence: false,
|
|
75
|
+
endpoints: []
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// AST-based detection
|
|
79
|
+
traverse(ast, {
|
|
80
|
+
// Detect Socket.io initialization: io('http://...')
|
|
81
|
+
CallExpression(path) {
|
|
82
|
+
const { callee, arguments: args } = path.node;
|
|
83
|
+
|
|
84
|
+
// Socket.io: io(url)
|
|
85
|
+
if (callee.type === 'Identifier' && callee.name === 'io') {
|
|
86
|
+
detections.hasSocket = true;
|
|
87
|
+
detections.socketType = 'socket.io';
|
|
88
|
+
|
|
89
|
+
if (args.length > 0 && args[0].type === 'StringLiteral') {
|
|
90
|
+
detections.endpoints.push({
|
|
91
|
+
type: 'socket.io',
|
|
92
|
+
url: args[0].value
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Socket.io: socketIOClient(url)
|
|
98
|
+
if (callee.type === 'Identifier' && callee.name === 'socketIOClient') {
|
|
99
|
+
detections.hasSocket = true;
|
|
100
|
+
detections.socketType = 'socket.io';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// WebSocket: new WebSocket(url)
|
|
104
|
+
if (callee.type === 'NewExpression' &&
|
|
105
|
+
callee.callee?.name === 'WebSocket') {
|
|
106
|
+
detections.hasSocket = true;
|
|
107
|
+
detections.socketType = 'websocket';
|
|
108
|
+
|
|
109
|
+
if (args.length > 0 && args[0].type === 'StringLiteral') {
|
|
110
|
+
detections.endpoints.push({
|
|
111
|
+
type: 'websocket',
|
|
112
|
+
url: args[0].value
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Detect socket.emit('event', data)
|
|
118
|
+
if (callee.type === 'MemberExpression' &&
|
|
119
|
+
callee.property.type === 'Identifier' &&
|
|
120
|
+
callee.property.name === 'emit') {
|
|
121
|
+
|
|
122
|
+
if (args.length > 0 && args[0].type === 'StringLiteral') {
|
|
123
|
+
const eventName = args[0].value;
|
|
124
|
+
detections.events.push({
|
|
125
|
+
type: 'emit',
|
|
126
|
+
name: eventName
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Check for chat-related events
|
|
130
|
+
if (isChatEvent(eventName)) {
|
|
131
|
+
detections.hasChat = true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check for room events
|
|
135
|
+
if (isRoomEvent(eventName)) {
|
|
136
|
+
detections.rooms = true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check for presence events
|
|
140
|
+
if (isPresenceEvent(eventName)) {
|
|
141
|
+
detections.presence = true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Detect socket.on('event', callback)
|
|
147
|
+
if (callee.type === 'MemberExpression' &&
|
|
148
|
+
callee.property.type === 'Identifier' &&
|
|
149
|
+
callee.property.name === 'on') {
|
|
150
|
+
|
|
151
|
+
if (args.length > 0 && args[0].type === 'StringLiteral') {
|
|
152
|
+
const eventName = args[0].value;
|
|
153
|
+
detections.events.push({
|
|
154
|
+
type: 'on',
|
|
155
|
+
name: eventName
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (isChatEvent(eventName)) {
|
|
159
|
+
detections.hasChat = true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (isRoomEvent(eventName)) {
|
|
163
|
+
detections.rooms = true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (isPresenceEvent(eventName)) {
|
|
167
|
+
detections.presence = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
// Detect import statements
|
|
174
|
+
ImportDeclaration(path) {
|
|
175
|
+
const source = path.node.source.value;
|
|
176
|
+
|
|
177
|
+
if (source === 'socket.io-client' || source.includes('socket.io')) {
|
|
178
|
+
detections.hasSocket = true;
|
|
179
|
+
detections.socketType = 'socket.io';
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
// Detect chat-related variable/function names
|
|
184
|
+
Identifier(path) {
|
|
185
|
+
const name = path.node.name;
|
|
186
|
+
|
|
187
|
+
if (isChatIdentifier(name)) {
|
|
188
|
+
detections.hasChat = true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Regex-based detection (fallback)
|
|
194
|
+
const regexDetections = detectSocketRegex(sourceCode);
|
|
195
|
+
|
|
196
|
+
if (regexDetections.hasSocket) {
|
|
197
|
+
detections.hasSocket = true;
|
|
198
|
+
detections.socketType = detections.socketType || regexDetections.socketType;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (regexDetections.hasChat) {
|
|
202
|
+
detections.hasChat = true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (regexDetections.rooms) {
|
|
206
|
+
detections.rooms = true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (regexDetections.presence) {
|
|
210
|
+
detections.presence = true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Deduplicate events
|
|
214
|
+
detections.events = Array.from(
|
|
215
|
+
new Map(detections.events.map(e => [e.name, e])).values()
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
return detections;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Regex-based socket detection (fallback)
|
|
223
|
+
*/
|
|
224
|
+
function detectSocketRegex(sourceCode) {
|
|
225
|
+
const result = {
|
|
226
|
+
hasSocket: false,
|
|
227
|
+
hasChat: false,
|
|
228
|
+
socketType: null,
|
|
229
|
+
rooms: false,
|
|
230
|
+
presence: false
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Check Socket.io patterns
|
|
234
|
+
for (const pattern of CHAT_PATTERNS.socketIo) {
|
|
235
|
+
if (pattern.test(sourceCode)) {
|
|
236
|
+
result.hasSocket = true;
|
|
237
|
+
result.socketType = 'socket.io';
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Check WebSocket patterns
|
|
243
|
+
if (!result.hasSocket) {
|
|
244
|
+
for (const pattern of CHAT_PATTERNS.websocket) {
|
|
245
|
+
if (pattern.test(sourceCode)) {
|
|
246
|
+
result.hasSocket = true;
|
|
247
|
+
result.socketType = 'websocket';
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check chat UI patterns
|
|
254
|
+
for (const pattern of CHAT_PATTERNS.chatUI) {
|
|
255
|
+
if (pattern.test(sourceCode)) {
|
|
256
|
+
result.hasChat = true;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Check events
|
|
262
|
+
for (const pattern of CHAT_PATTERNS.events) {
|
|
263
|
+
if (pattern.test(sourceCode)) {
|
|
264
|
+
result.hasChat = true;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Check rooms
|
|
270
|
+
for (const pattern of CHAT_PATTERNS.rooms) {
|
|
271
|
+
if (pattern.test(sourceCode)) {
|
|
272
|
+
result.rooms = true;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Check presence
|
|
278
|
+
for (const pattern of CHAT_PATTERNS.presence) {
|
|
279
|
+
if (pattern.test(sourceCode)) {
|
|
280
|
+
result.presence = true;
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Helper: Check if event name is chat-related
|
|
290
|
+
*/
|
|
291
|
+
function isChatEvent(eventName) {
|
|
292
|
+
const chatKeywords = [
|
|
293
|
+
'message', 'chat', 'send', 'receive',
|
|
294
|
+
'msg', 'text', 'conversation'
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
const lowerName = eventName.toLowerCase();
|
|
298
|
+
return chatKeywords.some(kw => lowerName.includes(kw));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Helper: Check if event name is room-related
|
|
303
|
+
*/
|
|
304
|
+
function isRoomEvent(eventName) {
|
|
305
|
+
const roomKeywords = [
|
|
306
|
+
'room', 'channel', 'join', 'leave',
|
|
307
|
+
'subscribe', 'unsubscribe'
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
const lowerName = eventName.toLowerCase();
|
|
311
|
+
return roomKeywords.some(kw => lowerName.includes(kw));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Helper: Check if event name is presence-related
|
|
316
|
+
*/
|
|
317
|
+
function isPresenceEvent(eventName) {
|
|
318
|
+
const presenceKeywords = [
|
|
319
|
+
'online', 'offline', 'presence',
|
|
320
|
+
'typing', 'active', 'idle'
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
const lowerName = eventName.toLowerCase();
|
|
324
|
+
return presenceKeywords.some(kw => lowerName.includes(kw));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Helper: Check if identifier is chat-related
|
|
329
|
+
*/
|
|
330
|
+
function isChatIdentifier(name) {
|
|
331
|
+
const chatKeywords = [
|
|
332
|
+
'sendMessage', 'receiveMessage', 'chatBox',
|
|
333
|
+
'messageList', 'messageInput', 'chatInput',
|
|
334
|
+
'conversation', 'chatHistory', 'messageHistory'
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
const lowerName = name.toLowerCase();
|
|
338
|
+
return chatKeywords.some(kw => lowerName.includes(kw.toLowerCase()));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export default detectSocket;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function pascalCase(name) {
|
|
2
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function generateControllers(ir) {
|
|
6
|
+
const output = {};
|
|
7
|
+
|
|
8
|
+
for (const resource of ir.resources || []) {
|
|
9
|
+
const model = pascalCase(resource.name);
|
|
10
|
+
|
|
11
|
+
output[`controllers/${resource.name}Controller.js`] = `import ${model} from '../models/${model}.js';\n\nexport const list${model}s = async (req, res) => {\n const data = await ${model}.find();\n res.json({ success: true, data });\n};\n\nexport const get${model} = async (req, res) => {\n const data = await ${model}.findById(req.params.id);\n res.json({ success: true, data });\n};\n\nexport const create${model} = async (req, res) => {\n const data = await ${model}.create(req.body);\n res.status(201).json({ success: true, data });\n};\n\nexport const update${model} = async (req, res) => {\n const data = await ${model}.findByIdAndUpdate(req.params.id, req.body, { new: true });\n res.json({ success: true, data });\n};\n\nexport const delete${model} = async (req, res) => {\n await ${model}.findByIdAndDelete(req.params.id);\n res.json({ success: true });\n};\n`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return output;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default generateControllers;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function mapFieldToMongoose(field) {
|
|
2
|
+
if (field.type === 'ObjectId') {
|
|
3
|
+
return `${field.name}: { type: mongoose.Schema.Types.ObjectId${field.ref ? `, ref: '${field.ref}'` : ''}${field.required ? ', required: true' : ''} }`;
|
|
4
|
+
}
|
|
5
|
+
return `${field.name}: { type: ${field.type}${field.required ? ', required: true' : ''} }`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function modelName(name) {
|
|
9
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function generateModels(ir) {
|
|
13
|
+
const output = {};
|
|
14
|
+
|
|
15
|
+
for (const resource of ir.resources || []) {
|
|
16
|
+
const schemaFields = (resource.fields || []).map(mapFieldToMongoose).join(',\n ');
|
|
17
|
+
const className = modelName(resource.name);
|
|
18
|
+
|
|
19
|
+
output[`models/${className}.js`] = `import mongoose from 'mongoose';\n\nconst ${resource.name}Schema = new mongoose.Schema({\n ${schemaFields}\n}, { timestamps: true });\n\nexport default mongoose.model('${className}', ${resource.name}Schema);\n`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return output;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default generateModels;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function pascalCase(name) {
|
|
2
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function generateRoutes(ir) {
|
|
6
|
+
const output = {};
|
|
7
|
+
|
|
8
|
+
for (const resource of ir.resources || []) {
|
|
9
|
+
const model = pascalCase(resource.name);
|
|
10
|
+
|
|
11
|
+
output[`routes/${resource.name}.routes.js`] = `import express from 'express';\nimport {\n list${model}s,\n get${model},\n create${model},\n update${model},\n delete${model}\n} from '../controllers/${resource.name}Controller.js';\n\nconst router = express.Router();\n\nrouter.get('/', list${model}s);\nrouter.get('/:id', get${model});\nrouter.post('/', create${model});\nrouter.put('/:id', update${model});\nrouter.delete('/:id', delete${model});\n\nexport default router;\n`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return output;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default generateRoutes;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function generateServer(ir) {
|
|
2
|
+
const imports = (ir.resources || [])
|
|
3
|
+
.map((r) => `import ${r.name}Routes from './routes/${r.name}.routes.js';`)
|
|
4
|
+
.join('\n');
|
|
5
|
+
|
|
6
|
+
const routes = (ir.resources || [])
|
|
7
|
+
.map((r) => `app.use('/api/${r.name}s', ${r.name}Routes);`)
|
|
8
|
+
.join('\n');
|
|
9
|
+
|
|
10
|
+
const serverCode = `import 'dotenv/config';\nimport express from 'express';\nimport cors from 'cors';\nimport mongoose from 'mongoose';\n${imports}\n\nconst app = express();\nconst PORT = process.env.PORT || 3000;\nconst MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/offbyt';\n\napp.use(cors());\napp.use(express.json());\n\n${routes}\n\nconst healthHandler = (req, res) => {\n res.json({\n status: 'ok',\n timestamp: new Date().toISOString(),\n database: mongoose.connection.readyState === 1 ? 'Connected' : 'Disconnected'\n });\n};\n\napp.get('/health', healthHandler);\napp.get(`/api/health`, healthHandler);\n\napp.use((req, res) => {\n res.status(404).json({\n error: 'Not Found',\n path: req.path,\n method: req.method\n });\n});\n\nasync function connectDB() {\n try {\n await mongoose.connect(MONGODB_URI, {\n serverSelectionTimeoutMS: 5000\n });\n console.log('✅ MongoDB Connected');\n } catch (error) {\n console.error('⌠MongoDB Connection Failed:', error.message);\n process.exit(1);\n }\n}\n\nasync function startServer() {\n await connectDB();\n app.listen(PORT, () => {\n console.log(\`🚀 Server running on \${PORT}\`);\n });\n}\n\nstartServer().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n`;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
'server.js': serverCode
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default generateServer;
|
|
18
|
+
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket/Chat Backend Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates complete Socket.io backend infrastructure including:
|
|
5
|
+
* - Socket.io server setup
|
|
6
|
+
* - Chat models (Message, Conversation)
|
|
7
|
+
* - REST API routes for chat
|
|
8
|
+
* - Authentication middleware
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { socketServerTemplate } from '../../templates/socket.server.template.js';
|
|
12
|
+
import { messageModelTemplate, conversationModelTemplate } from '../../templates/chat.models.template.js';
|
|
13
|
+
import { chatRoutesTemplate } from '../../templates/chat.routes.template.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate complete socket/chat backend
|
|
17
|
+
*/
|
|
18
|
+
export function generateSocketBackend(socketDetection) {
|
|
19
|
+
const files = {};
|
|
20
|
+
|
|
21
|
+
// Only generate if socket is detected
|
|
22
|
+
if (!socketDetection || !socketDetection.hasSocket) {
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 1. Socket.io server setup
|
|
27
|
+
files['socket/index.js'] = socketServerTemplate;
|
|
28
|
+
|
|
29
|
+
// 2. Chat models
|
|
30
|
+
files['models/Message.js'] = messageModelTemplate;
|
|
31
|
+
files['models/Conversation.js'] = conversationModelTemplate;
|
|
32
|
+
|
|
33
|
+
// 3. Chat routes (REST API endpoints)
|
|
34
|
+
files['routes/chat.routes.js'] = chatRoutesTemplate;
|
|
35
|
+
|
|
36
|
+
// 4. Package.json dependencies
|
|
37
|
+
const socketDependencies = {
|
|
38
|
+
'socket.io': '^4.6.1',
|
|
39
|
+
'jsonwebtoken': '^9.0.2'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
files,
|
|
44
|
+
dependencies: socketDependencies,
|
|
45
|
+
instructions: generateSetupInstructions(socketDetection)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate setup instructions
|
|
51
|
+
*/
|
|
52
|
+
function generateSetupInstructions(socketDetection) {
|
|
53
|
+
const instructions = {
|
|
54
|
+
title: '🔌 Socket.io Chat Backend Generated',
|
|
55
|
+
steps: [
|
|
56
|
+
'1. Install dependencies: npm install socket.io jsonwebtoken',
|
|
57
|
+
'2. Import socket initialization in server.js',
|
|
58
|
+
'3. Initialize Socket.io with HTTP server',
|
|
59
|
+
'4. Register chat routes at /api/chat',
|
|
60
|
+
'5. Set JWT_SECRET in .env file',
|
|
61
|
+
'6. Set CLIENT_URL in .env (for CORS)'
|
|
62
|
+
],
|
|
63
|
+
features: []
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (socketDetection.hasChat) {
|
|
67
|
+
instructions.features.push('✅ Real-time messaging');
|
|
68
|
+
instructions.features.push('✅ Message history & persistence');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (socketDetection.rooms) {
|
|
72
|
+
instructions.features.push('✅ Rooms/Channels support');
|
|
73
|
+
instructions.features.push('✅ Group conversations');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (socketDetection.presence) {
|
|
77
|
+
instructions.features.push('✅ Online/Offline presence');
|
|
78
|
+
instructions.features.push('✅ Typing indicators');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
instructions.features.push('✅ Read receipts');
|
|
82
|
+
instructions.features.push('✅ File sharing support');
|
|
83
|
+
instructions.features.push('✅ Message reactions');
|
|
84
|
+
instructions.features.push('✅ JWT Authentication');
|
|
85
|
+
|
|
86
|
+
return instructions;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Generate modified server.js with Socket.io integration
|
|
91
|
+
*/
|
|
92
|
+
export function generateServerWithSocket(baseServerCode, hasSocket = false) {
|
|
93
|
+
if (!hasSocket) {
|
|
94
|
+
return baseServerCode;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let modifiedCode = baseServerCode;
|
|
98
|
+
|
|
99
|
+
// Check if already has Socket.io imports
|
|
100
|
+
if (modifiedCode.includes('socket.io') || modifiedCode.includes('initializeSocket')) {
|
|
101
|
+
return modifiedCode; // Already integrated
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Add Socket.io imports after express import
|
|
105
|
+
const socketImports = `import { createServer } from 'http';
|
|
106
|
+
import initializeSocket from './socket/index.js';
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
// Insert imports after express import
|
|
110
|
+
modifiedCode = modifiedCode.replace(
|
|
111
|
+
/(import express from ['"]express['"];)/,
|
|
112
|
+
`$1\n${socketImports}`
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Create HTTP server and initialize Socket.io after app creation
|
|
116
|
+
const serverSetup = `
|
|
117
|
+
// ============================================
|
|
118
|
+
// SOCKET.IO SETUP
|
|
119
|
+
// ============================================
|
|
120
|
+
const httpServer = createServer(app);
|
|
121
|
+
const io = initializeSocket(httpServer);
|
|
122
|
+
|
|
123
|
+
// Make io accessible in routes via req.app.get('io')
|
|
124
|
+
app.set('io', io);
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
// Add server setup after app initialization (after const app = express())
|
|
128
|
+
modifiedCode = modifiedCode.replace(
|
|
129
|
+
/(const app = express\(\);[\s\S]*?const API_VERSION[^;]*;)/,
|
|
130
|
+
`$1\n${serverSetup}`
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Replace app.listen with httpServer.listen at the end
|
|
134
|
+
modifiedCode = modifiedCode.replace(
|
|
135
|
+
/app\.listen\((PORT[^)]*)\)/g,
|
|
136
|
+
'httpServer.listen($1)'
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return modifiedCode;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Update package.json with socket dependencies
|
|
144
|
+
*/
|
|
145
|
+
export function addSocketDependencies(packageJson) {
|
|
146
|
+
if (!packageJson.dependencies) {
|
|
147
|
+
packageJson.dependencies = {};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
packageJson.dependencies['socket.io'] = '^4.6.1';
|
|
151
|
+
packageJson.dependencies['jsonwebtoken'] = '^9.0.2';
|
|
152
|
+
|
|
153
|
+
return packageJson;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default {
|
|
157
|
+
generateSocketBackend,
|
|
158
|
+
generateServerWithSocket,
|
|
159
|
+
addSocketDependencies
|
|
160
|
+
};
|
package/core/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { scanProject } from './scanner/scanProject.js';
|
|
2
|
+
export { parseJS } from './parser/parseJS.js';
|
|
3
|
+
export { parseTS } from './parser/parseTS.js';
|
|
4
|
+
export { detectFetch } from './detector/detectFetch.js';
|
|
5
|
+
export { detectAxios } from './detector/detectAxios.js';
|
|
6
|
+
export { detectForms } from './detector/detectForms.js';
|
|
7
|
+
export { detectSocket } from './detector/detectSocket.js';
|
|
8
|
+
export { buildIRFromDetections } from './ir/buildIR.js';
|
|
9
|
+
export { applyRuleEngine } from './rules/resourceRules.js';
|
|
10
|
+
export { generateModels } from './generator/generateModels.js';
|
|
11
|
+
export { generateControllers } from './generator/generateControllers.js';
|
|
12
|
+
export { generateRoutes } from './generator/generateRoutes.js';
|
|
13
|
+
export { generateServer } from './generator/generateServer.js';
|
|
14
|
+
export { generateSocketBackend, generateServerWithSocket, addSocketDependencies } from './generator/generateSocket.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function createField(name, type = 'String', required = false, extra = {}) {
|
|
2
|
+
return { name, type, required, ...extra };
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function createResource({ name, path, fields = [], methods = [] }) {
|
|
6
|
+
return {
|
|
7
|
+
name,
|
|
8
|
+
path,
|
|
9
|
+
fields,
|
|
10
|
+
methods: Array.from(new Set(methods)).sort()
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createRelation({ from, to, type = 'many-to-one' }) {
|
|
15
|
+
return { from, to, type };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createIR({ resources = [], relations = [], auth = { enabled: false }, database = 'mongodb' } = {}) {
|
|
19
|
+
return {
|
|
20
|
+
resources,
|
|
21
|
+
relations,
|
|
22
|
+
auth,
|
|
23
|
+
database
|
|
24
|
+
};
|
|
25
|
+
}
|