@simplysm/sd-cli 14.0.48 → 14.0.51

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 (133) hide show
  1. package/README.md +22 -679
  2. package/dist/engines/EsbuildClientEngine.d.ts.map +1 -1
  3. package/dist/engines/EsbuildClientEngine.js +1 -2
  4. package/dist/engines/EsbuildClientEngine.js.map +1 -1
  5. package/dist/esbuild/esbuild-angular-compiler-plugin.js.map +1 -1
  6. package/dist/lint/lint-core.js.map +1 -1
  7. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  8. package/dist/orchestrators/ServerRuntimeManager.d.ts.map +1 -1
  9. package/dist/orchestrators/ServerRuntimeManager.js +2 -4
  10. package/dist/orchestrators/ServerRuntimeManager.js.map +1 -1
  11. package/dist/ts-compiler/SdTsCompiler.d.ts +1 -7
  12. package/dist/ts-compiler/SdTsCompiler.d.ts.map +1 -1
  13. package/dist/ts-compiler/SdTsCompiler.js +109 -116
  14. package/dist/ts-compiler/SdTsCompiler.js.map +1 -1
  15. package/dist/utils/package-utils.d.ts.map +1 -1
  16. package/dist/utils/package-utils.js.map +1 -1
  17. package/docs/angular-vite-plugin/sd-angular-plugin.md +54 -0
  18. package/docs/config/build-target.md +31 -0
  19. package/docs/config/npm-config.md +27 -0
  20. package/docs/config/sd-browser-support-config.md +19 -0
  21. package/docs/config/sd-build-package-config.md +21 -0
  22. package/docs/config/sd-capacitor-config.md +109 -0
  23. package/docs/config/sd-client-package-config.md +33 -0
  24. package/docs/config/sd-config.md +73 -0
  25. package/docs/config/sd-electron-config.md +27 -0
  26. package/docs/config/sd-package-config.md +18 -0
  27. package/docs/config/sd-post-publish-script-config.md +19 -0
  28. package/docs/config/sd-publish-config.md +72 -0
  29. package/docs/config/sd-pwa-config.md +41 -0
  30. package/docs/config/sd-scripts-package-config.md +19 -0
  31. package/docs/config/sd-server-package-config.md +32 -0
  32. package/docs/config/sd-watch-hook-config.md +19 -0
  33. package/docs/ts-compiler/sd-ts-compiler.md +152 -0
  34. package/package.json +7 -6
  35. package/src/engines/EsbuildClientEngine.ts +1 -2
  36. package/src/esbuild/esbuild-angular-compiler-plugin.ts +1 -1
  37. package/src/lint/lint-core.ts +1 -1
  38. package/src/orchestrators/DevOrchestrator.ts +3 -3
  39. package/src/orchestrators/ServerRuntimeManager.ts +2 -5
  40. package/src/ts-compiler/SdTsCompiler.ts +136 -154
  41. package/src/utils/package-utils.ts +1 -2
  42. package/tests/angular/angular-compiler-hmr-removal.verify.md +12 -12
  43. package/tests/angular/onbuild-lint-removal.verify.md +4 -4
  44. package/tests/angular/vite-angular-plugin-sdtscompiler.verify.md +9 -9
  45. package/tests/angular/vite-angular-plugin-vitest.verify.md +16 -16
  46. package/tests/capacitor/capacitor-android-exports.verify.md +7 -7
  47. package/tests/commands/publish-npm-local-split.verify.md +5 -5
  48. package/tests/commands/publish-responsibility-split.verify.md +9 -9
  49. package/tests/commands/publish-set.verify.md +3 -3
  50. package/tests/commands/publish-storage-split.verify.md +4 -4
  51. package/tests/commands/slice3-severity-cleanup.verify.md +8 -8
  52. package/tests/deps/deps-directory-separation.verify.md +11 -11
  53. package/tests/deps/replace-deps/replace-deps-perf.verify.md +6 -6
  54. package/tests/deps/server-externals/mise-toml-parse-intent.verify.md +8 -8
  55. package/tests/electron/electron-symlink-cleanup.verify.md +4 -4
  56. package/tests/engines/engine-duplicate-output-removal.verify.md +6 -6
  57. package/tests/engines/engine-typecheck-selection.verify.md +4 -4
  58. package/tests/engines/esbuild-client-engine.verify.md +11 -11
  59. package/tests/engines/normalize-result.verify.md +5 -5
  60. package/tests/engines/vite-dependency-cleanup.verify.md +11 -11
  61. package/tests/esbuild/esbuild-angular-compiler-plugin-hmr.verify.md +10 -10
  62. package/tests/esbuild/esbuild-angular-compiler-plugin-onload.verify.md +17 -17
  63. package/tests/esbuild/esbuild-angular-compiler-plugin-onstart-extraction.verify.md +12 -12
  64. package/tests/esbuild/esbuild-angular-compiler-plugin-sdtscompiler.verify.md +11 -11
  65. package/tests/esbuild/esbuild-angular-compiler-plugin-stylesheet.verify.md +13 -13
  66. package/tests/esbuild/esbuild-angular-compiler-plugin-worker.verify.md +32 -32
  67. package/tests/esbuild/esbuild-angular-compiler-plugin.spec.ts +1 -1
  68. package/tests/esbuild/esbuild-angular-compiler-plugin.verify.md +9 -9
  69. package/tests/esbuild/esbuild-postcss-plugin-chunking.verify.md +3 -3
  70. package/tests/esbuild/esbuild-postcss-plugin.acc.spec.ts +1 -1
  71. package/tests/esbuild/esbuild-tsc-plugin-imports.verify.md +9 -9
  72. package/tests/esbuild/esbuild-tsc-plugin.acc.spec.ts +2 -2
  73. package/tests/esbuild/esbuild-tsc-plugin.spec.ts +3 -3
  74. package/tests/esbuild/esbuild-worker-plugin-node.verify.md +7 -7
  75. package/tests/esbuild/esbuild-worker-plugin.spec.ts +1 -1
  76. package/tests/esbuild/esbuild-worker-plugin.verify.md +3 -3
  77. package/tests/orchestrators/build-orchestrator.spec.ts +9 -9
  78. package/tests/orchestrators/dev-orchestrator.spec.ts +4 -4
  79. package/tests/orchestrators/dist-delete-watcher.verify.md +6 -6
  80. package/tests/orchestrators/orchestrator-baseenv.verify.md +6 -6
  81. package/tests/orchestrators/orchestrator-diagnostic-formatting.verify.md +6 -6
  82. package/tests/orchestrators/orchestrator-initializemode-signature.verify.md +5 -5
  83. package/tests/orchestrators/slice1-stdout-to-consola.verify.md +6 -6
  84. package/tests/orchestrators/watch-orchestrator.spec.ts +2 -2
  85. package/tests/sd-cli-catch-all.verify.md +3 -3
  86. package/tests/sd-cli-log-tag.verify.md +7 -7
  87. package/tests/ts-compiler/SdTsCompiler-affected-files.verify.md +4 -4
  88. package/tests/ts-compiler/SdTsCompiler-crash-handling.verify.md +24 -0
  89. package/tests/ts-compiler/SdTsCompiler-diagnostics.verify.md +8 -8
  90. package/tests/ts-compiler/SdTsCompiler-emit.verify.md +5 -5
  91. package/tests/ts-compiler/SdTsCompiler.verify.md +20 -20
  92. package/tests/ts-compiler/scss-lint-integration.verify.md +10 -10
  93. package/tests/utils/copy-public-outdir.verify.md +4 -4
  94. package/tests/utils/copy-src.spec.ts +5 -5
  95. package/tests/utils/dev-http-server.verify.md +4 -4
  96. package/tests/utils/engine-watch-events.verify.md +8 -8
  97. package/tests/utils/esbuild-client-config-integration.verify.md +5 -5
  98. package/tests/utils/esbuild-client-config-postcss.verify.md +2 -2
  99. package/tests/utils/esbuild-client-config.acc.spec.ts +1 -1
  100. package/tests/utils/esbuild-client-config.spec.ts +4 -4
  101. package/tests/utils/esbuild-client-config.verify.md +16 -16
  102. package/tests/utils/esbuild-config.spec.ts +3 -3
  103. package/tests/utils/esbuild-index-html.verify.md +6 -6
  104. package/tests/utils/esbuild-postcss-plugin.spec.ts +1 -1
  105. package/tests/utils/esbuild-pwa.verify.md +5 -5
  106. package/tests/utils/esbuild-scss-plugin.verify.md +4 -4
  107. package/tests/utils/hmr-client-script.acc.spec.ts +1 -1
  108. package/tests/utils/hmr-service.verify.md +10 -10
  109. package/tests/utils/lint-core-import-paths.verify.md +6 -6
  110. package/tests/utils/ngtsc-build-core.spec.ts +1 -1
  111. package/tests/utils/package-utils.spec.ts +6 -6
  112. package/tests/utils/replace-deps-split.verify.md +11 -11
  113. package/tests/utils/replace-deps-watch.verify.md +5 -5
  114. package/tests/utils/server-production-files-import-paths.verify.md +10 -10
  115. package/tests/utils/vite-config-cleanup.verify.md +3 -3
  116. package/tests/workers/build-watch-paths-library.verify.md +6 -6
  117. package/tests/workers/build-watch-paths-ngtsc-server.verify.md +8 -8
  118. package/tests/workers/client-worker-browser-support.verify.md +3 -3
  119. package/tests/workers/client-worker-cleanup.verify.md +4 -4
  120. package/tests/workers/client-worker-initial-build-error.verify.md +3 -3
  121. package/tests/workers/client-worker-initial-build-warnings.verify.md +3 -3
  122. package/tests/workers/client-worker-mtime-incremental.verify.md +6 -6
  123. package/tests/workers/client-worker-onend-sync.verify.md +3 -3
  124. package/tests/workers/client-worker-refactor.verify.md +18 -18
  125. package/tests/workers/client-worker-ts-cache-invalidation.verify.md +8 -8
  126. package/tests/workers/dev-port-file.verify.md +3 -3
  127. package/tests/workers/ngtsc-build-rootnames-refresh.verify.md +4 -4
  128. package/tests/workers/server-build-context-dispose.verify.md +4 -4
  129. package/tests/workers/server-build-worker-plugin.verify.md +5 -5
  130. package/tests/workers/server-build-worker-refactoring.verify.md +10 -10
  131. package/tests/workers/server-build-worker.spec.ts +1 -1
  132. package/tests/workers/server-esbuild-context-integration.verify.md +6 -6
  133. package/tests/workers/server-esbuild-context-tsc.verify.md +3 -3
