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/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
- var createdTargetRoot = null;
58
- var isCleaningUp = false;
10
+ let createdTargetRoot = null;
11
+ let isCleaningUp = false;
59
12
  // Helper: catalog.json에서 catalog 파싱
60
13
  function loadCatalogJson() {
61
- var catalogPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "catalog.json");
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
- return __awaiter(this, void 0, void 0, function () {
68
- var shutdownHandler, argv, argProjectName, useDefaults, parseYesNo, result, e_1, targetDir, targetRoot, overwrite, result_1, templateRoot, copy, write, files, _i, _a, file, rootPkgPath, pkg, parentCatalog, catalogEntries, _b, _c, pkgName, workspaceContent, isPnpm, pnpmOption, result_2, error_1, isDatabase, dockerOption, result_3, useDbDefaults, answers, error_2, env, initSqlPath, initSql;
69
- var _d;
70
- return __generator(this, function (_e) {
71
- switch (_e.label) {
72
- case 0:
73
- shutdownHandler = function () {
74
- cleanup();
75
- process.exit(1);
76
- };
77
- process.on("SIGINT", shutdownHandler);
78
- process.on("SIGTERM", shutdownHandler);
79
- argv = minimist(process.argv.slice(2), {
80
- boolean: ["yes", "y", "skip-pnpm", "skip-docker"],
81
- string: [
82
- "db-user",
83
- "db-password",
84
- "db-name",
85
- "container-name",
86
- "docker-project",
87
- "pnpm",
88
- "docker",
89
- ],
90
- alias: {
91
- y: "yes",
92
- "docker-pj-name": "docker-project", // --docker-pj-name은 --docker-project의 alias
93
- },
94
- });
95
- argProjectName = argv._[0];
96
- useDefaults = argv.yes || argv.y;
97
- parseYesNo = function (value) {
98
- if (value === undefined)
99
- return undefined;
100
- var lower = value.toLowerCase();
101
- if (["y", "yes", "true", "1"].includes(lower))
102
- return true;
103
- if (["n", "no", "false", "0"].includes(lower))
104
- return false;
105
- return undefined;
106
- };
107
- _e.label = 1;
108
- case 1:
109
- _e.trys.push([1, 5, , 6]);
110
- if (!argProjectName) return [3 /*break*/, 2];
111
- result = { targetDir: argProjectName };
112
- return [3 /*break*/, 4];
113
- case 2: return [4 /*yield*/, prompts([
114
- {
115
- type: "text",
116
- name: "targetDir",
117
- message: "Project name:",
118
- initial: "my_sonamu_app",
119
- validate: function (value) {
120
- if (!value) {
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
- else {
203
- // 파일은 복사 (gitignore .gitignore rename)
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
- write = function (file) {
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
- rootPkgPath = path.join(templateRoot, "package.json");
240
- if (fs.existsSync(rootPkgPath)) {
241
- pkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
242
- pkg.name = targetDir;
243
- fs.writeFileSync(path.join(targetRoot, "package.json"), JSON.stringify(pkg, null, 2));
244
- console.log("".concat(chalk.green("CREATE"), " ").concat(path.join(targetRoot, "package.json")));
245
- }
246
- parentCatalog = loadCatalogJson();
247
- catalogEntries = [];
248
- for (_b = 0, _c = Object.keys(parentCatalog).sort(); _b < _c.length; _b++) {
249
- pkgName = _c[_b];
250
- catalogEntries.push(" \"".concat(pkgName, "\": ").concat(parentCatalog[pkgName]));
251
- }
252
- workspaceContent = "packages:\n - packages/api\n - packages/web\n\ncatalog:\n".concat(catalogEntries.join("\n"), "\n\nonlyBuiltDependencies:\n - \"@parcel/watcher\"\n - \"@swc/core\"\n - bcrypt\n - esbuild\n - libpq\n - sharp\n - sodium-native\n - unrs-resolver\n\noverrides:\n axios@<0.30.0: \">=0.30.0\"\n axios@<0.30.2: \">=0.30.2\"\n axios@>=0.8.1 <0.28.0: \">=0.28.0\"\n mdast-util-to-hast@>=13.0.0 <13.2.1: \">=13.2.1\"\n prismjs@<1.30.0: \">=1.30.0\"\n");
253
- fs.writeFileSync(path.join(targetRoot, "pnpm-workspace.yaml"), workspaceContent);
254
- console.log("".concat(chalk.green("CREATE"), " ").concat(path.join(targetRoot, "pnpm-workspace.yaml")));
255
- console.log("\n\uD83C\uDF32 Created project in ".concat(targetRoot, "\n"));
256
- isPnpm = true;
257
- pnpmOption = parseYesNo(argv.pnpm);
258
- if (!(argv["skip-pnpm"] || pnpmOption === false)) return [3 /*break*/, 10];
259
- // --skip-pnpm 또는 --pnpm n 옵션으로 스킵
260
- isPnpm = false;
261
- return [3 /*break*/, 13];
262
- case 10:
263
- if (!(pnpmOption === true || useDefaults)) return [3 /*break*/, 11];
264
- // --pnpm y 또는 --yes 옵션이면 자동 진행
265
- isPnpm = true;
266
- return [3 /*break*/, 13];
267
- case 11: return [4 /*yield*/, prompts({
268
- type: "confirm",
269
- name: "isPnpm",
270
- message: "Would you like to set up pnpm?",
271
- initial: true,
272
- }, {
273
- onCancel: createCancelHandler(),
274
- })];
275
- case 12:
276
- result_2 = _e.sent();
277
- isPnpm = result_2.isPnpm;
278
- _e.label = 13;
279
- case 13:
280
- if (!isPnpm) return [3 /*break*/, 18];
281
- _e.label = 14;
282
- case 14:
283
- _e.trys.push([14, 16, , 17]);
284
- // Install dependencies from the root directory (workspace)
285
- return [4 /*yield*/, setupPnpm(targetRoot)];
286
- case 15:
287
- // Install dependencies from the root directory (workspace)
288
- _e.sent();
289
- return [3 /*break*/, 17];
290
- case 16:
291
- error_1 = _e.sent();
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
- function executeCommand(command_1, args_1, cwd_1) {
365
- return __awaiter(this, arguments, void 0, function (command, args, cwd, options) {
366
- var _a, showOutput, child, spinner, startTime, success, output, errorOutput;
367
- if (options === void 0) { options = {}; }
368
- return __generator(this, function (_b) {
369
- _a = options.showOutput, showOutput = _a === void 0 ? false : _a;
370
- child = spawn(command, args, {
371
- cwd: cwd,
372
- stdio: ["inherit", "pipe", "pipe"], // stdin은 상속, stdout/stderr는 pipe로 처리
373
- env: __assign({}, process.env), // 환경변수 상속
374
- });
375
- spinner = ora("Running ".concat(command, " ").concat(args.join(" ")));
376
- success = true;
377
- output = "";
378
- errorOutput = "";
379
- return [2 /*return*/, new Promise(function (resolve, reject) {
380
- var _a, _b;
381
- child.on("spawn", function () {
382
- spinner.start();
383
- startTime = Date.now();
384
- });
385
- (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", function (data) {
386
- output += data.toString();
387
- });
388
- (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on("data", function (data) {
389
- errorOutput += data.toString();
390
- });
391
- child.on("error", function (error) {
392
- success = false;
393
- spinner.fail("".concat(command, " ").concat(args.join(" ")));
394
- console.error(chalk.red("\uD83D\uDEA8 Error: ".concat(command)));
395
- console.error(error);
396
- reject(error);
397
- });
398
- child.on("close", function (code) {
399
- if (!success || code !== 0) {
400
- if (code !== 0) {
401
- spinner.fail("".concat(command, " ").concat(args.join(" ")));
402
- console.error(chalk.red("Command failed with exit code ".concat(code, ": ").concat(command, " ").concat(args.join(" "))));
403
- if (errorOutput) {
404
- console.error(errorOutput);
405
- }
406
- reject(new Error("Command failed with exit code ".concat(code)));
407
- }
408
- return;
409
- }
410
- var durationS = ((Date.now() - startTime) / 1000).toFixed(2);
411
- if (showOutput && output.trim()) {
412
- spinner.succeed("".concat(command, " ").concat(args.join(" "), " ").concat(chalk.dim("".concat(durationS, "s"))));
413
- console.log(chalk.cyan(output.trim()));
414
- }
415
- else {
416
- spinner.succeed("".concat(command, " ").concat(args.join(" "), " ").concat(chalk.dim("".concat(durationS, "s"))));
417
- }
418
- resolve("");
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
- function setupPnpm(projectName, dir) {
425
- return __awaiter(this, void 0, void 0, function () {
426
- var cwd, error_3;
427
- return __generator(this, function (_a) {
428
- switch (_a.label) {
429
- case 0:
430
- cwd = dir ? path.resolve(projectName, dir) : path.resolve(projectName);
431
- _a.label = 1;
432
- case 1:
433
- _a.trys.push([1, 3, , 4]);
434
- console.log(chalk.blue("Setting up pnpm in ".concat(cwd, "...")));
435
- return [4 /*yield*/, executeCommand("pnpm", ["install"], cwd)];
436
- case 2:
437
- _a.sent();
438
- console.log(chalk.green("\u2705 pnpm has been set up in ".concat(cwd, "\n")));
439
- return [3 /*break*/, 4];
440
- case 3:
441
- error_3 = _a.sent();
442
- console.error(chalk.red("\u274C Failed to set up pnpm in ".concat(cwd)));
443
- console.error(error_3);
444
- throw error_3;
445
- case 4: return [2 /*return*/];
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 promptDatabase(projectName, argv, useDefaults) {
451
- return __awaiter(this, void 0, void 0, function () {
452
- var dockerProject, dbUser, containerName, databaseName, dbPassword, answers;
453
- return __generator(this, function (_a) {
454
- switch (_a.label) {
455
- case 0:
456
- dockerProject = argv["docker-project"];
457
- dbUser = argv["db-user"];
458
- containerName = argv["container-name"];
459
- databaseName = argv["db-name"];
460
- dbPassword = argv["db-password"];
461
- // 모든 값이 제공되었으면 프롬프트 스킵
462
- if (dockerProject && dbUser && containerName && databaseName && dbPassword) {
463
- return [2 /*return*/, {
464
- DOCKER_PROJECT_NAME: dockerProject,
465
- DB_USER: dbUser,
466
- CONTAINER_NAME: containerName,
467
- DATABASE_NAME: databaseName,
468
- DB_PASSWORD: dbPassword,
469
- }];
470
- }
471
- // --yes 옵션이면 기본값 사용
472
- if (useDefaults) {
473
- return [2 /*return*/, {
474
- DOCKER_PROJECT_NAME: dockerProject || "".concat(projectName, "-docker"),
475
- DB_USER: dbUser || "postgres",
476
- CONTAINER_NAME: containerName || "".concat(projectName, "-container"),
477
- DATABASE_NAME: databaseName || projectName,
478
- DB_PASSWORD: dbPassword || "1234",
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
- return [4 /*yield*/, prompts([
482
- {
483
- type: dockerProject ? null : "text",
484
- name: "DOCKER_PROJECT_NAME",
485
- message: "Enter the Docker project name:",
486
- initial: dockerProject || "".concat(projectName, "-docker"),
487
- },
488
- {
489
- type: dbUser ? null : "text",
490
- name: "DB_USER",
491
- message: "Enter the database user: ",
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 function () {
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("Warning: Failed to remove ".concat(dirPath)));
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("Cleaned up ".concat(createdTargetRoot, "\n")));
492
+ console.log(chalk.green(`Cleaned up ${createdTargetRoot}\n`));
555
493
  }
556
494
  }
557
495
  catch (error) {
558
- console.error(chalk.red("Failed to clean up: ".concat(error)));
496
+ console.error(chalk.red(`Failed to clean up: ${error}`));
559
497
  }
560
498
  }
561
499
  init()
562
- .then(function (createdTarget) {
500
+ .then((createdTarget) => {
563
501
  console.log(chalk.green("\nProject created successfully!\n"));
564
- console.log(chalk.green("project was created in ".concat(chalk.blue(createdTarget), "\n")));
502
+ console.log(chalk.green(`project was created in ${chalk.blue(createdTarget)}\n`));
565
503
  })
566
- .catch(function (e) {
504
+ .catch((e) => {
567
505
  cleanup();
568
506
  console.error(e);
569
507
  process.exit(1);