@simplysm/sd-cli 14.0.49 → 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 (59) 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 +6 -5
  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/esbuild/esbuild-angular-compiler-plugin.spec.ts +1 -1
  43. package/tests/esbuild/esbuild-postcss-plugin.acc.spec.ts +1 -1
  44. package/tests/esbuild/esbuild-tsc-plugin.acc.spec.ts +2 -2
  45. package/tests/esbuild/esbuild-tsc-plugin.spec.ts +3 -3
  46. package/tests/esbuild/esbuild-worker-plugin.spec.ts +1 -1
  47. package/tests/orchestrators/build-orchestrator.spec.ts +9 -9
  48. package/tests/orchestrators/dev-orchestrator.spec.ts +4 -4
  49. package/tests/orchestrators/watch-orchestrator.spec.ts +2 -2
  50. package/tests/ts-compiler/SdTsCompiler-crash-handling.verify.md +24 -0
  51. package/tests/utils/copy-src.spec.ts +5 -5
  52. package/tests/utils/esbuild-client-config.acc.spec.ts +1 -1
  53. package/tests/utils/esbuild-client-config.spec.ts +4 -4
  54. package/tests/utils/esbuild-config.spec.ts +3 -3
  55. package/tests/utils/esbuild-postcss-plugin.spec.ts +1 -1
  56. package/tests/utils/hmr-client-script.acc.spec.ts +1 -1
  57. package/tests/utils/ngtsc-build-core.spec.ts +1 -1
  58. package/tests/utils/package-utils.spec.ts +6 -6
  59. package/tests/workers/server-build-worker.spec.ts +1 -1
@@ -2,6 +2,7 @@ import path from "path";
2
2
  import { createHash } from "crypto";
3
3
  import ts from "typescript";
4
4
  import { consola, type ConsolaInstance } from "consola";
5
+ import { SdError } from "@simplysm/core-common";
5
6
  import { pathx } from "@simplysm/core-node";
6
7
  import type { ISdTsCompilerOptions, ISdTsCompilerEmitOptions } from "./sd-ts-compiler-options";
7
8
  import type { ISdTsCompilerResult } from "./sd-ts-compiler-result";
@@ -104,6 +105,21 @@ export class SdTsCompiler {
104
105
  return ctx.stage;
105
106
  }
106
107
 
