@simplysm/sd-cli 14.0.15 → 14.0.17

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 (216) hide show
  1. package/README.md +4 -3
  2. package/dist/angular/client-transform-stylesheet.d.ts +2 -0
  3. package/dist/angular/client-transform-stylesheet.d.ts.map +1 -1
  4. package/dist/angular/client-transform-stylesheet.js +88 -2
  5. package/dist/angular/client-transform-stylesheet.js.map +1 -1
  6. package/dist/angular/vite-angular-plugin.d.ts +7 -0
  7. package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
  8. package/dist/angular/vite-angular-plugin.js +78 -16
  9. package/dist/angular/vite-angular-plugin.js.map +1 -1
  10. package/dist/capacitor/capacitor.d.ts.map +1 -1
  11. package/dist/capacitor/capacitor.js +24 -13
  12. package/dist/capacitor/capacitor.js.map +1 -1
  13. package/dist/commands/check.d.ts.map +1 -1
  14. package/dist/commands/check.js +8 -9
  15. package/dist/commands/check.js.map +1 -1
  16. package/dist/commands/device.d.ts +1 -1
  17. package/dist/commands/device.d.ts.map +1 -1
  18. package/dist/commands/device.js +61 -12
  19. package/dist/commands/device.js.map +1 -1
  20. package/dist/commands/lint.d.ts +0 -1
  21. package/dist/commands/lint.d.ts.map +1 -1
  22. package/dist/commands/lint.js +2 -3
  23. package/dist/commands/lint.js.map +1 -1
  24. package/dist/commands/publish.js +3 -3
  25. package/dist/commands/publish.js.map +1 -1
  26. package/dist/commands/replace-deps.js +1 -1
  27. package/dist/commands/replace-deps.js.map +1 -1
  28. package/dist/commands/typecheck.d.ts.map +1 -1
  29. package/dist/commands/typecheck.js +1 -2
  30. package/dist/commands/typecheck.js.map +1 -1
  31. package/dist/electron/electron.d.ts +3 -2
  32. package/dist/electron/electron.d.ts.map +1 -1
  33. package/dist/electron/electron.js +54 -31
  34. package/dist/electron/electron.js.map +1 -1
  35. package/dist/engines/BaseEngine.js +1 -1
  36. package/dist/engines/BaseEngine.js.map +1 -1
  37. package/dist/engines/NgtscEngine.d.ts.map +1 -1
  38. package/dist/engines/NgtscEngine.js +0 -1
  39. package/dist/engines/NgtscEngine.js.map +1 -1
  40. package/dist/engines/ServerEsbuildEngine.d.ts.map +1 -1
  41. package/dist/engines/ServerEsbuildEngine.js +0 -1
  42. package/dist/engines/ServerEsbuildEngine.js.map +1 -1
  43. package/dist/engines/TscEngine.d.ts.map +1 -1
  44. package/dist/engines/TscEngine.js +0 -1
  45. package/dist/engines/TscEngine.js.map +1 -1
  46. package/dist/engines/ViteEngine.d.ts.map +1 -1
  47. package/dist/engines/ViteEngine.js +10 -1
  48. package/dist/engines/ViteEngine.js.map +1 -1
  49. package/dist/engines/index.d.ts +0 -10
  50. package/dist/engines/index.d.ts.map +1 -1
  51. package/dist/engines/index.js +0 -5
  52. package/dist/engines/index.js.map +1 -1
  53. package/dist/engines/types.d.ts +0 -1
  54. package/dist/engines/types.d.ts.map +1 -1
  55. package/dist/infra/SignalHandler.d.ts +1 -6
  56. package/dist/infra/SignalHandler.d.ts.map +1 -1
  57. package/dist/infra/SignalHandler.js +4 -13
  58. package/dist/infra/SignalHandler.js.map +1 -1
  59. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  60. package/dist/orchestrators/BuildOrchestrator.js +7 -12
  61. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  62. package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
  63. package/dist/orchestrators/DevWatchOrchestrator.js +18 -11
  64. package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
  65. package/dist/sd-cli-entry.d.ts +0 -1
  66. package/dist/sd-cli-entry.d.ts.map +1 -1
  67. package/dist/sd-cli-entry.js +13 -16
  68. package/dist/sd-cli-entry.js.map +1 -1
  69. package/dist/sd-cli.js +1 -1
  70. package/dist/sd-cli.js.map +1 -1
  71. package/dist/sd-config.types.d.ts +12 -2
  72. package/dist/sd-config.types.d.ts.map +1 -1
  73. package/dist/utils/angular-compiler.d.ts.map +1 -1
  74. package/dist/utils/angular-compiler.js +20 -13
  75. package/dist/utils/angular-compiler.js.map +1 -1
  76. package/dist/utils/esbuild-config.d.ts +1 -1
  77. package/dist/utils/esbuild-config.d.ts.map +1 -1
  78. package/dist/utils/esbuild-config.js +1 -4
  79. package/dist/utils/esbuild-config.js.map +1 -1
  80. package/dist/utils/ngtsc-build-core.d.ts.map +1 -1
  81. package/dist/utils/ngtsc-build-core.js +3 -0
  82. package/dist/utils/ngtsc-build-core.js.map +1 -1
  83. package/dist/utils/orchestrator-utils.js +1 -1
  84. package/dist/utils/orchestrator-utils.js.map +1 -1
  85. package/dist/utils/tsc-build.d.ts +5 -0
  86. package/dist/utils/tsc-build.d.ts.map +1 -1
  87. package/dist/utils/tsc-build.js +2 -1
  88. package/dist/utils/tsc-build.js.map +1 -1
  89. package/dist/utils/vite-config.d.ts +1 -1
  90. package/dist/utils/vite-config.d.ts.map +1 -1
  91. package/dist/utils/vite-config.js +22 -53
  92. package/dist/utils/vite-config.js.map +1 -1
  93. package/dist/utils/vite-pwa-plugin.d.ts +9 -0
  94. package/dist/utils/vite-pwa-plugin.d.ts.map +1 -0
  95. package/dist/utils/vite-pwa-plugin.js +139 -0
  96. package/dist/utils/vite-pwa-plugin.js.map +1 -0
  97. package/dist/utils/worker-utils.d.ts +2 -5
  98. package/dist/utils/worker-utils.d.ts.map +1 -1
  99. package/dist/utils/worker-utils.js +5 -11
  100. package/dist/utils/worker-utils.js.map +1 -1
  101. package/dist/workers/client.worker.d.ts.map +1 -1
  102. package/dist/workers/client.worker.js +9 -3
  103. package/dist/workers/client.worker.js.map +1 -1
  104. package/dist/workers/library-build.worker.d.ts.map +1 -1
  105. package/dist/workers/library-build.worker.js +6 -2
  106. package/dist/workers/library-build.worker.js.map +1 -1
  107. package/dist/workers/ngtsc-build.worker.js +2 -2
  108. package/dist/workers/ngtsc-build.worker.js.map +1 -1
  109. package/dist/workers/server-build.worker.d.ts.map +1 -1
  110. package/dist/workers/server-build.worker.js +6 -2
  111. package/dist/workers/server-build.worker.js.map +1 -1
  112. package/dist/workers/server-runtime.worker.js +4 -4
  113. package/dist/workers/server-runtime.worker.js.map +1 -1
  114. package/docs/config.md +30 -2
  115. package/docs/pwa-configuration-types.md +1 -1
  116. package/package.json +8 -10
  117. package/src/angular/client-transform-stylesheet.ts +104 -2
  118. package/src/angular/vite-angular-plugin.ts +92 -31
  119. package/src/capacitor/capacitor.ts +25 -26
  120. package/src/commands/check.ts +8 -11
  121. package/src/commands/device.ts +71 -17
  122. package/src/commands/lint.ts +2 -3
  123. package/src/commands/publish.ts +3 -3
  124. package/src/commands/replace-deps.ts +1 -1
  125. package/src/commands/typecheck.ts +1 -2
  126. package/src/electron/electron.ts +62 -43
  127. package/src/engines/BaseEngine.ts +1 -1
  128. package/src/engines/NgtscEngine.ts +0 -1
  129. package/src/engines/ServerEsbuildEngine.ts +0 -1
  130. package/src/engines/TscEngine.ts +0 -1
  131. package/src/engines/ViteEngine.ts +9 -1
  132. package/src/engines/index.ts +0 -10
  133. package/src/engines/types.ts +0 -1
  134. package/src/infra/SignalHandler.ts +4 -14
  135. package/src/orchestrators/BuildOrchestrator.ts +7 -9
  136. package/src/orchestrators/DevWatchOrchestrator.ts +22 -10
  137. package/src/sd-cli-entry.ts +17 -24
  138. package/src/sd-cli.ts +1 -1
  139. package/src/sd-config.types.ts +13 -2
  140. package/src/utils/angular-compiler.ts +21 -21
  141. package/src/utils/esbuild-config.ts +2 -5
  142. package/src/utils/ngtsc-build-core.ts +7 -0
  143. package/src/utils/orchestrator-utils.ts +1 -1
  144. package/src/utils/tsc-build.ts +7 -0
  145. package/src/utils/vite-config.ts +23 -55
  146. package/src/utils/vite-pwa-plugin.ts +168 -0
  147. package/src/utils/worker-utils.ts +5 -11
  148. package/src/workers/client.worker.ts +11 -3
  149. package/src/workers/library-build.worker.ts +6 -2
  150. package/src/workers/ngtsc-build.worker.ts +2 -2
  151. package/src/workers/server-build.worker.ts +7 -2
  152. package/src/workers/server-runtime.worker.ts +4 -4
  153. package/tests/angular/client-transform-stylesheet.spec.ts +43 -0
  154. package/tests/angular/find-affected-by-scss.spec.ts +37 -0
  155. package/tests/angular/fixtures/basic-app/scss/_colors.scss +1 -0
  156. package/tests/angular/fixtures/basic-app/scss/_variables.scss +3 -0
  157. package/tests/angular/fixtures/basic-app/src/styled.component.ts +14 -0
  158. package/tests/angular/linker-disk-cache.spec.ts +158 -0
  159. package/tests/angular/scss-disk-cache.spec.ts +162 -0
  160. package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +15 -15
  161. package/tests/angular/vite-angular-plugin-hmr.spec.ts +9 -9
  162. package/tests/angular/vite-angular-plugin-lint.spec.ts +4 -4
  163. package/tests/angular/vite-angular-plugin-scss-hmr.spec.ts +87 -0
  164. package/tests/angular/vite-angular-plugin.spec.ts +15 -15
  165. package/tests/capacitor/capacitor-icon.spec.ts +2 -4
  166. package/tests/capacitor/capacitor-init.spec.ts +2 -4
  167. package/tests/capacitor/capacitor-workspace.spec.ts +2 -4
  168. package/tests/commands/device.spec.ts +108 -8
  169. package/tests/commands/publish.spec.ts +2 -2
  170. package/tests/commands/typecheck.spec.ts +1 -1
  171. package/tests/electron/electron.spec.ts +24 -17
  172. package/tests/engines/ngtsc-engine.spec.ts +0 -3
  173. package/tests/engines/server-esbuild-engine.spec.ts +0 -3
  174. package/tests/engines/tsc-engine.spec.ts +1 -2
  175. package/tests/engines/vite-engine.spec.ts +0 -2
  176. package/tests/infra/signal-handler.spec.ts +1 -12
  177. package/tests/orchestrators/build-orchestrator.spec.ts +1 -7
  178. package/tests/orchestrators/dev-watch-orchestrator.spec.ts +24 -66
  179. package/tests/utils/angular-compiler.spec.ts +1396 -32
  180. package/tests/utils/esbuild-config.spec.ts +4 -7
  181. package/tests/utils/{ngtsc-build-core-angular-compiler.spec.ts → ngtsc-build-core.spec.ts} +142 -11
  182. package/tests/utils/orchestrator-utils.spec.ts +2 -2
  183. package/tests/utils/sd-config.spec.ts +2 -2
  184. package/tests/utils/tsc-build.spec.ts +4 -1
  185. package/tests/utils/vite-config.spec.ts +130 -261
  186. package/tests/utils/vite-pwa-plugin.acc.spec.ts +143 -0
  187. package/tests/utils/vite-pwa-plugin.spec.ts +350 -0
  188. package/tests/utils/worker-utils.spec.ts +8 -7
  189. package/tests/workers/client-worker.spec.ts +50 -1
  190. package/tests/workers/dev-port-file.verify.md +6 -0
  191. package/tests/workers/library-build-lint.spec.ts +1 -1
  192. package/tests/workers/library-build-worker.spec.ts +1 -1
  193. package/tests/workers/ngtsc-build-lint.spec.ts +1 -1
  194. package/tests/workers/server-build-lint.spec.ts +1 -1
  195. package/tests/workers/server-build-worker.spec.ts +1 -1
  196. package/tests/workers/server-runtime-worker.spec.ts +8 -1
  197. package/dist/infra/WorkerManager.d.ts +0 -40
  198. package/dist/infra/WorkerManager.d.ts.map +0 -1
  199. package/dist/infra/WorkerManager.js +0 -59
  200. package/dist/infra/WorkerManager.js.map +0 -1
  201. package/dist/utils/SdCliReporter.d.ts +0 -18
  202. package/dist/utils/SdCliReporter.d.ts.map +0 -1
  203. package/dist/utils/SdCliReporter.js +0 -144
  204. package/dist/utils/SdCliReporter.js.map +0 -1
  205. package/src/infra/WorkerManager.ts +0 -65
  206. package/src/utils/SdCliReporter.ts +0 -177
  207. package/tests/angular/scss-compiler-async.spec.ts +0 -54
  208. package/tests/commands/dev.spec.ts +0 -53
  209. package/tests/commands/watch.spec.ts +0 -53
  210. package/tests/infra/worker-manager.spec.ts +0 -63
  211. package/tests/utils/angular-compiler-emit.spec.ts +0 -570
  212. package/tests/utils/angular-compiler-init.spec.ts +0 -705
  213. package/tests/utils/angular-compiler-update.spec.ts +0 -293
  214. package/tests/utils/build-env.spec.ts +0 -33
  215. package/tests/utils/ngtsc-build-core-transform-stylesheet.spec.ts +0 -124
  216. package/tests/utils/ngtsc-scss-refactor.spec.ts +0 -47
