@seip/blue-bird 1.1.4 → 1.1.5

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/core/cli/nginx.js CHANGED
@@ -1,138 +1,138 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import chalk from "chalk";
6
-
7
- /**
8
- * Parses .env file to extract APP_URL and PORT.
9
- * @returns {{ domain: string, port: number, appUrl: string }}
10
- */
11
- function getEnvConfig() {
12
- const env = { ...process.env };
13
- const envPath = path.resolve(process.cwd(), ".env");
14
- if (fs.existsSync(envPath)) {
15
- const content = fs.readFileSync(envPath, "utf-8");
16
- content.split(/\r?\n/).forEach((line) => {
17
- line = line.trim();
18
- if (line && !line.startsWith("#") && line.includes("=")) {
19
- const idx = line.indexOf("=");
20
- const key = line.substring(0, idx).trim();
21
- const value = line.substring(idx + 1).trim().replace(/^['"]|['"]$/g, "");
22
- env[key] = value;
23
- }
24
- });
25
- }
26
-
27
- let domain = "";
28
- if (env.APP_URL) {
29
- try {
30
- const parsedUrl = new URL(env.APP_URL.includes("://") ? env.APP_URL : `http://${env.APP_URL}`);
31
- domain = parsedUrl.hostname;
32
- } catch {
33
- domain = env.APP_URL.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/:\d+$/, "").trim();
34
- }
35
- }
36
-
37
- const port = parseInt(env.PORT || "3000", 10);
38
- return { domain, port, appUrl: env.APP_URL || "" };
39
- }
40
-
41
- /**
42
- * Generates Host Nginx reverse proxy configuration snippet.
43
- */
44
- function generateNginxConfig() {
45
- const rawArgs = process.argv.slice(2);
46
- const filteredArgs = rawArgs.filter(
47
- (a) => a !== "nginx:conf" && a !== "nginx:host" && a !== "nginx" && !a.endsWith("nginx.js") && !a.endsWith("init.js")
48
- );
49
-
50
- const envConfig = getEnvConfig();
51
-
52
- let domainArg = filteredArgs.find((a) => !a.startsWith("-") && isNaN(Number(a)));
53
- let portArg = filteredArgs.find((a) => !isNaN(Number(a)));
54
-
55
- let domain = domainArg || envConfig.domain;
56
- let port = portArg ? parseInt(portArg, 10) : envConfig.port;
57
-
58
- if (!domain) {
59
- domain = "example.com";
60
- console.log(chalk.yellow("[INFO] No domain provided and APP_URL is not configured in .env. Defaulting to 'example.com'."));
61
- } else if (!domainArg && envConfig.domain) {
62
- console.log(chalk.cyan(`[INFO] Using domain '${domain}' and port ${port} resolved from .env (APP_URL / PORT).`));
63
- console.log(chalk.gray(` (You can override via: npx blue-bird nginx:conf <domain> [port])\n`));
64
- }
65
-
66
- // Clean domain name (strip protocol, path, port)
67
- domain = domain.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/:\d+$/, "").trim();
68
-
69
- const nginxSnippet = `# =============================================================
70
- # Blue Bird Host Nginx Reverse Proxy Configuration
71
- # File: /etc/nginx/sites-available/${domain}
72
- # =============================================================
73
-
74
- server {
75
- listen 80;
76
- listen [::]:80;
77
- server_name ${domain};
78
-
79
- # Maximum request body size for file uploads
80
- client_max_body_size 50M;
81
-
82
- # Security: Block hidden files and scan attempts
83
- location ~* /\\.(env|git) {
84
- deny all;
85
- return 404;
86
- }
87
-
88
- # Reverse proxy to Blue Bird container stack
89
- location / {
90
- proxy_pass http://127.0.0.1:${port};
91
- proxy_http_version 1.1;
92
-
93
- # WebSocket upgrade support
94
- proxy_set_header Upgrade $http_upgrade;
95
- proxy_set_header Connection "upgrade";
96
-
97
- # Standard proxy headers
98
- proxy_set_header Host $host;
99
- proxy_set_header X-Real-IP $remote_addr;
100
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
101
- proxy_set_header X-Forwarded-Proto $scheme;
102
- proxy_set_header X-Forwarded-Host $host;
103
- proxy_set_header X-Forwarded-Port $server_port;
104
-
105
- # Proxy timeouts
106
- proxy_connect_timeout 60s;
107
- proxy_send_timeout 60s;
108
- proxy_read_timeout 60s;
109
- }
110
- }`;
111
-
112
- console.log(chalk.bold.cyan("============================================================="));
113
- console.log(chalk.bold.cyan(` Host Nginx Configuration & Let's Encrypt Setup for: ${domain}`));
114
- console.log(chalk.bold.cyan("============================================================="));
115
- console.log("");
116
- console.log(chalk.yellow("1. Create the site configuration file on your VPS:"));
117
- console.log(chalk.green(` sudo nano /etc/nginx/sites-available/${domain}`));
118
- console.log("");
119
- console.log(chalk.yellow("2. Paste the following configuration block:"));
120
- console.log("");
121
- console.log(chalk.white(nginxSnippet));
122
- console.log("");
123
- console.log(chalk.yellow("3. Enable the site configuration:"));
124
- console.log(chalk.green(` sudo ln -s /etc/nginx/sites-available/${domain} /etc/nginx/sites-enabled/`));
125
- console.log("");
126
- console.log(chalk.yellow("4. Test Nginx syntax:"));
127
- console.log(chalk.green(" sudo nginx -t"));
128
- console.log("");
129
- console.log(chalk.yellow("5. Reload Nginx to apply changes:"));
130
- console.log(chalk.green(" sudo systemctl reload nginx"));
131
- console.log("");
132
- console.log(chalk.yellow("6. Provision free SSL certificate with Certbot (Let's Encrypt):"));
133
- console.log(chalk.green(` sudo certbot --nginx -d ${domain}`));
134
- console.log("");
135
- console.log(chalk.bold.cyan("============================================================="));
136
- }
137
-
138
- generateNginxConfig();
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import chalk from "chalk";
6
+
7
+ /**
8
+ * Parses .env file to extract APP_URL and PORT.
9
+ * @returns {{ domain: string, port: number, appUrl: string }}
10
+ */
11
+ function getEnvConfig() {
12
+ const env = { ...process.env };
13
+ const envPath = path.resolve(process.cwd(), ".env");
14
+ if (fs.existsSync(envPath)) {
15
+ const content = fs.readFileSync(envPath, "utf-8");
16
+ content.split(/\r?\n/).forEach((line) => {
17
+ line = line.trim();
18
+ if (line && !line.startsWith("#") && line.includes("=")) {
19
+ const idx = line.indexOf("=");
20
+ const key = line.substring(0, idx).trim();
21
+ const value = line.substring(idx + 1).trim().replace(/^['"]|['"]$/g, "");
22
+ env[key] = value;
23
+ }
24
+ });
25
+ }
26
+
27
+ let domain = "";
28
+ if (env.APP_URL) {
29
+ try {
30
+ const parsedUrl = new URL(env.APP_URL.includes("://") ? env.APP_URL : `http://${env.APP_URL}`);
31
+ domain = parsedUrl.hostname;
32
+ } catch {
33
+ domain = env.APP_URL.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/:\d+$/, "").trim();
34
+ }
35
+ }
36
+
37
+ const port = parseInt(env.PORT || "3000", 10);
38
+ return { domain, port, appUrl: env.APP_URL || "" };
39
+ }
40
+
41
+ /**
42
+ * Generates Host Nginx reverse proxy configuration snippet.
43
+ */
44
+ function generateNginxConfig() {
45
+ const rawArgs = process.argv.slice(2);
46
+ const filteredArgs = rawArgs.filter(
47
+ (a) => a !== "nginx:conf" && a !== "nginx:host" && a !== "nginx" && !a.endsWith("nginx.js") && !a.endsWith("init.js")
48
+ );
49
+
50
+ const envConfig = getEnvConfig();
51
+
52
+ let domainArg = filteredArgs.find((a) => !a.startsWith("-") && isNaN(Number(a)));
53
+ let portArg = filteredArgs.find((a) => !isNaN(Number(a)));
54
+
55
+ let domain = domainArg || envConfig.domain;
56
+ let port = portArg ? parseInt(portArg, 10) : envConfig.port;
57
+
58
+ if (!domain) {
59
+ domain = "example.com";
60
+ console.log(chalk.yellow("[INFO] No domain provided and APP_URL is not configured in .env. Defaulting to 'example.com'."));
61
+ } else if (!domainArg && envConfig.domain) {
62
+ console.log(chalk.cyan(`[INFO] Using domain '${domain}' and port ${port} resolved from .env (APP_URL / PORT).`));
63
+ console.log(chalk.gray(` (You can override via: npx blue-bird nginx:conf <domain> [port])\n`));
64
+ }
65
+
66
+ // Clean domain name (strip protocol, path, port)
67
+ domain = domain.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/:\d+$/, "").trim();
68
+
69
+ const nginxSnippet = `# =============================================================
70
+ # Blue Bird Host Nginx Reverse Proxy Configuration
71
+ # File: /etc/nginx/sites-available/${domain}
72
+ # =============================================================
73
+
74
+ server {
75
+ listen 80;
76
+ listen [::]:80;
77
+ server_name ${domain};
78
+
79
+ # Maximum request body size for file uploads
80
+ client_max_body_size 50M;
81
+
82
+ # Security: Block hidden files and scan attempts
83
+ location ~* /\\.(env|git) {
84
+ deny all;
85
+ return 404;
86
+ }
87
+
88
+ # Reverse proxy to Blue Bird container stack
89
+ location / {
90
+ proxy_pass http://127.0.0.1:${port};
91
+ proxy_http_version 1.1;
92
+
93
+ # WebSocket upgrade support
94
+ proxy_set_header Upgrade $http_upgrade;
95
+ proxy_set_header Connection "upgrade";
96
+
97
+ # Standard proxy headers
98
+ proxy_set_header Host $host;
99
+ proxy_set_header X-Real-IP $remote_addr;
100
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
101
+ proxy_set_header X-Forwarded-Proto $scheme;
102
+ proxy_set_header X-Forwarded-Host $host;
103
+ proxy_set_header X-Forwarded-Port $server_port;
104
+
105
+ # Proxy timeouts
106
+ proxy_connect_timeout 60s;
107
+ proxy_send_timeout 60s;
108
+ proxy_read_timeout 60s;
109
+ }
110
+ }`;
111
+
112
+ console.log(chalk.bold.cyan("============================================================="));
113
+ console.log(chalk.bold.cyan(` Host Nginx Configuration & Let's Encrypt Setup for: ${domain}`));
114
+ console.log(chalk.bold.cyan("============================================================="));
115
+ console.log("");
116
+ console.log(chalk.yellow("1. Create the site configuration file on your VPS:"));
117
+ console.log(chalk.green(` sudo nano /etc/nginx/sites-available/${domain}`));
118
+ console.log("");
119
+ console.log(chalk.yellow("2. Paste the following configuration block:"));
120
+ console.log("");
121
+ console.log(chalk.white(nginxSnippet));
122
+ console.log("");
123
+ console.log(chalk.yellow("3. Enable the site configuration:"));
124
+ console.log(chalk.green(` sudo ln -s /etc/nginx/sites-available/${domain} /etc/nginx/sites-enabled/`));
125
+ console.log("");
126
+ console.log(chalk.yellow("4. Test Nginx syntax:"));
127
+ console.log(chalk.green(" sudo nginx -t"));
128
+ console.log("");
129
+ console.log(chalk.yellow("5. Reload Nginx to apply changes:"));
130
+ console.log(chalk.green(" sudo systemctl reload nginx"));
131
+ console.log("");
132
+ console.log(chalk.yellow("6. Provision free SSL certificate with Certbot (Let's Encrypt):"));
133
+ console.log(chalk.green(` sudo certbot --nginx -d ${domain}`));
134
+ console.log("");
135
+ console.log(chalk.bold.cyan("============================================================="));
136
+ }
137
+
138
+ generateNginxConfig();
package/core/cli/route.js CHANGED
@@ -1,144 +1,144 @@
1
- #!/usr/bin/env node
2
-
3
- import path from "node:path";
4
- import fs from "node:fs";
5
- import chalk from "chalk";
6
-
7
- class RouteCLI {
8
- /**
9
- * Creates a new RESTful route file with Validation, Cache, and optional Auth.
10
- */
11
- create() {
12
- const rawArgs = process.argv.slice(2);
13
- // Filter out command name if invoked as 'route' or 'make:route'
14
- const args = rawArgs.filter(
15
- (a) => a !== "route" && a !== "make:route" && !a.endsWith("route.js")
16
- );
17
-
18
- let routeName = args.find((a) => !a.startsWith("-"));
19
- const withAuth = args.some((a) => a === "--auth" || a === "-a");
20
-
21
- if (!routeName) {
22
- console.log(chalk.red("[ERROR] Missing route name."));
23
- console.log("");
24
- console.log("Usage:");
25
- console.log(" npx blue-bird make:route <name> [--auth]");
26
- console.log("");
27
- console.log("Examples:");
28
- console.log(" npx blue-bird make:route products");
29
- console.log(" npx blue-bird make:route articles --auth");
30
- process.exit(1);
31
- }
32
-
33
- // Normalize casing
34
- routeName = routeName.toLowerCase().replace(/[^a-z0-9_-]/g, "");
35
- const singularName = routeName.endsWith("s") ? routeName.slice(0, -1) : routeName;
36
- const pascalName = routeName.charAt(0).toUpperCase() + routeName.slice(1);
37
- const routerVarName = `router${pascalName}`;
38
- const basePath = `/${routeName}`;
39
-
40
- const routesFolder = path.resolve(process.cwd(), "backend/routes");
41
- if (!fs.existsSync(routesFolder)) {
42
- fs.mkdirSync(routesFolder, { recursive: true });
43
- }
44
-
45
- const filePath = path.join(routesFolder, `${routeName}.js`);
46
- if (fs.existsSync(filePath)) {
47
- console.log(chalk.yellow(`[WARN] Route file '${routeName}.js' already exists at backend/routes/${routeName}.js.`));
48
- return;
49
- }
50
-
51
- const authImport = withAuth
52
- ? `import Auth from "@seip/blue-bird/core/auth.js";\n`
53
- : "";
54
-
55
- const authProtect = withAuth ? `Auth.protect(), ` : "";
56
-
57
- const content = `import Router from "@seip/blue-bird/core/router.js";
58
- import Validator from "@seip/blue-bird/core/validate.js";
59
- import Cache from "@seip/blue-bird/core/cache.js";
60
- ${authImport}
61
- const ${routerVarName} = new Router("${basePath}");
62
-
63
- // Validation schema for incoming requests
64
- const ${singularName}Schema = {
65
- name: { required: true, min: 2, max: 255 },
66
- description: { required: false },
67
- price: { required: false }
68
- };
69
-
70
- const validate${pascalName} = new Validator(${singularName}Schema, "en");
71
-
72
- /**
73
- * GET ${basePath}
74
- * List all items with in-memory / Redis route caching (60 seconds)
75
- */
76
- ${routerVarName}.get("/", Cache.middleware(60), (req, res) => {
77
- res.ok({ ${routeName}: [] }, "${pascalName} list retrieved successfully");
78
- });
79
-
80
- /**
81
- * GET ${basePath}/:id
82
- * Retrieve a single item by ID
83
- */
84
- ${routerVarName}.get("/:id", Cache.middleware(60), (req, res) => {
85
- const { id } = req.params;
86
- res.ok({ ${singularName}: { id } }, "${pascalName} retrieved successfully");
87
- });
88
-
89
- /**
90
- * POST ${basePath}
91
- * Create a new item (with validation and automatic cache invalidation)
92
- */
93
- ${routerVarName}.post("/", ${authProtect}validate${pascalName}.middleware(), async (req, res) => {
94
- const data = req.body;
95
-
96
- // Invalidate cached route list
97
- await Cache.delete("${basePath}");
98
-
99
- res.created({ ${singularName}: data }, "${pascalName} created successfully");
100
- });
101
-
102
- /**
103
- * PUT ${basePath}/:id
104
- * Update an existing item
105
- */
106
- ${routerVarName}.put("/:id", ${authProtect}validate${pascalName}.middleware(), async (req, res) => {
107
- const { id } = req.params;
108
- const data = req.body;
109
-
110
- // Invalidate cached item and list
111
- await Cache.delete("${basePath}");
112
- await Cache.delete(\`${basePath}/\${id}\`);
113
-
114
- res.ok({ ${singularName}: { id, ...data } }, "${pascalName} updated successfully");
115
- });
116
-
117
- /**
118
- * DELETE ${basePath}/:id
119
- * Delete an existing item
120
- */
121
- ${routerVarName}.delete("/:id", ${authProtect}async (req, res) => {
122
- const { id } = req.params;
123
-
124
- // Invalidate cache
125
- await Cache.delete("${basePath}");
126
- await Cache.delete(\`${basePath}/\${id}\`);
127
-
128
- res.ok({ id }, "${pascalName} deleted successfully");
129
- });
130
-
131
- export default ${routerVarName};
132
- `;
133
-
134
- fs.writeFileSync(filePath, content, "utf-8");
135
- console.log(chalk.green(`[OK] Route '${routeName}' created successfully at backend/routes/${routeName}.js`));
136
- console.log("");
137
- console.log(chalk.cyan("To register this route, import it in backend/index.js:"));
138
- console.log(chalk.gray(` import ${routerVarName} from "./routes/${routeName}.js";`));
139
- console.log(chalk.gray(` // Pass ${routerVarName} into App({ routes: [...] })`));
140
- }
141
- }
142
-
143
- const routeCLI = new RouteCLI();
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+ import fs from "node:fs";
5
+ import chalk from "chalk";
6
+
7
+ class RouteCLI {
8
+ /**
9
+ * Creates a new RESTful route file with Validation, Cache, and optional Auth.
10
+ */
11
+ create() {
12
+ const rawArgs = process.argv.slice(2);
13
+ // Filter out command name if invoked as 'route' or 'make:route'
14
+ const args = rawArgs.filter(
15
+ (a) => a !== "route" && a !== "make:route" && !a.endsWith("route.js")
16
+ );
17
+
18
+ let routeName = args.find((a) => !a.startsWith("-"));
19
+ const withAuth = args.some((a) => a === "--auth" || a === "-a");
20
+
21
+ if (!routeName) {
22
+ console.log(chalk.red("[ERROR] Missing route name."));
23
+ console.log("");
24
+ console.log("Usage:");
25
+ console.log(" npx blue-bird make:route <name> [--auth]");
26
+ console.log("");
27
+ console.log("Examples:");
28
+ console.log(" npx blue-bird make:route products");
29
+ console.log(" npx blue-bird make:route articles --auth");
30
+ process.exit(1);
31
+ }
32
+
33
+ // Normalize casing
34
+ routeName = routeName.toLowerCase().replace(/[^a-z0-9_-]/g, "");
35
+ const singularName = routeName.endsWith("s") ? routeName.slice(0, -1) : routeName;
36
+ const pascalName = routeName.charAt(0).toUpperCase() + routeName.slice(1);
37
+ const routerVarName = `router${pascalName}`;
38
+ const basePath = `/${routeName}`;
39
+
40
+ const routesFolder = path.resolve(process.cwd(), "backend/routes");
41
+ if (!fs.existsSync(routesFolder)) {
42
+ fs.mkdirSync(routesFolder, { recursive: true });
43
+ }
44
+
45
+ const filePath = path.join(routesFolder, `${routeName}.js`);
46
+ if (fs.existsSync(filePath)) {
47
+ console.log(chalk.yellow(`[WARN] Route file '${routeName}.js' already exists at backend/routes/${routeName}.js.`));
48
+ return;
49
+ }
50
+
51
+ const authImport = withAuth
52
+ ? `import Auth from "@seip/blue-bird/core/auth.js";\n`
53
+ : "";
54
+
55
+ const authProtect = withAuth ? `Auth.protect(), ` : "";
56
+
57
+ const content = `import Router from "@seip/blue-bird/core/router.js";
58
+ import Validator from "@seip/blue-bird/core/validate.js";
59
+ import Cache from "@seip/blue-bird/core/cache.js";
60
+ ${authImport}
61
+ const ${routerVarName} = new Router("${basePath}");
62
+
63
+ // Validation schema for incoming requests
64
+ const ${singularName}Schema = {
65
+ name: { required: true, min: 2, max: 255 },
66
+ description: { required: false },
67
+ price: { required: false }
68
+ };
69
+
70
+ const validate${pascalName} = new Validator(${singularName}Schema, "en");
71
+
72
+ /**
73
+ * GET ${basePath}
74
+ * List all items with in-memory / Redis route caching (60 seconds)
75
+ */
76
+ ${routerVarName}.get("/", Cache.middleware(60), (req, res) => {
77
+ res.ok({ ${routeName}: [] }, "${pascalName} list retrieved successfully");
78
+ });
79
+
80
+ /**
81
+ * GET ${basePath}/:id
82
+ * Retrieve a single item by ID
83
+ */
84
+ ${routerVarName}.get("/:id", Cache.middleware(60), (req, res) => {
85
+ const { id } = req.params;
86
+ res.ok({ ${singularName}: { id } }, "${pascalName} retrieved successfully");
87
+ });
88
+
89
+ /**
90
+ * POST ${basePath}
91
+ * Create a new item (with validation and automatic cache invalidation)
92
+ */
93
+ ${routerVarName}.post("/", ${authProtect}validate${pascalName}.middleware(), async (req, res) => {
94
+ const data = req.body;
95
+
96
+ // Invalidate cached route list
97
+ await Cache.delete("${basePath}");
98
+
99
+ res.created({ ${singularName}: data }, "${pascalName} created successfully");
100
+ });
101
+
102
+ /**
103
+ * PUT ${basePath}/:id
104
+ * Update an existing item
105
+ */
106
+ ${routerVarName}.put("/:id", ${authProtect}validate${pascalName}.middleware(), async (req, res) => {
107
+ const { id } = req.params;
108
+ const data = req.body;
109
+
110
+ // Invalidate cached item and list
111
+ await Cache.delete("${basePath}");
112
+ await Cache.delete(\`${basePath}/\${id}\`);
113
+
114
+ res.ok({ ${singularName}: { id, ...data } }, "${pascalName} updated successfully");
115
+ });
116
+
117
+ /**
118
+ * DELETE ${basePath}/:id
119
+ * Delete an existing item
120
+ */
121
+ ${routerVarName}.delete("/:id", ${authProtect}async (req, res) => {
122
+ const { id } = req.params;
123
+
124
+ // Invalidate cache
125
+ await Cache.delete("${basePath}");
126
+ await Cache.delete(\`${basePath}/\${id}\`);
127
+
128
+ res.ok({ id }, "${pascalName} deleted successfully");
129
+ });
130
+
131
+ export default ${routerVarName};
132
+ `;
133
+
134
+ fs.writeFileSync(filePath, content, "utf-8");
135
+ console.log(chalk.green(`[OK] Route '${routeName}' created successfully at backend/routes/${routeName}.js`));
136
+ console.log("");
137
+ console.log(chalk.cyan("To register this route, import it in backend/index.js:"));
138
+ console.log(chalk.gray(` import ${routerVarName} from "./routes/${routeName}.js";`));
139
+ console.log(chalk.gray(` // Pass ${routerVarName} into App({ routes: [...] })`));
140
+ }
141
+ }
142
+
143
+ const routeCLI = new RouteCLI();
144
144
  routeCLI.create();