@simplysm/sd-cli 14.0.11 → 14.0.13

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 (265) hide show
  1. package/README.md +58 -253
  2. package/dist/angular/client-transform-stylesheet.js +1 -1
  3. package/dist/angular/client-transform-stylesheet.js.map +1 -1
  4. package/dist/angular/vite-angular-plugin.d.ts +4 -2
  5. package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
  6. package/dist/angular/vite-angular-plugin.js +73 -36
  7. package/dist/angular/vite-angular-plugin.js.map +1 -1
  8. package/dist/angular/vite-postcss-inline-plugin.d.ts +1 -1
  9. package/dist/angular/vite-postcss-inline-plugin.js +1 -1
  10. package/dist/capacitor/capacitor.d.ts +20 -2
  11. package/dist/capacitor/capacitor.d.ts.map +1 -1
  12. package/dist/capacitor/capacitor.js +155 -28
  13. package/dist/capacitor/capacitor.js.map +1 -1
  14. package/dist/commands/build.d.ts +3 -10
  15. package/dist/commands/build.d.ts.map +1 -1
  16. package/dist/commands/build.js +3 -10
  17. package/dist/commands/build.js.map +1 -1
  18. package/dist/commands/check.js +3 -3
  19. package/dist/commands/check.js.map +1 -1
  20. package/dist/commands/dev.d.ts +3 -9
  21. package/dist/commands/dev.d.ts.map +1 -1
  22. package/dist/commands/dev.js +3 -9
  23. package/dist/commands/dev.js.map +1 -1
  24. package/dist/commands/device.d.ts +3 -3
  25. package/dist/commands/device.js +5 -5
  26. package/dist/commands/device.js.map +1 -1
  27. package/dist/commands/publish.d.ts +1 -1
  28. package/dist/commands/publish.d.ts.map +1 -1
  29. package/dist/commands/publish.js +24 -26
  30. package/dist/commands/publish.js.map +1 -1
  31. package/dist/commands/replace-deps.d.ts +3 -3
  32. package/dist/commands/replace-deps.d.ts.map +1 -1
  33. package/dist/commands/replace-deps.js +1 -1
  34. package/dist/commands/typecheck.d.ts +4 -3
  35. package/dist/commands/typecheck.d.ts.map +1 -1
  36. package/dist/commands/typecheck.js +5 -11
  37. package/dist/commands/typecheck.js.map +1 -1
  38. package/dist/commands/watch.d.ts +9 -9
  39. package/dist/commands/watch.js +9 -9
  40. package/dist/electron/electron.d.ts.map +1 -1
  41. package/dist/electron/electron.js +42 -3
  42. package/dist/electron/electron.js.map +1 -1
  43. package/dist/engines/BaseEngine.d.ts +1 -1
  44. package/dist/engines/BaseEngine.d.ts.map +1 -1
  45. package/dist/engines/BaseEngine.js +3 -1
  46. package/dist/engines/BaseEngine.js.map +1 -1
  47. package/dist/engines/NgtscEngine.d.ts +7 -7
  48. package/dist/engines/NgtscEngine.d.ts.map +1 -1
  49. package/dist/engines/NgtscEngine.js +3 -3
  50. package/dist/engines/ServerEsbuildEngine.d.ts +7 -7
  51. package/dist/engines/ServerEsbuildEngine.d.ts.map +1 -1
  52. package/dist/engines/ServerEsbuildEngine.js +3 -3
  53. package/dist/engines/TscEngine.d.ts +7 -7
  54. package/dist/engines/TscEngine.d.ts.map +1 -1
  55. package/dist/engines/TscEngine.js +3 -3
  56. package/dist/engines/ViteEngine.d.ts +7 -1
  57. package/dist/engines/ViteEngine.d.ts.map +1 -1
  58. package/dist/engines/ViteEngine.js +13 -12
  59. package/dist/engines/ViteEngine.js.map +1 -1
  60. package/dist/engines/index.d.ts +9 -5
  61. package/dist/engines/index.d.ts.map +1 -1
  62. package/dist/engines/index.js +7 -5
  63. package/dist/engines/index.js.map +1 -1
  64. package/dist/engines/types.d.ts +20 -20
  65. package/dist/engines/types.d.ts.map +1 -1
  66. package/dist/infra/ResultCollector.d.ts +9 -9
  67. package/dist/infra/ResultCollector.js +8 -8
  68. package/dist/infra/SignalHandler.d.ts +7 -7
  69. package/dist/infra/SignalHandler.js +7 -7
  70. package/dist/infra/WorkerManager.d.ts +14 -14
  71. package/dist/infra/WorkerManager.js +14 -14
  72. package/dist/orchestrators/BuildOrchestrator.d.ts +25 -25
  73. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  74. package/dist/orchestrators/BuildOrchestrator.js +34 -30
  75. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  76. package/dist/orchestrators/DevWatchOrchestrator.d.ts +7 -7
  77. package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
  78. package/dist/orchestrators/DevWatchOrchestrator.js +34 -34
  79. package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
  80. package/dist/sd-cli-entry.d.ts +2 -2
  81. package/dist/sd-cli-entry.d.ts.map +1 -1
  82. package/dist/sd-cli-entry.js +15 -8
  83. package/dist/sd-cli-entry.js.map +1 -1
  84. package/dist/sd-cli.d.ts +3 -3
  85. package/dist/sd-cli.js +16 -16
  86. package/dist/sd-cli.js.map +1 -1
  87. package/dist/sd-config.types.d.ts +105 -105
  88. package/dist/sd-config.types.d.ts.map +1 -1
  89. package/dist/utils/angular-compiler.js +5 -5
  90. package/dist/utils/angular-compiler.js.map +1 -1
  91. package/dist/utils/build-env.d.ts +1 -1
  92. package/dist/utils/build-env.js +1 -1
  93. package/dist/utils/concurrency.d.ts +7 -7
  94. package/dist/utils/concurrency.js +7 -7
  95. package/dist/utils/copy-public.d.ts +9 -9
  96. package/dist/utils/copy-public.js +17 -17
  97. package/dist/utils/copy-public.js.map +1 -1
  98. package/dist/utils/copy-src.d.ts +9 -9
  99. package/dist/utils/copy-src.js +11 -11
  100. package/dist/utils/copy-src.js.map +1 -1
  101. package/dist/utils/engine-stop.d.ts +8 -9
  102. package/dist/utils/engine-stop.d.ts.map +1 -1
  103. package/dist/utils/engine-stop.js +9 -10
  104. package/dist/utils/engine-stop.js.map +1 -1
  105. package/dist/utils/esbuild-config.d.ts +23 -23
  106. package/dist/utils/esbuild-config.d.ts.map +1 -1
  107. package/dist/utils/esbuild-config.js +25 -25
  108. package/dist/utils/esbuild-config.js.map +1 -1
  109. package/dist/utils/lint-with-program.d.ts +15 -15
  110. package/dist/utils/lint-with-program.d.ts.map +1 -1
  111. package/dist/utils/lint-with-program.js +29 -29
  112. package/dist/utils/lint-with-program.js.map +1 -1
  113. package/dist/utils/ngtsc-build-core.d.ts +8 -8
  114. package/dist/utils/ngtsc-build-core.d.ts.map +1 -1
  115. package/dist/utils/ngtsc-build-core.js +14 -14
  116. package/dist/utils/ngtsc-build-core.js.map +1 -1
  117. package/dist/utils/output-path-rewriter.d.ts +14 -14
  118. package/dist/utils/output-path-rewriter.js +18 -18
  119. package/dist/utils/output-path-rewriter.js.map +1 -1
  120. package/dist/utils/output-utils.d.ts +6 -6
  121. package/dist/utils/output-utils.js +11 -11
  122. package/dist/utils/output-utils.js.map +1 -1
  123. package/dist/utils/package-utils.d.ts +21 -21
  124. package/dist/utils/package-utils.d.ts.map +1 -1
  125. package/dist/utils/package-utils.js +56 -45
  126. package/dist/utils/package-utils.js.map +1 -1
  127. package/dist/utils/replace-deps.d.ts +25 -25
  128. package/dist/utils/replace-deps.d.ts.map +1 -1
  129. package/dist/utils/replace-deps.js +84 -65
  130. package/dist/utils/replace-deps.js.map +1 -1
  131. package/dist/utils/sd-config.d.ts +3 -3
  132. package/dist/utils/sd-config.js +3 -3
  133. package/dist/utils/tsc-build.d.ts +13 -13
  134. package/dist/utils/tsc-build.d.ts.map +1 -1
  135. package/dist/utils/tsc-build.js +9 -9
  136. package/dist/utils/tsc-build.js.map +1 -1
  137. package/dist/utils/tsconfig.d.ts +11 -9
  138. package/dist/utils/tsconfig.d.ts.map +1 -1
  139. package/dist/utils/tsconfig.js +11 -9
  140. package/dist/utils/tsconfig.js.map +1 -1
  141. package/dist/utils/typecheck-non-package.d.ts +5 -6
  142. package/dist/utils/typecheck-non-package.d.ts.map +1 -1
  143. package/dist/utils/typecheck-non-package.js +7 -8
  144. package/dist/utils/typecheck-non-package.js.map +1 -1
  145. package/dist/utils/typecheck-serialization.d.ts +8 -8
  146. package/dist/utils/typecheck-serialization.d.ts.map +1 -1
  147. package/dist/utils/typecheck-serialization.js +12 -16
  148. package/dist/utils/typecheck-serialization.js.map +1 -1
  149. package/dist/utils/vite-config.d.ts +12 -5
  150. package/dist/utils/vite-config.d.ts.map +1 -1
  151. package/dist/utils/vite-config.js +95 -41
  152. package/dist/utils/vite-config.js.map +1 -1
  153. package/dist/utils/vite-scope-watch-plugin.d.ts.map +1 -1
  154. package/dist/utils/vite-scope-watch-plugin.js +1 -1
  155. package/dist/utils/vite-scope-watch-plugin.js.map +1 -1
  156. package/dist/utils/worker-events.d.ts +12 -12
  157. package/dist/utils/worker-events.d.ts.map +1 -1
  158. package/dist/utils/worker-events.js +10 -10
  159. package/dist/utils/worker-events.js.map +1 -1
  160. package/dist/utils/worker-utils.d.ts +12 -13
  161. package/dist/utils/worker-utils.d.ts.map +1 -1
  162. package/dist/utils/worker-utils.js +12 -13
  163. package/dist/utils/worker-utils.js.map +1 -1
  164. package/dist/vitest-plugin.d.ts.map +1 -1
  165. package/dist/vitest-plugin.js +5 -7
  166. package/dist/vitest-plugin.js.map +1 -1
  167. package/dist/workers/client.worker.d.ts +8 -2
  168. package/dist/workers/client.worker.d.ts.map +1 -1
  169. package/dist/workers/client.worker.js +215 -6
  170. package/dist/workers/client.worker.js.map +1 -1
  171. package/dist/workers/library-build.worker.d.ts +1 -1
  172. package/dist/workers/library-build.worker.d.ts.map +1 -1
  173. package/dist/workers/library-build.worker.js +7 -7
  174. package/dist/workers/library-build.worker.js.map +1 -1
  175. package/dist/workers/lint.worker.d.ts +2 -2
  176. package/dist/workers/lint.worker.js +2 -2
  177. package/dist/workers/ngtsc-build.worker.js +30 -30
  178. package/dist/workers/ngtsc-build.worker.js.map +1 -1
  179. package/dist/workers/server-build.worker.d.ts +17 -17
  180. package/dist/workers/server-build.worker.d.ts.map +1 -1
  181. package/dist/workers/server-build.worker.js +46 -46
  182. package/dist/workers/server-build.worker.js.map +1 -1
  183. package/dist/workers/server-runtime.worker.d.ts +7 -7
  184. package/dist/workers/server-runtime.worker.d.ts.map +1 -1
  185. package/dist/workers/server-runtime.worker.js +17 -17
  186. package/dist/workers/server-runtime.worker.js.map +1 -1
  187. package/docs/config.md +340 -0
  188. package/docs/publish-configuration-types.md +87 -0
  189. package/docs/pwa-configuration-types.md +55 -0
  190. package/docs/vitest-plugin.md +47 -0
  191. package/package.json +9 -7
  192. package/src/angular/client-transform-stylesheet.ts +1 -1
  193. package/src/angular/vite-angular-plugin.ts +89 -39
  194. package/src/angular/vite-postcss-inline-plugin.ts +1 -1
  195. package/src/capacitor/capacitor.ts +185 -38
  196. package/src/commands/build.ts +3 -10
  197. package/src/commands/check.ts +3 -3
  198. package/src/commands/dev.ts +3 -9
  199. package/src/commands/device.ts +5 -5
  200. package/src/commands/publish.ts +30 -26
  201. package/src/commands/replace-deps.ts +3 -3
  202. package/src/commands/typecheck.ts +7 -13
  203. package/src/commands/watch.ts +9 -9
  204. package/src/electron/electron.ts +49 -4
  205. package/src/engines/BaseEngine.ts +4 -1
  206. package/src/engines/NgtscEngine.ts +7 -7
  207. package/src/engines/ServerEsbuildEngine.ts +7 -7
  208. package/src/engines/TscEngine.ts +7 -7
  209. package/src/engines/ViteEngine.ts +18 -13
  210. package/src/engines/index.ts +11 -5
  211. package/src/engines/types.ts +20 -20
  212. package/src/infra/ResultCollector.ts +9 -9
  213. package/src/infra/SignalHandler.ts +7 -7
  214. package/src/infra/WorkerManager.ts +14 -14
  215. package/src/orchestrators/BuildOrchestrator.ts +42 -38
  216. package/src/orchestrators/DevWatchOrchestrator.ts +36 -36
  217. package/src/sd-cli-entry.ts +15 -8
  218. package/src/sd-cli.ts +16 -16
  219. package/src/sd-config.types.ts +107 -107
  220. package/src/utils/angular-compiler.ts +5 -5
  221. package/src/utils/build-env.ts +1 -1
  222. package/src/utils/concurrency.ts +7 -7
  223. package/src/utils/copy-public.ts +17 -17
  224. package/src/utils/copy-src.ts +11 -11
  225. package/src/utils/engine-stop.ts +9 -10
  226. package/src/utils/esbuild-config.ts +29 -29
  227. package/src/utils/lint-with-program.ts +34 -34
  228. package/src/utils/ngtsc-build-core.ts +17 -17
  229. package/src/utils/output-path-rewriter.ts +18 -18
  230. package/src/utils/output-utils.ts +11 -11
  231. package/src/utils/package-utils.ts +57 -45
  232. package/src/utils/replace-deps.ts +92 -67
  233. package/src/utils/sd-config.ts +3 -3
  234. package/src/utils/tsc-build.ts +18 -18
  235. package/src/utils/tsconfig.ts +11 -9
  236. package/src/utils/typecheck-non-package.ts +7 -8
  237. package/src/utils/typecheck-serialization.ts +13 -15
  238. package/src/utils/vite-config.ts +108 -46
  239. package/src/utils/vite-scope-watch-plugin.ts +6 -1
  240. package/src/utils/worker-events.ts +16 -16
  241. package/src/utils/worker-utils.ts +12 -13
  242. package/src/vitest-plugin.ts +5 -8
  243. package/src/workers/client.worker.ts +246 -7
  244. package/src/workers/library-build.worker.ts +8 -8
  245. package/src/workers/lint.worker.ts +2 -2
  246. package/src/workers/ngtsc-build.worker.ts +31 -31
  247. package/src/workers/server-build.worker.ts +60 -60
  248. package/src/workers/server-runtime.worker.ts +22 -22
  249. package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +1 -0
  250. package/tests/angular/vite-angular-plugin-hmr.spec.ts +78 -0
  251. package/tests/angular/vite-angular-plugin.spec.ts +67 -0
  252. package/tests/capacitor/capacitor-build.spec.ts +93 -11
  253. package/tests/capacitor/capacitor-icon.spec.ts +7 -5
  254. package/tests/capacitor/capacitor-init.spec.ts +124 -10
  255. package/tests/capacitor/capacitor-run.spec.ts +14 -17
  256. package/tests/capacitor/capacitor-workspace.spec.ts +5 -3
  257. package/tests/commands/check.spec.ts +2 -2
  258. package/tests/commands/publish.spec.ts +2 -2
  259. package/tests/commands/typecheck.spec.ts +8 -0
  260. package/tests/electron/electron.spec.ts +12 -10
  261. package/tests/engines/base-engine.spec.ts +37 -0
  262. package/tests/engines/vite-engine.spec.ts +115 -3
  263. package/tests/utils/vite-config.spec.ts +162 -90
  264. package/tests/workers/client-worker.spec.ts +690 -0
  265. package/tests/workers/server-build-worker.spec.ts +3 -3
