@hubspot/cli 7.9.0-beta.1 → 7.9.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 (83) hide show
  1. package/commands/project/__tests__/deploy.test.js +4 -3
  2. package/lang/en.d.ts +10 -10
  3. package/lang/en.js +13 -13
  4. package/lib/__tests__/npm.test.js +1 -1
  5. package/lib/__tests__/sandboxSync.test.js +1 -1
  6. package/lib/__tests__/usageTracking.test.js +2 -2
  7. package/lib/configMigrate.js +3 -3
  8. package/lib/doctor/DiagnosticInfoBuilder.js +1 -1
  9. package/lib/doctor/Doctor.js +1 -1
  10. package/lib/doctor/__tests__/DiagnosticInfoBuilder.test.js +4 -2
  11. package/lib/doctor/__tests__/Doctor.test.js +1 -1
  12. package/lib/jsonLoader.d.ts +14 -0
  13. package/lib/jsonLoader.js +60 -0
  14. package/lib/middleware/__test__/requestMiddleware.test.js +1 -1
  15. package/lib/middleware/autoUpdateMiddleware.js +1 -1
  16. package/lib/middleware/commandTargetingUtils.js +1 -0
  17. package/lib/middleware/fireAlarmMiddleware.js +1 -1
  18. package/lib/middleware/notificationsMiddleware.js +1 -1
  19. package/lib/middleware/requestMiddleware.js +1 -1
  20. package/lib/npm.js +1 -1
  21. package/lib/projects/__tests__/AppDevModeInterface.test.js +3 -0
  22. package/lib/projects/create/__tests__/v2.test.js +20 -14
  23. package/lib/projects/create/v2.js +8 -13
  24. package/lib/projects/localDev/LocalDevLogger.js +2 -2
  25. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -3
  26. package/lib/projects/localDev/LocalDevWebsocketServer.js +1 -1
  27. package/lib/prompts/promptUtils.d.ts +8 -0
  28. package/lib/prompts/promptUtils.js +7 -1
  29. package/lib/prompts/selectProjectTemplatePrompt.js +4 -0
  30. package/lib/sandboxSync.js +1 -1
  31. package/lib/usageTracking.js +2 -2
  32. package/mcp-server/tools/cms/HsCreateFunctionTool.js +2 -2
  33. package/mcp-server/tools/cms/HsCreateModuleTool.js +2 -2
  34. package/mcp-server/tools/cms/HsCreateTemplateTool.js +2 -2
  35. package/mcp-server/tools/cms/HsFunctionLogsTool.js +2 -9
  36. package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
  37. package/mcp-server/tools/cms/HsListTool.js +1 -1
  38. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +7 -4
  39. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +7 -3
  40. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +7 -4
  41. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +5 -1
  42. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +8 -3
  43. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +8 -3
  44. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +4 -1
  45. package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -5
  46. package/mcp-server/tools/project/CreateProjectTool.js +2 -2
  47. package/mcp-server/tools/project/DeployProjectTool.d.ts +4 -1
  48. package/mcp-server/tools/project/DeployProjectTool.js +4 -3
  49. package/mcp-server/tools/project/DocFetchTool.d.ts +4 -1
  50. package/mcp-server/tools/project/DocFetchTool.js +7 -6
  51. package/mcp-server/tools/project/DocsSearchTool.js +5 -5
  52. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +4 -1
  53. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +7 -5
  54. package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +8 -2
  55. package/mcp-server/tools/project/GetApplicationInfoTool.js +7 -6
  56. package/mcp-server/tools/project/GetConfigValuesTool.js +4 -4
  57. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +4 -1
  58. package/mcp-server/tools/project/GuidedWalkthroughTool.js +6 -14
  59. package/mcp-server/tools/project/UploadProjectTools.d.ts +4 -1
  60. package/mcp-server/tools/project/UploadProjectTools.js +4 -3
  61. package/mcp-server/tools/project/ValidateProjectTool.d.ts +4 -1
  62. package/mcp-server/tools/project/ValidateProjectTool.js +5 -4
  63. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +6 -1
  64. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +7 -3
  65. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +7 -2
  66. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +8 -3
  67. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +6 -2
  68. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +8 -3
  69. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +9 -5
  70. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +6 -2
  71. package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +43 -13
  72. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +8 -2
  73. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +8 -2
  74. package/mcp-server/utils/__tests__/content.test.d.ts +1 -0
  75. package/mcp-server/utils/__tests__/content.test.js +166 -0
  76. package/mcp-server/utils/__tests__/feedbackTracking.test.d.ts +1 -0
  77. package/mcp-server/utils/__tests__/feedbackTracking.test.js +121 -0
  78. package/mcp-server/utils/content.d.ts +1 -1
  79. package/mcp-server/utils/content.js +8 -1
  80. package/mcp-server/utils/feedbackTracking.d.ts +1 -0
  81. package/mcp-server/utils/feedbackTracking.js +41 -0
  82. package/package.json +2 -2
  83. package/commands/project/__tests__/fixtures/exampleProject.json +0 -33
