@hubspot/cli 8.0.11-experimental.2 → 8.0.12-experimental.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/commands/account/link.d.ts +4 -0
  2. package/commands/account/link.js +88 -0
  3. package/commands/account/unlink.d.ts +4 -0
  4. package/commands/account/unlink.js +69 -0
  5. package/commands/account.js +4 -0
  6. package/commands/project/appInstallStatus.d.ts +4 -0
  7. package/commands/project/appInstallStatus.js +132 -0
  8. package/commands/project/create.js +8 -0
  9. package/commands/project/lint.js +2 -1
  10. package/commands/project/upload.d.ts +2 -0
  11. package/commands/project/upload.js +35 -3
  12. package/commands/project.js +2 -0
  13. package/lang/en.d.ts +94 -0
  14. package/lang/en.js +103 -4
  15. package/lib/app/migrate.js +2 -1
  16. package/lib/link/accountTableUtils.d.ts +10 -0
  17. package/lib/link/accountTableUtils.js +39 -0
  18. package/lib/link/index.d.ts +14 -0
  19. package/lib/link/index.js +154 -0
  20. package/lib/link/linkUtils.d.ts +4 -0
  21. package/lib/link/linkUtils.js +24 -0
  22. package/lib/link/prompts.d.ts +7 -0
  23. package/lib/link/prompts.js +126 -0
  24. package/lib/link/renderLinkedAccountsTable.d.ts +2 -0
  25. package/lib/link/renderLinkedAccountsTable.js +14 -0
  26. package/lib/projects/ProjectLogsManager.js +4 -1
  27. package/lib/projects/preview.d.ts +7 -0
  28. package/lib/projects/preview.js +71 -0
  29. package/lib/projects/uieLinting.d.ts +8 -3
  30. package/lib/projects/uieLinting.js +48 -27
  31. package/lib/projects/upload.d.ts +1 -0
  32. package/lib/projects/upload.js +4 -3
  33. package/lib/prompts/projectsLogsPrompt.js +3 -0
  34. package/lib/prompts/promptUtils.js +1 -0
  35. package/mcp-server/server.js +35 -1
  36. package/package.json +7 -7
  37. package/types/Link.d.ts +27 -0
  38. package/types/Link.js +1 -0
  39. package/types/Prompts.d.ts +1 -0
