@seip/blue-bird 0.2.6 → 0.3.1

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
@@ -8,6 +8,6 @@ TITLE="Blue Bird"
8
8
  VERSION="1.0.0"
9
9
  LANGMETA="en"
10
10
  HOST="http://localhost"
11
- PORT=3001
11
+ PORT=3000
12
12
  STATIC_PATH="frontend/public"
13
13
  JWT_SECRET="JWT_SECRET"
package/backend/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import App from "@seip/blue-bird/core/app.js";
2
- import routerUsers from "./routes/app.js";
2
+ import routerApiExample from "./routes/api.js";
3
+ import routerFrontendExample from "./routes/frontend.js";
3
4
 
4
5
  const app = new App({
5
- routes: [routerUsers],
6
+ routes: [routerApiExample, routerFrontendExample],
6
7
  cors: [],
7
- middlewares: [],
8
+ middlewares: [],
8
9
  host: "http://localhost"
9
10
  })
10
11
 
@@ -0,0 +1,34 @@
1
+ import Router from "@seip/blue-bird/core/router.js"
2
+ import Validator from "@seip/blue-bird/core/validate.js"
3
+
4
+
5
+ const routerApiExample = new Router("/")
6
+
7
+ routerApiExample.get("/users", (req, res) => {
8
+ const users = [
9
+ {
10
+ name: "John Doe",
11
+ email: "john.doe@example.com",
12
+ },
13
+ {
14
+ name: "Jane Doe2",
15
+ email: "jane.doe2@example.com",
16
+ },
17
+ ]
18
+ res.json(users)
19
+ })
20
+
21
+ const loginSchema = {
22
+ email: { required: true, email: true },
23
+ password: { required: true, min: 6 }
24
+ };
25
+
26
+ const loginValidator = new Validator(loginSchema, 'es');
27
+
28
+ routerApiExample.post('/login', loginValidator.middleware(), (req, res) => {
29
+ res.json({ message: 'Login successful' });
30
+ });
31
+
32
+
33
+
34
+ export default routerApiExample
@@ -0,0 +1,51 @@
1
+ import Router from "@seip/blue-bird/core/router.js"
2
+ import Template from "@seip/blue-bird/core/template.js"
3
+
4
+ const routerFrontendExample = new Router();
5
+
6
+ const routesFrontend = [
7
+ {
8
+ path: "/",
9
+ component: "Home",
10
+ meta: { titleMeta: "Home - Blue Bird", descriptionMeta: "Welcome to Blue Bird" },
11
+ props: { id: 1, name: "Name" }
12
+
13
+ },
14
+ {
15
+ path: "/about",
16
+ component: "About",
17
+ meta: { titleMeta: "About - Blue Bird", descriptionMeta: "About blue bird" },
18
+ props: { id: 2, name: "Name 2" }
19
+ },
20
+
21
+ ];
22
+ routesFrontend.forEach(route_ => {
23
+ routerFrontendExample.get(route_.path, (req, res) => {
24
+ const dynamicProps = {
25
+ props: {
26
+ params: req.params,
27
+ query: req.query,
28
+ ...route_.props ?? {}
29
+ }
30
+ };
31
+
32
+ return Template.renderReact(res, route_.component, dynamicProps, {
33
+ metaTags: route_.meta,
34
+ scriptsInBody: [{ src: "https://cdn.tailwindcss.com" }]
35
+ });
36
+ });
37
+ });
38
+
39
+
40
+ routerFrontendExample.get("*", (req, res) => {
41
+ const response = Template.renderReact(res, "App", { title: "404 - not found" },
42
+ {
43
+ scriptsInBody: [
44
+ { "src": "https://cdn.tailwindcss.com" }
45
+ ]
46
+ }
47
+ );
48
+ return response;
49
+ })
50
+
51
+ export default routerFrontendExample;
package/core/app.js CHANGED
@@ -9,7 +9,7 @@ import helmet from "helmet"
9
9
  import Config from "./config.js"
10
10
  import Logger from "./logger.js"
11
11
  import Debug from "./debug.js"
12
- import Swagger from "./swagger.js"
12
+
13
13
 
14
14
  const __dirname = Config.dirname()
15
15
  const props = Config.props()