@@ -41,7 +41,7 @@ function setupPlugin(plugin: esbuild.Plugin) {
41
41
  outputFiles: [],
42
42
  metafile: { inputs: {}, outputs: {} },
43
43
  ...result,
44
- } as esbuild.BuildResult)) ?? null;
44
+ })) ?? null;
45
45
  },
46
46
  };
47
47
  }
@@ -73,7 +73,7 @@ function createErrorCompileResult(): ISdTsCompilerResult {
73
73
  return {
74
74
  ...createSuccessCompileResult(),
75
75
  errors: ["TS2322: Type 'string' is not assignable to type 'number'"],
76
- diagnostics: [{ category: 1, code: 2322, messageText: "Type mismatch" }] as any,
76
+ diagnostics: [{ category: 1, code: 2322, messageText: "Type mismatch" }],
77
77
  errorCount: 1,
78
78
  };
79
79
  }
@@ -41,7 +41,7 @@ function setupPlugin(plugin: esbuild.Plugin) {
41
41
  outputFiles: [],
42
42
  metafile: { inputs: {}, outputs: {} },
43
43
  ...result,
44
- } as esbuild.BuildResult)) ?? null;
44
+ })) ?? null;
45
45
  },
46
46
  };
47
47
  }
@@ -147,7 +147,7 @@ describe("createTscPlugin — Unit Tests", () => {
147
147
  it("env, includeTests 옵션을 SdTsCompiler에 전달한다", async () => {
148
148
  const result = createTscPlugin({
149
149
  ...baseOptions,
150
- env: "node" as any,
150
+ env: "node",
151
151
  includeTests: true,
152
152
  });
153
153
  const lifecycle = setupPlugin(result.plugin);
@@ -172,7 +172,7 @@ describe("createTscPlugin — Unit Tests", () => {
172
172
  ];
173
173
  mockCompileAsync.mockResolvedValue({
174
174
  ...createSuccessCompileResult(),
175
- diagnostics: diagnostics as any,
175
+ diagnostics: diagnostics,
176
176
  });
177
177
 
178
178
  const result = createTscPlugin(baseOptions);
@@ -2,10 +2,10 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] `NODE_WORKER_PATTERN` 정규식이 상대 경로만 감지: `esbuild-worker-plugin.ts:25` — `(\.\.?\/[^"']+)` 패턴으로 `./` 또는 `../`로 시작하는 경로만 캡처. `"some-package"` 같은 절대 모듈 경로는 매칭되지 않음.
6
- - [x] `bundleWorker`에 platform 파라미터 추가: `esbuild-worker-plugin.ts:42` — `platform: esbuild.Platform` 파라미터 사용. 기존 `"browser"` 하드코딩 제거.
7
- - [x] 브라우저 Worker 패턴이 `platform: "browser"`로 빌드: `esbuild-worker-plugin.ts:137` — `processWorkerBundle(fullWorkerPath, "browser")` 호출. 기존 동작 유지.
8
- - [x] Node.js Worker 패턴이 메인 빌드의 platform 계승: `esbuild-worker-plugin.ts:149` — `build.initialOptions.platform ?? "browser"` 사용. 서버 빌드(platform: "node")에서는 "node"로 빌드.
9
- - [x] 경로 치환이 `new URL("path", import.meta.url).href` 형태: `esbuild-worker-plugin.ts:153` — `new URL("${workerCodePath}", import.meta.url).href` 반환. file:// URL을 반환하므로 core-node Worker 호환.
10
- - [x] 기존 브라우저 Worker 테스트 22개 모두 통과: `pnpm test --run` 결과 32개 전체 통과 (기존 22 + 신규 10).
11
- - [x] `external: undefined` 설정 유지: `esbuild-worker-plugin.ts:62` — 메인 빌드의 external이 Worker 번들에 상속되지 않음.
5
+ - `NODE_WORKER_PATTERN` 정규식이 상대 경로만 감지: `esbuild-worker-plugin.ts:25` — `(\.\.?\/[^"']+)` 패턴으로 `./` 또는 `../`로 시작하는 경로만 캡처. `"some-package"` 같은 절대 모듈 경로는 매칭되지 않음.
6
+ - `bundleWorker`에 platform 파라미터 추가: `esbuild-worker-plugin.ts:42` — `platform: esbuild.Platform` 파라미터 사용. 기존 `"browser"` 하드코딩 제거.
7
+ - 브라우저 Worker 패턴이 `platform: "browser"`로 빌드: `esbuild-worker-plugin.ts:137` — `processWorkerBundle(fullWorkerPath, "browser")` 호출. 기존 동작 유지.
8
+ - Node.js Worker 패턴이 메인 빌드의 platform 계승: `esbuild-worker-plugin.ts:149` — `build.initialOptions.platform ?? "browser"` 사용. 서버 빌드(platform: "node")에서는 "node"로 빌드.
9
+ - 경로 치환이 `new URL("path", import.meta.url).href` 형태: `esbuild-worker-plugin.ts:153` — `new URL("${workerCodePath}", import.meta.url).href` 반환. file:// URL을 반환하므로 core-node Worker 호환.
10
+ - 기존 브라우저 Worker 테스트 22개 모두 통과: `pnpm test --run` 결과 32개 전체 통과 (기존 22 + 신규 10).
11
+ - `external: undefined` 설정 유지: `esbuild-worker-plugin.ts:62` — 메인 빌드의 external이 Worker 번들에 상속되지 않음.
@@ -44,7 +44,7 @@ function createTrackedBuild(overrides?: Partial<esbuild.BuildOptions>): {
44
44
  transformSyncCalls.push(args);
45
45
  return esbuild.transformSync(...args);
46
46
  },
47
- } as typeof esbuild;
47
+ };
48
48
 
49
49
  const build = {
50
50
  esbuild: trackedEsbuild,
@@ -2,6 +2,6 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] `transformWorkerPatterns`에서 `workerResult.warnings`를 warnings 배열에 push: `esbuild-worker-plugin.ts:109` — `warnings.push(...workerResult.warnings)` 확인. esbuild.buildSync의 BuildResult.warnings가 그대로 전달됨.
6
- - [x] 반환 결과의 `warnings` 필드에 수집된 경고가 포함됨: `esbuild-worker-plugin.ts:172` — `warnings` 배열이 결과 객체에 포함됨.
7
- - [x] 플러그인의 onLoad에서 warnings를 esbuild에 전달: `esbuild-worker-plugin.ts:210` — `warnings: result.warnings.length > 0 ? result.warnings : undefined` — onLoad 결과에 warnings를 포함하여 esbuild가 빌드 경고로 처리.
5
+ - `transformWorkerPatterns`에서 `workerResult.warnings`를 warnings 배열에 push: `esbuild-worker-plugin.ts:109` — `warnings.push(...workerResult.warnings)` 확인. esbuild.buildSync의 BuildResult.warnings가 그대로 전달됨.
6
+ - 반환 결과의 `warnings` 필드에 수집된 경고가 포함됨: `esbuild-worker-plugin.ts:172` — `warnings` 배열이 결과 객체에 포함됨.
7
+ - 플러그인의 onLoad에서 warnings를 esbuild에 전달: `esbuild-worker-plugin.ts:210` — `warnings: result.warnings.length > 0 ? result.warnings : undefined` — onLoad 결과에 warnings를 포함하여 esbuild가 빌드 경고로 처리.
@@ -129,7 +129,7 @@ function createMockWorkerProxy(overrides: Partial<MockWorkerProxy> = {}): MockWo
129
129
  }
130
130
 
131
131
  function setupDefaults(config: Partial<SdConfig> = {}): void {
132
- vi.mocked(loadSdConfig).mockResolvedValue({ packages: {}, ...config } as SdConfig);
132
+ vi.mocked(loadSdConfig).mockResolvedValue({ packages: {}, ...config });
133
133
  vi.mocked(getVersion).mockResolvedValue("1.0.0");
134
134
  }
135
135
 
@@ -372,7 +372,7 @@ describe("BuildOrchestrator.start", () => {
372
372
  run: vi.fn().mockRejectedValue(new Error("build failed")),
373
373
  startWatch: vi.fn(),
374
374
  stop: vi.fn().mockResolvedValue(undefined),
375
- } as any);
375
+ });
376
376
  const mockProxy = createMockWorkerProxy();
