@hubspot/cli 7.7.21-experimental.0 → 7.7.22-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 (75) hide show
  1. package/bin/cli.js +6 -2
  2. package/commands/__tests__/getStarted.test.js +0 -10
  3. package/commands/create/api-sample.d.ts +1 -1
  4. package/commands/create/app.d.ts +1 -1
  5. package/commands/create/function.d.ts +1 -1
  6. package/commands/create/index.d.ts +1 -1
  7. package/commands/create/module.d.ts +1 -1
  8. package/commands/create/react-app.d.ts +1 -1
  9. package/commands/create/template.d.ts +1 -1
  10. package/commands/create/vue-app.d.ts +1 -1
  11. package/commands/create/webpack-serverless.d.ts +1 -1
  12. package/commands/create/website-theme.d.ts +1 -1
  13. package/commands/create.d.ts +1 -1
  14. package/commands/getStarted.js +12 -27
  15. package/commands/mcp/setup.js +1 -0
  16. package/commands/mcp/start.d.ts +4 -1
  17. package/commands/mcp/start.js +8 -3
  18. package/commands/project/__tests__/deploy.test.js +28 -26
  19. package/commands/project/__tests__/devUnifiedFlow.test.js +19 -16
  20. package/commands/project/create.js +2 -2
  21. package/commands/project/deploy.d.ts +3 -2
  22. package/commands/project/deploy.js +63 -54
  23. package/commands/project/dev/unifiedFlow.js +7 -6
  24. package/commands/testAccount/__tests__/createConfig.test.js +0 -3
  25. package/commands/testAccount/create.js +12 -25
  26. package/commands/testAccount/createConfig.d.ts +0 -2
  27. package/commands/testAccount/createConfig.js +8 -9
  28. package/lang/en.d.ts +40 -23
  29. package/lang/en.js +41 -24
  30. package/lang/en.lyaml +0 -26
  31. package/lib/__tests__/buildAccount.test.js +31 -3
  32. package/lib/__tests__/usageTracking.test.js +8 -14
  33. package/lib/buildAccount.d.ts +8 -2
  34. package/lib/buildAccount.js +54 -5
  35. package/lib/mcp/setup.js +26 -5
  36. package/lib/projects/add/legacyAddComponent.js +2 -2
  37. package/lib/projects/add/v3AddComponent.js +2 -2
  38. package/lib/projects/localDev/DevServerManager.js +0 -2
  39. package/lib/projects/localDev/DevServerManagerV2.js +0 -2
  40. package/lib/projects/localDev/helpers.d.ts +1 -1
  41. package/lib/projects/localDev/helpers.js +2 -2
  42. package/lib/projects/upload.js +1 -1
  43. package/lib/prompts/createApiSamplePrompt.d.ts +1 -1
  44. package/lib/prompts/createDeveloperTestAccountConfigPrompt.d.ts +11 -10
  45. package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +73 -31
  46. package/lib/prompts/promptUtils.js +66 -56
  47. package/lib/prompts/sandboxesPrompt.d.ts +1 -1
  48. package/lib/sandboxSync.d.ts +1 -1
  49. package/lib/sandboxes.d.ts +1 -1
  50. package/lib/schema.js +5 -1
  51. package/lib/ui/index.js +1 -1
  52. package/lib/usageTracking.d.ts +11 -0
  53. package/lib/usageTracking.js +66 -75
  54. package/mcp-server/tools/project/AddFeatureToProject.js +4 -1
  55. package/mcp-server/tools/project/CreateProjectTool.d.ts +2 -2
  56. package/mcp-server/tools/project/CreateProjectTool.js +4 -1
  57. package/mcp-server/tools/project/DeployProject.js +4 -1
  58. package/mcp-server/tools/project/GuidedWalkthroughTool.js +4 -1
  59. package/mcp-server/tools/project/UploadProjectTools.js +4 -1
  60. package/mcp-server/tools/project/ValidateProjectTool.js +4 -1
  61. package/mcp-server/tools/project/__tests__/AddFeatureToProject.test.js +1 -0
  62. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -0
  63. package/mcp-server/tools/project/__tests__/DeployProject.test.js +1 -0
  64. package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +1 -0
  65. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +1 -0
  66. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +1 -0
  67. package/mcp-server/utils/__tests__/project.test.js +9 -6
  68. package/mcp-server/utils/project.js +3 -0
  69. package/mcp-server/utils/toolUsageTracking.d.ts +1 -0
  70. package/mcp-server/utils/toolUsageTracking.js +22 -0
  71. package/package.json +4 -4
  72. /package/types/{cms.d.ts → Cms.d.ts} +0 -0
  73. /package/types/{cms.js → Cms.js} +0 -0
  74. /package/types/{sandboxes.d.ts → Sandboxes.d.ts} +0 -0
  75. /package/types/{sandboxes.js → Sandboxes.js} +0 -0
package/lang/en.js CHANGED
@@ -44,7 +44,6 @@ export const commands = {
44
44
  app: 'App',
45
45
  cms: 'CMS',
46
46
  },
47
- installDependencies: 'Would you like to install dependencies now?',
48
47
  uploadProject: 'Would you like to upload your project to HubSpot now?',
