@hubspot/cli 7.6.0-beta.11 → 7.6.0-beta.13

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 (158) hide show
  1. package/commands/app/__tests__/migrate.test.js +1 -0
  2. package/commands/getStarted.js +7 -20
  3. package/commands/mcp/setup.d.ts +0 -1
  4. package/commands/mcp/setup.js +11 -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/add.d.ts +1 -1
  9. package/commands/project/add.js +3 -5
  10. package/commands/project/create.js +7 -2
  11. package/commands/project/deploy.js +9 -61
  12. package/commands/project/dev/index.js +1 -1
  13. package/commands/project/dev/unifiedFlow.js +4 -1
  14. package/commands/project/migrate.js +26 -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 +20 -5
  19. package/lang/en.js +38 -22
  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/__tests__/migrate.test.js +14 -51
  24. package/lib/app/migrate.d.ts +2 -8
  25. package/lib/app/migrate.js +5 -73
  26. package/lib/constants.d.ts +4 -0
  27. package/lib/constants.js +4 -0
  28. package/lib/errorHandlers/index.d.ts +4 -0
  29. package/lib/errorHandlers/index.js +1 -1
  30. package/lib/hasFeature.js +6 -0
  31. package/lib/importData.js +1 -1
  32. package/lib/links.d.ts +1 -0
  33. package/lib/links.js +10 -3
  34. package/lib/mcp/setup.d.ts +0 -2
  35. package/lib/mcp/setup.js +4 -29
  36. package/lib/projects/__tests__/AppDevModeInterface.test.js +71 -44
  37. package/lib/projects/__tests__/LocalDevProcess.test.js +1 -0
  38. package/lib/projects/__tests__/components.test.js +164 -7
  39. package/lib/projects/__tests__/deploy.test.js +164 -0
  40. package/lib/projects/__tests__/platformVersion.test.d.ts +1 -0
  41. package/lib/projects/__tests__/{buildAndDeploy.test.js → platformVersion.test.js} +2 -2
  42. package/lib/projects/add/__tests__/legacyAddComponent.test.js +49 -6
  43. package/lib/projects/add/__tests__/v3AddComponent.test.js +142 -8
  44. package/lib/projects/add/legacyAddComponent.d.ts +1 -1
  45. package/lib/projects/add/legacyAddComponent.js +5 -1
  46. package/lib/projects/add/v3AddComponent.d.ts +2 -1
  47. package/lib/projects/add/v3AddComponent.js +22 -5
  48. package/lib/projects/components.d.ts +1 -0
  49. package/lib/projects/components.js +27 -1
  50. package/lib/projects/create/__tests__/v3.test.js +97 -9
  51. package/lib/projects/create/index.js +2 -2
  52. package/lib/projects/create/legacy.js +1 -1
  53. package/lib/projects/create/v3.d.ts +2 -2
  54. package/lib/projects/create/v3.js +35 -12
  55. package/lib/projects/deploy.d.ts +13 -0
  56. package/lib/projects/deploy.js +63 -0
  57. package/lib/projects/localDev/AppDevModeInterface.d.ts +5 -3
  58. package/lib/projects/localDev/AppDevModeInterface.js +110 -47
  59. package/lib/projects/localDev/DevServerManagerV2.js +1 -0
  60. package/lib/projects/localDev/LocalDevProcess.js +3 -1
  61. package/lib/projects/localDev/LocalDevState.d.ts +5 -2
  62. package/lib/projects/localDev/LocalDevState.js +9 -1
  63. package/lib/projects/localDev/helpers/project.d.ts +2 -2
  64. package/lib/projects/localDev/helpers/project.js +6 -8
  65. package/lib/projects/platformVersion.d.ts +1 -0
  66. package/lib/projects/platformVersion.js +10 -0
  67. package/lib/projects/{buildAndDeploy.d.ts → pollProjectBuildAndDeploy.d.ts} +0 -1
  68. package/lib/projects/{buildAndDeploy.js → pollProjectBuildAndDeploy.js} +0 -10
  69. package/lib/projects/upload.js +1 -1
  70. package/lib/projects/urls.d.ts +1 -0
  71. package/lib/projects/urls.js +3 -0
  72. package/lib/prompts/__tests__/projectAddPrompt.test.d.ts +1 -0
  73. package/lib/prompts/__tests__/projectAddPrompt.test.js +143 -0
  74. package/lib/prompts/__tests__/selectProjectTemplatePrompt.test.d.ts +1 -0
  75. package/lib/prompts/__tests__/selectProjectTemplatePrompt.test.js +160 -0
  76. package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +1 -0
  77. package/lib/prompts/importDataFilePathPrompt.js +4 -2
  78. package/lib/prompts/installAppPrompt.d.ts +6 -1
  79. package/lib/prompts/installAppPrompt.js +6 -1
  80. package/lib/prompts/projectAddPrompt.js +1 -1
  81. package/lib/prompts/promptUtils.d.ts +5 -0
  82. package/lib/prompts/promptUtils.js +9 -0
  83. package/lib/prompts/selectProjectTemplatePrompt.js +1 -1
  84. package/lib/theme/__tests__/migrate.test.d.ts +1 -0
  85. package/lib/theme/__tests__/migrate.test.js +233 -0
  86. package/lib/theme/migrate.d.ts +13 -0
  87. package/lib/theme/migrate.js +90 -0
  88. package/lib/ui/index.js +3 -6
  89. package/lib/usageTracking.js +2 -2
  90. package/mcp-server/tools/cms/HsCreateFunctionTool.d.ts +32 -0
  91. package/mcp-server/tools/cms/HsCreateFunctionTool.js +96 -0
  92. package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +38 -0
  93. package/mcp-server/tools/cms/HsCreateModuleTool.js +118 -0
  94. package/mcp-server/tools/cms/HsCreateTemplateTool.d.ts +26 -0
  95. package/mcp-server/tools/cms/HsCreateTemplateTool.js +75 -0
  96. package/mcp-server/tools/cms/HsFunctionLogsTool.d.ts +32 -0
  97. package/mcp-server/tools/cms/HsFunctionLogsTool.js +76 -0
  98. package/mcp-server/tools/cms/HsListFunctionsTool.d.ts +23 -0
  99. package/mcp-server/tools/cms/HsListFunctionsTool.js +58 -0
  100. package/mcp-server/tools/cms/HsListTool.js +1 -1
  101. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.d.ts +1 -0
  102. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +251 -0
  103. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.d.ts +1 -0
  104. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +224 -0
  105. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.d.ts +1 -0
  106. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +206 -0
  107. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.d.ts +1 -0
  108. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +183 -0
  109. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.d.ts +1 -0
  110. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +120 -0
  111. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +1 -1
  112. package/mcp-server/tools/index.js +13 -1
  113. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +3 -3
  114. package/mcp-server/tools/project/AddFeatureToProjectTool.js +3 -3
  115. package/mcp-server/tools/project/CreateProjectTool.d.ts +3 -3
  116. package/mcp-server/tools/project/CreateProjectTool.js +5 -5
  117. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  118. package/mcp-server/tools/project/DocFetchTool.js +2 -2
  119. package/mcp-server/tools/project/DocsSearchTool.d.ts +4 -1
  120. package/mcp-server/tools/project/DocsSearchTool.js +7 -7
  121. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +4 -1
  122. package/mcp-server/tools/project/GetConfigValuesTool.js +14 -8
  123. package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -1
  124. package/mcp-server/tools/project/UploadProjectTools.js +2 -2
  125. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  126. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +1 -1
  127. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -1
  128. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +1 -1
  129. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +2 -2
  130. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +14 -12
  131. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +9 -8
  132. package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +1 -1
  133. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +1 -1
  134. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +1 -1
  135. package/mcp-server/tools/project/constants.d.ts +1 -1
  136. package/mcp-server/tools/project/constants.js +14 -6
  137. package/mcp-server/utils/__tests__/cliConfig.test.d.ts +1 -0
  138. package/mcp-server/utils/__tests__/cliConfig.test.js +110 -0
  139. package/mcp-server/utils/cliConfig.d.ts +1 -0
  140. package/mcp-server/utils/cliConfig.js +12 -0
  141. package/package.json +4 -3
  142. package/types/LocalDev.d.ts +2 -1
  143. package/types/Projects.d.ts +1 -0
  144. package/ui/components/BoxWithTitle.d.ts +8 -0
  145. package/ui/components/BoxWithTitle.js +9 -0
  146. package/ui/components/HorizontalSelectPrompt.d.ts +8 -0
  147. package/ui/components/HorizontalSelectPrompt.js +30 -0
  148. package/ui/components/StatusMessageBoxes.d.ts +12 -0
  149. package/ui/components/StatusMessageBoxes.js +31 -0
  150. package/ui/lib/ui-testing-utils.d.ts +9 -0
  151. package/ui/lib/ui-testing-utils.js +47 -0
  152. package/ui/lib/useTerminalSize.d.ts +13 -0
  153. package/ui/lib/useTerminalSize.js +31 -0
  154. package/ui/styles.d.ts +18 -0
  155. package/ui/styles.js +18 -0
  156. package/ui/views/UiSandbox.d.ts +5 -0
  157. package/ui/views/UiSandbox.js +25 -0
  158. /package/lib/projects/__tests__/{buildAndDeploy.test.d.ts → deploy.test.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { IntermediateRepresentationNodeLocalDev } from '@hubspot/project-parsing-lib/src/lib/types.js';
1
+ import { IntermediateRepresentationNodeLocalDev, HSProfileVariables } from '@hubspot/project-parsing-lib/src/lib/types.js';
2
2
  import { Environment } from '@hubspot/local-dev-lib/types/Config';
3
3
  import { ProjectConfig } from '../../../types/Projects.js';
4
4
  import { LocalDevStateConstructorOptions, LocalDevStateListener, AppLocalDevData, LocalDevServerMessage } from '../../../types/LocalDev.js';
@@ -12,13 +12,14 @@ declare class LocalDevState {
12
12
  private _projectName;
13
13
  private _debug;
14
14
  private _projectNodes;
15
+ private _projectProfileData;
15
16
  private _projectNodesAtLastUpload;
16
17
  private _env;
17
18
  private _listeners;
18
19
  private _appData;
19
20
  private _devServerMessage;
20
21
  private _uploadWarnings;
21
- constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, initialProjectNodes, profile, env, }: LocalDevStateConstructorOptions);
22
+ constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, initialProjectNodes, initialProjectProfileData, profile, env, }: LocalDevStateConstructorOptions);
22
23
  private runListeners;
