@simplysm/sd-cli 14.0.16 → 14.0.18

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 (202) hide show
  1. package/README.md +2 -1
  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 +9 -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.map +1 -1
  17. package/dist/commands/device.js +33 -1
  18. package/dist/commands/device.js.map +1 -1
  19. package/dist/commands/lint.d.ts +0 -1
  20. package/dist/commands/lint.d.ts.map +1 -1
  21. package/dist/commands/lint.js +2 -3
  22. package/dist/commands/lint.js.map +1 -1
  23. package/dist/commands/publish.js +2 -2
  24. package/dist/commands/publish.js.map +1 -1
  25. package/dist/commands/typecheck.d.ts.map +1 -1
  26. package/dist/commands/typecheck.js +0 -1
  27. package/dist/commands/typecheck.js.map +1 -1
  28. package/dist/electron/electron.d.ts +3 -2
  29. package/dist/electron/electron.d.ts.map +1 -1
  30. package/dist/electron/electron.js +54 -31
  31. package/dist/electron/electron.js.map +1 -1
  32. package/dist/engines/BaseEngine.js +1 -1
  33. package/dist/engines/BaseEngine.js.map +1 -1
  34. package/dist/engines/NgtscEngine.d.ts.map +1 -1
  35. package/dist/engines/NgtscEngine.js +0 -1
  36. package/dist/engines/NgtscEngine.js.map +1 -1
  37. package/dist/engines/ServerEsbuildEngine.d.ts.map +1 -1
  38. package/dist/engines/ServerEsbuildEngine.js +0 -1
  39. package/dist/engines/ServerEsbuildEngine.js.map +1 -1
  40. package/dist/engines/TscEngine.d.ts.map +1 -1
  41. package/dist/engines/TscEngine.js +0 -1
  42. package/dist/engines/TscEngine.js.map +1 -1
  43. package/dist/engines/ViteEngine.d.ts.map +1 -1
  44. package/dist/engines/ViteEngine.js +8 -1
  45. package/dist/engines/ViteEngine.js.map +1 -1
  46. package/dist/engines/index.d.ts +0 -10
  47. package/dist/engines/index.d.ts.map +1 -1
  48. package/dist/engines/index.js +0 -5
  49. package/dist/engines/index.js.map +1 -1
  50. package/dist/engines/types.d.ts +0 -1
  51. package/dist/engines/types.d.ts.map +1 -1
  52. package/dist/infra/SignalHandler.d.ts +1 -6
  53. package/dist/infra/SignalHandler.d.ts.map +1 -1
  54. package/dist/infra/SignalHandler.js +4 -13
  55. package/dist/infra/SignalHandler.js.map +1 -1
  56. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  57. package/dist/orchestrators/BuildOrchestrator.js +7 -12
  58. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  59. package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
  60. package/dist/orchestrators/DevWatchOrchestrator.js +17 -10
  61. package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
  62. package/dist/sd-cli-entry.d.ts +0 -1
  63. package/dist/sd-cli-entry.d.ts.map +1 -1
  64. package/dist/sd-cli-entry.js +7 -8
  65. package/dist/sd-cli-entry.js.map +1 -1
  66. package/dist/sd-config.types.d.ts +11 -1
  67. package/dist/sd-config.types.d.ts.map +1 -1
  68. package/dist/utils/angular-compiler.d.ts.map +1 -1
  69. package/dist/utils/angular-compiler.js +20 -13
  70. package/dist/utils/angular-compiler.js.map +1 -1
  71. package/dist/utils/esbuild-config.d.ts +1 -1
  72. package/dist/utils/esbuild-config.d.ts.map +1 -1
  73. package/dist/utils/esbuild-config.js +1 -4
  74. package/dist/utils/esbuild-config.js.map +1 -1
  75. package/dist/utils/ngtsc-build-core.d.ts.map +1 -1
  76. package/dist/utils/ngtsc-build-core.js +3 -0
  77. package/dist/utils/ngtsc-build-core.js.map +1 -1
  78. package/dist/utils/tsc-build.d.ts +5 -0
  79. package/dist/utils/tsc-build.d.ts.map +1 -1
  80. package/dist/utils/tsc-build.js +2 -1
  81. package/dist/utils/tsc-build.js.map +1 -1
  82. package/dist/utils/vite-config.d.ts +1 -1
  83. package/dist/utils/vite-config.d.ts.map +1 -1
  84. package/dist/utils/vite-config.js +22 -53
  85. package/dist/utils/vite-config.js.map +1 -1
  86. package/dist/utils/vite-pwa-plugin.d.ts +9 -0
  87. package/dist/utils/vite-pwa-plugin.d.ts.map +1 -0
  88. package/dist/utils/vite-pwa-plugin.js +139 -0
  89. package/dist/utils/vite-pwa-plugin.js.map +1 -0
  90. package/dist/utils/worker-utils.d.ts +2 -5
  91. package/dist/utils/worker-utils.d.ts.map +1 -1
  92. package/dist/utils/worker-utils.js +5 -11
  93. package/dist/utils/worker-utils.js.map +1 -1
  94. package/dist/workers/client.worker.d.ts.map +1 -1
  95. package/dist/workers/client.worker.js +9 -3
  96. package/dist/workers/client.worker.js.map +1 -1
  97. package/dist/workers/library-build.worker.d.ts.map +1 -1
  98. package/dist/workers/library-build.worker.js +6 -2
  99. package/dist/workers/library-build.worker.js.map +1 -1
  100. package/dist/workers/ngtsc-build.worker.js +2 -2
  101. package/dist/workers/ngtsc-build.worker.js.map +1 -1
  102. package/dist/workers/server-build.worker.d.ts.map +1 -1
  103. package/dist/workers/server-build.worker.js +6 -2
  104. package/dist/workers/server-build.worker.js.map +1 -1
  105. package/dist/workers/server-runtime.worker.js +4 -4
  106. package/dist/workers/server-runtime.worker.js.map +1 -1
  107. package/docs/config.md +26 -0
  108. package/docs/pwa-configuration-types.md +1 -1
  109. package/package.json +8 -10
  110. package/src/angular/client-transform-stylesheet.ts +104 -2
  111. package/src/angular/vite-angular-plugin.ts +92 -31
  112. package/src/capacitor/capacitor.ts +10 -26
  113. package/src/commands/check.ts +8 -11
  114. package/src/commands/device.ts +38 -3
  115. package/src/commands/lint.ts +2 -3
  116. package/src/commands/publish.ts +2 -2
  117. package/src/commands/typecheck.ts +0 -1
  118. package/src/electron/electron.ts +62 -43
  119. package/src/engines/BaseEngine.ts +1 -1
  120. package/src/engines/NgtscEngine.ts +0 -1
  121. package/src/engines/ServerEsbuildEngine.ts +0 -1
  122. package/src/engines/TscEngine.ts +0 -1
  123. package/src/engines/ViteEngine.ts +7 -1
  124. package/src/engines/index.ts +0 -10
  125. package/src/engines/types.ts +0 -1
  126. package/src/infra/SignalHandler.ts +4 -14
  127. package/src/orchestrators/BuildOrchestrator.ts +7 -9
  128. package/src/orchestrators/DevWatchOrchestrator.ts +21 -9
  129. package/src/sd-cli-entry.ts +11 -16
  130. package/src/sd-config.types.ts +12 -1
  131. package/src/utils/angular-compiler.ts +21 -21
  132. package/src/utils/esbuild-config.ts +2 -5
  133. package/src/utils/ngtsc-build-core.ts +7 -0
  134. package/src/utils/tsc-build.ts +7 -0
  135. package/src/utils/vite-config.ts +23 -55
  136. package/src/utils/vite-pwa-plugin.ts +168 -0
  137. package/src/utils/worker-utils.ts +5 -11
  138. package/src/workers/client.worker.ts +11 -3
  139. package/src/workers/library-build.worker.ts +6 -2
  140. package/src/workers/ngtsc-build.worker.ts +2 -2
  141. package/src/workers/server-build.worker.ts +7 -2
  142. package/src/workers/server-runtime.worker.ts +4 -4
  143. package/tests/angular/client-transform-stylesheet.spec.ts +43 -0
  144. package/tests/angular/find-affected-by-scss.spec.ts +37 -0
  145. package/tests/angular/fixtures/basic-app/scss/_colors.scss +1 -0
  146. package/tests/angular/fixtures/basic-app/scss/_variables.scss +3 -0
  147. package/tests/angular/fixtures/basic-app/src/styled.component.ts +14 -0
  148. package/tests/angular/linker-disk-cache.spec.ts +158 -0
  149. package/tests/angular/scss-disk-cache.spec.ts +162 -0
  150. package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +15 -15
  151. package/tests/angular/vite-angular-plugin-hmr.spec.ts +9 -9
  152. package/tests/angular/vite-angular-plugin-lint.spec.ts +4 -4
  153. package/tests/angular/vite-angular-plugin-scss-hmr.spec.ts +87 -0
  154. package/tests/angular/vite-angular-plugin.spec.ts +15 -15
  155. package/tests/capacitor/capacitor-icon.spec.ts +2 -4
  156. package/tests/capacitor/capacitor-init.spec.ts +2 -4
  157. package/tests/capacitor/capacitor-workspace.spec.ts +2 -4
  158. package/tests/commands/device.spec.ts +100 -0
  159. package/tests/electron/electron.spec.ts +24 -17
  160. package/tests/engines/ngtsc-engine.spec.ts +0 -3
  161. package/tests/engines/server-esbuild-engine.spec.ts +0 -3
  162. package/tests/engines/tsc-engine.spec.ts +1 -2
  163. package/tests/engines/vite-engine.spec.ts +0 -2
  164. package/tests/infra/signal-handler.spec.ts +1 -12
  165. package/tests/orchestrators/build-orchestrator.spec.ts +0 -6
  166. package/tests/orchestrators/dev-watch-orchestrator.spec.ts +24 -66
  167. package/tests/utils/angular-compiler.spec.ts +1396 -32
  168. package/tests/utils/esbuild-config.spec.ts +4 -7
  169. package/tests/utils/{ngtsc-build-core-angular-compiler.spec.ts → ngtsc-build-core.spec.ts} +142 -11
  170. package/tests/utils/tsc-build.spec.ts +4 -1
  171. package/tests/utils/vite-config.spec.ts +130 -261
  172. package/tests/utils/vite-pwa-plugin.acc.spec.ts +143 -0
  173. package/tests/utils/vite-pwa-plugin.spec.ts +350 -0
  174. package/tests/utils/worker-utils.spec.ts +8 -7
  175. package/tests/workers/client-worker.spec.ts +50 -1
  176. package/tests/workers/dev-port-file.verify.md +6 -0
  177. package/tests/workers/library-build-lint.spec.ts +1 -1
  178. package/tests/workers/library-build-worker.spec.ts +1 -1
  179. package/tests/workers/ngtsc-build-lint.spec.ts +1 -1
  180. package/tests/workers/server-build-lint.spec.ts +1 -1
  181. package/tests/workers/server-build-worker.spec.ts +1 -1
  182. package/tests/workers/server-runtime-worker.spec.ts +8 -1
  183. package/dist/infra/WorkerManager.d.ts +0 -40
  184. package/dist/infra/WorkerManager.d.ts.map +0 -1
  185. package/dist/infra/WorkerManager.js +0 -59
  186. package/dist/infra/WorkerManager.js.map +0 -1
  187. package/dist/utils/SdCliReporter.d.ts +0 -18
  188. package/dist/utils/SdCliReporter.d.ts.map +0 -1
  189. package/dist/utils/SdCliReporter.js +0 -144
  190. package/dist/utils/SdCliReporter.js.map +0 -1
  191. package/src/infra/WorkerManager.ts +0 -65
  192. package/src/utils/SdCliReporter.ts +0 -177
  193. package/tests/angular/scss-compiler-async.spec.ts +0 -54
  194. package/tests/commands/dev.spec.ts +0 -53
  195. package/tests/commands/watch.spec.ts +0 -53
  196. package/tests/infra/worker-manager.spec.ts +0 -63
  197. package/tests/utils/angular-compiler-emit.spec.ts +0 -570
  198. package/tests/utils/angular-compiler-init.spec.ts +0 -705
  199. package/tests/utils/angular-compiler-update.spec.ts +0 -293
  200. package/tests/utils/build-env.spec.ts +0 -33
  201. package/tests/utils/ngtsc-build-core-transform-stylesheet.spec.ts +0 -124
  202. package/tests/utils/ngtsc-scss-refactor.spec.ts +0 -47
