@hubspot/cli 7.11.8-experimental.0 → 8.0.0-beta.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 (363) hide show
  1. package/api/__tests__/migrate.test.js +19 -1
  2. package/api/migrate.d.ts +1 -1
  3. package/api/migrate.js +2 -1
  4. package/bin/cli.js +12 -27
  5. package/commands/__tests__/customObject.test.js +0 -2
  6. package/commands/__tests__/doctor.test.js +0 -2
  7. package/commands/__tests__/getStarted.test.js +0 -4
  8. package/commands/__tests__/project.test.js +0 -4
  9. package/commands/__tests__/upgrade.test.js +309 -0
  10. package/commands/account/__tests__/auth.test.js +180 -0
  11. package/commands/account/__tests__/list.test.js +128 -3
  12. package/commands/account/__tests__/rename.test.js +0 -2
  13. package/commands/account/__tests__/use.test.js +138 -0
  14. package/commands/account/auth.js +1 -1
  15. package/commands/account/clean.js +4 -3
  16. package/commands/account/createOverride.js +3 -2
  17. package/commands/account/info.js +2 -2
  18. package/commands/account/list.js +4 -4
  19. package/commands/account/remove.js +3 -2
  20. package/commands/account/removeOverride.js +3 -2
  21. package/commands/account/use.js +4 -3
  22. package/commands/app/__tests__/migrate.test.js +8 -25
  23. package/commands/app/migrate.js +10 -16
  24. package/commands/app/secret/__tests__/add.test.js +112 -0
  25. package/commands/app/secret/add.js +13 -13
  26. package/commands/auth.js +8 -2
  27. package/commands/cms/__tests__/fetch.test.js +114 -15
  28. package/commands/cms/__tests__/upload.test.js +308 -0
  29. package/commands/cms/__tests__/watch.test.js +212 -0
  30. package/commands/cms/app/create.js +2 -3
  31. package/commands/cms/convertFields.js +1 -1
  32. package/commands/cms/fetch.js +3 -2
  33. package/commands/cms/function/deploy.js +2 -2
  34. package/commands/cms/function/list.js +2 -3
  35. package/commands/cms/lighthouseScore.js +19 -27
  36. package/commands/cms/module/marketplace-validate.js +0 -1
  37. package/commands/cms/theme/__tests__/preview.test.js +2 -8
  38. package/commands/cms/theme/create.js +1 -1
  39. package/commands/cms/theme/marketplace-validate.js +0 -1
  40. package/commands/cms/theme/preview.d.ts +0 -1
  41. package/commands/cms/theme/preview.js +12 -52
  42. package/commands/cms/upload.js +3 -3
  43. package/commands/cms/watch.js +3 -3
  44. package/commands/customObject.js +0 -2
  45. package/commands/doctor.js +10 -2
  46. package/commands/filemanager/__tests__/upload.test.js +161 -0
  47. package/commands/getStarted.js +13 -3
  48. package/commands/hubdb/__tests__/list.test.js +0 -9
  49. package/commands/hubdb/list.js +6 -8
  50. package/commands/init.js +8 -2
  51. package/commands/mcp/__tests__/start.test.js +113 -3
  52. package/commands/mcp/setup.js +0 -7
  53. package/commands/mcp/start.d.ts +1 -1
  54. package/commands/mcp/start.js +0 -7
  55. package/commands/project/__tests__/add.test.js +0 -2
  56. package/commands/project/__tests__/create.test.js +2 -2
  57. package/commands/project/__tests__/deploy.test.js +0 -4
  58. package/commands/project/__tests__/dev.test.js +273 -0
  59. package/commands/project/__tests__/devUnifiedFlow.test.js +2 -5
  60. package/commands/project/__tests__/installDeps.test.js +0 -2
  61. package/commands/project/__tests__/lint.test.js +0 -5
  62. package/commands/project/__tests__/logs.test.js +24 -31
  63. package/commands/project/__tests__/migrate.test.js +7 -12
  64. package/commands/project/__tests__/updateDeps.test.js +0 -2
  65. package/commands/project/__tests__/upload.test.js +191 -0
  66. package/commands/project/__tests__/validate.test.js +314 -31
  67. package/commands/project/cloneApp.d.ts +1 -7
  68. package/commands/project/cloneApp.js +1 -149
  69. package/commands/project/create.js +3 -4
  70. package/commands/project/deploy.js +18 -7
  71. package/commands/project/dev/deprecatedFlow.js +0 -2
  72. package/commands/project/dev/index.js +23 -11
  73. package/commands/project/dev/unifiedFlow.d.ts +1 -1
  74. package/commands/project/dev/unifiedFlow.js +1 -4
  75. package/commands/project/list.js +4 -4
  76. package/commands/project/listBuilds.js +2 -7
  77. package/commands/project/logs.js +19 -12
  78. package/commands/project/migrate.js +3 -3
  79. package/commands/project/profile/add.js +1 -1
  80. package/commands/project/profile/delete.js +1 -1
  81. package/commands/project/upload.d.ts +1 -1
  82. package/commands/project/upload.js +13 -4
  83. package/commands/project/validate.js +85 -13
  84. package/commands/project/watch.js +7 -7
  85. package/commands/project.js +0 -4
  86. package/commands/sandbox/__tests__/create.test.js +0 -2
  87. package/commands/secret/__tests__/addSecret.test.js +140 -7
  88. package/commands/secret/addSecret.js +3 -1
  89. package/commands/testAccount/__tests__/create.test.js +6 -1
  90. package/commands/testAccount/__tests__/importData.test.js +0 -1
  91. package/commands/testAccount/create.d.ts +1 -0
  92. package/commands/testAccount/create.js +13 -5
  93. package/commands/upgrade.d.ts +8 -0
  94. package/commands/upgrade.js +119 -0
  95. package/lang/en.d.ts +88 -10
  96. package/lang/en.js +105 -26
  97. package/lib/__tests__/buildAccount.test.js +0 -13
  98. package/lib/__tests__/cliUpgradeUtils.test.js +131 -0
  99. package/lib/__tests__/commonOpts.test.js +0 -1
  100. package/lib/__tests__/dependencyManagement.test.js +633 -13
  101. package/lib/__tests__/developerTestAccounts.test.js +0 -1
  102. package/lib/__tests__/hasFeature.test.js +0 -6
  103. package/lib/__tests__/importData.test.js +0 -1
  104. package/lib/__tests__/npmCli.test.js +84 -0
  105. package/lib/__tests__/oauth.test.js +1 -11
  106. package/lib/__tests__/process.test.js +0 -1
  107. package/lib/__tests__/sandboxSync.test.js +0 -1
  108. package/lib/__tests__/sandboxes.test.js +0 -1
  109. package/lib/__tests__/serverlessLogs.test.js +0 -1
  110. package/lib/__tests__/usageTracking.test.js +39 -6
  111. package/lib/__tests__/validation.test.js +0 -1
  112. package/lib/app/__tests__/migrate.test.js +137 -12
  113. package/lib/app/migrate.d.ts +5 -2
  114. package/lib/app/migrate.js +30 -11
  115. package/lib/app/urls.d.ts +1 -1
  116. package/lib/buildAccount.d.ts +1 -1
  117. package/lib/cliUpgradeUtils.d.ts +22 -0
  118. package/lib/cliUpgradeUtils.js +62 -0
  119. package/lib/cmsAssets/api-sample.js +2 -5
  120. package/lib/cmsAssets/function.js +1 -9
  121. package/lib/cmsAssets/module.js +1 -9
  122. package/lib/cmsAssets/template.js +1 -9
  123. package/lib/configOptions.d.ts +0 -1
  124. package/lib/configOptions.js +1 -5
  125. package/lib/constants.d.ts +6 -0
  126. package/lib/constants.js +10 -4
  127. package/lib/dependencyManagement.d.ts +9 -0
  128. package/lib/dependencyManagement.js +127 -26
  129. package/lib/developerTestAccounts.d.ts +1 -1
  130. package/lib/doctor/Diagnosis.d.ts +1 -0
  131. package/lib/doctor/Diagnosis.js +7 -0
  132. package/lib/doctor/DiagnosticInfoBuilder.d.ts +2 -1
  133. package/lib/doctor/DiagnosticInfoBuilder.js +8 -4
  134. package/lib/doctor/Doctor.d.ts +12 -0
  135. package/lib/doctor/Doctor.js +283 -33
  136. package/lib/doctor/__tests__/Diagnosis.test.js +1 -0
  137. package/lib/doctor/__tests__/Doctor.test.js +201 -51
  138. package/lib/errorHandlers/__tests__/index.test.d.ts +1 -0
  139. package/lib/errorHandlers/__tests__/index.test.js +278 -0
  140. package/lib/errorHandlers/index.d.ts +1 -0
  141. package/lib/errorHandlers/index.js +14 -2
  142. package/lib/http.js +3 -1
  143. package/lib/links.js +2 -3
  144. package/lib/mcp/__tests__/setup.test.js +69 -2
  145. package/lib/mcp/setup.d.ts +1 -0
  146. package/lib/mcp/setup.js +37 -4
  147. package/lib/middleware/__tests__/configMiddleware.test.js +1 -43
  148. package/lib/middleware/__tests__/usageTrackingMiddleware.test.d.ts +1 -0
  149. package/lib/middleware/__tests__/usageTrackingMiddleware.test.js +44 -0
  150. package/lib/middleware/__tests__/yargsChecksMiddleware.test.js +0 -5
  151. package/lib/middleware/autoUpdateMiddleware.js +58 -57
  152. package/lib/middleware/configMiddleware.d.ts +0 -3
  153. package/lib/middleware/configMiddleware.js +0 -11
  154. package/lib/middleware/fireAlarmMiddleware.js +1 -1
  155. package/lib/middleware/spinniesMiddleware.d.ts +1 -0
  156. package/lib/middleware/spinniesMiddleware.js +4 -0
  157. package/lib/middleware/usageTrackingMiddleware.d.ts +13 -0
  158. package/lib/middleware/usageTrackingMiddleware.js +16 -0
  159. package/lib/{npm.d.ts → npm/npmCli.d.ts} +8 -3
  160. package/lib/npm/npmCli.js +59 -0
  161. package/lib/npm/packageJson.d.ts +24 -0
  162. package/lib/npm/packageJson.js +102 -0
  163. package/lib/npm/workspaces.d.ts +12 -0
  164. package/lib/npm/workspaces.js +48 -0
  165. package/lib/oauth.js +1 -3
  166. package/lib/projects/__tests__/AppDevModeInterface.test.js +40 -18
  167. package/lib/projects/__tests__/DevServerManager.test.js +1 -0
  168. package/lib/projects/__tests__/DevSessionManager.test.d.ts +1 -0
  169. package/lib/projects/__tests__/DevSessionManager.test.js +250 -0
  170. package/lib/projects/__tests__/LocalDevProcess.test.js +19 -6
  171. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +0 -2
  172. package/lib/projects/__tests__/UIExtensionsDevModeInterface.test.js +0 -1
  173. package/lib/projects/__tests__/components.test.js +6 -22
  174. package/lib/projects/__tests__/deploy.test.js +0 -1
  175. package/lib/projects/__tests__/localDevProjectHelpers.test.js +3 -5
  176. package/lib/projects/__tests__/pollProjectBuildAndDeploy.test.d.ts +1 -0
  177. package/lib/projects/__tests__/pollProjectBuildAndDeploy.test.js +328 -0
  178. package/lib/projects/__tests__/projectProfiles.test.d.ts +1 -0
  179. package/lib/projects/__tests__/projectProfiles.test.js +441 -0
  180. package/lib/projects/__tests__/projects.test.js +0 -1
  181. package/lib/projects/__tests__/structure.test.js +0 -1
  182. package/lib/projects/__tests__/uieLinting.test.js +2 -11
  183. package/lib/projects/__tests__/upload.test.js +104 -3
  184. package/lib/projects/add/__tests__/legacyAddComponent.test.js +0 -2
  185. package/lib/projects/add/__tests__/v2AddComponent.test.js +2 -4
  186. package/lib/projects/add/v2AddComponent.js +2 -3
  187. package/lib/projects/components.d.ts +1 -1
  188. package/lib/projects/components.js +4 -4
  189. package/lib/projects/create/__tests__/legacy.test.js +0 -1
  190. package/lib/projects/create/__tests__/v2.test.js +0 -1
  191. package/lib/projects/create/v2.d.ts +1 -1
  192. package/lib/projects/create/v2.js +1 -1
  193. package/lib/projects/ensureProjectExists.js +0 -1
  194. package/lib/projects/localDev/AppDevModeInterface.js +9 -2
  195. package/lib/projects/localDev/DevSessionManager.d.ts +18 -0
  196. package/lib/projects/localDev/DevSessionManager.js +95 -0
  197. package/lib/projects/localDev/LocalDevLogger.d.ts +4 -0
  198. package/lib/projects/localDev/LocalDevLogger.js +18 -7
  199. package/lib/projects/localDev/LocalDevManager_DEPRECATED.d.ts +4 -3
  200. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +23 -12
  201. package/lib/projects/localDev/LocalDevProcess.d.ts +2 -1
  202. package/lib/projects/localDev/LocalDevProcess.js +18 -7
  203. package/lib/projects/localDev/LocalDevState.d.ts +3 -2
  204. package/lib/projects/localDev/helpers/account.d.ts +1 -1
  205. package/lib/projects/localDev/helpers/devSessionsApi.d.ts +9 -0
  206. package/lib/projects/localDev/helpers/devSessionsApi.js +19 -0
  207. package/lib/projects/localDev/helpers/project.d.ts +1 -1
  208. package/lib/projects/localDev/helpers/project.js +1 -2
  209. package/lib/projects/pollProjectBuildAndDeploy.js +4 -5
  210. package/lib/projects/projectProfiles.d.ts +17 -0
  211. package/lib/projects/projectProfiles.js +140 -0
  212. package/lib/projects/structure.d.ts +1 -1
  213. package/lib/projects/uieLinting.js +6 -8
  214. package/lib/projects/upload.d.ts +9 -1
  215. package/lib/projects/upload.js +11 -5
  216. package/lib/projects/urls.d.ts +1 -0
  217. package/lib/projects/urls.js +3 -3
  218. package/lib/prompts/__tests__/createDeveloperTestAccountConfigPrompt.test.js +8 -4
  219. package/lib/prompts/createDeveloperTestAccountConfigPrompt.d.ts +2 -0
  220. package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +19 -13
  221. package/lib/sandboxSync.d.ts +1 -1
  222. package/lib/sandboxes.d.ts +1 -1
  223. package/lib/serverlessLogs.js +0 -1
  224. package/lib/theme/__tests__/migrate.test.js +12 -4
  225. package/lib/theme/migrate.js +2 -3
  226. package/lib/ui/__tests__/SpinniesManager.test.js +0 -1
  227. package/lib/usageTracking.js +18 -0
  228. package/mcp-server/tools/cms/HsCreateFunctionTool.d.ts +2 -2
  229. package/mcp-server/tools/cms/HsCreateFunctionTool.js +2 -1
  230. package/mcp-server/tools/cms/HsCreateModuleTool.js +2 -1
  231. package/mcp-server/tools/cms/HsCreateTemplateTool.js +2 -1
  232. package/mcp-server/tools/cms/HsFunctionLogsTool.js +2 -2
  233. package/mcp-server/tools/cms/HsListFunctionsTool.js +2 -2
  234. package/mcp-server/tools/cms/HsListTool.js +2 -2
  235. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +4 -4
  236. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +4 -4
  237. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +4 -4
  238. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +4 -4
  239. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +4 -4
  240. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +4 -4
  241. package/mcp-server/tools/project/AddFeatureToProjectTool.js +4 -3
  242. package/mcp-server/tools/project/CreateProjectTool.js +4 -3
  243. package/mcp-server/tools/project/CreateTestAccountTool.d.ts +7 -2
  244. package/mcp-server/tools/project/CreateTestAccountTool.js +19 -9
  245. package/mcp-server/tools/project/DocFetchTool.js +2 -1
  246. package/mcp-server/tools/project/DocsSearchTool.js +2 -1
  247. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +4 -4
  248. package/mcp-server/tools/project/GetApplicationInfoTool.js +5 -5
  249. package/mcp-server/tools/project/GetConfigValuesTool.js +2 -1
  250. package/mcp-server/tools/project/UploadProjectTools.js +6 -3
  251. package/mcp-server/tools/project/ValidateProjectTool.js +2 -1
  252. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +4 -2
  253. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +4 -2
  254. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +23 -4
  255. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +3 -1
  256. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +0 -1
  257. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +3 -1
  258. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +4 -2
  259. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +8 -5
  260. package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.js +3 -1
  261. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.js +3 -1
  262. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +7 -3
  263. package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +3 -1
  264. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +17 -3
  265. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +3 -1
  266. package/mcp-server/utils/__tests__/content.test.js +0 -3
  267. package/mcp-server/utils/__tests__/feedbackTracking.test.js +0 -3
  268. package/mcp-server/utils/__tests__/project.test.js +65 -4
  269. package/mcp-server/utils/project.js +6 -2
  270. package/package.json +15 -17
  271. package/types/Cms.d.ts +1 -1
  272. package/types/Cms.js +2 -0
  273. package/types/LocalDev.d.ts +3 -2
  274. package/types/PackageJson.d.ts +10 -0
  275. package/types/PackageJson.js +1 -0
  276. package/types/ProjectComponents.d.ts +1 -1
  277. package/ui/components/BoxWithTitle.js +1 -1
  278. package/ui/components/Table.d.ts +89 -0
  279. package/ui/components/Table.js +246 -0
  280. package/ui/lib/table.d.ts +2 -0
  281. package/ui/lib/table.js +11 -0
  282. package/ui/playground/Playground.d.ts +5 -0
  283. package/ui/{views/UiSandbox.js → playground/Playground.js} +4 -4
  284. package/ui/{lib/ui-testing-utils.d.ts → playground/fixtures.d.ts} +1 -1
  285. package/ui/{lib/ui-testing-utils.js → playground/fixtures.js} +33 -1
  286. package/ui/render.d.ts +19 -0
  287. package/ui/render.js +44 -0
  288. package/commands/__tests__/create.test.js +0 -53
  289. package/commands/create.d.ts +0 -4
  290. package/commands/create.js +0 -137
  291. package/commands/customObject/__tests__/schema.test.js +0 -53
  292. package/commands/customObject/schema/create.d.ts +0 -4
  293. package/commands/customObject/schema/create.js +0 -34
  294. package/commands/customObject/schema/delete.d.ts +0 -4
  295. package/commands/customObject/schema/delete.js +0 -37
  296. package/commands/customObject/schema/fetch-all.d.ts +0 -4
  297. package/commands/customObject/schema/fetch-all.js +0 -32
  298. package/commands/customObject/schema/fetch.d.ts +0 -4
  299. package/commands/customObject/schema/fetch.js +0 -36
  300. package/commands/customObject/schema/list.d.ts +0 -4
  301. package/commands/customObject/schema/list.js +0 -26
  302. package/commands/customObject/schema/update.d.ts +0 -4
  303. package/commands/customObject/schema/update.js +0 -39
  304. package/commands/customObject/schema.d.ts +0 -3
  305. package/commands/customObject/schema.js +0 -31
  306. package/commands/fetch.d.ts +0 -4
  307. package/commands/fetch.js +0 -52
  308. package/commands/function/deploy.d.ts +0 -4
  309. package/commands/function/deploy.js +0 -31
  310. package/commands/function/list.d.ts +0 -4
  311. package/commands/function/list.js +0 -33
  312. package/commands/function/server.d.ts +0 -4
  313. package/commands/function/server.js +0 -57
  314. package/commands/function.d.ts +0 -3
  315. package/commands/function.js +0 -32
  316. package/commands/lint.d.ts +0 -4
  317. package/commands/lint.js +0 -31
  318. package/commands/list.d.ts +0 -4
  319. package/commands/list.js +0 -31
  320. package/commands/logs.d.ts +0 -4
  321. package/commands/logs.js +0 -58
  322. package/commands/module/marketplace-validate.d.ts +0 -4
  323. package/commands/module/marketplace-validate.js +0 -31
  324. package/commands/module.d.ts +0 -3
  325. package/commands/module.js +0 -23
  326. package/commands/mv.d.ts +0 -4
  327. package/commands/mv.js +0 -35
  328. package/commands/project/__tests__/migrateApp.test.js +0 -78
  329. package/commands/project/migrateApp.d.ts +0 -4
  330. package/commands/project/migrateApp.js +0 -55
  331. package/commands/remove.d.ts +0 -4
  332. package/commands/remove.js +0 -31
  333. package/commands/theme/generate-selectors.d.ts +0 -4
  334. package/commands/theme/generate-selectors.js +0 -30
  335. package/commands/theme/marketplace-validate.d.ts +0 -4
  336. package/commands/theme/marketplace-validate.js +0 -33
  337. package/commands/theme/preview.d.ts +0 -4
  338. package/commands/theme/preview.js +0 -59
  339. package/commands/theme.d.ts +0 -3
  340. package/commands/theme.js +0 -29
  341. package/commands/upload.d.ts +0 -4
  342. package/commands/upload.js +0 -62
  343. package/commands/watch.d.ts +0 -4
  344. package/commands/watch.js +0 -73
  345. package/lib/__tests__/npm.test.js +0 -57
  346. package/lib/__tests__/projectProfiles.test.js +0 -129
  347. package/lib/app/__tests__/migrate_legacy.test.js +0 -143
  348. package/lib/app/migrate_legacy.d.ts +0 -4
  349. package/lib/app/migrate_legacy.js +0 -121
  350. package/lib/npm.js +0 -33
  351. package/lib/projectProfiles.d.ts +0 -7
  352. package/lib/projectProfiles.js +0 -73
  353. package/lib/ui/table.d.ts +0 -3
  354. package/lib/ui/table.js +0 -63
  355. package/ui/index.d.ts +0 -1
  356. package/ui/index.js +0 -6
  357. package/ui/views/UiSandbox.d.ts +0 -5
  358. /package/commands/__tests__/{create.test.d.ts → upgrade.test.d.ts} +0 -0
  359. /package/commands/{customObject/__tests__/schema.test.d.ts → cms/__tests__/upload.test.d.ts} +0 -0
  360. /package/commands/{project/__tests__/migrateApp.test.d.ts → cms/__tests__/watch.test.d.ts} +0 -0
  361. /package/{lib/__tests__/npm.test.d.ts → commands/project/__tests__/dev.test.d.ts} +0 -0
  362. /package/lib/__tests__/{projectProfiles.test.d.ts → cliUpgradeUtils.test.d.ts} +0 -0
  363. /package/lib/{app/__tests__/migrate_legacy.test.d.ts → __tests__/npmCli.test.d.ts} +0 -0
