@seip/blue-bird 0.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.
@@ -0,0 +1,394 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ import chalk from "chalk";
5
+ import Config from "../config.js";
6
+
7
+ const __dirname = Config.dirname();
8
+ const props = Config.props();
9
+
10
+ /**
11
+ * Scaffolding class for setting up React and Vite in the project.
12
+ */
13
+ class ReactScaffold {
14
+ /**
15
+ * Initializes the Scaffolder with the base application directory.
16
+ */
17
+ constructor() {
18
+ this.appDir = __dirname;
19
+ }
20
+
21
+ /**
22
+ * Executes the installation process.
23
+ */
24
+ async install() {
25
+ console.log(chalk.cyan("Initializing React + Vite setup..."));
26
+
27
+ try {
28
+ this.createStructure();
29
+ this.updatePackageJson();
30
+ this.createViteConfig();
31
+ this.createAppjs();
32
+ this.createMainJs();
33
+ this.createPagesJs();
34
+ this.updateGitIgnore();
35
+ this.npmInstall();
36
+
37
+ console.log(chalk.green("React scaffolding created successfully!"));
38
+ console.log(chalk.cyan("\nNext steps:"));
39
+ console.log(chalk.white(" 1. Run (Blue Bird Server): ") + chalk.bold("npm run dev"));
40
+ console.log(chalk.white(" 2. Run (React Vite Dev): ") + chalk.bold("npm run vite:dev"));
41
+ console.log(chalk.blue("\nBlue Bird React setup completed!"));
42
+ } catch (error) {
43
+ console.error(chalk.red("Fatal error during scaffolding:"), error.message);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Creates the necessary directory structure for React resources.
49
+ */
50
+ createStructure() {
51
+ const dirs = [
52
+ 'frontend/resources/js/pages'
53
+ ];
54
+
55
+ dirs.forEach(dir => {
56
+ const fullPath = path.join(this.appDir, dir);
57
+ if (!fs.existsSync(fullPath)) {
58
+ fs.mkdirSync(fullPath, { recursive: true });
59
+ console.log(chalk.gray(`Created directory: ${dir}`));
60
+ }
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Updates the project's package.json with React and Vite dependencies and scripts.
66
+ */
67
+ updatePackageJson() {
68
+ const packagePath = path.join(this.appDir, 'package.json');
69
+ if (!fs.existsSync(packagePath)) {
70
+ console.warn(chalk.yellow("package.json not found. Initializing with npm init..."));
71
+ execSync('npm init -y', { cwd: this.appDir });
72
+ }
73
+
74
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
75
+
76
+ pkg.scripts = pkg.scripts || {};
77
+ pkg.scripts["vite:dev"] = "vite";
78
+ pkg.scripts["vite:build"] = "vite build";
79
+
80
+ pkg.devDependencies = pkg.devDependencies || {};
81
+ pkg.devDependencies["vite"] = "^5.0.0";
82
+ pkg.devDependencies["@vitejs/plugin-react"] = "^4.2.0";
83
+
84
+ pkg.dependencies = pkg.dependencies || {};
85
+ pkg.dependencies["react"] = "^18.2.0";
86
+ pkg.dependencies["react-dom"] = "^18.2.0";
87
+ pkg.dependencies["react-router-dom"] = "^6.21.0";
88
+
89
+ fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2));
90
+ console.log(chalk.gray("Updated package.json dependencies and scripts."));
91
+ }
92
+
93
+ /**
94
+ * Creates the vite.config.js file with appropriate root and outDir settings.
95
+ */
96
+ createViteConfig() {
97
+ const file = path.join(this.appDir, 'vite.config.js');
98
+ if (fs.existsSync(file)) {
99
+ console.warn(chalk.yellow("vite.config.js already exists. Skipping."));
100
+ return;
101
+ }
102
+
103
+ // We use props.static.path to determine where the build goes
104
+ const outDir = path.join(props.static.path, 'build');
105
+
106
+ const content = `import { defineConfig } from 'vite';
107
+ import react from '@vitejs/plugin-react';
108
+ import path from 'path';
109
+
110
+ export default defineConfig({
111
+ plugins: [react()],
112
+ root: path.resolve(__dirname, 'frontend/resources/js'),
113
+ base: '/build/',
114
+ build: {
115
+ outDir: path.resolve(__dirname, '${outDir.replace(/\\/g, '/')}'),
116
+ emptyOutDir: true,
117
+ manifest: true,
118
+ rollupOptions: {
119
+ input: path.resolve(__dirname, 'frontend/resources/js/main.jsx'),
120
+ },
121
+ },
122
+ server: {
123
+ origin: 'http://localhost:5173',
124
+ strictPort: true,
125
+ cors: true,
126
+ },
127
+ });`;
128
+ fs.writeFileSync(file, content);
129
+ console.log(chalk.gray("Created vite.config.js"));
130
+ }
131
+
132
+ createPagesJs() {
133
+ const file = path.join(this.appDir, 'frontend/resources/js/pages/Home.jsx');
134
+ if (fs.existsSync(file)) {
135
+ console.warn(chalk.yellow("Home.jsx already exists. Skipping."));
136
+ return;
137
+ }
138
+
139
+ const content = `import React, { useEffect } from 'react';
140
+
141
+ export default function Home() {
142
+ useEffect(() => {
143
+ // Example API call to the backend
144
+ fetch("http://localhost:3000/login", {
145
+ method: "POST",
146
+ headers: {
147
+ "Content-Type": "application/json",
148
+ },
149
+ body: JSON.stringify({}),
150
+ })
151
+ .then((response) => response.json())
152
+ .then((data) => console.log('Backend response:', data))
153
+ .catch((error) => console.error('Error fetching from backend:', error));
154
+ }, []);
155
+
156
+ return (
157
+ <div style={{ textAlign: 'center', padding: '4rem 2rem' }}>
158
+ <header style={{ marginBottom: '3rem' }}>
159
+ <h1 style={{
160
+ fontSize: '3.5rem',
161
+ fontWeight: '800',
162
+ background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
163
+ WebkitBackgroundClip: 'text',
164
+ WebkitTextFillColor: 'transparent',
165
+ marginBottom: '1rem'
166
+ }}>
167
+ Welcome to Blue Bird
168
+ </h1>
169
+ <p style={{ fontSize: '1.25rem', color: '#6b7280', maxWidth: '600px', margin: '0 auto' }}>
170
+ The elegant, fast, and weightless framework for modern web development.
171
+ </p>
172
+ </header>
173
+
174
+ <div style={{ display: 'flex', gap: '1rem', justifyContent: 'center', marginBottom: '4rem' }}>
175
+ <a
176
+ href="https://seip25.github.io/Blue-bird/"
177
+ target="_blank"
178
+ rel="noopener noreferrer"
179
+ style={{
180
+ backgroundColor: '#2563eb',
181
+ color: 'white',
182
+ padding: '0.75rem 1.5rem',
183
+ borderRadius: '0.5rem',
184
+ textDecoration: 'none',
185
+ fontWeight: '600',
186
+ transition: 'background-color 0.2s'
187
+ }}
188
+ onMouseOver={(e) => e.target.style.backgroundColor = '#1d4ed8'}
189
+ onMouseOut={(e) => e.target.style.backgroundColor = '#2563eb'}
190
+ >
191
+ Documentation
192
+ </a>
193
+ <a
194
+ href="https://seip25.github.io/Blue-bird/en.html"
195
+ target="_blank"
196
+ rel="noopener noreferrer"
197
+ style={{
198
+ backgroundColor: 'white',
199
+ color: '#374151',
200
+ padding: '0.75rem 1.5rem',
201
+ borderRadius: '0.5rem',
202
+ textDecoration: 'none',
203
+ fontWeight: '600',
204
+ border: '1px solid #d1d5db',
205
+ transition: 'background-color 0.2s'
206
+ }}
207
+ onMouseOver={(e) => e.target.style.backgroundColor = '#f9fafb'}
208
+ onMouseOut={(e) => e.target.style.backgroundColor = 'white'}
209
+ >
210
+ English Docs
211
+ </a>
212
+ </div>
213
+
214
+ <div style={{
215
+ display: 'grid',
216
+ gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
217
+ gap: '2rem',
218
+ maxWidth: '1000px',
219
+ margin: '0 auto'
220
+ }}>
221
+ <div style={cardStyle}>
222
+ <h3>Lightweight</h3>
223
+ <p>Built with performance and simplicity in mind.</p>
224
+ </div>
225
+ <div style={cardStyle}>
226
+ <h3>React Powered</h3>
227
+ <p>Full React + Vite integration with island hydration.</p>
228
+ </div>
229
+ <div style={cardStyle}>
230
+ <h3>Express Backend</h3>
231
+ <p>Robust and scalable backend architecture.</p>
232
+ </div>
233
+ </div>
234
+ </div>
235
+ );
236
+ }
237
+
238
+ const cardStyle = {
239
+ padding: '1.5rem',
240
+ borderRadius: '0.75rem',
241
+ border: '1px solid #e5e7eb',
242
+ textAlign: 'left',
243
+ backgroundColor: 'white',
244
+ boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)'
245
+ };`;
246
+ fs.writeFileSync(file, content);
247
+ console.log(chalk.gray("Created frontend/resources/js/pages/Home.jsx"));
248
+
249
+ const file2 = path.join(this.appDir, 'frontend/resources/js/pages/About.jsx');
250
+ if (fs.existsSync(file2)) {
251
+ console.warn(chalk.yellow("About.jsx already exists. Skipping."));
252
+ return;
253
+ }
254
+
255
+ const content2 = `import React from 'react';
256
+
257
+ export default function About() {
258
+ return (
259
+ <div style={{ padding: '2rem' }}>
260
+ <h1 style={{ color: '#111827', marginBottom: '1rem' }}>About Blue Bird</h1>
261
+ <p style={{ color: '#4b5563', lineHeight: '1.6' }}>
262
+ Blue Bird is a modern framework designed to bridge the gap between backend routing and frontend interactivity.
263
+ It provides a seamless developer experience for building fast, reactive web applications.
264
+ </p>
265
+ </div>
266
+ );
267
+ }`;
268
+ fs.writeFileSync(file2, content2);
269
+ console.log(chalk.gray("Created frontend/resources/js/pages/About.jsx"));
270
+ }
271
+
272
+ /**
273
+ * Creates the main entry point for React (main.jsx) which handles island hydration.
274
+ */
275
+ createAppjs() {
276
+ const file = path.join(this.appDir, 'frontend/resources/js/App.jsx');
277
+ if (fs.existsSync(file)) {
278
+ console.warn(chalk.yellow("App.jsx already exists. Skipping."));
279
+ return;
280
+ }
281
+
282
+ const content = `import React from 'react';
283
+ import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
284
+ import Home from './pages/Home';
285
+ import About from './pages/About';
286
+
287
+ export default function App(props) {
288
+ return (
289
+ <Router>
290
+ <div style={{
291
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
292
+ minHeight: '100vh',
293
+ backgroundColor: '#f9fafb',
294
+ color: '#111827'
295
+ }}>
296
+ <nav style={{
297
+ background: 'white',
298
+ padding: '1rem 2rem',
299
+ display: 'flex',
300
+ justifyContent: 'space-between',
301
+ alignItems: 'center',
302
+ boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1)',
303
+ position: 'sticky',
304
+ top: 0,
305
+ zIndex: 10
306
+ }}>
307
+ <div style={{ fontWeight: 'bold', fontSize: '1.25rem', color: '#2563eb' }}>
308
+ Blue Bird
309
+ </div>
310
+ <div style={{ display: 'flex', gap: '2rem' }}>
311
+ <Link to="/" style={navLinkStyle}>Home</Link>
312
+ <Link to="/about" style={navLinkStyle}>About</Link>
313
+ </div>
314
+ </nav>
315
+
316
+ <main style={{ maxWidth: '1200px', margin: '0 auto' }}>
317
+ {/* Uncomment to debug props if needed */}
318
+ {/* <div style={{ padding: '0.5rem', background: '#ececec', fontSize: '0.75rem' }}>Props: {JSON.stringify(props)}</div> */}
319
+
320
+ <Routes>
321
+ <Route path="/" element={<Home />} />
322
+ <Route path="/about" element={<About />} />
323
+ </Routes>
324
+ </main>
325
+ </div>
326
+ </Router>
327
+ );
328
+ }
329
+
330
+ const navLinkStyle = {
331
+ color: '#4b5563',
332
+ textDecoration: 'none',
333
+ fontWeight: '500',
334
+ fontSize: '0.95rem',
335
+ transition: 'color 0.2s'
336
+ };`;
337
+ fs.writeFileSync(file, content);
338
+ console.log(chalk.gray("Created frontend/resources/js/App.jsx"));
339
+ }
340
+ createMainJs() {
341
+ const file = path.join(this.appDir, 'frontend/resources/js/Main.jsx');
342
+ if (fs.existsSync(file)) {
343
+ console.warn(chalk.yellow("Main.jsx already exists. Skipping."));
344
+ return;
345
+ }
346
+
347
+ const content = `import React from 'react';
348
+ import { createRoot } from 'react-dom/client';
349
+ import App from './App';
350
+
351
+ document.addEventListener('DOMContentLoaded', () => {
352
+ document.querySelectorAll('[data-react-component]').forEach(el => {
353
+ const name = el.dataset.reactComponent;
354
+ const props = JSON.parse(el.dataset.props || '{}');
355
+ createRoot(el).render(<App {...props} />);
356
+ });
357
+ });`;
358
+ fs.writeFileSync(file, content);
359
+ console.log(chalk.gray("Created frontend/resources/js/Main.jsx"));
360
+ }
361
+
362
+
363
+
364
+ /**
365
+ * Ensures node_modules and other build artifacts are ignored by Git.
366
+ */
367
+ updateGitIgnore() {
368
+ const file = path.join(this.appDir, '.gitignore');
369
+ const entry = "\nnode_modules\ndist\nfrontend/public/build\n";
370
+
371
+ if (fs.existsSync(file)) {
372
+ const content = fs.readFileSync(file, 'utf8');
373
+ if (!content.includes('node_modules')) {
374
+ fs.appendFileSync(file, entry);
375
+ console.log(chalk.gray("Updated .gitignore"));
376
+ }
377
+ } else {
378
+ fs.writeFileSync(file, entry);
379
+ console.log(chalk.gray("Created .gitignore"));
380
+ }
381
+ }
382
+ npmInstall() {
383
+ try {
384
+ execSync('npm install', { cwd: this.appDir });
385
+ console.log(chalk.gray("Installed dependencies"));
386
+ } catch (error) {
387
+ console.error(chalk.red("Error installing dependencies:"), error.message);
388
+ }
389
+ }
390
+ }
391
+
392
+
393
+ const scaffold = new ReactScaffold();
394
+ scaffold.install();
package/core/config.js ADDED
@@ -0,0 +1,42 @@
1
+ import path from "path";
2
+
3
+ /**
4
+ * Configuration class to manage application-wide settings and environment variables.
5
+ */
6
+ class Config {
7
+
8
+ /**
9
+ * Returns the base directory of the application.
10
+ * @returns {string} The current working directory.
11
+ */
12
+ static dirname() {
13
+ return process.cwd()
14
+ }
15
+
16
+ /**
17
+ * Retrieves application properties from environment variables or default values.
18
+ * @returns {Object} The configuration properties object.
19
+ */
20
+ static props() {
21
+ const config = {
22
+ debug: process.env.DEBUG === "true" ? true : false,
23
+ descriptionMeta: process.env.DESCRIPTION_META || "",
24
+ keywordsMeta: process.env.KEYWORDS_META || "",
25
+ titleMeta: process.env.TITLE_META || "",
26
+ authorMeta: process.env.AUTHOR_META || "",
27
+ description: process.env.DESCRIPTION || "",
28
+ title: process.env.TITLE || "",
29
+ version: process.env.VERSION || "1.0.0",
30
+ langMeta: process.env.LANGMETA || "en",
31
+ host: process.env.HOST || "http://localhost",
32
+ port: parseInt(process.env.PORT) || 3001,
33
+ static: {
34
+ path: process.env.STATIC_PATH || "frontend/public",
35
+ options: {}
36
+ }
37
+
38
+ }
39
+ return config
40
+ }
41
+ }
42
+ export default Config
package/core/logger.js ADDED
@@ -0,0 +1,80 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import Config from "./config.js"
4
+
5
+ const __dirname = Config.dirname()
6
+
7
+ /**
8
+ * Logger class for managing application logs by creating dated folders and log files.
9
+ */
10
+ class Logger {
11
+
12
+ /**
13
+ * Initializes the Logger instance and ensures the logs directory exists.
14
+ */
15
+ constructor() {
16
+ this.folder = path.join(__dirname, "logs");
17
+ if (!fs.existsSync(this.folder)) {
18
+ fs.mkdirSync(this.folder, { recursive: true });
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Ensures and returns the path to the log folder for the current day.
24
+ * @returns {string} The absolute path to the current day's log folder.
25
+ */
26
+ nowFolder() {
27
+ const folder = path.join(this.folder, this.now());
28
+
29
+ if (!fs.existsSync(folder)) {
30
+ fs.mkdirSync(folder, { recursive: true });
31
+ }
32
+ return folder;
33
+ }
34
+
35
+ /**
36
+ * Gets the current date formatted as YYYY-MM-DD.
37
+ * @returns {string} The formatted date string.
38
+ */
39
+ now() {
40
+ return new Date().toISOString().split("T")[0];
41
+ }
42
+
43
+ /**
44
+ * Appends an informational message to the info.log file.
45
+ * @param {string} message - The message to log.
46
+ */
47
+ info(message) {
48
+ const logFile = path.join(this.nowFolder(), 'info.log');
49
+ fs.appendFileSync(logFile, `${message}\n`);
50
+ }
51
+
52
+ /**
53
+ * Appends an error message to the error.log file.
54
+ * @param {string} message - The error message to log.
55
+ */
56
+ error(message) {
57
+ const logFile = path.join(this.nowFolder(), 'error.log');
58
+ fs.appendFileSync(logFile, `${message}\n`);
59
+ }
60
+
61
+ /**
62
+ * Appends a warning message to the warn.log file.
63
+ * @param {string} message - The warning message to log.
64
+ */
65
+ warning(message) {
66
+ const logFile = path.join(this.nowFolder(), 'warn.log');
67
+ fs.appendFileSync(logFile, `${message}\n`);
68
+ }
69
+
70
+ /**
71
+ * Appends a debug message to the debug.log file.
72
+ * @param {string} message - The debug message to log.
73
+ */
74
+ debug(message) {
75
+ const logFile = path.join(this.nowFolder(), 'debug.log');
76
+ fs.appendFileSync(logFile, `${message}\n`);
77
+ }
78
+ }
79
+
80
+ export default Logger;
@@ -0,0 +1,27 @@
1
+ import Auth from "./auth.js";
2
+
3
+ /**
4
+ * Common middlewares for the Blue Bird framework.
5
+ */
6
+ const Middleware = {
7
+ /**
8
+ * Authentication protection middleware.
9
+ * @type {Function}
10
+ */
11
+ auth: Auth.protect(),
12
+
13
+ /**
14
+ * Web authentication protection middleware (redirects to home if fails).
15
+ * @type {Function}
16
+ */
17
+ webAuth: Auth.protect({ redirect: "/" }),
18
+
19
+ /**
20
+ * Logging middleware (can be extended).
21
+ */
22
+ logger: (req, res, next) => {
23
+ next();
24
+ }
25
+ };
26
+
27
+ export default Middleware;
package/core/router.js ADDED
@@ -0,0 +1,96 @@
1
+ import express from "express";
2
+ import Config from "./config.js";
3
+
4
+ const __dirname = Config.dirname()
5
+ const props = Config.props()
6
+
7
+ /**
8
+ * Router wrapper class for handling Express routing logic.
9
+ */
10
+ class Router {
11
+ /**
12
+ * Creates a new Router instance.
13
+ * @param {string} [path="/"] - The base path for this router.
14
+ */
15
+ constructor(path = "/") {
16
+ this.router = express.Router()
17
+ this.path = path
18
+ }
19
+
20
+ /**
21
+ * Registers a GET route handler.
22
+ * @param {string} path - The relative path for the GET route.
23
+ * @param {...Function} callback - One or more handler functions (middlewares and controller).
24
+ */
25
+ get(path, ...callback) {
26
+ if (path === "/*" || path === "*") {
27
+ path = /.*/
28
+ }
29
+ this.router.get(path, callback)
30
+ }
31
+
32
+ /**
33
+ * Registers a POST route handler.
34
+ * @param {string} path - The relative path for the POST route.
35
+ * @param {...Function} callback - One or more handler functions (middlewares and controller).
36
+ */
37
+ post(path, ...callback) {
38
+ if (path === "/*" || path === "*") {
39
+ path = /.*/
40
+ }
41
+ this.router.post(path, callback)
42
+ }
43
+
44
+ /**
45
+ * Registers a PUT route handler.
46
+ * @param {string} path - The relative path for the PUT route.
47
+ * @param {...Function} callback - One or more handler functions (middlewares and controller).
48
+ */
49
+ put(path, ...callback) {
50
+ this.router.put(path, callback)
51
+ }
52
+
53
+ /**
54
+ * Registers a DELETE route handler.
55
+ * @param {string} path - The relative path for the DELETE route.
56
+ * @param {...Function} callback - One or more handler functions (middlewares and controller).
57
+ */
58
+ delete(path, ...callback) {
59
+ this.router.delete(path, callback)
60
+ }
61
+
62
+ /**
63
+ * Registers a PATCH route handler.
64
+ * @param {string} path - The relative path for the PATCH route.
65
+ * @param {...Function} callback - One or more handler functions (middlewares and controller).
66
+ */
67
+ patch(path, ...callback) {
68
+ this.router.patch(path, callback)
69
+ }
70
+
71
+ /**
72
+ * Registers an OPTIONS route handler.
73
+ * @param {string} path - The relative path for the OPTIONS route.
74
+ * @param {...Function} callback - One or more handler functions (middlewares and controller).
75
+ */
76
+ options(path, ...callback) {
77
+ this.router.options(path, callback)
78
+ }
79
+
80
+ /**
81
+ * Returns the underlying Express router instance.
82
+ * @returns {import('express').Router} The Express router object.
83
+ */
84
+ getRouter() {
85
+ return this.router
86
+ }
87
+
88
+ /**
89
+ * Returns the base path associated with this router.
90
+ * @returns {string} The router path.
91
+ */
92
+ getPath() {
93
+ return this.path
94
+ }
95
+ }
96
+ export default Router;