@axis-ds/cli 1.6.1 → 1.7.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 CHANGED
@@ -45,7 +45,7 @@ axis list
45
45
 
46
46
  - `axis.config.json` 과 `cn()` 을 담은 `utils.ts` 를 만들고, `clsx` · `tailwind-merge` 가 `package.json` 에 없으면 `dependencies` 에 더합니다. 설치는 실행하지 않고 명령만 안내합니다.
47
47
  - `axis.config.json` 의 `registry` 는 기본값(`https://axis-design-system.pages.dev/r`)이거나, 이미 있던 config 의 값입니다. 종전 기본값 `https://axis.minu.best/r` 는 Cloudflare 403 이라 바꿨습니다. add · list 는 그 값을 새 기본값으로 바꿔 읽으며 한 줄 알립니다(위 「컴포넌트 추가」, 파일은 고치지 않음). init 을 다시 실행하면 그 값을 새 기본값으로 바꿔 쓰고 「registry 를 새 기본 주소로 갱신」으로 알립니다. 사용자가 지정한 다른 값은 init 도 그대로 둡니다.
48
- - `utils.ts` 가 이미 있으면 같은 내용이면 「유지(이미 같은 내용)」(다시 쓰지 않음), 다르면 init 의 utils 로 덮어쓰고 「덮어씀」으로 알립니다. `utils.ts` 쓰기는 `axis add` 와 같은 규칙입니다: 심볼릭 링크면 같은 내용일 때 「유지(이미 같은 내용, 링크)」로 두고, 내용이 다른 링크(프로젝트 안 · 밖) · 깨진 링크 · 디렉터리 · 읽을 수 없는 파일 · 다른 경로와 하드링크로 이어진 파일은 쓰지 않고 그 사실을 알린 뒤 rc 1 입니다. 이 판정은 쓰기 전에 하므로 이때는 `axis.config.json` · `package.json` 도 쓰지 않습니다. 다른 경로와 하드링크로 이어진 `axis.config.json` 을 다시 써야 할 때도 같습니다.
48
+ - `utils.ts` 가 이미 있으면 같은 내용이면 「유지(이미 같은 내용)」(다시 쓰지 않음), 다르면 init 의 utils 로 덮어쓰고 「덮어씀」으로 알립니다(터미널이 아닌 곳에서 `-y` 없이 실행하면 덮어쓰지 않고 rc 1, 아래 「터미널이 아닌 곳에서 실행」). `utils.ts` 쓰기는 `axis add` 와 같은 규칙입니다: 심볼릭 링크면 같은 내용일 때 「유지(이미 같은 내용, 링크)」로 두고, 내용이 다른 링크(프로젝트 안 · 밖) · 깨진 링크 · 디렉터리 · 읽을 수 없는 파일 · 다른 경로와 하드링크로 이어진 파일은 쓰지 않고 그 사실을 알린 뒤 rc 1 입니다. 이 판정은 쓰기 전에 하므로 이때는 `axis.config.json` · `package.json` 도 쓰지 않습니다. 다른 경로와 하드링크로 이어진 `axis.config.json` 을 다시 써야 할 때도 같습니다.
49
49
  - 경로 기본값(`-y` 값 · 대화형 초기값)은 실제 프로젝트를 보고 정합니다(1.6.0 부터). `componentsDir` 는 `src/` 가 있으면 `./src/components/ui`, 없으면 `./components/ui` 이고 `src/` 를 새로 만들지 않습니다. `globalCss` 는 `app/globals.css` · `src/app/globals.css` · `styles/globals.css` · `src/styles/globals.css` · `globals.css` 가운데 실제로 있는 첫 파일, `tailwindConfig` 는 있는 `tailwind.config.{ts,js,mjs,cjs}` 가운데 첫 파일입니다. 찾지 못하면 기본값을 기록하고 그 사실을 한 줄 알립니다. Tailwind 4 안내 제목도 이 경로를 씁니다.
