@simplysm/sd-cli 13.0.99 → 14.0.1

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 (409) hide show
  1. package/dist/commands/build.js +29 -19
  2. package/dist/commands/build.js.map +1 -6
  3. package/dist/commands/check.d.ts +1 -0
  4. package/dist/commands/check.d.ts.map +1 -1
  5. package/dist/commands/check.js +130 -115
  6. package/dist/commands/check.js.map +1 -6
  7. package/dist/commands/dev.d.ts +6 -7
  8. package/dist/commands/dev.d.ts.map +1 -1
  9. package/dist/commands/dev.js +24 -14
  10. package/dist/commands/dev.js.map +1 -6
  11. package/dist/commands/lint.d.ts +1 -1
  12. package/dist/commands/lint.js +158 -116
  13. package/dist/commands/lint.js.map +1 -6
  14. package/dist/commands/publish.d.ts.map +1 -1
  15. package/dist/commands/publish.js +637 -510
  16. package/dist/commands/publish.js.map +1 -6
  17. package/dist/commands/replace-deps.js +12 -12
  18. package/dist/commands/replace-deps.js.map +1 -6
  19. package/dist/commands/typecheck.d.ts +5 -30
  20. package/dist/commands/typecheck.d.ts.map +1 -1
  21. package/dist/commands/typecheck.js +144 -207
  22. package/dist/commands/typecheck.js.map +1 -6
  23. package/dist/commands/watch.d.ts +6 -4
  24. package/dist/commands/watch.d.ts.map +1 -1
  25. package/dist/commands/watch.js +25 -16
  26. package/dist/commands/watch.js.map +1 -6
  27. package/dist/engines/NgtscEngine.d.ts +47 -0
  28. package/dist/engines/NgtscEngine.d.ts.map +1 -0
  29. package/dist/engines/NgtscEngine.js +151 -0
  30. package/dist/engines/NgtscEngine.js.map +1 -0
  31. package/dist/engines/ServerEsbuildEngine.d.ts +47 -0
  32. package/dist/engines/ServerEsbuildEngine.d.ts.map +1 -0
  33. package/dist/engines/ServerEsbuildEngine.js +159 -0
  34. package/dist/engines/ServerEsbuildEngine.js.map +1 -0
  35. package/dist/engines/TscEngine.d.ts +47 -0
  36. package/dist/engines/TscEngine.d.ts.map +1 -0
  37. package/dist/engines/TscEngine.js +153 -0
  38. package/dist/engines/TscEngine.js.map +1 -0
  39. package/dist/engines/ViteEngine.d.ts +49 -0
  40. package/dist/engines/ViteEngine.d.ts.map +1 -0
  41. package/dist/engines/ViteEngine.js +161 -0
  42. package/dist/engines/ViteEngine.js.map +1 -0
  43. package/dist/engines/index.d.ts +26 -0
  44. package/dist/engines/index.d.ts.map +1 -0
  45. package/dist/engines/index.js +30 -0
  46. package/dist/engines/index.js.map +1 -0
  47. package/dist/engines/types.d.ts +77 -0
  48. package/dist/engines/types.d.ts.map +1 -0
  49. package/dist/engines/types.js +2 -0
  50. package/dist/engines/types.js.map +1 -0
  51. package/dist/index.d.ts +0 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +2 -2
  54. package/dist/index.js.map +1 -6
  55. package/dist/infra/ResultCollector.d.ts +1 -1
  56. package/dist/infra/ResultCollector.d.ts.map +1 -1
  57. package/dist/infra/ResultCollector.js +30 -27
  58. package/dist/infra/ResultCollector.js.map +1 -6
  59. package/dist/infra/SignalHandler.js +45 -42
  60. package/dist/infra/SignalHandler.js.map +1 -6
  61. package/dist/infra/WorkerManager.js +56 -53
  62. package/dist/infra/WorkerManager.js.map +1 -6
  63. package/dist/orchestrators/BuildOrchestrator.d.ts +33 -1
  64. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  65. package/dist/orchestrators/BuildOrchestrator.js +314 -309
  66. package/dist/orchestrators/BuildOrchestrator.js.map +1 -6
  67. package/dist/orchestrators/DevWatchOrchestrator.d.ts +60 -0
  68. package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -0
  69. package/dist/orchestrators/DevWatchOrchestrator.js +465 -0
  70. package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -0
  71. package/dist/sd-cli-entry.d.ts.map +1 -1
  72. package/dist/sd-cli-entry.js +190 -266
  73. package/dist/sd-cli-entry.js.map +1 -6
  74. package/dist/sd-cli.js +77 -49
  75. package/dist/sd-cli.js.map +1 -6
  76. package/dist/sd-config.types.d.ts +2 -0
  77. package/dist/sd-config.types.d.ts.map +1 -1
  78. package/dist/sd-config.types.js +2 -1
  79. package/dist/sd-config.types.js.map +1 -6
  80. package/dist/utils/angular-build.d.ts +77 -0
  81. package/dist/utils/angular-build.d.ts.map +1 -0
  82. package/dist/utils/angular-build.js +84 -0
  83. package/dist/utils/angular-build.js.map +1 -0
  84. package/dist/utils/build-env.js +9 -9
  85. package/dist/utils/build-env.js.map +1 -6
  86. package/dist/utils/concurrency.d.ts +15 -0
  87. package/dist/utils/concurrency.d.ts.map +1 -0
  88. package/dist/utils/concurrency.js +38 -0
  89. package/dist/utils/concurrency.js.map +1 -0
  90. package/dist/utils/copy-public.js +104 -87
  91. package/dist/utils/copy-public.js.map +1 -6
  92. package/dist/utils/copy-src.js +49 -35
  93. package/dist/utils/copy-src.js.map +1 -6
  94. package/dist/utils/esbuild-config.d.ts +0 -29
  95. package/dist/utils/esbuild-config.d.ts.map +1 -1
  96. package/dist/utils/esbuild-config.js +151 -218
  97. package/dist/utils/esbuild-config.js.map +1 -6
  98. package/dist/utils/ngtsc-build-core.d.ts +49 -0
  99. package/dist/utils/ngtsc-build-core.d.ts.map +1 -0
  100. package/dist/utils/ngtsc-build-core.js +250 -0
  101. package/dist/utils/ngtsc-build-core.js.map +1 -0
  102. package/dist/utils/output-path-rewriter.d.ts +23 -0
  103. package/dist/utils/output-path-rewriter.d.ts.map +1 -0
  104. package/dist/utils/output-path-rewriter.js +74 -0
  105. package/dist/utils/output-path-rewriter.js.map +1 -0
  106. package/dist/utils/output-utils.js +55 -40
  107. package/dist/utils/output-utils.js.map +1 -6
  108. package/dist/utils/package-utils.d.ts +8 -0
  109. package/dist/utils/package-utils.d.ts.map +1 -1
  110. package/dist/utils/package-utils.js +103 -73
  111. package/dist/utils/package-utils.js.map +1 -6
  112. package/dist/utils/rebuild-manager.js +41 -44
  113. package/dist/utils/rebuild-manager.js.map +1 -6
  114. package/dist/utils/replace-deps.js +283 -184
  115. package/dist/utils/replace-deps.js.map +1 -6
  116. package/dist/utils/scss-compiler.d.ts +10 -0
  117. package/dist/utils/scss-compiler.d.ts.map +1 -0
  118. package/dist/utils/scss-compiler.js +36 -0
  119. package/dist/utils/scss-compiler.js.map +1 -0
  120. package/dist/utils/sd-config.js +29 -19
  121. package/dist/utils/sd-config.js.map +1 -6
  122. package/dist/utils/tsc-build.d.ts +36 -0
  123. package/dist/utils/tsc-build.d.ts.map +1 -0
  124. package/dist/utils/tsc-build.js +130 -0
  125. package/dist/utils/tsc-build.js.map +1 -0
  126. package/dist/utils/tsconfig.d.ts +7 -26
  127. package/dist/utils/tsconfig.d.ts.map +1 -1
  128. package/dist/utils/tsconfig.js +39 -64
  129. package/dist/utils/tsconfig.js.map +1 -6
  130. package/dist/utils/typecheck-non-package.d.ts +18 -0
  131. package/dist/utils/typecheck-non-package.d.ts.map +1 -0
  132. package/dist/utils/typecheck-non-package.js +64 -0
  133. package/dist/utils/typecheck-non-package.js.map +1 -0
  134. package/dist/utils/typecheck-serialization.js +58 -40
  135. package/dist/utils/typecheck-serialization.js.map +1 -6
  136. package/dist/utils/worker-events.js +48 -40
  137. package/dist/utils/worker-events.js.map +1 -6
  138. package/dist/utils/worker-utils.js +48 -28
  139. package/dist/utils/worker-utils.js.map +1 -6
  140. package/dist/vitest-plugin.d.ts +9 -0
  141. package/dist/vitest-plugin.d.ts.map +1 -0
  142. package/dist/vitest-plugin.js +85 -0
  143. package/dist/vitest-plugin.js.map +1 -0
  144. package/dist/workers/library-build.worker.d.ts +54 -0
  145. package/dist/workers/library-build.worker.d.ts.map +1 -0
  146. package/dist/workers/library-build.worker.js +97 -0
  147. package/dist/workers/library-build.worker.js.map +1 -0
  148. package/dist/workers/lint.worker.js +9 -6
  149. package/dist/workers/lint.worker.js.map +1 -6
  150. package/dist/workers/ngtsc-build.worker.d.ts +23 -0
  151. package/dist/workers/ngtsc-build.worker.d.ts.map +1 -0
  152. package/dist/workers/ngtsc-build.worker.js +98 -0
  153. package/dist/workers/ngtsc-build.worker.js.map +1 -0
  154. package/dist/workers/{server.worker.d.ts → server-build.worker.d.ts} +39 -29
  155. package/dist/workers/server-build.worker.d.ts.map +1 -0
  156. package/dist/workers/server-build.worker.js +399 -0
  157. package/dist/workers/server-build.worker.js.map +1 -0
  158. package/dist/workers/server-runtime.worker.d.ts +3 -2
  159. package/dist/workers/server-runtime.worker.d.ts.map +1 -1
  160. package/dist/workers/server-runtime.worker.js +100 -95
  161. package/dist/workers/server-runtime.worker.js.map +1 -6
  162. package/dist/workers/vite-build.worker.d.ts +56 -0
  163. package/dist/workers/vite-build.worker.d.ts.map +1 -0
  164. package/dist/workers/vite-build.worker.js +167 -0
  165. package/dist/workers/vite-build.worker.js.map +1 -0
  166. package/package.json +10 -16
  167. package/src/commands/check.ts +21 -3
  168. package/src/commands/dev.ts +10 -8
  169. package/src/commands/lint.ts +1 -1
  170. package/src/commands/publish.ts +4 -0
  171. package/src/commands/typecheck.ts +89 -256
  172. package/src/commands/watch.ts +9 -8
  173. package/src/engines/NgtscEngine.ts +190 -0
  174. package/src/engines/ServerEsbuildEngine.ts +195 -0
  175. package/src/engines/TscEngine.ts +189 -0
  176. package/src/engines/ViteEngine.ts +203 -0
  177. package/src/engines/index.ts +49 -0
  178. package/src/engines/types.ts +79 -0
  179. package/src/index.ts +0 -3
  180. package/src/infra/ResultCollector.ts +1 -1
  181. package/src/orchestrators/BuildOrchestrator.ts +87 -157
  182. package/src/orchestrators/DevWatchOrchestrator.ts +573 -0
  183. package/src/sd-cli-entry.ts +13 -116
  184. package/src/sd-config.types.ts +2 -0
  185. package/src/utils/angular-build.ts +157 -0
  186. package/src/utils/concurrency.ts +43 -0
  187. package/src/utils/esbuild-config.ts +1 -122
  188. package/src/utils/ngtsc-build-core.ts +379 -0
  189. package/src/utils/output-path-rewriter.ts +82 -0
  190. package/src/utils/package-utils.ts +20 -0
  191. package/src/utils/scss-compiler.ts +58 -0
  192. package/src/utils/tsc-build.ts +175 -0
  193. package/src/utils/tsconfig.ts +27 -95
  194. package/src/utils/typecheck-non-package.ts +87 -0
  195. package/src/vitest-plugin.ts +118 -0
  196. package/src/workers/library-build.worker.ts +153 -0
  197. package/src/workers/ngtsc-build.worker.ts +146 -0
  198. package/src/workers/server-build.worker.ts +565 -0
  199. package/src/workers/server-runtime.worker.ts +17 -26
  200. package/src/workers/vite-build.worker.ts +252 -0
  201. package/tests/commands/check.spec.ts +276 -0
  202. package/tests/commands/dev.spec.ts +53 -0
  203. package/tests/commands/lint.spec.ts +243 -0
  204. package/tests/commands/publish.spec.ts +1159 -0
  205. package/tests/commands/typecheck.spec.ts +294 -0
  206. package/tests/commands/watch.spec.ts +53 -0
  207. package/tests/engines/engine-selection.spec.ts +247 -0
  208. package/tests/engines/ngtsc-engine.spec.ts +274 -0
  209. package/tests/engines/server-esbuild-engine.spec.ts +256 -0
  210. package/tests/engines/tsc-engine.spec.ts +213 -0
  211. package/tests/engines/vite-engine.spec.ts +358 -0
  212. package/tests/infra/result-collector.spec.ts +46 -0
  213. package/tests/infra/signal-handler.spec.ts +32 -0
  214. package/tests/infra/worker-manager.spec.ts +63 -0
  215. package/tests/orchestrators/build-orchestrator.spec.ts +772 -0
  216. package/tests/orchestrators/dev-watch-orchestrator.spec.ts +1173 -0
  217. package/tests/sd-cli-entry.spec.ts +49 -0
  218. package/tests/utils/angular-build.spec.ts +251 -0
  219. package/tests/utils/build-env.spec.ts +33 -0
  220. package/tests/utils/concurrency.spec.ts +65 -0
  221. package/tests/utils/copy-src.spec.ts +144 -0
  222. package/tests/utils/esbuild-config.spec.ts +186 -0
  223. package/tests/utils/external-modules.spec.ts +161 -0
  224. package/tests/utils/ngtsc-scss-refactor.spec.ts +66 -0
  225. package/tests/utils/output-path-rewriter.spec.ts +165 -0
  226. package/tests/utils/output-utils.spec.ts +104 -0
  227. package/tests/utils/package-utils.spec.ts +52 -0
  228. package/tests/utils/rebuild-manager.spec.ts +30 -27
  229. package/tests/utils/replace-deps.spec.ts +69 -0
  230. package/tests/utils/scss-compiler.spec.ts +131 -0
  231. package/tests/utils/sd-config.spec.ts +77 -0
  232. package/tests/utils/tsc-build.spec.ts +358 -0
  233. package/tests/utils/tsconfig-angular.spec.ts +71 -0
  234. package/tests/utils/typecheck-non-package.spec.ts +120 -0
  235. package/tests/utils/worker-events.spec.ts +155 -0
  236. package/tests/utils/worker-utils.spec.ts +43 -0
  237. package/tests/vitest-plugin-cwd.spec.ts +68 -0
  238. package/tests/vitest-plugin.spec.ts +103 -0
  239. package/tests/workers/library-build-worker.spec.ts +258 -0
  240. package/tests/workers/ngtsc-build-worker.spec.ts +187 -0
  241. package/tests/workers/server-build-worker.spec.ts +566 -0
  242. package/tests/workers/server-runtime-worker.spec.ts +251 -0
  243. package/README.md +0 -295
  244. package/dist/builders/BaseBuilder.d.ts +0 -88
  245. package/dist/builders/BaseBuilder.d.ts.map +0 -1
  246. package/dist/builders/BaseBuilder.js +0 -142
  247. package/dist/builders/BaseBuilder.js.map +0 -6
  248. package/dist/builders/DtsBuilder.d.ts +0 -22
  249. package/dist/builders/DtsBuilder.d.ts.map +0 -1
  250. package/dist/builders/DtsBuilder.js +0 -72
  251. package/dist/builders/DtsBuilder.js.map +0 -6
  252. package/dist/builders/LibraryBuilder.d.ts +0 -22
  253. package/dist/builders/LibraryBuilder.d.ts.map +0 -1
  254. package/dist/builders/LibraryBuilder.js +0 -85
  255. package/dist/builders/LibraryBuilder.js.map +0 -6
  256. package/dist/builders/types.d.ts +0 -55
  257. package/dist/builders/types.d.ts.map +0 -1
  258. package/dist/builders/types.js +0 -1
  259. package/dist/builders/types.js.map +0 -6
  260. package/dist/capacitor/capacitor.d.ts +0 -151
  261. package/dist/capacitor/capacitor.d.ts.map +0 -1
  262. package/dist/capacitor/capacitor.js +0 -694
  263. package/dist/capacitor/capacitor.js.map +0 -6
  264. package/dist/commands/device.d.ts +0 -22
  265. package/dist/commands/device.d.ts.map +0 -1
  266. package/dist/commands/device.js +0 -98
  267. package/dist/commands/device.js.map +0 -6
  268. package/dist/commands/init.d.ts +0 -14
  269. package/dist/commands/init.d.ts.map +0 -1
  270. package/dist/commands/init.js +0 -72
  271. package/dist/commands/init.js.map +0 -6
  272. package/dist/electron/electron.d.ts +0 -84
  273. package/dist/electron/electron.d.ts.map +0 -1
  274. package/dist/electron/electron.js +0 -263
  275. package/dist/electron/electron.js.map +0 -6
  276. package/dist/orchestrators/DevOrchestrator.d.ts +0 -83
  277. package/dist/orchestrators/DevOrchestrator.d.ts.map +0 -1
  278. package/dist/orchestrators/DevOrchestrator.js +0 -540
  279. package/dist/orchestrators/DevOrchestrator.js.map +0 -6
  280. package/dist/orchestrators/WatchOrchestrator.d.ts +0 -57
  281. package/dist/orchestrators/WatchOrchestrator.d.ts.map +0 -1
  282. package/dist/orchestrators/WatchOrchestrator.js +0 -199
  283. package/dist/orchestrators/WatchOrchestrator.js.map +0 -6
  284. package/dist/utils/tailwind-config-deps.d.ts +0 -8
  285. package/dist/utils/tailwind-config-deps.d.ts.map +0 -1
  286. package/dist/utils/tailwind-config-deps.js +0 -82
  287. package/dist/utils/tailwind-config-deps.js.map +0 -6
  288. package/dist/utils/template.d.ts +0 -14
  289. package/dist/utils/template.d.ts.map +0 -1
  290. package/dist/utils/template.js +0 -33
  291. package/dist/utils/template.js.map +0 -6
  292. package/dist/utils/vite-config.d.ts +0 -35
  293. package/dist/utils/vite-config.d.ts.map +0 -1
  294. package/dist/utils/vite-config.js +0 -259
  295. package/dist/utils/vite-config.js.map +0 -6
  296. package/dist/workers/client.worker.d.ts +0 -83
  297. package/dist/workers/client.worker.d.ts.map +0 -1
  298. package/dist/workers/client.worker.js +0 -111
  299. package/dist/workers/client.worker.js.map +0 -6
  300. package/dist/workers/dts.worker.d.ts +0 -75
  301. package/dist/workers/dts.worker.d.ts.map +0 -1
  302. package/dist/workers/dts.worker.js +0 -270
  303. package/dist/workers/dts.worker.js.map +0 -6
  304. package/dist/workers/library.worker.d.ts +0 -75
  305. package/dist/workers/library.worker.d.ts.map +0 -1
  306. package/dist/workers/library.worker.js +0 -166
  307. package/dist/workers/library.worker.js.map +0 -6
  308. package/dist/workers/server.worker.d.ts.map +0 -1
  309. package/dist/workers/server.worker.js +0 -482
  310. package/dist/workers/server.worker.js.map +0 -6
  311. package/src/builders/BaseBuilder.ts +0 -218
  312. package/src/builders/DtsBuilder.ts +0 -92
  313. package/src/builders/LibraryBuilder.ts +0 -110
  314. package/src/builders/types.ts +0 -60
  315. package/src/capacitor/capacitor.ts +0 -931
  316. package/src/commands/device.ts +0 -140
  317. package/src/commands/init.ts +0 -113
  318. package/src/electron/electron.ts +0 -362
  319. package/src/orchestrators/DevOrchestrator.ts +0 -744
  320. package/src/orchestrators/WatchOrchestrator.ts +0 -277
  321. package/src/utils/tailwind-config-deps.ts +0 -98
  322. package/src/utils/template.ts +0 -56
  323. package/src/utils/vite-config.ts +0 -390
  324. package/src/workers/client.worker.ts +0 -250
  325. package/src/workers/dts.worker.ts +0 -453
  326. package/src/workers/library.worker.ts +0 -316
  327. package/src/workers/server.worker.ts +0 -734
  328. package/templates/init/.gitignore.hbs +0 -34
  329. package/templates/init/.npmrc.hbs +0 -1
  330. package/templates/init/.prettierignore +0 -1
  331. package/templates/init/.prettierrc.yaml +0 -12
  332. package/templates/init/eslint.config.ts +0 -15
  333. package/templates/init/mise.toml +0 -3
  334. package/templates/init/package.json.hbs +0 -32
  335. package/templates/init/packages/client-admin/index.html.hbs +0 -144
  336. package/templates/init/packages/client-admin/package.json.hbs +0 -27
  337. package/templates/init/packages/client-admin/public/assets/logo-landscape.png +0 -0
  338. package/templates/init/packages/client-admin/public/assets/logo.png +0 -0
  339. package/templates/init/packages/client-admin/public/favicon.ico +0 -0
  340. package/templates/init/packages/client-admin/src/App.tsx +0 -42
  341. package/templates/init/packages/client-admin/src/dev/DevDialog.tsx +0 -34
  342. package/templates/init/packages/client-admin/src/events/AuthChangeEvent.ts +0 -3
  343. package/templates/init/packages/client-admin/src/main.css +0 -4
  344. package/templates/init/packages/client-admin/src/main.tsx.hbs +0 -146
  345. package/templates/init/packages/client-admin/src/providers/AppServiceProvider.tsx.hbs +0 -103
  346. package/templates/init/packages/client-admin/src/providers/AppStructureProvider.tsx +0 -84
  347. package/templates/init/packages/client-admin/src/providers/AuthProvider.tsx.hbs +0 -96
  348. package/templates/init/packages/client-admin/src/providers/configureSharedData.ts.hbs +0 -67
  349. package/templates/init/packages/client-admin/src/views/auth/LoginView.tsx +0 -132
  350. package/templates/init/packages/client-admin/src/views/home/HomeView.tsx +0 -108
  351. package/templates/init/packages/client-admin/src/views/home/base/employee/EmployeeDetail.tsx.hbs +0 -243
  352. package/templates/init/packages/client-admin/src/views/home/base/employee/EmployeeSheet.tsx.hbs +0 -271
  353. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RoleDetail.tsx.hbs +0 -146
  354. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RolePermissionDetail.tsx.hbs +0 -121
  355. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RolePermissionView.tsx +0 -52
  356. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RoleSheet.tsx.hbs +0 -125
  357. package/templates/init/packages/client-admin/src/views/home/main/MainView.tsx.hbs +0 -13
  358. package/templates/init/packages/client-admin/src/views/home/my-info/MyInfoDetail.tsx.hbs +0 -241
  359. package/templates/init/packages/client-admin/src/views/home/system/system-log/SystemLogSheet.tsx.hbs +0 -169
  360. package/templates/init/packages/client-admin/src/views/not-found/NotFoundView.tsx +0 -15
  361. package/templates/init/packages/client-admin/tailwind.config.ts +0 -10
  362. package/templates/init/packages/db-main/package.json.hbs +0 -13
  363. package/templates/init/packages/db-main/src/MainDbContext.ts +0 -22
  364. package/templates/init/packages/db-main/src/dataLogExt.ts +0 -127
  365. package/templates/init/packages/db-main/src/index.ts +0 -14
  366. package/templates/init/packages/db-main/src/tables/base/Employee.ts +0 -24
  367. package/templates/init/packages/db-main/src/tables/base/EmployeeConfig.ts +0 -13
  368. package/templates/init/packages/db-main/src/tables/base/Role.ts +0 -9
  369. package/templates/init/packages/db-main/src/tables/base/RolePermission.ts +0 -13
  370. package/templates/init/packages/db-main/src/tables/system/_DataLog.ts +0 -19
  371. package/templates/init/packages/db-main/src/tables/system/_Log.ts +0 -16
  372. package/templates/init/packages/server/package.json.hbs +0 -20
  373. package/templates/init/packages/server/public-dev/dev//354/264/210/352/270/260/355/231/224.xlsx +0 -0
  374. package/templates/init/packages/server/src/index.ts +0 -4
  375. package/templates/init/packages/server/src/main.ts.hbs +0 -34
  376. package/templates/init/packages/server/src/services/AuthService.ts.hbs +0 -171
  377. package/templates/init/packages/server/src/services/DevService.ts.hbs +0 -94
  378. package/templates/init/packages/server/src/services/EmployeeService.ts.hbs +0 -122
  379. package/templates/init/packages/server/src/services/RoleService.ts.hbs +0 -59
  380. package/templates/init/pnpm-workspace.yaml +0 -15
  381. package/templates/init/sd.config.ts.hbs +0 -48
  382. package/templates/init/tsconfig.json.hbs +0 -39
  383. package/templates/init/vitest.config.ts +0 -36
  384. package/tests/capacitor-exclude.spec.ts +0 -78
  385. package/tests/capacitor.spec.ts +0 -49
  386. package/tests/copy-src.spec.ts +0 -50
  387. package/tests/electron-exclude.spec.ts +0 -61
  388. package/tests/get-compiler-options-for-package.spec.ts +0 -80
  389. package/tests/get-package-source-files.spec.ts +0 -139
  390. package/tests/get-types-from-package-json.spec.ts +0 -92
  391. package/tests/infra/ResultCollector.spec.ts +0 -30
  392. package/tests/infra/SignalHandler.spec.ts +0 -38
  393. package/tests/infra/WorkerManager.spec.ts +0 -63
  394. package/tests/load-ignore-patterns.spec.ts +0 -163
  395. package/tests/load-sd-config.spec.ts +0 -100
  396. package/tests/package-utils.spec.ts +0 -188
  397. package/tests/parse-root-tsconfig.spec.ts +0 -89
  398. package/tests/publish-config-narrowing.spec.ts +0 -20
  399. package/tests/replace-deps.spec.ts +0 -308
  400. package/tests/run-lint.spec.ts +0 -366
  401. package/tests/run-typecheck.spec.ts +0 -544
  402. package/tests/run-watch.spec.ts +0 -76
  403. package/tests/sd-cli.spec.ts +0 -265
  404. package/tests/sd-public-dev-plugin-mime.spec.ts +0 -19
  405. package/tests/tailwind-config-deps.spec.ts +0 -30
  406. package/tests/template.spec.ts +0 -70
  407. package/tests/vite-config-exclude.spec.ts +0 -35
  408. package/tests/vite-config-outdir.spec.ts +0 -38
  409. package/tests/write-changed-output-files.spec.ts +0 -97
