sh-ui-cli 0.71.1 → 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 +41 -1
- package/data/changelog/versions.json +12 -0
- package/package.json +1 -1
- package/src/doctor.mjs +23 -8
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
|
-
|
|
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,18 @@
|
|
|
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
|
+
},
|
|
5
17
|
{
|
|
6
18
|
"version": "0.71.1",
|
|
7
19
|
"date": "2026-05-09",
|
package/package.json
CHANGED
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}
|
|
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 처럼
|
|
97
|
-
|
|
98
|
+
// 모노레포 ui-core 처럼 토큰을 가지지 않는 컴포넌트-only 패키지 — 이 케이스는
|
|
99
|
+
// 정상이라 안내 한 줄만 ok 로 표시 (warn 으로 노이즈 만들지 않음).
|
|
100
|
+
report.ok(
|
|
98
101
|
"paths.tokens",
|
|
99
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
330
|
+
return { ok: report.failCount === 0, failCount: report.failCount, warnCount: report.warnCount };
|
|
316
331
|
}
|