@simplysm/sd-cli 14.0.15 → 14.0.17

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 (216) hide show
  1. package/README.md +4 -3
  2. package/dist/angular/client-transform-stylesheet.d.ts +2 -0
  3. package/dist/angular/client-transform-stylesheet.d.ts.map +1 -1
  4. package/dist/angular/client-transform-stylesheet.js +88 -2
  5. package/dist/angular/client-transform-stylesheet.js.map +1 -1
  6. package/dist/angular/vite-angular-plugin.d.ts +7 -0
  7. package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
  8. package/dist/angular/vite-angular-plugin.js +78 -16
  9. package/dist/angular/vite-angular-plugin.js.map +1 -1
  10. package/dist/capacitor/capacitor.d.ts.map +1 -1
  11. package/dist/capacitor/capacitor.js +24 -13
  12. package/dist/capacitor/capacitor.js.map +1 -1
  13. package/dist/commands/check.d.ts.map +1 -1
  14. package/dist/commands/check.js +8 -9
  15. package/dist/commands/check.js.map +1 -1
  16. package/dist/commands/device.d.ts +1 -1
  17. package/dist/commands/device.d.ts.map +1 -1
  18. package/dist/commands/device.js +61 -12
  19. package/dist/commands/device.js.map +1 -1
  20. package/dist/commands/lint.d.ts +0 -1
  21. package/dist/commands/lint.d.ts.map +1 -1
  22. package/dist/commands/lint.js +2 -3
  23. package/dist/commands/lint.js.map +1 -1
  24. package/dist/commands/publish.js +3 -3
  25. package/dist/commands/publish.js.map +1 -1
  26. package/dist/commands/replace-deps.js +1 -1
  27. package/dist/commands/replace-deps.js.map +1 -1
  28. package/dist/commands/typecheck.d.ts.map +1 -1
  29. package/dist/commands/typecheck.js +1 -2
  30. package/dist/commands/typecheck.js.map +1 -1
  31. package/dist/electron/electron.d.ts +3 -2
  32. package/dist/electron/electron.d.ts.map +1 -1
  33. package/dist/electron/electron.js +54 -31
  34. package/dist/electron/electron.js.map +1 -1
  35. package/dist/engines/BaseEngine.js +1 -1
  36. package/dist/engines/BaseEngine.js.map +1 -1
  37. package/dist/engines/NgtscEngine.d.ts.map +1 -1
  38. package/dist/engines/NgtscEngine.js +0 -1
  39. package/dist/engines/NgtscEngine.js.map +1 -1
  40. package/dist/engines/ServerEsbuildEngine.d.ts.map +1 -1
  41. package/dist/engines/ServerEsbuildEngine.js +0 -1
  42. package/dist/engines/ServerEsbuildEngine.js.map +1 -1
  43. package/dist/engines/TscEngine.d.ts.map +1 -1
  44. package/dist/engines/TscEngine.js +0 -1
  45. package/dist/engines/TscEngine.js.map +1 -1
  46. package/dist/engines/ViteEngine.d.ts.map +1 -1
  47. package/dist/engines/ViteEngine.js +10 -1
  48. package/dist/engines/ViteEngine.js.map +1 -1
  49. package/dist/engines/index.d.ts +0 -10
  50. package/dist/engines/index.d.ts.map +1 -1
  51. package/dist/engines/index.js +0 -5
  52. package/dist/engines/index.js.map +1 -1
  53. package/dist/engines/types.d.ts +0 -1
  54. package/dist/engines/types.d.ts.map +1 -1
  55. package/dist/infra/SignalHandler.d.ts +1 -6
  56. package/dist/infra/SignalHandler.d.ts.map +1 -1
  57. package/dist/infra/SignalHandler.js +4 -13
  58. package/dist/infra/SignalHandler.js.map +1 -1
  59. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  60. package/dist/orchestrators/BuildOrchestrator.js +7 -12
  61. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  62. package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
  63. package/dist/orchestrators/DevWatchOrchestrator.js +18 -11
  64. package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
  65. package/dist/sd-cli-entry.d.ts +0 -1
  66. package/dist/sd-cli-entry.d.ts.map +1 -1
  67. package/dist/sd-cli-entry.js +13 -16
  68. package/dist/sd-cli-entry.js.map +1 -1
  69. package/dist/sd-cli.js +1 -1
  70. package/dist/sd-cli.js.map +1 -1
  71. package/dist/sd-config.types.d.ts +12 -2
  72. package/dist/sd-config.types.d.ts.map +1 -1
  73. package/dist/utils/angular-compiler.d.ts.map +1 -1
  74. package/dist/utils/angular-compiler.js +20 -13
  75. package/dist/utils/angular-compiler.js.map +1 -1
  76. package/dist/utils/esbuild-config.d.ts +1 -1
  77. package/dist/utils/esbuild-config.d.ts.map +1 -1
  78. package/dist/utils/esbuild-config.js +1 -4
  79. package/dist/utils/esbuild-config.js.map +1 -1
  80. package/dist/utils/ngtsc-build-core.d.ts.map +1 -1
  81. package/dist/utils/ngtsc-build-core.js +3 -0
  82. package/dist/utils/ngtsc-build-core.js.map +1 -1
  83. package/dist/utils/orchestrator-utils.js +1 -1
  84. package/dist/utils/orchestrator-utils.js.map +1 -1
  85. package/dist/utils/tsc-build.d.ts +5 -0
  86. package/dist/utils/tsc-build.d.ts.map +1 -1
  87. package/dist/utils/tsc-build.js +2 -1
  88. package/dist/utils/tsc-build.js.map +1 -1
  89. package/dist/utils/vite-config.d.ts +1 -1
  90. package/dist/utils/vite-config.d.ts.map +1 -1
  91. package/dist/utils/vite-config.js +22 -53
  92. package/dist/utils/vite-config.js.map +1 -1
  93. package/dist/utils/vite-pwa-plugin.d.ts +9 -0
  94. package/dist/utils/vite-pwa-plugin.d.ts.map +1 -0
  95. package/dist/utils/vite-pwa-plugin.js +139 -0
  96. package/dist/utils/vite-pwa-plugin.js.map +1 -0
  97. package/dist/utils/worker-utils.d.ts +2 -5
  98. package/dist/utils/worker-utils.d.ts.map +1 -1
  99. package/dist/utils/worker-utils.js +5 -11
  100. package/dist/utils/worker-utils.js.map +1 -1
  101. package/dist/workers/client.worker.d.ts.map +1 -1
  102. package/dist/workers/client.worker.js +9 -3
  103. package/dist/workers/client.worker.js.map +1 -1
  104. package/dist/workers/library-build.worker.d.ts.map +1 -1
  105. package/dist/workers/library-build.worker.js +6 -2
  106. package/dist/workers/library-build.worker.js.map +1 -1
  107. package/dist/workers/ngtsc-build.worker.js +2 -2
  108. package/dist/workers/ngtsc-build.worker.js.map +1 -1
  109. package/dist/workers/server-build.worker.d.ts.map +1 -1
  110. package/dist/workers/server-build.worker.js +6 -2
  111. package/dist/workers/server-build.worker.js.map +1 -1
  112. package/dist/workers/server-runtime.worker.js +4 -4
  113. package/dist/workers/server-runtime.worker.js.map +1 -1
  114. package/docs/config.md +30 -2
  115. package/docs/pwa-configuration-types.md +1 -1
  116. package/package.json +8 -10
  117. package/src/angular/client-transform-stylesheet.ts +104 -2
  118. package/src/angular/vite-angular-plugin.ts +92 -31
  119. package/src/capacitor/capacitor.ts +25 -26
  120. package/src/commands/check.ts +8 -11
  121. package/src/commands/device.ts +71 -17
  122. package/src/commands/lint.ts +2 -3
  123. package/src/commands/publish.ts +3 -3
  124. package/src/commands/replace-deps.ts +1 -1
  125. package/src/commands/typecheck.ts +1 -2
  126. package/src/electron/electron.ts +62 -43
  127. package/src/engines/BaseEngine.ts +1 -1
  128. package/src/engines/NgtscEngine.ts +0 -1
  129. package/src/engines/ServerEsbuildEngine.ts +0 -1
  130. package/src/engines/TscEngine.ts +0 -1
  131. package/src/engines/ViteEngine.ts +9 -1
  132. package/src/engines/index.ts +0 -10
  133. package/src/engines/types.ts +0 -1
  134. package/src/infra/SignalHandler.ts +4 -14
  135. package/src/orchestrators/BuildOrchestrator.ts +7 -9
  136. package/src/orchestrators/DevWatchOrchestrator.ts +22 -10
  137. package/src/sd-cli-entry.ts +17 -24
  138. package/src/sd-cli.ts +1 -1
  139. package/src/sd-config.types.ts +13 -2
  140. package/src/utils/angular-compiler.ts +21 -21
  141. package/src/utils/esbuild-config.ts +2 -5
  142. package/src/utils/ngtsc-build-core.ts +7 -0
  143. package/src/utils/orchestrator-utils.ts +1 -1
  144. package/src/utils/tsc-build.ts +7 -0
  145. package/src/utils/vite-config.ts +23 -55
  146. package/src/utils/vite-pwa-plugin.ts +168 -0
  147. package/src/utils/worker-utils.ts +5 -11
  148. package/src/workers/client.worker.ts +11 -3
  149. package/src/workers/library-build.worker.ts +6 -2
  150. package/src/workers/ngtsc-build.worker.ts +2 -2
  151. package/src/workers/server-build.worker.ts +7 -2
  152. package/src/workers/server-runtime.worker.ts +4 -4
  153. package/tests/angular/client-transform-stylesheet.spec.ts +43 -0
  154. package/tests/angular/find-affected-by-scss.spec.ts +37 -0
  155. package/tests/angular/fixtures/basic-app/scss/_colors.scss +1 -0
  156. package/tests/angular/fixtures/basic-app/scss/_variables.scss +3 -0
  157. package/tests/angular/fixtures/basic-app/src/styled.component.ts +14 -0
  158. package/tests/angular/linker-disk-cache.spec.ts +158 -0
  159. package/tests/angular/scss-disk-cache.spec.ts +162 -0
  160. package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +15 -15
  161. package/tests/angular/vite-angular-plugin-hmr.spec.ts +9 -9
  162. package/tests/angular/vite-angular-plugin-lint.spec.ts +4 -4
  163. package/tests/angular/vite-angular-plugin-scss-hmr.spec.ts +87 -0
  164. package/tests/angular/vite-angular-plugin.spec.ts +15 -15
  165. package/tests/capacitor/capacitor-icon.spec.ts +2 -4
  166. package/tests/capacitor/capacitor-init.spec.ts +2 -4
  167. package/tests/capacitor/capacitor-workspace.spec.ts +2 -4
  168. package/tests/commands/device.spec.ts +108 -8
  169. package/tests/commands/publish.spec.ts +2 -2
  170. package/tests/commands/typecheck.spec.ts +1 -1
  171. package/tests/electron/electron.spec.ts +24 -17
  172. package/tests/engines/ngtsc-engine.spec.ts +0 -3
  173. package/tests/engines/server-esbuild-engine.spec.ts +0 -3
  174. package/tests/engines/tsc-engine.spec.ts +1 -2
  175. package/tests/engines/vite-engine.spec.ts +0 -2
  176. package/tests/infra/signal-handler.spec.ts +1 -12
  177. package/tests/orchestrators/build-orchestrator.spec.ts +1 -7
  178. package/tests/orchestrators/dev-watch-orchestrator.spec.ts +24 -66
  179. package/tests/utils/angular-compiler.spec.ts +1396 -32
  180. package/tests/utils/esbuild-config.spec.ts +4 -7
  181. package/tests/utils/{ngtsc-build-core-angular-compiler.spec.ts → ngtsc-build-core.spec.ts} +142 -11
  182. package/tests/utils/orchestrator-utils.spec.ts +2 -2
  183. package/tests/utils/sd-config.spec.ts +2 -2
  184. package/tests/utils/tsc-build.spec.ts +4 -1
  185. package/tests/utils/vite-config.spec.ts +130 -261
  186. package/tests/utils/vite-pwa-plugin.acc.spec.ts +143 -0
  187. package/tests/utils/vite-pwa-plugin.spec.ts +350 -0
  188. package/tests/utils/worker-utils.spec.ts +8 -7
  189. package/tests/workers/client-worker.spec.ts +50 -1
  190. package/tests/workers/dev-port-file.verify.md +6 -0
  191. package/tests/workers/library-build-lint.spec.ts +1 -1
  192. package/tests/workers/library-build-worker.spec.ts +1 -1
  193. package/tests/workers/ngtsc-build-lint.spec.ts +1 -1
  194. package/tests/workers/server-build-lint.spec.ts +1 -1
  195. package/tests/workers/server-build-worker.spec.ts +1 -1
  196. package/tests/workers/server-runtime-worker.spec.ts +8 -1
  197. package/dist/infra/WorkerManager.d.ts +0 -40
  198. package/dist/infra/WorkerManager.d.ts.map +0 -1
  199. package/dist/infra/WorkerManager.js +0 -59
  200. package/dist/infra/WorkerManager.js.map +0 -1
  201. package/dist/utils/SdCliReporter.d.ts +0 -18
  202. package/dist/utils/SdCliReporter.d.ts.map +0 -1
  203. package/dist/utils/SdCliReporter.js +0 -144
  204. package/dist/utils/SdCliReporter.js.map +0 -1
  205. package/src/infra/WorkerManager.ts +0 -65
  206. package/src/utils/SdCliReporter.ts +0 -177
  207. package/tests/angular/scss-compiler-async.spec.ts +0 -54
  208. package/tests/commands/dev.spec.ts +0 -53
  209. package/tests/commands/watch.spec.ts +0 -53
  210. package/tests/infra/worker-manager.spec.ts +0 -63
  211. package/tests/utils/angular-compiler-emit.spec.ts +0 -570
  212. package/tests/utils/angular-compiler-init.spec.ts +0 -705
  213. package/tests/utils/angular-compiler-update.spec.ts +0 -293
  214. package/tests/utils/build-env.spec.ts +0 -33
  215. package/tests/utils/ngtsc-build-core-transform-stylesheet.spec.ts +0 -124
  216. package/tests/utils/ngtsc-scss-refactor.spec.ts +0 -47
