@hubspot/cli 4.2.1-beta.2 → 4.2.1-beta.4

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
@@ -42,7 +42,11 @@ const cmsCommand = require('../commands/cms');
42
42
  const feedbackCommand = require('../commands/feedback');
43
43
  const { EXIT_CODES } = require('../lib/enums/exitCodes');
44
44
 
45
- const notifier = updateNotifier({ pkg: { ...pkg, name: '@hubspot/cli' } });
45
+ const notifier = updateNotifier({
46
+ pkg: { ...pkg, name: '@hubspot/cli' },
47
+ distTag: 'latest',
48
+ shouldNotifyInNpmScript: true,
49
+ });
46
50
 
47
51
  const i18nKey = 'cli.commands.generalErrors';
48
52
 
@@ -51,7 +55,6 @@ const CLI_UPGRADE_MESSAGE =
51
55
  '\n\nTo upgrade, run:\n\nnpm uninstall -g @hubspot/cms-cli\nand npm install -g @hubspot/cli';
52
56
 
53
57
  notifier.notify({
54
- shouldNotifyInNpmScript: true,
55
58
  message: pkg.name === '@hubspot/cms-cli' ? CLI_UPGRADE_MESSAGE : null,
56
59
  });
57
60
 
@@ -0,0 +1,139 @@
1
+ const { logger } = require('@hubspot/cli-lib/logger');
2
+ const {
3
+ accessTokenForPersonalAccessKey,
4
+ } = require('@hubspot/cli-lib/personalAccessKey');
5
+ const { getConfig } = require('@hubspot/cli-lib');
6
+
7
+ const { trackCommandUsage } = require('../../lib/usageTracking');
8
+ const { i18n } = require('../../lib/lang');
9
+ const { loadAndValidateOptions } = require('../../lib/validation');
10
+ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
11
+ const {
12
+ addConfigOptions,
13
+ addAccountOptions,
14
+ addUseEnvironmentOptions,
15
+ addTestingOptions,
16
+ } = require('../../lib/commonOpts');
17
+ const { getAccountName } = require('../../lib/sandboxes');
18
+ const { promptUser } = require('../../lib/prompts/promptUtils');
19
+ const { getTableContents } = require('@hubspot/cli-lib/lib/table');
20
+ const SpinniesManager = require('../../lib/SpinniesManager');
21
+ const { deleteAccount } = require('@hubspot/cli-lib/lib/config');
22
+ const {
23
+ isSpecifiedHubSpotAuthError,
24
+ } = require('@hubspot/cli-lib/errorHandlers/apiErrors');
25
+
26
+ const i18nKey = 'cli.commands.accounts.subcommands.clean';
27
+
28
+ exports.command = 'clean';
29
+ exports.describe = i18n(`${i18nKey}.describe`);
30
+
31
+ exports.handler = async options => {
32
+ const { qa } = options;
33
+ await loadAndValidateOptions(options, false);
34
+
35
+ const config = getConfig();
36
+
37
+ trackCommandUsage('accounts-clean', null);
38
+
39
+ const filteredTestAccounts = config.portals.filter(p =>
40
+ qa ? p.env === 'qa' : p.env !== 'qa'
41
+ );
42
+
43
+ if (filteredTestAccounts && filteredTestAccounts.length === 0) {
44
+ logger.log(i18n(`${i18nKey}.noResults`));
45
+ process.exit(EXIT_CODES.SUCCESS);
46
+ }
47
+
48
+ const accountsToRemove = [];
49
+ SpinniesManager.init({
50
+ succeedColor: 'white',
51
+ });
52
+ SpinniesManager.add('accountsClean', {
53
+ text: i18n(`${i18nKey}.loading.add`),
54
+ });
55
+
56
+ for (const account of filteredTestAccounts) {
57
+ try {
58
+ await accessTokenForPersonalAccessKey(account.portalId);
59
+ } catch (error) {
60
+ if (
61
+ isSpecifiedHubSpotAuthError(error, {
62
+ statusCode: 401,
63
+ category: 'INVALID_AUTHENTICATION',
64
+ subCategory: 'LocalDevAuthErrorType.PORTAL_NOT_ACTIVE',
65
+ }) ||
66
+ isSpecifiedHubSpotAuthError(error, {
67
+ statusCode: 404,
68
+ category: 'INVALID_AUTHENTICATION',
69
+ subCategory: 'LocalDevAuthErrorType.INVALID_PORTAL_ID',
70
+ })
71
+ ) {
72
+ accountsToRemove.push(account);
73
+ }
74
+ }
75
+ }
76
+
77
+ if (accountsToRemove.length > 0) {
78
+ const oneAccountFound = accountsToRemove.length === 1;
79
+ SpinniesManager.succeed('accountsClean', {
80
+ text: i18n(
81
+ oneAccountFound
82
+ ? `${i18nKey}.inactiveAccountsFound.one`
83
+ : `${i18nKey}.inactiveAccountsFound.other`,
84
+ {
85
+ count: accountsToRemove.length,
86
+ }
87
+ ),
88
+ });
89
+ logger.log(
90
+ getTableContents(
91
+ accountsToRemove.map(p => [getAccountName(p)]),
92
+ { border: { bodyLeft: ' ' } }
93
+ )
94
+ );
95
+ const { accountsCleanPrompt } = await promptUser([
96
+ {
97
+ name: 'accountsCleanPrompt',
98
+ type: 'confirm',
99
+ message: i18n(
100
+ oneAccountFound
101
+ ? `${i18nKey}.confirm.one`
102
+ : `${i18nKey}.confirm.other`,
103
+ {
104
+ count: accountsToRemove.length,
105
+ }
106
+ ),
107
+ },
108
+ ]);
109
+ if (accountsCleanPrompt) {
110
+ logger.log('');
111
+ for (const accountToRemove of accountsToRemove) {
112
+ await deleteAccount(accountToRemove.name);
113
+ logger.log(
114
+ i18n(`${i18nKey}.removeSuccess`, {
115
+ accountName: getAccountName(accountToRemove),
116
+ })
117
+ );
118
+ }
119
+ }
120
+ } else {
121
+ SpinniesManager.succeed('accountsClean', {
122
+ text: i18n(`${i18nKey}.noResults`),
123
+ });
124
+ }
125
+
126
+ logger.log('');
127
+ process.exit(EXIT_CODES.SUCCESS);
128
+ };
129
+
130
+ exports.builder = yargs => {
131
+ addConfigOptions(yargs, true);
132
+ addAccountOptions(yargs, true);
133
+ addUseEnvironmentOptions(yargs, true);
134
+ addTestingOptions(yargs, true);
135
+
136
+ yargs.example([['$0 accounts clean']]);
137
+
138
+ return yargs;
139
+ };
@@ -5,6 +5,7 @@ const rename = require('./accounts/rename');
5
5
  const use = require('./accounts/use');
