@hubspot/cli 8.0.0 → 8.0.1-experimental.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.
- package/commands/mcp/__tests__/start.test.js +67 -1
- package/commands/mcp/start.js +19 -1
- package/lang/en.d.ts +1 -0
- package/lang/en.js +1 -0
- package/lib/mcp/__tests__/setup.test.js +17 -0
- package/lib/mcp/setup.d.ts +1 -0
- package/lib/mcp/setup.js +52 -14
- package/mcp-server/utils/__tests__/project.test.js +125 -0
- package/mcp-server/utils/project.js +8 -0
- package/package.json +1 -1
|
@@ -7,14 +7,20 @@ import * as errorHandlers from '../../../lib/errorHandlers/index.js';
|
|
|
7
7
|
import * as usageTrackingLib from '../../../lib/usageTracking.js';
|
|
8
8
|
import * as processLib from '../../../lib/process.js';
|
|
9
9
|
import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
|
|
10
|
-
|
|
10
|
+
// Create a mock execAsync function before importing the module
|
|
11
|
+
const execAsyncMock = vi.fn();
|
|
11
12
|
vi.mock('yargs');
|
|
12
13
|
vi.mock('../../../lib/commonOpts');
|
|
13
14
|
vi.mock('node:child_process');
|
|
15
|
+
vi.mock('node:util', () => ({
|
|
16
|
+
promisify: vi.fn(() => execAsyncMock),
|
|
17
|
+
}));
|
|
14
18
|
vi.mock('fs');
|
|
15
19
|
vi.mock('@hubspot/local-dev-lib/config');
|
|
16
20
|
vi.mock('../../../lib/errorHandlers/index.js');
|
|
17
21
|
vi.mock('../../../lib/process.js');
|
|
22
|
+
// Import after mocks are set up
|
|
23
|
+
const startCommand = await import('../start.js').then(m => m.default);
|
|
18
24
|
const spawnSpy = vi.mocked(spawn);
|
|
19
25
|
const existsSyncSpy = vi.spyOn(fs, 'existsSync');
|
|
20
26
|
const trackCommandUsageSpy = vi.spyOn(usageTrackingLib, 'trackCommandUsage');
|
|
@@ -36,6 +42,7 @@ describe('commands/mcp/start', () => {
|
|
|
36
42
|
processExitSpy.mockImplementation(() => { });
|
|
37
43
|
// Mock config to prevent reading actual config file in CI
|
|
38
44
|
getConfigAccountIfExistsSpy.mockReturnValue(undefined);
|
|
45
|
+
execAsyncMock.mockClear();
|
|
39
46
|
});
|
|
40
47
|
describe('command', () => {
|
|
41
48
|
it('should have the correct command structure', () => {
|
|
@@ -133,5 +140,64 @@ describe('commands/mcp/start', () => {
|
|
|
133
140
|
expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining('Failed to start'));
|
|
134
141
|
expect(logErrorSpy).toHaveBeenCalledWith(error);
|
|
135
142
|
});
|
|
143
|
+
it('should fetch CLI version in standalone mode', async () => {
|
|
144
|
+
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
145
|
+
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
146
|
+
execAsyncMock.mockResolvedValue({
|
|
147
|
+
stdout: '8.0.0',
|
|
148
|
+
stderr: '',
|
|
149
|
+
});
|
|
150
|
+
await startCommand.handler(args);
|
|
151
|
+
expect(execAsyncMock).toHaveBeenCalledWith('npm view @hubspot/cli version');
|
|
152
|
+
expect(spawnSpy).toHaveBeenCalledWith('node', expect.any(Array), expect.objectContaining({
|
|
153
|
+
env: expect.objectContaining({
|
|
154
|
+
HUBSPOT_CLI_VERSION: '8.0.0',
|
|
155
|
+
}),
|
|
156
|
+
}));
|
|
157
|
+
// Restore original env
|
|
158
|
+
if (originalEnv === undefined) {
|
|
159
|
+
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
it('should not fetch CLI version when not in standalone mode', async () => {
|
|
166
|
+
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
167
|
+
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
168
|
+
await startCommand.handler(args);
|
|
169
|
+
expect(execAsyncMock).not.toHaveBeenCalled();
|
|
170
|
+
expect(spawnSpy).toHaveBeenCalledWith('node', expect.any(Array), expect.objectContaining({
|
|
171
|
+
env: expect.not.objectContaining({
|
|
172
|
+
HUBSPOT_CLI_VERSION: expect.anything(),
|
|
173
|
+
}),
|
|
174
|
+
}));
|
|
175
|
+
// Restore original env
|
|
176
|
+
if (originalEnv !== undefined) {
|
|
177
|
+
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
it('should handle version fetch errors gracefully', async () => {
|
|
181
|
+
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
182
|
+
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
183
|
+
const error = new Error('Version fetch failed');
|
|
184
|
+
execAsyncMock.mockRejectedValue(error);
|
|
185
|
+
await startCommand.handler(args);
|
|
186
|
+
expect(execAsyncMock).toHaveBeenCalledWith('npm view @hubspot/cli version');
|
|
187
|
+
expect(uiLogger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to get CLI version'));
|
|
188
|
+
expect(logErrorSpy).toHaveBeenCalledWith(error);
|
|
189
|
+
expect(spawnSpy).toHaveBeenCalledWith('node', expect.any(Array), expect.objectContaining({
|
|
190
|
+
env: expect.not.objectContaining({
|
|
191
|
+
HUBSPOT_CLI_VERSION: expect.anything(),
|
|
192
|
+
}),
|
|
193
|
+
}));
|
|
194
|
+
// Restore original env
|
|
195
|
+
if (originalEnv === undefined) {
|
|
196
|
+
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
136
202
|
});
|
|
137
203
|
});
|
package/commands/mcp/start.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
1
|
+
import { spawn, exec } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import fs from 'fs';
|
|
4
5
|
import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
@@ -9,6 +10,7 @@ import { commands } from '../../lang/en.js';
|
|
|
9
10
|
import { handleExit } from '../../lib/process.js';
|
|
10
11
|
import { trackCommandUsage } from '../../lib/usageTracking.js';
|
|
11
12
|
import { fileURLToPath } from 'url';
|
|
13
|
+
const execAsync = promisify(exec);
|
|
12
14
|
const command = 'start';
|
|
13
15
|
const describe = undefined; // Leave hidden for now
|
|
14
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -28,12 +30,28 @@ async function startMcpServer(aiAgent) {
|
|
|
28
30
|
uiLogger.debug(commands.mcp.start.startingServer);
|
|
29
31
|
uiLogger.debug(commands.mcp.start.stopInstructions);
|
|
30
32
|
const args = [serverPath];
|
|
33
|
+
// Get CLI version if running in standalone mode
|
|
34
|
+
let cliVersion;
|
|
35
|
+
if (process.env.HUBSPOT_MCP_STANDALONE === 'true') {
|
|
36
|
+
try {
|
|
37
|
+
// const { stdout } = await execAsync('npm view @hubspot/cli version');
|
|
38
|
+
// // Extract version number from output (e.g., "8.0.0")
|
|
39
|
+
// cliVersion = stdout.trim();
|
|
40
|
+
cliVersion = '8.0.1-experimental.0';
|
|
41
|
+
uiLogger.debug(`Using @hubspot/cli version: ${cliVersion}`);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
uiLogger.warn('Failed to get CLI version, will use latest');
|
|
45
|
+
logError(error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
31
48
|
// Start the server using ts-node
|
|
32
49
|
const child = spawn(`node`, args, {
|
|
33
50
|
stdio: 'inherit',
|
|
34
51
|
env: {
|
|
35
52
|
...process.env,
|
|
36
53
|
HUBSPOT_MCP_AI_AGENT: aiAgent || 'unknown',
|
|
54
|
+
...(cliVersion && { HUBSPOT_CLI_VERSION: cliVersion }),
|
|
37
55
|
},
|
|
38
56
|
});
|
|
39
57
|
// Handle server process events
|
package/lang/en.d.ts
CHANGED
package/lang/en.js
CHANGED
|
@@ -1262,6 +1262,7 @@ export const commands = {
|
|
|
1262
1262
|
prompts: {
|
|
1263
1263
|
targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
|
|
1264
1264
|
targetsRequired: 'Must choose at least one app to configure.',
|
|
1265
|
+
standaloneMode: 'Do you want to run in standalone mode? (This will use npx @hubspot/cli instead of local hs command)',
|
|
1265
1266
|
},
|
|
1266
1267
|
},
|
|
1267
1268
|
start: {
|
|
@@ -132,6 +132,23 @@ describe('lib/mcp/setup', () => {
|
|
|
132
132
|
});
|
|
133
133
|
expect(mockedLogError).toHaveBeenCalledWith(error);
|
|
134
134
|
});
|
|
135
|
+
it('should pass through environment variables in command', async () => {
|
|
136
|
+
const mockMcpCommandWithEnv = {
|
|
137
|
+
command: 'test-command',
|
|
138
|
+
args: ['--arg1'],
|
|
139
|
+
env: { HUBSPOT_MCP_STANDALONE: 'true' },
|
|
140
|
+
};
|
|
141
|
+
mockedExecAsync.mockResolvedValueOnce({
|
|
142
|
+
stdout: 'codex version 1.0.0',
|
|
143
|
+
stderr: '',
|
|
144
|
+
});
|
|
145
|
+
mockedExecAsync.mockResolvedValueOnce({ stdout: '', stderr: '' });
|
|
146
|
+
const result = await setupCodex(mockMcpCommandWithEnv);
|
|
147
|
+
expect(result).toBe(true);
|
|
148
|
+
// The env is passed through buildCommandWithAgentString, but not used in the command string
|
|
149
|
+
// This is expected as env vars are set by the client when starting the MCP server
|
|
150
|
+
expect(mockedExecAsync).toHaveBeenCalledWith('codex mcp add "HubSpotDev" -- test-command --arg1 --ai-agent codex');
|
|
151
|
+
});
|
|
135
152
|
});
|
|
136
153
|
describe('setupGemini', () => {
|
|
137
154
|
const mockMcpCommand = {
|
package/lib/mcp/setup.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare const supportedTools: {
|
|
|
5
5
|
interface McpCommand {
|
|
6
6
|
command: string;
|
|
7
7
|
args: string[];
|
|
8
|
+
env?: Record<string, string>;
|
|
8
9
|
}
|
|
9
10
|
export declare function addMcpServerToConfig(targets: string[] | undefined): Promise<string[]>;
|
|
10
11
|
export declare function setupVsCode(mcpCommand?: McpCommand): Promise<boolean>;
|
package/lib/mcp/setup.js
CHANGED
|
@@ -47,23 +47,44 @@ export async function addMcpServerToConfig(targets) {
|
|
|
47
47
|
else {
|
|
48
48
|
derivedTargets = targets;
|
|
49
49
|
}
|
|
50
|
+
// Prompt for standalone mode
|
|
51
|
+
const { useStandaloneMode } = await promptUser({
|
|
52
|
+
name: 'useStandaloneMode',
|
|
53
|
+
type: 'confirm',
|
|
54
|
+
message: commands.mcp.setup.prompts.standaloneMode,
|
|
55
|
+
default: false,
|
|
56
|
+
});
|
|
57
|
+
const mcpCommand = useStandaloneMode
|
|
58
|
+
? {
|
|
59
|
+
command: 'npx',
|
|
60
|
+
args: [
|
|
61
|
+
'-y',
|
|
62
|
+
'-p',
|
|
63
|
+
'@hubspot/cli@8.0.1-experimental.0',
|
|
64
|
+
'hs',
|
|
65
|
+
'mcp',
|
|
66
|
+
'start',
|
|
67
|
+
],
|
|
68
|
+
env: { HUBSPOT_MCP_STANDALONE: 'true' },
|
|
69
|
+
}
|
|
70
|
+
: defaultMcpCommand;
|
|
50
71
|
if (derivedTargets.includes(claudeCode)) {
|
|
51
|
-
await runSetupFunction(setupClaudeCode);
|
|
72
|
+
await runSetupFunction(() => setupClaudeCode(mcpCommand));
|
|
52
73
|
}
|
|
53
74
|
if (derivedTargets.includes(cursor)) {
|
|
54
|
-
await runSetupFunction(setupCursor);
|
|
75
|
+
await runSetupFunction(() => setupCursor(mcpCommand));
|
|
55
76
|
}
|
|
56
77
|
if (derivedTargets.includes(windsurf)) {
|
|
57
|
-
await runSetupFunction(setupWindsurf);
|
|
78
|
+
await runSetupFunction(() => setupWindsurf(mcpCommand));
|
|
58
79
|
}
|
|
59
80
|
if (derivedTargets.includes(vscode)) {
|
|
60
|
-
await runSetupFunction(setupVsCode);
|
|
81
|
+
await runSetupFunction(() => setupVsCode(mcpCommand));
|
|
61
82
|
}
|
|
62
83
|
if (derivedTargets.includes(codex)) {
|
|
63
|
-
await runSetupFunction(setupCodex);
|
|
84
|
+
await runSetupFunction(() => setupCodex(mcpCommand));
|
|
64
85
|
}
|
|
65
86
|
if (derivedTargets.includes(gemini)) {
|
|
66
|
-
await runSetupFunction(setupGemini);
|
|
87
|
+
await runSetupFunction(() => setupGemini(mcpCommand));
|
|
67
88
|
}
|
|
68
89
|
uiLogger.info(commands.mcp.setup.success(derivedTargets));
|
|
69
90
|
return derivedTargets;
|
|
@@ -122,9 +143,14 @@ function setupMcpConfigFile(config) {
|
|
|
122
143
|
mcpConfig.mcpServers = {};
|
|
123
144
|
}
|
|
124
145
|
// Add or update HubSpot CLI MCP server
|
|
125
|
-
|
|
126
|
-
|
|
146
|
+
const serverConfig = {
|
|
147
|
+
command: config.mcpCommand.command,
|
|
148
|
+
args: config.mcpCommand.args,
|
|
127
149
|
};
|
|
150
|
+
if (config.mcpCommand.env) {
|
|
151
|
+
serverConfig.env = config.mcpCommand.env;
|
|
152
|
+
}
|
|
153
|
+
mcpConfig.mcpServers[mcpServerName] = serverConfig;
|
|
128
154
|
// Write the updated config
|
|
129
155
|
fs.writeFileSync(config.configPath, JSON.stringify(mcpConfig, null, 2));
|
|
130
156
|
SpinniesManager.succeed('spinner', {
|
|
@@ -145,10 +171,16 @@ export async function setupVsCode(mcpCommand = defaultMcpCommand) {
|
|
|
145
171
|
SpinniesManager.add('vsCode', {
|
|
146
172
|
text: commands.mcp.setup.spinners.configuringVsCode,
|
|
147
173
|
});
|
|
148
|
-
const
|
|
174
|
+
const commandWithAgent = buildCommandWithAgentString(mcpCommand, vscode);
|
|
175
|
+
const configObject = {
|
|
149
176
|
name: mcpServerName,
|
|
150
|
-
|
|
151
|
-
|
|
177
|
+
command: commandWithAgent.command,
|
|
178
|
+
args: commandWithAgent.args,
|
|
179
|
+
};
|
|
180
|
+
if (commandWithAgent.env) {
|
|
181
|
+
configObject.env = commandWithAgent.env;
|
|
182
|
+
}
|
|
183
|
+
const mcpConfig = JSON.stringify(configObject);
|
|
152
184
|
await execAsync(`code --add-mcp ${JSON.stringify(mcpConfig)}`);
|
|
153
185
|
SpinniesManager.succeed('vsCode', {
|
|
154
186
|
text: commands.mcp.setup.spinners.configuredVsCode,
|
|
@@ -180,10 +212,16 @@ export async function setupClaudeCode(mcpCommand = defaultMcpCommand) {
|
|
|
180
212
|
// Check if claude command is available
|
|
181
213
|
await execAsync('claude --version');
|
|
182
214
|
// Run claude mcp add command
|
|
183
|
-
const
|
|
215
|
+
const commandWithAgent = buildCommandWithAgentString(mcpCommand, claudeCode);
|
|
216
|
+
const configObject = {
|
|
184
217
|
type: 'stdio',
|
|
185
|
-
|
|
186
|
-
|
|
218
|
+
command: commandWithAgent.command,
|
|
219
|
+
args: commandWithAgent.args,
|
|
220
|
+
};
|
|
221
|
+
if (commandWithAgent.env) {
|
|
222
|
+
configObject.env = commandWithAgent.env;
|
|
223
|
+
}
|
|
224
|
+
const mcpConfig = JSON.stringify(configObject);
|
|
187
225
|
const { stdout } = await execAsync('claude mcp list');
|
|
188
226
|
if (stdout.includes(mcpServerName)) {
|
|
189
227
|
SpinniesManager.update('claudeCode', {
|
|
@@ -136,5 +136,130 @@ describe('mcp-server/utils/project', () => {
|
|
|
136
136
|
env: expect.any(Object),
|
|
137
137
|
}));
|
|
138
138
|
});
|
|
139
|
+
it('should use npx -p @hubspot/cli when HUBSPOT_MCP_STANDALONE is true', async () => {
|
|
140
|
+
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
141
|
+
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
142
|
+
const hsCommand = 'hs project upload';
|
|
143
|
+
const expectedResult = {
|
|
144
|
+
stdout: 'success',
|
|
145
|
+
stderr: '',
|
|
146
|
+
};
|
|
147
|
+
mockExistsSync.mockReturnValue(true);
|
|
148
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
149
|
+
await runCommandInDir(mockDirectory, hsCommand);
|
|
150
|
+
expect(mockExecAsync).toHaveBeenCalledWith('npx -p @hubspot/cli hs project upload --disable-usage-tracking "true"', expect.objectContaining({
|
|
151
|
+
cwd: mockResolvedPath,
|
|
152
|
+
env: expect.any(Object),
|
|
153
|
+
}));
|
|
154
|
+
// Restore original env
|
|
155
|
+
if (originalEnv === undefined) {
|
|
156
|
+
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
it('should use regular hs command when HUBSPOT_MCP_STANDALONE is not set', async () => {
|
|
163
|
+
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
164
|
+
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
165
|
+
const hsCommand = 'hs project upload';
|
|
166
|
+
const expectedResult = {
|
|
167
|
+
stdout: 'success',
|
|
168
|
+
stderr: '',
|
|
169
|
+
};
|
|
170
|
+
mockExistsSync.mockReturnValue(true);
|
|
171
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
172
|
+
await runCommandInDir(mockDirectory, hsCommand);
|
|
173
|
+
expect(mockExecAsync).toHaveBeenCalledWith('hs project upload --disable-usage-tracking "true"', expect.objectContaining({
|
|
174
|
+
cwd: mockResolvedPath,
|
|
175
|
+
env: expect.any(Object),
|
|
176
|
+
}));
|
|
177
|
+
// Restore original env
|
|
178
|
+
if (originalEnv !== undefined) {
|
|
179
|
+
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
it('should use npx -p @hubspot/cli for hs commands with flags in standalone mode', async () => {
|
|
183
|
+
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
184
|
+
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
185
|
+
const hsCommand = 'hs project upload --profile prod';
|
|
186
|
+
const expectedResult = {
|
|
187
|
+
stdout: 'success',
|
|
188
|
+
stderr: '',
|
|
189
|
+
};
|
|
190
|
+
mockExistsSync.mockReturnValue(true);
|
|
191
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
192
|
+
await runCommandInDir(mockDirectory, hsCommand);
|
|
193
|
+
expect(mockExecAsync).toHaveBeenCalledWith('npx -p @hubspot/cli hs project upload --profile prod --disable-usage-tracking "true"', expect.objectContaining({
|
|
194
|
+
cwd: mockResolvedPath,
|
|
195
|
+
env: expect.any(Object),
|
|
196
|
+
}));
|
|
197
|
+
// Restore original env
|
|
198
|
+
if (originalEnv === undefined) {
|
|
199
|
+
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
it('should use pinned CLI version when HUBSPOT_CLI_VERSION is set in standalone mode', async () => {
|
|
206
|
+
const originalStandaloneEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
207
|
+
const originalVersionEnv = process.env.HUBSPOT_CLI_VERSION;
|
|
208
|
+
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
209
|
+
process.env.HUBSPOT_CLI_VERSION = '8.0.0';
|
|
210
|
+
const hsCommand = 'hs project upload';
|
|
211
|
+
const expectedResult = {
|
|
212
|
+
stdout: 'success',
|
|
213
|
+
stderr: '',
|
|
214
|
+
};
|
|
215
|
+
mockExistsSync.mockReturnValue(true);
|
|
216
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
217
|
+
await runCommandInDir(mockDirectory, hsCommand);
|
|
218
|
+
expect(mockExecAsync).toHaveBeenCalledWith('npx -p @hubspot/cli@8.0.0 hs project upload --disable-usage-tracking "true"', expect.objectContaining({
|
|
219
|
+
cwd: mockResolvedPath,
|
|
220
|
+
env: expect.any(Object),
|
|
221
|
+
}));
|
|
222
|
+
// Restore original env
|
|
223
|
+
if (originalStandaloneEnv === undefined) {
|
|
224
|
+
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
process.env.HUBSPOT_MCP_STANDALONE = originalStandaloneEnv;
|
|
228
|
+
}
|
|
229
|
+
if (originalVersionEnv === undefined) {
|
|
230
|
+
delete process.env.HUBSPOT_CLI_VERSION;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
process.env.HUBSPOT_CLI_VERSION = originalVersionEnv;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
it('should use latest CLI version when HUBSPOT_CLI_VERSION is not set in standalone mode', async () => {
|
|
237
|
+
const originalStandaloneEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
238
|
+
const originalVersionEnv = process.env.HUBSPOT_CLI_VERSION;
|
|
239
|
+
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
240
|
+
delete process.env.HUBSPOT_CLI_VERSION;
|
|
241
|
+
const hsCommand = 'hs project upload';
|
|
242
|
+
const expectedResult = {
|
|
243
|
+
stdout: 'success',
|
|
244
|
+
stderr: '',
|
|
245
|
+
};
|
|
246
|
+
mockExistsSync.mockReturnValue(true);
|
|
247
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
248
|
+
await runCommandInDir(mockDirectory, hsCommand);
|
|
249
|
+
expect(mockExecAsync).toHaveBeenCalledWith('npx -p @hubspot/cli hs project upload --disable-usage-tracking "true"', expect.objectContaining({
|
|
250
|
+
cwd: mockResolvedPath,
|
|
251
|
+
env: expect.any(Object),
|
|
252
|
+
}));
|
|
253
|
+
// Restore original env
|
|
254
|
+
if (originalStandaloneEnv === undefined) {
|
|
255
|
+
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
process.env.HUBSPOT_MCP_STANDALONE = originalStandaloneEnv;
|
|
259
|
+
}
|
|
260
|
+
if (originalVersionEnv !== undefined) {
|
|
261
|
+
process.env.HUBSPOT_CLI_VERSION = originalVersionEnv;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
139
264
|
});
|
|
140
265
|
});
|
|
@@ -7,6 +7,14 @@ export async function runCommandInDir(directory, command) {
|
|
|
7
7
|
}
|
|
8
8
|
let finalCommand = command;
|
|
9
9
|
if (command.startsWith('hs ')) {
|
|
10
|
+
// Check if running in standalone mode
|
|
11
|
+
if (process.env.HUBSPOT_MCP_STANDALONE === 'true') {
|
|
12
|
+
// Use pinned version if available, otherwise use latest
|
|
13
|
+
const cliPackage = process.env.HUBSPOT_CLI_VERSION
|
|
14
|
+
? `@hubspot/cli@8.0.1-experimental.0`
|
|
15
|
+
: '@hubspot/cli';
|
|
16
|
+
finalCommand = command.replace(/^hs /, `npx -y -p ${cliPackage} hs `);
|
|
17
|
+
}
|
|
10
18
|
finalCommand = addFlag(finalCommand, 'disable-usage-tracking', true);
|
|
11
19
|
}
|
|
12
20
|
return execAsync(finalCommand, {
|