@@ -200,8 +200,8 @@ export class BuildOrchestrator {
200
200
 
201
201
  // 결과 수집
202
202
  const results: BuildStepResult[] = [];
203
- // 에러 추적 (콜백에서 변경 추적을 위해 객체로 래핑)
204
- const state = { hasError: false };
203
+ // results에 포함되지 않는 에러 추적 (네이티브 빌드 실패, rejected 태스크 )
204
+ let hasUntrackedError = false;
205
205
 
206
206
  // 파일 캐시 (진단 출력용)
207
207
  const fileCache = new Map<string, string>();
@@ -249,7 +249,6 @@ export class BuildOrchestrator {
249
249
  warnings: engineResult.build.warnings.length > 0 ? engineResult.build.warnings : undefined,
250
250
  diagnostics,
251
251
  });
252
- if (!engineResult.build.success) state.hasError = true;
253
252
 
254
253
  } finally {
255
254
  await engine.stop();
@@ -289,7 +288,6 @@ export class BuildOrchestrator {
289
288
  warnings: engineResult.build.warnings.length > 0 ? engineResult.build.warnings : undefined,
290
289
  diagnostics,
291
290
  });
292
- if (!engineResult.build.success) state.hasError = true;
293
291
 
294
292
  } finally {
295
293
  await engine.stop();
@@ -327,7 +325,6 @@ export class BuildOrchestrator {
327
325
  warnings: engineResult.build.warnings.length > 0 ? engineResult.build.warnings : undefined,
328
326
  diagnostics,
329
327
  });
