@hubspot/cli 8.0.8-experimental.0 → 8.0.8-experimental.10
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/cms/theme/preview.js +11 -64
- 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/__tests__/commandSuggestion.test.js +2 -0
- package/lib/cms/devServerProcess.d.ts +13 -0
- package/lib/cms/devServerProcess.js +188 -0
- package/lib/commandSuggestion.js +1 -7
- package/lib/mcp/__tests__/setup.test.js +0 -15
- package/lib/mcp/setup.d.ts +0 -1
- package/lib/mcp/setup.js +34 -105
- 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 +20 -3
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +7 -11
- package/mcp-server/tools/project/CreateProjectTool.d.ts +24 -4
- package/mcp-server/tools/project/CreateProjectTool.js +6 -11
- package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
- package/mcp-server/tools/project/DeployProjectTool.js +1 -1
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +5 -8
- package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
- package/mcp-server/tools/project/GetBuildLogsTool.js +6 -7
- package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
- package/mcp-server/tools/project/GetBuildStatusTool.js +3 -4
- package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +6 -1
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -6
- 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__/GetApiUsagePatternsByAppIdTool.test.js +0 -32
- 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 +12 -1
- package/mcp-server/tools/project/constants.js +12 -16
- 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 +1 -1
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import cliProgress from 'cli-progress';
|
|
4
3
|
import { commands } from '../../../lang/en.js';
|
|
5
4
|
import { getCwd } from '@hubspot/local-dev-lib/path';
|
|
6
|
-
import { FILE_UPLOAD_RESULT_TYPES } from '@hubspot/local-dev-lib/constants/files';
|
|
7
5
|
import { getThemeJSONPath } from '@hubspot/local-dev-lib/cms/themes';
|
|
8
|
-
|
|
9
|
-
import {
|
|
6
|
+
// Use subprocess approach to avoid React version conflicts
|
|
7
|
+
// import { createDevServer } from '@hubspot/cms-dev-server';
|
|
8
|
+
import { spawnDevServer } from '../../../lib/cms/devServerProcess.js';
|
|
10
9
|
import { trackCommandUsage } from '../../../lib/usageTracking.js';
|
|
11
10
|
import { previewPrompt, previewProjectPrompt, } from '../../../lib/prompts/previewPrompt.js';
|
|
12
11
|
import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
|
|
13
|
-
import { ApiErrorContext, logError } from '../../../lib/errorHandlers/index.js';
|
|
14
12
|
import { getProjectConfig } from '../../../lib/projects/config.js';
|
|
15
13
|
import { findProjectComponents } from '../../../lib/projects/structure.js';
|
|
16
14
|
import { ComponentTypes } from '../../../types/Projects.js';
|
|
@@ -73,67 +71,16 @@ async function determineSrcAndDest(args) {
|
|
|
73
71
|
async function handler(args) {
|
|
74
72
|
const { derivedAccountId, noSsl, resetSession, port, generateFieldsTypes } = args;
|
|
75
73
|
const { absoluteSrc, dest } = await determineSrcAndDest(args);
|
|
76
|
-
const filePaths = await getUploadableFileList(absoluteSrc, false);
|
|
77
|
-
function startProgressBar(numFiles) {
|
|
78
|
-
const initialUploadProgressBar = new cliProgress.SingleBar({
|
|
79
|
-
gracefulExit: true,
|
|
80
|
-
format: '[{bar}] {percentage}% | {value}/{total} | {label}',
|
|
81
|
-
hideCursor: true,
|
|
82
|
-
}, cliProgress.Presets.rect);
|
|
83
|
-
initialUploadProgressBar.start(numFiles, 0, {
|
|
84
|
-
label: commands.cms.subcommands.theme.subcommands.preview
|
|
85
|
-
.initialUploadProgressBar.start,
|
|
86
|
-
});
|
|
87
|
-
let uploadsHaveStarted = false;
|
|
88
|
-
const uploadOptions = {
|
|
89
|
-
onAttemptCallback: () => {
|
|
90
|
-
/* Intentionally blank */
|
|
91
|
-
},
|
|
92
|
-
onSuccessCallback: () => {
|
|
93
|
-
initialUploadProgressBar.increment();
|
|
94
|
-
if (!uploadsHaveStarted) {
|
|
95
|
-
uploadsHaveStarted = true;
|
|
96
|
-
initialUploadProgressBar.update(0, {
|
|
97
|
-
label: commands.cms.subcommands.theme.subcommands.preview
|
|
98
|
-
.initialUploadProgressBar.uploading,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
onFirstErrorCallback: () => {
|
|
103
|
-
/* Intentionally blank */
|
|
104
|
-
},
|
|
105
|
-
onRetryCallback: () => {
|
|
106
|
-
/* Intentionally blank */
|
|
107
|
-
},
|
|
108
|
-
onFinalErrorCallback: () => initialUploadProgressBar.increment(),
|
|
109
|
-
onFinishCallback: (results) => {
|
|
110
|
-
initialUploadProgressBar.update(numFiles, {
|
|
111
|
-
label: commands.cms.subcommands.theme.subcommands.preview
|
|
112
|
-
.initialUploadProgressBar.finish,
|
|
113
|
-
});
|
|
114
|
-
initialUploadProgressBar.stop();
|
|
115
|
-
results.forEach(result => {
|
|
116
|
-
if (result.resultType == FILE_UPLOAD_RESULT_TYPES.FAILURE) {
|
|
117
|
-
uiLogger.error(commands.cms.subcommands.theme.subcommands.preview.errors.uploadFailed(result.file, dest));
|
|
118
|
-
logError(result.error, new ApiErrorContext({
|
|
119
|
-
accountId: derivedAccountId,
|
|
120
|
-
request: dest,
|
|
121
|
-
payload: result.file,
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
return uploadOptions;
|
|
128
|
-
}
|
|
129
74
|
trackCommandUsage('preview', {}, derivedAccountId);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
75
|
+
// Spawn dev server in isolated subprocess to avoid React version conflicts
|
|
76
|
+
// File listing and progress bars are handled within the subprocess
|
|
77
|
+
spawnDevServer({
|
|
78
|
+
absoluteSrc,
|
|
79
|
+
accountName: derivedAccountId?.toString(),
|
|
80
|
+
noSsl,
|
|
81
|
+
port,
|
|
82
|
+
generateFieldsTypes,
|
|
135
83
|
resetSession: resetSession || false,
|
|
136
|
-
startProgressBar,
|
|
137
84
|
dest,
|
|
138
85
|
});
|
|
139
86
|
}
|
|
@@ -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: {
|
|
@@ -71,6 +71,8 @@ describe('lib/commandSuggestion', () => {
|
|
|
71
71
|
// Create a mock yargs builder with strict method
|
|
72
72
|
const mockYargsBuilder = {
|
|
73
73
|
strict: vi.fn().mockReturnThis(),
|
|
74
|
+
help: vi.fn().mockReturnThis(),
|
|
75
|
+
version: vi.fn().mockReturnThis(),
|
|
74
76
|
};
|
|
75
77
|
await commandModule.builder(mockYargsBuilder);
|
|
76
78
|
expect(mockYargsBuilder.strict).toHaveBeenCalledWith(false);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ChildProcess } from 'child_process';
|
|
2
|
+
interface DevServerOptions {
|
|
3
|
+
absoluteSrc: string;
|
|
4
|
+
accountName?: string;
|
|
5
|
+
configPath?: string;
|
|
6
|
+
noSsl?: boolean;
|
|
7
|
+
port?: number;
|
|
8
|
+
generateFieldsTypes?: boolean;
|
|
9
|
+
resetSession?: boolean;
|
|
10
|
+
dest?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function spawnDevServer(options: DevServerOptions): ChildProcess;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { spawn, execSync } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
/**
|
|
7
|
+
* Ensures cms-dev-server is installed in an isolated cache directory.
|
|
8
|
+
* This prevents React version conflicts with the CLI.
|
|
9
|
+
*/
|
|
10
|
+
function ensureCmsDevServerCache(targetVersion) {
|
|
11
|
+
const cacheDir = path.join(os.homedir(), '.hubspot', 'cms-dev-server-cache');
|
|
12
|
+
const packageJsonPath = path.join(cacheDir, 'node_modules', '@hubspot', 'cms-dev-server', 'package.json');
|
|
13
|
+
// Check if already installed with correct version
|
|
14
|
+
let needsInstall = true;
|
|
15
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
16
|
+
try {
|
|
17
|
+
const installedPackage = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
18
|
+
if (installedPackage.version === targetVersion) {
|
|
19
|
+
needsInstall = false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
// If we can't read the package.json, reinstall
|
|
24
|
+
needsInstall = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (needsInstall) {
|
|
28
|
+
console.log(`Setting up isolated cms-dev-server ${targetVersion}...`);
|
|
29
|
+
// Create cache directory
|
|
30
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
31
|
+
// Clear old installation if exists
|
|
32
|
+
const nodeModulesDir = path.join(cacheDir, 'node_modules');
|
|
33
|
+
if (fs.existsSync(nodeModulesDir)) {
|
|
34
|
+
fs.rmSync(nodeModulesDir, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
// Install cms-dev-server with production dependencies only
|
|
37
|
+
try {
|
|
38
|
+
execSync(`npm install @hubspot/cms-dev-server@${targetVersion} --production --no-save --loglevel=error`, {
|
|
39
|
+
cwd: cacheDir,
|
|
40
|
+
stdio: 'ignore', // Suppress npm output
|
|
41
|
+
});
|
|
42
|
+
console.log('✓ cms-dev-server setup complete');
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
console.error('Failed to install cms-dev-server:', e);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return cacheDir;
|
|
50
|
+
}
|
|
51
|
+
export function spawnDevServer(options) {
|
|
52
|
+
const { absoluteSrc, accountName, noSsl, port, generateFieldsTypes, resetSession, dest, } = options;
|
|
53
|
+
// Get the version from CLI's package.json
|
|
54
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
55
|
+
const cliPackageJsonPath = path.join(__dirname, '../../package.json');
|
|
56
|
+
const cliPackageJson = JSON.parse(fs.readFileSync(cliPackageJsonPath, 'utf8'));
|
|
57
|
+
const targetVersion = cliPackageJson.dependencies['@hubspot/cms-dev-server'];
|
|
58
|
+
if (!targetVersion) {
|
|
59
|
+
console.error('Could not determine cms-dev-server version from package.json');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
// Ensure cms-dev-server is installed in isolated cache
|
|
63
|
+
const cacheDir = ensureCmsDevServerCache(targetVersion);
|
|
64
|
+
// Build a Node script that imports and calls createDevServer
|
|
65
|
+
// This runs in the isolated cache directory, completely separate from CLI's node_modules
|
|
66
|
+
const script = `
|
|
67
|
+
// Suppress library deprecation warnings (e.g., body-parser)
|
|
68
|
+
process.noDeprecation = true;
|
|
69
|
+
|
|
70
|
+
const { createDevServer } = await import('@hubspot/cms-dev-server');
|
|
71
|
+
const { walk } = await import('@hubspot/local-dev-lib/fs');
|
|
72
|
+
const { createIgnoreFilter } = await import('@hubspot/local-dev-lib/ignoreRules');
|
|
73
|
+
const { isAllowedExtension } = await import('@hubspot/local-dev-lib/path');
|
|
74
|
+
const { FILE_UPLOAD_RESULT_TYPES } = await import('@hubspot/local-dev-lib/constants/files');
|
|
75
|
+
const cliProgress = (await import('cli-progress')).default;
|
|
76
|
+
|
|
77
|
+
${dest
|
|
78
|
+
? `
|
|
79
|
+
// Get uploadable files for preview
|
|
80
|
+
let filePaths = [];
|
|
81
|
+
try {
|
|
82
|
+
filePaths = await walk(${JSON.stringify(absoluteSrc)});
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error('Error walking directory:', e);
|
|
85
|
+
}
|
|
86
|
+
filePaths = filePaths
|
|
87
|
+
.filter(file => isAllowedExtension(file))
|
|
88
|
+
.filter(createIgnoreFilter(false));
|
|
89
|
+
|
|
90
|
+
// Create progress bar for initial upload
|
|
91
|
+
function startProgressBar(numFiles) {
|
|
92
|
+
const initialUploadProgressBar = new cliProgress.SingleBar(
|
|
93
|
+
{
|
|
94
|
+
gracefulExit: true,
|
|
95
|
+
format: '[{bar}] {percentage}% | {value}/{total} | {label}',
|
|
96
|
+
hideCursor: true,
|
|
97
|
+
},
|
|
98
|
+
cliProgress.Presets.rect
|
|
99
|
+
);
|
|
100
|
+
initialUploadProgressBar.start(numFiles, 0, {
|
|
101
|
+
label: 'Preparing upload...',
|
|
102
|
+
});
|
|
103
|
+
let uploadsHaveStarted = false;
|
|
104
|
+
return {
|
|
105
|
+
onAttemptCallback: () => {},
|
|
106
|
+
onSuccessCallback: () => {
|
|
107
|
+
initialUploadProgressBar.increment();
|
|
108
|
+
if (!uploadsHaveStarted) {
|
|
109
|
+
uploadsHaveStarted = true;
|
|
110
|
+
initialUploadProgressBar.update(0, {
|
|
111
|
+
label: 'Uploading files...',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
onFirstErrorCallback: () => {},
|
|
116
|
+
onRetryCallback: () => {},
|
|
117
|
+
onFinalErrorCallback: () => initialUploadProgressBar.increment(),
|
|
118
|
+
onFinishCallback: (results) => {
|
|
119
|
+
initialUploadProgressBar.update(numFiles, {
|
|
120
|
+
label: 'Upload complete',
|
|
121
|
+
});
|
|
122
|
+
initialUploadProgressBar.stop();
|
|
123
|
+
results.forEach(result => {
|
|
124
|
+
if (result.resultType == FILE_UPLOAD_RESULT_TYPES.FAILURE) {
|
|
125
|
+
console.error(\`Failed to upload \${result.file}\`);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const themePreviewOptions = {
|
|
133
|
+
filePaths,
|
|
134
|
+
startProgressBar,
|
|
135
|
+
resetSession: ${Boolean(resetSession)},
|
|
136
|
+
dest: ${JSON.stringify(dest)},
|
|
137
|
+
};
|
|
138
|
+
`
|
|
139
|
+
: 'const themePreviewOptions = undefined;'}
|
|
140
|
+
|
|
141
|
+
createDevServer(
|
|
142
|
+
${JSON.stringify(absoluteSrc)},
|
|
143
|
+
false, // storybook
|
|
144
|
+
'', // config (uses env var HUBSPOT_CONFIG_PATH)
|
|
145
|
+
${JSON.stringify(accountName || '')},
|
|
146
|
+
${!noSsl}, // sslEnabled
|
|
147
|
+
${Boolean(generateFieldsTypes)}, // fieldGenEnabled
|
|
148
|
+
themePreviewOptions
|
|
149
|
+
);
|
|
150
|
+
`;
|
|
151
|
+
// Set environment variables
|
|
152
|
+
const env = { ...process.env };
|
|
153
|
+
if (port) {
|
|
154
|
+
env.PORT = port.toString();
|
|
155
|
+
}
|
|
156
|
+
// Suppress Node.js deprecation warnings
|
|
157
|
+
env.NODE_NO_WARNINGS = '1';
|
|
158
|
+
// Spawn Node with the inline script from the isolated cache directory
|
|
159
|
+
// This ensures complete isolation from CLI's React 19
|
|
160
|
+
const devServer = spawn('node', ['--input-type=module', '-e', script], {
|
|
161
|
+
stdio: 'inherit',
|
|
162
|
+
env,
|
|
163
|
+
cwd: cacheDir,
|
|
164
|
+
});
|
|
165
|
+
// Handle process events
|
|
166
|
+
devServer.on('error', error => {
|
|
167
|
+
console.error('Failed to start dev server:', error);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
});
|
|
170
|
+
devServer.on('exit', (code, signal) => {
|
|
171
|
+
if (code !== 0 && code !== null) {
|
|
172
|
+
console.error(`Dev server exited with code ${code}`);
|
|
173
|
+
process.exit(code);
|
|
174
|
+
}
|
|
175
|
+
if (signal) {
|
|
176
|
+
console.error(`Dev server killed with signal ${signal}`);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
// Handle CLI termination
|
|
181
|
+
process.on('SIGINT', () => {
|
|
182
|
+
devServer.kill('SIGINT');
|
|
183
|
+
});
|
|
184
|
+
process.on('SIGTERM', () => {
|
|
185
|
+
devServer.kill('SIGTERM');
|
|
186
|
+
});
|
|
187
|
+
return devServer;
|
|
188
|
+
}
|
package/lib/commandSuggestion.js
CHANGED
|
@@ -18,12 +18,6 @@ export const commandSuggestionMappings = {
|
|
|
18
18
|
'theme generate-selectors': 'hs cms theme generate-selectors',
|
|
19
19
|
'theme marketplace-validate': 'hs cms theme marketplace-validate',
|
|
20
20
|
'theme preview': 'hs cms theme preview',
|
|
21
|
-
'custom-object schema create': 'hs custom-object create-schema',
|
|
22
|
-
'custom-object schema delete': 'hs custom-object delete-schema',
|
|
23
|
-
'custom-object schema fetch-all': 'hs custom-object fetch-all-schemas',
|
|
24
|
-
'custom-object schema fetch': 'hs custom-object fetch-schema',
|
|
25
|
-
'custom-object schema list': 'hs custom-object list-schemas',
|
|
26
|
-
'custom-object schema update': 'hs custom-object update-schema',
|
|
27
21
|
};
|
|
28
22
|
function createCommandSuggestionHandler(newCommand) {
|
|
29
23
|
return () => {
|
|
@@ -35,7 +29,7 @@ function createCommandSuggestion(oldCommand, newCommand) {
|
|
|
35
29
|
return {
|
|
36
30
|
command: oldCommand,
|
|
37
31
|
builder: async (yargs) => {
|
|
38
|
-
return yargs.strict(false);
|
|
32
|
+
return yargs.strict(false).help(false).version(false);
|
|
39
33
|
},
|
|
40
34
|
handler: createCommandSuggestionHandler(newCommand),
|
|
41
35
|
};
|
|
@@ -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" -- test-command --arg1 --ai-agent codex --env HUBSPOT_MCP_STANDALONE=true');
|
|
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;
|
|
@@ -155,14 +122,9 @@ function setupMcpConfigFile(config) {
|
|
|
155
122
|
mcpConfig.mcpServers = {};
|
|
156
123
|
}
|
|
157
124
|
// Add or update HubSpot CLI MCP server
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
args: config.mcpCommand.args,
|
|
125
|
+
mcpConfig.mcpServers[mcpServerName] = {
|
|
126
|
+
...config.mcpCommand,
|
|
161
127
|
};
|
|
162
|
-
if (config.mcpCommand.env) {
|
|
163
|
-
serverConfig.env = config.mcpCommand.env;
|
|
164
|
-
}
|
|
165
|
-
mcpConfig.mcpServers[mcpServerName] = serverConfig;
|
|
166
128
|
// Write the updated config
|
|
167
129
|
fs.writeFileSync(config.configPath, JSON.stringify(mcpConfig, null, 2));
|
|
168
130
|
SpinniesManager.succeed('spinner', {
|
|
@@ -183,16 +145,10 @@ export async function setupVsCode(mcpCommand = defaultMcpCommand) {
|
|
|
183
145
|
SpinniesManager.add('vsCode', {
|
|
184
146
|
text: commands.mcp.setup.spinners.configuringVsCode,
|
|
185
147
|
});
|
|
186
|
-
const
|
|
187
|
-
const configObject = {
|
|
148
|
+
const mcpConfig = JSON.stringify({
|
|
188
149
|
name: mcpServerName,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
};
|
|
192
|
-
if (commandWithAgent.env) {
|
|
193
|
-
configObject.env = commandWithAgent.env;
|
|
194
|
-
}
|
|
195
|
-
const mcpConfig = JSON.stringify(configObject);
|
|
150
|
+
...buildCommandWithAgentString(mcpCommand, vscode),
|
|
151
|
+
});
|
|
196
152
|
await execAsync(`code --add-mcp ${JSON.stringify(mcpConfig)}`);
|
|
197
153
|
SpinniesManager.succeed('vsCode', {
|
|
198
154
|
text: commands.mcp.setup.spinners.configuredVsCode,
|
|
@@ -223,44 +179,30 @@ export async function setupClaudeCode(mcpCommand = defaultMcpCommand) {
|
|
|
223
179
|
try {
|
|
224
180
|
// Check if claude command is available
|
|
225
181
|
await execAsync('claude --version');
|
|
226
|
-
// Run claude mcp add command
|
|
227
|
-
const commandWithAgent = buildCommandWithAgentString(mcpCommand, claudeCode);
|
|
228
|
-
const configObject = {
|
|
229
|
-
type: 'stdio',
|
|
230
|
-
command: commandWithAgent.command,
|
|
231
|
-
args: commandWithAgent.args,
|
|
232
|
-
};
|
|
233
|
-
if (commandWithAgent.env) {
|
|
234
|
-
configObject.env = commandWithAgent.env;
|
|
235
|
-
}
|
|
236
|
-
const mcpConfig = JSON.stringify(configObject);
|
|
237
|
-
const { stdout } = await execAsync('claude mcp list');
|
|
238
|
-
if (stdout.includes(mcpServerName)) {
|
|
239
|
-
SpinniesManager.update('claudeCode', {
|
|
240
|
-
text: commands.mcp.setup.spinners.alreadyInstalled,
|
|
241
|
-
});
|
|
242
|
-
await execAsync(`claude mcp remove "${mcpServerName}" --scope user`);
|
|
243
|
-
}
|
|
244
|
-
await execAsync(`claude mcp add-json "${mcpServerName}" ${JSON.stringify(mcpConfig)} --scope user`);
|
|
245
|
-
SpinniesManager.succeed('claudeCode', {
|
|
246
|
-
text: commands.mcp.setup.spinners.configuredClaudeCode,
|
|
247
|
-
});
|
|
248
|
-
return true;
|
|
249
182
|
}
|
|
250
|
-
catch (
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
SpinniesManager.fail('claudeCode', {
|
|
258
|
-
text: commands.mcp.setup.spinners.claudeCodeInstallFailed,
|
|
259
|
-
});
|
|
260
|
-
logError(error);
|
|
261
|
-
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
SpinniesManager.fail('claudeCode', {
|
|
185
|
+
text: commands.mcp.setup.spinners.claudeCodeNotFound,
|
|
186
|
+
});
|
|
262
187
|
return false;
|
|
263
188
|
}
|
|
189
|
+
// Run claude mcp add command
|
|
190
|
+
const mcpConfig = JSON.stringify({
|
|
191
|
+
type: 'stdio',
|
|
192
|
+
...buildCommandWithAgentString(mcpCommand, claudeCode),
|
|
193
|
+
});
|
|
194
|
+
const { stdout } = await execAsync('claude mcp list');
|
|
195
|
+
if (stdout.includes(mcpServerName)) {
|
|
196
|
+
SpinniesManager.update('claudeCode', {
|
|
197
|
+
text: commands.mcp.setup.spinners.alreadyInstalled,
|
|
198
|
+
});
|
|
199
|
+
await execAsync(`claude mcp remove "${mcpServerName}" --scope user`);
|
|
200
|
+
}
|
|
201
|
+
await execAsync(`claude mcp add-json "${mcpServerName}" ${JSON.stringify(mcpConfig)} --scope user`);
|
|
202
|
+
SpinniesManager.succeed('claudeCode', {
|
|
203
|
+
text: commands.mcp.setup.spinners.configuredClaudeCode,
|
|
204
|
+
});
|
|
205
|
+
return true;
|
|
264
206
|
}
|
|
265
207
|
catch (error) {
|
|
266
208
|
SpinniesManager.fail('claudeCode', {
|
|
@@ -306,7 +248,7 @@ export async function setupCodex(mcpCommand = defaultMcpCommand) {
|
|
|
306
248
|
return false;
|
|
307
249
|
}
|
|
308
250
|
const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, codex);
|
|
309
|
-
await execAsync(`codex mcp add "${mcpServerName}" -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}
|
|
251
|
+
await execAsync(`codex mcp add "${mcpServerName}" -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
|
|
310
252
|
SpinniesManager.succeed('codexSpinner', {
|
|
311
253
|
text: commands.mcp.setup.spinners.configuredCodex,
|
|
312
254
|
});
|
|
@@ -335,7 +277,7 @@ export async function setupGemini(mcpCommand = defaultMcpCommand) {
|
|
|
335
277
|
return false;
|
|
336
278
|
}
|
|
337
279
|
const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, gemini);
|
|
338
|
-
await execAsync(`gemini mcp add -s user "${mcpServerName}" ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}
|
|
280
|
+
await execAsync(`gemini mcp add -s user "${mcpServerName}" ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
|
|
339
281
|
SpinniesManager.succeed('geminiSpinner', {
|
|
340
282
|
text: commands.mcp.setup.spinners.configuredGemini,
|
|
341
283
|
});
|
|
@@ -354,16 +296,3 @@ function buildCommandWithAgentString(mcpCommand, agent) {
|
|
|
354
296
|
mcpCommandCopy.args.push('--ai-agent', agent);
|
|
355
297
|
return mcpCommandCopy;
|
|
356
298
|
}
|
|
357
|
-
function buildEnvFlagString(mcpCommand) {
|
|
358
|
-
const envFlags = [];
|
|
359
|
-
if (mcpCommand.env) {
|
|
360
|
-
const env = Object.entries(mcpCommand.env);
|
|
361
|
-
env.forEach(([key, value]) => {
|
|
362
|
-
envFlags.push(`--env ${key}=${value}`);
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
if (envFlags.length === 0) {
|
|
366
|
-
return '';
|
|
367
|
-
}
|
|
368
|
-
return ` ${envFlags.join(' ')}`;
|
|
369
|
-
}
|
|
@@ -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';
|