@hubspot/cli 8.0.12-experimental.0 → 8.0.12-experimental.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 (53) 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.js +3 -2
  6. package/commands/account/list.js +29 -71
  7. package/commands/account/remove.js +2 -0
  8. package/commands/account/removeOverride.js +3 -0
  9. package/commands/account/unlink.js +3 -2
  10. package/commands/account/use.js +71 -1
  11. package/commands/project/dev/deprecatedFlow.js +20 -2
  12. package/commands/project/dev/index.js +6 -0
  13. package/commands/project/dev/unifiedFlow.js +20 -3
  14. package/commands/project/lint.js +18 -1
  15. package/commands/project/upload.js +27 -15
  16. package/lang/en.d.ts +28 -0
  17. package/lang/en.js +33 -4
  18. package/lib/constants.d.ts +2 -0
  19. package/lib/constants.js +4 -0
  20. package/lib/doctor/Doctor.js +5 -5
  21. package/lib/link/index.d.ts +4 -0
  22. package/lib/link/index.js +40 -9
  23. package/lib/link/linkUtils.d.ts +1 -0
  24. package/lib/link/linkUtils.js +26 -1
  25. package/lib/link/warnIfLinkedDirectory.d.ts +1 -0
  26. package/lib/link/warnIfLinkedDirectory.js +9 -0
  27. package/lib/projects/localDev/DevServerManager_DEPRECATED.d.ts +2 -1
  28. package/lib/projects/localDev/DevServerManager_DEPRECATED.js +2 -2
  29. package/lib/projects/localDev/LocalDevManager_DEPRECATED.d.ts +2 -0
  30. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -0
  31. package/lib/projects/preview.js +11 -24
  32. package/lib/projects/uieLinting.d.ts +9 -0
  33. package/lib/projects/uieLinting.js +45 -1
  34. package/lib/ui/accountTable.d.ts +8 -0
  35. package/lib/ui/accountTable.js +67 -0
  36. package/lib/yargs/parseYargsOrExit.d.ts +4 -0
  37. package/lib/yargs/parseYargsOrExit.js +25 -0
  38. package/mcp-server/server.js +8 -4
  39. package/mcp-server/tools/index.js +2 -0
  40. package/mcp-server/tools/project/AddFeatureToProjectTool.js +1 -1
  41. package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
  42. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  43. package/mcp-server/tools/project/FindProjectsTool.d.ts +15 -0
  44. package/mcp-server/tools/project/FindProjectsTool.js +60 -0
  45. package/mcp-server/tools/project/GetBuildLogsTool.js +1 -1
  46. package/mcp-server/tools/project/GetBuildStatusTool.js +1 -1
  47. package/mcp-server/tools/project/UploadProjectTools.js +1 -1
  48. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  49. package/package.json +1 -1
  50. package/types/Link.d.ts +8 -3
  51. package/types/Link.js +5 -1
  52. package/types/PackageJson.d.ts +1 -0
  53. package/types/Yargs.d.ts +1 -0
package/bin/cli.js CHANGED
@@ -36,6 +36,7 @@ import { uiLogger } from '../lib/ui/logger.js';
36
36
  import { initializeSpinniesManager } from '../lib/middleware/spinniesMiddleware.js';
37
37
  import { addCommandSuggestions } from '../lib/commandSuggestion.js';
38
38
  import { pkg } from '../lib/jsonLoader.js';
39
+ import { parseYargsOrExit } from '../lib/yargs/parseYargsOrExit.js';
39
40
  function getTerminalWidth() {
40
41
  const width = yargs().terminalWidth();
41
42
  if (width >= 100)
@@ -74,7 +75,6 @@ const argv = yargs(process.argv.slice(2))
74
75
  initializeSpinniesManager,
75
76
  ])
76
77
  .exitProcess(false)
