@hubspot/cli 8.0.10-experimental.4 → 8.0.10-experimental.5

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 (57) hide show
  1. package/api/migrate.js +2 -2
  2. package/commands/account/auth.js +15 -5
  3. package/commands/account/use.js +14 -4
  4. package/commands/app/__tests__/migrate.test.js +2 -2
  5. package/commands/app/migrate.js +3 -3
  6. package/commands/auth.js +10 -6
  7. package/commands/hubdb/clear.js +4 -0
  8. package/commands/hubdb/delete.js +4 -0
  9. package/commands/hubdb/fetch.js +4 -0
  10. package/commands/init.js +4 -0
  11. package/commands/project/__tests__/create.test.js +2 -2
  12. package/commands/project/__tests__/migrate.test.js +1 -1
  13. package/commands/project/create.js +3 -3
  14. package/commands/project/dev/index.js +29 -19
  15. package/commands/project/download.js +5 -1
  16. package/commands/project/migrate.js +1 -1
  17. package/commands/sandbox/__tests__/create.test.js +1 -48
  18. package/commands/sandbox/create.js +3 -30
  19. package/commands/testAccount/create.js +4 -0
  20. package/lang/en.d.ts +1 -1
  21. package/lang/en.js +1 -1
  22. package/lib/__tests__/buildAccount.test.js +1 -52
  23. package/lib/__tests__/sandboxes.test.js +1 -29
  24. package/lib/accountAuth.js +4 -0
  25. package/lib/app/__tests__/migrate.test.js +1 -1
  26. package/lib/app/migrate.js +1 -1
  27. package/lib/buildAccount.d.ts +1 -6
  28. package/lib/buildAccount.js +9 -42
  29. package/lib/constants.d.ts +0 -2
  30. package/lib/constants.js +0 -2
  31. package/lib/errors/PromptExitError.d.ts +4 -0
  32. package/lib/errors/PromptExitError.js +8 -0
  33. package/lib/projects/__tests__/components.test.js +14 -0
  34. package/lib/projects/components.js +12 -2
  35. package/lib/projects/localDev/AppDevModeInterface.js +4 -0
  36. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +4 -0
  37. package/lib/projects/localDev/helpers/account.js +5 -11
  38. package/lib/prompts/downloadProjectPrompt.js +11 -10
  39. package/lib/prompts/installAppPrompt.js +3 -2
  40. package/lib/prompts/personalAccessKeyPrompt.js +3 -2
  41. package/lib/prompts/projectDevTargetAccountPrompt.js +13 -16
  42. package/lib/prompts/selectHubDBTablePrompt.js +8 -4
  43. package/lib/prompts/selectPublicAppForMigrationPrompt.js +12 -6
  44. package/lib/sandboxes.d.ts +1 -9
  45. package/lib/sandboxes.js +0 -21
  46. package/lib/theme/__tests__/migrate.test.js +1 -5
  47. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +1 -0
  48. package/mcp-server/tools/project/CreateProjectTool.d.ts +1 -0
  49. package/mcp-server/tools/project/CreateProjectTool.js +2 -1
  50. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +0 -1
  51. package/mcp-server/tools/project/constants.d.ts +1 -0
  52. package/mcp-server/tools/project/constants.js +2 -1
  53. package/package.json +1 -1
  54. package/lib/__tests__/sandboxSync.test.d.ts +0 -1
  55. package/lib/__tests__/sandboxSync.test.js +0 -147
  56. package/lib/sandboxSync.d.ts +0 -4
  57. package/lib/sandboxSync.js +0 -102
@@ -1,7 +1,7 @@
1
1
  import { getAccessToken, updateConfigWithAccessToken, } from '@hubspot/local-dev-lib/personalAccessKey';
2
2
  import { getConfigAccountIfExists, updateConfigAccount, } from '@hubspot/local-dev-lib/config';
3
3
  import { createDeveloperTestAccount, fetchDeveloperTestAccountGateSyncStatus, generateDeveloperTestAccountPersonalAccessKey, } from '@hubspot/local-dev-lib/api/developerTestAccounts';
4
- import { createSandbox, createV2Sandbox, getSandboxPersonalAccessKey, } from '@hubspot/local-dev-lib/api/sandboxHubs';
4
+ import { createV2Sandbox, getSandboxPersonalAccessKey, } from '@hubspot/local-dev-lib/api/sandboxHubs';
5
5
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
6
6
  import { personalAccessKeyPrompt } from '../prompts/personalAccessKeyPrompt.js';
7
7
  import { cliAccountNamePrompt } from '../prompts/accountNamePrompt.js';
@@ -32,7 +32,6 @@ const mockedCliAccountNamePrompt = cliAccountNamePrompt;
32
32
  const mockedCreateDeveloperTestAccount = createDeveloperTestAccount;
33
33
  const mockedFetchDeveloperTestAccountGateSyncStatus = fetchDeveloperTestAccountGateSyncStatus;
34
34
  const mockedGenerateDeveloperTestAccountPersonalAccessKey = generateDeveloperTestAccountPersonalAccessKey;
35
- const mockedCreateSandbox = createSandbox;
36
35
  const mockedCreateV2Sandbox = createV2Sandbox;
37
36
  const mockedGetPersonalAccessKey = getSandboxPersonalAccessKey;
