@hubspot/cli 8.0.8-experimental.5 → 8.0.8-experimental.6
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 +1 -8
- package/commands/mcp/start.js +1 -0
- package/lang/en.d.ts +0 -2
- package/lang/en.js +0 -2
- package/lib/mcp/__tests__/setup.test.js +0 -15
- package/lib/mcp/setup.d.ts +0 -1
- package/lib/mcp/setup.js +30 -77
- package/mcp-server/tools/cms/HsCreateFunctionTool.js +1 -1
- package/mcp-server/tools/cms/HsCreateModuleTool.js +1 -1
- package/mcp-server/tools/cms/HsCreateTemplateTool.js +1 -1
- package/mcp-server/tools/cms/HsFunctionLogsTool.js +1 -1
- package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
- package/mcp-server/tools/cms/HsListTool.js +1 -1
- package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +2 -1
- package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +2 -2
- package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +2 -2
- package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +2 -2
- package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +2 -2
- package/mcp-server/tools/cms/__tests__/HsListTool.test.js +2 -2
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +1 -1
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +1 -1
- package/mcp-server/tools/project/CreateProjectTool.d.ts +1 -1
- package/mcp-server/tools/project/CreateProjectTool.js +1 -1
- package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
- package/mcp-server/tools/project/DeployProjectTool.js +1 -1
- package/mcp-server/tools/project/UploadProjectTools.js +1 -1
- package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
- package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +2 -2
- package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +2 -2
- package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +2 -2
- package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +2 -2
- package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +2 -10
- package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
- package/mcp-server/tools/project/constants.d.ts +1 -1
- package/mcp-server/utils/__tests__/command.test.js +3 -233
- package/mcp-server/utils/__tests__/project.test.d.ts +1 -0
- package/mcp-server/utils/__tests__/project.test.js +140 -0
- package/mcp-server/utils/command.d.ts +0 -5
- package/mcp-server/utils/command.js +0 -24
- package/mcp-server/utils/project.d.ts +5 -0
- package/mcp-server/utils/project.js +18 -0
- package/package.json +2 -2
|
@@ -7,20 +7,14 @@ 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
|
-
|
|
11
|
-
const execAsyncMock = vi.fn();
|
|
10
|
+
import startCommand from '../start.js';
|
|
12
11
|
vi.mock('yargs');
|
|
13
12
|
vi.mock('../../../lib/commonOpts');
|
|
14
13
|
vi.mock('node:child_process');
|
|
15
|
-
vi.mock('node:util', () => ({
|
|
16
|
-
promisify: vi.fn(() => execAsyncMock),
|
|
17
|
-
}));
|
|
18
14
|
vi.mock('fs');
|
|
19
15
|
vi.mock('@hubspot/local-dev-lib/config');
|
|
20
16
|
vi.mock('../../../lib/errorHandlers/index.js');
|
|
21
17
|
vi.mock('../../../lib/process.js');
|
|
22
|
-
// Import after mocks are set up
|
|
23
|
-
const startCommand = await import('../start.js').then(m => m.default);
|
|
24
18
|
const spawnSpy = vi.mocked(spawn);
|
|
25
19
|
const existsSyncSpy = vi.spyOn(fs, 'existsSync');
|
|
26
20
|
const trackCommandUsageSpy = vi.spyOn(usageTrackingLib, 'trackCommandUsage');
|
|
@@ -42,7 +36,6 @@ describe('commands/mcp/start', () => {
|
|
|
42
36
|
processExitSpy.mockImplementation(() => { });
|
|
43
37
|
// Mock config to prevent reading actual config file in CI
|
|
44
38
|
getConfigAccountIfExistsSpy.mockReturnValue(undefined);
|
|
45
|
-
execAsyncMock.mockClear();
|
|
46
39
|
});
|
|
47
40
|
describe('command', () => {
|
|
48
41
|
it('should have the correct command structure', () => {
|
package/commands/mcp/start.js
CHANGED
|
@@ -28,6 +28,7 @@ async function startMcpServer(aiAgent) {
|
|
|
28
28
|
uiLogger.debug(commands.mcp.start.startingServer);
|
|
29
29
|
uiLogger.debug(commands.mcp.start.stopInstructions);
|
|
30
30
|
const args = [serverPath];
|
|
31
|
+
// Start the server using ts-node
|
|
31
32
|
const child = spawn(`node`, args, {
|
|
32
33
|
stdio: 'inherit',
|
|
33
34
|
env: {
|
package/lang/en.d.ts
CHANGED
package/lang/en.js
CHANGED
|
@@ -1311,8 +1311,6 @@ export const commands = {
|
|
|
1311
1311
|
prompts: {
|
|
1312
1312
|
targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
|
|
1313
1313
|
targetsRequired: 'Must choose at least one app to configure.',
|
|
1314
|
-
standaloneMode: 'Do you want to run in standalone mode? (This will use npx @hubspot/cli instead of the installed hs command)',
|
|
1315
|
-
cliVersion: 'Specify a CLI version to pin (leave blank for latest):',
|
|
1316
1314
|
},
|
|
1317
1315
|
},
|
|
1318
1316
|
start: {
|
|
@@ -132,21 +132,6 @@ 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
|
-
expect(mockedExecAsync).toHaveBeenCalledWith('codex mcp add "HubSpotDev" --env HUBSPOT_MCP_STANDALONE=true -- test-command --arg1 --ai-agent codex');
|
|
149
|
-
});
|
|
150
135
|
});
|
|
151
136
|
describe('setupGemini', () => {
|
|
152
137
|
const mockMcpCommand = {
|
package/lib/mcp/setup.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ export declare const supportedTools: {
|
|
|
5
5
|
interface McpCommand {
|
|
6
6
|
command: string;
|
|
7
7
|
args: string[];
|
|
8
|
-
env?: Record<string, string>;
|
|
9
8
|
}
|
|
10
9
|
export declare function addMcpServerToConfig(targets: string[] | undefined): Promise<string[]>;
|
|
11
10
|
export declare function setupVsCode(mcpCommand?: McpCommand): Promise<boolean>;
|
package/lib/mcp/setup.js
CHANGED
|
@@ -47,56 +47,23 @@ 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 { cliVersion } = useStandaloneMode
|
|
58
|
-
? await promptUser({
|
|
59
|
-
name: 'cliVersion',
|
|
60
|
-
type: 'input',
|
|
61
|
-
message: commands.mcp.setup.prompts.cliVersion,
|
|
62
|
-
validate: (v) => !v || /^[\d]+\.[\d]+\.[\d]+([-+][\w.]+)?$/.test(v.trim())
|
|
63
|
-
? true
|
|
64
|
-
: 'Please enter a valid semver version (e.g. 8.0.1) or leave blank for latest.',
|
|
65
|
-
})
|
|
66
|
-
: { cliVersion: '' };
|
|
67
|
-
const cliPackage = cliVersion
|
|
68
|
-
? `@hubspot/cli@${cliVersion}`
|
|
69
|
-
: '@hubspot/cli';
|
|
70
|
-
const standaloneEnv = {
|
|
71
|
-
HUBSPOT_MCP_STANDALONE: 'true',
|
|
72
|
-
};
|
|
73
|
-
if (cliVersion) {
|
|
74
|
-
standaloneEnv.HUBSPOT_CLI_VERSION = cliVersion;
|
|
75
|
-
}
|
|
76
|
-
const mcpCommand = useStandaloneMode
|
|
77
|
-
? {
|
|
78
|
-
command: 'npx',
|
|
79
|
-
args: ['-y', '-p', cliPackage, 'hs', 'mcp', 'start'],
|
|
80
|
-
env: standaloneEnv,
|
|
81
|
-
}
|
|
82
|
-
: defaultMcpCommand;
|
|
83
50
|
if (derivedTargets.includes(claudeCode)) {
|
|
84
|
-
await runSetupFunction(
|
|
51
|
+
await runSetupFunction(setupClaudeCode);
|
|
85
52
|
}
|
|
86
53
|
if (derivedTargets.includes(cursor)) {
|
|
87
|
-
await runSetupFunction(
|
|
54
|
+
await runSetupFunction(setupCursor);
|
|
88
55
|
}
|
|
89
56
|
if (derivedTargets.includes(windsurf)) {
|
|
90
|
-
await runSetupFunction(
|
|
57
|
+
await runSetupFunction(setupWindsurf);
|
|
91
58
|
}
|
|
92
59
|
if (derivedTargets.includes(vscode)) {
|
|
93
|
-
await runSetupFunction(
|
|
60
|
+
await runSetupFunction(setupVsCode);
|
|
94
61
|
}
|
|
95
62
|
if (derivedTargets.includes(codex)) {
|
|
96
|
-
await runSetupFunction(
|
|
63
|
+
await runSetupFunction(setupCodex);
|
|
97
64
|
}
|
|
98
65
|
if (derivedTargets.includes(gemini)) {
|
|
99
|
-
await runSetupFunction(
|
|
66
|
+
await runSetupFunction(setupGemini);
|
|
100
67
|
}
|
|
101
68
|
uiLogger.info(commands.mcp.setup.success(derivedTargets));
|
|
102
69
|
return derivedTargets;
|
|
@@ -154,7 +121,10 @@ function setupMcpConfigFile(config) {
|
|
|
154
121
|
if (!mcpConfig.mcpServers) {
|
|
155
122
|
mcpConfig.mcpServers = {};
|
|
156
123
|
}
|
|
157
|
-
|
|
124
|
+
// Add or update HubSpot CLI MCP server
|
|
125
|
+
mcpConfig.mcpServers[mcpServerName] = {
|
|
126
|
+
...config.mcpCommand,
|
|
127
|
+
};
|
|
158
128
|
// Write the updated config
|
|
159
129
|
fs.writeFileSync(config.configPath, JSON.stringify(mcpConfig, null, 2));
|
|
160
130
|
SpinniesManager.succeed('spinner', {
|
|
@@ -175,12 +145,10 @@ export async function setupVsCode(mcpCommand = defaultMcpCommand) {
|
|
|
175
145
|
SpinniesManager.add('vsCode', {
|
|
176
146
|
text: commands.mcp.setup.spinners.configuringVsCode,
|
|
177
147
|
});
|
|
178
|
-
const
|
|
179
|
-
const configObject = {
|
|
148
|
+
const mcpConfig = JSON.stringify({
|
|
180
149
|
name: mcpServerName,
|
|
181
|
-
...
|
|
182
|
-
};
|
|
183
|
-
const mcpConfig = JSON.stringify(configObject);
|
|
150
|
+
...buildCommandWithAgentString(mcpCommand, vscode),
|
|
151
|
+
});
|
|
184
152
|
await execAsync(`code --add-mcp ${JSON.stringify(mcpConfig)}`);
|
|
185
153
|
SpinniesManager.succeed('vsCode', {
|
|
186
154
|
text: commands.mcp.setup.spinners.configuredVsCode,
|
|
@@ -204,27 +172,25 @@ export async function setupVsCode(mcpCommand = defaultMcpCommand) {
|
|
|
204
172
|
}
|
|
205
173
|
}
|
|
206
174
|
export async function setupClaudeCode(mcpCommand = defaultMcpCommand) {
|
|
207
|
-
SpinniesManager.add('claudeCode', {
|
|
208
|
-
text: commands.mcp.setup.spinners.configuringClaudeCode,
|
|
209
|
-
});
|
|
210
175
|
try {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
catch (e) {
|
|
215
|
-
SpinniesManager.fail('claudeCode', {
|
|
216
|
-
text: commands.mcp.setup.spinners.claudeCodeNotFound,
|
|
176
|
+
SpinniesManager.add('claudeCode', {
|
|
177
|
+
text: commands.mcp.setup.spinners.configuringClaudeCode,
|
|
217
178
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
179
|
+
try {
|
|
180
|
+
// Check if claude command is available
|
|
181
|
+
await execAsync('claude --version');
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
SpinniesManager.fail('claudeCode', {
|
|
185
|
+
text: commands.mcp.setup.spinners.claudeCodeNotFound,
|
|
186
|
+
});
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
221
189
|
// Run claude mcp add command
|
|
222
|
-
const
|
|
223
|
-
const configObject = {
|
|
190
|
+
const mcpConfig = JSON.stringify({
|
|
224
191
|
type: 'stdio',
|
|
225
|
-
...
|
|
226
|
-
};
|
|
227
|
-
const mcpConfig = JSON.stringify(configObject);
|
|
192
|
+
...buildCommandWithAgentString(mcpCommand, claudeCode),
|
|
193
|
+
});
|
|
228
194
|
const { stdout } = await execAsync('claude mcp list');
|
|
229
195
|
if (stdout.includes(mcpServerName)) {
|
|
230
196
|
SpinniesManager.update('claudeCode', {
|
|
@@ -282,7 +248,7 @@ export async function setupCodex(mcpCommand = defaultMcpCommand) {
|
|
|
282
248
|
return false;
|
|
283
249
|
}
|
|
284
250
|
const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, codex);
|
|
285
|
-
await execAsync(`codex mcp add "${mcpServerName}"
|
|
251
|
+
await execAsync(`codex mcp add "${mcpServerName}" -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
|
|
286
252
|
SpinniesManager.succeed('codexSpinner', {
|
|
287
253
|
text: commands.mcp.setup.spinners.configuredCodex,
|
|
288
254
|
});
|
|
@@ -311,7 +277,7 @@ export async function setupGemini(mcpCommand = defaultMcpCommand) {
|
|
|
311
277
|
return false;
|
|
312
278
|
}
|
|
313
279
|
const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, gemini);
|
|
314
|
-
await execAsync(`gemini mcp add -s user
|
|
280
|
+
await execAsync(`gemini mcp add -s user "${mcpServerName}" ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
|
|
315
281
|
SpinniesManager.succeed('geminiSpinner', {
|
|
316
282
|
text: commands.mcp.setup.spinners.configuredGemini,
|
|
317
283
|
});
|
|
@@ -330,16 +296,3 @@ function buildCommandWithAgentString(mcpCommand, agent) {
|
|
|
330
296
|
mcpCommandCopy.args.push('--ai-agent', agent);
|
|
331
297
|
return mcpCommandCopy;
|
|
332
298
|
}
|
|
333
|
-
function buildEnvFlagString(mcpCommand) {
|
|
334
|
-
const envFlags = [];
|
|
335
|
-
if (mcpCommand.env) {
|
|
336
|
-
const env = Object.entries(mcpCommand.env);
|
|
337
|
-
env.forEach(([key, value]) => {
|
|
338
|
-
envFlags.push(`--env ${key}=${value}`);
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
if (envFlags.length === 0) {
|
|
342
|
-
return '';
|
|
343
|
-
}
|
|
344
|
-
return ` ${envFlags.join(' ')}`;
|
|
345
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Tool } from '../../types.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
|
|
4
|
-
import { runCommandInDir } from '../../utils/
|
|
4
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
5
5
|
import { formatTextContents, formatTextContent } from '../../utils/content.js';
|
|
6
6
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
7
7
|
import { addFlag } from '../../utils/command.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Tool } from '../../types.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
|
|
4
|
-
import { runCommandInDir } from '../../utils/
|
|
4
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
5
5
|
import { formatTextContents, formatTextContent } from '../../utils/content.js';
|
|
6
6
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
7
7
|
import { addFlag } from '../../utils/command.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Tool } from '../../types.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
|
|
4
|
-
import { runCommandInDir } from '../../utils/
|
|
4
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
5
5
|
import { formatTextContents, formatTextContent } from '../../utils/content.js';
|
|
6
6
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
7
7
|
import { addFlag } from '../../utils/command.js';
|
|
@@ -2,7 +2,7 @@ import { Tool } from '../../types.js';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { addFlag } from '../../utils/command.js';
|
|
4
4
|
import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
|
|
5
|
-
import { runCommandInDir } from '../../utils/
|
|
5
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
6
6
|
import { formatTextContents } from '../../utils/content.js';
|
|
7
7
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
8
8
|
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
@@ -2,7 +2,7 @@ import { Tool } from '../../types.js';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { addFlag } from '../../utils/command.js';
|
|
4
4
|
import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
|
|
5
|
-
import { runCommandInDir } from '../../utils/
|
|
5
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
6
6
|
import { formatTextContents } from '../../utils/content.js';
|
|
7
7
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
8
8
|
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
@@ -2,7 +2,7 @@ import { Tool } from '../../types.js';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { addFlag } from '../../utils/command.js';
|
|
4
4
|
import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
|
|
5
|
-
import { runCommandInDir } from '../../utils/
|
|
5
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
6
6
|
import { formatTextContents } from '../../utils/content.js';
|
|
7
7
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
8
8
|
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { HsCreateFunctionTool } from '../HsCreateFunctionTool.js';
|
|
3
|
-
import { runCommandInDir } from '../../../utils/
|
|
3
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
4
4
|
import { addFlag } from '../../../utils/command.js';
|
|
5
5
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
6
6
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
7
7
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
8
|
+
vi.mock('../../../utils/project');
|
|
8
9
|
vi.mock('../../../utils/command');
|
|
9
10
|
vi.mock('../../../utils/toolUsageTracking');
|
|
10
11
|
vi.mock('../../../utils/feedbackTracking');
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { HsCreateModuleTool } from '../HsCreateModuleTool.js';
|
|
3
|
-
import { runCommandInDir } from '../../../utils/
|
|
3
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
4
4
|
import { addFlag } from '../../../utils/command.js';
|
|
5
5
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
6
6
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
7
7
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
8
|
-
vi.mock('../../../utils/
|
|
8
|
+
vi.mock('../../../utils/project');
|
|
9
9
|
vi.mock('../../../utils/command');
|
|
10
10
|
vi.mock('../../../utils/toolUsageTracking');
|
|
11
11
|
vi.mock('../../../utils/feedbackTracking');
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { HsCreateTemplateTool } from '../HsCreateTemplateTool.js';
|
|
3
|
-
import { runCommandInDir } from '../../../utils/
|
|
3
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
4
4
|
import { addFlag } from '../../../utils/command.js';
|
|
5
5
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
6
6
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
7
7
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
8
|
-
vi.mock('../../../utils/
|
|
8
|
+
vi.mock('../../../utils/project');
|
|
9
9
|
vi.mock('../../../utils/command');
|
|
10
10
|
vi.mock('../../../utils/toolUsageTracking');
|
|
11
11
|
vi.mock('../../../utils/feedbackTracking');
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { HsFunctionLogsTool } from '../HsFunctionLogsTool.js';
|
|
3
|
-
import { runCommandInDir } from '../../../utils/
|
|
3
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
4
4
|
import { addFlag } from '../../../utils/command.js';
|
|
5
5
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
6
6
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
7
7
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
8
|
-
vi.mock('../../../utils/
|
|
8
|
+
vi.mock('../../../utils/project');
|
|
9
9
|
vi.mock('../../../utils/command');
|
|
10
10
|
vi.mock('../../../utils/toolUsageTracking');
|
|
11
11
|
vi.mock('../../../utils/feedbackTracking');
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { HsListFunctionsTool } from '../HsListFunctionsTool.js';
|
|
3
|
-
import { runCommandInDir } from '../../../utils/
|
|
3
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
4
4
|
import { addFlag } from '../../../utils/command.js';
|
|
5
5
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
6
6
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
7
7
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
8
|
-
vi.mock('../../../utils/
|
|
8
|
+
vi.mock('../../../utils/project');
|
|
9
9
|
vi.mock('../../../utils/command');
|
|
10
10
|
vi.mock('../../../utils/toolUsageTracking');
|
|
11
11
|
vi.mock('../../../utils/feedbackTracking');
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { HsListTool } from '../HsListTool.js';
|
|
3
|
-
import { runCommandInDir } from '../../../utils/
|
|
3
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
4
4
|
import { addFlag } from '../../../utils/command.js';
|
|
5
5
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
6
6
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
7
7
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
8
|
-
vi.mock('../../../utils/
|
|
8
|
+
vi.mock('../../../utils/project');
|
|
9
9
|
vi.mock('../../../utils/command');
|
|
10
10
|
vi.mock('../../../utils/toolUsageTracking');
|
|
11
11
|
vi.mock('../../../utils/feedbackTracking');
|
|
@@ -17,8 +17,8 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
17
17
|
card: "card";
|
|
18
18
|
settings: "settings";
|
|
19
19
|
"app-event": "app-event";
|
|
20
|
-
page: "page";
|
|
21
20
|
"workflow-action-tool": "workflow-action-tool";
|
|
21
|
+
page: "page";
|
|
22
22
|
webhooks: "webhooks";
|
|
23
23
|
"workflow-action": "workflow-action";
|
|
24
24
|
"app-function": "app-function";
|
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, } from '../../../lib/constants.js';
|
|
4
4
|
import { addFlag } from '../../utils/command.js';
|
|
5
5
|
import { absoluteCurrentWorkingDirectory, absoluteProjectPath, features, } from './constants.js';
|
|
6
|
-
import { runCommandInDir } from '../../utils/
|
|
6
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
7
7
|
import { formatTextContents, formatTextContent } from '../../utils/content.js';
|
|
8
8
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
9
9
|
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
@@ -21,8 +21,8 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
21
21
|
card: "card";
|
|
22
22
|
settings: "settings";
|
|
23
23
|
"app-event": "app-event";
|
|
24
|
-
page: "page";
|
|
25
24
|
"workflow-action-tool": "workflow-action-tool";
|
|
25
|
+
page: "page";
|
|
26
26
|
webhooks: "webhooks";
|
|
27
27
|
"workflow-action": "workflow-action";
|
|
28
28
|
"app-function": "app-function";
|
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, EMPTY_PROJECT, PROJECT_WITH_APP, } from '../../../lib/constants.js';
|
|
4
4
|
import { addFlag } from '../../utils/command.js';
|
|
5
5
|
import { absoluteCurrentWorkingDirectory, features } from './constants.js';
|
|
6
|
-
import { runCommandInDir } from '../../utils/
|
|
6
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
7
7
|
import { formatTextContents, formatTextContent } from '../../utils/content.js';
|
|
8
8
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
9
9
|
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
@@ -3,7 +3,7 @@ import fs from 'fs';
|
|
|
3
3
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
4
4
|
import { formatTextContents, formatTextContent } from '../../utils/content.js';
|
|
5
5
|
import { addFlag } from '../../utils/command.js';
|
|
6
|
-
import { runCommandInDir } from '../../utils/
|
|
6
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
7
7
|
import { ACCOUNT_LEVELS, ACCOUNT_LEVEL_CHOICES, } from '../../../lib/constants.js';
|
|
8
8
|
import { Tool } from '../../types.js';
|
|
9
9
|
import { absoluteCurrentWorkingDirectory } from './constants.js';
|
|
@@ -2,7 +2,7 @@ import { Tool } from '../../types.js';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { addFlag } from '../../utils/command.js';
|
|
4
4
|
import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
|
|
5
|
-
import { runCommandInDir } from '../../utils/
|
|
5
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
6
6
|
import { formatTextContents, formatTextContent } from '../../utils/content.js';
|
|
7
7
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
8
8
|
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
@@ -3,7 +3,7 @@ import z from 'zod';
|
|
|
3
3
|
import { getAllHsProfiles } from '@hubspot/project-parsing-lib/profiles';
|
|
4
4
|
import { getProjectConfig } from '../../../lib/projects/config.js';
|
|
5
5
|
import { Tool } from '../../types.js';
|
|
6
|
-
import { runCommandInDir } from '../../utils/
|
|
6
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
7
7
|
import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
|
|
8
8
|
import { formatTextContent, formatTextContents } from '../../utils/content.js';
|
|
9
9
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Tool } from '../../types.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
|
|
4
|
-
import { runCommandInDir } from '../../utils/
|
|
4
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
5
5
|
import { formatTextContents } from '../../utils/content.js';
|
|
6
6
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
7
7
|
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { AddFeatureToProjectTool, } from '../AddFeatureToProjectTool.js';
|
|
2
|
-
import { runCommandInDir } from '../../../utils/
|
|
2
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
3
3
|
import { addFlag } from '../../../utils/command.js';
|
|
4
4
|
import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, } from '../../../../lib/constants.js';
|
|
5
5
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
6
6
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
7
7
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
8
|
-
vi.mock('../../../utils/
|
|
8
|
+
vi.mock('../../../utils/project');
|
|
9
9
|
vi.mock('../../../utils/command');
|
|
10
10
|
vi.mock('../../../../lib/constants');
|
|
11
11
|
vi.mock('../../../utils/toolUsageTracking');
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { CreateProjectTool, } from '../CreateProjectTool.js';
|
|
2
|
-
import { runCommandInDir } from '../../../utils/
|
|
2
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
3
3
|
import { addFlag } from '../../../utils/command.js';
|
|
4
4
|
import { APP_DISTRIBUTION_TYPES, EMPTY_PROJECT, PROJECT_WITH_APP, } from '../../../../lib/constants.js';
|
|
5
5
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
6
6
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
7
7
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
8
|
-
vi.mock('../../../utils/
|
|
8
|
+
vi.mock('../../../utils/project');
|
|
9
9
|
vi.mock('../../../utils/command');
|
|
10
10
|
vi.mock('../../../../lib/constants');
|
|
11
11
|
vi.mock('../../../../lib/projects/create/v2');
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CreateTestAccountTool, } from '../CreateTestAccountTool.js';
|
|
2
|
-
import { runCommandInDir } from '../../../utils/
|
|
2
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
3
3
|
import { addFlag } from '../../../utils/command.js';
|
|
4
4
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
5
5
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import * as config from '@hubspot/local-dev-lib/config';
|
|
8
8
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
9
|
-
vi.mock('../../../utils/
|
|
9
|
+
vi.mock('../../../utils/project');
|
|
10
10
|
vi.mock('../../../utils/command');
|
|
11
11
|
vi.mock('../../../utils/toolUsageTracking');
|
|
12
12
|
vi.mock('../../../utils/feedbackTracking');
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DeployProjectTool } from '../DeployProjectTool.js';
|
|
2
|
-
import { runCommandInDir } from '../../../utils/
|
|
2
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
3
3
|
import { addFlag } from '../../../utils/command.js';
|
|
4
4
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
5
5
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
6
6
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
7
|
-
vi.mock('../../../utils/
|
|
7
|
+
vi.mock('../../../utils/project');
|
|
8
8
|
vi.mock('../../../utils/command');
|
|
9
9
|
vi.mock('../../../utils/toolUsageTracking');
|
|
10
10
|
vi.mock('../../../utils/feedbackTracking');
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import { UploadProjectTools } from '../UploadProjectTools.js';
|
|
2
2
|
import { getAllHsProfiles } from '@hubspot/project-parsing-lib/profiles';
|
|
3
3
|
import { getProjectConfig } from '../../../../lib/projects/config.js';
|
|
4
|
-
import { runCommandInDir } from '../../../utils/
|
|
4
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
5
5
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
6
6
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
7
7
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
8
8
|
vi.mock('@hubspot/project-parsing-lib/profiles');
|
|
9
9
|
vi.mock('../../../../lib/projects/config.js');
|
|
10
|
-
vi.mock('../../../utils/
|
|
11
|
-
runCommandInDir: vi.fn(),
|
|
12
|
-
addFlag: vi.fn((command, flagName, value) => {
|
|
13
|
-
if (Array.isArray(value)) {
|
|
14
|
-
return `${command} --${flagName} ${value.map(item => `"${item}"`).join(' ')}`;
|
|
15
|
-
}
|
|
16
|
-
return `${command} --${flagName} "${value}"`;
|
|
17
|
-
}),
|
|
18
|
-
}));
|
|
10
|
+
vi.mock('../../../utils/project');
|
|
19
11
|
vi.mock('../../../utils/toolUsageTracking');
|
|
20
12
|
vi.mock('../../../utils/feedbackTracking');
|
|
21
13
|
const mockTrackToolUsage = trackToolUsage;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ValidateProjectTool, } from '../ValidateProjectTool.js';
|
|
2
|
-
import { runCommandInDir } from '../../../utils/
|
|
2
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
3
3
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
4
4
|
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
5
5
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
6
|
-
vi.mock('../../../utils/
|
|
6
|
+
vi.mock('../../../utils/project');
|
|
7
7
|
vi.mock('../../../utils/toolUsageTracking');
|
|
8
8
|
vi.mock('../../../utils/feedbackTracking');
|
|
9
9
|
const mockTrackToolUsage = trackToolUsage;
|
|
@@ -5,8 +5,8 @@ export declare const features: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
|
5
5
|
card: "card";
|
|
6
6
|
settings: "settings";
|
|
7
7
|
"app-event": "app-event";
|
|
8
|
-
page: "page";
|
|
9
8
|
"workflow-action-tool": "workflow-action-tool";
|
|
9
|
+
page: "page";
|
|
10
10
|
webhooks: "webhooks";
|
|
11
11
|
"workflow-action": "workflow-action";
|
|
12
12
|
"app-function": "app-function";
|
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
vi.mock('node:child_process');
|
|
5
|
-
vi.mock('util', () => ({
|
|
6
|
-
default: {
|
|
7
|
-
promisify: () => mockExecAsync,
|
|
8
|
-
},
|
|
9
|
-
promisify: () => mockExecAsync,
|
|
10
|
-
}));
|
|
11
|
-
vi.mock('fs');
|
|
12
|
-
vi.mock('path');
|
|
13
|
-
// Import after mocks are set up
|
|
14
|
-
const { addFlag, runCommandInDir } = await import('../command.js');
|
|
15
|
-
const mockExistsSync = vi.mocked(fs.existsSync);
|
|
16
|
-
const mockMkdirSync = vi.mocked(fs.mkdirSync);
|
|
17
|
-
const mockResolve = vi.mocked(path.resolve);
|
|
1
|
+
import { addFlag } from '../command.js';
|
|
2
|
+
vi.mock('child_process');
|
|
3
|
+
vi.mock('util');
|
|
18
4
|
describe('mcp-server/utils/command', () => {
|
|
19
5
|
describe('addFlag', () => {
|
|
20
6
|
it('should add string flag to command', () => {
|
|
@@ -56,220 +42,4 @@ describe('mcp-server/utils/command', () => {
|
|
|
56
42
|
expect(result).toBe('hs project create --features "card with spaces" "settings"');
|
|
57
43
|
});
|
|
58
44
|
});
|
|
59
|
-
describe('runCommandInDir', () => {
|
|
60
|
-
const mockDirectory = '/test/directory';
|
|
61
|
-
const mockCommand = 'npm install';
|
|
62
|
-
const mockResolvedPath = '/resolved/test/directory';
|
|
63
|
-
beforeEach(() => {
|
|
64
|
-
mockResolve.mockReturnValue(mockResolvedPath);
|
|
65
|
-
});
|
|
66
|
-
it('should run command in existing directory', async () => {
|
|
67
|
-
const expectedResult = {
|
|
68
|
-
stdout: 'command output',
|
|
69
|
-
stderr: '',
|
|
70
|
-
};
|
|
71
|
-
mockExistsSync.mockReturnValue(true);
|
|
72
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
73
|
-
const result = await runCommandInDir(mockDirectory, mockCommand);
|
|
74
|
-
expect(mockExistsSync).toHaveBeenCalledWith(mockDirectory);
|
|
75
|
-
expect(mockMkdirSync).not.toHaveBeenCalled();
|
|
76
|
-
expect(mockResolve).toHaveBeenCalledWith(mockDirectory);
|
|
77
|
-
expect(mockExecAsync).toHaveBeenCalledWith(mockCommand, expect.objectContaining({
|
|
78
|
-
cwd: mockResolvedPath,
|
|
79
|
-
env: expect.any(Object),
|
|
80
|
-
}));
|
|
81
|
-
expect(result).toEqual(expectedResult);
|
|
82
|
-
});
|
|
83
|
-
it('should create directory if it does not exist', async () => {
|
|
84
|
-
const expectedResult = {
|
|
85
|
-
stdout: 'command output',
|
|
86
|
-
stderr: '',
|
|
87
|
-
};
|
|
88
|
-
mockExistsSync.mockReturnValue(false);
|
|
89
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
90
|
-
const result = await runCommandInDir(mockDirectory, mockCommand);
|
|
91
|
-
expect(mockExistsSync).toHaveBeenCalledWith(mockDirectory);
|
|
92
|
-
expect(mockMkdirSync).toHaveBeenCalledWith(mockDirectory);
|
|
93
|
-
expect(mockResolve).toHaveBeenCalledWith(mockDirectory);
|
|
94
|
-
expect(mockExecAsync).toHaveBeenCalledWith(mockCommand, expect.objectContaining({
|
|
95
|
-
cwd: mockResolvedPath,
|
|
96
|
-
env: expect.any(Object),
|
|
97
|
-
}));
|
|
98
|
-
expect(result).toEqual(expectedResult);
|
|
99
|
-
});
|
|
100
|
-
it('should propagate execAsync errors', async () => {
|
|
101
|
-
const error = new Error('Command failed');
|
|
102
|
-
mockExistsSync.mockReturnValue(true);
|
|
103
|
-
mockExecAsync.mockRejectedValue(error);
|
|
104
|
-
await expect(runCommandInDir(mockDirectory, mockCommand)).rejects.toThrow('Command failed');
|
|
105
|
-
expect(mockExecAsync).toHaveBeenCalledWith(mockCommand, expect.objectContaining({
|
|
106
|
-
cwd: mockResolvedPath,
|
|
107
|
-
env: expect.any(Object),
|
|
108
|
-
}));
|
|
109
|
-
});
|
|
110
|
-
it('should handle stderr in results', async () => {
|
|
111
|
-
const expectedResult = {
|
|
112
|
-
stdout: 'some output',
|
|
113
|
-
stderr: 'warning message',
|
|
114
|
-
};
|
|
115
|
-
mockExistsSync.mockReturnValue(true);
|
|
116
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
117
|
-
const result = await runCommandInDir(mockDirectory, mockCommand);
|
|
118
|
-
expect(result.stdout).toBe('some output');
|
|
119
|
-
expect(result.stderr).toBe('warning message');
|
|
120
|
-
});
|
|
121
|
-
it('should add --disable-usage-tracking flag to hs commands', async () => {
|
|
122
|
-
const hsCommand = 'hs project upload';
|
|
123
|
-
const expectedResult = {
|
|
124
|
-
stdout: 'success',
|
|
125
|
-
stderr: '',
|
|
126
|
-
};
|
|
127
|
-
mockExistsSync.mockReturnValue(true);
|
|
128
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
129
|
-
await runCommandInDir(mockDirectory, hsCommand);
|
|
130
|
-
expect(mockExecAsync).toHaveBeenCalledWith('hs project upload --disable-usage-tracking "true"', expect.objectContaining({
|
|
131
|
-
cwd: mockResolvedPath,
|
|
132
|
-
env: expect.any(Object),
|
|
133
|
-
}));
|
|
134
|
-
});
|
|
135
|
-
it('should not add --disable-usage-tracking flag to non-hs commands', async () => {
|
|
136
|
-
const nonHsCommand = 'npm install';
|
|
137
|
-
const expectedResult = {
|
|
138
|
-
stdout: 'success',
|
|
139
|
-
stderr: '',
|
|
140
|
-
};
|
|
141
|
-
mockExistsSync.mockReturnValue(true);
|
|
142
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
143
|
-
await runCommandInDir(mockDirectory, nonHsCommand);
|
|
144
|
-
expect(mockExecAsync).toHaveBeenCalledWith('npm install', expect.objectContaining({
|
|
145
|
-
cwd: mockResolvedPath,
|
|
146
|
-
env: expect.any(Object),
|
|
147
|
-
}));
|
|
148
|
-
});
|
|
149
|
-
it('should add --disable-usage-tracking flag to hs commands with existing flags', async () => {
|
|
150
|
-
const hsCommand = 'hs project upload --profile prod';
|
|
151
|
-
const expectedResult = {
|
|
152
|
-
stdout: 'success',
|
|
153
|
-
stderr: '',
|
|
154
|
-
};
|
|
155
|
-
mockExistsSync.mockReturnValue(true);
|
|
156
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
157
|
-
await runCommandInDir(mockDirectory, hsCommand);
|
|
158
|
-
expect(mockExecAsync).toHaveBeenCalledWith('hs project upload --profile prod --disable-usage-tracking "true"', expect.objectContaining({
|
|
159
|
-
cwd: mockResolvedPath,
|
|
160
|
-
env: expect.any(Object),
|
|
161
|
-
}));
|
|
162
|
-
});
|
|
163
|
-
it('should handle hs commands that start with whitespace', async () => {
|
|
164
|
-
const hsCommand = 'hs init';
|
|
165
|
-
const expectedResult = {
|
|
166
|
-
stdout: 'success',
|
|
167
|
-
stderr: '',
|
|
168
|
-
};
|
|
169
|
-
mockExistsSync.mockReturnValue(true);
|
|
170
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
171
|
-
await runCommandInDir(mockDirectory, hsCommand);
|
|
172
|
-
expect(mockExecAsync).toHaveBeenCalledWith('hs init --disable-usage-tracking "true"', expect.objectContaining({
|
|
173
|
-
cwd: mockResolvedPath,
|
|
174
|
-
env: expect.any(Object),
|
|
175
|
-
}));
|
|
176
|
-
});
|
|
177
|
-
it('should use npx -p @hubspot/cli when HUBSPOT_MCP_STANDALONE is true', async () => {
|
|
178
|
-
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
179
|
-
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
180
|
-
const hsCommand = 'hs project upload';
|
|
181
|
-
const expectedResult = {
|
|
182
|
-
stdout: 'success',
|
|
183
|
-
stderr: '',
|
|
184
|
-
};
|
|
185
|
-
mockExistsSync.mockReturnValue(true);
|
|
186
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
187
|
-
await runCommandInDir(mockDirectory, hsCommand);
|
|
188
|
-
expect(mockExecAsync).toHaveBeenCalledWith('npx -y -p @hubspot/cli hs project upload --disable-usage-tracking "true"', expect.objectContaining({
|
|
189
|
-
cwd: mockResolvedPath,
|
|
190
|
-
env: expect.any(Object),
|
|
191
|
-
}));
|
|
192
|
-
// Restore original env
|
|
193
|
-
if (originalEnv === undefined) {
|
|
194
|
-
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
it('should use regular hs command when HUBSPOT_MCP_STANDALONE is not set', async () => {
|
|
201
|
-
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
202
|
-
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
203
|
-
const hsCommand = 'hs project upload';
|
|
204
|
-
const expectedResult = {
|
|
205
|
-
stdout: 'success',
|
|
206
|
-
stderr: '',
|
|
207
|
-
};
|
|
208
|
-
mockExistsSync.mockReturnValue(true);
|
|
209
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
210
|
-
await runCommandInDir(mockDirectory, hsCommand);
|
|
211
|
-
expect(mockExecAsync).toHaveBeenCalledWith('hs project upload --disable-usage-tracking "true"', expect.objectContaining({
|
|
212
|
-
cwd: mockResolvedPath,
|
|
213
|
-
env: expect.any(Object),
|
|
214
|
-
}));
|
|
215
|
-
// Restore original env
|
|
216
|
-
if (originalEnv !== undefined) {
|
|
217
|
-
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
it('should use npx -p @hubspot/cli for hs commands with flags in standalone mode', async () => {
|
|
221
|
-
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
222
|
-
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
223
|
-
const hsCommand = 'hs project upload --profile prod';
|
|
224
|
-
const expectedResult = {
|
|
225
|
-
stdout: 'success',
|
|
226
|
-
stderr: '',
|
|
227
|
-
};
|
|
228
|
-
mockExistsSync.mockReturnValue(true);
|
|
229
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
230
|
-
await runCommandInDir(mockDirectory, hsCommand);
|
|
231
|
-
expect(mockExecAsync).toHaveBeenCalledWith('npx -y -p @hubspot/cli hs project upload --profile prod --disable-usage-tracking "true"', expect.objectContaining({
|
|
232
|
-
cwd: mockResolvedPath,
|
|
233
|
-
env: expect.any(Object),
|
|
234
|
-
}));
|
|
235
|
-
// Restore original env
|
|
236
|
-
if (originalEnv === undefined) {
|
|
237
|
-
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
it('should use pinned CLI version when HUBSPOT_CLI_VERSION is set in standalone mode', async () => {
|
|
244
|
-
const originalStandaloneEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
245
|
-
const originalVersionEnv = process.env.HUBSPOT_CLI_VERSION;
|
|
246
|
-
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
247
|
-
process.env.HUBSPOT_CLI_VERSION = '8.1.0';
|
|
248
|
-
const hsCommand = 'hs project upload';
|
|
249
|
-
const expectedResult = {
|
|
250
|
-
stdout: 'success',
|
|
251
|
-
stderr: '',
|
|
252
|
-
};
|
|
253
|
-
mockExistsSync.mockReturnValue(true);
|
|
254
|
-
mockExecAsync.mockResolvedValue(expectedResult);
|
|
255
|
-
await runCommandInDir(mockDirectory, hsCommand);
|
|
256
|
-
expect(mockExecAsync).toHaveBeenCalledWith('npx -y -p @hubspot/cli@8.1.0 hs project upload --disable-usage-tracking "true"', expect.objectContaining({
|
|
257
|
-
cwd: mockResolvedPath,
|
|
258
|
-
env: expect.any(Object),
|
|
259
|
-
}));
|
|
260
|
-
// Restore original env
|
|
261
|
-
if (originalStandaloneEnv === undefined) {
|
|
262
|
-
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
process.env.HUBSPOT_MCP_STANDALONE = originalStandaloneEnv;
|
|
266
|
-
}
|
|
267
|
-
if (originalVersionEnv === undefined) {
|
|
268
|
-
delete process.env.HUBSPOT_CLI_VERSION;
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
process.env.HUBSPOT_CLI_VERSION = originalVersionEnv;
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
45
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { runCommandInDir } from '../project.js';
|
|
2
|
+
import { execAsync } from '../command.js';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
vi.mock('../command', () => ({
|
|
6
|
+
execAsync: vi.fn(),
|
|
7
|
+
addFlag: vi.fn((command, flagName, value) => {
|
|
8
|
+
if (Array.isArray(value)) {
|
|
9
|
+
return `${command} --${flagName} ${value.map(item => `"${item}"`).join(' ')}`;
|
|
10
|
+
}
|
|
11
|
+
return `${command} --${flagName} "${value}"`;
|
|
12
|
+
}),
|
|
13
|
+
}));
|
|
14
|
+
vi.mock('fs');
|
|
15
|
+
vi.mock('path');
|
|
16
|
+
const mockExecAsync = execAsync;
|
|
17
|
+
const mockExistsSync = fs.existsSync;
|
|
18
|
+
const mockMkdirSync = fs.mkdirSync;
|
|
19
|
+
const mockResolve = path.resolve;
|
|
20
|
+
describe('mcp-server/utils/project', () => {
|
|
21
|
+
describe('runCommandInDir', () => {
|
|
22
|
+
const mockDirectory = '/test/directory';
|
|
23
|
+
const mockCommand = 'npm install';
|
|
24
|
+
const mockResolvedPath = '/resolved/test/directory';
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
mockResolve.mockReturnValue(mockResolvedPath);
|
|
27
|
+
});
|
|
28
|
+
it('should run command in existing directory', async () => {
|
|
29
|
+
const expectedResult = {
|
|
30
|
+
stdout: 'command output',
|
|
31
|
+
stderr: '',
|
|
32
|
+
};
|
|
33
|
+
mockExistsSync.mockReturnValue(true);
|
|
34
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
35
|
+
const result = await runCommandInDir(mockDirectory, mockCommand);
|
|
36
|
+
expect(mockExistsSync).toHaveBeenCalledWith(mockDirectory);
|
|
37
|
+
expect(mockMkdirSync).not.toHaveBeenCalled();
|
|
38
|
+
expect(mockResolve).toHaveBeenCalledWith(mockDirectory);
|
|
39
|
+
expect(mockExecAsync).toHaveBeenCalledWith(mockCommand, expect.objectContaining({
|
|
40
|
+
cwd: mockResolvedPath,
|
|
41
|
+
env: expect.any(Object),
|
|
42
|
+
}));
|
|
43
|
+
expect(result).toEqual(expectedResult);
|
|
44
|
+
});
|
|
45
|
+
it('should create directory if it does not exist', async () => {
|
|
46
|
+
const expectedResult = {
|
|
47
|
+
stdout: 'command output',
|
|
48
|
+
stderr: '',
|
|
49
|
+
};
|
|
50
|
+
mockExistsSync.mockReturnValue(false);
|
|
51
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
52
|
+
const result = await runCommandInDir(mockDirectory, mockCommand);
|
|
53
|
+
expect(mockExistsSync).toHaveBeenCalledWith(mockDirectory);
|
|
54
|
+
expect(mockMkdirSync).toHaveBeenCalledWith(mockDirectory);
|
|
55
|
+
expect(mockResolve).toHaveBeenCalledWith(mockDirectory);
|
|
56
|
+
expect(mockExecAsync).toHaveBeenCalledWith(mockCommand, expect.objectContaining({
|
|
57
|
+
cwd: mockResolvedPath,
|
|
58
|
+
env: expect.any(Object),
|
|
59
|
+
}));
|
|
60
|
+
expect(result).toEqual(expectedResult);
|
|
61
|
+
});
|
|
62
|
+
it('should propagate execAsync errors', async () => {
|
|
63
|
+
const error = new Error('Command failed');
|
|
64
|
+
mockExistsSync.mockReturnValue(true);
|
|
65
|
+
mockExecAsync.mockRejectedValue(error);
|
|
66
|
+
await expect(runCommandInDir(mockDirectory, mockCommand)).rejects.toThrow('Command failed');
|
|
67
|
+
expect(mockExecAsync).toHaveBeenCalledWith(mockCommand, expect.objectContaining({
|
|
68
|
+
cwd: mockResolvedPath,
|
|
69
|
+
env: expect.any(Object),
|
|
70
|
+
}));
|
|
71
|
+
});
|
|
72
|
+
it('should handle stderr in results', async () => {
|
|
73
|
+
const expectedResult = {
|
|
74
|
+
stdout: 'some output',
|
|
75
|
+
stderr: 'warning message',
|
|
76
|
+
};
|
|
77
|
+
mockExistsSync.mockReturnValue(true);
|
|
78
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
79
|
+
const result = await runCommandInDir(mockDirectory, mockCommand);
|
|
80
|
+
expect(result.stdout).toBe('some output');
|
|
81
|
+
expect(result.stderr).toBe('warning message');
|
|
82
|
+
});
|
|
83
|
+
it('should add --disable-usage-tracking flag to hs commands', async () => {
|
|
84
|
+
const hsCommand = 'hs project upload';
|
|
85
|
+
const expectedResult = {
|
|
86
|
+
stdout: 'success',
|
|
87
|
+
stderr: '',
|
|
88
|
+
};
|
|
89
|
+
mockExistsSync.mockReturnValue(true);
|
|
90
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
91
|
+
await runCommandInDir(mockDirectory, hsCommand);
|
|
92
|
+
expect(mockExecAsync).toHaveBeenCalledWith('hs project upload --disable-usage-tracking "true"', expect.objectContaining({
|
|
93
|
+
cwd: mockResolvedPath,
|
|
94
|
+
env: expect.any(Object),
|
|
95
|
+
}));
|
|
96
|
+
});
|
|
97
|
+
it('should not add --disable-usage-tracking flag to non-hs commands', async () => {
|
|
98
|
+
const nonHsCommand = 'npm install';
|
|
99
|
+
const expectedResult = {
|
|
100
|
+
stdout: 'success',
|
|
101
|
+
stderr: '',
|
|
102
|
+
};
|
|
103
|
+
mockExistsSync.mockReturnValue(true);
|
|
104
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
105
|
+
await runCommandInDir(mockDirectory, nonHsCommand);
|
|
106
|
+
expect(mockExecAsync).toHaveBeenCalledWith('npm install', expect.objectContaining({
|
|
107
|
+
cwd: mockResolvedPath,
|
|
108
|
+
env: expect.any(Object),
|
|
109
|
+
}));
|
|
110
|
+
});
|
|
111
|
+
it('should add --disable-usage-tracking flag to hs commands with existing flags', async () => {
|
|
112
|
+
const hsCommand = 'hs project upload --profile prod';
|
|
113
|
+
const expectedResult = {
|
|
114
|
+
stdout: 'success',
|
|
115
|
+
stderr: '',
|
|
116
|
+
};
|
|
117
|
+
mockExistsSync.mockReturnValue(true);
|
|
118
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
119
|
+
await runCommandInDir(mockDirectory, hsCommand);
|
|
120
|
+
expect(mockExecAsync).toHaveBeenCalledWith('hs project upload --profile prod --disable-usage-tracking "true"', expect.objectContaining({
|
|
121
|
+
cwd: mockResolvedPath,
|
|
122
|
+
env: expect.any(Object),
|
|
123
|
+
}));
|
|
124
|
+
});
|
|
125
|
+
it('should handle hs commands that start with whitespace', async () => {
|
|
126
|
+
const hsCommand = 'hs init';
|
|
127
|
+
const expectedResult = {
|
|
128
|
+
stdout: 'success',
|
|
129
|
+
stderr: '',
|
|
130
|
+
};
|
|
131
|
+
mockExistsSync.mockReturnValue(true);
|
|
132
|
+
mockExecAsync.mockResolvedValue(expectedResult);
|
|
133
|
+
await runCommandInDir(mockDirectory, hsCommand);
|
|
134
|
+
expect(mockExecAsync).toHaveBeenCalledWith('hs init --disable-usage-tracking "true"', expect.objectContaining({
|
|
135
|
+
cwd: mockResolvedPath,
|
|
136
|
+
env: expect.any(Object),
|
|
137
|
+
}));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
import { exec } from 'node:child_process';
|
|
2
2
|
export declare const execAsync: typeof exec.__promisify__;
|
|
3
3
|
export declare function addFlag(command: string, flagName: string, value: string | number | boolean | string[]): string;
|
|
4
|
-
export interface CommandResults {
|
|
5
|
-
stderr: string;
|
|
6
|
-
stdout: string;
|
|
7
|
-
}
|
|
8
|
-
export declare function runCommandInDir(directory: string, command: string): Promise<CommandResults>;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs';
|
|
3
1
|
import util from 'util';
|
|
4
2
|
import { exec } from 'node:child_process';
|
|
5
3
|
export const execAsync = util.promisify(exec);
|
|
@@ -9,25 +7,3 @@ export function addFlag(command, flagName, value) {
|
|
|
9
7
|
}
|
|
10
8
|
return `${command} --${flagName} "${value}"`;
|
|
11
9
|
}
|
|
12
|
-
export async function runCommandInDir(directory, command) {
|
|
13
|
-
if (!fs.existsSync(directory)) {
|
|
14
|
-
fs.mkdirSync(directory);
|
|
15
|
-
}
|
|
16
|
-
let finalCommand = command;
|
|
17
|
-
if (command.startsWith('hs ')) {
|
|
18
|
-
// Check if running in standalone mode
|
|
19
|
-
if (process.env.HUBSPOT_MCP_STANDALONE === 'true') {
|
|
20
|
-
const cliPackage = process.env.HUBSPOT_CLI_VERSION
|
|
21
|
-
? `@hubspot/cli@${process.env.HUBSPOT_CLI_VERSION}`
|
|
22
|
-
: '@hubspot/cli';
|
|
23
|
-
finalCommand = command.replace(/^hs /, `npx -y -p ${cliPackage} hs `);
|
|
24
|
-
}
|
|
25
|
-
finalCommand = addFlag(finalCommand, 'disable-usage-tracking', true);
|
|
26
|
-
}
|
|
27
|
-
return execAsync(finalCommand, {
|
|
28
|
-
cwd: path.resolve(directory),
|
|
29
|
-
env: {
|
|
30
|
-
...process.env,
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { addFlag, execAsync } from './command.js';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
export async function runCommandInDir(directory, command) {
|
|
5
|
+
if (!fs.existsSync(directory)) {
|
|
6
|
+
fs.mkdirSync(directory);
|
|
7
|
+
}
|
|
8
|
+
let finalCommand = command;
|
|
9
|
+
if (command.startsWith('hs ')) {
|
|
10
|
+
finalCommand = addFlag(finalCommand, 'disable-usage-tracking', true);
|
|
11
|
+
}
|
|
12
|
+
return execAsync(finalCommand, {
|
|
13
|
+
cwd: path.resolve(directory),
|
|
14
|
+
env: {
|
|
15
|
+
...process.env,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cli",
|
|
3
|
-
"version": "8.0.8-experimental.
|
|
3
|
+
"version": "8.0.8-experimental.6",
|
|
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",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@hubspot/cms-dev-server": "1.2.16",
|
|
10
10
|
"@hubspot/local-dev-lib": "5.1.1",
|
|
11
|
-
"@hubspot/project-parsing-lib": "0.
|
|
11
|
+
"@hubspot/project-parsing-lib": "0.2.0-experimental.1",
|
|
12
12
|
"@hubspot/serverless-dev-runtime": "7.0.7",
|
|
13
13
|
"@hubspot/ui-extensions-dev-server": "1.1.8",
|
|
14
14
|
"@inquirer/prompts": "7.1.0",
|