@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
@@ -1,705 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import ts from "typescript";
3
- import path from "path";
4
- import fs from "fs";
5
- import os from "os";
6
-
7
- // --- Mock Setup ---
8
-
9
- const mockAnalyzeAsync = vi.fn().mockResolvedValue(undefined);
10
- const mockGetDiagnosticsForFile = vi.fn().mockReturnValue([]);
11
- const mockGetOptionDiagnostics = vi.fn().mockReturnValue([]);
12
- const mockGetResourceDependencies = vi.fn().mockReturnValue([]);
13
- const mockIgnoreForDiagnostics = new Set<ts.SourceFile>();
14
- const mockIgnoreForEmit = new Set<ts.SourceFile>();
15
-
16
- const ngtscConstructorSpy = vi.fn();
17
-
18
- // Create a real ts.Program from a temp directory to satisfy TypeScript's BuilderProgram API
19
- function createRealTsProgram(
20
- files: Record<string, string> = { "index.ts": "export const x = 1;" },
21
- ): { program: ts.Program; dir: string; rootNames: string[] } {
22
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "angular-compiler-test-"));
23
- const rootNames: string[] = [];
24
- for (const [name, content] of Object.entries(files)) {
25
- const filePath = path.join(dir, name);
26
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
27
- fs.writeFileSync(filePath, content, "utf-8");
28
- rootNames.push(filePath);
29
- }
30
-
31
- const options: ts.CompilerOptions = {
32
- target: ts.ScriptTarget.ESNext,
33
- module: ts.ModuleKind.ESNext,
34
- moduleResolution: ts.ModuleResolutionKind.Bundler,
35
- noEmit: true,
36
- strict: false,
37
- skipLibCheck: true,
38
- types: [],
39
- };
40
- const host = ts.createCompilerHost(options);
41
- const program = ts.createProgram(rootNames, options, host);
42
- return { program, dir, rootNames };
43
- }
44
-
45
- let realProgram: { program: ts.Program; dir: string; rootNames: string[] };
46
-
47
- vi.mock("../../src/utils/angular-build", () => {
48
- class NgtscProgram {
49
- compiler = {
50
- analyzeAsync: mockAnalyzeAsync,
51
- getDiagnosticsForFile: mockGetDiagnosticsForFile,
52
- getOptionDiagnostics: mockGetOptionDiagnostics,
53
- getResourceDependencies: mockGetResourceDependencies,
54
- ignoreForDiagnostics: mockIgnoreForDiagnostics,
55
- ignoreForEmit: mockIgnoreForEmit,
56
- };
57
- constructor(...args: unknown[]) {
58
- ngtscConstructorSpy(...args);
59
- }
60
- getTsProgram() {
61
- return realProgram.program;
62
- }
63
- }
64
- return {
65
- NgtscProgram,
66
- OptimizeFor: { WholeProgram: 0, SingleFile: 1 },
67
- };
68
- });
69
-
70
- const { AngularCompiler, AngularSourceFileCache } = await import(
71
- "../../src/utils/angular-compiler"
72
- );
73
-
74
- // --- Tests ---
75
-
76
- beforeEach(() => {
77
- vi.clearAllMocks();
78
- mockIgnoreForDiagnostics.clear();
79
- mockIgnoreForEmit.clear();
80
- mockGetResourceDependencies.mockReturnValue([]);
81
- mockGetDiagnosticsForFile.mockReturnValue([]);
82
-
83
- realProgram = createRealTsProgram();
84
- });
85
-
86
- // --- Unit Tests: Slice 2 — AngularCompiler ---
87
-
88
- describe("AngularCompiler — Unit Tests", () => {
89
- it("initialize() 전 getTsProgram()은 에러를 던진다", () => {
90
- const compiler = new AngularCompiler({
91
- rootNames: ["src/main.ts"],
92
- compilerOptions: { target: ts.ScriptTarget.ESNext },
93
- });
94
-
95
- expect(() => compiler.getTsProgram()).toThrow("initialize()");
96
- });
97
-
98
- it("initialize() 전 compiler getter는 에러를 던진다", () => {
99
- const compiler = new AngularCompiler({
100
- rootNames: ["src/main.ts"],
101
- compilerOptions: { target: ts.ScriptTarget.ESNext },
102
- });
103
-
104
- expect(() => compiler.compiler).toThrow("initialize()");
105
- });
106
-
107
- it("initialize() 전 ngtscProgram은 undefined이다", () => {
108
- const compiler = new AngularCompiler({
109
- rootNames: ["src/main.ts"],
110
- compilerOptions: { target: ts.ScriptTarget.ESNext },
111
- });
112
-
113
- expect(compiler.ngtscProgram).toBeUndefined();
114
- });
115
-
116
- it("host.readResource가 파일 내용을 반환한다", async () => {
117
- const compiler = new AngularCompiler({
118
- rootNames: realProgram.rootNames,
119
- compilerOptions: { target: ts.ScriptTarget.ESNext },
120
- });
121
-
122
- await compiler.initialize();
123
-
124
- const hostArg = ngtscConstructorSpy.mock.calls[0][2] as Record<string, Function>;
125
- // readResource는 host.readFile을 래핑한다
126
- expect(typeof hostArg["readResource"]).toBe("function");
127
- const content = (hostArg["readResource"])(realProgram.rootNames[0]);
128
- expect(content).toContain("export const x = 1");
129
- });
130
-
131
- it("host.transformResource — style이 아닌 type은 null을 반환한다", async () => {
132
- const transformStylesheet = vi.fn().mockResolvedValue("css");
133
- const compiler = new AngularCompiler({
134
- rootNames: realProgram.rootNames,
135
- compilerOptions: { target: ts.ScriptTarget.ESNext },
136
- transformStylesheet,
137
- });
138
-
139
- await compiler.initialize();
140
-
141
- const hostArg = ngtscConstructorSpy.mock.calls[0][2] as Record<string, Function>;
142
- const result = await (hostArg["transformResource"])("data", {
143
- type: "template",
144
- containingFile: "/app/comp.ts",
145
- resourceFile: null,
146
- });
147
- expect(result).toBeNull();
148
- expect(transformStylesheet).not.toHaveBeenCalled();
149
- });
150
-
151
- it("host.transformResource — 빈 스타일은 빈 content를 반환한다", async () => {
152
- const transformStylesheet = vi.fn().mockResolvedValue("css");
153
- const compiler = new AngularCompiler({
154
- rootNames: realProgram.rootNames,
155
- compilerOptions: { target: ts.ScriptTarget.ESNext },
156
- transformStylesheet,
157
- });
158
-
159
- await compiler.initialize();
160
-
161
- const hostArg = ngtscConstructorSpy.mock.calls[0][2] as Record<string, Function>;
162
- const result = await (hostArg["transformResource"])(" ", {
163
- type: "style",
164
- containingFile: "/app/comp.ts",
165
- resourceFile: null,
166
- });
167
- expect(result).toEqual({ content: "" });
168
- expect(transformStylesheet).not.toHaveBeenCalled();
169
- });
170
-
171
- it("host.getModifiedResourceFiles는 sourceFileCache.modifiedFiles를 반환한다", async () => {
172
- const cache = new AngularSourceFileCache();
173
- cache.modifiedFiles.add("src/styles.scss");
174
-
175
- const compiler = new AngularCompiler({
176
- rootNames: realProgram.rootNames,
177
- compilerOptions: { target: ts.ScriptTarget.ESNext },
178
- sourceFileCache: cache,
179
- });
180
-
181
- await compiler.initialize();
182
-
183
- const hostArg = ngtscConstructorSpy.mock.calls[0][2] as Record<string, Function>;
184
- expect(typeof hostArg["getModifiedResourceFiles"]).toBe("function");
185
- const modifiedFiles = (hostArg["getModifiedResourceFiles"])();
186
- expect(modifiedFiles.has("src/styles.scss")).toBe(true);
187
- });
188
-
189
- it("resourceNameToFileName — 존재하지 않는 파일은 null 반환", async () => {
190
- const compiler = new AngularCompiler({
191
- rootNames: realProgram.rootNames,
192
- compilerOptions: { target: ts.ScriptTarget.ESNext },
193
- });
194
-
195
- await compiler.initialize();
196
-
197
- const hostArg = ngtscConstructorSpy.mock.calls[0][2] as Record<string, Function>;
198
- const result = (hostArg["resourceNameToFileName"])(
199
- "./nonexistent.html",
200
- realProgram.rootNames[0],
201
- );
202
- expect(result).toBeNull();
203
- });
204
-
205
- it("resourceNameToFileName — 존재하는 템플릿 파일은 resolvedPath 반환", async () => {
206
- const htmlPath = path.join(realProgram.dir, "comp.html");
207
- fs.writeFileSync(htmlPath, "<div></div>", "utf-8");
208
-
209
- const compiler = new AngularCompiler({
210
- rootNames: realProgram.rootNames,
211
- compilerOptions: { target: ts.ScriptTarget.ESNext },
212
- });
213
-
214
- await compiler.initialize();
215
-
216
- const hostArg = ngtscConstructorSpy.mock.calls[0][2] as Record<string, Function>;
217
- const result = (hostArg["resourceNameToFileName"])(
218
- "./comp.html",
219
- realProgram.rootNames[0],
220
- );
221
- expect(result).toBe(htmlPath);
222
- });
223
-
224
- it("resourceNameToFileName — externalStylesheets와 stylesheet일 때 SHA256 ID 반환", async () => {
225
- const scssPath = path.join(realProgram.dir, "comp.scss");
226
- fs.writeFileSync(scssPath, ".x{}", "utf-8");
227
-
228
- const externalStylesheets = new Map<string, string>();
229
- const compiler = new AngularCompiler({
230
- rootNames: realProgram.rootNames,
231
- compilerOptions: { target: ts.ScriptTarget.ESNext },
232
- externalStylesheets,
233
- });
234
-
235
- await compiler.initialize();
236
-
237
- const hostArg = ngtscConstructorSpy.mock.calls[0][2] as Record<string, Function>;
238
- const result = (hostArg["resourceNameToFileName"])(
239
- "./comp.scss",
240
- realProgram.rootNames[0],
241
- );
242
- // SHA256 ID + .css가 반환되어야 한다
243
- expect(result).toMatch(/^[a-f0-9]{64}\.css$/);
244
- // externalStylesheets에 매핑이 저장되어야 한다
245
- expect(externalStylesheets.has(scssPath)).toBe(true);
246
- });
247
-
248
- it("angularCompilerOptions가 compilerOptions에 병합된다", async () => {
249
- const compiler = new AngularCompiler({
250
- rootNames: realProgram.rootNames,
251
- compilerOptions: { target: ts.ScriptTarget.ESNext },
252
- angularCompilerOptions: { strictTemplates: true },
253
- });
254
-
255
- await compiler.initialize();
256
-
257
- const passedOptions = ngtscConstructorSpy.mock.calls[0][1] as Record<string, unknown>;
258
- expect(passedOptions["strictTemplates"]).toBe(true);
259
- });
260
-
261
- it("collectDiagnostics() — initialize() 전 호출 시 에러", () => {
262
- const compiler = new AngularCompiler({
263
- rootNames: ["src/main.ts"],
264
- compilerOptions: { target: ts.ScriptTarget.ESNext },
265
- });
266
-
267
- expect(() => [...compiler.collectDiagnostics()]).toThrow("initialize()");
268
- });
269
- });
270
-
271
- // --- Acceptance Tests: Slice 2 — AngularCompiler 초기화 ---
272
-
273
- describe("AngularCompiler — 초기화", () => {
274
- // Scenario: 최초 초기화
275
- it("initialize()로 호스트, NgtscProgram, BuilderProgram이 생성되고 analyzeAsync가 호출된다", async () => {
276
- const compiler = new AngularCompiler({
277
- rootNames: ["src/main.ts"],
278
- compilerOptions: { target: ts.ScriptTarget.ESNext },
279
- });
280
-
281
- const result = await compiler.initialize();
282
-
283
- // NgtscProgram이 생성되었다
284
- expect(ngtscConstructorSpy).toHaveBeenCalledTimes(1);
285
- // oldProgram은 undefined (최초)
286
- expect(ngtscConstructorSpy.mock.calls[0][3]).toBeUndefined();
287
- // analyzeAsync가 호출되었다
288
- expect(mockAnalyzeAsync).toHaveBeenCalledTimes(1);
289
- // affectedFiles가 반환된다
290
- expect(result.affectedFiles).toBeInstanceOf(Set);
291
- });
292
-
293
- // Scenario: 재초기화 (incremental)
294
- it("두번째 initialize()에서 이전 NgtscProgram이 oldProgram으로 전달된다", async () => {
295
- const compiler = new AngularCompiler({
296
- rootNames: ["src/main.ts"],
297
- compilerOptions: { target: ts.ScriptTarget.ESNext },
298
- });
299
-
300
- await compiler.initialize();
301
- const firstProgram = compiler.ngtscProgram;
302
-
303
- await compiler.initialize();
304
-
305
- // 두번째 호출에서 oldProgram으로 첫번째 NgtscProgram이 전달됨
306
- expect(ngtscConstructorSpy).toHaveBeenCalledTimes(2);
307
- expect(ngtscConstructorSpy.mock.calls[1][3]).toBe(firstProgram);
308
- });
309
-
310
- // Scenario: transformStylesheet 콜백 주입 (library)
311
- it("transformStylesheet 콜백이 host.transformResource를 통해 호출된다", async () => {
312
- const transformStylesheet = vi.fn().mockResolvedValue("body { color: red; }");
313
-
314
- const compiler = new AngularCompiler({
315
- rootNames: ["src/main.ts"],
316
- compilerOptions: { target: ts.ScriptTarget.ESNext },
317
- transformStylesheet,
318
- });
319
-
320
- await compiler.initialize();
321
-
322
- // NgtscProgram 생성 시 전달된 host를 가져온다
323
- const hostArg = ngtscConstructorSpy.mock.calls[0][2] as Record<string, unknown>;
324
- expect(typeof hostArg["transformResource"]).toBe("function");
325
-
326
- // transformResource를 호출하면 transformStylesheet가 호출된다
327
- const result = await (hostArg["transformResource"] as Function)(
328
- ".button { color: blue; }",
329
- { type: "style", containingFile: "/app/comp.ts", resourceFile: "/app/comp.scss" },
330
- );
331
- expect(transformStylesheet).toHaveBeenCalledWith(
332
- ".button { color: blue; }",
333
- "/app/comp.ts",
334
- "/app/comp.scss",
335
- );
336
- expect(result).toEqual({ content: "body { color: red; }" });
337
- });
338
-
339
- // Scenario: compilerOptionsTransformer 적용
340
- it("compilerOptionsTransformer가 NgtscProgram 생성에 사용된다", async () => {
341
- const transformer = vi.fn((opts: ts.CompilerOptions) => ({
342
- ...opts,
343
- noEmit: false,
344
- declaration: false,
345
- }));
346
-
347
- const compiler = new AngularCompiler({
348
- rootNames: ["src/main.ts"],
349
- compilerOptions: { target: ts.ScriptTarget.ESNext, noEmit: true },
350
- compilerOptionsTransformer: transformer,
351
- });
352
-
353
- await compiler.initialize();
354
-
355
- expect(transformer).toHaveBeenCalledTimes(1);
356
- // NgtscProgram에 전달된 options에 변환이 적용되었는지 확인
357
- const passedOptions = ngtscConstructorSpy.mock.calls[0][1] as ts.CompilerOptions;
358
- expect(passedOptions.noEmit).toBe(false);
359
- expect(passedOptions.declaration).toBe(false);
360
- });
361
-
362
- // Scenario: resourceNameToFileName으로 외부 스타일시트 경로 해석
363
- it("host.resourceNameToFileName이 절대 경로를 반환한다", async () => {
364
- const compiler = new AngularCompiler({
365
- rootNames: ["src/main.ts"],
366
- compilerOptions: { target: ts.ScriptTarget.ESNext },
367
- });
368
-
369
- await compiler.initialize();
370
-
371
- const hostArg = ngtscConstructorSpy.mock.calls[0][2] as Record<string, Function>;
372
- expect(typeof hostArg["resourceNameToFileName"]).toBe("function");
373
- });
374
-
375
- // Scenario: declaration true 설정 (library)
376
- it("compilerOptionsTransformer로 declaration: true 설정", async () => {
377
- const compiler = new AngularCompiler({
378
- rootNames: ["src/main.ts"],
379
- compilerOptions: { target: ts.ScriptTarget.ESNext },
380
- compilerOptionsTransformer: (opts) => ({
381
- ...opts,
382
- declaration: true,
383
- declarationMap: true,
384
- }),
385
- });
386
-
387
- await compiler.initialize();
388
-
389
- const passedOptions = ngtscConstructorSpy.mock.calls[0][1] as ts.CompilerOptions;
390
- expect(passedOptions.declaration).toBe(true);
391
- expect(passedOptions.declarationMap).toBe(true);
392
- });
393
-
394
- // Scenario: declaration false 설정 (client)
395
- it("compilerOptionsTransformer로 declaration: false 설정", async () => {
396
- const compiler = new AngularCompiler({
397
- rootNames: ["src/main.ts"],
398
- compilerOptions: { target: ts.ScriptTarget.ESNext },
399
- compilerOptionsTransformer: (opts) => ({
400
- ...opts,
401
- declaration: false,
402
- }),
403
- });
404
-
405
- await compiler.initialize();
406
-
407
- const passedOptions = ngtscConstructorSpy.mock.calls[0][1] as ts.CompilerOptions;
408
- expect(passedOptions.declaration).toBe(false);
409
- });
410
-
411
- // Scenario: 초기화 후 ts.Program 획득
412
- it("getTsProgram()으로 ts.Program을 반환한다", async () => {
413
- const compiler = new AngularCompiler({
414
- rootNames: ["src/main.ts"],
415
- compilerOptions: { target: ts.ScriptTarget.ESNext },
416
- });
417
-
418
- await compiler.initialize();
419
-
420
- const program = compiler.getTsProgram();
421
- expect(program).toBe(realProgram.program);
422
- });
423
-
424
- // Scenario: ESLint에 Program 주입 가능
425
- it("getTsProgram()의 결과가 ESLint parserOptions.programs에 주입 가능한 형태이다", async () => {
426
- const compiler = new AngularCompiler({
427
- rootNames: ["src/main.ts"],
428
- compilerOptions: { target: ts.ScriptTarget.ESNext },
429
- });
430
-
431
- await compiler.initialize();
432
-
433
- const program = compiler.getTsProgram();
434
- // ESLint parserOptions.programs는 ts.Program 배열을 받는다
435
- const parserOptions = { programs: [program], project: null };
436
- expect(parserOptions.programs).toHaveLength(1);
437
- expect(parserOptions.programs[0]).toBe(realProgram.program);
438
- });
439
-
440
- // Scenario: SourceFileCache 통합
441
- it("sourceFileCache 제공 시 augmentHostWithCaching이 적용된다", async () => {
442
- const cache = new AngularSourceFileCache();
443
-
444
- const compiler = new AngularCompiler({
445
- rootNames: ["src/main.ts"],
446
- compilerOptions: { target: ts.ScriptTarget.ESNext },
447
- sourceFileCache: cache,
448
- });
449
-
450
- await compiler.initialize();
451
-
452
- // host가 캐시를 사용하도록 래핑되었는지 간접 확인:
453
- // NgtscProgram이 성공적으로 생성되었으면 호스트가 정상적으로 구성된 것
454
- expect(ngtscConstructorSpy).toHaveBeenCalledTimes(1);
455
- });
456
-
457
- // Scenario: packageJsonCache — node_modules 변경 시 clear
458
- it("modifiedFiles에 node_modules 파일이 있으면 packageJsonCache가 clear된다", async () => {
459
- const cache = new AngularSourceFileCache();
460
-
461
- const compiler = new AngularCompiler({
462
- rootNames: ["src/main.ts"],
463
- compilerOptions: { target: ts.ScriptTarget.ESNext },
464
- sourceFileCache: cache,
465
- });
466
-
467
- // 첫 초기화 — packageJsonCache 생성
468
- await compiler.initialize();
469
-
470
- // node_modules 파일 변경
471
- cache.modifiedFiles.add("node_modules/some-pkg/index.js");
472
-
473
- // 재초기화 — packageJsonCache.clear() 호출되어야 함
474
- // 에러 없이 완료되면 성공
475
- await compiler.initialize();
476
-
477
- expect(ngtscConstructorSpy).toHaveBeenCalledTimes(2);
478
- });
479
-
480
- // Scenario: packageJsonCache — node_modules 미변경 시 재사용
481
- it("modifiedFiles에 node_modules 파일이 없으면 packageJsonCache가 재사용된다", async () => {
482
- const cache = new AngularSourceFileCache();
483
-
484
- const compiler = new AngularCompiler({
485
- rootNames: ["src/main.ts"],
486
- compilerOptions: { target: ts.ScriptTarget.ESNext },
487
- sourceFileCache: cache,
488
- });
489
-
490
- // 첫 초기화
491
- await compiler.initialize();
492
-
493
- // 일반 파일만 변경
494
- cache.modifiedFiles.add("src/app/component.ts");
495
-
496
- // 재초기화
497
- await compiler.initialize();
498
-
499
- // 에러 없이 완료
500
- expect(ngtscConstructorSpy).toHaveBeenCalledTimes(2);
501
- });
502
- });
503
-
504
- // --- Acceptance Tests: Slice 2 — affected 파일 ---
505
-
506
- describe("AngularCompiler — affected 파일", () => {
507
- // Scenario: 소스 파일 변경 시 affected 파일 수집
508
- it("initialize()가 BuilderProgram 기반으로 affected 파일을 반환한다", async () => {
509
- const compiler = new AngularCompiler({
510
- rootNames: realProgram.rootNames,
511
- compilerOptions: { target: ts.ScriptTarget.ESNext },
512
- });
513
-
514
- const result = await compiler.initialize();
515
- // affected 파일이 Set으로 반환된다
516
- expect(result.affectedFiles).toBeInstanceOf(Set);
517
- });
518
-
519
- // Scenario: 리소스 변경 시 해당 .ts 파일이 affected에 추가
520
- it("리소스 변경 시 해당 .ts 파일이 affected에 추가된다", async () => {
521
- // realProgram has index.ts — mock its resource dependencies
522
- const sourceFiles = realProgram.program.getSourceFiles();
523
- const indexFile = sourceFiles.find((sf) => sf.fileName.includes("index.ts"));
524
- const scssPath = path.join(realProgram.dir, "component.scss");
525
- fs.writeFileSync(scssPath, ".x { }", "utf-8");
526
-
527
- mockGetResourceDependencies.mockImplementation((sf: ts.SourceFile) => {
528
- if (sf === indexFile) {
529
- return [scssPath];
530
- }
531
- return [];
532
- });
533
-
534
- const cache = new AngularSourceFileCache();
535
- cache.modifiedFiles.add(scssPath);
536
-
537
- const compiler = new AngularCompiler({
538
- rootNames: realProgram.rootNames,
539
- compilerOptions: { target: ts.ScriptTarget.ESNext },
540
- sourceFileCache: cache,
541
- });
542
-
543
- const result = await compiler.initialize();
544
- // index.ts가 affected에 추가되어야 한다 (리소스 변경으로 인해)
545
- const affectedFileNames = [...result.affectedFiles].map((sf) => sf.fileName);
546
- expect(affectedFileNames.some((f) => f.includes("index.ts"))).toBe(true);
547
- });
548
- });
549
-
550
- // --- Acceptance Tests: Slice 3 — collectDiagnostics ---
551
-
552
- describe("AngularCompiler — collectDiagnostics", () => {
553
- // Scenario: 4종 진단 수집
554
- it("collectDiagnostics가 Option, Syntactic, Semantic, Angular 진단을 수집한다", async () => {
555
- const compiler = new AngularCompiler({
556
- rootNames: realProgram.rootNames,
557
- compilerOptions: {
558
- target: ts.ScriptTarget.ESNext,
559
- module: ts.ModuleKind.ESNext,
560
- moduleResolution: ts.ModuleResolutionKind.Bundler,
561
- skipLibCheck: true,
562
- types: [],
563
- },
564
- });
565
-
566
- await compiler.initialize();
567
-
568
- // collectDiagnostics가 에러 없이 실행된다
569
- const diagnostics = [...compiler.collectDiagnostics()];
570
- expect(Array.isArray(diagnostics)).toBe(true);
571
- });
572
-
573
- // Scenario: affected 파일의 Angular 진단은 재계산 후 캐시
574
- it("affected 파일의 Angular 진단은 getDiagnosticsForFile로 재계산된다", async () => {
575
- const indexFile = realProgram.program.getSourceFiles()
576
- .find((sf) => sf.fileName.includes("index.ts"));
577
- const scssPath = path.join(realProgram.dir, "component.scss");
578
- fs.writeFileSync(scssPath, ".x { }", "utf-8");
579
-
580
- const mockDiag = {
581
- category: ts.DiagnosticCategory.Error,
582
- code: 1000,
583
- messageText: "test error",
584
- file: indexFile,
585
- start: 0,
586
- length: 5,
587
- } as ts.Diagnostic;
588
- mockGetDiagnosticsForFile.mockReturnValue([mockDiag]);
589
- mockGetResourceDependencies.mockImplementation((sf: ts.SourceFile) => {
590
- if (sf.fileName.includes("index.ts")) {
591
- return [scssPath];
592
- }
593
- return [];
594
- });
595
-
596
- const cache = new AngularSourceFileCache();
597
- cache.modifiedFiles.add(scssPath);
598
-
599
- const compiler = new AngularCompiler({
600
- rootNames: realProgram.rootNames,
601
- compilerOptions: {
602
- target: ts.ScriptTarget.ESNext,
603
- module: ts.ModuleKind.ESNext,
604
- moduleResolution: ts.ModuleResolutionKind.Bundler,
605
- skipLibCheck: true,
606
- types: [],
607
- },
608
- sourceFileCache: cache,
609
- });
610
-
611
- await compiler.initialize();
612
-
613
- const diagnostics = [...compiler.collectDiagnostics()];
614
- // getDiagnosticsForFile이 호출되었다
615
- expect(mockGetDiagnosticsForFile).toHaveBeenCalled();
616
- // 진단이 수집되었다
617
- expect(diagnostics.some((d) => d.code === 1000)).toBe(true);
618
- });
619
-
620
- // Scenario: non-affected 파일의 Angular 진단은 캐시 반환
621
- it("non-affected 파일의 Angular 진단은 캐시에서 반환된다 (첫 빌드에서 모든 파일이 affected)", async () => {
622
- const mockDiag = {
623
- category: ts.DiagnosticCategory.Warning,
624
- code: 2000,
625
- messageText: "cached warning",
626
- } as ts.Diagnostic;
627
- mockGetDiagnosticsForFile.mockReturnValue([mockDiag]);
628
-
629
- const compiler = new AngularCompiler({
630
- rootNames: realProgram.rootNames,
631
- compilerOptions: {
632
- target: ts.ScriptTarget.ESNext,
633
- module: ts.ModuleKind.ESNext,
634
- moduleResolution: ts.ModuleResolutionKind.Bundler,
635
- skipLibCheck: true,
636
- types: [],
637
- },
638
- });
639
-
640
- await compiler.initialize();
641
-
642
- // 첫 collectDiagnostics — 초기 빌드에서 모든 파일이 affected → getDiagnosticsForFile 호출됨
643
- const diags1 = [...compiler.collectDiagnostics()];
644
- const callCount1 = mockGetDiagnosticsForFile.mock.calls.length;
645
- expect(callCount1).toBeGreaterThan(0);
646
- expect(diags1.some((d) => d.code === 2000)).toBe(true);
647
-
648
- // 두번째 initialize + collectDiagnostics — 변경 없으면 캐시 사용
649
- mockGetDiagnosticsForFile.mockClear();
650
- await compiler.initialize();
651
- const diags2 = [...compiler.collectDiagnostics()];
652
- // 두번째에서는 변경이 없으므로 affected가 비어 있어야 하고, 캐시에서 반환
653
- // (하지만 BuilderProgram은 재생성되므로 affected가 다시 계산될 수 있다)
654
- // 최소한 에러 없이 동작해야 한다
655
- expect(Array.isArray(diags2)).toBe(true);
656
- });
657
-
658
- // Scenario: 리소스 변경 시 해당 .ts 파일의 캐시 무효화
659
- it("리소스 변경 시 해당 .ts 파일의 diagnosticCache가 무효화된다", async () => {
660
- const scssPath = path.join(realProgram.dir, "styles.scss");
661
- fs.writeFileSync(scssPath, ".y { }", "utf-8");
662
-
663
- const mockDiag = {
664
- category: ts.DiagnosticCategory.Error,
665
- code: 3000,
666
- messageText: "resource error",
667
- } as ts.Diagnostic;
668
- mockGetDiagnosticsForFile.mockReturnValue([mockDiag]);
669
- mockGetResourceDependencies.mockImplementation((sf: ts.SourceFile) => {
670
- if (sf.fileName.includes("index.ts")) {
671
- return [scssPath];
672
- }
673
- return [];
674
- });
675
-
676
- const cache = new AngularSourceFileCache();
677
-
678
- const compiler = new AngularCompiler({
679
- rootNames: realProgram.rootNames,
680
- compilerOptions: {
681
- target: ts.ScriptTarget.ESNext,
682
- module: ts.ModuleKind.ESNext,
683
- moduleResolution: ts.ModuleResolutionKind.Bundler,
684
- skipLibCheck: true,
685
- types: [],
686
- },
687
- sourceFileCache: cache,
688
- });
689
-
690
- // 첫 빌드
691
- await compiler.initialize();
692
- void [...compiler.collectDiagnostics()];
693
- expect(mockGetDiagnosticsForFile).toHaveBeenCalled();
694
-
695
- // 리소스 변경 후 재빌드
696
- mockGetDiagnosticsForFile.mockClear();
697
- cache.modifiedFiles.add(scssPath);
698
-
699
- await compiler.initialize();
700
- void [...compiler.collectDiagnostics()];
701
-
702
- // 리소스 변경으로 캐시 무효화 → getDiagnosticsForFile이 다시 호출되어야 한다
703
- expect(mockGetDiagnosticsForFile).toHaveBeenCalled();
704
- });
705
- });