@hubspot/cli 8.5.0 → 8.6.0-beta.1

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.
Files changed (69) hide show
  1. package/bin/cli.js +4 -3
  2. package/commands/account/clean.js +2 -0
  3. package/commands/account/createOverride.js +3 -0
  4. package/commands/account/info.js +34 -16
  5. package/commands/account/link.d.ts +4 -0
  6. package/commands/account/link.js +89 -0
  7. package/commands/account/list.js +29 -71
  8. package/commands/account/remove.js +2 -0
  9. package/commands/account/removeOverride.js +3 -0
  10. package/commands/account/unlink.d.ts +4 -0
  11. package/commands/account/unlink.js +70 -0
  12. package/commands/account/use.js +71 -1
  13. package/commands/account.js +4 -0
  14. package/commands/project/appInstallStatus.d.ts +4 -0
  15. package/commands/project/appInstallStatus.js +132 -0
  16. package/commands/project/create.js +8 -0
  17. package/commands/project/dev/deprecatedFlow.js +20 -2
  18. package/commands/project/dev/index.js +6 -0
  19. package/commands/project/dev/unifiedFlow.js +36 -10
  20. package/commands/project/lint.js +20 -2
  21. package/commands/project.js +2 -0
  22. package/lang/en.d.ts +103 -0
  23. package/lang/en.js +117 -8
  24. package/lib/app/migrate.js +2 -1
  25. package/lib/constants.d.ts +1 -0
  26. package/lib/constants.js +3 -0
  27. package/lib/doctor/Doctor.js +5 -5
  28. package/lib/link/accountTableUtils.d.ts +10 -0
  29. package/lib/link/accountTableUtils.js +39 -0
  30. package/lib/link/index.d.ts +18 -0
  31. package/lib/link/index.js +185 -0
  32. package/lib/link/linkUtils.d.ts +5 -0
  33. package/lib/link/linkUtils.js +49 -0
  34. package/lib/link/prompts.d.ts +7 -0
  35. package/lib/link/prompts.js +126 -0
  36. package/lib/link/renderLinkedAccountsTable.d.ts +2 -0
  37. package/lib/link/renderLinkedAccountsTable.js +14 -0
  38. package/lib/link/warnIfLinkedDirectory.d.ts +1 -0
  39. package/lib/link/warnIfLinkedDirectory.js +9 -0
  40. package/lib/projects/localDev/DevServerManager_DEPRECATED.d.ts +2 -1
  41. package/lib/projects/localDev/DevServerManager_DEPRECATED.js +2 -2
  42. package/lib/projects/localDev/LocalDevManager_DEPRECATED.d.ts +2 -0
  43. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -0
  44. package/lib/projects/uieLinting.d.ts +17 -3
  45. package/lib/projects/uieLinting.js +93 -28
  46. package/lib/prompts/projectDevTargetAccountPrompt.d.ts +1 -0
  47. package/lib/prompts/projectDevTargetAccountPrompt.js +10 -0
  48. package/lib/prompts/promptUtils.js +1 -0
  49. package/lib/ui/accountTable.d.ts +8 -0
  50. package/lib/ui/accountTable.js +67 -0
  51. package/lib/yargs/parseYargsOrExit.d.ts +4 -0
  52. package/lib/yargs/parseYargsOrExit.js +25 -0
  53. package/mcp-server/server.js +39 -1
  54. package/mcp-server/tools/index.js +2 -0
  55. package/mcp-server/tools/project/AddFeatureToProjectTool.js +1 -1
  56. package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
  57. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  58. package/mcp-server/tools/project/FindProjectsTool.d.ts +15 -0
  59. package/mcp-server/tools/project/FindProjectsTool.js +60 -0
  60. package/mcp-server/tools/project/GetBuildLogsTool.js +1 -1
  61. package/mcp-server/tools/project/GetBuildStatusTool.js +1 -1
  62. package/mcp-server/tools/project/UploadProjectTools.js +1 -1
  63. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  64. package/package.json +2 -2
  65. package/types/Link.d.ts +32 -0
  66. package/types/Link.js +5 -0
  67. package/types/PackageJson.d.ts +1 -0
  68. package/types/Prompts.d.ts +1 -0
  69. package/types/Yargs.d.ts +1 -0
