@simplysm/sd-cli 14.0.11 → 14.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/README.md +58 -253
  2. package/dist/angular/client-transform-stylesheet.js +1 -1
  3. package/dist/angular/client-transform-stylesheet.js.map +1 -1
  4. package/dist/angular/vite-angular-plugin.d.ts +4 -2
  5. package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
  6. package/dist/angular/vite-angular-plugin.js +73 -36
  7. package/dist/angular/vite-angular-plugin.js.map +1 -1
  8. package/dist/angular/vite-postcss-inline-plugin.d.ts +1 -1
  9. package/dist/angular/vite-postcss-inline-plugin.js +1 -1
  10. package/dist/capacitor/capacitor.d.ts +20 -2
  11. package/dist/capacitor/capacitor.d.ts.map +1 -1
  12. package/dist/capacitor/capacitor.js +155 -28
  13. package/dist/capacitor/capacitor.js.map +1 -1
  14. package/dist/commands/build.d.ts +3 -10
  15. package/dist/commands/build.d.ts.map +1 -1
  16. package/dist/commands/build.js +3 -10
  17. package/dist/commands/build.js.map +1 -1
  18. package/dist/commands/check.js +3 -3
  19. package/dist/commands/check.js.map +1 -1
  20. package/dist/commands/dev.d.ts +3 -9
  21. package/dist/commands/dev.d.ts.map +1 -1
  22. package/dist/commands/dev.js +3 -9
  23. package/dist/commands/dev.js.map +1 -1
  24. package/dist/commands/device.d.ts +3 -3
  25. package/dist/commands/device.js +5 -5
  26. package/dist/commands/device.js.map +1 -1
  27. package/dist/commands/publish.d.ts +1 -1
  28. package/dist/commands/publish.d.ts.map +1 -1
  29. package/dist/commands/publish.js +24 -26
  30. package/dist/commands/publish.js.map +1 -1
  31. package/dist/commands/replace-deps.d.ts +3 -3
  32. package/dist/commands/replace-deps.d.ts.map +1 -1
  33. package/dist/commands/replace-deps.js +1 -1
  34. package/dist/commands/typecheck.d.ts +4 -3
  35. package/dist/commands/typecheck.d.ts.map +1 -1
  36. package/dist/commands/typecheck.js +5 -11
  37. package/dist/commands/typecheck.js.map +1 -1
  38. package/dist/commands/watch.d.ts +9 -9
  39. package/dist/commands/watch.js +9 -9
  40. package/dist/electron/electron.d.ts.map +1 -1
  41. package/dist/electron/electron.js +42 -3
  42. package/dist/electron/electron.js.map +1 -1
  43. package/dist/engines/BaseEngine.d.ts +1 -1
  44. package/dist/engines/BaseEngine.d.ts.map +1 -1
  45. package/dist/engines/BaseEngine.js +3 -1
  46. package/dist/engines/BaseEngine.js.map +1 -1
  47. package/dist/engines/NgtscEngine.d.ts +7 -7
  48. package/dist/engines/NgtscEngine.d.ts.map +1 -1
  49. package/dist/engines/NgtscEngine.js +3 -3
  50. package/dist/engines/ServerEsbuildEngine.d.ts +7 -7
  51. package/dist/engines/ServerEsbuildEngine.d.ts.map +1 -1
  52. package/dist/engines/ServerEsbuildEngine.js +3 -3
  53. package/dist/engines/TscEngine.d.ts +7 -7
  54. package/dist/engines/TscEngine.d.ts.map +1 -1
  55. package/dist/engines/TscEngine.js +3 -3
  56. package/dist/engines/ViteEngine.d.ts +7 -1
  57. package/dist/engines/ViteEngine.d.ts.map +1 -1
  58. package/dist/engines/ViteEngine.js +13 -12
  59. package/dist/engines/ViteEngine.js.map +1 -1
  60. package/dist/engines/index.d.ts +9 -5
  61. package/dist/engines/index.d.ts.map +1 -1
  62. package/dist/engines/index.js +7 -5
  63. package/dist/engines/index.js.map +1 -1
  64. package/dist/engines/types.d.ts +20 -20
  65. package/dist/engines/types.d.ts.map +1 -1
  66. package/dist/infra/ResultCollector.d.ts +9 -9
  67. package/dist/infra/ResultCollector.js +8 -8
  68. package/dist/infra/SignalHandler.d.ts +7 -7
  69. package/dist/infra/SignalHandler.js +7 -7
  70. package/dist/infra/WorkerManager.d.ts +14 -14
  71. package/dist/infra/WorkerManager.js +14 -14
  72. package/dist/orchestrators/BuildOrchestrator.d.ts +25 -25
  73. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  74. package/dist/orchestrators/BuildOrchestrator.js +34 -30
  75. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  76. package/dist/orchestrators/DevWatchOrchestrator.d.ts +7 -7
  77. package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
  78. package/dist/orchestrators/DevWatchOrchestrator.js +34 -34
  79. package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
  80. package/dist/sd-cli-entry.d.ts +2 -2
  81. package/dist/sd-cli-entry.d.ts.map +1 -1
  82. package/dist/sd-cli-entry.js +15 -8
  83. package/dist/sd-cli-entry.js.map +1 -1
  84. package/dist/sd-cli.d.ts +3 -3
  85. package/dist/sd-cli.js +16 -16
  86. package/dist/sd-cli.js.map +1 -1
  87. package/dist/sd-config.types.d.ts +105 -105
  88. package/dist/sd-config.types.d.ts.map +1 -1
  89. package/dist/utils/angular-compiler.js +5 -5
  90. package/dist/utils/angular-compiler.js.map +1 -1
  91. package/dist/utils/build-env.d.ts +1 -1
  92. package/dist/utils/build-env.js +1 -1
  93. package/dist/utils/concurrency.d.ts +7 -7
  94. package/dist/utils/concurrency.js +7 -7
  95. package/dist/utils/copy-public.d.ts +9 -9
  96. package/dist/utils/copy-public.js +17 -17
  97. package/dist/utils/copy-public.js.map +1 -1
  98. package/dist/utils/copy-src.d.ts +9 -9
  99. package/dist/utils/copy-src.js +11 -11
  100. package/dist/utils/copy-src.js.map +1 -1
  101. package/dist/utils/engine-stop.d.ts +8 -9
  102. package/dist/utils/engine-stop.d.ts.map +1 -1
  103. package/dist/utils/engine-stop.js +9 -10
  104. package/dist/utils/engine-stop.js.map +1 -1
  105. package/dist/utils/esbuild-config.d.ts +23 -23
  106. package/dist/utils/esbuild-config.d.ts.map +1 -1
  107. package/dist/utils/esbuild-config.js +25 -25
  108. package/dist/utils/esbuild-config.js.map +1 -1
  109. package/dist/utils/lint-with-program.d.ts +15 -15
  110. package/dist/utils/lint-with-program.d.ts.map +1 -1
  111. package/dist/utils/lint-with-program.js +29 -29
  112. package/dist/utils/lint-with-program.js.map +1 -1
  113. package/dist/utils/ngtsc-build-core.d.ts +8 -8
  114. package/dist/utils/ngtsc-build-core.d.ts.map +1 -1
  115. package/dist/utils/ngtsc-build-core.js +14 -14
  116. package/dist/utils/ngtsc-build-core.js.map +1 -1
  117. package/dist/utils/output-path-rewriter.d.ts +14 -14
  118. package/dist/utils/output-path-rewriter.js +18 -18
  119. package/dist/utils/output-path-rewriter.js.map +1 -1
  120. package/dist/utils/output-utils.d.ts +6 -6
  121. package/dist/utils/output-utils.js +11 -11
  122. package/dist/utils/output-utils.js.map +1 -1
  123. package/dist/utils/package-utils.d.ts +21 -21
  124. package/dist/utils/package-utils.d.ts.map +1 -1
  125. package/dist/utils/package-utils.js +56 -45
  126. package/dist/utils/package-utils.js.map +1 -1
  127. package/dist/utils/replace-deps.d.ts +25 -25
  128. package/dist/utils/replace-deps.d.ts.map +1 -1
  129. package/dist/utils/replace-deps.js +84 -65
  130. package/dist/utils/replace-deps.js.map +1 -1
  131. package/dist/utils/sd-config.d.ts +3 -3
  132. package/dist/utils/sd-config.js +3 -3
  133. package/dist/utils/tsc-build.d.ts +13 -13
  134. package/dist/utils/tsc-build.d.ts.map +1 -1
  135. package/dist/utils/tsc-build.js +9 -9
  136. package/dist/utils/tsc-build.js.map +1 -1
  137. package/dist/utils/tsconfig.d.ts +11 -9
  138. package/dist/utils/tsconfig.d.ts.map +1 -1
  139. package/dist/utils/tsconfig.js +11 -9
  140. package/dist/utils/tsconfig.js.map +1 -1
  141. package/dist/utils/typecheck-non-package.d.ts +5 -6
  142. package/dist/utils/typecheck-non-package.d.ts.map +1 -1
  143. package/dist/utils/typecheck-non-package.js +7 -8
  144. package/dist/utils/typecheck-non-package.js.map +1 -1
  145. package/dist/utils/typecheck-serialization.d.ts +8 -8
  146. package/dist/utils/typecheck-serialization.d.ts.map +1 -1
  147. package/dist/utils/typecheck-serialization.js +12 -16
  148. package/dist/utils/typecheck-serialization.js.map +1 -1
  149. package/dist/utils/vite-config.d.ts +12 -5
  150. package/dist/utils/vite-config.d.ts.map +1 -1
  151. package/dist/utils/vite-config.js +95 -41
  152. package/dist/utils/vite-config.js.map +1 -1
  153. package/dist/utils/vite-scope-watch-plugin.d.ts.map +1 -1
  154. package/dist/utils/vite-scope-watch-plugin.js +1 -1
  155. package/dist/utils/vite-scope-watch-plugin.js.map +1 -1
  156. package/dist/utils/worker-events.d.ts +12 -12
  157. package/dist/utils/worker-events.d.ts.map +1 -1
  158. package/dist/utils/worker-events.js +10 -10
  159. package/dist/utils/worker-events.js.map +1 -1
  160. package/dist/utils/worker-utils.d.ts +12 -13
  161. package/dist/utils/worker-utils.d.ts.map +1 -1
  162. package/dist/utils/worker-utils.js +12 -13
  163. package/dist/utils/worker-utils.js.map +1 -1
  164. package/dist/vitest-plugin.d.ts.map +1 -1
  165. package/dist/vitest-plugin.js +5 -7
  166. package/dist/vitest-plugin.js.map +1 -1
  167. package/dist/workers/client.worker.d.ts +8 -2
  168. package/dist/workers/client.worker.d.ts.map +1 -1
  169. package/dist/workers/client.worker.js +215 -6
  170. package/dist/workers/client.worker.js.map +1 -1
  171. package/dist/workers/library-build.worker.d.ts +1 -1
  172. package/dist/workers/library-build.worker.d.ts.map +1 -1
  173. package/dist/workers/library-build.worker.js +7 -7
  174. package/dist/workers/library-build.worker.js.map +1 -1
  175. package/dist/workers/lint.worker.d.ts +2 -2
  176. package/dist/workers/lint.worker.js +2 -2
  177. package/dist/workers/ngtsc-build.worker.js +30 -30
  178. package/dist/workers/ngtsc-build.worker.js.map +1 -1
  179. package/dist/workers/server-build.worker.d.ts +17 -17
  180. package/dist/workers/server-build.worker.d.ts.map +1 -1
  181. package/dist/workers/server-build.worker.js +46 -46
  182. package/dist/workers/server-build.worker.js.map +1 -1
  183. package/dist/workers/server-runtime.worker.d.ts +7 -7
  184. package/dist/workers/server-runtime.worker.d.ts.map +1 -1
  185. package/dist/workers/server-runtime.worker.js +17 -17
  186. package/dist/workers/server-runtime.worker.js.map +1 -1
  187. package/docs/config.md +340 -0
  188. package/docs/publish-configuration-types.md +87 -0
  189. package/docs/pwa-configuration-types.md +55 -0
  190. package/docs/vitest-plugin.md +47 -0
  191. package/package.json +9 -7
  192. package/src/angular/client-transform-stylesheet.ts +1 -1
  193. package/src/angular/vite-angular-plugin.ts +89 -39
  194. package/src/angular/vite-postcss-inline-plugin.ts +1 -1
  195. package/src/capacitor/capacitor.ts +185 -38
  196. package/src/commands/build.ts +3 -10
  197. package/src/commands/check.ts +3 -3
  198. package/src/commands/dev.ts +3 -9
  199. package/src/commands/device.ts +5 -5
  200. package/src/commands/publish.ts +30 -26
  201. package/src/commands/replace-deps.ts +3 -3
  202. package/src/commands/typecheck.ts +7 -13
  203. package/src/commands/watch.ts +9 -9
  204. package/src/electron/electron.ts +49 -4
  205. package/src/engines/BaseEngine.ts +4 -1
  206. package/src/engines/NgtscEngine.ts +7 -7
  207. package/src/engines/ServerEsbuildEngine.ts +7 -7
  208. package/src/engines/TscEngine.ts +7 -7
  209. package/src/engines/ViteEngine.ts +18 -13
  210. package/src/engines/index.ts +11 -5
  211. package/src/engines/types.ts +20 -20
  212. package/src/infra/ResultCollector.ts +9 -9
  213. package/src/infra/SignalHandler.ts +7 -7
  214. package/src/infra/WorkerManager.ts +14 -14
  215. package/src/orchestrators/BuildOrchestrator.ts +42 -38
  216. package/src/orchestrators/DevWatchOrchestrator.ts +36 -36
  217. package/src/sd-cli-entry.ts +15 -8
  218. package/src/sd-cli.ts +16 -16
  219. package/src/sd-config.types.ts +107 -107
  220. package/src/utils/angular-compiler.ts +5 -5
  221. package/src/utils/build-env.ts +1 -1
  222. package/src/utils/concurrency.ts +7 -7
  223. package/src/utils/copy-public.ts +17 -17
  224. package/src/utils/copy-src.ts +11 -11
  225. package/src/utils/engine-stop.ts +9 -10
  226. package/src/utils/esbuild-config.ts +29 -29
  227. package/src/utils/lint-with-program.ts +34 -34
  228. package/src/utils/ngtsc-build-core.ts +17 -17
  229. package/src/utils/output-path-rewriter.ts +18 -18
  230. package/src/utils/output-utils.ts +11 -11
  231. package/src/utils/package-utils.ts +57 -45
  232. package/src/utils/replace-deps.ts +92 -67
  233. package/src/utils/sd-config.ts +3 -3
  234. package/src/utils/tsc-build.ts +18 -18
  235. package/src/utils/tsconfig.ts +11 -9
  236. package/src/utils/typecheck-non-package.ts +7 -8
  237. package/src/utils/typecheck-serialization.ts +13 -15
  238. package/src/utils/vite-config.ts +108 -46
  239. package/src/utils/vite-scope-watch-plugin.ts +6 -1
  240. package/src/utils/worker-events.ts +16 -16
  241. package/src/utils/worker-utils.ts +12 -13
  242. package/src/vitest-plugin.ts +5 -8
  243. package/src/workers/client.worker.ts +246 -7
  244. package/src/workers/library-build.worker.ts +8 -8
  245. package/src/workers/lint.worker.ts +2 -2
  246. package/src/workers/ngtsc-build.worker.ts +31 -31
  247. package/src/workers/server-build.worker.ts +60 -60
  248. package/src/workers/server-runtime.worker.ts +22 -22
  249. package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +1 -0
  250. package/tests/angular/vite-angular-plugin-hmr.spec.ts +78 -0
  251. package/tests/angular/vite-angular-plugin.spec.ts +67 -0
  252. package/tests/capacitor/capacitor-build.spec.ts +93 -11
  253. package/tests/capacitor/capacitor-icon.spec.ts +7 -5
  254. package/tests/capacitor/capacitor-init.spec.ts +124 -10
  255. package/tests/capacitor/capacitor-run.spec.ts +14 -17
  256. package/tests/capacitor/capacitor-workspace.spec.ts +5 -3
  257. package/tests/commands/check.spec.ts +2 -2
  258. package/tests/commands/publish.spec.ts +2 -2
  259. package/tests/commands/typecheck.spec.ts +8 -0
  260. package/tests/electron/electron.spec.ts +12 -10
  261. package/tests/engines/base-engine.spec.ts +37 -0
  262. package/tests/engines/vite-engine.spec.ts +115 -3
  263. package/tests/utils/vite-config.spec.ts +162 -90
  264. package/tests/workers/client-worker.spec.ts +690 -0
  265. package/tests/workers/server-build-worker.spec.ts +3 -3
