@hubspot/cli 7.6.0-beta.10 → 7.6.0-beta.12

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 (150) hide show
  1. package/commands/app/__tests__/migrate.test.js +1 -0
  2. package/commands/getStarted.js +70 -22
  3. package/commands/mcp/setup.d.ts +0 -1
  4. package/commands/mcp/setup.js +3 -11
  5. package/commands/project/__tests__/add.test.js +64 -0
  6. package/commands/project/__tests__/create.test.js +57 -0
  7. package/commands/project/__tests__/deploy.test.js +3 -2
  8. package/commands/project/__tests__/devUnifiedFlow.test.js +14 -5
  9. package/commands/project/add.d.ts +1 -1
  10. package/commands/project/add.js +3 -5
  11. package/commands/project/create.js +7 -2
  12. package/commands/project/deploy.js +9 -61
  13. package/commands/project/dev/index.js +33 -13
  14. package/commands/project/dev/unifiedFlow.js +8 -7
  15. package/commands/project/upload.js +2 -2
  16. package/commands/project/validate.js +1 -1
  17. package/commands/project/watch.js +2 -2
  18. package/lang/en.d.ts +36 -13
  19. package/lang/en.js +49 -25
  20. package/lang/en.lyaml +12 -12
  21. package/lib/__tests__/hasFeature.test.js +145 -7
  22. package/lib/__tests__/importData.test.js +1 -1
  23. package/lib/app/migrate.js +9 -2
  24. package/lib/constants.d.ts +2 -0
  25. package/lib/constants.js +2 -0
  26. package/lib/errorHandlers/index.d.ts +4 -0
  27. package/lib/errorHandlers/index.js +1 -1
  28. package/lib/hasFeature.js +6 -0
  29. package/lib/importData.js +1 -1
  30. package/lib/mcp/setup.d.ts +0 -2
  31. package/lib/mcp/setup.js +4 -29
  32. package/lib/projects/__tests__/AppDevModeInterface.test.js +72 -44
  33. package/lib/projects/__tests__/LocalDevProcess.test.js +1 -0
  34. package/lib/projects/__tests__/components.test.js +164 -7
  35. package/lib/projects/__tests__/deploy.test.js +164 -0
  36. package/lib/projects/__tests__/platformVersion.test.d.ts +1 -0
  37. package/lib/projects/__tests__/{buildAndDeploy.test.js → platformVersion.test.js} +2 -2
  38. package/lib/projects/add/__tests__/legacyAddComponent.test.js +49 -6
  39. package/lib/projects/add/__tests__/v3AddComponent.test.js +142 -8
  40. package/lib/projects/add/legacyAddComponent.d.ts +1 -1
  41. package/lib/projects/add/legacyAddComponent.js +5 -1
  42. package/lib/projects/add/v3AddComponent.d.ts +2 -1
  43. package/lib/projects/add/v3AddComponent.js +22 -5
  44. package/lib/projects/components.d.ts +1 -0
  45. package/lib/projects/components.js +27 -1
  46. package/lib/projects/create/__tests__/v3.test.js +97 -9
  47. package/lib/projects/create/index.js +2 -2
  48. package/lib/projects/create/legacy.js +1 -1
  49. package/lib/projects/create/v3.d.ts +2 -2
  50. package/lib/projects/create/v3.js +35 -12
  51. package/lib/projects/deploy.d.ts +13 -0
  52. package/lib/projects/deploy.js +63 -0
  53. package/lib/projects/localDev/AppDevModeInterface.d.ts +5 -3
  54. package/lib/projects/localDev/AppDevModeInterface.js +132 -48
  55. package/lib/projects/localDev/DevServerManagerV2.js +1 -0
  56. package/lib/projects/localDev/LocalDevProcess.js +3 -1
  57. package/lib/projects/localDev/LocalDevState.d.ts +5 -2
  58. package/lib/projects/localDev/LocalDevState.js +9 -1
  59. package/lib/projects/localDev/helpers/account.js +2 -2
  60. package/lib/projects/localDev/helpers/project.d.ts +2 -2
  61. package/lib/projects/localDev/helpers/project.js +6 -8
  62. package/lib/projects/platformVersion.d.ts +1 -0
  63. package/lib/projects/platformVersion.js +10 -0
  64. package/lib/projects/{buildAndDeploy.d.ts → pollProjectBuildAndDeploy.d.ts} +0 -1
  65. package/lib/projects/{buildAndDeploy.js → pollProjectBuildAndDeploy.js} +0 -10
  66. package/lib/projects/upload.js +1 -1
  67. package/lib/projects/urls.d.ts +1 -0
  68. package/lib/projects/urls.js +3 -0
  69. package/lib/prompts/__tests__/projectAddPrompt.test.d.ts +1 -0
  70. package/lib/prompts/__tests__/projectAddPrompt.test.js +143 -0
  71. package/lib/prompts/__tests__/selectProjectTemplatePrompt.test.d.ts +1 -0
  72. package/lib/prompts/__tests__/selectProjectTemplatePrompt.test.js +160 -0
  73. package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +1 -0
  74. package/lib/prompts/importDataFilePathPrompt.js +4 -2
  75. package/lib/prompts/installAppPrompt.d.ts +6 -1
  76. package/lib/prompts/installAppPrompt.js +6 -1
  77. package/lib/prompts/projectAddPrompt.js +1 -1
  78. package/lib/prompts/projectDevTargetAccountPrompt.js +1 -0
  79. package/lib/prompts/promptUtils.d.ts +7 -1
  80. package/lib/prompts/promptUtils.js +14 -1
  81. package/lib/prompts/selectProjectTemplatePrompt.js +1 -1
  82. package/lib/ui/index.js +3 -6
  83. package/mcp-server/server.js +2 -1
  84. package/mcp-server/tools/cms/HsCreateFunctionTool.d.ts +32 -0
  85. package/mcp-server/tools/cms/HsCreateFunctionTool.js +96 -0
  86. package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +38 -0
  87. package/mcp-server/tools/cms/HsCreateModuleTool.js +118 -0
  88. package/mcp-server/tools/cms/HsCreateTemplateTool.d.ts +26 -0
  89. package/mcp-server/tools/cms/HsCreateTemplateTool.js +75 -0
  90. package/mcp-server/tools/cms/HsFunctionLogsTool.d.ts +32 -0
  91. package/mcp-server/tools/cms/HsFunctionLogsTool.js +76 -0
  92. package/mcp-server/tools/cms/HsListFunctionsTool.d.ts +23 -0
  93. package/mcp-server/tools/cms/HsListFunctionsTool.js +58 -0
  94. package/mcp-server/tools/cms/HsListTool.d.ts +23 -0
  95. package/mcp-server/tools/cms/HsListTool.js +58 -0
  96. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.d.ts +1 -0
  97. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +251 -0
  98. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.d.ts +1 -0
  99. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +224 -0
  100. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.d.ts +1 -0
  101. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +206 -0
  102. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.d.ts +1 -0
  103. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +183 -0
  104. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.d.ts +1 -0
  105. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +120 -0
  106. package/mcp-server/tools/cms/__tests__/HsListTool.test.d.ts +1 -0
  107. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +120 -0
  108. package/mcp-server/tools/index.d.ts +1 -0
  109. package/mcp-server/tools/index.js +16 -0
  110. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +3 -3
  111. package/mcp-server/tools/project/AddFeatureToProjectTool.js +3 -3
  112. package/mcp-server/tools/project/CreateProjectTool.d.ts +3 -3
  113. package/mcp-server/tools/project/CreateProjectTool.js +5 -5
  114. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  115. package/mcp-server/tools/project/DocFetchTool.js +3 -3
  116. package/mcp-server/tools/project/DocsSearchTool.js +3 -3
  117. package/mcp-server/tools/project/GetConfigValuesTool.js +4 -4
  118. package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -1
  119. package/mcp-server/tools/project/UploadProjectTools.js +2 -2
  120. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  121. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +1 -1
  122. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -1
  123. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +1 -1
  124. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +3 -3
  125. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +3 -3
  126. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +1 -1
  127. package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +1 -1
  128. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +1 -1
  129. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +1 -1
  130. package/mcp-server/tools/project/constants.d.ts +1 -1
  131. package/mcp-server/tools/project/constants.js +14 -6
  132. package/package.json +5 -4
  133. package/types/LocalDev.d.ts +2 -1
  134. package/types/Projects.d.ts +1 -0
  135. package/types/Prompts.d.ts +1 -0
  136. package/ui/components/BoxWithTitle.d.ts +8 -0
  137. package/ui/components/BoxWithTitle.js +9 -0
  138. package/ui/components/HorizontalSelectPrompt.d.ts +8 -0
  139. package/ui/components/HorizontalSelectPrompt.js +30 -0
  140. package/ui/components/StatusMessageBoxes.d.ts +12 -0
  141. package/ui/components/StatusMessageBoxes.js +31 -0
  142. package/ui/lib/ui-testing-utils.d.ts +9 -0
  143. package/ui/lib/ui-testing-utils.js +47 -0
  144. package/ui/lib/useTerminalSize.d.ts +13 -0
  145. package/ui/lib/useTerminalSize.js +31 -0
  146. package/ui/styles.d.ts +18 -0
  147. package/ui/styles.js +18 -0
  148. package/ui/views/UiSandbox.d.ts +5 -0
  149. package/ui/views/UiSandbox.js +25 -0
  150. /package/lib/projects/__tests__/{buildAndDeploy.test.d.ts → deploy.test.d.ts} +0 -0
