@simplysm/sd-cli 13.0.11 → 13.0.13

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 (160) hide show
  1. package/README.md +42 -2
  2. package/dist/builders/BaseBuilder.d.ts +15 -4
  3. package/dist/builders/BaseBuilder.d.ts.map +1 -1
  4. package/dist/builders/BaseBuilder.js +50 -0
  5. package/dist/builders/BaseBuilder.js.map +1 -1
  6. package/dist/builders/DtsBuilder.d.ts.map +1 -1
  7. package/dist/builders/DtsBuilder.js +2 -39
  8. package/dist/builders/DtsBuilder.js.map +1 -1
  9. package/dist/builders/LibraryBuilder.d.ts.map +1 -1
  10. package/dist/builders/LibraryBuilder.js +2 -39
  11. package/dist/builders/LibraryBuilder.js.map +1 -1
  12. package/dist/builders/types.d.ts +2 -2
  13. package/dist/builders/types.d.ts.map +1 -1
  14. package/dist/capacitor/capacitor.d.ts.map +1 -1
  15. package/dist/capacitor/capacitor.js +2 -1
  16. package/dist/capacitor/capacitor.js.map +1 -1
  17. package/dist/commands/add-client.d.ts.map +1 -1
  18. package/dist/commands/add-client.js +1 -9
  19. package/dist/commands/add-client.js.map +1 -1
  20. package/dist/commands/add-server.d.ts.map +1 -1
  21. package/dist/commands/add-server.js +1 -9
  22. package/dist/commands/add-server.js.map +1 -1
  23. package/dist/commands/build.d.ts +1 -2
  24. package/dist/commands/build.d.ts.map +1 -1
  25. package/dist/commands/build.js +12 -311
  26. package/dist/commands/build.js.map +1 -1
  27. package/dist/commands/dev.d.ts +1 -1
  28. package/dist/commands/dev.d.ts.map +1 -1
  29. package/dist/commands/dev.js +11 -432
  30. package/dist/commands/dev.js.map +1 -1
  31. package/dist/commands/device.d.ts.map +1 -1
  32. package/dist/commands/device.js +17 -32
  33. package/dist/commands/device.js.map +1 -1
  34. package/dist/commands/init.d.ts.map +1 -1
  35. package/dist/commands/init.js +1 -9
  36. package/dist/commands/init.js.map +1 -1
  37. package/dist/commands/lint.d.ts +1 -1
  38. package/dist/commands/lint.d.ts.map +1 -1
  39. package/dist/commands/lint.js +71 -118
  40. package/dist/commands/lint.js.map +2 -2
  41. package/dist/commands/publish.d.ts.map +1 -1
  42. package/dist/commands/publish.js +47 -69
  43. package/dist/commands/publish.js.map +1 -1
  44. package/dist/commands/typecheck.d.ts +1 -1
  45. package/dist/commands/typecheck.d.ts.map +1 -1
  46. package/dist/commands/typecheck.js +11 -24
  47. package/dist/commands/typecheck.js.map +1 -1
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +4 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/infra/ResultCollector.d.ts +1 -1
  53. package/dist/infra/ResultCollector.d.ts.map +1 -1
  54. package/dist/infra/ResultCollector.js +1 -1
  55. package/dist/infra/ResultCollector.js.map +1 -1
  56. package/dist/orchestrators/BuildOrchestrator.d.ts +53 -0
  57. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -0
  58. package/dist/orchestrators/BuildOrchestrator.js +338 -0
  59. package/dist/orchestrators/BuildOrchestrator.js.map +6 -0
  60. package/dist/orchestrators/DevOrchestrator.d.ts +64 -0
  61. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -0
  62. package/dist/orchestrators/DevOrchestrator.js +524 -0
  63. package/dist/orchestrators/DevOrchestrator.js.map +6 -0
  64. package/dist/orchestrators/WatchOrchestrator.d.ts +1 -1
  65. package/dist/orchestrators/WatchOrchestrator.d.ts.map +1 -1
  66. package/dist/orchestrators/WatchOrchestrator.js +30 -21
  67. package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
  68. package/dist/orchestrators/index.d.ts +2 -0
  69. package/dist/orchestrators/index.d.ts.map +1 -1
  70. package/dist/orchestrators/index.js +4 -0
  71. package/dist/orchestrators/index.js.map +1 -1
  72. package/dist/sd-cli-entry.js +14 -14
  73. package/dist/sd-cli-entry.js.map +1 -1
  74. package/dist/sd-cli.js +6 -4
  75. package/dist/sd-cli.js.map +1 -1
  76. package/dist/utils/esbuild-config.d.ts +8 -0
  77. package/dist/utils/esbuild-config.d.ts.map +1 -1
  78. package/dist/utils/esbuild-config.js +23 -28
  79. package/dist/utils/esbuild-config.js.map +1 -1
  80. package/dist/utils/output-utils.d.ts +0 -1
  81. package/dist/utils/output-utils.d.ts.map +1 -1
  82. package/dist/utils/output-utils.js +1 -1
  83. package/dist/utils/output-utils.js.map +1 -1
  84. package/dist/utils/package-utils.d.ts +5 -1
  85. package/dist/utils/package-utils.d.ts.map +1 -1
  86. package/dist/utils/package-utils.js +12 -0
  87. package/dist/utils/package-utils.js.map +1 -1
  88. package/dist/utils/rebuild-manager.d.ts +15 -0
  89. package/dist/utils/rebuild-manager.d.ts.map +1 -0
  90. package/dist/utils/rebuild-manager.js +50 -0
  91. package/dist/utils/rebuild-manager.js.map +6 -0
  92. package/dist/utils/replace-deps.d.ts.map +1 -1
  93. package/dist/utils/replace-deps.js +7 -13
  94. package/dist/utils/replace-deps.js.map +1 -1
  95. package/dist/utils/vite-config.d.ts.map +1 -1
  96. package/dist/utils/vite-config.js +61 -7
  97. package/dist/utils/vite-config.js.map +1 -1
  98. package/dist/utils/worker-events.d.ts +5 -4
  99. package/dist/utils/worker-events.d.ts.map +1 -1
  100. package/dist/utils/worker-events.js +4 -0
  101. package/dist/utils/worker-events.js.map +1 -1
  102. package/dist/utils/worker-utils.d.ts +13 -0
  103. package/dist/utils/worker-utils.d.ts.map +1 -0
  104. package/dist/utils/worker-utils.js +15 -0
  105. package/dist/utils/worker-utils.js.map +6 -0
  106. package/dist/workers/client.worker.d.ts.map +1 -1
  107. package/dist/workers/client.worker.js +2 -14
  108. package/dist/workers/client.worker.js.map +1 -1
  109. package/dist/workers/library.worker.d.ts.map +1 -1
  110. package/dist/workers/library.worker.js +14 -16
  111. package/dist/workers/library.worker.js.map +1 -1
  112. package/dist/workers/server-runtime.worker.d.ts.map +1 -1
  113. package/dist/workers/server-runtime.worker.js +11 -11
  114. package/dist/workers/server-runtime.worker.js.map +1 -1
  115. package/dist/workers/server.worker.d.ts.map +1 -1
  116. package/dist/workers/server.worker.js +2 -14
  117. package/dist/workers/server.worker.js.map +1 -1
  118. package/package.json +4 -5
  119. package/src/builders/BaseBuilder.ts +71 -4
  120. package/src/builders/DtsBuilder.ts +2 -49
  121. package/src/builders/LibraryBuilder.ts +2 -51
  122. package/src/builders/types.ts +2 -2
  123. package/src/capacitor/capacitor.ts +2 -1
  124. package/src/commands/add-client.ts +1 -14
  125. package/src/commands/add-server.ts +1 -13
  126. package/src/commands/build.ts +13 -443
  127. package/src/commands/dev.ts +12 -582
  128. package/src/commands/device.ts +17 -34
  129. package/src/commands/init.ts +1 -13
  130. package/src/commands/lint.ts +85 -146
  131. package/src/commands/publish.ts +58 -76
  132. package/src/commands/typecheck.ts +13 -46
  133. package/src/index.ts +1 -0
  134. package/src/infra/ResultCollector.ts +2 -2
  135. package/src/orchestrators/BuildOrchestrator.ts +499 -0
  136. package/src/orchestrators/DevOrchestrator.ts +703 -0
  137. package/src/orchestrators/WatchOrchestrator.ts +42 -25
  138. package/src/orchestrators/index.ts +2 -0
  139. package/src/sd-cli-entry.ts +14 -14
  140. package/src/sd-cli.ts +6 -4
  141. package/src/utils/esbuild-config.ts +31 -33
  142. package/src/utils/output-utils.ts +1 -2
  143. package/src/utils/package-utils.ts +16 -1
  144. package/src/utils/rebuild-manager.ts +65 -0
  145. package/src/utils/replace-deps.ts +25 -23
  146. package/src/utils/vite-config.ts +115 -9
  147. package/src/utils/worker-events.ts +13 -5
  148. package/src/utils/worker-utils.ts +26 -0
  149. package/src/workers/client.worker.ts +3 -19
  150. package/src/workers/library.worker.ts +16 -22
  151. package/src/workers/server-runtime.worker.ts +16 -17
  152. package/src/workers/server.worker.ts +2 -19
  153. package/templates/add-client/__CLIENT__/package.json.hbs +1 -1
  154. package/templates/add-server/__SERVER__/package.json.hbs +2 -2
  155. package/templates/init/package.json.hbs +3 -3
  156. package/dist/utils/listr-manager.d.ts +0 -37
  157. package/dist/utils/listr-manager.d.ts.map +0 -1
  158. package/dist/utils/listr-manager.js +0 -59
  159. package/dist/utils/listr-manager.js.map +0 -6
  160. package/src/utils/listr-manager.ts +0 -89
