@simplysm/sd-cli 13.0.68 → 13.0.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/README.md +10 -957
  2. package/dist/builders/BaseBuilder.d.ts +23 -23
  3. package/dist/builders/BaseBuilder.d.ts.map +1 -1
  4. package/dist/builders/BaseBuilder.js +15 -15
  5. package/dist/builders/DtsBuilder.d.ts +4 -4
  6. package/dist/builders/DtsBuilder.js +1 -1
  7. package/dist/builders/LibraryBuilder.d.ts +3 -3
  8. package/dist/builders/types.d.ts +10 -10
  9. package/dist/capacitor/capacitor.d.ts +36 -36
  10. package/dist/capacitor/capacitor.js +63 -63
  11. package/dist/capacitor/capacitor.js.map +1 -1
  12. package/dist/commands/add-client.d.ts +8 -8
  13. package/dist/commands/add-client.js +15 -15
  14. package/dist/commands/add-client.js.map +1 -1
  15. package/dist/commands/add-server.d.ts +9 -9
  16. package/dist/commands/add-server.js +13 -13
  17. package/dist/commands/add-server.js.map +1 -1
  18. package/dist/commands/build.d.ts +9 -9
  19. package/dist/commands/check.js +3 -3
  20. package/dist/commands/check.js.map +1 -1
  21. package/dist/commands/dev.d.ts +9 -9
  22. package/dist/commands/device.d.ts +9 -9
  23. package/dist/commands/device.d.ts.map +1 -1
  24. package/dist/commands/device.js +17 -17
  25. package/dist/commands/device.js.map +1 -1
  26. package/dist/commands/init.d.ts +6 -6
  27. package/dist/commands/init.js +12 -12
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/lint.d.ts +23 -23
  30. package/dist/commands/lint.d.ts.map +1 -1
  31. package/dist/commands/lint.js +25 -25
  32. package/dist/commands/lint.js.map +1 -1
  33. package/dist/commands/publish.d.ts +13 -13
  34. package/dist/commands/publish.d.ts.map +1 -1
  35. package/dist/commands/publish.js +61 -61
  36. package/dist/commands/publish.js.map +1 -1
  37. package/dist/commands/replace-deps.d.ts +3 -3
  38. package/dist/commands/replace-deps.d.ts.map +1 -1
  39. package/dist/commands/replace-deps.js +1 -1
  40. package/dist/commands/replace-deps.js.map +1 -1
  41. package/dist/commands/typecheck.d.ts +20 -20
  42. package/dist/commands/typecheck.d.ts.map +1 -1
  43. package/dist/commands/typecheck.js +20 -20
  44. package/dist/commands/typecheck.js.map +1 -1
  45. package/dist/commands/watch.d.ts +7 -7
  46. package/dist/electron/electron.d.ts +27 -27
  47. package/dist/electron/electron.js +32 -32
  48. package/dist/electron/electron.js.map +1 -1
  49. package/dist/infra/ResultCollector.d.ts +9 -9
  50. package/dist/infra/ResultCollector.js +5 -5
  51. package/dist/infra/SignalHandler.d.ts +7 -7
  52. package/dist/infra/SignalHandler.js +4 -4
  53. package/dist/infra/WorkerManager.d.ts +14 -14
  54. package/dist/infra/WorkerManager.js +11 -11
  55. package/dist/orchestrators/BuildOrchestrator.d.ts +19 -19
  56. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  57. package/dist/orchestrators/BuildOrchestrator.js +26 -26
  58. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  59. package/dist/orchestrators/DevOrchestrator.d.ts +25 -25
  60. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
  61. package/dist/orchestrators/DevOrchestrator.js +30 -30
  62. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  63. package/dist/orchestrators/WatchOrchestrator.d.ts +13 -13
  64. package/dist/orchestrators/WatchOrchestrator.js +17 -17
  65. package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
  66. package/dist/sd-cli-entry.d.ts +2 -2
  67. package/dist/sd-cli-entry.js +38 -38
  68. package/dist/sd-cli-entry.js.map +1 -1
  69. package/dist/sd-cli.d.ts +2 -2
  70. package/dist/sd-cli.js +1 -1
  71. package/dist/sd-cli.js.map +1 -1
  72. package/dist/sd-config.types.d.ts +84 -84
  73. package/dist/sd-config.types.d.ts.map +1 -1
  74. package/dist/utils/build-env.d.ts +1 -1
  75. package/dist/utils/config-editor.d.ts +5 -5
  76. package/dist/utils/config-editor.js +2 -2
  77. package/dist/utils/config-editor.js.map +1 -1
  78. package/dist/utils/copy-public.d.ts +9 -9
  79. package/dist/utils/copy-src.d.ts +9 -9
  80. package/dist/utils/esbuild-config.d.ts +30 -30
  81. package/dist/utils/esbuild-config.d.ts.map +1 -1
  82. package/dist/utils/output-utils.d.ts +6 -6
  83. package/dist/utils/package-utils.d.ts +6 -6
  84. package/dist/utils/package-utils.js +1 -1
  85. package/dist/utils/package-utils.js.map +1 -1
  86. package/dist/utils/rebuild-manager.js +3 -3
  87. package/dist/utils/rebuild-manager.js.map +1 -1
  88. package/dist/utils/replace-deps.d.ts +25 -25
  89. package/dist/utils/replace-deps.js +3 -3
  90. package/dist/utils/replace-deps.js.map +1 -1
  91. package/dist/utils/sd-config.d.ts +3 -3
  92. package/dist/utils/sd-config.js +3 -3
  93. package/dist/utils/sd-config.js.map +1 -1
  94. package/dist/utils/tailwind-config-deps.d.ts +3 -3
  95. package/dist/utils/template.d.ts +8 -8
  96. package/dist/utils/tsconfig.d.ts +16 -16
  97. package/dist/utils/tsconfig.js +2 -2
  98. package/dist/utils/tsconfig.js.map +1 -1
  99. package/dist/utils/typecheck-serialization.d.ts +8 -8
  100. package/dist/utils/vite-config.d.ts +8 -8
  101. package/dist/utils/vite-config.d.ts.map +1 -1
  102. package/dist/utils/vite-config.js +3 -3
  103. package/dist/utils/worker-events.d.ts +12 -12
  104. package/dist/utils/worker-events.d.ts.map +1 -1
  105. package/dist/utils/worker-utils.d.ts +3 -3
  106. package/dist/utils/worker-utils.js +2 -2
  107. package/dist/utils/worker-utils.js.map +1 -1
  108. package/dist/workers/client.worker.d.ts +14 -14
  109. package/dist/workers/client.worker.d.ts.map +1 -1
  110. package/dist/workers/client.worker.js +1 -1
  111. package/dist/workers/client.worker.js.map +1 -1
  112. package/dist/workers/dts.worker.d.ts +13 -13
  113. package/dist/workers/dts.worker.d.ts.map +1 -1
  114. package/dist/workers/dts.worker.js +3 -3
  115. package/dist/workers/dts.worker.js.map +1 -1
  116. package/dist/workers/library.worker.d.ts +12 -12
  117. package/dist/workers/library.worker.js +1 -1
  118. package/dist/workers/library.worker.js.map +1 -1
  119. package/dist/workers/lint.worker.d.ts +1 -1
  120. package/dist/workers/server-runtime.worker.d.ts +6 -6
  121. package/dist/workers/server-runtime.worker.js +6 -6
  122. package/dist/workers/server-runtime.worker.js.map +1 -1
  123. package/dist/workers/server.worker.d.ts +20 -20
  124. package/dist/workers/server.worker.d.ts.map +1 -1
  125. package/dist/workers/server.worker.js +6 -6
  126. package/dist/workers/server.worker.js.map +1 -1
  127. package/package.json +8 -7
  128. package/src/builders/BaseBuilder.ts +33 -33
  129. package/src/builders/DtsBuilder.ts +5 -5
  130. package/src/builders/LibraryBuilder.ts +9 -9
  131. package/src/builders/types.ts +10 -10
  132. package/src/capacitor/capacitor.ts +119 -119
  133. package/src/commands/add-client.ts +31 -31
  134. package/src/commands/add-server.ts +34 -34
  135. package/src/commands/build.ts +9 -9
  136. package/src/commands/check.ts +5 -5
  137. package/src/commands/dev.ts +9 -9
  138. package/src/commands/device.ts +30 -30
  139. package/src/commands/init.ts +25 -25
  140. package/src/commands/lint.ts +64 -64
  141. package/src/commands/publish.ts +139 -139
  142. package/src/commands/replace-deps.ts +4 -4
  143. package/src/commands/typecheck.ts +74 -74
  144. package/src/commands/watch.ts +7 -7
  145. package/src/electron/electron.ts +51 -51
  146. package/src/infra/ResultCollector.ts +9 -9
  147. package/src/infra/SignalHandler.ts +7 -7
  148. package/src/infra/WorkerManager.ts +14 -14
  149. package/src/orchestrators/BuildOrchestrator.ts +76 -76
  150. package/src/orchestrators/DevOrchestrator.ts +88 -88
  151. package/src/orchestrators/WatchOrchestrator.ts +39 -39
  152. package/src/sd-cli-entry.ts +43 -43
  153. package/src/sd-cli.ts +15 -15
  154. package/src/sd-config.types.ts +85 -85
  155. package/src/utils/build-env.ts +1 -1
  156. package/src/utils/config-editor.ts +19 -19
  157. package/src/utils/copy-public.ts +17 -17
  158. package/src/utils/copy-src.ts +11 -11
  159. package/src/utils/esbuild-config.ts +33 -33
  160. package/src/utils/output-utils.ts +11 -11
  161. package/src/utils/package-utils.ts +12 -12
  162. package/src/utils/rebuild-manager.ts +3 -3
  163. package/src/utils/replace-deps.ts +361 -361
  164. package/src/utils/sd-config.ts +44 -44
  165. package/src/utils/tailwind-config-deps.ts +98 -98
  166. package/src/utils/template.ts +56 -56
  167. package/src/utils/tsconfig.ts +127 -127
  168. package/src/utils/typecheck-serialization.ts +86 -86
  169. package/src/utils/vite-config.ts +341 -341
  170. package/src/utils/worker-events.ts +16 -16
  171. package/src/utils/worker-utils.ts +45 -45
  172. package/src/workers/client.worker.ts +34 -34
  173. package/src/workers/dts.worker.ts +467 -467
  174. package/src/workers/library.worker.ts +314 -314
  175. package/src/workers/lint.worker.ts +16 -16
  176. package/src/workers/server-runtime.worker.ts +157 -157
  177. package/src/workers/server.worker.ts +572 -572
  178. package/templates/add-client/__CLIENT__/package.json.hbs +1 -1
  179. package/templates/add-server/__SERVER__/package.json.hbs +2 -2
  180. package/templates/init/package.json.hbs +3 -3
  181. package/tests/config-editor.spec.ts +160 -0
  182. package/tests/copy-src.spec.ts +50 -0
  183. package/tests/get-compiler-options-for-package.spec.ts +139 -0
  184. package/tests/get-package-source-files.spec.ts +181 -0
  185. package/tests/get-types-from-package-json.spec.ts +107 -0
  186. package/tests/infra/ResultCollector.spec.ts +39 -0
  187. package/tests/infra/SignalHandler.spec.ts +38 -0
  188. package/tests/infra/WorkerManager.spec.ts +97 -0
  189. package/tests/load-ignore-patterns.spec.ts +188 -0
  190. package/tests/load-sd-config.spec.ts +137 -0
  191. package/tests/package-utils.spec.ts +188 -0
  192. package/tests/parse-root-tsconfig.spec.ts +89 -0
  193. package/tests/replace-deps.spec.ts +308 -0
  194. package/tests/run-lint.spec.ts +415 -0
  195. package/tests/run-typecheck.spec.ts +653 -0
  196. package/tests/run-watch.spec.ts +75 -0
  197. package/tests/sd-cli.spec.ts +330 -0
  198. package/tests/tailwind-config-deps.spec.ts +30 -0
  199. package/tests/template.spec.ts +70 -0
  200. package/tests/utils/rebuild-manager.spec.ts +43 -0
  201. package/tests/write-changed-output-files.spec.ts +97 -0
