@hubspot/cli 7.7.35-experimental.0 → 7.8.0-beta.0

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 (284) 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 +0 -3
  7. package/commands/account/clean.js +18 -27
  8. package/commands/account/createOverride.js +13 -31
  9. package/commands/account/info.js +20 -31
  10. package/commands/account/list.js +16 -22
  11. package/commands/account/remove.js +12 -20
  12. package/commands/account/removeOverride.js +11 -21
  13. package/commands/account/rename.js +6 -9
  14. package/commands/account/use.js +12 -26
  15. package/commands/account.js +2 -2
  16. package/commands/app/__tests__/migrate.test.js +5 -6
  17. package/commands/app/migrate.js +13 -19
  18. package/commands/app/secret/add.js +2 -1
  19. package/commands/app/secret/delete.js +2 -1
  20. package/commands/app/secret/list.js +2 -1
  21. package/commands/app/secret/update.js +2 -1
  22. package/commands/app/secret.js +2 -1
  23. package/commands/app.js +2 -2
  24. package/commands/auth.d.ts +1 -0
  25. package/commands/auth.js +16 -7
  26. package/commands/cms/convertFields.js +7 -9
  27. package/commands/cms/getReactModule.js +9 -14
  28. package/commands/cms/lighthouseScore.js +33 -36
  29. package/commands/cms.js +2 -2
  30. package/commands/completion.js +3 -3
  31. package/commands/config/set.d.ts +1 -1
  32. package/commands/config/set.js +64 -37
  33. package/commands/config.js +2 -2
  34. package/commands/create.js +2 -2
  35. package/commands/customObject/create.js +10 -12
  36. package/commands/customObject/schema/create.js +9 -11
  37. package/commands/customObject/schema/delete.js +16 -16
  38. package/commands/customObject/schema/fetch-all.js +12 -11
  39. package/commands/customObject/schema/fetch.js +15 -15
  40. package/commands/customObject/schema/list.js +4 -4
  41. package/commands/customObject/schema/update.js +13 -13
  42. package/commands/customObject/schema.js +2 -2
  43. package/commands/customObject.js +6 -7
  44. package/commands/doctor.js +8 -11
  45. package/commands/feedback.js +7 -12
  46. package/commands/fetch.js +8 -8
  47. package/commands/filemanager/fetch.js +7 -7
  48. package/commands/filemanager/upload.js +15 -34
  49. package/commands/filemanager.js +2 -2
  50. package/commands/function/deploy.js +11 -29
  51. package/commands/function/list.js +8 -8
  52. package/commands/function/server.js +9 -11
  53. package/commands/function.d.ts +1 -1
  54. package/commands/function.js +2 -2
  55. package/commands/getStarted.d.ts +0 -2
  56. package/commands/getStarted.js +4 -4
  57. package/commands/hubdb/clear.js +7 -15
  58. package/commands/hubdb/create.js +9 -15
  59. package/commands/hubdb/delete.js +8 -15
  60. package/commands/hubdb/fetch.js +6 -9
  61. package/commands/hubdb.d.ts +1 -1
  62. package/commands/hubdb.js +2 -2
  63. package/commands/init.js +2 -3
  64. package/commands/lint.js +16 -16
  65. package/commands/list.js +8 -14
  66. package/commands/logs.js +14 -20
  67. package/commands/mcp/__tests__/setup.test.js +2 -2
  68. package/commands/mcp/setup.js +11 -2
  69. package/commands/mcp.js +3 -3
  70. package/commands/mv.js +6 -17
  71. package/commands/open.js +5 -5
  72. package/commands/project/__tests__/add.test.js +4 -2
  73. package/commands/project/__tests__/create.test.js +6 -6
  74. package/commands/project/__tests__/deploy.test.js +3 -7
  75. package/commands/project/__tests__/devUnifiedFlow.test.js +2 -4
  76. package/commands/project/__tests__/installDeps.test.js +8 -8
  77. package/commands/project/__tests__/logs.test.js +1 -4
  78. package/commands/project/__tests__/migrate.test.js +6 -7
  79. package/commands/project/__tests__/migrateApp.test.js +3 -7
  80. package/commands/project/__tests__/profile.test.js +1 -1
  81. package/commands/project/__tests__/validate.test.js +98 -0
  82. package/commands/project/add.js +4 -8
  83. package/commands/project/cloneApp.js +14 -19
  84. package/commands/project/create.js +2 -9
  85. package/commands/project/deploy.js +5 -5
  86. package/commands/project/dev/deprecatedFlow.js +7 -16
  87. package/commands/project/dev/index.js +16 -13
  88. package/commands/project/dev/unifiedFlow.js +8 -4
  89. package/commands/project/download.js +10 -14
  90. package/commands/project/installDeps.js +8 -9
  91. package/commands/project/listBuilds.js +12 -21
  92. package/commands/project/logs.js +21 -24
  93. package/commands/project/migrate.js +41 -13
  94. package/commands/project/migrateApp.js +10 -17
  95. package/commands/project/open.js +6 -14
  96. package/commands/project/profile/add.js +3 -3
  97. package/commands/project/profile/delete.js +1 -2
  98. package/commands/project/profile.js +2 -3
  99. package/commands/project/upload.js +16 -25
  100. package/commands/project/validate.js +7 -7
  101. package/commands/project/watch.js +13 -22
  102. package/commands/project.js +2 -3
  103. package/commands/sandbox/__tests__/create.test.js +5 -5
  104. package/commands/sandbox/create.js +22 -32
  105. package/commands/sandbox/delete.js +35 -63
  106. package/commands/sandbox.js +2 -2
  107. package/commands/secret/addSecret.js +7 -17
  108. package/commands/secret/deleteSecret.js +10 -20
  109. package/commands/secret/listSecret.js +8 -10
  110. package/commands/secret/updateSecret.js +9 -17
  111. package/commands/secret.js +2 -2
  112. package/commands/testAccount/__tests__/delete.test.js +2 -4
  113. package/commands/testAccount/delete.d.ts +4 -3
  114. package/commands/testAccount/delete.js +155 -14
  115. package/commands/testAccount/importData.d.ts +1 -1
  116. package/commands/testAccount/importData.js +1 -1
  117. package/commands/testAccount.js +1 -1
  118. package/lang/en.d.ts +317 -98
  119. package/lang/en.js +312 -96
  120. package/lang/en.lyaml +2 -2
  121. package/lib/__tests__/buildAccount.test.js +2 -1
  122. package/lib/__tests__/commonOpts.test.js +1 -1
  123. package/lib/__tests__/dependencyManagement.test.js +1 -1
  124. package/lib/__tests__/developerTestAccounts.test.js +3 -3
  125. package/lib/__tests__/npm.test.js +1 -1
  126. package/lib/__tests__/oauth.test.js +4 -4
  127. package/lib/__tests__/process.test.js +10 -5
  128. package/lib/__tests__/sandboxSync.test.js +8 -8
  129. package/lib/__tests__/sandboxes.test.js +8 -8
  130. package/lib/__tests__/serverlessLogs.test.js +1 -1
  131. package/lib/__tests__/usageTracking.test.js +5 -5
  132. package/lib/__tests__/validation.test.js +2 -1
  133. package/lib/__tests__/yargsUtils.test.js +83 -9
  134. package/lib/app/__tests__/migrate.test.js +19 -56
  135. package/lib/app/__tests__/migrate_legacy.test.js +1 -1
  136. package/lib/app/migrate.d.ts +2 -8
  137. package/lib/app/migrate.js +5 -80
  138. package/lib/app/migrate_legacy.js +20 -24
  139. package/lib/buildAccount.js +25 -57
  140. package/lib/commonOpts.d.ts +1 -1
  141. package/lib/commonOpts.js +25 -22
  142. package/lib/configOptions.js +7 -0
  143. package/lib/constants.d.ts +13 -0
  144. package/lib/constants.js +17 -0
  145. package/lib/dependencyManagement.js +9 -27
  146. package/lib/developerTestAccounts.js +9 -23
  147. package/lib/doctor/Diagnosis.js +11 -23
  148. package/lib/doctor/DiagnosticInfoBuilder.js +12 -11
  149. package/lib/doctor/Doctor.js +42 -90
  150. package/lib/doctor/__tests__/Doctor.test.js +4 -4
  151. package/lib/errorHandlers/index.js +12 -20
  152. package/lib/errorHandlers/suppressError.js +10 -17
  153. package/lib/lang.js +6 -5
  154. package/lib/links.d.ts +1 -0
  155. package/lib/links.js +14 -7
  156. package/lib/mcp/setup.js +1 -1
  157. package/lib/middleware/__test__/commandTargetingUtils.test.d.ts +1 -0
  158. package/lib/middleware/__test__/commandTargetingUtils.test.js +99 -0
  159. package/lib/middleware/__test__/configMiddleware.test.js +11 -11
  160. package/lib/middleware/__test__/yargsChecksMiddleware.test.js +6 -8
  161. package/lib/middleware/commandTargetingUtils.d.ts +8 -0
  162. package/lib/middleware/commandTargetingUtils.js +78 -0
  163. package/lib/middleware/configMiddleware.d.ts +1 -1
  164. package/lib/middleware/configMiddleware.js +21 -81
  165. package/lib/middleware/fireAlarmMiddleware.js +15 -5
  166. package/lib/middleware/gitMiddleware.js +5 -1
  167. package/lib/middleware/notificationsMiddleware.js +5 -11
  168. package/lib/middleware/yargsChecksMiddleware.js +6 -9
  169. package/lib/npm.js +2 -2
  170. package/lib/oauth.js +5 -5
  171. package/lib/process.js +5 -4
  172. package/lib/projects/__tests__/AppDevModeInterface.test.js +2 -0
  173. package/lib/projects/__tests__/LocalDevProcess.test.js +227 -16
  174. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +86 -60
  175. package/lib/projects/__tests__/deploy.test.js +71 -6
  176. package/lib/projects/__tests__/localDevProjectHelpers.test.js +6 -2
  177. package/lib/projects/__tests__/platformVersion.test.js +8 -8
  178. package/lib/projects/__tests__/projects.test.js +12 -12
  179. package/lib/projects/__tests__/structure.test.js +3 -3
  180. package/lib/projects/__tests__/upload.test.d.ts +1 -0
  181. package/lib/projects/__tests__/upload.test.js +82 -0
  182. package/lib/projects/add/__tests__/legacyAddComponent.test.js +6 -6
  183. package/lib/projects/add/__tests__/v3AddComponent.test.js +4 -4
  184. package/lib/projects/create/__tests__/legacy.test.js +5 -5
  185. package/lib/projects/create/__tests__/v3.test.js +80 -5
  186. package/lib/projects/create/index.js +2 -2
  187. package/lib/projects/create/legacy.js +2 -2
  188. package/lib/projects/create/v3.js +10 -8
  189. package/lib/projects/localDev/AppDevModeInterface.d.ts +2 -0
  190. package/lib/projects/localDev/AppDevModeInterface.js +21 -12
  191. package/lib/projects/localDev/LocalDevLogger.d.ts +4 -0
  192. package/lib/projects/localDev/LocalDevLogger.js +27 -6
  193. package/lib/projects/localDev/LocalDevManager.js +4 -5
  194. package/lib/projects/localDev/LocalDevProcess.d.ts +7 -5
  195. package/lib/projects/localDev/LocalDevProcess.js +90 -19
  196. package/lib/projects/localDev/LocalDevState.d.ts +9 -8
  197. package/lib/projects/localDev/LocalDevState.js +18 -17
  198. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +2 -1
  199. package/lib/projects/localDev/LocalDevWebsocketServer.js +62 -33
  200. package/lib/projects/localDev/helpers/project.d.ts +1 -0
  201. package/lib/projects/localDev/helpers/project.js +42 -1
  202. package/lib/projects/localDev/localDevWebsocketServerUtils.d.ts +4 -0
  203. package/lib/projects/localDev/localDevWebsocketServerUtils.js +10 -0
  204. package/lib/projects/platformVersion.d.ts +1 -1
  205. package/lib/projects/platformVersion.js +1 -1
  206. package/lib/projects/pollProjectBuildAndDeploy.js +4 -4
  207. package/lib/projects/structure.js +6 -6
  208. package/lib/projects/upload.d.ts +1 -1
  209. package/lib/projects/upload.js +17 -8
  210. package/lib/prompts/accountNamePrompt.js +14 -19
  211. package/lib/prompts/accountsPrompt.js +2 -2
  212. package/lib/prompts/cmsFieldPrompt.js +2 -2
  213. package/lib/prompts/createApiSamplePrompt.js +5 -5
  214. package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +10 -1
  215. package/lib/prompts/createFunctionPrompt.js +14 -14
  216. package/lib/prompts/createModulePrompt.js +9 -9
  217. package/lib/prompts/createTemplatePrompt.js +2 -2
  218. package/lib/prompts/downloadProjectPrompt.js +5 -8
  219. package/lib/prompts/personalAccessKeyPrompt.js +3 -3
  220. package/lib/prompts/previewPrompt.js +6 -6
  221. package/lib/prompts/projectAddPrompt.js +8 -1
  222. package/lib/prompts/projectDevTargetAccountPrompt.js +20 -32
  223. package/lib/prompts/projectNamePrompt.js +4 -8
  224. package/lib/prompts/projectsLogsPrompt.js +2 -4
  225. package/lib/prompts/promptUtils.js +30 -9
  226. package/lib/prompts/sandboxesPrompt.js +7 -7
  227. package/lib/prompts/secretPrompt.js +3 -3
  228. package/lib/prompts/selectAppPrompt.js +3 -3
  229. package/lib/prompts/selectHubDBTablePrompt.js +9 -13
  230. package/lib/prompts/selectProjectTemplatePrompt.js +2 -0
  231. package/lib/prompts/selectPublicAppForMigrationPrompt.js +15 -19
  232. package/lib/prompts/setAsDefaultAccountPrompt.js +4 -8
  233. package/lib/prompts/uploadPrompt.js +5 -5
  234. package/lib/sandboxSync.js +24 -41
  235. package/lib/sandboxes.js +19 -47
  236. package/lib/schema.js +3 -3
  237. package/lib/serverlessLogs.js +11 -13
  238. package/lib/theme/__tests__/migrate.test.d.ts +1 -0
  239. package/lib/theme/__tests__/migrate.test.js +233 -0
  240. package/lib/theme/migrate.d.ts +13 -0
  241. package/lib/theme/migrate.js +90 -0
  242. package/lib/ui/SpinniesManager.d.ts +2 -0
  243. package/lib/ui/SpinniesManager.js +7 -0
  244. package/lib/ui/boxen.js +1 -2
  245. package/lib/ui/git.js +13 -10
  246. package/lib/ui/index.js +38 -37
  247. package/lib/ui/serverlessFunctionLogs.js +9 -7
  248. package/lib/ui/uiMessages.d.ts +68 -0
  249. package/lib/ui/uiMessages.js +71 -0
  250. package/lib/usageTracking.js +8 -8
  251. package/lib/validation.js +20 -23
  252. package/lib/yargsUtils.d.ts +1 -1
  253. package/lib/yargsUtils.js +12 -5
  254. package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +2 -2
  255. package/mcp-server/tools/index.js +4 -0
  256. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +2 -2
  257. package/mcp-server/tools/project/CreateProjectTool.d.ts +2 -2
  258. package/mcp-server/tools/project/DocsSearchTool.d.ts +4 -1
  259. package/mcp-server/tools/project/DocsSearchTool.js +5 -5
  260. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +23 -0
  261. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +68 -0
  262. package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +11 -0
  263. package/mcp-server/tools/project/GetApplicationInfoTool.js +49 -0
  264. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +4 -1
  265. package/mcp-server/tools/project/GetConfigValuesTool.js +12 -6
  266. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +2 -2
  267. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +12 -10
  268. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.d.ts +1 -0
  269. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +169 -0
  270. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.d.ts +1 -0
  271. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +115 -0
  272. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +8 -7
  273. package/mcp-server/utils/__tests__/cliConfig.test.d.ts +1 -0
  274. package/mcp-server/utils/__tests__/cliConfig.test.js +110 -0
  275. package/mcp-server/utils/cliConfig.d.ts +1 -0
  276. package/mcp-server/utils/cliConfig.js +12 -0
  277. package/mcp-server/utils/toolUsageTracking.js +2 -2
  278. package/package.json +4 -4
  279. package/types/LocalDev.d.ts +19 -3
  280. package/ui/index.js +1 -1
  281. package/lib/middleware/__test__/utils.test.js +0 -51
  282. package/lib/middleware/utils.d.ts +0 -8
  283. package/lib/middleware/utils.js +0 -14
  284. /package/{lib/middleware/__test__/utils.test.d.ts → commands/project/__tests__/validate.test.d.ts} +0 -0
