@hubspot/cli 8.1.0-beta.0 → 8.2.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 (95) hide show
  1. package/commands/cms/__tests__/watch.test.js +0 -8
  2. package/commands/cms/function/logs.js +1 -0
  3. package/commands/cms/theme/preview.js +9 -64
  4. package/commands/cms/watch.d.ts +0 -1
  5. package/commands/cms/watch.js +2 -8
  6. package/commands/feedback.js +1 -1
  7. package/commands/mcp/__tests__/start.test.js +8 -1
  8. package/commands/mcp/setup.js +1 -9
  9. package/commands/mcp/start.js +0 -1
  10. package/commands/project/__tests__/create.test.js +1 -1
  11. package/commands/project/create.js +2 -2
  12. package/commands/project/watch.js +15 -2
  13. package/lang/en.d.ts +17 -6
  14. package/lang/en.js +18 -7
  15. package/lib/__tests__/commandSuggestion.test.js +2 -0
  16. package/lib/__tests__/serverlessLogs.test.js +79 -64
  17. package/lib/commandSuggestion.js +1 -7
  18. package/lib/constants.d.ts +1 -1
  19. package/lib/constants.js +1 -1
  20. package/lib/generateSelectors.js +1 -2
  21. package/lib/getStartedV2Actions.d.ts +13 -0
  22. package/lib/getStartedV2Actions.js +53 -0
  23. package/lib/mcp/__tests__/setup.test.js +357 -28
  24. package/lib/mcp/setup.d.ts +1 -0
  25. package/lib/mcp/setup.js +77 -30
  26. package/lib/projects/create/__tests__/legacy.test.js +6 -24
  27. package/lib/projects/create/index.js +1 -4
  28. package/lib/projects/create/legacy.js +3 -8
  29. package/lib/projects/create/v2.js +1 -9
  30. package/lib/projects/ensureProjectExists.js +1 -2
  31. package/lib/projects/pollProjectBuildAndDeploy.js +90 -85
  32. package/lib/projects/upload.d.ts +1 -0
  33. package/lib/projects/upload.js +37 -46
  34. package/lib/projects/watch.d.ts +2 -1
  35. package/lib/projects/watch.js +32 -24
  36. package/lib/serverlessLogs.js +50 -44
  37. package/lib/theme/cmsDevServerProcess.d.ts +12 -0
  38. package/lib/theme/cmsDevServerProcess.js +148 -0
  39. package/lib/theme/cmsDevServerRunner.d.ts +14 -0
  40. package/lib/theme/cmsDevServerRunner.js +90 -0
  41. package/lib/usageTracking.js +8 -5
  42. package/mcp-server/tools/cms/HsCreateFunctionTool.js +1 -1
  43. package/mcp-server/tools/cms/HsCreateModuleTool.js +1 -1
  44. package/mcp-server/tools/cms/HsCreateTemplateTool.js +1 -1
  45. package/mcp-server/tools/cms/HsFunctionLogsTool.js +1 -1
  46. package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
  47. package/mcp-server/tools/cms/HsListTool.js +1 -1
  48. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -2
  49. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +1 -2
  50. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +1 -2
  51. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +1 -2
  52. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +1 -2
  53. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +1 -2
  54. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +20 -3
  55. package/mcp-server/tools/project/AddFeatureToProjectTool.js +7 -11
  56. package/mcp-server/tools/project/CreateProjectTool.d.ts +24 -4
  57. package/mcp-server/tools/project/CreateProjectTool.js +6 -11
  58. package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
  59. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  60. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +5 -8
  61. package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
  62. package/mcp-server/tools/project/GetBuildLogsTool.js +6 -7
  63. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
  64. package/mcp-server/tools/project/GetBuildStatusTool.js +3 -4
  65. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +6 -1
  66. package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -6
  67. package/mcp-server/tools/project/UploadProjectTools.js +1 -1
  68. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  69. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +1 -2
  70. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -2
  71. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +1 -2
  72. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +1 -2
  73. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +0 -32
  74. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +10 -2
  75. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
  76. package/mcp-server/tools/project/constants.d.ts +12 -1
  77. package/mcp-server/tools/project/constants.js +12 -16
  78. package/mcp-server/utils/__tests__/command.test.js +233 -3
  79. package/mcp-server/utils/__tests__/feedbackTracking.test.js +9 -64
  80. package/mcp-server/utils/command.d.ts +5 -0
  81. package/mcp-server/utils/command.js +24 -0
  82. package/mcp-server/utils/feedbackTracking.js +2 -17
  83. package/package.json +4 -5
  84. package/ui/components/getStarted/GetStartedFlow.js +79 -2
  85. package/ui/components/getStarted/reducer.d.ts +20 -0
  86. package/ui/components/getStarted/reducer.js +36 -0
  87. package/ui/components/getStarted/screens/InstallationScreen.d.ts +7 -0
  88. package/ui/components/getStarted/screens/InstallationScreen.js +16 -0
  89. package/ui/components/getStarted/screens/ProjectSetupScreen.js +2 -1
  90. package/ui/lib/constants.d.ts +1 -0
  91. package/ui/lib/constants.js +1 -0
  92. package/mcp-server/utils/__tests__/project.test.d.ts +0 -1
  93. package/mcp-server/utils/__tests__/project.test.js +0 -140
  94. package/mcp-server/utils/project.d.ts +0 -5
  95. package/mcp-server/utils/project.js +0 -18
