@simplysm/sd-cli 13.0.100 → 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
@@ -0,0 +1,1159 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import path from "path";
3
+
4
+ //#region Mock Infrastructure
5
+
6
+ const mocks = vi.hoisted(() => ({
7
+ loadSdConfig: vi.fn(),
8
+ runBuild: vi.fn(),
9
+ parseWorkspaceGlobs: vi.fn(),
10
+ execa: vi.fn(),
11
+ fsx: {
12
+ readJson: vi.fn(),
13
+ write: vi.fn(),
14
+ read: vi.fn(),
15
+ exists: vi.fn(),
16
+ glob: vi.fn(),
17
+ copy: vi.fn(),
18
+ },
19
+ storageConnect: vi.fn(),
20
+ SshClientInstance: {
21
+ on: vi.fn(),
22
+ connect: vi.fn(),
23
+ end: vi.fn(),
24
+ exec: vi.fn(),
25
+ },
26
+ SshClient: vi.fn(),
27
+ sshUtils: {
28
+ generateKeyPairSync: vi.fn(),
29
+ parseKey: vi.fn(),
30
+ },
31
+ passwordPrompt: vi.fn(),
32
+ fsExistsSync: vi.fn(),
33
+ fsReadFileSync: vi.fn(),
34
+ fsWriteFileSync: vi.fn(),
35
+ fsMkdirSync: vi.fn(),
36
+ homedir: vi.fn(),
37
+ }));
38
+
39
+ vi.mock("../../src/utils/sd-config", () => ({
40
+ loadSdConfig: mocks.loadSdConfig,
41
+ }));
42
+
43
+ vi.mock("../../src/commands/build", () => ({
44
+ runBuild: mocks.runBuild,
45
+ }));
46
+
47
+ vi.mock("../../src/utils/replace-deps", () => ({
48
+ parseWorkspaceGlobs: mocks.parseWorkspaceGlobs,
49
+ }));
50
+
51
+ vi.mock("execa", () => ({
52
+ execa: mocks.execa,
53
+ }));
54
+
55
+ vi.mock("@simplysm/core-node", () => ({
56
+ fsx: mocks.fsx,
57
+ }));
58
+
59
+ vi.mock("@simplysm/core-common", () => {
60
+ const envProxy = new Proxy(
61
+ {},
62
+ { get: (_t, key) => process.env[key as string] },
63
+ );
64
+ return {
65
+ env: envProxy,
66
+ json: {
67
+ stringify: (v: unknown, opts?: { space?: number }) =>
68
+ JSON.stringify(v, null, opts?.space),
69
+ },
70
+ SdError: class SdError extends Error {
71
+ constructor(message: string) {
72
+ super(message);
73
+ this.name = "SdError";
74
+ }
75
+ },
76
+ };
77
+ });
78
+
79
+ vi.mock("@simplysm/storage", () => ({
80
+ StorageFactory: { connect: mocks.storageConnect },
81
+ }));
82
+
83
+ vi.mock("ssh2", () => {
84
+ const ClientCtor = mocks.SshClient;
85
+ return {
86
+ default: {
87
+ Client: ClientCtor,
88
+ utils: mocks.sshUtils,
89
+ },
90
+ };
91
+ });
92
+
93
+ vi.mock("@inquirer/prompts", () => ({
94
+ password: mocks.passwordPrompt,
95
+ }));
96
+
97
+ vi.mock("fs", async (importOriginal: () => Promise<Record<string, unknown>>) => {
98
+ const actual = await importOriginal();
99
+ return {
100
+ ...actual,
101
+ default: {
102
+ ...(actual["default"] as Record<string, unknown>),
103
+ existsSync: mocks.fsExistsSync,
104
+ readFileSync: mocks.fsReadFileSync,
105
+ writeFileSync: mocks.fsWriteFileSync,
106
+ mkdirSync: mocks.fsMkdirSync,
107
+ },
108
+ };
109
+ });
110
+
111
+ vi.mock("os", async (importOriginal: () => Promise<Record<string, unknown>>) => {
112
+ const actual = await importOriginal();
113
+ return {
114
+ ...actual,
115
+ default: {
116
+ ...(actual["default"] as Record<string, unknown>),
117
+ homedir: mocks.homedir,
118
+ },
119
+ };
120
+ });
121
+
122
+ vi.mock("consola", () => {
123
+ const fns = (): Record<string, unknown> => ({
124
+ debug: vi.fn(),
125
+ start: vi.fn(),
126
+ success: vi.fn(),
127
+ fail: vi.fn(),
128
+ info: vi.fn(),
129
+ error: vi.fn(),
130
+ warn: vi.fn(),
131
+ log: vi.fn(),
132
+ withTag: vi.fn(() => fns()),
133
+ level: 0,
134
+ });
135
+ const c = fns();
136
+ return { consola: c, default: c, LogLevels: {} };
137
+ });
138
+
139
+ const { runPublish } = await import("../../src/commands/publish");
140
+
141
+ //#endregion
142
+
143
+ //#region Test Helpers
144
+
145
+ const CWD = process.cwd();
146
+
147
+ function pkgPath(name: string): string {
148
+ return path.resolve(CWD, `packages/${name}`);
149
+ }
150
+
151
+ function createPkgJson(
152
+ name: string,
153
+ version: string,
154
+ deps: Record<string, string> = {},
155
+ ): { name: string; version: string; dependencies: Record<string, string> } {
156
+ return { name: `@simplysm/${name}`, version, dependencies: deps };
157
+ }
158
+
159
+ /**
160
+ * Set up a default happy-path mock environment for npm publish
161
+ */
162
+ function setupHappyPath(opts: {
163
+ version?: string;
164
+ packages?: Record<string, { publish?: { type: string; [k: string]: unknown }; target?: string }>;
165
+ packageDeps?: Record<string, Record<string, string>>;
166
+ templateFiles?: string[];
167
+ hasGit?: boolean;
168
+ } = {}) {
169
+ const version = opts.version ?? "14.0.0";
170
+ const packages = opts.packages ?? {
171
+ "pkg-a": { target: "node", publish: { type: "npm" } },
172
+ "pkg-b": { target: "node", publish: { type: "npm" } },
173
+ };
174
+ const packageDeps = opts.packageDeps ?? {};
175
+ const templateFiles = opts.templateFiles ?? [];
176
+ const hasGit = opts.hasGit ?? true;
177
+ const pkgNames = Object.keys(packages);
178
+
179
+ // loadSdConfig
180
+ mocks.loadSdConfig.mockResolvedValue({ packages });
181
+
182
+ // parseWorkspaceGlobs
183
+ mocks.parseWorkspaceGlobs.mockReturnValue(["packages/*"]);
184
+
185
+ // fsx.readJson — route by path
186
+ mocks.fsx.readJson.mockImplementation((p: string) => {
187
+ const basename = path.basename(path.dirname(p));
188
+ // Root package.json
189
+ if (p === path.resolve(CWD, "package.json")) {
190
+ return createPkgJson("simplysm", version);
191
+ }
192
+ // Package package.json
193
+ if (pkgNames.includes(basename)) {
194
+ return createPkgJson(basename, version, packageDeps[basename] ?? {});
195
+ }
196
+ throw new Error(`Unexpected readJson path: ${p}`);
197
+ });
198
+
199
+ // fsx.write
200
+ mocks.fsx.write.mockResolvedValue(undefined);
201
+
202
+ // fsx.read
203
+ mocks.fsx.read.mockImplementation((p: string) => {
204
+ if (p.includes("pnpm-workspace.yaml")) {
205
+ return "packages:\n - packages/*";
206
+ }
207
+ if (p.endsWith(".hbs")) {
208
+ return `"@simplysm/core-common": "~${version}"`;
209
+ }
210
+ return "";
211
+ });
212
+
213
+ // fsx.exists
214
+ mocks.fsx.exists.mockImplementation((p: string) => {
215
+ if (p.endsWith("pnpm-workspace.yaml")) return true;
216
+ if (p.endsWith(".git")) return hasGit;
217
+ return false;
218
+ });
219
+
220
+ // fsx.glob
221
+ mocks.fsx.glob.mockImplementation((pattern: string) => {
222
+ if (pattern.includes("templates")) {
223
+ return templateFiles.map((f) => path.resolve(CWD, f));
224
+ }
225
+ // packages/*
226
+ return pkgNames.map((n) => pkgPath(n));
227
+ });
228
+
229
+ // fsx.copy
230
+ mocks.fsx.copy.mockResolvedValue(undefined);
231
+
232
+ // runBuild
233
+ mocks.runBuild.mockResolvedValue(undefined);
234
+
235
+ // execa — default: all succeed
236
+ mocks.execa.mockImplementation(
237
+ (cmd: string, _args?: string[], _opts?: unknown) => {
238
+ if (cmd === "npm") return { stdout: "testuser", stderr: "", exitCode: 0 };
239
+ return { stdout: "", stderr: "", exitCode: 0 };
240
+ },
241
+ );
242
+
243
+ // SSH-related: not needed for npm-only, but set defaults
244
+ mocks.homedir.mockReturnValue("/mock/home");
245
+ mocks.fsExistsSync.mockReturnValue(true);
246
+ mocks.fsReadFileSync.mockReturnValue(new TextEncoder().encode("fake-key"));
247
+ mocks.sshUtils.parseKey.mockReturnValue({});
248
+ }
249
+
250
+ /**
251
+ * Get all execa calls matching a command prefix
252
+ */
253
+ function getExecaCalls(
254
+ cmd: string,
255
+ firstArg?: string,
256
+ ): Array<{ cmd: string; args: string[] }> {
257
+ return mocks.execa.mock.calls
258
+ .filter(
259
+ (c: unknown[]) =>
260
+ c[0] === cmd && (firstArg == null || (c[1] as string[] | undefined)?.[0] === firstArg),
261
+ )
262
+ .map((c: unknown[]) => ({ cmd: c[0] as string, args: (c[1] as string[] | undefined) ?? [] }));
263
+ }
264
+
265
+ //#endregion
266
+
267
+ describe("runPublish", () => {
268
+ let savedExitCode: string | number | undefined;
269
+
270
+ beforeEach(() => {
271
+ vi.clearAllMocks();
272
+ savedExitCode = process.exitCode;
273
+ process.exitCode = undefined;
274
+ });
275
+
276
+ afterEach(() => {
277
+ process.exitCode = savedExitCode;
278
+ });
279
+
280
+ //#region Slice 1: Package Selection + Happy Path
281
+
282
+ describe("Slice 1: Package selection", () => {
283
+ it("publishes only targeted packages when targets specified", async () => {
284
+ setupHappyPath();
285
+
286
+ await runPublish({
287
+ targets: ["pkg-a"],
288
+ noBuild: false,
289
+ dryRun: false,
290
+ options: [],
291
+ });
292
+
293
+ const publishCalls = getExecaCalls("pnpm", "publish");
294
+ expect(publishCalls).toHaveLength(1);
295
+ // The publish call should be for pkg-a's directory
296
+ const call = mocks.execa.mock.calls.find(
297
+ (c: unknown[]) => c[0] === "pnpm" && (c[1] as string[] | undefined)?.[0] === "publish",
298
+ );
299
+ expect(call?.[2]).toHaveProperty("cwd", pkgPath("pkg-a"));
300
+ });
301
+
302
+ it("publishes all packages with publish config when targets empty", async () => {
303
+ setupHappyPath();
304
+
305
+ await runPublish({
306
+ targets: [],
307
+ noBuild: false,
308
+ dryRun: false,
309
+ options: [],
310
+ });
311
+
312
+ const publishCalls = getExecaCalls("pnpm", "publish");
313
+ expect(publishCalls).toHaveLength(2);
314
+ });
315
+
316
+ it("ignores packages without publish config", async () => {
317
+ setupHappyPath({
318
+ packages: {
319
+ "pkg-a": { target: "node", publish: { type: "npm" } },
320
+ "pkg-b": { target: "node" }, // no publish
321
+ },
322
+ });
323
+
324
+ await runPublish({
325
+ targets: [],
326
+ noBuild: false,
327
+ dryRun: false,
328
+ options: [],
329
+ });
330
+
331
+ const publishCalls = getExecaCalls("pnpm", "publish");
332
+ expect(publishCalls).toHaveLength(1);
333
+ });
334
+
335
+ it("outputs no-op message when no packages to deploy", async () => {
336
+ setupHappyPath({
337
+ packages: {
338
+ "pkg-a": { target: "node" }, // no publish
339
+ },
340
+ });
341
+
342
+ const writeSpy = vi.spyOn(process.stdout, "write").mockReturnValue(true);
343
+
344
+ await runPublish({
345
+ targets: [],
346
+ noBuild: false,
347
+ dryRun: false,
348
+ options: [],
349
+ });
350
+
351
+ const output = writeSpy.mock.calls.map((c) => String(c[0])).join("");
352
+ expect(output).toContain("No packages to deploy");
353
+ writeSpy.mockRestore();
354
+ });
355
+ });
356
+
357
+ //#endregion
358
+
359
+ //#region Slice 2: Pre-validation
360
+
361
+ describe("Slice 2: Pre-validation", () => {
362
+ it("passes when npm whoami returns valid username", async () => {
363
+ setupHappyPath();
364
+
365
+ await runPublish({
366
+ targets: [],
367
+ noBuild: false,
368
+ dryRun: false,
369
+ options: [],
370
+ });
371
+
372
+ expect(process.exitCode).toBeUndefined();
373
+ expect(getExecaCalls("npm", "whoami")).toHaveLength(1);
374
+ });
375
+
376
+ it("aborts when npm whoami fails", async () => {
377
+ setupHappyPath();
378
+ mocks.execa.mockImplementation((cmd: string) => {
379
+ if (cmd === "npm") throw new Error("npm not authenticated");
380
+ return { stdout: "", stderr: "", exitCode: 0 };
381
+ });
382
+
383
+ await runPublish({
384
+ targets: [],
385
+ noBuild: false,
386
+ dryRun: false,
387
+ options: [],
388
+ });
389
+
390
+ expect(process.exitCode).toBe(1);
391
+ // Should not reach build phase
392
+ expect(mocks.runBuild).not.toHaveBeenCalled();
393
+ });
394
+
395
+ it("skips npm auth check when no npm publish packages", async () => {
396
+ setupHappyPath({
397
+ packages: {
398
+ "pkg-a": {
399
+ target: "node",
400
+ publish: {
401
+ type: "local-directory",
402
+ path: "/deploy/%VER%",
403
+ },
404
+ },
405
+ },
406
+ hasGit: false,
407
+ });
408
+
409
+ await runPublish({
410
+ targets: [],
411
+ noBuild: false,
412
+ dryRun: false,
413
+ options: [],
414
+ });
415
+
416
+ expect(getExecaCalls("npm", "whoami")).toHaveLength(0);
417
+ });
418
+
419
+ it("auto-commits when uncommitted changes detected", async () => {
420
+ setupHappyPath();
421
+ let gitDiffCallCount = 0;
422
+ mocks.execa.mockImplementation(
423
+ (cmd: string, args?: string[], _opts?: unknown) => {
424
+ if (cmd === "npm") return { stdout: "testuser", stderr: "", exitCode: 0 };
425
+ if (cmd === "git" && args?.[0] === "diff") {
426
+ gitDiffCallCount++;
427
+ // First call: has changes; after claude commit: no changes
428
+ if (gitDiffCallCount <= 2) {
429
+ return { stdout: "file.txt", stderr: "", exitCode: 0 };
430
+ }
431
+ return { stdout: "", stderr: "", exitCode: 0 };
432
+ }
433
+ return { stdout: "", stderr: "", exitCode: 0 };
434
+ },
435
+ );
436
+
437
+ await runPublish({
438
+ targets: [],
439
+ noBuild: false,
440
+ dryRun: false,
441
+ options: [],
442
+ });
443
+
444
+ // claude CLI should have been called for auto-commit
445
+ const claudeCalls = mocks.execa.mock.calls.filter(
446
+ (c: unknown[]) => c[0] === "claude",
447
+ );
448
+ expect(claudeCalls).toHaveLength(1);
449
+ expect(claudeCalls[0][1]).toContain("/sd-commit all");
450
+ });
451
+
452
+ it("aborts when auto-commit fails to resolve all changes", async () => {
453
+ setupHappyPath();
454
+ mocks.execa.mockImplementation((cmd: string, args?: string[]) => {
455
+ if (cmd === "npm") return { stdout: "testuser", stderr: "", exitCode: 0 };
456
+ if (cmd === "git" && args?.[0] === "diff") {
457
+ // Always has changes (auto-commit doesn't resolve)
458
+ return { stdout: "file.txt", stderr: "", exitCode: 0 };
459
+ }
460
+ return { stdout: "", stderr: "", exitCode: 0 };
461
+ });
462
+
463
+ await runPublish({
464
+ targets: [],
465
+ noBuild: false,
466
+ dryRun: false,
467
+ options: [],
468
+ });
469
+
470
+ expect(process.exitCode).toBe(1);
471
+ });
472
+
473
+ it("skips auto-commit when no uncommitted changes", async () => {
474
+ setupHappyPath();
475
+
476
+ await runPublish({
477
+ targets: [],
478
+ noBuild: false,
479
+ dryRun: false,
480
+ options: [],
481
+ });
482
+
483
+ const claudeCalls = mocks.execa.mock.calls.filter(
484
+ (c: unknown[]) => c[0] === "claude",
485
+ );
486
+ expect(claudeCalls).toHaveLength(0);
487
+ });
488
+ });
489
+
490
+ //#endregion
491
+
492
+ //#region Slice 3: Version Upgrade + Git
493
+
494
+ describe("Slice 3: Version upgrade + Git", () => {
495
+ it("increments patch for stable version", async () => {
496
+ setupHappyPath({ version: "14.0.0" });
497
+
498
+ await runPublish({
499
+ targets: [],
500
+ noBuild: false,
501
+ dryRun: false,
502
+ options: [],
503
+ });
504
+
505
+ // Check that fsx.write was called with new version
506
+ const writeCalls = mocks.fsx.write.mock.calls;
507
+ const rootPkgWrite = writeCalls.find((c: unknown[]) =>
508
+ (c[0] as string).endsWith("package.json") &&
509
+ !(c[0] as string).includes("packages/"),
510
+ );
511
+ expect(rootPkgWrite).toBeDefined();
512
+ expect(rootPkgWrite![1]).toContain('"14.0.1"');
513
+ });
514
+
515
+ it("increments prerelease for prerelease version", async () => {
516
+ setupHappyPath({ version: "14.0.0-beta.1" });
517
+
518
+ await runPublish({
519
+ targets: [],
520
+ noBuild: false,
521
+ dryRun: false,
522
+ options: [],
523
+ });
524
+
525
+ const writeCalls = mocks.fsx.write.mock.calls;
526
+ const rootPkgWrite = writeCalls.find((c: unknown[]) =>
527
+ (c[0] as string).endsWith("package.json") &&
528
+ !(c[0] as string).includes("packages/"),
529
+ );
530
+ expect(rootPkgWrite).toBeDefined();
531
+ expect(rootPkgWrite![1]).toContain('"14.0.0-beta.2"');
532
+ });
533
+
534
+ it("syncs version across all workspace packages", async () => {
535
+ setupHappyPath({ version: "14.0.0" });
536
+
537
+ await runPublish({
538
+ targets: [],
539
+ noBuild: false,
540
+ dryRun: false,
541
+ options: [],
542
+ });
543
+
544
+ // Each package's package.json should be written with new version
545
+ const writeCalls = mocks.fsx.write.mock.calls.filter((c: unknown[]) => {
546
+ const p = (c[0] as string).replace(/\\/g, "/");
547
+ return p.includes("packages/") && p.endsWith("package.json");
548
+ });
549
+ expect(writeCalls.length).toBeGreaterThanOrEqual(2);
550
+ for (const call of writeCalls) {
551
+ expect(call[1]).toContain('"14.0.1"');
552
+ }
553
+ });
554
+
555
+ it("syncs @simplysm version in template files", async () => {
556
+ setupHappyPath({
557
+ version: "14.0.0",
558
+ templateFiles: ["packages/sd-cli/templates/test.hbs"],
559
+ });
560
+
561
+ await runPublish({
562
+ targets: [],
563
+ noBuild: false,
564
+ dryRun: false,
565
+ options: [],
566
+ });
567
+
568
+ // Template file should be written with updated version
569
+ const templateWrite = mocks.fsx.write.mock.calls.find((c: unknown[]) =>
570
+ (c[0] as string).endsWith(".hbs"),
571
+ );
572
+ expect(templateWrite).toBeDefined();
573
+ expect(templateWrite![1]).toContain("~14.0.1");
574
+ });
575
+
576
+ it("commits version files and creates annotated tag", async () => {
577
+ setupHappyPath({ version: "14.0.0" });
578
+
579
+ await runPublish({
580
+ targets: [],
581
+ noBuild: false,
582
+ dryRun: false,
583
+ options: [],
584
+ });
585
+
586
+ const commitCalls = getExecaCalls("git", "commit");
587
+ expect(commitCalls).toHaveLength(1);
588
+ expect(commitCalls[0].args).toContain("v14.0.1");
589
+
590
+ const tagCalls = getExecaCalls("git", "tag");
591
+ expect(tagCalls).toHaveLength(1);
592
+ expect(tagCalls[0].args).toContain("v14.0.1");
593
+ expect(tagCalls[0].args).toContain("-a");
594
+
595
+ expect(getExecaCalls("git", "push")).toHaveLength(2); // push + push --tags
596
+ });
597
+
598
+ it("skips git operations when .git directory absent", async () => {
599
+ setupHappyPath({ hasGit: false });
600
+
601
+ await runPublish({
602
+ targets: [],
603
+ noBuild: false,
604
+ dryRun: false,
605
+ options: [],
606
+ });
607
+
608
+ expect(getExecaCalls("git", "commit")).toHaveLength(0);
609
+ expect(getExecaCalls("git", "tag")).toHaveLength(0);
610
+ // Should still publish
611
+ expect(getExecaCalls("pnpm", "publish").length).toBeGreaterThan(0);
612
+ });
613
+
614
+ it("aborts with recovery message when git operations fail", async () => {
615
+ setupHappyPath();
616
+ mocks.execa.mockImplementation((cmd: string, args?: string[]) => {
617
+ if (cmd === "npm") return { stdout: "testuser", stderr: "", exitCode: 0 };
618
+ if (cmd === "git" && args?.[0] === "commit") {
619
+ throw new Error("git commit failed");
620
+ }
621
+ return { stdout: "", stderr: "", exitCode: 0 };
622
+ });
623
+
624
+ await runPublish({
625
+ targets: [],
626
+ noBuild: false,
627
+ dryRun: false,
628
+ options: [],
629
+ });
630
+
631
+ expect(process.exitCode).toBe(1);
632
+ expect(getExecaCalls("pnpm", "publish")).toHaveLength(0);
633
+ });
634
+ });
635
+
636
+ //#endregion
637
+
638
+ //#region Slice 4: Deployment Types + Ordering + Retry
639
+
640
+ describe("Slice 4: Deployment types + ordering + retry", () => {
641
+ it("publishes npm stable version without --tag", async () => {
642
+ setupHappyPath({ version: "14.0.0" });
643
+
644
+ await runPublish({
645
+ targets: [],
646
+ noBuild: false,
647
+ dryRun: false,
648
+ options: [],
649
+ });
650
+
651
+ const publishCalls = getExecaCalls("pnpm", "publish");
652
+ expect(publishCalls.length).toBeGreaterThan(0);
653
+ for (const call of publishCalls) {
654
+ expect(call.args).toContain("--access");
655
+ expect(call.args).toContain("public");
656
+ expect(call.args).toContain("--no-git-checks");
657
+ expect(call.args).not.toContain("--tag");
658
+ }
659
+ });
660
+
661
+ it("publishes npm prerelease version with --tag", async () => {
662
+ setupHappyPath({ version: "14.0.0-beta.1" });
663
+
664
+ await runPublish({
665
+ targets: [],
666
+ noBuild: false,
667
+ dryRun: false,
668
+ options: [],
669
+ });
670
+
671
+ const publishCalls = getExecaCalls("pnpm", "publish");
672
+ expect(publishCalls.length).toBeGreaterThan(0);
673
+ for (const call of publishCalls) {
674
+ expect(call.args).toContain("--tag");
675
+ expect(call.args).toContain("beta");
676
+ }
677
+ });
678
+
679
+ it("copies dist to local directory with env var substitution", async () => {
680
+ setupHappyPath({
681
+ version: "14.0.0",
682
+ packages: {
683
+ "pkg-a": {
684
+ target: "node",
685
+ publish: { type: "local-directory", path: "/deploy/%VER%" },
686
+ },
687
+ },
688
+ hasGit: false,
689
+ });
690
+
691
+ await runPublish({
692
+ targets: [],
693
+ noBuild: false,
694
+ dryRun: false,
695
+ options: [],
696
+ });
697
+
698
+ expect(mocks.fsx.copy).toHaveBeenCalledWith(
699
+ path.resolve(pkgPath("pkg-a"), "dist"),
700
+ "/deploy/14.0.1",
701
+ );
702
+ });
703
+
704
+ it("uploads dist to SFTP server", async () => {
705
+ setupHappyPath({
706
+ version: "14.0.0",
707
+ packages: {
708
+ "pkg-a": {
709
+ target: "node",
710
+ publish: {
711
+ type: "sftp",
712
+ host: "example.com",
713
+ port: 22,
714
+ user: "deploy",
715
+ password: "secret",
716
+ path: "/app",
717
+ },
718
+ },
719
+ },
720
+ hasGit: false,
721
+ });
722
+
723
+ mocks.storageConnect.mockImplementation(
724
+ async (
725
+ _type: string,
726
+ _opts: unknown,
727
+ cb: (storage: { uploadDir: (...args: unknown[]) => Promise<void> }) => Promise<void>,
728
+ ) => {
729
+ await cb({ uploadDir: vi.fn() });
730
+ },
731
+ );
732
+
733
+ await runPublish({
734
+ targets: [],
735
+ noBuild: false,
736
+ dryRun: false,
737
+ options: [],
738
+ });
739
+
740
+ expect(mocks.storageConnect).toHaveBeenCalledWith(
741
+ "sftp",
742
+ expect.objectContaining({ host: "example.com" }),
743
+ expect.any(Function),
744
+ );
745
+ });
746
+
747
+ it("uploads to root path when remote path is not specified", async () => {
748
+ setupHappyPath({
749
+ version: "14.0.0",
750
+ packages: {
751
+ "pkg-a": {
752
+ target: "node",
753
+ publish: {
754
+ type: "sftp",
755
+ host: "example.com",
756
+ user: "deploy",
757
+ password: "secret",
758
+ },
759
+ },
760
+ },
761
+ hasGit: false,
762
+ });
763
+
764
+ let uploadedPath: string | undefined;
765
+ mocks.storageConnect.mockImplementation(
766
+ async (
767
+ _type: string,
768
+ _opts: unknown,
769
+ cb: (storage: { uploadDir: (src: string, dest: string) => Promise<void> }) => Promise<void>,
770
+ ) => {
771
+ await cb({
772
+ uploadDir: vi.fn((_src: string, dest: string) => {
773
+ uploadedPath = dest;
774
+ return Promise.resolve();
775
+ }),
776
+ });
777
+ },
778
+ );
779
+
780
+ await runPublish({
781
+ targets: [],
782
+ noBuild: false,
783
+ dryRun: false,
784
+ options: [],
785
+ });
786
+
787
+ expect(uploadedPath).toBe("/");
788
+ });
789
+
790
+ it("deploys packages in dependency order (Level 0 before Level 1)", async () => {
791
+ setupHappyPath({
792
+ packages: {
793
+ "pkg-a": { target: "node", publish: { type: "npm" } },
794
+ "pkg-b": { target: "node", publish: { type: "npm" } },
795
+ },
796
+ packageDeps: {
797
+ "pkg-a": {},
798
+ "pkg-b": { "@simplysm/pkg-a": "~14.0.0" },
799
+ },
800
+ });
801
+
802
+ const publishOrder: string[] = [];
803
+ mocks.execa.mockImplementation(
804
+ (cmd: string, args?: string[], opts?: { cwd?: string }) => {
805
+ if (cmd === "pnpm" && args?.[0] === "publish") {
806
+ publishOrder.push(path.basename(opts?.cwd ?? ""));
807
+ }
808
+ if (cmd === "npm") return { stdout: "testuser", stderr: "", exitCode: 0 };
809
+ return { stdout: "", stderr: "", exitCode: 0 };
810
+ },
811
+ );
812
+
813
+ await runPublish({
814
+ targets: [],
815
+ noBuild: false,
816
+ dryRun: false,
817
+ options: [],
818
+ });
819
+
820
+ const aIdx = publishOrder.indexOf("pkg-a");
821
+ const bIdx = publishOrder.indexOf("pkg-b");
822
+ expect(aIdx).toBeLessThan(bIdx);
823
+ });
824
+
825
+ it("only tracks @simplysm scoped dependencies for level computation", async () => {
826
+ setupHappyPath({
827
+ packages: {
828
+ "pkg-a": { target: "node", publish: { type: "npm" } },
829
+ },
830
+ packageDeps: {
831
+ // External dep should not affect level
832
+ "pkg-a": { "lodash": "^4.0.0" },
833
+ },
834
+ });
835
+
836
+ await runPublish({
837
+ targets: [],
838
+ noBuild: false,
839
+ dryRun: false,
840
+ options: [],
841
+ });
842
+
843
+ // Should succeed without issues (lodash doesn't create level dependency)
844
+ expect(process.exitCode).toBeUndefined();
845
+ expect(getExecaCalls("pnpm", "publish")).toHaveLength(1);
846
+ });
847
+
848
+ it("retries failed publish up to 3 times then reports error", async () => {
849
+ setupHappyPath({
850
+ packages: {
851
+ "pkg-a": { target: "node", publish: { type: "npm" } },
852
+ },
853
+ });
854
+
855
+ let publishAttempts = 0;
856
+ mocks.execa.mockImplementation(
857
+ (cmd: string, args?: string[]) => {
858
+ if (cmd === "pnpm" && args?.[0] === "publish") {
859
+ publishAttempts++;
860
+ throw new Error("publish failed");
861
+ }
862
+ if (cmd === "npm") return { stdout: "testuser", stderr: "", exitCode: 0 };
863
+ return { stdout: "", stderr: "", exitCode: 0 };
864
+ },
865
+ );
866
+
867
+ await runPublish({
868
+ targets: [],
869
+ noBuild: false,
870
+ dryRun: false,
871
+ options: [],
872
+ });
873
+
874
+ expect(publishAttempts).toBe(3);
875
+ expect(process.exitCode).toBe(1);
876
+ });
877
+
878
+ it("succeeds on retry after initial failure", async () => {
879
+ setupHappyPath({
880
+ packages: {
881
+ "pkg-a": { target: "node", publish: { type: "npm" } },
882
+ },
883
+ });
884
+
885
+ let publishAttempts = 0;
886
+ mocks.execa.mockImplementation(
887
+ (cmd: string, args?: string[]) => {
888
+ if (cmd === "pnpm" && args?.[0] === "publish") {
889
+ publishAttempts++;
890
+ if (publishAttempts === 1) throw new Error("temporary failure");
891
+ return { stdout: "", stderr: "", exitCode: 0 };
892
+ }
893
+ if (cmd === "npm") return { stdout: "testuser", stderr: "", exitCode: 0 };
894
+ return { stdout: "", stderr: "", exitCode: 0 };
895
+ },
896
+ );
897
+
898
+ await runPublish({
899
+ targets: [],
900
+ noBuild: false,
901
+ dryRun: false,
902
+ options: [],
903
+ });
904
+
905
+ expect(publishAttempts).toBe(2);
906
+ expect(process.exitCode).toBeUndefined();
907
+ });
908
+ });
909
+
910
+ //#endregion
911
+
912
+ //#region Slice 5: noBuild + Dry-run + postPublish + Build Failure
913
+
914
+ describe("Slice 5: noBuild, dry-run, postPublish, build failure", () => {
915
+ it("skips version upgrade, build, and git when noBuild is true", async () => {
916
+ setupHappyPath();
917
+
918
+ await runPublish({
919
+ targets: [],
920
+ noBuild: true,
921
+ dryRun: false,
922
+ options: [],
923
+ });
924
+
925
+ // No version upgrade (fsx.write for package.json should not be called)
926
+ const versionWrites = mocks.fsx.write.mock.calls.filter((c: unknown[]) =>
927
+ (c[0] as string).endsWith("package.json"),
928
+ );
929
+ expect(versionWrites).toHaveLength(0);
930
+
931
+ // No build
932
+ expect(mocks.runBuild).not.toHaveBeenCalled();
933
+
934
+ // No git commit/tag
935
+ expect(getExecaCalls("git", "commit")).toHaveLength(0);
936
+ expect(getExecaCalls("git", "tag")).toHaveLength(0);
937
+
938
+ // But still publishes
939
+ expect(getExecaCalls("pnpm", "publish").length).toBeGreaterThan(0);
940
+ });
941
+
942
+ it("skips git uncommitted check when noBuild is true", async () => {
943
+ setupHappyPath();
944
+ mocks.execa.mockImplementation((cmd: string, args?: string[]) => {
945
+ if (cmd === "npm") return { stdout: "testuser", stderr: "", exitCode: 0 };
946
+ if (cmd === "git" && args?.[0] === "diff") {
947
+ return { stdout: "changed-file.txt", stderr: "", exitCode: 0 };
948
+ }
949
+ return { stdout: "", stderr: "", exitCode: 0 };
950
+ });
951
+
952
+ await runPublish({
953
+ targets: [],
954
+ noBuild: true,
955
+ dryRun: false,
956
+ options: [],
957
+ });
958
+
959
+ // Should not auto-commit since noBuild skips the check
960
+ const claudeCalls = mocks.execa.mock.calls.filter(
961
+ (c: unknown[]) => c[0] === "claude",
962
+ );
963
+ expect(claudeCalls).toHaveLength(0);
964
+ // Should succeed
965
+ expect(process.exitCode).toBeUndefined();
966
+ });
967
+
968
+ it("builds only publish-configured packages", async () => {
969
+ setupHappyPath({
970
+ packages: {
971
+ "pkg-a": { target: "node", publish: { type: "npm" } },
972
+ "pkg-b": { target: "node", publish: { type: "npm" } },
973
+ "pkg-c": { target: "node" }, // no publish
974
+ },
975
+ });
976
+
977
+ await runPublish({
978
+ targets: [],
979
+ noBuild: false,
980
+ dryRun: false,
981
+ options: [],
982
+ });
983
+
984
+ expect(mocks.runBuild).toHaveBeenCalledWith(
985
+ expect.objectContaining({
986
+ targets: expect.arrayContaining(["pkg-a", "pkg-b"]),
987
+ }),
988
+ );
989
+ const buildTargets = mocks.runBuild.mock.calls[0][0].targets;
990
+ expect(buildTargets).not.toContain("pkg-c");
991
+ });
992
+
993
+ it("aborts with recovery message when build fails", async () => {
994
+ setupHappyPath();
995
+ mocks.runBuild.mockRejectedValue(new Error("build failed"));
996
+
997
+ await runPublish({
998
+ targets: [],
999
+ noBuild: false,
1000
+ dryRun: false,
1001
+ options: [],
1002
+ });
1003
+
1004
+ expect(process.exitCode).toBe(1);
1005
+ expect(getExecaCalls("pnpm", "publish")).toHaveLength(0);
1006
+ });
1007
+
1008
+ it("does not modify files in dry-run version upgrade", async () => {
1009
+ setupHappyPath({ version: "14.0.0" });
1010
+
1011
+ await runPublish({
1012
+ targets: [],
1013
+ noBuild: false,
1014
+ dryRun: true,
1015
+ options: [],
1016
+ });
1017
+
1018
+ // fsx.write should not be called for version changes
1019
+ const pkgJsonWrites = mocks.fsx.write.mock.calls.filter((c: unknown[]) =>
1020
+ (c[0] as string).endsWith("package.json"),
1021
+ );
1022
+ expect(pkgJsonWrites).toHaveLength(0);
1023
+ });
1024
+
1025
+ it("adds --dry-run to pnpm publish in dry-run mode", async () => {
1026
+ setupHappyPath();
1027
+
1028
+ await runPublish({
1029
+ targets: [],
1030
+ noBuild: false,
1031
+ dryRun: true,
1032
+ options: [],
1033
+ });
1034
+
1035
+ const publishCalls = getExecaCalls("pnpm", "publish");
1036
+ expect(publishCalls.length).toBeGreaterThan(0);
1037
+ for (const call of publishCalls) {
1038
+ expect(call.args).toContain("--dry-run");
1039
+ }
1040
+ });
1041
+
1042
+ it("executes postPublish scripts with env var substitution", async () => {
1043
+ setupHappyPath({ version: "14.0.0" });
1044
+ mocks.loadSdConfig.mockResolvedValue({
1045
+ packages: {
1046
+ "pkg-a": { target: "node", publish: { type: "npm" } },
1047
+ },
1048
+ postPublish: [
1049
+ { type: "script", cmd: "echo", args: ["v%VER%"] },
1050
+ ],
1051
+ });
1052
+
1053
+ await runPublish({
1054
+ targets: [],
1055
+ noBuild: false,
1056
+ dryRun: false,
1057
+ options: [],
1058
+ });
1059
+
1060
+ const echoCalls = mocks.execa.mock.calls.filter(
1061
+ (c: unknown[]) => c[0] === "echo",
1062
+ );
1063
+ expect(echoCalls).toHaveLength(1);
1064
+ expect(echoCalls[0][1]).toEqual(["v14.0.1"]);
1065
+ });
1066
+
1067
+ it("warns but does not fail when postPublish script fails", async () => {
1068
+ setupHappyPath({ version: "14.0.0" });
1069
+ mocks.loadSdConfig.mockResolvedValue({
1070
+ packages: {
1071
+ "pkg-a": { target: "node", publish: { type: "npm" } },
1072
+ },
1073
+ postPublish: [
1074
+ { type: "script", cmd: "failing-cmd", args: [] },
1075
+ ],
1076
+ });
1077
+ mocks.execa.mockImplementation((cmd: string) => {
1078
+ if (cmd === "failing-cmd") throw new Error("script failed");
1079
+ if (cmd === "npm") return { stdout: "testuser", stderr: "", exitCode: 0 };
1080
+ return { stdout: "", stderr: "", exitCode: 0 };
1081
+ });
1082
+
1083
+ await runPublish({
1084
+ targets: [],
1085
+ noBuild: false,
1086
+ dryRun: false,
1087
+ options: [],
1088
+ });
1089
+
1090
+ // Should NOT set exit code to 1
1091
+ expect(process.exitCode).toBeUndefined();
1092
+ });
1093
+
1094
+ it("passes options to loadSdConfig", async () => {
1095
+ setupHappyPath();
1096
+
1097
+ await runPublish({
1098
+ targets: [],
1099
+ noBuild: true,
1100
+ dryRun: false,
1101
+ options: ["production"],
1102
+ });
1103
+
1104
+ expect(mocks.loadSdConfig).toHaveBeenCalledWith(
1105
+ expect.objectContaining({ options: ["production"] }),
1106
+ );
1107
+ });
1108
+
1109
+ it("throws error for unresolved environment variables", async () => {
1110
+ setupHappyPath({
1111
+ version: "14.0.0",
1112
+ packages: {
1113
+ "pkg-a": {
1114
+ target: "node",
1115
+ publish: {
1116
+ type: "local-directory",
1117
+ path: "/deploy/%UNDEFINED_VAR%",
1118
+ },
1119
+ },
1120
+ },
1121
+ hasGit: false,
1122
+ });
1123
+
1124
+ await runPublish({
1125
+ targets: [],
1126
+ noBuild: false,
1127
+ dryRun: false,
1128
+ options: [],
1129
+ });
1130
+
1131
+ // Should fail due to unresolved env var
1132
+ expect(process.exitCode).toBe(1);
1133
+ });
1134
+ });
1135
+
1136
+ //#endregion
1137
+
1138
+ describe("target validation", () => {
1139
+ it("throws error for unknown target", async () => {
1140
+ mocks.loadSdConfig.mockResolvedValue({
1141
+ packages: {
1142
+ "pkg-a": { target: "node", publish: { type: "npm" } },
1143
+ },
1144
+ });
1145
+
1146
+ await expect(
1147
+ runPublish({ targets: ["nonexistent"], noBuild: false, dryRun: false, options: [] }),
1148
+ ).rejects.toThrow("Unknown target: nonexistent");
1149
+ });
1150
+
1151
+ it("does not throw for valid target", async () => {
1152
+ setupHappyPath();
1153
+
1154
+ // Should not throw for valid target
1155
+ await runPublish({ targets: ["pkg-a"], noBuild: false, dryRun: false, options: [] });
1156
+ // If it didn't throw, validation passed
1157
+ });
1158
+ });
1159
+ });