@hubspot/cli 5.2.1-beta.0 → 5.2.1-beta.2

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/lang/en.lyaml CHANGED
@@ -475,21 +475,10 @@ en:
475
475
  describe: "Start local dev for the current project"
476
476
  logs:
477
477
  betaMessage: "HubSpot projects local development"
478
- nonSandboxWarning: "Testing in a sandbox is strongly recommended. To switch the target account, select an option below or run {{#bold}}`hs accounts use`{{/bold}} before running the command again."
479
478
  placeholderAccountSelection: "Using default account as target account (for now)"
480
- projectMustExistExplanation: "The project {{ projectName }} does not exist in the target account {{ accountIdentifier}}. This command requires the project to exist in the target account."
481
- choseNotToCreateProject: "Exiting because this command requires the project to exist in the target account."
482
- initialUploadMessage: "HubSpot Local Dev Server Startup"
483
- declineDefaultAccountExplanation: "To develop on a different account, run {{ useCommand }} to change your default account, then re-run {{ devCommand }}."
484
- status:
485
- creatingProject: "Creating project {{ projectName }} in {{ accountIdentifier }}"
486
- createdProject: "Created project {{ projectName }} in {{ accountIdentifier }}"
487
- failedToCreateProject: "Failed to create project in the target account."
488
- prompt:
489
- createProject: "Create new project {{ projectName}} in {{#bold}}[{{ accountIdentifier }}]{{/bold}}?"
490
479
  errors:
491
480
  noProjectConfig: "No project detected. Please run this command again from a project directory."
492
- projectLockedError: "Your project is locked. This may mean that another user is running the {{#bold}}`hs project watch`{{/bold}} command for this project. If this is you, unlock the project in Projects UI."
481
+ invalidProjectComponents: "Projects cannot contain both private and public apps. Move your apps to separate projects before attempting local development."
493
482
  examples:
494
483
  default: "Start local dev for the current project"
495
484
  create:
@@ -893,6 +882,7 @@ en:
893
882
  describe: "Upload and watch a theme directory on your computer for changes and start a local development server to preview theme changes on a site"
894
883
  errors:
895
884
  invalidPath: "The path \"{{ path }}\" is not a path to a directory"
885
+ noThemeComponents: "Your project has no theme components available to preview."
896
886
  options:
897
887
  src:
898
888
  describe: "Path to the local directory your theme is in, relative to your current working directory"
@@ -952,6 +942,26 @@ en:
952
942
  setupError: "Failed to setup local dev server: {{ message }}"
953
943
  startError: "Failed to start local dev server: {{ message }}"
954
944
  fileChangeError: "Failed to notify local dev server of file change: {{ message }}"
945
+ localDev:
946
+ confirmDefaultAccountIsTarget:
947
+ declineDefaultAccountExplanation: "To develop on a different account, run {{ useCommand }} to change your default account, then re-run {{ devCommand }}."
948
+ checkIfAppDevloperAccount: "This project contains a public app. Local development of public apps is only supported on developer accounts and developer test accounts. Change your default account using {{#bold}}`hs accounts use`{{/bold}}, or link a new account with {{#bold}}`hs auth`{{/bold}}."
949
+ checkIfDeveloperTestAccount: "This project contains a public app. The \"--account\" flag must point to a developer test account to develop this project locally. Alternatively, change your default account to an App Developer Account using {{#bold}}`hs accounts use`{{/bold}} and run {{#bold}}`hs project dev`{{/bold}} to set up a new Developer Test Account."
950
+ suggestRecommendedNestedAccount:
951
+ nonSandboxWarning: "Testing in a sandbox is strongly recommended. To switch the target account, select an option below or run {{#bold}}`hs accounts use`{{/bold}} before running the command again."
952
+ publicAppNonDeveloperTestAccountWarning: "Local development of public apps is only supported in {{#bold}}developer test accounts{{/bold}}."
953
+ privateAppNonDeveloperTestAccountWarning: "Local development of private apps is only supported in {{#bold}}developer test accounts{{/bold}}"
954
+ createNewProjectForLocalDev:
955
+ projectMustExistExplanation: "The project {{ projectName }} does not exist in the target account {{ accountIdentifier}}. This command requires the project to exist in the target account."
956
+ publicAppProjectMustExistExplanation: "The project {{ projectName }} does not exist in {{ accountIdentifier}}, the app developer account associated with your target account. This command requires the project to exist in this app developer account."
957
+ createProject: "Create new project {{ projectName}} in {{#bold}}[{{ accountIdentifier }}]{{/bold}}?"
958
+ choseNotToCreateProject: "Exiting because this command requires the project to exist in the target account."
959
+ creatingProject: "Creating project {{ projectName }} in {{ accountIdentifier }}"
960
+ createdProject: "Created project {{ projectName }} in {{ accountIdentifier }}"
961
+ failedToCreateProject: "Failed to create project in the target account."
962
+ createInitialBuildForNewProject:
963
+ initialUploadMessage: "HubSpot Local Dev Server Startup"
964
+ projectLockedError: "Your project is locked. This may mean that another user is running the {{#bold}}`hs project watch`{{/bold}} command for this project. If this is you, unlock the project in Projects UI."
955
965
  projects:
