offbyt 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +2 -0
  2. package/cli/index.js +2 -0
  3. package/cli.js +206 -0
  4. package/core/detector/detectAxios.js +107 -0
  5. package/core/detector/detectFetch.js +148 -0
  6. package/core/detector/detectForms.js +55 -0
  7. package/core/detector/detectSocket.js +341 -0
  8. package/core/generator/generateControllers.js +17 -0
  9. package/core/generator/generateModels.js +25 -0
  10. package/core/generator/generateRoutes.js +17 -0
  11. package/core/generator/generateServer.js +18 -0
  12. package/core/generator/generateSocket.js +160 -0
  13. package/core/index.js +14 -0
  14. package/core/ir/IRTypes.js +25 -0
  15. package/core/ir/buildIR.js +83 -0
  16. package/core/parser/parseJS.js +26 -0
  17. package/core/parser/parseTS.js +27 -0
  18. package/core/rules/relationRules.js +38 -0
  19. package/core/rules/resourceRules.js +32 -0
  20. package/core/rules/schemaInference.js +26 -0
  21. package/core/scanner/scanProject.js +58 -0
  22. package/deploy/cloudflare.js +41 -0
  23. package/deploy/cloudflareWorker.js +122 -0
  24. package/deploy/connect.js +198 -0
  25. package/deploy/flyio.js +51 -0
  26. package/deploy/index.js +322 -0
  27. package/deploy/netlify.js +29 -0
  28. package/deploy/railway.js +215 -0
  29. package/deploy/render.js +195 -0
  30. package/deploy/utils.js +383 -0
  31. package/deploy/vercel.js +29 -0
  32. package/index.js +18 -0
  33. package/lib/generator/advancedCrudGenerator.js +475 -0
  34. package/lib/generator/crudCodeGenerator.js +486 -0
  35. package/lib/generator/irBasedGenerator.js +360 -0
  36. package/lib/ir-builder/index.js +16 -0
  37. package/lib/ir-builder/irBuilder.js +330 -0
  38. package/lib/ir-builder/rulesEngine.js +353 -0
  39. package/lib/ir-builder/templateEngine.js +193 -0
  40. package/lib/ir-builder/templates/index.js +14 -0
  41. package/lib/ir-builder/templates/model.template.js +47 -0
  42. package/lib/ir-builder/templates/routes-generic.template.js +66 -0
  43. package/lib/ir-builder/templates/routes-user.template.js +105 -0
  44. package/lib/ir-builder/templates/routes.template.js +102 -0
  45. package/lib/ir-builder/templates/validation.template.js +15 -0
  46. package/lib/ir-integration.js +349 -0
  47. package/lib/modes/benchmark.js +162 -0
  48. package/lib/modes/configBasedGenerator.js +2258 -0
  49. package/lib/modes/connect.js +1125 -0
  50. package/lib/modes/doctorAi.js +172 -0
  51. package/lib/modes/generateApi.js +435 -0
  52. package/lib/modes/interactiveSetup.js +548 -0
  53. package/lib/modes/offline.clean.js +14 -0
  54. package/lib/modes/offline.enhanced.js +787 -0
  55. package/lib/modes/offline.js +295 -0
  56. package/lib/modes/offline.v2.js +13 -0
  57. package/lib/modes/sync.js +629 -0
  58. package/lib/scanner/apiEndpointExtractor.js +387 -0
  59. package/lib/scanner/authPatternDetector.js +54 -0
  60. package/lib/scanner/frontendScanner.js +642 -0
  61. package/lib/utils/apiClientGenerator.js +242 -0
  62. package/lib/utils/apiScanner.js +95 -0
  63. package/lib/utils/codeInjector.js +350 -0
  64. package/lib/utils/doctor.js +381 -0
  65. package/lib/utils/envGenerator.js +36 -0
  66. package/lib/utils/loadTester.js +61 -0
  67. package/lib/utils/performanceAnalyzer.js +298 -0
  68. package/lib/utils/resourceDetector.js +281 -0
  69. package/package.json +20 -0
  70. package/templates/.env.template +31 -0
  71. package/templates/advanced.model.template.js +201 -0
  72. package/templates/advanced.route.template.js +341 -0
  73. package/templates/auth.middleware.template.js +87 -0
  74. package/templates/auth.routes.template.js +238 -0
  75. package/templates/auth.user.model.template.js +78 -0
  76. package/templates/cache.middleware.js +34 -0
  77. package/templates/chat.models.template.js +260 -0
  78. package/templates/chat.routes.template.js +478 -0
  79. package/templates/compression.middleware.js +19 -0
  80. package/templates/database.config.js +74 -0
  81. package/templates/errorHandler.middleware.js +54 -0
  82. package/templates/express/controller.ejs +26 -0
  83. package/templates/express/model.ejs +9 -0
  84. package/templates/express/route.ejs +18 -0
  85. package/templates/express/server.ejs +16 -0
  86. package/templates/frontend.env.template +14 -0
  87. package/templates/model.template.js +86 -0
  88. package/templates/package.production.json +51 -0
  89. package/templates/package.template.json +41 -0
  90. package/templates/pagination.utility.js +110 -0
  91. package/templates/production.server.template.js +233 -0
  92. package/templates/rateLimiter.middleware.js +36 -0
  93. package/templates/requestLogger.middleware.js +19 -0
  94. package/templates/response.helper.js +179 -0
  95. package/templates/route.template.js +130 -0
  96. package/templates/security.middleware.js +78 -0
  97. package/templates/server.template.js +91 -0
  98. package/templates/socket.server.template.js +433 -0
  99. package/templates/utils.helper.js +157 -0
  100. package/templates/validation.middleware.js +63 -0
  101. package/templates/validation.schema.js +128 -0
  102. package/utils/fileWriter.js +15 -0
  103. package/utils/logger.js +18 -0
