@mandujs/core 0.12.1 → 0.13.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.
Files changed (177) hide show
  1. package/README.ko.md +304 -304
  2. package/README.md +653 -653
  3. package/package.json +8 -8
  4. package/src/brain/architecture/analyzer.ts +28 -26
  5. package/src/brain/doctor/analyzer.ts +1 -1
  6. package/src/bundler/build.ts +91 -91
  7. package/src/bundler/css.ts +302 -302
  8. package/src/bundler/dev.ts +0 -1
  9. package/src/change/history.ts +3 -3
  10. package/src/change/snapshot.ts +10 -9
  11. package/src/change/transaction.ts +2 -2
  12. package/src/client/Link.tsx +227 -227
  13. package/src/client/globals.ts +44 -44
  14. package/src/client/hooks.ts +267 -267
  15. package/src/client/index.ts +5 -5
  16. package/src/client/island.ts +8 -8
  17. package/src/client/router.ts +435 -435
  18. package/src/client/runtime.ts +23 -23
  19. package/src/client/serialize.ts +404 -404
  20. package/src/client/window-state.ts +101 -101
  21. package/src/config/mandu.ts +94 -96
  22. package/src/config/validate.ts +213 -215
  23. package/src/config/watcher.ts +311 -311
  24. package/src/constants.ts +40 -40
  25. package/src/content/content-layer.ts +314 -314
  26. package/src/content/content.test.ts +433 -433
  27. package/src/content/data-store.ts +245 -245
  28. package/src/content/digest.ts +133 -133
  29. package/src/content/index.ts +164 -164
  30. package/src/content/loader-context.ts +172 -172
  31. package/src/content/loaders/api.ts +216 -216
  32. package/src/content/loaders/file.ts +169 -169
  33. package/src/content/loaders/glob.ts +252 -252
  34. package/src/content/loaders/index.ts +34 -34
  35. package/src/content/loaders/types.ts +137 -137
  36. package/src/content/meta-store.ts +209 -209
  37. package/src/content/types.ts +282 -282
  38. package/src/content/watcher.ts +135 -135
  39. package/src/contract/client-safe.test.ts +42 -42
  40. package/src/contract/client-safe.ts +114 -114
  41. package/src/contract/client.ts +16 -16
  42. package/src/contract/define.ts +459 -459
  43. package/src/contract/handler.ts +10 -10
  44. package/src/contract/normalize.test.ts +276 -276
  45. package/src/contract/normalize.ts +404 -404
  46. package/src/contract/registry.test.ts +206 -206
  47. package/src/contract/registry.ts +568 -568
  48. package/src/contract/schema.ts +48 -48
  49. package/src/contract/types.ts +58 -58
  50. package/src/contract/validator.ts +32 -32
  51. package/src/devtools/ai/context-builder.ts +375 -375
  52. package/src/devtools/ai/index.ts +25 -25
  53. package/src/devtools/ai/mcp-connector.ts +465 -465
  54. package/src/devtools/client/catchers/error-catcher.ts +327 -327
  55. package/src/devtools/client/catchers/index.ts +18 -18
  56. package/src/devtools/client/catchers/network-proxy.ts +363 -363
  57. package/src/devtools/client/components/index.ts +39 -39
  58. package/src/devtools/client/components/kitchen-root.tsx +362 -362
  59. package/src/devtools/client/components/mandu-character.tsx +241 -241
  60. package/src/devtools/client/components/overlay.tsx +368 -368
  61. package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
  62. package/src/devtools/client/components/panel/guard-panel.tsx +244 -244
  63. package/src/devtools/client/components/panel/index.ts +32 -32
  64. package/src/devtools/client/components/panel/islands-panel.tsx +304 -304
  65. package/src/devtools/client/components/panel/network-panel.tsx +292 -292
  66. package/src/devtools/client/components/panel/panel-container.tsx +259 -259
  67. package/src/devtools/client/filters/context-filters.ts +282 -282
  68. package/src/devtools/client/filters/index.ts +16 -16
  69. package/src/devtools/client/index.ts +63 -63
  70. package/src/devtools/client/persistence.ts +335 -335
  71. package/src/devtools/client/state-manager.ts +478 -478
  72. package/src/devtools/design-tokens.ts +263 -263
  73. package/src/devtools/hook/create-hook.ts +207 -207
  74. package/src/devtools/hook/index.ts +13 -13
  75. package/src/devtools/index.ts +439 -439
  76. package/src/devtools/init.ts +266 -266
  77. package/src/devtools/protocol.ts +237 -237
  78. package/src/devtools/server/index.ts +17 -17
  79. package/src/devtools/server/source-context.ts +444 -444
  80. package/src/devtools/types.ts +319 -319
  81. package/src/devtools/worker/index.ts +25 -25
  82. package/src/devtools/worker/redaction-worker.ts +222 -222
  83. package/src/devtools/worker/worker-manager.ts +409 -409
  84. package/src/error/classifier.ts +2 -2
  85. package/src/error/domains.ts +265 -265
  86. package/src/error/formatter.ts +32 -32
  87. package/src/error/result.ts +46 -46
  88. package/src/error/stack-analyzer.ts +5 -0
  89. package/src/error/types.ts +6 -6
  90. package/src/errors/extractor.ts +409 -409
  91. package/src/errors/index.ts +19 -19
  92. package/src/filling/auth.ts +308 -308
  93. package/src/filling/context.ts +569 -569
  94. package/src/filling/deps.ts +238 -238
  95. package/src/generator/contract-glue.ts +2 -1
  96. package/src/generator/generate.ts +12 -10
  97. package/src/generator/index.ts +3 -3
  98. package/src/generator/templates.ts +80 -79
  99. package/src/guard/analyzer.ts +360 -360
  100. package/src/guard/ast-analyzer.ts +806 -806
  101. package/src/guard/auto-correct.ts +1 -1
  102. package/src/guard/check.ts +128 -128
  103. package/src/guard/contract-guard.ts +9 -9
  104. package/src/guard/file-type.test.ts +24 -24
  105. package/src/guard/healing.ts +2 -0
  106. package/src/guard/index.ts +2 -0
  107. package/src/guard/negotiation.ts +430 -4
  108. package/src/guard/presets/atomic.ts +70 -70
  109. package/src/guard/presets/clean.ts +77 -77
  110. package/src/guard/presets/cqrs.test.ts +175 -0
  111. package/src/guard/presets/cqrs.ts +107 -0
  112. package/src/guard/presets/fsd.ts +79 -79
  113. package/src/guard/presets/hexagonal.ts +68 -68
  114. package/src/guard/presets/index.ts +291 -288
  115. package/src/guard/reporter.ts +445 -445
  116. package/src/guard/rules.ts +12 -12
  117. package/src/guard/statistics.ts +578 -578
  118. package/src/guard/suggestions.ts +358 -352
  119. package/src/guard/types.ts +348 -347
  120. package/src/guard/validator.ts +834 -834
  121. package/src/guard/watcher.ts +404 -404
  122. package/src/index.ts +1 -0
  123. package/src/intent/index.ts +310 -310
  124. package/src/island/index.ts +304 -304
  125. package/src/logging/index.ts +22 -22
  126. package/src/logging/transports.ts +365 -365
  127. package/src/paths.test.ts +47 -0
  128. package/src/paths.ts +47 -0
  129. package/src/plugins/index.ts +38 -38
  130. package/src/plugins/registry.ts +377 -377
  131. package/src/plugins/types.ts +363 -363
  132. package/src/report/build.ts +1 -1
  133. package/src/report/index.ts +1 -1
  134. package/src/router/fs-patterns.ts +387 -387
  135. package/src/router/fs-routes.ts +344 -401
  136. package/src/router/fs-scanner.ts +497 -497
  137. package/src/router/fs-types.ts +270 -278
  138. package/src/router/index.ts +81 -81
  139. package/src/runtime/boundary.tsx +232 -232
  140. package/src/runtime/compose.ts +222 -222
  141. package/src/runtime/lifecycle.ts +381 -381
  142. package/src/runtime/logger.test.ts +345 -345
  143. package/src/runtime/logger.ts +677 -677
  144. package/src/runtime/router.test.ts +476 -476
  145. package/src/runtime/router.ts +105 -105
  146. package/src/runtime/security.ts +155 -155
  147. package/src/runtime/server.ts +24 -24
  148. package/src/runtime/session-key.ts +328 -328
  149. package/src/runtime/ssr.ts +367 -367
  150. package/src/runtime/streaming-ssr.ts +1245 -1245
  151. package/src/runtime/trace.ts +144 -144
  152. package/src/seo/index.ts +214 -214
  153. package/src/seo/integration/ssr.ts +307 -307
  154. package/src/seo/render/basic.ts +427 -427
  155. package/src/seo/render/index.ts +143 -143
  156. package/src/seo/render/jsonld.ts +539 -539
  157. package/src/seo/render/opengraph.ts +191 -191
  158. package/src/seo/render/robots.ts +116 -116
  159. package/src/seo/render/sitemap.ts +137 -137
  160. package/src/seo/render/twitter.ts +126 -126
  161. package/src/seo/resolve/index.ts +353 -353
  162. package/src/seo/resolve/opengraph.ts +143 -143
  163. package/src/seo/resolve/robots.ts +73 -73
  164. package/src/seo/resolve/title.ts +94 -94
  165. package/src/seo/resolve/twitter.ts +73 -73
  166. package/src/seo/resolve/url.ts +97 -97
  167. package/src/seo/routes/index.ts +290 -290
  168. package/src/seo/types.ts +575 -575
  169. package/src/slot/validator.ts +39 -39
  170. package/src/spec/index.ts +3 -3
  171. package/src/spec/load.ts +76 -76
  172. package/src/spec/lock.ts +56 -56
  173. package/src/utils/bun.ts +8 -8
  174. package/src/utils/lru-cache.ts +75 -75
  175. package/src/utils/safe-io.ts +188 -188
  176. package/src/utils/string-safe.ts +298 -298
  177. package/src/watcher/rules.ts +5 -5
