@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/bin/cli.js CHANGED
@@ -5,6 +5,7 @@ const updateNotifier = require('update-notifier');
5
5
  const chalk = require('chalk');
6
6
 
7
7
  const { logger } = require('@hubspot/local-dev-lib/logger');
8
+ const { addUserAgentHeader } = require('@hubspot/local-dev-lib/http');
8
9
  const { logErrorInstance } = require('../lib/errorHandlers/standardErrors');
9
10
  const { setLogLevel, getCommandName } = require('../lib/commonOpts');
10
11
  const {
@@ -110,9 +111,13 @@ const performChecks = argv => {
110
111
  }
111
112
  };
112
113
 
114
+ const setRequestHeaders = () => {
115
+ addUserAgentHeader('HubSpot CLI', pkg.version);
116
+ };
117
+
113
118
  const argv = yargs
114
119
  .usage('The command line interface to interact with HubSpot.')
115
- .middleware([setLogLevel])
120
+ .middleware([setLogLevel, setRequestHeaders])
116
121
  .exitProcess(false)
117
122
  .fail(handleFailure)
118
123
  .option('debug', {
@@ -13,7 +13,6 @@ const {
13
13
  addUseEnvironmentOptions,
14
14
  addTestingOptions,
15
15
  } = require('../../lib/commonOpts');
16
- const { getAccountName } = require('../../lib/sandboxes');
17
16
  const { promptUser } = require('../../lib/prompts/promptUtils');
18
17
  const { getTableContents } = require('../../lib/ui/table');
19
18
  const SpinniesManager = require('../../lib/ui/SpinniesManager');
@@ -21,6 +20,7 @@ const { getConfig, deleteAccount } = require('@hubspot/local-dev-lib/config');
21
20
  const {
22
21
  isSpecifiedHubSpotAuthError,
23
22
  } = require('@hubspot/local-dev-lib/errors/apiErrors');
23
+ const { uiAccountDescription } = require('../../lib/ui');
24
24
 
25
25
  const i18nKey = 'cli.commands.accounts.subcommands.clean';
26
26
 
@@ -87,7 +87,7 @@ exports.handler = async options => {
87
87
  });
88
88
  logger.log(
89
89
  getTableContents(
90
- accountsToRemove.map(p => [getAccountName(p)]),
90
+ accountsToRemove.map(p => [uiAccountDescription(p)]),
91
91
  { border: { bodyLeft: ' ' } }
92
92
  )
93
93
  );
@@ -111,7 +111,7 @@ exports.handler = async options => {
111
111
  await deleteAccount(accountToRemove.name);
112
112
  logger.log(
113
113
  i18n(`${i18nKey}.removeSuccess`, {
114
- accountName: getAccountName(accountToRemove),
114
+ accountName: uiAccountDescription(accountToRemove),
115
115
  })
116
116
  );
117
117
  }
@@ -9,8 +9,13 @@ const {
9
9
  } = require('../../lib/commonOpts');
10
10
  const { trackCommandUsage } = require('../../lib/usageTracking');
11
11
  const { loadAndValidateOptions } = require('../../lib/validation');
12
- const { isSandbox, getSandboxName } = require('../../lib/sandboxes');
13
- const { isDeveloperTestAccount } = require('../../lib/developerTestAccounts');
12
+ const { getSandboxName } = require('../../lib/sandboxes');
13
+ const {
14
+ isSandbox,
15
+ isDeveloperTestAccount,
16
+ isAppDeveloperAccount,
17
+ } = require('../../lib/accountTypes');
18
+
14
19
  const { i18n } = require('../../lib/lang');
15
20
  const {
16
21
  HUBSPOT_ACCOUNT_TYPES,
@@ -53,21 +58,28 @@ const sortAndMapPortals = portals => {
53
58
 
54
59
  const getPortalData = mappedPortalData => {
55
60
  const portalData = [];
56
- Object.values(mappedPortalData).forEach(set => {
61
+ Object.entries(mappedPortalData).forEach(([key, set]) => {
62
+ const hasParentPortal = set.filter(
63
+ p => p.portalId === parseInt(key, 10)
64
+ )[0];
57
65
  set.forEach(portal => {
58
66
  let name = portal.name;
59
67
  if (isSandbox(portal)) {
60
68
  name = `${portal.name} ${getSandboxName(portal)}`;
61
- if (set.length > 1) {
69
+ if (hasParentPortal && set.length > 1) {
62
70
  name = `↳ ${name}`;
63
71
  }
64
72
  } else if (isDeveloperTestAccount(portal)) {
65
73
  name = `${portal.name} [${
66
74
  HUBSPOT_ACCOUNT_TYPE_STRINGS[HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST]
67
75
  }]`;
68
- if (set.length > 1) {
76
+ if (hasParentPortal && set.length > 1) {
69
77
  name = `↳ ${name}`;
70
78
  }
79
+ } else if (isAppDeveloperAccount(portal)) {
80
+ name = `${portal.name} [${
81
+ HUBSPOT_ACCOUNT_TYPE_STRINGS[HUBSPOT_ACCOUNT_TYPES.APP_DEVELOPER]
82
+ }]`;
71
83
  }
72
84
  portalData.push([name, portal.portalId, portal.authType]);
73
85
  });
@@ -5,10 +5,7 @@ const {
5
5
  addUseEnvironmentOptions,
6
6
  addTestingOptions,
7
7
  } = require('../../lib/commonOpts');
8
- const {
9
- trackCommandUsage,
10
- trackCommandMetadataUsage,
11
- } = require('../../lib/usageTracking');
8
+ const { trackCommandUsage } = require('../../lib/usageTracking');
12
9
  const { loadAndValidateOptions } = require('../../lib/validation');
13
10
  const { handleExit } = require('../../lib/process');
14
11
  const { i18n } = require('../../lib/lang');
@@ -18,61 +15,38 @@ const {
18
15
  getAccountConfig,
19
16
  getEnv,
20
17
  } = require('@hubspot/local-dev-lib/config');
21
- const {
22
- createProject,
23
- fetchProject,
24
- } = require('@hubspot/local-dev-lib/api/projects');
18
+ const { fetchProject } = require('@hubspot/local-dev-lib/api/projects');
25
19
  const {
26
20
  getProjectConfig,
27
21
  ensureProjectExists,
28
- handleProjectUpload,
29
- pollProjectBuildAndDeploy,
30
22
  validateProjectConfig,
31
23
  } = require('../../lib/projects');
32
24
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
33
- const {
34
- uiAccountDescription,
35
- uiBetaTag,
36
- uiCommandReference,
37
- uiLine,
38
- } = require('../../lib/ui');
39
- const { confirmPrompt } = require('../../lib/prompts/promptUtils');
40
- const {
41
- selectTargetAccountPrompt,
42
- confirmDefaultAccountPrompt,
43
- } = require('../../lib/prompts/projectDevTargetAccountPrompt');
25
+ const { uiBetaTag } = require('../../lib/ui');
44
26
  const SpinniesManager = require('../../lib/ui/SpinniesManager');
45
27
  const LocalDevManager = require('../../lib/LocalDevManager');
46
- const { isSandbox, getSandboxTypeAsString } = require('../../lib/sandboxes');
47
- const { sandboxNamePrompt } = require('../../lib/prompts/sandboxesPrompt');
48
28
  const {
49
- validateSandboxUsageLimits,
50
- getAvailableSyncTypes,
51
- } = require('../../lib/sandboxes');
29
+ isSandbox,
30
+ isDeveloperTestAccount,
31
+ isStandardAccount,
32
+ isAppDeveloperAccount,
33
+ } = require('../../lib/accountTypes');
52
34
  const { getValidEnv } = require('@hubspot/local-dev-lib/environment');
35
+
53
36
  const {
54
- PROJECT_BUILD_TEXT,
55
- PROJECT_DEPLOY_TEXT,
56
- PROJECT_ERROR_TYPES,
57
- } = require('../../lib/constants');
58
-
59
- const { buildSandbox } = require('../../lib/sandboxCreate');
60
- const { syncSandbox } = require('../../lib/sandboxSync');
61
- const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls');
62
- const {
63
- logApiErrorInstance,
64
- ApiErrorContext,
65
- isSpecifiedError, // Migrate isSpecifiedError to local-dev-lib version only after uploadProject is migrated to local-dev-lib
66
- } = require('../../lib/errorHandlers/apiErrors');
67
- const {
68
- isMissingScopeError,
69
- } = require('@hubspot/local-dev-lib/errors/apiErrors');
70
- const { logErrorInstance } = require('../../lib/errorHandlers/standardErrors');
71
- const { isDeveloperTestAccount } = require('../../lib/developerTestAccounts');
37
+ findProjectComponents,
38
+ getProjectComponentTypes,
39
+ COMPONENT_TYPES,
40
+ } = require('../../lib/projectStructure');
72
41
  const {
73
- HUBSPOT_ACCOUNT_TYPES,
74
- HUBSPOT_ACCOUNT_TYPE_STRINGS,
75
- } = require('@hubspot/local-dev-lib/constants/config');
42
+ confirmDefaultAccountIsTarget,
43
+ suggestRecommendedNestedAccount,
44
+ checkIfAppDeveloperAccount,
45
+ createSandboxForLocalDev,
46
+ createDeveloperTestAccountForLocalDev,
47
+ createNewProjectForLocalDev,
48
+ createInitialBuildForNewProject,
49
+ } = require('../../lib/localDev');
76
50
 
77
51
  const i18nKey = 'cli.commands.project.subcommands.dev';
78
52
 
@@ -98,126 +72,77 @@ exports.handler = async options => {
98
72
 
99
73
  validateProjectConfig(projectConfig, projectDir);
100
74
 
75
+ const components = await findProjectComponents(projectDir);
76
+ const componentTypes = getProjectComponentTypes(components);
77
+ const hasPrivateApps = componentTypes[COMPONENT_TYPES.privateApp];
78
+ const hasPublicApps = componentTypes[COMPONENT_TYPES.publicApp];
79
+
80
+ if (hasPrivateApps && hasPublicApps) {
81
+ logger.error(i18n(`${i18nKey}.errors.invalidProjectComponents`));
82
+ process.exit(EXIT_CODES.SUCCESS);
83
+ }
84
+
101
85
  const accounts = getConfigAccounts();
102
- let targetAccountId = options.account ? accountId : null;
103
- let createNewSandbox = false;
104
- const defaultAccountIsSandbox = isSandbox(accountConfig);
105
- const defaultAccountIsDeveloperTestAccount = isDeveloperTestAccount(
106
- accountConfig
107
- );
108
86
 
109
- if (
110
- !targetAccountId &&
111
- (defaultAccountIsSandbox || defaultAccountIsDeveloperTestAccount)
112
- ) {
113
- logger.log();
114
- const useDefaultAccount = await confirmDefaultAccountPrompt(
115
- accountConfig.name,
116
- defaultAccountIsSandbox
117
- ? `${getSandboxTypeAsString(accountConfig.accountType)} sandbox`
118
- : HUBSPOT_ACCOUNT_TYPE_STRINGS[HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST]
119
- );
87
+ const defaultAccountIsRecommendedType =
88
+ isDeveloperTestAccount(accountConfig) ||
89
+ (!hasPublicApps && isSandbox(accountConfig));
120
90
 
121
- if (useDefaultAccount) {
122
- targetAccountId = accountId;
123
- } else {
124
- logger.log(
125
- i18n(`${i18nKey}.logs.declineDefaultAccountExplanation`, {
126
- useCommand: uiCommandReference('hs accounts use'),
127
- devCommand: uiCommandReference('hs project dev'),
128
- })
129
- );
130
- process.exit(EXIT_CODES.SUCCESS);
131
- }
132
- }
91
+ // The account that the project must exist in
92
+ let targetProjectAccountId = options.account ? accountId : null;
93
+ // The account that we are locally testing against
94
+ let targetTestingAccountId = targetProjectAccountId;
133
95
 
134
- if (!targetAccountId) {
135
- logger.log();
136
- uiLine();
137
- logger.warn(i18n(`${i18nKey}.logs.nonSandboxWarning`));
138
- uiLine();
139
- logger.log();
96
+ let createNewSandbox = false;
97
+ let createNewDeveloperTestAccount = false;
98
+
99
+ if (!targetProjectAccountId && defaultAccountIsRecommendedType) {
100
+ await confirmDefaultAccountIsTarget(accountConfig, hasPublicApps);
101
+ targetProjectAccountId = hasPublicApps
102
+ ? accountConfig.parentAccountId
103
+ : accountId;
104
+ } else if (!targetProjectAccountId && hasPublicApps) {
105
+ checkIfAppDeveloperAccount(accountConfig);
106
+ }
140
107
 
108
+ if (!targetProjectAccountId) {
141
109
  const {
142
- targetAccountId: promptTargetAccountId,
143
- createNewSandbox: promptCreateNewSandbox,
144
- } = await selectTargetAccountPrompt(accounts, accountConfig);
110
+ targetAccountId,
111
+ parentAccountId,
112
+ createNestedAccount,
113
+ } = await suggestRecommendedNestedAccount(
114
+ accounts,
115
+ accountConfig,
116
+ hasPublicApps
117
+ );
118
+
119
+ targetProjectAccountId = hasPublicApps ? parentAccountId : targetAccountId;
120
+ targetTestingAccountId = targetAccountId;
145
121
 
146
- targetAccountId = promptTargetAccountId;
147
- createNewSandbox = promptCreateNewSandbox;
122
+ createNewSandbox = isStandardAccount(accountConfig) && createNestedAccount;
123
+ createNewDeveloperTestAccount =
124
+ isAppDeveloperAccount(accountConfig) && createNestedAccount;
148
125
  }
149
126
 
150
127
  if (createNewSandbox) {
151
- try {
152
- await validateSandboxUsageLimits(
153
- accountConfig,
154
- HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
155
- env
156
- );
157
- } catch (err) {
158
- if (isMissingScopeError(err)) {
159
- logger.error(
160
- i18n('cli.lib.sandbox.create.failure.scopes.message', {
161
- accountName: accountConfig.name || accountId,
162
- })
163
- );
164
- const websiteOrigin = getHubSpotWebsiteOrigin(env);
165
- const url = `${websiteOrigin}/personal-access-key/${accountId}`;
166
- logger.info(
167
- i18n('cli.lib.sandbox.create.failure.scopes.instructions', {
168
- accountName: accountConfig.name || accountId,
169
- url,
170
- })
171
- );
172
- } else {
173
- logErrorInstance(err);
174
- }
175
- process.exit(EXIT_CODES.ERROR);
176
- }
177
- try {
178
- const { name } = await sandboxNamePrompt(
179
- HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX
180
- );
181
-
182
- trackCommandMetadataUsage(
183
- 'sandbox-create',
184
- { step: 'project-dev' },
185
- accountId
186
- );
187
-
188
- const { result } = await buildSandbox({
189
- name,
190
- type: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
191
- accountConfig,
192
- env,
193
- });
194
-
195
- targetAccountId = result.sandbox.sandboxHubId;
196
-
197
- const sandboxAccountConfig = getAccountConfig(
198
- result.sandbox.sandboxHubId
199
- );
200
- const syncTasks = await getAvailableSyncTypes(
201
- accountConfig,
202
- sandboxAccountConfig
203
- );
204
- await syncSandbox({
205
- accountConfig: sandboxAccountConfig,
206
- parentAccountConfig: accountConfig,
207
- env,
208
- syncTasks,
209
- allowEarlyTermination: false, // Don't let user terminate early in this flow
210
- skipPolling: true, // Skip polling, sync will run and complete in the background
211
- });
212
- } catch (err) {
213
- logErrorInstance(err);
214
- process.exit(EXIT_CODES.ERROR);
215
- }
128
+ targetProjectAccountId = await createSandboxForLocalDev(
129
+ accountId,
130
+ accountConfig,
131
+ env
132
+ );
133
+ // We will be running our tests against this new sandbox account
134
+ targetTestingAccountId = targetProjectAccountId;
135
+ }
136
+ if (createNewDeveloperTestAccount) {
137
+ targetTestingAccountId = await createDeveloperTestAccountForLocalDev(
138
+ accountId,
139
+ accountConfig,
140
+ env
141
+ );
216
142
  }
217
143
 
218
- logger.log();
219
144
  const projectExists = await ensureProjectExists(
220
- targetAccountId,
145
+ targetProjectAccountId,
221
146
  projectConfig.name,
222
147
  {
223
148
  allowCreate: false,
@@ -230,7 +155,10 @@ exports.handler = async options => {
230
155
  let isGithubLinked;
231
156
 
232
157
  if (projectExists) {
233
- const project = await fetchProject(targetAccountId, projectConfig.name);
158
+ const project = await fetchProject(
159
+ targetProjectAccountId,
160
+ projectConfig.name
161
+ );
234
162
  deployedBuild = project.deployedBuild;
235
163
  isGithubLinked =
236
164
  project.sourceIntegration &&
@@ -240,114 +168,17 @@ exports.handler = async options => {
240
168
  SpinniesManager.init();
241
169
 
242
170
  if (!projectExists) {
243
- // Create the project without prompting if this is a newly created sandbox
244
- let shouldCreateProject = createNewSandbox;
245
-
246
- if (!shouldCreateProject) {
247
- logger.log();
248
- uiLine();
249
- logger.warn(
250
- i18n(`${i18nKey}.logs.projectMustExistExplanation`, {
251
- accountIdentifier: uiAccountDescription(targetAccountId),
252
- projectName: projectConfig.name,
253
- })
254
- );
255
- uiLine();
256
-
257
- shouldCreateProject = await confirmPrompt(
258
- i18n(`${i18nKey}.prompt.createProject`, {
259
- accountIdentifier: uiAccountDescription(targetAccountId),
260
- projectName: projectConfig.name,
261
- })
262
- );
263
- }
264
-
265
- if (shouldCreateProject) {
266
- SpinniesManager.add('createProject', {
267
- text: i18n(`${i18nKey}.status.creatingProject`, {
268
- accountIdentifier: uiAccountDescription(targetAccountId),
269
- projectName: projectConfig.name,
270
- }),
271
- });
272
-
273
- try {
274
- await createProject(targetAccountId, projectConfig.name);
275
- SpinniesManager.succeed('createProject', {
276
- text: i18n(`${i18nKey}.status.createdProject`, {
277
- accountIdentifier: uiAccountDescription(targetAccountId),
278
- projectName: projectConfig.name,
279
- }),
280
- succeedColor: 'white',
281
- });
282
- } catch (err) {
283
- SpinniesManager.fail('createProject');
284
- logger.log(i18n(`${i18nKey}.status.failedToCreateProject`));
285
- process.exit(EXIT_CODES.ERROR);
286
- }
287
- } else {
288
- // We cannot continue if the project does not exist in the target account
289
- logger.log();
290
- logger.log(i18n(`${i18nKey}.logs.choseNotToCreateProject`));
291
- process.exit(EXIT_CODES.SUCCESS);
292
- }
293
- }
294
-
295
- let initialUploadResult;
171
+ await createNewProjectForLocalDev(
172
+ projectConfig,
173
+ targetProjectAccountId,
174
+ createNewSandbox
175
+ );
296
176
 
297
- // Create an initial build if the project was newly created in the account
298
- if (!projectExists) {
299
- initialUploadResult = await handleProjectUpload(
300
- targetAccountId,
177
+ deployedBuild = await createInitialBuildForNewProject(
301
178
  projectConfig,
302
179
  projectDir,
303
- (...args) => pollProjectBuildAndDeploy(...args, true),
304
- i18n(`${i18nKey}.logs.initialUploadMessage`)
180
+ targetProjectAccountId
305
181
  );
306
-
307
- if (initialUploadResult.uploadError) {
308
- if (
309
- isSpecifiedError(initialUploadResult.uploadError, {
310
- subCategory: PROJECT_ERROR_TYPES.PROJECT_LOCKED,
311
- })
312
- ) {
313
- logger.log();
314
- logger.error(i18n(`${i18nKey}.errors.projectLockedError`));
315
- logger.log();
316
- } else {
317
- logApiErrorInstance(
318
- initialUploadResult.uploadError,
319
- new ApiErrorContext({
320
- accountId,
321
- projectName: projectConfig.name,
322
- })
323
- );
324
- }
325
- process.exit(EXIT_CODES.ERROR);
326
- }
327
-
328
- if (!initialUploadResult.succeeded) {
329
- let subTasks = [];
330
-
331
- if (initialUploadResult.buildResult.status === 'FAILURE') {
332
- subTasks =
333
- initialUploadResult.buildResult[PROJECT_BUILD_TEXT.SUBTASK_KEY];
334
- } else if (initialUploadResult.deployResult.status === 'FAILURE') {
335
- subTasks =
336
- initialUploadResult.deployResult[PROJECT_DEPLOY_TEXT.SUBTASK_KEY];
337
- }
338
-
339
- const failedSubTasks = subTasks.filter(task => task.status === 'FAILURE');
340
-
341
- logger.log();
342
- failedSubTasks.forEach(failedSubTask => {
343
- console.error(failedSubTask.errorMessage);
344
- });
345
- logger.log();
346
-
347
- process.exit(EXIT_CODES.ERROR);
348
- }
349
-
350
- deployedBuild = initialUploadResult.buildResult;
351
182
  }
352
183
 
353
184
  const LocalDev = new LocalDevManager({
@@ -355,8 +186,9 @@ exports.handler = async options => {
355
186
  deployedBuild,
356
187
  projectConfig,
357
188
  projectDir,
358
- targetAccountId,
189
+ targetAccountId: targetTestingAccountId,
359
190
  isGithubLinked,
191
+ components,
360
192
  });
361
193
 
362
194
  await LocalDev.start();
@@ -16,14 +16,15 @@ const {
16
16
  logFeedbackMessage,
17
17
  validateProjectConfig,
18
18
  pollProjectBuildAndDeploy,
19
+ displayWarnLogs,
19
20
  } = require('../../lib/projects');
20
21
  const { i18n } = require('../../lib/lang');
21
22
  const { getAccountConfig } = require('@hubspot/local-dev-lib/config');
23
+ const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/apiErrors');
22
24
  const { PROJECT_ERROR_TYPES } = require('../../lib/constants');
23
25
  const {
24
26
  logApiErrorInstance,
25
27
  ApiErrorContext,
26
- isSpecifiedError,
27
28
  } = require('../../lib/errorHandlers/apiErrors');
28
29
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
29
30
 
@@ -91,6 +92,8 @@ exports.handler = async options => {
91
92
  );
92
93
  uiLine();
93
94
  logFeedbackMessage(result.buildId);
95
+
96
+ displayWarnLogs(accountId, projectConfig.name, result.buildId);
94
97
  process.exit(EXIT_CODES.SUCCESS);
95
98
  }
96
99
  } catch (e) {
@@ -14,12 +14,14 @@ 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
16
  const { uiLine, uiAccountDescription } = require('../../lib/ui');
17
+ const {
18
+ isSandbox,
19
+ isStandardSandbox,
20
+ isDevelopmentSandbox,
21
+ } = require('../../lib/accountTypes');
17
22
  const {
18
23
  getAvailableSyncTypes,
19
24
  getSyncTypesWithContactRecordsPrompt,
20
- isDevelopmentSandbox,
21
- isStandardSandbox,
22
- isSandbox,
23
25
  } = require('../../lib/sandboxes');
24
26
  const { syncSandbox } = require('../../lib/sandboxSync');
25
27
  const { getValidEnv } = require('@hubspot/local-dev-lib/environment');