38
37
  const mockedPoll = poll;
@@ -162,56 +161,6 @@ describe('lib/buildAccount', () => {
162
161
  await expect(buildAccount.buildDeveloperTestAccount(mockDeveloperTestAccount.accountName, mockParentAccountConfig, mockParentAccountConfig.env, 10)).rejects.toThrow();
163
162
  });
164
163
  });
165
- describe('buildSandbox()', () => {
166
- const mockParentAccountConfig = {
167
- name: 'Prod account',
168
- accountId: 123456,
169
- accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD,
170
- env: 'prod',
171
- };
172
- const mockSandbox = {
173
- sandboxHubId: 56789,
174
- parentHubId: 123456,
175
- createdAt: '2025-01-01',
176
- type: 'STANDARD',
177
- version: 'V1',
178
- archived: false,
179
- name: 'Test Sandbox',
180
- domain: 'test-sandbox.hubspot.com',
181
- createdByUser: {
182
- id: 123456,
183
- email: 'test@test.com',
184
- firstName: 'Test',
185
- lastName: 'User',
186
- },
187
- };
188
- beforeEach(() => {
189
- vi.spyOn(buildAccount, 'saveAccountToConfig').mockResolvedValue(mockParentAccountConfig.name);
190
- mockedCreateSandbox.mockResolvedValue({
191
- data: { sandbox: mockSandbox, personalAccessKey: 'test-key' },
192
- });
193
- });
194
- it('should create a standard sandbox successfully', async () => {
195
- const result = await buildAccount.buildSandbox(mockSandbox.name, mockParentAccountConfig, HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX, mockParentAccountConfig.env, false);
196
- expect(result).toEqual({
197
- name: mockSandbox.name,
198
- personalAccessKey: 'test-key',
199
- sandbox: mockSandbox,
200
- });
201
- });
202
- it('should create a development sandbox successfully', async () => {
203
- const result = await buildAccount.buildSandbox(mockSandbox.name, mockParentAccountConfig, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, mockParentAccountConfig.env);
204
- expect(result).toEqual({
205
- name: mockSandbox.name,
206
- personalAccessKey: 'test-key',
207
- sandbox: mockSandbox,
208
- });
209
- });
210
- it('should handle API errors when creating sandbox', async () => {
211
- mockedCreateSandbox.mockRejectedValue(new Error('test-error'));
212
- await expect(buildAccount.buildSandbox(mockSandbox.name, mockParentAccountConfig, HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX, mockParentAccountConfig.env, false)).rejects.toThrow();
213
- });
214
- });
215
164
  describe('buildV2Sandbox()', () => {
216
165
  const mockParentAccountConfig = {
217
166
  name: 'Prod account',
@@ -1,10 +1,9 @@
1
1
  import { uiLogger } from '../ui/logger.js';
2
2
  import { getSandboxUsageLimits } from '@hubspot/local-dev-lib/api/sandboxHubs';
3
- import { fetchTypes } from '@hubspot/local-dev-lib/api/sandboxSync';
4
3
  import { getAllConfigAccounts, getConfigAccountIfExists, } from '@hubspot/local-dev-lib/config';
5
4
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
6
5
  import { mockHubSpotHttpError } from '../testUtils.js';
7
- import { getSandboxTypeAsString, getHasSandboxesByType, getAvailableSyncTypes, validateSandboxUsageLimits, handleSandboxCreateError, } from '../sandboxes.js';
6
+ import { getSandboxTypeAsString, getHasSandboxesByType, validateSandboxUsageLimits, handleSandboxCreateError, } from '../sandboxes.js';
8
7
  import { isMissingScopeError, isSpecifiedError, } from '@hubspot/local-dev-lib/errors/index';
9
8
  vi.mock('@hubspot/local-dev-lib/api/sandboxHubs');
10
9
  vi.mock('@hubspot/local-dev-lib/api/sandboxSync');
@@ -12,7 +11,6 @@ vi.mock('@hubspot/local-dev-lib/config');
12
11
  vi.mock('@hubspot/local-dev-lib/errors/index');
13
12
  const mockedGetConfigAccountIfExists = getConfigAccountIfExists;
14
13
  const mockedGetSandboxUsageLimits = getSandboxUsageLimits;
15
- const mockedFetchTypes = fetchTypes;
16
14
  const mockedGetAllConfigAccounts = getAllConfigAccounts;
17
15
  const mockedUiLogger = uiLogger;
18
16
  const mockedIsMissingScopeError = isMissingScopeError;
@@ -53,32 +51,6 @@ describe('lib/sandboxes', () => {
53
51
  expect(getHasSandboxesByType(mockParentAccount, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX)).toBe(false);
54
52
  });
55
53
  });
56
- describe('getAvailableSyncTypes()', () => {
57
- const mockParentAccount = {
58
- name: 'Parent Account',
59
- accountId: 123,
60
- env: 'qa',
61
- };
62
- const mockChildAccount = {
63
- ...mockParentAccount,
64
- accountId: 456,
65
- };
66
- it('returns available sync types when fetch is successful', async () => {
67
- const mockSyncTypes = [{ name: 'type1' }, { name: 'type2' }];
68
- mockedGetConfigAccountIfExists
69
- .mockReturnValue(mockParentAccount.accountId)
70
- .mockReturnValue(mockChildAccount.accountId);
71
- mockedFetchTypes.mockResolvedValue({
72
- data: { results: mockSyncTypes },
73
- });
74
- const result = await getAvailableSyncTypes(mockParentAccount, mockChildAccount);
75
- expect(result).toEqual([{ type: 'type1' }, { type: 'type2' }]);
76
- });
77
- it('throws error when sync types fetch fails', async () => {
78
- mockedFetchTypes.mockResolvedValue({ data: { results: null } });
79
- await expect(getAvailableSyncTypes(mockParentAccount, mockChildAccount)).rejects.toThrow(/Unable to fetch available sandbox sync types/);
80
- });
81
- });
82
54
  describe('validateSandboxUsageLimits()', () => {
83
55
  const mockAccount = {
84
56
  name: 'Test Account',
@@ -3,6 +3,7 @@ import { getAccessToken, updateConfigWithAccessToken, } from '@hubspot/local-dev
3
3
  import { toKebabCase } from '@hubspot/local-dev-lib/text';
4
4
  import { handleMerge, handleMigration } from './configMigrate.js';
5
5
  import { debugError, logError } from './errorHandlers/index.js';
6
+ import { PromptExitError } from './errors/PromptExitError.js';
6
7
  import { personalAccessKeyPrompt } from './prompts/personalAccessKeyPrompt.js';
7
8
  import { cliAccountNamePrompt } from './prompts/accountNamePrompt.js';
8
9
  import { setAsDefaultAccountPrompt } from './prompts/setAsDefaultAccountPrompt.js';
@@ -36,6 +37,9 @@ async function updateConfigWithNewAccount(env, configAlreadyExists, providedPers
36
37
  return updatedConfig;
37
38
  }
38
39
  catch (e) {
40
+ if (e instanceof PromptExitError) {
41
+ throw e;
42
+ }
39
43
  debugError(e);
40
44
  return null;
41
45
  }
@@ -424,7 +424,7 @@ describe('lib/app/migrate', () => {
424
424
  };
425
425
  mockedPoll.mockResolvedValue(mockStatus);
426
426
  const result = await pollMigrationStatus(ACCOUNT_ID, MIGRATION_ID);
427
- expect(mockedPoll).toHaveBeenCalledWith(expect.any(Function), expect.any(Object));
427
+ expect(mockedPoll).toHaveBeenCalledWith(expect.any(Function), expect.any(Object), 300_000);
428
428
  expect(result).toBe(mockStatus);
429
429
  });
430
430
  });
@@ -255,7 +255,7 @@ export async function pollMigrationStatus(derivedAccountId, migrationId, success
255
255
  return poll(() => checkMigrationStatusV2(derivedAccountId, migrationId), {
256
256
  successStates: [...successStates],
257
257
  errorStates: [...DEFAULT_POLLING_STATUS_LOOKUP.errorStates],
258
- });
258
+ }, 300_000);
259
259
  }
