@hubspot/cli 7.8.0-experimental.0 → 7.8.2-experimental.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 (143) hide show
  1. package/bin/cli.js +0 -2
  2. package/commands/__tests__/getStarted.test.js +2 -2
  3. package/commands/__tests__/mcp.test.js +1 -1
  4. package/commands/__tests__/project.test.js +0 -3
  5. package/commands/app/__tests__/migrate.test.js +0 -1
  6. package/commands/app/migrate.js +4 -5
  7. package/commands/app/secret/add.js +2 -1
  8. package/commands/app/secret/delete.js +2 -1
  9. package/commands/app/secret/list.js +2 -1
  10. package/commands/app/secret/update.js +2 -1
  11. package/commands/app/secret.js +2 -1
  12. package/commands/app.js +2 -2
  13. package/commands/config/set.js +0 -1
  14. package/commands/feedback.js +1 -1
  15. package/commands/getStarted.d.ts +1 -3
  16. package/commands/getStarted.js +66 -18
  17. package/commands/mcp/__tests__/setup.test.js +2 -2
  18. package/commands/mcp/setup.js +11 -2
  19. package/commands/mcp.js +3 -3
  20. package/commands/project/__tests__/create.test.js +6 -6
  21. package/commands/project/__tests__/deploy.test.js +0 -3
  22. package/commands/project/__tests__/devUnifiedFlow.test.js +2 -4
  23. package/commands/project/__tests__/logs.test.js +0 -3
  24. package/commands/project/__tests__/migrate.test.js +1 -2
  25. package/commands/project/__tests__/migrateApp.test.js +1 -2
  26. package/commands/project/__tests__/profile.test.js +1 -1
  27. package/commands/project/add.js +1 -5
  28. package/commands/project/create.js +3 -9
  29. package/commands/project/deploy.js +2 -2
  30. package/commands/project/dev/index.js +4 -3
  31. package/commands/project/dev/unifiedFlow.js +6 -4
  32. package/commands/project/download.js +1 -2
  33. package/commands/project/installDeps.js +1 -2
  34. package/commands/project/listBuilds.js +2 -2
  35. package/commands/project/logs.js +2 -2
  36. package/commands/project/migrate.js +28 -10
  37. package/commands/project/migrateApp.js +1 -2
  38. package/commands/project/open.js +1 -2
  39. package/commands/project/profile/add.js +3 -3
  40. package/commands/project/profile/delete.js +1 -2
  41. package/commands/project/profile.js +2 -3
  42. package/commands/project/upload.js +2 -2
  43. package/commands/project/validate.js +1 -1
  44. package/commands/project/watch.js +2 -2
  45. package/commands/project.js +1 -2
  46. package/commands/sandbox/delete.js +1 -1
  47. package/commands/testAccount/importData.d.ts +1 -1
  48. package/commands/testAccount/importData.js +1 -1
  49. package/commands/testAccount.js +1 -1
  50. package/lang/en.d.ts +15 -4
  51. package/lang/en.js +18 -6
  52. package/lib/__tests__/hasFeature.test.js +145 -7
  53. package/lib/app/__tests__/migrate.test.js +14 -51
  54. package/lib/app/migrate.d.ts +2 -8
  55. package/lib/app/migrate.js +5 -80
  56. package/lib/constants.d.ts +8 -0
  57. package/lib/constants.js +8 -0
  58. package/lib/dependencyManagement.d.ts +0 -5
  59. package/lib/dependencyManagement.js +0 -9
  60. package/lib/hasFeature.js +6 -0
  61. package/lib/links.d.ts +1 -0
  62. package/lib/links.js +10 -3
  63. package/lib/mcp/setup.js +1 -1
  64. package/lib/middleware/fireAlarmMiddleware.js +15 -5
  65. package/lib/projects/__tests__/LocalDevProcess.test.js +227 -16
  66. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +16 -21
  67. package/lib/projects/__tests__/deploy.test.js +71 -6
  68. package/lib/projects/__tests__/localDevProjectHelpers.test.js +4 -2
  69. package/lib/projects/create/__tests__/v3.test.js +79 -4
  70. package/lib/projects/create/v3.js +11 -8
  71. package/lib/projects/localDev/AppDevModeInterface.js +5 -5
  72. package/lib/projects/localDev/LocalDevLogger.d.ts +4 -0
  73. package/lib/projects/localDev/LocalDevLogger.js +22 -0
  74. package/lib/projects/localDev/LocalDevProcess.d.ts +7 -5
  75. package/lib/projects/localDev/LocalDevProcess.js +90 -19
  76. package/lib/projects/localDev/LocalDevState.d.ts +9 -8
  77. package/lib/projects/localDev/LocalDevState.js +18 -17
  78. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +2 -0
  79. package/lib/projects/localDev/LocalDevWebsocketServer.js +55 -23
  80. package/lib/projects/localDev/helpers/project.d.ts +2 -2
  81. package/lib/projects/localDev/helpers/project.js +10 -7
  82. package/lib/projects/localDev/localDevWebsocketServerUtils.d.ts +4 -0
  83. package/lib/projects/localDev/localDevWebsocketServerUtils.js +10 -0
  84. package/lib/projects/pollProjectBuildAndDeploy.js +4 -4
  85. package/lib/prompts/projectAddPrompt.js +2 -1
  86. package/lib/prompts/promptUtils.js +3 -0
  87. package/lib/prompts/selectProjectTemplatePrompt.js +2 -0
  88. package/lib/theme/__tests__/migrate.test.d.ts +1 -0
  89. package/lib/theme/__tests__/migrate.test.js +233 -0
  90. package/lib/theme/migrate.d.ts +13 -0
  91. package/lib/theme/migrate.js +90 -0
  92. package/lib/ui/SpinniesManager.js +105 -8
  93. package/lib/usageTracking.js +2 -2
  94. package/mcp-server/tools/cms/HsCreateFunctionTool.js +1 -1
  95. package/mcp-server/tools/cms/HsCreateModuleTool.js +1 -1
  96. package/mcp-server/tools/cms/HsCreateTemplateTool.js +1 -1
  97. package/mcp-server/tools/cms/HsFunctionLogsTool.js +2 -2
  98. package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
  99. package/mcp-server/tools/cms/HsListTool.js +1 -1
  100. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -1
  101. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +1 -1
  102. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +1 -1
  103. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +2 -2
  104. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +1 -1
  105. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +1 -1
  106. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +3 -3
  107. package/mcp-server/tools/project/AddFeatureToProjectTool.js +3 -3
  108. package/mcp-server/tools/project/CreateProjectTool.d.ts +3 -3
  109. package/mcp-server/tools/project/CreateProjectTool.js +5 -5
  110. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  111. package/mcp-server/tools/project/DocFetchTool.js +2 -2
  112. package/mcp-server/tools/project/DocsSearchTool.d.ts +4 -1
  113. package/mcp-server/tools/project/DocsSearchTool.js +7 -7
  114. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +4 -1
  115. package/mcp-server/tools/project/GetConfigValuesTool.js +11 -5
  116. package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -1
  117. package/mcp-server/tools/project/UploadProjectTools.js +2 -2
  118. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  119. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +1 -1
  120. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -1
  121. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +1 -1
  122. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +2 -2
  123. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +14 -12
  124. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +9 -8
  125. package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +1 -1
  126. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +1 -1
  127. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +1 -1
  128. package/mcp-server/tools/project/constants.d.ts +1 -1
  129. package/mcp-server/tools/project/constants.js +9 -3
  130. package/mcp-server/utils/__tests__/cliConfig.test.d.ts +1 -0
  131. package/mcp-server/utils/__tests__/cliConfig.test.js +110 -0
  132. package/mcp-server/utils/cliConfig.d.ts +1 -0
  133. package/mcp-server/utils/cliConfig.js +12 -0
  134. package/package.json +4 -9
  135. package/types/LocalDev.d.ts +19 -3
  136. package/ui/components/HorizontalSelectPrompt.js +1 -1
  137. package/ui/index.js +1 -1
  138. package/commands/getStartedV2.d.ts +0 -9
  139. package/commands/getStartedV2.js +0 -39
  140. package/ui/components/Ascii.d.ts +0 -10
  141. package/ui/components/Ascii.js +0 -11
  142. package/ui/views/GetStarted.d.ts +0 -7
  143. package/ui/views/GetStarted.js +0 -157