@@ -0,0 +1,132 @@
1
+ import path from 'path';
2
+ import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
3
+ import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
4
+ import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
5
+ import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
6
+ import { translateForLocalDev } from '@hubspot/project-parsing-lib/translate';
7
+ import { makeYargsHandlerWithUsageTracking } from '../../lib/yargs/makeYargsHandlerWithUsageTracking.js';
8
+ import { makeYargsBuilder } from '../../lib/yargsUtils.js';
9
+ import { uiLogger } from '../../lib/ui/logger.js';
10
+ import { ApiErrorContext, debugError, logError, } from '../../lib/errorHandlers/index.js';
11
+ import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
12
+ import { commands } from '../../lang/en.js';
13
+ import { getProjectConfig } from '../../lib/projects/config.js';
14
+ import { isAppIRNode } from '../../lib/projects/structure.js';
15
+ import { APP_AUTH_TYPES } from '../../lib/constants.js';
16
+ const command = 'app-install-status';
17
+ const describe = commands.project.installStatus.describe;
18
+ async function handler(args) {
19
+ const { derivedAccountId, formatOutputAsJson, exit } = args;
20
+ const { projectConfig, projectDir } = await getProjectConfig();
21
+ if (!projectConfig || !projectDir) {
22
+ uiLogger.error(commands.project.installStatus.errors.noProjectConfig);
23
+ return exit(EXIT_CODES.ERROR);
24
+ }
25
+ if (isLegacyProject(projectConfig.platformVersion)) {
26
+ uiLogger.error(commands.project.installStatus.errors.unsupportedPlatformVersion(projectConfig.platformVersion));
27
+ return exit(EXIT_CODES.ERROR);
28
+ }
29
+ let appNode;
30
+ try {
31
+ const { intermediateNodesIndexedByUid } = await translateForLocalDev({
32
+ projectSourceDir: path.join(projectDir, projectConfig.srcDir),
33
+ platformVersion: projectConfig.platformVersion,
34
+ accountId: derivedAccountId,
35
+ }, { skipValidation: true });
36
+ appNode = Object.values(intermediateNodesIndexedByUid).find(isAppIRNode);
37
+ }
38
+ catch (error) {
39
+ debugError(error);
40
+ uiLogger.error(commands.project.installStatus.errors.failedToParseProject);
41
+ return exit(EXIT_CODES.ERROR);
42
+ }
43
+ if (!appNode) {
44
+ uiLogger.error(commands.project.installStatus.errors.noAppInProject);
45
+ return exit(EXIT_CODES.ERROR);
46
+ }
47
+ if (appNode.config.auth.type.toLowerCase() !== APP_AUTH_TYPES.STATIC) {
48
+ uiLogger.error(commands.project.installStatus.errors.unsupportedAuthType(appNode.config.auth.type));
49
+ return exit(EXIT_CODES.ERROR);
50
+ }
51
+ let projectId;
52
+ try {
53
+ const response = await fetchProject(derivedAccountId, projectConfig.name);
54
+ projectId = response.data.id;
55
+ }
56
+ catch (error) {
57
+ logError(error, new ApiErrorContext({
58
+ accountId: derivedAccountId,
59
+ projectName: projectConfig.name,
60
+ }));
61
+ return exit(EXIT_CODES.ERROR);
62
+ }
63
+ let isInstalledWithScopeGroups = false;
64
+ let previouslyAuthorizedScopeGroups = [];
65
+ let appId;
66
+ try {
67
+ const response = await fetchAppInstallationData(derivedAccountId, projectId, appNode.uid, appNode.config.auth.requiredScopes, appNode.config.auth.optionalScopes);
68
+ isInstalledWithScopeGroups = response.data.isInstalledWithScopeGroups;
69
+ previouslyAuthorizedScopeGroups =
70
+ response.data.previouslyAuthorizedScopeGroups;
71
+ appId = response.data.appId;
72
+ }
73
+ catch (error) {
74
+ if (!isHubSpotHttpError(error) || error.status !== 404) {
75
+ logError(error, new ApiErrorContext({
76
+ accountId: derivedAccountId,
77
+ projectName: projectConfig.name,
78
+ }));
79
+ return exit(EXIT_CODES.ERROR);
80
+ }
81
+ }
82
+ const isInstalled = isInstalledWithScopeGroups || previouslyAuthorizedScopeGroups.length > 0;
83
+ if (formatOutputAsJson) {
84
+ uiLogger.json({
85
+ appId,
86
+ appUid: appNode.uid,
87
+ accountId: derivedAccountId,
88
+ projectId,
89
+ isInstalled,
90
+ isInstalledWithCurrentScopes: isInstalledWithScopeGroups,
91
+ previouslyAuthorizedScopeGroups,
92
+ });
93
+ return exit(isInstalled ? EXIT_CODES.SUCCESS : EXIT_CODES.WARNING);
94
+ }
95
+ if (isInstalled) {
96
+ if (isInstalledWithScopeGroups) {
97
+ uiLogger.success(commands.project.installStatus.success.installed(appNode.config.name, derivedAccountId));
98
+ }
99
+ else {
100
+ uiLogger.success(commands.project.installStatus.success.installedWithOutdatedScopes(appNode.config.name, derivedAccountId));
101
+ }
102
+ return exit(EXIT_CODES.SUCCESS);
103
+ }
104
+ uiLogger.log(commands.project.installStatus.notInstalled(appNode.config.name, derivedAccountId));
105
+ return exit(EXIT_CODES.WARNING);
106
+ }
107
+ function projectInstallStatusBuilder(yargs) {
108
+ yargs.example([
109
+ [
110
+ '$0 project app-install-status',
111
+ commands.project.installStatus.examples.default,
112
+ ],
113
+ [
114
+ '$0 project app-install-status --json',
115
+ commands.project.installStatus.examples.json,
116
+ ],
117
+ ]);
118
+ return yargs;
119
+ }
120
+ const builder = makeYargsBuilder(projectInstallStatusBuilder, command, describe, {
121
+ useGlobalOptions: true,
122
+ useConfigOptions: true,
123
+ useAccountOptions: true,
124
+ useJSONOutputOptions: true,
125
+ });
126
+ const projectInstallStatusCommand = {
127
+ command,
128
+ describe,
129
+ handler: makeYargsHandlerWithUsageTracking('project-app-install-status', handler),
130
+ builder,
131
+ };
132
+ export default projectInstallStatusCommand;
@@ -19,8 +19,15 @@ import { updateHsMetaFilesWithAutoGeneratedFields } from '../../lib/projects/com
19
19
  import SpinniesManager from '../../lib/ui/SpinniesManager.js';
