@hubspot/cli 5.2.0 → 5.2.1-beta.1

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:
@@ -952,6 +941,25 @@ en:
952
941
  setupError: "Failed to setup local dev server: {{ message }}"
953
942
  startError: "Failed to start local dev server: {{ message }}"
954
943
  fileChangeError: "Failed to notify local dev server of file change: {{ message }}"
944
+ localDev:
945
+ confirmDefaultAccountIsTarget:
946
+ declineDefaultAccountExplanation: "To develop on a different account, run {{ useCommand }} to change your default account, then re-run {{ devCommand }}."
947
+ checkCorrectParentAccountType:
948
+ standardAccountNotSupported: "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
+ suggestRecommendedNestedAccount:
950
+ 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."
951
+ publicAppNonDeveloperTestAccountWarning: "Local development of public apps is only supported in {{#bold}}developer test accounts{{/bold}}."
952
+ privateAppNonDeveloperTestAccountWarning: "Local development of private apps is only supported in {{#bold}}developer test accounts{{/bold}}"
953
+ createNewProjectForLocalDev:
954
+ projectMustExistExplanation: "The project {{ projectName }} does not exist in the target account {{ accountIdentifier}}. This command requires the project to exist in the target account."
955
+ createProject: "Create new project {{ projectName}} in {{#bold}}[{{ accountIdentifier }}]{{/bold}}?"
956
+ choseNotToCreateProject: "Exiting because this command requires the project to exist in the target account."
957
+ creatingProject: "Creating project {{ projectName }} in {{ accountIdentifier }}"
958
+ createdProject: "Created project {{ projectName }} in {{ accountIdentifier }}"
959
+ failedToCreateProject: "Failed to create project in the target account."
960
+ createInitialBuildForNewProject:
961
+ initialUploadMessage: "HubSpot Local Dev Server Startup"
962
+ 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
963
  projects:
956
964
  config:
957
965
  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,10 +1073,12 @@ en:
1065
1073
  prompts:
1066
1074
  projectDevTargetAccountPrompt:
1067
1075
  createNewSandboxOption: "<Test on a new development sandbox>"
1076
+ createNewDeveloperTestAccountOption: "<Test on a new developer test account>"
1068
1077
  chooseDefaultAccountOption: "<{{#bold}}\U00002757{{/bold}} Test on this production account {{#bold}}\U00002757{{/bold}}>"
1069
- promptMessage: "[--account] Choose a sandbox under {{ accountIdentifier }} to test with:"
1078
+ promptMessage: "[--account] Choose a {{ accountType }} under {{ accountIdentifier }} to test with:"
1070
1079
  sandboxLimit: "Your account reached the limit of {{ limit }} development sandboxes"
1071
1080
  sandboxLimitWithSuggestion: "Your account reached the limit of {{ limit }} development sandboxes. Run {{ authCommand }} to add an existing one to the config."
1081
+ developerTestAccountLimit: "Your account reached the limit of {{ limit }} developer test accounts."
1072
1082
  confirmDefaultAccount: "Continue testing on {{#bold}}{{ accountName }} ({{ accountType }}){{/bold}}? (Y/n)"
1073
1083
  projectLogsPrompt:
1074
1084
  projectName:
@@ -1150,6 +1160,13 @@ en:
1150
1160
  invalidTemplate: "[--template] Could not find template {{ template }}. Please choose an available template."
1151
1161
  noProjectsInConfig: "Please ensure that there is a config.json file that contains a \"projects\" field."
1152
1162
  missingPropertiesInConfig: "Please ensure that each of the projects in your config.json file contain the following properties: [\"name\", \"label\", \"path\", \"insertPath\"]."
1163
+ developerTestAccountPrompt:
1164
+ name:
1165
+ message: "Name your developer test account"
1166
+ errors:
1167
+ invalidName: "You entered an invalid name. Please try again."
1168
+ nameRequired: "The name may not be blank. Please try again."
1169
+ accountNameExists: "Account with name \"{{ name }}\" already exists in the CLI config, please enter a different name."
1153
1170
  downloadProjectPrompt:
1154
1171
  selectProject: "Select a project to download:"
1155
1172
  errors:
@@ -1217,6 +1234,25 @@ en:
1217
1234
  options:
1218
1235
  options:
1219
1236
  describe: "Options to pass to javascript fields files"
1237
+ developerTestAccount:
1238
+ create:
1239
+ loading:
1240
+ add: "Creating developer test account {{#bold}}{{ accountName }}{{/bold}}"
1241
+ fail: "Failed to create a developer test account {{#bold}}{{ accountName }}{{/bold}}."
1242
+ succeed: "Successfully created a developer test account {{#bold}}{{ name }}{{/bold}} with portalId {{#bold}}{{ accountId }}{{/bold}}."
1243
+ success:
1244
+ configFileUpdated: "{{ configFilename }} updated with {{ authMethod }} for account {{ account }}."
1245
+ failure:
1246
+ 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}}."
1247
+ limit: "{{#bold}}{{ accountName }}{{/bold}} reached the limit of {{ limit }} developer test accounts.
1248
+ \n- To connect a developer test account to your HubSpot CLI, run {{#bold}}hs auth{{/bold}} and follow the prompts."
1249
+ alreadyInConfig: "{{#bold}}{{ accountName }}{{/bold}} reached the limit of {{ limit }} developer test accounts.
1250
+ \n- To use an existing developer test account, run {{#bold}}hs accounts use{{/bold}}."
1251
+ scopes:
1252
+ message: "The personal access key you provided doesn't include developer test account permissions."
1253
+ instructions: "To update CLI permissions for \"{{ accountName }}\":
1254
+ \n- Go to {{ url }}, deactivate the existing personal access key, and create a new one that includes developer test account permissions.
1255
+ \n- Update the CLI config for this account by running {{#bold}}hs auth{{/bold}} and entering the new key.\n"
1220
1256
  sandbox:
1221
1257
  create:
1222
1258
  loading:
@@ -1232,7 +1268,7 @@ en:
1232
1268
  configFileUpdated: "{{ configFilename }} updated with {{ authMethod }} for account {{ account }}."
1233
1269
  failure:
1234
1270
  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."
1271
+ 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
1272
  limit:
1237
1273
  developer:
1238
1274
  one: "{{#bold}}{{ accountName }}{{/bold}} reached the limit of {{ limit }} development sandbox.
@@ -1336,7 +1372,7 @@ en:
1336
1372
  400: "The {{ messageDetail }} was bad."
1337
1373
  401: "The {{ messageDetail }} was unauthorized."
1338
1374
  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."
1375
+ 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
1376
  403: "The {{ messageDetail }} was forbidden."
1341
1377
  404Request: 'The {{ action }} failed because "{{ request }}" was not found in account {{ accountId }}.'
1342
1378
  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,186 @@
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
+ };
@@ -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
  };