@hubspot/cli 5.2.1-beta.9 → 5.3.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.
@@ -16,20 +16,55 @@ const {
16
16
  fetchProject,
17
17
  } = require('@hubspot/local-dev-lib/api/projects');
18
18
  const { loadAndValidateOptions } = require('../../lib/validation');
19
- const { getProjectConfig, pollDeployStatus } = require('../../lib/projects');
19
+ const {
20
+ getProjectConfig,
21
+ pollDeployStatus,
22
+ getProjectDetailUrl,
23
+ } = require('../../lib/projects');
20
24
  const { projectNamePrompt } = require('../../lib/prompts/projectNamePrompt');
21
- const { buildIdPrompt } = require('../../lib/prompts/buildIdPrompt');
25
+ const {
26
+ deployBuildIdPrompt,
27
+ } = require('../../lib/prompts/deployBuildIdPrompt');
22
28
  const { i18n } = require('../../lib/lang');
23
- const { uiBetaTag } = require('../../lib/ui');
29
+ const { uiBetaTag, uiLink } = require('../../lib/ui');
24
30
  const { getAccountConfig } = require('@hubspot/local-dev-lib/config');
25
31
 
26
32
  const i18nKey = 'commands.project.subcommands.deploy';
27
33
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
28
34
  const { uiCommandReference, uiAccountDescription } = require('../../lib/ui');
29
35
 
30
- exports.command = 'deploy [--project] [--buildId]';
36
+ exports.command = 'deploy';
31
37
  exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
32
38
 
