@simplysm/sd-cli 14.0.37 → 14.0.38

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 (66) hide show
  1. package/dist/angular/vite-angular-plugin.d.ts +2 -5
  2. package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
  3. package/dist/angular/vite-angular-plugin.js +19 -72
  4. package/dist/angular/vite-angular-plugin.js.map +1 -1
  5. package/dist/commands/publish/storage-publisher.js +1 -0
  6. package/dist/commands/publish/storage-publisher.js.map +1 -1
  7. package/dist/dev-server/hmr-service.d.ts +2 -0
  8. package/dist/dev-server/hmr-service.d.ts.map +1 -1
  9. package/dist/dev-server/hmr-service.js +24 -10
  10. package/dist/dev-server/hmr-service.js.map +1 -1
  11. package/dist/electron/electron.js +4 -4
  12. package/dist/electron/electron.js.map +1 -1
  13. package/dist/engines/BaseEngine.d.ts.map +1 -1
  14. package/dist/engines/BaseEngine.js +10 -1
  15. package/dist/engines/BaseEngine.js.map +1 -1
  16. package/dist/engines/EsbuildClientEngine.d.ts.map +1 -1
  17. package/dist/engines/EsbuildClientEngine.js +12 -1
  18. package/dist/engines/EsbuildClientEngine.js.map +1 -1
  19. package/dist/engines/engine-factory.d.ts +0 -4
  20. package/dist/engines/engine-factory.d.ts.map +1 -1
  21. package/dist/engines/engine-factory.js.map +1 -1
  22. package/dist/esbuild/esbuild-client-config.js +5 -5
  23. package/dist/esbuild/esbuild-client-config.js.map +1 -1
  24. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
  25. package/dist/orchestrators/DevOrchestrator.js +0 -5
  26. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  27. package/dist/orchestrators/TypecheckOrchestrator.js +2 -2
  28. package/dist/orchestrators/TypecheckOrchestrator.js.map +1 -1
  29. package/dist/sd-cli.js +2 -1
  30. package/dist/sd-cli.js.map +1 -1
  31. package/dist/utils/output-utils.d.ts.map +1 -1
  32. package/dist/utils/output-utils.js +3 -2
  33. package/dist/utils/output-utils.js.map +1 -1
  34. package/dist/workers/client.worker.d.ts.map +1 -1
  35. package/dist/workers/client.worker.js +7 -1
  36. package/dist/workers/client.worker.js.map +1 -1
  37. package/package.json +4 -4
  38. package/src/angular/vite-angular-plugin.ts +19 -82
  39. package/src/commands/publish/storage-publisher.ts +1 -0
  40. package/src/dev-server/hmr-service.ts +28 -13
  41. package/src/electron/electron.ts +5 -5
  42. package/src/engines/BaseEngine.ts +10 -1
  43. package/src/engines/EsbuildClientEngine.ts +13 -1
  44. package/src/engines/engine-factory.ts +0 -1
  45. package/src/esbuild/esbuild-client-config.ts +6 -6
  46. package/src/orchestrators/DevOrchestrator.ts +0 -6
  47. package/src/orchestrators/TypecheckOrchestrator.ts +2 -2
  48. package/src/sd-cli.ts +2 -1
  49. package/src/utils/output-utils.ts +3 -2
  50. package/src/workers/client.worker.ts +8 -1
  51. package/tests/angular/_vite-angular-plugin-test-setup.ts +3 -30
  52. package/tests/angular/vite-angular-plugin-legacy-watch.spec.ts +14 -31
  53. package/tests/angular/vite-angular-plugin-vitest.spec.ts +4 -34
  54. package/tests/angular/vite-angular-plugin.spec.ts +24 -60
  55. package/tests/commands/typecheck.spec.ts +1 -1
  56. package/tests/engines/base-engine.spec.ts +25 -0
  57. package/tests/engines/engine-adapter-isolation.spec.ts +3 -3
  58. package/tests/engines/esbuild-client-engine.acc.spec.ts +29 -0
  59. package/tests/engines/esbuild-client-engine.spec.ts +26 -0
  60. package/tests/orchestrators/build-orchestrator.spec.ts +1 -1
  61. package/tests/orchestrators/dev-orchestrator.spec.ts +1 -1
  62. package/tests/orchestrators/typecheck-orchestrator.spec.ts +1 -1
  63. package/tests/orchestrators/watch-orchestrator.spec.ts +1 -1
  64. package/tests/utils/esbuild-client-config.acc.spec.ts +4 -1
  65. package/tests/utils/hmr-service-dispatcher.acc.spec.ts +70 -0
  66. package/tests/workers/client-worker-initial-build-error.verify.md +8 -0