@@ -31,6 +31,27 @@ vi.mock("../../src/utils/sd-config", () => ({
31
31
  loadSdConfig: vi.fn(),
32
32
  }));
33
33
 
34
+ // fs mock (포트 파일 읽기용)
35
+ const mockReadFileSync = vi.fn();
36
+ const mockExistsSync = vi.fn().mockReturnValue(false);
37
+ vi.mock("node:fs", () => ({
38
+ default: {
39
+ readFileSync: (...args: any[]) => mockReadFileSync(...args),
40
+ existsSync: (...args: any[]) => mockExistsSync(...args),
41
+ },
42
+ readFileSync: (...args: any[]) => mockReadFileSync(...args),
43
+ existsSync: (...args: any[]) => mockExistsSync(...args),
44
+ }));
45
+
46
+ // http mock (헬스체크용)
47
+ const mockHttpGet = vi.fn();
48
+ vi.mock("node:http", () => ({
49
+ default: {
50
+ get: (...args: any[]) => mockHttpGet(...args),
51
+ },
52
+ get: (...args: any[]) => mockHttpGet(...args),
53
+ }));
54
+
34
55
  const { Capacitor } = await import("../../src/capacitor/capacitor");
35
56
  const { Electron } = await import("../../src/electron/electron");
36
57
  const { loadSdConfig } = await import("../../src/utils/sd-config");