@@ -6,6 +6,7 @@ import { fsx } from "@simplysm/core-node";
6
6
  import { env, json } from "@simplysm/core-common";
7
7
  import "@simplysm/core-common";
8
8
  import { loadSdConfig } from "../utils/sd-config.js";
9
+ import { validateTargets } from "../utils/package-utils.js";
9
10
  import { execa } from "execa";
10
11
  import { runBuild } from "./build.js";
11
12
  import { parseWorkspaceGlobs } from "../utils/replace-deps.js";
@@ -14,544 +15,670 @@ import fs from "fs";
14
15
  import ssh2 from "ssh2";
15
16
  import { password as passwordPrompt } from "@inquirer/prompts";
16
17
  const { Client: SshClient, utils } = ssh2;
18
+ //#endregion
19
+ //#region Utilities
20
+ /**
21
+ * Replace environment variables (%VAR% format)
22
+ * @throws throws an error if any variable substitution is empty
23
+ */
17
24
  function replaceEnvVariables(str, version, projectPath) {
18
- const result = str.replace(/%([^%]+)%/g, (match, envName) => {
19
- if (envName === "VER") {
20
- return version;
21
- }
22
- if (envName === "PROJECT") {
23
- return projectPath;
24
- }
25
- return env[envName] ?? match;
26
- });
27
- if (/%[^%]+%/.test(result)) {
28
- throw new Error(`Environment variable substitution failed: ${str} \u2192 ${result}`);
29
- }
30
- return result;
25
+ const result = str.replace(/%([^%]+)%/g, (match, envName) => {
26
+ if (envName === "VER") {
27
+ return version;
28
+ }
29
+ if (envName === "PROJECT") {
30
+ return projectPath;
31
+ }
32
+ return env[envName] ?? match;
33
+ });
34
+ // Throw error if any unsubstituted environment variables remain
35
+ if (/%[^%]+%/.test(result)) {
36
+ throw new Error(`Environment variable substitution failed: ${str} → ${result}`);
37
+ }
38
+ return result;
31
39
  }
