sh-ui-cli 0.73.0 → 0.74.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 CHANGED
@@ -16,6 +16,7 @@ const usage = `사용법:
16
16
  특수값: tokens → 설정 기반 토큰 파일 생성
17
17
  sh-ui list 현재 설치된 컴포넌트 목록 표시
18
18
  sh-ui doctor 프로젝트 정합성 점검 (config / tokens / 설치된 컴포넌트)
19
+ sh-ui upgrade-cli [--apply] sh-ui-cli 자체를 최신으로 업그레이드 + 설치 후 진단
19
20
  sh-ui tokens diff tokens.css 와 buildTokens 결과 비교 (added/changed/removed)
20
21
  sh-ui tokens upgrade [--apply|--replace]
21
22
  --apply: 추가만 incremental (사용자 편집 보존)
@@ -155,6 +156,12 @@ try {
155
156
  if (anyFailed) process.exit(1);
156
157
  break;
157
158
  }
159
+ case "upgrade-cli": {
160
+ const apply = rest.includes("--apply");
161
+ const { runUpgradeCli } = await import("../src/upgrade-cli.mjs");
162
+ await runUpgradeCli({ cwd: process.cwd(), apply });
163
+ break;
164
+ }
158
165
  case "theme": {
159
166
  const sub = rest[0];
160
167
  const flags = rest.slice(1);
@@ -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.74.0",
7
+ "date": "2026-05-10",
8
+ "title": "DX — sh-ui.config.json JSON Schema + sh-ui upgrade-cli",
9
+ "type": "minor",
10
+ "highlights": [
11
+ "**JSON Schema for sh-ui.config.json** — `packages/cli/sh-ui.schema.json` 신설. `$schema` 필드를 GitHub raw URL 로 통일 (`https://raw.githubusercontent.com/sanghyeonKim0201/sh-ui/live/packages/cli/sh-ui.schema.json`). VS Code / Cursor 등이 자동 fetch 해서 키 자동완성 + enum 값 제안 + 오타 빨간 줄. platform / cssFramework / cssStrategy / theme.base / paths.* / aliases.* 모두 description 과 examples 포함.",
12
+ "**조건부 require** — `cssStrategy: \"bundled\"` 면 `paths.cssBundle` 필수, JSON Schema if/then 으로 자동 검증.",
13
+ "**`sh-ui upgrade-cli` 명령** — npm registry 의 latest 버전과 사용자 node_modules 의 설치본 비교. 사이의 changelog highlights (versions.json) 자동 출력. `--apply` 시 사용자 패키지 매니저로 자동 install + 설치 후 doctor / tokens diff 권장 흐름 안내.",
14
+ "**모든 템플릿 + init/create + apps/docs 의 sh-ui.config.json 에 \\$schema 일괄 적용** — 신규 프로젝트는 처음부터 IDE validation 동작."
15
+ ],
16
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.74.0"
17
+ },
5
18
  {
6
19
  "version": "0.73.0",
7
20
  "date": "2026-05-10",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.73.0",
3
+ "version": "0.74.0",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -56,6 +56,7 @@
56
56
  "src",
57
57
  "data",
58
58
  "templates",
59
+ "sh-ui.schema.json",
59
60
  "LICENSE",
60
61
  "README.md"
61
62
  ]