@@ -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
@@ -91,6 +93,19 @@ vi.mock("consola", () => ({
91
93
 
92
94
  const PKG_PATH = "/fake/pkg";
93
95
 
96
+ /** Gradle 실행 명령을 찾는다 (Windows: cmd /c gradlew.bat, 그 외: gradlew) */
97
+ function findGradleCall(calls: { command: string; args: string[] }[]) {
98
+ return calls.find(
99
+ (c) => c.command.includes("gradlew") || (c.command === "cmd" && c.args.includes("gradlew.bat")),
100
+ );
101
+ }
102
+
103
+ function findGradleCallIndex(calls: { command: string; args: string[] }[]) {
104
+ return calls.findIndex(
105
+ (c) => c.command.includes("gradlew") || (c.command === "cmd" && c.args.includes("gradlew.bat")),
106
+ );
107
+ }
108
+
94
109
  function setupDefaultMocks() {
95
110
  mockFsxExists.mockResolvedValue(true);
96
111
 
@@ -179,7 +194,7 @@ describe("Capacitor 빌드", () => {
179
194
 
180
195
  await cap.build("/fake/out");
181
196
 
182
- const gradleCmd = execaCalls.find((c) => c.command.includes("gradlew"));
197
+ const gradleCmd = findGradleCall(execaCalls);
183
198
  expect(gradleCmd).toBeDefined();
184
199
  expect(gradleCmd!.args).toContain("bundleRelease");
185
200
  });
@@ -195,7 +210,7 @@ describe("Capacitor 빌드", () => {
195
210
 
196
211
  await cap.build("/fake/out");
197
212
 
198
- const gradleCmd = execaCalls.find((c) => c.command.includes("gradlew"));
213
+ const gradleCmd = findGradleCall(execaCalls);
199
214
  expect(gradleCmd).toBeDefined();
200
215
  expect(gradleCmd!.args).toContain("assembleRelease");
201
216
  });
@@ -212,7 +227,7 @@ describe("Capacitor 빌드", () => {
212
227
 
213
228
  await cap.build("/fake/out");
214
229
 
215
- const gradleCmd = execaCalls.find((c) => c.command.includes("gradlew"));
230
+ const gradleCmd = findGradleCall(execaCalls);
216
231
  expect(gradleCmd).toBeDefined();
217
232
  expect(gradleCmd!.args).toContain("assembleDebug");
218
233
  });
@@ -270,14 +285,14 @@ describe("Capacitor 빌드", () => {
270
285
 
271
286
  // cap copy가 gradlew보다 먼저 실행되는지 확인
272
287
  const capCopyIndex = execaCalls.findIndex(
273
- (c) => c.command === "npx" && c.args.includes("cap") && c.args.includes("copy"),
288
+ (c) => c.command === "pnpm" && c.args.includes("cap") && c.args.includes("copy"),
274
289
  );
275
- const gradlewIndex = execaCalls.findIndex((c) => c.command.includes("gradlew"));
290
+ const gradlewIndex = findGradleCallIndex(execaCalls);
276
291
  expect(capCopyIndex).toBeGreaterThanOrEqual(0);
277
292
  expect(gradlewIndex).toBeGreaterThan(capCopyIndex);
278
293
  });
279
294
 
280
- it("Windows에서 gradlew.bat을 사용한다", async () => {
295
+ it("Windows에서 cmd /c gradlew.bat으로 Gradle실행한다", async () => {
281
296
  const originalPlatform = process.platform;
282
297
  Object.defineProperty(process, "platform", { value: "win32" });
283
298
 
@@ -292,8 +307,37 @@ describe("Capacitor 빌드", () => {
292
307
 
293
308
  await cap.build("/fake/out");
294
309
 
295
- const gradleCmd = execaCalls.find((c) => c.command.includes("gradlew"));
296
- expect(gradleCmd!.command).toContain("gradlew.bat");
310
+ const gradleCmd = execaCalls.find((c) => c.command === "cmd");
311
+ expect(gradleCmd).toBeDefined();
312
+ expect(gradleCmd!.args[0]).toBe("/c");
313
+ expect(gradleCmd!.args[1]).toBe("gradlew.bat");
314
+ expect(gradleCmd!.args).toContain("assembleRelease");
315
+ expect(gradleCmd!.args).toContain("--no-daemon");
316
+ } finally {
317
+ Object.defineProperty(process, "platform", { value: originalPlatform });
318
+ }
319
+ });
320
+
321
+ it("Linux/macOS에서 gradlew를 직접 실행한다", async () => {
322
+ const originalPlatform = process.platform;
323
+ Object.defineProperty(process, "platform", { value: "linux" });
324
+
325
+ try {
326
+ const { Capacitor } = await import("../../src/capacitor/capacitor.js");
327
+
328
+ const cap = await Capacitor.create(PKG_PATH, {
329
+ appId: "com.test.app",
330
+ appName: "Test App",
331
+ platform: { android: {} },
332
+ });
333
+
334
+ await cap.build("/fake/out");
335
+
336
+ const gradleCmd = findGradleCall(execaCalls);
337
+ expect(gradleCmd).toBeDefined();
338
+ expect(gradleCmd!.command).toContain("gradlew");
339
+ expect(gradleCmd!.command).not.toContain("gradlew.bat");
340
+ expect(gradleCmd!.args).toContain("assembleRelease");
297
341
  } finally {
298
342
  Object.defineProperty(process, "platform", { value: originalPlatform });
299
343
  }
@@ -375,6 +419,44 @@ describe("Capacitor 빌드", () => {
375
419
  await expect(cap.build("/fake/out")).rejects.toThrow("keystore");
376
420
  });
377
421
 
422
+ it("비밀번호에 $, \\, ' 등 특수문자가 있으면 Groovy 이스케이프하여 build.gradle에 기록한다", async () => {
423
+ const { Capacitor } = await import("../../src/capacitor/capacitor.js");
424
+ mockFsxExists.mockResolvedValue(true);
425
+
426
+ const cap = await Capacitor.create(PKG_PATH, {
427
+ appId: "com.test.app",
428
+ appName: "Test App",
429
+ platform: {
430
+ android: {
431
+ sign: {
432
+ keystore: "my-release.keystore",
433
+ storePassword: "12tlavmf#$",
434
+ alias: "my-key",
435
+ password: "pass\\'word",
436
+ },
437
+ },
438
+ },
439
+ });
440
+
441
+ await cap.build("/fake/out");
442
+
443
+ const writeCalls = mockFsxWrite.mock.calls;
444
+ const gradleWrite = writeCalls.find(
445
+ (call) =>
446
+ typeof call[0] === "string" &&
447
+ call[0].includes("build.gradle") &&
448
+ typeof call[1] === "string" &&
449
+ call[1].includes("signingConfigs"),
450
+ );
451
+ expect(gradleWrite).toBeDefined();
452
+
453
+ const gradleContent = gradleWrite![1] as string;
454
+ // $ 는 Groovy single-quoted string에서 그대로 유지
455
+ expect(gradleContent).toContain("storePassword '12tlavmf#$'");
456
+ // \ → \\, ' → \' 이스케이프
457
+ expect(gradleContent).toContain("keyPassword 'pass\\\\\\'word'");
458
+ });
459
+
378
460
  it("signed 빌드 산출물이 unsigned 접미사 없이 복사된다", async () => {
379
461
  const { Capacitor } = await import("../../src/capacitor/capacitor.js");
380
462
 
@@ -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,102 @@ describe("Android 네이티브 설정", () => {
531
549
  expect(manifestWrite).toBeDefined();
532
550
  });
533
551
 
552
+ it("styles.xml의 Theme.SplashScreen parent를 변경하고 android:background를 android:windowBackground로 변경한다", 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
+ const content = stylesWrite![1] as string;
571
+ // @drawable/splash는 유지
572
+ expect(content).toContain("@drawable/splash");
573
+ // Theme.SplashScreen은 제거됨
574
+ expect(content).not.toContain('parent="Theme.SplashScreen"');
575
+ // android:background → android:windowBackground로 변경됨
576
+ expect(content).toContain('"android:windowBackground">@drawable/splash');
577
+ expect(content).not.toContain('"android:background">@drawable/splash');
578
+ });
579
+
580
+ it("이미 변경된 styles.xml은 재변경하지 않는다", async () => {
581
+ mockFsxRead.mockImplementation((p: string) => {
582
+ if (p.includes("styles.xml")) {
583
+ return `<?xml version="1.0" encoding="utf-8"?>
584
+ <resources>
585
+ <style name="AppTheme.NoActionBarLaunch" parent="Theme.AppCompat.DayNight.NoActionBar">
586
+ <item name="android:windowBackground">@drawable/splash</item>
587
+ </style>
588
+ </resources>`;
589
+ }
590
+ if (p.includes("AndroidManifest.xml")) {
591
+ return '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n<application>\n<activity android:name=".MainActivity">\n</activity>\n</application>\n</manifest>';
592
+ }
593
+ if (p.includes("build.gradle")) {
594
+ return `android {
595
+ defaultConfig {
596
+ versionCode 1
597
+ versionName "1.0"
598
+ minSdkVersion rootProject.ext.minSdkVersion
599
+ targetSdkVersion rootProject.ext.targetSdkVersion
600
+ }
601
+ buildTypes { release { } }
602
+ }`;
603
+ }
604
+ if (p.includes("gradle.properties")) {
605
+ return "org.gradle.jvmargs=-Xmx2048m";
606
+ }
607
+ return "";
608
+ });
609
+
610
+ const { Capacitor } = await import("../../src/capacitor/capacitor.js");
611
+ const cap = await Capacitor.create(PKG_PATH, {
612
+ appId: "com.test.app",
613
+ appName: "Test App",
614
+ platform: { android: {} },
615
+ });
616
+ await cap.initialize();
617
+
618
+ const writeCalls = mockFsxWrite.mock.calls;
619
+ const stylesWrite = writeCalls.find(
620
+ (call) =>
621
+ typeof call[0] === "string" &&
622
+ call[0].includes("styles.xml"),
623
+ );
624
+ expect(stylesWrite).toBeUndefined();
625
+ });
626
+
627
+ it("styles.xml이 없으면 warn을 출력한다", async () => {
628
+ mockFsxExists.mockImplementation((p: string) => {
629
+ const n = p.replace(/\\/g, "/");
630
+ if (n.includes(".capacitor.lock")) return false;
631
+ if (n.includes("styles.xml")) return false;
632
+ return true;
633
+ });
634
+
635
+ const { Capacitor } = await import("../../src/capacitor/capacitor.js");
636
+ const cap = await Capacitor.create(PKG_PATH, {
637
+ appId: "com.test.app",
638
+ appName: "Test App",
639
+ platform: { android: {} },
640
+ });
641
+ await cap.initialize();
642
+
643
+ expect(mockLoggerWarn).toHaveBeenCalledWith(
644
+ expect.stringContaining("styles.xml"),
645
+ );
646
+ });
647
+
534
648
  it("application 태그에 커스텀 속성을 추가한다", async () => {
535
649
  const { Capacitor } = await import("../../src/capacitor/capacitor.js");
536
650
  const cap = await Capacitor.create(PKG_PATH, {
@@ -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, "/"),
@@ -49,7 +49,7 @@ const execaCalls: { command: string; args: string[] }[] = [];
49
49
  let execaFactory: (...args: unknown[]) => Promise<{ stdout: string; stderr: string; exitCode: number }> = () =>
50
50
  Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
51
51
 
52
- const mockCpxExec = vi.fn((...args: unknown[]) => {
52
+ const mockCpxSpawn = vi.fn((...args: unknown[]) => {
53
53
  execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
54
54
  return execaFactory(...args);
55
55
  });
@@ -84,7 +84,9 @@ vi.mock("consola", () => ({
84
84
  warn: mockLoggerWarn,
85
85
  info: vi.fn(),
86
86
  }),
87
+ level: 0,
87
88
  },
89
+ LogLevels: { debug: 4 },
88
90
  }));
89
91
 
90
92
  //#endregion
@@ -175,14 +177,14 @@ describe("Capacitor.run()", () => {
175
177
 
176
178
  // cap copy android + cap run android
177
179
  const capCmds = execaCalls.filter(
178
- (c) => c.command === "npx" && c.args.includes("cap"),
180
+ (c) => c.command === "pnpm" && c.args.includes("cap"),
179
181
  );
180
182
  expect(capCmds.some((c) => c.args.includes("copy") && c.args.includes("android"))).toBe(true);
181
183
  expect(capCmds.some((c) => c.args.includes("run") && c.args.includes("android"))).toBe(true);
182
184
  });
183
185
 
184
- // Unit: adb kill-server retry on cap run failure
185
- it("retries cap run after adb kill-server on android platform failure", async () => {
186
+ // Unit: cap run 실패 시 adb kill-server 호출 에러를 re-throw한다
187
+ it("calls adb kill-server and re-throws on android platform cap run failure", async () => {
186
188
  const { Capacitor } = await import("../../src/capacitor/capacitor.js");
187
189
 
188
190
  let capRunCallCount = 0;
@@ -191,16 +193,13 @@ describe("Capacitor.run()", () => {
191
193
  const cmdArgs = (args[1] as string[] | undefined) ?? [];
192
194
 
193
195
  if (
194
- cmd === "npx" &&
196
+ cmd === "pnpm" &&
195
197
  cmdArgs.includes("cap") &&
196
198
  cmdArgs.includes("run") &&
197
199
  cmdArgs.includes("android")
198
200
  ) {
199
201
  capRunCallCount++;
200
- if (capRunCallCount === 1) {
201
- // First cap run fails
202
- return Promise.reject(new Error("cap run failed"));
203
- }
202
+ return Promise.reject(new Error("cap run failed"));
204
203
  }
205
204
  return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
206
205
  };
@@ -211,16 +210,14 @@ describe("Capacitor.run()", () => {
211
210
  platform: { android: {} },
212
211
  });
213
212
 
214
- await cap.run("http://localhost:4200");
213
+ await expect(cap.run("http://localhost:4200")).rejects.toThrow("cap run failed");
215
214
 
216
- // adb kill-server should have been called between the two cap run attempts
215
+ // adb kill-server should have been called
217
216
  expect(
218
217
  execaCalls.some((c) => c.command === "adb" && c.args.includes("kill-server")),
219
218
  ).toBe(true);
220
- expect(capRunCallCount).toBe(2);
221
- expect(mockLoggerWarn).toHaveBeenCalledWith(
222
- expect.stringContaining("adb kill-server"),
223
- );
219
+ // cap run은 한 번만 호출 (재시도 없음)
220
+ expect(capRunCallCount).toBe(1);
224
221
  });
225
222
 
226
223
  // Unit: _updateServerUrl replaces existing url
@@ -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
  });
@@ -75,7 +75,9 @@ vi.mock("fs/promises", () => ({
75
75
  vi.mock("consola", () => ({
76
76
  consola: {
77
77
  withTag: () => ({ debug: vi.fn(), warn: vi.fn() }),
78
+ level: 0,
78
79
  },
80
+ LogLevels: { debug: 4 },
79
81
  }));
80
82
 
81
83
  //#endregion
@@ -37,8 +37,8 @@ vi.mock("../../src/utils/sd-config", () => ({
37
37
 
38
38
  vi.mock("@simplysm/core-node", () => ({
39
39
  cpx: {
40
- exec: mocks.execa,
41
- execSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
40
+ spawn: mocks.execa,
41
+ spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
42
42
  },
43
43
  }));
44
44
 
@@ -51,8 +51,8 @@ vi.mock("../../src/utils/replace-deps", () => ({
51
51
  vi.mock("@simplysm/core-node", () => ({
52
52
  fsx: mocks.fsx,
53
53
  cpx: {
54
- exec: mocks.execa,
55
- execSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
54
+ spawn: mocks.execa,
55
+ spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
56
56
  },
57
57
  }));
58
58
 
@@ -493,6 +493,14 @@ describe("executeTypecheck", () => {
493
493
 
494
494
  //#endregion
495
495
 
496
+ it("throws when loadSdConfig fails (fail fast)", async () => {
497
+ mocks.loadSdConfig.mockRejectedValue(new Error("sd.config.ts not found"));
498
+ mocks.discoverWorkspacePackages.mockReturnValue(new Map());
499
+ mocks.mergeTestsPackagesIntoConfig.mockReturnValue({ merged: {}, pathMap: new Map() });
500
+
501
+ await expect(executeTypecheck({ targets: [], options: [] })).rejects.toThrow("sd.config.ts not found");
502
+ });
503
+
496
504
  //#region Slice 1: executeTypecheck lint integration (Feature 3.2)
497
505
 
498
506
  describe("lint integration", () => {