@hubspot/cli 8.0.1-experimental.0 → 8.0.2-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/bin/cli.js +8 -5
- package/commands/__tests__/getStarted.test.js +12 -0
- package/commands/getStarted.d.ts +2 -1
- package/commands/getStarted.js +38 -15
- package/commands/mcp/__tests__/start.test.js +1 -67
- package/commands/mcp/start.js +1 -19
- package/lang/en.d.ts +31 -6
- package/lang/en.js +33 -8
- package/lib/CLIWebSocketServer.d.ts +28 -0
- package/lib/CLIWebSocketServer.js +91 -0
- package/lib/__tests__/CLIWebSocketServer.test.d.ts +1 -0
- package/lib/__tests__/CLIWebSocketServer.test.js +252 -0
- package/lib/__tests__/commandSuggestion.test.d.ts +1 -0
- package/lib/__tests__/commandSuggestion.test.js +119 -0
- package/lib/commandSuggestion.d.ts +3 -0
- package/lib/commandSuggestion.js +45 -0
- package/lib/constants.d.ts +0 -1
- package/lib/constants.js +0 -1
- package/lib/getStarted/getStartedV2.d.ts +7 -0
- package/lib/getStarted/getStartedV2.js +56 -0
- package/lib/getStartedV2Actions.d.ts +8 -0
- package/lib/getStartedV2Actions.js +51 -0
- package/lib/mcp/__tests__/setup.test.js +0 -17
- package/lib/mcp/setup.d.ts +0 -1
- package/lib/mcp/setup.js +59 -103
- package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +43 -175
- package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +2 -7
- package/lib/projects/localDev/LocalDevWebsocketServer.js +51 -98
- package/lib/projects/localDev/localDevWebsocketServerUtils.d.ts +8 -7
- package/lib/projects/platformVersion.js +1 -1
- package/mcp-server/utils/__tests__/project.test.js +0 -125
- package/mcp-server/utils/project.js +0 -8
- package/package.json +9 -4
- package/types/LocalDev.d.ts +0 -4
- package/ui/components/ActionSection.d.ts +12 -0
- package/ui/components/ActionSection.js +25 -0
- package/ui/components/BoxWithTitle.d.ts +4 -2
- package/ui/components/BoxWithTitle.js +2 -2
- package/ui/components/FullScreen.d.ts +6 -0
- package/ui/components/FullScreen.js +13 -0
- package/ui/components/GetStartedFlow.d.ts +24 -0
- package/ui/components/GetStartedFlow.js +128 -0
- package/ui/components/InputField.d.ts +10 -0
- package/ui/components/InputField.js +10 -0
- package/ui/components/SelectInput.d.ts +11 -0
- package/ui/components/SelectInput.js +59 -0
- package/ui/components/StatusIcon.d.ts +9 -0
- package/ui/components/StatusIcon.js +17 -0
- package/ui/constants.d.ts +6 -0
- package/ui/constants.js +6 -0
- package/ui/playground/fixtures.js +47 -0
- package/ui/render.d.ts +4 -0
- package/ui/render.js +25 -0
- package/ui/styles.d.ts +3 -0
- package/ui/styles.js +3 -0
package/bin/cli.js
CHANGED
|
@@ -33,6 +33,7 @@ import mcpCommand from '../commands/mcp.js';
|
|
|
33
33
|
import upgradeCommand from '../commands/upgrade.js';
|
|
34
34
|
import { uiLogger } from '../lib/ui/logger.js';
|
|
35
35
|
import { initializeSpinniesManager } from '../lib/middleware/spinniesMiddleware.js';
|
|
36
|
+
import { addCommandSuggestions } from '../lib/commandSuggestion.js';
|
|
36
37
|
function getTerminalWidth() {
|
|
37
38
|
const width = yargs().terminalWidth();
|
|
38
39
|
if (width >= 100)
|
|
@@ -114,20 +115,22 @@ const argv = yargs(process.argv.slice(2))
|
|
|
114
115
|
.command(completionCommand)
|
|
115
116
|
.command(doctorCommand)
|
|
116
117
|
.command(mcpCommand)
|
|
117
|
-
.command(upgradeCommand)
|
|
118
|
+
.command(upgradeCommand);
|
|
119
|
+
const argvWithSuggestions = addCommandSuggestions(argv)
|
|
118
120
|
.help()
|
|
119
121
|
.alias('h', 'help')
|
|
120
122
|
.recommendCommands()
|
|
121
123
|
.demandCommand(1, '')
|
|
122
124
|
.wrap(getTerminalWidth())
|
|
123
125
|
.strict().argv;
|
|
124
|
-
if ('help' in
|
|
126
|
+
if ('help' in argvWithSuggestions && argvWithSuggestions.help !== undefined) {
|
|
125
127
|
(async () => {
|
|
126
|
-
await trackHelpUsage(getCommandName(
|
|
128
|
+
await trackHelpUsage(getCommandName(argvWithSuggestions));
|
|
127
129
|
})();
|
|
128
130
|
}
|
|
129
|
-
if ('convertFields' in
|
|
131
|
+
if ('convertFields' in argvWithSuggestions &&
|
|
132
|
+
argvWithSuggestions.convertFields !== undefined) {
|
|
130
133
|
(async () => {
|
|
131
|
-
await trackConvertFieldsUsage(getCommandName(
|
|
134
|
+
await trackConvertFieldsUsage(getCommandName(argvWithSuggestions));
|
|
132
135
|
})();
|
|
133
136
|
}
|
|
@@ -9,6 +9,8 @@ import { getProjectPackageJsonLocations, installPackages, } from '../../lib/depe
|
|
|
9
9
|
import { GET_STARTED_OPTIONS } from '../../lib/constants.js';
|
|
10
10
|
import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
11
11
|
import open from 'open';
|
|
12
|
+
import { renderInteractive } from '../../ui/render.js';
|
|
13
|
+
import { getGetStartedFlow } from '../../ui/components/GetStartedFlow.js';
|
|
12
14
|
vi.mock('../../lib/prompts/promptUtils');
|
|
13
15
|
vi.mock('../../lib/prompts/projectNameAndDestPrompt');
|
|
14
16
|
vi.mock('../../lib/projects/config');
|
|
@@ -16,6 +18,8 @@ vi.mock('../../lib/errorHandlers');
|
|
|
16
18
|
vi.mock('@hubspot/local-dev-lib/github');
|
|
17
19
|
vi.mock('../../lib/dependencyManagement');
|
|
18
20
|
vi.mock('@hubspot/local-dev-lib/config');
|
|
21
|
+
vi.mock('../../ui/render');
|
|
22
|
+
vi.mock('../../ui/components/GetStartedFlow');
|
|
19
23
|
vi.mock('open');
|
|
20
24
|
vi.mock('fs-extra', () => ({
|
|
21
25
|
default: {
|
|
@@ -103,6 +107,14 @@ describe('commands/get-started', () => {
|
|
|
103
107
|
await getStartedCommand.handler(mockArgs);
|
|
104
108
|
expect(open).toHaveBeenCalledWith(expect.stringContaining('design-manager'), { url: true });
|
|
105
109
|
});
|
|
110
|
+
it('should use Ink flow when v2 flag is enabled', async () => {
|
|
111
|
+
getGetStartedFlow.mockImplementation(() => null);
|
|
112
|
+
renderInteractive.mockResolvedValue(undefined);
|
|
113
|
+
await getStartedCommand.handler({ ...mockArgs, v2: true });
|
|
114
|
+
expect(renderInteractive).toHaveBeenCalled();
|
|
115
|
+
expect(promptUser).not.toHaveBeenCalled();
|
|
116
|
+
expect(process.exit).toHaveBeenCalledWith(EXIT_CODES.SUCCESS);
|
|
117
|
+
});
|
|
106
118
|
});
|
|
107
119
|
describe('App flow', () => {
|
|
108
120
|
beforeEach(() => {
|
package/commands/getStarted.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { AccountArgs,
|
|
1
|
+
import { AccountArgs, CommonArgs, ConfigArgs, EnvironmentArgs, YargsCommandModule } from '../types/Yargs.js';
|
|
2
2
|
type GetStartedArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & {
|
|
3
3
|
name?: string;
|
|
4
4
|
dest?: string;
|
|
5
|
+
v2?: boolean;
|
|
5
6
|
};
|
|
6
7
|
declare const getStartedCommand: YargsCommandModule<unknown, GetStartedArgs>;
|
|
7
8
|
export default getStartedCommand;
|
package/commands/getStarted.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
+
import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
|
|
2
|
+
import { getCwd } from '@hubspot/local-dev-lib/path';
|
|
1
3
|
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
4
|
import open from 'open';
|
|
4
|
-
import
|
|
5
|
-
import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
|
|
5
|
+
import path from 'path';
|
|
6
6
|
import { commands } from '../lang/en.js';
|
|
7
|
-
import {
|
|
7
|
+
import { GET_STARTED_OPTIONS, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, PROJECT_CONFIG_FILE, } from '../lib/constants.js';
|
|
8
8
|
import { EXIT_CODES } from '../lib/enums/exitCodes.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { debugError, logError } from '../lib/errorHandlers/index.js';
|
|
10
|
+
import { getProjectConfig, validateProjectConfig, writeProjectConfig, } from '../lib/projects/config.js';
|
|
11
|
+
import { handleProjectUpload } from '../lib/projects/upload.js';
|
|
11
12
|
import { projectNameAndDestPrompt } from '../lib/prompts/projectNameAndDestPrompt.js';
|
|
13
|
+
import { promptUser } from '../lib/prompts/promptUtils.js';
|
|
12
14
|
import { uiAccountDescription, uiFeatureHighlight, uiInfoSection, } from '../lib/ui/index.js';
|
|
13
15
|
import { uiLogger } from '../lib/ui/logger.js';
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
import { handleProjectUpload } from '../lib/projects/upload.js';
|
|
17
|
-
import { PROJECT_CONFIG_FILE, GET_STARTED_OPTIONS, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, } from '../lib/constants.js';
|
|
18
|
-
import { writeProjectConfig, getProjectConfig, validateProjectConfig, } from '../lib/projects/config.js';
|
|
19
|
-
import { pollProjectBuildAndDeploy } from '../lib/projects/pollProjectBuildAndDeploy.js';
|
|
16
|
+
import { trackCommandMetadataUsage, trackCommandUsage, } from '../lib/usageTracking.js';
|
|
17
|
+
import { makeYargsBuilder } from '../lib/yargsUtils.js';
|
|
20
18
|
import { isV2Project } from '../lib/projects/platformVersion.js';
|
|
21
|
-
import {
|
|
22
|
-
import { getStaticAuthAppInstallUrl } from '../lib/app/urls.js';
|
|
23
|
-
import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
|
|
19
|
+
import { pollProjectBuildAndDeploy } from '../lib/projects/pollProjectBuildAndDeploy.js';
|
|
24
20
|
import { fetchPublicAppsForPortal } from '@hubspot/local-dev-lib/api/appsDev';
|
|
21
|
+
import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
|
|
22
|
+
import { getStaticAuthAppInstallUrl } from '../lib/app/urls.js';
|
|
23
|
+
import ProjectValidationError from '../lib/errors/ProjectValidationError.js';
|
|
24
|
+
import { openLink } from '../lib/links.js';
|
|
25
|
+
import { runGetStartedV2 } from '../lib/getStarted/getStartedV2.js';
|
|
25
26
|
const command = 'get-started';
|
|
26
27
|
const describe = commands.getStarted.describe;
|
|
27
28
|
async function handler(args) {
|
|
@@ -29,6 +30,22 @@ async function handler(args) {
|
|
|
29
30
|
const env = getConfigAccountEnvironment(derivedAccountId);
|
|
30
31
|
await trackCommandUsage('get-started', {}, derivedAccountId);
|
|
31
32
|
const accountName = uiAccountDescription(derivedAccountId);
|
|
33
|
+
if (args.v2) {
|
|
34
|
+
try {
|
|
35
|
+
await runGetStartedV2({
|
|
36
|
+
...args,
|
|
37
|
+
name: args.name,
|
|
38
|
+
dest: args.dest,
|
|
39
|
+
});
|
|
40
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
logError(error);
|
|
45
|
+
process.exit(EXIT_CODES.ERROR);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
32
49
|
uiInfoSection(commands.getStarted.startTitle, () => {
|
|
33
50
|
uiLogger.log(commands.getStarted.startDescription);
|
|
34
51
|
uiLogger.log(commands.getStarted.guideOverview(accountName));
|
|
@@ -277,6 +294,12 @@ function getStartedBuilder(yargs) {
|
|
|
277
294
|
describe: commands.getStarted.options.dest.describe,
|
|
278
295
|
type: 'string',
|
|
279
296
|
},
|
|
297
|
+
v2: {
|
|
298
|
+
describe: commands.getStarted.options.v2.describe,
|
|
299
|
+
type: 'boolean',
|
|
300
|
+
default: false,
|
|
301
|
+
hidden: true,
|
|
302
|
+
},
|
|
280
303
|
'template-source': {
|
|
281
304
|
describe: commands.getStarted.options.templateSource.describe,
|
|
282
305
|
type: 'string',
|
|
@@ -7,20 +7,14 @@ import * as errorHandlers from '../../../lib/errorHandlers/index.js';
|
|
|
7
7
|
import * as usageTrackingLib from '../../../lib/usageTracking.js';
|
|
8
8
|
import * as processLib from '../../../lib/process.js';
|
|
9
9
|
import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
|
|
10
|
-
|
|
11
|
-
const execAsyncMock = vi.fn();
|
|
10
|
+
import startCommand from '../start.js';
|
|
12
11
|
vi.mock('yargs');
|
|
13
12
|
vi.mock('../../../lib/commonOpts');
|
|
14
13
|
vi.mock('node:child_process');
|
|
15
|
-
vi.mock('node:util', () => ({
|
|
16
|
-
promisify: vi.fn(() => execAsyncMock),
|
|
17
|
-
}));
|
|
18
14
|
vi.mock('fs');
|
|
19
15
|
vi.mock('@hubspot/local-dev-lib/config');
|
|
20
16
|
vi.mock('../../../lib/errorHandlers/index.js');
|
|
21
17
|
vi.mock('../../../lib/process.js');
|
|
22
|
-
// Import after mocks are set up
|
|
23
|
-
const startCommand = await import('../start.js').then(m => m.default);
|
|
24
18
|
const spawnSpy = vi.mocked(spawn);
|
|
25
19
|
const existsSyncSpy = vi.spyOn(fs, 'existsSync');
|
|
26
20
|
const trackCommandUsageSpy = vi.spyOn(usageTrackingLib, 'trackCommandUsage');
|
|
@@ -42,7 +36,6 @@ describe('commands/mcp/start', () => {
|
|
|
42
36
|
processExitSpy.mockImplementation(() => { });
|
|
43
37
|
// Mock config to prevent reading actual config file in CI
|
|
44
38
|
getConfigAccountIfExistsSpy.mockReturnValue(undefined);
|
|
45
|
-
execAsyncMock.mockClear();
|
|
46
39
|
});
|
|
47
40
|
describe('command', () => {
|
|
48
41
|
it('should have the correct command structure', () => {
|
|
@@ -140,64 +133,5 @@ describe('commands/mcp/start', () => {
|
|
|
140
133
|
expect(uiLogger.error).toHaveBeenCalledWith(expect.stringContaining('Failed to start'));
|
|
141
134
|
expect(logErrorSpy).toHaveBeenCalledWith(error);
|
|
142
135
|
});
|
|
143
|
-
it('should fetch CLI version in standalone mode', async () => {
|
|
144
|
-
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
145
|
-
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
146
|
-
execAsyncMock.mockResolvedValue({
|
|
147
|
-
stdout: '8.0.0',
|
|
148
|
-
stderr: '',
|
|
149
|
-
});
|
|
150
|
-
await startCommand.handler(args);
|
|
151
|
-
expect(execAsyncMock).toHaveBeenCalledWith('npm view @hubspot/cli version');
|
|
152
|
-
expect(spawnSpy).toHaveBeenCalledWith('node', expect.any(Array), expect.objectContaining({
|
|
153
|
-
env: expect.objectContaining({
|
|
154
|
-
HUBSPOT_CLI_VERSION: '8.0.0',
|
|
155
|
-
}),
|
|
156
|
-
}));
|
|
157
|
-
// Restore original env
|
|
158
|
-
if (originalEnv === undefined) {
|
|
159
|
-
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
it('should not fetch CLI version when not in standalone mode', async () => {
|
|
166
|
-
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
167
|
-
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
168
|
-
await startCommand.handler(args);
|
|
169
|
-
expect(execAsyncMock).not.toHaveBeenCalled();
|
|
170
|
-
expect(spawnSpy).toHaveBeenCalledWith('node', expect.any(Array), expect.objectContaining({
|
|
171
|
-
env: expect.not.objectContaining({
|
|
172
|
-
HUBSPOT_CLI_VERSION: expect.anything(),
|
|
173
|
-
}),
|
|
174
|
-
}));
|
|
175
|
-
// Restore original env
|
|
176
|
-
if (originalEnv !== undefined) {
|
|
177
|
-
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
it('should handle version fetch errors gracefully', async () => {
|
|
181
|
-
const originalEnv = process.env.HUBSPOT_MCP_STANDALONE;
|
|
182
|
-
process.env.HUBSPOT_MCP_STANDALONE = 'true';
|
|
183
|
-
const error = new Error('Version fetch failed');
|
|
184
|
-
execAsyncMock.mockRejectedValue(error);
|
|
185
|
-
await startCommand.handler(args);
|
|
186
|
-
expect(execAsyncMock).toHaveBeenCalledWith('npm view @hubspot/cli version');
|
|
187
|
-
expect(uiLogger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to get CLI version'));
|
|
188
|
-
expect(logErrorSpy).toHaveBeenCalledWith(error);
|
|
189
|
-
expect(spawnSpy).toHaveBeenCalledWith('node', expect.any(Array), expect.objectContaining({
|
|
190
|
-
env: expect.not.objectContaining({
|
|
191
|
-
HUBSPOT_CLI_VERSION: expect.anything(),
|
|
192
|
-
}),
|
|
193
|
-
}));
|
|
194
|
-
// Restore original env
|
|
195
|
-
if (originalEnv === undefined) {
|
|
196
|
-
delete process.env.HUBSPOT_MCP_STANDALONE;
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
process.env.HUBSPOT_MCP_STANDALONE = originalEnv;
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
136
|
});
|
|
203
137
|
});
|
package/commands/mcp/start.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { spawn
|
|
2
|
-
import { promisify } from 'node:util';
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
import fs from 'fs';
|
|
5
4
|
import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
@@ -10,7 +9,6 @@ import { commands } from '../../lang/en.js';
|
|
|
10
9
|
import { handleExit } from '../../lib/process.js';
|
|
11
10
|
import { trackCommandUsage } from '../../lib/usageTracking.js';
|
|
12
11
|
import { fileURLToPath } from 'url';
|
|
13
|
-
const execAsync = promisify(exec);
|
|
14
12
|
const command = 'start';
|
|
15
13
|
const describe = undefined; // Leave hidden for now
|
|
16
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -30,28 +28,12 @@ async function startMcpServer(aiAgent) {
|
|
|
30
28
|
uiLogger.debug(commands.mcp.start.startingServer);
|
|
31
29
|
uiLogger.debug(commands.mcp.start.stopInstructions);
|
|
32
30
|
const args = [serverPath];
|
|
33
|
-
// Get CLI version if running in standalone mode
|
|
34
|
-
let cliVersion;
|
|
35
|
-
if (process.env.HUBSPOT_MCP_STANDALONE === 'true') {
|
|
36
|
-
try {
|
|
37
|
-
// const { stdout } = await execAsync('npm view @hubspot/cli version');
|
|
38
|
-
// // Extract version number from output (e.g., "8.0.0")
|
|
39
|
-
// cliVersion = stdout.trim();
|
|
40
|
-
cliVersion = '8.0.1-experimental.0';
|
|
41
|
-
uiLogger.debug(`Using @hubspot/cli version: ${cliVersion}`);
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
uiLogger.warn('Failed to get CLI version, will use latest');
|
|
45
|
-
logError(error);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
31
|
// Start the server using ts-node
|
|
49
32
|
const child = spawn(`node`, args, {
|
|
50
33
|
stdio: 'inherit',
|
|
51
34
|
env: {
|
|
52
35
|
...process.env,
|
|
53
36
|
HUBSPOT_MCP_AI_AGENT: aiAgent || 'unknown',
|
|
54
|
-
...(cliVersion && { HUBSPOT_CLI_VERSION: cliVersion }),
|
|
55
37
|
},
|
|
56
38
|
});
|
|
57
39
|
// Handle server process events
|
package/lang/en.d.ts
CHANGED
|
@@ -16,10 +16,34 @@ export declare const commands: {
|
|
|
16
16
|
name: {
|
|
17
17
|
describe: string;
|
|
18
18
|
};
|
|
19
|
+
v2: {
|
|
20
|
+
describe: string;
|
|
21
|
+
};
|
|
19
22
|
templateSource: {
|
|
20
23
|
describe: string;
|
|
21
24
|
};
|
|
22
25
|
};
|
|
26
|
+
v2: {
|
|
27
|
+
unknownError: string;
|
|
28
|
+
startTitle: string;
|
|
29
|
+
guideOverview: (accountName: string) => string;
|
|
30
|
+
projects: string;
|
|
31
|
+
runningProjectCreate: string;
|
|
32
|
+
templateSourceFlag: string;
|
|
33
|
+
runningInstallDeps: string;
|
|
34
|
+
installingDependenciesIn: (installPath: string) => string;
|
|
35
|
+
createdProjectSuccess: (projectName: string, projectDest: string) => string;
|
|
36
|
+
pressEnterToContinueDeploy: (accountName: string) => string;
|
|
37
|
+
prompts: {
|
|
38
|
+
selectOptionV2: string;
|
|
39
|
+
options: {
|
|
40
|
+
app: string;
|
|
41
|
+
cms: string;
|
|
42
|
+
cmsTheme: string;
|
|
43
|
+
cmsReactModule: string;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
};
|
|
23
47
|
startTitle: string;
|
|
24
48
|
verboseDescribe: string;
|
|
25
49
|
startDescription: string;
|
|
@@ -33,9 +57,12 @@ export declare const commands: {
|
|
|
33
57
|
openedDeveloperOverview: string;
|
|
34
58
|
prompts: {
|
|
35
59
|
selectOption: string;
|
|
60
|
+
selectOptionV2: string;
|
|
36
61
|
options: {
|
|
37
62
|
app: string;
|
|
38
63
|
cms: string;
|
|
64
|
+
cmsTheme: string;
|
|
65
|
+
cmsReactModule: string;
|
|
39
66
|
};
|
|
40
67
|
uploadProject: (accountName: string) => string;
|
|
41
68
|
projectCreated: {
|
|
@@ -1246,7 +1273,6 @@ export declare const commands: {
|
|
|
1246
1273
|
prompts: {
|
|
1247
1274
|
targets: string;
|
|
1248
1275
|
targetsRequired: string;
|
|
1249
|
-
standaloneMode: string;
|
|
1250
1276
|
};
|
|
1251
1277
|
};
|
|
1252
1278
|
start: {
|
|
@@ -2919,14 +2945,13 @@ export declare const lib: {
|
|
|
2919
2945
|
appDataNotFound: string;
|
|
2920
2946
|
oauthAppRedirectUrlError: (redirectUrl: string) => string;
|
|
2921
2947
|
};
|
|
2922
|
-
|
|
2948
|
+
CLIWebsocketServer: {
|
|
2923
2949
|
errors: {
|
|
2924
|
-
|
|
2950
|
+
portManagerNotRunning: (prefix?: string) => string;
|
|
2951
|
+
originNotAllowed: (origin?: string) => string;
|
|
2925
2952
|
missingTypeField: (data: string) => string;
|
|
2926
|
-
unknownMessageType: (type: string) => string;
|
|
2927
2953
|
invalidJSON: (data: string) => string;
|
|
2928
|
-
|
|
2929
|
-
originNotAllowed: (origin?: string) => string;
|
|
2954
|
+
unknownMessageType: (type: string) => string;
|
|
2930
2955
|
};
|
|
2931
2956
|
logs: {
|
|
2932
2957
|
startup: (port: number) => string;
|
package/lang/en.js
CHANGED
|
@@ -24,10 +24,34 @@ export const commands = {
|
|
|
24
24
|
name: {
|
|
25
25
|
describe: 'Project name (cannot be changed)',
|
|
26
26
|
},
|
|
27
|
+
v2: {
|
|
28
|
+
describe: 'Use the new v2 get-started experience',
|
|
29
|
+
},
|
|
27
30
|
templateSource: {
|
|
28
31
|
describe: 'Path to custom GitHub repository from which to create project template',
|
|
29
32
|
},
|
|
30
33
|
},
|
|
34
|
+
v2: {
|
|
35
|
+
unknownError: 'An unknown error occurred',
|
|
36
|
+
startTitle: 'Welcome to the HubSpot Developer Platform!',
|
|
37
|
+
guideOverview: (accountName) => `This guide will walk you through deploying your first project to ${chalk.bold(accountName)}. To target a different account, exit this guide ${chalk.bold('(ctrl + c)')} and run ${uiCommandReference('hs account use')} before trying again.`,
|
|
38
|
+
projects: 'Projects are used to build a variety of components.',
|
|
39
|
+
runningProjectCreate: `Running \`${uiCommandReference('hs project create', false)}\` ...`,
|
|
40
|
+
templateSourceFlag: '--template-source=',
|
|
41
|
+
runningInstallDeps: `Running \`${uiCommandReference('hs project install-deps', false)}\` to initialize dependencies ...`,
|
|
42
|
+
installingDependenciesIn: (installPath) => `Installing dependencies in ${installPath}`,
|
|
43
|
+
createdProjectSuccess: (projectName, projectDest) => `[SUCCESS] Project ${chalk.bold(projectName)} was successfully created in ${projectDest}`,
|
|
44
|
+
pressEnterToContinueDeploy: (accountName) => `Press ${chalk.bold('<enter>')} to deploy this project to ${chalk.bold(accountName)} ...`,
|
|
45
|
+
prompts: {
|
|
46
|
+
selectOptionV2: 'Choose a component type to get started',
|
|
47
|
+
options: {
|
|
48
|
+
app: 'App',
|
|
49
|
+
cms: 'CMS assets',
|
|
50
|
+
cmsTheme: 'CMS theme (coming soon)',
|
|
51
|
+
cmsReactModule: 'CMS React module (coming soon)',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
31
55
|
startTitle: 'Welcome to HubSpot Development!',
|
|
32
56
|
verboseDescribe: 'A step-by-step command to get you started with a HubSpot project.',
|
|
33
57
|
startDescription: 'You can use the HubSpot CLI to build apps, CMS themes, and more.\n',
|
|
@@ -41,9 +65,12 @@ export const commands = {
|
|
|
41
65
|
openedDeveloperOverview: 'HubSpot opened!',
|
|
42
66
|
prompts: {
|
|
43
67
|
selectOption: 'Are you looking to build apps or CMS assets?',
|
|
68
|
+
selectOptionV2: 'Choose a component type to get started',
|
|
44
69
|
options: {
|
|
45
70
|
app: 'App',
|
|
46
71
|
cms: 'CMS assets',
|
|
72
|
+
cmsTheme: 'CMS theme (coming soon)',
|
|
73
|
+
cmsReactModule: 'CMS React module (coming soon)',
|
|
47
74
|
},
|
|
48
75
|
uploadProject: (accountName) => `Would you like to upload this project to account "${accountName}" now?`,
|
|
49
76
|
projectCreated: {
|
|
@@ -1262,7 +1289,6 @@ export const commands = {
|
|
|
1262
1289
|
prompts: {
|
|
1263
1290
|
targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
|
|
1264
1291
|
targetsRequired: 'Must choose at least one app to configure.',
|
|
1265
|
-
standaloneMode: 'Do you want to run in standalone mode? (This will use npx @hubspot/cli instead of local hs command)',
|
|
1266
1292
|
},
|
|
1267
1293
|
},
|
|
1268
1294
|
start: {
|
|
@@ -1430,7 +1456,7 @@ export const commands = {
|
|
|
1430
1456
|
describe: 'The starting template. Only applies when platform version is less than 2025.2.',
|
|
1431
1457
|
},
|
|
1432
1458
|
templateSource: {
|
|
1433
|
-
describe: 'Path to custom GitHub repository from which to create project template',
|
|
1459
|
+
describe: 'Path to custom GitHub repository from which to create project template. Only applies when platform version is less than 2025.2.',
|
|
1434
1460
|
},
|
|
1435
1461
|
platformVersion: {
|
|
1436
1462
|
describe: 'The target platform version for the new project.',
|
|
@@ -2942,17 +2968,16 @@ export const lib = {
|
|
|
2942
2968
|
appDataNotFound: 'An error occurred while fetching data for your app.',
|
|
2943
2969
|
oauthAppRedirectUrlError: (redirectUrl) => `${chalk.bold('No reponse from your OAuth service:')} ${redirectUrl}\nYour app needs a valid OAuth2 service to be installed for local dev. ${uiLink('Learn more', 'https://developers.hubspot.com/docs/apps/developer-platform/build-apps/authentication/oauth/working-with-oauth')}`,
|
|
2944
2970
|
},
|
|
2945
|
-
|
|
2971
|
+
CLIWebsocketServer: {
|
|
2946
2972
|
errors: {
|
|
2947
|
-
|
|
2973
|
+
portManagerNotRunning: (prefix) => `${prefix ? `${prefix} ` : ''}PortManagerServing must be running before starting WebsocketServer.`,
|
|
2974
|
+
originNotAllowed: (origin) => `Connections from ${origin ? `origin ${origin}` : 'this origin'} are not allowed.`,
|
|
2948
2975
|
missingTypeField: (data) => `Unsupported message received. Missing type field: ${data}`,
|
|
2949
|
-
unknownMessageType: (type) => `Unsupported message received. Unknown message type: ${type}`,
|
|
2950
2976
|
invalidJSON: (data) => `Unsupported message received. Invalid JSON: ${data}`,
|
|
2951
|
-
|
|
2952
|
-
originNotAllowed: (origin) => `Connections from ${origin ? `origin ${origin}` : 'this origin'} are not allowed.`,
|
|
2977
|
+
unknownMessageType: (type) => `Unsupported message received. Unknown message type: ${type}`,
|
|
2953
2978
|
},
|
|
2954
2979
|
logs: {
|
|
2955
|
-
startup: (port) => `
|
|
2980
|
+
startup: (port) => `WebsocketServer running on port ${port}`,
|
|
2956
2981
|
},
|
|
2957
2982
|
},
|
|
2958
2983
|
LocalDevProcess: {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
export type CLIWebSocketMessage = {
|
|
3
|
+
type: string;
|
|
4
|
+
data?: unknown;
|
|
5
|
+
};
|
|
6
|
+
declare class CLIWebSocketServer {
|
|
7
|
+
private server?;
|
|
8
|
+
private instanceId;
|
|
9
|
+
private logPrefix?;
|
|
10
|
+
private debug?;
|
|
11
|
+
constructor({ instanceId, logPrefix, debug, }: {
|
|
12
|
+
instanceId: string;
|
|
13
|
+
logPrefix?: string;
|
|
14
|
+
debug?: boolean;
|
|
15
|
+
});
|
|
16
|
+
private log;
|
|
17
|
+
private logError;
|
|
18
|
+
sendMessage(websocket: WebSocket, message: CLIWebSocketMessage): void;
|
|
19
|
+
private sendCliMetadata;
|
|
20
|
+
start({ onConnection, onMessage, onClose, metadata, }: {
|
|
21
|
+
onConnection?: (websocket: WebSocket) => void;
|
|
22
|
+
onMessage?: (websocket: WebSocket, message: CLIWebSocketMessage) => boolean;
|
|
23
|
+
onClose?: () => void;
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
shutdown(): void;
|
|
27
|
+
}
|
|
28
|
+
export default CLIWebSocketServer;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { WebSocketServer } from 'ws';
|
|
2
|
+
import { isPortManagerServerRunning, requestPorts, } from '@hubspot/local-dev-lib/portManager';
|
|
3
|
+
import { uiLogger } from './ui/logger.js';
|
|
4
|
+
import { lib } from '../lang/en.js';
|
|
5
|
+
import { pkg } from './jsonLoader.js';
|
|
6
|
+
const DOMAINS = ['hubspot.com', 'hubspotqa.com'];
|
|
7
|
+
const SUBDOMAINS = ['local', 'app', 'app-na2', 'app-na3', 'app-ap1', 'app-eu1'];
|
|
8
|
+
const ALLOWED_ORIGIN_REGEX = new RegExp(`^https://(${SUBDOMAINS.join('|')})\\.(${DOMAINS.join('|')})$`);
|
|
9
|
+
const CLI_METADATA_MESSAGE_TYPE = 'server:cliMetadata';
|
|
10
|
+
class CLIWebSocketServer {
|
|
11
|
+
server;
|
|
12
|
+
instanceId;
|
|
13
|
+
logPrefix;
|
|
14
|
+
debug;
|
|
15
|
+
constructor({ instanceId, logPrefix, debug, }) {
|
|
16
|
+
this.instanceId = instanceId;
|
|
17
|
+
this.logPrefix = logPrefix;
|
|
18
|
+
this.debug = debug;
|
|
19
|
+
}
|
|
20
|
+
log(message) {
|
|
21
|
+
if (this.debug) {
|
|
22
|
+
uiLogger.log(`${this.logPrefix} ${message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
logError(message) {
|
|
26
|
+
if (this.debug) {
|
|
27
|
+
uiLogger.error(`${this.logPrefix} ${message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
sendMessage(websocket, message) {
|
|
31
|
+
websocket.send(JSON.stringify(message));
|
|
32
|
+
}
|
|
33
|
+
sendCliMetadata(websocket, metadata) {
|
|
34
|
+
this.sendMessage(websocket, {
|
|
35
|
+
type: CLI_METADATA_MESSAGE_TYPE,
|
|
36
|
+
data: {
|
|
37
|
+
cliVersion: pkg.version,
|
|
38
|
+
...metadata,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async start({ onConnection, onMessage, onClose, metadata, }) {
|
|
43
|
+
const portManagerIsRunning = await isPortManagerServerRunning();
|
|
44
|
+
if (!portManagerIsRunning) {
|
|
45
|
+
throw new Error(lib.CLIWebsocketServer.errors.portManagerNotRunning(this.logPrefix));
|
|
46
|
+
}
|
|
47
|
+
const portData = await requestPorts([{ instanceId: this.instanceId }]);
|
|
48
|
+
const port = portData[this.instanceId];
|
|
49
|
+
this.server = new WebSocketServer({ port });
|
|
50
|
+
this.log(lib.CLIWebsocketServer.logs.startup(port));
|
|
51
|
+
this.server.on('connection', (ws, req) => {
|
|
52
|
+
const origin = req.headers.origin;
|
|
53
|
+
if (!origin || !ALLOWED_ORIGIN_REGEX.test(origin)) {
|
|
54
|
+
ws.close(1008, lib.CLIWebsocketServer.errors.originNotAllowed(origin));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.sendCliMetadata(ws, metadata);
|
|
58
|
+
if (onConnection) {
|
|
59
|
+
onConnection(ws);
|
|
60
|
+
}
|
|
61
|
+
ws.on('message', data => {
|
|
62
|
+
try {
|
|
63
|
+
const message = JSON.parse(data.toString());
|
|
64
|
+
if (!message.type) {
|
|
65
|
+
this.logError(lib.CLIWebsocketServer.errors.missingTypeField(data.toString()));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (onMessage) {
|
|
69
|
+
const messageHandled = onMessage(ws, message);
|
|
70
|
+
if (!messageHandled) {
|
|
71
|
+
this.logError(lib.CLIWebsocketServer.errors.unknownMessageType(message.type));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
this.logError(lib.CLIWebsocketServer.errors.invalidJSON(data.toString()));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
this.server.on('close', () => {
|
|
81
|
+
if (onClose) {
|
|
82
|
+
onClose();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
shutdown() {
|
|
87
|
+
this.server?.close();
|
|
88
|
+
this.server = undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export default CLIWebSocketServer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|