@seip/blue-bird 0.4.7 → 0.4.8

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/AGENTS.md CHANGED
@@ -123,19 +123,57 @@ routerApi.post("/users", validateUser.middleware(), (req, res) => {
123
123
 
124
124
  ## 4. Authentication (Auth)
125
125
 
126
- The system includes built-in JWT handling. The framework assumes tokens are passed via Cookies or the `Authorization` header.
126
+ The system includes built-in JWT handling with AES-256-GCM encryption. The framework handles tokens via Cookies or the `Authorization` header.
127
+
128
+ ### Protecting Routes
129
+ Use `Auth.protect()` as a middleware to secure routes.
127
130
 
128
131
  ```javascript
129
132
  import Auth from "@seip/blue-bird/core/auth.js";
130
133
 
131
134
  // Protect an API route (returns 401 if failed):
132
135
  router.get("/profile", Auth.protect(), (req, res) => {
133
- // The user payload is attached to req.user
136
+ // The decrypted user payload is attached to req.user
134
137
  res.json({ user: req.user });
135
138
  });
136
139
 
137
140
  // Protect a React route (redirects to login):
138
141
  router.get("/dashboard", Auth.protect({ redirect: "/login" }), (req, res) => { ... });
142
+
143
+ // Custom cookie key and storage key:
144
+ router.get("/admin", Auth.protect({
145
+ cookieKey: "admin_session",
146
+ key: "admin"
147
+ }), (req, res) => {
148
+ res.json({ admin: req.admin });
149
+ });
150
+ ```
151
+
152
+ ### Login and Logout
153
+ The `Auth` class provides helpers to handle session management via cookies.
154
+
155
+ ```javascript
156
+ // Login a user
157
+ router.post("/login", async (req, res) => {
158
+ const user = { id: 1, name: "John" };
159
+
160
+ // Generates JWT and sets "auth" cookie (24h default)
161
+ await Auth.login(res, user);
162
+
163
+ res.json({ message: "Logged in" });
164
+ });
165
+
166
+ // Logout a user
167
+ router.post("/logout", async (req, res) => {
168
+ await Auth.logout(res);
169
+ res.json({ message: "Logged out" });
170
+ });
171
+
172
+ // Customizing Login (key, expiration, cookie options)
173
+ await Auth.login(res, user, "my_session", {
174
+ expiresIn: "7d",
175
+ cookie: { httpOnly: true, secure: true }
176
+ });
139
177
  ```
140
178
 
141
179
  ## 5. Performance Coaching (Cache)
package/backend/index.js CHANGED
@@ -2,12 +2,31 @@ import App from "@seip/blue-bird/core/app.js";
2
2
  import routerApiExample from "./routes/api.js";
3
3
  import routerFrontendExample from "./routes/frontend.js";
4
4
 
5
+ /**
6
+ * Main entry point for the Blue Bird application.
7
+ * Initializes the App instance with routes, configuration, and starts the server.
8
+ */
5
9
  const app = new App({
10
+ /**
11
+ * Array of router instances to be registered in the application.
12
+ * Each router can have its own base path.
13
+ */
6
14
  routes: [routerApiExample, routerFrontendExample],
15
+
16
+ /** CORS configuration. If empty, uses default settings. */
7
17
  cors: [],
18
+
19
+ /** Global middlewares to be applied before routes. */
8
20
  middlewares: [],
21
+
22
+ /** Base host URL for the application. */
9
23
  host: "http://localhost",
24
+
25
+ /** Server port, defaults to environment variable or fallback in config. */
10
26
  port: process.env.PORT,
11
27
  });
12
28
 
29
+ /**
30
+ * Starts the application server.
31
+ */
13
32
  app.run();
@@ -1,8 +1,11 @@
1
1
  import Router from "@seip/blue-bird/core/router.js";
2
2
  import Validator from "@seip/blue-bird/core/validate.js";
3
+ import Cache from "@seip/blue-bird/core/cache.js";
4
+ import Auth from "@seip/blue-bird/core/auth.js"
3
5
 
4
6
  const routerApiExample = new Router("/api");
5
7
 
8
+ /* simple test route */
6
9
  routerApiExample.get("/users", (req, res) => {
7
10
  const users = [
8
11
  {
@@ -16,7 +19,10 @@ routerApiExample.get("/users", (req, res) => {
16
19
  ];
17
20
  res.json(users);
18
21
  });
22
+ /* End simple test route */
19
23
 
24
+
25
+ /* Validation example */
20
26
  const loginSchema = {
21
27
  email: { required: true, email: true },
22
28
  password: { required: true, min: 6 },
@@ -27,5 +33,43 @@ const loginValidator = new Validator(loginSchema);
27
33
  routerApiExample.post("/login", loginValidator.middleware(), (req, res) => {
28
34
  res.json({ message: "Login successful", body: req.body });
29
35
  });
36
+ /* End Validation example */
37
+
38
+ /* Cache example */
39
+ routerApiExample.get("/cache", Cache.middleware(), async (req, res) => {
40
+ //in debug =false , testing /api/cache should take 2 seconds , and after that should take 0 seconds
41
+ await new Promise(resolve => setTimeout(resolve, 2000));
42
+ res.json({ message: "Cache successful" });
43
+ })
44
+ /* End Cache example */
45
+
46
+ /* Auth example */
47
+ routerApiExample.get("/auth_generate", async (req, res) => {
48
+ const token = await Auth.login(res, { id: 1, name: "John Doe" })
49
+ /*Or use
50
+ const token = Auth.generateToken({ id: user.id });//to generate token
51
+ res.cookie("auth", token,{
52
+ maxAge: 24 * 60 * 60 * 1000,
53
+ httpOnly: true,
54
+ secure: production,
55
+ sameSite: "strict",
56
+ path: "/",
57
+ });
58
+ */
59
+ res.json({ message: "Auth successful", token });
60
+
61
+ })
62
+
63
+ routerApiExample.get("/auth_logout", async (req, res) => {
64
+ await Auth.logout(res)
65
+ res.json({ message: "Auth successful" });
66
+ })
67
+
68
+ routerApiExample.get("/auth_verify", Auth.protect(), (req, res) => {
69
+ // req.user has inject with the jwt token payload and decoded by the Auth.protect() middleware
70
+ const userInfo = req.user
71
+ res.json({ message: "Auth successful", user: userInfo });
72
+ })
73
+ /* End Auth example */
30
74
 
31
75
  export default routerApiExample;
@@ -6,36 +6,42 @@ import seoData from "./seo.js";
6
6
  const routerFrontendExample = new Router();
7
7
  routerFrontendExample.use(App.helmet()); // Helmet for frontend router
8
8
 
9
+ /* Render HTML frontend/landing.html */
9
10
  routerFrontendExample.get("/landing", (req, res) => {
10
11
  return Template.renderHtml(res, "landing", {
11
- metaTags:{
12
- titleMeta:"Landing Example",
12
+ metaTags: {
13
+ titleMeta: "Landing Example",
13
14
  descriptionMeta: "Description meta",
14
15
  keywordsMeta: "keywordsMeta"
15
16
  }
16
17
  });
17
18
  });
19
+ /* End Render HTML frontend/landing.html */
18
20
 
21
+ /* SEO example */
19
22
  routerFrontendExample.seo(
20
23
  [
21
24
  {
22
25
  path: "/",
23
26
  component: "Home",
24
- seoKey: "home",
25
- props: { id: 1, name: "Name" },
27
+ seoKey: "home",//key in seo.js data
28
+ props: { id: 1, name: "Name" },// Props pass to react component or
26
29
  },
27
30
  {
28
31
  path: "/about",
29
32
  component: "About",
30
- seoKey: "about",
33
+ seoKey: "about",//key in seo.js data
31
34
  props: { id: 2, name: "Name 2" },
32
35
  },
33
36
  ],
34
37
  { languages: ["en", "es"], defaultLanguage: "en", seoData },
35
38
  );
39
+ /* End SEO example */
36
40
 
41
+ /* Render React example for '*' route (catch all routes) */
37
42
  routerFrontendExample.get("*", (req, res) => {
38
43
  return Template.renderReact(res, "App");
39
44
  });
45
+ /* End Render React example */
40
46
 
41
47
  export default routerFrontendExample;
package/core/auth.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import jwt from "jsonwebtoken";
2
2
  import crypto from "node:crypto";
3
+ import Config from "./config.js";
3
4
 
5
+ const propsConfig = Config.props();
6
+ const jwtSecret = propsConfig.jwtSecret;
7
+ const production = propsConfig.debug;
4
8
  /**
5
9
  * Auth class to handle JWT generation, verification and protection with AES-256-GCM encryption.
6
10
  */
@@ -49,13 +53,13 @@ class Auth {
49
53
  /**
50
54
  * Generates an encrypted JWT token.
51
55
  * @param {Object} payload - The data to store in the token.
52
- * @param {string} [secret=process.env.JWT_SECRET] - The secret key.
56
+ * @param {string} [secret=process.env.JWT_SECRET] - The secret key .
53
57
  * @param {string} [expiresIn="24h"] - Expiration time.
54
58
  * @returns {string} The generated token.
55
59
  */
56
60
  static generateToken(
57
61
  payload,
58
- secret = process.env.JWT_SECRET,
62
+ secret = jwtSecret,
59
63
  expiresIn = "24h"
60
64
  ) {
61
65
  if (!secret)
@@ -70,7 +74,7 @@ class Auth {
70
74
  * @param {string} [secret=process.env.JWT_SECRET] - The secret key.
71
75
  * @returns {Object|null} The decoded and decrypted payload or null if invalid.
72
76
  */
73
- static verifyToken(token, secret = process.env.JWT_SECRET) {
77
+ static verifyToken(token, secret = jwtSecret) {
74
78
  if (!secret)
75
79
  throw new Error("FATAL: JWT_SECRET environment variable is not defined.");
76
80
  try {
@@ -84,30 +88,42 @@ class Auth {
84
88
 
85
89
  /**
86
90
  * Middleware to protect routes. Checks for token in Cookies or Authorization header.
87
- * @param {Object} options - Options for protection.
91
+ * @param {Object} [options={}] - Options for protection.
88
92
  * @param {string} [options.redirect=null] - URL to redirect if not authenticated.
89
93
  * @param {string} [options.key="user"] - Key to store the decoded token in the request.
94
+ * @param {string} [options.cookieKey="auth"] - The cookie key to look for the token.
90
95
  * @returns {Function} Express middleware.
96
+ * @example
97
+ * router.get("/profile", Auth.protect(), (req, res) => { ... });
98
+ * // Or with custom cookie key:
99
+ * router.get("/admin", Auth.protect({ cookieKey: "admin_session" }), (req, res) => { ... });
91
100
  */
92
- static protect(options = { redirect: null, key: "user" }) {
101
+ static protect(options = {}) {
102
+ const { redirect = null, key = "user", cookieKey = "auth" } = options;
103
+
93
104
  return (req, res, next) => {
94
105
  const token =
95
- req.cookies?.auth || req.headers.authorization?.split(" ")[1];
106
+ req.cookies?.[cookieKey] || req.headers.authorization?.split(" ")[1];
96
107
 
97
- const isContentTypeJson = req.headers["content-type"] === "application/json";
108
+ const isContentTypeJson =
109
+ req.headers["content-type"] === "application/json";
98
110
 
99
111
  if (!token) {
100
- if (options.redirect && !isContentTypeJson) return res.redirect(options.redirect);
101
- return isContentTypeJson ? res.status(401).json({ message: "Unauthorized" }) : res.status(401).send();
112
+ if (redirect && !isContentTypeJson) return res.redirect(redirect);
113
+ return isContentTypeJson
114
+ ? res.status(401).json({ message: "Unauthorized" })
115
+ : res.status(401).send();
102
116
  }
103
117
 
104
118
  const decoded = this.verifyToken(token);
105
119
  if (!decoded) {
106
- if (options.redirect && !isContentTypeJson) return res.redirect(options.redirect);
107
- return isContentTypeJson ? res.status(401).json({ message: "Unauthorized" }) : res.status(401).send();
120
+ if (redirect && !isContentTypeJson) return res.redirect(redirect);
121
+ return isContentTypeJson
122
+ ? res.status(401).json({ message: "Unauthorized" })
123
+ : res.status(401).send();
108
124
  }
109
125
 
110
- req[options.key || "user"] = decoded;
126
+ req[key || "user"] = decoded;
111
127
  next();
112
128
  };
113
129
  }
@@ -117,12 +133,29 @@ class Auth {
117
133
  * @param {import('express').Response} res - The response object.
118
134
  * @param {Object} data - The data to store in the token.
119
135
  * @param {string} [key="auth"] - The key for the cookie.
120
- * @param {Object} [options={cookie: {maxAge: 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: "strict"}}] - Options for the cookie.
121
- * @returns {string} The generated token.
136
+ * @param {Object} [options={}] - Options for the cookie and token.
137
+ * @param {string} [options.expiresIn="24h"] - Token expiration (e.g., "1h", "7d").
138
+ * @param {import('express').CookieOptions} [options.cookie] - Express cookie options.
139
+ * @returns {Promise<string>} The generated token.
140
+ * @example
141
+ * await Auth.login(res, { id: 1, name: "Admin" });
122
142
  */
123
- static login(res, data, key = "auth", options = { cookie: { maxAge: 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: "strict" } }) {
124
- const token = this.generateToken(data);
125
- res.cookie(key, token, options.cookie);
143
+ static async login(res, data, key = "auth", options = {}) {
144
+ const { expiresIn = "24h", cookie = {} } = options;
145
+
146
+ const token = this.generateToken(data, jwtSecret, expiresIn);
147
+
148
+ const defaultCookieOptions = {
149
+ maxAge: 24 * 60 * 60 * 1000,
150
+ httpOnly: true,
151
+ secure: production,
152
+ sameSite: "strict",
153
+ path: "/",
154
+ };
155
+
156
+ const finalCookieOptions = { ...defaultCookieOptions, ...cookie };
157
+
158
+ res.cookie(key, token, finalCookieOptions);
126
159
  return token;
127
160
  }
128
161
 
@@ -130,11 +163,16 @@ class Auth {
130
163
  * Logs out a user by clearing the authentication cookie.
131
164
  * @param {import('express').Response} res - The response object.
132
165
  * @param {string} [key="auth"] - The key for the cookie.
133
- * @param {Object} [options={cookie: {maxAge: 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: "strict"}}] - Options for the cookie.
134
- * @returns {boolean} True if the cookie was cleared successfully.
166
+ * @param {import('express').CookieOptions} [options={}] - Options for clearing the cookie.
167
+ * @returns {Promise<boolean>} True if the cookie was cleared successfully.
168
+ * @example
169
+ * await Auth.logout(res);
135
170
  */
136
- static logout(res, key = "auth", options = { cookie: { maxAge: 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: "strict" } }) {
137
- res.clearCookie(key, options.cookie);
171
+ static async logout(res, key = "auth", options = {}) {
172
+ const defaultOptions = {
173
+ path: "/",
174
+ };
175
+ res.clearCookie(key, { ...defaultOptions, ...options });
138
176
  return true;
139
177
  }
140
178
  }
package/core/cache.js CHANGED
@@ -9,37 +9,40 @@ setInterval(() => {
9
9
  }
10
10
  }, 300000).unref();
11
11
  /**
12
- * Cache Middleware
13
- * @example
14
- * router.get("/stats",
15
- Cache.middleware(120),
16
- controller.stats
17
- );
18
- * */
12
+ * Simple in-memory Cache class to provide middleware for Express routes.
13
+ * Caches JSON responses based on the request URL.
14
+ */
19
15
  class Cache {
20
-
21
- static middleware(seconds = 60) {
22
- return (req, res, next) => {
23
-
24
- const key = req.originalUrl;
25
-
26
- if (CACHE[key] && CACHE[key].expiry > Date.now()) {
27
- return res.json(CACHE[key].data);
28
- }
29
-
30
- const originalJson = res.json.bind(res);
31
-
32
- res.json = (body) => {
33
- CACHE[key] = {
34
- data: body,
35
- expiry: Date.now() + seconds * 1000
36
- };
37
- return originalJson(body);
38
- };
39
-
40
- next();
16
+ /**
17
+ * Middleware to cache responses.
18
+ * @param {number} [seconds=60] - Number of seconds to cache the response.
19
+ * @returns {Function} Express middleware function.
20
+ * @example
21
+ * router.get("/stats", Cache.middleware(120), (req, res) => {
22
+ * res.json({ ok: true });
23
+ * });
24
+ */
25
+ static middleware(seconds = 60) {
26
+ return (req, res, next) => {
27
+ const key = req.originalUrl;
28
+
29
+ if (CACHE[key] && CACHE[key].expiry > Date.now()) {
30
+ return res.json(CACHE[key].data);
31
+ }
32
+
33
+ const originalJson = res.json.bind(res);
34
+
35
+ res.json = (body) => {
36
+ CACHE[key] = {
37
+ data: body,
38
+ expiry: Date.now() + seconds * 1000,
41
39
  };
42
- }
40
+ return originalJson(body);
41
+ };
42
+
43
+ next();
44
+ };
45
+ }
43
46
  }
44
47
 
45
48
  export default Cache;
package/core/config.js CHANGED
@@ -40,6 +40,7 @@ class Config {
40
40
  host: process.env.HOST || "http://localhost",
41
41
  appUrl: process.env.APP_URL || process.env.HOST || "http://localhost",
42
42
  port: Number.isNaN(portRaw) ? 3000 : portRaw,
43
+ jwtSecret: process.env.JWT_SECRET,
43
44
  static: {
44
45
  path: process.env.STATIC_PATH || "frontend/public",
45
46
  options: {},
@@ -30,12 +30,10 @@ function generateRoutes(routes, languages = []) {
30
30
  const allRoutes = [];
31
31
 
32
32
  routes.forEach(({ path: routePath, element }) => {
33
- // Base path
34
33
  allRoutes.push(
35
34
  <Route key={routePath} path={routePath} element={element} />
36
35
  );
37
36
 
38
- // Language-prefixed paths (only if multiple languages are used)
39
37
  if (languages && languages.length > 0) {
40
38
  languages.forEach((lang) => {
41
39
  const langPath = `/${lang}${routePath === "/" ? "" : routePath}`;
@@ -56,7 +54,7 @@ export default function App(_props) {
56
54
  <ThemeProvider>
57
55
  <LanguageProvider initialLang={lang}>
58
56
  <Router>
59
- <SPAProvider languages={LANGUAGES} defaultLanguage={DEFAULT_LANGUAGE}>
57
+ <SPAProvider languages={LANGUAGES} defaultLanguage={DEFAULT_LANGUAGE} {..._props} >
60
58
  <Suspense fallback={<Skeleton />}>
61
59
  <Routes>
62
60
  {generateRoutes(ROUTES, LANGUAGES)}
@@ -28,7 +28,7 @@ const SPAContext = createContext({
28
28
  * @param {Array<string>} [props.languages=[]] - Supported language codes.
29
29
  * @param {string} [props.defaultLanguage="en"] - Default language.
30
30
  */
31
- export function SPAProvider({
31
+ export function SPAProvider({ props,
32
32
  children,
33
33
  languages = [],
34
34
  defaultLanguage = "en",
@@ -36,7 +36,7 @@ export function SPAProvider({
36
36
  const location = useLocation();
37
37
  const navigate = useNavigate();
38
38
  const { lang, setLang } = useLanguage();
39
- const [pageProps, setPageProps] = useState({});
39
+ const [pageProps, setPageProps] = useState(props);
40
40
  const [pageMeta, setPageMeta] = useState({});
41
41
  const [loading, setLoading] = useState(false);
42
42
  const isFirstRender = useRef(true);
@@ -136,7 +136,6 @@ export function SPAProvider({
136
136
  }
137
137
  });
138
138
 
139
- // Update OG tags
140
139
  const ogUpdates = {
141
140
  "og:title": meta.titleMeta,
142
141
  "og:description": meta.descriptionMeta,
@@ -153,21 +152,21 @@ export function SPAProvider({
153
152
  }, []);
154
153
 
155
154
  useEffect(() => {
156
- // Sync language from URL prefix if present
157
155
  const urlLang = detectLangFromPath(location.pathname);
158
156
  if (urlLang) {
159
157
  setLang(urlLang);
160
158
  } else if (languages.length > 0 && !isFirstRender.current) {
161
- // On subsequent navigations, if prefix is missing, revert to default
162
159
  setLang(defaultLanguage);
163
160
  }
164
161
 
165
- // Skip fetch on initial render
166
162
  if (isFirstRender.current) {
167
163
  isFirstRender.current = false;
168
164
  return;
169
165
  }
170
166
 
167
+ setPageProps({});
168
+ setPageMeta({});
169
+
171
170
  if (abortRef.current) {
172
171
  abortRef.current.abort();
173
172
  }
@@ -4,9 +4,13 @@ import { useLanguage } from '../blue-bird/contexts/LanguageContext';
4
4
 
5
5
  import Card from '../blue-bird/components/Card';
6
6
  import Typography from '../blue-bird/components/Typography'
7
+ import { useSPA } from '../blue-bird/contexts/SPAContext.jsx';
7
8
 
8
9
  export default function About() {
9
10
  const { t } = useLanguage();
11
+ const { navigateToLang, pageProps, pageMeta } = useSPA();
12
+ console.log("pageProps", pageProps);
13
+ console.log("pageMeta", pageMeta);
10
14
  return (
11
15
  <div
12
16
  className="bg-white dark:bg-slate-900 text-gray-900 dark:text-gray-100 min-h-screen"
@@ -3,11 +3,16 @@ import Card from '../blue-bird/components/Card';
3
3
  import Header from '../components/Header';
4
4
  import { useLanguage } from '../blue-bird/contexts/LanguageContext';
5
5
  import Typography from '../blue-bird/components/Typography';
6
+ import { useSPA } from '../blue-bird/contexts/SPAContext.jsx';
6
7
 
7
8
  export default function Home() {
8
9
  const { t } = useLanguage();
10
+ const { navigateToLang, pageProps, pageMeta } = useSPA();
11
+ console.log("pageProps", pageProps);
12
+ console.log("pageMeta", pageMeta);
13
+
9
14
  useEffect(() => {
10
- // Example API call to the backend
15
+
11
16
  fetch("/api/login", {
12
17
  method: "POST",
13
18
  headers: {
package/package.json CHANGED
@@ -1,58 +1,58 @@
1
- {
2
- "name": "@seip/blue-bird",
3
- "version": "0.4.7",
4
- "description": "Express + React opinionated framework with SPA or API architecture and built-in JWT auth",
5
- "type": "module",
6
- "bin": {
7
- "blue-bird": "core/cli/init.js"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/seip25/Blue-bird.git"
12
- },
13
- "keywords": [
14
- "express",
15
- "react",
16
- "framework",
17
- "vite"
18
- ],
19
- "author": "Seip25",
20
- "license": "MIT",
21
- "bugs": {
22
- "url": "https://github.com/seip25/Blue-bird/issues"
23
- },
24
- "homepage": "https://seip25.github.io/Blue-bird/",
25
- "scripts": {
26
- "dev": "node --watch --env-file=.env backend/index.js",
27
- "start": "node --env-file=.env backend/index.js",
28
- "create-react-app": "node core/cli/react.js",
29
- "react": "node core/cli/react.js",
30
- "init": "node core/cli/init.js",
31
- "route": "node core/cli/route.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"
36
- },
37
- "dependencies": {
38
- "@tailwindcss/vite": "^4.2.2",
39
- "chalk": "^5.6.2",
40
- "compression": "^1.8.1",
41
- "cookie-parser": "^1.4.7",
42
- "cors": "^2.8.6",
43
- "express": "^5.2.1",
44
- "express-rate-limit": "^8.2.1",
45
- "helmet": "^8.1.0",
46
- "jsonwebtoken": "^9.0.2",
47
- "multer": "^2.0.2",
48
- "react": "^19.2.4",
49
- "react-dom": "^19.2.4",
50
- "react-router-dom": "^7.2.0",
51
- "tailwindcss": "^4.2.2",
52
- "xss": "^1.0.15"
53
- },
54
- "devDependencies": {
55
- "@vitejs/plugin-react": "^4.3.4",
56
- "vite": "^7.3.1"
57
- }
1
+ {
2
+ "name": "@seip/blue-bird",
3
+ "version": "0.4.8",
4
+ "description": "Express + React opinionated framework with SPA or API architecture and built-in JWT auth",
5
+ "type": "module",
6
+ "bin": {
7
+ "blue-bird": "core/cli/init.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/seip25/Blue-bird.git"
12
+ },
13
+ "keywords": [
14
+ "express",
15
+ "react",
16
+ "framework",
17
+ "vite"
18
+ ],
19
+ "author": "Seip25",
20
+ "license": "MIT",
21
+ "bugs": {
22
+ "url": "https://github.com/seip25/Blue-bird/issues"
23
+ },
24
+ "homepage": "https://seip25.github.io/Blue-bird/",
25
+ "scripts": {
26
+ "dev": "node --watch --env-file=.env backend/index.js",
27
+ "start": "node --env-file=.env backend/index.js",
28
+ "create-react-app": "node core/cli/react.js",
29
+ "react": "node core/cli/react.js",
30
+ "init": "node core/cli/init.js",
31
+ "route": "node core/cli/route.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"
36
+ },
37
+ "dependencies": {
38
+ "@tailwindcss/vite": "^4.2.2",
39
+ "chalk": "^5.6.2",
40
+ "compression": "^1.8.1",
41
+ "cookie-parser": "^1.4.7",
42
+ "cors": "^2.8.6",
43
+ "express": "^5.2.1",
44
+ "express-rate-limit": "^8.2.1",
45
+ "helmet": "^8.1.0",
46
+ "jsonwebtoken": "^9.0.2",
47
+ "multer": "^2.0.2",
48
+ "react": "^19.2.4",
49
+ "react-dom": "^19.2.4",
50
+ "react-router-dom": "^7.2.0",
51
+ "tailwindcss": "^4.2.2",
52
+ "xss": "^1.0.15"
53
+ },
54
+ "devDependencies": {
55
+ "@vitejs/plugin-react": "^4.3.4",
56
+ "vite": "^7.3.1"
57
+ }
58
58
  }