@seip/blue-bird 1.3.0 → 1.3.2
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/core/cli/docker.js +32 -4
- package/core/cli/doctor.js +11 -1
- package/core/cli/init.js +106 -102
- package/core/database.js +39 -1
- package/frontend/about.html +1 -0
- package/frontend/index.html +1 -0
- package/package.json +1 -1
package/core/cli/docker.js
CHANGED
|
@@ -33,16 +33,44 @@ function getEnvVars() {
|
|
|
33
33
|
* @returns {string} 'sqlite', 'postgres', 'mysql', or 'none'.
|
|
34
34
|
*/
|
|
35
35
|
function getDbType(env = getEnvVars()) {
|
|
36
|
-
|
|
36
|
+
const dbTypeRaw = (env.DB_TYPE || "").toLowerCase().trim();
|
|
37
|
+
if (
|
|
38
|
+
dbTypeRaw === "sqlite" ||
|
|
39
|
+
dbTypeRaw === "sqlite3" ||
|
|
40
|
+
dbTypeRaw === "sql" ||
|
|
41
|
+
dbTypeRaw === "better-sqlite3" ||
|
|
42
|
+
dbTypeRaw === "better-sqlite" ||
|
|
43
|
+
dbTypeRaw === "lite"
|
|
44
|
+
) {
|
|
37
45
|
return "sqlite";
|
|
38
46
|
}
|
|
39
|
-
if (
|
|
47
|
+
if (
|
|
48
|
+
dbTypeRaw === "postgres" ||
|
|
49
|
+
dbTypeRaw === "postgresql" ||
|
|
50
|
+
dbTypeRaw === "pg" ||
|
|
51
|
+
dbTypeRaw === "psql" ||
|
|
52
|
+
dbTypeRaw === "pgsql" ||
|
|
53
|
+
dbTypeRaw === "postgre" ||
|
|
54
|
+
dbTypeRaw === "postgr" ||
|
|
55
|
+
dbTypeRaw === "psgr"
|
|
56
|
+
) {
|
|
40
57
|
return "postgres";
|
|
41
58
|
}
|
|
42
|
-
if (
|
|
59
|
+
if (
|
|
60
|
+
dbTypeRaw === "mysql" ||
|
|
61
|
+
dbTypeRaw === "mariadb" ||
|
|
62
|
+
dbTypeRaw === "maria" ||
|
|
63
|
+
dbTypeRaw === "my"
|
|
64
|
+
) {
|
|
43
65
|
return "mysql";
|
|
44
66
|
}
|
|
45
|
-
if (
|
|
67
|
+
if (
|
|
68
|
+
dbTypeRaw === "none" ||
|
|
69
|
+
dbTypeRaw === "no" ||
|
|
70
|
+
dbTypeRaw === "false" ||
|
|
71
|
+
dbTypeRaw === "null" ||
|
|
72
|
+
dbTypeRaw === "0"
|
|
73
|
+
) {
|
|
46
74
|
return "none";
|
|
47
75
|
}
|
|
48
76
|
if (env.DATABASE_URL && !env.DATABASE_URL.startsWith("#")) {
|
package/core/cli/doctor.js
CHANGED
|
@@ -268,7 +268,17 @@ export async function runDoctor() {
|
|
|
268
268
|
// 5. Database & Cache Driver Status
|
|
269
269
|
// -------------------------------------------------------------
|
|
270
270
|
console.log(chalk.bold("[5/5] Database & Cache Architecture:"));
|
|
271
|
-
const
|
|
271
|
+
const rawDbType = (env.DB_TYPE || "").toLowerCase().trim();
|
|
272
|
+
let dbType = "sqlite";
|
|
273
|
+
if (["sqlite", "sqlite3", "sql", "better-sqlite3", "better-sqlite", "lite"].includes(rawDbType)) {
|
|
274
|
+
dbType = "sqlite";
|
|
275
|
+
} else if (["postgres", "postgresql", "pg", "psql", "pgsql", "postgre", "postgr", "psgr"].includes(rawDbType)) {
|
|
276
|
+
dbType = "postgres";
|
|
277
|
+
} else if (["mysql", "mariadb", "maria", "my"].includes(rawDbType)) {
|
|
278
|
+
dbType = "mysql";
|
|
279
|
+
} else if (["none", "no", "false", "null", "0"].includes(rawDbType)) {
|
|
280
|
+
dbType = "none";
|
|
281
|
+
}
|
|
272
282
|
console.log(chalk.green(` [INFO] Database Type: ${dbType.toUpperCase()}`));
|
|
273
283
|
const cacheMode = (env.CACHE_MODE || "memory").toLowerCase();
|
|
274
284
|
console.log(chalk.green(` [INFO] Cache Mode: ${cacheMode.toUpperCase()}`));
|
package/core/cli/init.js
CHANGED
|
@@ -62,21 +62,47 @@ class ProjectInit {
|
|
|
62
62
|
const cleanDbTypeAns = dbTypeAns.toLowerCase().trim();
|
|
63
63
|
if (
|
|
64
64
|
cleanDbTypeAns === "sqlite" ||
|
|
65
|
+
cleanDbTypeAns === "sqlite3" ||
|
|
65
66
|
cleanDbTypeAns === "sql" ||
|
|
66
|
-
cleanDbTypeAns === "sqlite3"
|
|
67
|
+
cleanDbTypeAns === "better-sqlite3" ||
|
|
68
|
+
cleanDbTypeAns === "better-sqlite" ||
|
|
69
|
+
cleanDbTypeAns === "lite"
|
|
67
70
|
) {
|
|
68
71
|
dbType = "sqlite";
|
|
69
72
|
} else if (
|
|
70
73
|
cleanDbTypeAns === "postgres" ||
|
|
71
74
|
cleanDbTypeAns === "postgresql" ||
|
|
72
75
|
cleanDbTypeAns === "pg" ||
|
|
73
|
-
cleanDbTypeAns === "
|
|
76
|
+
cleanDbTypeAns === "psql" ||
|
|
77
|
+
cleanDbTypeAns === "pgsql" ||
|
|
78
|
+
cleanDbTypeAns === "postgre" ||
|
|
79
|
+
cleanDbTypeAns === "postgr" ||
|
|
80
|
+
cleanDbTypeAns === "psgr"
|
|
74
81
|
) {
|
|
75
82
|
dbType = "postgres";
|
|
76
|
-
} else if (
|
|
83
|
+
} else if (
|
|
84
|
+
cleanDbTypeAns === "mysql" ||
|
|
85
|
+
cleanDbTypeAns === "mariadb" ||
|
|
86
|
+
cleanDbTypeAns === "maria" ||
|
|
87
|
+
cleanDbTypeAns === "my"
|
|
88
|
+
) {
|
|
77
89
|
dbType = "mysql";
|
|
78
|
-
} else
|
|
90
|
+
} else if (
|
|
91
|
+
cleanDbTypeAns === "none" ||
|
|
92
|
+
cleanDbTypeAns === "no" ||
|
|
93
|
+
cleanDbTypeAns === "null" ||
|
|
94
|
+
cleanDbTypeAns === "false" ||
|
|
95
|
+
cleanDbTypeAns === "0" ||
|
|
96
|
+
cleanDbTypeAns === "n"
|
|
97
|
+
) {
|
|
79
98
|
dbType = "none";
|
|
99
|
+
} else {
|
|
100
|
+
console.log(
|
|
101
|
+
chalk.yellow(
|
|
102
|
+
`[WARN] Unknown database '${dbTypeAns}'. Defaulting to 'sqlite'.`,
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
dbType = "sqlite";
|
|
80
106
|
}
|
|
81
107
|
|
|
82
108
|
if (dbType === "sqlite") {
|
|
@@ -87,14 +113,21 @@ class ProjectInit {
|
|
|
87
113
|
if (!fs.existsSync(dbDir)) {
|
|
88
114
|
fs.mkdirSync(dbDir, { recursive: true });
|
|
89
115
|
}
|
|
90
|
-
} else if (dbType
|
|
116
|
+
} else if (dbType === "mysql") {
|
|
91
117
|
dbName = await ask("Database Name", dbName);
|
|
92
|
-
dbUser = await ask(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
dbUser = await ask("Database User", "root");
|
|
119
|
+
dbPassword = await ask("Database Password", "root");
|
|
120
|
+
const defaultDbPort = 3306;
|
|
121
|
+
const dbPortInput = await ask("Database Port", defaultDbPort);
|
|
122
|
+
dbPort = parseInt(dbPortInput, 10);
|
|
123
|
+
if (Number.isNaN(dbPort)) {
|
|
124
|
+
dbPort = defaultDbPort;
|
|
125
|
+
}
|
|
126
|
+
} else if (dbType === "postgres") {
|
|
127
|
+
dbName = await ask("Database Name", dbName);
|
|
128
|
+
dbUser = await ask("Database User", "postgres");
|
|
129
|
+
dbPassword = await ask("Database Password", "root");
|
|
130
|
+
const defaultDbPort = 5432;
|
|
98
131
|
const dbPortInput = await ask("Database Port", defaultDbPort);
|
|
99
132
|
dbPort = parseInt(dbPortInput, 10);
|
|
100
133
|
if (Number.isNaN(dbPort)) {
|
|
@@ -176,106 +209,70 @@ class ProjectInit {
|
|
|
176
209
|
);
|
|
177
210
|
const composeDest = path.join(this.appDir, "docker-compose.yml");
|
|
178
211
|
if (fs.existsSync(composeSrc)) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
);
|
|
184
|
-
} else {
|
|
185
|
-
console.log(
|
|
186
|
-
chalk.yellow(`[SKIP] docker-compose.yml already exists, skipping.`),
|
|
187
|
-
);
|
|
188
|
-
}
|
|
212
|
+
fs.copyFileSync(composeSrc, composeDest);
|
|
213
|
+
console.log(
|
|
214
|
+
chalk.green(`[OK] Created docker-compose.yml (${dbType} mode).`),
|
|
215
|
+
);
|
|
189
216
|
} else {
|
|
190
217
|
const fallbackSrc = path.join(this.sourceDir, "docker-compose.yml");
|
|
191
|
-
if (fs.existsSync(fallbackSrc)
|
|
218
|
+
if (fs.existsSync(fallbackSrc)) {
|
|
192
219
|
fs.copyFileSync(fallbackSrc, composeDest);
|
|
193
220
|
console.log(chalk.green(`[OK] Created docker-compose.yml.`));
|
|
194
221
|
}
|
|
195
222
|
}
|
|
196
223
|
|
|
197
224
|
const envPath = path.join(this.appDir, ".env");
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
JWT_SECRET: jwtSecret,
|
|
217
|
-
DB_TYPE: dbType,
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
if (dbType === "sqlite") {
|
|
221
|
-
updates.DB_TYPE = "sqlite";
|
|
222
|
-
updates.DB_FILE = dbFile;
|
|
223
|
-
updates.DATABASE_URL = `sqlite:${dbFile}`;
|
|
224
|
-
} else if (dbType === "mysql") {
|
|
225
|
-
updates.DB_TYPE = "mysql";
|
|
226
|
-
updates.DB_NAME = dbName;
|
|
227
|
-
updates.DB_USER = dbUser;
|
|
228
|
-
updates.DB_PASSWORD = dbPassword;
|
|
229
|
-
updates.DB_PORT = dbPort;
|
|
230
|
-
updates.DATABASE_URL = `mysql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}`;
|
|
231
|
-
} else if (dbType === "postgres") {
|
|
232
|
-
updates.DB_TYPE = "postgres";
|
|
233
|
-
updates.DB_NAME = dbName;
|
|
234
|
-
updates.DB_USER = dbUser;
|
|
235
|
-
updates.DB_PASSWORD = dbPassword;
|
|
236
|
-
updates.DB_PORT = dbPort;
|
|
237
|
-
updates.DATABASE_URL = `postgresql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}?schema=public`;
|
|
238
|
-
}
|
|
225
|
+
const jwtSecret = crypto.randomBytes(32).toString("hex");
|
|
226
|
+
const composeProjectName =
|
|
227
|
+
title
|
|
228
|
+
.toLowerCase()
|
|
229
|
+
.trim()
|
|
230
|
+
.replace(/\s+/g, "-")
|
|
231
|
+
.replace(/[^a-z0-9_-]/g, "") || "blue-bird";
|
|
232
|
+
|
|
233
|
+
let dbEnvBlock = "";
|
|
234
|
+
if (dbType === "sqlite") {
|
|
235
|
+
dbEnvBlock = `# Database Configuration\n# SQLite (Default - uses WAL mode and busy timeout automatically)\nDB_TYPE="sqlite"\nDB_FILE="${dbFile}"\nDATABASE_URL="sqlite:${dbFile}"`;
|
|
236
|
+
} else if (dbType === "mysql") {
|
|
237
|
+
dbEnvBlock = `# Database Configuration\n# MySQL\nDB_TYPE="mysql"\nDB_HOST="localhost"\nDB_PORT=${dbPort}\nDB_NAME="${dbName}"\nDB_USER="${dbUser}"\nDB_PASSWORD="${dbPassword}"\nDATABASE_URL="mysql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}"`;
|
|
238
|
+
} else if (dbType === "postgres") {
|
|
239
|
+
dbEnvBlock = `# Database Configuration\n# PostgreSQL\nDB_TYPE="postgres"\nDB_HOST="localhost"\nDB_PORT=${dbPort}\nDB_NAME="${dbName}"\nDB_USER="${dbUser}"\nDB_PASSWORD="${dbPassword}"\nDATABASE_URL="postgresql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}?schema=public"`;
|
|
240
|
+
} else {
|
|
241
|
+
dbEnvBlock = `# Database Configuration\n# None\nDB_TYPE="none"`;
|
|
242
|
+
}
|
|
239
243
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (key === "COMPOSE_PROJECT_NAME") {
|
|
247
|
-
foundComposeProjectName = true;
|
|
248
|
-
}
|
|
249
|
-
if (updates[key] !== undefined) {
|
|
250
|
-
const value = updates[key];
|
|
251
|
-
if (typeof value === "string" && !value.startsWith('"')) {
|
|
252
|
-
return `${key}="${value}"`;
|
|
253
|
-
}
|
|
254
|
-
return `${key}=${value}`;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return line;
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
if (!foundComposeProjectName && updates.COMPOSE_PROJECT_NAME) {
|
|
261
|
-
const titleIdx = updatedLines.findIndex((l) => l.startsWith("TITLE="));
|
|
262
|
-
if (titleIdx !== -1) {
|
|
263
|
-
updatedLines.splice(
|
|
264
|
-
titleIdx,
|
|
265
|
-
0,
|
|
266
|
-
`COMPOSE_PROJECT_NAME="${updates.COMPOSE_PROJECT_NAME}"`,
|
|
267
|
-
);
|
|
268
|
-
} else {
|
|
269
|
-
updatedLines.push(
|
|
270
|
-
`COMPOSE_PROJECT_NAME="${updates.COMPOSE_PROJECT_NAME}"`,
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
envContent = updatedLines.join("\n");
|
|
244
|
+
const envContent = `# Server and Application Configuration
|
|
245
|
+
DEBUG=true
|
|
246
|
+
PORT=${port}
|
|
247
|
+
HOST="localhost"
|
|
248
|
+
APP_URL="${appUrl}"
|
|
249
|
+
VERSION="1.0.0"
|
|
275
250
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
251
|
+
# Docker / Swagger Config
|
|
252
|
+
COMPOSE_PROJECT_NAME="${composeProjectName}"
|
|
253
|
+
TITLE="${title}"
|
|
254
|
+
DESCRIPTION="Description project"
|
|
255
|
+
|
|
256
|
+
# Security Configuration
|
|
257
|
+
JWT_SECRET="${jwtSecret}"
|
|
258
|
+
|
|
259
|
+
# Cache Configuration
|
|
260
|
+
# Options: "memory" (Fast in-memory RAM), "redis" (Distributed container cache), "none" (Disabled)
|
|
261
|
+
CACHE_MODE="memory"
|
|
262
|
+
|
|
263
|
+
# Redis Configuration (Used when CACHE_MODE="redis")
|
|
264
|
+
REDIS_HOST="localhost"
|
|
265
|
+
REDIS_PORT=6379
|
|
266
|
+
REDIS_PASSWORD=""
|
|
267
|
+
|
|
268
|
+
# PM2 Clustering Instances (integer or 'max')
|
|
269
|
+
PM2_INSTANCES=1
|
|
270
|
+
|
|
271
|
+
${dbEnvBlock}
|
|
272
|
+
`;
|
|
273
|
+
|
|
274
|
+
fs.writeFileSync(envPath, envContent, "utf-8");
|
|
275
|
+
console.log(chalk.green("[OK] Created and configured .env file."));
|
|
279
276
|
|
|
280
277
|
this.updatePackageJson();
|
|
281
278
|
|
|
@@ -289,7 +286,7 @@ class ProjectInit {
|
|
|
289
286
|
packagesToInstall.push("better-sqlite3");
|
|
290
287
|
} else if (dbType === "postgres") {
|
|
291
288
|
packagesToInstall.push("pg");
|
|
292
|
-
} else {
|
|
289
|
+
} else if (dbType === "mysql") {
|
|
293
290
|
packagesToInstall.push("mysql2");
|
|
294
291
|
}
|
|
295
292
|
execSync(`npm install ${packagesToInstall.join(" ")}`, {
|
|
@@ -426,9 +423,16 @@ function addCommand(feature) {
|
|
|
426
423
|
"better-sqlite3": "better-sqlite3",
|
|
427
424
|
mysql: "mysql2",
|
|
428
425
|
mysql2: "mysql2",
|
|
426
|
+
mariadb: "mysql2",
|
|
427
|
+
maria: "mysql2",
|
|
429
428
|
postgres: "pg",
|
|
430
429
|
postgresql: "pg",
|
|
431
430
|
pg: "pg",
|
|
431
|
+
psql: "pg",
|
|
432
|
+
pgsql: "pg",
|
|
433
|
+
psgr: "pg",
|
|
434
|
+
postgre: "pg",
|
|
435
|
+
postgr: "pg",
|
|
432
436
|
bcrypt: "bcrypt",
|
|
433
437
|
swagger: "swagger-ui-express",
|
|
434
438
|
};
|
package/core/database.js
CHANGED
|
@@ -3,7 +3,45 @@ import path from "node:path";
|
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
4
|
import { getRedisClient } from "./cache.js";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const rawDbType = (process.env.DB_TYPE || "").toLowerCase().trim();
|
|
7
|
+
let DB_TYPE = "";
|
|
8
|
+
|
|
9
|
+
if (
|
|
10
|
+
rawDbType === "sqlite" ||
|
|
11
|
+
rawDbType === "sqlite3" ||
|
|
12
|
+
rawDbType === "sql" ||
|
|
13
|
+
rawDbType === "better-sqlite3" ||
|
|
14
|
+
rawDbType === "better-sqlite" ||
|
|
15
|
+
rawDbType === "lite"
|
|
16
|
+
) {
|
|
17
|
+
DB_TYPE = "sqlite";
|
|
18
|
+
} else if (
|
|
19
|
+
rawDbType === "postgres" ||
|
|
20
|
+
rawDbType === "postgresql" ||
|
|
21
|
+
rawDbType === "pg" ||
|
|
22
|
+
rawDbType === "psql" ||
|
|
23
|
+
rawDbType === "pgsql" ||
|
|
24
|
+
rawDbType === "postgre" ||
|
|
25
|
+
rawDbType === "postgr" ||
|
|
26
|
+
rawDbType === "psgr"
|
|
27
|
+
) {
|
|
28
|
+
DB_TYPE = "postgres";
|
|
29
|
+
} else if (
|
|
30
|
+
rawDbType === "mysql" ||
|
|
31
|
+
rawDbType === "mariadb" ||
|
|
32
|
+
rawDbType === "maria" ||
|
|
33
|
+
rawDbType === "my"
|
|
34
|
+
) {
|
|
35
|
+
DB_TYPE = "mysql";
|
|
36
|
+
} else if (
|
|
37
|
+
rawDbType === "none" ||
|
|
38
|
+
rawDbType === "no" ||
|
|
39
|
+
rawDbType === "false" ||
|
|
40
|
+
rawDbType === "null" ||
|
|
41
|
+
rawDbType === "0"
|
|
42
|
+
) {
|
|
43
|
+
DB_TYPE = "none";
|
|
44
|
+
}
|
|
7
45
|
|
|
8
46
|
if (
|
|
9
47
|
!DB_TYPE &&
|
package/frontend/about.html
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
<h4>Menu</h4>
|
|
34
34
|
<a href="/">Home</a>
|
|
35
35
|
<a href="/about" class="active badge badge-glow px-3 py-3">About</a>
|
|
36
|
+
<a href="https://seip25.github.io/Blue-bird" target="_blank">Blue bird</a>
|
|
36
37
|
<a href="https://seip25.github.io/Blue-bird-css/" target="_blank">Blue Bird CSS</a>
|
|
37
38
|
<a href="https://github.com/seip25/Blue-bird" target="_blank" role="button"
|
|
38
39
|
class="btn-sm outline">GitHub</a>
|
package/frontend/index.html
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
<h4>Menu</h4>
|
|
34
34
|
<a href="/" class="active badge badge-glow px-3 py-3">Home</a>
|
|
35
35
|
<a href="/about">About</a>
|
|
36
|
+
<a href="https://seip25.github.io/Blue-bird" target="_blank">Blue bird</a>
|
|
36
37
|
<a href="https://seip25.github.io/Blue-bird-css/" target="_blank">Blue Bird CSS</a>
|
|
37
38
|
<a href="https://github.com/seip25/Blue-bird" target="_blank" role="button"
|
|
38
39
|
class="btn-sm outline">GitHub</a>
|