6
6
  const info = require('./accounts/info');
7
7
  const remove = require('./accounts/remove');
8
+ const clean = require('./accounts/clean');
8
9
 
9
10
  const i18nKey = 'cli.commands.accounts';
10
11
 
@@ -24,6 +25,7 @@ exports.builder = yargs => {
24
25
  .command(use)
25
26
  .command(info)
26
27
  .command(remove)
28
+ .command(clean)
27
29
  .demandCommand(1, '');
28
30
 
29
31
  return yargs;
@@ -38,7 +38,7 @@ exports.handler = async options => {
38
38
  await createProjectConfig(
39
39
  path.resolve(getCwd(), options.location || location),
40
40
  options.name || name,
41
- options.template || template,
41
+ template || { path: options.template },
42
42
  options.templateSource
43
43
  );
44
44
 
@@ -23,17 +23,19 @@ const {
23
23
  showPlatformVersionWarning,
24
24
  } = require('../../lib/projects');
25
25
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
26
- const { uiAccountDescription, uiBetaMessage, uiLine } = require('../../lib/ui');
26
+ const {
27
+ uiAccountDescription,
28
+ uiBetaMessage,
29
+ uiCommandReference,
30
+ uiLine,
31
+ } = require('../../lib/ui');
27
32
  const { confirmPrompt } = require('../../lib/prompts/promptUtils');
28
33
  const {
29
34
  selectTargetAccountPrompt,
35
+ confirmDefaultSandboxAccountPrompt,
30
36
  } = require('../../lib/prompts/projectDevTargetAccountPrompt');
31
37
  const SpinniesManager = require('../../lib/SpinniesManager');
32
- const {
33
- LocalDevManager,
34
- UPLOAD_PERMISSIONS,
35
- } = require('../../lib/LocalDevManager');
36
- const LocalDevManagerV2 = require('../../lib/LocalDevManagerV2');
38
+ const LocalDevManager = require('../../lib/LocalDevManager');
37
39
  const { isSandbox } = require('../../lib/sandboxes');
38
40
  const { getAccountConfig, getEnv } = require('@hubspot/cli-lib');
39
41
  const { sandboxNamePrompt } = require('../../lib/prompts/sandboxesPrompt');
@@ -83,15 +85,29 @@ exports.handler = async options => {
83
85
  process.exit(EXIT_CODES.ERROR);
84
86
  }
85
87
 
86
- await showPlatformVersionWarning(accountId, projectConfig);
87
-
88
88
  const accounts = getConfigAccounts();
89
89
  let targetAccountId = options.account ? accountId : null;
90
90
  let createNewSandbox = false;
91
91
  const defaultAccountIsSandbox = isSandbox(accountConfig);
92
92
 
93
93
  if (!targetAccountId && defaultAccountIsSandbox) {
94
- targetAccountId = accountId;
94
+ logger.log();
95
+ const useDefaultSandboxAccount = await confirmDefaultSandboxAccountPrompt(
96
+ accountConfig.name,
97
+ accountConfig.sandboxAccountType
98
+ );
99
+
100
+ if (useDefaultSandboxAccount) {
101
+ targetAccountId = accountId;
102
+ } else {
103
+ logger.log(
104
+ i18n(`${i18nKey}.logs.declineDefaultSandboxExplanation`, {
105
+ useCommand: uiCommandReference('hs accounts use'),
106
+ devCommand: uiCommandReference('hs project dev'),
107
+ })
108
+ );
109
+ process.exit(EXIT_CODES.SUCCESS);
110
+ }
95
111
  }
96
112
 
97
113
  if (!targetAccountId) {
@@ -172,32 +188,22 @@ exports.handler = async options => {
172
188
  }
173
189
  }
174
190
 
191
+ logger.log();
175
192
  const projectExists = await ensureProjectExists(
176
193
  targetAccountId,
177
194
  projectConfig.name,
178
195
  {
179
196
  allowCreate: false,
180
197
  noLogs: true,
181
- withPolling: true,
198
+ withPolling: createNewSandbox,
182
199
  }
183
200
  );
184
201
 
185
- const isNonSandboxAccount =
186
- !defaultAccountIsSandbox && targetAccountId === accountId;
187
-
188
- let uploadPermission = isNonSandboxAccount
189
- ? UPLOAD_PERMISSIONS.manual
190
- : UPLOAD_PERMISSIONS.always;
191
-
192
202
  let deployedBuild;
193
203
 
194
204
  if (projectExists) {
195
205
  const project = await fetchProject(targetAccountId, projectConfig.name);
196
206
  deployedBuild = project.deployedBuild;
197
-
198
- if (options.local || options.localAll || project.sourceIntegration) {
199
- uploadPermission = UPLOAD_PERMISSIONS.never;
200
- }
201
207
  }
202
208
 
203
209
  SpinniesManager.init();
@@ -207,6 +213,7 @@ exports.handler = async options => {
207
213
  let shouldCreateProject = createNewSandbox;
208
214
 
209
215
  if (!shouldCreateProject) {
216
+ logger.log();
210
217
  uiLine();
211
218
  logger.warn(
212
219
  i18n(`${i18nKey}.logs.projectMustExistExplanation`, {
@@ -225,13 +232,16 @@ exports.handler = async options => {
225
232
  }
226
233
 
227
234
  if (shouldCreateProject) {
235
+ await showPlatformVersionWarning(accountId, projectConfig);
236
+
237
+ SpinniesManager.add('createProject', {
238
+ text: i18n(`${i18nKey}.status.creatingProject`, {
239
+ accountIdentifier: uiAccountDescription(targetAccountId),
240
+ projectName: projectConfig.name,
241
+ }),
242
+ });
243
+
228
244
  try {
229
- SpinniesManager.add('createProject', {
230
- text: i18n(`${i18nKey}.status.creatingProject`, {
231
- accountIdentifier: uiAccountDescription(targetAccountId),
232
- projectName: projectConfig.name,
233
- }),
234
- });
235
245
  await createProject(targetAccountId, projectConfig.name);
236
246
  SpinniesManager.succeed('createProject', {
237
247
  text: i18n(`${i18nKey}.status.createdProject`, {
@@ -241,28 +251,22 @@ exports.handler = async options => {
241
251
  succeedColor: 'white',
242
252
  });
243
253
  } catch (err) {
254
+ SpinniesManager.fail('createProject');
244
255
  logger.log(i18n(`${i18nKey}.status.failedToCreateProject`));
245
256
  process.exit(EXIT_CODES.ERROR);
246
257
  }
247
258
  } else {
248
259
  // We cannot continue if the project does not exist in the target account
260
+ logger.log();
249
261
  logger.log(i18n(`${i18nKey}.logs.choseNotToCreateProject`));
250
262
  process.exit(EXIT_CODES.SUCCESS);
251
263
  }
252
264
  }
253
265
 
254
- SpinniesManager.add('devModeSetup', {
255
- text: i18n(`${i18nKey}.status.startupMessage`, {
256
- projectName: projectConfig.name,
257
- }),
258
- isParent: true,
259
- });
260
-
261
266
  let initialUploadResult;
262
267
 
263
- // Create an initial build if the project was newly created in the account or if
264
- // our upload permission is set to "always"
265
- if (!projectExists || uploadPermission === UPLOAD_PERMISSIONS.always) {
268
+ // Create an initial build if the project was newly created in the account
269
+ if (!projectExists) {
266
270
  initialUploadResult = await handleProjectUpload(
267
271
  targetAccountId,
268
272
  projectConfig,
@@ -272,8 +276,6 @@ exports.handler = async options => {
272
276
  );
273
277
 
274
278
  if (initialUploadResult.uploadError) {
275
- SpinniesManager.fail('devModeSetup');
276
-
277
279
  if (
278
280
  isSpecifiedError(initialUploadResult.uploadError, {
279
281
  subCategory: ERROR_TYPES.PROJECT_LOCKED,
@@ -293,75 +295,42 @@ exports.handler = async options => {
293
295
  }
294
296
  process.exit(EXIT_CODES.ERROR);
295
297
  }
296
- }
297
298
 
298
- // Let the user know when the initial build or deploy fails
299
- // Do this before starting the dev server for v2 behavior because we cannot
300
- // run a server on a broken project
301
- if (
302
- (options.local || options.localAll) &&
303
- initialUploadResult &&
304
- !initialUploadResult.succeeded
305
- ) {
306
- SpinniesManager.fail('devModeSetup');
307
-
308
- let subTasks = [];
309
-
310
- if (initialUploadResult.buildResult.status === 'FAILURE') {
311
- subTasks =
312
- initialUploadResult.buildResult[PROJECT_BUILD_TEXT.SUBTASK_KEY];
313
- } else if (initialUploadResult.deployResult.status === 'FAILURE') {
314
- subTasks =
315
- initialUploadResult.deployResult[PROJECT_DEPLOY_TEXT.SUBTASK_KEY];
316
- }
299
+ if (!initialUploadResult.succeeded) {
300
+ let subTasks = [];
317
301
 
318
- const failedSubTasks = subTasks.filter(task => task.status === 'FAILURE');
302
+ if (initialUploadResult.buildResult.status === 'FAILURE') {
303
+ subTasks =
304
+ initialUploadResult.buildResult[PROJECT_BUILD_TEXT.SUBTASK_KEY];
305
+ } else if (initialUploadResult.deployResult.status === 'FAILURE') {
306
+ subTasks =
307
+ initialUploadResult.deployResult[PROJECT_DEPLOY_TEXT.SUBTASK_KEY];
308
+ }
319
309
 
320
- logger.log();
321
- failedSubTasks.forEach(failedSubTask => {
322
- console.log(failedSubTask.errorMessage);
323
- });
324
- logger.log();
310
+ const failedSubTasks = subTasks.filter(task => task.status === 'FAILURE');
325
311
 
326
- process.exit(EXIT_CODES.ERROR);
312
+ logger.log();
313
+ failedSubTasks.forEach(failedSubTask => {
314
+ console.error(failedSubTask.errorMessage);
315
+ });
316
+ logger.log();
317
+
318
+ process.exit(EXIT_CODES.ERROR);
319
+ }
320
+
321
+ deployedBuild = initialUploadResult.buildResult;
327
322
  }
328
323
 
329
- SpinniesManager.remove('devModeSetup');
330
-
331
- const LocalDev =
332
- options.local || options.localAll
333
- ? new LocalDevManagerV2({
334
- alpha: options.localAll,
335
- debug: options.debug,
336
- deployedBuild,
337
- projectConfig,
338
- projectDir,
339
- targetAccountId,
340
- })
341
- : new LocalDevManager({
342
- debug: options.debug,
343
- projectConfig,
344
- projectDir,
345
- targetAccountId,
346
- uploadPermission,
347
- });
324
+ const LocalDev = new LocalDevManager({
325
+ debug: options.debug,
326
+ deployedBuild,
327
+ projectConfig,
328
+ projectDir,
329
+ targetAccountId,
330
+ });
348
331
 
349
332
  await LocalDev.start();
350
333
 
351
- // Let the user know when the initial build or deploy fails
352
- if (
353
- !options.local &&
354
- !options.localAll &&
355
- initialUploadResult &&
356
- !initialUploadResult.succeeded
357
- ) {
358
- if (initialUploadResult.buildResult.status === 'FAILURE') {
359
- LocalDev.logBuildError(initialUploadResult.buildResult);
360
- } else if (initialUploadResult.deployResult.status === 'FAILURE') {
361
- LocalDev.logDeployError(initialUploadResult.deployResult);
362
- }
363
- }
364
-
365
334
  handleExit(LocalDev.stop);
366
335
  };
367
336
 
@@ -371,18 +340,6 @@ exports.builder = yargs => {
371
340
  addUseEnvironmentOptions(yargs, true);
372
341
  addTestingOptions(yargs, true);
373
342
 
374
- yargs.option('local', {
375
- describe: i18n(`${i18nKey}.options.local.describe`),
376
- type: 'boolean',
377
- hidden: true,
378
- });
379
-
380
- yargs.option('local-all', {
381
- describe: i18n(`${i18nKey}.options.localAll.describe`),
382
- type: 'boolean',
383
- hidden: true,
384
- });
385
-
386
343
  yargs.example([['$0 project dev', i18n(`${i18nKey}.examples.default`)]]);
387
344
 
388
345
  return yargs;
@@ -17,10 +17,7 @@ const {
17
17
  downloadProject,
18
18
  fetchProjectBuilds,
19
19
  } = require('@hubspot/cli-lib/api/dfs');
20
- const {
21
- createProjectConfig,
22
- ensureProjectExists,
23
- } = require('../../lib/projects');
20
+ const { ensureProjectExists, getProjectConfig } = require('../../lib/projects');
24
21
  const { loadAndValidateOptions } = require('../../lib/validation');
25
22
  const {
26
23
  downloadProjectPrompt,
@@ -36,6 +33,13 @@ exports.describe = i18n(`${i18nKey}.describe`);
36
33
  exports.handler = async options => {
37
34
  await loadAndValidateOptions(options);
38
35
 
36
+ const { projectConfig } = await getProjectConfig();
37
+
38
+ if (projectConfig) {
39
+ logger.error(i18n(`${i18nKey}.warnings.cannotDownloadWithinProject`));
40
+ process.exit(EXIT_CODES.ERROR);
41
+ }
42
+
39
43
  const { project, dest, buildNumber } = options;
40
44
  let { project: promptedProjectName } = await downloadProjectPrompt(options);
41
45
  let projectName = promptedProjectName || project;
@@ -63,17 +67,6 @@ exports.handler = async options => {
63
67
 
64
68
  const absoluteDestPath = dest ? path.resolve(getCwd(), dest) : getCwd();
65
69
 
66
- const projectConfigCreated = await createProjectConfig(
67
- absoluteDestPath,
68
- projectName,
69
- { name: 'no-template' }
70
- );
71
-
72
- if (!projectConfigCreated) {
73
- logger.log(i18n(`${i18nKey}.logs.downloadCancelled`));
74
- process.exit(EXIT_CODES.SUCCESS);
75
- }
76
-
77
70
  let buildNumberToDownload = buildNumber;
78
71
 
79
72
  if (!buildNumberToDownload) {
@@ -98,10 +91,8 @@ exports.handler = async options => {
98
91
  await extractZipArchive(
99
92
  zippedProject,
100
93
  projectName,
101
- path.resolve(absoluteDestPath, 'src'),
102
- {
103
- includesRootDir: false,
104
- }
94
+ path.resolve(absoluteDestPath),
95
+ { includesRootDir: false }
105
96
  );
106
97
 
107
98
  logger.log(
@@ -28,8 +28,8 @@ const { promptUser } = require('../../lib/prompts/promptUtils');
28
28
  const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
29
29
  const {
30
30
  isSpecifiedError,
31
+ isSpecifiedHubSpotAuthError,
31
32
  } = require('@hubspot/cli-lib/errorHandlers/apiErrors');
32
- const { HubSpotAuthError } = require('@hubspot/cli-lib/lib/models/Errors');
33
33
  const { getAccountName } = require('../../lib/sandboxes');
34
34
  const { getValidEnv } = require('@hubspot/cli-lib/lib/environment');
35
35
 
@@ -168,7 +168,7 @@ exports.handler = async options => {
168
168
  } catch (err) {
169
169
  debugErrorAndContext(err);
170
170
 
171
- if (err instanceof HubSpotAuthError && err.statusCode === 401) {
171
+ if (isSpecifiedHubSpotAuthError(err, { statusCode: 401 })) {
172
172
  // Intercept invalid key error
173
173
  // This command uses the parent portal PAK to delete a sandbox, so we must specify which account needs a new key
174
174
  logger.log('');
@@ -1,12 +1,13 @@
1
1
  const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts');
2
+ const { i18n } = require('../lib/lang');
2
3
  const create = require('./sandbox/create');
3
4
  const del = require('./sandbox/delete');
4
5
  const sync = require('./sandbox/sync');
5
6
 
6
- // const i18nKey = 'cli.commands.sandbox';
7
+ const i18nKey = 'cli.commands.sandbox';
7
8
 
8
9
  exports.command = 'sandbox';
9
- exports.describe = false; // i18n(`${i18nKey}.describe`);
10
+ exports.describe = i18n(`${i18nKey}.describe`);
10
11
 
11
12
  exports.builder = yargs => {
12
13
  addConfigOptions(yargs, true);