@hubspot/cli 8.0.11-experimental.1 → 8.0.12-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/api/migrate.js +3 -3
- package/bin/cli.js +3 -0
- package/commands/account/link.d.ts +4 -0
- package/commands/account/link.js +88 -0
- package/commands/account/unlink.d.ts +4 -0
- package/commands/account/unlink.js +69 -0
- package/commands/account.js +4 -0
- package/commands/app/migrate.js +4 -4
- package/commands/getStarted.js +2 -2
- package/commands/project/add.js +3 -3
- package/commands/project/appInstallStatus.d.ts +4 -0
- package/commands/project/appInstallStatus.js +132 -0
- package/commands/project/create.js +18 -7
- package/commands/project/delete.js +2 -2
- package/commands/project/deploy.js +4 -3
- package/commands/project/dev/index.js +5 -4
- package/commands/project/info.js +2 -2
- package/commands/project/lint.js +2 -1
- package/commands/project/migrate.js +5 -5
- package/commands/project/profile/add.js +2 -2
- package/commands/project/profile/delete.js +2 -2
- package/commands/project/upload.d.ts +2 -0
- package/commands/project/upload.js +38 -6
- package/commands/project/validate.js +2 -2
- package/commands/project/watch.js +2 -2
- package/commands/project.js +8 -3
- package/commands/testAccount/create.js +4 -4
- package/lang/en.d.ts +95 -0
- package/lang/en.js +108 -8
- package/lib/app/migrate.js +8 -0
- package/lib/doctor/Doctor.js +2 -2
- package/lib/getStartedV2Actions.js +2 -2
- package/lib/link/accountTableUtils.d.ts +10 -0
- package/lib/link/accountTableUtils.js +39 -0
- package/lib/link/index.d.ts +14 -0
- package/lib/link/index.js +154 -0
- package/lib/link/linkUtils.d.ts +4 -0
- package/lib/link/linkUtils.js +24 -0
- package/lib/link/prompts.d.ts +7 -0
- package/lib/link/prompts.js +126 -0
- package/lib/link/renderLinkedAccountsTable.d.ts +2 -0
- package/lib/link/renderLinkedAccountsTable.js +14 -0
- package/lib/projects/ProjectLogsManager.js +6 -3
- package/lib/projects/create/index.js +2 -2
- package/lib/projects/create/legacy.js +2 -2
- package/lib/projects/create/v2.js +2 -2
- package/lib/projects/delete.js +2 -2
- package/lib/projects/deploy.d.ts +1 -1
- package/lib/projects/deploy.js +2 -2
- package/lib/projects/preview.d.ts +7 -0
- package/lib/projects/preview.js +71 -0
- package/lib/projects/uieLinting.d.ts +8 -3
- package/lib/projects/uieLinting.js +48 -27
- package/lib/projects/upload.d.ts +1 -0
- package/lib/projects/upload.js +8 -7
- package/lib/prompts/projectsLogsPrompt.js +3 -0
- package/lib/prompts/promptUtils.js +1 -0
- package/lib/theme/cmsDevServerProcess.js +1 -1
- package/mcp-server/Tool.d.ts +15 -0
- package/mcp-server/Tool.js +53 -0
- package/mcp-server/server.js +39 -3
- package/mcp-server/tools/cms/HsCreateFunctionTool.d.ts +4 -2
- package/mcp-server/tools/cms/HsCreateFunctionTool.js +8 -6
- package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +4 -2
- package/mcp-server/tools/cms/HsCreateModuleTool.js +8 -6
- package/mcp-server/tools/cms/HsCreateTemplateTool.d.ts +4 -2
- package/mcp-server/tools/cms/HsCreateTemplateTool.js +8 -6
- package/mcp-server/tools/cms/HsFunctionLogsTool.d.ts +4 -2
- package/mcp-server/tools/cms/HsFunctionLogsTool.js +8 -6
- package/mcp-server/tools/cms/HsListFunctionsTool.d.ts +4 -2
- package/mcp-server/tools/cms/HsListFunctionsTool.js +8 -6
- package/mcp-server/tools/cms/HsListTool.d.ts +4 -2
- package/mcp-server/tools/cms/HsListTool.js +8 -6
- package/mcp-server/tools/index.d.ts +3 -2
- package/mcp-server/tools/index.js +22 -22
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +6 -4
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +8 -6
- package/mcp-server/tools/project/CreateProjectTool.d.ts +6 -4
- package/mcp-server/tools/project/CreateProjectTool.js +9 -7
- package/mcp-server/tools/project/CreateTestAccountTool.d.ts +4 -2
- package/mcp-server/tools/project/CreateTestAccountTool.js +20 -8
- package/mcp-server/tools/project/DeployProjectTool.d.ts +4 -2
- package/mcp-server/tools/project/DeployProjectTool.js +4 -6
- package/mcp-server/tools/project/DocFetchTool.d.ts +4 -2
- package/mcp-server/tools/project/DocFetchTool.js +8 -6
- package/mcp-server/tools/project/DocsSearchTool.d.ts +9 -3
- package/mcp-server/tools/project/DocsSearchTool.js +32 -9
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +4 -2
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +8 -6
- package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +4 -2
- package/mcp-server/tools/project/GetApplicationInfoTool.js +8 -6
- package/mcp-server/tools/project/GetBuildLogsTool.d.ts +4 -2
- package/mcp-server/tools/project/GetBuildLogsTool.js +8 -6
- package/mcp-server/tools/project/GetBuildStatusTool.d.ts +4 -2
- package/mcp-server/tools/project/GetBuildStatusTool.js +8 -6
- package/mcp-server/tools/project/GetConfigValuesTool.d.ts +4 -2
- package/mcp-server/tools/project/GetConfigValuesTool.js +12 -7
- package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +4 -2
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +4 -6
- package/mcp-server/tools/project/UploadProjectTools.d.ts +4 -2
- package/mcp-server/tools/project/UploadProjectTools.js +9 -7
- package/mcp-server/tools/project/ValidateProjectTool.d.ts +4 -2
- package/mcp-server/tools/project/ValidateProjectTool.js +8 -6
- package/mcp-server/tools/project/constants.d.ts +2 -2
- package/mcp-server/types.d.ts +0 -7
- package/mcp-server/types.js +1 -13
- package/mcp-server/utils/logger.d.ts +10 -0
- package/mcp-server/utils/logger.js +29 -0
- package/mcp-server/utils/toolUsageTracking.js +0 -2
- package/package.json +10 -10
- package/types/Link.d.ts +27 -0
- package/types/Link.js +1 -0
- package/types/Prompts.d.ts +1 -0
- package/lib/projects/platformVersion.d.ts +0 -9
- package/lib/projects/platformVersion.js +0 -39
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { promptUser } from '../prompts/promptUtils.js';
|
|
2
|
+
import { uiAccountDescription } from '../ui/index.js';
|
|
3
|
+
import { uiLogger } from '../ui/logger.js';
|
|
4
|
+
import { commands } from '../../lang/en.js';
|
|
5
|
+
import { buildAccountRow, getNameColumnWidth, buildAccountHeader, sortDefaultFirst, } from './accountTableUtils.js';
|
|
6
|
+
import { Separator } from '@inquirer/prompts';
|
|
7
|
+
function buildColumnarChoices(accounts, localDefaultAccount) {
|
|
8
|
+
const rows = accounts.map(a => ({
|
|
9
|
+
...buildAccountRow(a.accountId, a.accountId === localDefaultAccount),
|
|
10
|
+
disabled: a.disabled,
|
|
11
|
+
checked: a.checked,
|
|
12
|
+
hint: a.hint,
|
|
13
|
+
}));
|
|
14
|
+
const nameWidth = getNameColumnWidth(rows);
|
|
15
|
+
const header = buildAccountHeader(nameWidth);
|
|
16
|
+
return [
|
|
17
|
+
new Separator(header),
|
|
18
|
+
...rows.map(row => {
|
|
19
|
+
const label = `${row.name.padEnd(nameWidth)} ${row.accountId}`;
|
|
20
|
+
return {
|
|
21
|
+
name: row.hint ? `${label} ${row.hint}` : label,
|
|
22
|
+
short: uiAccountDescription(Number(row.accountId), false),
|
|
23
|
+
value: Number(row.accountId),
|
|
24
|
+
disabled: row.disabled,
|
|
25
|
+
checked: row.checked,
|
|
26
|
+
};
|
|
27
|
+
}),
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
function mapLinkAccountChoices(eligibleAccounts, inEligibleAccounts, accountOverrideId, localDefaultAccount, preselectedAccountId) {
|
|
31
|
+
const sortedIneligible = sortDefaultFirst(inEligibleAccounts, localDefaultAccount);
|
|
32
|
+
const accounts = [
|
|
33
|
+
...eligibleAccounts.map(a => ({
|
|
34
|
+
accountId: a.accountId,
|
|
35
|
+
disabled: false,
|
|
36
|
+
checked: a.accountId === accountOverrideId ||
|
|
37
|
+
a.accountId === preselectedAccountId,
|
|
38
|
+
hint: a.accountId === accountOverrideId
|
|
39
|
+
? commands.account.subcommands.link.prompts.fromHsAccount
|
|
40
|
+
: a.accountId === preselectedAccountId
|
|
41
|
+
? commands.account.subcommands.link.prompts.newlyAuthenticated
|
|
42
|
+
: undefined,
|
|
43
|
+
})),
|
|
44
|
+
...sortedIneligible.map(a => ({
|
|
45
|
+
accountId: a.accountId,
|
|
46
|
+
disabled: commands.account.subcommands.link.prompts.alreadyLinked,
|
|
47
|
+
checked: false,
|
|
48
|
+
})),
|
|
49
|
+
];
|
|
50
|
+
return buildColumnarChoices(accounts, localDefaultAccount);
|
|
51
|
+
}
|
|
52
|
+
export async function promptForAction(state) {
|
|
53
|
+
const isSettingsEmpty = state.accounts.length === 0 && state.localDefaultAccount === undefined;
|
|
54
|
+
const { accountEditOption } = await promptUser({
|
|
55
|
+
type: 'list',
|
|
56
|
+
name: 'accountEditOption',
|
|
57
|
+
message: isSettingsEmpty
|
|
58
|
+
? commands.account.subcommands.link.prompts.howToProceed
|
|
59
|
+
: commands.account.subcommands.link.prompts.whatToDo,
|
|
60
|
+
choices: [
|
|
61
|
+
{
|
|
62
|
+
name: commands.account.subcommands.link.prompts.linkExisting,
|
|
63
|
+
value: 'link',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: commands.account.subcommands.link.prompts.authenticateNew,
|
|
67
|
+
value: 'authenticate',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: commands.account.subcommands.link.prompts.cancel,
|
|
71
|
+
value: 'cancel',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
uiLogger.log('');
|
|
76
|
+
return accountEditOption;
|
|
77
|
+
}
|
|
78
|
+
export async function promptForDefaultAccount(accounts, currentDefaultAccount, prompt = '') {
|
|
79
|
+
const choiceAccounts = accounts.map(accountId => ({
|
|
80
|
+
accountId,
|
|
81
|
+
}));
|
|
82
|
+
const choices = buildColumnarChoices(choiceAccounts, currentDefaultAccount);
|
|
83
|
+
const { defaultAccount } = await promptUser({
|
|
84
|
+
type: 'list',
|
|
85
|
+
name: 'defaultAccount',
|
|
86
|
+
pageSize: 20,
|
|
87
|
+
message: prompt || commands.account.subcommands.link.prompts.selectDefault,
|
|
88
|
+
choices,
|
|
89
|
+
default: currentDefaultAccount ?? undefined,
|
|
90
|
+
});
|
|
91
|
+
uiLogger.log('');
|
|
92
|
+
return defaultAccount;
|
|
93
|
+
}
|
|
94
|
+
export async function promptForAccountsToLink(context, eligibleAccounts, inEligibleAccounts, localDefaultAccount) {
|
|
95
|
+
const { accountsToAdd } = await promptUser({
|
|
96
|
+
type: 'checkbox',
|
|
97
|
+
name: 'accountsToAdd',
|
|
98
|
+
pageSize: 20,
|
|
99
|
+
message: commands.account.subcommands.link.prompts.selectToLink,
|
|
100
|
+
choices: mapLinkAccountChoices(eligibleAccounts, inEligibleAccounts, context.accountOverrideId, localDefaultAccount, context.preselectedAccountId),
|
|
101
|
+
validate: (answer) => {
|
|
102
|
+
if (answer.length === 0) {
|
|
103
|
+
return commands.account.subcommands.link.prompts.mustSelectOne;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
uiLogger.log('');
|
|
109
|
+
return accountsToAdd;
|
|
110
|
+
}
|
|
111
|
+
export async function promptForAccountsToUnlink(accounts, localDefaultAccount) {
|
|
112
|
+
const sortedAccounts = sortDefaultFirst(accounts, localDefaultAccount);
|
|
113
|
+
const choiceAccounts = sortedAccounts.map(accountId => ({
|
|
114
|
+
accountId,
|
|
115
|
+
}));
|
|
116
|
+
const choices = buildColumnarChoices(choiceAccounts, localDefaultAccount);
|
|
117
|
+
const { accountsToRemove } = await promptUser({
|
|
118
|
+
type: 'checkbox',
|
|
119
|
+
name: 'accountsToRemove',
|
|
120
|
+
pageSize: 20,
|
|
121
|
+
message: commands.account.subcommands.link.prompts.selectToUnlink,
|
|
122
|
+
choices,
|
|
123
|
+
});
|
|
124
|
+
uiLogger.log('');
|
|
125
|
+
return accountsToRemove;
|
|
126
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { commands } from '../../lang/en.js';
|
|
2
|
+
import { renderTable } from '../../ui/render.js';
|
|
3
|
+
import { buildAccountRow, sortDefaultFirst } from './accountTableUtils.js';
|
|
4
|
+
export async function renderLinkedAccountsTable(settings) {
|
|
5
|
+
const labels = commands.account.subcommands.list.labels;
|
|
6
|
+
const tableHeader = [labels.name, labels.accountId];
|
|
7
|
+
const sortedAccounts = sortDefaultFirst(settings.accounts, settings.localDefaultAccount);
|
|
8
|
+
const tableData = sortedAccounts.map(accountId => {
|
|
9
|
+
const isDefault = accountId === settings.localDefaultAccount;
|
|
10
|
+
const row = buildAccountRow(accountId, isDefault);
|
|
11
|
+
return [row.name, row.accountId];
|
|
12
|
+
});
|
|
13
|
+
await renderTable(tableHeader, tableData, true);
|
|
14
|
+
}
|
|
@@ -4,7 +4,7 @@ import { fetchProjectComponentsMetadata } from '@hubspot/local-dev-lib/api/proje
|
|
|
4
4
|
import { fetchAppMetadataBySourceId } from '@hubspot/local-dev-lib/api/appsDev';
|
|
5
5
|
import { uiLogger } from '../ui/logger.js';
|
|
6
6
|
import { commands } from '../../lang/en.js';
|
|
7
|
-
import {
|
|
7
|
+
import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
|
|
8
8
|
import { getDeployedProjectNodes } from './localDev/helpers/project.js';
|
|
9
9
|
import { debugError } from '../errorHandlers/index.js';
|
|
10
10
|
class _ProjectLogsManager {
|
|
@@ -51,7 +51,7 @@ class _ProjectLogsManager {
|
|
|
51
51
|
throw new Error(commands.project.logs.errors.failedToFetchProjectDetails);
|
|
52
52
|
}
|
|
53
53
|
this.projectId = project.id;
|
|
54
|
-
if (
|
|
54
|
+
if (!isLegacyProject(projectConfig.platformVersion)) {
|
|
55
55
|
const deployedBuildId = project.deployedBuild.buildId;
|
|
56
56
|
if (!deployedBuildId) {
|
|
57
57
|
throw new Error(commands.project.logs.errors.noDeployedBuild);
|
|
@@ -134,9 +134,12 @@ class _ProjectLogsManager {
|
|
|
134
134
|
return this.functions.map(serverlessFunction => serverlessFunction.componentName);
|
|
135
135
|
}
|
|
136
136
|
setFunction(functionName) {
|
|
137
|
-
if (
|
|
137
|
+
if (this.functions.length === 0) {
|
|
138
138
|
throw new Error(commands.project.logs.errors.noFunctionsInProject);
|
|
139
139
|
}
|
|
140
|
+
if (!functionName) {
|
|
141
|
+
throw new Error(commands.project.logs.errors.functionNameRequired);
|
|
142
|
+
}
|
|
140
143
|
this.selectedFunction = this.functions.find(serverlessFunction => serverlessFunction.componentName === functionName);
|
|
141
144
|
if (!this.selectedFunction) {
|
|
142
145
|
throw new Error(commands.project.logs.errors.noFunctionWithName(functionName));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { selectProjectTemplatePrompt, } from '../../prompts/selectProjectTemplatePrompt.js';
|
|
2
2
|
import { projectNameAndDestPrompt } from '../../prompts/projectNameAndDestPrompt.js';
|
|
3
3
|
import { DEFAULT_PROJECT_TEMPLATE_BRANCH, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, EMPTY_PROJECT, } from '../../constants.js';
|
|
4
|
-
import {
|
|
4
|
+
import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
|
|
5
5
|
import { v2ComponentFlow } from './v2.js';
|
|
6
6
|
import { getProjectTemplateListFromRepo } from './legacy.js';
|
|
7
7
|
import { commands } from '../../../lang/en.js';
|
|
@@ -9,7 +9,7 @@ export async function handleProjectCreationFlow(args) {
|
|
|
9
9
|
const { platformVersion, templateSource, projectBase, auth: providedAuth, distribution: providedDistribution, } = args;
|
|
10
10
|
const repo = templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH;
|
|
11
11
|
const projectNameAndDestPromptResponse = await projectNameAndDestPrompt(args);
|
|
12
|
-
if (
|
|
12
|
+
if (!isLegacyProject(platformVersion)) {
|
|
13
13
|
const { componentTemplateChoices, authType, distribution, repoConfig, projectContents, } = await v2ComponentFlow(platformVersion, projectBase, providedAuth, providedDistribution, args.derivedAccountId);
|
|
14
14
|
const selectProjectTemplatePromptResponse = await selectProjectTemplatePrompt(args, undefined, projectContents !== EMPTY_PROJECT ? componentTemplateChoices : undefined);
|
|
15
15
|
return {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { fetchRepoFile } from '@hubspot/local-dev-lib/api/github';
|
|
2
2
|
import { DEFAULT_PROJECT_TEMPLATE_BRANCH, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, PROJECT_COMPONENT_TYPES, } from '../../constants.js';
|
|
3
3
|
import { debugError } from '../../errorHandlers/index.js';
|
|
4
|
-
import {
|
|
4
|
+
import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
|
|
5
5
|
import { lib } from '../../../lang/en.js';
|
|
6
6
|
const PROJECT_TEMPLATE_PROPERTIES = ['name', 'label', 'path'];
|
|
7
7
|
export const EMPTY_PROJECT_TEMPLATE_NAME = 'no-template';
|
|
8
8
|
export async function getConfigForPlatformVersion(platformVersion) {
|
|
9
9
|
let path = '';
|
|
10
|
-
if (
|
|
10
|
+
if (!isLegacyProject(platformVersion)) {
|
|
11
11
|
path = `${platformVersion}/`;
|
|
12
12
|
}
|
|
13
13
|
const { data } = await fetchRepoFile(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, `${path}config.json`, DEFAULT_PROJECT_TEMPLATE_BRANCH);
|
|
@@ -2,7 +2,7 @@ import { marketplaceDistribution, oAuth, privateDistribution, staticAuth, EMPTY_
|
|
|
2
2
|
import { commands, lib } from '../../../lang/en.js';
|
|
3
3
|
import { listPrompt } from '../../prompts/promptUtils.js';
|
|
4
4
|
import { APP_EVENTS_KEY as AppEventsKey } from '@hubspot/project-parsing-lib/constants';
|
|
5
|
-
import {
|
|
5
|
+
import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { getConfigForPlatformVersion } from './legacy.js';
|
|
8
8
|
import { hasFeature } from '../../hasFeature.js';
|
|
@@ -134,7 +134,7 @@ export async function v2ComponentFlow(platformVersion, projectBase, providedAuth
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
export function generateComponentPaths({ selectProjectTemplatePromptResponse, platformVersion, repoConfig, projectContents, authType, distribution, }) {
|
|
137
|
-
if (
|
|
137
|
+
if (isLegacyProject(platformVersion)) {
|
|
138
138
|
return [];
|
|
139
139
|
}
|
|
140
140
|
const components = selectProjectTemplatePromptResponse.componentTemplates?.map((componentTemplate) => {
|
package/lib/projects/delete.js
CHANGED
|
@@ -10,7 +10,7 @@ import { PromptExitError } from '../errors/PromptExitError.js';
|
|
|
10
10
|
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
11
11
|
import { AUTO_GENERATED_COMPONENT_TYPES } from '@hubspot/project-parsing-lib/constants';
|
|
12
12
|
import { mapToUserFacingType } from '@hubspot/project-parsing-lib/transform';
|
|
13
|
-
import {
|
|
13
|
+
import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
|
|
14
14
|
import { COMPONENT_TYPES, SUBCOMPONENT_TYPES, } from '@hubspot/local-dev-lib/enums/build';
|
|
15
15
|
export const DELETION_POLL_TIMEOUT_MS = 5 * 60 * 1000;
|
|
16
16
|
export const DELETION_DEPLOY_SUCCESS_STATES = [
|
|
@@ -81,7 +81,7 @@ export async function checkDeployedComponents(accountId, projectName) {
|
|
|
81
81
|
if (!platformVersion) {
|
|
82
82
|
throw new Error(commands.project.delete.errors.noPlatformVersion);
|
|
83
83
|
}
|
|
84
|
-
if (
|
|
84
|
+
if (isLegacyProject(platformVersion)) {
|
|
85
85
|
const userVisibleComponents = [];
|
|
86
86
|
projectData.deployedBuild?.subbuildStatuses?.forEach(item => {
|
|
87
87
|
if (LEGACY_COMPONENTS_TO_FILTER.includes(item.buildType)) {
|
package/lib/projects/deploy.d.ts
CHANGED
|
@@ -10,4 +10,4 @@ export declare function logDeployErrors(errorData: {
|
|
|
10
10
|
};
|
|
11
11
|
}>;
|
|
12
12
|
}): void;
|
|
13
|
-
export declare function handleProjectDeploy(targetAccountId: number, projectName: string, buildId: number,
|
|
13
|
+
export declare function handleProjectDeploy(targetAccountId: number, projectName: string, buildId: number, isLegacyProject: boolean, force: boolean): Promise<Deploy | undefined>;
|
package/lib/projects/deploy.js
CHANGED
|
@@ -47,9 +47,9 @@ function handleBlockedDeploy(deployResp) {
|
|
|
47
47
|
uiLogger.log('');
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
-
export async function handleProjectDeploy(targetAccountId, projectName, buildId,
|
|
50
|
+
export async function handleProjectDeploy(targetAccountId, projectName, buildId, isLegacyProject, force) {
|
|
51
51
|
let deployId;
|
|
52
|
-
if (
|
|
52
|
+
if (!isLegacyProject) {
|
|
53
53
|
const { data: deployResp } = await deployProjectV2(targetAccountId, projectName, buildId, force);
|
|
54
54
|
if (!deployResp || deployResp.buildResultType !== 'DEPLOY_QUEUED') {
|
|
55
55
|
if (deployResp?.buildResultType === 'DEPLOY_BLOCKED') {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { triggerAutoRelease, getAutoReleaseStatus, } from '@hubspot/local-dev-lib/api/projects';
|
|
2
|
+
import { DEFAULT_POLLING_DELAY } from '../constants.js';
|
|
3
|
+
import SpinniesManager from '../ui/SpinniesManager.js';
|
|
4
|
+
import { logError, ApiErrorContext } from '../errorHandlers/index.js';
|
|
5
|
+
import { lib } from '../../lang/en.js';
|
|
6
|
+
const PREVIEW_POLL_TIMEOUT = 5 * 60 * 1000;
|
|
7
|
+
export async function triggerAndPollPreview(accountId, projectId, buildId, targetPortalId) {
|
|
8
|
+
let triggerResponse;
|
|
9
|
+
SpinniesManager.add('preview', {
|
|
10
|
+
text: lib.projectPreview.triggeringPreview(buildId, targetPortalId),
|
|
11
|
+
succeedColor: 'white',
|
|
12
|
+
});
|
|
13
|
+
try {
|
|
14
|
+
const { data } = await triggerAutoRelease(accountId, projectId, buildId, targetPortalId);
|
|
15
|
+
triggerResponse = data;
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
SpinniesManager.fail('preview', {
|
|
19
|
+
text: lib.projectPreview.triggerFailed,
|
|
20
|
+
});
|
|
21
|
+
logError(e, new ApiErrorContext({
|
|
22
|
+
accountId,
|
|
23
|
+
request: 'preview trigger',
|
|
24
|
+
}));
|
|
25
|
+
return { succeeded: false };
|
|
26
|
+
}
|
|
27
|
+
const { releaseTag, appId } = triggerResponse;
|
|
28
|
+
SpinniesManager.update('preview', {
|
|
29
|
+
text: lib.projectPreview.pollingStatus(releaseTag, targetPortalId),
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
await pollPreviewStatus(accountId, projectId, targetPortalId, releaseTag, appId);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
SpinniesManager.fail('preview', {
|
|
36
|
+
text: lib.projectPreview.pollFailed,
|
|
37
|
+
});
|
|
38
|
+
logError(e, new ApiErrorContext({
|
|
39
|
+
accountId,
|
|
40
|
+
request: 'preview status',
|
|
41
|
+
}));
|
|
42
|
+
return { succeeded: false, releaseTag, appId };
|
|
43
|
+
}
|
|
44
|
+
SpinniesManager.succeed('preview', {
|
|
45
|
+
text: lib.projectPreview.succeeded(releaseTag, targetPortalId),
|
|
46
|
+
});
|
|
47
|
+
return { succeeded: true, releaseTag, appId };
|
|
48
|
+
}
|
|
49
|
+
function pollPreviewStatus(accountId, projectId, targetPortalId, expectedReleaseTag, appId) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
const pollInterval = setInterval(async () => {
|
|
53
|
+
try {
|
|
54
|
+
const { data } = await getAutoReleaseStatus(accountId, projectId, targetPortalId, expectedReleaseTag, appId);
|
|
55
|
+
if (data.status === 'COMPLETE') {
|
|
56
|
+
clearInterval(pollInterval);
|
|
57
|
+
resolve();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (Date.now() - startTime >= PREVIEW_POLL_TIMEOUT) {
|
|
61
|
+
clearInterval(pollInterval);
|
|
62
|
+
reject(new Error(lib.projectPreview.timeout));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
clearInterval(pollInterval);
|
|
67
|
+
reject(e);
|
|
68
|
+
}
|
|
69
|
+
}, DEFAULT_POLLING_DELAY);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
export declare const REQUIRED_PACKAGES_AND_MIN_VERSIONS: {
|
|
2
2
|
readonly eslint: "9.0.0";
|
|
3
|
-
readonly '@
|
|
4
|
-
readonly '@typescript-eslint/parser': "8.46.4";
|
|
3
|
+
readonly '@eslint/js': "9.0.0";
|
|
5
4
|
readonly 'typescript-eslint': "8.46.4";
|
|
5
|
+
readonly '@hubspot/eslint-config-ui-extensions': "1.0.0";
|
|
6
|
+
readonly 'eslint-config-prettier': "10.0.0";
|
|
7
|
+
readonly 'eslint-plugin-react': "7.0.0";
|
|
8
|
+
readonly 'eslint-plugin-react-hooks': "7.0.0";
|
|
9
|
+
readonly 'eslint-plugin-unused-imports': "4.0.0";
|
|
10
|
+
readonly prettier: "3.0.0";
|
|
6
11
|
readonly jiti: "2.6.1";
|
|
7
12
|
};
|
|
8
13
|
export declare function isEslintInstalled(directory: string): boolean;
|
|
@@ -13,7 +18,7 @@ export declare function getMissingLintPackages(directory: string): {
|
|
|
13
18
|
export declare function hasEslintConfig(directory: string): boolean;
|
|
14
19
|
export declare function hasDeprecatedEslintConfig(directory: string): boolean;
|
|
15
20
|
export declare function getDeprecatedEslintConfigFiles(directory: string): string[];
|
|
16
|
-
export declare function createEslintConfig(directory: string): string
|
|
21
|
+
export declare function createEslintConfig(directory: string, platformVersion?: string | null): Promise<string>;
|
|
17
22
|
export declare function lintPackagesInDirectory(directory: string, projectDir?: string): Promise<{
|
|
18
23
|
success: boolean;
|
|
19
24
|
output: string;
|
|
@@ -3,17 +3,27 @@ import path from 'path';
|
|
|
3
3
|
import util from 'util';
|
|
4
4
|
import semver from 'semver';
|
|
5
5
|
import { exec as execAsync } from 'node:child_process';
|
|
6
|
+
import { fetchRepoFile } from '@hubspot/local-dev-lib/api/github';
|
|
6
7
|
import { getProjectPackageJsonLocations, isPackageInstalled, } from '../dependencyManagement.js';
|
|
7
8
|
import { commands } from '../../lang/en.js';
|
|
8
9
|
import { uiLogger } from '../ui/logger.js';
|
|
9
10
|
import { safeGetPackageJsonCached } from '../npm/packageJson.js';
|
|
11
|
+
import { debugError } from '../errorHandlers/index.js';
|
|
12
|
+
import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
|
|
13
|
+
import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, DEFAULT_PROJECT_TEMPLATE_BRANCH, } from '../constants.js';
|
|
10
14
|
export const REQUIRED_PACKAGES_AND_MIN_VERSIONS = {
|
|
11
15
|
eslint: '9.0.0',
|
|
12
|
-
'@
|
|
13
|
-
'@typescript-eslint/parser': '8.46.4',
|
|
16
|
+
'@eslint/js': '9.0.0',
|
|
14
17
|
'typescript-eslint': '8.46.4',
|
|
18
|
+
'@hubspot/eslint-config-ui-extensions': '1.0.0',
|
|
19
|
+
'eslint-config-prettier': '10.0.0',
|
|
20
|
+
'eslint-plugin-react': '7.0.0',
|
|
21
|
+
'eslint-plugin-react-hooks': '7.0.0',
|
|
22
|
+
'eslint-plugin-unused-imports': '4.0.0',
|
|
23
|
+
prettier: '3.0.0',
|
|
15
24
|
jiti: '2.6.1',
|
|
16
25
|
};
|
|
26
|
+
const UIE_ESLINT_CONFIG_PATH_IN_REPO = 'components/cards/src/app/cards/eslint.config.js';
|
|
17
27
|
const ESLINT_CONFIG_FILES = [
|
|
18
28
|
'eslint.config.mts',
|
|
19
29
|
'eslint.config.ts',
|
|
@@ -30,28 +40,6 @@ const DEPRECATED_ESLINT_CONFIG_FILES = [
|
|
|
30
40
|
'.eslintrc.json',
|
|
31
41
|
'.eslintrc',
|
|
32
42
|
];
|
|
33
|
-
const ESLINT_CONFIG_TEMPLATE = `import { defineConfig } from "eslint/config";
|
|
34
|
-
import tsParser from "@typescript-eslint/parser";
|
|
35
|
-
|
|
36
|
-
export default defineConfig([
|
|
37
|
-
{
|
|
38
|
-
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
|
39
|
-
languageOptions: {
|
|
40
|
-
parser: tsParser,
|
|
41
|
-
parserOptions: {
|
|
42
|
-
ecmaVersion: "latest",
|
|
43
|
-
sourceType: "module",
|
|
44
|
-
ecmaFeatures: {
|
|
45
|
-
jsx: true
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
rules: {
|
|
50
|
-
"no-console": ["warn", { allow: ["warn", "error"] }]
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
]);
|
|
54
|
-
`;
|
|
55
43
|
function getPackageVersionFromPackageJson(directory, packageName) {
|
|
56
44
|
const packageJsonPath = path.join(directory, 'package.json');
|
|
57
45
|
const packageJson = safeGetPackageJsonCached(packageJsonPath);
|
|
@@ -127,10 +115,43 @@ export function getDeprecatedEslintConfigFiles(directory) {
|
|
|
127
115
|
return fs.existsSync(configPath);
|
|
128
116
|
});
|
|
129
117
|
}
|
|
130
|
-
|
|
131
|
-
|
|
118
|
+
function repoFileDataToString(data) {
|
|
119
|
+
if (typeof data === 'string') {
|
|
120
|
+
return data;
|
|
121
|
+
}
|
|
122
|
+
if (Buffer.isBuffer(data)) {
|
|
123
|
+
return data.toString('utf-8');
|
|
124
|
+
}
|
|
125
|
+
return String(data);
|
|
126
|
+
}
|
|
127
|
+
export async function createEslintConfig(directory, platformVersion) {
|
|
128
|
+
const versionForRemote = platformVersion && !isLegacyProject(platformVersion)
|
|
129
|
+
? platformVersion
|
|
130
|
+
: null;
|
|
131
|
+
if (versionForRemote === null) {
|
|
132
|
+
const message = commands.project.lint.createEslintConfigRequiresV2Platform(platformVersion);
|
|
133
|
+
uiLogger.error(message);
|
|
134
|
+
throw new Error(message);
|
|
135
|
+
}
|
|
136
|
+
let fetchedContent = null;
|
|
137
|
+
try {
|
|
138
|
+
const { data } = await fetchRepoFile(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, `${versionForRemote}/${UIE_ESLINT_CONFIG_PATH_IN_REPO}`, DEFAULT_PROJECT_TEMPLATE_BRANCH);
|
|
139
|
+
const content = repoFileDataToString(data);
|
|
140
|
+
if (content.trim().length > 0) {
|
|
141
|
+
fetchedContent = content;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
debugError(error);
|
|
146
|
+
}
|
|
147
|
+
if (fetchedContent === null) {
|
|
148
|
+
const message = commands.project.lint.failedToFetchRemoteEslintConfig(versionForRemote);
|
|
149
|
+
uiLogger.error(message);
|
|
150
|
+
throw new Error(message);
|
|
151
|
+
}
|
|
152
|
+
const configPath = path.join(directory, 'eslint.config.js');
|
|
132
153
|
try {
|
|
133
|
-
fs.writeFileSync(configPath,
|
|
154
|
+
fs.writeFileSync(configPath, fetchedContent, 'utf-8');
|
|
134
155
|
return path.relative(process.cwd(), configPath);
|
|
135
156
|
}
|
|
136
157
|
catch (error) {
|
package/lib/projects/upload.d.ts
CHANGED
package/lib/projects/upload.js
CHANGED
|
@@ -13,11 +13,11 @@ import util from 'node:util';
|
|
|
13
13
|
import { lib } from '../../lang/en.js';
|
|
14
14
|
import { ensureProjectExists } from './ensureProjectExists.js';
|
|
15
15
|
import { uiLogger } from '../ui/logger.js';
|
|
16
|
-
import { isV2Project } from './platformVersion.js';
|
|
17
16
|
import ProjectValidationError from '../errors/ProjectValidationError.js';
|
|
18
17
|
import { walk } from '@hubspot/local-dev-lib/fs';
|
|
19
18
|
import { LEGACY_CONFIG_FILES } from '../constants.js';
|
|
20
19
|
import { archiveWorkspacesAndDependencies, getPackageJsonPathsToUpdate, getLockfilePathsToUpdate, } from './workspaces.js';
|
|
20
|
+
import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
|
|
21
21
|
async function uploadProjectFiles(accountId, projectName, filePath, uploadMessage, platformVersion, intermediateRepresentation) {
|
|
22
22
|
const accountIdentifier = uiAccountDescription(accountId) || `${accountId}`;
|
|
23
23
|
SpinniesManager.add('upload', {
|
|
@@ -54,7 +54,7 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
|
|
|
54
54
|
// Versions <= 2025.1 do not support the new npm workspaces bundling behavior.
|
|
55
55
|
let workspaceMappings = [];
|
|
56
56
|
let fileDependencyMappings = [];
|
|
57
|
-
if (
|
|
57
|
+
if (!isLegacyProject(projectConfig.platformVersion)) {
|
|
58
58
|
const parsedPackageJsons = await findAndParsePackageJsonFiles(srcDir);
|
|
59
59
|
workspaceMappings = await collectWorkspaceDirectories(parsedPackageJsons);
|
|
60
60
|
fileDependencyMappings = await collectFileDependencies(parsedPackageJsons);
|
|
@@ -79,7 +79,7 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
|
|
|
79
79
|
return resolve({ uploadError: e });
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
const { projectExists } = await ensureProjectExists(accountId, projectConfig.name, {
|
|
82
|
+
const { projectExists, project } = await ensureProjectExists(accountId, projectConfig.name, {
|
|
83
83
|
forceCreate,
|
|
84
84
|
uploadCommand: isUploadCommand,
|
|
85
85
|
noLogs: true,
|
|
@@ -88,13 +88,14 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
|
|
|
88
88
|
uiLogger.log(lib.projectUpload.handleProjectUpload.projectDoesNotExist(accountId));
|
|
89
89
|
return resolve({ projectNotFound: true });
|
|
90
90
|
}
|
|
91
|
+
const projectId = project?.id;
|
|
91
92
|
const { buildId, error } = await uploadProjectFiles(accountId, projectConfig.name, tempFile.name, uploadMessage, projectConfig.platformVersion, intermediateRepresentation);
|
|
92
93
|
if (error) {
|
|
93
|
-
resolve({ uploadError: error });
|
|
94
|
+
resolve({ uploadError: error, projectId });
|
|
94
95
|
}
|
|
95
96
|
else if (callbackFunc) {
|
|
96
97
|
const uploadResult = await callbackFunc(accountId, projectConfig, tempFile, buildId);
|
|
97
|
-
resolve({ result: uploadResult });
|
|
98
|
+
resolve({ result: uploadResult, projectId });
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
catch (e) {
|
|
@@ -132,7 +133,7 @@ export async function validateSourceDirectory(srcDir, projectConfig, projectDir)
|
|
|
132
133
|
if (!projectFilePaths || projectFilePaths.length === 0) {
|
|
133
134
|
throw new ProjectValidationError(lib.projectUpload.handleProjectUpload.emptySource(projectConfig.srcDir));
|
|
134
135
|
}
|
|
135
|
-
if (
|
|
136
|
+
if (!isLegacyProject(projectConfig.platformVersion)) {
|
|
136
137
|
projectFilePaths.forEach(filePath => {
|
|
137
138
|
const filename = path.basename(filePath);
|
|
138
139
|
if (LEGACY_CONFIG_FILES.includes(filename)) {
|
|
@@ -143,7 +144,7 @@ export async function validateSourceDirectory(srcDir, projectConfig, projectDir)
|
|
|
143
144
|
}
|
|
144
145
|
export async function validateNoHSMetaMismatch(srcDir, projectConfig) {
|
|
145
146
|
const hasHsMetaFiles = await projectContainsHsMetaFiles(srcDir);
|
|
146
|
-
if (
|
|
147
|
+
if (isLegacyProject(projectConfig.platformVersion) && hasHsMetaFiles) {
|
|
147
148
|
throw new ProjectValidationError(lib.projectUpload.wrongPlatformVersionMetaFiles);
|
|
148
149
|
}
|
|
149
150
|
}
|
|
@@ -4,6 +4,9 @@ export async function projectLogsPrompt({ functionChoices, promptOptions, projec
|
|
|
4
4
|
if (!functionChoices) {
|
|
5
5
|
return {};
|
|
6
6
|
}
|
|
7
|
+
if (promptOptions?.function) {
|
|
8
|
+
return { functionName: promptOptions.function };
|
|
9
|
+
}
|
|
7
10
|
if (functionChoices.length === 1) {
|
|
8
11
|
return { functionName: functionChoices[0] };
|
|
9
12
|
}
|
|
@@ -10,7 +10,7 @@ import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
11
|
const __dirname = path.dirname(__filename);
|
|
12
12
|
// cms-dev-server version to install to isolated cache
|
|
13
|
-
const TARGET_CMS_DEV_SERVER_VERSION = '1.2.
|
|
13
|
+
const TARGET_CMS_DEV_SERVER_VERSION = '1.2.26';
|
|
14
14
|
/**
|
|
15
15
|
* Ensures cms-dev-server is installed in an isolated cache directory.
|
|
16
16
|
* This prevents React version conflicts with the CLI.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { McpLogger } from './utils/logger.js';
|
|
3
|
+
import { TextContentResponse } from './types.js';
|
|
4
|
+
export declare class Tool<InputSchema> {
|
|
5
|
+
protected mcpServer: McpServer;
|
|
6
|
+
protected logger: McpLogger;
|
|
7
|
+
protected toolName: string;
|
|
8
|
+
constructor(mcpServer: McpServer, logger: McpLogger, toolName: string);
|
|
9
|
+
register(): RegisteredTool;
|
|
10
|
+
handler(input: InputSchema): TextContentResponse | Promise<TextContentResponse>;
|
|
11
|
+
protected getTrackingMeta(input: InputSchema): {
|
|
12
|
+
[key: string]: string;
|
|
13
|
+
} | undefined;
|
|
14
|
+
protected wrappedHandler(input: InputSchema): Promise<TextContentResponse>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { formatTextContents } from './utils/content.js';
|
|
2
|
+
import { getErrorMessage } from '../lib/errorHandlers/index.js';
|
|
3
|
+
import { trackToolUsage } from './utils/toolUsageTracking.js';
|
|
4
|
+
export class Tool {
|
|
5
|
+
mcpServer;
|
|
6
|
+
logger;
|
|
7
|
+
toolName;
|
|
8
|
+
constructor(mcpServer, logger, toolName) {
|
|
9
|
+
this.mcpServer = mcpServer;
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
this.toolName = toolName;
|
|
12
|
+
}
|
|
13
|
+
register() {
|
|
14
|
+
throw new Error('Must implement register');
|
|
15
|
+
}
|
|
16
|
+
handler(
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
18
|
+
input) {
|
|
19
|
+
throw new Error('Must implement handler');
|
|
20
|
+
}
|
|
21
|
+
getTrackingMeta(
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
23
|
+
input) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
async wrappedHandler(input) {
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
try {
|
|
29
|
+
// `input` is logged unredacted. Tool input schemas MUST NOT include
|
|
30
|
+
// credentials or other sensitive values, since MCP clients (Claude
|
|
31
|
+
// Desktop, Inspector, etc.) will display these logs.
|
|
32
|
+
this.logger.debug(this.toolName, {
|
|
33
|
+
message: 'Tool invoked',
|
|
34
|
+
args: input,
|
|
35
|
+
});
|
|
36
|
+
await trackToolUsage(this.toolName, this.getTrackingMeta(input));
|
|
37
|
+
const result = await this.handler(input);
|
|
38
|
+
this.logger.debug(this.toolName, {
|
|
39
|
+
message: 'Tool completed',
|
|
40
|
+
durationMs: Date.now() - startTime,
|
|
41
|
+
});
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
this.logger.error(this.toolName, {
|
|
46
|
+
message: 'Tool failed',
|
|
47
|
+
error: error instanceof Error ? error.message : String(error),
|
|
48
|
+
durationMs: Date.now() - startTime,
|
|
49
|
+
});
|
|
50
|
+
return formatTextContents(getErrorMessage(error));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|