@simplysm/sd-cli 13.0.68 → 13.0.70

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 (201) hide show
  1. package/README.md +10 -957
  2. package/dist/builders/BaseBuilder.d.ts +23 -23
  3. package/dist/builders/BaseBuilder.d.ts.map +1 -1
  4. package/dist/builders/BaseBuilder.js +15 -15
  5. package/dist/builders/DtsBuilder.d.ts +4 -4
  6. package/dist/builders/DtsBuilder.js +1 -1
  7. package/dist/builders/LibraryBuilder.d.ts +3 -3
  8. package/dist/builders/types.d.ts +10 -10
  9. package/dist/capacitor/capacitor.d.ts +36 -36
  10. package/dist/capacitor/capacitor.js +63 -63
  11. package/dist/capacitor/capacitor.js.map +1 -1
  12. package/dist/commands/add-client.d.ts +8 -8
  13. package/dist/commands/add-client.js +15 -15
  14. package/dist/commands/add-client.js.map +1 -1
  15. package/dist/commands/add-server.d.ts +9 -9
  16. package/dist/commands/add-server.js +13 -13
  17. package/dist/commands/add-server.js.map +1 -1
  18. package/dist/commands/build.d.ts +9 -9
  19. package/dist/commands/check.js +3 -3
  20. package/dist/commands/check.js.map +1 -1
  21. package/dist/commands/dev.d.ts +9 -9
  22. package/dist/commands/device.d.ts +9 -9
  23. package/dist/commands/device.d.ts.map +1 -1
  24. package/dist/commands/device.js +17 -17
  25. package/dist/commands/device.js.map +1 -1
  26. package/dist/commands/init.d.ts +6 -6
  27. package/dist/commands/init.js +12 -12
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/lint.d.ts +23 -23
  30. package/dist/commands/lint.d.ts.map +1 -1
  31. package/dist/commands/lint.js +25 -25
  32. package/dist/commands/lint.js.map +1 -1
  33. package/dist/commands/publish.d.ts +13 -13
  34. package/dist/commands/publish.d.ts.map +1 -1
  35. package/dist/commands/publish.js +61 -61
  36. package/dist/commands/publish.js.map +1 -1
  37. package/dist/commands/replace-deps.d.ts +3 -3
  38. package/dist/commands/replace-deps.d.ts.map +1 -1
  39. package/dist/commands/replace-deps.js +1 -1
  40. package/dist/commands/replace-deps.js.map +1 -1
  41. package/dist/commands/typecheck.d.ts +20 -20
  42. package/dist/commands/typecheck.d.ts.map +1 -1
  43. package/dist/commands/typecheck.js +20 -20
  44. package/dist/commands/typecheck.js.map +1 -1
  45. package/dist/commands/watch.d.ts +7 -7
  46. package/dist/electron/electron.d.ts +27 -27
  47. package/dist/electron/electron.js +32 -32
  48. package/dist/electron/electron.js.map +1 -1
  49. package/dist/infra/ResultCollector.d.ts +9 -9
  50. package/dist/infra/ResultCollector.js +5 -5
  51. package/dist/infra/SignalHandler.d.ts +7 -7
  52. package/dist/infra/SignalHandler.js +4 -4
  53. package/dist/infra/WorkerManager.d.ts +14 -14
  54. package/dist/infra/WorkerManager.js +11 -11
  55. package/dist/orchestrators/BuildOrchestrator.d.ts +19 -19
  56. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  57. package/dist/orchestrators/BuildOrchestrator.js +26 -26
  58. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  59. package/dist/orchestrators/DevOrchestrator.d.ts +25 -25
  60. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
  61. package/dist/orchestrators/DevOrchestrator.js +30 -30
  62. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  63. package/dist/orchestrators/WatchOrchestrator.d.ts +13 -13
  64. package/dist/orchestrators/WatchOrchestrator.js +17 -17
  65. package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
  66. package/dist/sd-cli-entry.d.ts +2 -2
  67. package/dist/sd-cli-entry.js +38 -38
  68. package/dist/sd-cli-entry.js.map +1 -1
  69. package/dist/sd-cli.d.ts +2 -2
  70. package/dist/sd-cli.js +1 -1
  71. package/dist/sd-cli.js.map +1 -1
  72. package/dist/sd-config.types.d.ts +84 -84
  73. package/dist/sd-config.types.d.ts.map +1 -1
  74. package/dist/utils/build-env.d.ts +1 -1
  75. package/dist/utils/config-editor.d.ts +5 -5
  76. package/dist/utils/config-editor.js +2 -2
  77. package/dist/utils/config-editor.js.map +1 -1
  78. package/dist/utils/copy-public.d.ts +9 -9
  79. package/dist/utils/copy-src.d.ts +9 -9
  80. package/dist/utils/esbuild-config.d.ts +30 -30
  81. package/dist/utils/esbuild-config.d.ts.map +1 -1
  82. package/dist/utils/output-utils.d.ts +6 -6
  83. package/dist/utils/package-utils.d.ts +6 -6
  84. package/dist/utils/package-utils.js +1 -1
  85. package/dist/utils/package-utils.js.map +1 -1
  86. package/dist/utils/rebuild-manager.js +3 -3
  87. package/dist/utils/rebuild-manager.js.map +1 -1
  88. package/dist/utils/replace-deps.d.ts +25 -25
  89. package/dist/utils/replace-deps.js +3 -3
  90. package/dist/utils/replace-deps.js.map +1 -1
  91. package/dist/utils/sd-config.d.ts +3 -3
  92. package/dist/utils/sd-config.js +3 -3
  93. package/dist/utils/sd-config.js.map +1 -1
  94. package/dist/utils/tailwind-config-deps.d.ts +3 -3
  95. package/dist/utils/template.d.ts +8 -8
  96. package/dist/utils/tsconfig.d.ts +16 -16
  97. package/dist/utils/tsconfig.js +2 -2
  98. package/dist/utils/tsconfig.js.map +1 -1
  99. package/dist/utils/typecheck-serialization.d.ts +8 -8
  100. package/dist/utils/vite-config.d.ts +8 -8
  101. package/dist/utils/vite-config.d.ts.map +1 -1
  102. package/dist/utils/vite-config.js +3 -3
  103. package/dist/utils/worker-events.d.ts +12 -12
  104. package/dist/utils/worker-events.d.ts.map +1 -1
  105. package/dist/utils/worker-utils.d.ts +3 -3
  106. package/dist/utils/worker-utils.js +2 -2
  107. package/dist/utils/worker-utils.js.map +1 -1
  108. package/dist/workers/client.worker.d.ts +14 -14
  109. package/dist/workers/client.worker.d.ts.map +1 -1
  110. package/dist/workers/client.worker.js +1 -1
  111. package/dist/workers/client.worker.js.map +1 -1
  112. package/dist/workers/dts.worker.d.ts +13 -13
  113. package/dist/workers/dts.worker.d.ts.map +1 -1
  114. package/dist/workers/dts.worker.js +3 -3
  115. package/dist/workers/dts.worker.js.map +1 -1
  116. package/dist/workers/library.worker.d.ts +12 -12
  117. package/dist/workers/library.worker.js +1 -1
  118. package/dist/workers/library.worker.js.map +1 -1
  119. package/dist/workers/lint.worker.d.ts +1 -1
  120. package/dist/workers/server-runtime.worker.d.ts +6 -6
  121. package/dist/workers/server-runtime.worker.js +6 -6
  122. package/dist/workers/server-runtime.worker.js.map +1 -1
  123. package/dist/workers/server.worker.d.ts +20 -20
  124. package/dist/workers/server.worker.d.ts.map +1 -1
  125. package/dist/workers/server.worker.js +6 -6
  126. package/dist/workers/server.worker.js.map +1 -1
  127. package/package.json +8 -7
  128. package/src/builders/BaseBuilder.ts +33 -33
  129. package/src/builders/DtsBuilder.ts +5 -5
  130. package/src/builders/LibraryBuilder.ts +9 -9
  131. package/src/builders/types.ts +10 -10
  132. package/src/capacitor/capacitor.ts +119 -119
  133. package/src/commands/add-client.ts +31 -31
  134. package/src/commands/add-server.ts +34 -34
  135. package/src/commands/build.ts +9 -9
  136. package/src/commands/check.ts +5 -5
  137. package/src/commands/dev.ts +9 -9
  138. package/src/commands/device.ts +30 -30
  139. package/src/commands/init.ts +25 -25
  140. package/src/commands/lint.ts +64 -64
  141. package/src/commands/publish.ts +139 -139
  142. package/src/commands/replace-deps.ts +4 -4
  143. package/src/commands/typecheck.ts +74 -74
  144. package/src/commands/watch.ts +7 -7
  145. package/src/electron/electron.ts +51 -51
  146. package/src/infra/ResultCollector.ts +9 -9
  147. package/src/infra/SignalHandler.ts +7 -7
  148. package/src/infra/WorkerManager.ts +14 -14
  149. package/src/orchestrators/BuildOrchestrator.ts +76 -76
  150. package/src/orchestrators/DevOrchestrator.ts +88 -88
  151. package/src/orchestrators/WatchOrchestrator.ts +39 -39
  152. package/src/sd-cli-entry.ts +43 -43
  153. package/src/sd-cli.ts +15 -15
  154. package/src/sd-config.types.ts +85 -85
  155. package/src/utils/build-env.ts +1 -1
  156. package/src/utils/config-editor.ts +19 -19
  157. package/src/utils/copy-public.ts +17 -17
  158. package/src/utils/copy-src.ts +11 -11
  159. package/src/utils/esbuild-config.ts +33 -33
  160. package/src/utils/output-utils.ts +11 -11
  161. package/src/utils/package-utils.ts +12 -12
  162. package/src/utils/rebuild-manager.ts +3 -3
  163. package/src/utils/replace-deps.ts +361 -361
  164. package/src/utils/sd-config.ts +44 -44
  165. package/src/utils/tailwind-config-deps.ts +98 -98
  166. package/src/utils/template.ts +56 -56
  167. package/src/utils/tsconfig.ts +127 -127
  168. package/src/utils/typecheck-serialization.ts +86 -86
  169. package/src/utils/vite-config.ts +341 -341
  170. package/src/utils/worker-events.ts +16 -16
  171. package/src/utils/worker-utils.ts +45 -45
  172. package/src/workers/client.worker.ts +34 -34
  173. package/src/workers/dts.worker.ts +467 -467
  174. package/src/workers/library.worker.ts +314 -314
  175. package/src/workers/lint.worker.ts +16 -16
  176. package/src/workers/server-runtime.worker.ts +157 -157
  177. package/src/workers/server.worker.ts +572 -572
  178. package/templates/add-client/__CLIENT__/package.json.hbs +1 -1
  179. package/templates/add-server/__SERVER__/package.json.hbs +2 -2
  180. package/templates/init/package.json.hbs +3 -3
  181. package/tests/config-editor.spec.ts +160 -0
  182. package/tests/copy-src.spec.ts +50 -0
  183. package/tests/get-compiler-options-for-package.spec.ts +139 -0
  184. package/tests/get-package-source-files.spec.ts +181 -0
  185. package/tests/get-types-from-package-json.spec.ts +107 -0
  186. package/tests/infra/ResultCollector.spec.ts +39 -0
  187. package/tests/infra/SignalHandler.spec.ts +38 -0
  188. package/tests/infra/WorkerManager.spec.ts +97 -0
  189. package/tests/load-ignore-patterns.spec.ts +188 -0
  190. package/tests/load-sd-config.spec.ts +137 -0
  191. package/tests/package-utils.spec.ts +188 -0
  192. package/tests/parse-root-tsconfig.spec.ts +89 -0
  193. package/tests/replace-deps.spec.ts +308 -0
  194. package/tests/run-lint.spec.ts +415 -0
  195. package/tests/run-typecheck.spec.ts +653 -0
  196. package/tests/run-watch.spec.ts +75 -0
  197. package/tests/sd-cli.spec.ts +330 -0
  198. package/tests/tailwind-config-deps.spec.ts +30 -0
  199. package/tests/template.spec.ts +70 -0
  200. package/tests/utils/rebuild-manager.spec.ts +43 -0
  201. package/tests/write-changed-output-files.spec.ts +97 -0
