@hubspot/cli 7.7.27-experimental.1 → 7.7.28-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 (131) hide show
  1. package/README.md +0 -4
  2. package/api/__tests__/migrate.test.js +5 -5
  3. package/api/migrate.d.ts +10 -4
  4. package/api/migrate.js +2 -2
  5. package/commands/__tests__/create.test.js +20 -0
  6. package/commands/__tests__/testAccount.test.js +2 -0
  7. package/commands/app/__tests__/migrate.test.js +1 -0
  8. package/commands/create/function.js +2 -2
  9. package/commands/create/module.js +2 -2
  10. package/commands/create/template.js +2 -2
  11. package/commands/create.js +47 -0
  12. package/commands/getStarted.js +66 -4
  13. package/commands/mcp/setup.d.ts +0 -1
  14. package/commands/mcp/setup.js +3 -11
  15. package/commands/project/__tests__/create.test.js +57 -0
  16. package/commands/project/__tests__/devUnifiedFlow.test.js +18 -30
  17. package/commands/project/create.js +6 -1
  18. package/commands/project/deploy.js +31 -1
  19. package/commands/project/dev/deprecatedFlow.js +2 -1
  20. package/commands/project/dev/index.js +32 -12
  21. package/commands/project/dev/unifiedFlow.d.ts +1 -1
  22. package/commands/project/dev/unifiedFlow.js +10 -16
  23. package/commands/project/profile/delete.js +26 -14
  24. package/commands/project/upload.d.ts +2 -2
  25. package/commands/project/upload.js +1 -1
  26. package/commands/testAccount/__tests__/importData.test.d.ts +1 -0
  27. package/commands/testAccount/__tests__/importData.test.js +93 -0
  28. package/commands/testAccount/create.js +23 -13
  29. package/commands/testAccount/importData.d.ts +9 -0
  30. package/commands/testAccount/importData.js +61 -0
  31. package/commands/testAccount.js +2 -0
  32. package/lang/en.d.ts +160 -46
  33. package/lang/en.js +175 -59
  34. package/lang/en.lyaml +35 -14
  35. package/lib/__tests__/importData.test.d.ts +1 -0
  36. package/lib/__tests__/importData.test.js +89 -0
  37. package/lib/accountTypes.js +2 -3
  38. package/lib/app/__tests__/migrate.test.js +81 -36
  39. package/lib/app/migrate.d.ts +17 -4
  40. package/lib/app/migrate.js +97 -19
  41. package/lib/constants.d.ts +1 -0
  42. package/lib/constants.js +1 -0
  43. package/lib/hasFeature.d.ts +1 -0
  44. package/lib/hasFeature.js +7 -0
  45. package/lib/importData.d.ts +3 -0
  46. package/lib/importData.js +50 -0
  47. package/lib/mcp/setup.d.ts +0 -2
  48. package/lib/mcp/setup.js +0 -24
  49. package/lib/process.js +15 -4
  50. package/lib/projectProfiles.d.ts +1 -1
  51. package/lib/projectProfiles.js +10 -2
  52. package/lib/projects/__tests__/AppDevModeInterface.test.js +3 -3
  53. package/lib/projects/__tests__/LocalDevProcess.test.js +5 -95
  54. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +6 -6
  55. package/lib/projects/__tests__/components.test.js +164 -7
  56. package/lib/projects/__tests__/localDevProjectHelpers.test.d.ts +1 -0
  57. package/lib/projects/__tests__/localDevProjectHelpers.test.js +118 -0
  58. package/lib/projects/add/v3AddComponent.js +16 -4
  59. package/lib/projects/components.d.ts +1 -0
  60. package/lib/projects/components.js +27 -1
  61. package/lib/projects/localDev/AppDevModeInterface.js +35 -3
  62. package/lib/projects/localDev/LocalDevLogger.d.ts +0 -4
  63. package/lib/projects/localDev/LocalDevLogger.js +2 -19
  64. package/lib/projects/localDev/LocalDevManager.js +1 -1
  65. package/lib/projects/localDev/LocalDevProcess.d.ts +1 -2
  66. package/lib/projects/localDev/LocalDevProcess.js +3 -26
  67. package/lib/projects/localDev/LocalDevState.d.ts +6 -7
  68. package/lib/projects/localDev/LocalDevState.js +16 -15
  69. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +1 -0
  70. package/lib/projects/localDev/LocalDevWebsocketServer.js +17 -2
  71. package/lib/projects/localDev/{helpers.d.ts → helpers/account.d.ts} +1 -7
  72. package/lib/projects/localDev/{helpers.js → helpers/account.js} +44 -144
  73. package/lib/projects/localDev/helpers/project.d.ts +12 -0
  74. package/lib/projects/localDev/helpers/project.js +173 -0
  75. package/lib/projects/urls.d.ts +1 -0
  76. package/lib/projects/urls.js +4 -0
  77. package/lib/prompts/__tests__/createFunctionPrompt.test.d.ts +1 -0
  78. package/lib/prompts/__tests__/createFunctionPrompt.test.js +129 -0
  79. package/lib/prompts/__tests__/createModulePrompt.test.d.ts +1 -0
  80. package/lib/prompts/__tests__/createModulePrompt.test.js +187 -0
  81. package/lib/prompts/__tests__/createTemplatePrompt.test.d.ts +1 -0
  82. package/lib/prompts/__tests__/createTemplatePrompt.test.js +102 -0
  83. package/lib/prompts/confirmImportDataPrompt.d.ts +1 -0
  84. package/lib/prompts/confirmImportDataPrompt.js +12 -0
  85. package/lib/prompts/createFunctionPrompt.d.ts +2 -1
  86. package/lib/prompts/createFunctionPrompt.js +36 -7
  87. package/lib/prompts/createModulePrompt.d.ts +2 -1
  88. package/lib/prompts/createModulePrompt.js +48 -1
  89. package/lib/prompts/createTemplatePrompt.d.ts +3 -24
  90. package/lib/prompts/createTemplatePrompt.js +9 -1
  91. package/lib/prompts/importDataFilePathPrompt.d.ts +1 -0
  92. package/lib/prompts/importDataFilePathPrompt.js +24 -0
  93. package/lib/prompts/importDataTestAccountSelectPrompt.d.ts +3 -0
  94. package/lib/prompts/importDataTestAccountSelectPrompt.js +29 -0
  95. package/lib/prompts/projectDevTargetAccountPrompt.js +1 -0
  96. package/lib/prompts/promptUtils.d.ts +7 -1
  97. package/lib/prompts/promptUtils.js +14 -1
  98. package/lib/ui/__tests__/removeAnsiCodes.test.d.ts +1 -0
  99. package/lib/ui/__tests__/removeAnsiCodes.test.js +84 -0
  100. package/lib/ui/index.js +3 -6
  101. package/lib/ui/removeAnsiCodes.d.ts +1 -0
  102. package/lib/ui/removeAnsiCodes.js +4 -0
  103. package/mcp-server/server.js +2 -1
  104. package/mcp-server/tools/cms/HsListTool.d.ts +23 -0
  105. package/mcp-server/tools/cms/HsListTool.js +58 -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 +8 -0
  110. package/mcp-server/tools/project/DocFetchTool.d.ts +17 -0
  111. package/mcp-server/tools/project/DocFetchTool.js +49 -0
  112. package/mcp-server/tools/project/DocsSearchTool.d.ts +26 -0
  113. package/mcp-server/tools/project/DocsSearchTool.js +62 -0
  114. package/mcp-server/tools/project/GetConfigValuesTool.js +3 -2
  115. package/mcp-server/tools/project/__tests__/DocFetchTool.test.d.ts +1 -0
  116. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +117 -0
  117. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.d.ts +1 -0
  118. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +190 -0
  119. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +1 -1
  120. package/mcp-server/tools/project/constants.d.ts +2 -0
  121. package/mcp-server/tools/project/constants.js +6 -0
  122. package/mcp-server/utils/toolUsageTracking.d.ts +3 -1
  123. package/mcp-server/utils/toolUsageTracking.js +2 -1
  124. package/package.json +9 -6
  125. package/types/Cms.d.ts +16 -0
  126. package/types/Cms.js +25 -1
  127. package/types/LocalDev.d.ts +0 -3
  128. package/types/Prompts.d.ts +1 -0
  129. package/types/Yargs.d.ts +1 -1
  130. package/ui/index.d.ts +1 -0
  131. package/ui/index.js +6 -0