377
377
  vi.mocked(Worker.create).mockReturnValue(mockProxy as any);
378
378
 
@@ -413,7 +413,7 @@ describe("BuildOrchestrator.start", () => {
413
413
  }),
414
414
  startWatch: vi.fn(),
415
415
  stop: vi.fn().mockResolvedValue(undefined),
416
- } as any);
416
+ });
417
417
  const mockProxy = createMockWorkerProxy();
418
418
  vi.mocked(Worker.create).mockReturnValue(mockProxy as any);
419
419
 
@@ -466,7 +466,7 @@ describe("BuildOrchestrator.start", () => {
466
466
  stop: vi.fn().mockResolvedValue(undefined),
467
467
  };
468
468
  mockEngines.push(engine);
469
- return engine as any;
469
+ return engine;
470
470
  });
471
471
  const mockProxy = createMockWorkerProxy();
472
472
  vi.mocked(Worker.create).mockReturnValue(mockProxy as any);
@@ -496,7 +496,7 @@ describe("BuildOrchestrator.start", () => {
496
496
  }),
497
497
  startWatch: vi.fn(),
498
498
  stop: vi.fn().mockResolvedValue(undefined),
499
- } as any);
499
+ });
500
500
  const mockProxy = createMockWorkerProxy();
501
501
  vi.mocked(Worker.create).mockReturnValue(mockProxy as any);
502
502
 
@@ -602,7 +602,7 @@ describe("BuildOrchestrator client build", () => {
602
602
  }),
603
603
  startWatch: vi.fn(),
604
604
  stop: vi.fn().mockResolvedValue(undefined),
605
- } as any);
605
+ });
606
606
  const mockProxy = createMockWorkerProxy();
607
607
  vi.mocked(Worker.create).mockReturnValue(mockProxy as any);
608
608
 
@@ -635,7 +635,7 @@ describe("BuildOrchestrator client build", () => {
635
635
  }),
636
636
  startWatch: vi.fn(),
637
637
  stop: vi.fn().mockResolvedValue(undefined),
638
- } as any);
638
+ });
639
639
  const mockProxy = createMockWorkerProxy();
640
640
  vi.mocked(Worker.create).mockReturnValue(mockProxy as any);
641
641
 
@@ -720,7 +720,7 @@ describe("BuildOrchestrator client build", () => {
720
720
  run: vi.fn().mockRejectedValue(new Error("build crashed")),
721
721
  startWatch: vi.fn(),
722
722
  stop: vi.fn().mockResolvedValue(undefined),
723
- } as any);
723
+ });
724
724
  const mockProxy = createMockWorkerProxy();
725
725
  vi.mocked(Worker.create).mockReturnValue(mockProxy as any);
726
726
 
@@ -954,7 +954,7 @@ describe("BuildOrchestrator native build integration (Slice 1)", () => {
954
954
  }),
955
955
  startWatch: vi.fn(),
956
956
  stop: vi.fn().mockResolvedValue(undefined),
957
- } as any);
957
+ });
958
958
  const mockProxy = createMockWorkerProxy();
959
959
  vi.mocked(Worker.create).mockReturnValue(mockProxy as any);
960
960
 
@@ -187,7 +187,7 @@ function setupEngineWithResult(status: "success" | "error" = "success"): void {
187
187
  _pkgName: pkg.name,
188
188
  };
189
189
  mockBuildEngines.push(engine);
190
- return engine as any;
190
+ return engine;
191
191
  });
192
192
  }
193
193
 
@@ -275,7 +275,7 @@ describe("DevOrchestrator", () => {
275
275
  _pkgName: pkg.name,
276
276
  };
277
277
  mockBuildEngines.push(engine);
278
- return engine as any;
278
+ return engine;
279
279
  });
280
280
  });
281
281
 
