@hubspot/cli 8.0.10-experimental.1 → 8.0.10-experimental.2
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 +64 -11
- 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 +2 -6
- package/lang/en.js +2 -6
- package/lib/__tests__/serverlessLogs.test.js +71 -65
- package/lib/constants.d.ts +1 -1
- package/lib/constants.js +1 -1
- package/lib/generateSelectors.js +1 -2
- 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/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 +1 -1
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +1 -1
- package/mcp-server/tools/project/CreateProjectTool.d.ts +1 -1
- package/mcp-server/tools/project/CreateProjectTool.js +1 -1
- package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
- package/mcp-server/tools/project/DeployProjectTool.js +1 -1
- 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__/UploadProjectTools.test.js +10 -2
- package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
- package/mcp-server/tools/project/constants.d.ts +1 -1
- 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 -4
- package/lib/cms/devServerProcess.d.ts +0 -13
- package/lib/cms/devServerProcess.js +0 -200
- 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,14 +1,16 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import cliProgress from 'cli-progress';
|
|
3
4
|
import { commands } from '../../../lang/en.js';
|
|
4
5
|
import { getCwd } from '@hubspot/local-dev-lib/path';
|
|
6
|
+
import { FILE_UPLOAD_RESULT_TYPES } from '@hubspot/local-dev-lib/constants/files';
|
|
5
7
|
import { getThemeJSONPath } from '@hubspot/local-dev-lib/cms/themes';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { spawnDevServer } from '../../../lib/cms/devServerProcess.js';
|
|
8
|
+
import { createDevServer } from '@hubspot/cms-dev-server';
|
|
9
|
+
import { getUploadableFileList } from '../../../lib/upload.js';
|
|
9
10
|
import { trackCommandUsage } from '../../../lib/usageTracking.js';
|
|
10
11
|
import { previewPrompt, previewProjectPrompt, } from '../../../lib/prompts/previewPrompt.js';
|
|
11
12
|
import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
|
|
13
|
+
import { ApiErrorContext, logError } from '../../../lib/errorHandlers/index.js';
|
|
12
14
|
import { getProjectConfig } from '../../../lib/projects/config.js';
|
|
13
15
|
import { findProjectComponents } from '../../../lib/projects/structure.js';
|
|
14
16
|
import { ComponentTypes } from '../../../types/Projects.js';
|
|
@@ -71,16 +73,67 @@ async function determineSrcAndDest(args) {
|
|
|
71
73
|
async function handler(args) {
|
|
72
74
|
const { derivedAccountId, noSsl, resetSession, port, generateFieldsTypes } = args;
|
|
73
75
|
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
|
+
}
|
|
74
129
|
trackCommandUsage('preview', {}, derivedAccountId);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
noSsl,
|
|
81
|
-
port,
|
|
82
|
-
generateFieldsTypes,
|
|
130
|
+
if (port) {
|
|
131
|
+
process.env['PORT'] = port.toString();
|
|
132
|
+
}
|
|
133
|
+
createDevServer(absoluteSrc, false, '', '', !noSsl, generateFieldsTypes, {
|
|
134
|
+
filePaths,
|
|
83
135
|
resetSession: resetSession || false,
|
|
136
|
+
startProgressBar,
|
|
84
137
|
dest,
|
|
85
138
|
});
|
|
86
139
|
}
|
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
|
@@ -601,11 +601,6 @@ export declare const commands: {
|
|
|
601
601
|
src: string;
|
|
602
602
|
dest: string;
|
|
603
603
|
};
|
|
604
|
-
warnings: {
|
|
605
|
-
disableInitial: string;
|
|
606
|
-
initialUpload: string;
|
|
607
|
-
notUploaded: (path: string) => string;
|
|
608
|
-
};
|
|
609
604
|
};
|
|
610
605
|
fetch: {
|
|
611
606
|
describe: string;
|
|
@@ -1261,7 +1256,6 @@ export declare const commands: {
|
|
|
1261
1256
|
};
|
|
1262
1257
|
success: (derivedTargets: string[]) => string;
|
|
1263
1258
|
errors: {
|
|
1264
|
-
needsMcpAccess: (accountId?: number) => string;
|
|
1265
1259
|
errorParsingJsonFIle: (filename: string, errorMessage: string) => string;
|
|
1266
1260
|
};
|
|
1267
1261
|
spinners: {
|
|
@@ -1295,6 +1289,8 @@ export declare const commands: {
|
|
|
1295
1289
|
prompts: {
|
|
1296
1290
|
targets: string;
|
|
1297
1291
|
targetsRequired: string;
|
|
1292
|
+
standaloneMode: string;
|
|
1293
|
+
cliVersion: string;
|
|
1298
1294
|
};
|
|
1299
1295
|
};
|
|
1300
1296
|
start: {
|
package/lang/en.js
CHANGED
|
@@ -609,11 +609,6 @@ export const commands = {
|
|
|
609
609
|
src: 'Path to the local directory your files are in, relative to your current working directory',
|
|
610
610
|
dest: 'Path in HubSpot Design Tools. Can be a net new path',
|
|
611
611
|
},
|
|
612
|
-
warnings: {
|
|
613
|
-
disableInitial: `Passing the "${chalk.bold('--disable-initial')}" option is no longer necessary. Running "${uiCommandReference('hs watch')}" no longer uploads the watched directory by default.`,
|
|
614
|
-
initialUpload: `To upload the directory run "${uiCommandReference('hs upload')}" beforehand or add the "${chalk.bold('--initial-upload')}" option when running "${uiCommandReference('hs watch')}".`,
|
|
615
|
-
notUploaded: (path) => `The "${uiCommandReference('hs watch')}" command no longer uploads the watched directory when started. The directory "${path}" was not uploaded.`,
|
|
616
|
-
},
|
|
617
612
|
},
|
|
618
613
|
fetch: {
|
|
619
614
|
describe: 'Fetch a file, directory or module from HubSpot and write to a path on your computer.',
|
|
@@ -1271,7 +1266,6 @@ export const commands = {
|
|
|
1271
1266
|
},
|
|
1272
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')}.`,
|
|
1273
1268
|
errors: {
|
|
1274
|
-
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))}`,
|
|
1275
1269
|
errorParsingJsonFIle: (filename, errorMessage) => `Unable to update ${chalk.bold(filename)} due to invalid JSON: ${errorMessage}`,
|
|
1276
1270
|
},
|
|
1277
1271
|
spinners: {
|
|
@@ -1311,6 +1305,8 @@ export const commands = {
|
|
|
1311
1305
|
prompts: {
|
|
1312
1306
|
targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
|
|
1313
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):',
|
|
1314
1310
|
},
|
|
1315
1311
|
},
|
|
1316
1312
|
start: {
|
|
@@ -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,79 @@ 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
|
-
paging: {
|
|
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
|
+
config: { headers: {} },
|
|
45
|
+
}));
|
|
46
|
+
const tailCall = vi.fn(() => Promise.resolve({
|
|
47
|
+
data: {
|
|
48
|
+
results: [],
|
|
49
|
+
paging: {
|
|
50
|
+
next: {
|
|
51
|
+
after: 'somehash',
|
|
57
52
|
},
|
|
58
53
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
});
|
|
54
|
+
},
|
|
55
|
+
status: 200,
|
|
56
|
+
statusText: 'OK',
|
|
57
|
+
headers: {},
|
|
58
|
+
config: { headers: {} },
|
|
59
|
+
}));
|
|
65
60
|
// @ts-ignore - headers is not used in the actual function and does not need to be mocked
|
|
66
|
-
|
|
67
|
-
vi.
|
|
61
|
+
const tailPromise = tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
|
|
62
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
68
63
|
expect(fetchLatest).toHaveBeenCalled();
|
|
64
|
+
expect(tailCall).toHaveBeenCalledTimes(1);
|
|
65
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
69
66
|
expect(tailCall).toHaveBeenCalledTimes(2);
|
|
67
|
+
terminateTailLogs();
|
|
68
|
+
await tailPromise;
|
|
70
69
|
});
|
|
71
70
|
it('outputs results', async () => {
|
|
72
71
|
const compact = false;
|
|
73
|
-
const fetchLatest = vi.fn(() => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
90
|
-
});
|
|
72
|
+
const fetchLatest = vi.fn(() => Promise.resolve({
|
|
73
|
+
data: {
|
|
74
|
+
id: '1234',
|
|
75
|
+
executionTime: 510,
|
|
76
|
+
log: 'Log message',
|
|
77
|
+
error: { message: '', type: '', stackTrace: [], statusCode: null },
|
|
78
|
+
status: 'SUCCESS',
|
|
79
|
+
createdAt: 1620232011451,
|
|
80
|
+
memory: '70/128 MB',
|
|
81
|
+
duration: '53.40 ms',
|
|
82
|
+
},
|
|
83
|
+
status: 200,
|
|
84
|
+
statusText: 'OK',
|
|
85
|
+
headers: {},
|
|
86
|
+
config: { headers: {} },
|
|
87
|
+
}));
|
|
91
88
|
const latestLogResponse = {
|
|
92
89
|
results: [
|
|
93
90
|
{
|
|
@@ -117,12 +114,22 @@ describe('lib/serverlessLogs', () => {
|
|
|
117
114
|
},
|
|
118
115
|
},
|
|
119
116
|
};
|
|
120
|
-
const tailCall = vi.fn(() => Promise.resolve({
|
|
117
|
+
const tailCall = vi.fn(() => Promise.resolve({
|
|
118
|
+
data: latestLogResponse,
|
|
119
|
+
status: 200,
|
|
120
|
+
statusText: 'OK',
|
|
121
|
+
headers: {},
|
|
122
|
+
config: { headers: {} },
|
|
123
|
+
}));
|
|
121
124
|
// @ts-ignore - headers is not used in the actual function and does not need to be mocked
|
|
122
|
-
|
|
123
|
-
vi.
|
|
125
|
+
const tailPromise = tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
|
|
126
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
124
127
|
expect(outputLogs).toHaveBeenCalledWith(latestLogResponse, expect.objectContaining({ compact }));
|
|
128
|
+
expect(tailCall).toHaveBeenCalledTimes(1);
|
|
129
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
125
130
|
expect(tailCall).toHaveBeenCalledTimes(2);
|
|
131
|
+
terminateTailLogs();
|
|
132
|
+
await tailPromise;
|
|
126
133
|
});
|
|
127
134
|
it('handles no logs', async () => {
|
|
128
135
|
const compact = false;
|
|
@@ -141,8 +148,7 @@ describe('lib/serverlessLogs', () => {
|
|
|
141
148
|
statusCode: 404,
|
|
142
149
|
}));
|
|
143
150
|
await tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
|
|
144
|
-
|
|
145
|
-
expect(tailCall).toHaveBeenCalledTimes(2);
|
|
151
|
+
expect(tailCall).toHaveBeenCalledTimes(1);
|
|
146
152
|
});
|
|
147
153
|
});
|
|
148
154
|
});
|
package/lib/constants.d.ts
CHANGED
|
@@ -81,7 +81,6 @@ export declare const FEATURES: {
|
|
|
81
81
|
readonly SANDBOXES_V2_CLI: "sandboxes:v2:cliEnabled";
|
|
82
82
|
readonly APP_EVENTS: "Developers:UnifiedApps:AppEventsAccess";
|
|
83
83
|
readonly APPS_HOME: "UIE:AppHome";
|
|
84
|
-
readonly MCP_ACCESS: "Developers:CLIMCPAccess";
|
|
85
84
|
readonly THEME_MIGRATION_2025_2: "Developers:ProjectThemeMigrations:2025.2";
|
|
86
85
|
readonly AGENT_TOOLS: "ThirdPartyAgentTools";
|
|
87
86
|
};
|
|
@@ -145,3 +144,4 @@ export declare const ACCOUNT_LEVELS: {
|
|
|
145
144
|
readonly ENTERPRISE: "ENTERPRISE";
|
|
146
145
|
};
|
|
147
146
|
export declare const ACCOUNT_LEVEL_CHOICES: readonly ["FREE", "STARTER", "PROFESSIONAL", "ENTERPRISE"];
|
|
147
|
+
export declare const FEEDBACK_URL = "https://developers.hubspot.com/feedback";
|
package/lib/constants.js
CHANGED
|
@@ -73,7 +73,6 @@ export const FEATURES = {
|
|
|
73
73
|
SANDBOXES_V2_CLI: 'sandboxes:v2:cliEnabled',
|
|
74
74
|
APP_EVENTS: 'Developers:UnifiedApps:AppEventsAccess',
|
|
75
75
|
APPS_HOME: 'UIE:AppHome',
|
|
76
|
-
MCP_ACCESS: 'Developers:CLIMCPAccess',
|
|
77
76
|
THEME_MIGRATION_2025_2: 'Developers:ProjectThemeMigrations:2025.2',
|
|
78
77
|
AGENT_TOOLS: 'ThirdPartyAgentTools',
|
|
79
78
|
};
|
|
@@ -146,3 +145,4 @@ export const ACCOUNT_LEVEL_CHOICES = [
|
|
|
146
145
|
ACCOUNT_LEVELS.PROFESSIONAL,
|
|
147
146
|
ACCOUNT_LEVELS.ENTERPRISE,
|
|
148
147
|
];
|
|
148
|
+
export const FEEDBACK_URL = 'https://developers.hubspot.com/feedback';
|
package/lib/generateSelectors.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import { EXIT_CODES } from './enums/exitCodes.js';
|
|
3
2
|
import { commands } from '../lang/en.js';
|
|
4
3
|
import { uiLogger } from './ui/logger.js';
|
|
5
4
|
const CSS_COMMENTS_REGEX = new RegExp(/\/\*.*\*\//, 'g');
|
|
@@ -12,7 +11,7 @@ export function findFieldsJsonPath(basePath) {
|
|
|
12
11
|
const _path = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;
|
|
13
12
|
if (!fs.existsSync(_path)) {
|
|
14
13
|
uiLogger.error(commands.cms.subcommands.theme.subcommands.generateSelectors.errors.invalidPath(basePath));
|
|
15
|
-
|
|
14
|
+
return null;
|
|
16
15
|
}
|
|
17
16
|
const files = fs.readdirSync(_path);
|
|
18
17
|
if (files.includes('fields.json')) {
|