@hubspot/cli 5.2.1-beta.8 → 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.
Files changed (43) hide show
  1. package/commands/auth.js +2 -4
  2. package/commands/functions/deploy.js +2 -2
  3. package/commands/init.js +2 -4
  4. package/commands/module/marketplace-validate.js +1 -1
  5. package/commands/project/__tests__/deploy.test.js +432 -0
  6. package/commands/project/cloneApp.js +208 -0
  7. package/commands/project/deploy.js +82 -27
  8. package/commands/project/listBuilds.js +1 -1
  9. package/commands/project/logs.js +1 -1
  10. package/commands/project/migrateApp.js +85 -30
  11. package/commands/project/upload.js +1 -1
  12. package/commands/project.js +15 -12
  13. package/commands/sandbox/create.js +17 -48
  14. package/commands/sandbox/delete.js +5 -2
  15. package/commands/sandbox/sync.js +12 -2
  16. package/commands/sandbox.js +2 -1
  17. package/commands/theme/marketplace-validate.js +1 -1
  18. package/lang/en.lyaml +77 -76
  19. package/lib/LocalDevManager.js +59 -11
  20. package/lib/buildAccount.js +3 -3
  21. package/lib/constants.js +3 -1
  22. package/lib/interpolationHelpers.js +3 -0
  23. package/lib/localDev.js +21 -11
  24. package/lib/marketplace-validate.js +11 -3
  25. package/lib/polling.js +16 -10
  26. package/lib/projects.js +143 -100
  27. package/lib/prompts/accountNamePrompt.js +78 -0
  28. package/lib/prompts/activeInstallConfirmationPrompt.js +20 -0
  29. package/lib/prompts/createProjectPrompt.js +12 -2
  30. package/lib/prompts/deployBuildIdPrompt.js +22 -0
  31. package/lib/prompts/installPublicAppPrompt.js +13 -4
  32. package/lib/prompts/personalAccessKeyPrompt.js +2 -2
  33. package/lib/prompts/sandboxesPrompt.js +12 -41
  34. package/lib/prompts/selectPublicAppPrompt.js +41 -22
  35. package/lib/sandboxSync.js +49 -68
  36. package/lib/sandboxes.js +8 -149
  37. package/lib/serverlessLogs.js +2 -2
  38. package/lib/ui/index.js +74 -0
  39. package/package.json +5 -4
  40. package/lib/prompts/buildIdPrompt.js +0 -35
  41. package/lib/prompts/developerTestAccountNamePrompt.js +0 -29
  42. package/lib/prompts/enterAccountNamePrompt.js +0 -33
  43. package/lib/ui/CliProgressMultibarManager.js +0 -66
@@ -13,7 +13,7 @@ const {
13
13
  } = require('@hubspot/local-dev-lib/config');
14
14
  const { logger } = require('@hubspot/local-dev-lib/logger');
15
15
  const { i18n } = require('./lang');
16
- const { enterAccountNamePrompt } = require('./prompts/enterAccountNamePrompt');
16
+ const { cliAccountNamePrompt } = require('./prompts/accountNamePrompt');
17
17
  const SpinniesManager = require('./ui/SpinniesManager');