@@ -1,6 +1,8 @@
1
1
  import { createServer, build as viteBuild, type ViteDevServer } from "vite";
2
2
  import path from "path";
3
3
  import fs from "node:fs";
4
+ import http from "node:http";
5
+ import mime from "mime";
4
6
  import { createWorker } from "@simplysm/core-node";
5
7
  import { err as errNs } from "@simplysm/core-common";
6
8
  import { consola } from "consola";
@@ -31,8 +33,14 @@ export interface ClientBuildInfo {
31
33
  browserSupport?: SdBrowserSupportConfig;
32
34
  /** PWA 설정. false로 비활성화. 미설정 시 기본값으로 활성화 */
33
35
  pwa?: false | SdPwaConfig;
34
- /** Enable lint using ts.Program from compilation */
36
+ /** 컴파일의 ts.Program 사용하여 lint 실행 */
35
37
  enableLint?: boolean;
38
+ /** Vite optimizeDeps.exclude에 전달할 패키지 목록 */
39
+ exclude?: string[];
40
+ /** 빌드 출력 경로 (미설정 시 pkgDir/dist) */
41
+ outDir?: string;
42
+ /** Vite base 경로 (미설정 시 /{pkgName}/) */
43
+ base?: string;
36
44
  }
37
45
 
38
46
  /** Client 빌드 결과 */
