@hubspot/cli 8.15.0-beta.1 → 8.15.0-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.
package/bin/cli.js CHANGED
@@ -33,6 +33,7 @@ import mcpCommand from '../commands/mcp.js';
33
33
  import upgradeCommand from '../commands/upgrade.js';
34
34
  import apiCommand from '../commands/api.js';
35
35
  import { uiLogger } from '../lib/ui/logger.js';
36
+ import { lib } from '../lang/en.js';
36
37
  import { initializeSpinniesManager } from '../lib/middleware/spinniesMiddleware.js';
37
38
  import { addCommandSuggestions } from '../lib/commandSuggestion.js';
38
39
  import { pkg } from '../lib/jsonLoader.js';
@@ -80,10 +81,14 @@ const argv = yargs(process.argv.slice(2))
80
81
  describe: 'prevent hyperlinks from displaying in the ui',
81
82
  hidden: true,
82
83
  type: 'boolean',
84
+ })
85
+ .option('color', {
86
+ default: true,
87
+ describe: lib.commonOpts.options.color,
88
+ type: 'boolean',
83
89
  })
84
90
  .option('noColor', {
85
91
  default: false,
86
- describe: 'prevent color from displaying in the ui',
87
92
  hidden: true,
88
93
  type: 'boolean',
89
94
  })
package/bin/hs.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import './setupColor.js';
3
4
  import './silenceErrors.js';
4
5
 
5
6
  import './cli.js';
package/bin/hscms.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import './setupColor.js';
3
4
  import './silenceErrors.js';
4
5
 
5
6
  import './cli.js';
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { hasColorDisableSignal } from '../lib/ui/supportsColor.js';
3
+ // Runs before cli.js so it executes before chalk is imported. chalk reads
4
+ // FORCE_COLOR (but not NO_COLOR / COLOR) once at load and caches its color
5
+ // level, and lang/en.ts bakes chalk styling into strings at import time. So
6
+ // translating a no-color request into FORCE_COLOR=0 here, ahead of those
7
+ // imports, is what makes NO_COLOR / COLOR actually disable color everywhere.
8
+ if (hasColorDisableSignal()) {
9
+ process.env.FORCE_COLOR = '0';
10
+ }
package/lang/en.d.ts CHANGED
@@ -1376,6 +1376,7 @@ export declare const commands: {
1376
1376
  failedToConfigureVsCode: string;
1377
1377
  configuredVsCode: string;
1378
1378
  vsCodeNotFound: string;
1379
+ vsCodeVersionUnsupported: string;
1379
1380
  };
1380
1381
  otherInstructions: {
1381
1382
  header: string;
@@ -3838,6 +3839,7 @@ export declare const lib: {
3838
3839
  jsonOutput: string;
3839
3840
  jsonSchema: string;
3840
3841
  debug: string;
3842
+ color: string;
3841
3843
  };
3842
3844
  };