@@ -0,0 +1,71 @@
1
+ import { triggerAutoRelease, getAutoReleaseStatus, } from '@hubspot/local-dev-lib/api/projects';
2
+ import { DEFAULT_POLLING_DELAY } from '../constants.js';
3
+ import SpinniesManager from '../ui/SpinniesManager.js';
4
+ import { logError, ApiErrorContext } from '../errorHandlers/index.js';
5
+ import { lib } from '../../lang/en.js';
6
+ const PREVIEW_POLL_TIMEOUT = 5 * 60 * 1000;
7
+ export async function triggerAndPollPreview(accountId, projectId, buildId, targetPortalId) {
8
+ let triggerResponse;
9
+ SpinniesManager.add('preview', {
10
+ text: lib.projectPreview.triggeringPreview(buildId, targetPortalId),
11
+ succeedColor: 'white',
12
+ });
13
+ try {
14
+ const { data } = await triggerAutoRelease(accountId, projectId, buildId, targetPortalId);
15
+ triggerResponse = data;
16
+ }
17
+ catch (e) {
18
+ SpinniesManager.fail('preview', {
19
+ text: lib.projectPreview.triggerFailed,
20
+ });
21
+ logError(e, new ApiErrorContext({
22
+ accountId,
23
+ request: 'preview trigger',
24
+ }));
25
+ return { succeeded: false };
26
+ }
27
+ const { releaseTag, appId } = triggerResponse;
28
+ SpinniesManager.update('preview', {
29
+ text: lib.projectPreview.pollingStatus(releaseTag, targetPortalId),
30
+ });
31
+ try {
32
+ await pollPreviewStatus(accountId, projectId, targetPortalId, releaseTag, appId);
33
+ }
34
+ catch (e) {
35
+ SpinniesManager.fail('preview', {
36
+ text: lib.projectPreview.pollFailed,
37
+ });
38
+ logError(e, new ApiErrorContext({
39
+ accountId,
40
+ request: 'preview status',
41
+ }));
42
+ return { succeeded: false, releaseTag, appId };
43
+ }
44
+ SpinniesManager.succeed('preview', {
45
+ text: lib.projectPreview.succeeded(releaseTag, targetPortalId),
46
+ });
47
+ return { succeeded: true, releaseTag, appId };
48
+ }
49
+ function pollPreviewStatus(accountId, projectId, targetPortalId, expectedReleaseTag, appId) {
50
+ return new Promise((resolve, reject) => {
51
+ const startTime = Date.now();
52
+ const pollInterval = setInterval(async () => {
53
+ try {
54
+ const { data } = await getAutoReleaseStatus(accountId, projectId, targetPortalId, expectedReleaseTag, appId);
55
+ if (data.status === 'COMPLETE') {
56
+ clearInterval(pollInterval);
57
+ resolve();
58
+ return;
59
+ }
60
+ if (Date.now() - startTime >= PREVIEW_POLL_TIMEOUT) {
61
+ clearInterval(pollInterval);
62
+ reject(new Error(lib.projectPreview.timeout));
63
+ }
64
+ }
65
+ catch (e) {
66
+ clearInterval(pollInterval);
67
+ reject(e);
68
+ }
69
+ }, DEFAULT_POLLING_DELAY);
70
+ });
71
+ }
@@ -1,8 +1,13 @@
1
1
  export declare const REQUIRED_PACKAGES_AND_MIN_VERSIONS: {
2
2
  readonly eslint: "9.0.0";
3
- readonly '@typescript-eslint/eslint-plugin': "8.46.4";
4
- readonly '@typescript-eslint/parser': "8.46.4";
3
+ readonly '@eslint/js': "9.0.0";
5
4
  readonly 'typescript-eslint': "8.46.4";
5
+ readonly '@hubspot/eslint-config-ui-extensions': "1.0.0";
6
+ readonly 'eslint-config-prettier': "10.0.0";
7
+ readonly 'eslint-plugin-react': "7.0.0";
8
+ readonly 'eslint-plugin-react-hooks': "7.0.0";
9
+ readonly 'eslint-plugin-unused-imports': "4.0.0";
10
+ readonly prettier: "3.0.0";
6
11
  readonly jiti: "2.6.1";
7
12
  };
8
13
  export declare function isEslintInstalled(directory: string): boolean;
