@hubspot/cli 8.9.1-beta.0 → 8.10.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/account/auth.js +2 -0
- package/commands/app/logs.js +4 -0
- package/commands/getStarted.js +3 -0
- package/commands/init.js +2 -0
- package/commands/mcp/setup.js +2 -2
- package/commands/project/appInstallStatus.js +2 -2
- package/commands/project/create.js +2 -0
- package/commands/project/dev/unifiedFlow.js +3 -1
- package/commands/project/installApp.d.ts +7 -0
- package/commands/project/installApp.js +181 -0
- package/commands/project/installDeps.js +2 -0
- package/commands/project/upload.d.ts +2 -0
- package/commands/project/upload.js +26 -5
- package/commands/project.js +2 -0
- package/commands/upgrade.js +2 -0
- package/lang/en.d.ts +66 -0
- package/lang/en.js +73 -7
- package/lib/accountTargetDiscovery.d.ts +15 -0
- package/lib/accountTargetDiscovery.js +175 -0
- package/lib/app/install.d.ts +22 -0
- package/lib/app/install.js +42 -0
- package/lib/app/logs.js +1 -1
- package/lib/auth/awaitPersonalAccessKeyOverWebsocket.js +3 -2
- package/lib/doctor/Doctor.d.ts +1 -0
- package/lib/doctor/Doctor.js +26 -0
- package/lib/getStartedV2Actions.js +2 -1
- package/lib/mcp/clients.d.ts +13 -0
- package/lib/mcp/clients.js +55 -0
- package/lib/mcp/promotion.d.ts +3 -0
- package/lib/mcp/promotion.js +109 -0
- package/lib/mcp/setup.d.ts +3 -2
- package/lib/mcp/setup.js +17 -12
- package/lib/projects/installApp.d.ts +58 -0
- package/lib/projects/installApp.js +283 -0
- package/lib/projects/localDev/AppDevModeInterface.d.ts +0 -3
- package/lib/projects/localDev/AppDevModeInterface.js +22 -44
- package/lib/projects/localDev/LocalDevProcess.js +4 -2
- package/lib/projects/localDev/helpers/project.js +3 -2
- 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 +15 -4
- package/lib/prompts/projectDevTargetAccountPrompt.js +3 -2
- package/lib/theme/cmsDevServerProcess.js +1 -1
- package/lib/usageTracking.d.ts +1 -0
- package/lib/usageTracking.js +9 -0
- package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
- package/mcp-server/tools/project/UploadProjectTools.js +1 -1
- package/package.json +5 -5
- package/types/AccountTargets.d.ts +37 -0
- package/types/AccountTargets.js +12 -0
- package/types/LocalDev.d.ts +3 -2
- package/types/ProjectComponents.d.ts +2 -1
package/lang/en.js
CHANGED
|
@@ -4,7 +4,7 @@ import { PLATFORM_VERSIONS } from '@hubspot/project-parsing-lib/constants';
|
|
|
4
4
|
import { PERSONAL_ACCESS_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constants/auth';
|
|
5
5
|
import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, LEGACY_PUBLIC_APP_FILE, LOCAL_DEV_DEFAULT_PORT, PROJECT_CONFIG_FILE, PROJECT_WITH_APP, } from '../lib/constants.js';
|
|
6
6
|
import { ARCHIVED_HUBSPOT_CONFIG_YAML_FILE_NAME, DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME, GLOBAL_CONFIG_PATH, } from '@hubspot/local-dev-lib/constants/config';
|
|
7
|
-
import { indent, UI_COLORS, uiAccountDescription, uiAuthCommandReference, uiBetaTag, uiCommandReference, uiLink, } from '../lib/ui/index.js';
|
|
7
|
+
import { indent, UI_COLORS, uiAccountDescription, uiAuthCommandReference, uiBetaTag, uiCommandReference, uiDeprecatedTag, uiLink, } from '../lib/ui/index.js';
|
|
8
8
|
import { getLocalDevUiUrl, getProjectDetailUrl, getProjectSettingsUrl, } from '../lib/projects/urls.js';
|
|
9
9
|
import { getProductUpdatesUrl } from '../lib/links.js';
|
|
10
10
|
export const commands = {
|
|
@@ -1325,8 +1325,11 @@ export const commands = {
|
|
|
1325
1325
|
},
|
|
1326
1326
|
mcp: {
|
|
1327
1327
|
describe: 'Commands for managing HubSpot MCP servers.',
|
|
1328
|
+
promotion: {
|
|
1329
|
+
activeNudge: `${chalk.bold('Tip:')} Work faster with AI coding tools. Run ${uiCommandReference('hs mcp setup')} to connect HubSpot developer context to supported clients.`,
|
|
1330
|
+
},
|
|
1328
1331
|
setup: {
|
|
1329
|
-
describe: '
|
|
1332
|
+
describe: `Run ${uiCommandReference('hs mcp setup')} to connect the HubSpot Dev MCP and work faster with your AI coding tools.`,
|
|
1330
1333
|
installingDocSearch: 'Adding the docs-search mcp server',
|
|
1331
1334
|
codex: 'Codex CLI',
|
|
1332
1335
|
claudeCode: 'Claude Code',
|
|
@@ -1900,8 +1903,11 @@ export const commands = {
|
|
|
1900
1903
|
targetRequiresPreview: `${uiCommandReference('--target')} can only be used with ${uiCommandReference('--preview')}.`,
|
|
1901
1904
|
},
|
|
1902
1905
|
options: {
|
|
1906
|
+
force: {
|
|
1907
|
+
describe: 'Skip confirmation prompts and force the upload.',
|
|
1908
|
+
},
|
|
1903
1909
|
forceCreate: {
|
|
1904
|
-
describe:
|
|
1910
|
+
describe: uiDeprecatedTag(`Automatically create project if it does not exist. Use ${uiCommandReference('--force')} instead.`, false),
|
|
1905
1911
|
},
|
|
1906
1912
|
message: {
|
|
1907
1913
|
describe: 'Add a message when you upload your project and create a build',
|
|
@@ -1912,6 +1918,9 @@ export const commands = {
|
|
|
1912
1918
|
skipNpmAudit: {
|
|
1913
1919
|
describe: 'Skip the npm audit security check before uploading',
|
|
1914
1920
|
},
|
|
1921
|
+
skipAutoDeploy: {
|
|
1922
|
+
describe: 'Skip automatic deployment after a successful build',
|
|
1923
|
+
},
|
|
1915
1924
|
preview: {
|
|
1916
1925
|
describe: 'Preview the build on a target portal after a successful upload',
|
|
1917
1926
|
},
|
|
@@ -2192,6 +2201,47 @@ export const commands = {
|
|
|
2192
2201
|
force: 'skip confirmation prompt',
|
|
2193
2202
|
},
|
|
2194
2203
|
},
|
|
2204
|
+
installApp: {
|
|
2205
|
+
describe: 'Install a static auth app from the current project into the target account',
|
|
2206
|
+
verboseDescribe: `Install a static auth app from the current project into the target account.\n\nRun this command from within the project directory. The project must contain a privately distributed static auth app and must already be uploaded to the target account.`,
|
|
2207
|
+
examples: {
|
|
2208
|
+
default: 'Install the static auth app from the current project',
|
|
2209
|
+
withAccount: 'Install the app into a specific account',
|
|
2210
|
+
withProfile: 'Install the app into the account targeted by a profile',
|
|
2211
|
+
json: 'Output install result as JSON',
|
|
2212
|
+
},
|
|
2213
|
+
options: {
|
|
2214
|
+
force: 'Skip confirmation prompts',
|
|
2215
|
+
profile: 'The profile to target with this install',
|
|
2216
|
+
},
|
|
2217
|
+
errors: {
|
|
2218
|
+
noProjectConfig: `No project config found. Run this command from within a HubSpot project directory, or use ${uiCommandReference('hs project create')} to create a new one.`,
|
|
2219
|
+
unsupportedPlatformVersion: (platformVersion) => `This command is only supported for projects on platform version 2025.2 or later (detected: ${platformVersion}).`,
|
|
2220
|
+
failedToParseProject: 'Failed to parse the project. Check your project configuration is valid and try again.',
|
|
2221
|
+
noAppInProject: 'No app was found in the local project. This command requires a project that contains an app.',
|
|
2222
|
+
unsupportedAuthType: (authType) => `This command only supports static auth apps. Detected auth type: ${authType}.`,
|
|
2223
|
+
unsupportedDistribution: (distribution) => `This command only supports privately distributed apps. Detected distribution: ${distribution}.`,
|
|
2224
|
+
invalidAppDeveloperAccount: (accountId) => `Static auth apps can't be installed into the app developer account ${uiAccountDescription(accountId)}. Switch to a standard, sandbox, or developer test account with ${uiCommandReference('hs account use')}, or pass ${uiCommandReference('--account')}.`,
|
|
2225
|
+
projectNotFound: (accountId, projectName) => `The project ${chalk.bold(projectName)} does not exist in ${uiAccountDescription(accountId)}. Run ${uiCommandReference('hs project upload')} to upload your project files to HubSpot.`,
|
|
2226
|
+
appNotDeployed: (appName, accountId) => `The app ${chalk.bold(appName)} has not been deployed to ${uiAccountDescription(accountId)}. Upload the project with ${uiCommandReference('hs project upload')} and ensure the build succeeds, then try again.`,
|
|
2227
|
+
automaticInstallUnavailable: (appName, accountId) => `${chalk.bold(appName)} could not be installed automatically in ${uiAccountDescription(accountId)}.`,
|
|
2228
|
+
installFailed: (appName, accountId) => `Failed to install ${chalk.bold(appName)} in ${uiAccountDescription(accountId)}.`,
|
|
2229
|
+
},
|
|
2230
|
+
jsonErrors: {
|
|
2231
|
+
automaticInstallUnavailable: 'Automatic install is unavailable for this account.',
|
|
2232
|
+
installFailed: (appName, accountId) => `Failed to install ${appName} in account ${accountId}.`,
|
|
2233
|
+
},
|
|
2234
|
+
profileMessage: (profileName, accountId) => `Installing with ${chalk.bold(profileName)} profile: ${uiAccountDescription(accountId)}`,
|
|
2235
|
+
appDeveloperAccountNotice: (accountId) => `${uiAccountDescription(accountId)} is an app developer account, which can't have apps installed into it.`,
|
|
2236
|
+
selectInstallAccountPrompt: 'Select an account to install the app into',
|
|
2237
|
+
uploadAndDeployPrompt: (accountId) => `The app isn't deployed to ${uiAccountDescription(accountId)} yet. Upload and deploy the project now?`,
|
|
2238
|
+
alreadyInstalled: (appName, accountId) => `${chalk.bold(appName)} is already installed in ${uiAccountDescription(accountId)}.`,
|
|
2239
|
+
outdatedScopes: (appName, accountId) => `${chalk.bold(appName)} is installed in ${uiAccountDescription(accountId)} with outdated scopes.`,
|
|
2240
|
+
reinstallPrompt: 'Reinstall the app to grant the latest scopes?',
|
|
2241
|
+
installPrompt: (appName, accountId) => `Install ${chalk.bold(appName)} in ${uiAccountDescription(accountId)}?`,
|
|
2242
|
+
installFromBrowser: (installUrl) => `Install the app in HubSpot: ${uiLink('Open install page', installUrl)}`,
|
|
2243
|
+
success: (appName, accountId) => `${chalk.bold(appName)} installed in ${uiAccountDescription(accountId)}.`,
|
|
2244
|
+
},
|
|
2195
2245
|
installStatus: {
|
|
2196
2246
|
describe: 'Check whether a static auth app in the current project is installed in the target account. This command must be run from within a HubSpot project directory.',
|
|
2197
2247
|
examples: {
|
|
@@ -2632,7 +2682,7 @@ export const commands = {
|
|
|
2632
2682
|
serviceLevel: 'Service Hub tier. Options: FREE, STARTER, PROFESSIONAL, ENTERPRISE',
|
|
2633
2683
|
salesLevel: 'Sales Hub tier. Options: FREE, STARTER, PROFESSIONAL, ENTERPRISE',
|
|
2634
2684
|
contentLevel: 'Content Hub tier. Options: FREE, STARTER, PROFESSIONAL, ENTERPRISE',
|
|
2635
|
-
commerceLevel: '
|
|
2685
|
+
commerceLevel: 'Revenue Hub tier. Options: FREE, PROFESSIONAL, ENTERPRISE',
|
|
2636
2686
|
},
|
|
2637
2687
|
example: (configPath) => `Create a test account from the config file at ${configPath}`,
|
|
2638
2688
|
examples: {
|
|
@@ -3293,6 +3343,11 @@ export const lib = {
|
|
|
3293
3343
|
configMiddleWare: {
|
|
3294
3344
|
invalidAccountIdEnvironmentVariable: 'Unable to parse `HUBSPOT_ACCOUNT_ID` environment variable into a number',
|
|
3295
3345
|
},
|
|
3346
|
+
accountTargetDiscovery: {
|
|
3347
|
+
errors: {
|
|
3348
|
+
explicitAccountNotFound: (account) => `Account ${chalk.bold(String(account))} was not found in your config. Run ${uiCommandReference('hs account list')} to see available accounts.`,
|
|
3349
|
+
},
|
|
3350
|
+
},
|
|
3296
3351
|
process: {
|
|
3297
3352
|
exitDebug: (signal) => `Attempting to gracefully exit. Triggered by ${signal}`,
|
|
3298
3353
|
},
|
|
@@ -3569,6 +3624,11 @@ export const lib = {
|
|
|
3569
3624
|
feedbackHeader: "We'd love to hear your feedback!",
|
|
3570
3625
|
feedbackMessage: `How are you liking the new projects and developer tools? \n > Run ${uiCommandReference('hs feedback')} to let us know what you think!\n`,
|
|
3571
3626
|
},
|
|
3627
|
+
skippedHsMetaFiles: {
|
|
3628
|
+
warning: (files) => `The following *-hsmeta.json files will be skipped.\nThis CLI version doesn't support the features they require:\n${files.map(f => ` ${chalk.bold(f)}`).join('\n')}\nRun ${uiCommandReference('hs upgrade')} to update the CLI, which may add support for these features.`,
|
|
3629
|
+
uploadWarning: (files) => `The following *-hsmeta.json files will be skipped during upload.\nThis CLI version doesn't support the features they require:\n${files.map(f => ` ${chalk.bold(f)}`).join('\n')}\nRun ${uiCommandReference('hs upgrade')} to update the CLI, which may add support for these features.`,
|
|
3630
|
+
prompt: '[--force] Continue anyway?',
|
|
3631
|
+
},
|
|
3572
3632
|
components: {
|
|
3573
3633
|
unableToGetUidFromHsmeta: 'Unable to get UID from hsmeta',
|
|
3574
3634
|
buildSuccessMessage: {
|
|
@@ -3576,9 +3636,9 @@ export const lib = {
|
|
|
3576
3636
|
docsUrl: 'https://developers.hubspot.com/docs/apps/developer-platform/build-apps/overview',
|
|
3577
3637
|
headerCreated: (projectName, projectDest) => `${chalk.bold(projectName)} was successfully created in ${projectDest}`,
|
|
3578
3638
|
headerAdded: (featureText, uid, plural) => `${featureText} ${plural ? 'were' : 'was'} successfully added to ${uid}:`,
|
|
3579
|
-
docsDetails: (docsLink) =>
|
|
3580
|
-
uploadPrompt:
|
|
3581
|
-
devPrompt:
|
|
3639
|
+
docsDetails: (docsLink) => `${docsLink} for more details about building and testing these features.`,
|
|
3640
|
+
uploadPrompt: `Run ${uiCommandReference('hs project upload')} when you're ready to deploy.`,
|
|
3641
|
+
devPrompt: `Run ${uiCommandReference('hs project dev')} to start local development.`,
|
|
3582
3642
|
},
|
|
3583
3643
|
},
|
|
3584
3644
|
},
|
|
@@ -3873,6 +3933,7 @@ export const lib = {
|
|
|
3873
3933
|
confirmUseExistingDeveloperTestAccount: (accountName) => `Continue with ${accountName}? This account isn't currently connected to the HubSpot CLI. By continuing, you'll be prompted to generate a personal access key and connect it.`,
|
|
3874
3934
|
confirmLinkExistingDeveloperTestAccount: (accountName) => `${accountName} is not linked to this directory. Would you like to link it?`,
|
|
3875
3935
|
noAccountId: 'No account ID found for the selected account. Please try again.',
|
|
3936
|
+
fetchDeveloperTestAccountsError: 'Unable to fetch developer test accounts. Please try again.',
|
|
3876
3937
|
},
|
|
3877
3938
|
projectLogsPrompt: {
|
|
3878
3939
|
functionName: (projectName) => `[--function] Select function in ${chalk.bold(projectName)} project`,
|
|
@@ -4277,6 +4338,11 @@ export const lib = {
|
|
|
4277
4338
|
installed: (npmVersion) => `npm v${npmVersion} is installed`,
|
|
4278
4339
|
unableToDetermine: 'Unable to determine if npm is installed',
|
|
4279
4340
|
},
|
|
4341
|
+
mcpChecks: {
|
|
4342
|
+
configured: (clients) => `HubSpot MCP server configured in ${clients.join(', ')}`,
|
|
4343
|
+
notConfigured: (clients) => `HubSpot MCP server not configured in ${clients.join(', ')}`,
|
|
4344
|
+
notConfiguredSecondary: `Run ${uiCommandReference('hs mcp setup')} to connect AI coding tools to HubSpot developer context`,
|
|
4345
|
+
},
|
|
4280
4346
|
hsChecks: {
|
|
4281
4347
|
notLatest: (hsVersion) => `Version ${hsVersion} outdated`,
|
|
4282
4348
|
notLatestSecondary: (command, hsVersion) => `Run ${uiCommandReference(command)} to upgrade to the latest version ${hsVersion}`,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DiscoverAccountTargetsOptions, DiscoverAccountTargetsResult } from '../types/AccountTargets.js';
|
|
2
|
+
/**
|
|
3
|
+
* Discovers the candidate target accounts for a command, plus the recommended
|
|
4
|
+
* one to use when a single account is the obvious choice.
|
|
5
|
+
*
|
|
6
|
+
* Profiles take precedence: when the project defines profiles, the candidates
|
|
7
|
+
* are the profile accounts only. Otherwise they are ranked explicit account,
|
|
8
|
+
* then env config, then linked directory, then global default.
|
|
9
|
+
*
|
|
10
|
+
* Takes plain options (not yargs argv) and never prompts or exits, so both the
|
|
11
|
+
* CLI and MCP can call it. `recommended` is omitted when no account is the
|
|
12
|
+
* obvious choice (for example, several profiles). Throws when an explicit
|
|
13
|
+
* account is provided but not found in the config.
|
|
14
|
+
*/
|
|
15
|
+
export declare function discoverAccountTargets(options?: DiscoverAccountTargetsOptions): Promise<DiscoverAccountTargetsResult>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { getConfigAccountIfExists, getConfigDefaultAccountIfExists, } from '@hubspot/local-dev-lib/config';
|
|
3
|
+
import { getHsSettingsFileIfExists } from '@hubspot/local-dev-lib/config/hsSettings';
|
|
4
|
+
import { ENVIRONMENT_VARIABLES } from '@hubspot/local-dev-lib/constants/config';
|
|
5
|
+
import { getAllHsProfiles, loadHsProfileFile, } from '@hubspot/project-parsing-lib/profiles';
|
|
6
|
+
import { isTestAccountOrSandbox } from './accountTypes.js';
|
|
7
|
+
import { isDirectoryLinked } from './link/linkUtils.js';
|
|
8
|
+
import { lib } from '../lang/en.js';
|
|
9
|
+
import { ACCOUNT_TARGET_CATEGORIES, ACCOUNT_TARGET_SELECTION_SOURCES, } from '../types/AccountTargets.js';
|
|
10
|
+
function categorizeAccount(account) {
|
|
11
|
+
if (isTestAccountOrSandbox(account)) {
|
|
12
|
+
return ACCOUNT_TARGET_CATEGORIES.RECOMMENDED_TESTING;
|
|
13
|
+
}
|
|
14
|
+
if (!account.accountType) {
|
|
15
|
+
return ACCOUNT_TARGET_CATEGORIES.UNKNOWN;
|
|
16
|
+
}
|
|
17
|
+
return ACCOUNT_TARGET_CATEGORIES.PRODUCTION_WITH_CARE;
|
|
18
|
+
}
|
|
19
|
+
function candidateFromAccount(account, source, profileName) {
|
|
20
|
+
return {
|
|
21
|
+
accountId: account.accountId,
|
|
22
|
+
accountName: account.name,
|
|
23
|
+
accountType: account.accountType,
|
|
24
|
+
environment: account.env || undefined,
|
|
25
|
+
source,
|
|
26
|
+
category: categorizeAccount(account),
|
|
27
|
+
...(profileName ? { profileName } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function candidateFromAccountId(accountId, source, profileName) {
|
|
31
|
+
const account = getConfigAccountIfExists(accountId);
|
|
32
|
+
if (account) {
|
|
33
|
+
return candidateFromAccount(account, source, profileName);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
accountId,
|
|
37
|
+
source,
|
|
38
|
+
category: ACCOUNT_TARGET_CATEGORIES.UNKNOWN,
|
|
39
|
+
...(profileName ? { profileName } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function dedupeByAccountId(candidates) {
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
return candidates.filter(candidate => {
|
|
45
|
+
if (seen.has(candidate.accountId)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
seen.add(candidate.accountId);
|
|
49
|
+
return true;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function gatherProfileCandidates(projectDir, projectConfig) {
|
|
53
|
+
const projectSourceDir = path.join(projectDir, projectConfig.srcDir);
|
|
54
|
+
const profileNames = await getAllHsProfiles(projectSourceDir);
|
|
55
|
+
const candidates = [];
|
|
56
|
+
for (const name of profileNames) {
|
|
57
|
+
let profile;
|
|
58
|
+
try {
|
|
59
|
+
profile = loadHsProfileFile(projectSourceDir, name);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (profile && profile.accountId) {
|
|
65
|
+
candidates.push(candidateFromAccountId(profile.accountId, ACCOUNT_TARGET_SELECTION_SOURCES.PROFILE, name));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return candidates;
|
|
69
|
+
}
|
|
70
|
+
function resolveProfileRecommendation(profileCandidates, explicitProfileName) {
|
|
71
|
+
if (explicitProfileName) {
|
|
72
|
+
return profileCandidates.find(candidate => candidate.profileName === explicitProfileName);
|
|
73
|
+
}
|
|
74
|
+
if (profileCandidates.length === 1) {
|
|
75
|
+
return profileCandidates[0];
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
function resolveNonProfileRecommendation({ explicitCandidate, envCandidate, linkedCandidates, linkedDefaultAccountId, defaultCandidate, }) {
|
|
80
|
+
if (explicitCandidate) {
|
|
81
|
+
return explicitCandidate;
|
|
82
|
+
}
|
|
83
|
+
if (envCandidate) {
|
|
84
|
+
return envCandidate;
|
|
85
|
+
}
|
|
86
|
+
if (linkedCandidates.length > 0) {
|
|
87
|
+
if (linkedDefaultAccountId !== undefined) {
|
|
88
|
+
const linkedDefault = linkedCandidates.find(candidate => candidate.accountId === linkedDefaultAccountId);
|
|
89
|
+
if (linkedDefault) {
|
|
90
|
+
return linkedDefault;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (linkedCandidates.length === 1) {
|
|
94
|
+
return linkedCandidates[0];
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return defaultCandidate;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Discovers the candidate target accounts for a command, plus the recommended
|
|
102
|
+
* one to use when a single account is the obvious choice.
|
|
103
|
+
*
|
|
104
|
+
* Profiles take precedence: when the project defines profiles, the candidates
|
|
105
|
+
* are the profile accounts only. Otherwise they are ranked explicit account,
|
|
106
|
+
* then env config, then linked directory, then global default.
|
|
107
|
+
*
|
|
108
|
+
* Takes plain options (not yargs argv) and never prompts or exits, so both the
|
|
109
|
+
* CLI and MCP can call it. `recommended` is omitted when no account is the
|
|
110
|
+
* obvious choice (for example, several profiles). Throws when an explicit
|
|
111
|
+
* account is provided but not found in the config.
|
|
112
|
+
*/
|
|
113
|
+
export async function discoverAccountTargets(options = {}) {
|
|
114
|
+
const { explicitAccount, useEnv, profileName, projectDir, projectConfig } = options;
|
|
115
|
+
if (projectDir && projectConfig) {
|
|
116
|
+
const profileCandidates = await gatherProfileCandidates(projectDir, projectConfig);
|
|
117
|
+
if (profileCandidates.length > 0) {
|
|
118
|
+
return {
|
|
119
|
+
candidates: dedupeByAccountId(profileCandidates),
|
|
120
|
+
recommended: resolveProfileRecommendation(profileCandidates, profileName),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const envConfigActive = Boolean(useEnv) ||
|
|
125
|
+
process.env[ENVIRONMENT_VARIABLES.USE_ENVIRONMENT_HUBSPOT_CONFIG] ===
|
|
126
|
+
'true';
|
|
127
|
+
const ordered = [];
|
|
128
|
+
let explicitCandidate;
|
|
129
|
+
if (explicitAccount !== undefined && explicitAccount !== '') {
|
|
130
|
+
const account = getConfigAccountIfExists(explicitAccount);
|
|
131
|
+
if (!account) {
|
|
132
|
+
throw new Error(lib.accountTargetDiscovery.errors.explicitAccountNotFound(explicitAccount));
|
|
133
|
+
}
|
|
134
|
+
explicitCandidate = candidateFromAccount(account, ACCOUNT_TARGET_SELECTION_SOURCES.EXPLICIT_ACCOUNT);
|
|
135
|
+
ordered.push(explicitCandidate);
|
|
136
|
+
}
|
|
137
|
+
let envCandidate;
|
|
138
|
+
if (envConfigActive) {
|
|
139
|
+
const envAccount = getConfigDefaultAccountIfExists();
|
|
140
|
+
if (envAccount) {
|
|
141
|
+
envCandidate = candidateFromAccount(envAccount, ACCOUNT_TARGET_SELECTION_SOURCES.ENV_CONFIG);
|
|
142
|
+
ordered.push(envCandidate);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const settings = getHsSettingsFileIfExists();
|
|
146
|
+
const directoryIsLinked = isDirectoryLinked(settings);
|
|
147
|
+
const linkedCandidates = [];
|
|
148
|
+
if (directoryIsLinked) {
|
|
149
|
+
for (const accountId of settings.accounts) {
|
|
150
|
+
const candidate = candidateFromAccountId(accountId, ACCOUNT_TARGET_SELECTION_SOURCES.LINKED_DIRECTORY);
|
|
151
|
+
linkedCandidates.push(candidate);
|
|
152
|
+
ordered.push(candidate);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
let defaultCandidate;
|
|
156
|
+
if (!envConfigActive) {
|
|
157
|
+
const defaultAccount = getConfigDefaultAccountIfExists();
|
|
158
|
+
if (defaultAccount) {
|
|
159
|
+
defaultCandidate = candidateFromAccount(defaultAccount, ACCOUNT_TARGET_SELECTION_SOURCES.GLOBAL_DEFAULT);
|
|
160
|
+
ordered.push(defaultCandidate);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
candidates: dedupeByAccountId(ordered),
|
|
165
|
+
recommended: resolveNonProfileRecommendation({
|
|
166
|
+
explicitCandidate,
|
|
167
|
+
envCandidate,
|
|
168
|
+
linkedCandidates,
|
|
169
|
+
linkedDefaultAccountId: directoryIsLinked
|
|
170
|
+
? settings.localDefaultAccount
|
|
171
|
+
: undefined,
|
|
172
|
+
defaultCandidate,
|
|
173
|
+
}),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
|
|
2
|
+
import type { IntermediateRepresentationNodeLocalDev } from '@hubspot/project-parsing-lib/translate';
|
|
3
|
+
import type { AppInstallationState, AppIRNode } from '../../types/ProjectComponents.js';
|
|
4
|
+
export type ScopeGroup = {
|
|
5
|
+
id: number;
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
type CanUseStaticAuthTestAccountInstallOptions = {
|
|
9
|
+
accountConfig?: HubSpotConfigAccount;
|
|
10
|
+
appNode?: AppIRNode | null;
|
|
11
|
+
parentAccountId?: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function getAppNodeFromProjectNodes(projectNodes: {
|
|
14
|
+
[key: string]: IntermediateRepresentationNodeLocalDev;
|
|
15
|
+
}): AppIRNode | null;
|
|
16
|
+
export declare function isStaticAuthApp(appNode?: AppIRNode | null): boolean;
|
|
17
|
+
export declare function isOAuthApp(appNode?: AppIRNode | null): boolean;
|
|
18
|
+
export declare function isPrivateApp(appNode?: AppIRNode | null): boolean;
|
|
19
|
+
export declare function isMarketplaceApp(appNode?: AppIRNode | null): boolean;
|
|
20
|
+
export declare function getAppInstallationState(isInstalledWithScopeGroups: boolean, previouslyAuthorizedScopeGroups: ScopeGroup[]): AppInstallationState;
|
|
21
|
+
export declare function canUseStaticAuthTestAccountInstall({ accountConfig, appNode, parentAccountId, }: CanUseStaticAuthTestAccountInstallOptions): boolean;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, APP_INSTALLATION_STATES, } from '../constants.js';
|
|
2
|
+
import { isDeveloperTestAccount, isSandbox } from '../accountTypes.js';
|
|
3
|
+
import { isAppIRNode } from '../projects/structure.js';
|
|
4
|
+
export function getAppNodeFromProjectNodes(projectNodes) {
|
|
5
|
+
return Object.values(projectNodes).find(isAppIRNode) || null;
|
|
6
|
+
}
|
|
7
|
+
export function isStaticAuthApp(appNode) {
|
|
8
|
+
return appNode?.config.auth.type.toLowerCase() === APP_AUTH_TYPES.STATIC;
|
|
9
|
+
}
|
|
10
|
+
export function isOAuthApp(appNode) {
|
|
11
|
+
return appNode?.config.auth.type.toLowerCase() === APP_AUTH_TYPES.OAUTH;
|
|
12
|
+
}
|
|
13
|
+
export function isPrivateApp(appNode) {
|
|
14
|
+
return (appNode?.config.distribution?.toLowerCase() ===
|
|
15
|
+
APP_DISTRIBUTION_TYPES.PRIVATE);
|
|
16
|
+
}
|
|
17
|
+
export function isMarketplaceApp(appNode) {
|
|
18
|
+
return (appNode?.config.distribution?.toLowerCase() ===
|
|
19
|
+
APP_DISTRIBUTION_TYPES.MARKETPLACE);
|
|
20
|
+
}
|
|
21
|
+
export function getAppInstallationState(isInstalledWithScopeGroups, previouslyAuthorizedScopeGroups) {
|
|
22
|
+
if (isInstalledWithScopeGroups) {
|
|
23
|
+
return APP_INSTALLATION_STATES.INSTALLED;
|
|
24
|
+
}
|
|
25
|
+
if (previouslyAuthorizedScopeGroups.length > 0) {
|
|
26
|
+
return APP_INSTALLATION_STATES.INSTALLED_WITH_OUTDATED_SCOPES;
|
|
27
|
+
}
|
|
28
|
+
return APP_INSTALLATION_STATES.NOT_INSTALLED;
|
|
29
|
+
}
|
|
30
|
+
export function canUseStaticAuthTestAccountInstall({ accountConfig, appNode, parentAccountId, }) {
|
|
31
|
+
if (!accountConfig || !isStaticAuthApp(appNode)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const accountCanUseTestInstall = isDeveloperTestAccount(accountConfig) || isSandbox(accountConfig);
|
|
35
|
+
if (!accountCanUseTestInstall) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (parentAccountId === undefined) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return accountConfig.parentAccountId === parentAccountId;
|
|
42
|
+
}
|
package/lib/app/logs.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID } from 'crypto';
|
|
|
3
3
|
import open from 'open';
|
|
4
4
|
import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
|
|
5
5
|
import CLIWebSocketServer from '../CLIWebSocketServer.js';
|
|
6
|
-
import {
|
|
6
|
+
import { ACCOUNT_AUTH_UI_MESSAGE_RECEIVE_TYPES, ACCOUNT_AUTH_UI_MESSAGE_SEND_TYPES, ACCOUNT_AUTH_WEBSOCKET_SERVER_VERSION, } from '../constants.js';
|
|
7
7
|
import { personalAccessKeyPrompt } from '../prompts/personalAccessKeyPrompt.js';
|
|
8
8
|
import SpinniesManager from '../ui/SpinniesManager.js';
|
|
9
9
|
import { uiLogger } from '../ui/logger.js';
|
|
@@ -55,7 +55,8 @@ function createMessageHandler(server, cliCallbackToken, resolveWebsocketPak) {
|
|
|
55
55
|
}
|
|
56
56
|
async function awaitKeypressOrWebsocketPak(websocketPakPromise) {
|
|
57
57
|
if (!process.stdin.isTTY) {
|
|
58
|
-
|
|
58
|
+
const pak = await websocketPakPromise;
|
|
59
|
+
return { type: 'pak', pak };
|
|
59
60
|
}
|
|
60
61
|
SpinniesManager.init();
|
|
61
62
|
SpinniesManager.add(SPINNER_ID, {
|
package/lib/doctor/Doctor.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare class Doctor {
|
|
|
17
17
|
private checkIfNodeIsInstalled;
|
|
18
18
|
private checkIfNpmIsInstalled;
|
|
19
19
|
private checkCLIVersion;
|
|
20
|
+
private checkMcpReadiness;
|
|
20
21
|
private checkIfNpmInstallRequired;
|
|
21
22
|
private isValidJsonFile;
|
|
22
23
|
private checkProjectConfigJsonFiles;
|
package/lib/doctor/Doctor.js
CHANGED
|
@@ -23,6 +23,8 @@ import { pkg } from '../jsonLoader.js';
|
|
|
23
23
|
import { lib } from '../../lang/en.js';
|
|
24
24
|
import { uiLink } from '../ui/index.js';
|
|
25
25
|
import { isServerRunningAtUrl } from '../http.js';
|
|
26
|
+
import { detectConfiguredMcpClients } from '../mcp/promotion.js';
|
|
27
|
+
import { MCP_CLIENTS } from '../mcp/clients.js';
|
|
26
28
|
import { WEBHOOKS_KEY, APP_KEY } from '@hubspot/project-parsing-lib/constants';
|
|
27
29
|
import { validateProjectConfig } from '../projects/config.js';
|
|
28
30
|
import { validateSourceDirectory, handleTranslate, } from '../projects/upload.js';
|
|
@@ -73,6 +75,7 @@ export class Doctor {
|
|
|
73
75
|
this.checkIfNodeIsInstalled(),
|
|
74
76
|
this.checkIfNpmIsInstalled(),
|
|
75
77
|
this.checkCLIVersion(),
|
|
78
|
+
this.checkMcpReadiness(),
|
|
76
79
|
];
|
|
77
80
|
}
|
|
78
81
|
performNetworkingChecks() {
|
|
@@ -252,6 +255,29 @@ export class Doctor {
|
|
|
252
255
|
});
|
|
253
256
|
}
|
|
254
257
|
}
|
|
258
|
+
async checkMcpReadiness() {
|
|
259
|
+
try {
|
|
260
|
+
const configuredClients = detectConfiguredMcpClients();
|
|
261
|
+
const allClientIds = MCP_CLIENTS.map(c => c.id);
|
|
262
|
+
const unconfiguredClients = allClientIds.filter(id => !configuredClients.includes(id));
|
|
263
|
+
if (configuredClients.length > 0) {
|
|
264
|
+
this.diagnosis?.addCliSection({
|
|
265
|
+
type: 'success',
|
|
266
|
+
message: lib.doctor.mcpChecks.configured(configuredClients),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (unconfiguredClients.length > 0) {
|
|
270
|
+
this.diagnosis?.addCliSection({
|
|
271
|
+
type: 'warning',
|
|
272
|
+
message: lib.doctor.mcpChecks.notConfigured(unconfiguredClients),
|
|
273
|
+
secondaryMessaging: lib.doctor.mcpChecks.notConfiguredSecondary,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
uiLogger.debug(getErrorMessage(e));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
255
281
|
async checkIfNpmInstallRequired() {
|
|
256
282
|
let foundError = false;
|
|
257
283
|
for (const packageFile of this.diagnosticInfo?.packageFiles || []) {
|
|
@@ -73,7 +73,7 @@ async function fetchAppAfterDeploy(accountId, env) {
|
|
|
73
73
|
async function buildProjectMetadata(projectDir, projectConfig, accountId) {
|
|
74
74
|
const srcDir = path.join(projectDir, projectConfig.srcDir);
|
|
75
75
|
const projectMetadata = await getProjectMetadata(srcDir);
|
|
76
|
-
const intermediateRepresentation = await translate({
|
|
76
|
+
const { intermediateRepresentation } = await translate({
|
|
77
77
|
projectSourceDir: srcDir,
|
|
78
78
|
platformVersion: projectConfig.platformVersion,
|
|
79
79
|
accountId,
|
|
@@ -111,6 +111,7 @@ export async function uploadAndDeployAction({ accountId, projectDest, }) {
|
|
|
111
111
|
isUploadCommand: false,
|
|
112
112
|
sendIR: !isLegacyProject(projectConfig.platformVersion),
|
|
113
113
|
skipValidation: false,
|
|
114
|
+
force: true,
|
|
114
115
|
});
|
|
115
116
|
if (uploadError) {
|
|
116
117
|
throw new ProjectUploadError(commands.getStarted.errors.uploadActionFailed, uploadError);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const MCP_SERVER_NAME = "HubSpotDev";
|
|
2
|
+
export type McpClientId = 'codex' | 'claude' | 'cursor' | 'gemini' | 'vscode' | 'windsurf';
|
|
3
|
+
type McpClientDetection = {
|
|
4
|
+
type: 'json' | 'text';
|
|
5
|
+
pathSegments: string[];
|
|
6
|
+
};
|
|
7
|
+
export type McpClient = {
|
|
8
|
+
id: McpClientId;
|
|
9
|
+
detection: McpClientDetection;
|
|
10
|
+
};
|
|
11
|
+
export declare const MCP_CLIENTS: McpClient[];
|
|
12
|
+
export declare function getMcpClientPathSegments(id: McpClientId): string[];
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const MCP_SERVER_NAME = 'HubSpotDev';
|
|
2
|
+
function getVsCodeMcpConfigPathSegments() {
|
|
3
|
+
if (process.platform === 'darwin') {
|
|
4
|
+
return ['Library', 'Application Support', 'Code', 'User', 'mcp.json'];
|
|
5
|
+
}
|
|
6
|
+
if (process.platform === 'win32') {
|
|
7
|
+
return ['AppData', 'Roaming', 'Code', 'User', 'mcp.json'];
|
|
8
|
+
}
|
|
9
|
+
return ['.config', 'Code', 'User', 'mcp.json'];
|
|
10
|
+
}
|
|
11
|
+
export const MCP_CLIENTS = [
|
|
12
|
+
{
|
|
13
|
+
id: 'codex',
|
|
14
|
+
detection: {
|
|
15
|
+
type: 'text',
|
|
16
|
+
pathSegments: ['.codex', 'config.toml'],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'claude',
|
|
21
|
+
detection: { type: 'json', pathSegments: ['.claude.json'] },
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'cursor',
|
|
25
|
+
detection: { type: 'json', pathSegments: ['.cursor', 'mcp.json'] },
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'gemini',
|
|
29
|
+
detection: {
|
|
30
|
+
type: 'json',
|
|
31
|
+
pathSegments: ['.gemini', 'settings.json'],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'vscode',
|
|
36
|
+
detection: {
|
|
37
|
+
type: 'json',
|
|
38
|
+
pathSegments: getVsCodeMcpConfigPathSegments(),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'windsurf',
|
|
43
|
+
detection: {
|
|
44
|
+
type: 'json',
|
|
45
|
+
pathSegments: ['.codeium', 'windsurf', 'mcp_config.json'],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
export function getMcpClientPathSegments(id) {
|
|
50
|
+
const client = MCP_CLIENTS.find(c => c.id === id);
|
|
51
|
+
if (!client) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return client.detection.pathSegments;
|
|
55
|
+
}
|