@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
@@ -9,7 +9,7 @@ const { loadAndValidateOptions } = require('../../lib/validation');
9
9
  const { i18n } = require('../../lib/lang');
10
10
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
11
11
  const { getAccountConfig, getEnv } = require('@hubspot/local-dev-lib/config');
12
- const { uiFeatureHighlight, uiAccountDescription } = require('../../lib/ui');
12
+ const { uiFeatureHighlight, uiBetaTag } = require('../../lib/ui');
13
13
  const {
14
14
  sandboxTypeMap,
15
15
  getAvailableSyncTypes,
@@ -18,14 +18,8 @@ const {
18
18
  } = require('../../lib/sandboxes');
19
19
  const { getValidEnv } = require('@hubspot/local-dev-lib/environment');
20
20
  const { logger } = require('@hubspot/local-dev-lib/logger');
21
- const {
22
- trackCommandUsage,
23
- trackCommandMetadataUsage,
24
- } = require('../../lib/usageTracking');
25
- const {
26
- sandboxTypePrompt,
27
- sandboxNamePrompt,
28
- } = require('../../lib/prompts/sandboxesPrompt');
21
+ const { trackCommandUsage } = require('../../lib/usageTracking');
22
+ const { sandboxTypePrompt } = require('../../lib/prompts/sandboxesPrompt');
29
23
  const { promptUser } = require('../../lib/prompts/promptUtils');
30
24
  const { syncSandbox } = require('../../lib/sandboxSync');
31
25
  const { logErrorInstance } = require('../../lib/errorHandlers/standardErrors');
@@ -38,11 +32,14 @@ const {
38
32
  HUBSPOT_ACCOUNT_TYPE_STRINGS,
39
33
  } = require('@hubspot/local-dev-lib/constants/config');
40
34
  const { buildNewAccount } = require('../../lib/buildAccount');
35
+ const {
36
+ hubspotAccountNamePrompt,
37
+ } = require('../../lib/prompts/accountNamePrompt');
41
38
 
42
39
  const i18nKey = 'commands.sandbox.subcommands.create';
43
40
 
44
41
  exports.command = 'create [--name] [--type]';
45
- exports.describe = i18n(`${i18nKey}.describe`);
42
+ exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
46
43
 
47
44
  exports.handler = async options => {
48
45
  await loadAndValidateOptions(options);
@@ -112,7 +109,7 @@ exports.handler = async options => {
112
109
 
113
110
  if (!name) {
114
111
  if (!force) {
115
- namePrompt = await sandboxNamePrompt(sandboxType);
112
+ namePrompt = await hubspotAccountNamePrompt({ accountType: sandboxType });
116
113
  } else {
117
114
  logger.error(i18n(`${i18nKey}.failure.optionMissing.name`));
118
115
  process.exit(EXIT_CODES.ERROR);
@@ -120,34 +117,18 @@ exports.handler = async options => {
120
117
  }
121
118
  const sandboxName = name || namePrompt.name;
122
119
 
123
- let sandboxSyncPromptResult = true;
124
- let contactRecordsSyncPromptResult = true;
120
+ let contactRecordsSyncPromptResult = false;
125
121
  if (!force) {
126
- const syncI18nKey = 'lib.sandbox.sync';
127
- const sandboxLangKey =
128
- sandboxType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX
129
- ? 'developer'
130
- : 'standard';
131
- const { sandboxSyncPrompt } = await promptUser([
132
- {
133
- name: 'sandboxSyncPrompt',
134
- type: 'confirm',
135
- message: i18n(`${syncI18nKey}.confirm.createFlow.${sandboxLangKey}`, {
136
- parentAccountName: uiAccountDescription(accountId),
137
- sandboxName,
138
- }),
139
- },
140
- ]);
141
- sandboxSyncPromptResult = sandboxSyncPrompt;
142
- // We can prompt for contact records before fetching types since we're starting with a fresh sandbox in create
143
- if (sandboxSyncPrompt) {
122
+ const isStandardSandbox =
123
+ sandboxType === HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX;
124
+
125
+ // Prompt to sync contact records for standard sandboxes only
126
+ if (isStandardSandbox) {
144
127
  const { contactRecordsSyncPrompt } = await promptUser([
145
128
  {
146
129
  name: 'contactRecordsSyncPrompt',
147
130
  type: 'confirm',
148
- message: i18n(
149
- `${syncI18nKey}.confirm.syncContactRecords.${sandboxLangKey}`
150
- ),
131
+ message: i18n('lib.sandbox.sync.confirm.syncContactRecords.standard'),
151
132
  },
152
133
  ]);
153
134
  contactRecordsSyncPromptResult = contactRecordsSyncPrompt;
@@ -163,15 +144,8 @@ exports.handler = async options => {
163
144
  force,
164
145
  });
165
146
 
166
- // Prompt user to sync assets after sandbox creation
167
147
  const sandboxAccountConfig = getAccountConfig(result.sandbox.sandboxHubId);
168
148
  const handleSyncSandbox = async syncTasks => {
169
- // Send tracking event for secondary action, in this case a sandbox sync within the sandbox create flow
170
- trackCommandMetadataUsage(
171
- 'sandbox-sync',
172
- { step: 'sandbox-create' },
173
- result.sandbox.sandboxHubId
174
- );
175
149
  await syncSandbox({
176
150
  accountConfig: sandboxAccountConfig,
177
151
  parentAccountConfig: accountConfig,
@@ -184,18 +158,13 @@ exports.handler = async options => {
184
158
  accountConfig,
185
159
  sandboxAccountConfig
186
160
  );
161
+
187
162
  if (!contactRecordsSyncPromptResult) {
188
163
  availableSyncTasks = availableSyncTasks.filter(
189
164
  t => t.type !== syncTypes.OBJECT_RECORDS
190
165
  );
191
166
  }
192
- if (!force) {
193
- if (sandboxSyncPromptResult) {
194
- await handleSyncSandbox(availableSyncTasks);
195
- }
196
- } else {
197
- await handleSyncSandbox(availableSyncTasks);
198
- }
167
+ await handleSyncSandbox(availableSyncTasks);
199
168
  } catch (err) {
200
169
  logErrorInstance(err);
201
170
  throw err;
@@ -33,12 +33,15 @@ const { promptUser } = require('../../lib/prompts/promptUtils');
33
33
  const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls');
34
34
 
35
35
  const { getValidEnv } = require('@hubspot/local-dev-lib/environment');
36
- const { uiAccountDescription } = require('../../lib/ui');
36
+ const { uiAccountDescription, uiBetaTag } = require('../../lib/ui');
37
37
 
38
38
  const i18nKey = 'commands.sandbox.subcommands.delete';
39
39
 
40
40
  exports.command = 'delete [--account]';
41
- exports.describe = i18n(`${i18nKey}.describe`);
41
+ exports.describe = exports.describe = uiBetaTag(
42
+ i18n(`${i18nKey}.describe`),
43
+ false
44
+ );
42
45
 
43
46
  exports.handler = async options => {
44
47
  await loadAndValidateOptions(options, false);
@@ -13,7 +13,12 @@ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
13
13
  const { getAccountConfig, getEnv } = require('@hubspot/local-dev-lib/config');
14
14
  const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls');
15
15
  const { promptUser } = require('../../lib/prompts/promptUtils');
16
- const { uiLine, uiAccountDescription } = require('../../lib/ui');
16
+ const {
17
+ uiLine,
18
+ uiAccountDescription,
19
+ uiDeprecatedMessage,
20
+ uiDeprecatedDescription,
21
+ } = require('../../lib/ui');
17
22
  const {
18
23
  isSandbox,
19
24
  isStandardSandbox,
@@ -35,11 +40,16 @@ const {
35
40
  const i18nKey = 'commands.sandbox.subcommands.sync';
36
41
 
37
42
  exports.command = 'sync';
38
- exports.describe = i18n(`${i18nKey}.describe`);
43
+ exports.describe = uiDeprecatedDescription(
44
+ i18n(`${i18nKey}.describe`),
45
+ 'hs sandbox sync'
46
+ );
39
47
 
40
48
  exports.handler = async options => {
41
49
  await loadAndValidateOptions(options);
42
50
 
51
+ uiDeprecatedMessage('hs sandbox sync');
52
+
43
53
  const { force } = options; // For scripting purposes
44
54
  const accountId = getAccountId(options);
45
55
  const accountConfig = getAccountConfig(accountId);
@@ -1,5 +1,6 @@
1
1
  const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts');
2
2
  const { i18n } = require('../lib/lang');
3
+ const { uiBetaTag } = require('../lib/ui');
3
4
  const create = require('./sandbox/create');
4
5
  const del = require('./sandbox/delete');
5
6
  const sync = require('./sandbox/sync');
@@ -7,7 +8,7 @@ const sync = require('./sandbox/sync');
7
8
  const i18nKey = 'commands.sandbox';
8
9
 
9
10
  exports.command = 'sandbox';
10
- exports.describe = i18n(`${i18nKey}.describe`);
11
+ exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
11
12
 
12
13
  exports.builder = yargs => {
13
14
  addConfigOptions(yargs);
@@ -48,7 +48,7 @@ exports.handler = async options => {
48
48
  accountId,
49
49
  validationId
50
50
  );
51
- processValidationErrors(validationResults);
51
+ processValidationErrors(i18nKey, validationResults);
52
52
  displayValidationResults(i18nKey, validationResults);
53
53
 
54
54
  process.exit();
package/lang/en.lyaml CHANGED
@@ -514,20 +514,38 @@ en:
514
514
  done: "Converting app configuration to public-app.json component definition ... DONE"
515
515
  failure: "Converting app configuration to public-app.json component definition ... FAILED"
516
516
  warning:
517
- title: "{{#bold}}Migrate an app to the projects framework?{{/bold}}"
518
- projectConversion: "{{#bold}}The selected app will be permanently converted to a project component.{{/bold}}"
517
+ title: "{{#bold}}Migrate {{appName}} to the projects framework?{{/bold}}"
518
+ projectConversion: "{{#bold}}The selected app will be converted to a project component.{{/bold}}"
519
519
  appConfig: "All supported app configuration will be moved to the {{#bold}}public-app.json{{/bold}} component definition file. Future updates to those features must be made through the project build and deploy pipeline, not the developer account UI."
520
520
  buildAndDeploy: "This will create a new project with a single app component and immediately build and deploy it to your developer account (build #1)."
521
521
  existingApps: "{{#bold}}This will not affect existing app users or installs.{{/bold}}"
522
522
  copyApp: "We strongly recommend making a copy of your app to test this process in a development app before replacing production."
523
- createAppPrompt: "Proceed with migrating this app to a project component?"
523
+ migrationInterrupted: "\nThe command is terminated, but app migration is still in progress. Please check your account to ensure that the project and associated app have been created successfully."
524
+ createAppPrompt: "Proceed with migrating this app to a project component (this process can't be aborted)?"
524
525
  projectDetailsLink: "View project details in your developer account"
525
526
  errors:
526
- invalidAccountType: "Public apps must be migrated within an app developer account. Your current account {{#bold}}{{ accountName }}{{/bold}} is a {{ accountType }}.
527
- \n- Run {{ useCommand }} to switch your default account to an app developer account.
528
- \n- Run {{ authCommand }} to connect an app developer account to the HubSpot CLI.\n"
527
+ invalidAccountTypeTitle: "{{#bold}}Developer account not targeted{{/bold}}"
528
+ invalidAccountTypeDescription: "Only public apps created in a developer account can be converted to a project component. Select a connected developer account with {{useCommand}} or {{authCommand}} and try again."
529
529
  projectAlreadyExists: "A project with name {{ projectName }} already exists. Please choose another name."
530
- invalidAppId: "[--appId] Could not find appId {{ appId }}. Please choose another public app."
530
+ invalidApp: "Could not migrate appId {{ appId }}. This app cannot be migrated at this time. Please choose another public app."
531
+ cloneApp:
532
+ describe: "Clone a public app using the projects framework"
533
+ examples:
534
+ default: "Clone a public app using the projects framework"
535
+ options:
536
+ appId:
537
+ describe: "The ID for the public app being cloned"
538
+ location:
539
+ describe: "Directory where project should be created"
540
+ cloneStatus:
541
+ inProgress: "Cloning app configuration to {{#bold}}public-app.json{{/bold}} component definition ..."
542
+ done: "Cloning app configuration to public-app.json component definition ... DONE"
543
+ success: "Your cloned project was created in {{ location }}"
544
+ failure: "Cloning app configuration to public-app.json component definition ... FAILED"
545
+ errors:
546
+ invalidAccountTypeTitle: "{{#bold}}Developer account not targeted{{/bold}}"
547
+ invalidAccountTypeDescription: "Only public apps created in a developer account can be converted to a project component. Select a connected developer account with {{useCommand}} or {{authCommand}} and try again."
548
+ couldNotWriteConfigPath: "Failed to write project config at {{ configPath }}"
531
549
  add:
532
550
  describe: "Create a new component within a project"
533
551
  options:
@@ -551,12 +569,15 @@ en:
551
569
  deploy: "Deploy error: {{ details }}"
552
570
  noBuilds: "Deploy error: no builds for this project were found."
553
571
  noBuildId: "You must specify a build to deploy"
554
- projectNotFound: "{{ projectName }} does not exist in account {{ accountIdentifier }}. Run {{ command }} to upload your project files to HubSpot."
572
+ projectNotFound: "The project {{ projectName }} does not exist in account {{ accountIdentifier }}. Run {{ command }} to upload your project files to HubSpot."
573
+ buildIdDoesNotExist: "Build {{ buildId }} does not exist for project {{ projectName }}. {{ linkToProject }}"
574
+ buildAlreadyDeployed: "Build {{ buildId }} is already deployed. {{ linkToProject}}"
575
+ viewProjectsBuilds: 'View project builds in HubSpot'
555
576
  examples:
556
577
  default: "Deploy the latest build of the current project"
557
578
  withOptions: "Deploy build 5 of the project my-project"
558
579
  options:
559
- buildId:
580
+ build:
560
581
  describe: "Project build ID to be deployed"
561
582
  project:
562
583
  describe: "Project name"
@@ -959,7 +980,8 @@ en:
959
980
  learnMoreLocalDevServer: "Learn more about the projects local dev server"
960
981
  running: "Running {{#bold}}{{ projectName }}{{/bold}} locally on {{ accountIdentifier }}, waiting for changes ..."
961
982
  quitHelper: "Press {{#bold}}'q'{{/bold}} to stop the local dev server"
962
- viewInHubSpotLink: "View project in HubSpot"
983
+ viewProjectLink: "View project in HubSpot"
984
+ viewTestAccountLink: "View developer test account in HubSpot"
963
985
  exitingStart: "Stopping local dev server ..."
964
986
  exitingSucceed: "Successfully exited"
965
987
  exitingFail: "Failed to cleanup before exiting"
@@ -968,17 +990,15 @@ en:
968
990
  appLabel: "[App]"
969
991
  uiExtensionLabel: "[UI Extension]"
970
992
  missingComponents: "Couldn't find the following components in the deployed build for this project: {{#bold}}'{{ missingComponents }}'{{/bold}}. This may cause issues in local development."
971
- defaultWarning: "Changing project configuration requires a new project build."
972
- defaultPublicAppWarning: "Changing project configuration requires a new project build. This will affect existing users of your public app. If your app has users in production, we strongly recommend creating a copy of this app to test your changes before proceding."
993
+ defaultWarning: "{{#bold}}Changing project configuration requires a new project build.{{/bold}}"
994
+ defaultPublicAppWarning: "{{#bold}}Changing project configuration requires a new project build.{{/bold}}\n\nThis will affect your public app's {{#bold}}{{ installCount }} existing {{ installText }}{{/bold}}. If your app has users in production, we strongly recommend creating a copy of this app to test your changes before proceding."
973
995
  header: "{{ warning }} To reflect these changes and continue testing:"
974
996
  stopDev: " * Stop {{ command }}"
975
997
  runUpload: " * Run {{ command }}"
976
998
  restartDev: " * Re-run {{ command }}"
977
999
  pushToGithub: " * Commit and push your changes to GitHub"
978
1000
  activeInstallWarning:
979
- installCount: "{{#bold}}The app {{ appName }} has {{ installCount }} production installs{{/bold}}"
980
- genericHeader: "{{#bold}}Local development can affect existing installs of your public app.{{/bold}}"
981
- genericExplanation: "Some changes made during local development may need to be synced to HubSpot, which will impact users with existing installs. You will always be asked to confirm these changes before uploading them. If your app has any production installs, we strongly recommend creating a copy of this app to develop on instead."
1001
+ installCount: "{{#bold}}The app {{ appName }} has {{ installCount }} production {{ installText }}{{/bold}}"
982
1002
  explanation: "Some changes made during local development may need to be synced to HubSpot, which will impact those existing installs. We strongly recommend creating a copy of this app to use instead."
983
1003
  confirmation: "You will always be asked to confirm any permanent changes to your app’s configuration before uploading them."
984
1004
  devServer:
@@ -1032,9 +1052,12 @@ en:
1032
1052
  successStatusText: "DONE"
1033
1053
  failedStatusText: "FAILED"
1034
1054
  errorFetchingTaskStatus: "Error fetching {{ taskType }} status"
1055
+ pollBuildAutodeployStatusError: "Error fetching autodeploy status for build #{{ buildId }}"
1035
1056
  pollProjectBuildAndDeploy:
1036
1057
  buildSucceededAutomaticallyDeploying: "Build #{{ buildId }} succeeded. {{#bold}}Automatically deploying{{/bold}} to {{ accountIdentifier }}\n"
1037
1058
  cleanedUpTempFile: "Cleaned up temporary file {{ path }}"
1059
+ viewDeploys: "View all deploys for this project in HubSpot"
1060
+ unableToFindAutodeployStatus: "Unable to find the auto deploy for build #{{ buildId }}. This deploy may have been skipped. {{ viewDeploysLink }}."
1038
1061
  logFeedbackMessage:
1039
1062
  feedbackHeader: "We'd love to hear your feedback!"
1040
1063
  feedbackMessage: "How are you liking the new projects and developer tools? \n > Run `{{#yellow}}hs feedback{{/yellow}}` to let us know what you think!\n"
@@ -1043,6 +1066,14 @@ en:
1043
1066
  betaWarning:
1044
1067
  header: "{{#yellow}}***************************** WARNING ****************************{{/yellow}}"
1045
1068
  footer: "{{#yellow}}******************************************************************{{/yellow}}"
1069
+ infoTag: "{{#bold}}[INFO]{{/bold}}"
1070
+ deprecatedTag: "{{#bold}}[DEPRECATED]{{/bold}}"
1071
+ errorTag: "{{#bold}}[ERROR]{{/bold}}"
1072
+ deprecatedMessage: "The {{ command }} command is deprecated and will be disabled soon. {{ url }}"
1073
+ deprecatedDescription: "{{ message }}. The {{ command }} command is deprecated and will be disabled soon. {{ url }}"
1074
+ deprecatedUrlText: 'Learn more.'
1075
+ disabledMessage: "The {{ command }} command is disabled. Run {{ npmCommand }} to update to the latest HubSpot CLI version. {{ url }}"
1076
+ disabledUrlText: "See all HubSpot CLI commands here."
1046
1077
  featureHighlight:
1047
1078
  defaultTitle: "What's next?"
1048
1079
  commandKeys:
@@ -1143,8 +1174,13 @@ en:
1143
1174
  setAsDefaultAccountMessage: "Set this account as the default?"
1144
1175
  setAsDefaultAccount: "Account \"{{ accountName }}\" set as the default account"
1145
1176
  keepingCurrentDefault: "Account \"{{ accountName }}\" will continue to be the default account"
1146
- enterAccountNamePrompt:
1177
+ accountNamePrompt:
1147
1178
  enterAccountName: "Enter a unique name to reference this account in the CLI:"
1179
+ enterDeveloperTestAccountName: "Name your developer test account:"
1180
+ enterStandardSandboxName: "Name your standard sandbox:"
1181
+ enterDevelopmentSandboxName: "Name your development sandbox:"
1182
+ sandboxDefaultName: "New {{ sandboxType }} sandbox"
1183
+ developerTestAccountDefaultName: "Developer test account {{ count }}"
1148
1184
  errors:
1149
1185
  invalidName: "You entered an invalid name. Please try again."
1150
1186
  nameRequired: "The name may not be blank. Please try again."
@@ -1206,6 +1242,7 @@ en:
1206
1242
  nameRequired: "A project name is required"
1207
1243
  locationRequired: "A project location is required"
1208
1244
  invalidLocation: "The selected destination already exists. Please provide a new path for this project."
1245
+ invalidCharacters: "The selected destination contains invalid characters. Please provide a new path and try again."
1209
1246
  invalidTemplate: "[--template] Could not find template {{ template }}. Please choose an available template."
1210
1247
  noProjectsInConfig: "Please ensure that there is a config.json file that contains a \"projects\" field."
1211
1248
  missingPropertiesInConfig: "Please ensure that each of the projects in your config.json file contain the following properties: [\"name\", \"label\", \"path\", \"insertPath\"]."
@@ -1213,17 +1250,12 @@ en:
1213
1250
  selectAppIdMigrate: "[--appId] Choose an app under {{ accountName }} to migrate:"
1214
1251
  selectAppIdClone: "[--appId] Choose an app under {{ accountName }} to clone:"
1215
1252
  errors:
1216
- noApps: "{{#bold}}No apps to migrate{{/bold}}"
1217
- noAppsMessage: "The selected developer account {{#bold}}{{ accountName }}{{/bold}} doesn't have any apps that can be migrated to the projects framework."
1253
+ noAppsMigration: "{{#bold}}No apps to migrate{{/bold}}"
1254
+ noAppsClone: "{{#bold}}No apps to clone{{/bold}}"
1255
+ noAppsMigrationMessage: "The selected developer account {{#bold}}{{ accountName }}{{/bold}} doesn't have any apps that can be migrated to the projects framework."
1256
+ noAppsCloneMessage: "The selected developer account {{#bold}}{{ accountName }}{{/bold}} doesn't have any apps that can be cloned to the projects framework."
1218
1257
  errorFetchingApps: "There was an error fetching public apps."
1219
- invalidAppId: "[--appId] Could not find appId {{ appId }}. Please choose another public app."
1220
- developerTestAccountPrompt:
1221
- name:
1222
- message: "Name your developer test account"
1223
- errors:
1224
- invalidName: "You entered an invalid name. Please try again."
1225
- nameRequired: "The name may not be blank. Please try again."
1226
- accountNameExists: "Account with name \"{{ name }}\" already exists in the CLI config, please enter a different name."
1258
+ cannotBeMigrated: "Cannot be migrated"
1227
1259
  downloadProjectPrompt:
1228
1260
  selectProject: "Select a project to download:"
1229
1261
  errors:
@@ -1239,19 +1271,12 @@ en:
1239
1271
  errors:
1240
1272
  invalidValue: "You entered an invalid value. Please try again."
1241
1273
  sandboxesPrompt:
1242
- name:
1243
- message: "Name your sandbox"
1244
- developmentSandboxMessage: "Name your development sandbox"
1245
- errors:
1246
- invalidName: "You entered an invalid name. Please try again."
1247
- nameRequired: "The name may not be blank. Please try again."
1248
- accountNameExists: "Account with name \"{{ name }}\" already exists in the CLI config, please enter a different name."
1249
- selectAccountName: "Select the sandbox account you want to delete"
1250
- selectParentAccountName: "Select the account that the sandbox belongs to"
1274
+ selectAccountName: "Select the sandbox account you want to delete"
1275
+ selectParentAccountName: "Select the account that the sandbox belongs to"
1251
1276
  type:
1252
- message: "What type of sandbox would you like to create?"
1253
- developer: "Development sandbox (Isolated environment for developers)"
1254
- standard: "Standard sandbox (Testing environment for all Super Admins)"
1277
+ message: "Choose the type of sandbox you want to create"
1278
+ developer: "Development sandbox (Includes production's object definitions)"
1279
+ standard: "Standard sandbox (Includes partial copy of production's assets)"
1255
1280
  uploadPrompt:
1256
1281
  enterDest: "[--dest] Enter the destination path: "
1257
1282
  enterSrc: "[--src] Enter the source path: "
@@ -1271,11 +1296,8 @@ en:
1271
1296
  general: "[--general] Tell us about your experience with HubSpot's developer tools"
1272
1297
  bugPrompt: "Create an issue on Github in your browser?"
1273
1298
  generalPrompt: "Create an issue on Github in your browser?"
1274
- buildIdPrompt:
1299
+ deployBuildIdPrompt:
1275
1300
  enterBuildId: "[--build] Deploy which build?"
1276
- errors:
1277
- buildIdDoesNotExist: "Build {{ buildId }} does not exist for project {{ projectName }}."
1278
- buildAlreadyDeployed: "Build {{ buildId }} is already deployed."
1279
1301
  previewPrompt:
1280
1302
  enterSrc: "[--src] Enter a local theme directory to preview."
1281
1303
  enterDest: "[--dest] Enter the destination path for the src theme in HubSpot Design Tools."
@@ -1287,8 +1309,12 @@ en:
1287
1309
  message: "You are about to remove any remote files in \"{{ filePath }}\" on HubSpot account {{ accountId }} that don't exist locally. Are you sure you want to do this?"
1288
1310
  installPublicAppPrompt:
1289
1311
  explanation: "Local development requires this app to be installed in the target test account"
1312
+ reinstallExplanation: "This app's required scopes have been updated since it was last installed on the target test account. To avoid issues with local development, we recommend reinstalling the app with the updated scopes."
1290
1313
  prompt: "Open hubspot.com to install this app?"
1314
+ reinstallPrompt: "Open hubspot.com to reinstall this app?"
1291
1315
  decline: "To continue local development of this app, install it in your target test account and re-run {{#bold}}`hs project dev`{{/bold}}"
1316
+ activeInstallConfirmationPrompt:
1317
+ message: "Proceed with local development of this {{#bold}}production{{/bold}} app?"
1292
1318
  convertFields:
1293
1319
  positionals:
1294
1320
  src:
@@ -1321,11 +1347,11 @@ en:
1321
1347
  developer:
1322
1348
  add: "Creating development sandbox {{#bold}}{{ accountName }}{{/bold}}"
1323
1349
  fail: "Failed to create a development sandbox {{#bold}}{{ accountName }}{{/bold}}."
1324
- succeed: "Successfully created a development sandbox {{#bold}}{{ accountName }}{{/bold}} with portalId {{#bold}}{{ accountId }}{{/bold}}."
1350
+ succeed: "Created {{#bold}}{{ accountName }} [dev sandbox] ({{ accountId }}){{/bold}}."
1325
1351
  standard:
1326
1352
  add: "Creating standard sandbox {{#bold}}{{ accountName }}{{/bold}}"
1327
1353
  fail: "Failed to create a standard sandbox {{#bold}}{{ accountName }}{{/bold}}."
1328
- succeed: "Successfully created a standard sandbox {{#bold}}{{ accountName }}{{/bold}} with portalId {{#bold}}{{ accountId }}{{/bold}}."
1354
+ succeed: "Created {{#bold}}{{ accountName }} [standard sandbox] ({{ accountId }}){{/bold}}."
1329
1355
  failure:
1330
1356
  invalidUser: "Couldn't create {{#bold}}{{ accountName }}{{/bold}} because your account has been removed from {{#bold}}{{ parentAccountName }}{{/bold}} or your permission set doesn't allow you to create the sandbox. To update your permissions, contact a super admin in {{#bold}}{{ parentAccountName }}{{/bold}}."
1331
1357
  403Gating: "Couldn't create {{#bold}}{{ accountName }}{{/bold}} because {{#bold}}{{ parentAccountName }}{{/bold}} does not have access to development sandboxes. To opt in to the CRM Development Beta and use development sandboxes, visit https://app.hubspot.com/l/product-updates/in-beta?update=13899236."
@@ -1367,53 +1393,28 @@ en:
1367
1393
  sync:
1368
1394
  info:
1369
1395
  syncStatus: "View the sync status details at: {{#bold}}{{ url }}{{/bold}}"
1370
- earlyExit: "Syncing may take some time. Hit {{#bold}}Enter{{/bold}} or {{#bold}}Ctrl+C{{/bold}} to exit and continue the sync in the background.\n"
1396
+ syncMessage: "Asset sync from production to the sandbox is in progress and is running in the background. It may take some time. {{ url }}"
1397
+ syncMessageDevSb: "Sync of object definitions from production to the sandbox is in progress and is running in the background. It may take some time. {{ url }}"
1398
+ syncStatusDetailsLinkText: "View sync status details here"
1371
1399
  confirm:
1372
1400
  createFlow:
1373
1401
  standard: "Sync all supported assets to {{#cyan}}{{#bold}}{{ sandboxName }}{{/bold}}{{/cyan}} from {{#bold}}{{ parentAccountName }}{{/bold}}?"
1374
1402
  developer: "Sync CRM object definitions to {{#cyan}}{{#bold}}{{ sandboxName }}{{/bold}}{{/cyan}} from {{#bold}}{{ parentAccountName }}{{/bold}}?"
1375
1403
  syncContactRecords:
1376
- standard: "Include up to 5000 most recently updated contacts? This includes up to 100 of each of the following: associated deals, tickets, and companies. This can be done once per sandbox."
1404
+ standard: "Copy up to 5000 most recently updated contacts? This includes up to 100 of each of the following: associated deals, tickets, and companies."
1377
1405
  developer: "Include up to 100 most recently updated contacts? This includes up to 100 of each of the following: associated deals, tickets, and companies. This can be done once per sandbox."
1378
1406
  loading:
1379
1407
  startSync: "Initiating sync..."
1380
1408
  fail: "Failed to sync sandbox."
1381
- succeed: "Sandbox sync initiated to {{ accountName }}."
1382
- skipPolling: "Syncing CRM object definitions."
1383
- skipPollingWithContacts: "Syncing CRM object definitions and up to 100 most recently updated contacts with associated deals, tickets, and companies (up to 100 each)."
1384
- polling:
1385
- syncing: "Syncing sandbox..."
1386
- fail: "Failed to fetch sync updates. View the sync status at: {{ url }}"
1387
- succeed: "Sandbox sync complete."
1409
+ succeed: "Initiated asset sync from production to {{ accountName }}"
1410
+ succeedDevSb: "Initiated sync of object definitions from production to {{ accountName }}"
1411
+ successDevSbInfo: "Initiated sync of object definitions from production to {{ accountName }}. It may take some time. {{ url }}"
1388
1412
  failure:
1389
1413
  invalidUser: "Couldn't sync {{ accountName }} because your account has been removed from {{ parentAccountName }} or your permission set doesn't allow you to sync the sandbox. To update your permissions, contact a super admin in {{ parentAccountName }}."
1390
1414
  missingScopes: "Couldn’t run the sync because there are scopes missing in your production account. To update scopes, deactivate your current personal access key for {{#bold}}{{ accountName }}{{/bold}}, and generate a new one. Then run `hs auth` to update the CLI with the new key."
1391
1415
  syncInProgress: "Couldn’t run the sync because there’s another sync in progress. Wait for the current sync to finish and then try again. To check the sync status, visit the sync activity log: {{ url }}."
1392
1416
  notSuperAdmin: "Couldn't run the sync because you are not a super admin in {{ account }}. Ask the account owner for super admin access to the sandbox."
1393
1417
  objectNotFound: "Couldn't sync the sandbox because {{#bold}}{{ account }}{{/bold}} may have been deleted through the UI. Run {{#bold}}hs sandbox delete{{/bold}} to remove this account from the config. "
1394
- types:
1395
- parcels:
1396
- label: "Account tools and features"
1397
- super-admins:
1398
- label: "Super Admins"
1399
- object-schemas:
1400
- label: "Object definitions"
1401
- object-records:
1402
- label: "Contacts and associated records"
1403
- cms-developer-assets:
1404
- label: "Themes, templates, and modules"
1405
- object-pipelines:
1406
- label: "Pipelines"
1407
- object-lists:
1408
- label: "Lists"
1409
- workflows:
1410
- label: "Workflows"
1411
- forms:
1412
- label: "Forms"
1413
- lead-flows:
1414
- label: "Lead Flows"
1415
- marketing-email:
1416
- label: "Marketing emails"
1417
1418
  errorHandlers:
1418
1419
  standardErrors:
1419
1420
  errorOccurred: "Error: {{ error }}"
@@ -9,6 +9,7 @@ const {
9
9
  } = require('@hubspot/local-dev-lib/api/localDevAuth');
10
10
  const {
11
11
  fetchPublicAppsForPortal,
12
+ fetchPublicAppProductionInstallCounts,
12
13
  } = require('@hubspot/local-dev-lib/api/appsDev');
13
14
  const {
14
15
  getAccountId,
@@ -19,6 +20,7 @@ const SpinniesManager = require('./ui/SpinniesManager');
19
20
  const DevServerManager = require('./DevServerManager');
20
21
  const { EXIT_CODES } = require('./enums/exitCodes');
21
22
  const { getProjectDetailUrl } = require('./projects');
23
+ const { getAccountHomeUrl } = require('./localDev');
22
24
  const {
23
25
  CONFIG_FILES,
24
26
  COMPONENT_TYPES,
@@ -34,6 +36,9 @@ const {
34
36
  } = require('./ui');
35
37
  const { logErrorInstance } = require('./errorHandlers/standardErrors');
36
38
  const { installPublicAppPrompt } = require('./prompts/installPublicAppPrompt');
39
+ const {
40
+ activeInstallConfirmationPrompt,
41
+ } = require('./prompts/activeInstallConfirmationPrompt');
37
42
 
38
43
  const WATCH_EVENTS = {
39
44
  add: 'add',
@@ -62,6 +67,7 @@ class LocalDevManager {
62
67
  this.activeApp = null;
63
68
  this.activePublicAppData = null;
64
69
  this.env = options.env;
70
+ this.publicAppActiveInstalls = null;
65
71
 
66
72
  this.projectSourceDir = path.join(
67
73
  this.projectDir,
@@ -132,20 +138,43 @@ class LocalDevManager {
132
138
  ({ sourceId }) => sourceId === this.activeApp.config.uid
133
139
  );
134
140
 
141
+ // TODO: Update to account for new API with { data }
142
+ const {
143
+ uniquePortalInstallCount,
144
+ } = await fetchPublicAppProductionInstallCounts(
145
+ activePublicAppData.id,
146
+ this.targetProjectAccountId
147
+ );
148
+
135
149
  this.activePublicAppData = activePublicAppData;
150
+ this.publicAppActiveInstalls = uniquePortalInstallCount;
136
151
  }
137
152
 
138
153
  async checkActivePublicAppInstalls() {
139
- // TODO: Add check for installs once we have that info
140
- if (!this.activePublicAppData) {
154
+ if (
155
+ !this.activePublicAppData ||
156
+ !this.publicAppActiveInstalls ||
157
+ this.publicAppActiveInstalls < 1
158
+ ) {
141
159
  return;
142
160
  }
143
161
  uiLine();
144
- // TODO: Replace with final copy
145
162
 
146
- logger.warn(i18n(`${i18nKey}.activeInstallWarning.genericHeader`));
147
- logger.log(i18n(`${i18nKey}.activeInstallWarning.genericExplanation`));
163
+ logger.warn(
164
+ i18n(`${i18nKey}.activeInstallWarning.installCount`, {
165
+ appName: this.activePublicAppData.name,
166
+ installCount: this.publicAppActiveInstalls,
167
+ installText:
168
+ this.publicAppActiveInstalls === 1 ? 'install' : 'installs',
169
+ })
170
+ );
171
+ logger.log(i18n(`${i18nKey}.activeInstallWarning.explanation`));
148
172
  uiLine();
173
+ const proceed = await activeInstallConfirmationPrompt();
174
+
175
+ if (!proceed) {
176
+ process.exit(EXIT_CODES.SUCCESS);
177
+ }
149
178
  }
150
179
 
151
180
  async start() {
@@ -193,13 +222,23 @@ class LocalDevManager {
193
222
  );
194
223
  logger.log(
195
224
  uiLink(
196
- i18n(`${i18nKey}.viewInHubSpotLink`),
225
+ i18n(`${i18nKey}.viewProjectLink`),
197
226
  getProjectDetailUrl(
198
227
  this.projectConfig.name,
199
228
  this.targetProjectAccountId
200
229
  )
201
230
  )
202
231
  );
232
+
233
+ if (this.activeApp.type === COMPONENT_TYPES.publicApp) {
234
+ logger.log(
235
+ uiLink(
236
+ i18n(`${i18nKey}.viewTestAccountLink`),
237
+ getAccountHomeUrl(this.targetAccountId)
238
+ )
239
+ );
240
+ }
241
+
203
242
  logger.log();
204
243
  logger.log(i18n(`${i18nKey}.quitHelper`));
205
244
  uiLine();
@@ -258,16 +297,20 @@ class LocalDevManager {
258
297
 
259
298
  async checkPublicAppInstallation() {
260
299
  const {
261
- isInstalledWithScopeGroups: isInstalled,
300
+ isInstalledWithScopeGroups,
301
+ previouslyAuthorizedScopeGroups,
262
302
  } = await this.getActiveAppInstallationData();
263
303
 
264
- if (!isInstalled) {
304
+ const isReinstall = previouslyAuthorizedScopeGroups.length > 0;
305
+
306
+ if (!isInstalledWithScopeGroups) {
265
307
  await installPublicAppPrompt(
266
308
  this.env,
267
309
  this.targetAccountId,
268
310
  this.activePublicAppData.clientId,
269
311
  this.activeApp.config.auth.requiredScopes,
270
- this.activeApp.config.auth.redirectUrls
312
+ this.activeApp.config.auth.redirectUrls,
313
+ isReinstall
271
314
  );
272
315
  }
273
316
  }
@@ -294,8 +337,13 @@ class LocalDevManager {
294
337
  let warning = reason;
295
338
  if (!reason) {
296
339
  warning =
297
- this.activeApp.type === COMPONENT_TYPES.publicApp
298
- ? i18n(`${i18nKey}.uploadWarning.defaultPublicAppWarning`)
340
+ this.activeApp.type === COMPONENT_TYPES.publicApp &&
341
+ this.publicAppActiveInstalls > 0
342
+ ? i18n(`${i18nKey}.uploadWarning.defaultPublicAppWarning`, {
343
+ installCount: this.publicAppActiveInstalls,
344
+ installText:
345
+ this.publicAppActiveInstalls === 1 ? 'install' : 'installs',
346
+ })
299
347
  : i18n(`${i18nKey}.uploadWarning.defaultWarning`);
300
348
  }
301
349