@hubspot/cli 7.9.0 → 7.10.0-beta.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/commands/__tests__/project.test.js +2 -0
- package/commands/account/__tests__/rename.test.js +35 -0
- package/commands/account/createOverride.js +2 -12
- package/commands/account/removeOverride.js +2 -10
- package/commands/account/rename.d.ts +1 -1
- package/commands/account/rename.js +5 -2
- package/commands/cms/theme/preview.js +1 -4
- package/commands/config/set.js +1 -2
- package/commands/getStarted.js +13 -19
- package/commands/hubdb.d.ts +1 -1
- package/commands/project/__tests__/updateDeps.test.d.ts +1 -0
- package/commands/project/__tests__/updateDeps.test.js +142 -0
- package/commands/project/create.js +0 -1
- package/commands/project/dev/index.js +8 -1
- package/commands/project/listBuilds.js +7 -1
- package/commands/project/updateDeps.d.ts +6 -0
- package/commands/project/updateDeps.js +80 -0
- package/commands/project/upload.js +7 -1
- package/commands/project/validate.js +7 -1
- package/commands/project/watch.js +7 -2
- package/commands/project.js +2 -0
- package/commands/testAccount/__tests__/create.test.js +68 -0
- package/commands/testAccount/create.d.ts +8 -0
- package/commands/testAccount/create.js +134 -44
- package/commands/testAccount/importData.d.ts +1 -1
- package/lang/en.d.ts +3194 -3184
- package/lang/en.js +43 -8
- package/lib/__tests__/dependencyManagement.test.js +273 -1
- package/lib/commonOpts.js +2 -5
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +6 -0
- package/lib/dependencyManagement.d.ts +8 -2
- package/lib/dependencyManagement.js +75 -12
- package/lib/mcp/__tests__/setup.test.d.ts +1 -0
- package/lib/mcp/__tests__/setup.test.js +127 -0
- package/lib/mcp/setup.d.ts +4 -12
- package/lib/mcp/setup.js +34 -1
- package/lib/middleware/autoUpdateMiddleware.d.ts +3 -1
- package/lib/middleware/autoUpdateMiddleware.js +1 -0
- package/lib/npm.d.ts +3 -0
- package/lib/npm.js +6 -0
- package/lib/projects/__tests__/components.test.js +148 -24
- package/lib/projects/__tests__/platformVersion.test.js +5 -1
- package/lib/projects/__tests__/projects.test.js +13 -42
- package/lib/projects/components.js +76 -20
- package/lib/projects/config.js +5 -9
- package/lib/projects/platformVersion.js +1 -1
- package/lib/prompts/__tests__/createDeveloperTestAccountConfigPrompt.test.d.ts +1 -0
- package/lib/prompts/__tests__/createDeveloperTestAccountConfigPrompt.test.js +153 -0
- package/lib/prompts/createDeveloperTestAccountConfigPrompt.d.ts +5 -0
- package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +76 -66
- package/mcp-server/tools/cms/HsCreateFunctionTool.js +6 -0
- package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +4 -4
- package/mcp-server/tools/cms/HsCreateModuleTool.js +6 -0
- package/mcp-server/tools/cms/HsCreateTemplateTool.js +6 -0
- package/mcp-server/tools/cms/HsFunctionLogsTool.d.ts +4 -4
- package/mcp-server/tools/cms/HsFunctionLogsTool.js +4 -0
- package/mcp-server/tools/cms/HsListFunctionsTool.js +4 -0
- package/mcp-server/tools/cms/HsListTool.js +4 -0
- package/mcp-server/tools/index.js +2 -0
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -0
- package/mcp-server/tools/project/CreateProjectTool.js +6 -0
- package/mcp-server/tools/project/CreateTestAccountTool.d.ts +41 -0
- package/mcp-server/tools/project/CreateTestAccountTool.js +137 -0
- package/mcp-server/tools/project/DeployProjectTool.js +6 -0
- package/mcp-server/tools/project/DocFetchTool.js +4 -0
- package/mcp-server/tools/project/DocsSearchTool.js +4 -0
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +4 -0
- package/mcp-server/tools/project/GetApplicationInfoTool.js +4 -0
- package/mcp-server/tools/project/GetConfigValuesTool.js +4 -0
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +4 -0
- package/mcp-server/tools/project/UploadProjectTools.d.ts +9 -3
- package/mcp-server/tools/project/UploadProjectTools.js +50 -4
- package/mcp-server/tools/project/ValidateProjectTool.js +4 -0
- package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.d.ts +1 -0
- package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +231 -0
- package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +2 -2
- package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +56 -4
- package/package.json +2 -2
- package/lang/en.lyaml +0 -1508
- package/lib/lang.d.ts +0 -8
- package/lib/lang.js +0 -72
|
@@ -1,9 +1,51 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import { coerceToValidUid, metafileExtension, } from '@hubspot/project-parsing-lib';
|
|
4
|
+
import { fileExists } from '../validation.js';
|
|
4
5
|
import { uiLogger } from '../ui/logger.js';
|
|
5
6
|
import { AppKey } from '@hubspot/project-parsing-lib/src/lib/constants.js';
|
|
6
7
|
import { lib } from '../../lang/en.js';
|
|
8
|
+
import { debugError } from '../errorHandlers/index.js';
|
|
9
|
+
// Prefix for the metafile extension
|
|
10
|
+
const metafileExtensionPrefix = path.parse(metafileExtension).name;
|
|
11
|
+
function applyDifferentiatorToFilename(filename, differentiator, isHsMetaFile) {
|
|
12
|
+
const { name, ext, dir } = path.parse(filename);
|
|
13
|
+
if (isHsMetaFile) {
|
|
14
|
+
return path.join(dir, `${name.replace(metafileExtensionPrefix, '')}-${differentiator}${metafileExtension}`);
|
|
15
|
+
}
|
|
16
|
+
return path.join(dir, `${name}-${differentiator}${ext}`);
|
|
17
|
+
}
|
|
18
|
+
// Generates safe filename differentiators, avoiding collisions with existing filenames
|
|
19
|
+
// E.x. "NewCard.tsx" -> "NewCard-1.tsx"
|
|
20
|
+
function generateSafeFilenameDifferentiator(sourceFiles, hsMetaFiles) {
|
|
21
|
+
let differentiator = 1;
|
|
22
|
+
let isDifferentiatorUnique = false;
|
|
23
|
+
let maxAttempts = 10;
|
|
24
|
+
while (!isDifferentiatorUnique) {
|
|
25
|
+
differentiator++;
|
|
26
|
+
maxAttempts--;
|
|
27
|
+
try {
|
|
28
|
+
const isDifferentiatorUniqueForSourceFiles = sourceFiles.every(file => {
|
|
29
|
+
return !fileExists(applyDifferentiatorToFilename(file, differentiator, false));
|
|
30
|
+
});
|
|
31
|
+
const isDifferentiatorUniqueForHsMetaFiles = hsMetaFiles.every(file => {
|
|
32
|
+
return !fileExists(applyDifferentiatorToFilename(file, differentiator, true));
|
|
33
|
+
});
|
|
34
|
+
isDifferentiatorUnique =
|
|
35
|
+
isDifferentiatorUniqueForSourceFiles &&
|
|
36
|
+
isDifferentiatorUniqueForHsMetaFiles;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
uiLogger.debug(lib.projects.generateSafeFilenameDifferentiator.failedToCheckFiles);
|
|
40
|
+
maxAttempts = 0;
|
|
41
|
+
}
|
|
42
|
+
// If we've tried too many times, just use a timestamp
|
|
43
|
+
if (maxAttempts <= 0) {
|
|
44
|
+
return Date.now();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return differentiator;
|
|
48
|
+
}
|
|
7
49
|
// Handles a collision between component source files
|
|
8
50
|
export function handleComponentCollision({ dest, src, collisions }) {
|
|
9
51
|
const hsMetaFiles = [];
|
|
@@ -20,19 +62,20 @@ export function handleComponentCollision({ dest, src, collisions }) {
|
|
|
20
62
|
sourceFiles.push(collision);
|
|
21
63
|
}
|
|
22
64
|
});
|
|
23
|
-
const
|
|
24
|
-
|
|
65
|
+
const filenameDifferentiator = generateSafeFilenameDifferentiator(sourceFiles, hsMetaFiles);
|
|
66
|
+
// Exclude markdown files fromthe rename process because they should not be duplicated
|
|
67
|
+
const sourceFilenameMapping = sourceFiles
|
|
68
|
+
.filter(filename => !filename.endsWith('.md'))
|
|
69
|
+
.reduce((acc, filename) => {
|
|
25
70
|
return {
|
|
26
71
|
...acc,
|
|
27
|
-
[filename]:
|
|
72
|
+
[filename]: applyDifferentiatorToFilename(filename, filenameDifferentiator, false),
|
|
28
73
|
};
|
|
29
74
|
}, {});
|
|
30
|
-
const metafileExtensionPrefix = path.parse(metafileExtension).name;
|
|
31
75
|
const metaFilenameMapping = hsMetaFiles.reduce((acc, filename) => {
|
|
32
|
-
const { name, dir } = path.parse(filename);
|
|
33
76
|
return {
|
|
34
77
|
...acc,
|
|
35
|
-
[filename]:
|
|
78
|
+
[filename]: applyDifferentiatorToFilename(filename, filenameDifferentiator, true),
|
|
36
79
|
};
|
|
37
80
|
}, {});
|
|
38
81
|
// Update the metafiles that might contain references to the old filenames
|
|
@@ -81,22 +124,35 @@ export function updateHsMetaFilesWithAutoGeneratedFields(projectName, hsMetaFile
|
|
|
81
124
|
uiLogger.log('');
|
|
82
125
|
uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.header);
|
|
83
126
|
for (const hsMetaFile of hsMetaFilePaths) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
127
|
+
try {
|
|
128
|
+
const component = JSON.parse(fs.readFileSync(hsMetaFile).toString());
|
|
129
|
+
const getBaseUid = () => {
|
|
130
|
+
const customUid = coerceToValidUid(`${projectName}_${component.type}`);
|
|
131
|
+
if (customUid) {
|
|
132
|
+
return customUid.replace(/-/g, '_');
|
|
133
|
+
}
|
|
134
|
+
return component.uid;
|
|
135
|
+
};
|
|
136
|
+
let uid = getBaseUid();
|
|
137
|
+
let differentiator = 1;
|
|
138
|
+
while (existingUids.includes(uid)) {
|
|
139
|
+
differentiator++;
|
|
140
|
+
uid = `${getBaseUid()}-${differentiator}`;
|
|
141
|
+
}
|
|
142
|
+
component.uid = uid;
|
|
143
|
+
if (component.type === AppKey && component.config) {
|
|
144
|
+
component.config.name = `${projectName}-Application`;
|
|
145
|
+
uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.applicationLog(component.type, component.uid, component.config.name));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.componentLog(component.type, component.uid));
|
|
149
|
+
}
|
|
150
|
+
fs.writeFileSync(hsMetaFile, JSON.stringify(component, null, 2));
|
|
90
151
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.applicationLog(component.type, component.uid, component.config.name));
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
uiLogger.log(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.componentLog(component.type, component.uid));
|
|
152
|
+
catch (error) {
|
|
153
|
+
debugError(error);
|
|
154
|
+
uiLogger.error(lib.projects.updateHsMetaFilesWithAutoGeneratedFields.failedToUpdate(hsMetaFile));
|
|
98
155
|
}
|
|
99
|
-
fs.writeFileSync(hsMetaFile, JSON.stringify(component, null, 2));
|
|
100
156
|
}
|
|
101
157
|
uiLogger.log('');
|
|
102
158
|
}
|
package/lib/projects/config.js
CHANGED
|
@@ -4,8 +4,8 @@ import findup from 'findup-sync';
|
|
|
4
4
|
import { getAbsoluteFilePath, getCwd } from '@hubspot/local-dev-lib/path';
|
|
5
5
|
import { PROJECT_CONFIG_FILE } from '../constants.js';
|
|
6
6
|
import { lib } from '../../lang/en.js';
|
|
7
|
-
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
8
7
|
import { uiLogger } from '../ui/logger.js';
|
|
8
|
+
import ProjectValidationError from '../errors/ProjectValidationError.js';
|
|
9
9
|
export function writeProjectConfig(configPath, config) {
|
|
10
10
|
try {
|
|
11
11
|
fs.ensureFileSync(configPath);
|
|
@@ -50,21 +50,17 @@ export async function getProjectConfig(dir) {
|
|
|
50
50
|
}
|
|
51
51
|
export function validateProjectConfig(projectConfig, projectDir) {
|
|
52
52
|
if (!projectConfig || !projectDir) {
|
|
53
|
-
|
|
54
|
-
return process.exit(EXIT_CODES.ERROR);
|
|
53
|
+
throw new ProjectValidationError(lib.projects.validateProjectConfig.configNotFound);
|
|
55
54
|
}
|
|
56
55
|
if (!projectConfig.name || !projectConfig.srcDir) {
|
|
57
|
-
|
|
58
|
-
return process.exit(EXIT_CODES.ERROR);
|
|
56
|
+
throw new ProjectValidationError(lib.projects.validateProjectConfig.configMissingFields);
|
|
59
57
|
}
|
|
60
58
|
const resolvedPath = path.resolve(projectDir, projectConfig.srcDir);
|
|
61
59
|
if (!resolvedPath.startsWith(projectDir)) {
|
|
62
60
|
const projectConfigFile = path.relative('.', path.join(projectDir, PROJECT_CONFIG_FILE));
|
|
63
|
-
|
|
64
|
-
return process.exit(EXIT_CODES.ERROR);
|
|
61
|
+
throw new ProjectValidationError(lib.projects.validateProjectConfig.srcOutsideProjectDir(projectConfigFile, projectConfig.srcDir));
|
|
65
62
|
}
|
|
66
63
|
if (!fs.existsSync(resolvedPath)) {
|
|
67
|
-
|
|
68
|
-
return process.exit(EXIT_CODES.ERROR);
|
|
64
|
+
throw new ProjectValidationError(lib.projects.validateProjectConfig.srcDirNotFound(projectConfig.srcDir, projectDir));
|
|
69
65
|
}
|
|
70
66
|
}
|
|
@@ -5,6 +5,6 @@ export function isV2Project(platformVersion) {
|
|
|
5
5
|
if (platformVersion.toLowerCase() === 'unstable') {
|
|
6
6
|
return true;
|
|
7
7
|
}
|
|
8
|
-
const [year, minor] = platformVersion.split(
|
|
8
|
+
const [year, minor] = platformVersion.split(/[.-]/);
|
|
9
9
|
return Number(year) >= 2025 && Number(minor) >= 2;
|
|
10
10
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { createDeveloperTestAccountConfigPrompt } from '../createDeveloperTestAccountConfigPrompt.js';
|
|
3
|
+
import * as promptUtils from '../promptUtils.js';
|
|
4
|
+
vi.mock('../promptUtils.js');
|
|
5
|
+
describe('createDeveloperTestAccountConfigPrompt', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
describe('with name and description provided via args', () => {
|
|
10
|
+
it('should skip name and description prompts when provided', async () => {
|
|
11
|
+
const mockPromptUser = vi.mocked(promptUtils.promptUser);
|
|
12
|
+
mockPromptUser.mockResolvedValueOnce({}); // name/description prompts skipped
|
|
13
|
+
mockPromptUser.mockResolvedValueOnce({
|
|
14
|
+
useDefaultAccountLevels: 'default',
|
|
15
|
+
}); // tier selection
|
|
16
|
+
const result = await createDeveloperTestAccountConfigPrompt({
|
|
17
|
+
name: 'TestAccount',
|
|
18
|
+
description: 'Test description',
|
|
19
|
+
});
|
|
20
|
+
expect(result).toEqual({
|
|
21
|
+
accountName: 'TestAccount',
|
|
22
|
+
description: 'Test description',
|
|
23
|
+
marketingLevel: 'ENTERPRISE',
|
|
24
|
+
opsLevel: 'ENTERPRISE',
|
|
25
|
+
serviceLevel: 'ENTERPRISE',
|
|
26
|
+
salesLevel: 'ENTERPRISE',
|
|
27
|
+
contentLevel: 'ENTERPRISE',
|
|
28
|
+
});
|
|
29
|
+
expect(mockPromptUser).toHaveBeenCalledTimes(2);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('with tier flags provided', () => {
|
|
33
|
+
it('should skip tier prompts and use provided values with defaults', async () => {
|
|
34
|
+
const mockPromptUser = vi.mocked(promptUtils.promptUser);
|
|
35
|
+
mockPromptUser.mockResolvedValueOnce({}); // name/description prompts skipped
|
|
36
|
+
const result = await createDeveloperTestAccountConfigPrompt({
|
|
37
|
+
name: 'TestAccount',
|
|
38
|
+
description: 'Test',
|
|
39
|
+
marketingLevel: 'PROFESSIONAL',
|
|
40
|
+
salesLevel: 'STARTER',
|
|
41
|
+
});
|
|
42
|
+
expect(result).toEqual({
|
|
43
|
+
accountName: 'TestAccount',
|
|
44
|
+
description: 'Test',
|
|
45
|
+
marketingLevel: 'PROFESSIONAL',
|
|
46
|
+
opsLevel: 'ENTERPRISE',
|
|
47
|
+
serviceLevel: 'ENTERPRISE',
|
|
48
|
+
salesLevel: 'STARTER',
|
|
49
|
+
contentLevel: 'ENTERPRISE',
|
|
50
|
+
});
|
|
51
|
+
// Should only call promptUser once (for name/description which are skipped)
|
|
52
|
+
expect(mockPromptUser).toHaveBeenCalledTimes(1);
|
|
53
|
+
});
|
|
54
|
+
it('should default unprovided tiers to ENTERPRISE', async () => {
|
|
55
|
+
const mockPromptUser = vi.mocked(promptUtils.promptUser);
|
|
56
|
+
mockPromptUser.mockResolvedValueOnce({
|
|
57
|
+
description: 'Test',
|
|
58
|
+
}); // description prompt (name provided via args, description not provided)
|
|
59
|
+
const result = await createDeveloperTestAccountConfigPrompt({
|
|
60
|
+
name: 'TestAccount',
|
|
61
|
+
contentLevel: 'FREE',
|
|
62
|
+
});
|
|
63
|
+
expect(result).toEqual({
|
|
64
|
+
accountName: 'TestAccount',
|
|
65
|
+
description: 'Test',
|
|
66
|
+
marketingLevel: 'ENTERPRISE',
|
|
67
|
+
opsLevel: 'ENTERPRISE',
|
|
68
|
+
serviceLevel: 'ENTERPRISE',
|
|
69
|
+
salesLevel: 'ENTERPRISE',
|
|
70
|
+
contentLevel: 'FREE',
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('with no flags provided', () => {
|
|
75
|
+
it('should prompt for name, description, and tier selection', async () => {
|
|
76
|
+
const mockPromptUser = vi.mocked(promptUtils.promptUser);
|
|
77
|
+
// First call: name/description prompts
|
|
78
|
+
mockPromptUser.mockResolvedValueOnce({
|
|
79
|
+
accountName: 'PromptedAccount',
|
|
80
|
+
description: 'Prompted description',
|
|
81
|
+
});
|
|
82
|
+
// Second call: tier selection
|
|
83
|
+
mockPromptUser.mockResolvedValueOnce({
|
|
84
|
+
useDefaultAccountLevels: 'default',
|
|
85
|
+
});
|
|
86
|
+
const result = await createDeveloperTestAccountConfigPrompt({});
|
|
87
|
+
expect(result).toEqual({
|
|
88
|
+
accountName: 'PromptedAccount',
|
|
89
|
+
description: 'Prompted description',
|
|
90
|
+
marketingLevel: 'ENTERPRISE',
|
|
91
|
+
opsLevel: 'ENTERPRISE',
|
|
92
|
+
serviceLevel: 'ENTERPRISE',
|
|
93
|
+
salesLevel: 'ENTERPRISE',
|
|
94
|
+
contentLevel: 'ENTERPRISE',
|
|
95
|
+
});
|
|
96
|
+
expect(mockPromptUser).toHaveBeenCalledTimes(2);
|
|
97
|
+
});
|
|
98
|
+
it('should allow manual tier selection', async () => {
|
|
99
|
+
const mockPromptUser = vi.mocked(promptUtils.promptUser);
|
|
100
|
+
mockPromptUser.mockResolvedValueOnce({
|
|
101
|
+
accountName: 'TestAccount',
|
|
102
|
+
description: 'Test',
|
|
103
|
+
}); // name/description
|
|
104
|
+
mockPromptUser.mockResolvedValueOnce({
|
|
105
|
+
useDefaultAccountLevels: 'manual',
|
|
106
|
+
}); // tier choice
|
|
107
|
+
mockPromptUser.mockResolvedValueOnce({
|
|
108
|
+
testAccountLevels: [
|
|
109
|
+
{ hub: 'MARKETING', tier: 'PROFESSIONAL' },
|
|
110
|
+
{ hub: 'OPS', tier: 'STARTER' },
|
|
111
|
+
{ hub: 'SERVICE', tier: 'ENTERPRISE' },
|
|
112
|
+
{ hub: 'SALES', tier: 'FREE' },
|
|
113
|
+
{ hub: 'CONTENT', tier: 'ENTERPRISE' },
|
|
114
|
+
],
|
|
115
|
+
}); // manual tier selection
|
|
116
|
+
const result = await createDeveloperTestAccountConfigPrompt({});
|
|
117
|
+
expect(result).toEqual({
|
|
118
|
+
accountName: 'TestAccount',
|
|
119
|
+
description: 'Test',
|
|
120
|
+
marketingLevel: 'PROFESSIONAL',
|
|
121
|
+
opsLevel: 'STARTER',
|
|
122
|
+
serviceLevel: 'ENTERPRISE',
|
|
123
|
+
salesLevel: 'FREE',
|
|
124
|
+
contentLevel: 'ENTERPRISE',
|
|
125
|
+
});
|
|
126
|
+
expect(mockPromptUser).toHaveBeenCalledTimes(3);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('with only name provided', () => {
|
|
130
|
+
it('should skip name prompt but show description and tier prompts', async () => {
|
|
131
|
+
const mockPromptUser = vi.mocked(promptUtils.promptUser);
|
|
132
|
+
mockPromptUser.mockResolvedValueOnce({
|
|
133
|
+
description: 'Prompted description',
|
|
134
|
+
}); // description prompt (name skipped)
|
|
135
|
+
mockPromptUser.mockResolvedValueOnce({
|
|
136
|
+
useDefaultAccountLevels: 'default',
|
|
137
|
+
}); // tier selection
|
|
138
|
+
const result = await createDeveloperTestAccountConfigPrompt({
|
|
139
|
+
name: 'TestAccount',
|
|
140
|
+
});
|
|
141
|
+
expect(result).toEqual({
|
|
142
|
+
accountName: 'TestAccount',
|
|
143
|
+
description: 'Prompted description',
|
|
144
|
+
marketingLevel: 'ENTERPRISE',
|
|
145
|
+
opsLevel: 'ENTERPRISE',
|
|
146
|
+
serviceLevel: 'ENTERPRISE',
|
|
147
|
+
salesLevel: 'ENTERPRISE',
|
|
148
|
+
contentLevel: 'ENTERPRISE',
|
|
149
|
+
});
|
|
150
|
+
expect(mockPromptUser).toHaveBeenCalledTimes(2);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -14,5 +14,10 @@ export type HubConfig = {
|
|
|
14
14
|
export declare function createDeveloperTestAccountConfigPrompt(args?: {
|
|
15
15
|
name?: string;
|
|
16
16
|
description?: string;
|
|
17
|
+
marketingLevel?: AccountLevel;
|
|
18
|
+
opsLevel?: AccountLevel;
|
|
19
|
+
serviceLevel?: AccountLevel;
|
|
20
|
+
salesLevel?: AccountLevel;
|
|
21
|
+
contentLevel?: AccountLevel;
|
|
17
22
|
}, supportFlags?: boolean): Promise<DeveloperTestAccountConfig>;
|
|
18
23
|
export {};
|
|
@@ -49,15 +49,17 @@ const TEST_ACCOUNT_TIERS = [
|
|
|
49
49
|
new Separator(),
|
|
50
50
|
];
|
|
51
51
|
export async function createDeveloperTestAccountConfigPrompt(args = {}, supportFlags = true) {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
const hasAnyTierLevels = !!(args.marketingLevel ||
|
|
53
|
+
args.opsLevel ||
|
|
54
|
+
args.serviceLevel ||
|
|
55
|
+
args.salesLevel ||
|
|
56
|
+
args.contentLevel);
|
|
57
|
+
const result = await promptUser([
|
|
58
|
+
{
|
|
58
59
|
name: 'accountName',
|
|
59
60
|
message: lib.prompts.createDeveloperTestAccountConfigPrompt.namePrompt(supportFlags),
|
|
60
61
|
type: 'input',
|
|
62
|
+
when: !args.name,
|
|
61
63
|
validate: value => {
|
|
62
64
|
if (!value) {
|
|
63
65
|
return lib.prompts.createDeveloperTestAccountConfigPrompt.errors
|
|
@@ -65,58 +67,67 @@ export async function createDeveloperTestAccountConfigPrompt(args = {}, supportF
|
|
|
65
67
|
}
|
|
66
68
|
return true;
|
|
67
69
|
},
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
if (!accountDescription) {
|
|
72
|
-
const descriptionPromptResult = await promptUser({
|
|
70
|
+
},
|
|
71
|
+
{
|
|
73
72
|
name: 'description',
|
|
74
73
|
message: lib.prompts.createDeveloperTestAccountConfigPrompt.descriptionPrompt(supportFlags),
|
|
75
74
|
type: 'input',
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
{
|
|
91
|
-
name: lib.prompts.createDeveloperTestAccountConfigPrompt
|
|
92
|
-
.useDefaultAccountLevelsPrompt.manual,
|
|
93
|
-
value: 'manual',
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
});
|
|
97
|
-
if (useDefaultAccountLevelsPromptResult.useDefaultAccountLevels === 'default') {
|
|
98
|
-
accountLevelsArray = [
|
|
99
|
-
{ hub: 'MARKETING', tier: AccountTiers.ENTERPRISE },
|
|
100
|
-
{ hub: 'OPS', tier: AccountTiers.ENTERPRISE },
|
|
101
|
-
{ hub: 'SERVICE', tier: AccountTiers.ENTERPRISE },
|
|
102
|
-
{ hub: 'SALES', tier: AccountTiers.ENTERPRISE },
|
|
103
|
-
{ hub: 'CONTENT', tier: AccountTiers.ENTERPRISE },
|
|
104
|
-
];
|
|
75
|
+
when: !args.description,
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
const accountName = args.name || result.accountName;
|
|
79
|
+
const description = args.description || result.description;
|
|
80
|
+
let accountLevels = {};
|
|
81
|
+
if (hasAnyTierLevels) {
|
|
82
|
+
accountLevels = {
|
|
83
|
+
marketingLevel: args.marketingLevel || 'ENTERPRISE',
|
|
84
|
+
opsLevel: args.opsLevel || 'ENTERPRISE',
|
|
85
|
+
serviceLevel: args.serviceLevel || 'ENTERPRISE',
|
|
86
|
+
salesLevel: args.salesLevel || 'ENTERPRISE',
|
|
87
|
+
contentLevel: args.contentLevel || 'ENTERPRISE',
|
|
88
|
+
};
|
|
105
89
|
}
|
|
106
90
|
else {
|
|
107
|
-
const
|
|
108
|
-
name: '
|
|
109
|
-
message: lib.prompts.createDeveloperTestAccountConfigPrompt
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
choices:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
91
|
+
const tierChoiceResult = await promptUser({
|
|
92
|
+
name: 'useDefaultAccountLevels',
|
|
93
|
+
message: lib.prompts.createDeveloperTestAccountConfigPrompt
|
|
94
|
+
.useDefaultAccountLevelsPrompt.message,
|
|
95
|
+
type: 'list',
|
|
96
|
+
choices: [
|
|
97
|
+
{
|
|
98
|
+
name: lib.prompts.createDeveloperTestAccountConfigPrompt
|
|
99
|
+
.useDefaultAccountLevelsPrompt.default,
|
|
100
|
+
value: 'default',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: lib.prompts.createDeveloperTestAccountConfigPrompt
|
|
104
|
+
.useDefaultAccountLevelsPrompt.manual,
|
|
105
|
+
value: 'manual',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
if (tierChoiceResult.useDefaultAccountLevels === 'default') {
|
|
110
|
+
accountLevels = {
|
|
111
|
+
marketingLevel: 'ENTERPRISE',
|
|
112
|
+
opsLevel: 'ENTERPRISE',
|
|
113
|
+
serviceLevel: 'ENTERPRISE',
|
|
114
|
+
salesLevel: 'ENTERPRISE',
|
|
115
|
+
contentLevel: 'ENTERPRISE',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const tierResult = await promptUser({
|
|
120
|
+
name: 'testAccountLevels',
|
|
121
|
+
message: lib.prompts.createDeveloperTestAccountConfigPrompt.tiersPrompt,
|
|
122
|
+
type: 'checkbox',
|
|
123
|
+
pageSize: 13,
|
|
124
|
+
choices: TEST_ACCOUNT_TIERS,
|
|
125
|
+
loop: false,
|
|
126
|
+
validate: choices => {
|
|
127
|
+
if (choices?.length < Object.keys(hubs).length) {
|
|
128
|
+
return lib.prompts.createDeveloperTestAccountConfigPrompt.errors
|
|
129
|
+
.allHubsRequired;
|
|
130
|
+
}
|
|
120
131
|
const hubMap = {};
|
|
121
132
|
for (const choice of choices) {
|
|
122
133
|
const { hub } = choice.value;
|
|
@@ -126,21 +137,20 @@ export async function createDeveloperTestAccountConfigPrompt(args = {}, supportF
|
|
|
126
137
|
}
|
|
127
138
|
hubMap[hub] = true;
|
|
128
139
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
return true;
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
accountLevels = tierResult.testAccountLevels.reduce((acc, level) => {
|
|
144
|
+
const { hub: hubName, tier: hubTier } = level;
|
|
145
|
+
const hubLevel = hubs[hubName];
|
|
146
|
+
acc[hubLevel] = hubTier;
|
|
147
|
+
return acc;
|
|
148
|
+
}, {});
|
|
149
|
+
}
|
|
134
150
|
}
|
|
135
|
-
const accountLevels = accountLevelsArray.reduce((acc, level) => {
|
|
136
|
-
const { hub: hubName, tier: hubTier } = level;
|
|
137
|
-
const hubLevel = hubs[hubName];
|
|
138
|
-
acc[hubLevel] = hubTier;
|
|
139
|
-
return acc;
|
|
140
|
-
}, {});
|
|
141
151
|
return {
|
|
142
|
-
accountName
|
|
143
|
-
description
|
|
152
|
+
accountName,
|
|
153
|
+
description,
|
|
144
154
|
...accountLevels,
|
|
145
155
|
};
|
|
146
156
|
}
|
|
@@ -91,6 +91,12 @@ export class HsCreateFunctionTool extends Tool {
|
|
|
91
91
|
title: 'Create HubSpot CMS Serverless Function',
|
|
92
92
|
description: `Creates a new HubSpot CMS serverless function using the hs create function command. Functions can be created non-interactively by specifying functionsFolder, filename, and endpointPath. Supports all HTTP methods (${HTTP_METHODS.join(', ')}).`,
|
|
93
93
|
inputSchema,
|
|
94
|
+
annotations: {
|
|
95
|
+
readOnlyHint: false,
|
|
96
|
+
destructiveHint: false,
|
|
97
|
+
idempotentHint: false,
|
|
98
|
+
openWorldHint: false,
|
|
99
|
+
},
|
|
94
100
|
}, this.handler);
|
|
95
101
|
}
|
|
96
102
|
}
|
|
@@ -13,20 +13,20 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
13
13
|
}, "strip", z.ZodTypeAny, {
|
|
14
14
|
absoluteCurrentWorkingDirectory: string;
|
|
15
15
|
dest?: string | undefined;
|
|
16
|
+
global?: boolean | undefined;
|
|
16
17
|
moduleLabel?: string | undefined;
|
|
17
18
|
reactType?: boolean | undefined;
|
|
18
|
-
global?: boolean | undefined;
|
|
19
|
-
availableForNewContent?: boolean | undefined;
|
|
20
19
|
contentTypes?: string | undefined;
|
|
20
|
+
availableForNewContent?: boolean | undefined;
|
|
21
21
|
userSuppliedName?: string | undefined;
|
|
22
22
|
}, {
|
|
23
23
|
absoluteCurrentWorkingDirectory: string;
|
|
24
24
|
dest?: string | undefined;
|
|
25
|
+
global?: boolean | undefined;
|
|
25
26
|
moduleLabel?: string | undefined;
|
|
26
27
|
reactType?: boolean | undefined;
|
|
27
|
-
global?: boolean | undefined;
|
|
28
|
-
availableForNewContent?: boolean | undefined;
|
|
29
28
|
contentTypes?: string | undefined;
|
|
29
|
+
availableForNewContent?: boolean | undefined;
|
|
30
30
|
userSuppliedName?: string | undefined;
|
|
31
31
|
}>;
|
|
32
32
|
export type HsCreateModuleInputSchema = z.infer<typeof inputSchemaZodObject>;
|
|
@@ -113,6 +113,12 @@ export class HsCreateModuleTool extends Tool {
|
|
|
113
113
|
title: 'Create HubSpot CMS Module',
|
|
114
114
|
description: 'Creates a new HubSpot CMS module using the hs create module command. Modules can be created non-interactively by specifying moduleLabel and other module options. You can create either HubL or React modules by setting the reactType parameter.',
|
|
115
115
|
inputSchema,
|
|
116
|
+
annotations: {
|
|
117
|
+
readOnlyHint: false,
|
|
118
|
+
destructiveHint: false,
|
|
119
|
+
idempotentHint: false,
|
|
120
|
+
openWorldHint: false,
|
|
121
|
+
},
|
|
116
122
|
}, this.handler);
|
|
117
123
|
}
|
|
118
124
|
}
|
|
@@ -70,6 +70,12 @@ export class HsCreateTemplateTool extends Tool {
|
|
|
70
70
|
title: 'Create HubSpot CMS Template',
|
|
71
71
|
description: `Creates a new HubSpot CMS template using the hs create template command. Templates can be created non-interactively by specifying templateType. Supports all template types including: ${TEMPLATE_TYPES.join(', ')}.`,
|
|
72
72
|
inputSchema,
|
|
73
|
+
annotations: {
|
|
74
|
+
readOnlyHint: false,
|
|
75
|
+
destructiveHint: false,
|
|
76
|
+
idempotentHint: false,
|
|
77
|
+
openWorldHint: false,
|
|
78
|
+
},
|
|
73
79
|
}, this.handler);
|
|
74
80
|
}
|
|
75
81
|
}
|
|
@@ -12,16 +12,16 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
12
12
|
endpoint: string;
|
|
13
13
|
absoluteCurrentWorkingDirectory: string;
|
|
14
14
|
account?: string | undefined;
|
|
15
|
-
limit?: number | undefined;
|
|
16
|
-
compact?: boolean | undefined;
|
|
17
15
|
latest?: boolean | undefined;
|
|
16
|
+
compact?: boolean | undefined;
|
|
17
|
+
limit?: number | undefined;
|
|
18
18
|
}, {
|
|
19
19
|
endpoint: string;
|
|
20
20
|
absoluteCurrentWorkingDirectory: string;
|
|
21
21
|
account?: string | undefined;
|
|
22
|
-
limit?: number | undefined;
|
|
23
|
-
compact?: boolean | undefined;
|
|
24
22
|
latest?: boolean | undefined;
|
|
23
|
+
compact?: boolean | undefined;
|
|
24
|
+
limit?: number | undefined;
|
|
25
25
|
}>;
|
|
26
26
|
export type HsFunctionLogsInputSchema = z.infer<typeof inputSchemaZodObject>;
|
|
27
27
|
export declare class HsFunctionLogsTool extends Tool<HsFunctionLogsInputSchema> {
|
|
@@ -64,6 +64,10 @@ export class HsFunctionLogsTool extends Tool {
|
|
|
64
64
|
title: 'Get HubSpot CMS serverless function logs for an endpoint',
|
|
65
65
|
description: 'Retrieve logs for HubSpot CMS serverless functions. Use this tool to help debug issues with serverless functions by reading the production logs. Supports various options like latest, compact, and limiting results. Use after listing functions with list-cms-serverless-functions to get the endpoint path.',
|
|
66
66
|
inputSchema,
|
|
67
|
+
annotations: {
|
|
68
|
+
readOnlyHint: true,
|
|
69
|
+
openWorldHint: true,
|
|
70
|
+
},
|
|
67
71
|
}, this.handler);
|
|
68
72
|
}
|
|
69
73
|
}
|
|
@@ -53,6 +53,10 @@ export class HsListFunctionsTool extends Tool {
|
|
|
53
53
|
title: 'List HubSpot CMS Serverless Functions',
|
|
54
54
|
description: 'Get a list of all serverless functions deployed in a HubSpot portal/account. Shows function routes, HTTP methods, secrets, and timestamps.',
|
|
55
55
|
inputSchema,
|
|
56
|
+
annotations: {
|
|
57
|
+
readOnlyHint: true,
|
|
58
|
+
openWorldHint: true,
|
|
59
|
+
},
|
|
56
60
|
}, this.handler);
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -53,6 +53,10 @@ export class HsListTool extends Tool {
|
|
|
53
53
|
title: 'List HubSpot CMS Directory Contents',
|
|
54
54
|
description: 'List remote contents of a HubSpot CMS directory.',
|
|
55
55
|
inputSchema,
|
|
56
|
+
annotations: {
|
|
57
|
+
readOnlyHint: true,
|
|
58
|
+
openWorldHint: true,
|
|
59
|
+
},
|
|
56
60
|
}, this.handler);
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -15,11 +15,13 @@ import { HsCreateTemplateTool } from './cms/HsCreateTemplateTool.js';
|
|
|
15
15
|
import { HsCreateFunctionTool } from './cms/HsCreateFunctionTool.js';
|
|
16
16
|
import { HsListFunctionsTool } from './cms/HsListFunctionsTool.js';
|
|
17
17
|
import { HsFunctionLogsTool } from './cms/HsFunctionLogsTool.js';
|
|
18
|
+
import { CreateTestAccountTool } from './project/CreateTestAccountTool.js';
|
|
18
19
|
export function registerProjectTools(mcpServer) {
|
|
19
20
|
return [
|
|
20
21
|
new UploadProjectTools(mcpServer).register(),
|
|
21
22
|
new CreateProjectTool(mcpServer).register(),
|
|
22
23
|
new GuidedWalkthroughTool(mcpServer).register(),
|
|
24
|
+
new CreateTestAccountTool(mcpServer).register(),
|
|
23
25
|
new DeployProjectTool(mcpServer).register(),
|
|
24
26
|
new AddFeatureToProjectTool(mcpServer).register(),
|
|
25
27
|
new ValidateProjectTool(mcpServer).register(),
|