@@ -35,7 +35,7 @@ class App {
35
35
  * @param {boolean|Object} [options.rateLimit=false] - Enable global rate limiting.
36
36
  * @param {boolean|Object} [options.helmet=true] - Enable Helmet security headers.
37
37
  * @param {boolean} [options.xssClean=true] - Enable XSS body sanitization.
38
- * @param {boolean|Object} [options.swagger=true] - Enable swagger
38
+ * @param {boolean|Object} [options.swagger=false] - Enable swagger
39
39
  * @example
40
40
  * const app = new App({
41
41
  * routes: [],
@@ -86,7 +86,7 @@ class App {
86
86
  rateLimit: false,
87
87
  helmet: false,
88
88
  xssClean: true,
89
- swagger: true
89
+ swagger: false
90
90
 
91
91
  }) {
92
92
  this.app = express()
@@ -104,7 +104,7 @@ class App {
104
104
  this.rateLimit = options.rateLimit ?? false
105
105
  this.helmet = options.helmet ?? true
106
106
  this.xssClean = options.xssClean ?? true
107
- this.swagger = options.swagger ?? true
107
+ this.swagger = options.swagger ?? false
108
108
  this.dispatch()
109
109
 
110
110
  }
@@ -207,7 +207,7 @@ class App {
207
207
  this.errorHandler();
208
208
 
209
209
  if (this.swagger) {
210
-
210
+ const Swagger = import("./swagger.js")
211
211
  const defaultSwaggerOptions = {
212
212
  info: {
213
213
  title: "Blue Bird API",
package/core/cli/init.js CHANGED
@@ -68,7 +68,8 @@ class ProjectInit {
68
68
  "react": "blue-bird react",
69
69
  "init": "blue-bird",
70
70
  "route": "blue-bird route",
71
- "component": "blue-bird component"
71
+ "component": "blue-bird component",
72
+ "swagger-install": "blue-bird swagger-install"
72
73
  };
73
74
 
74
75
  let updated = false;
@@ -111,6 +112,7 @@ const command = args[0];
111
112
  if (command === "react") import("./react.js");
112
113
  else if (command === "route") import("./route.js")
113
114
  else if (command === "component") import("./component.js")
115
+ else if (command === "swagger-install") import("./swagger.js")
114
116
  else initializer.run();
115
117
 
116
118
 
@@ -0,0 +1,40 @@
1
+ import { execSync } from "node:child_process";
2
+
3
+ class SwaggerCli {
4
+ install() {
5
+ const dependencies = this.checkDependencies();
6
+ if (dependencies.missingDependencies.length > 0) {
7
+ console.log("Installing dependencies...");
8
+ console.log(`Installing swagger-jsdoc...`);
9
+ execSync(`npm install swagger-jsdoc@6.2.8`, { stdio: "inherit" });
10
+ console.log(`Installing swagger-ui-express...`);
11
+ execSync(`npm install swagger-ui-express@5.0.1`, { stdio: "inherit" });
12
+ }
13
+ }
14
+ checkDependencies() {
15
+ const dependencies = [
16
+ "swagger-jsdoc",
17
+ "swagger-ui-express"
18
+ ];
19
+ const missingDependencies = [];
20
+ dependencies.forEach(dependency => {
21
+ if (!this.checkDependency(dependency)) {
22
+ missingDependencies.push(dependency);
23
+ }
24
+ });
25
+ return {
26
+ missingDependencies
27
+ };
28
+ }
29
+ checkDependency(dependency) {
30
+ try {
31
+ require.resolve(dependency);
32
+ return true;
33
+ } catch (error) {
34
+ return false;
35
+ }
36
+ }
37
+ }
38
+
39
+ const swaggerExecutor = new SwaggerCli();
40
+ swaggerExecutor.install();
package/core/template.js CHANGED
@@ -127,7 +127,7 @@ class Template {
127
127
  */
128
128
  static renderReact(res, component = "App", componentProps = {}, options = {}) {
129
129
  try {
130
- const {
130
+ let {
131
131
  langHtml = options.langHtml || props.langMeta || "en",
132
132
  classBody = "body",
133
133
  head = [],
@@ -135,20 +135,25 @@ class Template {
135
135
  scriptsInHead = [],
136
136
  scriptsInBody = [],
137
137
  cache = true,
138
- metaTags = {
139
- titleMeta: options.metaTags?.titleMeta || props.titleMeta,
140
- descriptionMeta: options.metaTags?.descriptionMeta || props.descriptionMeta,
141
- keywordsMeta: options.metaTags?.keywordsMeta || props.keywordsMeta,
142
- authorMeta: options.metaTags?.authorMeta || props.authorMeta,
143
- langMeta: options.metaTags?.langMeta || props.langMeta,
144
- },
138
+ metaTags
145
139
  } = options;
140
+ const metaTagsDefault = {
141
+ titleMeta: props.titleMeta,
142
+ descriptionMeta: props.descriptionMeta,
143
+ keywordsMeta: props.keywordsMeta,
144
+ authorMeta: props.authorMeta,
145
+ langMeta: props.langMeta,
146
+ }
147
+ metaTags = {
148
+ ...metaTagsDefault,
149
+ ...metaTags
150
+ }
146
151
 
147
152
  res.type("text/html");
148
153
  res.status(200);
149
-
150
- if (cache && CACHE_TEMPLATE[component]) {
151
- return res.send(CACHE_TEMPLATE[component]);
154
+ const cacheKey = `${component}_${metaTags.titleMeta}`;
155
+ if (cache && CACHE_TEMPLATE[cacheKey]) {
156
+ return res.send(CACHE_TEMPLATE[cacheKey]);
152
157
  }
153
158
 
154
159
  const title = this.escapeHtml(metaTags.titleMeta || "");
@@ -190,7 +195,7 @@ class Template {
190
195
  .replace(/__SCRIPTS_BODY__/g, scriptsBodyTags);
191
196
 
192
197
  html = this.minifyHtml(html);
193
- CACHE_TEMPLATE[component] = html;
198
+ CACHE_TEMPLATE[cacheKey] = html;
194
199
  return res.send(html);
195
200
 
196
201
  } catch (error) {
@@ -280,4 +285,4 @@ window.__vite_plugin_react_preamble_installed__ = true;
280
285
  }
281
286
  }
282
287
 
283
- export default Template;
288
+ export default Template;
@@ -1,5 +1,6 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="__LANG__">
3
+
3
4
  <head>
4
5
  <meta charset="UTF-8">
5
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -11,10 +12,12 @@
11
12
  __HEAD_OPTIONS__
12
13
  __LINK_STYLES__
13
14
  __SCRIPTS_HEAD__
15
+ __VITE_ASSETS__
14
16
  </head>
17
+
15
18
  <body class="__CLASS_BODY__">
16
19
  <div id="root" data-react-component="__COMPONENT__" data-props='__PROPS__'></div>
17
- __VITE_ASSETS__
18
20
  __SCRIPTS_BODY__
19
21
  </body>
22
+
20
23
  </html>
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
3
+ import Home from './pages/Home';
4
+ import About from './pages/About';
5
+
6
+ export default function App(_props) {
7
+ const {
8
+ component,
9
+ props
10
+ } = _props;
11
+
12
+ console.log(`Check props and component `)
13
+ console.log(`Component: ${component}`)
14
+ console.log(props)
15
+
16
+ return (
17
+ <Router>
18
+ <div
19
+ className="bg-white text-gray-900"
20
+ >
21
+ <nav
22
+ className='bg-white text-gray-900 border border-gray-200 px-4 py-4 flex justify-between items-center gap-4 sticky top-0 z-10'
23
+ >
24
+ <div className='font-bold text-xl text-blue-600'>
25
+ Blue Bird
26
+ </div>
27
+ <div className='flex justify-between items-center gap-4'>
28
+ <Link to="/" className='text-gray-500 hover:text-gray-900'>Home</Link>
29
+ <Link to="/about" className='text-gray-500 hover:text-gray-900'>About</Link>
30
+ </div>
31
+ </nav>
32
+ <main className='max-w-7xl mx-auto'>
33
+ <Routes>
34
+ <Route path="/" element={<Home />} />
35
+ <Route path="/about" element={<About />} />
36
+ </Routes>
37
+ </main>
38
+ </div>
39
+ </Router>
40
+ );
41
+ }
42
+
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App';
4
+
5
+
6
+ document.addEventListener('DOMContentLoaded', () => {
7
+ document.querySelectorAll('[data-react-component]').forEach(el => {
8
+ const component = {
9
+ component:el.dataset.reactComponent
10
+ };
11
+ const props = JSON.parse(el.dataset.props || '{}');
12
+ const allProps={
13
+ ...props,
14
+ ...component
15
+ }
16
+ createRoot(el).render(<App {...allProps} />);
17
+ });
18
+ });
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+
3
+ export default function About() {
4
+ return (
5
+ <div className='p-4'>
6
+ <h1 className='text-xl font-bold text-gray-900 mb-4'>About Blue Bird</h1>
7
+ <p className='text-gray-500 leading-1.6'>
8
+ Blue Bird is a modern framework designed to bridge the gap between backend routing and frontend interactivity.
9
+ It provides a seamless developer experience for building fast, reactive web applications.
10
+ </p>
11
+ <p className='text-red-500 text-xl mt-8 '>
12
+ Check your console JS
13
+ </p>
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,68 @@
1
+ import React, { useEffect } from 'react';
2
+
3
+ export default function Home() {
4
+ useEffect(() => {
5
+ // Example API call to the backend
6
+ fetch("http://localhost:3000/login", {
7
+ method: "POST",
8
+ headers: {
9
+ "Content-Type": "application/json",
10
+ },
11
+ body: JSON.stringify({
12
+ email: "example@example.com",
13
+ password: "myPassword123"
14
+ }),
15
+ })
16
+ .then((response) => response.json())
17
+ .then((data) => console.log('Backend response:', data))
18
+ .catch((error) => console.error('Error fetching from backend:', error));
19
+ }, []);
20
+
21
+ return (
22
+ <div className='text-center p-4'>
23
+ <header className='mb-4'>
24
+ <h1 className='text-3xl font-bold bg-gradient-to-r from-blue-500 to-blue-600 bg-clip-text text-transparent mb-4'>
25
+ Welcome to Blue Bird
26
+ </h1>
27
+ <p className='text-gray-500 max-w-600px mx-auto'>
28
+ The elegant, fast, and weightless framework for modern web development.
29
+ </p>
30
+ </header>
31
+
32
+ <div className='flex gap-4 justify-center mb-8'>
33
+ <a
34
+ href="https://seip25.github.io/Blue-bird/en.html"
35
+ target="_blank"
36
+ rel="noopener noreferrer"
37
+ className='bg-blue-600 text-white px-4 py-2 rounded-lg font-semibold transition-colors hover:bg-blue-400'
38
+ >
39
+ Documentation (Eng)
40
+ </a>
41
+ <a
42
+ href="https://seip25.github.io/Blue-bird/"
43
+ target="_blank"
44
+ rel="noopener noreferrer"
45
+ className='bg-blue-50 text-blue-500 px-4 py-2 rounded-lg font-semibold transition-colors hover:bg-blue-100 '
46
+ >
47
+ Documentación (Esp)
48
+
49
+ </a>
50
+ </div>
51
+
52
+ <div className='mt-8 grid grid-cols-1 md:grid-cols-3 gap-4 max-w-1000px mx-auto'>
53
+ <div className='p-4 rounded-lg bg-gray-50 shadow-sm'>
54
+ <h3 className='text-blue-500 font-semibold text-xl mb-4'>Lightweight</h3>
55
+ <p>Built with performance and simplicity in mind.</p>
56
+ </div>
57
+ <div className='p-4 rounded-lg bg-gray-50 shadow-sm'>
58
+ <h3 className='text-blue-500 font-semibold text-xl mb-4'>React Powered</h3>
59
+ <p>Full React + Vite integration .</p>
60
+ </div>
61
+ <div className='p-4 rounded-lg bg-gray-50 shadow-sm'>
62
+ <h3 className='text-blue-500 font-semibold text-xl mb-4'>Express Backend</h3>
63
+ <p>Robust and scalable backend architecture.</p>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ );
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seip/blue-bird",
3
- "version": "0.2.6",
3
+ "version": "0.3.1",
4
4
  "description": "Express + React opinionated framework with SPA or API architecture and built-in JWT auth",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,10 @@
29
29
  "react": "node core/cli/react.js",
30
30
  "init": "node core/cli/init.js",
31
31
  "route": "node core/cli/route.js",
32
- "component": "node core/cli/component.js"
32
+ "component": "node core/cli/component.js",
33
+ "swagger-install": "node core/cli/swagger.js",
34
+ "vite:dev": "vite",
35
+ "vite:build": "vite build"
33
36
  },
34
37
  "dependencies": {
35
38
  "chalk": "^5.6.2",
@@ -40,8 +43,13 @@
40
43
  "helmet": "^8.1.0",
41
44
  "jsonwebtoken": "^9.0.2",
42
45
  "multer": "^2.0.2",
43
- "swagger-jsdoc": "^6.2.8",
44
- "swagger-ui-express": "^5.0.1",
46
+ "react": "^19.2.4",
47
+ "react-dom": "^19.2.4",
48
+ "react-router-dom": "7.13.1",
45
49
  "xss": "^1.0.15"
50
+ },
51
+ "devDependencies": {
52
+ "@vitejs/plugin-react": "^4.2.0",
53
+ "vite": "7.3.1"
46
54
  }
47
55
  }
package/vite.config.js ADDED
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import path from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ root: path.resolve(__dirname, 'frontend/resources/js'),
8
+ base: '/build/',
9
+ build: {
10
+ outDir: path.resolve(__dirname, 'frontend/public/build'),
11
+ emptyOutDir: true,
12
+ manifest: true,
13
+ rollupOptions: {
14
+ input: path.resolve(__dirname, 'frontend/resources/js/Main.jsx'),
15
+ },
16
+ },
17
+ server: {
18
+ origin: 'http://localhost:5173',
19
+ strictPort: true,
20
+ cors: true,
21
+ },
22
+ });
@@ -1,99 +0,0 @@
1
- import Router from "@seip/blue-bird/core/router.js"
2
- import Validator from "@seip/blue-bird/core/validate.js"
3
- import Template from "@seip/blue-bird/core/template.js"
4
-
5
-
6
- const routerUsers = new Router("/")
7
-
8
- //Example swagger docs
9
- /**
10
- * @swagger
11
- * /users:
12
- * get:
13
- * summary: Get all users
14
- * tags: [Users]
15
- * responses:
16
- * 200:
17
- * description: List of users
18
- *
19
- */
20
- routerUsers.get("/users", (req, res) => {
21
- const users = [
22
- {
23
- name: "John Doe",
24
- email: "john.doe@example.com",
25
- },
26
- {
27
- name: "Jane Doe2",
28
- email: "jane.doe2@example.com",
29
- },
30
- ]
31
- res.json(users)
32
- })
33
-
34
- const loginSchema = {
35
- email: { required: true, email: true },
36
- password: { required: true, min: 6 }
37
- };
38
-
39
- const loginValidator = new Validator(loginSchema, 'es');
40
- /**
41
- * @swagger
42
- * /login:
43
- * post:
44
- * summary: Login
45
- * tags: [Users]
46
- * description: Login with email and password
47
- * requestBody:
48
- * required: true
49
- * content:
50
- * application/json:
51
- * schema:
52
- * type: object
53
- * required:
54
- * - email
55
- * - password
56
- * properties:
57
- * email:
58
- * type: string
59
- * format: email
60
- * example: example@email.com
61
- * password:
62
- * type: string
63
- * format: password
64
- * example: 123456
65
- * responses:
66
- * 200:
67
- * description: Login success
68
- *
69
- * 400:
70
- * description: Error
71
- * 401:
72
- * description: Error in request
73
- */
74
-
75
- routerUsers.post('/login', loginValidator.middleware(), (req, res) => {
76
- res.json({ message: 'Login successful' });
77
- });
78
-
79
- //Example renderReact with customized meta Tags
80
- routerUsers.get("/about", (req, res) => {
81
- const response = Template.renderReact(res, "About", { title: "About Example title" }, {
82
- metaTags: {
83
- titleMeta: "About Title",
84
- descriptionMeta: "About description",
85
- keywordsMeta: "About,express, react, framework",
86
- authorMeta: "Blue Bird",
87
- langMeta: "es"
88
- }
89
- });
90
- return response;
91
- })
92
-
93
- routerUsers.get("*", (req, res) => {
94
- const response = Template.renderReact(res, "App", { title: "Example title" });
95
- return response;
96
- })
97
-
98
-
99
- export default routerUsers