108
+ private _createCrashDiagnostic(e: unknown): SerializedDiagnostic {
109
+ const contextLabel = this._formatCrashContext();
110
+ const sdError =
111
+ e instanceof Error
112
+ ? new SdError(e, `TsCompiler 내부 크래시 @${contextLabel}`)
113
+ : new SdError(`TsCompiler 내부 크래시 @${contextLabel}`, String(e));
114
+ const message = sdError.stack ?? sdError.message;
115
+ this._logger.debug(message);
116
+ return {
117
+ category: ts.DiagnosticCategory.Error,
118
+ code: 0,
119
+ messageText: message,
120
+ };
121
+ }
122
+
107
123
  private _getScssLoadPaths(): string[] {
108
124
  const { pkgDir, cwd } = this._options;
109
125
  return [path.join(pkgDir, "scss"), path.join(cwd, "node_modules")];
@@ -251,29 +267,40 @@ export class SdTsCompiler {
251
267
  program = builderProgram.getProgram();
252
268
  }
253
269
 
254
- // 9. 위험 구간 (체커 진입TsCompiler 내부 크래시 단일 catch)
270
+ // 9. 위험 구간 (단계별 catch부분 복구)
271
+ const crashDiagnostics: SerializedDiagnostic[] = [];
272
+
255
273
  try {
274
+ // 9-0. analyzeAsync (Angular only)
256
275
  if (isForAngular) {
257
- this._setCrashContext("analyzeAsync");
258
- this._logger.debug(`[${pkgName}] AOT analyzeAsync 시작`);
259
- await angularProgram!.compiler.analyzeAsync();
260
- this._logger.debug(`[${pkgName}] AOT analyzeAsync 완료`);
261
- this._ngtscProgram = angularProgram;
276
+ try {
277
+ this._setCrashContext("analyzeAsync");
278
+ this._logger.debug(`[${pkgName}] AOT analyzeAsync 시작`);
279
+ await angularProgram!.compiler.analyzeAsync();
280
+ this._logger.debug(`[${pkgName}] AOT analyzeAsync 완료`);
281
+ this._ngtscProgram = angularProgram;
282
+ } catch (e) {
283
+ crashDiagnostics.push(this._createCrashDiagnostic(e));
284
+ }
262
285
  }
263
286
 
264
287
  // 9-1. affected files 추적
265
- this._setCrashContext("findAffectedFiles");
266
288
  let affectedFiles: ReadonlySet<string> | undefined;
267
- if (isForAngular) {
268
- const result = this._findAffectedFilesForAngular(
269
- builderProgram,
270
- this._ngtscProgram!.compiler,
271
- this._sourceFileCache,
272
- );
273
- this._affectedSourceFiles = result.affectedSourceFiles;
274
- affectedFiles = result.affectedPaths;
275
- } else {
276
- affectedFiles = this._findAffectedFilesForTsc(builderProgram);
289
+ try {
290
+ this._setCrashContext("findAffectedFiles");
291
+ if (isForAngular && this._ngtscProgram != null) {
292
+ const result = this._findAffectedFilesForAngular(
293
+ builderProgram,
294
+ this._ngtscProgram.compiler,
295
+ this._sourceFileCache,
296
+ );
297
+ this._affectedSourceFiles = result.affectedSourceFiles;
298
+ affectedFiles = result.affectedPaths;
299
+ } else if (!isForAngular) {
300
+ affectedFiles = this._findAffectedFilesForTsc(builderProgram);
301
+ }
302
+ } catch (e) {
303
+ crashDiagnostics.push(this._createCrashDiagnostic(e));
277
304
  }
278
305
 
279
306
  if (affectedFiles != null) {
@@ -289,51 +316,83 @@ export class SdTsCompiler {
289
316
  }
290
317
 
291
318
  // 9-2. emit 처리
292
- this._setCrashContext("emit");
293
319
  let emitResults: EmitResult[] | undefined;
294
- if (isForAngular) {
295
- emitResults = this._emitAngular(
296
- this._ngtscProgram!,
297
- builderProgram,
298
- this._affectedSourceFiles,
299
- emitOptions,
300
- );
301
- } else {
302
- this._emitTsc(builderProgram);
320
+ try {
321
+ this._setCrashContext("emit");
322
+ if (isForAngular && this._ngtscProgram != null) {
323
+ emitResults = this._emitAngular(
324
+ this._ngtscProgram,
325
+ builderProgram,
326
+ this._affectedSourceFiles,
327
+ emitOptions,
328
+ );
329
+ } else if (!isForAngular) {
330
+ this._emitTsc(builderProgram);
331
+ }
332
+ } catch (e) {
333
+ crashDiagnostics.push(this._createCrashDiagnostic(e));
303
334
  }
304
335
 
305
336
  // 9-3. 진단 수집
306
- this._setCrashContext("collectDiagnostics");
307
- const rawDiagnostics = isForAngular
308
- ? this._collectDiagnosticsForAngular(
309
- this._ngtscProgram!,
310
- builderProgram,
311
- this._affectedSourceFiles,
312
- )
313
- : this._collectDiagnosticsForTsc(builderProgram);
314
- const diagResult = this._finalizeDiagnostics(rawDiagnostics);
337
+ let diagResult:
338
+ | {
339
+ diagnostics: SerializedDiagnostic[];
340
+ errorCount: number;
341
+ warningCount: number;
342
+ errors?: string[];
343
+ }
344
+ | undefined;
345
+ try {
346
+ this._setCrashContext("collectDiagnostics");
347
+ const rawDiagnostics =
348
+ isForAngular && this._ngtscProgram != null
349
+ ? this._collectDiagnosticsForAngular(
350
+ this._ngtscProgram,
351
+ builderProgram,
352
+ this._affectedSourceFiles,
353
+ )
354
+ : this._collectDiagnosticsForTsc(builderProgram);
355
+ diagResult = this._finalizeDiagnostics(rawDiagnostics);
356
+ } catch (e) {
357
+ crashDiagnostics.push(this._createCrashDiagnostic(e));
358
+ }
315
359
 
316
360
  // 9-4. 글로벌 SCSS + lint 병렬 실행
317
- this._setCrashContext("lintAndGlobalScss");
318
- const [, lintResult] = await Promise.all([
319
- // globalScss
320
- this._options.globalScss === true
321
- ? Promise.resolve().then(() => {
322
- const loadPaths = this._getScssLoadPaths();
323
- const globalScssErrors = compileGlobalScss(pkgDir, loadPaths);
324
- this._scssErrors.push(...globalScssErrors);
325
- })
326
- : Promise.resolve(),
327
- // lint
328
- this._options.lint === true
329
- ? this._runLint(program, affectedFiles)
330
- : Promise.resolve(undefined),
331
- ]);
361
+ let lintResult: LintWithProgramResult | undefined;
362
+ try {
363
+ this._setCrashContext("lintAndGlobalScss");
364
+ const [, lr] = await Promise.all([
365
+ // globalScss
366
+ this._options.globalScss === true
367
+ ? Promise.resolve().then(() => {
368
+ const loadPaths = this._getScssLoadPaths();
369
+ const globalScssErrors = compileGlobalScss(pkgDir, loadPaths);
370
+ this._scssErrors.push(...globalScssErrors);
371
+ })
372
+ : Promise.resolve(),
373
+ // lint
374
+ this._options.lint === true
375
+ ? this._runLint(program, affectedFiles)
376
+ : Promise.resolve(undefined),
377
+ ]);
378
+ lintResult = lr;
379
+ } catch (e) {
380
+ crashDiagnostics.push(this._createCrashDiagnostic(e));
381
+ }
332
382
 
333
383
  // 9-5. 상태 저장
334
384
  this._builderProgram = builderProgram;
335
385
  this._crashContext = undefined;
336
386
 
387
+ // 결과 조립 (crashDiagnostics 합산)
388
+ const finalDiagnostics = [...(diagResult?.diagnostics ?? []), ...crashDiagnostics];
389
+ const finalErrorCount = (diagResult?.errorCount ?? 0) + crashDiagnostics.length;
390
+ const finalWarningCount = diagResult?.warningCount ?? 0;
391
+ const crashErrors = crashDiagnostics.map((d) =>
392
+ typeof d.messageText === "string" ? d.messageText : "TsCompiler 내부 크래시",
393
+ );
394
+ const finalErrors = [...(diagResult?.errors ?? []), ...crashErrors];
395
+
337
396
  this._logger.debug(`[${pkgName}] compileAsync 완료`);
338
397
 
339
398
  return {
@@ -341,10 +400,10 @@ export class SdTsCompiler {
341
400
  builderProgram,
342
401
  isForAngular,
343
402
  affectedFiles,
344
- diagnostics: diagResult.diagnostics,
345
- errorCount: diagResult.errorCount,
346
- warningCount: diagResult.warningCount,
347
- errors: diagResult.errors,
403
+ diagnostics: finalDiagnostics,
404
+ errorCount: finalErrorCount,
405
+ warningCount: finalWarningCount,
406
+ errors: finalErrors.length > 0 ? finalErrors : undefined,
348
407
  ngtscProgram: this._ngtscProgram,
349
408
  emitResults,
350
409
  lint: lintResult,
@@ -352,35 +411,8 @@ export class SdTsCompiler {
352
411
  scssDependencies: new Map(this._scssDependencies),
353
412
  };
354
413
  } catch (e) {
355
- // TsCompiler 내부 크래시 (TS 5.9 overload 버그 등) — 단일 에러 진단으로 degrade
356
- const contextLabel = this._formatCrashContext();
357
- const rawMsg = e instanceof Error ? (e.stack ?? e.message) : String(e);
358
- this._logger.debug(`[${pkgName}] crash @${contextLabel}: ${rawMsg}`);
359
-
360
- // per-file 프로브: affected 파일 각각을 개별 try-catch로 재체크하여 재현 파일 특정
361
- const probeReport = isForAngular
362
- ? this._probeCrashPerFileAngular(
363
- this._ngtscProgram,
364
- builderProgram,
365
- this._affectedSourceFiles,
366
- )
367
- : this._probeCrashPerFileTsc(builderProgram, this._affectedSourceFiles);
368
-
369
- const parts: string[] = [
370
- `TsCompiler 내부 크래시 @${contextLabel}`,
371
- "",
372
- rawMsg,
373
- ];
374
- if (probeReport.length > 0) {
375
- parts.push("", "크래시 재현 파일 (per-file 프로브):", ...probeReport);
376
- }
377
- const message = parts.join("\n");
378
-
379
- const crashDiag: SerializedDiagnostic = {
380
- category: ts.DiagnosticCategory.Error,
381
- code: 0,
382
- messageText: message,
383
- };
414
+ // 최종 안전망 단계별 catch를 우회한 예상치 못한 에러
415
+ const crashDiag = this._createCrashDiagnostic(e);
384
416
  return {
385
417
  program,
386
418
  builderProgram,
@@ -389,7 +421,11 @@ export class SdTsCompiler {
389
421
  diagnostics: [crashDiag],
390
422
  errorCount: 1,
391
423
  warningCount: 0,
392
- errors: [message],
424
+ errors: [
425
+ typeof crashDiag.messageText === "string"
426
+ ? crashDiag.messageText
427
+ : "TsCompiler 내부 크래시",
428
+ ],
393
429
  ngtscProgram: this._ngtscProgram,
394
430
  emitResults: undefined,
395
431
  lint: undefined,
@@ -399,72 +435,6 @@ export class SdTsCompiler {
399
435
  }
400
436
  }
401
437
 
402
- /**
403
- * 크래시 발생 후, affected sourceFile을 개별 try-catch로 재검사하여 원인 파일을 특정한다.
404
- * Angular: `getDiagnosticsForFile(sf, SingleFile)` + `builderProgram.getSemanticDiagnostics(sf)` 개별 호출.
405
- * 각 파일별로 크래시 재현 여부를 기록. 프로브 자체 크래시는 흡수한다.
406
- */
407
- private _probeCrashPerFileAngular(
408
- ngtscProgram: NgtscProgram | undefined,
409
- builderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram,
410
- affectedSourceFiles: ReadonlySet<ts.SourceFile>,
411
- ): string[] {
412
- const report: string[] = [];
413
- const angularCompiler = ngtscProgram?.compiler;
414
- const { cwd } = this._options;
415
-
416
- for (const sf of affectedSourceFiles) {
417
- if (angularCompiler?.ignoreForDiagnostics.has(sf) === true) continue;
418
-
419
- const rel = path.relative(cwd, sf.fileName);
420
-
421
- try {
422
- builderProgram.getSemanticDiagnostics(sf);
423
- } catch (e) {
424
- report.push(
425
- ` - ${rel} [getSemanticDiagnostics]: ${e instanceof Error ? e.message : String(e)}`,
426
- );
427
- continue;
428
- }
429
-
430
- if (angularCompiler != null && !sf.isDeclarationFile) {
431
- try {
432
- angularCompiler.getDiagnosticsForFile(sf, OptimizeFor.SingleFile);
433
- } catch (e) {
434
- report.push(
435
- ` - ${rel} [getDiagnosticsForFile]: ${e instanceof Error ? e.message : String(e)}`,
436
- );
437
- }
438
- }
439
- }
440
- return report;
441
- }
442
-
443
- private _probeCrashPerFileTsc(
444
- builderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram,
445
- affectedSourceFiles: ReadonlySet<ts.SourceFile>,
446
- ): string[] {
447
- const report: string[] = [];
448
- const { cwd } = this._options;
449
-
450
- const targets =
451
- affectedSourceFiles.size > 0
452
- ? affectedSourceFiles
453
- : new Set(builderProgram.getSourceFiles());
454
-
455
- for (const sf of targets) {
456
- try {
457
- builderProgram.getSemanticDiagnostics(sf);
458
- } catch (e) {
459
- const rel = path.relative(cwd, sf.fileName);
460
- report.push(
461
- ` - ${rel} [getSemanticDiagnostics]: ${e instanceof Error ? e.message : String(e)}`,
462
- );
463
- }
464
- }
465
- return report;
466
- }
467
-
468
438
  private async _runLint(
469
439
  program: ts.Program,
470
440
  affectedFiles?: ReadonlySet<string>,
@@ -615,11 +585,16 @@ export class SdTsCompiler {
615
585
  ): ts.Diagnostic[] {
616
586
  const diagnostics: ts.Diagnostic[] = [
617
587
  ...builderProgram.getConfigFileParsingDiagnostics(),
618
- ...builderProgram.getSyntacticDiagnostics(),
619
588
  ...builderProgram.getOptionsDiagnostics(),
620
589
  ...builderProgram.getGlobalDiagnostics(),
621
- ...builderProgram.getSemanticDiagnostics(),
622
590
  ];
591
+
592
+ for (const sourceFile of builderProgram.getSourceFiles()) {
593
+ this._setCrashContext("collectDiagnostics", sourceFile.fileName);
594
+ diagnostics.push(...builderProgram.getSyntacticDiagnostics(sourceFile));
595
+ diagnostics.push(...builderProgram.getSemanticDiagnostics(sourceFile));
596
+ }
597
+
623
598
  if (!this._options.output.dts) {
624
599
  diagnostics.push(...builderProgram.getProgram().getDeclarationDiagnostics());
625
600
  }
@@ -802,7 +777,13 @@ export class SdTsCompiler {
802
777
  ): ReadonlySet<string> | undefined {
803
778
  const affectedFiles = new Set<string>();
804
779
  while (true) {
805
- const result = builderProgram.getSemanticDiagnosticsOfNextAffectedFile();
780
+ const result = builderProgram.getSemanticDiagnosticsOfNextAffectedFile(
781
+ undefined,
782
+ (sourceFile) => {
783
+ this._setCrashContext("findAffectedFiles", sourceFile.fileName);
784
+ return false;
785
+ },
786
+ );
806
787
  if (result == null) break;
807
788
  if ("fileName" in result.affected) {
808
789
  affectedFiles.add(pathx.posix(result.affected.fileName));
@@ -829,6 +810,7 @@ export class SdTsCompiler {
829
810
  const result = builderProgram.getSemanticDiagnosticsOfNextAffectedFile(
830
811
  undefined,
831
812
  (sourceFile) => {
813
+ this._setCrashContext("findAffectedFiles", sourceFile.fileName);
832
814
  if (
833
815
  angularCompiler.ignoreForDiagnostics.has(sourceFile) &&
834
816
  sourceFile.fileName.endsWith(".ngtypecheck.ts")
@@ -4,7 +4,6 @@ import { SdError } from "@simplysm/core-common";
4
4
  import { pathx } from "@simplysm/core-node";
5
5
  import { createLazyLogger } from "../runtime/lazy-logger";
6
6
  import type {
7
- SdBuildPackageConfig,
8
7
  SdPackageConfig,
9
8
  } from "../sd-config.types";
10
9
 
@@ -90,7 +89,7 @@ export function mergeTestsPackagesIntoConfig(
90
89
  );
91
90
  }
92
91
 
93
- merged[name] = { target: "node" } as SdBuildPackageConfig;
92
+ merged[name] = { target: "node" };
94
93
  pathMap.set(name, relPath);
95
94
  }
96
95
 
@@ -48,7 +48,7 @@ function setupPlugin(plugin: esbuild.Plugin) {
48
48
  outputFiles: [],
49
49
  metafile: { inputs: {}, outputs: {} },
50
50
  ...result,
51
- } as esbuild.BuildResult);
51
+ });
52
52
  },
53
53
  invokeOnDispose() { onDisposeCb?.(); },
54
54
  get onStartCb() { return onStartCb; },
@@ -32,7 +32,7 @@ function setupPlugin(plugin: esbuild.Plugin) {
32
32
  outputFiles: [],
33
33
  metafile: { inputs: {}, outputs: {} },
34
34
  ...result,
35
- } as esbuild.BuildResult)) ?? null
35
+ })) ?? null
36
36
  );
37
37
  },
38
38
  };
@@ -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);
@@ -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,
@@ -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: [] });
@@ -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
@@ -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으로 원본 에러 메시지+스택이 보존되는지 확인
@@ -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 () => {
@@ -423,7 +423,7 @@ describe("createClientEsbuildContext — Acceptance", () => {
423
423
  pkgDir: "/workspace/packages/my-app",
424
424
  cwd: "/workspace",
425
425
  mode: "dev",
426
- plugins: [customPlugin as any],
426
+ plugins: [customPlugin],
427
427
  onEnd: vi.fn(),
428
428
  });
429
429