@hubspot/cli 7.9.0-beta.0 → 7.9.0-beta.2

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 (89) hide show
  1. package/commands/project/__tests__/deploy.test.js +4 -3
  2. package/lang/en.d.ts +11 -10
  3. package/lang/en.js +14 -13
  4. package/lib/__tests__/http.test.d.ts +1 -0
  5. package/lib/__tests__/http.test.js +40 -0
  6. package/lib/__tests__/npm.test.js +1 -1
  7. package/lib/__tests__/sandboxSync.test.js +1 -1
  8. package/lib/__tests__/usageTracking.test.js +2 -2
  9. package/lib/configMigrate.js +3 -3
  10. package/lib/doctor/DiagnosticInfoBuilder.js +1 -1
  11. package/lib/doctor/Doctor.js +1 -1
  12. package/lib/doctor/__tests__/DiagnosticInfoBuilder.test.js +4 -2
  13. package/lib/doctor/__tests__/Doctor.test.js +1 -1
  14. package/lib/http.d.ts +1 -0
  15. package/lib/http.js +26 -0
  16. package/lib/jsonLoader.d.ts +14 -0
  17. package/lib/jsonLoader.js +60 -0
  18. package/lib/middleware/__test__/requestMiddleware.test.js +1 -1
  19. package/lib/middleware/autoUpdateMiddleware.js +1 -1
  20. package/lib/middleware/commandTargetingUtils.js +1 -0
  21. package/lib/middleware/fireAlarmMiddleware.js +1 -1
  22. package/lib/middleware/notificationsMiddleware.js +1 -1
  23. package/lib/middleware/requestMiddleware.js +1 -1
  24. package/lib/npm.js +1 -1
  25. package/lib/projects/__tests__/AppDevModeInterface.test.js +3 -0
  26. package/lib/projects/create/__tests__/v2.test.js +20 -14
  27. package/lib/projects/create/v2.js +8 -13
  28. package/lib/projects/localDev/AppDevModeInterface.d.ts +1 -0
  29. package/lib/projects/localDev/AppDevModeInterface.js +16 -0
  30. package/lib/projects/localDev/LocalDevLogger.js +2 -2
  31. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -3
  32. package/lib/projects/localDev/LocalDevWebsocketServer.js +1 -1
  33. package/lib/prompts/promptUtils.d.ts +8 -0
  34. package/lib/prompts/promptUtils.js +7 -1
  35. package/lib/prompts/selectProjectTemplatePrompt.js +4 -0
  36. package/lib/sandboxSync.js +1 -1
  37. package/lib/usageTracking.js +2 -2
  38. package/mcp-server/tools/cms/HsCreateFunctionTool.js +2 -2
  39. package/mcp-server/tools/cms/HsCreateModuleTool.js +2 -2
  40. package/mcp-server/tools/cms/HsCreateTemplateTool.js +2 -2
  41. package/mcp-server/tools/cms/HsFunctionLogsTool.js +2 -9
  42. package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
  43. package/mcp-server/tools/cms/HsListTool.js +1 -1
  44. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +7 -4
  45. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +7 -3
  46. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +7 -4
  47. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +5 -1
  48. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +8 -3
  49. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +8 -3
  50. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +4 -1
  51. package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -5
  52. package/mcp-server/tools/project/CreateProjectTool.js +2 -2
  53. package/mcp-server/tools/project/DeployProjectTool.d.ts +4 -1
  54. package/mcp-server/tools/project/DeployProjectTool.js +4 -3
  55. package/mcp-server/tools/project/DocFetchTool.d.ts +4 -1
  56. package/mcp-server/tools/project/DocFetchTool.js +7 -6
  57. package/mcp-server/tools/project/DocsSearchTool.js +5 -5
  58. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +4 -1
  59. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +7 -5
  60. package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +8 -2
  61. package/mcp-server/tools/project/GetApplicationInfoTool.js +7 -6
  62. package/mcp-server/tools/project/GetConfigValuesTool.js +4 -4
  63. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +4 -1
  64. package/mcp-server/tools/project/GuidedWalkthroughTool.js +6 -14
  65. package/mcp-server/tools/project/UploadProjectTools.d.ts +4 -1
  66. package/mcp-server/tools/project/UploadProjectTools.js +4 -3
  67. package/mcp-server/tools/project/ValidateProjectTool.d.ts +4 -1
  68. package/mcp-server/tools/project/ValidateProjectTool.js +5 -4
  69. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +6 -1
  70. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +7 -3
  71. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +7 -2
  72. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +8 -3
  73. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +6 -2
  74. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +8 -3
  75. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +9 -5
  76. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +6 -2
  77. package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +43 -13
  78. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +8 -2
  79. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +8 -2
  80. package/mcp-server/utils/__tests__/content.test.d.ts +1 -0
  81. package/mcp-server/utils/__tests__/content.test.js +166 -0
  82. package/mcp-server/utils/__tests__/feedbackTracking.test.d.ts +1 -0
  83. package/mcp-server/utils/__tests__/feedbackTracking.test.js +121 -0
  84. package/mcp-server/utils/content.d.ts +1 -1
  85. package/mcp-server/utils/content.js +8 -1
  86. package/mcp-server/utils/feedbackTracking.d.ts +1 -0
  87. package/mcp-server/utils/feedbackTracking.js +41 -0
  88. package/package.json +2 -2
  89. package/commands/project/__tests__/fixtures/exampleProject.json +0 -33
