@simplysm/sd-cli 14.0.47 → 14.0.49

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 (200) hide show
  1. package/README.md +782 -0
  2. package/dist/angular/ngtsc-build-core.js +2 -2
  3. package/dist/angular/ngtsc-build-core.js.map +1 -1
  4. package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
  5. package/dist/angular/vite-angular-plugin.js +3 -2
  6. package/dist/angular/vite-angular-plugin.js.map +1 -1
  7. package/dist/capacitor/capacitor-android.js +2 -2
  8. package/dist/capacitor/capacitor-android.js.map +1 -1
  9. package/dist/capacitor/capacitor-build.d.ts.map +1 -1
  10. package/dist/capacitor/capacitor-build.js +2 -1
  11. package/dist/capacitor/capacitor-build.js.map +1 -1
  12. package/dist/capacitor/capacitor-icon.d.ts.map +1 -1
  13. package/dist/capacitor/capacitor-icon.js +2 -1
  14. package/dist/capacitor/capacitor-icon.js.map +1 -1
  15. package/dist/capacitor/capacitor-npm-config.d.ts.map +1 -1
  16. package/dist/capacitor/capacitor-npm-config.js +2 -1
  17. package/dist/capacitor/capacitor-npm-config.js.map +1 -1
  18. package/dist/capacitor/capacitor.d.ts.map +1 -1
  19. package/dist/capacitor/capacitor.js +2 -1
  20. package/dist/capacitor/capacitor.js.map +1 -1
  21. package/dist/commands/device.js +2 -2
  22. package/dist/commands/device.js.map +1 -1
  23. package/dist/commands/replace-deps.js +2 -2
  24. package/dist/commands/replace-deps.js.map +1 -1
  25. package/dist/deps/replace-deps/collect-deps.js +2 -2
  26. package/dist/deps/replace-deps/collect-deps.js.map +1 -1
  27. package/dist/deps/replace-deps/replace-deps.d.ts.map +1 -1
  28. package/dist/deps/replace-deps/replace-deps.js +108 -81
  29. package/dist/deps/replace-deps/replace-deps.js.map +1 -1
  30. package/dist/deps/server-externals/server-production-files.js +2 -2
  31. package/dist/deps/server-externals/server-production-files.js.map +1 -1
  32. package/dist/electron/electron.d.ts.map +1 -1
  33. package/dist/electron/electron.js +2 -1
  34. package/dist/electron/electron.js.map +1 -1
  35. package/dist/engines/BaseEngine.d.ts.map +1 -1
  36. package/dist/engines/BaseEngine.js +2 -2
  37. package/dist/engines/BaseEngine.js.map +1 -1
  38. package/dist/engines/EsbuildClientEngine.d.ts.map +1 -1
  39. package/dist/engines/EsbuildClientEngine.js +2 -2
  40. package/dist/engines/EsbuildClientEngine.js.map +1 -1
  41. package/dist/engines/NgtscEngine.js +2 -2
  42. package/dist/engines/NgtscEngine.js.map +1 -1
  43. package/dist/engines/ServerEsbuildEngine.js +2 -2
  44. package/dist/engines/ServerEsbuildEngine.js.map +1 -1
  45. package/dist/engines/TscEngine.js +2 -2
  46. package/dist/engines/TscEngine.js.map +1 -1
  47. package/dist/engines/engine-factory.d.ts.map +1 -1
  48. package/dist/engines/engine-factory.js +2 -2
  49. package/dist/engines/engine-factory.js.map +1 -1
  50. package/dist/esbuild/esbuild-angular-compiler-plugin.d.ts.map +1 -1
  51. package/dist/esbuild/esbuild-angular-compiler-plugin.js +46 -18
  52. package/dist/esbuild/esbuild-angular-compiler-plugin.js.map +1 -1
  53. package/dist/esbuild/esbuild-config.js +2 -2
  54. package/dist/esbuild/esbuild-config.js.map +1 -1
  55. package/dist/lint/lint-with-program.js +2 -2
  56. package/dist/lint/lint-with-program.js.map +1 -1
  57. package/dist/runtime/lazy-logger.d.ts +14 -0
  58. package/dist/runtime/lazy-logger.d.ts.map +1 -0
  59. package/dist/runtime/lazy-logger.js +23 -0
  60. package/dist/runtime/lazy-logger.js.map +1 -0
  61. package/dist/sd-cli-entry.js +2 -2
  62. package/dist/sd-cli-entry.js.map +1 -1
  63. package/dist/sd-cli.js +2 -2
  64. package/dist/sd-cli.js.map +1 -1
  65. package/dist/ts-compiler/SdTsCompiler.d.ts +11 -0
  66. package/dist/ts-compiler/SdTsCompiler.d.ts.map +1 -1
  67. package/dist/ts-compiler/SdTsCompiler.js +223 -116
  68. package/dist/ts-compiler/SdTsCompiler.js.map +1 -1
  69. package/dist/typecheck/typecheck-non-package.js +2 -2
  70. package/dist/typecheck/typecheck-non-package.js.map +1 -1
  71. package/dist/typecheck/typecheck-serialization.d.ts +31 -9
  72. package/dist/typecheck/typecheck-serialization.d.ts.map +1 -1
  73. package/dist/typecheck/typecheck-serialization.js +62 -22
  74. package/dist/typecheck/typecheck-serialization.js.map +1 -1
  75. package/dist/utils/output-utils.js +2 -2
  76. package/dist/utils/output-utils.js.map +1 -1
  77. package/dist/utils/package-classify.js +2 -2
  78. package/dist/utils/package-classify.js.map +1 -1
  79. package/dist/utils/package-utils.js +2 -2
  80. package/dist/utils/package-utils.js.map +1 -1
  81. package/dist/utils/sd-config.js +2 -2
  82. package/dist/utils/sd-config.js.map +1 -1
  83. package/dist/utils/tsconfig.d.ts.map +1 -1
  84. package/dist/utils/tsconfig.js +3 -5
  85. package/dist/utils/tsconfig.js.map +1 -1
  86. package/package.json +5 -5
  87. package/src/angular/ngtsc-build-core.ts +3 -3
  88. package/src/angular/vite-angular-plugin.ts +3 -2
  89. package/src/capacitor/capacitor-android.ts +2 -2
  90. package/src/capacitor/capacitor-build.ts +2 -1
  91. package/src/capacitor/capacitor-icon.ts +2 -1
  92. package/src/capacitor/capacitor-npm-config.ts +2 -1
  93. package/src/capacitor/capacitor.ts +2 -1
  94. package/src/commands/device.ts +2 -2
  95. package/src/commands/replace-deps.ts +2 -2
  96. package/src/deps/replace-deps/collect-deps.ts +2 -2
  97. package/src/deps/replace-deps/replace-deps.ts +119 -85
  98. package/src/deps/server-externals/server-production-files.ts +2 -2
  99. package/src/electron/electron.ts +2 -1
  100. package/src/engines/BaseEngine.ts +2 -2
  101. package/src/engines/EsbuildClientEngine.ts +2 -2
  102. package/src/engines/NgtscEngine.ts +2 -2
  103. package/src/engines/ServerEsbuildEngine.ts +2 -2
  104. package/src/engines/TscEngine.ts +2 -2
  105. package/src/engines/engine-factory.ts +2 -2
  106. package/src/esbuild/esbuild-angular-compiler-plugin.ts +60 -19
  107. package/src/esbuild/esbuild-config.ts +2 -2
  108. package/src/lint/lint-with-program.ts +2 -2
  109. package/src/runtime/lazy-logger.ts +23 -0
  110. package/src/sd-cli-entry.ts +2 -2
  111. package/src/sd-cli.ts +2 -2
  112. package/src/ts-compiler/SdTsCompiler.ts +280 -138
  113. package/src/typecheck/typecheck-non-package.ts +2 -2
  114. package/src/typecheck/typecheck-serialization.ts +100 -26
  115. package/src/utils/output-utils.ts +2 -2
  116. package/src/utils/package-classify.ts +2 -2
  117. package/src/utils/package-utils.ts +2 -2
  118. package/src/utils/sd-config.ts +2 -2
  119. package/src/utils/tsconfig.ts +3 -4
  120. package/tests/angular/angular-compiler-hmr-removal.verify.md +12 -12
  121. package/tests/angular/onbuild-lint-removal.verify.md +4 -4
  122. package/tests/angular/vite-angular-plugin-sdtscompiler.verify.md +9 -9
  123. package/tests/angular/vite-angular-plugin-vitest.verify.md +16 -16
  124. package/tests/capacitor/capacitor-android-exports.verify.md +7 -7
  125. package/tests/commands/publish-npm-local-split.verify.md +5 -5
  126. package/tests/commands/publish-responsibility-split.verify.md +9 -9
  127. package/tests/commands/publish-set.verify.md +3 -3
  128. package/tests/commands/publish-storage-split.verify.md +4 -4
  129. package/tests/commands/slice3-severity-cleanup.verify.md +8 -8
  130. package/tests/deps/deps-directory-separation.verify.md +11 -11
  131. package/tests/deps/replace-deps/replace-deps-perf.verify.md +6 -6
  132. package/tests/deps/server-externals/mise-toml-parse-intent.verify.md +8 -8
  133. package/tests/electron/electron-symlink-cleanup.verify.md +4 -4
  134. package/tests/engines/engine-duplicate-output-removal.verify.md +6 -6
  135. package/tests/engines/engine-typecheck-selection.verify.md +4 -4
  136. package/tests/engines/esbuild-client-engine.verify.md +11 -11
  137. package/tests/engines/normalize-result.verify.md +5 -5
  138. package/tests/engines/vite-dependency-cleanup.verify.md +11 -11
  139. package/tests/esbuild/esbuild-angular-compiler-plugin-hmr.verify.md +10 -10
  140. package/tests/esbuild/esbuild-angular-compiler-plugin-onload.verify.md +17 -17
  141. package/tests/esbuild/esbuild-angular-compiler-plugin-onstart-extraction.verify.md +12 -12
  142. package/tests/esbuild/esbuild-angular-compiler-plugin-sdtscompiler.verify.md +11 -11
  143. package/tests/esbuild/esbuild-angular-compiler-plugin-stylesheet.verify.md +13 -13
  144. package/tests/esbuild/esbuild-angular-compiler-plugin-worker.verify.md +32 -32
  145. package/tests/esbuild/esbuild-angular-compiler-plugin.verify.md +9 -9
  146. package/tests/esbuild/esbuild-postcss-plugin-chunking.verify.md +3 -3
  147. package/tests/esbuild/esbuild-tsc-plugin-imports.verify.md +9 -9
  148. package/tests/esbuild/esbuild-worker-plugin-node.verify.md +7 -7
  149. package/tests/esbuild/esbuild-worker-plugin.spec.ts +8 -0
  150. package/tests/esbuild/esbuild-worker-plugin.verify.md +3 -3
  151. package/tests/orchestrators/dist-delete-watcher.verify.md +6 -6
  152. package/tests/orchestrators/orchestrator-baseenv.verify.md +6 -6
  153. package/tests/orchestrators/orchestrator-diagnostic-formatting.verify.md +6 -6
  154. package/tests/orchestrators/orchestrator-initializemode-signature.verify.md +5 -5
  155. package/tests/orchestrators/slice1-stdout-to-consola.verify.md +6 -6
  156. package/tests/sd-cli-catch-all.verify.md +3 -3
  157. package/tests/sd-cli-log-tag.verify.md +7 -7
  158. package/tests/ts-compiler/SdTsCompiler-affected-files.verify.md +4 -4
  159. package/tests/ts-compiler/SdTsCompiler-diagnostics.verify.md +8 -8
  160. package/tests/ts-compiler/SdTsCompiler-emit.verify.md +5 -5
  161. package/tests/ts-compiler/SdTsCompiler.verify.md +20 -20
  162. package/tests/ts-compiler/scss-lint-integration.verify.md +10 -10
  163. package/tests/utils/copy-public-outdir.verify.md +4 -4
  164. package/tests/utils/dev-http-server.verify.md +4 -4
  165. package/tests/utils/engine-watch-events.verify.md +8 -8
  166. package/tests/utils/esbuild-client-config-integration.verify.md +5 -5
  167. package/tests/utils/esbuild-client-config-postcss.verify.md +2 -2
  168. package/tests/utils/esbuild-client-config.verify.md +16 -16
  169. package/tests/utils/esbuild-index-html.verify.md +6 -6
  170. package/tests/utils/esbuild-pwa.verify.md +5 -5
  171. package/tests/utils/esbuild-scss-plugin.verify.md +4 -4
  172. package/tests/utils/hmr-service.verify.md +10 -10
  173. package/tests/utils/lint-core-import-paths.verify.md +6 -6
  174. package/tests/utils/replace-deps-split.verify.md +11 -11
  175. package/tests/utils/replace-deps-watch.acc.spec.ts +85 -0
  176. package/tests/utils/replace-deps-watch.spec.ts +198 -1
  177. package/tests/utils/replace-deps-watch.verify.md +5 -5
  178. package/tests/utils/server-production-files-import-paths.verify.md +10 -10
  179. package/tests/utils/vite-config-cleanup.verify.md +3 -3
  180. package/tests/workers/build-watch-paths-library.verify.md +6 -6
  181. package/tests/workers/build-watch-paths-ngtsc-server.verify.md +8 -8
  182. package/tests/workers/client-worker-browser-support.verify.md +3 -3
  183. package/tests/workers/client-worker-cleanup.verify.md +4 -4
  184. package/tests/workers/client-worker-initial-build-error.verify.md +3 -3
  185. package/tests/workers/client-worker-initial-build-warnings.verify.md +3 -3
  186. package/tests/workers/client-worker-mtime-incremental.verify.md +6 -6
  187. package/tests/workers/client-worker-onend-sync.verify.md +3 -3
  188. package/tests/workers/client-worker-refactor.verify.md +18 -18
  189. package/tests/workers/client-worker-ts-cache-invalidation.verify.md +8 -8
  190. package/tests/workers/dev-port-file.verify.md +3 -3
  191. package/tests/workers/ngtsc-build-rootnames-refresh.verify.md +4 -4
  192. package/tests/workers/server-build-context-dispose.verify.md +4 -4
  193. package/tests/workers/server-build-worker-plugin.verify.md +5 -5
  194. package/tests/workers/server-build-worker-refactoring.verify.md +10 -10
  195. package/tests/workers/server-esbuild-context-integration.verify.md +6 -6
  196. package/tests/workers/server-esbuild-context-tsc.verify.md +3 -3
  197. package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.d.ts +0 -3
  198. package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.d.ts.map +0 -1
  199. package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.js +0 -9
  200. package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import path from "path";