@@ -1,7 +1,10 @@
1
1
  import path from 'path';
2
2
  import { translateForLocalDev } from '@hubspot/project-parsing-lib';
3
3
  import { handleProjectUpload } from '../upload.js';
4
+ import { handleProjectDeploy } from '../deploy.js';
4
5
  import { getProjectConfig } from '../config.js';
6
+ import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
7
+ import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
5
8
  import LocalDevProcess from '../localDev/LocalDevProcess.js';
6
9
  import LocalDevLogger from '../localDev/LocalDevLogger.js';
7
10
  import DevServerManagerV2 from '../localDev/DevServerManagerV2.js';
@@ -19,7 +22,10 @@ vi.mock('@hubspot/ui-extensions-dev-server', () => ({
19
22
  vi.mock('open');
20
23
  vi.mock('@hubspot/project-parsing-lib');
21
24
  vi.mock('../upload');
25
+ vi.mock('../deploy');
22
26
  vi.mock('../config');
27
+ vi.mock('@hubspot/local-dev-lib/api/projects');
28
+ vi.mock('@hubspot/local-dev-lib/errors/index');
23
29
  vi.mock('../localDev/LocalDevLogger');
24
30
  vi.mock('../localDev/DevServerManagerV2');
25
31
  // Tests for LocalDevProcess and LocalDevState
@@ -37,11 +43,35 @@ describe('LocalDevProcess', () => {
37
43
  projectConfig: mockProjectConfig,
38
44
  targetProjectAccountId: 123,
39
45
  targetTestingAccountId: 456,
40
- projectId: 789,
46
+ projectData: {
47
+ id: 789,
48
+ name: 'test-project',
49
+ portalId: 123,
50
+ createdAt: 0,
51
+ deletedAt: 0,
52
+ isLocked: false,
53
+ updatedAt: 0,
54
+ latestBuild: {
55
+ activitySource: { type: 'HUBSPOT_USER', userId: 456 },
56
+ buildId: 123,
57
+ createdAt: '2023-01-01T00:00:00Z',
58
+ deployableState: 'DEPLOYABLE',
59
+ deployStatusTaskLocator: { id: 'task-123', links: [] },
60
+ enqueuedAt: '2023-01-01T00:00:00Z',
61
+ finishedAt: '2023-01-01T00:05:00Z',
62
+ isAutoDeployEnabled: false,
63
+ portalId: 123,
64
+ projectName: 'test-project',
65
+ startedAt: '2023-01-01T00:01:00Z',
66
+ status: 'SUCCESS',
67
+ subbuildStatuses: [],
68
+ uploadMessage: 'Build completed',
69
+ autoDeployId: 0,
70
+ },
71
+ },
41
72
  initialProjectNodes: {},
42
73
  initialProjectProfileData: {},
43
74
  env: ENVIRONMENTS.PROD,
44
- projectName: 'test-project',
45
75
  };
46
76
  beforeEach(() => {
47
77
  vi.clearAllMocks();
@@ -62,6 +92,9 @@ describe('LocalDevProcess', () => {
62
92
  uploadSuccess: vi.fn(),
63
93
  fileChangeError: vi.fn(),
64
94
  uploadWarning: vi.fn(),
95
+ deployInitiated: vi.fn(),
96
+ deployError: vi.fn(),
97
+ deploySuccess: vi.fn(),
65
98
  };
66
99
  mockDevServerManager = {
67
100
  setup: vi.fn().mockResolvedValue(undefined),
@@ -72,6 +105,8 @@ describe('LocalDevProcess', () => {
72
105
  // Mock constructors
73
106
  LocalDevLogger.mockImplementation(() => mockLocalDevLogger);
74
107
  DevServerManagerV2.mockImplementation(() => mockDevServerManager);
108
+ // Mock external functions
109
+ isHubSpotHttpError.mockReturnValue(false);
75
110
  // Create process instance
76
111
  process = new LocalDevProcess(mockOptions);
77
112
  // Mock process.exit
@@ -141,9 +176,14 @@ describe('LocalDevProcess', () => {
141
176
  handleProjectUpload.mockResolvedValue({
142
177
  uploadError: new Error('Upload failed'),
143
178
  });
144
- const success = await process.uploadProject();
179
+ const result = await process.uploadProject();
145
180
  expect(mockLocalDevLogger.uploadError).toHaveBeenCalledWith(new Error('Upload failed'));
146
- expect(success).toBe(false);
181
+ expect(result).toEqual({
182
+ uploadSuccess: false,
183
+ buildSuccess: false,
184
+ deploySuccess: false,
185
+ deployId: undefined,
186
+ });
147
187
  });
148
188
  it('should handle successful upload', async () => {
149
189
  await process.handleConfigFileChange();
@@ -152,38 +192,120 @@ describe('LocalDevProcess', () => {
152
192
  });
153
193
  handleProjectUpload.mockResolvedValue({
154
194
  uploadError: null,
195
+ result: {
196
+ deployResult: {
197
+ id: 'deploy-123',
198
+ deployId: 123,
199
+ status: 'SUCCESS',
200
+ },
201
+ },
202
+ });
203
+ fetchProject.mockResolvedValue({
204
+ data: {
205
+ id: 789,
206
+ name: 'test-project',
207
+ portalId: 123,
208
+ createdAt: 0,
209
+ deletedAt: 0,
210
+ isLocked: false,
211
+ updatedAt: 0,
212
+ latestBuild: { id: 'build-1', status: 'SUCCESS' },
213
+ deployedBuild: { id: 'build-1', status: 'SUCCESS' },
214
+ },
155
215
  });
156
- const success = await process.uploadProject();
216
+ const result = await process.uploadProject();
217
+ expect(fetchProject).toHaveBeenCalledWith(mockOptions.targetProjectAccountId, mockOptions.projectConfig.name);
157
218
  expect(mockLocalDevLogger.uploadSuccess).toHaveBeenCalled();
158
219
  // @ts-expect-error accessing private property for testing
159
220
  expect(process.state.uploadWarnings.size).toBe(0);
160
- expect(success).toBe(true);
221
+ expect(result).toEqual({
222
+ uploadSuccess: true,
223
+ buildSuccess: true,
224
+ deploySuccess: true,
225
+ deployId: 123,
226
+ });
161
227
  });
162
- it('should reset projectNodesAtLastUpload', async () => {
163
- const mockNodes = { node1: { uid: 'node1' } };
164
- const initialProjectNodes = { existingNode: { uid: 'existingNode' } };
228
+ it('should reset projectNodesAtLastUpload if deploy is successful', async () => {
229
+ const mockInitialNodes = {
230
+ node1: {
231
+ uid: 'node1',
232
+ componentType: 'APP',
233
+ localDev: {
234
+ componentRoot: '/test/path',
235
+ componentConfigPath: '/test/path/config.json',
236
+ configUpdatedSinceLastUpload: false,
237
+ },
238
+ componentDeps: {},
239
+ metaFilePath: '/test/path',
240
+ config: { name: 'Node 1' },
241
+ files: [],
242
+ },
243
+ };
244
+ const mockNewNodes = {
245
+ node1: {
246
+ uid: 'node2',
247
+ componentType: 'APP',
248
+ localDev: {
249
+ componentRoot: '/test/path',
250
+ componentConfigPath: '/test/path/config.json',
251
+ configUpdatedSinceLastUpload: false,
252
+ },
253
+ componentDeps: {},
254
+ metaFilePath: '/test/path',
255
+ config: { name: 'Node 2' },
256
+ files: [],
257
+ },
258
+ };
165
259
  // @ts-expect-error accessing private property for testing
166
- process.state._projectNodesAtLastUpload = initialProjectNodes;
260
+ process.state.projectNodesAtLastDeploy = mockInitialNodes;
167
261
  getProjectConfig.mockResolvedValue({
168
262
  projectConfig: mockOptions.projectConfig,
169
263
  });
170
264
  handleProjectUpload.mockResolvedValue({
171
265
  uploadError: null,
266
+ result: {
267
+ deployResult: {
268
+ id: 'deploy-123',
269
+ deployId: 456,
270
+ status: 'SUCCESS',
271
+ },
272
+ },
172
273
  });
173
274
  translateForLocalDev.mockResolvedValue({
174
- intermediateNodesIndexedByUid: mockNodes,
275
+ intermediateNodesIndexedByUid: mockNewNodes,
175
276
  });
176
- const success = await process.uploadProject();
177
- // Verify translateForLocalDev was called without projectNodesAtLastUpload option
277
+ fetchProject.mockResolvedValue({
278
+ data: {
279
+ id: 789,
280
+ name: 'test-project',
281
+ portalId: 123,
282
+ createdAt: 0,
283
+ deletedAt: 0,
284
+ isLocked: false,
285
+ updatedAt: 0,
286
+ latestBuild: { id: 'build-1', status: 'SUCCESS' },
287
+ deployedBuild: { id: 'build-1', status: 'SUCCESS' },
288
+ },
289
+ });
290
+ const result = await process.uploadProject();
291
+ // Verify translateForLocalDev was called during updateProjectNodesAfterDeploy
178
292
  expect(translateForLocalDev).toHaveBeenCalledWith({
179
293
  projectSourceDir: path.join(mockOptions.projectDir, mockOptions.projectConfig.srcDir),
180
294
  platformVersion: mockOptions.projectConfig.platformVersion,
181
295
  accountId: mockOptions.targetProjectAccountId,
182
- }, { projectNodesAtLastUpload: undefined });
296
+ }, {
297
+ profile: undefined,
298
+ projectNodesAtLastUpload: undefined,
299
+ });
183
300
  // Verify projectNodesAtLastUpload was reset to the new nodes
184
301
  // @ts-expect-error accessing private property for testing
185
- expect(process.state.projectNodesAtLastUpload).toEqual(mockNodes);
186
- expect(success).toBe(true);
302
+ expect(process.state.projectNodesAtLastDeploy).toEqual(mockNewNodes);
303
+ expect(result).toEqual({
304
+ uploadSuccess: true,
305
+ buildSuccess: true,
306
+ deploySuccess: true,
307
+ deployId: 456,
308
+ });
187
309
  });
188
310
  });
189
311
  describe('handleFileChange()', () => {
@@ -252,4 +374,93 @@ describe('LocalDevProcess', () => {
252
374
  expect(listener).toHaveBeenCalledTimes(1);
253
375
  });
254
376
  });
377
+ describe('deployLatestBuild()', () => {
378
+ beforeEach(() => {
379
+ vi.clearAllMocks();
380
+ });
381
+ it('should successfully deploy latest build', async () => {
382
+ const mockDeploy = {
383
+ deployId: 456,
384
+ buildId: 123,
385
+ status: 'SUCCESS',
386
+ enqueuedAt: '2023-01-01T00:00:00Z',
387
+ startedAt: '2023-01-01T00:01:00Z',
388
+ finishedAt: '2023-01-01T00:05:00Z',
389
+ portalId: 123,
390
+ projectName: 'test-project',
391
+ userId: 789,
392
+ source: 'HUBSPOT_USER',
393
+ subdeployStatuses: [],
394
+ };
395
+ handleProjectDeploy.mockResolvedValue(mockDeploy);
396
+ const result = await process.deployLatestBuild();
397
+ expect(mockLocalDevLogger.deployInitiated).toHaveBeenCalled();
398
+ expect(handleProjectDeploy).toHaveBeenCalledWith(123, // targetProjectAccountId
399
+ 'test-project', // projectName
400
+ 123, // buildId
401
+ true, // useV3Api
402
+ false // force
403
+ );
404
+ expect(mockLocalDevLogger.deploySuccess).toHaveBeenCalled();
405
+ expect(result).toEqual({
406
+ success: true,
407
+ deployId: 456,
408
+ });
409
+ });
410
+ it('should deploy with force parameter', async () => {
411
+ const mockDeploy = {
412
+ deployId: 456,
413
+ buildId: 123,
414
+ status: 'SUCCESS',
415
+ enqueuedAt: '2023-01-01T00:00:00Z',
416
+ startedAt: '2023-01-01T00:01:00Z',
417
+ finishedAt: '2023-01-01T00:05:00Z',
418
+ portalId: 123,
419
+ projectName: 'test-project',
420
+ userId: 789,
421
+ source: 'HUBSPOT_USER',
422
+ subdeployStatuses: [],
423
+ };
424
+ handleProjectDeploy.mockResolvedValue(mockDeploy);
425
+ const result = await process.deployLatestBuild(true);
426
+ expect(handleProjectDeploy).toHaveBeenCalledWith(123, // targetProjectAccountId
427
+ 'test-project', // projectName
428
+ 123, // buildId
429
+ true, // useV3Api
430
+ true // force
431
+ );
432
+ expect(result).toEqual({
433
+ success: true,
434
+ deployId: 456,
435
+ });
436
+ });
437
+ it('should return error when no build exists', async () => {
438
+ // Create a process without latestBuild
439
+ const optionsWithoutBuild = {
440
+ ...mockOptions,
441
+ projectData: {
442
+ ...mockOptions.projectData,
443
+ latestBuild: undefined,
444
+ },
445
+ };
446
+ const processWithoutBuild = new LocalDevProcess(optionsWithoutBuild);
447
+ const result = await processWithoutBuild.deployLatestBuild();
448
+ expect(mockLocalDevLogger.deployInitiated).toHaveBeenCalled();
449
+ expect(mockLocalDevLogger.deployError).toHaveBeenCalledWith('Error deploying project. No build was found to deploy.');
450
+ expect(result).toEqual({
451
+ success: false,
452
+ });
453
+ expect(handleProjectDeploy).not.toHaveBeenCalled();
454
+ });
455
+ it('should handle deploy failure when no deploy object returned', async () => {
456
+ handleProjectDeploy.mockResolvedValue(undefined);
457
+ const result = await process.deployLatestBuild();
458
+ expect(mockLocalDevLogger.deployInitiated).toHaveBeenCalled();
459
+ expect(handleProjectDeploy).toHaveBeenCalled();
460
+ expect(result).toEqual({
461
+ success: false,
462
+ });
463
+ expect(mockLocalDevLogger.deploySuccess).not.toHaveBeenCalled();
464
+ });
465
+ });
255
466
  });
@@ -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' },
@@ -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,11 +308,11 @@ 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(3); // projectNodes, appData, and uploadWarnings listeners
312
+ expect(closeCallbacks2).toHaveLength(3); // projectNodes, appData, 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)
315
+ // Should have removed listeners for first connection (3 listeners: projectNodes, appData, and uploadWarnings)
292
316
  expect(mockLocalDevProcess.removeStateListener).toHaveBeenCalledTimes(3);
293
317
  // Simulate second connection closing
294
318
  closeCallbacks2.forEach(callback => callback());
@@ -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',