@@ -1,8 +1,6 @@
1
- import { Separator } from '@inquirer/prompts';
2
1
  import { marketplaceDistribution, oAuth, privateDistribution, staticAuth, EMPTY_PROJECT, PROJECT_WITH_APP, FEATURES, } from '../../constants.js';
3
2
  import { commands, lib } from '../../../lang/en.js';
4
3
  import { listPrompt } from '../../prompts/promptUtils.js';
5
- import chalk from 'chalk';
6
4
  import { isV2Project } from '../platformVersion.js';
7
5
  import path from 'path';
8
6
  import { getConfigForPlatformVersion } from './legacy.js';
@@ -75,25 +73,27 @@ export async function calculateComponentTemplateChoices(components, authType, di
75
73
  if (Array.isArray(supportedAuthTypes) &&
76
74
  authType &&
77
75
  !supportedAuthTypes.includes(authType.toLowerCase())) {
78
- disabledReasons.push(commands.project.add.error.authTypeNotAllowed(authType));
76
+ const supportedAuthTypesString = supportedAuthTypes.join(', ');
77
+ disabledReasons.push(commands.project.add.error.authTypeNotAllowed(supportedAuthTypesString));
79
78
  }
80
79
  if (Array.isArray(supportedDistributions) &&
81
80
  distribution &&
82
81
  !supportedDistributions.includes(distribution.toLowerCase())) {
83
- disabledReasons.push(commands.project.add.error.distributionNotAllowed(distribution));
82
+ const supportedDistributionsString = supportedDistributions.join(', ');
83
+ disabledReasons.push(commands.project.add.error.distributionNotAllowed(supportedDistributionsString));
84
84
  }
85
85
  const templateGate = componentTypeToGateMap[template.cliSelector || template.type];
86
86
  if (templateGate) {
87
87
  const isUngated = await hasFeature(accountId, templateGate);
88
88
  if (!isUngated) {
89
- disabledReasons.unshift(commands.project.add.error.portalDoesNotHaveAccessToThisFeature(accountId));
89
+ disabledReasons.unshift(commands.project.add.error.portalDoesNotHaveAccessToThisFeature());
90
90
  }
91
91
  }
92
92
  if (disabledReasons.length > 0) {
93
93
  disabledComponents.push({
94
- name: `[${chalk.yellow('DISABLED')}] ${template.label} -`,
94
+ name: `${template.label} [${template.cliSelector || template.type}]`,
95
95
  value: template,
96
- disabled: disabledReasons.join(' '),
96
+ disabled: `– ${disabledReasons.join(' ')}`,
97
97
  });
98
98
  }
99
99
  else {
@@ -104,12 +104,7 @@ export async function calculateComponentTemplateChoices(components, authType, di
104
104
  }
105
105
  }
106
106
  return disabledComponents.length
107
- ? [
108
- ...enabledComponents,
109
- new Separator(),
110
- ...disabledComponents,
111
- new Separator(),
112
- ]
107
+ ? [...enabledComponents, ...disabledComponents]
113
108
  : [...enabledComponents];
114
109
  }