@@ -12,8 +12,9 @@ import * as projectNamePrompt from '../../../lib/prompts/projectNamePrompt.js';
12
12
  import * as promptUtils from '../../../lib/prompts/promptUtils.js';
13
13
  import { trackCommandUsage } from '../../../lib/usageTracking.js';
14
14
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
15
- import exampleProject from './fixtures/exampleProject.json' with { type: 'json' };
15
+ import { loadJson } from '../../../lib/jsonLoader.js';
16
16
  import { mockHubSpotHttpResponse, mockHubSpotHttpError, } from '../../../lib/testUtils.js';
17
+ const exampleProject = loadJson(import.meta.url, './fixtures/exampleProject.json');
17
18
  import projectDeployCommand from '../deploy.js';
18
19
  import { uiLogger } from '../../../lib/ui/logger.js';
19
20
  vi.mock('@hubspot/local-dev-lib/api/projects');
@@ -204,7 +205,7 @@ describe('commands/project/deploy', () => {
204
205
  expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
205
206
  });
206
207
  it('should log an error and exit when buildId option is not a valid build', async () => {
207
- args.buildId = exampleProject.latestBuild.buildId + 1;
208
+ args.buildId = (exampleProject.latestBuild?.buildId ?? 0) + 1;
208
209
  await projectDeployCommand.handler(args);
209
210
  expect(uiLogger.error).toHaveBeenCalledTimes(1);
210
211
  expect(uiLogger.error).toHaveBeenCalledWith(expect.stringMatching(`Build ${args.buildId} does not exist for project`));
@@ -222,7 +223,7 @@ describe('commands/project/deploy', () => {
222
223
  it('should prompt for build id if no option is provided', async () => {
223
224
  delete args.buildId;
224
225
  promptUserSpy.mockResolvedValue({
225
- buildId: exampleProject.latestBuild.buildId,
226
+ buildId: exampleProject.latestBuild?.buildId ?? 0,
226
227
  });
227
228
  await projectDeployCommand.handler(args);
228
229
  expect(promptUserSpy).toHaveBeenCalledTimes(1);
package/lang/en.d.ts CHANGED
@@ -1382,7 +1382,7 @@ Local development of unified apps is currently only compatible with accounts tha
1382
1382
  ${string}`;
1383
1383
  };
1384
1384
  readonly prompts: {
1385
- readonly parentComponents: "[--project-base] What would you like in your project?";
1385
+ readonly parentComponents: "[--project-base] Choose what to include in your project:";
1386
1386
  readonly emptyProject: "Empty Project";
1387
1387
  readonly app: "App";
1388
1388
  };
@@ -1532,7 +1532,7 @@ ${string}`;
1532
1532
  readonly maxExceeded: (maxCount: number) => string;
1533
1533
  readonly authTypeNotAllowed: (authType: string) => string;
1534
1534
  readonly distributionNotAllowed: (dist: string) => string;
1535
- readonly portalDoesNotHaveAccessToThisFeature: (accountId: number) => string;
1535
+ readonly portalDoesNotHaveAccessToThisFeature: () => string;
1536
1536
  readonly locationInProject: "This command must be run from within a project directory.";
1537
1537
  readonly failedToFetchComponentList: "Failed to fetch the list of available features. Please try again later.";
1538
1538
  readonly projectContainsPublicApp: "This project contains a public app. This command is currently only compatible with projects that contain private apps.";
@@ -2771,7 +2771,7 @@ export declare const lib: {
2771
2771
  readonly failedToInitialize: "Missing required arguments to initialize Local Dev";
2772
2772
  readonly noDeployedBuild: (projectName: string, accountIdentifier: string, uploadCommand: string) => string;
2773
2773
  readonly noComponents: "There are no components in this project.";
2774
- readonly betaMessage: "HubSpot projects local development";
2774
+ readonly headerMessage: "HubSpot projects local development";
2775
2775
  readonly learnMoreLocalDevServer: string;
2776
2776
  readonly running: (projectName: string, accountIdentifier: string) => string;
2777
2777
  readonly quitHelper: `Press ${string} to stop the local dev server`;
@@ -2956,8 +2956,8 @@ Run ${string} to upgrade to version ${string}`;
2956
2956
  readonly prompt: {
2957
2957
  readonly marketPlaceDistribution: "On the HubSpot marketplace";
2958
2958
  readonly privateDistribution: "Privately";
2959
- readonly distribution: "[--distribution] How would you like to distribute your application?";
2960
- readonly auth: "[--auth] What type of authentication would you like your application to use";
2959
+ readonly distribution: "[--distribution] Choose how to distribute your application:";
2960
+ readonly auth: "[--auth] Choose your authentication type:";
2961
2961
  readonly staticAuth: "Static Auth";
2962
2962
  readonly oauth: "OAuth";
2963
2963
  };
@@ -3364,8 +3364,8 @@ Run ${string} to upgrade to version ${string}`;
3364
3364
  };
3365
3365
  };
3366
3366
  readonly projectNameAndDestPrompt: {
3367
- readonly enterName: "[--name] Give your project a name: ";
3368
- readonly enterDest: "[--dest] Enter the folder to create the project in:";
3367
+ readonly enterName: "[--name] Enter your project name:";
3368
+ readonly enterDest: "[--dest] Choose where to create the project:";
3369
3369
  readonly errors: {
3370
3370
  readonly nameRequired: "A project name is required";
3371
3371
  readonly destRequired: "A project dest is required";
@@ -3375,7 +3375,7 @@ Run ${string} to upgrade to version ${string}`;
3375
3375
  };
3376
3376
  readonly selectProjectTemplatePrompt: {
3377
3377
  readonly selectTemplate: "[--template] Choose a project template: ";
3378
- readonly features: "[--features] Which features would you like your app to include?";
3378
+ readonly features: "[--features] Choose which features to add:";
3379
3379
  readonly errors: {
3380
3380
  readonly invalidTemplate: (template: string) => string;
3381
3381
  readonly projectTemplateRequired: "Project template is required when projectTemplates is provided";
@@ -3409,8 +3409,8 @@ Run ${string} to upgrade to version ${string}`;
3409
3409
  };
3410
3410
  };
3411
3411
  readonly projectAddPrompt: {
3412
- readonly selectType: "[--type] Select an app feature to add: ";
3413
- readonly selectFeatures: "[--features] Select an app feature to add: ";
3412
+ readonly selectType: "[--type] Choose which features to add: ";
3413
+ readonly selectFeatures: "[--features] Choose which features to add: ";
3414
3414
  readonly enterName: "[--name] Give your component a name: ";
3415
3415
  readonly errors: {
3416
3416
  readonly nameRequired: "A component name is required";
package/lang/en.js CHANGED
@@ -1382,7 +1382,7 @@ export const commands = {
1382
1382
  welcomeMessage: `\n${chalk.bold('Welcome to HubSpot Developer Projects!')}`,
1383
1383
  },
1384
1384
  prompts: {
1385
- parentComponents: '[--project-base] What would you like in your project?',
1385
+ parentComponents: '[--project-base] Choose what to include in your project:',
1386
1386
  emptyProject: 'Empty Project',
1387
1387
  app: 'App',
1388
1388
  },
@@ -1526,9 +1526,9 @@ export const commands = {
1526
1526
  failedToDownloadComponent: 'Failed to download project. Please try again later.',
1527
1527
  invalidComponentType: (componentType) => `'${componentType}' is not a valid project component type.`,
1528
1528
  maxExceeded: (maxCount) => `This project has the maximum allowed(${maxCount})`,
1529
- authTypeNotAllowed: (authType) => `Auth type '${authType}' not allowed.`,
1530
- distributionNotAllowed: (dist) => `Distribution '${dist}' not allowed.`,
1531
- portalDoesNotHaveAccessToThisFeature: (accountId) => `The account ${uiAccountDescription(accountId)} does not have access to this feature.`,
1529
+ authTypeNotAllowed: (authType) => `Requires auth type '${authType}'.`,
1530
+ distributionNotAllowed: (dist) => `Requires distribution '${dist}'.`,
1531
+ portalDoesNotHaveAccessToThisFeature: () => "This account doesn't have access to this feature.",
1532
1532
  locationInProject: 'This command must be run from within a project directory.',
1533
1533
  failedToFetchComponentList: 'Failed to fetch the list of available features. Please try again later.',
1534
1534
  projectContainsPublicApp: 'This project contains a public app. This command is currently only compatible with projects that contain private apps.',
@@ -2765,8 +2765,8 @@ export const lib = {
2765
2765
  failedToInitialize: 'Missing required arguments to initialize Local Dev',
2766
2766
  noDeployedBuild: (projectName, accountIdentifier, uploadCommand) => `Your project ${chalk.bold(projectName)} exists in ${accountIdentifier}, but has no deployed build. Projects must be successfully deployed to be developed locally. Address any build and deploy errors your project may have, then run ${uploadCommand} to upload and deploy your project.`,
2767
2767
  noComponents: 'There are no components in this project.',
2768
- betaMessage: 'HubSpot projects local development',
2769
- learnMoreLocalDevServer: uiLink('Learn more about the projects local dev server', 'https://developers.hubspot.com/docs/platform/project-cli-commands#start-a-local-development-server'),
2768
+ headerMessage: 'HubSpot projects local development',
2769
+ learnMoreLocalDevServer: uiLink('Learn more about the projects local dev server', 'https://developers.hubspot.com/docs/developer-tooling/local-development/hubspot-cli/project-commands'),
2770
2770
  running: (projectName, accountIdentifier) => chalk.hex(UI_COLORS.SORBET)(`Running ${chalk.bold(projectName)} locally on ${accountIdentifier}, waiting for changes ...`),
2771
2771
  quitHelper: `Press ${chalk.bold('q')} to stop the local dev server`,
2772
2772
  viewProjectLink: (name, accountId) => uiLink('View project in HubSpot', getProjectDetailUrl(name, accountId) || ''),
@@ -2949,8 +2949,8 @@ export const lib = {
2949
2949
  prompt: {
2950
2950
  marketPlaceDistribution: 'On the HubSpot marketplace',
2951
2951
  privateDistribution: 'Privately',
2952
- distribution: '[--distribution] How would you like to distribute your application?',
2953
- auth: '[--auth] What type of authentication would you like your application to use',
2952
+ distribution: '[--distribution] Choose how to distribute your application:',
2953
+ auth: '[--auth] Choose your authentication type:',
2954
2954
  staticAuth: 'Static Auth',
2955
2955
  oauth: 'OAuth',
2956
2956
  },
@@ -3355,8 +3355,8 @@ export const lib = {
3355
3355
  },
3356
3356
  },
3357
3357
  projectNameAndDestPrompt: {
3358
- enterName: '[--name] Give your project a name: ',
3359
- enterDest: '[--dest] Enter the folder to create the project in:',
3358
+ enterName: '[--name] Enter your project name:',
3359
+ enterDest: '[--dest] Choose where to create the project:',
3360
3360
  errors: {
3361
3361
  nameRequired: 'A project name is required',
3362
3362
  destRequired: 'A project dest is required',
@@ -3366,7 +3366,7 @@ export const lib = {
3366
3366
  },
3367
3367
  selectProjectTemplatePrompt: {
3368
3368
  selectTemplate: '[--template] Choose a project template: ',
3369
- features: '[--features] Which features would you like your app to include?',
3369
+ features: '[--features] Choose which features to add:',
3370
3370
  errors: {
3371
3371
  invalidTemplate: (template) => `[--template] Could not find template "${template}". Please choose an available template:`,
3372
3372
  projectTemplateRequired: 'Project template is required when projectTemplates is provided',
@@ -3400,8 +3400,8 @@ export const lib = {
3400
3400
  },
3401
3401
  },
3402
3402
  projectAddPrompt: {
3403
- selectType: '[--type] Select an app feature to add: ',
3404
- selectFeatures: '[--features] Select an app feature to add: ',
3403
+ selectType: '[--type] Choose which features to add: ',
3404
+ selectFeatures: '[--features] Choose which features to add: ',
3405
3405
  enterName: '[--name] Give your component a name: ',
3406
3406
  errors: {
3407
3407
  nameRequired: 'A component name is required',
@@ -1,6 +1,6 @@
1
1
  import util from 'util';
2
2
  import { isGloballyInstalled, getLatestCliVersion, DEFAULT_PACKAGE_MANAGER, } from '../npm.js';
3
- import pkg from '../../package.json' with { type: 'json' };
3
+ import { pkg } from '../jsonLoader.js';
4
4
  vi.mock('../../ui/logger.js');
5
5
  vi.mock('../ui/SpinniesManager');
6
6
  describe('lib/npm', () => {
@@ -59,7 +59,7 @@ describe('lib/sandboxSync', () => {
59
59
  it('throws error when account IDs are missing', async () => {
60
60
  mockedGetAccountId.mockReset();
61
61
  mockedGetAccountId.mockReturnValue(null);
62
- const errorRegex = new RegExp(`Couldn't sync ${mockChildAccount.portalId} because your account has been removed from`);
62
+ const errorRegex = new RegExp(`because your account has been removed from`);
63
63
  await expect(syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)).rejects.toThrow(errorRegex);
64
64
  });
65
65
  it('handles sync in progress error', async () => {
@@ -3,8 +3,8 @@ import { isTrackingAllowed, getAccountConfig, } from '@hubspot/local-dev-lib/con
3
3
  import { API_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constants/auth';
4
4
  import { uiLogger } from '../ui/logger.js';
5
5
  import { trackCommandUsage, trackHelpUsage, trackConvertFieldsUsage, trackAuthAction, trackCommandMetadataUsage, } from '../usageTracking.js';
6
- import packageJson from '../../package.json' with { type: 'json' };
7
- const version = packageJson.version;
6
+ import { pkg } from '../jsonLoader.js';
7
+ const version = pkg.version;
8
8
  vi.mock('@hubspot/local-dev-lib/trackUsage');
9
9
  vi.mock('@hubspot/local-dev-lib/config');
10
10
  vi.mock('../ui/logger.js');
@@ -24,7 +24,7 @@ async function promptNewAccountName(account, globalConfig, renamedAccounts) {
24
24
  if (value === account.name) {
25
25
  return lib.configMigrate.handleAccountNameConflicts.errors.sameName;
26
26
  }
27
- const existingAccount = globalConfig.accounts.some(acc => acc.name === value);
27
+ const existingAccount = globalConfig.accounts?.some(acc => acc.name === value);
28
28
  const renamedAccount = renamedAccounts.some(acc => acc.name === value);
29
29
  if (existingAccount || renamedAccount) {
30
30
  return lib.configMigrate.handleAccountNameConflicts.errors.nameAlreadyInConfig(value);
@@ -85,8 +85,8 @@ async function handleAccountNameConflicts(globalConfig, deprecatedConfig, force)
85
85
  const accountsWithConflictsToRemove = new Set();
86
86
  const renamedAccounts = [];
87
87
  const accountsNotYetInGlobal = deprecatedConfig.portals.filter(portal => portal.portalId &&
88
- !globalConfig.accounts.some(acc => acc.accountId === portal.portalId));
89
- const accountsWithConflicts = accountsNotYetInGlobal.filter(localAccount => globalConfig.accounts.some(globalAccount => globalAccount.name === localAccount.name));
88
+ !globalConfig.accounts?.some(acc => acc.accountId === portal.portalId));
89
+ const accountsWithConflicts = accountsNotYetInGlobal.filter(localAccount => globalConfig.accounts?.some(globalAccount => globalAccount.name === localAccount.name));
90
90
  if (accountsWithConflicts.length > 0) {
91
91
  uiLogger.log('');
92
92
  uiLogger.warn(lib.configMigrate.handleAccountNameConflicts.warnings.accountNameConflictMessage(accountsWithConflicts.length));
@@ -1,7 +1,7 @@
1
1
  import { getProjectConfig } from '../projects/config.js';
2
2
  import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
3
3
  import path from 'path';
4
- import pkg from '../../package.json' with { type: 'json' };
4
+ import { pkg } from '../jsonLoader.js';
5
5
  import { uiLogger } from '../ui/logger.js';
6
6
  import { getAccountId, getDefaultAccountOverrideFilePath, isConfigFlagEnabled, } from '@hubspot/local-dev-lib/config';
7
7
  import { getAccountConfig, getConfigPath } from '@hubspot/local-dev-lib/config';
@@ -13,7 +13,7 @@ import { PORT_MANAGER_SERVER_PORT } from '@hubspot/local-dev-lib/constants/ports
13
13
  import { accessTokenForPersonalAccessKey, authorizedScopesForPortalAndUser, scopesOnAccessToken, } from '@hubspot/local-dev-lib/personalAccessKey';
14
14
  import { isSpecifiedError } from '@hubspot/local-dev-lib/errors/index';
15
15
  import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
16
- import pkg from '../../package.json' with { type: 'json' };
16
+ import { pkg } from '../jsonLoader.js';
17
17
  import { lib } from '../../lang/en.js';
18
18
  import { uiLink } from '../ui/index.js';
19
19
  const minMajorNodeVersion = 18;
@@ -6,11 +6,13 @@ vi.mock('@hubspot/local-dev-lib/personalAccessKey');
6
6
  vi.mock('../../projects/config');
7
7
  vi.mock('@hubspot/local-dev-lib/api/projects');
8
8
  vi.mock('util');
9
- vi.mock('../../../package.json', () => {
9
+ vi.mock('../../jsonLoader.js', () => {
10
10
  return {
11
- default: {
11
+ pkg: {
12
+ name: '@hubspot/cli',
12
13
  version: '1.0.0',
13
14
  },
15
+ loadJson: vi.fn(),
14
16
  };
15
17
  });
16
18
  import { DiagnosticInfoBuilder, } from '../DiagnosticInfoBuilder.js';
@@ -359,7 +359,7 @@ describe('lib/doctor/Doctor', () => {
359
359
  expect(doctor.diagnosis?.addProjectSection).toHaveBeenCalledWith({
360
360
  type: 'warning',
361
361
  message: 'Port 8080 is in use',
362
- secondaryMessaging: expect.stringMatching(/Make sure it is available before running `hs project dev`/),
362
+ secondaryMessaging: expect.stringMatching(/Make sure it is available before running/),
363
363
  });
364
364
  });
365
365
  it('should add success section if port is available', async () => {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Generic JSON loader that works in both test (lib/) and production (dist/lib/) environments.
3
+ * Automatically resolves paths relative to the caller's location.
4
+ *
5
+ * @param importMetaUrl - Pass import.meta.url from the calling file
6
+ * @param relativePath - Path to JSON file relative to the caller (e.g., '../package.json', './fixtures/data.json')
7
+ * @returns The loaded JSON object
8
+ */
9
+ export declare function loadJson<T = unknown>(importMetaUrl: string, relativePath: string): T;
10
+ export declare const pkg: {
11
+ [key: string]: unknown;
12
+ name: string;
13
+ version: string;
14
+ };
@@ -0,0 +1,60 @@
1
+ // NOTE: Can be switched back to standard import with min node version 23
2
+ import { createRequire } from 'module';
3
+ import { fileURLToPath } from 'url';
4
+ import { existsSync } from 'fs';
5
+ import path from 'path';
6
+ /**
7
+ * Generic JSON loader that works in both test (lib/) and production (dist/lib/) environments.
8
+ * Automatically resolves paths relative to the caller's location.
9
+ *
10
+ * @param importMetaUrl - Pass import.meta.url from the calling file
11
+ * @param relativePath - Path to JSON file relative to the caller (e.g., '../package.json', './fixtures/data.json')
12
+ * @returns The loaded JSON object
13
+ */
14
+ export function loadJson(importMetaUrl, relativePath) {
15
+ const callerDir = path.dirname(fileURLToPath(importMetaUrl));
16
+ const resolvedPath = path.resolve(callerDir, relativePath);
17
+ // If the resolved path exists, use it directly
18
+ if (existsSync(resolvedPath)) {
19
+ return createRequire(importMetaUrl)(resolvedPath);
20
+ }
21
+ // If not found, try adjusting for dist/ directory
22
+ // This handles the case where we're in dist/lib/ but the JSON is at project root
23
+ const pathParts = resolvedPath.split(path.sep);
24
+ const distIndex = pathParts.indexOf('dist');
25
+ if (distIndex !== -1) {
26
+ // Remove 'dist' from the path and try again
27
+ pathParts.splice(distIndex, 1);
28
+ const adjustedPath = pathParts.join(path.sep);
29
+ if (existsSync(adjustedPath)) {
30
+ return createRequire(importMetaUrl)(adjustedPath);
31
+ }
32
+ }
33
+ throw new Error(`JSON file not found: ${relativePath} (resolved to ${resolvedPath})`);
34
+ }
35
+ /**
36
+ * Helper to find package.json by walking up the directory tree.
37
+ * Works regardless of whether we're in lib/ or dist/lib/.
38
+ */
39
+ function findPackageJsonPath(startDir) {
40
+ let currentDir = startDir;
41
+ while (true) {
42
+ const pkgPath = path.join(currentDir, 'package.json');
43
+ if (existsSync(pkgPath)) {
44
+ return pkgPath;
45
+ }
46
+ const parentDir = path.dirname(currentDir);
47
+ if (parentDir === currentDir) {
48
+ // Reached root without finding package.json (e.g., in test environments)
49
+ return null;
50
+ }
51
+ currentDir = parentDir;
52
+ }
53
+ }
54
+ // Load package.json once when this module is imported for convenience
55
+ // In test environments where this can't be found, tests should mock this module
56
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
57
+ const pkgPath = findPackageJsonPath(__dirname);
58
+ export const pkg = pkgPath
59
+ ? createRequire(import.meta.url)(pkgPath)
60
+ : { name: 'unknown', version: 'unknown' };
@@ -1,6 +1,6 @@
1
1
  import { addUserAgentHeader } from '@hubspot/local-dev-lib/http';
2
2
  import { setRequestHeaders } from '../requestMiddleware.js';
3
- import pkg from '../../../package.json' with { type: 'json' };
3
+ import { pkg } from '../../jsonLoader.js';
4
4
  vi.mock('@hubspot/local-dev-lib/http', () => ({
5
5
  addUserAgentHeader: vi.fn(),
6
6
  }));
@@ -1,6 +1,6 @@
1
1
  import updateNotifier from 'update-notifier';
2
2
  import { isConfigFlagEnabled } from '@hubspot/local-dev-lib/config';
3
- import pkg from '../../package.json' with { type: 'json' };
3
+ import { pkg } from '../jsonLoader.js';
4
4
  import { UI_COLORS } from '../ui/index.js';
5
5
  import SpinniesManager from '../ui/SpinniesManager.js';
6
6
  import { lib } from '../../lang/en.js';
@@ -35,6 +35,7 @@ export function shouldLoadConfigForCommand(commandParts) {
35
35
  }
36
36
  const SKIP_CONFIG_VALIDATION_COMMANDS = {
37
37
  auth: true,
38
+ account: { auth: true },
38
39
  mcp: {
39
40
  setup: true,
40
41
  start: true,
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { fetchFireAlarms } from '@hubspot/local-dev-lib/api/fireAlarm';
3
3
  import { debugError } from '../errorHandlers/index.js';
4
- import pkg from '../../package.json' with { type: 'json' };
4
+ import { pkg } from '../jsonLoader.js';
5
5
  import { logInBox } from '../ui/boxen.js';
6
6
  import { renderInline } from '../../ui/index.js';
7
7
  import { getWarningBox } from '../../ui/components/StatusMessageBoxes.js';
@@ -1,5 +1,5 @@
1
1
  import updateNotifier from 'update-notifier';
2
- import pkg from '../../package.json' with { type: 'json' };
2
+ import { pkg } from '../jsonLoader.js';
3
3
  import { UI_COLORS } from '../ui/index.js';
4
4
  import { lib } from '../../lang/en.js';
5
5
  const notifier = updateNotifier({
@@ -1,5 +1,5 @@
1
1
  import { addUserAgentHeader } from '@hubspot/local-dev-lib/http';
2
- import pkg from '../../package.json' with { type: 'json' };
2
+ import { pkg } from '../jsonLoader.js';
3
3
  export function setRequestHeaders() {
4
4
  addUserAgentHeader('HubSpot CLI', pkg.version);
5
5
  }
package/lib/npm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { exec as execAsync } from 'node:child_process';
2
2
  import util from 'util';
3
3
  import { uiLogger } from './ui/logger.js';
4
- import pkg from '../package.json' with { type: 'json' };
4
+ import { pkg } from './jsonLoader.js';
5
5
  export const DEFAULT_PACKAGE_MANAGER = 'npm';
6
6
  export async function isGloballyInstalled(packageName) {
7
7
  const exec = util.promisify(execAsync);
@@ -21,6 +21,7 @@ import { confirmPrompt } from '../../prompts/promptUtils.js';
21
21
  import { getOauthAppInstallUrl, getStaticAuthAppInstallUrl, } from '../../app/urls.js';
22
22
  import { isDeveloperTestAccount, isSandbox } from '../../accountTypes.js';
23
23
  import { logError } from '../../errorHandlers/index.js';
24
+ import { isServerRunningAtUrl } from '../../http.js';
24
25
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, APP_INSTALLATION_STATES, LOCAL_DEV_SERVER_MESSAGE_TYPES, } from '../../constants.js';
25
26
  import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
26
27
  vi.mock('@hubspot/local-dev-lib/api/localDevAuth');
@@ -36,6 +37,7 @@ vi.mock('../../errorHandlers/index');
36
37
  vi.mock('../localDev/LocalDevState');
37
38
  vi.mock('../localDev/LocalDevLogger');
38
39
  vi.mock('../../ui/SpinniesManager');
40
+ vi.mock('../../http');
39
41
  describe('AppDevModeInterface', () => {
40
42
  let appDevModeInterface;
41
43
  let mockLocalDevState;
@@ -139,6 +141,7 @@ describe('AppDevModeInterface', () => {
139
141
  });
140
142
  confirmPrompt.mockResolvedValue(true);
141
143
  installStaticAuthAppOnTestAccount.mockResolvedValue(undefined);
144
+ isServerRunningAtUrl.mockResolvedValue(true);
142
145
  // Mock process.exit
143
146
  vi.spyOn(global.process, 'exit').mockImplementation((code) => {
144
147
  throw new Error(`Process.exit called with code ${code}`);
@@ -37,12 +37,12 @@ describe('lib/projects/create/v2', () => {
37
37
  };
38
38
  it('returns enabled components when they meet all requirements', async () => {
39
39
  const choices = await calculateComponentTemplateChoices(mockComponents, 'oauth', 'private', 123, mockProjectMetadataForChoices);
40
- expect(choices).toHaveLength(4); // includes separator
40
+ expect(choices).toHaveLength(2);
41
41
  expect(choices[0]).toEqual({
42
42
  name: 'Module Component [module]',
43
43
  value: mockComponents[0],
44
44
  });
45
- expect(choices[2]).toEqual({
45
+ expect(choices[1]).toEqual({
46
46
  name: expect.stringContaining('Card Component'),
47
47
  value: mockComponents[1],
48
48
  disabled: expect.stringContaining('maximum'),
@@ -50,20 +50,20 @@ describe('lib/projects/create/v2', () => {
50
50
  });
51
51
  it('disables components when auth type is not supported', async () => {
52
52
  const choices = await calculateComponentTemplateChoices(mockComponents, 'privatekey', 'private', 123, mockProjectMetadataForChoices);
53
- // All components should be disabled, so they come after the separator
54
- expect(choices[1]).toEqual({
53
+ // All components should be disabled
54
+ expect(choices[0]).toEqual({
55
55
  name: expect.stringContaining('Module Component'),
56
56
  value: mockComponents[0],
57
- disabled: expect.stringContaining('privatekey'),
57
+ disabled: expect.stringContaining('oauth'),
58
58
  });
59
59
  });
60
60
  it('disables components when distribution is not supported', async () => {
61
61
  const choices = await calculateComponentTemplateChoices(mockComponents, 'oauth', 'enterprise', 123, mockProjectMetadataForChoices);
62
- // All components should be disabled, so they come after the separator
63
- expect(choices[1]).toEqual({
62
+ // All components should be disabled
63
+ expect(choices[0]).toEqual({
64
64
  name: expect.stringContaining('Module Component'),
65
65
  value: mockComponents[0],
66
- disabled: expect.stringContaining('enterprise'),
66
+ disabled: expect.stringContaining('private'),
67
67
  });
68
68
  });
69
69
  it('handles components without auth type or distribution restrictions', async () => {
@@ -125,8 +125,8 @@ describe('lib/projects/create/v2', () => {
125
125
  },
126
126
  };
127
127
  const choices = await calculateComponentTemplateChoices(componentWithCliSelector, 'oauth', 'private', 123, projectMetadataAtMaxWorkflowAction);
128
- expect(choices).toHaveLength(3); // includes separators
129
- expect(choices[1]).toEqual({
128
+ expect(choices).toHaveLength(1);
129
+ expect(choices[0]).toEqual({
130
130
  name: expect.stringContaining('Workflow Action Tool'),
131
131
  value: componentWithCliSelector[0],
132
132
  disabled: expect.stringContaining('maximum'),
@@ -183,12 +183,18 @@ describe('lib/projects/create/v2', () => {
183
183
  supportedDistributions: ['private'],
184
184
  },
185
185
  ];
186
- const choices = await calculateComponentTemplateChoices(gatedComponent, 'oauth', 'private', 123, mockProjectMetadataForChoices);
187
- expect(choices).toHaveLength(3); // includes separators
188
- expect(choices[1]).toEqual({
186
+ const projectMetadataWithWorkflowAction = {
187
+ hsMetaFiles: [],
188
+ components: {
189
+ 'workflow-action': { count: 0, maxCount: 3, hsMetaFiles: [] },
190
+ },
191
+ };
192
+ const choices = await calculateComponentTemplateChoices(gatedComponent, 'oauth', 'private', 123, projectMetadataWithWorkflowAction);
193
+ expect(choices).toHaveLength(1);
194
+ expect(choices[0]).toEqual({
189
195
  name: expect.stringContaining('Workflow Action Tool'),
190
196
  value: gatedComponent[0],
191
- disabled: expect.stringContaining('does not have access to this feature'),
197
+ disabled: expect.stringContaining("doesn't have access to this feature"),
192
198
  });
193
199
  expect(mockHasFeature).toHaveBeenCalledWith(123, expect.any(String));
194
200
  });
@@ -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) {
@@ -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'];