330
- if (!engineResult.build.success) state.hasError = true;
331
328
 
332
329
  // 네이티브 빌드 (빌드 성공 시에만 실행)
333
330
  if (engineResult.build.success) {
@@ -362,7 +359,7 @@ export class BuildOrchestrator {
362
359
  const nativeResults = await Promise.allSettled(nativeBuildPromises);
363
360
  for (const nativeResult of nativeResults) {
364
361
  if (nativeResult.status === "rejected") {
365
- state.hasError = true;
362
+ hasUntrackedError = true;
366
363
  const err = nativeResult.reason;
367
364
  this._logger.error(
368
365
  `[${name}] 네이티브 빌드 실패: ${err instanceof Error ? err.message : String(err)}`,
@@ -391,7 +388,7 @@ export class BuildOrchestrator {
391
388
  if (stack != null) {
392
389
  this._logger.debug(`빌드 예외 스택:\n${stack}`);
393
390
  }
394
- state.hasError = true;
391
+ hasUntrackedError = true;
395
392
  }
396
393
  }
397
394
  this._logger.success("빌드 실행 완료");
@@ -429,13 +426,14 @@ export class BuildOrchestrator {
429
426
  // 최종 결과 로그
430
427
  const errorCount = results.filter((r) => !r.success).length;
431
428
  const warningCount = results.filter((r) => r.warnings != null).length;
432
- if (state.hasError) {
429
+ const hasError = errorCount > 0 || hasUntrackedError;
430
+ if (hasError) {
433
431
  this._logger.error("빌드 에러 발생", { errorCount, warningCount });
434
432
  } else {
435
433
  this._logger.info("빌드 완료", { errorCount, warningCount });
436
434
  }
437
435
 
438
- return state.hasError;
436
+ return hasError;
439
437
  }
440
438
 
441
439
  /**
@@ -97,7 +97,7 @@ export class DevWatchOrchestrator {
97
97
  sdConfig = await loadSdConfig({
98
98
  cwd: this._cwd,
99
99
  dev: true,
100
- options: this._options.options,
100
+ opt: this._options.options,
101
101
  });
102
102
  this._logger.debug("sd.config.ts 로드 완료");
103
103
  } catch (err) {
@@ -156,15 +156,11 @@ export class DevWatchOrchestrator {
156
156
  this._resultCollector = new ResultCollector();
157
157
  this._rebuildManager = new RebuildManager(this._logger);
158
158
 
159
- // 배치 완료 핸들러
159
+ // 배치 완료 핸들러 (watch 모드만 여기서 등록, dev 모드는 _startDevMode() 끝에서 등록)
160
160
  if (this._options.mode === "watch") {
161
161
  this._rebuildManager.on("batchComplete", (_completedKeys) => {
162
162
  printErrors(this._resultCollector.toMap());
163
163
  });
164
- } else {
165
- this._rebuildManager.on("batchComplete", (completedKeys) => {
166
- this._onDevBatchComplete(completedKeys);
167
- });
168
164
  }
169
165
 
170
166
  // 라이브러리 패키지용 BuildEngine 생성 (watch 모드 전용)
@@ -418,9 +414,26 @@ export class DevWatchOrchestrator {
418
414
  }
419
415
  }
420
416
 
421
- // Print initial results
422
- printErrors(this._resultCollector.toMap());
423
- printServers(this._resultCollector.toMap(), this._serverClientsMap);
417
+ // 모든 엔진 준비 완료 후 서버 런타임 시작 (클라이언트 포트 확보 보장)
418
+ await this._restartServers();
419
+
420
+ // 독립 클라이언트만 존재하고 서버가 없는 경우 URL 출력 예약
421
+ if (this._serverPackages.length === 0) {
422
+ const hasIndependentClients = this._clientPackages.some(({ name }) => {
423
+ const isServerConnected = [...this._serverClientsMap.values()].some(
424
+ (clients) => clients.includes(name),
425
+ );
426
+ return !isServerConnected && this._getClientPort(name) != null;
427
+ });
428
+ if (hasIndependentClients) {
429
+ this._schedulePrintServers();
430
+ }
431
+ }
432
+
433
+ // dev 모드 배치 완료 핸들러 등록 (이후 watch 이벤트용)
434
+ this._rebuildManager.on("batchComplete", (completedKeys) => {
435
+ this._onDevBatchComplete(completedKeys);
436
+ });
424
437
  }
425
438
 
426
439
  private _onDevBatchComplete(completedKeys: string[]): void {
@@ -464,7 +477,6 @@ export class DevWatchOrchestrator {
464
477
  await Promise.all(restartPromises);
465
478
  this._logger.debug("서버 재시작 완료");
466
479
  printErrors(this._resultCollector.toMap());
467
- this._schedulePrintServers();
468
480
  }
469
481
 
470
482
  private _schedulePrintServers(): void {
@@ -2,10 +2,10 @@
2
2
  /* eslint-disable no-console */
3
3
 
4
4
  // 사이드 이펙트: Map/Array prototype 확장 (getOrCreate 등)
5
- import "@simplysm/core-common";
5
+ import { env } from "@simplysm/core-common";
6
6
  import yargs, { type Argv } from "yargs";
7
7
  import { hideBin } from "yargs/helpers";
8
- import { runCheck, type CheckType } from "./commands/check";
8
+ import { type CheckType, runCheck } from "./commands/check";
9
9
  import { runWatch } from "./commands/watch";
10
10
  import { runDev } from "./commands/dev";
11
11
  import { runBuild } from "./commands/build";
@@ -15,14 +15,12 @@ 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, LogLevels } from "consola";
19
- import { SdCliReporter } from "./utils/SdCliReporter";
18
+ import { consola } from "consola";
19
+ import { setupConsola } from "@simplysm/core-node";
20
20
 
21
21
  Error.stackTraceLimit = Infinity;
22
22
  EventEmitter.defaultMaxListeners = 100;
23
23
 
24
- consola.options.reporters = [new SdCliReporter()];
25
-
26
24
  const COMMAND_NAMES = ["check", "watch", "dev", "device", "build", "publish", "replace-deps"];
27
25
 
28
26
  async function collectYargsHelp(argv: string[]): Promise<string> {
@@ -36,7 +34,6 @@ async function collectYargsHelp(argv: string[]): Promise<string> {
36
34
  } catch {
37
35
  // yargs가 help 출력 후 throw할 수 있음
38
36
  } finally {
39
-
40
37
  console.log = orig;
41
38
  }
42
39
  return lines.join("\n");
@@ -58,9 +55,9 @@ export function createCliParser(argv: string[]): Argv {
58
55
  async () => {
59
56
  for (const cmdName of COMMAND_NAMES) {
60
57
  const helpText = await collectYargsHelp([cmdName, "--help"]);
61
-
58
+
62
59
  console.log(helpText);
63
-
60
+
64
61
  console.log();
65
62
  }
66
63
  },
@@ -78,9 +75,9 @@ export function createCliParser(argv: string[]): Argv {
78
75
  })
79
76
  .middleware((args) => {
80
77
  if (args.debug) {
81
- consola.level = LogLevels.debug;
82
- process.env["SD_DEBUG"] = "true";
78
+ env("SD_DEBUG", "true");
83
79
  }
80
+ setupConsola({ cli: true });
84
81
  })
85
82
  .command(
86
83
  "check [targets..]",
@@ -113,9 +110,7 @@ export function createCliParser(argv: string[]): Argv {
113
110
  types: (() => {
114
111
  const validTypes = ["typecheck", "lint", "test"] as const;
115
112
  const types = args.type.split(",").map((t) => t.trim());
116
- const invalidTypes = types.filter(
117
- (t) => !validTypes.includes(t as CheckType),
118
- );
113
+ const invalidTypes = types.filter((t) => !validTypes.includes(t as CheckType));
119
114
  if (invalidTypes.length > 0) {
120
115
  throw new Error(
121
116
  `Invalid check type(s): ${invalidTypes.join(", ")}. Valid types: ${validTypes.join(", ")}`,
@@ -186,24 +181,22 @@ export function createCliParser(argv: string[]): Argv {
186
181
  },
187
182
  )
188
183
  .command(
189
- "device",
184
+ "device [target]",
190
185
  "Run native app on device/desktop",
191
186
  (cmd) =>
192
187
  cmd
193
188
  .version(false)
194
189
  .hide("help")
190
+ .positional("target", {
191
+ type: "string",
192
+ describe: "Client package to run (e.g., my-client-app)",
193
+ })
195
194
  .options({
196
- "package": {
197
- type: "string",
198
- alias: "p",
199
- description: "Client package name to run",
200
- demandOption: true,
201
- },
202
- "url": {
195
+ url: {
203
196
  type: "string",
204
197
  description: "Dev server URL (auto-detected from sd.config.ts if omitted)",
205
198
  },
206
- "opt": {
199
+ opt: {
207
200
  type: "string",
208
201
  array: true,
209
202
  alias: "o",
@@ -214,7 +207,7 @@ export function createCliParser(argv: string[]): Argv {
214
207
  async (args) => {
215
208
  const { runDevice } = await import("./commands/device");
216
209
  await runDevice({
217
- package: args.package,
210
+ target: args.target,
218
211
  url: args.url,
219
212
  options: args.opt,
220
213
  });
package/src/sd-cli.ts CHANGED
@@ -30,7 +30,7 @@ if (isDev) {
30
30
  try {
31
31
  const { loadSdConfig } = await import("./utils/sd-config.js");
32
32
  const { setupReplaceDeps } = await import("./utils/replace-deps.js");
33
- const sdConfig = await loadSdConfig({ cwd: process.cwd(), dev: false, options: [] });
33
+ const sdConfig = await loadSdConfig({ cwd: process.cwd(), dev: false, opt: [] });
34
34
  if (process.argv[2] !== "replace-deps" && sdConfig.replaceDeps != null) {
35
35
  await setupReplaceDeps(process.cwd(), sdConfig.replaceDeps);
36
36
  }
@@ -1,3 +1,14 @@
1
+ /** npm package.json 구조 */
2
+ export interface NpmConfig {
3
+ name: string;
4
+ version: string;
5
+ description?: string;
6
+ dependencies?: Record<string, string>;
7
+ devDependencies?: Record<string, string>;
8
+ peerDependencies?: Record<string, string>;
9
+ volta?: unknown;
10
+ }
11
+
1
12
  /**
2
13
  * 빌드 타겟 타입 (esbuild로 빌드)
3
14
  * - node: Node.js 전용 패키지
@@ -169,7 +180,7 @@ export interface SdElectronConfig {
169
180
  //#region PWA 설정 타입
170
181
 
171
182
  /**
172
- * PWA manifest 설정 (VitePWA manifest 옵션의 서브셋)
183
+ * PWA manifest 설정
173
184
  */
174
185
  export interface SdPwaManifestConfig {
175
186
  name?: string;
@@ -325,7 +336,7 @@ export interface SdConfigParams {
325
336
  /** 개발 모드 플래그 */
326
337
  dev: boolean;
327
338
  /** 추가 옵션 (CLI의 -o 플래그에서 전달) */
328
- options: string[];
339
+ opt: string[];
329
340
  }
330
341
 
331
342
  /**
@@ -285,11 +285,16 @@ export class AngularCompiler {
285
285
  const programFiles = tsProgram.getSourceFiles();
286
286
  logger.debug(`ts.Program 소스 파일: ${programFiles.length}개`);
287
287
 
288
- // 9. BuilderProgram 생성
288
+ // 9. BuilderProgram 생성 (진단 + affected file 탐지용)
289
+ // .tsbuildinfo에서 이전 상태를 복원하여 프로세스 재시작 후에도 incremental 진단 유지
290
+ let oldBuilderProgram = this._builderProgram;
291
+ if (oldBuilderProgram == null) {
292
+ oldBuilderProgram = ts.readBuilderProgram(mergedOptions, host) ?? undefined;
293
+ }
289
294
  const builderProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
290
295
  tsProgram,
291
296
  host,
292
- this._builderProgram,
297
+ oldBuilderProgram,
293
298
  );
294
299
 
295
300
  // 10. AOT 분석
@@ -446,7 +451,7 @@ export class AngularCompiler {
446
451
  }
447
452
 
448
453
  const angularCompiler = this._ngtscProgram.compiler;
449
- const builderProgram = this._builderProgram;
454
+ const tsProgram = this._ngtscProgram.getTsProgram();
450
455
  const affectedFiles = this._affectedFiles;
451
456
 
452
457
  // 2. prepareEmit() → Angular transformers 획득
@@ -466,7 +471,6 @@ export class AngularCompiler {
466
471
 
467
472
  // 4. emit 결과를 수집하는 구조
468
473
  const emitResults: EmitResult[] = [];
469
- const emittedSourceFiles = new Set<ts.SourceFile>();
470
474
  const emitDeclarationOnly = !!compilerOptions.emitDeclarationOnly;
471
475
 
472
476
  const writeFileCallback: ts.WriteFileCallback = (
@@ -484,25 +488,16 @@ export class AngularCompiler {
484
488
  return;
485
489
  }
486
490
  angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
487
- emittedSourceFiles.add(sourceFile);
488
491
  emitResults.push({ filename, contents, sourceFileName: sourceFile.fileName });
489
492
  };
490
493
 
491
- // 5. emitNextAffectedFile 루프
492
- while (
493
- builderProgram.emitNextAffectedFile(
494
- writeFileCallback,
495
- undefined,
496
- emitDeclarationOnly,
497
- transformers,
498
- )
499
- ) {
500
- /* empty */
501
- }
502
-
503
- // 6. 2차 루프: TypeScript가 affected로 판단하지 않았지만 Angular이 처리해야 할 파일
504
- for (const sourceFile of builderProgram.getSourceFiles()) {
505
- if (emittedSourceFiles.has(sourceFile) || angularCompiler.ignoreForEmit.has(sourceFile)) {
494
+ // 5. ts.Program.emit()으로 직접 emit (NgtscProgram.emit 패턴)
495
+ // EmitAndSemanticDiagnosticsBuilderProgram.emitNextAffectedFile()은 내부적으로
496
+ // emitKind를 DTS-only로 결정할 수 있어 Angular before transformer가 .js를 생성하지 않고
497
+ // DtsTransformRegistry에 메타데이터를 등록하지 않는 문제가 있다.
498
+ // ts.Program.emit()을 직접 호출하면 항상 full emit이 수행된다.
499
+ for (const sourceFile of tsProgram.getSourceFiles()) {
500
+ if (angularCompiler.ignoreForEmit.has(sourceFile)) {
506
501
  continue;
507
502
  }
508
503
  if (sourceFile.isDeclarationFile) {
@@ -514,7 +509,7 @@ export class AngularCompiler {
514
509
  ) {
515
510
  continue;
516
511
  }
517
- builderProgram.emit(
512
+ tsProgram.emit(
518
513
  sourceFile,
519
514
  writeFileCallback,
520
515
  undefined,
@@ -523,6 +518,11 @@ export class AngularCompiler {
523
518
  );
524
519
  }
525
520
 
521
+ // 6. .tsbuildinfo 영속화 (프로세스 재시작 후 incremental 진단 유지)
522
+ // TS 5.9에서 emitBuildInfo()가 제거됨. emit()이 내부적으로 build info를 기록한다.
523
+ // no-op writeFile로 JS/DTS 재출력 없이 build info만 갱신.
524
+ this._builderProgram.emit(undefined, () => {});
525
+
526
526
  // 7. sourceFilter 적용 후 yield
527
527
  logger.debug(`emitAffectedFiles 완료 (${emitResults.length}개 파일)`);
528
528
  for (const result of emitResults) {
@@ -14,9 +14,8 @@ const logger = consola.withTag("sd:cli:esbuild-config");
14
14
  * - 기타 파일(.js.map 등): 원본 내용을 그대로 비교한다
15
15
  * - 기존 파일과 내용이 동일하면 타임스탬프 보존을 위해 쓰기를 스킵한다
16
16
  */
17
- export async function writeChangedOutputFiles(outputFiles: esbuild.OutputFile[]): Promise<boolean> {
17
+ export async function writeChangedOutputFiles(outputFiles: esbuild.OutputFile[]): Promise<void> {
18
18
  logger.debug(`변경된 출력 파일 쓰기 시작 (${outputFiles.length}개)`);
19
- let hasChanges = false;
20
19
  await Promise.all(
21
20
  outputFiles.map(async (file) => {
22
21
  const finalText = file.path.endsWith(".js")
@@ -36,13 +35,11 @@ export async function writeChangedOutputFiles(outputFiles: esbuild.OutputFile[])
36
35
  // 파일이 아직 존재하지 않음
37
36
  }
38
37
 
39
- hasChanges = true;
40
38
  await fs.mkdir(path.dirname(file.path), { recursive: true });
41
39
  await fs.writeFile(file.path, finalText);
42
40
  }),
43
41
  );
44
- logger.debug(`변경된 출력 파일 쓰기 완료 (변경: ${String(hasChanges)})`);
45
- return hasChanges;
42
+ logger.debug("변경된 출력 파일 쓰기 완료");
46
43
  }
47
44
 
48
45
  /**
@@ -59,10 +59,17 @@ export function buildCompilerOptions(
59
59
  pkgDir: string,
60
60
  output: BuildOutput,
61
61
  ): ts.CompilerOptions {
62
+ const needsEmit = output.js || output.dts;
62
63
  const options: ts.CompilerOptions = {
63
64
  ...baseOptions,
64
65
  sourceMap: false,
65
66
  outDir: path.join(pkgDir, "dist"),
67
+ incremental: true,
68
+ tsBuildInfoFile: path.join(
69
+ pkgDir,
70
+ ".cache",
71
+ needsEmit ? "ngtsc-build.tsbuildinfo" : "ngtsc-typecheck.tsbuildinfo",
72
+ ),
66
73
  };
67
74
 
68
75
  if (output.js && output.dts) {
@@ -19,7 +19,7 @@ export async function loadAndValidateConfig(params: {
19
19
  const config = await loadSdConfig({
20
20
  cwd: params.cwd,
21
21
  dev: params.dev,
22
- options: params.options,
22
+ opt: params.options,
23
23
  });
24
24
  validateTargets(params.targets, params.packagesForValidation ?? config.packages);
25
25
  return config;
@@ -30,6 +30,8 @@ export interface TscPackageBuildOptions {
30
30
  env?: TypecheckEnv;
31
31
  /** 타입체크 전용 모드에서 tests/ 파일 포함 여부. 기본값 false. */
32
32
  includeTests?: boolean;
33
+ /** 이전 빌드의 BuilderProgram. watch 모드에서 SourceFile 재사용을 위해 전달한다. */
34
+ oldBuilderProgram?: ts.EmitAndSemanticDiagnosticsBuilderProgram;
33
35
  }
34
36
 
35
37
  /**
@@ -46,6 +48,9 @@ export interface TscPackageBuildResult {
46
48
  /** 이 빌드에서 영향받은 파일 (정규화된 순방향 슬래시 경로).
47
49
  * watch 모드에서 증분 lint에 사용한다. */
48
50
  affectedFiles?: ReadonlySet<string>;
51
+ /** 다음 빌드에서 SourceFile 재사용을 위한 BuilderProgram.
52
+ * watch 모드에서 이 값을 저장해두고 다음 빌드 시 oldBuilderProgram으로 전달한다. */
53
+ builderProgram?: ts.EmitAndSemanticDiagnosticsBuilderProgram;
49
54
  }
50
55
 
51
56
  /**
@@ -142,6 +147,7 @@ export function runTscPackageBuild(options: TscPackageBuildOptions): TscPackageB
142
147
  rootFiles,
143
148
  compilerOptions,
144
149
  host,
150
+ options.oldBuilderProgram,
145
151
  );
146
152
 
147
153
  // builder program의 증분 분석을 통해 affected 파일을 추적한다.
@@ -199,6 +205,7 @@ export function runTscPackageBuild(options: TscPackageBuildOptions): TscPackageB
199
205
  warningCount,
200
206
  program: builderProgram.getProgram(),
201
207
  affectedFiles,
208
+ builderProgram,
202
209
  };
203
210
  } catch (err) {
204
211
  const message = errNs.message(err);
@@ -1,6 +1,5 @@
1
1
  import type { InlineConfig, PluginOption } from "vite";
2
2
  import path from "path";
3
- import tsconfigPaths from "vite-tsconfig-paths";
4
3
  import browserslistToEsbuild from "browserslist-to-esbuild";
5
4
  import { sdAngularPlugin } from "../angular/vite-angular-plugin.js";
6
5
  import solidPlugin from "vite-plugin-solid";
@@ -10,8 +9,7 @@ import {
10
9
  } from "./vite-scope-watch-plugin.js";
11
10
  import { sdPostCssInlinePlugin } from "../angular/vite-postcss-inline-plugin.js";
12
11
  import type { SdPwaConfig } from "../sd-config.types.js";
13
- import { VitePWA } from "vite-plugin-pwa";
14
- import { generatePwaIcons } from "./generate-pwa-icons.js";
12
+ import { sdPwaPlugin } from "./vite-pwa-plugin.js";
15
13
 
16
14
  /** createClientViteConfig 옵션 */
17
15
  export interface CreateClientViteConfigOptions {
@@ -70,9 +68,9 @@ export interface CreateClientViteConfigOptions {
70
68
  * Angular AOT 플러그인, tsconfigPaths, env define, server/build 기본 설정,
71
69
  * browserslist, PostCSS, polyfills, legacyModule (inlineDynamicImports) 등을 통합 구성한다.
72
70
  */
73
- export async function createClientViteConfig(
71
+ export function createClientViteConfig(
74
72
  options: CreateClientViteConfigOptions,
75
- ): Promise<InlineConfig> {
73
+ ): InlineConfig {
76
74
  const name = options.pkgName.replace(/^@[^/]+\//, "");
77
75
 
78
76
  // browserslist → esbuild target
@@ -101,9 +99,7 @@ export async function createClientViteConfig(
101
99
  }
102
100
 
103
101
  // plugins
104
- const plugins: PluginOption[] = [
105
- tsconfigPaths({ projects: [options.tsconfigPath] }),
106
- ];
102
+ const plugins: PluginOption[] = [];
107
103
 
108
104
  if (options.framework === "solid") {
109
105
  plugins.push(solidPlugin());
@@ -170,59 +166,23 @@ export async function createClientViteConfig(
170
166
  const config: InlineConfig = {
171
167
  root: options.pkgDir,
172
168
  base: options.base ?? `/${name}/`,
169
+ resolve: { tsconfigPaths: true },
173
170
  define: Object.keys(define).length > 0 ? define : undefined,
174
171
  plugins,
175
172
  server: serverConfig,
176
173
  css: cssConfig,
177
- esbuild: {
178
- target: esbuildTarget,
179
- },
180
174
  build: {
181
175
  target: esbuildTarget,
182
176
  },
183
177
  optimizeDeps: {
184
178
  ...optimizeDepsConfig,
185
- esbuildOptions: {
186
- target: esbuildTarget as string[],
187
- },
188
179
  },
189
180
  };
190
181
 
191
182
  // PWA (build 모드 + pwa !== false)
192
183
  if (options.mode === "build" && options.pwa !== false) {
193
- const pwaConfig = typeof options.pwa === "object" ? options.pwa : {};
194
-
195
- // 아이콘 자동 생성 (커스텀 icons 미설정 시)
196
- let iconsConfig: Record<string, unknown> = {};
197
- if (pwaConfig.manifest?.icons != null) {
198
- iconsConfig = { icons: pwaConfig.manifest.icons };
199
- } else {
200
- const generatedIcons = await generatePwaIcons(options.pkgDir);
201
- if (generatedIcons.length > 0) {
202
- iconsConfig = { icons: generatedIcons };
203
- }
204
- }
205
-
206
- const pwaManifest = {
207
- name: pwaConfig.manifest?.name ?? name,
208
- short_name: pwaConfig.manifest?.short_name ?? name,
209
- display: pwaConfig.manifest?.display ?? "standalone",
210
- theme_color: pwaConfig.manifest?.theme_color ?? "#ffffff",
211
- background_color: pwaConfig.manifest?.background_color ?? "#ffffff",
212
- ...iconsConfig,
213
- };
214
- const pwaWorkbox = {
215
- globPatterns: pwaConfig.workbox?.globPatterns ?? [
216
- "**/*.{js,css,html,ico,png,svg,woff2}",
217
- ],
218
- };
219
184
  (config.plugins as PluginOption[]).push(
220
- VitePWA({
221
- registerType: "prompt",
222
- injectRegister: "script",
223
- manifest: pwaManifest,
224
- workbox: pwaWorkbox,
225
- }),
185
+ sdPwaPlugin({ pkgDir: options.pkgDir, pkgName: name, pwa: options.pwa }),
226
186
  );
227
187
  }
228
188
 
@@ -271,24 +231,18 @@ export async function createClientViteConfig(
271
231
  }
272
232
  }
273
233
 
274
- // legacyModule: true → 코드 스플리팅 비활성화 + esbuild import.meta 변환 + 잔여 import() 제거
234
+ // legacyModule: true → 코드 스플리팅 비활성화 + import.meta/import() 치환 (Chrome 61 호환)
275
235
  if (options.legacyModule === true) {
276
- config.esbuild = {
277
- ...config.esbuild,
278
- supported: {
279
- "import-meta": false,
280
- },
281
- };
282
236
  config.build = {
283
237
  ...config.build,
284
- rollupOptions: {
238
+ rolldownOptions: {
285
239
  output: {
286
240
  inlineDynamicImports: true,
287
241
  },
288
242
  },
289
243
  };
290
244
 
291
- // Rollup이 인라인하지 못한 잔여 dynamic import()를 제거한다.
245
+ // Rolldown이 인라인하지 못한 잔여 dynamic import()를 제거한다.
292
246
  // inlineDynamicImports가 정적 경로를 모두 인라인한 후에도,
293
247
  // @vite-ignore나 런타임 계산 경로의 import()가 남을 수 있다.
294
248
  // Chrome 61은 import() 구문을 파싱하지 못하므로 no-op 함수로 치환한다.
@@ -306,6 +260,20 @@ export async function createClientViteConfig(
306
260
  };
307
261
  },
308
262
  });
263
+
264
+ // import.meta 구문을 치환한다. Chrome 61은 import.meta를 파싱하지 못한다 (Chrome 64+).
265
+ // Vite/Rolldown이 빌드 시 대부분의 import.meta를 resolve하지만, 잔여분에 대한 안전망이다.
266
+ (config.plugins as PluginOption[]).push({
267
+ name: "sd-legacy-strip-import-meta",
268
+ enforce: "post",
269
+ renderChunk(code) {
270
+ if (!code.includes("import.meta")) return null;
271
+ return {
272
+ code: code.replace(/\bimport\.meta\b/g, "(void 0)"),
273
+ map: null,
274
+ };
275
+ },
276
+ });
309
277
  }
310
278
 
311
279
  // build 모드 설정 (프로덕션 빌드 또는 legacyModule dev)