2
2
  import { createHash } from "crypto";
3
3
  import ts from "typescript";
4
- import { consola } from "consola";
4
+ import { consola, type ConsolaInstance } from "consola";
5
5
  import { pathx } from "@simplysm/core-node";
6
6
  import type { ISdTsCompilerOptions, ISdTsCompilerEmitOptions } from "./sd-ts-compiler-options";
7
7
  import type { ISdTsCompilerResult } from "./sd-ts-compiler-result";
@@ -28,8 +28,6 @@ import {
28
28
  } from "../angular/ngtsc-build-core";
29
29
  import { LintWithProgramRunner, type LintWithProgramResult } from "../lint/lint-with-program";
30
30
 
31
- const logger = consola.withTag("sd:cli:SdTsCompiler");
32
-
33
31
  type NgCompiler = NgtscProgram["compiler"];
34
32
 
35
33
  function hasTemplateExtension(file: string): boolean {
@@ -80,10 +78,32 @@ export class SdTsCompiler {
80
78
  // ── Lint (lazy init, 인스턴스 재사용) ──
81
79
  private _lintRunner?: LintWithProgramRunner;
82
80
 
81
+ // ── 크래시 디버깅용 컨텍스트 (compileAsync 내 단계/파일 진입 시 갱신) ──
82
+ private _crashContext?: { stage: string; file?: string };
83
+
84
+ // ── Logger ──
85
+ // NOTE: 모듈 레벨이 아닌 인스턴스 레벨에서 생성해야 한다.
86
+ // consola.withTag는 호출 시점의 consola.level/reporters를 스냅샷 복사하므로,
87
+ // setupConsola 이전(모듈 import 시점)에 만들면 debug 레벨이 반영되지 않는다.
88
+ private readonly _logger: ConsolaInstance = consola.withTag("sd:cli:SdTsCompiler");
89
+
83
90
  constructor(options: ISdTsCompilerOptions) {
84
91
  this._options = options;
85
92
  }
86
93
 
94
+ private _setCrashContext(stage: string, file?: string): void {
95
+ this._crashContext = { stage, file };
96
+ }
97
+
98
+ private _formatCrashContext(): string {
99
+ const ctx = this._crashContext;
100
+ if (ctx == null) return "unknown";
101
+ if (ctx.file != null) {
102
+ return `${ctx.stage} [${path.relative(this._options.cwd, ctx.file)}]`;
103
+ }
104
+ return ctx.stage;
105
+ }
106
+
87
107
  private _getScssLoadPaths(): string[] {
88
108
  const { pkgDir, cwd } = this._options;
89
109
  return [path.join(pkgDir, "scss"), path.join(cwd, "node_modules")];
@@ -121,11 +141,20 @@ export class SdTsCompiler {
121
141
  modifiedFiles?: ReadonlySet<string>,
122
142
  emitOptions?: ISdTsCompilerEmitOptions,
123
143
  ): Promise<ISdTsCompilerResult> {
124
- const { pkgDir } = this._options;
144
+ const { pkgDir, cwd } = this._options;
125
145
  const pkgName = path.basename(pkgDir);
146
+ this._crashContext = undefined;
126
147
 
127
148
  // 1. 증분: sourceFileCache 무효화 + packageJsonCache 클리어
128
149
  if (modifiedFiles != null && modifiedFiles.size > 0) {
150
+ const sampleFiles = [...modifiedFiles]
151
+ .slice(0, 10)
152
+ .map((f) => path.relative(cwd, f))
153
+ .join(", ");
154
+ this._logger.debug(
155
+ `[${pkgName}] modifiedFiles (${modifiedFiles.size}개)${modifiedFiles.size > 10 ? " [상위 10개]" : ""}: ${sampleFiles}`,
156
+ );
157
+
129
158
  if (this._sourceFileCache != null) {
130
159
  this._sourceFileCache.invalidate(modifiedFiles);
131
160
  }
@@ -149,12 +178,12 @@ export class SdTsCompiler {
149
178
  ? (parsed.raw.angularCompilerOptions as Record<string, unknown>)
150
179
  : undefined;
151
180
 
152
- logger.debug(`[${pkgName}] isForAngular: ${isForAngular}`);
181
+ this._logger.debug(`[${pkgName}] isForAngular: ${isForAngular}`);
153
182
 
154
183
  // 3. rootNames 필터링
155
184
  const rootNames = this._filterRootNames(parsed);
156
185
 
157
- logger.debug(`[${pkgName}] rootNames: ${rootNames.length}개`);
186
+ this._logger.debug(`[${pkgName}] rootNames: ${rootNames.length}개`);
158
187
 
159
188
  // 4. compilerOptions 구성
160
189
  const compilerOptions = this._buildCompilerOptions(
@@ -181,9 +210,10 @@ export class SdTsCompiler {
181
210
  // 7. compiler host 생성
182
211
  const host = this._createHost(compilerOptions, isForAngular, effectiveTransformStylesheet);
183
212
 
184
- // 8. program 생성
213
+ // 8. program 생성 (체커 미진입 구간 — try 밖)
185
214
  let program: ts.Program;
186
215
  let builderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram;
216
+ let angularProgram: NgtscProgram | undefined;
187
217
 
188
218
  if (isForAngular) {
189
219
  // Angular: sourceFileCache 확보
@@ -193,8 +223,8 @@ export class SdTsCompiler {
193
223
  augmentHostWithCaching(host, this._sourceFileCache);
194
224
 
195
225
  // NgtscProgram 생성
196
- logger.debug(`[${pkgName}] NgtscProgram 생성 중...`);
197
- const angularProgram = new NgtscProgram(
226
+ this._logger.debug(`[${pkgName}] NgtscProgram 생성 중...`);
227
+ angularProgram = new NgtscProgram(
198
228
  rootNames,
199
229
  compilerOptions,
200
230
  host,
@@ -210,13 +240,6 @@ export class SdTsCompiler {
210
240
  this._builderProgram,
211
241
  );
212
242
  program = tsProgram;
213
-
214
- // 7. Angular analyzeAsync
215
- logger.debug(`[${pkgName}] AOT analyzeAsync 시작`);
216
- await angularProgram.compiler.analyzeAsync();
217
- logger.debug(`[${pkgName}] AOT analyzeAsync 완료`);
218
-
219
- this._ngtscProgram = angularProgram;
220
243
  } else {
221
244
  // Non-Angular: BuilderProgram 직접 생성
222
245
  builderProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
@@ -228,84 +251,218 @@ export class SdTsCompiler {
228
251
  program = builderProgram.getProgram();
229
252
  }
230
253
 
231
- // 8. affected files 추적
232
- let affectedFiles: ReadonlySet<string> | undefined;
233
- if (isForAngular) {
234
- const result = this._findAffectedFilesForAngular(
235
- builderProgram,
236
- this._ngtscProgram!.compiler,
237
- this._sourceFileCache,
238
- );
239
- this._affectedSourceFiles = result.affectedSourceFiles;
240
- affectedFiles = result.affectedPaths;
241
- } else {
242
- affectedFiles = this._findAffectedFilesForTsc(builderProgram);
243
- }
254
+ // 9. 위험 구간 (체커 진입 — TsCompiler 내부 크래시 단일 catch)
255
+ try {
256
+ 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;
262
+ }
244
263
 
245
- logger.debug(`[${pkgName}] affected files: ${affectedFiles != null ? `${affectedFiles.size}개` : "전체 (global change)"}`);
264
+ // 9-1. affected files 추적
265
+ this._setCrashContext("findAffectedFiles");
266
+ 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);
277
+ }
246
278
 
247
- // 9. emit 처리
248
- let emitResults: EmitResult[] | undefined;
249
- if (isForAngular) {
250
- emitResults = this._emitAngular(
251
- this._ngtscProgram!,
279
+ if (affectedFiles != null) {
280
+ const sample = [...affectedFiles]
281
+ .slice(0, 10)
282
+ .map((f) => path.relative(cwd, f))
283
+ .join(", ");
284
+ this._logger.debug(
285
+ `[${pkgName}] affected files (${affectedFiles.size}개)${affectedFiles.size > 10 ? " [상위 10개]" : ""}: ${sample}`,
286
+ );
287
+ } else {
288
+ this._logger.debug(`[${pkgName}] affected files: 전체 (global change)`);
289
+ }
290
+
291
+ // 9-2. emit 처리
292
+ this._setCrashContext("emit");
293
+ 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);
303
+ }
304
+
305
+ // 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);
315
+
316
+ // 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
+ ]);
332
+
333
+ // 9-5. 상태 저장
334
+ this._builderProgram = builderProgram;
335
+ this._crashContext = undefined;
336
+
337
+ this._logger.debug(`[${pkgName}] compileAsync 완료`);
338
+
339
+ return {
340
+ program,
252
341
  builderProgram,
253
- this._affectedSourceFiles,
254
- emitOptions,
255
- );
256
- } else {
257
- this._emitTsc(builderProgram);
258
- }
342
+ isForAngular,
343
+ affectedFiles,
344
+ diagnostics: diagResult.diagnostics,
345
+ errorCount: diagResult.errorCount,
346
+ warningCount: diagResult.warningCount,
347
+ errors: diagResult.errors,
348
+ ngtscProgram: this._ngtscProgram,
349
+ emitResults,
350
+ lint: lintResult,
351
+ scssErrors: [...this._scssErrors],
352
+ scssDependencies: new Map(this._scssDependencies),
353
+ };
354
+ } 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");
259
378
 
260
- // 10. 진단 수집
261
- let rawDiagnostics: ts.Diagnostic[];
262
- if (isForAngular) {
263
- rawDiagnostics = this._collectDiagnosticsForAngular(
264
- this._ngtscProgram!,
379
+ const crashDiag: SerializedDiagnostic = {
380
+ category: ts.DiagnosticCategory.Error,
381
+ code: 0,
382
+ messageText: message,
383
+ };
384
+ return {
385
+ program,
265
386
  builderProgram,
266
- this._affectedSourceFiles,
267
- );
268
- } else {
269
- rawDiagnostics = this._collectDiagnosticsForTsc(builderProgram);
387
+ isForAngular,
388
+ affectedFiles: undefined,
389
+ diagnostics: [crashDiag],
390
+ errorCount: 1,
391
+ warningCount: 0,
392
+ errors: [message],
393
+ ngtscProgram: this._ngtscProgram,
394
+ emitResults: undefined,
395
+ lint: undefined,
396
+ scssErrors: [...this._scssErrors],
397
+ scssDependencies: new Map(this._scssDependencies),
398
+ };
399
+ }
400
+ }
401
+
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
+ }
270
439
  }
271
- const diagResult = this._finalizeDiagnostics(rawDiagnostics);
272
-
273
- // 11. 글로벌 SCSS + lint 병렬 실행
274
- const [, lintResult] = await Promise.all([
275
- // globalScss
276
- this._options.globalScss === true
277
- ? Promise.resolve().then(() => {
278
- const loadPaths = this._getScssLoadPaths();
279
- const globalScssErrors = compileGlobalScss(pkgDir, loadPaths);
280
- this._scssErrors.push(...globalScssErrors);
281
- })
282
- : Promise.resolve(),
283
- // lint
284
- this._options.lint === true
285
- ? this._runLint(program, affectedFiles)
286
- : Promise.resolve(undefined),
287
- ]);
288
-
289
- // 12. 상태 저장
290
- this._builderProgram = builderProgram;
291
-
292
- logger.debug(`[${pkgName}] compileAsync 완료`);
440
+ return report;
441
+ }
293
442
 
294
- return {
295
- program,
296
- builderProgram,
297
- isForAngular,
298
- affectedFiles,
299
- diagnostics: diagResult.diagnostics,
300
- errorCount: diagResult.errorCount,
301
- warningCount: diagResult.warningCount,
302
- errors: diagResult.errors,
303
- ngtscProgram: this._ngtscProgram,
304
- emitResults,
305
- lint: lintResult,
306
- scssErrors: [...this._scssErrors],
307
- scssDependencies: new Map(this._scssDependencies),
308
- };
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;
309
466
  }