@@ -58,7 +66,113 @@ export interface ClientWorkerEvents extends Record<string, unknown> {
58
66
 
59
67
  const logger = consola.withTag("sd:cli:client:worker");
60
68
 
69
+ /** viteBuild({ build: { watch: {} } }) 반환 타입의 최소 인터페이스 */
70
+ interface WatcherHandle {
71
+ on(event: string, handler: (event: { code: string; error?: { message: string } }) => void): void;
72
+ close(): Promise<void>;
73
+ }
74
+
61
75
  let viteServer: ViteDevServer | undefined;
76
+ let rollupWatcher: WatcherHandle | undefined;
77
+ let legacyHttpServer: http.Server | undefined;
78
+
79
+ /** SSE 연결된 클라이언트 목록 (live reload용) */
80
+ const sseClients = new Set<http.ServerResponse>();
81
+
82
+ /** SSE 연결된 모든 클라이언트에 reload 신호를 전송한다 */
83
+ function notifyLiveReload(): void {
84
+ for (const client of sseClients) {
85
+ client.write("data: reload\n\n");
86
+ }
87
+ }
88
+
89
+
90
+ /** live reload 클라이언트 스크립트 (HTML에 주입) */
91
+ const LIVE_RELOAD_SCRIPT = `<script>(function(){var s=new EventSource("__live-reload");s.onmessage=function(){location.reload();};})()</script>`;
92
+
93
+ /**
94
+ * HTML 응답 시 live reload 스크립트를 </body> 직전에 주입한다.
95
+ */
96
+ function injectLiveReloadScript(html: string): string {
97
+ const idx = html.lastIndexOf("</body>");
98
+ if (idx !== -1) {
99
+ return html.slice(0, idx) + LIVE_RELOAD_SCRIPT + html.slice(idx);
100
+ }
101
+ return html + LIVE_RELOAD_SCRIPT;
102
+ }
103
+
104
+ /**
105
+ * legacy dev 모드용 HTTP 정적 파일 서버를 생성한다.
106
+ * dist/ 디렉토리의 파일을 서빙하고, SPA fallback + SSE live reload를 지원한다.
107
+ */
108
+ function createLegacyHttpServer(distDir: string, basePath: string): http.Server {
109
+ return http.createServer((req, res) => {
110
+ const url = (req.url ?? "/").split("?")[0];
111
+
112
+ // basePath prefix 제거
113
+ let relativePath: string;
114
+ if (url.startsWith(basePath)) {
115
+ relativePath = url.slice(basePath.length);
116
+ } else {
117
+ res.writeHead(404);
118
+ res.end("Not Found");
119
+ return;
120
+ }
121
+
122
+ // SSE live reload 엔드포인트
123
+ if (relativePath === "__live-reload" || relativePath === "/__live-reload") {
124
+ res.writeHead(200, {
125
+ "Content-Type": "text/event-stream",
126
+ "Cache-Control": "no-cache",
127
+ "Connection": "keep-alive",
128
+ });
129
+ sseClients.add(res);
130
+ req.on("close", () => {
131
+ sseClients.delete(res);
132
+ });
133
+ return;
134
+ }
135
+
136
+ // 빈 경로 또는 / → index.html
137
+ if (relativePath === "" || relativePath === "/") {
138
+ relativePath = "index.html";
139
+ }
140
+
141
+ // 선행 슬래시 제거
142
+ if (relativePath.startsWith("/")) {
143
+ relativePath = relativePath.slice(1);
144
+ }
145
+
146
+ const filePath = path.join(distDir, relativePath);
147
+ const ext = path.extname(filePath);
148
+
149
+ // 파일 존재 확인
150
+ if (fs.existsSync(filePath) && !fs.statSync(filePath).isDirectory()) {
151
+ const contentType = mime.getType(ext) ?? "application/octet-stream";
152
+ if (ext === ".html") {
153
+ // HTML: live reload 스크립트 주입
154
+ const content = injectLiveReloadScript(fs.readFileSync(filePath, "utf-8"));
155
+ res.writeHead(200, { "Content-Type": contentType });
156
+ res.end(content);
157
+ } else {
158
+ const content = fs.readFileSync(filePath);
159
+ res.writeHead(200, { "Content-Type": contentType });
160
+ res.end(content);
161
+ }
162
+ } else {
163
+ // SPA fallback: index.html 반환 (live reload 스크립트 주입)
164
+ const indexPath = path.join(distDir, "index.html");
165
+ if (fs.existsSync(indexPath)) {
166
+ const content = injectLiveReloadScript(fs.readFileSync(indexPath, "utf-8"));
167
+ res.writeHead(200, { "Content-Type": "text/html" });
168
+ res.end(content);
169
+ } else {
170
+ res.writeHead(404);
171
+ res.end("Not Found");
172
+ }
173
+ }
174
+ });
175
+ }
62
176
 
63
177
  function resolvePackageInfo(info: ClientBuildInfo): {
64
178
  tsconfigPath: string;
@@ -77,6 +191,9 @@ function resolvePackageInfo(info: ClientBuildInfo): {
77
191
  * 서버가 준비되면 serverReady 이벤트로 포트를 알린다.
78
192
  */
79
193
  async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
194
+ if (info.browserSupport?.legacyModule === true) {
195
+ return startLegacyWatch(info);
196
+ }
80
197
  logger.debug(`[${info.name}] client worker startWatch 시작 (port: ${info.port ?? "auto"})`);
81
198
  try {
82
199
  const { tsconfigPath, pkgName } = resolvePackageInfo(info);
@@ -102,6 +219,7 @@ async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
102
219
  legacyModule: info.browserSupport?.legacyModule,
103
220
  polyfills,
104
221
  pwa: info.pwa,
222
+ exclude: info.exclude,
105
223
  });
106
224
 
107
225
  logger.debug(`[${info.name}] Vite server 생성 시작`);
@@ -138,15 +256,134 @@ async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
138
256
  }
139
257
 
140
258
  /**
141
- * dev server 중지. Vite server를 정리한다.
259
+ * legacy watch 시작. Vite build --watch로 파일 변경을 감시한다.
260
+ * legacyModule: true일 때 createServer 대신 사용한다.
261
+ */
262
+ async function startLegacyWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
263
+ logger.debug(`[${info.name}] client worker startLegacyWatch 시작`);
264
+ try {
265
+ const { tsconfigPath, pkgName } = resolvePackageInfo(info);
266
+
267
+ // dist 초기화 (첫 빌드만 비움)
268
+ const distDir = path.join(info.pkgDir, "dist");
269
+ fs.rmSync(distDir, { recursive: true, force: true });
270
+
271
+ // polyfills.ts 자동 감지
272
+ const polyfillsPath = path.join(info.pkgDir, "src", "polyfills.ts");
273
+ const polyfills = fs.existsSync(polyfillsPath) ? ["./src/polyfills.ts"] : undefined;
274
+
275
+ const viteConfig = await createClientViteConfig({
276
+ pkgDir: info.pkgDir,
277
+ pkgName,
278
+ mode: "build",
279
+ tsconfigPath,
280
+ serverPort: 0,
281
+ env: info.env,
282
+ watch: true,
283
+ onBuildStart: () => sender.send("buildStart", {}),
284
+ onBuild: (result) => sender.send("build", result),
285
+ enableLint: info.enableLint,
286
+ replaceDeps: info.replaceDeps,
287
+ onScopeRebuild: () => sender.send("scopeRebuild", {}),
288
+ browserslist: info.browserSupport?.browserslist,
289
+ postCssPlugins: info.browserSupport?.postCss?.plugins,
290
+ legacyModule: info.browserSupport?.legacyModule,
291
+ polyfills,
292
+ pwa: false,
293
+ exclude: info.exclude,
294
+ });
295
+
296
+ const watcher = (await viteBuild(viteConfig)) as WatcherHandle;
297
+ rollupWatcher = watcher;
298
+
299
+ // .config.json 생성
300
+ writeConfigJson(info.pkgDir, info.configs);
301
+
302
+ // HTTP 정적 파일 서버 시작
303
+ const name = pkgName.replace(/^@[^/]+\//, "");
304
+ const basePath = `/${name}/`;
305
+ const httpServer = createLegacyHttpServer(distDir, basePath);
306
+ legacyHttpServer = httpServer;
307
+
308
+ const serverPort = await new Promise<number>((resolve, reject) => {
309
+ httpServer.listen(info.port ?? 0, "0.0.0.0", () => {
310
+ const addr = httpServer.address();
311
+ if (typeof addr === "object" && addr != null) {
312
+ resolve(addr.port);
313
+ } else {
314
+ reject(new Error("HTTP 서버 포트를 감지할 수 없습니다."));
315
+ }
316
+ });
317
+ httpServer.on("error", reject);
318
+ });
319
+
320
+ sender.send("serverReady", { port: serverPort });
321
+
322
+ // 첫 빌드 완료 대기
323
+ return await new Promise<ClientBuildResult>((resolve) => {
324
+ let firstBuildResolved = false;
325
+
326
+ watcher.on("event", (event: { code: string; error?: { message: string } }) => {
327
+ if (event.code === "END") {
328
+ if (!firstBuildResolved) {
329
+ firstBuildResolved = true;
330
+ resolve({ success: true });
331
+ } else {
332
+ // 재빌드 완료 → 브라우저 live reload
333
+ notifyLiveReload();
334
+ }
335
+ } else if (event.code === "ERROR") {
336
+ const message = event.error?.message ?? "Unknown build error";
337
+ sender.send("error", { message });
338
+ if (!firstBuildResolved) {
339
+ firstBuildResolved = true;
340
+ resolve({ success: false, errors: [message] });
341
+ }
342
+ }
343
+ });
344
+ });
345
+ } catch (err) {
346
+ const message = errNs.message(err);
347
+ sender.send("error", { message });
348
+ return { success: false, errors: [message] };
349
+ }
350
+ }
351
+
352
+ /**
353
+ * dev server 중지. Vite server 또는 RollupWatcher를 정리한다.
142
354
  */
143
355
  async function stopWatch(): Promise<void> {
144
356
  logger.debug("Vite server 정리 시작");
357
+
358
+ const watcherToClose = rollupWatcher;
359
+ rollupWatcher = undefined;
360
+ if (watcherToClose != null) {
361
+ await watcherToClose.close();
362
+ }
363
+
364
+ // SSE 클라이언트 정리
365
+ for (const client of sseClients) {
366
+ client.end();
367
+ }
368
+ sseClients.clear();
369
+
370
+ const httpServerToClose = legacyHttpServer;
371
+ legacyHttpServer = undefined;
372
+ if (httpServerToClose != null) {
373
+ await new Promise<void>((resolve, reject) => {
374
+ httpServerToClose.close((err) => {
375
+ if (err != null) reject(err);
376
+ else resolve();
377
+ });
378
+ });
379
+ }
380
+
145
381
  const serverToClose = viteServer;
146
382
  viteServer = undefined;
147
383
  if (serverToClose != null) {
148
384
  await serverToClose.close();
149
385
  }
386
+
150
387
  logger.debug("Vite server 정리 완료");
151
388
  }
152
389
 
@@ -182,12 +419,15 @@ async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
182
419
  legacyModule: info.browserSupport?.legacyModule,
183
420
  polyfills,
184
421
  pwa: info.pwa,
422
+ exclude: info.exclude,
423
+ outDir: info.outDir,
424
+ base: info.base,
185
425
  });
