@hubspot/cli 4.0.1-beta.3 → 4.0.1

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/bin/cli.js CHANGED
@@ -35,6 +35,7 @@ const moduleCommand = require('../commands/module');
35
35
  const configCommand = require('../commands/config');
36
36
  const accountsCommand = require('../commands/accounts');
37
37
  const sandboxesCommand = require('../commands/sandbox');
38
+ const cmsCommand = require('../commands/cms');
38
39
  const { EXIT_CODES } = require('../lib/enums/exitCodes');
39
40
 
40
41
  const notifier = updateNotifier({ pkg: { ...pkg, name: '@hubspot/cli' } });
@@ -129,6 +130,7 @@ const argv = yargs
129
130
  .command(authCommand)
130
131
  .command(initCommand)
131
132
  .command(logsCommand)
133
+ .command(cmsCommand)
132
134
  .command(lintCommand)
133
135
  .command(hubdbCommand)
134
136
  .command(watchCommand)
@@ -5,47 +5,24 @@ const {
5
5
  updateDefaultAccount,
6
6
  getAccountId: getAccountIdFromConfig,
7
7
  } = require('@hubspot/cli-lib/lib/config');
8
- const { loadAndValidateOptions } = require('../../lib/validation');
9
8
 
10
- const { getAccountId } = require('../../lib/commonOpts');
11
9
  const { trackCommandUsage } = require('../../lib/usageTracking');
12
- const { promptUser } = require('../../lib/prompts/promptUtils');
13
10
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
11
+ const { selectAccountFromConfig } = require('../../lib/prompts/accountsPrompt');
12
+ const { loadAndValidateOptions } = require('../../lib/validation');
14
13
 
15
14
  const i18nKey = 'cli.commands.accounts.subcommands.use';
16
15
 
17
- const selectAccountFromConfig = async config => {
18
- const { default: selectedDefault } = await promptUser([
19
- {
20
- type: 'list',
21
- look: false,
22
- name: 'default',
23
- pageSize: 20,
24
- message: i18n(`${i18nKey}.promptMessage`),
25
- choices: config.portals.map(p => ({
26
- name: `${p.name} (${p.portalId})`,
27
- value: p.name || p.portalId,
28
- })),
29
- default: config.defaultPortal,
30
- },
31
- ]);
32
-
33
- return selectedDefault;
34
- };
35
-
36
16
  exports.command = 'use [--account]';
37
17
  exports.describe = i18n(`${i18nKey}.describe`);
38
18
 