@@ -471,13 +471,13 @@ describe("DevOrchestrator", () => {
471
471
  // --- Unit: disposes replaceDeps on shutdown ---
472
472
  it("disposes replaceDeps watcher on dev shutdown", async () => {
473
473
  const mockDispose = vi.fn();
474
- vi.mocked(watchReplaceDeps).mockResolvedValue({ entries: [], dispose: mockDispose } as any);
474
+ vi.mocked(watchReplaceDeps).mockResolvedValue({ entries: [], dispose: mockDispose });
475
475
 
476
476
  setupDefaults(createConfig({
477
477
  packages: { "service-server": { target: "server" } },
478
478
  replaceDeps: { "@simplysm/*": "packages/*/src" },
479
479
  }));
480
- vi.mocked(watchReplaceDeps).mockResolvedValue({ entries: [], dispose: mockDispose } as any);
480
+ vi.mocked(watchReplaceDeps).mockResolvedValue({ entries: [], dispose: mockDispose });
481
481
  setupEngineWithResult("success");
482
482
 
483
483
  const orchestrator = new DevOrchestrator({ targets: [], options: [] });
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] 디버그 하드코딩 제거: `DevWatchOrchestrator.ts:281` — `angular` 하드코딩 블록이 제거됨
6
- - [x] 모든 라이브러리 패키지 감시: `_startWatchMode()`에서 `this._libraryPackages`를 순회하며 각 `pkg.dir/dist`를 감시
7
- - [x] 클래스 필드 저장: `_distDeleteWatchers: FsWatcher[]` 필드에 push
8
- - [x] shutdown() 정리: `shutdown()`에서 `this._distDeleteWatchers.map(w => w.close())` 호출 확인
9
- - [x] 정리 후 초기화: `this._distDeleteWatchers = []`로 참조 해제 확인
10
- - [x] 로그 형식: `[dist-delete:{패키지명}]` 형식으로 어떤 패키지의 dist가 삭제되었는지 식별 가능
5
+ - 디버그 하드코딩 제거: `DevWatchOrchestrator.ts:281` — `angular` 하드코딩 블록이 제거됨
6
+ - 모든 라이브러리 패키지 감시: `_startWatchMode()`에서 `this._libraryPackages`를 순회하며 각 `pkg.dir/dist`를 감시
7
+ - 클래스 필드 저장: `_distDeleteWatchers: FsWatcher[]` 필드에 push
8
+ - shutdown() 정리: `shutdown()`에서 `this._distDeleteWatchers.map(w => w.close())` 호출 확인
9
+ - 정리 후 초기화: `this._distDeleteWatchers = []`로 참조 해제 확인
10
+ - 로그 형식: `[dist-delete:{패키지명}]` 형식으로 어떤 패키지의 dist가 삭제되었는지 식별 가능
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] BaseOrchestrator에 `_baseEnv` protected 필드 추가됨: `BaseOrchestrator.ts:31`에 `protected _baseEnv!: { VER: string; DEV: string }` 확인
6
- - [x] BaseOrchestrator.initialize()에서 getVersion + dev 파라미터로 _baseEnv 설정: `BaseOrchestrator.ts:69-71`에 `getVersion(this._cwd)` → `{ VER: version, DEV: params.dev ? "true" : "false" }` 확인
7
- - [x] DevOrchestrator._initializeMode()에서 getVersion/baseEnv 코드 제거: `_initializeMode`는 `void` 반환, `getVersion` import도 제거됨
8
- - [x] DevOrchestrator에서 private `_baseEnv` 필드 제거: BaseOrchestrator의 protected 필드를 상속
9
- - [x] WatchOrchestrator는 변경 없음: BaseOrchestrator에서 _baseEnv가 자동 설정되며, WatchOrchestrator는 이를 사용하지 않아도 무해
10
- - [x] 기존 테스트 업데이트: watch-orchestrator.spec.ts에서 `_baseEnv` 미존재 assertion 제거 (이제 Base 공통 필드)
5
+ - BaseOrchestrator에 `_baseEnv` protected 필드 추가됨: `BaseOrchestrator.ts:31`에 `protected _baseEnv!: { VER: string; DEV: string }` 확인
6
+ - BaseOrchestrator.initialize()에서 getVersion + dev 파라미터로 _baseEnv 설정: `BaseOrchestrator.ts:69-71`에 `getVersion(this._cwd)` → `{ VER: version, DEV: params.dev ? "true" : "false" }` 확인
7
+ - DevOrchestrator._initializeMode()에서 getVersion/baseEnv 코드 제거: `_initializeMode`는 `void` 반환, `getVersion` import도 제거됨
8
+ - DevOrchestrator에서 private `_baseEnv` 필드 제거: BaseOrchestrator의 protected 필드를 상속
9
+ - WatchOrchestrator는 변경 없음: BaseOrchestrator에서 _baseEnv가 자동 설정되며, WatchOrchestrator는 이를 사용하지 않아도 무해
10
+ - 기존 테스트 업데이트: watch-orchestrator.spec.ts에서 `_baseEnv` 미존재 assertion 제거 (이제 Base 공통 필드)
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] BuildOrchestrator._printBuildResults()가 formatDiagnosticsOutput()을 사용: `BuildOrchestrator.ts:465-468`에서 `formatDiagnosticsOutput(allDiagnostics, this._cwd)` 호출 확인. 기존 인라인 `FormatDiagnosticsHost` 생성 + `sortAndDeduplicateDiagnostics` + `formatDiagnosticsWithColorAndContext` 패턴이 제거됨
6
- - [x] TypecheckOrchestrator._aggregateTypecheckResults()가 formatDiagnosticsOutput()을 사용: `TypecheckOrchestrator.ts:362`에서 `formatDiagnosticsOutput(allDiagnostics, this._cwd)` 호출 확인. 기존 인라인 `FormatDiagnosticsHost` + `sortAndDeduplicateDiagnostics` + `formatDiagnosticsWithColorAndContext` 패턴이 제거됨
7
- - [x] BuildOrchestrator의 import에 `formatDiagnosticsOutput`이 추가됨 (line 15)
8
- - [x] TypecheckOrchestrator의 import에 `formatDiagnosticsOutput`이 추가됨 (line 11)
9
- - [x] 기존 동작 보존: BuildOrchestrator의 per-result 경고/에러 로깅 로직(`formatBuildMessages`)은 그대로 유지됨
10
- - [x] 기존 동작 보존: TypecheckOrchestrator의 요약 로그/lint 결과 생성 로직은 그대로 유지됨
5
+ - BuildOrchestrator._printBuildResults()가 formatDiagnosticsOutput()을 사용: `BuildOrchestrator.ts:465-468`에서 `formatDiagnosticsOutput(allDiagnostics, this._cwd)` 호출 확인. 기존 인라인 `FormatDiagnosticsHost` 생성 + `sortAndDeduplicateDiagnostics` + `formatDiagnosticsWithColorAndContext` 패턴이 제거됨
6
+ - TypecheckOrchestrator._aggregateTypecheckResults()가 formatDiagnosticsOutput()을 사용: `TypecheckOrchestrator.ts:362`에서 `formatDiagnosticsOutput(allDiagnostics, this._cwd)` 호출 확인. 기존 인라인 `FormatDiagnosticsHost` + `sortAndDeduplicateDiagnostics` + `formatDiagnosticsWithColorAndContext` 패턴이 제거됨
7
+ - BuildOrchestrator의 import에 `formatDiagnosticsOutput`이 추가됨 (line 15)
8
+ - TypecheckOrchestrator의 import에 `formatDiagnosticsOutput`이 추가됨 (line 11)
9
+ - 기존 동작 보존: BuildOrchestrator의 per-result 경고/에러 로깅 로직(`formatBuildMessages`)은 그대로 유지됨
10
+ - 기존 동작 보존: TypecheckOrchestrator의 요약 로그/lint 결과 생성 로직은 그대로 유지됨
@@ -2,8 +2,8 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] BaseOrchestrator._initializeMode 추상 메서드에서 `options: string[]` 파라미터 제거: `BaseOrchestrator.ts:109-111`에 `(config: SdConfig, targets: string[])` 2개 파라미터만 확인
6
- - [x] BaseOrchestrator.initialize() 호출부에서 options 인자 제거: `BaseOrchestrator.ts:79`에 `_initializeMode(sdConfig, params.targets)` 확인
7
- - [x] options는 BaseOrchestrator.initialize()의 loadSdConfig()에서만 사용됨: `BaseOrchestrator.ts:53`에 `opt: params.options` 확인
8
- - [x] DevOrchestrator._initializeMode 시그니처 변경 불필요: 이미 `(config, targets)` 2개 파라미터
9
- - [x] WatchOrchestrator._initializeMode 시그니처 변경 불필요: 이미 `(config, targets)` 2개 파라미터
5
+ - BaseOrchestrator._initializeMode 추상 메서드에서 `options: string[]` 파라미터 제거: `BaseOrchestrator.ts:109-111`에 `(config: SdConfig, targets: string[])` 2개 파라미터만 확인
6
+ - BaseOrchestrator.initialize() 호출부에서 options 인자 제거: `BaseOrchestrator.ts:79`에 `_initializeMode(sdConfig, params.targets)` 확인
7
+ - options는 BaseOrchestrator.initialize()의 loadSdConfig()에서만 사용됨: `BaseOrchestrator.ts:53`에 `opt: params.options` 확인
8
+ - DevOrchestrator._initializeMode 시그니처 변경 불필요: 이미 `(config, targets)` 2개 파라미터
9
+ - WatchOrchestrator._initializeMode 시그니처 변경 불필요: 이미 `(config, targets)` 2개 파라미터
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] BaseOrchestrator.ts:105 — `this._logger.start("종료 중...")` 사용 확인: `process.stdout.write("⏳ 종료 중...\n")` → `this._logger.start("종료 중...")` 변경 완료. 수동 아이콘 ⏳ 제거됨
6
- - [x] BaseOrchestrator.ts:107 — `this._logger.success("종료 완료")` 사용 확인: `process.stdout.write("✔ 종료 완료\n")` → `this._logger.success("종료 완료")` 변경 완료. 수동 아이콘 ✔ 제거됨
7
- - [x] BuildOrchestrator.ts:173 — `this._logger.info("빌드할 패키지가 없습니다.")` 사용 확인: `process.stdout.write("✔ 빌드할 패키지가 없습니다.\n")` → `this._logger.info(...)` 변경 완료. 자동 테스트에서도 검증됨
8
- - [x] WatchOrchestrator.ts:69 — `this._logger.warn("워치 대상 패키지가 없습니다.")` 사용 확인: `process.stdout.write("⚠ 워치 대상 패키지가 없습니다.\n")` → `this._logger.warn(...)` 변경 완료. 수동 아이콘 ⚠ 제거됨
9
- - [x] publish-command.ts:121 — `logger.info("배포할 패키지가 없습니다.")` 사용 확인: `process.stdout.write("✔ 배포할 패키지가 없습니다.\n")` → `logger.info(...)` 변경 완료. 수동 아이콘 ✔ 제거됨
10
- - [x] 모든 대상 파일에서 process.stdout.write가 더 이상 사용되지 않음 확인 (output-utils.ts의 printServers 내 `"\n"` 출력은 Feature 1.2 범위 외)
5
+ - BaseOrchestrator.ts:105 — `this._logger.start("종료 중...")` 사용 확인: `process.stdout.write("⏳ 종료 중...\n")` → `this._logger.start("종료 중...")` 변경 완료. 수동 아이콘 ⏳ 제거됨
6
+ - BaseOrchestrator.ts:107 — `this._logger.success("종료 완료")` 사용 확인: `process.stdout.write("✔ 종료 완료\n")` → `this._logger.success("종료 완료")` 변경 완료. 수동 아이콘 ✔ 제거됨
7
+ - BuildOrchestrator.ts:173 — `this._logger.info("빌드할 패키지가 없습니다.")` 사용 확인: `process.stdout.write("✔ 빌드할 패키지가 없습니다.\n")` → `this._logger.info(...)` 변경 완료. 자동 테스트에서도 검증됨
8
+ - WatchOrchestrator.ts:69 — `this._logger.warn("워치 대상 패키지가 없습니다.")` 사용 확인: `process.stdout.write("⚠ 워치 대상 패키지가 없습니다.\n")` → `this._logger.warn(...)` 변경 완료. 수동 아이콘 ⚠ 제거됨
9
+ - publish-command.ts:121 — `logger.info("배포할 패키지가 없습니다.")` 사용 확인: `process.stdout.write("✔ 배포할 패키지가 없습니다.\n")` → `logger.info(...)` 변경 완료. 수동 아이콘 ✔ 제거됨
10
+ - 모든 대상 파일에서 process.stdout.write가 더 이상 사용되지 않음 확인 (output-utils.ts의 printServers 내 `"\n"` 출력은 Feature 1.2 범위 외)
@@ -156,7 +156,7 @@ describe("WatchOrchestrator", () => {
156
156
  _pkgName: pkg.name,
157
157
  };
158
158
  mockBuildEngines.push(engine);
159
- return engine as any;
159
+ return engine;
160
160
  });
161
161
  });
162
162
 