@@ -10,6 +10,7 @@ vi.mock('@hubspot/local-dev-lib/config');
10
10
  vi.mock('@hubspot/local-dev-lib/logger');
11
11
  vi.mock('../../../lib/app/migrate');
12
12
  vi.mock('../../../lib/app/migrate_legacy');
13
+ vi.mock('../../../lib/projects/config.js');
13
14
  const mockYargs = yargs;
14
15
  const mockedGetAccountConfig = getAccountConfig;
15
16
  const mockedMigrateApp2023_2 = migrateApp2023_2;
@@ -4,7 +4,7 @@ import open from 'open';
4
4
  import { getCwd } from '@hubspot/local-dev-lib/path';
5
5
  import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
6
6
  import { commands } from '../lang/en.js';
7
- import { trackCommandUsage } from '../lib/usageTracking.js';
7
+ import { trackCommandMetadataUsage, trackCommandUsage, } from '../lib/usageTracking.js';
8
8
  import { EXIT_CODES } from '../lib/enums/exitCodes.js';
9
9
  import { makeYargsBuilder } from '../lib/yargsUtils.js';
10
10
  import { promptUser } from '../lib/prompts/promptUtils.js';
@@ -16,7 +16,8 @@ import { handleProjectUpload } from '../lib/projects/upload.js';
16
16
  import { PROJECT_CONFIG_FILE, GET_STARTED_OPTIONS, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, } from '../lib/constants.js';
