@hubspot/cli 8.5.0 → 8.6.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/clean.js +2 -0
- package/commands/account/createOverride.js +3 -0
- package/commands/account/info.js +34 -16
- package/commands/account/link.d.ts +4 -0
- package/commands/account/link.js +89 -0
- package/commands/account/list.js +29 -71
- package/commands/account/remove.js +2 -0
- package/commands/account/removeOverride.js +3 -0
- package/commands/account/unlink.d.ts +4 -0
- package/commands/account/unlink.js +70 -0
- package/commands/account/use.js +71 -1
- package/commands/account.js +4 -0
- package/commands/project/appInstallStatus.d.ts +4 -0
- package/commands/project/appInstallStatus.js +132 -0
- package/commands/project/create.js +8 -0
- package/commands/project/dev/deprecatedFlow.js +20 -2
- package/commands/project/dev/index.js +6 -0
- package/commands/project/dev/unifiedFlow.js +20 -3
- package/commands/project/lint.js +20 -2
- package/commands/project.js +2 -0
- package/lang/en.d.ts +102 -0
- package/lang/en.js +116 -8
- package/lib/app/migrate.js +2 -1
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +3 -0
- package/lib/doctor/Doctor.js +5 -5
- package/lib/link/accountTableUtils.d.ts +10 -0
- package/lib/link/accountTableUtils.js +39 -0
- package/lib/link/index.d.ts +18 -0
- package/lib/link/index.js +185 -0
- package/lib/link/linkUtils.d.ts +5 -0
- package/lib/link/linkUtils.js +49 -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/link/warnIfLinkedDirectory.d.ts +1 -0
- package/lib/link/warnIfLinkedDirectory.js +9 -0
- package/lib/projects/localDev/DevServerManager_DEPRECATED.d.ts +2 -1
- package/lib/projects/localDev/DevServerManager_DEPRECATED.js +2 -2
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.d.ts +2 -0
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -0
- package/lib/projects/uieLinting.d.ts +17 -3
- package/lib/projects/uieLinting.js +93 -28
- package/lib/prompts/promptUtils.js +1 -0
- package/lib/ui/accountTable.d.ts +8 -0
- package/lib/ui/accountTable.js +67 -0
- package/mcp-server/server.js +39 -1
- package/mcp-server/tools/index.js +2 -0
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +1 -1
- package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
- package/mcp-server/tools/project/DeployProjectTool.js +1 -1
- package/mcp-server/tools/project/FindProjectsTool.d.ts +15 -0
- package/mcp-server/tools/project/FindProjectsTool.js +60 -0
- package/mcp-server/tools/project/GetBuildLogsTool.js +1 -1
- package/mcp-server/tools/project/GetBuildStatusTool.js +1 -1
- package/mcp-server/tools/project/UploadProjectTools.js +1 -1
- package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
- package/package.json +2 -2
- package/types/Link.d.ts +32 -0
- package/types/Link.js +5 -0
- package/types/PackageJson.d.ts +1 -0
- package/types/Prompts.d.ts +1 -0
- package/types/Yargs.d.ts +1 -0
package/lang/en.js
CHANGED
|
@@ -2,11 +2,12 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { mapToUserFriendlyName } from '@hubspot/project-parsing-lib/transform';
|
|
3
3
|
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
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { LOCAL_DEV_DEFAULT_PORT } from '../lib/constants.js';
|
|
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';
|
|
8
|
+
import { getLocalDevUiUrl, getProjectDetailUrl, getProjectSettingsUrl, } from '../lib/projects/urls.js';
|
|
8
9
|
import { getProductUpdatesUrl } from '../lib/links.js';
|
|
9
|
-
import { APP_DISTRIBUTION_TYPES,
|
|
10
|
+
import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, LEGACY_PUBLIC_APP_FILE, PROJECT_CONFIG_FILE, PROJECT_WITH_APP, } from '../lib/constants.js';
|
|
10
11
|
export const commands = {
|
|
11
12
|
generalErrors: {
|
|
12
13
|
srcIsProject: (src, command) => `"${src}" is in a project folder. Did you mean "hs project ${command}"?`,
|
|
@@ -137,8 +138,12 @@ export const commands = {
|
|
|
137
138
|
},
|
|
138
139
|
list: {
|
|
139
140
|
accounts: `${chalk.bold('Accounts')}:`,
|
|
141
|
+
allAccounts: `${chalk.bold('All Accounts')}:`,
|
|
142
|
+
linkedAccounts: `${chalk.bold('Linked Accounts')}:`,
|
|
140
143
|
defaultAccountTitle: `${chalk.bold('Default Account')}`,
|
|
144
|
+
linkedDefaultTitle: `${chalk.bold('Linked Default Account')}`,
|
|
141
145
|
currentResolvedDefaultAccount: (accountId) => `Account: ${uiAccountDescription(accountId)}`,
|
|
146
|
+
directory: (dir) => `Directory: ${dir}`,
|
|
142
147
|
describe: 'List names of accounts defined in config.',
|
|
143
148
|
configPath: (configPath) => `Source: ${configPath}`,
|
|
144
149
|
overrideFilePathTitle: `${chalk.bold('Default Account Override')}`,
|
|
@@ -185,6 +190,70 @@ export const commands = {
|
|
|
185
190
|
success: {
|
|
186
191
|
defaultAccountUpdated: (accountName) => `Default account updated to "${accountName}"`,
|
|
187
192
|
},
|
|
193
|
+
linked: {
|
|
194
|
+
editingLinkedDefault: (dir) => `Editing the default linked account for this directory (not the global default):\n${indent(1)}${dir}`,
|
|
195
|
+
alreadyDefault: (accountId) => `${uiAccountDescription(accountId)} is the only linked account and is already the default.`,
|
|
196
|
+
setLinkedDefault: (account) => `Linked default set to ${chalk.cyan(account)}`,
|
|
197
|
+
accountNotLinked: (account) => `${account} is not linked to this directory.`,
|
|
198
|
+
promptToLink: (account) => `Would you like to link ${account} to this directory?`,
|
|
199
|
+
settingGlobalDefault: 'Setting global default instead.',
|
|
200
|
+
nonInteractiveNotLinked: (account) => `${account} is not linked to this directory. Setting global default instead.`,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
link: {
|
|
204
|
+
describe: 'Link authenticated HubSpot accounts to the current directory',
|
|
205
|
+
verboseDescribe: `Link one or more authenticated HubSpot accounts to the current directory. Linked accounts are saved in a local ${chalk.bold('.hs/settings.json')} file (added to .gitignore automatically).\n\nThis lets the CLI know which account to use when you run commands from this directory, without changing the global default. Run ${uiCommandReference('hs account current')} to see the active configuration.`,
|
|
206
|
+
shared: {
|
|
207
|
+
noLinkedAccounts: 'No HubSpot accounts are linked in this directory.',
|
|
208
|
+
globalAccountsAvailable: (count) => `You have ${chalk.cyan(count.toString())} ${count === 1 ? 'account' : 'accounts'} in your global config.`,
|
|
209
|
+
configurePrompt: `To configure this directory, run:\n${indent(1)}${uiCommandReference('hs account link')}`,
|
|
210
|
+
deprecatedConfigNotSupported: (command) => `${uiCommandReference(command)} does not support deprecated config. Run ${uiCommandReference('hs config migrate')} to migrate to global config. Then re-run ${uiCommandReference(command)}`,
|
|
211
|
+
writeSettingsFailed: (path, err) => `Failed to write to ${path}: ${err}`,
|
|
212
|
+
savedToSettings: (path) => `Saved to ${path}`,
|
|
213
|
+
usingLinkedAccounts: (settingsPath) => `This directory has linked accounts via ${settingsPath}. Only linked accounts will be available for this command. To manage linked accounts, run ${uiCommandReference('hs account link')}.`,
|
|
214
|
+
accountAutoLinked: (accountId) => `Automatically linked ${uiAccountDescription(accountId)} to this directory.`,
|
|
215
|
+
accountAutoLinkFailed: (accountId) => `Could not automatically link ${uiAccountDescription(accountId)} to this directory. Run ${uiCommandReference('hs account link')} to link it manually.`,
|
|
216
|
+
},
|
|
217
|
+
linkingDirectory: (dir) => `Linking HubSpot account(s) for directory:\n${indent(1)}${dir}`,
|
|
218
|
+
managingLinkedAccounts: (dir) => `Managing linked accounts for:\n${indent(1)}${dir}`,
|
|
219
|
+
settingsInfo: (path) => `\u2139 Accounts linked here are saved in ${path}`,
|
|
220
|
+
success: {
|
|
221
|
+
created: (path) => `Linked to ${path} (created .hs and added to .gitignore).\n${indent(1)}Now when you work within this directory, the CLI will use the linked account(s).`,
|
|
222
|
+
},
|
|
223
|
+
errors: {
|
|
224
|
+
authFailed: `Authentication failed to complete`,
|
|
225
|
+
},
|
|
226
|
+
events: {
|
|
227
|
+
accountsLinked: (count) => `Linked ${count} account${count !== 1 ? 's' : ''}`,
|
|
228
|
+
accountsUnlinked: (count) => `Unlinked ${count} account${count !== 1 ? 's' : ''}`,
|
|
229
|
+
overrideAccountDetected: (accountId) => `\nCurrent default account (from .hsaccount):\n${indent(1)}${uiAccountDescription(accountId)}`,
|
|
230
|
+
defaultAccountSet: (accountId) => `Linked default account set to ${chalk.cyan(uiAccountDescription(accountId))}`,
|
|
231
|
+
defaultAccountRemoved: (isSelectionRequired) => `The linked default account was removed. ${isSelectionRequired ? 'A linked default account is required.\n' : ''}`,
|
|
232
|
+
defaultAccountRemains: (accountId) => `Linked default account remains ${chalk.cyan(uiAccountDescription(accountId))}`,
|
|
233
|
+
updatedLinkedAccounts: 'Updated linked accounts',
|
|
234
|
+
noAccountsLinked: `All accounts have been unlinked. Your global config accounts will be used.\n${indent(1)}The ${chalk.bold('.hs')} directory is no longer needed and can be safely deleted.`,
|
|
235
|
+
overrideFileRemoved: '.hsaccount file removed',
|
|
236
|
+
invalidDefaultAccount: (accountId) => `Default account ${uiAccountDescription(accountId)} is not in the linked accounts list and has been reset. Please select a new default.`,
|
|
237
|
+
},
|
|
238
|
+
prompts: {
|
|
239
|
+
howToProceed: 'How would you like to link an account?',
|
|
240
|
+
whatToDo: 'Which action would you like to perform?',
|
|
241
|
+
linkExisting: 'Link existing authenticated account(s)',
|
|
242
|
+
authenticateNew: 'Authenticate and link a new account',
|
|
243
|
+
cancel: 'Cancel',
|
|
244
|
+
selectDefault: 'Select a linked default account',
|
|
245
|
+
selectToLink: 'Select authenticated account(s) to link:',
|
|
246
|
+
selectToUnlink: 'Select account(s) to unlink',
|
|
247
|
+
alreadyLinked: '- Already linked',
|
|
248
|
+
fromHsAccount: '(from .hsaccount)',
|
|
249
|
+
newlyAuthenticated: '(just authenticated)',
|
|
250
|
+
mustSelectOne: 'You must select at least one account to link',
|
|
251
|
+
keepAsDefault: 'Keep this as the default?',
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
unlink: {
|
|
255
|
+
describe: 'Unlink HubSpot account(s) from the current directory',
|
|
256
|
+
verboseDescribe: `Remove one or more linked accounts from this directory's ${chalk.bold('.hs/settings.json')}. If the default account is removed, you will be prompted to select a new one.\n\nThis does not delete the account from the global config - it only removes the local association.`,
|
|
188
257
|
},
|
|
189
258
|
remove: {
|
|
190
259
|
describe: 'Remove an account from the config.',
|
|
@@ -253,6 +322,9 @@ export const commands = {
|
|
|
253
322
|
},
|
|
254
323
|
name: (name) => `${chalk.bold('Account name')}: ${name}`,
|
|
255
324
|
scopeGroups: `${chalk.bold('Scopes available')}:`,
|
|
325
|
+
linkedDefaultTitle: `${chalk.bold('Linked Default Account')}`,
|
|
326
|
+
settingsPath: (path) => `Source: ${path}`,
|
|
327
|
+
linkedDefault: (account) => `Account: ${account}`,
|
|
256
328
|
},
|
|
257
329
|
clean: {
|
|
258
330
|
describe: 'Check for inactive accounts and removes them from the CLI config.',
|
|
@@ -1464,6 +1536,7 @@ export const commands = {
|
|
|
1464
1536
|
projectAccount: 'The id of the account to upload your project to. Must be used with --testing-account. Supported on platform versions 2025.2 and newer.',
|
|
1465
1537
|
testingAccount: 'The id of the account to install apps and test on. Must be used with --project-account. Supported on platform versions 2025.2 and newer.',
|
|
1466
1538
|
account: 'The id of the account to upload your project to. Unsupported on platform versions 2025.2 and newer.',
|
|
1539
|
+
port: `The port for the local dev server. Defaults to ${LOCAL_DEV_DEFAULT_PORT}.`,
|
|
1467
1540
|
},
|
|
1468
1541
|
},
|
|
1469
1542
|
create: {
|
|
@@ -1474,6 +1547,9 @@ export const commands = {
|
|
|
1474
1547
|
failedToFetchProjectList: 'Failed to fetch the list of available project templates. Please try again later.',
|
|
1475
1548
|
cannotNestProjects: (projectDir) => `A project already exists at ${projectDir}. Projects cannot be nested within other projects. Please choose a different destination and try again.`,
|
|
1476
1549
|
},
|
|
1550
|
+
warnings: {
|
|
1551
|
+
betaPlatformVersion: (platformVersion) => `Your portal must be enrolled in the beta to use platform version ${platformVersion}. If you are not enrolled in the beta, your upload will fail. ${uiLink('Enroll in the beta', getProductUpdatesUrl('286886'))}`,
|
|
1552
|
+
},
|
|
1477
1553
|
logs: {
|
|
1478
1554
|
success: (projectName, projectDest) => `Project ${chalk.bold(projectName)} was successfully created in ${projectDest}`,
|
|
1479
1555
|
},
|
|
@@ -1881,6 +1957,7 @@ export const commands = {
|
|
|
1881
1957
|
loading: {
|
|
1882
1958
|
checking: 'Checking lint packages and configuration…',
|
|
1883
1959
|
creatingConfig: 'Creating ESLint configuration files…',
|
|
1960
|
+
addingLintScripts: 'Adding lint scripts to package.json files…',
|
|
1884
1961
|
linting: 'Linting…',
|
|
1885
1962
|
},
|
|
1886
1963
|
noProjectConfig: 'No project detected. Run this command from a project directory.',
|
|
@@ -1900,8 +1977,17 @@ export const commands = {
|
|
|
1900
1977
|
},
|
|
1901
1978
|
createEslintConfigPrompt: (directories) => `ESLint configuration file not found in the following ${directories.length === 1 ? 'directory' : 'directories'}:\n${directories.map(d => ` - ${d}`).join('\n')}\n\nWould you like to set up the required ESLint configuration?`,
|
|
1902
1979
|
eslintConfigCreated: (configPath) => `ESLint configuration created at ${configPath}`,
|
|
1980
|
+
createEslintConfigRequiresV2Platform: (platformVersion) => {
|
|
1981
|
+
if (!platformVersion) {
|
|
1982
|
+
return 'Automatic ESLint configuration requires a Developer Platform project (2025.2 or later) with platformVersion set in hsproject.json. Add an eslint.config.js file manually, or set platformVersion and try again.';
|
|
1983
|
+
}
|
|
1984
|
+
return `Automatic ESLint configuration is not available for platform version ${platformVersion}. Use Developer Platform 2025.2 or later, or add eslint.config.js manually.`;
|
|
1985
|
+
},
|
|
1986
|
+
failedToFetchRemoteEslintConfig: (platformVersion) => `Could not download the ESLint config from HubSpot project components for platform version ${platformVersion}. Check your network connection and try again, or add eslint.config.js manually.`,
|
|
1903
1987
|
failedToCreateEslintConfig: (configPath) => `Failed to create ESLint configuration at ${configPath}`,
|
|
1904
1988
|
eslintConfigRequired: 'ESLint configuration is required to run the lint command. Run the command again to create the configuration.',
|
|
1989
|
+
lintScriptsAdded: (scriptNames, packageJsonPath) => `Added ${scriptNames.map(s => `"${s}"`).join(' and ')} to ${packageJsonPath}`,
|
|
1990
|
+
failedToAddLintScripts: (packageJsonPath) => `Failed to add lint scripts to ${packageJsonPath}`,
|
|
1905
1991
|
},
|
|
1906
1992
|
updateDeps: {
|
|
1907
1993
|
help: {
|
|
@@ -2030,6 +2116,25 @@ export const commands = {
|
|
|
2030
2116
|
force: 'skip confirmation prompt',
|
|
2031
2117
|
},
|
|
2032
2118
|
},
|
|
2119
|
+
installStatus: {
|
|
2120
|
+
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.',
|
|
2121
|
+
examples: {
|
|
2122
|
+
default: 'Check install status for the static auth app in the current project',
|
|
2123
|
+
json: 'Output install status as JSON',
|
|
2124
|
+
},
|
|
2125
|
+
errors: {
|
|
2126
|
+
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.`,
|
|
2127
|
+
unsupportedPlatformVersion: (platformVersion) => `This command is only supported for projects on platform version 2025.2 or later (detected: ${platformVersion}).`,
|
|
2128
|
+
failedToParseProject: 'Failed to parse the project. Check your project configuration is valid and try again.',
|
|
2129
|
+
noAppInProject: 'No app was found in the local project. Install status is only available for projects that contain an app.',
|
|
2130
|
+
unsupportedAuthType: (authType) => `This command only supports static auth apps. Detected auth type: ${authType}.`,
|
|
2131
|
+
},
|
|
2132
|
+
success: {
|
|
2133
|
+
installed: (appName, accountId) => `${chalk.bold(appName)} is installed in ${uiAccountDescription(accountId)}.`,
|
|
2134
|
+
installedWithOutdatedScopes: (appName, accountId) => `${chalk.bold(appName)} is installed in ${uiAccountDescription(accountId)} with outdated scopes. Reinstall the app to grant the latest scopes.`,
|
|
2135
|
+
},
|
|
2136
|
+
notInstalled: (appName, accountId) => `${chalk.bold(appName)} is not installed in ${uiAccountDescription(accountId)}.`,
|
|
2137
|
+
},
|
|
2033
2138
|
},
|
|
2034
2139
|
sandbox: {
|
|
2035
2140
|
describe: 'Commands for managing sandboxes.',
|
|
@@ -2345,7 +2450,7 @@ export const commands = {
|
|
|
2345
2450
|
opsLevel: 'Operations Hub tier. Options: FREE, STARTER, PROFESSIONAL, ENTERPRISE',
|
|
2346
2451
|
serviceLevel: 'Service Hub tier. Options: FREE, STARTER, PROFESSIONAL, ENTERPRISE',
|
|
2347
2452
|
salesLevel: 'Sales Hub tier. Options: FREE, STARTER, PROFESSIONAL, ENTERPRISE',
|
|
2348
|
-
contentLevel: '
|
|
2453
|
+
contentLevel: 'Content Hub tier. Options: FREE, STARTER, PROFESSIONAL, ENTERPRISE',
|
|
2349
2454
|
commerceLevel: 'Commerce Hub tier. Options: FREE, PROFESSIONAL, ENTERPRISE',
|
|
2350
2455
|
},
|
|
2351
2456
|
example: (configPath) => `Create a test account from the config file at ${configPath}`,
|
|
@@ -2998,6 +3103,9 @@ export const commands = {
|
|
|
2998
3103
|
},
|
|
2999
3104
|
};
|
|
3000
3105
|
export const lib = {
|
|
3106
|
+
linkedDirectory: {
|
|
3107
|
+
warning: (action, settingsPath) => `This directory has linked accounts via ${settingsPath}. ${uiCommandReference(action)} modifies your global config, not the linked directory settings. Use ${uiCommandReference('hs account link')} to manage linked accounts.\n`,
|
|
3108
|
+
},
|
|
3001
3109
|
parsing: {
|
|
3002
3110
|
unableToParseStringToNumber: 'Unable to parse string to number',
|
|
3003
3111
|
},
|
|
@@ -3964,9 +4072,9 @@ export const lib = {
|
|
|
3964
4072
|
validJson: 'JSON files valid',
|
|
3965
4073
|
},
|
|
3966
4074
|
port: {
|
|
3967
|
-
inUse: (port) => `
|
|
3968
|
-
inUseSecondary: `Make sure it is available before running ${uiCommandReference('hs project dev')}`,
|
|
3969
|
-
available: (port) => `
|
|
4075
|
+
inUse: (port) => `Default port ${port} is in use`,
|
|
4076
|
+
inUseSecondary: `Make sure it is available before running ${uiCommandReference('hs project dev')}, or use ${uiCommandReference('--port')} to specify a different port`,
|
|
4077
|
+
available: (port) => `Default port ${port} available for local development`,
|
|
3970
4078
|
},
|
|
3971
4079
|
projectValidation: {
|
|
3972
4080
|
valid: 'Project configuration and structure is valid',
|
package/lib/app/migrate.js
CHANGED
|
@@ -127,7 +127,8 @@ export async function selectAppToMigrate(allApps, derivedAccountId, appId) {
|
|
|
127
127
|
let hasLegacyCrmCards = false;
|
|
128
128
|
selectedApp?.migrationComponents.forEach(component => {
|
|
129
129
|
const userFacingComponentType = mapToUserFacingType(component.componentType);
|
|
130
|
-
if (component.componentType === 'LEGACY_CRM_CARD' &&
|
|
130
|
+
if (component.componentType === 'LEGACY_CRM_CARD' &&
|
|
131
|
+
!component.isSupported) {
|
|
131
132
|
hasLegacyCrmCards = true;
|
|
132
133
|
}
|
|
133
134
|
const shouldDisplayComponent = !AUTO_GENERATED_COMPONENT_TYPES.includes(userFacingComponentType);
|
package/lib/constants.d.ts
CHANGED
|
@@ -142,3 +142,4 @@ export declare const ACCOUNT_LEVELS: {
|
|
|
142
142
|
};
|
|
143
143
|
export declare const ACCOUNT_LEVEL_CHOICES: readonly ["FREE", "STARTER", "PROFESSIONAL", "ENTERPRISE"];
|
|
144
144
|
export declare const FEEDBACK_URL = "https://developers.hubspot.com/feedback";
|
|
145
|
+
export declare const LOCAL_DEV_DEFAULT_PORT = 4828;
|
package/lib/constants.js
CHANGED
|
@@ -143,3 +143,6 @@ export const ACCOUNT_LEVEL_CHOICES = [
|
|
|
143
143
|
ACCOUNT_LEVELS.ENTERPRISE,
|
|
144
144
|
];
|
|
145
145
|
export const FEEDBACK_URL = 'https://developers.hubspot.com/feedback';
|
|
146
|
+
// TODO: remove this constant and use PORT_MANAGER_SERVER_PORT from
|
|
147
|
+
// @hubspot/local-dev-lib once LDL changes its default to 4828.
|
|
148
|
+
export const LOCAL_DEV_DEFAULT_PORT = 4828;
|
package/lib/doctor/Doctor.js
CHANGED
|
@@ -10,8 +10,8 @@ import fs from 'fs';
|
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import { Diagnosis } from './Diagnosis.js';
|
|
12
12
|
import { DiagnosticInfoBuilder, } from './DiagnosticInfoBuilder.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { isPortAvailable } from '@hubspot/local-dev-lib/portManager';
|
|
14
|
+
import { LOCAL_DEV_DEFAULT_PORT } from '../constants.js';
|
|
15
15
|
import { accessTokenForPersonalAccessKey,
|
|
16
16
|
// scopesOnAccessToken,
|
|
17
17
|
} from '@hubspot/local-dev-lib/personalAccessKey';
|
|
@@ -322,16 +322,16 @@ export class Doctor {
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
async checkIfPortsAreAvailable() {
|
|
325
|
-
if (await
|
|
325
|
+
if (await isPortAvailable(LOCAL_DEV_DEFAULT_PORT)) {
|
|
326
326
|
this.diagnosis?.addProjectSection({
|
|
327
327
|
type: 'success',
|
|
328
|
-
message: lib.doctor.port.available(
|
|
328
|
+
message: lib.doctor.port.available(LOCAL_DEV_DEFAULT_PORT),
|
|
329
329
|
});
|
|
330
330
|
return;
|
|
331
331
|
}
|
|
332
332
|
this.diagnosis?.addProjectSection({
|
|
333
333
|
type: 'warning',
|
|
334
|
-
message: lib.doctor.port.inUse(
|
|
334
|
+
message: lib.doctor.port.inUse(LOCAL_DEV_DEFAULT_PORT),
|
|
335
335
|
secondaryMessaging: lib.doctor.port.inUseSecondary,
|
|
336
336
|
});
|
|
337
337
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface AccountRow {
|
|
2
|
+
name: string;
|
|
3
|
+
accountId: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function buildAccountRow(accountId: number, isDefault: boolean): AccountRow;
|
|
6
|
+
export declare function buildAccountHeader(nameColumnWidth: number): string;
|
|
7
|
+
export declare function getNameColumnWidth(rows: AccountRow[]): number;
|
|
8
|
+
export declare function sortDefaultFirst<T extends number | {
|
|
9
|
+
accountId: number;
|
|
10
|
+
}>(items: T[], defaultAccount: number | undefined): T[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { HUBSPOT_ACCOUNT_TYPE_STRINGS } from '@hubspot/local-dev-lib/constants/config';
|
|
2
|
+
import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { commands } from '../../lang/en.js';
|
|
5
|
+
import { indent } from '../ui/index.js';
|
|
6
|
+
import { INK_COLORS } from '../../ui/styles.js';
|
|
7
|
+
export function buildAccountRow(accountId, isDefault) {
|
|
8
|
+
const account = getConfigAccountIfExists(accountId);
|
|
9
|
+
let name = String(accountId);
|
|
10
|
+
if (account && account.accountType) {
|
|
11
|
+
const typeStr = HUBSPOT_ACCOUNT_TYPE_STRINGS[account.accountType];
|
|
12
|
+
name = `${account.name} [${typeStr}]`;
|
|
13
|
+
}
|
|
14
|
+
if (isDefault) {
|
|
15
|
+
name = `${name} (default)`;
|
|
16
|
+
}
|
|
17
|
+
return { name, accountId: String(accountId) };
|
|
18
|
+
}
|
|
19
|
+
export function buildAccountHeader(nameColumnWidth) {
|
|
20
|
+
const labels = commands.account.subcommands.list.labels;
|
|
21
|
+
const paddedName = chalk.bold(chalk.hex(INK_COLORS.INFO_BLUE)(labels.name.padEnd(nameColumnWidth)));
|
|
22
|
+
const accountId = chalk.bold(chalk.hex(INK_COLORS.INFO_BLUE)(labels.accountId));
|
|
23
|
+
return `${indent(1)}${paddedName} ${accountId}`;
|
|
24
|
+
}
|
|
25
|
+
export function getNameColumnWidth(rows) {
|
|
26
|
+
const labels = commands.account.subcommands.list.labels;
|
|
27
|
+
return Math.max(labels.name.length, ...rows.map(r => r.name.length));
|
|
28
|
+
}
|
|
29
|
+
export function sortDefaultFirst(items, defaultAccount) {
|
|
30
|
+
return [...items].sort((a, b) => {
|
|
31
|
+
const aId = typeof a === 'number' ? a : a.accountId;
|
|
32
|
+
const bId = typeof b === 'number' ? b : b.accountId;
|
|
33
|
+
if (aId === defaultAccount)
|
|
34
|
+
return -1;
|
|
35
|
+
if (bId === defaultAccount)
|
|
36
|
+
return 1;
|
|
37
|
+
return 0;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { HsSettingsFile } from '@hubspot/local-dev-lib/types/HsSettings';
|
|
2
|
+
import { ActionHandlerParams, ActionResult, LinkArgs } from '../../types/Link.js';
|
|
3
|
+
import { ArgumentsCamelCase } from 'yargs';
|
|
4
|
+
export declare class ActionHandlers {
|
|
5
|
+
static link({ state, context, }: ActionHandlerParams): Promise<ActionResult>;
|
|
6
|
+
static unlink({ state }: ActionHandlerParams): Promise<ActionResult>;
|
|
7
|
+
static authenticate({ state, context, args, }: ActionHandlerParams): Promise<ActionResult>;
|
|
8
|
+
static cancel(): Promise<ActionResult>;
|
|
9
|
+
}
|
|
10
|
+
export declare function handleLinkFlow({ settings, accountOverrideId, args, }: {
|
|
11
|
+
settings: HsSettingsFile;
|
|
12
|
+
accountOverrideId: number | null;
|
|
13
|
+
args: ArgumentsCamelCase<LinkArgs>;
|
|
14
|
+
}): Promise<ActionResult>;
|
|
15
|
+
export declare function handleLinkedUseAction({ state, targetAccountId, }: {
|
|
16
|
+
state: HsSettingsFile;
|
|
17
|
+
targetAccountId?: number;
|
|
18
|
+
}): Promise<ActionResult>;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { getAllConfigAccounts, getConfigDefaultAccountIfExists, } from '@hubspot/local-dev-lib/config';
|
|
2
|
+
import { confirmPrompt } from '../prompts/promptUtils.js';
|
|
3
|
+
import { promptForAccountsToLink, promptForAccountsToUnlink, promptForAction, promptForDefaultAccount, } from './prompts.js';
|
|
4
|
+
import { ACTION_RESULT_STATUS, } from '../../types/Link.js';
|
|
5
|
+
import { getDefaultAccountOverrideFilePath, removeDefaultAccountOverrideFile, } from '@hubspot/local-dev-lib/config/defaultAccountOverride';
|
|
6
|
+
import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
|
|
7
|
+
import { authenticateNewAccount } from '../accountAuth.js';
|
|
8
|
+
import { commands } from '../../lang/en.js';
|
|
9
|
+
import { uiLogger } from '../ui/logger.js';
|
|
10
|
+
export class ActionHandlers {
|
|
11
|
+
static async link({ state, context, }) {
|
|
12
|
+
const { eligibleAccounts, inEligibleAccounts } = context.globalAccountsList.reduce((accumulator, account) => {
|
|
13
|
+
if (state.accounts.includes(account.accountId)) {
|
|
14
|
+
accumulator.inEligibleAccounts.push(account);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
accumulator.eligibleAccounts.push(account);
|
|
18
|
+
}
|
|
19
|
+
return accumulator;
|
|
20
|
+
}, { eligibleAccounts: [], inEligibleAccounts: [] });
|
|
21
|
+
const toAdd = await promptForAccountsToLink(context, eligibleAccounts, inEligibleAccounts, state.localDefaultAccount);
|
|
22
|
+
const accounts = [...state.accounts, ...toAdd];
|
|
23
|
+
uiLogger.info(commands.account.subcommands.link.events.accountsLinked(toAdd.length));
|
|
24
|
+
const overrideFilePath = getDefaultAccountOverrideFilePath();
|
|
25
|
+
if (overrideFilePath &&
|
|
26
|
+
context.accountOverrideId &&
|
|
27
|
+
accounts.includes(context.accountOverrideId)) {
|
|
28
|
+
uiLogger.log(commands.account.subcommands.link.events.overrideAccountDetected(context.accountOverrideId));
|
|
29
|
+
uiLogger.log('');
|
|
30
|
+
const useOverride = await confirmPrompt(commands.account.subcommands.link.prompts.keepAsDefault);
|
|
31
|
+
removeDefaultAccountOverrideFile();
|
|
32
|
+
uiLogger.success(commands.account.subcommands.link.events.overrideFileRemoved);
|
|
33
|
+
if (useOverride) {
|
|
34
|
+
uiLogger.success(commands.account.subcommands.link.events.defaultAccountSet(context.accountOverrideId));
|
|
35
|
+
return {
|
|
36
|
+
status: ACTION_RESULT_STATUS.SUCCESS,
|
|
37
|
+
settings: {
|
|
38
|
+
accounts,
|
|
39
|
+
localDefaultAccount: context.accountOverrideId,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const localDefaultAccount = await resolveDefaultAccount({
|
|
45
|
+
accounts,
|
|
46
|
+
currentDefault: state.localDefaultAccount,
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
status: ACTION_RESULT_STATUS.SUCCESS,
|
|
50
|
+
settings: { accounts, localDefaultAccount },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
static async unlink({ state }) {
|
|
54
|
+
const toRemove = await promptForAccountsToUnlink(state.accounts, state.localDefaultAccount);
|
|
55
|
+
if (toRemove.length === 0) {
|
|
56
|
+
return { status: ACTION_RESULT_STATUS.NOOP };
|
|
57
|
+
}
|
|
58
|
+
const remainingAccounts = state.accounts.filter(account => !toRemove.includes(account));
|
|
59
|
+
const defaultWasRemoved = state.localDefaultAccount !== undefined &&
|
|
60
|
+
toRemove.includes(state.localDefaultAccount);
|
|
61
|
+
// All accounts removed
|
|
62
|
+
if (remainingAccounts.length === 0) {
|
|
63
|
+
uiLogger.success(commands.account.subcommands.link.events.noAccountsLinked);
|
|
64
|
+
return {
|
|
65
|
+
status: ACTION_RESULT_STATUS.SUCCESS,
|
|
66
|
+
settings: {
|
|
67
|
+
accounts: remainingAccounts,
|
|
68
|
+
localDefaultAccount: undefined,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Default was NOT removed — accounts remain, default unchanged
|
|
73
|
+
if (!defaultWasRemoved) {
|
|
74
|
+
uiLogger.success(commands.account.subcommands.link.events.accountsUnlinked(toRemove.length));
|
|
75
|
+
uiLogger.success(commands.account.subcommands.link.events.defaultAccountRemains(state.localDefaultAccount));
|
|
76
|
+
return {
|
|
77
|
+
status: ACTION_RESULT_STATUS.SUCCESS,
|
|
78
|
+
settings: {
|
|
79
|
+
accounts: remainingAccounts,
|
|
80
|
+
localDefaultAccount: state.localDefaultAccount,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// Default WAS removed — need a new default
|
|
85
|
+
uiLogger.warn(commands.account.subcommands.link.events.defaultAccountRemoved(remainingAccounts.length !== 1));
|
|
86
|
+
const localDefaultAccount = await resolveDefaultAccount({
|
|
87
|
+
accounts: remainingAccounts,
|
|
88
|
+
currentDefault: undefined,
|
|
89
|
+
});
|
|
90
|
+
if (remainingAccounts.length === 1) {
|
|
91
|
+
uiLogger.success(commands.account.subcommands.link.events.updatedLinkedAccounts);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
status: ACTION_RESULT_STATUS.SUCCESS,
|
|
95
|
+
settings: { accounts: remainingAccounts, localDefaultAccount },
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
static async authenticate({ state, context, args, }) {
|
|
99
|
+
const updatedConfig = await authenticateNewAccount({
|
|
100
|
+
env: args.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD,
|
|
101
|
+
setAsDefaultAccount: false,
|
|
102
|
+
});
|
|
103
|
+
if (!updatedConfig) {
|
|
104
|
+
return {
|
|
105
|
+
status: ACTION_RESULT_STATUS.ERROR,
|
|
106
|
+
reason: commands.account.subcommands.link.errors.authFailed,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const updatedContext = {
|
|
110
|
+
globalAccountsList: getAllConfigAccounts(),
|
|
111
|
+
globalDefaultAccount: getConfigDefaultAccountIfExists(),
|
|
112
|
+
accountOverrideId: context.accountOverrideId,
|
|
113
|
+
preselectedAccountId: updatedConfig.accountId,
|
|
114
|
+
};
|
|
115
|
+
return ActionHandlers.link({ state, context: updatedContext, args });
|
|
116
|
+
}
|
|
117
|
+
static async cancel() {
|
|
118
|
+
return {
|
|
119
|
+
status: ACTION_RESULT_STATUS.NOOP,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export async function handleLinkFlow({ settings, accountOverrideId, args, }) {
|
|
124
|
+
const context = {
|
|
125
|
+
globalAccountsList: getAllConfigAccounts(),
|
|
126
|
+
globalDefaultAccount: getConfigDefaultAccountIfExists(),
|
|
127
|
+
accountOverrideId,
|
|
128
|
+
};
|
|
129
|
+
const accounts = settings.accounts ?? [];
|
|
130
|
+
// The default account must be one of the linked accounts. This can get
|
|
131
|
+
// out of sync if the settings file is manually edited.
|
|
132
|
+
const hasInvalidDefault = settings.localDefaultAccount !== undefined &&
|
|
133
|
+
!accounts.includes(settings.localDefaultAccount);
|
|
134
|
+
if (hasInvalidDefault) {
|
|
135
|
+
uiLogger.warn(commands.account.subcommands.link.events.invalidDefaultAccount(settings.localDefaultAccount));
|
|
136
|
+
}
|
|
137
|
+
const initialState = {
|
|
138
|
+
localDefaultAccount: hasInvalidDefault
|
|
139
|
+
? undefined
|
|
140
|
+
: settings.localDefaultAccount,
|
|
141
|
+
accounts,
|
|
142
|
+
};
|
|
143
|
+
return runAction(initialState, context, args);
|
|
144
|
+
}
|
|
145
|
+
async function runAction(state, context, args) {
|
|
146
|
+
const action = await promptForAction(state);
|
|
147
|
+
return ActionHandlers[action]({ state, context, args });
|
|
148
|
+
}
|
|
149
|
+
async function resolveDefaultAccount({ accounts, currentDefault, prompt = '', }) {
|
|
150
|
+
if (accounts.length === 1) {
|
|
151
|
+
uiLogger.success(commands.account.subcommands.link.events.defaultAccountSet(accounts[0]));
|
|
152
|
+
return accounts[0];
|
|
153
|
+
}
|
|
154
|
+
return promptForDefaultAccount(accounts, currentDefault, prompt);
|
|
155
|
+
}
|
|
156
|
+
export async function handleLinkedUseAction({ state, targetAccountId, }) {
|
|
157
|
+
if (targetAccountId !== undefined) {
|
|
158
|
+
if (state.accounts.includes(targetAccountId)) {
|
|
159
|
+
uiLogger.success(commands.account.subcommands.link.events.defaultAccountSet(targetAccountId));
|
|
160
|
+
return {
|
|
161
|
+
status: ACTION_RESULT_STATUS.SUCCESS,
|
|
162
|
+
settings: {
|
|
163
|
+
accounts: state.accounts,
|
|
164
|
+
localDefaultAccount: targetAccountId,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const accounts = [...state.accounts, targetAccountId];
|
|
169
|
+
uiLogger.info(commands.account.subcommands.link.events.accountsLinked(1));
|
|
170
|
+
uiLogger.success(commands.account.subcommands.link.events.defaultAccountSet(targetAccountId));
|
|
171
|
+
return {
|
|
172
|
+
status: ACTION_RESULT_STATUS.SUCCESS,
|
|
173
|
+
settings: { accounts, localDefaultAccount: targetAccountId },
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const localDefaultAccount = await resolveDefaultAccount({
|
|
177
|
+
accounts: state.accounts,
|
|
178
|
+
currentDefault: state.localDefaultAccount,
|
|
179
|
+
});
|
|
180
|
+
uiLogger.success(commands.account.subcommands.link.events.defaultAccountSet(localDefaultAccount));
|
|
181
|
+
return {
|
|
182
|
+
status: ACTION_RESULT_STATUS.SUCCESS,
|
|
183
|
+
settings: { accounts: state.accounts, localDefaultAccount },
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { HsSettingsFile } from '@hubspot/local-dev-lib/types/HsSettings';
|
|
2
|
+
export declare function isDirectoryLinked(settings: HsSettingsFile | null): settings is HsSettingsFile;
|
|
3
|
+
export declare function hasDeprecatedConfigConflict(commandArgs: (string | number)[]): boolean;
|
|
4
|
+
export declare function addAccountToLinkedSettings(accountId: number): void;
|
|
5
|
+
export declare function writeLinkedSettings(settings: HsSettingsFile, settingsPath: string): boolean;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { localConfigFileExists } from '@hubspot/local-dev-lib/config';
|
|
2
|
+
import { getHsSettingsFileIfExists, writeHsSettingsFile, } from '@hubspot/local-dev-lib/config/hsSettings';
|
|
3
|
+
import { uiLogger } from '../ui/logger.js';
|
|
4
|
+
import { debugError } from '../errorHandlers/index.js';
|
|
5
|
+
import { commands } from '../../lang/en.js';
|
|
6
|
+
export function isDirectoryLinked(settings) {
|
|
7
|
+
return settings !== null && settings.accounts.length > 0;
|
|
8
|
+
}
|
|
9
|
+
export function hasDeprecatedConfigConflict(commandArgs) {
|
|
10
|
+
if (localConfigFileExists()) {
|
|
11
|
+
uiLogger.error(commands.account.subcommands.link.shared.deprecatedConfigNotSupported(`hs ${commandArgs.join(' ')}`));
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
export function addAccountToLinkedSettings(accountId) {
|
|
17
|
+
if (localConfigFileExists()) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const settings = getHsSettingsFileIfExists();
|
|
21
|
+
if (!settings || settings.accounts.length === 0) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (settings.accounts.includes(accountId)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const updated = {
|
|
28
|
+
...settings,
|
|
29
|
+
accounts: [...settings.accounts, accountId],
|
|
30
|
+
};
|
|
31
|
+
try {
|
|
32
|
+
writeHsSettingsFile(updated);
|
|
33
|
+
uiLogger.info(commands.account.subcommands.link.shared.accountAutoLinked(accountId));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
uiLogger.warn(commands.account.subcommands.link.shared.accountAutoLinkFailed(accountId));
|
|
37
|
+
debugError(err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function writeLinkedSettings(settings, settingsPath) {
|
|
41
|
+
try {
|
|
42
|
+
writeHsSettingsFile(settings);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
uiLogger.error(commands.account.subcommands.link.shared.writeSettingsFailed(settingsPath, err));
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
|
|
2
|
+
import { ActionName, LinkContext } from '../../types/Link.js';
|
|
3
|
+
import { HsSettingsFile } from '@hubspot/local-dev-lib/types/HsSettings';
|
|
4
|
+
export declare function promptForAction(state: HsSettingsFile): Promise<ActionName>;
|
|
5
|
+
export declare function promptForDefaultAccount(accounts: number[], currentDefaultAccount: number | undefined, prompt?: string): Promise<number>;
|
|
6
|
+
export declare function promptForAccountsToLink(context: LinkContext, eligibleAccounts: HubSpotConfigAccount[], inEligibleAccounts: HubSpotConfigAccount[], localDefaultAccount: number | undefined): Promise<number[]>;
|
|
7
|
+
export declare function promptForAccountsToUnlink(accounts: number[], localDefaultAccount: number | undefined): Promise<number[]>;
|