@hubspot/cli 8.0.3-experimental.1 → 8.0.4-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 (36) hide show
  1. package/commands/project/migrate.js +2 -2
  2. package/lang/en.d.ts +5 -8
  3. package/lang/en.js +6 -9
  4. package/lib/getStartedV2Actions.d.ts +13 -0
  5. package/lib/getStartedV2Actions.js +53 -0
  6. package/lib/projects/__tests__/upload.test.js +0 -10
  7. package/lib/projects/platformVersion.d.ts +1 -1
  8. package/lib/projects/platformVersion.js +2 -1
  9. package/lib/projects/upload.js +0 -9
  10. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +20 -3
  11. package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -10
  12. package/mcp-server/tools/project/CreateProjectTool.d.ts +24 -4
  13. package/mcp-server/tools/project/CreateProjectTool.js +5 -10
  14. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +5 -8
  15. package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
  16. package/mcp-server/tools/project/GetBuildLogsTool.js +6 -7
  17. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
  18. package/mcp-server/tools/project/GetBuildStatusTool.js +3 -4
  19. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +6 -1
  20. package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -6
  21. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +0 -32
  22. package/mcp-server/tools/project/constants.d.ts +12 -1
  23. package/mcp-server/tools/project/constants.js +12 -16
  24. package/package.json +3 -3
  25. package/ui/components/getStarted/GetStartedFlow.js +79 -2
  26. package/ui/components/getStarted/reducer.d.ts +20 -0
  27. package/ui/components/getStarted/reducer.js +36 -0
  28. package/ui/components/getStarted/screens/InstallationScreen.d.ts +7 -0
  29. package/ui/components/getStarted/screens/InstallationScreen.js +16 -0
  30. package/ui/components/getStarted/screens/ProjectSetupScreen.js +2 -1
  31. package/ui/lib/constants.d.ts +1 -0
  32. package/ui/lib/constants.js +1 -0
  33. package/lib/projects/__tests__/workspaceArchive.test.d.ts +0 -1
  34. package/lib/projects/__tests__/workspaceArchive.test.js +0 -207
  35. package/lib/projects/workspaces.d.ts +0 -36
  36. package/lib/projects/workspaces.js +0 -224
@@ -13,7 +13,7 @@ import { getHasMigratableThemes, migrateThemes2025_2, } from '../../lib/theme/mi
13
13
  import { hasFeature } from '../../lib/hasFeature.js';
14
14
  import { FEATURES } from '../../lib/constants.js';
15
15
  import { trackCommandMetadataUsage, trackCommandUsage, } from '../../lib/usageTracking.js';
16
- const { v2025_2 } = PLATFORM_VERSIONS;
16
+ const { v2025_2, v2026_3 } = PLATFORM_VERSIONS;
17
17
  const command = 'migrate';
18
18
  const describe = commands.project.migrate.describe;