17
17
  import { writeProjectConfig, getProjectConfig, validateProjectConfig, } from '../lib/projects/config.js';
18
18
  import { getProjectPackageJsonLocations, installPackages, } from '../lib/dependencyManagement.js';
19
- import { pollProjectBuildAndDeploy, useV3Api, } from '../lib/projects/buildAndDeploy.js';
19
+ import { pollProjectBuildAndDeploy } from '../lib/projects/pollProjectBuildAndDeploy.js';
20
+ import { useV3Api } from '../lib/projects/platformVersion.js';
20
21
  import { openLink } from '../lib/links.js';
21
22
  import { getStaticAuthAppInstallUrl } from '../lib/app/urls.js';
22
23
  import { getEnv } from '@hubspot/local-dev-lib/config';
@@ -27,11 +28,11 @@ export const describe = undefined;
27
28
  async function handler(args) {
28
29
  const { derivedAccountId } = args;
29
30
  const env = getEnv(derivedAccountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD;
30
- // TODO: Put this in constants.ts once we have a defined place for the template before INBOUND
31
- const templateSource = 'robrown-hubspot/hubspot-project-components-ua-app-objects-beta';
32
- trackCommandUsage('get-started', {}, derivedAccountId);
31
+ await trackCommandUsage('get-started', {}, derivedAccountId);
32
+ const accountName = uiAccountDescription(derivedAccountId);
33
33
  uiInfoSection(commands.getStarted.startTitle, () => {
34
34
  uiLogger.log(commands.getStarted.startDescription);
35
+ uiLogger.log(commands.getStarted.guideOverview(accountName));
35
36
  });
36
37
  const { default: selectedOption } = await promptUser([
37
38
  {
@@ -51,6 +52,8 @@ async function handler(args) {
51
52
  default: GET_STARTED_OPTIONS.APP,
52
53
  },
53
54
  ]);
55
+ // Track user's initial choice
56
+ await trackCommandMetadataUsage('get-started', { step: 'select-option', type: selectedOption }, derivedAccountId);
54
57
  if (selectedOption === GET_STARTED_OPTIONS.CMS) {
55
58
  uiLogger.log(' ');
56
59
  uiLogger.log(commands.getStarted.designManager);
@@ -63,6 +66,11 @@ async function handler(args) {
63
66
  message: commands.getStarted.openDesignManagerPrompt,
64
67
  },
65
68
  ]);
69
+ // Track Design Manager browser action
70
+ await trackCommandMetadataUsage('get-started', {
71
+ step: 'open-design-manager',
72
+ type: shouldOpen ? 'opened' : 'declined',
73
+ }, derivedAccountId);
66
74
  if (shouldOpen) {
67
75
  uiLogger.log('');
68
76
  openLink(derivedAccountId, 'design-manager');
@@ -73,36 +81,37 @@ async function handler(args) {
73
81
  else {
74
82
  uiLogger.log(' ');
75
83
  uiLogger.log(commands.getStarted.logs.appSelected);
76
- // 1. Fetch project templates
77
- let latestRepoReleaseTag;
78
84
  const { dest, name } = await projectNameAndDestPrompt(args);
79
- // Specific template for get-started command
80
- const projectTemplate = {
81
- name: 'private-app-get-started-template',
82
- label: 'CRM getting started project with private apps',
83
- path: 'projects/private-app-get-started-template',
84
- };
85
- // 3. Create the project files
86
85
  const projectDest = path.resolve(getCwd(), dest);
87
86
  const { projectConfig: existingProjectConfig, projectDir: existingProjectDir, } = await getProjectConfig(projectDest);
88
87
  if (existingProjectConfig &&
89
88
  existingProjectDir &&
90
89
  projectDest.startsWith(existingProjectDir)) {
90
+ // Track nested project error
91
+ await trackCommandMetadataUsage('get-started', {
92
+ successful: false,
93
+ step: 'project-creation',
94
+ }, derivedAccountId);
91
95
  uiLogger.log(' ');
92
96
  uiLogger.error(commands.project.create.errors.cannotNestProjects(existingProjectDir));
93
97
  process.exit(EXIT_CODES.ERROR);
94
98
  }
95
- const repo = templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH;
96
99
  // 4. Clone the project template from GitHub
97
- // This is temporary until we have the UA template in the main repo
98
100
  try {
99
- await cloneGithubRepo(repo, projectDest, {
100
- sourceDir: projectTemplate.path,
101
- tag: latestRepoReleaseTag,
101
+ await cloneGithubRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, projectDest, {
102
+ sourceDir: '2025.2/private-app-get-started-template',
102
103
  hideLogs: true,
103
104
  });
105
+ await trackCommandMetadataUsage('get-started', {
106
+ successful: true,
107
+ step: 'github-clone',
108
+ }, derivedAccountId);
104
109
  }
105
110
  catch (err) {
111
+ await trackCommandMetadataUsage('get-started', {
112
+ successful: false,
113
+ step: 'github-clone',
114
+ }, derivedAccountId);
106
115
  debugError(err);
107
116
  uiLogger.log(' ');
108
117
  uiLogger.error(commands.project.create.errors.failedToDownloadProject);
@@ -121,6 +130,11 @@ async function handler(args) {
121
130
  uiLogger.log(' ');
122
131
  uiLogger.log(commands.getStarted.prompts.projectCreated.description);
123
132
  uiLogger.log(' ');
133
+ // Track successful project creation
134
+ await trackCommandMetadataUsage('get-started', {
135
+ successful: true,
136
+ step: 'project-creation',
137
+ }, derivedAccountId);
124
138
  // 5. Install dependencies
125
139
  const installLocations = await getProjectPackageJsonLocations(projectDest);
126
140
  try {
@@ -138,7 +152,6 @@ async function handler(args) {
138
152
  uiLogger.log(' ');
139
153
  }
140
154
  // 6. Ask user if they want to upload the project
141
- const accountName = uiAccountDescription(derivedAccountId);
142
155
  const { shouldUpload } = await promptUser([
143
156
  {
144
157
  type: 'confirm',
@@ -147,22 +160,31 @@ async function handler(args) {
147
160
  default: true,
148
161
  },
149
162
  ]);
163
+ // Track upload decision
164
+ await trackCommandMetadataUsage('get-started', {
165
+ step: 'upload-decision',
166
+ type: shouldUpload ? 'upload' : 'skip',
167
+ }, derivedAccountId);
150
168
  if (shouldUpload) {
151
169
  try {
152
170
  // Get the project config for the newly created project
153
171
  const { projectConfig: newProjectConfig, projectDir: newProjectDir } = await getProjectConfig(projectDest);
154
172
  if (!newProjectConfig || !newProjectDir) {
173
+ // Track config file not found error
174
+ await trackCommandMetadataUsage('get-started', {
175
+ successful: false,
176
+ step: 'config-file-not-found',
177
+ }, derivedAccountId);
155
178
  uiLogger.log(' ');
156
179
  uiLogger.error(commands.getStarted.errors.configFileNotFound);
157
180
  process.exit(EXIT_CODES.ERROR);
158
181
  }
159
182
  validateProjectConfig(newProjectConfig, newProjectDir);
160
- const targetAccountId = derivedAccountId;
161
183
  uiLogger.log(' ');
162
184
  uiLogger.log(commands.getStarted.logs.uploadingProject);
163
185
  uiLogger.log(' ');
164
186
  const { result, uploadError } = await handleProjectUpload({
165
- accountId: targetAccountId,
187
+ accountId: derivedAccountId,
166
188
  projectConfig: newProjectConfig,
167
189
  projectDir: newProjectDir,
168
190
  callbackFunc: pollProjectBuildAndDeploy,
@@ -173,11 +195,22 @@ async function handler(args) {
173
195
  skipValidation: false,
174
196
  });
175
197
  if (uploadError) {
198
+ // Track upload failure
199
+ await trackCommandMetadataUsage('get-started', {
200
+ successful: false,
201
+ step: 'upload',
202
+ }, derivedAccountId);
176
203
  uiLogger.log(' ');
177
204
  uiLogger.error(commands.getStarted.errors.uploadFailed);
178
205
  debugError(uploadError);
179
206
  }
180
207
  else if (result) {
208
+ // Track successful upload completion
209
+ await trackCommandMetadataUsage('get-started', {
210
+ successful: true,
211
+ step: 'upload',
212
+ }, derivedAccountId);
213
+ uiLogger.log(' ');
181
214
  uiLogger.success(commands.getStarted.logs.uploadSuccess);
182
215
  const { data: { results }, } = await fetchPublicAppsForPortal(derivedAccountId);
183
216
  const lastCreatedApp = results.sort((a, b) => b.createdAt - a.createdAt)[0];
@@ -192,6 +225,11 @@ async function handler(args) {
192
225
  message: commands.getStarted.openInstallUrl,
193
226
  },
194
227
  ]);
228
+ // Track Developer Overview browser action
229
+ await trackCommandMetadataUsage('get-started', {
230
+ step: 'open-distribution-page',
231
+ type: shouldOpenOverview ? 'opened' : 'declined',
232
+ }, derivedAccountId);
195
233
  if (shouldOpenOverview) {
196
234
  open(getStaticAuthAppInstallUrl({
197
235
  targetAccountId: derivedAccountId,
@@ -207,6 +245,11 @@ async function handler(args) {
207
245
  }
208
246
  }
209
247
  catch (err) {
248
+ // Track upload exception
249
+ await trackCommandMetadataUsage('get-started', {
250
+ successful: false,
251
+ step: 'upload',
252
+ }, derivedAccountId);
210
253
  uiLogger.log(' ');
211
254
  uiLogger.error(commands.getStarted.errors.uploadFailed);
212
255
  debugError(err);
@@ -214,6 +257,11 @@ async function handler(args) {
214
257
  }
215
258
  }
216
259
  }
260
+ // Track successful completion of get-started command
261
+ await trackCommandMetadataUsage('get-started', {
262
+ successful: true,
263
+ step: 'command-completed',
264
+ }, derivedAccountId);
217
265
  process.exit(EXIT_CODES.SUCCESS);
218
266
  }
219
267
  function getStartedBuilder(yargs) {
@@ -1,7 +1,6 @@
1
1
  import { CommonArgs, YargsCommandModule } from '../../types/Yargs.js';
2
2
  interface MCPSetupArgs extends CommonArgs {
3
3
  client?: string[];
4
- addDocsSearch?: boolean;
5
4
  }
6
5
  declare const mcpSetupCommand: YargsCommandModule<unknown, MCPSetupArgs>;
7
6
  export default mcpSetupCommand;
@@ -2,7 +2,7 @@ import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
2
2
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
3
3
  import { commands } from '../../lang/en.js';
4
4
  import { uiLogger } from '../../lib/ui/logger.js';
5
- import { addMcpServerToConfig, addMintlifyMcpServer, supportedTools, } from '../../lib/mcp/setup.js';
5
+ import { addMcpServerToConfig, supportedTools } from '../../lib/mcp/setup.js';
6
6
  import { trackCommandUsage } from '../../lib/usageTracking.js';
7
7
  const command = ['setup', 'update'];
8
8
  const describe = undefined; // Leave hidden for now
@@ -16,10 +16,7 @@ async function handler(args) {
16
16
  }
17
17
  trackCommandUsage('mcp-setup', {}, args.derivedAccountId);
18
18
  try {
19
- const derivedTargets = await addMcpServerToConfig(args.client);
20
- if (args.addDocsSearch) {
21
- await addMintlifyMcpServer(derivedTargets);
22
- }
19
+ await addMcpServerToConfig(args.client);
23
20
  }
24
21
  catch (e) {
25
22
  process.exit(EXIT_CODES.ERROR);
@@ -27,15 +24,10 @@ async function handler(args) {
27
24
  process.exit(EXIT_CODES.SUCCESS);
28
25
  }
29
26
  function setupBuilder(yargs) {
30
- yargs
31
- .option('client', {
27
+ yargs.option('client', {
32
28
  describe: commands.mcp.setup.args.client,
33
29
  type: 'array',
34
30
  choices: [...supportedTools.map(tool => tool.value)],
35
- })
36
- .option('add-docs-search', {
37
- type: 'boolean',
38
- hidden: true,
39
31
  });
40
32
  return yargs;
41
33
  }
@@ -1,7 +1,22 @@
1
1
  import yargs from 'yargs';
2
2
  import projectAddCommand from '../add.js';
3
3
  import { marketplaceDistribution, oAuth, privateDistribution, staticAuth, } from '../../../lib/constants.js';
4
+ import { v3AddComponent } from '../../../lib/projects/add/v3AddComponent.js';
5
+ import { legacyAddComponent } from '../../../lib/projects/add/legacyAddComponent.js';
6
+ import { getProjectConfig } from '../../../lib/projects/config.js';
7
+ import { useV3Api } from '../../../lib/projects/platformVersion.js';
8
+ import { trackCommandUsage } from '../../../lib/usageTracking.js';
4
9
  vi.mock('../../../lib/commonOpts');
10
+ vi.mock('../../../lib/projects/add/v3AddComponent');
11
+ vi.mock('../../../lib/projects/add/legacyAddComponent');
12
+ vi.mock('../../../lib/projects/config');
13
+ vi.mock('../../../lib/projects/platformVersion');
14
+ vi.mock('../../../lib/usageTracking');
15
+ const mockedV3AddComponent = vi.mocked(v3AddComponent);
16
+ const mockedLegacyAddComponent = vi.mocked(legacyAddComponent);
17
+ const mockedGetProjectConfig = vi.mocked(getProjectConfig);
18
+ const mockedUseV3Api = vi.mocked(useV3Api);
19
+ const mockedTrackCommandUsage = vi.mocked(trackCommandUsage);
5
20
  describe('commands/project/add', () => {
6
21
  const yargsMock = yargs;
7
22
  describe('command', () => {
@@ -40,4 +55,53 @@ describe('commands/project/add', () => {
40
55
  });
41
56
  });
42
57
  });
58
+ describe('handler', () => {
59
+ const mockProjectConfig = {
60
+ name: 'test-project',
61
+ srcDir: 'src',
62
+ platformVersion: 'v3',
63
+ };
64
+ const mockProjectDir = '/path/to/project';
65
+ const mockArgs = {
66
+ derivedAccountId: 123,
67
+ name: 'test-component',
68
+ type: 'module',
69
+ };
70
+ beforeEach(() => {
71
+ mockedGetProjectConfig.mockResolvedValue({
72
+ projectConfig: mockProjectConfig,
73
+ projectDir: mockProjectDir,
74
+ });
75
+ mockedTrackCommandUsage.mockResolvedValue();
76
+ mockedV3AddComponent.mockResolvedValue();
77
+ mockedLegacyAddComponent.mockResolvedValue();
78
+ vi.spyOn(process, 'exit').mockImplementation(() => {
79
+ throw new Error('process.exit called');
80
+ });
81
+ });
82
+ it('should call v3AddComponent with accountId for v3 projects', async () => {
83
+ mockedUseV3Api.mockReturnValue(true);
84
+ await expect(projectAddCommand.handler(mockArgs)).rejects.toThrow('process.exit called');
85
+ expect(mockedV3AddComponent).toHaveBeenCalledWith(mockArgs, mockProjectDir, mockProjectConfig, 123);
86
+ expect(mockedLegacyAddComponent).not.toHaveBeenCalled();
87
+ });
88
+ it('should call legacyAddComponent for non-v3 projects', async () => {
89
+ mockedUseV3Api.mockReturnValue(false);
90
+ await expect(projectAddCommand.handler(mockArgs)).rejects.toThrow('process.exit called');
91
+ expect(mockedLegacyAddComponent).toHaveBeenCalledWith(mockArgs, mockProjectDir, mockProjectConfig, 123);
92
+ expect(mockedV3AddComponent).not.toHaveBeenCalled();
93
+ });
94
+ it('should exit with error when project config is not found', async () => {
95
+ mockedGetProjectConfig.mockResolvedValue({
96
+ projectConfig: null,
97
+ projectDir: null,
98
+ });
99
+ const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
100
+ throw new Error('process.exit called');
101
+ });
102
+ await expect(projectAddCommand.handler(mockArgs)).rejects.toThrow('process.exit called');
103
+ expect(mockExit).toHaveBeenCalledWith(1);
104
+ mockExit.mockRestore();
105
+ });
106
+ });
43
107
  });
@@ -27,14 +27,71 @@ describe('commands/project/create', () => {
27
27
  it('should define project creation options', () => {
28
28
  const optionsSpy = vi.spyOn(yargsMock, 'options');
29
29
  const exampleSpy = vi.spyOn(yargsMock, 'example');
30
+ const conflictsSpy = vi.spyOn(yargsMock, 'conflicts');
30
31
  projectCreateCommand.builder(yargsMock);
31
32
  expect(optionsSpy).toHaveBeenCalledWith(expect.objectContaining({
32
33
  name: expect.any(Object),
33
34
  dest: expect.any(Object),
34
35
  template: expect.any(Object),
35
36
  'template-source': expect.any(Object),
37
+ 'platform-version': expect.any(Object),
38
+ 'project-base': expect.any(Object),
39
+ distribution: expect.any(Object),
40
+ auth: expect.any(Object),
41
+ features: expect.any(Object),
36
42
  }));
43
+ expect(conflictsSpy).toHaveBeenCalledWith('template', 'features');
37
44
  expect(exampleSpy).toHaveBeenCalled();
38
45
  });
46
+ it('should define platform version option with correct choices', () => {
47
+ const optionsSpy = vi.spyOn(yargsMock, 'options');
48
+ projectCreateCommand.builder(yargsMock);
49
+ const optionsCall = optionsSpy.mock.calls[0][0];
50
+ expect(optionsCall['platform-version']).toEqual(expect.objectContaining({
51
+ hidden: true,
52
+ type: 'string',
53
+ choices: ['2023.2', '2025.1', '2025.2'],
54
+ default: '2023.2',
55
+ }));
56
+ });
57
+ it('should define project base option with correct choices', () => {
58
+ const optionsSpy = vi.spyOn(yargsMock, 'options');
59
+ projectCreateCommand.builder(yargsMock);
60
+ const optionsCall = optionsSpy.mock.calls[0][0];
61
+ expect(optionsCall['project-base']).toEqual(expect.objectContaining({
62
+ hidden: true,
63
+ type: 'string',
64
+ choices: ['empty', 'app'],
65
+ }));
66
+ });
67
+ it('should define distribution option with correct choices', () => {
68
+ const optionsSpy = vi.spyOn(yargsMock, 'options');
69
+ projectCreateCommand.builder(yargsMock);
70
+ const optionsCall = optionsSpy.mock.calls[0][0];
71
+ expect(optionsCall.distribution).toEqual(expect.objectContaining({
72
+ hidden: true,
73
+ type: 'string',
74
+ choices: ['private', 'marketplace'],
75
+ }));
76
+ });
77
+ it('should define auth option with correct choices', () => {
78
+ const optionsSpy = vi.spyOn(yargsMock, 'options');
79
+ projectCreateCommand.builder(yargsMock);
80
+ const optionsCall = optionsSpy.mock.calls[0][0];
81
+ expect(optionsCall.auth).toEqual(expect.objectContaining({
82
+ hidden: true,
83
+ type: 'string',
84
+ choices: ['oauth', 'static'],
85
+ }));
86
+ });
87
+ it('should define features option as array', () => {
88
+ const optionsSpy = vi.spyOn(yargsMock, 'options');
89
+ projectCreateCommand.builder(yargsMock);
90
+ const optionsCall = optionsSpy.mock.calls[0][0];
91
+ expect(optionsCall.features).toEqual(expect.objectContaining({
92
+ hidden: true,
93
+ type: 'array',
94
+ }));
95
+ });
39
96
  });
40
97
  });
@@ -8,7 +8,7 @@ import * as ui from '../../../lib/ui/index.js';
8
8
  import { addAccountOptions, addConfigOptions, addJSONOutputOptions, addUseEnvironmentOptions, } from '../../../lib/commonOpts.js';
9
9
  import * as projectUtils from '../../../lib/projects/config.js';
10
10
  import * as projectUrlUtils from '../../../lib/projects/urls.js';
11
- import { pollDeployStatus } from '../../../lib/projects/buildAndDeploy.js';
11
+ import { pollDeployStatus } from '../../../lib/projects/pollProjectBuildAndDeploy.js';
12
12
  import * as projectNamePrompt from '../../../lib/prompts/projectNamePrompt.js';
13
13
  import * as promptUtils from '../../../lib/prompts/promptUtils.js';
14
14
  import { trackCommandUsage } from '../../../lib/usageTracking.js';
@@ -23,7 +23,8 @@ vi.mock('../../../lib/commonOpts');
23
23
  vi.mock('../../../lib/validation');
24
24
  vi.mock('../../../lib/projects/config');
25
25
  vi.mock('../../../lib/projects/urls');
26
- vi.mock('../../../lib/projects/buildAndDeploy');
26
+ vi.mock('../../../lib/projects/pollProjectBuildAndDeploy');
27
+ vi.mock('../../../lib/projects/platformVersion');
27
28
  vi.mock('../../../lib/prompts/projectNamePrompt');
28
29
  vi.mock('../../../lib/prompts/promptUtils');
29
30
  vi.mock('../../../lib/usageTracking');
@@ -380,17 +380,26 @@ describe('unifiedProjectDevFlow', () => {
380
380
  beforeEach(() => {
381
381
  isTestAccountOrSandbox.mockReturnValue(false);
382
382
  });
383
- it('should display account type information when prompting', async () => {
384
- selectAccountTypePrompt.mockResolvedValue(HUBSPOT_ACCOUNT_TYPES.STANDARD);
383
+ it('should log info message when default account is a sandbox or test account', async () => {
384
+ isTestAccountOrSandbox.mockReturnValue(true);
385
+ await unifiedProjectDevFlow({
386
+ args: mockArgs,
387
+ targetProjectAccountId: mockTargetProjectAccountId,
388
+ projectConfig: mockProjectConfig,
389
+ projectDir: mockProjectDir,
390
+ });
391
+ expect(uiLogger.log).toHaveBeenCalledWith(commands.project.dev.logs.defaultSandboxOrDevTestTestingAccountExplanation(mockTargetProjectAccountId));
392
+ });
393
+ it('should log info message when testingAccount flag is provided', async () => {
394
+ const providedTestingAccountId = 999;
385
395
  await unifiedProjectDevFlow({
386
396
  args: mockArgs,
387
397
  targetProjectAccountId: mockTargetProjectAccountId,
398
+ providedTargetTestingAccountId: providedTestingAccountId,
388
399
  projectConfig: mockProjectConfig,
389
400
  projectDir: mockProjectDir,
390
401
  });
391
- expect(uiLine).toHaveBeenCalled();
392
- expect(uiLogger.log).toHaveBeenCalledWith(commands.project.dev.logs.accountTypeInformation);
393
- expect(uiLogger.log).toHaveBeenCalledWith(commands.project.dev.logs.learnMoreMessage);
402
+ expect(uiLogger.log).toHaveBeenCalledWith(commands.project.dev.logs.testingAccountFlagExplanation(providedTestingAccountId));
394
403
  });
395
404
  });
396
405
  });
@@ -1,5 +1,5 @@
1
1
  import { YargsCommandModule, CommonArgs } from '../../types/Yargs.js';
2
- type ProjectAddArgs = CommonArgs & {
2
+ export type ProjectAddArgs = CommonArgs & {
3
3
  type?: string;
4
4
  name?: string;
5
5
  features?: string[];
@@ -1,11 +1,10 @@
1
1
  import { logError } from '../../lib/errorHandlers/index.js';
2
- import { trackCommandUsage } from '../../lib/usageTracking.js';
3
2
  import { getProjectConfig } from '../../lib/projects/config.js';
4
3
  import { uiBetaTag } from '../../lib/ui/index.js';
5
4
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
6
5
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
7
6
  import { commands } from '../../lang/en.js';
8
- import { useV3Api } from '../../lib/projects/buildAndDeploy.js';
7
+ import { useV3Api } from '../../lib/projects/platformVersion.js';
9
8
  import { legacyAddComponent } from '../../lib/projects/add/legacyAddComponent.js';
10
9
  import { v3AddComponent } from '../../lib/projects/add/v3AddComponent.js';
11
10
  import { marketplaceDistribution, oAuth, privateDistribution, staticAuth, } from '../../lib/constants.js';
@@ -15,7 +14,6 @@ const describe = uiBetaTag(commands.project.add.describe, false);
15
14
  async function handler(args) {
16
15
  try {
17
16
  const { derivedAccountId } = args;
18
- trackCommandUsage('project-add', undefined, derivedAccountId);
19
17
  const { projectConfig, projectDir } = await getProjectConfig();
20
18
  if (!projectDir || !projectConfig) {
21
19
  uiLogger.error(commands.project.add.error.locationInProject);
@@ -23,10 +21,10 @@ async function handler(args) {
23
21
  }
24
22
  const isV3ProjectCreate = useV3Api(projectConfig.platformVersion);
25
23
  if (isV3ProjectCreate) {
26
- await v3AddComponent(args, projectDir, projectConfig);
24
+ await v3AddComponent(args, projectDir, projectConfig, derivedAccountId);
27
25
  }
28
26
  else {
29
- await legacyAddComponent(args, projectDir, projectConfig);
27
+ await legacyAddComponent(args, projectDir, projectConfig, derivedAccountId);
30
28
  }
31
29
  }
32
30
  catch (e) {
@@ -16,6 +16,8 @@ import { PLATFORM_VERSIONS } from '@hubspot/local-dev-lib/constants/projects';
16
16
  import { commands } from '../../lang/en.js';
17
17
  import { uiLogger } from '../../lib/ui/logger.js';
18
18
  import { handleProjectCreationFlow, } from '../../lib/projects/create/index.js';
19
+ import { getProjectMetadata, } from '@hubspot/project-parsing-lib/src/lib/project.js';
20
+ import { updateHsMetaFilesWithAutoGeneratedFields } from '../../lib/projects/components.js';
19
21
  const command = ['create', 'init'];
20
22
  const describe = uiBetaTag(commands.project.create.describe, false);
21
23
  const { v2023_2, v2025_1, v2025_2 } = PLATFORM_VERSIONS;
@@ -39,7 +41,7 @@ async function handler(args) {
39
41
  type: selectProjectTemplatePromptResponse.projectTemplate?.name ||
40
42
  (selectProjectTemplatePromptResponse.componentTemplates || [])
41
43
  // @ts-expect-error
42
- .map((item) => item.label)
44
+ .map((item) => item.type)
43
45
  .join(','),
44
46
  }, derivedAccountId);
45
47
  const projectDest = path.resolve(getCwd(), projectNameAndDestPromptResponse.dest);
@@ -73,10 +75,13 @@ async function handler(args) {
73
75
  }
74
76
  const projectConfigPath = path.join(projectDest, PROJECT_CONFIG_FILE);
75
77
  const parsedConfigFile = JSON.parse(fs.readFileSync(projectConfigPath).toString());
78
+ const projectName = projectNameAndDestPromptResponse.name;
76
79
  writeProjectConfig(projectConfigPath, {
77
80
  ...parsedConfigFile,
78
- name: projectNameAndDestPromptResponse.name,
81
+ name: projectName,
79
82
  });
83
+ const projectMetadata = await getProjectMetadata(path.join(projectDest, parsedConfigFile.srcDir));
84
+ updateHsMetaFilesWithAutoGeneratedFields(projectName, projectMetadata.hsMetaFiles);
80
85
  // If the template is 'no-template', we need to manually create a src directory
81
86
  if (selectProjectTemplatePromptResponse.projectTemplate?.name ===
82
87
  EMPTY_PROJECT_TEMPLATE_NAME ||