@@ -1,314 +1,314 @@
1
- import path from "path";
2
- import esbuild from "esbuild";
3
- import { createWorker, FsWatcher } from "@simplysm/core-node";
4
- import { errorMessage } from "@simplysm/core-common";
5
- import { consola } from "consola";
6
- import type { SdBuildPackageConfig } from "../sd-config.types";
7
- import {
8
- parseRootTsconfig,
9
- getPackageSourceFiles,
10
- getCompilerOptionsForPackage,
11
- } from "../utils/tsconfig";
12
- import {
13
- createLibraryEsbuildOptions,
14
- getTypecheckEnvFromTarget,
15
- writeChangedOutputFiles,
16
- } from "../utils/esbuild-config";
17
- import { registerCleanupHandlers, createOnceGuard } from "../utils/worker-utils";
18
-
19
- //#region Types
20
-
21
- /**
22
- * Library 빌드 정보 (일회성 빌드용)
23
- */
24
- export interface LibraryBuildInfo {
25
- name: string;
26
- config: SdBuildPackageConfig;
27
- cwd: string;
28
- pkgDir: string;
29
- }
30
-
31
- /**
32
- * Library 빌드 결과
33
- */
34
- export interface LibraryBuildResult {
35
- success: boolean;
36
- errors?: string[];
37
- warnings?: string[];
38
- }
39
-
40
- /**
41
- * Library Watch 정보
42
- */
43
- export interface LibraryWatchInfo {
44
- name: string;
45
- config: SdBuildPackageConfig;
46
- cwd: string;
47
- pkgDir: string;
48
- }
49
-
50
- /**
51
- * 빌드 이벤트
52
- */
53
- export interface LibraryBuildEvent {
54
- success: boolean;
55
- errors?: string[];
56
- warnings?: string[];
57
- }
58
-
59
- /**
60
- * 에러 이벤트
61
- */
62
- export interface LibraryErrorEvent {
63
- message: string;
64
- }
65
-
66
- /**
67
- * Worker 이벤트 타입
68
- */
69
- export interface LibraryWorkerEvents extends Record<string, unknown> {
70
- buildStart: Record<string, never>;
71
- build: LibraryBuildEvent;
72
- error: LibraryErrorEvent;
73
- }
74
-
75
- //#endregion
76
-
77
- //#region 리소스 관리
78
-
79
- const logger = consola.withTag("sd:cli:library:worker");
80
-
81
- /** esbuild build context (정리 대상) */
82
- let esbuildContext: esbuild.BuildContext | undefined;
83
-
84
- /** FsWatcher (정리 대상) */
85
- let fsWatcher: FsWatcher | undefined;
86
-
87
- /**
88
- * 리소스 정리
89
- */
90
- async function cleanup(): Promise<void> {
91
- // 전역 변수를 임시 변수로 캡처 초기화
92
- // (Promise.all 대기 다른 호출에서 전역 변수를 수정할 있으므로)
93
- const contextToDispose = esbuildContext;
94
- esbuildContext = undefined;
95
-
96
- const watcherToClose = fsWatcher;
97
- fsWatcher = undefined;
98
-
99
- await Promise.all([contextToDispose?.dispose(), watcherToClose?.close()]);
100
- }
101
-
102
- // 프로세스 종료 리소스 정리 (SIGTERM/SIGINT)
103
- // 주의: worker.terminate() 핸들러들을 호출하지 않고 즉시 종료됨.
104
- // 그러나 watch 모드에서 정상 종료는 메인 프로세스의 SIGINT/SIGTERM 통해 이루어지므로 문제없음.
105
- registerCleanupHandlers(cleanup, logger);
106
-
107
- //#endregion
108
-
109
- //#region Worker
110
-
111
- /**
112
- * 일회성 빌드
113
- */
114
- async function build(info: LibraryBuildInfo): Promise<LibraryBuildResult> {
115
- try {
116
- // tsconfig 파싱
117
- const parsedConfig = parseRootTsconfig(info.cwd);
118
- const entryPoints = getPackageSourceFiles(info.pkgDir, parsedConfig);
119
-
120
- // 타겟별 compilerOptions 생성
121
- const env = getTypecheckEnvFromTarget(info.config.target);
122
- const compilerOptions = await getCompilerOptionsForPackage(
123
- parsedConfig.options,
124
- env,
125
- info.pkgDir,
126
- );
127
-
128
- // esbuild 일회성 빌드
129
- const esbuildOptions = createLibraryEsbuildOptions({
130
- pkgDir: info.pkgDir,
131
- entryPoints,
132
- target: info.config.target,
133
- compilerOptions,
134
- });
135
-
136
- const result = await esbuild.build(esbuildOptions);
137
- if (result.outputFiles) {
138
- await writeChangedOutputFiles(result.outputFiles);
139
- }
140
- const errors = result.errors.map((e) => e.text);
141
- const warnings = result.warnings.map((w) => w.text);
142
- return {
143
- success: result.errors.length === 0,
144
- errors: errors.length > 0 ? errors : undefined,
145
- warnings: warnings.length > 0 ? warnings : undefined,
146
- };
147
- } catch (err) {
148
- return {
149
- success: false,
150
- errors: [errorMessage(err)],
151
- };
152
- }
153
- }
154
-
155
- const guardStartWatch = createOnceGuard("startWatch");
156
-
157
- /**
158
- * esbuild context 생성 초기 빌드 수행
159
- */
160
- async function createAndBuildContext(
161
- pkgDir: string,
162
- cwd: string,
163
- config: SdBuildPackageConfig,
164
- isFirstBuild: boolean,
165
- resolveFirstBuild?: () => void,
166
- ): Promise<esbuild.BuildContext> {
167
- // tsconfig 파싱
168
- const parsedConfig = parseRootTsconfig(cwd);
169
- const entryPoints = getPackageSourceFiles(pkgDir, parsedConfig);
170
-
171
- // 타겟별 compilerOptions 생성
172
- const env = getTypecheckEnvFromTarget(config.target);
173
- const compilerOptions = await getCompilerOptionsForPackage(parsedConfig.options, env, pkgDir);
174
-
175
- // esbuild 옵션 생성
176
- const baseOptions = createLibraryEsbuildOptions({
177
- pkgDir,
178
- entryPoints,
179
- target: config.target,
180
- compilerOptions,
181
- });
182
-
183
- let isBuildFirstTime = isFirstBuild;
184
-
185
- // context 생성 + watch-notify 플러그인 추가
186
- const context = await esbuild.context({
187
- ...baseOptions,
188
- plugins: [
189
- ...(baseOptions.plugins ?? []),
190
- {
191
- name: "watch-notify",
192
- setup(pluginBuild) {
193
- pluginBuild.onStart(() => {
194
- sender.send("buildStart", {});
195
- });
196
-
197
- pluginBuild.onEnd(async (result) => {
198
- // Write only changed files to disk
199
- if (result.outputFiles) {
200
- await writeChangedOutputFiles(result.outputFiles);
201
- }
202
-
203
- const errors = result.errors.map((e) => e.text);
204
- const warnings = result.warnings.map((w) => w.text);
205
- const success = result.errors.length === 0;
206
-
207
- sender.send("build", {
208
- success,
209
- errors: errors.length > 0 ? errors : undefined,
210
- warnings: warnings.length > 0 ? warnings : undefined,
211
- });
212
-
213
- if (isBuildFirstTime) {
214
- isBuildFirstTime = false;
215
- resolveFirstBuild?.();
216
- }
217
- });
218
- },
219
- },
220
- ],
221
- });
222
-
223
- // 초기 빌드
224
- await context.rebuild();
225
-
226
- return context;
227
- }
228
-
229
- /**
230
- * watch 시작
231
- * @remarks 함수는 Worker당 번만 호출되어야 합니다.
232
- * @throws 이미 watch 시작된 경우
233
- */
234
- async function startWatch(info: LibraryWatchInfo): Promise<void> {
235
- guardStartWatch();
236
-
237
- try {
238
- // 번째 빌드 완료 대기를 위한 Promise
239
- let resolveFirstBuild!: () => void;
240
- const firstBuildPromise = new Promise<void>((resolve) => {
241
- resolveFirstBuild = resolve;
242
- });
243
-
244
- // 초기 esbuild context 생성 및 빌드
245
- esbuildContext = await createAndBuildContext(
246
- info.pkgDir,
247
- info.cwd,
248
- info.config,
249
- true,
250
- resolveFirstBuild,
251
- );
252
-
253
- // 번째 빌드 완료 대기
254
- await firstBuildPromise;
255
-
256
- // FsWatcher 시작 (src/**/*.{ts,tsx} 감시)
257
- const srcPattern = path.join(info.pkgDir, "src", "**", "*.{ts,tsx}");
258
- fsWatcher = await FsWatcher.watch([srcPattern]);
259
-
260
- // 파일 변경 감지 시 처리
261
- fsWatcher.onChange({ delay: 300 }, async (changes) => {
262
- try {
263
- // add 또는 unlink 이벤트가 있는지 확인
264
- const hasAddOrUnlink = changes.some((c) => c.event === "add" || c.event === "unlink");
265
-
266
- if (hasAddOrUnlink) {
267
- // entry points 변경이 있으므로 context 재생성
268
- logger.debug("파일 추가/삭제 감지, context 재생성");
269
-
270
- const oldContext = esbuildContext;
271
- esbuildContext = await createAndBuildContext(info.pkgDir, info.cwd, info.config, false);
272
-
273
- if (oldContext != null) {
274
- await oldContext.dispose();
275
- }
276
- } else {
277
- // 파일 내용만 변경 (change 이벤트)
278
- if (esbuildContext != null) {
279
- await esbuildContext.rebuild();
280
- }
281
- }
282
- } catch (err) {
283
- sender.send("error", {
284
- message: errorMessage(err),
285
- });
286
- }
287
- });
288
- } catch (err) {
289
- sender.send("error", {
290
- message: errorMessage(err),
291
- });
292
- }
293
- }
294
-
295
- /**
296
- * watch 중지
297
- * @remarks esbuild context를 정리합니다.
298
- */
299
- async function stopWatch(): Promise<void> {
300
- await cleanup();
301
- }
302
-
303
- const sender = createWorker<
304
- { build: typeof build; startWatch: typeof startWatch; stopWatch: typeof stopWatch },
305
- LibraryWorkerEvents
306
- >({
307
- build,
308
- startWatch,
309
- stopWatch,
310
- });
311
-
312
- export default sender;
313
-
314
- //#endregion
1
+ import path from "path";
2
+ import esbuild from "esbuild";
3
+ import { createWorker, FsWatcher } from "@simplysm/core-node";
4
+ import { errorMessage } from "@simplysm/core-common";
5
+ import { consola } from "consola";
6
+ import type { SdBuildPackageConfig } from "../sd-config.types";
7
+ import {
8
+ parseRootTsconfig,
9
+ getPackageSourceFiles,
10
+ getCompilerOptionsForPackage,
11
+ } from "../utils/tsconfig";
12
+ import {
13
+ createLibraryEsbuildOptions,
14
+ getTypecheckEnvFromTarget,
15
+ writeChangedOutputFiles,
16
+ } from "../utils/esbuild-config";
17
+ import { registerCleanupHandlers, createOnceGuard } from "../utils/worker-utils";
18
+
19
+ //#region Types
20
+
21
+ /**
22
+ * Library build info (for one-time build)
23
+ */
24
+ export interface LibraryBuildInfo {
25
+ name: string;
26
+ config: SdBuildPackageConfig;
27
+ cwd: string;
28
+ pkgDir: string;
29
+ }
30
+
31
+ /**
32
+ * Library build result
33
+ */
34
+ export interface LibraryBuildResult {
35
+ success: boolean;
36
+ errors?: string[];
37
+ warnings?: string[];
38
+ }
39
+
40
+ /**
41
+ * Library watch info
42
+ */
43
+ export interface LibraryWatchInfo {
44
+ name: string;
45
+ config: SdBuildPackageConfig;
46
+ cwd: string;
47
+ pkgDir: string;
48
+ }
49
+
50
+ /**
51
+ * Build event
52
+ */
53
+ export interface LibraryBuildEvent {
54
+ success: boolean;
55
+ errors?: string[];
56
+ warnings?: string[];
57
+ }
58
+
59
+ /**
60
+ * Error event
61
+ */
62
+ export interface LibraryErrorEvent {
63
+ message: string;
64
+ }
65
+
66
+ /**
67
+ * Worker event types
68
+ */
69
+ export interface LibraryWorkerEvents extends Record<string, unknown> {
70
+ buildStart: Record<string, never>;
71
+ build: LibraryBuildEvent;
72
+ error: LibraryErrorEvent;
73
+ }
74
+
75
+ //#endregion
76
+
77
+ //#region Resource Management
78
+
79
+ const logger = consola.withTag("sd:cli:library:worker");
80
+
81
+ /** esbuild build context (to be cleaned up) */
82
+ let esbuildContext: esbuild.BuildContext | undefined;
83
+
84
+ /** FsWatcher (to be cleaned up) */
85
+ let fsWatcher: FsWatcher | undefined;
86
+
87
+ /**
88
+ * Clean up resources
89
+ */
90
+ async function cleanup(): Promise<void> {
91
+ // Capture global variables to temporary variables and initialize
92
+ // (other calls can modify global variables while Promise.all is waiting)
93
+ const contextToDispose = esbuildContext;
94
+ esbuildContext = undefined;
95
+
96
+ const watcherToClose = fsWatcher;
97
+ fsWatcher = undefined;
98
+
99
+ await Promise.all([contextToDispose?.dispose(), watcherToClose?.close()]);
100
+ }
101
+
102
+ // Clean up resources before process termination (SIGTERM/SIGINT)
103
+ // Note: worker.terminate() does not call these handlers and terminates immediately.
104
+ // However, normal shutdown in watch mode is handled via SIGINT/SIGTERM from the main process, so this is fine.
105
+ registerCleanupHandlers(cleanup, logger);
106
+
107
+ //#endregion
108
+
109
+ //#region Worker
110
+
111
+ /**
112
+ * One-time build
113
+ */
114
+ async function build(info: LibraryBuildInfo): Promise<LibraryBuildResult> {
115
+ try {
116
+ // Parse tsconfig
117
+ const parsedConfig = parseRootTsconfig(info.cwd);
118
+ const entryPoints = getPackageSourceFiles(info.pkgDir, parsedConfig);
119
+
120
+ // Create compilerOptions per target
121
+ const env = getTypecheckEnvFromTarget(info.config.target);
122
+ const compilerOptions = await getCompilerOptionsForPackage(
123
+ parsedConfig.options,
124
+ env,
125
+ info.pkgDir,
126
+ );
127
+
128
+ // One-time esbuild
129
+ const esbuildOptions = createLibraryEsbuildOptions({
130
+ pkgDir: info.pkgDir,
131
+ entryPoints,
132
+ target: info.config.target,
133
+ compilerOptions,
134
+ });
135
+
136
+ const result = await esbuild.build(esbuildOptions);
137
+ if (result.outputFiles) {
138
+ await writeChangedOutputFiles(result.outputFiles);
139
+ }
140
+ const errors = result.errors.map((e) => e.text);
141
+ const warnings = result.warnings.map((w) => w.text);
142
+ return {
143
+ success: result.errors.length === 0,
144
+ errors: errors.length > 0 ? errors : undefined,
145
+ warnings: warnings.length > 0 ? warnings : undefined,
146
+ };
147
+ } catch (err) {
148
+ return {
149
+ success: false,
150
+ errors: [errorMessage(err)],
151
+ };
152
+ }
153
+ }
154
+
155
+ const guardStartWatch = createOnceGuard("startWatch");
156
+
157
+ /**
158
+ * Create esbuild context and perform initial build
159
+ */
160
+ async function createAndBuildContext(
161
+ pkgDir: string,
162
+ cwd: string,
163
+ config: SdBuildPackageConfig,
164
+ isFirstBuild: boolean,
165
+ resolveFirstBuild?: () => void,
166
+ ): Promise<esbuild.BuildContext> {
167
+ // Parse tsconfig
168
+ const parsedConfig = parseRootTsconfig(cwd);
169
+ const entryPoints = getPackageSourceFiles(pkgDir, parsedConfig);
170
+
171
+ // Create compilerOptions per target
172
+ const env = getTypecheckEnvFromTarget(config.target);
173
+ const compilerOptions = await getCompilerOptionsForPackage(parsedConfig.options, env, pkgDir);
174
+
175
+ // Create esbuild options
176
+ const baseOptions = createLibraryEsbuildOptions({
177
+ pkgDir,
178
+ entryPoints,
179
+ target: config.target,
180
+ compilerOptions,
181
+ });
182
+
183
+ let isBuildFirstTime = isFirstBuild;
184
+
185
+ // Create context + add watch-notify plugin
186
+ const context = await esbuild.context({
187
+ ...baseOptions,
188
+ plugins: [
189
+ ...(baseOptions.plugins ?? []),
190
+ {
191
+ name: "watch-notify",
192
+ setup(pluginBuild) {
193
+ pluginBuild.onStart(() => {
194
+ sender.send("buildStart", {});
195
+ });
196
+
197
+ pluginBuild.onEnd(async (result) => {
198
+ // Write only changed files to disk
199
+ if (result.outputFiles) {
200
+ await writeChangedOutputFiles(result.outputFiles);
201
+ }
202
+
203
+ const errors = result.errors.map((e) => e.text);
204
+ const warnings = result.warnings.map((w) => w.text);
205
+ const success = result.errors.length === 0;
206
+
207
+ sender.send("build", {
208
+ success,
209
+ errors: errors.length > 0 ? errors : undefined,
210
+ warnings: warnings.length > 0 ? warnings : undefined,
211
+ });
212
+
213
+ if (isBuildFirstTime) {
214
+ isBuildFirstTime = false;
215
+ resolveFirstBuild?.();
216
+ }
217
+ });
218
+ },
219
+ },
220
+ ],
221
+ });
222
+
223
+ // Initial build
224
+ await context.rebuild();
225
+
226
+ return context;
227
+ }
228
+
229
+ /**
230
+ * Start watch
231
+ * @remarks This function should be called only once per Worker.
232
+ * @throws If watch has already been started
233
+ */
234
+ async function startWatch(info: LibraryWatchInfo): Promise<void> {
235
+ guardStartWatch();
236
+
237
+ try {
238
+ // Promise to wait for first build completion
239
+ let resolveFirstBuild!: () => void;
240
+ const firstBuildPromise = new Promise<void>((resolve) => {
241
+ resolveFirstBuild = resolve;
242
+ });
243
+
244
+ // Create initial esbuild context and build
245
+ esbuildContext = await createAndBuildContext(
246
+ info.pkgDir,
247
+ info.cwd,
248
+ info.config,
249
+ true,
250
+ resolveFirstBuild,
251
+ );
252
+
253
+ // Wait for first build completion
254
+ await firstBuildPromise;
255
+
256
+ // Start FsWatcher (watch src/**/*.{ts,tsx})
257
+ const srcPattern = path.join(info.pkgDir, "src", "**", "*.{ts,tsx}");
258
+ fsWatcher = await FsWatcher.watch([srcPattern]);
259
+
260
+ // Handle file changes
261
+ fsWatcher.onChange({ delay: 300 }, async (changes) => {
262
+ try {
263
+ // Check if there are add or unlink events
264
+ const hasAddOrUnlink = changes.some((c) => c.event === "add" || c.event === "unlink");
265
+
266
+ if (hasAddOrUnlink) {
267
+ // Entry points have changed, recreate context
268
+ logger.debug("File add/remove detected, recreating context");
269
+
270
+ const oldContext = esbuildContext;
271
+ esbuildContext = await createAndBuildContext(info.pkgDir, info.cwd, info.config, false);
272
+
273
+ if (oldContext != null) {
274
+ await oldContext.dispose();
275
+ }
276
+ } else {
277
+ // Only file content changed (change event)
278
+ if (esbuildContext != null) {
279
+ await esbuildContext.rebuild();
280
+ }
281
+ }
282
+ } catch (err) {
283
+ sender.send("error", {
284
+ message: errorMessage(err),
285
+ });
286
+ }
287
+ });
288
+ } catch (err) {
289
+ sender.send("error", {
290
+ message: errorMessage(err),
291
+ });
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Stop watch
297
+ * @remarks Cleans up esbuild context.
298
+ */
299
+ async function stopWatch(): Promise<void> {
300
+ await cleanup();
301
+ }
302
+
303
+ const sender = createWorker<
304
+ { build: typeof build; startWatch: typeof startWatch; stopWatch: typeof stopWatch },
305
+ LibraryWorkerEvents
306
+ >({
307
+ build,
308
+ startWatch,
309
+ stopWatch,
310
+ });
311
+
312
+ export default sender;
313
+
314
+ //#endregion
@@ -1,16 +1,16 @@
1
- import { createWorker } from "@simplysm/core-node";
2
- import { executeLint, type LintOptions, type LintResult } from "../commands/lint";
3
-
4
- //#region Worker
5
-
6
- /**
7
- * Lint worker.
8
- * check 명령과 BuildOrchestrator에서 lint 별도 스레드로 실행하기 위한 워커.
9
- */
10
- async function lint(options: LintOptions): Promise<LintResult> {
11
- return executeLint(options);
12
- }
13
-
14
- export default createWorker({ lint });
15
-
16
- //#endregion
1
+ import { createWorker } from "@simplysm/core-node";
2
+ import { executeLint, type LintOptions, type LintResult } from "../commands/lint";
3
+
4
+ //#region Worker
5
+
6
+ /**
7
+ * Lint worker.
8
+ * Worker to run lint in separate thread from check command and BuildOrchestrator
9
+ */
10
+ async function lint(options: LintOptions): Promise<LintResult> {
11
+ return executeLint(options);
12
+ }
13
+
14
+ export default createWorker({ lint });
15
+
16
+ //#endregion