115
110
  export async function v2ComponentFlow(platformVersion, projectBase, providedAuth, providedDistribution, accountId) {
@@ -26,6 +26,7 @@ declare class AppDevModeInterface {
26
26
  private autoInstallStaticAuthApp;
27
27
  private installAppOrOpenInstallUrl;
28
28
  private checkTestAccountAppInstallation;
29
+ private validateOauthAppRedirectUrl;
29
30
  private resolveAppInstallPromise;
30
31
  private handleAppInstallSuccessDevServerMessage;
31
32
  private handleAppInstallInitiatedDevServerMessage;
@@ -13,6 +13,7 @@ import { uiLogger } from '../../ui/logger.js';
13
13
  import { getOauthAppInstallUrl, getStaticAuthAppInstallUrl, } from '../../app/urls.js';
14
14
  import { isDeveloperTestAccount, isSandbox } from '../../accountTypes.js';
15
15
  import SpinniesManager from '../../ui/SpinniesManager.js';
16
+ import { isServerRunningAtUrl } from '../../http.js';
16
17
  class AppDevModeInterface {
17
18
  localDevState;
18
19
  localDevLogger;
@@ -214,6 +215,18 @@ class AppDevModeInterface {
214
215
  }
215
216
  return { needsInstall: !isInstalledWithScopeGroups, isReinstall };
216
217
  }
218
+ async validateOauthAppRedirectUrl() {
219
+ const redirectUrl = this.appNode?.config.auth.redirectUrls[0];
220
+ if (!redirectUrl) {
221
+ return;
222
+ }
223
+ const serverIsRunningAtRedirectUrl = await isServerRunningAtUrl(redirectUrl);
224
+ if (!serverIsRunningAtRedirectUrl) {
225
+ uiLogger.log('');
226
+ uiLogger.error(lib.AppDevModeInterface.oauthAppRedirectUrlError(redirectUrl));
227
+ process.exit(EXIT_CODES.ERROR);
228
+ }
229
+ }
217
230
  resolveAppInstallPromise() {
218
231
  if (this.appInstallResolve) {
219
232
  this.appInstallResolve();
@@ -284,6 +297,9 @@ class AppDevModeInterface {
284
297
  failColor: 'white',
285
298
  });
286
299
  }
300
+ if (this.isOAuthApp()) {
301
+ await this.validateOauthAppRedirectUrl();
302
+ }
287
303
  this.localDevState.addListener('devServerMessage', this.onDevServerMessage);
288
304
  await this.installAppOrOpenInstallUrl(isReinstall || false);
289
305
  }
@@ -1,7 +1,7 @@
1
1
  import { getAccountId, hasLocalStateFlag } from '@hubspot/local-dev-lib/config';
2
2
  import { getConfigDefaultAccount } from '@hubspot/local-dev-lib/config';
3
3
  import { uiLogger } from '../../ui/logger.js';
4
- import { uiBetaTag, uiLine, uiAccountDescription, uiCommandReference, } from '../../ui/index.js';
4
+ import { uiLine, uiAccountDescription, uiCommandReference, } from '../../ui/index.js';
5
5
  import { lib } from '../../../lang/en.js';
6
6
  import SpinniesManager from '../../ui/SpinniesManager.js';
7
7
  import { logError } from '../../errorHandlers/index.js';
@@ -75,7 +75,7 @@ class LocalDevLogger {
75
75
  if (!this.state.debug) {
76
76
  console.clear();
77
77
  }
78
- uiBetaTag(lib.LocalDevManager.betaMessage);
78
+ uiLogger.log(lib.LocalDevManager.headerMessage);
79
79
  uiLogger.log(lib.LocalDevManager.learnMoreLocalDevServer);
80
80
  uiLogger.log('');
81
81
  uiLogger.log(lib.LocalDevManager.running(this.state.projectConfig.name, uiAccountDescription(this.state.targetProjectAccountId)));
@@ -11,7 +11,7 @@ import { EXIT_CODES } from '../../enums/exitCodes.js';
11
11
  import { getAccountHomeUrl } from '../urls.js';
12
12
  import { componentIsApp, componentIsPublicApp, CONFIG_FILES, getAppCardConfigs, getComponentUid, } from '../structure.js';
13
13
  import { ComponentTypes, } from '../../../types/Projects.js';
14
- import { UI_COLORS, uiCommandReference, uiAccountDescription, uiBetaTag, uiLink, uiLine, } from '../../ui/index.js';
14
+ import { UI_COLORS, uiCommandReference, uiAccountDescription, uiLink, uiLine, } from '../../ui/index.js';
15
15
  import { logError } from '../../errorHandlers/index.js';
16
16
  import { installAppBrowserPrompt } from '../../prompts/installAppPrompt.js';
17
17
  import { confirmPrompt } from '../../prompts/promptUtils.js';
@@ -131,8 +131,8 @@ class LocalDevManager {
131
131
  else if (!this.debug) {
132
132
  console.clear();
133
133
  }
134
- uiBetaTag(lib.LocalDevManager.betaMessage);
135
- uiLogger.log(uiLink(lib.LocalDevManager.learnMoreLocalDevServer, 'https://developers.hubspot.com/docs/platform/project-cli-commands#start-a-local-development-server'));
134
+ uiLogger.log(lib.LocalDevManager.headerMessage);
135
+ uiLogger.log(uiLink(lib.LocalDevManager.learnMoreLocalDevServer, 'https://developers.hubspot.com/docs/developer-tooling/local-development/hubspot-cli/project-commands'));
136
136
  uiLogger.log('');
137
137
  uiLogger.log(chalk.hex(UI_COLORS.SORBET)(lib.LocalDevManager.running(this.projectConfig.name, uiAccountDescription(this.targetAccountId))));
138
138
  uiLogger.log(lib.LocalDevManager.viewProjectLink(this.projectConfig.name, this.targetProjectAccountId));
@@ -6,7 +6,7 @@ import { LOCAL_DEV_UI_MESSAGE_SEND_TYPES, LOCAL_DEV_SERVER_MESSAGE_TYPES, CONFIG
6
6
  import { lib } from '../../../lang/en.js';
7
7
  import { removeAnsiCodes } from '../../ui/removeAnsiCodes.js';
8
8
  import { isDeployWebsocketMessage, isViewedWelcomeScreenWebsocketMessage, isUploadWebsocketMessage, isAppInstallFailureWebsocketMessage, isAppInstallSuccessWebsocketMessage, isAppInstallInitiatedWebsocketMessage, } from './localDevWebsocketServerUtils.js';
9
- import pkg from '../../../package.json' with { type: 'json' };
9
+ import { pkg } from '../../jsonLoader.js';
10
10
  const LOCAL_DEV_WEBSOCKET_SERVER_VERSION = 2;
11
11
  const LOG_PREFIX = '[LocalDevWebsocketServer]';
12
12
  const DOMAINS = ['hubspot.com', 'hubspotqa.com'];
@@ -6,6 +6,14 @@ export declare const PROMPT_THEME: {
6
6
  idle: string;
7
7
  };
8
8
  };
9
+ export declare const CHECKBOX_PROMPT_THEME: {
10
+ prefix: {
11
+ idle: string;
12
+ };
13
+ style: {
14
+ disabledChoice: (text: string) => string;
15
+ };
16
+ };
9
17
  export declare function promptUser<T extends GenericPromptResponse>(config: PromptConfig<T> | PromptConfig<T>[]): Promise<T>;
10
18
  export declare function confirmPrompt(message: string, options?: {
11
19
  defaultAnswer?: boolean;
@@ -5,6 +5,12 @@ import { lib } from '../../lang/en.js';
5
5
  import { uiLogger } from '../ui/logger.js';
6
6
  export const Separator = new _Separator();
7
7
  export const PROMPT_THEME = { prefix: { idle: chalk.green('?') } };
8
+ export const CHECKBOX_PROMPT_THEME = {
9
+ prefix: { idle: chalk.green('?') },
10
+ style: {
11
+ disabledChoice: (text) => chalk.dim(` ◯ ${text}`),
12
+ },
13
+ };
8
14
  function isUserCancellationError(error) {
9
15
  return error instanceof Error && error.name === 'ExitPromptError';
10
16
  }
@@ -147,7 +153,7 @@ function handleCheckboxPrompt(config) {
147
153
  pageSize: config.pageSize,
148
154
  validate: config.validate,
149
155
  loop: config.loop,
150
- theme: PROMPT_THEME,
156
+ theme: CHECKBOX_PROMPT_THEME,
151
157
  shortcuts: {
152
158
  invert: null,
153
159
  },
@@ -1,6 +1,7 @@
1
1
  import { Separator } from '@inquirer/prompts';
2
2
  import { promptUser } from './promptUtils.js';
3
3
  import { lib } from '../../lang/en.js';
4
+ import { uiLogger } from '../ui/logger.js';
4
5
  function findTemplateByNameOrLabel(projectTemplates, templateNameOrLabel) {
5
6
  return projectTemplates.find(t => t.name === templateNameOrLabel || t.label === templateNameOrLabel);
6
7
  }
@@ -55,6 +56,9 @@ export async function selectProjectTemplatePrompt(promptOptions, projectTemplate
55
56
  pageSize: componentTemplates?.length,
56
57
  },
57
58
  ]);
59
+ if (result.componentTemplates?.length === 0) {
60
+ uiLogger.log(lib.projects.add.nothingAdded);
61
+ }
58
62
  if (!result.componentTemplates) {
59
63
  result.componentTemplates = selectedComponents;
60
64
  }
@@ -85,7 +85,7 @@ export async function syncSandbox(accountConfig, parentAccountConfig, env, syncT
85
85
  else if (isSpecifiedError(err, {
86
86
  statusCode: 404,
87
87
  })) {
88
- uiCommandDisabledBanner('hs sandbox sync', 'https://app.hubspot.com/l/docs/guides/crm/project-cli-commands#developer-projects-cli-commands-beta');
88
+ uiCommandDisabledBanner('hs sandbox sync', 'https://developers.hubspot.com/docs/developer-tooling/local-development/hubspot-cli/project-commands');
89
89
  }
90
90
  else {
91
91
  logError(err, new ApiErrorContext({
@@ -2,9 +2,9 @@ import { trackUsage } from '@hubspot/local-dev-lib/trackUsage';
2
2
  import { isTrackingAllowed, getAccountConfig, } from '@hubspot/local-dev-lib/config';
3
3
  import { API_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constants/auth';
4
4
  import { uiLogger } from './ui/logger.js';
5
- import packageJson from '../package.json' with { type: 'json' };
6
- const version = packageJson.version;
5
+ import { pkg } from './jsonLoader.js';
7
6
  import { debugError } from './errorHandlers/index.js';
7
+ const version = pkg.version;
8
8
  export const EventClass = {
9
9
  USAGE: 'USAGE',
10
10
  INTERACTION: 'INTERACTION',
@@ -80,10 +80,10 @@ export class HsCreateFunctionTool extends Tool {
80
80
  }
81
81
  try {
82
82
  const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
83
- return formatTextContents(stdout, stderr);
83
+ return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
84
84
  }
85
85
  catch (error) {
86
- return formatTextContents(error instanceof Error ? error.message : `${error}`);
86
+ return formatTextContents(absoluteCurrentWorkingDirectory, error instanceof Error ? error.message : `${error}`);
87
87
  }
88
88
  }
89
89
  register() {
@@ -102,10 +102,10 @@ export class HsCreateModuleTool extends Tool {
102
102
  }
103
103
  try {
104
104
  const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
105
- return formatTextContents(stdout, stderr);
105
+ return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
106
106
  }
107
107
  catch (error) {
108
- return formatTextContents(error instanceof Error ? error.message : `${error}`);
108
+ return formatTextContents(absoluteCurrentWorkingDirectory, error instanceof Error ? error.message : `${error}`);
109
109
  }
110
110
  }
111
111
  register() {
@@ -59,10 +59,10 @@ export class HsCreateTemplateTool extends Tool {
59
59
  }
60
60
  try {
61
61
  const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
62
- return formatTextContents(stdout, stderr);
62
+ return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
63
63
  }
64
64
  catch (error) {
65
- return formatTextContents(error instanceof Error ? error.message : `${error}`);
65
+ return formatTextContents(absoluteCurrentWorkingDirectory, error instanceof Error ? error.message : `${error}`);
66
66
  }
67
67
  }
68
68
  register() {
@@ -52,18 +52,11 @@ export class HsFunctionLogsTool extends Tool {
52
52
  }
53
53
  try {
54
54
  const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
55
- return formatTextContents(stdout, stderr);
55
+ return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
56
56
  }
57
57
  catch (error) {
58
58
  const errorMessage = error instanceof Error ? error.message : String(error);
59
- return {
60
- content: [
61
- {
62
- type: 'text',
63
- text: `Error executing hs logs command: ${errorMessage}`,
64
- },
65
- ],
66
- };
59
+ return formatTextContents(absoluteCurrentWorkingDirectory, `Error executing hs logs command: ${errorMessage}`);
67
60
  }
68
61
  }
69
62
  register() {
@@ -34,7 +34,7 @@ export class HsListFunctionsTool extends Tool {
34
34
  }
35
35
  try {
36
36
  const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
37
- return formatTextContents(stdout, stderr);
37
+ return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
38
38
  }
39
39
  catch (error) {
40
40
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -34,7 +34,7 @@ export class HsListTool extends Tool {
34
34
  }
35
35
  try {
36
36
  const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
37
- return formatTextContents(stdout, stderr);
37
+ return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
38
38
  }
39
39
  catch (error) {
40
40
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -2,13 +2,15 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsCreateFunctionTool } from '../HsCreateFunctionTool.js';
3
3
  import { runCommandInDir } from '../../../utils/project.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
- import { HTTP_METHODS } from '../../../../types/Cms.js';
5
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
7
7
  vi.mock('../../../utils/project');
8
8
  vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/toolUsageTracking', () => ({
10
10
  trackToolUsage: vi.fn(),
11
11
  }));
12
+ vi.mock('../../../utils/feedbackTracking');
13
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
12
14
  const mockRunCommandInDir = runCommandInDir;
13
15
  const mockAddFlag = addFlag;
14
16
  describe('HsCreateFunctionTool', () => {
@@ -23,16 +25,17 @@ describe('HsCreateFunctionTool', () => {
23
25
  };
24
26
  mockRegisteredTool = {};
25
27
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
28
+ mockMcpFeedbackRequest.mockResolvedValue('');
26
29
  tool = new HsCreateFunctionTool(mockMcpServer);
27
30
  });
28
31
  describe('register', () => {
29
32
  it('should register the tool with the MCP server', () => {
30
33
  const result = tool.register();
31
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-cms-function', {
34
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-cms-function', expect.objectContaining({
32
35
  title: 'Create HubSpot CMS Serverless Function',
33
- description: `Creates a new HubSpot CMS serverless function using the hs create function command. Functions can be created non-interactively by specifying functionsFolder, filename, and endpointPath. Supports all HTTP methods (${HTTP_METHODS.join(', ')}).`,
36
+ description: expect.stringContaining('Creates a new HubSpot CMS serverless function'),
34
37
  inputSchema: expect.any(Object),
35
- }, expect.any(Function));
38
+ }), expect.any(Function));
36
39
  expect(result).toBe(mockRegisteredTool);
37
40
  });
38
41
  });
@@ -2,12 +2,15 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsCreateModuleTool } from '../HsCreateModuleTool.js';
3
3
  import { runCommandInDir } from '../../../utils/project.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
5
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
7
  vi.mock('../../../utils/project');
7
8
  vi.mock('../../../utils/command');
8
9
  vi.mock('../../../utils/toolUsageTracking', () => ({
9
10
  trackToolUsage: vi.fn(),
10
11
  }));
12
+ vi.mock('../../../utils/feedbackTracking');
13
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
11
14
  const mockRunCommandInDir = runCommandInDir;
12
15
  const mockAddFlag = addFlag;
13
16
  describe('HsCreateModuleTool', () => {
@@ -22,16 +25,17 @@ describe('HsCreateModuleTool', () => {
22
25
  };
23
26
  mockRegisteredTool = {};
24
27
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
28
+ mockMcpFeedbackRequest.mockResolvedValue('');
25
29
  tool = new HsCreateModuleTool(mockMcpServer);
26
30
  });
27
31
  describe('register', () => {
28
32
  it('should register the tool with the MCP server', () => {
29
33
  const result = tool.register();
30
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-cms-module', {
34
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-cms-module', expect.objectContaining({
31
35
  title: 'Create HubSpot CMS Module',
32
- description: 'Creates a new HubSpot CMS module using the hs create module command. Modules can be created non-interactively by specifying moduleLabel and other module options. You can create either HubL or React modules by setting the reactType parameter.',
36
+ description: expect.stringContaining('Creates a new HubSpot CMS module'),
33
37
  inputSchema: expect.any(Object),
34
- }, expect.any(Function));
38
+ }), expect.any(Function));
35
39
  expect(result).toBe(mockRegisteredTool);
36
40
  });
37
41
  });
