@seip/blue-bird 1.0.2 → 1.1.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/.env_example CHANGED
@@ -6,6 +6,7 @@ APP_URL="http://localhost"
6
6
  VERSION="1.0.0"
7
7
 
8
8
  # Docker / Swagger Config
9
+ COMPOSE_PROJECT_NAME="blue-bird"
9
10
  TITLE="Blue-Bird"
10
11
  DESCRIPTION="Description project"
11
12
 
package/AGENTS.md CHANGED
@@ -6,7 +6,7 @@ This document serves as the primary manual for any AI Agent interacting with the
6
6
 
7
7
  Blue Bird is a performance-first API framework built on **Express**. It saves developers from repetitive configuration, validation, security, JWT authentication, and database environment configuration out of the box, delegating all static frontend rendering to Nginx.
8
8
 
9
- - **Entrypoint (`index.js`)**: Initializes the server using `App` from `core/app.js` and registers the routes.
9
+ - **Entrypoint (`backend/index.js`)**: Initializes the server using `App` from `core/app.js` and registers the routes.
10
10
  - **Backend (`backend/`)**: Application routes and logic (e.g. `backend/routes/`).
11
11
  - **Frontend (`frontend/`)**: Static assets (HTML, CSS, JS). Handled directly by Nginx in production, bypassing Express.
12
12
  - **Core (`core/`)**: The framework core. Contains wrapper classes such as `Router`, `Validator`, `Auth`, `Cache`, etc. **DO NOT MODIFY** the core unless explicitly requested, as it could break other apps.
