@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
@@ -9,31 +9,31 @@ import { registerCleanupHandlers, applyDebugLevel } from "../utils/worker-utils"
9
9
  //#region Types
10
10
 
11
11
  /**
12
- * Server runtime start info
12
+ * 서버 런타임 시작 정보
13
13
  */
14
14
  export interface ServerRuntimeStartInfo {
15
15
  mainJsPath: string;
16
- /** Client Vite dev server ports for @fastify/http-proxy registration */
16
+ /** @fastify/http-proxy 등록을 위한 Client Vite dev server 포트 */
17
17
  clientPorts?: Record<string, number>;
18
18
  env?: Record<string, string>;
19
19
  }
20
20
 
21
21
  /**
22
- * Server ready event
22
+ * 서버 준비 완료 이벤트
23
23
  */
24
24
  export interface ServerRuntimeReadyEvent {
25
25
  port: number;
26
26
  }
27
27
 
28
28
  /**
29
- * Error event
29
+ * 에러 이벤트
30
30
  */
31
31
  export interface ServerRuntimeErrorEvent {
32
32
  message: string;
33
33
  }
34
34
 
35
35
  /**
36
- * Worker event types
36
+ * 워커 이벤트 타입
37
37
  */
38
38
  export interface ServerRuntimeWorkerEvents extends Record<string, unknown> {
39
39
  serverReady: ServerRuntimeReadyEvent;
@@ -46,11 +46,11 @@ applyDebugLevel();
46
46
 
47
47
  const logger = consola.withTag("sd:cli:server-runtime:worker");
48
48
 
49
- /** Server instance (to be cleaned up) */
49
+ /** 서버 인스턴스 (정리 대상) */
50
50
  let serverInstance: { close: () => Promise<void> } | undefined;
51
51
 
52
52
  /**
53
- * Clean up resources
53
+ * 리소스 정리
54
54
  */
55
55
  async function cleanup(): Promise<void> {
56
56
  const server = serverInstance;
@@ -60,15 +60,15 @@ async function cleanup(): Promise<void> {
60
60
  }
61
61
  }
62
62
 
63
- // Catch runtime errors that occur after server listen() and send them as a custom "error" event
64
- // (Without this handler, the worker will crash but dev.ts's buildResolver won't be called, causing listr to hang)
63
+ // 서버 listen() 이후 발생하는 런타임 에러를 잡아 커스텀 "error" 이벤트로 전송
64
+ // ( 핸들러 없이는 워커가 크래시해도 빌드 Promise가 resolve되지 않아 프로세스가 중단될 있다)
65
65
  process.on("uncaughtException", (err) => {
66
66
  logger.error("서버 런타임 미처리 에러", err);
67
67
  sender.send("error", {
68
68
  message: errNs.message(err),
69
69
  });
70
- // Allow event to be sent before exit
71
- setTimeout(() => process.exit(1), 100);
70
+ // 이벤트 전송 종료할 있도록 대기
71
+ setTimeout(() => process.exit(1), 500);
72
72
  });
73
73
 
74
74
  process.on("unhandledRejection", (reason) => {
@@ -76,14 +76,14 @@ process.on("unhandledRejection", (reason) => {
76
76
  sender.send("error", {
77
77
  message: errNs.message(reason),
78
78
  });
79
- // Allow event to be sent before exit
80
- setTimeout(() => process.exit(1), 100);
79
+ // 이벤트 전송 종료할 있도록 대기
80
+ setTimeout(() => process.exit(1), 500);
81
81
  });
82
82
 
83
83
  registerCleanupHandlers(cleanup, logger);
84
84
 
85
85
  /**
86
- * Check if a port is available for use
86
+ * 포트가 사용 가능한지 확인한다
87
87
  */
88
88
  function isPortAvailable(port: number): Promise<boolean> {
89
89
  return new Promise((resolve) => {
@@ -97,7 +97,7 @@ function isPortAvailable(port: number): Promise<boolean> {
97
97
  }
98
98
 
99
99
  /**
100
- * Find and return an available port starting from the specified port
100
+ * 지정된 포트부터 사용 가능한 포트를 찾아 반환한다
101
101
  */
102
102
  async function findAvailablePort(startPort: number, maxRetries = 20): Promise<number> {
103
103
  for (let i = 0; i < maxRetries; i++) {
@@ -112,21 +112,21 @@ async function findAvailablePort(startPort: number, maxRetries = 20): Promise<nu
112
112
  }
113
113
 
114
114
  /**
115
- * Start Server Runtime
116
- * Import main.js, then listen
115
+ * 서버 런타임 시작
116
+ * main.js import한 listen 수행
117
117
  */
118
118
  async function start(info: ServerRuntimeStartInfo): Promise<void> {
119
119
  try {
120
120
  const startTime = performance.now();
121
121
 
122
- // Inject environment variables into process.env before importing main.js
122
+ // main.js import 전에 환경변수를 process.env 주입
123
123
  if (info.env != null) {
124
124
  for (const [key, value] of Object.entries(info.env)) {
125
125
  process.env[key] = value;
126
126
  }
127
127
  }
128
128
 
129
- // Import main.js (must export a server instance)
129
+ // main.js import (서버 인스턴스를 export해야 )
130
130
  logger.debug("main.js 임포트 중...");
131
131
  let stepStart = performance.now();
132
132
  const module = await import(pathToFileURL(info.mainJsPath).href);
@@ -137,10 +137,10 @@ async function start(info: ServerRuntimeStartInfo): Promise<void> {
137
137
  throw new Error("main.js must export a server instance.");
138
138
  }
139
139
 
140
- // Save server instance (for cleanup)
140
+ // 서버 인스턴스 저장 (정리용)
141
141
  serverInstance = server;
142
142
 
143
- // Register client proxies (before listen)
143
+ // 클라이언트 프록시 등록 (listen )
144
144
  if (info.clientPorts != null && Object.keys(info.clientPorts).length > 0) {
145
145
  for (const [name, port] of Object.entries(info.clientPorts)) {
146
146
  logger.debug(`프록시 등록: /${name} → http://127.0.0.1:${String(port)}`);
@@ -153,7 +153,7 @@ async function start(info: ServerRuntimeStartInfo): Promise<void> {
153
153
  }
154
154
  }
155
155
 
156
- // Find available port (auto-increment on port conflict)
156
+ // 사용 가능한 포트 탐색 (포트 충돌 자동 증가)
157
157
  logger.debug("사용 가능한 포트 탐색 중...");
158
158
  stepStart = performance.now();
159
159
  const originalPort = server.options.port;
@@ -195,6 +195,7 @@ describe("sdAngularPlugin HMR fallback", () => {
195
195
  const middlewares: Array<(req: IncomingMessage, res: ServerResponse, next: () => void) => void> =
196
196
  [];
197
197
  const mockServer = {
198
+ config: { base: "/" },
198
199
  middlewares: {
199
200
  use: (fn: (req: IncomingMessage, res: ServerResponse, next: () => void) => void) => {
200
201
  middlewares.push(fn);
@@ -17,6 +17,7 @@ describe("sdAngularPlugin HMR + component-middleware", () => {
17
17
  const middlewares: Array<(req: IncomingMessage, res: ServerResponse, next: () => void) => void> =
18
18
  [];
19
19
  const mockServer = {
20
+ config: { base: "/" },
20
21
  middlewares: {
21
22
  use: (fn: (req: IncomingMessage, res: ServerResponse, next: () => void) => void) => {
22
23
  middlewares.push(fn);
@@ -72,6 +73,7 @@ describe("sdAngularPlugin HMR + component-middleware", () => {
72
73
  const middlewares: Array<(req: IncomingMessage, res: ServerResponse, next: () => void) => void> =
73
74
  [];
74
75
  const mockServer = {
76
+ config: { base: "/" },
75
77
  middlewares: {
76
78
  use: (fn: (req: IncomingMessage, res: ServerResponse, next: () => void) => void) => {
77
79
  middlewares.push(fn);
@@ -101,6 +103,7 @@ describe("sdAngularPlugin HMR + component-middleware", () => {
101
103
  const middlewares: Array<(req: IncomingMessage, res: ServerResponse, next: () => void) => void> =
102
104
  [];
103
105
  const mockServer = {
106
+ config: { base: "/" },
104
107
  middlewares: {
105
108
  use: (fn: (req: IncomingMessage, res: ServerResponse, next: () => void) => void) => {
106
109
  middlewares.push(fn);
@@ -138,6 +141,7 @@ describe("sdAngularPlugin HMR + component-middleware", () => {
138
141
  const middlewares: Array<(req: IncomingMessage, res: ServerResponse, next: () => void) => void> =
139
142
  [];
140
143
  const mockServer = {
144
+ config: { base: "/" },
141
145
  middlewares: {
142
146
  use: (fn: (req: IncomingMessage, res: ServerResponse, next: () => void) => void) => {
143
147
  middlewares.push(fn);
@@ -177,6 +181,7 @@ describe("sdAngularPlugin HMR + component-middleware", () => {
177
181
  const middlewares: Array<(req: IncomingMessage, res: ServerResponse, next: () => void) => void> =
178
182
  [];
179
183
  const mockServer = {
184
+ config: { base: "/" },
180
185
  middlewares: {
181
186
  use: (fn: (req: IncomingMessage, res: ServerResponse, next: () => void) => void) => {
182
187
  middlewares.push(fn);
@@ -223,4 +228,77 @@ describe("sdAngularPlugin HMR + component-middleware", () => {
223
228
 
224
229
  await (plugin as any).buildEnd?.call({});
225
230
  });
231
+
232
+ // Acceptance: base path가 포함된 /@ng/component 요청도 정상 응답한다
233
+ it("serves /@ng/component requests with base path prefix", async () => {
234
+ const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
235
+ await (plugin as any).buildStart?.call({});
236
+
237
+ const middlewares: Array<(req: IncomingMessage, res: ServerResponse, next: () => void) => void> =
238
+ [];
239
+ const mockServer = {
240
+ config: { base: "/client-pda/" },
241
+ middlewares: {
242
+ use: (fn: (req: IncomingMessage, res: ServerResponse, next: () => void) => void) => {
243
+ middlewares.push(fn);
244
+ },
245
+ },
246
+ httpServer: { on: vi.fn() },
247
+ };
248
+ (plugin as any).configureServer?.(mockServer);
249
+
250
+ const middleware = middlewares[0];
251
+ // 실제 브라우저에서는 /client-pda/src/services/@ng/component 형태로 요청됨
252
+ const mockReq = {
253
+ url: "/client-pda/src/services/@ng/component?c=testId",
254
+ } as IncomingMessage;
255
+
256
+ let statusCode: number | undefined;
257
+ let headers: Record<string, string> = {};
258
+ const mockRes = {
259
+ writeHead(code: number, hdrs: Record<string, string>) {
260
+ statusCode = code;
261
+ headers = hdrs;
262
+ },
263
+ end: vi.fn(),
264
+ } as unknown as ServerResponse;
265
+
266
+ const next = vi.fn();
267
+ middleware(mockReq, mockRes, next);
268
+
269
+ expect(statusCode).toBe(200);
270
+ expect(headers["Content-Type"]).toBe("text/javascript");
271
+ expect(next).not.toHaveBeenCalled();
272
+
273
+ await (plugin as any).buildEnd?.call({});
274
+ });
275
+
276
+ // Acceptance: base path가 있지만 @ng/component가 아닌 요청은 next()로 통과
277
+ it("passes through non-/@ng/component requests with base path", async () => {
278
+ const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
279
+ await (plugin as any).buildStart?.call({});
280
+
281
+ const middlewares: Array<(req: IncomingMessage, res: ServerResponse, next: () => void) => void> =
282
+ [];
283
+ const mockServer = {
284
+ config: { base: "/client-pda/" },
285
+ middlewares: {
286
+ use: (fn: (req: IncomingMessage, res: ServerResponse, next: () => void) => void) => {
287
+ middlewares.push(fn);
288
+ },
289
+ },
290
+ httpServer: { on: vi.fn() },
291
+ };
292
+ (plugin as any).configureServer?.(mockServer);
293
+
294
+ const middleware = middlewares[0];
295
+ const mockReq = { url: "/client-pda/other-path" } as IncomingMessage;
296
+ const mockRes = {} as ServerResponse;
297
+ const next = vi.fn();
298
+
299
+ middleware(mockReq, mockRes, next);
300
+ expect(next).toHaveBeenCalled();
301
+
302
+ await (plugin as any).buildEnd?.call({});
303
+ });
226
304
  });
@@ -192,6 +192,73 @@ describe("sdAngularPlugin", () => {
192
192
  // (in real use, Vite server close triggers this)
193
193
  });
194
194
 
195
+ // Scenario: optimizeDeps에 Angular Linker esbuild 플러그인이 등록된다 (Feature 1.1 Angular Linker)
196
+ it("registers angular-vite-optimize-deps esbuild plugin in optimizeDeps config", () => {
197
+ const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
198
+ const config = (plugin as any).config?.();
199
+
200
+ const esbuildPlugins = config?.optimizeDeps?.esbuildOptions?.plugins as
201
+ | { name: string }[]
202
+ | undefined;
203
+ expect(esbuildPlugins).toBeDefined();
204
+ expect(esbuildPlugins!.some((p) => p.name === "angular-vite-optimize-deps")).toBe(true);
205
+ });
206
+
207
+ // Scenario: .mjs 파일이 JavaScriptTransformer를 통과한다 (Feature 1.1 Angular Linker)
208
+ it("transforms .mjs files through JavaScriptTransformer", async () => {
209
+ const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
210
+ await (plugin as any).buildStart?.call({});
211
+
212
+ const mjsCode = "export const x = 1;";
213
+ const result = await (plugin as any).transform?.call(
214
+ {},
215
+ mjsCode,
216
+ "/some/library/module.mjs",
217
+ );
218
+
219
+ // .mjs 파일은 transform 결과를 반환해야 한다 (undefined가 아님)
220
+ expect(result).toBeDefined();
221
+ expect(result.code).toBeDefined();
222
+ expect(typeof result.code).toBe("string");
223
+
224
+ await (plugin as any).buildEnd?.call({});
225
+ });
226
+
227
+ // Scenario: .js 파일이 JavaScriptTransformer를 통과한다 (Feature 1.1 Angular Linker)
228
+ it("transforms .js files through JavaScriptTransformer", async () => {
229
+ const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
230
+ await (plugin as any).buildStart?.call({});
231
+
232
+ const jsCode = "export const y = 2;";
233
+ const result = await (plugin as any).transform?.call(
234
+ {},
235
+ jsCode,
236
+ "/some/library/module.js",
237
+ );
238
+
239
+ expect(result).toBeDefined();
240
+ expect(result.code).toBeDefined();
241
+ expect(typeof result.code).toBe("string");
242
+
243
+ await (plugin as any).buildEnd?.call({});
244
+ });
245
+
246
+ // Scenario: 비대상 파일(.css 등)은 transform하지 않는다 (Feature 1.1 Angular Linker)
247
+ it("returns undefined for non-JS files like .css", async () => {
248
+ const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
249
+ await (plugin as any).buildStart?.call({});
250
+
251
+ const result = await (plugin as any).transform?.call(
252
+ {},
253
+ "body { color: red; }",
254
+ "/some/styles.css",
255
+ );
256
+
257
+ expect(result).toBeUndefined();
258
+
259
+ await (plugin as any).buildEnd?.call({});
260
+ });
261
+
195
262
  // Scenario: Angular .ts 파일 transform
196
263
  it("transforms emitted .ts files with compiled JS", async () => {
197
264
  const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: false });
@@ -27,8 +27,8 @@ vi.mock("@simplysm/core-node", () => ({
27
27
  copy: mockFsxCopy,
28
28
  },
29
29
  cpx: {
30
- exec: mockCpxExec,
31
- execSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
30
+ spawn: mockCpxSpawn,
31
+ spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
32
32
  },
33
33
  pathx: {
34
34
  posixResolve: (...args: string[]) => path.resolve(...args).replace(/\\/g, "/"),
@@ -46,7 +46,7 @@ vi.mock("@simplysm/core-common", () => ({
46
46
 
47
47
  // cpx mock (was execa)
48
48
  const execaCalls: { command: string; args: string[] }[] = [];
49
- const mockCpxExec = vi.fn((...args: unknown[]) => {
49
+ const mockCpxSpawn = vi.fn((...args: unknown[]) => {
50
50
  execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
51
51
  return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
52
52
  });
@@ -82,7 +82,9 @@ vi.mock("consola", () => ({
82
82
  warn: mockLoggerWarn,
83
83
  success: mockLoggerSuccess,
84
84
  }),
85
+ level: 0,
85
86
  },
87
+ LogLevels: { debug: 4 },
86
88
  }));
87
89
 
88
90
  //#endregion
@@ -270,7 +272,7 @@ describe("Capacitor 빌드", () => {
270
272
 
271
273
  // cap copy가 gradlew보다 먼저 실행되는지 확인
272
274
  const capCopyIndex = execaCalls.findIndex(
273
- (c) => c.command === "npx" && c.args.includes("cap") && c.args.includes("copy"),
275
+ (c) => c.command === "pnpm" && c.args.includes("cap") && c.args.includes("copy"),
274
276
  );
275
277
  const gradlewIndex = execaCalls.findIndex((c) => c.command.includes("gradlew"));
276
278
  expect(capCopyIndex).toBeGreaterThanOrEqual(0);
@@ -25,8 +25,8 @@ vi.mock("@simplysm/core-node", () => ({
25
25
  glob: mockFsxGlob,
26
26
  },
27
27
  cpx: {
28
- exec: mockCpxExec,
29
- execSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
28
+ spawn: mockCpxSpawn,
29
+ spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
30
30
  },
31
31
  pathx: {
32
32
  posixResolve: (...args: string[]) => path.resolve(...args).replace(/\\/g, "/"),
@@ -44,7 +44,7 @@ vi.mock("@simplysm/core-common", () => ({
44
44
 
45
45
  // cpx mock (was execa)
46
46
  const execaCalls: { command: string; args: string[] }[] = [];
47
- const mockCpxExec = vi.fn((...args: unknown[]) => {
47
+ const mockCpxSpawn = vi.fn((...args: unknown[]) => {
48
48
  execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
49
49
  return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
50
50
  });
@@ -82,7 +82,9 @@ vi.mock("consola", () => ({
82
82
  warn: mockLoggerWarn,
83
83
  success: mockLoggerSuccess,
84
84
  }),
85
+ level: 0,
85
86
  },
87
+ LogLevels: { debug: 4 },
86
88
  }));
87
89
 
88
90
  //#endregion
@@ -178,7 +180,7 @@ describe("Capacitor 아이콘 처리", () => {
178
180
 
179
181
  // capacitor-assets generate가 실행되었는지 확인
180
182
  const assetsCmd = execaCalls.find(
181
- (c) => c.command === "npx" && c.args.includes("capacitor-assets"),
183
+ (c) => c.command === "pnpm" && c.args.includes("capacitor-assets"),
182
184
  );
183
185
  expect(assetsCmd).toBeDefined();
184
186
 
@@ -202,7 +204,7 @@ describe("Capacitor 아이콘 처리", () => {
202
204
 
203
205
  // capacitor-assets generate가 실행되지 않아야 한다
204
206
  const assetsCmd = execaCalls.find(
205
- (c) => c.command === "npx" && c.args.includes("capacitor-assets"),
207
+ (c) => c.command === "pnpm" && c.args.includes("capacitor-assets"),
206
208
  );
207
209
  expect(assetsCmd).toBeUndefined();
208
210
  });
@@ -26,8 +26,8 @@ vi.mock("@simplysm/core-node", () => ({
26
26
  copy: mockFsxCopy,
27
27
  },
28
28
  cpx: {
29
- exec: mockCpxExec,
30
- execSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
29
+ spawn: mockCpxSpawn,
30
+ spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
31
31
  },
32
32
  pathx: {
33
33
  posixResolve: (...args: string[]) => path.resolve(...args).replace(/\\/g, "/"),
@@ -43,7 +43,7 @@ vi.mock("@simplysm/core-common", () => ({
43
43
  }));
44
44
 
45
45
  const execaCalls: { command: string; args: string[] }[] = [];
46
- const mockCpxExec = vi.fn((...args: unknown[]) => {
46
+ const mockCpxSpawn = vi.fn((...args: unknown[]) => {
47
47
  execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
48
48
  return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
49
49
  });
@@ -71,8 +71,10 @@ const mockLoggerDebug = vi.fn();
71
71
  const mockLoggerWarn = vi.fn();
72
72
  vi.mock("consola", () => ({
73
73
  consola: {
74
+ level: 0,
74
75
  withTag: () => ({ debug: mockLoggerDebug, warn: mockLoggerWarn }),
75
76
  },
77
+ LogLevels: { debug: 4 },
76
78
  }));
77
79
 
78
80
  //#endregion
@@ -131,6 +133,22 @@ function setupDefaultMocks() {
131
133
  if (p.includes("gradle.properties")) {
132
134
  return "org.gradle.jvmargs=-Xmx2048m";
133
135
  }
136
+ if (p.includes("styles.xml")) {
137
+ return `<?xml version="1.0" encoding="utf-8"?>
138
+ <resources>
139
+ <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
140
+ <item name="colorPrimary">@color/colorPrimary</item>
141
+ </style>
142
+ <style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
143
+ <item name="windowActionBar">false</item>
144
+ <item name="windowNoTitle">true</item>
145
+ <item name="android:background">@null</item>
146
+ </style>
147
+ <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
148
+ <item name="android:background">@drawable/splash</item>
149
+ </style>
150
+ </resources>`;
151
+ }
134
152
  return "";
135
153
  });
136
154
 
@@ -197,7 +215,7 @@ describe("Capacitor 초기화", () => {
197
215
  setupDefaultMocks();
198
216
  });
199
217
 
200
- it("최초 초기화: npm install, cap init, cap add android를 실행한다", async () => {
218
+ it("최초 초기화: pnpm install, cap init, cap add android를 실행한다", async () => {
201
219
  let androidAdded = false;
202
220
  mockFsxExists.mockImplementation((p: string) => {
203
221
  const n = p.replace(/\\/g, "/");
@@ -220,18 +238,18 @@ describe("Capacitor 초기화", () => {
220
238
  });
221
239
  await cap.initialize();
222
240
 
223
- expect(execaCalls.some((c) => c.command === "npm" && c.args.includes("install"))).toBe(true);
241
+ expect(execaCalls.some((c) => c.command === "pnpm" && c.args.includes("install"))).toBe(true);
224
242
  expect(
225
- execaCalls.some((c) => c.command === "npx" && c.args.includes("cap") && c.args.includes("init")),
243
+ execaCalls.some((c) => c.command === "pnpm" && c.args.includes("cap") && c.args.includes("init")),
226
244
  ).toBe(true);
227
245
  expect(
228
246
  execaCalls.some(
229
- (c) => c.command === "npx" && c.args.includes("cap") && c.args.includes("add"),
247
+ (c) => c.command === "pnpm" && c.args.includes("cap") && c.args.includes("add"),
230
248
  ),
231
249
  ).toBe(true);
232
250
  });
233
251
 
234
- it("재초기화: 설정 미변경 시 npm install을 건너뛴다", async () => {
252
+ it("재초기화: 설정 미변경 시 pnpm install을 건너뛴다", async () => {
235
253
  const { Capacitor } = await import("../../src/capacitor/capacitor.js");
236
254
  const cap = await Capacitor.create(PKG_PATH, {
237
255
  appId: "com.test.app",
@@ -240,10 +258,10 @@ describe("Capacitor 초기화", () => {
240
258
  });
241
259
  await cap.initialize();
242
260
 
243
- expect(execaCalls.some((c) => c.command === "npm" && c.args.includes("install"))).toBe(false);
261
+ expect(execaCalls.some((c) => c.command === "pnpm" && c.args.includes("install"))).toBe(false);
244
262
  });
245
263
 
246
- it("플러그인 추가: package.json에 플러그인을 추가하고 npm install을 실행한다", async () => {
264
+ it("플러그인 추가: package.json에 플러그인을 추가하고 pnpm install을 실행한다", async () => {
247
265
  const { Capacitor } = await import("../../src/capacitor/capacitor.js");
248
266
 
249
267
  // 클라이언트 패키지의 deps에 플러그인 포함
@@ -531,6 +549,98 @@ describe("Android 네이티브 설정", () => {
531
549
  expect(manifestWrite).toBeDefined();
532
550
  });
533
551
 
552
+ it("styles.xml의 Theme.SplashScreen parent를 Theme.AppCompat.DayNight.NoActionBar로 변경한다", async () => {
553
+ const { Capacitor } = await import("../../src/capacitor/capacitor.js");
554
+ const cap = await Capacitor.create(PKG_PATH, {
555
+ appId: "com.test.app",
556
+ appName: "Test App",
557
+ platform: { android: {} },
558
+ });
559
+ await cap.initialize();
560
+
561
+ const writeCalls = mockFsxWrite.mock.calls;
562
+ const stylesWrite = writeCalls.find(
563
+ (call) =>
564
+ typeof call[0] === "string" &&
565
+ call[0].includes("styles.xml") &&
566
+ typeof call[1] === "string" &&
567
+ call[1].includes('parent="Theme.AppCompat.DayNight.NoActionBar"'),
568
+ );
569
+ expect(stylesWrite).toBeDefined();
570
+ // android:background는 유지
571
+ expect((stylesWrite![1] as string)).toContain("@drawable/splash");
572
+ // Theme.SplashScreen은 제거됨
573
+ expect((stylesWrite![1] as string)).not.toContain('parent="Theme.SplashScreen"');
574
+ });
575
+
576
+ it("이미 변경된 styles.xml은 재변경하지 않는다", async () => {
577
+ mockFsxRead.mockImplementation((p: string) => {
578
+ if (p.includes("styles.xml")) {
579
+ return `<?xml version="1.0" encoding="utf-8"?>
580
+ <resources>
581
+ <style name="AppTheme.NoActionBarLaunch" parent="Theme.AppCompat.DayNight.NoActionBar">
582
+ <item name="android:background">@drawable/splash</item>
583
+ </style>
584
+ </resources>`;
585
+ }
586
+ if (p.includes("AndroidManifest.xml")) {
587
+ return '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n<application>\n<activity android:name=".MainActivity">\n</activity>\n</application>\n</manifest>';
588
+ }
589
+ if (p.includes("build.gradle")) {
590
+ return `android {
591
+ defaultConfig {
592
+ versionCode 1
593
+ versionName "1.0"
594
+ minSdkVersion rootProject.ext.minSdkVersion
595
+ targetSdkVersion rootProject.ext.targetSdkVersion
596
+ }
597
+ buildTypes { release { } }
598
+ }`;
599
+ }
600
+ if (p.includes("gradle.properties")) {
601
+ return "org.gradle.jvmargs=-Xmx2048m";
602
+ }
603
+ return "";
604
+ });
605
+
606
+ const { Capacitor } = await import("../../src/capacitor/capacitor.js");
607
+ const cap = await Capacitor.create(PKG_PATH, {
608
+ appId: "com.test.app",
609
+ appName: "Test App",
610
+ platform: { android: {} },
611
+ });
612
+ await cap.initialize();
613
+
614
+ const writeCalls = mockFsxWrite.mock.calls;
615
+ const stylesWrite = writeCalls.find(
616
+ (call) =>
617
+ typeof call[0] === "string" &&
618
+ call[0].includes("styles.xml"),
619
+ );
620
+ expect(stylesWrite).toBeUndefined();
621
+ });
622
+
623
+ it("styles.xml이 없으면 warn을 출력한다", async () => {
624
+ mockFsxExists.mockImplementation((p: string) => {
625
+ const n = p.replace(/\\/g, "/");
626
+ if (n.includes(".capacitor.lock")) return false;
627
+ if (n.includes("styles.xml")) return false;
628
+ return true;
629
+ });
630
+
631
+ const { Capacitor } = await import("../../src/capacitor/capacitor.js");
632
+ const cap = await Capacitor.create(PKG_PATH, {
633
+ appId: "com.test.app",
634
+ appName: "Test App",
635
+ platform: { android: {} },
636
+ });
637
+ await cap.initialize();
638
+
639
+ expect(mockLoggerWarn).toHaveBeenCalledWith(
640
+ expect.stringContaining("styles.xml"),
641
+ );
642
+ });
643
+
534
644
  it("application 태그에 커스텀 속성을 추가한다", async () => {
535
645
  const { Capacitor } = await import("../../src/capacitor/capacitor.js");
536
646
  const cap = await Capacitor.create(PKG_PATH, {