@hubspot/cli 7.10.1-experimental.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.
- package/commands/project/__tests__/deploy.test.js +6 -6
- package/commands/project/__tests__/validate.test.js +27 -285
- package/commands/project/create.js +20 -14
- package/commands/project/deploy.js +6 -14
- package/commands/project/dev/index.js +4 -13
- package/commands/project/dev/unifiedFlow.js +7 -1
- package/commands/project/upload.js +2 -8
- package/commands/project/validate.js +12 -72
- package/lang/en.d.ts +19 -14
- package/lang/en.js +21 -16
- package/lib/__tests__/projectProfiles.test.js +32 -273
- package/lib/errorHandlers/index.js +10 -7
- package/lib/projectProfiles.d.ts +3 -4
- package/lib/projectProfiles.js +32 -78
- package/lib/projects/__tests__/components.test.js +2 -22
- package/lib/projects/__tests__/deploy.test.js +15 -13
- package/lib/projects/add/__tests__/legacyAddComponent.test.js +1 -1
- package/lib/projects/add/__tests__/v2AddComponent.test.js +30 -4
- package/lib/projects/add/legacyAddComponent.js +1 -1
- package/lib/projects/add/v2AddComponent.js +16 -5
- package/lib/projects/components.d.ts +8 -1
- package/lib/projects/components.js +91 -8
- package/lib/projects/deploy.js +21 -8
- package/lib/projects/localDev/DevServerManager_DEPRECATED.js +9 -1
- package/lib/projects/localDev/helpers/process.js +5 -3
- package/lib/ui/SpinniesManager.d.ts +5 -7
- package/lib/ui/SpinniesManager.js +9 -12
- package/lib/ui/__tests__/SpinniesManager.test.d.ts +1 -0
- package/lib/ui/__tests__/SpinniesManager.test.js +489 -0
- package/mcp-server/utils/config.js +1 -1
- package/package.json +4 -4
- package/ui/components/BoxWithTitle.js +1 -1
|
@@ -37,7 +37,7 @@ const getProjectConfigSpy = vi.spyOn(projectUtils, 'getProjectConfig');
|
|
|
37
37
|
const projectNamePromptSpy = vi.spyOn(projectNamePrompt, 'projectNamePrompt');
|
|
38
38
|
const getProjectDetailUrlSpy = vi.spyOn(projectUrlUtils, 'getProjectDetailUrl');
|
|
39
39
|
const fetchProjectSpy = vi.spyOn(projectApiUtils, 'fetchProject');
|
|
40
|
-
const
|
|
40
|
+
const deployProjectV1Spy = vi.spyOn(projectApiUtils, 'deployProjectV1');
|
|
41
41
|
const getConfigAccountByIdSpy = vi.spyOn(configUtils, 'getConfigAccountById');
|
|
42
42
|
const promptUserSpy = vi.spyOn(promptUtils, 'promptUser');
|
|
43
43
|
const processExitSpy = vi.spyOn(process, 'exit');
|
|
@@ -144,7 +144,7 @@ describe('commands/project/deploy', () => {
|
|
|
144
144
|
env: 'qa',
|
|
145
145
|
});
|
|
146
146
|
fetchProjectSpy.mockReturnValue(mockHubSpotHttpResponse(exampleProject));
|
|
147
|
-
|
|
147
|
+
deployProjectV1Spy.mockReturnValue(mockHubSpotHttpResponse(deployDetails));
|
|
148
148
|
// Spy on process.exit so our tests don't close when it's called
|
|
149
149
|
// @ts-expect-error Doesn't match the actual signature because then the linter complains about unused variables
|
|
150
150
|
processExitSpy.mockImplementation(() => { });
|
|
@@ -245,12 +245,12 @@ describe('commands/project/deploy', () => {
|
|
|
245
245
|
});
|
|
246
246
|
it('should deploy the project', async () => {
|
|
247
247
|
await projectDeployCommand.handler(args);
|
|
248
|
-
expect(
|
|
249
|
-
expect(
|
|
248
|
+
expect(deployProjectV1Spy).toHaveBeenCalledTimes(1);
|
|
249
|
+
expect(deployProjectV1Spy).toHaveBeenCalledWith(args.derivedAccountId, projectNameFromPrompt, args.buildId, undefined);
|
|
250
250
|
});
|
|
251
251
|
it('should log an error and exit when the deploy fails', async () => {
|
|
252
252
|
// @ts-expect-error Testing an edge case where the response is empty
|
|
253
|
-
|
|
253
|
+
deployProjectV1Spy.mockResolvedValue({});
|
|
254
254
|
await projectDeployCommand.handler(args);
|
|
255
255
|
expect(uiLogger.error).toHaveBeenCalledTimes(1);
|
|
256
256
|
expect(uiLogger.error).toHaveBeenCalledWith(`Deploy error: an unknown error occurred.`);
|
|
@@ -303,7 +303,7 @@ describe('commands/project/deploy', () => {
|
|
|
303
303
|
});
|
|
304
304
|
await projectDeployCommand.handler(args);
|
|
305
305
|
expect(uiLogger.error).toHaveBeenCalledTimes(1);
|
|
306
|
-
expect(uiLogger.error).toHaveBeenCalledWith(`The request for 'project deploy' in account ${args.derivedAccountId} failed due to a client error.`);
|
|
306
|
+
expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining(`The request for 'project deploy' in account ${args.derivedAccountId} failed due to a client error.`));
|
|
307
307
|
expect(processExitSpy).toHaveBeenCalledTimes(1);
|
|
308
308
|
expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
309
309
|
});
|
|
@@ -5,14 +5,11 @@ 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 {
|
|
8
|
+
import { loadAndValidateProfile } from '../../../lib/projectProfiles.js';
|
|
9
9
|
import { trackCommandUsage } from '../../../lib/usageTracking.js';
|
|
10
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';
|
|
16
13
|
// Mock dependencies
|
|
17
14
|
vi.mock('../../../lib/projects/upload.js');
|
|
18
15
|
vi.mock('../../../lib/projects/config.js');
|
|
@@ -22,35 +19,15 @@ vi.mock('../../../lib/projectProfiles.js');
|
|
|
22
19
|
vi.mock('../../../lib/errorHandlers/index.js');
|
|
23
20
|
vi.mock('@hubspot/local-dev-lib/config');
|
|
24
21
|
vi.mock('../../../lib/projects/platformVersion.js');
|
|
25
|
-
vi.mock('@hubspot/project-parsing-lib');
|
|
26
|
-
vi.mock('../../../lib/ui/SpinniesManager.js');
|
|
27
22
|
describe('commands/project/validate', () => {
|
|
28
23
|
const projectDir = '/test/project';
|
|
29
24
|
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
|
-
};
|
|
40
25
|
beforeEach(() => {
|
|
41
26
|
// Mock process.exit to throw to stop execution
|
|
42
27
|
exitSpy = vi.spyOn(process, 'exit').mockImplementation(code => {
|
|
43
28
|
throw new Error(`Process exited with code ${code}`);
|
|
44
29
|
});
|
|
45
30
|
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([]);
|
|
54
31
|
});
|
|
55
32
|
afterEach(() => {
|
|
56
33
|
exitSpy.mockRestore();
|
|
@@ -88,269 +65,34 @@ describe('commands/project/validate', () => {
|
|
|
88
65
|
})).rejects.toThrow('Process exited with code 1');
|
|
89
66
|
expect(uiLogger.error).toHaveBeenCalledWith(commands.project.validate.mustBeRanWithinAProject);
|
|
90
67
|
});
|
|
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
|
-
});
|
|
129
68
|
});
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
});
|
|
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,
|
|
193
78
|
});
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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);
|
|
79
|
+
vi.mocked(isV2Project).mockReturnValue(true);
|
|
80
|
+
vi.mocked(validateProjectConfig).mockReturnValue(undefined);
|
|
81
|
+
vi.mocked(loadAndValidateProfile).mockResolvedValue(123);
|
|
82
|
+
vi.mocked(getConfigAccountById).mockReturnValue({
|
|
83
|
+
accountType: 'STANDARD',
|
|
84
|
+
accountId: 123,
|
|
85
|
+
env: 'prod',
|
|
354
86
|
});
|
|
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);
|
|
355
97
|
});
|
|
356
98
|
});
|
|
@@ -7,7 +7,6 @@ import { writeProjectConfig, getProjectConfig, } from '../../lib/projects/config
|
|
|
7
7
|
import { EMPTY_PROJECT_TEMPLATE_NAME } from '../../lib/projects/create/legacy.js';
|
|
8
8
|
import { generateComponentPaths } from '../../lib/projects/create/v2.js';
|
|
9
9
|
import { PROJECT_WITH_APP, EMPTY_PROJECT } from '../../lib/constants.js';
|
|
10
|
-
import { uiFeatureHighlight } from '../../lib/ui/index.js';
|
|
11
10
|
import { debugError, logError } from '../../lib/errorHandlers/index.js';
|
|
12
11
|
import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
13
12
|
import { PROJECT_CONFIG_FILE, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, marketplaceDistribution, privateDistribution, oAuth, staticAuth, DEFAULT_PROJECT_TEMPLATE_BRANCH, } from '../../lib/constants.js';
|
|
@@ -18,6 +17,7 @@ import { uiLogger } from '../../lib/ui/logger.js';
|
|
|
18
17
|
import { handleProjectCreationFlow, } from '../../lib/projects/create/index.js';
|
|
19
18
|
import { getProjectMetadata, } from '@hubspot/project-parsing-lib/src/lib/project.js';
|
|
20
19
|
import { updateHsMetaFilesWithAutoGeneratedFields } from '../../lib/projects/components.js';
|
|
20
|
+
import SpinniesManager from '../../lib/ui/SpinniesManager.js';
|
|
21
21
|
const command = ['create', 'init'];
|
|
22
22
|
const describe = commands.project.create.describe;
|
|
23
23
|
const { v2023_2, v2025_1, v2025_2 } = PLATFORM_VERSIONS;
|
|
@@ -61,6 +61,12 @@ async function handler(args) {
|
|
|
61
61
|
authType,
|
|
62
62
|
distribution,
|
|
63
63
|
});
|
|
64
|
+
const isProjectEmpty = selectProjectTemplatePromptResponse.projectTemplate?.name ===
|
|
65
|
+
EMPTY_PROJECT_TEMPLATE_NAME || projectContents === EMPTY_PROJECT;
|
|
66
|
+
SpinniesManager.init();
|
|
67
|
+
SpinniesManager.add('project-create', {
|
|
68
|
+
text: commands.project.create.creatingComponent(isProjectEmpty, projectNameAndDestPromptResponse.name),
|
|
69
|
+
});
|
|
64
70
|
try {
|
|
65
71
|
await cloneGithubRepo(repo, projectDest, {
|
|
66
72
|
sourceDir: selectProjectTemplatePromptResponse.projectTemplate?.path || components,
|
|
@@ -69,6 +75,9 @@ async function handler(args) {
|
|
|
69
75
|
});
|
|
70
76
|
}
|
|
71
77
|
catch (err) {
|
|
78
|
+
SpinniesManager.fail('project-create', {
|
|
79
|
+
text: commands.project.create.failure(isProjectEmpty, projectNameAndDestPromptResponse.name),
|
|
80
|
+
});
|
|
72
81
|
debugError(err);
|
|
73
82
|
uiLogger.error(commands.project.create.errors.failedToDownloadProject);
|
|
74
83
|
process.exit(EXIT_CODES.ERROR);
|
|
@@ -80,23 +89,20 @@ async function handler(args) {
|
|
|
80
89
|
...parsedConfigFile,
|
|
81
90
|
name: projectName,
|
|
82
91
|
});
|
|
92
|
+
SpinniesManager.succeed('project-create', {
|
|
93
|
+
text: commands.project.create.success(isProjectEmpty, projectName),
|
|
94
|
+
});
|
|
83
95
|
const projectMetadata = await getProjectMetadata(path.join(projectDest, parsedConfigFile.srcDir));
|
|
84
|
-
updateHsMetaFilesWithAutoGeneratedFields(projectName, projectMetadata.hsMetaFiles
|
|
96
|
+
await updateHsMetaFilesWithAutoGeneratedFields(projectName, projectMetadata.hsMetaFiles, [], {
|
|
97
|
+
updatedProjectMetadata: projectMetadata,
|
|
98
|
+
showSuccessMessage: true,
|
|
99
|
+
isProjectEmpty,
|
|
100
|
+
projectDest,
|
|
101
|
+
});
|
|
85
102
|
// If the template is 'no-template', we need to manually create a src directory
|
|
86
|
-
if (
|
|
87
|
-
EMPTY_PROJECT_TEMPLATE_NAME ||
|
|
88
|
-
projectContents === EMPTY_PROJECT) {
|
|
103
|
+
if (isProjectEmpty) {
|
|
89
104
|
fs.ensureDirSync(path.join(projectDest, 'src'));
|
|
90
105
|
}
|
|
91
|
-
uiLogger.success(commands.project.create.logs.success(projectNameAndDestPromptResponse.name, projectDest));
|
|
92
|
-
uiLogger.log(commands.project.create.logs.welcomeMessage);
|
|
93
|
-
uiFeatureHighlight([
|
|
94
|
-
'projectCommandTip',
|
|
95
|
-
'projectUploadCommand',
|
|
96
|
-
'projectDevCommand',
|
|
97
|
-
'projectHelpCommand',
|
|
98
|
-
'feedbackCommand',
|
|
99
|
-
]);
|
|
100
106
|
process.exit(EXIT_CODES.SUCCESS);
|
|
101
107
|
}
|
|
102
108
|
function projectCreateBuilder(yargs) {
|
|
@@ -7,10 +7,11 @@ 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';
|
|
10
11
|
import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
11
12
|
import { uiLogger } from '../../lib/ui/logger.js';
|
|
12
13
|
import { makeYargsBuilder } from '../../lib/yargsUtils.js';
|
|
13
|
-
import { loadProfile, logProfileFooter, logProfileHeader,
|
|
14
|
+
import { loadProfile, logProfileFooter, logProfileHeader, exitIfUsingProfiles, } from '../../lib/projectProfiles.js';
|
|
14
15
|
import { PROJECT_DEPLOY_TEXT } from '../../lib/constants.js';
|
|
15
16
|
import { commands } from '../../lang/en.js';
|
|
16
17
|
import { handleProjectDeploy, validateBuildIdForDeploy, logDeployErrors, } from '../../lib/projects/deploy.js';
|
|
@@ -26,12 +27,9 @@ async function handler(args) {
|
|
|
26
27
|
if (isV2Project(projectConfig?.platformVersion)) {
|
|
27
28
|
if (args.profile) {
|
|
28
29
|
logProfileHeader(args.profile);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
logError(error);
|
|
30
|
+
const profile = loadProfile(projectConfig, projectDir, args.profile);
|
|
31
|
+
if (!profile) {
|
|
32
|
+
uiLine();
|
|
35
33
|
process.exit(EXIT_CODES.ERROR);
|
|
36
34
|
}
|
|
37
35
|
targetAccountId = profile.accountId;
|
|
@@ -39,13 +37,7 @@ async function handler(args) {
|
|
|
39
37
|
}
|
|
40
38
|
else {
|
|
41
39
|
// A profile must be specified if this project has profiles configured
|
|
42
|
-
|
|
43
|
-
await enforceProfileUsage(projectConfig, projectDir);
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
logError(error);
|
|
47
|
-
process.exit(EXIT_CODES.ERROR);
|
|
48
|
-
}
|
|
40
|
+
await exitIfUsingProfiles(projectConfig, projectDir);
|
|
49
41
|
}
|
|
50
42
|
}
|
|
51
43
|
if (!targetAccountId) {
|
|
@@ -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,
|
|
10
|
+
import { loadProfile, exitIfUsingProfiles, } 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';
|
|
@@ -64,11 +64,8 @@ async function handler(args) {
|
|
|
64
64
|
}
|
|
65
65
|
if (!targetProjectAccountId && isV2Project(projectConfig.platformVersion)) {
|
|
66
66
|
if (args.profile) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
logError(error);
|
|
67
|
+
profile = loadProfile(projectConfig, projectDir, args.profile);
|
|
68
|
+
if (!profile) {
|
|
72
69
|
uiLine();
|
|
73
70
|
process.exit(EXIT_CODES.ERROR);
|
|
74
71
|
}
|
|
@@ -78,13 +75,7 @@ async function handler(args) {
|
|
|
78
75
|
}
|
|
79
76
|
else {
|
|
80
77
|
// A profile must be specified if this project has profiles configured
|
|
81
|
-
|
|
82
|
-
await enforceProfileUsage(projectConfig, projectDir);
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
logError(error);
|
|
86
|
-
process.exit(EXIT_CODES.ERROR);
|
|
87
|
-
}
|
|
78
|
+
await exitIfUsingProfiles(projectConfig, projectDir);
|
|
88
79
|
}
|
|
89
80
|
}
|
|
90
81
|
if (!targetProjectAccountId) {
|
|
@@ -123,7 +123,13 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
|
|
|
123
123
|
// Check for missing/outdated dependencies
|
|
124
124
|
await checkAndInstallDependencies();
|
|
125
125
|
// End setup, start local dev process
|
|
126
|
-
|
|
126
|
+
try {
|
|
127
|
+
await startPortManagerServer();
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
logError(e);
|
|
131
|
+
process.exit(EXIT_CODES.ERROR);
|
|
132
|
+
}
|
|
127
133
|
const localDevProcess = new LocalDevProcess({
|
|
128
134
|
initialProjectNodes: projectNodes,
|
|
129
135
|
initialProjectProfileData: projectProfileData,
|
|
@@ -28,14 +28,8 @@ async function handler(args) {
|
|
|
28
28
|
process.exit(EXIT_CODES.ERROR);
|
|
29
29
|
}
|
|
30
30
|
let targetAccountId;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
targetAccountId = await loadAndValidateProfile(projectConfig, projectDir, profile);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
logError(err);
|
|
38
|
-
process.exit(EXIT_CODES.ERROR);
|
|
31
|
+
if (isV2Project(projectConfig.platformVersion)) {
|
|
32
|
+
targetAccountId = await loadAndValidateProfile(projectConfig, projectDir, profile);
|
|
39
33
|
}
|
|
40
34
|
targetAccountId = targetAccountId || derivedAccountId;
|
|
41
35
|
const accountConfig = getConfigAccountById(targetAccountId);
|