@hubspot/cli 7.10.0 → 7.10.1-experimental.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) 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 +10 -5
  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 +286 -28
  29. package/commands/project/cloneApp.js +2 -2
  30. package/commands/project/deploy.js +16 -8
  31. package/commands/project/dev/deprecatedFlow.js +4 -5
  32. package/commands/project/dev/index.js +19 -7
  33. package/commands/project/dev/unifiedFlow.js +4 -5
  34. package/commands/project/lint.d.ts +6 -0
  35. package/commands/project/lint.js +178 -0
  36. package/commands/project/logs.js +2 -3
  37. package/commands/project/migrate.js +4 -13
  38. package/commands/project/profile/add.js +6 -7
  39. package/commands/project/profile/delete.js +2 -2
  40. package/commands/project/upload.js +10 -4
  41. package/commands/project/validate.js +73 -13
  42. package/commands/project.js +2 -0
  43. package/commands/sandbox/__tests__/create.test.js +14 -5
  44. package/commands/sandbox/create.js +4 -5
  45. package/commands/sandbox/delete.js +23 -20
  46. package/commands/testAccount/create.js +2 -2
  47. package/commands/testAccount/delete.js +9 -8
  48. package/lang/en.d.ts +48 -11
  49. package/lang/en.js +58 -15
  50. package/lib/__tests__/buildAccount.test.js +22 -30
  51. package/lib/__tests__/commonOpts.test.js +9 -13
  52. package/lib/__tests__/developerTestAccounts.test.js +29 -17
  53. package/lib/__tests__/importData.test.js +20 -10
  54. package/lib/__tests__/oauth.test.js +19 -8
  55. package/lib/__tests__/projectProfiles.test.js +273 -32
  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/importData.js +8 -7
  80. package/lib/links.js +5 -5
  81. package/lib/middleware/{__test__ → __tests__}/commandTargetingUtils.test.js +3 -3
  82. package/lib/middleware/{__test__ → __tests__}/configMiddleware.test.js +23 -22
  83. package/lib/middleware/{__test__ → __tests__}/gitMiddleware.test.js +9 -7
  84. package/lib/middleware/autoUpdateMiddleware.js +34 -23
  85. package/lib/middleware/commandTargetingUtils.js +3 -2
  86. package/lib/middleware/configMiddleware.d.ts +6 -1
  87. package/lib/middleware/configMiddleware.js +36 -15
  88. package/lib/middleware/fireAlarmMiddleware.js +4 -15
  89. package/lib/middleware/gitMiddleware.js +8 -4
  90. package/lib/oauth.d.ts +2 -2
  91. package/lib/oauth.js +8 -10
  92. package/lib/projectProfiles.d.ts +4 -3
  93. package/lib/projectProfiles.js +78 -32
  94. package/lib/projects/__tests__/AppDevModeInterface.test.js +17 -6
  95. package/lib/projects/__tests__/DevServerManager.test.js +1 -0
  96. package/lib/projects/__tests__/LocalDevProcess.test.js +1 -0
  97. package/lib/projects/__tests__/deploy.test.js +1 -0
  98. package/lib/projects/__tests__/uieLinting.test.js +640 -0
  99. package/lib/projects/create/__tests__/v2.test.js +11 -0
  100. package/lib/projects/localDev/AppDevModeInterface.js +2 -2
  101. package/lib/projects/localDev/DevServerManager_DEPRECATED.js +2 -2
  102. package/lib/projects/localDev/LocalDevLogger.js +4 -4
  103. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -3
  104. package/lib/projects/localDev/helpers/account.d.ts +10 -10
  105. package/lib/projects/localDev/helpers/account.js +6 -11
  106. package/lib/projects/uieLinting.d.ts +33 -0
  107. package/lib/projects/uieLinting.js +222 -0
  108. package/lib/projects/urls.js +5 -6
  109. package/lib/prompts/__tests__/downloadProjectPrompt.test.js +7 -5
  110. package/lib/prompts/accountNamePrompt.js +3 -3
  111. package/lib/prompts/accountsPrompt.d.ts +1 -1
  112. package/lib/prompts/accountsPrompt.js +6 -7
  113. package/lib/prompts/confirmImportDataPrompt.js +2 -2
  114. package/lib/prompts/downloadProjectPrompt.d.ts +1 -0
  115. package/lib/prompts/downloadProjectPrompt.js +5 -2
  116. package/lib/prompts/importDataTestAccountSelectPrompt.js +4 -5
  117. package/lib/prompts/personalAccessKeyPrompt.js +2 -2
  118. package/lib/prompts/projectDevTargetAccountPrompt.d.ts +3 -3
  119. package/lib/prompts/projectDevTargetAccountPrompt.js +5 -7
  120. package/lib/prompts/sandboxesPrompt.js +7 -8
  121. package/lib/prompts/setAsDefaultAccountPrompt.js +7 -6
  122. package/lib/sandboxSync.d.ts +2 -2
  123. package/lib/sandboxSync.js +3 -9
  124. package/lib/sandboxes.d.ts +4 -4
  125. package/lib/sandboxes.js +6 -11
  126. package/lib/serverlessLogs.js +2 -2
  127. package/lib/theme/__tests__/migrate.test.js +15 -0
  128. package/lib/ui/index.js +6 -3
  129. package/lib/usageTracking.js +15 -8
  130. package/lib/validation.js +13 -11
  131. package/mcp-server/tools/cms/HsCreateFunctionTool.js +4 -2
  132. package/mcp-server/tools/cms/HsCreateModuleTool.js +4 -2
  133. package/mcp-server/tools/cms/HsCreateTemplateTool.js +4 -2
  134. package/mcp-server/tools/cms/HsFunctionLogsTool.js +4 -2
  135. package/mcp-server/tools/cms/HsListFunctionsTool.js +3 -1
  136. package/mcp-server/tools/cms/HsListTool.js +3 -1
  137. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -0
  138. package/mcp-server/tools/index.js +4 -0
  139. package/mcp-server/tools/project/AddFeatureToProjectTool.js +4 -2
  140. package/mcp-server/tools/project/CreateProjectTool.js +4 -2
  141. package/mcp-server/tools/project/CreateTestAccountTool.js +17 -7
  142. package/mcp-server/tools/project/DeployProjectTool.js +3 -1
  143. package/mcp-server/tools/project/DocFetchTool.js +6 -4
  144. package/mcp-server/tools/project/DocsSearchTool.d.ts +1 -1
  145. package/mcp-server/tools/project/DocsSearchTool.js +10 -8
  146. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +1 -1
  147. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +9 -7
  148. package/mcp-server/tools/project/GetApplicationInfoTool.js +8 -6
  149. package/mcp-server/tools/project/GetBuildLogsTool.d.ts +26 -0
  150. package/mcp-server/tools/project/GetBuildLogsTool.js +125 -0
  151. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +26 -0
  152. package/mcp-server/tools/project/GetBuildStatusTool.js +166 -0
  153. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +1 -1
  154. package/mcp-server/tools/project/GetConfigValuesTool.js +9 -7
  155. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +1 -1
  156. package/mcp-server/tools/project/GuidedWalkthroughTool.js +5 -3
  157. package/mcp-server/tools/project/UploadProjectTools.js +3 -1
  158. package/mcp-server/tools/project/ValidateProjectTool.js +4 -2
  159. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +12 -2
  160. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +5 -1
  161. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +23 -11
  162. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +7 -5
  163. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +7 -5
  164. package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.d.ts +1 -0
  165. package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.js +305 -0
  166. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.d.ts +1 -0
  167. package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.js +240 -0
  168. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +8 -6
  169. package/mcp-server/utils/__tests__/content.test.js +21 -20
  170. package/mcp-server/utils/__tests__/feedbackTracking.test.js +34 -28
  171. package/mcp-server/utils/config.d.ts +1 -0
  172. package/mcp-server/utils/config.js +10 -0
  173. package/mcp-server/utils/content.d.ts +1 -1
  174. package/mcp-server/utils/content.js +2 -2
  175. package/mcp-server/utils/feedbackTracking.d.ts +1 -1
  176. package/mcp-server/utils/feedbackTracking.js +3 -3
  177. package/mcp-server/utils/toolUsageTracking.js +4 -3
  178. package/package.json +9 -9
  179. package/ui/components/BoxWithTitle.d.ts +2 -1
  180. package/ui/components/BoxWithTitle.js +2 -2
  181. package/ui/components/StatusMessageBoxes.d.ts +5 -4
  182. package/ui/components/StatusMessageBoxes.js +8 -8
  183. package/lib/middleware/__test__/notificationsMiddleware.test.js +0 -8
  184. package/lib/middleware/notificationsMiddleware.d.ts +0 -1
  185. package/lib/middleware/notificationsMiddleware.js +0 -28
  186. package/lib/ui/boxen.d.ts +0 -5
  187. package/lib/ui/boxen.js +0 -26
  188. package/mcp-server/utils/__tests__/cliConfig.test.js +0 -110
  189. package/mcp-server/utils/cliConfig.d.ts +0 -1
  190. package/mcp-server/utils/cliConfig.js +0 -12
  191. /package/{lib/middleware/__test__/commandTargetingUtils.test.d.ts → commands/project/__tests__/lint.test.d.ts} +0 -0
  192. /package/lib/middleware/{__test__/configMiddleware.test.d.ts → __tests__/commandTargetingUtils.test.d.ts} +0 -0
  193. /package/lib/middleware/{__test__/gitMiddleware.test.d.ts → __tests__/configMiddleware.test.d.ts} +0 -0
  194. /package/lib/middleware/{__test__/notificationsMiddleware.test.d.ts → __tests__/gitMiddleware.test.d.ts} +0 -0
  195. /package/lib/middleware/{__test__ → __tests__}/requestMiddleware.test.d.ts +0 -0
  196. /package/lib/middleware/{__test__ → __tests__}/requestMiddleware.test.js +0 -0
  197. /package/lib/middleware/{__test__ → __tests__}/yargsChecksMiddleware.test.d.ts +0 -0
  198. /package/lib/middleware/{__test__ → __tests__}/yargsChecksMiddleware.test.js +0 -0
  199. /package/{mcp-server/utils/__tests__/cliConfig.test.d.ts → lib/projects/__tests__/uieLinting.test.d.ts} +0 -0
