@mandujs/core 0.13.0 → 0.13.1

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.
Files changed (155) hide show
  1. package/README.ko.md +4 -4
  2. package/README.md +653 -653
  3. package/package.json +1 -1
  4. package/src/bundler/build.ts +91 -91
  5. package/src/bundler/css.ts +302 -302
  6. package/src/client/Link.tsx +227 -227
  7. package/src/client/globals.ts +44 -44
  8. package/src/client/hooks.ts +267 -267
  9. package/src/client/index.ts +5 -5
  10. package/src/client/island.ts +8 -8
  11. package/src/client/router.ts +435 -435
  12. package/src/client/runtime.ts +23 -23
  13. package/src/client/serialize.ts +404 -404
  14. package/src/client/window-state.ts +101 -101
  15. package/src/config/mandu.ts +9 -0
  16. package/src/config/validate.ts +12 -0
  17. package/src/config/watcher.ts +311 -311
  18. package/src/constants.ts +40 -40
  19. package/src/content/content-layer.ts +314 -314
  20. package/src/content/content.test.ts +433 -433
  21. package/src/content/data-store.ts +245 -245
  22. package/src/content/digest.ts +133 -133
  23. package/src/content/index.ts +164 -164
  24. package/src/content/loader-context.ts +172 -172
  25. package/src/content/loaders/api.ts +216 -216
  26. package/src/content/loaders/file.ts +169 -169
  27. package/src/content/loaders/glob.ts +252 -252
  28. package/src/content/loaders/index.ts +34 -34
  29. package/src/content/loaders/types.ts +137 -137
  30. package/src/content/meta-store.ts +209 -209
  31. package/src/content/types.ts +282 -282
  32. package/src/content/watcher.ts +135 -135
  33. package/src/contract/client-safe.test.ts +42 -42
  34. package/src/contract/client-safe.ts +114 -114
  35. package/src/contract/client.ts +16 -16
  36. package/src/contract/define.ts +459 -459
  37. package/src/contract/handler.ts +10 -10
  38. package/src/contract/normalize.test.ts +276 -276
  39. package/src/contract/normalize.ts +404 -404
  40. package/src/contract/registry.test.ts +206 -206
  41. package/src/contract/registry.ts +568 -568
  42. package/src/contract/schema.ts +48 -48
  43. package/src/contract/types.ts +58 -58
  44. package/src/contract/validator.ts +32 -32
  45. package/src/devtools/ai/context-builder.ts +375 -375
  46. package/src/devtools/ai/index.ts +25 -25
  47. package/src/devtools/ai/mcp-connector.ts +465 -465
  48. package/src/devtools/client/catchers/error-catcher.ts +327 -327
  49. package/src/devtools/client/catchers/index.ts +18 -18
  50. package/src/devtools/client/catchers/network-proxy.ts +363 -363
  51. package/src/devtools/client/components/index.ts +39 -39
  52. package/src/devtools/client/components/kitchen-root.tsx +362 -362
  53. package/src/devtools/client/components/mandu-character.tsx +241 -241
  54. package/src/devtools/client/components/overlay.tsx +368 -368
  55. package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
  56. package/src/devtools/client/components/panel/guard-panel.tsx +244 -244
  57. package/src/devtools/client/components/panel/index.ts +32 -32
  58. package/src/devtools/client/components/panel/islands-panel.tsx +304 -304
  59. package/src/devtools/client/components/panel/network-panel.tsx +292 -292
  60. package/src/devtools/client/components/panel/panel-container.tsx +259 -259
  61. package/src/devtools/client/filters/context-filters.ts +282 -282
  62. package/src/devtools/client/filters/index.ts +16 -16
  63. package/src/devtools/client/index.ts +63 -63
  64. package/src/devtools/client/persistence.ts +335 -335
  65. package/src/devtools/client/state-manager.ts +478 -478
  66. package/src/devtools/design-tokens.ts +263 -263
  67. package/src/devtools/hook/create-hook.ts +207 -207
  68. package/src/devtools/hook/index.ts +13 -13
  69. package/src/devtools/index.ts +439 -439
  70. package/src/devtools/init.ts +266 -266
  71. package/src/devtools/protocol.ts +237 -237
  72. package/src/devtools/server/index.ts +17 -17
  73. package/src/devtools/server/source-context.ts +444 -444
  74. package/src/devtools/types.ts +319 -319
  75. package/src/devtools/worker/index.ts +25 -25
  76. package/src/devtools/worker/redaction-worker.ts +222 -222
  77. package/src/devtools/worker/worker-manager.ts +409 -409
  78. package/src/error/domains.ts +265 -265
  79. package/src/error/result.ts +46 -46
  80. package/src/error/types.ts +6 -6
  81. package/src/errors/extractor.ts +409 -409
  82. package/src/errors/index.ts +19 -19
  83. package/src/filling/auth.ts +308 -308
  84. package/src/filling/context.ts +24 -1
  85. package/src/filling/deps.ts +238 -238
  86. package/src/filling/index.ts +2 -0
  87. package/src/filling/sse.test.ts +168 -0
  88. package/src/filling/sse.ts +162 -0
  89. package/src/generator/index.ts +3 -3
  90. package/src/guard/analyzer.ts +360 -360
  91. package/src/guard/ast-analyzer.ts +806 -806
  92. package/src/guard/contract-guard.ts +9 -9
  93. package/src/guard/file-type.test.ts +24 -24
  94. package/src/guard/presets/atomic.ts +70 -70
  95. package/src/guard/presets/clean.ts +77 -77
  96. package/src/guard/presets/fsd.ts +79 -79
  97. package/src/guard/presets/hexagonal.ts +68 -68
  98. package/src/guard/presets/index.ts +291 -291
  99. package/src/guard/reporter.ts +445 -445
  100. package/src/guard/rules.ts +12 -12
  101. package/src/guard/statistics.ts +578 -578
  102. package/src/guard/suggestions.ts +358 -358
  103. package/src/guard/types.ts +348 -348
  104. package/src/guard/validator.ts +834 -834
  105. package/src/guard/watcher.ts +404 -404
  106. package/src/index.ts +6 -1
  107. package/src/intent/index.ts +310 -310
  108. package/src/island/index.ts +304 -304
  109. package/src/logging/index.ts +22 -22
  110. package/src/logging/transports.ts +365 -365
  111. package/src/plugins/index.ts +38 -38
  112. package/src/plugins/registry.ts +377 -377
  113. package/src/plugins/types.ts +363 -363
  114. package/src/report/index.ts +1 -1
  115. package/src/router/fs-patterns.ts +387 -387
  116. package/src/router/fs-scanner.ts +497 -497
  117. package/src/runtime/boundary.tsx +232 -232
  118. package/src/runtime/compose.ts +222 -222
  119. package/src/runtime/escape.ts +44 -0
  120. package/src/runtime/lifecycle.ts +381 -381
  121. package/src/runtime/logger.test.ts +345 -345
  122. package/src/runtime/logger.ts +677 -677
  123. package/src/runtime/router.test.ts +476 -476
  124. package/src/runtime/router.ts +105 -105
  125. package/src/runtime/security.ts +155 -155
  126. package/src/runtime/server.ts +257 -0
  127. package/src/runtime/session-key.ts +328 -328
  128. package/src/runtime/ssr.ts +16 -21
  129. package/src/runtime/streaming-ssr.ts +24 -33
  130. package/src/runtime/trace.ts +144 -144
  131. package/src/seo/index.ts +214 -214
  132. package/src/seo/integration/ssr.ts +307 -307
  133. package/src/seo/render/basic.ts +427 -427
  134. package/src/seo/render/index.ts +143 -143
  135. package/src/seo/render/jsonld.ts +539 -539
  136. package/src/seo/render/opengraph.ts +191 -191
  137. package/src/seo/render/robots.ts +116 -116
  138. package/src/seo/render/sitemap.ts +137 -137
  139. package/src/seo/render/twitter.ts +126 -126
  140. package/src/seo/resolve/index.ts +353 -353
  141. package/src/seo/resolve/opengraph.ts +143 -143
  142. package/src/seo/resolve/robots.ts +73 -73
  143. package/src/seo/resolve/title.ts +94 -94
  144. package/src/seo/resolve/twitter.ts +73 -73
  145. package/src/seo/resolve/url.ts +97 -97
  146. package/src/seo/routes/index.ts +290 -290
  147. package/src/seo/types.ts +575 -575
  148. package/src/slot/validator.ts +39 -39
  149. package/src/spec/index.ts +3 -3
  150. package/src/spec/load.ts +76 -76
  151. package/src/spec/lock.ts +56 -56
  152. package/src/utils/bun.ts +8 -8
  153. package/src/utils/lru-cache.ts +75 -75
  154. package/src/utils/safe-io.ts +188 -188
  155. package/src/utils/string-safe.ts +298 -298
