sh-ui-cli 0.71.1 → 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 +60 -1
- package/data/changelog/versions.json +26 -0
- package/package.json +1 -1
- package/src/doctor.mjs +23 -8
- 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)
|
|
@@ -109,7 +112,47 @@ try {
|
|
|
109
112
|
}
|
|
110
113
|
case "doctor": {
|
|
111
114
|
const { doctor } = await import("../src/doctor.mjs");
|
|
112
|
-
|
|
115
|
+
const { existsSync, readdirSync } = await import("node:fs");
|
|
116
|
+
const { resolve } = await import("node:path");
|
|
117
|
+
// add 와 같은 walk-up + monorepo 라우팅. standalone 이면 그 root 1개,
|
|
118
|
+
// monorepo 면 packages/ui/ui-core + ui-apps/ui-* 모두 순회.
|
|
119
|
+
const ctx = findShUiContext(process.cwd());
|
|
120
|
+
if (!ctx) {
|
|
121
|
+
console.error(
|
|
122
|
+
"✗ sh-ui.config.json 또는 pnpm-workspace.yaml 을 cwd 부터 부모 트리에서 찾지 못했습니다.",
|
|
123
|
+
);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
let anyFailed = false;
|
|
127
|
+
const targets = [];
|
|
128
|
+
if (ctx.kind === "config") {
|
|
129
|
+
targets.push(ctx.root);
|
|
130
|
+
} else {
|
|
131
|
+
const uiCore = resolve(ctx.root, "packages/ui/ui-core");
|
|
132
|
+
if (existsSync(resolve(uiCore, "sh-ui.config.json"))) targets.push(uiCore);
|
|
133
|
+
const uiAppsRoot = resolve(ctx.root, "packages/ui/ui-apps");
|
|
134
|
+
if (existsSync(uiAppsRoot)) {
|
|
135
|
+
for (const name of readdirSync(uiAppsRoot)) {
|
|
136
|
+
const dir = resolve(uiAppsRoot, name);
|
|
137
|
+
if (existsSync(resolve(dir, "sh-ui.config.json"))) targets.push(dir);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (targets.length === 0) {
|
|
141
|
+
console.error(
|
|
142
|
+
"✗ monorepo 에서 sh-ui.config.json 가지는 패키지를 찾지 못했습니다.\n" +
|
|
143
|
+
" packages/ui/ui-core/ 또는 packages/ui/ui-apps/ui-*/ 에 있어야 합니다.",
|
|
144
|
+
);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
for (const target of targets) {
|
|
149
|
+
if (targets.length > 1) {
|
|
150
|
+
console.log(`\n──── ${target.replace(ctx.root + "/", "")} ────`);
|
|
151
|
+
}
|
|
152
|
+
const result = await doctor({ cwd: target });
|
|
153
|
+
if (!result.ok) anyFailed = true;
|
|
154
|
+
}
|
|
155
|
+
if (anyFailed) process.exit(1);
|
|
113
156
|
break;
|
|
114
157
|
}
|
|
115
158
|
case "theme": {
|
|
@@ -184,6 +227,22 @@ try {
|
|
|
184
227
|
await renameApp({ cwd: process.cwd(), oldName, newName, yes, dryRun, skipInstall });
|
|
185
228
|
break;
|
|
186
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
|
+
}
|
|
187
246
|
case "migrate-v065": {
|
|
188
247
|
const apply = rest.includes("--apply");
|
|
189
248
|
const skipImportRewrite = rest.includes("--skip-import-rewrite");
|
|
@@ -2,6 +2,32 @@
|
|
|
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
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"version": "0.71.2",
|
|
21
|
+
"date": "2026-05-09",
|
|
22
|
+
"title": "fix(doctor) — monorepo walk-up + 컴포넌트-only 패키지 인식",
|
|
23
|
+
"type": "patch",
|
|
24
|
+
"highlights": [
|
|
25
|
+
"**doctor 의 walk-up + monorepo 라우팅** — `add` 와 같은 정책을 적용. monorepo 어느 디렉토리 (apps/web, ui-core 안 등) 에서 실행해도 `packages/ui/ui-core` 와 모든 `packages/ui/ui-apps/ui-*` 를 자동 순회. 각 패키지마다 헤더 출력 + 결과 집계, 하나라도 fail 이면 exit 1.",
|
|
26
|
+
"**컴포넌트-only 패키지 인식** — ui-core 처럼 `paths.tokens` 가 없는 패키지는 의도된 형태. 이전엔 'theme 미설정 / 토큰 검증 스킵' 로 fail/warn 폭발 → 이제 ✓ '(컴포넌트 only — 토큰 없음, 검증 스킵)' 한 줄로 정리.",
|
|
27
|
+
"**doctor 함수 시그니처** — `process.exit()` 직접 호출 대신 `{ ok, failCount, warnCount }` 반환. 호출부 (bin) 가 monorepo 순회 후 집계 exit. API consumer (테스트, 향후 lint:doctor 등) 도 활용 가능."
|
|
28
|
+
],
|
|
29
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.71.2"
|
|
30
|
+
},
|
|
5
31
|
{
|
|
6
32
|
"version": "0.71.1",
|
|
7
33
|
"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
|
}
|
|
@@ -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
|
+
}
|