@@ -494,7 +494,7 @@ describe("WatchOrchestrator", () => {
494
494
 
495
495
  it("disposes replaceDepWatcher even when initialize fails after watchReplaceDeps", async () => {
496
496
  const mockDispose = vi.fn();
497
- vi.mocked(watchReplaceDeps).mockResolvedValue({ entries: [], dispose: mockDispose } as any);
497
+ vi.mocked(watchReplaceDeps).mockResolvedValue({ entries: [], dispose: mockDispose });
498
498
 
499
499
  // loadSdConfig succeeds but we'll make classifyWatchPackages fail
500
500
  // by having watchReplaceDeps succeed first, then causing a later failure
@@ -2,6 +2,6 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] catch 블록이 에러 코드를 확인하여 MODULE_NOT_FOUND/ERR_MODULE_NOT_FOUND만 무시: `sd-cli.ts:37-41` 확인 완료. `code` 프로퍼티를 체크하고 해당 코드가 아닌 경우 `console.warn`으로 경고 출력
6
- - [x] 예상 외 에러(SyntaxError 등)에 경고 로그 출력: `console.warn("[sd-cli] replaceDeps 사전 설정 실패:", ...)` 확인
7
- - [x] Phase 2로 정상 진행: catch 후 코드 흐름이 Phase 2(line 44~)로 계속됨 확인
5
+ - catch 블록이 에러 코드를 확인하여 MODULE_NOT_FOUND/ERR_MODULE_NOT_FOUND만 무시: `sd-cli.ts:37-41` 확인 완료. `code` 프로퍼티를 체크하고 해당 코드가 아닌 경우 `console.warn`으로 경고 출력
6
+ - 예상 외 에러(SyntaxError 등)에 경고 로그 출력: `console.warn("[sd-cli] replaceDeps 사전 설정 실패:", ...)` 확인
7
+ - Phase 2로 정상 진행: catch 후 코드 흐름이 Phase 2(line 44~)로 계속됨 확인
@@ -2,10 +2,10 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] sd-cli.ts에 `consola.withTag("sd:cli")` 모듈-레벨 로거가 생성됨: `src/sd-cli.ts:18` — `const logger = consola.withTag("sd:cli");`
6
- - [x] sd-cli.ts replaceDeps 경고에서 수동 `[sd-cli]` prefix가 제거됨: `src/sd-cli.ts:43` — `logger.warn("replaceDeps 사전 설정 실패:", ...)` (이전: `consola.warn("[sd-cli] replaceDeps ..."`)
7
- - [x] sd-cli.ts CPU affinity 경고가 consola로 전환됨: `src/sd-cli.ts:104-108` — `logger.warn("CPU affinity/priority 설정 실패:", ...)` (이전: `console.warn("Failed to configure ..."`)
8
- - [x] sd-cli.ts에서 `eslint-disable-next-line no-console` 주석이 제거됨: `src/sd-cli.ts:104` 부근에 해당 주석 없음
9
- - [x] sd-cli-entry.ts에 `consola.withTag("sd:cli:entry")` 모듈-레벨 로거가 생성됨: `src/sd-cli-entry.ts:20` — `const logger = consola.withTag("sd:cli:entry");`
10
- - [x] sd-cli-entry.ts .fail() 핸들러가 태그 로거를 사용함: `src/sd-cli-entry.ts:329` — `logger.error(msg);` (이전: `consola.error(msg)`)
11
- - [x] sd-cli-entry.ts의 `/* eslint-disable no-console */`이 유지됨: `src/sd-cli-entry.ts:1` — `collectYargsHelp`의 `console.log` 사용 때문에 필요
5
+ - sd-cli.ts에 `consola.withTag("sd:cli")` 모듈-레벨 로거가 생성됨: `src/sd-cli.ts:18` — `const logger = consola.withTag("sd:cli");`
6
+ - sd-cli.ts replaceDeps 경고에서 수동 `[sd-cli]` prefix가 제거됨: `src/sd-cli.ts:43` — `logger.warn("replaceDeps 사전 설정 실패:", ...)` (이전: `consola.warn("[sd-cli] replaceDeps ..."`)
7
+ - sd-cli.ts CPU affinity 경고가 consola로 전환됨: `src/sd-cli.ts:104-108` — `logger.warn("CPU affinity/priority 설정 실패:", ...)` (이전: `console.warn("Failed to configure ..."`)
8
+ - sd-cli.ts에서 `eslint-disable-next-line no-console` 주석이 제거됨: `src/sd-cli.ts:104` 부근에 해당 주석 없음
9
+ - sd-cli-entry.ts에 `consola.withTag("sd:cli:entry")` 모듈-레벨 로거가 생성됨: `src/sd-cli-entry.ts:20` — `const logger = consola.withTag("sd:cli:entry");`
10
+ - sd-cli-entry.ts .fail() 핸들러가 태그 로거를 사용함: `src/sd-cli-entry.ts:329` — `logger.error(msg);` (이전: `consola.error(msg)`)
11
+ - sd-cli-entry.ts의 `/* eslint-disable no-console */`이 유지됨: `src/sd-cli-entry.ts:1` — `collectYargsHelp`의 `console.log` 사용 때문에 필요
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] Non-Angular 전역 변경 시 affectedFiles=undefined: `_findAffectedFilesForTsc`에서 `result.affected`가 `ts.SourceFile`이 아닌 경우(`"fileName" in result.affected` 실패) `return undefined`를 반환. (`SdTsCompiler.ts:272-275`)
6
- - [x] Angular TypeScript 5.9 크래시 방어: `_findAffectedFilesForAngular`에서 `getSemanticDiagnosticsOfNextAffectedFile`를 try-catch로 감싸고, catch 시 전체 소스를 affected로 처리 (`SdTsCompiler.ts:290-298`)
7
- - [x] Angular 리소스 의존성 기반 추가 affected: `_findAffectedFilesForAngular`에서 `sourceFileCache.modifiedFiles`와 `angularCompiler.getResourceDependencies(sourceFile)`를 비교하여 매칭 시 `_diagnosticCache.delete(sourceFile)` + `affectedSourceFiles.add(sourceFile)` (`SdTsCompiler.ts:317-334`)
8
- - [x] Angular .ngtypecheck.ts → 원본 .ts 매핑: `getSemanticDiagnosticsOfNextAffectedFile`의 두 번째 인자 콜백에서 `.ngtypecheck.ts` 파일을 원본 `.ts`로 매핑하여 affected에 추가 (`SdTsCompiler.ts:284-295`)
5
+ - Non-Angular 전역 변경 시 affectedFiles=undefined: `_findAffectedFilesForTsc`에서 `result.affected`가 `ts.SourceFile`이 아닌 경우(`"fileName" in result.affected` 실패) `return undefined`를 반환. (`SdTsCompiler.ts:272-275`)
6
+ - Angular TypeScript 5.9 크래시 방어: `_findAffectedFilesForAngular`에서 `getSemanticDiagnosticsOfNextAffectedFile`를 try-catch로 감싸고, catch 시 전체 소스를 affected로 처리 (`SdTsCompiler.ts:290-298`)
7
+ - Angular 리소스 의존성 기반 추가 affected: `_findAffectedFilesForAngular`에서 `sourceFileCache.modifiedFiles`와 `angularCompiler.getResourceDependencies(sourceFile)`를 비교하여 매칭 시 `_diagnosticCache.delete(sourceFile)` + `affectedSourceFiles.add(sourceFile)` (`SdTsCompiler.ts:317-334`)
8
+ - Angular .ngtypecheck.ts → 원본 .ts 매핑: `getSemanticDiagnosticsOfNextAffectedFile`의 두 번째 인자 콜백에서 `.ngtypecheck.ts` 파일을 원본 `.ts`로 매핑하여 affected에 추가 (`SdTsCompiler.ts:284-295`)
@@ -0,0 +1,24 @@
1
+ # SdTsCompiler 크래시 핸들링 — LLM 검증
2
+
3
+ ## 검증 항목
4
+
5
+ ### 구조적 검증
6
+
7
+ - 단계별 try-catch: compileAsync 내 analyzeAsync, findAffectedFiles, emit, collectDiagnostics, lintAndGlobalScss 각각이 개별 try-catch로 감싸져 있는지 확인
8
+ - 바깥 try-catch 유지: 단계별 try-catch를 감싸는 최종 안전망 try-catch가 존재하는지 확인
9
+ - SdError 사용: catch 블록에서 SdError(원본에러, context)로 감싸는지 확인
10
+ - crashDiagnostics 누적: 각 단계 catch에서 SerializedDiagnostic을 배열에 누적하고, 최종 결과에 합산하는지 확인
11
+ - per-file 프로브 제거: _probeCrashPerFileAngular, _probeCrashPerFileTsc 메서드가 삭제되었는지 확인
12
+
13
+ ### 파일 추적 검증
14
+
15
+ - _findAffectedFilesForTsc: ignoreSourceFile 콜백으로 _setCrashContext("findAffectedFiles", sourceFile.fileName)를 호출하는지 확인
16
+ - _findAffectedFilesForAngular: 기존 콜백에 _setCrashContext("findAffectedFiles", sourceFile.fileName) 추가, 기존 ngtypecheck 필터링 로직 유지 확인
17
+ - _collectDiagnosticsForTsc: 파일 단위 루프로 분리되어 _setCrashContext("collectDiagnostics", sourceFile.fileName)를 호출하는지 확인
18
+ - _collectDiagnosticsForAngular: 기존 파일 추적 유지 확인
19
+
20
+ ### 단계 간 의존성 검증
21
+
22
+ - analyzeAsync 크래시 시: _ngtscProgram 미설정 → Angular emit/diagnostics에서 null 체크로 스킵하는지 확인
23
+ - findAffectedFiles 크래시 시: affectedFiles=undefined(전체), _affectedSourceFiles 빈 Set → emit/diagnostics가 전체 대상으로 동작하는지 확인
24
+ - _createCrashDiagnostic: SdError cause chain으로 원본 에러 메시지+스택이 보존되는지 확인
@@ -2,11 +2,11 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] Angular TS 5.9 getSemanticDiagnostics 크래시 방어: `_collectDiagnosticsForAngular`에서 `builderProgram.getSemanticDiagnostics(sourceFile)`를 try-catch로 감싸고, catch 시 빈 배열로 처리 (`SdTsCompiler.ts` `_collectDiagnosticsForAngular` 메서드 내 per-file 루프)
6
- - [x] Angular diagnosticCache 갱신: affected 파일이면 `getDiagnosticsForFile` 호출 후 `this._diagnosticCache.set(sourceFile, angularDiagnostics)` 수행 (`SdTsCompiler.ts` `_collectDiagnosticsForAngular` 메서드)
7
- - [x] Angular diagnosticCache 반환: 비-affected 파일이면 `this._diagnosticCache.get(sourceFile)` 캐시 결과 반환 (`SdTsCompiler.ts` `_collectDiagnosticsForAngular` 메서드)
8
- - [x] Angular ignoreForDiagnostics 건너뜀: per-file 루프에서 `angularCompiler.ignoreForDiagnostics.has(sourceFile)` 체크 후 continue
9
- - [x] Non-Angular 7종 진단 수집: `_collectDiagnosticsForTsc`에서 config + syntactic + options + global + semantic + declaration 진단 수집 (declaration은 `!output.dts` 조건)
10
- - [x] isWorkspaceDiagnostic 필터링: `_finalizeDiagnostics`에서 `rawDiagnostics.filter(d => isWorkspaceDiagnostic(d, this._options.cwd))` 적용
11
- - [x] serializeDiagnostic 직렬화: `_finalizeDiagnostics`에서 `filtered.map(serializeDiagnostic)` 적용
12
- - [x] formatDiagnosticError 포맷: `_finalizeDiagnostics`에서 Error 카테고리 진단을 `formatDiagnosticError`로 포맷하여 `errors` 배열 생성
5
+ - Angular TS 5.9 getSemanticDiagnostics 크래시 방어: `_collectDiagnosticsForAngular`에서 `builderProgram.getSemanticDiagnostics(sourceFile)`를 try-catch로 감싸고, catch 시 빈 배열로 처리 (`SdTsCompiler.ts` `_collectDiagnosticsForAngular` 메서드 내 per-file 루프)
6
+ - Angular diagnosticCache 갱신: affected 파일이면 `getDiagnosticsForFile` 호출 후 `this._diagnosticCache.set(sourceFile, angularDiagnostics)` 수행 (`SdTsCompiler.ts` `_collectDiagnosticsForAngular` 메서드)
7
+ - Angular diagnosticCache 반환: 비-affected 파일이면 `this._diagnosticCache.get(sourceFile)` 캐시 결과 반환 (`SdTsCompiler.ts` `_collectDiagnosticsForAngular` 메서드)
8
+ - Angular ignoreForDiagnostics 건너뜀: per-file 루프에서 `angularCompiler.ignoreForDiagnostics.has(sourceFile)` 체크 후 continue
9
+ - Non-Angular 7종 진단 수집: `_collectDiagnosticsForTsc`에서 config + syntactic + options + global + semantic + declaration 진단 수집 (declaration은 `!output.dts` 조건)
10
+ - isWorkspaceDiagnostic 필터링: `_finalizeDiagnostics`에서 `rawDiagnostics.filter(d => isWorkspaceDiagnostic(d, this._options.cwd))` 적용
11
+ - serializeDiagnostic 직렬화: `_finalizeDiagnostics`에서 `filtered.map(serializeDiagnostic)` 적용
12
+ - formatDiagnosticError 포맷: `_finalizeDiagnostics`에서 Error 카테고리 진단을 `formatDiagnosticError`로 포맷하여 `errors` 배열 생성
@@ -2,8 +2,8 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] Angular emit — additionalTransformers 병합: `_emitAngular`에서 `emitOptions?.additionalTransformers`의 before/after를 `angularCompiler.prepareEmit().transformers`에 push 병합 (`SdTsCompiler.ts` `_emitAngular` 메서드)
6
- - [x] Angular emit — tsbuildinfo 영속화 크래시 방어: `builderProgram.emit(undefined, () => {})` 호출이 try-catch로 감싸져 있음 (`SdTsCompiler.ts` `_emitAngular` 메서드 하단)
7
- - [x] Angular emit — sourceFilter 적용: emitResults를 `emitOptions.sourceFilter`로 필터링하여 반환 (`SdTsCompiler.ts` `_emitAngular` 메서드 마지막)
8
- - [x] Angular emit — incrementalCompilation.recordSuccessfulEmit 호출: writeFileCallback에서 각 sourceFile에 대해 호출됨
9
- - [x] Angular emit — ignoreForEmit/isDeclarationFile/safeToSkipEmit 건너뜀: per-file emit 루프에서 세 조건 모두 continue 처리
5
+ - Angular emit — additionalTransformers 병합: `_emitAngular`에서 `emitOptions?.additionalTransformers`의 before/after를 `angularCompiler.prepareEmit().transformers`에 push 병합 (`SdTsCompiler.ts` `_emitAngular` 메서드)
6
+ - Angular emit — tsbuildinfo 영속화 크래시 방어: `builderProgram.emit(undefined, () => {})` 호출이 try-catch로 감싸져 있음 (`SdTsCompiler.ts` `_emitAngular` 메서드 하단)
7
+ - Angular emit — sourceFilter 적용: emitResults를 `emitOptions.sourceFilter`로 필터링하여 반환 (`SdTsCompiler.ts` `_emitAngular` 메서드 마지막)
8
+ - Angular emit — incrementalCompilation.recordSuccessfulEmit 호출: writeFileCallback에서 각 sourceFile에 대해 호출됨
9
+ - Angular emit — ignoreForEmit/isDeclarationFile/safeToSkipEmit 건너뜀: per-file emit 루프에서 세 조건 모두 continue 처리
@@ -4,38 +4,38 @@
4
4
 
