@hubspot/cli 4.1.8-beta.3 → 4.1.8-beta.5

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.
@@ -29,6 +29,7 @@ const {
29
29
  LocalDevManager,
30
30
  UPLOAD_PERMISSIONS,
31
31
  } = require('../../lib/LocalDevManager');
32
+ const { isSandbox } = require('../../lib/sandboxes');
32
33
  const { getAccountConfig, getEnv } = require('@hubspot/cli-lib');
33
34
  const { sandboxNamePrompt } = require('../../lib/prompts/sandboxesPrompt');
34
35
  const {
@@ -37,17 +38,23 @@ const {
37
38
  getAvailableSyncTypes,
38
39
  } = require('../../lib/sandboxes');
39
40
  const { getValidEnv } = require('@hubspot/cli-lib/lib/environment');
40
- const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
41
+ const { ERROR_TYPES } = require('@hubspot/cli-lib/lib/constants');
42
+ const {
43
+ logErrorInstance,
44
+ logApiErrorInstance,
45
+ ApiErrorContext,
46
+ } = require('@hubspot/cli-lib/errorHandlers');
41
47
  const { buildSandbox } = require('../../lib/sandbox-create');
42
48
  const { syncSandbox } = require('../../lib/sandbox-sync');
43
49
  const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
44
50
  const {
45
51
  isMissingScopeError,
52
+ isSpecifiedError,
46
53
  } = require('@hubspot/cli-lib/errorHandlers/apiErrors');
47
54
 
48
55
  const i18nKey = 'cli.commands.project.subcommands.dev';
49
56
 
50
- exports.command = 'dev [--account] [--mockServers]';
57
+ exports.command = 'dev [--account] [--port]';
51
58
  exports.describe = null; //i18n(`${i18nKey}.describe`);
52
59
 
53
60
  exports.handler = async options => {
@@ -60,13 +67,7 @@ exports.handler = async options => {
60
67
 
61
68
  const { projectConfig, projectDir } = await getProjectConfig();
62
69
 
63
- logger.log(i18n(`${i18nKey}.logs.introHeader`));
64
- uiLine();
65
- logger.log(i18n(`${i18nKey}.logs.introBody1`));
66
- logger.log();
67
- logger.log(i18n(`${i18nKey}.logs.introBody2`));
68
- uiLine();
69
- logger.log();
70
+ logger.log(i18n(`${i18nKey}.logs.betaMessage`));
70
71
 
71
72
  if (!projectConfig) {
72
73
  logger.error(i18n(`${i18nKey}.errors.noProjectConfig`));
@@ -74,47 +75,32 @@ exports.handler = async options => {
74
75
  }
75
76
 
76
77
  const accounts = getConfigAccounts();
77
- let targetAccountId = options.accountId;
78
+ let targetAccountId = options.account ? accountId : null;
78
79
  let createNewSandbox = false;
79
- let chooseNonSandbox = false;
80
+ const defaultAccountIsSandbox = isSandbox(accountConfig);
81
+
82
+ if (!targetAccountId && defaultAccountIsSandbox) {
83
+ targetAccountId = accountId;
84
+ }
80
85
 
81
86
  if (!targetAccountId) {
87
+ //logger.log(i18n(`${i18nKey}.logs.learnMoreLink`));
88
+ logger.log();
89
+ uiLine();
90
+ logger.warn(i18n(`${i18nKey}.logs.nonSandboxWarning`));
91
+ uiLine();
92
+ logger.log();
93
+
82
94
  const {
83
95
  targetAccountId: promptTargetAccountId,
84
- chooseNonSandbox: promptChooseNonSandbox,
85
96
  createNewSandbox: promptCreateNewSandbox,
86
- } = await selectTargetAccountPrompt(accounts, accountConfig, false);
97
+ } = await selectTargetAccountPrompt(accounts, accountConfig);
87
98
 
88
99
  targetAccountId = promptTargetAccountId;
89
- chooseNonSandbox = promptChooseNonSandbox;
90
100
  createNewSandbox = promptCreateNewSandbox;
91
101
  }
92
102
 
93
- logger.log();
94
-
95
- // Show a warning if the user chooses a non-sandbox account (false)
96
- let shouldTargetNonSandboxAccount;
97
- if (chooseNonSandbox) {
98
- uiLine();
99
- logger.warn(i18n(`${i18nKey}.logs.prodAccountWarning`));
100
- uiLine();
101
- logger.log();
102
-
103
- shouldTargetNonSandboxAccount = await confirmPrompt(
104
- i18n(`${i18nKey}.prompt.targetNonSandbox`)
105
- );
106
-
107
- if (shouldTargetNonSandboxAccount) {
108
- const {
109
- targetAccountId: promptNonSandboxTargetAccountId,
110
- } = await selectTargetAccountPrompt(accounts, accountConfig, true);
111
-
112
- targetAccountId = promptNonSandboxTargetAccountId;
113
- logger.log();
114
- } else {
115
- process.exit(EXIT_CODES.SUCCESS);
116
- }
117
- } else if (createNewSandbox) {
103
+ if (createNewSandbox) {
118
104
  try {
119
105
  await validateSandboxUsageLimits(accountConfig, DEVELOPER_SANDBOX, env);
120
106
  } catch (err) {
@@ -179,7 +165,8 @@ exports.handler = async options => {
179
165
  }
180
166
  );
181
167
 
182
- const isNonSandboxAccount = shouldTargetNonSandboxAccount;
168
+ const isNonSandboxAccount =
169
+ !defaultAccountIsSandbox && targetAccountId === accountId;
183
170
 
184
171
  let uploadPermission = isNonSandboxAccount
185
172
  ? UPLOAD_PERMISSIONS.manual
@@ -198,21 +185,26 @@ exports.handler = async options => {
198
185
  const spinnies = SpinniesManager.init();
199
186
 
200
187
  if (!projectExists) {
201
- uiLine();
202
- logger.warn(
203
- i18n(`${i18nKey}.logs.projectMustExistExplanation`, {
204
- accountIdentifier: uiAccountDescription(targetAccountId),
205
- projectName: projectConfig.name,
206
- })
207
- );
208
- uiLine();
188
+ // Create the project without prompting if this is a newly created sandbox
189
+ let shouldCreateProject = createNewSandbox;
190
+
191
+ if (!shouldCreateProject) {
192
+ uiLine();
193
+ logger.warn(
194
+ i18n(`${i18nKey}.logs.projectMustExistExplanation`, {
195
+ accountIdentifier: uiAccountDescription(targetAccountId),
196
+ projectName: projectConfig.name,
197
+ })
198
+ );
199
+ uiLine();
209
200
 
210
- const shouldCreateProject = await confirmPrompt(
211
- i18n(`${i18nKey}.prompt.createProject`, {
212
- accountIdentifier: uiAccountDescription(targetAccountId),
213
- projectName: projectConfig.name,
214
- })
215
- );
201
+ shouldCreateProject = await confirmPrompt(
202
+ i18n(`${i18nKey}.prompt.createProject`, {
203
+ accountIdentifier: uiAccountDescription(targetAccountId),
204
+ projectName: projectConfig.name,
205
+ })
206
+ );
207
+ }
216
208
 
217
209
  if (shouldCreateProject) {
218
210
  try {
@@ -228,9 +220,10 @@ exports.handler = async options => {
228
220
  accountIdentifier: uiAccountDescription(targetAccountId),
229
221
  projectName: projectConfig.name,
230
222
  }),
223
+ succeedColor: 'white',
231
224
  });
232
225
  } catch (err) {
233
- logger.log(i18n(`${i18nKey}.logs.failedToCreateProject`));
226
+ logger.log(i18n(`${i18nKey}.status.failedToCreateProject`));
234
227
  process.exit(EXIT_CODES.ERROR);
235
228
  }
236
229
  } else {
@@ -260,10 +253,24 @@ exports.handler = async options => {
260
253
  );
261
254
  }
262
255
 
263
- if (result && !result.succeeded) {
264
- spinnies.fail('devModeSetup', {
265
- text: i18n(`${i18nKey}.status.startupFailed`),
266
- });
256
+ if (result && result.error) {
257
+ if (
258
+ isSpecifiedError(result.error, {
259
+ subCategory: ERROR_TYPES.PROJECT_LOCKED,
260
+ })
261
+ ) {
262
+ logger.log();
263
+ logger.error(i18n(`${i18nKey}.errors.projectLockedError`));
264
+ logger.log();
265
+ } else {
266
+ logApiErrorInstance(
267
+ result.error,
268
+ new ApiErrorContext({
269
+ accountId,
270
+ projectName: projectConfig.name,
271
+ })
272
+ );
273
+ }
267
274
  process.exit(EXIT_CODES.ERROR);
268
275
  } else {
269
276
  spinnies.remove('devModeSetup');
@@ -271,11 +278,11 @@ exports.handler = async options => {
271
278
 
272
279
  const LocalDev = new LocalDevManager({
273
280
  debug: options.debug,
274
- mockServers: options.mockServers,
275
281
  projectConfig,
276
282
  projectDir,
277
283
  targetAccountId,
278
284
  uploadPermission,
285
+ port: options.port,
279
286
  });
280
287
 
281
288
  await LocalDev.start();
@@ -289,11 +296,11 @@ exports.builder = yargs => {
289
296
  addUseEnvironmentOptions(yargs, true);
290
297
  addTestingOptions(yargs, true);
291
298
 
292
- yargs.option('mockServers', {
293
- describe: 'mock servers',
294
- type: 'boolean',
295
- default: false,
299
+ yargs.option('port', {
300
+ describe: i18n(`${i18nKey}.options.port.describe`),
301
+ type: 'number',
296
302
  });
303
+
297
304
  yargs.example([['$0 project dev', i18n(`${i18nKey}.examples.default`)]]);
298
305
 
299
306
  return yargs;
@@ -19,6 +19,14 @@ const {
19
19
  } = require('../../lib/projects');
20
20
  const { i18n } = require('../../lib/lang');
21
21
  const { getAccountConfig } = require('@hubspot/cli-lib');
22
+ const { ERROR_TYPES } = require('@hubspot/cli-lib/lib/constants');
23
+ const {
24
+ isSpecifiedError,
25
+ } = require('@hubspot/cli-lib/errorHandlers/apiErrors');
26
+ const {
27
+ logApiErrorInstance,
28
+ ApiErrorContext,
29
+ } = require('@hubspot/cli-lib/errorHandlers');
22
30
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
23
31
 
24
32
  const i18nKey = 'cli.commands.project.subcommands.upload';
@@ -50,6 +58,26 @@ exports.handler = async options => {
50
58
  message
51
59
  );
52
60
 
61
+ if (result.error) {
62
+ if (
63
+ isSpecifiedError(result.error, {
64
+ subCategory: ERROR_TYPES.PROJECT_LOCKED,
65
+ })
66
+ ) {
67
+ logger.log();
68
+ logger.error(i18n(`${i18nKey}.errors.projectLockedError`));
69
+ logger.log();
70
+ } else {
71
+ logApiErrorInstance(
72
+ result.error,
73
+ new ApiErrorContext({
74
+ accountId,
75
+ projectName: projectConfig.name,
76
+ })
77
+ );
78
+ }
79
+ process.exit(EXIT_CODES.ERROR);
80
+ }
53
81
  if (result.buildSucceeded && !result.autodeployEnabled) {
54
82
  uiLine();
55
83
  logger.log(
package/lang/en.lyaml CHANGED
@@ -448,11 +448,10 @@ en:
448
448
  dev:
449
449
  describe: "Start local dev for the current project"
450
450
  logs:
451
- introHeader: "{{#bold}}HubSpot projects local development (beta){{/bold}}"
452
- introBody1: "This command runs a file watcher that works together with a HubSpot account to allow developers to write code locally and test quickly."
453
- introBody2: "All project file changes will be reflected in the target account after a slight delay."
451
+ betaMessage: "{{#yellow}}{{#bold}}[beta]{{/bold}}{{/yellow}} HubSpot projects local development"
452
+ learnMoreLink: "Learn more about the projects local dev server"
453
+ nonSandboxWarning: "Testing in a sandbox is strongly recommended. To switch the target account, select an option below or run {{#bold}}`hs accounts use`{{/bold}} before running the command again."
454
454
  placeholderAccountSelection: "Using default account as target account (for now)"
455
- prodAccountWarning: "Targeting a non-sandbox account is risky because this command will upload changes to the target account. We recommend picking a sandbox account for any work that has not been finalized or is undergoing testing prior to deploying to production."
456
455
  projectMustExistExplanation: "The project {{ projectName }} does not exist in the target account {{ accountIdentifier}}. This command requires the project to exist in the target account."
457
456
  choseNotToCreateProject: "Exiting because this command requires the project to exist in the target account."
458
457
  initialUploadMessage: "HubSpot Local Dev Server Startup"
@@ -461,12 +460,15 @@ en:
461
460
  createdProject: "Created project {{ projectName }} in {{ accountIdentifier }}"
462
461
  failedToCreateProject: "Failed to create project in the target account."
463
462
  startupMessage: "Starting local dev server for {{#bold}}{{ projectName }}{{/bold}} ..."
464
- startupFailed: "Failed to initialize local dev server"
465
463
  prompt:
466
464
  createProject: "Create new project {{ projectName}} in {{#bold}}[{{ accountIdentifier }}]{{/bold}}?"
467
465
  targetNonSandbox: "Continue testing in a non-sandbox account?"
466
+ options:
467
+ port:
468
+ describe: "The port that the running server will listen on"
468
469
  errors:
469
470
  noProjectConfig: "No project detected. Please run this command again from a project directory."
471
+ projectLockedError: "Your project is locked. This may mean that another user is running the {{#bold}}`hs project dev`{{/bold}} command for this project. If this is you, unlock the project in Projects UI."
470
472
  examples:
471
473
  default: "Start local dev for the current project"
472
474
  create:
@@ -558,6 +560,8 @@ en:
558
560
  buildSucceeded: "Build #{{ buildId }} succeeded\n"
559
561
  readyToGoLive: "🚀 Ready to take your project live?"
560
562
  runCommand: "Run `{{ command }}`"
563
+ errors:
564
+ projectLockedError: "Your project is locked. This may mean that another user is running the {{#bold}}`hs project dev`{{/bold}} command for this project. If this is you, unlock the project in Projects UI."
561
565
  options:
562
566
  forceCreate:
563
567
  describe: "Automatically create project if it does not exist"
@@ -566,15 +570,6 @@ en:
566
570
  positionals:
567
571
  path:
568
572
  describe: "Path to a project folder"
569
- unlock:
570
- describe: "Unlock a locked project"
571
- examples:
572
- default: "Unlock a locked project in the myProjectsFolder folder"
573
- logs:
574
- unlockSucceeded: "Successfully unlocked the project"
575
- positionals:
576
- path:
577
- describe: "Path to a project folder"
578
573
  watch:
579
574
  describe: "Watch your local project for changes and automatically upload changed files to a new build in HubSpot"
580
575
  examples:
@@ -841,6 +836,8 @@ en:
841
836
  options:
842
837
  describe: "Options to pass to javascript fields files"
843
838
  lib:
839
+ DevServerManager:
840
+ portConflict: "The port {{ port }} is already in use."
844
841
  LocalDevManager:
845
842
  exitingStart: "Stopping local dev server ..."
846
843
  exitingSucceed: "Successfully exited"
@@ -848,6 +845,8 @@ en:
848
845
  previousStagingBuildCancelled: "Failed to create a staging build because the project was already locked. It is now unlocked. Run the command again."
849
846
  cancelledFromUI: "The dev process has been cancelled from the UI. Any changes made since cancelling have not been uploaded. To resume dev mode, rerun {{#yellow}}`hs project dev`{{/yellow}}."
850
847
  header:
848
+ betaMessage: "{{#yellow}}{{#bold}}[beta]{{/bold}}{{/yellow}} HubSpot projects local development"
849
+ learnMoreLink: "Learn more about the projects local dev server"
851
850
  running: "Running {{ projectName}} locally on {{ accountIdentifier }}, waiting for project file changes ..."
852
851
  quitHelper: "Press {{#bold}}'q'{{/bold}} to stop the local dev server"
853
852
  viewInHubSpotLink: "View in HubSpot"
@@ -860,20 +859,25 @@ en:
860
859
  manualUpload: "{{#bold}}Status:{{/bold}} {{#green}}Manually uploading pending changes{{/green}}"
861
860
  upload:
862
861
  noUploadsAllowed: "The change to {{ filePath }} requires an upload, but the CLI cannot upload to a project that is using a github integration."
863
- manualUploadSkipped: "Manual upload skipped. Some changes may not be visible."
864
- manualUploadRequired: "Project files changed, manual upload and deploy is needed ..."
862
+ manualUploadSkipped: "Manually upload and deploy project: {{#green}}(n){{/green}}"
863
+ manualUploadConfirmed: "Manually upload and deploy project: {{#green}}(Y){{/green}}"
864
+ manualUploadRequired: "Project file changes require a manual upload and deploy ..."
865
865
  manualUploadExplanation1: "{{#yellow}}> Dev server is running on a {{#bold}}non-sandbox account{{/bold}}.{{/yellow}}"
866
866
  manualUploadExplanation2: "{{#yellow}}> Uploading changes may overwrite production data.{{/yellow}}"
867
867
  manualUploadPrompt: "? Manually upload and deploy project? {{#green}}Y/n{{/green}}"
868
- uploadingChange: "[INFO] Uploading {{ filePath }}"
868
+ uploadingAddChange: "[INFO] Uploading {{ filePath }}"
869
+ uploadedAddChange: "[INFO] Uploaded {{ filePath }}"
870
+ uploadingRemoveChange: "[INFO] Removing {{ filePath }}"
871
+ uploadedRemoveChange: "[INFO] Removed {{ filePath }}"
869
872
  uploadingChanges: "{{#bold}}Building and deploying recent changes on {{ accountIdentifier }}{{/bold}}"
873
+ uploadedChangesSucceeded: "{{#bold}}Built and deployed recent changes on {{ accountIdentifier }}{{/bold}}"
874
+ uploadedChangesFailed: "{{#bold}}Failed to build and deploy recent changes on {{ accountIdentifier }}{{/bold}}"
870
875
  projects:
871
876
  uploadProjectFiles:
872
877
  add: "Uploading {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}"
873
878
  fail: "Failed to upload {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}"
874
879
  succeed: "Uploaded {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}"
875
880
  buildCreated: "Project \"{{ projectName }}\" uploaded and build #{{ buildId }} created"
876
- projectLockedError: "\nYour project may be locked by an active `{{#yellow}}hs project watch{{/yellow}}`. Try stopping the `{{#yellow}}watch{{/yellow}}` process before uploading.\nIf that doesn't work, you may need to start and stop a new `{{#yellow}}watch{{/yellow}}` process to unlock the project."
877
881
  handleProjectUpload:
878
882
  emptySource: "Source directory \"{{ srcDir }}\" is empty. Add files to your project and rerun `{{#yellow}}hs project upload{{/yellow}}` to upload them to HubSpot."
879
883
  compressed: "Project files compressed: {{ byteCount }} bytes"
@@ -955,12 +959,10 @@ en:
955
959
  describe: "Use environment variable config"
956
960
  prompts:
957
961
  projectDevTargetAccountPrompt:
958
- createNewSandboxOption: "<< Create a new development sandbox account >>"
959
- chooseNonSandboxOption: "<< Choose a non-sandbox account >>"
960
- chooseNonSandboxAccount: "[--account] Choose a non-sandbox account to test with:"
961
- chooseSandboxAccount: "[--account] Choose a sandbox account to test with:"
962
- defaultAccountNotProd: "Option unavailable because your default account must be a production account."
963
- sandboxLimit: "Option unavailable because you’ve reached the limit of {{ limit }} development sandboxes."
962
+ createNewSandboxOption: "<Test on a new dev sandbox>"
963
+ chooseDefaultAccountOption: "<{{#bold}}!{{/bold}} Test on this production account {{#bold}}!{{/bold}}>"
964
+ promptMessage: "[--account] Choose a sandbox under {{ accountIdentifier }} to test with:"
965
+ sandboxLimit: "You’ve reached the limit of {{ limit }} development sandboxes"
964
966
  projectLogsPrompt:
965
967
  projectName:
966
968
  message: "[--project] Enter the project name:"
@@ -0,0 +1,93 @@
1
+ const express = require('express');
2
+ const bodyParser = require('body-parser');
3
+ const cors = require('cors');
4
+ const { i18n } = require('./lang');
5
+ const { getProjectDetailUrl } = require('./projects');
6
+ const { EXIT_CODES } = require('./enums/exitCodes');
7
+ const { logger } = require('@hubspot/cli-lib/logger');
8
+
9
+ const i18nKey = 'cli.lib.DevServerManager';
10
+
11
+ const DEFAULT_PORT = 8080;
12
+
13
+ class DevServerManager {
14
+ constructor() {
15
+ this.server = null;
16
+ this.devServers = {};
17
+ }
18
+
19
+ async iterateDevServers(callback) {
20
+ const serverKeys = Object.keys(this.devServers);
21
+
22
+ for (let i = 0; i < serverKeys.length; i++) {
23
+ const serverKey = serverKeys[i];
24
+ const serverInterface = this.devServers[serverKey];
25
+ await callback(serverInterface, serverKey);
26
+ }
27
+ }
28
+
29
+ async start({ projectConfig, accountId, port }) {
30
+ const app = express();
31
+
32
+ // Install Middleware
33
+ app.use(bodyParser.json({ limit: '50mb' }));
34
+ app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
35
+ app.use(cors());
36
+
37
+ // Configure
38
+ app.set('trust proxy', true);
39
+
40
+ // Initialize a base route
41
+ app.get('/', (req, res) => {
42
+ res.send('HubSpot local dev server');
43
+ });
44
+
45
+ // Initialize URL redirects
46
+ app.get('/hs/project', (req, res) => {
47
+ res.redirect(getProjectDetailUrl(projectConfig.name, accountId));
48
+ });
49
+ app.get('/hs/learnMore', (req, res) => {
50
+ //TODO link to docs
51
+ res.redirect(getProjectDetailUrl(projectConfig.name, accountId));
52
+ });
53
+
54
+ // Initialize component servers
55
+ await this.iterateDevServers(async (serverInterface, serverKey) => {
56
+ if (serverInterface.start) {
57
+ const serverApp = await serverInterface.start(serverKey);
58
+ app.use(`/${serverKey}`, serverApp);
59
+ }
60
+ });
61
+
62
+ // Start server
63
+ this.server = app.listen(port || DEFAULT_PORT).on('error', err => {
64
+ if (err.code === 'EADDRINUSE') {
65
+ logger.error(
66
+ i18n(`${i18nKey}.portConflict`, { port: port || DEFAULT_PORT })
67
+ );
68
+ logger.log();
69
+ process.exit(EXIT_CODES.ERROR);
70
+ }
71
+ });
72
+
73
+ return this.server.address()
74
+ ? `http://localhost:${this.server.address().port}`
75
+ : null;
76
+ }
77
+
78
+ async notify() {
79
+ return { uploadRequired: true };
80
+ }
81
+
82
+ async execute() {
83
+ return;
84
+ }
85
+
86
+ async cleanup() {
87
+ if (this.server) {
88
+ await this.server.close();
89
+ }
90
+ }
91
+ }
92
+
93
+ module.exports = new DevServerManager();
@@ -22,16 +22,14 @@ const {
22
22
  queueBuild,
23
23
  } = require('@hubspot/cli-lib/api/dfs');
24
24
  const SpinniesManager = require('./SpinniesManager');
25
+ const DevServerManager = require('./DevServerManager');
25
26
  const { EXIT_CODES } = require('./enums/exitCodes');
26
- const {
27
- getProjectDetailUrl,
28
- pollProjectBuildAndDeploy,
29
- } = require('./projects');
27
+ const { pollProjectBuildAndDeploy } = require('./projects');
30
28
  const { uiAccountDescription, uiLink } = require('./ui');
31
29
 
32
30
  const i18nKey = 'cli.lib.LocalDevManager';
33
31
 
34
- const BUILD_DEBOUNCE_TIME = 2000;
32
+ const BUILD_DEBOUNCE_TIME = 3500;
35
33
 
36
34
  const WATCH_EVENTS = {
37
35
  add: 'add',
@@ -45,6 +43,7 @@ const UPLOAD_PERMISSIONS = {
45
43
  manual: 'manual',
46
44
  never: 'never',
47
45
  };
46
+
48
47
  class LocalDevManager {
49
48
  constructor(options) {
50
49
  this.targetAccountId = options.targetAccountId;
@@ -53,7 +52,6 @@ class LocalDevManager {
53
52
  this.uploadPermission =
54
53
  options.uploadPermission || UPLOAD_PERMISSIONS.always;
55
54
  this.debug = options.debug || false;
56
- this.mockServers = options.mockServers || false;
57
55
  this.projectSourceDir = path.join(
58
56
  this.projectDir,
59
57
  this.projectConfig.srcDir
@@ -64,6 +62,8 @@ class LocalDevManager {
64
62
  this.standbyChanges = [];
65
63
  this.debouncedBuild = null;
66
64
  this.currentStagedBuildId = null;
65
+ this.port = options.port;
66
+ this.devServerPath = null;
67
67
 
68
68
  if (!this.targetAccountId || !this.projectConfig || !this.projectDir) {
69
69
  process.exit(EXIT_CODES.ERROR);
@@ -90,8 +90,9 @@ class LocalDevManager {
90
90
 
91
91
  this.uploadQueue.start();
92
92
 
93
- this.logConsoleHeader();
94
93
  await this.startServers();
94
+
95
+ this.logConsoleHeader();
95
96
  await this.startWatching();
96
97
  this.updateKeypressListeners();
97
98
  }
@@ -145,6 +146,26 @@ class LocalDevManager {
145
146
 
146
147
  logConsoleHeader() {
147
148
  this.spinnies.removeAll();
149
+ this.spinnies.add('betaMessage', {
150
+ text: i18n(`${i18nKey}.header.betaMessage`),
151
+ category: 'header',
152
+ status: 'non-spinnable',
153
+ });
154
+
155
+ // this.spinnies.add('learnMoreLink', {
156
+ // text: uiLink(
157
+ // i18n(`${i18nKey}.header.learnMoreLink`),
158
+ // this.generateLocalURL(`/hs/learnMore`),
159
+ // { inSpinnies: true }
160
+ // ),
161
+ // category: 'header',
162
+ // status: 'non-spinnable',
163
+ // });
164
+ this.spinnies.add('spacer-1', {
165
+ text: ' ',
166
+ status: 'non-spinnable',
167
+ category: 'header',
168
+ });
148
169
  this.spinnies.add('devModeRunning', {
149
170
  text: i18n(`${i18nKey}.header.running`, {
150
171
  accountIdentifier: uiAccountDescription(this.targetAccountId),
@@ -159,23 +180,17 @@ class LocalDevManager {
159
180
  indent: 1,
160
181
  category: 'header',
161
182
  });
162
- const projectDetailUrl = getProjectDetailUrl(
163
- this.projectConfig.name,
164
- this.targetAccountId
165
- );
166
183
  this.spinnies.add('viewInHubSpotLink', {
167
184
  text: uiLink(
168
185
  i18n(`${i18nKey}.header.viewInHubSpotLink`),
169
- projectDetailUrl,
170
- {
171
- inSpinnies: true,
172
- }
186
+ this.generateLocalURL(`/hs/project`),
187
+ { inSpinnies: true }
173
188
  ),
174
189
  status: 'non-spinnable',
175
190
  indent: 1,
176
191
  category: 'header',
177
192
  });
178
- this.spinnies.add('spacer-1', {
193
+ this.spinnies.add('spacer-2', {
179
194
  text: ' ',
180
195
  status: 'non-spinnable',
181
196
  category: 'header',
@@ -202,24 +217,29 @@ class LocalDevManager {
202
217
  handleKeypress(async key => {
203
218
  if ((key.ctrl && key.name === 'c') || key.name === 'q') {
204
219
  this.stop();
205
- } else if (key.name === 'y') {
206
- if (
207
- this.uploadPermission === UPLOAD_PERMISSIONS.manual &&
208
- this.hasAnyUnsupportedStandbyChanges()
209
- ) {
210
- this.clearConsoleContent();
220
+ } else if (
221
+ (key.name === 'y' || key.name === 'n') &&
222
+ this.uploadPermission === UPLOAD_PERMISSIONS.manual &&
223
+ this.hasAnyUnsupportedStandbyChanges()
224
+ ) {
225
+ this.spinnies.remove('manualUploadRequired');
226
+ this.spinnies.remove('manualUploadExplanation1');
227
+ this.spinnies.remove('manualUploadExplanation2');
228
+ this.spinnies.remove('manualUploadPrompt');
229
+
230
+ if (key.name === 'y') {
231
+ this.spinnies.add(null, {
232
+ text: i18n(`${i18nKey}.upload.manualUploadConfirmed`),
233
+ status: 'succeed',
234
+ succeedColor: 'white',
235
+ noIndent: true,
236
+ });
211
237
  this.updateDevModeStatus('manualUpload');
212
238
  await this.createNewStagingBuild();
213
239
  await this.flushStandbyChanges();
214
240
  await this.queueBuild();
215
- }
216
- } else if (key.name === 'n') {
217
- if (
218
- this.uploadPermission === UPLOAD_PERMISSIONS.manual &&
219
- this.hasAnyUnsupportedStandbyChanges()
220
- ) {
221
- this.clearConsoleContent();
222
- this.spinnies.add('manualUploadSkipped', {
241
+ } else if (key.name === 'n') {
242
+ this.spinnies.add(null, {
223
243
  text: i18n(`${i18nKey}.upload.manualUploadSkipped`),
224
244
  status: 'fail',
225
245
  failColor: 'white',
@@ -230,6 +250,10 @@ class LocalDevManager {
230
250
  });
231
251
  }
232
252
 
253
+ generateLocalURL(path) {
254
+ return this.devServerPath ? `${this.devServerPath}${path}` : null;
255
+ }
256
+
233
257
  updateDevModeStatus(langKey) {
234
258
  this.spinnies.update('devModeStatus', {
235
259
  text: i18n(`${i18nKey}.header.status.${langKey}`),
@@ -239,13 +263,6 @@ class LocalDevManager {
239
263
  }
240
264
 
241
265
  async pauseUploadQueue() {
242
- this.spinnies.add('uploading', {
243
- text: i18n(`${i18nKey}.upload.uploadingChanges`, {
244
- accountIdentifier: uiAccountDescription(this.targetAccountId),
245
- }),
246
- noIndent: true,
247
- });
248
-
249
266
  this.uploadQueue.pause();
250
267
  await this.uploadQueue.onIdle();
251
268
  }
@@ -297,11 +314,13 @@ class LocalDevManager {
297
314
  remotePath: path.relative(this.projectSourceDir, filePath),
298
315
  };
299
316
 
300
- const isSupportedChange = await this.notifyServers(changeInfo);
317
+ const notifyResponse = await this.notifyServers(changeInfo);
301
318
 
302
- if (isSupportedChange) {
319
+ if (!notifyResponse.uploadRequired) {
303
320
  this.updateDevModeStatus('supportedChange');
304
321
  this.addChangeToStandbyQueue({ ...changeInfo, supported: true });
322
+
323
+ await this.executeServers(notifyResponse, changeInfo);
305
324
  return;
306
325
  }
307
326
 
@@ -311,13 +330,7 @@ class LocalDevManager {
311
330
  }
312
331
 
313
332
  if (this.uploadQueue.isPaused) {
314
- if (
315
- !this.standbyChanges.find(
316
- changeInfo => changeInfo.filePath === filePath
317
- )
318
- ) {
319
- this.addChangeToStandbyQueue({ ...changeInfo, supported: false });
320
- }
333
+ this.addChangeToStandbyQueue({ ...changeInfo, supported: false });
321
334
  } else {
322
335
  await this.flushStandbyChanges();
323
336
 
@@ -334,7 +347,6 @@ class LocalDevManager {
334
347
  handlePreventedUpload(changeInfo) {
335
348
  const { remotePath } = changeInfo;
336
349
 
337
- this.clearConsoleContent();
338
350
  if (this.uploadPermission === UPLOAD_PERMISSIONS.never) {
339
351
  this.updateDevModeStatus('noUploadsAllowed');
340
352
 
@@ -388,35 +400,63 @@ class LocalDevManager {
388
400
  logger.debug(`File ignored: ${filePath}`);
389
401
  return;
390
402
  }
391
- this.standbyChanges.push(changeInfo);
403
+
404
+ const existingIndex = this.standbyChanges.findIndex(
405
+ standyChangeInfo => standyChangeInfo.filePath === filePath
406
+ );
407
+
408
+ if (existingIndex > -1) {
409
+ // Make sure the most recent event to this file is the one that gets acted on
410
+ this.standbyChanges[existingIndex].event = event;
411
+ } else {
412
+ this.standbyChanges.push(changeInfo);
413
+ }
392
414
  }
393
415
 
394
416
  async sendChanges(changeInfo) {
395
417
  const { event, filePath, remotePath } = changeInfo;
396
418
 
397
- this.spinnies.add(filePath, {
398
- text: i18n(`${i18nKey}.upload.uploadingChange`, {
399
- filePath: remotePath,
400
- }),
401
- status: 'non-spinnable',
402
- });
403
419
  try {
404
420
  if (event === WATCH_EVENTS.add || event === WATCH_EVENTS.change) {
421
+ const spinniesKey = this.spinnies.add(null, {
422
+ text: i18n(`${i18nKey}.upload.uploadingAddChange`, {
423
+ filePath: remotePath,
424
+ }),
425
+ status: 'non-spinnable',
426
+ });
405
427
  await uploadFileToBuild(
406
428
  this.targetAccountId,
407
429
  this.projectConfig.name,
408
430
  filePath,
409
431
  remotePath
410
432
  );
433
+ this.spinnies.update(spinniesKey, {
434
+ text: i18n(`${i18nKey}.upload.uploadedAddChange`, {
435
+ filePath: remotePath,
436
+ }),
437
+ status: 'non-spinnable',
438
+ });
411
439
  } else if (
412
440
  event === WATCH_EVENTS.unlink ||
413
441
  event === WATCH_EVENTS.unlinkDir
414
442
  ) {
443
+ const spinniesKey = this.spinnies.add(null, {
444
+ text: i18n(`${i18nKey}.upload.uploadingRemoveChange`, {
445
+ filePath: remotePath,
446
+ }),
447
+ status: 'non-spinnable',
448
+ });
415
449
  await deleteFileFromBuild(
416
450
  this.targetAccountId,
417
451
  this.projectConfig.name,
418
452
  remotePath
419
453
  );
454
+ this.spinnies.update(spinniesKey, {
455
+ text: i18n(`${i18nKey}.upload.uploadedRemoveChange`, {
456
+ filePath: remotePath,
457
+ }),
458
+ status: 'non-spinnable',
459
+ });
420
460
  }
421
461
  } catch (err) {
422
462
  logger.debug(err);
@@ -439,6 +479,13 @@ class LocalDevManager {
439
479
  }
440
480
 
441
481
  async queueBuild() {
482
+ const spinniesKey = this.spinnies.add(null, {
483
+ text: i18n(`${i18nKey}.upload.uploadingChanges`, {
484
+ accountIdentifier: uiAccountDescription(this.targetAccountId),
485
+ }),
486
+ noIndent: true,
487
+ });
488
+
442
489
  await this.pauseUploadQueue();
443
490
 
444
491
  try {
@@ -464,7 +511,7 @@ class LocalDevManager {
464
511
  return;
465
512
  }
466
513
 
467
- await pollProjectBuildAndDeploy(
514
+ const result = await pollProjectBuildAndDeploy(
468
515
  this.targetAccountId,
469
516
  this.projectConfig,
470
517
  null,
@@ -472,12 +519,31 @@ class LocalDevManager {
472
519
  true
473
520
  );
474
521
 
522
+ if (result && result.succeeded) {
523
+ this.spinnies.succeed(spinniesKey, {
524
+ text: i18n(`${i18nKey}.upload.uploadedChangesSucceeded`, {
525
+ accountIdentifier: uiAccountDescription(this.targetAccountId),
526
+ }),
527
+ succeedColor: 'white',
528
+ noIndent: true,
529
+ });
530
+ } else {
531
+ this.spinnies.fail(spinniesKey, {
532
+ text: i18n(`${i18nKey}.upload.uploadedChangesFailed`, {
533
+ accountIdentifier: uiAccountDescription(this.targetAccountId),
534
+ }),
535
+ failColor: 'white',
536
+ noIndent: true,
537
+ });
538
+ }
539
+
540
+ this.spinnies.removeAll({ targetCategory: 'projectPollStatus' });
541
+
475
542
  if (this.uploadPermission === UPLOAD_PERMISSIONS.always) {
476
543
  await this.createNewStagingBuild();
477
544
  }
478
545
 
479
546
  this.uploadQueue.start();
480
- this.clearConsoleContent();
481
547
 
482
548
  if (this.hasAnyUnsupportedStandbyChanges()) {
483
549
  this.flushStandbyChanges();
@@ -510,23 +576,24 @@ class LocalDevManager {
510
576
  }
511
577
 
512
578
  async startServers() {
513
- // TODO spin up local dev servers
514
- return true;
579
+ this.devServerPath = await DevServerManager.start({
580
+ accountId: this.targetAccountId,
581
+ projectConfig: this.projectConfig,
582
+ port: this.port,
583
+ });
515
584
  }
516
585
 
517
586
  async notifyServers(changeInfo) {
518
- const { remotePath } = changeInfo;
587
+ const notifyResponse = await DevServerManager.notify(changeInfo);
588
+ return notifyResponse;
589
+ }
519
590
 
520
- // TODO notify servers of the change
521
- if (this.mockServers) {
522
- return !remotePath.endsWith('app.json');
523
- }
524
- return false;
591
+ async executeServers(notifyResponse, changeInfo) {
592
+ await DevServerManager.execute(notifyResponse, changeInfo);
525
593
  }
526
594
 
527
595
  async cleanupServers() {
528
- // TODO tell servers to cleanup
529
- return;
596
+ await DevServerManager.cleanup();
530
597
  }
531
598
  }
532
599
 
@@ -51,18 +51,23 @@ class SpinniesManager {
51
51
  const { category, isParent, noIndent, ...rest } = options;
52
52
  const originalIndent = rest.indent || 0;
53
53
 
54
+ // Support adding generic spinnies lines without specifying a key
55
+ const uniqueKey = key || `${Date.now()}`;
56
+
54
57
  if (category) {
55
- this.addKeyToCategory(key, category);
58
+ this.addKeyToCategory(uniqueKey, category);
56
59
  }
57
60
 
58
- this.spinnies.add(key, {
61
+ this.spinnies.add(uniqueKey, {
59
62
  ...rest,
60
63
  indent: this.parentKey && !noIndent ? originalIndent + 1 : originalIndent,
61
64
  });
62
65
 
63
66
  if (isParent) {
64
- this.parentKey = key;
67
+ this.parentKey = uniqueKey;
65
68
  }
69
+
70
+ return uniqueKey;
66
71
  }
67
72
 
68
73
  remove(key) {
@@ -79,10 +84,14 @@ class SpinniesManager {
79
84
  * Removes all spinnies instances
80
85
  * @param {string} preserveCategory - do not remove spinnies with a matching category
81
86
  */
82
- removeAll({ preserveCategory = null } = {}) {
87
+ removeAll({ preserveCategory = null, targetCategory = null } = {}) {
83
88
  if (this.spinnies) {
84
89
  Object.keys(this.spinnies.spinners).forEach(key => {
85
- if (
90
+ if (targetCategory) {
91
+ if (this.getCategoryForKey(key) === targetCategory) {
92
+ this.remove(key);
93
+ }
94
+ } else if (
86
95
  !preserveCategory ||
87
96
  this.getCategoryForKey(key) !== preserveCategory
88
97
  ) {
package/lib/projects.js CHANGED
@@ -10,7 +10,6 @@ const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
10
10
  const {
11
11
  ENVIRONMENTS,
12
12
  FEEDBACK_INTERVAL,
13
- ERROR_TYPES,
14
13
  POLLING_DELAY,
15
14
  PROJECT_BUILD_TEXT,
16
15
  PROJECT_DEPLOY_TEXT,
@@ -320,6 +319,7 @@ const uploadProjectFiles = async (
320
319
  });
321
320
 
322
321
  let buildId;
322
+ let error;
323
323
 
324
324
  try {
325
325
  const upload = await uploadProject(
@@ -352,20 +352,10 @@ const uploadProjectFiles = async (
352
352
  }),
353
353
  });
354
354
 
355
- logApiErrorInstance(
356
- err,
357
- new ApiErrorContext({
358
- accountId,
359
- projectName,
360
- })
361
- );
362
- if (err.error.subCategory === ERROR_TYPES.PROJECT_LOCKED) {
363
- logger.log(i18n(`${i18nKey}.uploadProjectFiles.projectLockedError`));
364
- }
365
- process.exit(EXIT_CODES.ERROR);
355
+ error = err;
366
356
  }
367
357
 
368
- return { buildId };
358
+ return { buildId, error };
369
359
  };
370
360
 
371
361
  const pollProjectBuildAndDeploy = async (
@@ -478,7 +468,7 @@ const handleProjectUpload = async (
478
468
 
479
469
  const result = new Promise(resolve =>
480
470
  output.on('close', async function() {
481
- let result = {};
471
+ let uploadResult = {};
482
472
 
483
473
  logger.debug(
484
474
  i18n(`${i18nKey}.handleProjectUpload.compressed`, {
@@ -486,22 +476,24 @@ const handleProjectUpload = async (
486
476
  })
487
477
  );
488
478
 
489
- const { buildId } = await uploadProjectFiles(
479
+ const { buildId, error } = await uploadProjectFiles(
490
480
  accountId,
491
481
  projectConfig.name,
492
482
  tempFile.name,
493
483
  uploadMessage
494
484
  );
495
485
 
496
- if (callbackFunc) {
497
- result = await callbackFunc(
486
+ if (error) {
487
+ uploadResult.error = error;
488
+ } else if (callbackFunc) {
489
+ uploadResult = await callbackFunc(
498
490
  accountId,
499
491
  projectConfig,
500
492
  tempFile,
501
493
  buildId
502
494
  );
503
495
  }
504
- resolve(result);
496
+ resolve(uploadResult);
505
497
  })
506
498
  );
507
499
 
@@ -558,6 +550,7 @@ const makePollTaskStatusFunc = ({
558
550
  succeedColor: 'white',
559
551
  failColor: 'white',
560
552
  failPrefix: chalk.bold('!'),
553
+ category: 'projectPollStatus',
561
554
  });
562
555
 
563
556
  const [
@@ -618,6 +611,7 @@ const makePollTaskStatusFunc = ({
618
611
  indent,
619
612
  succeedColor: 'white',
620
613
  failColor: 'white',
614
+ category: 'projectPollStatus',
621
615
  });
622
616
  };
623
617
 
@@ -9,90 +9,64 @@ const { logger } = require('@hubspot/cli-lib/logger');
9
9
  const i18nKey = 'cli.lib.prompts.projectDevTargetAccountPrompt';
10
10
 
11
11
  const mapSandboxAccount = accountConfig => ({
12
- name: getAccountName(accountConfig),
12
+ name: getAccountName(accountConfig, false),
13
13
  value: {
14
14
  targetAccountId: getAccountId(accountConfig.name),
15
- chooseNonSandbox: false,
16
15
  createNewSandbox: false,
17
16
  },
18
17
  });
19
18
 
20
- const selectTargetAccountPrompt = async (
21
- accounts,
22
- defaultAccountConfig,
23
- nonSandbox = false
24
- ) => {
25
- let choices;
19
+ const selectTargetAccountPrompt = async (accounts, defaultAccountConfig) => {
20
+ let sandboxUsage = {};
21
+ const defaultAccountId = getAccountId(defaultAccountConfig.name);
26
22
 
27
- if (nonSandbox) {
28
- choices = accounts
29
- .filter(accountConfig => !isSandbox(accountConfig))
30
- .map(accountConfig => {
31
- const accountId = getAccountId(accountConfig.name);
32
- return {
33
- name: uiAccountDescription(accountId),
34
- value: {
35
- targetAccountId: accountId,
36
- chooseNonSandbox: false,
37
- createNewSandbox: false,
38
- },
39
- };
40
- });
41
- } else {
42
- let sandboxUsage = {};
43
- try {
44
- const accountId = getAccountId(defaultAccountConfig.portalId);
45
- sandboxUsage = await getSandboxUsageLimits(accountId);
46
- } catch (err) {
47
- logger.debug('Unable to fetch sandbox usage limits: ', err);
48
- }
49
- const sandboxAccounts = accounts.reverse().filter(isSandbox);
50
- let disabledMessage = false;
51
- if (isSandbox(defaultAccountConfig)) {
52
- disabledMessage = i18n(`${i18nKey}.defaultAccountNotProd`);
53
- }
54
- if (
55
- sandboxUsage['DEVELOPER'] &&
56
- sandboxUsage['DEVELOPER'].available === 0
57
- ) {
58
- disabledMessage = i18n(`${i18nKey}.sandboxLimit`, {
59
- limit: sandboxUsage['DEVELOPER'].limit,
60
- });
61
- }
62
- // Order choices by Create new -> Developer Sandbox -> Standard Sandbox -> Non sandbox
63
- choices = [
64
- {
65
- name: i18n(`${i18nKey}.createNewSandboxOption`),
66
- value: {
67
- targetAccountId: null,
68
- chooseNonSandbox: false,
69
- createNewSandbox: true,
70
- },
71
- disabled: disabledMessage,
23
+ try {
24
+ sandboxUsage = await getSandboxUsageLimits(defaultAccountId);
25
+ } catch (err) {
26
+ logger.debug('Unable to fetch sandbox usage limits: ', err);
27
+ }
28
+
29
+ const sandboxAccounts = accounts.reverse().filter(isSandbox);
30
+ let disabledMessage = false;
31
+
32
+ if (sandboxUsage['DEVELOPER'] && sandboxUsage['DEVELOPER'].available === 0) {
33
+ disabledMessage = i18n(`${i18nKey}.sandboxLimit`, {
34
+ limit: sandboxUsage['DEVELOPER'].limit,
35
+ });
36
+ }
37
+
38
+ // Order choices by Developer Sandbox -> Standard Sandbox
39
+ const choices = [
40
+ ...sandboxAccounts
41
+ .filter(a => a.sandboxAccountType === 'DEVELOPER')
42
+ .map(mapSandboxAccount),
43
+ ...sandboxAccounts
44
+ .filter(a => a.sandboxAccountType === 'STANDARD')
45
+ .map(mapSandboxAccount),
46
+ {
47
+ name: i18n(`${i18nKey}.createNewSandboxOption`),
48
+ value: {
49
+ targetAccountId: null,
50
+ createNewSandbox: true,
72
51
  },
73
- ...sandboxAccounts
74
- .filter(a => a.sandboxAccountType === 'DEVELOPER')
75
- .map(mapSandboxAccount),
76
- ...sandboxAccounts
77
- .filter(a => a.sandboxAccountType === 'STANDARD')
78
- .map(mapSandboxAccount),
79
- {
80
- name: i18n(`${i18nKey}.chooseNonSandboxOption`),
81
- value: {
82
- targetAccountId: null,
83
- chooseNonSandbox: true,
84
- createNewSandbox: false,
85
- },
52
+ disabled: disabledMessage,
53
+ },
54
+ {
55
+ name: i18n(`${i18nKey}.chooseDefaultAccountOption`),
56
+ value: {
57
+ targetAccountId: defaultAccountId,
58
+ createNewSandbox: false,
86
59
  },
87
- ];
88
- }
60
+ },
61
+ ];
62
+
89
63
  const { targetAccountInfo } = await promptUser([
90
64
  {
91
65
  name: 'targetAccountInfo',
92
66
  type: 'list',
93
- message: nonSandbox
94
- ? i18n(`${i18nKey}.chooseNonSandboxAccount`)
95
- : i18n(`${i18nKey}.chooseSandboxAccount`),
67
+ message: i18n(`${i18nKey}.promptMessage`, {
68
+ accountIdentifier: uiAccountDescription(defaultAccountId),
69
+ }),
96
70
  choices,
97
71
  },
98
72
  ]);
package/lib/sandboxes.js CHANGED
@@ -55,13 +55,16 @@ const getSandboxTypeAsString = type =>
55
55
  const isSandbox = config =>
56
56
  config.sandboxAccountType && config.sandboxAccountType !== null;
57
57
 
58
- function getAccountName(config) {
58
+ function getAccountName(config, bold = true) {
59
59
  const sandboxName = `[${getSandboxTypeAsString(
60
60
  config.sandboxAccountType
61
61
  )} sandbox] `;
62
- return chalk.bold(
63
- `${config.name} ${isSandbox(config) ? sandboxName : ''}(${config.portalId})`
64
- );
62
+
63
+ const message = `${config.name} ${isSandbox(config) ? sandboxName : ''}(${
64
+ config.portalId
65
+ })`;
66
+
67
+ return bold ? chalk.bold(message) : message;
65
68
  }
66
69
 
67
70
  function getHasSandboxesByType(parentAccountConfig, type) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "4.1.8-beta.3",
3
+ "version": "4.1.8-beta.5",
4
4
  "description": "CLI for working with HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -8,16 +8,18 @@
8
8
  "url": "https://github.com/HubSpot/hubspot-cms-tools"
9
9
  },
10
10
  "dependencies": {
11
- "@hubspot/cli-lib": "4.1.8-beta.3",
12
- "@hubspot/serverless-dev-runtime": "4.1.8-beta.3",
11
+ "@hubspot/cli-lib": "^4.1.12",
12
+ "@hubspot/serverless-dev-runtime": "4.1.8-beta.4",
13
13
  "archiver": "^5.3.0",
14
+ "body-parser": "^1.19.0",
14
15
  "chalk": "^4.1.2",
15
16
  "chokidar": "^3.0.1",
16
17
  "cli-progress": "^3.11.2",
18
+ "cors": "^2.8.5",
17
19
  "express": "^4.17.1",
18
20
  "findup-sync": "^4.0.0",
19
21
  "fs-extra": "^8.1.0",
20
- "inquirer": "^8.2.0",
22
+ "inquirer": "8.2.0",
21
23
  "js-yaml": "^4.1.0",
22
24
  "moment": "^2.29.1",
23
25
  "open": "^7.0.3",
@@ -41,5 +43,5 @@
41
43
  "publishConfig": {
42
44
  "access": "public"
43
45
  },
44
- "gitHead": "1b4eaa68c074fc230a21538fabd1160be6cc2c18"
46
+ "gitHead": "5b5d25bf5e3a4d29b516dbc31195f4cd7051b898"
45
47
  }