@@ -55,14 +76,14 @@ describe("runDevice", () => {
55
76
  },
56
77
  });
57
78
 
58
- await runDevice({ package: "client-pda", options: [] });
79
+ await runDevice({ target: "client-pda", options: [] });
59
80
 
60
81
  expect(Capacitor.create).toHaveBeenCalledWith(
61
82
  expect.stringContaining("client-pda"),
62
83
  { appId: "com.test.app", appName: "TestApp" },
63
84
  undefined,
64
85
  );
65
- expect(mockCapacitorInstance.run).toHaveBeenCalledWith("http://localhost:40480");
86
+ expect(mockCapacitorInstance.run).toHaveBeenCalledWith("http://localhost:40480/client-pda/");
66
87
  });
67
88
 
68
89
  // Acceptance: Scenario "device 명령어로 Electron 앱 실행"
@@ -77,14 +98,14 @@ describe("runDevice", () => {
77
98
  },
78
99
  });
79
100
 
80
- await runDevice({ package: "my-client", options: [] });
101
+ await runDevice({ target: "my-client", options: [] });
81
102
 
82
103
  expect(Electron.create).toHaveBeenCalledWith(
83
104
  expect.stringContaining("my-client"),
84
105
  { appId: "com.test.electron" },
85
106
  undefined,
86
107
  );
87
- expect(mockElectronInstance.run).toHaveBeenCalledWith("http://localhost:4200");
108
+ expect(mockElectronInstance.run).toHaveBeenCalledWith("http://localhost:4200/my-client/");
88
109
  });
89
110
 
90
111
  // Acceptance: Scenario "device 명령어에 URL 옵션 지정"
@@ -99,7 +120,7 @@ describe("runDevice", () => {
99
120
  },
100
121
  });
101
122
 
102
- await runDevice({ package: "client-pda", url: "http://192.168.1.100:4200", options: [] });
123
+ await runDevice({ target: "client-pda", url: "http://192.168.1.100:4200", options: [] });
103
124
 
104
125
  expect(mockCapacitorInstance.run).toHaveBeenCalledWith("http://192.168.1.100:4200");
105
126
  });
@@ -117,7 +138,7 @@ describe("runDevice", () => {
117
138
  },
118
139
  });
119
140
 
120
- await runDevice({ package: "my-client", options: [] });
141
+ await runDevice({ target: "my-client", options: [] });
121
142
 
122
143
  expect(Electron.create).toHaveBeenCalled();
