@simplysm/sd-cli 14.0.11 → 14.0.12

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 (263) 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 +1 -1
  5. package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
  6. package/dist/angular/vite-angular-plugin.js +60 -34
  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 +14 -2
  11. package/dist/capacitor/capacitor.d.ts.map +1 -1
  12. package/dist/capacitor/capacitor.js +131 -17
  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 +18 -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 +1 -1
  57. package/dist/engines/ViteEngine.d.ts.map +1 -1
  58. package/dist/engines/ViteEngine.js +7 -12
  59. package/dist/engines/ViteEngine.js.map +1 -1
  60. package/dist/engines/index.d.ts +5 -5
  61. package/dist/engines/index.js +5 -5
  62. package/dist/engines/types.d.ts +20 -20
  63. package/dist/engines/types.d.ts.map +1 -1
  64. package/dist/infra/ResultCollector.d.ts +9 -9
  65. package/dist/infra/ResultCollector.js +8 -8
  66. package/dist/infra/SignalHandler.d.ts +7 -7
  67. package/dist/infra/SignalHandler.js +7 -7
  68. package/dist/infra/WorkerManager.d.ts +14 -14
  69. package/dist/infra/WorkerManager.js +14 -14
  70. package/dist/orchestrators/BuildOrchestrator.d.ts +25 -25
  71. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  72. package/dist/orchestrators/BuildOrchestrator.js +29 -29
  73. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  74. package/dist/orchestrators/DevWatchOrchestrator.d.ts +7 -7
  75. package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
  76. package/dist/orchestrators/DevWatchOrchestrator.js +34 -34
  77. package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
  78. package/dist/sd-cli-entry.d.ts +2 -2
  79. package/dist/sd-cli-entry.d.ts.map +1 -1
  80. package/dist/sd-cli-entry.js +15 -8
  81. package/dist/sd-cli-entry.js.map +1 -1
  82. package/dist/sd-cli.d.ts +3 -3
  83. package/dist/sd-cli.js +16 -16
  84. package/dist/sd-cli.js.map +1 -1
  85. package/dist/sd-config.types.d.ts +105 -105
  86. package/dist/sd-config.types.d.ts.map +1 -1
  87. package/dist/utils/angular-compiler.js +5 -5
  88. package/dist/utils/angular-compiler.js.map +1 -1
  89. package/dist/utils/build-env.d.ts +1 -1
  90. package/dist/utils/build-env.js +1 -1
  91. package/dist/utils/concurrency.d.ts +7 -7
  92. package/dist/utils/concurrency.js +7 -7
  93. package/dist/utils/copy-public.d.ts +9 -9
  94. package/dist/utils/copy-public.js +17 -17
  95. package/dist/utils/copy-public.js.map +1 -1
  96. package/dist/utils/copy-src.d.ts +9 -9
  97. package/dist/utils/copy-src.js +11 -11
  98. package/dist/utils/copy-src.js.map +1 -1
  99. package/dist/utils/engine-stop.d.ts +8 -9
  100. package/dist/utils/engine-stop.d.ts.map +1 -1
  101. package/dist/utils/engine-stop.js +9 -10
  102. package/dist/utils/engine-stop.js.map +1 -1
  103. package/dist/utils/esbuild-config.d.ts +23 -23
  104. package/dist/utils/esbuild-config.d.ts.map +1 -1
  105. package/dist/utils/esbuild-config.js +25 -25
  106. package/dist/utils/esbuild-config.js.map +1 -1
  107. package/dist/utils/lint-with-program.d.ts +15 -15
  108. package/dist/utils/lint-with-program.d.ts.map +1 -1
  109. package/dist/utils/lint-with-program.js +29 -29
  110. package/dist/utils/lint-with-program.js.map +1 -1
  111. package/dist/utils/ngtsc-build-core.d.ts +8 -8
  112. package/dist/utils/ngtsc-build-core.d.ts.map +1 -1
  113. package/dist/utils/ngtsc-build-core.js +14 -14
  114. package/dist/utils/ngtsc-build-core.js.map +1 -1
  115. package/dist/utils/output-path-rewriter.d.ts +14 -14
  116. package/dist/utils/output-path-rewriter.js +18 -18
  117. package/dist/utils/output-path-rewriter.js.map +1 -1
  118. package/dist/utils/output-utils.d.ts +6 -6
  119. package/dist/utils/output-utils.js +11 -11
  120. package/dist/utils/output-utils.js.map +1 -1
  121. package/dist/utils/package-utils.d.ts +21 -21
  122. package/dist/utils/package-utils.d.ts.map +1 -1
  123. package/dist/utils/package-utils.js +56 -45
  124. package/dist/utils/package-utils.js.map +1 -1
  125. package/dist/utils/replace-deps.d.ts +25 -25
  126. package/dist/utils/replace-deps.d.ts.map +1 -1
  127. package/dist/utils/replace-deps.js +84 -65
  128. package/dist/utils/replace-deps.js.map +1 -1
  129. package/dist/utils/sd-config.d.ts +3 -3
  130. package/dist/utils/sd-config.js +3 -3
  131. package/dist/utils/tsc-build.d.ts +13 -13
  132. package/dist/utils/tsc-build.d.ts.map +1 -1
  133. package/dist/utils/tsc-build.js +9 -9
  134. package/dist/utils/tsc-build.js.map +1 -1
  135. package/dist/utils/tsconfig.d.ts +11 -9
  136. package/dist/utils/tsconfig.d.ts.map +1 -1
  137. package/dist/utils/tsconfig.js +11 -9
  138. package/dist/utils/tsconfig.js.map +1 -1
  139. package/dist/utils/typecheck-non-package.d.ts +5 -6
  140. package/dist/utils/typecheck-non-package.d.ts.map +1 -1
  141. package/dist/utils/typecheck-non-package.js +7 -8
  142. package/dist/utils/typecheck-non-package.js.map +1 -1
  143. package/dist/utils/typecheck-serialization.d.ts +8 -8
  144. package/dist/utils/typecheck-serialization.d.ts.map +1 -1
  145. package/dist/utils/typecheck-serialization.js +12 -16
  146. package/dist/utils/typecheck-serialization.js.map +1 -1
  147. package/dist/utils/vite-config.d.ts +8 -5
  148. package/dist/utils/vite-config.d.ts.map +1 -1
  149. package/dist/utils/vite-config.js +36 -29
  150. package/dist/utils/vite-config.js.map +1 -1
  151. package/dist/utils/vite-scope-watch-plugin.d.ts.map +1 -1
  152. package/dist/utils/vite-scope-watch-plugin.js +1 -1
  153. package/dist/utils/vite-scope-watch-plugin.js.map +1 -1
  154. package/dist/utils/worker-events.d.ts +12 -12
  155. package/dist/utils/worker-events.d.ts.map +1 -1
  156. package/dist/utils/worker-events.js +10 -10
  157. package/dist/utils/worker-events.js.map +1 -1
  158. package/dist/utils/worker-utils.d.ts +12 -13
  159. package/dist/utils/worker-utils.d.ts.map +1 -1
  160. package/dist/utils/worker-utils.js +12 -13
  161. package/dist/utils/worker-utils.js.map +1 -1
  162. package/dist/vitest-plugin.d.ts.map +1 -1
  163. package/dist/vitest-plugin.js +5 -7
  164. package/dist/vitest-plugin.js.map +1 -1
  165. package/dist/workers/client.worker.d.ts +4 -2
  166. package/dist/workers/client.worker.d.ts.map +1 -1
  167. package/dist/workers/client.worker.js +209 -1
  168. package/dist/workers/client.worker.js.map +1 -1
  169. package/dist/workers/library-build.worker.d.ts +1 -1
  170. package/dist/workers/library-build.worker.d.ts.map +1 -1
  171. package/dist/workers/library-build.worker.js +7 -7
  172. package/dist/workers/library-build.worker.js.map +1 -1
  173. package/dist/workers/lint.worker.d.ts +2 -2
  174. package/dist/workers/lint.worker.js +2 -2
  175. package/dist/workers/ngtsc-build.worker.js +30 -30
  176. package/dist/workers/ngtsc-build.worker.js.map +1 -1
  177. package/dist/workers/server-build.worker.d.ts +17 -17
  178. package/dist/workers/server-build.worker.d.ts.map +1 -1
  179. package/dist/workers/server-build.worker.js +46 -46
  180. package/dist/workers/server-build.worker.js.map +1 -1
  181. package/dist/workers/server-runtime.worker.d.ts +7 -7
  182. package/dist/workers/server-runtime.worker.d.ts.map +1 -1
  183. package/dist/workers/server-runtime.worker.js +17 -17
  184. package/dist/workers/server-runtime.worker.js.map +1 -1
  185. package/docs/config.md +340 -0
  186. package/docs/publish-configuration-types.md +87 -0
  187. package/docs/pwa-configuration-types.md +55 -0
  188. package/docs/vitest-plugin.md +47 -0
  189. package/package.json +9 -7
  190. package/src/angular/client-transform-stylesheet.ts +1 -1
  191. package/src/angular/vite-angular-plugin.ts +70 -37
  192. package/src/angular/vite-postcss-inline-plugin.ts +1 -1
  193. package/src/capacitor/capacitor.ts +159 -23
  194. package/src/commands/build.ts +3 -10
  195. package/src/commands/check.ts +3 -3
  196. package/src/commands/dev.ts +3 -9
  197. package/src/commands/device.ts +5 -5
  198. package/src/commands/publish.ts +30 -26
  199. package/src/commands/replace-deps.ts +3 -3
  200. package/src/commands/typecheck.ts +7 -13
  201. package/src/commands/watch.ts +9 -9
  202. package/src/electron/electron.ts +49 -4
  203. package/src/engines/BaseEngine.ts +4 -1
  204. package/src/engines/NgtscEngine.ts +7 -7
  205. package/src/engines/ServerEsbuildEngine.ts +7 -7
  206. package/src/engines/TscEngine.ts +7 -7
  207. package/src/engines/ViteEngine.ts +8 -13
  208. package/src/engines/index.ts +5 -5
  209. package/src/engines/types.ts +20 -20
  210. package/src/infra/ResultCollector.ts +9 -9
  211. package/src/infra/SignalHandler.ts +7 -7
  212. package/src/infra/WorkerManager.ts +14 -14
  213. package/src/orchestrators/BuildOrchestrator.ts +37 -37
  214. package/src/orchestrators/DevWatchOrchestrator.ts +36 -36
  215. package/src/sd-cli-entry.ts +15 -8
  216. package/src/sd-cli.ts +16 -16
  217. package/src/sd-config.types.ts +107 -107
  218. package/src/utils/angular-compiler.ts +5 -5
  219. package/src/utils/build-env.ts +1 -1
  220. package/src/utils/concurrency.ts +7 -7
  221. package/src/utils/copy-public.ts +17 -17
  222. package/src/utils/copy-src.ts +11 -11
  223. package/src/utils/engine-stop.ts +9 -10
  224. package/src/utils/esbuild-config.ts +29 -29
  225. package/src/utils/lint-with-program.ts +34 -34
  226. package/src/utils/ngtsc-build-core.ts +17 -17
  227. package/src/utils/output-path-rewriter.ts +18 -18
  228. package/src/utils/output-utils.ts +11 -11
  229. package/src/utils/package-utils.ts +57 -45
  230. package/src/utils/replace-deps.ts +92 -67
  231. package/src/utils/sd-config.ts +3 -3
  232. package/src/utils/tsc-build.ts +18 -18
  233. package/src/utils/tsconfig.ts +11 -9
  234. package/src/utils/typecheck-non-package.ts +7 -8
  235. package/src/utils/typecheck-serialization.ts +13 -15
  236. package/src/utils/vite-config.ts +45 -35
  237. package/src/utils/vite-scope-watch-plugin.ts +6 -1
  238. package/src/utils/worker-events.ts +16 -16
  239. package/src/utils/worker-utils.ts +12 -13
  240. package/src/vitest-plugin.ts +5 -8
  241. package/src/workers/client.worker.ts +236 -2
  242. package/src/workers/library-build.worker.ts +8 -8
  243. package/src/workers/lint.worker.ts +2 -2
  244. package/src/workers/ngtsc-build.worker.ts +31 -31
  245. package/src/workers/server-build.worker.ts +60 -60
  246. package/src/workers/server-runtime.worker.ts +22 -22
  247. package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +1 -0
  248. package/tests/angular/vite-angular-plugin-hmr.spec.ts +78 -0
  249. package/tests/angular/vite-angular-plugin.spec.ts +67 -0
  250. package/tests/capacitor/capacitor-build.spec.ts +6 -4
  251. package/tests/capacitor/capacitor-icon.spec.ts +7 -5
  252. package/tests/capacitor/capacitor-init.spec.ts +120 -10
  253. package/tests/capacitor/capacitor-run.spec.ts +14 -17
  254. package/tests/capacitor/capacitor-workspace.spec.ts +5 -3
  255. package/tests/commands/check.spec.ts +2 -2
  256. package/tests/commands/publish.spec.ts +2 -2
  257. package/tests/commands/typecheck.spec.ts +8 -0
  258. package/tests/electron/electron.spec.ts +12 -10
  259. package/tests/engines/base-engine.spec.ts +37 -0
  260. package/tests/engines/vite-engine.spec.ts +115 -3
  261. package/tests/utils/vite-config.spec.ts +144 -90
  262. package/tests/workers/client-worker.spec.ts +690 -0
  263. package/tests/workers/server-build-worker.spec.ts +3 -3
