@seip/blue-bird 0.7.5 → 0.8.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 (42) hide show
  1. package/.env_example +34 -36
  2. package/AGENTS.md +174 -241
  3. package/LICENSE +21 -21
  4. package/README.md +312 -343
  5. package/{index.js → backend/index.js} +22 -30
  6. package/backend/routes/api.js +57 -57
  7. package/core/app.js +338 -402
  8. package/core/auth.js +262 -256
  9. package/core/cache.js +174 -174
  10. package/core/cli/docker.js +488 -370
  11. package/core/cli/init.js +332 -238
  12. package/core/cli/route.js +42 -42
  13. package/core/config.js +52 -52
  14. package/core/database.js +263 -182
  15. package/core/debug.js +248 -248
  16. package/core/logger.js +115 -115
  17. package/core/middleware.js +27 -27
  18. package/core/router.js +144 -144
  19. package/core/swagger.js +40 -40
  20. package/core/upload.js +77 -77
  21. package/core/validate.js +380 -380
  22. package/docker/Dockerfile +16 -16
  23. package/docker/docker-compose.dev.yml +6 -0
  24. package/docker/docker-compose.mysql.yml +92 -0
  25. package/docker/docker-compose.none.yml +68 -0
  26. package/docker/docker-compose.postgres.yml +93 -0
  27. package/docker/nginx.conf +98 -106
  28. package/docker-compose.yml +92 -93
  29. package/frontend/about.html +98 -0
  30. package/frontend/css/app.css +0 -0
  31. package/frontend/favicon.ico +0 -0
  32. package/frontend/index.html +141 -0
  33. package/frontend/js/bundle.js +8 -0
  34. package/package.json +64 -72
  35. package/backend/logs/2026-07-14/info.log +0 -48
  36. package/frontend/astro.config.mjs +0 -35
  37. package/frontend/public/css/app.css +0 -319
  38. package/frontend/public/favicon.ico +0 -0
  39. package/frontend/src/http/api.js +0 -29
  40. package/frontend/src/layouts/Layout.astro +0 -20
  41. package/frontend/src/pages/about.astro +0 -54
  42. package/frontend/src/pages/index.astro +0 -110