39
19
  exports.handler = async options => {
40
- await loadAndValidateOptions({ debug: options.debug });
20
+ await loadAndValidateOptions(options, false);
41
21
 
42
- const accountId = getAccountId(options);
43
22
  const config = getConfig();
44
23
 
45
24
  let newDefaultAccount = options.account;
46
25
 
47
- trackCommandUsage('accounts-use', {}, accountId);
48
-
49
26
  if (!newDefaultAccount) {
50
27
  newDefaultAccount = await selectAccountFromConfig(config);
51
28
  } else if (!getAccountIdFromConfig(newDefaultAccount)) {
@@ -58,6 +35,12 @@ exports.handler = async options => {
58
35
  newDefaultAccount = await selectAccountFromConfig(config);
59
36
  }
60
37
 
38
+ trackCommandUsage(
39
+ 'accounts-use',
40
+ {},
41
+ getAccountIdFromConfig(newDefaultAccount)
42
+ );
43
+
61
44
  updateDefaultAccount(newDefaultAccount);
62
45
 
63
46
  return logger.success(
@@ -0,0 +1,325 @@
1
+ //const chalk = require('chalk');
2
+ const Spinnies = require('spinnies');
3
+ const {
4
+ addAccountOptions,
5
+ addConfigOptions,
6
+ addUseEnvironmentOptions,
7
+ getAccountId,
8
+ } = require('../../lib/commonOpts');
9
+ const { logger } = require('@hubspot/cli-lib/logger');
10
+ const {
11
+ getTableContents,
12
+ getTableHeader,
13
+ } = require('@hubspot/cli-lib/lib/table');
14
+ const { loadAndValidateOptions } = require('../../lib/validation');
15
+ const { promptUser } = require('../../lib/prompts/promptUtils');
16
+ const { i18n } = require('@hubspot/cli-lib/lib/lang');
17
+ const { fetchThemes } = require('@hubspot/cli-lib/api/designManager');
18
+ const {
19
+ requestLighthouseScore,
20
+ getLighthouseScoreStatus,
21
+ getLighthouseScore,
22
+ } = require('@hubspot/cli-lib/api/lighthouseScore');
23
+ const {
24
+ HUBSPOT_FOLDER,
25
+ MARKETPLACE_FOLDER,
26
+ } = require('@hubspot/cli-lib/lib/constants');
27
+ const { uiLink } = require('../../lib/ui');
28
+ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
29
+
30
+ const i18nKey = 'cli.commands.cms.subcommands.lighthouseScore';
31
+
32
+ const DEFAULT_TABLE_HEADER = [
33
+ 'Accessibility',
34
+ 'Best practices',
35
+ 'Performace',
36
+ 'PWA',
37
+ 'SEO',
38
+ ];
39
+
40
+ exports.command = 'lighthouse-score [--theme]';
41
+ exports.describe = i18n(`${i18nKey}.describe`);
42
+
43
+ const selectTheme = async accountId => {
44
+ const { theme: selectedTheme } = await promptUser([
45
+ {
46
+ type: 'list',
47
+ look: false,
48
+ name: 'theme',
49
+ message: i18n(`${i18nKey}.info.promptMessage`),
50
+ choices: async () => {
51
+ try {
52
+ const result = await fetchThemes(accountId, {
53
+ limit: 500,
54
+ sorting: 'MOST_USED',
55
+ });
56
+ if (result && result.objects) {
57
+ return result.objects
58
+ .map(({ theme }) => theme.path)
59
+ .filter(
60
+ themePath =>
61
+ !themePath.startsWith(HUBSPOT_FOLDER) &&
62
+ !themePath.startsWith(MARKETPLACE_FOLDER)
63
+ );
64
+ }
65
+ } catch (err) {
66
+ logger.error(i18n(`${i18nKey}.errors.failedToFetchThemes`));
67
+ process.exit(EXIT_CODES.ERROR);
68
+ }
69
+ },
70
+ },
71
+ ]);
72
+
73
+ return selectedTheme;
74
+ };
75
+
76
+ exports.handler = async options => {
77
+ await loadAndValidateOptions(options);
78
+ const accountId = getAccountId(options);
79
+
80
+ const includeDesktopScore = options.target === 'desktop' || !options.verbose;
81
+ const includeMobileScore = options.target === 'mobile' || !options.verbose;
82
+ let themeToCheck = options.theme;
83
+
84
+ if (themeToCheck) {
85
+ let isValidTheme = true;
86
+ try {
87
+ const result = await fetchThemes(accountId, {
88
+ name: encodeURIComponent(themeToCheck),
89
+ });
90
+ isValidTheme = result && result.total;
91
+ } catch (err) {
92
+ isValidTheme = false;
93
+ }
94
+ if (!isValidTheme) {
95
+ logger.error(
96
+ i18n(`${i18nKey}.errors.themeNotFound`, { theme: themeToCheck })
97
+ );
98
+ process.exit(EXIT_CODES.ERROR);
99
+ }
100
+ } else {
101
+ themeToCheck = await selectTheme(accountId);
102
+ logger.log();
103
+ }
104
+
105
+ // Kick off the scoring
106
+ let requestResult;
107
+ try {
108
+ requestResult = await requestLighthouseScore(accountId, {
109
+ themePath: themeToCheck,
110
+ });
111
+ } catch (err) {
112
+ logger.debug(err);
113
+ }
114
+
115
+ if (!requestResult || !requestResult.mobileId || !requestResult.desktopId) {
116
+ logger.error(i18n(`${i18nKey}.errors.failedToGetLighthouseScore`));
117
+ process.exit(EXIT_CODES.ERROR);
118
+ }
119
+
120
+ // Poll till scoring is finished
121
+ try {
122
+ const spinnies = new Spinnies();
123
+
124
+ spinnies.add('lighthouseScore', {
125
+ text: i18n(`${i18nKey}.info.generatingScore`, { theme: themeToCheck }),
126
+ });
127
+
128
+ const checkScoreStatus = async () => {
129
+ const desktopScoreStatus = includeDesktopScore
130
+ ? await getLighthouseScoreStatus(accountId, {
131
+ themeId: requestResult.desktopId,
132
+ })
133
+ : 'COMPLETED';
134
+ const mobileScoreStatus = includeMobileScore
135
+ ? await getLighthouseScoreStatus(accountId, {
136
+ themeId: requestResult.mobileId,
137
+ })
138
+ : 'COMPLETED';
139
+
140
+ if (
141
+ desktopScoreStatus === 'REQUESTED' ||
142
+ mobileScoreStatus === 'REQUESTED'
143
+ ) {
144
+ await new Promise(resolve => setTimeout(resolve, 2000));
145
+ await checkScoreStatus();
146
+ }
147
+ };
148
+
149
+ await checkScoreStatus();
150
+
151
+ spinnies.remove('lighthouseScore');
152
+ } catch (err) {
153
+ logger.debug(err);
154
+ process.exit(EXIT_CODES.ERROR);
155
+ }
156
+
157
+ // Fetch the scoring results
158
+ let desktopScoreResult;
159
+ let mobileScoreResult;
160
+ let verboseViewAverageScoreResult;
161
+ try {
162
+ const params = { isAverage: !options.verbose };
163
+ desktopScoreResult = includeDesktopScore
164
+ ? await getLighthouseScore(accountId, {
165
+ ...params,
166
+ desktopId: requestResult.desktopId,
167
+ })
168
+ : {};
169
+ mobileScoreResult = includeMobileScore
170
+ ? await getLighthouseScore(accountId, {
171
+ ...params,
172
+ mobileId: requestResult.mobileId,
173
+ })
174
+ : {};
175
+ // This is needed to show the average scores above the verbose output
176
+ verboseViewAverageScoreResult = options.verbose
177
+ ? await getLighthouseScore(accountId, {
178
+ ...params,
179
+ isAverage: true,
180
+ desktopId: includeDesktopScore ? requestResult.desktopId : null,
181
+ mobileId: includeMobileScore ? requestResult.mobileId : null,
182
+ })
183
+ : {};
184
+ } catch (err) {
185
+ logger.error(i18n(`${i18nKey}.errors.failedToGetLighthouseScore`));
186
+ process.exit(EXIT_CODES.ERROR);
187
+ }
188
+
189
+ if (options.verbose) {
190
+ logger.log(`${themeToCheck} ${options.target} scores`);
191
+
192
+ const tableHeader = getTableHeader(DEFAULT_TABLE_HEADER);
193
+
194
+ const scores = verboseViewAverageScoreResult.scores
195
+ ? verboseViewAverageScoreResult.scores[0]
196
+ : {};
197
+
198
+ const averageTableData = [
199
+ scores.accessibilityScore,
200
+ scores.bestPracticesScore,
201
+ scores.performanceScore,
202
+ scores.pwaScore,
203
+ scores.seoScore,
204
+ ];
205
+
206
+ logger.log(
207
+ getTableContents([tableHeader, averageTableData], {
208
+ border: { bodyLeft: ' ' },
209
+ })
210
+ );
211
+ logger.log(i18n(`${i18nKey}.info.pageTemplateScoreTitle`));
212
+
213
+ const table2Header = getTableHeader([
214
+ 'Template path',
215
+ ...DEFAULT_TABLE_HEADER,
216
+ ]);
217
+
218
+ const scoreResult =
219
+ options.target === 'desktop' ? desktopScoreResult : mobileScoreResult;
220
+
221
+ const templateTableData = scoreResult.scores.map(score => {
222
+ return [
223
+ score.templatePath,
224
+ score.accessibilityScore,
225
+ score.bestPracticesScore,
226
+ score.performanceScore,
227
+ score.pwaScore,
228
+ score.seoScore,
229
+ ];
230
+ });
231
+
232
+ logger.log(
233
+ getTableContents([table2Header, ...templateTableData], {
234
+ border: { bodyLeft: ' ' },
235
+ })
236
+ );
237
+
238
+ logger.log(i18n(`${i18nKey}.info.lighthouseLinksTitle`));
239
+
240
+ scoreResult.scores.forEach(score => {
241
+ logger.log(' ', uiLink(score.templatePath, score.link));
242
+ });
243
+
244
+ if (scoreResult.failedTemplatePaths.length) {
245
+ logger.log();
246
+ logger.error(i18n(`${i18nKey}.info.failedTemplatePathsTitle`));
247
+ scoreResult.failedTemplatePaths.forEach(failedTemplatePath => {
248
+ logger.log(' ', failedTemplatePath);
249
+ });
250
+ }
251
+
252
+ logger.log();
253
+ logger.info(
254
+ i18n(`${i18nKey}.info.targetDeviceNote`, { target: options.target })
255
+ );
256
+ } else {
257
+ logger.log(`Theme: ${themeToCheck}`);
258
+ const tableHeader = getTableHeader(['Target', ...DEFAULT_TABLE_HEADER]);
259
+
260
+ const getTableData = (target, scoreResult) => {
261
+ const scores = scoreResult.scores ? scoreResult.scores[0] : {};
262
+ return [
263
+ target,
264
+ scores.accessibilityScore,
265
+ scores.bestPracticesScore,
266
+ scores.performanceScore,
267
+ scores.pwaScore,
268
+ scores.seoScore,
269
+ ];
270
+ };
271
+
272
+ const tableData = [
273
+ getTableData('desktop', desktopScoreResult),
274
+ getTableData('mobile', mobileScoreResult),
275
+ ];
276
+
277
+ logger.log(
278
+ getTableContents([tableHeader, ...tableData], {
279
+ border: { bodyLeft: ' ' },
280
+ })
281
+ );
282
+
283
+ logger.info(i18n(`${i18nKey}.info.verboseOptionNote`));
284
+ }
285
+
286
+ logger.log();
287
+ logger.log(
288
+ `Powered by ${uiLink(
289
+ 'Google Lighthouse',
290
+ 'https://developer.chrome.com/docs/lighthouse/overview/'
291
+ )}.`
292
+ );
293
+
294
+ process.exit();
295
+ };
296
+
297
+ exports.builder = yargs => {
298
+ yargs.option('theme', {
299
+ describe: i18n(`${i18nKey}.options.theme.describe`),
300
+ type: 'string',
301
+ });
302
+ yargs.option('target', {
303
+ describe: i18n(`${i18nKey}.options.target.describe`),
304
+ type: 'string',
305
+ choices: ['desktop', 'mobile'],
306
+ default: 'desktop',
307
+ });
308
+ yargs.option('verbose', {
309
+ describe: i18n(`${i18nKey}.options.verbose.describe`),
310
+ boolean: true,
311
+ default: false,
312
+ });
313
+ yargs.example([
314
+ [
315
+ '$0 cms lighthouse-score --theme=my-theme',
316
+ i18n(`${i18nKey}.examples.default`),
317
+ ],
318
+ ]);
319
+
320
+ addConfigOptions(yargs, true);
321
+ addAccountOptions(yargs, true);
322
+ addUseEnvironmentOptions(yargs, true);
323
+
324
+ return yargs;
325
+ };
@@ -0,0 +1,16 @@
1
+ const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts');
2
+ const lighthouseScore = require('./cms/lighthouseScore');
3
+
4
+ // const i18nKey = 'cli.commands.cms';
5
+
6
+ exports.command = 'cms';
7
+ exports.describe = false; // i18n(`${i18nKey}.describe`);
8
+
9
+ exports.builder = yargs => {
10
+ addConfigOptions(yargs, true);
11
+ addAccountOptions(yargs, true);
12
+
13
+ yargs.command(lighthouseScore).demandCommand(1, '');
14
+
15
+ return yargs;
16
+ };
package/commands/fetch.js CHANGED
@@ -76,5 +76,12 @@ exports.builder = yargs => {
76
76
  },
77
77
  });
78
78
 
79
+ yargs.options({
80
+ assetVersion: {
81
+ type: 'number',
82
+ describe: i18n(`${i18nKey}.options.assetVersion.describe`),
83
+ },
84
+ });
85
+
79
86
  return yargs;
80
87
  };