@@ -1,114 +1,95 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import path from "path";
3
- import type { SdConfig } from "../../src/sd-config.types";
4
3
  import {
5
4
  FIXTURE_DIR,
6
5
  PKG_DIR,
7
- createTestSdConfig,
8
6
  initPlugin,
9
7
  } from "./_vite-angular-plugin-test-setup";
10
8
 
11
- const mockLoadSdConfig = vi.fn<(...args: unknown[]) => Promise<SdConfig>>();
12
-
13
- vi.mock("../../src/utils/sd-config", () => ({
14
- loadSdConfig: (...args: unknown[]) => mockLoadSdConfig(...args),
15
- }));
16
-
17
9
  const { sdAngularPlugin } = await import("../../src/angular/vite-angular-plugin.js");
18
10
 
19
11
  beforeEach(() => {
20
12
  vi.clearAllMocks();
21
13
  vi.spyOn(process, "cwd").mockReturnValue(FIXTURE_DIR);
22
- mockLoadSdConfig.mockResolvedValue(createTestSdConfig());
23
14
  });
24
15
 
25
16
  describe("sdAngularPlugin", () => {
26
- // config() define 반환은 Vitest 축소로 제거됨 (esbuild-client-config에서 처리)
27
-
28
17
  // Scenario: non-Angular .ts 파일은 기본 처리
29
18
  it("returns undefined for non-emitted .ts files", async () => {
30
- const plugin = sdAngularPlugin({ pkg: "basic-app"});
19
+ const plugin = sdAngularPlugin({ pkg: "basic-app" });
31
20
  await initPlugin(plugin);
32
21
 
33
- const result = await (plugin as any).transform?.call(
22
+ const result = (plugin as any).transform?.call(
34
23
  {},
35
24
  "export const x = 1;",
36
25
  "/some/unknown/file.ts",
37
26
  );
38
27
 
39
28
  expect(result).toBeUndefined();
40
- await (plugin as any).buildEnd?.call({});
29
+ (plugin as any).buildEnd?.call({});
41
30
  });
42
31
 
43
32
  // Scenario: buildStart에서 초기화 및 emit + buildEnd에서 리소스 정리
44
33
  it("initializes facade in buildStart and disposes in buildEnd", async () => {
45
- const plugin = sdAngularPlugin({ pkg: "basic-app"});
34
+ const plugin = sdAngularPlugin({ pkg: "basic-app" });
46
35
  await initPlugin(plugin);
47
- await (plugin as any).buildEnd?.call({});
36
+ (plugin as any).buildEnd?.call({});
48
37
  });
49
38
 
50
- // handleHotUpdate, configureServer, optimizeDeps는 Vitest 축소로 제거됨
51
- // Feature 3.3에서 esbuild 기반 테스트로 교체 예정
52
-
53
- // Scenario: .mjs 파일이 JavaScriptTransformer를 통과한다
54
- it("transforms .mjs files through JavaScriptTransformer", async () => {
55
- const plugin = sdAngularPlugin({ pkg: "basic-app"});
39
+ // Scenario: .mjs/.js 파일은 처리하지 않는다
40
+ it("returns undefined for .mjs files", async () => {
41
+ const plugin = sdAngularPlugin({ pkg: "basic-app" });
56
42
  await initPlugin(plugin);
57
43
 
58
- const result = await (plugin as any).transform?.call(
44
+ const result = (plugin as any).transform?.call(
59
45
  {},
60
46
  "export const x = 1;",
61
47
  "/some/library/module.mjs",
62
48
  );
63
49
 
64
- expect(result).toBeDefined();
65
- expect(result.code).toBeDefined();
66
- expect(typeof result.code).toBe("string");
67
- await (plugin as any).buildEnd?.call({});
50
+ expect(result).toBeUndefined();
51
+ (plugin as any).buildEnd?.call({});
68
52
  });
69
53
 
70
- // Scenario: .js 파일이 JavaScriptTransformer를 통과한다
71
- it("transforms .js files through JavaScriptTransformer", async () => {
72
- const plugin = sdAngularPlugin({ pkg: "basic-app"});
54
+ it("returns undefined for .js files", async () => {
55
+ const plugin = sdAngularPlugin({ pkg: "basic-app" });
73
56
  await initPlugin(plugin);
74
57
 
75
- const result = await (plugin as any).transform?.call(
58
+ const result = (plugin as any).transform?.call(
76
59
  {},
77
60
  "export const y = 2;",
78
61
  "/some/library/module.js",
79
62
  );
80
63
 
81
- expect(result).toBeDefined();
82
- expect(result.code).toBeDefined();
83
- expect(typeof result.code).toBe("string");
84
- await (plugin as any).buildEnd?.call({});
64
+ expect(result).toBeUndefined();
65
+ (plugin as any).buildEnd?.call({});
85
66
  });
86
67
 
87
68
  // Scenario: 비대상 파일(.css 등)은 transform하지 않는다
88
69
  it("returns undefined for non-JS files like .css", async () => {
89
- const plugin = sdAngularPlugin({ pkg: "basic-app"});
70
+ const plugin = sdAngularPlugin({ pkg: "basic-app" });
90
71
  await initPlugin(plugin);
91
72
 
92
- const result = await (plugin as any).transform?.call(
73
+ const result = (plugin as any).transform?.call(
93
74
  {},
94
75
  "body { color: red; }",
95
76
  "/some/styles.css",
96
77
  );
97
78
 
98
79
  expect(result).toBeUndefined();
99
- await (plugin as any).buildEnd?.call({});
80
+ (plugin as any).buildEnd?.call({});
100
81
  });
101
82
 
102
- // Scenario: Angular .ts 파일 transform (prod 모드)
83
+ // Scenario: Angular .ts 파일 transform
103
84
  it("transforms emitted .ts files with compiled JS", async () => {
104
- const plugin = sdAngularPlugin({ pkg: "basic-app"});
105
- await initPlugin(plugin, { mode: "production", command: "build" });
85
+ const plugin = sdAngularPlugin({ pkg: "basic-app" });
86
+ await initPlugin(plugin);
106
87
 
107
88
  const appComponentPath = path
108
89
  .join(PKG_DIR, "src/app.component.ts")
109
90
  .replace(/\\/g, "/");
110
91
 
111
- const result = await (plugin as any).transform?.call({}, "", appComponentPath);
92
+ const result = (plugin as any).transform?.call({}, "", appComponentPath);
112
93
 
113
94
  if (result != null) {
114
95
  expect(result).toHaveProperty("code");
@@ -116,23 +97,6 @@ describe("sdAngularPlugin", () => {
116
97
  expect(result.code.length).toBeGreaterThan(0);
117
98
  }
118
99
 
119
- await (plugin as any).buildEnd?.call({});
120
- });
121
-
122
- // config() define 반환값은 Vitest 축소로 제거됨 (Feature 3.3에서 별도 테스트)
123
-
124
- // Scenario: browserSupport 미설정 시 기본값 동작
125
- it("works when browserSupport is not set in sd.config.ts", async () => {
126
- mockLoadSdConfig.mockResolvedValue(createTestSdConfig());
127
-
128
- const plugin = sdAngularPlugin({ pkg: "basic-app" });
129
-
130
- await initPlugin(plugin);
131
-
132
- const appComponentPath = PKG_DIR.replace(/\\/g, "/") + "/src/app.component.ts";
133
- const result = await (plugin as any).transform?.call({}, "", appComponentPath);
134
- expect(result).toBeDefined();
135
-
136
- await (plugin as any).buildEnd?.call({});
100
+ (plugin as any).buildEnd?.call({});
137
101
  });
138
102
  });
@@ -27,7 +27,7 @@ vi.mock("../../src/typecheck/typecheck-non-package", () => ({
27
27
  typecheckNonPackageFiles: mocks.typecheckNonPackageFiles,
28
28
  }));
29
29
 
30
- vi.mock("../../src/engines/index", () => ({
30
+ vi.mock("../../src/engines/engine-factory", () => ({
31
31
  createTypecheckEngine: mocks.createTypecheckEngine,
32
32
  }));
33
33
 
@@ -254,6 +254,31 @@ describe("BaseEngine", () => {
254
254
  await engine.stop();
255
255
  });
256
256
 
257
+ it("_callStartWatch 실패 시 에러를 ResultCollector에 보고하고 resolveInitialBuild를 호출한다", async () => {
258
+ const mockResultCollector = { add: vi.fn() };
259
+
260
+ mockWorker.startWatch.mockRejectedValue(new Error("Worker RPC failed"));
261
+
262
+ const engine = new TscEngine({
263
+ cwd: "/root",
264
+ pkg: createMockPkg(),
265
+ resultCollector: mockResultCollector as any,
266
+ });
267
+
268
+ // startWatch()가 hang하지 않고 resolve되어야 한다
269
+ await engine.startWatch({ js: true, dts: true });
270
+
271
+ // ResultCollector에 에러가 보고되어야 한다
272
+ const errorReport = mockResultCollector.add.mock.calls.find(
273
+ (c: any[]) => c[0].type === "build" && c[0].status === "error",
274
+ );
275
+ expect(errorReport).toBeDefined();
276
+ expect(errorReport![0].name).toBe("test-pkg");
277
+ expect(errorReport![0].message).toContain("Worker RPC failed");
278
+
279
+ await engine.stop();
280
+ });
281
+
257
282
  it("calls resolver on error event to release RebuildManager batch", async () => {
258
283
  const mockResolver = vi.fn();
259
284
  const mockRebuildManager = { registerBuild: vi.fn(() => mockResolver) };
@@ -25,7 +25,7 @@ describe("EsbuildClientEngine adapter isolation", () => {
25
25
  }
26
26
  });
27
27
 
28
- it("vite-angular-plugin.ts imports only JavaScriptTransformer from @angular/build/private", () => {
28
+ it("vite-angular-plugin.ts does not import @angular/* directly", () => {
29
29
 
30
30
  const pluginFile = path.resolve(
31
31
  import.meta.dirname,
@@ -33,8 +33,8 @@ describe("EsbuildClientEngine adapter isolation", () => {
33
33
  );
34
34
  const content = fs.readFileSync(pluginFile, "utf-8");
35
35
 
36
- expect(content).toContain("@angular/build/private");
37
- expect(content).toContain("JavaScriptTransformer");
36
+ const angularImportPattern = /from\s+["']@angular\/(build|compiler-cli)/;
37
+ expect(angularImportPattern.test(content)).toBe(false);
38
38
  expect(content).not.toContain("createAngularCompilation");
39
39
  expect(content).not.toMatch(/\bSourceFileCache\b(?<!AngularSourceFileCache)/);
40
40
  expect(content).not.toContain("ComponentStylesheetBundler");
@@ -134,6 +134,35 @@ describe("EsbuildClientEngine Acceptance", () => {
134
134
  await engine.stop();
135
135
  });
136
136
 
137
+ // Scenario: 초기 빌드 실패 시 ResultCollector에 에러 보고
138
+ it("초기 빌드 실패 시 ResultCollector에 에러가 보고된다", async () => {
139
+ // Given: client 패키지가 정의되어 있다
140
+ const mockResultCollector = { add: vi.fn() };
141
+
142
+ mockWorker.startWatch.mockResolvedValue({
143
+ success: false,
144
+ errors: ["esbuild compilation failed"],
145
+ });
146
+
147
+ const engine = new EsbuildClientEngine({
148
+ cwd: "/root",
149
+ pkg: createMockPkg(),
150
+ resultCollector: mockResultCollector as any,
151
+ });
152
+
153
+ // When: startWatch()를 호출한다
154
+ await engine.startWatch({ js: true, dts: false });
155
+
156
+ // Then: ResultCollector에 에러가 보고된다
157
+ const errorReport = mockResultCollector.add.mock.calls.find(
158
+ (c: any[]) => c[0].type === "build" && c[0].status === "error",
159
+ );
160
+ expect(errorReport).toBeDefined();
161
+ expect(errorReport![0].name).toBe("my-client");
162
+
163
+ await engine.stop();
164
+ });
165
+
137
166
  // Scenario: 엔진 중지
138
167
  it("stop()으로 worker를 종료하고 .dev-port를 삭제한다", async () => {
139
168
  // Given: dev watch 모드가 실행 중이다
@@ -271,6 +271,32 @@ describe("EsbuildClientEngine", () => {
271
271
  await engine.stop();
272
272
  });
273
273
 
274
+ it("초기 빌드 실패 시 에러를 logger.error로 출력한다", async () => {
275
+ mockWorker.startWatch.mockResolvedValue({
276
+ success: false,
277
+ errors: ["Module not found: @angular/core", "Syntax error in app.ts"],
278
+ });
279
+
280
+ const engine = new EsbuildClientEngine({ cwd: "/root", pkg: createMockPkg() });
281
+ await engine.startWatch({ js: true, dts: false });
282
+
283
+ // startWatch가 reject하지 않고 정상 완료되는지 확인 (기존 동작 유지)
284
+ // 에러 로깅은 verify.md에서 검증
285
+ expect(mockWorker.startWatch).toHaveBeenCalled();
286
+
287
+ await engine.stop();
288
+ });
289
+
290
+ it("초기 빌드 성공 시 에러 로깅을 하지 않는다", async () => {
291
+ mockWorker.startWatch.mockResolvedValue({ success: true });
292
+
293
+ const engine = new EsbuildClientEngine({ cwd: "/root", pkg: createMockPkg() });
294
+ // 예외 없이 완료
295
+ await expect(engine.startWatch({ js: true, dts: false })).resolves.toBeUndefined();
296
+
297
+ await engine.stop();
298
+ });
299
+
274
300
  it("scopeRebuild 이벤트를 구독하지 않는다", async () => {
275
301
  mockWorker.startWatch.mockResolvedValue({ success: true });
276
302
 
@@ -59,7 +59,7 @@ const mockEngines: Array<{
59
59
  stop: ReturnType<typeof vi.fn>;
60
60
  }> = [];
61
61
 
62
- vi.mock("../../src/engines/index", () => ({
62
+ vi.mock("../../src/engines/engine-factory", () => ({
63
63
  createBuildEngine: vi.fn(() => {
64
64
  const engine = {
65
65
  run: vi.fn().mockResolvedValue({
@@ -40,7 +40,7 @@ const mockBuildEngines: Array<{
40
40
  port?: number;
41
41
  }> = [];
42
42
 
43
- vi.mock("../../src/engines/index", () => ({
43
+ vi.mock("../../src/engines/engine-factory", () => ({
44
44
  createBuildEngine: vi.fn((pkg: any, options: any) => {
45
45
  const engine = {
46
46
  run: vi.fn().mockResolvedValue({
@@ -28,7 +28,7 @@ vi.mock("../../src/typecheck/typecheck-non-package", () => ({
28
28
  typecheckNonPackageFiles: mocks.typecheckNonPackageFiles,
29
29
  }));
30
30
 
31
- vi.mock("../../src/engines/index", () => ({
31
+ vi.mock("../../src/engines/engine-factory", () => ({
32
32
  createTypecheckEngine: mocks.createTypecheckEngine,
33
33
  }));
34
34
 
@@ -45,7 +45,7 @@ const mockBuildEngines: Array<{
45
45
  _pkgName: string;
46
46
  }> = [];
47
47
 
48
- vi.mock("../../src/engines/index", () => ({
48
+ vi.mock("../../src/engines/engine-factory", () => ({
49
49
  createBuildEngine: vi.fn((pkg: any, options: any) => {
50
50
  const engine = {
51
51
  run: vi.fn().mockResolvedValue({
@@ -108,7 +108,7 @@ describe("createClientEsbuildContext — Acceptance", () => {
108
108
  expect(pluginOpts.incremental).toBe(true);
109
109
  expect(pluginOpts.sourceFileCache).toBe(mockSourceFileCache);
110
110
  expect(pluginOpts.loadResultCache).toBe(mockSourceFileCache.loadResultCache);
111
- expect((pluginOpts as any).browserOnlyBuild).toBe(true);
111
+ expect(pluginOpts.includeTestMetadata).toBe(true);
112
112
 
113
113
  // BundleStylesheetOptions
114
114
  expect(styleOpts.workspaceRoot).toBe("/workspace");
@@ -161,6 +161,9 @@ describe("createClientEsbuildContext — Acceptance", () => {
161
161
  expect(pluginOpts.sourcemap).toBe(false);
162
162
  expect(pluginOpts.advancedOptimizations).toBe(true);
163
163
  expect(pluginOpts.thirdPartySourcemaps).toBe(false);
164
+ expect(pluginOpts.incremental).toBe(false);
165
+ expect(pluginOpts.includeTestMetadata).toBe(false);
166
+ expect((pluginOpts as unknown as Record<string, unknown>)["browserOnlyBuild"]).toBeUndefined();
164
167
 
165
168
  // BundleStylesheetOptions
166
169
  expect(styleOpts.optimization).toBe(true);
@@ -155,6 +155,76 @@ describe("HMR 디스패처 통합", () => {
155
155
  });
156
156
  });
157
157
 
158
+ describe("Scenario: 파일 크기 동일하지만 내용 변경 → 변경 감지", () => {
159
+ it("CSS 내용이 변경되었지만 크기가 동일한 경우 css-update를 전송한다", async () => {
160
+ // 임시 dist 디렉토리에 CSS 파일 생성
161
+ const distDir = path.join(tmpDir, "dist-hash-test");
162
+ fs.mkdirSync(distDir, { recursive: true });
163
+ fs.writeFileSync(path.join(distDir, "main.css"), "body { color: red; }");
164
+ fs.writeFileSync(path.join(distDir, "main.js"), "console.log('hello');");
165
+
166
+ // outDir을 전달하는 HMR 서비스 생성
167
+ hmrService.close();
168
+ await new Promise<void>((resolve, reject) => {
169
+ httpServer.close((err) => {
170
+ if (err != null) reject(err);
171
+ else resolve();
172
+ });
173
+ });
174
+
175
+ httpServer = http.createServer((_req, res) => { res.writeHead(404); res.end(); });
176
+ hmrService = createHmrService({
177
+ httpServer,
178
+ basePath: "/app/",
179
+ templateUpdates,
180
+ outDir: distDir,
181
+ });
182
+ port = await new Promise<number>((resolve, reject) => {
183
+ httpServer.listen(0, "127.0.0.1", () => {
184
+ const addr = httpServer.address();
185
+ if (typeof addr === "object" && addr != null) resolve(addr.port);
186
+ else reject(new Error("포트 감지 실패"));
187
+ });
188
+ httpServer.on("error", reject);
189
+ });
190
+
191
+ const cssContent = "body { color: red; }";
192
+ const metafile1: esbuild.Metafile = {
193
+ inputs: {},
194
+ outputs: {
195
+ "main.js": { bytes: 22, inputs: {}, imports: [], exports: [] },
196
+ "main.css": { bytes: cssContent.length, inputs: {}, imports: [], exports: [] },
197
+ },
198
+ };
199
+
200
+ // 첫 빌드 (baseline)
201
+ hmrService.onBuildEnd(metafile1);
202
+ await new Promise((r) => setTimeout(r, 150));
203
+
204
+ // CSS 내용 변경 (같은 크기)
205
+ const newCssContent = "body { color: blu; }"; // 같은 길이
206
+ fs.writeFileSync(path.join(distDir, "main.css"), newCssContent);
207
+
208
+ const ws = await connectWs();
209
+ const msgPromise = waitForMessage(ws);
210
+
211
+ const metafile2: esbuild.Metafile = {
212
+ inputs: {},
213
+ outputs: {
214
+ "main.js": { bytes: 22, inputs: {}, imports: [], exports: [] },
215
+ "main.css": { bytes: newCssContent.length, inputs: {}, imports: [], exports: [] },
216
+ },
217
+ };
218
+ hmrService.onBuildEnd(metafile2);
219
+
220
+ const msg = await msgPromise;
221
+ expect(msg["type"]).toBe("css-update");
222
+
223
+ ws.close();
224
+ fs.rmSync(distDir, { recursive: true, force: true });
225
+ });
226
+ });
227
+
158
228
  describe("Scenario: CSS-only 변경 → css-update 메시지", () => {
159
229
  it("JS 출력은 동일하고 CSS만 변경되면 css-update를 전송한다", async () => {
160
230
  // 초기 빌드
@@ -0,0 +1,8 @@
1
+ # 초기 빌드 에러 보고 — LLM 검증
2
+
3
+ ## 검증 항목
4
+
5
+ - [x] client.worker.ts onEnd에서 초기 빌드 시 errors 필드가 포함되는지: `client.worker.ts:285-291` — `initialBuildResolve`에 `errors: result.errors.map((e) => e.text)` 포함 확인
6
+ - [x] EsbuildClientEngine.startWatch에서 반환값의 success 확인 후 에러 로깅하는지: `EsbuildClientEngine.ts:135-137` — `result != null && !result.success` 조건으로 `logger.error` 호출 확인. 테스트 실행 시 `[my-client] 초기 빌드 실패: Module not found: @angular/core; Syntax error in app.ts` 출력 확인
7
+ - [x] esbuild-client-config.ts에서 dev 모드 logLevel이 "warning"인지: `esbuild-client-config.ts:169` — `logLevel: isDev ? "warning" : "silent"` 확인. `isDev = options.mode === "dev"` (line 52)
8
+ - [x] esbuild-client-config.ts에서 build 모드 logLevel이 "silent"인지: 동일 라인 — 삼항 연산자의 false 분기가 `"silent"` 확인