@@ -2,13 +2,15 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsCreateTemplateTool } from '../HsCreateTemplateTool.js';
3
3
  import { runCommandInDir } from '../../../utils/project.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
- import { TEMPLATE_TYPES } from '../../../../types/Cms.js';
5
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
7
7
  vi.mock('../../../utils/project');
8
8
  vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/toolUsageTracking', () => ({
10
10
  trackToolUsage: vi.fn(),
11
11
  }));
12
+ vi.mock('../../../utils/feedbackTracking');
13
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
12
14
  const mockRunCommandInDir = runCommandInDir;
13
15
  const mockAddFlag = addFlag;
14
16
  describe('HsCreateTemplateTool', () => {
@@ -23,16 +25,17 @@ describe('HsCreateTemplateTool', () => {
23
25
  };
24
26
  mockRegisteredTool = {};
25
27
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
28
+ mockMcpFeedbackRequest.mockResolvedValue('');
26
29
  tool = new HsCreateTemplateTool(mockMcpServer);
27
30
  });
28
31
  describe('register', () => {
29
32
  it('should register the tool with the MCP server', () => {
30
33
  const result = tool.register();
31
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-cms-template', {
34
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-cms-template', expect.objectContaining({
32
35
  title: 'Create HubSpot CMS Template',
33
- description: `Creates a new HubSpot CMS template using the hs create template command. Templates can be created non-interactively by specifying templateType. Supports all template types including: ${TEMPLATE_TYPES.join(', ')}.`,
36
+ description: expect.stringContaining('Creates a new HubSpot CMS template'),
34
37
  inputSchema: expect.any(Object),
35
- }, expect.any(Function));
38
+ }), expect.any(Function));
36
39
  expect(result).toBe(mockRegisteredTool);
