@hubspot/cli 7.6.2-beta.0 → 7.7.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.
Files changed (33) hide show
  1. package/commands/config/set.d.ts +1 -1
  2. package/commands/config/set.js +65 -33
  3. package/commands/init.js +0 -1
  4. package/commands/project/__tests__/validate.test.d.ts +1 -0
  5. package/commands/project/__tests__/validate.test.js +98 -0
  6. package/commands/project/validate.js +4 -4
  7. package/commands/testAccount/__tests__/delete.test.js +2 -4
  8. package/commands/testAccount/delete.d.ts +4 -3
  9. package/commands/testAccount/delete.js +155 -14
  10. package/lang/en.d.ts +37 -8
  11. package/lang/en.js +49 -20
  12. package/lib/__tests__/yargsUtils.test.js +83 -9
  13. package/lib/configOptions.js +7 -0
  14. package/lib/constants.d.ts +6 -0
  15. package/lib/constants.js +10 -0
  16. package/lib/doctor/DiagnosticInfoBuilder.js +7 -6
  17. package/lib/projects/__tests__/AppDevModeInterface.test.js +2 -0
  18. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +64 -33
  19. package/lib/projects/__tests__/localDevProjectHelpers.test.js +2 -0
  20. package/lib/projects/__tests__/upload.test.d.ts +1 -0
  21. package/lib/projects/__tests__/upload.test.js +82 -0
  22. package/lib/projects/localDev/AppDevModeInterface.d.ts +2 -0
  23. package/lib/projects/localDev/AppDevModeInterface.js +17 -8
  24. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +0 -1
  25. package/lib/projects/localDev/LocalDevWebsocketServer.js +4 -7
  26. package/lib/projects/structure.js +4 -4
  27. package/lib/projects/upload.d.ts +1 -1
  28. package/lib/projects/upload.js +15 -6
  29. package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +10 -1
  30. package/lib/prompts/promptUtils.js +8 -5
  31. package/lib/yargsUtils.d.ts +1 -1
  32. package/lib/yargsUtils.js +12 -5
  33. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
1
  import { CmsPublishMode } from '@hubspot/local-dev-lib/types/Files';
2
2
  import { CommonArgs, ConfigArgs, YargsCommandModule } from '../../types/Yargs.js';
