@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
@@ -11,7 +11,7 @@ vi.mock('@hubspot/ui-extensions-dev-server', () => {
11
11
  };
12
12
  });
13
13
  import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
14
- import { fetchPublicAppsForPortal, fetchPublicAppProductionInstallCounts, installStaticAuthAppOnTestAccount, } from '@hubspot/local-dev-lib/api/appsDev';
14
+ import { fetchAppMetadataByUid, fetchPublicAppProductionInstallCounts, installStaticAuthAppOnTestAccount, } from '@hubspot/local-dev-lib/api/appsDev';
15
15
  import { DevModeUnifiedInterface as UIEDevModeInterface } from '@hubspot/ui-extensions-dev-server';
16
16
  import { requestPorts } from '@hubspot/local-dev-lib/portManager';
17
17
  import { getAccountConfig } from '@hubspot/local-dev-lib/config';
@@ -67,6 +67,8 @@ describe('AppDevModeInterface', () => {
67
67
  componentRoot: '/test/path',
68
68
  componentConfigPath: '/test/path/config.json',
69
69
  configUpdatedSinceLastUpload: false,
70
+ removed: false,
71
+ parsingErrors: [],
70
72
  },
71
73
  componentDeps: {},
72
74
  metaFilePath: '/test/path',
@@ -107,8 +109,8 @@ describe('AppDevModeInterface', () => {
107
109
  LocalDevState.mockImplementation(() => mockLocalDevState);
108
110
  LocalDevLogger.mockImplementation(() => mockLocalDevLogger);
109
111
  // Mock external dependencies
110
- fetchPublicAppsForPortal.mockResolvedValue({
111
- data: { results: [mockPublicApp] },
112
+ fetchAppMetadataByUid.mockResolvedValue({
113
+ data: mockPublicApp,
112
114
  });
113
115
  fetchPublicAppProductionInstallCounts.mockResolvedValue({
114
116
  data: { uniquePortalInstallCount: 5 },
@@ -127,7 +129,16 @@ describe('AppDevModeInterface', () => {
127
129
  getOauthAppInstallUrl.mockReturnValue('http://oauth-install-url');
128
130
  getStaticAuthAppInstallUrl.mockReturnValue('http://static-install-url');
129
131
  installAppAutoPrompt.mockResolvedValue(true);
130
- installAppBrowserPrompt.mockResolvedValue(undefined);
132
+ installAppBrowserPrompt.mockImplementation(async () => {
133
+ setTimeout(() => {
134
+ const addListenerCall = mockLocalDevState.addListener.mock.calls.find(call => call[0] === 'devServerMessage');
135
+ if (addListenerCall) {
136
+ const callback = addListenerCall[1];
137
+ callback(LOCAL_DEV_SERVER_MESSAGE_TYPES.STATIC_AUTH_APP_INSTALL_SUCCESS);
138
+ }
139
+ }, 0);
140
+ return undefined;
141
+ });
131
142
  confirmPrompt.mockResolvedValue(true);
132
143
  installStaticAuthAppOnTestAccount.mockResolvedValue(undefined);
133
144
  // Mock process.exit
@@ -189,12 +200,12 @@ describe('AppDevModeInterface', () => {
189
200
  it('should return early if no app node exists', async () => {
190
201
  mockLocalDevState.projectNodes = {};
191
202
  await appDevModeInterface.setup({});
192
- expect(fetchPublicAppsForPortal).not.toHaveBeenCalled();
203
+ expect(fetchAppMetadataByUid).not.toHaveBeenCalled();
193
204
  expect(UIEDevModeInterface.setup).not.toHaveBeenCalled();
194
205
  });
195
206
  it('should setup successfully with private app', async () => {
196
207
  await appDevModeInterface.setup({});
197
- expect(fetchPublicAppsForPortal).toHaveBeenCalledWith(12345);
208
+ expect(fetchAppMetadataByUid).toHaveBeenCalledWith('test-app-uid', 12345);
198
209
  expect(fetchPublicAppProductionInstallCounts).toHaveBeenCalledWith(123, 12345);
199
210
  expect(fetchAppInstallationData).toHaveBeenCalledWith(67890, 999, 'test-app-uid', ['test-scope'], []);
200
211
  expect(UIEDevModeInterface.setup).toHaveBeenCalled();
@@ -240,21 +251,16 @@ describe('AppDevModeInterface', () => {
240
251
  await newAppDevModeInterface.setup({});
241
252
  expect(process.exit).toHaveBeenCalledWith(0);
242
253
  });
243
- // @TODO: Restore test account auto install functionality
244
- // it('should auto-install static auth app on test account', async () => {
245
- // (fetchAppInstallationData as Mock).mockResolvedValue({
246
- // data: {
247
- // isInstalledWithScopeGroups: false,
248
- // previouslyAuthorizedScopeGroups: [],
249
- // },
250
- // });
251
- // await appDevModeInterface.setup({});
252
- // expect(installStaticAuthAppOnTestAccount).toHaveBeenCalledWith(
253
- // 123,
254
- // 67890,
255
- // [1, 2, 3]
256
- // );
257
- // });
254
+ it('should auto-install static auth app on test account', async () => {
255
+ fetchAppInstallationData.mockResolvedValue({
256
+ data: {
257
+ isInstalledWithScopeGroups: false,
258
+ previouslyAuthorizedScopeGroups: [],
259
+ },
260
+ });
261
+ await appDevModeInterface.setup({});
262
+ expect(installStaticAuthAppOnTestAccount).toHaveBeenCalledWith(123, 67890, [1, 2, 3]);
263
+ });
258
264
  it('should open browser for OAuth app installation', async () => {
259
265
  const oauthAppNode = {
260
266
  ...mockAppNode,
@@ -293,81 +299,47 @@ describe('AppDevModeInterface', () => {
293
299
  },
294
300
  });
295
301
  await appDevModeInterface.setup({});
296
- expect(installAppBrowserPrompt).toHaveBeenCalledWith('http://static-install-url', true, {
297
- appUid: 'test-app-uid',
298
- projectAccountId: 12345,
299
- projectName: 'test-project',
300
- testingAccountId: 67890,
301
- });
302
+ expect(installAppBrowserPrompt).toHaveBeenCalledWith('http://static-install-url', true);
302
303
  });
303
304
  it('should handle errors during setup', async () => {
304
305
  const error = new Error('Setup failed');
305
- fetchPublicAppsForPortal.mockRejectedValue(error);
306
+ fetchAppMetadataByUid.mockRejectedValue(error);
306
307
  await appDevModeInterface.setup({});
307
308
  expect(logError).toHaveBeenCalledWith(error);
308
309
  });
309
- it('should exit if app not found in portal', async () => {
310
- // Set up conditions for non-automatic installation to force getAppInstallUrl call
311
- getAccountConfig.mockReturnValue(null);
312
- // First call for fetchAppData succeeds
313
- fetchPublicAppsForPortal
314
- .mockResolvedValueOnce({
315
- data: { results: [mockPublicApp] },
316
- })
317
- // Second call for getAppInstallUrl fails (app not found)
318
- .mockResolvedValueOnce({
319
- data: { results: [] },
310
+ it('should exit if user declines auto-install', async () => {
311
+ // Set up conditions for automatic installation
312
+ getAccountConfig.mockReturnValue({
313
+ parentAccountId: 12345, // matches targetProjectAccountId
320
314
  });
315
+ isDeveloperTestAccount.mockReturnValue(true);
321
316
  fetchAppInstallationData.mockResolvedValue({
322
317
  data: {
323
318
  isInstalledWithScopeGroups: false,
324
319
  previouslyAuthorizedScopeGroups: [],
325
320
  },
326
321
  });
322
+ installAppAutoPrompt.mockResolvedValue(false);
323
+ // Create a new instance to trigger the exit during setup
324
+ const newAppDevModeInterface = new AppDevModeInterface({
325
+ localDevState: mockLocalDevState,
326
+ localDevLogger: mockLocalDevLogger,
327
+ });
327
328
  // The setup method catches the error, so we check that process.exit was called
329
+ await newAppDevModeInterface.setup({});
330
+ expect(process.exit).toHaveBeenCalledWith(0);
331
+ });
332
+ it('should fallback to browser install if auto-install fails', async () => {
333
+ fetchAppInstallationData.mockResolvedValue({
334
+ data: {
335
+ isInstalledWithScopeGroups: false,
336
+ previouslyAuthorizedScopeGroups: [],
337
+ },
338
+ });
339
+ installStaticAuthAppOnTestAccount.mockRejectedValue(new Error('Install failed'));
328
340
  await appDevModeInterface.setup({});
329
- expect(process.exit).toHaveBeenCalledWith(1);
330
- });
331
- // @TODO: Restore test account auto install functionality
332
- // it('should exit if user declines auto-install', async () => {
333
- // // Set up conditions for automatic installation
334
- // (getAccountConfig as Mock).mockReturnValue({
335
- // parentAccountId: 12345, // matches targetProjectAccountId
336
- // });
337
- // (isDeveloperTestAccount as Mock).mockReturnValue(true);
338
- // (fetchAppInstallationData as Mock).mockResolvedValue({
339
- // data: {
340
- // isInstalledWithScopeGroups: false,
341
- // previouslyAuthorizedScopeGroups: [],
342
- // },
343
- // });
344
- // (installAppAutoPrompt as Mock).mockResolvedValue(false);
345
- // // Create a new instance to trigger the exit during setup
346
- // const newAppDevModeInterface = new AppDevModeInterface({
347
- // localDevState: mockLocalDevState,
348
- // localDevLogger: mockLocalDevLogger,
349
- // });
350
- // // The setup method catches the error, so we check that process.exit was called
351
- // await newAppDevModeInterface.setup({});
352
- // expect(process.exit).toHaveBeenCalledWith(0);
353
- // });
354
- // @TODO: Restore test account auto install functionality
355
- // it('should fallback to browser install if auto-install fails', async () => {
356
- // (fetchAppInstallationData as Mock).mockResolvedValue({
357
- // data: {
358
- // isInstalledWithScopeGroups: false,
359
- // previouslyAuthorizedScopeGroups: [],
360
- // },
361
- // });
362
- // (installStaticAuthAppOnTestAccount as Mock).mockRejectedValue(
363
- // new Error('Install failed')
364
- // );
365
- // await appDevModeInterface.setup({});
366
- // expect(installAppBrowserPrompt).toHaveBeenCalledWith(
367
- // 'http://static-install-url',
368
- // false
369
- // );
370
- // });
341
+ expect(installAppBrowserPrompt).toHaveBeenCalledWith('http://static-install-url', false);
342
+ });
371
343
  });
372
344
  describe('start()', () => {
373
345
  it('should return early if no app node exists', async () => {
@@ -424,14 +396,21 @@ describe('AppDevModeInterface', () => {
424
396
  // Reset mocks to ensure clean state
425
397
  vi.clearAllMocks();
426
398
  // Set up basic mocks
427
- fetchPublicAppsForPortal.mockResolvedValue({
428
- data: { results: [mockPublicApp] },
399
+ fetchAppMetadataByUid.mockResolvedValue({
400
+ data: mockPublicApp,
429
401
  });
430
402
  fetchPublicAppProductionInstallCounts.mockResolvedValue({
431
403
  data: { uniquePortalInstallCount: 5 },
432
404
  });
433
405
  getStaticAuthAppInstallUrl.mockReturnValue('http://static-install-url');
434
- installAppBrowserPrompt.mockResolvedValue(undefined);
406
+ installAppBrowserPrompt.mockImplementation(async () => {
407
+ const addListenerCall = mockLocalDevState.addListener.mock.calls.find(call => call[0] === 'devServerMessage');
408
+ if (addListenerCall) {
409
+ const callback = addListenerCall[1];
410
+ callback(LOCAL_DEV_SERVER_MESSAGE_TYPES.STATIC_AUTH_APP_INSTALL_SUCCESS);
411
+ }
412
+ return undefined;
413
+ });
435
414
  // Reset the mock LocalDevState
436
415
  mockLocalDevState.getAppDataByUid = vi.fn().mockReturnValue(mockAppData);
437
416
  mockLocalDevState.setAppDataForUid = vi.fn();
@@ -457,14 +436,23 @@ describe('AppDevModeInterface', () => {
457
436
  // Reset mocks to ensure clean state
458
437
  vi.clearAllMocks();
459
438
  // Set up basic mocks
460
- fetchPublicAppsForPortal.mockResolvedValue({
461
- data: { results: [mockPublicApp] },
439
+ fetchAppMetadataByUid.mockResolvedValue({
440
+ data: mockPublicApp,
462
441
  });
463
442
  fetchPublicAppProductionInstallCounts.mockResolvedValue({
464
443
  data: { uniquePortalInstallCount: 5 },
465
444
  });
466
445
  getOauthAppInstallUrl.mockReturnValue('http://oauth-install-url');
467
- installAppBrowserPrompt.mockResolvedValue(undefined);
446
+ installAppBrowserPrompt.mockImplementation(async () => {
447
+ setTimeout(() => {
448
+ const addListenerCall = mockLocalDevState.addListener.mock.calls.find(call => call[0] === 'devServerMessage');
449
+ if (addListenerCall) {
450
+ const callback = addListenerCall[1];
451
+ callback(LOCAL_DEV_SERVER_MESSAGE_TYPES.STATIC_AUTH_APP_INSTALL_SUCCESS);
452
+ }
453
+ }, 0);
454
+ return undefined;
455
+ });
468
456
  // Reset the mock LocalDevState
469
457
  mockLocalDevState.getAppDataByUid = vi.fn().mockReturnValue(mockAppData);
470
458
  mockLocalDevState.setAppDataForUid = vi.fn();
@@ -501,14 +489,23 @@ describe('AppDevModeInterface', () => {
501
489
  // Reset mocks to ensure clean state
502
490
  vi.clearAllMocks();
503
491
  // Set up basic mocks
504
- fetchPublicAppsForPortal.mockResolvedValue({
505
- data: { results: [mockPublicApp] },
492
+ fetchAppMetadataByUid.mockResolvedValue({
493
+ data: mockPublicApp,
506
494
  });
507
495
  fetchPublicAppProductionInstallCounts.mockResolvedValue({
508
496
  data: { uniquePortalInstallCount: 5 },
509
497
  });
510
498
  getStaticAuthAppInstallUrl.mockReturnValue('http://static-install-url');
511
- installAppBrowserPrompt.mockResolvedValue(undefined);
499
+ installAppBrowserPrompt.mockImplementation(async () => {
500
+ setTimeout(() => {
501
+ const addListenerCall = mockLocalDevState.addListener.mock.calls.find(call => call[0] === 'devServerMessage');
502
+ if (addListenerCall) {
503
+ const callback = addListenerCall[1];
504
+ callback(LOCAL_DEV_SERVER_MESSAGE_TYPES.STATIC_AUTH_APP_INSTALL_SUCCESS);
505
+ }
506
+ }, 0);
507
+ return undefined;
508
+ });
512
509
  // Reset the mock LocalDevState
513
510
  mockLocalDevState.getAppDataByUid = vi.fn().mockReturnValue(mockAppData);
514
511
  mockLocalDevState.setAppDataForUid = vi.fn();
@@ -1,10 +1,13 @@
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
- import DevServerManagerV2 from '../localDev/DevServerManagerV2.js';
10
+ import DevServerManager from '../localDev/DevServerManager.js';
8
11
  import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
9
12
  import { vi } from 'vitest';
10
13
  // Mock @hubspot/ui-extensions-dev-server
@@ -19,9 +22,12 @@ 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
- vi.mock('../localDev/DevServerManagerV2');
30
+ vi.mock('../localDev/DevServerManager');
25
31
  // Tests for LocalDevProcess and LocalDevState
26
32
  describe('LocalDevProcess', () => {
27
33
  let mockLocalDevLogger;
@@ -37,11 +43,36 @@ 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
+ platformVersion: '2025.2',
71
+ },
72
+ },
41
73
  initialProjectNodes: {},
42
74
  initialProjectProfileData: {},
43
75
  env: ENVIRONMENTS.PROD,
44
- projectName: 'test-project',
45
76
  };
46
77
  beforeEach(() => {
47
78
  vi.clearAllMocks();
@@ -62,6 +93,9 @@ describe('LocalDevProcess', () => {
62
93
  uploadSuccess: vi.fn(),
63
94
  fileChangeError: vi.fn(),
64
95
  uploadWarning: vi.fn(),
96
+ deployInitiated: vi.fn(),
97
+ deployError: vi.fn(),
98
+ deploySuccess: vi.fn(),
65
99
  };
66
100
  mockDevServerManager = {
67
101
  setup: vi.fn().mockResolvedValue(undefined),
@@ -71,7 +105,9 @@ describe('LocalDevProcess', () => {
71
105
  };
72
106
  // Mock constructors
73
107
  LocalDevLogger.mockImplementation(() => mockLocalDevLogger);
74
- DevServerManagerV2.mockImplementation(() => mockDevServerManager);
108
+ DevServerManager.mockImplementation(() => mockDevServerManager);
109
+ // Mock external functions
110
+ isHubSpotHttpError.mockReturnValue(false);
75
111
  // Create process instance
76
112
  process = new LocalDevProcess(mockOptions);
77
113
  // Mock process.exit
@@ -141,9 +177,14 @@ describe('LocalDevProcess', () => {
141
177
  handleProjectUpload.mockResolvedValue({
142
178
  uploadError: new Error('Upload failed'),
143
179
  });
144
- const success = await process.uploadProject();
180
+ const result = await process.uploadProject();
145
181
  expect(mockLocalDevLogger.uploadError).toHaveBeenCalledWith(new Error('Upload failed'));
146
- expect(success).toBe(false);
182
+ expect(result).toEqual({
183
+ uploadSuccess: false,
184
+ buildSuccess: false,
185
+ deploySuccess: false,
186
+ deployId: undefined,
187
+ });
147
188
  });
148
189
  it('should handle successful upload', async () => {
149
190
  await process.handleConfigFileChange();
@@ -152,38 +193,120 @@ describe('LocalDevProcess', () => {
152
193
  });
153
194
  handleProjectUpload.mockResolvedValue({
154
195
  uploadError: null,
196
+ result: {
197
+ deployResult: {
198
+ id: 'deploy-123',
199
+ deployId: 123,
200
+ status: 'SUCCESS',
201
+ },
202
+ },
203
+ });
204
+ fetchProject.mockResolvedValue({
205
+ data: {
206
+ id: 789,
207
+ name: 'test-project',
208
+ portalId: 123,
209
+ createdAt: 0,
210
+ deletedAt: 0,
211
+ isLocked: false,
212
+ updatedAt: 0,
213
+ latestBuild: { id: 'build-1', status: 'SUCCESS' },
214
+ deployedBuild: { id: 'build-1', status: 'SUCCESS' },
215
+ },
155
216
  });
156
- const success = await process.uploadProject();
217
+ const result = await process.uploadProject();
218
+ expect(fetchProject).toHaveBeenCalledWith(mockOptions.targetProjectAccountId, mockOptions.projectConfig.name);
157
219
  expect(mockLocalDevLogger.uploadSuccess).toHaveBeenCalled();
158
220
  // @ts-expect-error accessing private property for testing
159
221
  expect(process.state.uploadWarnings.size).toBe(0);
160
- expect(success).toBe(true);
222
+ expect(result).toEqual({
223
+ uploadSuccess: true,
224
+ buildSuccess: true,
225
+ deploySuccess: true,
226
+ deployId: 123,
227
+ });
161
228
  });
162
- it('should reset projectNodesAtLastUpload', async () => {
163
- const mockNodes = { node1: { uid: 'node1' } };
164
- const initialProjectNodes = { existingNode: { uid: 'existingNode' } };
229
+ it('should reset projectNodesAtLastUpload if deploy is successful', async () => {
230
+ const mockInitialNodes = {
231
+ node1: {
232
+ uid: 'node1',
233
+ componentType: 'APP',
234
+ localDev: {
235
+ componentRoot: '/test/path',
236
+ componentConfigPath: '/test/path/config.json',
237
+ configUpdatedSinceLastUpload: false,
238
+ },
239
+ componentDeps: {},
240
+ metaFilePath: '/test/path',
241
+ config: { name: 'Node 1' },
242
+ files: [],
243
+ },
244
+ };
245
+ const mockNewNodes = {
246
+ node1: {
247
+ uid: 'node2',
248
+ componentType: 'APP',
249
+ localDev: {
250
+ componentRoot: '/test/path',
251
+ componentConfigPath: '/test/path/config.json',
252
+ configUpdatedSinceLastUpload: false,
253
+ },
254
+ componentDeps: {},
255
+ metaFilePath: '/test/path',
256
+ config: { name: 'Node 2' },
257
+ files: [],
258
+ },
259
+ };
165
260
  // @ts-expect-error accessing private property for testing
166
- process.state._projectNodesAtLastUpload = initialProjectNodes;
261
+ process.state.projectNodesAtLastDeploy = mockInitialNodes;
167
262
  getProjectConfig.mockResolvedValue({
168
263
  projectConfig: mockOptions.projectConfig,
169
264
  });
170
265
  handleProjectUpload.mockResolvedValue({
171
266
  uploadError: null,
267
+ result: {
268
+ deployResult: {
269
+ id: 'deploy-123',
270
+ deployId: 456,
271
+ status: 'SUCCESS',
272
+ },
273
+ },
172
274
  });
173
275
  translateForLocalDev.mockResolvedValue({
174
- intermediateNodesIndexedByUid: mockNodes,
276
+ intermediateNodesIndexedByUid: mockNewNodes,
175
277
  });
176
- const success = await process.uploadProject();
177
- // Verify translateForLocalDev was called without projectNodesAtLastUpload option
278
+ fetchProject.mockResolvedValue({
279
+ data: {
280
+ id: 789,
281
+ name: 'test-project',
282
+ portalId: 123,
283
+ createdAt: 0,
284
+ deletedAt: 0,
285
+ isLocked: false,
286
+ updatedAt: 0,
287
+ latestBuild: { id: 'build-1', status: 'SUCCESS' },
288
+ deployedBuild: { id: 'build-1', status: 'SUCCESS' },
289
+ },
290
+ });
291
+ const result = await process.uploadProject();
292
+ // Verify translateForLocalDev was called during updateProjectNodesAfterDeploy
178
293
  expect(translateForLocalDev).toHaveBeenCalledWith({
179
294
  projectSourceDir: path.join(mockOptions.projectDir, mockOptions.projectConfig.srcDir),
180
295
  platformVersion: mockOptions.projectConfig.platformVersion,
181
296
  accountId: mockOptions.targetProjectAccountId,
182
- }, { projectNodesAtLastUpload: undefined });
297
+ }, {
298
+ profile: undefined,
299
+ projectNodesAtLastUpload: undefined,
300
+ });
183
301
  // Verify projectNodesAtLastUpload was reset to the new nodes
184
302
  // @ts-expect-error accessing private property for testing
185
- expect(process.state.projectNodesAtLastUpload).toEqual(mockNodes);
186
- expect(success).toBe(true);
303
+ expect(process.state.projectNodesAtLastDeploy).toEqual(mockNewNodes);
304
+ expect(result).toEqual({
305
+ uploadSuccess: true,
306
+ buildSuccess: true,
307
+ deploySuccess: true,
308
+ deployId: 456,
309
+ });
187
310
  });
188
311
  });
189
312
  describe('handleFileChange()', () => {
@@ -252,4 +375,93 @@ describe('LocalDevProcess', () => {
252
375
  expect(listener).toHaveBeenCalledTimes(1);
253
376
  });
254
377
  });
378
+ describe('deployLatestBuild()', () => {
379
+ beforeEach(() => {
380
+ vi.clearAllMocks();
381
+ });
382
+ it('should successfully deploy latest build', async () => {
383
+ const mockDeploy = {
384
+ deployId: 456,
385
+ buildId: 123,
386
+ status: 'SUCCESS',
387
+ enqueuedAt: '2023-01-01T00:00:00Z',
388
+ startedAt: '2023-01-01T00:01:00Z',
389
+ finishedAt: '2023-01-01T00:05:00Z',
390
+ portalId: 123,
391
+ projectName: 'test-project',
392
+ userId: 789,
393
+ source: 'HUBSPOT_USER',
394
+ subdeployStatuses: [],
395
+ };
396
+ handleProjectDeploy.mockResolvedValue(mockDeploy);
397
+ const result = await process.deployLatestBuild();
398
+ expect(mockLocalDevLogger.deployInitiated).toHaveBeenCalled();
399
+ expect(handleProjectDeploy).toHaveBeenCalledWith(123, // targetProjectAccountId
400
+ 'test-project', // projectName
401
+ 123, // buildId
402
+ true, // useV2Api
403
+ false // force
404
+ );
405
+ expect(mockLocalDevLogger.deploySuccess).toHaveBeenCalled();
406
+ expect(result).toEqual({
407
+ success: true,
408
+ deployId: 456,
409
+ });
410
+ });
411
+ it('should deploy with force parameter', async () => {
412
+ const mockDeploy = {
413
+ deployId: 456,
414
+ buildId: 123,
415
+ status: 'SUCCESS',
416
+ enqueuedAt: '2023-01-01T00:00:00Z',
417
+ startedAt: '2023-01-01T00:01:00Z',
418
+ finishedAt: '2023-01-01T00:05:00Z',
419
+ portalId: 123,
420
+ projectName: 'test-project',
421
+ userId: 789,
422
+ source: 'HUBSPOT_USER',
423
+ subdeployStatuses: [],
424
+ };
425
+ handleProjectDeploy.mockResolvedValue(mockDeploy);
426
+ const result = await process.deployLatestBuild(true);
427
+ expect(handleProjectDeploy).toHaveBeenCalledWith(123, // targetProjectAccountId
428
+ 'test-project', // projectName
429
+ 123, // buildId
430
+ true, // useV2Api
431
+ true // force
432
+ );
433
+ expect(result).toEqual({
434
+ success: true,
435
+ deployId: 456,
436
+ });
437
+ });
438
+ it('should return error when no build exists', async () => {
439
+ // Create a process without latestBuild
440
+ const optionsWithoutBuild = {
441
+ ...mockOptions,
442
+ projectData: {
443
+ ...mockOptions.projectData,
444
+ latestBuild: undefined,
445
+ },
446
+ };
447
+ const processWithoutBuild = new LocalDevProcess(optionsWithoutBuild);
448
+ const result = await processWithoutBuild.deployLatestBuild();
449
+ expect(mockLocalDevLogger.deployInitiated).toHaveBeenCalled();
450
+ expect(mockLocalDevLogger.deployError).toHaveBeenCalledWith('Error deploying project. No build was found to deploy.');
451
+ expect(result).toEqual({
452
+ success: false,
453
+ });
454
+ expect(handleProjectDeploy).not.toHaveBeenCalled();
455
+ });
456
+ it('should handle deploy failure when no deploy object returned', async () => {
457
+ handleProjectDeploy.mockResolvedValue(undefined);
458
+ const result = await process.deployLatestBuild();
459
+ expect(mockLocalDevLogger.deployInitiated).toHaveBeenCalled();
460
+ expect(handleProjectDeploy).toHaveBeenCalled();
461
+ expect(result).toEqual({
462
+ success: false,
463
+ });
464
+ expect(mockLocalDevLogger.deploySuccess).not.toHaveBeenCalled();
465
+ });
466
+ });
255
467
  });