49
48
  projectCreated: {
50
49
  title: chalk.bold('Next steps:'),
@@ -54,7 +53,6 @@ export const commands = {
54
53
  logs: {
55
54
  appSelected: `We'll create a new project with a sample app for you.\nProjects are what you can use to create apps, themes, and more at HubSpot.\nUsually you'll use the ${uiCommandReference('hs project create')} command, but we'll go ahead and make one now.`,
56
55
  dependenciesInstalled: 'Dependencies installed successfully.',
57
- dependenciesNotInstalled: `Dependencies not installed. Remember to do this later with the command ${uiCommandReference('hs project install-deps')} from the project directory.`,
58
56
  uploadingProject: 'Uploading your project to HubSpot...',
59
57
  uploadSuccess: 'Project uploaded successfully!',
60
58
  developerOverviewLink: 'Open this link to navigate to your HubSpot developer portal',
@@ -840,6 +838,7 @@ export const commands = {
840
838
  success: (derivedTargets) => `You can now use the HubSpot CLI MCP Server in ${derivedTargets.join(', ')}. ${chalk.bold('You may need to restart these tools to apply the changes')}.`,
841
839
  errors: {
842
840
  needsNode20: `This feature requires node >=20`,
841
+ errorParsingJsonFIle: (filename, errorMessage) => `Unable to update ${chalk.bold(filename)} due to invalid JSON: ${errorMessage}`,
843
842
  },
844
843
  spinners: {
845
844
  failedToConfigure: 'Failed to configure the HubSpot mcp server.',
@@ -1134,19 +1133,19 @@ export const commands = {
1134
1133
  describe: 'Which features to include with the application.',
1135
1134
  },
1136
1135
  },
1137
- creatingComponent: (projectName) => `\nAdding a new component to ${chalk.bold(projectName)}\n`,
1136
+ creatingComponent: (projectName) => `\nAdding a new app feature to ${chalk.bold(projectName)}\n`,
1138
1137
  success: (componentName, multiple = false) => `${componentName || 'An app'} ${multiple ? 'were' : 'was'} successfully added to your ${componentName ? 'app' : 'project'}.`,
1139
1138
  error: {
1140
- failedToDownloadComponent: 'Failed to download project component. Please try again later.',
1139
+ failedToDownloadComponent: 'Failed to download project. Please try again later.',
1141
1140
  maxExceeded: (maxCount) => `This project has the maximum allowed(${maxCount})`,
1142
1141
  authTypeNotAllowed: (authType) => `Auth type '${authType}' not allowed.`,
1143
1142
  distributionNotAllowed: (dist) => `Distribution '${dist}' not allowed.`,
1144
1143
  locationInProject: 'This command must be run from within a project directory.',
1145
- failedToFetchComponentList: 'Failed to fetch the list of available components. Please try again later.',
1144
+ failedToFetchComponentList: 'Failed to fetch the list of available features. Please try again later.',
1146
1145
  projectContainsPublicApp: 'This project contains a public app. This command is currently only compatible with projects that contain private apps.',
1147
1146
  },
1148
1147
  examples: {
1149
- default: 'Create a component within your project',
1148
+ default: 'Create an app feature within your project',
1150
1149
  withFlags: 'Use --name and --type flags to bypass the prompt.',
1151
1150
  },
1152
1151
  },
@@ -1157,25 +1156,24 @@ export const commands = {
1157
1156
  deploying: (path) => `Deploying project at path: ${path}`,
1158
1157
  },
1159
1158
  errors: {
1160
- deploy: (details) => `Deploy error: ${details}`,
1159
+ deploy: 'Deploy error: an unknown error occurred.',
1161
1160
  noBuilds: 'Deploy error: no builds for this project were found.',
1162
1161
  noBuildId: 'You must specify a build to deploy',
1163
- projectNotFound: (projectName, accountIdentifier, command) => `The project ${chalk.bold(projectName)} does not exist in account ${accountIdentifier}. Run ${command} to upload your project files to HubSpot.`,
1164
- buildIdDoesNotExist: (buildId, projectName, linkToProject) => `Build ${buildId} does not exist for project ${chalk.bold(projectName)}. ${linkToProject}`,
1165
- buildAlreadyDeployed: (buildId, linkToProject) => `Build ${buildId} is already deployed. ${linkToProject}`,
1166
- viewProjectsBuilds: 'View project builds in HubSpot',
1162
+ projectNotFound: (accountId, projectName) => `The project ${chalk.bold(projectName)} does not exist in account ${uiAccountDescription(accountId)}. Run ${uiCommandReference('hs project upload')} to upload your project files to HubSpot.`,
1163
+ buildIdDoesNotExist: (accountId, buildId, projectName) => `Build ${buildId} does not exist for project ${chalk.bold(projectName)}. ${uiLink('View project builds in HubSpot', getProjectDetailUrl(projectName, accountId))}`,
1164
+ buildAlreadyDeployed: (accountId, buildId, projectName) => `Build ${buildId} is already deployed. ${uiLink('View project builds in HubSpot', getProjectDetailUrl(projectName, accountId))}`,
1165
+ deployContainsRemovals: (componentName) => `- This deploy would remove the ${chalk.bold(componentName)} component. To proceed, run the deploy command with the ${uiCommandReference('--force')} flag`,
1167
1166
  },
1168
1167
  examples: {
1169
1168
  default: 'Deploy the latest build of the current project',
1170
1169
  withOptions: 'Deploy build 5 of the project my-project',
1171
1170
  },
1172
1171
  options: {
1173
- build: {
1174
- describe: 'Project build ID to be deployed',
1175
- },
1176
- project: {
1177
- describe: 'Project name',
1178
- },
1172
+ build: 'Project build ID to be deployed',
1173
+ project: 'Project name',
1174
+ profile: 'The profile to target with this deploy',
1175
+ force: 'Skip warnings and force deploy. Use this carefully as it will bypass warnings for destructive actions.',
1176
+ deployLatestBuild: 'Deploy the latest build of the current project',
1179
1177
  },
1180
1178
  },
1181
1179
  listBuilds: {
@@ -1803,7 +1801,7 @@ export const commands = {
1803
1801
  start: (testAccountName) => `Creating test account "${chalk.bold(testAccountName)}"...`,
1804
1802
  syncing: 'Test account created! Syncing account data... (may take a few minutes - you can exit and the sync will continue)',
1805
1803
  success: (testAccountName, testAccountId) => `Test account "${chalk.bold(testAccountName)}" successfully created with id: ${chalk.bold(testAccountId)}`,
1806
- failure: 'Failed to sync data into test account. The account may not be ready to use.',
1804
+ createFailure: 'Failed to create test account.',
1807
1805
  },
1808
1806
  options: {
1809
1807
  configPath: 'The path to the test account config',
@@ -1812,7 +1810,7 @@ export const commands = {
1812
1810
  },
1813
1811
  createConfig: {
1814
1812
  describe: 'Create a test account config file.',
1815
- pathPrompt: '[--path] What is the path to the test account config?',
1813
+ pathPrompt: '[--path] Enter the name of the Test Account config file: ',
1816
1814
  errors: {
1817
1815
  pathError: 'Path is required',
1818
1816
  pathFormatError: 'Path must end with .json',
@@ -1825,7 +1823,6 @@ export const commands = {
1825
1823
  options: {
1826
1824
  name: 'The name of the test account',
1827
1825
  description: 'The description of the test account',
1828
- tiers: 'The tiers of the test account',
1829
1826
  path: 'The path to the test account config',
1830
1827
  },
1831
1828
  example: (name) => `Create a test account config file with the name "${name}"`,
@@ -2752,6 +2749,12 @@ export const lib = {
2752
2749
  noLogsFound: 'No logs found.',
2753
2750
  },
2754
2751
  },
2752
+ buildAccount: {
2753
+ createDeveloperTestAccountV3: {
2754
+ syncFailure: 'Failed to sync developer test account',
2755
+ pakFailure: 'Failed to generate personal access key for developer test account',
2756
+ },
2757
+ },
2755
2758
  configOptions: {
2756
2759
  enableOrDisableBooleanFieldPrompt: {
2757
2760
  message: (fieldName) => `Choose to enable or disable ${fieldName}`,
@@ -2834,11 +2837,25 @@ export const lib = {
2834
2837
  keepingCurrentDefault: (accountName) => `Account "${accountName}" will continue to be the default account`,
2835
2838
  },
2836
2839
  createDeveloperTestAccountConfigPrompt: {
2837
- namePrompt: '[--name] What is the name of the test account?',
2838
- descriptionPrompt: '[--description] What is the description of the test account?',
2839
- tiersPrompt: '[--tiers] Which product tiers should the test account have?',
2840
+ namePrompt: (withFlag = true) => `${withFlag ? '[--name] ' : ''}Enter the name of the Test Account:`,
2841
+ descriptionPrompt: (withFlag = true) => `${withFlag ? '[--description] ' : ''}Enter the description of the Test Account:`,
2842
+ useDefaultAccountLevelsPrompt: {
2843
+ message: 'Would you like to create a default Test Account, or customize your own?',
2844
+ default: 'Default (All Hubs, ENTERPRISE)',
2845
+ manual: 'Customize my own',
2846
+ },
2847
+ tiersPrompt: 'Select an option per hub to customize tiers. If left blank, default is ENTERPRISE',
2848
+ hubTypes: {
2849
+ marketing: 'Marketing',
2850
+ ops: 'Ops',
2851
+ service: 'Service',
2852
+ sales: 'Sales',
2853
+ content: 'Content',
2854
+ },
2840
2855
  errors: {
2856
+ allHubsRequired: 'Select a tier for each hub',
2841
2857
  tiersError: 'Cannot have more than one tier per hub',
2858
+ nameRequired: 'The name may not be blank. Please add a name for the Test Account.',
2842
2859
  },
2843
2860
  },
2844
2861
  accountNamePrompt: {
@@ -2961,7 +2978,7 @@ export const lib = {
2961
2978
  },
2962
2979
  },
2963
2980
  projectAddPrompt: {
2964
- selectType: '[--type] Select a component to add: ',
2981
+ selectType: '[--type] Select an app feature to add: ',
2965
2982
  enterName: '[--name] Give your component a name: ',
2966
2983
  errors: {
2967
2984
  nameRequired: 'A component name is required',
package/lang/en.lyaml CHANGED
@@ -681,32 +681,6 @@ en:
681
681
  examples:
682
682
  default: "Create a component within your project"
683
683
  withFlags: "Use --name and --type flags to bypass the prompt."
684
- deploy:
685
- describe: "Deploy a project build."
686
- deployBuildIdPrompt: "[--build] Deploy which build?"
687
- debug:
688
- deploying: "Deploying project at path: {{ path }}"
689
- errors:
690
- deploy: "Deploy error: an unknown error occurred."
691
- noBuilds: "Deploy error: no builds for this project were found."
692
- noBuildId: "You must specify a build to deploy"
693
- projectNotFound: "The project {{ projectName }} does not exist in account {{ accountIdentifier }}. Run {{ command }} to upload your project files to HubSpot."
694
- buildIdDoesNotExist: "Build {{ buildId }} does not exist for project {{ projectName }}. {{ linkToProject }}"
695
- buildAlreadyDeployed: "Build {{ buildId }} is already deployed. {{ linkToProject}}"
696
- viewProjectsBuilds: 'View project builds in HubSpot'
697
- deployContainsRemovals: "- This deploy would remove the {{#bold}}{{ componentName }}{{/bold}} component. To proceed, run the deploy command with the {{ forceFlag }} flag"
698
- examples:
699
- default: "Deploy the latest build of the current project"
700
- withOptions: "Deploy build 5 of the project my-project"
701
- options:
702
- build:
703
- describe: "Project build ID to be deployed"
704
- project:
705
- describe: "Project name"
706
- profile:
707
- describe: "The profile to target with this deploy"
708
- force:
709
- describe: "Skip warnings and force deploy. Use this carefully as it will bypass warnings for destructive actions."
710
684
  listBuilds:
711
685
  describe: "List the project's builds."
712
686
  continueOrExitPrompt: "Press <enter> to load more, or ctrl+c to exit"
@@ -1,6 +1,6 @@
1
1
  import { getAccessToken, updateConfigWithAccessToken, } from '@hubspot/local-dev-lib/personalAccessKey';
2
2
  import { accountNameExistsInConfig, updateAccountConfig, writeConfig, getAccountId, } from '@hubspot/local-dev-lib/config';
3
- import { createDeveloperTestAccount } from '@hubspot/local-dev-lib/api/developerTestAccounts';
3
+ import { createDeveloperTestAccount, fetchDeveloperTestAccountGateSyncStatus, generateDeveloperTestAccountPersonalAccessKey, } from '@hubspot/local-dev-lib/api/developerTestAccounts';
4
4
  import { createSandbox } from '@hubspot/local-dev-lib/api/sandboxHubs';
5
5
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
6
6
  import { personalAccessKeyPrompt } from '../prompts/personalAccessKeyPrompt.js';
@@ -30,6 +30,8 @@ const mockedWriteConfig = writeConfig;
30
30
  const mockedCliAccountNamePrompt = cliAccountNamePrompt;
31
31
  const mockedGetAccountId = getAccountId;
32
32
  const mockedCreateDeveloperTestAccount = createDeveloperTestAccount;
33
+ const mockedFetchDeveloperTestAccountGateSyncStatus = fetchDeveloperTestAccountGateSyncStatus;
34
+ const mockedGenerateDeveloperTestAccountPersonalAccessKey = generateDeveloperTestAccountPersonalAccessKey;
33
35
  const mockedCreateSandbox = createSandbox;
34
36
  describe('lib/buildAccount', () => {
35
37
  describe('saveAccountToConfig()', () => {
@@ -97,11 +99,37 @@ describe('lib/buildAccount', () => {
97
99
  expect(result).toBe('test-account-with-new-name');
98
100
  });
99
101
  });
102
+ describe('createDeveloperTestAccountV3()', () => {
103
+ const parentAccountId = 123456;
104
+ const mockDeveoperTestAccountConfig = {
105
+ accountName: 'Developer Test Account',
106
+ description: 'Test Account created by the HubSpot CLI',
107
+ };
108
+ beforeEach(() => {
109
+ mockedCreateDeveloperTestAccount.mockResolvedValue({
110
+ data: { id: 123456 },
111
+ });
112
+ mockedFetchDeveloperTestAccountGateSyncStatus.mockResolvedValue({
113
+ data: { status: 'SUCCESS' },
114
+ });
115
+ mockedGenerateDeveloperTestAccountPersonalAccessKey.mockResolvedValue({
116
+ data: { personalAccessKey: 'test-key' },
117
+ });
118
+ });
119
+ it('should create a developer test account successfully', async () => {
120
+ const result = await buildAccount.createDeveloperTestAccountV3(parentAccountId, mockDeveoperTestAccountConfig);
121
+ expect(result).toEqual({
122
+ accountName: mockDeveoperTestAccountConfig.accountName,
123
+ accountId: 123456,
124
+ personalAccessKey: 'test-key',
125
+ });
126
+ }, 10000);
127
+ });
100
128
  describe('buildDeveloperTestAccount()', () => {
101
129
  const mockParentAccountConfig = {
102
- name: 'Developer Test Account',
130
+ name: 'Developer Account',
103
131
  accountId: 123456,
104
- accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST,
132
+ accountType: HUBSPOT_ACCOUNT_TYPES.APP_DEVELOPER,
105
133
  env: 'prod',
106
134
  };
107
135
  const mockDeveloperTestAccount = {
@@ -30,8 +30,6 @@ describe('lib/usageTracking', () => {
30
30
  });
31
31
  it('should track command usage with default auth type', async () => {
32
32
  await trackCommandUsage(mockCommand, {}, mockAccountId);
33
- // Allow setImmediate to execute
34
- await new Promise(resolve => setImmediate(resolve));
35
33
  expect(mockedTrackUsage).toHaveBeenCalledWith('cli-interaction', 'INTERACTION', expect.objectContaining({
36
34
  action: 'cli-command',
37
35
  command: mockCommand,
@@ -45,18 +43,16 @@ describe('lib/usageTracking', () => {
45
43
  it('should track command usage with custom auth type', async () => {
46
44
  mockedGetAccountConfig.mockReturnValue({ authType: 'oauth2' });
47
45
  await trackCommandUsage(mockCommand, {}, mockAccountId);
48
- // Allow setImmediate to execute
49
- await new Promise(resolve => setImmediate(resolve));
50
46
  expect(mockedTrackUsage).toHaveBeenCalledWith('cli-interaction', 'INTERACTION', expect.objectContaining({
51
47
  authType: 'oauth2',
52
48
  }), mockAccountId);
53
49
  });
54
50
  it('should handle tracking errors gracefully', async () => {
55
51
  const error = new Error('Tracking failed');
56
- mockedTrackUsage.mockRejectedValue(error);
52
+ mockedTrackUsage.mockImplementationOnce(() => {
53
+ throw error;
54
+ });
57
55
  await trackCommandUsage(mockCommand);
58
- // Allow setImmediate to execute
59
- await new Promise(resolve => setImmediate(resolve));
60
56
  expect(mockedLogger.debug).toHaveBeenCalledWith(expect.stringContaining(error.message));
61
57
  });
62
58
  });
@@ -76,7 +72,7 @@ describe('lib/usageTracking', () => {
76
72
  nodeVersion: mockNodeVersion,
77
73
  nodeMajorVersion: 'v16',
78
74
  version,
79
- }));
75
+ }), undefined);
80
76
  });
81
77
  it('should track main help usage without command', async () => {
82
78
  await trackHelpUsage('');
@@ -99,7 +95,7 @@ describe('lib/usageTracking', () => {
99
95
  nodeVersion: mockNodeVersion,
100
96
  nodeMajorVersion: 'v16',
101
97
  version,
102
- }));
98
+ }), undefined);
103
99
  });
104
100
  });
105
101
  describe('trackAuthAction()', () => {
@@ -137,8 +133,6 @@ describe('lib/usageTracking', () => {
137
133
  });
138
134
  it('should track command metadata usage', async () => {
139
135
  await trackCommandMetadataUsage(mockCommand, mockMeta, mockAccountId);
140
- // Allow setImmediate to execute
141
- await new Promise(resolve => setImmediate(resolve));
142
136
  expect(mockedTrackUsage).toHaveBeenCalledWith('cli-interaction', 'INTERACTION', expect.objectContaining({
143
137
  action: 'cli-command-metadata',
144
138
  command: mockCommand,
@@ -154,17 +148,17 @@ describe('lib/usageTracking', () => {
154
148
  it('should return "macos" for darwin platform', async () => {
155
149
  Object.defineProperty(process, 'platform', { value: 'darwin' });
156
150
  await trackHelpUsage('test');
157
- expect(mockedTrackUsage).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({ os: 'macos' }));
151
+ expect(mockedTrackUsage).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({ os: 'macos' }), undefined);
158
152
  });
159
153
  it('should return "windows" for win32 platform', async () => {
160
154
  Object.defineProperty(process, 'platform', { value: 'win32' });
161
155
  await trackHelpUsage('test');
162
- expect(mockedTrackUsage).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({ os: 'windows' }));
156
+ expect(mockedTrackUsage).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({ os: 'windows' }), undefined);
163
157
  });
164
158
  it('should return platform name for other platforms', async () => {
165
159
  Object.defineProperty(process, 'platform', { value: 'linux' });
166
160
  await trackHelpUsage('test');
167
- expect(mockedTrackUsage).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({ os: 'linux' }));
161
+ expect(mockedTrackUsage).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({ os: 'linux' }), undefined);
168
162
  });
169
163
  });
170
164
  });
@@ -1,9 +1,15 @@
1
+ import { DeveloperTestAccountConfig } from '@hubspot/local-dev-lib/types/developerTestAccounts';
1
2
  import { Environment } from '@hubspot/local-dev-lib/types/Config';
2
3
  import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts';
3
4
  import { SandboxResponse } from '@hubspot/local-dev-lib/types/Sandbox';
4
- import { SandboxAccountType } from '../types/sandboxes.js';
5
+ import { SandboxAccountType } from '../types/Sandboxes.js';
5
6
  export declare function saveAccountToConfig(accountId: number | undefined, accountName: string, env: Environment, personalAccessKey?: string, force?: boolean): Promise<string>;
6
- export declare function buildDeveloperTestAccount(testAccountName: string, parentAccountConfig: CLIAccount, env: Environment, portalLimit: number): Promise<number>;
7
+ export declare function createDeveloperTestAccountV3(parentAccountId: number, testAccountConfig: DeveloperTestAccountConfig): Promise<{
8
+ accountName: string;
9
+ accountId?: number;
10
+ personalAccessKey?: string;
11
+ }>;
12
+ export declare function buildDeveloperTestAccount(testAccountName: string, parentAccountConfig: CLIAccount, env: Environment, portalLimit: number, useV3?: boolean): Promise<number>;
7
13
  type SandboxAccount = SandboxResponse & {
8
14
  name: string;
9
15
  };
@@ -2,16 +2,19 @@ import { getAccessToken, updateConfigWithAccessToken, } from '@hubspot/local-dev
2
2
  import { accountNameExistsInConfig, updateAccountConfig, writeConfig, getAccountId, } from '@hubspot/local-dev-lib/config';
3
3
  import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier';
4
4
  import { logger } from '@hubspot/local-dev-lib/logger';
5
- import { createDeveloperTestAccount } from '@hubspot/local-dev-lib/api/developerTestAccounts';
5
+ import { createDeveloperTestAccount, fetchDeveloperTestAccountGateSyncStatus, generateDeveloperTestAccountPersonalAccessKey, } from '@hubspot/local-dev-lib/api/developerTestAccounts';
6
6
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
7
7
  import { createSandbox } from '@hubspot/local-dev-lib/api/sandboxHubs';
8
8
  import { personalAccessKeyPrompt } from './prompts/personalAccessKeyPrompt.js';
9
+ import { createDeveloperTestAccountConfigPrompt } from './prompts/createDeveloperTestAccountConfigPrompt.js';
9
10
  import { i18n } from './lang.js';
10
11
  import { cliAccountNamePrompt } from './prompts/accountNamePrompt.js';
11
12
  import SpinniesManager from './ui/SpinniesManager.js';
12
13
  import { debugError, logError } from './errorHandlers/index.js';
13
14
  import { SANDBOX_API_TYPE_MAP, handleSandboxCreateError } from './sandboxes.js';
14
15
  import { handleDeveloperTestAccountCreateError } from './developerTestAccounts.js';
16
+ import { lib } from '../lang/en.js';
17
+ import { poll } from './polling.js';
15
18
  export async function saveAccountToConfig(accountId, accountName, env, personalAccessKey, force = false) {
16
19
  if (!personalAccessKey) {
17
20
  const configData = await personalAccessKeyPrompt({
@@ -52,12 +55,51 @@ export async function saveAccountToConfig(accountId, accountName, env, personalA
52
55
  logger.log('');
53
56
  return validName;
54
57
  }
55
- export async function buildDeveloperTestAccount(testAccountName, parentAccountConfig, env, portalLimit) {
58
+ export async function createDeveloperTestAccountV3(parentAccountId, testAccountConfig) {
59
+ const result = {
60
+ accountName: testAccountConfig.accountName,
61
+ };
62
+ const { data } = await createDeveloperTestAccount(parentAccountId, testAccountConfig);
63
+ result.accountId = data.id;
64
+ try {
65
+ await poll(() => fetchDeveloperTestAccountGateSyncStatus(parentAccountId, result.accountId), {
66
+ successStates: ['SUCCESS'],
67
+ errorStates: [],
68
+ });
69
+ }
70
+ catch (err) {
71
+ debugError(err);
72
+ throw new Error(lib.buildAccount.createDeveloperTestAccountV3.syncFailure);
73
+ }
74
+ // HACK: The status endpoint sometimes returns an early success status.
75
+ // Sleep for an extra 5 seconds to make sure the sync is actually complete.
76
+ await new Promise(resolve => setTimeout(resolve, 5000));
77
+ try {
78
+ // Attempt to generate a new personal access key for the test account now that gate sync is complete.
79
+ const { data } = await generateDeveloperTestAccountPersonalAccessKey(parentAccountId, result.accountId);
80
+ result.personalAccessKey = data.personalAccessKey;
81
+ }
82
+ catch (err) {
83
+ debugError(err);
84
+ throw new Error(lib.buildAccount.createDeveloperTestAccountV3.pakFailure);
85
+ }
86
+ return result;
87
+ }
88
+ export async function buildDeveloperTestAccount(testAccountName, parentAccountConfig, env, portalLimit, useV3 = false) {
56
89
  const id = getAccountIdentifier(parentAccountConfig);
57
90
  const parentAccountId = getAccountId(id);
91
+ let testAccountConfig = {
92
+ accountName: testAccountName,
93
+ };
58
94
  if (!parentAccountId) {
59
95
  throw new Error(i18n(`lib.developerTestAccount.create.loading.fail`));
60
96
  }
97
+ if (useV3) {
98
+ testAccountConfig = await createDeveloperTestAccountConfigPrompt({
99
+ name: testAccountConfig.accountName,
100
+ description: 'Test Account created by the HubSpot CLI',
101
+ }, false);
102
+ }
61
103
  SpinniesManager.init({
62
104
  succeedColor: 'white',
63
105
  });
@@ -70,9 +112,16 @@ export async function buildDeveloperTestAccount(testAccountName, parentAccountCo
70
112
  let developerTestAccountId;
71
113
  let developerTestAccountPersonalAccessKey;
72
114
  try {
73
- const { data } = await createDeveloperTestAccount(parentAccountId, testAccountName);
74
- developerTestAccountId = data.id;
75
- developerTestAccountPersonalAccessKey = data.personalAccessKey;
115
+ if (useV3) {
116
+ const result = await createDeveloperTestAccountV3(parentAccountId, testAccountConfig);
117
+ developerTestAccountId = result.accountId;
118
+ developerTestAccountPersonalAccessKey = result.personalAccessKey;
119
+ }
120
+ else {
121
+ const { data } = await createDeveloperTestAccount(parentAccountId, testAccountName);
122
+ developerTestAccountId = data.id;
123
+ developerTestAccountPersonalAccessKey = data.personalAccessKey;
124
+ }
76
125
  SpinniesManager.succeed('buildDeveloperTestAccount', {
77
126
  text: i18n(`lib.developerTestAccount.create.loading.succeed`, {
78
127
  accountName: testAccountName,
package/lib/mcp/setup.js CHANGED
@@ -100,9 +100,9 @@ function setupMcpConfigFile(config) {
100
100
  fs.writeFileSync(config.configPath, JSON.stringify({}, null, 2));
101
101
  }
102
102
  let mcpConfig = {};
103
+ let configContent;
103
104
  try {
104
- const configContent = fs.readFileSync(config.configPath, 'utf8');
105
- mcpConfig = JSON.parse(configContent);
105
+ configContent = fs.readFileSync(config.configPath, 'utf8');
106
106
  }
107
107
  catch (error) {
108
108
  SpinniesManager.fail('spinner', {
@@ -111,6 +111,22 @@ function setupMcpConfigFile(config) {
111
111
  logError(error);
112
112
  return false;
113
113
  }
114
+ try {
115
+ // In the event the file exists, but is empty, initialize it to and empty object
116
+ if (configContent.trim() === '') {
117
+ mcpConfig = {};
118
+ }
119
+ else {
120
+ mcpConfig = JSON.parse(configContent);
121
+ }
122
+ }
123
+ catch (error) {
124
+ SpinniesManager.fail('spinner', {
125
+ text: config.failedMessage,
126
+ });
127
+ uiLogger.error(commands.mcp.setup.errors.errorParsingJsonFIle(config.configPath, error instanceof Error ? error.message : `${error}`));
128
+ return false;
129
+ }
114
130
  // Initialize mcpServers if it doesn't exist
115
131
  if (!mcpConfig.mcpServers) {
116
132
  mcpConfig.mcpServers = {};
@@ -145,7 +161,7 @@ export async function setupClaudeCode(mcpCommand = defaultMcpCommand) {
145
161
  // Run claude mcp add command
146
162
  const mcpConfig = JSON.stringify({
147
163
  type: 'stdio',
148
- ...mcpCommand,
164
+ ...buildCommandWithAgentString(mcpCommand, claudeCode),
149
165
  });
150
166
  const { stdout } = await execAsync('claude mcp list');
151
167
  if (stdout.includes(mcpServerName)) {
@@ -191,7 +207,7 @@ export function setupCursor(mcpCommand = defaultMcpCommand) {
191
207
  configuringMessage: commands.mcp.setup.spinners.configuringCursor,
192
208
  configuredMessage: commands.mcp.setup.spinners.configuredCursor,
193
209
  failedMessage: commands.mcp.setup.spinners.failedToConfigureCursor,
194
- mcpCommand,
210
+ mcpCommand: buildCommandWithAgentString(mcpCommand, cursor),
195
211
  });
196
212
  }
197
213
  export function setupWindsurf(mcpCommand = defaultMcpCommand) {
@@ -201,6 +217,11 @@ export function setupWindsurf(mcpCommand = defaultMcpCommand) {
201
217
  configuringMessage: commands.mcp.setup.spinners.configuringWindsurf,
202
218
  configuredMessage: commands.mcp.setup.spinners.configuredWindsurf,
203
219
  failedMessage: commands.mcp.setup.spinners.failedToConfigureWindsurf,
204
- mcpCommand,
220
+ mcpCommand: buildCommandWithAgentString(mcpCommand, windsurf),
205
221
  });
206
222
  }
223
+ function buildCommandWithAgentString(mcpCommand, agent) {
224
+ const mcpCommandCopy = structuredClone(mcpCommand);
225
+ mcpCommandCopy.args.push('--ai-agent', agent);
226
+ return mcpCommandCopy;
227
+ }
@@ -5,7 +5,7 @@ import { commands } from '../../../lang/en.js';
5
5
  import { getProjectComponentListFromRepo } from '../create/legacy.js';
6
6
  import { projectAddPrompt } from '../../prompts/projectAddPrompt.js';
7
7
  import path from 'path';
8
- import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH } from '../../constants.js';
8
+ import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, DEFAULT_PROJECT_TEMPLATE_BRANCH } from '../../constants.js';
9
9
  import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
10
10
  import { uiLogger } from '../../ui/logger.js';
11
11
  export async function legacyAddComponent(args, projectDir, projectConfig) {
@@ -31,7 +31,7 @@ export async function legacyAddComponent(args, projectDir, projectConfig) {
31
31
  const componentPath = path.join(projectDir, projectConfig.srcDir, projectAddPromptResponse.name);
32
32
  await cloneGithubRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, componentPath, {
33
33
  sourceDir: projectAddPromptResponse.componentTemplate.path,
34
- branch: 'main',
34
+ branch: DEFAULT_PROJECT_TEMPLATE_BRANCH,
35
35
  hideLogs: true,
36
36
  });
37
37
  uiLogger.success(commands.project.add.success(projectAddPromptResponse.name));
@@ -4,7 +4,7 @@ import { calculateComponentTemplateChoices, createV3App, PROJECT_WITH_APP, } fro
4
4
  import path from 'path';
5
5
  import fs from 'fs';
6
6
  import { projectAddPromptV3 } from '../../prompts/projectAddPrompt.js';
7
- import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH } from '../../constants.js';
7
+ import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, DEFAULT_PROJECT_TEMPLATE_BRANCH } from '../../constants.js';
8
8
  import { handleComponentCollision } from '../components.js';
9
9
  import { getProjectMetadata, } from '@hubspot/project-parsing-lib/src/lib/project.js';
10
10
  import { AppKey } from '@hubspot/project-parsing-lib/src/lib/constants.js';
@@ -67,7 +67,7 @@ export async function v3AddComponent(args, projectDir, projectConfig) {
67
67
  await cloneGithubRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, projectDir, {
68
68
  sourceDir: components,
69
69
  hideLogs: true,
70
- branch: 'main',
70
+ branch: DEFAULT_PROJECT_TEMPLATE_BRANCH,
71
71
  handleCollision: handleComponentCollision,
72
72
  });
73
73
  uiLogger.success(commands.project.add.success(projectAddPromptResponse.componentTemplate
@@ -1,5 +1,4 @@
1
1
  import { logger } from '@hubspot/local-dev-lib/logger';
2
- import { promptUser } from '../../prompts/promptUtils.js';
3
2
  import { DevModeInterface as UIEDevModeInterface } from '@hubspot/ui-extensions-dev-server';
4
3
  import { startPortManagerServer, stopPortManagerServer, requestPorts, } from '@hubspot/local-dev-lib/portManager';
5
4
  import { getHubSpotApiOrigin, getHubSpotWebsiteOrigin, } from '@hubspot/local-dev-lib/urls';
@@ -69,7 +68,6 @@ class DevServerManager {
69
68
  await serverInterface.setup({
70
69
  components: compatibleComponents,
71
70
  onUploadRequired,
72
- promptUser,
73
71
  logger,
74
72
  urls: {
75
73
  api: getHubSpotApiOrigin(env),
@@ -1,5 +1,4 @@
1
1
  import { logger } from '@hubspot/local-dev-lib/logger';
2
- import { promptUser } from '../../prompts/promptUtils.js';
3
2
  import { startPortManagerServer, stopPortManagerServer, } from '@hubspot/local-dev-lib/portManager';
4
3
  import { getHubSpotApiOrigin, getHubSpotWebsiteOrigin, } from '@hubspot/local-dev-lib/urls';
5
4
  import { getAccountConfig } from '@hubspot/local-dev-lib/config';
@@ -35,7 +34,6 @@ class DevServerManagerV2 {
35
34
  // @TODO: In the future, update UIE Dev Server to use LocalDevState
36
35
  await serverInterface.setup({
37
36
  components: this.localDevState.projectNodes,
38
- promptUser,
39
37
  logger,
40
38
  urls: {
41
39
  api: getHubSpotApiOrigin(env),
@@ -11,7 +11,7 @@ export declare function checkIfParentAccountIsAuthed(accountConfig: CLIAccount):
11
11
  export declare function checkIfAccountFlagIsSupported(accountConfig: CLIAccount, hasPublicApps: boolean): void;
12
12
  export declare function suggestRecommendedNestedAccount(accounts: CLIAccount[], accountConfig: CLIAccount, hasPublicApps: boolean): Promise<ProjectDevTargetAccountPromptResponse>;
13
13
  export declare function createSandboxForLocalDev(accountId: number, accountConfig: CLIAccount, env: Environment): Promise<number>;
14
- export declare function createDeveloperTestAccountForLocalDev(accountId: number, accountConfig: CLIAccount, env: Environment): Promise<number>;
14
+ export declare function createDeveloperTestAccountForLocalDev(accountId: number, accountConfig: CLIAccount, env: Environment, useV3?: boolean): Promise<number>;
15
15
  export declare function useExistingDevTestAccount(env: Environment, account: DeveloperTestAccount): Promise<void>;
16
16
  export declare function createNewProjectForLocalDev(projectConfig: ProjectConfig, targetAccountId: number, shouldCreateWithoutConfirmation: boolean, hasPublicApps: boolean): Promise<Project>;
17
17
  export declare function createInitialBuildForNewProject(projectConfig: ProjectConfig, projectDir: string, targetAccountId: number, sendIR?: boolean, profile?: string): Promise<Build>;
@@ -134,7 +134,7 @@ export async function createSandboxForLocalDev(accountId, accountConfig, env) {
134
134
  }
135
135
  }
136
136
  // Create a developer test account and return its accountId
137
- export async function createDeveloperTestAccountForLocalDev(accountId, accountConfig, env) {
137
+ export async function createDeveloperTestAccountForLocalDev(accountId, accountConfig, env, useV3 = false) {
138
138
  let currentPortalCount = 0;
139
139
  let maxTestPortals = 10;
140
140
  try {
@@ -164,7 +164,7 @@ export async function createDeveloperTestAccountForLocalDev(accountId, accountCo
164
164
  accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST,
165
165
  });
166
166
  trackCommandMetadataUsage('developer-test-account-create', { step: 'project-dev' }, accountId);
167
- const result = await buildDeveloperTestAccount(name, accountConfig, env, maxTestPortals);
167
+ const result = await buildDeveloperTestAccount(name, accountConfig, env, maxTestPortals, useV3);
168
168
  return result;
169
169
  }
170
170
  catch (err) {
@@ -16,7 +16,7 @@ import { useV3Api } from './buildAndDeploy.js';
16
16
  import { EXIT_CODES } from '../enums/exitCodes.js';
17
17
  async function uploadProjectFiles(accountId, projectName, filePath, uploadMessage, platformVersion, intermediateRepresentation) {
18
18
  SpinniesManager.init({});
19
- const accountIdentifier = uiAccountDescription(accountId);
19
+ const accountIdentifier = uiAccountDescription(accountId) || `${accountId}`;
20
20
  SpinniesManager.add('upload', {
21
21
  text: lib.projectUpload.uploadProjectFiles.add(projectName, accountIdentifier),
22
22
  succeedColor: 'white',
@@ -1,4 +1,4 @@
1
- import { ApiSampleConfig } from '../../types/cms.js';
1
+ import { ApiSampleConfig } from '../../types/Cms.js';
2
2
  type SampleTypePromptResponse = {
3
3
  sampleType?: string;
4
4
  };