@@ -145,25 +145,25 @@ async function startCommand(service) {
145
145
  /**
146
146
  * Handles the 'dev' CLI command.
147
147
  */
148
- async function devCommand() {
149
- checkComposeFile();
150
- const dbType = getDbType();
151
- console.log(chalk.cyan("Starting development environment (Database + Redis)..."));
152
-
153
- const containers = ["redis"];
154
- if (dbType !== "none") {
155
- containers.unshift(dbType);
156
- }
157
-
158
- const code = await runCmd("docker", ["compose", "up", "-d", ...containers]);
159
- if (code === 0) {
160
- console.log(chalk.green("Development containers started."));
161
- console.log(chalk.cyan("View live logs with: npx blue-bird docker logs db"));
162
- console.log(chalk.yellow("Now execute: npm run dev"));
163
- } else {
164
- console.error(chalk.red("Error starting development environment."));
165
- process.exit(1);
166
- }
148
+ async function devCommand() {
149
+ checkComposeFile();
150
+ const dbType = getDbType();
151
+ console.log(chalk.cyan("Starting development environment (Database + Redis)..."));
152
+
153
+ const containers = ["redis"];
154
+ if (dbType !== "none") {
155
+ containers.unshift(dbType);
156
+ }
157
+
158
+ const code = await runCmd("docker", ["compose", "up", "-d", ...containers]);
159
+ if (code === 0) {
160
+ console.log(chalk.green("Development containers started."));
161
+ console.log(chalk.cyan("View live logs with: npx blue-bird docker logs db"));
162
+ console.log(chalk.yellow("Now execute: npm run dev"));
163
+ } else {
164
+ console.error(chalk.red("Error starting development environment."));
165
+ process.exit(1);
166
+ }
167
167
  }
168
168
 
169
169
  /**
package/core/cli/init.js CHANGED
@@ -103,8 +103,6 @@ class ProjectInit {
103
103
  "docker",
104
104
  ".env_example",
105
105
  "AGENTS.md",
106
- "index.js",
107
- ".gitignore"
108
106
  ];
109
107
 
110
108
  try {
@@ -128,6 +126,24 @@ class ProjectInit {
128
126
  }
129
127
  });
130
128
 
129
+ const gitignoreContent = `node_modules\nlogs\n.env\npackage-lock.json\n*.db\ntest/\n\nbackups/*.sql\n`;
130
+ const gitignoreDest = path.join(this.appDir, ".gitignore");
131
+ const gitignoreSrc = path.join(this.sourceDir, ".gitignore");
132
+
133
+ if (!fs.existsSync(gitignoreDest)) {
134
+ if (fs.existsSync(gitignoreSrc)) {
135
+ fs.copyFileSync(gitignoreSrc, gitignoreDest);
136
+ console.log(chalk.green("[OK] Copied .gitignore to root."));
137
+ } else {
138
+ fs.writeFileSync(gitignoreDest, gitignoreContent, "utf-8");
139
+ console.log(chalk.green("[OK] Created .gitignore file."));
140
+ }
141
+ } else {
142
+ console.log(
143
+ chalk.yellow("[SKIP] .gitignore already exists, skipping."),
144
+ );
145
+ }
146
+
131
147
  const composeTemplateName =
132
148
  dbType === "postgres"
133
149
  ? "docker-compose.postgres.yml"
@@ -166,8 +182,15 @@ class ProjectInit {
166
182
  let envContent = fs.readFileSync(envExamplePath, "utf-8");
167
183
 
168
184
  const jwtSecret = crypto.randomBytes(32).toString("hex");
185
+ const composeProjectName =
186
+ title
187
+ .toLowerCase()
188
+ .trim()
189
+ .replace(/\s+/g, "-")
190
+ .replace(/[^a-z0-9_-]/g, "") || "blue-bird";
169
191
 
170
192
  const updates = {
193
+ COMPOSE_PROJECT_NAME: composeProjectName,
171
194
  TITLE: title,
172
195
  PORT: port,
173
196
  APP_URL: appUrl,
@@ -190,10 +213,14 @@ class ProjectInit {
190
213
  }
191
214
 
192
215
  const lines = envContent.split(/\r?\n/);
216
+ let foundComposeProjectName = false;
193
217
  const updatedLines = lines.map((line) => {
194
218
  const match = line.match(/^([A-Z_]+)=(.+)/);
195
219
  if (match) {
196
220
  const key = match[1];
221
+ if (key === "COMPOSE_PROJECT_NAME") {
222
+ foundComposeProjectName = true;
223
+ }
197
224
  if (updates[key] !== undefined) {
198
225
  const value = updates[key];
199
226
  if (typeof value === "string" && !value.startsWith('"')) {
@@ -204,6 +231,21 @@ class ProjectInit {
204
231
  }
205
232
  return line;
206
233
  });
234
+
235
+ if (!foundComposeProjectName && updates.COMPOSE_PROJECT_NAME) {
236
+ const titleIdx = updatedLines.findIndex((l) => l.startsWith("TITLE="));
237
+ if (titleIdx !== -1) {
238
+ updatedLines.splice(
239
+ titleIdx,
240
+ 0,
241
+ `COMPOSE_PROJECT_NAME="${updates.COMPOSE_PROJECT_NAME}"`,
242
+ );
243
+ } else {
244
+ updatedLines.push(
245
+ `COMPOSE_PROJECT_NAME="${updates.COMPOSE_PROJECT_NAME}"`,
246
+ );
247
+ }
248
+ }
207
249
  envContent = updatedLines.join("\n");
208
250
 
209
251
  fs.writeFileSync(envPath, envContent, "utf-8");
package/core/index.d.ts CHANGED
@@ -1,137 +1,137 @@
1
- import { Router as ExpressRouter, Request, Response, NextFunction } from "express";
2
-
3
- declare global {
4
- namespace Express {
5
- interface Response {
6
- /**
7
- * Sends a standardized JSON success response.
8
- * @param data Data payload to return.
9
- * @param message Success message.
10
- * @param statusCode HTTP status code (default: 200).
11
- */
12
- success(data?: any, message?: string, statusCode?: number): Response;
13
-
14
- /**
15
- * Sends a standardized JSON error response.
16
- * @param message Error message.
17
- * @param statusCode HTTP status code (default: 400).
18
- * @param errors Array or object of detailed errors.
19
- */
20
- error(message?: string, statusCode?: number, errors?: any): Response;
21
-
22
- /**
23
- * Sends a standardized paginated JSON response.
24
- * @param data Array of records for current page.
25
- * @param pagination Object containing page, limit, and total count.
26
- * @param message Success message.
27
- */
28
- paginate(
29
- data?: any[],
30
- pagination?: { page?: number; limit?: number; total?: number },
31
- message?: string
32
- ): Response;
33
- }
34
- }
35
- }
36
-
37
- export class AppError extends Error {
38
- statusCode: number;
39
- errors: any;
40
- isOperational: boolean;
41
-
42
- constructor(message: string, statusCode?: number, errors?: any);
43
- }
44
-
45
- export class App {
46
- constructor(options?: {
47
- routes?: any[];
48
- cors?: any;
49
- middlewares?: any[];
50
- port?: number | string;
51
- host?: string;
52
- logger?: boolean;
53
- notFound?: boolean;
54
- json?: boolean;
55
- urlencoded?: boolean;
56
- static?: { path: string; options?: any };
57
- cookieParser?: boolean;
58
- rateLimit?: boolean | any;
59
- swagger?: boolean | any;
60
- compression?: boolean;
61
- });
62
-
63
- use(record: any): void;
64
- set(key: string, value: any): void;
65
- websocket(
66
- options?:
67
- | ((ws: any, req: any) => void)
68
- | { path?: string; auth?: boolean }
69
- ): WebSocketManager;
70
- run(): void;
71
-
72
- static helmet(options?: any): any;
73
- }
74
-
75
- export class WebSocketManager {
76
- constructor(server: any, options?: { path?: string; auth?: boolean });
77
- onConnection(handler: (ws: any, req: any) => void): void;
78
- join(room: string, ws: any): void;
79
- leave(room: string, ws: any): void;
80
- broadcast(data: any, room?: string | null): void;
81
- }
82
-
83
- export class Router {
84
- constructor(path?: string, options?: { seo?: boolean; languages?: string[] });
85
-
86
- use(...middleware: any[]): void;
87
- get(path: string | RegExp, ...callback: any[]): void;
88
- post(path: string | RegExp, ...callback: any[]): void;
89
- put(path: string | RegExp, ...callback: any[]): void;
90
- delete(path: string | RegExp, ...callback: any[]): void;
91
- patch(path: string | RegExp, ...callback: any[]): void;
92
- options(path: string | RegExp, ...callback: any[]): void;
93
- getRouter(): ExpressRouter;
94
- getPath(): string;
95
- }
96
-
97
- export class Validator {
98
- constructor(schema: Record<string, any>, lang?: string);
99
- middleware(): (req: Request, res: Response, next: NextFunction) => void;
100
- validate(data: Record<string, any>): { valid: boolean; errors: any[] };
101
- }
102
-
103
- export class Auth {
104
- static encrypt(payload: any, secret: string): string;
105
- static decrypt(data: string, secret: string): any;
106
- static generateToken(payload: any, secret?: string, expiresIn?: string | number): string;
107
- static verifyToken(token: string, secret?: string): any;
108
- static protect(options?: { redirect?: string | null; key?: string; cookieKey?: string }): (req: Request, res: Response, next: NextFunction) => Promise<any>;
109
- static login(res: Response, data: any, key?: string, options?: { expiresIn?: string | number; cookie?: any }): Promise<string>;
110
- static logout(res: Response, key?: string, options?: any, req?: Request): Promise<boolean>;
111
- }
112
-
113
- export class Cache {
114
- static middleware(seconds?: number): (req: Request, res: Response, next: NextFunction) => Promise<any>;
115
- static get(key: string): Promise<any | null>;
116
- static set(key: string, value: any, seconds?: number): Promise<boolean>;
117
- static delete(keys: string | string[]): Promise<boolean>;
118
- static del(keys: string | string[]): Promise<boolean>;
119
- static clear(): Promise<boolean>;
120
- }
121
-
122
- export function getRedisClient(): any;
123
-
124
- export class Database {
125
- constructor(connectionLimit?: number, queueLimit?: number, config?: any);
126
- init(retries?: number): Promise<boolean>;
127
- query(sql: string, params?: any[], options?: any): Promise<any>;
128
- paginate(
129
- sql: string,
130
- params?: any[],
131
- options?: { page?: number; limit?: number; cache?: number }
132
- ): Promise<{ data: any[]; total: number; page: number; limit: number; totalPages: number }>;
133
- transaction<T = any>(callback: (tx: { query: (sql: string, params?: any[], options?: any) => Promise<any> }) => Promise<T>): Promise<T>;
134
- executeTransaction<T = any>(callback: (tx: { query: (sql: string, params?: any[], options?: any) => Promise<any> }) => Promise<T>): Promise<T>;
135
- }
136
-
137
- export default App;
1
+ import { Router as ExpressRouter, Request, Response, NextFunction } from "express";
2
+
3
+ declare global {
4
+ namespace Express {
5
+ interface Response {
6
+ /**
7
+ * Sends a standardized JSON success response.
8
+ * @param data Data payload to return.
9
+ * @param message Success message.
10
+ * @param statusCode HTTP status code (default: 200).
11
+ */
12
+ success(data?: any, message?: string, statusCode?: number): Response;
13
+
14
+ /**
15
+ * Sends a standardized JSON error response.
16
+ * @param message Error message.
17
+ * @param statusCode HTTP status code (default: 400).
18
+ * @param errors Array or object of detailed errors.
19
+ */
20
+ error(message?: string, statusCode?: number, errors?: any): Response;
21
+
22
+ /**
23
+ * Sends a standardized paginated JSON response.
24
+ * @param data Array of records for current page.
25
+ * @param pagination Object containing page, limit, and total count.
26
+ * @param message Success message.
27
+ */
28
+ paginate(
29
+ data?: any[],
30
+ pagination?: { page?: number; limit?: number; total?: number },
31
+ message?: string
32
+ ): Response;
33
+ }
34
+ }
35
+ }
36
+
37
+ export class AppError extends Error {
38
+ statusCode: number;
39
+ errors: any;
40
+ isOperational: boolean;
41
+
42
+ constructor(message: string, statusCode?: number, errors?: any);
43
+ }
44
+
45
+ export class App {
46
+ constructor(options?: {
47
+ routes?: any[];
48
+ cors?: any;
49
+ middlewares?: any[];
50
+ port?: number | string;
51
+ host?: string;
52
+ logger?: boolean;
53
+ notFound?: boolean;
54
+ json?: boolean;
55
+ urlencoded?: boolean;
56
+ static?: { path: string; options?: any };
57
+ cookieParser?: boolean;
58
+ rateLimit?: boolean | any;
59
+ swagger?: boolean | any;
60
+ compression?: boolean;
61
+ });
62
+
63
+ use(record: any): void;
64
+ set(key: string, value: any): void;
65
+ websocket(
66
+ options?:
67
+ | ((ws: any, req: any) => void)
68
+ | { path?: string; auth?: boolean }
69
+ ): WebSocketManager;
70
+ run(): void;
71
+
72
+ static helmet(options?: any): any;
73
+ }
74
+
75
+ export class WebSocketManager {
76
+ constructor(server: any, options?: { path?: string; auth?: boolean });
77
+ onConnection(handler: (ws: any, req: any) => void): void;
78
+ join(room: string, ws: any): void;
79
+ leave(room: string, ws: any): void;
80
+ broadcast(data: any, room?: string | null): void;
81
+ }
82
+
83
+ export class Router {
84
+ constructor(path?: string, options?: { seo?: boolean; languages?: string[] });
85
+
86
+ use(...middleware: any[]): void;
87
+ get(path: string | RegExp, ...callback: any[]): void;
88
+ post(path: string | RegExp, ...callback: any[]): void;
89
+ put(path: string | RegExp, ...callback: any[]): void;
90
+ delete(path: string | RegExp, ...callback: any[]): void;
91
+ patch(path: string | RegExp, ...callback: any[]): void;
92
+ options(path: string | RegExp, ...callback: any[]): void;
93
+ getRouter(): ExpressRouter;
94
+ getPath(): string;
95
+ }
96
+
97
+ export class Validator {
98
+ constructor(schema: Record<string, any>, lang?: string);
99
+ middleware(): (req: Request, res: Response, next: NextFunction) => void;
100
+ validate(data: Record<string, any>): { valid: boolean; errors: any[] };
101
+ }
102
+
103
+ export class Auth {
104
+ static encrypt(payload: any, secret: string): string;
105
+ static decrypt(data: string, secret: string): any;
106
+ static generateToken(payload: any, secret?: string, expiresIn?: string | number): string;
107
+ static verifyToken(token: string, secret?: string): any;
108
+ static protect(options?: { redirect?: string | null; key?: string; cookieKey?: string }): (req: Request, res: Response, next: NextFunction) => Promise<any>;
109
+ static login(res: Response, data: any, key?: string, options?: { expiresIn?: string | number; cookie?: any }): Promise<string>;
110
+ static logout(res: Response, key?: string, options?: any, req?: Request): Promise<boolean>;
111
+ }
112
+
113
+ export class Cache {
114
+ static middleware(seconds?: number): (req: Request, res: Response, next: NextFunction) => Promise<any>;
115
+ static get(key: string): Promise<any | null>;
116
+ static set(key: string, value: any, seconds?: number): Promise<boolean>;
117
+ static delete(keys: string | string[]): Promise<boolean>;
118
+ static del(keys: string | string[]): Promise<boolean>;
119
+ static clear(): Promise<boolean>;
120
+ }
121
+
122
+ export function getRedisClient(): any;
123
+
124
+ export class Database {
125
+ constructor(connectionLimit?: number, queueLimit?: number, config?: any);
126
+ init(retries?: number): Promise<boolean>;
127
+ query(sql: string, params?: any[], options?: any): Promise<any>;
128
+ paginate(
129
+ sql: string,
130
+ params?: any[],
131
+ options?: { page?: number; limit?: number; cache?: number }
132
+ ): Promise<{ data: any[]; total: number; page: number; limit: number; totalPages: number }>;
133
+ transaction<T = any>(callback: (tx: { query: (sql: string, params?: any[], options?: any) => Promise<any> }) => Promise<T>): Promise<T>;
134
+ executeTransaction<T = any>(callback: (tx: { query: (sql: string, params?: any[], options?: any) => Promise<any> }) => Promise<T>): Promise<T>;
135
+ }
136
+
137
+ export default App;