310
467
 
311
468
  private async _runLint(
@@ -322,12 +479,12 @@ export class SdTsCompiler {
322
479
  });
323
480
  }
324
481
 
325
- logger.debug(`[${pkgName}] lint 시작`);
482
+ this._logger.debug(`[${pkgName}] lint 시작`);
326
483
  const result = await this._lintRunner.lint({
327
484
  program,
328
485
  affectedFiles,
329
486
  });
330
- logger.debug(`[${pkgName}] lint 완료 (에러: ${result.errorCount}, 경고: ${result.warningCount})`);
487
+ this._logger.debug(`[${pkgName}] lint 완료 (에러: ${result.errorCount}, 경고: ${result.warningCount})`);
331
488
 
332
489
  return result;
333
490
  }
@@ -456,14 +613,17 @@ export class SdTsCompiler {
456
613
  private _collectDiagnosticsForTsc(
457
614
  builderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram,
458
615
  ): ts.Diagnostic[] {
459
- return [
616
+ const diagnostics: ts.Diagnostic[] = [
460
617
  ...builderProgram.getConfigFileParsingDiagnostics(),
461
618
  ...builderProgram.getSyntacticDiagnostics(),
462
619
  ...builderProgram.getOptionsDiagnostics(),
463
620
  ...builderProgram.getGlobalDiagnostics(),
464
621
  ...builderProgram.getSemanticDiagnostics(),
465
- ...(!this._options.output.dts ? builderProgram.getProgram().getDeclarationDiagnostics() : []),
466
622
  ];
623
+ if (!this._options.output.dts) {
624
+ diagnostics.push(...builderProgram.getProgram().getDeclarationDiagnostics());
625
+ }
626
+ return diagnostics;
467
627
  }
468
628
 
469
629
  private _collectDiagnosticsForAngular(
@@ -490,17 +650,9 @@ export class SdTsCompiler {
490
650
  continue;
491
651
  }
492
652
 
653
+ this._setCrashContext("collectDiagnostics.getSemanticDiagnostics", sourceFile.fileName);
493
654
  diagnostics.push(...builderProgram.getSyntacticDiagnostics(sourceFile));
494
-
495
- // TypeScript 5.9 + NgtscProgram: getSemanticDiagnostics 크래시 방어
496
- let semanticDiags: readonly ts.Diagnostic[];
497
- try {
498
- semanticDiags = builderProgram.getSemanticDiagnostics(sourceFile);
499
- } catch {
500
- logger.debug(`getSemanticDiagnostics 크래시 (무시): ${sourceFile.fileName}`);
501
- semanticDiags = [];
502
- }
503
- diagnostics.push(...semanticDiags);
655
+ diagnostics.push(...builderProgram.getSemanticDiagnostics(sourceFile));
504
656
 
505
657
  // Declaration files는 Angular 진단 없음
506
658
  if (sourceFile.isDeclarationFile) {
@@ -509,6 +661,7 @@ export class SdTsCompiler {
509
661
 
510
662
  // Angular 템플릿 진단 (diagnosticCache 활용)
511
663
  if (affectedSourceFiles.has(sourceFile)) {
664
+ this._setCrashContext("collectDiagnostics.getDiagnosticsForFile", sourceFile.fileName);
512
665
  const angularDiagnostics = angularCompiler.getDiagnosticsForFile(
513
666
  sourceFile,
514
667
  optimization,
@@ -572,6 +725,7 @@ export class SdTsCompiler {
572
725
  const tsProgram = ngtscProgram.getTsProgram();
573
726
 
574
727
  // prepareEmit() → Angular transformers 획득
728
+ this._setCrashContext("emit.prepareEmit");
575
729
  const transformers = angularCompiler.prepareEmit().transformers;
576
730
  transformers.before ??= [];
577
731
  transformers.after ??= [];
@@ -622,6 +776,7 @@ export class SdTsCompiler {
622
776
  ) {
623
777
  continue;
624
778
  }
779
+ this._setCrashContext("emit.tsProgram.emit", sourceFile.fileName);
625
780
  tsProgram.emit(
626
781
  sourceFile,
627
782
  writeFileCallback,
@@ -631,15 +786,11 @@ export class SdTsCompiler {
631
786
  );
632
787
  }
633
788
 
634
- // .tsbuildinfo 영속화 (try-catch: TS 5.9 + NgtscProgram 크래시 방어)
635
- try {
636
- builderProgram.emit(undefined, () => {});
637
- } catch {
638
- logger.debug("builderProgram.emit 크래시 (무시) — tsbuildinfo 영속화 생략");
639
- }
789
+ this._setCrashContext("emit.builderProgram.emit");
790
+ builderProgram.emit(undefined, () => {});
640
791
 
641
792
  // sourceFilter 적용
642
- logger.debug(`emitAffectedFiles 완료 (${emitResults.length}개 파일)`);
793
+ this._logger.debug(`emitAffectedFiles 완료 (${emitResults.length}개 파일)`);
643
794
  if (emitOptions?.sourceFilter != null) {
644
795
  return emitResults.filter((r) => emitOptions.sourceFilter!(r.sourceFileName));
645
796
  }
@@ -656,7 +807,6 @@ export class SdTsCompiler {
656
807
  if ("fileName" in result.affected) {
657
808
  affectedFiles.add(pathx.posix(result.affected.fileName));
658
809
  } else {
659
- // ts.Program 반환 — 전역 변경, 전체 리빌드로 처리
660
810
  return undefined;
661
811
  }
662
812
  }
@@ -667,40 +817,32 @@ export class SdTsCompiler {
667
817
  builderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram,
668
818
  angularCompiler: NgCompiler,
669
819
  sourceFileCache?: AngularSourceFileCache,
670
- ): { affectedSourceFiles: Set<ts.SourceFile>; affectedPaths: ReadonlySet<string> | undefined } {
671
- logger.debug("Angular affected 파일 탐색 시작");
820
+ ): {
821
+ affectedSourceFiles: Set<ts.SourceFile>;
822
+ affectedPaths: ReadonlySet<string> | undefined;
823
+ } {
824
+ this._logger.debug("Angular affected 파일 탐색 시작");
672
825
  const affectedSourceFiles = new Set<ts.SourceFile>();
673
826
  let isGlobalChange = false;
674
827
 
675
828
  while (true) {
676
- let result: ReturnType<typeof builderProgram.getSemanticDiagnosticsOfNextAffectedFile>;
677
- try {
678
- result = builderProgram.getSemanticDiagnosticsOfNextAffectedFile(
679
- undefined,
680
- (sourceFile) => {
681
- if (
682
- angularCompiler.ignoreForDiagnostics.has(sourceFile) &&
683
- sourceFile.fileName.endsWith(".ngtypecheck.ts")
684
- ) {
685
- const originalFilename = sourceFile.fileName.slice(0, -15) + ".ts";
686
- const originalSourceFile = builderProgram.getSourceFile(originalFilename);
687
- if (originalSourceFile) {
688
- affectedSourceFiles.add(originalSourceFile);
689
- }
690
- return true;
829
+ const result = builderProgram.getSemanticDiagnosticsOfNextAffectedFile(
830
+ undefined,
831
+ (sourceFile) => {
832
+ if (
833
+ angularCompiler.ignoreForDiagnostics.has(sourceFile) &&
834
+ sourceFile.fileName.endsWith(".ngtypecheck.ts")
835
+ ) {
836
+ const originalFilename = sourceFile.fileName.slice(0, -15) + ".ts";
837
+ const originalSourceFile = builderProgram.getSourceFile(originalFilename);
838
+ if (originalSourceFile) {
839
+ affectedSourceFiles.add(originalSourceFile);
691
840
  }
692
- return false;
693
- },
694
- );
695
- } catch {
696
- logger.debug("getSemanticDiagnosticsOfNextAffectedFile 크래시 (무시) — 모든 소스를 affected로 처리");
697
- for (const sourceFile of builderProgram.getSourceFiles()) {
698
- if (!angularCompiler.ignoreForDiagnostics.has(sourceFile)) {
699
- affectedSourceFiles.add(sourceFile);
841
+ return true;
700
842
  }
701
- }
702
- break;
703
- }
843
+ return false;
844
+ },
845
+ );
704
846
  if (!result) break;
705
847
  if ("fileName" in result.affected) {
706
848
  affectedSourceFiles.add(result.affected);
@@ -748,7 +890,7 @@ export class SdTsCompiler {
748
890
  affectedPaths = paths;
749
891
  }
750
892
 
751
- logger.debug(`Angular affected 파일 탐색 완료 (${affectedSourceFiles.size}개)`);
893
+ this._logger.debug(`Angular affected 파일 탐색 완료 (${affectedSourceFiles.size}개)`);
752
894
  return { affectedSourceFiles, affectedPaths };
753
895
  }
754
896
 
@@ -1,11 +1,11 @@
1
1
  import path from "path";
2
2
  import ts from "typescript";
3
- import { consola } from "consola";
4
3
  import { pathx } from "@simplysm/core-node";
4
+ import { createLazyLogger } from "../runtime/lazy-logger";
5
5
  import { parseTsconfig } from "../utils/tsconfig";
6
6
  import { serializeDiagnostic, type SerializedDiagnostic } from "./typecheck-serialization";
7
7
 
8
- const logger = consola.withTag("sd:cli:typecheck-non-pkg");
8
+ const logger = createLazyLogger("sd:cli:typecheck-non-pkg");
9
9
 
10
10
  export interface NonPackageTypecheckResult {
11
11
  success: boolean;