@hubspot/cli 7.10.0 → 7.11.0-beta.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 (211) hide show
  1. package/bin/cli.js +5 -4
  2. package/commands/__tests__/getStarted.test.js +10 -0
  3. package/commands/__tests__/project.test.js +3 -0
  4. package/commands/account/__tests__/rename.test.js +10 -3
  5. package/commands/account/auth.js +10 -14
  6. package/commands/account/clean.js +11 -19
  7. package/commands/account/createOverride.js +15 -11
  8. package/commands/account/info.js +8 -5
  9. package/commands/account/list.js +15 -19
  10. package/commands/account/remove.js +23 -22
  11. package/commands/account/removeOverride.js +6 -6
  12. package/commands/account/rename.js +2 -2
  13. package/commands/account/use.js +19 -8
  14. package/commands/app/__tests__/migrate.test.js +8 -4
  15. package/commands/app/migrate.js +2 -2
  16. package/commands/auth.js +18 -14
  17. package/commands/config/migrate.js +5 -5
  18. package/commands/customObject/createSchema.js +2 -3
  19. package/commands/customObject/updateSchema.js +2 -3
  20. package/commands/getStarted.js +2 -3
  21. package/commands/hubdb/__tests__/list.test.js +1 -0
  22. package/commands/hubdb/list.js +2 -2
  23. package/commands/init.js +36 -32
  24. package/commands/project/__tests__/deploy.test.js +16 -11
  25. package/commands/project/__tests__/devUnifiedFlow.test.js +6 -4
  26. package/commands/project/__tests__/lint.test.js +709 -0
  27. package/commands/project/__tests__/logs.test.js +4 -0
  28. package/commands/project/__tests__/validate.test.js +2 -2
  29. package/commands/project/cloneApp.js +2 -2
  30. package/commands/project/create.js +20 -14
  31. package/commands/project/deploy.js +2 -2
  32. package/commands/project/dev/deprecatedFlow.js +4 -5
  33. package/commands/project/dev/index.js +6 -3
  34. package/commands/project/dev/unifiedFlow.js +11 -6
  35. package/commands/project/lint.d.ts +6 -0
  36. package/commands/project/lint.js +178 -0
  37. package/commands/project/logs.js +2 -3
  38. package/commands/project/migrate.js +4 -13
  39. package/commands/project/profile/add.js +6 -7
  40. package/commands/project/profile/delete.js +2 -2
  41. package/commands/project/upload.js +2 -2
  42. package/commands/project/validate.js +2 -2
  43. package/commands/project.js +2 -0
  44. package/commands/sandbox/__tests__/create.test.js +14 -5
  45. package/commands/sandbox/create.js +4 -5
  46. package/commands/sandbox/delete.js +23 -20
  47. package/commands/testAccount/create.js +2 -2
  48. package/commands/testAccount/delete.js +9 -8
  49. package/lang/en.d.ts +54 -12
  50. package/lang/en.js +64 -16
  51. package/lib/__tests__/buildAccount.test.js +22 -30
  52. package/lib/__tests__/commonOpts.test.js +9 -13
  53. package/lib/__tests__/developerTestAccounts.test.js +29 -17
  54. package/lib/__tests__/importData.test.js +20 -10
  55. package/lib/__tests__/oauth.test.js +19 -8
  56. package/lib/__tests__/sandboxSync.test.js +33 -11
  57. package/lib/__tests__/sandboxes.test.js +30 -19
  58. package/lib/__tests__/usageTracking.test.js +10 -10
  59. package/lib/__tests__/validation.test.js +32 -32
  60. package/lib/accountTypes.d.ts +9 -9
  61. package/lib/accountTypes.js +2 -4
  62. package/lib/app/__tests__/migrate.test.js +15 -0
  63. package/lib/app/__tests__/migrate_legacy.test.js +9 -0
  64. package/lib/app/migrate_legacy.d.ts +2 -2
  65. package/lib/buildAccount.d.ts +4 -4
  66. package/lib/buildAccount.js +7 -14
  67. package/lib/commonOpts.js +3 -3
  68. package/lib/configMigrate.d.ts +2 -2
  69. package/lib/configMigrate.js +42 -18
  70. package/lib/configOptions.js +3 -2
  71. package/lib/developerTestAccounts.d.ts +3 -3
  72. package/lib/developerTestAccounts.js +4 -7
  73. package/lib/doctor/DiagnosticInfoBuilder.d.ts +1 -1
  74. package/lib/doctor/DiagnosticInfoBuilder.js +9 -6
  75. package/lib/doctor/Doctor.js +4 -3
  76. package/lib/doctor/__tests__/Diagnosis.test.js +4 -3
  77. package/lib/doctor/__tests__/DiagnosticInfoBuilder.test.js +17 -9
  78. package/lib/doctor/__tests__/Doctor.test.js +14 -0
  79. package/lib/errorHandlers/index.js +10 -7
  80. package/lib/importData.js +8 -7
  81. package/lib/links.js +5 -5
  82. package/lib/middleware/{__test__ → __tests__}/commandTargetingUtils.test.js +3 -3
  83. package/lib/middleware/{__test__ → __tests__}/configMiddleware.test.js +23 -22
  84. package/lib/middleware/{__test__ → __tests__}/gitMiddleware.test.js +9 -7
  85. package/lib/middleware/autoUpdateMiddleware.js +34 -23
  86. package/lib/middleware/commandTargetingUtils.js +3 -2
  87. package/lib/middleware/configMiddleware.d.ts +6 -1
  88. package/lib/middleware/configMiddleware.js +36 -15
  89. package/lib/middleware/fireAlarmMiddleware.js +4 -15
  90. package/lib/middleware/gitMiddleware.js +8 -4
  91. package/lib/oauth.d.ts +2 -2
  92. package/lib/oauth.js +8 -10
  93. package/lib/projects/__tests__/AppDevModeInterface.test.js +17 -6
  94. package/lib/projects/__tests__/DevServerManager.test.js +1 -0
  95. package/lib/projects/__tests__/LocalDevProcess.test.js +1 -0
  96. package/lib/projects/__tests__/components.test.js +2 -22
  97. package/lib/projects/__tests__/deploy.test.js +16 -13
  98. package/lib/projects/__tests__/uieLinting.test.js +640 -0
  99. package/lib/projects/add/__tests__/legacyAddComponent.test.js +1 -1
  100. package/lib/projects/add/__tests__/v2AddComponent.test.js +30 -4
  101. package/lib/projects/add/legacyAddComponent.js +1 -1
  102. package/lib/projects/add/v2AddComponent.js +16 -5
  103. package/lib/projects/components.d.ts +8 -1
  104. package/lib/projects/components.js +91 -8
  105. package/lib/projects/create/__tests__/v2.test.js +11 -0
  106. package/lib/projects/deploy.js +21 -8
  107. package/lib/projects/localDev/AppDevModeInterface.js +2 -2
  108. package/lib/projects/localDev/DevServerManager_DEPRECATED.js +11 -3
  109. package/lib/projects/localDev/LocalDevLogger.js +4 -4
  110. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -3
  111. package/lib/projects/localDev/helpers/account.d.ts +10 -10
  112. package/lib/projects/localDev/helpers/account.js +6 -11
  113. package/lib/projects/localDev/helpers/process.js +5 -3
  114. package/lib/projects/uieLinting.d.ts +33 -0
  115. package/lib/projects/uieLinting.js +222 -0
  116. package/lib/projects/urls.js +5 -6
  117. package/lib/prompts/__tests__/downloadProjectPrompt.test.js +7 -5
  118. package/lib/prompts/accountNamePrompt.js +3 -3
  119. package/lib/prompts/accountsPrompt.d.ts +1 -1
  120. package/lib/prompts/accountsPrompt.js +6 -7
  121. package/lib/prompts/confirmImportDataPrompt.js +2 -2
  122. package/lib/prompts/downloadProjectPrompt.d.ts +1 -0
  123. package/lib/prompts/downloadProjectPrompt.js +5 -2
  124. package/lib/prompts/importDataTestAccountSelectPrompt.js +4 -5
  125. package/lib/prompts/personalAccessKeyPrompt.js +2 -2
  126. package/lib/prompts/projectDevTargetAccountPrompt.d.ts +3 -3
  127. package/lib/prompts/projectDevTargetAccountPrompt.js +5 -7
  128. package/lib/prompts/sandboxesPrompt.js +7 -8
  129. package/lib/prompts/setAsDefaultAccountPrompt.js +7 -6
  130. package/lib/sandboxSync.d.ts +2 -2
  131. package/lib/sandboxSync.js +3 -9
  132. package/lib/sandboxes.d.ts +4 -4
  133. package/lib/sandboxes.js +6 -11
  134. package/lib/serverlessLogs.js +2 -2
  135. package/lib/theme/__tests__/migrate.test.js +15 -0
  136. package/lib/ui/SpinniesManager.d.ts +5 -7
  137. package/lib/ui/SpinniesManager.js +9 -12
  138. package/lib/ui/__tests__/SpinniesManager.test.d.ts +1 -0
  139. package/lib/ui/__tests__/SpinniesManager.test.js +489 -0
  140. package/lib/ui/index.js +6 -3
  141. package/lib/usageTracking.js +15 -8
  142. package/lib/validation.js +13 -11
  143. package/mcp-server/tools/cms/HsCreateFunctionTool.js +4 -2
  144. package/mcp-server/tools/cms/HsCreateModuleTool.js +4 -2
  145. package/mcp-server/tools/cms/HsCreateTemplateTool.js +4 -2
  146. package/mcp-server/tools/cms/HsFunctionLogsTool.js +4 -2
  147. package/mcp-server/tools/cms/HsListFunctionsTool.js +3 -1
  148. package/mcp-server/tools/cms/HsListTool.js +3 -1
  149. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -0
  150. package/mcp-server/tools/index.js +4 -0
  151. package/mcp-server/tools/project/AddFeatureToProjectTool.js +4 -2
  152. package/mcp-server/tools/project/CreateProjectTool.js +4 -2
  153. package/mcp-server/tools/project/CreateTestAccountTool.js +17 -7
  154. package/mcp-server/tools/project/DeployProjectTool.js +3 -1
  155. package/mcp-server/tools/project/DocFetchTool.js +6 -4
  156. package/mcp-server/tools/project/DocsSearchTool.d.ts +1 -1
  157. package/mcp-server/tools/project/DocsSearchTool.js +10 -8
  158. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +1 -1
  159. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +9 -7
  160. package/mcp-server/tools/project/GetApplicationInfoTool.js +8 -6
  161. package/mcp-server/tools/project/GetBuildLogsTool.d.ts +26 -0
  162. package/mcp-server/tools/project/GetBuildLogsTool.js +125 -0
  163. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +26 -0
  164. package/mcp-server/tools/project/GetBuildStatusTool.js +166 -0
  165. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +1 -1
  166. package/mcp-server/tools/project/GetConfigValuesTool.js +9 -7
  167. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +1 -1
  168. package/mcp-server/tools/project/GuidedWalkthroughTool.js +5 -3
  169. package/mcp-server/tools/project/UploadProjectTools.js +3 -1
  170. package/mcp-server/tools/project/ValidateProjectTool.js +4 -2
  171. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +12 -2
  172. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +5 -1
  173. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +23 -11
  174. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +7 -5
  175. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +7 -5
  176. package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.d.ts +1 -0
  177. package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.js +305 -0
  178. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.d.ts +1 -0
  179. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.js +240 -0
  180. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +8 -6
  181. package/mcp-server/utils/__tests__/content.test.js +21 -20
  182. package/mcp-server/utils/__tests__/feedbackTracking.test.js +34 -28
  183. package/mcp-server/utils/config.d.ts +1 -0
  184. package/mcp-server/utils/config.js +10 -0
  185. package/mcp-server/utils/content.d.ts +1 -1
  186. package/mcp-server/utils/content.js +2 -2
  187. package/mcp-server/utils/feedbackTracking.d.ts +1 -1
  188. package/mcp-server/utils/feedbackTracking.js +3 -3
  189. package/mcp-server/utils/toolUsageTracking.js +4 -3
  190. package/package.json +9 -9
  191. package/ui/components/BoxWithTitle.d.ts +2 -1
  192. package/ui/components/BoxWithTitle.js +2 -2
  193. package/ui/components/StatusMessageBoxes.d.ts +5 -4
  194. package/ui/components/StatusMessageBoxes.js +8 -8
  195. package/lib/middleware/__test__/notificationsMiddleware.test.js +0 -8
  196. package/lib/middleware/notificationsMiddleware.d.ts +0 -1
  197. package/lib/middleware/notificationsMiddleware.js +0 -28
  198. package/lib/ui/boxen.d.ts +0 -5
  199. package/lib/ui/boxen.js +0 -26
  200. package/mcp-server/utils/__tests__/cliConfig.test.js +0 -110
  201. package/mcp-server/utils/cliConfig.d.ts +0 -1
  202. package/mcp-server/utils/cliConfig.js +0 -12
  203. /package/{lib/middleware/__test__/commandTargetingUtils.test.d.ts → commands/project/__tests__/lint.test.d.ts} +0 -0
  204. /package/lib/middleware/{__test__/configMiddleware.test.d.ts → __tests__/commandTargetingUtils.test.d.ts} +0 -0
  205. /package/lib/middleware/{__test__/gitMiddleware.test.d.ts → __tests__/configMiddleware.test.d.ts} +0 -0
  206. /package/lib/middleware/{__test__/notificationsMiddleware.test.d.ts → __tests__/gitMiddleware.test.d.ts} +0 -0
  207. /package/lib/middleware/{__test__ → __tests__}/requestMiddleware.test.d.ts +0 -0
  208. /package/lib/middleware/{__test__ → __tests__}/requestMiddleware.test.js +0 -0
  209. /package/lib/middleware/{__test__ → __tests__}/yargsChecksMiddleware.test.d.ts +0 -0
  210. /package/lib/middleware/{__test__ → __tests__}/yargsChecksMiddleware.test.js +0 -0
  211. /package/{mcp-server/utils/__tests__/cliConfig.test.d.ts → lib/projects/__tests__/uieLinting.test.d.ts} +0 -0
