@hubspot/cli 4.2.1-beta.1 → 4.2.1-beta.3

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
@@ -115,7 +115,7 @@ const argv = yargs
115
115
  .option('debug', {
116
116
  alias: 'd',
117
117
  default: false,
118
- describe: 'set log level to debug',
118
+ describe: 'Set log level to debug',
119
119
  type: 'boolean',
120
120
  })
121
121
  .option('noHyperlinks', {
@@ -0,0 +1,139 @@
1
+ const { logger } = require('@hubspot/cli-lib/logger');
2
+ const {
3
+ accessTokenForPersonalAccessKey,
4
+ } = require('@hubspot/cli-lib/personalAccessKey');
5
+ const { getConfig } = require('@hubspot/cli-lib');
6
+
7
+ const { trackCommandUsage } = require('../../lib/usageTracking');
8
+ const { i18n } = require('../../lib/lang');
9
+ const { loadAndValidateOptions } = require('../../lib/validation');
10
+ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
11
+ const {
12
+ addConfigOptions,
13
+ addAccountOptions,
14
+ addUseEnvironmentOptions,
15
+ addTestingOptions,
16
+ } = require('../../lib/commonOpts');
17
+ const { getAccountName } = require('../../lib/sandboxes');
18
+ const { promptUser } = require('../../lib/prompts/promptUtils');
19
+ const { getTableContents } = require('@hubspot/cli-lib/lib/table');
20
+ const SpinniesManager = require('../../lib/SpinniesManager');
21
+ const { deleteAccount } = require('@hubspot/cli-lib/lib/config');
22
+ const {
23
+ isSpecifiedHubSpotAuthError,
24
+ } = require('@hubspot/cli-lib/errorHandlers/apiErrors');
25
+
26
+ const i18nKey = 'cli.commands.accounts.subcommands.clean';
27
+
28
+ exports.command = 'clean';
29
+ exports.describe = i18n(`${i18nKey}.describe`);
30
+
31
+ exports.handler = async options => {
32
+ const { qa } = options;
33
+ await loadAndValidateOptions(options, false);
34
+
35
+ const config = getConfig();
36
+
37
+ trackCommandUsage('accounts-clean', null);
38
+
39
+ const filteredTestAccounts = config.portals.filter(p =>
40
+ qa ? p.env === 'qa' : p.env !== 'qa'
41
+ );
42
+
43
+ if (filteredTestAccounts && filteredTestAccounts.length === 0) {
44
+ logger.log(i18n(`${i18nKey}.noResults`));
45
+ process.exit(EXIT_CODES.SUCCESS);
46
+ }
47
+
48
+ const accountsToRemove = [];
49
+ SpinniesManager.init({
50
+ succeedColor: 'white',
51
+ });
52
+ SpinniesManager.add('accountsClean', {
53
+ text: i18n(`${i18nKey}.loading.add`),
54
+ });
55
+
56
+ for (const account of filteredTestAccounts) {
57
+ try {
58
+ await accessTokenForPersonalAccessKey(account.portalId);
59
+ } catch (error) {
60
+ if (
61
+ isSpecifiedHubSpotAuthError(error, {
62
+ statusCode: 401,
63
+ category: 'INVALID_AUTHENTICATION',
64
+ subCategory: 'LocalDevAuthErrorType.PORTAL_NOT_ACTIVE',
65
+ }) ||
66
+ isSpecifiedHubSpotAuthError(error, {
67
+ statusCode: 404,
68
+ category: 'INVALID_AUTHENTICATION',
69
+ subCategory: 'LocalDevAuthErrorType.INVALID_PORTAL_ID',
70
+ })
71
+ ) {
72
+ accountsToRemove.push(account);
73
+ }
74
+ }
75
+ }
76
+
77
+ if (accountsToRemove.length > 0) {
78
+ const oneAccountFound = accountsToRemove.length === 1;
79
+ SpinniesManager.succeed('accountsClean', {
80
+ text: i18n(
81
+ oneAccountFound
82
+ ? `${i18nKey}.inactiveAccountsFound.one`
83
+ : `${i18nKey}.inactiveAccountsFound.other`,
84
+ {
85
+ count: accountsToRemove.length,
86
+ }
87
+ ),
88
+ });
89
+ logger.log(
90
+ getTableContents(
91
+ accountsToRemove.map(p => [getAccountName(p)]),
92
+ { border: { bodyLeft: ' ' } }
93
+ )
94
+ );
95
+ const { accountsCleanPrompt } = await promptUser([
96
+ {
97
+ name: 'accountsCleanPrompt',
98
+ type: 'confirm',
99
+ message: i18n(
100
+ oneAccountFound
101
+ ? `${i18nKey}.confirm.one`
102
+ : `${i18nKey}.confirm.other`,
103
+ {
104
+ count: accountsToRemove.length,
105
+ }
106
+ ),
107
+ },
108
+ ]);
109
+ if (accountsCleanPrompt) {
110
+ logger.log('');
111
+ for (const accountToRemove of accountsToRemove) {
112
+ await deleteAccount(accountToRemove.name);
113
+ logger.log(
114
+ i18n(`${i18nKey}.removeSuccess`, {
115
+ accountName: getAccountName(accountToRemove),
116
+ })
117
+ );
118
+ }
119
+ }
120
+ } else {
121
+ SpinniesManager.succeed('accountsClean', {
122
+ text: i18n(`${i18nKey}.noResults`),
123
+ });
124
+ }
125
+
126
+ logger.log('');
127
+ process.exit(EXIT_CODES.SUCCESS);
128
+ };
129
+
130
+ exports.builder = yargs => {
131
+ addConfigOptions(yargs, true);
132
+ addAccountOptions(yargs, true);
133
+ addUseEnvironmentOptions(yargs, true);
134
+ addTestingOptions(yargs, true);
135
+
136
+ yargs.example([['$0 accounts clean']]);
137
+
138
+ return yargs;
139
+ };
@@ -71,7 +71,7 @@ const getPortalData = mappedPortalData => {
71
71
  };
72
72
 
73
73
  exports.handler = async options => {
74
- await loadAndValidateOptions(options);
74
+ await loadAndValidateOptions(options, false);
75
75
 
76
76
  const accountId = getAccountId(options);
77
77
 
@@ -5,6 +5,7 @@ const rename = require('./accounts/rename');
5
5
  const use = require('./accounts/use');
6
6
  const info = require('./accounts/info');
7
7
  const remove = require('./accounts/remove');
8
+ const clean = require('./accounts/clean');
8
9
 
9
10
  const i18nKey = 'cli.commands.accounts';
10
11
 
@@ -24,6 +25,7 @@ exports.builder = yargs => {
24
25
  .command(use)
25
26
  .command(info)
26
27
  .command(remove)
28
+ .command(clean)
27
29
  .demandCommand(1, '');
28
30
 
29
31
  return yargs;
@@ -11,7 +11,7 @@ const { loadAndValidateOptions } = require('../../lib/validation');
11
11
  const i18nKey = 'cli.commands.project.subcommands.add';
12
12
 
13
13
  exports.command = 'add';
14
- exports.describe = null; //i18n(`${i18nKey}.describe`);
14
+ exports.describe = i18n(`${i18nKey}.describe`);
15
15
 
16
16
  exports.handler = async options => {
17
17
  await loadAndValidateOptions(options);
@@ -5,7 +5,10 @@ const {
5
5
  addUseEnvironmentOptions,
6
6
  addTestingOptions,
7
7
  } = require('../../lib/commonOpts');
8
- const { trackCommandUsage } = require('../../lib/usageTracking');
8
+ const {
9
+ trackCommandUsage,
10
+ trackCommandMetadataUsage,
11
+ } = require('../../lib/usageTracking');
9
12
  const { loadAndValidateOptions } = require('../../lib/validation');
10
13
  const { i18n } = require('../../lib/lang');
11
14
  const { logger } = require('@hubspot/cli-lib/logger');
@@ -17,19 +20,22 @@ const {
17
20
  ensureProjectExists,
18
21
  handleProjectUpload,
19
22
  pollProjectBuildAndDeploy,
23
+ showPlatformVersionWarning,
20
24
  } = require('../../lib/projects');
21
25
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
22
- const { uiAccountDescription, uiBetaMessage, uiLine } = require('../../lib/ui');
26
+ const {
27
+ uiAccountDescription,
28
+ uiBetaMessage,
29
+ uiCommandReference,
30
+ uiLine,
31
+ } = require('../../lib/ui');
23
32
  const { confirmPrompt } = require('../../lib/prompts/promptUtils');
24
33
  const {
25
34
  selectTargetAccountPrompt,
35
+ confirmDefaultSandboxAccountPrompt,
26
36
  } = require('../../lib/prompts/projectDevTargetAccountPrompt');
27
37
  const SpinniesManager = require('../../lib/SpinniesManager');
28
- const {
29
- LocalDevManager,
30
- UPLOAD_PERMISSIONS,
31
- } = require('../../lib/LocalDevManager');
32
- const LocalDevManagerV2 = require('../../lib/LocalDevManagerV2');
38
+ const LocalDevManager = require('../../lib/LocalDevManager');
33
39
  const { isSandbox } = require('../../lib/sandboxes');
34
40
  const { getAccountConfig, getEnv } = require('@hubspot/cli-lib');
35
41
  const { sandboxNamePrompt } = require('../../lib/prompts/sandboxesPrompt');
@@ -60,7 +66,7 @@ const {
60
66
  const i18nKey = 'cli.commands.project.subcommands.dev';
61
67
 
62
68
  exports.command = 'dev [--account]';
63
- exports.describe = null; //i18n(`${i18nKey}.describe`);
69
+ exports.describe = i18n(`${i18nKey}.describe`);
64
70
 
65
71
  exports.handler = async options => {
66
72
  await loadAndValidateOptions(options);
@@ -72,6 +78,9 @@ exports.handler = async options => {
72
78
 
73
79
  const { projectConfig, projectDir } = await getProjectConfig();
74
80
 
81
+ if (!options.debug) {
82
+ console.clear();
83
+ }
75
84
  uiBetaMessage(i18n(`${i18nKey}.logs.betaMessage`));
76
85
 
77
86
  if (!projectConfig) {
@@ -85,7 +94,23 @@ exports.handler = async options => {
85
94
  const defaultAccountIsSandbox = isSandbox(accountConfig);
86
95
 
87
96
  if (!targetAccountId && defaultAccountIsSandbox) {
88
- targetAccountId = accountId;
97
+ logger.log();
98
+ const useDefaultSandboxAccount = await confirmDefaultSandboxAccountPrompt(
99
+ accountConfig.name,
100
+ accountConfig.sandboxAccountType
101
+ );
102
+
103
+ if (useDefaultSandboxAccount) {
104
+ targetAccountId = accountId;
105
+ } else {
106
+ logger.log(
107
+ i18n(`${i18nKey}.logs.declineDefaultSandboxExplanation`, {
108
+ useCommand: uiCommandReference('hs accounts use'),
109
+ devCommand: uiCommandReference('hs project dev'),
110
+ })
111
+ );
112
+ process.exit(EXIT_CODES.SUCCESS);
113
+ }
89
114
  }
90
115
 
91
116
  if (!targetAccountId) {
@@ -129,6 +154,13 @@ exports.handler = async options => {
129
154
  }
130
155
  try {
131
156
  const { name } = await sandboxNamePrompt(DEVELOPER_SANDBOX);
157
+
158
+ trackCommandMetadataUsage(
159
+ 'sandbox-create',
160
+ { step: 'project-dev' },
161
+ accountId
162
+ );
163
+
132
164
  const { result } = await buildSandbox({
133
165
  name,
134
166
  type: DEVELOPER_SANDBOX,
@@ -159,40 +191,37 @@ exports.handler = async options => {
159
191
  }
160
192
  }
161
193
 
194
+ logger.log();
162
195
  const projectExists = await ensureProjectExists(
163
196
  targetAccountId,
164
197
  projectConfig.name,
165
198
  {
166
199
  allowCreate: false,
167
200
  noLogs: true,
168
- withPolling: true,
201
+ withPolling: createNewSandbox,
169
202
  }
170
203
  );
171
204
 
172
- const isNonSandboxAccount =
173
- !defaultAccountIsSandbox && targetAccountId === accountId;
174
-
175
- let uploadPermission = isNonSandboxAccount
176
- ? UPLOAD_PERMISSIONS.manual
177
- : UPLOAD_PERMISSIONS.always;
205
+ let deployedBuild;
178
206
 
179
207
  if (projectExists) {
180
- const { sourceIntegration } = await fetchProject(
181
- targetAccountId,
182
- projectConfig.name
183
- );
184
- if (options.extension || sourceIntegration) {
185
- uploadPermission = UPLOAD_PERMISSIONS.never;
186
- }
208
+ const project = await fetchProject(targetAccountId, projectConfig.name);
209
+ deployedBuild = project.deployedBuild;
187
210
  }
188
211
 
189
212
  SpinniesManager.init();
190
213
 
214
+ if (!options.debug) {
215
+ console.clear();
216
+ }
217
+ uiBetaMessage(i18n(`${i18nKey}.logs.betaMessage`));
218
+
191
219
  if (!projectExists) {
192
220
  // Create the project without prompting if this is a newly created sandbox
193
221
  let shouldCreateProject = createNewSandbox;
194
222
 
195
223
  if (!shouldCreateProject) {
224
+ logger.log();
196
225
  uiLine();
197
226
  logger.warn(
198
227
  i18n(`${i18nKey}.logs.projectMustExistExplanation`, {
@@ -211,6 +240,8 @@ exports.handler = async options => {
211
240
  }
212
241
 
213
242
  if (shouldCreateProject) {
243
+ await showPlatformVersionWarning(accountId, projectConfig);
244
+
214
245
  try {
215
246
  SpinniesManager.add('createProject', {
216
247
  text: i18n(`${i18nKey}.status.creatingProject`, {
@@ -232,23 +263,16 @@ exports.handler = async options => {
232
263
  }
233
264
  } else {
234
265
  // We cannot continue if the project does not exist in the target account
266
+ logger.log();
235
267
  logger.log(i18n(`${i18nKey}.logs.choseNotToCreateProject`));
236
268
  process.exit(EXIT_CODES.SUCCESS);
237
269
  }
238
270
  }
239
271
 
240
- SpinniesManager.add('devModeSetup', {
241
- text: i18n(`${i18nKey}.status.startupMessage`, {
242
- projectName: projectConfig.name,
243
- }),
244
- isParent: true,
245
- });
246
-
247
272
  let initialUploadResult;
248
273
 
249
- // Create an initial build if the project was newly created in the account or if
250
- // our upload permission is set to "always"
251
- if (!projectExists || uploadPermission === UPLOAD_PERMISSIONS.always) {
274
+ // Create an initial build if the project was newly created in the account
275
+ if (!projectExists) {
252
276
  initialUploadResult = await handleProjectUpload(
253
277
  targetAccountId,
254
278
  projectConfig,
@@ -258,8 +282,6 @@ exports.handler = async options => {
258
282
  );
259
283
 
260
284
  if (initialUploadResult.uploadError) {
261
- SpinniesManager.fail('devModeSetup');
262
-
263
285
  if (
264
286
  isSpecifiedError(initialUploadResult.uploadError, {
265
287
  subCategory: ERROR_TYPES.PROJECT_LOCKED,
@@ -279,72 +301,42 @@ exports.handler = async options => {
279
301
  }
280
302
  process.exit(EXIT_CODES.ERROR);
281
303
  }
282
- }
283
-
284
- // Let the user know when the initial build or deploy fails
285
- // Do this before starting the dev server for v2 behavior because we cannot
286
- // run a server on a broken project
287
- if (
288
- options.extension &&
289
- initialUploadResult &&
290
- !initialUploadResult.succeeded
291
- ) {
292
- SpinniesManager.fail('devModeSetup');
293
-
294
- let subTasks = [];
295
-
296
- if (initialUploadResult.buildResult.status === 'FAILURE') {
297
- subTasks =
298
- initialUploadResult.buildResult[PROJECT_BUILD_TEXT.SUBTASK_KEY];
299
- } else if (initialUploadResult.deployResult.status === 'FAILURE') {
300
- subTasks =
301
- initialUploadResult.deployResult[PROJECT_DEPLOY_TEXT.SUBTASK_KEY];
302
- }
303
304
 
304
- const failedSubTasks = subTasks.filter(task => task.status === 'FAILURE');
305
+ if (!initialUploadResult.succeeded) {
306
+ let subTasks = [];
305
307
 
306
- logger.log();
307
- failedSubTasks.forEach(failedSubTask => {
308
- console.log(failedSubTask.errorMessage);
309
- });
310
- logger.log();
308
+ if (initialUploadResult.buildResult.status === 'FAILURE') {
309
+ subTasks =
310
+ initialUploadResult.buildResult[PROJECT_BUILD_TEXT.SUBTASK_KEY];
311
+ } else if (initialUploadResult.deployResult.status === 'FAILURE') {
312
+ subTasks =
313
+ initialUploadResult.deployResult[PROJECT_DEPLOY_TEXT.SUBTASK_KEY];
314
+ }
311
315
 
312
- process.exit(EXIT_CODES.ERROR);
313
- }
316
+ const failedSubTasks = subTasks.filter(task => task.status === 'FAILURE');
314
317
 
315
- SpinniesManager.remove('devModeSetup');
316
-
317
- const LocalDev = options.extension
318
- ? new LocalDevManagerV2({
319
- debug: options.debug,
320
- extension: options.extension,
321
- projectConfig,
322
- projectDir,
323
- targetAccountId,
324
- })
325
- : new LocalDevManager({
326
- debug: options.debug,
327
- projectConfig,
328
- projectDir,
329
- targetAccountId,
330
- uploadPermission,
318
+ logger.log();
319
+ failedSubTasks.forEach(failedSubTask => {
320
+ console.log(failedSubTask.errorMessage);
331
321
  });
322
+ logger.log();
332
323
 
333
- await LocalDev.start();
334
-
335
- // Let the user know when the initial build or deploy fails
336
- if (
337
- !options.extension &&
338
- initialUploadResult &&
339
- !initialUploadResult.succeeded
340
- ) {
341
- if (initialUploadResult.buildResult.status === 'FAILURE') {
342
- LocalDev.logBuildError(initialUploadResult.buildResult);
343
- } else if (initialUploadResult.deployResult.status === 'FAILURE') {
344
- LocalDev.logDeployError(initialUploadResult.deployResult);
324
+ process.exit(EXIT_CODES.ERROR);
345
325
  }
326
+
327
+ deployedBuild = initialUploadResult.buildResult;
346
328
  }
347
329
 
330
+ const LocalDev = new LocalDevManager({
331
+ debug: options.debug,
332
+ deployedBuild,
333
+ projectConfig,
334
+ projectDir,
335
+ targetAccountId,
336
+ });
337
+
338
+ await LocalDev.start();
339
+
348
340
  handleExit(LocalDev.stop);
349
341
  };
350
342
 
@@ -354,12 +346,6 @@ exports.builder = yargs => {
354
346
  addUseEnvironmentOptions(yargs, true);
355
347
  addTestingOptions(yargs, true);
356
348
 
357
- yargs.option('extension', {
358
- describe: i18n(`${i18nKey}.options.extension.describe`),
359
- type: 'string',
360
- hidden: true,
361
- });
362
-
363
349
  yargs.example([['$0 project dev', i18n(`${i18nKey}.examples.default`)]]);
364
350
 
365
351
  return yargs;
@@ -17,10 +17,7 @@ const {
17
17
  downloadProject,
18
18
  fetchProjectBuilds,
19
19
  } = require('@hubspot/cli-lib/api/dfs');
20
- const {
21
- createProjectConfig,
22
- ensureProjectExists,
23
- } = require('../../lib/projects');
20
+ const { ensureProjectExists, getProjectConfig } = require('../../lib/projects');
24
21
  const { loadAndValidateOptions } = require('../../lib/validation');
25
22
  const {
26
23
  downloadProjectPrompt,
@@ -36,6 +33,13 @@ exports.describe = i18n(`${i18nKey}.describe`);
36
33
  exports.handler = async options => {
37
34
  await loadAndValidateOptions(options);
38
35
 
36
+ const { projectConfig } = await getProjectConfig();
37
+
38
+ if (projectConfig) {
39
+ logger.error(i18n(`${i18nKey}.warnings.cannotDownloadWithinProject`));
40
+ process.exit(EXIT_CODES.ERROR);
41
+ }
42
+
39
43
  const { project, dest, buildNumber } = options;
40
44
  let { project: promptedProjectName } = await downloadProjectPrompt(options);
41
45
  let projectName = promptedProjectName || project;
@@ -63,17 +67,6 @@ exports.handler = async options => {
63
67
 
64
68
  const absoluteDestPath = dest ? path.resolve(getCwd(), dest) : getCwd();
65
69
 
66
- const projectConfigCreated = await createProjectConfig(
67
- absoluteDestPath,
68
- projectName,
69
- { name: 'no-template' }
70
- );
71
-
72
- if (!projectConfigCreated) {
73
- logger.log(i18n(`${i18nKey}.logs.downloadCancelled`));
74
- process.exit(EXIT_CODES.SUCCESS);
75
- }
76
-
77
70
  let buildNumberToDownload = buildNumber;
78
71
 
79
72
  if (!buildNumberToDownload) {
@@ -98,10 +91,8 @@ exports.handler = async options => {
98
91
  await extractZipArchive(
99
92
  zippedProject,
100
93
  projectName,
101
- path.resolve(absoluteDestPath, 'src'),
102
- {
103
- includesRootDir: false,
104
- }
94
+ path.resolve(absoluteDestPath),
95
+ { includesRootDir: false }
105
96
  );
106
97
 
107
98
  logger.log(
@@ -7,6 +7,7 @@ const {
7
7
  addUseEnvironmentOptions,
8
8
  } = require('../../lib/commonOpts');
9
9
  const { trackCommandUsage } = require('../../lib/usageTracking');
10
+ const { i18n } = require('../../lib/lang');
10
11
  const {
11
12
  logApiErrorInstance,
12
13
  ApiErrorContext,
@@ -31,8 +32,10 @@ const {
31
32
  const moment = require('moment');
32
33
  const { promptUser } = require('../../lib/prompts/promptUtils');
33
34
 
35
+ const i18nKey = 'cli.commands.project.subcommands.listBuilds';
36
+
34
37
  exports.command = 'list-builds [path]';
35
- exports.describe = false;
38
+ exports.describe = i18n(`${i18nKey}.describe`);
36
39
 
37
40
  exports.handler = async options => {
38
41
  await loadAndValidateOptions(options);
@@ -16,6 +16,7 @@ const {
16
16
  logFeedbackMessage,
17
17
  validateProjectConfig,
18
18
  pollProjectBuildAndDeploy,
19
+ showPlatformVersionWarning,
19
20
  } = require('../../lib/projects');
20
21
  const { i18n } = require('../../lib/lang');
21
22
  const { getAccountConfig } = require('@hubspot/cli-lib');
@@ -48,6 +49,8 @@ exports.handler = async options => {
48
49
 
49
50
  validateProjectConfig(projectConfig, projectDir);
50
51
 
52
+ await showPlatformVersionWarning(accountId, projectConfig);
53
+
51
54
  await ensureProjectExists(accountId, projectConfig.name, { forceCreate });
52
55
 
53
56
  try {
@@ -21,6 +21,7 @@ const {
21
21
  pollDeployStatus,
22
22
  validateProjectConfig,
23
23
  logFeedbackMessage,
24
+ showPlatformVersionWarning,
24
25
  } = require('../../lib/projects');
25
26
  const {
26
27
  cancelStagedBuild,
@@ -97,6 +98,8 @@ exports.handler = async options => {
97
98
 
98
99
  validateProjectConfig(projectConfig, projectDir);
99
100
 
101
+ await showPlatformVersionWarning(accountId, projectConfig);
102
+
100
103
  await ensureProjectExists(accountId, projectConfig.name);
101
104
 
102
105
  try {
@@ -1,4 +1,5 @@
1
1
  const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts');
2
+ const { i18n } = require('../lib/lang');
2
3
  const deploy = require('./project/deploy');
3
4
  const create = require('./project/create');
4
5
  const upload = require('./project/upload');
@@ -10,24 +11,26 @@ const open = require('./project/open');
10
11
  const dev = require('./project/dev');
11
12
  const add = require('./project/add');
12
13
 
14
+ const i18nKey = 'cli.commands.project';
15
+
13
16
  exports.command = 'project';
14
- exports.describe = false; //'Commands for working with projects';
17
+ exports.describe = i18n(`${i18nKey}.describe`);
15
18
 
16
19
  exports.builder = yargs => {
17
20
  addConfigOptions(yargs, true);
18
21
  addAccountOptions(yargs, true);
19
22
 
20
23
  // TODO: deploy must be updated
21
- yargs.command(deploy).demandCommand(1, '');
22
24
  yargs.command(create).demandCommand(0, '');
23
- yargs.command(upload).demandCommand(0, '');
25
+ yargs.command(add).demandCommand(0, '');
24
26
  yargs.command(watch).demandCommand(0, '');
25
- yargs.command(listBuilds).demandCommand(0, '');
27
+ yargs.command(dev).demandCommand(0, '');
28
+ yargs.command(upload).demandCommand(0, '');
29
+ yargs.command(deploy).demandCommand(1, '');
26
30
  yargs.command(logs).demandCommand(1, '');
31
+ yargs.command(listBuilds).demandCommand(0, '');
27
32
  yargs.command(download).demandCommand(0, '');
28
33
  yargs.command(open).demandCommand(0, '');
29
- yargs.command(dev).demandCommand(0, '');
30
- yargs.command(add).demandCommand(0, '');
31
34
 
32
35
  return yargs;
33
36
  };
@@ -22,7 +22,10 @@ const {
22
22
  } = require('../../lib/sandboxes');
23
23
  const { getValidEnv } = require('@hubspot/cli-lib/lib/environment');
24
24
  const { logger } = require('@hubspot/cli-lib/logger');
25
- const { trackCommandUsage } = require('../../lib/usageTracking');
25
+ const {
26
+ trackCommandUsage,
27
+ trackCommandMetadataUsage,
28
+ } = require('../../lib/usageTracking');
26
29
  const {
27
30
  sandboxTypePrompt,
28
31
  sandboxNamePrompt,
@@ -153,6 +156,12 @@ exports.handler = async options => {
153
156
  // Prompt user to sync assets after sandbox creation
154
157
  const sandboxAccountConfig = getAccountConfig(result.sandbox.sandboxHubId);
155
158
  const handleSyncSandbox = async syncTasks => {
159
+ // Send tracking event for secondary action, in this case a sandbox sync within the sandbox create flow
160
+ trackCommandMetadataUsage(
161
+ 'sandbox-sync',
162
+ { step: 'sandbox-create' },
163
+ result.sandbox.sandboxHubId
164
+ );
156
165
  await syncSandbox({
157
166
  accountConfig: sandboxAccountConfig,
158
167
  parentAccountConfig: accountConfig,