23
24
  get targetProjectAccountId(): number;
24
25
  get targetTestingAccountId(): number;
@@ -34,6 +35,8 @@ declare class LocalDevState {
34
35
  set projectNodes(nodes: {
35
36
  [key: string]: IntermediateRepresentationNodeLocalDev;
36
37
  });
38
+ get projectProfileData(): HSProfileVariables;
39
+ set projectProfileData(profileData: HSProfileVariables);
37
40
  get projectNodesAtLastUpload(): {
38
41
  [key: string]: IntermediateRepresentationNodeLocalDev;
39
42
  };
@@ -9,13 +9,14 @@ class LocalDevState {
9
9
  _projectName;
10
10
  _debug;
11
11
  _projectNodes;
12
+ _projectProfileData;
12
13
  _projectNodesAtLastUpload;
13
14
  _env;
14
15
  _listeners;
15
16
  _appData;
16
17
  _devServerMessage;
17
18
  _uploadWarnings;
18
- constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, initialProjectNodes, profile, env, }) {
19
+ constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, initialProjectNodes, initialProjectProfileData, profile, env, }) {
19
20
  this._targetProjectAccountId = targetProjectAccountId;
20
21
  this._targetTestingAccountId = targetTestingAccountId;
21
22
  this._profile = profile;
@@ -26,6 +27,7 @@ class LocalDevState {
26
27
  this._debug = debug || false;
27
28
  this._projectNodes = initialProjectNodes;
28
29
  this._projectNodesAtLastUpload = initialProjectNodes;
30
+ this._projectProfileData = initialProjectProfileData;
29
31
  this._env = env;
30
32
  this._appData = {};
31
33
  this._devServerMessage = LOCAL_DEV_SERVER_MESSAGE_TYPES.INITIAL;
@@ -68,6 +70,12 @@ class LocalDevState {
68
70
  this._projectNodes = nodes;
69
71
  this.runListeners('projectNodes');
70
72
  }
73
+ get projectProfileData() {
74
+ return structuredClone(this._projectProfileData);
75
+ }
76
+ set projectProfileData(profileData) {
77
+ this._projectProfileData = profileData;
78
+ }
71
79
  get projectNodesAtLastUpload() {
72
80
  return structuredClone(this._projectNodesAtLastUpload);
73
81
  }
@@ -6,7 +6,7 @@ export declare function createNewProjectForLocalDev(projectConfig: ProjectConfig
6
6
  export declare function createInitialBuildForNewProject(projectConfig: ProjectConfig, projectDir: string, targetAccountId: number, sendIR?: boolean, profile?: string): Promise<Build>;
7
7
  export declare function compareLocalProjectToDeployed(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number | undefined, localProjectNodes: {
8
8
  [key: string]: IntermediateRepresentationNodeLocalDev;
9
- }): Promise<void>;
9
+ }, profile?: string): Promise<void>;
10
10
  export declare function isDeployedProjectUpToDateWithLocal(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number, localProjectNodes: {
11
11
  [key: string]: IntermediateRepresentationNodeLocalDev;
12
- }): Promise<boolean>;
12
+ }, profile?: string): Promise<boolean>;
@@ -17,7 +17,7 @@ import { uiAccountDescription } from '../../../ui/index.js';
17
17
  import SpinniesManager from '../../../ui/SpinniesManager.js';
