@hubspot/cli 8.0.11-experimental.2 → 8.0.12-experimental.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +4 -3
- package/commands/account/clean.js +2 -0
- package/commands/account/createOverride.js +3 -0
- package/commands/account/info.js +34 -16
- package/commands/account/link.d.ts +4 -0
- package/commands/account/link.js +89 -0
- package/commands/account/list.js +29 -71
- package/commands/account/remove.js +2 -0
- package/commands/account/removeOverride.js +3 -0
- package/commands/account/unlink.d.ts +4 -0
- package/commands/account/unlink.js +70 -0
- package/commands/account/use.js +71 -1
- package/commands/account.js +4 -0
- package/commands/project/appInstallStatus.d.ts +4 -0
- package/commands/project/appInstallStatus.js +132 -0
- package/commands/project/create.js +8 -0
- package/commands/project/dev/deprecatedFlow.js +20 -2
- package/commands/project/dev/index.js +6 -0
- package/commands/project/dev/unifiedFlow.js +20 -3
- package/commands/project/lint.js +20 -2
- package/commands/project/upload.d.ts +2 -0
- package/commands/project/upload.js +47 -3
- package/commands/project.js +2 -0
- package/lang/en.d.ts +122 -0
- package/lang/en.js +136 -8
- package/lib/app/migrate.js +2 -1
- package/lib/constants.d.ts +2 -0
- package/lib/constants.js +4 -0
- package/lib/doctor/Doctor.js +5 -5
- package/lib/link/accountTableUtils.d.ts +10 -0
- package/lib/link/accountTableUtils.js +39 -0
- package/lib/link/index.d.ts +18 -0
- package/lib/link/index.js +185 -0
- package/lib/link/linkUtils.d.ts +5 -0
- package/lib/link/linkUtils.js +49 -0
- package/lib/link/prompts.d.ts +7 -0
- package/lib/link/prompts.js +126 -0
- package/lib/link/renderLinkedAccountsTable.d.ts +2 -0
- package/lib/link/renderLinkedAccountsTable.js +14 -0
- package/lib/link/warnIfLinkedDirectory.d.ts +1 -0
- package/lib/link/warnIfLinkedDirectory.js +9 -0
- package/lib/projects/ProjectLogsManager.js +4 -1
- package/lib/projects/localDev/DevServerManager_DEPRECATED.d.ts +2 -1
- package/lib/projects/localDev/DevServerManager_DEPRECATED.js +2 -2
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.d.ts +2 -0
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -0
- package/lib/projects/preview.d.ts +7 -0
- package/lib/projects/preview.js +58 -0
- package/lib/projects/uieLinting.d.ts +17 -3
- package/lib/projects/uieLinting.js +93 -28
- package/lib/projects/upload.d.ts +1 -0
- package/lib/projects/upload.js +4 -3
- package/lib/prompts/projectsLogsPrompt.js +3 -0
- package/lib/prompts/promptUtils.js +1 -0
- package/lib/ui/accountTable.d.ts +8 -0
- package/lib/ui/accountTable.js +67 -0
- package/lib/yargs/parseYargsOrExit.d.ts +4 -0
- package/lib/yargs/parseYargsOrExit.js +25 -0
- package/mcp-server/server.js +39 -1
- package/mcp-server/tools/index.js +2 -0
- package/mcp-server/tools/project/AddFeatureToProjectTool.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/FindProjectsTool.d.ts +15 -0
- package/mcp-server/tools/project/FindProjectsTool.js +60 -0
- package/mcp-server/tools/project/GetBuildLogsTool.js +1 -1
- package/mcp-server/tools/project/GetBuildStatusTool.js +1 -1
- package/mcp-server/tools/project/UploadProjectTools.js +1 -1
- package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
- package/package.json +7 -7
- package/types/Link.d.ts +32 -0
- package/types/Link.js +5 -0
- package/types/PackageJson.d.ts +1 -0
- package/types/Prompts.d.ts +1 -0
- package/types/Yargs.d.ts +1 -0
|
@@ -3,17 +3,27 @@ import path from 'path';
|
|
|
3
3
|
import util from 'util';
|
|
4
4
|
import semver from 'semver';
|
|
5
5
|
import { exec as execAsync } from 'node:child_process';
|
|
6
|
+
import { fetchRepoFile } from '@hubspot/local-dev-lib/api/github';
|
|
6
7
|
import { getProjectPackageJsonLocations, isPackageInstalled, } from '../dependencyManagement.js';
|
|
7
8
|
import { commands } from '../../lang/en.js';
|
|
8
9
|
import { uiLogger } from '../ui/logger.js';
|
|
9
|
-
import { safeGetPackageJsonCached } from '../npm/packageJson.js';
|
|
10
|
+
import { clearPackageJsonCache, safeGetPackageJsonCached, } from '../npm/packageJson.js';
|
|
11
|
+
import { debugError } from '../errorHandlers/index.js';
|
|
12
|
+
import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
|
|
13
|
+
import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, DEFAULT_PROJECT_TEMPLATE_BRANCH, } from '../constants.js';
|
|
10
14
|
export const REQUIRED_PACKAGES_AND_MIN_VERSIONS = {
|
|
11
15
|
eslint: '9.0.0',
|
|
12
|
-
'@
|
|
13
|
-
'@typescript-eslint/parser': '8.46.4',
|
|
16
|
+
'@eslint/js': '9.0.0',
|
|
14
17
|
'typescript-eslint': '8.46.4',
|
|
18
|
+
'@hubspot/eslint-config-ui-extensions': '1.0.0',
|
|
19
|
+
'eslint-config-prettier': '10.0.0',
|
|
20
|
+
'eslint-plugin-react': '7.0.0',
|
|
21
|
+
'eslint-plugin-react-hooks': '7.0.0',
|
|
22
|
+
'eslint-plugin-unused-imports': '4.0.0',
|
|
23
|
+
prettier: '3.0.0',
|
|
15
24
|
jiti: '2.6.1',
|
|
16
25
|
};
|
|
26
|
+
const UIE_ESLINT_CONFIG_PATH_IN_REPO = 'components/cards/src/app/cards/eslint.config.js';
|
|
17
27
|
const ESLINT_CONFIG_FILES = [
|
|
18
28
|
'eslint.config.mts',
|
|
19
29
|
'eslint.config.ts',
|
|
@@ -30,28 +40,10 @@ const DEPRECATED_ESLINT_CONFIG_FILES = [
|
|
|
30
40
|
'.eslintrc.json',
|
|
31
41
|
'.eslintrc',
|
|
32
42
|
];
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
|
39
|
-
languageOptions: {
|
|
40
|
-
parser: tsParser,
|
|
41
|
-
parserOptions: {
|
|
42
|
-
ecmaVersion: "latest",
|
|
43
|
-
sourceType: "module",
|
|
44
|
-
ecmaFeatures: {
|
|
45
|
-
jsx: true
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
rules: {
|
|
50
|
-
"no-console": ["warn", { allow: ["warn", "error"] }]
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
]);
|
|
54
|
-
`;
|
|
43
|
+
export const LINT_SCRIPTS = {
|
|
44
|
+
lint: 'eslint .',
|
|
45
|
+
'lint:fix': 'eslint . --fix',
|
|
46
|
+
};
|
|
55
47
|
function getPackageVersionFromPackageJson(directory, packageName) {
|
|
56
48
|
const packageJsonPath = path.join(directory, 'package.json');
|
|
57
49
|
const packageJson = safeGetPackageJsonCached(packageJsonPath);
|
|
@@ -127,10 +119,43 @@ export function getDeprecatedEslintConfigFiles(directory) {
|
|
|
127
119
|
return fs.existsSync(configPath);
|
|
128
120
|
});
|
|
129
121
|
}
|
|
130
|
-
|
|
131
|
-
|
|
122
|
+
function repoFileDataToString(data) {
|
|
123
|
+
if (typeof data === 'string') {
|
|
124
|
+
return data;
|
|
125
|
+
}
|
|
126
|
+
if (Buffer.isBuffer(data)) {
|
|
127
|
+
return data.toString('utf-8');
|
|
128
|
+
}
|
|
129
|
+
return String(data);
|
|
130
|
+
}
|
|
131
|
+
export async function createEslintConfig(directory, platformVersion) {
|
|
132
|
+
const versionForRemote = platformVersion && !isLegacyProject(platformVersion)
|
|
133
|
+
? platformVersion
|
|
134
|
+
: null;
|
|
135
|
+
if (versionForRemote === null) {
|
|
136
|
+
const message = commands.project.lint.createEslintConfigRequiresV2Platform(platformVersion);
|
|
137
|
+
uiLogger.error(message);
|
|
138
|
+
throw new Error(message);
|
|
139
|
+
}
|
|
140
|
+
let fetchedContent = null;
|
|
132
141
|
try {
|
|
133
|
-
|
|
142
|
+
const { data } = await fetchRepoFile(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, `${versionForRemote}/${UIE_ESLINT_CONFIG_PATH_IN_REPO}`, DEFAULT_PROJECT_TEMPLATE_BRANCH);
|
|
143
|
+
const content = repoFileDataToString(data);
|
|
144
|
+
if (content.trim().length > 0) {
|
|
145
|
+
fetchedContent = content;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
debugError(error);
|
|
150
|
+
}
|
|
151
|
+
if (fetchedContent === null) {
|
|
152
|
+
const message = commands.project.lint.failedToFetchRemoteEslintConfig(versionForRemote);
|
|
153
|
+
uiLogger.error(message);
|
|
154
|
+
throw new Error(message);
|
|
155
|
+
}
|
|
156
|
+
const configPath = path.join(directory, 'eslint.config.js');
|
|
157
|
+
try {
|
|
158
|
+
fs.writeFileSync(configPath, fetchedContent, 'utf-8');
|
|
134
159
|
return path.relative(process.cwd(), configPath);
|
|
135
160
|
}
|
|
136
161
|
catch (error) {
|
|
@@ -218,3 +243,43 @@ export function displayLintResults(results) {
|
|
|
218
243
|
});
|
|
219
244
|
}
|
|
220
245
|
}
|
|
246
|
+
export function getMissingLintScripts(directory) {
|
|
247
|
+
const packageJsonPath = path.join(directory, 'package.json');
|
|
248
|
+
const packageJson = safeGetPackageJsonCached(packageJsonPath);
|
|
249
|
+
if (!packageJson) {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
return Object.keys(LINT_SCRIPTS).filter(scriptName => !packageJson.scripts?.[scriptName]);
|
|
253
|
+
}
|
|
254
|
+
export function addLintScriptsToPackageJson(directory) {
|
|
255
|
+
const packageJsonPath = path.join(directory, 'package.json');
|
|
256
|
+
try {
|
|
257
|
+
const rawContent = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
258
|
+
const packageJson = JSON.parse(rawContent);
|
|
259
|
+
if (!packageJson.scripts) {
|
|
260
|
+
packageJson.scripts = {};
|
|
261
|
+
}
|
|
262
|
+
const added = [];
|
|
263
|
+
for (const [scriptName, scriptValue] of Object.entries(LINT_SCRIPTS)) {
|
|
264
|
+
if (!packageJson.scripts[scriptName]) {
|
|
265
|
+
packageJson.scripts[scriptName] = scriptValue;
|
|
266
|
+
added.push(scriptName);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (added.length > 0) {
|
|
270
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
|
|
271
|
+
clearPackageJsonCache();
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
added,
|
|
275
|
+
relativePath: path.relative(process.cwd(), packageJsonPath),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
uiLogger.warn(commands.project.lint.failedToAddLintScripts(packageJsonPath));
|
|
280
|
+
return {
|
|
281
|
+
added: [],
|
|
282
|
+
relativePath: path.relative(process.cwd(), packageJsonPath),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
package/lib/projects/upload.d.ts
CHANGED
package/lib/projects/upload.js
CHANGED
|
@@ -79,7 +79,7 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
|
|
|
79
79
|
return resolve({ uploadError: e });
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
const { projectExists } = await ensureProjectExists(accountId, projectConfig.name, {
|
|
82
|
+
const { projectExists, project } = await ensureProjectExists(accountId, projectConfig.name, {
|
|
83
83
|
forceCreate,
|
|
84
84
|
uploadCommand: isUploadCommand,
|
|
85
85
|
noLogs: true,
|
|
@@ -88,13 +88,14 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
|
|
|
88
88
|
uiLogger.log(lib.projectUpload.handleProjectUpload.projectDoesNotExist(accountId));
|
|
89
89
|
return resolve({ projectNotFound: true });
|
|
90
90
|
}
|
|
91
|
+
const projectId = project?.id;
|
|
91
92
|
const { buildId, error } = await uploadProjectFiles(accountId, projectConfig.name, tempFile.name, uploadMessage, projectConfig.platformVersion, intermediateRepresentation);
|
|
92
93
|
if (error) {
|
|
93
|
-
resolve({ uploadError: error });
|
|
94
|
+
resolve({ uploadError: error, projectId });
|
|
94
95
|
}
|
|
95
96
|
else if (callbackFunc) {
|
|
96
97
|
const uploadResult = await callbackFunc(accountId, projectConfig, tempFile, buildId);
|
|
97
|
-
resolve({ result: uploadResult });
|
|
98
|
+
resolve({ result: uploadResult, projectId });
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
catch (e) {
|
|
@@ -4,6 +4,9 @@ export async function projectLogsPrompt({ functionChoices, promptOptions, projec
|
|
|
4
4
|
if (!functionChoices) {
|
|
5
5
|
return {};
|
|
6
6
|
}
|
|
7
|
+
if (promptOptions?.function) {
|
|
8
|
+
return { functionName: promptOptions.function };
|
|
9
|
+
}
|
|
7
10
|
if (functionChoices.length === 1) {
|
|
8
11
|
return { functionName: functionChoices[0] };
|
|
9
12
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
|
|
2
|
+
export declare function sortAndMapAccounts(accounts: HubSpotConfigAccount[]): {
|
|
3
|
+
[key: string]: HubSpotConfigAccount[];
|
|
4
|
+
};
|
|
5
|
+
export declare function getAccountData(mappedAccountData: {
|
|
6
|
+
[key: string]: HubSpotConfigAccount[];
|
|
7
|
+
}): string[][];
|
|
8
|
+
export declare function renderAccountTable(showAllLabel?: boolean): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { HUBSPOT_ACCOUNT_TYPES, HUBSPOT_ACCOUNT_TYPE_STRINGS, } from '@hubspot/local-dev-lib/constants/config';
|
|
2
|
+
import { isSandbox, isDeveloperTestAccount } from '../accountTypes.js';
|
|
3
|
+
import { getAllConfigAccounts } from '@hubspot/local-dev-lib/config';
|
|
4
|
+
import { commands } from '../../lang/en.js';
|
|
5
|
+
import { renderTable } from '../../ui/render.js';
|
|
6
|
+
import { uiLogger } from './logger.js';
|
|
7
|
+
export function sortAndMapAccounts(accounts) {
|
|
8
|
+
const mappedAccountData = {};
|
|
9
|
+
// Standard and app developer accounts
|
|
10
|
+
accounts
|
|
11
|
+
.filter(p => p.accountType &&
|
|
12
|
+
(p.accountType === HUBSPOT_ACCOUNT_TYPES.STANDARD ||
|
|
13
|
+
p.accountType === HUBSPOT_ACCOUNT_TYPES.APP_DEVELOPER))
|
|
14
|
+
.forEach(account => {
|
|
15
|
+
mappedAccountData[account.accountId] = [account];
|
|
16
|
+
});
|
|
17
|
+
// Non-standard accounts (sandbox, developer test account)
|
|
18
|
+
accounts
|
|
19
|
+
.filter(p => p.accountType && (isSandbox(p) || isDeveloperTestAccount(p)))
|
|
20
|
+
.forEach(p => {
|
|
21
|
+
if (p.parentAccountId) {
|
|
22
|
+
mappedAccountData[p.parentAccountId] = [
|
|
23
|
+
...(mappedAccountData[p.parentAccountId] || []),
|
|
24
|
+
p,
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
mappedAccountData[p.accountId] = [p];
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return mappedAccountData;
|
|
32
|
+
}
|
|
33
|
+
export function getAccountData(mappedAccountData) {
|
|
34
|
+
const accountData = [];
|
|
35
|
+
Object.entries(mappedAccountData).forEach(([key, set]) => {
|
|
36
|
+
const hasParentAccount = set.filter(p => p.accountId === parseInt(key, 10))[0];
|
|
37
|
+
set.forEach(account => {
|
|
38
|
+
let name = `${account.name} [${HUBSPOT_ACCOUNT_TYPE_STRINGS[account.accountType]}]`;
|
|
39
|
+
if (isSandbox(account)) {
|
|
40
|
+
if (hasParentAccount && set.length > 1) {
|
|
41
|
+
name = `↳ ${name}`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (isDeveloperTestAccount(account)) {
|
|
45
|
+
if (hasParentAccount && set.length > 1) {
|
|
46
|
+
name = `↳ ${name}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
accountData.push([name, String(account.accountId), account.authType]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return accountData;
|
|
53
|
+
}
|
|
54
|
+
export function renderAccountTable(showAllLabel = false) {
|
|
55
|
+
const accountsList = getAllConfigAccounts();
|
|
56
|
+
const mappedAccountData = sortAndMapAccounts(accountsList);
|
|
57
|
+
const accountData = getAccountData(mappedAccountData);
|
|
58
|
+
const tableHeader = [
|
|
59
|
+
commands.account.subcommands.list.labels.name,
|
|
60
|
+
commands.account.subcommands.list.labels.accountId,
|
|
61
|
+
commands.account.subcommands.list.labels.authType,
|
|
62
|
+
];
|
|
63
|
+
uiLogger.log(showAllLabel
|
|
64
|
+
? commands.account.subcommands.list.allAccounts
|
|
65
|
+
: commands.account.subcommands.list.accounts);
|
|
66
|
+
renderTable(tableHeader, accountData, true);
|
|
67
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ArgumentsCamelCase, Argv } from 'yargs';
|
|
2
|
+
type YargsFailureHandler<T> = (message: string | null, error: unknown, parser: Argv<T>) => never;
|
|
3
|
+
export declare function parseYargsOrExit<T>(parser: Argv<T>, handleFailure: YargsFailureHandler<T>): Promise<ArgumentsCamelCase<T>>;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function getYargsErrorMessage(error) {
|
|
2
|
+
return error instanceof Error && error.name === 'YError' ? error.message : '';
|
|
3
|
+
}
|
|
4
|
+
function getYargsFailureMessage(message, error) {
|
|
5
|
+
if (message) {
|
|
6
|
+
return message;
|
|
7
|
+
}
|
|
8
|
+
return getYargsErrorMessage(error) || message;
|
|
9
|
+
}
|
|
10
|
+
export async function parseYargsOrExit(parser, handleFailure) {
|
|
11
|
+
let failureHandled = false;
|
|
12
|
+
const parserWithFailureHandler = parser.fail((message, error, yargs) => {
|
|
13
|
+
failureHandled = true;
|
|
14
|
+
return handleFailure(getYargsFailureMessage(message, error), error, yargs);
|
|
15
|
+
});
|
|
16
|
+
try {
|
|
17
|
+
return await parserWithFailureHandler.parseAsync();
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (failureHandled) {
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
return handleFailure(getYargsErrorMessage(error), error, parserWithFailureHandler);
|
|
24
|
+
}
|
|
25
|
+
}
|
package/mcp-server/server.js
CHANGED
|
@@ -2,11 +2,49 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { registerProjectTools, registerCmsTools } from './tools/index.js';
|
|
4
4
|
import { McpLogger } from './utils/logger.js';
|
|
5
|
+
const instructions = `
|
|
6
|
+
This server exposes the HubSpot CLI (\`hs\`) for local development of HubSpot
|
|
7
|
+
projects, apps, and CMS assets. Prefer these tools over running \`hs\`,
|
|
8
|
+
\`npx hs\`, or HubSpot HTTP APIs directly via shell — they handle config
|
|
9
|
+
loading, auth, platform-version flags, and structured output for you.
|
|
10
|
+
|
|
11
|
+
WHEN TO USE THIS SERVER
|
|
12
|
+
- The user is working in a HubSpot project directory (has an hsproject.json
|
|
13
|
+
or *-hsmeta.json files), or wants to scaffold one.
|
|
14
|
+
- The user asks about HubSpot apps, CMS modules/templates/serverless
|
|
15
|
+
functions, project builds, deploys, or developer test accounts.
|
|
16
|
+
- The user asks a HubSpot platform/API question — answer it from the docs
|
|
17
|
+
via \`search-docs\` + \`fetch-doc\` rather than from prior knowledge.
|
|
18
|
+
|
|
19
|
+
REQUIRED WORKFLOWS
|
|
20
|
+
1. Documentation lookup: always call \`search-docs\` first, then
|
|
21
|
+
\`fetch-doc\` on the most relevant result(s) before planning, writing
|
|
22
|
+
code, or answering platform/API questions. Do not answer from memory.
|
|
23
|
+
2. Locating a HubSpot project: when the current working directory is not
|
|
24
|
+
a HubSpot project (no \`hsproject.json\`) or you need to determine
|
|
25
|
+
whether a directory contains one, call \`find-projects\` before
|
|
26
|
+
running any tool that requires a project path.
|
|
27
|
+
3. Editing \`*-hsmeta.json\`: call \`get-feature-config-schema\` for that
|
|
28
|
+
feature type first to learn the allowed fields and values.
|
|
29
|
+
4. Debugging a failed build: start with \`get-build-status\` to surface
|
|
30
|
+
error messages, and only reach for \`get-build-logs\` for deeper
|
|
31
|
+
troubleshooting or warnings.
|
|
32
|
+
5. Reading serverless function logs: call \`list-cms-serverless-functions\`
|
|
33
|
+
first to discover the endpoint path, then
|
|
34
|
+
\`get-cms-serverless-function-logs\`.
|
|
35
|
+
6. App analytics: call \`get-apps-info\` to discover \`appId\` values
|
|
36
|
+
before \`get-api-usage-patterns-by-app-id\`.
|
|
37
|
+
|
|
38
|
+
OUTPUT
|
|
39
|
+
Tool results contain the relevant \`hs\` stdout/stderr or structured data.
|
|
40
|
+
Surface error text from results to the user verbatim when troubleshooting,
|
|
41
|
+
rather than paraphrasing.
|
|
42
|
+
`.trim();
|
|
5
43
|
const server = new McpServer({
|
|
6
44
|
name: 'HubSpot CLI MCP Server',
|
|
7
45
|
version: '0.0.1',
|
|
8
46
|
description: 'Helps perform tasks for local development of HubSpot projects.',
|
|
9
|
-
}, { capabilities: { logging: {} } });
|
|
47
|
+
}, { capabilities: { logging: {} }, instructions });
|
|
10
48
|
const logger = new McpLogger(server);
|
|
11
49
|
registerProjectTools(server, logger);
|
|
12
50
|
registerCmsTools(server, logger);
|
|
@@ -18,6 +18,7 @@ import { HsCreateFunctionTool } from './cms/HsCreateFunctionTool.js';
|
|
|
18
18
|
import { HsListFunctionsTool } from './cms/HsListFunctionsTool.js';
|
|
19
19
|
import { HsFunctionLogsTool } from './cms/HsFunctionLogsTool.js';
|
|
20
20
|
import { CreateTestAccountTool } from './project/CreateTestAccountTool.js';
|
|
21
|
+
import { FindProjectsTool } from './project/FindProjectsTool.js';
|
|
21
22
|
export function registerProjectTools(mcpServer, logger) {
|
|
22
23
|
return [
|
|
23
24
|
new UploadProjectTools(mcpServer, logger).register(),
|
|
@@ -34,6 +35,7 @@ export function registerProjectTools(mcpServer, logger) {
|
|
|
34
35
|
new GetApplicationInfoTool(mcpServer, logger).register(),
|
|
35
36
|
new GetBuildLogsTool(mcpServer, logger).register(),
|
|
36
37
|
new GetBuildStatusTool(mcpServer, logger).register(),
|
|
38
|
+
new FindProjectsTool(mcpServer, logger).register(),
|
|
37
39
|
];
|
|
38
40
|
}
|
|
39
41
|
export function registerCmsTools(mcpServer, logger) {
|
|
@@ -71,7 +71,7 @@ export class AddFeatureToProjectTool extends Tool {
|
|
|
71
71
|
return this.mcpServer.registerTool(toolName, {
|
|
72
72
|
title: 'Add feature to HubSpot Project',
|
|
73
73
|
description: `Adds a feature to an existing HubSpot project.
|
|
74
|
-
Only works for projects with platformVersion '2025.2' and beyond
|
|
74
|
+
Only works for projects with platformVersion '2025.2' and beyond. If you do not know the project path, use the find-projects tool first to locate HubSpot projects in the workspace.`,
|
|
75
75
|
inputSchema,
|
|
76
76
|
annotations: {
|
|
77
77
|
readOnlyHint: false,
|
|
@@ -57,7 +57,7 @@ const inputSchema = {
|
|
|
57
57
|
contentLevel: z
|
|
58
58
|
.enum(ACCOUNT_LEVEL_CHOICES)
|
|
59
59
|
.optional()
|
|
60
|
-
.describe(`
|
|
60
|
+
.describe(`Content Hub tier level. Options: ${ACCOUNT_LEVEL_CHOICES.join(', ')}. Defaults to ENTERPRISE if not specified.`),
|
|
61
61
|
commerceLevel: z
|
|
62
62
|
.enum(ACCOUNT_LEVEL_CHOICES_WITHOUT_STARTER)
|
|
63
63
|
.optional()
|
|
@@ -43,7 +43,7 @@ export class DeployProjectTool extends Tool {
|
|
|
43
43
|
register() {
|
|
44
44
|
return this.mcpServer.registerTool(toolName, {
|
|
45
45
|
title: 'Deploy a build of HubSpot Project',
|
|
46
|
-
description: 'Takes a build number and a project name and deploys that build of the project. DO NOT run this tool unless the user specifies they would like to deploy the project.',
|
|
46
|
+
description: 'Takes a build number and a project name and deploys that build of the project. DO NOT run this tool unless the user specifies they would like to deploy the project. If you do not know the project path, use the find-projects tool first to locate HubSpot projects in the workspace.',
|
|
47
47
|
inputSchema,
|
|
48
48
|
annotations: {
|
|
49
49
|
readOnlyHint: false,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TextContentResponse } from '../../types.js';
|
|
2
|
+
import { Tool } from '../../Tool.js';
|
|
3
|
+
import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { McpLogger } from '../../utils/logger.js';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
declare const inputSchemaZodObject: z.ZodObject<{
|
|
7
|
+
absoluteDirectory: z.ZodString;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export type FindProjectsInputSchema = z.infer<typeof inputSchemaZodObject>;
|
|
10
|
+
export declare class FindProjectsTool extends Tool<FindProjectsInputSchema> {
|
|
11
|
+
constructor(mcpServer: McpServer, logger: McpLogger);
|
|
12
|
+
handler({ absoluteDirectory, }: FindProjectsInputSchema): Promise<TextContentResponse>;
|
|
13
|
+
register(): RegisteredTool;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Tool } from '../../Tool.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { formatTextContents } from '../../utils/content.js';
|
|
4
|
+
import { walk } from '@hubspot/local-dev-lib/fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { PROJECT_CONFIG_FILE } from '../../../lib/constants.js';
|
|
7
|
+
const TOOL_NAME = 'find-projects';
|
|
8
|
+
const IGNORE_DIRS = ['node_modules', '.git', '.vite'];
|
|
9
|
+
const inputSchema = {
|
|
10
|
+
absoluteDirectory: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The absolute path to the directory to search for HubSpot projects in.'),
|
|
13
|
+
};
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
|
+
const inputSchemaZodObject = z.object({ ...inputSchema });
|
|
16
|
+
export class FindProjectsTool extends Tool {
|
|
17
|
+
constructor(mcpServer, logger) {
|
|
18
|
+
super(mcpServer, logger, TOOL_NAME);
|
|
19
|
+
}
|
|
20
|
+
async handler({ absoluteDirectory, }) {
|
|
21
|
+
try {
|
|
22
|
+
const allFiles = await walk(absoluteDirectory, IGNORE_DIRS);
|
|
23
|
+
const projectFiles = allFiles.filter(file => path.basename(file) === PROJECT_CONFIG_FILE);
|
|
24
|
+
if (projectFiles.length === 0) {
|
|
25
|
+
return formatTextContents(`No ${PROJECT_CONFIG_FILE} files found under ${absoluteDirectory}.`);
|
|
26
|
+
}
|
|
27
|
+
const projectDirs = projectFiles.map(file => path.dirname(file));
|
|
28
|
+
const output = [
|
|
29
|
+
`Found ${projectFiles.length} project(s):`,
|
|
30
|
+
'',
|
|
31
|
+
...projectDirs.map(dir => ` ${dir}`),
|
|
32
|
+
].join('\n');
|
|
33
|
+
return formatTextContents(output);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
this.logger.debug(TOOL_NAME, {
|
|
37
|
+
message: 'Handler caught error',
|
|
38
|
+
error: error instanceof Error ? error.message : String(error),
|
|
39
|
+
});
|
|
40
|
+
const cause = error instanceof Error ? error.cause : undefined;
|
|
41
|
+
const causeMessage = cause instanceof Error ? `: ${cause.message}` : '';
|
|
42
|
+
const errorMessage = error instanceof Error
|
|
43
|
+
? error.message || `FileSystemError${causeMessage}`
|
|
44
|
+
: String(error);
|
|
45
|
+
return formatTextContents(`Error searching for projects: ${errorMessage}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
register() {
|
|
49
|
+
return this.mcpServer.registerTool(TOOL_NAME, {
|
|
50
|
+
title: 'Find HubSpot Projects',
|
|
51
|
+
description: 'Use this tool to locate HubSpot projects. Traverses child directories of the given directory to find hsproject.json files, returning the paths of all discovered HubSpot projects.',
|
|
52
|
+
inputSchema,
|
|
53
|
+
annotations: {
|
|
54
|
+
readOnlyHint: true,
|
|
55
|
+
openWorldHint: false,
|
|
56
|
+
idempotentHint: true,
|
|
57
|
+
},
|
|
58
|
+
}, input => this.wrappedHandler(input));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -114,7 +114,7 @@ export class GetBuildLogsTool extends Tool {
|
|
|
114
114
|
register() {
|
|
115
115
|
return this.mcpServer.registerTool(TOOL_NAME, {
|
|
116
116
|
title: 'Get HubSpot Project Build Logs',
|
|
117
|
-
description: 'Retrieves build logs for a specific HubSpot project build. Use this to debug build failures by viewing the full build pipeline output. This tool is for more comprehensive troubleshootings or addressing build WARNINGs, build errors should be troubleshooted with get-build-status tool first. Logs can be filtered by level (ERROR, WARN, INFO, or ALL). Use `hs project list-builds` first to identify the build ID and error messages.',
|
|
117
|
+
description: 'Retrieves build logs for a specific HubSpot project build. Use this to debug build failures by viewing the full build pipeline output. This tool is for more comprehensive troubleshootings or addressing build WARNINGs, build errors should be troubleshooted with get-build-status tool first. Logs can be filtered by level (ERROR, WARN, INFO, or ALL). Use `hs project list-builds` first to identify the build ID and error messages. If you do not know the project path, use the find-projects tool first to locate HubSpot projects in the workspace.',
|
|
118
118
|
inputSchema,
|
|
119
119
|
annotations: {
|
|
120
120
|
readOnlyHint: true,
|
|
@@ -155,7 +155,7 @@ export class GetBuildStatusTool extends Tool {
|
|
|
155
155
|
register() {
|
|
156
156
|
return this.mcpServer.registerTool(TOOL_NAME, {
|
|
157
157
|
title: 'Get HubSpot Projects Build Status and Errors',
|
|
158
|
-
description: 'Retrieves build status and error messages for HubSpot projects. When buildId is omitted, shows recent builds with their status(default 3) - use this to find the latest builds when troubleshooting. When buildId is provided, shows detailed error information for that specific build. Displays buildErrorMessage and subbuild failures to help diagnose build issues.',
|
|
158
|
+
description: 'Retrieves build status and error messages for HubSpot projects. When buildId is omitted, shows recent builds with their status(default 3) - use this to find the latest builds when troubleshooting. When buildId is provided, shows detailed error information for that specific build. Displays buildErrorMessage and subbuild failures to help diagnose build issues. If you do not know the project path, use the find-projects tool first to locate HubSpot projects in the workspace.',
|
|
159
159
|
inputSchema,
|
|
160
160
|
annotations: {
|
|
161
161
|
readOnlyHint: true,
|
|
@@ -72,7 +72,7 @@ export class UploadProjectTools extends Tool {
|
|
|
72
72
|
register() {
|
|
73
73
|
return this.mcpServer.registerTool(toolName, {
|
|
74
74
|
title: 'Upload HubSpot Project',
|
|
75
|
-
description: 'DO NOT run this tool unless the user specifies they would like to upload the project, it is potentially destructive. Uploads the HubSpot project in current working directory. If the project does not exist, it will be created. MUST be ran from within the project directory. IMPORTANT: Uploading a project does NOT automatically make cards live or visible to users. Cards must be manually added to a view in HubSpot after upload to become visible.',
|
|
75
|
+
description: 'DO NOT run this tool unless the user specifies they would like to upload the project, it is potentially destructive. Uploads the HubSpot project in current working directory. If the project does not exist, it will be created. MUST be ran from within the project directory. IMPORTANT: Uploading a project does NOT automatically make cards live or visible to users. Cards must be manually added to a view in HubSpot after upload to become visible. If you do not know the project path, use the find-projects tool first to locate HubSpot projects in the workspace.',
|
|
76
76
|
inputSchema,
|
|
77
77
|
annotations: {
|
|
78
78
|
readOnlyHint: false,
|
|
@@ -33,7 +33,7 @@ export class ValidateProjectTool extends Tool {
|
|
|
33
33
|
register() {
|
|
34
34
|
return this.mcpServer.registerTool(toolName, {
|
|
35
35
|
title: 'Validate HubSpot Project',
|
|
36
|
-
description: 'Validates the HubSpot project and its configuration files. This tool does not need to be ran before uploading the project',
|
|
36
|
+
description: 'Validates the HubSpot project and its configuration files. This tool does not need to be ran before uploading the project. If you do not know the project path, use the find-projects tool first to locate HubSpot projects in the workspace.',
|
|
37
37
|
inputSchema,
|
|
38
38
|
annotations: {
|
|
39
39
|
readOnlyHint: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cli",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.12-experimental.1",
|
|
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",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"!**/__tests__/**"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@hubspot/local-dev-lib": "
|
|
14
|
-
"@hubspot/project-parsing-lib": "0.
|
|
13
|
+
"@hubspot/local-dev-lib": "0.7.9-experimental.0",
|
|
14
|
+
"@hubspot/project-parsing-lib": "0.16.0",
|
|
15
15
|
"@hubspot/serverless-dev-runtime": "7.0.7",
|
|
16
16
|
"@hubspot/ui-extensions-dev-server": "2.0.5",
|
|
17
17
|
"@inquirer/prompts": "7.1.0",
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
"chokidar": "3.6.0",
|
|
22
22
|
"cli-cursor": "3.1.0",
|
|
23
23
|
"cli-progress": "3.12.0",
|
|
24
|
-
"express": "4.
|
|
24
|
+
"express": "4.22.1",
|
|
25
25
|
"findup-sync": "4.0.0",
|
|
26
26
|
"fs-extra": "8.1.0",
|
|
27
27
|
"ink": "6.6.0",
|
|
28
28
|
"ink-spinner": "5.0.0",
|
|
29
29
|
"ink-text-input": "6.0.0",
|
|
30
|
-
"js-yaml": "4.1.
|
|
31
|
-
"minimatch": "10.
|
|
30
|
+
"js-yaml": "4.1.1",
|
|
31
|
+
"minimatch": "10.2.5",
|
|
32
32
|
"moment": "2.30.1",
|
|
33
33
|
"open": "7.4.2",
|
|
34
34
|
"p-queue": "8.1.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@typescript-eslint/eslint-plugin": "^8.30.1",
|
|
60
60
|
"@typescript-eslint/parser": "^8.11.0",
|
|
61
61
|
"@vitest/coverage-v8": "^2.1.9",
|
|
62
|
-
"axios": "1.
|
|
62
|
+
"axios": "1.15.2",
|
|
63
63
|
"eslint": "^8.56.0",
|
|
64
64
|
"eslint-plugin-import": "^2.31.0",
|
|
65
65
|
"husky": "^4.3.8",
|
package/types/Link.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
|
|
2
|
+
import { HsSettingsFile } from '@hubspot/local-dev-lib/types/HsSettings';
|
|
3
|
+
import { CommonArgs, ConfigArgs } from './Yargs.js';
|
|
4
|
+
import { ArgumentsCamelCase } from 'yargs';
|
|
5
|
+
export type LinkArgs = CommonArgs & ConfigArgs;
|
|
6
|
+
export type LinkContext = {
|
|
7
|
+
globalAccountsList: HubSpotConfigAccount[];
|
|
8
|
+
globalDefaultAccount: HubSpotConfigAccount | undefined;
|
|
9
|
+
accountOverrideId: number | null;
|
|
10
|
+
preselectedAccountId?: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const ACTION_RESULT_STATUS: {
|
|
13
|
+
readonly SUCCESS: "success";
|
|
14
|
+
readonly ERROR: "error";
|
|
15
|
+
readonly NOOP: "noop";
|
|
16
|
+
};
|
|
17
|
+
export type ActionResult = {
|
|
18
|
+
status: typeof ACTION_RESULT_STATUS.SUCCESS;
|
|
19
|
+
settings: HsSettingsFile;
|
|
20
|
+
} | {
|
|
21
|
+
status: typeof ACTION_RESULT_STATUS.ERROR;
|
|
22
|
+
reason: string;
|
|
23
|
+
} | {
|
|
24
|
+
status: typeof ACTION_RESULT_STATUS.NOOP;
|
|
25
|
+
};
|
|
26
|
+
export type ActionHandlerParams = {
|
|
27
|
+
state: HsSettingsFile;
|
|
28
|
+
context: LinkContext;
|
|
29
|
+
args: ArgumentsCamelCase<LinkArgs>;
|
|
30
|
+
};
|
|
31
|
+
export type ActionHandler = (params: ActionHandlerParams) => Promise<ActionResult>;
|
|
32
|
+
export type ActionName = 'link' | 'unlink' | 'authenticate' | 'cancel';
|
package/types/Link.js
ADDED
package/types/PackageJson.d.ts
CHANGED
package/types/Prompts.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type GenericPromptResponse = {
|
|
|
5
5
|
type PromptType = 'confirm' | 'list' | 'checkbox' | 'input' | 'password' | 'number' | 'rawlist';
|
|
6
6
|
export type PromptChoices<T = any> = Array<string | {
|
|
7
7
|
name: string;
|
|
8
|
+
short?: string;
|
|
8
9
|
value?: T;
|
|
9
10
|
disabled?: string | boolean;
|
|
10
11
|
checked?: boolean;
|
package/types/Yargs.d.ts
CHANGED