956
966
  config:
957
967
  srcOutsideProjectDir: "Invalid value for 'srcDir' in {{ projectConfig }}: {{#bold}}srcDir: \"{{ srcDir }}\"{{/bold}}\n\t'srcDir' must be a relative path to a folder under the project root, such as \".\" or \"./src\""
@@ -1065,11 +1075,14 @@ en:
1065
1075
  prompts:
1066
1076
  projectDevTargetAccountPrompt:
1067
1077
  createNewSandboxOption: "<Test on a new development sandbox>"
1078
+ createNewDeveloperTestAccountOption: "<Test on a new developer test account>"
1068
1079
  chooseDefaultAccountOption: "<{{#bold}}\U00002757{{/bold}} Test on this production account {{#bold}}\U00002757{{/bold}}>"
1069
- promptMessage: "[--account] Choose a sandbox under {{ accountIdentifier }} to test with:"
1080
+ promptMessage: "[--account] Choose a {{ accountType }} under {{ accountIdentifier }} to test with:"
1070
1081
  sandboxLimit: "Your account reached the limit of {{ limit }} development sandboxes"
1071
1082
  sandboxLimitWithSuggestion: "Your account reached the limit of {{ limit }} development sandboxes. Run {{ authCommand }} to add an existing one to the config."
1083
+ developerTestAccountLimit: "Your account reached the limit of {{ limit }} developer test accounts."
1072
1084
  confirmDefaultAccount: "Continue testing on {{#bold}}{{ accountName }} ({{ accountType }}){{/bold}}? (Y/n)"
1085
+ confirmUseExistingDeveloperTestAccount: "Continue with {{ accountName }}? This account isn't currently connected to the HubSpot CLI. By continuing, you'll be prompted to generate a personal access key and connect it."
1073
1086
  projectLogsPrompt:
1074
1087
  projectName:
1075
1088
  message: "[--project] Enter the project name:"
@@ -1150,6 +1163,13 @@ en:
1150
1163
  invalidTemplate: "[--template] Could not find template {{ template }}. Please choose an available template."
1151
1164
  noProjectsInConfig: "Please ensure that there is a config.json file that contains a \"projects\" field."
1152
1165
  missingPropertiesInConfig: "Please ensure that each of the projects in your config.json file contain the following properties: [\"name\", \"label\", \"path\", \"insertPath\"]."
1166
+ developerTestAccountPrompt:
1167
+ name:
1168
+ message: "Name your developer test account"
1169
+ errors:
1170
+ invalidName: "You entered an invalid name. Please try again."
1171
+ nameRequired: "The name may not be blank. Please try again."
1172
+ accountNameExists: "Account with name \"{{ name }}\" already exists in the CLI config, please enter a different name."
1153
1173
  downloadProjectPrompt:
1154
1174
  selectProject: "Select a project to download:"
1155
1175
  errors:
@@ -1205,6 +1225,7 @@ en:
1205
1225
  previewPrompt:
1206
1226
  enterSrc: "[--src] Enter a local theme directory to preview."
1207
1227
  enterDest: "[--dest] Enter the destination path for the src theme in HubSpot Design Tools."
1228
+ themeProjectSelect: "[--theme] Select which theme to preview."
1208
1229
  errors:
1209
1230
  srcRequired: "You must specify a source directory."
1210
1231
  destRequired: "You must specify a destination directory."
@@ -1217,6 +1238,25 @@ en:
1217
1238
  options:
1218
1239
  options:
1219
1240
  describe: "Options to pass to javascript fields files"
1241
+ developerTestAccount:
1242
+ create:
1243
+ loading:
1244
+ add: "Creating developer test account {{#bold}}{{ accountName }}{{/bold}}"
1245
+ fail: "Failed to create a developer test account {{#bold}}{{ accountName }}{{/bold}}."
1246
+ succeed: "Successfully created a developer test account {{#bold}}{{ name }}{{/bold}} with portalId {{#bold}}{{ accountId }}{{/bold}}."
1247
+ success:
1248
+ configFileUpdated: "Account \"{{ accountName }}\" updated using \"{{ authType }}\""
1249
+ failure:
1250
+ 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}}."
1251
+ limit: "{{#bold}}{{ accountName }}{{/bold}} reached the limit of {{ limit }} developer test accounts.
1252
+ \n- To connect a developer test account to your HubSpot CLI, run {{#bold}}hs auth{{/bold}} and follow the prompts."
1253
+ alreadyInConfig: "{{#bold}}{{ accountName }}{{/bold}} reached the limit of {{ limit }} developer test accounts.
1254
+ \n- To use an existing developer test account, run {{#bold}}hs accounts use{{/bold}}."
1255
+ scopes:
1256
+ message: "The personal access key you provided doesn't include developer test account permissions."
1257
+ instructions: "To update CLI permissions for \"{{ accountName }}\":
1258
+ \n- Go to {{ url }}, deactivate the existing personal access key, and create a new one that includes developer test account permissions.
1259
+ \n- Update the CLI config for this account by running {{#bold}}hs auth{{/bold}} and entering the new key.\n"
1220
1260
  sandbox:
1221
1261
  create:
1222
1262
  loading:
@@ -1228,11 +1268,9 @@ en:
1228
1268
  add: "Creating standard sandbox {{#bold}}{{ sandboxName }}{{/bold}}"
1229
1269
  fail: "Failed to create a standard sandbox {{#bold}}{{ sandboxName }}{{/bold}}."
1230
1270
  succeed: "Successfully created a standard sandbox {{#bold}}{{ name }}{{/bold}} with portalId {{#bold}}{{ sandboxHubId }}{{/bold}}."
1231
- success:
1232
- configFileUpdated: "{{ configFilename }} updated with {{ authMethod }} for account {{ account }}."
1233
1271
  failure:
1234
1272
  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}}."
1235
- 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/whats-new/betas?productUpdateId=13860216."
1273
+ 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."
1236
1274
  limit:
1237
1275
  developer:
1238
1276
  one: "{{#bold}}{{ accountName }}{{/bold}} reached the limit of {{ limit }} development sandbox.
@@ -1336,7 +1374,7 @@ en:
1336
1374
  400: "The {{ messageDetail }} was bad."
1337
1375
  401: "The {{ messageDetail }} was unauthorized."
1338
1376
  403MissingScope: "Couldn't run the project command because there are scopes missing in your production account. To update scopes, deactivate your current personal access key for {{ accountId }}, and generate a new one. Then run `hs auth` to update the CLI with the new key."
1339
- 403Gating: "The current target account {{ accountId }} does not have access to HubSpot projects. To opt in to the CRM Development Beta and use projects, visit https://app.hubspot.com/l/whats-new/betas?productUpdateId=13860216."
1377
+ 403Gating: "The current target account {{ accountId }} does not have access to HubSpot projects. To opt in to the CRM Development Beta and use projects, visit https://app.hubspot.com/l/product-updates/in-beta?update=13899236."
1340
1378
  403: "The {{ messageDetail }} was forbidden."
1341
1379
  404Request: 'The {{ action }} failed because "{{ request }}" was not found in account {{ accountId }}.'
1342
1380
  404: "The {{ messageDetail }} was not found."
@@ -16,7 +16,6 @@ const { getProjectDetailUrl } = require('./projects');
16
16
  const {
17
17
  CONFIG_FILES,
18
18
  COMPONENT_TYPES,
19
- findProjectComponents,
20
19
  getAppCardConfigs,
21
20
  } = require('./projectStructure');
22
21
  const {
@@ -47,6 +46,7 @@ class LocalDevManager {
47
46
  this.isGithubLinked = options.isGithubLinked;
48
47
  this.watcher = null;
49
48
  this.uploadWarnings = {};
49
+ this.runnableComponents = this.getRunnableComponents(options.components);
50
50
 
51
51
  this.projectSourceDir = path.join(
52
52
  this.projectDir,
@@ -57,6 +57,27 @@ class LocalDevManager {
57
57
  logger.log(i18n(`${i18nKey}.failedToInitialize`));
58
58
  process.exit(EXIT_CODES.ERROR);
59
59
  }
60
+
61
+ // The project is empty, there is nothing to run locally
62
+ if (!options.components.length) {
63
+ logger.error(i18n(`${i18nKey}.noComponents`));
64
+ process.exit(EXIT_CODES.SUCCESS);
65
+ }
66
+
67
+ // The project does not contain any components that support local development
68
+ if (!this.runnableComponents.length) {
69
+ logger.error(
70
+ i18n(`${i18nKey}.noRunnableComponents`, {
71
+ projectSourceDir: this.projectSourceDir,
72
+ command: uiCommandReference('hs project add'),
73
+ })
74
+ );
75
+ process.exit(EXIT_CODES.SUCCESS);
76
+ }
77
+ }
78
+
79
+ getRunnableComponents(components) {
80
+ return components.filter(component => component.runnable);
60
81
  }
61
82
 
62
83
  async start() {
@@ -75,30 +96,7 @@ class LocalDevManager {
75
96
  process.exit(EXIT_CODES.SUCCESS);
76
97
  }
77
98
 
78
- const components = await findProjectComponents(this.projectSourceDir);
79
-
80
- // The project is empty, there is nothing to run locally
81
- if (!components.length) {
82
- logger.error(i18n(`${i18nKey}.noComponents`));
83
- process.exit(EXIT_CODES.SUCCESS);
84
- }
85
-
86
- const runnableComponents = components.filter(
87
- component => component.runnable
88
- );
89
-
90
- // The project does not contain any components that support local development
91
- if (!runnableComponents.length) {
92
- logger.error(
93
- i18n(`${i18nKey}.noRunnableComponents`, {
94
- projectSourceDir: this.projectSourceDir,
95
- command: uiCommandReference('hs project add'),
96
- })
97
- );
98
- process.exit(EXIT_CODES.SUCCESS);
99
- }
100
-
101
- const setupSucceeded = await this.devServerSetup(runnableComponents);
99
+ const setupSucceeded = await this.devServerSetup();
102
100
 
103
101
  if (!setupSucceeded) {
104
102
  process.exit(EXIT_CODES.ERROR);
@@ -130,7 +128,7 @@ class LocalDevManager {
130
128
  await this.devServerStart();
131
129
 
132
130
  // Initialize project file watcher to detect configuration file changes
133
- this.startWatching(runnableComponents);
131
+ this.startWatching();
134
132
 
135
133
  this.updateKeypressListeners();
136
134
 
@@ -138,7 +136,7 @@ class LocalDevManager {
138
136
 
139
137
  // Verify that there are no mismatches between components in the local project
140
138
  // and components in the deployed build of the project.
141
- this.compareLocalProjectToDeployed(runnableComponents);
139
+ this.compareLocalProjectToDeployed();
142
140
  }
143
141
 
144
142
  async stop(showProgress = true) {
@@ -235,14 +233,14 @@ class LocalDevManager {
235
233
  }.bind(this);
236
234
  }
237
235
 
238
- compareLocalProjectToDeployed(runnableComponents) {
236
+ compareLocalProjectToDeployed() {
239
237
  const deployedComponentNames = this.deployedBuild.subbuildStatuses.map(
240
238
  subbuildStatus => subbuildStatus.buildName
241
239
  );
242
240
 
243
241
  let missingComponents = [];
244
242
 
245
- runnableComponents.forEach(({ type, config, path }) => {
243
+ this.runnableComponents.forEach(({ type, config, path }) => {
246
244
  if (Object.values(COMPONENT_TYPES).includes(type)) {
247
245
  const cardConfigs = getAppCardConfigs(config, path);
248
246
 
@@ -277,12 +275,12 @@ class LocalDevManager {
277
275
  }
278
276
  }
279
277
 
280
- startWatching(runnableComponents) {
278
+ startWatching() {
281
279
  this.watcher = chokidar.watch(this.projectDir, {
282
280
  ignoreInitial: true,
283
281
  });
284
282
 
285
- const configPaths = runnableComponents
283
+ const configPaths = this.runnableComponents
286
284
  .filter(({ type }) => Object.values(COMPONENT_TYPES).includes(type))
287
285
  .map(component => {
288
286
  const appConfigPath = path.join(
@@ -321,10 +319,10 @@ class LocalDevManager {
321
319
  }
322
320
  }
323
321
 
324
- async devServerSetup(components) {
322
+ async devServerSetup() {
325
323
  try {
326
324
  await DevServerManager.setup({
327
- components,
325
+ components: this.runnableComponents,
328
326
  onUploadRequired: this.logUploadWarning.bind(this),
329
327
  accountId: this.targetAccountId,
330
328
  });
@@ -0,0 +1,34 @@
1
+ const {
2
+ HUBSPOT_ACCOUNT_TYPES,
3
+ } = require('@hubspot/local-dev-lib/constants/config');
4
+
5
+ const isAccountType = (config, accountType) =>
6
+ config.accountType && config.accountType === accountType;
7
+
8
+ const isStandardAccount = config =>
9
+ isAccountType(config, HUBSPOT_ACCOUNT_TYPES.STANDARD);
10
+
11
+ const isSandbox = config =>
12
+ isAccountType(config, HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX) ||
13
+ isAccountType(config, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX);
14
+
15
+ const isStandardSandbox = config =>
16
+ isAccountType(config, HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX);
17
+
18
+ const isDevelopmentSandbox = config =>
19
+ isAccountType(config, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX);
20
+
21
+ const isDeveloperTestAccount = config =>
22
+ isAccountType(config, HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST);
23
+
24
+ const isAppDeveloperAccount = config =>
25
+ isAccountType(config, HUBSPOT_ACCOUNT_TYPES.APP_DEVELOPER);
26
+
27
+ module.exports = {
28
+ isStandardAccount,
29
+ isSandbox,
30
+ isStandardSandbox,
31
+ isDevelopmentSandbox,
32
+ isDeveloperTestAccount,
33
+ isAppDeveloperAccount,
34
+ };
@@ -0,0 +1,187 @@
1
+ const SpinniesManager = require('./ui/SpinniesManager');
2
+ const {
3
+ getAccountId,
4
+ accountNameExistsInConfig,
5
+ updateAccountConfig,
6
+ writeConfig,
7
+ } = require('@hubspot/local-dev-lib/config');
8
+ const { logger } = require('@hubspot/local-dev-lib/logger');
9
+ const {
10
+ createDeveloperTestAccount,
11
+ } = require('@hubspot/local-dev-lib/developerTestAccounts');
12
+ const { i18n } = require('./lang');
13
+ const {
14
+ debugErrorAndContext,
15
+ logErrorInstance,
16
+ } = require('./errorHandlers/standardErrors');
17
+ const {
18
+ isMissingScopeError,
19
+ isSpecifiedError,
20
+ } = require('@hubspot/local-dev-lib/errors/apiErrors');
21
+ const {
22
+ getAccessToken,
23
+ updateConfigWithAccessToken,
24
+ } = require('@hubspot/local-dev-lib/personalAccessKey');
25
+ const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls');
26
+ const { uiAccountDescription } = require('./ui');
27
+ const {
28
+ personalAccessKeyPrompt,
29
+ } = require('./prompts/personalAccessKeyPrompt');
30
+ const { enterAccountNamePrompt } = require('./prompts/enterAccountNamePrompt');
31
+
32
+ const i18nKey = 'cli.lib.developerTestAccount';
33
+
34
+ const saveDevTestAccountToConfig = async (env, result, force = false) => {
35
+ let personalAccessKey = result.personalAccessKey;
36
+ if (!personalAccessKey) {
37
+ const configData = await personalAccessKeyPrompt({
38
+ env,
39
+ account: result.id,
40
+ });
41
+ personalAccessKey = configData.personalAccessKey;
42
+ }
43
+
44
+ const token = await getAccessToken(personalAccessKey, env);
45
+ const updatedConfig = await updateConfigWithAccessToken(
46
+ token,
47
+ personalAccessKey,
48
+ env
49
+ );
50
+
51
+ let validName = updatedConfig.name;
52
+ if (!updatedConfig.name) {
53
+ const nameForConfig = result.accountName
54
+ .toLowerCase()
55
+ .split(' ')
56
+ .join('-');
57
+ validName = nameForConfig;
58
+ const invalidAccountName = accountNameExistsInConfig(nameForConfig);
59
+ if (invalidAccountName) {
60
+ if (!force) {
61
+ logger.log('');
62
+ logger.warn(
63
+ i18n(
64
+ `cli.lib.prompts.enterAccountNamePrompt.errors.accountNameExists`,
65
+ { name: nameForConfig }
66
+ )
67
+ );
68
+ const { name: promptName } = await enterAccountNamePrompt(
69
+ nameForConfig + `_${result.id}`
70
+ );
71
+ validName = promptName;
72
+ } else {
73
+ // Basic invalid name handling when force flag is passed
74
+ validName = nameForConfig + `_${result.id}`;
75
+ }
76
+ }
77
+ }
78
+
79
+ updateAccountConfig({
80
+ ...updatedConfig,
81
+ environment: updatedConfig.env,
82
+ tokenInfo: updatedConfig.auth.tokenInfo,
83
+ name: validName,
84
+ });
85
+ writeConfig();
86
+
87
+ logger.log('');
88
+ return validName;
89
+ };
90
+
91
+ const buildDeveloperTestAccount = async ({
92
+ name,
93
+ accountConfig,
94
+ env,
95
+ maxTestPortals,
96
+ force = false,
97
+ }) => {
98
+ SpinniesManager.init({
99
+ succeedColor: 'white',
100
+ });
101
+ const accountId = getAccountId(accountConfig.portalId);
102
+
103
+ let result;
104
+ const spinniesI18nKey = `${i18nKey}.create.loading`;
105
+
106
+ try {
107
+ logger.log('');
108
+ SpinniesManager.add('devTestAcctCreate', {
109
+ text: i18n(`${spinniesI18nKey}.add`, {
110
+ accountName: name,
111
+ }),
112
+ });
113
+ result = await createDeveloperTestAccount(accountId, name);
114
+
115
+ SpinniesManager.succeed('devTestAcctCreate', {
116
+ text: i18n(`${spinniesI18nKey}.succeed`, {
117
+ name: result.accountName,
118
+ accountId: result.id,
119
+ }),
120
+ });
121
+ } catch (err) {
122
+ debugErrorAndContext(err);
123
+
124
+ SpinniesManager.fail('devTestAcctCreate', {
125
+ text: i18n(`${spinniesI18nKey}.fail`, {
126
+ accountName: name,
127
+ }),
128
+ });
129
+
130
+ if (isMissingScopeError(err)) {
131
+ logger.error(
132
+ i18n(`${i18nKey}.create.failure.scopes.message`, {
133
+ accountName: uiAccountDescription(accountId),
134
+ })
135
+ );
136
+ const websiteOrigin = getHubSpotWebsiteOrigin(env);
137
+ const url = `${websiteOrigin}/personal-access-key/${accountId}`;
138
+ logger.info(
139
+ i18n(`${i18nKey}.create.failure.scopes.instructions`, {
140
+ accountName: uiAccountDescription(accountId),
141
+ url,
142
+ })
143
+ );
144
+ } else if (
145
+ isSpecifiedError(err, {
146
+ statusCode: 400,
147
+ errorType: 'TEST_PORTAL_LIMIT_REACHED',
148
+ })
149
+ ) {
150
+ logger.log('');
151
+ logger.error(
152
+ i18n(`${i18nKey}.create.failure.limit`, {
153
+ accountName: uiAccountDescription(accountId),
154
+ limit: maxTestPortals,
155
+ })
156
+ );
157
+ logger.log('');
158
+ } else {
159
+ logErrorInstance(err);
160
+ }
161
+ throw err;
162
+ }
163
+
164
+ let devTestAcctConfigName;
165
+
166
+ try {
167
+ // Response contains PAK, save to config here
168
+ devTestAcctConfigName = await saveDevTestAccountToConfig(
169
+ env,
170
+ result,
171
+ force
172
+ );
173
+ } catch (err) {
174
+ logErrorInstance(err);
175
+ throw err;
176
+ }
177
+
178
+ return {
179
+ devTestAcctConfigName,
180
+ result,
181
+ };
182
+ };
183
+
184
+ module.exports = {
185
+ buildDeveloperTestAccount,
186
+ saveDevTestAccountToConfig,
187
+ };
@@ -1,11 +1,57 @@
1
1
  const {
2
2
  HUBSPOT_ACCOUNT_TYPES,
3
3
  } = require('@hubspot/local-dev-lib/constants/config');
4
+ const { getAccountId, getConfig } = require('@hubspot/local-dev-lib/config');
5
+ const { i18n } = require('./lang');
6
+ const {
7
+ fetchDeveloperTestAccounts,
8
+ } = require('@hubspot/local-dev-lib/developerTestAccounts');
9
+
10
+ const getHasDevTestAccounts = appDeveloperAccountConfig => {
11
+ const config = getConfig();
12
+ const parentPortalId = getAccountId(appDeveloperAccountConfig.portalId);
13
+ for (const portal of config.portals) {
14
+ if (
15
+ Boolean(portal.parentAccountId) &&
16
+ portal.parentAccountId === parentPortalId &&
17
+ portal.accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST
18
+ ) {
19
+ return true;
20
+ }
21
+ }
22
+ return false;
23
+ };
4
24
 
5
- const isDeveloperTestAccount = config =>
6
- config.accountType &&
7
- config.accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST;
25
+ const i18nKey = 'cli.lib.developerTestAccount';
8
26
 
27
+ const validateDevTestAccountUsageLimits = async accountConfig => {
28
+ const accountId = getAccountId(accountConfig.portalId);
29
+ const response = await fetchDeveloperTestAccounts(accountId);
30
+ if (response) {
31
+ const limit = response.maxTestPortals;
32
+ const count = response.results.length;
33
+ if (count >= limit) {
34
+ const hasDevTestAccounts = getHasDevTestAccounts(accountConfig);
35
+ if (hasDevTestAccounts) {
36
+ throw new Error(
37
+ i18n(`${i18nKey}.create.failure.alreadyInConfig`, {
38
+ accountName: accountConfig.name || accountId,
39
+ limit,
40
+ })
41
+ );
42
+ } else {
43
+ throw new Error(
44
+ i18n(`${i18nKey}.create.failure.limit`, {
45
+ accountName: accountConfig.name || accountId,
46
+ limit,
47
+ })
48
+ );
49
+ }
50
+ }
51
+ return response;
52
+ }
53
+ return null;
54
+ };
9
55
  module.exports = {
10
- isDeveloperTestAccount,
56
+ validateDevTestAccountUsageLimits,
11
57
  };