package/lib/mcp/setup.js CHANGED
@@ -47,23 +47,56 @@ export async function addMcpServerToConfig(targets) {
47
47
  else {
48
48
  derivedTargets = targets;
49
49
  }
50
+ // Prompt for standalone mode
51
+ const { useStandaloneMode } = await promptUser({
52
+ name: 'useStandaloneMode',
53
+ type: 'confirm',
54
+ message: commands.mcp.setup.prompts.standaloneMode,
55
+ default: false,
56
+ });
57
+ const { cliVersion } = useStandaloneMode
58
+ ? await promptUser({
59
+ name: 'cliVersion',
60
+ type: 'input',
61
+ message: commands.mcp.setup.prompts.cliVersion,
62
+ validate: (v) => !v || /^[\d]+\.[\d]+\.[\d]+([-+][\w.]+)?$/.test(v.trim())
63
+ ? true
64
+ : 'Please enter a valid semver version (e.g. 8.0.1) or leave blank for latest.',
65
+ })
66
+ : { cliVersion: '' };
67
+ const cliPackage = cliVersion
68
+ ? `@hubspot/cli@${cliVersion}`
69
+ : '@hubspot/cli';
70
+ const standaloneEnv = {
71
+ HUBSPOT_MCP_STANDALONE: 'true',
72
+ };
73
+ if (cliVersion) {
74
+ standaloneEnv.HUBSPOT_CLI_VERSION = cliVersion;
75
+ }
76
+ const mcpCommand = useStandaloneMode
77
+ ? {
78
+ command: 'npx',
79
+ args: ['-y', '-p', cliPackage, 'hs', 'mcp', 'start'],
80
+ env: standaloneEnv,
81
+ }
82
+ : defaultMcpCommand;
50
83
  if (derivedTargets.includes(claudeCode)) {
51
- await runSetupFunction(setupClaudeCode);
84
+ await runSetupFunction(() => setupClaudeCode(mcpCommand));
52
85
  }
53
86
  if (derivedTargets.includes(cursor)) {
54
- await runSetupFunction(setupCursor);
87
+ await runSetupFunction(() => setupCursor(mcpCommand));
55
88
  }
56
89
  if (derivedTargets.includes(windsurf)) {
57
- await runSetupFunction(setupWindsurf);
90
+ await runSetupFunction(() => setupWindsurf(mcpCommand));
58
91
  }
59
92
  if (derivedTargets.includes(vscode)) {
60
- await runSetupFunction(setupVsCode);
93
+ await runSetupFunction(() => setupVsCode(mcpCommand));
61
94
  }
62
95
  if (derivedTargets.includes(codex)) {
63
- await runSetupFunction(setupCodex);
96
+ await runSetupFunction(() => setupCodex(mcpCommand));
64
97
  }
65
98
  if (derivedTargets.includes(gemini)) {
66
- await runSetupFunction(setupGemini);
99
+ await runSetupFunction(() => setupGemini(mcpCommand));
67
100
  }
68
101
  uiLogger.info(commands.mcp.setup.success(derivedTargets));
69
102
  return derivedTargets;
@@ -121,10 +154,7 @@ function setupMcpConfigFile(config) {
121
154
  if (!mcpConfig.mcpServers) {
122
155
  mcpConfig.mcpServers = {};
123
156
  }
124
- // Add or update HubSpot CLI MCP server
125
- mcpConfig.mcpServers[mcpServerName] = {
126
- ...config.mcpCommand,
127
- };
157
+ mcpConfig.mcpServers[mcpServerName] = config.mcpCommand;
128
158
  // Write the updated config
129
159
  fs.writeFileSync(config.configPath, JSON.stringify(mcpConfig, null, 2));
130
160
  SpinniesManager.succeed('spinner', {
@@ -145,10 +175,12 @@ export async function setupVsCode(mcpCommand = defaultMcpCommand) {
145
175
  SpinniesManager.add('vsCode', {
146
176
  text: commands.mcp.setup.spinners.configuringVsCode,
147
177
  });
148
- const mcpConfig = JSON.stringify({
178
+ const commandWithAgent = buildCommandWithAgentString(mcpCommand, vscode);
179
+ const configObject = {
149
180
  name: mcpServerName,
150
- ...buildCommandWithAgentString(mcpCommand, vscode),
151
- });
181
+ ...commandWithAgent,
182
+ };
183
+ const mcpConfig = JSON.stringify(configObject);
152
184
  await execAsync(`code --add-mcp ${JSON.stringify(mcpConfig)}`);
153
185
  SpinniesManager.succeed('vsCode', {
154
186
  text: commands.mcp.setup.spinners.configuredVsCode,
@@ -172,25 +204,27 @@ export async function setupVsCode(mcpCommand = defaultMcpCommand) {
172
204
  }
173
205
  }
174
206
  export async function setupClaudeCode(mcpCommand = defaultMcpCommand) {
207
+ SpinniesManager.add('claudeCode', {
208
+ text: commands.mcp.setup.spinners.configuringClaudeCode,
209
+ });
175
210
  try {
176
- SpinniesManager.add('claudeCode', {
177
- text: commands.mcp.setup.spinners.configuringClaudeCode,
211
+ // Check if claude command is available
212
+ await execAsync('claude --version');
213
+ }
214
+ catch (e) {
215
+ SpinniesManager.fail('claudeCode', {
216
+ text: commands.mcp.setup.spinners.claudeCodeNotFound,
178
217
  });
179
- try {
180
- // Check if claude command is available
181
- await execAsync('claude --version');
182
- }
183
- catch (e) {
184
- SpinniesManager.fail('claudeCode', {
185
- text: commands.mcp.setup.spinners.claudeCodeNotFound,
186
- });
187
- return false;
188
- }
218
+ return false;
219
+ }
220
+ try {
189
221
  // Run claude mcp add command
190
- const mcpConfig = JSON.stringify({
222
+ const commandWithAgent = buildCommandWithAgentString(mcpCommand, claudeCode);
223
+ const configObject = {
191
224
  type: 'stdio',
192
- ...buildCommandWithAgentString(mcpCommand, claudeCode),
193
- });
225
+ ...commandWithAgent,
226
+ };
227
+ const mcpConfig = JSON.stringify(configObject);
194
228
  const { stdout } = await execAsync('claude mcp list');
195
229
  if (stdout.includes(mcpServerName)) {
196
230
  SpinniesManager.update('claudeCode', {
@@ -248,7 +282,7 @@ export async function setupCodex(mcpCommand = defaultMcpCommand) {
248
282
  return false;
249
283
  }
250
284
  const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, codex);
251
- await execAsync(`codex mcp add "${mcpServerName}" -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
285
+ await execAsync(`codex mcp add "${mcpServerName}"${buildEnvFlagString(mcpCommand)} -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
252
286
  SpinniesManager.succeed('codexSpinner', {
253
287
  text: commands.mcp.setup.spinners.configuredCodex,
254
288
  });
@@ -277,7 +311,7 @@ export async function setupGemini(mcpCommand = defaultMcpCommand) {
277
311
  return false;
278
312
  }
279
313
  const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, gemini);
280
- await execAsync(`gemini mcp add -s user "${mcpServerName}" ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
314
+ await execAsync(`gemini mcp add -s user${buildEnvFlagString(mcpCommand)} "${mcpServerName}" ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
281
315
  SpinniesManager.succeed('geminiSpinner', {
282
316
  text: commands.mcp.setup.spinners.configuredGemini,
283
317
  });
@@ -296,3 +330,16 @@ function buildCommandWithAgentString(mcpCommand, agent) {
296
330
  mcpCommandCopy.args.push('--ai-agent', agent);
297
331
  return mcpCommandCopy;
298
332
  }
333
+ function buildEnvFlagString(mcpCommand) {
334
+ const envFlags = [];
335
+ if (mcpCommand.env) {
336
+ const env = Object.entries(mcpCommand.env);
337
+ env.forEach(([key, value]) => {
338
+ envFlags.push(`--env ${key}="${value}"`);
339
+ });
340
+ }
341
+ if (envFlags.length === 0) {
342
+ return '';
343
+ }
344
+ return ` ${envFlags.join(' ')}`;
345
+ }
@@ -1,6 +1,4 @@
1
- import { uiLogger } from '../../../ui/logger.js';
2
1
  import * as github from '@hubspot/local-dev-lib/api/github';
3
- import { EXIT_CODES } from '../../../enums/exitCodes.js';
4
2
  import { getProjectComponentListFromRepo, getProjectTemplateListFromRepo, } from '../legacy.js';
5
3
  import { PROJECT_COMPONENT_TYPES, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, } from '../../../constants.js';
6
4
  vi.mock('@hubspot/local-dev-lib/api/github');
@@ -38,16 +36,6 @@ describe('lib/projects/create/legacy', () => {
38
36
  });
39
37
  });
40
38
  describe('getProjectTemplateListFromRepo()', () => {
41
- let exitMock;
42
- beforeEach(() => {
43
- // @ts-expect-error - Mocking process.exit
44
- exitMock = vi
45
- .spyOn(process, 'exit')
46
- .mockImplementation(() => undefined);
47
- });
48
- afterEach(() => {
49
- exitMock.mockRestore();
50
- });
51
39
  it('returns a list of project templates', async () => {
52
40
  // @ts-expect-error - Mocking AxiosResponse
53
41
  mockedFetchRepoFile.mockResolvedValue({
@@ -56,20 +44,16 @@ describe('lib/projects/create/legacy', () => {
56
44
  const templates = await getProjectTemplateListFromRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'gh-ref');
57
45
  expect(templates).toEqual(repoConfig[PROJECT_COMPONENT_TYPES.PROJECTS]);
58
46
  });
59
- it('Logs an error and exits the process if the request for the template list fails', async () => {
47
+ it('throws an error if the request for the template list fails', async () => {
60
48
  mockedFetchRepoFile.mockRejectedValue(new Error('Not found'));
61
- await getProjectTemplateListFromRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'gh-ref');
62
- expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/Failed to fetch the config.json file from the target repository/));
63
- expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
49
+ await expect(getProjectTemplateListFromRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'gh-ref')).rejects.toThrow('Failed to fetch the config.json file from the target repository');
64
50
  });
65
- it('Logs an error and exits the process if there are no projects listed in the repo config', async () => {
51
+ it('throws an error if there are no projects listed in the repo config', async () => {
66
52
  // @ts-expect-error - Mocking AxiosResponse
67
53
  mockedFetchRepoFile.mockResolvedValue({});
68
- await getProjectTemplateListFromRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'gh-ref');
69
- expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/Unable to find any projects in the target repository's config.json file/));
70
- expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
54
+ await expect(getProjectTemplateListFromRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'gh-ref')).rejects.toThrow('Unable to find any projects in the target repository');
71
55
  });
72
- it('Logs an error and exits the process if any of the projects in the repo config are missing required properties', async () => {
56
+ it('throws an error if any of the projects in the repo config are missing required properties', async () => {
73
57
  // @ts-expect-error - Mocking AxiosResponse
74
58
  mockedFetchRepoFile.mockResolvedValue({
75
59
  data: {
@@ -82,9 +66,7 @@ describe('lib/projects/create/legacy', () => {
82
66
  ],
83
67
  },
84
68
  });
85
- await getProjectTemplateListFromRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'gh-ref');
86
- expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/Found misconfigured projects in the target repository's config.json file/));
87
- expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR);
69
+ await expect(getProjectTemplateListFromRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'gh-ref')).rejects.toThrow('Found misconfigured projects in the target repository');
88
70
  });
89
71
  });
90
72
  });
@@ -4,9 +4,7 @@ import { DEFAULT_PROJECT_TEMPLATE_BRANCH, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH
4
4
  import { isV2Project } from '../platformVersion.js';
5
5
  import { v2ComponentFlow } from './v2.js';
6
6
  import { getProjectTemplateListFromRepo } from './legacy.js';
7
- import { uiLogger } from '../../ui/logger.js';
8
7
  import { commands } from '../../../lang/en.js';
9
- import { EXIT_CODES } from '../../enums/exitCodes.js';
10
8
  export async function handleProjectCreationFlow(args) {
11
9
  const { platformVersion, templateSource, projectBase, auth: providedAuth, distribution: providedDistribution, } = args;
12
10
  const repo = templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH;
@@ -25,8 +23,7 @@ export async function handleProjectCreationFlow(args) {
25
23
  }
26
24
  const projectTemplates = await getProjectTemplateListFromRepo(repo, DEFAULT_PROJECT_TEMPLATE_BRANCH);
27
25
  if (!projectTemplates.length) {
28
- uiLogger.error(commands.project.create.errors.failedToFetchProjectList);
29
- process.exit(EXIT_CODES.ERROR);
26
+ throw new Error(commands.project.create.errors.failedToFetchProjectList);
30
27
  }
31
28
  const selectProjectTemplatePromptResponse = await selectProjectTemplatePrompt(args, projectTemplates);
32
29
  return {
@@ -1,8 +1,6 @@
1
1
  import { fetchRepoFile } from '@hubspot/local-dev-lib/api/github';
2
2
  import { DEFAULT_PROJECT_TEMPLATE_BRANCH, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, PROJECT_COMPONENT_TYPES, } from '../../constants.js';
3
- import { EXIT_CODES } from '../../enums/exitCodes.js';
4
3
  import { debugError } from '../../errorHandlers/index.js';
5
- import { uiLogger } from '../../ui/logger.js';
6
4
  import { isV2Project } from '../platformVersion.js';
7
5
  import { lib } from '../../../lang/en.js';
8
6
  const PROJECT_TEMPLATE_PROPERTIES = ['name', 'label', 'path'];
@@ -36,18 +34,15 @@ export async function getProjectTemplateListFromRepo(templateSource, githubRef)
36
34
  }
37
35
  catch (e) {
38
36
  debugError(e);
39
- uiLogger.error(lib.projects.create.errors.missingConfigFileTemplateSource);
40
- return process.exit(EXIT_CODES.ERROR);
37
+ throw new Error(lib.projects.create.errors.missingConfigFileTemplateSource);
41
38
  }
42
39
  if (!config || !config[PROJECT_COMPONENT_TYPES.PROJECTS]) {
43
- uiLogger.error(lib.projects.create.errors.noProjectsInConfig);
44
- return process.exit(EXIT_CODES.ERROR);
40
+ throw new Error(lib.projects.create.errors.noProjectsInConfig);
45
41
  }
46
42
  const templates = config[PROJECT_COMPONENT_TYPES.PROJECTS];
47
43
  const templatesContainAllProperties = templates.every(config => PROJECT_TEMPLATE_PROPERTIES.every(p => Object.prototype.hasOwnProperty.call(config, p)));
48
44
  if (!templatesContainAllProperties) {
49
- uiLogger.error(lib.projects.create.errors.missingPropertiesInConfig);
50
- return process.exit(EXIT_CODES.ERROR);
45
+ throw new Error(lib.projects.create.errors.missingPropertiesInConfig);
51
46
  }
52
47
  return templates;
53
48
  }
@@ -5,8 +5,6 @@ import { APP_EVENTS_KEY as AppEventsKey, PAGES_KEY as PagesKey, } from '@hubspot
5
5
  import { isV2Project } from '../platformVersion.js';
6
6
  import path from 'path';
7
7
  import { getConfigForPlatformVersion } from './legacy.js';
8
- import { logError } from '../../errorHandlers/index.js';
9
- import { EXIT_CODES } from '../../enums/exitCodes.js';
10
8
  import { hasFeature } from '../../hasFeature.js';
11
9
  export async function createV2App(providedAuth, providedDistribution) {
12
10
  let authType;
@@ -111,13 +109,7 @@ export async function v2ComponentFlow(platformVersion, projectBase, providedAuth
111
109
  let repoConfig = undefined;
112
110
  let authType;
113
111
  let distribution;
114
- try {
115
- repoConfig = await getConfigForPlatformVersion(platformVersion);
116
- }
117
- catch (error) {
118
- logError(error);
119
- return process.exit(EXIT_CODES.SUCCESS);
120
- }
112
+ repoConfig = await getConfigForPlatformVersion(platformVersion);
121
113
  const projectContentsChoice = projectBase ||
122
114
  (await listPrompt(commands.project.create.prompts.parentComponents, {
123
115
  choices: [
@@ -2,7 +2,6 @@ import { createProject, fetchProject, } from '@hubspot/local-dev-lib/api/project
2
2
  import { isSpecifiedError } from '@hubspot/local-dev-lib/errors/index';
3
3
  import { DEFAULT_POLLING_DELAY } from '../constants.js';
4
4
  import { promptUser } from '../prompts/promptUtils.js';
5
- import { EXIT_CODES } from '../enums/exitCodes.js';
6
5
  import { uiAccountDescription } from '../ui/index.js';
7
6
  import SpinniesManager from '../ui/SpinniesManager.js';
8
7
  import { logError, ApiErrorContext } from '../errorHandlers/index.js';
@@ -85,6 +84,6 @@ export async function ensureProjectExists(accountId, projectName, { forceCreate
85
84
  }
86
85
  }
87
86
  logError(err, new ApiErrorContext({ accountId }));
88
- process.exit(EXIT_CODES.ERROR);
87
+ return { projectExists: false };
89
88
  }
90
89
  }
@@ -5,7 +5,6 @@ import SpinniesManager from '../ui/SpinniesManager.js';
5
5
  import { logError, ApiErrorContext } from '../errorHandlers/index.js';
6
6
  import { uiLine, uiLink, uiAccountDescription } from '../ui/index.js';
7
7
  import { getProjectBuildDetailUrl, getProjectDeployDetailUrl, getProjectActivityUrl, } from './urls.js';
8
- import { EXIT_CODES } from '../enums/exitCodes.js';
9
8
  import { lib } from '../../lang/en.js';
10
9
  import { uiLogger } from '../ui/logger.js';
11
10
  import { APP_FUNCTIONS_PACKAGE_KEY as AppFunctionsPackageKey } from '@hubspot/project-parsing-lib/constants';
@@ -32,8 +31,8 @@ function getSubtaskType(task) {
32
31
  return task.deployType;
33
32
  }
34
33
  function handleTaskStatusError(statusText) {
35
- uiLogger.error(lib.projectBuildAndDeploy.makePollTaskStatusFunc.errorFetchingTaskStatus(statusText.TYPE_KEY === PROJECT_BUILD_TEXT.TYPE_KEY ? 'build' : 'deploy'));
36
- process.exit(EXIT_CODES.ERROR);
34
+ const taskType = statusText.TYPE_KEY === PROJECT_BUILD_TEXT.TYPE_KEY ? 'build' : 'deploy';
35
+ throw new Error(lib.projectBuildAndDeploy.makePollTaskStatusFunc.errorFetchingTaskStatus(taskType));
37
36
  }
38
37
  function makePollTaskStatusFunc({ statusFn, structureFn, statusText, statusStrings, linkToHubSpot, }) {
39
38
  return async function (accountId, taskName, taskId, deployedBuildId, silenceLogs = false) {
@@ -111,97 +110,103 @@ function makePollTaskStatusFunc({ statusFn, structureFn, statusText, statusStrin
111
110
  task.subtasks.forEach((subtask, i) => addTaskSpinner(subtask, 4, i === task.subtasks.length - 1));
112
111
  });
113
112
  }
114
- return new Promise(resolve => {
113
+ return new Promise((resolve, reject) => {
115
114
  const pollInterval = setInterval(async () => {
116
- let taskStatus;
117
115
  try {
118
- const { data } = await statusFn(accountId, taskName, taskId);
119
- taskStatus = data;
120
- }
121
- catch (e) {
122
- uiLogger.debug(e);
123
- logError(e, new ApiErrorContext({
124
- accountId,
125
- projectName: taskName,
126
- }));
127
- handleTaskStatusError(statusText);
128
- }
129
- const subtasks = getSubtasks(taskStatus);
130
- if (!taskStatus || !taskStatus.status || !subtasks) {
131
- handleTaskStatusError(statusText);
132
- }
133
- const { status } = taskStatus;
134
- if (SpinniesManager.hasActiveSpinners()) {
135
- subtasks.forEach(subtask => {
136
- const { id, status } = subtask;
137
- const spinner = SpinniesManager.pick(id);
138
- if (!spinner || spinner.status !== SPINNER_STATUS.SPINNING) {
139
- return;
140
- }
141
- const topLevelTask = structuredTasks.find(t => t.id == id);
142
- if (status === statusText.STATES.SUCCESS ||
143
- status === statusText.STATES.FAILURE) {
144
- const taskStatusText = subtask.status === statusText.STATES.SUCCESS
145
- ? lib.projectBuildAndDeploy.makePollTaskStatusFunc
146
- .successStatusText
147
- : lib.projectBuildAndDeploy.makePollTaskStatusFunc
148
- .failedStatusText;
149
- const hasNewline = spinner?.text?.includes('\n') || Boolean(topLevelTask);
150
- const updatedText = `${spinner?.text?.replace('\n', '')} ${taskStatusText}${hasNewline ? '\n' : ''}`;
151
- if (status === statusText.STATES.SUCCESS) {
152
- SpinniesManager.succeed(id, { text: updatedText });
153
- }
154
- else {
155
- SpinniesManager.fail(id, { text: updatedText });
116
+ let taskStatus;
117
+ try {
118
+ const { data } = await statusFn(accountId, taskName, taskId);
119
+ taskStatus = data;
120
+ }
121
+ catch (e) {
122
+ uiLogger.debug(e);
123
+ logError(e, new ApiErrorContext({
124
+ accountId,
125
+ projectName: taskName,
126
+ }));
127
+ handleTaskStatusError(statusText);
128
+ }
129
+ const subtasks = getSubtasks(taskStatus);
130
+ if (!taskStatus || !taskStatus.status || !subtasks) {
131
+ handleTaskStatusError(statusText);
132
+ }
133
+ const { status } = taskStatus;
134
+ if (SpinniesManager.hasActiveSpinners()) {
135
+ subtasks.forEach(subtask => {
136
+ const { id, status } = subtask;
137
+ const spinner = SpinniesManager.pick(id);
138
+ if (!spinner || spinner.status !== SPINNER_STATUS.SPINNING) {
139
+ return;
156
140
  }
157
- if (topLevelTask) {
158
- topLevelTask.subtasks.forEach(currentSubtask => SpinniesManager.remove(currentSubtask.id));
141
+ const topLevelTask = structuredTasks.find(t => t.id == id);
142
+ if (status === statusText.STATES.SUCCESS ||
143
+ status === statusText.STATES.FAILURE) {
144
+ const taskStatusText = subtask.status === statusText.STATES.SUCCESS
145
+ ? lib.projectBuildAndDeploy.makePollTaskStatusFunc
146
+ .successStatusText
147
+ : lib.projectBuildAndDeploy.makePollTaskStatusFunc
148
+ .failedStatusText;
149
+ const hasNewline = spinner?.text?.includes('\n') || Boolean(topLevelTask);
150
+ const updatedText = `${spinner?.text?.replace('\n', '')} ${taskStatusText}${hasNewline ? '\n' : ''}`;
151
+ if (status === statusText.STATES.SUCCESS) {
152
+ SpinniesManager.succeed(id, { text: updatedText });
153
+ }
154
+ else {
155
+ SpinniesManager.fail(id, { text: updatedText });
156
+ }
157
+ if (topLevelTask) {
158
+ topLevelTask.subtasks.forEach(currentSubtask => SpinniesManager.remove(currentSubtask.id));
159
+ }
159
160
  }
160
- }
161
- });
162
- if (status === statusText.STATES.SUCCESS) {
163
- SpinniesManager.succeed(overallTaskSpinniesKey, {
164
- text: statusStrings.SUCCESS(taskName, displayId),
165
- });
166
- clearInterval(pollInterval);
167
- resolve(taskStatus);
168
- }
169
- else if (status === statusText.STATES.FAILURE) {
170
- SpinniesManager.fail(overallTaskSpinniesKey, {
171
- text: statusStrings.FAIL(taskName, displayId),
172
161
  });
173
- if (!silenceLogs) {
174
- const failedSubtasks = subtasks.filter(subtask => subtask.status === 'FAILURE');
175
- uiLine();
176
- uiLogger.log(`${statusStrings.SUBTASK_FAIL(failedSubtasks.length === 1
177
- ? getSubtaskName(failedSubtasks[0])
178
- : failedSubtasks.length + ' components', displayId)}\n`);
179
- uiLogger.log(lib.projectBuildAndDeploy.makePollTaskStatusFunc.errorSummary);
180
- uiLine();
181
- const displayErrors = failedSubtasks.filter(subtask => subtask?.standardError?.subCategory !==
182
- PROJECT_ERROR_TYPES.SUBBUILD_FAILED &&
183
- subtask?.standardError?.subCategory !==
184
- PROJECT_ERROR_TYPES.SUBDEPLOY_FAILED);
185
- displayErrors.forEach(subTask => {
186
- uiLogger.log(`\n--- ${chalk.bold(getSubtaskName(subTask))} failed with the following error ---`);
187
- uiLogger.error(subTask.errorMessage);
188
- // Log nested errors
189
- if (subTask.standardError && subTask.standardError.errors) {
190
- uiLogger.log('');
191
- subTask.standardError.errors.forEach(error => {
192
- uiLogger.log(error.message);
193
- });
194
- }
162
+ if (status === statusText.STATES.SUCCESS) {
163
+ SpinniesManager.succeed(overallTaskSpinniesKey, {
164
+ text: statusStrings.SUCCESS(taskName, displayId),
195
165
  });
166
+ clearInterval(pollInterval);
167
+ resolve(taskStatus);
168
+ }
169
+ else if (status === statusText.STATES.FAILURE) {
170
+ SpinniesManager.fail(overallTaskSpinniesKey, {
171
+ text: statusStrings.FAIL(taskName, displayId),
172
+ });
173
+ if (!silenceLogs) {
174
+ const failedSubtasks = subtasks.filter(subtask => subtask.status === 'FAILURE');
175
+ uiLine();
176
+ uiLogger.log(`${statusStrings.SUBTASK_FAIL(failedSubtasks.length === 1
177
+ ? getSubtaskName(failedSubtasks[0])
178
+ : failedSubtasks.length + ' components', displayId)}\n`);
179
+ uiLogger.log(lib.projectBuildAndDeploy.makePollTaskStatusFunc.errorSummary);
180
+ uiLine();
181
+ const displayErrors = failedSubtasks.filter(subtask => subtask?.standardError?.subCategory !==
182
+ PROJECT_ERROR_TYPES.SUBBUILD_FAILED &&
183
+ subtask?.standardError?.subCategory !==
184
+ PROJECT_ERROR_TYPES.SUBDEPLOY_FAILED);
185
+ displayErrors.forEach(subTask => {
186
+ uiLogger.log(`\n--- ${chalk.bold(getSubtaskName(subTask))} failed with the following error ---`);
187
+ uiLogger.error(subTask.errorMessage);
188
+ // Log nested errors
189
+ if (subTask.standardError && subTask.standardError.errors) {
190
+ uiLogger.log('');
191
+ subTask.standardError.errors.forEach(error => {
192
+ uiLogger.log(error.message);
193
+ });
194
+ }
195
+ });
196
+ }
197
+ clearInterval(pollInterval);
198
+ resolve(taskStatus);
199
+ }
200
+ else if (!subtasks.length) {
201
+ clearInterval(pollInterval);
202
+ resolve(taskStatus);
196
203
  }
197
- clearInterval(pollInterval);
198
- resolve(taskStatus);
199
- }
200
- else if (!subtasks.length) {
201
- clearInterval(pollInterval);
202
- resolve(taskStatus);
203
204
  }
204
205
  }
206
+ catch (e) {
207
+ clearInterval(pollInterval);
208
+ reject(e);
209
+ }
205
210
  }, DEFAULT_POLLING_DELAY);
206
211
  });
207
212
  };
@@ -4,6 +4,7 @@ type ProjectUploadCallbackFunction<T> = (accountId: number, projectConfig: Proje
4
4
  type ProjectUploadResult<T> = {
5
5
  result?: T;
6
6
  uploadError?: unknown;
7
+ projectNotFound?: boolean;
7
8
  };
8
9
  type HandleProjectUploadArg<T> = {
9
10
  accountId: number;
@@ -8,13 +8,11 @@ import { isTranslationError, translate, } from '@hubspot/project-parsing-lib/tra
8
8
  import { projectContainsHsMetaFiles } from '@hubspot/project-parsing-lib/projects';
9
9
  import SpinniesManager from '../ui/SpinniesManager.js';
10
10
  import { uiAccountDescription } from '../ui/index.js';
11
- import { logError } from '../errorHandlers/index.js';
12
11
  import util from 'node:util';
13
12
  import { lib } from '../../lang/en.js';
14
13
  import { ensureProjectExists } from './ensureProjectExists.js';
15
14
  import { uiLogger } from '../ui/logger.js';
16
15
  import { isV2Project } from './platformVersion.js';
17
- import { EXIT_CODES } from '../enums/exitCodes.js';
18
16
  import ProjectValidationError from '../errors/ProjectValidationError.js';
19
17
  import { walk } from '@hubspot/local-dev-lib/fs';
20
18
  import { LEGACY_CONFIG_FILES } from '../constants.js';
@@ -46,57 +44,50 @@ async function uploadProjectFiles(accountId, projectName, filePath, uploadMessag
46
44
  }
47
45
  export async function handleProjectUpload({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage = '', forceCreate = false, isUploadCommand = false, sendIR = false, skipValidation = false, }) {
48
46
  const srcDir = path.resolve(projectDir, projectConfig.srcDir);
49
- try {
50
- await validateSourceDirectory(srcDir, projectConfig, projectDir);
51
- }
52
- catch (e) {
53
- logError(e);
54
- process.exit(EXIT_CODES.ERROR);
55
- }
56
- try {
57
- await validateNoHSMetaMismatch(srcDir, projectConfig);
58
- }
59
- catch (e) {
60
- logError(e);
61
- process.exit(EXIT_CODES.ERROR);
62
- }
47
+ await validateSourceDirectory(srcDir, projectConfig, projectDir);
48
+ await validateNoHSMetaMismatch(srcDir, projectConfig);
63
49
  const tempFile = tmp.fileSync({ postfix: '.zip' });
64
50
  uiLogger.debug(lib.projectUpload.handleProjectUpload.compressing(tempFile.name));
65
51
  const output = fs.createWriteStream(tempFile.name);
66
52
  const archive = archiver('zip');
67
- const result = new Promise(resolve => output.on('close', async function () {
68
- uiLogger.debug(lib.projectUpload.handleProjectUpload.compressed(archive.pointer()));
69
- let intermediateRepresentation;
70
- if (sendIR) {
71
- try {
72
- intermediateRepresentation = await handleTranslate({
73
- projectDir,
74
- projectConfig,
75
- accountId,
76
- skipValidation,
77
- profile,
78
- });
53
+ const result = new Promise((resolve, reject) => output.on('close', async function () {
54
+ try {
55
+ uiLogger.debug(lib.projectUpload.handleProjectUpload.compressed(archive.pointer()));
56
+ let intermediateRepresentation;
57
+ if (sendIR) {
58
+ try {
59
+ intermediateRepresentation = await handleTranslate({
60
+ projectDir,
61
+ projectConfig,
62
+ accountId,
63
+ skipValidation,
64
+ profile,
65
+ });
66
+ }
67
+ catch (e) {
68
+ return resolve({ uploadError: e });
69
+ }
79
70
  }
80
- catch (e) {
81
- resolve({ uploadError: e });
71
+ const { projectExists } = await ensureProjectExists(accountId, projectConfig.name, {
72
+ forceCreate,
73
+ uploadCommand: isUploadCommand,
74
+ noLogs: true,
75
+ });
76
+ if (!projectExists) {
77
+ uiLogger.log(lib.projectUpload.handleProjectUpload.projectDoesNotExist(accountId));
78
+ return resolve({ projectNotFound: true });
79
+ }
80
+ const { buildId, error } = await uploadProjectFiles(accountId, projectConfig.name, tempFile.name, uploadMessage, projectConfig.platformVersion, intermediateRepresentation);
81
+ if (error) {
82
+ resolve({ uploadError: error });
83
+ }
84
+ else if (callbackFunc) {
85
+ const uploadResult = await callbackFunc(accountId, projectConfig, tempFile, buildId);
86
+ resolve({ result: uploadResult });
82
87
  }
83
88
  }
84
- const { projectExists } = await ensureProjectExists(accountId, projectConfig.name, {
85
- forceCreate,
86
- uploadCommand: isUploadCommand,
87
- noLogs: true,
88
- });
89
- if (!projectExists) {
90
- uiLogger.log(lib.projectUpload.handleProjectUpload.projectDoesNotExist(accountId));
91
- process.exit(EXIT_CODES.SUCCESS);
92
- }
93
- const { buildId, error } = await uploadProjectFiles(accountId, projectConfig.name, tempFile.name, uploadMessage, projectConfig.platformVersion, intermediateRepresentation);
94
- if (error) {
95
- resolve({ uploadError: error });
96
- }
97
- else if (callbackFunc) {
98
- const uploadResult = await callbackFunc(accountId, projectConfig, tempFile, buildId);
99
- resolve({ result: uploadResult });
89
+ catch (e) {
90
+ reject(e);
100
91
  }
101
92
  }));
102
93
  archive.pipe(output);