39
+ const validateBuildId = (
40
+ buildId,
41
+ deployedBuildId,
42
+ latestBuildId,
43
+ projectName,
44
+ accountId
45
+ ) => {
46
+ if (Number(buildId) > latestBuildId) {
47
+ return i18n(`${i18nKey}.errors.buildIdDoesNotExist`, {
48
+ buildId: buildId,
49
+ projectName,
50
+ linkToProject: uiLink(
51
+ i18n(`${i18nKey}.errors.viewProjectsBuilds`),
52
+ getProjectDetailUrl(projectName, accountId)
53
+ ),
54
+ });
55
+ }
56
+ if (Number(buildId) === deployedBuildId) {
57
+ return i18n(`${i18nKey}.errors.buildAlreadyDeployed`, {
58
+ buildId: buildId,
59
+ linkToProject: uiLink(
60
+ i18n(`${i18nKey}.errors.viewProjectsBuilds`),
61
+ getProjectDetailUrl(projectName, accountId)
62
+ ),
63
+ });
64
+ }
65
+ return true;
66
+ };
67
+
33
68
  exports.handler = async options => {
34
69
  await loadAndValidateOptions(options);
35
70
 
@@ -59,27 +94,47 @@ exports.handler = async options => {
59
94
  let buildIdToDeploy = buildIdOption;
60
95
 
61
96
  try {
62
- if (!buildIdOption) {
63
- const { latestBuild, deployedBuildId } = await fetchProject(
64
- accountId,
65
- projectName
97
+ const { latestBuild, deployedBuildId } = await fetchProject(
98
+ accountId,
99
+ projectName
100
+ );
101
+
102
+ if (!latestBuild || !latestBuild.buildId) {
103
+ logger.error(i18n(`${i18nKey}.errors.noBuilds`));
104
+ return process.exit(EXIT_CODES.ERROR);
105
+ }
106
+
107
+ if (buildIdToDeploy) {
108
+ const validationResult = validateBuildId(
109
+ buildIdToDeploy,
110
+ deployedBuildId,
111
+ latestBuild.buildId,
112
+ projectName,
113
+ accountId
66
114
  );
67
- if (!latestBuild || !latestBuild.buildId) {
68
- logger.error(i18n(`${i18nKey}.errors.noBuilds`));
69
- process.exit(EXIT_CODES.ERROR);
115
+ if (validationResult !== true) {
116
+ logger.error(validationResult);
117
+ return process.exit(EXIT_CODES.ERROR);
70
118
  }
71
- const buildIdPromptResponse = await buildIdPrompt(
119
+ } else {
120
+ const deployBuildIdPromptResponse = await deployBuildIdPrompt(
72
121
  latestBuild.buildId,
73
122
  deployedBuildId,
74
- projectName
123
+ buildId =>
124
+ validateBuildId(
125
+ buildId,
126
+ deployedBuildId,
127
+ latestBuild.buildId,
128
+ projectName,
129
+ accountId
130
+ )
75
131
  );
76
-
77
- buildIdToDeploy = buildIdPromptResponse.buildId;
132
+ buildIdToDeploy = deployBuildIdPromptResponse.buildId;
78
133
  }
79
134
 
80
135
  if (!buildIdToDeploy) {
81
136
  logger.error(i18n(`${i18nKey}.errors.noBuildId`));
82
- process.exit(EXIT_CODES.ERROR);
137
+ return process.exit(EXIT_CODES.ERROR);
83
138
  }
84
139
 
85
140
  const deployResp = await deployProject(
@@ -88,13 +143,13 @@ exports.handler = async options => {
88
143
  buildIdToDeploy
89
144
  );
90
145
 
91
- if (deployResp.error) {
146
+ if (!deployResp || deployResp.error) {
92
147
  logger.error(
93
148
  i18n(`${i18nKey}.errors.deploy`, {
94
149
  details: deployResp.error.message,
95
150
  })
96
151
  );
97
- return;
152
+ return process.exit(EXIT_CODES.ERROR);
98
153
  }
99
154
 
100
155
  await pollDeployStatus(
@@ -104,7 +159,7 @@ exports.handler = async options => {
104
159
  buildIdToDeploy
105
160
  );
106
161
  } catch (e) {
107
- if (e.statusCode === 404) {
162
+ if (e.response && e.response.status === 404) {
108
163
  logger.error(
109
164
  i18n(`${i18nKey}.errors.projectNotFound`, {
110
165
  projectName: chalk.bold(projectName),
@@ -112,13 +167,12 @@ exports.handler = async options => {
112
167
  command: uiCommandReference('hs project upload'),
113
168
  })
114
169
  );
115
- }
116
- if (e.statusCode === 400) {
117
- logger.error(e.error.message);
170
+ } else if (e.response && e.response.status === 400) {
171
+ logger.error(e.message);
118
172
  } else {
119
173
  logApiErrorInstance(e, new ApiErrorContext({ accountId, projectName }));
120
174
  }
121
- process.exit(EXIT_CODES.ERROR);
175
+ return process.exit(EXIT_CODES.ERROR);
122
176
  }
123
177
  };
124
178
 
@@ -128,16 +182,17 @@ exports.builder = yargs => {
128
182
  describe: i18n(`${i18nKey}.options.project.describe`),
129
183
  type: 'string',
130
184
  },
131
- buildId: {
132
- describe: i18n(`${i18nKey}.options.buildId.describe`),
185
+ build: {
186
+ alias: ['buildId'],
187
+ describe: i18n(`${i18nKey}.options.build.describe`),
133
188
  type: 'number',
134
189
  },
135
190
  });
136
191
 
137
- yargs.example([['$0 project deploy', i18n(`${i18nKey}.examples.default`)]]);
138
192
  yargs.example([
193
+ ['$0 project deploy', i18n(`${i18nKey}.examples.default`)],
139
194
  [
140
- '$0 project deploy --project="my-project" --buildId=5',
195
+ '$0 project deploy --project="my-project" --build=5',
141
196
  i18n(`${i18nKey}.examples.withOptions`),
142
197
  ],
143
198
  ]);
@@ -124,7 +124,7 @@ exports.handler = async options => {
124
124
 
125
125
  await fetchAndDisplayBuilds(project, { limit });
126
126
  } catch (e) {
127
- if (e.statusCode === 404) {
127
+ if (e.response && e.response.status === 404) {
128
128
  logger.error(`Project ${projectConfig.name} not found. `);
129
129
  } else {
130
130
  logApiErrorInstance(
@@ -49,7 +49,7 @@ const getPrivateAppsUrl = accountId => {
49
49
  // We currently cannot fetch logs directly to the CLI. See internal CLI issue #413 for more information.
50
50
 
51
51
  // const handleLogsError = (e, name, projectName) => {
52
- // if (e.statusCode === 404) {
52
+ // if (e.response && e.response.status === 404) {
53
53
  // logger.debug(`Log fetch error: ${e.message}`);
54
54
  // logger.log(i18n(`${i18nKey}.logs.noLogsFound`, { name }));
55
55
  // } else {
@@ -5,14 +5,16 @@ const {
5
5
  getAccountId,
6
6
  addUseEnvironmentOptions,
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 {
11
14
  createProjectPrompt,
12
15
  } = require('../../lib/prompts/createProjectPrompt');
13
16
  const { i18n } = require('../../lib/lang');
14
17
  const {
15
- fetchPublicAppOptions,
16
18
  selectPublicAppPrompt,
17
19
  } = require('../../lib/prompts/selectPublicAppPrompt');
18
20
  const { poll } = require('../../lib/polling');
@@ -43,6 +45,9 @@ const { getAccountConfig } = require('@hubspot/local-dev-lib/config');
43
45
  const { downloadProject } = require('@hubspot/local-dev-lib/api/projects');
44
46
  const { extractZipArchive } = require('@hubspot/local-dev-lib/archive');
45
47
  const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls');
48
+ const {
49
+ fetchPublicAppMetadata,
50
+ } = require('@hubspot/local-dev-lib/api/appsDev');
46
51
 
47
52
  const i18nKey = 'commands.project.subcommands.migrateApp';
48
53
 
@@ -68,7 +73,7 @@ exports.handler = async options => {
68
73
  })
69
74
  );
70
75
  uiLine();
71
- process.exit(EXIT_CODES.ERROR);
76
+ process.exit(EXIT_CODES.SUCCESS);
72
77
  }
73
78
 
74
79
  const { appId } =
@@ -77,33 +82,55 @@ exports.handler = async options => {
77
82
  : await selectPublicAppPrompt({
78
83
  accountId,
79
84
  accountName,
80
- migrateApp: true,
85
+ isMigratingApp: true,
81
86
  });
82
87
 
83
- const publicApps = await fetchPublicAppOptions(accountId, accountName);
84
- const selectedApp = publicApps.find(a => a.id === appId);
85
- const appName = selectedApp ? selectedApp.name : 'app';
86
- if (!selectedApp) {
87
- logger.error(i18n(`${i18nKey}.errors.invalidAppId`, { appId }));
88
+ let appName;
89
+ let preventProjectMigrations;
90
+ let listingInfo;
91
+ try {
92
+ const selectedApp = await fetchPublicAppMetadata(appId, accountId);
93
+ // preventProjectMigrations returns true if we have not added app to allowlist config.
94
+ // listingInfo will only exist for marketplace apps
95
+ preventProjectMigrations = selectedApp.preventProjectMigrations;
96
+ listingInfo = selectedApp.listingInfo;
97
+ if (preventProjectMigrations && listingInfo) {
98
+ logger.error(i18n(`${i18nKey}.errors.invalidApp`, { appId }));
99
+ process.exit(EXIT_CODES.ERROR);
100
+ }
101
+ appName = selectedApp.name;
102
+ } catch (error) {
103
+ logApiErrorInstance(error, new ApiErrorContext({ accountId }));
88
104
  process.exit(EXIT_CODES.ERROR);
89
105
  }
90
106
 
91
- const { name, location } = await createProjectPrompt('', options, true);
107
+ let projectName;
108
+ let projectLocation;
109
+ try {
110
+ const { name, location } = await createProjectPrompt('', options, true);
92
111
 
93
- const projectName = options.name || name;
94
- const projectLocation = options.location || location;
112
+ projectName = options.name || name;
113
+ projectLocation = options.location || location;
95
114
 
96
- const { projectExists } = await ensureProjectExists(accountId, projectName, {
97
- allowCreate: false,
98
- noLogs: true,
99
- });
100
-
101
- if (projectExists) {
102
- logger.error(
103
- i18n(`${i18nKey}.errors.projectAlreadyExists`, {
104
- projectName,
105
- })
115
+ const { projectExists } = await ensureProjectExists(
116
+ accountId,
117
+ projectName,
118
+ {
119
+ allowCreate: false,
120
+ noLogs: true,
121
+ }
106
122
  );
123
+
124
+ if (projectExists) {
125
+ logger.error(
126
+ i18n(`${i18nKey}.errors.projectAlreadyExists`, {
127
+ projectName,
128
+ })
129
+ );
130
+ process.exit(EXIT_CODES.ERROR);
131
+ }
132
+ } catch (error) {
133
+ logApiErrorInstance(error, new ApiErrorContext({ accountId }));
107
134
  process.exit(EXIT_CODES.ERROR);
108
135
  }
109
136
 
@@ -164,6 +191,13 @@ exports.handler = async options => {
164
191
  { includesRootDir: true, hideLogs: true }
165
192
  );
166
193
 
194
+ const isListed = Boolean(listingInfo);
195
+ trackCommandMetadataUsage(
196
+ 'migrate-app',
197
+ { projectName, appId, status, preventProjectMigrations, isListed },
198
+ accountId
199
+ );
200
+
167
201
  SpinniesManager.succeed('migrateApp', {
168
202
  text: i18n(`${i18nKey}.migrationStatus.done`),
169
203
  succeedColor: 'white',
@@ -181,12 +215,20 @@ exports.handler = async options => {
181
215
  process.exit(EXIT_CODES.SUCCESS);
182
216
  }
183
217
  } catch (error) {
218
+ trackCommandMetadataUsage(
219
+ 'migrate-app',
220
+ { projectName, appId, status: 'FAILURE', error },
221
+ accountId
222
+ );
184
223
  SpinniesManager.fail('migrateApp', {
185
224
  text: i18n(`${i18nKey}.migrationStatus.failure`),
186
225
  failColor: 'white',
187
226
  });
188
- if (error.errors) {
189
- error.errors.forEach(logApiErrorInstance);
227
+ // Migrations endpoints return a response object with an errors property. The errors property contains an array of errors.
228
+ if (error.errors && Array.isArray(error.errors)) {
229
+ error.errors.forEach(e =>
230
+ logApiErrorInstance(e, new ApiErrorContext({ accountId }))
231
+ );
190
232
  } else {
191
233
  logApiErrorInstance(error, new ApiErrorContext({ accountId }));
192
234
  }
@@ -92,7 +92,7 @@ exports.handler = async options => {
92
92
  logger.log(
93
93
  i18n(`${i18nKey}.logs.autoDeployDisabled`, {
94
94
  deployCommand: uiCommandReference(
95
- `hs project deploy --buildId=${result.buildId}`
95
+ `hs project deploy --build=${result.buildId}`
96
96
  ),
97
97
  })
98
98
  );
@@ -12,6 +12,7 @@ const open = require('./project/open');
12
12
  const dev = require('./project/dev');
13
13
  const add = require('./project/add');
14
14
  const migrateApp = require('./project/migrateApp');
15
+ const cloneApp = require('./project/cloneApp');
15
16
 
16
17
  const i18nKey = 'commands.project';
17
18
 
@@ -22,18 +23,20 @@ exports.builder = yargs => {
22
23
  addConfigOptions(yargs);
23
24
  addAccountOptions(yargs);
24
25
 
25
- // TODO: deploy must be updated
26
- yargs.command(create).demandCommand(0, '');
27
- yargs.command(add).demandCommand(0, '');
28
- yargs.command(watch).demandCommand(0, '');
29
- yargs.command(dev).demandCommand(0, '');
30
- yargs.command(upload).demandCommand(0, '');
31
- yargs.command(deploy).demandCommand(1, '');
32
- yargs.command(logs).demandCommand(1, '');
33
- yargs.command(listBuilds).demandCommand(0, '');
34
- yargs.command(download).demandCommand(0, '');
35
- yargs.command(open).demandCommand(0, '');
36
- yargs.command(migrateApp).demandCommand(0, '');
26
+ yargs
27
+ .command(create)
28
+ .command(add)
29
+ .command(watch)
30
+ .command(dev)
31
+ .command(upload)
32
+ .command(deploy)
33
+ .command(logs)
34
+ .command(listBuilds)
35
+ .command(download)
36
+ .command(open)
37
+ .command(migrateApp)
38
+ .command(cloneApp)
39
+ .demandCommand(1, '');
37
40
 
38
41
  return yargs;
39
42
  };
@@ -9,7 +9,7 @@ const { loadAndValidateOptions } = require('../../lib/validation');
9
9
  const { i18n } = require('../../lib/lang');
10
10
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
11
11
  const { getAccountConfig, getEnv } = require('@hubspot/local-dev-lib/config');
12
- const { uiFeatureHighlight, uiAccountDescription } = require('../../lib/ui');
12
+ const { uiFeatureHighlight, uiBetaTag } = require('../../lib/ui');
13
13
  const {
14
14
  sandboxTypeMap,
15
15
  getAvailableSyncTypes,
@@ -18,10 +18,7 @@ const {
18
18
  } = require('../../lib/sandboxes');
19
19
  const { getValidEnv } = require('@hubspot/local-dev-lib/environment');
20
20
  const { logger } = require('@hubspot/local-dev-lib/logger');
21
- const {
22
- trackCommandUsage,
23
- trackCommandMetadataUsage,
24
- } = require('../../lib/usageTracking');
21
+ const { trackCommandUsage } = require('../../lib/usageTracking');
25
22
  const { sandboxTypePrompt } = require('../../lib/prompts/sandboxesPrompt');
26
23
  const { promptUser } = require('../../lib/prompts/promptUtils');
27
24
  const { syncSandbox } = require('../../lib/sandboxSync');
@@ -42,7 +39,7 @@ const {
42
39
  const i18nKey = 'commands.sandbox.subcommands.create';
43
40
 
44
41
  exports.command = 'create [--name] [--type]';
45
- exports.describe = i18n(`${i18nKey}.describe`);
42
+ exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
46
43
 
47
44
  exports.handler = async options => {
48
45
  await loadAndValidateOptions(options);
@@ -120,34 +117,18 @@ exports.handler = async options => {
120
117
  }
121
118
  const sandboxName = name || namePrompt.name;
122
119
 
123
- let sandboxSyncPromptResult = true;
124
- let contactRecordsSyncPromptResult = true;
120
+ let contactRecordsSyncPromptResult = false;
125
121
  if (!force) {
126
- const syncI18nKey = 'lib.sandbox.sync';
127
- const sandboxLangKey =
128
- sandboxType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX
129
- ? 'developer'
130
- : 'standard';
131
- const { sandboxSyncPrompt } = await promptUser([
132
- {
133
- name: 'sandboxSyncPrompt',
134
- type: 'confirm',
135
- message: i18n(`${syncI18nKey}.confirm.createFlow.${sandboxLangKey}`, {
136
- parentAccountName: uiAccountDescription(accountId),
137
- sandboxName,
138
- }),
139
- },
140
- ]);
141
- sandboxSyncPromptResult = sandboxSyncPrompt;
142
- // We can prompt for contact records before fetching types since we're starting with a fresh sandbox in create
143
- if (sandboxSyncPrompt) {
122
+ const isStandardSandbox =
123
+ sandboxType === HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX;
124
+
125
+ // Prompt to sync contact records for standard sandboxes only
126
+ if (isStandardSandbox) {
144
127
  const { contactRecordsSyncPrompt } = await promptUser([
145
128
  {
146
129
  name: 'contactRecordsSyncPrompt',
147
130
  type: 'confirm',
148
- message: i18n(
149
- `${syncI18nKey}.confirm.syncContactRecords.${sandboxLangKey}`
150
- ),
131
+ message: i18n('lib.sandbox.sync.confirm.syncContactRecords.standard'),
151
132
  },
152
133
  ]);
153
134
  contactRecordsSyncPromptResult = contactRecordsSyncPrompt;
@@ -163,15 +144,8 @@ exports.handler = async options => {
163
144
  force,
164
145
  });
165
146
 
166
- // Prompt user to sync assets after sandbox creation
167
147
  const sandboxAccountConfig = getAccountConfig(result.sandbox.sandboxHubId);
168
148
  const handleSyncSandbox = async syncTasks => {
169
- // Send tracking event for secondary action, in this case a sandbox sync within the sandbox create flow
170
- trackCommandMetadataUsage(
171
- 'sandbox-sync',
172
- { step: 'sandbox-create' },
173
- result.sandbox.sandboxHubId
174
- );
175
149
  await syncSandbox({
176
150
  accountConfig: sandboxAccountConfig,
177
151
  parentAccountConfig: accountConfig,
@@ -184,18 +158,13 @@ exports.handler = async options => {
184
158
  accountConfig,
185
159
  sandboxAccountConfig
186
160
  );
161
+
187
162
  if (!contactRecordsSyncPromptResult) {
188
163
  availableSyncTasks = availableSyncTasks.filter(
189
164
  t => t.type !== syncTypes.OBJECT_RECORDS
190
165
  );
191
166
  }
192
- if (!force) {
193
- if (sandboxSyncPromptResult) {
194
- await handleSyncSandbox(availableSyncTasks);
195
- }
196
- } else {
197
- await handleSyncSandbox(availableSyncTasks);
198
- }
167
+ await handleSyncSandbox(availableSyncTasks);
199
168
  } catch (err) {
200
169
  logErrorInstance(err);
201
170
  throw err;
@@ -33,12 +33,15 @@ const { promptUser } = require('../../lib/prompts/promptUtils');
33
33
  const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls');
34
34
 
35
35
  const { getValidEnv } = require('@hubspot/local-dev-lib/environment');
36
- const { uiAccountDescription } = require('../../lib/ui');
36
+ const { uiAccountDescription, uiBetaTag } = require('../../lib/ui');
37
37
 
38
38
  const i18nKey = 'commands.sandbox.subcommands.delete';
39
39
 
40
40
  exports.command = 'delete [--account]';
41
- exports.describe = i18n(`${i18nKey}.describe`);
41
+ exports.describe = exports.describe = uiBetaTag(
42
+ i18n(`${i18nKey}.describe`),
43
+ false
44
+ );
42
45
 
43
46
  exports.handler = async options => {
44
47
  await loadAndValidateOptions(options, false);
@@ -13,7 +13,12 @@ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
13
13
  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
- const { uiLine, uiAccountDescription } = require('../../lib/ui');
16
+ const {
17
+ uiLine,
18
+ uiAccountDescription,
19
+ uiDeprecatedMessage,
20
+ uiDeprecatedDescription,
21
+ } = require('../../lib/ui');
17
22
  const {
18
23
  isSandbox,
19
24
  isStandardSandbox,
@@ -35,11 +40,16 @@ const {
35
40
  const i18nKey = 'commands.sandbox.subcommands.sync';
36
41
 
37
42
  exports.command = 'sync';
38
- exports.describe = i18n(`${i18nKey}.describe`);
43
+ exports.describe = uiDeprecatedDescription(
44
+ i18n(`${i18nKey}.describe`),
45
+ 'hs sandbox sync'
46
+ );
39
47
 
40
48
  exports.handler = async options => {
41
49
  await loadAndValidateOptions(options);
42
50
 
51
+ uiDeprecatedMessage('hs sandbox sync');
52
+
43
53
  const { force } = options; // For scripting purposes
44
54
  const accountId = getAccountId(options);
45
55
  const accountConfig = getAccountConfig(accountId);
@@ -1,5 +1,6 @@
1
1
  const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts');
2
2
  const { i18n } = require('../lib/lang');
3
+ const { uiBetaTag } = require('../lib/ui');
3
4
  const create = require('./sandbox/create');
4
5
  const del = require('./sandbox/delete');
5
6
  const sync = require('./sandbox/sync');
@@ -7,7 +8,7 @@ const sync = require('./sandbox/sync');
7
8
  const i18nKey = 'commands.sandbox';
8
9
 
9
10
  exports.command = 'sandbox';
10
- exports.describe = i18n(`${i18nKey}.describe`);
11
+ exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
11
12
 
12
13
  exports.builder = yargs => {
13
14
  addConfigOptions(yargs);