@@ -5,8 +5,8 @@ import path from 'path';
5
5
  import { getProjectConfig } from '../projects/config.js';
6
6
  import SpinniesManager from '../ui/SpinniesManager.js';
7
7
  import fs from 'fs';
8
+ import { clearPackageJsonCache } from '../npm/packageJson.js';
8
9
  vi.mock('../projects/config');
9
- vi.mock('../../ui/logger.js');
10
10
  vi.mock('@hubspot/local-dev-lib/fs');
11
11
  vi.mock('fs');
12
12
  vi.mock('../ui/SpinniesManager', () => ({
@@ -45,17 +45,18 @@ describe('lib/dependencyManagement', () => {
45
45
  },
46
46
  });
47
47
  mockedFs.existsSync.mockReturnValue(true); // Default to true, override in specific tests
48
- vi.clearAllMocks();
48
+ clearPackageJsonCache();
49
49
  });
50
50
  describe('installPackages()', () => {
51
51
  it('should setup a loading spinner', async () => {
52
52
  const packages = ['package1', 'package2'];
53
+ mockedWalk.mockResolvedValue(installLocations);
53
54
  await installPackages({ packages, installLocations });
54
- expect(SpinniesManager.init).toHaveBeenCalledTimes(installLocations.length);
55
55
  expect(SpinniesManager.add).toHaveBeenCalledTimes(installLocations.length);
56
56
  expect(SpinniesManager.succeed).toHaveBeenCalledTimes(installLocations.length);
57
57
  });
58
58
  it('should install the provided packages in all the provided install locations', async () => {
59
+ mockedWalk.mockResolvedValue(installLocations);
59
60
  const packages = ['package1', 'package2'];
60
61
  await installPackages({ packages, installLocations });
61
62
  expect(execMock).toHaveBeenCalledTimes(installLocations.length);
@@ -74,6 +75,7 @@ describe('lib/dependencyManagement', () => {
74
75
  }
75
76
  });
76
77
  it('should use the provided install locations', async () => {
78
+ mockedWalk.mockResolvedValue(installLocations);
77
79
  await installPackages({ installLocations });
78
80
  expect(execMock).toHaveBeenCalledTimes(installLocations.length);
79
81
  expect(execMock).toHaveBeenCalledWith(`npm install `, {
@@ -136,21 +138,30 @@ describe('lib/dependencyManagement', () => {
136
138
  }
137
139
  });
138
140
  it('should not use --save-dev flag when dev is true but no packages are provided', async () => {
141
+ mockedWalk.mockResolvedValue(installLocations);
139
142
  await installPackages({ installLocations, dev: true });
140
143
  expect(execMock).toHaveBeenCalledTimes(installLocations.length);
141
- for (const location of installLocations) {
142
- expect(execMock).toHaveBeenCalledWith(`npm install `, {
143
- cwd: location,
144
- });
144
+ for (let i = 0; i < installLocations.length; i++) {
145
+ const installLocation = installLocations[i];
146
+ expect(execMock.mock.calls[i]).toEqual([
147
+ `npm install `,
148
+ {
149
+ cwd: installLocation,
150
+ },
151
+ ]);
145
152
  }
146
153
  });
147
154
  it('should not use --save-dev flag when dev is true but packages array is empty', async () => {
148
155
  await installPackages({ packages: [], installLocations, dev: true });
149
156
  expect(execMock).toHaveBeenCalledTimes(installLocations.length);
150
- for (const location of installLocations) {
151
- expect(execMock).toHaveBeenCalledWith(`npm install `, {
152
- cwd: location,
153
- });
157
+ for (let i = 0; i < installLocations.length; i++) {
158
+ const installLocation = installLocations[i];
159
+ expect(execMock.mock.calls[i]).toEqual([
160
+ `npm install `,
161
+ {
162
+ cwd: installLocation,
163
+ },
164
+ ]);
154
165
  }
155
166
  });
156
167
  it('should throw an error when installing the dependencies fails', async () => {
@@ -183,13 +194,14 @@ describe('lib/dependencyManagement', () => {
183
194
  });
184
195
  describe('updatePackages()', () => {
185
196
  it('should setup a loading spinner', async () => {
197
+ mockedWalk.mockResolvedValue(installLocations);
186
198
  const packages = ['package1', 'package2'];
187
199
  await updatePackages({ packages, installLocations });
188
- expect(SpinniesManager.init).toHaveBeenCalledTimes(installLocations.length);
189
200
  expect(SpinniesManager.add).toHaveBeenCalledTimes(installLocations.length);
190
201
  expect(SpinniesManager.succeed).toHaveBeenCalledTimes(installLocations.length);
191
202
  });
192
203
  it('should update the provided packages in all the provided install locations', async () => {
204
+ mockedWalk.mockResolvedValue(installLocations);
193
205
  const packages = ['package1', 'package2'];
194
206
  await updatePackages({ packages, installLocations });
195
207
  expect(execMock).toHaveBeenCalledTimes(installLocations.length);
@@ -341,7 +353,6 @@ describe('lib/dependencyManagement', () => {
341
353
  });
342
354
  }
343
355
  beforeEach(() => {
344
- vi.clearAllMocks();
345
356
  readFileSyncSpy.mockReset();
346
357
  existsSyncSpy.mockReset();
347
358
  });
@@ -444,4 +455,613 @@ describe('lib/dependencyManagement', () => {
444
455
  expect(result).toBe(true);
445
456
  });
446
457
  });
458
+ describe('npm workspaces support', () => {
459
+ const workspaceRoot = path.join(projectDir, 'workspace');
460
+ const pkg1Dir = path.join(workspaceRoot, 'packages', 'pkg-a');
461
+ const pkg2Dir = path.join(workspaceRoot, 'packages', 'pkg-b');
462
+ const standaloneDir = path.join(projectDir, 'standalone');
463
+ function mockWorkspaceSetup(workspaceRootPath, workspacePatterns, packageDirs) {
464
+ const allPackageJsons = [
465
+ path.join(workspaceRootPath, 'package.json'),
466
+ ...packageDirs.map(d => path.join(d, 'package.json')),
467
+ ];
468
+ mockedWalk.mockResolvedValue(allPackageJsons);
469
+ mockedFs.readFileSync.mockImplementation(filePath => {
470
+ const pathStr = filePath.toString();
471
+ if (pathStr === path.join(workspaceRootPath, 'package.json')) {
472
+ return JSON.stringify({
473
+ name: 'workspace-root',
474
+ workspaces: workspacePatterns,
475
+ });
476
+ }
477
+ // Default package.json for workspace members
478
+ return JSON.stringify({
479
+ name: path.basename(path.dirname(pathStr)),
480
+ });
481
+ });
482
+ }
483
+ describe('installPackages()', () => {
484
+ describe('workspace detection', () => {
485
+ it('should return workspace root when directory matches workspace pattern', async () => {
486
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
487
+ await installPackages({
488
+ packages: ['lodash'],
489
+ installLocations: [pkg1Dir],
490
+ });
491
+ expect(execMock).toHaveBeenCalledTimes(1);
492
+ expect(execMock).toHaveBeenCalledWith(`npm install --workspace=packages/pkg-a lodash`, { cwd: workspaceRoot });
493
+ });
494
+ it('should handle packages without workspaces field as non-workspace', async () => {
495
+ mockedWalk.mockResolvedValue([
496
+ path.join(standaloneDir, 'package.json'),
497
+ ]);
498
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'standalone' }));
499
+ await installPackages({
500
+ packages: ['react'],
501
+ installLocations: [standaloneDir],
502
+ });
503
+ expect(execMock).toHaveBeenCalledTimes(1);
504
+ expect(execMock).toHaveBeenCalledWith('npm install react', {
505
+ cwd: standaloneDir,
506
+ });
507
+ });
508
+ it('should handle empty workspaces array as non-workspace', async () => {
509
+ mockedWalk.mockResolvedValue([
510
+ path.join(workspaceRoot, 'package.json'),
511
+ ]);
512
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({
513
+ name: 'workspace-root',
514
+ workspaces: [],
515
+ }));
516
+ await installPackages({
517
+ packages: ['test'],
518
+ installLocations: [workspaceRoot],
519
+ });
520
+ expect(execMock).toHaveBeenCalledTimes(1);
521
+ expect(execMock).toHaveBeenCalledWith('npm install test', {
522
+ cwd: workspaceRoot,
523
+ });
524
+ });
525
+ it('should match nested glob patterns like packages/**/*', async () => {
526
+ const nestedDir = path.join(workspaceRoot, 'packages', 'frontend', 'ui');
527
+ mockWorkspaceSetup(workspaceRoot, ['packages/**/*'], [nestedDir]);
528
+ await installPackages({
529
+ packages: ['axios'],
530
+ installLocations: [nestedDir],
531
+ });
532
+ expect(execMock).toHaveBeenCalledTimes(1);
533
+ expect(execMock).toHaveBeenCalledWith(`npm install --workspace=packages/frontend/ui axios`, { cwd: workspaceRoot });
534
+ });
535
+ it('should match against multiple workspace patterns', async () => {
536
+ const appsDir = path.join(workspaceRoot, 'apps', 'web');
537
+ mockWorkspaceSetup(workspaceRoot, ['packages/*', 'apps/*'], [pkg1Dir, appsDir]);
538
+ await installPackages({
539
+ packages: ['typescript'],
540
+ installLocations: [pkg1Dir, appsDir],
541
+ });
542
+ expect(execMock).toHaveBeenCalledTimes(2);
543
+ expect(execMock).toHaveBeenCalledWith(`npm install --workspace=packages/pkg-a typescript`, { cwd: workspaceRoot });
544
+ expect(execMock).toHaveBeenCalledWith(`npm install --workspace=apps/web typescript`, { cwd: workspaceRoot });
545
+ });
546
+ it('should return null when directory does not match workspace patterns', async () => {
547
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], []);
548
+ mockedWalk.mockResolvedValue([
549
+ path.join(workspaceRoot, 'package.json'),
550
+ path.join(standaloneDir, 'package.json'),
551
+ ]);
552
+ mockedFs.readFileSync.mockImplementation(filePath => {
553
+ const pathStr = filePath.toString();
554
+ if (pathStr === path.join(workspaceRoot, 'package.json')) {
555
+ return JSON.stringify({
556
+ name: 'workspace',
557
+ workspaces: ['packages/*'],
558
+ });
559
+ }
560
+ return JSON.stringify({ name: 'standalone' });
561
+ });
562
+ await installPackages({
563
+ packages: ['test'],
564
+ installLocations: [standaloneDir],
565
+ });
566
+ expect(execMock).toHaveBeenCalledTimes(1);
567
+ expect(execMock).toHaveBeenCalledWith('npm install test', {
568
+ cwd: standaloneDir,
569
+ });
570
+ });
571
+ });
572
+ describe('installation behavior without specific packages', () => {
573
+ it('should install at workspace root when no packages and directory is in workspace', async () => {
574
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir, pkg2Dir]);
575
+ await installPackages({
576
+ installLocations: [pkg1Dir, pkg2Dir],
577
+ });
578
+ // Should install once at workspace root
579
+ expect(execMock).toHaveBeenCalledTimes(1);
580
+ expect(execMock).toHaveBeenCalledWith('npm install ', {
581
+ cwd: workspaceRoot,
582
+ });
583
+ });
584
+ it('should install in each directory when not in workspace', async () => {
585
+ const dir1 = path.join(projectDir, 'dir1');
586
+ const dir2 = path.join(projectDir, 'dir2');
587
+ mockedWalk.mockResolvedValue([
588
+ path.join(dir1, 'package.json'),
589
+ path.join(dir2, 'package.json'),
590
+ ]);
591
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'standalone' }));
592
+ await installPackages({
593
+ installLocations: [dir1, dir2],
594
+ });
595
+ expect(execMock).toHaveBeenCalledTimes(2);
596
+ expect(execMock).toHaveBeenCalledWith('npm install ', { cwd: dir1 });
597
+ expect(execMock).toHaveBeenCalledWith('npm install ', { cwd: dir2 });
598
+ });
599
+ it('should install at workspace roots and non-workspace directories', async () => {
600
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
601
+ mockedWalk.mockResolvedValue([
602
+ path.join(workspaceRoot, 'package.json'),
603
+ path.join(pkg1Dir, 'package.json'),
604
+ path.join(standaloneDir, 'package.json'),
605
+ ]);
606
+ mockedFs.readFileSync.mockImplementation(filePath => {
607
+ const pathStr = filePath.toString();
608
+ if (pathStr === path.join(workspaceRoot, 'package.json')) {
609
+ return JSON.stringify({
610
+ name: 'workspace',
611
+ workspaces: ['packages/*'],
612
+ });
613
+ }
614
+ return JSON.stringify({ name: 'pkg' });
615
+ });
616
+ await installPackages({
617
+ installLocations: [pkg1Dir, standaloneDir],
618
+ });
619
+ expect(execMock).toHaveBeenCalledTimes(2);
620
+ expect(execMock).toHaveBeenCalledWith('npm install ', {
621
+ cwd: workspaceRoot,
622
+ });
623
+ expect(execMock).toHaveBeenCalledWith('npm install ', {
624
+ cwd: standaloneDir,
625
+ });
626
+ });
627
+ });
628
+ describe('installation behavior with specific packages', () => {
629
+ it('should use --workspace flag when installing packages in workspace', async () => {
630
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
631
+ await installPackages({
632
+ packages: ['lodash', 'axios'],
633
+ installLocations: [pkg1Dir],
634
+ });
635
+ expect(execMock).toHaveBeenCalledTimes(1);
636
+ expect(execMock).toHaveBeenCalledWith(`npm install --workspace=packages/pkg-a lodash axios`, { cwd: workspaceRoot });
637
+ });
638
+ it('should install packages normally in non-workspace directories', async () => {
639
+ mockedWalk.mockResolvedValue([
640
+ path.join(standaloneDir, 'package.json'),
641
+ ]);
642
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'standalone' }));
643
+ await installPackages({
644
+ packages: ['react', 'react-dom'],
645
+ installLocations: [standaloneDir],
646
+ });
647
+ expect(execMock).toHaveBeenCalledTimes(1);
648
+ expect(execMock).toHaveBeenCalledWith('npm install react react-dom', {
649
+ cwd: standaloneDir,
650
+ });
651
+ });
652
+ it('should handle multiple workspace packages with separate commands', async () => {
653
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir, pkg2Dir]);
654
+ await installPackages({
655
+ packages: ['typescript'],
656
+ installLocations: [pkg1Dir, pkg2Dir],
657
+ });
658
+ expect(execMock).toHaveBeenCalledTimes(2);
659
+ expect(execMock).toHaveBeenCalledWith(`npm install --workspace=packages/pkg-a typescript`, { cwd: workspaceRoot });
660
+ expect(execMock).toHaveBeenCalledWith(`npm install --workspace=packages/pkg-b typescript`, { cwd: workspaceRoot });
661
+ });
662
+ it('should handle mixed workspace and non-workspace installations', async () => {
663
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
664
+ mockedWalk.mockResolvedValue([
665
+ path.join(workspaceRoot, 'package.json'),
666
+ path.join(pkg1Dir, 'package.json'),
667
+ path.join(standaloneDir, 'package.json'),
668
+ ]);
669
+ mockedFs.readFileSync.mockImplementation(filePath => {
670
+ const pathStr = filePath.toString();
671
+ if (pathStr === path.join(workspaceRoot, 'package.json')) {
672
+ return JSON.stringify({
673
+ name: 'workspace',
674
+ workspaces: ['packages/*'],
675
+ });
676
+ }
677
+ return JSON.stringify({ name: 'pkg' });
678
+ });
679
+ await installPackages({
680
+ packages: ['lodash'],
681
+ installLocations: [pkg1Dir, standaloneDir],
682
+ });
683
+ expect(execMock).toHaveBeenCalledTimes(2);
684
+ expect(execMock).toHaveBeenCalledWith(`npm install --workspace=packages/pkg-a lodash`, { cwd: workspaceRoot });
685
+ expect(execMock).toHaveBeenCalledWith('npm install lodash', {
686
+ cwd: standaloneDir,
687
+ });
688
+ });
689
+ });
690
+ describe('command construction', () => {
691
+ it('should combine --save-dev and --workspace flags correctly', async () => {
692
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
693
+ await installPackages({
694
+ packages: ['eslint', 'prettier'],
695
+ installLocations: [pkg1Dir],
696
+ dev: true,
697
+ });
698
+ expect(execMock).toHaveBeenCalledTimes(1);
699
+ expect(execMock).toHaveBeenCalledWith(`npm install --save-dev --workspace=packages/pkg-a eslint prettier`, { cwd: workspaceRoot });
700
+ });
701
+ it('should not use --save-dev flag when dev is true but no packages provided', async () => {
702
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
703
+ await installPackages({
704
+ installLocations: [pkg1Dir],
705
+ dev: true,
706
+ });
707
+ expect(execMock).toHaveBeenCalledTimes(1);
708
+ expect(execMock).toHaveBeenCalledWith('npm install ', {
709
+ cwd: workspaceRoot,
710
+ });
711
+ });
712
+ it('should execute npm commands in workspace root directory for workspace packages', async () => {
713
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
714
+ await installPackages({
715
+ packages: ['test'],
716
+ installLocations: [pkg1Dir],
717
+ });
718
+ expect(execMock).toHaveBeenCalledWith(expect.any(String), {
719
+ cwd: workspaceRoot,
720
+ });
721
+ });
722
+ it('should execute npm commands in package directory for non-workspace packages', async () => {
723
+ mockedWalk.mockResolvedValue([
724
+ path.join(standaloneDir, 'package.json'),
725
+ ]);
726
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'standalone' }));
727
+ await installPackages({
728
+ packages: ['test'],
729
+ installLocations: [standaloneDir],
730
+ });
731
+ expect(execMock).toHaveBeenCalledWith(expect.any(String), {
732
+ cwd: standaloneDir,
733
+ });
734
+ });
735
+ });
736
+ describe('edge cases', () => {
737
+ it('should correctly calculate relative paths for deeply nested packages', async () => {
738
+ const deeplyNestedDir = path.join(workspaceRoot, 'packages', 'frontend', 'components', 'ui');
739
+ mockWorkspaceSetup(workspaceRoot, ['packages/**/*'], [deeplyNestedDir]);
740
+ await installPackages({
741
+ packages: ['test'],
742
+ installLocations: [deeplyNestedDir],
743
+ });
744
+ expect(execMock).toHaveBeenCalledWith(`npm install --workspace=packages/frontend/components/ui test`, { cwd: workspaceRoot });
745
+ });
746
+ it('should handle invalid package.json gracefully', async () => {
747
+ mockedWalk.mockResolvedValue([
748
+ path.join(workspaceRoot, 'package.json'),
749
+ ]);
750
+ mockedFs.readFileSync.mockReturnValue('invalid json{');
751
+ await installPackages({
752
+ packages: ['test'],
753
+ installLocations: [workspaceRoot],
754
+ });
755
+ // Should treat as non-workspace since parsing fails
756
+ expect(execMock).toHaveBeenCalledWith('npm install test', {
757
+ cwd: workspaceRoot,
758
+ });
759
+ });
760
+ it('should show spinner for package directory not workspace root', async () => {
761
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
762
+ await installPackages({
763
+ packages: ['lodash'],
764
+ installLocations: [pkg1Dir],
765
+ });
766
+ expect(SpinniesManager.add).toHaveBeenCalledWith(`installingDependencies-${pkg1Dir}`, expect.objectContaining({
767
+ text: expect.stringContaining(path.relative(process.cwd(), pkg1Dir)),
768
+ }));
769
+ expect(SpinniesManager.succeed).toHaveBeenCalledWith(`installingDependencies-${pkg1Dir}`, expect.any(Object));
770
+ });
771
+ it('should report errors with correct directory even when using workspace root', async () => {
772
+ execMock = vi.fn().mockImplementation(() => {
773
+ throw new Error('Installation failed');
774
+ });
775
+ util.promisify = mockedPromisify(execMock);
776
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
777
+ mockedFs.existsSync.mockReturnValue(true);
778
+ await expect(installPackages({
779
+ packages: ['test'],
780
+ installLocations: [pkg1Dir],
781
+ })).rejects.toThrowError();
782
+ expect(SpinniesManager.fail).toHaveBeenCalledWith(`installingDependencies-${pkg1Dir}`, expect.any(Object));
783
+ });
784
+ });
785
+ });
786
+ describe('updatePackages()', () => {
787
+ describe('workspace detection', () => {
788
+ it('should return workspace root when directory matches workspace pattern', async () => {
789
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
790
+ await updatePackages({
791
+ packages: ['lodash'],
792
+ installLocations: [pkg1Dir],
793
+ });
794
+ expect(execMock).toHaveBeenCalledTimes(1);
795
+ expect(execMock).toHaveBeenCalledWith(`npm update --workspace=packages/pkg-a lodash`, { cwd: workspaceRoot });
796
+ });
797
+ it('should handle packages without workspaces field as non-workspace', async () => {
798
+ mockedWalk.mockResolvedValue([
799
+ path.join(standaloneDir, 'package.json'),
800
+ ]);
801
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'standalone' }));
802
+ await updatePackages({
803
+ packages: ['react'],
804
+ installLocations: [standaloneDir],
805
+ });
806
+ expect(execMock).toHaveBeenCalledTimes(1);
807
+ expect(execMock).toHaveBeenCalledWith('npm update react', {
808
+ cwd: standaloneDir,
809
+ });
810
+ });
811
+ it('should handle empty workspaces array as non-workspace', async () => {
812
+ mockedWalk.mockResolvedValue([
813
+ path.join(workspaceRoot, 'package.json'),
814
+ ]);
815
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({
816
+ name: 'workspace-root',
817
+ workspaces: [],
818
+ }));
819
+ await updatePackages({
820
+ packages: ['test'],
821
+ installLocations: [workspaceRoot],
822
+ });
823
+ expect(execMock).toHaveBeenCalledTimes(1);
824
+ expect(execMock).toHaveBeenCalledWith('npm update test', {
825
+ cwd: workspaceRoot,
826
+ });
827
+ });
828
+ it('should match nested glob patterns like packages/**/*', async () => {
829
+ const nestedDir = path.join(workspaceRoot, 'packages', 'frontend', 'ui');
830
+ mockWorkspaceSetup(workspaceRoot, ['packages/**/*'], [nestedDir]);
831
+ await updatePackages({
832
+ packages: ['axios'],
833
+ installLocations: [nestedDir],
834
+ });
835
+ expect(execMock).toHaveBeenCalledTimes(1);
836
+ expect(execMock).toHaveBeenCalledWith(`npm update --workspace=packages/frontend/ui axios`, { cwd: workspaceRoot });
837
+ });
838
+ it('should match against multiple workspace patterns', async () => {
839
+ const appsDir = path.join(workspaceRoot, 'apps', 'web');
840
+ mockWorkspaceSetup(workspaceRoot, ['packages/*', 'apps/*'], [pkg1Dir, appsDir]);
841
+ await updatePackages({
842
+ packages: ['typescript'],
843
+ installLocations: [pkg1Dir, appsDir],
844
+ });
845
+ expect(execMock).toHaveBeenCalledTimes(2);
846
+ expect(execMock).toHaveBeenCalledWith(`npm update --workspace=packages/pkg-a typescript`, { cwd: workspaceRoot });
847
+ expect(execMock).toHaveBeenCalledWith(`npm update --workspace=apps/web typescript`, { cwd: workspaceRoot });
848
+ });
849
+ it('should return null when directory does not match workspace patterns', async () => {
850
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], []);
851
+ mockedWalk.mockResolvedValue([
852
+ path.join(workspaceRoot, 'package.json'),
853
+ path.join(standaloneDir, 'package.json'),
854
+ ]);
855
+ mockedFs.readFileSync.mockImplementation(filePath => {
856
+ const pathStr = filePath.toString();
857
+ if (pathStr === path.join(workspaceRoot, 'package.json')) {
858
+ return JSON.stringify({
859
+ name: 'workspace',
860
+ workspaces: ['packages/*'],
861
+ });
862
+ }
863
+ return JSON.stringify({ name: 'standalone' });
864
+ });
865
+ await updatePackages({
866
+ packages: ['test'],
867
+ installLocations: [standaloneDir],
868
+ });
869
+ expect(execMock).toHaveBeenCalledTimes(1);
870
+ expect(execMock).toHaveBeenCalledWith('npm update test', {
871
+ cwd: standaloneDir,
872
+ });
873
+ });
874
+ });
875
+ describe('update behavior without specific packages', () => {
876
+ it('should update at workspace root when no packages and directory is in workspace', async () => {
877
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir, pkg2Dir]);
878
+ await updatePackages({
879
+ installLocations: [pkg1Dir, pkg2Dir],
880
+ });
881
+ expect(execMock).toHaveBeenCalledTimes(1);
882
+ expect(execMock).toHaveBeenCalledWith('npm update ', {
883
+ cwd: workspaceRoot,
884
+ });
885
+ });
886
+ it('should update in each directory when not in workspace', async () => {
887
+ const dir1 = path.join(projectDir, 'dir1');
888
+ const dir2 = path.join(projectDir, 'dir2');
889
+ mockedWalk.mockResolvedValue([
890
+ path.join(dir1, 'package.json'),
891
+ path.join(dir2, 'package.json'),
892
+ ]);
893
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'standalone' }));
894
+ await updatePackages({
895
+ installLocations: [dir1, dir2],
896
+ });
897
+ expect(execMock).toHaveBeenCalledTimes(2);
898
+ expect(execMock).toHaveBeenCalledWith('npm update ', { cwd: dir1 });
899
+ expect(execMock).toHaveBeenCalledWith('npm update ', { cwd: dir2 });
900
+ });
901
+ it('should update at workspace roots and non-workspace directories', async () => {
902
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
903
+ mockedWalk.mockResolvedValue([
904
+ path.join(workspaceRoot, 'package.json'),
905
+ path.join(pkg1Dir, 'package.json'),
906
+ path.join(standaloneDir, 'package.json'),
907
+ ]);
908
+ mockedFs.readFileSync.mockImplementation(filePath => {
909
+ const pathStr = filePath.toString();
910
+ if (pathStr === path.join(workspaceRoot, 'package.json')) {
911
+ return JSON.stringify({
912
+ name: 'workspace',
913
+ workspaces: ['packages/*'],
914
+ });
915
+ }
916
+ return JSON.stringify({ name: 'pkg' });
917
+ });
918
+ await updatePackages({
919
+ installLocations: [pkg1Dir, standaloneDir],
920
+ });
921
+ expect(execMock).toHaveBeenCalledTimes(2);
922
+ expect(execMock).toHaveBeenCalledWith('npm update ', {
923
+ cwd: workspaceRoot,
924
+ });
925
+ expect(execMock).toHaveBeenCalledWith('npm update ', {
926
+ cwd: standaloneDir,
927
+ });
928
+ });
929
+ });
930
+ describe('update behavior with specific packages', () => {
931
+ it('should use --workspace flag when updating packages in workspace', async () => {
932
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
933
+ await updatePackages({
934
+ packages: ['lodash', 'axios'],
935
+ installLocations: [pkg1Dir],
936
+ });
937
+ expect(execMock).toHaveBeenCalledTimes(1);
938
+ expect(execMock).toHaveBeenCalledWith(`npm update --workspace=packages/pkg-a lodash axios`, { cwd: workspaceRoot });
939
+ });
940
+ it('should update packages normally in non-workspace directories', async () => {
941
+ mockedWalk.mockResolvedValue([
942
+ path.join(standaloneDir, 'package.json'),
943
+ ]);
944
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'standalone' }));
945
+ await updatePackages({
946
+ packages: ['react', 'react-dom'],
947
+ installLocations: [standaloneDir],
948
+ });
949
+ expect(execMock).toHaveBeenCalledTimes(1);
950
+ expect(execMock).toHaveBeenCalledWith('npm update react react-dom', {
951
+ cwd: standaloneDir,
952
+ });
953
+ });
954
+ it('should handle multiple workspace packages with separate commands', async () => {
955
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir, pkg2Dir]);
956
+ await updatePackages({
957
+ packages: ['typescript'],
958
+ installLocations: [pkg1Dir, pkg2Dir],
959
+ });
960
+ expect(execMock).toHaveBeenCalledTimes(2);
961
+ expect(execMock).toHaveBeenCalledWith(`npm update --workspace=packages/pkg-a typescript`, { cwd: workspaceRoot });
962
+ expect(execMock).toHaveBeenCalledWith(`npm update --workspace=packages/pkg-b typescript`, { cwd: workspaceRoot });
963
+ });
964
+ it('should handle mixed workspace and non-workspace updates', async () => {
965
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
966
+ mockedWalk.mockResolvedValue([
967
+ path.join(workspaceRoot, 'package.json'),
968
+ path.join(pkg1Dir, 'package.json'),
969
+ path.join(standaloneDir, 'package.json'),
970
+ ]);
971
+ mockedFs.readFileSync.mockImplementation(filePath => {
972
+ const pathStr = filePath.toString();
973
+ if (pathStr === path.join(workspaceRoot, 'package.json')) {
974
+ return JSON.stringify({
975
+ name: 'workspace',
976
+ workspaces: ['packages/*'],
977
+ });
978
+ }
979
+ return JSON.stringify({ name: 'pkg' });
980
+ });
981
+ await updatePackages({
982
+ packages: ['lodash'],
983
+ installLocations: [pkg1Dir, standaloneDir],
984
+ });
985
+ expect(execMock).toHaveBeenCalledTimes(2);
986
+ expect(execMock).toHaveBeenCalledWith(`npm update --workspace=packages/pkg-a lodash`, { cwd: workspaceRoot });
987
+ expect(execMock).toHaveBeenCalledWith('npm update lodash', {
988
+ cwd: standaloneDir,
989
+ });
990
+ });
991
+ });
992
+ describe('command construction', () => {
993
+ it('should execute npm commands in workspace root directory for workspace packages', async () => {
994
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
995
+ await updatePackages({
996
+ packages: ['test'],
997
+ installLocations: [pkg1Dir],
998
+ });
999
+ expect(execMock).toHaveBeenCalledWith(expect.any(String), {
1000
+ cwd: workspaceRoot,
1001
+ });
1002
+ });
1003
+ it('should execute npm commands in package directory for non-workspace packages', async () => {
1004
+ mockedWalk.mockResolvedValue([
1005
+ path.join(standaloneDir, 'package.json'),
1006
+ ]);
1007
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'standalone' }));
1008
+ await updatePackages({
1009
+ packages: ['test'],
1010
+ installLocations: [standaloneDir],
1011
+ });
1012
+ expect(execMock).toHaveBeenCalledWith(expect.any(String), {
1013
+ cwd: standaloneDir,
1014
+ });
1015
+ });
1016
+ });
1017
+ describe('edge cases', () => {
1018
+ it('should correctly calculate relative paths for deeply nested packages', async () => {
1019
+ const deeplyNestedDir = path.join(workspaceRoot, 'packages', 'frontend', 'components', 'ui');
1020
+ mockWorkspaceSetup(workspaceRoot, ['packages/**/*'], [deeplyNestedDir]);
1021
+ await updatePackages({
1022
+ packages: ['test'],
1023
+ installLocations: [deeplyNestedDir],
1024
+ });
1025
+ expect(execMock).toHaveBeenCalledWith(`npm update --workspace=packages/frontend/components/ui test`, { cwd: workspaceRoot });
1026
+ });
1027
+ it('should handle invalid package.json gracefully', async () => {
1028
+ mockedWalk.mockResolvedValue([
1029
+ path.join(workspaceRoot, 'package.json'),
1030
+ ]);
1031
+ mockedFs.readFileSync.mockReturnValue('invalid json{');
1032
+ await updatePackages({
1033
+ packages: ['test'],
1034
+ installLocations: [workspaceRoot],
1035
+ });
1036
+ expect(execMock).toHaveBeenCalledWith('npm update test', {
1037
+ cwd: workspaceRoot,
1038
+ });
1039
+ });
1040
+ it('should show spinner for package directory not workspace root', async () => {
1041
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
1042
+ await updatePackages({
1043
+ packages: ['lodash'],
1044
+ installLocations: [pkg1Dir],
1045
+ });
1046
+ expect(SpinniesManager.add).toHaveBeenCalledWith(`updatingDependencies-${pkg1Dir}`, expect.objectContaining({
1047
+ text: expect.stringContaining(path.relative(process.cwd(), pkg1Dir)),
1048
+ }));
1049
+ expect(SpinniesManager.succeed).toHaveBeenCalledWith(`updatingDependencies-${pkg1Dir}`, expect.any(Object));
1050
+ });
1051
+ it('should report errors with correct directory even when using workspace root', async () => {
1052
+ execMock = vi.fn().mockImplementation(() => {
1053
+ throw new Error('Update failed');
1054
+ });
1055
+ util.promisify = mockedPromisify(execMock);
1056
+ mockWorkspaceSetup(workspaceRoot, ['packages/*'], [pkg1Dir]);
1057
+ mockedFs.existsSync.mockReturnValue(true);
1058
+ await expect(updatePackages({
1059
+ packages: ['test'],
1060
+ installLocations: [pkg1Dir],
1061
+ })).rejects.toThrowError();
1062
+ expect(SpinniesManager.fail).toHaveBeenCalledWith(`updatingDependencies-${pkg1Dir}`, expect.any(Object));
1063
+ });
1064
+ });
1065
+ });
1066
+ });
447
1067
  });