@seip/blue-bird 0.7.5 → 0.7.6
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 +4 -6
- package/AGENTS.md +20 -12
- package/README.md +38 -14
- package/core/cli/docker.js +128 -41
- package/core/cli/init.js +296 -198
- package/core/database.js +138 -57
- package/docker/docker-compose.mysql.yml +92 -0
- package/docker/docker-compose.none.yml +68 -0
- package/docker/docker-compose.postgres.yml +93 -0
- package/docker-compose.yml +1 -2
- package/package.json +3 -4
- package/backend/logs/2026-07-14/info.log +0 -48
package/core/cli/init.js
CHANGED
|
@@ -12,220 +12,318 @@ import { execSync } from "node:child_process";
|
|
|
12
12
|
* Initializes a new Blue Bird project by copying the base structure.
|
|
13
13
|
*/
|
|
14
14
|
class ProjectInit {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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();
|
|
18
98
|
}
|
|
19
99
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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`;
|
|
73
189
|
}
|
|
74
190
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
itemsToCopy.forEach(item => {
|
|
87
|
-
const src = path.join(this.sourceDir, item);
|
|
88
|
-
const dest = path.join(this.appDir, item);
|
|
89
|
-
|
|
90
|
-
if (fs.existsSync(src)) {
|
|
91
|
-
if (!fs.existsSync(dest)) {
|
|
92
|
-
this.copyRecursive(src, dest);
|
|
93
|
-
console.log(chalk.green(`[OK] Copied ${item} to root.`));
|
|
94
|
-
} else {
|
|
95
|
-
console.log(chalk.yellow(`[SKIP] ${item} already exists, skipping.`));
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
console.warn(chalk.red(`[ERROR] Source ${item} not found in ${this.sourceDir}`));
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const envPath = path.join(this.appDir, ".env");
|
|
103
|
-
const envExamplePath = path.join(this.appDir, ".env_example");
|
|
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."));
|
|
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}`;
|
|
144
202
|
}
|
|
203
|
+
}
|
|
204
|
+
return line;
|
|
205
|
+
});
|
|
206
|
+
envContent = updatedLines.join("\n");
|
|
145
207
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
}
|
|
208
|
+
fs.writeFileSync(envPath, envContent, "utf-8");
|
|
209
|
+
console.log(chalk.green("[OK] Created and configured .env file."));
|
|
210
|
+
}
|
|
157
211
|
|
|
158
|
-
|
|
159
|
-
console.log(chalk.white("Next steps:"));
|
|
160
|
-
console.log(chalk.bold(" npm install"));
|
|
161
|
-
console.log(chalk.bold(" npm run dev"));
|
|
212
|
+
this.updatePackageJson();
|
|
162
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
|
+
);
|
|
163
234
|
} catch (error) {
|
|
164
|
-
|
|
235
|
+
console.warn(
|
|
236
|
+
chalk.yellow(
|
|
237
|
+
"[ERROR] Automatic package installation failed. Please run 'npm install' manually.",
|
|
238
|
+
),
|
|
239
|
+
);
|
|
165
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
|
+
);
|
|
166
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
|
+
"dev:astro": "astro dev --root frontend",
|
|
267
|
+
"dev:api": "node --watch --env-file=.env index.js",
|
|
268
|
+
start: "node --env-file=.env index.js",
|
|
269
|
+
build: "astro build --root frontend",
|
|
270
|
+
init: "blue-bird",
|
|
271
|
+
route: "blue-bird route",
|
|
272
|
+
"swagger-install": "blue-bird swagger-install",
|
|
273
|
+
docker: "blue-bird docker",
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
let updated = false;
|
|
277
|
+
for (const [key, value] of Object.entries(scriptsToAdd)) {
|
|
278
|
+
if (!pkg.scripts[key]) {
|
|
279
|
+
pkg.scripts[key] = value;
|
|
280
|
+
updated = true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
167
283
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const pkgPath = path.join(this.appDir, "package.json");
|
|
173
|
-
if (fs.existsSync(pkgPath)) {
|
|
174
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
175
|
-
pkg.scripts = pkg.scripts || {};
|
|
176
|
-
|
|
177
|
-
const scriptsToAdd = {
|
|
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",
|
|
183
|
-
"init": "blue-bird",
|
|
184
|
-
"route": "blue-bird route",
|
|
185
|
-
"swagger-install": "blue-bird swagger-install",
|
|
186
|
-
"docker": "blue-bird docker"
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
let updated = false;
|
|
190
|
-
for (const [key, value] of Object.entries(scriptsToAdd)) {
|
|
191
|
-
if (!pkg.scripts[key]) {
|
|
192
|
-
pkg.scripts[key] = value;
|
|
193
|
-
updated = true;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (pkg.type !== "module") {
|
|
198
|
-
pkg.type = "module";
|
|
199
|
-
updated = true;
|
|
200
|
-
}
|
|
284
|
+
if (pkg.type !== "module") {
|
|
285
|
+
pkg.type = "module";
|
|
286
|
+
updated = true;
|
|
287
|
+
}
|
|
201
288
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
289
|
+
if (updated) {
|
|
290
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
291
|
+
console.log(chalk.green("[OK] Updated package.json configuration."));
|
|
292
|
+
}
|
|
207
293
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Copies a file or directory recursively.
|
|
298
|
+
* @param {string} src - Source path.
|
|
299
|
+
* @param {string} dest - Destination path.
|
|
300
|
+
*/
|
|
301
|
+
copyRecursive(src, dest) {
|
|
302
|
+
const stats = fs.statSync(src);
|
|
303
|
+
const isDirectory = stats.isDirectory();
|
|
304
|
+
|
|
305
|
+
if (isDirectory) {
|
|
306
|
+
if (!fs.existsSync(dest)) {
|
|
307
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
308
|
+
}
|
|
309
|
+
fs.readdirSync(src).forEach((childItemName) => {
|
|
310
|
+
if (
|
|
311
|
+
childItemName === "node_modules" ||
|
|
312
|
+
childItemName === ".astro" ||
|
|
313
|
+
childItemName === "dist" ||
|
|
314
|
+
childItemName === ".git"
|
|
315
|
+
) {
|
|
316
|
+
return;
|
|
227
317
|
}
|
|
318
|
+
this.copyRecursive(
|
|
319
|
+
path.join(src, childItemName),
|
|
320
|
+
path.join(dest, childItemName),
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
fs.copyFileSync(src, dest);
|
|
228
325
|
}
|
|
326
|
+
}
|
|
229
327
|
}
|
|
230
328
|
|
|
231
329
|
const initializer = new ProjectInit();
|