@simplysm/sd-cli 13.0.28 → 13.0.30

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 (135) hide show
  1. package/README.md +30 -28
  2. package/dist/capacitor/capacitor.d.ts.map +1 -1
  3. package/dist/capacitor/capacitor.js +55 -10
  4. package/dist/capacitor/capacitor.js.map +1 -1
  5. package/dist/commands/add-client.d.ts.map +1 -1
  6. package/dist/commands/add-client.js +5 -1
  7. package/dist/commands/add-client.js.map +1 -1
  8. package/dist/commands/build.d.ts.map +1 -1
  9. package/dist/commands/build.js +3 -1
  10. package/dist/commands/build.js.map +1 -1
  11. package/dist/commands/device.d.ts.map +1 -1
  12. package/dist/commands/device.js +9 -3
  13. package/dist/commands/device.js.map +1 -1
  14. package/dist/commands/init.d.ts.map +1 -1
  15. package/dist/commands/init.js +3 -1
  16. package/dist/commands/init.js.map +1 -1
  17. package/dist/commands/lint.d.ts.map +1 -1
  18. package/dist/commands/lint.js +17 -4
  19. package/dist/commands/lint.js.map +1 -1
  20. package/dist/commands/publish.d.ts.map +1 -1
  21. package/dist/commands/publish.js +16 -4
  22. package/dist/commands/publish.js.map +1 -1
  23. package/dist/commands/typecheck.d.ts.map +1 -1
  24. package/dist/commands/typecheck.js.map +1 -1
  25. package/dist/commands/watch.d.ts.map +1 -1
  26. package/dist/commands/watch.js +3 -1
  27. package/dist/commands/watch.js.map +1 -1
  28. package/dist/electron/electron.d.ts.map +1 -1
  29. package/dist/electron/electron.js +8 -2
  30. package/dist/electron/electron.js.map +1 -1
  31. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  32. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  33. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
  34. package/dist/orchestrators/DevOrchestrator.js +24 -7
  35. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  36. package/dist/orchestrators/WatchOrchestrator.d.ts.map +1 -1
  37. package/dist/orchestrators/WatchOrchestrator.js +4 -1
  38. package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
  39. package/dist/sd-cli-entry.js.map +1 -1
  40. package/dist/sd-cli.js +6 -1
  41. package/dist/sd-cli.js.map +1 -1
  42. package/dist/utils/config-editor.d.ts.map +1 -1
  43. package/dist/utils/config-editor.js +3 -1
  44. package/dist/utils/config-editor.js.map +1 -1
  45. package/dist/utils/copy-public.d.ts.map +1 -1
  46. package/dist/utils/copy-public.js.map +1 -1
  47. package/dist/utils/esbuild-config.d.ts.map +1 -1
  48. package/dist/utils/esbuild-config.js +6 -2
  49. package/dist/utils/esbuild-config.js.map +1 -1
  50. package/dist/utils/output-utils.d.ts.map +1 -1
  51. package/dist/utils/output-utils.js.map +1 -1
  52. package/dist/utils/package-utils.d.ts +4 -3
  53. package/dist/utils/package-utils.d.ts.map +1 -1
  54. package/dist/utils/package-utils.js +10 -2
  55. package/dist/utils/package-utils.js.map +1 -1
  56. package/dist/utils/rebuild-manager.d.ts.map +1 -1
  57. package/dist/utils/rebuild-manager.js.map +1 -1
  58. package/dist/utils/replace-deps.d.ts.map +1 -1
  59. package/dist/utils/replace-deps.js +3 -1
  60. package/dist/utils/replace-deps.js.map +1 -1
  61. package/dist/utils/sd-config.d.ts.map +1 -1
  62. package/dist/utils/sd-config.js.map +1 -1
  63. package/dist/utils/spawn.d.ts.map +1 -1
  64. package/dist/utils/spawn.js +4 -2
  65. package/dist/utils/spawn.js.map +1 -1
  66. package/dist/utils/template.d.ts.map +1 -1
  67. package/dist/utils/template.js +6 -1
  68. package/dist/utils/template.js.map +1 -1
  69. package/dist/utils/tsconfig.d.ts.map +1 -1
  70. package/dist/utils/tsconfig.js +3 -1
  71. package/dist/utils/tsconfig.js.map +1 -1
  72. package/dist/utils/typecheck-serialization.d.ts.map +1 -1
  73. package/dist/utils/typecheck-serialization.js +2 -1
  74. package/dist/utils/typecheck-serialization.js.map +1 -1
  75. package/dist/utils/vite-config.d.ts.map +1 -1
  76. package/dist/utils/vite-config.js +24 -17
  77. package/dist/utils/vite-config.js.map +1 -1
  78. package/dist/utils/worker-events.d.ts.map +1 -1
  79. package/dist/utils/worker-events.js.map +1 -1
  80. package/dist/utils/worker-utils.d.ts.map +1 -1
  81. package/dist/utils/worker-utils.js.map +1 -1
  82. package/dist/workers/client.worker.d.ts.map +1 -1
  83. package/dist/workers/client.worker.js +10 -2
  84. package/dist/workers/client.worker.js.map +1 -1
  85. package/dist/workers/dts.worker.d.ts.map +1 -1
  86. package/dist/workers/dts.worker.js +14 -4
  87. package/dist/workers/dts.worker.js.map +1 -1
  88. package/dist/workers/library.worker.d.ts.map +1 -1
  89. package/dist/workers/library.worker.js +17 -3
  90. package/dist/workers/library.worker.js.map +1 -1
  91. package/dist/workers/server-runtime.worker.d.ts.map +1 -1
  92. package/dist/workers/server-runtime.worker.js +3 -1
  93. package/dist/workers/server-runtime.worker.js.map +1 -1
  94. package/dist/workers/server.worker.d.ts.map +1 -1
  95. package/dist/workers/server.worker.js +20 -4
  96. package/dist/workers/server.worker.js.map +1 -1
  97. package/package.json +5 -5
  98. package/src/capacitor/capacitor.ts +78 -18
  99. package/src/commands/add-client.ts +5 -1
  100. package/src/commands/build.ts +4 -1
  101. package/src/commands/device.ts +9 -3
  102. package/src/commands/init.ts +3 -1
  103. package/src/commands/lint.ts +22 -5
  104. package/src/commands/publish.ts +22 -6
  105. package/src/commands/typecheck.ts +5 -1
  106. package/src/commands/watch.ts +4 -1
  107. package/src/electron/electron.ts +14 -3
  108. package/src/orchestrators/BuildOrchestrator.ts +15 -4
  109. package/src/orchestrators/DevOrchestrator.ts +42 -12
  110. package/src/orchestrators/WatchOrchestrator.ts +5 -1
  111. package/src/sd-cli-entry.ts +4 -1
  112. package/src/sd-cli.ts +6 -1
  113. package/src/utils/config-editor.ts +15 -5
  114. package/src/utils/copy-public.ts +4 -1
  115. package/src/utils/esbuild-config.ts +18 -4
  116. package/src/utils/output-utils.ts +4 -1
  117. package/src/utils/package-utils.ts +16 -4
  118. package/src/utils/rebuild-manager.ts +4 -1
  119. package/src/utils/replace-deps.ts +7 -2
  120. package/src/utils/sd-config.ts +5 -1
  121. package/src/utils/spawn.ts +3 -1
  122. package/src/utils/template.ts +6 -1
  123. package/src/utils/tsconfig.ts +7 -2
  124. package/src/utils/typecheck-serialization.ts +6 -2
  125. package/src/utils/vite-config.ts +36 -20
  126. package/src/utils/worker-events.ts +4 -1
  127. package/src/utils/worker-utils.ts +4 -1
  128. package/src/workers/client.worker.ts +12 -3
  129. package/src/workers/dts.worker.ts +24 -7
  130. package/src/workers/library.worker.ts +17 -3
  131. package/src/workers/server-runtime.worker.ts +3 -1
  132. package/src/workers/server.worker.ts +23 -5
  133. package/templates/add-client/__CLIENT__/package.json.hbs +1 -1
  134. package/templates/add-server/__SERVER__/package.json.hbs +2 -2
  135. package/templates/init/package.json.hbs +3 -3
