@simplysm/sd-cli 13.0.10 → 13.0.12

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 (154) hide show
  1. package/dist/builders/BaseBuilder.d.ts +15 -4
  2. package/dist/builders/BaseBuilder.d.ts.map +1 -1
  3. package/dist/builders/BaseBuilder.js +50 -0
  4. package/dist/builders/BaseBuilder.js.map +1 -1
  5. package/dist/builders/DtsBuilder.d.ts.map +1 -1
  6. package/dist/builders/DtsBuilder.js +2 -39
  7. package/dist/builders/DtsBuilder.js.map +1 -1
  8. package/dist/builders/LibraryBuilder.d.ts.map +1 -1
  9. package/dist/builders/LibraryBuilder.js +2 -39
  10. package/dist/builders/LibraryBuilder.js.map +1 -1
  11. package/dist/builders/types.d.ts +2 -2
  12. package/dist/builders/types.d.ts.map +1 -1
  13. package/dist/capacitor/capacitor.d.ts.map +1 -1
  14. package/dist/capacitor/capacitor.js +2 -1
  15. package/dist/capacitor/capacitor.js.map +1 -1
  16. package/dist/commands/add-client.d.ts.map +1 -1
  17. package/dist/commands/add-client.js +1 -9
  18. package/dist/commands/add-client.js.map +1 -1
  19. package/dist/commands/add-server.d.ts.map +1 -1
  20. package/dist/commands/add-server.js +1 -9
  21. package/dist/commands/add-server.js.map +1 -1
  22. package/dist/commands/build.d.ts +1 -2
  23. package/dist/commands/build.d.ts.map +1 -1
  24. package/dist/commands/build.js +12 -311
  25. package/dist/commands/build.js.map +1 -1
  26. package/dist/commands/dev.d.ts +1 -1
  27. package/dist/commands/dev.d.ts.map +1 -1
  28. package/dist/commands/dev.js +11 -432
  29. package/dist/commands/dev.js.map +1 -1
  30. package/dist/commands/device.d.ts.map +1 -1
  31. package/dist/commands/device.js +17 -32
  32. package/dist/commands/device.js.map +1 -1
  33. package/dist/commands/init.d.ts.map +1 -1
  34. package/dist/commands/init.js +1 -9
  35. package/dist/commands/init.js.map +1 -1
  36. package/dist/commands/lint.d.ts +1 -1
  37. package/dist/commands/lint.d.ts.map +1 -1
  38. package/dist/commands/lint.js +71 -118
  39. package/dist/commands/lint.js.map +2 -2
  40. package/dist/commands/publish.d.ts.map +1 -1
  41. package/dist/commands/publish.js +47 -69
  42. package/dist/commands/publish.js.map +1 -1
  43. package/dist/commands/typecheck.d.ts +1 -1
  44. package/dist/commands/typecheck.d.ts.map +1 -1
  45. package/dist/commands/typecheck.js +11 -24
  46. package/dist/commands/typecheck.js.map +1 -1
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +4 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/infra/ResultCollector.d.ts +1 -1
  52. package/dist/infra/ResultCollector.d.ts.map +1 -1
  53. package/dist/infra/ResultCollector.js +1 -1
  54. package/dist/infra/ResultCollector.js.map +1 -1
  55. package/dist/orchestrators/BuildOrchestrator.d.ts +53 -0
  56. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -0
  57. package/dist/orchestrators/BuildOrchestrator.js +338 -0
  58. package/dist/orchestrators/BuildOrchestrator.js.map +6 -0
  59. package/dist/orchestrators/DevOrchestrator.d.ts +64 -0
  60. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -0
  61. package/dist/orchestrators/DevOrchestrator.js +524 -0
  62. package/dist/orchestrators/DevOrchestrator.js.map +6 -0
  63. package/dist/orchestrators/WatchOrchestrator.d.ts +1 -1
  64. package/dist/orchestrators/WatchOrchestrator.d.ts.map +1 -1
  65. package/dist/orchestrators/WatchOrchestrator.js +30 -21
  66. package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
  67. package/dist/orchestrators/index.d.ts +2 -0
  68. package/dist/orchestrators/index.d.ts.map +1 -1
  69. package/dist/orchestrators/index.js +4 -0
  70. package/dist/orchestrators/index.js.map +1 -1
  71. package/dist/sd-cli-entry.js +14 -14
  72. package/dist/sd-cli-entry.js.map +1 -1
  73. package/dist/sd-cli.js +6 -4
  74. package/dist/sd-cli.js.map +1 -1
  75. package/dist/utils/output-utils.d.ts +0 -1
  76. package/dist/utils/output-utils.d.ts.map +1 -1
  77. package/dist/utils/output-utils.js +1 -1
  78. package/dist/utils/output-utils.js.map +1 -1
  79. package/dist/utils/package-utils.d.ts +5 -1
  80. package/dist/utils/package-utils.d.ts.map +1 -1
  81. package/dist/utils/package-utils.js +12 -0
  82. package/dist/utils/package-utils.js.map +1 -1
  83. package/dist/utils/rebuild-manager.d.ts +15 -0
  84. package/dist/utils/rebuild-manager.d.ts.map +1 -0
  85. package/dist/utils/rebuild-manager.js +50 -0
  86. package/dist/utils/rebuild-manager.js.map +6 -0
  87. package/dist/utils/replace-deps.d.ts.map +1 -1
  88. package/dist/utils/replace-deps.js +7 -14
  89. package/dist/utils/replace-deps.js.map +1 -1
  90. package/dist/utils/vite-config.d.ts.map +1 -1
  91. package/dist/utils/vite-config.js +43 -5
  92. package/dist/utils/vite-config.js.map +1 -1
  93. package/dist/utils/worker-events.d.ts +5 -4
  94. package/dist/utils/worker-events.d.ts.map +1 -1
  95. package/dist/utils/worker-events.js +4 -0
  96. package/dist/utils/worker-events.js.map +1 -1
  97. package/dist/utils/worker-utils.d.ts +13 -0
  98. package/dist/utils/worker-utils.d.ts.map +1 -0
  99. package/dist/utils/worker-utils.js +15 -0
  100. package/dist/utils/worker-utils.js.map +6 -0
  101. package/dist/workers/client.worker.d.ts.map +1 -1
  102. package/dist/workers/client.worker.js +2 -14
  103. package/dist/workers/client.worker.js.map +1 -1
  104. package/dist/workers/library.worker.d.ts.map +1 -1
  105. package/dist/workers/library.worker.js +2 -14
  106. package/dist/workers/library.worker.js.map +1 -1
  107. package/dist/workers/server-runtime.worker.d.ts.map +1 -1
  108. package/dist/workers/server-runtime.worker.js +11 -11
  109. package/dist/workers/server-runtime.worker.js.map +1 -1
  110. package/dist/workers/server.worker.d.ts.map +1 -1
  111. package/dist/workers/server.worker.js +2 -14
  112. package/dist/workers/server.worker.js.map +1 -1
  113. package/package.json +4 -5
  114. package/src/builders/BaseBuilder.ts +71 -4
  115. package/src/builders/DtsBuilder.ts +2 -49
  116. package/src/builders/LibraryBuilder.ts +2 -51
  117. package/src/builders/types.ts +2 -2
  118. package/src/capacitor/capacitor.ts +2 -1
  119. package/src/commands/add-client.ts +1 -14
  120. package/src/commands/add-server.ts +1 -13
  121. package/src/commands/build.ts +13 -443
  122. package/src/commands/dev.ts +12 -582
  123. package/src/commands/device.ts +17 -34
  124. package/src/commands/init.ts +1 -13
  125. package/src/commands/lint.ts +85 -146
  126. package/src/commands/publish.ts +58 -76
  127. package/src/commands/typecheck.ts +13 -46
  128. package/src/index.ts +1 -0
  129. package/src/infra/ResultCollector.ts +2 -2
  130. package/src/orchestrators/BuildOrchestrator.ts +499 -0
  131. package/src/orchestrators/DevOrchestrator.ts +703 -0
  132. package/src/orchestrators/WatchOrchestrator.ts +42 -25
  133. package/src/orchestrators/index.ts +2 -0
  134. package/src/sd-cli-entry.ts +14 -14
  135. package/src/sd-cli.ts +6 -4
  136. package/src/utils/output-utils.ts +1 -2
  137. package/src/utils/package-utils.ts +16 -1
  138. package/src/utils/rebuild-manager.ts +65 -0
  139. package/src/utils/replace-deps.ts +26 -25
  140. package/src/utils/vite-config.ts +84 -6
  141. package/src/utils/worker-events.ts +13 -5
  142. package/src/utils/worker-utils.ts +26 -0
  143. package/src/workers/client.worker.ts +3 -19
  144. package/src/workers/library.worker.ts +2 -19
  145. package/src/workers/server-runtime.worker.ts +16 -17
  146. package/src/workers/server.worker.ts +2 -19
  147. package/templates/add-client/__CLIENT__/package.json.hbs +1 -1
  148. package/templates/add-server/__SERVER__/package.json.hbs +2 -2
  149. package/templates/init/package.json.hbs +3 -3
  150. package/dist/utils/listr-manager.d.ts +0 -37
  151. package/dist/utils/listr-manager.d.ts.map +0 -1
  152. package/dist/utils/listr-manager.js +0 -59
  153. package/dist/utils/listr-manager.js.map +0 -6
  154. package/src/utils/listr-manager.ts +0 -89
