@seip/blue-bird 0.7.6 → 0.9.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 +34 -34
- package/AGENTS.md +174 -249
- package/LICENSE +21 -21
- package/README.md +331 -367
- package/{index.js → backend/index.js} +22 -30
- package/backend/routes/api.js +57 -57
- package/core/app.js +338 -402
- package/core/auth.js +262 -256
- package/core/cache.js +174 -174
- package/core/cli/docker.js +488 -457
- package/core/cli/init.js +333 -337
- package/core/cli/route.js +42 -42
- package/core/config.js +52 -52
- package/core/database.js +263 -263
- package/core/debug.js +248 -248
- package/core/logger.js +115 -115
- package/core/middleware.js +27 -27
- package/core/router.js +144 -144
- package/core/swagger.js +40 -40
- package/core/upload.js +77 -77
- package/core/validate.js +380 -380
- package/docker/Dockerfile +16 -16
- package/docker/docker-compose.dev.yml +6 -0
- package/docker/docker-compose.mysql.yml +92 -92
- package/docker/docker-compose.none.yml +68 -68
- package/docker/docker-compose.postgres.yml +93 -93
- package/docker/nginx.conf +98 -106
- package/docker-compose.yml +92 -92
- package/frontend/about.html +103 -0
- package/frontend/images/favicon.ico +0 -0
- package/frontend/index.html +141 -0
- package/frontend/js/tailwind.js +8 -0
- package/package.json +64 -71
- package/frontend/astro.config.mjs +0 -35
- package/frontend/public/css/app.css +0 -319
- package/frontend/public/favicon.ico +0 -0
- package/frontend/src/http/api.js +0 -29
- package/frontend/src/layouts/Layout.astro +0 -20
- package/frontend/src/pages/about.astro +0 -54
- package/frontend/src/pages/index.astro +0 -110
package/core/cli/init.js
CHANGED
|
@@ -1,337 +1,333 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
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";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Initializes a new Blue Bird project by copying the base structure.
|
|
13
|
-
*/
|
|
14
|
-
class ProjectInit {
|
|
15
|
-
constructor() {
|
|
16
|
-
this.appDir = process.cwd();
|
|
17
|
-
this.sourceDir = path.resolve(import.meta.dirname, "../../");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Runs the project initialization process.
|
|
22
|
-
*/
|
|
23
|
-
async run() {
|
|
24
|
-
console.log(chalk.cyan("Starting Blue Bird project initialization..."));
|
|
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 dbType = "none";
|
|
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 =
|
|
40
|
-
defaultValue !== undefined
|
|
41
|
-
? `${query} [${defaultValue}]: `
|
|
42
|
-
: `${query}: `;
|
|
43
|
-
const answer = await rl.question(formattedQuery);
|
|
44
|
-
return answer.trim() || defaultValue;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
title = await ask("Project Title", title);
|
|
48
|
-
const portInput = await ask("Server Port", port);
|
|
49
|
-
port = parseInt(portInput, 10);
|
|
50
|
-
if (Number.isNaN(port)) {
|
|
51
|
-
port = 3000;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const defaultAppUrl = `http://localhost:${port}`;
|
|
55
|
-
appUrl = await ask("Application URL", defaultAppUrl);
|
|
56
|
-
|
|
57
|
-
const dbTypeAns = await ask(
|
|
58
|
-
"Which database do you want to configure? (none / mysql / postgres)",
|
|
59
|
-
"none",
|
|
60
|
-
);
|
|
61
|
-
const cleanDbTypeAns = dbTypeAns.toLowerCase().trim();
|
|
62
|
-
if (
|
|
63
|
-
cleanDbTypeAns === "postgres" ||
|
|
64
|
-
cleanDbTypeAns === "postgresql" ||
|
|
65
|
-
cleanDbTypeAns === "pg" ||
|
|
66
|
-
cleanDbTypeAns === "postgr"
|
|
67
|
-
) {
|
|
68
|
-
dbType = "postgres";
|
|
69
|
-
} else if (cleanDbTypeAns === "mysql") {
|
|
70
|
-
dbType = "mysql";
|
|
71
|
-
} else {
|
|
72
|
-
dbType = "none";
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (dbType !== "none") {
|
|
76
|
-
dbName = await ask("Database Name", dbName);
|
|
77
|
-
dbUser = await ask(
|
|
78
|
-
"Database User",
|
|
79
|
-
dbType === "postgres" ? "postgres" : "root",
|
|
80
|
-
);
|
|
81
|
-
dbPassword = await ask("Database Password", dbPassword);
|
|
82
|
-
const defaultDbPort = dbType === "postgres" ? 5432 : 3306;
|
|
83
|
-
const dbPortInput = await ask("Database Port", defaultDbPort);
|
|
84
|
-
dbPort = parseInt(dbPortInput, 10);
|
|
85
|
-
if (Number.isNaN(dbPort)) {
|
|
86
|
-
dbPort = defaultDbPort;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error(
|
|
91
|
-
chalk.red("[ERROR] Error reading configuration input:"),
|
|
92
|
-
error.message,
|
|
93
|
-
);
|
|
94
|
-
rl.close();
|
|
95
|
-
return;
|
|
96
|
-
} finally {
|
|
97
|
-
rl.close();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const itemsToCopy = [
|
|
101
|
-
"backend",
|
|
102
|
-
"frontend",
|
|
103
|
-
"docker",
|
|
104
|
-
".env_example",
|
|
105
|
-
"AGENTS.md",
|
|
106
|
-
"index.js",
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
itemsToCopy.forEach((item) => {
|
|
111
|
-
const src = path.join(this.sourceDir, item);
|
|
112
|
-
const dest = path.join(this.appDir, item);
|
|
113
|
-
|
|
114
|
-
if (fs.existsSync(src)) {
|
|
115
|
-
if (!fs.existsSync(dest)) {
|
|
116
|
-
this.copyRecursive(src, dest);
|
|
117
|
-
console.log(chalk.green(`[OK] Copied ${item} to root.`));
|
|
118
|
-
} else {
|
|
119
|
-
console.log(
|
|
120
|
-
chalk.yellow(`[SKIP] ${item} already exists, skipping.`),
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
} else {
|
|
124
|
-
console.warn(
|
|
125
|
-
chalk.red(`[ERROR] Source ${item} not found in ${this.sourceDir}`),
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const composeTemplateName =
|
|
131
|
-
dbType === "postgres"
|
|
132
|
-
? "docker-compose.postgres.yml"
|
|
133
|
-
: dbType === "mysql"
|
|
134
|
-
? "docker-compose.mysql.yml"
|
|
135
|
-
: "docker-compose.none.yml";
|
|
136
|
-
const composeSrc = path.join(
|
|
137
|
-
this.sourceDir,
|
|
138
|
-
"docker",
|
|
139
|
-
composeTemplateName,
|
|
140
|
-
);
|
|
141
|
-
const composeDest = path.join(this.appDir, "docker-compose.yml");
|
|
142
|
-
if (fs.existsSync(composeSrc)) {
|
|
143
|
-
if (!fs.existsSync(composeDest)) {
|
|
144
|
-
fs.copyFileSync(composeSrc, composeDest);
|
|
145
|
-
console.log(
|
|
146
|
-
chalk.green(`[OK] Created docker-compose.yml (${dbType} mode).`),
|
|
147
|
-
);
|
|
148
|
-
} else {
|
|
149
|
-
console.log(
|
|
150
|
-
chalk.yellow(`[SKIP] docker-compose.yml already exists, skipping.`),
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
} else {
|
|
154
|
-
const fallbackSrc = path.join(this.sourceDir, "docker-compose.yml");
|
|
155
|
-
if (fs.existsSync(fallbackSrc) && !fs.existsSync(composeDest)) {
|
|
156
|
-
fs.copyFileSync(fallbackSrc, composeDest);
|
|
157
|
-
console.log(chalk.green(`[OK] Created docker-compose.yml.`));
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const envPath = path.join(this.appDir, ".env");
|
|
162
|
-
const envExamplePath = path.join(this.appDir, ".env_example");
|
|
163
|
-
|
|
164
|
-
if (fs.existsSync(envExamplePath)) {
|
|
165
|
-
let envContent = fs.readFileSync(envExamplePath, "utf-8");
|
|
166
|
-
|
|
167
|
-
const jwtSecret = crypto.randomBytes(32).toString("hex");
|
|
168
|
-
|
|
169
|
-
const updates = {
|
|
170
|
-
TITLE: title,
|
|
171
|
-
PORT: port,
|
|
172
|
-
APP_URL: appUrl,
|
|
173
|
-
JWT_SECRET: jwtSecret,
|
|
174
|
-
DB_TYPE: dbType,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
if (dbType === "mysql") {
|
|
178
|
-
updates.DB_NAME = dbName;
|
|
179
|
-
updates.DB_USER = dbUser;
|
|
180
|
-
updates.DB_PASSWORD = dbPassword;
|
|
181
|
-
updates.DB_PORT = dbPort;
|
|
182
|
-
updates.DATABASE_URL = `mysql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}`;
|
|
183
|
-
} else if (dbType === "postgres") {
|
|
184
|
-
updates.DB_NAME = dbName;
|
|
185
|
-
updates.DB_USER = dbUser;
|
|
186
|
-
updates.DB_PASSWORD = dbPassword;
|
|
187
|
-
updates.DB_PORT = dbPort;
|
|
188
|
-
updates.DATABASE_URL = `postgresql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}?schema=public`;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const lines = envContent.split(/\r?\n/);
|
|
192
|
-
const updatedLines = lines.map((line) => {
|
|
193
|
-
const match = line.match(/^([A-Z_]+)=(.+)/);
|
|
194
|
-
if (match) {
|
|
195
|
-
const key = match[1];
|
|
196
|
-
if (updates[key] !== undefined) {
|
|
197
|
-
const value = updates[key];
|
|
198
|
-
if (typeof value === "string" && !value.startsWith('"')) {
|
|
199
|
-
return `${key}="${value}"`;
|
|
200
|
-
}
|
|
201
|
-
return `${key}=${value}`;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return line;
|
|
205
|
-
});
|
|
206
|
-
envContent = updatedLines.join("\n");
|
|
207
|
-
|
|
208
|
-
fs.writeFileSync(envPath, envContent, "utf-8");
|
|
209
|
-
console.log(chalk.green("[OK] Created and configured .env file."));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
this.updatePackageJson();
|
|
213
|
-
|
|
214
|
-
if (dbType !== "none") {
|
|
215
|
-
console.log(
|
|
216
|
-
chalk.cyan(`[INFO] Installing ${dbType} and redis packages...`),
|
|
217
|
-
);
|
|
218
|
-
try {
|
|
219
|
-
let packagesToInstall = ["redis"];
|
|
220
|
-
if (dbType === "postgres") {
|
|
221
|
-
packagesToInstall.push("pg");
|
|
222
|
-
} else {
|
|
223
|
-
packagesToInstall.push("mysql2");
|
|
224
|
-
}
|
|
225
|
-
execSync(`npm install ${packagesToInstall.join(" ")}`, {
|
|
226
|
-
stdio: "inherit",
|
|
227
|
-
cwd: this.appDir,
|
|
228
|
-
});
|
|
229
|
-
console.log(
|
|
230
|
-
chalk.green(
|
|
231
|
-
`[OK] Successfully installed ${packagesToInstall.join(" ")}.`,
|
|
232
|
-
),
|
|
233
|
-
);
|
|
234
|
-
} catch (error) {
|
|
235
|
-
console.warn(
|
|
236
|
-
chalk.yellow(
|
|
237
|
-
"[ERROR] Automatic package installation failed. Please run 'npm install' manually.",
|
|
238
|
-
),
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
console.log(chalk.blue("\nBlue Bird initialization completed!"));
|
|
244
|
-
console.log(chalk.white("Next steps:"));
|
|
245
|
-
console.log(chalk.bold(" npm install"));
|
|
246
|
-
console.log(chalk.bold(" npm run dev"));
|
|
247
|
-
} catch (error) {
|
|
248
|
-
console.error(
|
|
249
|
-
chalk.red("[ERROR] Error during initialization:"),
|
|
250
|
-
error.message,
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Updates package.json with needed scripts and module type if not set.
|
|
257
|
-
*/
|
|
258
|
-
updatePackageJson() {
|
|
259
|
-
const pkgPath = path.join(this.appDir, "package.json");
|
|
260
|
-
if (fs.existsSync(pkgPath)) {
|
|
261
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
262
|
-
pkg.scripts = pkg.scripts || {};
|
|
263
|
-
|
|
264
|
-
const scriptsToAdd = {
|
|
265
|
-
dev: "node --watch --env-file=.env index.js",
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (command === "route") import("./route.js");
|
|
335
|
-
else if (command === "swagger-install") import("./swagger.js");
|
|
336
|
-
else if (command === "docker") import("./docker.js");
|
|
337
|
-
else initializer.run();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
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";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Initializes a new Blue Bird project by copying the base structure.
|
|
13
|
+
*/
|
|
14
|
+
class ProjectInit {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.appDir = process.cwd();
|
|
17
|
+
this.sourceDir = path.resolve(import.meta.dirname, "../../");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Runs the project initialization process.
|
|
22
|
+
*/
|
|
23
|
+
async run() {
|
|
24
|
+
console.log(chalk.cyan("Starting Blue Bird project initialization..."));
|
|
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 dbType = "none";
|
|
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 =
|
|
40
|
+
defaultValue !== undefined
|
|
41
|
+
? `${query} [${defaultValue}]: `
|
|
42
|
+
: `${query}: `;
|
|
43
|
+
const answer = await rl.question(formattedQuery);
|
|
44
|
+
return answer.trim() || defaultValue;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
title = await ask("Project Title", title);
|
|
48
|
+
const portInput = await ask("Server Port", port);
|
|
49
|
+
port = parseInt(portInput, 10);
|
|
50
|
+
if (Number.isNaN(port)) {
|
|
51
|
+
port = 3000;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const defaultAppUrl = `http://localhost:${port}`;
|
|
55
|
+
appUrl = await ask("Application URL", defaultAppUrl);
|
|
56
|
+
|
|
57
|
+
const dbTypeAns = await ask(
|
|
58
|
+
"Which database do you want to configure? (none / mysql / postgres)",
|
|
59
|
+
"none",
|
|
60
|
+
);
|
|
61
|
+
const cleanDbTypeAns = dbTypeAns.toLowerCase().trim();
|
|
62
|
+
if (
|
|
63
|
+
cleanDbTypeAns === "postgres" ||
|
|
64
|
+
cleanDbTypeAns === "postgresql" ||
|
|
65
|
+
cleanDbTypeAns === "pg" ||
|
|
66
|
+
cleanDbTypeAns === "postgr"
|
|
67
|
+
) {
|
|
68
|
+
dbType = "postgres";
|
|
69
|
+
} else if (cleanDbTypeAns === "mysql") {
|
|
70
|
+
dbType = "mysql";
|
|
71
|
+
} else {
|
|
72
|
+
dbType = "none";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (dbType !== "none") {
|
|
76
|
+
dbName = await ask("Database Name", dbName);
|
|
77
|
+
dbUser = await ask(
|
|
78
|
+
"Database User",
|
|
79
|
+
dbType === "postgres" ? "postgres" : "root",
|
|
80
|
+
);
|
|
81
|
+
dbPassword = await ask("Database Password", dbPassword);
|
|
82
|
+
const defaultDbPort = dbType === "postgres" ? 5432 : 3306;
|
|
83
|
+
const dbPortInput = await ask("Database Port", defaultDbPort);
|
|
84
|
+
dbPort = parseInt(dbPortInput, 10);
|
|
85
|
+
if (Number.isNaN(dbPort)) {
|
|
86
|
+
dbPort = defaultDbPort;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(
|
|
91
|
+
chalk.red("[ERROR] Error reading configuration input:"),
|
|
92
|
+
error.message,
|
|
93
|
+
);
|
|
94
|
+
rl.close();
|
|
95
|
+
return;
|
|
96
|
+
} finally {
|
|
97
|
+
rl.close();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const itemsToCopy = [
|
|
101
|
+
"backend",
|
|
102
|
+
"frontend",
|
|
103
|
+
"docker",
|
|
104
|
+
".env_example",
|
|
105
|
+
"AGENTS.md",
|
|
106
|
+
"index.js",
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
itemsToCopy.forEach((item) => {
|
|
111
|
+
const src = path.join(this.sourceDir, item);
|
|
112
|
+
const dest = path.join(this.appDir, item);
|
|
113
|
+
|
|
114
|
+
if (fs.existsSync(src)) {
|
|
115
|
+
if (!fs.existsSync(dest)) {
|
|
116
|
+
this.copyRecursive(src, dest);
|
|
117
|
+
console.log(chalk.green(`[OK] Copied ${item} to root.`));
|
|
118
|
+
} else {
|
|
119
|
+
console.log(
|
|
120
|
+
chalk.yellow(`[SKIP] ${item} already exists, skipping.`),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
console.warn(
|
|
125
|
+
chalk.red(`[ERROR] Source ${item} not found in ${this.sourceDir}`),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const composeTemplateName =
|
|
131
|
+
dbType === "postgres"
|
|
132
|
+
? "docker-compose.postgres.yml"
|
|
133
|
+
: dbType === "mysql"
|
|
134
|
+
? "docker-compose.mysql.yml"
|
|
135
|
+
: "docker-compose.none.yml";
|
|
136
|
+
const composeSrc = path.join(
|
|
137
|
+
this.sourceDir,
|
|
138
|
+
"docker",
|
|
139
|
+
composeTemplateName,
|
|
140
|
+
);
|
|
141
|
+
const composeDest = path.join(this.appDir, "docker-compose.yml");
|
|
142
|
+
if (fs.existsSync(composeSrc)) {
|
|
143
|
+
if (!fs.existsSync(composeDest)) {
|
|
144
|
+
fs.copyFileSync(composeSrc, composeDest);
|
|
145
|
+
console.log(
|
|
146
|
+
chalk.green(`[OK] Created docker-compose.yml (${dbType} mode).`),
|
|
147
|
+
);
|
|
148
|
+
} else {
|
|
149
|
+
console.log(
|
|
150
|
+
chalk.yellow(`[SKIP] docker-compose.yml already exists, skipping.`),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
const fallbackSrc = path.join(this.sourceDir, "docker-compose.yml");
|
|
155
|
+
if (fs.existsSync(fallbackSrc) && !fs.existsSync(composeDest)) {
|
|
156
|
+
fs.copyFileSync(fallbackSrc, composeDest);
|
|
157
|
+
console.log(chalk.green(`[OK] Created docker-compose.yml.`));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const envPath = path.join(this.appDir, ".env");
|
|
162
|
+
const envExamplePath = path.join(this.appDir, ".env_example");
|
|
163
|
+
|
|
164
|
+
if (fs.existsSync(envExamplePath)) {
|
|
165
|
+
let envContent = fs.readFileSync(envExamplePath, "utf-8");
|
|
166
|
+
|
|
167
|
+
const jwtSecret = crypto.randomBytes(32).toString("hex");
|
|
168
|
+
|
|
169
|
+
const updates = {
|
|
170
|
+
TITLE: title,
|
|
171
|
+
PORT: port,
|
|
172
|
+
APP_URL: appUrl,
|
|
173
|
+
JWT_SECRET: jwtSecret,
|
|
174
|
+
DB_TYPE: dbType,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (dbType === "mysql") {
|
|
178
|
+
updates.DB_NAME = dbName;
|
|
179
|
+
updates.DB_USER = dbUser;
|
|
180
|
+
updates.DB_PASSWORD = dbPassword;
|
|
181
|
+
updates.DB_PORT = dbPort;
|
|
182
|
+
updates.DATABASE_URL = `mysql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}`;
|
|
183
|
+
} else if (dbType === "postgres") {
|
|
184
|
+
updates.DB_NAME = dbName;
|
|
185
|
+
updates.DB_USER = dbUser;
|
|
186
|
+
updates.DB_PASSWORD = dbPassword;
|
|
187
|
+
updates.DB_PORT = dbPort;
|
|
188
|
+
updates.DATABASE_URL = `postgresql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}?schema=public`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const lines = envContent.split(/\r?\n/);
|
|
192
|
+
const updatedLines = lines.map((line) => {
|
|
193
|
+
const match = line.match(/^([A-Z_]+)=(.+)/);
|
|
194
|
+
if (match) {
|
|
195
|
+
const key = match[1];
|
|
196
|
+
if (updates[key] !== undefined) {
|
|
197
|
+
const value = updates[key];
|
|
198
|
+
if (typeof value === "string" && !value.startsWith('"')) {
|
|
199
|
+
return `${key}="${value}"`;
|
|
200
|
+
}
|
|
201
|
+
return `${key}=${value}`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return line;
|
|
205
|
+
});
|
|
206
|
+
envContent = updatedLines.join("\n");
|
|
207
|
+
|
|
208
|
+
fs.writeFileSync(envPath, envContent, "utf-8");
|
|
209
|
+
console.log(chalk.green("[OK] Created and configured .env file."));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.updatePackageJson();
|
|
213
|
+
|
|
214
|
+
if (dbType !== "none") {
|
|
215
|
+
console.log(
|
|
216
|
+
chalk.cyan(`[INFO] Installing ${dbType} and redis packages...`),
|
|
217
|
+
);
|
|
218
|
+
try {
|
|
219
|
+
let packagesToInstall = ["redis"];
|
|
220
|
+
if (dbType === "postgres") {
|
|
221
|
+
packagesToInstall.push("pg");
|
|
222
|
+
} else {
|
|
223
|
+
packagesToInstall.push("mysql2");
|
|
224
|
+
}
|
|
225
|
+
execSync(`npm install ${packagesToInstall.join(" ")}`, {
|
|
226
|
+
stdio: "inherit",
|
|
227
|
+
cwd: this.appDir,
|
|
228
|
+
});
|
|
229
|
+
console.log(
|
|
230
|
+
chalk.green(
|
|
231
|
+
`[OK] Successfully installed ${packagesToInstall.join(" ")}.`,
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.warn(
|
|
236
|
+
chalk.yellow(
|
|
237
|
+
"[ERROR] Automatic package installation failed. Please run 'npm install' manually.",
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log(chalk.blue("\nBlue Bird initialization completed!"));
|
|
244
|
+
console.log(chalk.white("Next steps:"));
|
|
245
|
+
console.log(chalk.bold(" npm install"));
|
|
246
|
+
console.log(chalk.bold(" npm run dev"));
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error(
|
|
249
|
+
chalk.red("[ERROR] Error during initialization:"),
|
|
250
|
+
error.message,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Updates package.json with needed scripts and module type if not set.
|
|
257
|
+
*/
|
|
258
|
+
updatePackageJson() {
|
|
259
|
+
const pkgPath = path.join(this.appDir, "package.json");
|
|
260
|
+
if (fs.existsSync(pkgPath)) {
|
|
261
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
262
|
+
pkg.scripts = pkg.scripts || {};
|
|
263
|
+
|
|
264
|
+
const scriptsToAdd = {
|
|
265
|
+
dev: "node --watch --env-file=.env index.js",
|
|
266
|
+
start: "node --env-file=.env index.js",
|
|
267
|
+
init: "blue-bird",
|
|
268
|
+
route: "blue-bird route",
|
|
269
|
+
"swagger-install": "blue-bird swagger-install",
|
|
270
|
+
docker: "blue-bird docker",
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
let updated = false;
|
|
274
|
+
for (const [key, value] of Object.entries(scriptsToAdd)) {
|
|
275
|
+
if (!pkg.scripts[key]) {
|
|
276
|
+
pkg.scripts[key] = value;
|
|
277
|
+
updated = true;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (pkg.type !== "module") {
|
|
282
|
+
pkg.type = "module";
|
|
283
|
+
updated = true;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (updated) {
|
|
287
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
288
|
+
console.log(chalk.green("[OK] Updated package.json configuration."));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Copies a file or directory recursively.
|
|
295
|
+
* @param {string} src - Source path.
|
|
296
|
+
* @param {string} dest - Destination path.
|
|
297
|
+
*/
|
|
298
|
+
copyRecursive(src, dest) {
|
|
299
|
+
const stats = fs.statSync(src);
|
|
300
|
+
const isDirectory = stats.isDirectory();
|
|
301
|
+
|
|
302
|
+
if (isDirectory) {
|
|
303
|
+
if (!fs.existsSync(dest)) {
|
|
304
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
305
|
+
}
|
|
306
|
+
fs.readdirSync(src).forEach((childItemName) => {
|
|
307
|
+
if (
|
|
308
|
+
childItemName === "node_modules" ||
|
|
309
|
+
childItemName === "dist" ||
|
|
310
|
+
childItemName === ".git"
|
|
311
|
+
) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
this.copyRecursive(
|
|
315
|
+
path.join(src, childItemName),
|
|
316
|
+
path.join(dest, childItemName),
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
fs.copyFileSync(src, dest);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const initializer = new ProjectInit();
|
|
326
|
+
|
|
327
|
+
const args = process.argv.slice(2);
|
|
328
|
+
const command = args[0];
|
|
329
|
+
|
|
330
|
+
if (command === "route") import("./route.js");
|
|
331
|
+
else if (command === "swagger-install") import("./swagger.js");
|
|
332
|
+
else if (command === "docker") import("./docker.js");
|
|
333
|
+
else initializer.run();
|