18
18
  import { EXIT_CODES } from '../../../enums/exitCodes.js';
19
19
  import { handleProjectUpload } from '../../upload.js';
20
- import { pollProjectBuildAndDeploy } from '../../buildAndDeploy.js';
20
+ import { pollProjectBuildAndDeploy } from '../../pollProjectBuildAndDeploy.js';
21
21
  import { logError } from '../../../errorHandlers/index.js';
22
22
  import { ApiErrorContext } from '../../../errorHandlers/index.js';
23
23
  // Prompt the user to create a new project if one doesn't exist on their target account
@@ -123,7 +123,7 @@ export async function createInitialBuildForNewProject(projectConfig, projectDir,
123
123
  }
124
124
  return initialUploadResult.buildResult;
125
125
  }
126
- export async function compareLocalProjectToDeployed(projectConfig, accountId, deployedBuildId, localProjectNodes) {
126
+ export async function compareLocalProjectToDeployed(projectConfig, accountId, deployedBuildId, localProjectNodes, profile) {
127
127
  uiLogger.log('');
128
128
  if (!deployedBuildId) {
129
129
  uiLogger.error(lib.localDevHelpers.project.compareLocalProjectToDeployed.noDeployedBuild(projectConfig.name, uiAccountDescription(accountId)));
@@ -132,12 +132,11 @@ export async function compareLocalProjectToDeployed(projectConfig, accountId, de
132
132
  SpinniesManager.add('compareLocalProjectToDeployed', {
133
133
  text: lib.localDevHelpers.project.compareLocalProjectToDeployed.checking,
134
134
  });
135
- const isUpToDate = await isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes);
135
+ const isUpToDate = await isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes, profile);
136
136
  if (isUpToDate) {
137
137
  SpinniesManager.succeed('compareLocalProjectToDeployed', {
138
138
  text: lib.localDevHelpers.project.compareLocalProjectToDeployed.upToDate,
139
139
  });
140
- uiLogger.log('');
141
140
  }
142
141
  else {
143
142
  SpinniesManager.fail('compareLocalProjectToDeployed', {
@@ -145,12 +144,11 @@ export async function compareLocalProjectToDeployed(projectConfig, accountId, de
145
144
  .notUpToDate,
146
145
  });
147
146
  uiLogger.log('');
148
- uiLogger.log(lib.localDevHelpers.project.compareLocalProjectToDeployed
149
- .notUpToDateExplanation);
147
+ uiLogger.log(lib.localDevHelpers.project.compareLocalProjectToDeployed.notUpToDateExplanation(profile));
150
148
  process.exit(EXIT_CODES.SUCCESS);
151
149
  }
152
150
  }
153
- export async function isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes) {
151
+ export async function isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes, profile) {
154
152
  let tempDir = null;
155
153
  try {
156
154
  tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hubspot-project-compare-'));
@@ -162,7 +160,7 @@ export async function isDeployedProjectUpToDateWithLocal(projectConfig, accountI
162
160
  projectSourceDir: deployedProjectSourceDir,
163
161
  platformVersion: projectConfig.platformVersion,
164
162
  accountId: accountId,
165
- }, {});
163
+ }, { profile });
166
164
  return isDeepEqual(localProjectNodes, deployedProjectNodes, ['localDev']);