@@ -1,10 +1,9 @@
1
1
  import ts from "typescript";
2
2
  import path from "path";
3
3
  import os from "os";
4
- import { Listr } from "listr2";
5
4
  import { pathPosix, pathFilterByTargets, Worker, type WorkerProxy } from "@simplysm/core-node";
6
5
  import "@simplysm/core-common";
7
- import { consola, LogLevels } from "consola";
6
+ import { consola } from "consola";
8
7
  import type { SdConfig } from "../sd-config.types";
9
8
  import { parseRootTsconfig, type TypecheckEnv } from "../utils/tsconfig";
10
9
  import { loadSdConfig } from "../utils/sd-config";
@@ -163,7 +162,7 @@ function createTypecheckTasks(
163
162
  * - `sd.config.ts`를 로드하여 패키지별 타겟 정보 확인 (없으면 기본값 사용)
164
163
  * - Worker threads를 사용하여 실제 병렬 타입체크 수행
165
164
  * - incremental 컴파일 사용 (`.cache/typecheck-{env}.tsbuildinfo`)
166
- * - listr2를 사용하여 진행 상황 표시
165
+ * - consola 로깅을 사용하여 진행 상황 표시
167
166
  * - 에러 발생 시 `process.exitCode = 1` 설정
168
167
  *
169
168
  * @param options - 타입체크 실행 옵션
@@ -187,7 +186,7 @@ export async function runTypecheck(options: TypecheckOptions): Promise<void> {
187
186
  try {
188
187
  parsedConfig = parseRootTsconfig(cwd);
189
188
  } catch (err) {
190
- consola.error(err instanceof Error ? err.message : err);
189
+ logger.error(err instanceof Error ? err.message : String(err));
191
190
  process.exitCode = 1;
192
191
  return;
193
192
  }
@@ -233,39 +232,33 @@ export async function runTypecheck(options: TypecheckOptions): Promise<void> {
233
232
  const concurrency = Math.min(maxConcurrency, tasks.length);
234
233
  logger.debug("동시성 설정", { concurrency, maxConcurrency, taskCount: tasks.length });
235
234
 
236
- // Worker 풀 생성 (작업 수만큼만 생성)
235
+ // Worker 풀 생성
237
236
  const workerPath = import.meta.resolve("../workers/dts.worker");
238
237
  const workers: WorkerProxy<typeof DtsWorkerModule>[] = [];
239
238
  for (let i = 0; i < concurrency; i++) {
240
239
  workers.push(Worker.create<typeof DtsWorkerModule>(workerPath));
241
240
  }
242
241
 
243
- // 결과 수집용
244
242
  const allResults: { displayName: string; result: DtsBuildResult }[] = [];
245
243
 
246
- // listr2-Worker 연동 패턴:
247
- // 1. listr2의 각 task는 Promise를 반환하고, 해당 Promise가 resolve되면 task가 완료됨
248
- // 2. taskResolvers 맵에 task별 resolve 함수를 저장
249
- // 3. Worker가 작업 완료 시 해당 task의 resolver를 호출하여 listr2 UI 업데이트
250
- // 4. Worker 풀은 독립적으로 작업 큐에서 task를 가져와 실행
251
- const taskResolvers = new Map<string, () => void>();
252
-
253
244
  try {
254
- // 작업 큐
255
245
  let taskIndex = 0;
256
246
 
257
- // Worker에서 작업 실행
258
247
  async function runNextTask(worker: WorkerProxy<typeof DtsWorkerModule>): Promise<void> {
259
248
  while (taskIndex < tasks.length) {
260
249
  const currentIndex = taskIndex++;
261
250
  const task = tasks[currentIndex];
262
251
 
252
+ logger.debug(`[${task.displayName}] 타입체크 시작`);
263
253
  try {
264
254
  const result = await worker.buildDts(task.buildInfo);
265
-
266
255
  allResults.push({ displayName: task.displayName, result });
256
+ if (result.success) {
257
+ logger.debug(`[${task.displayName}] 타입체크 완료`);
258
+ } else {
259
+ logger.debug(`[${task.displayName}] 타입체크 실패`, { errorCount: result.errorCount });
260
+ }
267
261
  } catch (err) {
268
- // Worker 오류 로깅 및 결과로 변환
269
262
  logger.error(`Worker 오류: ${task.displayName}`, {
270
263
  error: err instanceof Error ? err.message : String(err),
271
264
  });
@@ -279,40 +272,14 @@ export async function runTypecheck(options: TypecheckOptions): Promise<void> {
279
272
  warningCount: 0,
280
273
  },
281
274
  });
282
- } finally {
283
- // 성공/실패 모두 task 완료 처리
284
- taskResolvers.get(task.displayName)?.();
285
275
  }
286
276
  }
287
277
  }
288
278
 
289
- // listr2로 진행 상황 표시
290
- const listr = new Listr(
291
- tasks.map((task) => ({
292
- title: task.displayName,
293
- task: () =>
294
- new Promise<void>((resolve) => {
295
- taskResolvers.set(task.displayName, resolve);
296
- }),
297
- })),
298
- {
299
- concurrent: concurrency,
300
- exitOnError: false,
301
- renderer: consola.level >= LogLevels.debug ? "verbose" : "default",
302
- },
303
- );
304
-
305
- // 병렬로 모든 worker 실행
306
- const workerPromises = workers.map((worker) => runNextTask(worker));
307
-
308
- // listr와 worker 동시 실행
309
- await Promise.all([listr.run(), ...workerPromises]);
279
+ logger.start(`타입체크 실행 중... (${tasks.length}개 대상, 동시성: ${concurrency})`);
280
+ await Promise.all(workers.map((worker) => runNextTask(worker)));
281
+ logger.success("타입체크 실행 완료");
310
282
  } finally {
311
- // 미해결 resolver 정리 (타임아웃/비정상 종료 대비)
312
- for (const resolver of taskResolvers.values()) {
313
- resolver();
314
- }
315
- // Worker 종료 (성공/실패 모두)
316
283
  await Promise.all(workers.map((w) => w.terminate()));
317
284
  }
318
285
 
package/src/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from "./sd-config.types";
2
+ export { createViteConfig, type ViteConfigOptions } from "./utils/vite-config";
@@ -5,7 +5,7 @@ export interface BuildResult {
5
5
  name: string;
6
6
  target: string;
7
7
  type: "build" | "dts" | "server" | "capacitor";
8
- status: "pending" | "building" | "success" | "error" | "server";
8
+ status: "pending" | "building" | "success" | "error" | "running";
9
9
  message?: string;
10
10
  port?: number;
11
11
  }
@@ -54,7 +54,7 @@ export class ResultCollector {
54
54
  * 서버 상태인 결과만 조회
55
55
  */
56
56
  getServers(): BuildResult[] {
57
- return this.getAll().filter((r) => r.type === "server" && r.status === "server");
57
+ return this.getAll().filter((r) => r.type === "server" && r.status === "running");
58
58
  }
59
59
 
60
60
  /**
@@ -0,0 +1,499 @@
1
+ import path from "path";
2
+ import ts from "typescript";
3
+ import { Worker, type WorkerProxy, fsRm } from "@simplysm/core-node";
4
+ import "@simplysm/core-common";
5
+ import { consola } from "consola";
6
+ import type { SdConfig, SdBuildPackageConfig, SdClientPackageConfig, SdServerPackageConfig } from "../sd-config.types";
7
+ import { loadSdConfig } from "../utils/sd-config";
8
+ import { getVersion } from "../utils/build-env";
9
+ import { setupReplaceDeps } from "../utils/replace-deps";
10
+ import type { TypecheckEnv } from "../utils/tsconfig";
11
+ import { deserializeDiagnostic } from "../utils/typecheck-serialization";
12
+ import { runLint, type LintOptions } from "../commands/lint";
13
+ import type * as LibraryWorkerModule from "../workers/library.worker";
14
+ import type * as ServerWorkerModule from "../workers/server.worker";
15
+ import type * as ClientWorkerModule from "../workers/client.worker";
16
+ import type * as DtsWorkerModule from "../workers/dts.worker";
17
+ import { Capacitor } from "../capacitor/capacitor";
18
+ import { Electron } from "../electron/electron";
19
+ import { copySrcFiles } from "../utils/copy-src";
20
+
21
+ //#region Types
22
+
23
+ /**
24
+ * Build Orchestrator 옵션
25
+ */
26
+ export interface BuildOrchestratorOptions {
27
+ /** 빌드할 패키지 필터 (빈 배열이면 모든 패키지) */
28
+ targets: string[];
29
+ /** sd.config.ts에 전달할 추가 옵션 */
30
+ options: string[];
31
+ }
32
+
33
+ /**
34
+ * 빌드 결과
35
+ */
36
+ interface BuildResult {
37
+ name: string;
38
+ target: string;
39
+ type: "js" | "dts" | "vite" | "capacitor" | "electron";
40
+ success: boolean;
41
+ errors?: string[];
42
+ diagnostics?: ts.Diagnostic[];
43
+ }
44
+
45
+ /**
46
+ * 패키지 분류 결과
47
+ */
48
+ interface ClassifiedPackages {
49
+ /** node/browser/neutral 타겟 (JS + dts) */
50
+ buildPackages: Array<{ name: string; config: SdBuildPackageConfig }>;
51
+ /** client 타겟 (Vite build + typecheck) */
52
+ clientPackages: Array<{ name: string; config: SdClientPackageConfig }>;
53
+ /** server 타겟 (JS 빌드, dts 제외) */
54
+ serverPackages: Array<{ name: string; config: SdServerPackageConfig }>;
55
+ }
56
+
57
+ //#endregion
58
+
59
+ //#region Utilities
60
+
61
+ /**
62
+ * 패키지를 타겟별로 분류
63
+ * - node/browser/neutral: buildPackages (JS + dts)
64
+ * - client: clientPackages (Vite build + typecheck)
65
+ * - server: serverPackages (JS 빌드, dts 제외)
66
+ * - scripts: 제외
67
+ */
68
+ function classifyPackages(
69
+ packages: Record<
70
+ string,
71
+ SdBuildPackageConfig | SdClientPackageConfig | SdServerPackageConfig | { target: "scripts" } | undefined
72
+ >,
73
+ targets: string[],
74
+ ): ClassifiedPackages {
75
+ const buildPackages: ClassifiedPackages["buildPackages"] = [];
76
+ const clientPackages: ClassifiedPackages["clientPackages"] = [];
77
+ const serverPackages: ClassifiedPackages["serverPackages"] = [];
78
+
79
+ for (const [name, config] of Object.entries(packages)) {
80
+ if (config == null) continue;
81
+ if (config.target === "scripts") continue;
82
+
83
+ // targets가 지정되면 해당 패키지만 포함
84
+ if (targets.length > 0 && !targets.includes(name)) continue;
85
+
86
+ if (config.target === "client") {
87
+ clientPackages.push({ name, config });
88
+ } else if (config.target === "server") {
89
+ serverPackages.push({ name, config });
90
+ } else {
91
+ buildPackages.push({ name, config });
92
+ }
93
+ }
94
+
95
+ return { buildPackages, clientPackages, serverPackages };
96
+ }
97
+
98
+ /**
99
+ * dist 폴더 삭제
100
+ */
101
+ async function cleanDistFolders(cwd: string, packageNames: string[]): Promise<void> {
102
+ await Promise.all(packageNames.map((name) => fsRm(path.join(cwd, "packages", name, "dist"))));
103
+ }
104
+
105
+ //#endregion
106
+
107
+ //#region BuildOrchestrator
108
+
109
+ /**
110
+ * 프로덕션 빌드를 조율하는 Orchestrator
111
+ *
112
+ * sd.config.ts 기반으로 패키지를 분류하고, 빌드를 실행한다.
113
+ * - lint 실행
114
+ * - dist 폴더 정리 (clean build)
115
+ * - node/browser/neutral 타겟: esbuild JS 빌드 + dts 생성
116
+ * - client 타겟: Vite production 빌드 + typecheck + Capacitor/Electron 빌드
117
+ * - server 타겟: esbuild JS 빌드
118
+ */
119
+ export class BuildOrchestrator {
120
+ private readonly _cwd: string;
121
+ private readonly _options: BuildOrchestratorOptions;
122
+ private readonly _logger = consola.withTag("sd:cli:build");
123
+
124
+ private _sdConfig: SdConfig | undefined;
125
+ private _classified: ClassifiedPackages | undefined;
126
+ private _allPackageNames: string[] = [];
127
+ private _baseEnv: { VER: string; DEV: string } | undefined;
128
+
129
+ constructor(options: BuildOrchestratorOptions) {
130
+ this._cwd = process.cwd();
131
+ this._options = options;
132
+ }
133
+
134
+ /**
135
+ * Orchestrator 초기화
136
+ * - sd.config.ts 로드
137
+ * - replaceDeps 설정
138
+ * - 패키지 분류
139
+ * - 환경변수 준비
140
+ */
141
+ async initialize(): Promise<void> {
142
+ this._logger.debug("빌드 시작", { targets: this._options.targets });
143
+
144
+ // sd.config.ts 로드
145
+ try {
146
+ this._sdConfig = await loadSdConfig({
147
+ cwd: this._cwd,
148
+ dev: false,
149
+ opt: this._options.options,
150
+ });
151
+ this._logger.debug("sd.config.ts 로드 완료");
152
+ } catch (err) {
153
+ this._logger.error(`sd.config.ts 로드 실패: ${err instanceof Error ? err.message : err}`);
154
+ process.exitCode = 1;
155
+ throw err;
156
+ }
157
+
158
+ // replaceDeps 설정이 있으면 symlink 교체
159
+ if (this._sdConfig.replaceDeps != null) {
160
+ await setupReplaceDeps(this._cwd, this._sdConfig.replaceDeps);
161
+ }
162
+
163
+ // VER, DEV 환경변수 준비
164
+ const version = await getVersion(this._cwd);
165
+ this._baseEnv = { VER: version, DEV: "false" };
166
+
167
+ // 패키지 분류
168
+ this._classified = classifyPackages(this._sdConfig.packages, this._options.targets);
169
+ this._allPackageNames = [
170
+ ...this._classified.buildPackages.map((p) => p.name),
171
+ ...this._classified.clientPackages.map((p) => p.name),
172
+ ...this._classified.serverPackages.map((p) => p.name),
173
+ ];
174
+
175
+ if (this._allPackageNames.length === 0) {
176
+ process.stdout.write("✔ 빌드할 패키지가 없습니다.\n");
177
+ return;
178
+ }
179
+
180
+ this._logger.debug("패키지 분류 완료", {
181
+ buildPackages: this._classified.buildPackages.map((p) => p.name),
182
+ clientPackages: this._classified.clientPackages.map((p) => p.name),
183
+ serverPackages: this._classified.serverPackages.map((p) => p.name),
184
+ });
185
+ }
186
+
187
+ /**
188
+ * 빌드 실행
189
+ * - Lint
190
+ * - Clean
191
+ * - Build (concurrent)
192
+ * - 결과 출력
193
+ *
194
+ * @returns 에러 여부 (true: 에러 있음)
195
+ */
196
+ async start(): Promise<boolean> {
197
+ if (this._allPackageNames.length === 0) {
198
+ return false;
199
+ }
200
+
201
+ const classified = this._classified!;
202
+ const baseEnv = this._baseEnv!;
203
+
204
+ // 결과 수집
205
+ const results: BuildResult[] = [];
206
+ // 에러 추적 (객체로 래핑하여 콜백 내 수정 추적 가능하게 함)
207
+ const state = { hasError: false };
208
+
209
+ // Worker 경로
210
+ const libraryWorkerPath = import.meta.resolve("../workers/library.worker");
211
+ const serverWorkerPath = import.meta.resolve("../workers/server.worker");
212
+ const clientWorkerPath = import.meta.resolve("../workers/client.worker");
213
+ const dtsWorkerPath = import.meta.resolve("../workers/dts.worker");
214
+
215
+ // 파일 캐시 (diagnostics 출력용)
216
+ const fileCache = new Map<string, string>();
217
+
218
+ // formatHost (diagnostics 출력용)
219
+ const formatHost: ts.FormatDiagnosticsHost = {
220
+ getCanonicalFileName: (f) => f,
221
+ getCurrentDirectory: () => this._cwd,
222
+ getNewLine: () => ts.sys.newLine,
223
+ };
224
+
225
+ // Lint 옵션 (전체 패키지 대상)
226
+ const lintOptions: LintOptions = {
227
+ targets: this._allPackageNames.map((name) => `packages/${name}`),
228
+ fix: false,
229
+ timing: false,
230
+ };
231
+
232
+ // Phase 1: Lint
233
+ this._logger.start("Lint");
234
+ await runLint(lintOptions);
235
+ // lint 에러가 있으면 process.exitCode가 1로 설정됨
236
+ if (process.exitCode === 1) {
237
+ state.hasError = true;
238
+ }
239
+ this._logger.success("Lint");
240
+
241
+ // Phase 2: Clean
242
+ this._logger.start("Clean");
243
+ await cleanDistFolders(this._cwd, this._allPackageNames);
244
+ this._logger.success("Clean");
245
+
246
+ // Phase 3: Build (concurrent)
247
+ this._logger.start("Build");
248
+
249
+ // 빌드 작업 목록 생성
250
+ const buildTasks: Array<() => Promise<void>> = [];
251
+
252
+ // buildPackages: JS 빌드 + dts 생성
253
+ for (const { name, config } of classified.buildPackages) {
254
+ const pkgDir = path.join(this._cwd, "packages", name);
255
+ const env: TypecheckEnv = config.target === "node" ? "node" : "browser";
256
+
257
+ buildTasks.push(async () => {
258
+ this._logger.debug(`${name} (${config.target}) 시작`);
259
+ // JS 빌드와 DTS 생성을 병렬 실행
260
+ const libraryWorker: WorkerProxy<typeof LibraryWorkerModule> =
261
+ Worker.create<typeof LibraryWorkerModule>(libraryWorkerPath);
262
+ const dtsWorker: WorkerProxy<typeof DtsWorkerModule> = Worker.create<typeof DtsWorkerModule>(dtsWorkerPath);
263
+
264
+ try {
265
+ const [buildResult, dtsResult] = await Promise.all([
266
+ // JS 빌드
267
+ libraryWorker.build({ name, config, cwd: this._cwd, pkgDir }),
268
+ // DTS 생성
269
+ dtsWorker.buildDts({ name, cwd: this._cwd, pkgDir, env, emit: true }),
270
+ ]);
271
+
272
+ // JS 빌드 결과 처리
273
+ results.push({
274
+ name,
275
+ target: config.target,
276
+ type: "js",
277
+ success: buildResult.success,
278
+ errors: buildResult.errors,
279
+ });
280
+ if (!buildResult.success) state.hasError = true;
281
+
282
+ // DTS 결과 처리
283
+ const diagnostics = dtsResult.diagnostics.map((d) => deserializeDiagnostic(d, fileCache));
284
+ results.push({
285
+ name,
286
+ target: config.target,
287
+ type: "dts",
288
+ success: dtsResult.success,
289
+ errors: dtsResult.errors,
290
+ diagnostics,
291
+ });
292
+ if (!dtsResult.success) state.hasError = true;
293
+ } finally {
294
+ await Promise.all([libraryWorker.terminate(), dtsWorker.terminate()]);
295
+ }
296
+
297
+ // copySrc 파일 복사
298
+ if (config.copySrc != null && config.copySrc.length > 0) {
299
+ await copySrcFiles(pkgDir, config.copySrc);
300
+ }
301
+ this._logger.debug(`${name} (${config.target}) 완료`);
302
+ });
303
+ }
304
+
305
+ // clientPackages: Vite 빌드 + typecheck + Capacitor 빌드
306
+ for (const { name, config } of classified.clientPackages) {
307
+ const pkgDir = path.join(this._cwd, "packages", name);
308
+
309
+ buildTasks.push(async () => {
310
+ this._logger.debug(`${name} (client) 시작`);
311
+ // Vite 빌드와 타입체크를 병렬 실행
312
+ const clientWorker: WorkerProxy<typeof ClientWorkerModule> =
313
+ Worker.create<typeof ClientWorkerModule>(clientWorkerPath);
314
+ const dtsWorker: WorkerProxy<typeof DtsWorkerModule> = Worker.create<typeof DtsWorkerModule>(dtsWorkerPath);
315
+
316
+ try {
317
+ const clientConfig: SdClientPackageConfig = {
318
+ ...config,
319
+ env: { ...baseEnv, ...config.env },
320
+ };
321
+ const [clientResult, dtsResult] = await Promise.all([
322
+ // Vite production 빌드
323
+ clientWorker.build({ name, config: clientConfig, cwd: this._cwd, pkgDir }),
324
+ // typecheck (dts 없이)
325
+ dtsWorker.buildDts({
326
+ name,
327
+ cwd: this._cwd,
328
+ pkgDir,
329
+ env: "browser",
330
+ emit: false,
331
+ }),
332
+ ]);
333
+
334
+ // Vite 빌드 결과 처리
335
+ results.push({
336
+ name,
337
+ target: "client",
338
+ type: "vite",
339
+ success: clientResult.success,
340
+ errors: clientResult.errors,
341
+ });
342
+ if (!clientResult.success) state.hasError = true;
343
+
344
+ // 타입체크 결과 처리
345
+ const diagnostics = dtsResult.diagnostics.map((d) => deserializeDiagnostic(d, fileCache));
346
+ results.push({
347
+ name,
348
+ target: "client",
349
+ type: "dts",
350
+ success: dtsResult.success,
351
+ errors: dtsResult.errors,
352
+ diagnostics,
353
+ });
354
+ if (!dtsResult.success) state.hasError = true;
355
+ } finally {
356
+ await Promise.all([clientWorker.terminate(), dtsWorker.terminate()]);
357
+ }
358
+
359
+ // Capacitor 빌드 (설정이 있는 경우만)
360
+ if (config.capacitor != null) {
361
+ const outPath = path.join(pkgDir, "dist");
362
+ try {
363
+ const cap = await Capacitor.create(pkgDir, config.capacitor);
364
+ await cap.initialize();
365
+ await cap.build(outPath);
366
+ results.push({
367
+ name,
368
+ target: "client",
369
+ type: "capacitor",
370
+ success: true,
371
+ });
372
+ } catch (err) {
373
+ results.push({
374
+ name,
375
+ target: "client",
376
+ type: "capacitor",
377
+ success: false,
378
+ errors: [err instanceof Error ? err.message : String(err)],
379
+ });
380
+ state.hasError = true;
381
+ }
382
+ }
383
+
384
+ // Electron 빌드 (설정이 있는 경우만)
385
+ if (config.electron != null) {
386
+ const outPath = path.join(pkgDir, "dist");
387
+ try {
388
+ const electron = await Electron.create(pkgDir, config.electron);
389
+ await electron.initialize();
390
+ await electron.build(outPath);
391
+ results.push({
392
+ name,
393
+ target: "client",
394
+ type: "electron",
395
+ success: true,
396
+ });
397
+ } catch (err) {
398
+ results.push({
399
+ name,
400
+ target: "client",
401
+ type: "electron",
402
+ success: false,
403
+ errors: [err instanceof Error ? err.message : String(err)],
404
+ });
405
+ state.hasError = true;
406
+ }
407
+ }
408
+ this._logger.debug(`${name} (client) 완료`);
409
+ });
410
+ }
411
+
412
+ // serverPackages: JS 빌드만 (dts 생성 제외)
413
+ for (const { name, config } of classified.serverPackages) {
414
+ const pkgDir = path.join(this._cwd, "packages", name);
415
+
416
+ buildTasks.push(async () => {
417
+ this._logger.debug(`${name} (server) 시작`);
418
+ const serverWorker: WorkerProxy<typeof ServerWorkerModule> =
419
+ Worker.create<typeof ServerWorkerModule>(serverWorkerPath);
420
+
421
+ try {
422
+ const buildResult = await serverWorker.build({
423
+ name,
424
+ cwd: this._cwd,
425
+ pkgDir,
426
+ env: { ...baseEnv, ...config.env },
427
+ configs: config.configs,
428
+ externals: config.externals,
429
+ pm2: config.pm2,
430
+ packageManager: config.packageManager,
431
+ });
432
+
433
+ results.push({
434
+ name,
435
+ target: "server",
436
+ type: "js",
437
+ success: buildResult.success,
438
+ errors: buildResult.errors,
439
+ });
440
+ if (!buildResult.success) state.hasError = true;
441
+ } finally {
442
+ await serverWorker.terminate();
443
+ }
444
+ this._logger.debug(`${name} (server) 완료`);
445
+ });
446
+ }
447
+
448
+ // 모든 빌드를 병렬 실행
449
+ await Promise.allSettled(buildTasks.map((task) => task()));
450
+ this._logger.success("Build");
451
+
452
+ // 결과 출력
453
+ const allDiagnostics: ts.Diagnostic[] = [];
454
+ for (const result of results) {
455
+ if (!result.success) {
456
+ const typeLabel = result.type === "dts" ? "dts" : result.target;
457
+ const errorLines: string[] = [`${result.name} (${typeLabel})`];
458
+ if (result.errors != null) {
459
+ for (const error of result.errors) {
460
+ for (const line of error.split("\n")) {
461
+ errorLines.push(` → ${line}`);
462
+ }
463
+ }
464
+ }
465
+ this._logger.error(errorLines.join("\n"));
466
+ }
467
+ if (result.diagnostics != null) {
468
+ allDiagnostics.push(...result.diagnostics);
469
+ }
470
+ }
471
+
472
+ // diagnostics 출력 (중복 제거)
473
+ if (allDiagnostics.length > 0) {
474
+ const uniqueDiagnostics = ts.sortAndDeduplicateDiagnostics(allDiagnostics);
475
+ const message = ts.formatDiagnosticsWithColorAndContext(uniqueDiagnostics, formatHost);
476
+ process.stdout.write(message);
477
+ }
478
+
479
+ // 결과 로그 출력
480
+ if (state.hasError) {
481
+ this._logger.error("빌드 실패");
482
+ } else {
483
+ this._logger.info("빌드 완료");
484
+ }
485
+
486
+ return state.hasError;
487
+ }
488
+
489
+ /**
490
+ * Orchestrator 종료 (현재는 정리할 리소스 없음)
491
+ */
492
+ async shutdown(): Promise<void> {
493
+ // 프로덕션 빌드는 일회성이므로 종료 시 정리할 리소스가 없음
494
+ // Worker는 각 빌드 태스크 내에서 terminate()로 정리됨
495
+ await Promise.resolve();
496
+ }
497
+ }
498
+
499
+ //#endregion