20
20
  const command = ['create', 'init'];
21
21
  const describe = commands.project.create.describe;
22
+ const BETA_VERSIONS = [
23
+ PLATFORM_VERSIONS.v2026_09_BETA,
24
+ PLATFORM_VERSIONS.v2026_03_BETA,
25
+ ];
22
26
  async function handler(args) {
23
27
  const { platformVersion, templateSource, exit, addUsageMetadata } = args;
28
+ if (BETA_VERSIONS.includes(platformVersion)) {
29
+ uiLogger.warn(commands.project.create.warnings.betaPlatformVersion(platformVersion));
30
+ }
24
31
  if (templateSource && !templateSource.includes('/')) {
25
32
  uiLogger.error(commands.project.create.errors.invalidTemplateSource);
26
33
  return exit(EXIT_CODES.ERROR);
@@ -128,6 +135,7 @@ function projectCreateBuilder(yargs) {
128
135
  PLATFORM_VERSIONS.v2025_2,
129
136
  PLATFORM_VERSIONS.v2026_03_BETA,
130
137
  PLATFORM_VERSIONS.v2026_03,
138
+ PLATFORM_VERSIONS.v2026_09_BETA,
131
139
  ],
132
140
  default: PLATFORM_VERSIONS.v2026_03,
133
141
  },
@@ -1,4 +1,5 @@
1
- import { getConfigAccountById, getAllConfigAccounts, getConfigAccountEnvironment, } from '@hubspot/local-dev-lib/config';
1
+ import { getConfigAccountById, getLinkedOrAllConfigAccounts, getConfigAccountEnvironment, } from '@hubspot/local-dev-lib/config';
2
+ import { getHsSettingsFileIfExists, getHsSettingsFilePath, } from '@hubspot/local-dev-lib/config/hsSettings';
2
3
  import { findProjectComponents, getProjectComponentTypes, } from '../../../lib/projects/structure.js';
3
4
  import { ComponentTypes } from '../../../types/Projects.js';
4
5
  import { commands } from '../../../lang/en.js';
@@ -11,6 +12,7 @@ import { handleExit } from '../../../lib/process.js';
11
12
  import { getErrorMessage } from '../../../lib/errorHandlers/index.js';
12
13
  import { isSandbox, isDeveloperTestAccount, } from '../../../lib/accountTypes.js';
13
14
  import { ensureProjectExists } from '../../../lib/projects/ensureProjectExists.js';
15
+ import { isDirectoryLinked, addAccountToLinkedSettings, } from '../../../lib/link/linkUtils.js';
14
16
  export async function deprecatedProjectDevFlow({ args, accountId, projectConfig, projectDir, }) {
15
17
  const { userProvidedAccount, derivedAccountId, exit } = args;
16
18
  const env = getConfigAccountEnvironment(derivedAccountId);
@@ -32,7 +34,9 @@ export async function deprecatedProjectDevFlow({ args, accountId, projectConfig,
32
34
  uiLogger.error(commands.project.dev.errors.invalidProjectComponents);
33
35
  return exit(EXIT_CODES.SUCCESS);
34
36
  }
35
- const accounts = getAllConfigAccounts();
37
+ const hsSettings = getHsSettingsFileIfExists();
38
+ const directoryIsLinked = isDirectoryLinked(hsSettings);
39
+ const accounts = getLinkedOrAllConfigAccounts();
36
40
  if (!accounts) {
37
41
  uiLogger.error(commands.project.dev.errors.noAccountsInConfig);
38
42
  return exit(EXIT_CODES.ERROR);
@@ -69,6 +73,10 @@ export async function deprecatedProjectDevFlow({ args, accountId, projectConfig,
69
73
  else {
70
74
  await checkIfDefaultAccountIsSupported(accountConfig, hasPublicApps, exit);
71
75
  }
76
+ if (directoryIsLinked) {
77
+ uiLogger.log('');
78
+ uiLogger.info(commands.account.subcommands.link.shared.usingLinkedAccounts(getHsSettingsFilePath()));
79
+ }
72
80
  // The user is targeting an account type that we recommend developing on
73
81
  if (!targetProjectAccountId && bypassRecommendedAccountPrompt) {
74
82
  targetTestingAccountId = derivedAccountId;
@@ -101,6 +109,9 @@ export async function deprecatedProjectDevFlow({ args, accountId, projectConfig,
101
109
  if (!accountAdded) {
102
110
  return exit(EXIT_CODES.SUCCESS);
103
111
  }
112
+ if (directoryIsLinked) {
113
+ addAccountToLinkedSettings(notInConfigAccount.id);
114
+ }
104
115
  }
105
116
  createNewSandbox = hasPrivateApps && createNestedAccount;
106
117
  createNewDeveloperTestAccount = hasPublicApps && createNestedAccount;
@@ -114,6 +125,9 @@ export async function deprecatedProjectDevFlow({ args, accountId, projectConfig,
114
125
  }
115
126
  // We will be running our tests against this new sandbox account
116
127
  targetTestingAccountId = targetProjectAccountId;
128
+ if (directoryIsLinked) {
129
+ addAccountToLinkedSettings(targetProjectAccountId);
130
+ }
117
131
  }
118
132
  if (createNewDeveloperTestAccount) {
119
133
  try {
@@ -123,6 +137,9 @@ export async function deprecatedProjectDevFlow({ args, accountId, projectConfig,
123
137
  return exit(EXIT_CODES.ERROR);
124
138
  }
125
139
  targetProjectAccountId = derivedAccountId;
140
+ if (directoryIsLinked) {
141
+ addAccountToLinkedSettings(targetTestingAccountId);
142
+ }
126
143
  }
127
144
  if (!targetProjectAccountId || !targetTestingAccountId) {
128
145
  uiLogger.error(commands.project.dev.errors.noAccount(accountId));
@@ -156,6 +173,7 @@ export async function deprecatedProjectDevFlow({ args, accountId, projectConfig,
156
173
  targetAccountId: targetTestingAccountId,
157
174
  env,
158
175
  exit,
176
+ port: args.port,
159
177
  });
160
178
  await LocalDev.start();
161
179
  handleExit(({ isSIGHUP }) => LocalDev.stop(!isSIGHUP));
@@ -13,6 +13,7 @@ import { uiLogger } from '../../../lib/ui/logger.js';
13
13
  import { logError } from '../../../lib/errorHandlers/index.js';
14
14
  import { projectProfilePrompt } from '../../../lib/prompts/projectProfilePrompt.js';
15
15
  import { isPromptExitError } from '../../../lib/errors/PromptExitError.js';
16
+ import { LOCAL_DEV_DEFAULT_PORT } from '../../../lib/constants.js';
16
17
  const command = 'dev';
17
18
  const describe = commands.project.dev.describe;
18
19
  function validateAccountFlags(testingAccount, projectAccount, userProvidedAccount, useV2) {
@@ -149,6 +150,11 @@ function projectDevBuilder(yargs) {
149
150
  type: 'string',
150
151
  description: commands.project.dev.options.account,
151
152
  });
153
+ yargs.option('port', {
154
+ type: 'number',
155
+ description: commands.project.dev.options.port,
156
+ default: LOCAL_DEV_DEFAULT_PORT,
157
+ });
152
158
  yargs.example([['$0 project dev', commands.project.dev.examples.default]]);
153
159
  yargs.conflicts('profile', 'account');
154
160
  yargs.conflicts('profile', 'testing-account');
@@ -3,13 +3,13 @@ import util from 'util';
3
3
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
4
4
  import { startPortManagerServer, stopPortManagerServer, } from '@hubspot/local-dev-lib/portManager';
5
5
  import { isTranslationError, translateForLocalDev, } from '@hubspot/project-parsing-lib/translate';
6
- import { getConfigAccountEnvironment, getAllConfigAccounts, getConfigAccountById, } from '@hubspot/local-dev-lib/config';
6
+ import { getConfigAccountEnvironment, getLinkedOrAllConfigAccounts, getConfigAccountById, getConfigAccountIfExists, } from '@hubspot/local-dev-lib/config';
7
7
  import { logError } from '../../../lib/errorHandlers/index.js';
8
8
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
9
9
  import { ensureProjectExists } from '../../../lib/projects/ensureProjectExists.js';
10
10
  import { createInitialBuildForNewProject, createNewProjectForLocalDev, compareLocalProjectToDeployed, checkAndInstallDependencies, } from '../../../lib/projects/localDev/helpers/project.js';
11
11
  import { useExistingDevTestAccount, createDeveloperTestAccountForLocalDev, selectAccountTypePrompt, createSandboxForLocalDev, } from '../../../lib/projects/localDev/helpers/account.js';
12
- import { selectDeveloperTestTargetAccountPrompt, selectSandboxTargetAccountPrompt, } from '../../../lib/prompts/projectDevTargetAccountPrompt.js';
12
+ import { selectDeveloperTestTargetAccountPrompt, selectSandboxTargetAccountPrompt, confirmLinkExistingDeveloperTestAccountPrompt, } from '../../../lib/prompts/projectDevTargetAccountPrompt.js';
13
13
  import LocalDevProcess from '../../../lib/projects/localDev/LocalDevProcess.js';
14
14
  import LocalDevWatcher from '../../../lib/projects/localDev/LocalDevWatcher.js';
15
15
  import { handleExit, handleKeypress } from '../../../lib/process.js';
@@ -18,6 +18,8 @@ import { uiLogger } from '../../../lib/ui/logger.js';
18
18
  import { commands } from '../../../lang/en.js';
19
19
  import LocalDevWebsocketServer from '../../../lib/projects/localDev/LocalDevWebsocketServer.js';
20
20
  import { isLocalDevRunning } from '../../../lib/projects/localDev/helpers/process.js';
21
+ import { getHsSettingsFileIfExists, getHsSettingsFilePath, } from '@hubspot/local-dev-lib/config/hsSettings';
22
+ import { isDirectoryLinked, addAccountToLinkedSettings, } from '../../../lib/link/linkUtils.js';
21
23
  export async function unifiedProjectDevFlow({ args, targetProjectAccountId, providedTargetTestingAccountId, projectConfig, projectDir, }) {
22
24
  const { exit } = args;
23
25
  if (await isLocalDevRunning()) {
@@ -56,13 +58,19 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
56
58
  uiLogger.error(commands.project.dev.errors.noAccount(targetProjectAccountId));
57
59
  return exit(EXIT_CODES.ERROR);
58
60
  }
59
- const accounts = getAllConfigAccounts();
61
+ const hsSettings = getHsSettingsFileIfExists();
62
+ const directoryIsLinked = isDirectoryLinked(hsSettings);
63
+ const accounts = getLinkedOrAllConfigAccounts();
60
64
  const accountIsCombined = await isUnifiedAccount(targetProjectAccountConfig);
61
65
  const targetProjectAccountIsTestAccountOrSandbox = isTestAccountOrSandbox(targetProjectAccountConfig);
62
66
  if (!accountIsCombined) {
63
67
  uiLogger.error(commands.project.dev.errors.accountNotCombined);
64
68
  return exit(EXIT_CODES.ERROR);
65
69
  }
70
+ if (directoryIsLinked && !providedTargetTestingAccountId) {
71
+ uiLogger.log('');
72
+ uiLogger.info(commands.account.subcommands.link.shared.usingLinkedAccounts(getHsSettingsFilePath()));
73
+ }
66
74
  let targetTestingAccountId = providedTargetTestingAccountId;
67
75
  // Temporarily removing logic to use profile account as testing account
68
76
  // if (profileConfig) {
@@ -81,14 +89,26 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
81
89
  const accountType = await selectAccountTypePrompt(targetProjectAccountConfig);
82
90
  if (accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST) {
83
91
  const devAccountPromptResponse = await selectDeveloperTestTargetAccountPrompt(accounts, targetProjectAccountConfig);
92
+ const { notInConfigAccount } = devAccountPromptResponse;
84
93
  targetTestingAccountId =
85
94
  devAccountPromptResponse.targetAccountId || undefined;
86
- if (!!devAccountPromptResponse.notInConfigAccount) {
87
- // When the developer test account isn't configured in the CLI config yet
88
- // Walk the user through adding the account's PAK to the config
89
- const accountAdded = await useExistingDevTestAccount(env, devAccountPromptResponse.notInConfigAccount);
90
- if (!accountAdded) {
91
- return exit(EXIT_CODES.SUCCESS);
95
+ if (notInConfigAccount) {
96
+ const existingGlobalConfig = getConfigAccountIfExists(notInConfigAccount.id);
97
+ if (directoryIsLinked && existingGlobalConfig) {
98
+ const shouldLink = await confirmLinkExistingDeveloperTestAccountPrompt(notInConfigAccount.accountName);
99
+ if (!shouldLink) {
100
+ return exit(EXIT_CODES.SUCCESS);
101
+ }
102
+ addAccountToLinkedSettings(notInConfigAccount.id);
103
+ }
104
+ else {
105
+ const accountAdded = await useExistingDevTestAccount(env, notInConfigAccount);
106
+ if (!accountAdded) {
107
+ return exit(EXIT_CODES.SUCCESS);
108
+ }
109
+ if (directoryIsLinked) {
110
+ addAccountToLinkedSettings(notInConfigAccount.id);
111
+ }
92
112
  }
93
113
  }
94
114
  else if (devAccountPromptResponse.createNestedAccount) {
@@ -99,6 +119,9 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
99
119
  catch {
100
120
  return exit(EXIT_CODES.ERROR);
101
121
  }
122
+ if (directoryIsLinked) {
123
+ addAccountToLinkedSettings(targetTestingAccountId);
124
+ }
102
125
  }
103
126
  }
104
127
  else if (accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX) {
@@ -112,6 +135,9 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
112
135
  catch {
113
136
  return exit(EXIT_CODES.ERROR);
114
137
  }
138
+ if (directoryIsLinked) {
139
+ addAccountToLinkedSettings(targetTestingAccountId);
140
+ }
115
141
  }
116
142
  }
117
143
  else {
@@ -143,7 +169,7 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
143
169
  }
144
170
  // End setup, start local dev process
145
171
  try {
146
- await startPortManagerServer();
172
+ await startPortManagerServer(args.port);
147
173
  }
148
174
  catch (e) {
149
175
  logError(e);
@@ -10,7 +10,7 @@ import { logError } from '../../lib/errorHandlers/index.js';
10
10
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
11
11
  import { promptUser } from '../../lib/prompts/promptUtils.js';
12
12
  import SpinniesManager from '../../lib/ui/SpinniesManager.js';
13
- import { areAllLintPackagesInstalled, getMissingLintPackages, lintPackages, displayLintResults, hasEslintConfig, hasDeprecatedEslintConfig, getDeprecatedEslintConfigFiles, createEslintConfig, REQUIRED_PACKAGES_AND_MIN_VERSIONS, } from '../../lib/projects/uieLinting.js';
13
+ import { areAllLintPackagesInstalled, getMissingLintPackages, getMissingLintScripts, addLintScriptsToPackageJson, lintPackages, displayLintResults, hasEslintConfig, hasDeprecatedEslintConfig, getDeprecatedEslintConfigFiles, createEslintConfig, REQUIRED_PACKAGES_AND_MIN_VERSIONS, } from '../../lib/projects/uieLinting.js';
14
14
  const command = 'lint';
15
15
  const describe = commands.project.lint.help.describe;
16
16
  async function handler(args) {
@@ -117,9 +117,10 @@ async function handler(args) {
117
117
  SpinniesManager.add('lintConfigCreate', {
118
118
  text: commands.project.lint.loading.creatingConfig,
119
119
  });
120
+ const platformVersion = projectConfig.projectConfig?.platformVersion ?? null;
120
121
  const createdConfigs = [];
121
122
  for (const location of locationsNeedingConfig) {
122
- const configPath = createEslintConfig(location);
123
+ const configPath = await createEslintConfig(location, platformVersion);
123
124
  createdConfigs.push(configPath);
124
125
  }
125
126
  SpinniesManager.succeed('lintConfigCreate');
@@ -132,6 +133,23 @@ async function handler(args) {
132
133
  return exit(EXIT_CODES.ERROR);
133
134
  }
134
135
  }
136
+ const locationsNeedingScripts = locationsReadyToLint.filter(location => getMissingLintScripts(location).length > 0);
137
+ if (locationsNeedingScripts.length > 0) {
138
+ SpinniesManager.add('lintScriptsAdd', {
139
+ text: commands.project.lint.loading.addingLintScripts,
140
+ });
141
+ const addedResults = [];
142
+ for (const location of locationsNeedingScripts) {
143
+ const result = addLintScriptsToPackageJson(location);
144
+ if (result.added.length > 0) {
145
+ addedResults.push(result);
146
+ }
147
+ }
148
+ SpinniesManager.succeed('lintScriptsAdd');
149
+ addedResults.forEach(({ added, relativePath }) => {
150
+ uiLogger.success(commands.project.lint.lintScriptsAdded(added, relativePath));
151
+ });
152
+ }
135
153
  SpinniesManager.add('lintRun', {
136
154
  text: commands.project.lint.loading.linting,
137
155
  });
@@ -19,6 +19,7 @@ import projectValidate from './project/validate.js';
19
19
  import list from './project/list.js';
20
20
  import info from './project/info.js';
21
21
  import deleteProject from './project/delete.js';
22
+ import appInstallStatus from './project/appInstallStatus.js';
22
23
  import { makeYargsBuilder } from '../lib/yargsUtils.js';
23
24
  import { getProjectConfig } from '../lib/projects/config.js';
24
25
  import { isSupportedPlatformVersion, LATEST_SUPPORTED_PLATFORM_VERSION, } from '@hubspot/project-parsing-lib/projects';
@@ -66,6 +67,7 @@ function projectBuilder(yargs) {
66
67
  .command(updateDeps)
67
68
  .command(profile)
68
69
  .command(projectValidate)
70
+ .command(appInstallStatus)
69
71
  .demandCommand(1, '');
70
72
  return yargs;
71
73
  }
package/lang/en.d.ts CHANGED
@@ -129,8 +129,12 @@ export declare const commands: {
129
129
  };
130
130
  list: {
131
131
  accounts: string;
132
+ allAccounts: string;
133
+ linkedAccounts: string;
132
134
  defaultAccountTitle: string;
135
+ linkedDefaultTitle: string;
133
136
  currentResolvedDefaultAccount: (accountId: number) => string;
137
+ directory: (dir: string) => string;
134
138
  describe: string;
135
139
  configPath: (configPath: string) => string;
136
140
  overrideFilePathTitle: string;
@@ -177,6 +181,70 @@ export declare const commands: {
177
181
  success: {
178
182
  defaultAccountUpdated: (accountName: string) => string;
179
183
  };
184
+ linked: {
185
+ editingLinkedDefault: (dir: string) => string;
186
+ alreadyDefault: (accountId: number) => string;
187
+ setLinkedDefault: (account: string) => string;
188
+ accountNotLinked: (account: string) => string;
189
+ promptToLink: (account: string) => string;
190
+ settingGlobalDefault: string;
191
+ nonInteractiveNotLinked: (account: string) => string;
192
+ };
193
+ };
194
+ link: {
195
+ describe: string;
196
+ verboseDescribe: string;
197
+ shared: {
198
+ noLinkedAccounts: string;
199
+ globalAccountsAvailable: (count: number) => string;
200
+ configurePrompt: string;
201
+ deprecatedConfigNotSupported: (command: string) => string;
202
+ writeSettingsFailed: (path: string, err: unknown) => string;
203
+ savedToSettings: (path: string) => string;
204
+ usingLinkedAccounts: (settingsPath: string) => string;
205
+ accountAutoLinked: (accountId: number) => string;
206
+ accountAutoLinkFailed: (accountId: number) => string;
207
+ };
208
+ linkingDirectory: (dir: string) => string;
209
+ managingLinkedAccounts: (dir: string) => string;
210
+ settingsInfo: (path: string) => string;
211
+ success: {
212
+ created: (path: string) => string;
213
+ };
214
+ errors: {
215
+ authFailed: string;
216
+ };
217
+ events: {
218
+ accountsLinked: (count: number) => string;
219
+ accountsUnlinked: (count: number) => string;
220
+ overrideAccountDetected: (accountId: number) => string;
221
+ defaultAccountSet: (accountId: number) => string;
222
+ defaultAccountRemoved: (isSelectionRequired: boolean) => string;
223
+ defaultAccountRemains: (accountId: number) => string;
224
+ updatedLinkedAccounts: string;
225
+ noAccountsLinked: string;
226
+ overrideFileRemoved: string;
227
+ invalidDefaultAccount: (accountId: number) => string;
228
+ };
229
+ prompts: {
230
+ howToProceed: string;
231
+ whatToDo: string;
232
+ linkExisting: string;
233
+ authenticateNew: string;
234
+ cancel: string;
235
+ selectDefault: string;
236
+ selectToLink: string;
237
+ selectToUnlink: string;
238
+ alreadyLinked: string;
239
+ fromHsAccount: string;
240
+ newlyAuthenticated: string;
241
+ mustSelectOne: string;
242
+ keepAsDefault: string;
243
+ };
244
+ };
245
+ unlink: {
246
+ describe: string;
247
+ verboseDescribe: string;
180
248
  };
181
249
  remove: {
182
250
  describe: string;
@@ -245,6 +313,9 @@ export declare const commands: {
245
313
  };
246
314
  name: (name: string) => string;
247
315
  scopeGroups: string;
316
+ linkedDefaultTitle: string;
317
+ settingsPath: (path: string) => string;
318
+ linkedDefault: (account: string) => string;
248
319
  };
249
320
  clean: {
250
321
  describe: string;
@@ -1448,6 +1519,7 @@ export declare const commands: {
1448
1519
  projectAccount: string;
1449
1520
  testingAccount: string;
1450
1521
  account: string;
1522
+ port: string;
1451
1523
  };
1452
1524
  };
1453
1525
  create: {
@@ -1458,6 +1530,9 @@ export declare const commands: {
1458
1530
  failedToFetchProjectList: string;
1459
1531
  cannotNestProjects: (projectDir: string) => string;
1460
1532
  };
1533
+ warnings: {
1534
+ betaPlatformVersion: (platformVersion: string) => string;
1535
+ };
1461
1536
  logs: {
1462
1537
  success: (projectName: string, projectDest: string) => string;
1463
1538
  };
@@ -1865,6 +1940,7 @@ export declare const commands: {
1865
1940
  loading: {
1866
1941
  checking: string;
1867
1942
  creatingConfig: string;
1943
+ addingLintScripts: string;
1868
1944
  linting: string;
1869
1945
  };
1870
1946
  noProjectConfig: string;
@@ -1877,8 +1953,12 @@ export declare const commands: {
1877
1953
  }[]) => string;
1878
1954
  createEslintConfigPrompt: (directories: string[]) => string;
1879
1955
  eslintConfigCreated: (configPath: string) => string;
1956
+ createEslintConfigRequiresV2Platform: (platformVersion?: string | null) => string;
1957
+ failedToFetchRemoteEslintConfig: (platformVersion: string) => string;
1880
1958
  failedToCreateEslintConfig: (configPath: string) => string;
1881
1959
  eslintConfigRequired: string;
1960
+ lintScriptsAdded: (scriptNames: string[], packageJsonPath: string) => string;
1961
+ failedToAddLintScripts: (packageJsonPath: string) => string;
1882
1962
  };
1883
1963
  updateDeps: {
1884
1964
  help: {
@@ -2010,6 +2090,25 @@ export declare const commands: {
2010
2090
  force: string;
2011
2091
  };
2012
2092
  };
2093
+ installStatus: {
2094
+ describe: string;
2095
+ examples: {
2096
+ default: string;
2097
+ json: string;
2098
+ };
2099
+ errors: {
2100
+ noProjectConfig: string;
2101
+ unsupportedPlatformVersion: (platformVersion: string) => string;
2102
+ failedToParseProject: string;
2103
+ noAppInProject: string;
2104
+ unsupportedAuthType: (authType: string) => string;
2105
+ };
2106
+ success: {
2107
+ installed: (appName: string, accountId: number) => string;
2108
+ installedWithOutdatedScopes: (appName: string, accountId: number) => string;
2109
+ };
2110
+ notInstalled: (appName: string, accountId: number) => string;
2111
+ };
2013
2112
  };
2014
2113
  sandbox: {
2015
2114
  describe: string;
@@ -2978,6 +3077,9 @@ export declare const commands: {
2978
3077
  };
2979
3078
  };
2980
3079
  export declare const lib: {
3080
+ linkedDirectory: {
3081
+ warning: (action: string, settingsPath: string) => string;
3082
+ };
2981
3083
  parsing: {
2982
3084
  unableToParseStringToNumber: string;
2983
3085
  };
@@ -3521,6 +3623,7 @@ export declare const lib: {
3521
3623
  developerTestAccountLimit: (limit: number) => string;
3522
3624
  confirmDefaultAccount: (accountName: string, accountType: string) => string;
3523
3625
  confirmUseExistingDeveloperTestAccount: (accountName: string) => string;
3626
+ confirmLinkExistingDeveloperTestAccount: (accountName: string) => string;
3524
3627
  noAccountId: string;
3525
3628
  };
3526
3629
  projectLogsPrompt: {