@hubspot/cli 8.0.10-experimental.1 → 8.0.10-experimental.3

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 (111) hide show
  1. package/commands/account/auth.js +15 -5
  2. package/commands/account/use.js +14 -4
  3. package/commands/auth.js +10 -6
  4. package/commands/cms/__tests__/watch.test.js +0 -8
  5. package/commands/cms/function/logs.js +1 -0
  6. package/commands/cms/theme/preview.js +2 -4
  7. package/commands/cms/watch.d.ts +0 -1
  8. package/commands/cms/watch.js +2 -8
  9. package/commands/feedback.js +1 -1
  10. package/commands/hubdb/clear.js +4 -0
  11. package/commands/hubdb/delete.js +4 -0
  12. package/commands/hubdb/fetch.js +4 -0
  13. package/commands/init.js +4 -0
  14. package/commands/mcp/__tests__/start.test.js +8 -1
  15. package/commands/mcp/setup.js +1 -9
  16. package/commands/mcp/start.js +0 -1
  17. package/commands/project/__tests__/create.test.js +1 -1
  18. package/commands/project/create.js +2 -2
  19. package/commands/project/dev/index.js +29 -19
  20. package/commands/project/download.js +5 -1
  21. package/commands/project/watch.js +15 -2
  22. package/commands/sandbox/__tests__/create.test.js +1 -48
  23. package/commands/sandbox/create.js +3 -30
  24. package/commands/testAccount/create.js +4 -0
  25. package/lang/en.d.ts +13 -6
  26. package/lang/en.js +13 -6
  27. package/lib/__tests__/buildAccount.test.js +1 -52
  28. package/lib/__tests__/sandboxes.test.js +1 -29
  29. package/lib/__tests__/serverlessLogs.test.js +79 -64
  30. package/lib/accountAuth.js +4 -0
  31. package/lib/buildAccount.d.ts +1 -6
  32. package/lib/buildAccount.js +9 -42
  33. package/lib/constants.d.ts +1 -3
  34. package/lib/constants.js +1 -3
  35. package/lib/errors/PromptExitError.d.ts +4 -0
  36. package/lib/errors/PromptExitError.js +8 -0
  37. package/lib/generateSelectors.js +1 -2
  38. package/lib/mcp/__tests__/setup.test.js +357 -28
  39. package/lib/mcp/setup.d.ts +1 -0
  40. package/lib/mcp/setup.js +77 -30
  41. package/lib/projects/__tests__/components.test.js +14 -0
  42. package/lib/projects/components.js +12 -2
  43. package/lib/projects/create/__tests__/legacy.test.js +6 -24
  44. package/lib/projects/create/index.js +1 -4
  45. package/lib/projects/create/legacy.js +3 -8
  46. package/lib/projects/create/v2.js +1 -9
  47. package/lib/projects/ensureProjectExists.js +1 -2
  48. package/lib/projects/localDev/AppDevModeInterface.js +4 -0
  49. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +4 -0
  50. package/lib/projects/localDev/helpers/account.js +5 -11
  51. package/lib/projects/pollProjectBuildAndDeploy.js +90 -85
  52. package/lib/projects/upload.d.ts +1 -0
  53. package/lib/projects/upload.js +37 -46
  54. package/lib/projects/watch.d.ts +2 -1
  55. package/lib/projects/watch.js +32 -24
  56. package/lib/prompts/downloadProjectPrompt.js +11 -10
  57. package/lib/prompts/installAppPrompt.js +3 -2
  58. package/lib/prompts/personalAccessKeyPrompt.js +3 -2
  59. package/lib/prompts/projectDevTargetAccountPrompt.js +13 -16
  60. package/lib/prompts/selectHubDBTablePrompt.js +8 -4
  61. package/lib/prompts/selectPublicAppForMigrationPrompt.js +12 -6
  62. package/lib/sandboxes.d.ts +1 -9
  63. package/lib/sandboxes.js +0 -21
  64. package/lib/serverlessLogs.js +50 -44
  65. package/lib/{cms/devServerProcess.d.ts → theme/cmsDevServerProcess.d.ts} +2 -3
  66. package/lib/theme/cmsDevServerProcess.js +148 -0
  67. package/lib/theme/cmsDevServerRunner.d.ts +14 -0
  68. package/lib/theme/cmsDevServerRunner.js +90 -0
  69. package/lib/usageTracking.js +8 -5
  70. package/mcp-server/tools/cms/HsCreateFunctionTool.js +1 -1
  71. package/mcp-server/tools/cms/HsCreateModuleTool.js +1 -1
  72. package/mcp-server/tools/cms/HsCreateTemplateTool.js +1 -1
  73. package/mcp-server/tools/cms/HsFunctionLogsTool.js +1 -1
  74. package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
  75. package/mcp-server/tools/cms/HsListTool.js +1 -1
  76. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -2
  77. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +1 -2
  78. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +1 -2
  79. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +1 -2
  80. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +1 -2
  81. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +1 -2
  82. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +1 -1
  83. package/mcp-server/tools/project/AddFeatureToProjectTool.js +1 -1
  84. package/mcp-server/tools/project/CreateProjectTool.d.ts +1 -1
  85. package/mcp-server/tools/project/CreateProjectTool.js +1 -1
  86. package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
  87. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  88. package/mcp-server/tools/project/UploadProjectTools.js +1 -1
  89. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  90. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +1 -2
  91. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -2
  92. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +1 -2
  93. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +1 -2
  94. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +10 -2
  95. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
  96. package/mcp-server/tools/project/constants.d.ts +1 -1
  97. package/mcp-server/utils/__tests__/command.test.js +233 -3
  98. package/mcp-server/utils/__tests__/feedbackTracking.test.js +9 -64
  99. package/mcp-server/utils/command.d.ts +5 -0
  100. package/mcp-server/utils/command.js +24 -0
  101. package/mcp-server/utils/feedbackTracking.js +2 -17
  102. package/package.json +4 -5
  103. package/lib/__tests__/sandboxSync.test.d.ts +0 -1
  104. package/lib/__tests__/sandboxSync.test.js +0 -147
  105. package/lib/cms/devServerProcess.js +0 -200
  106. package/lib/sandboxSync.d.ts +0 -4
  107. package/lib/sandboxSync.js +0 -102
  108. package/mcp-server/utils/__tests__/project.test.d.ts +0 -1
  109. package/mcp-server/utils/__tests__/project.test.js +0 -140
  110. package/mcp-server/utils/project.d.ts +0 -5
  111. package/mcp-server/utils/project.js +0 -18
