@hubspot/cli 8.0.8-experimental.5 → 8.0.8-experimental.7
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 -7
- package/lang/en.js +1 -8
- package/lib/__tests__/commandSuggestion.test.js +0 -2
- package/lib/cms/devServerProcess.d.ts +13 -0
- package/lib/cms/devServerProcess.js +122 -0
- package/lib/commandSuggestion.js +7 -1
- package/lib/getStartedV2Actions.d.ts +0 -13
- package/lib/getStartedV2Actions.js +0 -53
- 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 +3 -20
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +11 -7
- package/mcp-server/tools/project/CreateProjectTool.d.ts +4 -24
- package/mcp-server/tools/project/CreateProjectTool.js +11 -6
- 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 +8 -5
- package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
- package/mcp-server/tools/project/GetBuildLogsTool.js +7 -6
- package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
- package/mcp-server/tools/project/GetBuildStatusTool.js +4 -3
- package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +1 -6
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +6 -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__/GetApiUsagePatternsByAppIdTool.test.js +32 -0
- 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 -12
- package/mcp-server/tools/project/constants.js +16 -12
- 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
- package/ui/components/getStarted/GetStartedFlow.js +2 -79
- package/ui/components/getStarted/reducer.d.ts +0 -20
- package/ui/components/getStarted/reducer.js +0 -36
- package/ui/components/getStarted/screens/ProjectSetupScreen.js +1 -2
- package/ui/lib/constants.d.ts +0 -1
- package/ui/lib/constants.js +0 -1
- package/ui/components/getStarted/screens/InstallationScreen.d.ts +0 -7
- package/ui/components/getStarted/screens/InstallationScreen.js +0 -16
|
@@ -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
|
+
// Subprocess approach - don't import cms-dev-server directly 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 as subprocess to isolate React versions
|
|
76
|
+
// Calls createDevServer as library function to support standalone themes
|
|
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
|
@@ -43,11 +43,6 @@ export declare const commands: {
|
|
|
43
43
|
checkOutConfig: (configPath: string) => string;
|
|
44
44
|
pressEnterToInstall: (accountName: string) => string;
|
|
45
45
|
pressKeyToExit: string;
|
|
46
|
-
installingApp: (appName: string, accountName: string) => string;
|
|
47
|
-
installInstructions: string;
|
|
48
|
-
browserFailedToOpen: (url: string) => string;
|
|
49
|
-
pollingTimeout: (minutes: number) => string;
|
|
50
|
-
pressEnterToContinueSetup: string;
|
|
51
46
|
prompts: {
|
|
52
47
|
selectOptionV2: string;
|
|
53
48
|
options: {
|
|
@@ -1295,8 +1290,6 @@ export declare const commands: {
|
|
|
1295
1290
|
prompts: {
|
|
1296
1291
|
targets: string;
|
|
1297
1292
|
targetsRequired: string;
|
|
1298
|
-
standaloneMode: string;
|
|
1299
|
-
cliVersion: string;
|
|
1300
1293
|
};
|
|
1301
1294
|
};
|
|
1302
1295
|
start: {
|
package/lang/en.js
CHANGED
|
@@ -51,11 +51,6 @@ export const commands = {
|
|
|
51
51
|
checkOutConfig: (configPath) => `Check out ${chalk.cyan(configPath)} for the full configuration.`,
|
|
52
52
|
pressEnterToInstall: (accountName) => `? Press ${chalk.bold('<enter>')} to continue installing and previewing this app in ${chalk.bold(accountName)}`,
|
|
53
53
|
pressKeyToExit: `Press any key to exit...`,
|
|
54
|
-
installingApp: (appName, accountName) => `Installing ${chalk.bold(appName)} in ${chalk.bold(accountName)}...`,
|
|
55
|
-
installInstructions: `We'll take you to your HubSpot account and walk you through installing your app.`,
|
|
56
|
-
browserFailedToOpen: (url) => `⚠️ Failed to open browser automatically. Please open this URL manually:\n${chalk.cyan(url)}`,
|
|
57
|
-
pollingTimeout: (minutes) => `⚠️ Installation polling timed out after ${minutes} minutes. The app may still be installing in the background.`,
|
|
58
|
-
pressEnterToContinueSetup: `Press ${chalk.bold('<enter>')} to continue with card setup...`,
|
|
59
54
|
prompts: {
|
|
60
55
|
selectOptionV2: 'Choose a component type to get started',
|
|
61
56
|
options: {
|
|
@@ -1311,8 +1306,6 @@ export const commands = {
|
|
|
1311
1306
|
prompts: {
|
|
1312
1307
|
targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
|
|
1313
1308
|
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
1309
|
},
|
|
1317
1310
|
},
|
|
1318
1311
|
start: {
|
|
@@ -3083,7 +3076,7 @@ export const lib = {
|
|
|
3083
3076
|
updateSucceeded: (latestVersion) => `Successfully updated HubSpot CLI to version ${chalk.bold(latestVersion)}`,
|
|
3084
3077
|
notInstalledGlobally: 'Cannot auto-update the HubSpot CLI because NPM is not installed globally',
|
|
3085
3078
|
updateFailed: (latestVersion) => `Failed to update HubSpot CLI to version ${chalk.bold(latestVersion)}`,
|
|
3086
|
-
enableAutoUpdatesMessage: `The HubSpot CLI can automatically keep itself up to date.\n\nThis helps ensure compatibility with the HubSpot platform. You can change this later at any time.\n\nRun
|
|
3079
|
+
enableAutoUpdatesMessage: `The HubSpot CLI can automatically keep itself up to date.\n\nThis helps ensure compatibility with the HubSpot platform. You can change this later at any time.\n\nRun${uiCommandReference('hs config set --allow-auto-updates=true')}`,
|
|
3087
3080
|
},
|
|
3088
3081
|
},
|
|
3089
3082
|
projectProfiles: {
|
|
@@ -71,8 +71,6 @@ 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(),
|
|
76
74
|
};
|
|
77
75
|
await commandModule.builder(mockYargsBuilder);
|
|
78
76
|
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,122 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
export function spawnDevServer(options) {
|
|
3
|
+
const { absoluteSrc, accountName, noSsl, port, generateFieldsTypes, resetSession, dest, } = options;
|
|
4
|
+
// Build a Node script that imports and calls createDevServer
|
|
5
|
+
// This bypasses the CLI's project directory requirement while still isolating React versions
|
|
6
|
+
const script = `
|
|
7
|
+
const { createDevServer } = await import('@hubspot/cms-dev-server');
|
|
8
|
+
const { walk } = await import('@hubspot/local-dev-lib/fs');
|
|
9
|
+
const { createIgnoreFilter } = await import('@hubspot/local-dev-lib/ignoreRules');
|
|
10
|
+
const { isAllowedExtension } = await import('@hubspot/local-dev-lib/path');
|
|
11
|
+
const { FILE_UPLOAD_RESULT_TYPES } = await import('@hubspot/local-dev-lib/constants/files');
|
|
12
|
+
const cliProgress = (await import('cli-progress')).default;
|
|
13
|
+
|
|
14
|
+
${dest
|
|
15
|
+
? `
|
|
16
|
+
// Get uploadable files for preview (inlined from lib/upload.ts)
|
|
17
|
+
let filePaths = [];
|
|
18
|
+
try {
|
|
19
|
+
filePaths = await walk(${JSON.stringify(absoluteSrc)});
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error('Error walking directory:', e);
|
|
22
|
+
}
|
|
23
|
+
filePaths = filePaths
|
|
24
|
+
.filter(file => isAllowedExtension(file))
|
|
25
|
+
.filter(createIgnoreFilter(false));
|
|
26
|
+
|
|
27
|
+
// Create progress bar for initial upload
|
|
28
|
+
function startProgressBar(numFiles) {
|
|
29
|
+
const initialUploadProgressBar = new cliProgress.SingleBar(
|
|
30
|
+
{
|
|
31
|
+
gracefulExit: true,
|
|
32
|
+
format: '[{bar}] {percentage}% | {value}/{total} | {label}',
|
|
33
|
+
hideCursor: true,
|
|
34
|
+
},
|
|
35
|
+
cliProgress.Presets.rect
|
|
36
|
+
);
|
|
37
|
+
initialUploadProgressBar.start(numFiles, 0, {
|
|
38
|
+
label: 'Preparing upload...',
|
|
39
|
+
});
|
|
40
|
+
let uploadsHaveStarted = false;
|
|
41
|
+
return {
|
|
42
|
+
onAttemptCallback: () => {},
|
|
43
|
+
onSuccessCallback: () => {
|
|
44
|
+
initialUploadProgressBar.increment();
|
|
45
|
+
if (!uploadsHaveStarted) {
|
|
46
|
+
uploadsHaveStarted = true;
|
|
47
|
+
initialUploadProgressBar.update(0, {
|
|
48
|
+
label: 'Uploading files...',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
onFirstErrorCallback: () => {},
|
|
53
|
+
onRetryCallback: () => {},
|
|
54
|
+
onFinalErrorCallback: () => initialUploadProgressBar.increment(),
|
|
55
|
+
onFinishCallback: (results) => {
|
|
56
|
+
initialUploadProgressBar.update(numFiles, {
|
|
57
|
+
label: 'Upload complete',
|
|
58
|
+
});
|
|
59
|
+
initialUploadProgressBar.stop();
|
|
60
|
+
results.forEach(result => {
|
|
61
|
+
if (result.resultType == FILE_UPLOAD_RESULT_TYPES.FAILURE) {
|
|
62
|
+
console.error(\`Failed to upload \${result.file}\`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const themePreviewOptions = {
|
|
70
|
+
filePaths,
|
|
71
|
+
startProgressBar,
|
|
72
|
+
resetSession: ${Boolean(resetSession)},
|
|
73
|
+
dest: ${JSON.stringify(dest)},
|
|
74
|
+
};
|
|
75
|
+
`
|
|
76
|
+
: 'const themePreviewOptions = undefined;'}
|
|
77
|
+
|
|
78
|
+
createDevServer(
|
|
79
|
+
${JSON.stringify(absoluteSrc)},
|
|
80
|
+
false, // storybook
|
|
81
|
+
'', // config (uses env var HUBSPOT_CONFIG_PATH)
|
|
82
|
+
${JSON.stringify(accountName || '')},
|
|
83
|
+
${!noSsl}, // sslEnabled
|
|
84
|
+
${Boolean(generateFieldsTypes)}, // fieldGenEnabled
|
|
85
|
+
themePreviewOptions
|
|
86
|
+
);
|
|
87
|
+
`;
|
|
88
|
+
// Set environment variables
|
|
89
|
+
const env = { ...process.env };
|
|
90
|
+
if (port) {
|
|
91
|
+
env.PORT = port.toString();
|
|
92
|
+
}
|
|
93
|
+
// Spawn Node with the inline script
|
|
94
|
+
const devServer = spawn('node', ['--input-type=module', '-e', script], {
|
|
95
|
+
stdio: 'inherit',
|
|
96
|
+
env,
|
|
97
|
+
cwd: absoluteSrc,
|
|
98
|
+
});
|
|
99
|
+
// Handle process events
|
|
100
|
+
devServer.on('error', error => {
|
|
101
|
+
console.error('Failed to start dev server:', error);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
104
|
+
devServer.on('exit', (code, signal) => {
|
|
105
|
+
if (code !== 0 && code !== null) {
|
|
106
|
+
console.error(`Dev server exited with code ${code}`);
|
|
107
|
+
process.exit(code);
|
|
108
|
+
}
|
|
109
|
+
if (signal) {
|
|
110
|
+
console.error(`Dev server killed with signal ${signal}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// Handle CLI termination
|
|
115
|
+
process.on('SIGINT', () => {
|
|
116
|
+
devServer.kill('SIGINT');
|
|
117
|
+
});
|
|
118
|
+
process.on('SIGTERM', () => {
|
|
119
|
+
devServer.kill('SIGTERM');
|
|
120
|
+
});
|
|
121
|
+
return devServer;
|
|
122
|
+
}
|
package/lib/commandSuggestion.js
CHANGED
|
@@ -18,6 +18,12 @@ 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',
|
|
21
27
|
};
|
|
22
28
|
function createCommandSuggestionHandler(newCommand) {
|
|
23
29
|
return () => {
|
|
@@ -29,7 +35,7 @@ function createCommandSuggestion(oldCommand, newCommand) {
|
|
|
29
35
|
return {
|
|
30
36
|
command: oldCommand,
|
|
31
37
|
builder: async (yargs) => {
|
|
32
|
-
return yargs.strict(false)
|
|
38
|
+
return yargs.strict(false);
|
|
33
39
|
},
|
|
34
40
|
handler: createCommandSuggestionHandler(newCommand),
|
|
35
41
|
};
|
|
@@ -35,16 +35,3 @@ export declare function uploadAndDeployAction({ accountId, projectDest, }: {
|
|
|
35
35
|
projectDest: string;
|
|
36
36
|
}): Promise<UploadAndDeployResult>;
|
|
37
37
|
export declare function trackGetStartedUsage(params: Record<string, unknown>, accountId: number): Promise<void>;
|
|
38
|
-
export type PollAppInstallationOptions = {
|
|
39
|
-
accountId: number;
|
|
40
|
-
projectId: number;
|
|
41
|
-
appUid: string;
|
|
42
|
-
requiredScopes?: string[];
|
|
43
|
-
optionalScopes?: string[];
|
|
44
|
-
timeoutMs?: number;
|
|
45
|
-
intervalMs?: number;
|
|
46
|
-
onTimeout?: () => void;
|
|
47
|
-
};
|
|
48
|
-
export declare function pollAppInstallation({ accountId, projectId, appUid, requiredScopes, optionalScopes, timeoutMs, // 2 minutes
|
|
49
|
-
intervalMs, // 2 seconds
|
|
50
|
-
onTimeout, }: PollAppInstallationOptions): Promise<void>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fetchPublicAppsForPortal } from '@hubspot/local-dev-lib/api/appsDev';
|
|
4
|
-
import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
|
|
5
4
|
import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
|
|
6
5
|
import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
|
|
7
6
|
import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
|
|
@@ -145,55 +144,3 @@ export async function uploadAndDeployAction({ accountId, projectDest, }) {
|
|
|
145
144
|
export function trackGetStartedUsage(params, accountId) {
|
|
146
145
|
return trackCommandMetadataUsage('get-started', params, accountId);
|
|
147
146
|
}
|
|
148
|
-
export async function pollAppInstallation({ accountId, projectId, appUid, requiredScopes = [], optionalScopes = [], timeoutMs = 2 * 60 * 1000, // 2 minutes
|
|
149
|
-
intervalMs = 2000, // 2 seconds
|
|
150
|
-
onTimeout, }) {
|
|
151
|
-
return new Promise((resolve, reject) => {
|
|
152
|
-
let consecutiveErrors = 0;
|
|
153
|
-
const MAX_CONSECUTIVE_ERRORS = 5;
|
|
154
|
-
let pollInterval = null;
|
|
155
|
-
let pollTimeout = null;
|
|
156
|
-
const cleanup = () => {
|
|
157
|
-
if (pollInterval) {
|
|
158
|
-
clearTimeout(pollInterval);
|
|
159
|
-
pollInterval = null;
|
|
160
|
-
}
|
|
161
|
-
if (pollTimeout) {
|
|
162
|
-
clearTimeout(pollTimeout);
|
|
163
|
-
pollTimeout = null;
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
pollTimeout = setTimeout(() => {
|
|
167
|
-
cleanup();
|
|
168
|
-
if (onTimeout) {
|
|
169
|
-
onTimeout();
|
|
170
|
-
}
|
|
171
|
-
resolve(); // Resolve instead of reject to allow continuing with timeout state
|
|
172
|
-
}, timeoutMs);
|
|
173
|
-
const poll = async () => {
|
|
174
|
-
try {
|
|
175
|
-
const { data } = await fetchAppInstallationData(accountId, projectId, appUid, requiredScopes, optionalScopes);
|
|
176
|
-
// Reset error counter on successful fetch
|
|
177
|
-
consecutiveErrors = 0;
|
|
178
|
-
if (data.isInstalledWithScopeGroups) {
|
|
179
|
-
cleanup();
|
|
180
|
-
resolve();
|
|
181
|
-
}
|
|
182
|
-
else if (pollInterval) {
|
|
183
|
-
pollInterval = setTimeout(poll, intervalMs);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
consecutiveErrors++;
|
|
188
|
-
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
189
|
-
cleanup();
|
|
190
|
-
reject(new Error(`Failed to check app installation status after ${MAX_CONSECUTIVE_ERRORS} consecutive errors`, { cause: error }));
|
|
191
|
-
}
|
|
192
|
-
else if (pollInterval !== null) {
|
|
193
|
-
pollInterval = setTimeout(poll, intervalMs);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
pollInterval = setTimeout(poll, 0);
|
|
198
|
-
});
|
|
199
|
-
}
|
|
@@ -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';
|