19
19
  async function handler(args) {
@@ -67,7 +67,7 @@ function projectMigrateBuilder(yargs) {
67
67
  yargs
68
68
  .option('platform-version', {
69
69
  type: 'string',
70
- choices: [v2025_2],
70
+ choices: [v2025_2, v2026_3],
71
71
  default: v2025_2,
72
72
  })
73
73
  .option('unstable', {
package/lang/en.d.ts CHANGED
@@ -43,6 +43,11 @@ 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;
46
51
  prompts: {
47
52
  selectOptionV2: string;
48
53
  options: {
@@ -3179,14 +3184,6 @@ export declare const lib: {
3179
3184
  fileFiltered: (filename: string) => string;
3180
3185
  legacyFileDetected: (filename: string, platformVersion: string) => string;
3181
3186
  projectDoesNotExist: (accountId: number) => string;
3182
- workspaceIncluded: (workspaceDir: string, archivePath: string) => string;
3183
- fileDependencyIncluded: (packageName: string, localPath: string, archivePath: string) => string;
3184
- malformedPackageJson: (packageJsonPath: string, error: string) => string;
3185
- workspaceCollision: (archivePath: string, workspaceDir: string, existingWorkspace: string) => string;
3186
- fileDependencyAlreadyIncluded: (packageName: string, archivePath: string) => string;
3187
- updatingPackageJsonWorkspaces: (packageJsonPath: string) => string;
3188
- updatedWorkspaces: (workspaces: string) => string;
3189
- updatedFileDependency: (packageName: string, relativePath: string) => string;
3190
3187
  };
3191
3188
  };
3192
3189
  importData: {
package/lang/en.js CHANGED
@@ -51,6 +51,11 @@ 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...`,
54
59
  prompts: {
55
60
  selectOptionV2: 'Choose a component type to get started',
56
61
  options: {
@@ -3076,7 +3081,7 @@ export const lib = {
3076
3081
  updateSucceeded: (latestVersion) => `Successfully updated HubSpot CLI to version ${chalk.bold(latestVersion)}`,
3077
3082
  notInstalledGlobally: 'Cannot auto-update the HubSpot CLI because NPM is not installed globally',
3078
3083
  updateFailed: (latestVersion) => `Failed to update HubSpot CLI to version ${chalk.bold(latestVersion)}`,
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')}`,
3084
+ 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')}`,
3080
3085
  },
3081
3086
  },
3082
3087
  projectProfiles: {
@@ -3202,14 +3207,6 @@ export const lib = {
3202
3207
  fileFiltered: (filename) => `Ignore rule triggered for "${filename}"`,
3203
3208
  legacyFileDetected: (filename, platformVersion) => `The ${chalk.bold(filename)} file is not supported on platform version ${chalk.bold(platformVersion)} and will be ignored.`,
3204
3209
  projectDoesNotExist: (accountId) => `Upload cancelled. Run ${uiCommandReference('hs project upload')} again to create the project in ${uiAccountDescription(accountId)}.`,
3205
- workspaceIncluded: (workspaceDir, archivePath) => `Including workspace: ${workspaceDir} → ${archivePath}`,
3206
- fileDependencyIncluded: (packageName, localPath, archivePath) => `Including file: dependency ${packageName}: ${localPath} → ${archivePath}`,
3207
- malformedPackageJson: (packageJsonPath, error) => `Skipping malformed package.json at ${packageJsonPath}: ${error}`,
3208
- workspaceCollision: (archivePath, workspaceDir, existingWorkspace) => `Workspace collision: ${archivePath} from ${workspaceDir} and ${existingWorkspace}`,
3209
- fileDependencyAlreadyIncluded: (packageName, archivePath) => `file: dependency ${packageName} already included as workspace: ${archivePath}`,
3210
- updatingPackageJsonWorkspaces: (packageJsonPath) => `Updating package.json workspaces in archive: ${packageJsonPath}`,
3211
- updatedWorkspaces: (workspaces) => ` Updated workspaces: ${workspaces}`,
3212
- updatedFileDependency: (packageName, relativePath) => ` Updated dependencies.${packageName}: file:${relativePath}`,
3213
3210
  },
3214
3211
  },
3215
3212
  importData: {
@@ -35,3 +35,16 @@ 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,6 +1,7 @@
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';
4
5
  import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
5
6
  import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
6
7
  import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
@@ -144,3 +145,55 @@ export async function uploadAndDeployAction({ accountId, projectDest, }) {
144
145
  export function trackGetStartedUsage(params, accountId) {
145
146
  return trackCommandMetadataUsage('get-started', params, accountId);
146
147
  }
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
+ }
@@ -13,7 +13,6 @@ import { walk } from '@hubspot/local-dev-lib/fs';
13
13
  import { uploadProject } from '@hubspot/local-dev-lib/api/projects';
14
14
  import { ensureProjectExists } from '../ensureProjectExists.js';
15
15
  import { projectContainsHsMetaFiles } from '@hubspot/project-parsing-lib/projects';
16
- import { findAndParsePackageJsonFiles, collectWorkspaceDirectories, collectFileDependencies, } from '@hubspot/project-parsing-lib/workspaces';
17
16
  import { shouldIgnoreFile } from '@hubspot/local-dev-lib/ignoreRules';
18
17
  import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
19
18
  // Mock dependencies
@@ -23,11 +22,6 @@ vi.mock('@hubspot/local-dev-lib/fs');
23
22
  vi.mock('@hubspot/local-dev-lib/api/projects');
24
23
  vi.mock('../ensureProjectExists.js');
25
24
  vi.mock('@hubspot/project-parsing-lib/projects');
26
- vi.mock('@hubspot/project-parsing-lib/workspaces', () => ({
27
- findAndParsePackageJsonFiles: vi.fn(),
28
- collectWorkspaceDirectories: vi.fn(),
29
- collectFileDependencies: vi.fn(),
30
- }));
31
25
  vi.mock('@hubspot/local-dev-lib/ignoreRules');
32
26
  vi.mock('@hubspot/local-dev-lib/config');
33
27
  vi.mock('archiver');
@@ -128,10 +122,6 @@ describe('lib/projects/upload', () => {
128
122
  vi.mocked(shouldIgnoreFile).mockReturnValue(false);
129
123
  vi.mocked(projectContainsHsMetaFiles).mockResolvedValue(false);
130
124
  vi.mocked(isV2Project).mockReturnValue(false);
131
- // Mock workspace functions to return empty arrays
132
- vi.mocked(findAndParsePackageJsonFiles).mockResolvedValue([]);
133
- vi.mocked(collectWorkspaceDirectories).mockResolvedValue([]);
134
- vi.mocked(collectFileDependencies).mockResolvedValue([]);
135
125
  vi.mocked(tmp.fileSync).mockReturnValue({
136
126
  name: path.join(tempDir, 'test.zip'),
137
127
  fd: 1,
@@ -4,6 +4,6 @@
4
4
  *
5
5
  * We are unable to reliably support versions of projects that are newer than any given CLI release
6
6
  * */
7
- export declare const LATEST_SUPPORTED_PLATFORM_VERSION = "2026.03";
7
+ export declare const LATEST_SUPPORTED_PLATFORM_VERSION: string;
8
8
  export declare function isV2Project(platformVersion?: string | null): boolean;
9
9
  export declare function isUnsupportedPlatformVersion(platformVersion?: string | null): boolean;
@@ -1,10 +1,11 @@
1
+ import { PLATFORM_VERSIONS } from '@hubspot/local-dev-lib/constants/projects';
1
2
  /**
2
3
  * Used to surface warnings when users attempt to interact with new platform versions
3
4
  * that were released after this version of the CLI was released.
4
5
  *
5
6
  * We are unable to reliably support versions of projects that are newer than any given CLI release
6
7
  * */
7
- export const LATEST_SUPPORTED_PLATFORM_VERSION = '2026.03';
8
+ export const LATEST_SUPPORTED_PLATFORM_VERSION = PLATFORM_VERSIONS.v2026_3;
8
9
  function parsePlatformVersion(platformVersion) {
9
10
  const [year, minor] = platformVersion.split(/[.-]/);
10
11
  return {
@@ -6,7 +6,6 @@ import { uploadProject } from '@hubspot/local-dev-lib/api/projects';
6
6
  import { shouldIgnoreFile } from '@hubspot/local-dev-lib/ignoreRules';
7
7
  import { isTranslationError, translate, } from '@hubspot/project-parsing-lib/translate';
8
8
  import { projectContainsHsMetaFiles } from '@hubspot/project-parsing-lib/projects';
9
- import { findAndParsePackageJsonFiles, collectWorkspaceDirectories, collectFileDependencies, } from '@hubspot/project-parsing-lib/workspaces';
10
9
  import SpinniesManager from '../ui/SpinniesManager.js';
11
10
  import { uiAccountDescription } from '../ui/index.js';
12
11
  import { logError } from '../errorHandlers/index.js';
@@ -19,7 +18,6 @@ import { EXIT_CODES } from '../enums/exitCodes.js';
19
18
  import ProjectValidationError from '../errors/ProjectValidationError.js';
20
19
  import { walk } from '@hubspot/local-dev-lib/fs';
21
20
  import { LEGACY_CONFIG_FILES } from '../constants.js';
22
- import { archiveWorkspacesAndDependencies } from './workspaces.js';
23
21
  async function uploadProjectFiles(accountId, projectName, filePath, uploadMessage, platformVersion, intermediateRepresentation) {
24
22
  const accountIdentifier = uiAccountDescription(accountId) || `${accountId}`;
25
23
  SpinniesManager.add('upload', {
@@ -64,11 +62,6 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
64
62
  }
65
63
  const tempFile = tmp.fileSync({ postfix: '.zip' });
66
64
  uiLogger.debug(lib.projectUpload.handleProjectUpload.compressing(tempFile.name));
67
- // Find and parse all package.json files once (avoids duplicate filesystem walks)
68
- const parsedPackageJsons = await findAndParsePackageJsonFiles(srcDir);
69
- // Collect workspace directories and file: dependencies from parsed data
70
- const workspaceMappings = await collectWorkspaceDirectories(parsedPackageJsons);
71
- const fileDependencyMappings = await collectFileDependencies(parsedPackageJsons);
72
65
  const output = fs.createWriteStream(tempFile.name);
73
66
  const archive = archiver('zip');
74
67
  const result = new Promise(resolve => output.on('close', async function () {
@@ -121,8 +114,6 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
121
114
  }
122
115
  return ignored ? false : file;
123
116
  });
124
- // Archive workspaces and file: dependencies
125
- await archiveWorkspacesAndDependencies(archive, srcDir, projectDir, workspaceMappings, fileDependencyMappings);
126
117
  archive.finalize();
127
118
  return result;
128
119
  }
@@ -5,9 +5,26 @@ declare const inputSchemaZodObject: z.ZodObject<{
5
5
  absoluteProjectPath: z.ZodString;
6
6
  absoluteCurrentWorkingDirectory: z.ZodString;
7
7
  addApp: z.ZodBoolean;
8
- distribution: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"marketplace">, z.ZodLiteral<"private">]>>;
9
- auth: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"static">, z.ZodLiteral<"oauth">]>>;
10
- features: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">, z.ZodLiteral<"workflow-action">, z.ZodLiteral<"workflow-action-tool">, z.ZodLiteral<"app-object">, z.ZodLiteral<"app-event">, z.ZodLiteral<"scim">, z.ZodLiteral<"page">]>>>;
8
+ distribution: z.ZodOptional<z.ZodEnum<{
9
+ marketplace: "marketplace";
10
+ private: "private";
11
+ }>>;
12
+ auth: z.ZodOptional<z.ZodEnum<{
13
+ oauth: "oauth";
14
+ static: "static";
15
+ }>>;
16
+ features: z.ZodOptional<z.ZodArray<z.ZodEnum<{
17
+ card: "card";
18
+ settings: "settings";
19
+ "app-event": "app-event";
20
+ page: "page";
21
+ "workflow-action-tool": "workflow-action-tool";
22
+ webhooks: "webhooks";
23
+ "workflow-action": "workflow-action";
24
+ "app-function": "app-function";
25
+ "app-object": "app-object";
26
+ scim: "scim";
27
+ }>>>;
11
28
  }, z.core.$strip>;
12
29
  export type AddFeatureInputSchema = z.infer<typeof inputSchemaZodObject>;
13
30
  export declare class AddFeatureToProjectTool extends Tool<AddFeatureInputSchema> {
@@ -15,17 +15,13 @@ const inputSchema = {
15
15
  .boolean()
16
16
  .describe('Should an app be added? If there is no app in the project, an app must be added to add a feature'),
17
17
  distribution: z
18
- .optional(z.union([
19
- z.literal(APP_DISTRIBUTION_TYPES.MARKETPLACE),
20
- z.literal(APP_DISTRIBUTION_TYPES.PRIVATE),
21
- ]))
22
- .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Private is used if you do not wish to distribute your app on the HubSpot marketplace. '),
18
+ .enum([APP_DISTRIBUTION_TYPES.MARKETPLACE, APP_DISTRIBUTION_TYPES.PRIVATE])
19
+ .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Private is used if you do not wish to distribute your app on the HubSpot marketplace. ')
20
+ .optional(),
23
21
  auth: z
24
- .optional(z.union([
25
- z.literal(APP_AUTH_TYPES.STATIC),
26
- z.literal(APP_AUTH_TYPES.OAUTH),
27
- ]))
28
- .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Static uses a static non changing authentication token, and is only available for private distribution. '),
22
+ .enum([APP_AUTH_TYPES.STATIC, APP_AUTH_TYPES.OAUTH])
23
+ .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Static uses a static non changing authentication token, and is only available for private distribution. ')
24
+ .optional(),
29
25
  features,
30
26
  };
31
27
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -5,10 +5,30 @@ declare const inputSchemaZodObject: z.ZodObject<{
5
5
  absoluteCurrentWorkingDirectory: z.ZodString;
6
6
  name: z.ZodOptional<z.ZodString>;
7
7
  destination: z.ZodString;
8
- projectBase: z.ZodUnion<readonly [z.ZodLiteral<"empty">, z.ZodLiteral<"app">]>;
9
- distribution: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"marketplace">, z.ZodLiteral<"private">]>>;
10
- auth: z.ZodOptional<z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"static">, z.ZodLiteral<"oauth">]>>>;
11
- features: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">, z.ZodLiteral<"workflow-action">, z.ZodLiteral<"workflow-action-tool">, z.ZodLiteral<"app-object">, z.ZodLiteral<"app-event">, z.ZodLiteral<"scim">, z.ZodLiteral<"page">]>>>;
8
+ projectBase: z.ZodEnum<{
9
+ app: "app";
10
+ empty: "empty";
11
+ }>;
12
+ distribution: z.ZodOptional<z.ZodEnum<{
13
+ marketplace: "marketplace";
14
+ private: "private";
15
+ }>>;
16
+ auth: z.ZodOptional<z.ZodEnum<{
17
+ oauth: "oauth";
18
+ static: "static";
19
+ }>>;
20
+ features: z.ZodOptional<z.ZodArray<z.ZodEnum<{
21
+ card: "card";
22
+ settings: "settings";
23
+ "app-event": "app-event";
24
+ page: "page";
25
+ "workflow-action-tool": "workflow-action-tool";
26
+ webhooks: "webhooks";
27
+ "workflow-action": "workflow-action";
28
+ "app-function": "app-function";
29
+ "app-object": "app-object";
30
+ scim: "scim";
31
+ }>>>;
12
32
  }, z.core.$strip>;
13
33
  export type CreateProjectInputSchema = z.infer<typeof inputSchemaZodObject>;
14
34
  export declare class CreateProjectTool extends Tool<CreateProjectInputSchema> {
@@ -18,19 +18,14 @@ const inputSchema = {
18
18
  .string()
19
19
  .describe('DO NOT use the current directory unless the user has explicitly stated to do so. Relative path to the directory the project will be created in.'),
20
20
  projectBase: z
21
- .union([z.literal(EMPTY_PROJECT), z.literal(PROJECT_WITH_APP)])
21
+ .enum([EMPTY_PROJECT, PROJECT_WITH_APP])
22
22
  .describe('Empty will create an empty project, and app will create a project with an app inside of it.'),
23
23
  distribution: z
24
- .optional(z.union([
25
- z.literal(APP_DISTRIBUTION_TYPES.MARKETPLACE),
26
- z.literal(APP_DISTRIBUTION_TYPES.PRIVATE),
27
- ]))
28
- .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Private is used if you do not wish to distribute your app on the HubSpot marketplace. '),
24
+ .enum([APP_DISTRIBUTION_TYPES.MARKETPLACE, APP_DISTRIBUTION_TYPES.PRIVATE])
25
+ .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Private is used if you do not wish to distribute your app on the HubSpot marketplace. ')
26
+ .optional(),
29
27
  auth: z
30
- .optional(z.union([
31
- z.literal(APP_AUTH_TYPES.STATIC),
32
- z.literal(APP_AUTH_TYPES.OAUTH),
33
- ]))
28
+ .enum([APP_AUTH_TYPES.STATIC, APP_AUTH_TYPES.OAUTH])
34
29
  .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Static uses a static non changing authentication token, and is only available for private distribution. ')
35
30
  .optional(),
36
31
  features,
@@ -12,18 +12,15 @@ const inputSchema = {
12
12
  absoluteCurrentWorkingDirectory,
13
13
  appId: z
14
14
  .string()
15
- .regex(/^\d+$/, 'App ID must be a numeric string')
16
- .describe('The numeric app ID as a string (e.g., "3003909"). Use get-apps-info to find available app IDs.'),
15
+ .describe('The numeric app ID as a string (e.g., "3003909"). Must contain only digits. Use get-apps-info to find available app IDs.'),
17
16
  startDate: z
18
17
  .string()
19
- .regex(/^\d{4}-\d{2}-\d{2}$/, 'Start date must be in YYYY-MM-DD format')
20
- .optional()
21
- .describe('Start date for the usage patterns query in ISO 8601 format (e.g., 2025-01-01).'),
18
+ .describe('Start date for the usage patterns query in YYYY-MM-DD format (e.g., 2025-01-01).')
19
+ .optional(),
22
20
  endDate: z
23
21
  .string()
24
- .regex(/^\d{4}-\d{2}-\d{2}$/, 'End date must be in YYYY-MM-DD format')
25
- .optional()
26
- .describe('End date for the usage patterns query in ISO 8601 format (e.g., 2025-12-31).'),
22
+ .describe('End date for the usage patterns query in YYYY-MM-DD format (e.g., 2025-12-31).')
23
+ .optional(),
27
24
  };
28
25
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
29
26
  const inputSchemaZodObject = z.object({ ...inputSchema });
@@ -5,12 +5,12 @@ declare const inputSchemaZodObject: z.ZodObject<{
5
5
  absoluteProjectPath: z.ZodString;
6
6
  absoluteCurrentWorkingDirectory: z.ZodString;
7
7
  buildId: z.ZodNumber;
8
- logLevel: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
8
+ logLevel: z.ZodOptional<z.ZodEnum<{
9
9
  ERROR: "ERROR";
10
10
  WARN: "WARN";
11
11
  INFO: "INFO";
12
12
  ALL: "ALL";
13
- }>>>;
13
+ }>>;
14
14
  }, z.core.$strip>;
15
15
  export type GetBuildLogsInputSchema = z.infer<typeof inputSchemaZodObject>;
16
16
  export declare class GetBuildLogsTool extends Tool<GetBuildLogsInputSchema> {
@@ -18,9 +18,8 @@ const inputSchema = {
18
18
  .describe('Build ID to fetch logs for. Use get-build-status to find recent build IDs.'),
19
19
  logLevel: z
20
20
  .enum(['ERROR', 'WARN', 'INFO', 'ALL'])
21
- .optional()
22
- .default('ALL')
23
- .describe('Filter logs by level. ERROR: Show only errors, WARN: Show only warnings, INFO: Show only info, ALL: Show all logs.'),
21
+ .describe('Filter logs by level. ERROR: Show only errors, WARN: Show only warnings, INFO: Show only info, ALL: Show all logs. Defaults to ALL if not specified.')
22
+ .optional(),
24
23
  };
25
24
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
26
25
  const inputSchemaZodObject = z.object({ ...inputSchema });
@@ -85,14 +84,14 @@ export class GetBuildLogsTool extends Tool {
85
84
  if (allLogs.length === 0) {
86
85
  return formatTextContents(absoluteCurrentWorkingDirectory, `No logs found for build #${buildId} in project '${projectName}'.`);
87
86
  }
88
- const filteredLogs = filterLogsByLevel(allLogs, logLevel);
87
+ const resolvedLogLevel = logLevel || 'ALL';
88
+ const filteredLogs = filterLogsByLevel(allLogs, resolvedLogLevel);
89
89
  let output;
90
90
  if (filteredLogs.length === 0) {
91
- // No logs match filter, show all logs instead
92
- output = `No ${logLevel} level logs found for build #${buildId} in '${projectName}'.\nShowing all logs instead:\n\n${formatLogs(allLogs)}`;
91
+ output = `No ${resolvedLogLevel} level logs found for build #${buildId} in '${projectName}'.\nShowing all logs instead:\n\n${formatLogs(allLogs)}`;
93
92
  }
94
93
  else {
95
- output = `Logs for build #${buildId} in '${projectName}' (${logLevel} level):\n\n${formatLogs(filteredLogs)}`;
94
+ output = `Logs for build #${buildId} in '${projectName}' (${resolvedLogLevel} level):\n\n${formatLogs(filteredLogs)}`;
96
95
  }
97
96
  return formatTextContents(absoluteCurrentWorkingDirectory, output);
98
97
  }
@@ -5,7 +5,7 @@ declare const inputSchemaZodObject: z.ZodObject<{
5
5
  absoluteProjectPath: z.ZodString;
6
6
  absoluteCurrentWorkingDirectory: z.ZodString;
7
7
  buildId: z.ZodOptional<z.ZodNumber>;
8
- limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
8
+ limit: z.ZodOptional<z.ZodNumber>;
9
9
  }, z.core.$strip>;
10
10
  export type GetBuildStatusInputSchema = z.infer<typeof inputSchemaZodObject>;
11
11
  export declare class GetBuildStatusTool extends Tool<GetBuildStatusInputSchema> {
@@ -19,9 +19,8 @@ const inputSchema = {
19
19
  .describe('Optional: Specific build ID to inspect. If omitted, shows recent builds to help identify the latest build.'),
20
20
  limit: z
21
21
  .number()
22
- .optional()
23
- .default(3)
24
- .describe('Number of recent builds to fetch when buildId is not specified.'),
22
+ .describe('Number of recent builds to fetch when buildId is not specified. Defaults to 3 if not specified.')
23
+ .optional(),
25
24
  };
26
25
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
27
26
  const inputSchemaZodObject = z.object({ ...inputSchema });
@@ -127,7 +126,7 @@ export class GetBuildStatusTool extends Tool {
127
126
  }
128
127
  else {
129
128
  const response = await fetchProjectBuilds(accountId, projectName, {
130
- limit,
129
+ limit: limit || 3,
131
130
  });
132
131
  const { results } = response.data;
133
132
  if (!results || results.length === 0) {
@@ -3,7 +3,12 @@ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.
3
3
  import { z } from 'zod';
4
4
  declare const inputSchemaZodObject: z.ZodObject<{
5
5
  absoluteCurrentWorkingDirectory: z.ZodString;
6
- command: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"hs init">, z.ZodLiteral<"hs auth">, z.ZodLiteral<"hs project create">, z.ZodLiteral<"hs project upload">]>>;
6
+ command: z.ZodOptional<z.ZodEnum<{
7
+ "hs auth": "hs auth";
8
+ "hs project create": "hs project create";
9
+ "hs project upload": "hs project upload";
10
+ "hs init": "hs init";
11
+ }>>;
7
12
  }, z.core.$strip>;
8
13
  type InputSchemaType = z.infer<typeof inputSchemaZodObject>;
9
14
  export declare class GuidedWalkthroughTool extends Tool<InputSchemaType> {
@@ -14,12 +14,7 @@ const nextCommands = {
14
14
  const inputSchema = {
15
15
  absoluteCurrentWorkingDirectory,
16
16
  command: z
17
- .union([
18
- z.literal('hs init'),
19
- z.literal('hs auth'),
20
- z.literal('hs project create'),
21
- z.literal('hs project upload'),
22
- ])
17
+ .enum(['hs init', 'hs auth', 'hs project create', 'hs project upload'])
23
18
  .describe('The command to learn more about. Start with `hs init`')
24
19
  .optional(),
25
20
  };
@@ -1,5 +1,4 @@
1
1
  import { GetApiUsagePatternsByAppIdTool } from '../GetApiUsagePatternsByAppIdTool.js';
2
- import { z } from 'zod';
3
2
  import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
4
3
  import { http } from '@hubspot/local-dev-lib/http';
5
4
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
@@ -52,37 +51,6 @@ describe('mcp-server/tools/project/GetApiUsagePatternsByAppIdTool', () => {
52
51
  expect(result).toBe(mockRegisteredTool);
53
52
  });
54
53
  });
55
- describe('input validation', () => {
56
- const inputSchema = z.object({
57
- appId: z
58
- .string()
59
- .describe('The application ID to get API usage patterns for.'),
60
- startDate: z
61
- .string()
62
- .regex(/^\d{4}-\d{2}-\d{2}$/, 'Start date must be in YYYY-MM-DD format')
63
- .optional()
64
- .describe('Start date for the usage patterns query in ISO 8601 format (e.g., 2025-01-01).'),
65
- endDate: z
66
- .string()
67
- .regex(/^\d{4}-\d{2}-\d{2}$/, 'End date must be in YYYY-MM-DD format')
68
- .optional()
69
- .describe('End date for the usage patterns query in ISO 8601 format (e.g., 2025-12-31).'),
70
- });
71
- it('should validate date format correctly', () => {
72
- const validInput = {
73
- appId: '12345',
74
- startDate: '2025-01-01',
75
- endDate: '2025-12-31',
76
- };
77
- const invalidInput = {
78
- appId: '12345',
79
- startDate: '2025-1-1',
80
- endDate: '2025-12-31T00:00:00Z',
81
- };
82
- expect(() => inputSchema.parse(validInput)).not.toThrow();
83
- expect(() => inputSchema.parse(invalidInput)).toThrow();
84
- });
85
- });
86
54
  describe('handler', () => {
87
55
  const input = {
88
56
  absoluteCurrentWorkingDirectory: '/test/dir',
@@ -1,6 +1,17 @@
1
1
  import z from 'zod';
2
2
  export declare const absoluteProjectPath: z.ZodString;
3
3
  export declare const absoluteCurrentWorkingDirectory: z.ZodString;
4
- export declare const features: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">, z.ZodLiteral<"workflow-action">, z.ZodLiteral<"workflow-action-tool">, z.ZodLiteral<"app-object">, z.ZodLiteral<"app-event">, z.ZodLiteral<"scim">, z.ZodLiteral<"page">]>>>;
4
+ export declare const features: z.ZodOptional<z.ZodArray<z.ZodEnum<{
5
+ card: "card";
6
+ settings: "settings";
7
+ "app-event": "app-event";
8
+ page: "page";
9
+ "workflow-action-tool": "workflow-action-tool";
10
+ webhooks: "webhooks";
11
+ "workflow-action": "workflow-action";
12
+ "app-function": "app-function";
13
+ "app-object": "app-object";
14
+ scim: "scim";
15
+ }>>>;
5
16
  export declare const docsSearchQuery: z.ZodString;
6
17
  export declare const docUrl: z.ZodString;
@@ -6,23 +6,19 @@ export const absoluteCurrentWorkingDirectory = z
6
6
  .string()
7
7
  .describe('The absolute path to the current working directory.');
8
8
  export const features = z
9
- .array(z.union([
10
- z.literal('card'),
11
- z.literal('settings'),
12
- z
13
- .literal('app-function')
14
- .describe('Also known as a public serverless function'),
15
- z.literal('webhooks'),
16
- z
17
- .literal('workflow-action')
18
- .describe('Also known as a custom workflow action.'),
19
- z.literal('workflow-action-tool').describe('Also known as agent tools.'),
20
- z.literal('app-object'),
21
- z.literal('app-event'),
22
- z.literal('scim'),
23
- z.literal('page'),
9
+ .array(z.enum([
10
+ 'card',
11
+ 'settings',
12
+ 'app-function',
13
+ 'webhooks',
14
+ 'workflow-action',
15
+ 'workflow-action-tool',
16
+ 'app-object',
17
+ 'app-event',
18
+ 'scim',
19
+ 'page',
24
20
  ]))
25
- .describe('The features to include in the project, multiple options can be selected')
21
+ .describe('The features to include in the project, multiple options can be selected. "app-function" is also known as a public serverless function. "workflow-action" is also known as a custom workflow action. "workflow-action-tool" is also known as agent tools.')
26
22
  .optional();
27
23
  export const docsSearchQuery = z
28
24
  .string()
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "8.0.3-experimental.1",
3
+ "version": "8.0.4-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",
7
7
  "type": "module",
8
8
  "dependencies": {
9
9
  "@hubspot/cms-dev-server": "1.2.16",
10
- "@hubspot/local-dev-lib": "5.1.1",
11
- "@hubspot/project-parsing-lib": "0.2.0-experimental.0",
10
+ "@hubspot/local-dev-lib": "0.7.1-experimental.0",
11
+ "@hubspot/project-parsing-lib": "0.12.0",
12
12
  "@hubspot/serverless-dev-runtime": "7.0.7",
13
13
  "@hubspot/ui-extensions-dev-server": "1.1.8",
14
14
  "@inquirer/prompts": "7.1.0",