@hubspot/cli 8.2.0-experimental.2 → 8.2.1-experimental.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/account/clean.js +1 -1
- package/commands/account/list.js +1 -1
- package/commands/account/remove.js +1 -1
- package/commands/account/rename.js +1 -1
- package/commands/account/use.js +1 -1
- package/commands/api.d.ts +4 -1
- package/commands/api.js +11 -14
- package/commands/config/set.js +24 -5
- package/commands/customObject/createSchema.js +2 -3
- package/commands/customObject/updateSchema.js +2 -3
- package/commands/getStarted.js +1 -0
- package/commands/hubdb/list.d.ts +1 -1
- package/commands/hubdb/list.js +7 -8
- package/commands/mcp/setup.d.ts +2 -0
- package/commands/mcp/setup.js +29 -1
- package/commands/project/appInstallStatus.d.ts +2 -1
- package/commands/project/appInstallStatus.js +21 -19
- package/commands/project/create.js +0 -1
- package/commands/project/deploy.d.ts +2 -1
- package/commands/project/deploy.js +31 -14
- package/commands/project/dev/index.js +18 -64
- package/commands/project/dev/unifiedFlow.js +3 -1
- package/commands/project/info.d.ts +2 -1
- package/commands/project/info.js +8 -6
- package/commands/project/installApp.d.ts +2 -1
- package/commands/project/installApp.js +26 -27
- package/commands/project/list.js +1 -1
- package/commands/project/listBuilds.d.ts +1 -1
- package/commands/project/listBuilds.js +8 -6
- package/commands/project/logs.js +2 -4
- package/commands/project/profile/add.d.ts +2 -1
- package/commands/project/profile/add.js +22 -1
- package/commands/project/profile/delete.d.ts +1 -1
- package/commands/project/release/create.d.ts +2 -1
- package/commands/project/release/create.js +68 -69
- package/commands/project/release/info.d.ts +2 -1
- package/commands/project/release/info.js +7 -6
- package/commands/project/release/list.d.ts +2 -1
- package/commands/project/release/list.js +15 -7
- package/commands/project/upload.d.ts +4 -1
- package/commands/project/upload.js +48 -21
- package/commands/project/validate.d.ts +1 -1
- package/commands/project/watch.js +3 -2
- package/commands/project.js +2 -2
- package/commands/sandbox/delete.js +2 -3
- package/commands/testAccount/create.d.ts +2 -1
- package/commands/testAccount/create.js +7 -7
- package/commands/testAccount/importData.d.ts +1 -1
- package/lang/en.d.ts +61 -33
- package/lang/en.js +77 -48
- package/lib/app/urls.js +2 -3
- package/lib/commonOpts.js +6 -1
- package/lib/configOptions.d.ts +10 -10
- package/lib/configOptions.js +10 -11
- package/lib/constants.d.ts +2 -0
- package/lib/constants.js +2 -0
- package/lib/dependencyManagement.js +7 -7
- package/lib/getStartedV2Actions.js +2 -1
- package/lib/importData.js +3 -4
- package/lib/jsonOutput.d.ts +109 -0
- package/lib/jsonOutput.js +94 -0
- package/lib/links.js +6 -5
- package/lib/mcp/clients.d.ts +1 -1
- package/lib/mcp/clients.js +8 -1
- package/lib/mcp/setup.d.ts +9 -4
- package/lib/mcp/setup.js +115 -35
- package/lib/npm/npmCli.d.ts +1 -0
- package/lib/npm/npmCli.js +13 -0
- package/lib/projects/builds.d.ts +2 -0
- package/lib/projects/builds.js +22 -0
- package/lib/projects/create/v2.js +1 -0
- package/lib/projects/installApp.d.ts +1 -1
- package/lib/projects/installApp.js +8 -3
- package/lib/projects/localDev/DevSessionManager.d.ts +2 -2
- package/lib/projects/localDev/DevSessionManager.js +4 -30
- package/lib/projects/localDev/LocalDevProcess.js +7 -5
- package/lib/projects/localDev/helpers/account.d.ts +0 -7
- package/lib/projects/localDev/helpers/account.js +1 -80
- package/lib/projects/localDev/helpers/project.js +3 -2
- package/lib/projects/release.d.ts +4 -0
- package/lib/projects/release.js +90 -0
- package/lib/projects/ui.d.ts +1 -0
- package/lib/projects/ui.js +14 -0
- package/lib/projects/upload.d.ts +10 -2
- package/lib/projects/upload.js +14 -7
- package/lib/projects/urls.d.ts +0 -1
- package/lib/projects/urls.js +6 -13
- package/lib/yargs/makeWrappedYargsHandler.d.ts +5 -1
- package/lib/yargs/makeWrappedYargsHandler.js +123 -65
- package/mcp-server/Tool.d.ts +8 -3
- package/mcp-server/Tool.js +58 -32
- package/mcp-server/server.js +10 -6
- package/mcp-server/tools/cms/HsCreateFunctionTool.d.ts +2 -2
- package/mcp-server/tools/cms/HsCreateFunctionTool.js +10 -17
- package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +2 -2
- package/mcp-server/tools/cms/HsCreateModuleTool.js +12 -18
- package/mcp-server/tools/cms/HsCreateTemplateTool.d.ts +2 -2
- package/mcp-server/tools/cms/HsCreateTemplateTool.js +8 -10
- package/mcp-server/tools/cms/HsFunctionLogsTool.d.ts +2 -2
- package/mcp-server/tools/cms/HsFunctionLogsTool.js +9 -9
- package/mcp-server/tools/cms/HsListFunctionsTool.d.ts +2 -2
- package/mcp-server/tools/cms/HsListFunctionsTool.js +7 -7
- package/mcp-server/tools/cms/HsListTool.d.ts +2 -2
- package/mcp-server/tools/cms/HsListTool.js +6 -10
- package/mcp-server/tools/index.js +2 -0
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +3 -2
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +8 -9
- package/mcp-server/tools/project/AuthAccountTool.d.ts +18 -0
- package/mcp-server/tools/project/AuthAccountTool.js +78 -0
- package/mcp-server/tools/project/CreateProjectTool.d.ts +3 -2
- package/mcp-server/tools/project/CreateProjectTool.js +13 -12
- package/mcp-server/tools/project/CreateTestAccountTool.d.ts +2 -2
- package/mcp-server/tools/project/CreateTestAccountTool.js +14 -15
- package/mcp-server/tools/project/DeployProjectTool.d.ts +2 -2
- package/mcp-server/tools/project/DeployProjectTool.js +10 -10
- package/mcp-server/tools/project/DocFetchTool.js +1 -1
- package/mcp-server/tools/project/DocsSearchTool.js +8 -7
- package/mcp-server/tools/project/FindProjectsTool.js +1 -1
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +2 -1
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +9 -8
- package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +2 -1
- package/mcp-server/tools/project/GetApplicationInfoTool.js +12 -9
- package/mcp-server/tools/project/GetBuildLogsTool.js +11 -7
- package/mcp-server/tools/project/GetBuildStatusTool.js +11 -7
- package/mcp-server/tools/project/GetConfigValuesTool.d.ts +2 -1
- package/mcp-server/tools/project/GetConfigValuesTool.js +9 -7
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -1
- package/mcp-server/tools/project/UploadProjectTools.d.ts +2 -2
- package/mcp-server/tools/project/UploadProjectTools.js +9 -8
- package/mcp-server/tools/project/ValidateProjectTool.d.ts +2 -2
- package/mcp-server/tools/project/ValidateProjectTool.js +5 -4
- package/mcp-server/tools/project/constants.d.ts +1 -0
- package/mcp-server/tools/project/constants.js +2 -1
- package/mcp-server/utils/command.d.ts +25 -2
- package/mcp-server/utils/command.js +67 -17
- package/mcp-server/utils/config.js +5 -0
- package/mcp-server/utils/logger.d.ts +3 -0
- package/mcp-server/utils/logger.js +30 -0
- package/mcp-server/utils/toolUsageTracking.js +18 -3
- package/package.json +11 -9
- package/types/Yargs.d.ts +3 -1
- package/commands/project/dev/deprecatedFlow.d.ts +0 -11
- package/commands/project/dev/deprecatedFlow.js +0 -180
- package/lib/projects/localDev/DevServerManager_DEPRECATED.d.ts +0 -43
- package/lib/projects/localDev/DevServerManager_DEPRECATED.js +0 -128
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.d.ts +0 -64
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +0 -394
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { fetchProjectBuilds, getBuildStatus, } from '@hubspot/local-dev-lib/api/projects';
|
|
3
|
+
import { BUILD_STATUS } from '@hubspot/local-dev-lib/enums/build';
|
|
4
|
+
import { isSpecifiedError } from '@hubspot/local-dev-lib/errors/index';
|
|
5
|
+
import { meetsMinimumPlatformVersion } from '@hubspot/project-parsing-lib/projects';
|
|
6
|
+
import { PLATFORM_VERSIONS } from '@hubspot/project-parsing-lib/constants';
|
|
7
|
+
import { getLastSuccessfulBuild } from './builds.js';
|
|
8
|
+
import { logError, ApiErrorContext } from '../errorHandlers/index.js';
|
|
9
|
+
import { uiLogger } from '../ui/logger.js';
|
|
10
|
+
import { listPrompt } from '../prompts/promptUtils.js';
|
|
11
|
+
import { commands } from '../../lang/en.js';
|
|
12
|
+
import { createRelease } from '../../api/releases.js';
|
|
13
|
+
function buildChoiceName(build) {
|
|
14
|
+
const base = build.uploadMessage
|
|
15
|
+
? `${build.buildId} — ${build.uploadMessage}`
|
|
16
|
+
: `${build.buildId} — ${commands.project.release.create.noUploadMessage}`;
|
|
17
|
+
return build.status !== BUILD_STATUS.SUCCESS
|
|
18
|
+
? `[${chalk.yellow('DISABLED')}] ${base}`
|
|
19
|
+
: base;
|
|
20
|
+
}
|
|
21
|
+
export async function resolveBuildId(accountId, projectName, buildOption, force) {
|
|
22
|
+
if (buildOption) {
|
|
23
|
+
return buildOption;
|
|
24
|
+
}
|
|
25
|
+
if (force) {
|
|
26
|
+
const lastSuccessful = await getLastSuccessfulBuild(accountId, projectName);
|
|
27
|
+
return lastSuccessful ? lastSuccessful.buildId : null;
|
|
28
|
+
}
|
|
29
|
+
let results;
|
|
30
|
+
try {
|
|
31
|
+
({
|
|
32
|
+
data: { results },
|
|
33
|
+
} = await fetchProjectBuilds(accountId, projectName));
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
if (isSpecifiedError(e, { statusCode: 404 }) ||
|
|
37
|
+
isSpecifiedError(e, { statusCode: 400 })) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
if (results.length === 0) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
const lastSuccessful = await getLastSuccessfulBuild(accountId, projectName);
|
|
46
|
+
if (!lastSuccessful) {
|
|
47
|
+
uiLogger.error(commands.project.release.create.errors.noSuccessfulBuilds(projectName));
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return listPrompt(commands.project.release.create.buildIdPrompt, {
|
|
51
|
+
choices: results.map(b => ({
|
|
52
|
+
name: buildChoiceName(b),
|
|
53
|
+
value: b.buildId,
|
|
54
|
+
disabled: b.status !== BUILD_STATUS.SUCCESS
|
|
55
|
+
? `– ${commands.project.release.create.buildStatus[b.status] ?? b.status}`
|
|
56
|
+
: undefined,
|
|
57
|
+
})),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export async function validateBuildForRelease(accountId, projectName, buildId) {
|
|
61
|
+
try {
|
|
62
|
+
const { data: build } = await getBuildStatus(accountId, projectName, buildId);
|
|
63
|
+
return meetsMinimumPlatformVersion(build.platformVersion, PLATFORM_VERSIONS.v2026_09_BETA);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
if (isSpecifiedError(e, { statusCode: 404 })) {
|
|
67
|
+
uiLogger.error(commands.project.release.create.errors.buildNotFound(buildId, projectName));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
logError(e, new ApiErrorContext({
|
|
71
|
+
accountId,
|
|
72
|
+
request: 'project release create',
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
throw e;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function executeRelease(accountId, projectName, buildId) {
|
|
79
|
+
try {
|
|
80
|
+
const { data: release } = await createRelease(accountId, projectName, buildId);
|
|
81
|
+
return release;
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
logError(e, new ApiErrorContext({
|
|
85
|
+
accountId,
|
|
86
|
+
request: 'project release create',
|
|
87
|
+
}));
|
|
88
|
+
throw e;
|
|
89
|
+
}
|
|
90
|
+
}
|
package/lib/projects/ui.d.ts
CHANGED
package/lib/projects/ui.js
CHANGED
|
@@ -2,6 +2,7 @@ import { uiLogger } from '../ui/logger.js';
|
|
|
2
2
|
import { FEEDBACK_INTERVAL } from '../constants.js';
|
|
3
3
|
import { uiLine } from '../ui/index.js';
|
|
4
4
|
import { lib } from '../../lang/en.js';
|
|
5
|
+
import { confirmPrompt } from '../prompts/promptUtils.js';
|
|
5
6
|
export function logFeedbackMessage(buildId) {
|
|
6
7
|
if (buildId > 0 && buildId % FEEDBACK_INTERVAL === 0) {
|
|
7
8
|
uiLine();
|
|
@@ -10,3 +11,16 @@ export function logFeedbackMessage(buildId) {
|
|
|
10
11
|
uiLogger.log(lib.projects.logFeedbackMessage.feedbackMessage);
|
|
11
12
|
}
|
|
12
13
|
}
|
|
14
|
+
export async function warnAboutSkippedHsMetaFiles(skippedHsMetaFiles, force, isUpload = true) {
|
|
15
|
+
if (skippedHsMetaFiles.length === 0)
|
|
16
|
+
return true;
|
|
17
|
+
const message = isUpload
|
|
18
|
+
? lib.projects.skippedHsMetaFiles.uploadWarning(skippedHsMetaFiles)
|
|
19
|
+
: lib.projects.skippedHsMetaFiles.warning(skippedHsMetaFiles);
|
|
20
|
+
uiLogger.warn(message);
|
|
21
|
+
if (force)
|
|
22
|
+
return true;
|
|
23
|
+
return confirmPrompt(lib.projects.skippedHsMetaFiles.prompt, {
|
|
24
|
+
defaultAnswer: false,
|
|
25
|
+
});
|
|
26
|
+
}
|
package/lib/projects/upload.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FileResult } from 'tmp';
|
|
2
|
+
import { type IntermediateRepresentation } from '@hubspot/project-parsing-lib/translate';
|
|
2
3
|
import { ProjectConfig } from '../../types/Projects.js';
|
|
3
4
|
type ProjectUploadCallbackFunction<T> = (accountId: number, projectConfig: ProjectConfig, tempFile: FileResult, buildId: number) => Promise<T>;
|
|
4
5
|
type ProjectUploadResult<T> = {
|
|
@@ -6,6 +7,7 @@ type ProjectUploadResult<T> = {
|
|
|
6
7
|
uploadError?: unknown;
|
|
7
8
|
projectNotFound?: boolean;
|
|
8
9
|
projectId?: number;
|
|
10
|
+
userDeclined?: boolean;
|
|
9
11
|
};
|
|
10
12
|
type HandleProjectUploadArg<T> = {
|
|
11
13
|
accountId: number;
|
|
@@ -18,9 +20,11 @@ type HandleProjectUploadArg<T> = {
|
|
|
18
20
|
sendIR?: boolean;
|
|
19
21
|
skipValidation?: boolean;
|
|
20
22
|
skipNpmAudit?: boolean;
|
|
23
|
+
skipAutoDeploy?: boolean;
|
|
21
24
|
profile?: string;
|
|
25
|
+
force?: boolean;
|
|
22
26
|
};
|
|
23
|
-
export declare function handleProjectUpload<T>({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage, forceCreate, isUploadCommand, sendIR, skipValidation, skipNpmAudit, }: HandleProjectUploadArg<T>): Promise<ProjectUploadResult<T>>;
|
|
27
|
+
export declare function handleProjectUpload<T>({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage, forceCreate, isUploadCommand, sendIR, skipValidation, skipNpmAudit, skipAutoDeploy, force, }: HandleProjectUploadArg<T>): Promise<ProjectUploadResult<T>>;
|
|
24
28
|
export declare function validateSourceDirectory(srcDir: string, projectConfig: ProjectConfig, projectDir: string): Promise<void>;
|
|
25
29
|
export declare function validateNoHSMetaMismatch(srcDir: string, projectConfig: ProjectConfig): Promise<void>;
|
|
26
30
|
type HandleTranslateArg = {
|
|
@@ -31,5 +35,9 @@ type HandleTranslateArg = {
|
|
|
31
35
|
profile?: string;
|
|
32
36
|
includeTranslationErrorMessage?: boolean;
|
|
33
37
|
};
|
|
34
|
-
export
|
|
38
|
+
export type HandleTranslateResult = {
|
|
39
|
+
intermediateRepresentation: IntermediateRepresentation;
|
|
40
|
+
skippedHsMetaFiles: string[];
|
|
41
|
+
};
|
|
42
|
+
export declare function handleTranslate({ projectDir, projectConfig, accountId, skipValidation, profile, includeTranslationErrorMessage, }: HandleTranslateArg): Promise<HandleTranslateResult>;
|
|
35
43
|
export {};
|
package/lib/projects/upload.js
CHANGED
|
@@ -9,6 +9,7 @@ import { projectContainsHsMetaFiles } from '@hubspot/project-parsing-lib/project
|
|
|
9
9
|
import { findAndParsePackageJsonFiles, collectWorkspaceDirectories, collectFileDependencies, } from '@hubspot/project-parsing-lib/workspaces';
|
|
10
10
|
import SpinniesManager from '../ui/SpinniesManager.js';
|
|
11
11
|
import { uiAccountDescription } from '../ui/index.js';
|
|
12
|
+
import { warnAboutSkippedHsMetaFiles } from './ui.js';
|
|
12
13
|
import util from 'node:util';
|
|
13
14
|
import { lib } from '../../lang/en.js';
|
|
14
15
|
import { ensureProjectExists } from './ensureProjectExists.js';
|
|
@@ -20,7 +21,7 @@ import { archiveWorkspacesAndDependencies, getPackageJsonPathsToUpdate, getLockf
|
|
|
20
21
|
import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
|
|
21
22
|
import { validateLintConfigOnUpload } from './validateLintConfigOnUpload.js';
|
|
22
23
|
import { runNpmAuditsBeforeProjectUpload } from './npmAuditOnUpload.js';
|
|
23
|
-
async function uploadProjectFiles(accountId, projectName, filePath, uploadMessage, platformVersion, intermediateRepresentation) {
|
|
24
|
+
async function uploadProjectFiles(accountId, projectName, filePath, uploadMessage, platformVersion, intermediateRepresentation, skipAutoDeploy) {
|
|
24
25
|
const accountIdentifier = uiAccountDescription(accountId) || `${accountId}`;
|
|
25
26
|
SpinniesManager.add('upload', {
|
|
26
27
|
text: lib.projectUpload.uploadProjectFiles.add(projectName, accountIdentifier),
|
|
@@ -29,7 +30,7 @@ async function uploadProjectFiles(accountId, projectName, filePath, uploadMessag
|
|
|
29
30
|
let buildId;
|
|
30
31
|
let error;
|
|
31
32
|
try {
|
|
32
|
-
const { data: upload } = await uploadProject(accountId, projectName, filePath, uploadMessage, platformVersion, intermediateRepresentation);
|
|
33
|
+
const { data: upload } = await uploadProject(accountId, projectName, filePath, uploadMessage, platformVersion, intermediateRepresentation, skipAutoDeploy);
|
|
33
34
|
buildId = upload.buildId;
|
|
34
35
|
SpinniesManager.succeed('upload', {
|
|
35
36
|
text: lib.projectUpload.uploadProjectFiles.succeed(projectName, accountIdentifier),
|
|
@@ -46,7 +47,7 @@ async function uploadProjectFiles(accountId, projectName, filePath, uploadMessag
|
|
|
46
47
|
}
|
|
47
48
|
return { buildId, error };
|
|
48
49
|
}
|
|
49
|
-
export async function handleProjectUpload({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage = '', forceCreate = false, isUploadCommand = false, sendIR = false, skipValidation = false, skipNpmAudit = false, }) {
|
|
50
|
+
export async function handleProjectUpload({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage = '', forceCreate = false, isUploadCommand = false, sendIR = false, skipValidation = false, skipNpmAudit = false, skipAutoDeploy = false, force = false, }) {
|
|
50
51
|
const srcDir = path.resolve(projectDir, projectConfig.srcDir);
|
|
51
52
|
await validateSourceDirectory(srcDir, projectConfig, projectDir);
|
|
52
53
|
await validateNoHSMetaMismatch(srcDir, projectConfig);
|
|
@@ -86,13 +87,19 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
|
|
|
86
87
|
let intermediateRepresentation;
|
|
87
88
|
if (sendIR) {
|
|
88
89
|
try {
|
|
89
|
-
|
|
90
|
+
const translateResult = await handleTranslate({
|
|
90
91
|
projectDir,
|
|
91
92
|
projectConfig,
|
|
92
93
|
accountId,
|
|
93
94
|
skipValidation,
|
|
94
95
|
profile,
|
|
95
96
|
});
|
|
97
|
+
intermediateRepresentation =
|
|
98
|
+
translateResult.intermediateRepresentation;
|
|
99
|
+
const shouldContinue = await warnAboutSkippedHsMetaFiles(translateResult.skippedHsMetaFiles, force);
|
|
100
|
+
if (!shouldContinue) {
|
|
101
|
+
return resolve({ userDeclined: true });
|
|
102
|
+
}
|
|
96
103
|
}
|
|
97
104
|
catch (e) {
|
|
98
105
|
return resolve({ uploadError: e });
|
|
@@ -108,7 +115,7 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
|
|
|
108
115
|
return resolve({ projectNotFound: true });
|
|
109
116
|
}
|
|
110
117
|
const projectId = project?.id;
|
|
111
|
-
const { buildId, error } = await uploadProjectFiles(accountId, projectConfig.name, tempFile.name, uploadMessage, projectConfig.platformVersion, intermediateRepresentation);
|
|
118
|
+
const { buildId, error } = await uploadProjectFiles(accountId, projectConfig.name, tempFile.name, uploadMessage, projectConfig.platformVersion, intermediateRepresentation, skipAutoDeploy);
|
|
112
119
|
if (error) {
|
|
113
120
|
resolve({ uploadError: error, projectId });
|
|
114
121
|
}
|
|
@@ -169,13 +176,13 @@ export async function validateNoHSMetaMismatch(srcDir, projectConfig) {
|
|
|
169
176
|
}
|
|
170
177
|
export async function handleTranslate({ projectDir, projectConfig, accountId, skipValidation, profile, includeTranslationErrorMessage = true, }) {
|
|
171
178
|
try {
|
|
172
|
-
const intermediateRepresentation = await translate({
|
|
179
|
+
const { intermediateRepresentation, skippedHsMetaFiles } = await translate({
|
|
173
180
|
projectSourceDir: path.join(projectDir, projectConfig.srcDir),
|
|
174
181
|
platformVersion: projectConfig.platformVersion,
|
|
175
182
|
accountId,
|
|
176
183
|
}, { skipValidation, profile });
|
|
177
184
|
uiLogger.debug(util.inspect(intermediateRepresentation, false, null, true));
|
|
178
|
-
return intermediateRepresentation;
|
|
185
|
+
return { intermediateRepresentation, skippedHsMetaFiles };
|
|
179
186
|
}
|
|
180
187
|
catch (e) {
|
|
181
188
|
if (isTranslationError(e)) {
|
package/lib/projects/urls.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export declare function getBaseHubSpotUrlForAccount(accountId: number): string;
|
|
2
1
|
export declare function getProjectComponentDistributionUrl(projectName: string, componentName: string, accountId: number): string;
|
|
3
2
|
export declare function getDeveloperOverviewUrl(accountId: number): string;
|
|
4
3
|
export declare function getProjectDetailUrl(projectName: string, accountId: number): string | undefined;
|
package/lib/projects/urls.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
|
|
3
|
-
export function getBaseHubSpotUrlForAccount(accountId) {
|
|
4
|
-
return getHubSpotWebsiteOrigin(getConfigAccountEnvironment(accountId));
|
|
5
|
-
}
|
|
1
|
+
import { getHubSpotWebsiteOriginByAccountId } from '@hubspot/local-dev-lib/urls';
|
|
6
2
|
function getProjectHomeUrl(accountId) {
|
|
7
|
-
return `${
|
|
3
|
+
return `${getHubSpotWebsiteOriginByAccountId(accountId)}/developer-projects/${accountId}`;
|
|
8
4
|
}
|
|
9
5
|
export function getProjectComponentDistributionUrl(projectName, componentName, accountId) {
|
|
10
|
-
|
|
11
|
-
return `${baseUrl}/developer-projects/${accountId}/project/${projectName}/component/${componentName}/distribution`;
|
|
6
|
+
return `${getHubSpotWebsiteOriginByAccountId(accountId)}/developer-projects/${accountId}/project/${projectName}/component/${componentName}/distribution`;
|
|
12
7
|
}
|
|
13
8
|
export function getDeveloperOverviewUrl(accountId) {
|
|
14
|
-
|
|
15
|
-
return `${baseUrl}/developer-overview/${accountId}`;
|
|
9
|
+
return `${getHubSpotWebsiteOriginByAccountId(accountId)}/developer-overview/${accountId}`;
|
|
16
10
|
}
|
|
17
11
|
export function getProjectDetailUrl(projectName, accountId) {
|
|
18
12
|
if (!projectName)
|
|
@@ -34,9 +28,8 @@ export function getProjectDeployDetailUrl(projectName, deployId, accountId) {
|
|
|
34
28
|
return `${getProjectActivityUrl(projectName, accountId)}/deploy/${deployId}`;
|
|
35
29
|
}
|
|
36
30
|
export function getLocalDevUiUrl(accountId, showWelcomeScreen) {
|
|
37
|
-
return `${
|
|
31
|
+
return `${getHubSpotWebsiteOriginByAccountId(accountId)}/developer-projects-local-dev/${accountId}${showWelcomeScreen ? '?welcome' : ''}`;
|
|
38
32
|
}
|
|
39
33
|
export function getAccountHomeUrl(accountId) {
|
|
40
|
-
|
|
41
|
-
return `${baseUrl}/home?portalId=${accountId}`;
|
|
34
|
+
return `${getHubSpotWebsiteOriginByAccountId(accountId)}/home?portalId=${accountId}`;
|
|
42
35
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import { ArgumentsCamelCase } from 'yargs';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { CommonArgs } from '../../types/Yargs.js';
|
|
3
|
-
export
|
|
4
|
+
export type WrappedHandlerOptions = {
|
|
5
|
+
jsonOutputSchema?: z.ZodType;
|
|
6
|
+
};
|
|
7
|
+
export declare function makeWrappedYargsHandler<T extends CommonArgs>(trackingName: string, handler: (args: ArgumentsCamelCase<T>) => Promise<void>, options?: WrappedHandlerOptions): (args: ArgumentsCamelCase<T>) => Promise<void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os from 'os';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
import { getConfig } from '@hubspot/local-dev-lib/config';
|
|
4
5
|
import { getStateValue, setStateValue, } from '@hubspot/local-dev-lib/config/state';
|
|
5
6
|
import { STATE_FLAGS } from '@hubspot/local-dev-lib/constants/config';
|
|
@@ -11,7 +12,8 @@ import { lib } from '../../lang/en.js';
|
|
|
11
12
|
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
12
13
|
import { isPromptExitError } from '../errors/PromptExitError.js';
|
|
13
14
|
import { debugError } from '../errorHandlers/index.js';
|
|
14
|
-
|
|
15
|
+
import { MAX_LOG_FILES } from '../constants.js';
|
|
16
|
+
const HANDLER_LOG_DIR = path.join(os.homedir(), '.hscli', 'logs', 'cli');
|
|
15
17
|
function logUsageTrackingMessage(isJsonOutput) {
|
|
16
18
|
if (isJsonOutput) {
|
|
17
19
|
return;
|
|
@@ -32,90 +34,146 @@ function logUsageTrackingMessage(isJsonOutput) {
|
|
|
32
34
|
return;
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
37
|
+
function createUsageTracker(trackingName, derivedAccountId) {
|
|
38
|
+
const startTime = Date.now();
|
|
39
|
+
const meta = {};
|
|
40
|
+
let fired = false;
|
|
41
|
+
const addMetadata = (newMeta) => {
|
|
42
|
+
Object.assign(meta, newMeta);
|
|
43
|
+
};
|
|
44
|
+
const track = async (successful) => {
|
|
45
|
+
if (fired) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
fired = true;
|
|
49
|
+
try {
|
|
50
|
+
const { accountId: overrideAccountId, ...trackingMeta } = meta;
|
|
51
|
+
trackingMeta.successful = successful;
|
|
52
|
+
trackingMeta.executionTime = Date.now() - startTime;
|
|
53
|
+
await _trackCommandUsage(trackingName, trackingMeta, overrideAccountId ?? derivedAccountId);
|
|
54
|
+
}
|
|
55
|
+
catch (_e) { }
|
|
56
|
+
};
|
|
57
|
+
const onForcedExit = () => {
|
|
58
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
59
|
+
};
|
|
60
|
+
const onSigint = async () => {
|
|
61
|
+
process.removeListener('SIGINT', onSigint);
|
|
62
|
+
process.on('SIGINT', onForcedExit);
|
|
63
|
+
try {
|
|
64
|
+
await track(false);
|
|
65
|
+
}
|
|
66
|
+
catch (_e) { }
|
|
67
|
+
process.removeListener('SIGINT', onForcedExit);
|
|
68
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
69
|
+
};
|
|
70
|
+
process.on('SIGINT', onSigint);
|
|
71
|
+
const trackAndCleanup = async (successful) => {
|
|
72
|
+
await track(successful);
|
|
73
|
+
process.removeListener('SIGINT', onSigint);
|
|
74
|
+
process.removeListener('SIGINT', onForcedExit);
|
|
75
|
+
};
|
|
76
|
+
return { addMetadata, trackAndCleanup };
|
|
77
|
+
}
|
|
78
|
+
function createJsonOutputManager(isJsonOutput, schema) {
|
|
79
|
+
const data = {};
|
|
80
|
+
let emitted = false;
|
|
81
|
+
const add = (newData) => {
|
|
82
|
+
Object.assign(data, newData);
|
|
83
|
+
};
|
|
84
|
+
const emit = () => {
|
|
85
|
+
if (!emitted && isJsonOutput && Object.keys(data).length > 0) {
|
|
86
|
+
emitted = true;
|
|
87
|
+
if (schema) {
|
|
88
|
+
const result = schema.safeParse(data);
|
|
89
|
+
if (!result.success) {
|
|
90
|
+
uiLogger.json(data);
|
|
91
|
+
uiLogger.warn(lib.jsonSchema.validationFailed);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
55
94
|
}
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
95
|
+
uiLogger.json(data);
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
};
|
|
99
|
+
return { add, emit };
|
|
100
|
+
}
|
|
101
|
+
function createLogFileWriter(trackingName, isJsonOutput) {
|
|
102
|
+
const writeLogFile = () => {
|
|
103
|
+
if (isJsonOutput) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return ldlLogger.writeBufferedLogsToFile({
|
|
107
|
+
dir: HANDLER_LOG_DIR,
|
|
108
|
+
filenamePrefix: trackingName,
|
|
109
|
+
maxFiles: MAX_LOG_FILES,
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
const writeFailureLogFile = () => {
|
|
113
|
+
const savedPath = writeLogFile();
|
|
114
|
+
if (savedPath) {
|
|
115
|
+
uiLogger.log('');
|
|
116
|
+
uiLogger.error(lib.handlerLogFile.saved(savedPath));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return { writeLogFile, writeFailureLogFile };
|
|
120
|
+
}
|
|
121
|
+
export function makeWrappedYargsHandler(trackingName, handler, options) {
|
|
122
|
+
return async (args) => {
|
|
123
|
+
const wrappedHandlerArgs = args;
|
|
124
|
+
const isJsonOutput = Boolean(wrappedHandlerArgs.json || wrappedHandlerArgs.formatOutputAsJson);
|
|
125
|
+
const schema = options?.jsonOutputSchema;
|
|
126
|
+
const tracker = createUsageTracker(trackingName, args.derivedAccountId);
|
|
127
|
+
if (wrappedHandlerArgs.jsonSchema) {
|
|
128
|
+
if (schema) {
|
|
129
|
+
uiLogger.json(z.toJSONSchema(schema));
|
|
66
130
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
process.exit(EXIT_CODES.SUCCESS);
|
|
70
|
-
};
|
|
71
|
-
process.on('SIGINT', onSigint);
|
|
72
|
-
const trackCommandUsageAndRemoveListeners = async (successful) => {
|
|
73
|
-
await trackCommandUsage(successful);
|
|
74
|
-
process.removeListener('SIGINT', onSigint);
|
|
75
|
-
process.removeListener('SIGINT', onForcedExit);
|
|
76
|
-
};
|
|
77
|
-
const jsonArgs = args;
|
|
78
|
-
const isJsonOutput = Boolean(jsonArgs.json || jsonArgs.formatOutputAsJson);
|
|
79
|
-
const writeFailureLogFile = () => {
|
|
80
|
-
// Skip in JSON output modes so the side effect + stderr message don't
|
|
81
|
-
// interfere with structured output consumers.
|
|
82
|
-
if (isJsonOutput) {
|
|
83
|
-
return;
|
|
131
|
+
else {
|
|
132
|
+
uiLogger.json({ error: lib.jsonSchema.noSchemaForCommand });
|
|
84
133
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
134
|
+
await tracker.trackAndCleanup(true);
|
|
135
|
+
return process.exit(EXIT_CODES.SUCCESS);
|
|
136
|
+
}
|
|
137
|
+
const json = createJsonOutputManager(isJsonOutput, schema);
|
|
138
|
+
const logs = createLogFileWriter(trackingName, isJsonOutput);
|
|
139
|
+
wrappedHandlerArgs.addUsageMetadata = tracker.addMetadata;
|
|
140
|
+
wrappedHandlerArgs.addJsonOutput = json.add;
|
|
141
|
+
wrappedHandlerArgs.exit = async (code) => {
|
|
142
|
+
const jsonValid = json.emit();
|
|
143
|
+
const exitCode = !jsonValid && code === EXIT_CODES.SUCCESS ? EXIT_CODES.WARNING : code;
|
|
144
|
+
await tracker.trackAndCleanup(exitCode !== EXIT_CODES.ERROR);
|
|
145
|
+
if (exitCode === EXIT_CODES.ERROR) {
|
|
146
|
+
logs.writeFailureLogFile();
|
|
92
147
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
await trackCommandUsageAndRemoveListeners(code !== EXIT_CODES.ERROR);
|
|
96
|
-
if (code === EXIT_CODES.ERROR) {
|
|
97
|
-
writeFailureLogFile();
|
|
148
|
+
else {
|
|
149
|
+
logs.writeLogFile();
|
|
98
150
|
}
|
|
99
|
-
return process.exit(
|
|
151
|
+
return process.exit(exitCode);
|
|
100
152
|
};
|
|
101
153
|
logUsageTrackingMessage(isJsonOutput);
|
|
102
154
|
try {
|
|
103
|
-
await handler(
|
|
155
|
+
await handler(wrappedHandlerArgs);
|
|
104
156
|
}
|
|
105
157
|
catch (e) {
|
|
106
158
|
const isSuccessfulPromptExit = isPromptExitError(e)
|
|
107
159
|
? e.exitCode !== EXIT_CODES.ERROR
|
|
108
160
|
: false;
|
|
109
|
-
await
|
|
161
|
+
await tracker.trackAndCleanup(isSuccessfulPromptExit);
|
|
110
162
|
if (isPromptExitError(e)) {
|
|
163
|
+
logs.writeLogFile();
|
|
111
164
|
return process.exit(e.exitCode);
|
|
112
165
|
}
|
|
113
166
|
else {
|
|
114
167
|
debugError(e);
|
|
115
|
-
writeFailureLogFile();
|
|
168
|
+
logs.writeFailureLogFile();
|
|
116
169
|
return process.exit(EXIT_CODES.ERROR);
|
|
117
170
|
}
|
|
118
171
|
}
|
|
119
|
-
|
|
172
|
+
const jsonValid = json.emit();
|
|
173
|
+
await tracker.trackAndCleanup(true);
|
|
174
|
+
logs.writeLogFile();
|
|
175
|
+
if (!jsonValid) {
|
|
176
|
+
return process.exit(EXIT_CODES.WARNING);
|
|
177
|
+
}
|
|
120
178
|
};
|
|
121
179
|
}
|
package/mcp-server/Tool.d.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
3
|
+
import { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
2
4
|
import { McpLogger } from './utils/logger.js';
|
|
3
5
|
import { TextContentResponse } from './types.js';
|
|
6
|
+
import { CommandResults, Command } from './utils/command.js';
|
|
7
|
+
export type ToolExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
|
4
8
|
export declare class Tool<InputSchema> {
|
|
5
9
|
protected mcpServer: McpServer;
|
|
6
10
|
protected logger: McpLogger;
|
|
7
11
|
protected toolName: string;
|
|
8
12
|
constructor(mcpServer: McpServer, logger: McpLogger, toolName: string);
|
|
9
13
|
register(): RegisteredTool;
|
|
10
|
-
handler(
|
|
11
|
-
protected
|
|
14
|
+
handler(_input: InputSchema): TextContentResponse | Promise<TextContentResponse>;
|
|
15
|
+
protected runCommand(directory: string, command: Command, extra?: ToolExtra): Promise<CommandResults>;
|
|
16
|
+
protected getTrackingMeta(_input: InputSchema): {
|
|
12
17
|
[key: string]: string;
|
|
13
18
|
} | undefined;
|
|
14
|
-
protected wrappedHandler(input: InputSchema): Promise<TextContentResponse>;
|
|
19
|
+
protected wrappedHandler(input: InputSchema, extra?: ToolExtra): Promise<TextContentResponse>;
|
|
15
20
|
}
|
package/mcp-server/Tool.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { formatTextContents } from './utils/content.js';
|
|
2
2
|
import { getErrorMessage } from '../lib/errorHandlers/index.js';
|
|
3
3
|
import { trackToolUsage } from './utils/toolUsageTracking.js';
|
|
4
|
+
import { runCommandInDir } from './utils/command.js';
|
|
4
5
|
export class Tool {
|
|
5
6
|
mcpServer;
|
|
6
7
|
logger;
|
|
@@ -13,41 +14,66 @@ export class Tool {
|
|
|
13
14
|
register() {
|
|
14
15
|
throw new Error('Must implement register');
|
|
15
16
|
}
|
|
16
|
-
handler(
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
18
|
-
input) {
|
|
17
|
+
handler(_input) {
|
|
19
18
|
throw new Error('Must implement handler');
|
|
20
19
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
runCommand(directory, command, extra) {
|
|
21
|
+
let progressCount = 0;
|
|
22
|
+
return runCommandInDir(directory, command, async (chunk) => {
|
|
23
|
+
try {
|
|
24
|
+
const message = `${chunk.trimEnd()}`;
|
|
25
|
+
this.logger.debug(this.toolName, message);
|
|
26
|
+
const token = extra?._meta?.progressToken;
|
|
27
|
+
if (token !== undefined && extra?.sendNotification) {
|
|
28
|
+
progressCount++;
|
|
29
|
+
await extra.sendNotification({
|
|
30
|
+
method: 'notifications/progress',
|
|
31
|
+
params: {
|
|
32
|
+
progressToken: token,
|
|
33
|
+
progress: progressCount,
|
|
34
|
+
message,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Swallow notification errors so a failed notification never crashes the tool
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
getTrackingMeta(_input) {
|
|
24
45
|
return undefined;
|
|
25
46
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
wrappedHandler(input, extra) {
|
|
48
|
+
return this.logger.runWithBuffer(async () => {
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
try {
|
|
51
|
+
// `input` is logged unredacted. Tool input schemas MUST NOT include
|
|
52
|
+
// credentials or other sensitive values, since MCP clients (Claude
|
|
53
|
+
// Desktop, Inspector, etc.) will display these logs.
|
|
54
|
+
this.logger.debug(this.toolName, {
|
|
55
|
+
message: 'Tool invoked',
|
|
56
|
+
args: input,
|
|
57
|
+
});
|
|
58
|
+
await trackToolUsage(this.toolName, this.getTrackingMeta(input));
|
|
59
|
+
const result = await this.handler(input, extra);
|
|
60
|
+
this.logger.debug(this.toolName, {
|
|
61
|
+
message: 'Tool completed',
|
|
62
|
+
durationMs: Date.now() - startTime,
|
|
63
|
+
});
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
this.logger.error(this.toolName, {
|
|
68
|
+
message: 'Tool failed',
|
|
69
|
+
error: error instanceof Error ? error.message : String(error),
|
|
70
|
+
durationMs: Date.now() - startTime,
|
|
71
|
+
});
|
|
72
|
+
return formatTextContents(getErrorMessage(error));
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
this.logger.flushLogsToFile(this.toolName);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
52
78
|
}
|
|
53
79
|
}
|