@@ -2,7 +2,7 @@ import { uiLogger } from '../../ui/logger.js';
2
2
  import * as cliConfig from '@hubspot/local-dev-lib/config';
3
3
  import * as validation from '../../validation.js';
4
4
  import { EXIT_CODES } from '../../enums/exitCodes.js';
5
- import { handleDeprecatedEnvVariables, injectAccountIdMiddleware, loadAndValidateConfigMiddleware, validateAccountOptions, } from '../configMiddleware.js';
5
+ import { handleDeprecatedEnvVariables, injectAccountIdMiddleware, validateConfigMiddleware, validateAccountOptions, } from '../configMiddleware.js';
6
6
  vi.mock('../../ui/logger.js', () => ({
7
7
  uiLogger: {
8
8
  error: vi.fn(),
@@ -12,10 +12,10 @@ vi.mock('../../ui/logger.js', () => ({
12
12
  vi.mock('@hubspot/local-dev-lib/config');
13
13
  vi.mock('../../validation');
14
14
  const validateAccountSpy = vi.spyOn(validation, 'validateAccount');
15
- const loadConfigSpy = vi.spyOn(cliConfig, 'loadConfig');
16
- const getAccountIdSpy = vi.spyOn(cliConfig, 'getAccountId');
15
+ const getConfigAccountIfExistsSpy = vi.spyOn(cliConfig, 'getConfigAccountIfExists');
16
+ const globalConfigFileExistsSpy = vi.spyOn(cliConfig, 'globalConfigFileExists');
17
17
  const configFileExistsSpy = vi.spyOn(cliConfig, 'configFileExists');
18
- const getConfigPathSpy = vi.spyOn(cliConfig, 'getConfigPath');
18
+ const getConfigFilePathSpy = vi.spyOn(cliConfig, 'getConfigFilePath');
19
19
  const validateConfigSpy = vi.spyOn(cliConfig, 'validateConfig');
20
20
  const processExitSpy = vi.spyOn(process, 'exit');
21
21
  describe('lib/middleware/configMiddleware', () => {
@@ -23,7 +23,7 @@ describe('lib/middleware/configMiddleware', () => {
23
23
  processExitSpy.mockImplementation(code => {
24
24
  throw new Error(`Process.exit called with code ${code}`);
25
25
  });
26
- getConfigPathSpy.mockReturnValue('/path/to/config');
26
+ getConfigFilePathSpy.mockReturnValue('/path/to/config');
27
27
  });
28
28
  describe('handleDeprecatedEnvVariables()', () => {
29
29
  it('should handle deprecated HUBSPOT_PORTAL_ID environment variable', () => {
@@ -76,11 +76,13 @@ describe('lib/middleware/configMiddleware', () => {
76
76
  await injectAccountIdMiddleware(argv);
77
77
  expect(argv.userProvidedAccount).toBeUndefined();
78
78
  expect(argv.derivedAccountId).toBe(123);
79
- expect(getAccountIdSpy).not.toHaveBeenCalled();
79
+ expect(getConfigAccountIfExistsSpy).not.toHaveBeenCalled();
80
80
  process.env = originalEnv;
81
81
  });
82
82
  it('should use getAccountId when useEnv is false', async () => {
83
- getAccountIdSpy.mockReturnValue(456);
83
+ getConfigAccountIfExistsSpy.mockReturnValue({
84
+ accountId: 456,
85
+ });
84
86
  const argv = {
85
87
  _: ['some-command'],
86
88
  account: 'test-account',
@@ -90,41 +92,40 @@ describe('lib/middleware/configMiddleware', () => {
90
92
  await injectAccountIdMiddleware(argv);
91
93
  expect(argv.userProvidedAccount).toBe('test-account');
92
94
  expect(argv.derivedAccountId).toBe(456);
93
- expect(getAccountIdSpy).toHaveBeenCalledWith('test-account');
95
+ expect(getConfigAccountIfExistsSpy).toHaveBeenCalledWith('test-account');
94
96
  });
95
97
  });
96
- describe('loadAndValidateConfigMiddleware()', () => {
97
- it('should exit with error when config file exists and --config flag is used', async () => {
98
+ describe('validateConfigMiddleware()', () => {
99
+ it('should allow using --config flag', async () => {
98
100
  configFileExistsSpy.mockReturnValue(true);
101
+ validateConfigSpy.mockReturnValue({ isValid: true, errors: [] });
99
102
  const argv = {
100
103
  _: ['some-command'],
101
104
  config: 'custom-config.json',
102
105
  $0: 'hs',
103
106
  };
104
- await expect(loadAndValidateConfigMiddleware(argv)).rejects.toThrow();
105
- expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
106
- expect(uiLogger.error).toHaveBeenCalledWith('A configuration file already exists at /path/to/config. To specify a new configuration file, delete the existing one and try again.');
107
+ await validateConfigMiddleware(argv);
108
+ // Should not throw or exit - this is now allowed
109
+ expect(processExitSpy).not.toHaveBeenCalled();
110
+ expect(uiLogger.error).not.toHaveBeenCalled();
107
111
  });
108
- it('should load config and validate for non-init commands', async () => {
109
- configFileExistsSpy.mockReturnValue(false);
110
- loadConfigSpy.mockReturnValue({});
111
- validateConfigSpy.mockReturnValue(true);
112
+ it('should validate config for non-init commands', async () => {
113
+ globalConfigFileExistsSpy.mockReturnValue(true);
114
+ validateConfigSpy.mockReturnValue({ isValid: true, errors: [] });
112
115
  const argv = {
113
116
  _: ['some-command'],
114
117
  $0: 'hs',
115
118
  };
116
- await loadAndValidateConfigMiddleware(argv);
117
- expect(loadConfigSpy).toHaveBeenCalled();
119
+ await validateConfigMiddleware(argv);
118
120
  expect(validateConfigSpy).toHaveBeenCalled();
119
121
  });
120
122
  it('should skip validation for init command', async () => {
121
- configFileExistsSpy.mockReturnValue(false);
123
+ globalConfigFileExistsSpy.mockReturnValue(false);
122
124
  const argv = {
123
125
  _: ['init'],
124
126
  $0: 'hs',
125
127
  };
126
- await loadAndValidateConfigMiddleware(argv);
127
- expect(loadConfigSpy).not.toHaveBeenCalled();
128
+ await validateConfigMiddleware(argv);
128
129
  expect(validateConfigSpy).not.toHaveBeenCalled();
129
130
  });
130
131
  });
@@ -3,19 +3,19 @@ import * as gitUI from '../../ui/git.js';
3
3
  import { checkAndWarnGitInclusionMiddleware } from '../gitMiddleware.js';
4
4
  vi.mock('@hubspot/local-dev-lib/config');
5
5
  vi.mock('../../ui/git');
6
- const getConfigPathSpy = vi.spyOn(config, 'getConfigPath');
6
+ const getConfigFilePathSpy = vi.spyOn(config, 'getConfigFilePath');
7
7
  const checkAndWarnGitInclusionSpy = vi.spyOn(gitUI, 'checkAndWarnGitInclusion');
8
8
  describe('lib/middleware/gitMiddleware', () => {
9
9
  describe('checkAndWarnGitInclusionMiddleware()', () => {
10
10
  it('should call checkAndWarnGitInclusion when command is provided and config path exists', () => {
11
11
  const mockConfigPath = '/path/to/config.js';
12
- getConfigPathSpy.mockReturnValue(mockConfigPath);
12
+ getConfigFilePathSpy.mockReturnValue(mockConfigPath);
13
13
  const argv = {
14
14
  _: ['some-command'],
15
15
  $0: 'hs',
16
16
  };
17
17
  checkAndWarnGitInclusionMiddleware(argv);
18
- expect(getConfigPathSpy).toHaveBeenCalledTimes(1);
18
+ expect(getConfigFilePathSpy).toHaveBeenCalledTimes(1);
19
19
  expect(checkAndWarnGitInclusionSpy).toHaveBeenCalledWith(mockConfigPath);
20
20
  });
21
21
  it('should not call checkAndWarnGitInclusion when no command is provided', () => {
@@ -24,17 +24,19 @@ describe('lib/middleware/gitMiddleware', () => {
24
24
  $0: 'hs',
25
25
  };
26
26
  checkAndWarnGitInclusionMiddleware(argv);
27
- expect(getConfigPathSpy).not.toHaveBeenCalled();
27
+ expect(getConfigFilePathSpy).not.toHaveBeenCalled();
28
28
  expect(checkAndWarnGitInclusionSpy).not.toHaveBeenCalled();
29
29
  });
30
- it('should not call checkAndWarnGitInclusion when config path is null', () => {
31
- getConfigPathSpy.mockReturnValue(null);
30
+ it('should not call checkAndWarnGitInclusion when config path does not exist', () => {
31
+ getConfigFilePathSpy.mockImplementation(() => {
32
+ throw new Error('Config path does not exist');
33
+ });
32
34
  const argv = {
33
35
  _: ['some-command'],
34
36
  $0: 'hs',
35
37
  };
36
38
  checkAndWarnGitInclusionMiddleware(argv);
37
- expect(getConfigPathSpy).toHaveBeenCalledTimes(1);
39
+ expect(getConfigFilePathSpy).toHaveBeenCalledTimes(1);
38
40
  expect(checkAndWarnGitInclusionSpy).not.toHaveBeenCalled();
39
41
  });
40
42
  });
@@ -1,13 +1,14 @@
1
1
  import updateNotifier from 'update-notifier';
2
- import { isConfigFlagEnabled } from '@hubspot/local-dev-lib/config';
2
+ import { getConfig } from '@hubspot/local-dev-lib/config';
3
3
  import { pkg } from '../jsonLoader.js';
4
- import { UI_COLORS } from '../ui/index.js';
5
4
  import SpinniesManager from '../ui/SpinniesManager.js';
6
5
  import { lib } from '../../lang/en.js';
7
6
  import { DEFAULT_PACKAGE_MANAGER, isGloballyInstalled, executeInstall, } from '../npm.js';
8
7
  import { debugError } from '../errorHandlers/index.js';
9
8
  import { uiLogger } from '../ui/logger.js';
10
9
  import { isTargetedCommand } from './commandTargetingUtils.js';
10
+ import { renderInline } from '../../ui/index.js';
11
+ import { getWarningBox } from '../../ui/components/StatusMessageBoxes.js';
11
12
  // Default behavior is to check for notifications at most once per day
12
13
  // update-notifier stores the last checked date in the user's home directory
13
14
  const notifier = updateNotifier({
@@ -16,23 +17,16 @@ const notifier = updateNotifier({
16
17
  shouldNotifyInNpmScript: true,
17
18
  });
18
19
  const CMS_CLI_PACKAGE_NAME = '@hubspot/cms-cli';
19
- function updateNotification() {
20
- notifier.notify({
20
+ async function updateNotification(currentVersion, latestVersion, updateCommand) {
21
+ await renderInline(getWarningBox({
22
+ title: pkg.name === CMS_CLI_PACKAGE_NAME
23
+ ? ''
24
+ : lib.middleware.updateNotification.notifyTitle,
21
25
  message: pkg.name === CMS_CLI_PACKAGE_NAME
22
26
  ? lib.middleware.updateNotification.cmsUpdateNotification(CMS_CLI_PACKAGE_NAME)
23
- : lib.middleware.updateNotification.cliUpdateNotification,
24
- defer: false,
25
- boxenOptions: {
26
- borderColor: UI_COLORS.MARIGOLD_DARK,
27
- margin: 1,
28
- padding: 1,
29
- textAlignment: 'center',
30
- borderStyle: 'round',
31
- title: pkg.name === CMS_CLI_PACKAGE_NAME
32
- ? undefined
33
- : lib.middleware.updateNotification.notifyTitle,
34
- },
35
- });
27
+ : lib.middleware.updateNotification.cliUpdateNotification(currentVersion, updateCommand, latestVersion),
28
+ textCentered: true,
29
+ }));
36
30
  }
37
31
  const SKIP_AUTO_UPDATE_COMMANDS = {
38
32
  config: {
@@ -43,13 +37,28 @@ const preventAutoUpdateForCommand = (commandParts) => {
43
37
  return isTargetedCommand(commandParts, SKIP_AUTO_UPDATE_COMMANDS);
44
38
  };
45
39
  export async function autoUpdateCLI(argv) {
46
- // This lets us back to default update-notifier behavior
47
40
  let showManualInstallHelp = true;
41
+ let isGlobalInstall = null;
42
+ const checkGlobalInstall = async () => {
43
+ if (isGlobalInstall === null) {
44
+ isGlobalInstall =
45
+ (await isGloballyInstalled(DEFAULT_PACKAGE_MANAGER)) &&
46
+ (await isGloballyInstalled('hs'));
47
+ }
48
+ return isGlobalInstall;
49
+ };
50
+ let config;
51
+ try {
52
+ config = getConfig();
53
+ }
54
+ catch (e) {
55
+ debugError(e);
56
+ }
48
57
  if (notifier &&
49
58
  notifier.update &&
50
59
  !argv.useEnv &&
51
60
  !process.env.SKIP_HUBSPOT_CLI_AUTO_UPDATES &&
52
- isConfigFlagEnabled('allowAutoUpdates') &&
61
+ config?.allowAutoUpdates !== false &&
53
62
  !preventAutoUpdateForCommand(argv._)) {
54
63
  // Ignore all update notifications if the current version is a pre-release
55
64
  if (!notifier.update.current.includes('-')) {
@@ -63,8 +72,7 @@ export async function autoUpdateCLI(argv) {
63
72
  text: lib.middleware.autoUpdateCLI.updateAvailable(notifier.update.latest),
64
73
  });
65
74
  try {
66
- if ((await isGloballyInstalled(DEFAULT_PACKAGE_MANAGER)) &&
67
- (await isGloballyInstalled('hs'))) {
75
+ if (await checkGlobalInstall()) {
68
76
  await executeInstall(['@hubspot/cli@latest'], '-g');
69
77
  showManualInstallHelp = false;
70
78
  SpinniesManager.succeed('cliAutoUpdate', {
@@ -87,7 +95,10 @@ export async function autoUpdateCLI(argv) {
87
95
  }
88
96
  }
89
97
  }
90
- if (showManualInstallHelp) {
91
- updateNotification();
98
+ if (showManualInstallHelp &&
99
+ notifier.update &&
100
+ process.stdout.isTTY &&
101
+ !notifier.update.current.includes('-')) {
102
+ await updateNotification(notifier.update.current, notifier.update.latest, `npm i ${(await checkGlobalInstall()) ? '-g' : ''} @hubspot/cli`);
92
103
  }
93
104
  }
@@ -1,4 +1,4 @@
1
- import { configFileExists } from '@hubspot/local-dev-lib/config';
1
+ import { globalConfigFileExists } from '@hubspot/local-dev-lib/config';
2
2
  export function isTargetedCommand(commandParts, targetCommandMap) {
3
3
  const currentCommandPart = commandParts[0];
4
4
  if (!targetCommandMap[currentCommandPart]) {
@@ -20,10 +20,11 @@ export function isTargetedCommand(commandParts, targetCommandMap) {
20
20
  const SKIP_CONFIG_LOADING_COMMANDS = {
21
21
  init: true,
22
22
  feedback: true,
23
+ mcp: { start: true },
23
24
  };
24
25
  // Returns true if the command requires a config file to be present
25
26
  export function shouldLoadConfigForCommand(commandParts) {
26
- const globalConfigExists = configFileExists(true);
27
+ const globalConfigExists = globalConfigFileExists();
27
28
  // the user is trying to migrate the global config
28
29
  const isGlobalConfigMigration = !globalConfigExists &&
29
30
  isTargetedCommand(commandParts, {
@@ -3,11 +3,16 @@ import { CLIOptions } from '@hubspot/local-dev-lib/types/CLIOptions';
3
3
  export declare function handleDeprecatedEnvVariables(argv: Arguments<{
4
4
  useEnv?: boolean;
5
5
  }>): void;
6
+ export declare function handleCustomConfigLocationMiddleware(argv: Arguments<{
7
+ useEnv?: boolean;
8
+ config?: string;
9
+ }>): void;
6
10
  /**
7
11
  * Auto-injects the derivedAccountId flag into all commands
8
12
  */
9
13
  export declare function injectAccountIdMiddleware(argv: Arguments<{
10
14
  account?: string;
15
+ config?: string;
11
16
  }>): Promise<void>;
12
- export declare function loadAndValidateConfigMiddleware(argv: Arguments<CLIOptions>): Promise<void>;
17
+ export declare function validateConfigMiddleware(argv: Arguments<CLIOptions>): Promise<void>;
13
18
  export declare function validateAccountOptions(argv: Arguments): Promise<void>;
@@ -1,9 +1,11 @@
1
- import { loadConfig, getAccountId, configFileExists, getConfigPath, validateConfig, } from '@hubspot/local-dev-lib/config';
1
+ import path from 'path';
2
+ import { getConfigAccountIfExists, validateConfig, getConfigDefaultAccountIfExists, configFileExists, } from '@hubspot/local-dev-lib/config';
3
+ import { getCwd } from '@hubspot/local-dev-lib/path';
2
4
  import { validateAccount } from '../validation.js';
3
5
  import { EXIT_CODES } from '../enums/exitCodes.js';
4
6
  import { commands } from '../../lang/en.js';
5
7
  import { uiDeprecatedTag } from '../ui/index.js';
6
- import { isTargetedCommand, shouldLoadConfigForCommand, shouldRunAccountValidationForCommand, shouldRunConfigValidationForCommand, } from './commandTargetingUtils.js';
8
+ import { shouldLoadConfigForCommand, shouldRunAccountValidationForCommand, shouldRunConfigValidationForCommand, } from './commandTargetingUtils.js';
7
9
  import { parseStringToNumber } from '../parsing.js';
8
10
  import { uiLogger } from '../ui/logger.js';
9
11
  import { lib } from '../../lang/en.js';
@@ -17,6 +19,18 @@ export function handleDeprecatedEnvVariables(argv) {
17
19
  process.env.HUBSPOT_ACCOUNT_ID = process.env.HUBSPOT_PORTAL_ID;
18
20
  }
19
21
  }
22
+ export function handleCustomConfigLocationMiddleware(argv) {
23
+ const { useEnv, config } = argv;
24
+ if (useEnv) {
25
+ process.env.USE_ENVIRONMENT_HUBSPOT_CONFIG = 'true';
26
+ }
27
+ else if (config && typeof config === 'string') {
28
+ const absoluteConfigPath = path.isAbsolute(config)
29
+ ? config
30
+ : path.join(getCwd(), config);
31
+ process.env.HUBSPOT_CONFIG_PATH = absoluteConfigPath;
32
+ }
33
+ }
20
34
  /**
21
35
  * Auto-injects the derivedAccountId flag into all commands
22
36
  */
@@ -33,10 +47,23 @@ export async function injectAccountIdMiddleware(argv) {
33
47
  }
34
48
  }
35
49
  else {
36
- argv.derivedAccountId = getAccountId(account);
50
+ // Wrap in try-catch to handle cases where config file doesn't exist yet (e.g., during hs init)
51
+ try {
52
+ let accountInConfig = account
53
+ ? getConfigAccountIfExists(account)
54
+ : undefined;
55
+ if (!accountInConfig) {
56
+ accountInConfig = getConfigDefaultAccountIfExists();
57
+ }
58
+ argv.derivedAccountId = accountInConfig?.accountId;
59
+ }
60
+ catch (err) {
61
+ // Config file doesn't exist yet, which is fine for commands like hs init
62
+ argv.derivedAccountId = undefined;
63
+ }
37
64
  }
38
65
  }
39
- export async function loadAndValidateConfigMiddleware(argv) {
66
+ export async function validateConfigMiddleware(argv) {
40
67
  // Skip this when no command is provided
41
68
  if (!argv._.length || argv.help) {
42
69
  return;
@@ -45,23 +72,17 @@ export async function loadAndValidateConfigMiddleware(argv) {
45
72
  if (!shouldLoadConfigForCommand(argv._)) {
46
73
  return;
47
74
  }
48
- // If the config file exists and the --config flag is used, exit with an error
49
- if (configFileExists(true) &&
50
- argv.config &&
51
- !isTargetedCommand(argv._, { config: { migrate: true } })) {
52
- uiLogger.error(commands.generalErrors.loadConfigMiddleware.configFileExists(getConfigPath()));
53
- process.exit(EXIT_CODES.ERROR);
54
- }
55
- const config = loadConfig(argv.config, argv);
56
75
  // We don't run validation for auth because users should be able to run it when
57
76
  // no accounts are configured, but we still want to exit if the config file is not found
58
- if (isTargetedCommand(argv._, { auth: true }) && !config) {
77
+ if (!process.env.USE_ENVIRONMENT_HUBSPOT_CONFIG && !configFileExists()) {
78
+ console.error('Config file not found, run hs account auth to configure your account');
59
79
  process.exit(EXIT_CODES.ERROR);
60
80
  }
61
81
  // Only validate the config if the command requires it
62
82
  if (shouldRunConfigValidationForCommand(argv._)) {
63
- const configIsValid = validateConfig();
64
- if (!configIsValid) {
83
+ const { isValid, errors } = validateConfig();
84
+ if (!isValid) {
85
+ uiLogger.error(commands.generalErrors.validateConfigMiddleware.configValidationFailed(errors));
65
86
  process.exit(EXIT_CODES.ERROR);
66
87
  }
67
88
  }
@@ -2,7 +2,6 @@ import chalk from 'chalk';
2
2
  import { fetchFireAlarms } from '@hubspot/local-dev-lib/api/fireAlarm';
3
3
  import { debugError } from '../errorHandlers/index.js';
4
4
  import { pkg } from '../jsonLoader.js';
5
- import { logInBox } from '../ui/boxen.js';
6
5
  import { renderInline } from '../../ui/index.js';
7
6
  import { getWarningBox } from '../../ui/components/StatusMessageBoxes.js';
8
7
  /*
@@ -100,20 +99,10 @@ async function logFireAlarms(accountId, command, version) {
100
99
  }
101
100
  return acc;
102
101
  }, '');
103
- if (!process.env.HUBSPOT_ENABLE_INK) {
104
- await logInBox({
105
- contents: notifications,
106
- options: {
107
- title: 'Notifications',
108
- },
109
- });
110
- }
111
- else {
112
- await renderInline(getWarningBox({
113
- title: 'Notifications',
114
- message: notifications,
115
- }));
116
- }
102
+ await renderInline(getWarningBox({
103
+ title: 'Notifications',
104
+ message: notifications,
105
+ }));
117
106
  }
118
107
  }
119
108
  export async function checkFireAlarms(argv) {
@@ -1,15 +1,19 @@
1
- import { getConfigPath, configFileExists } from '@hubspot/local-dev-lib/config';
1
+ import { getConfigFilePath, globalConfigFileExists, } from '@hubspot/local-dev-lib/config';
2
2
  import { checkAndWarnGitInclusion } from '../ui/git.js';
3
+ import { debugError } from '../errorHandlers/index.js';
3
4
  export function checkAndWarnGitInclusionMiddleware(argv) {
4
5
  // Skip this when no command is provided
5
6
  if (argv._.length) {
6
7
  // Skip if using global config
7
- if (configFileExists(true)) {
8
+ if (globalConfigFileExists()) {
8
9
  return;
9
10
  }
10
- const configPath = getConfigPath();
11
- if (configPath) {
11
+ try {
12
+ const configPath = getConfigFilePath();
12
13
  checkAndWarnGitInclusion(configPath);
13
14
  }
15
+ catch (error) {
16
+ debugError(error);
17
+ }
14
18
  }
15
19
  }
package/lib/oauth.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { OAuth2ManagerAccountConfig } from '@hubspot/local-dev-lib/types/Accounts';
2
- export declare function authenticateWithOauth(accountConfig: OAuth2ManagerAccountConfig): Promise<void>;
1
+ import { OAuthConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
2
+ export declare function authenticateWithOauth(accountConfig: OAuthConfigAccount): Promise<void>;
package/lib/oauth.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import express from 'express';
2
2
  import open from 'open';
3
3
  import { OAuth2Manager } from '@hubspot/local-dev-lib/models/OAuth2Manager';
4
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
5
- import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier';
4
+ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
6
5
  import { addOauthToAccountConfig } from '@hubspot/local-dev-lib/oauth';
7
6
  import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
8
7
  import { uiLogger } from './ui/logger.js';
@@ -14,15 +13,15 @@ import { EXIT_CODES } from './enums/exitCodes.js';
14
13
  const PORT = 3000;
15
14
  const redirectUri = `http://localhost:${PORT}/oauth-callback`;
16
15
  function buildAuthUrl(oauthManager) {
17
- const { env: accountEnv, clientId, scopes: accountScopes, } = oauthManager.account;
16
+ const { env: accountEnv, auth } = oauthManager.account;
18
17
  const env = accountEnv || ENVIRONMENTS.PROD;
19
- const scopes = accountScopes || DEFAULT_OAUTH_SCOPES;
20
- if (!clientId) {
18
+ const scopes = auth.scopes.length > 0 ? auth.scopes : DEFAULT_OAUTH_SCOPES;
19
+ if (!auth.clientId) {
21
20
  uiLogger.error(lib.oauth.missingClientId);
22
21
  process.exit(EXIT_CODES.ERROR);
23
22
  }
24
23
  return (`${getHubSpotWebsiteOrigin(env)}/oauth/${oauthManager.account.accountId}/authorize` +
25
- `?client_id=${encodeURIComponent(clientId)}` + // app's client ID
24
+ `?client_id=${encodeURIComponent(auth.clientId)}` + // app's client ID
26
25
  `&scope=${encodeURIComponent(scopes.join(' '))}` + // scopes being requested by the app
27
26
  `&redirect_uri=${encodeURIComponent(redirectUri)}` // where to send the user after the consent page
28
27
  );
@@ -45,8 +44,8 @@ async function authorize(oauthManager) {
45
44
  if (req.query.code) {
46
45
  const authCodeProof = {
47
46
  grant_type: 'authorization_code',
48
- client_id: oauthManager.account.clientId,
49
- client_secret: oauthManager.account.clientSecret,
47
+ client_id: oauthManager.account.auth.clientId,
48
+ client_secret: oauthManager.account.auth.clientSecret,
50
49
  redirect_uri: redirectUri,
51
50
  code: req.query.code,
52
51
  };
@@ -82,8 +81,7 @@ async function authorize(oauthManager) {
82
81
  });
83
82
  }
84
83
  function setupOauth(accountConfig) {
85
- const accountId = getAccountIdentifier(accountConfig);
86
- const config = getAccountConfig(accountId);
84
+ const config = getConfigAccountById(accountConfig.accountId);
87
85
  return new OAuth2Manager({
88
86
  ...accountConfig,
89
87
  env: accountConfig.env || config?.env || ENVIRONMENTS.PROD,
@@ -12,7 +12,7 @@ vi.mock('@hubspot/ui-extensions-dev-server', () => {
12
12
  });
13
13
  import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
14
14
  import { fetchAppMetadataByUid, fetchPublicAppProductionInstallCounts, installStaticAuthAppOnTestAccount, } from '@hubspot/local-dev-lib/api/appsDev';
15
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
15
+ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
16
16
  import AppDevModeInterface from '../localDev/AppDevModeInterface.js';
17
17
  import LocalDevState from '../localDev/LocalDevState.js';
18
18
  import LocalDevLogger from '../localDev/LocalDevLogger.js';
@@ -121,7 +121,7 @@ describe('AppDevModeInterface', () => {
121
121
  previouslyAuthorizedScopeGroups: [],
122
122
  },
123
123
  });
124
- getAccountConfig.mockReturnValue({
124
+ getConfigAccountById.mockReturnValue({
125
125
  parentAccountId: 12345,
126
126
  });
127
127
  isDeveloperTestAccount.mockReturnValue(true);
@@ -290,13 +290,14 @@ describe('AppDevModeInterface', () => {
290
290
  });
291
291
  it('should handle app reinstallation', async () => {
292
292
  // Set up conditions for non-automatic installation
293
- getAccountConfig.mockReturnValue(null);
294
293
  fetchAppInstallationData.mockResolvedValue({
295
294
  data: {
296
295
  isInstalledWithScopeGroups: false,
297
296
  previouslyAuthorizedScopeGroups: ['old-scope'],
298
297
  },
299
298
  });
299
+ // Make it not automatically installable by making it not a test account
300
+ isDeveloperTestAccount.mockReturnValue(false);
300
301
  await appDevModeInterface.setup();
301
302
  expect(installAppBrowserPrompt).toHaveBeenCalledWith('http://static-install-url', true);
302
303
  });
@@ -308,7 +309,7 @@ describe('AppDevModeInterface', () => {
308
309
  });
309
310
  it('should exit if user declines auto-install', async () => {
310
311
  // Set up conditions for automatic installation
311
- getAccountConfig.mockReturnValue({
312
+ getConfigAccountById.mockReturnValue({
312
313
  parentAccountId: 12345, // matches targetProjectAccountId
313
314
  });
314
315
  isDeveloperTestAccount.mockReturnValue(true);
@@ -375,6 +376,15 @@ describe('AppDevModeInterface', () => {
375
376
  data: { uniquePortalInstallCount: 5 },
376
377
  });
377
378
  getStaticAuthAppInstallUrl.mockReturnValue('http://static-install-url');
379
+ getConfigAccountById.mockReturnValue({
380
+ parentAccountId: 12345,
381
+ });
382
+ isDeveloperTestAccount.mockReturnValue(true);
383
+ isSandbox.mockReturnValue(false);
384
+ installAppAutoPrompt.mockResolvedValue(true);
385
+ confirmPrompt.mockResolvedValue(true);
386
+ installStaticAuthAppOnTestAccount.mockResolvedValue(undefined);
387
+ isServerRunningAtUrl.mockResolvedValue(true);
378
388
  installAppBrowserPrompt.mockImplementation(async () => {
379
389
  const addListenerCall = mockLocalDevState.addListener.mock.calls.find(call => call[0] === 'devServerMessage');
380
390
  if (addListenerCall) {
@@ -387,8 +397,8 @@ describe('AppDevModeInterface', () => {
387
397
  mockLocalDevState.getAppDataByUid = vi.fn().mockReturnValue(mockAppData);
388
398
  mockLocalDevState.setAppDataForUid = vi.fn();
389
399
  mockLocalDevState.addListener = vi.fn();
390
- // Target account config is missing
391
- getAccountConfig.mockReturnValue(null);
400
+ // Target account config is missing - make it not a test account so isAutomaticallyInstallable returns false
401
+ isDeveloperTestAccount.mockReturnValue(false);
392
402
  // App is not installed
393
403
  fetchAppInstallationData.mockResolvedValue({
394
404
  data: {
@@ -401,6 +411,7 @@ describe('AppDevModeInterface', () => {
401
411
  localDevState: mockLocalDevState,
402
412
  localDevLogger: mockLocalDevLogger,
403
413
  });
414
+ // Remove the spy to see if that's causing the timeout
404
415
  await newAppDevModeInterface.setup();
405
416
  expect(installAppBrowserPrompt).toHaveBeenCalled();
406
417
  });
@@ -28,6 +28,7 @@ vi.mock('@hubspot/local-dev-lib/config', () => ({
28
28
  getAccountId: vi.fn().mockReturnValue(123),
29
29
  hasLocalStateFlag: vi.fn().mockReturnValue(false),
30
30
  getConfigDefaultAccount: vi.fn().mockReturnValue({ accountId: 123 }),
31
+ globalConfigFileExists: vi.fn().mockReturnValue(true),
31
32
  }));
32
33
  vi.mock('@hubspot/local-dev-lib/urls', () => ({
33
34
  getHubSpotApiOrigin: vi.fn().mockReturnValue('https://api.hubspot.com'),
@@ -26,6 +26,7 @@ vi.mock('../deploy');
26
26
  vi.mock('../config');
27
27
  vi.mock('@hubspot/local-dev-lib/api/projects');
28
28
  vi.mock('@hubspot/local-dev-lib/errors/index');
29
+ vi.mock('@hubspot/local-dev-lib/config');
29
30
  vi.mock('../localDev/LocalDevLogger');
30
31
  vi.mock('../localDev/DevServerManager');
31
32
  // Tests for LocalDevProcess and LocalDevState