@simplysm/sd-cli 13.0.68 → 13.0.70

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 (201) hide show
  1. package/README.md +10 -957
  2. package/dist/builders/BaseBuilder.d.ts +23 -23
  3. package/dist/builders/BaseBuilder.d.ts.map +1 -1
  4. package/dist/builders/BaseBuilder.js +15 -15
  5. package/dist/builders/DtsBuilder.d.ts +4 -4
  6. package/dist/builders/DtsBuilder.js +1 -1
  7. package/dist/builders/LibraryBuilder.d.ts +3 -3
  8. package/dist/builders/types.d.ts +10 -10
  9. package/dist/capacitor/capacitor.d.ts +36 -36
  10. package/dist/capacitor/capacitor.js +63 -63
  11. package/dist/capacitor/capacitor.js.map +1 -1
  12. package/dist/commands/add-client.d.ts +8 -8
  13. package/dist/commands/add-client.js +15 -15
  14. package/dist/commands/add-client.js.map +1 -1
  15. package/dist/commands/add-server.d.ts +9 -9
  16. package/dist/commands/add-server.js +13 -13
  17. package/dist/commands/add-server.js.map +1 -1
  18. package/dist/commands/build.d.ts +9 -9
  19. package/dist/commands/check.js +3 -3
  20. package/dist/commands/check.js.map +1 -1
  21. package/dist/commands/dev.d.ts +9 -9
  22. package/dist/commands/device.d.ts +9 -9
  23. package/dist/commands/device.d.ts.map +1 -1
  24. package/dist/commands/device.js +17 -17
  25. package/dist/commands/device.js.map +1 -1
  26. package/dist/commands/init.d.ts +6 -6
  27. package/dist/commands/init.js +12 -12
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/lint.d.ts +23 -23
  30. package/dist/commands/lint.d.ts.map +1 -1
  31. package/dist/commands/lint.js +25 -25
  32. package/dist/commands/lint.js.map +1 -1
  33. package/dist/commands/publish.d.ts +13 -13
  34. package/dist/commands/publish.d.ts.map +1 -1
  35. package/dist/commands/publish.js +61 -61
  36. package/dist/commands/publish.js.map +1 -1
  37. package/dist/commands/replace-deps.d.ts +3 -3
  38. package/dist/commands/replace-deps.d.ts.map +1 -1
  39. package/dist/commands/replace-deps.js +1 -1
  40. package/dist/commands/replace-deps.js.map +1 -1
  41. package/dist/commands/typecheck.d.ts +20 -20
  42. package/dist/commands/typecheck.d.ts.map +1 -1
  43. package/dist/commands/typecheck.js +20 -20
  44. package/dist/commands/typecheck.js.map +1 -1
  45. package/dist/commands/watch.d.ts +7 -7
  46. package/dist/electron/electron.d.ts +27 -27
  47. package/dist/electron/electron.js +32 -32
  48. package/dist/electron/electron.js.map +1 -1
  49. package/dist/infra/ResultCollector.d.ts +9 -9
  50. package/dist/infra/ResultCollector.js +5 -5
  51. package/dist/infra/SignalHandler.d.ts +7 -7
  52. package/dist/infra/SignalHandler.js +4 -4
  53. package/dist/infra/WorkerManager.d.ts +14 -14
  54. package/dist/infra/WorkerManager.js +11 -11
  55. package/dist/orchestrators/BuildOrchestrator.d.ts +19 -19
  56. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  57. package/dist/orchestrators/BuildOrchestrator.js +26 -26
  58. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  59. package/dist/orchestrators/DevOrchestrator.d.ts +25 -25
  60. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
  61. package/dist/orchestrators/DevOrchestrator.js +30 -30
  62. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  63. package/dist/orchestrators/WatchOrchestrator.d.ts +13 -13
  64. package/dist/orchestrators/WatchOrchestrator.js +17 -17
  65. package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
  66. package/dist/sd-cli-entry.d.ts +2 -2
  67. package/dist/sd-cli-entry.js +38 -38
  68. package/dist/sd-cli-entry.js.map +1 -1
  69. package/dist/sd-cli.d.ts +2 -2
  70. package/dist/sd-cli.js +1 -1
  71. package/dist/sd-cli.js.map +1 -1
  72. package/dist/sd-config.types.d.ts +84 -84
  73. package/dist/sd-config.types.d.ts.map +1 -1
  74. package/dist/utils/build-env.d.ts +1 -1
  75. package/dist/utils/config-editor.d.ts +5 -5
  76. package/dist/utils/config-editor.js +2 -2
  77. package/dist/utils/config-editor.js.map +1 -1
  78. package/dist/utils/copy-public.d.ts +9 -9
  79. package/dist/utils/copy-src.d.ts +9 -9
  80. package/dist/utils/esbuild-config.d.ts +30 -30
  81. package/dist/utils/esbuild-config.d.ts.map +1 -1
  82. package/dist/utils/output-utils.d.ts +6 -6
  83. package/dist/utils/package-utils.d.ts +6 -6
  84. package/dist/utils/package-utils.js +1 -1
  85. package/dist/utils/package-utils.js.map +1 -1
  86. package/dist/utils/rebuild-manager.js +3 -3
  87. package/dist/utils/rebuild-manager.js.map +1 -1
  88. package/dist/utils/replace-deps.d.ts +25 -25
  89. package/dist/utils/replace-deps.js +3 -3
  90. package/dist/utils/replace-deps.js.map +1 -1
  91. package/dist/utils/sd-config.d.ts +3 -3
  92. package/dist/utils/sd-config.js +3 -3
  93. package/dist/utils/sd-config.js.map +1 -1
  94. package/dist/utils/tailwind-config-deps.d.ts +3 -3
  95. package/dist/utils/template.d.ts +8 -8
  96. package/dist/utils/tsconfig.d.ts +16 -16
  97. package/dist/utils/tsconfig.js +2 -2
  98. package/dist/utils/tsconfig.js.map +1 -1
  99. package/dist/utils/typecheck-serialization.d.ts +8 -8
  100. package/dist/utils/vite-config.d.ts +8 -8
  101. package/dist/utils/vite-config.d.ts.map +1 -1
  102. package/dist/utils/vite-config.js +3 -3
  103. package/dist/utils/worker-events.d.ts +12 -12
  104. package/dist/utils/worker-events.d.ts.map +1 -1
  105. package/dist/utils/worker-utils.d.ts +3 -3
  106. package/dist/utils/worker-utils.js +2 -2
  107. package/dist/utils/worker-utils.js.map +1 -1
  108. package/dist/workers/client.worker.d.ts +14 -14
  109. package/dist/workers/client.worker.d.ts.map +1 -1
  110. package/dist/workers/client.worker.js +1 -1
  111. package/dist/workers/client.worker.js.map +1 -1
  112. package/dist/workers/dts.worker.d.ts +13 -13
  113. package/dist/workers/dts.worker.d.ts.map +1 -1
  114. package/dist/workers/dts.worker.js +3 -3
  115. package/dist/workers/dts.worker.js.map +1 -1
  116. package/dist/workers/library.worker.d.ts +12 -12
  117. package/dist/workers/library.worker.js +1 -1
  118. package/dist/workers/library.worker.js.map +1 -1
  119. package/dist/workers/lint.worker.d.ts +1 -1
  120. package/dist/workers/server-runtime.worker.d.ts +6 -6
  121. package/dist/workers/server-runtime.worker.js +6 -6
  122. package/dist/workers/server-runtime.worker.js.map +1 -1
  123. package/dist/workers/server.worker.d.ts +20 -20
  124. package/dist/workers/server.worker.d.ts.map +1 -1
  125. package/dist/workers/server.worker.js +6 -6
  126. package/dist/workers/server.worker.js.map +1 -1
  127. package/package.json +8 -7
  128. package/src/builders/BaseBuilder.ts +33 -33
  129. package/src/builders/DtsBuilder.ts +5 -5
  130. package/src/builders/LibraryBuilder.ts +9 -9
  131. package/src/builders/types.ts +10 -10
  132. package/src/capacitor/capacitor.ts +119 -119
  133. package/src/commands/add-client.ts +31 -31
  134. package/src/commands/add-server.ts +34 -34
  135. package/src/commands/build.ts +9 -9
  136. package/src/commands/check.ts +5 -5
  137. package/src/commands/dev.ts +9 -9
  138. package/src/commands/device.ts +30 -30
  139. package/src/commands/init.ts +25 -25
  140. package/src/commands/lint.ts +64 -64
  141. package/src/commands/publish.ts +139 -139
  142. package/src/commands/replace-deps.ts +4 -4
  143. package/src/commands/typecheck.ts +74 -74
  144. package/src/commands/watch.ts +7 -7
  145. package/src/electron/electron.ts +51 -51
  146. package/src/infra/ResultCollector.ts +9 -9
  147. package/src/infra/SignalHandler.ts +7 -7
  148. package/src/infra/WorkerManager.ts +14 -14
  149. package/src/orchestrators/BuildOrchestrator.ts +76 -76
  150. package/src/orchestrators/DevOrchestrator.ts +88 -88
  151. package/src/orchestrators/WatchOrchestrator.ts +39 -39
  152. package/src/sd-cli-entry.ts +43 -43
  153. package/src/sd-cli.ts +15 -15
  154. package/src/sd-config.types.ts +85 -85
  155. package/src/utils/build-env.ts +1 -1
  156. package/src/utils/config-editor.ts +19 -19
  157. package/src/utils/copy-public.ts +17 -17
  158. package/src/utils/copy-src.ts +11 -11
  159. package/src/utils/esbuild-config.ts +33 -33
  160. package/src/utils/output-utils.ts +11 -11
  161. package/src/utils/package-utils.ts +12 -12
  162. package/src/utils/rebuild-manager.ts +3 -3
  163. package/src/utils/replace-deps.ts +361 -361
  164. package/src/utils/sd-config.ts +44 -44
  165. package/src/utils/tailwind-config-deps.ts +98 -98
  166. package/src/utils/template.ts +56 -56
  167. package/src/utils/tsconfig.ts +127 -127
  168. package/src/utils/typecheck-serialization.ts +86 -86
  169. package/src/utils/vite-config.ts +341 -341
  170. package/src/utils/worker-events.ts +16 -16
  171. package/src/utils/worker-utils.ts +45 -45
  172. package/src/workers/client.worker.ts +34 -34
  173. package/src/workers/dts.worker.ts +467 -467
  174. package/src/workers/library.worker.ts +314 -314
  175. package/src/workers/lint.worker.ts +16 -16
  176. package/src/workers/server-runtime.worker.ts +157 -157
  177. package/src/workers/server.worker.ts +572 -572
  178. package/templates/add-client/__CLIENT__/package.json.hbs +1 -1
  179. package/templates/add-server/__SERVER__/package.json.hbs +2 -2
  180. package/templates/init/package.json.hbs +3 -3
  181. package/tests/config-editor.spec.ts +160 -0
  182. package/tests/copy-src.spec.ts +50 -0
  183. package/tests/get-compiler-options-for-package.spec.ts +139 -0
  184. package/tests/get-package-source-files.spec.ts +181 -0
  185. package/tests/get-types-from-package-json.spec.ts +107 -0
  186. package/tests/infra/ResultCollector.spec.ts +39 -0
  187. package/tests/infra/SignalHandler.spec.ts +38 -0
  188. package/tests/infra/WorkerManager.spec.ts +97 -0
  189. package/tests/load-ignore-patterns.spec.ts +188 -0
  190. package/tests/load-sd-config.spec.ts +137 -0
  191. package/tests/package-utils.spec.ts +188 -0
  192. package/tests/parse-root-tsconfig.spec.ts +89 -0
  193. package/tests/replace-deps.spec.ts +308 -0
  194. package/tests/run-lint.spec.ts +415 -0
  195. package/tests/run-typecheck.spec.ts +653 -0
  196. package/tests/run-watch.spec.ts +75 -0
  197. package/tests/sd-cli.spec.ts +330 -0
  198. package/tests/tailwind-config-deps.spec.ts +30 -0
  199. package/tests/template.spec.ts +70 -0
  200. package/tests/utils/rebuild-manager.spec.ts +43 -0
  201. package/tests/write-changed-output-files.spec.ts +97 -0