@@ -8,6 +8,8 @@ import * as ui from '../../../lib/ui/index.js';
8
8
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
9
9
  import { logError } from '../../../lib/errorHandlers/index.js';
10
10
  import projectLogsCommand from '../logs.js';
11
+ import * as config from '@hubspot/local-dev-lib/config';
12
+ import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
11
13
  vi.mock('../../ui/logger.js');
12
14
  vi.mock('../../../lib/commonOpts');
13
15
  vi.mock('../../../lib/usageTracking');
@@ -25,6 +27,7 @@ const projectLogsManagerGetFunctionNamesSpy = vi.spyOn(ProjectLogsManager, 'getF
25
27
  const projectLogsManagerInitSpy = vi.spyOn(ProjectLogsManager, 'init');
26
28
  const getTableHeaderSpy = vi.spyOn(table, 'getTableHeader');
27
29
  const getTableContentsSpy = vi.spyOn(table, 'getTableContents');
30
+ const getConfigAccountEnvironmentSpy = vi.spyOn(config, 'getConfigAccountEnvironment');
28
31
  const optionsSpy = vi
29
32
  .spyOn(yargs, 'options')
30
33
  .mockReturnValue(yargs);
@@ -34,6 +37,7 @@ const conflictsSpy = vi
34
37
  const exampleSpy = vi
35
38
  .spyOn(yargs, 'example')
36
39
  .mockReturnValue(yargs);
40
+ getConfigAccountEnvironmentSpy.mockReturnValue(ENVIRONMENTS.PROD);
37
41
  describe('commands/project/logs', () => {
38
42
  beforeEach(() => {
39
43
  // @ts-expect-error Doesn't match the actual signature because then the linter complains about unused variables
@@ -5,11 +5,14 @@ import { getProjectConfig, validateProjectConfig, } from '../../../lib/projects/
5
5
  import { uiLogger } from '../../../lib/ui/logger.js';
6
6
  import { commands } from '../../../lang/en.js';
7
7
  import { isV2Project } from '../../../lib/projects/platformVersion.js';
8
- import { loadAndValidateProfile } from '../../../lib/projectProfiles.js';
8
+ import { validateProjectForProfile } from '../../../lib/projectProfiles.js';
9
9
  import { trackCommandUsage } from '../../../lib/usageTracking.js';
10
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
10
+ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
11
11
  import { handleTranslate } from '../../../lib/projects/upload.js';
12
12
  import projectValidateCommand from '../validate.js';
13
+ import { getAllHsProfiles } from '@hubspot/project-parsing-lib';
14
+ import SpinniesManager from '../../../lib/ui/SpinniesManager.js';
15
+ import { logError } from '../../../lib/errorHandlers/index.js';
13
16
  // Mock dependencies
14
17
  vi.mock('../../../lib/projects/upload.js');
15
18
  vi.mock('../../../lib/projects/config.js');
@@ -19,15 +22,35 @@ vi.mock('../../../lib/projectProfiles.js');
19
22
  vi.mock('../../../lib/errorHandlers/index.js');
20
23
  vi.mock('@hubspot/local-dev-lib/config');
21
24
  vi.mock('../../../lib/projects/platformVersion.js');
25
+ vi.mock('@hubspot/project-parsing-lib');
26
+ vi.mock('../../../lib/ui/SpinniesManager.js');
22
27
  describe('commands/project/validate', () => {
23
28
  const projectDir = '/test/project';
24
29
  let exitSpy;
30
+ const mockProjectConfig = {
31
+ name: 'test-project',
32
+ srcDir: 'src',
33
+ platformVersion: '2025.2',
34
+ };
35
+ const mockAccountConfig = {
36
+ accountType: 'STANDARD',
37
+ accountId: 123,
38
+ env: 'prod',
39
+ };
25
40
  beforeEach(() => {
26
41
  // Mock process.exit to throw to stop execution
27
42
  exitSpy = vi.spyOn(process, 'exit').mockImplementation(code => {
28
43
  throw new Error(`Process exited with code ${code}`);
29
44
  });
30
45
  vi.clearAllMocks();
46
+ // Set up default mocks
47
+ vi.mocked(getConfigAccountById).mockReturnValue(mockAccountConfig);
48
+ vi.mocked(trackCommandUsage);
49
+ vi.mocked(SpinniesManager.init);
50
+ vi.mocked(SpinniesManager.add);
51
+ vi.mocked(SpinniesManager.succeed);
52
+ vi.mocked(SpinniesManager.fail);
53
+ vi.mocked(validateProjectForProfile).mockResolvedValue([]);
31
54
  });
32
55
  afterEach(() => {
33
56
  exitSpy.mockRestore();
@@ -65,34 +88,269 @@ describe('commands/project/validate', () => {
65
88
  })).rejects.toThrow('Process exited with code 1');
66
89
  expect(uiLogger.error).toHaveBeenCalledWith(commands.project.validate.mustBeRanWithinAProject);
67
90
  });
91
+ it('should exit with error for non-V2 projects', async () => {
92
+ vi.mocked(getProjectConfig).mockResolvedValue({
93
+ projectConfig: {
94
+ name: 'test',
95
+ srcDir: 'src',
96
+ platformVersion: '2024.1',
97
+ },
98
+ projectDir,
99
+ });
100
+ vi.mocked(isV2Project).mockReturnValue(false);
101
+ await expect(
102
+ // @ts-expect-error partial mock
103
+ projectValidateCommand.handler({
104
+ derivedAccountId: 123,
105
+ d: false,
106
+ debug: false,
107
+ })).rejects.toThrow('Process exited with code 1');
108
+ expect(uiLogger.error).toHaveBeenCalledWith(commands.project.validate.badVersion);
109
+ });
110
+ it('should exit with error when validateProjectConfig throws', async () => {
111
+ vi.mocked(getProjectConfig).mockResolvedValue({
112
+ projectConfig: mockProjectConfig,
113
+ projectDir,
114
+ });
115
+ vi.mocked(isV2Project).mockReturnValue(true);
116
+ const error = new Error('Invalid project config');
117
+ vi.mocked(validateProjectConfig).mockImplementation(() => {
118
+ throw error;
119
+ });
120
+ await expect(
121
+ // @ts-expect-error partial mock
122
+ projectValidateCommand.handler({
123
+ derivedAccountId: 123,
124
+ d: false,
125
+ debug: false,
126
+ })).rejects.toThrow('Process exited with code 1');
127
+ expect(logError).toHaveBeenCalledWith(error);
128
+ });
68
129
  });
69
- it('should call validateSourceDirectory with correct parameters', async () => {
70
- const mockProjectConfig = {
71
- name: 'test-project',
72
- srcDir: 'src',
73
- platformVersion: '2025.2',
74
- };
75
- vi.mocked(getProjectConfig).mockResolvedValue({
76
- projectConfig: mockProjectConfig,
77
- projectDir,
130
+ describe('profile validation', () => {
131
+ describe('when a specific profile is provided', () => {
132
+ it('should validate only the specified profile', async () => {
133
+ vi.mocked(getProjectConfig).mockResolvedValue({
134
+ projectConfig: mockProjectConfig,
135
+ projectDir,
136
+ });
137
+ vi.mocked(isV2Project).mockReturnValue(true);
138
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
139
+ vi.mocked(getAllHsProfiles).mockResolvedValue(['dev', 'prod', 'qa']);
140
+ vi.mocked(validateProjectForProfile).mockResolvedValue([]);
141
+ vi.mocked(validateSourceDirectory).mockResolvedValue(undefined);
142
+ await expect(projectValidateCommand.handler({
143
+ derivedAccountId: 123,
144
+ profile: 'dev',
145
+ d: false,
146
+ debug: false,
147
+ })).rejects.toThrow('Process exited with code 0');
148
+ // Should call validateProjectForProfile for the specified profile
149
+ expect(validateProjectForProfile).toHaveBeenCalledWith(mockProjectConfig, projectDir, 'dev', 123);
150
+ expect(uiLogger.success).toHaveBeenCalledWith(commands.project.validate.success(mockProjectConfig.name));
151
+ });
152
+ it('should handle profile validation failure', async () => {
153
+ vi.mocked(getProjectConfig).mockResolvedValue({
154
+ projectConfig: mockProjectConfig,
155
+ projectDir,
156
+ });
157
+ vi.mocked(isV2Project).mockReturnValue(true);
158
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
159
+ vi.mocked(getAllHsProfiles).mockResolvedValue(['dev', 'prod']);
160
+ const error = new Error('Profile not found');
161
+ vi.mocked(validateProjectForProfile).mockResolvedValue([error.message]);
162
+ await expect(projectValidateCommand.handler({
163
+ derivedAccountId: 123,
164
+ profile: 'dev',
165
+ d: false,
166
+ debug: false,
167
+ })).rejects.toThrow('Process exited with code 1');
168
+ // The error message is logged as a string, not the Error object
169
+ expect(uiLogger.error).toHaveBeenCalledWith(error.message);
170
+ });
171
+ it('should handle translate failure for a profile', async () => {
172
+ vi.mocked(getProjectConfig).mockResolvedValue({
173
+ projectConfig: mockProjectConfig,
174
+ projectDir,
175
+ });
176
+ vi.mocked(isV2Project).mockReturnValue(true);
177
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
178
+ vi.mocked(getAllHsProfiles).mockResolvedValue(['dev', 'prod']);
179
+ const error = new Error('Translation failed');
180
+ vi.mocked(validateProjectForProfile).mockResolvedValue([
181
+ commands.project.validate.failure(mockProjectConfig.name),
182
+ error,
183
+ ]);
184
+ await expect(projectValidateCommand.handler({
185
+ derivedAccountId: 123,
186
+ profile: 'dev',
187
+ d: false,
188
+ debug: false,
189
+ })).rejects.toThrow('Process exited with code 1');
190
+ // The error object is logged via logError
191
+ expect(logError).toHaveBeenCalledWith(error);
192
+ });
78
193
  });
79
- vi.mocked(isV2Project).mockReturnValue(true);
80
- vi.mocked(validateProjectConfig).mockReturnValue(undefined);
81
- vi.mocked(loadAndValidateProfile).mockResolvedValue(123);
82
- vi.mocked(getAccountConfig).mockReturnValue({
83
- accountType: 'STANDARD',
84
- accountId: 123,
85
- env: 'prod',
194
+ describe('when no profile is provided and project has profiles', () => {
195
+ it('should validate all profiles', async () => {
196
+ vi.mocked(getProjectConfig).mockResolvedValue({
197
+ projectConfig: mockProjectConfig,
198
+ projectDir,
199
+ });
200
+ vi.mocked(isV2Project).mockReturnValue(true);
201
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
202
+ vi.mocked(getAllHsProfiles).mockResolvedValue(['dev', 'prod', 'qa']);
203
+ vi.mocked(validateProjectForProfile).mockResolvedValue([]);
204
+ vi.mocked(validateSourceDirectory).mockResolvedValue(undefined);
205
+ await expect(projectValidateCommand.handler({
206
+ derivedAccountId: 123,
207
+ d: false,
208
+ debug: false,
209
+ })).rejects.toThrow('Process exited with code 0');
210
+ // Should validate all three profiles
211
+ expect(validateProjectForProfile).toHaveBeenCalledTimes(3);
212
+ expect(validateProjectForProfile).toHaveBeenCalledWith(mockProjectConfig, projectDir, 'dev', 123, true);
213
+ expect(validateProjectForProfile).toHaveBeenCalledWith(mockProjectConfig, projectDir, 'prod', 123, true);
214
+ expect(validateProjectForProfile).toHaveBeenCalledWith(mockProjectConfig, projectDir, 'qa', 123, true);
215
+ // Should show success for all profiles
216
+ expect(SpinniesManager.succeed).toHaveBeenCalledWith('validatingAllProfiles', expect.any(Object));
217
+ expect(uiLogger.success).toHaveBeenCalledWith(commands.project.validate.success(mockProjectConfig.name));
218
+ });
219
+ it('should handle failure when validating multiple profiles', async () => {
220
+ vi.mocked(getProjectConfig).mockResolvedValue({
221
+ projectConfig: mockProjectConfig,
222
+ projectDir,
223
+ });
224
+ vi.mocked(isV2Project).mockReturnValue(true);
225
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
226
+ vi.mocked(getAllHsProfiles).mockResolvedValue(['dev', 'prod']);
227
+ vi.mocked(validateProjectForProfile)
228
+ .mockResolvedValueOnce([]) // dev succeeds
229
+ .mockResolvedValueOnce(['Profile not found']); // prod fails
230
+ await expect(projectValidateCommand.handler({
231
+ derivedAccountId: 123,
232
+ d: false,
233
+ debug: false,
234
+ })).rejects.toThrow('Process exited with code 1');
235
+ expect(SpinniesManager.fail).toHaveBeenCalledWith('validatingAllProfiles', expect.any(Object));
236
+ });
237
+ it('should continue validating remaining profiles after one fails', async () => {
238
+ vi.mocked(getProjectConfig).mockResolvedValue({
239
+ projectConfig: mockProjectConfig,
240
+ projectDir,
241
+ });
242
+ vi.mocked(isV2Project).mockReturnValue(true);
243
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
244
+ vi.mocked(getAllHsProfiles).mockResolvedValue(['dev', 'prod', 'qa']);
245
+ vi.mocked(validateProjectForProfile)
246
+ .mockResolvedValueOnce([]) // dev succeeds
247
+ .mockResolvedValueOnce(['Profile not found']) // prod fails
248
+ .mockResolvedValueOnce([]); // qa succeeds
249
+ vi.mocked(validateSourceDirectory).mockResolvedValue(undefined);
250
+ await expect(projectValidateCommand.handler({
251
+ derivedAccountId: 123,
252
+ d: false,
253
+ debug: false,
254
+ })).rejects.toThrow('Process exited with code 1');
255
+ // All three profiles should be attempted
256
+ expect(validateProjectForProfile).toHaveBeenCalledTimes(3);
257
+ });
258
+ });
259
+ describe('when no profile is provided and project has no profiles', () => {
260
+ it('should validate without a profile', async () => {
261
+ vi.mocked(getProjectConfig).mockResolvedValue({
262
+ projectConfig: mockProjectConfig,
263
+ projectDir,
264
+ });
265
+ vi.mocked(isV2Project).mockReturnValue(true);
266
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
267
+ vi.mocked(getAllHsProfiles).mockResolvedValue([]);
268
+ vi.mocked(handleTranslate).mockResolvedValue(undefined);
269
+ vi.mocked(validateSourceDirectory).mockResolvedValue(undefined);
270
+ await expect(projectValidateCommand.handler({
271
+ derivedAccountId: 123,
272
+ d: false,
273
+ debug: false,
274
+ })).rejects.toThrow('Process exited with code 0');
275
+ // Should call handleTranslate without a profile
276
+ expect(handleTranslate).toHaveBeenCalledWith(projectDir, mockProjectConfig, 123, false, undefined);
277
+ expect(uiLogger.success).toHaveBeenCalledWith(commands.project.validate.success(mockProjectConfig.name));
278
+ });
279
+ it('should handle validation failure when no profiles exist', async () => {
280
+ vi.mocked(getProjectConfig).mockResolvedValue({
281
+ projectConfig: mockProjectConfig,
282
+ projectDir,
283
+ });
284
+ vi.mocked(isV2Project).mockReturnValue(true);
285
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
286
+ vi.mocked(getAllHsProfiles).mockResolvedValue([]);
287
+ const error = new Error('Translation failed');
288
+ vi.mocked(handleTranslate).mockRejectedValue(error);
289
+ await expect(projectValidateCommand.handler({
290
+ derivedAccountId: 123,
291
+ d: false,
292
+ debug: false,
293
+ })).rejects.toThrow('Process exited with code 1');
294
+ expect(uiLogger.error).toHaveBeenCalledWith(commands.project.validate.failure(mockProjectConfig.name));
295
+ expect(logError).toHaveBeenCalledWith(error);
296
+ });
297
+ });
298
+ });
299
+ describe('source directory validation', () => {
300
+ it('should call validateSourceDirectory with correct parameters', async () => {
301
+ vi.mocked(getProjectConfig).mockResolvedValue({
302
+ projectConfig: mockProjectConfig,
303
+ projectDir,
304
+ });
305
+ vi.mocked(isV2Project).mockReturnValue(true);
306
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
307
+ vi.mocked(getAllHsProfiles).mockResolvedValue([]);
308
+ vi.mocked(handleTranslate).mockResolvedValue(undefined);
309
+ vi.mocked(validateSourceDirectory).mockResolvedValue(undefined);
310
+ await expect(projectValidateCommand.handler({
311
+ derivedAccountId: 123,
312
+ d: false,
313
+ debug: false,
314
+ })).rejects.toThrow('Process exited with code 0');
315
+ const expectedSrcDir = path.resolve(projectDir, mockProjectConfig.srcDir);
316
+ expect(validateSourceDirectory).toHaveBeenCalledWith(expectedSrcDir, mockProjectConfig, projectDir);
317
+ });
318
+ it('should exit with error when validateSourceDirectory throws', async () => {
319
+ vi.mocked(getProjectConfig).mockResolvedValue({
320
+ projectConfig: mockProjectConfig,
321
+ projectDir,
322
+ });
323
+ vi.mocked(isV2Project).mockReturnValue(true);
324
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
325
+ vi.mocked(getAllHsProfiles).mockResolvedValue([]);
326
+ vi.mocked(handleTranslate).mockResolvedValue(undefined);
327
+ const error = new Error('Invalid source directory');
328
+ vi.mocked(validateSourceDirectory).mockRejectedValue(error);
329
+ await expect(projectValidateCommand.handler({
330
+ derivedAccountId: 123,
331
+ d: false,
332
+ debug: false,
333
+ })).rejects.toThrow('Process exited with code 1');
334
+ expect(logError).toHaveBeenCalledWith(error);
335
+ });
336
+ });
337
+ describe('command usage tracking', () => {
338
+ it('should track command usage with account type', async () => {
339
+ vi.mocked(getProjectConfig).mockResolvedValue({
340
+ projectConfig: mockProjectConfig,
341
+ projectDir,
342
+ });
343
+ vi.mocked(isV2Project).mockReturnValue(true);
344
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
345
+ vi.mocked(getAllHsProfiles).mockResolvedValue([]);
346
+ vi.mocked(handleTranslate).mockResolvedValue(undefined);
347
+ vi.mocked(validateSourceDirectory).mockResolvedValue(undefined);
348
+ await expect(projectValidateCommand.handler({
349
+ derivedAccountId: 123,
350
+ d: false,
351
+ debug: false,
352
+ })).rejects.toThrow('Process exited with code 0');
353
+ expect(trackCommandUsage).toHaveBeenCalledWith('project-validate', { type: 'STANDARD' }, 123);
86
354
  });
87
- vi.mocked(trackCommandUsage);
88
- vi.mocked(validateSourceDirectory).mockResolvedValue(undefined);
89
- vi.mocked(handleTranslate).mockResolvedValue(undefined);
90
- await expect(projectValidateCommand.handler({
91
- derivedAccountId: 123,
92
- d: false,
93
- debug: false,
94
- })).rejects.toThrow('Process exited with code 0');
95
- const expectedSrcDir = path.resolve(projectDir, mockProjectConfig.srcDir);
96
- expect(validateSourceDirectory).toHaveBeenCalledWith(expectedSrcDir, mockProjectConfig, projectDir);
97
355
  });
98
356
  });
@@ -14,7 +14,7 @@ import { cloneApp, checkCloneStatus, downloadClonedProject, } from '@hubspot/loc
14
14
  import { getCwd, sanitizeFileName } from '@hubspot/local-dev-lib/path';
15
15
  import { uiLogger } from '../../lib/ui/logger.js';
16
16
  import { extractZipArchive } from '@hubspot/local-dev-lib/archive';
17
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
17
+ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
18
18
  import SpinniesManager from '../../lib/ui/SpinniesManager.js';
19
19
  import { logInvalidAccountError } from '../../lib/app/migrate.js';
20
20
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
@@ -25,7 +25,7 @@ const deprecated = true;
25
25
  async function handler(args) {
26
26
  const { derivedAccountId } = args;
27
27
  await trackCommandUsage('clone-app', {}, derivedAccountId);
28
- const accountConfig = getAccountConfig(derivedAccountId);
28
+ const accountConfig = getConfigAccountById(derivedAccountId);
29
29
  const accountName = uiAccountDescription(derivedAccountId);
30
30
  if (!accountConfig) {
31
31
  throw new Error(commands.project.cloneApp.errors.noAccountConfig(derivedAccountId));
@@ -1,5 +1,5 @@
1
1
  import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
2
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
2
+ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
3
3
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
4
4
  import { isV2Project } from '../../lib/projects/platformVersion.js';
5
5
  import { trackCommandUsage } from '../../lib/usageTracking.js';
@@ -7,11 +7,10 @@ import { logError, ApiErrorContext } from '../../lib/errorHandlers/index.js';
7
7
  import { getProjectConfig } from '../../lib/projects/config.js';
8
8
  import { projectNamePrompt } from '../../lib/prompts/projectNamePrompt.js';
9
9
  import { promptUser } from '../../lib/prompts/promptUtils.js';
10
- import { uiLine } from '../../lib/ui/index.js';
11
10
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
12
11
  import { uiLogger } from '../../lib/ui/logger.js';
13
12
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
14
- import { loadProfile, logProfileFooter, logProfileHeader, exitIfUsingProfiles, } from '../../lib/projectProfiles.js';
13
+ import { loadProfile, logProfileFooter, logProfileHeader, enforceProfileUsage, } from '../../lib/projectProfiles.js';
15
14
  import { PROJECT_DEPLOY_TEXT } from '../../lib/constants.js';
16
15
  import { commands } from '../../lang/en.js';
17
16
  import { handleProjectDeploy, validateBuildIdForDeploy, logDeployErrors, } from '../../lib/projects/deploy.js';
@@ -19,7 +18,7 @@ const command = 'deploy';
19
18
  const describe = commands.project.deploy.describe;
20
19
  async function handler(args) {
21
20
  const { derivedAccountId, project: projectOption, buildId: buildIdOption, force: forceOption, deployLatestBuild: deployLatestBuildOption, json: formatOutputAsJson, } = args;
22
- const accountConfig = getAccountConfig(derivedAccountId);
21
+ const accountConfig = getConfigAccountById(derivedAccountId);
23
22
  const accountType = accountConfig && accountConfig.accountType;
24
23
  let targetAccountId;
25
24
  const jsonOutput = {};
@@ -27,9 +26,12 @@ async function handler(args) {
27
26
  if (isV2Project(projectConfig?.platformVersion)) {
28
27
  if (args.profile) {
29
28
  logProfileHeader(args.profile);
30
- const profile = loadProfile(projectConfig, projectDir, args.profile);
31
- if (!profile) {
32
- uiLine();
29
+ let profile;
30
+ try {
31
+ profile = loadProfile(projectConfig, projectDir, args.profile);
32
+ }
33
+ catch (error) {
34
+ logError(error);
33
35
  process.exit(EXIT_CODES.ERROR);
34
36
  }
35
37
  targetAccountId = profile.accountId;
@@ -37,7 +39,13 @@ async function handler(args) {
37
39
  }
38
40
  else {
39
41
  // A profile must be specified if this project has profiles configured
40
- await exitIfUsingProfiles(projectConfig, projectDir);
42
+ try {
43
+ await enforceProfileUsage(projectConfig, projectDir);
44
+ }
45
+ catch (error) {
46
+ logError(error);
47
+ process.exit(EXIT_CODES.ERROR);
48
+ }
41
49
  }
42
50
  }
43
51
  if (!targetAccountId) {
@@ -1,5 +1,4 @@
1
- import { getAccountConfig, getConfigAccounts, getEnv, } from '@hubspot/local-dev-lib/config';
2
- import { getValidEnv } from '@hubspot/local-dev-lib/environment';
1
+ import { getConfigAccountById, getAllConfigAccounts, getConfigAccountEnvironment, } from '@hubspot/local-dev-lib/config';
3
2
  import { findProjectComponents, getProjectComponentTypes, } from '../../../lib/projects/structure.js';
4
3
  import { ComponentTypes } from '../../../types/Projects.js';
5
4
  import { commands } from '../../../lang/en.js';
@@ -14,13 +13,13 @@ import { isSandbox, isDeveloperTestAccount, } from '../../../lib/accountTypes.js
14
13
  import { ensureProjectExists } from '../../../lib/projects/ensureProjectExists.js';
15
14
  export async function deprecatedProjectDevFlow({ args, accountId, projectConfig, projectDir, }) {
16
15
  const { userProvidedAccount, derivedAccountId } = args;
17
- const env = getValidEnv(getEnv(derivedAccountId));
16
+ const env = getConfigAccountEnvironment(derivedAccountId);
18
17
  const components = await findProjectComponents(projectDir);
19
18
  const runnableComponents = components.filter(component => component.runnable);
20
19
  const componentTypes = getProjectComponentTypes(runnableComponents);
21
20
  const hasPrivateApps = !!componentTypes[ComponentTypes.PrivateApp];
22
21
  const hasPublicApps = !!componentTypes[ComponentTypes.PublicApp];
23
- const accountConfig = getAccountConfig(accountId);
22
+ const accountConfig = getConfigAccountById(accountId);
24
23
  if (!accountConfig) {
25
24
  uiLogger.error(commands.project.dev.errors.noAccount(accountId));
26
25
  process.exit(EXIT_CODES.ERROR);
@@ -33,7 +32,7 @@ export async function deprecatedProjectDevFlow({ args, accountId, projectConfig,
33
32
  uiLogger.error(commands.project.dev.errors.invalidProjectComponents);
34
33
  process.exit(EXIT_CODES.SUCCESS);
35
34
  }
36
- const accounts = getConfigAccounts();
35
+ const accounts = getAllConfigAccounts();
37
36
  if (!accounts) {
38
37
  uiLogger.error(commands.project.dev.errors.noAccountsInConfig);
39
38
  process.exit(EXIT_CODES.ERROR);
@@ -1,5 +1,5 @@
1
1
  import { trackCommandUsage } from '../../../lib/usageTracking.js';
2
- import { getAccountId } from '@hubspot/local-dev-lib/config';
2
+ import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
3
3
  import { getProjectConfig, validateProjectConfig, } from '../../../lib/projects/config.js';
4
4
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
5
5
  import { uiLine } from '../../../lib/ui/index.js';
@@ -7,7 +7,7 @@ import { deprecatedProjectDevFlow } from './deprecatedFlow.js';
7
7
  import { unifiedProjectDevFlow } from './unifiedFlow.js';
8
8
  import { isV2Project } from '../../../lib/projects/platformVersion.js';
9
9
  import { makeYargsBuilder } from '../../../lib/yargsUtils.js';
10
- import { loadProfile, exitIfUsingProfiles, } from '../../../lib/projectProfiles.js';
10
+ import { loadProfile, enforceProfileUsage, } from '../../../lib/projectProfiles.js';
11
11
  import { commands } from '../../../lang/en.js';
12
12
  import { uiLogger } from '../../../lib/ui/logger.js';
13
13
  import { logError } from '../../../lib/errorHandlers/index.js';
@@ -51,7 +51,8 @@ async function handler(args) {
51
51
  let profile;
52
52
  // Using the new --projectAccount flag
53
53
  if (projectAccount) {
54
- targetProjectAccountId = getAccountId(projectAccount);
54
+ targetProjectAccountId =
55
+ getConfigAccountIfExists(projectAccount)?.accountId;
55
56
  if (targetProjectAccountId) {
56
57
  uiLogger.log('');
57
58
  uiLogger.log(commands.project.dev.logs.projectAccountFlagExplanation(targetProjectAccountId));
@@ -63,8 +64,11 @@ async function handler(args) {
63
64
  }
64
65
  if (!targetProjectAccountId && isV2Project(projectConfig.platformVersion)) {
65
66
  if (args.profile) {
66
- profile = loadProfile(projectConfig, projectDir, args.profile);
67
- if (!profile) {
67
+ try {
68
+ profile = loadProfile(projectConfig, projectDir, args.profile);
69
+ }
70
+ catch (error) {
71
+ logError(error);
68
72
  uiLine();
69
73
  process.exit(EXIT_CODES.ERROR);
70
74
  }
@@ -74,7 +78,13 @@ async function handler(args) {
74
78
  }
75
79
  else {
76
80
  // A profile must be specified if this project has profiles configured
77
- await exitIfUsingProfiles(projectConfig, projectDir);
81
+ try {
82
+ await enforceProfileUsage(projectConfig, projectDir);
83
+ }
84
+ catch (error) {
85
+ logError(error);
86
+ process.exit(EXIT_CODES.ERROR);
87
+ }
78
88
  }
79
89
  }
80
90
  if (!targetProjectAccountId) {
@@ -87,7 +97,9 @@ async function handler(args) {
87
97
  }
88
98
  trackCommandUsage('project-dev', {}, targetProjectAccountId);
89
99
  if (isV2Project(projectConfig.platformVersion)) {
90
- const targetTestingAccountId = (testingAccount && getAccountId(testingAccount)) || undefined;
100
+ const targetTestingAccountId = testingAccount
101
+ ? getConfigAccountIfExists(testingAccount)?.accountId
102
+ : undefined;
91
103
  await unifiedProjectDevFlow({
92
104
  args,
93
105
  targetProjectAccountId,
@@ -4,8 +4,7 @@ import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
4
4
  import { startPortManagerServer, stopPortManagerServer, } from '@hubspot/local-dev-lib/portManager';
5
5
  import { isTranslationError } from '@hubspot/project-parsing-lib/src/lib/errors.js';
6
6
  import { translateForLocalDev } from '@hubspot/project-parsing-lib';
7
- import { getEnv, getConfigAccounts, getAccountConfig, } from '@hubspot/local-dev-lib/config';
8
- import { getValidEnv } from '@hubspot/local-dev-lib/environment';
7
+ import { getConfigAccountEnvironment, getAllConfigAccounts, getConfigAccountById, } from '@hubspot/local-dev-lib/config';
9
8
  import { logError } from '../../../lib/errorHandlers/index.js';
10
9
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
11
10
  import { ensureProjectExists } from '../../../lib/projects/ensureProjectExists.js';
@@ -23,7 +22,7 @@ import LocalDevWebsocketServer from '../../../lib/projects/localDev/LocalDevWebs
23
22
  import { confirmLocalDevIsNotRunning } from '../../../lib/projects/localDev/helpers/process.js';
24
23
  export async function unifiedProjectDevFlow({ args, targetProjectAccountId, providedTargetTestingAccountId, projectConfig, projectDir, }) {
25
24
  await confirmLocalDevIsNotRunning();
26
- const env = getValidEnv(getEnv(targetProjectAccountId));
25
+ const env = getConfigAccountEnvironment(targetProjectAccountId);
27
26
  let projectNodes;
28
27
  let projectProfileData;
29
28
  // Get IR
@@ -50,12 +49,12 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
50
49
  uiLogger.error(commands.project.dev.errors.noRunnableComponents);
51
50
  process.exit(EXIT_CODES.SUCCESS);
52
51
  }
53
- const targetProjectAccountConfig = getAccountConfig(targetProjectAccountId);
52
+ const targetProjectAccountConfig = getConfigAccountById(targetProjectAccountId);
54
53
  if (!targetProjectAccountConfig) {
55
54
  uiLogger.error(commands.project.dev.errors.noAccount(targetProjectAccountId));
56
55
  process.exit(EXIT_CODES.ERROR);
57
56
  }
58
- const accounts = getConfigAccounts();
57
+ const accounts = getAllConfigAccounts();
59
58
  const accountIsCombined = await isUnifiedAccount(targetProjectAccountConfig);
60
59
  const targetProjectAccountIsTestAccountOrSandbox = isTestAccountOrSandbox(targetProjectAccountConfig);
61
60
  if (!accountIsCombined) {
@@ -0,0 +1,6 @@
1
+ import { CommonArgs, YargsCommandModule } from '../../types/Yargs.js';
2
+ export type ProjectLintArgs = CommonArgs & {
3
+ installMissingDeps?: boolean;
4
+ };
5
+ declare const projectLintCommand: YargsCommandModule<unknown, ProjectLintArgs>;
6
+ export default projectLintCommand;