@hubspot/cli 5.3.1 → 5.4.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/bin/cli.js +24 -5
  2. package/commands/__tests__/projects.test.js +105 -0
  3. package/commands/accounts/clean.js +1 -1
  4. package/commands/cms/convertFields.js +13 -7
  5. package/commands/project/__tests__/deploy.test.js +1 -1
  6. package/commands/project/__tests__/installDeps.test.js +168 -0
  7. package/commands/project/__tests__/logs.test.js +305 -0
  8. package/commands/project/add.js +24 -12
  9. package/commands/project/cloneApp.js +13 -21
  10. package/commands/project/deploy.js +4 -1
  11. package/commands/project/dev.js +22 -11
  12. package/commands/project/download.js +6 -3
  13. package/commands/project/installDeps.js +78 -0
  14. package/commands/project/logs.js +80 -242
  15. package/commands/project/migrateApp.js +8 -9
  16. package/commands/project/upload.js +5 -3
  17. package/commands/project/watch.js +3 -9
  18. package/commands/project.js +2 -0
  19. package/commands/sandbox/create.js +1 -0
  20. package/commands/sandbox.js +0 -2
  21. package/lang/en.lyaml +40 -75
  22. package/lib/LocalDevManager.js +1 -22
  23. package/lib/__tests__/dependencyManagement.test.js +245 -0
  24. package/lib/__tests__/projectLogsManager.test.js +210 -0
  25. package/lib/dependencyManagement.js +157 -0
  26. package/lib/errorHandlers/apiErrors.js +1 -3
  27. package/lib/errorHandlers/overrideErrors.js +57 -36
  28. package/lib/localDev.js +25 -16
  29. package/lib/projectLogsManager.js +144 -0
  30. package/lib/projects.js +17 -7
  31. package/lib/projectsWatch.js +2 -5
  32. package/lib/prompts/__tests__/projectsLogsPrompt.test.js +46 -0
  33. package/lib/prompts/createProjectPrompt.js +4 -0
  34. package/lib/prompts/projectAddPrompt.js +4 -21
  35. package/lib/prompts/projectDevTargetAccountPrompt.js +16 -25
  36. package/lib/prompts/projectsLogsPrompt.js +17 -108
  37. package/lib/sandboxSync.js +13 -15
  38. package/package.json +6 -6
  39. package/commands/sandbox/sync.js +0 -225