@@ -0,0 +1,653 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
2
+ import "@simplysm/core-common"; // Import to use Map.getOrCreate extension method
3
+
4
+ // 외부 의존성 모킹
5
+ vi.mock("typescript", () => {
6
+ const DiagnosticCategory = {
7
+ Error: 1,
8
+ Warning: 0,
9
+ };
10
+ return {
11
+ default: {
12
+ sys: {
13
+ readFile: vi.fn(),
14
+ newLine: "\n",
15
+ },
16
+ readConfigFile: vi.fn(),
17
+ parseJsonConfigFileContent: vi.fn(),
18
+ createIncrementalProgram: vi.fn(),
19
+ getPreEmitDiagnostics: vi.fn(),
20
+ formatDiagnosticsWithColorAndContext: vi.fn(),
21
+ sortAndDeduplicateDiagnostics: vi.fn(),
22
+ createSourceFile: vi.fn().mockReturnValue({}),
23
+ ScriptTarget: { Latest: 99 },
24
+ ScriptKind: { TS: 3 },
25
+ DiagnosticCategory,
26
+ },
27
+ };
28
+ });
29
+
30
+ vi.mock("@simplysm/core-node", () => {
31
+ const posix = (p: string) => p.replace(/\\/g, "/");
32
+ const isChildPath = (child: string, parent: string) => {
33
+ if (child === parent) return false;
34
+ const parentWithSlash = parent.endsWith("/") ? parent : parent + "/";
35
+ return child.startsWith(parentWithSlash);
36
+ };
37
+
38
+ // Mock worker proxy - provides build and terminate methods
39
+ const createMockWorkerProxy = () => ({
40
+ build: vi.fn(() =>
41
+ Promise.resolve({
42
+ success: true,
43
+ diagnostics: [],
44
+ errorCount: 0,
45
+ warningCount: 0,
46
+ }),
47
+ ),
48
+ terminate: vi.fn(() => Promise.resolve()),
49
+ });
50
+
51
+ return {
52
+ fsExists: vi.fn(),
53
+ fsExistsSync: vi.fn(() => false),
54
+ fsReadJson: vi.fn(),
55
+ fsReadSync: vi.fn(() => ""),
56
+ pathPosix: vi.fn(posix),
57
+ pathIsChildPath: vi.fn(isChildPath),
58
+ pathFilterByTargets: vi.fn((files: string[], targets: string[], cwd: string) => {
59
+ if (targets.length === 0) return files;
60
+ return files.filter((file) => {
61
+ const relativePath = posix(file.replace(cwd + "/", ""));
62
+ return targets.some(
63
+ (target) => relativePath === target || isChildPath(relativePath, target),
64
+ );
65
+ });
66
+ }),
67
+ Worker: {
68
+ create: vi.fn(() => createMockWorkerProxy()),
69
+ },
70
+ };
71
+ });
72
+
73
+ vi.mock("consola", () => {
74
+ const mockLogger = {
75
+ debug: vi.fn(),
76
+ info: vi.fn(),
77
+ error: vi.fn(),
78
+ warn: vi.fn(),
79
+ start: vi.fn(),
80
+ success: vi.fn(),
81
+ fail: vi.fn(),
82
+ withTag: vi.fn(() => mockLogger),
83
+ level: 3, // info level
84
+ };
85
+ return {
86
+ consola: mockLogger,
87
+ default: mockLogger,
88
+ LogLevels: { debug: 4, info: 3, warn: 2, error: 1 },
89
+ };
90
+ });
91
+
92
+ const mockJitiImport = vi.fn();
93
+ vi.mock("jiti", () => ({
94
+ createJiti: vi.fn(() => ({
95
+ import: mockJitiImport,
96
+ })),
97
+ }));
98
+
99
+ import path from "path";
100
+ import ts from "typescript";
101
+ import { fsExists, fsReadJson } from "@simplysm/core-node";
102
+ import { runTypecheck } from "../src/commands/typecheck";
103
+
104
+ describe("runTypecheck", () => {
105
+ let originalExitCode: typeof process.exitCode;
106
+ let originalCwd: () => string;
107
+
108
+ beforeEach(() => {
109
+ vi.clearAllMocks();
110
+ originalExitCode = process.exitCode;
111
+ originalCwd = process.cwd;
112
+ process.cwd = vi.fn().mockReturnValue("/project");
113
+ });
114
+
115
+ afterEach(() => {
116
+ vi.restoreAllMocks();
117
+ process.exitCode = originalExitCode;
118
+ process.cwd = originalCwd;
119
+ });
120
+
121
+ it("exits early when no files to typecheck", async () => {
122
+ vi.mocked(ts.readConfigFile).mockReturnValue({
123
+ config: {},
124
+ });
125
+
126
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
127
+ options: { lib: ["ES2024"], types: [] },
128
+ fileNames: [],
129
+ errors: [],
130
+ } as unknown as ts.ParsedCommandLine);
131
+
132
+ await runTypecheck({ targets: [], options: [] });
133
+
134
+ expect(process.exitCode).toBeUndefined();
135
+ });
136
+
137
+ it("sets exitCode to 1 when tsconfig.json fails to load", async () => {
138
+ vi.mocked(ts.readConfigFile).mockReturnValue({
139
+ error: {
140
+ category: ts.DiagnosticCategory.Error,
141
+ messageText: "Failed to read tsconfig.json",
142
+ } as ts.Diagnostic,
143
+ });
144
+
145
+ vi.mocked(ts.formatDiagnosticsWithColorAndContext).mockReturnValue("");
146
+
147
+ await runTypecheck({ targets: [], options: [] });
148
+
149
+ expect(process.exitCode).toBe(1);
150
+ });
151
+
152
+ it("sets exitCode to 1 when tsconfig.json fails to parse", async () => {
153
+ vi.mocked(ts.readConfigFile).mockReturnValue({
154
+ config: {},
155
+ });
156
+
157
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
158
+ options: {},
159
+ fileNames: [],
160
+ errors: [{ category: ts.DiagnosticCategory.Error, messageText: "Parse error" }],
161
+ } as unknown as ts.ParsedCommandLine);
162
+
163
+ vi.mocked(ts.formatDiagnosticsWithColorAndContext).mockReturnValue("");
164
+
165
+ await runTypecheck({ targets: [], options: [] });
166
+
167
+ expect(process.exitCode).toBe(1);
168
+ });
169
+
170
+ it("filters files using targets option", async () => {
171
+ vi.mocked(ts.readConfigFile).mockReturnValue({
172
+ config: {},
173
+ });
174
+
175
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
176
+ options: { lib: ["ES2024"], types: [] },
177
+ fileNames: [
178
+ "/project/packages/core-common/src/index.ts",
179
+ "/project/packages/core-node/src/index.ts",
180
+ "/project/packages/cli/src/index.ts",
181
+ ],
182
+ errors: [],
183
+ } as unknown as ts.ParsedCommandLine);
184
+
185
+ vi.mocked(fsExists).mockResolvedValue(false);
186
+ vi.mocked(fsReadJson).mockResolvedValue({ devDependencies: {} });
187
+
188
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockReturnValue(
189
+ [] as unknown as ts.SortedReadonlyArray<ts.Diagnostic>,
190
+ );
191
+
192
+ await runTypecheck({
193
+ targets: ["packages/core-common"],
194
+ options: [],
195
+ });
196
+
197
+ // Worker를 통해 실행되므로 exitCode가 설정되지 않아야 함
198
+ expect(process.exitCode).toBeUndefined();
199
+ });
200
+
201
+ it("filters files using targets option (including tests)", async () => {
202
+ vi.mocked(ts.readConfigFile).mockReturnValue({
203
+ config: {},
204
+ });
205
+
206
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
207
+ options: { lib: ["ES2024"], types: [] },
208
+ fileNames: [
209
+ "/project/packages/core-common/src/index.ts",
210
+ "/project/packages/core-common/tests/utils.spec.ts",
211
+ "/project/packages/core-node/src/index.ts",
212
+ "/project/packages/cli/src/index.ts",
213
+ ],
214
+ errors: [],
215
+ } as unknown as ts.ParsedCommandLine);
216
+
217
+ vi.mocked(fsExists).mockResolvedValue(false);
218
+ vi.mocked(fsReadJson).mockResolvedValue({ devDependencies: {} });
219
+
220
+ const { Worker } = await import("@simplysm/core-node");
221
+ const mockBuildDts = vi.fn(() =>
222
+ Promise.resolve({
223
+ success: true,
224
+ diagnostics: [],
225
+ errorCount: 0,
226
+ warningCount: 0,
227
+ }),
228
+ );
229
+ vi.mocked(Worker.create).mockReturnValue({
230
+ build: mockBuildDts,
231
+ terminate: vi.fn(() => Promise.resolve()),
232
+ } as unknown as ReturnType<typeof Worker.create>);
233
+
234
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockReturnValue(
235
+ [] as unknown as ts.SortedReadonlyArray<ts.Diagnostic>,
236
+ );
237
+
238
+ await runTypecheck({
239
+ targets: ["packages/core-common"],
240
+ options: [],
241
+ });
242
+
243
+ // Call buildDts only for core-common package
244
+ expect(mockBuildDts).toHaveBeenCalledTimes(2); // neutral: node + browser
245
+ for (const call of mockBuildDts.mock.calls as unknown[][]) {
246
+ expect((call[0] as { name: string }).name).toBe("core-common");
247
+ }
248
+ });
249
+
250
+ it("continues with default value when sd.config.ts fails to load", async () => {
251
+ vi.mocked(ts.readConfigFile).mockReturnValue({
252
+ config: {},
253
+ });
254
+
255
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
256
+ options: { lib: ["ES2024"], types: [] },
257
+ fileNames: ["/project/sd.config.ts"],
258
+ errors: [],
259
+ } as unknown as ts.ParsedCommandLine);
260
+
261
+ vi.mocked(fsExists).mockResolvedValue(false);
262
+
263
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockReturnValue(
264
+ [] as unknown as ts.SortedReadonlyArray<ts.Diagnostic>,
265
+ );
266
+
267
+ // Should proceed without error even if sd.config.ts fails to load
268
+ await runTypecheck({ targets: [], options: [] });
269
+
270
+ expect(process.exitCode).toBeUndefined();
271
+ });
272
+
273
+ it("uses default value when default export is not a function", async () => {
274
+ vi.mocked(ts.readConfigFile).mockReturnValue({
275
+ config: {},
276
+ });
277
+
278
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
279
+ options: { lib: ["ES2024"], types: [] },
280
+ fileNames: ["/project/packages/core-common/src/index.ts"],
281
+ errors: [],
282
+ } as unknown as ts.ParsedCommandLine);
283
+
284
+ vi.mocked(fsExists).mockResolvedValue(false);
285
+ vi.mocked(fsReadJson).mockResolvedValue({ devDependencies: {} });
286
+
287
+ // When sd.config.ts's default export is an object, not a function
288
+ mockJitiImport.mockResolvedValue({
289
+ default: { packages: {} }, // object, not a function
290
+ });
291
+
292
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockReturnValue(
293
+ [] as unknown as ts.SortedReadonlyArray<ts.Diagnostic>,
294
+ );
295
+
296
+ // Should proceed with default value without error
297
+ await runTypecheck({ targets: [], options: [] });
298
+
299
+ expect(process.exitCode).toBeUndefined();
300
+ });
301
+
302
+ it("uses default value when no default export in sd.config.ts", async () => {
303
+ vi.mocked(ts.readConfigFile).mockReturnValue({
304
+ config: {},
305
+ });
306
+
307
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
308
+ options: { lib: ["ES2024"], types: [] },
309
+ fileNames: ["/project/packages/core-common/src/index.ts"],
310
+ errors: [],
311
+ } as unknown as ts.ParsedCommandLine);
312
+
313
+ vi.mocked(fsExists).mockResolvedValue(false);
314
+ vi.mocked(fsReadJson).mockResolvedValue({ devDependencies: {} });
315
+
316
+ // When sd.config.ts has no default export
317
+ mockJitiImport.mockResolvedValue({
318
+ someOtherExport: () => ({}),
319
+ });
320
+
321
+ const { Worker } = await import("@simplysm/core-node");
322
+ vi.mocked(Worker.create).mockReturnValue({
323
+ build: vi.fn(() =>
324
+ Promise.resolve({
325
+ success: true,
326
+ diagnostics: [],
327
+ errorCount: 0,
328
+ warningCount: 0,
329
+ }),
330
+ ),
331
+ terminate: vi.fn(() => Promise.resolve()),
332
+ } as unknown as ReturnType<typeof Worker.create>);
333
+
334
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockReturnValue(
335
+ [] as unknown as ts.SortedReadonlyArray<ts.Diagnostic>,
336
+ );
337
+
338
+ // Should proceed with default value without error
339
+ await runTypecheck({ targets: [], options: [] });
340
+
341
+ expect(process.exitCode).toBeUndefined();
342
+ });
343
+
344
+ it("typecheck multiple packages", async () => {
345
+ vi.mocked(ts.readConfigFile).mockReturnValue({
346
+ config: {},
347
+ });
348
+
349
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
350
+ options: { lib: ["ES2024", "DOM"], types: [] },
351
+ fileNames: [
352
+ "/project/packages/core-node/src/index.ts",
353
+ "/project/packages/core-browser/src/index.ts",
354
+ "/project/packages/core-common/src/index.ts",
355
+ ],
356
+ errors: [],
357
+ } as unknown as ts.ParsedCommandLine);
358
+
359
+ vi.mocked(fsExists).mockResolvedValue(false);
360
+ vi.mocked(fsReadJson).mockResolvedValue({ devDependencies: {} });
361
+
362
+ const { Worker } = await import("@simplysm/core-node");
363
+ vi.mocked(Worker.create).mockReturnValue({
364
+ build: vi.fn(() =>
365
+ Promise.resolve({
366
+ success: true,
367
+ diagnostics: [],
368
+ errorCount: 0,
369
+ warningCount: 0,
370
+ }),
371
+ ),
372
+ terminate: vi.fn(() => Promise.resolve()),
373
+ } as unknown as ReturnType<typeof Worker.create>);
374
+
375
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockReturnValue(
376
+ [] as unknown as ts.SortedReadonlyArray<ts.Diagnostic>,
377
+ );
378
+
379
+ await runTypecheck({ targets: [], options: [] });
380
+
381
+ expect(process.exitCode).toBeUndefined();
382
+ });
383
+
384
+ it("sets exitCode to 1 when typecheck errors occur", async () => {
385
+ vi.mocked(ts.readConfigFile).mockReturnValue({ config: {} });
386
+
387
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
388
+ options: { lib: ["ES2024"], types: [] },
389
+ fileNames: ["/project/packages/core-common/src/index.ts"],
390
+ errors: [],
391
+ } as unknown as ts.ParsedCommandLine);
392
+
393
+ vi.mocked(fsExists).mockResolvedValue(false);
394
+ vi.mocked(fsReadJson).mockResolvedValue({ devDependencies: {} });
395
+
396
+ // Worker가 에러 결과를 반환하도록 모킹
397
+ const { Worker } = await import("@simplysm/core-node");
398
+ vi.mocked(Worker.create).mockReturnValue({
399
+ build: vi.fn(() =>
400
+ Promise.resolve({
401
+ success: false,
402
+ diagnostics: [
403
+ {
404
+ category: 1,
405
+ code: 2322,
406
+ messageText: "Type error",
407
+ fileName: "/project/packages/core-common/src/index.ts",
408
+ },
409
+ ],
410
+ errorCount: 1,
411
+ warningCount: 0,
412
+ }),
413
+ ),
414
+ terminate: vi.fn(() => Promise.resolve()),
415
+ } as unknown as ReturnType<typeof Worker.create>);
416
+
417
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockReturnValue(
418
+ [] as unknown as ts.SortedReadonlyArray<ts.Diagnostic>,
419
+ );
420
+ vi.mocked(ts.formatDiagnosticsWithColorAndContext).mockReturnValue("");
421
+
422
+ await runTypecheck({ targets: [], options: [] });
423
+
424
+ expect(process.exitCode).toBe(1);
425
+ });
426
+
427
+ it("creates other task when non-package files (like tests/) are included", async () => {
428
+ vi.mocked(ts.readConfigFile).mockReturnValue({ config: {} });
429
+
430
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
431
+ options: { lib: ["ES2024"], types: [] },
432
+ fileNames: ["/project/packages/core-node/src/index.ts", "/project/tests/orm/some-test.ts"],
433
+ errors: [],
434
+ } as unknown as ts.ParsedCommandLine);
435
+
436
+ vi.mocked(fsExists).mockResolvedValue(false);
437
+ // Set core-node as node target package
438
+ vi.mocked(fsReadJson).mockImplementation((filePath: string) => {
439
+ if (filePath.includes("core-node")) {
440
+ return Promise.resolve({ name: "@simplysm/core-node" });
441
+ }
442
+ return Promise.resolve({ devDependencies: {} });
443
+ });
444
+
445
+ // Mock sd.config.ts: set core-node as node target
446
+ mockJitiImport.mockResolvedValue({
447
+ default: () => ({
448
+ packages: {
449
+ "core-node": { target: "node" },
450
+ },
451
+ }),
452
+ });
453
+
454
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockReturnValue(
455
+ [] as unknown as ts.SortedReadonlyArray<ts.Diagnostic>,
456
+ );
457
+
458
+ const { Worker } = await import("@simplysm/core-node");
459
+ const mockBuildDts = vi.fn(() =>
460
+ Promise.resolve({
461
+ success: true,
462
+ diagnostics: [],
463
+ errorCount: 0,
464
+ warningCount: 0,
465
+ }),
466
+ );
467
+ vi.mocked(Worker.create).mockReturnValue({
468
+ build: mockBuildDts,
469
+ terminate: vi.fn(() => Promise.resolve()),
470
+ } as unknown as ReturnType<typeof Worker.create>);
471
+
472
+ await runTypecheck({ targets: [], options: [] });
473
+
474
+ // Verify buildDts call
475
+ expect(mockBuildDts).toHaveBeenCalled();
476
+
477
+ // Other task: called without pkgDir/env
478
+ const calls = mockBuildDts.mock.calls as unknown[][];
479
+ const nonPkgCall = calls.find((call) => (call[0] as { name: string }).name === "root");
480
+ expect(nonPkgCall).toBeDefined();
481
+ expect((nonPkgCall![0] as { pkgDir?: string }).pkgDir).toBeUndefined();
482
+ expect((nonPkgCall![0] as { env?: string }).env).toBeUndefined();
483
+
484
+ // core-node package task also exists
485
+ const pkgCall = calls.find((call) => (call[0] as { name: string }).name === "core-node");
486
+ expect(pkgCall).toBeDefined();
487
+ });
488
+
489
+ it("does not create other task when only packages/ files exist", async () => {
490
+ vi.mocked(ts.readConfigFile).mockReturnValue({ config: {} });
491
+
492
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
493
+ options: { lib: ["ES2024"], types: [] },
494
+ fileNames: ["/project/packages/core-common/src/index.ts"],
495
+ errors: [],
496
+ } as unknown as ts.ParsedCommandLine);
497
+
498
+ vi.mocked(fsExists).mockResolvedValue(false);
499
+ vi.mocked(fsReadJson).mockResolvedValue({ devDependencies: {} });
500
+
501
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockReturnValue(
502
+ [] as unknown as ts.SortedReadonlyArray<ts.Diagnostic>,
503
+ );
504
+
505
+ const { Worker } = await import("@simplysm/core-node");
506
+ const mockBuildDts = vi.fn(() =>
507
+ Promise.resolve({
508
+ success: true,
509
+ diagnostics: [],
510
+ errorCount: 0,
511
+ warningCount: 0,
512
+ }),
513
+ );
514
+ vi.mocked(Worker.create).mockReturnValue({
515
+ build: mockBuildDts,
516
+ terminate: vi.fn(() => Promise.resolve()),
517
+ } as unknown as ReturnType<typeof Worker.create>);
518
+
519
+ await runTypecheck({ targets: [], options: [] });
520
+
521
+ // Should have no call with name="root"
522
+ const nonPkgCall = (mockBuildDts.mock.calls as unknown[][]).find(
523
+ (call) => (call[0] as { name: string }).name === "root",
524
+ );
525
+ expect(nonPkgCall).toBeUndefined();
526
+ });
527
+ });
528
+
529
+ describe("executeTypecheck", () => {
530
+ const cwd = "/project";
531
+ let originalExitCode: typeof process.exitCode;
532
+ let originalCwd: () => string;
533
+
534
+ beforeEach(() => {
535
+ vi.clearAllMocks();
536
+ originalExitCode = process.exitCode;
537
+ originalCwd = process.cwd;
538
+ process.cwd = vi.fn().mockReturnValue(cwd);
539
+ });
540
+
541
+ afterEach(() => {
542
+ vi.restoreAllMocks();
543
+ process.exitCode = originalExitCode;
544
+ process.cwd = originalCwd;
545
+ });
546
+
547
+ it("returns success result when no errors", async () => {
548
+ const { executeTypecheck } = await import("../src/commands/typecheck");
549
+ const { Worker } = await import("@simplysm/core-node");
550
+
551
+ vi.mocked(ts.readConfigFile).mockReturnValue({ config: { compilerOptions: {} } });
552
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
553
+ fileNames: [path.resolve(cwd, "packages/core-common/src/index.ts")],
554
+ options: {},
555
+ errors: [],
556
+ } as never);
557
+
558
+ vi.mocked(fsExists).mockResolvedValue(false);
559
+ vi.mocked(fsReadJson).mockResolvedValue({ devDependencies: {} });
560
+
561
+ vi.mocked(Worker.create).mockReturnValue({
562
+ build: vi.fn(() =>
563
+ Promise.resolve({
564
+ success: true,
565
+ diagnostics: [],
566
+ errorCount: 0,
567
+ warningCount: 0,
568
+ }),
569
+ ),
570
+ terminate: vi.fn(() => Promise.resolve()),
571
+ } as unknown as ReturnType<typeof Worker.create>);
572
+
573
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockReturnValue(
574
+ [] as unknown as ts.SortedReadonlyArray<ts.Diagnostic>,
575
+ );
576
+
577
+ const result = await executeTypecheck({ targets: [], options: [] });
578
+
579
+ expect(result.success).toBe(true);
580
+ expect(result.errorCount).toBe(0);
581
+ // executeTypecheck should not set process.exitCode
582
+ expect(process.exitCode).toBeUndefined();
583
+ });
584
+
585
+ it("returns failure result without setting exitCode when errors exist", async () => {
586
+ const { executeTypecheck } = await import("../src/commands/typecheck");
587
+ const { Worker } = await import("@simplysm/core-node");
588
+
589
+ vi.mocked(ts.readConfigFile).mockReturnValue({ config: { compilerOptions: {} } });
590
+ vi.mocked(ts.parseJsonConfigFileContent).mockReturnValue({
591
+ fileNames: [path.resolve(cwd, "packages/core-common/src/index.ts")],
592
+ options: {},
593
+ errors: [],
594
+ } as never);
595
+
596
+ vi.mocked(fsExists).mockResolvedValue(false);
597
+ vi.mocked(fsReadJson).mockResolvedValue({ devDependencies: {} });
598
+
599
+ // Mock worker to return error results
600
+ vi.mocked(Worker.create).mockReturnValue({
601
+ build: vi.fn(() =>
602
+ Promise.resolve({
603
+ success: false,
604
+ diagnostics: [
605
+ {
606
+ category: 1,
607
+ code: 2322,
608
+ messageText: "Type error",
609
+ fileName: "/project/packages/core-common/src/index.ts",
610
+ },
611
+ ],
612
+ errorCount: 1,
613
+ warningCount: 0,
614
+ }),
615
+ ),
616
+ terminate: vi.fn(() => Promise.resolve()),
617
+ } as unknown as ReturnType<typeof Worker.create>);
618
+
619
+ vi.mocked(ts.sortAndDeduplicateDiagnostics).mockImplementation(
620
+ (diagnostics) => diagnostics as ts.SortedReadonlyArray<ts.Diagnostic>,
621
+ );
622
+ vi.mocked(ts.formatDiagnosticsWithColorAndContext).mockReturnValue("error output");
623
+
624
+ const result = await executeTypecheck({ targets: [], options: [] });
625
+
626
+ expect(result.success).toBe(false);
627
+ // core-common is neutral, so creates 2 tasks (node + browser), each with errorCount 1
628
+ expect(result.errorCount).toBe(2);
629
+ expect(result.formattedOutput).toBe("error output");
630
+ // executeTypecheck should not set process.exitCode
631
+ expect(process.exitCode).toBeUndefined();
632
+ });
633
+
634
+ it("returns failure result when tsconfig.json fails to load", async () => {
635
+ const { executeTypecheck } = await import("../src/commands/typecheck");
636
+
637
+ vi.mocked(ts.readConfigFile).mockReturnValue({
638
+ error: {
639
+ category: ts.DiagnosticCategory.Error,
640
+ messageText: "Failed to read tsconfig.json",
641
+ } as ts.Diagnostic,
642
+ });
643
+
644
+ vi.mocked(ts.formatDiagnosticsWithColorAndContext).mockReturnValue("");
645
+
646
+ const result = await executeTypecheck({ targets: [], options: [] });
647
+
648
+ expect(result.success).toBe(false);
649
+ expect(result.errorCount).toBe(1);
650
+ // executeTypecheck should not set process.exitCode
651
+ expect(process.exitCode).toBeUndefined();
652
+ });
653
+ });