@seip/blue-bird 1.0.2 → 1.1.1
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 -12
- package/AGENTS.md +98 -26
- package/README.md +80 -23
- package/core/app.js +42 -0
- package/core/cache.js +62 -30
- package/core/cli/docker.js +236 -59
- package/core/cli/init.js +135 -9
- package/core/database.js +205 -20
- package/core/hash.js +201 -0
- package/core/index.d.ts +194 -137
- package/core/upload.js +83 -57
- package/core/ws.js +227 -210
- package/docker/docker-compose.sqlite.yml +69 -0
- package/frontend/js/utils.js +557 -557
- package/package.json +68 -66
package/core/cli/docker.js
CHANGED
|
@@ -26,11 +26,14 @@ function getEnvVars() {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Determines the target database type (mysql or postgres) from environment variables.
|
|
29
|
+
* Determines the target database type (sqlite, mysql, or postgres) from environment variables.
|
|
30
30
|
* @param {Object} [env] - Environment dictionary.
|
|
31
|
-
* @returns {string} 'postgres' or '
|
|
31
|
+
* @returns {string} 'sqlite', 'postgres', 'mysql', or 'none'.
|
|
32
32
|
*/
|
|
33
33
|
function getDbType(env = getEnvVars()) {
|
|
34
|
+
if (env.DB_TYPE && (env.DB_TYPE.toLowerCase() === "sqlite" || env.DB_TYPE.toLowerCase() === "sqlite3")) {
|
|
35
|
+
return "sqlite";
|
|
36
|
+
}
|
|
34
37
|
if (env.DB_TYPE && (env.DB_TYPE.toLowerCase() === "postgres" || env.DB_TYPE.toLowerCase() === "postgresql" || env.DB_TYPE.toLowerCase() === "pg")) {
|
|
35
38
|
return "postgres";
|
|
36
39
|
}
|
|
@@ -41,6 +44,9 @@ function getDbType(env = getEnvVars()) {
|
|
|
41
44
|
return "none";
|
|
42
45
|
}
|
|
43
46
|
if (env.DATABASE_URL && !env.DATABASE_URL.startsWith("#")) {
|
|
47
|
+
if (env.DATABASE_URL.startsWith("sqlite://") || env.DATABASE_URL.startsWith("sqlite:")) {
|
|
48
|
+
return "sqlite";
|
|
49
|
+
}
|
|
44
50
|
if (env.DATABASE_URL.startsWith("postgres://") || env.DATABASE_URL.startsWith("postgresql://")) {
|
|
45
51
|
return "postgres";
|
|
46
52
|
}
|
|
@@ -48,7 +54,7 @@ function getDbType(env = getEnvVars()) {
|
|
|
48
54
|
return "mysql";
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
|
-
return "
|
|
57
|
+
return "sqlite";
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
/**
|
|
@@ -84,12 +90,23 @@ async function startCommand(service) {
|
|
|
84
90
|
checkComposeFile();
|
|
85
91
|
const dbType = getDbType();
|
|
86
92
|
|
|
87
|
-
if (service === "mysql" || service === "--mysql" || service === "postgres" || service === "--postgres" || service === "db" || service === "--db" || service === "dev") {
|
|
88
|
-
const targetContainer = (service === "postgres" || service === "--postgres") ? "postgres" : ((service === "mysql" || service === "--mysql") ? "mysql" : dbType);
|
|
93
|
+
if (service === "sqlite" || service === "--sqlite" || service === "mysql" || service === "--mysql" || service === "postgres" || service === "--postgres" || service === "db" || service === "--db" || service === "dev") {
|
|
94
|
+
const targetContainer = (service === "sqlite" || service === "--sqlite") ? "sqlite" : ((service === "postgres" || service === "--postgres") ? "postgres" : ((service === "mysql" || service === "--mysql") ? "mysql" : dbType));
|
|
89
95
|
if (targetContainer === "none") {
|
|
90
96
|
console.log(chalk.yellow("[INFO] DB_TYPE is set to 'none', skipping database container startup."));
|
|
91
97
|
return;
|
|
92
98
|
}
|
|
99
|
+
if (targetContainer === "sqlite") {
|
|
100
|
+
console.log(chalk.cyan("[INFO] SQLite is file-based (embedded in app). Starting Redis container..."));
|
|
101
|
+
const code = await runCmd("docker", ["compose", "up", "-d", "redis"]);
|
|
102
|
+
if (code === 0) {
|
|
103
|
+
console.log(chalk.green("Redis started."));
|
|
104
|
+
} else {
|
|
105
|
+
console.error(chalk.red("Error starting Redis."));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
93
110
|
console.log(chalk.cyan(`Starting ${targetContainer.toUpperCase()} container...`));
|
|
94
111
|
const code = await runCmd("docker", ["compose", "up", "-d", targetContainer]);
|
|
95
112
|
if (code === 0) {
|
|
@@ -108,8 +125,8 @@ async function startCommand(service) {
|
|
|
108
125
|
process.exit(1);
|
|
109
126
|
}
|
|
110
127
|
} else if (service === "dbs" || service === "databases") {
|
|
111
|
-
if (dbType === "none") {
|
|
112
|
-
console.log(chalk.cyan(
|
|
128
|
+
if (dbType === "none" || dbType === "sqlite") {
|
|
129
|
+
console.log(chalk.cyan(`DB_TYPE is '${dbType}' (embedded/none), starting Redis container only...`));
|
|
113
130
|
const code = await runCmd("docker", ["compose", "up", "-d", "redis"]);
|
|
114
131
|
if (code === 0) {
|
|
115
132
|
console.log(chalk.green("Redis started."));
|
|
@@ -137,7 +154,7 @@ async function startCommand(service) {
|
|
|
137
154
|
process.exit(1);
|
|
138
155
|
}
|
|
139
156
|
} else {
|
|
140
|
-
console.error(chalk.red(`Unknown service '${service}'. Use: mysql, postgres, db, redis, dbs, prod.`));
|
|
157
|
+
console.error(chalk.red(`Unknown service '${service}'. Use: sqlite, mysql, postgres, db, redis, dbs, prod.`));
|
|
141
158
|
process.exit(1);
|
|
142
159
|
}
|
|
143
160
|
}
|
|
@@ -145,25 +162,27 @@ async function startCommand(service) {
|
|
|
145
162
|
/**
|
|
146
163
|
* Handles the 'dev' CLI command.
|
|
147
164
|
*/
|
|
148
|
-
async function devCommand() {
|
|
149
|
-
checkComposeFile();
|
|
150
|
-
const dbType = getDbType();
|
|
151
|
-
console.log(chalk.cyan("Starting development environment (Database + Redis)..."));
|
|
152
|
-
|
|
153
|
-
const containers = ["redis"];
|
|
154
|
-
if (dbType !== "none") {
|
|
155
|
-
containers.unshift(dbType);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const code = await runCmd("docker", ["compose", "up", "-d", ...containers]);
|
|
159
|
-
if (code === 0) {
|
|
160
|
-
console.log(chalk.green("Development containers started."));
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
console.
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
async function devCommand() {
|
|
166
|
+
checkComposeFile();
|
|
167
|
+
const dbType = getDbType();
|
|
168
|
+
console.log(chalk.cyan("Starting development environment (Database + Redis)..."));
|
|
169
|
+
|
|
170
|
+
const containers = ["redis"];
|
|
171
|
+
if (dbType !== "none" && dbType !== "sqlite") {
|
|
172
|
+
containers.unshift(dbType);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const code = await runCmd("docker", ["compose", "up", "-d", ...containers]);
|
|
176
|
+
if (code === 0) {
|
|
177
|
+
console.log(chalk.green("Development containers started."));
|
|
178
|
+
if (dbType !== "none" && dbType !== "sqlite") {
|
|
179
|
+
console.log(chalk.cyan("View live logs with: npx blue-bird docker logs db"));
|
|
180
|
+
}
|
|
181
|
+
console.log(chalk.yellow("Now execute: npm run dev"));
|
|
182
|
+
} else {
|
|
183
|
+
console.error(chalk.red("Error starting development environment."));
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
167
186
|
}
|
|
168
187
|
|
|
169
188
|
/**
|
|
@@ -178,10 +197,10 @@ async function stopCommand(service) {
|
|
|
178
197
|
console.log(chalk.cyan("Stopping all Blue Bird containers..."));
|
|
179
198
|
await runCmd("docker", ["compose", "--profile", "prod", "down"]);
|
|
180
199
|
console.log(chalk.green("All containers stopped."));
|
|
181
|
-
} else if (service === "mysql" || service === "--mysql" || service === "postgres" || service === "--postgres" || service === "db" || service === "--db") {
|
|
182
|
-
const targetContainer = (service === "postgres" || service === "--postgres") ? "postgres" : ((service === "mysql" || service === "--mysql") ? "mysql" : dbType);
|
|
183
|
-
if (targetContainer === "none") {
|
|
184
|
-
console.log(chalk.yellow(
|
|
200
|
+
} else if (service === "sqlite" || service === "--sqlite" || service === "mysql" || service === "--mysql" || service === "postgres" || service === "--postgres" || service === "db" || service === "--db") {
|
|
201
|
+
const targetContainer = (service === "sqlite" || service === "--sqlite") ? "sqlite" : ((service === "postgres" || service === "--postgres") ? "postgres" : ((service === "mysql" || service === "--mysql") ? "mysql" : dbType));
|
|
202
|
+
if (targetContainer === "none" || targetContainer === "sqlite") {
|
|
203
|
+
console.log(chalk.yellow(`[INFO] DB_TYPE is set to '${targetContainer}', no database container to stop.`));
|
|
185
204
|
return;
|
|
186
205
|
}
|
|
187
206
|
console.log(chalk.cyan(`Stopping ${targetContainer.toUpperCase()}...`));
|
|
@@ -199,7 +218,7 @@ async function stopCommand(service) {
|
|
|
199
218
|
await runCmd("docker", ["compose", "--profile", "prod", "rm", "-f", "app"]);
|
|
200
219
|
console.log(chalk.green("App container stopped."));
|
|
201
220
|
} else {
|
|
202
|
-
console.error(chalk.red(`Unknown service '${service}'. Use: all, mysql, postgres, db, redis, app.`));
|
|
221
|
+
console.error(chalk.red(`Unknown service '${service}'. Use: all, sqlite, mysql, postgres, db, redis, app.`));
|
|
203
222
|
process.exit(1);
|
|
204
223
|
}
|
|
205
224
|
}
|
|
@@ -245,13 +264,17 @@ async function logsCommand(service, followOpt) {
|
|
|
245
264
|
const follow = followOpt !== "--no-follow";
|
|
246
265
|
const dbType = getDbType();
|
|
247
266
|
let targetService = "app";
|
|
248
|
-
if (service === "mysql" || service === "postgres" || service === "db") {
|
|
267
|
+
if (service === "mysql" || service === "postgres" || service === "sqlite" || service === "db") {
|
|
249
268
|
targetService = service === "db" ? dbType : service;
|
|
250
269
|
}
|
|
251
270
|
if (targetService === "none") {
|
|
252
271
|
console.log(chalk.yellow("[INFO] DB_TYPE is set to 'none', no database container logs to display."));
|
|
253
272
|
return;
|
|
254
273
|
}
|
|
274
|
+
if (targetService === "sqlite") {
|
|
275
|
+
console.log(chalk.yellow("[INFO] SQLite is embedded in the Node.js application container. Displaying app logs:"));
|
|
276
|
+
targetService = "app";
|
|
277
|
+
}
|
|
255
278
|
|
|
256
279
|
const cmdArgs = ["compose"];
|
|
257
280
|
if (targetService === "app") {
|
|
@@ -267,22 +290,14 @@ async function logsCommand(service, followOpt) {
|
|
|
267
290
|
}
|
|
268
291
|
|
|
269
292
|
/**
|
|
270
|
-
* Handles interactive shell connections into the MySQL or PostgreSQL
|
|
271
|
-
* @param {string} userOpt - DB username.
|
|
272
|
-
* @param {string} passOpt - DB password.
|
|
273
|
-
* @param {string} dbOpt - DB database name.
|
|
274
|
-
* @param {boolean} rootOpt - Flag for overriding database credentials to connect as root/postgres.
|
|
275
|
-
* @param {string} explicitService - Explicit target service if specified ('mysql' or 'postgres').
|
|
276
|
-
*/
|
|
277
|
-
/**
|
|
278
|
-
* Handles interactive shell connections or smart queries into the MySQL or PostgreSQL container.
|
|
293
|
+
* Handles interactive shell connections or smart queries into the SQLite, MySQL, or PostgreSQL database.
|
|
279
294
|
* @param {string[]} clientArgs - CLI arguments.
|
|
280
|
-
* @param {string} explicitService - Explicit target service if specified ('mysql', 'postgres', 'psql', 'db').
|
|
295
|
+
* @param {string} explicitService - Explicit target service if specified ('sqlite', 'mysql', 'postgres', 'psql', 'db').
|
|
281
296
|
*/
|
|
282
297
|
async function dbClientCommand(clientArgs = [], explicitService) {
|
|
283
298
|
checkComposeFile();
|
|
284
299
|
const env = getEnvVars();
|
|
285
|
-
const dbType = explicitService === "postgres" || explicitService === "psql" ? "postgres" : (explicitService === "mysql" ? "mysql" : getDbType(env));
|
|
300
|
+
const dbType = explicitService === "sqlite" ? "sqlite" : (explicitService === "postgres" || explicitService === "psql" ? "postgres" : (explicitService === "mysql" ? "mysql" : getDbType(env)));
|
|
286
301
|
if (dbType === "none") {
|
|
287
302
|
console.error(chalk.yellow("[INFO] DB_TYPE is set to 'none'. No database container available to connect to."));
|
|
288
303
|
return;
|
|
@@ -340,23 +355,27 @@ async function dbClientCommand(clientArgs = [], explicitService) {
|
|
|
340
355
|
if (firstPos === "tables") {
|
|
341
356
|
if (dbType === "postgres") {
|
|
342
357
|
sqlQuery = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';";
|
|
358
|
+
} else if (dbType === "sqlite") {
|
|
359
|
+
sqlQuery = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';";
|
|
343
360
|
} else {
|
|
344
361
|
sqlQuery = "SHOW TABLES;";
|
|
345
362
|
}
|
|
346
363
|
} else if (firstPos === "columns" || firstPos === "cols" || firstPos === "describe" || firstPos === "desc") {
|
|
347
364
|
const tableName = positionalArgs[1];
|
|
348
365
|
if (!tableName) {
|
|
349
|
-
console.error(chalk.red("Error: Please specify a table name. Example: npx blue-bird docker
|
|
366
|
+
console.error(chalk.red("Error: Please specify a table name. Example: npx blue-bird docker db columns users"));
|
|
350
367
|
process.exit(1);
|
|
351
368
|
}
|
|
352
369
|
if (dbType === "postgres") {
|
|
353
370
|
sqlQuery = `SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_name = '${tableName}';`;
|
|
371
|
+
} else if (dbType === "sqlite") {
|
|
372
|
+
sqlQuery = `PRAGMA table_info(${tableName});`;
|
|
354
373
|
} else {
|
|
355
374
|
sqlQuery = `SHOW COLUMNS FROM ${tableName};`;
|
|
356
375
|
}
|
|
357
376
|
} else {
|
|
358
377
|
const rawInput = positionalArgs.join(" ").trim();
|
|
359
|
-
const isFullQuery = /^(select|show|desc|describe|explain|insert|update|delete|create|drop|alter|truncate)\b/i.test(rawInput) || rawInput.includes(" ");
|
|
378
|
+
const isFullQuery = /^(select|show|desc|describe|explain|insert|update|delete|create|drop|alter|truncate|pragma)\b/i.test(rawInput) || rawInput.includes(" ");
|
|
360
379
|
if (isFullQuery) {
|
|
361
380
|
sqlQuery = rawInput;
|
|
362
381
|
} else {
|
|
@@ -375,7 +394,53 @@ async function dbClientCommand(clientArgs = [], explicitService) {
|
|
|
375
394
|
}
|
|
376
395
|
}
|
|
377
396
|
|
|
378
|
-
if (dbType === "
|
|
397
|
+
if (dbType === "sqlite") {
|
|
398
|
+
const dbFile = env.DB_FILE || "database/blue_bird.db";
|
|
399
|
+
const dbPath = path.isAbsolute(dbFile) ? dbFile : path.resolve(process.cwd(), dbFile);
|
|
400
|
+
if (!fs.existsSync(dbPath)) {
|
|
401
|
+
console.log(chalk.yellow(`[INFO] SQLite database file not found at '${dbFile}'. It will be created upon query execution.`));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
let BetterSqlite;
|
|
406
|
+
try {
|
|
407
|
+
BetterSqlite = (await import("better-sqlite3")).default;
|
|
408
|
+
} catch (err) {
|
|
409
|
+
console.error(chalk.red("Error: 'better-sqlite3' is not installed. Run: npm install better-sqlite3"));
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const dir = path.dirname(dbPath);
|
|
414
|
+
if (!fs.existsSync(dir)) {
|
|
415
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const db = new BetterSqlite(dbPath);
|
|
419
|
+
if (sqlQuery) {
|
|
420
|
+
console.log(chalk.cyan(`🔍 Executing SQLite query on '${dbFile}':`));
|
|
421
|
+
console.log(chalk.gray(` ${sqlQuery}\n`));
|
|
422
|
+
const isSelect = /^(select|pragma|explain)/i.test(sqlQuery.trim());
|
|
423
|
+
if (isSelect) {
|
|
424
|
+
const rows = db.prepare(sqlQuery).all();
|
|
425
|
+
console.table(rows);
|
|
426
|
+
} else {
|
|
427
|
+
const info = db.prepare(sqlQuery).run();
|
|
428
|
+
console.log(chalk.green(`✔ Query executed successfully. Changes: ${info.changes}, LastInsertRowId: ${info.lastInsertRowid}`));
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
console.log(chalk.cyan(`Connected to SQLite database '${dbFile}'.`));
|
|
432
|
+
console.log(chalk.gray("Active Tables:"));
|
|
433
|
+
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all();
|
|
434
|
+
console.table(tables);
|
|
435
|
+
console.log(chalk.yellow("\nTip: Run queries with: npx blue-bird docker db \"SELECT * FROM table_name\""));
|
|
436
|
+
}
|
|
437
|
+
db.close();
|
|
438
|
+
return;
|
|
439
|
+
} catch (err) {
|
|
440
|
+
console.error(chalk.red("[DATABASE ERROR] SQLite operation failed:"), err.message);
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
} else if (dbType === "postgres") {
|
|
379
444
|
dbUser = rootOpt ? "postgres" : (dbUser || env.DB_USER || "postgres");
|
|
380
445
|
dbName = dbName || env.DB_NAME || "blue_bird";
|
|
381
446
|
|
|
@@ -434,8 +499,8 @@ async function dbClientCommand(clientArgs = [], explicitService) {
|
|
|
434
499
|
}
|
|
435
500
|
|
|
436
501
|
/**
|
|
437
|
-
* Exports database schema & data into a .sql file inside backups/ folder.
|
|
438
|
-
* @param {string} dbType - Target db type ('mysql', 'postgres', 'none').
|
|
502
|
+
* Exports database schema & data into a .sql or .db file inside backups/ folder.
|
|
503
|
+
* @param {string} dbType - Target db type ('sqlite', 'mysql', 'postgres', 'none').
|
|
439
504
|
* @param {string} [filenameArg] - Custom backup filename.
|
|
440
505
|
* @param {string} [userOpt] - Custom db user.
|
|
441
506
|
* @param {string} [passOpt] - Custom db password.
|
|
@@ -456,6 +521,29 @@ async function exportDbCommand(dbType, filenameArg, userOpt, passOpt, dbOpt) {
|
|
|
456
521
|
fs.mkdirSync(backupsDir, { recursive: true });
|
|
457
522
|
}
|
|
458
523
|
|
|
524
|
+
if (targetDbType === "sqlite") {
|
|
525
|
+
const dbFile = env.DB_FILE || "database/blue_bird.db";
|
|
526
|
+
const srcDbPath = path.isAbsolute(dbFile) ? dbFile : path.resolve(process.cwd(), dbFile);
|
|
527
|
+
if (!fs.existsSync(srcDbPath)) {
|
|
528
|
+
console.error(chalk.red(`Error: SQLite database file '${dbFile}' does not exist.`));
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
532
|
+
const backupName = filenameArg || `backup_sqlite_${timestamp}.db`;
|
|
533
|
+
const destFile = path.isAbsolute(backupName)
|
|
534
|
+
? backupName
|
|
535
|
+
: (backupName.includes("/") || backupName.includes("\\")
|
|
536
|
+
? path.resolve(process.cwd(), backupName)
|
|
537
|
+
: path.join(backupsDir, backupName));
|
|
538
|
+
|
|
539
|
+
fs.copyFileSync(srcDbPath, destFile);
|
|
540
|
+
const stats = fs.statSync(destFile);
|
|
541
|
+
const sizeKb = (stats.size / 1024).toFixed(2);
|
|
542
|
+
console.log(chalk.green(`\n✔ SQLite database exported successfully!`));
|
|
543
|
+
console.log(chalk.cyan(` File: ${path.relative(process.cwd(), destFile)} (${sizeKb} KB)`));
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
459
547
|
let outputFile;
|
|
460
548
|
if (filenameArg) {
|
|
461
549
|
let name = filenameArg;
|
|
@@ -525,8 +613,8 @@ function exportDbToFile(cmdArgs, outputFile) {
|
|
|
525
613
|
}
|
|
526
614
|
|
|
527
615
|
/**
|
|
528
|
-
* Imports a .sql file from backups/ folder into the
|
|
529
|
-
* @param {string} dbType - Target db type ('mysql', 'postgres', 'none').
|
|
616
|
+
* Imports a .sql or .db file from backups/ folder into the database.
|
|
617
|
+
* @param {string} dbType - Target db type ('sqlite', 'mysql', 'postgres', 'none').
|
|
530
618
|
* @param {string} [filenameArg] - Custom backup filename.
|
|
531
619
|
* @param {string} [userOpt] - Custom db user.
|
|
532
620
|
* @param {string} [passOpt] - Custom db password.
|
|
@@ -550,24 +638,29 @@ async function importDbCommand(dbType, filenameArg, userOpt, passOpt, dbOpt) {
|
|
|
550
638
|
let inputFile;
|
|
551
639
|
if (filenameArg) {
|
|
552
640
|
let name = filenameArg;
|
|
553
|
-
if (
|
|
554
|
-
|
|
641
|
+
if (path.isAbsolute(name) && fs.existsSync(name)) {
|
|
642
|
+
inputFile = name;
|
|
643
|
+
} else if (fs.existsSync(path.resolve(process.cwd(), name))) {
|
|
555
644
|
inputFile = path.resolve(process.cwd(), name);
|
|
556
645
|
} else if (fs.existsSync(path.join(backupsDir, name))) {
|
|
557
646
|
inputFile = path.join(backupsDir, name);
|
|
647
|
+
} else if (fs.existsSync(path.join(backupsDir, `${name}.sql`))) {
|
|
648
|
+
inputFile = path.join(backupsDir, `${name}.sql`);
|
|
649
|
+
} else if (fs.existsSync(path.join(backupsDir, `${name}.db`))) {
|
|
650
|
+
inputFile = path.join(backupsDir, `${name}.db`);
|
|
558
651
|
} else {
|
|
559
652
|
console.error(chalk.red(`Error: Backup file '${filenameArg}' not found in current directory or 'backups/' folder.`));
|
|
560
653
|
process.exit(1);
|
|
561
654
|
}
|
|
562
655
|
} else {
|
|
563
656
|
const files = fs.readdirSync(backupsDir)
|
|
564
|
-
.filter(f => f.endsWith(".sql"))
|
|
657
|
+
.filter(f => f.endsWith(".sql") || f.endsWith(".db"))
|
|
565
658
|
.map(f => ({ name: f, time: fs.statSync(path.join(backupsDir, f)).mtimeMs }))
|
|
566
659
|
.sort((a, b) => b.time - a.time);
|
|
567
660
|
|
|
568
661
|
if (files.length === 0) {
|
|
569
|
-
console.error(chalk.red(`Error: No
|
|
570
|
-
console.log(chalk.yellow(`Usage: npx blue-bird docker import <file.sql>`));
|
|
662
|
+
console.error(chalk.red(`Error: No backup files found in 'backups/' directory.`));
|
|
663
|
+
console.log(chalk.yellow(`Usage: npx blue-bird docker import <file.sql | file.db>`));
|
|
571
664
|
process.exit(1);
|
|
572
665
|
}
|
|
573
666
|
|
|
@@ -575,6 +668,36 @@ async function importDbCommand(dbType, filenameArg, userOpt, passOpt, dbOpt) {
|
|
|
575
668
|
console.log(chalk.yellow(`[INFO] No file specified. Using most recent backup: '${files[0].name}'`));
|
|
576
669
|
}
|
|
577
670
|
|
|
671
|
+
const relPath = path.relative(process.cwd(), inputFile);
|
|
672
|
+
|
|
673
|
+
if (targetDbType === "sqlite") {
|
|
674
|
+
const dbFile = env.DB_FILE || "database/blue_bird.db";
|
|
675
|
+
const destDbPath = path.isAbsolute(dbFile) ? dbFile : path.resolve(process.cwd(), dbFile);
|
|
676
|
+
const destDir = path.dirname(destDbPath);
|
|
677
|
+
if (!fs.existsSync(destDir)) {
|
|
678
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (inputFile.endsWith(".db") || inputFile.endsWith(".sqlite")) {
|
|
682
|
+
fs.copyFileSync(inputFile, destDbPath);
|
|
683
|
+
console.log(chalk.green(`\n✔ SQLite database restored successfully from '${relPath}' to '${dbFile}'!`));
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
try {
|
|
688
|
+
const BetterSqlite = (await import("better-sqlite3")).default;
|
|
689
|
+
const db = new BetterSqlite(destDbPath);
|
|
690
|
+
const sqlContent = fs.readFileSync(inputFile, "utf-8");
|
|
691
|
+
db.exec(sqlContent);
|
|
692
|
+
db.close();
|
|
693
|
+
console.log(chalk.green(`\n✔ SQL statements from '${relPath}' imported successfully into '${dbFile}'!`));
|
|
694
|
+
} catch (err) {
|
|
695
|
+
console.error(chalk.red(`\n✖ SQLite import failed:`), err.message);
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
578
701
|
const dbUser = userOpt || env.DB_USER || (targetDbType === "postgres" ? "postgres" : "root");
|
|
579
702
|
const dbPass = passOpt || env.DB_PASSWORD || (targetDbType === "postgres" ? "postgres" : "root");
|
|
580
703
|
const dbName = dbOpt || env.DB_NAME || "blue_bird";
|
|
@@ -586,7 +709,6 @@ async function importDbCommand(dbType, filenameArg, userOpt, passOpt, dbOpt) {
|
|
|
586
709
|
cmdArgs = ["compose", "exec", "-T", "mysql", "mysql", `-u${dbUser}`, `-p${dbPass}`, dbName];
|
|
587
710
|
}
|
|
588
711
|
|
|
589
|
-
const relPath = path.relative(process.cwd(), inputFile);
|
|
590
712
|
console.log(chalk.cyan(`📥 Importing SQL dump '${relPath}' into ${targetDbType.toUpperCase()} database '${dbName}'...`));
|
|
591
713
|
|
|
592
714
|
const success = await importDbFromFile(cmdArgs, inputFile);
|
|
@@ -733,7 +855,9 @@ async function main() {
|
|
|
733
855
|
process.env.BLUEBIRD_PROJECT_NAME = projectName;
|
|
734
856
|
process.env.TITLE = projectName;
|
|
735
857
|
|
|
736
|
-
const
|
|
858
|
+
const rawArgs = process.argv.slice(2);
|
|
859
|
+
const dockerIdx = rawArgs.indexOf("docker");
|
|
860
|
+
const args = dockerIdx !== -1 ? rawArgs.slice(dockerIdx + 1) : rawArgs;
|
|
737
861
|
const command = args[0];
|
|
738
862
|
|
|
739
863
|
if (!command) {
|
|
@@ -741,6 +865,7 @@ async function main() {
|
|
|
741
865
|
return;
|
|
742
866
|
}
|
|
743
867
|
|
|
868
|
+
|
|
744
869
|
switch (command) {
|
|
745
870
|
case "start":
|
|
746
871
|
await startCommand(args[1]);
|
|
@@ -775,6 +900,7 @@ async function main() {
|
|
|
775
900
|
case "redis":
|
|
776
901
|
await redisCommand(args.slice(1));
|
|
777
902
|
break;
|
|
903
|
+
case "sqlite":
|
|
778
904
|
case "mysql":
|
|
779
905
|
case "postgres":
|
|
780
906
|
case "psql":
|
|
@@ -793,10 +919,61 @@ async function main() {
|
|
|
793
919
|
await pruneCommand(force, all);
|
|
794
920
|
break;
|
|
795
921
|
}
|
|
922
|
+
case "help":
|
|
923
|
+
case "--help":
|
|
924
|
+
case "-h":
|
|
925
|
+
helpCommand();
|
|
926
|
+
break;
|
|
796
927
|
default:
|
|
797
928
|
console.log(chalk.yellow(`Unknown docker command: ${command}`));
|
|
798
|
-
console.log("
|
|
929
|
+
console.log("Run 'npx blue-bird docker help' to see all available commands.");
|
|
799
930
|
}
|
|
931
|
+
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Displays the Blue Bird Docker CLI help manual.
|
|
936
|
+
*/
|
|
937
|
+
function helpCommand() {
|
|
938
|
+
console.log(chalk.bold.blue("\n🐦 Blue Bird Docker CLI — Command Reference"));
|
|
939
|
+
console.log(chalk.gray("────────────────────────────────────────────────────────────"));
|
|
940
|
+
console.log(chalk.bold("Usage: ") + chalk.cyan("npx blue-bird docker <command> [options]\n"));
|
|
941
|
+
|
|
942
|
+
console.log(chalk.bold.yellow("📦 Container Lifecycle:"));
|
|
943
|
+
console.log(` ${chalk.green("dev")} Starts development containers (Redis / DB). Run npm run dev locally.`);
|
|
944
|
+
console.log(` ${chalk.green("start [service]")} Starts production stack (app, nginx, redis, db) or specific service.`);
|
|
945
|
+
console.log(` ${chalk.green("start dev")} Starts development services only.`);
|
|
946
|
+
console.log(` ${chalk.green("start db")} Starts configured database container only.`);
|
|
947
|
+
console.log(` ${chalk.green("start redis")} Starts Redis container only.`);
|
|
948
|
+
console.log(` ${chalk.green("start dbs")} Starts all database containers (DB + Redis).`);
|
|
949
|
+
console.log(` ${chalk.green("stop [service]")} Stops all running containers (or specific service).`);
|
|
950
|
+
console.log(` ${chalk.green("build [--no-cache]")} Builds or updates the Node.js production image.`);
|
|
951
|
+
console.log(` ${chalk.green("ps | status")} Lists status of active project containers.`);
|
|
952
|
+
console.log(` ${chalk.green("logs [service] [-f]")} Tails logs for container (app, db, redis, nginx).`);
|
|
953
|
+
|
|
954
|
+
console.log(chalk.bold.yellow("\n🗄️ Database & Inspection:"));
|
|
955
|
+
console.log(` ${chalk.green("db | sqlite | mysql | psql")} [query/table]`);
|
|
956
|
+
console.log(` Inspects active database (tables, columns, or runs SQL query).`);
|
|
957
|
+
console.log(` ${chalk.gray("e.g. npx blue-bird docker db tables")}`);
|
|
958
|
+
console.log(` ${chalk.gray("e.g. npx blue-bird docker db columns users")}`);
|
|
959
|
+
console.log(` ${chalk.gray("e.g. npx blue-bird docker db \"SELECT * FROM users\"")}`);
|
|
960
|
+
console.log(` ${chalk.green("export | dump [file]")} Dumps database backup (.db file for SQLite or .sql) into backups/.`);
|
|
961
|
+
console.log(` ${chalk.green("import | restore [file]")} Restores database from backups/ (.db or .sql).`);
|
|
962
|
+
|
|
963
|
+
console.log(chalk.bold.yellow("\n⚡ Redis & Monitoring:"));
|
|
964
|
+
console.log(` ${chalk.green("redis")} [command] Runs interactive Redis CLI or smart subcommands.`);
|
|
965
|
+
console.log(` ${chalk.gray("e.g. npx blue-bird docker redis monitor")}`);
|
|
966
|
+
console.log(` ${chalk.gray("e.g. npx blue-bird docker redis keys")}`);
|
|
967
|
+
console.log(` ${chalk.gray("e.g. npx blue-bird docker redis key <keyname>")}`);
|
|
968
|
+
console.log(` ${chalk.green("pm2 [args]")} Runs PM2 commands inside app container (status, monit, reload).`);
|
|
969
|
+
console.log(` ${chalk.green("df | disk")} Shows Docker disk usage statistics.`);
|
|
970
|
+
console.log(` ${chalk.green("prune | clean [-f] [-a]")} Cleans unused volumes, dangling images, and build caches.`);
|
|
971
|
+
|
|
972
|
+
console.log(chalk.bold.yellow("\nℹ️ Help:"));
|
|
973
|
+
console.log(` ${chalk.green("help | --help | -h")} Displays this help message.`);
|
|
974
|
+
console.log(chalk.gray("────────────────────────────────────────────────────────────\n"));
|
|
800
975
|
}
|
|
801
976
|
|
|
802
977
|
main();
|
|
978
|
+
|
|
979
|
+
|