@@ -8,6 +8,7 @@ import { VitePWA } from "vite-plugin-pwa";
8
8
  import tailwindcss from "tailwindcss";
9
9
  import type esbuild from "esbuild";
10
10
  import { getTailwindConfigDeps } from "./tailwind-config-deps.js";
11
+ import { FsWatcher } from "@simplysm/core-node";
11
12
 
12
13
  /**
13
14
  * Tailwind config의 scope 패키지 의존성을 watch하는 Vite 플러그인.
@@ -60,30 +61,111 @@ function sdTailwindConfigDepsPlugin(pkgDir: string): Plugin {
60
61
  };
61
62
  }
62
63
 
64
+ /**
65
+ * 패키지가 subpath-only export인지 확인 (exports에 "."이 없는 패키지)
66
+ *
67
+ * 예: @tiptap/pm은 "./state", "./view" 등 subpath만 export하므로 pre-bundling 불가
68
+ * pnpm 구조에서 두 경로를 시도:
69
+ * 1. realpath를 따라 .pnpm node_modules에서 찾기
70
+ * 2. symlink된 workspace 패키지의 node_modules에서 fallback
71
+ */
72
+ function isSubpathOnlyPackage(pkgJsonPath: string): boolean {
73
+ try {
74
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8")) as {
75
+ exports?: Record<string, unknown> | string;
76
+ main?: string;
77
+ module?: string;
78
+ };
79
+ if (
80
+ pkgJson.exports != null &&
81
+ typeof pkgJson.exports === "object" &&
82
+ !("." in pkgJson.exports) &&
83
+ pkgJson.main == null &&
84
+ pkgJson.module == null
85
+ ) {
86
+ return true;
87
+ }
88
+ } catch {
89
+ // 읽기 실패 시 false 반환 (pre-bundling 포함)
90
+ }
91
+ return false;
92
+ }
93
+
63
94
  /**
64
95
  * scope 패키지의 dist 디렉토리 변경을 감지하는 Vite 플러그인.
65
96
  *
66
97
  * Vite는 node_modules를 기본적으로 watch에서 제외하므로,
67
98
  * scope 패키지의 dist 파일이 변경되어도 HMR/리빌드가 트리거되지 않는다.
68
- * 이 플러그인은 scope 패키지의 dist 디렉토리를 watcher에 명시적으로 추가하고,
99
+ * 이 플러그인은 별도의 FsWatcher로 scope 패키지의 dist 디렉토리를 감시하고,
100
+ * 변경 시 Vite의 내부 HMR 파이프라인을 트리거한다.
69
101
  * optimizeDeps에서 제외하여 pre-bundled 캐시로 인한 변경 무시를 방지한다.
70
102
  */
