@hubspot/cli 7.9.0-beta.2 → 7.9.0-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/__tests__/project.test.js +2 -0
- package/commands/account/createOverride.js +2 -12
- package/commands/account/removeOverride.js +2 -10
- package/commands/cms/theme/preview.js +1 -4
- package/commands/getStarted.js +7 -19
- package/commands/project/__tests__/updateDeps.test.d.ts +1 -0
- package/commands/project/__tests__/updateDeps.test.js +142 -0
- package/commands/project/create.js +0 -1
- package/commands/project/updateDeps.d.ts +6 -0
- package/commands/project/updateDeps.js +80 -0
- package/commands/project.js +2 -0
- package/commands/testAccount/create.js +1 -1
- package/lang/en.d.ts +20 -5
- package/lang/en.js +19 -5
- package/lib/__tests__/dependencyManagement.test.js +273 -1
- package/lib/commonOpts.js +2 -5
- package/lib/dependencyManagement.d.ts +8 -2
- package/lib/dependencyManagement.js +75 -12
- package/lib/npm.d.ts +3 -0
- package/lib/npm.js +6 -0
- package/lib/projects/__tests__/platformVersion.test.js +5 -1
- package/lib/projects/platformVersion.js +1 -1
- package/package.json +2 -2
- package/lang/en.lyaml +0 -1508
- package/lib/lang.d.ts +0 -8
- package/lib/lang.js +0 -72
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import util from 'util';
|
|
2
|
-
import { installPackages, getProjectPackageJsonLocations, } from '../dependencyManagement.js';
|
|
2
|
+
import { installPackages, updatePackages, getProjectPackageJsonLocations, isPackageInstalled, } from '../dependencyManagement.js';
|
|
3
3
|
import { walk } from '@hubspot/local-dev-lib/fs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { getProjectConfig } from '../projects/config.js';
|
|
@@ -105,6 +105,54 @@ describe('lib/dependencyManagement', () => {
|
|
|
105
105
|
cwd: extensionsDir,
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
|
+
it('should install packages as dev dependencies when dev flag is true', async () => {
|
|
109
|
+
const packages = ['eslint', 'prettier'];
|
|
110
|
+
await installPackages({ packages, installLocations, dev: true });
|
|
111
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
112
|
+
for (const location of installLocations) {
|
|
113
|
+
expect(execMock).toHaveBeenCalledWith(`npm install --save-dev eslint prettier`, {
|
|
114
|
+
cwd: location,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
it('should install packages as regular dependencies when dev flag is false', async () => {
|
|
119
|
+
const packages = ['react', 'react-dom'];
|
|
120
|
+
await installPackages({ packages, installLocations, dev: false });
|
|
121
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
122
|
+
for (const location of installLocations) {
|
|
123
|
+
expect(execMock).toHaveBeenCalledWith(`npm install react react-dom`, {
|
|
124
|
+
cwd: location,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
it('should install packages as regular dependencies when dev flag is not provided', async () => {
|
|
129
|
+
const packages = ['axios'];
|
|
130
|
+
await installPackages({ packages, installLocations });
|
|
131
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
132
|
+
for (const location of installLocations) {
|
|
133
|
+
expect(execMock).toHaveBeenCalledWith(`npm install axios`, {
|
|
134
|
+
cwd: location,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
it('should not use --save-dev flag when dev is true but no packages are provided', async () => {
|
|
139
|
+
await installPackages({ installLocations, dev: true });
|
|
140
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
141
|
+
for (const location of installLocations) {
|
|
142
|
+
expect(execMock).toHaveBeenCalledWith(`npm install `, {
|
|
143
|
+
cwd: location,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
it('should not use --save-dev flag when dev is true but packages array is empty', async () => {
|
|
148
|
+
await installPackages({ packages: [], installLocations, dev: true });
|
|
149
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
150
|
+
for (const location of installLocations) {
|
|
151
|
+
expect(execMock).toHaveBeenCalledWith(`npm install `, {
|
|
152
|
+
cwd: location,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
108
156
|
it('should throw an error when installing the dependencies fails', async () => {
|
|
109
157
|
execMock = vi.fn().mockImplementation(command => {
|
|
110
158
|
if (command === 'npm --version') {
|
|
@@ -133,6 +181,92 @@ describe('lib/dependencyManagement', () => {
|
|
|
133
181
|
});
|
|
134
182
|
});
|
|
135
183
|
});
|
|
184
|
+
describe('updatePackages()', () => {
|
|
185
|
+
it('should setup a loading spinner', async () => {
|
|
186
|
+
const packages = ['package1', 'package2'];
|
|
187
|
+
await updatePackages({ packages, installLocations });
|
|
188
|
+
expect(SpinniesManager.init).toHaveBeenCalledTimes(installLocations.length);
|
|
189
|
+
expect(SpinniesManager.add).toHaveBeenCalledTimes(installLocations.length);
|
|
190
|
+
expect(SpinniesManager.succeed).toHaveBeenCalledTimes(installLocations.length);
|
|
191
|
+
});
|
|
192
|
+
it('should update the provided packages in all the provided install locations', async () => {
|
|
193
|
+
const packages = ['package1', 'package2'];
|
|
194
|
+
await updatePackages({ packages, installLocations });
|
|
195
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
196
|
+
expect(SpinniesManager.add).toHaveBeenCalledTimes(installLocations.length);
|
|
197
|
+
expect(SpinniesManager.succeed).toHaveBeenCalledTimes(installLocations.length);
|
|
198
|
+
for (const location of installLocations) {
|
|
199
|
+
expect(execMock).toHaveBeenCalledWith(`npm update package1 package2`, {
|
|
200
|
+
cwd: location,
|
|
201
|
+
});
|
|
202
|
+
expect(SpinniesManager.add).toHaveBeenCalledWith(`updatingDependencies-${location}`, {
|
|
203
|
+
text: `Updating [package1, package2] in ${location}`,
|
|
204
|
+
});
|
|
205
|
+
expect(SpinniesManager.succeed).toHaveBeenCalledWith(`updatingDependencies-${location}`, {
|
|
206
|
+
text: `Updated dependencies in ${location}`,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
it('should use the provided install locations', async () => {
|
|
211
|
+
await updatePackages({ installLocations });
|
|
212
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length);
|
|
213
|
+
expect(execMock).toHaveBeenCalledWith(`npm update `, {
|
|
214
|
+
cwd: appFunctionsDir,
|
|
215
|
+
});
|
|
216
|
+
expect(execMock).toHaveBeenCalledWith(`npm update `, {
|
|
217
|
+
cwd: extensionsDir,
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
it('should locate the projects package.json files when install locations is not provided', async () => {
|
|
221
|
+
const installLocations = [
|
|
222
|
+
path.join(appFunctionsDir, 'package.json'),
|
|
223
|
+
path.join(extensionsDir, 'package.json'),
|
|
224
|
+
];
|
|
225
|
+
mockedWalk.mockResolvedValue(installLocations);
|
|
226
|
+
mockedGetProjectConfig.mockResolvedValue({
|
|
227
|
+
projectDir,
|
|
228
|
+
projectConfig: {
|
|
229
|
+
srcDir,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
await updatePackages({});
|
|
233
|
+
// It's called once per each install location, plus once to check if npm installed
|
|
234
|
+
expect(execMock).toHaveBeenCalledTimes(installLocations.length + 1);
|
|
235
|
+
expect(execMock).toHaveBeenCalledWith(`npm update `, {
|
|
236
|
+
cwd: appFunctionsDir,
|
|
237
|
+
});
|
|
238
|
+
expect(execMock).toHaveBeenCalledWith(`npm update `, {
|
|
239
|
+
cwd: extensionsDir,
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
it('should throw an error when updating the dependencies fails', async () => {
|
|
243
|
+
execMock = vi.fn().mockImplementation(command => {
|
|
244
|
+
if (command === 'npm --version') {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
throw new Error('OH NO');
|
|
248
|
+
});
|
|
249
|
+
util.promisify = mockedPromisify(execMock);
|
|
250
|
+
// Mock walk to return the directory paths instead of package.json paths
|
|
251
|
+
mockedWalk.mockResolvedValue([appFunctionsDir, extensionsDir]);
|
|
252
|
+
mockedFs.existsSync.mockImplementation(filePath => {
|
|
253
|
+
const pathStr = filePath.toString();
|
|
254
|
+
if (pathStr === projectDir ||
|
|
255
|
+
pathStr === path.join(projectDir, srcDir)) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
});
|
|
260
|
+
await expect(() => updatePackages({ installLocations: [appFunctionsDir, extensionsDir] })).rejects.toThrowError(`Updating dependencies for ${appFunctionsDir} failed`);
|
|
261
|
+
expect(SpinniesManager.fail).toHaveBeenCalledTimes(installLocations.length);
|
|
262
|
+
expect(SpinniesManager.fail).toHaveBeenCalledWith(`updatingDependencies-${appFunctionsDir}`, {
|
|
263
|
+
text: `Updating dependencies for ${appFunctionsDir} failed`,
|
|
264
|
+
});
|
|
265
|
+
expect(SpinniesManager.fail).toHaveBeenCalledWith(`updatingDependencies-${extensionsDir}`, {
|
|
266
|
+
text: `Updating dependencies for ${extensionsDir} failed`,
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
136
270
|
describe('getProjectPackageJsonFiles()', () => {
|
|
137
271
|
it('should throw an error when ran outside the boundary of a project', async () => {
|
|
138
272
|
mockedGetProjectConfig.mockResolvedValue({});
|
|
@@ -149,6 +283,30 @@ describe('lib/dependencyManagement', () => {
|
|
|
149
283
|
mockedFs.existsSync.mockReturnValueOnce(false);
|
|
150
284
|
await expect(() => getProjectPackageJsonLocations()).rejects.toThrowError(new RegExp(`No dependencies to install. The project ${projectName} folder might be missing component or subcomponent files.`));
|
|
151
285
|
});
|
|
286
|
+
it('should throw "install" error message when isUpdate=false and no package.json files found', async () => {
|
|
287
|
+
mockedWalk.mockResolvedValue([]);
|
|
288
|
+
mockedFs.existsSync.mockImplementation(filePath => {
|
|
289
|
+
const pathStr = filePath.toString();
|
|
290
|
+
if (pathStr === projectDir ||
|
|
291
|
+
pathStr === path.join(projectDir, srcDir)) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
return false;
|
|
295
|
+
});
|
|
296
|
+
await expect(() => getProjectPackageJsonLocations(undefined, false)).rejects.toThrowError(new RegExp(`No dependencies to install. The project ${projectName} folder might be missing component or subcomponent files.`));
|
|
297
|
+
});
|
|
298
|
+
it('should throw "update" error message when isUpdate=true and no package.json files found', async () => {
|
|
299
|
+
mockedWalk.mockResolvedValue([]);
|
|
300
|
+
mockedFs.existsSync.mockImplementation(filePath => {
|
|
301
|
+
const pathStr = filePath.toString();
|
|
302
|
+
if (pathStr === projectDir ||
|
|
303
|
+
pathStr === path.join(projectDir, srcDir)) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
return false;
|
|
307
|
+
});
|
|
308
|
+
await expect(() => getProjectPackageJsonLocations(undefined, true)).rejects.toThrowError(new RegExp(`No dependencies to update. The project ${projectName} folder might be missing component or subcomponent files.`));
|
|
309
|
+
});
|
|
152
310
|
it('should ignore package.json files in certain directories', async () => {
|
|
153
311
|
const nodeModulesDir = path.join(appDir, 'node_modules');
|
|
154
312
|
const viteDir = path.join(appDir, '.vite');
|
|
@@ -172,4 +330,118 @@ describe('lib/dependencyManagement', () => {
|
|
|
172
330
|
expect(actual).toEqual([appFunctionsDir, extensionsDir]);
|
|
173
331
|
});
|
|
174
332
|
});
|
|
333
|
+
describe('isPackageInstalled()', () => {
|
|
334
|
+
const testDir = '/test/directory';
|
|
335
|
+
const readFileSyncSpy = vi.spyOn(fs, 'readFileSync');
|
|
336
|
+
const existsSyncSpy = vi.spyOn(fs, 'existsSync');
|
|
337
|
+
function mockNodeModulesExists(packageName, exists = true) {
|
|
338
|
+
existsSyncSpy.mockImplementation(filePath => {
|
|
339
|
+
const pathStr = filePath.toString();
|
|
340
|
+
return (exists && pathStr === path.join(testDir, 'node_modules', packageName));
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
beforeEach(() => {
|
|
344
|
+
vi.clearAllMocks();
|
|
345
|
+
readFileSyncSpy.mockReset();
|
|
346
|
+
existsSyncSpy.mockReset();
|
|
347
|
+
});
|
|
348
|
+
it('should return true if package is in dependencies and in node_modules', () => {
|
|
349
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
350
|
+
dependencies: {
|
|
351
|
+
eslint: '^9.0.0',
|
|
352
|
+
},
|
|
353
|
+
}));
|
|
354
|
+
mockNodeModulesExists('eslint', true);
|
|
355
|
+
const result = isPackageInstalled(testDir, 'eslint');
|
|
356
|
+
expect(result).toBe(true);
|
|
357
|
+
expect(readFileSyncSpy).toHaveBeenCalledWith(path.join(testDir, 'package.json'), 'utf-8');
|
|
358
|
+
expect(existsSyncSpy).toHaveBeenCalledWith(path.join(testDir, 'node_modules', 'eslint'));
|
|
359
|
+
});
|
|
360
|
+
it('should return true if package is in devDependencies and in node_modules', () => {
|
|
361
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
362
|
+
devDependencies: {
|
|
363
|
+
prettier: '^3.0.0',
|
|
364
|
+
},
|
|
365
|
+
}));
|
|
366
|
+
mockNodeModulesExists('prettier', true);
|
|
367
|
+
const result = isPackageInstalled(testDir, 'prettier');
|
|
368
|
+
expect(result).toBe(true);
|
|
369
|
+
});
|
|
370
|
+
it('should return false if package is in package.json but not in node_modules', () => {
|
|
371
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
372
|
+
dependencies: {
|
|
373
|
+
react: '^18.0.0',
|
|
374
|
+
},
|
|
375
|
+
}));
|
|
376
|
+
mockNodeModulesExists('react', false);
|
|
377
|
+
const result = isPackageInstalled(testDir, 'react');
|
|
378
|
+
expect(result).toBe(false);
|
|
379
|
+
});
|
|
380
|
+
it('should return false if package is not in package.json but is in node_modules', () => {
|
|
381
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
382
|
+
dependencies: {
|
|
383
|
+
typescript: '^5.0.0',
|
|
384
|
+
},
|
|
385
|
+
}));
|
|
386
|
+
mockNodeModulesExists('lodash', true);
|
|
387
|
+
const result = isPackageInstalled(testDir, 'lodash');
|
|
388
|
+
expect(result).toBe(false);
|
|
389
|
+
});
|
|
390
|
+
it('should return false if package is not in package.json and not in node_modules', () => {
|
|
391
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
392
|
+
dependencies: {},
|
|
393
|
+
}));
|
|
394
|
+
mockNodeModulesExists('nonexistent-package', false);
|
|
395
|
+
const result = isPackageInstalled(testDir, 'nonexistent-package');
|
|
396
|
+
expect(result).toBe(false);
|
|
397
|
+
});
|
|
398
|
+
it('should return false if package.json cannot be read', () => {
|
|
399
|
+
readFileSyncSpy.mockImplementationOnce(() => {
|
|
400
|
+
throw new Error('File not found');
|
|
401
|
+
});
|
|
402
|
+
const result = isPackageInstalled(testDir, 'eslint');
|
|
403
|
+
expect(result).toBe(false);
|
|
404
|
+
});
|
|
405
|
+
it('should return false if package.json has invalid JSON', () => {
|
|
406
|
+
readFileSyncSpy.mockReturnValueOnce('invalid json{');
|
|
407
|
+
const result = isPackageInstalled(testDir, 'eslint');
|
|
408
|
+
expect(result).toBe(false);
|
|
409
|
+
});
|
|
410
|
+
it('should return false if checking node_modules throws an error', () => {
|
|
411
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
412
|
+
dependencies: {
|
|
413
|
+
eslint: '^9.0.0',
|
|
414
|
+
},
|
|
415
|
+
}));
|
|
416
|
+
existsSyncSpy.mockImplementation(() => {
|
|
417
|
+
throw new Error('Permission denied');
|
|
418
|
+
});
|
|
419
|
+
const result = isPackageInstalled(testDir, 'eslint');
|
|
420
|
+
expect(result).toBe(false);
|
|
421
|
+
});
|
|
422
|
+
it('should handle scoped packages correctly', () => {
|
|
423
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
424
|
+
dependencies: {
|
|
425
|
+
'@typescript-eslint/parser': '^8.0.0',
|
|
426
|
+
},
|
|
427
|
+
}));
|
|
428
|
+
mockNodeModulesExists('@typescript-eslint/parser', true);
|
|
429
|
+
const result = isPackageInstalled(testDir, '@typescript-eslint/parser');
|
|
430
|
+
expect(result).toBe(true);
|
|
431
|
+
expect(existsSyncSpy).toHaveBeenCalledWith(path.join(testDir, 'node_modules', '@typescript-eslint/parser'));
|
|
432
|
+
});
|
|
433
|
+
it('should check both dependencies and devDependencies', () => {
|
|
434
|
+
readFileSyncSpy.mockReturnValueOnce(JSON.stringify({
|
|
435
|
+
dependencies: {
|
|
436
|
+
react: '^18.0.0',
|
|
437
|
+
},
|
|
438
|
+
devDependencies: {
|
|
439
|
+
eslint: '^9.0.0',
|
|
440
|
+
},
|
|
441
|
+
}));
|
|
442
|
+
mockNodeModulesExists('eslint', true);
|
|
443
|
+
const result = isPackageInstalled(testDir, 'eslint');
|
|
444
|
+
expect(result).toBe(true);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
175
447
|
});
|
package/lib/commonOpts.js
CHANGED
|
@@ -149,16 +149,13 @@ export function setCLILogLevel(options) {
|
|
|
149
149
|
setLogLevel(LOG_LEVEL.ERROR);
|
|
150
150
|
SpinniesManager.setDisableOutput(true);
|
|
151
151
|
}
|
|
152
|
-
else if (debug) {
|
|
152
|
+
else if (debug || networkDebug) {
|
|
153
153
|
setLogLevel(LOG_LEVEL.DEBUG);
|
|
154
|
+
process.env.HUBSPOT_NETWORK_LOGGING = 'true';
|
|
154
155
|
}
|
|
155
156
|
else {
|
|
156
157
|
setLogLevel(LOG_LEVEL.LOG);
|
|
157
158
|
}
|
|
158
|
-
if (networkDebug) {
|
|
159
|
-
process.env.HUBSPOT_NETWORK_LOGGING = 'true';
|
|
160
|
-
setLogLevel(LOG_LEVEL.DEBUG);
|
|
161
|
-
}
|
|
162
159
|
}
|
|
163
160
|
export function getCommandName(argv) {
|
|
164
161
|
return String(argv && argv._ && argv._[0]) || '';
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
export declare function installPackages({ packages, installLocations, }: {
|
|
1
|
+
export declare function installPackages({ packages, installLocations, dev, }: {
|
|
2
2
|
packages?: string[];
|
|
3
3
|
installLocations?: string[];
|
|
4
|
+
dev?: boolean;
|
|
4
5
|
}): Promise<void>;
|
|
5
|
-
export declare function
|
|
6
|
+
export declare function updatePackages({ packages, installLocations, }: {
|
|
7
|
+
packages?: string[];
|
|
8
|
+
installLocations?: string[];
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
export declare function getProjectPackageJsonLocations(dir?: string, isUpdate?: boolean): Promise<string[]>;
|
|
11
|
+
export declare function isPackageInstalled(directory: string, packageName: string): boolean;
|
|
6
12
|
export declare function hasMissingPackages(directory: string): Promise<boolean>;
|
|
@@ -6,19 +6,21 @@ import { walk } from '@hubspot/local-dev-lib/fs';
|
|
|
6
6
|
import { getProjectConfig } from './projects/config.js';
|
|
7
7
|
import { commands } from '../lang/en.js';
|
|
8
8
|
import SpinniesManager from './ui/SpinniesManager.js';
|
|
9
|
-
import { isGloballyInstalled, executeInstall, DEFAULT_PACKAGE_MANAGER, } from './npm.js';
|
|
9
|
+
import { isGloballyInstalled, executeInstall, executeUpdate, DEFAULT_PACKAGE_MANAGER, } from './npm.js';
|
|
10
10
|
class NoPackageJsonFilesError extends Error {
|
|
11
|
-
constructor(projectName) {
|
|
12
|
-
super(
|
|
11
|
+
constructor(projectName, isUpdate = false) {
|
|
12
|
+
super(isUpdate
|
|
13
|
+
? commands.project.updateDeps.noPackageJsonInProject(projectName)
|
|
14
|
+
: commands.project.installDeps.noPackageJsonInProject(projectName));
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
|
-
export async function installPackages({ packages, installLocations, }) {
|
|
17
|
+
export async function installPackages({ packages, installLocations, dev = false, }) {
|
|
16
18
|
const installDirs = installLocations || (await getProjectPackageJsonLocations());
|
|
17
19
|
await Promise.all(installDirs.map(async (dir) => {
|
|
18
|
-
await installPackagesInDirectory(dir, packages);
|
|
20
|
+
await installPackagesInDirectory(dir, packages, dev);
|
|
19
21
|
}));
|
|
20
22
|
}
|
|
21
|
-
async function installPackagesInDirectory(directory, packages) {
|
|
23
|
+
async function installPackagesInDirectory(directory, packages, dev = false) {
|
|
22
24
|
const spinner = `installingDependencies-${directory}`;
|
|
23
25
|
const relativeDir = path.relative(process.cwd(), directory);
|
|
24
26
|
SpinniesManager.init();
|
|
@@ -28,7 +30,8 @@ async function installPackagesInDirectory(directory, packages) {
|
|
|
28
30
|
: commands.project.installDeps.installingDependencies(relativeDir),
|
|
29
31
|
});
|
|
30
32
|
try {
|
|
31
|
-
|
|
33
|
+
const flags = dev && packages && packages.length > 0 ? '--save-dev' : null;
|
|
34
|
+
await executeInstall(packages, flags, { cwd: directory });
|
|
32
35
|
SpinniesManager.succeed(spinner, {
|
|
33
36
|
text: commands.project.installDeps.installationSuccessful(relativeDir),
|
|
34
37
|
});
|
|
@@ -42,26 +45,60 @@ async function installPackagesInDirectory(directory, packages) {
|
|
|
42
45
|
});
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
|
-
export async function
|
|
48
|
+
export async function updatePackages({ packages, installLocations, }) {
|
|
49
|
+
const installDirs = installLocations || (await getProjectPackageJsonLocations(undefined, true));
|
|
50
|
+
await Promise.all(installDirs.map(async (dir) => {
|
|
51
|
+
await updatePackagesInDirectory(dir, packages);
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
async function updatePackagesInDirectory(directory, packages) {
|
|
55
|
+
const spinner = `updatingDependencies-${directory}`;
|
|
56
|
+
const relativeDir = path.relative(process.cwd(), directory);
|
|
57
|
+
SpinniesManager.init();
|
|
58
|
+
SpinniesManager.add(spinner, {
|
|
59
|
+
text: packages && packages.length
|
|
60
|
+
? commands.project.updateDeps.updatingDependenciesToLocation(`[${packages.join(', ')}]`, relativeDir)
|
|
61
|
+
: commands.project.updateDeps.updatingDependencies(relativeDir),
|
|
62
|
+
});
|
|
63
|
+
try {
|
|
64
|
+
await executeUpdate(packages, null, { cwd: directory });
|
|
65
|
+
SpinniesManager.succeed(spinner, {
|
|
66
|
+
text: commands.project.updateDeps.updateSuccessful(relativeDir),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
SpinniesManager.fail(spinner, {
|
|
71
|
+
text: commands.project.updateDeps.updatingDependenciesFailed(relativeDir),
|
|
72
|
+
});
|
|
73
|
+
throw new Error(commands.project.updateDeps.updatingDependenciesFailed(relativeDir), {
|
|
74
|
+
cause: e,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function getProjectPackageJsonLocations(dir, isUpdate = false) {
|
|
46
79
|
const projectConfig = await getProjectConfig(dir);
|
|
47
80
|
if (!projectConfig ||
|
|
48
81
|
!projectConfig.projectDir ||
|
|
49
82
|
!projectConfig.projectConfig) {
|
|
50
|
-
throw new Error(
|
|
83
|
+
throw new Error(isUpdate
|
|
84
|
+
? commands.project.updateDeps.noProjectConfig
|
|
85
|
+
: commands.project.installDeps.noProjectConfig);
|
|
51
86
|
}
|
|
52
87
|
const { projectDir, projectConfig: { srcDir, name }, } = projectConfig;
|
|
53
88
|
if (!(await isGloballyInstalled(DEFAULT_PACKAGE_MANAGER))) {
|
|
54
|
-
throw new Error(
|
|
89
|
+
throw new Error(isUpdate
|
|
90
|
+
? commands.project.updateDeps.packageManagerNotInstalled(DEFAULT_PACKAGE_MANAGER)
|
|
91
|
+
: commands.project.installDeps.packageManagerNotInstalled(DEFAULT_PACKAGE_MANAGER));
|
|
55
92
|
}
|
|
56
93
|
if (!fs.existsSync(projectConfig.projectDir) ||
|
|
57
94
|
!fs.existsSync(path.join(projectDir, srcDir))) {
|
|
58
|
-
throw new NoPackageJsonFilesError(name);
|
|
95
|
+
throw new NoPackageJsonFilesError(name, isUpdate);
|
|
59
96
|
}
|
|
60
97
|
const packageJsonFiles = (await walk(path.join(projectDir, srcDir))).filter(file => file.includes('package.json') &&
|
|
61
98
|
!file.includes('node_modules') &&
|
|
62
99
|
!file.includes('.vite'));
|
|
63
100
|
if (packageJsonFiles.length === 0) {
|
|
64
|
-
throw new NoPackageJsonFilesError(name);
|
|
101
|
+
throw new NoPackageJsonFilesError(name, isUpdate);
|
|
65
102
|
}
|
|
66
103
|
const packageParentDirs = [];
|
|
67
104
|
packageJsonFiles.forEach(packageJsonFile => {
|
|
@@ -70,6 +107,32 @@ export async function getProjectPackageJsonLocations(dir) {
|
|
|
70
107
|
});
|
|
71
108
|
return packageParentDirs;
|
|
72
109
|
}
|
|
110
|
+
function isPackageInPackageJson(directory, packageName) {
|
|
111
|
+
const packageJsonPath = path.join(directory, 'package.json');
|
|
112
|
+
try {
|
|
113
|
+
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
114
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
115
|
+
return !!((packageJson.dependencies && packageJson.dependencies[packageName]) ||
|
|
116
|
+
(packageJson.devDependencies && packageJson.devDependencies[packageName]));
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function isPackageInNodeModules(directory, packageName) {
|
|
123
|
+
const packagePath = path.join(directory, 'node_modules', packageName);
|
|
124
|
+
try {
|
|
125
|
+
return fs.existsSync(packagePath);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export function isPackageInstalled(directory, packageName) {
|
|
132
|
+
const inPackageJson = isPackageInPackageJson(directory, packageName);
|
|
133
|
+
const actuallyInstalled = isPackageInNodeModules(directory, packageName);
|
|
134
|
+
return inPackageJson && actuallyInstalled;
|
|
135
|
+
}
|
|
73
136
|
export async function hasMissingPackages(directory) {
|
|
74
137
|
const exec = util.promisify(execAsync);
|
|
75
138
|
const { stdout } = await exec(`npm install --ignore-scripts --dry-run`, {
|
package/lib/npm.d.ts
CHANGED
|
@@ -7,3 +7,6 @@ export declare function getLatestCliVersion(): Promise<{
|
|
|
7
7
|
export declare function executeInstall(packages?: string[], flags?: string | null, options?: {
|
|
8
8
|
cwd?: string;
|
|
9
9
|
}): Promise<void>;
|
|
10
|
+
export declare function executeUpdate(packages?: string[], flags?: string | null, options?: {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
}): Promise<void>;
|
package/lib/npm.js
CHANGED
|
@@ -25,3 +25,9 @@ export async function executeInstall(packages = [], flags, options) {
|
|
|
25
25
|
const exec = util.promisify(execAsync);
|
|
26
26
|
await exec(installCommand, options);
|
|
27
27
|
}
|
|
28
|
+
export async function executeUpdate(packages = [], flags, options) {
|
|
29
|
+
const updateCommand = `${DEFAULT_PACKAGE_MANAGER} update${flags ? ` ${flags}` : ''} ${packages.join(' ')}`;
|
|
30
|
+
uiLogger.debug('Running', updateCommand);
|
|
31
|
+
const exec = util.promisify(execAsync);
|
|
32
|
+
await exec(updateCommand, options);
|
|
33
|
+
}
|
|
@@ -8,10 +8,14 @@ describe('platformVersion', () => {
|
|
|
8
8
|
expect(isV2Project('2025.2')).toBe(true);
|
|
9
9
|
});
|
|
10
10
|
it('returns true if platform version is greater than the minimum', () => {
|
|
11
|
-
expect(isV2Project('
|
|
11
|
+
expect(isV2Project('2025.3')).toBe(true);
|
|
12
|
+
expect(isV2Project('2026.03')).toBe(true);
|
|
13
|
+
expect(isV2Project('2026.03-beta')).toBe(true);
|
|
12
14
|
});
|
|
13
15
|
it('returns false if platform version is less than the minimum', () => {
|
|
14
16
|
expect(isV2Project('2025.0')).toBe(false);
|
|
17
|
+
expect(isV2Project('2025.01')).toBe(false);
|
|
18
|
+
expect(isV2Project('2025.01-beta')).toBe(false);
|
|
15
19
|
});
|
|
16
20
|
it('returns false if platform version is invalid', () => {
|
|
17
21
|
expect(isV2Project(null)).toBe(false);
|
|
@@ -5,6 +5,6 @@ export function isV2Project(platformVersion) {
|
|
|
5
5
|
if (platformVersion.toLowerCase() === 'unstable') {
|
|
6
6
|
return true;
|
|
7
7
|
}
|
|
8
|
-
const [year, minor] = platformVersion.split(
|
|
8
|
+
const [year, minor] = platformVersion.split(/[.-]/);
|
|
9
9
|
return Number(year) >= 2025 && Number(minor) >= 2;
|
|
10
10
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cli",
|
|
3
|
-
"version": "7.9.0-
|
|
3
|
+
"version": "7.9.0-experimental.0",
|
|
4
4
|
"description": "The official CLI for developing on HubSpot",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": "https://github.com/HubSpot/hubspot-cli",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@hubspot/local-dev-lib": "3.21.
|
|
9
|
+
"@hubspot/local-dev-lib": "3.21.2",
|
|
10
10
|
"@hubspot/project-parsing-lib": "0.10.1",
|
|
11
11
|
"@hubspot/serverless-dev-runtime": "7.0.6",
|
|
12
12
|
"@hubspot/theme-preview-dev-server": "0.0.10",
|