sh-ui-cli 0.64.6 → 0.65.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/data/changelog/versions.json +25 -0
- package/package.json +1 -1
- package/src/add.mjs +13 -0
- package/src/create/generator.js +41 -7
- package/src/mcp.mjs +24 -0
- package/templates/monorepo/packages/ui/ui-core/package.json +13 -1
- package/templates/monorepo/packages/ui/ui-core/sh-ui.config.json +15 -0
- package/templates/ui-app-template/package.json +2 -11
- package/templates/ui-app-template/sh-ui.config.json +2 -8
- package/templates/ui-app-template/src/lib/.gitkeep +0 -0
- /package/templates/{ui-app-template → monorepo/packages/ui/ui-core}/src/components/.gitkeep +0 -0
- /package/templates/{ui-app-template → monorepo/packages/ui/ui-core}/src/hooks/.gitkeep +0 -0
|
@@ -2,6 +2,31 @@
|
|
|
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.65.0",
|
|
7
|
+
"date": "2026-05-09",
|
|
8
|
+
"title": "monorepo 컴포넌트 단일 SoT — ui-core 도입, ui-app 은 tokens-only",
|
|
9
|
+
"type": "minor",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**컴포넌트 중복 emit 제거** — monorepo 에서 `sh-ui add <component>` 가 `packages/ui/ui-core` 한 곳에만 컴포넌트/훅을 떨어뜨림. 이전엔 ui-{app} 마다 같은 파일이 복제돼 sync 비용이 컸다.",
|
|
12
|
+
"**역할 분리** — `ui-core` 가 컴포넌트/훅/lib SoT, `ui-{app}` 은 토큰/스타일 전용 (`role: \"tokens-only\"` 마커). `sh-ui add tokens` 만 ui-app 으로 라우팅, 그 외는 모두 ui-core.",
|
|
13
|
+
"**친절한 거부 메시지** — 사용자가 ui-app 에서 직접 `sh-ui add button` 을 실행하면 \"ui-core 에 추가하세요\" 안내 후 종료. 잘못된 위치에 컴포넌트 누적 차단.",
|
|
14
|
+
"**v0.64.x 호환 fallback** — 기존 monorepo 레이아웃(ui-core 부재)에서는 자동으로 ui-apps 직접 라우팅으로 폴백. 마이그레이션 자동 도구는 후속 v0.66.0 (PR-D)."
|
|
15
|
+
],
|
|
16
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.65.0"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"version": "0.64.7",
|
|
20
|
+
"date": "2026-05-08",
|
|
21
|
+
"title": "patch — MCP instructions 에 \"UI 짤 때 사고 순서\" 섹션 추가 (raw HTML 기본값 회피)",
|
|
22
|
+
"type": "patch",
|
|
23
|
+
"highlights": [
|
|
24
|
+
"**MCP server `instructions` 에 AI 행동 가이드 추가** — sh-ui MCP 가 등록되면 매 세션 상단에 노출되는 instructions 텍스트에 \"UI 짤 때 사고 순서\" 섹션 추가. AI 가 `<aside>` / `<nav>` / `<header>` / `<table>` 같은 시맨틱 태그를 raw 로 쓰기 전에 `sh_ui_list_components` 로 카탈로그를 훑고, Sidebar / Header / Breadcrumb 같은 위젯 단위 컴포넌트를 우선 사용하도록 유도.",
|
|
25
|
+
"**흔한 누락 패턴 4건 명시** — Sidebar 직접 짜기, Breadcrumb 직접 짜기, DialogClose render prop 누락, Card 미사용. 이전 세션에서 실제로 반복된 실수를 instructions 에 박아 같은 회귀 차단.",
|
|
26
|
+
"**raw HTML 폴백이 정당한 경우 가이드** — 카탈로그에 매칭 없음 / 의도적 변형 / ad-hoc 레이아웃. 코드에 한 줄 코멘트로 의도 남기기 권장."
|
|
27
|
+
],
|
|
28
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.64.7"
|
|
29
|
+
},
|
|
5
30
|
{
|
|
6
31
|
"version": "0.64.6",
|
|
7
32
|
"date": "2026-05-08",
|
package/package.json
CHANGED
package/src/add.mjs
CHANGED
|
@@ -363,6 +363,19 @@ export async function add({
|
|
|
363
363
|
);
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
// role: "tokens-only" 패키지 (v0.65+ ui-app) 는 tokens 만 허용.
|
|
367
|
+
// 컴포넌트는 sibling ui-core 패키지로 라우팅되도록 친절한 에러로 안내.
|
|
368
|
+
if (config.role === "tokens-only") {
|
|
369
|
+
const offending = names.filter((n) => n !== "tokens");
|
|
370
|
+
if (offending.length > 0) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
`이 패키지는 'tokens-only' role 입니다 — ${offending.map((n) => `'${n}'`).join(', ')} 컴포넌트를 추가할 수 없습니다.\n` +
|
|
373
|
+
`컴포넌트는 sibling ui-core 패키지에 추가하세요 (예: cd ../ui-core && sh-ui add ${offending[0]}).\n` +
|
|
374
|
+
`또는 monorepo 루트에서 \`sh-ui add ${offending[0]}\` 실행 시 자동으로 ui-core 로 라우팅됩니다.`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
366
379
|
// 비대화형(non-TTY)이면 prompt 를 못 띄우니 안전하게 keep 으로 강등.
|
|
367
380
|
const effectiveStrategy =
|
|
368
381
|
onConflict === "prompt" && !process.stdin.isTTY ? "keep" : onConflict;
|
package/src/create/generator.js
CHANGED
|
@@ -401,9 +401,45 @@ export async function addComponent(componentName, appName) {
|
|
|
401
401
|
return;
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
-
// Monorepo: packages/ui/ui-apps
|
|
404
|
+
// Monorepo (v0.65+): tokens 는 packages/ui/ui-apps/ui-{app} 으로, 그 외 컴포넌트/훅은
|
|
405
|
+
// packages/ui/ui-core 단일 SoT 로 라우팅. 컴포넌트 중복 emit 제거가 v0.65 의 핵심.
|
|
406
|
+
// v0.64.x 호환: ui-core/sh-ui.config.json 미존재 시 (구 모노레포 레이아웃) 기존 ui-apps
|
|
407
|
+
// 직접 라우팅 분기로 내려감.
|
|
408
|
+
const uiCoreDir = path.join(cwd, 'packages', 'ui', 'ui-core');
|
|
405
409
|
const uiAppsDir = path.join(cwd, 'packages', 'ui', 'ui-apps');
|
|
406
|
-
|
|
410
|
+
const hasUiCore = await fs.pathExists(path.join(uiCoreDir, 'sh-ui.config.json'));
|
|
411
|
+
const hasUiApps = await fs.pathExists(uiAppsDir);
|
|
412
|
+
|
|
413
|
+
if (!hasUiCore && !hasUiApps) {
|
|
414
|
+
console.log('❌ packages/ui/ui-core 또는 packages/ui/ui-apps/ 디렉토리가 없습니다.');
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!componentName) {
|
|
419
|
+
componentName = await input({ message: '컴포넌트 이름:' });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const isTokens = componentName === 'tokens';
|
|
423
|
+
|
|
424
|
+
// ─── 비-tokens (컴포넌트/훅/lib) → ui-core 단일 라우팅 ───
|
|
425
|
+
if (!isTokens && hasUiCore) {
|
|
426
|
+
if (appName) {
|
|
427
|
+
console.log(
|
|
428
|
+
`ℹ️ v0.65+ 에서 컴포넌트는 packages/ui/ui-core 에 단일 emit 됩니다 (--app ${appName} 무시).`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
console.log(`\n📦 packages/ui/ui-core 에 ${componentName} 추가 중...`);
|
|
432
|
+
try {
|
|
433
|
+
execSync(`npx sh-ui add ${componentName}`, { cwd: uiCoreDir, stdio: 'inherit' });
|
|
434
|
+
console.log(`✅ packages/ui/ui-core 완료`);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
console.log(`❌ packages/ui/ui-core 실패: ${error.message}`);
|
|
437
|
+
}
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ─── tokens → ui-apps/ui-{app}(s), 또는 v0.64.x 호환 fallback (ui-core 없음) ───
|
|
442
|
+
if (!hasUiApps) {
|
|
407
443
|
console.log('❌ packages/ui/ui-apps/ 디렉토리가 없습니다.');
|
|
408
444
|
return;
|
|
409
445
|
}
|
|
@@ -418,10 +454,6 @@ export async function addComponent(componentName, appName) {
|
|
|
418
454
|
return;
|
|
419
455
|
}
|
|
420
456
|
|
|
421
|
-
if (!componentName) {
|
|
422
|
-
componentName = await input({ message: '컴포넌트 이름:' });
|
|
423
|
-
}
|
|
424
|
-
|
|
425
457
|
let targets;
|
|
426
458
|
if (appName) {
|
|
427
459
|
const pkgName = `ui-${appName}`;
|
|
@@ -431,9 +463,11 @@ export async function addComponent(componentName, appName) {
|
|
|
431
463
|
return;
|
|
432
464
|
}
|
|
433
465
|
targets = [pkgName];
|
|
466
|
+
} else if (uiPackages.length === 1) {
|
|
467
|
+
targets = uiPackages;
|
|
434
468
|
} else {
|
|
435
469
|
const choice = await select({
|
|
436
|
-
message: '어디에 추가할까요?',
|
|
470
|
+
message: isTokens ? '어느 ui 패키지에 토큰을 추가할까요?' : '어디에 추가할까요?',
|
|
437
471
|
choices: [
|
|
438
472
|
{ name: '모든 ui 패키지', value: 'all' },
|
|
439
473
|
...uiPackages.map((name) => ({ name: `packages/ui/ui-apps/${name}`, value: name })),
|
package/src/mcp.mjs
CHANGED
|
@@ -175,6 +175,30 @@ function buildServerInstructions(cliName) {
|
|
|
175
175
|
- \`sh_ui_add_component\` / \`sh_ui_remove_component\` — 설치/삭제
|
|
176
176
|
- \`sh_ui_get_changelog\` — 최근 변경 내역
|
|
177
177
|
|
|
178
|
+
## UI 짤 때 사고 순서 (raw HTML 기본값 회피)
|
|
179
|
+
|
|
180
|
+
새 위젯·페이지를 작성할 때 \`<aside>\` / \`<nav>\` / \`<header>\` / \`<table>\` / \`<button>\` 같은 시맨틱 태그를 raw 로 쓰기 전에:
|
|
181
|
+
|
|
182
|
+
1. **\`sh_ui_list_components\` 로 카탈로그 한 번 훑기** — Sidebar / Header / AppShell / Breadcrumb / Pagination / Tabs 등 **위젯 단위** 컴포넌트도 있다 (Button/Input 같은 primitive 만 있는 게 아님). "사이드바 직접 짜야지" 같은 자동 사고를 의식적으로 멈추고 카탈로그 확인.
|
|
183
|
+
2. 매칭되는 sh-ui 컴포넌트가 있으면 **그걸 우선 사용**. \`sh_ui_get_component\` 로 props/사용 패턴 확인.
|
|
184
|
+
3. 매칭 없을 때만 raw HTML + Tailwind 폴백.
|
|
185
|
+
4. \`sh_ui_add_component\` 로 설치한 컴포넌트는 페이지 작성 단계에서 다시 의식적으로 import — install 해놓고 안 쓰는 누락이 잦다 (Breadcrumb / DropdownMenu 류). add 시점에 사용처를 머릿속에 메모하고, 페이지 짤 때 그 목록을 한 번 훑기.
|
|
186
|
+
|
|
187
|
+
### raw HTML 폴백이 정당한 경우
|
|
188
|
+
- 카탈로그에 매칭 컴포넌트 없음 (예: \`<table>\` — sh-ui 에 table 없음)
|
|
189
|
+
- 의도적인 변형 (sh-ui 변종에 없는 dashed border 카드 등)
|
|
190
|
+
- 한 번 쓰는 ad-hoc 레이아웃
|
|
191
|
+
|
|
192
|
+
이런 경우라도 **"sh-ui 에 X 없어서 직접"** 같은 코멘트를 짧게 남기면 다음 작업자(사람·AI) 가 의도 인식 가능.
|
|
193
|
+
|
|
194
|
+
### 흔한 누락 패턴
|
|
195
|
+
|
|
196
|
+
이전 세션에서 반복된 실수:
|
|
197
|
+
- 사이드바를 \`<aside>\` + \`<Link>\` 로 직접 짬 → sh-ui 의 \`Sidebar\` / \`SidebarProvider\` / \`SidebarMenu\` 등 풍부한 API 가 있음
|
|
198
|
+
- breadcrumb 을 \`<nav><Link>···<ChevronRight />···</nav>\` 로 직접 짬 → \`Breadcrumb\` / \`BreadcrumbList\` / \`BreadcrumbItem\` / \`BreadcrumbSeparator\` 가 카탈로그에 있음
|
|
199
|
+
- 다이얼로그 cancel 버튼을 \`<Button onClick={() => setOpen(false)}>\` 로 우회 → 정석은 \`<DialogClose render={<Button>취소</Button>} />\` (Base UI render prop)
|
|
200
|
+
- table 외관의 카드 그리드를 raw \`<div>\` 로 → \`Card\` / \`CardHeader\` / \`CardContent\` / \`CardFooter\` 사용
|
|
201
|
+
|
|
178
202
|
## 앱 이름 변경 (monorepo)
|
|
179
203
|
|
|
180
204
|
사용자가 "apps/web 을 apps/dashboard 로 바꿔줘" 같이 모노레포 앱 이름 변경을 요청하면 \`sh_ui_rename_app\` 사용 — 손으로 6~10 군데 (디렉토리, package.json name, tsconfig paths, Dockerfile WORKDIR, next.config transpilePackages, sh-ui.config aliases, README, .github/workflows) 갈아엎지 않도록 자동화. \`dryRun: true\` 로 먼저 변경 매트릭스 보여주고 사용자 확인 후 실행 권장.
|
|
@@ -7,17 +7,29 @@
|
|
|
7
7
|
"lint": "eslint . --max-warnings 0"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
+
"@base-ui/react": "^1.4.1",
|
|
10
11
|
"class-variance-authority": "^0.7.1",
|
|
11
12
|
"clsx": "^2.1.1",
|
|
12
|
-
"
|
|
13
|
+
"lucide-react": "^0.563.0",
|
|
14
|
+
"next-themes": "^0.4.6",
|
|
15
|
+
"react": "^19.2.4",
|
|
16
|
+
"react-dom": "^19.2.4",
|
|
17
|
+
"react-hook-form": "^7.56.4",
|
|
18
|
+
"sonner": "^2.0.7",
|
|
19
|
+
"tailwind-merge": "^3.5.0",
|
|
20
|
+
"zod": "^4.3.6"
|
|
13
21
|
},
|
|
14
22
|
"devDependencies": {
|
|
23
|
+
"@types/react": "^19.2.10",
|
|
24
|
+
"@types/react-dom": "^19.2.3",
|
|
15
25
|
"@workspace/eslint-config": "workspace:*",
|
|
16
26
|
"@workspace/typescript-config": "workspace:*",
|
|
17
27
|
"eslint": "^9.39.2",
|
|
18
28
|
"typescript": "^5.9.3"
|
|
19
29
|
},
|
|
20
30
|
"exports": {
|
|
31
|
+
"./components/*": "./src/components/*.tsx",
|
|
32
|
+
"./hooks/*": "./src/hooks/*.ts",
|
|
21
33
|
"./lib/*": "./src/lib/*.ts"
|
|
22
34
|
}
|
|
23
35
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"platform": "react",
|
|
3
|
+
"cssFramework": "plain",
|
|
4
|
+
"paths": {
|
|
5
|
+
"components": "src/components",
|
|
6
|
+
"hooks": "src/hooks",
|
|
7
|
+
"utils": "src/lib/utils.ts"
|
|
8
|
+
},
|
|
9
|
+
"aliases": {
|
|
10
|
+
"components": "@workspace/ui-core/components",
|
|
11
|
+
"hooks": "@workspace/ui-core/hooks",
|
|
12
|
+
"utils": "@workspace/ui-core/lib/utils",
|
|
13
|
+
"ui": "@workspace/ui-core/components"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -7,15 +7,9 @@
|
|
|
7
7
|
"lint": "eslint . --max-warnings 0"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@base-ui/react": "^1.4.1",
|
|
11
10
|
"@workspace/ui-core": "workspace:*",
|
|
12
|
-
"lucide-react": "^0.563.0",
|
|
13
|
-
"next-themes": "^0.4.6",
|
|
14
11
|
"react": "^19.2.4",
|
|
15
|
-
"react-dom": "^19.2.4"
|
|
16
|
-
"react-hook-form": "^7.56.4",
|
|
17
|
-
"sonner": "^2.0.7",
|
|
18
|
-
"zod": "^4.3.6"
|
|
12
|
+
"react-dom": "^19.2.4"
|
|
19
13
|
},
|
|
20
14
|
"devDependencies": {
|
|
21
15
|
"@tailwindcss/postcss": "^4.1.18",
|
|
@@ -30,9 +24,6 @@
|
|
|
30
24
|
},
|
|
31
25
|
"exports": {
|
|
32
26
|
"./globals.css": "./src/styles/globals.css",
|
|
33
|
-
"./postcss.config": "./postcss.config.mjs"
|
|
34
|
-
"./lib/*": "./src/lib/*.ts",
|
|
35
|
-
"./components/*": "./src/components/*.tsx",
|
|
36
|
-
"./hooks/*": "./src/hooks/*.ts"
|
|
27
|
+
"./postcss.config": "./postcss.config.mjs"
|
|
37
28
|
}
|
|
38
29
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"platform": "react",
|
|
3
3
|
"cssFramework": "plain",
|
|
4
|
+
"role": "tokens-only",
|
|
4
5
|
"theme": {
|
|
5
6
|
"base": "neutral",
|
|
6
7
|
"radius": "md",
|
|
@@ -8,13 +9,6 @@
|
|
|
8
9
|
},
|
|
9
10
|
"paths": {
|
|
10
11
|
"tokens": "src/styles/tokens.css",
|
|
11
|
-
"styles": "src/styles"
|
|
12
|
-
"components": "src/components",
|
|
13
|
-
"utils": "src/lib/utils.ts"
|
|
14
|
-
},
|
|
15
|
-
"aliases": {
|
|
16
|
-
"components": "@workspace/ui-app-name/components",
|
|
17
|
-
"utils": "@workspace/ui-app-name/lib/utils",
|
|
18
|
-
"ui": "@workspace/ui-app-name/components"
|
|
12
|
+
"styles": "src/styles"
|
|
19
13
|
}
|
|
20
14
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|