package/core/app.js CHANGED
@@ -1,402 +1,338 @@
1
- import express from "express";
2
- import cors from "cors";
3
- import path from "path";
4
- import fs from "node:fs";
5
- import chalk from "chalk";
6
- import helmet from "helmet";
7
- import cookieParser from "cookie-parser";
8
- import rateLimit from "express-rate-limit";
9
- import compression from "compression";
10
- import Config from "./config.js";
11
- import Logger from "./logger.js";
12
- import Debug from "./debug.js";
13
-
14
- const __dirname = Config.dirname();
15
- const props = Config.props();
16
-
17
- /**
18
- * Main Application class to manage Express server, routes, and middlewares.
19
- */
20
- class App {
21
- /**
22
- * Initializes the App instance with the provided options.
23
- * @param {Object} [options] - Configuration options for the application.
24
- * @param {Array<{path: string, router: import('express').Router}>} [options.routes=[]] - Array of route objects containing path and router components.
25
- * @param {Object} [options.cors={}] - CORS configuration options.
26
- * @param {Array<Function>} [options.middlewares=[]] - Array of middleware functions to be applied.
27
- * @param {number|string} [options.port=3000] - Server port.
28
- * @param {string} [options.host="http://localhost"] - Server host URL.
29
- * @param {boolean} [options.logger=true] - Whether to enable the request logger.
30
- * @param {boolean} [options.notFound=true] - Whether to enable the default 404 handler.
31
- * @param {boolean} [options.json=true] - Whether to enable JSON body parsing.
32
- * @param {boolean} [options.urlencoded=true] - Whether to enable URL-encoded body parsing.
33
- * @param {Object} [options.static={path: null, options: {}}] - Static file configuration.
34
- * @param {boolean} [options.cookieParser=true] - Whether to enable cookie parsing.
35
- * @param {boolean|Object} [options.rateLimit=false] - Enable global rate limiting.
36
- * @param {boolean|Object} [options.swagger=false] - Enable swagger.
37
- * @param {boolean} [options.compression=true] - Enable compression.
38
- * @param {boolean} [options.astro=true] - Astro handler.
39
- * @example
40
- * const app = new App({
41
- * routes: [],
42
- * cors: {},
43
- * middlewares: [],
44
- * port: 3000,
45
- * host: "http://localhost",
46
- * logger: true,
47
- * notFound: true,
48
- * json: true,
49
- * urlencoded: true,
50
- * static: { path: "public", options: {} },
51
- * cookieParser: true,
52
- * rateLimit: { windowMs: 10 * 60 * 1000, max: 50 },
53
- * swagger: {
54
- * info: { title: "Blue Bird API", version: "1.0.0", description: "API Documentation" },
55
- * url: "http://localhost:8000"
56
- * },
57
- * compression:true,
58
- * astro: {
59
- * server: true,
60
- * serverEntry: "./frontend/dist/server/entry.mjs",
61
- * client: false,
62
- * clientDir: "./frontend/dist/client",
63
- * base: "/"
64
- * }
65
- * });
66
- */
67
- constructor(options = {}) {
68
- this.app = express();
69
- this.routes = options.routes || [];
70
- this.cors = options.cors || {};
71
- this.middlewares = options.middlewares || [];
72
- this.port = options.port || props.port;
73
- this.host = options.host || props.host;
74
- this.appUrl = options.appUrl || props.appUrl;
75
- this.logger = options.logger ?? false;
76
- this.notFound = options.notFound ?? true;
77
- this.json = options.json ?? true;
78
- this.urlencoded = options.urlencoded ?? true;
79
- this.static = options.static || props.static;
80
- this.cookieParser = options.cookieParser ?? true;
81
- this.rateLimit = options.rateLimit ?? false;
82
- this.swagger = options.swagger ?? false;
83
- this.compression = options.compression ?? true;
84
- this.astro = options.astro || false;
85
- this.loggerInstance = new Logger();
86
- /** @type {Set<import('http').ServerResponse>} */
87
- this._hotReloadClients = new Set();
88
- this._ready = this._dispatch();
89
- }
90
-
91
- /**
92
- * Registers a custom middleware or module in the Express application.
93
- * @param {Function|import('express').Router} record - The middleware function or Express router to register.
94
- * @example
95
- * app.use((req, res, next) => {
96
- * console.log("Middleware");
97
- * next();
98
- * });
99
- */
100
- use(record) {
101
- this.app.use(record);
102
- }
103
-
104
- /**
105
- * Sets a configuration value in the Express application.
106
- * @param {string} key - The configuration key.
107
- * @param {*} value - The value to set for the configuration key.
108
- */
109
- set(key, value) {
110
- this.app.set(key, value);
111
- }
112
-
113
- /**
114
- * Bootstraps the application by configuring global middlewares and routes.
115
- * @private
116
- */
117
- async _dispatch() {
118
- if (this.compression) this.app.use(compression());
119
- if (this.json) this.app.use(express.json());
120
- if (this.urlencoded) this.app.use(express.urlencoded({ extended: true }));
121
- if (this.cookieParser) this.app.use(cookieParser());
122
-
123
- this.app.use((req, res, next) => {
124
- req.lang = req.query?.lang || req.body?.lang || req.cookies?.lang || "en";
125
- res.locals.lang = req.lang;
126
- next();
127
- });
128
-
129
- if (this.static.path)
130
- this.app.use(
131
- express.static(path.join(__dirname, this.static.path), {
132
- ...this.static.options,
133
- setHeaders: (res) => {
134
- res.setHeader("X-Powered-By", "Blue Bird");
135
- res.setHeader(
136
- "Cache-Control",
137
- "public, max-age=31536000, immutable",
138
- );
139
- },
140
- }),
141
- );
142
-
143
- this.app.use(cors(this.cors));
144
- if (this.rateLimit) {
145
- if (!this.app.get("trust proxy")) {
146
- this.app.set("trust proxy", 1);
147
- }
148
- const defaultRateLimit = {
149
- windowMs: 15 * 60 * 1000,
150
- max: 500,
151
- standardHeaders: true,
152
- legacyHeaders: false,
153
- message: {
154
- success: false,
155
- message: "Too many requests, please try again later.",
156
- },
157
- };
158
- const optionsRateLimiter = {
159
- ...defaultRateLimit,
160
- ...(typeof this.rateLimit === "object" ? this.rateLimit : {}),
161
- };
162
-
163
- if (props.debug) {
164
- optionsRateLimiter.skip = (req) => req.path.startsWith("/debug");
165
- }
166
-
167
- const limiter = rateLimit(optionsRateLimiter);
168
- this.app.use(limiter);
169
- }
170
-
171
- this.middlewares.forEach((middleware) => {
172
- this.app.use(middleware);
173
- });
174
-
175
- if (this.logger || props.debug) this._middlewareLogger(this.logger);
176
-
177
- this.app.use((req, res, next) => {
178
- res.setHeader("X-Powered-By", "Blue Bird");
179
- next();
180
- });
181
-
182
- if (props.debug) {
183
- Debug.middlewareMetrics(this.app);
184
- }
185
-
186
- if (this.swagger) {
187
- const { default: Swagger } = await import("./swagger.js");
188
- const defaultSwaggerOptions = {
189
- info: {
190
- title: "Blue Bird API",
191
- version: "1.0.0",
192
- description: "Blue Bird Framework API Documentation",
193
- },
194
- url: this.appUrl ? this.appUrl : `${this.host}:${this.port}`,
195
- route: "/docs",
196
- };
197
-
198
- const swaggerOptions = {
199
- ...defaultSwaggerOptions,
200
- ...(typeof this.swagger === "object" ? this.swagger : {}),
201
- };
202
-
203
- Swagger.init(this.app, swaggerOptions);
204
- }
205
-
206
- this._dispatchRoutes();
207
-
208
- if (this.astro) {
209
- const defaultAstro = {
210
- server: true,
211
- serverEntry: "./frontend/dist/server/entry.mjs",
212
- client: false,
213
- clientDir: "./frontend/dist/client",
214
- base: "/",
215
- };
216
- const astroConfig =
217
- typeof this.astro === "object"
218
- ? { ...defaultAstro, ...this.astro }
219
- : { ...defaultAstro };
220
-
221
- if (astroConfig.client) {
222
- const clientPath = path.resolve(astroConfig.clientDir);
223
- if (fs.existsSync(clientPath)) {
224
- this.app.use(astroConfig.base, express.static(clientPath));
225
- console.log(
226
- chalk.green(
227
- `[OK] Astro Static Client Assets registered at ${astroConfig.base}`,
228
- ),
229
- );
230
- } else {
231
- console.warn(
232
- chalk.yellow(
233
- `[WARN] Astro client directory not found at: ${clientPath}`,
234
- ),
235
- );
236
- }
237
- }
238
-
239
- if (astroConfig.server) {
240
- const entryPath = path.resolve(astroConfig.serverEntry);
241
- if (fs.existsSync(entryPath)) {
242
- try {
243
- const { handler: ssrHandler } = await import(entryPath);
244
- this.app.use(ssrHandler);
245
- console.log(
246
- chalk.green("[OK] Astro SSR Handler registered successfully."),
247
- );
248
- } catch (error) {
249
- console.error(
250
- chalk.red("[ERROR] Failed to load Astro SSR Handler:"),
251
- error.message,
252
- );
253
- }
254
- } else {
255
- console.warn(
256
- chalk.yellow(
257
- `[WARN] Astro build entrypoint not found at: ${entryPath}`,
258
- ),
259
- );
260
- }
261
- }
262
- }
263
-
264
- if (this.notFound) this._notFoundDefault();
265
-
266
- this._errorHandler();
267
- }
268
-
269
- /**
270
- * Middleware that logs incoming HTTP requests to the console and to a log file.
271
- * @private
272
- * @param {boolean} [logger=false]
273
- */
274
- _middlewareLogger(logger = false) {
275
- this.app.use((req, res, next) => {
276
- const method = req.method;
277
- const url = req.url.replace(
278
- /(password|token|authorization)=([^&]+)/gi,
279
- "$1=***",
280
- );
281
- if (url.includes("chrome")) return;
282
- const params =
283
- Object.keys(req.params).length > 0
284
- ? ` ${JSON.stringify(req.params)}`
285
- : "";
286
-
287
- const ip = req.ip;
288
- const now = new Date().toISOString();
289
- const time = `${now.split("T")[0]} ${now.split("T")[1].split(".")[0]}`;
290
- let message = ` ${time} -${ip} -[${method}] ${url} ${params}`;
291
-
292
- if (logger) this.loggerInstance.info(message);
293
-
294
- if (props.debug) {
295
- message = `${chalk.bold.green(time)} - ${chalk.bold.cyan(ip)} -[${chalk.bold.red(method)}] ${chalk.bold.blue(url)} ${chalk.bold.yellow(params)}`;
296
- console.log(message);
297
- }
298
- next();
299
- });
300
- }
301
-
302
- /**
303
- * Global error handler for the application.
304
- * @private
305
- */
306
- _errorHandler() {
307
- this.app.use((err, req, res, next) => {
308
- const status = err.status || 500;
309
- const message = err.message || "Internal Server Error";
310
-
311
- this.loggerInstance.error(`[${status}] ${message} - ${err.stack}`);
312
-
313
- if (props.debug) {
314
- return res.status(status).json({
315
- success: false,
316
- error: true,
317
- message: message,
318
- stack: err.stack,
319
- });
320
- }
321
-
322
- return res.status(status).json({
323
- success: false,
324
- error: true,
325
- message: status === 500 ? "Internal Server Error" : message,
326
- });
327
- });
328
- }
329
-
330
- /**
331
- * Iterates through the stored routes and attaches them to the Express application instance.
332
- * @private
333
- */
334
- _dispatchRoutes() {
335
- if (props.debug) {
336
- const debug = new Debug();
337
- const debugRouter = debug.getRouter();
338
- this.app.use(debugRouter.path, debugRouter.router);
339
- }
340
- this.routes.forEach((route) => {
341
- this.app.use(route.path, route.router);
342
- });
343
- }
344
-
345
- /**
346
- * Default 404 handler for unmatched routes.
347
- * @private
348
- */
349
- _notFoundDefault() {
350
- this.app.use((req, res) => {
351
- return res.status(404).json({ message: "Not Found" });
352
- });
353
- }
354
-
355
- /**
356
- * Starts the HTTP server and begins listening for incoming connections.
357
- */
358
-
359
- run() {
360
- this._ready
361
- .then(() => {
362
- this.app.listen(this.port, () => {
363
- console.log(
364
- chalk.bold.blue("Blue Bird Server Online\n") +
365
- chalk.bold.cyan("App URL: ") +
366
- chalk.green(`${this.appUrl}`) +
367
- "\n" +
368
- chalk.bold.cyan("Internal: ") +
369
- chalk.green(`${this.host}:${this.port}`) +
370
- "\n" +
371
- (props.debug ? chalk.bold.magenta("Hot Reload: enabled\n") : "") +
372
- chalk.gray("────────────────────────────────"),
373
- );
374
- });
375
- })
376
- .catch((err) => {
377
- console.error(
378
- chalk.bold.red("Failed to start Blue Bird:"),
379
- err.message,
380
- );
381
- process.exit(1);
382
- });
383
- }
384
-
385
- /**
386
- * Returns a pre-configured Helmet middleware for use on specific routers.
387
- * @param {Object} [options={}] - Helmet options to override defaults.
388
- * @returns {Function} Helmet middleware function.
389
- * @example
390
- * const router = new Router("/web");
391
- * router.use(App.helmet({ contentSecurityPolicy: false }));
392
- */
393
- static helmet(options = {}) {
394
- const defaultOptions = {
395
- contentSecurityPolicy: props.debug ? false : undefined,
396
- hidePoweredBy: false,
397
- };
398
- return helmet({ ...defaultOptions, ...options });
399
- }
400
- }
401
-
402
- export default App;
1
+ import express from "express";
2
+ import cors from "cors";
3
+ import path from "path";
4
+ import fs from "node:fs";
5
+ import chalk from "chalk";
6
+ import helmet from "helmet";
7
+ import cookieParser from "cookie-parser";
8
+ import rateLimit from "express-rate-limit";
9
+ import compression from "compression";
10
+ import Config from "./config.js";
11
+ import Logger from "./logger.js";
12
+ import Debug from "./debug.js";
13
+
14
+ const __dirname = Config.dirname();
15
+ const props = Config.props();
16
+
17
+ /**
18
+ * Main Application class to manage Express server, routes, and middlewares.
19
+ */
20
+ class App {
21
+ /**
22
+ * Initializes the App instance with the provided options.
23
+ * @param {Object} [options] - Configuration options for the application.
24
+ * @param {Array<{path: string, router: import('express').Router}>} [options.routes=[]] - Array of route objects containing path and router components.
25
+ * @param {Object} [options.cors={}] - CORS configuration options.
26
+ * @param {Array<Function>} [options.middlewares=[]] - Array of middleware functions to be applied.
27
+ * @param {number|string} [options.port=3000] - Server port.
28
+ * @param {string} [options.host="http://localhost"] - Server host URL.
29
+ * @param {boolean} [options.logger=true] - Whether to enable the request logger.
30
+ * @param {boolean} [options.notFound=true] - Whether to enable the default 404 handler.
31
+ * @param {boolean} [options.json=true] - Whether to enable JSON body parsing.
32
+ * @param {boolean} [options.urlencoded=true] - Whether to enable URL-encoded body parsing.
33
+ * @param {Object} [options.static={path: null, options: {}}] - Static file configuration.
34
+ * @param {boolean} [options.cookieParser=true] - Whether to enable cookie parsing.
35
+ * @param {boolean|Object} [options.rateLimit=false] - Enable global rate limiting.
36
+ * @param {boolean|Object} [options.swagger=false] - Enable swagger.
37
+ * @param {boolean} [options.compression=true] - Enable compression.
38
+ * @example
39
+ * const app = new App({
40
+ * routes: [],
41
+ * cors: {},
42
+ * middlewares: [],
43
+ * port: 3000,
44
+ * host: "http://localhost",
45
+ * logger: true,
46
+ * notFound: true,
47
+ * json: true,
48
+ * urlencoded: true,
49
+ * static: { path: "public", options: {} },
50
+ * cookieParser: true,
51
+ * rateLimit: { windowMs: 10 * 60 * 1000, max: 50 },
52
+ * swagger: {
53
+ * info: { title: "Blue Bird API", version: "1.0.0", description: "API Documentation" },
54
+ * url: "http://localhost:8000"
55
+ * },
56
+ * compression:true
57
+ * });
58
+ */
59
+ constructor(options = {}) {
60
+ this.app = express();
61
+ this.routes = options.routes || [];
62
+ this.cors = options.cors || {};
63
+ this.middlewares = options.middlewares || [];
64
+ this.port = options.port || props.port;
65
+ this.host = options.host || props.host;
66
+ this.appUrl = options.appUrl || props.appUrl;
67
+ this.logger = options.logger ?? false;
68
+ this.notFound = options.notFound ?? true;
69
+ this.json = options.json ?? true;
70
+ this.urlencoded = options.urlencoded ?? true;
71
+ this.static = options.static || props.static;
72
+ this.cookieParser = options.cookieParser ?? true;
73
+ this.rateLimit = options.rateLimit ?? false;
74
+ this.swagger = options.swagger ?? false;
75
+ this.compression = options.compression ?? true;
76
+ this.loggerInstance = new Logger();
77
+ /** @type {Set<import('http').ServerResponse>} */
78
+ this._hotReloadClients = new Set();
79
+ this._ready = this._dispatch();
80
+ }
81
+
82
+ /**
83
+ * Registers a custom middleware or module in the Express application.
84
+ * @param {Function|import('express').Router} record - The middleware function or Express router to register.
85
+ * @example
86
+ * app.use((req, res, next) => {
87
+ * console.log("Middleware");
88
+ * next();
89
+ * });
90
+ */
91
+ use(record) {
92
+ this.app.use(record);
93
+ }
94
+
95
+ /**
96
+ * Sets a configuration value in the Express application.
97
+ * @param {string} key - The configuration key.
98
+ * @param {*} value - The value to set for the configuration key.
99
+ */
100
+ set(key, value) {
101
+ this.app.set(key, value);
102
+ }
103
+
104
+ /**
105
+ * Bootstraps the application by configuring global middlewares and routes.
106
+ * @private
107
+ */
108
+ async _dispatch() {
109
+ if (this.compression) this.app.use(compression());
110
+ if (this.json) this.app.use(express.json());
111
+ if (this.urlencoded) this.app.use(express.urlencoded({ extended: true }));
112
+ if (this.cookieParser) this.app.use(cookieParser());
113
+
114
+ this.app.use((req, res, next) => {
115
+ req.lang = req.query?.lang || req.body?.lang || req.cookies?.lang || "en";
116
+ res.locals.lang = req.lang;
117
+ next();
118
+ });
119
+
120
+ if (this.static.path)
121
+ this.app.use(
122
+ express.static(path.join(__dirname, this.static.path), {
123
+ ...this.static.options,
124
+ setHeaders: (res) => {
125
+ res.setHeader("X-Powered-By", "Blue Bird");
126
+ res.setHeader(
127
+ "Cache-Control",
128
+ "public, max-age=31536000, immutable",
129
+ );
130
+ },
131
+ }),
132
+ );
133
+
134
+ this.app.use(cors(this.cors));
135
+ if (this.rateLimit) {
136
+ if (!this.app.get("trust proxy")) {
137
+ this.app.set("trust proxy", 1);
138
+ }
139
+ const defaultRateLimit = {
140
+ windowMs: 15 * 60 * 1000,
141
+ max: 500,
142
+ standardHeaders: true,
143
+ legacyHeaders: false,
144
+ message: {
145
+ success: false,
146
+ message: "Too many requests, please try again later.",
147
+ },
148
+ };
149
+ const optionsRateLimiter = {
150
+ ...defaultRateLimit,
151
+ ...(typeof this.rateLimit === "object" ? this.rateLimit : {}),
152
+ };
153
+
154
+ if (props.debug) {
155
+ optionsRateLimiter.skip = (req) => req.path.startsWith("/debug");
156
+ }
157
+
158
+ const limiter = rateLimit(optionsRateLimiter);
159
+ this.app.use(limiter);
160
+ }
161
+
162
+ this.middlewares.forEach((middleware) => {
163
+ this.app.use(middleware);
164
+ });
165
+
166
+ if (this.logger || props.debug) this._middlewareLogger(this.logger);
167
+
168
+ this.app.use((req, res, next) => {
169
+ res.setHeader("X-Powered-By", "Blue Bird");
170
+ next();
171
+ });
172
+
173
+ if (props.debug) {
174
+ Debug.middlewareMetrics(this.app);
175
+ }
176
+
177
+ if (this.swagger) {
178
+ const { default: Swagger } = await import("./swagger.js");
179
+ const defaultSwaggerOptions = {
180
+ info: {
181
+ title: "Blue Bird API",
182
+ version: "1.0.0",
183
+ description: "Blue Bird Framework API Documentation",
184
+ },
185
+ url: this.appUrl ? this.appUrl : `${this.host}:${this.port}`,
186
+ route: "/docs",
187
+ };
188
+
189
+ const swaggerOptions = {
190
+ ...defaultSwaggerOptions,
191
+ ...(typeof this.swagger === "object" ? this.swagger : {}),
192
+ };
193
+
194
+ Swagger.init(this.app, swaggerOptions);
195
+ }
196
+
197
+ this._dispatchRoutes();
198
+
199
+
200
+ if (this.notFound) this._notFoundDefault();
201
+
202
+ this._errorHandler();
203
+ }
204
+
205
+ /**
206
+ * Middleware that logs incoming HTTP requests to the console and to a log file.
207
+ * @private
208
+ * @param {boolean} [logger=false]
209
+ */
210
+ _middlewareLogger(logger = false) {
211
+ this.app.use((req, res, next) => {
212
+ const method = req.method;
213
+ const url = req.url.replace(
214
+ /(password|token|authorization)=([^&]+)/gi,
215
+ "$1=***",
216
+ );
217
+ if (url.includes("chrome")) return;
218
+ const params =
219
+ Object.keys(req.params).length > 0
220
+ ? ` ${JSON.stringify(req.params)}`
221
+ : "";
222
+
223
+ const ip = req.ip;
224
+ const now = new Date().toISOString();
225
+ const time = `${now.split("T")[0]} ${now.split("T")[1].split(".")[0]}`;
226
+ let message = ` ${time} -${ip} -[${method}] ${url} ${params}`;
227
+
228
+ if (logger) this.loggerInstance.info(message);
229
+
230
+ if (props.debug) {
231
+ message = `${chalk.bold.green(time)} - ${chalk.bold.cyan(ip)} -[${chalk.bold.red(method)}] ${chalk.bold.blue(url)} ${chalk.bold.yellow(params)}`;
232
+ console.log(message);
233
+ }
234
+ next();
235
+ });
236
+ }
237
+
238
+ /**
239
+ * Global error handler for the application.
240
+ * @private
241
+ */
242
+ _errorHandler() {
243
+ this.app.use((err, req, res, next) => {
244
+ const status = err.status || 500;
245
+ const message = err.message || "Internal Server Error";
246
+
247
+ this.loggerInstance.error(`[${status}] ${message} - ${err.stack}`);
248
+
249
+ if (props.debug) {
250
+ return res.status(status).json({
251
+ success: false,
252
+ error: true,
253
+ message: message,
254
+ stack: err.stack,
255
+ });
256
+ }
257
+
258
+ return res.status(status).json({
259
+ success: false,
260
+ error: true,
261
+ message: status === 500 ? "Internal Server Error" : message,
262
+ });
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Iterates through the stored routes and attaches them to the Express application instance.
268
+ * @private
269
+ */
270
+ _dispatchRoutes() {
271
+ if (props.debug) {
272
+ const debug = new Debug();
273
+ const debugRouter = debug.getRouter();
274
+ this.app.use(debugRouter.path, debugRouter.router);
275
+ }
276
+ this.routes.forEach((route) => {
277
+ this.app.use(route.path, route.router);
278
+ });
279
+ }
280
+
281
+ /**
282
+ * Default 404 handler for unmatched routes.
283
+ * @private
284
+ */
285
+ _notFoundDefault() {
286
+ this.app.use((req, res) => {
287
+ return res.status(404).json({ message: "Not Found" });
288
+ });
289
+ }
290
+
291
+ /**
292
+ * Starts the HTTP server and begins listening for incoming connections.
293
+ */
294
+
295
+ run() {
296
+ this._ready
297
+ .then(() => {
298
+ this.app.listen(this.port, () => {
299
+ console.log(
300
+ chalk.bold.blue("Blue Bird Server Online\n") +
301
+ chalk.bold.cyan("App URL: ") +
302
+ chalk.green(`${this.appUrl}`) +
303
+ "\n" +
304
+ chalk.bold.cyan("Internal: ") +
305
+ chalk.green(`${this.host}:${this.port}`) +
306
+ "\n" +
307
+ (props.debug ? chalk.bold.magenta("Hot Reload: enabled\n") : "") +
308
+ chalk.gray("────────────────────────────────"),
309
+ );
310
+ });
311
+ })
312
+ .catch((err) => {
313
+ console.error(
314
+ chalk.bold.red("Failed to start Blue Bird:"),
315
+ err.message,
316
+ );
317
+ process.exit(1);
318
+ });
319
+ }
320
+
321
+ /**
322
+ * Returns a pre-configured Helmet middleware for use on specific routers.
323
+ * @param {Object} [options={}] - Helmet options to override defaults.
324
+ * @returns {Function} Helmet middleware function.
325
+ * @example
326
+ * const router = new Router("/web");
327
+ * router.use(App.helmet({ contentSecurityPolicy: false }));
328
+ */
329
+ static helmet(options = {}) {
330
+ const defaultOptions = {
331
+ contentSecurityPolicy: props.debug ? false : undefined,
332
+ hidePoweredBy: false,
333
+ };
334
+ return helmet({ ...defaultOptions, ...options });
335
+ }
336
+ }
337
+
338
+ export default App;