@hubspot/cli 7.7.22-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/config/set.d.ts +1 -0
- package/commands/config/set.js +19 -9
- 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/commands/testAccount/createConfig.js +1 -1
- package/lang/en.d.ts +17 -4
- package/lang/en.js +22 -6
- package/lang/en.lyaml +4 -2
- package/lib/__tests__/buildAccount.test.js +62 -4
- package/lib/__tests__/yargsUtils.test.js +12 -1
- package/lib/buildAccount.d.ts +4 -1
- package/lib/buildAccount.js +57 -2
- package/lib/commonOpts.js +25 -0
- package/lib/configOptions.d.ts +5 -0
- package/lib/configOptions.js +11 -1
- package/lib/constants.d.ts +8 -0
- package/lib/constants.js +8 -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/legacyAddComponent.js +1 -1
- package/lib/projects/add/v3AddComponent.js +3 -2
- package/lib/projects/create/index.js +3 -3
- package/lib/projects/create/legacy.js +2 -2
- package/lib/projects/create/v3.d.ts +0 -2
- package/lib/projects/create/v3.js +1 -3
- package/lib/projects/localDev/LocalDevLogger.js +11 -2
- package/lib/projects/localDev/LocalDevProcess.d.ts +2 -0
- package/lib/projects/localDev/LocalDevProcess.js +15 -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 +40 -30
- package/lib/projects/upload.js +5 -12
- package/lib/projects/urls.d.ts +1 -1
- package/lib/projects/urls.js +2 -2
- 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/lib/yargsUtils.d.ts +1 -0
- package/lib/yargsUtils.js +6 -0
- 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 +3 -3
- 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/config/set.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ type ConfigSetArgs = CommonArgs & ConfigArgs & {
|
|
|
5
5
|
allowUsageTracking?: boolean;
|
|
6
6
|
httpTimeout?: string;
|
|
7
7
|
allowAutoUpdates?: boolean;
|
|
8
|
+
autoOpenBrowser?: boolean;
|
|
8
9
|
};
|
|
9
10
|
declare const configSetCommand: YargsCommandModule<unknown, ConfigSetArgs>;
|
|
10
11
|
export default configSetCommand;
|
package/commands/config/set.js
CHANGED
|
@@ -2,9 +2,9 @@ import { i18n } from '../../lib/lang.js';
|
|
|
2
2
|
import { trackCommandUsage } from '../../lib/usageTracking.js';
|
|
3
3
|
import { promptUser } from '../../lib/prompts/promptUtils.js';
|
|
4
4
|
import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
5
|
-
import { setDefaultCmsPublishMode, setHttpTimeout, setAllowUsageTracking, setAllowAutoUpdates, } from '../../lib/configOptions.js';
|
|
5
|
+
import { setDefaultCmsPublishMode, setHttpTimeout, setAllowUsageTracking, setAllowAutoUpdates, setAutoOpenBrowser, } from '../../lib/configOptions.js';
|
|
6
6
|
import { commands } from '../../lang/en.js';
|
|
7
|
-
import { makeYargsBuilder } from '../../lib/yargsUtils.js';
|
|
7
|
+
import { makeYargsBuilder, getExclusiveConflicts, } from '../../lib/yargsUtils.js';
|
|
8
8
|
const command = 'set';
|
|
9
9
|
const describe = commands.config.subcommands.set.describe;
|
|
10
10
|
async function selectOptions() {
|
|
@@ -28,7 +28,7 @@ async function selectOptions() {
|
|
|
28
28
|
return configOption;
|
|
29
29
|
}
|
|
30
30
|
async function handleConfigUpdate(accountId, args) {
|
|
31
|
-
const { allowAutoUpdates, allowUsageTracking, defaultCmsPublishMode, httpTimeout, } = args;
|
|
31
|
+
const { allowAutoUpdates, allowUsageTracking, defaultCmsPublishMode, httpTimeout, autoOpenBrowser, } = args;
|
|
32
32
|
if (typeof defaultCmsPublishMode !== 'undefined') {
|
|
33
33
|
await setDefaultCmsPublishMode({ defaultCmsPublishMode, accountId });
|
|
34
34
|
return true;
|
|
@@ -45,6 +45,10 @@ async function handleConfigUpdate(accountId, args) {
|
|
|
45
45
|
await setAllowAutoUpdates({ allowAutoUpdates, accountId });
|
|
46
46
|
return true;
|
|
47
47
|
}
|
|
48
|
+
else if (typeof autoOpenBrowser !== 'undefined') {
|
|
49
|
+
await setAutoOpenBrowser({ autoOpenBrowser, accountId });
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
48
52
|
return false;
|
|
49
53
|
}
|
|
50
54
|
async function handler(args) {
|
|
@@ -77,13 +81,19 @@ function configSetBuilder(yargs) {
|
|
|
77
81
|
type: 'boolean',
|
|
78
82
|
hidden: true,
|
|
79
83
|
},
|
|
84
|
+
'auto-open-browser': {
|
|
85
|
+
describe: commands.config.subcommands.set.options.autoOpenBrowser.describe,
|
|
86
|
+
type: 'boolean',
|
|
87
|
+
hidden: true,
|
|
88
|
+
},
|
|
80
89
|
})
|
|
81
|
-
.conflicts(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
.conflicts(getExclusiveConflicts([
|
|
91
|
+
'default-cms-publish-mode',
|
|
92
|
+
'allow-usage-tracking',
|
|
93
|
+
'http-timeout',
|
|
94
|
+
'allow-auto-updates',
|
|
95
|
+
'auto-open-browser',
|
|
96
|
+
]))
|
|
87
97
|
.example([
|
|
88
98
|
[
|
|
89
99
|
'$0 config set',
|
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) {
|
|
@@ -7,7 +7,7 @@ import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
|
7
7
|
import { uiLogger } from '../../lib/ui/logger.js';
|
|
8
8
|
import { trackCommandUsage } from '../../lib/usageTracking.js';
|
|
9
9
|
import { commands } from '../../lang/en.js';
|
|
10
|
-
import { createDeveloperTestAccountConfigPrompt
|
|
10
|
+
import { createDeveloperTestAccountConfigPrompt } from '../../lib/prompts/createDeveloperTestAccountConfigPrompt.js';
|
|
11
11
|
import { fileExists } from '../../lib/validation.js';
|
|
12
12
|
const command = 'create-config';
|
|
13
13
|
const describe = commands.testAccount.createConfig.describe;
|
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}.`;
|
|
@@ -252,6 +253,9 @@ Global configuration replaces hubspot.config.yml, and you will be prompted to mi
|
|
|
252
253
|
readonly allowAutoUpdates: {
|
|
253
254
|
readonly describe: "Enable or disable auto updates";
|
|
254
255
|
};
|
|
256
|
+
readonly autoOpenBrowser: {
|
|
257
|
+
readonly describe: "Enable or disable automatic opening of the browser";
|
|
258
|
+
};
|
|
255
259
|
};
|
|
256
260
|
};
|
|
257
261
|
};
|
|
@@ -825,9 +829,9 @@ Global configuration replaces hubspot.config.yml, and you will be prompted to mi
|
|
|
825
829
|
readonly setup: {
|
|
826
830
|
readonly installingDocSearch: "Adding the docs-search mcp server";
|
|
827
831
|
readonly claudeCode: "Claude Code";
|
|
828
|
-
readonly claudeDesktop: "Claude Desktop";
|
|
829
832
|
readonly cursor: "Cursor";
|
|
830
833
|
readonly windsurf: "Windsurf";
|
|
834
|
+
readonly vsCode: "VSCode";
|
|
831
835
|
readonly args: {
|
|
832
836
|
readonly client: "Target applications to configure";
|
|
833
837
|
readonly docsSearch: "Should the docs search mcp server be installed";
|
|
@@ -839,8 +843,6 @@ Global configuration replaces hubspot.config.yml, and you will be prompted to mi
|
|
|
839
843
|
};
|
|
840
844
|
readonly spinners: {
|
|
841
845
|
readonly failedToConfigure: "Failed to configure the HubSpot mcp server.";
|
|
842
|
-
readonly configuringClaudeDesktop: "Configuring Claude Desktop...";
|
|
843
|
-
readonly configuredClaudeDesktop: "Configured Claude Desktop";
|
|
844
846
|
readonly configuringClaudeCode: "Configuring Claude Code...";
|
|
845
847
|
readonly configuredClaudeCode: "Configured Claude Code";
|
|
846
848
|
readonly claudeCodeNotFound: "Claude Code not found - skipping configuration";
|
|
@@ -853,6 +855,10 @@ Global configuration replaces hubspot.config.yml, and you will be prompted to mi
|
|
|
853
855
|
readonly configuringWindsurf: "Configuring Windsurf...";
|
|
854
856
|
readonly failedToConfigureWindsurf: "Failed to configure Windsurf";
|
|
855
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";
|
|
856
862
|
};
|
|
857
863
|
readonly prompts: {
|
|
858
864
|
readonly targets: "[--client] Which tools would you like to add the HubSpot CLI MCP server to?";
|
|
@@ -1383,6 +1389,7 @@ ${string}`;
|
|
|
1383
1389
|
readonly default: "Validate the project before uploading";
|
|
1384
1390
|
};
|
|
1385
1391
|
readonly success: (projectName: string) => string;
|
|
1392
|
+
readonly failure: (projectName: string) => string;
|
|
1386
1393
|
readonly options: {
|
|
1387
1394
|
readonly profile: {
|
|
1388
1395
|
readonly describe: "The profile to target for this validation";
|
|
@@ -2459,7 +2466,8 @@ export declare const lib: {
|
|
|
2459
2466
|
readonly running: (projectName: string, accountIdentifier: string) => string;
|
|
2460
2467
|
readonly quitHelper: `Press ${string} to stop the local dev server`;
|
|
2461
2468
|
readonly viewProjectLink: (name: string, accountId: number) => string;
|
|
2462
|
-
readonly viewLocalDevUILink: (accountId: number) => string;
|
|
2469
|
+
readonly viewLocalDevUILink: (accountId: number, showWelcomeScreen: boolean) => string;
|
|
2470
|
+
readonly localDevUIAutoMessage: (accountId: number, showWelcomeScreen: boolean) => string;
|
|
2463
2471
|
readonly viewTestAccountLink: "View developer test account in HubSpot";
|
|
2464
2472
|
readonly exitingStart: "Stopping local dev server ...";
|
|
2465
2473
|
readonly exitingSucceed: "Successfully exited";
|
|
@@ -2791,6 +2799,11 @@ Run ${string} to upgrade to version ${string}`;
|
|
|
2791
2799
|
readonly promptMessage: "Enter http timeout duration";
|
|
2792
2800
|
readonly success: (timeout: string) => string;
|
|
2793
2801
|
};
|
|
2802
|
+
readonly setAutoOpenBrowser: {
|
|
2803
|
+
readonly fieldName: "auto open browser";
|
|
2804
|
+
readonly enabled: "Auto opening your browser has been enabled";
|
|
2805
|
+
readonly disabled: "Auto opening your browser has been disabled";
|
|
2806
|
+
};
|
|
2794
2807
|
};
|
|
2795
2808
|
readonly commonOpts: {
|
|
2796
2809
|
readonly options: {
|