sh-ui-cli 0.71.0 → 0.71.2

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/bin/sh-ui.mjs CHANGED
@@ -109,7 +109,47 @@ try {
109
109
  }
110
110
  case "doctor": {
111
111
  const { doctor } = await import("../src/doctor.mjs");
112
- await doctor({ cwd: process.cwd() });
112
+ const { existsSync, readdirSync } = await import("node:fs");
113
+ const { resolve } = await import("node:path");
114
+ // add 와 같은 walk-up + monorepo 라우팅. standalone 이면 그 root 1개,
115
+ // monorepo 면 packages/ui/ui-core + ui-apps/ui-* 모두 순회.
116
+ const ctx = findShUiContext(process.cwd());
117
+ if (!ctx) {
118
+ console.error(
119
+ "✗ sh-ui.config.json 또는 pnpm-workspace.yaml 을 cwd 부터 부모 트리에서 찾지 못했습니다.",
120
+ );
121
+ process.exit(1);
122
+ }
123
+ let anyFailed = false;
124
+ const targets = [];
125
+ if (ctx.kind === "config") {
126
+ targets.push(ctx.root);
127
+ } else {
128
+ const uiCore = resolve(ctx.root, "packages/ui/ui-core");
129
+ if (existsSync(resolve(uiCore, "sh-ui.config.json"))) targets.push(uiCore);
130
+ const uiAppsRoot = resolve(ctx.root, "packages/ui/ui-apps");
131
+ if (existsSync(uiAppsRoot)) {
132
+ for (const name of readdirSync(uiAppsRoot)) {
133
+ const dir = resolve(uiAppsRoot, name);
134
+ if (existsSync(resolve(dir, "sh-ui.config.json"))) targets.push(dir);
135
+ }
136
+ }
137
+ if (targets.length === 0) {
138
+ console.error(
139
+ "✗ monorepo 에서 sh-ui.config.json 가지는 패키지를 찾지 못했습니다.\n" +
140
+ " packages/ui/ui-core/ 또는 packages/ui/ui-apps/ui-*/ 에 있어야 합니다.",
141
+ );
142
+ process.exit(1);
143
+ }
144
+ }
145
+ for (const target of targets) {
146
+ if (targets.length > 1) {
147
+ console.log(`\n──── ${target.replace(ctx.root + "/", "")} ────`);
148
+ }
149
+ const result = await doctor({ cwd: target });
150
+ if (!result.ok) anyFailed = true;
151
+ }
152
+ if (anyFailed) process.exit(1);
113
153
  break;
114
154
  }