@@ -26,7 +26,12 @@ export interface LintOptions {
26
26
  //#region Utilities
27
27
 
28
28
  /** ESLint 설정 파일 탐색 순서 */
29
- const ESLINT_CONFIG_FILES = ["eslint.config.ts", "eslint.config.mts", "eslint.config.js", "eslint.config.mjs"] as const;
29
+ const ESLINT_CONFIG_FILES = [
30
+ "eslint.config.ts",
31
+ "eslint.config.mts",
32
+ "eslint.config.js",
33
+ "eslint.config.mjs",
34
+ ] as const;
30
35
 
31
36
  /** Stylelint 설정 파일 탐색 순서 */
32
37
  const STYLELINT_CONFIG_FILES = [
@@ -66,7 +71,9 @@ export async function loadIgnorePatterns(cwd: string): Promise<string[]> {
66
71
  }
67
72
 
68
73
  if (configPath == null) {
69
- throw new SdError(`ESLint 설정 파일을 찾을 수 없습니다 (cwd: ${cwd}): ${ESLINT_CONFIG_FILES.join(", ")}`);
74
+ throw new SdError(
75
+ `ESLint 설정 파일을 찾을 수 없습니다 (cwd: ${cwd}): ${ESLINT_CONFIG_FILES.join(", ")}`,
76
+ );
70
77
  }
71
78
 
72
79
  const jiti = createJiti(import.meta.url);
@@ -75,7 +82,11 @@ export async function loadIgnorePatterns(cwd: string): Promise<string[]> {
75
82
  let configs: unknown;
76
83
  if (Array.isArray(configModule)) {
77
84
  configs = configModule;
78
- } else if (configModule != null && typeof configModule === "object" && "default" in configModule) {
85
+ } else if (
86
+ configModule != null &&
87
+ typeof configModule === "object" &&
88
+ "default" in configModule
89
+ ) {
79
90
  configs = configModule.default;
80
91
  } else {
81
92
  throw new SdError(`ESLint 설정 파일이 올바른 형식이 아닙니다: ${configPath}`);
@@ -240,14 +251,20 @@ export async function runLint(options: LintOptions): Promise<void> {
240
251
  );
241
252
 
242
253
  if (stylelintErrorCount > 0) {
243
- logger.error("Stylelint 에러 발생", { errorCount: stylelintErrorCount, warningCount: stylelintWarningCount });
254
+ logger.error("Stylelint 에러 발생", {
255
+ errorCount: stylelintErrorCount,
256
+ warningCount: stylelintWarningCount,
257
+ });
244
258
  } else if (stylelintWarningCount > 0) {
245
259
  logger.info("Stylelint 완료 (경고 있음)", {
246
260
  errorCount: stylelintErrorCount,
247
261
  warningCount: stylelintWarningCount,
248
262
  });
249
263
  } else {
250
- logger.info("Stylelint 완료", { errorCount: stylelintErrorCount, warningCount: stylelintWarningCount });
264
+ logger.info("Stylelint 완료", {
265
+ errorCount: stylelintErrorCount,
266
+ warningCount: stylelintWarningCount,
267
+ });
251
268
  }
252
269
 
253
270
  // Stylelint formatter 출력
@@ -279,7 +279,9 @@ async function upgradeVersion(
279
279
 
280
280
  // prerelease 여부에 따라 증가 방식 결정
281
281
  const newVersion =
282
- prereleaseInfo !== null ? semver.inc(currentVersion, "prerelease")! : semver.inc(currentVersion, "patch")!;
282
+ prereleaseInfo !== null
283
+ ? semver.inc(currentVersion, "prerelease")!
284
+ : semver.inc(currentVersion, "patch")!;
283
285
 
284
286
  if (dryRun) {
285
287
  // dry-run: 파일 수정 없이 새 버전만 반환
@@ -368,7 +370,9 @@ async function publishPackage(
368
370
  const remotePath = publishConfig.path ?? "/";
369
371
 
370
372
  if (dryRun) {
371
- logger.info(`[DRY-RUN] [${pkgName}] ${publishConfig.type} 업로드: ${distPath} → ${remotePath}`);
373
+ logger.info(
374
+ `[DRY-RUN] [${pkgName}] ${publishConfig.type} 업로드: ${distPath} → ${remotePath}`,
375
+ );
372
376
  } else {
373
377
  logger.debug(`[${pkgName}] ${publishConfig.type} 업로드: ${distPath} → ${remotePath}`);
374
378
  await StorageFactory.connect(
@@ -499,7 +503,9 @@ export async function runPublish(options: PublishOptions): Promise<void> {
499
503
  workspaceGlobs.push(...parseWorkspaceGlobs(yamlContent));
500
504
  }
501
505
 
502
- const allPkgPaths = (await Promise.all(workspaceGlobs.map((item) => fsGlob(path.resolve(cwd, item)))))
506
+ const allPkgPaths = (
507
+ await Promise.all(workspaceGlobs.map((item) => fsGlob(path.resolve(cwd, item))))
508
+ )
503
509
  .flat()
504
510
  .filter((item) => !path.basename(item).includes("."));
505
511
 
@@ -587,7 +593,13 @@ export async function runPublish(options: PublishOptions): Promise<void> {
587
593
  if (diff.trim() !== "" || stagedDiff.trim() !== "") {
588
594
  logger.info("커밋되지 않은 변경사항 감지. claude 자동 커밋 시도...");
589
595
  try {
590
- await spawn("claude", ["-p", "/sd-commit all", "--dangerously-skip-permissions", "--model", "haiku"]);
596
+ await spawn("claude", [
597
+ "-p",
598
+ "/sd-commit all",
599
+ "--dangerously-skip-permissions",
600
+ "--model",
601
+ "haiku",
602
+ ]);
591
603
  } catch (e) {
592
604
  throw new Error(
593
605
  "자동 커밋에 실패했습니다. 수동으로 커밋 후 다시 시도하세요.\n" +
@@ -599,7 +611,9 @@ export async function runPublish(options: PublishOptions): Promise<void> {
599
611
  const recheckDiff = await spawn("git", ["diff", "--name-only"]);
600
612
  const recheckStaged = await spawn("git", ["diff", "--cached", "--name-only"]);
601
613
  if (recheckDiff.trim() !== "" || recheckStaged.trim() !== "") {
602
- throw new Error("자동 커밋 후에도 미커밋 변경사항이 남아있습니다.\n" + recheckDiff + recheckStaged);
614
+ throw new Error(
615
+ "자동 커밋 후에도 미커밋 변경사항이 남아있습니다.\n" + recheckDiff + recheckStaged,
616
+ );
603
617
  }
604
618
  logger.info("자동 커밋 완료.");
605
619
  }
@@ -801,7 +815,9 @@ export async function runPublish(options: PublishOptions): Promise<void> {
801
815
  }
802
816
  } catch (err) {
803
817
  // postPublish 실패 시 경고만 출력 (배포 롤백 불가)
804
- logger.warn(`postPublish 스크립트 실패 (계속 진행): ${err instanceof Error ? err.message : err}`);
818
+ logger.warn(
819
+ `postPublish 스크립트 실패 (계속 진행): ${err instanceof Error ? err.message : err}`,
820
+ );
805
821
  }
806
822
  }
807
823
  }
@@ -68,7 +68,11 @@ function toTypecheckEnvs(target: string | undefined): TypecheckEnv[] {
68
68
  * scripts 타겟 패키지는 제외합니다.
69
69
  * @internal 테스트용으로 export
70
70
  */
71
- export function extractPackages(fileNames: string[], cwd: string, config: SdConfig): Map<string, PackageInfo> {
71
+ export function extractPackages(
72
+ fileNames: string[],
73
+ cwd: string,
74
+ config: SdConfig,
75
+ ): Map<string, PackageInfo> {
72
76
  const packages = new Map<string, PackageInfo>();
73
77
 
74
78
  for (const fileName of fileNames) {
@@ -1,5 +1,8 @@
1
1
  // packages/cli/src/commands/watch.ts
2
- import { WatchOrchestrator, type WatchOrchestratorOptions } from "../orchestrators/WatchOrchestrator";
2
+ import {
3
+ WatchOrchestrator,
4
+ type WatchOrchestratorOptions,
5
+ } from "../orchestrators/WatchOrchestrator";
3
6
 
4
7
  /**
5
8
  * Watch 명령 옵션 (하위 호환성)
@@ -61,7 +61,12 @@ export class Electron {
61
61
  /**
62
62
  * 명령어 실행 (로깅 포함)
63
63
  */
64
- private async _exec(cmd: string, args: string[], cwd: string, env?: Record<string, string>): Promise<string> {
64
+ private async _exec(
65
+ cmd: string,
66
+ args: string[],
67
+ cwd: string,
68
+ env?: Record<string, string>,
69
+ ): Promise<string> {
65
70
  Electron._logger.debug(`실행 명령: ${cmd} ${args.join(" ")}`);
66
71
  const result = await spawn(cmd, args, { cwd, env });
67
72
  Electron._logger.debug(`실행 결과: ${result}`);
@@ -263,7 +268,9 @@ export class Electron {
263
268
  */
264
269
  private async _runElectronBuilder(srcPath: string): Promise<void> {
265
270
  if (!Electron._canCreateSymlink()) {
266
- throw new Error("Electron 빌드를 위해서는 Symlink 생성 권한이 필요합니다. 윈도우의 개발자모드를 활성화하세요.");
271
+ throw new Error(
272
+ "Electron 빌드를 위해서는 Symlink 생성 권한이 필요합니다. 윈도우의 개발자모드를 활성화하세요.",
273
+ );
267
274
  }
268
275
 
269
276
  const distPath = path.resolve(this._electronPath, "dist");
@@ -292,7 +299,11 @@ export class Electron {
292
299
  const configFilePath = path.resolve(this._electronPath, "builder-config.json");
293
300
  await fsWriteJson(configFilePath, builderConfig, { space: 2 });
294
301
 
295
- await this._exec("npx", ["electron-builder", "--win", "--config", configFilePath], this._pkgPath);
302
+ await this._exec(
303
+ "npx",
304
+ ["electron-builder", "--win", "--config", configFilePath],
305
+ this._pkgPath,
306
+ );
296
307
  }
297
308
 
298
309
  /**
@@ -3,7 +3,12 @@ import ts from "typescript";
3
3
  import { Worker, type WorkerProxy, fsRm } from "@simplysm/core-node";
4
4
  import "@simplysm/core-common";
5
5
  import { consola } from "consola";
6
- import type { SdConfig, SdBuildPackageConfig, SdClientPackageConfig, SdServerPackageConfig } from "../sd-config.types";
6
+ import type {
7
+ SdConfig,
8
+ SdBuildPackageConfig,
9
+ SdClientPackageConfig,
10
+ SdServerPackageConfig,
11
+ } from "../sd-config.types";
7
12
  import { loadSdConfig } from "../utils/sd-config";
8
13
  import { getVersion } from "../utils/build-env";
9
14
  import { setupReplaceDeps } from "../utils/replace-deps";
@@ -69,7 +74,11 @@ interface ClassifiedPackages {
69
74
  function classifyPackages(
70
75
  packages: Record<
71
76
  string,
72
- SdBuildPackageConfig | SdClientPackageConfig | SdServerPackageConfig | { target: "scripts" } | undefined
77
+ | SdBuildPackageConfig
78
+ | SdClientPackageConfig
79
+ | SdServerPackageConfig
80
+ | { target: "scripts" }
81
+ | undefined
73
82
  >,
74
83
  targets: string[],
75
84
  ): ClassifiedPackages {
@@ -260,7 +269,8 @@ export class BuildOrchestrator {
260
269
  // JS 빌드와 DTS 생성을 병렬 실행
261
270
  const libraryWorker: WorkerProxy<typeof LibraryWorkerModule> =
262
271
  Worker.create<typeof LibraryWorkerModule>(libraryWorkerPath);
263
- const dtsWorker: WorkerProxy<typeof DtsWorkerModule> = Worker.create<typeof DtsWorkerModule>(dtsWorkerPath);
272
+ const dtsWorker: WorkerProxy<typeof DtsWorkerModule> =
273
+ Worker.create<typeof DtsWorkerModule>(dtsWorkerPath);
264
274
 
265
275
  try {
266
276
  const [buildResult, dtsResult] = await Promise.all([
@@ -313,7 +323,8 @@ export class BuildOrchestrator {
313
323
  // Vite 빌드와 타입체크를 병렬 실행
314
324
  const clientWorker: WorkerProxy<typeof ClientWorkerModule> =
315
325
  Worker.create<typeof ClientWorkerModule>(clientWorkerPath);
316
- const dtsWorker: WorkerProxy<typeof DtsWorkerModule> = Worker.create<typeof DtsWorkerModule>(dtsWorkerPath);
326
+ const dtsWorker: WorkerProxy<typeof DtsWorkerModule> =
327
+ Worker.create<typeof DtsWorkerModule>(dtsWorkerPath);
317
328
 
318
329
  try {
319
330
  const clientConfig: SdClientPackageConfig = {
@@ -5,12 +5,20 @@ import type { SdConfig, SdClientPackageConfig, SdServerPackageConfig } from "../
5
5
  import { consola } from "consola";
6
6
  import { loadSdConfig } from "../utils/sd-config";
7
7
  import { getVersion } from "../utils/build-env";
8
- import { setupReplaceDeps, watchReplaceDeps, type WatchReplaceDepResult } from "../utils/replace-deps";
8
+ import {
9
+ setupReplaceDeps,
10
+ watchReplaceDeps,
11
+ type WatchReplaceDepResult,
12
+ } from "../utils/replace-deps";
9
13
  import type * as ClientWorkerModule from "../workers/client.worker";
10
14
  import type * as ServerWorkerModule from "../workers/server.worker";
11
15
  import type * as ServerRuntimeWorkerModule from "../workers/server-runtime.worker";
12
16
  import { Capacitor } from "../capacitor/capacitor";
13
- import { filterPackagesByTargets, getWatchScopes, type PackageResult } from "../utils/package-utils";
17
+ import {
18
+ filterPackagesByTargets,
19
+ getWatchScopes,
20
+ type PackageResult,
21
+ } from "../utils/package-utils";
14
22
  import { printErrors, printServers } from "../utils/output-utils";
15
23
  import { RebuildManager } from "../utils/rebuild-manager";
16
24
  import {
@@ -86,7 +94,10 @@ export class DevOrchestrator {
86
94
  mainJsPath?: string;
87
95
  }
88
96
  >();
89
- private readonly _serverRuntimeWorkers = new Map<string, WorkerProxy<typeof ServerRuntimeWorkerModule>>();
97
+ private readonly _serverRuntimeWorkers = new Map<
98
+ string,
99
+ WorkerProxy<typeof ServerRuntimeWorkerModule>
100
+ >();
90
101
 
91
102
  // State
92
103
  private readonly _results = new Map<string, PackageResult>();
@@ -125,7 +136,11 @@ export class DevOrchestrator {
125
136
 
126
137
  // sd.config.ts 로드 (dev는 패키지 빌드 정보가 필요하므로 필수)
127
138
  try {
128
- this._sdConfig = await loadSdConfig({ cwd: this._cwd, dev: true, opt: this._options.options });
139
+ this._sdConfig = await loadSdConfig({
140
+ cwd: this._cwd,
141
+ dev: true,
142
+ opt: this._options.options,
143
+ });
129
144
  this._logger.debug("sd.config.ts 로드 완료");
130
145
  } catch (err) {
131
146
  this._logger.error(`sd.config.ts 로드 실패: ${err instanceof Error ? err.message : err}`);
@@ -146,7 +161,7 @@ export class DevOrchestrator {
146
161
  // watchScopes 생성 (루트 package.json에서 scope 추출)
147
162
  const rootPkgJsonPath = path.join(this._cwd, "package.json");
148
163
  const rootPkgName = JSON.parse(fs.readFileSync(rootPkgJsonPath, "utf-8")).name as string;
149
- this._watchScopes = getWatchScopes(rootPkgName);
164
+ this._watchScopes = getWatchScopes(rootPkgName, this._sdConfig.replaceDeps);
150
165
 
151
166
  // targets 필터링
152
167
  const allPackages = filterPackagesByTargets(this._sdConfig.packages, targets);
@@ -213,7 +228,8 @@ export class DevOrchestrator {
213
228
  this._standaloneClientWorkers = this._clientPackages
214
229
  .filter(
215
230
  ({ config }) =>
216
- typeof config.server === "number" || (typeof config.server === "string" && !serverNames.has(config.server)),
231
+ typeof config.server === "number" ||
232
+ (typeof config.server === "string" && !serverNames.has(config.server)),
217
233
  )
218
234
  .map(({ name, config }) => ({
219
235
  name,
@@ -247,7 +263,10 @@ export class DevOrchestrator {
247
263
 
248
264
  // Vite client 빌드 Promise 미리 생성 (서버 연결 클라이언트)
249
265
  const viteClientBuildPromises = new Map<string, Promise<void>>();
250
- const viteClientReadyPromises = new Map<string, { promise: Promise<void>; resolver: () => void }>();
266
+ const viteClientReadyPromises = new Map<
267
+ string,
268
+ { promise: Promise<void>; resolver: () => void }
269
+ >();
251
270
  for (const workerInfo of this._viteClientWorkers) {
252
271
  viteClientBuildPromises.set(
253
272
  workerInfo.name,
@@ -260,7 +279,10 @@ export class DevOrchestrator {
260
279
  const readyPromise = new Promise<void>((resolve) => {
261
280
  readyResolver = resolve;
262
281
  });
263
- viteClientReadyPromises.set(workerInfo.name, { promise: readyPromise, resolver: readyResolver });
282
+ viteClientReadyPromises.set(workerInfo.name, {
283
+ promise: readyPromise,
284
+ resolver: readyResolver,
285
+ });
264
286
  }
265
287
 
266
288
  // Server Build Worker 및 Promise 생성
@@ -277,7 +299,10 @@ export class DevOrchestrator {
277
299
  }
278
300
 
279
301
  // Server Runtime Promise (초기 서버 시작 완료 대기용)
280
- const serverRuntimePromises = new Map<string, { promise: Promise<void>; resolver: () => void }>();
302
+ const serverRuntimePromises = new Map<
303
+ string,
304
+ { promise: Promise<void>; resolver: () => void }
305
+ >();
281
306
  for (const { name } of this._serverPackages) {
282
307
  let resolver!: () => void;
283
308
  const promise = new Promise<void>((resolve) => {
@@ -448,7 +473,8 @@ export class DevOrchestrator {
448
473
  }
449
474
 
450
475
  // 새 Server Runtime Worker 생성 및 시작
451
- const runtimeWorker = Worker.create<typeof ServerRuntimeWorkerModule>(serverRuntimeWorkerPath);
476
+ const runtimeWorker =
477
+ Worker.create<typeof ServerRuntimeWorkerModule>(serverRuntimeWorkerPath);
452
478
  this._serverRuntimeWorkers.set(serverName, runtimeWorker);
453
479
 
454
480
  // 이 서버에 연결된 클라이언트들의 Vite 서버가 준비될 때까지 대기
@@ -456,7 +482,9 @@ export class DevOrchestrator {
456
482
  const clientReadyPromises = connectedClients
457
483
  .map((clientName) => viteClientReadyPromises.get(clientName)?.promise)
458
484
  .filter((p): p is Promise<void> => p != null);
459
- this._logger.debug(`[${serverName}] 클라이언트 대기: ${String(clientReadyPromises.length)}개`);
485
+ this._logger.debug(
486
+ `[${serverName}] 클라이언트 대기: ${String(clientReadyPromises.length)}개`,
487
+ );
460
488
  if (clientReadyPromises.length > 0) {
461
489
  await Promise.all(clientReadyPromises);
462
490
  }
@@ -651,7 +679,9 @@ export class DevOrchestrator {
651
679
  })),
652
680
  ];
653
681
 
654
- const initialResults = await Promise.allSettled(initialBuildPromises.map((item) => item.promise));
682
+ const initialResults = await Promise.allSettled(
683
+ initialBuildPromises.map((item) => item.promise),
684
+ );
655
685
 
656
686
  initialResults.forEach((result, index) => {
657
687
  const taskName = initialBuildPromises[index].name;
@@ -3,7 +3,11 @@ import { consola } from "consola";
3
3
  import type { BuildTarget, SdConfig, SdPackageConfig } from "../sd-config.types";
4
4
  import { loadSdConfig } from "../utils/sd-config";
5
5
  import { filterPackagesByTargets } from "../utils/package-utils";
6
- import { setupReplaceDeps, watchReplaceDeps, type WatchReplaceDepResult } from "../utils/replace-deps";
6
+ import {
7
+ setupReplaceDeps,
8
+ watchReplaceDeps,
9
+ type WatchReplaceDepResult,
10
+ } from "../utils/replace-deps";
7
11
  import { printErrors } from "../utils/output-utils";
8
12
  import { RebuildManager } from "../utils/rebuild-manager";
9
13
  import { ResultCollector } from "../infra/ResultCollector";
@@ -325,6 +325,9 @@ export function createCliParser(argv: string[]): Argv {
325
325
  // CLI로 직접 실행될 때만 파싱 수행
326
326
  // ESM에서 메인 모듈 판별: import.meta.url과 process.argv[1]을 정규화하여 비교
327
327
  const cliEntryPath = process.argv.at(1);
328
- if (cliEntryPath != null && fileURLToPath(import.meta.url) === fs.realpathSync(path.resolve(cliEntryPath))) {
328
+ if (
329
+ cliEntryPath != null &&
330
+ fileURLToPath(import.meta.url) === fs.realpathSync(path.resolve(cliEntryPath))
331
+ ) {
329
332
  await createCliParser(hideBin(process.argv)).parse();
330
333
  }
package/src/sd-cli.ts CHANGED
@@ -42,7 +42,12 @@ if (isDev) {
42
42
  const cliEntryFilePath = path.join(__dirname, "sd-cli-entry.js");
43
43
  const child = spawn(
44
44
  "node",
45
- ["--max-old-space-size=8192", "--max-semi-space-size=16", cliEntryFilePath, ...process.argv.slice(2)],
45
+ [
46
+ "--max-old-space-size=8192",
47
+ "--max-semi-space-size=16",
48
+ cliEntryFilePath,
49
+ ...process.argv.slice(2),
50
+ ],
46
51
  { stdio: "inherit" },
47
52
  );
48
53
  child.on("spawn", () => {
@@ -31,7 +31,9 @@ function findPackagesObject(configPath: string): {
31
31
  }
32
32
 
33
33
  // "packages" 프로퍼티 찾기
34
- const packagesProp = returnObj.getPropertyOrThrow("packages").asKindOrThrow(SyntaxKind.PropertyAssignment);
34
+ const packagesProp = returnObj
35
+ .getPropertyOrThrow("packages")
36
+ .asKindOrThrow(SyntaxKind.PropertyAssignment);
35
37
  const packagesObj = packagesProp.getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
36
38
 
37
39
  return { project, packagesObj };
@@ -50,7 +52,8 @@ export function addPackageToSdConfig(
50
52
  const { project, packagesObj } = findPackagesObject(configPath);
51
53
 
52
54
  // 이미 존재하는지 확인 (따옴표 있는 형태와 없는 형태 모두 체크)
53
- const existing = packagesObj.getProperty(`"${packageName}"`) ?? packagesObj.getProperty(packageName);
55
+ const existing =
56
+ packagesObj.getProperty(`"${packageName}"`) ?? packagesObj.getProperty(packageName);
54
57
  if (existing) {
55
58
  return false;
56
59
  }
@@ -72,10 +75,15 @@ export function addPackageToSdConfig(
72
75
  /**
73
76
  * sd.config.ts에서 특정 클라이언트의 server 필드를 설정한다.
74
77
  */
75
- export function setClientServerInSdConfig(configPath: string, clientName: string, serverName: string): void {
78
+ export function setClientServerInSdConfig(
79
+ configPath: string,
80
+ clientName: string,
81
+ serverName: string,
82
+ ): void {
76
83
  const { project, packagesObj } = findPackagesObject(configPath);
77
84
 
78
- const clientPropNode = packagesObj.getProperty(`"${clientName}"`) ?? packagesObj.getProperty(clientName);
85
+ const clientPropNode =
86
+ packagesObj.getProperty(`"${clientName}"`) ?? packagesObj.getProperty(clientName);
79
87
  if (clientPropNode == null) {
80
88
  throw new Error(`클라이언트 "${clientName}"을(를) sd.config.ts에서 찾을 수 없습니다.`);
81
89
  }
@@ -108,7 +116,9 @@ export function addTailwindToEslintConfig(configPath: string, clientName: string
108
116
  const sourceFile = project.addSourceFileAtPath(configPath);
109
117
 
110
118
  // default export 배열 찾기
111
- const defaultExport = sourceFile.getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
119
+ const defaultExport = sourceFile.getFirstDescendantByKindOrThrow(
120
+ SyntaxKind.ArrayLiteralExpression,
121
+ );
112
122
 
113
123
  // tailwindcss 설정이 이미 있는지 확인
114
124
  const text = defaultExport.getText();
@@ -47,7 +47,10 @@ export async function copyPublicFiles(pkgDir: string, includeDev: boolean): Prom
47
47
  * @param includeDev public-dev/ 포함 여부 (dev 모드에서만 true)
48
48
  * @returns FsWatcher 인스턴스 (shutdown 시 close() 호출 필요) 또는 watch할 대상이 없으면 undefined
49
49
  */
50
- export async function watchPublicFiles(pkgDir: string, includeDev: boolean): Promise<FsWatcher | undefined> {
50
+ export async function watchPublicFiles(
51
+ pkgDir: string,
52
+ includeDev: boolean,
53
+ ): Promise<FsWatcher | undefined> {
51
54
  const distDir = path.join(pkgDir, "dist");
52
55
  const publicDir = path.join(pkgDir, "public");
53
56
  const publicDevDir = path.join(pkgDir, "public-dev");
@@ -99,7 +99,9 @@ export function createLibraryEsbuildOptions(options: LibraryEsbuildOptions): esb
99
99
  target: options.target === "node" ? "node20" : "chrome84",
100
100
  bundle: false,
101
101
  write: false,
102
- tsconfigRaw: { compilerOptions: options.compilerOptions as esbuild.TsconfigRaw["compilerOptions"] },
102
+ tsconfigRaw: {
103
+ compilerOptions: options.compilerOptions as esbuild.TsconfigRaw["compilerOptions"],
104
+ },
103
105
  logLevel: "silent",
104
106
  plugins,
105
107
  };
@@ -135,7 +137,9 @@ export function createServerEsbuildOptions(options: ServerEsbuildOptions): esbui
135
137
  },
136
138
  external: options.external,
137
139
  define,
138
- tsconfigRaw: { compilerOptions: options.compilerOptions as esbuild.TsconfigRaw["compilerOptions"] },
140
+ tsconfigRaw: {
141
+ compilerOptions: options.compilerOptions as esbuild.TsconfigRaw["compilerOptions"],
142
+ },
139
143
  logLevel: "silent",
140
144
  };
141
145
  }
@@ -176,7 +180,12 @@ export function collectUninstalledOptionalPeerDeps(pkgDir: string): string[] {
176
180
  return [...external];
177
181
  }
178
182
 
179
- function scanOptionalPeerDeps(pkgName: string, resolveDir: string, external: Set<string>, visited: Set<string>): void {
183
+ function scanOptionalPeerDeps(
184
+ pkgName: string,
185
+ resolveDir: string,
186
+ external: Set<string>,
187
+ visited: Set<string>,
188
+ ): void {
180
189
  if (visited.has(pkgName)) return;
181
190
  visited.add(pkgName);
182
191
 
@@ -233,7 +242,12 @@ export function collectNativeModuleExternals(pkgDir: string): string[] {
233
242
  return [...external];
234
243
  }
235
244
 
236
- function scanNativeModules(pkgName: string, resolveDir: string, external: Set<string>, visited: Set<string>): void {
245
+ function scanNativeModules(
246
+ pkgName: string,
247
+ resolveDir: string,
248
+ external: Set<string>,
249
+ visited: Set<string>,
250
+ ): void {
237
251
  if (visited.has(pkgName)) return;
238
252
  visited.add(pkgName);
239
253
 
@@ -32,7 +32,10 @@ export function printErrors(results: Map<string, ErrorResult>): void {
32
32
  * @param results 패키지별 빌드 결과 상태
33
33
  * @param serverClientsMap 서버별 연결된 클라이언트 목록
34
34
  */
35
- export function printServers(results: Map<string, PackageResult>, serverClientsMap?: Map<string, string[]>): void {
35
+ export function printServers(
36
+ results: Map<string, PackageResult>,
37
+ serverClientsMap?: Map<string, string[]>,
38
+ ): void {
36
39
  // 서버 정보 수집
37
40
  const servers = [...results.values()].filter((r) => r.status === "running" && r.port != null);
38
41
 
@@ -16,18 +16,30 @@ export function findPackageRoot(startDir: string): string {
16
16
  }
17
17
 
18
18
  /**
19
- * 패키지명에서 watch scope 목록을 생성한다.
19
+ * 패키지명과 replaceDeps 설정에서 watch scope 목록을 생성한다.
20
20
  * - 패키지명의 scope (예: "@myapp/root" → "@myapp")
21
- * - @simplysm (항상 포함)
21
+ * - replaceDeps 키의 scope (예: "@simplysm/*" "@simplysm")
22
22
  * @param packageName 루트 package.json의 name 필드
23
+ * @param replaceDeps sd.config.ts의 replaceDeps 설정 (키: glob 패턴, 값: 소스 경로)
23
24
  * @returns scope 배열 (중복 제거)
24
25
  */
25
- export function getWatchScopes(packageName: string): string[] {
26
- const scopes = new Set(["@simplysm"]);
26
+ export function getWatchScopes(
27
+ packageName: string,
28
+ replaceDeps?: Record<string, string>,
29
+ ): string[] {
30
+ const scopes = new Set<string>();
27
31
  const match = packageName.match(/^(@[^/]+)\//);
28
32
  if (match != null) {
29
33
  scopes.add(match[1]);
30
34
  }
35
+ if (replaceDeps != null) {
36
+ for (const pattern of Object.keys(replaceDeps)) {
37
+ const depMatch = pattern.match(/^(@[^/]+)\//);
38
+ if (depMatch != null) {
39
+ scopes.add(depMatch[1]);
40
+ }
41
+ }
42
+ }
31
43
  return [...scopes];
32
44
  }
33
45
 
@@ -7,7 +7,10 @@ interface RebuildManagerEvents {
7
7
 
8
8
  export class RebuildManager extends EventEmitter<RebuildManagerEvents> {
9
9
  private _isRunning = false;
10
- private readonly _pendingBuilds = new Map<string, { title: string; promise: Promise<void>; resolver: () => void }>();
10
+ private readonly _pendingBuilds = new Map<
11
+ string,
12
+ { title: string; promise: Promise<void>; resolver: () => void }
13
+ >();
11
14
  private readonly _logger: ReturnType<typeof consola.withTag>;
12
15
 
13
16
  constructor(logger: ReturnType<typeof consola.withTag>) {
@@ -157,7 +157,10 @@ async function collectSearchRoots(projectRoot: string): Promise<string[]> {
157
157
  * @param projectRoot - 프로젝트 루트 경로
158
158
  * @param replaceDeps - sd.config.ts의 replaceDeps 설정
159
159
  */
160
- export async function setupReplaceDeps(projectRoot: string, replaceDeps: Record<string, string>): Promise<void> {
160
+ export async function setupReplaceDeps(
161
+ projectRoot: string,
162
+ replaceDeps: Record<string, string>,
163
+ ): Promise<void> {
161
164
  const logger = consola.withTag("sd:cli:replace-deps");
162
165
 
163
166
  // 1. Workspace 패키지 경로 목록 수집
@@ -348,7 +351,9 @@ export async function watchReplaceDeps(
348
351
  logger.info(`삭제: ${relativePath} (${e.targetName})`);
349
352
  }
350
353
  } catch (err) {
351
- logger.error(`복사 실패 (${e.targetName}/${relativePath}): ${err instanceof Error ? err.message : err}`);
354
+ logger.error(
355
+ `복사 실패 (${e.targetName}/${relativePath}): ${err instanceof Error ? err.message : err}`,
356
+ );
352
357
  }
353
358
  }
354
359
  }
@@ -9,7 +9,11 @@ import type { SdConfig } from "../sd-config.types";
9
9
  * @returns SdConfig 객체
10
10
  * @throws sd.config.ts가 없거나 형식이 잘못된 경우
11
11
  */
12
- export async function loadSdConfig(params: { cwd: string; dev: boolean; opt: string[] }): Promise<SdConfig> {
12
+ export async function loadSdConfig(params: {
13
+ cwd: string;
14
+ dev: boolean;
15
+ opt: string[];
16
+ }): Promise<SdConfig> {
13
17
  const sdConfigPath = path.resolve(params.cwd, "sd.config.ts");
14
18
 
15
19
  if (!(await fsExists(sdConfigPath))) {
@@ -72,7 +72,9 @@ export async function spawn(cmd: string, args: string[], options?: SpawnOptions)
72
72
  if (code === 0) {
73
73
  resolve(output);
74
74
  } else {
75
- reject(new Error(`명령어 실패 (${cmd} ${args.join(" ")})\n종료 코드: ${code}\n출력:\n${output}`));
75
+ reject(
76
+ new Error(`명령어 실패 (${cmd} ${args.join(" ")})\n종료 코드: ${code}\n출력:\n${output}`),
77
+ );
76
78
  }
77
79
  });
78
80
  });
@@ -31,7 +31,12 @@ export async function renderTemplateDir(
31
31
  if (stat.isDirectory()) {
32
32
  // 디렉토리 이름 치환 적용
33
33
  const destName = dirReplacements?.[entry] ?? entry;
34
- await renderTemplateDir(path.join(srcDir, entry), path.join(destDir, destName), context, dirReplacements);
34
+ await renderTemplateDir(
35
+ path.join(srcDir, entry),
36
+ path.join(destDir, destName),
37
+ context,
38
+ dirReplacements,
39
+ );
35
40
  } else if (entry.endsWith(".hbs")) {
36
41
  // Handlebars 템플릿 렌더링
37
42
  const source = await fsRead(srcPath);