77
- .fail(handleFailure)
78
78
  .option('noHyperlinks', {
79
79
  default: false,
80
80
  describe: 'prevent hyperlinks from displaying in the ui',
@@ -119,7 +119,7 @@ const argv = yargs(process.argv.slice(2))
119
119
  .command(doctorCommand)
120
120
  .command(mcpCommand)
121
121
  .command(upgradeCommand);
122
- const argvWithSuggestions = addCommandSuggestions(argv)
122
+ const parser = addCommandSuggestions(argv)
123
123
  .help()
124
124
  .alias('h', 'help')
125
125
  .version(pkg.version)
@@ -127,7 +127,8 @@ const argvWithSuggestions = addCommandSuggestions(argv)
127
127
  .recommendCommands()
128
128
  .demandCommand(1, '')
129
129
  .wrap(getTerminalWidth())
130
- .strict().argv;
130
+ .strict();
131
+ const argvWithSuggestions = await parseYargsOrExit(parser, handleFailure);
131
132
  if ('help' in argvWithSuggestions && argvWithSuggestions.help !== undefined) {
132
133
  (async () => {
133
134
  await trackHelpUsage(getCommandName(argvWithSuggestions));
@@ -14,6 +14,7 @@ import { makeYargsBuilder } from '../../lib/yargsUtils.js';
14
14
  import { commands } from '../../lang/en.js';
15
15
  import { uiLogger } from '../../lib/ui/logger.js';
16
16
  import { renderList } from '../../ui/render.js';
17
+ import { warnIfLinkedDirectory } from '../../lib/link/warnIfLinkedDirectory.js';
17
18
  const command = 'clean';
18
19
  const describe = commands.account.subcommands.clean.describe;
19
20
  async function handler(args) {
@@ -24,6 +25,7 @@ async function handler(args) {
24
25
  uiLogger.log(commands.account.subcommands.clean.noResults);
25
26
  return exit(EXIT_CODES.SUCCESS);
26
27
  }
28
+ warnIfLinkedDirectory(args._);
27
29
  const accountsToRemove = [];
28
30
  SpinniesManager.init({
29
31
  succeedColor: 'white',
@@ -12,11 +12,14 @@ import { makeYargsHandlerWithUsageTracking } from '../../lib/yargs/makeYargsHand
12
12
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
13
13
  import { commands } from '../../lang/en.js';
14
14
  import { uiLogger } from '../../lib/ui/logger.js';
15
+ import { warnIfLinkedDirectory } from '../../lib/link/warnIfLinkedDirectory.js';
15
16
  const command = 'create-override [account]';
16
17
  const describe = commands.account.subcommands.createOverride.describe(DEFAULT_ACCOUNT_OVERRIDE_FILE_NAME);
17
18
  async function handler(args) {
18
19
  const { exit } = args;
19
20
  let overrideDefaultAccount = args.account;
21
+ // TODO: Block this command when linked directory exists (next breaking change)
22
+ warnIfLinkedDirectory(args._);
20
23
  const globalConfigExists = globalConfigFileExists();
21
24
  if (!globalConfigExists) {
22
25
  uiLogger.error(commands.account.subcommands.createOverride.errors.globalConfigNotFound);
@@ -1,6 +1,9 @@
1
1
  import { getConfigAccountById, getConfigDefaultAccount, getConfigFilePath, } from '@hubspot/local-dev-lib/config';
2
2
  import { getDefaultAccountOverrideFilePath } from '@hubspot/local-dev-lib/config/defaultAccountOverride';
3
+ import { getHsSettingsFileIfExists, getHsSettingsFilePath, } from '@hubspot/local-dev-lib/config/hsSettings';
4
+ import { isDirectoryLinked } from '../../lib/link/linkUtils.js';
3
5
  import { getAccessToken } from '@hubspot/local-dev-lib/personalAccessKey';
6
+ import { uiAccountDescription } from '../../lib/ui/index.js';
4
7
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
5
8
  import { indent } from '../../lib/ui/index.js';
6
9
  import { makeYargsHandlerWithUsageTracking } from '../../lib/yargs/makeYargsHandlerWithUsageTracking.js';
@@ -9,31 +12,46 @@ import { uiLogger } from '../../lib/ui/logger.js';
9
12
  import { renderList } from '../../ui/render.js';
10
13
  const describe = commands.account.subcommands.info.describe;
11
14
  const command = 'info [account]';
15
+ function logLinkedAccountInfo(hsSettingsPath, localDefaultAccount) {
16
+ uiLogger.log(commands.account.subcommands.info.linkedDefaultTitle);
17
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.info.settingsPath(hsSettingsPath)}`);
18
+ if (localDefaultAccount) {
19
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.info.linkedDefault(uiAccountDescription(localDefaultAccount))}`);
20
+ }
21
+ }
22
+ function logGlobalAccountInfo() {
23
+ const configPath = getConfigFilePath();
24
+ if (configPath) {
25
+ uiLogger.log(commands.account.subcommands.info.defaultAccountTitle);
26
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.info.configPath(configPath)}`);
27
+ const defaultAccount = getConfigDefaultAccount();
28
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.info.defaultAccount(defaultAccount.name)}`);
29
+ }
30
+ const overrideFilePath = getDefaultAccountOverrideFilePath();
31
+ if (overrideFilePath) {
32
+ uiLogger.log('');
33
+ uiLogger.log(commands.account.subcommands.info.overrideFilePathTitle);
34
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.info.overrideFilePath(overrideFilePath)}`);
35
+ const defaultAccount = getConfigDefaultAccount();
36
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.info.overrideAccount(defaultAccount.name)}`);
37
+ }
38
+ }
12
39
  async function handler(args) {
13
40
  const { derivedAccountId } = args;
14
41
  const config = getConfigAccountById(derivedAccountId);
15
- // check if the provided account is using a personal access key, if not, show an error
16
42
  if (config && config.authType === 'personalaccesskey') {
17
43
  const { name, personalAccessKey, env } = config;
18
44
  let scopeGroups = [];
19
45
  const response = await getAccessToken(personalAccessKey, env, derivedAccountId);
20
46
  scopeGroups = response.scopeGroups.map(s => [s]);
21
- // If a default account is present in the config, display it
22
- const configPath = getConfigFilePath();
23
- if (configPath) {
24
- uiLogger.log(commands.account.subcommands.info.defaultAccountTitle);
25
- uiLogger.log(`${indent(1)}${commands.account.subcommands.info.configPath(configPath)}`);
26
- const defaultAccount = getConfigDefaultAccount();
27
- uiLogger.log(`${indent(1)}${commands.account.subcommands.info.defaultAccount(defaultAccount.name)}`);
47
+ const hsSettings = getHsSettingsFileIfExists();
48
+ const hsSettingsPath = getHsSettingsFilePath();
49
+ const isLinked = isDirectoryLinked(hsSettings) && hsSettingsPath !== null;
50
+ if (isLinked) {
51
+ logLinkedAccountInfo(hsSettingsPath, hsSettings.localDefaultAccount);
28
52
  }
29
- // If a default account override is present, display it
30
- const overrideFilePath = getDefaultAccountOverrideFilePath();
31
- if (overrideFilePath) {
32
- uiLogger.log('');
33
- uiLogger.log(commands.account.subcommands.info.overrideFilePathTitle);
34
- uiLogger.log(`${indent(1)}${commands.account.subcommands.info.overrideFilePath(overrideFilePath)}`);
35
- const defaultAccount = getConfigDefaultAccount();
36
- uiLogger.log(`${indent(1)}${commands.account.subcommands.info.overrideAccount(defaultAccount.name)}`);
53
+ else {
54
+ logGlobalAccountInfo();
37
55
  }
38
56
  uiLogger.log('');
39
57
  uiLogger.log(commands.account.subcommands.info.name(name));
@@ -7,6 +7,7 @@ import { getHsSettingsFileIfExists, getHsSettingsFilePath, } from '@hubspot/loca
7
7
  import { getDefaultAccountOverrideAccountId } from '@hubspot/local-dev-lib/config/defaultAccountOverride';
8
8
  import { checkAndAddHsFolderToGitignore } from '@hubspot/local-dev-lib/gitignore';
9
9
  import { DEFAULT_HS_SETTINGS_PATH, EMPTY_HS_SETTINGS_FILE, } from '@hubspot/local-dev-lib/constants/config';
10
+ import { ACTION_RESULT_STATUS } from '../../types/Link.js';
10
11
  import { handleLinkFlow } from '../../lib/link/index.js';
11
12
  import { hasDeprecatedConfigConflict, writeLinkedSettings, } from '../../lib/link/linkUtils.js';
12
13
  import { renderLinkedAccountsTable } from '../../lib/link/renderLinkedAccountsTable.js';
@@ -46,11 +47,11 @@ async function handler(args) {
46
47
  accountOverrideId,
47
48
  args,
48
49
  });
49
- if (result.status === 'error') {
50
+ if (result.status === ACTION_RESULT_STATUS.ERROR) {
50
51
  uiLogger.error(result.reason);
51
52
  return exit(EXIT_CODES.ERROR);
52
53
  }
53
- if (result.status === 'noop') {
54
+ if (result.status === ACTION_RESULT_STATUS.NOOP) {
54
55
  return exit(EXIT_CODES.SUCCESS);
55
56
  }
56
57
  const settingsPath = getHsSettingsFilePath() || DEFAULT_HS_SETTINGS_PATH;
@@ -1,92 +1,50 @@
1
- import { getConfigFilePath, getAllConfigAccounts, getConfigDefaultAccountIfExists, } from '@hubspot/local-dev-lib/config';
1
+ import { getConfigFilePath, getConfigDefaultAccountIfExists, } from '@hubspot/local-dev-lib/config';
2
2
  import { getDefaultAccountOverrideFilePath } from '@hubspot/local-dev-lib/config/defaultAccountOverride';
3
+ import { getHsSettingsFileIfExists, getHsSettingsFilePath, } from '@hubspot/local-dev-lib/config/hsSettings';
4
+ import { getCwd } from '@hubspot/local-dev-lib/path';
3
5
  import { indent } from '../../lib/ui/index.js';
4
- import { isSandbox, isDeveloperTestAccount } from '../../lib/accountTypes.js';
5
- import { HUBSPOT_ACCOUNT_TYPES, HUBSPOT_ACCOUNT_TYPE_STRINGS, } from '@hubspot/local-dev-lib/constants/config';
6
6
  import { makeYargsHandlerWithUsageTracking } from '../../lib/yargs/makeYargsHandlerWithUsageTracking.js';
7
7
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
8
8
  import { uiLogger } from '../../lib/ui/logger.js';
9
9
  import { commands } from '../../lang/en.js';
10
- import { renderTable } from '../../ui/render.js';
10
+ import { renderAccountTable } from '../../lib/ui/accountTable.js';
11
+ import { renderLinkedAccountsTable } from '../../lib/link/renderLinkedAccountsTable.js';
12
+ import { isDirectoryLinked } from '../../lib/link/linkUtils.js';
11
13
  const command = ['list', 'ls'];
12
14
  const describe = commands.account.subcommands.list.describe;
13
- function sortAndMapAccounts(accounts) {
14
- const mappedAccountData = {};
15
- // Standard and app developer accounts
16
- accounts
17
- .filter(p => p.accountType &&
18
- (p.accountType === HUBSPOT_ACCOUNT_TYPES.STANDARD ||
19
- p.accountType === HUBSPOT_ACCOUNT_TYPES.APP_DEVELOPER))
20
- .forEach(account => {
21
- mappedAccountData[account.accountId] = [account];
22
- });
23
- // Non-standard accounts (sandbox, developer test account)
24
- accounts
25
- .filter(p => p.accountType && (isSandbox(p) || isDeveloperTestAccount(p)))
26
- .forEach(p => {
27
- if (p.parentAccountId) {
28
- mappedAccountData[p.parentAccountId] = [
29
- ...(mappedAccountData[p.parentAccountId] || []),
30
- p,
31
- ];
32
- }
33
- else {
34
- mappedAccountData[p.accountId] = [p];
35
- }
36
- });
37
- return mappedAccountData;
38
- }
39
- function getAccountData(mappedAccountData) {
40
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
- const accountData = [];
42
- Object.entries(mappedAccountData).forEach(([key, set]) => {
43
- const hasParentAccount = set.filter(p => p.accountId === parseInt(key, 10))[0];
44
- set.forEach(account => {
45
- let name = `${account.name} [${HUBSPOT_ACCOUNT_TYPE_STRINGS[account.accountType]}]`;
46
- if (isSandbox(account)) {
47
- if (hasParentAccount && set.length > 1) {
48
- name = `↳ ${name}`;
49
- }
50
- }
51
- else if (isDeveloperTestAccount(account)) {
52
- if (hasParentAccount && set.length > 1) {
53
- name = `↳ ${name}`;
54
- }
55
- }
56
- accountData.push([name, account.accountId, account.authType]);
57
- });
58
- });
59
- return accountData;
60
- }
61
15
  async function handler() {
62
16
  const configPath = getConfigFilePath();
63
- const accountsList = getAllConfigAccounts();
64
- const mappedAccountData = sortAndMapAccounts(accountsList);
65
- const accountData = getAccountData(mappedAccountData);
66
- const tableHeader = [
67
- commands.account.subcommands.list.labels.name,
68
- commands.account.subcommands.list.labels.accountId,
69
- commands.account.subcommands.list.labels.authType,
70
- ];
71
17
  const defaultAccount = getConfigDefaultAccountIfExists();
72
18
  const accountId = defaultAccount?.accountId;
73
19
  const overrideFilePath = getDefaultAccountOverrideFilePath();
74
- // If a default account is present in the config, display it
75
- if (configPath && accountId) {
76
- uiLogger.log(commands.account.subcommands.list.defaultAccountTitle);
77
- uiLogger.log(`${indent(1)}${commands.account.subcommands.list.configPath(configPath)}`);
78
- uiLogger.log(`${indent(1)}${commands.account.subcommands.list.currentResolvedDefaultAccount(accountId)}`);
20
+ const hsSettings = getHsSettingsFileIfExists();
21
+ const hsSettingsPath = getHsSettingsFilePath();
22
+ const isLinked = isDirectoryLinked(hsSettings);
23
+ if (isLinked && hsSettingsPath) {
24
+ uiLogger.log(commands.account.subcommands.list.linkedDefaultTitle);
25
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.list.directory(getCwd())}`);
26
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.list.configPath(hsSettingsPath)}`);
27
+ if (hsSettings.localDefaultAccount) {
28
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.list.currentResolvedDefaultAccount(hsSettings.localDefaultAccount)}`);
29
+ }
30
+ uiLogger.log('');
31
+ uiLogger.log(commands.account.subcommands.list.linkedAccounts);
32
+ await renderLinkedAccountsTable(hsSettings);
79
33
  uiLogger.log('');
80
34
  }
81
- // If a default account override is present, display it
82
- if (overrideFilePath && accountId) {
83
- uiLogger.log(commands.account.subcommands.list.overrideFilePathTitle);
84
- uiLogger.log(`${indent(1)}${commands.account.subcommands.list.overrideFilePath(overrideFilePath)}`);
35
+ if (!isLinked && configPath && accountId) {
36
+ uiLogger.log(commands.account.subcommands.list.defaultAccountTitle);
37
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.list.configPath(configPath)}`);
85
38
  uiLogger.log(`${indent(1)}${commands.account.subcommands.list.currentResolvedDefaultAccount(accountId)}`);
86
39
  uiLogger.log('');
40
+ if (overrideFilePath) {
41
+ uiLogger.log(commands.account.subcommands.list.overrideFilePathTitle);
42
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.list.overrideFilePath(overrideFilePath)}`);
43
+ uiLogger.log(`${indent(1)}${commands.account.subcommands.list.currentResolvedDefaultAccount(accountId)}`);
44
+ uiLogger.log('');
45
+ }
87
46
  }
88
- uiLogger.log(commands.account.subcommands.list.accounts);
89
- renderTable(tableHeader, accountData, true);
47
+ renderAccountTable(isLinked);
90
48
  }
91
49
  function accountListBuilder(yargs) {
92
50
  yargs.example([['$0 accounts list']]);
@@ -8,9 +8,11 @@ import { makeYargsHandlerWithUsageTracking } from '../../lib/yargs/makeYargsHand
8
8
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
9
9
  import { uiLogger } from '../../lib/ui/logger.js';
10
10
  import { commands } from '../../lang/en.js';
11
+ import { warnIfLinkedDirectory } from '../../lib/link/warnIfLinkedDirectory.js';
11
12
  const command = 'remove [account]';
12
13
  const describe = commands.account.subcommands.remove.describe;
13
14
  async function handler(args) {
15
+ warnIfLinkedDirectory(args._);
14
16
  const { account: accountFlag } = args;
15
17
  let accountToRemoveConfig = accountFlag
16
18
  ? getConfigAccountIfExists(accountFlag)
@@ -9,10 +9,13 @@ import { makeYargsHandlerWithUsageTracking } from '../../lib/yargs/makeYargsHand
9
9
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
10
10
  import { uiLogger } from '../../lib/ui/logger.js';
11
11
  import { commands } from '../../lang/en.js';
12
+ import { warnIfLinkedDirectory } from '../../lib/link/warnIfLinkedDirectory.js';
12
13
  const command = 'remove-override';
13
14
  const describe = commands.account.subcommands.removeOverride.describe(DEFAULT_ACCOUNT_OVERRIDE_FILE_NAME);
14
15
  async function handler(args) {
15
16
  const { force, exit } = args;
17
+ // TODO: Block this command when linked directory exists (next breaking change)
18
+ warnIfLinkedDirectory(args._);
16
19
  const globalConfigExists = globalConfigFileExists();
17
20
  if (!globalConfigExists) {
18
21
  uiLogger.error(commands.account.subcommands.removeOverride.errors.globalConfigNotFound);
@@ -4,6 +4,7 @@ import { getAllConfigAccounts } from '@hubspot/local-dev-lib/config';
4
4
  import { getCwd } from '@hubspot/local-dev-lib/path';
5
5
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
6
6
  import { getHsSettingsFileIfExists, getHsSettingsFilePath, } from '@hubspot/local-dev-lib/config/hsSettings';
7
+ import { ACTION_RESULT_STATUS } from '../../types/Link.js';
7
8
  import { ActionHandlers } from '../../lib/link/index.js';
8
9
  import { hasDeprecatedConfigConflict, isDirectoryLinked, writeLinkedSettings, } from '../../lib/link/linkUtils.js';
9
10
  import { commands } from '../../lang/en.js';
@@ -38,11 +39,11 @@ async function handler(args) {
38
39
  },
39
40
  args,
40
41
  });
41
- if (result.status === 'error') {
42
+ if (result.status === ACTION_RESULT_STATUS.ERROR) {
42
43
  uiLogger.error(result.reason);
43
44
  return exit(EXIT_CODES.ERROR);
44
45
  }
45
- if (result.status === 'noop') {
46
+ if (result.status === ACTION_RESULT_STATUS.NOOP) {
46
47
  return exit(EXIT_CODES.SUCCESS);
47
48
  }
48
49
  const settingsPath = getHsSettingsFilePath() || DEFAULT_HS_SETTINGS_PATH;
@@ -1,5 +1,7 @@
1
1
  import { getConfigFilePath, setConfigAccountAsDefault, getConfigAccountIfExists, getConfigAccountByName, getConfigAccountById, globalConfigFileExists, getAllConfigAccounts, } from '@hubspot/local-dev-lib/config';
2
2
  import { getDefaultAccountOverrideAccountId, getDefaultAccountOverrideFilePath, } from '@hubspot/local-dev-lib/config/defaultAccountOverride';
3
+ import { getHsSettingsFileIfExists, getHsSettingsFilePath, writeHsSettingsFile, } from '@hubspot/local-dev-lib/config/hsSettings';
4
+ import { getCwd } from '@hubspot/local-dev-lib/path';
3
5
  import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
4
6
  import { commands } from '../../lang/en.js';
5
7
  import { uiLogger } from '../../lib/ui/logger.js';
@@ -8,9 +10,69 @@ import { makeYargsHandlerWithUsageTracking } from '../../lib/yargs/makeYargsHand
8
10
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
9
11
  import { authenticateNewAccount } from '../../lib/accountAuth.js';
10
12
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
13
+ import { confirmPrompt } from '../../lib/prompts/promptUtils.js';
14
+ import { handleLinkedUseAction } from '../../lib/link/index.js';
15
+ import { isDirectoryLinked } from '../../lib/link/linkUtils.js';
16
+ import { ACTION_RESULT_STATUS } from '../../types/Link.js';
17
+ import { DEFAULT_HS_SETTINGS_PATH } from '@hubspot/local-dev-lib/constants/config';
11
18
  const command = 'use [account]';
12
19
  const describe = commands.account.subcommands.use.describe;
13
- async function handler(args) {
20
+ async function handleLinkedUse(args, hsSettings) {
21
+ const { exit } = args;
22
+ uiLogger.log(commands.account.subcommands.use.linked.editingLinkedDefault(getCwd()));
23
+ uiLogger.log('');
24
+ if (!args.account && hsSettings.accounts.length === 1) {
25
+ uiLogger.log(commands.account.subcommands.use.linked.alreadyDefault(hsSettings.accounts[0]));
26
+ return exit(EXIT_CODES.SUCCESS);
27
+ }
28
+ let targetAccountId;
29
+ if (args.account) {
30
+ const account = getConfigAccountIfExists(args.account);
31
+ if (!account) {
32
+ uiLogger.error(commands.account.subcommands.use.errors.accountNotFound(args.account, getConfigFilePath()));
33
+ return exit(EXIT_CODES.ERROR);
34
+ }
35
+ if (!hsSettings.accounts.includes(account.accountId)) {
36
+ if (!process.stdin.isTTY) {
37
+ uiLogger.log(commands.account.subcommands.use.linked.nonInteractiveNotLinked(account.name));
38
+ setConfigAccountAsDefault(String(args.account));
39
+ uiLogger.success(commands.account.subcommands.use.success.defaultAccountUpdated(account.name));
40
+ return exit(EXIT_CODES.SUCCESS);
41
+ }
42
+ uiLogger.log(commands.account.subcommands.use.linked.accountNotLinked(account.name));
43
+ const shouldLink = await confirmPrompt(commands.account.subcommands.use.linked.promptToLink(account.name));
44
+ if (!shouldLink) {
45
+ uiLogger.log(commands.account.subcommands.use.linked.settingGlobalDefault);
46
+ setConfigAccountAsDefault(String(args.account));
47
+ uiLogger.success(commands.account.subcommands.use.success.defaultAccountUpdated(account.name));
48
+ return exit(EXIT_CODES.SUCCESS);
49
+ }
50
+ }
51
+ targetAccountId = account.accountId;
52
+ }
53
+ const result = await handleLinkedUseAction({
54
+ state: hsSettings,
55
+ targetAccountId,
56
+ });
57
+ if (result.status === ACTION_RESULT_STATUS.ERROR) {
58
+ uiLogger.error(result.reason);
59
+ return exit(EXIT_CODES.ERROR);
60
+ }
61
+ if (result.status === ACTION_RESULT_STATUS.NOOP) {
62
+ return exit(EXIT_CODES.SUCCESS);
63
+ }
64
+ const settingsPath = getHsSettingsFilePath() || DEFAULT_HS_SETTINGS_PATH;
65
+ try {
66
+ writeHsSettingsFile(result.settings);
67
+ }
68
+ catch (err) {
69
+ uiLogger.error(commands.account.subcommands.link.shared.writeSettingsFailed(settingsPath, err));
70
+ return exit(EXIT_CODES.ERROR);
71
+ }
72
+ uiLogger.success(commands.account.subcommands.link.shared.savedToSettings(settingsPath));
73
+ return exit(EXIT_CODES.SUCCESS);
74
+ }
75
+ async function handleGlobalUse(args) {
14
76
  const { exit } = args;
15
77
  let newDefaultAccount = args.account;
16
78
  const usingGlobalConfig = globalConfigFileExists();
@@ -52,6 +114,14 @@ async function handler(args) {
52
114
  setConfigAccountAsDefault(String(newDefaultAccount));
53
115
  return uiLogger.success(commands.account.subcommands.use.success.defaultAccountUpdated(account.name));
54
116
  }
117
+ async function handler(args) {
118
+ const hsSettings = getHsSettingsFileIfExists();
119
+ const isLinked = isDirectoryLinked(hsSettings);
120
+ if (isLinked) {
121
+ return handleLinkedUse(args, hsSettings);
122
+ }
123
+ return handleGlobalUse(args);
124
+ }
55
125
  function accountUseBuilder(yargs) {
56
126
  yargs.positional('account', {
57
127
  describe: commands.account.subcommands.use.options.account.describe,
@@ -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,7 +3,7 @@ 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, } 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';
@@ -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) {
@@ -90,6 +98,9 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
90
98
  if (!accountAdded) {
91
99
  return exit(EXIT_CODES.SUCCESS);
92
100
  }
101
+ if (directoryIsLinked) {
102
+ addAccountToLinkedSettings(devAccountPromptResponse.notInConfigAccount.id);
103
+ }
93
104
  }
94
105
  else if (devAccountPromptResponse.createNestedAccount) {
95
106
  // Create a new developer test account and automatically add it to the CLI config
@@ -99,6 +110,9 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
99
110
  catch {
100
111
  return exit(EXIT_CODES.ERROR);
101
112
  }
113
+ if (directoryIsLinked) {
114
+ addAccountToLinkedSettings(targetTestingAccountId);
115
+ }
102
116
  }
103
117
  }
104
118
  else if (accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX) {
@@ -112,6 +126,9 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
112
126
  catch {
113
127
  return exit(EXIT_CODES.ERROR);
114
128
  }
129
+ if (directoryIsLinked) {
130
+ addAccountToLinkedSettings(targetTestingAccountId);
131
+ }
115
132
  }
116
133
  }
117
134
  else {
@@ -143,7 +160,7 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
143
160
  }
144
161
  // End setup, start local dev process
145
162
  try {
146
- await startPortManagerServer();
163
+ await startPortManagerServer(args.port);
147
164
  }
148
165
  catch (e) {
149
166
  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) {
@@ -133,6 +133,23 @@ async function handler(args) {
133
133
  return exit(EXIT_CODES.ERROR);
134
134
  }
135
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
+ }
136
153
  SpinniesManager.add('lintRun', {
137
154
  text: commands.project.lint.loading.linting,
138
155
  });