37
40
  });
38
41
  });
@@ -2,12 +2,15 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsFunctionLogsTool } from '../HsFunctionLogsTool.js';
3
3
  import { runCommandInDir } from '../../../utils/project.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
5
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
7
  vi.mock('../../../utils/project');
7
8
  vi.mock('../../../utils/command');
8
9
  vi.mock('../../../utils/toolUsageTracking', () => ({
9
10
  trackToolUsage: vi.fn(),
10
11
  }));
12
+ vi.mock('../../../utils/feedbackTracking');
13
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
11
14
  const mockRunCommandInDir = runCommandInDir;
12
15
  const mockAddFlag = addFlag;
13
16
  describe('HsFunctionLogsTool', () => {
@@ -22,6 +25,7 @@ describe('HsFunctionLogsTool', () => {
22
25
  };
23
26
  mockRegisteredTool = {};
24
27
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
28
+ mockMcpFeedbackRequest.mockResolvedValue('');
25
29
  tool = new HsFunctionLogsTool(mockMcpServer);
26
30
  });
27
31
  describe('register', () => {
@@ -29,7 +33,7 @@ describe('HsFunctionLogsTool', () => {
29
33
  const result = tool.register();
30
34
  expect(mockMcpServer.registerTool).toHaveBeenCalledWith('get-cms-serverless-function-logs', expect.objectContaining({
31
35
  title: 'Get HubSpot CMS serverless function logs for an endpoint',
32
- description: 'Retrieve logs for HubSpot CMS serverless functions. Use this tool to help debug issues with serverless functions by reading the production logs. Supports various options like latest, compact, and limiting results. Use after listing functions with list-cms-serverless-functions to get the endpoint path.',
36
+ description: expect.stringContaining('Retrieve logs for HubSpot CMS serverless functions'),
33
37
  inputSchema: expect.any(Object),
34
38
  }), expect.any(Function));
35
39
  expect(result).toBe(mockRegisteredTool);
@@ -2,12 +2,15 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsListFunctionsTool } from '../HsListFunctionsTool.js';
3
3
  import { runCommandInDir } from '../../../utils/project.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
5
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
7
  vi.mock('../../../utils/project');
7
8
  vi.mock('../../../utils/command');
8
9
  vi.mock('../../../utils/toolUsageTracking', () => ({
9
10
  trackToolUsage: vi.fn(),
10
11
  }));
12
+ vi.mock('../../../utils/feedbackTracking');
13
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
11
14
  const mockRunCommandInDir = runCommandInDir;
12
15
  const mockAddFlag = addFlag;
13
16
  describe('HsListFunctionsTool', () => {
@@ -22,16 +25,18 @@ describe('HsListFunctionsTool', () => {
22
25
  };
23
26
  mockRegisteredTool = {};
24
27
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
28
+ mockMcpFeedbackRequest.mockResolvedValue('');
25
29
  tool = new HsListFunctionsTool(mockMcpServer);
26
30
  });
27
31
  describe('register', () => {
28
32
  it('should register the tool with the MCP server', () => {
29
33
  const result = tool.register();
30
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('list-cms-serverless-functions', {
34
+ // First assertion - verify original description
35
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('list-cms-serverless-functions', expect.objectContaining({
31
36
  title: 'List HubSpot CMS Serverless Functions',
32
- description: 'Get a list of all serverless functions deployed in a HubSpot portal/account. Shows function routes, HTTP methods, secrets, and timestamps.',
37
+ description: expect.stringContaining('Get a list of all serverless functions deployed in a HubSpot portal/account'),
33
38
  inputSchema: expect.any(Object),
34
- }, expect.any(Function));
39
+ }), expect.any(Function));
35
40
  expect(result).toBe(mockRegisteredTool);
36
41
  });
37
42
  });
@@ -2,12 +2,15 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsListTool } from '../HsListTool.js';
3
3
  import { runCommandInDir } from '../../../utils/project.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
5
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
7
  vi.mock('../../../utils/project');
7
8
  vi.mock('../../../utils/command');
8
9
  vi.mock('../../../utils/toolUsageTracking', () => ({
9
10
  trackToolUsage: vi.fn(),
10
11
  }));
12
+ vi.mock('../../../utils/feedbackTracking');
13
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
11
14
  const mockRunCommandInDir = runCommandInDir;
12
15
  const mockAddFlag = addFlag;
13
16
  describe('HsListTool', () => {
@@ -22,16 +25,18 @@ describe('HsListTool', () => {
22
25
  };
23
26
  mockRegisteredTool = {};
24
27
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
28
+ mockMcpFeedbackRequest.mockResolvedValue('');
25
29
  tool = new HsListTool(mockMcpServer);
26
30
  });
27
31
  describe('register', () => {
28
32
  it('should register the tool with the MCP server', () => {
29
33
  const result = tool.register();
30
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('list-cms-remote-contents', {
34
+ // First assertion - verify original description
35
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('list-cms-remote-contents', expect.objectContaining({
31
36
  title: 'List HubSpot CMS Directory Contents',
32
- description: 'List remote contents of a HubSpot CMS directory.',
37
+ description: expect.stringContaining('List remote contents of a HubSpot CMS directory'),
33
38
  inputSchema: expect.any(Object),
34
- }, expect.any(Function));
39
+ }), expect.any(Function));
35
40
  expect(result).toBe(mockRegisteredTool);
36
41
  });
37
42
  });
@@ -3,18 +3,21 @@ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.
3
3
  import { z } from 'zod';
4
4
  declare const inputSchemaZodObject: z.ZodObject<{
5
5
  absoluteProjectPath: z.ZodString;
6
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
7
  addApp: z.ZodBoolean;
7
8
  distribution: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"marketplace">, z.ZodLiteral<"private">]>>;
8
9
  auth: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"static">, z.ZodLiteral<"oauth">]>>;
9
10
  features: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">, z.ZodLiteral<"workflow-action">, z.ZodLiteral<"workflow-action-tool">, z.ZodLiteral<"app-object">, z.ZodLiteral<"app-event">, z.ZodLiteral<"scim">, z.ZodLiteral<"page">]>, "many">>;
10
11
  }, "strip", z.ZodTypeAny, {
11
12
  absoluteProjectPath: string;
13
+ absoluteCurrentWorkingDirectory: string;
12
14
  addApp: boolean;
13
15
  auth?: "oauth" | "static" | undefined;
14
16
  distribution?: "marketplace" | "private" | undefined;
15
17
  features?: ("card" | "settings" | "page" | "app-event" | "workflow-action-tool" | "workflow-action" | "app-function" | "webhooks" | "app-object" | "scim")[] | undefined;
16
18
  }, {
17
19
  absoluteProjectPath: string;
20
+ absoluteCurrentWorkingDirectory: string;
18
21
  addApp: boolean;
19
22
  auth?: "oauth" | "static" | undefined;
20
23
  distribution?: "marketplace" | "private" | undefined;
@@ -23,7 +26,7 @@ declare const inputSchemaZodObject: z.ZodObject<{
23
26
  export type AddFeatureInputSchema = z.infer<typeof inputSchemaZodObject>;
24
27
  export declare class AddFeatureToProjectTool extends Tool<AddFeatureInputSchema> {
25
28
  constructor(mcpServer: McpServer);
26
- handler({ absoluteProjectPath, distribution, auth, features, addApp, }: AddFeatureInputSchema): Promise<TextContentResponse>;
29
+ handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, distribution, auth, features, addApp, }: AddFeatureInputSchema): Promise<TextContentResponse>;
27
30
  register(): RegisteredTool;
28
31
  }
29
32
  export {};
@@ -2,12 +2,13 @@ import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, } from '../../../lib/constants.js';
4
4
  import { addFlag } from '../../utils/command.js';
5
- import { absoluteProjectPath, features } from './constants.js';
5
+ import { absoluteCurrentWorkingDirectory, absoluteProjectPath, features, } from './constants.js';
6
6
  import { runCommandInDir } from '../../utils/project.js';
7
7
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
8
8
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
9
9
  const inputSchema = {
10
10
  absoluteProjectPath,
11
+ absoluteCurrentWorkingDirectory,
11
12
  addApp: z
12
13
  .boolean()
13
14
  .describe('Should an app be added? If there is no app in the project, an app must be added to add a feature'),
@@ -34,7 +35,7 @@ export class AddFeatureToProjectTool extends Tool {
34
35
  constructor(mcpServer) {
35
36
  super(mcpServer);
36
37
  }
37
- async handler({ absoluteProjectPath, distribution, auth, features, addApp, }) {
38
+ async handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, distribution, auth, features, addApp, }) {
38
39
  try {
39
40
  await trackToolUsage(toolName);
40
41
  let command = `hs project add`;
@@ -59,16 +60,16 @@ export class AddFeatureToProjectTool extends Tool {
59
60
  // If features isn't provided, pass an empty array to bypass the prompt
60
61
  command = addFlag(command, 'features', features || []);
61
62
  const { stdout, stderr } = await runCommandInDir(absoluteProjectPath, command);
62
- return formatTextContents(stdout, stderr);
63
+ return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
63
64
  }
64
65
  catch (error) {
65
- return formatTextContents(error instanceof Error ? error.message : `${error}`);
66
+ return formatTextContents(absoluteCurrentWorkingDirectory, error instanceof Error ? error.message : `${error}`);
66
67
  }
67
68
  }
68
69
  register() {
69
70
  return this.mcpServer.registerTool(toolName, {
70
71
  title: 'Add feature to HubSpot Project',
71
- description: `Adds a feature to an existing HubSpot project.
72
+ description: `Adds a feature to an existing HubSpot project.
72
73
  Only works for projects with platformVersion '2025.2' and beyond`,
73
74
  inputSchema,
74
75
  }, this.handler);
@@ -77,10 +77,10 @@ export class CreateProjectTool extends Tool {
77
77
  command = addFlag(command, 'features', features || []);
78
78
  try {
79
79
  const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
80
- return formatTextContents(stdout, stderr);
80
+ return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
81
81
  }
82
82
  catch (error) {
83
- return formatTextContents(error instanceof Error ? error.message : `${error}`);
83
+ return formatTextContents(absoluteCurrentWorkingDirectory, error instanceof Error ? error.message : `${error}`);
84
84
  }
85
85
  }
86
86
  register() {