115
155
  case "theme": {
@@ -2,6 +2,32 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$description": "sh-ui 릴리즈 노트 단일 소스. docs(React)와 showcase(Flutter)가 함께 읽는다. 새 릴리즈마다 맨 앞에 추가.",
4
4
  "versions": [
5
+ {
6
+ "version": "0.71.2",
7
+ "date": "2026-05-09",
8
+ "title": "fix(doctor) — monorepo walk-up + 컴포넌트-only 패키지 인식",
9
+ "type": "patch",
10
+ "highlights": [
11
+ "**doctor 의 walk-up + monorepo 라우팅** — `add` 와 같은 정책을 적용. monorepo 어느 디렉토리 (apps/web, ui-core 안 등) 에서 실행해도 `packages/ui/ui-core` 와 모든 `packages/ui/ui-apps/ui-*` 를 자동 순회. 각 패키지마다 헤더 출력 + 결과 집계, 하나라도 fail 이면 exit 1.",
12
+ "**컴포넌트-only 패키지 인식** — ui-core 처럼 `paths.tokens` 가 없는 패키지는 의도된 형태. 이전엔 'theme 미설정 / 토큰 검증 스킵' 로 fail/warn 폭발 → 이제 ✓ '(컴포넌트 only — 토큰 없음, 검증 스킵)' 한 줄로 정리.",
13
+ "**doctor 함수 시그니처** — `process.exit()` 직접 호출 대신 `{ ok, failCount, warnCount }` 반환. 호출부 (bin) 가 monorepo 순회 후 집계 exit. API consumer (테스트, 향후 lint:doctor 등) 도 활용 가능."
14
+ ],
15
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.71.2"
16
+ },
17
+ {
18
+ "version": "0.71.1",
19
+ "date": "2026-05-09",
20
+ "title": "chore(mcp): instructions 에 v0.68~0.71 신규 명령 가이드 반영",
21
+ "type": "patch",
22
+ "highlights": [
23
+ "**MCP `instructions` 갱신** — `sh-ui doctor` (v0.68), `sh-ui tokens diff/upgrade --apply/--replace` (v0.69), `sh-ui theme extract` (v0.70), `cssStrategy: bundled` (v0.71) 사용 시점을 명시. AI 가 사용자 의도 (\"focus 표시 안 보임\", \"새 토큰 받아줘\", \"색 base64 로 뽑아줘\", \"styles.css 너무 많아\") 와 Bash 명령 호출을 직접 매핑.",
24
+ "**진단 흐름** — UI 작업 후 시각적 깨짐 또는 사용자가 토큰을 손댄 직후 doctor 권장. 누락 토큰 신호 시 tokens upgrade --apply 로 incremental 적용.",
25
+ "**토큰 마이그레이션 가이드** — tokens diff 로 미리보기 → --apply (사용자 편집 보존) 또는 --replace (리셋) 선택. buildable preset 제약 + custom/rich preset 은 encode/decode round-trip 으로 우회.",
26
+ "**theme extract 활용 패턴** — encode 의 역방향. tokens.css 직접 편집 후 base64 추출 → 다른 앱에 동일 톤 박기 또는 디자인 시스템 문서 보존.",
27
+ "**cssStrategy: bundled 전환 가이드** — config 옵션 + paths.cssBundle 안내 + globals.css import 책임 + 마이그레이션 시 force=true 재실행."
28
+ ],
29
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.71.1"
30
+ },
5
31
  {
6
32
  "version": "0.71.0",
7
33
  "date": "2026-05-09",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.71.0",
3
+ "version": "0.71.2",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/doctor.mjs CHANGED
@@ -72,16 +72,18 @@ async function loadConfig(cwd, report) {
72
72
  const config = JSON.parse(await readFile(configPath, "utf8"));
73
73
  const issues = [];
74
74
  if (!config.platform) issues.push("platform 미설정");
75
- if (!config.theme) issues.push("theme 미설정");
76
75
  if (!config.paths) issues.push("paths 미설정");
76
+ // theme 은 컴포넌트-only 패키지 (monorepo ui-core, paths.tokens 없음) 에서는
77
+ // 의도적으로 비어 있다 — 그땐 누락이라고 보지 않음.
78
+ if (!config.theme && config.paths?.tokens) issues.push("theme 미설정");
77
79
  if (issues.length > 0) {
78
80
  report.fail("sh-ui.config.json", `필수 필드 누락: ${issues.join(", ")}`);
79
81
  return null;
80
82
  }
83
+ const themeInfo = config.theme?.base ? `theme.base=${config.theme.base}` : "(컴포넌트 only)";
81
84
  report.ok(
82
85
  "sh-ui.config.json",
83
- `platform=${config.platform} theme.base=${config.theme?.base ?? "?"} ` +
84
- `cssFramework=${config.cssFramework ?? "(기본 plain)"}`,
86
+ `platform=${config.platform} ${themeInfo} cssFramework=${config.cssFramework ?? "(기본 plain)"}`,
85
87
  );
86
88
  return config;
87
89
  } catch (err) {
@@ -93,10 +95,11 @@ async function loadConfig(cwd, report) {
93
95
  function checkTokensFile(config, cwd, report) {
94
96
  const rel = config.paths?.tokens;
95
97
  if (!rel) {
96
- // 모노레포 ui-core 처럼 tokens 가지지 않는 패키지 — 이 경우 검사 스킵.
97
- report.warn(
98
+ // 모노레포 ui-core 처럼 토큰을 가지지 않는 컴포넌트-only 패키지 — 이 케이스는
99
+ // 정상이라 안내 한 줄만 ok 로 표시 (warn 으로 노이즈 만들지 않음).
100
+ report.ok(
98
101
  "paths.tokens",
99
- "config paths.tokens 가 없습니다 — 토큰 검증 스킵.",
102
+ "(컴포넌트 only — 토큰 없음, 검증 스킵)",
100
103
  );
101
104
  return null;
102
105
  }
@@ -171,6 +174,14 @@ async function checkCssEntry(config, cwd, tokensPath, report) {
171
174
  */
172
175
  async function checkInstalledComponents(config, cwd, definedVars, report) {
173
176
  if (!definedVars) {
177
+ // tokens 없는 컴포넌트-only 패키지 — 의존성 검증은 의미 없음. 조용히 ok.
178
+ if (!config.paths?.tokens) {
179
+ report.ok(
180
+ "컴포넌트 토큰 의존성",
181
+ "(컴포넌트 only — 검증 스킵)",
182
+ );
183
+ return;
184
+ }
174
185
  report.warn(
175
186
  "컴포넌트 토큰 의존성",
176
187
  "tokens.css 가 없어 검증 스킵.",
@@ -290,6 +301,10 @@ function effectiveFramework(entry, cssFramework) {
290
301
  return hasVariant ? cssFramework : "plain";
291
302
  }
292
303
 
304
+ /**
305
+ * @returns {Promise<{ ok: boolean, failCount: number, warnCount: number }>}
306
+ * 호출부가 exit code 결정. 한 cwd 에서 다른 cwd 로 이어 호출 가능 (monorepo 순회).
307
+ */
293
308
  export async function doctor({ cwd }) {
294
309
  console.log(`sh-ui doctor — ${relative(process.cwd(), cwd) || "."}\n`);
295
310
  const report = new Report();
@@ -297,7 +312,7 @@ export async function doctor({ cwd }) {
297
312
  const config = await loadConfig(cwd, report);
298
313
  if (!config) {
299
314
  report.render();
300
- process.exit(1);
315
+ return { ok: false, failCount: report.failCount, warnCount: report.warnCount };
301
316
  }
302
317
 
303
318
  const tokensPath = checkTokensFile(config, cwd, report);
@@ -312,5 +327,5 @@ export async function doctor({ cwd }) {
312
327
  await checkInstalledComponents(config, cwd, definedVars, report);
313
328
 
314
329
  report.render();
315
- if (report.failCount > 0) process.exit(1);
330
+ return { ok: report.failCount === 0, failCount: report.failCount, warnCount: report.warnCount };
316
331
  }
package/src/mcp.mjs CHANGED
@@ -194,6 +194,43 @@ function buildServerInstructions(cliName) {
194
194
  - \`sh_ui_add_component\` / \`sh_ui_remove_component\` — 설치/삭제
195
195
  - \`sh_ui_get_changelog\` — 최근 변경 내역
196
196
 
197
+ ## 진단 — sh-ui doctor (v0.68+)
198
+
199
+ UI 작업이 끝났는데 컴포넌트가 시각적으로 깨졌거나 ("focus outline 안 보임", "hover 색 변화 없음" 류) 사용자가 sh-ui 버전을 올린 직후 / 토큰을 손댄 직후라면 \`npx ${cliName} doctor\` 를 Bash 로 실행해 한 번에 점검:
200
+ - sh-ui.config.json / paths.tokens / paths.cssEntry import / 설치된 컴포넌트의 토큰 의존성 검증
201
+ - 누락 토큰 (silent breakage) 을 fail 로 표시 — 화면이 깨진 이유를 즉시 파악
202
+ - exit 1 이면 사용자에게 어떤 토큰이 누락됐는지 보고 + \`sh-ui tokens upgrade --apply\` 권장
203
+
204
+ ## 토큰 마이그레이션 — tokens diff / upgrade (v0.69+)
205
+
206
+ 사용자가 sh-ui 버전을 올린 뒤 "새 토큰이 들어왔어?" / "tokens.css 업그레이드해줘" 류 요청 또는 doctor 가 누락 토큰을 신호하면:
207
+
208
+ 1. **\`npx ${cliName} tokens diff\`** — 현재 tokens.css vs buildTokens 결과의 added/changed/removed 미리보기. 사용자에게 결과를 보여주고 어떤 모드로 적용할지 확인.
209
+ 2. **\`npx ${cliName} tokens upgrade --apply\`** — added 변수만 incremental 추가 (사용자가 손댄 색은 보존). 대부분의 경우 이쪽.
210
+ 3. **\`npx ${cliName} tokens upgrade --replace\`** — 사용자가 "표준값으로 리셋" 명시할 때만. 모든 편집이 사라짐.
211
+
212
+ 제약: theme.base 가 buildable preset (neutral/zinc/slate) 일 때만 동작. custom (base64) / rich preset (rose/emerald/violet) 은 친절 에러로 종료 — 그땐 \`sh_ui_decode_theme\` → 객체 수정 → \`sh_ui_encode_theme\` round-trip 으로 새 base64 만들어 재스캐폴드.
213
+
214
+ ## 테마 추출 — theme extract (v0.70+)
215
+
216
+ 사용자가 "지금 색 그대로 다른 앱에 박고 싶어" / "현재 토큰 base64 로 뽑아줘" / "디자인 시스템 문서에 색 스냅샷 저장" 같은 요청에는 \`npx ${cliName} theme extract\` (Bash):
217
+ - 현재 tokens.css 의 light/dark + radius 를 sh-ui base64 로 추출 (stdout, stderr 에 정보 분리)
218
+ - 추출된 base64 를 \`sh_ui_create_project\` 의 \`theme\` 인자에 그대로 넘기면 동일 톤의 새 앱 생성
219
+ - \`sh_ui_encode_theme\` 의 역방향 — 사용자가 tokens.css 를 직접 손댄 결과를 다시 base64 화
220
+
221
+ 제약: tokens.css 모든 필수 색이 #RRGGBB 여야 함. color-mix() / var() / rgba() 가 섞이면 친절 에러 — 먼저 \`tokens upgrade --replace\` 로 표준값 hex 화 후 재시도 권장.
222
+
223
+ ## CSS 번들 모드 — cssStrategy: bundled (v0.71+)
224
+
225
+ 사용자가 "컴포넌트 폴더에 styles.css 너무 많이 떨어진다" / "한 파일로 합치고 싶어" 류 요청 또는 50개+ 컴포넌트 깐 모노레포에서 파일 폭증을 호소하면:
226
+
227
+ 1. **\`sh-ui.config.json\` 에 \`cssStrategy: "bundled"\` + \`paths.cssBundle: "src/styles/sh-ui-components.css"\` 추가**.
228
+ 2. 사용자가 \`paths.cssBundle\` 을 globals.css 에서 한 번 import (자동화 안 함 — 사용자에게 안내).
229
+ 3. 이후 \`sh_ui_add_component\` 가 컴포넌트 styles.css 를 cssBundle 의 \`/* sh-ui:component:NAME-start ... -end */\` 마커 섹션으로 누적. 컴포넌트 .tsx 의 styles.css import 는 자동 제거.
230
+ 4. \`sh_ui_remove_component\` 도 .tsx + 번들 섹션을 같이 정리.
231
+
232
+ 제약: \`cssFramework: "plain"\` 에서만 동작. tailwind/css-modules/vanilla-extract 는 자체 스코프가 있어 bundled 무시. 기존 per-component 프로젝트에서 bundled 로 마이그레이션은 자동화 없음 — config 추가 후 모든 컴포넌트 \`sh_ui_add_component force=true\` 재실행 안내.
233
+
197
234
  ### 모노레포 라우팅 (v0.65+)
198
235
 
199
236
  monorepo 에서 \`sh_ui_add_component\` 호출 시: