@hubspot/cli 4.1.7-beta.2 → 4.1.8-beta.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.
@@ -0,0 +1,84 @@
1
+ const { logger } = require('@hubspot/cli-lib/logger');
2
+ const {
3
+ getConfig,
4
+ getConfigPath,
5
+ deleteAccount,
6
+ getConfigDefaultAccount,
7
+ getAccountId: getAccountIdFromConfig,
8
+ updateDefaultAccount,
9
+ } = require('@hubspot/cli-lib/lib/config');
10
+
11
+ const { trackCommandUsage } = require('../../lib/usageTracking');
12
+ const { i18n } = require('@hubspot/cli-lib/lib/lang');
13
+ const { selectAccountFromConfig } = require('../../lib/prompts/accountsPrompt');
14
+ const { loadAndValidateOptions } = require('../../lib/validation');
15
+
16
+ const i18nKey = 'cli.commands.accounts.subcommands.remove';
17
+
18
+ exports.command = 'remove [--account]';
19
+ exports.describe = i18n(`${i18nKey}.describe`);
20
+
21
+ exports.handler = async options => {
22
+ await loadAndValidateOptions(options, false);
23
+
24
+ let config = getConfig();
25
+
26
+ let accountToRemove = options.account;
27
+
28
+ if (accountToRemove && !getAccountIdFromConfig(accountToRemove)) {
29
+ logger.error(
30
+ i18n(`${i18nKey}.errors.accountNotFound`, {
31
+ specifiedAccount: accountToRemove,
32
+ configPath: getConfigPath(),
33
+ })
34
+ );
35
+ }
36
+
37
+ if (!accountToRemove || !getAccountIdFromConfig(accountToRemove)) {
38
+ accountToRemove = await selectAccountFromConfig(
39
+ config,
40
+ i18n(`${i18nKey}.prompts.selectAccountToRemove`)
41
+ );
42
+ }
43
+
44
+ trackCommandUsage(
45
+ 'accounts-remove',
46
+ null,
47
+ getAccountIdFromConfig(accountToRemove)
48
+ );
49
+
50
+ const currentDefaultAccount = getConfigDefaultAccount();
51
+
52
+ await deleteAccount(accountToRemove);
53
+ logger.success(
54
+ i18n(`${i18nKey}.success.accountRemoved`, {
55
+ accountName: accountToRemove,
56
+ })
57
+ );
58
+
59
+ // Get updated version of the config
60
+ config = getConfig();
61
+
62
+ if (accountToRemove === currentDefaultAccount) {
63
+ logger.log();
64
+ logger.log(i18n(`${i18nKey}.logs.replaceDefaultAccount`));
65
+ const newDefaultAccount = await selectAccountFromConfig(config);
66
+ updateDefaultAccount(newDefaultAccount);
67
+ }
68
+ };
69
+
70
+ exports.builder = yargs => {
71
+ yargs.option('account', {
72
+ describe: i18n(`${i18nKey}.options.account.describe`),
73
+ type: 'string',
74
+ });
75
+ yargs.example([
76
+ ['$0 accounts remove', i18n(`${i18nKey}.examples.default`)],
77
+ [
78
+ '$0 accounts remove --account=MyAccount',
79
+ i18n(`${i18nKey}.examples.byName`),
80
+ ],
81
+ ]);
82
+
83
+ return yargs;
84
+ };
@@ -4,6 +4,7 @@ const list = require('./accounts/list');
4
4
  const rename = require('./accounts/rename');
5
5
  const use = require('./accounts/use');
6
6
  const info = require('./accounts/info');
7
+ const remove = require('./accounts/remove');
7
8
 
8
9
  const i18nKey = 'cli.commands.accounts';
9
10
 
@@ -22,6 +23,7 @@ exports.builder = yargs => {
22
23
  .command(rename)
23
24
  .command(use)
24
25
  .command(info)
26
+ .command(remove)
25
27
  .demandCommand(1, '');
26
28
 
27
29
  return yargs;
@@ -108,7 +108,6 @@ exports.handler = async options => {
108
108
  );
109
109
  } else {
110
110
  logApiErrorInstance(
111
- accountId,
112
111
  e,
113
112
  new ApiErrorContext({ accountId, request: functionPath })
114
113
  );
@@ -36,7 +36,7 @@ exports.handler = async options => {
36
36
  logger.debug(i18n(`${i18nKey}.debug.gettingFunctions`));
37
37
 
38
38
  const routesResp = await getRoutes(accountId).catch(async e => {
39
- await logApiErrorInstance(accountId, e, new ApiErrorContext({ accountId }));
39
+ await logApiErrorInstance(e, new ApiErrorContext({ accountId }));
40
40
  process.exit(EXIT_CODES.SUCCESS);
41
41
  });
42
42
 
package/commands/list.js CHANGED
@@ -48,11 +48,7 @@ exports.handler = async options => {
48
48
  try {
49
49
  contentsResp = await getDirectoryContentsByPath(accountId, directoryPath);
50
50
  } catch (e) {
51
- logApiErrorInstance(
52
- accountId,
53
- e,
54
- new ApiErrorContext({ accountId, directoryPath })
55
- );
51
+ logApiErrorInstance(e, new ApiErrorContext({ accountId, directoryPath }));
56
52
  process.exit(EXIT_CODES.SUCCESS);
57
53
  }
58
54
 
@@ -14,6 +14,8 @@ const { createSandboxPrompt } = require('../../lib/prompts/sandboxesPrompt');
14
14
  const {
15
15
  getSandboxType,
16
16
  sandboxCreatePersonalAccessKeyFlow,
17
+ getHasDevelopmentSandboxes,
18
+ getDevSandboxLimit,
17
19
  } = require('../../lib/sandboxes');
18
20
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
19
21
  const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
@@ -22,7 +24,7 @@ const {
22
24
  } = require('@hubspot/cli-lib/errorHandlers/standardErrors');
23
25
  const { ENVIRONMENTS } = require('@hubspot/cli-lib/lib/constants');
24
26
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
25
- const { getAccountConfig } = require('@hubspot/cli-lib');
27
+ const { getAccountConfig, getEnv } = require('@hubspot/cli-lib');
26
28
  const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
27
29
  const {
28
30
  isMissingScopeError,
@@ -127,7 +129,32 @@ exports.handler = async options => {
127
129
  err.error.message
128
130
  ) {
129
131
  logger.log('');
130
- logger.error(err.error.message);
132
+ const devSandboxLimit = getDevSandboxLimit(err.error);
133
+ const plural = devSandboxLimit !== 1;
134
+ const hasDevelopmentSandboxes = getHasDevelopmentSandboxes(accountConfig);
135
+ if (hasDevelopmentSandboxes) {
136
+ logger.error(
137
+ i18n(
138
+ `${i18nKey}.failure.alreadyInConfig.${plural ? 'other' : 'one'}`,
139
+ {
140
+ accountName: accountConfig.name || accountId,
141
+ limit: devSandboxLimit,
142
+ }
143
+ )
144
+ );
145
+ } else {
146
+ const baseUrl = getHubSpotWebsiteOrigin(
147
+ getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD
148
+ );
149
+ logger.error(
150
+ i18n(`${i18nKey}.failure.limit.${plural ? 'other' : 'one'}`, {
151
+ accountName: accountConfig.name || accountId,
152
+ limit: devSandboxLimit,
153
+ devSandboxesLink: `${baseUrl}/sandboxes-developer/${accountId}/development`,
154
+ })
155
+ );
156
+ }
157
+ logger.log('');
131
158
  } else {
132
159
  logErrorInstance(err);
133
160
  }
@@ -14,10 +14,11 @@ const {
14
14
  const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
15
15
  const { deleteSandbox } = require('@hubspot/cli-lib/sandboxes');
16
16
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
17
- const { getConfig, getEnv } = require('@hubspot/cli-lib');
17
+ const { getConfig, getEnv, getAccountConfig } = require('@hubspot/cli-lib');
18
18
  const { deleteSandboxPrompt } = require('../../lib/prompts/sandboxesPrompt');
19
19
  const {
20
20
  removeSandboxAccountFromConfig,
21
+ updateDefaultAccount,
21
22
  } = require('@hubspot/cli-lib/lib/config');
22
23
  const {
23
24
  selectAndSetAsDefaultAccountPrompt,
@@ -29,6 +30,8 @@ const { ENVIRONMENTS } = require('@hubspot/cli-lib/lib/constants');
29
30
  const {
30
31
  isSpecifiedError,
31
32
  } = require('@hubspot/cli-lib/errorHandlers/apiErrors');
33
+ const { HubSpotAuthError } = require('@hubspot/cli-lib/lib/models/Errors');
34
+ const { getAccountName } = require('../../lib/sandboxes');
32
35
 
33
36
  const i18nKey = 'cli.commands.sandbox.subcommands.delete';
34
37
 
@@ -38,47 +41,66 @@ exports.describe = i18n(`${i18nKey}.describe`);
38
41
  exports.handler = async options => {
39
42
  await loadAndValidateOptions(options, false);
40
43
 
41
- const { account } = options;
44
+ const { account, force } = options;
42
45
  const config = getConfig();
43
46
 
44
47
  let accountPrompt;
45
48
  if (!account) {
46
- accountPrompt = await deleteSandboxPrompt(config);
49
+ if (!force) {
50
+ accountPrompt = await deleteSandboxPrompt(config);
51
+ } else {
52
+ // Account is required, throw error if force flag is present and no account is specified
53
+ logger.log('');
54
+ logger.error(i18n(`${i18nKey}.failure.noAccount`));
55
+ process.exit(EXIT_CODES.ERROR);
56
+ }
57
+ if (!accountPrompt) {
58
+ logger.log('');
59
+ logger.error(i18n(`${i18nKey}.failure.noSandboxAccounts`));
60
+ process.exit(EXIT_CODES.ERROR);
61
+ }
47
62
  }
63
+
48
64
  const sandboxAccountId = getAccountId({
49
65
  account: account || accountPrompt.account,
50
66
  });
51
-
67
+ const accountConfig = getAccountConfig(sandboxAccountId);
52
68
  const isDefaultAccount =
53
69
  sandboxAccountId === getAccountId(config.defaultPortal);
54
70
 
55
71
  trackCommandUsage('sandbox-delete', null, sandboxAccountId);
56
72
 
73
+ const baseUrl = getHubSpotWebsiteOrigin(
74
+ getEnv(sandboxAccountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD
75
+ );
76
+
57
77
  let parentAccountId;
58
78
  for (const portal of config.portals) {
59
79
  if (portal.portalId === sandboxAccountId) {
60
80
  if (portal.parentAccountId) {
61
81
  parentAccountId = portal.parentAccountId;
62
- } else {
82
+ } else if (!force) {
63
83
  const parentAccountPrompt = await deleteSandboxPrompt(config, true);
64
84
  parentAccountId = getAccountId({
65
85
  account: parentAccountPrompt.account,
66
86
  });
87
+ } else {
88
+ logger.error(i18n(`${i18nKey}.failure.noParentAccount`));
89
+ process.exit(EXIT_CODES.ERROR);
67
90
  }
68
91
  }
69
92
  }
70
93
 
71
- if (!getAccountId({ account: parentAccountId })) {
72
- const baseUrl = getHubSpotWebsiteOrigin(
73
- getEnv(sandboxAccountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD
74
- );
75
- const url = `${baseUrl}/sandboxes/${parentAccountId}`;
76
- const command = `hs auth ${
77
- getEnv(sandboxAccountId) === 'qa' ? '--qa' : ''
78
- } --account=${parentAccountId}`;
94
+ const parentAccount = getAccountConfig(parentAccountId);
95
+ const url = `${baseUrl}/sandboxes/${parentAccountId}`;
96
+ const command = `hs auth ${
97
+ getEnv(sandboxAccountId) === 'qa' ? '--qa' : ''
98
+ } --account=${parentAccountId}`;
99
+
100
+ if (parentAccountId && !getAccountId({ account: parentAccountId })) {
79
101
  logger.log('');
80
102
  logger.error(
81
- i18n(`${i18nKey}.noParentPortalAvailable`, {
103
+ i18n(`${i18nKey}.failure.noParentPortalAvailable`, {
82
104
  parentAccountId,
83
105
  url,
84
106
  command,
@@ -90,30 +112,32 @@ exports.handler = async options => {
90
112
 
91
113
  logger.debug(
92
114
  i18n(`${i18nKey}.debug.deleting`, {
93
- account: account || accountPrompt.account,
115
+ account: getAccountName(accountConfig),
94
116
  })
95
117
  );
96
118
 
97
119
  if (isDefaultAccount) {
98
120
  logger.log(
99
121
  i18n(`${i18nKey}.defaultAccountWarning`, {
100
- account: account || accountPrompt.account,
122
+ account: getAccountName(accountConfig),
101
123
  })
102
124
  );
103
125
  }
104
126
 
105
127
  try {
106
- const { confirmSandboxDeletePrompt: confirmed } = await promptUser([
107
- {
108
- name: 'confirmSandboxDeletePrompt',
109
- type: 'confirm',
110
- message: i18n(`${i18nKey}.confirm`, {
111
- account: account || accountPrompt.account,
112
- }),
113
- },
114
- ]);
115
- if (!confirmed) {
116
- process.exit(EXIT_CODES.SUCCESS);
128
+ if (!force) {
129
+ const { confirmSandboxDeletePrompt: confirmed } = await promptUser([
130
+ {
131
+ name: 'confirmSandboxDeletePrompt',
132
+ type: 'confirm',
133
+ message: i18n(`${i18nKey}.confirm`, {
134
+ account: getAccountName(accountConfig),
135
+ }),
136
+ },
137
+ ]);
138
+ if (!confirmed) {
139
+ process.exit(EXIT_CODES.SUCCESS);
140
+ }
117
141
  }
118
142
 
119
143
  await deleteSandbox(parentAccountId, sandboxAccountId);
@@ -133,8 +157,11 @@ exports.handler = async options => {
133
157
  const promptDefaultAccount = removeSandboxAccountFromConfig(
134
158
  sandboxAccountId
135
159
  );
136
- if (promptDefaultAccount) {
160
+ if (promptDefaultAccount && !force) {
137
161
  await selectAndSetAsDefaultAccountPrompt(getConfig());
162
+ } else {
163
+ // If force is specified, skip prompt and set the parent account id as the default account
164
+ updateDefaultAccount(parentAccountId);
138
165
  }
139
166
  process.exit(EXIT_CODES.SUCCESS);
140
167
  } catch (err) {
@@ -146,7 +173,20 @@ exports.handler = async options => {
146
173
  sandboxAccountId
147
174
  );
148
175
 
149
- if (
176
+ if (err instanceof HubSpotAuthError) {
177
+ // Intercept invalid key error
178
+ // This command uses the parent portal PAK to delete a sandbox, so we must specify which account needs a new key
179
+ const regex = /\bYour personal access key is invalid\b/;
180
+ const match = err.message.match(regex);
181
+ if (match && match[0]) {
182
+ logger.log('');
183
+ logger.error(
184
+ i18n(`${i18nKey}.failure.invalidKey`, {
185
+ account: getAccountName(parentAccount),
186
+ })
187
+ );
188
+ }
189
+ } else if (
150
190
  isSpecifiedError(
151
191
  err,
152
192
  404,
@@ -156,8 +196,8 @@ exports.handler = async options => {
156
196
  ) {
157
197
  logger.log('');
158
198
  logger.warn(
159
- i18n(`${i18nKey}.objectNotFound`, {
160
- account: account || accountPrompt.account,
199
+ i18n(`${i18nKey}.failure.objectNotFound`, {
200
+ account: getAccountName(accountConfig),
161
201
  })
162
202
  );
163
203
  logger.log('');
@@ -165,8 +205,11 @@ exports.handler = async options => {
165
205
  const promptDefaultAccount = removeSandboxAccountFromConfig(
166
206
  sandboxAccountId
167
207
  );
168
- if (promptDefaultAccount) {
208
+ if (promptDefaultAccount && !force) {
169
209
  await selectAndSetAsDefaultAccountPrompt(getConfig());
210
+ } else {
211
+ // If force is specified, skip prompt and set the parent account id as the default account
212
+ updateDefaultAccount(parentAccountId);
170
213
  }
171
214
  process.exit(EXIT_CODES.SUCCESS);
172
215
  } else {
@@ -181,6 +224,11 @@ exports.builder = yargs => {
181
224
  describe: i18n(`${i18nKey}.options.account.describe`),
182
225
  type: 'string',
183
226
  });
227
+ yargs.option('f', {
228
+ type: 'boolean',
229
+ alias: 'force',
230
+ describe: i18n(`${i18nKey}.examples.force`),
231
+ });
184
232
 
185
233
  yargs.example([
186
234
  [
@@ -15,14 +15,14 @@ const mapAccountChoices = portals =>
15
15
 
16
16
  const i18nKey = 'cli.commands.accounts.subcommands.use';
17
17
 
18
- const selectAccountFromConfig = async config => {
18
+ const selectAccountFromConfig = async (config, prompt) => {
19
19
  const { default: selectedDefault } = await promptUser([
20
20
  {
21
21
  type: 'list',
22
22
  look: false,
23
23
  name: 'default',
24
24
  pageSize: 20,
25
- message: i18n(`${i18nKey}.promptMessage`),
25
+ message: prompt || i18n(`${i18nKey}.promptMessage`),
26
26
  choices: mapAccountChoices(config.portals),
27
27
  default: config.defaultPortal,
28
28
  },
@@ -49,4 +49,5 @@ const selectAndSetAsDefaultAccountPrompt = async config => {
49
49
  module.exports = {
50
50
  selectAndSetAsDefaultAccountPrompt,
51
51
  selectAccountFromConfig,
52
+ mapAccountChoices,
52
53
  };
@@ -66,7 +66,7 @@ const projectLogsPrompt = (accountId, promptOptions = {}) => {
66
66
 
67
67
  if (deployedBuild && deployedBuild.subbuildStatuses) {
68
68
  return deployedBuild.subbuildStatuses
69
- .filter(subbuild => subbuild.buildType === 'APP')
69
+ .filter(subbuild => subbuild.buildType === 'PRIVATE_APP')
70
70
  .map(subbuild => ({
71
71
  name: subbuild.buildName,
72
72
  value: subbuild.buildName,
@@ -15,6 +15,18 @@ const mapSandboxAccountChoices = portals =>
15
15
  };
16
16
  });
17
17
 
18
+ const mapNonSandboxAccountChoices = portals =>
19
+ portals
20
+ .filter(
21
+ p => p.sandboxAccountType === null || p.sandboxAccountType === undefined
22
+ )
23
+ .map(p => {
24
+ return {
25
+ name: `${p.name} (${p.portalId})`,
26
+ value: p.name || p.portalId,
27
+ };
28
+ });
29
+
18
30
  const createSandboxPrompt = () => {
19
31
  return promptUser([
20
32
  {
@@ -32,6 +44,12 @@ const createSandboxPrompt = () => {
32
44
  };
33
45
 
34
46
  const deleteSandboxPrompt = (config, promptParentAccount = false) => {
47
+ const choices = promptParentAccount
48
+ ? mapNonSandboxAccountChoices(config.portals)
49
+ : mapSandboxAccountChoices(config.portals);
50
+ if (!choices.length) {
51
+ return undefined;
52
+ }
35
53
  return promptUser([
36
54
  {
37
55
  name: 'account',
@@ -43,7 +61,7 @@ const deleteSandboxPrompt = (config, promptParentAccount = false) => {
43
61
  type: 'list',
44
62
  look: false,
45
63
  pageSize: 20,
46
- choices: mapSandboxAccountChoices(config.portals),
64
+ choices,
47
65
  default: config.defaultPortal,
48
66
  },
49
67
  ]);
package/lib/sandboxes.js CHANGED
@@ -35,6 +35,29 @@ function getAccountName(config) {
35
35
  return `${config.name} ${isSandbox ? sandboxName : ''}(${config.portalId})`;
36
36
  }
37
37
 
38
+ function getHasDevelopmentSandboxes(parentAccountConfig) {
39
+ const config = getConfig();
40
+ const parentPortalId = parentAccountConfig.portalId;
41
+ for (const portal of config.portals) {
42
+ if (
43
+ (portal.parentAccountId !== null ||
44
+ portal.parentAccountId !== undefined) &&
45
+ portal.parentAccountId === parentPortalId &&
46
+ portal.sandboxAccountType &&
47
+ portal.sandboxAccountType === 'DEVELOPER'
48
+ ) {
49
+ return true;
50
+ }
51
+ }
52
+ return false;
53
+ }
54
+
55
+ function getDevSandboxLimit(error) {
56
+ // Error context should contain a limit property with a list of one number. That number is the current limit
57
+ const limit = error.context && error.context.limit && error.context.limit[0];
58
+ return limit ? parseInt(limit, 10) : 1; // Default to 1
59
+ }
60
+
38
61
  // Fetches available sync types for a given sandbox portal
39
62
  async function getAvailableSyncTypes(parentAccountConfig, config) {
40
63
  const parentPortalId = parentAccountConfig.portalId;
@@ -211,6 +234,8 @@ function pollSyncTaskStatus(accountId, taskId, syncStatusUrl) {
211
234
  module.exports = {
212
235
  getSandboxType,
213
236
  getAccountName,
237
+ getHasDevelopmentSandboxes,
238
+ getDevSandboxLimit,
214
239
  getAvailableSyncTypes,
215
240
  sandboxCreatePersonalAccessKeyFlow,
216
241
  pollSyncTaskStatus,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "4.1.7-beta.2",
3
+ "version": "4.1.8-beta.0",
4
4
  "description": "CLI for working with HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -8,8 +8,8 @@
8
8
  "url": "https://github.com/HubSpot/hubspot-cms-tools"
9
9
  },
10
10
  "dependencies": {
11
- "@hubspot/cli-lib": "4.1.7-beta.2",
12
- "@hubspot/serverless-dev-runtime": "4.1.7-beta.2",
11
+ "@hubspot/cli-lib": "4.1.8-beta.0",
12
+ "@hubspot/serverless-dev-runtime": "4.1.8-beta.0",
13
13
  "archiver": "^5.3.0",
14
14
  "chalk": "^4.1.2",
15
15
  "cli-progress": "^3.11.2",
@@ -38,5 +38,5 @@
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "ce9f106f0da7a39fbf14ff0ead2d5d750adee428"
41
+ "gitHead": "af16182b2c7abd401771d5e0eed3de21e7b0477c"
42
42
  }