create-sonamu 0.1.18 → 0.2.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/README.md +69 -65
- package/catalog.json +9 -12
- package/index.js +444 -506
- package/package.json +24 -25
- package/template/src/.oxfmtrc.json +23 -0
- package/template/src/.oxlintrc.json +24 -0
- package/template/src/.zed/settings.json +14 -14
- package/template/src/package.json +5 -4
- package/template/src/packages/api/custom-sequencer.ts +1 -1
- package/template/src/packages/api/package.json +7 -7
- package/template/src/packages/api/package.json.bak +2 -2
- package/template/src/packages/api/src/index.ts +1 -1
- package/template/src/packages/api/src/typings/fastify.d.ts +1 -1
- package/template/src/packages/api/src/typings/sonamu.d.ts +2 -3
- package/template/src/packages/api/tsconfig.json +2 -1
- package/template/src/packages/web/package.json +4 -4
- package/template/src/packages/web/package.json.bak +1 -1
- package/template/src/packages/web/src/admin-common/ApiLogViewer.tsx +4 -4
- package/template/src/packages/web/src/entry-client.tsx +2 -2
- package/template/src/packages/web/src/services/sonamu.shared.ts +2 -2
- package/template/src/packages/web/tsconfig.app.json +3 -1
- package/template/src/packages/web/tsconfig.node.json +2 -1
- package/template/src/packages/web/vite.config.ts +6 -5
- package/template/src/biome.json +0 -84
- package/template/src/packages/api/.swcrc +0 -18
package/index.js
CHANGED
|
@@ -1,50 +1,3 @@
|
|
|
1
|
-
var __assign = (this && this.__assign) || function () {
|
|
2
|
-
__assign = Object.assign || function(t) {
|
|
3
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
-
s = arguments[i];
|
|
5
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
-
t[p] = s[p];
|
|
7
|
-
}
|
|
8
|
-
return t;
|
|
9
|
-
};
|
|
10
|
-
return __assign.apply(this, arguments);
|
|
11
|
-
};
|
|
12
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
13
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
14
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
15
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
16
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
17
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
18
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
22
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
23
|
-
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
24
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
25
|
-
function step(op) {
|
|
26
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
27
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
28
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
29
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
30
|
-
switch (op[0]) {
|
|
31
|
-
case 0: case 1: t = op; break;
|
|
32
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
33
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
34
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
35
|
-
default:
|
|
36
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
37
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
38
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
39
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
40
|
-
if (t[2]) _.ops.pop();
|
|
41
|
-
_.trys.pop(); continue;
|
|
42
|
-
}
|
|
43
|
-
op = body.call(thisArg, _);
|
|
44
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
45
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
1
|
import { spawn } from "node:child_process";
|
|
49
2
|
import * as fs from "node:fs";
|
|
50
3
|
import * as path from "node:path";
|
|
@@ -54,479 +7,464 @@ import minimist from "minimist";
|
|
|
54
7
|
import ora from "ora";
|
|
55
8
|
import prompts from "prompts";
|
|
56
9
|
// 생성된 파일/디렉토리 전역에서 추적하기 위한 변수
|
|
57
|
-
|
|
58
|
-
|
|
10
|
+
let createdTargetRoot = null;
|
|
11
|
+
let isCleaningUp = false;
|
|
59
12
|
// Helper: catalog.json에서 catalog 파싱
|
|
60
13
|
function loadCatalogJson() {
|
|
61
|
-
|
|
14
|
+
const catalogPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "catalog.json");
|
|
62
15
|
if (!fs.existsSync(catalogPath))
|
|
63
16
|
return {};
|
|
64
17
|
return JSON.parse(fs.readFileSync(catalogPath, "utf-8"));
|
|
65
18
|
}
|
|
66
|
-
function init() {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return "Project name is required";
|
|
122
|
-
}
|
|
123
|
-
if (value.includes(" ")) {
|
|
124
|
-
return "Project name cannot contain spaces";
|
|
125
|
-
}
|
|
126
|
-
if (value.includes("-")) {
|
|
127
|
-
return "Project name cannot contain hyphens";
|
|
128
|
-
}
|
|
129
|
-
return true;
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
], {
|
|
133
|
-
onCancel: createCancelHandler(),
|
|
134
|
-
})];
|
|
135
|
-
case 3:
|
|
136
|
-
result = _e.sent();
|
|
137
|
-
_e.label = 4;
|
|
138
|
-
case 4: return [3 /*break*/, 6];
|
|
139
|
-
case 5:
|
|
140
|
-
e_1 = _e.sent();
|
|
141
|
-
cleanup();
|
|
142
|
-
console.error(e_1);
|
|
143
|
-
process.exit(1);
|
|
144
|
-
return [3 /*break*/, 6];
|
|
145
|
-
case 6:
|
|
146
|
-
targetDir = result.targetDir;
|
|
147
|
-
targetRoot = path.join(process.cwd(), targetDir);
|
|
148
|
-
if (!fs.existsSync(targetRoot)) return [3 /*break*/, 9];
|
|
149
|
-
overwrite = useDefaults;
|
|
150
|
-
if (!!useDefaults) return [3 /*break*/, 8];
|
|
151
|
-
return [4 /*yield*/, prompts({
|
|
152
|
-
type: "confirm",
|
|
153
|
-
name: "overwrite",
|
|
154
|
-
message: "Directory ".concat(targetRoot, " already exists. Overwrite?"),
|
|
155
|
-
initial: true,
|
|
156
|
-
}, {
|
|
157
|
-
onCancel: createCancelHandler(),
|
|
158
|
-
})];
|
|
159
|
-
case 7:
|
|
160
|
-
result_1 = _e.sent();
|
|
161
|
-
overwrite = result_1.overwrite;
|
|
162
|
-
_e.label = 8;
|
|
163
|
-
case 8:
|
|
164
|
-
if (!overwrite) {
|
|
165
|
-
console.log(chalk.yellow("Operation cancelled."));
|
|
166
|
-
process.exit(0);
|
|
167
|
-
}
|
|
168
|
-
// 기존 디렉토리 삭제
|
|
169
|
-
console.log(chalk.yellow("Removing existing directory: ".concat(targetRoot)));
|
|
170
|
-
removeDirectory(targetRoot);
|
|
171
|
-
_e.label = 9;
|
|
172
|
-
case 9:
|
|
173
|
-
createdTargetRoot = targetRoot; // 생성된 디렉토리 추적 시작
|
|
174
|
-
templateRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "template", "src");
|
|
175
|
-
// 복사 시작 전에 타겟 프로젝트 폴더 생성
|
|
176
|
-
if (!fs.existsSync(targetRoot)) {
|
|
177
|
-
fs.mkdirSync(targetRoot, { recursive: true });
|
|
178
|
-
}
|
|
179
|
-
copy = function (src, dest) {
|
|
180
|
-
var stat = fs.statSync(src);
|
|
181
|
-
var basename = path.basename(src);
|
|
182
|
-
// 제외할 디렉토리/파일 목록
|
|
183
|
-
var excludeList = ["dist", ".git", ".gitkeep", "node_modules", "pnpm-lock.yaml"];
|
|
184
|
-
if (excludeList.includes(basename)) {
|
|
185
|
-
if (basename === ".gitkeep") {
|
|
186
|
-
console.log("".concat(chalk.green("CREATE"), " ").concat(dest.split(".gitkeep")[0]));
|
|
187
|
-
}
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (stat.isDirectory()) {
|
|
191
|
-
// 디렉토리는 생성
|
|
192
|
-
if (!fs.existsSync(dest)) {
|
|
193
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
194
|
-
}
|
|
195
|
-
for (var _i = 0, _a = fs.readdirSync(src); _i < _a.length; _i++) {
|
|
196
|
-
var file = _a[_i];
|
|
197
|
-
var srcFile = path.resolve(src, file);
|
|
198
|
-
var destFile = path.resolve(dest, file);
|
|
199
|
-
copy(srcFile, destFile);
|
|
200
|
-
}
|
|
19
|
+
async function init() {
|
|
20
|
+
// Graceful shutdown 핸들러 설정
|
|
21
|
+
const shutdownHandler = () => {
|
|
22
|
+
cleanup();
|
|
23
|
+
process.exit(1);
|
|
24
|
+
};
|
|
25
|
+
process.on("SIGINT", shutdownHandler);
|
|
26
|
+
process.on("SIGTERM", shutdownHandler);
|
|
27
|
+
// CLI 인자 파싱
|
|
28
|
+
const argv = minimist(process.argv.slice(2), {
|
|
29
|
+
boolean: ["yes", "y", "skip-pnpm", "skip-docker"],
|
|
30
|
+
string: [
|
|
31
|
+
"db-user",
|
|
32
|
+
"db-password",
|
|
33
|
+
"db-name",
|
|
34
|
+
"container-name",
|
|
35
|
+
"docker-project",
|
|
36
|
+
"pnpm",
|
|
37
|
+
"docker",
|
|
38
|
+
],
|
|
39
|
+
alias: {
|
|
40
|
+
y: "yes",
|
|
41
|
+
"docker-pj-name": "docker-project", // --docker-pj-name은 --docker-project의 alias
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
// 첫 번째 인자를 프로젝트명으로 사용
|
|
45
|
+
const argProjectName = argv._[0];
|
|
46
|
+
const useDefaults = argv.yes || argv.y;
|
|
47
|
+
// Helper: 'y', 'yes', 'true', '1' → true / 'n', 'no', 'false', '0' → false / undefined → undefined
|
|
48
|
+
const parseYesNo = (value) => {
|
|
49
|
+
if (value === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
const lower = value.toLowerCase();
|
|
52
|
+
if (["y", "yes", "true", "1"].includes(lower))
|
|
53
|
+
return true;
|
|
54
|
+
if (["n", "no", "false", "0"].includes(lower))
|
|
55
|
+
return false;
|
|
56
|
+
return undefined;
|
|
57
|
+
};
|
|
58
|
+
let result;
|
|
59
|
+
try {
|
|
60
|
+
// argProjectName이 있으면 프롬프트 스킵
|
|
61
|
+
if (argProjectName) {
|
|
62
|
+
result = { targetDir: argProjectName };
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
result = await prompts([
|
|
66
|
+
{
|
|
67
|
+
type: "text",
|
|
68
|
+
name: "targetDir",
|
|
69
|
+
message: "Project name:",
|
|
70
|
+
initial: "my_sonamu_app",
|
|
71
|
+
validate: (value) => {
|
|
72
|
+
if (!value) {
|
|
73
|
+
return "Project name is required";
|
|
201
74
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
var destPath = dest;
|
|
205
|
-
if (basename === "gitignore") {
|
|
206
|
-
destPath = path.join(path.dirname(dest), ".gitignore");
|
|
207
|
-
}
|
|
208
|
-
if (basename === "env") {
|
|
209
|
-
destPath = path.join(path.dirname(dest), ".env");
|
|
210
|
-
}
|
|
211
|
-
fs.copyFileSync(src, destPath);
|
|
212
|
-
// 셸 스크립트는 실행권한 부여
|
|
213
|
-
if (basename.endsWith(".sh")) {
|
|
214
|
-
fs.chmodSync(destPath, 493);
|
|
215
|
-
}
|
|
216
|
-
console.log("".concat(chalk.green("CREATE"), " ").concat(destPath));
|
|
75
|
+
if (value.includes(" ")) {
|
|
76
|
+
return "Project name cannot contain spaces";
|
|
217
77
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
var src = path.join(templateRoot, file);
|
|
221
|
-
var dest = path.join(targetRoot, file);
|
|
222
|
-
copy(src, dest);
|
|
223
|
-
};
|
|
224
|
-
files = fs.readdirSync(templateRoot);
|
|
225
|
-
for (_i = 0, _a = files.filter(function (f) { return f !== "package.json"; }); _i < _a.length; _i++) {
|
|
226
|
-
file = _a[_i];
|
|
227
|
-
write(file);
|
|
228
|
-
}
|
|
229
|
-
// 2. Copy package.json and modify name
|
|
230
|
-
["packages/api", "packages/web"].forEach(function (dir) {
|
|
231
|
-
var pkgPath = path.join(templateRoot, dir, "package.json");
|
|
232
|
-
if (fs.existsSync(pkgPath)) {
|
|
233
|
-
var pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
234
|
-
var pkgType = dir.split("/")[1];
|
|
235
|
-
pkg.name = "".concat(targetDir, "-").concat(pkgType);
|
|
236
|
-
fs.writeFileSync(path.join(targetRoot, dir, "package.json"), JSON.stringify(pkg, null, 2));
|
|
78
|
+
if (value.includes("-")) {
|
|
79
|
+
return "Project name cannot contain hyphens";
|
|
237
80
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
cleanup();
|
|
293
|
-
throw error_1;
|
|
294
|
-
case 17: return [3 /*break*/, 19];
|
|
295
|
-
case 18:
|
|
296
|
-
console.log("\nTo set up pnpm, run the following commands:\n");
|
|
297
|
-
console.log(chalk.gray(" $ cd ".concat(targetRoot)));
|
|
298
|
-
console.log(chalk.gray(" $ pnpm install"));
|
|
299
|
-
_e.label = 19;
|
|
300
|
-
case 19:
|
|
301
|
-
isDatabase = true;
|
|
302
|
-
dockerOption = parseYesNo(argv.docker);
|
|
303
|
-
if (!(argv["skip-docker"] || dockerOption === false)) return [3 /*break*/, 20];
|
|
304
|
-
// --skip-docker 또는 --docker n 옵션으로 스킵
|
|
305
|
-
isDatabase = false;
|
|
306
|
-
return [3 /*break*/, 23];
|
|
307
|
-
case 20:
|
|
308
|
-
if (!(dockerOption === true || useDefaults)) return [3 /*break*/, 21];
|
|
309
|
-
// --docker y 또는 --yes 옵션이면 자동 진행
|
|
310
|
-
isDatabase = true;
|
|
311
|
-
return [3 /*break*/, 23];
|
|
312
|
-
case 21: return [4 /*yield*/, prompts({
|
|
313
|
-
type: "confirm",
|
|
314
|
-
name: "isDatabase",
|
|
315
|
-
message: "Would you like to set up a database using Docker?",
|
|
316
|
-
initial: true,
|
|
317
|
-
}, {
|
|
318
|
-
onCancel: createCancelHandler(),
|
|
319
|
-
})];
|
|
320
|
-
case 22:
|
|
321
|
-
result_3 = _e.sent();
|
|
322
|
-
isDatabase = result_3.isDatabase;
|
|
323
|
-
_e.label = 23;
|
|
324
|
-
case 23:
|
|
325
|
-
if (!isDatabase) return [3 /*break*/, 28];
|
|
326
|
-
console.log("\nSetting up a database using Docker...");
|
|
327
|
-
useDbDefaults = useDefaults || dockerOption === true;
|
|
328
|
-
answers = void 0;
|
|
329
|
-
_e.label = 24;
|
|
330
|
-
case 24:
|
|
331
|
-
_e.trys.push([24, 26, , 27]);
|
|
332
|
-
return [4 /*yield*/, promptDatabase(targetDir, argv, useDbDefaults)];
|
|
333
|
-
case 25:
|
|
334
|
-
answers = _e.sent();
|
|
335
|
-
return [3 /*break*/, 27];
|
|
336
|
-
case 26:
|
|
337
|
-
error_2 = _e.sent();
|
|
338
|
-
cleanup();
|
|
339
|
-
throw error_2;
|
|
340
|
-
case 27:
|
|
341
|
-
env = "# Database Configuration\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=".concat((_d = answers.DB_USER) !== null && _d !== void 0 ? _d : "postgres", "\nDB_PASSWORD=").concat(answers.DB_PASSWORD, "\nCONTAINER_NAME=").concat(answers.CONTAINER_NAME, "\nDATABASE_NAME=").concat(answers.DATABASE_NAME, "\nPROJECT_NAME=").concat(targetDir, "\n");
|
|
342
|
-
fs.writeFileSync(path.join(targetRoot, "packages", "api", ".env"), env);
|
|
343
|
-
initSqlPath = path.join(targetRoot, "packages", "api", "database", "fixtures", "init.sql");
|
|
344
|
-
if (fs.existsSync(initSqlPath)) {
|
|
345
|
-
initSql = fs.readFileSync(initSqlPath, "utf-8");
|
|
346
|
-
initSql = initSql.replace(/\$\{DATABASE_NAME\}/g, answers.DATABASE_NAME);
|
|
347
|
-
fs.writeFileSync(initSqlPath, initSql);
|
|
348
|
-
}
|
|
349
|
-
return [3 /*break*/, 29];
|
|
350
|
-
case 28:
|
|
351
|
-
console.log("\nTo set up a database using Docker, run the following commands:\n");
|
|
352
|
-
console.log(chalk.gray(" $ cd ".concat(targetRoot, "/packages/api/database")));
|
|
353
|
-
console.log(chalk.gray(" $ docker compose -p ".concat(targetDir, " up -d")));
|
|
354
|
-
console.log("\nOr use your preferred database management tool.");
|
|
355
|
-
_e.label = 29;
|
|
356
|
-
case 29:
|
|
357
|
-
// 성공적으로 완료되면 cleanup 방지
|
|
358
|
-
createdTargetRoot = null;
|
|
359
|
-
return [2 /*return*/, targetRoot];
|
|
81
|
+
return true;
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
], {
|
|
85
|
+
onCancel: createCancelHandler(),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
cleanup();
|
|
91
|
+
console.error(e);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
const { targetDir } = result;
|
|
95
|
+
// 현재 실행 경로(cwd) 하위에 생성 (절대경로 안받음)
|
|
96
|
+
const targetRoot = path.join(process.cwd(), targetDir);
|
|
97
|
+
// 프로젝트 디렉토리가 이미 존재하는지 확인
|
|
98
|
+
if (fs.existsSync(targetRoot)) {
|
|
99
|
+
let overwrite = useDefaults; // --yes 옵션이면 자동으로 overwrite
|
|
100
|
+
if (!useDefaults) {
|
|
101
|
+
const result = await prompts({
|
|
102
|
+
type: "confirm",
|
|
103
|
+
name: "overwrite",
|
|
104
|
+
message: `Directory ${targetRoot} already exists. Overwrite?`,
|
|
105
|
+
initial: true,
|
|
106
|
+
}, {
|
|
107
|
+
onCancel: createCancelHandler(),
|
|
108
|
+
});
|
|
109
|
+
overwrite = result.overwrite;
|
|
110
|
+
}
|
|
111
|
+
if (!overwrite) {
|
|
112
|
+
console.log(chalk.yellow("Operation cancelled."));
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
// 기존 디렉토리 삭제
|
|
116
|
+
console.log(chalk.yellow(`Removing existing directory: ${targetRoot}`));
|
|
117
|
+
removeDirectory(targetRoot);
|
|
118
|
+
}
|
|
119
|
+
createdTargetRoot = targetRoot; // 생성된 디렉토리 추적 시작
|
|
120
|
+
// 템플릿 경로 설정 (src 포함)
|
|
121
|
+
const templateRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "template", "src");
|
|
122
|
+
// 복사 시작 전에 타겟 프로젝트 폴더 생성
|
|
123
|
+
if (!fs.existsSync(targetRoot)) {
|
|
124
|
+
fs.mkdirSync(targetRoot, { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
// 템플릿 파일 복사 함수
|
|
127
|
+
const copy = (src, dest) => {
|
|
128
|
+
const stat = fs.statSync(src);
|
|
129
|
+
const basename = path.basename(src);
|
|
130
|
+
// 제외할 디렉토리/파일 목록
|
|
131
|
+
const excludeList = ["dist", ".git", ".gitkeep", "node_modules", "pnpm-lock.yaml"];
|
|
132
|
+
if (excludeList.includes(basename)) {
|
|
133
|
+
if (basename === ".gitkeep") {
|
|
134
|
+
console.log(`${chalk.green("CREATE")} ${dest.split(".gitkeep")[0]}`);
|
|
360
135
|
}
|
|
361
|
-
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (stat.isDirectory()) {
|
|
139
|
+
// 디렉토리는 생성
|
|
140
|
+
if (!fs.existsSync(dest)) {
|
|
141
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
for (const file of fs.readdirSync(src)) {
|
|
144
|
+
const srcFile = path.resolve(src, file);
|
|
145
|
+
const destFile = path.resolve(dest, file);
|
|
146
|
+
copy(srcFile, destFile);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// 파일은 복사 (gitignore → .gitignore rename)
|
|
151
|
+
let destPath = dest;
|
|
152
|
+
if (basename === "gitignore") {
|
|
153
|
+
destPath = path.join(path.dirname(dest), ".gitignore");
|
|
154
|
+
}
|
|
155
|
+
if (basename === "env") {
|
|
156
|
+
destPath = path.join(path.dirname(dest), ".env");
|
|
157
|
+
}
|
|
158
|
+
fs.copyFileSync(src, destPath);
|
|
159
|
+
// 셸 스크립트는 실행권한 부여
|
|
160
|
+
if (basename.endsWith(".sh")) {
|
|
161
|
+
fs.chmodSync(destPath, 0o755);
|
|
162
|
+
}
|
|
163
|
+
console.log(`${chalk.green("CREATE")} ${destPath}`);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const write = (file) => {
|
|
167
|
+
const src = path.join(templateRoot, file);
|
|
168
|
+
const dest = path.join(targetRoot, file);
|
|
169
|
+
copy(src, dest);
|
|
170
|
+
};
|
|
171
|
+
// 1. Copy all files except package.json
|
|
172
|
+
const files = fs.readdirSync(templateRoot);
|
|
173
|
+
for (const file of files.filter((f) => f !== "package.json")) {
|
|
174
|
+
write(file);
|
|
175
|
+
}
|
|
176
|
+
// 2. Copy package.json and modify name
|
|
177
|
+
["packages/api", "packages/web"].forEach((dir) => {
|
|
178
|
+
const pkgPath = path.join(templateRoot, dir, "package.json");
|
|
179
|
+
if (fs.existsSync(pkgPath)) {
|
|
180
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
181
|
+
const pkgType = dir.split("/")[1];
|
|
182
|
+
pkg.name = `${targetDir}-${pkgType}`;
|
|
183
|
+
fs.writeFileSync(path.join(targetRoot, dir, "package.json"), JSON.stringify(pkg, null, 2));
|
|
184
|
+
}
|
|
362
185
|
});
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
186
|
+
// 4. Copy root package.json and modify name
|
|
187
|
+
const rootPkgPath = path.join(templateRoot, "package.json");
|
|
188
|
+
if (fs.existsSync(rootPkgPath)) {
|
|
189
|
+
const pkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
|
|
190
|
+
pkg.name = targetDir;
|
|
191
|
+
fs.writeFileSync(path.join(targetRoot, "package.json"), JSON.stringify(pkg, null, 2));
|
|
192
|
+
console.log(`${chalk.green("CREATE")} ${path.join(targetRoot, "package.json")}`);
|
|
193
|
+
}
|
|
194
|
+
// Load catalog from bundled catalog.json
|
|
195
|
+
const parentCatalog = loadCatalogJson();
|
|
196
|
+
// Build catalog for the new project
|
|
197
|
+
const catalogEntries = [];
|
|
198
|
+
for (const pkgName of Object.keys(parentCatalog).toSorted()) {
|
|
199
|
+
catalogEntries.push(` "${pkgName}": ${parentCatalog[pkgName]}`);
|
|
200
|
+
}
|
|
201
|
+
const workspaceContent = `packages:
|
|
202
|
+
- packages/api
|
|
203
|
+
- packages/web
|
|
204
|
+
|
|
205
|
+
catalog:
|
|
206
|
+
${catalogEntries.join("\n")}
|
|
207
|
+
|
|
208
|
+
onlyBuiltDependencies:
|
|
209
|
+
- "@parcel/watcher"
|
|
210
|
+
- bcrypt
|
|
211
|
+
- esbuild
|
|
212
|
+
- libpq
|
|
213
|
+
- sharp
|
|
214
|
+
- sodium-native
|
|
215
|
+
- unrs-resolver
|
|
216
|
+
|
|
217
|
+
overrides:
|
|
218
|
+
axios@<0.30.0: ">=0.30.0"
|
|
219
|
+
axios@<0.30.2: ">=0.30.2"
|
|
220
|
+
axios@>=0.8.1 <0.28.0: ">=0.28.0"
|
|
221
|
+
mdast-util-to-hast@>=13.0.0 <13.2.1: ">=13.2.1"
|
|
222
|
+
prismjs@<1.30.0: ">=1.30.0"
|
|
223
|
+
`;
|
|
224
|
+
fs.writeFileSync(path.join(targetRoot, "pnpm-workspace.yaml"), workspaceContent);
|
|
225
|
+
console.log(`${chalk.green("CREATE")} ${path.join(targetRoot, "pnpm-workspace.yaml")}`);
|
|
226
|
+
console.log(`\n🌲 Created project in ${targetRoot}\n`);
|
|
227
|
+
// 3. Set up pnpm
|
|
228
|
+
let isPnpm = true; // 기본값
|
|
229
|
+
const pnpmOption = parseYesNo(argv.pnpm);
|
|
230
|
+
if (argv["skip-pnpm"] || pnpmOption === false) {
|
|
231
|
+
// --skip-pnpm 또는 --pnpm n 옵션으로 스킵
|
|
232
|
+
isPnpm = false;
|
|
233
|
+
}
|
|
234
|
+
else if (pnpmOption || useDefaults) {
|
|
235
|
+
// --pnpm y 또는 --yes 옵션이면 자동 진행
|
|
236
|
+
isPnpm = true;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// 옵션이 없으면 프롬프트로 물어봄
|
|
240
|
+
const result = await prompts({
|
|
241
|
+
type: "confirm",
|
|
242
|
+
name: "isPnpm",
|
|
243
|
+
message: "Would you like to set up pnpm?",
|
|
244
|
+
initial: true,
|
|
245
|
+
}, {
|
|
246
|
+
onCancel: createCancelHandler(),
|
|
421
247
|
});
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
248
|
+
isPnpm = result.isPnpm;
|
|
249
|
+
}
|
|
250
|
+
if (isPnpm) {
|
|
251
|
+
try {
|
|
252
|
+
// Install dependencies from the root directory (workspace)
|
|
253
|
+
await setupPnpm(targetRoot);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
cleanup();
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
console.log(`\nTo set up pnpm, run the following commands:\n`);
|
|
262
|
+
console.log(chalk.gray(` $ cd ${targetRoot}`));
|
|
263
|
+
console.log(chalk.gray(` $ pnpm install`));
|
|
264
|
+
}
|
|
265
|
+
// 4. Set up Database using Docker
|
|
266
|
+
let isDatabase = true; // 기본값
|
|
267
|
+
const dockerOption = parseYesNo(argv.docker);
|
|
268
|
+
if (argv["skip-docker"] || dockerOption === false) {
|
|
269
|
+
// --skip-docker 또는 --docker n 옵션으로 스킵
|
|
270
|
+
isDatabase = false;
|
|
271
|
+
}
|
|
272
|
+
else if (dockerOption || useDefaults) {
|
|
273
|
+
// --docker y 또는 --yes 옵션이면 자동 진행
|
|
274
|
+
isDatabase = true;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// 옵션이 없으면 프롬프트로 물어봄
|
|
278
|
+
const result = await prompts({
|
|
279
|
+
type: "confirm",
|
|
280
|
+
name: "isDatabase",
|
|
281
|
+
message: "Would you like to set up a database using Docker?",
|
|
282
|
+
initial: true,
|
|
283
|
+
}, {
|
|
284
|
+
onCancel: createCancelHandler(),
|
|
447
285
|
});
|
|
448
|
-
|
|
286
|
+
isDatabase = result.isDatabase;
|
|
287
|
+
}
|
|
288
|
+
if (isDatabase) {
|
|
289
|
+
console.log(`\nSetting up a database using Docker...`);
|
|
290
|
+
// --docker y 옵션이 있으면 DB 옵션도 기본값 사용
|
|
291
|
+
const useDbDefaults = useDefaults || dockerOption;
|
|
292
|
+
// 프롬프트로 입력받은 DB 정보 .env 파일에 추가
|
|
293
|
+
let answers;
|
|
294
|
+
try {
|
|
295
|
+
answers = await promptDatabase(targetDir, argv, useDbDefaults);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
cleanup();
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
const env = `# Database Configuration
|
|
302
|
+
DB_HOST=0.0.0.0
|
|
303
|
+
DB_PORT=5432
|
|
304
|
+
DB_USER=${answers.DB_USER ?? "postgres"}
|
|
305
|
+
DB_PASSWORD=${answers.DB_PASSWORD}
|
|
306
|
+
CONTAINER_NAME=${answers.CONTAINER_NAME}
|
|
307
|
+
DATABASE_NAME=${answers.DATABASE_NAME}
|
|
308
|
+
PROJECT_NAME=${targetDir}
|
|
309
|
+
`;
|
|
310
|
+
fs.writeFileSync(path.join(targetRoot, "packages", "api", ".env"), env);
|
|
311
|
+
// init.sql 변수 치환
|
|
312
|
+
const initSqlPath = path.join(targetRoot, "packages", "api", "database", "fixtures", "init.sql");
|
|
313
|
+
if (fs.existsSync(initSqlPath)) {
|
|
314
|
+
let initSql = fs.readFileSync(initSqlPath, "utf-8");
|
|
315
|
+
initSql = initSql.replace(/\$\{DATABASE_NAME\}/g, answers.DATABASE_NAME);
|
|
316
|
+
fs.writeFileSync(initSqlPath, initSql);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
console.log(`\nTo set up a database using Docker, run the following commands:\n`);
|
|
321
|
+
console.log(chalk.gray(` $ cd ${targetRoot}/packages/api/database`));
|
|
322
|
+
console.log(chalk.gray(` $ docker compose -p ${targetDir} up -d`));
|
|
323
|
+
console.log(`\nOr use your preferred database management tool.`);
|
|
324
|
+
}
|
|
325
|
+
// 성공적으로 완료되면 cleanup 방지
|
|
326
|
+
createdTargetRoot = null;
|
|
327
|
+
return targetRoot;
|
|
449
328
|
}
|
|
450
|
-
function
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
329
|
+
async function executeCommand(command, args, cwd, options = {}) {
|
|
330
|
+
const { showOutput = false } = options;
|
|
331
|
+
const child = spawn(command, args, {
|
|
332
|
+
cwd,
|
|
333
|
+
stdio: ["inherit", "pipe", "pipe"], // stdin은 상속, stdout/stderr는 pipe로 처리
|
|
334
|
+
env: { ...process.env }, // 환경변수 상속
|
|
335
|
+
});
|
|
336
|
+
const spinner = ora(`Running ${command} ${args.join(" ")}`);
|
|
337
|
+
let startTime;
|
|
338
|
+
let success = true;
|
|
339
|
+
let output = "";
|
|
340
|
+
let errorOutput = "";
|
|
341
|
+
return new Promise((resolve, reject) => {
|
|
342
|
+
child.on("spawn", () => {
|
|
343
|
+
spinner.start();
|
|
344
|
+
startTime = Date.now();
|
|
345
|
+
});
|
|
346
|
+
child.stdout?.on("data", (data) => {
|
|
347
|
+
output += data.toString();
|
|
348
|
+
});
|
|
349
|
+
child.stderr?.on("data", (data) => {
|
|
350
|
+
errorOutput += data.toString();
|
|
351
|
+
});
|
|
352
|
+
child.on("error", (error) => {
|
|
353
|
+
success = false;
|
|
354
|
+
spinner.fail(`${command} ${args.join(" ")}`);
|
|
355
|
+
console.error(chalk.red(`🚨 Error: ${command}`));
|
|
356
|
+
console.error(error);
|
|
357
|
+
reject(error);
|
|
358
|
+
});
|
|
359
|
+
child.on("close", (code) => {
|
|
360
|
+
if (!success || code !== 0) {
|
|
361
|
+
if (code !== 0) {
|
|
362
|
+
spinner.fail(`${command} ${args.join(" ")}`);
|
|
363
|
+
console.error(chalk.red(`Command failed with exit code ${code}: ${command} ${args.join(" ")}`));
|
|
364
|
+
if (errorOutput) {
|
|
365
|
+
console.error(errorOutput);
|
|
480
366
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
initial: dbUser || "postgres",
|
|
493
|
-
},
|
|
494
|
-
{
|
|
495
|
-
type: containerName ? null : "text",
|
|
496
|
-
name: "CONTAINER_NAME",
|
|
497
|
-
message: "Enter the container name: ",
|
|
498
|
-
initial: containerName || "".concat(projectName, "-container"),
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
type: databaseName ? null : "text",
|
|
502
|
-
name: "DATABASE_NAME",
|
|
503
|
-
message: "Enter the database name: ",
|
|
504
|
-
initial: databaseName || projectName,
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
type: dbPassword ? null : "password",
|
|
508
|
-
name: "DB_PASSWORD",
|
|
509
|
-
message: "Enter the database password: ",
|
|
510
|
-
initial: dbPassword || "",
|
|
511
|
-
},
|
|
512
|
-
], {
|
|
513
|
-
onCancel: createCancelHandler(),
|
|
514
|
-
})];
|
|
515
|
-
case 1:
|
|
516
|
-
answers = _a.sent();
|
|
517
|
-
return [2 /*return*/, {
|
|
518
|
-
DOCKER_PROJECT_NAME: dockerProject || answers.DOCKER_PROJECT_NAME,
|
|
519
|
-
DB_USER: dbUser || answers.DB_USER,
|
|
520
|
-
CONTAINER_NAME: containerName || answers.CONTAINER_NAME,
|
|
521
|
-
DATABASE_NAME: databaseName || answers.DATABASE_NAME,
|
|
522
|
-
DB_PASSWORD: dbPassword || answers.DB_PASSWORD,
|
|
523
|
-
}];
|
|
367
|
+
reject(new Error(`Command failed with exit code ${code}`));
|
|
368
|
+
}
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const durationS = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
372
|
+
if (showOutput && output.trim()) {
|
|
373
|
+
spinner.succeed(`${command} ${args.join(" ")} ${chalk.dim(`${durationS}s`)}`);
|
|
374
|
+
console.log(chalk.cyan(output.trim()));
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
spinner.succeed(`${command} ${args.join(" ")} ${chalk.dim(`${durationS}s`)}`);
|
|
524
378
|
}
|
|
379
|
+
resolve("");
|
|
525
380
|
});
|
|
526
381
|
});
|
|
527
382
|
}
|
|
383
|
+
async function setupPnpm(projectName, dir) {
|
|
384
|
+
const cwd = dir ? path.resolve(projectName, dir) : path.resolve(projectName);
|
|
385
|
+
try {
|
|
386
|
+
console.log(chalk.blue(`Setting up pnpm in ${cwd}...`));
|
|
387
|
+
await executeCommand("pnpm", ["install"], cwd);
|
|
388
|
+
console.log(chalk.green(`✅ pnpm has been set up in ${cwd}\n`));
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
console.error(chalk.red(`❌ Failed to set up pnpm in ${cwd}`));
|
|
392
|
+
console.error(error);
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async function promptDatabase(projectName, argv, useDefaults) {
|
|
397
|
+
// CLI 옵션에서 값 가져오기
|
|
398
|
+
const dockerProject = argv["docker-project"];
|
|
399
|
+
const dbUser = argv["db-user"];
|
|
400
|
+
const containerName = argv["container-name"];
|
|
401
|
+
const databaseName = argv["db-name"];
|
|
402
|
+
const dbPassword = argv["db-password"];
|
|
403
|
+
// 모든 값이 제공되었으면 프롬프트 스킵
|
|
404
|
+
if (dockerProject && dbUser && containerName && databaseName && dbPassword) {
|
|
405
|
+
return {
|
|
406
|
+
DOCKER_PROJECT_NAME: dockerProject,
|
|
407
|
+
DB_USER: dbUser,
|
|
408
|
+
CONTAINER_NAME: containerName,
|
|
409
|
+
DATABASE_NAME: databaseName,
|
|
410
|
+
DB_PASSWORD: dbPassword,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
// --yes 옵션이면 기본값 사용
|
|
414
|
+
if (useDefaults) {
|
|
415
|
+
return {
|
|
416
|
+
DOCKER_PROJECT_NAME: dockerProject || `${projectName}-docker`,
|
|
417
|
+
DB_USER: dbUser || "postgres",
|
|
418
|
+
CONTAINER_NAME: containerName || `${projectName}-container`,
|
|
419
|
+
DATABASE_NAME: databaseName || projectName,
|
|
420
|
+
DB_PASSWORD: dbPassword || "1234",
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
// 일부만 제공되었거나 모두 없으면 프롬프트로 물어봄
|
|
424
|
+
const answers = await prompts([
|
|
425
|
+
{
|
|
426
|
+
type: dockerProject ? null : "text",
|
|
427
|
+
name: "DOCKER_PROJECT_NAME",
|
|
428
|
+
message: "Enter the Docker project name:",
|
|
429
|
+
initial: dockerProject || `${projectName}-docker`,
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
type: dbUser ? null : "text",
|
|
433
|
+
name: "DB_USER",
|
|
434
|
+
message: "Enter the database user: ",
|
|
435
|
+
initial: dbUser || "postgres",
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
type: containerName ? null : "text",
|
|
439
|
+
name: "CONTAINER_NAME",
|
|
440
|
+
message: "Enter the container name: ",
|
|
441
|
+
initial: containerName || `${projectName}-container`,
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
type: databaseName ? null : "text",
|
|
445
|
+
name: "DATABASE_NAME",
|
|
446
|
+
message: "Enter the database name: ",
|
|
447
|
+
initial: databaseName || projectName,
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
type: dbPassword ? null : "password",
|
|
451
|
+
name: "DB_PASSWORD",
|
|
452
|
+
message: "Enter the database password: ",
|
|
453
|
+
initial: dbPassword || "",
|
|
454
|
+
},
|
|
455
|
+
], {
|
|
456
|
+
onCancel: createCancelHandler(),
|
|
457
|
+
});
|
|
458
|
+
return {
|
|
459
|
+
DOCKER_PROJECT_NAME: dockerProject || answers.DOCKER_PROJECT_NAME,
|
|
460
|
+
DB_USER: dbUser || answers.DB_USER,
|
|
461
|
+
CONTAINER_NAME: containerName || answers.CONTAINER_NAME,
|
|
462
|
+
DATABASE_NAME: databaseName || answers.DATABASE_NAME,
|
|
463
|
+
DB_PASSWORD: dbPassword || answers.DB_PASSWORD,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
528
466
|
function createCancelHandler() {
|
|
529
|
-
return
|
|
467
|
+
return () => {
|
|
530
468
|
cleanup();
|
|
531
469
|
throw new Error("Operation cancelled.");
|
|
532
470
|
};
|
|
@@ -539,7 +477,7 @@ function removeDirectory(dirPath) {
|
|
|
539
477
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
540
478
|
}
|
|
541
479
|
catch (_error) {
|
|
542
|
-
console.error(chalk.yellow(
|
|
480
|
+
console.error(chalk.yellow(`Warning: Failed to remove ${dirPath}`));
|
|
543
481
|
}
|
|
544
482
|
}
|
|
545
483
|
function cleanup() {
|
|
@@ -551,19 +489,19 @@ function cleanup() {
|
|
|
551
489
|
try {
|
|
552
490
|
if (fs.existsSync(createdTargetRoot)) {
|
|
553
491
|
removeDirectory(createdTargetRoot);
|
|
554
|
-
console.log(chalk.green(
|
|
492
|
+
console.log(chalk.green(`Cleaned up ${createdTargetRoot}\n`));
|
|
555
493
|
}
|
|
556
494
|
}
|
|
557
495
|
catch (error) {
|
|
558
|
-
console.error(chalk.red(
|
|
496
|
+
console.error(chalk.red(`Failed to clean up: ${error}`));
|
|
559
497
|
}
|
|
560
498
|
}
|
|
561
499
|
init()
|
|
562
|
-
.then(
|
|
500
|
+
.then((createdTarget) => {
|
|
563
501
|
console.log(chalk.green("\nProject created successfully!\n"));
|
|
564
|
-
console.log(chalk.green(
|
|
502
|
+
console.log(chalk.green(`project was created in ${chalk.blue(createdTarget)}\n`));
|
|
565
503
|
})
|
|
566
|
-
.catch(
|
|
504
|
+
.catch((e) => {
|
|
567
505
|
cleanup();
|
|
568
506
|
console.error(e);
|
|
569
507
|
process.exit(1);
|