@@ -99,7 +99,8 @@ describe('isDeployedProjectUpToDateWithLocal', () => {
99
99
  it('should clean up temp directory even when errors occur', async () => {
100
100
  // Mock downloadProject to throw an error after temp dir is created
101
101
  downloadProject.mockRejectedValue(new Error('Download Error'));
102
- await expect(isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes)).rejects.toThrow('Download Error');
102
+ const result = await isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes);
103
+ expect(result).toBe(false);
103
104
  expect(fs.remove).toHaveBeenCalledWith(mockTempDir);
104
105
  });
105
106
  it('should handle translateForLocalDev errors', async () => {
@@ -111,7 +112,8 @@ describe('isDeployedProjectUpToDateWithLocal', () => {
111
112
  extractZipArchive.mockResolvedValue(undefined);
112
113
  // Mock translate to throw an error
113
114
  translate.mockRejectedValue(new Error('Translation Error'));
114
- await expect(isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes)).rejects.toThrow('Translation Error');
115
+ const result = await isDeployedProjectUpToDateWithLocal(mockProjectConfig, mockAccountId, mockBuildId, mockLocalProjectNodes);
116
+ expect(result).toBe(false);
115
117
  expect(fs.remove).toHaveBeenCalledWith(mockTempDir);
116
118
  });
117
119
  });
@@ -1,8 +1,17 @@
1
1
  import { calculateComponentTemplateChoices } from '../v3.js';
2
+ import { hasFeature } from '../../../hasFeature.js';
2
3
  vi.mock('@hubspot/local-dev-lib/logger');