@@ -0,0 +1,135 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://raw.githubusercontent.com/sanghyeonKim0201/sh-ui/live/packages/cli/sh-ui.schema.json",
4
+ "title": "sh-ui project configuration",
5
+ "description": "sh-ui CLI 가 읽는 프로젝트 설정. 컴포넌트 추가/제거/토큰 빌드의 모든 동작이 이 파일을 따라간다.",
6
+ "type": "object",
7
+ "required": ["platform", "paths"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "$schema": {
11
+ "type": "string",
12
+ "description": "JSON Schema URL — IDE 자동완성용. 그대로 두는 게 안전."
13
+ },
14
+ "platform": {
15
+ "enum": ["react", "flutter"],
16
+ "description": "타겟 플랫폼. React (웹) 또는 Flutter."
17
+ },
18
+ "cssFramework": {
19
+ "enum": ["plain", "tailwind", "css-modules", "vanilla-extract"],
20
+ "description": "CSS 변종. plain = 표준 CSS + var(--*), tailwind = utility class, css-modules = .module.css, vanilla-extract = .css.ts (실험). React 전용 — Flutter 면 무시.",
21
+ "default": "plain"
22
+ },
23
+ "cssStrategy": {
24
+ "enum": ["per-component", "bundled"],
25
+ "description": "컴포넌트 CSS 전략. per-component = 각 컴포넌트 폴더에 styles.css (기본). bundled = paths.cssBundle 단일 파일에 마커 섹션 (v0.71+). React + plain 만 지원.",
26
+ "default": "per-component"
27
+ },
28
+ "role": {
29
+ "enum": ["tokens-only"],
30
+ "description": "v0.65+ 모노레포 ui-app 에 부여 — 컴포넌트 추가는 sibling ui-core 로 라우팅하고 tokens 만 허용."
31
+ },
32
+ "theme": {
33
+ "type": "object",
34
+ "description": "디자인 토큰의 색·radius·모드 설정. buildTokens 가 이 값으로 tokens.css 또는 sh_ui_tokens.dart 를 생성.",
35
+ "additionalProperties": false,
36
+ "properties": {
37
+ "base": {
38
+ "type": "string",
39
+ "description": "기본 색 스케일. buildable preset (neutral/zinc/slate) 은 buildTokens 로 재생성 가능. rich preset (rose/emerald/violet) 또는 'custom' (base64 originated) 은 보존-only.",
40
+ "examples": ["neutral", "zinc", "slate", "rose", "emerald", "violet", "custom"]
41
+ },
42
+ "radius": {
43
+ "enum": ["none", "sm", "md", "lg", "xl", "full"],
44
+ "description": "기본 코너 반경 — buildTokens 가 --radius CSS 변수로 emit."
45
+ },
46
+ "mode": {
47
+ "enum": ["light", "dark", "light-dark"],
48
+ "description": "다크 모드 전략. light-dark = OS 자동 + .light/.dark 클래스 토글, light/dark = 강제 단일 모드."
49
+ }
50
+ }
51
+ },
52
+ "paths": {
53
+ "type": "object",
54
+ "description": "프로젝트 내 파일/디렉토리 위치. CLI 가 컴포넌트 dest, tokens 위치, alias placeholder 해석에 사용.",
55
+ "additionalProperties": false,
56
+ "properties": {
57
+ "tokens": {
58
+ "type": "string",
59
+ "description": "tokens.css (React) 또는 sh_ui_tokens.dart (Flutter) 파일 경로.",
60
+ "examples": ["src/styles/tokens.css", "src/shared/styles/tokens.css", "lib/styles/tokens.css", "lib/sh_ui/foundation/sh_ui_tokens.dart"]
61
+ },
62
+ "cssEntry": {
63
+ "type": "string",
64
+ "description": "tokens.css 를 import 하는 globals.css 의 경로 (선택). doctor 가 import 정합성 검증에 사용. v0.68+.",
65
+ "examples": ["app/globals.css", "src/styles/globals.css"]
66
+ },
67
+ "cssBundle": {
68
+ "type": "string",
69
+ "description": "cssStrategy=bundled 일 때 컴포넌트 CSS 가 누적되는 단일 파일 경로. v0.71+.",
70
+ "examples": ["src/styles/sh-ui-components.css"]
71
+ },
72
+ "styles": {
73
+ "type": "string",
74
+ "description": "styles 디렉토리 (tokens, base 등 공유 CSS 위치).",
75
+ "examples": ["src/styles", "src/shared/styles"]
76
+ },
77
+ "components": {
78
+ "type": "string",
79
+ "description": "컴포넌트가 복사될 디렉토리. CLI 의 add 가 이 아래 <name>/index.tsx 등을 작성.",
80
+ "examples": ["src/components/ui", "src/shared/ui", "lib/sh_ui/widgets"]
81
+ },
82
+ "utils": {
83
+ "type": "string",
84
+ "description": "cn 유틸 등을 둘 utils 파일 경로 (React)."
85
+ },
86
+ "hooks": {
87
+ "type": "string",
88
+ "description": "hooks 디렉토리 (선택)."
89
+ },
90
+ "foundation": {
91
+ "type": "string",
92
+ "description": "Flutter foundation 디렉토리 (tokens.dart 등이 있는 곳)."
93
+ },
94
+ "widgets": {
95
+ "type": "string",
96
+ "description": "Flutter widgets 디렉토리."
97
+ }
98
+ }
99
+ },
100
+ "aliases": {
101
+ "type": "object",
102
+ "description": "TS module alias — 컴포넌트가 cn 유틸 등을 import 할 때 치환. tsconfig.json 의 paths 와 정합되어야 함.",
103
+ "additionalProperties": true,
104
+ "properties": {
105
+ "components": {
106
+ "type": "string",
107
+ "examples": ["@/components/ui", "@workspace/ui-core/components"]
108
+ },
109
+ "utils": {
110
+ "type": "string",
111
+ "examples": ["@/lib/utils", "@workspace/ui-core/lib/utils"]
112
+ },
113
+ "ui": {
114
+ "type": "string"
115
+ },
116
+ "hooks": {
117
+ "type": "string"
118
+ }
119
+ }
120
+ }
121
+ },
122
+ "allOf": [
123
+ {
124
+ "if": {
125
+ "properties": { "cssStrategy": { "const": "bundled" } },
126
+ "required": ["cssStrategy"]
127
+ },
128
+ "then": {
129
+ "properties": {
130
+ "paths": { "required": ["cssBundle"] }
131
+ }
132
+ }
133
+ }
134
+ ]
135
+ }
package/src/init.mjs CHANGED
@@ -152,7 +152,7 @@ function labelFor(key) {
152
152
 
153
153
  function buildConfig({ platform, base, radius, mode, cssFramework }) {
154
154
  return {
155
- $schema: "https://your-ds.dev/sh-ui.schema.json",
155
+ $schema: "https://raw.githubusercontent.com/sanghyeonKim0201/sh-ui/live/packages/cli/sh-ui.schema.json",
156
156
  platform,
157
157
  cssFramework,
158
158
  theme: { base, radius, mode },
@@ -0,0 +1,180 @@
1
+ // `sh-ui upgrade-cli` — sh-ui-cli 자체 업그레이드 + 설치 후 자동 진단.
2
+ //
3
+ // 동작:
4
+ // 1) npm registry 에서 sh-ui-cli 의 latest 버전 조회.
5
+ // 2) 현재 실행 중인 CLI 의 package.json 에서 버전 읽음.
6
+ // 3) 동일하면 "이미 최신" 안내 + 설치된 sh-ui-cli 패키지 (사용자의 node_modules)
7
+ // 가 다른지 확인 — devDep 갱신 안내.
8
+ // 4) 다르면 install 명령 출력 (사용자의 패키지 매니저에 맞춰).
9
+ // 5) --apply 면 실제 install 실행.
10
+ // 6) install 후 next-step 안내 — sh-ui doctor / tokens diff.
11
+
12
+ import { readFile } from "node:fs/promises";
13
+ import { existsSync } from "node:fs";
14
+ import { dirname, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import { spawn } from "node:child_process";
17
+
18
+ const CLI_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
19
+ const REGISTRY_URL = "https://registry.npmjs.org/sh-ui-cli/latest";
20
+
21
+ async function readCliVersion() {
22
+ const pkg = JSON.parse(
23
+ await readFile(resolve(CLI_ROOT, "package.json"), "utf8"),
24
+ );
25
+ return pkg.version;
26
+ }
27
+
28
+ /** 사용자 프로젝트의 node_modules/sh-ui-cli/package.json 버전 (devDep 으로 설치된 것) */
29
+ async function readInstalledVersion(cwd) {
30
+ let dir = resolve(cwd);
31
+ while (true) {
32
+ const candidate = resolve(dir, "node_modules/sh-ui-cli/package.json");
33
+ if (existsSync(candidate)) {
34
+ try {
35
+ const pkg = JSON.parse(await readFile(candidate, "utf8"));
36
+ return { version: pkg.version, path: candidate };
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+ const parent = dirname(dir);
42
+ if (parent === dir) return null;
43
+ dir = parent;
44
+ }
45
+ }
46
+
47
+ async function fetchLatestVersion() {
48
+ // node 18+ 는 fetch 기본. 의존성 없는 단순 GET.
49
+ try {
50
+ const res = await fetch(REGISTRY_URL, {
51
+ headers: { accept: "application/json" },
52
+ });
53
+ if (!res.ok) {
54
+ throw new Error(`HTTP ${res.status}`);
55
+ }
56
+ const data = await res.json();
57
+ return data.version;
58
+ } catch (err) {
59
+ throw new Error(
60
+ `npm registry 에서 latest 버전을 가져오지 못했습니다 (${err.message}). ` +
61
+ "오프라인이거나 registry 접근이 막혔을 수 있습니다.",
62
+ );
63
+ }
64
+ }
65
+
66
+ function detectPackageManager(cwd) {
67
+ let dir = resolve(cwd);
68
+ while (true) {
69
+ if (existsSync(resolve(dir, "pnpm-lock.yaml"))) return "pnpm";
70
+ if (existsSync(resolve(dir, "pnpm-workspace.yaml"))) return "pnpm";
71
+ if (
72
+ existsSync(resolve(dir, "bun.lockb")) ||
73
+ existsSync(resolve(dir, "bun.lock"))
74
+ )
75
+ return "bun";
76
+ if (existsSync(resolve(dir, "yarn.lock"))) return "yarn";
77
+ if (existsSync(resolve(dir, "package-lock.json"))) return "npm";
78
+ const parent = dirname(dir);
79
+ if (parent === dir) return "npm";
80
+ dir = parent;
81
+ }
82
+ }
83
+
84
+ function installCommand(pm, version) {
85
+ const addCmd = pm === "npm" ? "install -D" : "add -D";
86
+ return `${pm} ${addCmd} sh-ui-cli@${version}`;
87
+ }
88
+
89
+ function runInstall(pm, version, cwd) {
90
+ const args = pm === "npm" ? ["install", "-D"] : ["add", "-D"];
91
+ args.push(`sh-ui-cli@${version}`);
92
+ console.log(`\n실행: ${pm} ${args.join(" ")}\n`);
93
+ const isWin = process.platform === "win32";
94
+ return new Promise((ok, bad) => {
95
+ const child = spawn(pm, args, { cwd, stdio: "inherit", shell: isWin });
96
+ child.on("exit", (code) =>
97
+ code === 0 ? ok() : bad(new Error(`${pm} exited with code ${code}`)),
98
+ );
99
+ child.on("error", bad);
100
+ });
101
+ }
102
+
103
+ /**
104
+ * versions.json 에서 from..to 사이의 highlights 를 추출.
105
+ * from = 사용자 현재, to = latest. 없으면 최근 N개만.
106
+ */
107
+ async function readChangelogRange(fromVersion, toVersion) {
108
+ const candidates = [
109
+ resolve(CLI_ROOT, "data/changelog/versions.json"), // bundled
110
+ resolve(CLI_ROOT, "../changelog/versions.json"), // monorepo dev
111
+ ];
112
+ let path;
113
+ for (const p of candidates) {
114
+ if (existsSync(p)) {
115
+ path = p;
116
+ break;
117
+ }
118
+ }
119
+ if (!path) return null;
120
+ const data = JSON.parse(await readFile(path, "utf8"));
121
+ const versions = data.versions ?? [];
122
+ if (!fromVersion || !toVersion) return versions.slice(0, 5);
123
+ // versions 는 최신 → 옛날 순. from 보다 새로운 것만 추출 (from 자기 자신은 제외).
124
+ const out = [];
125
+ for (const v of versions) {
126
+ if (v.version === fromVersion) break;
127
+ out.push(v);
128
+ }
129
+ return out;
130
+ }
131
+
132
+ export async function runUpgradeCli({ cwd, apply }) {
133
+ const installed = await readInstalledVersion(cwd);
134
+ console.log(`\nsh-ui-cli 업그레이드 점검\n`);
135
+
136
+ const latest = await fetchLatestVersion();
137
+ console.log(` npm latest : v${latest}`);
138
+ if (installed) {
139
+ console.log(` 현재 설치본 : v${installed.version}`);
140
+ } else {
141
+ console.log(` 현재 설치본 : (찾지 못함 — npx 로 실행 중일 수 있음)`);
142
+ }
143
+
144
+ const reference = installed?.version ?? (await readCliVersion());
145
+ if (reference === latest) {
146
+ console.log(`\n✓ 이미 최신.\n`);
147
+ console.log(`다음 단계 권장:\n sh-ui doctor — 프로젝트 정합성 점검`);
148
+ return;
149
+ }
150
+
151
+ const changelog = await readChangelogRange(reference, latest);
152
+ if (changelog && changelog.length > 0) {
153
+ console.log(`\n변경 highlights (${changelog.length}개 릴리즈):`);
154
+ for (const v of changelog) {
155
+ const type = v.type ? ` [${v.type}]` : "";
156
+ console.log(` v${v.version}${type} — ${v.title}`);
157
+ }
158
+ }
159
+
160
+ const pm = detectPackageManager(cwd);
161
+ const cmd = installCommand(pm, latest);
162
+
163
+ if (!apply) {
164
+ console.log(`\n설치 명령:\n ${cmd}\n`);
165
+ console.log(
166
+ `실제 실행하려면: \`sh-ui upgrade-cli --apply\` (자동 install + 설치 후 doctor)`,
167
+ );
168
+ return;
169
+ }
170
+
171
+ // --apply
172
+ await runInstall(pm, latest, cwd);
173
+ console.log(`\n✓ sh-ui-cli@${latest} 설치 완료.\n`);
174
+ console.log(
175
+ `다음 단계 (필요 시):\n` +
176
+ ` sh-ui doctor — 신규 토큰 누락 / config 이슈 진단\n` +
177
+ ` sh-ui tokens diff — buildTokens 와 비교 미리보기\n` +
178
+ ` sh-ui tokens upgrade --apply — 추가만 incremental 적용 (사용자 편집 보존)`,
179
+ );
180
+ }
@@ -1,4 +1,5 @@
1
1
  {
2
+ "$schema": "https://raw.githubusercontent.com/sanghyeonKim0201/sh-ui/live/packages/cli/sh-ui.schema.json",
2
3
  "platform": "flutter",
3
4
  "theme": {
4
5
  "base": "neutral",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "$schema": "https://raw.githubusercontent.com/sanghyeonKim0201/sh-ui/live/packages/cli/sh-ui.schema.json",
2
3
  "platform": "react",
3
4
  "cssFramework": "plain",
4
5
  "paths": {
@@ -1,4 +1,5 @@
1
1
  {
2
+ "$schema": "https://raw.githubusercontent.com/sanghyeonKim0201/sh-ui/live/packages/cli/sh-ui.schema.json",
2
3
  "platform": "react",
3
4
  "cssFramework": "plain",
4
5
  "theme": {
@@ -1,4 +1,5 @@
1
1
  {
2
+ "$schema": "https://raw.githubusercontent.com/sanghyeonKim0201/sh-ui/live/packages/cli/sh-ui.schema.json",
2
3
  "platform": "react",
3
4
  "cssFramework": "plain",
4
5
  "theme": {
@@ -1,4 +1,5 @@
1
1
  {
2
+ "$schema": "https://raw.githubusercontent.com/sanghyeonKim0201/sh-ui/live/packages/cli/sh-ui.schema.json",
2
3
  "platform": "react",
3
4
  "cssFramework": "plain",
4
5
  "role": "tokens-only",