sh-ui-cli 0.71.2 → 0.72.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 +19 -0
- package/data/changelog/versions.json +14 -0
- package/package.json +1 -1
- package/src/migrate-bundled.mjs +172 -0
package/bin/sh-ui.mjs
CHANGED
|
@@ -29,6 +29,9 @@ const usage = `사용법:
|
|
|
29
29
|
디렉토리 + 모든 import/path 패턴)
|
|
30
30
|
sh-ui migrate-v065 v0.64.x → v0.65 자동 마이그레이션
|
|
31
31
|
(--dry-run 기본, --apply 로 실제 적용)
|
|
32
|
+
sh-ui migrate bundled cssStrategy=bundled 로 전환 (per-component
|
|
33
|
+
styles.css → 단일 sh-ui-components.css)
|
|
34
|
+
(--apply 로 실제 적용)
|
|
32
35
|
sh-ui mcp MCP 서버(stdio) 시작 — IDE-내 AI용
|
|
33
36
|
sh-ui mcp init --client <name> IDE MCP 설정 파일에 sh-ui 엔트리 자동 추가
|
|
34
37
|
(claude-code | cursor | claude-desktop)
|
|
@@ -224,6 +227,22 @@ try {
|
|
|
224
227
|
await renameApp({ cwd: process.cwd(), oldName, newName, yes, dryRun, skipInstall });
|
|
225
228
|
break;
|
|
226
229
|
}
|
|
230
|
+
case "migrate": {
|
|
231
|
+
// sh-ui migrate bundled [--apply] [--bundle <path>]
|
|
232
|
+
const sub = rest[0];
|
|
233
|
+
const flags = rest.slice(1);
|
|
234
|
+
if (sub === "bundled") {
|
|
235
|
+
const apply = flags.includes("--apply");
|
|
236
|
+
const bIdx = flags.indexOf("--bundle");
|
|
237
|
+
const bundleArg = bIdx !== -1 ? flags[bIdx + 1] : null;
|
|
238
|
+
const { runMigrateBundled } = await import("../src/migrate-bundled.mjs");
|
|
239
|
+
await runMigrateBundled({ cwd: process.cwd(), apply, bundleArg });
|
|
240
|
+
} else {
|
|
241
|
+
console.error(`에러: 알 수 없는 migrate 서브명령 '${sub ?? ""}'. 'bundled' 만 지원.\n`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
227
246
|
case "migrate-v065": {
|
|
228
247
|
const apply = rest.includes("--apply");
|
|
229
248
|
const skipImportRewrite = rest.includes("--skip-import-rewrite");
|
|
@@ -2,6 +2,20 @@
|
|
|
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.72.0",
|
|
7
|
+
"date": "2026-05-10",
|
|
8
|
+
"title": "sh-ui migrate bundled — per-component → bundled 자동 전환 (Phase D 후속)",
|
|
9
|
+
"type": "minor",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**`sh-ui migrate bundled` 명령** — 기존 per-component 프로젝트를 bundled 로 한 번에 전환. paths.components 아래 모든 styles.css 를 bundle 의 마커 섹션으로 누적 + 원본 파일 삭제 + .tsx import 제거 + config 갱신.",
|
|
12
|
+
"**dry-run 기본** — 변경 매트릭스 (대상 컴포넌트 + 파일 목록) 출력 후 사용자 확인. `--apply` 로만 실제 변경.",
|
|
13
|
+
"**`--bundle <path>` 옵션** — bundle 파일 위치 명시. 미지정 시 paths.styles 또는 paths.tokens 디렉토리 + sh-ui-components.css 를 자동 사용.",
|
|
14
|
+
"**완료 후 안내** — globals.css 에 `@import './sh-ui-components.css';` 한 줄 추가하라는 메시지 (자동 편집 안 함 — 사용자 entry CSS 위치/스타일 다양해서).",
|
|
15
|
+
"**제약** — cssFramework: plain + React 만. tailwind/css-modules/vanilla-extract 면 친절 에러. 이미 bundled 면 noop."
|
|
16
|
+
],
|
|
17
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.72.0"
|
|
18
|
+
},
|
|
5
19
|
{
|
|
6
20
|
"version": "0.71.2",
|
|
7
21
|
"date": "2026-05-09",
|
package/package.json
CHANGED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// `sh-ui migrate bundled` — 기존 per-component 프로젝트를 cssStrategy: bundled 로 전환.
|
|
2
|
+
//
|
|
3
|
+
// 동작:
|
|
4
|
+
// 1) sh-ui.config.json 의 cssStrategy 가 미설정/per-component 인지 확인.
|
|
5
|
+
// 2) 사용자에게 paths.cssBundle 위치 안내 (기본값 자동 결정 — paths.styles 또는
|
|
6
|
+
// paths.tokens 의 디렉토리 + `sh-ui-components.css`).
|
|
7
|
+
// 3) paths.components 아래 모든 `<name>/styles.css` 를 읽어 bundle 에 섹션으로 누적.
|
|
8
|
+
// 4) 각 컴포넌트 .tsx 의 `import "./styles.css";` 라인 제거.
|
|
9
|
+
// 5) per-component styles.css 파일 삭제.
|
|
10
|
+
// 6) sh-ui.config.json 에 `cssStrategy: "bundled"` + `paths.cssBundle` 저장.
|
|
11
|
+
//
|
|
12
|
+
// dry-run 기본 — 실제 변경은 --apply 로만. 실행 전 사용자에게 변경 매트릭스 안내.
|
|
13
|
+
|
|
14
|
+
import { readFile, writeFile, readdir, rm, mkdir } from "node:fs/promises";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { resolve, relative, dirname, join } from "node:path";
|
|
17
|
+
import { upsertSection, stripStylesImport } from "./css-bundle.mjs";
|
|
18
|
+
|
|
19
|
+
async function loadConfig(cwd) {
|
|
20
|
+
const configPath = resolve(cwd, "sh-ui.config.json");
|
|
21
|
+
if (!existsSync(configPath)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"sh-ui.config.json 을 찾을 수 없습니다. 먼저 `sh-ui init` 또는 `sh-ui create`.",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return { configPath, config: JSON.parse(await readFile(configPath, "utf8")) };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* paths.cssBundle 기본값 — 기존 paths.styles 또는 paths.tokens 의 부모 디렉토리에
|
|
31
|
+
* `sh-ui-components.css` 를 둔다.
|
|
32
|
+
*/
|
|
33
|
+
function defaultBundlePath(config) {
|
|
34
|
+
const stylesDir = config.paths?.styles
|
|
35
|
+
?? (config.paths?.tokens && dirname(config.paths.tokens));
|
|
36
|
+
if (!stylesDir) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"paths.styles 또는 paths.tokens 를 설정에서 찾을 수 없어 cssBundle 기본 경로를 정할 수 없습니다.\n" +
|
|
39
|
+
" --bundle <path> 로 명시하세요 (예: --bundle src/styles/sh-ui-components.css).",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return join(stylesDir, "sh-ui-components.css");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* paths.components 아래 컴포넌트 폴더 (직속 디렉토리 한 단계만).
|
|
47
|
+
*/
|
|
48
|
+
async function listComponentDirs(componentsAbs) {
|
|
49
|
+
if (!existsSync(componentsAbs)) return [];
|
|
50
|
+
const entries = await readdir(componentsAbs, { withFileTypes: true });
|
|
51
|
+
return entries
|
|
52
|
+
.filter((e) => e.isDirectory())
|
|
53
|
+
.map((e) => ({ name: e.name, dir: resolve(componentsAbs, e.name) }));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 컴포넌트 디렉토리에서 styles.css / styles.module.css 를 찾아 (있으면) 반환.
|
|
58
|
+
* .tsx 변종은 별도 — `migrateOne` 이 처리.
|
|
59
|
+
*/
|
|
60
|
+
function findStyleFiles(componentDir) {
|
|
61
|
+
return [
|
|
62
|
+
resolve(componentDir, "styles.css"),
|
|
63
|
+
resolve(componentDir, "styles.module.css"),
|
|
64
|
+
].filter((p) => existsSync(p));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function findTsxFiles(componentDir) {
|
|
68
|
+
return [
|
|
69
|
+
resolve(componentDir, "index.tsx"),
|
|
70
|
+
resolve(componentDir, "index.module.tsx"),
|
|
71
|
+
].filter((p) => existsSync(p));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function runMigrateBundled({ cwd, apply, bundleArg }) {
|
|
75
|
+
const { configPath, config } = await loadConfig(cwd);
|
|
76
|
+
|
|
77
|
+
if (config.cssStrategy === "bundled") {
|
|
78
|
+
console.log("이미 cssStrategy='bundled' 로 설정돼 있습니다 — 마이그레이션 불필요.");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if ((config.cssFramework ?? "plain") !== "plain") {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`cssFramework='${config.cssFramework}' 에선 bundled 모드가 동작하지 않습니다 (plain 만 지원).`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (config.platform !== "react") {
|
|
89
|
+
throw new Error("bundled 마이그레이션은 React 만 지원합니다.");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const componentsRel = config.paths?.components;
|
|
93
|
+
if (!componentsRel) {
|
|
94
|
+
throw new Error("paths.components 가 sh-ui.config.json 에 없습니다.");
|
|
95
|
+
}
|
|
96
|
+
const componentsAbs = resolve(cwd, componentsRel);
|
|
97
|
+
const components = await listComponentDirs(componentsAbs);
|
|
98
|
+
if (components.length === 0) {
|
|
99
|
+
console.log(`${componentsRel} 아래에 컴포넌트가 없습니다. 마이그레이션할 게 없음.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const bundleRel = bundleArg ?? defaultBundlePath(config);
|
|
103
|
+
const bundleAbs = resolve(cwd, bundleRel);
|
|
104
|
+
|
|
105
|
+
// 변경 매트릭스 계산
|
|
106
|
+
const plan = []; // { name, styleFiles: [], tsxFiles: [] }
|
|
107
|
+
for (const c of components) {
|
|
108
|
+
const styleFiles = findStyleFiles(c.dir);
|
|
109
|
+
const tsxFiles = findTsxFiles(c.dir);
|
|
110
|
+
if (styleFiles.length === 0 && tsxFiles.length === 0) continue;
|
|
111
|
+
plan.push({ name: c.name, dir: c.dir, styleFiles, tsxFiles });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log(`\n── bundled 마이그레이션 ${apply ? "(실행)" : "(dry-run)"} ──`);
|
|
115
|
+
console.log(` bundle 파일: ${bundleRel}`);
|
|
116
|
+
console.log(` 대상 컴포넌트: ${plan.length}개`);
|
|
117
|
+
for (const p of plan) {
|
|
118
|
+
const styleNames = p.styleFiles.map((f) => relative(cwd, f));
|
|
119
|
+
const tsxNames = p.tsxFiles.map((f) => relative(cwd, f));
|
|
120
|
+
console.log(` · ${p.name}`);
|
|
121
|
+
for (const f of styleNames) console.log(` styles → bundle 섹션 + 파일 삭제: ${f}`);
|
|
122
|
+
for (const f of tsxNames) console.log(` .tsx import 제거: ${f}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!apply) {
|
|
126
|
+
console.log(`\n실제 적용은 \`sh-ui migrate bundled --apply\`.`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 적용 시작 — 번들 텍스트를 먼저 빌드, 마지막에 한 번에 쓴다.
|
|
131
|
+
let bundleText = existsSync(bundleAbs)
|
|
132
|
+
? await readFile(bundleAbs, "utf8")
|
|
133
|
+
: `/* sh-ui — 컴포넌트 CSS 번들 (cssStrategy: bundled). 마커 사이는 sh-ui 가 관리, 그 밖은 사용자 자유. */\n\n`;
|
|
134
|
+
|
|
135
|
+
for (const p of plan) {
|
|
136
|
+
let cssAccum = "";
|
|
137
|
+
for (const f of p.styleFiles) {
|
|
138
|
+
const text = await readFile(f, "utf8");
|
|
139
|
+
cssAccum += (cssAccum ? "\n\n" : "") + text.trim();
|
|
140
|
+
}
|
|
141
|
+
if (cssAccum) {
|
|
142
|
+
bundleText = upsertSection(bundleText, p.name, cssAccum);
|
|
143
|
+
}
|
|
144
|
+
for (const f of p.tsxFiles) {
|
|
145
|
+
const before = await readFile(f, "utf8");
|
|
146
|
+
const after = stripStylesImport(before);
|
|
147
|
+
if (before !== after) await writeFile(f, after, "utf8");
|
|
148
|
+
}
|
|
149
|
+
for (const f of p.styleFiles) {
|
|
150
|
+
await rm(f);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await mkdir(dirname(bundleAbs), { recursive: true });
|
|
155
|
+
await writeFile(bundleAbs, bundleText, "utf8");
|
|
156
|
+
|
|
157
|
+
// config 갱신
|
|
158
|
+
const nextConfig = {
|
|
159
|
+
...config,
|
|
160
|
+
cssStrategy: "bundled",
|
|
161
|
+
paths: { ...config.paths, cssBundle: bundleRel },
|
|
162
|
+
};
|
|
163
|
+
await writeFile(configPath, JSON.stringify(nextConfig, null, 2) + "\n", "utf8");
|
|
164
|
+
|
|
165
|
+
console.log(
|
|
166
|
+
`\n✓ 마이그레이션 완료.\n` +
|
|
167
|
+
` bundle: ${bundleRel}\n` +
|
|
168
|
+
` config: cssStrategy='bundled', paths.cssBundle 추가\n\n` +
|
|
169
|
+
`다음 단계: globals.css (또는 entry CSS) 에서 한 번 import 하세요:\n` +
|
|
170
|
+
` @import './${bundleRel.replace(/^.*\//, "")}';`,
|
|
171
|
+
);
|
|
172
|
+
}
|