186
426
 
187
427
  await viteBuild(viteConfig);
188
428
 
189
- // .config.json 생성
190
- writeConfigJson(info.pkgDir, info.configs);
429
+ // .config.json 생성 (항상 dist/에 기록 — outDir과 무관)
430
+ writeConfigJson(path.join(info.pkgDir, "dist"), info.configs);
191
431
 
192
432
  logger.debug(`[${info.name}] client worker build 완료`);
193
433
  return { success: true, lint: lintResult };
@@ -198,12 +438,11 @@ async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
198
438
  }
199
439
  }
200
440
 
201
- /** dist/.config.json 생성 */
441
+ /** .config.json 생성 */
202
442
  function writeConfigJson(
203
- pkgDir: string,
443
+ distDir: string,
204
444
  configs?: Record<string, unknown>,
205
445
  ): void {
206
- const distDir = path.join(pkgDir, "dist");
207
446
  fs.mkdirSync(distDir, { recursive: true });
208
447
  fs.writeFileSync(
209
448
  path.join(distDir, ".config.json"),
@@ -21,7 +21,7 @@ export interface LibraryBuildInfo {
21
21
  cwd: string;
22
22
  pkgDir: string;
23
23
  output: BuildOutput;
24
- /** replaceDeps configuration from sd.config.ts */
24
+ /** sd.config.ts replaceDeps 설정 */
25
25
  replaceDeps?: Record<string, string>;
26
26
  }
27
27
 
@@ -73,7 +73,7 @@ async function build(info: LibraryBuildInfo): Promise<LibraryBuildResult> {
73
73
  });
74
74
  logger.debug(`[${info.name}] library worker build 완료 (success: ${tscResult.success})`);
75
75
 
76
- // Run lint if enabled and program is available
76
+ // lint 실행 (활성화 + program 사용 가능 시)
77
77
  let lint: LintWithProgramResult | undefined;
78
78
  if (info.output.lint === true && tscResult.program != null) {
79
79
  logger.debug(`[${info.name}] lint 시작`);
@@ -102,7 +102,7 @@ async function build(info: LibraryBuildInfo): Promise<LibraryBuildResult> {
102
102
 
103
103
  const guardStartWatch = createOnceGuard("startWatch");
104
104
 
105
- // Mutable state for watch mode
105
+ // watch 모드용 가변 상태
106
106
  let watchInfo: LibraryBuildInfo | undefined;
107
107
  let watchLintRunner: LintWithProgramRunner | undefined;
108
108
  let lastSourceFilePaths: Set<string> | undefined;
@@ -128,10 +128,10 @@ async function rebuildAll(): Promise<CombinedBuildEvent> {
128
128
  includeTests: info.output.includeTests,
129
129
  });
130
130
 
131
- // Update source file paths for dependency filtering
131
+ // 의존성 필터링을 위한 소스 파일 경로 업데이트
132
132
  lastSourceFilePaths = extractSourceFilePaths(tscResult.program) ?? lastSourceFilePaths;
133
133
 
134
- // Run lint if enabled and program is available
134
+ // lint 실행 (활성화 + program 사용 가능 시)
135
135
  let lint: LintWithProgramResult | undefined;
136
136
  if (info.output.lint === true && tscResult.program != null) {
137
137
  logger.debug(`[${info.name}] lint 시작`);
@@ -160,14 +160,14 @@ async function startWatch(info: LibraryBuildInfo): Promise<void> {
160
160
  watchInfo = info;
161
161
 
162
162
  try {
163
- // Initial build
163
+ // 초기 빌드
164
164
  const initialResult = await rebuildAll();
165
165
  sender.send("build", initialResult);
166
166
 
167
- // Collect workspace dependency paths + replaceDeps
167
+ // workspace 의존성 경로 + replaceDeps 수집
168
168
  const { workspaceDeps, replaceDeps } = collectDeps(info.pkgDir, info.cwd, info.replaceDeps);
169
169
 
170
- // Start FsWatcher — own src/ + workspace deps' src/ + replaceDeps dist/
170
+ // FsWatcher 시작 자체 src/ + workspace 의존성 src/ + replaceDeps dist/
171
171
  logger.debug(`[${info.name}] FsWatcher 시작`);
172
172
  const watchPaths = [
173
173
  pathx.posixResolve(info.pkgDir, "src", "**", "*.ts"),
@@ -4,8 +4,8 @@ import { executeLint, type LintOptions, type LintResult } from "../commands/lint
4
4
  //#region Worker
5
5
 
6
6
  /**
7
- * Lint worker.
8
- * Worker to run lint in separate thread from check command and BuildOrchestrator
7
+ * lint 워커.
8
+ * check 명령어와 BuildOrchestrator에서 별도 스레드로 lint를 실행하는 워커
9
9
  */
10
10
  async function lint(options: LintOptions): Promise<LintResult> {
11
11
  return executeLint(options);
@@ -30,7 +30,7 @@ import { collectDeps } from "../utils/package-utils";
30
30
 
31
31
  applyDebugLevel();
32
32
 
33
- //#region Types (re-export for worker interface)
33
+ //#region 타입 (워커 인터페이스용 re-export)
34
34
 
35
35
  export type { NgtscBuildInfo, NgtscBuildResult, NgtscCombinedBuildEvent };
36
36
 
@@ -69,7 +69,7 @@ async function build(info: NgtscBuildInfo): Promise<NgtscBuildResult> {
69
69
  const { program, ...result } = await runNgtscBuild({ ...info, env: info.output.env });
70
70
  logger.debug(`[${info.name}] ngtsc worker build 완료 (build.success: ${result.build.success})`);
71
71
 
72
- // Run lint if enabled and program is available
72
+ // lint 실행 (활성화 + program 사용 가능 시)
73
73
  if (info.output.lint === true && program != null) {
74
74
  logger.debug(`[${info.name}] lint 시작`);
75
75
  const lintRunner = new LintWithProgramRunner({
@@ -104,11 +104,11 @@ function extractSourceFilePaths(program: ReturnType<AngularCompiler["getTsProgra
104
104
  }
105
105
 
106
106
  /**
107
- * Perform a watch build (initial or incremental) using AngularCompiler.
108
- * Returns NgtscCombinedBuildEvent for sending to the engine.
107
+ * AngularCompiler를 사용하여 watch 빌드(초기 또는 증분) 수행한다.
108
+ * 엔진에 전송할 NgtscCombinedBuildEvent 반환한다.
109
109
  *
110
- * @param affectedFileNames - When provided (watch rebuild), only these files are linted.
111
- * When undefined (initial build), all workspace files are linted.
110
+ * @param affectedFileNames - 제공 (watch 재빌드) 해당 파일만 lint 수행.
111
+ * 미제공 (초기 빌드) workspace 전체 파일을 lint 수행.
112
112
  */
113
113
  async function performWatchBuild(
114
114
  info: NgtscBuildInfo,
@@ -122,7 +122,7 @@ async function performWatchBuild(
122
122
  const pkgSrcDir = path.join(info.pkgDir, "src");
123
123
  const normalizedSrcDir = pathx.posix(pkgSrcDir);
124
124
 
125
- // Collect diagnostics — workspace scope (no package-level filtering)
125
+ // 진단 수집 — workspace 스코프 (패키지 단위 필터링 없음)
126
126
  const allDiagnostics = [...compiler.collectDiagnostics()].filter(
127
127
  (d) => isWorkspaceDiagnostic(d, info.cwd),
128
128
  );
@@ -134,7 +134,7 @@ async function performWatchBuild(
134
134
  .filter((d) => d.category === ts.DiagnosticCategory.Error)
135
135
  .map(formatDiagnosticError);
136
136
 
137
- // Emit via AngularCompiler + output-path-rewriting
137
+ // AngularCompiler로 emit + output-path-rewriting 적용
138
138
  const loadPaths = buildScssLoadPaths(info);
139
139
  const emitResults = compiler.emitAffectedFiles({
140
140
  sourceFilter: (fileName: string) =>
@@ -147,17 +147,17 @@ async function performWatchBuild(
147
147
  registry: sideEffectScssRegistry,
148
148
  });
149
149
 
150
- // Side-effect SCSS compilation (skip when no .scss/.css files changed)
150
+ // 사이드 이펙트 SCSS 컴파일 (.scss/.css 파일 변경 없으면 건너뜀)
151
151
  if (hasScssChanges) {
152
152
  compileSideEffectScss(sideEffectScssRegistry, loadPaths, scssErrors, scssDependencies);
153
153
  }
154
154
 
155
- // Global SCSS compilation
155
+ // 전역 SCSS 컴파일
156
156
  const globalScssErrors = compileGlobalScss(info.pkgDir, loadPaths);
157
157
 
158
158
  const allErrors = [...errors, ...scssErrors, ...globalScssErrors];
159
159
 
160
- // Run lint if enabled
160
+ // lint 실행 (활성화 시)
161
161
  let lint: LintWithProgramResult | undefined;
162
162
  if (info.output.lint === true) {
163
163
  logger.debug(`[${info.name}] lint 시작`);
@@ -189,7 +189,7 @@ async function startWatch(info: NgtscBuildInfo): Promise<void> {
189
189
  watchInfo = { ...info, env: info.output.env };
190
190
 
191
191
  try {
192
- // Parse tsconfig and prepare compiler options
192
+ // tsconfig 파싱 컴파일러 옵션 준비
193
193
  const parsedConfig = parseTsconfig(watchInfo.pkgDir);
194
194
  const sourceFiles = watchInfo.output.includeTests === true
195
195
  ? getPackageFiles(watchInfo.pkgDir, parsedConfig)
@@ -202,13 +202,13 @@ async function startWatch(info: NgtscBuildInfo): Promise<void> {
202
202
 
203
203
  const angularOptions = (parsedConfig.raw?.angularCompilerOptions ?? {}) as Record<string, unknown>;
204
204
 
205
- // SCSS closure variables
205
+ // SCSS 클로저 변수
206
206
  const scssErrors: string[] = [];
207
207
  const scssDependencies = new Map<string, Set<string>>();
208
208
  const loadPaths = buildScssLoadPaths(watchInfo);
209
209
  currentScssDependencies = scssDependencies;
210
210
 
211
- // Create AngularSourceFileCache + AngularCompiler
211
+ // AngularSourceFileCache + AngularCompiler 생성
212
212
  const sourceFileCache = new AngularSourceFileCache();
213
213
  const compiler = new AngularCompiler({
214
214
  rootNames: sourceFiles,
@@ -217,34 +217,34 @@ async function startWatch(info: NgtscBuildInfo): Promise<void> {
217
217
  sourceFileCache,
218
218
  transformStylesheet: createLibraryTransformStylesheet(loadPaths, scssErrors, scssDependencies),
219
219
  });
220
- // Initial build
220
+ // 초기 빌드
221
221
  await compiler.initialize();
222
222
  lastSourceFilePaths = extractSourceFilePaths(compiler.getTsProgram());
223
223
  const initialResult = await performWatchBuild(watchInfo, compiler, scssDependencies, scssErrors);
224
224
  sender.send("build", initialResult);
225
225
 
226
- // Collect workspace dependency paths + replaceDeps
226
+ // workspace 의존성 경로 + replaceDeps 수집
227
227
  const { workspaceDeps, replaceDeps } = collectDeps(
228
228
  watchInfo.pkgDir,
229
229
  watchInfo.cwd,
230
230
  watchInfo.replaceDeps,
231
231
  );
232
232
 
233
- // Start FsWatcher
233
+ // FsWatcher 시작
234
234
  logger.debug(`[${watchInfo.name}] FsWatcher 시작`);
235
235
  const watchPaths = [
236
- path.join(watchInfo.pkgDir, "src", "**", "*.{ts,scss,css}"),
237
- path.join(watchInfo.pkgDir, "scss", "**", "*.{scss,css}"),
236
+ pathx.posixResolve(watchInfo.pkgDir, "src", "**", "*.{ts,scss,css}"),
237
+ pathx.posixResolve(watchInfo.pkgDir, "scss", "**", "*.{scss,css}"),
238
238
  ...workspaceDeps.flatMap((d) => {
239
- const depDir = path.join(watchInfo!.cwd, "packages", d);
239
+ const depDir = pathx.posixResolve(watchInfo!.cwd, "packages", d);
240
240
  return [
241
- path.join(depDir, "src", "**", "*.{ts,scss,css}"),
242
- path.join(depDir, "scss", "**", "*.{scss,css}"),
241
+ pathx.posixResolve(depDir, "src", "**", "*.{ts,scss,css}"),
242
+ pathx.posixResolve(depDir, "scss", "**", "*.{scss,css}"),
243
243
  ];
244
244
  }),
245
245
  ...replaceDeps.flatMap((pkg) => [
246
- path.join(watchInfo!.cwd, "node_modules", ...pkg.split("/"), "dist", "**", "*.{js,mjs,cjs}"),
247
- path.join(watchInfo!.pkgDir, "node_modules", ...pkg.split("/"), "dist", "**", "*.{js,mjs,cjs}"),
246
+ pathx.posixResolve(watchInfo!.cwd, "node_modules", ...pkg.split("/"), "dist", "**", "*.{js,mjs,cjs}"),
247
+ pathx.posixResolve(watchInfo!.pkgDir, "node_modules", ...pkg.split("/"), "dist", "**", "*.{js,mjs,cjs}"),
248
248
  ]),
249
249
  ];
250
250
  fsWatcher = await FsWatcher.watch(watchPaths);
@@ -255,12 +255,12 @@ async function startWatch(info: NgtscBuildInfo): Promise<void> {
255
255
  (c) => c.event === "add" || c.event === "unlink",
256
256
  );
257
257
 
258
- // Collect modified files (all changed + SCSS dependency reverse-lookup)
258
+ // 변경된 파일 수집 (전체 변경 + SCSS 의존성 역방향 탐색)
259
259
  const modifiedFiles = new Set<string>();
260
260
  for (const f of changedFiles) {
261
261
  modifiedFiles.add(f.path);
262
262
 
263
- // SCSS dependency reverse-lookup
263
+ // SCSS 의존성 역방향 탐색
264
264
  if (
265
265
  (f.path.endsWith(".scss") || f.path.endsWith(".css")) &&
266
266
  currentScssDependencies != null
@@ -273,7 +273,7 @@ async function startWatch(info: NgtscBuildInfo): Promise<void> {
273
273
  }
274
274
  }
275
275
 
276
- // Dependency filter: skip rebuild if no relevant changes
276
+ // 의존성 필터: 관련 변경이 없으면 리빌드 건너뜀
277
277
  if (!hasFileAddOrRemove && lastSourceFilePaths != null) {
278
278
  const hasRelevantChange = [...modifiedFiles].some((p) =>
279
279
  lastSourceFilePaths!.has(p),
@@ -286,17 +286,17 @@ async function startWatch(info: NgtscBuildInfo): Promise<void> {
286
286
 
287
287
  sender.send("buildStart", {});
288
288
 
289
- // Clear SCSS errors for fresh rebuild
289
+ // 리빌드를 위해 SCSS 에러 초기화
290
290
  scssErrors.length = 0;
291
291
  scssDependencies.clear();
292
292
 
293
- // Incremental rebuild via AngularCompiler.update()
293
+ // AngularCompiler.update()를 통한 증분 리빌드
294
294
  const updateResult = await compiler.update(modifiedFiles);
295
295
 
296
- // Update source file paths after rebuild
296
+ // 리빌드 소스 파일 경로 업데이트
297
297
  lastSourceFilePaths = extractSourceFilePaths(compiler.getTsProgram());
298
298
 
299
- // Convert affected ts.SourceFile set to file name strings for incremental lint
299
+ // 증분 lint를 위해 영향받은 ts.SourceFile 집합을 파일명 문자열로 변환
300
300
  const affectedFileNames = new Set<string>();
301
301
  for (const sf of updateResult.affectedFiles) {
302
302
  affectedFileNames.add(pathx.posix(sf.fileName));