5
5
  ### Angular host 확장 (Slice 2)
6
6
 
7
- - [x] **readResource 콜백 등록**: `SdTsCompiler.ts:268` — `hostAny["readResource"]`가 `host.readFile` 위임. `angular-compiler.ts:181-183`의 패턴과 동일 확인
8
- - [x] **transformResource 콜백 등록**: `SdTsCompiler.ts:271-286` — `transformStylesheet` 옵션이 있을 때만 등록. context.type !== "style" 시 null 반환, 빈 데이터 시 `{ content: "" }` 반환. `angular-compiler.ts:186-203`의 패턴과 동일
9
- - [x] **getModifiedResourceFiles 콜백**: `SdTsCompiler.ts:289-292` — `sourceFileCache`가 있을 때만 등록, `cache.modifiedFiles` 반환. `angular-compiler.ts:206-208`의 패턴과 동일
10
- - [x] **resourceNameToFileName 콜백**: `SdTsCompiler.ts:295-312` — 경로 해석 + `externalStylesheets` 처리 + `hasTemplateExtension` 분기. `angular-compiler.ts:210-228`의 패턴과 동일
7
+ - **readResource 콜백 등록**: `SdTsCompiler.ts:268` — `hostAny["readResource"]`가 `host.readFile` 위임. `angular-compiler.ts:181-183`의 패턴과 동일 확인
8
+ - **transformResource 콜백 등록**: `SdTsCompiler.ts:271-286` — `transformStylesheet` 옵션이 있을 때만 등록. context.type !== "style" 시 null 반환, 빈 데이터 시 `{ content: "" }` 반환. `angular-compiler.ts:186-203`의 패턴과 동일
9
+ - **getModifiedResourceFiles 콜백**: `SdTsCompiler.ts:289-292` — `sourceFileCache`가 있을 때만 등록, `cache.modifiedFiles` 반환. `angular-compiler.ts:206-208`의 패턴과 동일
10
+ - **resourceNameToFileName 콜백**: `SdTsCompiler.ts:295-312` — 경로 해석 + `externalStylesheets` 처리 + `hasTemplateExtension` 분기. `angular-compiler.ts:210-228`의 패턴과 동일
11
11
 
12
12
  ### moduleResolutionCache 재사용 (Slice 2)
13
13
 
14
- - [x] **초기 생성**: `SdTsCompiler.ts:252-253` — `ts.createModuleResolutionCache(cwd, canonicalize, options, this._packageJsonCache)` 호출. 4번째 인자로 `this._packageJsonCache` 전달
15
- - [x] **packageJsonCache 추출**: `SdTsCompiler.ts:256-258` — 첫 호출 시 `moduleResolutionCache.getPackageJsonInfoCache()`로 추출하여 `_packageJsonCache`에 저장
16
- - [x] **후속 호출 시 재사용**: 두 번째 `compileAsync` 호출 시 `this._packageJsonCache`가 null이 아니므로 `createModuleResolutionCache`의 4번째 인자로 전달됨 → 패키지 해석 결과 재사용
14
+ - **초기 생성**: `SdTsCompiler.ts:252-253` — `ts.createModuleResolutionCache(cwd, canonicalize, options, this._packageJsonCache)` 호출. 4번째 인자로 `this._packageJsonCache` 전달
15
+ - **packageJsonCache 추출**: `SdTsCompiler.ts:256-258` — 첫 호출 시 `moduleResolutionCache.getPackageJsonInfoCache()`로 추출하여 `_packageJsonCache`에 저장
16
+ - **후속 호출 시 재사용**: 두 번째 `compileAsync` 호출 시 `this._packageJsonCache`가 null이 아니므로 `createModuleResolutionCache`의 4번째 인자로 전달됨 → 패키지 해석 결과 재사용
17
17
 
18
18
  ### sourceFileCache 관리 (Slice 2-3)
19
19
 
