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

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.
@@ -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;
@@ -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');
@@ -76,6 +78,9 @@ exports.handler = async options => {
76
78
 
77
79
  const { projectConfig, projectDir } = await getProjectConfig();
78
80
 
81
+ if (!options.debug) {
82
+ console.clear();
83
+ }
79
84
  uiBetaMessage(i18n(`${i18nKey}.logs.betaMessage`));
80
85
 
81
86
  if (!projectConfig) {
@@ -83,15 +88,29 @@ exports.handler = async options => {
83
88
  process.exit(EXIT_CODES.ERROR);
84
89
  }
85
90
 
86
- await showPlatformVersionWarning(accountId, projectConfig);
87
-
88
91
  const accounts = getConfigAccounts();
89
92
  let targetAccountId = options.account ? accountId : null;
90
93
  let createNewSandbox = false;
91
94
  const defaultAccountIsSandbox = isSandbox(accountConfig);
92
95
 
93
96
  if (!targetAccountId && defaultAccountIsSandbox) {
94
- targetAccountId = accountId;
97
+ logger.log();
98
+ const useDefaultSandboxAccount = await confirmDefaultSandboxAccountPrompt(
99
+ accountConfig.name,
100
+ accountConfig.sandboxAccountType
101
+ );
102
+
103
+ if (useDefaultSandboxAccount) {
104
+ targetAccountId = accountId;
105
+ } else {
106
+ logger.log(
107
+ i18n(`${i18nKey}.logs.declineDefaultSandboxExplanation`, {
108
+ useCommand: uiCommandReference('hs accounts use'),
109
+ devCommand: uiCommandReference('hs project dev'),
110
+ })
111
+ );
112
+ process.exit(EXIT_CODES.SUCCESS);
113
+ }
95
114
  }
96
115
 
97
116
  if (!targetAccountId) {
@@ -172,41 +191,37 @@ exports.handler = async options => {
172
191
  }
173
192
  }
174
193
 
194
+ logger.log();
175
195
  const projectExists = await ensureProjectExists(
176
196
  targetAccountId,
177
197
  projectConfig.name,
178
198
  {
179
199
  allowCreate: false,
180
200
  noLogs: true,
181
- withPolling: true,
201
+ withPolling: createNewSandbox,
182
202
  }
183
203
  );
184
204
 
185
- const isNonSandboxAccount =
186
- !defaultAccountIsSandbox && targetAccountId === accountId;
187
-
188
- let uploadPermission = isNonSandboxAccount
189
- ? UPLOAD_PERMISSIONS.manual
190
- : UPLOAD_PERMISSIONS.always;
191
-
192
205
  let deployedBuild;
193
206
 
194
207
  if (projectExists) {
195
208
  const project = await fetchProject(targetAccountId, projectConfig.name);
196
209
  deployedBuild = project.deployedBuild;
197
-
198
- if (options.local || options.localAll || project.sourceIntegration) {
199
- uploadPermission = UPLOAD_PERMISSIONS.never;
200
- }
201
210
  }
202
211
 
203
212
  SpinniesManager.init();
204
213
 
214
+ if (!options.debug) {
215
+ console.clear();
216
+ }
217
+ uiBetaMessage(i18n(`${i18nKey}.logs.betaMessage`));
218
+
205
219
  if (!projectExists) {
206
220
  // Create the project without prompting if this is a newly created sandbox
207
221
  let shouldCreateProject = createNewSandbox;
208
222
 
209
223
  if (!shouldCreateProject) {
224
+ logger.log();
210
225
  uiLine();
211
226
  logger.warn(
212
227
  i18n(`${i18nKey}.logs.projectMustExistExplanation`, {
@@ -225,6 +240,8 @@ exports.handler = async options => {
225
240
  }
226
241
 
227
242
  if (shouldCreateProject) {
243
+ await showPlatformVersionWarning(accountId, projectConfig);
244
+
228
245
  try {
229
246
  SpinniesManager.add('createProject', {
230
247
  text: i18n(`${i18nKey}.status.creatingProject`, {
@@ -246,23 +263,16 @@ exports.handler = async options => {
246
263
  }
247
264
  } else {
248
265
  // We cannot continue if the project does not exist in the target account
266
+ logger.log();
249
267
  logger.log(i18n(`${i18nKey}.logs.choseNotToCreateProject`));
250
268
  process.exit(EXIT_CODES.SUCCESS);
251
269
  }
252
270
  }
253
271
 
254
- SpinniesManager.add('devModeSetup', {
255
- text: i18n(`${i18nKey}.status.startupMessage`, {
256
- projectName: projectConfig.name,
257
- }),
258
- isParent: true,
259
- });
260
-
261
272
  let initialUploadResult;
262
273
 
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) {
274
+ // Create an initial build if the project was newly created in the account
275
+ if (!projectExists) {
266
276
  initialUploadResult = await handleProjectUpload(
267
277
  targetAccountId,
268
278
  projectConfig,
@@ -272,8 +282,6 @@ exports.handler = async options => {
272
282
  );
273
283
 
274
284
  if (initialUploadResult.uploadError) {
275
- SpinniesManager.fail('devModeSetup');
276
-
277
285
  if (
278
286
  isSpecifiedError(initialUploadResult.uploadError, {
279
287
  subCategory: ERROR_TYPES.PROJECT_LOCKED,
@@ -293,75 +301,42 @@ exports.handler = async options => {
293
301
  }
294
302
  process.exit(EXIT_CODES.ERROR);
295
303
  }
296
- }
297
304
 
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
- }
305
+ if (!initialUploadResult.succeeded) {
306
+ let subTasks = [];
317
307
 
318
- const failedSubTasks = subTasks.filter(task => task.status === 'FAILURE');
308
+ if (initialUploadResult.buildResult.status === 'FAILURE') {
309
+ subTasks =
310
+ initialUploadResult.buildResult[PROJECT_BUILD_TEXT.SUBTASK_KEY];
311
+ } else if (initialUploadResult.deployResult.status === 'FAILURE') {
312
+ subTasks =
313
+ initialUploadResult.deployResult[PROJECT_DEPLOY_TEXT.SUBTASK_KEY];
314
+ }
319
315
 
320
- logger.log();
321
- failedSubTasks.forEach(failedSubTask => {
322
- console.log(failedSubTask.errorMessage);
323
- });
324
- logger.log();
316
+ const failedSubTasks = subTasks.filter(task => task.status === 'FAILURE');
325
317
 
326
- process.exit(EXIT_CODES.ERROR);
318
+ logger.log();
319
+ failedSubTasks.forEach(failedSubTask => {
320
+ console.log(failedSubTask.errorMessage);
321
+ });
322
+ logger.log();
323
+
324
+ process.exit(EXIT_CODES.ERROR);
325
+ }
326
+
327
+ deployedBuild = initialUploadResult.buildResult;
327
328
  }
328
329
 
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
- });
330
+ const LocalDev = new LocalDevManager({
331
+ debug: options.debug,
332
+ deployedBuild,
333
+ projectConfig,
334
+ projectDir,
335
+ targetAccountId,
336
+ });
348
337
 
349
338
  await LocalDev.start();
350
339
 
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
340
  handleExit(LocalDev.stop);
366
341
  };
367
342
 
@@ -371,18 +346,6 @@ exports.builder = yargs => {
371
346
  addUseEnvironmentOptions(yargs, true);
372
347
  addTestingOptions(yargs, true);
373
348
 
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
349
  yargs.example([['$0 project dev', i18n(`${i18nKey}.examples.default`)]]);
387
350
 
388
351
  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);