123
144
  expect(Capacitor.create).not.toHaveBeenCalled();
@@ -131,7 +152,7 @@ describe("runDevice", () => {
131
152
  },
132
153
  });
133
154
 
134
- await expect(runDevice({ package: "nonexistent", options: [] })).rejects.toThrow();
155
+ await expect(runDevice({ target: "nonexistent", options: [] })).rejects.toThrow();
135
156
  });
136
157
 
137
158
  // Unit: client가 아닌 패키지 에러
@@ -142,6 +163,85 @@ describe("runDevice", () => {
142
163
  },
143
164
  });
144
165
 
145
- await expect(runDevice({ package: "my-server", options: [] })).rejects.toThrow();
166
+ await expect(runDevice({ target: "my-server", options: [] })).rejects.toThrow();
167
+ });
168
+
169
+ // Acceptance: Scenario "dev 서버 실행 중 device 실행 시 URL 자동 생성"
170
+ it("auto-detects URL from .dev-port when server is a string", async () => {
171
+ vi.mocked(loadSdConfig).mockResolvedValue({
172
+ packages: {
173
+ "client-devtool": {
174
+ target: "client",
175
+ server: "server",
176
+ electron: { appId: "com.test.electron" },
177
+ },
178
+ },
179
+ });
180
+
181
+ mockReadFileSync.mockReturnValue("5173");
182
+
183
+ // HTTP 헬스체크 성공 mock
184
+ mockHttpGet.mockImplementation((_url: string, cb: Function) => {
185
+ const res = { resume: vi.fn() };
186
+ cb(res);
187
+ return { on: vi.fn(), setTimeout: vi.fn() };
188
+ });
189
+
190
+ await runDevice({ target: "client-devtool", options: [] });
191
+
192
+ expect(mockElectronInstance.run).toHaveBeenCalledWith(
193
+ "http://localhost:5173/client-devtool/",
194
+ );
195
+ });
196
+
197
+ // Acceptance: Scenario "dev 서버 미실행 시 에러"
198
+ it("throws when .dev-port file does not exist and server is a string", async () => {
199
+ vi.mocked(loadSdConfig).mockResolvedValue({
200
+ packages: {
201
+ "client-devtool": {
202
+ target: "client",
203
+ server: "server",
204
+ electron: { appId: "com.test.electron" },
205
+ },
206
+ },
207
+ });
208
+
209
+ mockReadFileSync.mockImplementation(() => {
210
+ throw new Error("ENOENT");
211
+ });
212
+
213
+ await expect(runDevice({ target: "client-devtool", options: [] })).rejects.toThrow(
214
+ "dev 서버가 실행 중이 아닙니다",
215
+ );
216
+ });
217
+
218
+ // Acceptance: Scenario "stale 포트 파일 존재 시 헬스체크 실패 에러"
219
+ it("throws when .dev-port exists but health check fails", async () => {
220
+ vi.mocked(loadSdConfig).mockResolvedValue({
221
+ packages: {
222
+ "client-devtool": {
223
+ target: "client",
224
+ server: "server",
225
+ electron: { appId: "com.test.electron" },
226
+ },
227
+ },
228
+ });
229
+
230
+ mockReadFileSync.mockReturnValue("5173");
231
+
232
+ // HTTP 헬스체크 실패 mock
233
+ mockHttpGet.mockImplementation((_url: string, _cb: Function) => {
234
+ const req = {
235
+ on: vi.fn((event: string, handler: Function) => {
236
+ if (event === "error") handler(new Error("ECONNREFUSED"));
237
+ }),
238
+ setTimeout: vi.fn(),
239
+ };
240
+ return req;
241
+ });
242
+
243
+ await expect(runDevice({ target: "client-devtool", options: [] })).rejects.toThrow(
244
+ "dev 서버가 응답하지 않습니다",
245
+ );
146
246
  });
147
247
  });
@@ -446,7 +446,7 @@ describe("runPublish", () => {
446
446
  (c: unknown[]) => c[0] === "claude",
447
447
  );
448
448
  expect(claudeCalls).toHaveLength(1);
449
- expect((claudeCalls[0][1] as string[])).toContain("/sd-commit all");
449
+ expect((claudeCalls[0][1] as string[])).toContain("/sd-commit");
450
450
  });
451
451
 
452
452
  it("aborts when auto-commit claude command fails", async () => {
@@ -1105,7 +1105,7 @@ describe("runPublish", () => {
1105
1105
  });
1106
1106
 
1107
1107
  expect(mocks.loadSdConfig).toHaveBeenCalledWith(
1108
- expect.objectContaining({ options: ["production"] }),
1108
+ expect.objectContaining({ opt: ["production"] }),
1109
1109
  );
1110
1110
  });
1111
1111
 
@@ -299,7 +299,7 @@ describe("executeTypecheck", () => {
299
299
  await executeTypecheck({ targets: [], options: ["key=value"] });
300
300
 
301
301
  expect(mocks.loadSdConfig).toHaveBeenCalledWith(
302
- expect.objectContaining({ options: ["key=value"] }),
302
+ expect.objectContaining({ opt: ["key=value"] }),
303
303
  );
304
304
  });
305
305
 
@@ -7,6 +7,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
7
7
  const mockFsxExists = vi.fn();
8
8
  const mockFsxReadJson = vi.fn();
9
9
  const mockFsxWriteJson = vi.fn().mockResolvedValue(undefined);
10
+ const mockFsxWrite = vi.fn().mockResolvedValue(undefined);
10
11
  const mockFsxMkdir = vi.fn().mockResolvedValue(undefined);
11
12
  const mockFsxCopy = vi.fn().mockResolvedValue(undefined);
12
13
  const mockFsxReaddir = vi.fn();