20
- - [x] **외부 제공 시 사용**: `SdTsCompiler.ts:117` — `this._options.sourceFileCache ?? new AngularSourceFileCache()` 패턴으로 외부 제공 시 우선 사용
21
- - [x] **미제공 시 내부 생성**: 위 라인에서 `??` 연산자로 `AngularSourceFileCache` 내부 생성
22
- - [x] **augmentHostWithCaching 적용**: `SdTsCompiler.ts:118` — Angular 프로그램 생성 직전에 `augmentHostWithCaching(host, this._sourceFileCache)` 호출
23
- - [x] **증분 빌드 시 무효화**: `SdTsCompiler.ts:74-76` — `modifiedFiles != null && size > 0 && _sourceFileCache != null` 조건으로 `sourceFileCache.invalidate(modifiedFiles)` 호출
20
+ - **외부 제공 시 사용**: `SdTsCompiler.ts:117` — `this._options.sourceFileCache ?? new AngularSourceFileCache()` 패턴으로 외부 제공 시 우선 사용
21
+ - **미제공 시 내부 생성**: 위 라인에서 `??` 연산자로 `AngularSourceFileCache` 내부 생성
22
+ - **augmentHostWithCaching 적용**: `SdTsCompiler.ts:118` — Angular 프로그램 생성 직전에 `augmentHostWithCaching(host, this._sourceFileCache)` 호출
23
+ - **증분 빌드 시 무효화**: `SdTsCompiler.ts:74-76` — `modifiedFiles != null && size > 0 && _sourceFileCache != null` 조건으로 `sourceFileCache.invalidate(modifiedFiles)` 호출
24
24
 
25
25
  ### Non-Angular writeFile 훅 (Slice 2)
26
26
 
27
- - [x] **emit 모드에서만 설정**: `SdTsCompiler.ts:238` — `!isForAngular && needsEmit` 조건으로 writeFile 훅 적용
28
- - [x] **createOutputPathRewriter 사용**: `SdTsCompiler.ts:239` — `createOutputPathRewriter(pkgDir)` 호출
29
- - [x] **addJsExtensionToImports 적용**: `SdTsCompiler.ts:243` — `.js` 파일에만 `addJsExtensionToImports` 적용
30
- - [x] **result null 시 쓰기 스킵**: `SdTsCompiler.ts:241` — `result != null` 체크로 다른 패키지 출력 무시
27
+ - **emit 모드에서만 설정**: `SdTsCompiler.ts:238` — `!isForAngular && needsEmit` 조건으로 writeFile 훅 적용
28
+ - **createOutputPathRewriter 사용**: `SdTsCompiler.ts:239` — `createOutputPathRewriter(pkgDir)` 호출
29
+ - **addJsExtensionToImports 적용**: `SdTsCompiler.ts:243` — `.js` 파일에만 `addJsExtensionToImports` 적용
30
+ - **result null 시 쓰기 스킵**: `SdTsCompiler.ts:241` — `result != null` 체크로 다른 패키지 출력 무시
31
31
 
32
32
  ### compilerOptions 구성 순서 (Slice 1)
33
33
 
34
- - [x] **순서 확인**: `SdTsCompiler.ts:170-225` — env 조정(171) → output 플래그(175-202) → angular 병합(205) → tsBuildInfoFile(210) → transformer(221) 순서 확인
34
+ - **순서 확인**: `SdTsCompiler.ts:170-225` — env 조정(171) → output 플래그(175-202) → angular 병합(205) → tsBuildInfoFile(210) → transformer(221) 순서 확인
35
35
 
36
36
  ### Program 생성 분기 (Slice 3)
37
37
 
