@seip/blue-bird 0.4.5 → 0.4.7
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 +26 -25
- package/AGENTS.md +199 -199
- package/README.md +79 -79
- package/backend/index.js +13 -13
- package/backend/routes/frontend.js +41 -41
- package/backend/routes/seo.js +39 -39
- package/core/app.js +330 -325
- package/core/auth.js +142 -114
- package/core/cache.js +44 -44
- package/core/cli/component.js +42 -42
- package/core/cli/init.js +119 -118
- package/core/cli/react.js +435 -435
- package/core/cli/route.js +42 -42
- package/core/config.js +51 -47
- package/core/debug.js +248 -248
- package/core/logger.js +100 -100
- package/core/middleware.js +27 -27
- package/core/router.js +333 -333
- package/core/seo.js +95 -100
- package/core/template.js +478 -462
- package/core/upload.js +77 -76
- package/core/validate.js +380 -380
- package/frontend/index.html +31 -26
- package/frontend/landing.html +70 -69
- package/frontend/resources/css/tailwind.css +17 -17
- package/frontend/resources/js/App.jsx +70 -70
- package/frontend/resources/js/Main.jsx +18 -18
- package/frontend/resources/js/blue-bird/components/Button.jsx +67 -67
- package/frontend/resources/js/blue-bird/components/Card.jsx +18 -18
- package/frontend/resources/js/blue-bird/components/DataTable.jsx +126 -126
- package/frontend/resources/js/blue-bird/components/Input.jsx +21 -21
- package/frontend/resources/js/blue-bird/components/Label.jsx +12 -12
- package/frontend/resources/js/blue-bird/components/LanguageButton.jsx +23 -23
- package/frontend/resources/js/blue-bird/components/Link.jsx +15 -15
- package/frontend/resources/js/blue-bird/components/Modal.jsx +27 -27
- package/frontend/resources/js/blue-bird/components/Skeleton.jsx +44 -44
- package/frontend/resources/js/blue-bird/components/Translate.jsx +12 -12
- package/frontend/resources/js/blue-bird/components/Typography.jsx +69 -69
- package/frontend/resources/js/blue-bird/contexts/LanguageContext.jsx +41 -41
- package/frontend/resources/js/blue-bird/contexts/SPAContext.jsx +239 -237
- package/frontend/resources/js/blue-bird/contexts/SnackbarContext.jsx +38 -38
- package/frontend/resources/js/blue-bird/contexts/ThemeContext.jsx +49 -49
- package/frontend/resources/js/blue-bird/locales/en.json +47 -47
- package/frontend/resources/js/blue-bird/locales/es.json +47 -47
- package/frontend/resources/js/components/Header.jsx +55 -55
- package/frontend/resources/js/pages/About.jsx +31 -31
- package/frontend/resources/js/pages/Home.jsx +82 -82
- package/package.json +1 -1
- package/vite.config.js +22 -22
- package/frontend/public/robots.txt +0 -0
- package/frontend/public/sitemap.xml +0 -0
package/core/auth.js
CHANGED
|
@@ -1,114 +1,142 @@
|
|
|
1
|
-
import jwt from "jsonwebtoken";
|
|
2
|
-
import crypto from "node:crypto";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Auth class to handle JWT generation, verification and protection with AES-256-GCM encryption.
|
|
6
|
-
*/
|
|
7
|
-
class Auth {
|
|
8
|
-
/**
|
|
9
|
-
* Encrypts a payload using AES-256-GCM.
|
|
10
|
-
* @param {Object} payload - The data to encrypt.
|
|
11
|
-
* @param {string} secret - The secret key for encryption.
|
|
12
|
-
* @returns {string} The encrypted string in format iv:tag:encrypted.
|
|
13
|
-
*/
|
|
14
|
-
static encrypt(payload, secret) {
|
|
15
|
-
const iv = crypto.randomBytes(12);
|
|
16
|
-
const key = crypto.createHash("sha256").update(secret).digest();
|
|
17
|
-
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
18
|
-
let encrypted = cipher.update(JSON.stringify(payload), "utf8", "hex");
|
|
19
|
-
encrypted += cipher.final("hex");
|
|
20
|
-
const tag = cipher.getAuthTag().toString("hex");
|
|
21
|
-
return `${iv.toString("hex")}:${tag}:${encrypted}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Decrypts a payload using AES-256-GCM.
|
|
26
|
-
* @param {string} data - The encrypted string in format iv:tag:encrypted.
|
|
27
|
-
* @param {string} secret - The secret key for decryption.
|
|
28
|
-
* @returns {Object|null} The decrypted object or null if failed.
|
|
29
|
-
*/
|
|
30
|
-
static decrypt(data, secret) {
|
|
31
|
-
try {
|
|
32
|
-
const [ivHex, tagHex, encryptedHex] = data.split(":");
|
|
33
|
-
if (!ivHex || !tagHex || !encryptedHex) return null;
|
|
34
|
-
|
|
35
|
-
const iv = Buffer.from(ivHex, "hex");
|
|
36
|
-
const tag = Buffer.from(tagHex, "hex");
|
|
37
|
-
const key = crypto.createHash("sha256").update(secret).digest();
|
|
38
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
|
39
|
-
decipher.setAuthTag(tag);
|
|
40
|
-
|
|
41
|
-
let decrypted = decipher.update(encryptedHex, "hex", "utf8");
|
|
42
|
-
decrypted += decipher.final("utf8");
|
|
43
|
-
return JSON.parse(decrypted);
|
|
44
|
-
} catch (error) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Generates an encrypted JWT token.
|
|
51
|
-
* @param {Object} payload - The data to store in the token.
|
|
52
|
-
* @param {string} [secret=process.env.JWT_SECRET] - The secret key.
|
|
53
|
-
* @param {string} [expiresIn="24h"] - Expiration time.
|
|
54
|
-
* @returns {string} The generated token.
|
|
55
|
-
*/
|
|
56
|
-
static generateToken(
|
|
57
|
-
payload,
|
|
58
|
-
secret = process.env.JWT_SECRET,
|
|
59
|
-
expiresIn = "24h"
|
|
60
|
-
) {
|
|
61
|
-
if (!secret)
|
|
62
|
-
throw new Error("FATAL: JWT_SECRET environment variable is not defined.");
|
|
63
|
-
const encrypted = this.encrypt(payload, secret);
|
|
64
|
-
return jwt.sign({ data: encrypted }, secret, { expiresIn });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Verifies and decrypts a JWT token.
|
|
69
|
-
* @param {string} token - The token to verify.
|
|
70
|
-
* @param {string} [secret=process.env.JWT_SECRET] - The secret key.
|
|
71
|
-
* @returns {Object|null} The decoded and decrypted payload or null if invalid.
|
|
72
|
-
*/
|
|
73
|
-
static verifyToken(token, secret = process.env.JWT_SECRET) {
|
|
74
|
-
if (!secret)
|
|
75
|
-
throw new Error("FATAL: JWT_SECRET environment variable is not defined.");
|
|
76
|
-
try {
|
|
77
|
-
const decoded = jwt.verify(token, secret);
|
|
78
|
-
if (!decoded || !decoded.data) return null;
|
|
79
|
-
return this.decrypt(decoded.data, secret);
|
|
80
|
-
} catch (error) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Middleware to protect routes. Checks for token in Cookies or Authorization header.
|
|
87
|
-
* @param {Object} options - Options for protection.
|
|
88
|
-
* @param {string} [options.redirect=null] - URL to redirect if not authenticated.
|
|
89
|
-
* @param {string} [options.key="user"] - Key to store the decoded token in the request.
|
|
90
|
-
* @returns {Function} Express middleware.
|
|
91
|
-
*/
|
|
92
|
-
static protect(options = { redirect: null, key: "user" }) {
|
|
93
|
-
return (req, res, next) => {
|
|
94
|
-
const token =
|
|
95
|
-
req.cookies?.auth || req.headers.authorization?.split(" ")[1];
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Auth class to handle JWT generation, verification and protection with AES-256-GCM encryption.
|
|
6
|
+
*/
|
|
7
|
+
class Auth {
|
|
8
|
+
/**
|
|
9
|
+
* Encrypts a payload using AES-256-GCM.
|
|
10
|
+
* @param {Object} payload - The data to encrypt.
|
|
11
|
+
* @param {string} secret - The secret key for encryption.
|
|
12
|
+
* @returns {string} The encrypted string in format iv:tag:encrypted.
|
|
13
|
+
*/
|
|
14
|
+
static encrypt(payload, secret) {
|
|
15
|
+
const iv = crypto.randomBytes(12);
|
|
16
|
+
const key = crypto.createHash("sha256").update(secret).digest();
|
|
17
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
18
|
+
let encrypted = cipher.update(JSON.stringify(payload), "utf8", "hex");
|
|
19
|
+
encrypted += cipher.final("hex");
|
|
20
|
+
const tag = cipher.getAuthTag().toString("hex");
|
|
21
|
+
return `${iv.toString("hex")}:${tag}:${encrypted}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decrypts a payload using AES-256-GCM.
|
|
26
|
+
* @param {string} data - The encrypted string in format iv:tag:encrypted.
|
|
27
|
+
* @param {string} secret - The secret key for decryption.
|
|
28
|
+
* @returns {Object|null} The decrypted object or null if failed.
|
|
29
|
+
*/
|
|
30
|
+
static decrypt(data, secret) {
|
|
31
|
+
try {
|
|
32
|
+
const [ivHex, tagHex, encryptedHex] = data.split(":");
|
|
33
|
+
if (!ivHex || !tagHex || !encryptedHex) return null;
|
|
34
|
+
|
|
35
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
36
|
+
const tag = Buffer.from(tagHex, "hex");
|
|
37
|
+
const key = crypto.createHash("sha256").update(secret).digest();
|
|
38
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
|
39
|
+
decipher.setAuthTag(tag);
|
|
40
|
+
|
|
41
|
+
let decrypted = decipher.update(encryptedHex, "hex", "utf8");
|
|
42
|
+
decrypted += decipher.final("utf8");
|
|
43
|
+
return JSON.parse(decrypted);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generates an encrypted JWT token.
|
|
51
|
+
* @param {Object} payload - The data to store in the token.
|
|
52
|
+
* @param {string} [secret=process.env.JWT_SECRET] - The secret key.
|
|
53
|
+
* @param {string} [expiresIn="24h"] - Expiration time.
|
|
54
|
+
* @returns {string} The generated token.
|
|
55
|
+
*/
|
|
56
|
+
static generateToken(
|
|
57
|
+
payload,
|
|
58
|
+
secret = process.env.JWT_SECRET,
|
|
59
|
+
expiresIn = "24h"
|
|
60
|
+
) {
|
|
61
|
+
if (!secret)
|
|
62
|
+
throw new Error("FATAL: JWT_SECRET environment variable is not defined.");
|
|
63
|
+
const encrypted = this.encrypt(payload, secret);
|
|
64
|
+
return jwt.sign({ data: encrypted }, secret, { expiresIn });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Verifies and decrypts a JWT token.
|
|
69
|
+
* @param {string} token - The token to verify.
|
|
70
|
+
* @param {string} [secret=process.env.JWT_SECRET] - The secret key.
|
|
71
|
+
* @returns {Object|null} The decoded and decrypted payload or null if invalid.
|
|
72
|
+
*/
|
|
73
|
+
static verifyToken(token, secret = process.env.JWT_SECRET) {
|
|
74
|
+
if (!secret)
|
|
75
|
+
throw new Error("FATAL: JWT_SECRET environment variable is not defined.");
|
|
76
|
+
try {
|
|
77
|
+
const decoded = jwt.verify(token, secret);
|
|
78
|
+
if (!decoded || !decoded.data) return null;
|
|
79
|
+
return this.decrypt(decoded.data, secret);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Middleware to protect routes. Checks for token in Cookies or Authorization header.
|
|
87
|
+
* @param {Object} options - Options for protection.
|
|
88
|
+
* @param {string} [options.redirect=null] - URL to redirect if not authenticated.
|
|
89
|
+
* @param {string} [options.key="user"] - Key to store the decoded token in the request.
|
|
90
|
+
* @returns {Function} Express middleware.
|
|
91
|
+
*/
|
|
92
|
+
static protect(options = { redirect: null, key: "user" }) {
|
|
93
|
+
return (req, res, next) => {
|
|
94
|
+
const token =
|
|
95
|
+
req.cookies?.auth || req.headers.authorization?.split(" ")[1];
|
|
96
|
+
|
|
97
|
+
const isContentTypeJson = req.headers["content-type"] === "application/json";
|
|
98
|
+
|
|
99
|
+
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();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const decoded = this.verifyToken(token);
|
|
105
|
+
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();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
req[options.key || "user"] = decoded;
|
|
111
|
+
next();
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Logs in a user by setting an authentication cookie.
|
|
117
|
+
* @param {import('express').Response} res - The response object.
|
|
118
|
+
* @param {Object} data - The data to store in the token.
|
|
119
|
+
* @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.
|
|
122
|
+
*/
|
|
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);
|
|
126
|
+
return token;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Logs out a user by clearing the authentication cookie.
|
|
131
|
+
* @param {import('express').Response} res - The response object.
|
|
132
|
+
* @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.
|
|
135
|
+
*/
|
|
136
|
+
static logout(res, key = "auth", options = { cookie: { maxAge: 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: "strict" } }) {
|
|
137
|
+
res.clearCookie(key, options.cookie);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default Auth;
|
package/core/cache.js
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
const CACHE = {};
|
|
2
|
-
|
|
3
|
-
setInterval(() => {
|
|
4
|
-
const now = Date.now();
|
|
5
|
-
for (const key in CACHE) {
|
|
6
|
-
if (CACHE[key].expiry <= now) {
|
|
7
|
-
delete CACHE[key];
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
}, 300000).unref();
|
|
11
|
-
/**
|
|
12
|
-
* Cache Middleware
|
|
13
|
-
* @example
|
|
14
|
-
* router.get("/stats",
|
|
15
|
-
Cache.middleware(120),
|
|
16
|
-
controller.stats
|
|
17
|
-
);
|
|
18
|
-
* */
|
|
19
|
-
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();
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
1
|
+
const CACHE = {};
|
|
2
|
+
|
|
3
|
+
setInterval(() => {
|
|
4
|
+
const now = Date.now();
|
|
5
|
+
for (const key in CACHE) {
|
|
6
|
+
if (CACHE[key].expiry <= now) {
|
|
7
|
+
delete CACHE[key];
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}, 300000).unref();
|
|
11
|
+
/**
|
|
12
|
+
* Cache Middleware
|
|
13
|
+
* @example
|
|
14
|
+
* router.get("/stats",
|
|
15
|
+
Cache.middleware(120),
|
|
16
|
+
controller.stats
|
|
17
|
+
);
|
|
18
|
+
* */
|
|
19
|
+
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();
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
45
|
export default Cache;
|
package/core/cli/component.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import Config from "../config.js";
|
|
4
|
-
|
|
5
|
-
const __dirname = Config.dirname();
|
|
6
|
-
|
|
7
|
-
class ComponentCLI {
|
|
8
|
-
/**
|
|
9
|
-
* Create component react
|
|
10
|
-
* @return {void}
|
|
11
|
-
* /
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
create() {
|
|
15
|
-
const folder = path.join(process.cwd(), "frontend/resources/components");
|
|
16
|
-
if (!fs.existsSync(folder)) {
|
|
17
|
-
fs.mkdirSync(folder, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
let nameComponent =`Component-${Math.random().toString(36).substring(7)}`;
|
|
20
|
-
const nameParam = process.argv[2];
|
|
21
|
-
if (nameParam.length > 0 && typeof nameParam === "string") {
|
|
22
|
-
nameComponent = nameParam;
|
|
23
|
-
nameComponent = nameComponent.charAt(0).toUpperCase() + nameComponent.slice(1);
|
|
24
|
-
}
|
|
25
|
-
const filePath = path.join(folder, `${nameComponent}.jsx`);
|
|
26
|
-
const content = `import React from 'react';
|
|
27
|
-
|
|
28
|
-
export default function ${nameComponent}() {
|
|
29
|
-
return (
|
|
30
|
-
<div>
|
|
31
|
-
<h1>${nameComponent} Component</h1>
|
|
32
|
-
</div>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
`;
|
|
36
|
-
fs.writeFileSync(filePath, content);
|
|
37
|
-
console.log(`Component ${nameComponent} created at ${filePath}`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const componentCLI = new ComponentCLI();
|
|
42
|
-
componentCLI.create();
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import Config from "../config.js";
|
|
4
|
+
|
|
5
|
+
const __dirname = Config.dirname();
|
|
6
|
+
|
|
7
|
+
class ComponentCLI {
|
|
8
|
+
/**
|
|
9
|
+
* Create component react
|
|
10
|
+
* @return {void}
|
|
11
|
+
* /
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
create() {
|
|
15
|
+
const folder = path.join(process.cwd(), "frontend/resources/components");
|
|
16
|
+
if (!fs.existsSync(folder)) {
|
|
17
|
+
fs.mkdirSync(folder, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
let nameComponent =`Component-${Math.random().toString(36).substring(7)}`;
|
|
20
|
+
const nameParam = process.argv[2];
|
|
21
|
+
if (nameParam.length > 0 && typeof nameParam === "string") {
|
|
22
|
+
nameComponent = nameParam;
|
|
23
|
+
nameComponent = nameComponent.charAt(0).toUpperCase() + nameComponent.slice(1);
|
|
24
|
+
}
|
|
25
|
+
const filePath = path.join(folder, `${nameComponent}.jsx`);
|
|
26
|
+
const content = `import React from 'react';
|
|
27
|
+
|
|
28
|
+
export default function ${nameComponent}() {
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
<h1>${nameComponent} Component</h1>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
fs.writeFileSync(filePath, content);
|
|
37
|
+
console.log(`Component ${nameComponent} created at ${filePath}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const componentCLI = new ComponentCLI();
|
|
42
|
+
componentCLI.create();
|