50
50
  - config 가 이미 있으면 출력이 「생성 완료」가 아니라 사실대로 「유지(이미 같은 내용)」 또는 「갱신」입니다. 갱신은 init 이 다루는 키(`$schema` · `componentsDir` · `tailwindConfig` · `globalCss` · `registry`)만 바꾸고, 그 밖의 키(`brand` · `monetDir` · `template` · `agenticDir` 등)는 값과 순서를 그대로 둡니다(1.6.0 부터, 이전에는 빠졌음). 들여쓰기도 기존 파일을 따릅니다. 기존 `componentsDir` 는 그대로 두고, `globalCss` · `tailwindConfig` 는 가리키는 파일이 있으면 그대로, 없으면 찾은 파일로 바꾸고 알립니다(찾지 못하면 그대로 두고 알림). Tailwind 4 이상이면 `tailwindConfig` 를 지우고 알립니다. `registry` 가 종전 기본값이면 위처럼 새 기본값으로 바꿉니다. `AXIS_REGISTRY_URL` 은 config 에 기록하지 않고 add · list 를 실행할 때만 우선합니다.
51
51
  - 애니메이션 플러그인(`tailwindcss-animate` · `tw-animate-css`)이 `package.json` 에 없으면, Dialog · Select 등의 열고 닫는 애니메이션에 필요하다는 안내 한 줄을 냅니다(Tailwind 3 은 plugins, Tailwind 4 는 `@import "tw-animate-css";`).
@@ -112,6 +112,24 @@ axis theme add ontology
112
112
  - FAIL 이 하나라도 있으면 종료 코드 1 로 끝납니다. WARN 만 있으면 0 입니다(1.3.0 부터, 이전에는 결과와 관계없이 0).
113
113
  - MCP `axis.check_project` 도 같은 항목 · 문구를 냅니다.
114
114
 
115
+ ## 터미널이 아닌 곳에서 실행 (CI · 파이프 · `</dev/null`)
116
+
117
+ stdin 이 터미널이 아니면 질문을 띄우지 않습니다(1.7.0 부터). 이전에는 `axis add` 말고는 입력이 닫힌 곳(`</dev/null` · 빈 파이프)에서 질문 한 줄만 찍고 아무것도 하지 않은 채 rc 0 으로 끝났고, 파이프로 응답을 넣으면 그 값으로 동작했습니다. 질문의 성격에 따라 이렇게 합니다.
118
+
119
+ | 명령 | 터미널이 아닐 때 |
120
+ |---|---|
121
+ | `axis init`(`-y` 없음) | `-y` 와 같은 기본값으로 진행하고 쓴 값을 한 줄 알림. 단 내용이 다른 기존 `utils.ts` 가 있으면 덮어쓰지 않고 rc 1, 아무것도 쓰지 않음(`-y` 면 덮어씀) |
122
+ | `axis template init <name>`(`--dir` 없음) | 템플릿 이름 디렉터리(`./<name>`)로 진행하고 한 줄 알림 |
123
+ | `axis template init`(이름 없음) | rc 1, 템플릿 이름 안내(템플릿 선택에는 기본값이 없음) |
124
+ | `axis template init` 대상이 비어 있지 않음 | rc 1, 아무것도 쓰지 않음. `-y` 면 진행(템플릿과 같은 경로의 파일은 덮어씀) |
125
+ | `axis template apply` 충돌 파일 있음 | rc 1, 아무것도 쓰지 않음. `-y` 면 덮어씀, `--dry-run` 으로 미리보기 |
126
+ | `axis add` 내용이 다른 기존 파일 | `-y` 와 같이 건너뛰고 목록을 알림(종전과 같음) |
127
+ | `axis v0 import` · `axis monet import` | rc 1, 대화형 터미널에서 실행하라는 안내(`v0` 는 `axis v0 convert <file>` 도 안내) |
128
+
129
+ - `axis template init -y` 는 질문을 기본값으로 답하고, 비어 있지 않은 디렉터리도 진행합니다(`template apply -y` 와 같은 뜻). 터미널에서도 같습니다.
130
+ - 판정은 stdin 이 터미널인지 하나로 합니다. 그래서 `printf '\n' | axis template init dashboard` 처럼 파이프로 응답을 넣던 스크립트도 이제는 묻지 않고 위 표대로 동작합니다. 값은 `--dir` · `-y` 같은 옵션으로 주세요.
131
+ - Windows Git Bash(mintty)처럼 사람이 앞에 있어도 stdin 이 터미널로 잡히지 않는 환경에서는 질문 없이 위 표대로 동작할 수 있습니다(실측 전). 질문이 뜨지 않으면 `winpty npx axis ...` 로 실행하거나, PowerShell · Windows Terminal 에서 실행하거나, `--dir` · `-y` 같은 옵션으로 값을 주세요.
132
+
115
133
  ## 문서
