bro-framework 2.2.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/bro.js CHANGED
@@ -1,260 +1,254 @@
1
- #!/usr/bin/env node
2
-
3
- import path from 'path';
4
- import fs from 'fs';
5
- import { pathToFileURL } from 'url';
6
- import { register } from 'tsx/esm/api';
7
-
8
- register();
9
-
10
- import { createServer } from '../src/server.js';
11
- import { colors, printBanner, printRoute, printHotReload } from '../src/logger.js';
12
- import { scanTasks, stopTasks } from '../src/tasks.js';
13
- import { generateSDK } from '../src/sdk.js';
14
- import dotenv from 'dotenv';
15
- import chokidar from 'chokidar';
16
-
17
- dotenv.config();
18
-
19
- const command = process.argv[2] || 'dev';
20
-
21
- if (command === 'dev') {
22
- process.env.NODE_ENV = process.env.NODE_ENV || 'development';
23
- } else if (command === 'start') {
24
- process.env.NODE_ENV = 'production';
25
- }
26
-
27
- const CONFIG_TEMPLATE = `import { defineConfig } from 'bro-framework';
28
-
29
- export default defineConfig({
30
- // Server Settings
31
- server: {
32
- port: 5000,
33
- cors: true // Set to true to allow all, or pass a CORS options object
34
- },
35
-
36
- // Authentication Settings
37
- auth: {
38
- jwtSecret: 'dev_secret_please_change',
39
- expiresIn: '7d'
40
- },
41
-
42
- // API Documentation (Scalar UI)
43
- docs: process.env.NODE_ENV !== 'production', // Set to false to disable completely, or true to force in prod
44
-
45
- // Rate Limiting
46
- rateLimit: {
47
- windowMs: 15 * 60 * 1000, // 15 minutes
48
- max: 100 // limit each IP to 100 requests per windowMs
49
- },
50
-
51
- // WebSockets Setup
52
- sockets: async (io, db) => {
53
- io.on('connection', (socket) => {
54
- console.log('Client connected:', socket.id);
55
- });
56
- },
57
-
58
- // Database Context Injection
59
- // This instance will be injected into every route's ctx.db (if defined)
60
- db: async () => {
61
- // If you use a database, set up your connection here
62
- // and return the connection instance or an object of your models.
63
- // Could be MongoDB, MySQL, etc. (your choice)
64
- // --- MONGOOSE EXAMPLE ---
65
- // import mongoose from 'mongoose';
66
-
67
- // await mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost:27017/bro_database');
68
- // console.log("Connected to MongoDB");
69
-
70
- // You can return mongoose itself, or an object of your models
71
- // to access them instantly in your routes without importing them!
72
- // Example: return { User, Post };
73
-
74
- // return mongoose.connection;
75
- // --------------------------
76
- return null;
77
- }
78
- });
79
- `;
80
-
81
- function scaffoldConfig() {
82
- const configPath = path.join(process.cwd(), 'bro.config.js');
83
- if (!fs.existsSync(configPath)) {
84
- fs.writeFileSync(configPath, CONFIG_TEMPLATE, 'utf-8');
85
- console.log(`\n ${colors.green} Created default bro.config.js${colors.reset}\n`);
86
- }
87
- }
88
-
89
- function ensureTypeModule() {
90
- const pkgPath = path.join(process.cwd(), 'package.json');
91
-
92
- if (fs.existsSync(pkgPath)) {
93
- try {
94
- const pkgRaw = fs.readFileSync(pkgPath, 'utf-8');
95
- const pkg = JSON.parse(pkgRaw);
96
-
97
- if (pkg.type !== 'module') {
98
- pkg.type = 'module';
99
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf-8');
100
- console.log(`\n ${colors.green} Auto-configured package.json for ES Modules${colors.reset}`);
101
- }
102
- } catch (err) {
103
- console.error(`\n ${colors.red} Failed to parse package.json for ES Modules setup${colors.reset}`, err);
104
- }
105
- } else {
106
- const defaultPkg = {
107
- name: "bro-app",
108
- version: "1.0.0",
109
- type: "module",
110
- private: true
111
- };
112
- fs.writeFileSync(pkgPath, JSON.stringify(defaultPkg, null, 2), 'utf-8');
113
- console.log(`\n ${colors.green} Created package.json with ES Modules enabled${colors.reset}`);
114
- }
115
- }
116
-
117
- if (command === 'init') {
118
- ensureTypeModule();
119
- scaffoldConfig();
120
- process.exit(0);
121
- }
122
-
123
- if (['sdk', 'generate-client', 'client'].includes(command)) {
124
- generateSDK().then(() => {
125
- console.log(`\n ${colors.green} bro-sdk.js generated successfully!${colors.reset}\n`);
126
- process.exit(0);
127
- }).catch(err => {
128
- console.error(`\n ${colors.red} Error generating SDK:${colors.reset}`, err.message);
129
- process.exit(1);
130
- });
131
- }
132
-
133
- async function bootstrap() {
134
- if (command === 'dev') {
135
- ensureTypeModule();
136
- scaffoldConfig();
137
- }
138
-
139
- const startTime = performance.now();
140
-
141
- const cwd = process.cwd();
142
- const configPath = path.join(cwd, 'bro.config.js');
143
- const routesDir = path.join(cwd, 'routes');
144
-
145
- let globalConfig = {
146
- port: process.env.PORT || 5000,
147
- jwtSecret: process.env.JWT_SECRET || 'dev_secret_please_change'
148
- };
149
-
150
- let db = null;
151
-
152
- if (fs.existsSync(configPath)) {
153
- try {
154
- const configModule = await import(pathToFileURL(configPath).href);
155
- const userConfig = configModule.default || configModule.config || {};
156
-
157
- if (userConfig.server?.port) globalConfig.port = userConfig.server.port;
158
- if (userConfig.auth?.jwtSecret) globalConfig.jwtSecret = userConfig.auth.jwtSecret;
159
-
160
- globalConfig = { ...globalConfig, ...userConfig };
161
-
162
- if (typeof globalConfig.db === 'function') {
163
- db = await globalConfig.db();
164
- } else if (globalConfig.db && typeof globalConfig.db.init === 'function') {
165
- db = await globalConfig.db.init();
166
- } else if (globalConfig.db) {
167
- db = globalConfig.db;
168
- if (db instanceof Promise) db = await db;
169
- }
170
- } catch (err) {
171
- console.error('✗ Failed to load bro.config.js:', err);
172
- }
173
- }
174
-
175
- if (globalConfig.env) {
176
- const envResult = globalConfig.env.safeParse(process.env);
177
- if (!envResult.success) {
178
- console.error(`\n ${colors.red} Environment Validation Failed${colors.reset}`);
179
- envResult.error.errors.forEach(err => {
180
- console.error(` ${colors.dim}-${colors.reset} ${colors.bold}${err.path.join('.')}${colors.reset}: ${err.message}`);
181
- });
182
- console.error("");
183
- process.exit(1);
184
- }
185
- globalConfig.envData = envResult.data;
186
- }
187
-
188
- if (!fs.existsSync(routesDir)) {
189
- console.error(`✗ Error: 'routes' directory not found in ${cwd}`);
190
- console.error(` Please create a 'routes/' folder and add your first route.`);
191
- process.exit(1);
192
- }
193
-
194
- const { app, server, routes: initialRoutes, reload, io } = await createServer(globalConfig, routesDir, db);
195
- const port = globalConfig.port;
196
-
197
- let currentRoutes = initialRoutes;
198
-
199
- server.listen(port, async () => {
200
- if (command === 'dev') {
201
- console.clear();
202
- printBanner(port, performance.now() - startTime);
203
- } else if (command === 'start') {
204
- console.log(`[bro.js] Server running in production on port ${port}`);
205
- }
206
-
207
- await scanTasks({ db, io });
208
-
209
- if (command === 'dev') {
210
- const printCurrentRoutes = (routesToPrint) => {
211
- if (routesToPrint.length > 0) {
212
- routesToPrint.forEach((r, i) => {
213
- printRoute(r.method, r.path, r.auth, i === routesToPrint.length - 1);
214
- });
215
- console.log("");
216
- } else {
217
- console.log(" No routes found.\n");
218
- }
219
- };
220
-
221
- printCurrentRoutes(currentRoutes);
222
-
223
- const watcher = chokidar.watch(routesDir, { ignoreInitial: true });
224
-
225
- watcher.on('all', async (event, filepath) => {
226
- if (!filepath.endsWith('.js') && !filepath.endsWith('.ts')) return;
227
-
228
- try {
229
- const reloadStartTime = performance.now();
230
- currentRoutes = await reload();
231
- const reloadTimeMs = performance.now() - reloadStartTime;
232
-
233
- printHotReload(path.basename(filepath), event, reloadTimeMs);
234
- printCurrentRoutes(currentRoutes);
235
- } catch (err) {
236
- console.error(`\n ✗ Error hot-reloading routes:`, err);
237
- }
238
- });
239
- }
240
-
241
- const handleShutdown = async (signal) => {
242
- console.log(`\n[bro.js] Received ${signal}. Shutting down gracefully...`);
243
- stopTasks();
244
- if (io) io.close();
245
- server.close(() => {
246
- console.log('[bro.js] HTTP server closed.');
247
- process.exit(0);
248
- });
249
- };
250
-
251
- process.on('SIGINT', () => handleShutdown('SIGINT'));
252
- process.on('SIGTERM', () => handleShutdown('SIGTERM'));
253
- });
254
- }
255
-
256
- if (command === 'dev' || command === 'start') {
257
- bootstrap();
258
- } else {
259
- console.log(`Usage: bro dev | bro start | bro init`);
260
- }
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { pathToFileURL } from 'url';
6
+ import { register } from 'tsx/esm/api';
7
+
8
+ register();
9
+
10
+ import { createServer } from '../src/server.js';
11
+ import { colors, printBanner, printRoute, printHotReload } from '../src/logger.js';
12
+ import { generateSDK } from '../src/sdk.js';
13
+ import dotenv from 'dotenv';
14
+ import chokidar from 'chokidar';
15
+
16
+ dotenv.config();
17
+
18
+ const command = process.argv[2] || 'dev';
19
+
20
+ if (command === 'dev') {
21
+ process.env.NODE_ENV = process.env.NODE_ENV || 'development';
22
+ } else if (command === 'start') {
23
+ process.env.NODE_ENV = 'production';
24
+ }
25
+
26
+ const CONFIG_TEMPLATE = `import { defineConfig } from 'bro-framework';
27
+
28
+ export default defineConfig({
29
+ // Server Settings
30
+ server: {
31
+ port: 5000,
32
+ cors: true // Set to true to allow all, or pass a CORS options object
33
+ },
34
+
35
+ // Authentication Settings
36
+ auth: {
37
+ jwtSecret: 'dev_secret_please_change',
38
+ expiresIn: '7d'
39
+ },
40
+
41
+ // API Documentation (Scalar UI)
42
+ docs: process.env.NODE_ENV !== 'production', // Set to false to disable completely, or true to force in prod
43
+
44
+ // Rate Limiting
45
+ rateLimit: {
46
+ windowMs: 15 * 60 * 1000, // 15 minutes
47
+ max: 100 // limit each IP to 100 requests per windowMs
48
+ },
49
+
50
+ // WebSockets Setup
51
+ sockets: async (io, db) => {
52
+ io.on('connection', (socket) => {
53
+ console.log('Client connected:', socket.id);
54
+ });
55
+ },
56
+
57
+ // Database Context Injection
58
+ // This instance will be injected into every route's ctx.db (if defined)
59
+ db: async () => {
60
+ // If you use a database, set up your connection here
61
+ // and return the connection instance or an object of your models.
62
+ // Could be MongoDB, MySQL, etc. (your choice)
63
+ // --- MONGOOSE EXAMPLE ---
64
+ // import mongoose from 'mongoose';
65
+
66
+ // await mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost:27017/bro_database');
67
+ // console.log("Connected to MongoDB");
68
+
69
+ // You can return mongoose itself, or an object of your models
70
+ // to access them instantly in your routes without importing them!
71
+ // Example: return { User, Post };
72
+
73
+ // return mongoose.connection;
74
+ // --------------------------
75
+ return null;
76
+ }
77
+ });
78
+ `;
79
+
80
+ function scaffoldConfig() {
81
+ const configPath = path.join(process.cwd(), 'bro.config.js');
82
+ if (!fs.existsSync(configPath)) {
83
+ fs.writeFileSync(configPath, CONFIG_TEMPLATE, 'utf-8');
84
+ console.log(`\n ${colors.green} Created default bro.config.js${colors.reset}\n`);
85
+ }
86
+ }
87
+
88
+ function ensureTypeModule() {
89
+ const pkgPath = path.join(process.cwd(), 'package.json');
90
+
91
+ if (fs.existsSync(pkgPath)) {
92
+ try {
93
+ const pkgRaw = fs.readFileSync(pkgPath, 'utf-8');
94
+ const pkg = JSON.parse(pkgRaw);
95
+
96
+ if (pkg.type !== 'module') {
97
+ pkg.type = 'module';
98
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf-8');
99
+ console.log(`\n ${colors.green} Auto-configured package.json for ES Modules${colors.reset}`);
100
+ }
101
+ } catch (err) {
102
+ console.error(`\n ${colors.red} Failed to parse package.json for ES Modules setup${colors.reset}`, err);
103
+ }
104
+ } else {
105
+ const defaultPkg = {
106
+ name: "bro-app",
107
+ version: "1.0.0",
108
+ type: "module",
109
+ private: true
110
+ };
111
+ fs.writeFileSync(pkgPath, JSON.stringify(defaultPkg, null, 2), 'utf-8');
112
+ console.log(`\n ${colors.green} Created package.json with ES Modules enabled${colors.reset}`);
113
+ }
114
+ }
115
+
116
+ if (command === 'init') {
117
+ ensureTypeModule();
118
+ scaffoldConfig();
119
+ process.exit(0);
120
+ }
121
+
122
+ if (['sdk', 'generate-client', 'client'].includes(command)) {
123
+ generateSDK().then(() => {
124
+ console.log(`\n ${colors.green} bro-sdk.js generated successfully!${colors.reset}\n`);
125
+ process.exit(0);
126
+ }).catch(err => {
127
+ console.error(`\n ${colors.red} Error generating SDK:${colors.reset}`, err.message);
128
+ process.exit(1);
129
+ });
130
+ }
131
+
132
+ async function bootstrap() {
133
+ if (command === 'dev') {
134
+ ensureTypeModule();
135
+ scaffoldConfig();
136
+ }
137
+
138
+ const startTime = performance.now();
139
+
140
+ const cwd = process.cwd();
141
+ const configPath = path.join(cwd, 'bro.config.js');
142
+ const routesDir = path.join(cwd, 'routes');
143
+
144
+ let globalConfig = {
145
+ port: process.env.PORT || 5000,
146
+ jwtSecret: process.env.JWT_SECRET || 'dev_secret_please_change'
147
+ };
148
+
149
+ let db = null;
150
+
151
+ if (fs.existsSync(configPath)) {
152
+ try {
153
+ const configModule = await import(pathToFileURL(configPath).href);
154
+ const userConfig = configModule.default || configModule.config || {};
155
+
156
+ if (userConfig.server?.port) globalConfig.port = userConfig.server.port;
157
+ if (userConfig.auth?.jwtSecret) globalConfig.jwtSecret = userConfig.auth.jwtSecret;
158
+
159
+ globalConfig = { ...globalConfig, ...userConfig };
160
+
161
+ if (typeof globalConfig.db === 'function') {
162
+ db = await globalConfig.db();
163
+ } else if (globalConfig.db && typeof globalConfig.db.init === 'function') {
164
+ db = await globalConfig.db.init();
165
+ } else if (globalConfig.db) {
166
+ db = globalConfig.db;
167
+ if (db instanceof Promise) db = await db;
168
+ }
169
+ } catch (err) {
170
+ console.error('✗ Failed to load bro.config.js:', err);
171
+ }
172
+ }
173
+
174
+ if (globalConfig.env) {
175
+ const envResult = globalConfig.env.safeParse(process.env);
176
+ if (!envResult.success) {
177
+ console.error(`\n ${colors.red} Environment Validation Failed${colors.reset}`);
178
+ envResult.error.errors.forEach(err => {
179
+ console.error(` ${colors.dim}-${colors.reset} ${colors.bold}${err.path.join('.')}${colors.reset}: ${err.message}`);
180
+ });
181
+ console.error("");
182
+ process.exit(1);
183
+ }
184
+ globalConfig.envData = envResult.data;
185
+ }
186
+
187
+ if (!fs.existsSync(routesDir)) {
188
+ console.error(`✗ Error: 'routes' directory not found in ${cwd}`);
189
+ console.error(` Please create a 'routes/' folder and add your first route.`);
190
+ process.exit(1);
191
+ }
192
+
193
+ const { app, server, routes: initialRoutes, reload, io, shutdown } = await createServer(globalConfig, routesDir, db);
194
+ const port = globalConfig.port;
195
+
196
+ let currentRoutes = initialRoutes;
197
+
198
+ server.listen(port, async () => {
199
+ if (command === 'dev') {
200
+ console.clear();
201
+ printBanner(port, performance.now() - startTime);
202
+ } else if (command === 'start') {
203
+ console.log(`[bro.js] Server running in production on port ${port}`);
204
+ }
205
+
206
+ if (command === 'dev') {
207
+ const printCurrentRoutes = (routesToPrint) => {
208
+ if (routesToPrint.length > 0) {
209
+ routesToPrint.forEach((r, i) => {
210
+ printRoute(r.method, r.path, r.auth, i === routesToPrint.length - 1);
211
+ });
212
+ console.log("");
213
+ } else {
214
+ console.log(" No routes found.\n");
215
+ }
216
+ };
217
+
218
+ printCurrentRoutes(currentRoutes);
219
+
220
+ const watcher = chokidar.watch(routesDir, { ignoreInitial: true });
221
+
222
+ watcher.on('all', async (event, filepath) => {
223
+ if (!filepath.endsWith('.js') && !filepath.endsWith('.ts')) return;
224
+
225
+ try {
226
+ const reloadStartTime = performance.now();
227
+ currentRoutes = await reload();
228
+ const reloadTimeMs = performance.now() - reloadStartTime;
229
+
230
+ printHotReload(path.basename(filepath), event, reloadTimeMs);
231
+ printCurrentRoutes(currentRoutes);
232
+ } catch (err) {
233
+ console.error(`\n ✗ Error hot-reloading routes:`, err);
234
+ }
235
+ });
236
+ }
237
+
238
+ const handleShutdown = async (signal) => {
239
+ console.log(`\n[bro.js] Received ${signal}. Shutting down gracefully...`);
240
+ await shutdown();
241
+ console.log('[bro.js] HTTP server closed.');
242
+ process.exit(0);
243
+ };
244
+
245
+ process.on('SIGINT', () => handleShutdown('SIGINT'));
246
+ process.on('SIGTERM', () => handleShutdown('SIGTERM'));
247
+ });
248
+ }
249
+
250
+ if (command === 'dev' || command === 'start') {
251
+ bootstrap();
252
+ } else {
253
+ console.log(`Usage: bro dev | bro start | bro init`);
254
+ }