@@ -1,6 +1,5 @@
1
1
  import type { InlineConfig, PluginOption } from "vite";
2
2
  import path from "path";
3
- import { pathx } from "@simplysm/core-node";
4
3
  import tsconfigPaths from "vite-tsconfig-paths";
5
4
  import browserslistToEsbuild from "browserslist-to-esbuild";
6
5
  import { sdAngularPlugin } from "../angular/vite-angular-plugin.js";
@@ -36,7 +35,7 @@ export interface CreateClientViteConfigOptions {
36
35
  warnings?: string[];
37
36
  lint?: { success: boolean; errorCount: number; warningCount: number; formattedOutput: string };
38
37
  }) => void;
39
- /** Enable lint using ts.Program from compilation */
38
+ /** 컴파일의 ts.Program 사용하여 lint 실행 */
40
39
  enableLint?: boolean;
41
40
  /** replaceDeps 목록 (dev 모드에서 sdScopeWatchPlugin에 전달) */
42
41
  replaceDeps?: ScopeWatchReplaceDep[];
@@ -48,18 +47,21 @@ export interface CreateClientViteConfigOptions {
48
47
  postCssPlugins?: unknown[];
49
48
  /** polyfills 경로 배열 (transformIndexHtml로 주입) */
50
49
  polyfills?: string[];
51
- /** legacy module support (disables code splitting + replaces import.meta) */
50
+ /** 레거시 모듈 지원 (코드 스플리팅 비활성화) */
52
51
  legacyModule?: boolean;
53
52
  /** PWA 설정. false로 비활성화. 미설정 시 기본값으로 활성화 */
54
53
  pwa?: false | SdPwaConfig;
54
+ /** Vite optimizeDeps.exclude에 전달할 패키지 목록 */
55
+ exclude?: string[];
56
+ /** watch 모드 (build.watch 활성화, emptyOutDir: false) */
57
+ watch?: boolean;
55
58
  }
