@hubspot/cli 7.10.0-experimental.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.
- package/commands/project/__tests__/validate.test.js +285 -27
- package/commands/project/deploy.js +14 -6
- package/commands/project/dev/index.js +13 -4
- package/commands/project/migrate.js +4 -13
- package/commands/project/upload.js +8 -2
- package/commands/project/validate.js +72 -12
- package/lang/en.d.ts +13 -10
- package/lang/en.js +14 -11
- package/lib/__tests__/projectProfiles.test.js +273 -32
- package/lib/middleware/autoUpdateMiddleware.js +25 -22
- package/lib/middleware/fireAlarmMiddleware.js +4 -15
- package/lib/projectProfiles.d.ts +4 -3
- package/lib/projectProfiles.js +78 -32
- package/lib/projects/localDev/LocalDevLogger.d.ts +0 -3
- package/lib/projects/localDev/LocalDevLogger.js +0 -9
- package/lib/projects/localDev/LocalDevProcess.d.ts +0 -1
- package/lib/projects/localDev/LocalDevProcess.js +1 -12
- package/lib/projects/localDev/LocalDevState.d.ts +0 -3
- package/lib/projects/localDev/LocalDevState.js +0 -9
- package/mcp-server/utils/config.js +1 -1
- package/package.json +3 -4
- package/types/LocalDev.d.ts +0 -1
- package/ui/components/BoxWithTitle.d.ts +2 -1
- package/ui/components/BoxWithTitle.js +2 -2
- package/ui/components/StatusMessageBoxes.d.ts +5 -4
- package/ui/components/StatusMessageBoxes.js +8 -8
- package/lib/projects/localDev/DevSessionManager.d.ts +0 -17
- package/lib/projects/localDev/DevSessionManager.js +0 -56
- package/lib/projects/localDev/helpers/devSessionsApi.d.ts +0 -9
- package/lib/projects/localDev/helpers/devSessionsApi.js +0 -19
- package/lib/ui/boxen.d.ts +0 -5
- package/lib/ui/boxen.js +0 -26
|
@@ -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 {
|
|
8
|
+
import { validateProjectForProfile } 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';
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
});
|
|
@@ -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,
|
|
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';
|
|
@@ -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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -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, 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';
|
|
@@ -64,8 +64,11 @@ async function handler(args) {
|
|
|
64
64
|
}
|
|
65
65
|
if (!targetProjectAccountId && isV2Project(projectConfig.platformVersion)) {
|
|
66
66
|
if (args.profile) {
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
try {
|
|
68
|
+
profile = loadProfile(projectConfig, projectDir, args.profile);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logError(error);
|
|
69
72
|
uiLine();
|
|
70
73
|
process.exit(EXIT_CODES.ERROR);
|
|
71
74
|
}
|
|
@@ -75,7 +78,13 @@ async function handler(args) {
|
|
|
75
78
|
}
|
|
76
79
|
else {
|
|
77
80
|
// A profile must be specified if this project has profiles configured
|
|
78
|
-
|
|
81
|
+
try {
|
|
82
|
+
await enforceProfileUsage(projectConfig, projectDir);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logError(error);
|
|
86
|
+
process.exit(EXIT_CODES.ERROR);
|
|
87
|
+
}
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
90
|
if (!targetProjectAccountId) {
|
|
@@ -7,7 +7,6 @@ import { makeYargsBuilder } from '../../lib/yargsUtils.js';
|
|
|
7
7
|
import { uiCommandReference } from '../../lib/ui/index.js';
|
|
8
8
|
import { commands, lib } from '../../lang/en.js';
|
|
9
9
|
import { uiLogger } from '../../lib/ui/logger.js';
|
|
10
|
-
import { logInBox } from '../../lib/ui/boxen.js';
|
|
11
10
|
import { renderInline } from '../../ui/index.js';
|
|
12
11
|
import { getWarningBox } from '../../ui/components/StatusMessageBoxes.js';
|
|
13
12
|
import { getHasMigratableThemes, migrateThemes2025_2, } from '../../lib/theme/migrate.js';
|
|
@@ -26,18 +25,10 @@ async function handler(args) {
|
|
|
26
25
|
return process.exit(EXIT_CODES.ERROR);
|
|
27
26
|
}
|
|
28
27
|
if (projectConfig?.projectConfig) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
await renderInline(getWarningBox({
|
|
37
|
-
title: lib.migrate.projectMigrationWarningTitle,
|
|
38
|
-
message: lib.migrate.projectMigrationWarning,
|
|
39
|
-
}));
|
|
40
|
-
}
|
|
28
|
+
await renderInline(getWarningBox({
|
|
29
|
+
title: lib.migrate.projectMigrationWarningTitle,
|
|
30
|
+
message: lib.migrate.projectMigrationWarning,
|
|
31
|
+
}));
|
|
41
32
|
}
|
|
42
33
|
try {
|
|
43
34
|
const { hasMigratableThemes, migratableThemesCount } = await getHasMigratableThemes(projectConfig);
|
|
@@ -28,8 +28,14 @@ async function handler(args) {
|
|
|
28
28
|
process.exit(EXIT_CODES.ERROR);
|
|
29
29
|
}
|
|
30
30
|
let targetAccountId;
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
try {
|
|
32
|
+
if (isV2Project(projectConfig.platformVersion)) {
|
|
33
|
+
targetAccountId = await loadAndValidateProfile(projectConfig, projectDir, profile);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
logError(err);
|
|
38
|
+
process.exit(EXIT_CODES.ERROR);
|
|
33
39
|
}
|
|
34
40
|
targetAccountId = targetAccountId || derivedAccountId;
|
|
35
41
|
const accountConfig = getConfigAccountById(targetAccountId);
|
|
@@ -8,13 +8,19 @@ import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
|
8
8
|
import { makeYargsBuilder } from '../../lib/yargsUtils.js';
|
|
9
9
|
import { validateSourceDirectory, handleTranslate, } from '../../lib/projects/upload.js';
|
|
10
10
|
import { commands } from '../../lang/en.js';
|
|
11
|
-
import {
|
|
11
|
+
import { validateProjectForProfile } from '../../lib/projectProfiles.js';
|
|
12
12
|
import { logError } from '../../lib/errorHandlers/index.js';
|
|
13
|
+
import { getAllHsProfiles } from '@hubspot/project-parsing-lib';
|
|
14
|
+
import SpinniesManager from '../../lib/ui/SpinniesManager.js';
|
|
13
15
|
const command = 'validate';
|
|
14
16
|
const describe = commands.project.validate.describe;
|
|
15
17
|
async function handler(args) {
|
|
18
|
+
SpinniesManager.init();
|
|
16
19
|
const { derivedAccountId, profile } = args;
|
|
17
20
|
const { projectConfig, projectDir } = await getProjectConfig();
|
|
21
|
+
const accountConfig = getConfigAccountById(derivedAccountId);
|
|
22
|
+
const accountType = accountConfig && accountConfig.accountType;
|
|
23
|
+
trackCommandUsage('project-validate', { type: accountType }, derivedAccountId);
|
|
18
24
|
if (!projectConfig || !projectDir) {
|
|
19
25
|
uiLogger.error(commands.project.validate.mustBeRanWithinAProject);
|
|
20
26
|
process.exit(EXIT_CODES.ERROR);
|
|
@@ -30,25 +36,79 @@ async function handler(args) {
|
|
|
30
36
|
logError(error);
|
|
31
37
|
process.exit(EXIT_CODES.ERROR);
|
|
32
38
|
}
|
|
33
|
-
let
|
|
34
|
-
targetAccountId = targetAccountId || derivedAccountId;
|
|
35
|
-
const accountConfig = getConfigAccountById(targetAccountId);
|
|
36
|
-
const accountType = accountConfig && accountConfig.accountType;
|
|
37
|
-
trackCommandUsage('project-validate', { type: accountType }, targetAccountId);
|
|
39
|
+
let validationSucceeded = true;
|
|
38
40
|
const srcDir = path.resolve(projectDir, projectConfig.srcDir);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
// Get all of the profiles except the provided profile
|
|
42
|
+
const profiles = (await getAllHsProfiles(path.join(projectDir, projectConfig.srcDir))).filter(profileName => profileName !== profile);
|
|
43
|
+
// If a profile is specified, only validate that profile
|
|
44
|
+
if (profile) {
|
|
45
|
+
const validationErrors = await validateProjectForProfile(projectConfig, projectDir, profile, derivedAccountId);
|
|
46
|
+
if (validationErrors.length) {
|
|
47
|
+
validationErrors.forEach(error => {
|
|
48
|
+
uiLogger.log('');
|
|
49
|
+
if (error instanceof Error) {
|
|
50
|
+
logError(error);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
uiLogger.error(error);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
validationSucceeded = false;
|
|
57
|
+
}
|
|
41
58
|
}
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
else if (profiles.length > 0) {
|
|
60
|
+
// If no profile was specified and the project has profiles, validate all of them
|
|
61
|
+
SpinniesManager.add('validatingAllProfiles', {
|
|
62
|
+
text: commands.project.validate.spinners.validatingAllProfiles,
|
|
63
|
+
});
|
|
64
|
+
const errors = [];
|
|
65
|
+
for (const profileName of profiles) {
|
|
66
|
+
const validationErrors = await validateProjectForProfile(projectConfig, projectDir, profileName, derivedAccountId, true);
|
|
67
|
+
if (validationErrors.length) {
|
|
68
|
+
errors.push(...validationErrors);
|
|
69
|
+
validationSucceeded = false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (validationSucceeded) {
|
|
73
|
+
SpinniesManager.succeed('validatingAllProfiles', {
|
|
74
|
+
text: commands.project.validate.spinners.allProfilesValidationSucceeded,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
SpinniesManager.fail('validatingAllProfiles', {
|
|
79
|
+
text: commands.project.validate.spinners.allProfilesValidationFailed,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
errors.forEach(error => {
|
|
83
|
+
uiLogger.log('');
|
|
84
|
+
if (error instanceof Error) {
|
|
85
|
+
logError(error);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
uiLogger.error(error);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else if (profiles.length === 0) {
|
|
93
|
+
// If the project has no profiles, validate the project without a profile
|
|
94
|
+
try {
|
|
95
|
+
await handleTranslate(projectDir, projectConfig, derivedAccountId, false, undefined);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
uiLogger.error(commands.project.validate.failure(projectConfig.name));
|
|
99
|
+
logError(e);
|
|
100
|
+
validationSucceeded = false;
|
|
101
|
+
uiLogger.log('');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!validationSucceeded) {
|
|
44
105
|
process.exit(EXIT_CODES.ERROR);
|
|
45
106
|
}
|
|
46
107
|
try {
|
|
47
|
-
await
|
|
108
|
+
await validateSourceDirectory(srcDir, projectConfig, projectDir);
|
|
48
109
|
}
|
|
49
110
|
catch (e) {
|
|
50
111
|
logError(e);
|
|
51
|
-
uiLogger.error(commands.project.validate.failure(projectConfig.name));
|
|
52
112
|
process.exit(EXIT_CODES.ERROR);
|
|
53
113
|
}
|
|
54
114
|
uiLogger.success(commands.project.validate.success(projectConfig.name));
|
package/lang/en.d.ts
CHANGED
|
@@ -1802,7 +1802,16 @@ export declare const commands: {
|
|
|
1802
1802
|
default: string;
|
|
1803
1803
|
};
|
|
1804
1804
|
success: (projectName: string) => string;
|
|
1805
|
-
failure: (projectName: string) => string;
|
|
1805
|
+
failure: (projectName: string, profileName?: string) => string;
|
|
1806
|
+
spinners: {
|
|
1807
|
+
validatingProfile: (profileName: string) => string;
|
|
1808
|
+
profileValidationFailed: (profileName: string) => string;
|
|
1809
|
+
profileValidationSucceeded: (profileName: string) => string;
|
|
1810
|
+
invalidWithProfile: (profileName: string, projectName: string) => string;
|
|
1811
|
+
validatingAllProfiles: string;
|
|
1812
|
+
allProfilesValidationSucceeded: string;
|
|
1813
|
+
allProfilesValidationFailed: string;
|
|
1814
|
+
};
|
|
1806
1815
|
options: {
|
|
1807
1816
|
profile: {
|
|
1808
1817
|
describe: string;
|
|
@@ -2844,11 +2853,6 @@ export declare const lib: {
|
|
|
2844
2853
|
startError: (message: string) => string;
|
|
2845
2854
|
fileChangeError: (message: string) => string;
|
|
2846
2855
|
};
|
|
2847
|
-
devSession: {
|
|
2848
|
-
registrationError: (message: string) => string;
|
|
2849
|
-
heartbeatError: (message: string) => string;
|
|
2850
|
-
deletionError: (message: string) => string;
|
|
2851
|
-
};
|
|
2852
2856
|
};
|
|
2853
2857
|
AppDevModeInterface: {
|
|
2854
2858
|
autoInstallStaticAuthApp: {
|
|
@@ -2961,7 +2965,7 @@ export declare const lib: {
|
|
|
2961
2965
|
updateNotification: {
|
|
2962
2966
|
notifyTitle: string;
|
|
2963
2967
|
cmsUpdateNotification: (packageName: string) => string;
|
|
2964
|
-
cliUpdateNotification: string;
|
|
2968
|
+
cliUpdateNotification: (currentVersion: string, updateCommand: string, latestVersion: string) => string;
|
|
2965
2969
|
};
|
|
2966
2970
|
autoUpdateCLI: {
|
|
2967
2971
|
updateAvailable: (latestVersion: string) => string;
|
|
@@ -2986,7 +2990,9 @@ export declare const lib: {
|
|
|
2986
2990
|
noProjectConfig: string;
|
|
2987
2991
|
profileNotFound: (profileName: string) => string;
|
|
2988
2992
|
missingAccountId: (profileName: string) => string;
|
|
2993
|
+
listedAccountNotFound: (accountId: number, profileName: string) => string;
|
|
2989
2994
|
failedToLoadProfile: (profileName: string) => string;
|
|
2995
|
+
profileNotValid: (profileName: string, errors: string[]) => string;
|
|
2990
2996
|
};
|
|
2991
2997
|
};
|
|
2992
2998
|
};
|
|
@@ -3077,9 +3083,6 @@ export declare const lib: {
|
|
|
3077
3083
|
legacyFileDetected: (filename: string, platformVersion: string) => string;
|
|
3078
3084
|
};
|
|
3079
3085
|
};
|
|
3080
|
-
boxen: {
|
|
3081
|
-
failedToLoad: string;
|
|
3082
|
-
};
|
|
3083
3086
|
importData: {
|
|
3084
3087
|
errors: {
|
|
3085
3088
|
incorrectAccountType: (derivedAccountId: number) => string;
|