40
+ /**
41
+ * Wait with countdown
42
+ */
32
43
  async function waitWithCountdown(message, seconds) {
33
- for (let i = seconds; i > 0; i--) {
34
- if (i !== seconds && process.stdout.isTTY) {
35
- process.stdout.cursorTo(0);
36
- }
37
- process.stdout.write(`${message} ${i}`);
38
- await new Promise((resolve) => setTimeout(resolve, 1e3));
39
- }
40
- if (process.stdout.isTTY) {
41
- process.stdout.cursorTo(0);
42
- process.stdout.clearLine(0);
43
- } else {
44
- process.stdout.write("\n");
45
- }
44
+ for (let i = seconds; i > 0; i--) {
45
+ if (i !== seconds && process.stdout.isTTY) {
46
+ process.stdout.cursorTo(0);
47
+ }
48
+ process.stdout.write(`${message} ${i}`);
49
+ await new Promise((resolve) => setTimeout(resolve, 1000));
50
+ }
51
+ if (process.stdout.isTTY) {
52
+ process.stdout.cursorTo(0);
53
+ process.stdout.clearLine(0);
54
+ }
55
+ else {
56
+ process.stdout.write("\n");
57
+ }
46
58
  }
59
+ /**
60
+ * Pre-verify and configure SSH key authentication
61
+ *
62
+ * For SFTP servers without pass:
63
+ * 1. Generate SSH key file if it doesn't exist
64
+ * 2. Test key authentication, and if it fails, register public key using password
65
+ */
47
66
  async function ensureSshAuth(publishPackages, logger) {
48
- const sshTargets = /* @__PURE__ */ new Map();
49
- for (const pkg of publishPackages) {
50
- if (pkg.config.type === "npm") continue;
51
- if (pkg.config.type !== "sftp") continue;
52
- if (pkg.config.password != null) continue;
53
- if (pkg.config.user == null) {
54
- throw new Error(`[${pkg.name}] SFTP config missing user.`);
55
- }
56
- const key = `${pkg.config.user}@${pkg.config.host}`;
57
- sshTargets.set(key, {
58
- host: pkg.config.host,
59
- port: pkg.config.port,
60
- user: pkg.config.user
61
- });
62
- }
63
- if (sshTargets.size === 0) return;
64
- const sshDir = path.join(os.homedir(), ".ssh");
65
- const keyPath = path.join(sshDir, "id_ed25519");
66
- const pubKeyPath = path.join(sshDir, "id_ed25519.pub");
67
- if (!fs.existsSync(keyPath)) {
68
- logger.info("SSH key not found. Creating one...");
69
- if (!fs.existsSync(sshDir)) {
70
- fs.mkdirSync(sshDir, { mode: 448 });
71
- }
72
- const keyPair = utils.generateKeyPairSync("ed25519");
73
- fs.writeFileSync(keyPath, keyPair.private, { mode: 384 });
74
- fs.writeFileSync(pubKeyPath, keyPair.public + "\n", { mode: 420 });
75
- logger.info(`SSH key created: ${keyPath}`);
76
- }
77
- const privateKeyData = fs.readFileSync(keyPath);
78
- const publicKey = fs.readFileSync(pubKeyPath, "utf-8").trim();
79
- const parsed = utils.parseKey(privateKeyData);
80
- const isKeyEncrypted = parsed instanceof Error;
81
- const sshAgent = process.env["SSH_AUTH_SOCK"];
82
- for (const [label, target] of sshTargets) {
83
- const canAuth = await testSshKeyAuth(target, {
84
- privateKey: isKeyEncrypted ? void 0 : privateKeyData,
85
- agent: sshAgent
86
- });
87
- if (canAuth) {
88
- logger.debug(`SSH key authentication verified: ${label}`);
89
- continue;
67
+ // Collect SFTP servers without pass (deduplicate user@host)
68
+ const sshTargets = new Map();
69
+ for (const pkg of publishPackages) {
70
+ if (pkg.config.type === "npm")
71
+ continue;
72
+ if (pkg.config.type !== "sftp")
73
+ continue;
74
+ if (pkg.config.password != null)
75
+ continue;
76
+ if (pkg.config.user == null) {
77
+ throw new Error(`[${pkg.name}] SFTP config missing user.`);
78
+ }
79
+ const key = `${pkg.config.user}@${pkg.config.host}`;
80
+ sshTargets.set(key, {
81
+ host: pkg.config.host,
82
+ port: pkg.config.port,
83
+ user: pkg.config.user,
84
+ });
85
+ }
86
+ if (sshTargets.size === 0)
87
+ return;
88
+ // Check/create SSH key file
89
+ const sshDir = path.join(os.homedir(), ".ssh");
90
+ const keyPath = path.join(sshDir, "id_ed25519");
91
+ const pubKeyPath = path.join(sshDir, "id_ed25519.pub");
92
+ if (!fs.existsSync(keyPath)) {
93
+ logger.info("SSH key not found. Creating one...");
94
+ if (!fs.existsSync(sshDir)) {
95
+ fs.mkdirSync(sshDir, { mode: 0o700 });
96
+ }
97
+ const keyPair = utils.generateKeyPairSync("ed25519");
98
+ fs.writeFileSync(keyPath, keyPair.private, { mode: 0o600 });
99
+ fs.writeFileSync(pubKeyPath, keyPair.public + "\n", { mode: 0o644 });
100
+ logger.info(`SSH key created: ${keyPath}`);
101
+ }
102
+ const privateKeyData = fs.readFileSync(keyPath);
103
+ const publicKey = fs.readFileSync(pubKeyPath, "utf-8").trim();
104
+ // Check if privateKey is encrypted
105
+ const parsed = utils.parseKey(privateKeyData);
106
+ const isKeyEncrypted = parsed instanceof Error;
107
+ const sshAgent = process.env["SSH_AUTH_SOCK"];
108
+ // Verify key authentication for each server
109
+ for (const [label, target] of sshTargets) {
110
+ const canAuth = await testSshKeyAuth(target, {
111
+ privateKey: isKeyEncrypted ? undefined : privateKeyData,
112
+ agent: sshAgent,
113
+ });
114
+ if (canAuth) {
115
+ logger.debug(`SSH key authentication verified: ${label}`);
116
+ continue;
117
+ }
118
+ // Key authentication failed → register public key using password
119
+ logger.info(`${label}: SSH key not registered on server.`);
120
+ const pass = await passwordPrompt({
121
+ message: `${label} password (to register public key):`,
122
+ });
123
+ await registerSshPublicKey(target, pass, publicKey);
124
+ logger.info(`SSH public key registered: ${label}`);
90
125
  }
91
- logger.info(`${label}: SSH key not registered on server.`);
92
- const pass = await passwordPrompt({
93
- message: `${label} password (to register public key):`
94
- });
95
- await registerSshPublicKey(target, pass, publicKey);
96
- logger.info(`SSH public key registered: ${label}`);
97
- }
98
126
  }
127
+ /**
128
+ * Test SSH key authentication (connect and immediately disconnect)
129
+ */
99
130
  function testSshKeyAuth(target, auth) {
100
- if (auth.privateKey == null && auth.agent == null) {
101
- return Promise.resolve(false);
102
- }
103
- return new Promise((resolve) => {
104
- const conn = new SshClient();
105
- conn.on("ready", () => {
106
- conn.end();
107
- resolve(true);
108
- });
109
- conn.on("error", () => {
110
- resolve(false);
111
- });
112
- conn.connect({
113
- host: target.host,
114
- port: target.port ?? 22,
115
- username: target.user,
116
- ...auth.privateKey != null ? { privateKey: auth.privateKey } : {},
117
- ...auth.agent != null ? { agent: auth.agent } : {},
118
- readyTimeout: 1e4
131
+ if (auth.privateKey == null && auth.agent == null) {
132
+ return Promise.resolve(false);
133
+ }
134
+ return new Promise((resolve) => {
135
+ const conn = new SshClient();
136
+ conn.on("ready", () => {
137
+ conn.end();
138
+ resolve(true);
139
+ });
140
+ conn.on("error", () => {
141
+ resolve(false);
142
+ });
143
+ conn.connect({
144
+ host: target.host,
145
+ port: target.port ?? 22,
146
+ username: target.user,
147
+ ...(auth.privateKey != null ? { privateKey: auth.privateKey } : {}),
148
+ ...(auth.agent != null ? { agent: auth.agent } : {}),
149
+ readyTimeout: 10_000,
150
+ });
119
151
  });
120
- });
121
152
  }
153
+ /**
154
+ * Connect to server with password and register SSH public key
155
+ */
122
156
  function registerSshPublicKey(target, pass, publicKey) {
123
- return new Promise((resolve, reject) => {
124
- const conn = new SshClient();
125
- conn.on("ready", () => {
126
- const escapedKey = publicKey.replace(/'/g, "'\\''");
127
- const cmd = [
128
- "mkdir -p ~/.ssh",
129
- "chmod 700 ~/.ssh",
130
- `echo '${escapedKey}' >> ~/.ssh/authorized_keys`,
131
- "chmod 600 ~/.ssh/authorized_keys"
132
- ].join(" && ");
133
- conn.exec(cmd, (err, stream) => {
134
- if (err) {
135
- conn.end();
136
- reject(new Error(`Failed to execute SSH command: ${err.message}`));
137
- return;
138
- }
139
- let stderr = "";
140
- stream.on("data", () => {
157
+ return new Promise((resolve, reject) => {
158
+ const conn = new SshClient();
159
+ conn.on("ready", () => {
160
+ // Add public key to authorized_keys
161
+ const escapedKey = publicKey.replace(/'/g, "'\\''");
162
+ const cmd = [
163
+ "mkdir -p ~/.ssh",
164
+ "chmod 700 ~/.ssh",
165
+ `echo '${escapedKey}' >> ~/.ssh/authorized_keys`,
166
+ "chmod 600 ~/.ssh/authorized_keys",
167
+ ].join(" && ");
168
+ conn.exec(cmd, (err, stream) => {
169
+ if (err) {
170
+ conn.end();
171
+ reject(new Error(`Failed to execute SSH command: ${err.message}`));
172
+ return;
173
+ }
174
+ let stderr = "";
175
+ stream.on("data", () => { }); // Consume stdout (unconsumed stream won't close)
176
+ stream.stderr.on("data", (data) => {
177
+ stderr += data.toString();
178
+ });
179
+ stream.on("exit", (code) => {
180
+ conn.end();
181
+ if (code !== 0) {
182
+ reject(new Error(`Failed to register SSH public key (exit code: ${code}): ${stderr}`));
183
+ }
184
+ else {
185
+ resolve();
186
+ }
187
+ });
188
+ });
141
189
  });
142
- stream.stderr.on("data", (data) => {
143
- stderr += data.toString();
190
+ conn.on("error", (err) => {
191
+ reject(new Error(`SSH connection failed (${target.host}): ${err.message}`));
144
192
  });
145
- stream.on("exit", (code) => {
146
- conn.end();
147
- if (code !== 0) {
148
- reject(new Error(`Failed to register SSH public key (exit code: ${code}): ${stderr}`));
149
- } else {
150
- resolve();
151
- }
193
+ conn.connect({
194
+ host: target.host,
195
+ port: target.port ?? 22,
196
+ username: target.user,
197
+ password: pass,
198
+ readyTimeout: 10_000,
152
199
  });
153
- });
154
- });
155
- conn.on("error", (err) => {
156
- reject(new Error(`SSH connection failed (${target.host}): ${err.message}`));
157
200
  });
158
- conn.connect({
159
- host: target.host,
160
- port: target.port ?? 22,
161
- username: target.user,
162
- password: pass,
163
- readyTimeout: 1e4
164
- });
165
- });
166
201
  }
202
+ //#endregion
203
+ //#region Version Upgrade
204
+ /**
205
+ * Upgrade project and package versions
206
+ * @param dryRun if true, only calculate new version without modifying files
207
+ */
167
208
  async function upgradeVersion(cwd, allPkgPaths, dryRun) {
168
- const changedFiles = [];
169
- const projPkgPath = path.resolve(cwd, "package.json");
170
- const projPkg = await fsx.readJson(projPkgPath);
171
- const currentVersion = projPkg.version;
172
- const prereleaseInfo = semver.prerelease(currentVersion);
173
- const newVersion = prereleaseInfo !== null ? semver.inc(currentVersion, "prerelease") : semver.inc(currentVersion, "patch");
174
- if (dryRun) {
175
- return { version: newVersion, changedFiles: [] };
176
- }
177
- projPkg.version = newVersion;
178
- await fsx.write(projPkgPath, json.stringify(projPkg, { space: 2 }) + "\n");
179
- changedFiles.push(projPkgPath);
180
- for (const pkgPath of allPkgPaths) {
181
- const pkgJsonPath = path.resolve(pkgPath, "package.json");
182
- const pkgJson = await fsx.readJson(pkgJsonPath);
183
- pkgJson.version = newVersion;
184
- await fsx.write(pkgJsonPath, json.stringify(pkgJson, { space: 2 }) + "\n");
185
- changedFiles.push(pkgJsonPath);
186
- }
187
- const templateFiles = await fsx.glob(path.resolve(cwd, "packages/sd-cli/templates/**/*.hbs"));
188
- const versionRegex = /("@simplysm\/[^"]+"\s*:\s*)"~[^"]+"/g;
189
- for (const templatePath of templateFiles) {
190
- const content = await fsx.read(templatePath);
191
- const newContent = content.replace(versionRegex, `$1"~${newVersion}"`);
192
- if (content !== newContent) {
193
- await fsx.write(templatePath, newContent);
194
- changedFiles.push(templatePath);
195
- }
196
- }
197
- return { version: newVersion, changedFiles };
209
+ const changedFiles = [];
210
+ const projPkgPath = path.resolve(cwd, "package.json");
211
+ const projPkg = await fsx.readJson(projPkgPath);
212
+ const currentVersion = projPkg.version;
213
+ const prereleaseInfo = semver.prerelease(currentVersion);
214
+ // Determine increment strategy based on prerelease status
215
+ const newVersion = prereleaseInfo !== null
216
+ ? semver.inc(currentVersion, "prerelease")
217
+ : semver.inc(currentVersion, "patch");
218
+ if (dryRun) {
219
+ // dry-run: return new version without modifying files
220
+ return { version: newVersion, changedFiles: [] };
221
+ }
222
+ projPkg.version = newVersion;
223
+ await fsx.write(projPkgPath, json.stringify(projPkg, { space: 2 }) + "\n");
224
+ changedFiles.push(projPkgPath);
225
+ // Set version in each package's package.json
226
+ for (const pkgPath of allPkgPaths) {
227
+ const pkgJsonPath = path.resolve(pkgPath, "package.json");
228
+ const pkgJson = await fsx.readJson(pkgJsonPath);
229
+ pkgJson.version = newVersion;
230
+ await fsx.write(pkgJsonPath, json.stringify(pkgJson, { space: 2 }) + "\n");
231
+ changedFiles.push(pkgJsonPath);
232
+ }
233
+ // Synchronize @simplysm package version in template files
234
+ const templateFiles = await fsx.glob(path.resolve(cwd, "packages/sd-cli/templates/**/*.hbs"));
235
+ const versionRegex = /("@simplysm\/[^"]+"\s*:\s*)"~[^"]+"/g;
236
+ for (const templatePath of templateFiles) {
237
+ const content = await fsx.read(templatePath);
238
+ const newContent = content.replace(versionRegex, `$1"~${newVersion}"`);
239
+ if (content !== newContent) {
240
+ await fsx.write(templatePath, newContent);
241
+ changedFiles.push(templatePath);
242
+ }
243
+ }
244
+ return { version: newVersion, changedFiles };
198
245
  }
246
+ //#endregion
247
+ //#region Package Publishing
248
+ /**
249
+ * Publish individual package
250
+ * @param dryRun if true, simulate deployment without actually publishing
251
+ */
199
252
  async function publishPackage(pkgPath, publishConfig, version, projectPath, logger, dryRun) {
200
- const pkgName = path.basename(pkgPath);
201
- if (publishConfig.type === "npm") {
202
- const prereleaseInfo = semver.prerelease(version);
203
- const args = ["publish", "--access", "public", "--no-git-checks"];
204
- if (prereleaseInfo !== null && typeof prereleaseInfo[0] === "string") {
205
- args.push("--tag", prereleaseInfo[0]);
253
+ const pkgName = path.basename(pkgPath);
254
+ if (publishConfig.type === "npm") {
255
+ // npm publish
256
+ const prereleaseInfo = semver.prerelease(version);
257
+ const args = ["publish", "--access", "public", "--no-git-checks"];
258
+ if (prereleaseInfo !== null && typeof prereleaseInfo[0] === "string") {
259
+ args.push("--tag", prereleaseInfo[0]);
260
+ }
261
+ if (dryRun) {
262
+ args.push("--dry-run");
263
+ logger.info(`[DRY-RUN] [${pkgName}] pnpm ${args.join(" ")}`);
264
+ }
265
+ else {
266
+ logger.debug(`[${pkgName}] pnpm ${args.join(" ")}`);
267
+ }
268
+ await execa("pnpm", args, { cwd: pkgPath });
269
+ }
270
+ else if (publishConfig.type === "local-directory") {
271
+ // Copy to local directory
272
+ const targetPath = replaceEnvVariables(publishConfig.path, version, projectPath);
273
+ const distPath = path.resolve(pkgPath, "dist");
274
+ if (dryRun) {
275
+ logger.info(`[DRY-RUN] [${pkgName}] copy to local: ${distPath} → ${targetPath}`);
276
+ }
277
+ else {
278
+ logger.debug(`[${pkgName}] copy to local: ${distPath} → ${targetPath}`);
279
+ await fsx.copy(distPath, targetPath);
280
+ }
281
+ }
282
+ else {
283
+ // Upload to storage
284
+ const distPath = path.resolve(pkgPath, "dist");
285
+ const remotePath = publishConfig.path ?? "/";
286
+ if (dryRun) {
287
+ logger.info(`[DRY-RUN] [${pkgName}] ${publishConfig.type} upload: ${distPath} → ${remotePath}`);
288
+ }
289
+ else {
290
+ logger.debug(`[${pkgName}] ${publishConfig.type} upload: ${distPath} → ${remotePath}`);
291
+ await StorageFactory.connect(publishConfig.type, {
292
+ host: publishConfig.host,
293
+ port: publishConfig.port,
294
+ user: publishConfig.user,
295
+ password: publishConfig.password,
296
+ }, async (storage) => {
297
+ await storage.uploadDir(distPath, remotePath);
298
+ });
299
+ }
206
300
  }
207
- if (dryRun) {
208
- args.push("--dry-run");
209
- logger.info(`[DRY-RUN] [${pkgName}] pnpm ${args.join(" ")}`);
210
- } else {
211
- logger.debug(`[${pkgName}] pnpm ${args.join(" ")}`);
212
- }
213
- await execa("pnpm", args, { cwd: pkgPath });
214
- } else if (publishConfig.type === "local-directory") {
215
- const targetPath = replaceEnvVariables(publishConfig.path, version, projectPath);
216
- const distPath = path.resolve(pkgPath, "dist");
217
- if (dryRun) {
218
- logger.info(`[DRY-RUN] [${pkgName}] copy to local: ${distPath} \u2192 ${targetPath}`);
219
- } else {
220
- logger.debug(`[${pkgName}] copy to local: ${distPath} \u2192 ${targetPath}`);
221
- await fsx.copy(distPath, targetPath);
222
- }
223
- } else {
224
- const distPath = path.resolve(pkgPath, "dist");
225
- const remotePath = publishConfig.path ?? "/";
226
- if (dryRun) {
227
- logger.info(
228
- `[DRY-RUN] [${pkgName}] ${publishConfig.type} upload: ${distPath} \u2192 ${remotePath}`
229
- );
230
- } else {
231
- logger.debug(`[${pkgName}] ${publishConfig.type} upload: ${distPath} \u2192 ${remotePath}`);
232
- await StorageFactory.connect(
233
- publishConfig.type,
234
- {
235
- host: publishConfig.host,
236
- port: publishConfig.port,
237
- user: publishConfig.user,
238
- password: publishConfig.password
239
- },
240
- async (storage) => {
241
- await storage.uploadDir(distPath, remotePath);
242
- }
243
- );
244
- }
245
- }
246
301
  }
302
+ //#endregion
303
+ //#region Dependency Levels
304
+ /**
305
+ * Calculate dependency levels for packages to publish.
306
+ * Packages with no dependencies → Level 0, depends only on Level 0 → Level 1, ...
307
+ */
247
308
  async function computePublishLevels(publishPkgs) {
248
- const pkgNames = new Set(publishPkgs.map((p) => p.name));
249
- const depsMap = /* @__PURE__ */ new Map();
250
- for (const pkg of publishPkgs) {
251
- const pkgJson = await fsx.readJson(path.resolve(pkg.path, "package.json"));
252
- const allDeps = {
253
- ...pkgJson.dependencies,
254
- ...pkgJson.peerDependencies,
255
- ...pkgJson.optionalDependencies
256
- };
257
- const workspaceDeps = /* @__PURE__ */ new Set();
258
- for (const depName of Object.keys(allDeps)) {
259
- const shortName = depName.replace(/^@simplysm\//, "");
260
- if (shortName !== depName && pkgNames.has(shortName)) {
261
- workspaceDeps.add(shortName);
262
- }
263
- }
264
- depsMap.set(pkg.name, workspaceDeps);
265
- }
266
- const levels = [];
267
- const assigned = /* @__PURE__ */ new Set();
268
- const remaining = new Map(publishPkgs.map((p) => [p.name, p]));
269
- while (remaining.size > 0) {
270
- const level = [];
271
- for (const [name, pkg] of remaining) {
272
- const deps = depsMap.get(name);
273
- if ([...deps].every((d) => assigned.has(d))) {
274
- level.push(pkg);
275
- }
276
- }
277
- if (level.length === 0) {
278
- levels.push([...remaining.values()]);
279
- break;
280
- }
281
- for (const pkg of level) {
282
- assigned.add(pkg.name);
283
- remaining.delete(pkg.name);
284
- }
285
- levels.push(level);
286
- }
287
- return levels;
309
+ const pkgNames = new Set(publishPkgs.map((p) => p.name));
310
+ // Collect workspace dependencies for each package
311
+ const depsMap = new Map();
312
+ for (const pkg of publishPkgs) {
313
+ const pkgJson = await fsx.readJson(path.resolve(pkg.path, "package.json"));
314
+ const allDeps = {
315
+ ...pkgJson.dependencies,
316
+ ...pkgJson.peerDependencies,
317
+ ...pkgJson.optionalDependencies,
318
+ };
319
+ const workspaceDeps = new Set();
320
+ for (const depName of Object.keys(allDeps)) {
321
+ const shortName = depName.replace(/^@simplysm\//, "");
322
+ if (shortName !== depName && pkgNames.has(shortName)) {
323
+ workspaceDeps.add(shortName);
324
+ }
325
+ }
326
+ depsMap.set(pkg.name, workspaceDeps);
327
+ }
328
+ // Topological sort to classify into levels
329
+ const levels = [];
330
+ const assigned = new Set();
331
+ const remaining = new Map(publishPkgs.map((p) => [p.name, p]));
332
+ while (remaining.size > 0) {
333
+ const level = [];
334
+ for (const [name, pkg] of remaining) {
335
+ const deps = depsMap.get(name);
336
+ if ([...deps].every((d) => assigned.has(d))) {
337
+ level.push(pkg);
338
+ }
339
+ }
340
+ if (level.length === 0) {
341
+ // Circular dependency — place all remaining packages in final level
342
+ levels.push([...remaining.values()]);
343
+ break;
344
+ }
345
+ for (const pkg of level) {
346
+ assigned.add(pkg.name);
347
+ remaining.delete(pkg.name);
348
+ }
349
+ levels.push(level);
350
+ }
351
+ return levels;
288
352
  }
289
- async function runPublish(options) {
290
- const { targets, noBuild, dryRun } = options;
291
- const cwd = process.cwd();
292
- const logger = consola.withTag("sd:cli:publish");
293
- if (dryRun) {
294
- logger.info("[DRY-RUN] Simulation mode - no actual deployment");
295
- }
296
- logger.debug("publish start", { targets, noBuild, dryRun });
297
- let sdConfig;
298
- try {
299
- sdConfig = await loadSdConfig({ cwd, dev: false, options: options.options });
300
- logger.debug("sd.config.ts loaded");
301
- } catch (err) {
302
- logger.error(`Failed to load sd.config.ts: ${err instanceof Error ? err.message : err}`);
303
- process.exitCode = 1;
304
- return;
305
- }
306
- const projPkgPath = path.resolve(cwd, "package.json");
307
- const projPkg = await fsx.readJson(projPkgPath);
308
- const workspaceYamlPath = path.resolve(cwd, "pnpm-workspace.yaml");
309
- const workspaceGlobs = [];
310
- if (await fsx.exists(workspaceYamlPath)) {
311
- const yamlContent = await fsx.read(workspaceYamlPath);
312
- workspaceGlobs.push(...parseWorkspaceGlobs(yamlContent));
313
- }
314
- const allPkgPaths = (await Promise.all(workspaceGlobs.map((item) => fsx.glob(path.resolve(cwd, item))))).flat().filter((item) => !path.basename(item).includes("."));
315
- const publishPackages = [];
316
- for (const [name, config] of Object.entries(sdConfig.packages)) {
317
- if (config == null) continue;
318
- const pkgConfig = config;
319
- if (pkgConfig.publish == null) continue;
320
- if (targets.length > 0 && !targets.includes(name)) continue;
321
- const pkgPath = allPkgPaths.find((p) => path.basename(p) === name);
322
- if (pkgPath == null) {
323
- logger.warn(`Package not found: ${name}`);
324
- continue;
325
- }
326
- publishPackages.push({
327
- name,
328
- path: pkgPath,
329
- config: pkgConfig.publish
330
- });
331
- }
332
- if (publishPackages.length === 0) {
333
- process.stdout.write("\u2714 No packages to deploy.\n");
334
- return;
335
- }
336
- logger.debug(
337
- "Target packages to deploy",
338
- publishPackages.map((p) => p.name)
339
- );
340
- const hasGit = await fsx.exists(path.resolve(cwd, ".git"));
341
- if (publishPackages.some((p) => p.config.type === "npm")) {
342
- logger.debug("Verifying npm authentication...");
343
- try {
344
- const { stdout: whoami } = await execa("npm", ["whoami"]);
345
- if (whoami.trim() === "") {
346
- throw new Error("npm login information not found.");
347
- }
348
- logger.debug(`npm login verified: ${whoami.trim()}`);
349
- } catch (err) {
350
- logger.error(`npm whoami failed:`, err);
351
- process.exitCode = 1;
352
- return;
353
- }
354
- }
355
- try {
356
- await ensureSshAuth(publishPackages, logger);
357
- } catch (err) {
358
- logger.error(`Failed to setup SSH authentication: ${err instanceof Error ? err.message : err}`);
359
- process.exitCode = 1;
360
- return;
361
- }
362
- if (!noBuild && hasGit) {
363
- logger.debug("Checking git commit status...");
364
- try {
365
- const { stdout: diff } = await execa("git", ["diff", "--name-only"]);
366
- const { stdout: stagedDiff } = await execa("git", ["diff", "--cached", "--name-only"]);
367
- if (diff.trim() !== "" || stagedDiff.trim() !== "") {
368
- logger.info("Uncommitted changes detected. Attempting auto-commit with claude...");
369
- try {
370
- await execa("claude", [
371
- "-p",
372
- "/sd-commit all",
373
- "--dangerously-skip-permissions",
374
- "--model",
375
- "haiku"
376
- ], { stdio: "inherit" });
377
- } catch (e) {
378
- throw new Error(
379
- "Auto-commit failed. Please commit manually and try again.\n" + (e instanceof Error ? e.message : String(e))
380
- );
381
- }
382
- const { stdout: recheckDiff } = await execa("git", ["diff", "--name-only"]);
383
- const { stdout: recheckStaged } = await execa("git", ["diff", "--cached", "--name-only"]);
384
- if (recheckDiff.trim() !== "" || recheckStaged.trim() !== "") {
385
- throw new Error(
386
- "Uncommitted changes still remain after auto-commit.\n" + recheckDiff + recheckStaged
387
- );
388
- }
389
- logger.info("Auto-commit completed.");
390
- }
391
- } catch (err) {
392
- logger.error(err instanceof Error ? err.message : err);
393
- process.exitCode = 1;
394
- return;
395
- }
396
- }
397
- let version = projPkg.version;
398
- if (noBuild) {
399
- logger.warn("Deploying without building is quite dangerous.");
400
- await waitWithCountdown("Press 'CTRL+C' to stop the process.", 5);
401
- } else {
402
- logger.debug("Upgrading version...");
403
- const upgradeResult = await upgradeVersion(cwd, allPkgPaths, dryRun);
404
- version = upgradeResult.version;
405
- const _changedFiles = upgradeResult.changedFiles;
353
+ //#endregion
354
+ //#region Main
355
+ /**
356
+ * Execute publish command.
357
+ *
358
+ * **Deployment order (safety first):**
359
+ * 1. Pre-validation (npm auth, Git status)
360
+ * 2. Version upgrade (package.json + templates)
361
+ * 3. Build
362
+ * 4. Git commit/tag/push (explicitly stage only changed files)
363
+ * 5. pnpm deployment
364
+ * 6. postPublish (continue even if it fails)
365
+ */
366
+ export async function runPublish(options) {
367
+ const { targets, noBuild, dryRun } = options;
368
+ const cwd = process.cwd();
369
+ const logger = consola.withTag("sd:cli:publish");
406
370
  if (dryRun) {
407
- logger.info(`[DRY-RUN] Version upgrade: ${projPkg.version} \u2192 ${version} (files not modified)`);
408
- } else {
409
- logger.info(`Version upgrade: ${projPkg.version} \u2192 ${version}`);
371
+ logger.info("[DRY-RUN] Simulation mode - no actual deployment");
410
372
  }
411
- if (dryRun) {
412
- logger.info("[DRY-RUN] Starting build (validation only)...");
413
- } else {
414
- logger.debug("Starting build...");
373
+ logger.debug("publish start", { targets, noBuild, dryRun });
374
+ // Load sd.config.ts
375
+ let sdConfig;
376
+ try {
377
+ sdConfig = await loadSdConfig({ cwd, dev: false, options: options.options });
378
+ logger.debug("sd.config.ts loaded");
379
+ }
380
+ catch (err) {
381
+ logger.error(`Failed to load sd.config.ts: ${err instanceof Error ? err.message : err}`);
382
+ process.exitCode = 1;
383
+ return;
384
+ }
385
+ // Validate targets
386
+ validateTargets(targets, sdConfig.packages);
387
+ // Load package.json
388
+ const projPkgPath = path.resolve(cwd, "package.json");
389
+ const projPkg = await fsx.readJson(projPkgPath);
390
+ // Collect workspace package paths from pnpm-workspace.yaml
391
+ const workspaceYamlPath = path.resolve(cwd, "pnpm-workspace.yaml");
392
+ const workspaceGlobs = [];
393
+ if (await fsx.exists(workspaceYamlPath)) {
394
+ const yamlContent = await fsx.read(workspaceYamlPath);
395
+ workspaceGlobs.push(...parseWorkspaceGlobs(yamlContent));
396
+ }
397
+ const allPkgPaths = (await Promise.all(workspaceGlobs.map((item) => fsx.glob(path.resolve(cwd, item)))))
398
+ .flat()
399
+ .filter((item) => !path.basename(item).includes("."));
400
+ // Filter packages with publish configuration
401
+ const publishPackages = [];
402
+ for (const [name, config] of Object.entries(sdConfig.packages)) {
403
+ if (config == null)
404
+ continue;
405
+ const pkgConfig = config;
406
+ if (pkgConfig.publish == null)
407
+ continue;
408
+ // If targets is specified, include only those packages
409
+ if (targets.length > 0 && !targets.includes(name))
410
+ continue;
411
+ const pkgPath = allPkgPaths.find((p) => path.basename(p) === name);
412
+ if (pkgPath == null) {
413
+ logger.warn(`Package not found: ${name}`);
414
+ continue;
415
+ }
416
+ publishPackages.push({
417
+ name,
418
+ path: pkgPath,
419
+ config: pkgConfig.publish,
420
+ });
415
421
  }
422
+ if (publishPackages.length === 0) {
423
+ process.stdout.write("✔ No packages to deploy.\n");
424
+ return;
425
+ }
426
+ logger.debug("Target packages to deploy", publishPackages.map((p) => p.name));
427
+ // Check if Git is available
428
+ const hasGit = await fsx.exists(path.resolve(cwd, ".git"));
429
+ //#region Phase 1: Pre-validation
430
+ // Verify npm authentication (if npm publish config exists)
431
+ if (publishPackages.some((p) => p.config.type === "npm")) {
432
+ logger.debug("Verifying npm authentication...");
433
+ try {
434
+ const { stdout: whoami } = await execa("npm", ["whoami"]);
435
+ if (whoami.trim() === "") {
436
+ throw new Error("npm login information not found.");
437
+ }
438
+ logger.debug(`npm login verified: ${whoami.trim()}`);
439
+ }
440
+ catch (err) {
441
+ logger.error(`npm whoami failed:`, err);
442
+ /*logger.error(
443
+ "npm token is invalid or expired.\n" +
444
+ "Create a Granular Access Token at https://www.npmjs.com/settings/~/tokens, then:\n" +
445
+ " npm config set //registry.npmjs.org/:_authToken <token>",
446
+ );*/
447
+ process.exitCode = 1;
448
+ return;
449
+ }
450
+ }
451
+ // Verify SSH key authentication (if SFTP publish config without pass exists)
416
452
  try {
417
- await runBuild({
418
- targets: publishPackages.map((p) => p.name),
419
- options: options.options
420
- });
421
- if (process.exitCode === 1) {
422
- throw new Error("Build failed");
423
- }
424
- } catch {
425
- if (dryRun) {
426
- logger.error("[DRY-RUN] Build failed");
427
- } else {
428
- logger.error(
429
- "Build failed. Manual recovery may be necessary:\n To revert version changes:\n git checkout -- package.json packages/*/package.json packages/sd-cli/templates/"
430
- );
431
- }
432
- process.exitCode = 1;
433
- return;
434
- }
435
- if (hasGit) {
436
- if (dryRun) {
437
- logger.info("[DRY-RUN] Simulating Git commit/tag/push...");
438
- logger.info(`[DRY-RUN] git add (${_changedFiles.length} files)`);
439
- logger.info(`[DRY-RUN] git commit -m "v${version}"`);
440
- logger.info(`[DRY-RUN] git tag -a v${version} -m "v${version}"`);
441
- logger.info("[DRY-RUN] git push --dry-run");
442
- await execa("git", ["push", "--dry-run"]);
443
- logger.info("[DRY-RUN] git push --tags --dry-run");
444
- await execa("git", ["push", "--tags", "--dry-run"]);
445
- logger.info("[DRY-RUN] Git operations simulation completed");
446
- } else {
447
- logger.debug("Git commit/tag/push...");
453
+ await ensureSshAuth(publishPackages, logger);
454
+ }
455
+ catch (err) {
456
+ logger.error(`Failed to setup SSH authentication: ${err instanceof Error ? err.message : err}`);
457
+ process.exitCode = 1;
458
+ return;
459
+ }
460
+ // Check for uncommitted changes and attempt auto-commit (unless noBuild is set)
461
+ if (!noBuild && hasGit) {
462
+ logger.debug("Checking git commit status...");
448
463
  try {
449
- await execa("git", ["add", ..._changedFiles]);
450
- await execa("git", ["commit", "-m", `v${version}`]);
451
- await execa("git", ["tag", "-a", `v${version}`, "-m", `v${version}`]);
452
- await execa("git", ["push"]);
453
- await execa("git", ["push", "--tags"]);
454
- logger.debug("Git operations completed");
455
- } catch (err) {
456
- logger.error(
457
- `Git operations failed: ${err instanceof Error ? err.message : err}
458
- Manual recovery may be necessary:
459
- git revert HEAD # Revert version commit
460
- git tag -d v${version} # Delete tag`
461
- );
462
- process.exitCode = 1;
463
- return;
464
- }
465
- }
466
- }
467
- }
468
- const levels = await computePublishLevels(publishPackages);
469
- const publishedPackages = [];
470
- let publishFailed = false;
471
- for (let levelIdx = 0; levelIdx < levels.length; levelIdx++) {
472
- if (publishFailed) break;
473
- const levelPkgs = levels[levelIdx];
474
- logger.start(`Level ${levelIdx + 1}/${levels.length}`);
475
- const publishPromises = levelPkgs.map(async (pkg) => {
476
- const maxRetries = 3;
477
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
464
+ const { stdout: diff } = await execa("git", ["diff", "--name-only"]);
465
+ const { stdout: stagedDiff } = await execa("git", ["diff", "--cached", "--name-only"]);
466
+ if (diff.trim() !== "" || stagedDiff.trim() !== "") {
467
+ logger.info("Uncommitted changes detected. Attempting auto-commit with claude...");
468
+ try {
469
+ await execa("claude", [
470
+ "-p",
471
+ "/sd-commit all",
472
+ "--dangerously-skip-permissions",
473
+ "--model",
474
+ "haiku",
475
+ ], { stdio: "inherit" });
476
+ }
477
+ catch (e) {
478
+ throw new Error("Auto-commit failed. Please commit manually and try again.\n" +
479
+ (e instanceof Error ? e.message : String(e)));
480
+ }
481
+ // Re-verify after commit
482
+ const { stdout: recheckDiff } = await execa("git", ["diff", "--name-only"]);
483
+ const { stdout: recheckStaged } = await execa("git", ["diff", "--cached", "--name-only"]);
484
+ if (recheckDiff.trim() !== "" || recheckStaged.trim() !== "") {
485
+ throw new Error("Uncommitted changes still remain after auto-commit.\n" + recheckDiff + recheckStaged);
486
+ }
487
+ logger.info("Auto-commit completed.");
488
+ }
489
+ }
490
+ catch (err) {
491
+ logger.error(err instanceof Error ? err.message : err);
492
+ process.exitCode = 1;
493
+ return;
494
+ }
495
+ }
496
+ //#endregion
497
+ //#region Phase 2 & 3: Build or noBuild warning
498
+ let version = projPkg.version;
499
+ if (noBuild) {
500
+ // noBuild warning
501
+ logger.warn("Deploying without building is quite dangerous.");
502
+ await waitWithCountdown("Press 'CTRL+C' to stop the process.", 5);
503
+ }
504
+ else {
505
+ // Version upgrade
506
+ logger.debug("Upgrading version...");
507
+ const upgradeResult = await upgradeVersion(cwd, allPkgPaths, dryRun);
508
+ version = upgradeResult.version;
509
+ const _changedFiles = upgradeResult.changedFiles;
510
+ if (dryRun) {
511
+ logger.info(`[DRY-RUN] Version upgrade: ${projPkg.version} → ${version} (files not modified)`);
512
+ }
513
+ else {
514
+ logger.info(`Version upgrade: ${projPkg.version} → ${version}`);
515
+ }
516
+ // Run build
517
+ if (dryRun) {
518
+ logger.info("[DRY-RUN] Starting build (validation only)...");
519
+ }
520
+ else {
521
+ logger.debug("Starting build...");
522
+ }
478
523
  try {
479
- await publishPackage(pkg.path, pkg.config, version, cwd, logger, dryRun);
480
- logger.debug(dryRun ? `[DRY-RUN] ${pkg.name}` : pkg.name);
481
- publishedPackages.push(pkg.name);
482
- return { status: "success", name: pkg.name };
483
- } catch (err) {
484
- if (attempt < maxRetries) {
485
- const delay = attempt * 5e3;
486
- logger.debug(
487
- dryRun ? `[DRY-RUN] ${pkg.name} (retry ${attempt + 1}/${maxRetries})` : `${pkg.name} (retry ${attempt + 1}/${maxRetries})`
488
- );
489
- await new Promise((resolve) => setTimeout(resolve, delay));
490
- } else {
491
- throw err;
492
- }
493
- }
494
- }
495
- return { status: "error", name: pkg.name, error: new Error("Unknown error") };
496
- });
497
- const results = await Promise.allSettled(publishPromises);
498
- const rejectedResults = results.filter(
499
- (r) => r.status === "rejected"
500
- );
501
- if (rejectedResults.length > 0) {
502
- publishFailed = true;
503
- for (const r of rejectedResults) {
504
- logger.error(r.reason instanceof Error ? r.reason.message : r.reason);
505
- }
506
- logger.fail(`Level ${levelIdx + 1}/${levels.length}`);
507
- } else {
508
- logger.success(`Level ${levelIdx + 1}/${levels.length}`);
509
- }
510
- }
511
- const allPkgNames = publishPackages.map((p) => p.name);
512
- const failedPkgNames = allPkgNames.filter((n) => !publishedPackages.includes(n));
513
- if (failedPkgNames.length > 0) {
514
- if (publishedPackages.length > 0) {
515
- logger.error(
516
- "Error during deployment.\nAlready deployed packages:\n" + publishedPackages.map((n) => ` - ${n}`).join("\n") + "\n\nManual recovery may be necessary.\nnpm packages can be deleted within 72 hours with `npm unpublish <pkg>@<version>`."
517
- );
518
- }
519
- for (const name of failedPkgNames) {
520
- logger.error(`[${name}] Deployment failed`);
521
- }
522
- process.exitCode = 1;
523
- return;
524
- }
525
- if (sdConfig.postPublish != null && sdConfig.postPublish.length > 0) {
526
- if (dryRun) {
527
- logger.info("[DRY-RUN] Simulating postPublish scripts...");
528
- } else {
529
- logger.debug("Running postPublish scripts...");
530
- }
531
- for (const script of sdConfig.postPublish) {
532
- try {
533
- const cmd = replaceEnvVariables(script.cmd, version, cwd);
534
- const args = script.args.map((arg) => replaceEnvVariables(arg, version, cwd));
524
+ await runBuild({
525
+ targets: publishPackages.map((p) => p.name),
526
+ options: options.options,
527
+ });
528
+ // Check build failure
529
+ if (process.exitCode === 1) {
530
+ throw new Error("Build failed");
531
+ }
532
+ }
533
+ catch {
534
+ if (dryRun) {
535
+ logger.error("[DRY-RUN] Build failed");
536
+ }
537
+ else {
538
+ logger.error("Build failed. Manual recovery may be necessary:\n" +
539
+ " To revert version changes:\n" +
540
+ " git checkout -- package.json packages/*/package.json packages/sd-cli/templates/");
541
+ }
542
+ process.exitCode = 1;
543
+ return;
544
+ }
545
+ //#region Phase 3: Git commit/tag/push
546
+ if (hasGit) {
547
+ if (dryRun) {
548
+ logger.info("[DRY-RUN] Simulating Git commit/tag/push...");
549
+ logger.info(`[DRY-RUN] git add (${_changedFiles.length} files)`);
550
+ logger.info(`[DRY-RUN] git commit -m "v${version}"`);
551
+ logger.info(`[DRY-RUN] git tag -a v${version} -m "v${version}"`);
552
+ logger.info("[DRY-RUN] git push --dry-run");
553
+ await execa("git", ["push", "--dry-run"]);
554
+ logger.info("[DRY-RUN] git push --tags --dry-run");
555
+ await execa("git", ["push", "--tags", "--dry-run"]);
556
+ logger.info("[DRY-RUN] Git operations simulation completed");
557
+ }
558
+ else {
559
+ logger.debug("Git commit/tag/push...");
560
+ try {
561
+ await execa("git", ["add", ..._changedFiles]);
562
+ await execa("git", ["commit", "-m", `v${version}`]);
563
+ await execa("git", ["tag", "-a", `v${version}`, "-m", `v${version}`]);
564
+ await execa("git", ["push"]);
565
+ await execa("git", ["push", "--tags"]);
566
+ logger.debug("Git operations completed");
567
+ }
568
+ catch (err) {
569
+ logger.error(`Git operations failed: ${err instanceof Error ? err.message : err}\n` +
570
+ "Manual recovery may be necessary:\n" +
571
+ ` git revert HEAD # Revert version commit\n` +
572
+ ` git tag -d v${version} # Delete tag`);
573
+ process.exitCode = 1;
574
+ return;
575
+ }
576
+ }
577
+ }
578
+ //#endregion
579
+ }
580
+ //#endregion
581
+ //#region Phase 4: Deployment (sequential by dependency level, parallel within level)
582
+ const levels = await computePublishLevels(publishPackages);
583
+ const publishedPackages = [];
584
+ let publishFailed = false;
585
+ // Sequential execution per level
586
+ for (let levelIdx = 0; levelIdx < levels.length; levelIdx++) {
587
+ if (publishFailed)
588
+ break;
589
+ const levelPkgs = levels[levelIdx];
590
+ logger.start(`Level ${levelIdx + 1}/${levels.length}`);
591
+ // Parallel execution within level (Promise.allSettled)
592
+ const publishPromises = levelPkgs.map(async (pkg) => {
593
+ const maxRetries = 3;
594
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
595
+ try {
596
+ await publishPackage(pkg.path, pkg.config, version, cwd, logger, dryRun);
597
+ logger.debug(dryRun ? `[DRY-RUN] ${pkg.name}` : pkg.name);
598
+ publishedPackages.push(pkg.name);
599
+ return { status: "success", name: pkg.name };
600
+ }
601
+ catch (err) {
602
+ if (attempt < maxRetries) {
603
+ const delay = attempt * 5_000;
604
+ logger.debug(dryRun
605
+ ? `[DRY-RUN] ${pkg.name} (retry ${attempt + 1}/${maxRetries})`
606
+ : `${pkg.name} (retry ${attempt + 1}/${maxRetries})`);
607
+ await new Promise((resolve) => setTimeout(resolve, delay));
608
+ }
609
+ else {
610
+ throw err;
611
+ }
612
+ }
613
+ }
614
+ // Fallback for TypeScript type checker (actually unreachable)
615
+ return { status: "error", name: pkg.name, error: new Error("Unknown error") };
616
+ });
617
+ const results = await Promise.allSettled(publishPromises);
618
+ // Check for failures within level
619
+ const rejectedResults = results.filter((r) => r.status === "rejected");
620
+ if (rejectedResults.length > 0) {
621
+ publishFailed = true;
622
+ for (const r of rejectedResults) {
623
+ logger.error(r.reason instanceof Error ? r.reason.message : r.reason);
624
+ }
625
+ logger.fail(`Level ${levelIdx + 1}/${levels.length}`);
626
+ }
627
+ else {
628
+ logger.success(`Level ${levelIdx + 1}/${levels.length}`);
629
+ }
630
+ }
631
+ // Check failed packages
632
+ const allPkgNames = publishPackages.map((p) => p.name);
633
+ const failedPkgNames = allPkgNames.filter((n) => !publishedPackages.includes(n));
634
+ if (failedPkgNames.length > 0) {
635
+ if (publishedPackages.length > 0) {
636
+ logger.error("Error during deployment.\n" +
637
+ "Already deployed packages:\n" +
638
+ publishedPackages.map((n) => ` - ${n}`).join("\n") +
639
+ "\n\nManual recovery may be necessary.\n" +
640
+ "npm packages can be deleted within 72 hours with `npm unpublish <pkg>@<version>`.");
641
+ }
642
+ for (const name of failedPkgNames) {
643
+ logger.error(`[${name}] Deployment failed`);
644
+ }
645
+ process.exitCode = 1;
646
+ return;
647
+ }
648
+ //#endregion
649
+ //#region Phase 5: postPublish
650
+ if (sdConfig.postPublish != null && sdConfig.postPublish.length > 0) {
535
651
  if (dryRun) {
536
- logger.info(`[DRY-RUN] Will execute: ${cmd} ${args.join(" ")}`);
537
- } else {
538
- logger.debug(`Executing: ${cmd} ${args.join(" ")}`);
539
- await execa(cmd, args, { cwd });
540
- }
541
- } catch (err) {
542
- logger.warn(
543
- `postPublish script failed (continuing): ${err instanceof Error ? err.message : err}`
544
- );
545
- }
546
- }
547
- }
548
- if (dryRun) {
549
- logger.info(`[DRY-RUN] Simulation completed. Actual deployment version: v${version}`);
550
- } else {
551
- logger.info(`All deployments completed. (v${version})`);
552
- }
652
+ logger.info("[DRY-RUN] Simulating postPublish scripts...");
653
+ }
654
+ else {
655
+ logger.debug("Running postPublish scripts...");
656
+ }
657
+ for (const script of sdConfig.postPublish) {
658
+ try {
659
+ const cmd = replaceEnvVariables(script.cmd, version, cwd);
660
+ const args = script.args.map((arg) => replaceEnvVariables(arg, version, cwd));
661
+ if (dryRun) {
662
+ logger.info(`[DRY-RUN] Will execute: ${cmd} ${args.join(" ")}`);
663
+ }
664
+ else {
665
+ logger.debug(`Executing: ${cmd} ${args.join(" ")}`);
666
+ await execa(cmd, args, { cwd });
667
+ }
668
+ }
669
+ catch (err) {
670
+ // On postPublish failure, only warn (no deployment rollback possible)
671
+ logger.warn(`postPublish script failed (continuing): ${err instanceof Error ? err.message : err}`);
672
+ }
673
+ }
674
+ }
675
+ //#endregion
676
+ if (dryRun) {
677
+ logger.info(`[DRY-RUN] Simulation completed. Actual deployment version: v${version}`);
678
+ }
679
+ else {
680
+ logger.info(`All deployments completed. (v${version})`);
681
+ }
553
682
  }
554
- export {
555
- runPublish
556
- };
557
- //# sourceMappingURL=publish.js.map
683
+ //#endregion
684
+ //# sourceMappingURL=publish.js.map