@@ -6,17 +6,14 @@ import { HUBSPOT_ACCOUNT_TYPES, HUBSPOT_ACCOUNT_TYPE_STRINGS, } from '@hubspot/l
6
6
  import { commands, lib } from '../../lang/en.js';
7
7
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
8
8
  import { uiFeatureHighlight, uiBetaTag } from '../../lib/ui/index.js';
9
- import { SANDBOX_TYPE_MAP, getAvailableSyncTypes, SYNC_TYPES, validateSandboxUsageLimits, } from '../../lib/sandboxes.js';
9
+ import { SANDBOX_TYPE_MAP, validateSandboxUsageLimits, } from '../../lib/sandboxes.js';
10
10
  import { trackCommandUsage } from '../../lib/usageTracking.js';
11
11
  import { sandboxTypePrompt } from '../../lib/prompts/sandboxesPrompt.js';
12
12
  import { promptUser } from '../../lib/prompts/promptUtils.js';
13
- import { syncSandbox } from '../../lib/sandboxSync.js';
14
13
  import { logError } from '../../lib/errorHandlers/index.js';
15
- import { buildSandbox, buildV2Sandbox } from '../../lib/buildAccount.js';
14
+ import { buildV2Sandbox } from '../../lib/buildAccount.js';
16
15
  import { hubspotAccountNamePrompt } from '../../lib/prompts/accountNamePrompt.js';
17
16
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
18
- import { hasFeature } from '../../lib/hasFeature.js';
19
- import { FEATURES } from '../../lib/constants.js';
20
17
  const command = 'create';
21
18
  const describe = uiBetaTag(commands.sandbox.subcommands.create.describe, false);
22
19
  async function handler(args) {
@@ -98,38 +95,14 @@ async function handler(args) {
98
95
  contactRecordsSyncPromptResult = contactRecordsSyncPrompt;
99
96
  }
100
97
  }