@@ -13,7 +18,7 @@ export declare function getMissingLintPackages(directory: string): {
13
18
  export declare function hasEslintConfig(directory: string): boolean;
14
19
  export declare function hasDeprecatedEslintConfig(directory: string): boolean;
15
20
  export declare function getDeprecatedEslintConfigFiles(directory: string): string[];
16
- export declare function createEslintConfig(directory: string): string;
21
+ export declare function createEslintConfig(directory: string, platformVersion?: string | null): Promise<string>;
17
22
  export declare function lintPackagesInDirectory(directory: string, projectDir?: string): Promise<{
18
23
  success: boolean;
19
24
  output: string;
@@ -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
10
  import { 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
- '@typescript-eslint/eslint-plugin': '8.46.4',
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,6 @@ const DEPRECATED_ESLINT_CONFIG_FILES = [
30
40
  '.eslintrc.json',
31
41
  '.eslintrc',
32
42
  ];
33
- const ESLINT_CONFIG_TEMPLATE = `import { defineConfig } from "eslint/config";
34
- import tsParser from "@typescript-eslint/parser";
35
-
36
- export default defineConfig([
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
- `;
55
43
  function getPackageVersionFromPackageJson(directory, packageName) {
56
44
  const packageJsonPath = path.join(directory, 'package.json');
57
45
  const packageJson = safeGetPackageJsonCached(packageJsonPath);
@@ -127,10 +115,43 @@ export function getDeprecatedEslintConfigFiles(directory) {
127
115
  return fs.existsSync(configPath);
128
116
  });
129
117
  }
130
- export function createEslintConfig(directory) {
131
- const configPath = path.join(directory, 'eslint.config.mts');
118
+ function repoFileDataToString(data) {
119
+ if (typeof data === 'string') {
120
+ return data;
121
+ }
122
+ if (Buffer.isBuffer(data)) {
123
+ return data.toString('utf-8');
124
+ }
125
+ return String(data);
126
+ }
127
+ export async function createEslintConfig(directory, platformVersion) {
128
+ const versionForRemote = platformVersion && !isLegacyProject(platformVersion)
129
+ ? platformVersion
130
+ : null;
131
+ if (versionForRemote === null) {
132
+ const message = commands.project.lint.createEslintConfigRequiresV2Platform(platformVersion);
133
+ uiLogger.error(message);
134
+ throw new Error(message);
135
+ }
136
+ let fetchedContent = null;
137
+ try {
138
+ const { data } = await fetchRepoFile(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, `${versionForRemote}/${UIE_ESLINT_CONFIG_PATH_IN_REPO}`, DEFAULT_PROJECT_TEMPLATE_BRANCH);
139
+ const content = repoFileDataToString(data);
140
+ if (content.trim().length > 0) {
141
+ fetchedContent = content;
142
+ }
143
+ }
144
+ catch (error) {
145
+ debugError(error);
146
+ }
147
+ if (fetchedContent === null) {
148
+ const message = commands.project.lint.failedToFetchRemoteEslintConfig(versionForRemote);
149
+ uiLogger.error(message);
150
+ throw new Error(message);
151
+ }
152
+ const configPath = path.join(directory, 'eslint.config.js');
132
153
  try {
133
- fs.writeFileSync(configPath, ESLINT_CONFIG_TEMPLATE, 'utf-8');
154
+ fs.writeFileSync(configPath, fetchedContent, 'utf-8');
134
155
  return path.relative(process.cwd(), configPath);
135
156
  }
136
157
  catch (error) {
@@ -5,6 +5,7 @@ type ProjectUploadResult<T> = {
5
5
  result?: T;
6
6
  uploadError?: unknown;
7
7
  projectNotFound?: boolean;
8
+ projectId?: number;
8
9
  };
9
10
  type HandleProjectUploadArg<T> = {
10
11
  accountId: number;
@@ -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
  }
@@ -42,6 +42,7 @@ function mapPromptChoicesToChoices(choices) {
42
42
  return {
43
43
  value: choice.value,
44
44
  name: choice.name,
45
+ short: choice.short,
45
46
  disabled: choice.disabled,
46
47
  checked: choice.checked,
47
48
  };
@@ -2,11 +2,45 @@ 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. Editing \`*-hsmeta.json\`: call \`get-feature-config-schema\` for that
24
+ feature type first to learn the allowed fields and values.
25
+ 3. Debugging a failed build: start with \`get-build-status\` to surface
26
+ error messages, and only reach for \`get-build-logs\` for deeper
27
+ troubleshooting or warnings.
28
+ 4. Reading serverless function logs: call \`list-cms-serverless-functions\`
29
+ first to discover the endpoint path, then
30
+ \`get-cms-serverless-function-logs\`.
31
+ 5. App analytics: call \`get-apps-info\` to discover \`appId\` values
32
+ before \`get-api-usage-patterns-by-app-id\`.
33
+
34
+ OUTPUT
35
+ Tool results contain the relevant \`hs\` stdout/stderr or structured data.
36
+ Surface error text from results to the user verbatim when troubleshooting,
37
+ rather than paraphrasing.
38
+ `.trim();
5
39
  const server = new McpServer({
6
40
  name: 'HubSpot CLI MCP Server',
7
41
  version: '0.0.1',
8
42
  description: 'Helps perform tasks for local development of HubSpot projects.',
9
- }, { capabilities: { logging: {} } });
43
+ }, { capabilities: { logging: {} }, instructions });
10
44
  const logger = new McpLogger(server);
11
45
  registerProjectTools(server, logger);
12
46
  registerCmsTools(server, logger);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "8.0.11-experimental.2",
3
+ "version": "8.0.12-experimental.0",
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": "5.3.3",
14
- "@hubspot/project-parsing-lib": "0.2.1-experimental.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.21.2",
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.0",
31
- "minimatch": "10.0.1",
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.14.0",
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",
@@ -0,0 +1,27 @@
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 type ActionResult = {
13
+ status: 'success';
14
+ settings: HsSettingsFile;
15
+ } | {
16
+ status: 'error';
17
+ reason: string;
18
+ } | {
19
+ status: 'noop';
20
+ };
21
+ export type ActionHandlerParams = {
22
+ state: HsSettingsFile;
23
+ context: LinkContext;
24
+ args: ArgumentsCamelCase<LinkArgs>;
25
+ };
26
+ export type ActionHandler = (params: ActionHandlerParams) => Promise<ActionResult>;
27
+ export type ActionName = 'link' | 'unlink' | 'authenticate' | 'cancel';
package/types/Link.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -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;