@hubspot/cli 5.2.1-beta.9 → 5.3.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/lib/projects.js CHANGED
@@ -60,8 +60,10 @@ const writeProjectConfig = (configPath, config) => {
60
60
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
61
61
  logger.debug(`Wrote project config at ${configPath}`);
62
62
  } catch (e) {
63
- logger.error(`Could not write project config at ${configPath}`);
63
+ logger.debug(e);
64
+ return false;
64
65
  }
66
+ return true;
65
67
  };
66
68
 
67
69
  const getIsInProject = _dir => {
@@ -326,17 +328,19 @@ const getProjectDetailUrl = (projectName, accountId) => {
326
328
  return `${getProjectHomeUrl(accountId)}/project/${projectName}`;
327
329
  };
328
330
 
331
+ const getProjectActivityUrl = (projectName, accountId) => {
332
+ if (!projectName) return;
333
+ return `${getProjectDetailUrl(projectName, accountId)}/activity`;
334
+ };
335
+
329
336
  const getProjectBuildDetailUrl = (projectName, buildId, accountId) => {
330
337
  if (!projectName || !buildId || !accountId) return;
331
- return `${getProjectDetailUrl(projectName, accountId)}/build/${buildId}`;
338
+ return `${getProjectActivityUrl(projectName, accountId)}/build/${buildId}`;
332
339
  };
333
340
 
334
341
  const getProjectDeployDetailUrl = (projectName, deployId, accountId) => {
335
342
  if (!projectName || !deployId || !accountId) return;
336
- return `${getProjectDetailUrl(
337
- projectName,
338
- accountId
339
- )}/activity/deploy/${deployId}`;
343
+ return `${getProjectActivityUrl(projectName, accountId)}/deploy/${deployId}`;
340
344
  };
341
345
 
342
346
  const uploadProjectFiles = async (
@@ -405,7 +409,7 @@ const pollProjectBuildAndDeploy = async (
405
409
  buildId,
406
410
  silenceLogs = false
407
411
  ) => {
408
- const buildStatus = await pollBuildStatus(
412
+ let buildStatus = await pollBuildStatus(
409
413
  accountId,
410
414
  projectConfig.name,
411
415
  buildId,
@@ -413,16 +417,6 @@ const pollProjectBuildAndDeploy = async (
413
417
  silenceLogs
414
418
  );
415
419
 
416
- const {
417
- autoDeployId,
418
- isAutoDeployEnabled,
419
- deployStatusTaskLocator,
420
- } = buildStatus;
421
-
422
- // autoDeployId of 0 indicates a skipped deploy
423
- const isDeploying =
424
- isAutoDeployEnabled && autoDeployId > 0 && deployStatusTaskLocator;
425
-
426
420
  if (!silenceLogs) {
427
421
  uiLine();
428
422
  }
@@ -437,7 +431,7 @@ const pollProjectBuildAndDeploy = async (
437
431
  if (buildStatus.status === 'FAILURE') {
438
432
  result.succeeded = false;
439
433
  return result;
440
- } else if (isDeploying) {
434
+ } else if (buildStatus.isAutoDeployEnabled) {
441
435
  if (!silenceLogs) {
442
436
  logger.log(
443
437
  i18n(
@@ -452,17 +446,45 @@ const pollProjectBuildAndDeploy = async (
452
446
  displayWarnLogs(accountId, projectConfig.name, buildId);
453
447
  }
454
448
 
455
- const deployStatus = await pollDeployStatus(
456
- accountId,
457
- projectConfig.name,
458
- deployStatusTaskLocator.id,
459
- buildId,
460
- silenceLogs
461
- );
462
- result.deployResult = deployStatus;
449
+ // autoDeployId of 0 indicates a skipped deploy
450
+ const getIsDeploying = () =>
451
+ buildStatus.autoDeployId > 0 && buildStatus.deployStatusTaskLocator;
452
+
453
+ // Sometimes the deploys do not immediately initiate, give them a chance to kick off
454
+ if (!getIsDeploying()) {
455
+ buildStatus = await pollBuildAutodeployStatus(
456
+ accountId,
457
+ projectConfig.name,
458
+ buildId
459
+ );
460
+ }
461
+
462
+ if (getIsDeploying()) {
463
+ const deployStatus = await pollDeployStatus(
464
+ accountId,
465
+ projectConfig.name,
466
+ buildStatus.deployStatusTaskLocator.id,
467
+ buildId,
468
+ silenceLogs
469
+ );
470
+ result.deployResult = deployStatus;
463
471
 
464
- if (deployStatus.status === 'FAILURE') {
465
- result.succeeded = false;
472
+ if (deployStatus.status === 'FAILURE') {
473
+ result.succeeded = false;
474
+ }
475
+ } else if (!silenceLogs) {
476
+ logger.log(
477
+ i18n(
478
+ `${i18nKey}.pollProjectBuildAndDeploy.unableToFindAutodeployStatus`,
479
+ {
480
+ buildId,
481
+ viewDeploysLink: uiLink(
482
+ i18n(`${i18nKey}.pollProjectBuildAndDeploy.viewDeploys`),
483
+ getProjectActivityUrl(projectConfig.name, accountId)
484
+ ),
485
+ }
486
+ )
487
+ );
466
488
  }
467
489
  }
468
490
 
@@ -588,17 +610,6 @@ const makePollTaskStatusFunc = ({
588
610
  statusStrings,
589
611
  linkToHubSpot,
590
612
  }) => {
591
- const isTaskComplete = task => {
592
- if (
593
- !task[statusText.SUBTASK_KEY].length ||
594
- task.status === statusText.STATES.FAILURE
595
- ) {
596
- return true;
597
- } else if (task.status === statusText.STATES.SUCCESS) {
598
- return task.isAutoDeployEnabled ? !!task.deployStatusTaskLocator : true;
599
- }
600
- };
601
-
602
613
  return async (
603
614
  accountId,
604
615
  taskName,
@@ -635,9 +646,9 @@ const makePollTaskStatusFunc = ({
635
646
 
636
647
  const tasksById = initialTaskStatus[statusText.SUBTASK_KEY].reduce(
637
648
  (acc, task) => {
638
- const type = task[statusText.TYPE_KEY];
639
- if (type !== 'APP_ID' && type !== 'SERVERLESS_PKG') {
640
- acc[task.id] = task;
649
+ const { id, visible } = task;
650
+ if (visible) {
651
+ acc[id] = task;
641
652
  }
642
653
  return acc;
643
654
  },
@@ -677,7 +688,7 @@ const makePollTaskStatusFunc = ({
677
688
  const formattedTaskType = PROJECT_TASK_TYPES[taskType]
678
689
  ? `[${PROJECT_TASK_TYPES[taskType]}]`
679
690
  : '';
680
- const text = `${statusText.STATUS_TEXT} ${chalk.bold(
691
+ const text = `${indent <= 2 ? statusText.STATUS_TEXT : ''} ${chalk.bold(
681
692
  taskName
682
693
  )} ${formattedTaskType} ...${newline ? '\n' : ''}`;
683
694
 
@@ -703,13 +714,7 @@ const makePollTaskStatusFunc = ({
703
714
  try {
704
715
  taskStatus = await statusFn(accountId, taskName, taskId);
705
716
  } catch (e) {
706
- logApiErrorInstance(
707
- e,
708
- new ApiErrorContext({
709
- accountId,
710
- projectName: taskName,
711
- })
712
- );
717
+ logger.debug(e);
713
718
  return reject(
714
719
  new Error(
715
720
  i18n(
@@ -785,61 +790,64 @@ const makePollTaskStatusFunc = ({
785
790
  }
786
791
  });
787
792
 
788
- if (isTaskComplete(taskStatus)) {
789
- if (status === statusText.STATES.SUCCESS) {
790
- SpinniesManager.succeed(overallTaskSpinniesKey, {
791
- text: statusStrings.SUCCESS(taskName, displayId),
792
- });
793
- } else if (status === statusText.STATES.FAILURE) {
794
- SpinniesManager.fail(overallTaskSpinniesKey, {
795
- text: statusStrings.FAIL(taskName, displayId),
796
- });
797
-
798
- if (!silenceLogs) {
799
- const failedSubtasks = subTaskStatus.filter(
800
- subtask => subtask.status === 'FAILURE'
801
- );
802
-
803
- uiLine();
793
+ if (status === statusText.STATES.SUCCESS) {
794
+ SpinniesManager.succeed(overallTaskSpinniesKey, {
795
+ text: statusStrings.SUCCESS(taskName, displayId),
796
+ });
797
+ clearInterval(pollInterval);
798
+ resolve(taskStatus);
799
+ } else if (status === statusText.STATES.FAILURE) {
800
+ SpinniesManager.fail(overallTaskSpinniesKey, {
801
+ text: statusStrings.FAIL(taskName, displayId),
802
+ });
803
+
804
+ if (!silenceLogs) {
805
+ const failedSubtasks = subTaskStatus.filter(
806
+ subtask => subtask.status === 'FAILURE'
807
+ );
808
+
809
+ uiLine();
810
+ logger.log(
811
+ `${statusStrings.SUBTASK_FAIL(
812
+ displayId,
813
+ failedSubtasks.length === 1
814
+ ? failedSubtasks[0][statusText.SUBTASK_NAME_KEY]
815
+ : failedSubtasks.length + ' components'
816
+ )}\n`
817
+ );
818
+ logger.log('See below for a summary of errors.');
819
+ uiLine();
820
+
821
+ const displayErrors = failedSubtasks.filter(
822
+ subtask =>
823
+ subtask.standardError.subCategory !==
824
+ PROJECT_ERROR_TYPES.SUBBUILD_FAILED &&
825
+ subtask.standardError.subCategory !==
826
+ PROJECT_ERROR_TYPES.SUBDEPLOY_FAILED
827
+ );
828
+
829
+ displayErrors.forEach(subTask => {
804
830
  logger.log(
805
- `${statusStrings.SUBTASK_FAIL(
806
- displayId,
807
- failedSubtasks.length === 1
808
- ? failedSubtasks[0][statusText.SUBTASK_NAME_KEY]
809
- : failedSubtasks.length + ' components'
810
- )}\n`
811
- );
812
- logger.log('See below for a summary of errors.');
813
- uiLine();
814
-
815
- const displayErrors = failedSubtasks.filter(
816
- subtask =>
817
- subtask.standardError.subCategory !==
818
- PROJECT_ERROR_TYPES.SUBBUILD_FAILED &&
819
- subtask.standardError.subCategory !==
820
- PROJECT_ERROR_TYPES.SUBDEPLOY_FAILED
831
+ `\n--- ${chalk.bold(
832
+ subTask[statusText.SUBTASK_NAME_KEY]
833
+ )} failed with the following error ---`
821
834
  );
822
-
823
- displayErrors.forEach(subTask => {
824
- logger.log(
825
- `\n--- ${chalk.bold(
826
- subTask[statusText.SUBTASK_NAME_KEY]
827
- )} failed with the following error ---`
828
- );
829
- logger.error(subTask.errorMessage);
830
-
831
- // Log nested errors
832
- if (subTask.standardError && subTask.standardError.errors) {
833
- logger.log();
834
- subTask.standardError.errors.forEach(error => {
835
- logger.log(error.message);
836
- });
837
- }
838
- });
839
- }
835
+ logger.error(subTask.errorMessage);
836
+
837
+ // Log nested errors
838
+ if (subTask.standardError && subTask.standardError.errors) {
839
+ logger.log();
840
+ subTask.standardError.errors.forEach(error => {
841
+ logger.log(error.message);
842
+ });
843
+ }
844
+ });
840
845
  }
841
846
  clearInterval(pollInterval);
842
847
  resolve(taskStatus);
848
+ } else if (!subTaskStatus.length) {
849
+ clearInterval(pollInterval);
850
+ resolve(taskStatus);
843
851
  }
844
852
  }
845
853
  }, POLLING_DELAY);
@@ -847,6 +855,41 @@ const makePollTaskStatusFunc = ({
847
855
  };
848
856
  };
849
857
 
858
+ const pollBuildAutodeployStatus = (accountId, taskName, buildId) => {
859
+ return new Promise((resolve, reject) => {
860
+ let maxIntervals = (30 * 1000) / POLLING_DELAY; // Num of intervals in ~30s
861
+
862
+ const pollInterval = setInterval(async () => {
863
+ let taskStatus;
864
+ try {
865
+ taskStatus = await getBuildStatus(accountId, taskName, buildId);
866
+ } catch (e) {
867
+ logger.debug(e);
868
+ return reject(
869
+ new Error(
870
+ i18n(`${i18nKey}.pollBuildAutodeployStatusError`, { buildId })
871
+ )
872
+ );
873
+ }
874
+
875
+ if (!taskStatus || !taskStatus.status) {
876
+ return reject(
877
+ new Error(
878
+ i18n(`${i18nKey}.pollBuildAutodeployStatusError`, { buildId })
879
+ )
880
+ );
881
+ }
882
+
883
+ if (taskStatus.deployStatusTaskLocator || maxIntervals <= 0) {
884
+ clearInterval(pollInterval);
885
+ resolve(taskStatus);
886
+ } else {
887
+ maxIntervals -= 1;
888
+ }
889
+ }, POLLING_DELAY);
890
+ });
891
+ };
892
+
850
893
  const pollBuildStatus = makePollTaskStatusFunc({
851
894
  linkToHubSpot: (accountId, taskName, taskId) =>
852
895
  uiLink(
@@ -45,9 +45,6 @@ const hubspotAccountNamePrompt = ({ accountType, currentPortalCount = 0 }) => {
45
45
  promptMessageString = isDevelopmentSandbox
46
46
  ? i18n(`${i18nKey}.enterDevelopmentSandboxName`)
47
47
  : i18n(`${i18nKey}.enterStandardSandboxName`);
48
- defaultName = i18n(`${i18nKey}.sandboxDefaultName`, {
49
- sandboxType: isDevelopmentSandbox ? 'development' : 'standard',
50
- });
51
48
  } else if (isDeveloperTestAccount) {
52
49
  promptMessageString = i18n(`${i18nKey}.enterDeveloperTestAccountName`);
53
50
  defaultName = i18n(`${i18nKey}.developerTestAccountDefaultName`, {
@@ -62,7 +59,7 @@ const hubspotAccountNamePrompt = ({ accountType, currentPortalCount = 0 }) => {
62
59
  validate(val) {
63
60
  if (typeof val !== 'string') {
64
61
  return i18n(`${i18nKey}.errors.invalidName`);
65
- } else if (!val.length) {
62
+ } else if (!val.trim().length) {
66
63
  return i18n(`${i18nKey}.errors.nameRequired`);
67
64
  }
68
65
  return accountNameExistsInConfig(val)
@@ -1,6 +1,10 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { getCwd } = require('@hubspot/local-dev-lib/path');
3
+ const {
4
+ getCwd,
5
+ sanitizeFileName,
6
+ isValidPath,
7
+ } = require('@hubspot/local-dev-lib/path');
4
8
  const { PROJECT_COMPONENT_TYPES } = require('../../lib/constants');
5
9
  const { promptUser } = require('./promptUtils');
6
10
  const { fetchFileFromRepository } = require('@hubspot/local-dev-lib/github');
@@ -79,7 +83,10 @@ const createProjectPrompt = async (
79
83
  message: i18n(`${i18nKey}.enterLocation`),
80
84
  when: !promptOptions.location,
81
85
  default: answers => {
82
- return path.resolve(getCwd(), answers.name || promptOptions.name);
86
+ const projectName = sanitizeFileName(
87
+ answers.name || promptOptions.name
88
+ );
89
+ return path.resolve(getCwd(), projectName);
83
90
  },
84
91
  validate: input => {
85
92
  if (!input) {
@@ -88,6 +95,9 @@ const createProjectPrompt = async (
88
95
  if (fs.existsSync(input)) {
89
96
  return i18n(`${i18nKey}.errors.invalidLocation`);
90
97
  }
98
+ if (!isValidPath(input)) {
99
+ return i18n(`${i18nKey}.errors.invalidCharacters`);
100
+ }
91
101
  return true;
92
102
  },
93
103
  },
@@ -0,0 +1,22 @@
1
+ const { promptUser } = require('./promptUtils');
2
+ const { i18n } = require('../lang');
3
+
4
+ const i18nKey = 'lib.prompts.deployBuildIdPrompt';
5
+
6
+ const deployBuildIdPrompt = (latestBuildId, deployedBuildId, validate) => {
7
+ return promptUser({
8
+ name: 'buildId',
9
+ message: i18n(`${i18nKey}.enterBuildId`),
10
+ default: () => {
11
+ if (latestBuildId === deployedBuildId) {
12
+ return;
13
+ }
14
+ return latestBuildId;
15
+ },
16
+ validate,
17
+ });
18
+ };
19
+
20
+ module.exports = {
21
+ deployBuildIdPrompt,
22
+ };
@@ -12,20 +12,29 @@ const installPublicAppPrompt = async (
12
12
  targetAccountId,
13
13
  clientId,
14
14
  scopes,
15
- redirectUrls
15
+ redirectUrls,
16
+ isReinstall = false
16
17
  ) => {
17
18
  logger.log('');
18
- logger.log(i18n(`${i18nKey}.explanation`));
19
+ if (isReinstall) {
20
+ logger.log(i18n(`${i18nKey}.reinstallExplanation`));
21
+ } else {
22
+ logger.log(i18n(`${i18nKey}.explanation`));
23
+ }
19
24
 
20
25
  const { shouldOpenBrowser } = await promptUser({
21
26
  name: 'shouldOpenBrowser',
22
27
  type: 'confirm',
23
- message: i18n(`${i18nKey}.prompt`),
28
+ message: i18n(
29
+ isReinstall ? `${i18nKey}.reinstallPrompt` : `${i18nKey}.prompt`
30
+ ),
24
31
  });
25
32
 
26
- if (!shouldOpenBrowser) {
33
+ if (!isReinstall && !shouldOpenBrowser) {
27
34
  logger.log(i18n(`${i18nKey}.decline`));
28
35
  process.exit(EXIT_CODES.SUCCESS);
36
+ } else if (!shouldOpenBrowser) {
37
+ return;
29
38
  }
30
39
 
31
40
  const websiteOrigin = getHubSpotWebsiteOrigin(env);
@@ -10,17 +10,35 @@ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
10
10
 
11
11
  const i18nKey = 'lib.prompts.selectPublicAppPrompt';
12
12
 
13
- const fetchPublicAppOptions = async (accountId, accountName) => {
13
+ const fetchPublicAppOptions = async (
14
+ accountId,
15
+ accountName,
16
+ isMigratingApp = false
17
+ ) => {
14
18
  try {
15
19
  const publicApps = await fetchPublicAppsForPortal(accountId);
16
20
  const filteredPublicApps = publicApps.filter(
17
21
  app => !app.projectId && !app.sourceId
18
22
  );
19
23
 
20
- if (!filteredPublicApps.length) {
24
+ if (
25
+ !filteredPublicApps.length ||
26
+ (isMigratingApp &&
27
+ !filteredPublicApps.some(
28
+ app => !app.preventProjectMigrations || !app.listingInfo
29
+ ))
30
+ ) {
31
+ const headerTranslationKey = isMigratingApp
32
+ ? 'noAppsMigration'
33
+ : 'noAppsClone';
34
+ const messageTranslationKey = isMigratingApp
35
+ ? 'noAppsMigrationMessage'
36
+ : 'noAppsCloneMessage';
21
37
  uiLine();
22
- logger.error(i18n(`${i18nKey}.errors.noApps`));
23
- logger.log(i18n(`${i18nKey}.errors.noAppsMessage`, { accountName }));
38
+ logger.error(i18n(`${i18nKey}.errors.${headerTranslationKey}`));
39
+ logger.log(
40
+ i18n(`${i18nKey}.errors.${messageTranslationKey}`, { accountName })
41
+ );
24
42
  uiLine();
25
43
  process.exit(EXIT_CODES.SUCCESS);
26
44
  }
@@ -35,10 +53,16 @@ const fetchPublicAppOptions = async (accountId, accountName) => {
35
53
  const selectPublicAppPrompt = async ({
36
54
  accountId,
37
55
  accountName,
38
- migrateApp = false,
56
+ isMigratingApp = false,
39
57
  }) => {
40
- const publicApps = await fetchPublicAppOptions(accountId, accountName);
41
- const translationKey = migrateApp ? 'selectAppIdMigrate' : 'selectAppIdClone';
58
+ const publicApps = await fetchPublicAppOptions(
59
+ accountId,
60
+ accountName,
61
+ isMigratingApp
62
+ );
63
+ const translationKey = isMigratingApp
64
+ ? 'selectAppIdMigrate'
65
+ : 'selectAppIdClone';
42
66
 
43
67
  return promptUser([
44
68
  {
@@ -48,10 +72,11 @@ const selectPublicAppPrompt = async ({
48
72
  }),
49
73
  type: 'list',
50
74
  choices: publicApps.map(app => {
51
- if (app.listingInfo) {
75
+ const { preventProjectMigrations, listingInfo } = app;
76
+ if (isMigratingApp && preventProjectMigrations && listingInfo) {
52
77
  return {
53
78
  name: `${app.name} (${app.id})`,
54
- disabled: i18n(`${i18nKey}.errors.marketplaceApp`),
79
+ disabled: i18n(`${i18nKey}.errors.cannotBeMigrated`),
55
80
  };
56
81
  }
57
82
  return {
@@ -64,6 +89,5 @@ const selectPublicAppPrompt = async ({
64
89
  };
65
90
 
66
91
  module.exports = {
67
- fetchPublicAppOptions,
68
92
  selectPublicAppPrompt,
69
93
  };