@@ -1,302 +1,302 @@
1
- /**
2
- * Mandu CSS Builder
3
- * Tailwind CSS v4 CLI 기반 CSS 빌드 및 감시
4
- *
5
- * 특징:
6
- * - Tailwind v4 Oxide Engine (Rust) 사용
7
- * - Zero Config: @import "tailwindcss" 자동 감지
8
- * - 출력: .mandu/client/globals.css
9
- */
10
-
11
- import { spawn, type Subprocess } from "bun";
12
- import path from "path";
13
- import fs from "fs/promises";
14
-
15
- // ========== Types ==========
16
-
17
- export interface CSSBuildOptions {
18
- /** 프로젝트 루트 디렉토리 */
19
- rootDir: string;
20
- /** CSS 입력 파일 (기본: "app/globals.css") */
21
- input?: string;
22
- /** CSS 출력 파일 (기본: ".mandu/client/globals.css") */
23
- output?: string;
24
- /** Watch 모드 활성화 */
25
- watch?: boolean;
26
- /** Minify 활성화 (production) */
27
- minify?: boolean;
28
- /** 빌드 완료 콜백 */
29
- onBuild?: (result: CSSBuildResult) => void;
30
- /** 에러 콜백 */
31
- onError?: (error: Error) => void;
32
- }
33
-
34
- export interface CSSBuildResult {
35
- success: boolean;
36
- outputPath: string;
37
- buildTime?: number;
38
- error?: string;
39
- }
40
-
41
- export interface CSSWatcher {
42
- /** Tailwind CLI 프로세스 */
43
- process: Subprocess;
44
- /** 출력 파일 경로 (절대 경로) */
45
- outputPath: string;
46
- /** 서버 경로 (/.mandu/client/globals.css) */
47
- serverPath: string;
48
- /** 프로세스 종료 */
49
- close: () => void;
50
- }
51
-
52
- // ========== Constants ==========
53
-
54
- const DEFAULT_INPUT = "app/globals.css";
55
- const DEFAULT_OUTPUT = ".mandu/client/globals.css";
56
- const SERVER_CSS_PATH = "/.mandu/client/globals.css";
57
-
58
- // ========== Detection ==========
59
-
60
- /**
61
- * Tailwind v4 프로젝트인지 감지
62
- * app/globals.css에 @import "tailwindcss" 포함 여부 확인
63
- */
64
- export async function isTailwindProject(rootDir: string): Promise<boolean> {
65
- const cssPath = path.join(rootDir, DEFAULT_INPUT);
66
-
67
- try {
68
- const content = await fs.readFile(cssPath, "utf-8");
69
- // Tailwind v4: @import "tailwindcss"
70
- // Tailwind v3: @tailwind base; @tailwind components; @tailwind utilities;
71
- return (
72
- content.includes('@import "tailwindcss"') ||
73
- content.includes("@import 'tailwindcss'") ||
74
- content.includes("@tailwind base")
75
- );
76
- } catch {
77
- return false;
78
- }
79
- }
80
-
81
- /**
82
- * CSS 입력 파일 존재 여부 확인
83
- */
84
- export async function hasCSSEntry(rootDir: string, input?: string): Promise<boolean> {
85
- const cssPath = path.join(rootDir, input || DEFAULT_INPUT);
86
- try {
87
- await fs.access(cssPath);
88
- return true;
89
- } catch {
90
- return false;
91
- }
92
- }
93
-
94
- // ========== Build ==========
95
-
96
- /**
97
- * CSS 일회성 빌드 (production용)
98
- */
99
- export async function buildCSS(options: CSSBuildOptions): Promise<CSSBuildResult> {
100
- const {
101
- rootDir,
102
- input = DEFAULT_INPUT,
103
- output = DEFAULT_OUTPUT,
104
- minify = true,
105
- } = options;
106
-
107
- const inputPath = path.join(rootDir, input);
108
- const outputPath = path.join(rootDir, output);
109
- const startTime = performance.now();
110
-
111
- // 출력 디렉토리 생성
112
- await fs.mkdir(path.dirname(outputPath), { recursive: true });
113
-
114
- // Tailwind CLI 실행
115
- const args = [
116
- "@tailwindcss/cli",
117
- "-i", inputPath,
118
- "-o", outputPath,
119
- ];
120
-
121
- if (minify) {
122
- args.push("--minify");
123
- }
124
-
125
- try {
126
- const proc = spawn(["bunx", ...args], {
127
- cwd: rootDir,
128
- stdout: "pipe",
129
- stderr: "pipe",
130
- });
131
-
132
- // 프로세스 완료 대기
133
- const exitCode = await proc.exited;
134
-
135
- if (exitCode !== 0) {
136
- const stderr = await new Response(proc.stderr).text();
137
- return {
138
- success: false,
139
- outputPath,
140
- error: stderr || `Tailwind CLI exited with code ${exitCode}`,
141
- };
142
- }
143
-
144
- const buildTime = performance.now() - startTime;
145
-
146
- return {
147
- success: true,
148
- outputPath,
149
- buildTime,
150
- };
151
- } catch (error) {
152
- return {
153
- success: false,
154
- outputPath,
155
- error: error instanceof Error ? error.message : String(error),
156
- };
157
- }
158
- }
159
-
160
- // ========== Watch ==========
161
-
162
- /**
163
- * CSS 감시 모드 시작 (development용)
164
- * Tailwind CLI --watch 모드로 실행
165
- */
166
- export async function startCSSWatch(options: CSSBuildOptions): Promise<CSSWatcher> {
167
- const {
168
- rootDir,
169
- input = DEFAULT_INPUT,
170
- output = DEFAULT_OUTPUT,
171
- minify = false,
172
- onBuild,
173
- onError,
174
- } = options;
175
-
176
- const inputPath = path.join(rootDir, input);
177
- const outputPath = path.join(rootDir, output);
178
-
179
- try {
180
- // 출력 디렉토리 생성
181
- await fs.mkdir(path.dirname(outputPath), { recursive: true });
182
- } catch (error) {
183
- const err = new Error(`CSS 출력 디렉토리 생성 실패: ${error instanceof Error ? error.message : error}`);
184
- console.error(`❌ ${err.message}`);
185
- onError?.(err);
186
- throw err;
187
- }
188
-
189
- // Tailwind CLI 인자 구성
190
- const args = [
191
- "@tailwindcss/cli",
192
- "-i", inputPath,
193
- "-o", outputPath,
194
- "--watch",
195
- ];
196
-
197
- if (minify) {
198
- args.push("--minify");
199
- }
200
-
201
- console.log(`🎨 Tailwind CSS v4 빌드 시작...`);
202
- console.log(` 입력: ${input}`);
203
- console.log(` 출력: ${output}`);
204
-
205
- // Bun subprocess로 Tailwind CLI 실행
206
- let proc;
207
- try {
208
- proc = spawn(["bunx", ...args], {
209
- cwd: rootDir,
210
- stdout: "pipe",
211
- stderr: "pipe",
212
- });
213
- } catch (error) {
214
- const err = new Error(
215
- `Tailwind CLI 실행 실패. @tailwindcss/cli가 설치되어 있는지 확인하세요.\n` +
216
- `설치: bun add -d @tailwindcss/cli tailwindcss\n` +
217
- `원인: ${error instanceof Error ? error.message : error}`
218
- );
219
- console.error(`❌ ${err.message}`);
220
- onError?.(err);
221
- throw err;
222
- }
223
-
224
- // stdout 모니터링 (빌드 완료 감지)
225
- (async () => {
226
- const reader = proc.stdout.getReader();
227
- const decoder = new TextDecoder();
228
-
229
- while (true) {
230
- const { done, value } = await reader.read();
231
- if (done) break;
232
-
233
- const text = decoder.decode(value);
234
- const lines = text.split("\n").filter((l) => l.trim());
235
-
236
- for (const line of lines) {
237
- // Tailwind v4 출력 패턴: "Done in Xms" 또는 빌드 완료 메시지
238
- if (line.includes("Done in") || line.includes("Rebuilt in")) {
239
- console.log(` ✅ CSS ${line.trim()}`);
240
- onBuild?.({
241
- success: true,
242
- outputPath,
243
- });
244
- } else if (line.includes("warn") || line.includes("Warning")) {
245
- console.log(` ⚠️ CSS ${line.trim()}`);
246
- }
247
- }
248
- }
249
- })();
250
-
251
- // stderr 모니터링 (에러 감지)
252
- (async () => {
253
- const reader = proc.stderr.getReader();
254
- const decoder = new TextDecoder();
255
-
256
- while (true) {
257
- const { done, value } = await reader.read();
258
- if (done) break;
259
-
260
- const text = decoder.decode(value).trim();
261
- if (text) {
262
- // bash_profile 경고는 무시
263
- if (text.includes(".bash_profile") || text.includes("$'\\377")) {
264
- continue;
265
- }
266
- console.error(` ❌ CSS Error: ${text}`);
267
- onError?.(new Error(text));
268
- }
269
- }
270
- })();
271
-
272
- // 프로세스 종료 감지
273
- proc.exited.then((code) => {
274
- if (code !== 0 && code !== null) {
275
- console.error(` ❌ Tailwind CLI exited with code ${code}`);
276
- }
277
- });
278
-
279
- return {
280
- process: proc,
281
- outputPath,
282
- serverPath: SERVER_CSS_PATH,
283
- close: () => {
284
- proc.kill();
285
- },
286
- };
287
- }
288
-
289
- /**
290
- * CSS 서버 경로 반환
291
- */
292
- export function getCSSServerPath(): string {
293
- return SERVER_CSS_PATH;
294
- }
295
-
296
- /**
297
- * CSS 링크 태그 생성
298
- */
299
- export function generateCSSLinkTag(isDev: boolean = false): string {
300
- const cacheBust = isDev ? `?t=${Date.now()}` : "";
301
- return `<link rel="stylesheet" href="${SERVER_CSS_PATH}${cacheBust}">`;
302
- }
1
+ /**
2
+ * Mandu CSS Builder
3
+ * Tailwind CSS v4 CLI 기반 CSS 빌드 및 감시
4
+ *
5
+ * 특징:
6
+ * - Tailwind v4 Oxide Engine (Rust) 사용
7
+ * - Zero Config: @import "tailwindcss" 자동 감지
8
+ * - 출력: .mandu/client/globals.css
9
+ */
10
+
11
+ import { spawn, type Subprocess } from "bun";
12
+ import path from "path";
13
+ import fs from "fs/promises";
14
+
15
+ // ========== Types ==========
16
+
17
+ export interface CSSBuildOptions {
18
+ /** 프로젝트 루트 디렉토리 */
19
+ rootDir: string;
20
+ /** CSS 입력 파일 (기본: "app/globals.css") */
21
+ input?: string;
22
+ /** CSS 출력 파일 (기본: ".mandu/client/globals.css") */
23
+ output?: string;
24
+ /** Watch 모드 활성화 */
25
+ watch?: boolean;
26
+ /** Minify 활성화 (production) */
27
+ minify?: boolean;
28
+ /** 빌드 완료 콜백 */
29
+ onBuild?: (result: CSSBuildResult) => void;
30
+ /** 에러 콜백 */
31
+ onError?: (error: Error) => void;
32
+ }
33
+
34
+ export interface CSSBuildResult {
35
+ success: boolean;
36
+ outputPath: string;
37
+ buildTime?: number;
38
+ error?: string;
39
+ }
40
+
41
+ export interface CSSWatcher {
42
+ /** Tailwind CLI 프로세스 */
43
+ process: Subprocess;
44
+ /** 출력 파일 경로 (절대 경로) */
45
+ outputPath: string;
46
+ /** 서버 경로 (/.mandu/client/globals.css) */
47
+ serverPath: string;
48
+ /** 프로세스 종료 */
49
+ close: () => void;
50
+ }
51
+
52
+ // ========== Constants ==========
53
+
54
+ const DEFAULT_INPUT = "app/globals.css";
55
+ const DEFAULT_OUTPUT = ".mandu/client/globals.css";
56
+ const SERVER_CSS_PATH = "/.mandu/client/globals.css";
57
+
58
+ // ========== Detection ==========
59
+
60
+ /**
61
+ * Tailwind v4 프로젝트인지 감지
62
+ * app/globals.css에 @import "tailwindcss" 포함 여부 확인
63
+ */
64
+ export async function isTailwindProject(rootDir: string): Promise<boolean> {
65
+ const cssPath = path.join(rootDir, DEFAULT_INPUT);
66
+
67
+ try {
68
+ const content = await fs.readFile(cssPath, "utf-8");
69
+ // Tailwind v4: @import "tailwindcss"
70
+ // Tailwind v3: @tailwind base; @tailwind components; @tailwind utilities;
71
+ return (
72
+ content.includes('@import "tailwindcss"') ||
73
+ content.includes("@import 'tailwindcss'") ||
74
+ content.includes("@tailwind base")
75
+ );
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * CSS 입력 파일 존재 여부 확인
83
+ */
84
+ export async function hasCSSEntry(rootDir: string, input?: string): Promise<boolean> {
85
+ const cssPath = path.join(rootDir, input || DEFAULT_INPUT);
86
+ try {
87
+ await fs.access(cssPath);
88
+ return true;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ // ========== Build ==========
95
+
96
+ /**
97
+ * CSS 일회성 빌드 (production용)
98
+ */
99
+ export async function buildCSS(options: CSSBuildOptions): Promise<CSSBuildResult> {
100
+ const {
101
+ rootDir,
102
+ input = DEFAULT_INPUT,
103
+ output = DEFAULT_OUTPUT,
104
+ minify = true,
105
+ } = options;
106
+
107
+ const inputPath = path.join(rootDir, input);
108
+ const outputPath = path.join(rootDir, output);
109
+ const startTime = performance.now();
110
+
111
+ // 출력 디렉토리 생성
112
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
113
+
114
+ // Tailwind CLI 실행
115
+ const args = [
116
+ "@tailwindcss/cli",
117
+ "-i", inputPath,
118
+ "-o", outputPath,
119
+ ];
120
+
121
+ if (minify) {
122
+ args.push("--minify");
123
+ }
124
+
125
+ try {
126
+ const proc = spawn(["bunx", ...args], {
127
+ cwd: rootDir,
128
+ stdout: "pipe",
129
+ stderr: "pipe",
130
+ });
131
+
132
+ // 프로세스 완료 대기
133
+ const exitCode = await proc.exited;
134
+
135
+ if (exitCode !== 0) {
136
+ const stderr = await new Response(proc.stderr).text();
137
+ return {
138
+ success: false,
139
+ outputPath,
140
+ error: stderr || `Tailwind CLI exited with code ${exitCode}`,
141
+ };
142
+ }
143
+
144
+ const buildTime = performance.now() - startTime;
145
+
146
+ return {
147
+ success: true,
148
+ outputPath,
149
+ buildTime,
150
+ };
151
+ } catch (error) {
152
+ return {
153
+ success: false,
154
+ outputPath,
155
+ error: error instanceof Error ? error.message : String(error),
156
+ };
157
+ }
158
+ }
159
+
160
+ // ========== Watch ==========
161
+
162
+ /**
163
+ * CSS 감시 모드 시작 (development용)
164
+ * Tailwind CLI --watch 모드로 실행
165
+ */
166
+ export async function startCSSWatch(options: CSSBuildOptions): Promise<CSSWatcher> {
167
+ const {
168
+ rootDir,
169
+ input = DEFAULT_INPUT,
170
+ output = DEFAULT_OUTPUT,
171
+ minify = false,
172
+ onBuild,
173
+ onError,
174
+ } = options;
175
+
176
+ const inputPath = path.join(rootDir, input);
177
+ const outputPath = path.join(rootDir, output);
178
+
179
+ try {
180
+ // 출력 디렉토리 생성
181
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
182
+ } catch (error) {
183
+ const err = new Error(`CSS 출력 디렉토리 생성 실패: ${error instanceof Error ? error.message : error}`);
184
+ console.error(`❌ ${err.message}`);
185
+ onError?.(err);
186
+ throw err;
187
+ }
188
+
189
+ // Tailwind CLI 인자 구성
190
+ const args = [
191
+ "@tailwindcss/cli",
192
+ "-i", inputPath,
193
+ "-o", outputPath,
194
+ "--watch",
195
+ ];
196
+
197
+ if (minify) {
198
+ args.push("--minify");
199
+ }
200
+
201
+ console.log(`🎨 Tailwind CSS v4 빌드 시작...`);
202
+ console.log(` 입력: ${input}`);
203
+ console.log(` 출력: ${output}`);
204
+
205
+ // Bun subprocess로 Tailwind CLI 실행
206
+ let proc;
207
+ try {
208
+ proc = spawn(["bunx", ...args], {
209
+ cwd: rootDir,
210
+ stdout: "pipe",
211
+ stderr: "pipe",
212
+ });
213
+ } catch (error) {
214
+ const err = new Error(
215
+ `Tailwind CLI 실행 실패. @tailwindcss/cli가 설치되어 있는지 확인하세요.\n` +
216
+ `설치: bun add -d @tailwindcss/cli tailwindcss\n` +
217
+ `원인: ${error instanceof Error ? error.message : error}`
218
+ );
219
+ console.error(`❌ ${err.message}`);
220
+ onError?.(err);
221
+ throw err;
222
+ }
223
+
224
+ // stdout 모니터링 (빌드 완료 감지)
225
+ (async () => {
226
+ const reader = proc.stdout.getReader();
227
+ const decoder = new TextDecoder();
228
+
229
+ while (true) {
230
+ const { done, value } = await reader.read();
231
+ if (done) break;
232
+
233
+ const text = decoder.decode(value);
234
+ const lines = text.split("\n").filter((l) => l.trim());
235
+
236
+ for (const line of lines) {
237
+ // Tailwind v4 출력 패턴: "Done in Xms" 또는 빌드 완료 메시지
238
+ if (line.includes("Done in") || line.includes("Rebuilt in")) {
239
+ console.log(` ✅ CSS ${line.trim()}`);
240
+ onBuild?.({
241
+ success: true,
242
+ outputPath,
243
+ });
244
+ } else if (line.includes("warn") || line.includes("Warning")) {
245
+ console.log(` ⚠️ CSS ${line.trim()}`);
246
+ }
247
+ }
248
+ }
249
+ })();
250
+
251
+ // stderr 모니터링 (에러 감지)
252
+ (async () => {
253
+ const reader = proc.stderr.getReader();
254
+ const decoder = new TextDecoder();
255
+
256
+ while (true) {
257
+ const { done, value } = await reader.read();
258
+ if (done) break;
259
+
260
+ const text = decoder.decode(value).trim();
261
+ if (text) {
262
+ // bash_profile 경고는 무시
263
+ if (text.includes(".bash_profile") || text.includes("$'\\377")) {
264
+ continue;
265
+ }
266
+ console.error(` ❌ CSS Error: ${text}`);
267
+ onError?.(new Error(text));
268
+ }
269
+ }
270
+ })();
271
+
272
+ // 프로세스 종료 감지
273
+ proc.exited.then((code) => {
274
+ if (code !== 0 && code !== null) {
275
+ console.error(` ❌ Tailwind CLI exited with code ${code}`);
276
+ }
277
+ });
278
+
279
+ return {
280
+ process: proc,
281
+ outputPath,
282
+ serverPath: SERVER_CSS_PATH,
283
+ close: () => {
284
+ proc.kill();
285
+ },
286
+ };
287
+ }
288
+
289
+ /**
290
+ * CSS 서버 경로 반환
291
+ */
292
+ export function getCSSServerPath(): string {
293
+ return SERVER_CSS_PATH;
294
+ }
295
+
296
+ /**
297
+ * CSS 링크 태그 생성
298
+ */
299
+ export function generateCSSLinkTag(isDev: boolean = false): string {
300
+ const cacheBust = isDev ? `?t=${Date.now()}` : "";
301
+ return `<link rel="stylesheet" href="${SERVER_CSS_PATH}${cacheBust}">`;
302
+ }