18
18
  const {
19
19
  debugErrorAndContext,
@@ -65,11 +65,11 @@ async function saveAccountToConfig({
65
65
  if (!force) {
66
66
  logger.log('');
67
67
  logger.warn(
68
- i18n(`lib.prompts.enterAccountNamePrompt.errors.accountNameExists`, {
68
+ i18n(`lib.prompts.accountNamePrompt.errors.accountNameExists`, {
69
69
  name: nameForConfig,
70
70
  })
71
71
  );
72
- const { name: promptName } = await enterAccountNamePrompt(
72
+ const { name: promptName } = await cliAccountNamePrompt(
73
73
  nameForConfig + `_${accountId}`
74
74
  );
75
75
  validName = promptName;
package/lib/constants.js CHANGED
@@ -25,6 +25,7 @@ const POLLING_DELAY = 2000;
25
25
  const POLLING_STATUS = {
26
26
  SUCCESS: 'SUCCESS',
27
27
  ERROR: 'ERROR',
28
+ REVERTED: 'REVERTED',
28
29
  FAILURE: 'FAILURE',
29
30
  };
30
31
 
@@ -65,8 +66,9 @@ const PROJECT_ERROR_TYPES = {
65
66
  };
66
67
  const PROJECT_TASK_TYPES = {
67
68
  PRIVATE_APP: 'private app',
69
+ PUBLIC_APP: 'public app',
68
70
  APP_FUNCTION: 'function',
69
- CRM_CARD_V2: 'crm card',
71
+ CRM_CARD_V2: 'card',
70
72
  };
71
73
  const PROJECT_COMPONENT_TYPES = {
72
74
  PROJECTS: 'projects',
@@ -20,4 +20,7 @@ module.exports = {
20
20
  cyan: function(stringValue) {
21
21
  return chalk.cyan(stringValue);
22
22
  },
23
+ orange: function(stringValue) {
24
+ return chalk.hex('#FC9900')(stringValue);
25
+ },
23
26
  };
package/lib/localDev.js CHANGED
@@ -8,18 +8,17 @@ const {
8
8
  isSpecifiedError,
9
9
  } = require('@hubspot/local-dev-lib/errors/apiErrors');
10
10
  const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls');
11
- const { getAccountConfig } = require('@hubspot/local-dev-lib/config');
11
+ const { getAccountConfig, getEnv } = require('@hubspot/local-dev-lib/config');
12
12
  const { createProject } = require('@hubspot/local-dev-lib/api/projects');
13
+ const {
14
+ ENVIRONMENTS,
15
+ } = require('@hubspot/local-dev-lib/constants/environments');
13
16
  const {
14
17
  confirmDefaultAccountPrompt,
15
18
  selectSandboxTargetAccountPrompt,
16
19
  selectDeveloperTestTargetAccountPrompt,
17
20
  confirmUseExistingDeveloperTestAccountPrompt,
18
21
  } = require('./prompts/projectDevTargetAccountPrompt');
19
- const { sandboxNamePrompt } = require('./prompts/sandboxesPrompt');
20
- const {
21
- developerTestAccountNamePrompt,
22
- } = require('./prompts/developerTestAccountNamePrompt');
23
22
  const { confirmPrompt } = require('./prompts/promptUtils');
24
23
  const {
25
24
  validateSandboxUsageLimits,
@@ -56,6 +55,7 @@ const {
56
55
  PERSONAL_ACCESS_KEY_AUTH_METHOD,
57
56
  } = require('@hubspot/local-dev-lib/constants/auth');
58
57
  const { buildNewAccount, saveAccountToConfig } = require('./buildAccount');
58
+ const { hubspotAccountNamePrompt } = require('./prompts/accountNamePrompt');
59
59
 
60
60
  const i18nKey = 'lib.localDev';
61
61
 
@@ -163,9 +163,9 @@ const createSandboxForLocalDev = async (accountId, accountConfig, env) => {
163
163
  process.exit(EXIT_CODES.ERROR);
164
164
  }
165
165
  try {
166
- const { name } = await sandboxNamePrompt(
167
- HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX
168
- );
166
+ const { name } = await hubspotAccountNamePrompt({
167
+ accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
168
+ });
169
169
 
170
170
  trackCommandMetadataUsage(
171
171
  'sandbox-create',
@@ -192,8 +192,7 @@ const createSandboxForLocalDev = async (accountId, accountConfig, env) => {
192
192
  parentAccountConfig: accountConfig,
193
193
  env,
194
194
  syncTasks,
195
- allowEarlyTermination: false, // Don't let user terminate early in this flow
196
- skipPolling: true, // Skip polling, sync will run and complete in the background
195
+ slimInfoMessage: true,
197
196
  });
198
197
  return targetAccountId;
199
198
  } catch (err) {
@@ -242,7 +241,10 @@ const createDeveloperTestAccountForLocalDev = async (
242
241
  }
243
242
 
244
243
  try {
245
- const { name } = await developerTestAccountNamePrompt(currentPortalCount);
244
+ const { name } = await hubspotAccountNamePrompt({
245
+ currentPortalCount,
246
+ accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST,
247
+ });
246
248
  trackCommandMetadataUsage(
247
249
  'developer-test-account-create',
248
250
  { step: 'project-dev' },
@@ -427,6 +429,13 @@ const createInitialBuildForNewProject = async (
427
429
  return initialUploadResult.buildResult;
428
430
  };
429
431
 
432
+ const getAccountHomeUrl = accountId => {
433
+ const baseUrl = getHubSpotWebsiteOrigin(
434
+ getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD
435
+ );
436
+ return `${baseUrl}/home?portalId=${accountId}`;
437
+ };
438
+
430
439
  module.exports = {
431
440
  confirmDefaultAccountIsTarget,
432
441
  checkIfAppDeveloperAccount,
@@ -437,4 +446,5 @@ module.exports = {
437
446
  useExistingDevTestAccount,
438
447
  createNewProjectForLocalDev,
439
448
  createInitialBuildForNewProject,
449
+ getAccountHomeUrl,
440
450
  };
@@ -57,12 +57,20 @@ const fetchValidationResults = async (accountId, validationId) => {
57
57
  }
58
58
  };
59
59
 
60
- const processValidationErrors = validationResults => {
60
+ const processValidationErrors = (i18nKey, validationResults) => {
61
61
  if (validationResults.errors.length) {
62
- const { errors } = validationResults;
62
+ const { assetPath, errors } = validationResults;
63
63
 
64
64
  errors.forEach(err => {
65
- logger.error(`${err.context}`);
65
+ if (err.failureReasonType === 'DOWNLOAD_EMPTY') {
66
+ logger.error(
67
+ i18n(`${i18nKey}.errors.invalidPath`, {
68
+ path: assetPath,
69
+ })
70
+ );
71
+ } else {
72
+ logger.error(`${err.context}`);
73
+ }
66
74
  });
67
75
  process.exit(EXIT_CODES.ERROR);
68
76
  }
package/lib/polling.js CHANGED
@@ -3,18 +3,24 @@ const { POLLING_DELAY, POLLING_STATUS } = require('./constants');
3
3
  const poll = (callback, accountId, taskId) => {
4
4
  return new Promise((resolve, reject) => {
5
5
  const pollInterval = setInterval(async () => {
6
- const pollResp = await callback(accountId, taskId);
7
- const { status } = pollResp;
6
+ try {
7
+ const pollResp = await callback(accountId, taskId);
8
+ const { status } = pollResp;
8
9
 
9
- if (status === POLLING_STATUS.SUCCESS) {
10
+ if (status === POLLING_STATUS.SUCCESS) {
11
+ clearInterval(pollInterval);
12
+ resolve(pollResp);
13
+ } else if (
14
+ status === POLLING_STATUS.ERROR ||
15
+ status === POLLING_STATUS.REVERTED ||
16
+ status === POLLING_STATUS.FAILURE
17
+ ) {
18
+ clearInterval(pollInterval);
19
+ reject(pollResp);
20
+ }
21
+ } catch (error) {
10
22
  clearInterval(pollInterval);
11
- resolve(pollResp);
12
- } else if (
13
- status === POLLING_STATUS.ERROR ||
14
- status === POLLING_STATUS.FAILURE
15
- ) {
16
- clearInterval(pollInterval);
17
- reject(pollResp);
23
+ reject(error);
18
24
  }
19
25
  }, POLLING_DELAY);
20
26
  });
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(
@@ -0,0 +1,78 @@
1
+ const { accountNameExistsInConfig } = require('@hubspot/local-dev-lib/config');
2
+ const { STRING_WITH_NO_SPACES_REGEX } = require('../regex');
3
+ const { promptUser } = require('./promptUtils');
4
+ const { i18n } = require('../lang');
5
+ const {
6
+ HUBSPOT_ACCOUNT_TYPES,
7
+ } = require('@hubspot/local-dev-lib/constants/config');
8
+
9
+ const i18nKey = 'lib.prompts.accountNamePrompt';
10
+
11
+ const getCliAccountNamePromptConfig = defaultName => ({
12
+ name: 'name',
13
+ message: i18n(`${i18nKey}.enterAccountName`),
14
+ default: defaultName,
15
+ validate(val) {
16
+ if (typeof val !== 'string') {
17
+ return i18n(`${i18nKey}.errors.invalidName`);
18
+ } else if (!val.length) {
19
+ return i18n(`${i18nKey}.errors.nameRequired`);
20
+ } else if (!STRING_WITH_NO_SPACES_REGEX.test(val)) {
21
+ return i18n(`${i18nKey}.errors.spacesInName`);
22
+ }
23
+ return accountNameExistsInConfig(val)
24
+ ? i18n(`${i18nKey}.errors.accountNameExists`, { name: val })
25
+ : true;
26
+ },
27
+ });
28
+
29
+ const cliAccountNamePrompt = defaultName => {
30
+ return promptUser(getCliAccountNamePromptConfig(defaultName));
31
+ };
32
+
33
+ const hubspotAccountNamePrompt = ({ accountType, currentPortalCount = 0 }) => {
34
+ const isDevelopmentSandbox =
35
+ accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX;
36
+ const isSandbox =
37
+ accountType === HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX ||
38
+ isDevelopmentSandbox;
39
+ const isDeveloperTestAccount =
40
+ accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST;
41
+
42
+ let promptMessageString;
43
+ let defaultName;
44
+ if (isSandbox) {
45
+ promptMessageString = isDevelopmentSandbox
46
+ ? i18n(`${i18nKey}.enterDevelopmentSandboxName`)
47
+ : i18n(`${i18nKey}.enterStandardSandboxName`);
48
+ } else if (isDeveloperTestAccount) {
49
+ promptMessageString = i18n(`${i18nKey}.enterDeveloperTestAccountName`);
50
+ defaultName = i18n(`${i18nKey}.developerTestAccountDefaultName`, {
51
+ count: currentPortalCount + 1,
52
+ });
53
+ }
54
+
55
+ return promptUser([
56
+ {
57
+ name: 'name',
58
+ message: promptMessageString,
59
+ validate(val) {
60
+ if (typeof val !== 'string') {
61
+ return i18n(`${i18nKey}.errors.invalidName`);
62
+ } else if (!val.trim().length) {
63
+ return i18n(`${i18nKey}.errors.nameRequired`);
64
+ }
65
+ return accountNameExistsInConfig(val)
66
+ ? i18n(`${i18nKey}.errors.accountNameExists`, { name: val })
67
+ : true;
68
+ },
69
+ default: defaultName,
70
+ },
71
+ ]);
72
+ };
73
+
74
+ module.exports = {
75
+ getCliAccountNamePromptConfig,
76
+ cliAccountNamePrompt,
77
+ hubspotAccountNamePrompt,
78
+ };
@@ -0,0 +1,20 @@
1
+ const { promptUser } = require('./promptUtils');
2
+ const { i18n } = require('../lang');
3
+
4
+ const i18nKey = 'lib.prompts.activeInstallConfirmationPrompt';
5
+
6
+ const activeInstallConfirmationPrompt = async () => {
7
+ const { proceed } = await promptUser([
8
+ {
9
+ name: 'proceed',
10
+ message: i18n(`${i18nKey}.message`),
11
+ type: 'confirm',
12
+ default: false,
13
+ },
14
+ ]);
15
+ return proceed;
16
+ };
17
+
18
+ module.exports = {
19
+ activeInstallConfirmationPrompt,
20
+ };
@@ -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
  },