116
134
 
117
135
  자세한 사용법은 [AXIS Design System 문서](https://axis-design-system.pages.dev)를 참고하세요.
@@ -1,8 +1,13 @@
1
+ import {
2
+ ask,
3
+ failNonInteractive,
4
+ isInteractive
5
+ } from "./chunk-HFMMK6R4.js";
6
+
1
7
  // src/v0.ts
2
8
  import chalk from "chalk";
3
9
  import fs from "fs-extra";
4
10
  import path from "path";
5
- import prompts from "prompts";
6
11
  var V0_CONVERSION_RULES = [
7
12
  // import 경로 변환
8
13
  { from: /@\/components\/ui\//g, to: "@/components/ui/" },
@@ -69,7 +74,8 @@ ${result}`;
69
74
  console.log(chalk.gray(" - \uD658\uACBD \uBCC0\uC218 \uBC0F API \uC5D4\uB4DC\uD3EC\uC778\uD2B8\n"));
70
75
  }
71
76
  // V0 URL에서 가져오기 안내
72
- async importFromUrl() {
77
+ async importFromUrl(opts = {}) {
78
+ const interactive = opts.interactive ?? isInteractive();
73
79
  console.log(chalk.blue("\n\u{1F517} V0 URL\uC5D0\uC11C \uCEF4\uD3EC\uB10C\uD2B8 \uAC00\uC838\uC624\uAE30\n"));
74
80
  console.log(chalk.yellow("V0\uB294 \uC9C1\uC811 API\uB97C \uC81C\uACF5\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."));
75
81
  console.log(chalk.gray("\uB2E4\uC74C \uBC29\uBC95\uC73C\uB85C \uCF54\uB4DC\uB97C \uAC00\uC838\uC62C \uC218 \uC788\uC2B5\uB2C8\uB2E4:\n"));
@@ -85,26 +91,36 @@ ${result}`;
85
91
  console.log(chalk.gray(" 1. v0.app\uC5D0\uC11C \uCF54\uB4DC \uD0ED \uC5F4\uAE30"));
86
92
  console.log(chalk.gray(" 2. \uD30C\uC77C\uBCC4\uB85C \uCF54\uB4DC \uBCF5\uC0AC"));
87
93
  console.log(chalk.gray(" 3. npx @axis-ds/cli v0 convert\uB85C \uBCC0\uD658\n"));
88
- const { method } = await prompts({
89
- type: "select",
90
- name: "method",
91
- message: "\uC5B4\uB5A4 \uBC29\uBC95\uC744 \uC0AC\uC6A9\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
92
- choices: [
93
- { title: "GitHub\uC5D0\uC11C \uD074\uB860", value: "github" },
94
- { title: "\uB2E4\uC6B4\uB85C\uB4DC\uD55C ZIP \uD30C\uC77C \uBCC0\uD658", value: "zip" },
95
- { title: "\uD074\uB9BD\uBCF4\uB4DC\uC5D0\uC11C \uBCC0\uD658", value: "clipboard" },
96
- { title: "\uCDE8\uC18C", value: "cancel" }
97
- ]
98
- });
94
+ if (!interactive) {
95
+ failNonInteractive("\uBC29\uBC95\uC744 \uACE0\uB97C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC544\uBB34\uAC83\uB3C4 \uC4F0\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.", [
96
+ "\uC704 \uBC29\uBC95 1~3 \uC744 \uC9C1\uC811 \uB530\uB974\uAC70\uB098 \uB300\uD654\uD615 \uD130\uBBF8\uB110\uC5D0\uC11C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
97
+ "\uD30C\uC77C\uC774 \uC788\uC73C\uBA74: axis v0 convert <file> [-o <path>]"
98
+ ]);
99
+ return;
100
+ }
101
+ const { method } = await ask(
102
+ {
103
+ type: "select",
104
+ name: "method",
105
+ message: "\uC5B4\uB5A4 \uBC29\uBC95\uC744 \uC0AC\uC6A9\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
106
+ choices: [
107
+ { title: "GitHub\uC5D0\uC11C \uD074\uB860", value: "github" },
108
+ { title: "\uB2E4\uC6B4\uB85C\uB4DC\uD55C ZIP \uD30C\uC77C \uBCC0\uD658", value: "zip" },
109
+ { title: "\uD074\uB9BD\uBCF4\uB4DC\uC5D0\uC11C \uBCC0\uD658", value: "clipboard" },
110
+ { title: "\uCDE8\uC18C", value: "cancel" }
111
+ ]
112
+ },
113
+ interactive
114
+ );
99
115
  switch (method) {
100
116
  case "github":
101
117
  await this.setupGitHubSync();
102
118
  break;
103
119
  case "zip":
104
- await this.convertFromZip();
120
+ await this.convertFromZip({ interactive });
105
121
  break;
106
122
  case "clipboard":
107
- await this.convertFromClipboard();
123
+ await this.convertFromClipboard({ interactive });
108
124
  break;
109
125
  }
110
126
  }
@@ -122,14 +138,24 @@ ${result}`;
122
138
  console.log(chalk.gray("\uD301: GitHub Actions\uB85C \uC790\uB3D9 \uBCC0\uD658 \uC6CC\uD06C\uD50C\uB85C\uB3C4 \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.\n"));
123
139
  }
124
140
  // ZIP 파일에서 변환
125
- async convertFromZip() {
141
+ async convertFromZip(opts = {}) {
126
142
  console.log(chalk.blue("\n\u{1F4E6} ZIP \uD30C\uC77C\uC5D0\uC11C \uBCC0\uD658\n"));
127
- const { zipPath } = await prompts({
128
- type: "text",
129
- name: "zipPath",
130
- message: "\uB2E4\uC6B4\uB85C\uB4DC\uD55C ZIP \uD30C\uC77C \uACBD\uB85C:",
131
- validate: (v) => v.length > 0 ? true : "\uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694"
132
- });
143
+ const interactive = opts.interactive ?? isInteractive();
144
+ if (!interactive) {
145
+ failNonInteractive("ZIP \uD30C\uC77C \uACBD\uB85C\uB97C \uBB3C\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", [
146
+ "\uC555\uCD95\uC744 \uD47C \uB4A4: axis v0 convert <file> [-o <path>]"
147
+ ]);
148
+ return;
149
+ }
150
+ const { zipPath } = await ask(
151
+ {
152
+ type: "text",
153
+ name: "zipPath",
154
+ message: "\uB2E4\uC6B4\uB85C\uB4DC\uD55C ZIP \uD30C\uC77C \uACBD\uB85C:",
155
+ validate: (v) => v.length > 0 ? true : "\uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694"
156
+ },
157
+ interactive
158
+ );
133
159
  if (!zipPath) return;
134
160
  console.log(chalk.yellow("\nZIP \uBCC0\uD658 \uAE30\uB2A5\uC740 \uCD94\uD6C4 \uAD6C\uD604 \uC608\uC815\uC785\uB2C8\uB2E4."));
135
161
  console.log(chalk.gray("\uD604\uC7AC\uB294 \uC218\uB3D9\uC73C\uB85C \uC555\uCD95 \uD574\uC81C \uD6C4 convert \uBA85\uB839\uC5B4\uB97C \uC0AC\uC6A9\uD558\uC138\uC694:\n"));
@@ -137,22 +163,35 @@ ${result}`;
137
163
  console.log(chalk.cyan(" npx @axis-ds/cli v0 convert ./v0-project/components/*.tsx\n"));
138
164
  }
139
165
  // 클립보드에서 변환
140
- async convertFromClipboard() {
166
+ async convertFromClipboard(opts = {}) {
141
167
  console.log(chalk.blue("\n\u{1F4CB} \uD074\uB9BD\uBCF4\uB4DC\uC5D0\uC11C V0 \uCF54\uB4DC \uBCC0\uD658\n"));
142
- const { componentName } = await prompts({
143
- type: "text",
144
- name: "componentName",
145
- message: "\uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984 (\uC608: hero-section):",
146
- validate: (v) => v.length > 0 ? true : "\uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694"
147
- });
168
+ const interactive = opts.interactive ?? isInteractive();
169
+ if (!interactive) {
170
+ failNonInteractive("\uBD99\uC5EC \uB123\uC744 \uCF54\uB4DC\uC640 \uC774\uB984\uC744 \uBB3C\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC544\uBB34\uAC83\uB3C4 \uC4F0\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.", [
171
+ "\uCF54\uB4DC\uB97C \uD30C\uC77C\uB85C \uC800\uC7A5\uD55C \uB4A4: axis v0 convert <file> [-o <path>]"
172
+ ]);
173
+ return;
174
+ }
175
+ const { componentName } = await ask(
176
+ {
177
+ type: "text",
178
+ name: "componentName",
179
+ message: "\uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984 (\uC608: hero-section):",
180
+ validate: (v) => v.length > 0 ? true : "\uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694"
181
+ },
182
+ interactive
183
+ );
148
184
  if (!componentName) return;
149
185
  console.log(chalk.gray("\nV0\uC5D0\uC11C \uBCF5\uC0AC\uD55C \uCF54\uB4DC\uB97C \uBD99\uC5EC\uB123\uC73C\uC138\uC694."));
150
186
  console.log(chalk.gray("(\uC785\uB825 \uC644\uB8CC \uD6C4 \uBE48 \uC904\uC5D0\uC11C Enter \uB450 \uBC88)\n"));
151
- const { code } = await prompts({
152
- type: "text",
153
- name: "code",
154
- message: "\uCF54\uB4DC:"
155
- });
187
+ const { code } = await ask(
188
+ {
189
+ type: "text",
190
+ name: "code",
191
+ message: "\uCF54\uB4DC:"
192
+ },
193
+ interactive
194
+ );
156
195
  if (!code) {
157
196
  console.log(chalk.yellow("\n\uCF54\uB4DC\uAC00 \uC785\uB825\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4."));
158
197
  return;
@@ -0,0 +1,35 @@
1
+ // src/interactive.ts
2
+ import chalk from "chalk";
3
+ import prompts from "prompts";
4
+ var isInteractive = () => process.stdin.isTTY === true;
5
+ var NonInteractiveError = class extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "NonInteractiveError";
9
+ }
10
+ };
11
+ async function ask(questions, interactive) {
12
+ if (!interactive) {
13
+ const list = Array.isArray(questions) ? questions : [questions];
14
+ const first = list.find((q) => q.type !== null);
15
+ const message = typeof first?.message === "string" ? first.message : String(first?.name ?? "");
16
+ throw new NonInteractiveError(`\uBE44\uB300\uD654\uD615 \uC2E4\uD589\uC774\uB77C \uC9C8\uBB38(${message})\uC5D0 \uB2F5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB300\uD654\uD615 \uD130\uBBF8\uB110\uC5D0\uC11C \uC2E4\uD589\uD558\uAC70\uB098 \uD544\uC694\uD55C \uC635\uC158\uC744 \uC8FC\uC138\uC694.`);
17
+ }
18
+ return prompts(questions);
19
+ }
20
+ function noteDefault(message) {
21
+ console.log(chalk.gray(`\uBE44\uB300\uD654\uD615 \uC2E4\uD589\uC774\uB77C ${message}`));
22
+ }
23
+ function failNonInteractive(reason, hints) {
24
+ console.error(chalk.red(`\uBE44\uB300\uD654\uD615 \uC2E4\uD589\uC774\uB77C ${reason}`));
25
+ for (const hint of hints) console.error(chalk.yellow(` ${hint}`));
26
+ process.exitCode = 1;
27
+ }
28
+
29
+ export {
30
+ isInteractive,
31
+ NonInteractiveError,
32
+ ask,
33
+ noteDefault,
34
+ failNonInteractive
35
+ };
@@ -1,8 +1,13 @@
1
+ import {
2
+ ask,
3
+ failNonInteractive,
4
+ isInteractive
5
+ } from "./chunk-HFMMK6R4.js";
6
+
1
7
  // src/monet.ts
2
8
  import chalk from "chalk";
3
9
  import fs from "fs-extra";
4
10
  import path from "path";
5
- import prompts from "prompts";
6
11
  var MONET_CATEGORIES = [
7
12
  { id: "hero", name: "Hero Sections", count: 120 },
8
13
  { id: "feature", name: "Feature Sections", count: 191 },
@@ -111,24 +116,38 @@ var MonetClient = class {
111
116
  return examples[category] || [{ id: `${category}-basic`, desc: "\uAE30\uBCF8 \uD15C\uD50C\uB9BF" }];
112
117
  }
113
118
  // 클립보드에서 Monet 코드 가져오기
114
- async importFromClipboard() {
119
+ /** opts.interactive: 질문을 띄울 수 있는지(WI-0075). 없으면 stdin 이 TTY 인지로 정합니다 */
120
+ async importFromClipboard(opts = {}) {
115
121
  console.log(chalk.blue("\n\u{1F4CB} \uD074\uB9BD\uBCF4\uB4DC\uC5D0\uC11C Monet \uCEF4\uD3EC\uB10C\uD2B8 \uAC00\uC838\uC624\uAE30\n"));
116
122
  console.log(chalk.yellow("\uC900\uBE44 \uB2E8\uACC4:"));
117
123
  console.log(chalk.gray(" 1. monet.design\uC5D0\uC11C \uCEF4\uD3EC\uB10C\uD2B8 \uC120\uD0DD"));
118
124
  console.log(chalk.gray(" 2. 'Copy Code' \uBC84\uD2BC\uC73C\uB85C \uCF54\uB4DC \uBCF5\uC0AC"));
119
125
  console.log(chalk.gray(" 3. \uC774 \uBA85\uB839\uC5B4 \uC2E4\uD589\n"));
120
- const { componentName } = await prompts({
121
- type: "text",
122
- name: "componentName",
123
- message: "\uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984 (\uC608: hero-section):",
124
- validate: (v) => v.length > 0 ? true : "\uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694"
125
- });
126
+ const interactive = opts.interactive ?? isInteractive();
127
+ if (!interactive) {
128
+ failNonInteractive("\uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984\uACFC \uBD99\uC5EC \uB123\uC744 \uCF54\uB4DC\uB97C \uBB3C\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC544\uBB34\uAC83\uB3C4 \uC4F0\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.", [
129
+ "\uB300\uD654\uD615 \uD130\uBBF8\uB110\uC5D0\uC11C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694: axis monet import"
130
+ ]);
131
+ return;
132
+ }
133
+ const { componentName } = await ask(
134
+ {
135
+ type: "text",
136
+ name: "componentName",
137
+ message: "\uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984 (\uC608: hero-section):",
138
+ validate: (v) => v.length > 0 ? true : "\uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694"
139
+ },
140
+ interactive
141
+ );
126
142
  if (!componentName) return;
127
- const { componentCode } = await prompts({
128
- type: "text",
129
- name: "componentCode",
130
- message: "\uBCF5\uC0AC\uD55C \uCF54\uB4DC\uB97C \uBD99\uC5EC\uB123\uC73C\uC138\uC694 (\uD55C \uC904\uB85C):"
131
- });
143
+ const { componentCode } = await ask(
144
+ {
145
+ type: "text",
146
+ name: "componentCode",
147
+ message: "\uBCF5\uC0AC\uD55C \uCF54\uB4DC\uB97C \uBD99\uC5EC\uB123\uC73C\uC138\uC694 (\uD55C \uC904\uB85C):"
148
+ },
149
+ interactive
150
+ );
132
151
  if (!componentCode) {
133
152
  console.log(chalk.yellow("\n\uCF54\uB4DC\uAC00 \uC785\uB825\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4."));
134
153
  return;