167
165
  }
168
166
  finally {
@@ -0,0 +1 @@
1
+ export declare function useV3Api(platformVersion?: string | null): boolean;
@@ -0,0 +1,10 @@
1
+ export function useV3Api(platformVersion) {
2
+ if (!platformVersion || typeof platformVersion !== 'string') {
3
+ return false;
4
+ }
5
+ if (platformVersion.toLowerCase() === 'unstable') {
6
+ return true;
7
+ }
8
+ const [year, minor] = platformVersion.split('.');
9
+ return Number(year) >= 2025 && Number(minor) >= 2;
10
+ }
@@ -2,7 +2,6 @@ import { FileResult } from 'tmp';
2
2
  import { Build } from '@hubspot/local-dev-lib/types/Build';
3
3
  import { Deploy } from '@hubspot/local-dev-lib/types/Deploy';
4
4
  import { ProjectConfig, ProjectTask, ProjectPollResult } from '../../types/Projects.js';
5
- export declare function useV3Api(platformVersion?: string | null): boolean;
6
5
  type PollTaskStatusFunction<T extends ProjectTask> = (accountId: number, taskName: string, taskId: number, deployedBuildId: number | null, silenceLogs?: boolean) => Promise<T>;
7
6
  export declare const pollBuildStatus: PollTaskStatusFunction<Build>;
8
7
  export declare const pollDeployStatus: PollTaskStatusFunction<Deploy>;
@@ -13,16 +13,6 @@ import { mapToInternalType } from '@hubspot/project-parsing-lib';
13
13
  const SPINNER_STATUS = {
14
14
  SPINNING: 'spinning',
15
15
  };
16
- export function useV3Api(platformVersion) {
17
- if (!platformVersion || typeof platformVersion !== 'string') {
18
- return false;
19
- }
20
- if (platformVersion.toLowerCase() === 'unstable') {
21
- return true;
22
- }
23
- const [year, minor] = platformVersion.split('.');
24
- return Number(year) >= 2025 && Number(minor) >= 2;
25
- }
26
16
  function getSubtasks(task) {
27
17
  if ('subbuildStatuses' in task) {
28
18
  return task.subbuildStatuses;
@@ -12,7 +12,7 @@ import util from 'node:util';
12
12
  import { lib } from '../../lang/en.js';
13
13
  import { ensureProjectExists } from './ensureProjectExists.js';
14
14
  import { uiLogger } from '../ui/logger.js';
15
- import { useV3Api } from './buildAndDeploy.js';
15
+ import { useV3Api } from './platformVersion.js';
16
16
  import { EXIT_CODES } from '../enums/exitCodes.js';
17
17
  import ProjectValidationError from '../errors/ProjectValidationError.js';
18
18
  async function uploadProjectFiles(accountId, projectName, filePath, uploadMessage, platformVersion, intermediateRepresentation) {
@@ -7,3 +7,4 @@ export declare function getProjectBuildDetailUrl(projectName: string, buildId: n
7
7
  export declare function getProjectDeployDetailUrl(projectName: string, deployId: number, accountId: number): string;
8
8
  export declare function getLocalDevUiUrl(accountId: number, showWelcomeScreen?: boolean): string;
9
9
  export declare function getAccountHomeUrl(accountId: number): string;
10
+ export declare function getAppAllowlistUrl(accountId: number, projectName: string, appUid: string): string;
@@ -41,3 +41,6 @@ export function getAccountHomeUrl(accountId) {
41
41
  const baseUrl = getHubSpotWebsiteOrigin(getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD);
42
42
  return `${baseUrl}/home?portalId=${accountId}`;
43
43
  }
44
+ export function getAppAllowlistUrl(accountId, projectName, appUid) {
45
+ return `${getProjectHomeUrl(accountId)}/project/${projectName}/component/${appUid}/distribution?panel=static-token-allowlist`;
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,143 @@
1
+ import { Separator } from '@inquirer/prompts';
2
+ import { projectAddPromptV3 } from '../projectAddPrompt.js';
3
+ import { promptUser } from '../promptUtils.js';
4
+ vi.mock('../promptUtils');
5
+ const mockedPromptUser = vi.mocked(promptUser);
6
+ describe('lib/prompts/projectAddPrompt', () => {
7
+ const mockComponentTemplate = {
8
+ label: 'Test Module',
9
+ path: 'test-module',
10
+ type: 'module',
11
+ supportedAuthTypes: ['oauth'],
12
+ supportedDistributions: ['private'],
13
+ };
14
+ const mockComponentTemplateWithCliSelector = {
15
+ label: 'Workflow Action Tool',
16
+ path: 'workflow-action-tool',
17
+ type: 'workflow-action',
18
+ cliSelector: 'workflow-action-tool',
19
+ supportedAuthTypes: ['oauth'],
20
+ supportedDistributions: ['private'],
21
+ };
22
+ describe('projectAddPromptV3()', () => {
23
+ beforeEach(() => {
24
+ // Mock returns empty result, logic will use selectedComponents when selectedFeatures provided
25
+ mockedPromptUser.mockResolvedValue({});
26
+ });
27
+ it('should select component based on cliSelector when provided', async () => {
28
+ const templateChoice = {
29
+ name: 'Workflow Action Tool',
30
+ value: mockComponentTemplateWithCliSelector,
31
+ };
32
+ const components = [templateChoice];
33
+ const selectedFeatures = ['workflow-action-tool'];
34
+ const result = await projectAddPromptV3(components, selectedFeatures);
35
+ expect(result.componentTemplate).toEqual([
36
+ mockComponentTemplateWithCliSelector,
37
+ ]);
38
+ expect(mockedPromptUser).toHaveBeenCalledWith([
39
+ expect.objectContaining({
40
+ name: 'componentTemplate',
41
+ when: false, // selectedFeatures provided, so skip prompt
42
+ }),
43
+ ]);
44
+ });
45
+ it('should select component based on type when cliSelector not provided', async () => {
46
+ const templateChoice = {
47
+ name: 'Test Module',
48
+ value: mockComponentTemplate,
49
+ };
50
+ const components = [templateChoice];
51
+ const selectedFeatures = ['module'];
52
+ const result = await projectAddPromptV3(components, selectedFeatures);
53
+ expect(result.componentTemplate).toEqual([mockComponentTemplate]);
54
+ expect(mockedPromptUser).toHaveBeenCalledWith([
55
+ expect.objectContaining({
56
+ name: 'componentTemplate',
57
+ when: false, // selectedFeatures provided and selectedComponents found
58
+ }),
59
+ ]);
60
+ });
61
+ it('should prefer cliSelector over type when both are available', async () => {
62
+ const templateChoice = {
63
+ name: 'Workflow Action Tool',
64
+ value: mockComponentTemplateWithCliSelector,
65
+ };
66
+ const components = [templateChoice];
67
+ const selectedFeatures = ['workflow-action-tool']; // matches cliSelector
68
+ const result = await projectAddPromptV3(components, selectedFeatures);
69
+ expect(result.componentTemplate).toEqual([
70
+ mockComponentTemplateWithCliSelector,
71
+ ]);
72
+ });
73
+ it('should not select component when neither cliSelector nor type matches', async () => {
74
+ const templateChoice = {
75
+ name: 'Test Module',
76
+ value: mockComponentTemplate,
77
+ };
78
+ const components = [templateChoice];
79
+ const selectedFeatures = ['non-matching-feature'];
80
+ mockedPromptUser.mockResolvedValue({ componentTemplate: [] });
81
+ const result = await projectAddPromptV3(components, selectedFeatures);
82
+ expect(result.componentTemplate).toEqual([]);
83
+ });
84
+ it('should throw error when selected feature component is disabled', async () => {
85
+ const disabledTemplateChoice = {
86
+ name: 'Disabled Component',
87
+ value: mockComponentTemplateWithCliSelector,
88
+ disabled: 'Component is disabled for testing',
89
+ };
90
+ const components = [disabledTemplateChoice];
91
+ const selectedFeatures = ['workflow-action-tool'];
92
+ await expect(projectAddPromptV3(components, selectedFeatures)).rejects.toThrow(/Cannot.*feature.*workflow-action/);
93
+ });
94
+ it('should handle multiple components with mixed cliSelector availability', async () => {
95
+ const choice1 = {
96
+ name: 'Test Module',
97
+ value: mockComponentTemplate,
98
+ };
99
+ const choice2 = {
100
+ name: 'Workflow Action Tool',
101
+ value: mockComponentTemplateWithCliSelector,
102
+ };
103
+ const components = [choice1, choice2];
104
+ const selectedFeatures = ['module', 'workflow-action-tool'];
105
+ const result = await projectAddPromptV3(components, selectedFeatures);
106
+ expect(result.componentTemplate).toEqual([
107
+ mockComponentTemplate,
108
+ mockComponentTemplateWithCliSelector,
109
+ ]);
110
+ });
111
+ it('should skip Separator instances when processing components', async () => {
112
+ const separator = new Separator();
113
+ const templateChoice = {
114
+ name: 'Test Module',
115
+ value: mockComponentTemplate,
116
+ };
117
+ const components = [separator, templateChoice];
118
+ const selectedFeatures = ['module'];
119
+ const result = await projectAddPromptV3(components, selectedFeatures);
120
+ expect(result.componentTemplate).toEqual([mockComponentTemplate]);
121
+ });
122
+ it('should prompt user when no selectedFeatures provided', async () => {
123
+ const templateChoice = {
124
+ name: 'Test Module',
125
+ value: mockComponentTemplate,
126
+ };
127
+ const components = [templateChoice];
128
+ const selectedFeatures = undefined;
129
+ mockedPromptUser.mockResolvedValue({
130
+ componentTemplate: [mockComponentTemplate],
131
+ });
132
+ const result = await projectAddPromptV3(components, selectedFeatures);
133
+ expect(mockedPromptUser).toHaveBeenCalledWith([
134
+ expect.objectContaining({
135
+ name: 'componentTemplate',
136
+ type: 'checkbox',
137
+ choices: components,
138
+ }),
139
+ ]);
140
+ expect(result.componentTemplate).toEqual([mockComponentTemplate]);
141
+ });
142
+ });
143
+ });
@@ -0,0 +1,160 @@
1
+ import { Separator } from '@inquirer/prompts';
2
+ import { selectProjectTemplatePrompt } from '../selectProjectTemplatePrompt.js';
3
+ import { promptUser } from '../promptUtils.js';
4
+ vi.mock('../promptUtils');
5
+ const mockedPromptUser = vi.mocked(promptUser);
6
+ describe('lib/prompts/selectProjectTemplatePrompt', () => {
7
+ const mockComponentTemplate = {
8
+ label: 'Test Module',
9
+ path: 'test-module',
10
+ type: 'module',
11
+ supportedAuthTypes: ['oauth'],
12
+ supportedDistributions: ['private'],
13
+ };
14
+ const mockComponentTemplateWithCliSelector = {
15
+ label: 'Workflow Action Tool',
16
+ path: 'workflow-action-tool',
17
+ type: 'workflow-action',
18
+ cliSelector: 'workflow-action-tool',
19
+ supportedAuthTypes: ['oauth'],
20
+ supportedDistributions: ['private'],
21
+ };
22
+ beforeEach(() => {
23
+ mockedPromptUser.mockResolvedValue({});
24
+ });
25
+ describe('selectProjectTemplatePrompt with component templates', () => {
26
+ it('should select component based on cliSelector when provided', async () => {
27
+ const templateChoice = {
28
+ name: 'Workflow Action Tool',
29
+ value: mockComponentTemplateWithCliSelector,
30
+ };
31
+ const componentTemplates = [templateChoice];
32
+ const promptOptions = {
33
+ features: ['workflow-action-tool'],
34
+ };
35
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
36
+ expect(result.componentTemplates).toEqual([
37
+ mockComponentTemplateWithCliSelector,
38
+ ]);
39
+ expect(mockedPromptUser).toHaveBeenCalledWith([
40
+ expect.objectContaining({ name: 'projectTemplate' }),
41
+ expect.objectContaining({ name: 'componentTemplates' }),
42
+ ]);
43
+ });
44
+ it('should select component based on type when cliSelector not provided', async () => {
45
+ const templateChoice = {
46
+ name: 'Test Module',
47
+ value: mockComponentTemplate,
48
+ };
49
+ const componentTemplates = [templateChoice];
50
+ const promptOptions = {
51
+ features: ['module'],
52
+ };
53
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
54
+ expect(result.componentTemplates).toEqual([mockComponentTemplate]);
55
+ });
56
+ it('should prefer cliSelector over type when both are available', async () => {
57
+ const templateChoice = {
58
+ name: 'Workflow Action Tool',
59
+ value: mockComponentTemplateWithCliSelector,
60
+ };
61
+ const componentTemplates = [templateChoice];
62
+ const promptOptions = {
63
+ features: ['workflow-action-tool'], // matches cliSelector, not type
64
+ };
65
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
66
+ expect(result.componentTemplates).toEqual([
67
+ mockComponentTemplateWithCliSelector,
68
+ ]);
69
+ });
70
+ it('should not select component when neither cliSelector nor type matches', async () => {
71
+ const templateChoice = {
72
+ name: 'Test Module',
73
+ value: mockComponentTemplate,
74
+ };
75
+ const componentTemplates = [templateChoice];
76
+ const promptOptions = {
77
+ features: ['non-matching-feature'],
78
+ };
79
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
80
+ expect(result.componentTemplates).toEqual([]);
81
+ });
82
+ it('should throw error when selected feature component is disabled', async () => {
83
+ const disabledTemplateChoice = {
84
+ name: 'Disabled Component',
85
+ value: mockComponentTemplateWithCliSelector,
86
+ disabled: 'Component is disabled for testing',
87
+ };
88
+ const componentTemplates = [disabledTemplateChoice];
89
+ const promptOptions = {
90
+ features: ['workflow-action-tool'],
91
+ };
92
+ await expect(selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates)).rejects.toThrow(/Cannot create project with template.*workflow-action/);
93
+ });
94
+ it('should handle multiple components with mixed cliSelector availability', async () => {
95
+ const choice1 = {
96
+ name: 'Test Module',
97
+ value: mockComponentTemplate,
98
+ };
99
+ const choice2 = {
100
+ name: 'Workflow Action Tool',
101
+ value: mockComponentTemplateWithCliSelector,
102
+ };
103
+ const componentTemplates = [choice1, choice2];
104
+ const promptOptions = {
105
+ features: ['module', 'workflow-action-tool'],
106
+ };
107
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
108
+ expect(result.componentTemplates).toEqual([
109
+ mockComponentTemplate,
110
+ mockComponentTemplateWithCliSelector,
111
+ ]);
112
+ });
113
+ it('should skip Separator instances when processing components', async () => {
114
+ const separator = new Separator();
115
+ const templateChoice = {
116
+ name: 'Test Module',
117
+ value: mockComponentTemplate,
118
+ };
119
+ const componentTemplates = [separator, templateChoice];
120
+ const promptOptions = {
121
+ features: ['module'],
122
+ };
123
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
124
+ expect(result.componentTemplates).toEqual([mockComponentTemplate]);
125
+ });
126
+ it('should prompt user when no features provided', async () => {
127
+ const templateChoice = {
128
+ name: 'Test Module',
129
+ value: mockComponentTemplate,
130
+ };
131
+ const componentTemplates = [templateChoice];
132
+ const promptOptions = {};
133
+ mockedPromptUser.mockResolvedValue({
134
+ componentTemplates: [mockComponentTemplate],
135
+ });
136
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
137
+ expect(mockedPromptUser).toHaveBeenCalledWith([
138
+ expect.objectContaining({ name: 'projectTemplate' }),
139
+ expect.objectContaining({
140
+ name: 'componentTemplates',
141
+ type: 'checkbox',
142
+ choices: componentTemplates,
143
+ }),
144
+ ]);
145
+ expect(result.componentTemplates).toEqual([mockComponentTemplate]);
146
+ });
147
+ it('should handle empty componentTemplates selection', async () => {
148
+ const templateChoice = {
149
+ name: 'Test Module',
150
+ value: mockComponentTemplate,
151
+ };
152
+ const componentTemplates = [templateChoice];
153
+ const promptOptions = {
154
+ features: ['non-matching-feature'],
155
+ };
156
+ const result = await selectProjectTemplatePrompt(promptOptions, undefined, componentTemplates);
157
+ expect(result.componentTemplates).toEqual([]);
158
+ });
159
+ });
160
+ });
@@ -101,6 +101,7 @@ export async function createDeveloperTestAccountConfigPrompt(args = {}, supportF
101
101
  type: 'checkbox',
102
102
  pageSize: 13,
103
103
  choices: TEST_ACCOUNT_TIERS,
104
+ loop: false,
104
105
  validate: choices => {
105
106
  if (choices?.length < Object.keys(hubs).length) {
106
107
  return lib.prompts.createDeveloperTestAccountConfigPrompt.errors
@@ -1,8 +1,8 @@
1
1
  import { validateImportRequestFile } from '@hubspot/local-dev-lib/crm';
2
2
  import { promptUser } from './promptUtils.js';
3
3
  import { lib } from '../../lang/en.js';
4
- import { logError } from '../errorHandlers/index.js';
5
4
  import { uiLogger } from '../ui/logger.js';
5
+ import { isErrorWithMessageOrReason } from '../errorHandlers/index.js';
6
6
  export async function importDataFilePathPrompt() {
7
7
  uiLogger.log(lib.prompts.importDataFilePathPrompt.promptContext);
8
8
  const { filePath } = await promptUser({
@@ -15,7 +15,9 @@ export async function importDataFilePathPrompt() {
15
15
  return true;
16
16
  }
17
17
  catch (error) {
18
- logError(error);
18
+ if (isErrorWithMessageOrReason(error) && error.message) {
19
+ return error.message;
20
+ }
19
21
  return false;
20
22
  }
21
23
  },
@@ -1,2 +1,7 @@
1
- export declare function installAppBrowserPrompt(installUrl: string, isReinstall?: boolean): Promise<void>;
1
+ export declare function installAppBrowserPrompt(installUrl: string, isReinstall?: boolean, staticAuthInstallOptions?: {
2
+ testingAccountId: number;
3
+ projectAccountId: number;
4
+ projectName: string;
5
+ appUid: string;
6
+ }): Promise<void>;
2
7
  export declare function installAppAutoPrompt(): Promise<boolean>;
@@ -3,7 +3,7 @@ import { promptUser } from './promptUtils.js';
3
3
  import { EXIT_CODES } from '../enums/exitCodes.js';
4
4
  import { lib } from '../../lang/en.js';
5
5
  import { uiLogger } from '../ui/logger.js';
6
- export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
6
+ export async function installAppBrowserPrompt(installUrl, isReinstall = false, staticAuthInstallOptions) {
7
7
  uiLogger.log('');
8
8
  if (isReinstall) {
9
9
  uiLogger.log(lib.prompts.installAppPrompt.reinstallExplanation);
@@ -11,6 +11,11 @@ export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
11
11
  else {
12
12
  uiLogger.log(lib.prompts.installAppPrompt.explanation);
13
13
  }
14
+ if (staticAuthInstallOptions) {
15
+ const { testingAccountId, projectAccountId, projectName, appUid } = staticAuthInstallOptions;
16
+ uiLogger.log(lib.prompts.installAppPrompt.staticAuthExplanation(projectAccountId, testingAccountId, projectName, appUid));
17
+ uiLogger.log('');
18
+ }
14
19
  const { shouldOpenBrowser } = await promptUser({
15
20
  name: 'shouldOpenBrowser',
16
21
  type: 'confirm',
@@ -51,7 +51,7 @@ export async function projectAddPromptV3(components, selectedFeatures) {
51
51
  if (template instanceof Separator || !template.value) {
52
52
  return;
53
53
  }
54
- if (selectedFeatures?.includes(template.value.type)) {
54
+ if (selectedFeatures?.includes(template.value.cliSelector || template.value.type)) {
55
55
  if (template.disabled) {
56
56
  throw new Error(lib.prompts.projectAddPrompt.errors.cannotAddFeature(template.value.type, template.disabled));
57
57
  }
@@ -1,6 +1,11 @@
1
1
  import { Separator as _Separator } from '@inquirer/prompts';
2
2
  import { PromptConfig, GenericPromptResponse, PromptWhen, PromptChoices } from '../../types/Prompts.js';
3
3
  export declare const Separator: _Separator;
4
+ export declare const PROMPT_THEME: {
5
+ prefix: {
6
+ idle: string;
7
+ };
8
+ };
4
9
  export declare function promptUser<T extends GenericPromptResponse>(config: PromptConfig<T> | PromptConfig<T>[]): Promise<T>;
5
10
  export declare function confirmPrompt(message: string, options?: {
6
11
  defaultAnswer?: boolean;