@@ -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
+ }
@@ -63,7 +63,6 @@ const DEFAULT_COMMON_DIRS = [
63
63
  "client",
64
64
  "src/islands",
65
65
  "islands",
66
- "apps/web",
67
66
  ];
68
67
 
69
68
  /**
@@ -3,7 +3,7 @@ import type { ChangeRecord, HistoryConfig } from "./types";
3
3
  import { deleteSnapshot, listSnapshotIds } from "./snapshot";
4
4
  import { DEFAULT_HISTORY_CONFIG } from "./types";
5
5
 
6
- const SPEC_DIR = "spec";
6
+ const MANDU_DIR = ".mandu";
7
7
  const HISTORY_DIR = "history";
8
8
  const CHANGES_FILE = "changes.json";
9
9
 
@@ -11,7 +11,7 @@ const CHANGES_FILE = "changes.json";
11
11
  * Changes 파일 경로
12
12
  */
13
13
  function getChangesPath(rootDir: string): string {
14
- return path.join(rootDir, SPEC_DIR, HISTORY_DIR, CHANGES_FILE);
14
+ return path.join(rootDir, MANDU_DIR, HISTORY_DIR, CHANGES_FILE);
15
15
  }
16
16
 
17
17
  /**
@@ -43,7 +43,7 @@ export async function getChange(rootDir: string, id: string): Promise<ChangeReco
43
43
  */
44
44
  async function writeChanges(rootDir: string, changes: ChangeRecord[]): Promise<void> {
45
45
  const changesPath = getChangesPath(rootDir);
46
- const historyDir = path.join(rootDir, SPEC_DIR, HISTORY_DIR);
46
+ const historyDir = path.join(rootDir, MANDU_DIR, HISTORY_DIR);
47
47
 
48
48
  // 디렉토리 확보
49
49
  await Bun.write(path.join(historyDir, ".gitkeep"), "");