package/commands/init.js CHANGED
@@ -8,6 +8,7 @@ const {
8
8
  } = require('@hubspot/cli-lib/lib/config');
9
9
  const { addConfigOptions } = require('../lib/commonOpts');
10
10
  const { handleExit } = require('@hubspot/cli-lib/lib/process');
11
+ const { checkAndUpdateGitignore } = require('@hubspot/cli-lib/lib/git');
11
12
  const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
12
13
  const {
13
14
  DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME,
@@ -116,6 +117,8 @@ exports.handler = async options => {
116
117
  );
117
118
  const configPath = getConfigPath();
118
119
 
120
+ checkAndUpdateGitignore(configPath);
121
+
119
122
  logger.log('');
120
123
  logger.success(
121
124
  i18n(`${i18nKey}.success.configFileCreated`, {
@@ -7,12 +7,15 @@ const {
7
7
  } = require('../../lib/commonOpts');
8
8
  const { trackCommandUsage } = require('../../lib/usageTracking');
9
9
  const { logger } = require('@hubspot/cli-lib/logger');
10
-
10
+ const Spinnies = require('spinnies');
11
11
  const { createSandbox } = require('@hubspot/cli-lib/sandboxes');
12
12
  const { loadAndValidateOptions } = require('../../lib/validation');
13
13
  const { createSandboxPrompt } = require('../../lib/prompts/sandboxesPrompt');
14
14
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
15
15
  const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
16
+ const {
17
+ debugErrorAndContext,
18
+ } = require('@hubspot/cli-lib/errorHandlers/standardErrors');
16
19
  const {
17
20
  ENVIRONMENTS,
18
21
  PERSONAL_ACCESS_KEY_AUTH_METHOD,
@@ -103,7 +106,7 @@ const personalAccessKeyFlow = async (env, account, name) => {
103
106
  ]);
104
107
  };
105
108
 
106
- exports.command = 'create [name]';
109
+ exports.command = 'create [--name]';
107
110
  exports.describe = i18n(`${i18nKey}.describe`);
108
111
 
109
112
  exports.handler = async options => {
@@ -113,36 +116,49 @@ exports.handler = async options => {
113
116
  const accountId = getAccountId(options);
114
117
  const accountConfig = getAccountConfig(accountId);
115
118
  const env = options.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD;
116
- let namePrompt;
119
+ const spinnies = new Spinnies({
120
+ succeedColor: 'white',
121
+ });
117
122
 
118
123
  trackCommandUsage('sandbox-create', {}, accountId);
119
124
 
125
+ let namePrompt;
126
+
120
127
  if (!name) {
121
128
  namePrompt = await createSandboxPrompt();
122
129
  }
123
130
 
124
131
  const sandboxName = name || namePrompt.name;
125
132
 
126
- logger.debug(
127
- i18n(`${i18nKey}.debug.creating`, {
128
- name: sandboxName,
129
- })
130
- );
131
133
  let result;
134
+
132
135
  try {
133
- result = await createSandbox(accountId, sandboxName).then(
134
- ({ name, sandboxHubId }) => {
135
- logger.log('');
136
- logger.success(
137
- i18n(`${i18nKey}.success.create`, {
138
- name,
139
- sandboxHubId,
140
- })
141
- );
142
- return { name, sandboxHubId };
143
- }
144
- );
136
+ spinnies.add('sandboxCreate', {
137
+ text: i18n(`${i18nKey}.loading.add`, {
138
+ sandboxName,
139
+ }),
140
+ });
141
+
142
+ result = await createSandbox(accountId, sandboxName);
143
+
144
+ logger.log('');
145
+ spinnies.succeed('sandboxCreate', {
146
+ text: i18n(`${i18nKey}.loading.succeed`, {
147
+ name: result.name,
148
+ sandboxHubId: result.sandboxHubId,
149
+ }),
150
+ });
145
151
  } catch (err) {
152
+ debugErrorAndContext(err);
153
+
154
+ trackCommandUsage('sandbox-create', { success: false }, accountId);
155
+
156
+ spinnies.fail('sandboxCreate', {
157
+ text: i18n(`${i18nKey}.loading.fail`, {
158
+ sandboxName,
159
+ }),
160
+ });
161
+
146
162
  if (isMissingScopeError(err)) {
147
163
  logger.error(
148
164
  i18n(`${i18nKey}.failure.scopes.message`, {
@@ -167,17 +183,21 @@ exports.handler = async options => {
167
183
  process.exit(EXIT_CODES.SUCCESS);
168
184
  } catch (err) {
169
185
  logErrorInstance(err);
186
+ process.exit(EXIT_CODES.ERROR);
170
187
  }
171
188
  };
172
189
 
173
190
  exports.builder = yargs => {
174
- yargs.positional('name', {
175
- describe: i18n(`${i18nKey}.positionals.name.describe`),
191
+ yargs.option('name', {
192
+ describe: i18n(`${i18nKey}.options.name.describe`),
176
193
  type: 'string',
177
194
  });
178
195
 
179
196
  yargs.example([
180
- ['$0 sandbox create MySandboxAccount', i18n(`${i18nKey}.examples.default`)],
197
+ [
198
+ '$0 sandbox create --name=MySandboxAccount',
199
+ i18n(`${i18nKey}.examples.default`),
200
+ ],
181
201
  ]);
182
202
 
183
203
  addConfigOptions(yargs, true);
@@ -2,22 +2,157 @@ const {
2
2
  addAccountOptions,
3
3
  addConfigOptions,
4
4
  addUseEnvironmentOptions,
5
+ getAccountId,
6
+ addTestingOptions,
5
7
  } = require('../../lib/commonOpts');
6
8
  const { logger } = require('@hubspot/cli-lib/logger');
7
-
9
+ const { trackCommandUsage } = require('../../lib/usageTracking');
8
10
  const { loadAndValidateOptions } = require('../../lib/validation');
11
+ const {
12
+ debugErrorAndContext,
13
+ } = require('@hubspot/cli-lib/errorHandlers/standardErrors');
14
+
15
+ const { deleteSandbox } = require('@hubspot/cli-lib/sandboxes');
9
16
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
17
+ const { getConfig, getEnv } = require('@hubspot/cli-lib');
18
+ const { deleteSandboxPrompt } = require('../../lib/prompts/sandboxesPrompt');
19
+ const {
20
+ removeSandboxAccountFromConfig,
21
+ } = require('@hubspot/cli-lib/lib/config');
22
+ const {
23
+ selectAndSetAsDefaultAccountPrompt,
24
+ } = require('../../lib/prompts/accountsPrompt');
25
+ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
26
+ const { promptUser } = require('../../lib/prompts/promptUtils');
27
+ const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
28
+ const { ENVIRONMENTS } = require('@hubspot/cli-lib/lib/constants');
10
29
 
11
30
  const i18nKey = 'cli.commands.sandbox.subcommands.delete';
12
31
 
13
- exports.command = 'delete';
32
+ const SANDBOX_NOT_FOUND = 'SandboxErrors.SANDBOX_NOT_FOUND';
33
+ const OBJECT_NOT_FOUND = 'OBJECT_NOT_FOUND';
34
+
35
+ exports.command = 'delete [--account]';
14
36
  exports.describe = i18n(`${i18nKey}.describe`);
15
37
 
16
38
  exports.handler = async options => {
17
- await loadAndValidateOptions(options);
39
+ await loadAndValidateOptions(options, false);
40
+
41
+ const { account } = options;
42
+ const config = getConfig();
43
+
44
+ let accountPrompt;
45
+ if (!account) {
46
+ accountPrompt = await deleteSandboxPrompt(config);
47
+ }
48
+ const sandboxAccountId = getAccountId({
49
+ account: account || accountPrompt.account,
50
+ });
51
+
52
+ trackCommandUsage('sandbox-delete', {}, sandboxAccountId);
53
+
54
+ let parentAccountId;
55
+ for (const portal of config.portals) {
56
+ if (portal.portalId === sandboxAccountId) {
57
+ if (portal.parentAccountId) {
58
+ parentAccountId = portal.parentAccountId;
59
+ } else {
60
+ const parentAccountPrompt = await deleteSandboxPrompt(config, true);
61
+ parentAccountId = getAccountId({
62
+ account: parentAccountPrompt.account,
63
+ });
64
+ }
65
+ }
66
+ }
67
+
68
+ if (!getAccountId({ account: parentAccountId })) {
69
+ const baseUrl = getHubSpotWebsiteOrigin(
70
+ getEnv(sandboxAccountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD
71
+ );
72
+ const url = `${baseUrl}/sandboxes/${parentAccountId}`;
73
+ const command = `hs auth ${
74
+ getEnv(sandboxAccountId) === 'qa' ? '--qa' : ''
75
+ } --account=${parentAccountId}`;
76
+ logger.log('');
77
+ logger.error(
78
+ i18n(`${i18nKey}.noParentPortalAvailable`, {
79
+ parentAccountId,
80
+ url,
81
+ command,
82
+ })
83
+ );
84
+ logger.log('');
85
+ process.exit(EXIT_CODES.ERROR);
86
+ }
87
+
88
+ logger.debug(
89
+ i18n(`${i18nKey}.debug.deleting`, {
90
+ account: account || accountPrompt.account,
91
+ })
92
+ );
93
+
94
+ try {
95
+ const { confirmSandboxDeletePrompt: confirmed } = await promptUser([
96
+ {
97
+ name: 'confirmSandboxDeletePrompt',
98
+ type: 'confirm',
99
+ message: i18n(`${i18nKey}.confirm`, {
100
+ account: account || accountPrompt.account,
101
+ }),
102
+ },
103
+ ]);
104
+ if (!confirmed) {
105
+ process.exit(EXIT_CODES.SUCCESS);
106
+ }
107
+
108
+ await deleteSandbox(parentAccountId, sandboxAccountId);
109
+
110
+ logger.log('');
111
+ logger.success(
112
+ i18n(`${i18nKey}.success.delete`, {
113
+ account: account || accountPrompt.account,
114
+ sandboxHubId: sandboxAccountId,
115
+ })
116
+ );
117
+ logger.log('');
118
+
119
+ const promptDefaultAccount = removeSandboxAccountFromConfig(
120
+ sandboxAccountId
121
+ );
122
+ if (promptDefaultAccount) {
123
+ await selectAndSetAsDefaultAccountPrompt(config);
124
+ }
125
+ process.exit(EXIT_CODES.SUCCESS);
126
+ } catch (err) {
127
+ debugErrorAndContext(err);
128
+
129
+ trackCommandUsage('sandbox-delete', { success: false }, sandboxAccountId);
130
+
131
+ if (
132
+ err.error &&
133
+ err.error.category === OBJECT_NOT_FOUND &&
134
+ err.error.subCategory === SANDBOX_NOT_FOUND
135
+ ) {
136
+ logger.log('');
137
+ logger.warn(
138
+ i18n(`${i18nKey}.objectNotFound`, {
139
+ account: account || accountPrompt.account,
140
+ })
141
+ );
142
+ logger.log('');
18
143
 
19
- logger.log('');
20
- logger.log(i18n(`${i18nKey}.temporaryMessage`));
144
+ const promptDefaultAccount = removeSandboxAccountFromConfig(
145
+ sandboxAccountId
146
+ );
147
+ if (promptDefaultAccount) {
148
+ await selectAndSetAsDefaultAccountPrompt(config);
149
+ }
150
+ process.exit(EXIT_CODES.SUCCESS);
151
+ } else {
152
+ logger.error(err.error.message);
153
+ }
154
+ process.exit(EXIT_CODES.ERROR);
155
+ }
21
156
  };
22
157
 
23
158
  exports.builder = yargs => {
@@ -36,6 +171,7 @@ exports.builder = yargs => {
36
171
  addConfigOptions(yargs, true);
37
172
  addAccountOptions(yargs, true);
38
173
  addUseEnvironmentOptions(yargs, true);
174
+ addTestingOptions(yargs, true);
39
175
 
40
176
  return yargs;
41
177
  };
package/lib/projects.js CHANGED
@@ -224,6 +224,13 @@ const getProjectBuildDetailUrl = (projectName, buildId, accountId) => {
224
224
  return `${getProjectDetailUrl(projectName, accountId)}/build/${buildId}`;
225
225
  };
226
226
 
227
+ const getProjectDeployDetailUrl = (projectName, deployId, accountId) => {
228
+ if (!projectName || !deployId || !accountId) return;
229
+ return `${getProjectDetailUrl(
230
+ projectName,
231
+ accountId
232
+ )}/activity/deploy/${deployId}`;
233
+ };
227
234
  const uploadProjectFiles = async (accountId, projectName, filePath) => {
228
235
  const i18nKey = 'cli.commands.project.subcommands.upload';
229
236
  const spinnies = new Spinnies({
@@ -343,10 +350,13 @@ const makePollTaskStatusFunc = ({
343
350
  }
344
351
  };
345
352
 
346
- return async (accountId, taskName, taskId) => {
347
- let hubspotLinkText = '';
353
+ return async (accountId, taskName, taskId, deployedBuildId = null) => {
354
+ const displayId = deployedBuildId || taskId;
355
+
348
356
  if (linkToHubSpot) {
349
- logger.log(`\n${linkToHubSpot(taskName, taskId, accountId)}\n`);
357
+ logger.log(
358
+ `\n${linkToHubSpot(accountId, taskName, taskId, deployedBuildId)}\n`
359
+ );
350
360
  }
351
361
 
352
362
  const spinnies = new Spinnies({
@@ -372,7 +382,7 @@ const makePollTaskStatusFunc = ({
372
382
  const subTaskName = subTask[statusText.SUBTASK_NAME_KEY];
373
383
 
374
384
  spinnies.add(subTaskName, {
375
- text: `${chalk.bold(subTaskName)} #${taskId} ${
385
+ text: `${chalk.bold(subTaskName)} #${displayId} ${
376
386
  statusText.STATUS_TEXT[statusText.STATES.ENQUEUED]
377
387
  }\n`,
378
388
  indent: 2,
@@ -395,7 +405,7 @@ const makePollTaskStatusFunc = ({
395
405
  return;
396
406
  }
397
407
 
398
- const updatedText = `${chalk.bold(subTaskName)} #${taskId} ${
408
+ const updatedText = `${chalk.bold(subTaskName)} #${displayId} ${
399
409
  statusText.STATUS_TEXT[subTask.status]
400
410
  }\n`;
401
411
 
@@ -413,17 +423,17 @@ const makePollTaskStatusFunc = ({
413
423
  });
414
424
 
415
425
  if (isTaskComplete(taskStatus)) {
416
- subTaskStatus.forEach(subTask => {
417
- spinnies.remove(subTask[statusText.SUBTASK_NAME_KEY]);
418
- });
426
+ // subTaskStatus.forEach(subTask => {
427
+ // spinnies.remove(subTask[statusText.SUBTASK_NAME_KEY]);
428
+ // });
419
429
 
420
430
  if (status === statusText.STATES.SUCCESS) {
421
431
  spinnies.succeed('overallTaskStatus', {
422
- text: `${statusStrings.SUCCESS(taskName)}${hubspotLinkText}`,
432
+ text: statusStrings.SUCCESS(taskName),
423
433
  });
424
434
  } else if (status === statusText.STATES.FAILURE) {
425
435
  spinnies.fail('overallTaskStatus', {
426
- text: `${statusStrings.FAIL(taskName)}${hubspotLinkText}`,
436
+ text: statusStrings.FAIL(taskName),
427
437
  });
428
438
 
429
439
  const failedSubtask = subTaskStatus.filter(
@@ -433,7 +443,7 @@ const makePollTaskStatusFunc = ({
433
443
  uiLine();
434
444
  logger.log(
435
445
  `${statusStrings.SUBTASK_FAIL(
436
- taskId,
446
+ displayId,
437
447
  failedSubtask.length === 1
438
448
  ? failedSubtask[0][statusText.SUBTASK_NAME_KEY]
439
449
  : failedSubtask.length + ' components'
@@ -470,10 +480,10 @@ const makePollTaskStatusFunc = ({
470
480
  };
471
481
 
472
482
  const pollBuildStatus = makePollTaskStatusFunc({
473
- linkToHubSpot: (projectName, buildId, accountId) =>
483
+ linkToHubSpot: (accountId, taskName, taskId) =>
474
484
  uiLink(
475
- `View build #${buildId} in HubSpot`,
476
- getProjectBuildDetailUrl(projectName, buildId, accountId)
485
+ `View build #${taskId} in HubSpot`,
486
+ getProjectBuildDetailUrl(taskName, taskId, accountId)
477
487
  ),
478
488
  statusFn: getBuildStatus,
479
489
  statusText: PROJECT_BUILD_TEXT,
@@ -481,22 +491,27 @@ const pollBuildStatus = makePollTaskStatusFunc({
481
491
  INITIALIZE: name => `Building ${chalk.bold(name)}`,
482
492
  SUCCESS: name => `Built ${chalk.bold(name)}`,
483
493
  FAIL: name => `Failed to build ${chalk.bold(name)}`,
484
- SUBTASK_FAIL: (taskId, name) =>
485
- `Build #${taskId} failed because there was a problem\nbuilding ${chalk.bold(
494
+ SUBTASK_FAIL: (buildId, name) =>
495
+ `Build #${buildId} failed because there was a problem\nbuilding ${chalk.bold(
486
496
  name
487
497
  )}`,
488
498
  },
489
499
  });
490
500
 
491
501
  const pollDeployStatus = makePollTaskStatusFunc({
502
+ linkToHubSpot: (accountId, taskName, taskId, deployedBuildId) =>
503
+ uiLink(
504
+ `View deploy of build #${deployedBuildId} in HubSpot`,
505
+ getProjectDeployDetailUrl(taskName, taskId, accountId)
506
+ ),
492
507
  statusFn: getDeployStatus,
493
508
  statusText: PROJECT_DEPLOY_TEXT,
494
509
  statusStrings: {
495
510
  INITIALIZE: name => `Deploying ${chalk.bold(name)}`,
496
511
  SUCCESS: name => `Deployed ${chalk.bold(name)}`,
497
512
  FAIL: name => `Failed to deploy ${chalk.bold(name)}`,
498
- SUBTASK_FAIL: (taskId, name) =>
499
- `Deploy for build #${taskId} failed because there was a\nproblem deploying ${chalk.bold(
513
+ SUBTASK_FAIL: (deployedBuildId, name) =>
514
+ `Deploy for build #${deployedBuildId} failed because there was a\nproblem deploying ${chalk.bold(
500
515
  name
501
516
  )}`,
502
517
  },
@@ -0,0 +1,47 @@
1
+ const { updateDefaultAccount } = require('@hubspot/cli-lib/lib/config');
2
+ const { promptUser } = require('./promptUtils');
3
+ const { i18n } = require('@hubspot/cli-lib/lib/lang');
4
+
5
+ const i18nKey = 'cli.commands.accounts.subcommands.use';
6
+
7
+ const selectAccountFromConfig = async config => {
8
+ const { default: selectedDefault } = await promptUser([
9
+ {
10
+ type: 'list',
11
+ look: false,
12
+ name: 'default',
13
+ pageSize: 20,
14
+ message: i18n(`${i18nKey}.promptMessage`),
15
+ choices: config.portals.map(p => ({
16
+ name: `${p.name} (${p.portalId})`,
17
+ value: p.name || p.portalId,
18
+ })),
19
+ default: config.defaultPortal,
20
+ },
21
+ ]);
22
+
23
+ return selectedDefault;
24
+ };
25
+
26
+ const selectAndSetAsDefaultAccountPrompt = async config => {
27
+ const { default: selectedDefault } = await promptUser([
28
+ {
29
+ type: 'list',
30
+ look: false,
31
+ name: 'default',
32
+ pageSize: 20,
33
+ message: i18n(`${i18nKey}.promptMessage`),
34
+ choices: config.portals.map(p => ({
35
+ name: `${p.name} (${p.portalId})`,
36
+ value: p.name || p.portalId,
37
+ })),
38
+ default: config.defaultPortal,
39
+ },
40
+ ]);
41
+ updateDefaultAccount(selectedDefault);
42
+ };
43
+
44
+ module.exports = {
45
+ selectAndSetAsDefaultAccountPrompt,
46
+ selectAccountFromConfig,
47
+ };
@@ -19,6 +19,25 @@ const createSandboxPrompt = () => {
19
19
  ]);
20
20
  };
21
21
 
22
+ const deleteSandboxPrompt = (config, promptParentAccount = false) => {
23
+ return promptUser([
24
+ {
25
+ name: 'account',
26
+ message: i18n(
27
+ promptParentAccount
28
+ ? `${i18nKey}.selectParentAccountName`
29
+ : `${i18nKey}.selectAccountName`
30
+ ),
31
+ type: 'list',
32
+ look: false,
33
+ pageSize: 20,
34
+ choices: config.portals.map(p => p.name || p.portalId),
35
+ default: config.defaultPortal,
36
+ },
37
+ ]);
38
+ };
39
+
22
40
  module.exports = {
23
41
  createSandboxPrompt,
42
+ deleteSandboxPrompt,
24
43
  };
package/lib/validation.js CHANGED
@@ -26,14 +26,19 @@ const fs = require('fs');
26
26
  const path = require('path');
27
27
  const { EXIT_CODES } = require('./enums/exitCodes');
28
28
 
29
- async function loadAndValidateOptions(options) {
29
+ async function loadAndValidateOptions(options, shouldValidateAccount = true) {
30
30
  setLogLevel(options);
31
31
  logDebugInfo(options);
32
32
  const { config: configPath } = options;
33
33
  loadConfig(configPath, options);
34
34
  checkAndWarnGitInclusion(getConfigPath());
35
35
 
36
- if (!(validateConfig() && (await validateAccount(options)))) {
36
+ let validAccount = true;
37
+ if (shouldValidateAccount) {
38
+ validAccount = await validateAccount(options);
39
+ }
40
+
41
+ if (!(validateConfig() && validAccount)) {
37
42
  process.exit(EXIT_CODES.ERROR);
38
43
  }
39
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "4.0.1-beta.3",
3
+ "version": "4.0.1",
4
4
  "description": "CLI for working with HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -8,8 +8,8 @@
8
8
  "url": "https://github.com/HubSpot/hubspot-cms-tools"
9
9
  },
10
10
  "dependencies": {
11
- "@hubspot/cli-lib": "4.0.1-beta.3",
12
- "@hubspot/serverless-dev-runtime": "4.0.1-beta.3",
11
+ "@hubspot/cli-lib": "4.0.1",
12
+ "@hubspot/serverless-dev-runtime": "4.0.1",
13
13
  "archiver": "^5.3.0",
14
14
  "chalk": "^4.1.2",
15
15
  "express": "^4.17.1",
@@ -37,5 +37,5 @@
37
37
  "publishConfig": {
38
38
  "access": "public"
39
39
  },
40
- "gitHead": "e2fac76a5ea9f51f69dfb786a5f285ffcf88a47a"
40
+ "gitHead": "416585f18c9b856b4da3ef502d1eccaded9281d5"
41
41
  }