package/bin/cli.js CHANGED
@@ -15,6 +15,8 @@ const {
15
15
  const { getIsInProject } = require('../lib/projects');
16
16
  const pkg = require('../package.json');
17
17
  const { i18n } = require('../lib/lang');
18
+ const { EXIT_CODES } = require('../lib/enums/exitCodes');
19
+ const { UI_COLORS, uiCommandReference } = require('../lib/ui');
18
20
 
19
21
  const removeCommand = require('../commands/remove');
20
22
  const initCommand = require('../commands/init');
@@ -41,7 +43,6 @@ const accountsCommand = require('../commands/accounts');
41
43
  const sandboxesCommand = require('../commands/sandbox');
42
44
  const cmsCommand = require('../commands/cms');
43
45
  const feedbackCommand = require('../commands/feedback');
44
- const { EXIT_CODES } = require('../lib/enums/exitCodes');
45
46
 
46
47
  const notifier = updateNotifier({
47
48
  pkg: { ...pkg, name: '@hubspot/cli' },
@@ -51,12 +52,30 @@ const notifier = updateNotifier({
51
52
 
52
53
  const i18nKey = 'commands.generalErrors';
53
54
 
54
- const CLI_UPGRADE_MESSAGE =
55
- chalk.bold('The CMS CLI is now the HubSpot CLI') +
56
- '\n\nTo upgrade, run:\n\nnpm uninstall -g @hubspot/cms-cli\nand npm install -g @hubspot/cli';
55
+ const CMS_CLI_PACKAGE_NAME = '@hubspot/cms-cli';
57
56
 
58
57
  notifier.notify({
59
- message: pkg.name === '@hubspot/cms-cli' ? CLI_UPGRADE_MESSAGE : null,
58
+ message:
59
+ pkg.name === CMS_CLI_PACKAGE_NAME
60
+ ? i18n(`${i18nKey}.updateNotify.cmsUpdateNotification`, {
61
+ packageName: CMS_CLI_PACKAGE_NAME,
62
+ updateCommand: uiCommandReference('{updateCommand}'),
63
+ })
64
+ : i18n(`${i18nKey}.updateNotify.cliUpdateNotification`, {
65
+ updateCommand: uiCommandReference('{updateCommand}'),
66
+ }),
67
+ defer: false,
68
+ boxenOptions: {
69
+ borderColor: UI_COLORS.MARIGOLD_DARK,
70
+ margin: 1,
71
+ padding: 1,
72
+ textAlignment: 'center',
73
+ borderStyle: 'round',
74
+ title:
75
+ pkg.name === CMS_CLI_PACKAGE_NAME
76
+ ? null
77
+ : chalk.bold(i18n(`${i18nKey}.updateNotify.notifyTitle`)),
78
+ },
60
79
  });
61
80
 
62
81
  const getTerminalWidth = () => {
@@ -0,0 +1,105 @@
1
+ const { command, describe: projectDescribe, builder } = require('../project');
2
+
3
+ jest.mock('../project/deploy');
4
+ jest.mock('../project/create');
5
+ jest.mock('../project/upload');
6
+ jest.mock('../project/listBuilds');
7
+ jest.mock('../project/logs');
8
+ jest.mock('../project/watch');
9
+ jest.mock('../project/download');
10
+ jest.mock('../project/open');
11
+ jest.mock('../project/dev');
12
+ jest.mock('../project/add');
13
+ jest.mock('../project/migrateApp');
14
+ jest.mock('../project/cloneApp');
15
+ jest.mock('../project/installDeps');
16
+ jest.mock('../../lib/commonOpts');
17
+
18
+ const deploy = require('../project/deploy');
19
+ const create = require('../project/create');
20
+ const upload = require('../project/upload');
21
+ const listBuilds = require('../project/listBuilds');
22
+ const logs = require('../project/logs');
23
+ const watch = require('../project/watch');
24
+ const download = require('../project/download');
25
+ const open = require('../project/open');
26
+ const dev = require('../project/dev');
27
+ const add = require('../project/add');
28
+ const migrateApp = require('../project/migrateApp');
29
+ const cloneApp = require('../project/cloneApp');
30
+ const installDeps = require('../project/installDeps');
31
+ const { addConfigOptions, addAccountOptions } = require('../../lib/commonOpts');
32
+
33
+ describe('commands/projects', () => {
34
+ describe('command', () => {
35
+ it('should have the correct command structure', () => {
36
+ expect(command).toEqual('project');
37
+ });
38
+ });
39
+
40
+ describe('describe', () => {
41
+ it('should contain the beta tag', () => {
42
+ expect(projectDescribe).toContain('[BETA]');
43
+ });
44
+ it('should provide an accurate description of what the command is doing', () => {
45
+ expect(projectDescribe).toContain(
46
+ 'Commands for working with projects. For more information, visit our documentation: https://developers.hubspot.com/docs/platform/build-and-deploy-using-hubspot-projects'
47
+ );
48
+ });
49
+ });
50
+
51
+ describe('builder', () => {
52
+ let yargs;
53
+
54
+ const subcommands = [
55
+ ['create', create],
56
+ ['add', add],
57
+ ['watch', watch],
58
+ ['dev', dev],
59
+ ['upload', upload],
60
+ ['deploy', deploy],
61
+ ['logs', logs],
62
+ ['listBuilds', listBuilds],
63
+ ['download', download],
64
+ ['open', open],
65
+ ['migrateApp', migrateApp],
66
+ ['cloneApp', cloneApp],
67
+ ['installDeps', installDeps],
68
+ ];
69
+
70
+ beforeEach(() => {
71
+ yargs = {
72
+ command: jest.fn().mockImplementation(() => yargs),
73
+ demandCommand: jest.fn().mockImplementation(() => yargs),
74
+ };
75
+ });
76
+
77
+ it('should add the config options', () => {
78
+ builder(yargs);
79
+ expect(addConfigOptions).toHaveBeenCalledTimes(1);
80
+ expect(addConfigOptions).toHaveBeenCalledWith(yargs);
81
+ });
82
+
83
+ it('should add the account options', () => {
84
+ builder(yargs);
85
+ expect(addAccountOptions).toHaveBeenCalledTimes(1);
86
+ expect(addAccountOptions).toHaveBeenCalledWith(yargs);
87
+ });
88
+
89
+ it('should add the correct number of sub commands', () => {
90
+ builder(yargs);
91
+ expect(yargs.command).toHaveBeenCalledTimes(subcommands.length);
92
+ });
93
+
94
+ it.each(subcommands)('should attach the %s subcommand', (name, module) => {
95
+ builder(yargs);
96
+ expect(yargs.command).toHaveBeenCalledWith(module);
97
+ });
98
+
99
+ it('should demand the command takes one positional argument', () => {
100
+ builder(yargs);
101
+ expect(yargs.demandCommand).toHaveBeenCalledTimes(1);
102
+ expect(yargs.demandCommand).toHaveBeenCalledWith(1, '');
103
+ });
104
+ });
105
+ });
@@ -54,7 +54,7 @@ exports.handler = async options => {
54
54
 
55
55
  for (const account of filteredTestAccounts) {
56
56
  try {
57
- await accessTokenForPersonalAccessKey(account.portalId);
57
+ await accessTokenForPersonalAccessKey(account.portalId, true);
58
58
  } catch (error) {
59
59
  if (
60
60
  isSpecifiedHubSpotAuthError(error, {
@@ -13,6 +13,7 @@ const {
13
13
 
14
14
  const { trackConvertFieldsUsage } = require('../../lib/usageTracking');
15
15
  const { logErrorInstance } = require('../../lib/errorHandlers/standardErrors');
16
+ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
16
17
  const i18nKey = 'commands.convertFields';
17
18
 
18
19
  exports.command = 'convert-fields';
@@ -24,23 +25,27 @@ const invalidPath = src => {
24
25
  path: src,
25
26
  })
26
27
  );
28
+ process.exit(EXIT_CODES.ERROR);
27
29
  };
28
30
 
29
31
  exports.handler = async options => {
30
- const src = path.resolve(getCwd(), options.src);
31
- const themeJSONPath = getThemeJSONPath(src);
32
- const projectRoot = themeJSONPath
33
- ? path.dirname(themeJSONPath)
34
- : path.dirname(getCwd());
35
32
  let stats;
33
+ let projectRoot;
34
+ let src;
35
+
36
36
  try {
37
+ src = path.resolve(getCwd(), options.src);
38
+ const themeJSONPath = getThemeJSONPath(options.src);
39
+ projectRoot = themeJSONPath
40
+ ? path.dirname(themeJSONPath)
41
+ : path.dirname(getCwd());
37
42
  stats = fs.statSync(src);
38
43
  if (!stats.isFile() && !stats.isDirectory()) {
39
- invalidPath(src);
44
+ invalidPath(options.src);
40
45
  return;
41
46
  }
42
47
  } catch (e) {
43
- invalidPath(src);
48
+ invalidPath(options.src);
44
49
  }
45
50
 
46
51
  trackConvertFieldsUsage('process');
@@ -88,6 +93,7 @@ exports.builder = yargs => {
88
93
  yargs.option('src', {
89
94
  describe: i18n(`${i18nKey}.positionals.src.describe`),
90
95
  type: 'string',
96
+ demandOption: i18n(`${i18nKey}.errors.missingSrc`),
91
97
  });
92
98
  yargs.option('fieldOptions', {
93
99
  describe: i18n(`${i18nKey}.options.options.describe`),
@@ -423,7 +423,7 @@ describe('commands/project/deploy', () => {
423
423
 
424
424
  expect(logger.error).toHaveBeenCalledTimes(1);
425
425
  expect(logger.error).toHaveBeenCalledWith(
426
- `The request in account ${accountId} failed due to a client error.`
426
+ `The request for 'project deploy' in account ${accountId} failed due to a client error.`
427
427
  );
428
428
  expect(processExitSpy).toHaveBeenCalledTimes(1);
429
429
  expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
@@ -0,0 +1,168 @@
1
+ jest.mock('../../../lib/projects');
2
+ jest.mock('@hubspot/local-dev-lib/logger');
3
+ jest.mock('../../../lib/dependencyManagement');
4
+ jest.mock('../../../lib/prompts/promptUtils');
5
+ jest.mock('../../../lib/usageTracking');
6
+ jest.mock('../../../lib/commonOpts');
7
+
8
+ const { getProjectConfig } = require('../../../lib/projects');
9
+ const { EXIT_CODES } = require('../../../lib/enums/exitCodes');
10
+ const { logger } = require('@hubspot/local-dev-lib/logger');
11
+ const { trackCommandUsage } = require('../../../lib/usageTracking');
12
+ const { getAccountId } = require('../../../lib/commonOpts');
13
+ const {
14
+ installPackages,
15
+ getProjectPackageJsonLocations,
16
+ } = require('../../../lib/dependencyManagement');
17
+ const { promptUser } = require('../../../lib/prompts/promptUtils');
18
+ const {
19
+ command,
20
+ describe: installDepsDescribe,
21
+ builder,
22
+ handler,
23
+ } = require('../installDeps');
24
+ const path = require('path');
25
+
26
+ describe('commands/project/installDeps', () => {
27
+ describe('command', () => {
28
+ it('should have the correct command string', () => {
29
+ expect(command).toEqual('install-deps [packages..]');
30
+ });
31
+ });
32
+
33
+ describe('describe', () => {
34
+ it('should have the correct description', () => {
35
+ expect(installDepsDescribe).toEqual(null);
36
+ });
37
+ });
38
+
39
+ describe('builder', () => {
40
+ let yargs;
41
+ beforeEach(() => {
42
+ yargs = {
43
+ example: jest.fn().mockImplementation(() => yargs),
44
+ };
45
+ });
46
+
47
+ it('should add correct examples', () => {
48
+ builder(yargs);
49
+ expect(yargs.example).toHaveBeenCalledTimes(1);
50
+ expect(yargs.example).toHaveBeenCalledWith([
51
+ ['$0 project install-deps', 'Install the dependencies for the project'],
52
+ [
53
+ '$0 project install-deps dependency1 dependency2',
54
+ 'Install the dependencies to one or more project subcomponents',
55
+ ],
56
+ ]);
57
+ });
58
+ });
59
+
60
+ describe('handler', () => {
61
+ let processExitSpy;
62
+
63
+ beforeEach(() => {
64
+ processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
65
+ });
66
+
67
+ it('should track the command usage', async () => {
68
+ const accountId = 999999;
69
+ getAccountId.mockReturnValue(accountId);
70
+ await handler({});
71
+
72
+ expect(getAccountId).toHaveBeenCalledTimes(1);
73
+ expect(trackCommandUsage).toHaveBeenCalledTimes(1);
74
+ expect(trackCommandUsage).toHaveBeenCalledWith(
75
+ 'project-install-deps',
76
+ null,
77
+ accountId
78
+ );
79
+ });
80
+
81
+ it('should handle exceptions', async () => {
82
+ const error = new Error('Something went super wrong');
83
+
84
+ getProjectConfig.mockImplementationOnce(() => {
85
+ throw error;
86
+ });
87
+
88
+ await handler({});
89
+
90
+ expect(logger.debug).toHaveBeenCalledTimes(1);
91
+ expect(logger.debug).toHaveBeenCalledWith(error);
92
+
93
+ expect(logger.error).toHaveBeenCalledTimes(1);
94
+ expect(logger.error).toHaveBeenCalledWith(error.message);
95
+
96
+ expect(processExitSpy).toHaveBeenCalledTimes(1);
97
+ expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
98
+ });
99
+
100
+ it('should log an error and exit when the project config is not defined', async () => {
101
+ getProjectConfig.mockResolvedValueOnce(null);
102
+ await handler({});
103
+
104
+ expect(logger.error).toHaveBeenCalledTimes(1);
105
+ expect(logger.error).toHaveBeenCalledWith(
106
+ 'No project detected. Run this command from a project directory.'
107
+ );
108
+ expect(processExitSpy).toHaveBeenCalledTimes(1);
109
+ expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
110
+ });
111
+
112
+ it('should log an error and exit when the project config has no projectDir', async () => {
113
+ getProjectConfig.mockResolvedValueOnce({ projectDir: null });
114
+ await handler({});
115
+
116
+ expect(logger.error).toHaveBeenCalledTimes(1);
117
+ expect(logger.error).toHaveBeenCalledWith(
118
+ 'No project detected. Run this command from a project directory.'
119
+ );
120
+ expect(processExitSpy).toHaveBeenCalledTimes(1);
121
+ expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
122
+ });
123
+
124
+ it('should prompt for input when packages is defined', async () => {
125
+ const projectDir = 'src';
126
+ getProjectConfig.mockResolvedValue({ projectDir });
127
+ const packageJsonLocation = path.join(projectDir, 'directory1');
128
+ promptUser.mockResolvedValueOnce(packageJsonLocation);
129
+ getProjectPackageJsonLocations.mockResolvedValue([packageJsonLocation]);
130
+ await handler({ packages: ['@hubspot/local-dev-lib'] });
131
+ expect(getProjectPackageJsonLocations).toHaveBeenCalledTimes(1);
132
+ expect(promptUser).toHaveBeenCalledTimes(1);
133
+ expect(promptUser).toHaveBeenCalledWith([
134
+ {
135
+ name: 'selectedInstallLocations',
136
+ type: 'checkbox',
137
+ when: expect.any(Function),
138
+ choices: [
139
+ {
140
+ name: 'directory1',
141
+ value: packageJsonLocation,
142
+ },
143
+ ],
144
+ message: 'Choose the project components to install the dependencies:',
145
+ validate: expect.any(Function),
146
+ },
147
+ ]);
148
+ });
149
+
150
+ it('should call installPackages correctly', async () => {
151
+ const projectDir = 'src';
152
+ const packageJsonLocation = path.join(projectDir, 'directory1');
153
+ const installLocations = [packageJsonLocation];
154
+ const packages = ['@hubspot/local-dev-lib'];
155
+
156
+ getProjectConfig.mockResolvedValue({ projectDir });
157
+ promptUser.mockResolvedValueOnce(packageJsonLocation);
158
+ getProjectPackageJsonLocations.mockResolvedValue(installLocations);
159
+ await handler({ packages });
160
+
161
+ expect(installPackages).toHaveBeenCalledTimes(1);
162
+ expect(installPackages).toHaveBeenCalledWith({
163
+ packages,
164
+ installLocations,
165
+ });
166
+ });
167
+ });
168
+ });