3
4
  vi.mock('@hubspot/local-dev-lib/api/github');
5
+ vi.mock('../../../hasFeature.js');
6
+ const mockHasFeature = vi.mocked(hasFeature);
4
7
  describe('lib/projects/create/v3', () => {
8
+ beforeEach(() => {
9
+ mockHasFeature.mockResolvedValue(true);
10
+ });
5
11
  describe('calculateComponentTemplateChoices()', () => {
12
+ beforeEach(() => {
13
+ mockHasFeature.mockClear();
14
+ });
6
15
  const mockComponents = [
7
16
  {
8
17
  label: 'Module Component',
@@ -30,7 +39,7 @@ describe('lib/projects/create/v3', () => {
30
39
  const choices = await calculateComponentTemplateChoices(mockComponents, 'oauth', 'private', 123, mockProjectMetadataForChoices);
31
40
  expect(choices).toHaveLength(4); // includes separator
32
41
  expect(choices[0]).toEqual({
33
- name: 'Module Component',
42
+ name: 'Module Component [module]',
34
43
  value: mockComponents[0],
35
44
  });
36
45
  expect(choices[2]).toEqual({
@@ -70,7 +79,7 @@ describe('lib/projects/create/v3', () => {
70
79
  components: { module: { count: 0, maxCount: 5, hsMetaFiles: [] } },
71
80
  });
72
81
  expect(choices[0]).toEqual({
73
- name: 'Unrestricted Component',
82
+ name: 'Unrestricted Component [module]',
74
83
  value: componentsWithoutRestrictions[0],
75
84
  });
76
85
  });
@@ -94,7 +103,7 @@ describe('lib/projects/create/v3', () => {
94
103
  const choices = await calculateComponentTemplateChoices(componentWithCliSelector, 'oauth', 'private', 213, projectMetadataWithWorkflowAction);
95
104
  expect(choices).toHaveLength(1); // no disabled components
96
105
  expect(choices[0]).toEqual({
97
- name: 'Workflow Action Tool',
106
+ name: 'Workflow Action Tool [workflow-action-tool]',
98
107
  value: componentWithCliSelector[0],
99
108
  });
100
109
  });
@@ -137,7 +146,7 @@ describe('lib/projects/create/v3', () => {
137
146
  const choices = await calculateComponentTemplateChoices(componentWithCliSelector, 'oauth', 'private', 123, undefined);
138
147
  expect(choices).toHaveLength(1); // no disabled components
139
148
  expect(choices[0]).toEqual({
140
- name: 'Workflow Action Tool',
149
+ name: 'Workflow Action Tool [workflow-action-tool]',
141
150
  value: componentWithCliSelector[0],
142
151
  });
143
152
  });
@@ -162,5 +171,71 @@ describe('lib/projects/create/v3', () => {
162
171
  // @ts-expect-error breaking stuff on purpose
163
172
  projectMetadataWithoutComponents)).rejects.toThrow();
164
173
  });
174
+ it('disables gated components when hasFeature returns false', async () => {
175
+ mockHasFeature.mockResolvedValue(false);
176
+ const gatedComponent = [
177
+ {
178
+ label: 'Workflow Action Tool',
179
+ path: 'workflow-action-tool',
180
+ type: 'workflow-action',
181
+ cliSelector: 'workflow-action-tool',
182
+ supportedAuthTypes: ['oauth'],
183
+ supportedDistributions: ['private'],
184
+ },
185
+ ];
186
+ const choices = await calculateComponentTemplateChoices(gatedComponent, 'oauth', 'private', 123, mockProjectMetadataForChoices);
187
+ expect(choices).toHaveLength(3); // includes separators
188
+ expect(choices[1]).toEqual({
189
+ name: expect.stringContaining('Workflow Action Tool'),
190
+ value: gatedComponent[0],
191
+ disabled: expect.stringContaining('does not have access to this feature'),
192
+ });
193
+ expect(mockHasFeature).toHaveBeenCalledWith(123, expect.any(String));
194
+ });
195
+ it('enables gated components when hasFeature returns true', async () => {
196
+ mockHasFeature.mockResolvedValue(true);
197
+ const gatedComponent = [
198
+ {
199
+ label: 'Workflow Action Tool',
200
+ path: 'workflow-action-tool',
201
+ type: 'workflow-action',
202
+ cliSelector: 'workflow-action-tool',
203
+ supportedAuthTypes: ['oauth'],
204
+ supportedDistributions: ['private'],
205
+ },
206
+ ];
207
+ const projectMetadataWithWorkflowAction = {
208
+ hsMetaFiles: [],
209
+ components: {
210
+ 'workflow-action': { count: 0, maxCount: 3, hsMetaFiles: [] },
211
+ },
212
+ };
213
+ const choices = await calculateComponentTemplateChoices(gatedComponent, 'oauth', 'private', 123, projectMetadataWithWorkflowAction);
214
+ expect(choices).toHaveLength(1); // no disabled components
215
+ expect(choices[0]).toEqual({
216
+ name: 'Workflow Action Tool [workflow-action-tool]',
217
+ value: gatedComponent[0],
218
+ });
219
+ expect(mockHasFeature).toHaveBeenCalledWith(123, expect.any(String));
220
+ });
221
+ it('handles non-gated components without calling hasFeature', async () => {
222
+ const nonGatedComponent = [
223
+ {
224
+ label: 'Regular Component',
225
+ path: 'regular',
226
+ type: 'module',
227
+ supportedAuthTypes: ['oauth'],
228
+ supportedDistributions: ['private'],
229
+ },
230
+ ];
231
+ const choices = await calculateComponentTemplateChoices(nonGatedComponent, 'oauth', 'private', 123, mockProjectMetadataForChoices);
232
+ expect(choices).toHaveLength(1);
233
+ expect(choices[0]).toEqual({
234
+ name: 'Regular Component [module]',
235
+ value: nonGatedComponent[0],
236
+ });
237
+ // hasFeature should not be called for non-gated components
238
+ expect(mockHasFeature).not.toHaveBeenCalled();
239
+ });
165
240
  });
166
241
  });
@@ -9,7 +9,7 @@ import { getConfigForPlatformVersion } from './legacy.js';
9
9
  import { logError } from '../../errorHandlers/index.js';
10
10
  import { EXIT_CODES } from '../../enums/exitCodes.js';
11
11
  import { hasFeature } from '../../hasFeature.js';
12
- import { AppEventsKey } from '@hubspot/project-parsing-lib/src/lib/constants.js';
12
+ import { AppEventsKey, PagesKey, } from '@hubspot/project-parsing-lib/src/lib/constants.js';
13
13
  export async function createV3App(providedAuth, providedDistribution) {
14
14
  let authType;
15
15
  if (providedAuth &&
@@ -51,6 +51,8 @@ export async function createV3App(providedAuth, providedDistribution) {
51
51
  }
52
52
  const componentTypeToGateMap = {
53
53
  [AppEventsKey]: FEATURES.APP_EVENTS,
54
+ [PagesKey]: FEATURES.APPS_HOME,
55
+ 'workflow-action-tool': FEATURES.AGENT_TOOLS,
54
56
  };
55
57
  export async function calculateComponentTemplateChoices(components, authType, distribution, accountId, projectMetadata) {
56
58
  const enabledComponents = [];
@@ -72,30 +74,31 @@ export async function calculateComponentTemplateChoices(components, authType, di
72
74
  }
73
75
  if (Array.isArray(supportedAuthTypes) &&
74
76
  authType &&
75
- !supportedAuthTypes.includes(authType)) {
77
+ !supportedAuthTypes.includes(authType.toLowerCase())) {
76
78
  disabledReasons.push(commands.project.add.error.authTypeNotAllowed(authType));
77
79
  }
78
80
  if (Array.isArray(supportedDistributions) &&
79
81
  distribution &&
80
- !supportedDistributions.includes(distribution)) {
82
+ !supportedDistributions.includes(distribution.toLowerCase())) {
81
83
  disabledReasons.push(commands.project.add.error.distributionNotAllowed(distribution));
82
84
  }
83
- if (componentTypeToGateMap[template.type]) {
84
- const isUngated = await hasFeature(accountId, componentTypeToGateMap[template.type]);
85
+ const templateGate = componentTypeToGateMap[template.cliSelector || template.type];
86
+ if (templateGate) {
87
+ const isUngated = await hasFeature(accountId, templateGate);
85
88
  if (!isUngated) {
86
- disabledReasons.push(commands.project.add.error.portalDoesNotHaveAccessToThisFeature(accountId));
89
+ disabledReasons.unshift(commands.project.add.error.portalDoesNotHaveAccessToThisFeature(accountId));
87
90
  }
88
91
  }
89
92
  if (disabledReasons.length > 0) {
90
93
  disabledComponents.push({
91
- name: `[${chalk.yellow('DISABLED')}] ${template.label}`,
94
+ name: `[${chalk.yellow('DISABLED')}] ${template.label} -`,
92
95
  value: template,
93
96
  disabled: disabledReasons.join(' '),
94
97
  });
95
98
  }
96
99
  else {
97
100
  enabledComponents.push({
98
- name: template.label,
101
+ name: `${template.label} [${template.cliSelector || template.type}]`,
99
102
  value: template,
100
103
  });
101
104
  }
@@ -75,7 +75,7 @@ class AppDevModeInterface {
75
75
  // );
76
76
  // }
77
77
  async getAppInstallUrl() {
78
- if (this.appNode?.config.auth.type === APP_AUTH_TYPES.OAUTH) {
78
+ if (this.appNode?.config.auth.type.toLowerCase() === APP_AUTH_TYPES.OAUTH) {
79
79
  return getOauthAppInstallUrl({
80
80
  targetAccountId: this.localDevState.targetTestingAccountId,
81
81
  env: this.localDevState.env,
@@ -216,12 +216,12 @@ class AppDevModeInterface {
216
216
  const newDistribution = newAppNode?.config.distribution;
217
217
  const oldAuthType = this.appNode?.config.auth.type;
218
218
  const newAuthType = newAppNode?.config.auth.type;
219
- if (newDistribution === APP_DISTRIBUTION_TYPES.MARKETPLACE &&
220
- oldDistribution !== APP_DISTRIBUTION_TYPES.MARKETPLACE) {
219
+ if (newDistribution?.toLowerCase() === APP_DISTRIBUTION_TYPES.MARKETPLACE &&
220
+ oldDistribution?.toLowerCase() !== APP_DISTRIBUTION_TYPES.MARKETPLACE) {
221
221
  this.localDevState.addUploadWarning(lib.AppDevModeInterface.distributionChanged);
222
222
  }
223
- else if (newAuthType === APP_AUTH_TYPES.OAUTH &&
224
- oldAuthType !== APP_AUTH_TYPES.OAUTH) {
223
+ else if (newAuthType?.toLowerCase() === APP_AUTH_TYPES.OAUTH &&
224
+ oldAuthType?.toLowerCase() !== APP_AUTH_TYPES.OAUTH) {
225
225
  this.localDevState.addUploadWarning(lib.AppDevModeInterface.authTypeChanged);
226
226
  }
227
227
  };
@@ -18,9 +18,13 @@ declare class LocalDevLogger {
18
18
  cleanupError(): void;
19
19
  cleanupSuccess(): void;
20
20
  uploadInitiated(): void;
21
+ deployInitiated(): void;
21
22
  projectConfigMismatch(): void;
22
23
  uploadError(error: unknown): void;
23
24
  uploadSuccess(): void;
25
+ uploadSuccessAutoDeployDisabled(): void;
26
+ deployError(error?: unknown): void;
27
+ deploySuccess(): void;
24
28
  monitorConsoleOutput(): void;
25
29
  }
26
30
  export default LocalDevLogger;
@@ -112,6 +112,9 @@ class LocalDevLogger {
112
112
  uploadInitiated() {
113
113
  uiLogger.log(lib.LocalDevProcess.uploadInitiated);
114
114
  }
115
+ deployInitiated() {
116
+ uiLogger.log(lib.LocalDevProcess.deployInitiated);
117
+ }
115
118
  projectConfigMismatch() {
116
119
  uiLogger.log(lib.LocalDevProcess.projectConfigMismatch);
117
120
  }
@@ -127,6 +130,25 @@ class LocalDevLogger {
127
130
  uiLine();
128
131
  logger.log('');
129
132
  }
133
+ uploadSuccessAutoDeployDisabled() {
134
+ uiLogger.warn(lib.LocalDevProcess.uploadSuccessAutoDeployDisabled);
135
+ uiLine();
136
+ logger.log('');
137
+ }
138
+ deployError(error) {
139
+ logger.log('');
140
+ if (error) {
141
+ logError(error);
142
+ }
143
+ uiLogger.log(lib.LocalDevProcess.deployFailed);
144
+ logger.log('');
145
+ }
146
+ deploySuccess() {
147
+ logger.log('');
148
+ uiLogger.log(lib.LocalDevProcess.deploySuccess);
149
+ uiLine();
150
+ logger.log('');
151
+ }
130
152
  monitorConsoleOutput() {
131
153
  const originalStdoutWrite = process.stdout.write.bind(process.stdout);
132
154
  function customStdoutWrite(chunk, encoding, callback) {
@@ -1,15 +1,15 @@
1
1
  import { IntermediateRepresentationNodeLocalDev } from '@hubspot/project-parsing-lib/src/lib/types.js';
2
+ import { Project } from '@hubspot/local-dev-lib/types/Project';
2
3
  import LocalDevState from './LocalDevState.js';
3
4
  import LocalDevLogger from './LocalDevLogger.js';
4
- import { LocalDevStateConstructorOptions, LocalDevStateListener, LocalDevServerMessage } from '../../../types/LocalDev.js';
5
+ import { LocalDevStateConstructorOptions, LocalDevStateListener, LocalDevServerMessage, LocalDevProjectUploadResult, LocalDevProjectDeployResult } from '../../../types/LocalDev.js';
5
6
  declare class LocalDevProcess {
6
7
  private state;
7
8
  private _logger;
8
9
  private devServerManager;
9
10
  constructor(options: LocalDevStateConstructorOptions);
10
11
  get projectDir(): string;
11
- get projectId(): number;
12
- get projectName(): string;
12
+ get projectData(): Project;
13
13
  get targetProjectAccountId(): number;
14
14
  get targetTestingAccountId(): number;
15
15
  get projectNodes(): {
@@ -22,13 +22,15 @@ declare class LocalDevProcess {
22
22
  private projectConfigValidForUpload;
23
23
  private getIntermediateRepresentation;
24
24
  private updateProjectNodes;
25
- private updateProjectNodesAfterUpload;
25
+ private updateProjectNodesAfterDeploy;
26
26
  private openLocalDevUi;
27
+ private updateProjectData;
27
28
  handleFileChange(filePath: string, event: string): Promise<void>;
28
29
  handleConfigFileChange(): Promise<void>;
29
30
  start(): Promise<void>;
30
31
  stop(showProgress?: boolean): Promise<void>;
31
- uploadProject(): Promise<boolean>;
32
+ uploadProject(): Promise<LocalDevProjectUploadResult>;
33
+ deployLatestBuild(force?: boolean): Promise<LocalDevProjectDeployResult>;
32
34
  addStateListener<K extends keyof LocalDevState>(key: K, listener: LocalDevStateListener<K>): void;
33
35
  sendDevServerMessage(message: LocalDevServerMessage): void;
34
36
  removeStateListener<K extends keyof LocalDevState>(key: K, listener: LocalDevStateListener<K>): void;
@@ -1,5 +1,6 @@
1
1
  import { translateForLocalDev } from '@hubspot/project-parsing-lib';
2
2
  import { hasLocalStateFlag } from '@hubspot/local-dev-lib/config';
3
+ import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
3
4
  import path from 'path';
4
5
  import open from 'open';
5
6
  import LocalDevState from './LocalDevState.js';
@@ -8,10 +9,13 @@ import DevServerManagerV2 from './DevServerManagerV2.js';
8
9
  import { EXIT_CODES } from '../../enums/exitCodes.js';
9
10
  import { getProjectConfig } from '../config.js';
10
11
  import { handleProjectUpload } from '../upload.js';
12
+ import { handleProjectDeploy } from '../deploy.js';
11
13
  import { pollProjectBuildAndDeploy } from '../pollProjectBuildAndDeploy.js';
12
14
  import { getLocalDevUiUrl } from '../urls.js';
13
- import { CONFIG_LOCAL_STATE_FLAGS } from '../../constants.js';
15
+ import { CONFIG_LOCAL_STATE_FLAGS, PROJECT_DEPLOY_STATES, } from '../../constants.js';
14
16
  import { isAutoOpenBrowserEnabled } from '../../configOptions.js';
17
+ import { lib } from '../../../lang/en.js';
18
+ import { debugError } from '../../errorHandlers/index.js';
15
19
  class LocalDevProcess {
16
20
  state;
17
21
  _logger;
@@ -27,11 +31,8 @@ class LocalDevProcess {
27
31
  get projectDir() {
28
32
  return this.state.projectDir;
29
33
  }
30
- get projectId() {
31
- return this.state.projectId;
32
- }
33
- get projectName() {
34
- return this.state.projectName;
34
+ get projectData() {
35
+ return this.state.projectData;
35
36
  }
36
37
  get targetProjectAccountId() {
37
38
  return this.state.targetProjectAccountId;
@@ -87,34 +88,43 @@ class LocalDevProcess {
87
88
  });
88
89
  return true;
89
90
  }
90
- getIntermediateRepresentation(projectNodesAtLastUpload) {
91
+ getIntermediateRepresentation(projectNodesAtLastDeploy) {
91
92
  return translateForLocalDev({
92
93
  projectSourceDir: path.join(this.state.projectDir, this.state.projectConfig.srcDir),
93
94
  platformVersion: this.state.projectConfig.platformVersion,
94
95
  accountId: this.state.targetProjectAccountId,
95
96
  }, {
96
- projectNodesAtLastUpload,
97
+ projectNodesAtLastUpload: projectNodesAtLastDeploy,
97
98
  profile: this.state.profile,
98
99
  });
99
100
  }
100
101
  async updateProjectNodes() {
101
- const intermediateRepresentation = await this.getIntermediateRepresentation(this.state.projectNodesAtLastUpload);
102
+ const intermediateRepresentation = await this.getIntermediateRepresentation(this.state.projectNodesAtLastDeploy);
102
103
  this.state.projectNodes =
103
104
  intermediateRepresentation.intermediateNodesIndexedByUid;
104
105
  this.state.projectProfileData = intermediateRepresentation.profileData;
105
106
  }
106
- async updateProjectNodesAfterUpload() {
107
+ async updateProjectNodesAfterDeploy() {
107
108
  const intermediateRepresentation = await this.getIntermediateRepresentation();
108
109
  this.state.projectNodes =
109
110
  intermediateRepresentation.intermediateNodesIndexedByUid;
110
111
  this.state.projectProfileData = intermediateRepresentation.profileData;
111
- this.state.projectNodesAtLastUpload =
112
+ this.state.projectNodesAtLastDeploy =
112
113
  intermediateRepresentation.intermediateNodesIndexedByUid;
113
114
  }
114
115
  openLocalDevUi() {
115
116
  const showWelcomeScreen = !hasLocalStateFlag(CONFIG_LOCAL_STATE_FLAGS.LOCAL_DEV_UI_WELCOME);
116
117
  open(getLocalDevUiUrl(this.state.targetTestingAccountId, showWelcomeScreen));
117
118
  }
119
+ async updateProjectData() {
120
+ try {
121
+ const { data: projectData } = await fetchProject(this.state.targetProjectAccountId, this.state.projectConfig.name);
122
+ this.state.projectData = projectData;
123
+ }
124
+ catch (e) {
125
+ debugError(e);
126
+ }
127
+ }
118
128
  async handleFileChange(filePath, event) {
119
129
  await this.updateProjectNodes();
120
130
  try {
@@ -162,24 +172,85 @@ class LocalDevProcess {
162
172
  const isUploadable = await this.projectConfigValidForUpload();
163
173
  if (!isUploadable) {
164
174
  this.logger.projectConfigMismatch();
165
- return false;
175
+ return {
176
+ uploadSuccess: false,
177
+ buildSuccess: false,
178
+ deploySuccess: false,
179
+ };
166
180
  }
167
- const { uploadError } = await handleProjectUpload({
181
+ const { uploadError, result } = await handleProjectUpload({
168
182
  accountId: this.state.targetProjectAccountId,
169
183
  projectConfig: this.state.projectConfig,
170
184
  projectDir: this.state.projectDir,
171
185
  callbackFunc: pollProjectBuildAndDeploy,
172
186
  sendIR: true,
173
- skipValidation: true,
174
187
  });
188
+ const deploy = result?.deployResult;
175
189
  if (uploadError) {
176
190
  this.logger.uploadError(uploadError);
177
- return false;
191
+ return {
192
+ uploadSuccess: false,
193
+ buildSuccess: false,
194
+ deploySuccess: false,
195
+ deployId: deploy?.deployId,
196
+ };
178
197
  }
179
- await this.updateProjectNodesAfterUpload();
180
- this.logger.uploadSuccess();
181
- this.state.clearUploadWarnings();
182
- return true;
198
+ await this.updateProjectData();
199
+ if (deploy && deploy.status === PROJECT_DEPLOY_STATES.FAILURE) {
200
+ return {
201
+ uploadSuccess: false,
202
+ buildSuccess: true,
203
+ deploySuccess: false,
204
+ deployId: deploy.deployId,
205
+ };
206
+ }
207
+ else if (!deploy) {
208
+ this.logger.uploadSuccessAutoDeployDisabled();
209
+ }
210
+ else {
211
+ await this.updateProjectNodesAfterDeploy();
212
+ this.state.clearUploadWarnings();
213
+ this.logger.uploadSuccess();
214
+ }
215
+ return {
216
+ uploadSuccess: true,
217
+ buildSuccess: true,
218
+ deploySuccess: Boolean(deploy),
219
+ deployId: deploy?.deployId,
220
+ };
221
+ }
222
+ async deployLatestBuild(force = false) {
223
+ this.logger.deployInitiated();
224
+ if (!this.state.projectData.latestBuild) {
225
+ this.logger.deployError(lib.LocalDevProcess.noBuildToDeploy);
226
+ return {
227
+ success: false,
228
+ };
229
+ }
230
+ let deploy;
231
+ try {
232
+ deploy = await handleProjectDeploy(this.state.targetProjectAccountId, this.state.projectConfig.name, this.state.projectData.latestBuild.buildId, true, force);
233
+ }
234
+ catch (error) {
235
+ this.logger.deployError(error);
236
+ return {
237
+ success: false,
238
+ };
239
+ }
240
+ const success = deploy?.status === PROJECT_DEPLOY_STATES.SUCCESS;
241
+ if (success) {
242
+ await this.updateProjectData();
243
+ this.logger.deploySuccess();
244
+ await this.updateProjectNodesAfterDeploy();
245
+ this.state.clearUploadWarnings();
246
+ }
247
+ else {
248
+ this.logger.deployError();
249
+ }
250
+ return {
251
+ success,
252
+ deployId: deploy?.deployId,
253
+ };
183
254
  }
184
255
  addStateListener(key, listener) {
185
256
  this.state.addListener(key, listener);
@@ -1,5 +1,6 @@
1
1
  import { IntermediateRepresentationNodeLocalDev, HSProfileVariables } from '@hubspot/project-parsing-lib/src/lib/types.js';
2
2
  import { Environment } from '@hubspot/local-dev-lib/types/Config';
3
+ import { Project } from '@hubspot/local-dev-lib/types/Project';
3
4
  import { ProjectConfig } from '../../../types/Projects.js';
4
5
  import { LocalDevStateConstructorOptions, LocalDevStateListener, AppLocalDevData, LocalDevServerMessage } from '../../../types/LocalDev.js';
5
6
  declare class LocalDevState {
@@ -8,26 +9,23 @@ declare class LocalDevState {
8
9
  private _profile?;
9
10
  private _projectConfig;
10
11
  private _projectDir;
11
- private _projectId;
12
- private _projectName;
12
+ private _projectData;
13
13
  private _debug;
14
14
  private _projectNodes;
15
15
  private _projectProfileData;
16
- private _projectNodesAtLastUpload;
16
+ private _projectNodesAtLastDeploy;
17
17
  private _env;
18
18
  private _listeners;
19
19
  private _appData;
20
20
  private _devServerMessage;
21
21
  private _uploadWarnings;
22
- constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, initialProjectNodes, initialProjectProfileData, profile, env, }: LocalDevStateConstructorOptions);
22
+ constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectData, debug, initialProjectNodes, initialProjectProfileData, profile, env, }: LocalDevStateConstructorOptions);
23
23
  private runListeners;
24
24
  get targetProjectAccountId(): number;
25
25
  get targetTestingAccountId(): number;
26
26
  get profile(): string | undefined;
27
27
  get projectConfig(): ProjectConfig;
28
28
  get projectDir(): string;
29
- get projectId(): number;
30
- get projectName(): string;
31
29
  get debug(): boolean;
32
30
  get projectNodes(): {
33
31
  [key: string]: IntermediateRepresentationNodeLocalDev;
@@ -37,12 +35,15 @@ declare class LocalDevState {
37
35
  });
38
36
  get projectProfileData(): HSProfileVariables;
39
37
  set projectProfileData(profileData: HSProfileVariables);
40
- get projectNodesAtLastUpload(): {
38
+ get projectNodesAtLastDeploy(): {
41
39
  [key: string]: IntermediateRepresentationNodeLocalDev;
42
40
  };
43
- set projectNodesAtLastUpload(nodes: {
41
+ set projectNodesAtLastDeploy(nodes: {
44
42
  [key: string]: IntermediateRepresentationNodeLocalDev;
45
43
  });
44
+ get projectData(): Project;
45
+ get projectId(): number;
46
+ set projectData(projectData: Project);
46
47
  get env(): Environment;
47
48
  get appData(): Record<string, AppLocalDevData>;
48
49
  getAppDataByUid(uid: string): AppLocalDevData | undefined;
@@ -5,28 +5,26 @@ class LocalDevState {
5
5
  _profile;
6
6
  _projectConfig;
7
7
  _projectDir;
8
- _projectId;
9
- _projectName;
8
+ _projectData;
10
9
  _debug;
11
10
  _projectNodes;
12
11
  _projectProfileData;
13
- _projectNodesAtLastUpload;
12
+ _projectNodesAtLastDeploy;
14
13
  _env;
15
14
  _listeners;
16
15
  _appData;
17
16
  _devServerMessage;
18
17
  _uploadWarnings;
19
- constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, initialProjectNodes, initialProjectProfileData, profile, env, }) {
18
+ constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectData, debug, initialProjectNodes, initialProjectProfileData, profile, env, }) {
20
19
  this._targetProjectAccountId = targetProjectAccountId;
21
20
  this._targetTestingAccountId = targetTestingAccountId;
22
21
  this._profile = profile;
23
22
  this._projectConfig = projectConfig;
24
23
  this._projectDir = projectDir;
25
- this._projectId = projectId;
26
- this._projectName = projectName;
24
+ this._projectData = projectData;
27
25
  this._debug = debug || false;
28
26
  this._projectNodes = initialProjectNodes;
29
- this._projectNodesAtLastUpload = initialProjectNodes;
27
+ this._projectNodesAtLastDeploy = initialProjectNodes;
30
28
  this._projectProfileData = initialProjectProfileData;
31
29
  this._env = env;
32
30
  this._appData = {};
@@ -54,12 +52,6 @@ class LocalDevState {
54
52
  get projectDir() {
55
53
  return this._projectDir;
56
54
  }
57
- get projectId() {
58
- return this._projectId;
59
- }
60
- get projectName() {
61
- return this._projectName;
62
- }
63
55
  get debug() {
64
56
  return this._debug;
65
57
  }
@@ -76,11 +68,20 @@ class LocalDevState {
76
68
  set projectProfileData(profileData) {
77
69
  this._projectProfileData = profileData;
78
70
  }
79
- get projectNodesAtLastUpload() {
80
- return structuredClone(this._projectNodesAtLastUpload);
71
+ get projectNodesAtLastDeploy() {
72
+ return structuredClone(this._projectNodesAtLastDeploy);
73
+ }
74
+ set projectNodesAtLastDeploy(nodes) {
75
+ this._projectNodesAtLastDeploy = nodes;
76
+ }
77
+ get projectData() {
78
+ return structuredClone(this._projectData);
79
+ }
80
+ get projectId() {
81
+ return this.projectData.id;
81
82
  }
82
- set projectNodesAtLastUpload(nodes) {
83
- this._projectNodesAtLastUpload = nodes;
83
+ set projectData(projectData) {
84
+ this._projectData = projectData;
84
85
  }
85
86
  get env() {
86
87
  return this._env;
@@ -9,7 +9,9 @@ declare class LocalDevWebsocketServer {
9
9
  private logError;
10
10
  private sendMessage;
11
11
  private handleUpload;
12
+ private handleDeploy;
12
13
  private setupMessageHandlers;
14
+ private sendCliMetadata;
13
15
  private sendProjectData;
14
16
  private setupProjectNodesListener;
15
17
  private setupAppDataListener;