@simplysm/sd-cli 14.0.47 → 14.0.48

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 (126) 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 +4 -4
  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/esbuild/esbuild-worker-plugin.spec.ts +8 -0
  121. package/tests/utils/replace-deps-watch.acc.spec.ts +85 -0
  122. package/tests/utils/replace-deps-watch.spec.ts +198 -1
  123. package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.d.ts +0 -3
  124. package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.d.ts.map +0 -1
  125. package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.js +0 -9
  126. package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.js.map +0 -1
@@ -50,14 +50,27 @@ async function copyWithUnlink(
50
50
  if (stats.isDirectory()) {
51
51
  await fsx.mkdir(targetPath);
52
52
  const names = await fs.promises.readdir(sourcePath);
53
+ const allowedChildren = names
54
+ .map((name) => path.resolve(sourcePath, name))
55
+ .filter((child) => filter == null || filter(child));
56
+ const allowedBasenames = new Set(allowedChildren.map((c) => path.basename(c)));
57
+
58
+ // 고아 엔트리 정리: filter 범위 내이면서 소스에 없는 타겟 엔트리 삭제
59
+ const targetNames = await fs.promises.readdir(targetPath).catch(() => [] as string[]);
60
+ await Promise.all(
61
+ targetNames.map(async (name) => {
62
+ const targetChild = path.join(targetPath, name);
63
+ if (filter != null && !filter(targetChild)) return;
64
+ if (allowedBasenames.has(name)) return;
65
+ await fs.promises.rm(targetChild, { recursive: true, force: true });
66
+ }),
67
+ );
68
+
53
69
  await Promise.all(
54
- names
55
- .map((name) => path.resolve(sourcePath, name))
56
- .filter((child) => filter == null || filter(child))
57
- .map((child) => copyWithUnlink(
58
- child,
59
- path.join(targetPath, path.basename(child)),
60
- )),
70
+ allowedChildren.map((child) => copyWithUnlink(
71
+ child,
72
+ path.join(targetPath, path.basename(child)),
73
+ )),
61
74
  );
62
75
  } else {
63
76
  if (await isFileContentSame(sourcePath, targetPath)) return;
@@ -201,115 +214,136 @@ export async function watchReplaceDeps(
201
214
 
202
215
  const entries = await resolveAllReplaceDepEntries(projectRoot, replaceDeps, logger);
203
216
 
204
- // 소스 디렉토리 감시자 설정
205
- const watchers: FsWatcher[] = [];
206
- const watchedSources = new Set<string>();
207
-
208
217
  logger.start(`replace-deps 워치 시작 중... (${entries.length}개 대상)`);
209
218
 
219
+ // resolvedSourcePath(posix) → entries 그룹화
220
+ // resolvedSourcePath는 replace-deps-resolve의 pathx.posixResolve 결과로 이미 POSIX이다.
221
+ const sourceMap = new Map<string, ReplaceDepEntry[]>();
210
222
  for (const entry of entries) {
211
- if (watchedSources.has(entry.resolvedSourcePath)) continue;
212
- watchedSources.add(entry.resolvedSourcePath);
223
+ const key = entry.resolvedSourcePath;
224
+ const arr = sourceMap.get(key) ?? [];
225
+ arr.push(entry);
226
+ sourceMap.set(key, arr);
227
+ }
213
228
 
214
- try {
215
- // 소스 패키지의 files 필드를 읽어 감시 대상 경로 구성
216
- const files = await loadFilesField(entry.resolvedSourcePath);
217
- if (files == null) {
218
- logger.warn(`[${entry.targetName}] package.json에 files 필드가 없어 감시 건너뜀`);
219
- continue;
220
- }
229
+ // 각 source의 watchPaths 수집 (files 필드 없는 source는 경고 후 제외). source 단위 병렬.
230
+ const allWatchPaths = new Set<string>();
231
+ await Promise.all(
232
+ [...sourceMap].map(async ([sourcePath, sourceEntries]) => {
233
+ try {
234
+ const files = await loadFilesField(sourcePath);
235
+
236
+ if (files == null) {
237
+ logger.warn(
238
+ `[${sourceEntries[0].targetName}] package.json에 files 필드가 없어 감시 건너뜀`,
239
+ );
240
+ sourceMap.delete(sourcePath);
241
+ return;
242
+ }
221
243
 
222
- // files 항목 경로 + npm 기본 파일 경로
223
- const watchPaths = files.map((f) =>
224
- pathx.posix(path.join(entry.resolvedSourcePath, f)),
225
- );
244
+ for (const f of files) {
245
+ allWatchPaths.add(pathx.posix(path.join(sourcePath, f)));
246
+ }
226
247
 
227
- // 소스 루트에서 npm 기본 파일 패턴에 매칭되는 파일 추가
228
- try {
229
- const rootEntries = await fs.promises.readdir(entry.resolvedSourcePath);
248
+ const rootEntries = await fs.promises
249
+ .readdir(sourcePath)
250
+ .catch(() => [] as string[]);
230
251
  for (const name of rootEntries) {
231
252
  if (NPM_DEFAULT_FILE_PATTERN.test(name)) {
232
- watchPaths.push(pathx.posix(path.join(entry.resolvedSourcePath, name)));
253
+ allWatchPaths.add(pathx.posix(path.join(sourcePath, name)));
233
254
  }
234
255
  }
235
- } catch {
236
- // readdir 실패 시 npm 기본 파일 감시 생략
256
+ } catch (err) {
257
+ logger.error(
258
+ `[${sourceEntries[0].targetName}] 감시 설정 실패: ${err instanceof Error ? err.message : err}`,
259
+ );
260
+ sourceMap.delete(sourcePath);
237
261
  }
262
+ }),
263
+ );
264
+
265
+ if (allWatchPaths.size === 0) {
266
+ if (entries.length > 0) {
267
+ logger.warn("감시 대상이 없어 워치가 시작되지 않음");
268
+ } else {
269
+ logger.success("replace-deps 워치 준비 완료");
270
+ }
271
+ return { entries, dispose: () => {} };
272
+ }
238
273
 
239
- // 소스 경로에 해당하는 entries만 사전 필터링하여 캡처
240
- const sourceEntries = entries.filter(
241
- (e) => e.resolvedSourcePath === entry.resolvedSourcePath,
242
- );
274
+ // longest-prefix 매칭을 위해 경로 우선 정렬
275
+ const sortedSources = [...sourceMap.keys()].sort((a, b) => b.length - a.length);
276
+
277
+ const findSource = (changedPath: string): string | undefined => {
278
+ for (const src of sortedSources) {
279
+ if (changedPath === src || changedPath.startsWith(src + "/")) return src;
280
+ }
281
+ return undefined;
282
+ };
283
+
284
+ const watcher = await FsWatcher.watch([...allWatchPaths], {
285
+ followSymlinks: false,
286
+ });
243
287
 
244
- const watcher = await FsWatcher.watch(watchPaths, {
245
- followSymlinks: false,
246
- });
247
- watcher.onChange({ delay: 300 }, async (changeInfos) => {
248
- let hasActualCopy = false;
288
+ watcher.onChange({ delay: 300 }, async (changeInfos) => {
289
+ const flags = await Promise.all(
290
+ changeInfos.map(async ({ path: changedPath }) => {
291
+ const src = findSource(changedPath);
292
+ if (src == null) return false;
293
+ const sourceEntries = sourceMap.get(src)!;
249
294
 
250
- for (const { path: changedPath } of changeInfos) {
251
- // 사전 필터링된 항목만 순회
252
- for (const e of sourceEntries) {
253
- // 소스로부터의 상대 경로 계산
254
- const relativePath = pathx.posix(path.relative(e.resolvedSourcePath, changedPath));
255
- const destPath = pathx.posix(path.join(e.actualTargetPath, relativePath));
295
+ let localActualCopy = false;
256
296
 
297
+ // 동일 source의 복수 target 복사는 순차로 유지한다 (destination 중복 시 race 방지).
298
+ for (const e of sourceEntries) {
299
+ const relativePath = pathx.posix(path.relative(e.resolvedSourcePath, changedPath));
300
+ const destPath = pathx.posix(path.join(e.actualTargetPath, relativePath));
301
+
302
+ try {
303
+ let sourceExists = false;
257
304
  try {
258
- // 소스 존재 여부 확인
259
- let sourceExists = false;
260
- try {
261
- await fs.promises.access(changedPath);
262
- sourceExists = true;
263
- } catch {
264
- // 소스가 삭제됨
265
- }
305
+ await fs.promises.access(changedPath);
306
+ sourceExists = true;
307
+ } catch {
308
+ // 소스가 삭제됨
309
+ }
266
310
 
267
- if (sourceExists) {
268
- // 소스가 디렉토리인지 파일인지 확인
269
- const stat = await fs.promises.stat(changedPath);
270
- if (stat.isDirectory()) {
271
- await fsx.mkdir(destPath);
272
- } else {
273
- // 파일 내용이 동일하면 복사 건너뜀 (불필요한 리빌드 방지)
274
- if (await isFileContentSame(changedPath, destPath)) continue;
275
- await fsx.mkdir(pathx.posix(path.dirname(destPath)));
276
- await fsx.copy(changedPath, destPath);
277
- hasActualCopy = true;
278
- }
311
+ if (sourceExists) {
312
+ const stat = await fs.promises.stat(changedPath);
313
+ if (stat.isDirectory()) {
314
+ await fsx.mkdir(destPath);
279
315
  } else {
280
- // 소스가 삭제됨 대상도 삭제
281
- await fsx.rm(destPath);
282
- hasActualCopy = true;
316
+ if (await isFileContentSame(changedPath, destPath)) continue;
317
+ await fsx.mkdir(pathx.posix(path.dirname(destPath)));
318
+ await fsx.copy(changedPath, destPath);
319
+ localActualCopy = true;
283
320
  }
284
- } catch (err) {
285
- logger.error(
286
- `[${e.targetName}] 복사 실패 (${relativePath}): ${err instanceof Error ? err.message : err}`,
287
- );
321
+ } else {
322
+ await fsx.rm(destPath);
323
+ localActualCopy = true;
288
324
  }
325
+ } catch (err) {
326
+ logger.error(
327
+ `[${e.targetName}] 복사 실패 (${relativePath}): ${err instanceof Error ? err.message : err}`,
328
+ );
289
329
  }
290
330
  }
291
331
 
292
- if (hasActualCopy) {
293
- options?.onChanged?.();
294
- }
295
- });
332
+ return localActualCopy;
333
+ }),
334
+ );
296
335
 
297
- watchers.push(watcher);
298
- } catch (err) {
299
- logger.error(
300
- `[${entry.targetName}] 감시 설정 실패: ${err instanceof Error ? err.message : err}`,
301
- );
336
+ if (flags.some((f) => f)) {
337
+ options?.onChanged?.();
302
338
  }
303
- }
339
+ });
304
340
 
305
341
  logger.success("replace-deps 워치 준비 완료");
306
342
 
307
343
  return {
308
344
  entries,
309
345
  dispose: () => {
310
- for (const watcher of watchers) {
311
- void watcher.close();
312
- }
346
+ void watcher.close();
313
347
  },
314
348
  };
315
349
  }
@@ -4,10 +4,10 @@ import fs from "fs";
4
4
  import YAML from "yaml";
5
5
  import TOML from "smol-toml";
6
6
  import { cpx } from "@simplysm/core-node";
7
- import { consola } from "consola";
7
+ import { createLazyLogger } from "../../runtime/lazy-logger";
8
8
  import { collectAllDependencyExternals } from "../../esbuild/esbuild-config";
9
9
 
10
- const logger = consola.withTag("sd:cli:server-production-files");
10
+ const logger = createLazyLogger("sd:cli:server-production-files");
11
11
 
12
12
  /**
13
13
  * 외부 모듈을 두 용도로 분리하여 수집한다.
@@ -3,11 +3,12 @@ import fs from "fs";
3
3
  import module from "module";
4
4
  import { cpx, fsx, pathx } from "@simplysm/core-node";
5
5
  import { consola, LogLevels } from "consola";
6
+ import { createLazyLogger } from "../runtime/lazy-logger";
6
7
  import type { NpmConfig, SdElectronConfig } from "../sd-config.types.js";
7
8
  import { createEnvBanner } from "../esbuild/esbuild-config.js";
8
9
 
9
10
  export class Electron {
10
- private static readonly _logger = consola.withTag("sd:cli:electron");
11
+ private static readonly _logger = createLazyLogger("sd:cli:electron");
11
12
 
12
13
  private readonly _electronPath: string;
13
14
  private readonly _srcPath: string;
@@ -1,15 +1,15 @@
1
1
  import { Worker, type WorkerProxy } from "@simplysm/core-node";
2
2
  import { err as errNs } from "@simplysm/core-common";
3
- import { consola } from "consola";
4
3
  import type { BuildResult, ResultCollector } from "../runtime/ResultCollector";
5
4
  import { stopEngineWorker } from "../runtime/engine-stop";
6
5
  import { setupWatchEvents } from "../runtime/engine-watch-events";
6
+ import { createLazyLogger } from "../runtime/lazy-logger";
7
7
  import type { LintWithProgramResult } from "../lint/lint-with-program";
8
8
  import type { RebuildManager } from "../runtime/rebuild-manager";
9
9
  import type { SerializedDiagnostic } from "../typecheck/typecheck-serialization";
10
10
  import type { BuildEngine, BuildOutput, EngineResult, PackageInfo } from "./types";
11
11
 
12
- const logger = consola.withTag("sd:cli:engine");
12
+ const logger = createLazyLogger("sd:cli:engine");
13
13
 
14
14
  /**
15
15
  * 모든 빌드 워커가 공유하는 공통 빌드 워커 이벤트
@@ -1,15 +1,15 @@
1
1
  import fs from "node:fs";
2
2
  import path from "path";
3
3
  import { Worker, type WorkerProxy } from "@simplysm/core-node";
4
- import { consola } from "consola";
5
4
  import type * as ClientWorkerModule from "../workers/client.worker";
6
5
  import type { ResultCollector } from "../runtime/ResultCollector";
7
6
  import { stopEngineWorker } from "../runtime/engine-stop";
8
7
  import { setupWatchEvents, type NormalizedBuildInfo } from "../runtime/engine-watch-events";
8
+ import { createLazyLogger } from "../runtime/lazy-logger";
9
9
  import type { RebuildManager } from "../runtime/rebuild-manager";
10
10
  import type { BuildEngine, BuildOutput, ClientPackageInfo, EngineResult } from "./types";
11
11
 
12
- const logger = consola.withTag("sd:cli:engine:esbuild-client");
12
+ const logger = createLazyLogger("sd:cli:engine:esbuild-client");
13
13
 
14
14
  /**
15
15
  * EsbuildClientEngine 옵션
@@ -3,9 +3,9 @@ import type { ResultCollector } from "../runtime/ResultCollector";
3
3
  import type { RebuildManager } from "../runtime/rebuild-manager";
4
4
  import type { BuildOutput, BuildPackageInfo, EngineResult } from "./types";
5
5
  import { BaseEngine, type CommonBuildWorkerModule } from "./BaseEngine";
6
- import { consola } from "consola";
6
+ import { createLazyLogger } from "../runtime/lazy-logger";
7
7
 
8
- const logger = consola.withTag("sd:cli:engine:ngtsc");
8
+ const logger = createLazyLogger("sd:cli:engine:ngtsc");
9
9
 
10
10
  /**
11
11
  * NgtscEngine 옵션
@@ -3,9 +3,9 @@ import type { ResultCollector } from "../runtime/ResultCollector";
3
3
  import type { RebuildManager } from "../runtime/rebuild-manager";
4
4
  import type { BuildOutput, EngineResult, ServerPackageInfo } from "./types";
5
5
  import { BaseEngine, type CommonBuildWorkerModule } from "./BaseEngine";
6
- import { consola } from "consola";
6
+ import { createLazyLogger } from "../runtime/lazy-logger";
7
7
 
8
- const logger = consola.withTag("sd:cli:engine:server");
8
+ const logger = createLazyLogger("sd:cli:engine:server");
9
9
 
10
10
  /**
11
11
  * ServerEsbuildEngine 옵션
@@ -3,9 +3,9 @@ import type { ResultCollector } from "../runtime/ResultCollector";
3
3
  import type { RebuildManager } from "../runtime/rebuild-manager";
4
4
  import type { BuildOutput, BuildPackageInfo, EngineResult } from "./types";
5
5
  import { BaseEngine, type CommonBuildWorkerModule } from "./BaseEngine";
6
- import { consola } from "consola";
6
+ import { createLazyLogger } from "../runtime/lazy-logger";
7
7
 
8
- const logger = consola.withTag("sd:cli:engine:tsc");
8
+ const logger = createLazyLogger("sd:cli:engine:tsc");
9
9
 
10
10
  /**
11
11
  * TscEngine 옵션
@@ -1,5 +1,5 @@
1
- import { consola } from "consola";
2
1
  import type { ResultCollector } from "../runtime/ResultCollector";
2
+ import { createLazyLogger } from "../runtime/lazy-logger";
3
3
  import type { RebuildManager } from "../runtime/rebuild-manager";
4
4
  import { hasAngularCoreDependency } from "../utils/package-utils";
5
5
  import { NgtscEngine } from "./NgtscEngine";
@@ -8,7 +8,7 @@ import { TscEngine } from "./TscEngine";
8
8
  import { EsbuildClientEngine } from "./EsbuildClientEngine";
9
9
  import type { BuildEngine, BuildPackageInfo, ClientPackageInfo, ServerPackageInfo } from "./types";
10
10
 
11
- const logger = consola.withTag("sd:cli:engine");
11
+ const logger = createLazyLogger("sd:cli:engine");
12
12
 
13
13
 
14
14
  /**
@@ -3,10 +3,13 @@ import fs from "fs";
3
3
  import os from "os";
4
4
  import ts from "typescript";
5
5
  import type esbuild from "esbuild";
6
- import { consola } from "consola";
7
6
  import { JavaScriptTransformer, Cache as AngularCache } from "@angular/build/private";
7
+ import { createLazyLogger } from "../runtime/lazy-logger";
8
8
  import type { AngularSourceFileCache } from "../angular/angular-compiler";
9
- import type { SerializedDiagnostic } from "../typecheck/typecheck-serialization";
9
+ import type {
10
+ SerializedDiagnostic,
11
+ SerializedMessageChain,
12
+ } from "../typecheck/typecheck-serialization";
10
13
  import { SdTsCompiler } from "../ts-compiler/SdTsCompiler";
11
14
  import type { ISdTsCompilerResult } from "../ts-compiler/sd-ts-compiler-result";
12
15
  import { FileReferenceTracker } from "./file-reference-tracker";
@@ -15,7 +18,7 @@ import { createCachedLoad, type LoadResultCache } from "./load-result-cache";
15
18
  import { collectHmrCandidates, HMR_MODIFIED_FILE_LIMIT } from "../angular/hmr-candidates";
16
19
  import { transformWorkerPatterns } from "./esbuild-worker-plugin";
17
20
 
18
- const logger = consola.withTag("sd:cli:angular-plugin");
21
+ const logger = createLazyLogger("sd:cli:angular-plugin");
19
22
 
20
23
  //#region Types
21
24
 
@@ -125,29 +128,67 @@ export function convertDiagnostic(
125
128
  return { text, location };
126
129
  }
127
130
 
131
+ function flattenSerializedMessage(
132
+ messageText: string | SerializedMessageChain,
133
+ indent = 0,
134
+ ): string {
135
+ if (typeof messageText === "string") return messageText;
136
+ const prefix = " ".repeat(indent);
137
+ let text = prefix + messageText.messageText;
138
+ if (messageText.next != null) {
139
+ for (const n of messageText.next) {
140
+ text += "\n" + flattenSerializedMessage(n, indent + 1);
141
+ }
142
+ }
143
+ return text;
144
+ }
145
+
146
+ function locationOf(
147
+ fileInfo: { fileName: string } | undefined,
148
+ start: number | undefined,
149
+ length: number | undefined,
150
+ program: ts.Program,
151
+ cwd: string,
152
+ ): esbuild.PartialMessage["location"] {
153
+ if (fileInfo == null || start == null) return null;
154
+ const sf = program.getSourceFile(fileInfo.fileName);
155
+ if (sf == null) return null;
156
+ const pos = sf.getLineAndCharacterOfPosition(start);
157
+ const lineStart = sf.getLineStarts()[pos.line];
158
+ const lineEnd = sf.getLineStarts()[pos.line + 1] ?? sf.text.length;
159
+ const lineText = sf.text.slice(lineStart, lineEnd).replace(/\r?\n$/, "");
160
+ return {
161
+ file: path.relative(cwd, fileInfo.fileName),
162
+ line: pos.line + 1,
163
+ column: pos.character,
164
+ lineText,
165
+ length: length ?? 0,
166
+ };
167
+ }
168
+
128
169
  export function convertSerializedDiagnosticToEsbuild(
129
170
  d: SerializedDiagnostic,
130
171
  program: ts.Program,
131
172
  cwd: string,
132
173
  ): esbuild.PartialMessage {
133
- let location: esbuild.PartialMessage["location"] = null;
134
- if (d.file != null && d.start != null) {
135
- const sf = program.getSourceFile(d.file.fileName);
136
- if (sf != null) {
137
- const pos = sf.getLineAndCharacterOfPosition(d.start);
138
- const lineStart = sf.getLineStarts()[pos.line];
139
- const lineEnd = sf.getLineStarts()[pos.line + 1] ?? sf.text.length;
140
- const lineText = sf.text.slice(lineStart, lineEnd).replace(/\r?\n$/, "");
141
- location = {
142
- file: path.relative(cwd, d.file.fileName),
143
- line: pos.line + 1,
144
- column: pos.character,
145
- lineText,
146
- length: d.length ?? 0,
147
- };
174
+ const location = locationOf(d.file, d.start, d.length, program, cwd);
175
+ const text = flattenSerializedMessage(d.messageText);
176
+
177
+ const notes: esbuild.PartialNote[] = [];
178
+ if (d.relatedInformation != null) {
179
+ for (const ri of d.relatedInformation) {
180
+ notes.push({
181
+ text: flattenSerializedMessage(ri.messageText),
182
+ location: locationOf(ri.file, ri.start, ri.length, program, cwd),
183
+ });
148
184
  }
149
185
  }
150
- return { text: d.messageText, location };
186
+
187
+ return {
188
+ text,
189
+ location,
190
+ notes: notes.length > 0 ? notes : undefined,
191
+ };
151
192
  }
152
193
 
153
194
  //#endregion
@@ -3,10 +3,10 @@ import { readFileSync, existsSync } from "fs";
3
3
  import fs from "fs/promises";
4
4
  import { createRequire } from "module";
5
5
  import type esbuild from "esbuild";
6
- import { consola } from "consola";
6
+ import { createLazyLogger } from "../runtime/lazy-logger";
7
7
  import { addJsExtensionToImports } from "../utils/output-path-rewriter";
8
8
 
9
- const logger = consola.withTag("sd:cli:esbuild-config");
9
+ const logger = createLazyLogger("sd:cli:esbuild-config");
10
10
 
11
11
  /**
12
12
  * esbuild outputFiles에서 변경된 파일만 디스크에 쓴다.
@@ -1,9 +1,9 @@
1
1
  import type ts from "typescript";
2
2
  import { ESLint } from "eslint";
3
3
  import { pathx } from "@simplysm/core-node";
4
- import { consola } from "consola";
4
+ import { createLazyLogger } from "../runtime/lazy-logger";
5
5
 
6
- const logger = consola.withTag("sd:cli:lint-with-program");
6
+ const logger = createLazyLogger("sd:cli:lint-with-program");
7
7
 
8
8
  /**
9
9
  * LintWithProgramRunner.lint()가 반환하는 lint 결과
@@ -0,0 +1,23 @@
1
+ import { consola, type ConsolaInstance } from "consola";
2
+
3
+ /**
4
+ * 모듈 레벨에서 선언해도 안전한 lazy logger 프록시.
5
+ *
6
+ * consola.withTag()는 호출 시점의 consola.options(level/reporters)를
7
+ * 스냅샷 복사한 새 인스턴스를 반환한다. 모듈 import 시점에 호출하면
8
+ * setupConsola 이전의 기본 level(info) 상태가 고정되어 이후 setupConsola가
9
+ * level을 debug로 올려도 반영되지 않는다.
10
+ *
11
+ * createLazyLogger는 실제 ConsolaInstance 생성을 첫 접근 시점까지 미뤄
12
+ * setupConsola 이후의 설정이 반영된 스냅샷을 갖도록 한다.
13
+ */
14
+ export function createLazyLogger(tag: string): ConsolaInstance {
15
+ let cached: ConsolaInstance | undefined;
16
+ return new Proxy({} as ConsolaInstance, {
17
+ get(_target, prop) {
18
+ cached ??= consola.withTag(tag);
19
+ const value: unknown = Reflect.get(cached, prop);
20
+ return typeof value === "function" ? value.bind(cached) : value;
21
+ },
22
+ });
23
+ }
@@ -15,10 +15,10 @@ import path from "path";
15
15
  import fs from "fs";
16
16
  import { fileURLToPath } from "url";
17
17
  import { EventEmitter } from "node:events";
18
- import { consola } from "consola";
19
18
  import { setupConsola } from "@simplysm/core-node";
19
+ import { createLazyLogger } from "./runtime/lazy-logger";
20
20
 
21
- const logger = consola.withTag("sd:cli:entry");
21
+ const logger = createLazyLogger("sd:cli:entry");
22
22
 
23
23
  Error.stackTraceLimit = Infinity;
24
24
  EventEmitter.defaultMaxListeners = 100;
package/src/sd-cli.ts CHANGED
@@ -8,15 +8,15 @@
8
8
  */
9
9
 
10
10
  import { cpx, setupConsola } from "@simplysm/core-node";
11
- import { consola } from "consola";
12
11
  import os from "os";
13
12
  import path from "path";
14
13
  import { fileURLToPath } from "url";
14
+ import { createLazyLogger } from "./runtime/lazy-logger";
15
15
 
16
16
  const __filename = fileURLToPath(import.meta.url);
17
17
  const __dirname = path.dirname(__filename);
18
18
  const isDev = path.extname(__filename) === ".ts";
19
- const logger = consola.withTag("sd:cli");
19
+ const logger = createLazyLogger("sd:cli");
20
20
 
21
21
  if (isDev) {
22
22
  // 개발 모드 (.ts): affinity 적용 후 직접 실행