sh-ui-cli 0.115.0 → 0.117.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/bin/sh-ui.mjs +70 -3
- package/data/changelog/versions.json +24 -0
- package/data/registry/flutter/registry.json +9 -0
- package/data/registry/flutter/widgets/sh_ui_tree.dart +428 -0
- package/data/registry/react/components/rich-text-editor/index.module.tsx +98 -8
- package/data/registry/react/components/rich-text-editor/index.tailwind.tsx +87 -4
- package/data/registry/react/components/rich-text-editor/index.tsx +98 -8
- package/data/registry/react/components/rich-text-editor/rich-text-editor.test.tsx +283 -0
- package/data/registry/react/components/tree/flatten.test.ts +72 -0
- package/data/registry/react/components/tree/flatten.ts +69 -0
- package/data/registry/react/components/tree/index.module.tsx +215 -0
- package/data/registry/react/components/tree/index.tailwind.tsx +224 -0
- package/data/registry/react/components/tree/index.tsx +215 -0
- package/data/registry/react/components/tree/styles.css +69 -0
- package/data/registry/react/components/tree/styles.module.css +69 -0
- package/data/registry/react/components/tree/tree.test.tsx +110 -0
- package/data/registry/react/components/tree/types.ts +36 -0
- package/data/registry/react/peer-versions.json +9 -1
- package/data/registry/react/registry.json +45 -0
- package/data/registry/react/tokens-used.json +40 -1
- package/data/summaries/react.json +2 -1
- package/package.json +3 -3
- package/src/add.mjs +31 -1
- package/src/commands.mjs +6 -0
- package/src/create/generator.js +4 -0
- package/src/doctor.mjs +14 -0
- package/src/init.mjs +19 -0
- package/src/levenshtein.mjs +36 -0
- package/src/list.mjs +13 -0
- package/src/mcp.mjs +14 -0
- package/src/migrate-bundled.mjs +14 -0
- package/src/migrate-v065.mjs +14 -0
- package/src/remove.mjs +15 -0
- package/src/rename-app.mjs +15 -0
- package/src/theme-extract.mjs +13 -0
- package/src/tokens-cmd.mjs +12 -0
- package/src/upgrade-cli.mjs +13 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
|
|
3
|
+
export interface TreeNode {
|
|
4
|
+
id: string;
|
|
5
|
+
label: React.ReactNode;
|
|
6
|
+
children?: TreeNode[];
|
|
7
|
+
icon?: React.ReactNode;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type TreeSize = "sm" | "md";
|
|
12
|
+
|
|
13
|
+
export interface TreeProps {
|
|
14
|
+
nodes: TreeNode[];
|
|
15
|
+
expandedIds?: string[];
|
|
16
|
+
defaultExpandedIds?: string[];
|
|
17
|
+
onExpandedChange?: (ids: string[]) => void;
|
|
18
|
+
selectedId?: string | null;
|
|
19
|
+
defaultSelectedId?: string | null;
|
|
20
|
+
onSelect?: (id: string | null) => void;
|
|
21
|
+
renderLabel?: (node: TreeNode) => React.ReactNode;
|
|
22
|
+
size?: TreeSize;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface VisibleNode {
|
|
27
|
+
id: string;
|
|
28
|
+
node: TreeNode;
|
|
29
|
+
level: number;
|
|
30
|
+
parentId: string | null;
|
|
31
|
+
hasChildren: boolean;
|
|
32
|
+
expanded: boolean;
|
|
33
|
+
disabled: boolean;
|
|
34
|
+
setSize: number;
|
|
35
|
+
posInSet: number;
|
|
36
|
+
}
|
|
@@ -3,12 +3,20 @@
|
|
|
3
3
|
"versions": {
|
|
4
4
|
"@base-ui/react": "^1.4.1",
|
|
5
5
|
"@tanstack/react-form": "^1.29.1",
|
|
6
|
+
"@tiptap/core": "^3.26.0",
|
|
7
|
+
"@tiptap/extension-link": "^3.26.0",
|
|
8
|
+
"@tiptap/extension-placeholder": "^3.26.0",
|
|
9
|
+
"@tiptap/extension-text-style": "^3.26.0",
|
|
10
|
+
"@tiptap/pm": "^3.26.0",
|
|
11
|
+
"@tiptap/react": "^3.26.0",
|
|
12
|
+
"@tiptap/starter-kit": "^3.26.0",
|
|
6
13
|
"@vanilla-extract/css": "^1.16.0",
|
|
7
14
|
"class-variance-authority": "^0.7.1",
|
|
8
15
|
"clsx": "^2.1.1",
|
|
9
16
|
"lucide-react": "^1.11.0",
|
|
10
17
|
"react-hook-form": "^7.74.0",
|
|
11
18
|
"shiki": "^4.0.2",
|
|
12
|
-
"tailwind-merge": "^3.5.0"
|
|
19
|
+
"tailwind-merge": "^3.5.0",
|
|
20
|
+
"tiptap-markdown": "^0.9.0"
|
|
13
21
|
}
|
|
14
22
|
}
|
|
@@ -504,10 +504,12 @@
|
|
|
504
504
|
"dependencies": [
|
|
505
505
|
"@tiptap/react",
|
|
506
506
|
"@tiptap/pm",
|
|
507
|
+
"@tiptap/core",
|
|
507
508
|
"@tiptap/starter-kit",
|
|
508
509
|
"@tiptap/extension-placeholder",
|
|
509
510
|
"@tiptap/extension-link",
|
|
510
511
|
"@tiptap/extension-text-style",
|
|
512
|
+
"tiptap-markdown",
|
|
511
513
|
"lucide-react"
|
|
512
514
|
],
|
|
513
515
|
"registryDependencies": ["utils"]
|
|
@@ -1659,6 +1661,49 @@
|
|
|
1659
1661
|
"dependencies": ["@base-ui/react"],
|
|
1660
1662
|
"registryDependencies": ["utils"]
|
|
1661
1663
|
},
|
|
1664
|
+
"tree": {
|
|
1665
|
+
"name": "tree",
|
|
1666
|
+
"type": "component",
|
|
1667
|
+
"files": [
|
|
1668
|
+
{
|
|
1669
|
+
"src": "components/tree/index.tsx",
|
|
1670
|
+
"dest": "{components}/tree/index.tsx",
|
|
1671
|
+
"frameworks": ["plain"]
|
|
1672
|
+
},
|
|
1673
|
+
{
|
|
1674
|
+
"src": "components/tree/types.ts",
|
|
1675
|
+
"dest": "{components}/tree/types.ts",
|
|
1676
|
+
"frameworks": ["plain", "tailwind", "css-modules"]
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
"src": "components/tree/flatten.ts",
|
|
1680
|
+
"dest": "{components}/tree/flatten.ts",
|
|
1681
|
+
"frameworks": ["plain", "tailwind", "css-modules"]
|
|
1682
|
+
},
|
|
1683
|
+
{
|
|
1684
|
+
"src": "components/tree/styles.css",
|
|
1685
|
+
"dest": "{components}/tree/styles.css",
|
|
1686
|
+
"frameworks": ["plain"]
|
|
1687
|
+
},
|
|
1688
|
+
{
|
|
1689
|
+
"src": "components/tree/index.tailwind.tsx",
|
|
1690
|
+
"dest": "{components}/tree/index.tsx",
|
|
1691
|
+
"frameworks": ["tailwind"]
|
|
1692
|
+
},
|
|
1693
|
+
{
|
|
1694
|
+
"src": "components/tree/index.module.tsx",
|
|
1695
|
+
"dest": "{components}/tree/index.tsx",
|
|
1696
|
+
"frameworks": ["css-modules"]
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
"src": "components/tree/styles.module.css",
|
|
1700
|
+
"dest": "{components}/tree/styles.module.css",
|
|
1701
|
+
"frameworks": ["css-modules"]
|
|
1702
|
+
}
|
|
1703
|
+
],
|
|
1704
|
+
"dependencies": [],
|
|
1705
|
+
"registryDependencies": ["utils"]
|
|
1706
|
+
},
|
|
1662
1707
|
"carousel": {
|
|
1663
1708
|
"name": "carousel",
|
|
1664
1709
|
"type": "component",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$description": "컴포넌트별 토큰 의존성 (var(--*) 추출). build-registry-tokens.mjs 가 자동 생성.",
|
|
3
|
-
"$generated": "2026-06-
|
|
3
|
+
"$generated": "2026-06-18T06:02:23.309Z",
|
|
4
4
|
"components": {
|
|
5
5
|
"button": {
|
|
6
6
|
"plain": [
|
|
@@ -1931,6 +1931,45 @@
|
|
|
1931
1931
|
],
|
|
1932
1932
|
"vanilla-extract": []
|
|
1933
1933
|
},
|
|
1934
|
+
"tree": {
|
|
1935
|
+
"plain": [
|
|
1936
|
+
"--background-muted",
|
|
1937
|
+
"--border-width-strong",
|
|
1938
|
+
"--duration-fast",
|
|
1939
|
+
"--ease-standard",
|
|
1940
|
+
"--foreground",
|
|
1941
|
+
"--foreground-muted",
|
|
1942
|
+
"--radius",
|
|
1943
|
+
"--ring",
|
|
1944
|
+
"--space-2",
|
|
1945
|
+
"--space-4",
|
|
1946
|
+
"--text-xs",
|
|
1947
|
+
"--weight-medium"
|
|
1948
|
+
],
|
|
1949
|
+
"tailwind": [
|
|
1950
|
+
"--border-width-strong",
|
|
1951
|
+
"--duration-fast",
|
|
1952
|
+
"--radius",
|
|
1953
|
+
"--space-2",
|
|
1954
|
+
"--space-4",
|
|
1955
|
+
"--text-xs"
|
|
1956
|
+
],
|
|
1957
|
+
"css-modules": [
|
|
1958
|
+
"--background-muted",
|
|
1959
|
+
"--border-width-strong",
|
|
1960
|
+
"--duration-fast",
|
|
1961
|
+
"--ease-standard",
|
|
1962
|
+
"--foreground",
|
|
1963
|
+
"--foreground-muted",
|
|
1964
|
+
"--radius",
|
|
1965
|
+
"--ring",
|
|
1966
|
+
"--space-2",
|
|
1967
|
+
"--space-4",
|
|
1968
|
+
"--text-xs",
|
|
1969
|
+
"--weight-medium"
|
|
1970
|
+
],
|
|
1971
|
+
"vanilla-extract": []
|
|
1972
|
+
},
|
|
1934
1973
|
"carousel": {
|
|
1935
1974
|
"plain": [
|
|
1936
1975
|
"--background",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"page-toc": "페이지 자동 목차 — 헤딩 스캔 · slugify · IntersectionObserver active 추적 · smooth scroll. routeKey 로 라우트 변경 자동 재스캔, levels/excludeSelector 커스터마이즈.",
|
|
43
43
|
"code-editor": "CodeMirror 6 기반 코드 에디터 — js/ts/jsx/tsx/json/css/html/markdown, sh-ui 토큰 테마, readOnly·placeholder·minHeight/maxHeight. controlled(value/onChange) · uncontrolled(defaultValue) 모두 지원.",
|
|
44
44
|
"markdown-editor": "마크다운 에디터 — CodeEditor + react-markdown 라이브 프리뷰 합성, GFM 지원, raw HTML 차단으로 XSS 방어. controlled(value/onChange) · uncontrolled(defaultValue) 모두 지원.",
|
|
45
|
-
"rich-text-editor": "Tiptap 3 기반 WYSIWYG 에디터 —
|
|
45
|
+
"rich-text-editor": "Tiptap 3 기반 WYSIWYG 에디터 — format='html'(기본) 또는 format='markdown'(tiptap-markdown 직렬화) I/O, 기본 toolbar(헤딩·리스트·인용·코드·링크·강조), readOnly·placeholder. submitOnEnter+onSubmit(Enter 전송·Shift+Enter 줄바꿈), extensions(추가 확장 주입)·onCreate(에디터 인스턴스 접근). controlled(value/onChange) · uncontrolled(defaultValue) 모두 지원.",
|
|
46
46
|
"base": "CSS 리셋 — base.css.",
|
|
47
47
|
"breakpoints": "반응형 미디어 쿼리 토큰 — breakpoints.css.",
|
|
48
48
|
"focus-ring": "공용 포커스 링 스타일 — focus-ring.css.",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"form-tanstack": "TanStack Form 인스턴스 → sh-ui FormStore 어댑터. `adaptTanstackForm(tanstackInstance, config)` — TanStack Form 이 state owner. @tanstack/react-form 은 peerDependency.",
|
|
58
58
|
"calendar": "내부 캘린더 위젯 (DatePicker 가 사용). single / multiple / range 모드. CalendarMessages 로 a11y 텍스트 override. 일반적으로 직접 사용보다 DatePicker / DateRangePicker 권장.",
|
|
59
59
|
"scroll-area": "커스텀 스크롤 컨테이너 — composite export ScrollArea (Base UI). 내부에서 viewport + scrollbar + thumb + corner 를 자동 구성하며 OS-native 스크롤바를 대체한다. orientation: \"vertical\" | \"horizontal\" | \"both\" (기본 vertical). 외부 height/width 가 정해진 컨테이너 안에서 사용. viewportClassName 으로 viewport 의 패딩/레이아웃 분리 적용. 스크롤바는 hover/scrolling 시 fade in, prefers-reduced-motion 존중.",
|
|
60
|
+
"tree": "계층 데이터 트리 뷰 — Tree 단일 export + TreeNode 타입(`@/components/ui/tree/types`). nodes(TreeNode[]: id/label/children/icon/disabled) 를 평탄화해 role=tree 로 렌더. 확장: expandedIds/onExpandedChange(controlled) 또는 defaultExpandedIds(uncontrolled). 선택: selectedId/onSelect(controlled) 또는 defaultSelectedId(uncontrolled). 키보드 네비(화살표·Home/End·Enter·typeahead), renderLabel 커스텀 렌더러, size(sm/md). Base UI 비의존 자체 구현.",
|
|
60
61
|
"sheet": "화면 가장자리에서 슬라이드 인 하는 side drawer — separate exports: Sheet / SheetTrigger / SheetClose / SheetContent / SheetTitle / SheetDescription / SheetHeader / SheetFooter / SheetCloseX (Base UI Drawer 래핑, 포커스 트랩). 글로벌 알림함 · 작업 큐 · 보조 패널 같은 사이드바 무관 모달 시트용. 사이드바 인근 detail 패널은 SidebarPanel, 중앙 강제 모달은 Dialog 권장. SheetContent 의 side: \"right\" | \"left\" | \"top\" | \"bottom\" (기본 right) 으로 진입 방향 지정. SheetTrigger·SheetClose 는 자체 button — 다른 엘리먼트 슬롯은 `render` prop. ESC/바깥 클릭/포커스 복귀 자동, prefers-reduced-motion 시 transform 트랜지션 제거."
|
|
61
62
|
}
|
|
62
63
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sh-ui-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.117.0",
|
|
4
4
|
"description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"components"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@inquirer/prompts": "^8.
|
|
28
|
+
"@inquirer/prompts": "^8.5.2",
|
|
29
29
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
30
30
|
"zod": "^4.4.3"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"fs-extra": "^11.3.5",
|
|
34
|
-
"vitest": "^
|
|
34
|
+
"vitest": "^4.1.8"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
package/src/add.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { pathToFileURL } from "node:url";
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { select } from "@inquirer/prompts";
|
|
7
7
|
import { formatUnifiedDiff } from "./diff.mjs";
|
|
8
|
+
import { suggest } from "./levenshtein.mjs";
|
|
8
9
|
import { getRegistryRoot, getTokensRoot, getPeerVersionsPath } from "./paths.mjs";
|
|
9
10
|
import { THEME_BASES } from "./constants.js";
|
|
10
11
|
import {
|
|
@@ -20,6 +21,25 @@ import {
|
|
|
20
21
|
rewriteCrossComponentImports,
|
|
21
22
|
} from "./css-bundle.mjs";
|
|
22
23
|
|
|
24
|
+
export const HELP_TEXT = `sh-ui add — 컴포넌트 소스를 프로젝트로 복사 + 필요한 패키지 자동 설치
|
|
25
|
+
|
|
26
|
+
사용법:
|
|
27
|
+
sh-ui add <component...>
|
|
28
|
+
sh-ui add tokens 설정 기반 토큰 파일 생성 (특수값)
|
|
29
|
+
|
|
30
|
+
옵션:
|
|
31
|
+
--skip-install 외부 패키지 자동 설치 생략
|
|
32
|
+
--diff 파일을 쓰지 않고 변경 내역(unified diff)만 출력
|
|
33
|
+
--force 기존 파일을 모두 덮어쓰기 (prompt 없음)
|
|
34
|
+
--keep 기존 파일을 모두 유지 (prompt 없음)
|
|
35
|
+
--app <name> monorepo 라우팅 시 대상 ui-{name} 명시
|
|
36
|
+
|
|
37
|
+
예:
|
|
38
|
+
sh-ui add button
|
|
39
|
+
sh-ui add button card --diff
|
|
40
|
+
sh-ui add tokens
|
|
41
|
+
`;
|
|
42
|
+
|
|
23
43
|
/**
|
|
24
44
|
* 기존 파일과 registry 파일 내용이 다를 때 keep/overwrite 결정.
|
|
25
45
|
* strategy 가 "prompt" 면 사용자에게 묻고, 그 외엔 즉시 결정.
|
|
@@ -336,6 +356,16 @@ function effectiveFramework(entry, cssFramework) {
|
|
|
336
356
|
return hasVariant ? cssFramework : "plain";
|
|
337
357
|
}
|
|
338
358
|
|
|
359
|
+
/** 컴포넌트 not-found 에러 메시지. 가까운 후보가 있으면 "혹시 …?" 를 덧붙인다. */
|
|
360
|
+
export function buildNotFoundMessage(name, platform, candidates) {
|
|
361
|
+
const hits = suggest(name, candidates);
|
|
362
|
+
const hint = hits.length ? ` 혹시 ${hits.join(", ")}?` : "";
|
|
363
|
+
return (
|
|
364
|
+
`'${name}' 컴포넌트를 ${platform} 레지스트리에서 찾을 수 없습니다.${hint}` +
|
|
365
|
+
` 전체 목록: sh-ui list --all`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
339
369
|
async function addComponent(name, config, cwd, installed, pendingDeps, diffMode, summary, conflictResolver, validationCtx) {
|
|
340
370
|
const registryRoot = getRegistryRoot(config.platform);
|
|
341
371
|
const registry = JSON.parse(
|
|
@@ -344,7 +374,7 @@ async function addComponent(name, config, cwd, installed, pendingDeps, diffMode,
|
|
|
344
374
|
const entry = registry.components?.[name];
|
|
345
375
|
if (!entry) {
|
|
346
376
|
throw new Error(
|
|
347
|
-
|
|
377
|
+
buildNotFoundMessage(name, config.platform, Object.keys(registry.components ?? {})),
|
|
348
378
|
);
|
|
349
379
|
}
|
|
350
380
|
|
package/src/commands.mjs
ADDED
package/src/create/generator.js
CHANGED
|
@@ -678,6 +678,10 @@ async function inferMonorepoPlatform(cwd) {
|
|
|
678
678
|
* @param {string} [options.onConflict] 'prompt' | 'keep' | 'overwrite'
|
|
679
679
|
*/
|
|
680
680
|
export async function addComponent(options = {}) {
|
|
681
|
+
// monorepo 라우팅은 컴포넌트별 registry 조회를 직접 하지 않고 add() 로 위임한다.
|
|
682
|
+
// 따라서 not-found 처리(오타 추천 포함)는 add.mjs 의 addComponent 가 공유 헬퍼
|
|
683
|
+
// buildNotFoundMessage 로 던지는 메시지를 그대로 쓴다 — standalone 경로와 동일,
|
|
684
|
+
// 메시지 로직 중복 없음 (DRY).
|
|
681
685
|
const { add } = await import('../add.mjs');
|
|
682
686
|
|
|
683
687
|
const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
|
package/src/doctor.mjs
CHANGED
|
@@ -18,6 +18,20 @@ import {
|
|
|
18
18
|
findMissingTokens,
|
|
19
19
|
} from "./tokens-validate.mjs";
|
|
20
20
|
|
|
21
|
+
export const HELP_TEXT = `sh-ui doctor — 프로젝트 정합성 점검
|
|
22
|
+
|
|
23
|
+
사용법:
|
|
24
|
+
sh-ui doctor
|
|
25
|
+
|
|
26
|
+
점검 항목:
|
|
27
|
+
config sh-ui.config.json 존재·필드 유효성
|
|
28
|
+
tokens tokens.css 정의·필요 변수 누락 여부
|
|
29
|
+
설치된 컴포넌트 소스/스타일 파일 정합성
|
|
30
|
+
|
|
31
|
+
예:
|
|
32
|
+
sh-ui doctor
|
|
33
|
+
`;
|
|
34
|
+
|
|
21
35
|
const ICON = { ok: "✓", warn: "⚠", fail: "✗" };
|
|
22
36
|
|
|
23
37
|
class Report {
|
package/src/init.mjs
CHANGED
|
@@ -13,6 +13,25 @@ import {
|
|
|
13
13
|
CSS_FRAMEWORKS_PLANNED,
|
|
14
14
|
} from "./constants.js";
|
|
15
15
|
|
|
16
|
+
export const HELP_TEXT = `sh-ui init — sh-ui.config.json 생성 (기존 프로젝트에 sh-ui 얹기)
|
|
17
|
+
|
|
18
|
+
사용법:
|
|
19
|
+
sh-ui init [options]
|
|
20
|
+
|
|
21
|
+
옵션:
|
|
22
|
+
--platform <react|flutter> 타겟 플랫폼
|
|
23
|
+
--base <neutral|zinc|...> 색 베이스
|
|
24
|
+
--radius <none|sm|md|lg|xl|full> 모서리 반경
|
|
25
|
+
--mode <light|dark|light-dark> 색 모드
|
|
26
|
+
--cssFramework <plain|tailwind|css-modules> CSS 전략 (vanilla-extract 예정)
|
|
27
|
+
--force 기존 sh-ui.config.json 덮어쓰기
|
|
28
|
+
--yes 대화형 프롬프트 생략 (기본값 채택)
|
|
29
|
+
|
|
30
|
+
예:
|
|
31
|
+
sh-ui init
|
|
32
|
+
sh-ui init --platform react --base neutral --mode light-dark --yes
|
|
33
|
+
`;
|
|
34
|
+
|
|
16
35
|
const CHOICES = {
|
|
17
36
|
platform: INIT_PLATFORMS,
|
|
18
37
|
base: THEME_BASES,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// 의존성 없는 편집거리 + 후보 제안. diff.mjs 가 LCS 를 자체 구현한 관행을 따른다.
|
|
2
|
+
|
|
3
|
+
/** 두 문자열의 Levenshtein 편집거리 (삽입/삭제/치환 비용 1). */
|
|
4
|
+
export function levenshtein(a, b) {
|
|
5
|
+
const m = a.length;
|
|
6
|
+
const n = b.length;
|
|
7
|
+
if (m === 0) return n;
|
|
8
|
+
if (n === 0) return m;
|
|
9
|
+
let prev = Array.from({ length: n + 1 }, (_, j) => j);
|
|
10
|
+
let curr = new Array(n + 1);
|
|
11
|
+
for (let i = 1; i <= m; i++) {
|
|
12
|
+
curr[0] = i;
|
|
13
|
+
for (let j = 1; j <= n; j++) {
|
|
14
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
15
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
16
|
+
}
|
|
17
|
+
[prev, curr] = [curr, prev];
|
|
18
|
+
}
|
|
19
|
+
return prev[n];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* input 과 가까운 candidates 를 거리 오름차순으로 반환.
|
|
24
|
+
* @param {string} input
|
|
25
|
+
* @param {string[]} candidates
|
|
26
|
+
* @param {{max?: number, maxDistance?: number}} [opts]
|
|
27
|
+
* @returns {string[]} 거리 <= maxDistance 인 후보 상위 max 개 (없으면 빈 배열)
|
|
28
|
+
*/
|
|
29
|
+
export function suggest(input, candidates, { max = 3, maxDistance = 2 } = {}) {
|
|
30
|
+
return candidates
|
|
31
|
+
.map((name) => ({ name, dist: levenshtein(input, name) }))
|
|
32
|
+
.filter((c) => c.dist <= maxDistance)
|
|
33
|
+
.sort((a, b) => a.dist - b.dist || a.name.localeCompare(b.name))
|
|
34
|
+
.slice(0, max)
|
|
35
|
+
.map((c) => c.name);
|
|
36
|
+
}
|
package/src/list.mjs
CHANGED
|
@@ -3,6 +3,19 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { resolve, relative } from "node:path";
|
|
4
4
|
import { getRegistryRoot } from "./paths.mjs";
|
|
5
5
|
|
|
6
|
+
export const HELP_TEXT = `sh-ui list — 현재 설치된 컴포넌트 목록 표시
|
|
7
|
+
|
|
8
|
+
사용법:
|
|
9
|
+
sh-ui list
|
|
10
|
+
|
|
11
|
+
옵션:
|
|
12
|
+
--all 설치되지 않은 컴포넌트까지 모두 표시
|
|
13
|
+
|
|
14
|
+
예:
|
|
15
|
+
sh-ui list
|
|
16
|
+
sh-ui list --all
|
|
17
|
+
`;
|
|
18
|
+
|
|
6
19
|
function resolveDest(template, config) {
|
|
7
20
|
return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (m, key) => {
|
|
8
21
|
const v = config.paths?.[key];
|
package/src/mcp.mjs
CHANGED
|
@@ -56,6 +56,20 @@ import { THEME_PRESET_NAMES } from "./create/theme/presets.js";
|
|
|
56
56
|
import { decodeTheme } from "./create/theme/decode.js";
|
|
57
57
|
import { encodeTheme } from "./create/theme/encode.js";
|
|
58
58
|
|
|
59
|
+
export const HELP_TEXT = `sh-ui mcp — IDE-내 AI 용 MCP 서버 / 설정
|
|
60
|
+
|
|
61
|
+
사용법:
|
|
62
|
+
sh-ui mcp MCP 서버(stdio) 시작
|
|
63
|
+
sh-ui mcp init --client <name> IDE MCP 설정 파일에 sh-ui 엔트리 자동 추가
|
|
64
|
+
|
|
65
|
+
옵션 (mcp init):
|
|
66
|
+
--client <name> claude-code | cursor | claude-desktop | codex
|
|
67
|
+
|
|
68
|
+
예:
|
|
69
|
+
sh-ui mcp
|
|
70
|
+
sh-ui mcp init --client claude-code
|
|
71
|
+
`;
|
|
72
|
+
|
|
59
73
|
const PLATFORMS = INIT_PLATFORMS;
|
|
60
74
|
const BASES = THEME_BASES;
|
|
61
75
|
const RADII = THEME_RADII;
|
package/src/migrate-bundled.mjs
CHANGED
|
@@ -16,6 +16,20 @@ import { existsSync } from "node:fs";
|
|
|
16
16
|
import { resolve, relative, dirname, join } from "node:path";
|
|
17
17
|
import { upsertSection, stripStylesImport } from "./css-bundle.mjs";
|
|
18
18
|
|
|
19
|
+
export const HELP_TEXT = `sh-ui migrate bundled — cssStrategy=bundled 로 전환
|
|
20
|
+
|
|
21
|
+
사용법:
|
|
22
|
+
sh-ui migrate bundled per-component styles.css → 단일 sh-ui-components.css
|
|
23
|
+
|
|
24
|
+
옵션:
|
|
25
|
+
--apply 실제 적용 (미지정 시 미리보기)
|
|
26
|
+
--bundle <path> 번들 CSS 파일 경로 명시
|
|
27
|
+
|
|
28
|
+
예:
|
|
29
|
+
sh-ui migrate bundled
|
|
30
|
+
sh-ui migrate bundled --apply
|
|
31
|
+
`;
|
|
32
|
+
|
|
19
33
|
async function loadConfig(cwd) {
|
|
20
34
|
const configPath = resolve(cwd, "sh-ui.config.json");
|
|
21
35
|
if (!existsSync(configPath)) {
|
package/src/migrate-v065.mjs
CHANGED
|
@@ -14,6 +14,20 @@ import * as fsp from "node:fs/promises";
|
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
|
|
17
|
+
export const HELP_TEXT = `sh-ui migrate-v065 — v0.64.x → v0.65 자동 마이그레이션
|
|
18
|
+
|
|
19
|
+
사용법:
|
|
20
|
+
sh-ui migrate-v065 ui-app 컴포넌트를 ui-core 단일 SoT 로 dedup 이동
|
|
21
|
+
|
|
22
|
+
옵션:
|
|
23
|
+
--dry-run 변경 plan 만 출력 (기본값 — 미지정 시 동작)
|
|
24
|
+
--apply 실제 적용
|
|
25
|
+
|
|
26
|
+
예:
|
|
27
|
+
sh-ui migrate-v065
|
|
28
|
+
sh-ui migrate-v065 --apply
|
|
29
|
+
`;
|
|
30
|
+
|
|
17
31
|
const COMPONENT_KINDS = ["components", "hooks", "lib"];
|
|
18
32
|
|
|
19
33
|
async function fileExists(p) {
|
package/src/remove.mjs
CHANGED
|
@@ -2,6 +2,21 @@ import { readFile, rm, rmdir, readdir, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve, relative } from "node:path";
|
|
4
4
|
import { getRegistryRoot } from "./paths.mjs";
|
|
5
|
+
|
|
6
|
+
export const HELP_TEXT = `sh-ui remove — 설치된 컴포넌트 파일 삭제
|
|
7
|
+
|
|
8
|
+
사용법:
|
|
9
|
+
sh-ui remove <component...>
|
|
10
|
+
|
|
11
|
+
옵션:
|
|
12
|
+
--force 사용자가 수정한 파일도 삭제
|
|
13
|
+
--dry-run 변경 대상만 출력하고 실제로는 삭제하지 않음
|
|
14
|
+
|
|
15
|
+
예:
|
|
16
|
+
sh-ui remove button
|
|
17
|
+
sh-ui remove button card --dry-run
|
|
18
|
+
`;
|
|
19
|
+
|
|
5
20
|
import {
|
|
6
21
|
removeSection,
|
|
7
22
|
isStyleFile,
|
package/src/rename-app.mjs
CHANGED
|
@@ -19,6 +19,21 @@ import { spawn } from "node:child_process";
|
|
|
19
19
|
import { join, relative, resolve } from "node:path";
|
|
20
20
|
import { createInterface } from "node:readline/promises";
|
|
21
21
|
|
|
22
|
+
export const HELP_TEXT = `sh-ui rename-app — monorepo 의 앱 이름 일괄 변경
|
|
23
|
+
|
|
24
|
+
사용법:
|
|
25
|
+
sh-ui rename-app <old> <new> apps/<old>/, ui-<old>/ 디렉토리 + 모든 import/path 패턴
|
|
26
|
+
|
|
27
|
+
옵션:
|
|
28
|
+
--yes 대화형 확인 생략
|
|
29
|
+
--dry-run 변경 대상만 출력하고 실제로는 변경하지 않음
|
|
30
|
+
--skip-install 변경 후 pnpm install 생략
|
|
31
|
+
|
|
32
|
+
예:
|
|
33
|
+
sh-ui rename-app web admin
|
|
34
|
+
sh-ui rename-app web admin --dry-run
|
|
35
|
+
`;
|
|
36
|
+
|
|
22
37
|
/** 텍스트 파일로 처리할 확장자 (전체 파일을 읽어 치환). */
|
|
23
38
|
const TEXT_EXT = new Set([
|
|
24
39
|
".ts", ".tsx", ".js", ".mjs", ".cjs",
|
package/src/theme-extract.mjs
CHANGED
|
@@ -17,6 +17,19 @@ import { resolve, relative } from "node:path";
|
|
|
17
17
|
import { parseBlocks } from "./tokens-diff.mjs";
|
|
18
18
|
import { encodeTheme } from "./create/theme/encode.js";
|
|
19
19
|
|
|
20
|
+
export const HELP_TEXT = `sh-ui theme — 현재 토큰을 테마 코드로 추출
|
|
21
|
+
|
|
22
|
+
사용법:
|
|
23
|
+
sh-ui theme extract tokens.css 의 색·radius 를 base64 로 추출 (stdout)
|
|
24
|
+
|
|
25
|
+
옵션:
|
|
26
|
+
--out <path> 추출 결과를 stdout 대신 파일로 저장
|
|
27
|
+
|
|
28
|
+
예:
|
|
29
|
+
sh-ui theme extract
|
|
30
|
+
sh-ui theme extract --out theme.txt
|
|
31
|
+
`;
|
|
32
|
+
|
|
20
33
|
/**
|
|
21
34
|
* Dart 의 ShUiColorTokens 필드명 ↔ TOKEN_KEYS (hyphen) 매핑.
|
|
22
35
|
* inject.js 의 DART_FIELD_SOURCES 와 같은 매핑을 reverse 로 사용.
|
package/src/tokens-cmd.mjs
CHANGED
|
@@ -17,6 +17,18 @@ import { THEME_BASES } from "./constants.js";
|
|
|
17
17
|
import { parseBlocks, diffBlocks, applyAdditions } from "./tokens-diff.mjs";
|
|
18
18
|
import { parseDartTokens, diffDartTokens } from "./tokens-diff-dart.mjs";
|
|
19
19
|
|
|
20
|
+
export const HELP_TEXT = `sh-ui tokens — tokens.css 비교/업그레이드
|
|
21
|
+
|
|
22
|
+
사용법:
|
|
23
|
+
sh-ui tokens diff tokens.css 와 buildTokens 결과 비교
|
|
24
|
+
sh-ui tokens upgrade --apply 추가된 변수만 적용 (사용자 편집 보존)
|
|
25
|
+
sh-ui tokens upgrade --replace buildTokens 결과로 통째 덮어쓰기
|
|
26
|
+
|
|
27
|
+
예:
|
|
28
|
+
sh-ui tokens diff
|
|
29
|
+
sh-ui tokens upgrade --apply
|
|
30
|
+
`;
|
|
31
|
+
|
|
20
32
|
async function loadTokensBuilder() {
|
|
21
33
|
const url = pathToFileURL(resolve(getTokensRoot(), "build.mjs")).href;
|
|
22
34
|
return import(url);
|
package/src/upgrade-cli.mjs
CHANGED
|
@@ -15,6 +15,19 @@ import { dirname, resolve } from "node:path";
|
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { spawn } from "node:child_process";
|
|
17
17
|
|
|
18
|
+
export const HELP_TEXT = `sh-ui upgrade-cli — sh-ui-cli 자체를 최신으로 업그레이드
|
|
19
|
+
|
|
20
|
+
사용법:
|
|
21
|
+
sh-ui upgrade-cli 최신 버전 확인 + 변경 내역 미리보기
|
|
22
|
+
|
|
23
|
+
옵션:
|
|
24
|
+
--apply 실제 설치 후 진단 (미지정 시 미리보기만)
|
|
25
|
+
|
|
26
|
+
예:
|
|
27
|
+
sh-ui upgrade-cli
|
|
28
|
+
sh-ui upgrade-cli --apply
|
|
29
|
+
`;
|
|
30
|
+
|
|
18
31
|
const CLI_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
19
32
|
const REGISTRY_URL = "https://registry.npmjs.org/sh-ui-cli/latest";
|
|
20
33
|
|