101
- // Check if parent portal is ungated for v2 sandboxes
102
- const isUngatedForV2Cli = await hasFeature(derivedAccountId, FEATURES.SANDBOXES_V2_CLI);
103
- const isUngatedForV2Sandboxes = await hasFeature(derivedAccountId, FEATURES.SANDBOXES_V2);
104
- const canCreateV2Sandbox = isUngatedForV2Sandboxes && isUngatedForV2Cli;
105
98
  try {
106
- let result;
107
- if (canCreateV2Sandbox) {
108
- result = await buildV2Sandbox(sandboxName, accountConfig, sandboxType, contactRecordsSyncPromptResult, env, force);
109
- }
110
- else {
111
- result = await buildSandbox(sandboxName, accountConfig, sandboxType, env, force);
112
- }
99
+ const result = await buildV2Sandbox(sandboxName, accountConfig, sandboxType, contactRecordsSyncPromptResult, env, force);
113
100
  const sandboxAccountConfig = getConfigAccountById(result.sandbox.sandboxHubId);
114
101
  // Check if sandbox account config exists
115
102
  if (!sandboxAccountConfig) {
116
103
  uiLogger.error(commands.sandbox.subcommands.create.failure.noSandboxAccountConfig(result.sandbox.sandboxHubId));
117
104
  process.exit(EXIT_CODES.ERROR);
118
105
  }
119
- if (result && !canCreateV2Sandbox) {
120
- // For v1 sandboxes, keep sync here. Once we migrate to v2, this will be handled by BE automatically
121
- try {
122
- let availableSyncTasks = await getAvailableSyncTypes(accountConfig, sandboxAccountConfig);
123
- if (!contactRecordsSyncPromptResult) {
124
- availableSyncTasks = availableSyncTasks.filter(t => t.type !== SYNC_TYPES.OBJECT_RECORDS);
125
- }
126
- await syncSandbox(sandboxAccountConfig, accountConfig, env, availableSyncTasks);
127
- }
128
- catch (err) {
129
- logError(err);
130
- throw err;
131
- }
132
- }
133
106
  const highlightItems = ['accountsUseCommand', 'projectCreateCommand'];
134
107
  if (sandboxType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX) {
135
108
  highlightItems.push('projectDevCommand');
@@ -12,6 +12,7 @@ import { fileExists } from '../../lib/validation.js';
12
12
  import { commands } from '../../lang/en.js';
13
13
  import { createDeveloperTestAccountConfigPrompt } from '../../lib/prompts/createDeveloperTestAccountConfigPrompt.js';
14
14
  import { debugError, logError } from '../../lib/errorHandlers/index.js';
15
+ import { PromptExitError } from '../../lib/errors/PromptExitError.js';
15
16
  import SpinniesManager from '../../lib/ui/SpinniesManager.js';
16
17
  import { createDeveloperTestAccountV2, saveAccountToConfig, } from '../../lib/buildAccount.js';
17
18
  import { ACCOUNT_LEVEL_CHOICES, ACCOUNT_LEVELS } from '../../lib/constants.js';
@@ -130,6 +131,9 @@ async function handler(args) {
130
131
  }
131
132
  }
132
133
  catch (e) {
134
+ if (e instanceof PromptExitError) {
135
+ process.exit(e.exitCode);
136
+ }
133
137
  debugError(e);
134
138
  uiLogger.error(commands.testAccount.create.errors.saveAccountToConfigFailure(testAccountConfig.accountName));
135
139
  process.exit(EXIT_CODES.ERROR);
package/lang/en.d.ts CHANGED
@@ -601,11 +601,6 @@ export declare const commands: {
601
601
  src: string;
602
602
  dest: string;
603
603
  };
604
- warnings: {
605
- disableInitial: string;
606
- initialUpload: string;
607
- notUploaded: (path: string) => string;
608
- };
609
604
  };
610
605
  fetch: {
611
606
  describe: string;
@@ -1261,7 +1256,6 @@ export declare const commands: {
1261
1256
  };
1262
1257
  success: (derivedTargets: string[]) => string;
1263
1258
  errors: {
1264
- needsMcpAccess: (accountId?: number) => string;
1265
1259
  errorParsingJsonFIle: (filename: string, errorMessage: string) => string;
1266
1260
  };
1267
1261
  spinners: {
@@ -1295,6 +1289,8 @@ export declare const commands: {
1295
1289
  prompts: {
1296
1290
  targets: string;
1297
1291
  targetsRequired: string;
1292
+ standaloneMode: string;
1293
+ cliVersion: string;
1298
1294
  };
1299
1295
  };
1300
1296
  start: {
@@ -3478,6 +3474,7 @@ export declare const lib: {
3478
3474
  invalidOauthClientSecretCopy: string;
3479
3475
  invalidPersonalAccessKey: string;
3480
3476
  invalidPersonalAccessKeyCopy: string;
3477
+ authCancelled: string;
3481
3478
  };
3482
3479
  };
3483
3480
  createTemplatePrompt: {
@@ -3981,4 +3978,14 @@ export declare const lib: {
3981
3978
  copyingProjectFilesFailed: string;
3982
3979
  };
3983
3980
  };
3981
+ theme: {
3982
+ cmsDevServerProcess: {
3983
+ installStarted: (targetVersion: string) => string;
3984
+ installSucceeded: string;
3985
+ installFailed: string;
3986
+ serverStartError: (error: Error) => string;
3987
+ serverExit: (code: number) => string;
3988
+ serverKill: (signal: NodeJS.Signals) => string;
3989
+ };
3990
+ };
3984
3991
  };
package/lang/en.js CHANGED
@@ -609,11 +609,6 @@ export const commands = {
609
609
  src: 'Path to the local directory your files are in, relative to your current working directory',
610
610
  dest: 'Path in HubSpot Design Tools. Can be a net new path',
611
611
  },
612
- warnings: {
613
- disableInitial: `Passing the "${chalk.bold('--disable-initial')}" option is no longer necessary. Running "${uiCommandReference('hs watch')}" no longer uploads the watched directory by default.`,
614
- initialUpload: `To upload the directory run "${uiCommandReference('hs upload')}" beforehand or add the "${chalk.bold('--initial-upload')}" option when running "${uiCommandReference('hs watch')}".`,
615
- notUploaded: (path) => `The "${uiCommandReference('hs watch')}" command no longer uploads the watched directory when started. The directory "${path}" was not uploaded.`,
616
- },
617
612
  },
618
613
  fetch: {
619
614
  describe: 'Fetch a file, directory or module from HubSpot and write to a path on your computer.',
@@ -1271,7 +1266,6 @@ export const commands = {
1271
1266
  },
1272
1267
  success: (derivedTargets) => `You can now use the HubSpot CLI MCP Server in ${derivedTargets.join(', ')}. ${chalk.bold('You may need to restart these tools to apply the changes')}.`,
1273
1268
  errors: {
1274
- needsMcpAccess: (accountId) => `You must opt in to the developer MCP beta to use this feature on ${uiAccountDescription(accountId)}. Try again with a different account or ${uiLink('join the beta now', getProductUpdatesUrl('239890', accountId))}`,
1275
1269
  errorParsingJsonFIle: (filename, errorMessage) => `Unable to update ${chalk.bold(filename)} due to invalid JSON: ${errorMessage}`,
1276
1270
  },
1277
1271
  spinners: {
@@ -1311,6 +1305,8 @@ export const commands = {
1311
1305
  prompts: {
1312
1306
  targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
1313
1307
  targetsRequired: 'Must choose at least one app to configure.',
1308
+ standaloneMode: 'Do you want to run in standalone mode? (This will use npx @hubspot/cli instead of the installed hs command)',
1309
+ cliVersion: 'Specify a CLI version to pin (leave blank for latest):',
1314
1310
  },
1315
1311
  },
1316
1312
  start: {
@@ -3501,6 +3497,7 @@ export const lib = {
3501
3497
  invalidOauthClientSecretCopy: 'Please copy the actual OAuth2 client secret rather than the asterisks that mask it.',
3502
3498
  invalidPersonalAccessKey: 'You did not enter a valid access key. Please try again.',
3503
3499
  invalidPersonalAccessKeyCopy: 'Please copy the actual access key rather than the bullets that mask it.',
3500
+ authCancelled: 'Authentication cancelled.',
3504
3501
  },
3505
3502
  },
3506
3503
  createTemplatePrompt: {
@@ -4004,4 +4001,14 @@ export const lib = {
4004
4001
  copyingProjectFilesFailed: 'Unable to copy migrated project files',
4005
4002
  },
4006
4003
  },
4004
+ theme: {
4005
+ cmsDevServerProcess: {
4006
+ installStarted: (targetVersion) => `Installing cms-dev-server ${targetVersion}...`,
4007
+ installSucceeded: 'cms-dev-server setup complete',
4008
+ installFailed: 'Failed to install cms-dev-server',
4009
+ serverStartError: (error) => `Failed to start dev server: ${error}`,
4010
+ serverExit: (code) => `Dev server exited with code ${code}`,
4011
+ serverKill: (signal) => `Dev server killed with signal ${signal}`,
4012
+ },
4013
+ },
4007
4014
  };
@@ -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',
@@ -1,6 +1,6 @@
1
- import mockStdIn from 'mock-stdin';
2
1
  import { outputLogs } from '../ui/serverlessFunctionLogs.js';
3
2
  import { tailLogs } from '../serverlessLogs.js';
3
+ import { handleKeypress } from '../process.js';
4
4
  vi.mock('../ui/serverlessFunctionLogs');
5
5
  vi.mock('../ui/SpinniesManager', () => ({
6
6
  default: {
@@ -12,82 +12,87 @@ vi.mock('../ui/SpinniesManager', () => ({
12
12
  stopAll: vi.fn(),
13
13
  },
14
14
  }));
15
+ vi.mock('../process');
15
16
  vi.useFakeTimers();
16
17
  const ACCOUNT_ID = 123;
18
+ function terminateTailLogs() {
19
+ const keypressCallback = vi.mocked(handleKeypress).mock.calls[0][0];
20
+ keypressCallback({ name: 'q' });
21
+ }
17
22
  describe('lib/serverlessLogs', () => {
18
23
  describe('tailLogs()', () => {
19
- let stdinMock;
20
- beforeEach(() => {
21
- // @ts-ignore - we don't need to mock the entire process object
22
- vi.spyOn(process, 'exit').mockImplementation(() => { });
23
- stdinMock = mockStdIn.stdin();
24
- });
25
24
  afterEach(() => {
26
25
  vi.clearAllTimers();
27
- stdinMock.restore();
26
+ vi.clearAllMocks();
28
27
  });
29
28
  it('calls tailCall() to get the next results', async () => {
30
29
  const compact = false;
31
- const fetchLatest = vi.fn(() => {
32
- return Promise.resolve({
33
- data: {
34
- id: '1234',
35
- executionTime: 510,
36
- log: 'Log message',
37
- error: { message: '', type: '', stackTrace: [] },
38
- status: 'SUCCESS',
39
- createdAt: 1620232011451,
40
- memory: '70/128 MB',
41
- duration: '53.40 ms',
42
- },
43
- status: 200,
44
- statusText: 'OK',
45
- headers: {},
46
- config: {},
47
- });
48
- });
49
- const tailCall = vi.fn(() => {
50
- return Promise.resolve({
51
- data: {
52
- results: [],
53
- paging: {
54
- next: {
55
- after: 'somehash',
56
- },
30
+ const fetchLatest = vi.fn(() => Promise.resolve({
31
+ data: {
32
+ id: '1234',
33
+ executionTime: 510,
34
+ log: 'Log message',
35
+ error: { message: '', type: '', stackTrace: [] },
36
+ status: 'SUCCESS',
37
+ createdAt: 1620232011451,
38
+ memory: '70/128 MB',
39
+ duration: '53.40 ms',
40
+ },
41
+ status: 200,
42
+ statusText: 'OK',
43
+ headers: {},
44
+ // eslint-disable-next-line
45
+ config: { headers: {} },
46
+ }));
47
+ const tailCall = vi.fn(() => Promise.resolve({
48
+ data: {
49
+ results: [],
50
+ paging: {
51
+ next: {
52
+ after: 'somehash',
57
53
  },
58
54
  },
59
- status: 200,
60
- statusText: 'OK',
61
- headers: {},
62
- config: {},
63
- });
64
- });
55
+ },
56
+ status: 200,
57
+ statusText: 'OK',
58
+ headers: {},
59
+ // eslint-disable-next-line
60
+ config: { headers: {} },
61
+ }));
65
62
  // @ts-ignore - headers is not used in the actual function and does not need to be mocked
66
- await tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
67
- vi.runOnlyPendingTimers();
63
+ const tailPromise = tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
64
+ await vi.advanceTimersByTimeAsync(0);
68
65
  expect(fetchLatest).toHaveBeenCalled();
66
+ expect(tailCall).toHaveBeenCalledTimes(1);
67
+ await vi.advanceTimersByTimeAsync(5000);
69
68
  expect(tailCall).toHaveBeenCalledTimes(2);
69
+ terminateTailLogs();
70
+ await tailPromise;
70
71
  });
71
72
  it('outputs results', async () => {
72
73
  const compact = false;
73
- const fetchLatest = vi.fn(() => {
74
- return Promise.resolve({
75
- data: {
76
- id: '1234',
77
- executionTime: 510,
78
- log: 'Log message',
79
- error: { message: '', type: '', stackTrace: [], statusCode: null },
80
- status: 'SUCCESS',
81
- createdAt: 1620232011451,
82
- memory: '70/128 MB',
83
- duration: '53.40 ms',
74
+ const fetchLatest = vi.fn(() => Promise.resolve({
75
+ data: {
76
+ id: '1234',
77
+ executionTime: 510,
78
+ log: 'Log message',
79
+ error: {
80
+ message: '',
81
+ type: '',
82
+ stackTrace: [],
83
+ statusCode: null,
84
84
  },
85
- status: 200,
86
- statusText: 'OK',
87
- headers: {},
88
- config: {},
89
- });
90
- });
85
+ status: 'SUCCESS',
86
+ createdAt: 1620232011451,
87
+ memory: '70/128 MB',
88
+ duration: '53.40 ms',
89
+ },
90
+ status: 200,
91
+ statusText: 'OK',
92
+ headers: {},
93
+ // eslint-disable-next-line
94
+ config: { headers: {} },
95
+ }));
91
96
  const latestLogResponse = {
92
97
  results: [
93
98
  {
@@ -117,12 +122,23 @@ describe('lib/serverlessLogs', () => {
117
122
  },
118
123
  },
119
124
  };
120
- const tailCall = vi.fn(() => Promise.resolve({ data: latestLogResponse }));
125
+ const tailCall = vi.fn(() => Promise.resolve({
126
+ data: latestLogResponse,
127
+ status: 200,
128
+ statusText: 'OK',
129
+ headers: {},
130
+ // eslint-disable-next-line
131
+ config: { headers: {} },
132
+ }));
121
133
  // @ts-ignore - headers is not used in the actual function and does not need to be mocked
122
- await tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
123
- vi.runOnlyPendingTimers();
134
+ const tailPromise = tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
135
+ await vi.advanceTimersByTimeAsync(0);
124
136
  expect(outputLogs).toHaveBeenCalledWith(latestLogResponse, expect.objectContaining({ compact }));
137
+ expect(tailCall).toHaveBeenCalledTimes(1);
138
+ await vi.advanceTimersByTimeAsync(5000);
125
139
  expect(tailCall).toHaveBeenCalledTimes(2);
140
+ terminateTailLogs();
141
+ await tailPromise;
126
142
  });
127
143
  it('handles no logs', async () => {
128
144
  const compact = false;
@@ -141,8 +157,7 @@ describe('lib/serverlessLogs', () => {
141
157
  statusCode: 404,
142
158
  }));
143
159
  await tailLogs(ACCOUNT_ID, 'name', fetchLatest, tailCall, compact);
144
- vi.runOnlyPendingTimers();
145
- expect(tailCall).toHaveBeenCalledTimes(2);
160
+ expect(tailCall).toHaveBeenCalledTimes(1);
146
161
  });
147
162
  });
148
163
  });
@@ -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
  }
@@ -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,11 +77,8 @@ 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
- readonly MCP_ACCESS: "Developers:CLIMCPAccess";
85
82
  readonly THEME_MIGRATION_2025_2: "Developers:ProjectThemeMigrations:2025.2";
86
83
  readonly AGENT_TOOLS: "ThirdPartyAgentTools";
87
84
  };
@@ -145,3 +142,4 @@ export declare const ACCOUNT_LEVELS: {
145
142
  readonly ENTERPRISE: "ENTERPRISE";
146
143
  };
147
144
  export declare const ACCOUNT_LEVEL_CHOICES: readonly ["FREE", "STARTER", "PROFESSIONAL", "ENTERPRISE"];
145
+ export declare const FEEDBACK_URL = "https://developers.hubspot.com/feedback";