@@ -0,0 +1,381 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+
6
+ const execAsync = promisify(exec);
7
+
8
+ const MIN_NODE_MAJOR = 18;
9
+ const MIN_NPM_MAJOR = 9;
10
+
11
+ const DOCTOR_EXIT_CODES = {
12
+ healthy: 0,
13
+ issuesFound: 1,
14
+ internalError: 2
15
+ };
16
+
17
+ const ALLOWED_STATUS = new Set(['pass', 'warn', 'fail']);
18
+
19
+ export async function runDoctor(options = {}) {
20
+ const config = normalizeOptions(options);
21
+ const checks = [
22
+ { name: 'Node.js', test: () => checkNode(config) },
23
+ { name: 'npm', test: () => checkNpm(config) },
24
+ { name: 'MongoDB', test: () => checkMongoDB(config) },
25
+ { name: 'Git CLI', test: () => checkGit(config) },
26
+ { name: `Port ${config.port}`, test: () => checkPort(config) }
27
+ ];
28
+
29
+ const diagnostics = [];
30
+
31
+ try {
32
+ if (!config.json) {
33
+ console.log(chalk.cyan('\noffbyt Doctor - System Health Check\n'));
34
+ console.log(chalk.gray(` Mode: ${config.strict ? 'strict' : 'non-strict'} | Port: ${config.port}\n`));
35
+ }
36
+
37
+ for (const check of checks) {
38
+ const spinner = config.json ? null : ora(`Checking ${check.name}...`).start();
39
+ try {
40
+ const rawResult = await check.test();
41
+ const result = normalizeCheckResult(check.name, rawResult);
42
+ diagnostics.push(result);
43
+ renderCheckResult(spinner, result);
44
+ } catch (error) {
45
+ const result = {
46
+ name: check.name,
47
+ status: 'fail',
48
+ message: `- Unexpected error: ${error.message}`,
49
+ recommendation: 'Rerun command with --json and inspect error details.'
50
+ };
51
+ diagnostics.push(result);
52
+ renderCheckResult(spinner, result);
53
+ }
54
+ }
55
+
56
+ const summary = summarize(diagnostics);
57
+ const blockingIssues = config.strict ? summary.warn + summary.fail : summary.fail;
58
+ const report = {
59
+ ok: blockingIssues === 0,
60
+ strict: config.strict,
61
+ port: config.port,
62
+ exitCode: blockingIssues === 0 ? DOCTOR_EXIT_CODES.healthy : DOCTOR_EXIT_CODES.issuesFound,
63
+ timestamp: new Date().toISOString(),
64
+ summary,
65
+ diagnostics
66
+ };
67
+
68
+ if (config.json) {
69
+ console.log(JSON.stringify(report, null, 2));
70
+ } else {
71
+ printHumanSummary(report);
72
+ }
73
+
74
+ return report;
75
+ } catch (error) {
76
+ const report = {
77
+ ok: false,
78
+ strict: config.strict,
79
+ port: config.port,
80
+ exitCode: DOCTOR_EXIT_CODES.internalError,
81
+ timestamp: new Date().toISOString(),
82
+ summary: { pass: 0, warn: 0, fail: 1, total: 1 },
83
+ diagnostics: [
84
+ {
85
+ name: 'Doctor Runner',
86
+ status: 'fail',
87
+ message: `- Internal error: ${error.message}`,
88
+ recommendation: 'Try again or open an issue with the command output.'
89
+ }
90
+ ]
91
+ };
92
+
93
+ if (config.json) {
94
+ console.log(JSON.stringify(report, null, 2));
95
+ } else {
96
+ console.error(chalk.red(`\nDoctor failed to run: ${error.message}\n`));
97
+ }
98
+
99
+ return report;
100
+ }
101
+ }
102
+
103
+ function normalizeOptions(options) {
104
+ return {
105
+ strict: options.strict !== false,
106
+ json: Boolean(options.json),
107
+ port: Number.isInteger(options.port) ? options.port : 5000
108
+ };
109
+ }
110
+
111
+ function normalizeCheckResult(name, result) {
112
+ if (!result || typeof result !== 'object') {
113
+ return {
114
+ name,
115
+ status: 'fail',
116
+ message: '- Check returned invalid result format.',
117
+ recommendation: 'Update the check implementation to return a valid object.'
118
+ };
119
+ }
120
+
121
+ const status = ALLOWED_STATUS.has(result.status) ? result.status : (result.success ? 'pass' : 'warn');
122
+
123
+ return {
124
+ name,
125
+ status,
126
+ message: result.message || '',
127
+ recommendation: result.recommendation || null
128
+ };
129
+ }
130
+
131
+ function summarize(diagnostics) {
132
+ const summary = { pass: 0, warn: 0, fail: 0, total: diagnostics.length };
133
+
134
+ for (const diagnostic of diagnostics) {
135
+ if (diagnostic.status === 'pass') summary.pass += 1;
136
+ if (diagnostic.status === 'warn') summary.warn += 1;
137
+ if (diagnostic.status === 'fail') summary.fail += 1;
138
+ }
139
+
140
+ return summary;
141
+ }
142
+
143
+ function renderCheckResult(spinner, result) {
144
+ if (!spinner) return;
145
+
146
+ if (result.status === 'pass') {
147
+ spinner.succeed(`[OK] ${result.name} ${result.message}`.trim());
148
+ return;
149
+ }
150
+
151
+ if (result.status === 'warn') {
152
+ spinner.warn(`[WARN] ${result.name} ${result.message}`.trim());
153
+ return;
154
+ }
155
+
156
+ spinner.fail(`[FAIL] ${result.name} ${result.message}`.trim());
157
+ }
158
+
159
+ function printHumanSummary(report) {
160
+ const { summary, diagnostics, strict, exitCode } = report;
161
+
162
+ console.log(chalk.cyan('\nSummary:\n'));
163
+ console.log(` ${chalk.green('Passed:')} ${summary.pass}`);
164
+ console.log(` ${chalk.yellow('Warnings:')} ${summary.warn}`);
165
+ console.log(` ${chalk.red('Failed:')} ${summary.fail}`);
166
+ console.log(` ${chalk.gray('Strict Mode:')} ${strict ? 'ON' : 'OFF'}`);
167
+ console.log(` ${chalk.gray('Exit Code:')} ${exitCode}\n`);
168
+
169
+ const actionable = diagnostics.filter(diagnostic => diagnostic.status !== 'pass' && diagnostic.recommendation);
170
+
171
+ if (actionable.length > 0) {
172
+ console.log(chalk.cyan('Recommendations:'));
173
+ for (const diagnostic of actionable) {
174
+ console.log(` - ${diagnostic.name}: ${diagnostic.recommendation}`);
175
+ }
176
+ console.log('');
177
+ }
178
+
179
+ if (report.ok) {
180
+ console.log(chalk.green('All systems ready! You are good to go.\n'));
181
+ } else {
182
+ console.log(chalk.yellow('Fix issues above before using offbyt.\n'));
183
+ }
184
+ }
185
+
186
+ function parseMajorVersion(versionString) {
187
+ const match = /v?(\d+)(?:\.\d+)?(?:\.\d+)?/.exec(versionString.trim());
188
+ if (!match) return null;
189
+
190
+ const major = Number.parseInt(match[1], 10);
191
+ return Number.isNaN(major) ? null : major;
192
+ }
193
+
194
+ function statusFromStrictMode(strict) {
195
+ return strict ? 'fail' : 'warn';
196
+ }
197
+
198
+ function isCommandMissing(error) {
199
+ const stderr = error?.stderr || '';
200
+ const message = error?.message || '';
201
+ const combined = `${message} ${stderr}`.toLowerCase();
202
+
203
+ return combined.includes('not recognized') || combined.includes('not found') || combined.includes('enoent');
204
+ }
205
+
206
+ async function checkNode(config) {
207
+ try {
208
+ const { stdout } = await execAsync('node --version');
209
+ const version = stdout.trim();
210
+ const major = parseMajorVersion(version);
211
+
212
+ if (major === null) {
213
+ return {
214
+ status: statusFromStrictMode(config.strict),
215
+ message: `(Could not parse detected version: ${version})`,
216
+ recommendation: 'Reinstall Node.js from https://nodejs.org.'
217
+ };
218
+ }
219
+
220
+ if (major < MIN_NODE_MAJOR) {
221
+ return {
222
+ status: statusFromStrictMode(config.strict),
223
+ message: `(Detected ${version}, requires >= v${MIN_NODE_MAJOR})`,
224
+ recommendation: `Upgrade Node.js to v${MIN_NODE_MAJOR}+.`
225
+ };
226
+ }
227
+
228
+ return {
229
+ status: 'pass',
230
+ message: `(${version})`
231
+ };
232
+ } catch {
233
+ return {
234
+ status: 'fail',
235
+ message: '- Node.js not found',
236
+ recommendation: 'Install Node.js from https://nodejs.org.'
237
+ };
238
+ }
239
+ }
240
+
241
+ async function checkNpm(config) {
242
+ try {
243
+ const { stdout } = await execAsync('npm --version');
244
+ const version = stdout.trim();
245
+ const major = parseMajorVersion(version);
246
+
247
+ if (major === null) {
248
+ return {
249
+ status: statusFromStrictMode(config.strict),
250
+ message: `(Could not parse detected version: ${version})`,
251
+ recommendation: 'Reinstall npm (or reinstall Node.js).'
252
+ };
253
+ }
254
+
255
+ if (major < MIN_NPM_MAJOR) {
256
+ return {
257
+ status: statusFromStrictMode(config.strict),
258
+ message: `(Detected ${version}, requires >= ${MIN_NPM_MAJOR})`,
259
+ recommendation: `Upgrade npm to ${MIN_NPM_MAJOR}+.`
260
+ };
261
+ }
262
+
263
+ return {
264
+ status: 'pass',
265
+ message: `(${version})`
266
+ };
267
+ } catch {
268
+ return {
269
+ status: 'fail',
270
+ message: '- npm not found',
271
+ recommendation: 'Install Node.js which includes npm.'
272
+ };
273
+ }
274
+ }
275
+
276
+ async function checkMongoDB(config) {
277
+ try {
278
+ await execAsync('mongosh --version', { timeout: 5000 });
279
+ } catch {
280
+ return {
281
+ status: statusFromStrictMode(config.strict),
282
+ message: '- mongosh not found',
283
+ recommendation: 'Install MongoDB Shell (mongosh) or use MongoDB Atlas.'
284
+ };
285
+ }
286
+
287
+ try {
288
+ await execAsync('mongosh --eval "db.adminCommand({ ping: 1 })" --quiet', { timeout: 7000 });
289
+ return {
290
+ status: 'pass',
291
+ message: '(Reachable on localhost)'
292
+ };
293
+ } catch (error) {
294
+ if (isCommandMissing(error)) {
295
+ return {
296
+ status: statusFromStrictMode(config.strict),
297
+ message: '- mongosh command unavailable',
298
+ recommendation: 'Install MongoDB Shell (mongosh).'
299
+ };
300
+ }
301
+
302
+ return {
303
+ status: statusFromStrictMode(config.strict),
304
+ message: '- MongoDB not reachable on localhost',
305
+ recommendation: 'Start local MongoDB (`mongod`) or update your connection to MongoDB Atlas.'
306
+ };
307
+ }
308
+ }
309
+
310
+ async function checkGit(config) {
311
+ try {
312
+ const { stdout } = await execAsync('git --version', { timeout: 5000 });
313
+ return {
314
+ status: 'pass',
315
+ message: `(${stdout.trim()})`
316
+ };
317
+ } catch {
318
+ return {
319
+ status: statusFromStrictMode(config.strict),
320
+ message: '- Git CLI not found',
321
+ recommendation: 'Install Git from https://git-scm.com/downloads.'
322
+ };
323
+ }
324
+ }
325
+
326
+ async function checkPort(config) {
327
+ const port = config.port;
328
+
329
+ try {
330
+ const net = await import('net');
331
+
332
+ return new Promise(resolve => {
333
+ const server = net.createServer();
334
+ let settled = false;
335
+
336
+ const done = result => {
337
+ if (settled) return;
338
+ settled = true;
339
+
340
+ if (server.listening) {
341
+ server.close(() => resolve(result));
342
+ } else {
343
+ resolve(result);
344
+ }
345
+ };
346
+
347
+ server.once('error', error => {
348
+ if (error?.code === 'EADDRINUSE') {
349
+ done({
350
+ status: 'fail',
351
+ message: `- Port ${port} is already in use`,
352
+ recommendation: `Free port ${port} or run your backend on a different port.`
353
+ });
354
+ return;
355
+ }
356
+
357
+ done({
358
+ status: 'fail',
359
+ message: `- Port ${port} check failed (${error.message})`,
360
+ recommendation: `Verify local permissions and port availability for ${port}.`
361
+ });
362
+ });
363
+
364
+ server.once('listening', () => {
365
+ done({
366
+ status: 'pass',
367
+ message: `(Port ${port} available)`
368
+ });
369
+ });
370
+
371
+ server.listen(port, '127.0.0.1');
372
+ });
373
+ } catch {
374
+ return {
375
+ status: 'fail',
376
+ message: `- Port ${port} check could not be completed`,
377
+ recommendation: 'Retry the command and verify your network stack permissions.'
378
+ };
379
+ }
380
+ }
381
+
@@ -0,0 +1,36 @@
1
+ import crypto from 'crypto';
2
+
3
+ /**
4
+ * Generate secure JWT secret
5
+ */
6
+ export function generateJWTSecret() {
7
+ return crypto.randomBytes(32).toString('hex');
8
+ }
9
+
10
+ /**
11
+ * Generate environment file for auth
12
+ */
13
+ export function generateAuthEnv() {
14
+ const jwtSecret = generateJWTSecret();
15
+
16
+ return `# ========== SERVER ==========
17
+ NODE_ENV=development
18
+ PORT=5000
19
+
20
+ # ========== DATABASE ==========
21
+ MONGODB_URI=mongodb://localhost:27017/offbyt-app
22
+ DB_NAME=offbyt-app
23
+
24
+ # ========== SECURITY ==========
25
+ JWT_SECRET=${jwtSecret}
26
+ JWT_EXPIRES_IN=7d
27
+ BCRYPT_ROUNDS=10
28
+
29
+ # ========== CORS ==========
30
+ CORS_ORIGIN=*
31
+
32
+ # ========== API ==========
33
+ API_URL=http://localhost:5000
34
+ `;
35
+ }
36
+
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Load Tester - Uses autocannon to simulate traffic
3
+ */
4
+
5
+ import autocannon from 'autocannon';
6
+
7
+ /**
8
+ * Run load test on a specific endpoint
9
+ */
10
+ export async function runLoadTest(options) {
11
+ const {
12
+ url,
13
+ method = 'GET',
14
+ connections = 10,
15
+ duration = 10,
16
+ body = null
17
+ } = options;
18
+
19
+ return new Promise((resolve, reject) => {
20
+ const instance = autocannon({
21
+ url,
22
+ method,
23
+ connections,
24
+ duration,
25
+ pipelining: 1,
26
+ body: body ? JSON.stringify(body) : undefined,
27
+ headers: body ? { 'Content-Type': 'application/json' } : undefined,
28
+ excludeErrorStats: true
29
+ }, (err, result) => {
30
+ if (err) {
31
+ reject(err);
32
+ } else {
33
+ resolve({
34
+ requests: {
35
+ total: result.requests.total,
36
+ average: result.requests.average,
37
+ mean: result.requests.mean
38
+ },
39
+ latency: {
40
+ mean: result.latency.mean,
41
+ p50: result.latency.p50,
42
+ p99: result.latency.p99,
43
+ max: result.latency.max
44
+ },
45
+ throughput: {
46
+ average: result.throughput.average,
47
+ mean: result.throughput.mean
48
+ },
49
+ errors: result.errors || 0,
50
+ timeouts: result.timeouts || 0,
51
+ non2xx: result.non2xx || 0
52
+ });
53
+ }
54
+ });
55
+
56
+ // Handle autocannon events
57
+ autocannon.track(instance);
58
+ });
59
+ }
60
+
61
+ export default { runLoadTest };