@@ -17,6 +18,7 @@ vi.mock("@simplysm/core-node", () => ({
17
18
  exists: mockFsxExists,
18
19
  readJson: mockFsxReadJson,
19
20
  writeJson: mockFsxWriteJson,
21
+ write: mockFsxWrite,
20
22
  mkdir: mockFsxMkdir,
21
23
  copy: mockFsxCopy,
22
24
  readdir: mockFsxReaddir,
@@ -96,6 +98,11 @@ function setupDefaultMocks() {
96
98
  "better-sqlite3": "^11.0.0",
97
99
  "sharp": "^0.34.0",
98
100
  },
101
+ devDependencies: {
102
+ "electron": "^35.0.0",
103
+ "@electron/rebuild": "^4.0.0",
104
+ "electron-builder": "^26.0.0",
105
+ },
99
106
  });
100
107
  mockFsxReaddir.mockResolvedValue(["index.html", "assets", "electron"]);
101
108
  // Default: glob returns one exe file matching the builder output
@@ -169,7 +176,7 @@ describe("Electron", () => {
169
176
  //#region Rule: Electron 프로젝트를 초기화한다
170
177
 
171
178
  describe("인수 테스트: 초기화", () => {
172
- it("package.json 생성 + npm install + electron-rebuild를 실행한다", async () => {
179
+ it("package.json 생성 + pnpm install + electron-rebuild를 실행한다", async () => {
173
180
  const { Electron } = await import("../../src/electron/electron.js");
174
181
 
175
182
  const electron = await Electron.create(PKG_PATH, {
@@ -181,13 +188,13 @@ describe("Electron", () => {
181
188
 
182
189
  expect(findElectronPackageJson()).toBeDefined();
183
190
 
184
- const execaCalls = mockCpxSpawn.mock.calls;
191
+ const spawnCalls = mockCpxSpawn.mock.calls;
185
192
  expect(
186
- execaCalls.find((c) => c[0] === "npm" && (c[1] as string[]).includes("install")),
193
+ spawnCalls.find((c) => c[0] === "pnpm" && (c[1] as string[]).includes("install")),
187
194
  ).toBeDefined();
188
195
  expect(
189
- execaCalls.find(
190
- (c) => typeof c[0] === "string" && c[0].includes("electron-rebuild"),
196
+ spawnCalls.find(
197
+ (c) => c[0] === "pnpm" && (c[1] as string[]).includes("electron-rebuild"),
191
198
  ),
192
199
  ).toBeDefined();
193
200
  });
@@ -199,7 +206,7 @@ describe("Electron", () => {
199
206
  await electron.initialize();
200
207
 
201
208
  const rebuildCall = mockCpxSpawn.mock.calls.find(
202
- (c) => typeof c[0] === "string" && c[0].includes("electron-rebuild"),
209
+ (c) => c[0] === "pnpm" && (c[1] as string[]).includes("electron-rebuild"),
203
210
  );
204
211
  expect(rebuildCall).toBeUndefined();
205
212
  });
@@ -455,9 +462,9 @@ describe("Electron", () => {
455
462
  const electronKill = vi.fn();
456
463
  let resolveElectron: () => void = () => {};
457
464
 
458
- mockCpxSpawn.mockImplementation((cmd: string) => {
459
- if (typeof cmd === "string" && cmd.includes("electron")) {
460
- // Electron process: create a deferred promise we can resolve externally
465
+ mockCpxSpawn.mockImplementation((cmd: string, args: string[]) => {
466
+ // pnpm exec electron . Electron 프로세스
467
+ if (cmd === "pnpm" && args[0] === "exec" && args[1] === "electron" && args[2] === ".") {
461
468
  const p = new Promise<void>((resolve) => {
462
469
  resolveElectron = resolve;
463
470
  }) as any;
@@ -537,8 +544,8 @@ describe("Electron", () => {
537
544
  describe("단위: run() 플러그인 동작", () => {
538
545
  it("passes custom env and ELECTRON_DEV_URL via esbuild banner", async () => {
539
546
  let resolveElectron: () => void = () => {};
540
- mockCpxSpawn.mockImplementation((cmd: string) => {
541
- if (typeof cmd === "string" && cmd.includes("electron")) {
547
+ mockCpxSpawn.mockImplementation((cmd: string, args: string[]) => {
548
+ if (cmd === "pnpm" && args[0] === "exec" && args[1] === "electron" && args[2] === ".") {
542
549
  const p = new Promise<void>((resolve) => {
543
550
  resolveElectron = resolve;
544
551
  }) as any;
@@ -570,8 +577,8 @@ describe("Electron", () => {
570
577
 
571
578
  it("calls initialize() before starting esbuild context", async () => {
572
579
  let resolveElectron: () => void = () => {};
573
- mockCpxSpawn.mockImplementation((cmd: string) => {
574
- if (typeof cmd === "string" && cmd.includes("electron")) {
580
+ mockCpxSpawn.mockImplementation((cmd: string, args: string[]) => {
581
+ if (cmd === "pnpm" && args[0] === "exec" && args[1] === "electron" && args[2] === ".") {
575
582
  const p = new Promise<void>((resolve) => {
576
583
  resolveElectron = resolve;
577
584
  }) as any;
@@ -589,11 +596,11 @@ describe("Electron", () => {
589
596
  resolveElectron();
590
597
  await runPromise;
591
598
 
592
- // initialize calls npm install -> execa should have been called with npm install
593
- const npmInstallCall = mockCpxSpawn.mock.calls.find(
594
- (c: any[]) => c[0] === "npm" && (c[1] as string[]).includes("install"),
599
+ // initialize calls pnpm install
600
+ const pnpmInstallCall = mockCpxSpawn.mock.calls.find(
601
+ (c: any[]) => c[0] === "pnpm" && (c[1] as string[]).includes("install"),
595
602
  );
596
- expect(npmInstallCall).toBeDefined();
603
+ expect(pnpmInstallCall).toBeDefined();
597
604
  }, 10_000);
598
605
  });
599
606
 
@@ -78,7 +78,6 @@ describe("NgtscEngine", () => {
78
78
  output: { js: true, dts: true },
79
79
  }),
80
80
  );
81
- expect(result.success).toBe(true);
82
81
  expect(result.build.success).toBe(true);
83
82
  expect(result.build.diagnostics).toEqual([]);
84
83
  await engine.stop();
@@ -133,7 +132,6 @@ describe("NgtscEngine", () => {
133
132
  const engine = new NgtscEngine({ cwd: "/root", pkg: createMockPkg() });
134
133
  const result = await engine.run({ js: true, dts: true });
135
134
 
136
- expect(result.success).toBe(false);
137
135
  expect(result.build.success).toBe(false);
138
136
  expect(result.build.errors).toContain("TS2322: Type error");
139
137
  await engine.stop();
@@ -148,7 +146,6 @@ describe("NgtscEngine", () => {
148
146
  const engine = new NgtscEngine({ cwd: "/root", pkg: createMockPkg() });
149
147
  const result = await engine.run({ js: true, dts: true });
150
148
 
151
- expect(result.success).toBe(false);
152
149
  expect(result.build.success).toBe(false);
153
150
  expect(result.build.errors).toEqual(["ngtsc compilation error"]);
154
151
  await engine.stop();
@@ -89,7 +89,6 @@ describe("ServerEsbuildEngine", () => {
89
89
  packageManager: "mise",
90
90
  }),
91
91
  );
92
- expect(result.success).toBe(true);
93
92
  expect(result.build.success).toBe(true);
94
93
  await engine.stop();
95
94
  });
@@ -104,7 +103,6 @@ describe("ServerEsbuildEngine", () => {
104
103
  const engine = new ServerEsbuildEngine({ cwd: "/root", pkg: createMockPkg() });
105
104
  const result = await engine.run({ js: true, dts: false });
106
105
 
107
- expect(result.success).toBe(false);
108
106
  expect(result.build.warnings).toEqual(["warn1"]);
109
107
  expect(result.build.success).toBe(false);
110
108
  expect(result.build.diagnostics).toEqual([{ code: 2345, category: 1 }]);
@@ -121,7 +119,6 @@ describe("ServerEsbuildEngine", () => {
121
119
  const engine = new ServerEsbuildEngine({ cwd: "/root", pkg: createMockPkg() });
122
120
  const result = await engine.run({ js: true, dts: false });
123
121
 
124
- expect(result.success).toBe(false);
125
122
  expect(result.build.success).toBe(false);
126
123
  expect(result.build.errors).toEqual(["esbuild error"]);
127
124
  await engine.stop();
@@ -75,7 +75,6 @@ describe("TscEngine", () => {
75
75
  output: { js: true, dts: true },
76
76
  }),
77
77
  );
78
- expect(result.success).toBe(true);
79
78
  expect(result.build.success).toBe(true);
80
79
  await engine.stop();
81
80
  });
@@ -101,7 +100,7 @@ describe("TscEngine", () => {
101
100
  const engine = new TscEngine({ cwd: "/root", pkg: createMockPkg() });
102
101
  const result = await engine.run({ js: true, dts: true });
103
102
 
104
- expect(result.success).toBe(false);
103
+ expect(result.build.success).toBe(false);
105
104
  expect(result.build.errors).toEqual(["type error"]);
106
105
  expect(result.build.diagnostics).toHaveLength(1);
107
106
  await engine.stop();
@@ -74,7 +74,6 @@ describe("ViteEngine", () => {
74
74
  pkgDir: "/packages/my-client",
75
75
  }),
76
76
  );
77
- expect(result.success).toBe(true);
78
77
  expect(result.build.success).toBe(true);
79
78
  expect(result.build.errors).toEqual([]);
80
79
  expect(result.build.diagnostics).toEqual([]);
@@ -91,7 +90,6 @@ describe("ViteEngine", () => {
91
90
  const engine = new ViteEngine({ cwd: "/root", pkg: createMockPkg() });
92
91
  const result = await engine.run({ js: true, dts: false });
93
92
 
94
- expect(result.success).toBe(false);
95
93
  expect(result.build.success).toBe(false);
96
94
  expect(result.build.errors).toContain("TS2345: Argument of type...");
97
95
  await engine.stop();
@@ -1,23 +1,13 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, it } from "vitest";
2
2
  import { SignalHandler } from "../../src/infra/SignalHandler";
3
3
 
4
4
  describe("SignalHandler", () => {
5
5
  it("resolves waitForTermination on requestTermination", async () => {
6
6
  const handler = new SignalHandler();
7
7
 
8
- expect(handler.isTerminated()).toBe(false);
9
-
10
8
  handler.requestTermination();
11
9
 
12
10
  await handler.waitForTermination();
13
- expect(handler.isTerminated()).toBe(true);
14
- });
15
-
16
- it("is not terminated initially", () => {
17
- const handler = new SignalHandler();
18
- expect(handler.isTerminated()).toBe(false);
19
- // Clean up: prevent dangling signal listeners
20
- handler.requestTermination();
21
11
  });
22
12
 
23
13
  it("handles double requestTermination gracefully", async () => {
@@ -27,6 +17,5 @@ describe("SignalHandler", () => {
27
17
  handler.requestTermination();
28
18
 
29
19
  await handler.waitForTermination();
30
- expect(handler.isTerminated()).toBe(true);
31
20
  });
32
21
  });
@@ -66,7 +66,6 @@ vi.mock("../../src/engines/index", () => ({
66
66
  createBuildEngine: vi.fn(() => {
67
67
  const engine = {
68
68
  run: vi.fn().mockResolvedValue({
69
- success: true,
70
69
  build: { success: true, errors: [], warnings: [], diagnostics: [] },
71
70
  }),
72
71
  startWatch: vi.fn().mockResolvedValue(undefined),
@@ -159,7 +158,6 @@ beforeEach(() => {
159
158
  vi.mocked(createBuildEngine).mockImplementation(() => {
160
159
  const engine = {
161
160
  run: vi.fn().mockResolvedValue({
162
- success: true,
163
161
  build: { success: true, errors: [], warnings: [], diagnostics: [] },
164
162
 
165
163
  }),
@@ -218,7 +216,7 @@ describe("BuildOrchestrator.initialize", () => {
218
216
  await orchestrator.initialize();
219
217
 
220
218
  expect(loadSdConfig).toHaveBeenCalledWith(
221
- expect.objectContaining({ options: ["production"] }),
219
+ expect.objectContaining({ opt: ["production"] }),
222
220
  );
223
221
  });
224
222
 
@@ -413,7 +411,6 @@ describe("BuildOrchestrator.start", () => {
413
411
  });
414
412
  vi.mocked(createBuildEngine).mockReturnValue({
415
413
  run: vi.fn().mockResolvedValue({
416
- success: false,
417
414
  build: { success: false, errors: ["Module not found"], warnings: [], diagnostics: [] },
418
415
  }),
419
416
  startWatch: vi.fn(),
@@ -603,7 +600,6 @@ describe("BuildOrchestrator client build", () => {
603
600
  });
604
601
  vi.mocked(createBuildEngine).mockReturnValue({
605
602
  run: vi.fn().mockResolvedValue({
606
- success: true,
607
603
  build: { success: true, errors: [], warnings: [], diagnostics: [] },
608
604
  }),
609
605
  startWatch: vi.fn(),
@@ -637,7 +633,6 @@ describe("BuildOrchestrator client build", () => {
637
633
  });
638
634
  vi.mocked(createBuildEngine).mockReturnValue({
639
635
  run: vi.fn().mockResolvedValue({
640
- success: false,
641
636
  build: { success: false, errors: ["Template error"], warnings: [], diagnostics: [] },
642
637
  }),
643
638
  startWatch: vi.fn(),
@@ -957,7 +952,6 @@ describe("BuildOrchestrator native build integration (Slice 1)", () => {
957
952
  });
958
953
  vi.mocked(createBuildEngine).mockReturnValue({
959
954
  run: vi.fn().mockResolvedValue({
960
- success: false,
961
955
  build: { success: false, errors: ["Build error"], warnings: [], diagnostics: [] },
962
956
  }),
963
957
  startWatch: vi.fn(),
@@ -651,8 +651,9 @@ describe("DevWatchOrchestrator", () => {
651
651
  expect.any(Object),
652
652
  );
653
653
  expect(mockBuildEngines[0].startWatch).toHaveBeenCalledWith({ js: true, dts: false, lint: false });
654
- // Runtime is NOT started during initial build; it starts via batchComplete
655
- expect(Worker.create).not.toHaveBeenCalled();
654
+ // Runtime starts during start() after all engines ready
655
+ expect(Worker.create).toHaveBeenCalled();
656
+ expect(mockRuntimeProxies[0].start).toHaveBeenCalled();
656
657
  });
657
658
 
658
659
  // --- Acceptance: Server 패키지 dev 모드 실행 ---
@@ -833,7 +834,7 @@ describe("DevWatchOrchestrator", () => {
833
834
  });
834
835
 
835
836
  // --- Acceptance: Server batchComplete 시 runtime 시작 ---
836
- it("starts runtime on batchComplete with success", async () => {
837
+ it("starts runtime during start() after all engines ready", async () => {
837
838
  setupDefaults(createConfig({
838
839
  packages: { "service-server": { target: "server" } },
839
840
  }));
@@ -843,18 +844,7 @@ describe("DevWatchOrchestrator", () => {
843
844
  await orchestrator.initialize();
844
845
  await orchestrator.start();
845
846
 
846
- // No runtime started initially
847
- expect(Worker.create).not.toHaveBeenCalled();
848
-
849
- // Get batchComplete handler
850
- const rebuildInstance = vi.mocked(RebuildManager).mock.instances[0];
851
- const onCall = vi.mocked(rebuildInstance.on).mock.calls.find((c) => c[0] === "batchComplete");
852
- const batchHandler = onCall?.[1] as ((keys: string[]) => void) | undefined;
853
-
854
- batchHandler?.(["service-server:build"]);
855
- await new Promise((r) => setTimeout(r, 200));
856
-
857
- // First runtime created via batchComplete
847
+ // Runtime starts during start(), not via batchComplete
858
848
  expect(mockRuntimeProxies).toHaveLength(1);
859
849
  expect(mockRuntimeProxies[0].start).toHaveBeenCalled();
860
850
  });
@@ -870,12 +860,8 @@ describe("DevWatchOrchestrator", () => {
870
860
  await orchestrator.initialize();
871
861
  await orchestrator.start();
872
862
 
873
- // Trigger batchComplete to create runtime first
874
- const rebuildInstance = vi.mocked(RebuildManager).mock.instances[0];
875
- const onCall = vi.mocked(rebuildInstance.on).mock.calls.find((c) => c[0] === "batchComplete");
876
- const batchHandler = onCall?.[1] as ((keys: string[]) => void) | undefined;
877
- batchHandler?.(["service-server:build"]);
878
- await new Promise((r) => setTimeout(r, 200));
863
+ // Runtime is now started during start()
864
+ expect(mockRuntimeProxies).toHaveLength(1);
879
865
 
880
866
  await orchestrator.shutdown();
881
867
 
@@ -911,7 +897,7 @@ describe("DevWatchOrchestrator", () => {
911
897
  });
912
898
 
913
899
  // Unit: multiple server packages
914
- it("creates engines for multiple server packages (runtimes start via batchComplete)", async () => {
900
+ it("creates engines for multiple server packages (runtimes start during start())", async () => {
915
901
  setupDefaults(createConfig({
916
902
  packages: {
917
903
  "service-server": { target: "server" },
@@ -925,8 +911,8 @@ describe("DevWatchOrchestrator", () => {
925
911
  await orchestrator.start();
926
912
 
927
913
  expect(createBuildEngine).toHaveBeenCalledTimes(2);
928
- // Runtimes are NOT started during initial build
929
- expect(Worker.create).not.toHaveBeenCalled();
914
+ // Runtimes start during start() after all engines ready
915
+ expect(Worker.create).toHaveBeenCalledTimes(2);
930
916
  });
931
917
 
932
918
  // Unit: skips start when no packages
@@ -1027,16 +1013,7 @@ describe("DevWatchOrchestrator", () => {
1027
1013
  await orchestrator.initialize();
1028
1014
  await orchestrator.start();
1029
1015
 
1030
- // Runtime starts via batchComplete, not initial start
1031
- expect(Worker.create).not.toHaveBeenCalled();
1032
-
1033
- // Trigger batchComplete
1034
- const rebuildInstance = vi.mocked(RebuildManager).mock.instances[0];
1035
- const onCall = vi.mocked(rebuildInstance.on).mock.calls.find((c) => c[0] === "batchComplete");
1036
- const batchHandler = onCall?.[1] as ((keys: string[]) => void) | undefined;
1037
- batchHandler?.(["service-server:build"]);
1038
- await new Promise((r) => setTimeout(r, 200));
1039
-
1016
+ // Runtime starts during start() with client ports available
1040
1017
  expect(Worker.create).toHaveBeenCalled();
1041
1018
  expect(mockRuntimeProxies[0].start).toHaveBeenCalledWith(
1042
1019
  expect.objectContaining({
@@ -1059,13 +1036,7 @@ describe("DevWatchOrchestrator", () => {
1059
1036
  await orchestrator.initialize();
1060
1037
  await orchestrator.start();
1061
1038
 
1062
- // Trigger batchComplete to start server runtime
1063
- const rebuildInstance = vi.mocked(RebuildManager).mock.instances[0];
1064
- const onCall = vi.mocked(rebuildInstance.on).mock.calls.find((c) => c[0] === "batchComplete");
1065
- const batchHandler = onCall?.[1] as ((keys: string[]) => void) | undefined;
1066
- batchHandler?.(["service-server:build"]);
1067
- await new Promise((r) => setTimeout(r, 200));
1068
-
1039
+ // Runtime starts during start(), standalone client ports excluded
1069
1040
  expect(mockRuntimeProxies[0].start).toHaveBeenCalledWith(
1070
1041
  expect.objectContaining({
1071
1042
  clientPorts: {},
@@ -1112,13 +1083,7 @@ describe("DevWatchOrchestrator", () => {
1112
1083
  await orchestrator.initialize();
1113
1084
  await orchestrator.start();
1114
1085
 
1115
- // Trigger batchComplete to start server runtime
1116
- const rebuildInstance = vi.mocked(RebuildManager).mock.instances[0];
1117
- const onCall = vi.mocked(rebuildInstance.on).mock.calls.find((c) => c[0] === "batchComplete");
1118
- const batchHandler = onCall?.[1] as ((keys: string[]) => void) | undefined;
1119
- batchHandler?.(["service-server:build"]);
1120
- await new Promise((r) => setTimeout(r, 200));
1121
-
1086
+ // Runtime starts during start() but client port unavailable → empty clientPorts
1122
1087
  expect(mockRuntimeProxies[0].start).toHaveBeenCalledWith(
1123
1088
  expect.objectContaining({
1124
1089
  clientPorts: {},
@@ -1126,8 +1091,8 @@ describe("DevWatchOrchestrator", () => {
1126
1091
  );
1127
1092
  });
1128
1093
 
1129
- // --- Acceptance: printServers serverClientsMap 전달 ---
1130
- it("passes serverClientsMap to printServers", async () => {
1094
+ // --- Acceptance: printServers start()에서 직접 호출되지 않는다 (serverReady 이벤트 경로만 사용) ---
1095
+ it("does not call printServers directly during start()", async () => {
1131
1096
  setupDefaults(createConfig({
1132
1097
  packages: {
1133
1098
  "service-server": { target: "server" },
@@ -1140,12 +1105,9 @@ describe("DevWatchOrchestrator", () => {
1140
1105
  await orchestrator.initialize();
1141
1106
  await orchestrator.start();
1142
1107
 
1143
- expect(_printServers).toHaveBeenCalledWith(
1144
- expect.any(Map),
1145
- expect.any(Map),
1146
- );
1147
- const serverClientsMap = vi.mocked(_printServers).mock.calls[0][1] as Map<string, string[]>;
1148
- expect(serverClientsMap.get("service-server")).toEqual(["my-client"]);
1108
+ // printServers should NOT be called directly during start()
1109
+ // It should only be called via serverReady → _schedulePrintServers()
1110
+ expect(_printServers).not.toHaveBeenCalled();
1149
1111
  });
1150
1112
 
1151
1113
  // --- Acceptance: 서버 rebuild 시 프록시 재등록 ---
@@ -1162,14 +1124,7 @@ describe("DevWatchOrchestrator", () => {
1162
1124
  await orchestrator.initialize();
1163
1125
  await orchestrator.start();
1164
1126
 
1165
- // Trigger first batchComplete to start runtime
1166
- const rebuildInstance = vi.mocked(RebuildManager).mock.instances[0];
1167
- const onCall = vi.mocked(rebuildInstance.on).mock.calls.find((c) => c[0] === "batchComplete");
1168
- const batchHandler = onCall?.[1] as ((keys: string[]) => void) | undefined;
1169
- batchHandler?.(["service-server:build"]);
1170
- await new Promise((r) => setTimeout(r, 200));
1171
-
1172
- // First runtime created with clientPorts
1127
+ // First runtime created during start() with clientPorts
1173
1128
  expect(mockRuntimeProxies).toHaveLength(1);
1174
1129
  expect(mockRuntimeProxies[0].start).toHaveBeenCalledWith(
1175
1130
  expect.objectContaining({
@@ -1177,11 +1132,14 @@ describe("DevWatchOrchestrator", () => {
1177
1132
  }),
1178
1133
  );
1179
1134
 
1180
- // Trigger second batchComplete (rebuild)
1135
+ // Trigger batchComplete (rebuild after initial build)
1136
+ const rebuildInstance = vi.mocked(RebuildManager).mock.instances[0];
1137
+ const onCall = vi.mocked(rebuildInstance.on).mock.calls.find((c) => c[0] === "batchComplete");
1138
+ const batchHandler = onCall?.[1] as ((keys: string[]) => void) | undefined;
1181
1139
  batchHandler?.(["service-server:build"]);
1182
1140
  await new Promise((r) => setTimeout(r, 200));
1183
1141
 
1184
- // Second runtime start should also have clientPorts
1142
+ // Second runtime start (rebuild) should also have clientPorts
1185
1143
  expect(mockRuntimeProxies).toHaveLength(2);
1186
1144
  expect(mockRuntimeProxies[0].terminate).toHaveBeenCalled();
1187
1145
  expect(mockRuntimeProxies[1].start).toHaveBeenCalledWith(