@@ -3,20 +3,14 @@ import fs from "fs";
3
3
  import module from "module";
4
4
  import { cpx, fsx, pathx } from "@simplysm/core-node";
5
5
  import { consola, LogLevels } from "consola";
6
- import type { SdElectronConfig } from "../sd-config.types.js";
6
+ import type { NpmConfig, SdElectronConfig } from "../sd-config.types.js";
7
7
  import { createEnvBanner } from "../utils/esbuild-config.js";
8
8
 
9
- interface NpmConfig {
10
- name: string;
11
- version: string;
12
- description?: string;
13
- dependencies?: Record<string, string>;
14
- }
15
-
16
9
  export class Electron {
17
10
  private static readonly _logger = consola.withTag("sd:cli:electron");
18
11
 
19
12
  private readonly _electronPath: string;
13
+ private readonly _srcPath: string;
20
14
 
21
15
  private constructor(
22
16
  private readonly _pkgPath: string,
@@ -25,6 +19,7 @@ export class Electron {
25
19
  private readonly _exclude: string[],
26
20
  ) {
27
21
  this._electronPath = pathx.posixResolve(this._pkgPath, ".electron");
22
+ this._srcPath = pathx.posixResolve(this._electronPath, "src");
28
23
  }
29
24
 
30
25
  static async create(
@@ -44,10 +39,6 @@ export class Electron {
44
39
  }
45
40
  }
46
41
 
47
- private _localBin(name: string): string {
48
- return pathx.posixResolve(this._pkgPath, "node_modules/.bin", name);
49
- }
50
-
51
42
  private async _exec(
52
43
  cmd: string,
53
44
  args: string[],
@@ -70,20 +61,26 @@ export class Electron {
70
61
 
71
62
  async initialize(): Promise<void> {
72
63
  Electron._logger.debug("initialize 시작");
73
- const srcPath = pathx.posixResolve(this._electronPath, "src");
74
64
 
75
65
  Electron._logger.debug("package.json 설정 시작");
76
- await this._setupPackageJson(srcPath);
66
+ await this._setupNpmConf();
77
67
  Electron._logger.debug("package.json 설정 완료");
78
68
 
79
- Electron._logger.debug("npm install 시작");
80
- await this._exec("npm", ["install"], srcPath);
81
- Electron._logger.debug("npm install 완료");
69
+ // pnpm-workspace.yaml 생성 (상위 workspace 탐색 차단)
70
+ const workspaceYamlPath = pathx.posixResolve(this._srcPath, "pnpm-workspace.yaml");
71
+ if (!(await fsx.exists(workspaceYamlPath))) {
72
+ await fsx.write(workspaceYamlPath, "");
73
+ }
74
+
75
+ Electron._logger.debug("pnpm install 시작");
76
+ await this._exec("pnpm", ["install"], this._srcPath);
77
+ await this._exec("pnpm", ["approve-builds", "--all"], this._srcPath);
78
+ Electron._logger.debug("pnpm install 완료");
82
79
 
83
80
  const reinstallDeps = this._config.reinstallDependencies ?? [];
84
81
  if (reinstallDeps.length > 0) {
85
82
  Electron._logger.debug(`electron-rebuild 시작 (${reinstallDeps.join(", ")})`);
86
- await this._exec(this._localBin("electron-rebuild"), [], srcPath);
83
+ await this._exec("pnpm", ["exec", "electron-rebuild"], this._srcPath);
87
84
  Electron._logger.debug("electron-rebuild 완료");
88
85
  }
89
86
  Electron._logger.debug("initialize 완료");
@@ -91,9 +88,9 @@ export class Electron {
91
88
 
92
89
  async run(url: string): Promise<void> {
93
90
  Electron._logger.debug(`run 시작 (url: ${url})`);
94
- const srcPath = pathx.posixResolve(this._electronPath, "src");
95
91
 
96
92
  await this.initialize();
93
+ await this._copyPublicAssets();
97
94
 
98
95
  const esbuild = await import("esbuild");
99
96
  const entryPoint = pathx.posixResolve(this._pkgPath, "src/electron-main.ts");
@@ -104,7 +101,6 @@ export class Electron {
104
101
 
105
102
  const builtinModules = module.builtinModules.flatMap((m) => [m, `node:${m}`]);
106
103
  const reinstallDeps = this._config.reinstallDependencies ?? [];
107
- await fsx.mkdir(srcPath);
108
104
 
109
105
  let currentElectron: cpx.SpawnProcess | null = null;
110
106
  let isRestarting = false;
@@ -112,9 +108,10 @@ export class Electron {
112
108
 
113
109
  const spawnElectron = () => {
114
110
  Electron._logger.debug("Electron 프로세스 시작");
115
- currentElectron = cpx.spawn(this._localBin("electron"), ["."], {
116
- cwd: srcPath,
111
+ currentElectron = cpx.spawn("pnpm", ["exec", "electron", "."], {
112
+ cwd: this._srcPath,
117
113
  stdio: "inherit",
114
+ shell: true,
118
115
  reject: false,
119
116
  });
120
117
 
@@ -132,7 +129,7 @@ export class Electron {
132
129
  Electron._logger.debug("esbuild context 생성 시작");
133
130
  const ctx = await esbuild.context({
134
131
  entryPoints: [entryPoint],
135
- outfile: pathx.posixResolve(srcPath, "electron-main.js"),
132
+ outfile: pathx.posixResolve(this._srcPath, "electron-main.js"),
136
133
  platform: "node",
137
134
  target: "node20",
138
135
  format: "cjs",
@@ -205,18 +202,19 @@ export class Electron {
205
202
 
206
203
  async build(outPath: string): Promise<void> {
207
204
  Electron._logger.debug("build 시작");
208
- const srcPath = pathx.posixResolve(this._electronPath, "src");
205
+
206
+ await this.initialize();
209
207
 
210
208
  Electron._logger.debug("메인 프로세스 번들링 시작");
211
- await this._bundleMainProcess(srcPath);
209
+ await this._bundleMainProcess();
212
210
  Electron._logger.debug("메인 프로세스 번들링 완료");
213
211
 
214
212
  Electron._logger.debug("웹 에셋 복사 시작");
215
- await this._copyWebAssets(outPath, srcPath);
213
+ await this._copyWebAssets(outPath);
216
214
  Electron._logger.debug("웹 에셋 복사 완료");
217
215
 
218
216
  Electron._logger.debug("electron-builder 실행 시작");
219
- await this._runElectronBuilder(srcPath);
217
+ await this._runElectronBuilder();
220
218
  Electron._logger.debug("electron-builder 실행 완료");
221
219
 
222
220
  Electron._logger.debug("빌드 산출물 복사 시작");
@@ -230,14 +228,19 @@ export class Electron {
230
228
 
231
229
  //#region Private - Initialization
232
230
 
233
- private async _setupPackageJson(srcPath: string): Promise<void> {
234
- await fsx.mkdir(srcPath);
231
+ private async _setupNpmConf(): Promise<void> {
232
+ await fsx.mkdir(this._srcPath);
233
+
234
+ const mainDeps: Record<string, string | undefined> = {
235
+ ...this._npmConfig.dependencies,
236
+ ...this._npmConfig.devDependencies,
237
+ };
235
238
 
236
239
  const reinstallDeps = this._config.reinstallDependencies ?? [];
237
240
 
238
241
  const dependencies: Record<string, string> = {};
239
242
  for (const dep of reinstallDeps) {
240
- const version = this._npmConfig.dependencies?.[dep];
243
+ const version = mainDeps[dep];
241
244
  if (version != null) {
242
245
  dependencies[dep] = version;
243
246
  }
@@ -245,33 +248,39 @@ export class Electron {
245
248
 
246
249
  for (const excludePkg of this._exclude) {
247
250
  if (!(excludePkg in dependencies)) {
248
- const version = this._npmConfig.dependencies?.[excludePkg];
251
+ const version = mainDeps[excludePkg];
249
252
  if (version != null) {
250
253
  dependencies[excludePkg] = version;
251
254
  }
252
255
  }
253
256
  }
254
257
 
258
+ const devDependencies: Record<string, string> = {};
259
+ devDependencies["electron"] = "^41";
260
+ devDependencies["@electron/rebuild"] = "^4";
261
+ devDependencies["electron-builder"] = "^26";
262
+
255
263
  const packageJson: Record<string, unknown> = {
256
264
  name: this._npmConfig.name.replace(/^@/, "").replace(/\//, "-"),
257
265
  version: this._npmConfig.version,
258
266
  description: this._npmConfig.description,
259
267
  main: "electron-main.js",
260
268
  dependencies,
269
+ devDependencies,
261
270
  };
262
271
 
263
272
  if (this._config.postInstallScript != null) {
264
273
  packageJson["scripts"] = { postinstall: this._config.postInstallScript };
265
274
  }
266
275
 
267
- await fsx.writeJson(pathx.posixResolve(srcPath, "package.json"), packageJson, { space: 2 });
276
+ await fsx.writeJson(pathx.posixResolve(this._srcPath, "package.json"), packageJson, { space: 2 });
268
277
  }
269
278
 
270
279
  //#endregion
271
280
 
272
281
  //#region Private - Bundling
273
282
 
274
- private async _bundleMainProcess(outDir: string): Promise<void> {
283
+ private async _bundleMainProcess(): Promise<void> {
275
284
  const esbuild = await import("esbuild");
276
285
  const entryPoint = pathx.posixResolve(this._pkgPath, "src/electron-main.ts");
277
286
 
@@ -282,14 +291,12 @@ export class Electron {
282
291
  const builtinModules = module.builtinModules.flatMap((m) => [m, `node:${m}`]);
283
292
  const reinstallDeps = this._config.reinstallDependencies ?? [];
284
293
 
285
- await fsx.mkdir(outDir);
286
-
287
294
  const envBanner = createEnvBanner(this._config.env);
288
295
 
289
296
  Electron._logger.debug(`esbuild 번들링: ${entryPoint}`);
290
297
  await esbuild.build({
291
298
  entryPoints: [entryPoint],
292
- outfile: pathx.posixResolve(outDir, "electron-main.js"),
299
+ outfile: pathx.posixResolve(this._srcPath, "electron-main.js"),
293
300
  platform: "node",
294
301
  target: "node20",
295
302
  format: "cjs",
@@ -301,15 +308,27 @@ export class Electron {
301
308
 
302
309
  //#endregion
303
310
 
311
+ private async _copyPublicAssets(): Promise<void> {
312
+ const publicPath = pathx.posixResolve(this._pkgPath, "public");
313
+ if (!(await fsx.exists(publicPath))) return;
314
+
315
+ const items = await fsx.readdir(publicPath);
316
+ for (const item of items) {
317
+ const source = pathx.posixResolve(publicPath, item);
318
+ const dest = pathx.posixResolve(this._srcPath, item);
319
+ await fsx.copy(source, dest);
320
+ }
321
+ }
322
+
304
323
  //#region Private - Build
305
324
 
306
- private async _copyWebAssets(outPath: string, srcPath: string): Promise<void> {
325
+ private async _copyWebAssets(outPath: string): Promise<void> {
307
326
  const items = await fsx.readdir(outPath);
308
327
  for (const item of items) {
309
328
  if (item === "electron") continue;
310
329
 
311
330
  const source = pathx.posixResolve(outPath, item);
312
- const dest = pathx.posixResolve(srcPath, item);
331
+ const dest = pathx.posixResolve(this._srcPath, item);
313
332
  await fsx.copy(source, dest);
314
333
  }
315
334
  }
@@ -331,7 +350,7 @@ export class Electron {
331
350
  }
332
351
  }
333
352
 
334
- private async _runElectronBuilder(srcPath: string): Promise<void> {
353
+ private async _runElectronBuilder(): Promise<void> {
335
354
  if (!Electron._canCreateSymlink()) {
336
355
  throw new Error(
337
356
  "Symlink 생성 권한이 필요합니다. Windows 개발자 모드를 활성화하세요.",
@@ -349,7 +368,7 @@ export class Electron {
349
368
  },
350
369
  nsis: this._config.nsisOptions ?? {},
351
370
  directories: {
352
- app: srcPath,
371
+ app: this._srcPath,
353
372
  output: distPath,
354
373
  },
355
374
  removePackageScripts: false,
@@ -366,9 +385,9 @@ export class Electron {
366
385
 
367
386
  Electron._logger.debug(`electron-builder 설정: ${configFilePath}`);
368
387
  await this._exec(
369
- this._localBin("electron-builder"),
370
- ["--win", "--config", configFilePath],
371
- this._pkgPath,
388
+ "pnpm",
389
+ ["exec", "electron-builder", "--win", "--config", configFilePath],
390
+ this._srcPath,
372
391
  );
373
392
  }
374
393
 
@@ -112,7 +112,7 @@ export abstract class BaseEngine<
112
112
  logger.debug(`[${this._pkg.name}] run 시작 (js: ${output.js}, dts: ${output.dts}, env: ${output.env ?? "none"})`);
113
113
  this._createWorker();
114
114
  const result = await this._callBuild(output);
115
- logger.debug(`[${this._pkg.name}] run 완료 (success: ${result.success})`);
115
+ logger.debug(`[${this._pkg.name}] run 완료 (success: ${result.build.success})`);
116
116
  return result;
117
117
  }
118
118
 
@@ -53,7 +53,6 @@ export class NgtscEngine extends BaseEngine<
53
53
  });
54
54
 
55
55
  return {
56
- success: result.build.success,
57
56
  build: {
58
57
  success: result.build.success,
59
58
  errors: result.build.errors ?? [],
@@ -58,7 +58,6 @@ export class ServerEsbuildEngine extends BaseEngine<
58
58
  });
59
59
 
60
60
  return {
61
- success: result.build.success,
62
61
  build: {
63
62
  success: result.build.success,
64
63
  errors: result.build.errors ?? [],
@@ -54,7 +54,6 @@ export class TscEngine extends BaseEngine<
54
54
  });
55
55
 
56
56
  return {
57
- success: result.build.success,
58
57
  build: {
59
58
  success: result.build.success,
60
59
  errors: result.build.errors ?? [],
@@ -1,3 +1,5 @@
1
+ import fs from "node:fs";
2
+ import path from "path";
1
3
  import { Worker, type WorkerProxy } from "@simplysm/core-node";
2
4
  import { consola } from "consola";
3
5
  import type * as ClientWorkerModule from "../workers/client.worker";
@@ -81,7 +83,6 @@ export class ViteEngine implements BuildEngine {
81
83
 
82
84
  logger.debug(`[${this._pkg.name}] ViteEngine.run 완료 (success: ${result.success})`);
83
85
  return {
84
- success: result.success,
85
86
  build: {
86
87
  success: result.success,
87
88
  errors: result.errors ?? [],
@@ -196,6 +197,11 @@ export class ViteEngine implements BuildEngine {
196
197
  */
197
198
  async stop(): Promise<void> {
198
199
  logger.debug(`[${this._pkg.name}] ViteEngine stop 시작`);
200
+
201
+ // .dev-port 파일 삭제
202
+ const portFile = path.join(this._pkg.dir, "dist", ".dev-port");
203
+ try { fs.unlinkSync(portFile); } catch { /* 파일 없으면 무시 */ }
204
+
199
205
  await stopEngineWorker(this._worker, this._isWatchMode);
200
206
  this._worker = undefined;
201
207
  logger.debug(`[${this._pkg.name}] ViteEngine stop 완료`);
@@ -10,16 +10,6 @@ import type { BuildEngine, BuildPackageInfo, ClientPackageInfo, ServerPackageInf
10
10
 
11
11
  const logger = consola.withTag("sd:cli:engine");
12
12
 
13
- export { BaseEngine } from "./BaseEngine";
14
- export type { BaseEngineOptions, CommonBuildWorkerEvents, CommonBuildWorkerModule } from "./BaseEngine";
15
- export { NgtscEngine } from "./NgtscEngine";
16
- export type { NgtscEngineOptions } from "./NgtscEngine";
17
- export { ServerEsbuildEngine } from "./ServerEsbuildEngine";
18
- export type { ServerEsbuildEngineOptions } from "./ServerEsbuildEngine";
19
- export { TscEngine } from "./TscEngine";
20
- export type { TscEngineOptions } from "./TscEngine";
21
- export { ViteEngine } from "./ViteEngine";
22
- export type { ViteEngineOptions } from "./ViteEngine";
23
13
  export type { BuildEngine, BuildOutput, BuildPackageInfo, ClientPackageInfo, EngineResult, PackageInfo, ServerPackageInfo } from "./types";
24
14
 
25
15
  /**
@@ -45,7 +45,6 @@ export interface BuildOutput {
45
45
  * BuildEngine.run() 반환값
46
46
  */
47
47
  export interface EngineResult {
48
- success: boolean;
49
48
  build: {
50
49
  success: boolean;
51
50
  errors: string[];
@@ -7,7 +7,6 @@
7
7
  export class SignalHandler {
8
8
  private _terminateResolver: (() => void) | null = null;
9
9
  private readonly _terminatePromise: Promise<void>;
10
- private _terminated = false;
11
10
 
12
11
  constructor() {
13
12
  this._terminatePromise = new Promise((resolve) => {
@@ -17,8 +16,8 @@ export class SignalHandler {
17
16
  const handler = () => {
18
17
  process.off("SIGINT", handler);
19
18
  process.off("SIGTERM", handler);
20
- this._terminated = true;
21
19
  this._terminateResolver?.();
20
+ this._terminateResolver = null;
22
21
  };
23
22
 
24
23
  process.on("SIGINT", handler);
@@ -32,21 +31,12 @@ export class SignalHandler {
32
31
  return this._terminatePromise;
33
32
  }
34
33
 
35
- /**
36
- * 종료 여부를 확인한다
37
- */
38
- isTerminated(): boolean {
39
- return this._terminated;
40
- }
41
-
42
34
  /**
43
35
  * 프로그래밍 방식으로 종료를 요청한다
44
- * (테스트나 외부에서 종료를 트리거할 때 사용)
36
+ * (테스트에서 종료를 트리거할 때 사용)
45
37
  */
46
38
  requestTermination(): void {
47
- if (!this._terminated) {
48
- this._terminated = true;
49
- this._terminateResolver?.();
50
- }
39
+ this._terminateResolver?.();
40
+ this._terminateResolver = null;
51
41
  }
52
42
  }
@@ -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
  /**
@@ -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(", ")}`,
@@ -197,11 +192,11 @@ export function createCliParser(argv: string[]): Argv {
197
192
  describe: "Client package to run (e.g., my-client-app)",
198
193
  })
199
194
  .options({
200
- "url": {
195
+ url: {
201
196
  type: "string",
202
197
  description: "Dev server URL (auto-detected from sd.config.ts if omitted)",
203
198
  },
204
- "opt": {
199
+ opt: {
205
200
  type: "string",
206
201
  array: true,
207
202
  alias: "o",
@@ -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;