56
59
 
57
60
  /**
58
61
  * Client Vite 설정을 생성한다. dev/build 모드에서 공용으로 사용한다.
59
62
  *
60
- * Feature 3.1 범위: sdAngularPlugin, tsconfigPaths, env define, server/build 기본 설정
61
- * Feature 5.1: browserslist, PostCSS, polyfills
62
- * Feature 1.1: legacyModule (import.meta 치환 + inlineDynamicImports)
63
+ * Angular AOT 플러그인, tsconfigPaths, env define, server/build 기본 설정,
64
+ * browserslist, PostCSS, polyfills, legacyModule (inlineDynamicImports) 등을 통합 구성한다.
63
65
  */
64
66
  export async function createClientViteConfig(
65
67
  options: CreateClientViteConfigOptions,
@@ -112,8 +114,12 @@ export async function createClientViteConfig(
112
114
  );
113
115
  }
114
116
 
115
- // replaceDeps HMR (dev 모드만)
116
- if (options.mode === "dev" && options.replaceDeps != null && options.replaceDeps.length > 0) {
117
+ // replaceDeps HMR (dev 모드 또는 watch 모드)
118
+ if (
119
+ (options.mode === "dev" || options.watch === true) &&
120
+ options.replaceDeps != null &&
121
+ options.replaceDeps.length > 0
122
+ ) {
117
123
  plugins.push(
118
124
  sdScopeWatchPlugin({
119
125
  pkgDir: options.pkgDir,
@@ -127,7 +133,7 @@ export async function createClientViteConfig(
127
133
  const serverConfig =
128
134
  options.mode === "dev"
129
135
  ? {
130
- host: options.serverPort === 0 ? "127.0.0.1" : undefined,
136
+ host: options.serverPort === 0 ? "127.0.0.1" : "0.0.0.0",
131
137
  port: options.serverPort === 0 ? undefined : options.serverPort,
132
138
  strictPort: options.serverPort !== 0,
133
139
  }
@@ -139,6 +145,12 @@ export async function createClientViteConfig(
139
145
  ? { postcss: { plugins: options.postCssPlugins as import("postcss").AcceptedPlugin[] } }
140
146
  : undefined;
141
147
 
148
+ // optimizeDeps.exclude (사용자 지정 exclude)
149
+ const optimizeDepsConfig =
150
+ options.exclude != null && options.exclude.length > 0
151
+ ? { exclude: options.exclude }
152
+ : undefined;
153
+
142
154
  const config: InlineConfig = {
143
155
  root: options.pkgDir,
144
156
  base: `/${name}/`,
@@ -149,6 +161,15 @@ export async function createClientViteConfig(
149
161
  esbuild: {
150
162
  target: esbuildTarget,
151
163
  },
164
+ build: {
165
+ target: esbuildTarget,
166
+ },
167
+ optimizeDeps: {
168
+ ...optimizeDepsConfig,
169
+ esbuildOptions: {
170
+ target: esbuildTarget as string[],
171
+ },
172
+ },
152
173
  };
153
174
 
154
175
  // PWA (build 모드 + pwa !== false)
@@ -207,8 +228,15 @@ export async function createClientViteConfig(
207
228
  });
208
229
  }
209
230
 
210
- // legacyModule: true → 단일 번들 + import.meta 치환
231
+ // legacyModule: true → 코드 스플리팅 비활성화 + esbuild import.meta/import() 변환 활성화
211
232
  if (options.legacyModule === true) {
233
+ config.esbuild = {
234
+ ...config.esbuild,
235
+ supported: {
236
+ "import-meta": false,
237
+ "dynamic-import": false,
238
+ },
239
+ };
212
240
  config.build = {
213
241
  ...config.build,
214
242
  rollupOptions: {
@@ -217,39 +245,21 @@ export async function createClientViteConfig(
217
245
  },
218
246
  },
219
247
  };
220
-
221
- const pkgDir = options.pkgDir;
222
- const base = `/${name}/`;
223
-
224
- (config.plugins as PluginOption[]).push({
225
- name: "sd-legacy-import-meta",
226
- enforce: "post",
227
- transform(code: string, id: string) {
228
- if (!code.includes("import.meta")) return;
229
-
230
- // id(파일 경로)를 Vite 서빙 URL로 변환
231
- const relative = pathx.posix(path.relative(pkgDir, id));
232
- const moduleUrl = id.startsWith("/") || id.startsWith("\0")
233
- ? id // 가상 모듈(/@vite/client 등)은 그대로 사용
234
- : base + relative;
235
-
236
- const varName = "__sd_import_meta__";
237
- const injected = `const ${varName} = { url: new URL(${JSON.stringify(moduleUrl)}, document.baseURI).href };\n`;
238
- const replaced = code.replaceAll("import.meta", varName);
239
-
240
- return { code: injected + replaced, map: null };
241
- },
242
- });
243
248
  }
244
249
 
245
250
  // build 모드 설정
246
251
  if (options.mode === "build") {
247
- config.logLevel = "silent";
248
252
  config.build = {
249
253
  ...config.build,
250
254
  outDir: path.join(options.pkgDir, "dist"),
251
- emptyOutDir: true,
252
255
  };
256
+ if (options.watch === true) {
257
+ config.build.watch = {};
258
+ config.build.emptyOutDir = false;
259
+ } else {
260
+ config.logLevel = "silent";
261
+ config.build.emptyOutDir = true;
262
+ }
253
263
  }
254
264
 
255
265
  return config;
@@ -44,7 +44,12 @@ export function sdScopeWatchPlugin(options: SdScopeWatchPluginOptions): Plugin {
44
44
 
45
45
  const watchPaths: string[] = [];
46
46
  for (const dep of options.replaceDeps) {
47
- const distDir = path.join(dep.sourcePath, "dist");
47
+ const distDir = path.join(
48
+ options.pkgDir,
49
+ "node_modules",
50
+ ...dep.packageName.split("/"),
51
+ "dist",
52
+ );
48
53
  if (fs.existsSync(distDir)) {
49
54
  watchPaths.push(distDir);
50
55
  }
@@ -6,24 +6,24 @@ import { formatBuildMessages } from "./output-utils";
6
6
 
7
7
  const workerEventsLogger = consola.withTag("sd:cli:worker-events");
8
8
 
9
- /** Worker build completion event data */
9
+ /** 워커 빌드 완료 이벤트 데이터 */
10
10
  export interface BuildEventData {
11
11
  success: boolean;
12
12
  errors?: string[];
13
13
  warnings?: string[];
14
14
  }
15
15
 
16
- /** Worker error event data */
16
+ /** 워커 에러 이벤트 데이터 */
17
17
  export interface ErrorEventData {
18
18
  message: string;
19
19
  }
20
20
 
21
- /** Worker server ready event data */
21
+ /** 워커 서버 준비 완료 이벤트 데이터 */
22
22
  export interface ServerReadyEventData {
23
23
  port: number;
24
24
  }
25
25
 
26
- /** Server Build completion event data */
26
+ /** Server 빌드 완료 이벤트 데이터 */
27
27
  export interface ServerBuildEventData {
28
28
  success: boolean;
29
29
  mainJsPath: string;
@@ -32,7 +32,7 @@ export interface ServerBuildEventData {
32
32
  }
33
33
 
34
34
  /**
35
- * Base Worker info type
35
+ * 기본 Worker 정보 타입
36
36
  */
37
37
  export interface BaseWorkerInfo<TEvents extends Record<string, unknown> = Record<string, unknown>> {
38
38
  name: string;
@@ -46,7 +46,7 @@ export interface BaseWorkerInfo<TEvents extends Record<string, unknown> = Record
46
46
  }
47
47
 
48
48
  /**
49
- * Worker event handler options
49
+ * 워커 이벤트 핸들러 옵션
50
50
  */
51
51
  export interface WorkerEventHandlerOptions {
52
52
  resultKey: string;
@@ -55,13 +55,13 @@ export interface WorkerEventHandlerOptions {
55
55
  }
56
56
 
57
57
  /**
58
- * Register common Worker event handlers (buildStart, build, error only - serverReady not included)
58
+ * 공통 Worker 이벤트 핸들러를 등록한다 (buildStart, build, error serverReady 미포함)
59
59
  *
60
- * @param workerInfo Worker info
61
- * @param opts Handler options
62
- * @param results Result map
63
- * @param rebuildManager Rebuild manager
64
- * @returns completeTask function (saves result and signals build completion)
60
+ * @param workerInfo 워커 정보
61
+ * @param opts 핸들러 옵션
62
+ * @param results 결과
63
+ * @param rebuildManager 재빌드 매니저
64
+ * @returns completeTask 함수 (결과 저장 빌드 완료 신호)
65
65
  */
66
66
  export function registerWorkerEventHandlers(
67
67
  workerInfo: {
@@ -85,18 +85,18 @@ export function registerWorkerEventHandlers(
85
85
  workerInfo.isInitialBuild = false;
86
86
  };
87
87
 
88
- // Build start (on rebuild)
88
+ // 빌드 시작 (재빌드 )
89
89
  workerInfo.worker.on("buildStart", () => {
90
90
  if (!workerInfo.isInitialBuild) {
91
91
  workerInfo.buildResolver = rebuildManager.registerBuild(opts.resultKey, opts.listrTitle);
92
92
  }
93
93
  });
94
94
 
95
- // Build completion
95
+ // 빌드 완료
96
96
  workerInfo.worker.on("build", (data) => {
97
97
  workerEventsLogger.debug(`[${workerInfo.name}] build: success=${String(data.success)}`);
98
98
 
99
- // Print warnings
99
+ // 경고 출력
100
100
  if (data.warnings != null && data.warnings.length > 0) {
101
101
  workerEventsLogger.warn(
102
102
  formatBuildMessages(workerInfo.name, workerInfo.config.target, data.warnings),
@@ -112,7 +112,7 @@ export function registerWorkerEventHandlers(
112
112
  });
113
113
  });
114
114
 
115
- // Error
115
+ // 에러
116
116
  workerInfo.worker.on("error", (data) => {
117
117
  workerEventsLogger.debug(`[${workerInfo.name}] error: ${data.message}`);
118
118
  completeTask({
@@ -2,11 +2,11 @@ import consola, { type ConsolaInstance, LogLevels } from "consola";
2
2
  import { SdCliReporter } from "./SdCliReporter";
3
3
 
4
4
  /**
5
- * Apply sd-cli reporter and debug log level in worker threads
5
+ * 워커 스레드에서 sd-cli 리포터와 디버그 로그 레벨을 적용한다.
6
6
  *
7
- * Checks the SD_DEBUG environment variable (set by --debug flag in main process)
8
- * and applies debug log level to consola in the current worker thread.
9
- * Must be called at worker module top level.
7
+ * SD_DEBUG 환경변수(메인 프로세스의 --debug 플래그로 설정)를 확인하고
8
+ * 현재 워커 스레드의 consola 디버그 로그 레벨을 적용한다.
9
+ * 워커 모듈 최상위에서 호출해야 한다.
10
10
  */
11
11
  export function applyDebugLevel(): void {
12
12
  consola.options.reporters = [new SdCliReporter()];
@@ -16,14 +16,13 @@ export function applyDebugLevel(): void {
16
16
  }
17
17
 
18
18
  /**
19
- * Register cleanup handlers for worker process shutdown signals
19
+ * 워커 프로세스 종료 시그널에 대한 정리 핸들러를 등록한다.
20
20
  *
21
- * Registers SIGINT and SIGTERM handlers to gracefully cleanup resources
22
- * before process exit. Both handlers execute the cleanup function and
23
- * exit with code 0.
21
+ * 프로세스 종료 리소스를 정상적으로 정리하기 위해 SIGINT와 SIGTERM 핸들러를 등록한다.
22
+ * 핸들러 모두 정리 함수를 실행하고 코드 0으로 종료한다.
24
23
  *
25
- * @param cleanup - Cleanup function to execute on shutdown (sync or async)
26
- * @param logger - Consola logger instance for error logging
24
+ * @param cleanup - 종료 실행할 정리 함수 (동기 또는 비동기)
25
+ * @param logger - 에러 로깅용 consola 로거 인스턴스
27
26
  */
28
27
  export function registerCleanupHandlers(
29
28
  cleanup: () => void | Promise<void>,
@@ -45,10 +44,10 @@ export function registerCleanupHandlers(
45
44
  }
46
45
 
47
46
  /**
48
- * Create guard to prevent duplicate calls to Worker function
47
+ * Worker 함수의 중복 호출을 방지하는 가드를 생성한다.
49
48
  *
50
- * @param label - Function name to use in error message
51
- * @returns Guard function that throws error if called twice
49
+ * @param label - 에러 메시지에 사용할 함수명
50
+ * @returns 호출되면 에러를 던지는 가드 함수
52
51
  */
53
52
  export function createOnceGuard(label: string): () => void {
54
53
  let called = false;
@@ -1,6 +1,7 @@
1
1
  import type { Plugin } from "vite";
2
2
  import path from "path";
3
3
  import ts from "typescript";
4
+ import { pathx } from "@simplysm/core-node";
4
5
  import { NgtscProgram, type AngularLibraryHostExtensions } from "./utils/angular-build";
5
6
  import { compileScssString, compileScssFile } from "./utils/scss-compiler";
6
7
 
@@ -48,7 +49,7 @@ export function angularVitestPlugin(options: AngularVitestPluginOptions): Plugin
48
49
 
49
50
  const host = ts.createCompilerHost(compilerOptions);
50
51
 
51
- // AngularLibraryHostExtensions duck-typing
52
+ // AngularLibraryHostExtensions 덕 타이핑
52
53
  const hostExt = host as ts.CompilerHost & AngularLibraryHostExtensions;
53
54
  hostExt.readResource = (fileName: string) => ts.sys.readFile(fileName) ?? "";
54
55
 
@@ -95,12 +96,12 @@ export function angularVitestPlugin(options: AngularVitestPluginOptions): Plugin
95
96
  const { transformers } = program.compiler.prepareEmit();
96
97
  const tsProgram = program.getTsProgram();
97
98
 
98
- // per-file emit
99
+ // 파일별 emit
99
100
  for (const filePath of sourceFiles) {
100
101
  const sf = tsProgram.getSourceFile(filePath);
101
102
  if (sf == null) continue;
102
103
 
103
- currentSourcePath = normalizePath(filePath);
104
+ currentSourcePath = pathx.posix(filePath);
104
105
  tsProgram.emit(sf, host.writeFile, undefined, false, transformers);
105
106
  }
106
107
  },
@@ -110,7 +111,7 @@ export function angularVitestPlugin(options: AngularVitestPluginOptions): Plugin
110
111
  return undefined;
111
112
  }
112
113
 
113
- const compiled = compiledFiles.get(normalizePath(id));
114
+ const compiled = compiledFiles.get(pathx.posix(id));
114
115
  if (compiled == null) {
115
116
  return undefined;
116
117
  }
@@ -119,7 +120,3 @@ export function angularVitestPlugin(options: AngularVitestPluginOptions): Plugin
119
120
  },
120
121
  };
121
122
  }
122
-
123
- function normalizePath(p: string): string {
124
- return p.replace(/\\/g, "/");
125
- }
@@ -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,10 @@ 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[];
36
40
  }
37
41
 
38
42
  /** Client 빌드 결과 */
@@ -58,7 +62,113 @@ export interface ClientWorkerEvents extends Record<string, unknown> {
58
62
 
59
63
  const logger = consola.withTag("sd:cli:client:worker");
60
64
 
65
+ /** viteBuild({ build: { watch: {} } }) 반환 타입의 최소 인터페이스 */
66
+ interface WatcherHandle {
67
+ on(event: string, handler: (event: { code: string; error?: { message: string } }) => void): void;
68
+ close(): Promise<void>;
69
+ }
70
+
61
71
  let viteServer: ViteDevServer | undefined;
72
+ let rollupWatcher: WatcherHandle | undefined;
73
+ let legacyHttpServer: http.Server | undefined;
74
+
75
+ /** SSE 연결된 클라이언트 목록 (live reload용) */
76
+ const sseClients = new Set<http.ServerResponse>();
77
+
78
+ /** SSE 연결된 모든 클라이언트에 reload 신호를 전송한다 */
79
+ function notifyLiveReload(): void {
80
+ for (const client of sseClients) {
81
+ client.write("data: reload\n\n");
82
+ }
83
+ }
84
+
85
+
86
+ /** live reload 클라이언트 스크립트 (HTML에 주입) */
87
+ const LIVE_RELOAD_SCRIPT = `<script>(function(){var s=new EventSource("__live-reload");s.onmessage=function(){location.reload();};})()</script>`;
88
+
89
+ /**
90
+ * HTML 응답 시 live reload 스크립트를 </body> 직전에 주입한다.
91
+ */
92
+ function injectLiveReloadScript(html: string): string {
93
+ const idx = html.lastIndexOf("</body>");
94
+ if (idx !== -1) {
95
+ return html.slice(0, idx) + LIVE_RELOAD_SCRIPT + html.slice(idx);
96
+ }
97
+ return html + LIVE_RELOAD_SCRIPT;
98
+ }
99
+
100
+ /**
101
+ * legacy dev 모드용 HTTP 정적 파일 서버를 생성한다.
102
+ * dist/ 디렉토리의 파일을 서빙하고, SPA fallback + SSE live reload를 지원한다.
103
+ */
104
+ function createLegacyHttpServer(distDir: string, basePath: string): http.Server {
105
+ return http.createServer((req, res) => {
106
+ const url = (req.url ?? "/").split("?")[0];
107
+
108
+ // basePath prefix 제거
109
+ let relativePath: string;
110
+ if (url.startsWith(basePath)) {
111
+ relativePath = url.slice(basePath.length);
112
+ } else {
113
+ res.writeHead(404);
114
+ res.end("Not Found");
115
+ return;
116
+ }
117
+
118
+ // SSE live reload 엔드포인트
119
+ if (relativePath === "__live-reload" || relativePath === "/__live-reload") {
120
+ res.writeHead(200, {
121
+ "Content-Type": "text/event-stream",
122
+ "Cache-Control": "no-cache",
123
+ "Connection": "keep-alive",
124
+ });
125
+ sseClients.add(res);
126
+ req.on("close", () => {
127
+ sseClients.delete(res);
128
+ });
129
+ return;
130
+ }
131
+
132
+ // 빈 경로 또는 / → index.html
133
+ if (relativePath === "" || relativePath === "/") {
134
+ relativePath = "index.html";
135
+ }
136
+
137
+ // 선행 슬래시 제거
138
+ if (relativePath.startsWith("/")) {
139
+ relativePath = relativePath.slice(1);
140
+ }
141
+
142
+ const filePath = path.join(distDir, relativePath);
143
+ const ext = path.extname(filePath);
144
+
145
+ // 파일 존재 확인
146
+ if (fs.existsSync(filePath) && !fs.statSync(filePath).isDirectory()) {
147
+ const contentType = mime.getType(ext) ?? "application/octet-stream";
148
+ if (ext === ".html") {
149
+ // HTML: live reload 스크립트 주입
150
+ const content = injectLiveReloadScript(fs.readFileSync(filePath, "utf-8"));
151
+ res.writeHead(200, { "Content-Type": contentType });
152
+ res.end(content);
153
+ } else {
154
+ const content = fs.readFileSync(filePath);
155
+ res.writeHead(200, { "Content-Type": contentType });
156
+ res.end(content);
157
+ }
158
+ } else {
159
+ // SPA fallback: index.html 반환 (live reload 스크립트 주입)
160
+ const indexPath = path.join(distDir, "index.html");
161
+ if (fs.existsSync(indexPath)) {
162
+ const content = injectLiveReloadScript(fs.readFileSync(indexPath, "utf-8"));
163
+ res.writeHead(200, { "Content-Type": "text/html" });
164
+ res.end(content);
165
+ } else {
166
+ res.writeHead(404);
167
+ res.end("Not Found");
168
+ }
169
+ }
170
+ });
171
+ }
62
172
 
63
173
  function resolvePackageInfo(info: ClientBuildInfo): {
64
174
  tsconfigPath: string;
@@ -77,6 +187,9 @@ function resolvePackageInfo(info: ClientBuildInfo): {
77
187
  * 서버가 준비되면 serverReady 이벤트로 포트를 알린다.
78
188
  */
79
189
  async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
190
+ if (info.browserSupport?.legacyModule === true) {
191
+ return startLegacyWatch(info);
192
+ }
80
193
  logger.debug(`[${info.name}] client worker startWatch 시작 (port: ${info.port ?? "auto"})`);
81
194
  try {
82
195
  const { tsconfigPath, pkgName } = resolvePackageInfo(info);
@@ -102,6 +215,7 @@ async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
102
215
  legacyModule: info.browserSupport?.legacyModule,
103
216
  polyfills,
104
217
  pwa: info.pwa,
218
+ exclude: info.exclude,
105
219
  });
106
220
 
107
221
  logger.debug(`[${info.name}] Vite server 생성 시작`);
@@ -138,15 +252,134 @@ async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
138
252
  }
139
253
 
140
254
  /**
141
- * dev server 중지. Vite server를 정리한다.
255
+ * legacy watch 시작. Vite build --watch로 파일 변경을 감시한다.
256
+ * legacyModule: true일 때 createServer 대신 사용한다.
257
+ */
258
+ async function startLegacyWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
259
+ logger.debug(`[${info.name}] client worker startLegacyWatch 시작`);
260
+ try {
261
+ const { tsconfigPath, pkgName } = resolvePackageInfo(info);
262
+
263
+ // dist 초기화 (첫 빌드만 비움)
264
+ const distDir = path.join(info.pkgDir, "dist");
265
+ fs.rmSync(distDir, { recursive: true, force: true });
266
+
267
+ // polyfills.ts 자동 감지
268
+ const polyfillsPath = path.join(info.pkgDir, "src", "polyfills.ts");
269
+ const polyfills = fs.existsSync(polyfillsPath) ? ["./src/polyfills.ts"] : undefined;
270
+
271
+ const viteConfig = await createClientViteConfig({
272
+ pkgDir: info.pkgDir,
273
+ pkgName,
274
+ mode: "build",
275
+ tsconfigPath,
276
+ serverPort: 0,
277
+ env: info.env,
278
+ watch: true,
279
+ onBuildStart: () => sender.send("buildStart", {}),
280
+ onBuild: (result) => sender.send("build", result),
281
+ enableLint: info.enableLint,
282
+ replaceDeps: info.replaceDeps,
283
+ onScopeRebuild: () => sender.send("scopeRebuild", {}),
284
+ browserslist: info.browserSupport?.browserslist,
285
+ postCssPlugins: info.browserSupport?.postCss?.plugins,
286
+ legacyModule: info.browserSupport?.legacyModule,
287
+ polyfills,
288
+ pwa: false,
289
+ exclude: info.exclude,
290
+ });
291
+
292
+ const watcher = (await viteBuild(viteConfig)) as WatcherHandle;
293
+ rollupWatcher = watcher;
294
+
295
+ // .config.json 생성
296
+ writeConfigJson(info.pkgDir, info.configs);
297
+
298
+ // HTTP 정적 파일 서버 시작
299
+ const name = pkgName.replace(/^@[^/]+\//, "");
300
+ const basePath = `/${name}/`;
301
+ const httpServer = createLegacyHttpServer(distDir, basePath);
302
+ legacyHttpServer = httpServer;
303
+
304
+ const serverPort = await new Promise<number>((resolve, reject) => {
305
+ httpServer.listen(info.port ?? 0, "0.0.0.0", () => {
306
+ const addr = httpServer.address();
307
+ if (typeof addr === "object" && addr != null) {
308
+ resolve(addr.port);
309
+ } else {
310
+ reject(new Error("HTTP 서버 포트를 감지할 수 없습니다."));
311
+ }
312
+ });
313
+ httpServer.on("error", reject);
314
+ });
315
+
316
+ sender.send("serverReady", { port: serverPort });
317
+
318
+ // 첫 빌드 완료 대기
319
+ return await new Promise<ClientBuildResult>((resolve) => {
320
+ let firstBuildResolved = false;
321
+
322
+ watcher.on("event", (event: { code: string; error?: { message: string } }) => {
323
+ if (event.code === "END") {
324
+ if (!firstBuildResolved) {
325
+ firstBuildResolved = true;
326
+ resolve({ success: true });
327
+ } else {
328
+ // 재빌드 완료 → 브라우저 live reload
329
+ notifyLiveReload();
330
+ }
331
+ } else if (event.code === "ERROR") {
332
+ const message = event.error?.message ?? "Unknown build error";
333
+ sender.send("error", { message });
334
+ if (!firstBuildResolved) {
335
+ firstBuildResolved = true;
336
+ resolve({ success: false, errors: [message] });
337
+ }
338
+ }
339
+ });
340
+ });
341
+ } catch (err) {
342
+ const message = errNs.message(err);
343
+ sender.send("error", { message });
344
+ return { success: false, errors: [message] };
345
+ }
346
+ }
347
+
348
+ /**
349
+ * dev server 중지. Vite server 또는 RollupWatcher를 정리한다.
142
350
  */
143
351
  async function stopWatch(): Promise<void> {
144
352
  logger.debug("Vite server 정리 시작");
353
+
354
+ const watcherToClose = rollupWatcher;
355
+ rollupWatcher = undefined;
356
+ if (watcherToClose != null) {
357
+ await watcherToClose.close();
358
+ }
359
+
360
+ // SSE 클라이언트 정리
361
+ for (const client of sseClients) {
362
+ client.end();
363
+ }
364
+ sseClients.clear();
365
+
366
+ const httpServerToClose = legacyHttpServer;
367
+ legacyHttpServer = undefined;
368
+ if (httpServerToClose != null) {
369
+ await new Promise<void>((resolve, reject) => {
370
+ httpServerToClose.close((err) => {
371
+ if (err != null) reject(err);
372
+ else resolve();
373
+ });
374
+ });
375
+ }
376
+
145
377
  const serverToClose = viteServer;
146
378
  viteServer = undefined;
147
379
  if (serverToClose != null) {
148
380
  await serverToClose.close();
149
381
  }
382
+
150
383
  logger.debug("Vite server 정리 완료");
151
384
  }
152
385
 
@@ -182,6 +415,7 @@ async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
182
415
  legacyModule: info.browserSupport?.legacyModule,
183
416
  polyfills,
184
417
  pwa: info.pwa,
418
+ exclude: info.exclude,
185
419
  });
186
420
 
187
421
  await viteBuild(viteConfig);