3843
3845
  configMigrate: {
package/lang/en.js CHANGED
@@ -1392,6 +1392,7 @@ export const commands = {
1392
1392
  failedToConfigureVsCode: 'Failed to configure VSCode',
1393
1393
  configuredVsCode: 'Configured VSCode',
1394
1394
  vsCodeNotFound: "VSCode CLI is not installed (missing 'code' command). Install it and re-run hs mcp setup.",
1395
+ vsCodeVersionUnsupported: 'VSCode 1.99 or later is required for MCP server support. Please update VSCode and try again.',
1395
1396
  },
1396
1397
  otherInstructions: {
1397
1398
  header: 'To connect the HubSpot MCP server to another tool, add the following MCP server configuration.',
@@ -3877,6 +3878,7 @@ export const lib = {
3877
3878
  jsonOutput: 'Format output as JSON',
3878
3879
  jsonSchema: 'Print the JSON output schema and exit',
3879
3880
  debug: 'Set log level to debug',
3881
+ color: 'Enable colored output (use --no-color to disable)',
3880
3882
  },
3881
3883
  },
3882
3884
  configMigrate: {
package/lib/mcp/setup.js CHANGED
@@ -9,6 +9,7 @@ import path from 'path';
9
9
  import os from 'os';
10
10
  import fs from 'fs-extra';
11
11
  import { existsSync } from 'fs';
12
+ import semver from 'semver';
12
13
  const mcpServerName = MCP_SERVER_NAME;
13
14
  const claudeCode = 'claude';
14
15
  const cursor = 'cursor';
@@ -18,6 +19,7 @@ const codex = 'codex';
18
19
  const gemini = 'gemini';
19
20
  const opencode = 'opencode';
20
21
  const OTHER_TOOL = 'other';
22
+ const MINIMUM_VSCODE_VERSION = '1.99.0';
21
23
  const clientLabels = {
22
24
  codex: commands.mcp.setup.codex,
23
25
  claude: commands.mcp.setup.claudeCode,
@@ -204,11 +206,19 @@ function setupMcpConfigFile(config) {
204
206
  }
205
207
  }
206
208
  export async function setupVsCode(mcpCommand = defaultMcpCommand) {
209
+ const commandWithAgent = buildCommandWithAgentString(mcpCommand, vscode);
207
210
  try {
208
211
  SpinniesManager.add('vsCode', {
209
212
  text: commands.mcp.setup.spinners.configuringVsCode,
210
213
  });
211
- const commandWithAgent = buildCommandWithAgentString(mcpCommand, vscode);
214
+ const { stdout } = await execAsync('code --version');
215
+ const version = stdout.trim().split('\n')[0];
216
+ if (!semver.gte(version, MINIMUM_VSCODE_VERSION)) {
217
+ SpinniesManager.fail('vsCode', {
218
+ text: commands.mcp.setup.spinners.vsCodeVersionUnsupported,
219
+ });
220
+ return false;
221
+ }
212
222
  const configObject = {
213
223
  name: mcpServerName,
214
224
  ...commandWithAgent,
@@ -11,4 +11,5 @@ type MockErrorResponse = {
11
11
  };
12
12
  export declare function mockHubSpotHttpResponse<T>(data?: any): HubSpotPromise<T>;
13
13
  export declare function mockHubSpotHttpError(message: string, response: MockErrorResponse): HubSpotHttpError;
14
+ export declare function stubColorEnv(): void;
14
15
  export {};
package/lib/testUtils.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { vi } from 'vitest';
1
2
  import { HubSpotHttpError } from '@hubspot/local-dev-lib/models/HubSpotHttpError';
2
3
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
4
  export function mockHubSpotHttpResponse(data) {
@@ -16,3 +17,16 @@ export function mockHubSpotHttpError(message, response) {
16
17
  cause: { isAxiosError: true, response },
17
18
  });
18
19
  }
20
+ // Stubs the color-detection env to a color-capable interactive baseline.
21
+ // Call vi.unstubAllEnvs() in afterEach to restore.
22
+ export function stubColorEnv() {
23
+ [
24
+ 'CI',
25
+ 'NO_COLOR',
26
+ 'COLOR',
27
+ 'FORCE_COLOR',
28
+ 'COLORTERM',
29
+ 'TERM_PROGRAM',
30
+ ].forEach(key => vi.stubEnv(key, undefined));
31
+ vi.stubEnv('TERM', 'xterm-256color');
32
+ }
@@ -10,7 +10,7 @@ import { EXIT_CODES } from '../enums/exitCodes.js';
10
10
  const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = path.dirname(__filename);
12
12
  // cms-dev-server version to install to isolated cache
13
- const TARGET_CMS_DEV_SERVER_VERSION = '1.2.44';
13
+ const TARGET_CMS_DEV_SERVER_VERSION = '1.2.70';
14
14
  /**
15
15
  * Ensures cms-dev-server is installed in an isolated cache directory.
16
16
  * This prevents React version conflicts with the CLI.
@@ -13,6 +13,7 @@ declare class SpinniesManager {
13
13
  private currentFrameIndex;
14
14
  private spin;
15
15
  private sigintHandler;
16
+ private rawOutputCache;
16
17
  constructor();
17
18
  init(options?: Partial<SpinnerState>): void;
18
19
  private resetState;
@@ -12,6 +12,7 @@ import chalk from 'chalk';
12
12
  import cliCursor from 'cli-cursor';
13
13
  import { breakText, cleanStream, colorOptions, getLinesLength, prefixOptions, purgeSpinnerOptions, purgeSpinnersOptions, SPINNERS, writeStream, } from './spinniesUtils.js';
14
14
  import { isUnicodeSupported } from '@hubspot/local-dev-lib/isUnicodeSupported';
15
+ import { hasColorDisableSignal } from './supportsColor.js';
15
16
  import { uiLogger } from './logger.js';
16
17
  function safeColor(text, color) {
17
18
  const chalkFn = chalk[color];
@@ -31,6 +32,7 @@ class SpinniesManager {
31
32
  currentFrameIndex = 0;
32
33
  spin;
33
34
  sigintHandler = null;
35
+ rawOutputCache = {};
34
36
  constructor() {
35
37
  this.resetState();
36
38
  }
@@ -46,8 +48,8 @@ class SpinniesManager {
46
48
  this.spin =
47
49
  !this.options.disableSpins &&
48
50
  !process.env.CI &&
49
- process.stderr &&
50
- process.stderr.isTTY;
51
+ !hasColorDisableSignal() &&
52
+ Boolean(process.stderr?.isTTY);
51
53
  if (!this.hasAnySpinners()) {
52
54
  this.resetState();
53
55
  }
@@ -64,6 +66,7 @@ class SpinniesManager {
64
66
  this.stream = process.stderr;
65
67
  this.lineCount = 0;
66
68
  this.currentFrameIndex = 0;
69
+ this.rawOutputCache = {};
67
70
  }
68
71
  setDisableOutput(disableOutput) {
69
72
  this.disableOutput = disableOutput;
@@ -105,6 +108,7 @@ class SpinniesManager {
105
108
  return;
106
109
  }
107
110
  delete this.spinners[name];
111
+ delete this.rawOutputCache[name];
108
112
  // Update the spinner state to clean up the deleted spinner
109
113
  this.updateSpinnerState();
110
114
  }
@@ -230,8 +234,13 @@ class SpinniesManager {
230
234
  this.lineCount = linesLength.length;
231
235
  }
232
236
  setRawStreamOutput() {
233
- Object.values(this.spinners).forEach(i => {
234
- process.stderr.write(`- ${i.text}\n`);
237
+ Object.entries(this.spinners).forEach(([name, spinner]) => {
238
+ const line = `- ${spinner.text ?? ''}\n`;
239
+ if (this.rawOutputCache[name] === line) {
240
+ return;
241
+ }
242
+ this.rawOutputCache[name] = line;
243
+ process.stderr.write(line);
235
244
  });
236
245
  }
237
246
  checkIfActiveSpinners() {
@@ -246,6 +255,7 @@ class SpinniesManager {
246
255
  cliCursor.show();
247
256
  }
248
257
  this.spinners = {};
258
+ this.rawOutputCache = {};
249
259
  }
250
260
  }
251
261
  bindSigint() {
package/lib/ui/index.js CHANGED
@@ -2,7 +2,7 @@ import chalk from 'chalk';
2
2
  import { getConfigAccountIfExists, globalConfigFileExists, } from '@hubspot/local-dev-lib/config';
3
3
  import { uiLogger } from './logger.js';
4
4
  import { supportsHyperlinkModule } from './supportHyperlinks.js';
5
- import { supportsColor } from './supportsColor.js';
5
+ import { isColorEnabled } from './supportsColor.js';
6
6
  import { uiMessages } from './uiMessages.js';
7
7
  import { HUBSPOT_ACCOUNT_TYPE_STRINGS } from '@hubspot/local-dev-lib/constants/config';
8
8
  export const UI_COLORS = {
@@ -16,7 +16,7 @@ export function uiLine() {
16
16
  export function getTerminalUISupport() {
17
17
  return {
18
18
  hyperlinks: supportsHyperlinkModule.stdout,
19
- color: supportsColor.stdout.hasBasic,
19
+ color: isColorEnabled(),
20
20
  };
21
21
  }
22
22
  export function uiLink(linkText, url) {
@@ -4,6 +4,7 @@ interface ColorSupportLevel {
4
4
  has256: boolean;
5
5
  has16m: boolean;
6
6
  }
7
+ export declare function hasColorDisableSignal(): boolean;
7
8
  declare function createSupportsColor(stream: {
8
9
  isTTY?: boolean;
9
10
  } | null, options?: {
@@ -14,4 +15,7 @@ export declare const supportsColor: {
14
15
  stdout: ColorSupportLevel;
15
16
  stderr: ColorSupportLevel;
16
17
  };
18
+ export declare function isColorEnabled(stream?: {
19
+ isTTY?: boolean;
20
+ } | null): boolean;
17
21
  export {};
@@ -19,17 +19,29 @@ function translateLevel(level) {
19
19
  has16m: level >= 3,
20
20
  };
21
21
  }
22
+ function resolveColorPreference() {
23
+ const { NO_COLOR, COLOR, FORCE_COLOR, TERM } = env;
24
+ if (hasFlag('no-color') || hasFlag('noColor'))
25
+ return 'never';
26
+ if (NO_COLOR)
27
+ return 'never';
28
+ if (COLOR === 'false' || COLOR === '0')
29
+ return 'never';
30
+ if (FORCE_COLOR !== undefined) {
31
+ return FORCE_COLOR === '0' || FORCE_COLOR === 'false' ? 'never' : 'always';
32
+ }
33
+ if (TERM === 'dumb')
34
+ return 'never';
35
+ return 'auto';
36
+ }
37
+ export function hasColorDisableSignal() {
38
+ return resolveColorPreference() === 'never';
39
+ }
22
40
  function _supportsColor(haveStream, { streamIsTTY } = {}) {
23
41
  if (haveStream && !streamIsTTY) {
24
42
  return 0;
25
43
  }
26
44
  const min = 0;
27
- if (env.TERM === 'dumb') {
28
- return min;
29
- }
30
- if (hasFlag('noColor')) {
31
- return 0;
32
- }
33
45
  if (process.platform === 'win32') {
34
46
  // Windows 10 build 10586 is the first Windows release that supports 256 colors.
35
47
  // Windows 10 build 14931 is the first release that supports 16m/TrueColor.
@@ -95,3 +107,10 @@ export const supportsColor = {
95
107
  stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
96
108
  stderr: createSupportsColor({ isTTY: tty.isatty(2) }),
97
109
  };
110
+ export function isColorEnabled(stream = process.stdout) {
111
+ const preference = resolveColorPreference();
112
+ if (preference !== 'auto') {
113
+ return preference === 'always';
114
+ }
115
+ return createSupportsColor(stream, { streamIsTTY: stream?.isTTY }).hasBasic;
116
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "8.15.0-beta.1",
3
+ "version": "8.15.0-beta.2",
4
4
  "description": "The official CLI for developing on HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": "https://github.com/HubSpot/hubspot-cli",