@hubspot/cli 8.1.0-beta.0 → 8.2.0-beta.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/cms/__tests__/watch.test.js +0 -8
- package/commands/cms/function/logs.js +1 -0
- package/commands/cms/theme/preview.js +9 -64
- package/commands/cms/watch.d.ts +0 -1
- package/commands/cms/watch.js +2 -8
- package/commands/feedback.js +1 -1
- package/commands/mcp/__tests__/start.test.js +8 -1
- package/commands/mcp/setup.js +1 -9
- package/commands/mcp/start.js +0 -1
- package/commands/project/__tests__/create.test.js +1 -1
- package/commands/project/create.js +2 -2
- package/commands/project/watch.js +15 -2
- package/lang/en.d.ts +17 -6
- package/lang/en.js +18 -7
- package/lib/__tests__/commandSuggestion.test.js +2 -0
- package/lib/__tests__/serverlessLogs.test.js +79 -64
- package/lib/commandSuggestion.js +1 -7
- package/lib/constants.d.ts +1 -1
- package/lib/constants.js +1 -1
- package/lib/generateSelectors.js +1 -2
- package/lib/getStartedV2Actions.d.ts +13 -0
- package/lib/getStartedV2Actions.js +53 -0
- package/lib/mcp/__tests__/setup.test.js +357 -28
- package/lib/mcp/setup.d.ts +1 -0
- package/lib/mcp/setup.js +77 -30
- package/lib/projects/create/__tests__/legacy.test.js +6 -24
- package/lib/projects/create/index.js +1 -4
- package/lib/projects/create/legacy.js +3 -8
- package/lib/projects/create/v2.js +1 -9
- package/lib/projects/ensureProjectExists.js +1 -2
- package/lib/projects/pollProjectBuildAndDeploy.js +90 -85
- package/lib/projects/upload.d.ts +1 -0
- package/lib/projects/upload.js +37 -46
- package/lib/projects/watch.d.ts +2 -1
- package/lib/projects/watch.js +32 -24
- package/lib/serverlessLogs.js +50 -44
- package/lib/theme/cmsDevServerProcess.d.ts +12 -0
- package/lib/theme/cmsDevServerProcess.js +148 -0
- package/lib/theme/cmsDevServerRunner.d.ts +14 -0
- package/lib/theme/cmsDevServerRunner.js +90 -0
- package/lib/usageTracking.js +8 -5
- package/mcp-server/tools/cms/HsCreateFunctionTool.js +1 -1
- package/mcp-server/tools/cms/HsCreateModuleTool.js +1 -1
- package/mcp-server/tools/cms/HsCreateTemplateTool.js +1 -1
- package/mcp-server/tools/cms/HsFunctionLogsTool.js +1 -1
- package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
- package/mcp-server/tools/cms/HsListTool.js +1 -1
- package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -2
- package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +1 -2
- package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +1 -2
- package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +1 -2
- package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +1 -2
- package/mcp-server/tools/cms/__tests__/HsListTool.test.js +1 -2
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +20 -3
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +7 -11
- package/mcp-server/tools/project/CreateProjectTool.d.ts +24 -4
- package/mcp-server/tools/project/CreateProjectTool.js +6 -11
- package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
- package/mcp-server/tools/project/DeployProjectTool.js +1 -1
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +5 -8
- package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
- package/mcp-server/tools/project/GetBuildLogsTool.js +6 -7
- package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
- package/mcp-server/tools/project/GetBuildStatusTool.js +3 -4
- package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +6 -1
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -6
- package/mcp-server/tools/project/UploadProjectTools.js +1 -1
- package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
- package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +1 -2
- package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -2
- package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +1 -2
- package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +1 -2
- package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +0 -32
- package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +10 -2
- package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
- package/mcp-server/tools/project/constants.d.ts +12 -1
- package/mcp-server/tools/project/constants.js +12 -16
- package/mcp-server/utils/__tests__/command.test.js +233 -3
- package/mcp-server/utils/__tests__/feedbackTracking.test.js +9 -64
- package/mcp-server/utils/command.d.ts +5 -0
- package/mcp-server/utils/command.js +24 -0
- package/mcp-server/utils/feedbackTracking.js +2 -17
- package/package.json +4 -5
- package/ui/components/getStarted/GetStartedFlow.js +79 -2
- package/ui/components/getStarted/reducer.d.ts +20 -0
- package/ui/components/getStarted/reducer.js +36 -0
- package/ui/components/getStarted/screens/InstallationScreen.d.ts +7 -0
- package/ui/components/getStarted/screens/InstallationScreen.js +16 -0
- package/ui/components/getStarted/screens/ProjectSetupScreen.js +2 -1
- package/ui/lib/constants.d.ts +1 -0
- package/ui/lib/constants.js +1 -0
- package/mcp-server/utils/__tests__/project.test.d.ts +0 -1
- package/mcp-server/utils/__tests__/project.test.js +0 -140
- package/mcp-server/utils/project.d.ts +0 -5
- package/mcp-server/utils/project.js +0 -18
|
@@ -64,7 +64,6 @@ describe('commands/cms/watch', () => {
|
|
|
64
64
|
expect(positionalSpy).toHaveBeenCalledWith('dest', expect.objectContaining({ type: 'string' }));
|
|
65
65
|
expect(optionSpy).toHaveBeenCalledWith('remove', expect.objectContaining({ type: 'boolean', alias: 'r' }));
|
|
66
66
|
expect(optionSpy).toHaveBeenCalledWith('initial-upload', expect.objectContaining({ type: 'boolean', alias: 'i' }));
|
|
67
|
-
expect(optionSpy).toHaveBeenCalledWith('disable-initial', expect.objectContaining({ type: 'boolean' }));
|
|
68
67
|
expect(optionSpy).toHaveBeenCalledWith('notify', expect.objectContaining({ type: 'string', alias: 'n' }));
|
|
69
68
|
expect(optionSpy).toHaveBeenCalledWith('convert-fields', expect.objectContaining({ type: 'boolean' }));
|
|
70
69
|
});
|
|
@@ -78,7 +77,6 @@ describe('commands/cms/watch', () => {
|
|
|
78
77
|
derivedAccountId: 123456,
|
|
79
78
|
remove: false,
|
|
80
79
|
initialUpload: false,
|
|
81
|
-
disableInitial: false,
|
|
82
80
|
};
|
|
83
81
|
statSyncSpy.mockReturnValue({
|
|
84
82
|
isFile: () => false,
|
|
@@ -134,7 +132,6 @@ describe('commands/cms/watch', () => {
|
|
|
134
132
|
});
|
|
135
133
|
it('should start watching without initial upload by default', async () => {
|
|
136
134
|
await watchCommand.handler(args);
|
|
137
|
-
expect(uiLogger.info).toHaveBeenCalledWith(expect.stringContaining('not'));
|
|
138
135
|
expect(getUploadableFileListSpy).not.toHaveBeenCalled();
|
|
139
136
|
expect(watchSpy).toHaveBeenCalledWith(123456, path.resolve('/test/cwd', 'src'), '/dest', expect.objectContaining({
|
|
140
137
|
cmsPublishMode: 'publish',
|
|
@@ -152,11 +149,6 @@ describe('commands/cms/watch', () => {
|
|
|
152
149
|
filePaths: ['file1.js', 'file2.js'],
|
|
153
150
|
}), null, expect.any(Function), undefined, expect.any(Function));
|
|
154
151
|
});
|
|
155
|
-
it('should show disable initial message when disableInitial is true', async () => {
|
|
156
|
-
args.disableInitial = true;
|
|
157
|
-
await watchCommand.handler(args);
|
|
158
|
-
expect(uiLogger.info).toHaveBeenCalledWith(expect.stringContaining('disable'));
|
|
159
|
-
});
|
|
160
152
|
it('should pass remove option to watch', async () => {
|
|
161
153
|
args.remove = true;
|
|
162
154
|
await watchCommand.handler(args);
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import cliProgress from 'cli-progress';
|
|
4
3
|
import { commands } from '../../../lang/en.js';
|
|
5
4
|
import { getCwd } from '@hubspot/local-dev-lib/path';
|
|
6
|
-
import { FILE_UPLOAD_RESULT_TYPES } from '@hubspot/local-dev-lib/constants/files';
|
|
7
5
|
import { getThemeJSONPath } from '@hubspot/local-dev-lib/cms/themes';
|
|
8
|
-
import {
|
|
9
|
-
import { getUploadableFileList } from '../../../lib/upload.js';
|
|
6
|
+
import { spawnDevServer } from '../../../lib/theme/cmsDevServerProcess.js';
|
|
10
7
|
import { trackCommandUsage } from '../../../lib/usageTracking.js';
|
|
11
8
|
import { previewPrompt, previewProjectPrompt, } from '../../../lib/prompts/previewPrompt.js';
|
|
12
9
|
import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
|
|
13
|
-
import { ApiErrorContext, logError } from '../../../lib/errorHandlers/index.js';
|
|
14
10
|
import { getProjectConfig } from '../../../lib/projects/config.js';
|
|
15
11
|
import { findProjectComponents } from '../../../lib/projects/structure.js';
|
|
16
12
|
import { ComponentTypes } from '../../../types/Projects.js';
|
|
@@ -73,67 +69,16 @@ async function determineSrcAndDest(args) {
|
|
|
73
69
|
async function handler(args) {
|
|
74
70
|
const { derivedAccountId, noSsl, resetSession, port, generateFieldsTypes } = args;
|
|
75
71
|
const { absoluteSrc, dest } = await determineSrcAndDest(args);
|
|
76
|
-
const filePaths = await getUploadableFileList(absoluteSrc, false);
|
|
77
|
-
function startProgressBar(numFiles) {
|
|
78
|
-
const initialUploadProgressBar = new cliProgress.SingleBar({
|
|
79
|
-
gracefulExit: true,
|
|
80
|
-
format: '[{bar}] {percentage}% | {value}/{total} | {label}',
|
|
81
|
-
hideCursor: true,
|
|
82
|
-
}, cliProgress.Presets.rect);
|
|
83
|
-
initialUploadProgressBar.start(numFiles, 0, {
|
|
84
|
-
label: commands.cms.subcommands.theme.subcommands.preview
|
|
85
|
-
.initialUploadProgressBar.start,
|
|
86
|
-
});
|
|
87
|
-
let uploadsHaveStarted = false;
|
|
88
|
-
const uploadOptions = {
|
|
89
|
-
onAttemptCallback: () => {
|
|
90
|
-
/* Intentionally blank */
|
|
91
|
-
},
|
|
92
|
-
onSuccessCallback: () => {
|
|
93
|
-
initialUploadProgressBar.increment();
|
|
94
|
-
if (!uploadsHaveStarted) {
|
|
95
|
-
uploadsHaveStarted = true;
|
|
96
|
-
initialUploadProgressBar.update(0, {
|
|
97
|
-
label: commands.cms.subcommands.theme.subcommands.preview
|
|
98
|
-
.initialUploadProgressBar.uploading,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
onFirstErrorCallback: () => {
|
|
103
|
-
/* Intentionally blank */
|
|
104
|
-
},
|
|
105
|
-
onRetryCallback: () => {
|
|
106
|
-
/* Intentionally blank */
|
|
107
|
-
},
|
|
108
|
-
onFinalErrorCallback: () => initialUploadProgressBar.increment(),
|
|
109
|
-
onFinishCallback: (results) => {
|
|
110
|
-
initialUploadProgressBar.update(numFiles, {
|
|
111
|
-
label: commands.cms.subcommands.theme.subcommands.preview
|
|
112
|
-
.initialUploadProgressBar.finish,
|
|
113
|
-
});
|
|
114
|
-
initialUploadProgressBar.stop();
|
|
115
|
-
results.forEach(result => {
|
|
116
|
-
if (result.resultType == FILE_UPLOAD_RESULT_TYPES.FAILURE) {
|
|
117
|
-
uiLogger.error(commands.cms.subcommands.theme.subcommands.preview.errors.uploadFailed(result.file, dest));
|
|
118
|
-
logError(result.error, new ApiErrorContext({
|
|
119
|
-
accountId: derivedAccountId,
|
|
120
|
-
request: dest,
|
|
121
|
-
payload: result.file,
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
return uploadOptions;
|
|
128
|
-
}
|
|
129
72
|
trackCommandUsage('preview', {}, derivedAccountId);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
73
|
+
// Spawn dev server in isolated subprocess to avoid React version conflicts
|
|
74
|
+
// File listing and progress bars are handled within the subprocess
|
|
75
|
+
await spawnDevServer({
|
|
76
|
+
absoluteSrc,
|
|
77
|
+
accountName: derivedAccountId?.toString(),
|
|
78
|
+
noSsl,
|
|
79
|
+
port,
|
|
80
|
+
generateFieldsTypes,
|
|
135
81
|
resetSession: resetSession || false,
|
|
136
|
-
startProgressBar,
|
|
137
82
|
dest,
|
|
138
83
|
});
|
|
139
84
|
}
|
package/commands/cms/watch.d.ts
CHANGED
package/commands/cms/watch.js
CHANGED
|
@@ -15,7 +15,7 @@ import { uiLogger } from '../../lib/ui/logger.js';
|
|
|
15
15
|
const command = 'watch [src] [dest]';
|
|
16
16
|
const describe = commands.cms.subcommands.watch.describe;
|
|
17
17
|
const handler = async (args) => {
|
|
18
|
-
const { remove, initialUpload,
|
|
18
|
+
const { remove, initialUpload, notify, derivedAccountId } = args;
|
|
19
19
|
if (!validateCmsPublishMode(args)) {
|
|
20
20
|
process.exit(EXIT_CODES.ERROR);
|
|
21
21
|
}
|
|
@@ -40,13 +40,6 @@ const handler = async (args) => {
|
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
let filesToUpload = [];
|
|
43
|
-
if (disableInitial) {
|
|
44
|
-
uiLogger.info(commands.cms.subcommands.watch.warnings.disableInitial);
|
|
45
|
-
}
|
|
46
|
-
else if (!initialUpload) {
|
|
47
|
-
uiLogger.info(commands.cms.subcommands.watch.warnings.notUploaded(src));
|
|
48
|
-
uiLogger.info(commands.cms.subcommands.watch.warnings.initialUpload);
|
|
49
|
-
}
|
|
50
43
|
if (initialUpload) {
|
|
51
44
|
filesToUpload = await getUploadableFileList(absoluteSrcPath, args.convertFields);
|
|
52
45
|
}
|
|
@@ -99,6 +92,7 @@ function watchBuilder(yargs) {
|
|
|
99
92
|
describe: commands.cms.subcommands.watch.options.initialUpload,
|
|
100
93
|
type: 'boolean',
|
|
101
94
|
});
|
|
95
|
+
// TODO: remove this before the next major release
|
|
102
96
|
yargs.option('disable-initial', {
|
|
103
97
|
describe: commands.cms.subcommands.watch.options.disableInitial,
|
|
104
98
|
type: 'boolean',
|
package/commands/feedback.js
CHANGED
|
@@ -4,7 +4,7 @@ import { makeYargsBuilder } from '../lib/yargsUtils.js';
|
|
|
4
4
|
import { EXIT_CODES } from '../lib/enums/exitCodes.js';
|
|
5
5
|
import { commands } from '../lang/en.js';
|
|
6
6
|
import { uiLogger } from '../lib/ui/logger.js';
|
|
7
|
-
|
|
7
|
+
import { FEEDBACK_URL } from '../lib/constants.js';
|
|
8
8
|
const command = 'feedback';
|
|
9
9
|
const describe = commands.project.feedback.describe;
|
|
10
10
|
async function handler() {
|
|
@@ -7,14 +7,20 @@ 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
|
-
|
|
10
|
+
// Create a mock execAsync function before importing the module
|
|
11
|
+
const execAsyncMock = vi.fn();
|
|
11
12
|
vi.mock('yargs');
|
|
12
13
|
vi.mock('../../../lib/commonOpts');
|
|
13
14
|
vi.mock('node:child_process');
|
|
15
|
+
vi.mock('node:util', () => ({
|
|
16
|
+
promisify: vi.fn(() => execAsyncMock),
|
|
17
|
+
}));
|
|
14
18
|
vi.mock('fs');
|
|
15
19
|
vi.mock('@hubspot/local-dev-lib/config');
|
|
16
20
|
vi.mock('../../../lib/errorHandlers/index.js');
|
|
17
21
|
vi.mock('../../../lib/process.js');
|
|
22
|
+
// Import after mocks are set up
|
|
23
|
+
const startCommand = await import('../start.js').then(m => m.default);
|
|
18
24
|
const spawnSpy = vi.mocked(spawn);
|
|
19
25
|
const existsSyncSpy = vi.spyOn(fs, 'existsSync');
|
|
20
26
|
const trackCommandUsageSpy = vi.spyOn(usageTrackingLib, 'trackCommandUsage');
|
|
@@ -36,6 +42,7 @@ describe('commands/mcp/start', () => {
|
|
|
36
42
|
processExitSpy.mockImplementation(() => { });
|
|
37
43
|
// Mock config to prevent reading actual config file in CI
|
|
38
44
|
getConfigAccountIfExistsSpy.mockReturnValue(undefined);
|
|
45
|
+
execAsyncMock.mockClear();
|
|
39
46
|
});
|
|
40
47
|
describe('command', () => {
|
|
41
48
|
it('should have the correct command structure', () => {
|
package/commands/mcp/setup.js
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
|
|
2
2
|
import { makeYargsBuilder } from '../../lib/yargsUtils.js';
|
|
3
3
|
import { commands } from '../../lang/en.js';
|
|
4
|
-
import { uiLogger } from '../../lib/ui/logger.js';
|
|
5
4
|
import { addMcpServerToConfig, supportedTools } from '../../lib/mcp/setup.js';
|
|
6
5
|
import { trackCommandUsage } from '../../lib/usageTracking.js';
|
|
7
|
-
import { hasFeature } from '../../lib/hasFeature.js';
|
|
8
|
-
import { FEATURES } from '../../lib/constants.js';
|
|
9
6
|
const command = ['setup'];
|
|
10
7
|
const describe = commands.mcp.setup.describe;
|
|
11
8
|
async function handler(args) {
|
|
12
9
|
const { derivedAccountId } = args;
|
|
13
|
-
|
|
14
|
-
if (!hasMcpAccess) {
|
|
15
|
-
uiLogger.error(commands.mcp.setup.errors.needsMcpAccess(derivedAccountId));
|
|
16
|
-
process.exit(EXIT_CODES.ERROR);
|
|
17
|
-
}
|
|
18
|
-
trackCommandUsage('mcp-setup', {}, args.derivedAccountId);
|
|
10
|
+
await trackCommandUsage('mcp-setup', {}, derivedAccountId);
|
|
19
11
|
try {
|
|
20
12
|
await addMcpServerToConfig(args.client);
|
|
21
13
|
}
|
package/commands/mcp/start.js
CHANGED
|
@@ -28,7 +28,6 @@ async function startMcpServer(aiAgent) {
|
|
|
28
28
|
uiLogger.debug(commands.mcp.start.startingServer);
|
|
29
29
|
uiLogger.debug(commands.mcp.start.stopInstructions);
|
|
30
30
|
const args = [serverPath];
|
|
31
|
-
// Start the server using ts-node
|
|
32
31
|
const child = spawn(`node`, args, {
|
|
33
32
|
stdio: 'inherit',
|
|
34
33
|
env: {
|
|
@@ -50,7 +50,7 @@ describe('commands/project/create', () => {
|
|
|
50
50
|
expect(optionsCall['platform-version']).toEqual(expect.objectContaining({
|
|
51
51
|
describe: 'The target platform version for the new project.',
|
|
52
52
|
type: 'string',
|
|
53
|
-
choices: ['2025.1', '2025.2'],
|
|
53
|
+
choices: ['2025.1', '2025.2', '2026.03-beta'],
|
|
54
54
|
default: '2025.2',
|
|
55
55
|
}));
|
|
56
56
|
});
|
|
@@ -20,7 +20,7 @@ import { updateHsMetaFilesWithAutoGeneratedFields } from '../../lib/projects/com
|
|
|
20
20
|
import SpinniesManager from '../../lib/ui/SpinniesManager.js';
|
|
21
21
|
const command = ['create', 'init'];
|
|
22
22
|
const describe = commands.project.create.describe;
|
|
23
|
-
const { v2025_1, v2025_2 } = PLATFORM_VERSIONS;
|
|
23
|
+
const { v2025_1, v2025_2, v2026_03_beta } = PLATFORM_VERSIONS;
|
|
24
24
|
async function handler(args) {
|
|
25
25
|
const { derivedAccountId, platformVersion, templateSource } = args;
|
|
26
26
|
if (templateSource && !templateSource.includes('/')) {
|
|
@@ -125,7 +125,7 @@ function projectCreateBuilder(yargs) {
|
|
|
125
125
|
'platform-version': {
|
|
126
126
|
describe: commands.project.create.options.platformVersion.describe,
|
|
127
127
|
type: 'string',
|
|
128
|
-
choices: [v2025_1, v2025_2],
|
|
128
|
+
choices: [v2025_1, v2025_2, v2026_03_beta],
|
|
129
129
|
default: v2025_2,
|
|
130
130
|
},
|
|
131
131
|
'project-base': {
|
|
@@ -76,18 +76,30 @@ async function handler(args) {
|
|
|
76
76
|
try {
|
|
77
77
|
const { data: { results: builds }, } = await fetchProjectBuilds(derivedAccountId, projectConfig.name);
|
|
78
78
|
const hasNoBuilds = !builds || !builds.length;
|
|
79
|
+
const handleWatchTermination = (error) => {
|
|
80
|
+
if (error) {
|
|
81
|
+
logError(error, new ApiErrorContext({ accountId: derivedAccountId }));
|
|
82
|
+
process.exit(EXIT_CODES.ERROR);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
79
88
|
const startWatching = async () => {
|
|
80
|
-
await createWatcher(derivedAccountId, projectConfig, projectDir, handleBuildStatus, handleUserInput);
|
|
89
|
+
await createWatcher(derivedAccountId, projectConfig, projectDir, handleBuildStatus, handleUserInput, handleWatchTermination);
|
|
81
90
|
};
|
|
82
91
|
// Upload all files if no build exists for this project yet
|
|
83
92
|
if (initialUpload || hasNoBuilds) {
|
|
84
|
-
const { uploadError } = await handleProjectUpload({
|
|
93
|
+
const { uploadError, projectNotFound } = await handleProjectUpload({
|
|
85
94
|
accountId: derivedAccountId,
|
|
86
95
|
projectConfig,
|
|
87
96
|
projectDir,
|
|
88
97
|
callbackFunc: startWatching,
|
|
89
98
|
isUploadCommand: false,
|
|
90
99
|
});
|
|
100
|
+
if (projectNotFound) {
|
|
101
|
+
process.exit(EXIT_CODES.ERROR);
|
|
102
|
+
}
|
|
91
103
|
if (uploadError) {
|
|
92
104
|
if (isSpecifiedError(uploadError, {
|
|
93
105
|
subCategory: PROJECT_ERROR_TYPES.PROJECT_LOCKED,
|
|
@@ -111,6 +123,7 @@ async function handler(args) {
|
|
|
111
123
|
}
|
|
112
124
|
catch (e) {
|
|
113
125
|
logError(e, new ApiErrorContext({ accountId: derivedAccountId }));
|
|
126
|
+
process.exit(EXIT_CODES.ERROR);
|
|
114
127
|
}
|
|
115
128
|
}
|
|
116
129
|
function projectWatchBuilder(yargs) {
|
package/lang/en.d.ts
CHANGED
|
@@ -43,6 +43,11 @@ export declare const commands: {
|
|
|
43
43
|
checkOutConfig: (configPath: string) => string;
|
|
44
44
|
pressEnterToInstall: (accountName: string) => string;
|
|
45
45
|
pressKeyToExit: string;
|
|
46
|
+
installingApp: (appName: string, accountName: string) => string;
|
|
47
|
+
installInstructions: string;
|
|
48
|
+
browserFailedToOpen: (url: string) => string;
|
|
49
|
+
pollingTimeout: (minutes: number) => string;
|
|
50
|
+
pressEnterToContinueSetup: string;
|
|
46
51
|
prompts: {
|
|
47
52
|
selectOptionV2: string;
|
|
48
53
|
options: {
|
|
@@ -596,11 +601,6 @@ export declare const commands: {
|
|
|
596
601
|
src: string;
|
|
597
602
|
dest: string;
|
|
598
603
|
};
|
|
599
|
-
warnings: {
|
|
600
|
-
disableInitial: string;
|
|
601
|
-
initialUpload: string;
|
|
602
|
-
notUploaded: (path: string) => string;
|
|
603
|
-
};
|
|
604
604
|
};
|
|
605
605
|
fetch: {
|
|
606
606
|
describe: string;
|
|
@@ -1256,7 +1256,6 @@ export declare const commands: {
|
|
|
1256
1256
|
};
|
|
1257
1257
|
success: (derivedTargets: string[]) => string;
|
|
1258
1258
|
errors: {
|
|
1259
|
-
needsMcpAccess: (accountId?: number) => string;
|
|
1260
1259
|
errorParsingJsonFIle: (filename: string, errorMessage: string) => string;
|
|
1261
1260
|
};
|
|
1262
1261
|
spinners: {
|
|
@@ -1290,6 +1289,8 @@ export declare const commands: {
|
|
|
1290
1289
|
prompts: {
|
|
1291
1290
|
targets: string;
|
|
1292
1291
|
targetsRequired: string;
|
|
1292
|
+
standaloneMode: string;
|
|
1293
|
+
cliVersion: string;
|
|
1293
1294
|
};
|
|
1294
1295
|
};
|
|
1295
1296
|
start: {
|
|
@@ -3976,4 +3977,14 @@ export declare const lib: {
|
|
|
3976
3977
|
copyingProjectFilesFailed: string;
|
|
3977
3978
|
};
|
|
3978
3979
|
};
|
|
3980
|
+
theme: {
|
|
3981
|
+
cmsDevServerProcess: {
|
|
3982
|
+
installStarted: (targetVersion: string) => string;
|
|
3983
|
+
installSucceeded: string;
|
|
3984
|
+
installFailed: string;
|
|
3985
|
+
serverStartError: (error: Error) => string;
|
|
3986
|
+
serverExit: (code: number) => string;
|
|
3987
|
+
serverKill: (signal: NodeJS.Signals) => string;
|
|
3988
|
+
};
|
|
3989
|
+
};
|
|
3979
3990
|
};
|
package/lang/en.js
CHANGED
|
@@ -51,6 +51,11 @@ export const commands = {
|
|
|
51
51
|
checkOutConfig: (configPath) => `Check out ${chalk.cyan(configPath)} for the full configuration.`,
|
|
52
52
|
pressEnterToInstall: (accountName) => `? Press ${chalk.bold('<enter>')} to continue installing and previewing this app in ${chalk.bold(accountName)}`,
|
|
53
53
|
pressKeyToExit: `Press any key to exit...`,
|
|
54
|
+
installingApp: (appName, accountName) => `Installing ${chalk.bold(appName)} in ${chalk.bold(accountName)}...`,
|
|
55
|
+
installInstructions: `We'll take you to your HubSpot account and walk you through installing your app.`,
|
|
56
|
+
browserFailedToOpen: (url) => `⚠️ Failed to open browser automatically. Please open this URL manually:\n${chalk.cyan(url)}`,
|
|
57
|
+
pollingTimeout: (minutes) => `⚠️ Installation polling timed out after ${minutes} minutes. The app may still be installing in the background.`,
|
|
58
|
+
pressEnterToContinueSetup: `Press ${chalk.bold('<enter>')} to continue with card setup...`,
|
|
54
59
|
prompts: {
|
|
55
60
|
selectOptionV2: 'Choose a component type to get started',
|
|
56
61
|
options: {
|
|
@@ -604,11 +609,6 @@ export const commands = {
|
|
|
604
609
|
src: 'Path to the local directory your files are in, relative to your current working directory',
|
|
605
610
|
dest: 'Path in HubSpot Design Tools. Can be a net new path',
|
|
606
611
|
},
|
|
607
|
-
warnings: {
|
|
608
|
-
disableInitial: `Passing the "${chalk.bold('--disable-initial')}" option is no longer necessary. Running "${uiCommandReference('hs watch')}" no longer uploads the watched directory by default.`,
|
|
609
|
-
initialUpload: `To upload the directory run "${uiCommandReference('hs upload')}" beforehand or add the "${chalk.bold('--initial-upload')}" option when running "${uiCommandReference('hs watch')}".`,
|
|
610
|
-
notUploaded: (path) => `The "${uiCommandReference('hs watch')}" command no longer uploads the watched directory when started. The directory "${path}" was not uploaded.`,
|
|
611
|
-
},
|
|
612
612
|
},
|
|
613
613
|
fetch: {
|
|
614
614
|
describe: 'Fetch a file, directory or module from HubSpot and write to a path on your computer.',
|
|
@@ -1266,7 +1266,6 @@ export const commands = {
|
|
|
1266
1266
|
},
|
|
1267
1267
|
success: (derivedTargets) => `You can now use the HubSpot CLI MCP Server in ${derivedTargets.join(', ')}. ${chalk.bold('You may need to restart these tools to apply the changes')}.`,
|
|
1268
1268
|
errors: {
|
|
1269
|
-
needsMcpAccess: (accountId) => `You must opt in to the developer MCP beta to use this feature on ${uiAccountDescription(accountId)}. Try again with a different account or ${uiLink('join the beta now', getProductUpdatesUrl('239890', accountId))}`,
|
|
1270
1269
|
errorParsingJsonFIle: (filename, errorMessage) => `Unable to update ${chalk.bold(filename)} due to invalid JSON: ${errorMessage}`,
|
|
1271
1270
|
},
|
|
1272
1271
|
spinners: {
|
|
@@ -1306,6 +1305,8 @@ export const commands = {
|
|
|
1306
1305
|
prompts: {
|
|
1307
1306
|
targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
|
|
1308
1307
|
targetsRequired: 'Must choose at least one app to configure.',
|
|
1308
|
+
standaloneMode: 'Do you want to run in standalone mode? (This will use npx @hubspot/cli instead of the installed hs command)',
|
|
1309
|
+
cliVersion: 'Specify a CLI version to pin (leave blank for latest):',
|
|
1309
1310
|
},
|
|
1310
1311
|
},
|
|
1311
1312
|
start: {
|
|
@@ -3076,7 +3077,7 @@ export const lib = {
|
|
|
3076
3077
|
updateSucceeded: (latestVersion) => `Successfully updated HubSpot CLI to version ${chalk.bold(latestVersion)}`,
|
|
3077
3078
|
notInstalledGlobally: 'Cannot auto-update the HubSpot CLI because NPM is not installed globally',
|
|
3078
3079
|
updateFailed: (latestVersion) => `Failed to update HubSpot CLI to version ${chalk.bold(latestVersion)}`,
|
|
3079
|
-
enableAutoUpdatesMessage: `The HubSpot CLI can automatically keep itself up to date.\n\nThis helps ensure compatibility with the HubSpot platform. You can change this later at any time.\n\nRun${uiCommandReference('hs config set --allow-auto-updates=true')}`,
|
|
3080
|
+
enableAutoUpdatesMessage: `The HubSpot CLI can automatically keep itself up to date.\n\nThis helps ensure compatibility with the HubSpot platform. You can change this later at any time.\n\nRun ${uiCommandReference('hs config set --allow-auto-updates=true')}`,
|
|
3080
3081
|
},
|
|
3081
3082
|
},
|
|
3082
3083
|
projectProfiles: {
|
|
@@ -3999,4 +4000,14 @@ export const lib = {
|
|
|
3999
4000
|
copyingProjectFilesFailed: 'Unable to copy migrated project files',
|
|
4000
4001
|
},
|
|
4001
4002
|
},
|
|
4003
|
+
theme: {
|
|
4004
|
+
cmsDevServerProcess: {
|
|
4005
|
+
installStarted: (targetVersion) => `Installing cms-dev-server ${targetVersion}...`,
|
|
4006
|
+
installSucceeded: 'cms-dev-server setup complete',
|
|
4007
|
+
installFailed: 'Failed to install cms-dev-server',
|
|
4008
|
+
serverStartError: (error) => `Failed to start dev server: ${error}`,
|
|
4009
|
+
serverExit: (code) => `Dev server exited with code ${code}`,
|
|
4010
|
+
serverKill: (signal) => `Dev server killed with signal ${signal}`,
|
|
4011
|
+
},
|
|
4012
|
+
},
|
|
4002
4013
|
};
|
|
@@ -71,6 +71,8 @@ describe('lib/commandSuggestion', () => {
|
|
|
71
71
|
// Create a mock yargs builder with strict method
|
|
72
72
|
const mockYargsBuilder = {
|
|
73
73
|
strict: vi.fn().mockReturnThis(),
|
|
74
|
+
help: vi.fn().mockReturnThis(),
|
|
75
|
+
version: vi.fn().mockReturnThis(),
|
|
74
76
|
};
|
|
75
77
|
await commandModule.builder(mockYargsBuilder);
|
|
76
78
|
expect(mockYargsBuilder.strict).toHaveBeenCalledWith(false);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import mockStdIn from 'mock-stdin';
|
|
2
1
|
import { outputLogs } from '../ui/serverlessFunctionLogs.js';
|
|
3
2
|
import { tailLogs } from '../serverlessLogs.js';
|
|
3
|
+
import { handleKeypress } from '../process.js';
|
|
4
4
|
vi.mock('../ui/serverlessFunctionLogs');
|
|
5
5
|
vi.mock('../ui/SpinniesManager', () => ({
|
|
6
6
|
default: {
|
|
@@ -12,82 +12,87 @@ vi.mock('../ui/SpinniesManager', () => ({
|
|
|
12
12
|
stopAll: vi.fn(),
|
|
13
13
|
},
|
|
14
14
|
}));
|
|
15
|
+
vi.mock('../process');
|
|
15
16
|
vi.useFakeTimers();
|
|
16
17
|
const ACCOUNT_ID = 123;
|
|
18
|
+
function terminateTailLogs() {
|
|
19
|
+
const keypressCallback = vi.mocked(handleKeypress).mock.calls[0][0];
|
|
20
|
+
keypressCallback({ name: 'q' });
|
|
21
|
+
}
|
|
17
22
|
describe('lib/serverlessLogs', () => {
|
|
18
23
|
describe('tailLogs()', () => {
|
|
19
|
-
let stdinMock;
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
// @ts-ignore - we don't need to mock the entire process object
|
|
22
|
-
vi.spyOn(process, 'exit').mockImplementation(() => { });
|
|
23
|
-
stdinMock = mockStdIn.stdin();
|
|
24
|
-
});
|
|
25
24
|
afterEach(() => {
|
|
26
25
|
vi.clearAllTimers();
|
|
27
|
-
|
|
26
|
+
vi.clearAllMocks();
|
|
28
27
|
});
|
|
29
28
|
it('calls tailCall() to get the next results', async () => {
|
|
30
29
|
const compact = false;
|
|
31
|
-
const fetchLatest = vi.fn(() => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
next: {
|
|
55
|
-
after: 'somehash',
|
|
56
|
-
},
|
|
30
|
+
const fetchLatest = vi.fn(() => Promise.resolve({
|
|
31
|
+
data: {
|
|
32
|
+
id: '1234',
|
|
33
|
+
executionTime: 510,
|
|
34
|
+
log: 'Log message',
|
|
35
|
+
error: { message: '', type: '', stackTrace: [] },
|
|
36
|
+
status: 'SUCCESS',
|
|
37
|
+
createdAt: 1620232011451,
|
|
38
|
+
memory: '70/128 MB',
|
|
39
|
+
duration: '53.40 ms',
|
|
40
|
+
},
|
|
41
|
+
status: 200,
|
|
42
|
+
statusText: 'OK',
|
|
43
|
+
headers: {},
|
|
44
|
+
// eslint-disable-next-line
|
|
45
|
+
config: { headers: {} },
|
|
46
|
+
}));
|
|
47
|
+
const tailCall = vi.fn(() => Promise.resolve({
|
|
48
|
+
data: {
|
|
49
|
+
results: [],
|
|
50
|
+
paging: {
|
|
51
|
+
next: {
|
|
52
|
+
after: 'somehash',
|
|
57
53
|
},
|
|
58
54
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
},
|
|
56
|
+
status: 200,
|
|
57
|
+
statusText: 'OK',
|
|
58
|
+
headers: {},
|
|
59
|
+
// eslint-disable-next-line
|
|
60
|
+
config: { headers: {} },
|
|
61
|
+
}));
|
|
65
62
|
// @ts-ignore - headers is not used in the actual function and does not need to be mocked
|
|
66
|
-
|
|
67
|
-
vi.
|
|
63
|
+
const tailPromise = tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
|
|
64
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
68
65
|
expect(fetchLatest).toHaveBeenCalled();
|
|
66
|
+
expect(tailCall).toHaveBeenCalledTimes(1);
|
|
67
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
69
68
|
expect(tailCall).toHaveBeenCalledTimes(2);
|
|
69
|
+
terminateTailLogs();
|
|
70
|
+
await tailPromise;
|
|
70
71
|
});
|
|
71
72
|
it('outputs results', async () => {
|
|
72
73
|
const compact = false;
|
|
73
|
-
const fetchLatest = vi.fn(() => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
duration: '53.40 ms',
|
|
74
|
+
const fetchLatest = vi.fn(() => Promise.resolve({
|
|
75
|
+
data: {
|
|
76
|
+
id: '1234',
|
|
77
|
+
executionTime: 510,
|
|
78
|
+
log: 'Log message',
|
|
79
|
+
error: {
|
|
80
|
+
message: '',
|
|
81
|
+
type: '',
|
|
82
|
+
stackTrace: [],
|
|
83
|
+
statusCode: null,
|
|
84
84
|
},
|
|
85
|
-
status:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
85
|
+
status: 'SUCCESS',
|
|
86
|
+
createdAt: 1620232011451,
|
|
87
|
+
memory: '70/128 MB',
|
|
88
|
+
duration: '53.40 ms',
|
|
89
|
+
},
|
|
90
|
+
status: 200,
|
|
91
|
+
statusText: 'OK',
|
|
92
|
+
headers: {},
|
|
93
|
+
// eslint-disable-next-line
|
|
94
|
+
config: { headers: {} },
|
|
95
|
+
}));
|
|
91
96
|
const latestLogResponse = {
|
|
92
97
|
results: [
|
|
93
98
|
{
|
|
@@ -117,12 +122,23 @@ describe('lib/serverlessLogs', () => {
|
|
|
117
122
|
},
|
|
118
123
|
},
|
|
119
124
|
};
|
|
120
|
-
const tailCall = vi.fn(() => Promise.resolve({
|
|
125
|
+
const tailCall = vi.fn(() => Promise.resolve({
|
|
126
|
+
data: latestLogResponse,
|
|
127
|
+
status: 200,
|
|
128
|
+
statusText: 'OK',
|
|
129
|
+
headers: {},
|
|
130
|
+
// eslint-disable-next-line
|
|
131
|
+
config: { headers: {} },
|
|
132
|
+
}));
|
|
121
133
|
// @ts-ignore - headers is not used in the actual function and does not need to be mocked
|
|
122
|
-
|
|
123
|
-
vi.
|
|
134
|
+
const tailPromise = tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
|
|
135
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
124
136
|
expect(outputLogs).toHaveBeenCalledWith(latestLogResponse, expect.objectContaining({ compact }));
|
|
137
|
+
expect(tailCall).toHaveBeenCalledTimes(1);
|
|
138
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
125
139
|
expect(tailCall).toHaveBeenCalledTimes(2);
|
|
140
|
+
terminateTailLogs();
|
|
141
|
+
await tailPromise;
|
|
126
142
|
});
|
|
127
143
|
it('handles no logs', async () => {
|
|
128
144
|
const compact = false;
|
|
@@ -141,8 +157,7 @@ describe('lib/serverlessLogs', () => {
|
|
|
141
157
|
statusCode: 404,
|
|
142
158
|
}));
|
|
143
159
|
await tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
|
|
144
|
-
|
|
145
|
-
expect(tailCall).toHaveBeenCalledTimes(2);
|
|
160
|
+
expect(tailCall).toHaveBeenCalledTimes(1);
|
|
146
161
|
});
|
|
147
162
|
});
|
|
148
163
|
});
|