3
3
  type ConfigSetArgs = CommonArgs & ConfigArgs & {
4
- defaultCmsPublishMode: CmsPublishMode;
4
+ defaultCmsPublishMode?: CmsPublishMode;
5
5
  allowUsageTracking?: boolean;
6
6
  httpTimeout?: string;
7
7
  allowAutoUpdates?: boolean;
@@ -4,7 +4,9 @@ import { promptUser } from '../../lib/prompts/promptUtils.js';
4
4
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
5
5
  import { setDefaultCmsPublishMode, setHttpTimeout, setAllowUsageTracking, setAllowAutoUpdates, setAutoOpenBrowser, } from '../../lib/configOptions.js';
6
6
  import { commands } from '../../lang/en.js';
7
- import { makeYargsBuilder, getExclusiveConflicts, } from '../../lib/yargsUtils.js';
7
+ import { makeYargsBuilder, strictEnforceBoolean, } from '../../lib/yargsUtils.js';
8
+ import { logError } from '../../lib/errorHandlers/index.js';
9
+ import { uiLogger } from '../../lib/ui/logger.js';
8
10
  const command = 'set';
9
11
  const describe = commands.config.subcommands.set.describe;
10
12
  async function selectOptions() {
@@ -29,35 +31,41 @@ async function selectOptions() {
29
31
  }
30
32
  async function handleConfigUpdate(accountId, args) {
31
33
  const { allowAutoUpdates, allowUsageTracking, defaultCmsPublishMode, httpTimeout, autoOpenBrowser, } = args;
32
- if (typeof defaultCmsPublishMode !== 'undefined') {
33
- await setDefaultCmsPublishMode({ defaultCmsPublishMode, accountId });
34
- return true;
35
- }
36
- else if (typeof httpTimeout !== 'undefined') {
37
- await setHttpTimeout({ httpTimeout, accountId });
38
- return true;
34
+ if (allowAutoUpdates !== undefined) {
35
+ await setAllowAutoUpdates({ allowAutoUpdates, accountId });
39
36
  }
40
- else if (typeof allowUsageTracking !== 'undefined') {
37
+ if (allowUsageTracking !== undefined) {
41
38
  await setAllowUsageTracking({ allowUsageTracking, accountId });
42
- return true;
43
39
  }
44
- else if (typeof allowAutoUpdates !== 'undefined') {
45
- await setAllowAutoUpdates({ allowAutoUpdates, accountId });
46
- return true;
47
- }
48
- else if (typeof autoOpenBrowser !== 'undefined') {
40
+ if (autoOpenBrowser !== undefined) {
49
41
  await setAutoOpenBrowser({ autoOpenBrowser, accountId });
50
- return true;
51
42
  }
52
- return false;
43
+ if (defaultCmsPublishMode !== undefined) {
44
+ await setDefaultCmsPublishMode({ defaultCmsPublishMode, accountId });
45
+ }
46
+ if (httpTimeout !== undefined) {
47
+ await setHttpTimeout({ httpTimeout, accountId });
48
+ }
53
49
  }
54
50
  async function handler(args) {
55
- const { derivedAccountId } = args;
51
+ const { derivedAccountId, allowAutoUpdates, allowUsageTracking, defaultCmsPublishMode, httpTimeout, autoOpenBrowser, } = args;
56
52
  trackCommandUsage('config-set', {}, derivedAccountId);
57
- const configUpdated = await handleConfigUpdate(derivedAccountId, args);
58
- if (!configUpdated) {
59
- const selectedOptions = await selectOptions();
60
- await handleConfigUpdate(derivedAccountId, selectedOptions);
53
+ try {
54
+ if (allowAutoUpdates !== undefined ||
55
+ allowUsageTracking !== undefined ||
56
+ autoOpenBrowser !== undefined ||
57
+ defaultCmsPublishMode !== undefined ||
58
+ httpTimeout !== undefined) {
59
+ await handleConfigUpdate(derivedAccountId, args);
60
+ }
61
+ else {
62
+ const selectedOptions = await selectOptions();
63
+ await handleConfigUpdate(derivedAccountId, selectedOptions);
64
+ }
65
+ }
66
+ catch (err) {
67
+ logError(err);
68
+ process.exit(EXIT_CODES.ERROR);
61
69
  }
62
70
  process.exit(EXIT_CODES.SUCCESS);
63
71
  }
@@ -67,15 +75,23 @@ function configSetBuilder(yargs) {
67
75
  'default-cms-publish-mode': {
68
76
  describe: commands.config.subcommands.set.options.defaultMode.describe,
69
77
  type: 'string',
78
+ choices: ['draft', 'publish'],
79
+ },
80
+ 'http-timeout': {
81
+ describe: commands.config.subcommands.set.options.httpTimeout.describe,
82
+ type: 'number',
83
+ coerce: (value) => {
84
+ if (isNaN(value) || value < 3000) {
85
+ uiLogger.error(commands.config.subcommands.set.errors.invalidHTTPTimeout);
86
+ process.exit(EXIT_CODES.ERROR);
87
+ }
88
+ return value;
89
+ },
70
90
  },
71
91
  'allow-usage-tracking': {
72
92
  describe: commands.config.subcommands.set.options.allowUsageTracking.describe,
73
93
  type: 'boolean',
74
94
  },
75
- 'http-timeout': {
76
- describe: commands.config.subcommands.set.options.httpTimeout.describe,
77
- type: 'string',
78
- },
79
95
  'allow-auto-updates': {
80
96
  describe: commands.config.subcommands.set.options.allowAutoUpdates.describe,
81
97
  type: 'boolean',
@@ -86,18 +102,34 @@ function configSetBuilder(yargs) {
86
102
  type: 'boolean',
87
103
  },
88
104
  })
89
- .conflicts(getExclusiveConflicts([
90
- 'default-cms-publish-mode',
91
- 'allow-usage-tracking',
92
- 'http-timeout',
93
- 'allow-auto-updates',
94
- 'auto-open-browser',
95
- ]))
105
+ .check(() => {
106
+ // Use process.argv directly because yargs argv has already been parsed/coerced,
107
+ // but we need to validate the exact format the user provided (e.g., --flag=true vs --flag)
108
+ return strictEnforceBoolean(process.argv, [
109
+ 'allow-usage-tracking',
110
+ 'allow-auto-updates',
111
+ 'auto-open-browser',
112
+ ]);
113
+ })
96
114
  .example([
97
115
  [
98
116
  '$0 config set',
99
117
  i18n(`commands.config.subcommands.set.examples.default`),
100
118
  ],
119
+ ['$0 config set --allow-usage-tracking=false', 'Disable usage tracking'],
120
+ ['$0 config set --http-timeout=5000', 'Set HTTP timeout to 5000ms'],
121
+ [
122
+ '$0 config set --default-cms-publish-mode=draft',
123
+ 'Set default CMS publish mode to draft',
124
+ ],
125
+ [
126
+ '$0 config set --http-timeout=3000 --allow-usage-tracking=false',
127
+ 'Set HTTP timeout and disable usage tracking',
128
+ ],
129
+ [
130
+ '$0 config set --default-cms-publish-mode=draft --http-timeout=4000 --allow-usage-tracking=true',
131
+ 'Configure multiple settings at once',
132
+ ],
101
133
  ]);
102
134
  return yargs;
103
135
  }
package/commands/init.js CHANGED
@@ -99,7 +99,6 @@ async function handler(args) {
99
99
  uiLogger.error(commands.init.errors.bothConfigFilesNotAllowed(path));
100
100
  process.exit(EXIT_CODES.ERROR);
101
101
  }
102
- trackAuthAction('init', authType, TRACKING_STATUS.STARTED, parsedUserProvidedAccountId);
103
102
  createEmptyConfigFile({ path: configPath }, useHiddenConfig);
104
103
  //Needed to load deprecated config
105
104
  loadConfig(configPath, args);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,98 @@
1
+ import path from 'path';
2
+ import { vi } from 'vitest';
3
+ import { validateSourceDirectory } from '../../../lib/projects/upload.js';
4
+ import { getProjectConfig, validateProjectConfig, } from '../../../lib/projects/config.js';
5
+ import { uiLogger } from '../../../lib/ui/logger.js';
6
+ import { commands } from '../../../lang/en.js';
7
+ import { useV3Api } from '../../../lib/projects/platformVersion.js';
8
+ import { loadAndValidateProfile } from '../../../lib/projectProfiles.js';
9
+ import { trackCommandUsage } from '../../../lib/usageTracking.js';
10
+ import { getAccountConfig } from '@hubspot/local-dev-lib/config';
11
+ import { handleTranslate } from '../../../lib/projects/upload.js';
12
+ import projectValidateCommand from '../validate.js';
13
+ // Mock dependencies
14
+ vi.mock('../../../lib/projects/upload.js');
15
+ vi.mock('../../../lib/projects/config.js');
16
+ vi.mock('../../../lib/ui/logger.js');
17
+ vi.mock('../../../lib/usageTracking.js');
18
+ vi.mock('../../../lib/projectProfiles.js');
19
+ vi.mock('../../../lib/errorHandlers/index.js');
20
+ vi.mock('@hubspot/local-dev-lib/config');
21
+ vi.mock('../../../lib/projects/platformVersion.js');
22
+ describe('commands/project/validate', () => {
23
+ const projectDir = '/test/project';
24
+ let exitSpy;
25
+ beforeEach(() => {
26
+ // Mock process.exit to throw to stop execution
27
+ exitSpy = vi.spyOn(process, 'exit').mockImplementation(code => {
28
+ throw new Error(`Process exited with code ${code}`);
29
+ });
30
+ vi.clearAllMocks();
31
+ });
32
+ afterEach(() => {
33
+ exitSpy.mockRestore();
34
+ });
35
+ describe('project configuration validation', () => {
36
+ it('should exit with error when project config is null', async () => {
37
+ vi.mocked(getProjectConfig).mockResolvedValue({
38
+ projectConfig: null,
39
+ projectDir: null,
40
+ });
41
+ await expect(
42
+ // @ts-expect-error partial mock
43
+ projectValidateCommand.handler({
44
+ derivedAccountId: 123,
45
+ d: false,
46
+ debug: false,
47
+ })).rejects.toThrow('Process exited with code 1');
48
+ expect(uiLogger.error).toHaveBeenCalledWith(commands.project.validate.mustBeRanWithinAProject);
49
+ });
50
+ it('should exit with error when project directory is null', async () => {
51
+ vi.mocked(getProjectConfig).mockResolvedValue({
52
+ projectConfig: {
53
+ name: 'test',
54
+ srcDir: 'src',
55
+ platformVersion: '2025.2',
56
+ },
57
+ projectDir: null,
58
+ });
59
+ await expect(
60
+ // @ts-expect-error partial mock
61
+ projectValidateCommand.handler({
62
+ derivedAccountId: 123,
63
+ d: false,
64
+ debug: false,
65
+ })).rejects.toThrow('Process exited with code 1');
66
+ expect(uiLogger.error).toHaveBeenCalledWith(commands.project.validate.mustBeRanWithinAProject);
67
+ });
68
+ });
69
+ it('should call validateSourceDirectory with correct parameters', async () => {
70
+ const mockProjectConfig = {
71
+ name: 'test-project',
72
+ srcDir: 'src',
73
+ platformVersion: '2025.2',
74
+ };
75
+ vi.mocked(getProjectConfig).mockResolvedValue({
76
+ projectConfig: mockProjectConfig,
77
+ projectDir,
78
+ });
79
+ vi.mocked(useV3Api).mockReturnValue(true);
80
+ vi.mocked(validateProjectConfig).mockReturnValue(undefined);
81
+ vi.mocked(loadAndValidateProfile).mockResolvedValue(123);
82
+ vi.mocked(getAccountConfig).mockReturnValue({
83
+ accountType: 'STANDARD',
84
+ accountId: 123,
85
+ env: 'prod',
86
+ });
87
+ vi.mocked(trackCommandUsage);
88
+ vi.mocked(validateSourceDirectory).mockResolvedValue(undefined);
89
+ vi.mocked(handleTranslate).mockResolvedValue(undefined);
90
+ await expect(projectValidateCommand.handler({
91
+ derivedAccountId: 123,
92
+ d: false,
93
+ debug: false,
94
+ })).rejects.toThrow('Process exited with code 0');
95
+ const expectedSrcDir = path.resolve(projectDir, mockProjectConfig.srcDir);
96
+ expect(validateSourceDirectory).toHaveBeenCalledWith(expectedSrcDir, mockProjectConfig, projectDir);
97
+ });
98
+ });
@@ -3,10 +3,10 @@ import { getAccountConfig } from '@hubspot/local-dev-lib/config';
3
3
  import { useV3Api } from '../../lib/projects/platformVersion.js';
4
4
  import { trackCommandUsage } from '../../lib/usageTracking.js';
5
5
  import { uiLogger } from '../../lib/ui/logger.js';
6
- import { getProjectConfig, validateProjectConfig as validateProjectConfig, } from '../../lib/projects/config.js';
6
+ import { getProjectConfig, validateProjectConfig, } from '../../lib/projects/config.js';
7
7
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
8
8
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
9
- import { validateSourceDirectory as validateSourceDirectory, handleTranslate, } from '../../lib/projects/upload.js';
9
+ import { validateSourceDirectory, handleTranslate, } from '../../lib/projects/upload.js';
10
10
  import { commands } from '../../lang/en.js';
11
11
  import { loadAndValidateProfile } from '../../lib/projectProfiles.js';
12
12
  import { logError } from '../../lib/errorHandlers/index.js';
@@ -15,7 +15,7 @@ const describe = commands.project.validate.describe;
15
15
  async function handler(args) {
16
16
  const { derivedAccountId, profile } = args;
17
17
  const { projectConfig, projectDir } = await getProjectConfig();
18
- if (!projectConfig) {
18
+ if (!projectConfig || !projectDir) {
19
19
  uiLogger.error(commands.project.validate.mustBeRanWithinAProject);
20
20
  process.exit(EXIT_CODES.ERROR);
21
21
  }
@@ -31,7 +31,7 @@ async function handler(args) {
31
31
  trackCommandUsage('project-validate', { type: accountType }, targetAccountId);
32
32
  const srcDir = path.resolve(projectDir, projectConfig.srcDir);
33
33
  try {
34
- validateSourceDirectory(srcDir, projectConfig);
34
+ await validateSourceDirectory(srcDir, projectConfig, projectDir);
35
35
  }
36
36
  catch (e) {
37
37
  logError(e);
@@ -1,12 +1,12 @@
1
1
  import yargs from 'yargs';
2
- import { addAccountOptions, addConfigOptions, addUseEnvironmentOptions, addTestingOptions, } from '../../../lib/commonOpts.js';
2
+ import { addConfigOptions, addUseEnvironmentOptions, addTestingOptions, } from '../../../lib/commonOpts.js';
3
3
  import testAccountDeleteCommand from '../delete.js';
4
4
  vi.mock('../../../lib/commonOpts');
5
5
  describe('commands/testAccount/delete', () => {
6
6
  const yargsMock = yargs;
7
7
  describe('command', () => {
8
8
  it('should have the correct command structure', () => {
9
- expect(testAccountDeleteCommand.command).toEqual('delete <test-account-id>');
9
+ expect(testAccountDeleteCommand.command).toEqual('delete [test-account]');
10
10
  });
11
11
  });
12
12
  describe('describe', () => {
@@ -20,8 +20,6 @@ describe('commands/testAccount/delete', () => {
20
20
  expect(yargsMock.example).toHaveBeenCalledTimes(1);
21
21
  expect(addTestingOptions).toHaveBeenCalledTimes(1);
22
22
  expect(addTestingOptions).toHaveBeenCalledWith(yargsMock);
23
- expect(addAccountOptions).toHaveBeenCalledTimes(1);
24
- expect(addAccountOptions).toHaveBeenCalledWith(yargsMock);
25
23
  expect(addConfigOptions).toHaveBeenCalledTimes(1);
26
24
  expect(addConfigOptions).toHaveBeenCalledWith(yargsMock);
27
25
  expect(addUseEnvironmentOptions).toHaveBeenCalledTimes(1);
@@ -1,6 +1,7 @@
1
- import { CommonArgs, ConfigArgs, AccountArgs, EnvironmentArgs, TestingArgs, YargsCommandModule } from '../../types/Yargs.js';
2
- type DeleteTestAccountArgs = CommonArgs & AccountArgs & ConfigArgs & TestingArgs & EnvironmentArgs & {
3
- testAccountId: number;
1
+ import { CommonArgs, ConfigArgs, EnvironmentArgs, TestingArgs, YargsCommandModule } from '../../types/Yargs.js';
2
+ type DeleteTestAccountArgs = CommonArgs & ConfigArgs & TestingArgs & EnvironmentArgs & {
3
+ testAccount?: string | number;
4
+ force?: boolean;
4
5
  };
5
6
  declare const deleteTestAccountCommand: YargsCommandModule<unknown, DeleteTestAccountArgs>;
6
7
  export default deleteTestAccountCommand;
@@ -1,39 +1,180 @@
1
- import { deleteDeveloperTestAccount } from '@hubspot/local-dev-lib/api/developerTestAccounts';
1
+ import { fetchDeveloperTestAccounts, deleteDeveloperTestAccount, } from '@hubspot/local-dev-lib/api/developerTestAccounts';
2
2
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
3
3
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
4
4
  import { uiLogger } from '../../lib/ui/logger.js';
5
5
  import { trackCommandUsage } from '../../lib/usageTracking.js';
6
6
  import { commands } from '../../lang/en.js';
7
- const command = 'delete <test-account-id>';
7
+ import { deleteAccount, getAccountConfig, getAccountId, getConfigPath, loadConfig, updateDefaultAccount, } from '@hubspot/local-dev-lib/config';
8
+ import { promptUser } from '../../lib/prompts/promptUtils.js';
9
+ import { debugError } from '../../lib/errorHandlers/index.js';
10
+ const command = 'delete [test-account]';
8
11
  const describe = commands.testAccount.delete.describe;
9
- async function handler(args) {
10
- const { derivedAccountId, testAccountId } = args;
11
- trackCommandUsage('test-account-delete', {}, derivedAccountId);
12
+ async function getAccountPromptOptions(derivedAccountId) {
12
13
  try {
13
- await deleteDeveloperTestAccount(derivedAccountId, testAccountId, true);
14
- uiLogger.success(commands.testAccount.delete.success.testAccountDeleted(testAccountId));
14
+ const { data } = await fetchDeveloperTestAccounts(derivedAccountId);
15
+ return data.results.map(testAccount => ({
16
+ name: `${testAccount.accountName} (${testAccount.id})`,
17
+ value: testAccount.id,
18
+ }));
15
19
  }
16
20
  catch (err) {
17
- uiLogger.error(commands.testAccount.delete.errors.failedToDelete);
21
+ uiLogger.error(commands.testAccount.delete.errors.failedToFetchTestAccounts);
22
+ throw err;
23
+ }
24
+ }
25
+ async function accountToDeleteSelectionPrompt(derivedAccountId) {
26
+ const accountData = await getAccountPromptOptions(derivedAccountId);
27
+ if (accountData.length === 0) {
28
+ uiLogger.error(commands.testAccount.delete.errors.noAccountsToDelete(derivedAccountId));
29
+ process.exit(EXIT_CODES.ERROR);
30
+ }
31
+ const { testAccountToDelete } = await promptUser([
32
+ {
33
+ type: 'list',
34
+ name: 'testAccountToDelete',
35
+ pageSize: 20,
36
+ message: commands.testAccount.delete.prompts.selectTestAccounts,
37
+ choices: accountData,
38
+ },
39
+ ]);
40
+ return testAccountToDelete;
41
+ }
42
+ async function confirmDeletion() {
43
+ const { shouldDelete } = await promptUser([
44
+ {
45
+ type: 'confirm',
46
+ name: 'shouldDelete',
47
+ message: commands.testAccount.delete.prompts.confirmDeletion,
48
+ },
49
+ ]);
50
+ return shouldDelete;
51
+ }
52
+ async function deleteTestAccountInHubSpot(derivedAccountId, accountId) {
53
+ try {
54
+ await deleteDeveloperTestAccount(derivedAccountId, accountId, true);
55
+ uiLogger.success(commands.testAccount.delete.success.testAccountDeletedFromHubSpot(accountId));
56
+ }
57
+ catch (e) {
58
+ debugError(e);
59
+ uiLogger.error(commands.testAccount.delete.errors.failedToDelete(accountId));
60
+ }
61
+ }
62
+ async function deleteTestAccountFromConfig(testAccountId, parentAccountName, account) {
63
+ try {
64
+ // If the account isn't in the local config then it wasn't auth'd on the local machine
65
+ if (account && account.name && account.accountType) {
66
+ // If the deleted test account was the default account, replace the default account with the parent account
67
+ loadConfig(getConfigPath()); // Get updated version of the config
68
+ const defaultAccountId = getAccountId(); // We need to get the current default accountId before delete the test account
69
+ await deleteAccount(account.name);
70
+ uiLogger.success(commands.testAccount.delete.success.testAccountDeletedFromConfig(testAccountId));
71
+ if (testAccountId === defaultAccountId) {
72
+ updateDefaultAccount(parentAccountName);
73
+ uiLogger.info(commands.testAccount.delete.info.replaceDefaultAccount(testAccountId, parentAccountName));
74
+ }
75
+ }
76
+ }
77
+ catch (e) {
78
+ debugError(e);
79
+ uiLogger.error(commands.testAccount.delete.errors.failedToDeleteFromConfig(testAccountId));
80
+ }
81
+ }
82
+ async function validateTestAccountConfigs(testAccountId) {
83
+ if (!testAccountId) {
84
+ uiLogger.error(commands.testAccount.delete.errors.testAccountNotFound(testAccountId));
85
+ process.exit(EXIT_CODES.ERROR);
86
+ }
87
+ const testAccountConfig = getAccountConfig(testAccountId);
88
+ if (!testAccountConfig) {
89
+ uiLogger.error(commands.testAccount.delete.errors.testAccountNotFound(testAccountId));
18
90
  process.exit(EXIT_CODES.ERROR);
19
91
  }
92
+ const parentAccountConfig = getAccountConfig(testAccountConfig.parentAccountId);
93
+ if (!parentAccountConfig) {
94
+ uiLogger.error(commands.testAccount.delete.errors.parentAccountNotFound(testAccountId));
95
+ process.exit(EXIT_CODES.ERROR);
96
+ }
97
+ const parentAccountName = parentAccountConfig.name;
98
+ return { testAccountConfig, parentAccountName };
99
+ }
100
+ async function handler(args) {
101
+ const { derivedAccountId, testAccount, force } = args;
102
+ trackCommandUsage('test-account-delete', {}, derivedAccountId);
103
+ let testAccountIdToDelete = 0;
104
+ // See if the account exists
105
+ if (testAccount) {
106
+ const accountId = getAccountId(testAccount);
107
+ await validateTestAccountConfigs(accountId);
108
+ if (accountId) {
109
+ testAccountIdToDelete = accountId;
110
+ }
111
+ }
112
+ // Prompt for selection when name or id aren't provided
113
+ if (!testAccountIdToDelete) {
114
+ try {
115
+ testAccountIdToDelete =
116
+ await accountToDeleteSelectionPrompt(derivedAccountId);
117
+ }
118
+ catch (err) {
119
+ debugError(err);
120
+ uiLogger.error(commands.testAccount.delete.errors.failedToSelectAccount);
121
+ process.exit(EXIT_CODES.ERROR);
122
+ }
123
+ }
124
+ const { testAccountConfig, parentAccountName } = await validateTestAccountConfigs(testAccountIdToDelete);
125
+ // If --force, don't prompt user; else confirm deletion
126
+ let shouldDeleteAccount;
127
+ if (force) {
128
+ shouldDeleteAccount = true;
129
+ }
130
+ else {
131
+ shouldDeleteAccount = await confirmDeletion();
132
+ }
133
+ if (shouldDeleteAccount) {
134
+ const parentAccountId = testAccountConfig.parentAccountId;
135
+ await deleteTestAccountInHubSpot(parentAccountId, testAccountIdToDelete);
136
+ await deleteTestAccountFromConfig(testAccountIdToDelete, parentAccountName, testAccountConfig);
137
+ }
138
+ else {
139
+ uiLogger.info(commands.testAccount.delete.info.deletionCanceled);
140
+ }
20
141
  process.exit(EXIT_CODES.SUCCESS);
21
142
  }
22
143
  function deleteTestAccountBuilder(yargs) {
23
- yargs.positional('test-account-id', {
24
- type: 'number',
25
- description: commands.testAccount.delete.positionals.testAccountId,
26
- required: true,
144
+ yargs.positional('test-account', {
145
+ type: 'string',
146
+ description: commands.testAccount.delete.options.id,
147
+ required: false,
148
+ });
149
+ yargs.option('force', {
150
+ describe: commands.upload.options.force,
151
+ type: 'boolean',
152
+ default: false,
27
153
  });
28
154
  yargs.example([
29
- ['$0 delete 1234567890', commands.testAccount.delete.example(1234567890)],
155
+ [
156
+ '$0 test-account delete 12345678',
157
+ commands.testAccount.delete.examples.withPositionalID(12345678),
158
+ ],
159
+ [
160
+ '$0 test-account delete my-test-account',
161
+ commands.testAccount.delete.examples.withPositionalName('my-test-account'),
162
+ ],
163
+ [
164
+ '$0 test-account delete --test-account=12345678',
165
+ commands.testAccount.delete.examples.withID(12345678),
166
+ ],
167
+ [
168
+ '$0 test-account delete --test-account=my-test-account',
169
+ commands.testAccount.delete.examples.withName('my-test-account'),
170
+ ],
171
+ ['$0 test-account delete', commands.testAccount.delete.examples.withoutId],
30
172
  ]);
31
173
  return yargs;
32
174
  }
33
175
  const builder = makeYargsBuilder(deleteTestAccountBuilder, command, describe, {
34
176
  useGlobalOptions: true,
35
177
  useEnvironmentOptions: true,
36
- useAccountOptions: true,
37
178
  useConfigOptions: true,
38
179
  useTestingOptions: true,
39
180
  });
package/lang/en.d.ts CHANGED
@@ -260,6 +260,10 @@ Global configuration replaces hubspot.config.yml, and you will be prompted to mi
260
260
  readonly describe: "Enable or disable automatic opening of the browser";
261
261
  };
262
262
  };
263
+ readonly errors: {
264
+ readonly invalidBoolean: (commandName: string, value: string) => string;
265
+ readonly invalidHTTPTimeout: "Invalid HTTP timeout value. Must be a number greater than 3000.";
266
+ };
263
267
  };
264
268
  };
265
269
  };
@@ -1907,18 +1911,41 @@ ${string}`;
1907
1911
  readonly example: (name: string) => string;
1908
1912
  };
1909
1913
  readonly delete: {
1910
- readonly describe: "Delete a test account config file.";
1914
+ readonly describe: "Delete a test account from your HubSpot account and CLI config";
1911
1915
  readonly pathPrompt: "[--path] What is the path to the test account config?";
1916
+ readonly info: {
1917
+ readonly deletionCanceled: "Deletion canceled by user";
1918
+ readonly accountNotFoundWithId: (id: number) => string;
1919
+ readonly replaceDefaultAccount: (testAccountId: number, parentAccountName: string) => string;
1920
+ };
1921
+ readonly prompts: {
1922
+ readonly selectTestAccounts: "Select test account(s) to delete";
1923
+ readonly confirmDeletion: "All data for the account will be permanently deleted. Any connected apps will have their access tokens revoked. Do you wish to proceed?";
1924
+ };
1912
1925
  readonly errors: {
1913
- readonly failedToDelete: "Failed to delete test account";
1926
+ readonly failedToDelete: (testAccountToDelete: number) => string;
1927
+ readonly failedToSelectAccount: "Failed to select a test account to delete";
1928
+ readonly noAccountsToDelete: (accountId: number) => string;
1929
+ readonly failedToDeleteFromConfig: (testAccountToDelete: number) => string;
1930
+ readonly failedToFetchTestAccounts: "Failed to fetch developer test accounts";
1931
+ readonly testAccountNotFound: (nameOrId: string | number | null) => string;
1932
+ readonly parentAccountNotFound: (testAccountId: number) => string;
1914
1933
  };
1915
1934
  readonly success: {
1916
- readonly testAccountDeleted: (testAccountId: number) => string;
1935
+ readonly testAccountDeletedFromHubSpot: (testAccountToDelete: number) => string;
1936
+ readonly testAccountDeletedFromConfig: (accountId: number) => string;
1937
+ };
1938
+ readonly options: {
1939
+ readonly name: "The name of the test account (in your CLI config) to delete";
1940
+ readonly id: "The id of the test account";
1917
1941
  };
1918
- readonly positionals: {
1919
- readonly testAccountId: "The id of the test account";
1942
+ readonly examples: {
1943
+ readonly withPositionalID: (testAccountToDelete: number) => string;
1944
+ readonly withPositionalName: (testAccountToDelete: string) => string;
1945
+ readonly withID: (testAccountToDelete: number) => string;
1946
+ readonly withName: (testAccountToDelete: string) => string;
1947
+ readonly withoutId: "Delete a test account via a prompt";
1920
1948
  };
1921
- readonly example: (testAccountId: number) => string;
1922
1949
  };
1923
1950
  };
1924
1951
  readonly secrets: {
@@ -2770,6 +2797,7 @@ Run ${string} to upgrade to version ${string}`;
2770
2797
  readonly compressed: (byteCount: number) => string;
2771
2798
  readonly compressing: (path: string) => string;
2772
2799
  readonly fileFiltered: (filename: string) => string;
2800
+ readonly legacyFileDetected: (filename: string, platformVersion: string) => string;
2773
2801
  };
2774
2802
  };
2775
2803
  readonly boxen: {
@@ -2902,11 +2930,12 @@ Run ${string} to upgrade to version ${string}`;
2902
2930
  readonly setHttpTimeout: {
2903
2931
  readonly promptMessage: "Enter http timeout duration";
2904
2932
  readonly success: (timeout: string) => string;
2933
+ readonly error: (timeout: string) => string;
2905
2934
  };
2906
2935
  readonly setAutoOpenBrowser: {
2907
2936
  readonly fieldName: "auto open browser";
2908
- readonly enabled: "Auto opening your browser has been enabled";
2909
- readonly disabled: "Auto opening your browser has been disabled";
2937
+ readonly enabled: `Successfully updated ${string} to ${string}`;
2938
+ readonly disabled: `Successfully updated ${string} to ${string}`;
2910
2939
  };
2911
2940
  };
2912
2941
  readonly commonOpts: {