@hubspot/cli 7.7.23-experimental.0 → 7.7.24-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.
- package/commands/account/auth.js +15 -4
- package/commands/auth.js +1 -1
- package/commands/mcp/start.d.ts +1 -0
- package/commands/mcp/start.js +12 -4
- package/commands/project/create.js +2 -2
- package/commands/project/validate.js +1 -0
- package/commands/sandbox/__tests__/create.test.js +207 -0
- package/commands/sandbox/create.d.ts +1 -1
- package/commands/sandbox/create.js +31 -16
- package/lang/en.d.ts +7 -3
- package/lang/en.js +12 -5
- package/lang/en.lyaml +4 -2
- package/lib/__tests__/buildAccount.test.js +62 -4
- package/lib/buildAccount.d.ts +4 -1
- package/lib/buildAccount.js +57 -2
- package/lib/commonOpts.js +25 -0
- package/lib/constants.d.ts +4 -0
- package/lib/constants.js +4 -0
- package/lib/errorHandlers/index.js +1 -3
- package/lib/errors/ProjectValidationError.d.ts +4 -0
- package/lib/errors/ProjectValidationError.js +9 -0
- package/lib/mcp/setup.d.ts +4 -0
- package/lib/mcp/setup.js +36 -0
- package/lib/projects/__tests__/LocalDevProcess.test.js +35 -0
- package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +170 -1
- package/lib/projects/add/v3AddComponent.js +2 -1
- package/lib/projects/create/index.js +2 -2
- package/lib/projects/create/v3.d.ts +0 -2
- package/lib/projects/create/v3.js +1 -3
- package/lib/projects/localDev/LocalDevProcess.d.ts +1 -0
- package/lib/projects/localDev/LocalDevProcess.js +3 -0
- package/lib/projects/localDev/LocalDevState.d.ts +1 -0
- package/lib/projects/localDev/LocalDevState.js +5 -0
- package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +2 -2
- package/lib/projects/localDev/LocalDevWebsocketServer.js +35 -29
- package/lib/projects/upload.js +5 -12
- package/lib/sandboxes.d.ts +4 -0
- package/lib/sandboxes.js +4 -0
- package/lib/ui/index.d.ts +6 -0
- package/lib/ui/index.js +3 -5
- package/mcp-server/tools/index.js +6 -4
- package/mcp-server/tools/project/{AddFeatureToProject.d.ts → AddFeatureToProjectTool.d.ts} +4 -4
- package/mcp-server/tools/project/{AddFeatureToProject.js → AddFeatureToProjectTool.js} +6 -14
- package/mcp-server/tools/project/CreateProjectTool.d.ts +3 -3
- package/mcp-server/tools/project/CreateProjectTool.js +4 -14
- package/mcp-server/tools/project/{DeployProject.d.ts → DeployProjectTool.d.ts} +1 -1
- package/mcp-server/tools/project/{DeployProject.js → DeployProjectTool.js} +2 -2
- package/mcp-server/tools/project/GetConfigValuesTool.d.ts +20 -0
- package/mcp-server/tools/project/GetConfigValuesTool.js +51 -0
- package/mcp-server/tools/project/UploadProjectTools.js +1 -1
- package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
- package/mcp-server/tools/project/__tests__/{AddFeatureToProject.test.js → AddFeatureToProjectTool.test.js} +7 -7
- package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +3 -4
- package/mcp-server/tools/project/__tests__/{DeployProject.test.js → DeployProjectTool.test.js} +4 -4
- package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +198 -0
- package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +2 -2
- package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
- package/mcp-server/tools/project/constants.d.ts +1 -0
- package/mcp-server/tools/project/constants.js +11 -0
- package/mcp-server/utils/__tests__/command.test.js +76 -3
- package/mcp-server/utils/command.d.ts +6 -0
- package/mcp-server/utils/command.js +19 -0
- package/package.json +2 -2
- package/mcp-server/utils/__tests__/project.test.js +0 -79
- package/mcp-server/utils/project.d.ts +0 -5
- package/mcp-server/utils/project.js +0 -14
- /package/mcp-server/tools/project/__tests__/{AddFeatureToProject.test.d.ts → AddFeatureToProjectTool.test.d.ts} +0 -0
- /package/mcp-server/tools/project/__tests__/{DeployProject.test.d.ts → DeployProjectTool.test.d.ts} +0 -0
- /package/mcp-server/{utils/__tests__/project.test.d.ts → tools/project/__tests__/GetConfigValuesTool.test.d.ts} +0 -0
package/commands/account/auth.js
CHANGED
|
@@ -15,6 +15,7 @@ import { setAsDefaultAccountPrompt } from '../../lib/prompts/setAsDefaultAccount
|
|
|
15
15
|
import { logError } from '../../lib/errorHandlers/index.js';
|
|
16
16
|
import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
17
17
|
import { uiFeatureHighlight } from '../../lib/ui/index.js';
|
|
18
|
+
import { parseStringToNumber } from '../../lib/parsing.js';
|
|
18
19
|
import { makeYargsBuilder } from '../../lib/yargsUtils.js';
|
|
19
20
|
import { commands } from '../../lang/en.js';
|
|
20
21
|
import { uiLogger } from '../../lib/ui/logger.js';
|
|
@@ -96,9 +97,19 @@ async function handleConfigMigration() {
|
|
|
96
97
|
const describe = commands.account.subcommands.auth.describe;
|
|
97
98
|
const command = 'auth';
|
|
98
99
|
async function handler(args) {
|
|
99
|
-
const { disableTracking, personalAccessKey: providedPersonalAccessKey,
|
|
100
|
+
const { disableTracking, personalAccessKey: providedPersonalAccessKey, userProvidedAccount, } = args;
|
|
101
|
+
let parsedUserProvidedAccountId;
|
|
102
|
+
if (userProvidedAccount) {
|
|
103
|
+
try {
|
|
104
|
+
parsedUserProvidedAccountId = parseStringToNumber(userProvidedAccount);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
uiLogger.error(commands.account.subcommands.auth.errors.invalidAccountIdProvided);
|
|
108
|
+
process.exit(EXIT_CODES.ERROR);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
100
111
|
if (!disableTracking) {
|
|
101
|
-
trackCommandUsage('account-auth', {},
|
|
112
|
+
trackCommandUsage('account-auth', {}, parsedUserProvidedAccountId);
|
|
102
113
|
await trackAuthAction('account-auth', authType, TRACKING_STATUS.STARTED);
|
|
103
114
|
}
|
|
104
115
|
const configMigrationSuccess = await handleConfigMigration();
|
|
@@ -112,7 +123,7 @@ async function handler(args) {
|
|
|
112
123
|
}
|
|
113
124
|
loadConfig('');
|
|
114
125
|
handleExit(deleteEmptyConfigFile);
|
|
115
|
-
const updatedConfig = await updateConfigWithNewAccount(args.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD, configAlreadyExists, providedPersonalAccessKey,
|
|
126
|
+
const updatedConfig = await updateConfigWithNewAccount(args.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD, configAlreadyExists, providedPersonalAccessKey, parsedUserProvidedAccountId);
|
|
116
127
|
if (!updatedConfig) {
|
|
117
128
|
if (!disableTracking) {
|
|
118
129
|
await trackAuthAction('account-auth', authType, TRACKING_STATUS.ERROR);
|
|
@@ -143,7 +154,7 @@ function accountAuthBuilder(yargs) {
|
|
|
143
154
|
yargs.options({
|
|
144
155
|
account: {
|
|
145
156
|
describe: commands.account.subcommands.auth.options.account,
|
|
146
|
-
type: '
|
|
157
|
+
type: 'number',
|
|
147
158
|
alias: 'a',
|
|
148
159
|
},
|
|
149
160
|
'disable-tracking': {
|
package/commands/auth.js
CHANGED
|
@@ -40,7 +40,7 @@ async function handler(args) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
catch (err) {
|
|
43
|
-
|
|
43
|
+
uiLogger.error(commands.auth.errors.invalidAccountIdProvided);
|
|
44
44
|
process.exit(EXIT_CODES.ERROR);
|
|
45
45
|
}
|
|
46
46
|
const authType = (authTypeFlagValue && authTypeFlagValue.toLowerCase()) ||
|
package/commands/mcp/start.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CommonArgs, YargsCommandModule } from '../../types/Yargs.js';
|
|
2
2
|
interface McpStartArgs extends CommonArgs {
|
|
3
3
|
aiAgent: string;
|
|
4
|
+
standAloneMode: boolean;
|
|
4
5
|
}
|
|
5
6
|
declare const mcpStartCommand: YargsCommandModule<unknown, McpStartArgs>;
|
|
6
7
|
export default mcpStartCommand;
|
package/commands/mcp/start.js
CHANGED
|
@@ -8,8 +8,11 @@ import { logError } from '../../lib/errorHandlers/index.js';
|
|
|
8
8
|
import { commands } from '../../lang/en.js';
|
|
9
9
|
import { handleExit } from '../../lib/process.js';
|
|
10
10
|
import { trackCommandUsage } from '../../lib/usageTracking.js';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
11
12
|
const command = 'start';
|
|
12
13
|
const describe = undefined; // Leave hidden for now
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
13
16
|
async function handler(args) {
|
|
14
17
|
try {
|
|
15
18
|
await import('@modelcontextprotocol/sdk/server/mcp.js');
|
|
@@ -19,9 +22,10 @@ async function handler(args) {
|
|
|
19
22
|
process.exit(EXIT_CODES.ERROR);
|
|
20
23
|
}
|
|
21
24
|
trackCommandUsage('mcp-start', {}, args.derivedAccountId);
|
|
22
|
-
await startMcpServer(args
|
|
25
|
+
await startMcpServer(args);
|
|
23
26
|
}
|
|
24
|
-
async function startMcpServer(
|
|
27
|
+
async function startMcpServer(args) {
|
|
28
|
+
const { aiAgent, standAloneMode } = args;
|
|
25
29
|
try {
|
|
26
30
|
const serverPath = path.join(__dirname, '..', '..', 'mcp-server', 'server.js');
|
|
27
31
|
// Check if server file exists
|
|
@@ -29,8 +33,8 @@ async function startMcpServer(aiAgent) {
|
|
|
29
33
|
uiLogger.error(commands.mcp.start.errors.serverFileNotFound(serverPath));
|
|
30
34
|
return;
|
|
31
35
|
}
|
|
32
|
-
uiLogger.
|
|
33
|
-
uiLogger.
|
|
36
|
+
uiLogger.debug(commands.mcp.start.startingServer);
|
|
37
|
+
uiLogger.debug(commands.mcp.start.stopInstructions);
|
|
34
38
|
const args = [serverPath];
|
|
35
39
|
// Start the server using ts-node
|
|
36
40
|
const child = spawn(`node`, args, {
|
|
@@ -38,6 +42,7 @@ async function startMcpServer(aiAgent) {
|
|
|
38
42
|
env: {
|
|
39
43
|
...process.env,
|
|
40
44
|
HUBSPOT_MCP_AI_AGENT: aiAgent || 'unknown',
|
|
45
|
+
HUBSPOT_MCP_STANDALONE_MODE: `${standAloneMode}`,
|
|
41
46
|
},
|
|
42
47
|
});
|
|
43
48
|
// Handle server process events
|
|
@@ -63,6 +68,9 @@ function startBuilder(yargs) {
|
|
|
63
68
|
yargs.option('ai-agent', {
|
|
64
69
|
type: 'string',
|
|
65
70
|
});
|
|
71
|
+
yargs.option('stand-alone-mode', {
|
|
72
|
+
type: 'boolean',
|
|
73
|
+
});
|
|
66
74
|
return yargs;
|
|
67
75
|
}
|
|
68
76
|
const builder = makeYargsBuilder(startBuilder, command, describe, {
|
|
@@ -5,7 +5,8 @@ import { getCwd } from '@hubspot/local-dev-lib/path';
|
|
|
5
5
|
import { trackCommandUsage } from '../../lib/usageTracking.js';
|
|
6
6
|
import { writeProjectConfig, getProjectConfig, } from '../../lib/projects/config.js';
|
|
7
7
|
import { EMPTY_PROJECT_TEMPLATE_NAME } from '../../lib/projects/create/legacy.js';
|
|
8
|
-
import {
|
|
8
|
+
import { generateComponentPaths } from '../../lib/projects/create/v3.js';
|
|
9
|
+
import { PROJECT_WITH_APP, EMPTY_PROJECT } from '../../lib/constants.js';
|
|
9
10
|
import { uiBetaTag, uiFeatureHighlight } from '../../lib/ui/index.js';
|
|
10
11
|
import { debugError, logError } from '../../lib/errorHandlers/index.js';
|
|
11
12
|
import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
@@ -14,7 +15,6 @@ import { makeYargsBuilder } from '../../lib/yargsUtils.js';
|
|
|
14
15
|
import { PLATFORM_VERSIONS } from '@hubspot/local-dev-lib/constants/projects';
|
|
15
16
|
import { commands } from '../../lang/en.js';
|
|
16
17
|
import { uiLogger } from '../../lib/ui/logger.js';
|
|
17
|
-
import { generateComponentPaths } from '../../lib/projects/create/v3.js';
|
|
18
18
|
import { handleProjectCreationFlow, } from '../../lib/projects/create/index.js';
|
|
19
19
|
const command = ['create', 'init'];
|
|
20
20
|
const describe = uiBetaTag(commands.project.create.describe, false);
|
|
@@ -1,7 +1,43 @@
|
|
|
1
1
|
import yargs from 'yargs';
|
|
2
2
|
import { addAccountOptions, addConfigOptions, addUseEnvironmentOptions, addTestingOptions, } from '../../../lib/commonOpts.js';
|
|
3
3
|
import sandboxCreateCommand from '../create.js';
|
|
4
|
+
import { hasFeature } from '../../../lib/hasFeature.js';
|
|
5
|
+
import * as sandboxPrompts from '../../../lib/prompts/sandboxesPrompt.js';
|
|
6
|
+
import * as accountNamePrompt from '../../../lib/prompts/accountNamePrompt.js';
|
|
7
|
+
import * as configUtils from '@hubspot/local-dev-lib/config';
|
|
8
|
+
import * as promptUtils from '../../../lib/prompts/promptUtils.js';
|
|
9
|
+
import { trackCommandUsage } from '../../../lib/usageTracking.js';
|
|
10
|
+
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
|
|
11
|
+
import * as buildAccount from '../../../lib/buildAccount.js';
|
|
12
|
+
import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
|
|
13
|
+
import { logger } from '@hubspot/local-dev-lib/logger';
|
|
14
|
+
import * as sandboxesLib from '../../../lib/sandboxes.js';
|
|
15
|
+
import * as sandboxSync from '../../../lib/sandboxSync.js';
|
|
16
|
+
import { vi } from 'vitest';
|
|
17
|
+
vi.mock('@hubspot/local-dev-lib/logger');
|
|
18
|
+
vi.mock('@hubspot/local-dev-lib/config');
|
|
4
19
|
vi.mock('../../../lib/commonOpts');
|
|
20
|
+
vi.mock('../../../lib/hasFeature');
|
|
21
|
+
vi.mock('../../../lib/prompts/sandboxesPrompt');
|
|
22
|
+
vi.mock('../../../lib/prompts/promptUtils');
|
|
23
|
+
vi.mock('../../../lib/prompts/accountNamePrompt');
|
|
24
|
+
vi.mock('../../../lib/sandboxes');
|
|
25
|
+
vi.mock('../../../lib/usageTracking');
|
|
26
|
+
vi.mock('../../../lib/buildAccount');
|
|
27
|
+
vi.mock('../../../lib/sandboxes');
|
|
28
|
+
vi.mock('../../../lib/commonOpts');
|
|
29
|
+
const getAccountConfigSpy = vi.spyOn(configUtils, 'getAccountConfig');
|
|
30
|
+
const promptUserSpy = vi.spyOn(promptUtils, 'promptUser');
|
|
31
|
+
const sandboxTypePromptSpy = vi.spyOn(sandboxPrompts, 'sandboxTypePrompt');
|
|
32
|
+
const processExitSpy = vi.spyOn(process, 'exit');
|
|
33
|
+
const buildSandboxSpy = vi.spyOn(buildAccount, 'buildSandbox');
|
|
34
|
+
const buildV2SandboxSpy = vi.spyOn(buildAccount, 'buildV2Sandbox');
|
|
35
|
+
const getAvailableSyncTypesSpy = vi.spyOn(sandboxesLib, 'getAvailableSyncTypes');
|
|
36
|
+
const syncSandboxSpy = vi.spyOn(sandboxSync, 'syncSandbox');
|
|
37
|
+
const validateSandboxUsageLimitsSpy = vi.spyOn(sandboxesLib, 'validateSandboxUsageLimits');
|
|
38
|
+
const hubspotAccountNamePromptSpy = vi.spyOn(accountNamePrompt, 'hubspotAccountNamePrompt');
|
|
39
|
+
const mockedHasFeatureV2Sandboxes = hasFeature;
|
|
40
|
+
const mockedHasFeatureV2Cli = hasFeature;
|
|
5
41
|
describe('commands/sandbox/create', () => {
|
|
6
42
|
const yargsMock = yargs;
|
|
7
43
|
describe('command', () => {
|
|
@@ -28,4 +64,175 @@ describe('commands/sandbox/create', () => {
|
|
|
28
64
|
expect(addUseEnvironmentOptions).toHaveBeenCalledWith(yargsMock);
|
|
29
65
|
});
|
|
30
66
|
});
|
|
67
|
+
describe('handler', () => {
|
|
68
|
+
let args;
|
|
69
|
+
const sandboxNameFromPrompt = 'sandbox name from prompt';
|
|
70
|
+
const mockSandbox = {
|
|
71
|
+
sandboxHubId: 56789,
|
|
72
|
+
parentHubId: 123456,
|
|
73
|
+
createdAt: '2025-01-01',
|
|
74
|
+
type: 'DEVELOPER',
|
|
75
|
+
archived: false,
|
|
76
|
+
version: 'V1',
|
|
77
|
+
status: 'PENDING',
|
|
78
|
+
name: 'Test Sandbox',
|
|
79
|
+
domain: 'test-sandbox.hubspot.com',
|
|
80
|
+
createdByUser: {
|
|
81
|
+
userId: 11111,
|
|
82
|
+
email: 'test@test.com',
|
|
83
|
+
firstName: 'Test',
|
|
84
|
+
lastName: 'User',
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
args = {
|
|
89
|
+
derivedAccountId: 1234567890,
|
|
90
|
+
};
|
|
91
|
+
getAccountConfigSpy.mockReturnValue({
|
|
92
|
+
accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD,
|
|
93
|
+
env: 'prod',
|
|
94
|
+
});
|
|
95
|
+
hubspotAccountNamePromptSpy.mockResolvedValue({
|
|
96
|
+
name: sandboxNameFromPrompt,
|
|
97
|
+
});
|
|
98
|
+
sandboxTypePromptSpy.mockResolvedValue({
|
|
99
|
+
type: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
|
|
100
|
+
});
|
|
101
|
+
promptUserSpy.mockResolvedValue({
|
|
102
|
+
contactRecordsSyncPrompt: false,
|
|
103
|
+
});
|
|
104
|
+
validateSandboxUsageLimitsSpy.mockResolvedValue(undefined);
|
|
105
|
+
mockedHasFeatureV2Sandboxes.mockResolvedValue(false);
|
|
106
|
+
mockedHasFeatureV2Cli.mockResolvedValue(false);
|
|
107
|
+
buildSandboxSpy.mockResolvedValue({
|
|
108
|
+
sandbox: mockSandbox,
|
|
109
|
+
personalAccessKey: 'mock-personal-access-key',
|
|
110
|
+
name: sandboxNameFromPrompt,
|
|
111
|
+
});
|
|
112
|
+
buildV2SandboxSpy.mockResolvedValue({
|
|
113
|
+
sandbox: { ...mockSandbox, version: 'V2' },
|
|
114
|
+
});
|
|
115
|
+
getAvailableSyncTypesSpy.mockResolvedValue([
|
|
116
|
+
{ type: 'object-schemas' },
|
|
117
|
+
{ type: 'workflows' },
|
|
118
|
+
]);
|
|
119
|
+
syncSandboxSpy.mockResolvedValue(undefined);
|
|
120
|
+
// Spy on process.exit so our tests don't close when it's called
|
|
121
|
+
// @ts-expect-error Doesn't match the actual signature because then the linter complains about unused variables
|
|
122
|
+
processExitSpy.mockImplementation(() => { });
|
|
123
|
+
});
|
|
124
|
+
it('should load the account config for the correct account id', async () => {
|
|
125
|
+
await sandboxCreateCommand.handler(args);
|
|
126
|
+
expect(getAccountConfigSpy).toHaveBeenCalledTimes(2); // 1st is for parent account, 2nd is for sandbox account
|
|
127
|
+
expect(getAccountConfigSpy).toHaveBeenCalledWith(args.derivedAccountId);
|
|
128
|
+
});
|
|
129
|
+
it('should track the command usage', async () => {
|
|
130
|
+
await sandboxCreateCommand.handler(args);
|
|
131
|
+
expect(trackCommandUsage).toHaveBeenCalledTimes(1);
|
|
132
|
+
expect(trackCommandUsage).toHaveBeenCalledWith('sandbox-create', {}, args.derivedAccountId);
|
|
133
|
+
});
|
|
134
|
+
it('should validate sandbox usage limits', async () => {
|
|
135
|
+
await sandboxCreateCommand.handler(args);
|
|
136
|
+
expect(validateSandboxUsageLimitsSpy).toHaveBeenCalledTimes(1);
|
|
137
|
+
expect(validateSandboxUsageLimitsSpy).toHaveBeenCalledWith({
|
|
138
|
+
accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD,
|
|
139
|
+
env: 'prod',
|
|
140
|
+
}, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, 'prod');
|
|
141
|
+
});
|
|
142
|
+
it('should prompt for the sandbox type if no type is provided in options', async () => {
|
|
143
|
+
await sandboxCreateCommand.handler(args);
|
|
144
|
+
expect(sandboxTypePromptSpy).toHaveBeenCalledTimes(1);
|
|
145
|
+
});
|
|
146
|
+
it('should not prompt for the sandbox type if type is provided in options', async () => {
|
|
147
|
+
await sandboxCreateCommand.handler({
|
|
148
|
+
...args,
|
|
149
|
+
type: 'developer',
|
|
150
|
+
});
|
|
151
|
+
expect(sandboxTypePromptSpy).toHaveBeenCalledTimes(0);
|
|
152
|
+
});
|
|
153
|
+
it('should not prompt for contact records sync if the sandbox type is developer', async () => {
|
|
154
|
+
await sandboxCreateCommand.handler({
|
|
155
|
+
...args,
|
|
156
|
+
type: 'developer',
|
|
157
|
+
});
|
|
158
|
+
expect(promptUserSpy).toHaveBeenCalledTimes(0);
|
|
159
|
+
});
|
|
160
|
+
it('should prompt for the contact records sync if the sandbox type is standard', async () => {
|
|
161
|
+
await sandboxCreateCommand.handler({
|
|
162
|
+
...args,
|
|
163
|
+
type: 'standard',
|
|
164
|
+
});
|
|
165
|
+
expect(promptUserSpy).toHaveBeenCalledTimes(1);
|
|
166
|
+
});
|
|
167
|
+
it('should build a v1 sandbox if the parent account is not ungated for sandboxes:v2:enabled and not ungated for sandboxes:v2:cliEnabled', async () => {
|
|
168
|
+
await sandboxCreateCommand.handler(args);
|
|
169
|
+
expect(buildSandboxSpy).toHaveBeenCalledTimes(1);
|
|
170
|
+
expect(buildSandboxSpy).toHaveBeenCalledWith(sandboxNameFromPrompt, {
|
|
171
|
+
accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD,
|
|
172
|
+
env: 'prod',
|
|
173
|
+
}, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, 'prod', undefined // force
|
|
174
|
+
);
|
|
175
|
+
expect(getAvailableSyncTypesSpy).toHaveBeenCalledTimes(1);
|
|
176
|
+
expect(syncSandboxSpy).toHaveBeenCalledTimes(1);
|
|
177
|
+
});
|
|
178
|
+
it('should build a v1 sandbox if the parent account is ungated for sandboxes:v2:enabled but not ungated for sandboxes:v2:cliEnabled', async () => {
|
|
179
|
+
mockedHasFeatureV2Sandboxes.mockResolvedValue(true);
|
|
180
|
+
mockedHasFeatureV2Cli.mockResolvedValue(false);
|
|
181
|
+
await sandboxCreateCommand.handler(args);
|
|
182
|
+
expect(buildSandboxSpy).toHaveBeenCalledTimes(1);
|
|
183
|
+
expect(buildSandboxSpy).toHaveBeenCalledWith(sandboxNameFromPrompt, {
|
|
184
|
+
accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD,
|
|
185
|
+
env: 'prod',
|
|
186
|
+
}, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, 'prod', undefined // force
|
|
187
|
+
);
|
|
188
|
+
expect(getAvailableSyncTypesSpy).toHaveBeenCalledTimes(1);
|
|
189
|
+
expect(syncSandboxSpy).toHaveBeenCalledTimes(1);
|
|
190
|
+
});
|
|
191
|
+
it('should build a v2 sandbox if the parent account is ungated for both sandboxes:v2:enabled and sandboxes:v2:cliEnabled', async () => {
|
|
192
|
+
mockedHasFeatureV2Sandboxes.mockResolvedValue(true);
|
|
193
|
+
mockedHasFeatureV2Cli.mockResolvedValue(true);
|
|
194
|
+
await sandboxCreateCommand.handler(args);
|
|
195
|
+
expect(buildV2SandboxSpy).toHaveBeenCalledTimes(1);
|
|
196
|
+
expect(buildV2SandboxSpy).toHaveBeenCalledWith(sandboxNameFromPrompt, {
|
|
197
|
+
accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD,
|
|
198
|
+
env: 'prod',
|
|
199
|
+
}, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, false, // syncObjectRecords
|
|
200
|
+
'prod', undefined // force
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
it('should log an error and exit when force is used and invalid sandbox type is provided', async () => {
|
|
204
|
+
await sandboxCreateCommand.handler({
|
|
205
|
+
...args,
|
|
206
|
+
name: sandboxNameFromPrompt,
|
|
207
|
+
type: 'invalid',
|
|
208
|
+
force: true,
|
|
209
|
+
});
|
|
210
|
+
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
211
|
+
expect(processExitSpy).toHaveBeenCalled();
|
|
212
|
+
expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
213
|
+
});
|
|
214
|
+
it('should log an error and exit when force is used and no sandbox name is provided', async () => {
|
|
215
|
+
await sandboxCreateCommand.handler({
|
|
216
|
+
...args,
|
|
217
|
+
type: 'standard',
|
|
218
|
+
force: true,
|
|
219
|
+
});
|
|
220
|
+
expect(logger.error).toHaveBeenCalled();
|
|
221
|
+
expect(processExitSpy).toHaveBeenCalled();
|
|
222
|
+
expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
223
|
+
});
|
|
224
|
+
it('should error out if the default account type is not standard', async () => {
|
|
225
|
+
getAccountConfigSpy.mockReturnValue({
|
|
226
|
+
accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
|
|
227
|
+
env: 'prod',
|
|
228
|
+
});
|
|
229
|
+
await sandboxCreateCommand.handler({
|
|
230
|
+
...args,
|
|
231
|
+
type: 'developer',
|
|
232
|
+
});
|
|
233
|
+
expect(logger.error).toHaveBeenCalled();
|
|
234
|
+
expect(processExitSpy).toHaveBeenCalled();
|
|
235
|
+
expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
31
238
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CommonArgs, ConfigArgs, AccountArgs, EnvironmentArgs, TestingArgs, YargsCommandModule } from '../../types/Yargs.js';
|
|
2
|
-
type SandboxCreateArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & TestingArgs & {
|
|
2
|
+
export type SandboxCreateArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & TestingArgs & {
|
|
3
3
|
name?: string;
|
|
4
4
|
force?: boolean;
|
|
5
5
|
type?: string;
|
|
@@ -13,9 +13,11 @@ import { sandboxTypePrompt } from '../../lib/prompts/sandboxesPrompt.js';
|
|
|
13
13
|
import { promptUser } from '../../lib/prompts/promptUtils.js';
|
|
14
14
|
import { syncSandbox } from '../../lib/sandboxSync.js';
|
|
15
15
|
import { logError } from '../../lib/errorHandlers/index.js';
|
|
16
|
-
import { buildSandbox } from '../../lib/buildAccount.js';
|
|
16
|
+
import { buildSandbox, buildV2Sandbox } from '../../lib/buildAccount.js';
|
|
17
17
|
import { hubspotAccountNamePrompt } from '../../lib/prompts/accountNamePrompt.js';
|
|
18
18
|
import { makeYargsBuilder } from '../../lib/yargsUtils.js';
|
|
19
|
+
import { hasFeature } from '../../lib/hasFeature.js';
|
|
20
|
+
import { FEATURES } from '../../lib/constants.js';
|
|
19
21
|
const command = 'create';
|
|
20
22
|
const describe = uiBetaTag(i18n(`commands.sandbox.subcommands.create.describe`), false);
|
|
21
23
|
async function handler(args) {
|
|
@@ -84,7 +86,11 @@ async function handler(args) {
|
|
|
84
86
|
process.exit(EXIT_CODES.ERROR);
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
|
-
const sandboxName = name || namePrompt.name;
|
|
89
|
+
const sandboxName = name || (namePrompt && namePrompt.name);
|
|
90
|
+
if (!sandboxName) {
|
|
91
|
+
logger.error(i18n(`commands.sandbox.subcommands.create.failure.optionMissing.name`));
|
|
92
|
+
process.exit(EXIT_CODES.ERROR);
|
|
93
|
+
}
|
|
88
94
|
let contactRecordsSyncPromptResult = false;
|
|
89
95
|
if (!force) {
|
|
90
96
|
const isStandardSandbox = sandboxType === HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX;
|
|
@@ -100,8 +106,18 @@ async function handler(args) {
|
|
|
100
106
|
contactRecordsSyncPromptResult = contactRecordsSyncPrompt;
|
|
101
107
|
}
|
|
102
108
|
}
|
|
109
|
+
// Check if parent portal is ungated for v2 sandboxes
|
|
110
|
+
const isUngatedForV2Cli = await hasFeature(derivedAccountId, FEATURES.SANDBOXES_V2_CLI);
|
|
111
|
+
const isUngatedForV2Sandboxes = await hasFeature(derivedAccountId, FEATURES.SANDBOXES_V2);
|
|
112
|
+
const canCreateV2Sandbox = isUngatedForV2Sandboxes && isUngatedForV2Cli;
|
|
103
113
|
try {
|
|
104
|
-
|
|
114
|
+
let result;
|
|
115
|
+
if (canCreateV2Sandbox) {
|
|
116
|
+
result = await buildV2Sandbox(sandboxName, accountConfig, sandboxType, contactRecordsSyncPromptResult, env, force);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
result = await buildSandbox(sandboxName, accountConfig, sandboxType, env, force);
|
|
120
|
+
}
|
|
105
121
|
const sandboxAccountConfig = getAccountConfig(result.sandbox.sandboxHubId);
|
|
106
122
|
// Check if sandbox account config exists
|
|
107
123
|
if (!sandboxAccountConfig) {
|
|
@@ -111,20 +127,19 @@ async function handler(args) {
|
|
|
111
127
|
}));
|
|
112
128
|
process.exit(EXIT_CODES.ERROR);
|
|
113
129
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
130
|
+
if (result && !canCreateV2Sandbox) {
|
|
131
|
+
// For v1 sandboxes, keep sync here. Once we migrate to v2, this will be handled by BE automatically
|
|
132
|
+
try {
|
|
133
|
+
let availableSyncTasks = await getAvailableSyncTypes(accountConfig, sandboxAccountConfig);
|
|
134
|
+
if (!contactRecordsSyncPromptResult) {
|
|
135
|
+
availableSyncTasks = availableSyncTasks.filter(t => t.type !== SYNC_TYPES.OBJECT_RECORDS);
|
|
136
|
+
}
|
|
137
|
+
await syncSandbox(sandboxAccountConfig, accountConfig, env, availableSyncTasks);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
logError(err);
|
|
141
|
+
throw err;
|
|
122
142
|
}
|
|
123
|
-
await handleSyncSandbox(availableSyncTasks);
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
logError(err);
|
|
127
|
-
throw err;
|
|
128
143
|
}
|
|
129
144
|
const highlightItems = ['accountsUseCommand', 'projectCreateCommand'];
|
|
130
145
|
if (sandboxType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX) {
|
package/lang/en.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ Global configuration replaces hubspot.config.yml, and you will be prompted to mi
|
|
|
79
79
|
readonly personalAccessKey: "Enter existing personal access key";
|
|
80
80
|
};
|
|
81
81
|
readonly errors: {
|
|
82
|
+
readonly invalidAccountIdProvided: "--account must be a number.";
|
|
82
83
|
readonly failedToUpdateConfig: "Failed to update the configuration file. Please try again.";
|
|
83
84
|
readonly migrationNotConfirmed: `Did not migrate your configuration file. Run ${string} to update your existing config, or use ${string} to switch to the new global configuration.`;
|
|
84
85
|
readonly mergeNotConfirmed: `Did not merge configuration files. When you are ready to merge the deprecated config file with the global config file, run ${string}.`;
|
|
@@ -828,9 +829,9 @@ Global configuration replaces hubspot.config.yml, and you will be prompted to mi
|
|
|
828
829
|
readonly setup: {
|
|
829
830
|
readonly installingDocSearch: "Adding the docs-search mcp server";
|
|
830
831
|
readonly claudeCode: "Claude Code";
|
|
831
|
-
readonly claudeDesktop: "Claude Desktop";
|
|
832
832
|
readonly cursor: "Cursor";
|
|
833
833
|
readonly windsurf: "Windsurf";
|
|
834
|
+
readonly vsCode: "VSCode";
|
|
834
835
|
readonly args: {
|
|
835
836
|
readonly client: "Target applications to configure";
|
|
836
837
|
readonly docsSearch: "Should the docs search mcp server be installed";
|
|
@@ -842,8 +843,6 @@ Global configuration replaces hubspot.config.yml, and you will be prompted to mi
|
|
|
842
843
|
};
|
|
843
844
|
readonly spinners: {
|
|
844
845
|
readonly failedToConfigure: "Failed to configure the HubSpot mcp server.";
|
|
845
|
-
readonly configuringClaudeDesktop: "Configuring Claude Desktop...";
|
|
846
|
-
readonly configuredClaudeDesktop: "Configured Claude Desktop";
|
|
847
846
|
readonly configuringClaudeCode: "Configuring Claude Code...";
|
|
848
847
|
readonly configuredClaudeCode: "Configured Claude Code";
|
|
849
848
|
readonly claudeCodeNotFound: "Claude Code not found - skipping configuration";
|
|
@@ -856,6 +855,10 @@ Global configuration replaces hubspot.config.yml, and you will be prompted to mi
|
|
|
856
855
|
readonly configuringWindsurf: "Configuring Windsurf...";
|
|
857
856
|
readonly failedToConfigureWindsurf: "Failed to configure Windsurf";
|
|
858
857
|
readonly configuredWindsurf: "Configured Windsurf";
|
|
858
|
+
readonly configuringVsCode: "Configuring VSCode...";
|
|
859
|
+
readonly failedToConfigureVsCode: "Failed to configure VSCode";
|
|
860
|
+
readonly configuredVsCode: "Configured VSCode";
|
|
861
|
+
readonly vsCodeNotFound: "VSCode not found - skipping configuration";
|
|
859
862
|
};
|
|
860
863
|
readonly prompts: {
|
|
861
864
|
readonly targets: "[--client] Which tools would you like to add the HubSpot CLI MCP server to?";
|
|
@@ -1386,6 +1389,7 @@ ${string}`;
|
|
|
1386
1389
|
readonly default: "Validate the project before uploading";
|
|
1387
1390
|
};
|
|
1388
1391
|
readonly success: (projectName: string) => string;
|
|
1392
|
+
readonly failure: (projectName: string) => string;
|
|
1389
1393
|
readonly options: {
|
|
1390
1394
|
readonly profile: {
|
|
1391
1395
|
readonly describe: "The profile to target for this validation";
|
package/lang/en.js
CHANGED
|
@@ -4,8 +4,7 @@ import { PERSONAL_ACCESS_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constant
|
|
|
4
4
|
import { ARCHIVED_HUBSPOT_CONFIG_YAML_FILE_NAME, GLOBAL_CONFIG_PATH, DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME, } from '@hubspot/local-dev-lib/constants/config';
|
|
5
5
|
import { uiAccountDescription, uiBetaTag, uiCommandReference, uiLink, UI_COLORS, } from '../lib/ui/index.js';
|
|
6
6
|
import { getProjectDetailUrl, getProjectSettingsUrl, getLocalDevUiUrl, } from '../lib/projects/urls.js';
|
|
7
|
-
import { PROJECT_CONFIG_FILE } from '../lib/constants.js';
|
|
8
|
-
import { PROJECT_WITH_APP } from '../lib/projects/create/v3.js';
|
|
7
|
+
import { PROJECT_CONFIG_FILE, PROJECT_WITH_APP } from '../lib/constants.js';
|
|
9
8
|
export const commands = {
|
|
10
9
|
generalErrors: {
|
|
11
10
|
srcIsProject: (src, command) => `"${src}" is in a project folder. Did you mean "hs project ${command}"?`,
|
|
@@ -80,6 +79,7 @@ export const commands = {
|
|
|
80
79
|
personalAccessKey: 'Enter existing personal access key',
|
|
81
80
|
},
|
|
82
81
|
errors: {
|
|
82
|
+
invalidAccountIdProvided: `--account must be a number.`,
|
|
83
83
|
failedToUpdateConfig: 'Failed to update the configuration file. Please try again.',
|
|
84
84
|
migrationNotConfirmed: `Did not migrate your configuration file. Run ${uiCommandReference('hs auth')} to update your existing config, or use ${uiCommandReference('hs config migrate')} to switch to the new global configuration.`,
|
|
85
85
|
mergeNotConfirmed: `Did not merge configuration files. When you are ready to merge the deprecated config file with the global config file, run ${uiCommandReference('hs config migrate')}.`,
|
|
@@ -831,9 +831,9 @@ export const commands = {
|
|
|
831
831
|
setup: {
|
|
832
832
|
installingDocSearch: 'Adding the docs-search mcp server',
|
|
833
833
|
claudeCode: 'Claude Code',
|
|
834
|
-
claudeDesktop: 'Claude Desktop',
|
|
835
834
|
cursor: 'Cursor',
|
|
836
835
|
windsurf: 'Windsurf',
|
|
836
|
+
vsCode: 'VSCode',
|
|
837
837
|
args: {
|
|
838
838
|
client: 'Target applications to configure',
|
|
839
839
|
docsSearch: 'Should the docs search mcp server be installed',
|
|
@@ -845,20 +845,26 @@ export const commands = {
|
|
|
845
845
|
},
|
|
846
846
|
spinners: {
|
|
847
847
|
failedToConfigure: 'Failed to configure the HubSpot mcp server.',
|
|
848
|
-
|
|
849
|
-
configuredClaudeDesktop: 'Configured Claude Desktop',
|
|
848
|
+
// Claude
|
|
850
849
|
configuringClaudeCode: 'Configuring Claude Code...',
|
|
851
850
|
configuredClaudeCode: 'Configured Claude Code',
|
|
852
851
|
claudeCodeNotFound: 'Claude Code not found - skipping configuration',
|
|
853
852
|
claudeCodeInstallFailed: 'Claude Code CLI not working - skipping configuration',
|
|
854
853
|
failedToConfigureClaudeDesktop: 'Failed to configure Claude Desktop',
|
|
854
|
+
// Cursor
|
|
855
855
|
configuringCursor: 'Configuring Cursor...',
|
|
856
856
|
failedToConfigureCursor: 'Failed to configure Cursor',
|
|
857
857
|
configuredCursor: 'Configured Cursor',
|
|
858
858
|
alreadyInstalled: 'HubSpot CLI mcp server already installed, reinstalling',
|
|
859
|
+
// Windsurf
|
|
859
860
|
configuringWindsurf: 'Configuring Windsurf...',
|
|
860
861
|
failedToConfigureWindsurf: 'Failed to configure Windsurf',
|
|
861
862
|
configuredWindsurf: 'Configured Windsurf',
|
|
863
|
+
// VS Code
|
|
864
|
+
configuringVsCode: 'Configuring VSCode...',
|
|
865
|
+
failedToConfigureVsCode: 'Failed to configure VSCode',
|
|
866
|
+
configuredVsCode: 'Configured VSCode',
|
|
867
|
+
vsCodeNotFound: 'VSCode not found - skipping configuration',
|
|
862
868
|
},
|
|
863
869
|
prompts: {
|
|
864
870
|
targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
|
|
@@ -1378,6 +1384,7 @@ export const commands = {
|
|
|
1378
1384
|
default: 'Validate the project before uploading',
|
|
1379
1385
|
},
|
|
1380
1386
|
success: (projectName) => `Project ${projectName} is valid and ready to upload`,
|
|
1387
|
+
failure: (projectName) => `Project ${projectName} is invalid`,
|
|
1381
1388
|
options: {
|
|
1382
1389
|
profile: {
|
|
1383
1390
|
describe: 'The profile to target for this validation',
|
package/lang/en.lyaml
CHANGED
|
@@ -1038,12 +1038,14 @@ en:
|
|
|
1038
1038
|
compressing: "Compressing build files to \"{{ path }}\""
|
|
1039
1039
|
fileFiltered: "Ignore rule triggered for \"{{ filename }}\""
|
|
1040
1040
|
ui:
|
|
1041
|
-
betaTag: "
|
|
1041
|
+
betaTag: "[BETA]"
|
|
1042
|
+
betaTagWithStyle: "{{#bold}}[BETA]{{/bold}}"
|
|
1042
1043
|
betaWarning:
|
|
1043
1044
|
header: "{{#yellow}}***************************** WARNING ****************************{{/yellow}}"
|
|
1044
1045
|
footer: "{{#yellow}}******************************************************************{{/yellow}}"
|
|
1045
1046
|
infoTag: "{{#bold}}[INFO]{{/bold}}"
|
|
1046
|
-
deprecatedTag: "
|
|
1047
|
+
deprecatedTag: "[DEPRECATED]"
|
|
1048
|
+
deprecatedTagWithStyle: "{{#bold}}[DEPRECATED]{{/bold}}"
|
|
1047
1049
|
errorTag: "{{#bold}}[ERROR]{{/bold}}"
|
|
1048
1050
|
deprecatedMessage: "The {{ command }} command is deprecated and will be disabled soon. {{ url }}"
|
|
1049
1051
|
deprecatedDescription: "{{ message }}. The {{ command }} command is deprecated and will be disabled soon. {{ url }}"
|