@hubspot/cli 4.1.8-beta.0 → 4.1.8-beta.2

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.
@@ -1,22 +1,21 @@
1
- const chalk = require('chalk');
2
1
  const {
3
2
  addAccountOptions,
4
3
  addConfigOptions,
5
4
  getAccountId,
6
5
  addUseEnvironmentOptions,
7
6
  } = require('../../lib/commonOpts');
8
- const { trackCommandUsage } = require('../../lib/usageTracking');
9
- const { uiLine, uiAccountDescription } = require('../../lib/ui');
7
+ const chalk = require('chalk');
10
8
  const { logger } = require('@hubspot/cli-lib/logger');
9
+ const { uiLine } = require('../../lib/ui');
10
+ const { trackCommandUsage } = require('../../lib/usageTracking');
11
11
  const { loadAndValidateOptions } = require('../../lib/validation');
12
12
  const {
13
13
  ensureProjectExists,
14
14
  getProjectConfig,
15
15
  handleProjectUpload,
16
16
  logFeedbackMessage,
17
- pollBuildStatus,
18
- pollDeployStatus,
19
17
  validateProjectConfig,
18
+ pollProjectBuildAndDeploy,
20
19
  } = require('../../lib/projects');
21
20
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
22
21
  const { getAccountConfig } = require('@hubspot/cli-lib');