package/README.md CHANGED
@@ -4,10 +4,6 @@
4
4
 
5
5
  A CLI for HubSpot developers to enable local development and automation. [Learn more about building on HubSpot](https://developers.hubspot.com).
6
6
 
7
- ## Contributing
8
-
9
- For more information on developing, see the [Contributing Guide](CONTRIBUTING.md).
10
-
11
7
  ## Getting started
12
8
 
13
9
  For more information on using these tools, see [Local Development Tooling: Getting Started](https://developers.hubspot.com/docs/cms/guides/getting-started-with-local-development)
@@ -1,6 +1,6 @@
1
1
  import { http } from '@hubspot/local-dev-lib/http';
2
2
  import { MIGRATION_STATUS } from '@hubspot/local-dev-lib/types/Migration';
3
- import { listAppsForMigration, initializeMigration, continueMigration, checkMigrationStatusV2, isMigrationStatus, } from '../migrate.js';
3
+ import { listAppsForMigration, initializeAppMigration, continueAppMigration, checkMigrationStatusV2, isMigrationStatus, } from '../migrate.js';
4
4
  vi.mock('@hubspot/local-dev-lib/http');
5
5
  const httpMock = http;
6
6
  describe('api/migrate', () => {
@@ -49,12 +49,12 @@ describe('api/migrate', () => {
49
49
  expect(result).toEqual(mockResponse);
50
50
  });
51
51
  });
52
- describe('initializeMigration', () => {
52
+ describe('initializeAppMigration', () => {
53
53
  it('should call http.post with correct parameters', async () => {
54
54
  const mockResponse = { migrationId: mockMigrationId };
55
55
  // @ts-expect-error Mock
56
56
  httpMock.post.mockResolvedValue(mockResponse);
57
- const result = await initializeMigration(mockAccountId, mockAppId, mockPlatformVersion);
57
+ const result = await initializeAppMigration(mockAccountId, mockAppId, mockPlatformVersion);
58
58
  expect(http.post).toHaveBeenCalledWith(mockAccountId, {
59
59
  url: 'dfs/migrations/v2/migrations',
60
60
  data: {
@@ -65,12 +65,12 @@ describe('api/migrate', () => {
65
65
  expect(result).toEqual(mockResponse);
66
66
  });
67
67
  });
68
- describe('continueMigration', () => {
68
+ describe('continueAppMigration', () => {
69
69
  it('should call http.post with correct parameters', async () => {
70
70
  const mockResponse = { migrationId: mockMigrationId };
71
71
  // @ts-expect-error Mock
72
72
  httpMock.post.mockResolvedValue(mockResponse);
73
- const result = await continueMigration(mockPortalId, mockMigrationId, mockComponentUids, mockProjectName);
73
+ const result = await continueAppMigration(mockPortalId, mockMigrationId, mockComponentUids, mockProjectName);
74
74
  expect(http.post).toHaveBeenCalledWith(mockPortalId, {
75
75
  url: 'dfs/migrations/v2/migrations/continue',
76
76
  data: {
package/api/migrate.d.ts CHANGED
@@ -24,7 +24,13 @@ export interface ListAppsResponse {
24
24
  migratableApps: MigratableApp[];
25
25
  unmigratableApps: UnmigratableApp[];
26
26
  }
27
- export interface InitializeMigrationResponse {
27
+ export interface ListThemesResponse {
28
+ migratableThemes: {
29
+ THEME: string[];
30
+ REACT_THEME: string[];
31
+ };
32
+ }
33
+ export interface InitializeAppMigrationResponse {
28
34
  migrationId: number;
29
35
  }
30
36
  export interface ListAppsMigrationComponent {
@@ -32,7 +38,7 @@ export interface ListAppsMigrationComponent {
32
38
  componentType: string;
33
39
  isSupported: boolean;
34
40
  }
35
- export type ContinueMigrationResponse = {
41
+ export type ContinueAppMigrationResponse = {
36
42
  migrationId: number;
37
43
  };
38
44
  export interface MigrationBaseStatus {
@@ -65,7 +71,7 @@ export interface MigrationFailed extends MigrationBaseStatus {
65
71
  export type MigrationStatus = MigrationInProgress | MigrationInputRequired | MigrationSuccess | MigrationFailed;
66
72
  export declare function isMigrationStatus(error: unknown): error is MigrationStatus;
67
73
  export declare function listAppsForMigration(accountId: number, platformVersion: string): HubSpotPromise<ListAppsResponse>;
68
- export declare function initializeMigration(accountId: number, applicationId: number, platformVersion: string): HubSpotPromise<InitializeMigrationResponse>;
69
- export declare function continueMigration(portalId: number, migrationId: number, componentUids: Record<string, string>, projectName: string): HubSpotPromise<ContinueMigrationResponse>;
74
+ export declare function initializeAppMigration(accountId: number, applicationId: number, platformVersion: string): HubSpotPromise<InitializeAppMigrationResponse>;
75
+ export declare function continueAppMigration(portalId: number, migrationId: number, componentUids: Record<string, string>, projectName: string): HubSpotPromise<ContinueAppMigrationResponse>;
70
76
  export declare function checkMigrationStatusV2(accountId: number, id: number): HubSpotPromise<MigrationStatus>;
71
77
  export {};
package/api/migrate.js CHANGED
@@ -24,7 +24,7 @@ function mapPlatformVersionToEnum(platformVersion) {
24
24
  }
25
25
  return `V${platformVersion.replace('.', '_')}`;
26
26
  }
27
- export async function initializeMigration(accountId, applicationId, platformVersion) {
27
+ export async function initializeAppMigration(accountId, applicationId, platformVersion) {
28
28
  return http.post(accountId, {
29
29
  url: `${MIGRATIONS_API_PATH_V2}/migrations`,
30
30
  data: {
@@ -33,7 +33,7 @@ export async function initializeMigration(accountId, applicationId, platformVers
33
33
  },
34
34
  });
35
35
  }
36
- export async function continueMigration(portalId, migrationId, componentUids, projectName) {
36
+ export async function continueAppMigration(portalId, migrationId, componentUids, projectName) {
37
37
  return http.post(portalId, {
38
38
  url: `${MIGRATIONS_API_PATH_V2}/migrations/continue`,
39
39
  data: {
@@ -1,5 +1,6 @@
1
1
  import yargs from 'yargs';
2
2
  import createCommand from '../create.js';
3
+ import { TEMPLATE_TYPES, HTTP_METHODS } from '../../types/Cms.js';
3
4
  const positionalSpy = vi
4
5
  .spyOn(yargs, 'positional')
5
6
  .mockReturnValue(yargs);
@@ -28,6 +29,25 @@ describe('commands/create', () => {
28
29
  it('should support the correct options', () => {
29
30
  createCommand.builder(yargs);
30
31
  expect(optionSpy).toHaveBeenCalledWith('internal', expect.objectContaining({ type: 'boolean', hidden: true }));
32
+ // Template creation flags
33
+ expect(optionSpy).toHaveBeenCalledWith('template-type', expect.objectContaining({
34
+ type: 'string',
35
+ choices: [...TEMPLATE_TYPES],
36
+ }));
37
+ // Module creation flags
38
+ expect(optionSpy).toHaveBeenCalledWith('module-label', expect.objectContaining({ type: 'string' }));
39
+ expect(optionSpy).toHaveBeenCalledWith('react-type', expect.objectContaining({ type: 'boolean' }));
40
+ expect(optionSpy).toHaveBeenCalledWith('content-types', expect.objectContaining({ type: 'string' }));
41
+ expect(optionSpy).toHaveBeenCalledWith('global', expect.objectContaining({ type: 'boolean' }));
42
+ expect(optionSpy).toHaveBeenCalledWith('available-for-new-content', expect.objectContaining({ type: 'boolean' }));
43
+ // Function creation flags
44
+ expect(optionSpy).toHaveBeenCalledWith('functions-folder', expect.objectContaining({ type: 'string' }));
45
+ expect(optionSpy).toHaveBeenCalledWith('filename', expect.objectContaining({ type: 'string' }));
46
+ expect(optionSpy).toHaveBeenCalledWith('endpoint-method', expect.objectContaining({
47
+ type: 'string',
48
+ choices: [...HTTP_METHODS],
49
+ }));
50
+ expect(optionSpy).toHaveBeenCalledWith('endpoint-path', expect.objectContaining({ type: 'string' }));
31
51
  });
32
52
  });
33
53
  });
@@ -2,6 +2,7 @@ import yargs from 'yargs';
2
2
  import testAccountCreateCommand from '../testAccount/create.js';
3
3
  import testAccountCreateConfigCommand from '../testAccount/createConfig.js';
4
4
  import testAccountDeleteCommand from '../testAccount/delete.js';
5
+ import testAccountImportDataCommand from '../testAccount/importData.js';
5
6
  import testAccountCommands from '../testAccount.js';
6
7
  vi.mock('../testAccount/create');
7
8
  vi.mock('../testAccount/createConfig');
@@ -37,6 +38,7 @@ describe('commands/testAccount', () => {
37
38
  testAccountCreateCommand,
38
39
  testAccountCreateConfigCommand,
39
40
  testAccountDeleteCommand,
41
+ testAccountImportDataCommand,
40
42
  ];
41
43
  it('should demand the command takes one positional argument', () => {
42
44
  testAccountCommands.builder(yargs);
@@ -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;
@@ -5,8 +5,8 @@ import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
5
5
  const functionAssetType = {
6
6
  hidden: false,
7
7
  dest: ({ name }) => name,
8
- execute: async ({ dest }) => {
9
- const functionDefinition = await createFunctionPrompt();
8
+ execute: async ({ dest, commandArgs }) => {
9
+ const functionDefinition = await createFunctionPrompt(commandArgs);
10
10
  try {
11
11
  await createFunction(functionDefinition, dest);
12
12
  }
@@ -14,8 +14,8 @@ const moduleAssetType = {
14
14
  }
15
15
  return true;
16
16
  },
17
- execute: async ({ name, dest, getInternalVersion }) => {
18
- const moduleDefinition = await createModulePrompt();
17
+ execute: async ({ name, dest, getInternalVersion, commandArgs }) => {
18
+ const moduleDefinition = await createModulePrompt(commandArgs);
19
19
  try {
20
20
  await createModule(moduleDefinition, name, dest, getInternalVersion);
21
21
  }
@@ -14,8 +14,8 @@ const templateAssetType = {
14
14
  }
15
15
  return true;
16
16
  },
17
- execute: async ({ name, dest }) => {
18
- const { templateType } = await createTemplatePrompt();
17
+ execute: async ({ name, dest, commandArgs }) => {
18
+ const { templateType } = await createTemplatePrompt(commandArgs);
19
19
  try {
20
20
  await createTemplate(name, dest, templateType);
21
21
  }
@@ -8,6 +8,7 @@ import { commands } from '../lang/en.js';
8
8
  import { uiLogger } from '../lib/ui/logger.js';
9
9
  import { makeYargsBuilder } from '../lib/yargsUtils.js';
10
10
  import { EXIT_CODES } from '../lib/enums/exitCodes.js';
11
+ import { TEMPLATE_TYPES, HTTP_METHODS, CONTENT_TYPES } from '../types/Cms.js';
11
12
  const SUPPORTED_ASSET_TYPES = Object.keys(assets)
12
13
  .filter(t => !assets[t].hidden)
13
14
  .join(', ');
@@ -73,6 +74,52 @@ function createBuilder(yargs) {
73
74
  type: 'boolean',
74
75
  hidden: true,
75
76
  });
77
+ yargs.option('template-type', {
78
+ describe: commands.create.flags.templateType.describe,
79
+ type: 'string',
80
+ choices: [...TEMPLATE_TYPES],
81
+ });
82
+ yargs.option('module-label', {
83
+ describe: commands.create.flags.moduleLabel.describe,
84
+ type: 'string',
85
+ });
86
+ yargs.option('react-type', {
87
+ describe: commands.create.flags.reactType.describe,
88
+ type: 'boolean',
89
+ default: false,
90
+ });
91
+ yargs.option('content-types', {
92
+ describe: commands.create.flags.contentTypes.describe(CONTENT_TYPES),
93
+ type: 'string',
94
+ });
95
+ yargs.option('global', {
96
+ describe: commands.create.flags.global.describe,
97
+ type: 'boolean',
98
+ default: false,
99
+ });
100
+ yargs.option('available-for-new-content', {
101
+ describe: commands.create.flags.availableForNewContent.describe,
102
+ type: 'boolean',
103
+ default: true,
104
+ });
105
+ yargs.option('functions-folder', {
106
+ describe: commands.create.flags.functionsFolder.describe,
107
+ type: 'string',
108
+ });
109
+ yargs.option('filename', {
110
+ describe: commands.create.flags.filename.describe,
111
+ type: 'string',
112
+ });
113
+ yargs.option('endpoint-method', {
114
+ describe: commands.create.flags.endpointMethod.describe,
115
+ type: 'string',
116
+ choices: [...HTTP_METHODS],
117
+ default: 'GET',
118
+ });
119
+ yargs.option('endpoint-path', {
120
+ describe: commands.create.flags.endpointPath.describe,
121
+ type: 'string',
122
+ });
76
123
  return yargs;
77
124
  }
78
125
  const builder = makeYargsBuilder(createBuilder, command, describe, {
@@ -4,12 +4,12 @@ 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';
11
11
  import { projectNameAndDestPrompt } from '../lib/prompts/projectNameAndDestPrompt.js';
12
- import { uiFeatureHighlight, uiInfoSection } from '../lib/ui/index.js';
12
+ import { uiAccountDescription, uiFeatureHighlight, uiInfoSection, } from '../lib/ui/index.js';
13
13
  import { uiLogger } from '../lib/ui/logger.js';
14
14
  import { debugError, logError } from '../lib/errorHandlers/index.js';
15
15
  import { handleProjectUpload } from '../lib/projects/upload.js';
@@ -27,9 +27,9 @@ export const describe = undefined;
27
27
  async function handler(args) {
28
28
  const { derivedAccountId } = args;
29
29
  const env = getEnv(derivedAccountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD;
30
+ await trackCommandUsage('get-started', {}, derivedAccountId);
30
31
  // TODO: Put this in constants.ts once we have a defined place for the template before INBOUND
31
32
  const templateSource = 'robrown-hubspot/hubspot-project-components-ua-app-objects-beta';
32
- trackCommandUsage('get-started', {}, derivedAccountId);
33
33
  uiInfoSection(commands.getStarted.startTitle, () => {
34
34
  uiLogger.log(commands.getStarted.startDescription);
35
35
  });
@@ -51,6 +51,8 @@ async function handler(args) {
51
51
  default: GET_STARTED_OPTIONS.APP,
52
52
  },
53
53
  ]);
54
+ // Track user's initial choice
55
+ await trackCommandMetadataUsage('get-started', { step: 'select-option', type: selectedOption }, derivedAccountId);
54
56
  if (selectedOption === GET_STARTED_OPTIONS.CMS) {
55
57
  uiLogger.log(' ');
56
58
  uiLogger.log(commands.getStarted.designManager);
@@ -63,6 +65,11 @@ async function handler(args) {
63
65
  message: commands.getStarted.openDesignManagerPrompt,
64
66
  },
65
67
  ]);
68
+ // Track Design Manager browser action
69
+ await trackCommandMetadataUsage('get-started', {
70
+ step: 'open-design-manager',
71
+ type: shouldOpen ? 'opened' : 'declined',
72
+ }, derivedAccountId);
66
73
  if (shouldOpen) {
67
74
  uiLogger.log('');
68
75
  openLink(derivedAccountId, 'design-manager');
@@ -88,6 +95,11 @@ async function handler(args) {
88
95
  if (existingProjectConfig &&
89
96
  existingProjectDir &&
90
97
  projectDest.startsWith(existingProjectDir)) {
98
+ // Track nested project error
99
+ await trackCommandMetadataUsage('get-started', {
100
+ successful: false,
101
+ step: 'project-creation',
102
+ }, derivedAccountId);
91
103
  uiLogger.log(' ');
92
104
  uiLogger.error(commands.project.create.errors.cannotNestProjects(existingProjectDir));
93
105
  process.exit(EXIT_CODES.ERROR);
@@ -101,8 +113,16 @@ async function handler(args) {
101
113
  tag: latestRepoReleaseTag,
102
114
  hideLogs: true,
103
115
  });
116
+ await trackCommandMetadataUsage('get-started', {
117
+ successful: true,
118
+ step: 'github-clone',
119
+ }, derivedAccountId);
104
120
  }
105
121
  catch (err) {
122
+ await trackCommandMetadataUsage('get-started', {
123
+ successful: false,
124
+ step: 'github-clone',
125
+ }, derivedAccountId);
106
126
  debugError(err);
107
127
  uiLogger.log(' ');
108
128
  uiLogger.error(commands.project.create.errors.failedToDownloadProject);
@@ -121,6 +141,11 @@ async function handler(args) {
121
141
  uiLogger.log(' ');
122
142
  uiLogger.log(commands.getStarted.prompts.projectCreated.description);
123
143
  uiLogger.log(' ');
144
+ // Track successful project creation
145
+ await trackCommandMetadataUsage('get-started', {
146
+ successful: true,
147
+ step: 'project-creation',
148
+ }, derivedAccountId);
124
149
  // 5. Install dependencies
125
150
  const installLocations = await getProjectPackageJsonLocations(projectDest);
126
151
  try {
@@ -138,19 +163,30 @@ async function handler(args) {
138
163
  uiLogger.log(' ');
139
164
  }
140
165
  // 6. Ask user if they want to upload the project
166
+ const accountName = uiAccountDescription(derivedAccountId);
141
167
  const { shouldUpload } = await promptUser([
142
168
  {
143
169
  type: 'confirm',
144
170
  name: 'shouldUpload',
145
- message: commands.getStarted.prompts.uploadProject,
171
+ message: commands.getStarted.prompts.uploadProject(accountName),
146
172
  default: true,
147
173
  },
148
174
  ]);
175
+ // Track upload decision
176
+ await trackCommandMetadataUsage('get-started', {
177
+ step: 'upload-decision',
178
+ type: shouldUpload ? 'upload' : 'skip',
179
+ }, derivedAccountId);
149
180
  if (shouldUpload) {
150
181
  try {
151
182
  // Get the project config for the newly created project
152
183
  const { projectConfig: newProjectConfig, projectDir: newProjectDir } = await getProjectConfig(projectDest);
153
184
  if (!newProjectConfig || !newProjectDir) {
185
+ // Track config file not found error
186
+ await trackCommandMetadataUsage('get-started', {
187
+ successful: false,
188
+ step: 'config-file-not-found',
189
+ }, derivedAccountId);
154
190
  uiLogger.log(' ');
155
191
  uiLogger.error(commands.getStarted.errors.configFileNotFound);
156
192
  process.exit(EXIT_CODES.ERROR);
@@ -172,11 +208,22 @@ async function handler(args) {
172
208
  skipValidation: false,
173
209
  });
174
210
  if (uploadError) {
211
+ // Track upload failure
212
+ await trackCommandMetadataUsage('get-started', {
213
+ successful: false,
214
+ step: 'upload',
215
+ }, derivedAccountId);
175
216
  uiLogger.log(' ');
176
217
  uiLogger.error(commands.getStarted.errors.uploadFailed);
177
218
  debugError(uploadError);
178
219
  }
179
220
  else if (result) {
221
+ // Track successful upload completion
222
+ await trackCommandMetadataUsage('get-started', {
223
+ successful: true,
224
+ step: 'upload',
225
+ }, derivedAccountId);
226
+ uiLogger.log(' ');
180
227
  uiLogger.success(commands.getStarted.logs.uploadSuccess);
181
228
  const { data: { results }, } = await fetchPublicAppsForPortal(derivedAccountId);
182
229
  const lastCreatedApp = results.sort((a, b) => b.createdAt - a.createdAt)[0];
@@ -191,6 +238,11 @@ async function handler(args) {
191
238
  message: commands.getStarted.openInstallUrl,
192
239
  },
193
240
  ]);
241
+ // Track Developer Overview browser action
242
+ await trackCommandMetadataUsage('get-started', {
243
+ step: 'open-distribution-page',
244
+ type: shouldOpenOverview ? 'opened' : 'declined',
245
+ }, derivedAccountId);
194
246
  if (shouldOpenOverview) {
195
247
  open(getStaticAuthAppInstallUrl({
196
248
  targetAccountId: derivedAccountId,
@@ -206,6 +258,11 @@ async function handler(args) {
206
258
  }
207
259
  }
208
260
  catch (err) {
261
+ // Track upload exception
262
+ await trackCommandMetadataUsage('get-started', {
263
+ successful: false,
264
+ step: 'upload',
265
+ }, derivedAccountId);
209
266
  uiLogger.log(' ');
210
267
  uiLogger.error(commands.getStarted.errors.uploadFailed);
211
268
  debugError(err);
@@ -213,6 +270,11 @@ async function handler(args) {
213
270
  }
214
271
  }
215
272
  }
273
+ // Track successful completion of get-started command
274
+ await trackCommandMetadataUsage('get-started', {
275
+ successful: true,
276
+ step: 'command-completed',
277
+ }, derivedAccountId);
216
278
  process.exit(EXIT_CODES.SUCCESS);
217
279
  }
218
280
  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
  }
@@ -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
  });
@@ -5,7 +5,8 @@ import { getConfigAccounts, getAccountConfig, } from '@hubspot/local-dev-lib/con
5
5
  import { getValidEnv } from '@hubspot/local-dev-lib/environment';
6
6
  import { logError } from '../../../lib/errorHandlers/index.js';
7
7
  import { ensureProjectExists } from '../../../lib/projects/ensureProjectExists.js';
8
- import { createInitialBuildForNewProject, createNewProjectForLocalDev, useExistingDevTestAccount, createDeveloperTestAccountForLocalDev, selectAccountTypePrompt, } from '../../../lib/projects/localDev/helpers.js';
8
+ import { createInitialBuildForNewProject, createNewProjectForLocalDev, } from '../../../lib/projects/localDev/helpers/project.js';
9
+ import { useExistingDevTestAccount, createDeveloperTestAccountForLocalDev, selectAccountTypePrompt, } from '../../../lib/projects/localDev/helpers/account.js';
9
10
  import { selectDeveloperTestTargetAccountPrompt, selectSandboxTargetAccountPrompt, } from '../../../lib/prompts/projectDevTargetAccountPrompt.js';
10
11
  import SpinniesManager from '../../../lib/ui/SpinniesManager.js';
11
12
  import LocalDevProcess from '../../../lib/projects/localDev/LocalDevProcess.js';
@@ -34,7 +35,8 @@ vi.mock('@hubspot/local-dev-lib/config');
34
35
  vi.mock('@hubspot/local-dev-lib/environment');
35
36
  vi.mock('../../../lib/errorHandlers');
36
37
  vi.mock('../../../lib/projects/ensureProjectExists');
37
- vi.mock('../../../lib/projects/localDev/helpers');
38
+ vi.mock('../../../lib/projects/localDev/helpers/project');
39
+ vi.mock('../../../lib/projects/localDev/helpers/account');
38
40
  vi.mock('../../../lib/prompts/projectDevTargetAccountPrompt');
39
41
  vi.mock('../../../lib/ui/SpinniesManager');
40
42
  vi.mock('../../../lib/projects/localDev/LocalDevProcess');
@@ -322,31 +324,10 @@ describe('unifiedProjectDevFlow', () => {
322
324
  });
323
325
  expect(createNewProjectForLocalDev).not.toHaveBeenCalled();
324
326
  expect(LocalDevProcess).toHaveBeenCalledWith(expect.objectContaining({
325
- deployedBuild: mockProject.deployedBuild,
326
327
  projectId: mockProject.id,
327
328
  projectName: mockProject.name,
328
329
  }));
329
330
  });
330
- it('should detect GitHub linked projects', async () => {
331
- const githubLinkedProject = {
332
- ...mockProject,
333
- sourceIntegration: { source: 'GITHUB' },
334
- };
335
- ensureProjectExists.mockResolvedValue({
336
- projectExists: true,
337
- project: githubLinkedProject,
338
- });
339
- await unifiedProjectDevFlow({
340
- args: mockArgs,
341
- targetProjectAccountId: mockTargetProjectAccountId,
342
- providedTargetTestingAccountId: mockProvidedTargetTestingAccountId,
343
- projectConfig: mockProjectConfig,
344
- projectDir: mockProjectDir,
345
- });
346
- expect(LocalDevProcess).toHaveBeenCalledWith(expect.objectContaining({
347
- isGithubLinked: true,
348
- }));
349
- });
350
331
  });
351
332
  describe('local dev process setup', () => {
352
333
  it('should initialize LocalDevProcess with correct parameters', async () => {
@@ -360,8 +341,6 @@ describe('unifiedProjectDevFlow', () => {
360
341
  expect(LocalDevProcess).toHaveBeenCalledWith({
361
342
  initialProjectNodes: mockProjectNodes,
362
343
  debug: mockArgs.debug,
363
- deployedBuild: mockProject.deployedBuild,
364
- isGithubLinked: false,
365
344
  profile: mockArgs.profile,
366
345
  targetProjectAccountId: mockTargetProjectAccountId,
367
346
  targetTestingAccountId: mockProvidedTargetTestingAccountId,
@@ -401,17 +380,26 @@ describe('unifiedProjectDevFlow', () => {
401
380
  beforeEach(() => {
402
381
  isTestAccountOrSandbox.mockReturnValue(false);
403
382
  });
404
- it('should display account type information when prompting', async () => {
405
- 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;
406
395
  await unifiedProjectDevFlow({
407
396
  args: mockArgs,
408
397
  targetProjectAccountId: mockTargetProjectAccountId,
398
+ providedTargetTestingAccountId: providedTestingAccountId,
409
399
  projectConfig: mockProjectConfig,
410
400
  projectDir: mockProjectDir,
411
401
  });
412
- expect(uiLine).toHaveBeenCalled();
413
- expect(uiLogger.log).toHaveBeenCalledWith(commands.project.dev.logs.accountTypeInformation);
414
- expect(uiLogger.log).toHaveBeenCalledWith(commands.project.dev.logs.learnMoreMessage);
402
+ expect(uiLogger.log).toHaveBeenCalledWith(commands.project.dev.logs.testingAccountFlagExplanation(providedTestingAccountId));
415
403
  });
416
404
  });
417
405
  });