@hubspot/cli 7.7.35-experimental.0 → 7.8.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 (320) hide show
  1. package/bin/cli.js +31 -25
  2. package/commands/__tests__/auth.test.js +5 -0
  3. package/commands/__tests__/doctor.test.js +16 -16
  4. package/commands/__tests__/getStarted.test.js +2 -2
  5. package/commands/__tests__/mcp.test.js +1 -1
  6. package/commands/__tests__/project.test.js +2 -3
  7. package/commands/account/auth.js +1 -0
  8. package/commands/account/clean.js +18 -27
  9. package/commands/account/createOverride.js +13 -31
  10. package/commands/account/info.js +20 -31
  11. package/commands/account/list.js +16 -22
  12. package/commands/account/remove.js +12 -20
  13. package/commands/account/removeOverride.js +11 -21
  14. package/commands/account/rename.js +6 -9
  15. package/commands/account/use.js +12 -26
  16. package/commands/account.js +2 -2
  17. package/commands/app/__tests__/migrate.test.js +5 -6
  18. package/commands/app/migrate.js +13 -19
  19. package/commands/app/secret/add.js +2 -1
  20. package/commands/app/secret/delete.js +2 -1
  21. package/commands/app/secret/list.js +2 -1
  22. package/commands/app/secret/update.js +2 -1
  23. package/commands/app/secret.js +2 -1
  24. package/commands/app.js +2 -2
  25. package/commands/auth.d.ts +1 -0
  26. package/commands/auth.js +17 -7
  27. package/commands/cms/convertFields.js +7 -9
  28. package/commands/cms/getReactModule.js +9 -14
  29. package/commands/cms/lighthouseScore.js +33 -36
  30. package/commands/cms.js +2 -2
  31. package/commands/completion.js +3 -3
  32. package/commands/config/set.d.ts +1 -1
  33. package/commands/config/set.js +64 -37
  34. package/commands/config.js +2 -2
  35. package/commands/create.js +2 -2
  36. package/commands/customObject/create.js +10 -12
  37. package/commands/customObject/schema/create.js +9 -11
  38. package/commands/customObject/schema/delete.js +16 -16
  39. package/commands/customObject/schema/fetch-all.js +12 -11
  40. package/commands/customObject/schema/fetch.js +15 -15
  41. package/commands/customObject/schema/list.js +4 -4
  42. package/commands/customObject/schema/update.js +13 -13
  43. package/commands/customObject/schema.js +2 -2
  44. package/commands/customObject.js +6 -7
  45. package/commands/doctor.js +8 -11
  46. package/commands/feedback.js +8 -13
  47. package/commands/fetch.js +8 -8
  48. package/commands/filemanager/fetch.js +7 -7
  49. package/commands/filemanager/upload.js +15 -34
  50. package/commands/filemanager.js +2 -2
  51. package/commands/function/deploy.js +11 -29
  52. package/commands/function/list.js +8 -8
  53. package/commands/function/server.js +9 -11
  54. package/commands/function.d.ts +1 -1
  55. package/commands/function.js +2 -2
  56. package/commands/getStarted.d.ts +0 -2
  57. package/commands/getStarted.js +4 -4
  58. package/commands/hubdb/clear.js +7 -15
  59. package/commands/hubdb/create.js +9 -15
  60. package/commands/hubdb/delete.js +8 -15
  61. package/commands/hubdb/fetch.js +6 -9
  62. package/commands/hubdb.d.ts +1 -1
  63. package/commands/hubdb.js +2 -2
  64. package/commands/init.js +2 -3
  65. package/commands/lint.js +16 -16
  66. package/commands/list.js +8 -14
  67. package/commands/logs.js +14 -20
  68. package/commands/mcp/__tests__/setup.test.js +2 -2
  69. package/commands/mcp/setup.js +11 -2
  70. package/commands/mcp.js +3 -3
  71. package/commands/mv.js +6 -17
  72. package/commands/open.js +5 -5
  73. package/commands/project/__tests__/add.test.js +15 -13
  74. package/commands/project/__tests__/create.test.js +6 -6
  75. package/commands/project/__tests__/deploy.test.js +3 -7
  76. package/commands/project/__tests__/devUnifiedFlow.test.js +2 -4
  77. package/commands/project/__tests__/installDeps.test.js +8 -8
  78. package/commands/project/__tests__/list.test.js +31 -0
  79. package/commands/project/__tests__/logs.test.js +1 -4
  80. package/commands/project/__tests__/migrate.test.js +7 -7
  81. package/commands/project/__tests__/migrateApp.test.js +3 -7
  82. package/commands/project/__tests__/profile.test.js +1 -1
  83. package/commands/project/__tests__/validate.test.js +98 -0
  84. package/commands/project/add.d.ts +2 -2
  85. package/commands/project/add.js +7 -10
  86. package/commands/project/cloneApp.js +14 -19
  87. package/commands/project/create.js +3 -10
  88. package/commands/project/deploy.js +5 -5
  89. package/commands/project/dev/deprecatedFlow.js +9 -18
  90. package/commands/project/dev/index.js +21 -18
  91. package/commands/project/dev/unifiedFlow.js +14 -7
  92. package/commands/project/download.js +15 -16
  93. package/commands/project/installDeps.d.ts +2 -2
  94. package/commands/project/installDeps.js +9 -9
  95. package/commands/project/list.d.ts +4 -0
  96. package/commands/project/list.js +62 -0
  97. package/commands/project/listBuilds.js +12 -21
  98. package/commands/project/logs.js +21 -24
  99. package/commands/project/migrate.js +46 -15
  100. package/commands/project/migrateApp.js +10 -17
  101. package/commands/project/open.js +6 -14
  102. package/commands/project/profile/add.js +3 -3
  103. package/commands/project/profile/delete.js +1 -2
  104. package/commands/project/profile.js +2 -3
  105. package/commands/project/upload.js +16 -25
  106. package/commands/project/validate.js +7 -7
  107. package/commands/project/watch.js +13 -22
  108. package/commands/project.js +4 -3
  109. package/commands/sandbox/__tests__/create.test.js +5 -5
  110. package/commands/sandbox/create.js +22 -32
  111. package/commands/sandbox/delete.js +39 -64
  112. package/commands/sandbox.js +2 -2
  113. package/commands/secret/addSecret.js +7 -17
  114. package/commands/secret/deleteSecret.js +10 -20
  115. package/commands/secret/listSecret.js +8 -10
  116. package/commands/secret/updateSecret.js +9 -17
  117. package/commands/secret.js +2 -2
  118. package/commands/testAccount/__tests__/delete.test.js +2 -4
  119. package/commands/testAccount/create.js +2 -2
  120. package/commands/testAccount/delete.d.ts +4 -3
  121. package/commands/testAccount/delete.js +155 -14
  122. package/commands/testAccount/importData.d.ts +1 -1
  123. package/commands/testAccount/importData.js +1 -1
  124. package/commands/testAccount.js +1 -1
  125. package/commands/theme/preview.js +1 -4
  126. package/lang/en.d.ts +364 -110
  127. package/lang/en.js +409 -158
  128. package/lang/en.lyaml +4 -4
  129. package/lib/__tests__/buildAccount.test.js +4 -3
  130. package/lib/__tests__/commonOpts.test.js +1 -1
  131. package/lib/__tests__/dependencyManagement.test.js +1 -1
  132. package/lib/__tests__/developerTestAccounts.test.js +3 -3
  133. package/lib/__tests__/npm.test.js +1 -1
  134. package/lib/__tests__/oauth.test.js +4 -4
  135. package/lib/__tests__/process.test.js +10 -5
  136. package/lib/__tests__/sandboxSync.test.js +8 -8
  137. package/lib/__tests__/sandboxes.test.js +8 -8
  138. package/lib/__tests__/serverlessLogs.test.js +1 -1
  139. package/lib/__tests__/usageTracking.test.js +5 -5
  140. package/lib/__tests__/validation.test.js +2 -1
  141. package/lib/__tests__/yargsUtils.test.js +83 -9
  142. package/lib/app/__tests__/migrate.test.js +19 -56
  143. package/lib/app/__tests__/migrate_legacy.test.js +1 -1
  144. package/lib/app/migrate.d.ts +2 -8
  145. package/lib/app/migrate.js +6 -81
  146. package/lib/app/migrate_legacy.js +20 -24
  147. package/lib/buildAccount.d.ts +2 -2
  148. package/lib/buildAccount.js +32 -64
  149. package/lib/commonOpts.d.ts +1 -1
  150. package/lib/commonOpts.js +25 -22
  151. package/lib/configMigrate.js +88 -9
  152. package/lib/configOptions.js +7 -0
  153. package/lib/constants.d.ts +21 -1
  154. package/lib/constants.js +25 -1
  155. package/lib/dependencyManagement.js +9 -27
  156. package/lib/developerTestAccounts.js +9 -23
  157. package/lib/doctor/Diagnosis.js +11 -23
  158. package/lib/doctor/DiagnosticInfoBuilder.js +12 -11
  159. package/lib/doctor/Doctor.js +42 -90
  160. package/lib/doctor/__tests__/Doctor.test.js +4 -4
  161. package/lib/errorHandlers/index.js +12 -20
  162. package/lib/errorHandlers/suppressError.js +11 -18
  163. package/lib/lang.js +6 -5
  164. package/lib/links.d.ts +1 -0
  165. package/lib/links.js +14 -7
  166. package/lib/mcp/setup.js +1 -1
  167. package/lib/middleware/__test__/commandTargetingUtils.test.js +99 -0
  168. package/lib/middleware/__test__/configMiddleware.test.js +11 -11
  169. package/lib/middleware/__test__/yargsChecksMiddleware.test.js +6 -8
  170. package/lib/middleware/commandTargetingUtils.d.ts +8 -0
  171. package/lib/middleware/commandTargetingUtils.js +74 -0
  172. package/lib/middleware/configMiddleware.d.ts +1 -1
  173. package/lib/middleware/configMiddleware.js +21 -81
  174. package/lib/middleware/fireAlarmMiddleware.js +15 -5
  175. package/lib/middleware/gitMiddleware.js +5 -1
  176. package/lib/middleware/notificationsMiddleware.js +5 -11
  177. package/lib/middleware/yargsChecksMiddleware.js +6 -9
  178. package/lib/npm.js +2 -2
  179. package/lib/oauth.js +5 -5
  180. package/lib/process.js +5 -4
  181. package/lib/projects/__tests__/AppDevModeInterface.test.js +87 -90
  182. package/lib/projects/__tests__/LocalDevProcess.test.js +231 -19
  183. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +89 -63
  184. package/lib/projects/__tests__/deploy.test.js +73 -8
  185. package/lib/projects/__tests__/localDevProjectHelpers.test.js +6 -2
  186. package/lib/projects/__tests__/platformVersion.test.js +8 -8
  187. package/lib/projects/__tests__/projects.test.js +12 -12
  188. package/lib/projects/__tests__/structure.test.js +3 -3
  189. package/lib/projects/__tests__/upload.test.d.ts +1 -0
  190. package/lib/projects/__tests__/upload.test.js +82 -0
  191. package/lib/projects/add/__tests__/legacyAddComponent.test.js +6 -6
  192. package/lib/projects/add/__tests__/v2AddComponent.test.d.ts +1 -0
  193. package/lib/projects/add/__tests__/{v3AddComponent.test.js → v2AddComponent.test.js} +39 -39
  194. package/lib/projects/add/{v3AddComponent.d.ts → v2AddComponent.d.ts} +1 -1
  195. package/lib/projects/add/{v3AddComponent.js → v2AddComponent.js} +5 -5
  196. package/lib/projects/create/__tests__/legacy.test.js +5 -5
  197. package/lib/projects/create/__tests__/v2.test.d.ts +1 -0
  198. package/lib/projects/create/__tests__/{v3.test.js → v2.test.js} +82 -7
  199. package/lib/projects/create/index.js +4 -4
  200. package/lib/projects/create/legacy.js +2 -2
  201. package/lib/projects/create/{v3.d.ts → v2.d.ts} +3 -3
  202. package/lib/projects/create/{v3.js → v2.js} +13 -11
  203. package/lib/projects/deploy.d.ts +1 -1
  204. package/lib/projects/deploy.js +2 -2
  205. package/lib/projects/localDev/AppDevModeInterface.d.ts +10 -1
  206. package/lib/projects/localDev/AppDevModeInterface.js +118 -89
  207. package/lib/projects/localDev/DevServerManager.d.ts +11 -29
  208. package/lib/projects/localDev/DevServerManager.js +19 -61
  209. package/lib/projects/localDev/DevServerManager_DEPRECATED.d.ts +40 -0
  210. package/lib/projects/localDev/DevServerManager_DEPRECATED.js +120 -0
  211. package/lib/projects/localDev/LocalDevLogger.d.ts +4 -0
  212. package/lib/projects/localDev/LocalDevLogger.js +27 -6
  213. package/lib/projects/localDev/{LocalDevManager.js → LocalDevManager_DEPRECATED.js} +10 -11
  214. package/lib/projects/localDev/LocalDevProcess.d.ts +7 -5
  215. package/lib/projects/localDev/LocalDevProcess.js +93 -21
  216. package/lib/projects/localDev/LocalDevState.d.ts +12 -8
  217. package/lib/projects/localDev/LocalDevState.js +27 -17
  218. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +6 -1
  219. package/lib/projects/localDev/LocalDevWebsocketServer.js +94 -33
  220. package/lib/projects/localDev/helpers/account.d.ts +1 -1
  221. package/lib/projects/localDev/helpers/account.js +2 -2
  222. package/lib/projects/localDev/helpers/project.d.ts +1 -0
  223. package/lib/projects/localDev/helpers/project.js +44 -4
  224. package/lib/projects/localDev/localDevWebsocketServerUtils.d.ts +7 -0
  225. package/lib/projects/localDev/localDevWebsocketServerUtils.js +19 -0
  226. package/lib/projects/platformVersion.d.ts +1 -1
  227. package/lib/projects/platformVersion.js +1 -1
  228. package/lib/projects/pollProjectBuildAndDeploy.js +4 -4
  229. package/lib/projects/structure.js +6 -6
  230. package/lib/projects/upload.d.ts +1 -1
  231. package/lib/projects/upload.js +17 -8
  232. package/lib/projects/urls.d.ts +0 -1
  233. package/lib/projects/urls.js +0 -3
  234. package/lib/prompts/__tests__/downloadProjectPrompt.test.js +1 -0
  235. package/lib/prompts/__tests__/projectAddPrompt.test.js +10 -10
  236. package/lib/prompts/accountNamePrompt.js +14 -19
  237. package/lib/prompts/accountsPrompt.js +2 -2
  238. package/lib/prompts/cmsFieldPrompt.js +2 -2
  239. package/lib/prompts/createApiSamplePrompt.js +5 -5
  240. package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +10 -1
  241. package/lib/prompts/createFunctionPrompt.js +14 -14
  242. package/lib/prompts/createModulePrompt.js +9 -9
  243. package/lib/prompts/createTemplatePrompt.js +2 -2
  244. package/lib/prompts/downloadProjectPrompt.js +5 -8
  245. package/lib/prompts/installAppPrompt.d.ts +1 -6
  246. package/lib/prompts/installAppPrompt.js +1 -6
  247. package/lib/prompts/personalAccessKeyPrompt.js +3 -3
  248. package/lib/prompts/previewPrompt.js +6 -6
  249. package/lib/prompts/projectAddPrompt.d.ts +2 -2
  250. package/lib/prompts/projectAddPrompt.js +9 -2
  251. package/lib/prompts/projectDevTargetAccountPrompt.js +20 -32
  252. package/lib/prompts/projectNamePrompt.js +4 -8
  253. package/lib/prompts/projectsLogsPrompt.js +2 -4
  254. package/lib/prompts/promptUtils.js +30 -9
  255. package/lib/prompts/sandboxesPrompt.js +7 -7
  256. package/lib/prompts/secretPrompt.js +3 -3
  257. package/lib/prompts/selectAppPrompt.js +3 -3
  258. package/lib/prompts/selectHubDBTablePrompt.js +9 -13
  259. package/lib/prompts/selectProjectTemplatePrompt.js +2 -0
  260. package/lib/prompts/selectPublicAppForMigrationPrompt.js +15 -19
  261. package/lib/prompts/setAsDefaultAccountPrompt.js +4 -8
  262. package/lib/prompts/uploadPrompt.js +5 -5
  263. package/lib/sandboxSync.js +24 -41
  264. package/lib/sandboxes.js +19 -47
  265. package/lib/schema.js +3 -3
  266. package/lib/serverlessLogs.js +11 -13
  267. package/lib/theme/__tests__/migrate.test.d.ts +1 -0
  268. package/lib/theme/__tests__/migrate.test.js +233 -0
  269. package/lib/theme/migrate.d.ts +13 -0
  270. package/lib/theme/migrate.js +90 -0
  271. package/lib/ui/SpinniesManager.d.ts +2 -0
  272. package/lib/ui/SpinniesManager.js +7 -0
  273. package/lib/ui/boxen.js +1 -2
  274. package/lib/ui/git.js +13 -10
  275. package/lib/ui/index.d.ts +4 -0
  276. package/lib/ui/index.js +47 -38
  277. package/lib/ui/serverlessFunctionLogs.js +9 -7
  278. package/lib/ui/uiMessages.d.ts +72 -0
  279. package/lib/ui/uiMessages.js +75 -0
  280. package/lib/usageTracking.js +8 -8
  281. package/lib/validation.js +20 -23
  282. package/lib/yargsUtils.d.ts +1 -1
  283. package/lib/yargsUtils.js +12 -5
  284. package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +2 -2
  285. package/mcp-server/tools/index.js +4 -0
  286. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +2 -2
  287. package/mcp-server/tools/project/CreateProjectTool.d.ts +2 -2
  288. package/mcp-server/tools/project/DocsSearchTool.d.ts +4 -1
  289. package/mcp-server/tools/project/DocsSearchTool.js +5 -5
  290. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +23 -0
  291. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +68 -0
  292. package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +11 -0
  293. package/mcp-server/tools/project/GetApplicationInfoTool.js +49 -0
  294. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +4 -1
  295. package/mcp-server/tools/project/GetConfigValuesTool.js +12 -6
  296. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +2 -2
  297. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -1
  298. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +12 -10
  299. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.d.ts +1 -0
  300. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +169 -0
  301. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.d.ts +1 -0
  302. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +115 -0
  303. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +8 -7
  304. package/mcp-server/utils/__tests__/cliConfig.test.d.ts +1 -0
  305. package/mcp-server/utils/__tests__/cliConfig.test.js +110 -0
  306. package/mcp-server/utils/cliConfig.d.ts +1 -0
  307. package/mcp-server/utils/cliConfig.js +12 -0
  308. package/mcp-server/utils/toolUsageTracking.js +2 -2
  309. package/package.json +8 -7
  310. package/types/LocalDev.d.ts +19 -3
  311. package/ui/index.js +1 -1
  312. package/lib/middleware/__test__/utils.test.js +0 -51
  313. package/lib/middleware/utils.d.ts +0 -8
  314. package/lib/middleware/utils.js +0 -14
  315. package/lib/projects/localDev/DevServerManagerV2.d.ts +0 -22
  316. package/lib/projects/localDev/DevServerManagerV2.js +0 -81
  317. /package/{lib/middleware/__test__/utils.test.d.ts → commands/project/__tests__/list.test.d.ts} +0 -0
  318. /package/{lib/projects/add/__tests__/v3AddComponent.test.d.ts → commands/project/__tests__/validate.test.d.ts} +0 -0
  319. /package/lib/{projects/create/__tests__/v3.test.d.ts → middleware/__test__/commandTargetingUtils.test.d.ts} +0 -0
  320. /package/lib/projects/localDev/{LocalDevManager.d.ts → LocalDevManager_DEPRECATED.d.ts} +0 -0