260
260
  export async function finalizeAppMigration(derivedAccountId, migrationId, uidMap, projectName) {
261
261
  let pollResponse;
@@ -1,7 +1,7 @@
1
1
  import { DeveloperTestAccountConfig } from '@hubspot/local-dev-lib/types/developerTestAccounts';
2
2
  import { Environment } from '@hubspot/local-dev-lib/types/Accounts';
3
3
  import { HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
4
- import { SandboxResponse, V2Sandbox } from '@hubspot/local-dev-lib/types/Sandbox';
4
+ import { V2Sandbox } from '@hubspot/local-dev-lib/types/Sandbox';
5
5
  import { SandboxAccountType } from '../types/Sandboxes.js';
6
6
  export declare function saveAccountToConfig(accountId: number | undefined, accountName: string, env: Environment, personalAccessKey?: string, force?: boolean): Promise<string>;
7
7
  export declare function createDeveloperTestAccountV2(parentAccountId: number, testAccountConfig: DeveloperTestAccountConfig): Promise<{
@@ -10,11 +10,6 @@ export declare function createDeveloperTestAccountV2(parentAccountId: number, te
10
10
  personalAccessKey?: string;
11
11
  }>;
12
12
  export declare function buildDeveloperTestAccount(testAccountName: string, parentAccountConfig: HubSpotConfigAccount, env: Environment, portalLimit: number, useV2?: boolean): Promise<number>;
13
- type SandboxAccount = SandboxResponse & {
14
- name: string;
15
- };
16
- export declare function buildSandbox(sandboxName: string, parentAccountConfig: HubSpotConfigAccount, sandboxType: SandboxAccountType, env: Environment, force?: boolean): Promise<SandboxAccount>;
17
13
  export declare function buildV2Sandbox(sandboxName: string, parentAccountConfig: HubSpotConfigAccount, sandboxType: SandboxAccountType, syncObjectRecords: boolean, env: Environment, force?: boolean): Promise<{
18
14
  sandbox: V2Sandbox;
19
15
  }>;
20
- export {};
@@ -3,13 +3,14 @@ import { getConfigAccountIfExists, updateConfigAccount, } from '@hubspot/local-d
3
3
  import { uiLogger } from './ui/logger.js';
4
4
  import { createDeveloperTestAccount, fetchDeveloperTestAccountGateSyncStatus, generateDeveloperTestAccountPersonalAccessKey, } from '@hubspot/local-dev-lib/api/developerTestAccounts';
5
5
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
6
- import { createSandbox, createV2Sandbox, getSandboxPersonalAccessKey, } from '@hubspot/local-dev-lib/api/sandboxHubs';
6
+ import { createV2Sandbox, getSandboxPersonalAccessKey, } from '@hubspot/local-dev-lib/api/sandboxHubs';
7
+ import { PromptExitError } from './errors/PromptExitError.js';
7
8
  import { personalAccessKeyPrompt } from './prompts/personalAccessKeyPrompt.js';
8
9
  import { createDeveloperTestAccountConfigPrompt } from './prompts/createDeveloperTestAccountConfigPrompt.js';
9
10
  import { cliAccountNamePrompt } from './prompts/accountNamePrompt.js';
10
11
  import SpinniesManager from './ui/SpinniesManager.js';
11
12
  import { debugError, logError } from './errorHandlers/index.js';
12
- import { SANDBOX_API_TYPE_MAP, SANDBOX_TYPE_MAP_V2, handleSandboxCreateError, } from './sandboxes.js';
13
+ import { SANDBOX_TYPE_MAP_V2, handleSandboxCreateError } from './sandboxes.js';
13
14
  import { handleDeveloperTestAccountCreateError } from './developerTestAccounts.js';
14
15
  import { lib } from '../lang/en.js';
15
16
  import { poll } from './polling.js';
@@ -127,51 +128,14 @@ export async function buildDeveloperTestAccount(testAccountName, parentAccountCo
127
128
  await saveAccountToConfig(developerTestAccountId, testAccountName, env, developerTestAccountPersonalAccessKey);
128
129
  }
129
130
  catch (err) {
131
+ if (err instanceof PromptExitError) {
132
+ throw err;
133
+ }
130
134
  logError(err);
131
135
  throw err;
132
136
  }
133
137
  return developerTestAccountId;
134
138
  }
135
- export async function buildSandbox(sandboxName, parentAccountConfig, sandboxType, env, force = false) {
136
- const sandboxTypeKey = sandboxType === HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX
137
- ? 'standard'
138
- : 'developer';
139
- const parentAccountId = parentAccountConfig.accountId;
140
- if (!parentAccountId) {
141
- throw new Error(lib.sandbox.create[sandboxTypeKey].loading.fail(''));
142
- }
143
- SpinniesManager.init({
144
- succeedColor: 'white',
145
- });
146
- uiLogger.log('');
147
- SpinniesManager.add('buildSandbox', {
148
- text: lib.sandbox.create[sandboxTypeKey].loading.add(sandboxName),
149
- });
150
- let sandbox;
151
- try {
152
- const sandboxApiType = SANDBOX_API_TYPE_MAP[sandboxType];
153
- const { data } = await createSandbox(parentAccountId, sandboxName, sandboxApiType);
154
- sandbox = { name: sandboxName, ...data };
155
- SpinniesManager.succeed('buildSandbox', {
156
- text: lib.sandbox.create[sandboxTypeKey].loading.succeed(sandboxName, sandbox.sandbox.sandboxHubId.toString()),
157
- });
158
- }
159
- catch (e) {
160
- debugError(e);
161
- SpinniesManager.fail('buildSandbox', {
162
- text: lib.sandbox.create[sandboxTypeKey].loading.fail(sandboxName),
163
- });
164
- handleSandboxCreateError(e, env, sandboxName, parentAccountId);
165
- }
166
- try {
167
- await saveAccountToConfig(sandbox.sandbox.sandboxHubId, sandboxName, env, sandbox.personalAccessKey, force);
168
- }
169
- catch (err) {
170
- logError(err);
171
- throw err;
172
- }
173
- return sandbox;
174
- }
175
139
  export async function buildV2Sandbox(sandboxName, parentAccountConfig, sandboxType, syncObjectRecords, env, force = false) {
176
140
  const sandboxTypeKey = sandboxType === HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX
177
141
  ? 'standard'
@@ -210,6 +174,9 @@ export async function buildV2Sandbox(sandboxName, parentAccountConfig, sandboxTy
210
174
  await saveAccountToConfig(sandbox.sandboxHubId, sandboxName, env, pak, force);
211
175
  }
212
176
  catch (err) {
177
+ if (err instanceof PromptExitError) {
178
+ throw err;
179
+ }
213
180
  logError(err);
214
181
  throw err;
215
182
  }
@@ -77,8 +77,6 @@ export declare const APP_AUTH_TYPES: {
77
77
  };
78
78
  export declare const FEATURES: {
79
79
  readonly UNIFIED_APPS: "Developers:UnifiedApps:PrivateBeta";
80
- readonly SANDBOXES_V2: "sandboxes:v2:enabled";
81
- readonly SANDBOXES_V2_CLI: "sandboxes:v2:cliEnabled";
82
80
  readonly APP_EVENTS: "Developers:UnifiedApps:AppEventsAccess";
83
81
  readonly APPS_HOME: "UIE:AppHome";
84
82
  readonly THEME_MIGRATION_2025_2: "Developers:ProjectThemeMigrations:2025.2";
package/lib/constants.js CHANGED
@@ -69,8 +69,6 @@ export const APP_AUTH_TYPES = {
69
69
  };
70
70
  export const FEATURES = {
71
71
  UNIFIED_APPS: 'Developers:UnifiedApps:PrivateBeta',
72
- SANDBOXES_V2: 'sandboxes:v2:enabled',
73
- SANDBOXES_V2_CLI: 'sandboxes:v2:cliEnabled',
74
72
  APP_EVENTS: 'Developers:UnifiedApps:AppEventsAccess',
75
73
  APPS_HOME: 'UIE:AppHome',
76
74
  THEME_MIGRATION_2025_2: 'Developers:ProjectThemeMigrations:2025.2',
@@ -0,0 +1,4 @@
1
+ export declare class PromptExitError extends Error {
2
+ exitCode: number;
3
+ constructor(message: string, exitCode: number);
4
+ }
@@ -0,0 +1,8 @@
1
+ export class PromptExitError extends Error {
2
+ exitCode;
3
+ constructor(message, exitCode) {
4
+ super(message);
5
+ this.name = 'PromptExitError';
6
+ this.exitCode = exitCode;
7
+ }
8
+ }
@@ -174,6 +174,20 @@ describe('lib/projects/components', () => {
174
174
  expect(mockedFs.readFileSync).toHaveBeenCalledWith('/src/path/package.json', 'utf-8');
175
175
  consoleSpy.mockRestore();
176
176
  });
177
+ it('skips tooling config files on collision instead of renaming them', () => {
178
+ const collision = {
179
+ ...mockCollision,
180
+ collisions: [
181
+ 'src/app/cards/.prettierrc.json',
182
+ 'src/app/cards/component.js',
183
+ 'src/app/cards/eslint.config.js',
184
+ ],
185
+ };
186
+ mockedFs.copyFileSync.mockImplementation(() => { });
187
+ handleComponentCollision(collision);
188
+ expect(mockedFs.copyFileSync).toHaveBeenCalledTimes(1);
189
+ expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/src/app/cards/component.js', '/dest/path/src/app/cards/component-2.js');
190
+ });
177
191
  it('falls back to timestamp when maxAttempts is exhausted', () => {
178
192
  const collision = {
179
193
  ...mockCollision,
@@ -114,9 +114,19 @@ export function handleComponentCollision({ dest, src, collisions }) {
114
114
  }
115
115
  });
116
116
  const filenameDifferentiator = generateSafeFilenameDifferentiator(sourceFiles, hsMetaFiles);
117
- // Exclude markdown files fromthe rename process because they should not be duplicated
118
117
  const sourceFilenameMapping = sourceFiles
119
- .filter(filename => !filename.endsWith('.md'))
118
+ .filter(filename => {
119
+ // Exclude markdown files from the rename process because they should not be duplicated
120
+ if (filename.endsWith('.md')) {
121
+ return false;
122
+ }
123
+ // Also exclude the tooling config we add to new projects
124
+ const base = path.parse(filename).base;
125
+ if (base === '.prettierrc.json' || base === 'eslint.config.js') {
126
+ return false;
127
+ }
128
+ return true;
129
+ })
120
130
  .reduce((acc, filename) => {
121
131
  return {
122
132
  ...acc,
@@ -6,6 +6,7 @@ import { EXIT_CODES } from '../../enums/exitCodes.js';
6
6
  import { isAppIRNode } from '../../projects/structure.js';
7
7
  import { uiLine } from '../../ui/index.js';
8
8
  import { logError } from '../../errorHandlers/index.js';
9
+ import { PromptExitError } from '../../errors/PromptExitError.js';
9
10
  import { installAppAutoPrompt, installAppBrowserPrompt, } from '../../prompts/installAppPrompt.js';
10
11
  import { confirmPrompt } from '../../prompts/promptUtils.js';
11
12
  import { lib } from '../../../lang/en.js';
@@ -313,6 +314,9 @@ class AppDevModeInterface {
313
314
  }
314
315
  }
315
316
  catch (e) {
317
+ if (e instanceof PromptExitError) {
318
+ throw e;
319
+ }
316
320
  if (SpinniesManager.pick('fetchAppData')) {
317
321
  SpinniesManager.fail('fetchAppData', {
318
322
  text: lib.AppDevModeInterface.fetchAppData.error,
@@ -15,6 +15,7 @@ import { componentIsApp, componentIsPublicApp, CONFIG_FILES, getAppCardConfigs,
15
15
  import { ComponentTypes, } from '../../../types/Projects.js';
16
16
  import { UI_COLORS, uiCommandReference, uiAccountDescription, uiLink, uiLine, } from '../../ui/index.js';
17
17
  import { logError } from '../../errorHandlers/index.js';
18
+ import { PromptExitError } from '../../errors/PromptExitError.js';
18
19
  import { installAppBrowserPrompt } from '../../prompts/installAppPrompt.js';
19
20
  import { confirmPrompt } from '../../prompts/promptUtils.js';
20
21
  import { handleKeypress } from '../../process.js';
@@ -89,6 +90,9 @@ class LocalDevManager_DEPRECATED {
89
90
  await this.checkPublicAppInstallation();
90
91
  }
91
92
  catch (e) {
93
+ if (e instanceof PromptExitError) {
94
+ throw e;
95
+ }
92
96
  logError(e);
93
97
  }
94
98
  }
@@ -1,5 +1,5 @@
1
1
  import { HUBSPOT_ACCOUNT_TYPE_STRINGS } from '@hubspot/local-dev-lib/constants/config';
2
- import { getConfigAccountById, getConfigAccountIfExists, } from '@hubspot/local-dev-lib/config';
2
+ import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
3
3
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
4
4
  import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
5
5
  import { isMissingScopeError } from '@hubspot/local-dev-lib/errors/index';
@@ -18,12 +18,10 @@ import { selectDeveloperTestTargetAccountPrompt } from '../../../prompts/project
18
18
  import { selectSandboxTargetAccountPrompt } from '../../../prompts/projectDevTargetAccountPrompt.js';
19
19
  import { validateSandboxUsageLimits } from '../../../sandboxes.js';
20
20
  import { logError } from '../../../errorHandlers/index.js';
21
- import { syncSandbox } from '../../../sandboxSync.js';
22
- import { getAvailableSyncTypes } from '../../../sandboxes.js';
23
21
  import { hubspotAccountNamePrompt } from '../../../prompts/accountNamePrompt.js';
24
22
  import { trackCommandMetadataUsage } from '../../../usageTracking.js';
25
23
  import { validateDevTestAccountUsageLimits } from '../../../developerTestAccounts.js';
26
- import { buildSandbox, buildDeveloperTestAccount, saveAccountToConfig, } from '../../../buildAccount.js';
24
+ import { buildDeveloperTestAccount, saveAccountToConfig, buildV2Sandbox, } from '../../../buildAccount.js';
27
25
  import { debugError } from '../../../errorHandlers/index.js';
28
26
  import { listPrompt } from '../../../prompts/promptUtils.js';
29
27
  import { confirmUseExistingDeveloperTestAccountPrompt } from '../../../prompts/projectDevTargetAccountPrompt.js';
@@ -119,13 +117,9 @@ export async function createSandboxForLocalDev(accountId, accountConfig, env) {
119
117
  accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
120
118
  });
121
119
  trackCommandMetadataUsage('sandbox-create', { step: 'project-dev' }, accountId);
122
- const result = await buildSandbox(name, accountConfig, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, env);
123
- const targetAccountId = result.sandbox.sandboxHubId;
124
- const sandboxAccountConfig = getConfigAccountById(result.sandbox.sandboxHubId);
125
- const syncTasks = await getAvailableSyncTypes(accountConfig, sandboxAccountConfig);
126
- // For v1 sandboxes, keep sync here. Once we migrate to v2, this will be handled by BE automatically
127
- await syncSandbox(sandboxAccountConfig, accountConfig, env, syncTasks, true);
128
- return targetAccountId;
120
+ const result = await buildV2Sandbox(name, accountConfig, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, false, // syncObjectRecords
121
+ env);
122
+ return result.sandbox.sandboxHubId;
129
123
  }
130
124
  catch (err) {
131
125
  logError(err);
@@ -2,21 +2,22 @@ import { promptUser } from './promptUtils.js';
2
2
  import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
3
3
  import { fetchProjects } from '@hubspot/local-dev-lib/api/projects';
4
4
  import { logError, ApiErrorContext } from '../errorHandlers/index.js';
5
- import { uiLogger } from '../ui/logger.js';
6
- import { EXIT_CODES } from '../enums/exitCodes.js';
7
5
  import { lib } from '../../lang/en.js';
6
+ import { PromptExitError } from '../errors/PromptExitError.js';
7
+ import { EXIT_CODES } from '../enums/exitCodes.js';
8
+ import { uiLogger } from '../ui/logger.js';
8
9
  async function createProjectsList(accountId) {
9
- try {
10
- if (accountId) {
11
- const { data: projects } = await fetchProjects(accountId);
12
- return projects.results;
13
- }
10
+ if (!accountId) {
14
11
  uiLogger.error(lib.prompts.downloadProjectPrompt.errors.accountIdRequired);
15
- process.exit(EXIT_CODES.ERROR);
12
+ throw new PromptExitError(lib.prompts.downloadProjectPrompt.errors.accountIdRequired, EXIT_CODES.ERROR);
13
+ }
14
+ try {
15
+ const { data: projects } = await fetchProjects(accountId);
16
+ return projects.results;
16
17
  }
17
18
  catch (e) {
18
- logError(e, accountId ? new ApiErrorContext({ accountId }) : undefined);
19
- process.exit(EXIT_CODES.ERROR);
19
+ logError(e, new ApiErrorContext({ accountId }));
20
+ throw new PromptExitError('Failed to fetch projects', EXIT_CODES.ERROR);
20
21
  }
21
22
  }
22
23
  export async function downloadProjectPrompt(promptOptions) {
@@ -1,8 +1,9 @@
1
1
  import open from 'open';
2
2
  import { promptUser } from './promptUtils.js';
3
- import { EXIT_CODES } from '../enums/exitCodes.js';
4
3
  import { lib } from '../../lang/en.js';
5
4
  import { uiLogger } from '../ui/logger.js';
5
+ import { PromptExitError } from '../errors/PromptExitError.js';
6
+ import { EXIT_CODES } from '../enums/exitCodes.js';
6
7
  export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
7
8
  uiLogger.log('');
8
9
  if (isReinstall) {
@@ -20,7 +21,7 @@ export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
20
21
  });
21
22
  if (!isReinstall && !shouldOpenBrowser) {
22
23
  uiLogger.log(lib.prompts.installAppPrompt.decline);
23
- process.exit(EXIT_CODES.SUCCESS);
24
+ throw new PromptExitError(lib.prompts.installAppPrompt.decline, EXIT_CODES.SUCCESS);
24
25
  }
25
26
  else if (!shouldOpenBrowser) {
26
27
  return;
@@ -6,8 +6,9 @@ import { uiLogger } from '../ui/logger.js';
6
6
  import { promptUser } from './promptUtils.js';
7
7
  import { getCliAccountNamePromptConfig, } from './accountNamePrompt.js';
8
8
  import { uiInfoSection } from '../ui/index.js';
9
- import { EXIT_CODES } from '../enums/exitCodes.js';
10
9
  import { lib } from '../../lang/en.js';
10
+ import { PromptExitError } from '../errors/PromptExitError.js';
11
+ import { EXIT_CODES } from '../enums/exitCodes.js';
11
12
  /**
12
13
  * Displays notification to user that we are about to open the browser,
13
14
  * then opens their browser to the personal-access-key shortlink
@@ -27,7 +28,7 @@ export async function personalAccessKeyPrompt({ env, account, }) {
27
28
  ]);
28
29
  if (!choice) {
29
30
  deleteConfigFileIfEmpty();
30
- process.exit(EXIT_CODES.SUCCESS);
31
+ throw new PromptExitError(lib.prompts.personalAccessKeyPrompt.errors.authCancelled, EXIT_CODES.SUCCESS);
31
32
  }
32
33
  if (choice ===
33
34
  lib.prompts.personalAccessKeyPrompt.personalAccessKeyPromptChoices
@@ -6,6 +6,7 @@ import { lib } from '../../lang/en.js';
6
6
  import { uiLogger } from '../ui/logger.js';
7
7
  import { uiAccountDescription } from '../ui/index.js';
8
8
  import { isSandbox } from '../accountTypes.js';
9
+ import { PromptExitError } from '../errors/PromptExitError.js';
9
10
  import { EXIT_CODES } from '../enums/exitCodes.js';
10
11
  function mapNestedAccount(accountConfig) {
11
12
  const parentAccountId = accountConfig.parentAccountId ?? null;
@@ -23,20 +24,18 @@ function getNonConfigDeveloperTestAccountName(account) {
23
24
  }
24
25
  export async function selectSandboxTargetAccountPrompt(accounts, defaultAccountConfig) {
25
26
  const defaultAccountId = defaultAccountConfig.accountId;
27
+ if (!defaultAccountId) {
28
+ uiLogger.error(lib.prompts.projectDevTargetAccountPrompt.noAccountId);
29
+ throw new PromptExitError(lib.prompts.projectDevTargetAccountPrompt.noAccountId, EXIT_CODES.ERROR);
30
+ }
26
31
  let choices = [];
27
32
  let sandboxUsage = {
28
33
  STANDARD: { used: 0, available: 0, limit: 0 },
29
34
  DEVELOPER: { used: 0, available: 0, limit: 0 },
30
35
  };
31
36
  try {
32
- if (defaultAccountId) {
33
- const { data } = await getSandboxUsageLimits(defaultAccountId);
34
- sandboxUsage = data.usage;
35
- }
36
- else {
37
- uiLogger.error(lib.prompts.projectDevTargetAccountPrompt.noAccountId);
38
- process.exit(EXIT_CODES.ERROR);
39
- }
37
+ const { data } = await getSandboxUsageLimits(defaultAccountId);
38
+ sandboxUsage = data.usage;
40
39
  }
41
40
  catch (err) {
42
41
  uiLogger.debug('Unable to fetch sandbox usage limits: ', err);
@@ -83,16 +82,14 @@ export async function selectSandboxTargetAccountPrompt(accounts, defaultAccountC
83
82
  }
84
83
  export async function selectDeveloperTestTargetAccountPrompt(accounts, defaultAccountConfig) {
85
84
  const defaultAccountId = defaultAccountConfig.accountId;
85
+ if (!defaultAccountId) {
86
+ uiLogger.error(lib.prompts.projectDevTargetAccountPrompt.noAccountId);
87
+ throw new PromptExitError(lib.prompts.projectDevTargetAccountPrompt.noAccountId, EXIT_CODES.ERROR);
88
+ }
86
89
  let devTestAccountsResponse;
87
90
  try {
88
- if (defaultAccountId) {
89
- const { data } = await fetchDeveloperTestAccounts(defaultAccountId);
90
- devTestAccountsResponse = data;
91
- }
92
- else {
93
- uiLogger.error(lib.prompts.projectDevTargetAccountPrompt.noAccountId);
94
- process.exit(EXIT_CODES.ERROR);
95
- }
91
+ const { data } = await fetchDeveloperTestAccounts(defaultAccountId);
92
+ devTestAccountsResponse = data;
96
93
  }
97
94
  catch (err) {
98
95
  uiLogger.debug('Unable to fetch developer test account usage limits: ', err);
@@ -4,25 +4,29 @@ import { lib } from '../../lang/en.js';
4
4
  import { debugError } from '../errorHandlers/index.js';
5
5
  import { uiLogger } from '../ui/logger.js';
6
6
  import { fetchTables } from '@hubspot/local-dev-lib/api/hubdb';
7
- import { EXIT_CODES } from '../enums/exitCodes.js';
8
7
  import { isValidPath, untildify } from '@hubspot/local-dev-lib/path';
8
+ import { PromptExitError } from '../errors/PromptExitError.js';
9
+ import { EXIT_CODES } from '../enums/exitCodes.js';
9
10
  async function fetchHubDBOptions(accountId) {
10
11
  try {
11
12
  const { data: { results: tables }, } = await fetchTables(accountId);
12
13
  if (tables.length === 0) {
13
14
  uiLogger.log(lib.prompts.selectHubDBTablePrompt.errors.noTables(accountId.toString()));
14
- process.exit(EXIT_CODES.SUCCESS);
15
+ throw new PromptExitError(lib.prompts.selectHubDBTablePrompt.errors.noTables(accountId.toString()), EXIT_CODES.SUCCESS);
15
16
  }
16
17
  return tables;
17
18
  }
18
19
  catch (error) {
20
+ if (error instanceof PromptExitError) {
21
+ throw error;
22
+ }
19
23
  debugError(error, { accountId });
20
24
  uiLogger.error(lib.prompts.selectHubDBTablePrompt.errors.errorFetchingTables(accountId.toString()));
21
- process.exit(EXIT_CODES.ERROR);
25
+ throw new PromptExitError(lib.prompts.selectHubDBTablePrompt.errors.errorFetchingTables(accountId.toString()), EXIT_CODES.ERROR);
22
26
  }
23
27
  }
24
28
  export async function selectHubDBTablePrompt({ accountId, options, skipDestPrompt = true, }) {
25
- const hubdbTables = (await fetchHubDBOptions(accountId)) || [];
29
+ const hubdbTables = await fetchHubDBOptions(accountId);
26
30
  const id = options.tableId?.toString();
27
31
  const isValidTable = options.tableId && hubdbTables.find(table => table.id === id);
28
32
  return promptUser([
@@ -4,13 +4,14 @@ import { uiLine } from '../ui/index.js';
4
4
  import { logError } from '../errorHandlers/index.js';
5
5
  import { uiLogger } from '../ui/logger.js';
6
6
  import { fetchPublicAppsForPortal } from '@hubspot/local-dev-lib/api/appsDev';
7
+ import { PromptExitError } from '../errors/PromptExitError.js';
7
8
  import { EXIT_CODES } from '../enums/exitCodes.js';
8
9
  async function fetchPublicAppOptions(accountId, accountName, isMigratingApp = false) {
10
+ if (!accountId) {
11
+ uiLogger.error(lib.prompts.selectPublicAppForMigrationPrompt.errors.noAccountId);
12
+ throw new PromptExitError(lib.prompts.selectPublicAppForMigrationPrompt.errors.noAccountId, EXIT_CODES.ERROR);
13
+ }
9
14
  try {
10
- if (!accountId) {
11
- uiLogger.error(lib.prompts.selectPublicAppForMigrationPrompt.errors.noAccountId);
12
- process.exit(EXIT_CODES.ERROR);
13
- }
14
15
  const { data: { results: publicApps }, } = await fetchPublicAppsForPortal(accountId);
15
16
  const filteredPublicApps = publicApps.filter(app => !app.projectId && !app.sourceId);
16
17
  if (!filteredPublicApps.length ||
@@ -24,14 +25,19 @@ async function fetchPublicAppOptions(accountId, accountName, isMigratingApp = fa
24
25
  uiLogger.error(`${lib.prompts.selectPublicAppForMigrationPrompt.errors.noAppsClone}\n${lib.prompts.selectPublicAppForMigrationPrompt.errors.noAppsCloneMessage(accountName)}`);
25
26
  }
26
27
  uiLine();
27
- process.exit(EXIT_CODES.SUCCESS);
28
+ throw new PromptExitError(isMigratingApp
29
+ ? lib.prompts.selectPublicAppForMigrationPrompt.errors.noAppsMigration
30
+ : lib.prompts.selectPublicAppForMigrationPrompt.errors.noAppsClone, EXIT_CODES.SUCCESS);
28
31
  }
29
32
  return filteredPublicApps;
30
33
  }
31
34
  catch (error) {
35
+ if (error instanceof PromptExitError) {
36
+ throw error;
37
+ }
32
38
  logError(error, accountId ? { accountId } : undefined);
33
39
  uiLogger.error(lib.prompts.selectPublicAppForMigrationPrompt.errors.errorFetchingApps);
34
- process.exit(EXIT_CODES.ERROR);
40
+ throw new PromptExitError(lib.prompts.selectPublicAppForMigrationPrompt.errors.errorFetchingApps, EXIT_CODES.ERROR);
35
41
  }
36
42
  }
37
43
  export async function selectPublicAppForMigrationPrompt({ accountId, accountName, isMigratingApp = false, }) {