@seip/blue-bird 0.6.4 → 0.7.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.
- package/.env_example +0 -6
- package/AGENTS.md +41 -156
- package/README.md +46 -130
- package/backend/routes/api.js +21 -17
- package/core/app.js +86 -81
- package/core/cli/init.js +120 -11
- package/core/logger.js +77 -78
- package/core/router.js +2 -6
- package/frontend/astro.config.mjs +35 -0
- package/frontend/public/css/app.css +319 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/src/http/api.js +19 -0
- package/frontend/src/layouts/Layout.astro +20 -0
- package/frontend/src/pages/about.astro +54 -0
- package/frontend/src/pages/index.astro +104 -0
- package/{backend/index.js → index.js} +11 -4
- package/package.json +12 -4
- package/backend/routes/frontend.js +0 -39
- package/core/seo.js +0 -113
- package/core/template.js +0 -319
- package/frontend/public/js/blue-bird.js +0 -1465
- package/frontend/public/js/tailwind.js +0 -8
- package/frontend/templates/about.html +0 -105
- package/frontend/templates/index.html +0 -146
- package/frontend/templates/preact_example.html +0 -80
package/core/app.js
CHANGED
|
@@ -10,8 +10,6 @@ import compression from "compression";
|
|
|
10
10
|
import Config from "./config.js";
|
|
11
11
|
import Logger from "./logger.js";
|
|
12
12
|
import Debug from "./debug.js";
|
|
13
|
-
import Template from "./template.js";
|
|
14
|
-
import SEO from "./seo.js";
|
|
15
13
|
|
|
16
14
|
const __dirname = Config.dirname();
|
|
17
15
|
const props = Config.props();
|
|
@@ -36,7 +34,8 @@ class App {
|
|
|
36
34
|
* @param {boolean} [options.cookieParser=true] - Whether to enable cookie parsing.
|
|
37
35
|
* @param {boolean|Object} [options.rateLimit=false] - Enable global rate limiting.
|
|
38
36
|
* @param {boolean|Object} [options.swagger=false] - Enable swagger.
|
|
39
|
-
* @param {boolean} [options.compression=true] - Enable
|
|
37
|
+
* @param {boolean} [options.compression=true] - Enable compression.
|
|
38
|
+
* @param {boolean} [options.astro=true] - Astro handler.
|
|
40
39
|
* @example
|
|
41
40
|
* const app = new App({
|
|
42
41
|
* routes: [],
|
|
@@ -55,7 +54,14 @@ class App {
|
|
|
55
54
|
* info: { title: "Blue Bird API", version: "1.0.0", description: "API Documentation" },
|
|
56
55
|
* url: "http://localhost:8000"
|
|
57
56
|
* },
|
|
58
|
-
* compression:
|
|
57
|
+
* compression:true,
|
|
58
|
+
* astro: {
|
|
59
|
+
* server: true,
|
|
60
|
+
* serverEntry: "./frontend/dist/server/entry.mjs",
|
|
61
|
+
* client: false,
|
|
62
|
+
* clientDir: "./frontend/dist/client",
|
|
63
|
+
* base: "/"
|
|
64
|
+
* }
|
|
59
65
|
* });
|
|
60
66
|
*/
|
|
61
67
|
constructor(options = {}) {
|
|
@@ -75,6 +81,7 @@ class App {
|
|
|
75
81
|
this.rateLimit = options.rateLimit ?? false;
|
|
76
82
|
this.swagger = options.swagger ?? false;
|
|
77
83
|
this.compression = options.compression ?? true;
|
|
84
|
+
this.astro = options.astro || false;
|
|
78
85
|
this.loggerInstance = new Logger();
|
|
79
86
|
/** @type {Set<import('http').ServerResponse>} */
|
|
80
87
|
this._hotReloadClients = new Set();
|
|
@@ -121,13 +128,16 @@ class App {
|
|
|
121
128
|
|
|
122
129
|
if (this.static.path)
|
|
123
130
|
this.app.use(
|
|
124
|
-
express.static(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
res.setHeader("X-Powered-By", "Blue Bird");
|
|
128
|
-
res.setHeader(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
express.static(path.join(__dirname, this.static.path), {
|
|
132
|
+
...this.static.options,
|
|
133
|
+
setHeaders: (res) => {
|
|
134
|
+
res.setHeader("X-Powered-By", "Blue Bird");
|
|
135
|
+
res.setHeader(
|
|
136
|
+
"Cache-Control",
|
|
137
|
+
"public, max-age=31536000, immutable",
|
|
138
|
+
);
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
131
141
|
);
|
|
132
142
|
|
|
133
143
|
this.app.use(cors(this.cors));
|
|
@@ -165,13 +175,12 @@ class App {
|
|
|
165
175
|
if (this.logger || props.debug) this._middlewareLogger(this.logger);
|
|
166
176
|
|
|
167
177
|
this.app.use((req, res, next) => {
|
|
168
|
-
res.setHeader("X-Powered-By", "Blue Bird");
|
|
169
|
-
|
|
178
|
+
res.setHeader("X-Powered-By", "Blue Bird");
|
|
179
|
+
next();
|
|
170
180
|
});
|
|
171
181
|
|
|
172
182
|
if (props.debug) {
|
|
173
183
|
Debug.middlewareMetrics(this.app);
|
|
174
|
-
this._setupHotReload();
|
|
175
184
|
}
|
|
176
185
|
|
|
177
186
|
if (this.swagger) {
|
|
@@ -196,70 +205,65 @@ class App {
|
|
|
196
205
|
|
|
197
206
|
this._dispatchRoutes();
|
|
198
207
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
208
|
+
if (this.astro) {
|
|
209
|
+
const defaultAstro = {
|
|
210
|
+
server: true,
|
|
211
|
+
serverEntry: "./frontend/dist/server/entry.mjs",
|
|
212
|
+
client: false,
|
|
213
|
+
clientDir: "./frontend/dist/client",
|
|
214
|
+
base: "/",
|
|
215
|
+
};
|
|
216
|
+
const astroConfig =
|
|
217
|
+
typeof this.astro === "object"
|
|
218
|
+
? { ...defaultAstro, ...this.astro }
|
|
219
|
+
: { ...defaultAstro };
|
|
220
|
+
|
|
221
|
+
if (astroConfig.client) {
|
|
222
|
+
const clientPath = path.resolve(astroConfig.clientDir);
|
|
223
|
+
if (fs.existsSync(clientPath)) {
|
|
224
|
+
this.app.use(astroConfig.base, express.static(clientPath));
|
|
225
|
+
console.log(
|
|
226
|
+
chalk.green(
|
|
227
|
+
`[OK] Astro Static Client Assets registered at ${astroConfig.base}`,
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
} else {
|
|
231
|
+
console.warn(
|
|
232
|
+
chalk.yellow(
|
|
233
|
+
`[WARN] Astro client directory not found at: ${clientPath}`,
|
|
234
|
+
),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
225
237
|
}
|
|
226
|
-
this._hotReloadClients.add(res);
|
|
227
|
-
req.on("close", () => {
|
|
228
|
-
this._hotReloadClients.delete(res);
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
238
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
if (astroConfig.server) {
|
|
240
|
+
const entryPath = path.resolve(astroConfig.serverEntry);
|
|
241
|
+
if (fs.existsSync(entryPath)) {
|
|
242
|
+
try {
|
|
243
|
+
const { handler: ssrHandler } = await import(entryPath);
|
|
244
|
+
this.app.use(ssrHandler);
|
|
245
|
+
console.log(
|
|
246
|
+
chalk.green("[OK] Astro SSR Handler registered successfully."),
|
|
247
|
+
);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error(
|
|
250
|
+
chalk.red("[ERROR] Failed to load Astro SSR Handler:"),
|
|
251
|
+
error.message,
|
|
252
|
+
);
|
|
241
253
|
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
fs.watch(frontendPath, { recursive: true }, (eventType, filename) => {
|
|
250
|
-
if (!filename) return;
|
|
251
|
-
if (/\.(html|css|js)$/i.test(filename)) {
|
|
252
|
-
if (debounceTimer) clearTimeout(debounceTimer);
|
|
253
|
-
debounceTimer = setTimeout(() => {
|
|
254
|
-
console.log(chalk.magenta(`[Hot Reload] ${filename} changed`));
|
|
255
|
-
Template.clearCache();
|
|
256
|
-
notifyClients();
|
|
257
|
-
}, 200);
|
|
254
|
+
} else {
|
|
255
|
+
console.warn(
|
|
256
|
+
chalk.yellow(
|
|
257
|
+
`[WARN] Astro build entrypoint not found at: ${entryPath}`,
|
|
258
|
+
),
|
|
259
|
+
);
|
|
258
260
|
}
|
|
259
|
-
}
|
|
260
|
-
} catch (_) {
|
|
261
|
-
console.log(chalk.yellow("[Hot Reload] Could not watch frontend/ directory"));
|
|
261
|
+
}
|
|
262
262
|
}
|
|
263
|
+
|
|
264
|
+
if (this.notFound) this._notFoundDefault();
|
|
265
|
+
|
|
266
|
+
this._errorHandler();
|
|
263
267
|
}
|
|
264
268
|
|
|
265
269
|
/**
|
|
@@ -279,6 +283,7 @@ class App {
|
|
|
279
283
|
Object.keys(req.params).length > 0
|
|
280
284
|
? ` ${JSON.stringify(req.params)}`
|
|
281
285
|
: "";
|
|
286
|
+
|
|
282
287
|
const ip = req.ip;
|
|
283
288
|
const now = new Date().toISOString();
|
|
284
289
|
const time = `${now.split("T")[0]} ${now.split("T")[1].split(".")[0]}`;
|
|
@@ -357,14 +362,14 @@ class App {
|
|
|
357
362
|
this.app.listen(this.port, () => {
|
|
358
363
|
console.log(
|
|
359
364
|
chalk.bold.blue("Blue Bird Server Online\n") +
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
chalk.bold.cyan("App URL: ") +
|
|
366
|
+
chalk.green(`${this.appUrl}`) +
|
|
367
|
+
"\n" +
|
|
368
|
+
chalk.bold.cyan("Internal: ") +
|
|
369
|
+
chalk.green(`${this.host}:${this.port}`) +
|
|
370
|
+
"\n" +
|
|
371
|
+
(props.debug ? chalk.bold.magenta("Hot Reload: enabled\n") : "") +
|
|
372
|
+
chalk.gray("────────────────────────────────"),
|
|
368
373
|
);
|
|
369
374
|
});
|
|
370
375
|
})
|
package/core/cli/init.js
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
+
import readline from "node:readline/promises";
|
|
7
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
8
|
+
import crypto from "node:crypto";
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Initializes a new Blue Bird project by copying the base structure.
|
|
@@ -19,13 +23,63 @@ class ProjectInit {
|
|
|
19
23
|
async run() {
|
|
20
24
|
console.log(chalk.cyan("Starting Blue Bird project initialization..."));
|
|
21
25
|
|
|
26
|
+
const rl = readline.createInterface({ input, output });
|
|
27
|
+
|
|
28
|
+
let title = "Blue-Bird";
|
|
29
|
+
let port = 3000;
|
|
30
|
+
let appUrl = "http://localhost:3000";
|
|
31
|
+
let useMysql = false;
|
|
32
|
+
let dbName = "blue_bird";
|
|
33
|
+
let dbUser = "root";
|
|
34
|
+
let dbPassword = "root";
|
|
35
|
+
let dbPort = 3306;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const ask = async (query, defaultValue) => {
|
|
39
|
+
const formattedQuery = defaultValue !== undefined ? `${query} [${defaultValue}]: ` : `${query}: `;
|
|
40
|
+
const answer = await rl.question(formattedQuery);
|
|
41
|
+
return answer.trim() || defaultValue;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
title = await ask("Project Title", title);
|
|
45
|
+
const portInput = await ask("Server Port", port);
|
|
46
|
+
port = parseInt(portInput, 10);
|
|
47
|
+
if (Number.isNaN(port)) {
|
|
48
|
+
port = 3000;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const defaultAppUrl = `http://localhost:${port}`;
|
|
52
|
+
appUrl = await ask("Application URL", defaultAppUrl);
|
|
53
|
+
|
|
54
|
+
const mysqlAns = await ask("Do you want to configure MySQL? (y/n)", "n");
|
|
55
|
+
useMysql = mysqlAns.toLowerCase() === "y" || mysqlAns.toLowerCase() === "yes";
|
|
56
|
+
|
|
57
|
+
if (useMysql) {
|
|
58
|
+
dbName = await ask("Database Name", dbName);
|
|
59
|
+
dbUser = await ask("Database User", dbUser);
|
|
60
|
+
dbPassword = await ask("Database Password", dbPassword);
|
|
61
|
+
const dbPortInput = await ask("Database Port", dbPort);
|
|
62
|
+
dbPort = parseInt(dbPortInput, 10);
|
|
63
|
+
if (Number.isNaN(dbPort)) {
|
|
64
|
+
dbPort = 3306;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(chalk.red("[ERROR] Error reading configuration input:"), error.message);
|
|
69
|
+
rl.close();
|
|
70
|
+
return;
|
|
71
|
+
} finally {
|
|
72
|
+
rl.close();
|
|
73
|
+
}
|
|
74
|
+
|
|
22
75
|
const itemsToCopy = [
|
|
23
76
|
"backend",
|
|
24
77
|
"frontend",
|
|
25
78
|
"docker",
|
|
26
79
|
"docker-compose.yml",
|
|
27
80
|
".env_example",
|
|
28
|
-
"AGENTS.md"
|
|
81
|
+
"AGENTS.md",
|
|
82
|
+
"index.js"
|
|
29
83
|
];
|
|
30
84
|
|
|
31
85
|
try {
|
|
@@ -36,31 +90,78 @@ class ProjectInit {
|
|
|
36
90
|
if (fs.existsSync(src)) {
|
|
37
91
|
if (!fs.existsSync(dest)) {
|
|
38
92
|
this.copyRecursive(src, dest);
|
|
39
|
-
console.log(chalk.green(
|
|
93
|
+
console.log(chalk.green(`[OK] Copied ${item} to root.`));
|
|
40
94
|
} else {
|
|
41
|
-
console.log(chalk.yellow(
|
|
95
|
+
console.log(chalk.yellow(`[SKIP] ${item} already exists, skipping.`));
|
|
42
96
|
}
|
|
43
97
|
} else {
|
|
44
|
-
console.warn(chalk.red(
|
|
98
|
+
console.warn(chalk.red(`[ERROR] Source ${item} not found in ${this.sourceDir}`));
|
|
45
99
|
}
|
|
46
100
|
});
|
|
47
101
|
|
|
48
102
|
const envPath = path.join(this.appDir, ".env");
|
|
49
103
|
const envExamplePath = path.join(this.appDir, ".env_example");
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
104
|
+
|
|
105
|
+
if (fs.existsSync(envExamplePath)) {
|
|
106
|
+
let envContent = fs.readFileSync(envExamplePath, "utf-8");
|
|
107
|
+
|
|
108
|
+
const jwtSecret = crypto.randomBytes(32).toString("hex");
|
|
109
|
+
|
|
110
|
+
const updates = {
|
|
111
|
+
TITLE: title,
|
|
112
|
+
PORT: port,
|
|
113
|
+
APP_URL: appUrl,
|
|
114
|
+
JWT_SECRET: jwtSecret,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (useMysql) {
|
|
118
|
+
updates.DB_NAME = dbName;
|
|
119
|
+
updates.DB_USER = dbUser;
|
|
120
|
+
updates.DB_PASSWORD = dbPassword;
|
|
121
|
+
updates.DB_PORT = dbPort;
|
|
122
|
+
updates.DATABASE_URL = `mysql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const lines = envContent.split(/\r?\n/);
|
|
126
|
+
const updatedLines = lines.map(line => {
|
|
127
|
+
const match = line.match(/^([A-Z_]+)=(.+)/);
|
|
128
|
+
if (match) {
|
|
129
|
+
const key = match[1];
|
|
130
|
+
if (updates[key] !== undefined) {
|
|
131
|
+
const value = updates[key];
|
|
132
|
+
if (typeof value === "string" && !value.startsWith('"')) {
|
|
133
|
+
return `${key}="${value}"`;
|
|
134
|
+
}
|
|
135
|
+
return `${key}=${value}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return line;
|
|
139
|
+
});
|
|
140
|
+
envContent = updatedLines.join("\n");
|
|
141
|
+
|
|
142
|
+
fs.writeFileSync(envPath, envContent, "utf-8");
|
|
143
|
+
console.log(chalk.green("[OK] Created and configured .env file."));
|
|
53
144
|
}
|
|
54
145
|
|
|
55
146
|
this.updatePackageJson();
|
|
56
147
|
|
|
148
|
+
if (useMysql) {
|
|
149
|
+
console.log(chalk.cyan("[INFO] Installing mysql2 and redis packages..."));
|
|
150
|
+
try {
|
|
151
|
+
execSync("npm install mysql2 redis", { stdio: "inherit", cwd: this.appDir });
|
|
152
|
+
console.log(chalk.green("[OK] Successfully installed mysql2 and redis."));
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.warn(chalk.yellow("[ERROR] Automatic package installation failed. Please run 'npm install mysql2 redis' manually."));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
57
158
|
console.log(chalk.blue("\nBlue Bird initialization completed!"));
|
|
58
159
|
console.log(chalk.white("Next steps:"));
|
|
59
160
|
console.log(chalk.bold(" npm install"));
|
|
60
161
|
console.log(chalk.bold(" npm run dev"));
|
|
61
162
|
|
|
62
163
|
} catch (error) {
|
|
63
|
-
console.error(chalk.red("Error during initialization:"), error.message);
|
|
164
|
+
console.error(chalk.red("[ERROR] Error during initialization:"), error.message);
|
|
64
165
|
}
|
|
65
166
|
}
|
|
66
167
|
|
|
@@ -74,8 +175,11 @@ class ProjectInit {
|
|
|
74
175
|
pkg.scripts = pkg.scripts || {};
|
|
75
176
|
|
|
76
177
|
const scriptsToAdd = {
|
|
77
|
-
"dev": "node --watch --env-file=.env
|
|
78
|
-
"
|
|
178
|
+
"dev": "node --watch --env-file=.env index.js",
|
|
179
|
+
"dev:astro": "astro dev --root frontend",
|
|
180
|
+
"dev:api": "node --watch --env-file=.env index.js",
|
|
181
|
+
"start": "node --env-file=.env index.js",
|
|
182
|
+
"build": "astro build --root frontend",
|
|
79
183
|
"init": "blue-bird",
|
|
80
184
|
"route": "blue-bird route",
|
|
81
185
|
"swagger-install": "blue-bird swagger-install",
|
|
@@ -90,9 +194,14 @@ class ProjectInit {
|
|
|
90
194
|
}
|
|
91
195
|
}
|
|
92
196
|
|
|
197
|
+
if (pkg.type !== "module") {
|
|
198
|
+
pkg.type = "module";
|
|
199
|
+
updated = true;
|
|
200
|
+
}
|
|
201
|
+
|
|
93
202
|
if (updated) {
|
|
94
203
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
95
|
-
console.log(chalk.green("
|
|
204
|
+
console.log(chalk.green("[OK] Updated package.json configuration."));
|
|
96
205
|
}
|
|
97
206
|
}
|
|
98
207
|
}
|
package/core/logger.js
CHANGED
|
@@ -1,100 +1,99 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import Config from "./config.js"
|
|
3
|
+
import Config from "./config.js";
|
|
4
4
|
|
|
5
|
-
const __dirname = Config.dirname()
|
|
5
|
+
const __dirname = Config.dirname();
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Logger class for managing application logs by creating dated folders and log files.
|
|
9
9
|
*/
|
|
10
10
|
class Logger {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
fs.mkdirSync(this.folder, { recursive: true });
|
|
21
|
-
}
|
|
11
|
+
/**
|
|
12
|
+
* Initializes the Logger instance and ensures the logs directory exists.
|
|
13
|
+
*/
|
|
14
|
+
constructor() {
|
|
15
|
+
this.folder = path.join(__dirname, "backend", "logs");
|
|
16
|
+
this._currentDay = null;
|
|
17
|
+
this._currentDayFolder = null;
|
|
18
|
+
if (!fs.existsSync(this.folder)) {
|
|
19
|
+
fs.mkdirSync(this.folder, { recursive: true });
|
|
22
20
|
}
|
|
21
|
+
}
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (this._currentDay === today && this._currentDayFolder) {
|
|
33
|
-
return this._currentDayFolder;
|
|
34
|
-
}
|
|
23
|
+
/**
|
|
24
|
+
* Ensures and returns the path to the log folder for the current day.
|
|
25
|
+
* Caches the folder path for the current day to avoid repeated fs checks.
|
|
26
|
+
* @returns {string} The absolute path to the current day's log folder.
|
|
27
|
+
*/
|
|
28
|
+
nowFolder() {
|
|
29
|
+
const today = this.now();
|
|
35
30
|
|
|
36
|
-
|
|
31
|
+
if (this._currentDay === today && this._currentDayFolder) {
|
|
32
|
+
return this._currentDayFolder;
|
|
33
|
+
}
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
fs.mkdirSync(folder, { recursive: true });
|
|
40
|
-
}
|
|
35
|
+
const folder = path.join(this.folder, today);
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return folder;
|
|
37
|
+
if (!fs.existsSync(folder)) {
|
|
38
|
+
fs.mkdirSync(folder, { recursive: true });
|
|
45
39
|
}
|
|
46
40
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
now() {
|
|
52
|
-
return new Date().toISOString().split("T")[0];
|
|
53
|
-
}
|
|
41
|
+
this._currentDay = today;
|
|
42
|
+
this._currentDayFolder = folder;
|
|
43
|
+
return folder;
|
|
44
|
+
}
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (err) console.error('Logger write error:', err.message);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
46
|
+
/**
|
|
47
|
+
* Gets the current date formatted as YYYY-MM-DD.
|
|
48
|
+
* @returns {string} The formatted date string.
|
|
49
|
+
*/
|
|
50
|
+
now() {
|
|
51
|
+
return new Date().toISOString().split("T")[0];
|
|
52
|
+
}
|
|
65
53
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Appends an informational message to the info.log file (non-blocking).
|
|
56
|
+
* @param {string} message - The message to log.
|
|
57
|
+
*/
|
|
58
|
+
info(message) {
|
|
59
|
+
const logFile = path.join(this.nowFolder(), "info.log");
|
|
60
|
+
fs.appendFile(logFile, `${message}\n`, (err) => {
|
|
61
|
+
if (err) console.error("Logger write error:", err.message);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
76
64
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Appends an error message to the error.log file (non-blocking).
|
|
67
|
+
* @param {string} message - The error message to log.
|
|
68
|
+
*/
|
|
69
|
+
error(message) {
|
|
70
|
+
const logFile = path.join(this.nowFolder(), "error.log");
|
|
71
|
+
fs.appendFile(logFile, `${message}\n`, (err) => {
|
|
72
|
+
if (err) console.error("Logger write error:", err.message);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
87
75
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Appends a warning message to the warn.log file (non-blocking).
|
|
78
|
+
* @param {string} message - The warning message to log.
|
|
79
|
+
*/
|
|
80
|
+
warning(message) {
|
|
81
|
+
const logFile = path.join(this.nowFolder(), "warn.log");
|
|
82
|
+
fs.appendFile(logFile, `${message}\n`, (err) => {
|
|
83
|
+
if (err) console.error("Logger write error:", err.message);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Appends a debug message to the debug.log file (non-blocking).
|
|
89
|
+
* @param {string} message - The debug message to log.
|
|
90
|
+
*/
|
|
91
|
+
debug(message) {
|
|
92
|
+
const logFile = path.join(this.nowFolder(), "debug.log");
|
|
93
|
+
fs.appendFile(logFile, `${message}\n`, (err) => {
|
|
94
|
+
if (err) console.error("Logger write error:", err.message);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
export default Logger;
|
package/core/router.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import express from "express";
|
|
2
2
|
import Config from "./config.js";
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
const props = Config.props();
|
|
6
6
|
|
|
@@ -57,11 +57,7 @@ class Router {
|
|
|
57
57
|
if (path === "/*" || path === "*") {
|
|
58
58
|
path = /.*/;
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
const fullPath = this.path === "/" ? path : `${this.path}${path}`;
|
|
62
|
-
const normalizedPath = fullPath === "//" ? "/" : fullPath.replace(/\/+/g, "/");
|
|
63
|
-
SEO.addRoute(normalizedPath, this._languages);
|
|
64
|
-
}
|
|
60
|
+
|
|
65
61
|
this.router.get(path, callback);
|
|
66
62
|
}
|
|
67
63
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineConfig } from 'astro/config';
|
|
2
|
+
import node from '@astrojs/node';
|
|
3
|
+
import { loadEnv } from 'vite';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, resolve } from 'path';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
const env = loadEnv(
|
|
10
|
+
process.env.NODE_ENV ?? 'development',
|
|
11
|
+
resolve(__dirname, '..'),
|
|
12
|
+
'',
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const apiPort = env.PORT || '3000';
|
|
16
|
+
const apiHost = (env.HOST || 'localhost').replace(/^["']|["']$/g, '');
|
|
17
|
+
const apiTarget = `http://${apiHost}:${apiPort}`;
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
output: 'server',
|
|
21
|
+
adapter: node({
|
|
22
|
+
mode: 'middleware',
|
|
23
|
+
}),
|
|
24
|
+
vite: {
|
|
25
|
+
envDir: resolve(__dirname, '..'),
|
|
26
|
+
server: {
|
|
27
|
+
proxy: {
|
|
28
|
+
'/api': {
|
|
29
|
+
target: apiTarget,
|
|
30
|
+
changeOrigin: true,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|