@@ -1,12 +1,12 @@
1
1
  import { WebSocketServer } from 'ws';
2
2
  import { isPortManagerServerRunning, requestPorts, } from '@hubspot/local-dev-lib/portManager';
3
- import { logger } from '@hubspot/local-dev-lib/logger';
3
+ import { uiLogger } from '../../ui/logger.js';
4
4
  import { LOCAL_DEV_UI_MESSAGE_RECEIVE_TYPES, LOCAL_DEV_UI_MESSAGE_SEND_TYPES, LOCAL_DEV_SERVER_MESSAGE_TYPES, } from '../../constants.js';
5
5
  import LocalDevWebsocketServer from '../localDev/LocalDevWebsocketServer.js';
6
6
  import { lib } from '../../../lang/en.js';
7
7
  vi.mock('ws');
8
8
  vi.mock('@hubspot/local-dev-lib/portManager');
9
- vi.mock('@hubspot/local-dev-lib/logger');
9
+ vi.mock('../../ui/logger.js');
10
10
  describe('LocalDevWebsocketServer', () => {
11
11
  let mockLocalDevProcess;
12
12
  let mockWebSocket;
@@ -30,8 +30,16 @@ describe('LocalDevWebsocketServer', () => {
30
30
  mockLocalDevProcess = {
31
31
  addStateListener: vi.fn(),
32
32
  removeStateListener: vi.fn(),
33
- uploadProject: vi.fn(),
33
+ uploadProject: vi.fn().mockResolvedValue({}),
34
34
  sendDevServerMessage: vi.fn(),
35
+ projectData: {
36
+ name: 'test-project',
37
+ id: 123,
38
+ latestBuild: { id: 'build-1', status: 'SUCCESS' },
39
+ deployedBuild: { id: 'build-1', status: 'SUCCESS' },
40
+ },
41
+ targetProjectAccountId: 456,
42
+ targetTestingAccountId: 789,
35
43
  };
36
44
  // Mock WebSocketServer constructor
37
45
  WebSocketServer.mockImplementation(() => mockWebSocketServer);
@@ -51,39 +59,68 @@ describe('LocalDevWebsocketServer', () => {
51
59
  await server.start();
52
60
  expect(WebSocketServer).toHaveBeenCalledWith({ port: 1234 });
53
61
  expect(mockWebSocketServer.on).toHaveBeenCalledWith('connection', expect.any(Function));
54
- expect(logger.log).toHaveBeenCalled();
62
+ expect(uiLogger.log).toHaveBeenCalled();
55
63
  });
56
- it('should accept connection from valid origin', async () => {
57
- isPortManagerServerRunning.mockResolvedValue(true);
58
- requestPorts.mockResolvedValue({
59
- 'local-dev-ui-websocket-server': 1234,
64
+ describe('valid origins', () => {
65
+ const validOrigins = [
66
+ 'https://app.hubspot.com',
67
+ 'https://app.hubspotqa.com',
68
+ 'https://local.hubspot.com',
69
+ 'https://local.hubspotqa.com',
70
+ 'https://app-na2.hubspot.com',
71
+ 'https://app-na2.hubspotqa.com',
72
+ 'https://app-na3.hubspot.com',
73
+ 'https://app-na3.hubspotqa.com',
74
+ 'https://app-ap1.hubspot.com',
75
+ 'https://app-ap1.hubspotqa.com',
76
+ 'https://app-eu1.hubspot.com',
77
+ 'https://app-eu1.hubspotqa.com',
78
+ ];
79
+ validOrigins.forEach(origin => {
80
+ it(`should accept connection from ${origin}`, async () => {
81
+ isPortManagerServerRunning.mockResolvedValue(true);
82
+ requestPorts.mockResolvedValue({
83
+ 'local-dev-ui-websocket-server': 1234,
84
+ });
85
+ await server.start();
86
+ // Get the connection callback
87
+ const connectionCallback = mockWebSocketServer.on.mock
88
+ .calls[0][1];
89
+ // Simulate connection from valid origin
90
+ connectionCallback(mockWebSocket, {
91
+ headers: { origin },
92
+ });
93
+ expect(mockWebSocket.on).toHaveBeenCalledWith('message', expect.any(Function));
94
+ expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledWith('projectNodes', expect.any(Function));
95
+ expect(mockWebSocket.close).not.toHaveBeenCalled();
96
+ });
60
97
  });
61
- await server.start();
62
- // Get the connection callback
63
- const connectionCallback = mockWebSocketServer.on.mock.calls[0][1];
64
- // Simulate connection from valid origin
65
- connectionCallback(mockWebSocket, {
66
- headers: { origin: 'https://app.hubspot.com' },
67
- });
68
- expect(mockWebSocket.on).toHaveBeenCalledWith('message', expect.any(Function));
69
- expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledWith('projectNodes', expect.any(Function));
70
- expect(mockWebSocket.close).not.toHaveBeenCalled();
71
98
  });
72
- it('should reject connection from invalid origin', async () => {
73
- isPortManagerServerRunning.mockResolvedValue(true);
74
- requestPorts.mockResolvedValue({
75
- 'local-dev-ui-websocket-server': 1234,
99
+ describe('invalid origins', () => {
100
+ const invalidOrigins = [
101
+ 'https://malicious-site.com',
102
+ 'https://app.malicious-site.com',
103
+ 'https://app.hubspot.com.evil.com',
104
+ ];
105
+ invalidOrigins.forEach(origin => {
106
+ it(`should reject connection from "${origin}"`, async () => {
107
+ isPortManagerServerRunning.mockResolvedValue(true);
108
+ requestPorts.mockResolvedValue({
109
+ 'local-dev-ui-websocket-server': 1234,
110
+ });
111
+ await server.start();
112
+ // Get the connection callback
113
+ const connectionCallback = mockWebSocketServer.on.mock
114
+ .calls[0][1];
115
+ // Simulate connection from invalid origin
116
+ connectionCallback(mockWebSocket, {
117
+ headers: { origin },
118
+ });
119
+ expect(mockWebSocket.close).toHaveBeenCalledWith(1008, lib.LocalDevWebsocketServer.errors.originNotAllowed(origin));
120
+ expect(mockWebSocket.on).not.toHaveBeenCalled();
121
+ expect(mockLocalDevProcess.addStateListener).not.toHaveBeenCalled();
122
+ });
76
123
  });
77
- await server.start();
78
- // Get the connection callback
79
- const connectionCallback = mockWebSocketServer.on.mock.calls[0][1];
80
- // Simulate connection from invalid origin
81
- connectionCallback(mockWebSocket, {
82
- headers: { origin: 'https://malicious-site.com' },
83
- });
84
- expect(mockWebSocket.close).toHaveBeenCalledWith(1008, lib.LocalDevWebsocketServer.errors.originNotAllowed('https://malicious-site.com'));
85
- expect(mockWebSocket.on).not.toHaveBeenCalled();
86
- expect(mockLocalDevProcess.addStateListener).not.toHaveBeenCalled();
87
124
  });
88
125
  it('should reject connection with no origin header', async () => {
89
126
  isPortManagerServerRunning.mockResolvedValue(true);
@@ -111,7 +148,7 @@ describe('LocalDevWebsocketServer', () => {
111
148
  const connectionCallback = mockWebSocketServer.on.mock.calls[0][1];
112
149
  // Simulate connection from valid origin
113
150
  connectionCallback(mockWebSocket, {
114
- headers: { origin: 'https://app.hubspot.com' },
151
+ headers: { origin: 'https://app-na3.hubspot.com' },
115
152
  });
116
153
  expect(mockLocalDevProcess.sendDevServerMessage).toHaveBeenCalledWith(LOCAL_DEV_SERVER_MESSAGE_TYPES.WEBSOCKET_SERVER_CONNECTED);
117
154
  });
@@ -140,7 +177,7 @@ describe('LocalDevWebsocketServer', () => {
140
177
  const messageCallback = mockWebSocket.on.mock.calls[0][1];
141
178
  const message = {};
142
179
  messageCallback(JSON.stringify(message));
143
- expect(logger.error).toHaveBeenCalled();
180
+ expect(uiLogger.error).toHaveBeenCalled();
144
181
  });
145
182
  it('should log error for unknown message type', () => {
146
183
  const messageCallback = mockWebSocket.on.mock.calls[0][1];
@@ -148,13 +185,13 @@ describe('LocalDevWebsocketServer', () => {
148
185
  type: 'UNKNOWN_TYPE',
149
186
  };
150
187
  messageCallback(JSON.stringify(message));
151
- expect(logger.error).toHaveBeenCalled();
188
+ expect(uiLogger.error).toHaveBeenCalled();
152
189
  });
153
190
  it('should log error for invalid JSON', () => {
154
191
  const messageCallback = mockWebSocket.on.mock.calls[0][1];
155
192
  const invalidJson = 'invalid json';
156
193
  messageCallback(invalidJson);
157
- expect(logger.error).toHaveBeenCalled();
194
+ expect(uiLogger.error).toHaveBeenCalled();
158
195
  });
159
196
  });
160
197
  describe('shutdown()', () => {
@@ -205,7 +242,7 @@ describe('LocalDevWebsocketServer', () => {
205
242
  headers: { origin: 'https://app.hubspot.com' },
206
243
  });
207
244
  connectionCallback(mockWebSocket2, {
208
- headers: { origin: 'https://app.hubspotqa.com' },
245
+ headers: { origin: 'https://app-na2.hubspotqa.com' },
209
246
  });
210
247
  connectionCallback(mockWebSocket3, {
211
248
  headers: { origin: 'https://local.hubspot.com' },
@@ -215,7 +252,7 @@ describe('LocalDevWebsocketServer', () => {
215
252
  expect(mockWebSocket2.on).toHaveBeenCalledWith('message', expect.any(Function));
216
253
  expect(mockWebSocket3.on).toHaveBeenCalledWith('message', expect.any(Function));
217
254
  // Each connection should trigger state listener setup
218
- expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledTimes(9); // 3 listeners per connection * 3 connections
255
+ expect(mockLocalDevProcess.addStateListener).toHaveBeenCalledTimes(12); // 4 listeners per connection * 3 connections
219
256
  // Each connection should trigger dev server message
220
257
  expect(mockLocalDevProcess.sendDevServerMessage).toHaveBeenCalledTimes(3);
221
258
  expect(mockLocalDevProcess.sendDevServerMessage).toHaveBeenCalledWith(LOCAL_DEV_SERVER_MESSAGE_TYPES.WEBSOCKET_SERVER_CONNECTED);
@@ -225,29 +262,12 @@ describe('LocalDevWebsocketServer', () => {
225
262
  expect(mockWebSocket3.close).not.toHaveBeenCalled();
226
263
  });
227
264
  it('should send project data to each connection independently', () => {
228
- // Setup mock project data properties as getters
229
- Object.defineProperty(mockLocalDevProcess, 'projectName', {
230
- get: () => 'test-project',
231
- configurable: true,
232
- });
233
- Object.defineProperty(mockLocalDevProcess, 'projectId', {
234
- get: () => 123,
235
- configurable: true,
236
- });
237
- Object.defineProperty(mockLocalDevProcess, 'targetProjectAccountId', {
238
- get: () => 456,
239
- configurable: true,
240
- });
241
- Object.defineProperty(mockLocalDevProcess, 'targetTestingAccountId', {
242
- get: () => 789,
243
- configurable: true,
244
- });
245
265
  // Establish multiple connections
246
266
  connectionCallback(mockWebSocket1, {
247
267
  headers: { origin: 'https://app.hubspot.com' },
248
268
  });
249
269
  connectionCallback(mockWebSocket2, {
250
- headers: { origin: 'https://app.hubspotqa.com' },
270
+ headers: { origin: 'https://app-eu1.hubspotqa.com' },
251
271
  });
252
272
  // Each websocket should receive project data
253
273
  expect(mockWebSocket1.send).toHaveBeenCalledWith(JSON.stringify({
@@ -255,6 +275,8 @@ describe('LocalDevWebsocketServer', () => {
255
275
  data: {
256
276
  projectName: 'test-project',
257
277
  projectId: 123,
278
+ latestBuild: { id: 'build-1', status: 'SUCCESS' },
279
+ deployedBuild: { id: 'build-1', status: 'SUCCESS' },
258
280
  targetProjectAccountId: 456,
259
281
  targetTestingAccountId: 789,
260
282
  },
@@ -264,6 +286,8 @@ describe('LocalDevWebsocketServer', () => {
264
286
  data: {
265
287
  projectName: 'test-project',
266
288
  projectId: 123,
289
+ latestBuild: { id: 'build-1', status: 'SUCCESS' },
290
+ deployedBuild: { id: 'build-1', status: 'SUCCESS' },
267
291
  targetProjectAccountId: 456,
268
292
  targetTestingAccountId: 789,
269
293
  },
@@ -275,7 +299,7 @@ describe('LocalDevWebsocketServer', () => {
275
299
  headers: { origin: 'https://app.hubspot.com' },
276
300
  });
277
301
  connectionCallback(mockWebSocket2, {
278
- headers: { origin: 'https://app.hubspotqa.com' },
302
+ headers: { origin: 'https://app-ap1.hubspotqa.com' },
279
303
  });
280
304
  // Get all the close callbacks for both connections (there should be 2 per connection)
281
305
  const closeCallbacks1 = mockWebSocket1.on.mock.calls
@@ -284,16 +308,16 @@ describe('LocalDevWebsocketServer', () => {
284
308
  const closeCallbacks2 = mockWebSocket2.on.mock.calls
285
309
  .filter(call => call[0] === 'close')
286
310
  .map(call => call[1]);
287
- expect(closeCallbacks1).toHaveLength(3); // projectNodes and appData listeners
288
- expect(closeCallbacks2).toHaveLength(3); // projectNodes and appData listeners
311
+ expect(closeCallbacks1).toHaveLength(4); // projectNodes, appData, devServersStarted, and uploadWarnings listeners
312
+ expect(closeCallbacks2).toHaveLength(4); // projectNodes, appData, devServersStarted, and uploadWarnings listeners
289
313
  // Simulate first connection closing (call all close callbacks)
290
314
  closeCallbacks1.forEach(callback => callback());
291
- // Should have removed listeners for first connection (2 listeners: projectNodes and appData)
292
- expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(3);
315
+ // Should have removed listeners for first connection (4 listeners: projectNodes, appData, devServersStarted, and uploadWarnings)
316
+ expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(4);
293
317
  // Simulate second connection closing
294
318
  closeCallbacks2.forEach(callback => callback());
295
319
  // Should have removed listeners for second connection as well
296
- expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(6);
320
+ expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(8);
297
321
  });
298
322
  it('should broadcast state changes to all connected clients', () => {
299
323
  // Establish connections
@@ -301,7 +325,7 @@ describe('LocalDevWebsocketServer', () => {
301
325
  headers: { origin: 'https://app.hubspot.com' },
302
326
  });
303
327
  connectionCallback(mockWebSocket2, {
304
- headers: { origin: 'https://app.hubspotqa.com' },
328
+ headers: { origin: 'https://local.hubspotqa.com' },
305
329
  });
306
330
  // Get the projectNodes listeners that were registered
307
331
  const projectNodesListeners = mockLocalDevProcess.addStateListener.mock.calls
@@ -317,6 +341,8 @@ describe('LocalDevWebsocketServer', () => {
317
341
  componentRoot: '/test/path',
318
342
  componentConfigPath: '/test/path/config.json',
319
343
  configUpdatedSinceLastUpload: false,
344
+ removed: false,
345
+ parsingErrors: [],
320
346
  },
321
347
  componentDeps: {},
322
348
  metaFilePath: '/test/path',
@@ -99,7 +99,7 @@ describe('lib/projects/deploy', () => {
99
99
  const targetAccountId = 12345;
100
100
  const projectName = 'test-project';
101
101
  const buildId = 5;
102
- const useV3Api = true;
102
+ const useV2Api = true;
103
103
  const force = false;
104
104
  it('successfully deploys and returns deploy result', async () => {
105
105
  const mockDeployResponseData = {
@@ -126,9 +126,9 @@ describe('lib/projects/deploy', () => {
126
126
  data: mockDeployResponseData,
127
127
  });
128
128
  mockPollDeployStatus.mockResolvedValue(mockDeployResult);
129
- const result = await handleProjectDeploy(targetAccountId, projectName, buildId, useV3Api, force);
130
- expect(mockDeployProject).toHaveBeenCalledWith(targetAccountId, projectName, buildId, useV3Api, force);
131
- expect(result).toEqual(mockDeployResult);
129
+ const deploy = await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
130
+ expect(mockDeployProject).toHaveBeenCalledWith(targetAccountId, projectName, buildId, useV2Api, force);
131
+ expect(deploy).toEqual(mockDeployResult);
132
132
  });
133
133
  it('handles blocked deploy with warnings', async () => {
134
134
  const mockBlockedResponse = {
@@ -150,15 +150,80 @@ describe('lib/projects/deploy', () => {
150
150
  mockDeployProject.mockResolvedValue({
151
151
  data: mockBlockedResponse,
152
152
  });
153
- const result = await handleProjectDeploy(targetAccountId, projectName, buildId, useV3Api, force);
153
+ await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
154
154
  expect(mockUiLogger.warn).toHaveBeenCalledWith(commands.project.deploy.errors.deployWarningsHeader);
155
- expect(result).toBeUndefined();
155
+ });
156
+ it('handles blocked deploy with errors (cannot be forced)', async () => {
157
+ const mockBlockedResponse = {
158
+ buildResultType: 'DEPLOY_BLOCKED',
159
+ issues: [
160
+ {
161
+ uid: 'component-1',
162
+ componentTypeName: 'module',
163
+ errorMessages: [],
164
+ blockingMessages: [
165
+ {
166
+ message: 'This is an error',
167
+ isWarning: false,
168
+ },
169
+ ],
170
+ },
171
+ ],
172
+ };
173
+ mockDeployProject.mockResolvedValue({
174
+ data: mockBlockedResponse,
175
+ });
176
+ await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
177
+ expect(mockUiLogger.error).toHaveBeenCalledWith(commands.project.deploy.errors.deployBlockedHeader);
178
+ expect(mockUiLogger.log).toHaveBeenCalledWith(commands.project.deploy.errors.deployIssueComponentWarning('component-1', 'module', 'This is an error'));
179
+ });
180
+ it('handles blocked deploy with no blocking messages', async () => {
181
+ const mockBlockedResponse = {
182
+ buildResultType: 'DEPLOY_BLOCKED',
183
+ issues: [
184
+ {
185
+ uid: 'component-1',
186
+ componentTypeName: 'module',
187
+ errorMessages: [],
188
+ blockingMessages: [],
189
+ },
190
+ ],
191
+ };
192
+ mockDeployProject.mockResolvedValue({
193
+ data: mockBlockedResponse,
194
+ });
195
+ await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
196
+ expect(mockUiLogger.warn).toHaveBeenCalledWith(commands.project.deploy.errors.deployWarningsHeader);
197
+ expect(mockUiLogger.log).toHaveBeenCalledWith(commands.project.deploy.errors.deployIssueComponentGeneric('component-1', 'module'));
156
198
  });
157
199
  it('handles general deploy failure', async () => {
158
200
  mockDeployProject.mockResolvedValue({ data: null });
159
- const result = await handleProjectDeploy(targetAccountId, projectName, buildId, useV3Api, force);
201
+ const deploy = await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
202
+ expect(mockUiLogger.error).toHaveBeenCalledWith(commands.project.deploy.errors.deploy);
203
+ expect(deploy).toBeUndefined();
204
+ });
205
+ it('handles undefined deploy response', async () => {
206
+ mockDeployProject.mockResolvedValue({ data: undefined });
207
+ const deploy = await handleProjectDeploy(targetAccountId, projectName, buildId, useV2Api, force);
160
208
  expect(mockUiLogger.error).toHaveBeenCalledWith(commands.project.deploy.errors.deploy);
161
- expect(result).toBeUndefined();
209
+ expect(deploy).toBeUndefined();
210
+ });
211
+ it('passes correct parameters to deployProject', async () => {
212
+ const mockDeployResponseData = {
213
+ id: 'deploy-123',
214
+ buildResultType: 'DEPLOY_QUEUED',
215
+ links: {
216
+ status: 'http://status-url',
217
+ },
218
+ };
219
+ mockDeployProject.mockResolvedValue({
220
+ data: mockDeployResponseData,
221
+ });
222
+ mockPollDeployStatus.mockResolvedValue({});
223
+ await handleProjectDeploy(targetAccountId, projectName, buildId, false, true);
224
+ expect(mockDeployProject).toHaveBeenCalledWith(targetAccountId, projectName, buildId, false, // useV2Api
225
+ true // force
226
+ );
162
227
  });
163
228
  });
164
229
  });
@@ -30,6 +30,8 @@ describe('isDeployedProjectUpToDateWithLocal', () => {
30
30
  componentRoot: '/local/path',
31
31
  componentConfigPath: '/local/path/config.json',
32
32
  configUpdatedSinceLastUpload: false,
33
+ removed: false,
34
+ parsingErrors: [],
33
35
  },
34
36
  componentDeps: {},
35
37
  metaFilePath: '/local/path',
@@ -99,7 +101,8 @@ describe('isDeployedProjectUpToDateWithLocal', () => {
99
101
  it('should clean up temp directory even when errors occur', async () => {
100
102
  // Mock downloadProject to throw an error after temp dir is created
101
103
  downloadProject.mockRejectedValue(new Error('Download Error'));
102
- await expect(isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes)).rejects.toThrow('Download Error');
104
+ const result = await isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes);
105
+ expect(result).toBe(false);
103
106
  expect(fs.remove).toHaveBeenCalledWith(mockTempDir);
104
107
  });
105
108
  it('should handle translateForLocalDev errors', async () => {
@@ -111,7 +114,8 @@ describe('isDeployedProjectUpToDateWithLocal', () => {
111
114
  extractZipArchive.mockResolvedValue(undefined);
112
115
  // Mock translate to throw an error
113
116
  translate.mockRejectedValue(new Error('Translation Error'));
114
- await expect(isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes)).rejects.toThrow('Translation Error');
117
+ const result = await isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes);
118
+ expect(result).toBe(false);
115
119
  expect(fs.remove).toHaveBeenCalledWith(mockTempDir);
116
120
  });
117
121
  });
@@ -1,23 +1,23 @@
1
- import { useV3Api } from '../platformVersion.js';
1
+ import { isV2Project } from '../platformVersion.js';
2
2
  describe('platformVersion', () => {
3
- describe('useV3Api', () => {
3
+ describe('isV2Project', () => {
4
4
  it('returns true if platform version is UNSTABLE', () => {
5
- expect(useV3Api('UNSTABLE')).toBe(true);
5
+ expect(isV2Project('UNSTABLE')).toBe(true);
6
6
  });
7
7
  it('returns true if platform version is equal to the minimum', () => {
8
- expect(useV3Api('2025.2')).toBe(true);
8
+ expect(isV2Project('2025.2')).toBe(true);
9
9
  });
10
10
  it('returns true if platform version is greater than the minimum', () => {
11
- expect(useV3Api('2026.2')).toBe(true);
11
+ expect(isV2Project('2026.2')).toBe(true);
12
12
  });
13
13
  it('returns false if platform version is less than the minimum', () => {
14
- expect(useV3Api('2025.0')).toBe(false);
14
+ expect(isV2Project('2025.0')).toBe(false);
15
15
  });
16
16
  it('returns false if platform version is invalid', () => {
17
- expect(useV3Api(null)).toBe(false);
17
+ expect(isV2Project(null)).toBe(false);
18
18
  });
19
19
  it('returns false for an invalid platform version', () => {
20
- expect(useV3Api('notplaformversion')).toBe(false);
20
+ expect(isV2Project('notplaformversion')).toBe(false);
21
21
  });
22
22
  });
23
23
  });
@@ -3,8 +3,8 @@ import os from 'os';
3
3
  import path from 'path';
4
4
  import { EXIT_CODES } from '../../enums/exitCodes.js';
5
5
  import { validateProjectConfig } from '../../projects/config.js';
6
- import { logger } from '@hubspot/local-dev-lib/logger';
7
- vi.mock('@hubspot/local-dev-lib/logger');
6
+ import { uiLogger } from '../../ui/logger.js';
7
+ vi.mock('../../ui/logger.js');
8
8
  describe('lib/projects', () => {
9
9
  describe('validateProjectConfig()', () => {
10
10
  let projectDir;
@@ -26,58 +26,58 @@ describe('lib/projects', () => {
26
26
  // @ts-ignore Testing invalid input
27
27
  validateProjectConfig(null, projectDir);
28
28
  expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
29
- expect(logger.error).toHaveBeenCalledWith(expect.stringMatching(/.*Unable to locate a project configuration file. Try running again from a project directory, or run*/));
29
+ expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*Unable to locate a project configuration file. Try running again from a project directory, or run*/));
30
30
  });
31
31
  it('rejects configuration with missing name', () => {
32
32
  // @ts-ignore Testing invalid input
33
33
  validateProjectConfig({ srcDir: '.' }, projectDir);
34
34
  expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
35
- expect(logger.error).toHaveBeenCalledWith(expect.stringMatching(/.*missing required fields*/));
35
+ expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*missing required fields*/));
36
36
  });
37
37
  it('rejects configuration with missing srcDir', () => {
38
38
  // @ts-ignore Testing invalid input
39
39
  validateProjectConfig({ name: 'hello' }, projectDir);
40
40
  expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
41
- expect(logger.error).toHaveBeenCalledWith(expect.stringMatching(/.*missing required fields.*/));
41
+ expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*missing required fields.*/));
42
42
  });
43
43
  describe('rejects configuration with srcDir outside project directory', () => {
44
44
  it('for parent directory', () => {
45
45
  validateProjectConfig({ name: 'hello', srcDir: '..', platformVersion: '' }, projectDir);
46
46
  expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
47
- expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('srcDir: ".."'));
47
+ expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining('srcDir: ".."'));
48
48
  });
49
49
  it('for root directory', () => {
50
50
  validateProjectConfig({ name: 'hello', srcDir: '/', platformVersion: '' }, projectDir);
51
51
  expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
52
- expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('srcDir: "/"'));
52
+ expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining('srcDir: "/"'));
53
53
  });
54
54
  it('for complicated directory', () => {
55
55
  const srcDir = './src/././../src/../../src';
56
56
  validateProjectConfig({ name: 'hello', srcDir, platformVersion: '' }, projectDir);
57
57
  expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
58
- expect(logger.error).toHaveBeenCalledWith(expect.stringContaining(`srcDir: "${srcDir}"`));
58
+ expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining(`srcDir: "${srcDir}"`));
59
59
  });
60
60
  });
61
61
  it('rejects configuration with srcDir that does not exist', () => {
62
62
  validateProjectConfig({ name: 'hello', srcDir: 'foo', platformVersion: '' }, projectDir);
63
63
  expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
64
- expect(logger.error).toHaveBeenCalledWith(expect.stringMatching(/.*could not be found in.*/));
64
+ expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/.*could not be found in.*/));
65
65
  });
66
66
  describe('accepts configuration with valid srcDir', () => {
67
67
  it('for current directory', () => {
68
68
  validateProjectConfig({ name: 'hello', srcDir: '.', platformVersion: '' }, projectDir);
69
69
  expect(exitMock).not.toHaveBeenCalled();
70
- expect(logger.error).not.toHaveBeenCalled();
70
+ expect(uiLogger.error).not.toHaveBeenCalled();
71
71
  });
72
72
  it('for relative directory', () => {
73
73
  validateProjectConfig({ name: 'hello', srcDir: './src', platformVersion: '' }, projectDir);
74
74
  expect(exitMock).not.toHaveBeenCalled();
75
- expect(logger.error).not.toHaveBeenCalled();
75
+ expect(uiLogger.error).not.toHaveBeenCalled();
76
76
  });
77
77
  it('for implied relative directory', () => {
78
78
  validateProjectConfig({ name: 'hello', srcDir: 'src', platformVersion: '' }, projectDir);
79
79
  expect(exitMock).not.toHaveBeenCalled();
80
- expect(logger.error).not.toHaveBeenCalled();
80
+ expect(uiLogger.error).not.toHaveBeenCalled();
81
81
  });
82
82
  });
83
83
  });
@@ -1,11 +1,11 @@
1
1
  import fs from 'fs';
2
2
  import * as HSfs from '@hubspot/local-dev-lib/fs';
3
- import { logger } from '@hubspot/local-dev-lib/logger';
3
+ import { uiLogger } from '../../ui/logger.js';
4
4
  import { getComponentTypeFromConfigFile, loadConfigFile, getAppCardConfigs, getIsLegacyApp, componentIsApp, findProjectComponents, getProjectComponentTypes, getComponentUid, componentIsPublicApp, } from '../structure.js';
5
5
  import { ComponentTypes } from '../../../types/Projects.js';
6
6
  vi.mock('fs');
7
7
  vi.mock('@hubspot/local-dev-lib/fs');
8
- vi.mock('@hubspot/local-dev-lib/logger');
8
+ vi.mock('../../ui/logger.js');
9
9
  const mockedReadFileSync = fs.readFileSync;
10
10
  const mockedWalk = HSfs.walk;
11
11
  const getMockPrivateAppConfig = (cards = []) => ({
@@ -46,7 +46,7 @@ describe('lib/projects/structure', () => {
46
46
  throw new Error('File not found');
47
47
  });
48
48
  expect(loadConfigFile('nonexistent/path/app.json')).toBeNull();
49
- expect(logger.debug).toHaveBeenCalled();
49
+ expect(uiLogger.debug).toHaveBeenCalled();
50
50
  });
51
51
  });
52
52
  describe('getAppCardConfigs()', () => {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ import fs from 'fs-extra';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { vi } from 'vitest';
5
+ import { validateSourceDirectory } from '../upload.js';
6
+ import { uiLogger } from '../../ui/logger.js';
7
+ import { lib } from '../../../lang/en.js';
8
+ import { isV2Project } from '../platformVersion.js';
9
+ import ProjectValidationError from '../../errors/ProjectValidationError.js';
10
+ import { walk } from '@hubspot/local-dev-lib/fs';
11
+ // Mock dependencies
12
+ vi.mock('../../ui/logger.js');
13
+ vi.mock('../platformVersion.js');
14
+ vi.mock('@hubspot/local-dev-lib/fs');
15
+ describe('lib/projects/upload', () => {
16
+ describe('validateSourceDirectory', () => {
17
+ let tempDir;
18
+ let srcDir;
19
+ let projectConfig;
20
+ beforeEach(() => {
21
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'upload-test-'));
22
+ srcDir = path.join(tempDir, 'src');
23
+ fs.mkdirSync(srcDir, { recursive: true });
24
+ projectConfig = {
25
+ name: 'test-project',
26
+ srcDir: 'src',
27
+ platformVersion: '2025.2',
28
+ };
29
+ vi.clearAllMocks();
30
+ });
31
+ afterEach(() => {
32
+ fs.removeSync(tempDir);
33
+ });
34
+ it('should throw ProjectValidationError when source directory is empty', async () => {
35
+ vi.mocked(walk).mockResolvedValue([]);
36
+ await expect(validateSourceDirectory(srcDir, projectConfig, tempDir)).rejects.toThrow(ProjectValidationError);
37
+ expect(walk).toHaveBeenCalledWith(srcDir, ['node_modules']);
38
+ });
39
+ it('should warn about legacy files in V2 projects', async () => {
40
+ vi.mocked(isV2Project).mockReturnValue(true);
41
+ const legacyFilePath = path.join(srcDir, 'app', 'serverless.json');
42
+ vi.mocked(walk).mockResolvedValue([legacyFilePath]);
43
+ await validateSourceDirectory(srcDir, projectConfig, tempDir);
44
+ expect(uiLogger.warn).toHaveBeenCalledWith(lib.projectUpload.handleProjectUpload.legacyFileDetected('src/app/serverless.json', '2025.2'));
45
+ });
46
+ it('should warn about multiple legacy files', async () => {
47
+ vi.mocked(isV2Project).mockReturnValue(true);
48
+ const filePaths = [
49
+ path.join(srcDir, 'app1', 'serverless.json'),
50
+ path.join(srcDir, 'app2', 'app.json'),
51
+ path.join(srcDir, 'app3', 'public-app.json'),
52
+ ];
53
+ vi.mocked(walk).mockResolvedValue(filePaths);
54
+ await validateSourceDirectory(srcDir, projectConfig, tempDir);
55
+ expect(uiLogger.warn).toHaveBeenCalledTimes(3);
56
+ expect(uiLogger.warn).toHaveBeenCalledWith(lib.projectUpload.handleProjectUpload.legacyFileDetected('src/app1/serverless.json', '2025.2'));
57
+ expect(uiLogger.warn).toHaveBeenCalledWith(lib.projectUpload.handleProjectUpload.legacyFileDetected('src/app2/app.json', '2025.2'));
58
+ expect(uiLogger.warn).toHaveBeenCalledWith(lib.projectUpload.handleProjectUpload.legacyFileDetected('src/app3/public-app.json', '2025.2'));
59
+ });
60
+ it('should not warn about non-legacy files', async () => {
61
+ vi.mocked(isV2Project).mockReturnValue(true);
62
+ const filePaths = [
63
+ path.join(srcDir, 'component.js'),
64
+ path.join(srcDir, 'config.json'),
65
+ ];
66
+ vi.mocked(walk).mockResolvedValue(filePaths);
67
+ await validateSourceDirectory(srcDir, projectConfig, tempDir);
68
+ expect(uiLogger.warn).not.toHaveBeenCalled();
69
+ });
70
+ it('should not warn about legacy files in non-V2 projects', async () => {
71
+ vi.mocked(isV2Project).mockReturnValue(false);
72
+ projectConfig.platformVersion = '2025.1';
73
+ const filePaths = [
74
+ path.join(srcDir, 'app', 'serverless.json'),
75
+ path.join(srcDir, 'app', 'app.json'),
76
+ ];
77
+ vi.mocked(walk).mockResolvedValue(filePaths);
78
+ await validateSourceDirectory(srcDir, projectConfig, tempDir);
79
+ expect(uiLogger.warn).not.toHaveBeenCalled();
80
+ });
81
+ });
82
+ });