sh-ui-cli 0.64.7 → 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.
@@ -2,6 +2,19 @@
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
+ },
5
18
  {
6
19
  "version": "0.64.7",
7
20
  "date": "2026-05-08",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.64.7",
3
+ "version": "0.65.0",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
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;
@@ -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
- if (!(await fs.pathExists(uiAppsDir))) {
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 })),
@@ -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
- "tailwind-merge": "^3.5.0"
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