71
103
  function sdScopeWatchPlugin(pkgDir: string, scopes: string[]): Plugin {
72
104
  return {
73
105
  name: "sd-scope-watch",
74
106
  config() {
107
+ const excluded: string[] = [];
108
+ const nestedDepsToInclude: string[] = [];
109
+
110
+ for (const scope of scopes) {
111
+ // scope 패키지를 pre-bundling에서 제외하여 소스 코드로 취급
112
+ const scopeDir = path.join(pkgDir, "node_modules", scope);
113
+ if (!fs.existsSync(scopeDir)) continue;
114
+
115
+ for (const name of fs.readdirSync(scopeDir)) {
116
+ excluded.push(`${scope}/${name}`);
117
+
118
+ // excluded 패키지의 dependencies를 nested include로 추가하여 pre-bundling 보장
119
+ // Vite nested dependency 구문: "excluded-pkg > dep"
120
+ // (pnpm strict 모듈 격리에서 transitive dep을 resolve하기 위해 필요)
121
+ const depPkgJsonPath = path.join(scopeDir, name, "package.json");
122
+ try {
123
+ const depPkgJson = JSON.parse(fs.readFileSync(depPkgJsonPath, "utf-8")) as {
124
+ dependencies?: Record<string, string>;
125
+ };
126
+ const excludedPkg = `${scope}/${name}`;
127
+ for (const dep of Object.keys(depPkgJson.dependencies ?? {})) {
128
+ // 같은 scope 내 패키지는 이미 excluded이므로 제외
129
+ if (scopes.some((s) => dep.startsWith(`${s}/`))) continue;
130
+ // SolidJS 관련 패키지는 solid 플러그인 transform이 필요하므로 pre-bundling 불가
131
+ if (dep === "solid-js" || dep.startsWith("@solidjs/") || dep.startsWith("solid-")) continue;
132
+ // PostCSS/빌드 도구는 브라우저 pre-bundling 대상 아님
133
+ if (dep === "tailwindcss") continue;
134
+
135
+ // subpath-only 패키지 필터링: 두 경로를 시도하여 확인
136
+ // pnpm 구조에서 realpath를 따라 .pnpm node_modules에서 먼저 찾기
137
+ const realPkgPath = fs.realpathSync(path.join(scopeDir, name));
138
+ const pnpmNodeModules = path.resolve(realPkgPath, "../..");
139
+ const depPkgJsonResolved = path.join(pnpmNodeModules, dep, "package.json");
140
+ if (isSubpathOnlyPackage(depPkgJsonResolved)) {
141
+ continue;
142
+ }
143
+
144
+ // workspace 패키지는 realpath가 소스 디렉토리로 해석되어 .pnpm 구조가 아님
145
+ // symlink 경로의 node_modules에서 fallback 시도
146
+ const depPkgJsonFallback = path.join(scopeDir, name, "node_modules", dep, "package.json");
147
+ if (isSubpathOnlyPackage(depPkgJsonFallback)) {
148
+ continue;
149
+ }
150
+
151
+ nestedDepsToInclude.push(`${excludedPkg} > ${dep}`);
152
+ }
153
+ } catch {
154
+ // package.json 읽기 실패 시 스킵
155
+ }
156
+ }
157
+ }
158
+
75
159
  return {
76
160
  optimizeDeps: {
77
- exclude: scopes.flatMap((s) => {
78
- // scope 패키지를 pre-bundling에서 제외하여 소스 코드로 취급
79
- const scopeDir = path.join(pkgDir, "node_modules", s);
80
- if (!fs.existsSync(scopeDir)) return [];
81
- return fs.readdirSync(scopeDir).map((name) => `${s}/${name}`);
82
- }),
161
+ exclude: excluded,
162
+ include: [...new Set(nestedDepsToInclude)],
83
163
  },
84
164
  };
85
165
  },
86
- configureServer(server) {
166
+ async configureServer(server) {
167
+ const distDirs: string[] = [];
168
+
87
169
  for (const scope of scopes) {
88
170
  const scopeDir = path.join(pkgDir, "node_modules", scope);
89
171
  if (!fs.existsSync(scopeDir)) continue;
@@ -91,10 +173,34 @@ function sdScopeWatchPlugin(pkgDir: string, scopes: string[]): Plugin {
91
173
  for (const pkgName of fs.readdirSync(scopeDir)) {
92
174
  const distDir = path.join(scopeDir, pkgName, "dist");
93
175
  if (fs.existsSync(distDir)) {
94
- server.watcher.add(distDir);
176
+ distDirs.push(distDir);
95
177
  }
96
178
  }
97
179
  }
180
+
181
+ if (distDirs.length === 0) return;
182
+
183
+ // Vite의 기본 watcher는 **/node_modules/**를 ignore하고
184
+ // server.watcher.add()로는 이 패턴을 override할 수 없다.
185
+ // 별도의 FsWatcher로 scope 패키지의 dist 디렉토리를 감시한다.
186
+ const scopeWatcher = await FsWatcher.watch(distDirs);
187
+ scopeWatcher.onChange({ delay: 300 }, (changeInfos) => {
188
+ for (const { path: changedPath } of changeInfos) {
189
+ // pnpm symlink → real path 변환 (Vite module graph은 real path 사용)
190
+ let realPath: string;
191
+ try {
192
+ realPath = fs.realpathSync(changedPath);
193
+ } catch {
194
+ continue; // 삭제된 파일
195
+ }
196
+
197
+ // Vite의 내부 HMR 파이프라인 트리거
198
+ server.watcher.emit("change", realPath);
199
+ }
200
+ });
201
+
202
+ // 서버 종료 시 watcher 정리
203
+ server.httpServer?.on("close", () => void scopeWatcher.close());
98
204
  },
99
205
  };
100
206
  }
@@ -1,6 +1,9 @@
1
+ import { consola } from "consola";
1
2
  import type { PackageResult } from "./package-utils";
2
3
  import type { SdPackageConfig } from "../sd-config.types";
3
- import type { RebuildListrManager } from "./listr-manager";
4
+ import type { RebuildManager } from "./rebuild-manager";
5
+
6
+ const workerEventsLogger = consola.withTag("sd:cli:worker-events");
4
7
 
5
8
  /** Worker 빌드 완료 이벤트 데이터 */
6
9
  export interface BuildEventData {
@@ -28,10 +31,13 @@ export interface ServerBuildEventData {
28
31
  /**
29
32
  * 기본 Worker 정보 타입
30
33
  */
31
- export interface BaseWorkerInfo {
34
+ export interface BaseWorkerInfo<TEvents extends Record<string, any[]> = Record<string, any[]>> {
32
35
  name: string;
33
36
  config: SdPackageConfig;
34
- worker: { on: (event: string, handler: (data: unknown) => void) => void };
37
+ worker: {
38
+ on<K extends keyof TEvents>(event: K, handler: (data: TEvents[K][0]) => void): void;
39
+ send<K extends keyof TEvents>(event: K, data: TEvents[K][0]): void;
40
+ };
35
41
  isInitialBuild: boolean;
36
42
  buildResolver: (() => void) | undefined;
37
43
  }
@@ -54,11 +60,11 @@ export interface WorkerEventHandlerOptions {
54
60
  * @param rebuildManager 리빌드 매니저
55
61
  * @returns completeTask 함수 (결과를 저장하고 빌드 완료를 알림)
56
62
  */
57
- export function registerWorkerEventHandlers<T extends BaseWorkerInfo>(
63
+ export function registerWorkerEventHandlers<TEvents extends Record<string, any[]>, T extends BaseWorkerInfo<TEvents>>(
58
64
  workerInfo: T,
59
65
  opts: WorkerEventHandlerOptions,
60
66
  results: Map<string, PackageResult>,
61
- rebuildManager: RebuildListrManager,
67
+ rebuildManager: RebuildManager,
62
68
  ): (result: PackageResult) => void {
63
69
  const completeTask = (result: PackageResult): void => {
64
70
  results.set(opts.resultKey, result);
@@ -77,6 +83,7 @@ export function registerWorkerEventHandlers<T extends BaseWorkerInfo>(
77
83
  // 빌드 완료
78
84
  workerInfo.worker.on("build", (data) => {
79
85
  const event = data as BuildEventData;
86
+ workerEventsLogger.debug(`[${workerInfo.name}] build: success=${String(event.success)}`);
80
87
  completeTask({
81
88
  name: workerInfo.name,
82
89
  target: workerInfo.config.target,
@@ -89,6 +96,7 @@ export function registerWorkerEventHandlers<T extends BaseWorkerInfo>(
89
96
  // 에러
90
97
  workerInfo.worker.on("error", (data) => {
91
98
  const event = data as ErrorEventData;
99
+ workerEventsLogger.debug(`[${workerInfo.name}] error: ${event.message}`);
92
100
  completeTask({
93
101
  name: workerInfo.name,
94
102
  target: workerInfo.config.target,
@@ -0,0 +1,26 @@
1
+ import type { ConsolaInstance } from "consola";
2
+
3
+ /**
4
+ * Register cleanup handlers for worker process shutdown signals
5
+ *
6
+ * Registers SIGINT and SIGTERM handlers to gracefully cleanup resources
7
+ * before process exit. Both handlers execute the cleanup function and
8
+ * exit with code 0.
9
+ *
10
+ * @param cleanup - Async cleanup function to execute on shutdown
11
+ * @param logger - Consola logger instance for error logging
12
+ */
13
+ export function registerCleanupHandlers(cleanup: () => Promise<void>, logger: ConsolaInstance): void {
14
+ const handleSignal = () => {
15
+ cleanup()
16
+ .catch((err) => {
17
+ logger.error("cleanup 실패", err);
18
+ })
19
+ .finally(() => {
20
+ process.exit(0);
21
+ });
22
+ };
23
+
24
+ process.on("SIGTERM", handleSignal);
25
+ process.on("SIGINT", handleSignal);
26
+ }
@@ -6,6 +6,7 @@ import { consola } from "consola";
6
6
  import type { SdClientPackageConfig } from "../sd-config.types";
7
7
  import { parseRootTsconfig, getCompilerOptionsForPackage } from "../utils/tsconfig";
8
8
  import { createViteConfig } from "../utils/vite-config";
9
+ import { registerCleanupHandlers } from "../utils/worker-utils";
9
10
 
10
11
  //#region Types
11
12
 
@@ -97,25 +98,7 @@ async function cleanup(): Promise<void> {
97
98
  // 프로세스 종료 전 리소스 정리 (SIGTERM/SIGINT)
98
99
  // 주의: worker.terminate()는 이 핸들러들을 호출하지 않고 즉시 종료됨.
99
100
  // 그러나 watch 모드에서 정상 종료는 메인 프로세스의 SIGINT/SIGTERM을 통해 이루어지므로 문제없음.
100
- process.on("SIGTERM", () => {
101
- cleanup()
102
- .catch((err) => {
103
- logger.error("cleanup 실패", err);
104
- })
105
- .finally(() => {
106
- process.exit(0);
107
- });
108
- });
109
-
110
- process.on("SIGINT", () => {
111
- cleanup()
112
- .catch((err) => {
113
- logger.error("cleanup 실패", err);
114
- })
115
- .finally(() => {
116
- process.exit(0);
117
- });
118
- });
101
+ registerCleanupHandlers(cleanup, logger);
119
102
 
120
103
  //#endregion
121
104
 
@@ -208,6 +191,7 @@ async function startWatch(info: ClientWatchInfo): Promise<void> {
208
191
  // 실제 할당된 포트 반환 (config.server.port는 설정값이므로 httpServer에서 실제 포트를 가져옴)
209
192
  const address = viteServer.httpServer?.address();
210
193
  const actualPort = typeof address === "object" && address != null ? address.port : viteServer.config.server.port;
194
+
211
195
  sender.send("serverReady", { port: actualPort });
212
196
  } catch (err) {
213
197
  sender.send("error", {
@@ -4,7 +4,12 @@ import { createWorker, FsWatcher } from "@simplysm/core-node";
4
4
  import { consola } from "consola";
5
5
  import type { SdBuildPackageConfig } from "../sd-config.types";
6
6
  import { parseRootTsconfig, getPackageSourceFiles, getCompilerOptionsForPackage } from "../utils/tsconfig";
7
- import { createLibraryEsbuildOptions, getTypecheckEnvFromTarget } from "../utils/esbuild-config";
7
+ import {
8
+ createLibraryEsbuildOptions,
9
+ getTypecheckEnvFromTarget,
10
+ writeChangedOutputFiles,
11
+ } from "../utils/esbuild-config";
12
+ import { registerCleanupHandlers } from "../utils/worker-utils";
8
13
 
9
14
  //#region Types
10
15
 
@@ -90,25 +95,7 @@ async function cleanup(): Promise<void> {
90
95
  // 프로세스 종료 전 리소스 정리 (SIGTERM/SIGINT)
91
96
  // 주의: worker.terminate()는 이 핸들러들을 호출하지 않고 즉시 종료됨.
92
97
  // 그러나 watch 모드에서 정상 종료는 메인 프로세스의 SIGINT/SIGTERM을 통해 이루어지므로 문제없음.
93
- process.on("SIGTERM", () => {
94
- cleanup()
95
- .catch((err) => {
96
- logger.error("cleanup 실패", err);
97
- })
98
- .finally(() => {
99
- process.exit(0);
100
- });
101
- });
102
-
103
- process.on("SIGINT", () => {
104
- cleanup()
105
- .catch((err) => {
106
- logger.error("cleanup 실패", err);
107
- })
108
- .finally(() => {
109
- process.exit(0);
110
- });
111
- });
98
+ registerCleanupHandlers(cleanup, logger);
112
99
 
113
100
  //#endregion
114
101
 
@@ -136,7 +123,9 @@ async function build(info: LibraryBuildInfo): Promise<LibraryBuildResult> {
136
123
  });
137
124
 
138
125
  const result = await esbuild.build(esbuildOptions);
139
-
126
+ if (result.outputFiles) {
127
+ await writeChangedOutputFiles(result.outputFiles);
128
+ }
140
129
  const errors = result.errors.map((e) => e.text);
141
130
  return {
142
131
  success: result.errors.length === 0,
@@ -193,7 +182,12 @@ async function createAndBuildContext(
193
182
  sender.send("buildStart", {});
194
183
  });
195
184
 
196
- pluginBuild.onEnd((result) => {
185
+ pluginBuild.onEnd(async (result) => {
186
+ // Write only changed files to disk
187
+ if (result.outputFiles) {
188
+ await writeChangedOutputFiles(result.outputFiles);
189
+ }
190
+
197
191
  const errors = result.errors.map((e) => e.text);
198
192
  const success = result.errors.length === 0;
199
193
 
@@ -2,6 +2,7 @@ import proxy from "@fastify/http-proxy";
2
2
  import { createWorker } from "@simplysm/core-node";
3
3
  import { consola } from "consola";
4
4
  import net from "net";
5
+ import { registerCleanupHandlers } from "../utils/worker-utils";
5
6
 
6
7
  //#region Types
7
8
 
@@ -53,26 +54,24 @@ async function cleanup(): Promise<void> {
53
54
  serverInstance = undefined;
54
55
  }
55
56
 
56
- process.on("SIGTERM", () => {
57
- cleanup()
58
- .catch((err) => {
59
- logger.error("cleanup 실패", err);
60
- })
61
- .finally(() => {
62
- process.exit(0);
63
- });
57
+ // 서버 listen() 이후 발생하는 런타임 에러를 잡아서 custom "error" 이벤트로 전송
58
+ // (이 핸들러가 없으면 worker가 crash만 하고, dev.ts의 buildResolver가 호출되지 않아 listr가 멈춤)
59
+ process.on("uncaughtException", (err) => {
60
+ logger.error("Server Runtime 미처리 오류", err);
61
+ sender.send("error", {
62
+ message: err instanceof Error ? err.message : String(err),
63
+ });
64
64
  });
65
65
 
66
- process.on("SIGINT", () => {
67
- cleanup()
68
- .catch((err) => {
69
- logger.error("cleanup 실패", err);
70
- })
71
- .finally(() => {
72
- process.exit(0);
73
- });
66
+ process.on("unhandledRejection", (reason) => {
67
+ logger.error("Server Runtime 미처리 Promise 거부", reason);
68
+ sender.send("error", {
69
+ message: reason instanceof Error ? reason.message : String(reason),
70
+ });
74
71
  });
75
72
 
73
+ registerCleanupHandlers(cleanup, logger);
74
+
76
75
  /**
77
76
  * 포트가 사용 가능한지 확인
78
77
  */
@@ -97,7 +96,7 @@ async function findAvailablePort(startPort: number, maxRetries = 20): Promise<nu
97
96
  return port;
98
97
  }
99
98
  }
100
- return startPort;
99
+ throw new Error(`포트 ${startPort}부터 ${startPort + maxRetries - 1}까지 사용 가능한 포트가 없습니다.`);
101
100
  }
102
101
 
103
102
  /**
@@ -10,6 +10,7 @@ import {
10
10
  collectUninstalledOptionalPeerDeps,
11
11
  collectNativeModuleExternals,
12
12
  } from "../utils/esbuild-config";
13
+ import { registerCleanupHandlers } from "../utils/worker-utils";
13
14
 
14
15
  //#region Types
15
16
 
@@ -251,25 +252,7 @@ function generateProductionFiles(info: ServerBuildInfo, externals: string[]): vo
251
252
  // 프로세스 종료 전 리소스 정리 (SIGTERM/SIGINT)
252
253
  // 주의: worker.terminate()는 이 핸들러들을 호출하지 않고 즉시 종료됨.
253
254
  // 그러나 watch 모드에서 정상 종료는 메인 프로세스의 SIGINT/SIGTERM을 통해 이루어지므로 문제없음.
254
- process.on("SIGTERM", () => {
255
- cleanup()
256
- .catch((err) => {
257
- logger.error("cleanup 실패", err);
258
- })
259
- .finally(() => {
260
- process.exit(0);
261
- });
262
- });
263
-
264
- process.on("SIGINT", () => {
265
- cleanup()
266
- .catch((err) => {
267
- logger.error("cleanup 실패", err);
268
- })
269
- .finally(() => {
270
- process.exit(0);
271
- });
272
- });
255
+ registerCleanupHandlers(cleanup, logger);
273
256
 
274
257
  //#endregion
275
258
 
@@ -4,7 +4,7 @@
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "dependencies": {
7
- "@simplysm/solid": "~13.0.11",
7
+ "@simplysm/solid": "~13.0.13",
8
8
  {{#if router}}
9
9
  "@solidjs/router": "^0.15.4",
10
10
  {{/if}}
@@ -4,7 +4,7 @@
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "dependencies": {
7
- "@simplysm/core-common": "~13.0.11",
8
- "@simplysm/service-server": "~13.0.11"
7
+ "@simplysm/core-common": "~13.0.13",
8
+ "@simplysm/service-server": "~13.0.13"
9
9
  }
10
10
  }
@@ -15,9 +15,9 @@
15
15
  "vitest": "vitest"
16
16
  },
17
17
  "devDependencies": {
18
- "@simplysm/sd-cli": "~13.0.11",
19
- "@simplysm/claude": "~13.0.11",
20
- "@simplysm/lint": "~13.0.11",
18
+ "@simplysm/sd-cli": "~13.0.13",
19
+ "@simplysm/claude": "~13.0.13",
20
+ "@simplysm/lint": "~13.0.13",
21
21
  "@types/node": "^20.19.33",
22
22
  "eslint": "^9.39.2",
23
23
  "prettier": "^3.8.1",
@@ -1,37 +0,0 @@
1
- import { EventEmitter } from "node:events";
2
- import type { consola } from "consola";
3
- /**
4
- * RebuildListrManager 이벤트 타입
5
- */
6
- interface RebuildListrManagerEvents {
7
- batchComplete: [];
8
- }
9
- /**
10
- * 리빌드 시 Listr 실행을 관리하는 클래스
11
- *
12
- * 여러 Worker가 동시에 buildStart를 발생시킬 때, 한 번에 하나의 Listr만 실행되도록 보장합니다.
13
- * 실행 중에 들어온 빌드 요청은 pending에 모아두었다가 현재 배치가 완료되면 다음 배치로 실행합니다.
14
- *
15
- * EventEmitter를 확장하여 배치 완료 시 `batchComplete` 이벤트를 발생시킵니다.
16
- */
17
- export declare class RebuildListrManager extends EventEmitter<RebuildListrManagerEvents> {
18
- private readonly _logger;
19
- private _isRunning;
20
- private readonly _pendingBuilds;
21
- constructor(_logger: ReturnType<typeof consola.withTag>);
22
- /**
23
- * 빌드를 등록하고 resolver 함수를 반환합니다.
24
- *
25
- * @param key - 빌드를 식별하는 고유 키 (예: "core-common:build")
26
- * @param title - Listr에 표시할 제목 (예: "core-common (node)")
27
- * @returns 워커가 빌드 완료 시 호출할 resolver 함수
28
- */
29
- registerBuild(key: string, title: string): () => void;
30
- /**
31
- * pending에 있는 빌드들을 모아서 하나의 Listr로 실행합니다.
32
- * 실행 중에 들어온 새 빌드는 다음 배치로 넘어갑니다.
33
- */
34
- private _runBatch;
35
- }
36
- export {};
37
- //# sourceMappingURL=listr-manager.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"listr-manager.d.ts","sourceRoot":"","sources":["../../src/utils/listr-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC;;GAEG;AACH,UAAU,yBAAyB;IACjC,aAAa,EAAE,EAAE,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,qBAAa,mBAAoB,SAAQ,YAAY,CAAC,yBAAyB,CAAC;IAIlE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAsF;gBAExF,OAAO,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,OAAO,CAAC;IAIxE;;;;;;OAMG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,IAAI;IAgBrD;;;OAGG;YACW,SAAS;CAkCxB"}
@@ -1,59 +0,0 @@
1
- import { EventEmitter } from "node:events";
2
- import { Listr } from "listr2";
3
- class RebuildListrManager extends EventEmitter {
4
- constructor(_logger) {
5
- super();
6
- this._logger = _logger;
7
- }
8
- _isRunning = false;
9
- _pendingBuilds = /* @__PURE__ */ new Map();
10
- /**
11
- * 빌드를 등록하고 resolver 함수를 반환합니다.
12
- *
13
- * @param key - 빌드를 식별하는 고유 키 (예: "core-common:build")
14
- * @param title - Listr에 표시할 제목 (예: "core-common (node)")
15
- * @returns 워커가 빌드 완료 시 호출할 resolver 함수
16
- */
17
- registerBuild(key, title) {
18
- let resolver;
19
- const promise = new Promise((resolve) => {
20
- resolver = resolve;
21
- });
22
- this._pendingBuilds.set(key, { title, promise, resolver });
23
- if (!this._isRunning) {
24
- void Promise.resolve().then(() => void this._runBatch());
25
- }
26
- return resolver;
27
- }
28
- /**
29
- * pending에 있는 빌드들을 모아서 하나의 Listr로 실행합니다.
30
- * 실행 중에 들어온 새 빌드는 다음 배치로 넘어갑니다.
31
- */
32
- async _runBatch() {
33
- if (this._isRunning || this._pendingBuilds.size === 0) {
34
- return;
35
- }
36
- this._isRunning = true;
37
- const batchBuilds = new Map(this._pendingBuilds);
38
- this._pendingBuilds.clear();
39
- const tasks = Array.from(batchBuilds.entries()).map(([, { title, promise }]) => ({
40
- title,
41
- task: () => promise
42
- }));
43
- const listr = new Listr(tasks, { concurrent: true });
44
- try {
45
- await listr.run();
46
- this.emit("batchComplete");
47
- } catch (err) {
48
- this._logger.error("listr \uC2E4\uD589 \uC911 \uC624\uB958 \uBC1C\uC0DD", { error: String(err) });
49
- }
50
- this._isRunning = false;
51
- if (this._pendingBuilds.size > 0) {
52
- void this._runBatch();
53
- }
54
- }
55
- }
56
- export {
57
- RebuildListrManager
58
- };
59
- //# sourceMappingURL=listr-manager.js.map
@@ -1,6 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/utils/listr-manager.ts"],
4
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AAkBf,MAAM,4BAA4B,aAAwC;AAAA,EAI/E,YAA6B,SAA6C;AACxE,UAAM;AADqB;AAAA,EAE7B;AAAA,EALQ,aAAa;AAAA,EACJ,iBAAiB,oBAAI,IAA6E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAanH,cAAc,KAAa,OAA2B;AACpD,QAAI;AACJ,UAAM,UAAU,IAAI,QAAc,CAAC,YAAY;AAC7C,iBAAW;AAAA,IACb,CAAC;AAED,SAAK,eAAe,IAAI,KAAK,EAAE,OAAO,SAAS,SAAS,CAAC;AAGzD,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,QAAQ,QAAQ,EAAE,KAAK,MAAM,KAAK,KAAK,UAAU,CAAC;AAAA,IACzD;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,YAA2B;AACvC,QAAI,KAAK,cAAc,KAAK,eAAe,SAAS,GAAG;AACrD;AAAA,IACF;AAEA,SAAK,aAAa;AAGlB,UAAM,cAAc,IAAI,IAAI,KAAK,cAAc;AAC/C,SAAK,eAAe,MAAM;AAG1B,UAAM,QAAQ,MAAM,KAAK,YAAY,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,OAAO,QAAQ,CAAC,OAAO;AAAA,MAC/E;AAAA,MACA,MAAM,MAAM;AAAA,IACd,EAAE;AAEF,UAAM,QAAQ,IAAI,MAAM,OAAO,EAAE,YAAY,KAAK,CAAC;AAEnD,QAAI;AACF,YAAM,MAAM,IAAI;AAEhB,WAAK,KAAK,eAAe;AAAA,IAC3B,SAAS,KAAK;AACZ,WAAK,QAAQ,MAAM,uDAAoB,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,IAC/D;AAEA,SAAK,aAAa;AAGlB,QAAI,KAAK,eAAe,OAAO,GAAG;AAChC,WAAK,KAAK,UAAU;AAAA,IACtB;AAAA,EACF;AACF;",
5
- "names": []
6
- }