@@ -43,81 +42,34 @@ exports.handler = async options => {
43
42
 
44
43
  await ensureProjectExists(accountId, projectConfig.name, { forceCreate });
45
44
 
46
- const startPolling = async (tempFile, buildId) => {
47
- let exitCode = EXIT_CODES.SUCCESS;
48
-
49
- const {
50
- autoDeployId,
51
- isAutoDeployEnabled,
52
- deployStatusTaskLocator,
53
- status,
54
- } = await pollBuildStatus(accountId, projectConfig.name, buildId);
55
- // autoDeployId of 0 indicates a skipped deploy
56
- const isDeploying =
57
- isAutoDeployEnabled && autoDeployId > 0 && deployStatusTaskLocator;
58
-
59
- uiLine();
60
-
61
- if (status === 'FAILURE') {
62
- exitCode = EXIT_CODES.ERROR;
63
- return;
64
- } else if (isDeploying) {
65
- logger.log(
66
- i18n(`${i18nKey}.logs.buildSucceededAutomaticallyDeploying`, {
67
- accountIdentifier: uiAccountDescription(accountId),
68
- buildId,
69
- })
70
- );
71
- const { status } = await pollDeployStatus(
72
- accountId,
73
- projectConfig.name,
74
- deployStatusTaskLocator.id,
75
- buildId
76
- );
77
- if (status === 'FAILURE') {
78
- exitCode = EXIT_CODES.ERROR;
79
- }
80
- } else {
81
- uiLine();
82
- logger.log(
83
- chalk.bold(
84
- i18n(`${i18nKey}.logs.buildSucceeded`, {
85
- buildId,
86
- })
87
- )
88
- );
89
- logger.log(i18n(`${i18nKey}.logs.readyToGoLive`));
90
- logger.log(
91
- i18n(`${i18nKey}.logs.runCommand`, {
92
- command: chalk.hex('f5c26b')('hs project deploy'),
93
- })
94
- );
95
- uiLine();
96
- }
97
-
98
- try {
99
- tempFile.removeCallback();
100
- logger.debug(
101
- i18n(`${i18nKey}.debug.cleanedUpTempFile`, {
102
- path: tempFile.name,
103
- })
104
- );
105
- } catch (e) {
106
- logger.error(e);
107
- }
108
-
109
- logFeedbackMessage(buildId);
110
-
111
- process.exit(exitCode);
112
- };
113
-
114
- await handleProjectUpload(
45
+ const result = await handleProjectUpload(
115
46
  accountId,
116
47
  projectConfig,
117
48
  projectDir,
118
- startPolling,
49
+ pollProjectBuildAndDeploy,
119
50
  message
120
51
  );
52
+
53
+ if (result.buildSucceeded && !result.autodeployEnabled) {
54
+ uiLine();
55
+ logger.log(
56
+ chalk.bold(
57
+ i18n(`${i18nKey}.logs.buildSucceeded`, {
58
+ buildId: result.buildId,
59
+ })
60
+ )
61
+ );
62
+ logger.log(i18n(`${i18nKey}.logs.readyToGoLive`));
63
+ logger.log(
64
+ i18n(`${i18nKey}.logs.runCommand`, {
65
+ command: chalk.hex('f5c26b')('hs project deploy'),
66
+ })
67
+ );
68
+ uiLine();
69
+ }
70
+
71
+ logFeedbackMessage(result.buildId);
72
+ process.exit(result.succeeded ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
121
73
  };
122
74
 
123
75
  exports.builder = yargs => {
@@ -7,6 +7,8 @@ const logs = require('./project/logs');
7
7
  const watch = require('./project/watch');
8
8
  const download = require('./project/download');
9
9
  const open = require('./project/open');
10
+ const dev = require('./project/dev');
11
+ const add = require('./project/add');
10
12
 
11
13
  exports.command = 'project';
12
14
  exports.describe = false; //'Commands for working with projects';
@@ -24,6 +26,8 @@ exports.builder = yargs => {
24
26
  yargs.command(logs).demandCommand(1, '');
25
27
  yargs.command(download).demandCommand(0, '');
26
28
  yargs.command(open).demandCommand(0, '');
29
+ yargs.command(dev).demandCommand(0, '');
30
+ yargs.command(add).demandCommand(0, '');
27
31
 
28
32
  return yargs;
29
33
  };
@@ -5,183 +5,199 @@ const {
5
5
  addUseEnvironmentOptions,
6
6
  addTestingOptions,
7
7
  } = require('../../lib/commonOpts');
8
- const { trackCommandUsage } = require('../../lib/usageTracking');
9
- const { logger } = require('@hubspot/cli-lib/logger');
10
- const Spinnies = require('spinnies');
11
- const { createSandbox } = require('@hubspot/cli-lib/sandboxes');
12
8
  const { loadAndValidateOptions } = require('../../lib/validation');
13
- const { createSandboxPrompt } = require('../../lib/prompts/sandboxesPrompt');
14
- const {
15
- getSandboxType,
16
- sandboxCreatePersonalAccessKeyFlow,
17
- getHasDevelopmentSandboxes,
18
- getDevSandboxLimit,
19
- } = require('../../lib/sandboxes');
20
9
  const { i18n } = require('@hubspot/cli-lib/lib/lang');
21
- const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
22
- const {
23
- debugErrorAndContext,
24
- } = require('@hubspot/cli-lib/errorHandlers/standardErrors');
25
- const { ENVIRONMENTS } = require('@hubspot/cli-lib/lib/constants');
26
10
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
27
11
  const { getAccountConfig, getEnv } = require('@hubspot/cli-lib');
28
- const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
12
+ const { buildSandbox } = require('../../lib/sandbox-create');
13
+ const { uiFeatureHighlight } = require('../../lib/ui');
14
+ const {
15
+ sandboxTypeMap,
16
+ DEVELOPER_SANDBOX,
17
+ getSandboxTypeAsString,
18
+ getAccountName,
19
+ getAvailableSyncTypes,
20
+ syncTypes,
21
+ validateSandboxUsageLimits,
22
+ } = require('../../lib/sandboxes');
23
+ const { getValidEnv } = require('@hubspot/cli-lib/lib/environment');
24
+ const { logger } = require('@hubspot/cli-lib/logger');
25
+ const { trackCommandUsage } = require('../../lib/usageTracking');
29
26
  const {
30
- isMissingScopeError,
31
- isSpecifiedError,
32
- } = require('@hubspot/cli-lib/errorHandlers/apiErrors');
27
+ sandboxTypePrompt,
28
+ sandboxNamePrompt,
29
+ } = require('../../lib/prompts/sandboxesPrompt');
30
+ const { promptUser } = require('../../lib/prompts/promptUtils');
31
+ const { syncSandbox } = require('../../lib/sandbox-sync');
32
+ const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
33
33
 
34
34
  const i18nKey = 'cli.commands.sandbox.subcommands.create';
35
35
 
36
- exports.command = 'create [--name]';
36
+ exports.command = 'create [--name] [--type]';
37
37
  exports.describe = i18n(`${i18nKey}.describe`);
38
38
 
39
39
  exports.handler = async options => {
40
40
  await loadAndValidateOptions(options);
41
41
 
42
- const { name } = options;
42
+ const { name, type, force } = options;
43
43
  const accountId = getAccountId(options);
44
44
  const accountConfig = getAccountConfig(accountId);
45
- const env = options.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD;
46
- const spinnies = new Spinnies({
47
- succeedColor: 'white',
48
- });
45
+ const env = getValidEnv(getEnv(accountId));
49
46
 
50
47
  trackCommandUsage('sandbox-create', null, accountId);
51
48
 
49
+ // Default account is not a production portal
52
50
  if (
53
51
  accountConfig.sandboxAccountType &&
54
52
  accountConfig.sandboxAccountType !== null
55
53
  ) {
56
54
  trackCommandUsage('sandbox-create', { successful: false }, accountId);
57
-
58
55
  logger.error(
59
56
  i18n(`${i18nKey}.failure.creatingWithinSandbox`, {
60
- sandboxType: getSandboxType(accountConfig.sandboxAccountType),
57
+ sandboxType: getSandboxTypeAsString(accountConfig.sandboxAccountType),
58
+ sandboxName: accountConfig.name,
61
59
  })
62
60
  );
63
-
64
61
  process.exit(EXIT_CODES.ERROR);
65
62
  }
66
63
 
64
+ let typePrompt;
67
65
  let namePrompt;
68
66
 
69
- logger.log(i18n(`${i18nKey}.sandboxLimitation`));
70
- logger.log('');
67
+ if ((type && !sandboxTypeMap[type]) || !type) {
68
+ if (!force) {
69
+ typePrompt = await sandboxTypePrompt();
70
+ } else {
71
+ logger.error(i18n(`${i18nKey}.failure.optionMissing.type`));
72
+ trackCommandUsage('sandbox-create', { successful: false }, accountId);
73
+ process.exit(EXIT_CODES.ERROR);
74
+ }
75
+ }
76
+ const sandboxType = sandboxTypeMap[type] || sandboxTypeMap[typePrompt.type];
71
77
 
72
- if (!name) {
73
- namePrompt = await createSandboxPrompt();
78
+ // Check usage limits and exit if parent portal has no available sandboxes for the selected type
79
+ try {
80
+ await validateSandboxUsageLimits(accountConfig, sandboxType, env);
81
+ } catch (err) {
82
+ logErrorInstance(err);
83
+ trackCommandUsage('sandbox-create', { successful: false }, accountId);
84
+ process.exit(EXIT_CODES.ERROR);
74
85
  }
75
86
 
87
+ if (!name) {
88
+ if (!force) {
89
+ namePrompt = await sandboxNamePrompt(sandboxType);
90
+ } else {
91
+ logger.error(i18n(`${i18nKey}.failure.optionMissing.name`));
92
+ trackCommandUsage('sandbox-create', { successful: false }, accountId);
93
+ process.exit(EXIT_CODES.ERROR);
94
+ }
95
+ }
76
96
  const sandboxName = name || namePrompt.name;
77
97
 
78
- let result;
98
+ let sandboxSyncPromptResult = true;
99
+ let contactRecordsSyncPromptResult = true;
100
+ if (!force) {
101
+ const syncI18nKey = 'cli.lib.sandbox.sync';
102
+ const { sandboxSyncPrompt } = await promptUser([
103
+ {
104
+ name: 'sandboxSyncPrompt',
105
+ type: 'confirm',
106
+ message: i18n(`${syncI18nKey}.confirm.createFlow.${sandboxType}`, {
107
+ parentAccountName: getAccountName(accountConfig),
108
+ sandboxName,
109
+ }),
110
+ },
111
+ ]);
112
+ sandboxSyncPromptResult = sandboxSyncPrompt;
113
+ // We can prompt for contact records before fetching types since we're starting with a fresh sandbox in create
114
+ if (sandboxSyncPrompt) {
115
+ const { contactRecordsSyncPrompt } = await promptUser([
116
+ {
117
+ name: 'contactRecordsSyncPrompt',
118
+ type: 'confirm',
119
+ message: i18n(
120
+ `${syncI18nKey}.confirm.syncContactRecords.${sandboxType}`
121
+ ),
122
+ },
123
+ ]);
124
+ contactRecordsSyncPromptResult = contactRecordsSyncPrompt;
125
+ }
126
+ }
79
127
 
80
128
  try {
81
- spinnies.add('sandboxCreate', {
82
- text: i18n(`${i18nKey}.loading.add`, {
83
- sandboxName,
84
- }),
85
- });
86
-
87
- result = await createSandbox(accountId, sandboxName);
88
-
89
- logger.log('');
90
- spinnies.succeed('sandboxCreate', {
91
- text: i18n(`${i18nKey}.loading.succeed`, {
92
- name: result.name,
93
- sandboxHubId: result.sandboxHubId,
94
- }),
95
- });
96
- } catch (err) {
97
- debugErrorAndContext(err);
98
-
99
- trackCommandUsage('sandbox-create', { successful: false }, accountId);
100
-
101
- spinnies.fail('sandboxCreate', {
102
- text: i18n(`${i18nKey}.loading.fail`, {
103
- sandboxName,
104
- }),
129
+ const { result } = await buildSandbox({
130
+ name: sandboxName,
131
+ type: sandboxType,
132
+ accountConfig,
133
+ env,
134
+ force,
105
135
  });
106
136
 
107
- if (isMissingScopeError(err)) {
108
- logger.error(
109
- i18n(`${i18nKey}.failure.scopes.message`, {
110
- accountName: accountConfig.name || accountId,
111
- })
112
- );
113
- const websiteOrigin = getHubSpotWebsiteOrigin(env);
114
- const url = `${websiteOrigin}/personal-access-key/${accountId}`;
115
- logger.info(
116
- i18n(`${i18nKey}.failure.scopes.instructions`, {
117
- accountName: accountConfig.name || accountId,
118
- url,
119
- })
137
+ // Prompt user to sync assets after sandbox creation
138
+ const sandboxAccountConfig = getAccountConfig(result.sandbox.sandboxHubId);
139
+ const handleSyncSandbox = async syncTasks => {
140
+ await syncSandbox({
141
+ accountConfig: sandboxAccountConfig,
142
+ parentAccountConfig: accountConfig,
143
+ env,
144
+ syncTasks,
145
+ });
146
+ };
147
+ try {
148
+ let availableSyncTasks = await getAvailableSyncTypes(
149
+ accountConfig,
150
+ sandboxAccountConfig
120
151
  );
121
- } else if (
122
- isSpecifiedError(
123
- err,
124
- 400,
125
- 'VALIDATION_ERROR',
126
- 'SandboxErrors.NUM_DEVELOPMENT_SANDBOXES_LIMIT_EXCEEDED_ERROR'
127
- ) &&
128
- err.error &&
129
- err.error.message
130
- ) {
131
- logger.log('');
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
- )
152
+ if (!contactRecordsSyncPromptResult) {
153
+ availableSyncTasks = availableSyncTasks.filter(
154
+ t => t.type !== syncTypes.OBJECT_RECORDS
144
155
  );
156
+ }
157
+ if (!force) {
158
+ if (sandboxSyncPromptResult) {
159
+ await handleSyncSandbox(availableSyncTasks);
160
+ }
145
161
  } 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
- );
162
+ await handleSyncSandbox(availableSyncTasks);
156
163
  }
157
- logger.log('');
158
- } else {
164
+ } catch (err) {
159
165
  logErrorInstance(err);
166
+ throw err;
160
167
  }
161
- process.exit(EXIT_CODES.ERROR);
162
- }
163
- try {
164
- await sandboxCreatePersonalAccessKeyFlow(
165
- env,
166
- result.sandboxHubId,
167
- result.name
168
- );
168
+
169
+ uiFeatureHighlight([
170
+ 'accountsUseCommand',
171
+ sandboxType === DEVELOPER_SANDBOX
172
+ ? 'projectDevCommand'
173
+ : 'projectUploadCommand',
174
+ ]);
169
175
  process.exit(EXIT_CODES.SUCCESS);
170
- } catch (err) {
171
- logErrorInstance(err);
176
+ } catch (error) {
177
+ trackCommandUsage('sandbox-create', { successful: false }, accountId);
178
+ // Errors are logged in util functions
172
179
  process.exit(EXIT_CODES.ERROR);
173
180
  }
174
181
  };
175
182
 
176
183
  exports.builder = yargs => {
184
+ yargs.option('f', {
185
+ type: 'boolean',
186
+ alias: 'force',
187
+ describe: i18n(`${i18nKey}.examples.force`),
188
+ });
177
189
  yargs.option('name', {
178
190
  describe: i18n(`${i18nKey}.options.name.describe`),
179
191
  type: 'string',
180
192
  });
193
+ yargs.option('type', {
194
+ describe: i18n(`${i18nKey}.options.type.describe`),
195
+ type: 'string',
196
+ });
181
197
 
182
198
  yargs.example([
183
199
  [
184
- '$0 sandbox create --name=MySandboxAccount',
200
+ '$0 sandbox create --name=MySandboxAccount --type=STANDARD',
185
201
  i18n(`${i18nKey}.examples.default`),
186
202
  ],
187
203
  ]);
@@ -26,12 +26,12 @@ const {
26
26
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
27
27
  const { promptUser } = require('../../lib/prompts/promptUtils');
28
28
  const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
29
- const { ENVIRONMENTS } = require('@hubspot/cli-lib/lib/constants');
30
29
  const {
31
30
  isSpecifiedError,
32
31
  } = require('@hubspot/cli-lib/errorHandlers/apiErrors');
33
32
  const { HubSpotAuthError } = require('@hubspot/cli-lib/lib/models/Errors');
34
33
  const { getAccountName } = require('../../lib/sandboxes');
34
+ const { getValidEnv } = require('@hubspot/cli-lib/lib/environment');
35
35
 
36
36
  const i18nKey = 'cli.commands.sandbox.subcommands.delete';
37
37
 
@@ -71,7 +71,7 @@ exports.handler = async options => {
71
71
  trackCommandUsage('sandbox-delete', null, sandboxAccountId);
72
72
 
73
73
  const baseUrl = getHubSpotWebsiteOrigin(
74
- getEnv(sandboxAccountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD
74
+ getValidEnv(getEnv(sandboxAccountId))
75
75
  );
76
76
 
77
77
  let parentAccountId;
@@ -117,11 +117,12 @@ exports.handler = async options => {
117
117
  );
118
118
 
119
119
  if (isDefaultAccount) {
120
- logger.log(
120
+ logger.info(
121
121
  i18n(`${i18nKey}.defaultAccountWarning`, {
122
122
  account: getAccountName(accountConfig),
123
123
  })
124
124
  );
125
+ logger.log('');
125
126
  }
126
127
 
127
128
  try {
@@ -187,12 +188,11 @@ exports.handler = async options => {
187
188
  );
188
189
  }
189
190
  } else if (
190
- isSpecifiedError(
191
- err,
192
- 404,
193
- 'OBJECT_NOT_FOUND',
194
- 'SandboxErrors.SANDBOX_NOT_FOUND'
195
- )
191
+ isSpecifiedError(err, {
192
+ statusCode: 404,
193
+ category: 'OBJECT_NOT_FOUND',
194
+ subCategory: 'SandboxErrors.SANDBOX_NOT_FOUND',
195
+ })
196
196
  ) {
197
197
  logger.log('');
198
198
  logger.warn(