@@ -20,21 +20,21 @@ const { Client: SshClient, utils } = ssh2;
20
20
  //#region Types
21
21
 
22
22
  /**
23
- * Publish 명령 옵션
23
+ * Publish command options
24
24
  */
25
25
  export interface PublishOptions {
26
- /** 배포할 패키지 필터 ( 배열이면 publish 설정이 있는 모든 패키지) */
26
+ /** Filter for packages to deploy (empty array deploys all packages with publish config) */
27
27
  targets: string[];
28
- /** 빌드 없이 배포 (위험) */
28
+ /** Deploy without building (dangerous) */
29
29
  noBuild: boolean;
30
- /** 실제 배포 없이 시뮬레이션 */
30
+ /** Simulate deployment without actually deploying */
31
31
  dryRun: boolean;
32
- /** sd.config.ts 전달할 추가 옵션 */
32
+ /** Additional options to pass to sd.config.ts */
33
33
  options: string[];
34
34
  }
35
35
 
36
36
  /**
37
- * package.json 타입 (필요한 필드만)
37
+ * package.json type (required fields only)
38
38
  */
39
39
  interface PackageJson {
40
40
  name: string;
@@ -50,8 +50,8 @@ interface PackageJson {
50
50
  //#region Utilities
51
51
 
52
52
  /**
53
- * 환경변수 치환 (%VAR% 형식)
54
- * @throws 치환 결과가 문자열이면 에러
53
+ * Replace environment variables (%VAR% format)
54
+ * @throws throws an error if any variable substitution is empty
55
55
  */
56
56
  function replaceEnvVariables(str: string, version: string, projectPath: string): string {
57
57
  const result = str.replace(/%([^%]+)%/g, (match, envName: string) => {
@@ -64,16 +64,16 @@ function replaceEnvVariables(str: string, version: string, projectPath: string):
64
64
  return (env[envName] as string | undefined) ?? match;
65
65
  });
66
66
 
67
- // 치환되지 않은 환경변수가 남아있으면 에러
67
+ // Throw error if any unsubstituted environment variables remain
68
68
  if (/%[^%]+%/.test(result)) {
69
- throw new Error(`환경변수 치환 실패: ${str} → ${result}`);
69
+ throw new Error(`Environment variable substitution failed: ${str} → ${result}`);
70
70
  }
71
71
 
72
72
  return result;
73
73
  }
74
74
 
75
75
  /**
76
- * 카운트다운 대기
76
+ * Wait with countdown
77
77
  */
78
78
  async function waitWithCountdown(message: string, seconds: number): Promise<void> {
79
79
  for (let i = seconds; i > 0; i--) {
@@ -93,24 +93,24 @@ async function waitWithCountdown(message: string, seconds: number): Promise<void
93
93
  }
94
94
 
95
95
  /**
96
- * SSH 인증 사전 확인 및 설정
96
+ * Pre-verify and configure SSH key authentication
97
97
  *
98
- * pass가 없는 SFTP 서버에 대해:
99
- * 1. SSH 파일이 없으면 생성
100
- * 2. 인증을 테스트하고, 실패하면 비밀번호로 공개키 등록
98
+ * For SFTP servers without pass:
99
+ * 1. Generate SSH key file if it doesn't exist
100
+ * 2. Test key authentication, and if it fails, register public key using password
101
101
  */
102
102
  async function ensureSshAuth(
103
103
  publishPackages: Array<{ name: string; config: SdPublishConfig }>,
104
104
  logger: ReturnType<typeof consola.withTag>,
105
105
  ): Promise<void> {
106
- // pass 없는 SFTP 서버 수집 (user@host 중복 제거)
106
+ // Collect SFTP servers without pass (deduplicate user@host)
107
107
  const sshTargets = new Map<string, { host: string; port?: number; user: string }>();
108
108
  for (const pkg of publishPackages) {
109
109
  if (pkg.config === "npm") continue;
110
110
  if (pkg.config.type !== "sftp") continue;
111
111
  if (pkg.config.pass != null) continue;
112
112
  if (pkg.config.user == null) {
113
- throw new Error(`[${pkg.name}] SFTP 설정에 user가 없습니다.`);
113
+ throw new Error(`[${pkg.name}] SFTP config missing user.`);
114
114
  }
115
115
  const key = `${pkg.config.user}@${pkg.config.host}`;
116
116
  sshTargets.set(key, {
@@ -122,13 +122,13 @@ async function ensureSshAuth(
122
122
 
123
123
  if (sshTargets.size === 0) return;
124
124
 
125
- // SSH 파일 확인/생성
125
+ // Check/create SSH key file
126
126
  const sshDir = path.join(os.homedir(), ".ssh");
127
127
  const keyPath = path.join(sshDir, "id_ed25519");
128
128
  const pubKeyPath = path.join(sshDir, "id_ed25519.pub");
129
129
 
130
130
  if (!fs.existsSync(keyPath)) {
131
- logger.info("SSH 키가 없습니다. 생성합니다...");
131
+ logger.info("SSH key not found. Creating one...");
132
132
 
133
133
  if (!fs.existsSync(sshDir)) {
134
134
  fs.mkdirSync(sshDir, { mode: 0o700 });
@@ -138,41 +138,41 @@ async function ensureSshAuth(
138
138
  fs.writeFileSync(keyPath, keyPair.private, { mode: 0o600 });
139
139
  fs.writeFileSync(pubKeyPath, keyPair.public + "\n", { mode: 0o644 });
140
140
 
141
- logger.info(`SSH 생성 완료: ${keyPath}`);
141
+ logger.info(`SSH key created: ${keyPath}`);
142
142
  }
143
143
 
144
144
  const privateKeyData = fs.readFileSync(keyPath);
145
145
  const publicKey = fs.readFileSync(pubKeyPath, "utf-8").trim();
146
146
 
147
- // privateKey 암호화되어 있는지 확인
147
+ // Check if privateKey is encrypted
148
148
  const parsed = utils.parseKey(privateKeyData);
149
149
  const isKeyEncrypted = parsed instanceof Error;
150
150
  const sshAgent = process.env["SSH_AUTH_SOCK"];
151
151
 
152
- // 서버에 대해 인증 확인
152
+ // Verify key authentication for each server
153
153
  for (const [label, target] of sshTargets) {
154
154
  const canAuth = await testSshKeyAuth(target, {
155
155
  privateKey: isKeyEncrypted ? undefined : privateKeyData,
156
156
  agent: sshAgent,
157
157
  });
158
158
  if (canAuth) {
159
- logger.debug(`SSH 인증 확인: ${label}`);
159
+ logger.debug(`SSH key authentication verified: ${label}`);
160
160
  continue;
161
161
  }
162
162
 
163
- // 인증 실패비밀번호로 공개키 등록
164
- logger.info(`${label}: SSH 키가 서버에 등록되어 있지 않습니다.`);
163
+ // Key authentication failedregister public key using password
164
+ logger.info(`${label}: SSH key not registered on server.`);
165
165
  const pass = await passwordPrompt({
166
- message: `${label} 비밀번호 (공개키 등록용):`,
166
+ message: `${label} password (to register public key):`,
167
167
  });
168
168
 
169
169
  await registerSshPublicKey(target, pass, publicKey);
170
- logger.info(`SSH 공개키 등록 완료: ${label}`);
170
+ logger.info(`SSH public key registered: ${label}`);
171
171
  }
172
172
  }
173
173
 
174
174
  /**
175
- * SSH 인증 테스트 (접속 즉시 종료)
175
+ * Test SSH key authentication (connect and immediately disconnect)
176
176
  */
177
177
  function testSshKeyAuth(
178
178
  target: { host: string; port?: number; user: string },
@@ -203,7 +203,7 @@ function testSshKeyAuth(
203
203
  }
204
204
 
205
205
  /**
206
- * 비밀번호로 서버에 접속하여 SSH 공개키를 등록
206
+ * Connect to server with password and register SSH public key
207
207
  */
208
208
  function registerSshPublicKey(
209
209
  target: { host: string; port?: number; user: string },
@@ -213,7 +213,7 @@ function registerSshPublicKey(
213
213
  return new Promise((resolve, reject) => {
214
214
  const conn = new SshClient();
215
215
  conn.on("ready", () => {
216
- // authorized_keys에 공개키 추가
216
+ // Add public key to authorized_keys
217
217
  const escapedKey = publicKey.replace(/'/g, "'\\''");
218
218
  const cmd = [
219
219
  "mkdir -p ~/.ssh",
@@ -225,19 +225,19 @@ function registerSshPublicKey(
225
225
  conn.exec(cmd, (err, stream) => {
226
226
  if (err) {
227
227
  conn.end();
228
- reject(new Error(`SSH 명령 실행 실패: ${err.message}`));
228
+ reject(new Error(`Failed to execute SSH command: ${err.message}`));
229
229
  return;
230
230
  }
231
231
 
232
232
  let stderr = "";
233
- stream.on("data", () => {}); // stdout 소비 (미소비 stream 미종료)
233
+ stream.on("data", () => {}); // Consume stdout (unconsumed stream won't close)
234
234
  stream.stderr.on("data", (data: Uint8Array) => {
235
235
  stderr += data.toString();
236
236
  });
237
237
  stream.on("exit", (code: number | null) => {
238
238
  conn.end();
239
239
  if (code !== 0) {
240
- reject(new Error(`SSH 공개키 등록 실패 (exit code: ${code}): ${stderr}`));
240
+ reject(new Error(`Failed to register SSH public key (exit code: ${code}): ${stderr}`));
241
241
  } else {
242
242
  resolve();
243
243
  }
@@ -245,7 +245,7 @@ function registerSshPublicKey(
245
245
  });
246
246
  });
247
247
  conn.on("error", (err) => {
248
- reject(new Error(`SSH 접속 실패 (${target.host}): ${err.message}`));
248
+ reject(new Error(`SSH connection failed (${target.host}): ${err.message}`));
249
249
  });
250
250
  conn.connect({
251
251
  host: target.host,
@@ -262,8 +262,8 @@ function registerSshPublicKey(
262
262
  //#region Version Upgrade
263
263
 
264
264
  /**
265
- * 프로젝트 패키지 버전 업그레이드
266
- * @param dryRun true 파일 수정 없이 버전만 계산
265
+ * Upgrade project and package versions
266
+ * @param dryRun if true, only calculate new version without modifying files
267
267
  */
268
268
  async function upgradeVersion(
269
269
  cwd: string,
@@ -277,14 +277,14 @@ async function upgradeVersion(
277
277
  const currentVersion = projPkg.version;
278
278
  const prereleaseInfo = semver.prerelease(currentVersion);
279
279
 
280
- // prerelease 여부에 따라 증가 방식 결정
280
+ // Determine increment strategy based on prerelease status
281
281
  const newVersion =
282
282
  prereleaseInfo !== null
283
283
  ? semver.inc(currentVersion, "prerelease")!
284
284
  : semver.inc(currentVersion, "patch")!;
285
285
 
286
286
  if (dryRun) {
287
- // dry-run: 파일 수정 없이 버전만 반환
287
+ // dry-run: return new version without modifying files
288
288
  return { version: newVersion, changedFiles: [] };
289
289
  }
290
290
 
@@ -292,7 +292,7 @@ async function upgradeVersion(
292
292
  await fsWrite(projPkgPath, jsonStringify(projPkg, { space: 2 }) + "\n");
293
293
  changedFiles.push(projPkgPath);
294
294
 
295
- // 패키지 package.json 버전 설정
295
+ // Set version in each package's package.json
296
296
  for (const pkgPath of allPkgPaths) {
297
297
  const pkgJsonPath = path.resolve(pkgPath, "package.json");
298
298
  const pkgJson = await fsReadJson<PackageJson>(pkgJsonPath);
@@ -301,7 +301,7 @@ async function upgradeVersion(
301
301
  changedFiles.push(pkgJsonPath);
302
302
  }
303
303
 
304
- // 템플릿 파일의 @simplysm 패키지 버전 동기화
304
+ // Synchronize @simplysm package version in template files
305
305
  const templateFiles = await fsGlob(path.resolve(cwd, "packages/sd-cli/templates/**/*.hbs"));
306
306
  const versionRegex = /("@simplysm\/[^"]+"\s*:\s*)"~[^"]+"/g;
307
307
 
@@ -323,8 +323,8 @@ async function upgradeVersion(
323
323
  //#region Package Publishing
324
324
 
325
325
  /**
326
- * 개별 패키지 배포
327
- * @param dryRun true 실제 배포 없이 시뮬레이션
326
+ * Publish individual package
327
+ * @param dryRun if true, simulate deployment without actually publishing
328
328
  */
329
329
  async function publishPackage(
330
330
  pkgPath: string,
@@ -354,27 +354,27 @@ async function publishPackage(
354
354
 
355
355
  await execa("pnpm", args, { cwd: pkgPath });
356
356
  } else if (publishConfig.type === "local-directory") {
357
- // 로컬 디렉토리 복사
357
+ // Copy to local directory
358
358
  const targetPath = replaceEnvVariables(publishConfig.path, version, projectPath);
359
359
  const distPath = path.resolve(pkgPath, "dist");
360
360
 
361
361
  if (dryRun) {
362
- logger.info(`[DRY-RUN] [${pkgName}] 로컬 복사: ${distPath} → ${targetPath}`);
362
+ logger.info(`[DRY-RUN] [${pkgName}] copy to local: ${distPath} → ${targetPath}`);
363
363
  } else {
364
- logger.debug(`[${pkgName}] 로컬 복사: ${distPath} → ${targetPath}`);
364
+ logger.debug(`[${pkgName}] copy to local: ${distPath} → ${targetPath}`);
365
365
  await fsCopy(distPath, targetPath);
366
366
  }
367
367
  } else {
368
- // 스토리지 업로드
368
+ // Upload to storage
369
369
  const distPath = path.resolve(pkgPath, "dist");
370
370
  const remotePath = publishConfig.path ?? "/";
371
371
 
372
372
  if (dryRun) {
373
373
  logger.info(
374
- `[DRY-RUN] [${pkgName}] ${publishConfig.type} 업로드: ${distPath} → ${remotePath}`,
374
+ `[DRY-RUN] [${pkgName}] ${publishConfig.type} upload: ${distPath} → ${remotePath}`,
375
375
  );
376
376
  } else {
377
- logger.debug(`[${pkgName}] ${publishConfig.type} 업로드: ${distPath} → ${remotePath}`);
377
+ logger.debug(`[${pkgName}] ${publishConfig.type} upload: ${distPath} → ${remotePath}`);
378
378
  await StorageFactory.connect(
379
379
  publishConfig.type,
380
380
  {
@@ -396,15 +396,15 @@ async function publishPackage(
396
396
  //#region Dependency Levels
397
397
 
398
398
  /**
399
- * 배포 패키지의 의존성 레벨을 계산한다.
400
- * 의존성이 없는 패키지 → Level 0, Level 0에만 의존 → Level 1, ...
399
+ * Calculate dependency levels for packages to publish.
400
+ * Packages with no dependencies → Level 0, depends only on Level 0 → Level 1, ...
401
401
  */
402
402
  async function computePublishLevels(
403
403
  publishPkgs: Array<{ name: string; path: string; config: SdPublishConfig }>,
404
404
  ): Promise<Array<Array<{ name: string; path: string; config: SdPublishConfig }>>> {
405
405
  const pkgNames = new Set(publishPkgs.map((p) => p.name));
406
406
 
407
- // 패키지의 워크스페이스 의존성 수집
407
+ // Collect workspace dependencies for each package
408
408
  const depsMap = new Map<string, Set<string>>();
409
409
  for (const pkg of publishPkgs) {
410
410
  const pkgJson = await fsReadJson<PackageJson>(path.resolve(pkg.path, "package.json"));
@@ -424,7 +424,7 @@ async function computePublishLevels(
424
424
  depsMap.set(pkg.name, workspaceDeps);
425
425
  }
426
426
 
427
- // 위상 정렬로 레벨 분류
427
+ // Topological sort to classify into levels
428
428
  const levels: Array<Array<{ name: string; path: string; config: SdPublishConfig }>> = [];
429
429
  const assigned = new Set<string>();
430
430
  const remaining = new Map(publishPkgs.map((p) => [p.name, p]));
@@ -439,7 +439,7 @@ async function computePublishLevels(
439
439
  }
440
440
 
441
441
  if (level.length === 0) {
442
- // 순환 의존성남은 패키지를 모두 마지막 레벨에 배치
442
+ // Circular dependencyplace all remaining packages in final level
443
443
  levels.push([...remaining.values()]);
444
444
  break;
445
445
  }
@@ -459,15 +459,15 @@ async function computePublishLevels(
459
459
  //#region Main
460
460
 
461
461
  /**
462
- * publish 명령을 실행한다.
462
+ * Execute publish command.
463
463
  *
464
- * **배포 순서 (안전성 우선):**
465
- * 1. 사전 검증 (npm 인증, Git 상태)
466
- * 2. 버전 업그레이드 (package.json + 템플릿)
467
- * 3. 빌드
468
- * 4. Git 커밋/태그/푸시 (변경된 파일만 명시적으로 staging)
469
- * 5. pnpm 배포
470
- * 6. postPublish (실패해도 계속)
464
+ * **Deployment order (safety first):**
465
+ * 1. Pre-validation (npm auth, Git status)
466
+ * 2. Version upgrade (package.json + templates)
467
+ * 3. Build
468
+ * 4. Git commit/tag/push (explicitly stage only changed files)
469
+ * 5. pnpm deployment
470
+ * 6. postPublish (continue even if it fails)
471
471
  */
472
472
  export async function runPublish(options: PublishOptions): Promise<void> {
473
473
  const { targets, noBuild, dryRun } = options;
@@ -475,27 +475,27 @@ export async function runPublish(options: PublishOptions): Promise<void> {
475
475
  const logger = consola.withTag("sd:cli:publish");
476
476
 
477
477
  if (dryRun) {
478
- logger.info("[DRY-RUN] 시뮬레이션 모드 - 실제 배포 없음");
478
+ logger.info("[DRY-RUN] Simulation mode - no actual deployment");
479
479
  }
480
480
 
481
- logger.debug("배포 시작", { targets, noBuild, dryRun });
481
+ logger.debug("publish start", { targets, noBuild, dryRun });
482
482
 
483
- // sd.config.ts 로드
483
+ // Load sd.config.ts
484
484
  let sdConfig: SdConfig;
485
485
  try {
486
486
  sdConfig = await loadSdConfig({ cwd, dev: false, opt: options.options });
487
- logger.debug("sd.config.ts 로드 완료");
487
+ logger.debug("sd.config.ts loaded");
488
488
  } catch (err) {
489
- logger.error(`sd.config.ts 로드 실패: ${err instanceof Error ? err.message : err}`);
489
+ logger.error(`Failed to load sd.config.ts: ${err instanceof Error ? err.message : err}`);
490
490
  process.exitCode = 1;
491
491
  return;
492
492
  }
493
493
 
494
- // package.json 로드
494
+ // Load package.json
495
495
  const projPkgPath = path.resolve(cwd, "package.json");
496
496
  const projPkg = await fsReadJson<PackageJson>(projPkgPath);
497
497
 
498
- // pnpm-workspace.yaml에서 워크스페이스 패키지 경로 수집
498
+ // Collect workspace package paths from pnpm-workspace.yaml
499
499
  const workspaceYamlPath = path.resolve(cwd, "pnpm-workspace.yaml");
500
500
  const workspaceGlobs: string[] = [];
501
501
  if (await fsExists(workspaceYamlPath)) {
@@ -509,7 +509,7 @@ export async function runPublish(options: PublishOptions): Promise<void> {
509
509
  .flat()
510
510
  .filter((item) => !path.basename(item).includes("."));
511
511
 
512
- // publish 설정이 있는 패키지 필터링
512
+ // Filter packages with publish configuration
513
513
  const publishPackages: Array<{
514
514
  name: string;
515
515
  path: string;
@@ -523,12 +523,12 @@ export async function runPublish(options: PublishOptions): Promise<void> {
523
523
  const pkgConfig = config;
524
524
  if (pkgConfig.publish == null) continue;
525
525
 
526
- // targets 지정되면 해당 패키지만 포함
526
+ // If targets is specified, include only those packages
527
527
  if (targets.length > 0 && !targets.includes(name)) continue;
528
528
 
529
529
  const pkgPath = allPkgPaths.find((p) => path.basename(p) === name);
530
530
  if (pkgPath == null) {
531
- logger.warn(`패키지를 찾을 없습니다: ${name}`);
531
+ logger.warn(`Package not found: ${name}`);
532
532
  continue;
533
533
  }
534
534
 
@@ -540,59 +540,59 @@ export async function runPublish(options: PublishOptions): Promise<void> {
540
540
  }
541
541
 
542
542
  if (publishPackages.length === 0) {
543
- process.stdout.write("✔ 배포할 패키지가 없습니다.\n");
543
+ process.stdout.write("✔ No packages to deploy.\n");
544
544
  return;
545
545
  }
546
546
 
547
547
  logger.debug(
548
- "배포 대상 패키지",
548
+ "Target packages to deploy",
549
549
  publishPackages.map((p) => p.name),
550
550
  );
551
551
 
552
- // Git 사용 여부 확인
552
+ // Check if Git is available
553
553
  const hasGit = await fsExists(path.resolve(cwd, ".git"));
554
554
 
555
- //#region Phase 1: 사전 검증
555
+ //#region Phase 1: Pre-validation
556
556
 
557
- // npm 인증 확인 (npm publish 설정이 있는 경우)
557
+ // Verify npm authentication (if npm publish config exists)
558
558
  if (publishPackages.some((p) => p.config === "npm")) {
559
- logger.debug("npm 인증 확인...");
559
+ logger.debug("Verifying npm authentication...");
560
560
  try {
561
561
  const { stdout: whoami } = await execa("npm", ["whoami"]);
562
562
  if (whoami.trim() === "") {
563
- throw new Error("npm 로그인 정보가 없습니다.");
563
+ throw new Error("npm login information not found.");
564
564
  }
565
- logger.debug(`npm 로그인 확인: ${whoami.trim()}`);
565
+ logger.debug(`npm login verified: ${whoami.trim()}`);
566
566
  } catch (err) {
567
- logger.error(`npm whoami 실패:`, err);
567
+ logger.error(`npm whoami failed:`, err);
568
568
  /*logger.error(
569
- "npm 토큰이 유효하지 않거나 만료되었습니다.\n" +
570
- "https://www.npmjs.com/settings/~/tokens 에서 Granular Access Token 생성 후:\n" +
571
- " npm config set //registry.npmjs.org/:_authToken <토큰>",
569
+ "npm token is invalid or expired.\n" +
570
+ "Create a Granular Access Token at https://www.npmjs.com/settings/~/tokens, then:\n" +
571
+ " npm config set //registry.npmjs.org/:_authToken <token>",
572
572
  );*/
573
573
  process.exitCode = 1;
574
574
  return;
575
575
  }
576
576
  }
577
577
 
578
- // SSH 인증 확인 (pass 없는 SFTP publish 설정이 있는 경우)
578
+ // Verify SSH key authentication (if SFTP publish config without pass exists)
579
579
  try {
580
580
  await ensureSshAuth(publishPackages, logger);
581
581
  } catch (err) {
582
- logger.error(`SSH 인증 설정 실패: ${err instanceof Error ? err.message : err}`);
582
+ logger.error(`Failed to setup SSH authentication: ${err instanceof Error ? err.message : err}`);
583
583
  process.exitCode = 1;
584
584
  return;
585
585
  }
586
586
 
587
- // Git 미커밋 변경사항 확인 자동 커밋 (noBuild 아닌 경우)
587
+ // Check for uncommitted changes and attempt auto-commit (unless noBuild is set)
588
588
  if (!noBuild && hasGit) {
589
- logger.debug("Git 커밋 여부 확인...");
589
+ logger.debug("Checking git commit status...");
590
590
  try {
591
591
  const { stdout: diff } = await execa("git", ["diff", "--name-only"]);
592
592
  const { stdout: stagedDiff } = await execa("git", ["diff", "--cached", "--name-only"]);
593
593
 
594
594
  if (diff.trim() !== "" || stagedDiff.trim() !== "") {
595
- logger.info("커밋되지 않은 변경사항 감지. claude 자동 커밋 시도...");
595
+ logger.info("Uncommitted changes detected. Attempting auto-commit with claude...");
596
596
  try {
597
597
  await execa("claude", [
598
598
  "-p",
@@ -603,20 +603,20 @@ export async function runPublish(options: PublishOptions): Promise<void> {
603
603
  ]);
604
604
  } catch (e) {
605
605
  throw new Error(
606
- "자동 커밋에 실패했습니다. 수동으로 커밋 다시 시도하세요.\n" +
606
+ "Auto-commit failed. Please commit manually and try again.\n" +
607
607
  (e instanceof Error ? e.message : String(e)),
608
608
  );
609
609
  }
610
610
 
611
- // 커밋 재확인
611
+ // Re-verify after commit
612
612
  const { stdout: recheckDiff } = await execa("git", ["diff", "--name-only"]);
613
613
  const { stdout: recheckStaged } = await execa("git", ["diff", "--cached", "--name-only"]);
614
614
  if (recheckDiff.trim() !== "" || recheckStaged.trim() !== "") {
615
615
  throw new Error(
616
- "자동 커밋 후에도 미커밋 변경사항이 남아있습니다.\n" + recheckDiff + recheckStaged,
616
+ "Uncommitted changes still remain after auto-commit.\n" + recheckDiff + recheckStaged,
617
617
  );
618
618
  }
619
- logger.info("자동 커밋 완료.");
619
+ logger.info("Auto-commit completed.");
620
620
  }
621
621
  } catch (err) {
622
622
  logger.error(err instanceof Error ? err.message : err);
@@ -627,31 +627,31 @@ export async function runPublish(options: PublishOptions): Promise<void> {
627
627
 
628
628
  //#endregion
629
629
 
630
- //#region Phase 2 & 3: 빌드 또는 noBuild 경고
630
+ //#region Phase 2 & 3: Build or noBuild warning
631
631
 
632
632
  let version = projPkg.version;
633
633
 
634
634
  if (noBuild) {
635
- // noBuild 경고
636
- logger.warn("빌드하지 않고 배포하는 것은 상당히 위험합니다.");
637
- await waitWithCountdown("프로세스를 중지하려면 'CTRL+C' 누르세요.", 5);
635
+ // noBuild warning
636
+ logger.warn("Deploying without building is quite dangerous.");
637
+ await waitWithCountdown("Press 'CTRL+C' to stop the process.", 5);
638
638
  } else {
639
- // 버전 업그레이드
640
- logger.debug("버전 업그레이드...");
639
+ // Version upgrade
640
+ logger.debug("Upgrading version...");
641
641
  const upgradeResult = await upgradeVersion(cwd, allPkgPaths, dryRun);
642
642
  version = upgradeResult.version;
643
643
  const _changedFiles = upgradeResult.changedFiles;
644
644
  if (dryRun) {
645
- logger.info(`[DRY-RUN] 버전 업그레이드: ${projPkg.version} → ${version} (파일 수정 없음)`);
645
+ logger.info(`[DRY-RUN] Version upgrade: ${projPkg.version} → ${version} (files not modified)`);
646
646
  } else {
647
- logger.info(`버전 업그레이드: ${projPkg.version} → ${version}`);
647
+ logger.info(`Version upgrade: ${projPkg.version} → ${version}`);
648
648
  }
649
649
 
650
- // 빌드 실행
650
+ // Run build
651
651
  if (dryRun) {
652
- logger.info("[DRY-RUN] 빌드 시작 (검증용)...");
652
+ logger.info("[DRY-RUN] Starting build (validation only)...");
653
653
  } else {
654
- logger.debug("빌드 시작...");
654
+ logger.debug("Starting build...");
655
655
  }
656
656
 
657
657
  try {
@@ -660,17 +660,17 @@ export async function runPublish(options: PublishOptions): Promise<void> {
660
660
  options: options.options,
661
661
  });
662
662
 
663
- // 빌드 실패 확인
663
+ // Check build failure
664
664
  if (process.exitCode === 1) {
665
- throw new Error("빌드 실패");
665
+ throw new Error("Build failed");
666
666
  }
667
667
  } catch {
668
668
  if (dryRun) {
669
- logger.error("[DRY-RUN] 빌드 실패");
669
+ logger.error("[DRY-RUN] Build failed");
670
670
  } else {
671
671
  logger.error(
672
- "빌드 실패. 수동 복구가 필요할 있습니다:\n" +
673
- " 버전 변경을 되돌리려면:\n" +
672
+ "Build failed. Manual recovery may be necessary:\n" +
673
+ " To revert version changes:\n" +
674
674
  " git checkout -- package.json packages/*/package.json packages/sd-cli/templates/",
675
675
  );
676
676
  }
@@ -678,34 +678,34 @@ export async function runPublish(options: PublishOptions): Promise<void> {
678
678
  return;
679
679
  }
680
680
 
681
- //#region Phase 3: Git 커밋/태그/푸시
681
+ //#region Phase 3: Git commit/tag/push
682
682
 
683
683
  if (hasGit) {
684
684
  if (dryRun) {
685
- logger.info("[DRY-RUN] Git 커밋/태그/푸시 시뮬레이션...");
686
- logger.info(`[DRY-RUN] git add (${_changedFiles.length} 파일)`);
685
+ logger.info("[DRY-RUN] Simulating Git commit/tag/push...");
686
+ logger.info(`[DRY-RUN] git add (${_changedFiles.length} files)`);
687
687
  logger.info(`[DRY-RUN] git commit -m "v${version}"`);
688
688
  logger.info(`[DRY-RUN] git tag -a v${version} -m "v${version}"`);
689
689
  logger.info("[DRY-RUN] git push --dry-run");
690
690
  await execa("git", ["push", "--dry-run"]);
691
691
  logger.info("[DRY-RUN] git push --tags --dry-run");
692
692
  await execa("git", ["push", "--tags", "--dry-run"]);
693
- logger.info("[DRY-RUN] Git 작업 시뮬레이션 완료");
693
+ logger.info("[DRY-RUN] Git operations simulation completed");
694
694
  } else {
695
- logger.debug("Git 커밋/태그/푸시...");
695
+ logger.debug("Git commit/tag/push...");
696
696
  try {
697
697
  await execa("git", ["add", ..._changedFiles]);
698
698
  await execa("git", ["commit", "-m", `v${version}`]);
699
699
  await execa("git", ["tag", "-a", `v${version}`, "-m", `v${version}`]);
700
700
  await execa("git", ["push"]);
701
701
  await execa("git", ["push", "--tags"]);
702
- logger.debug("Git 작업 완료");
702
+ logger.debug("Git operations completed");
703
703
  } catch (err) {
704
704
  logger.error(
705
- `Git 작업 실패: ${err instanceof Error ? err.message : err}\n` +
706
- "수동 복구가 필요할 있습니다:\n" +
707
- ` git revert HEAD # 버전 커밋 되돌리기\n` +
708
- ` git tag -d v${version} # 태그 삭제`,
705
+ `Git operations failed: ${err instanceof Error ? err.message : err}\n` +
706
+ "Manual recovery may be necessary:\n" +
707
+ ` git revert HEAD # Revert version commit\n` +
708
+ ` git tag -d v${version} # Delete tag`,
709
709
  );
710
710
  process.exitCode = 1;
711
711
  return;
@@ -718,20 +718,20 @@ export async function runPublish(options: PublishOptions): Promise<void> {
718
718
 
719
719
  //#endregion
720
720
 
721
- //#region Phase 4: 배포 (의존성 레벨별 순차, 레벨 병렬)
721
+ //#region Phase 4: Deployment (sequential by dependency level, parallel within level)
722
722
 
723
723
  const levels = await computePublishLevels(publishPackages);
724
724
  const publishedPackages: string[] = [];
725
725
  let publishFailed = false;
726
726
 
727
- // 레벨별 순차 실행
727
+ // Sequential execution per level
728
728
  for (let levelIdx = 0; levelIdx < levels.length; levelIdx++) {
729
729
  if (publishFailed) break;
730
730
 
731
731
  const levelPkgs = levels[levelIdx];
732
732
  logger.start(`Level ${levelIdx + 1}/${levels.length}`);
733
733
 
734
- // 레벨 패키지 병렬 실행 (Promise.allSettled)
734
+ // Parallel execution within level (Promise.allSettled)
735
735
  const publishPromises = levelPkgs.map(async (pkg) => {
736
736
  const maxRetries = 3;
737
737
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -745,8 +745,8 @@ export async function runPublish(options: PublishOptions): Promise<void> {
745
745
  const delay = attempt * 5_000;
746
746
  logger.debug(
747
747
  dryRun
748
- ? `[DRY-RUN] ${pkg.name} (재시도 ${attempt + 1}/${maxRetries})`
749
- : `${pkg.name} (재시도 ${attempt + 1}/${maxRetries})`,
748
+ ? `[DRY-RUN] ${pkg.name} (retry ${attempt + 1}/${maxRetries})`
749
+ : `${pkg.name} (retry ${attempt + 1}/${maxRetries})`,
750
750
  );
751
751
  await new Promise((resolve) => setTimeout(resolve, delay));
752
752
  } else {
@@ -754,13 +754,13 @@ export async function runPublish(options: PublishOptions): Promise<void> {
754
754
  }
755
755
  }
756
756
  }
757
- // TypeScript 타입 체커를 위한 fallback (실제로는 도달하지 않음)
757
+ // Fallback for TypeScript type checker (actually unreachable)
758
758
  return { status: "error" as const, name: pkg.name, error: new Error("Unknown error") };
759
759
  });
760
760
 
761
761
  const results = await Promise.allSettled(publishPromises);
762
762
 
763
- // 레벨 실패 확인
763
+ // Check for failures within level
764
764
  const levelFailed = results.some((r) => r.status === "rejected");
765
765
  if (levelFailed) {
766
766
  publishFailed = true;
@@ -770,23 +770,23 @@ export async function runPublish(options: PublishOptions): Promise<void> {
770
770
  }
771
771
  }
772
772
 
773
- // 실패한 패키지 확인
773
+ // Check failed packages
774
774
  const allPkgNames = publishPackages.map((p) => p.name);
775
775
  const failedPkgNames = allPkgNames.filter((n) => !publishedPackages.includes(n));
776
776
 
777
777
  if (failedPkgNames.length > 0) {
778
778
  if (publishedPackages.length > 0) {
779
779
  logger.error(
780
- "배포 오류가 발생했습니다.\n" +
781
- "이미 배포된 패키지:\n" +
780
+ "Error during deployment.\n" +
781
+ "Already deployed packages:\n" +
782
782
  publishedPackages.map((n) => ` - ${n}`).join("\n") +
783
- "\n\n수동 복구가 필요할 있습니다.\n" +
784
- "npm 패키지는 72시간 내에 `npm unpublish <pkg>@<version>` 으로 삭제할 수 있습니다.",
783
+ "\n\nManual recovery may be necessary.\n" +
784
+ "npm packages can be deleted within 72 hours with `npm unpublish <pkg>@<version>`.",
785
785
  );
786
786
  }
787
787
 
788
788
  for (const name of failedPkgNames) {
789
- logger.error(`[${name}] 배포 실패`);
789
+ logger.error(`[${name}] Deployment failed`);
790
790
  }
791
791
  process.exitCode = 1;
792
792
  return;
@@ -798,9 +798,9 @@ export async function runPublish(options: PublishOptions): Promise<void> {
798
798
 
799
799
  if (sdConfig.postPublish != null && sdConfig.postPublish.length > 0) {
800
800
  if (dryRun) {
801
- logger.info("[DRY-RUN] postPublish 스크립트 시뮬레이션...");
801
+ logger.info("[DRY-RUN] Simulating postPublish scripts...");
802
802
  } else {
803
- logger.debug("postPublish 스크립트 실행...");
803
+ logger.debug("Running postPublish scripts...");
804
804
  }
805
805
 
806
806
  for (const script of sdConfig.postPublish) {
@@ -809,15 +809,15 @@ export async function runPublish(options: PublishOptions): Promise<void> {
809
809
  const args = script.args.map((arg) => replaceEnvVariables(arg, version, cwd));
810
810
 
811
811
  if (dryRun) {
812
- logger.info(`[DRY-RUN] 실행 예정: ${cmd} ${args.join(" ")}`);
812
+ logger.info(`[DRY-RUN] Will execute: ${cmd} ${args.join(" ")}`);
813
813
  } else {
814
- logger.debug(`실행: ${cmd} ${args.join(" ")}`);
814
+ logger.debug(`Executing: ${cmd} ${args.join(" ")}`);
815
815
  await execa(cmd, args, { cwd });
816
816
  }
817
817
  } catch (err) {
818
- // postPublish 실패 경고만 출력 (배포 롤백 불가)
818
+ // On postPublish failure, only warn (no deployment rollback possible)
819
819
  logger.warn(
820
- `postPublish 스크립트 실패 (계속 진행): ${err instanceof Error ? err.message : err}`,
820
+ `postPublish script failed (continuing): ${err instanceof Error ? err.message : err}`,
821
821
  );
822
822
  }
823
823
  }
@@ -826,9 +826,9 @@ export async function runPublish(options: PublishOptions): Promise<void> {
826
826
  //#endregion
827
827
 
828
828
  if (dryRun) {
829
- logger.info(`[DRY-RUN] 시뮬레이션 완료. 실제 배포 버전: v${version}`);
829
+ logger.info(`[DRY-RUN] Simulation completed. Actual deployment version: v${version}`);
830
830
  } else {
831
- logger.info(`모든 배포가 완료되었습니다. (v${version})`);
831
+ logger.info(`All deployments completed. (v${version})`);
832
832
  }
833
833
  }
834
834