38
- - [x] **Non-Angular**: `SdTsCompiler.ts:138-142` — `createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, options, host, oldBuilderProgram)` 4인자 오버로드
39
- - [x] **Angular**: `SdTsCompiler.ts:120-135` — NgtscProgram → getTsProgram → ensureSourceFileVersions → createEmitAndSemanticDiagnosticsBuilderProgram(tsProgram, host, old) → analyzeAsync
40
- - [x] **증분 빌드 상태**: `SdTsCompiler.ts:145` — `this._builderProgram = builderProgram` 저장. 다음 호출 시 `this._builderProgram`이 old로 전달됨
41
- - [x] **Angular 증분**: `SdTsCompiler.ts:123` — `this._ngtscProgram`을 NgtscProgram 4번째 인자로 전달. `SdTsCompiler.ts:136` — `this._ngtscProgram = angularProgram` 저장
38
+ - **Non-Angular**: `SdTsCompiler.ts:138-142` — `createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, options, host, oldBuilderProgram)` 4인자 오버로드
39
+ - **Angular**: `SdTsCompiler.ts:120-135` — NgtscProgram → getTsProgram → ensureSourceFileVersions → createEmitAndSemanticDiagnosticsBuilderProgram(tsProgram, host, old) → analyzeAsync
40
+ - **증분 빌드 상태**: `SdTsCompiler.ts:145` — `this._builderProgram = builderProgram` 저장. 다음 호출 시 `this._builderProgram`이 old로 전달됨
41
+ - **Angular 증분**: `SdTsCompiler.ts:123` — `this._ngtscProgram`을 NgtscProgram 4번째 인자로 전달. `SdTsCompiler.ts:136` — `this._ngtscProgram = angularProgram` 저장
@@ -2,13 +2,13 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] transformResource 콜백 연결: `SdTsCompiler.ts:_extendHostForAngular()`에서 `effectiveTransformStylesheet` 매개변수로 전달받아 `hostAny["transformResource"]`에 등록 확인 (SdTsCompiler.ts:553-579)
6
- - [x] 라이브러리 SCSS 콜백 자동 생성 조건: `isForAngular && effectiveTransformStylesheet == null` 분기 (SdTsCompiler.ts:163-172) — transformStylesheet 제공 시 그대로 사용, 미제공 시 createLibraryTransformStylesheet 생성
7
- - [x] SCSS 상태 리셋 위치: compileAsync 내 program 생성 전에 `_scssErrors.length = 0`, `_scssDependencies.clear()` 호출 (SdTsCompiler.ts:158-160)
8
- - [x] createLibraryTransformStylesheet 이동: ngtsc-build-core.ts에 정의, angular-build-pipeline.ts에서 import 변경 확인 — `compileScssString` import 추가됨
9
- - [x] sideEffectScssRegistry getter: public getter로 외부 접근 가능 (SdTsCompiler.ts:83-86)
10
- - [x] compileSideEffectScss: 내부 _scssErrors와 _scssDependencies에 에러/의존성 기록 (SdTsCompiler.ts:89-98)
11
- - [x] _runLint: LintWithProgramRunner 인스턴스 lazy init + 재사용 (SdTsCompiler.ts:_lintRunner 필드, _runLint 메서드)
12
- - [x] lint + globalScss 병렬: Promise.all로 동시 실행 (SdTsCompiler.ts:271-283)
13
- - [x] ISdTsCompilerResult 타입: lint?, scssErrors, scssDependencies 필드 추가 (sd-ts-compiler-result.ts:17-21)
14
- - [x] ISdTsCompilerOptions 타입: lint?, globalScss? 필드 추가 (sd-ts-compiler-options.ts:41-45)
5
+ - transformResource 콜백 연결: `SdTsCompiler.ts:_extendHostForAngular()`에서 `effectiveTransformStylesheet` 매개변수로 전달받아 `hostAny["transformResource"]`에 등록 확인 (SdTsCompiler.ts:553-579)
6
+ - 라이브러리 SCSS 콜백 자동 생성 조건: `isForAngular && effectiveTransformStylesheet == null` 분기 (SdTsCompiler.ts:163-172) — transformStylesheet 제공 시 그대로 사용, 미제공 시 createLibraryTransformStylesheet 생성
7
+ - SCSS 상태 리셋 위치: compileAsync 내 program 생성 전에 `_scssErrors.length = 0`, `_scssDependencies.clear()` 호출 (SdTsCompiler.ts:158-160)
8
+ - createLibraryTransformStylesheet 이동: ngtsc-build-core.ts에 정의, angular-build-pipeline.ts에서 import 변경 확인 — `compileScssString` import 추가됨
9
+ - sideEffectScssRegistry getter: public getter로 외부 접근 가능 (SdTsCompiler.ts:83-86)
10
+ - compileSideEffectScss: 내부 _scssErrors와 _scssDependencies에 에러/의존성 기록 (SdTsCompiler.ts:89-98)
11
+ - _runLint: LintWithProgramRunner 인스턴스 lazy init + 재사용 (SdTsCompiler.ts:_lintRunner 필드, _runLint 메서드)
12
+ - lint + globalScss 병렬: Promise.all로 동시 실행 (SdTsCompiler.ts:271-283)
13
+ - ISdTsCompilerResult 타입: lint?, scssErrors, scssDependencies 필드 추가 (sd-ts-compiler-result.ts:17-21)
14
+ - ISdTsCompilerOptions 타입: lint?, globalScss? 필드 추가 (sd-ts-compiler-options.ts:41-45)
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] `client.worker.ts:164`에서 `writeConfigJson(outdir, ...)` 사용 확인 — `writeConfigJson(outdir, info.configs)` (기존 `path.join(info.pkgDir, "dist")` 제거됨)
6
- - [x] `client.worker.ts:102`에서 `copyPublicFiles(info.pkgDir, false, outdir)` 전달 확인 — `outdir` 변수는 line 99에서 `info.outDir ?? path.join(info.pkgDir, "dist")`로 계산됨
7
- - [x] `server-build.worker.ts`의 호출이 변경되지 않음 확인 — line 225: `copyPublicFiles(info.pkgDir, false)`, line 374: `watchPublicFiles(info.pkgDir, true)` (2인자 호출 유지)
8
- - [x] `copy-public.ts`의 `watchPublicFiles` 내부 `copyPublicFiles` 호출에 `outDir` 전달 확인 — line 67: `await copyPublicFiles(pkgDir, includeDev, outDir)`
5
+ - `client.worker.ts:164`에서 `writeConfigJson(outdir, ...)` 사용 확인 — `writeConfigJson(outdir, info.configs)` (기존 `path.join(info.pkgDir, "dist")` 제거됨)
6
+ - `client.worker.ts:102`에서 `copyPublicFiles(info.pkgDir, false, outdir)` 전달 확인 — `outdir` 변수는 line 99에서 `info.outDir ?? path.join(info.pkgDir, "dist")`로 계산됨
7
+ - `server-build.worker.ts`의 호출이 변경되지 않음 확인 — line 225: `copyPublicFiles(info.pkgDir, false)`, line 374: `watchPublicFiles(info.pkgDir, true)` (2인자 호출 유지)
8
+ - `copy-public.ts`의 `watchPublicFiles` 내부 `copyPublicFiles` 호출에 `outDir` 전달 확인 — line 67: `await copyPublicFiles(pkgDir, includeDev, outDir)`
@@ -35,8 +35,8 @@ const distDir = toPosix(path.join(pkgDir, "dist"));
35
35
  describe("copySrcFiles", () => {
36
36
  beforeEach(() => {
37
37
  vi.clearAllMocks();
38
- vi.mocked(fsx.mkdir).mockResolvedValue(undefined as any);
39
- vi.mocked(fsx.copy).mockResolvedValue(undefined as any);
38
+ vi.mocked(fsx.mkdir).mockResolvedValue(undefined);
39
+ vi.mocked(fsx.copy).mockResolvedValue(undefined);
40
40
  });
41
41
 
42
42
  it("copies files matching glob patterns preserving relative paths", async () => {
@@ -84,9 +84,9 @@ describe("watchCopySrcFiles", () => {
84
84
  beforeEach(() => {
85
85
  vi.clearAllMocks();
86
86
  vi.mocked(fsx.glob).mockResolvedValue([]);
87
- vi.mocked(fsx.mkdir).mockResolvedValue(undefined as any);
88
- vi.mocked(fsx.copy).mockResolvedValue(undefined as any);
89
- vi.mocked(fsx.rm).mockResolvedValue(undefined as any);
87
+ vi.mocked(fsx.mkdir).mockResolvedValue(undefined);
88
+ vi.mocked(fsx.copy).mockResolvedValue(undefined);
89
+ vi.mocked(fsx.rm).mockResolvedValue(undefined);
90
90
  });
91
91
 
92
92
  it("performs initial copy then starts watch", async () => {
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 검증 항목
4
4
 
5
- - [x] `.dev-port` 파일 기록 패턴 유지: 기존 `client.worker.ts:246,319`에서 `fs.writeFileSync(path.join(distDir, ".dev-port"), String(port))` 패턴을 사용 중이며, 이 패턴은 호출자(worker/engine)에서 동일하게 사용 가능. `createDevHttpServer`의 `listen()` 반환값(port)을 사용하여 기록 가능.
6
- - [x] `.config.json` 기록 패턴 유지: 기존 `client.worker.ts:439-448`의 `writeConfigJson` 함수가 `fs.mkdirSync + fs.writeFileSync(JSON.stringify)` 패턴으로 구현되어 있으며, Feature 3.1(EsbuildClientEngine)에서 동일하게 호출 가능.
7
- - [x] configs 미설정 시 빈 객체: 기존 `writeConfigJson`에서 `configs ?? {}`로 처리 (`client.worker.ts:445`). 이 패턴은 호출자가 그대로 유지.
8
- - [x] 파일 기록 책임 분리: `createDevHttpServer`는 HTTP 서빙에만 집중하고, `.dev-port`/`.config.json` 기록은 호출자 책임 (설계 결정 D3 준수).
5
+ - `.dev-port` 파일 기록 패턴 유지: 기존 `client.worker.ts:246,319`에서 `fs.writeFileSync(path.join(distDir, ".dev-port"), String(port))` 패턴을 사용 중이며, 이 패턴은 호출자(worker/engine)에서 동일하게 사용 가능. `createDevHttpServer`의 `listen()` 반환값(port)을 사용하여 기록 가능.
6
+ - `.config.json` 기록 패턴 유지: 기존 `client.worker.ts:439-448`의 `writeConfigJson` 함수가 `fs.mkdirSync + fs.writeFileSync(JSON.stringify)` 패턴으로 구현되어 있으며, Feature 3.1(EsbuildClientEngine)에서 동일하게 호출 가능.
7
+ - configs 미설정 시 빈 객체: 기존 `writeConfigJson`에서 `configs ?? {}`로 처리 (`client.worker.ts:445`). 이 패턴은 호출자가 그대로 유지.
8
+ - 파일 기록 책임 분리: `createDevHttpServer`는 HTTP 서빙에만 집중하고, `.dev-port`/`.config.json` 기록은 호출자 책임 (설계 결정 D3 준수).
@@ -4,14 +4,14 @@
4
4
 
5
5
  ### Slice 1: BaseEngine 적용
6
6
 
7
- - [x] BaseEngine lint 결과 보고 유지: `BaseEngine.ts:148-157` — build 이벤트에서 `event.lint`가 있으면 lint BuildResult를 ResultCollector에 추가. 기존 로직 그대로 유지됨.
8
- - [x] BaseEngine 경고 로깅 유지: `BaseEngine.ts:144-146` — `event.build.warnings`가 있으면 `logger.warn` 호출. 기존 로직 그대로 유지됨.
9
- - [x] setupWatchEvents가 index.ts의 public export에 포함되지 않음: 내부 유틸리티이므로 패키지 외부 API 불변. `engines/index.ts`의 export 목록에 engine-watch-events 없음 확인.
10
- - [x] resolveInitialBuild가 _callStartWatch catch에서 호출됨: `BaseEngine.ts:162-164` — `this._callStartWatch(output).catch(() => { resolveInitialBuild(); })`. 기존의 `isInitialBuild` 가드와 동등한 동작.
7
+ - BaseEngine lint 결과 보고 유지: `BaseEngine.ts:148-157` — build 이벤트에서 `event.lint`가 있으면 lint BuildResult를 ResultCollector에 추가. 기존 로직 그대로 유지됨.
8
+ - BaseEngine 경고 로깅 유지: `BaseEngine.ts:144-146` — `event.build.warnings`가 있으면 `logger.warn` 호출. 기존 로직 그대로 유지됨.
9
+ - setupWatchEvents가 index.ts의 public export에 포함되지 않음: 내부 유틸리티이므로 패키지 외부 API 불변. `engines/index.ts`의 export 목록에 engine-watch-events 없음 확인.
10
+ - resolveInitialBuild가 _callStartWatch catch에서 호출됨: `BaseEngine.ts:162-164` — `this._callStartWatch(output).catch(() => { resolveInitialBuild(); })`. 기존의 `isInitialBuild` 가드와 동등한 동작.
11
11
 
12
12
  ### Slice 2: EsbuildClientEngine 적용
13
13
 
14
- - [x] EsbuildClientEngine이 waitForInitialBuild() 미사용: `EsbuildClientEngine.ts:103-111` — setupWatchEvents 호출 후 반환값 사용 안 함. 111행에 "waitForInitialBuild 미사용" 주석 확인.
15
- - [x] serverReady 이벤트 처리 유지: `EsbuildClientEngine.ts:97-101` — serverReady 핸들러가 setupWatchEvents 전에 등록됨. `this.port = event.port` 로직 그대로.
16
- - [x] error 로깅 유지: `EsbuildClientEngine.ts:113-117` — setupWatchEvents 이후 별도 error 핸들러에서 `logger.error` 호출. 기존 동작 보존.
17
- - [x] worker.startWatch() await 패턴 유지: `EsbuildClientEngine.ts:119-130` — 기존과 동일하게 `await this._worker!.startWatch(...)` 로 완료 감지.
14
+ - EsbuildClientEngine이 waitForInitialBuild() 미사용: `EsbuildClientEngine.ts:103-111` — setupWatchEvents 호출 후 반환값 사용 안 함. 111행에 "waitForInitialBuild 미사용" 주석 확인.
15
+ - serverReady 이벤트 처리 유지: `EsbuildClientEngine.ts:97-101` — serverReady 핸들러가 setupWatchEvents 전에 등록됨. `this.port = event.port` 로직 그대로.
16
+ - error 로깅 유지: `EsbuildClientEngine.ts:113-117` — setupWatchEvents 이후 별도 error 핸들러에서 `logger.error` 호출. 기존 동작 보존.
17
+ - worker.startWatch() await 패턴 유지: `EsbuildClientEngine.ts:119-130` — 기존과 동일하게 `await this._worker!.startWatch(...)` 로 완료 감지.