@hubspot/cli 8.0.10-experimental.2 → 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 (48) 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/theme/preview.js +9 -64
  5. package/commands/hubdb/clear.js +4 -0
  6. package/commands/hubdb/delete.js +4 -0
  7. package/commands/hubdb/fetch.js +4 -0
  8. package/commands/init.js +4 -0
  9. package/commands/project/dev/index.js +29 -19
  10. package/commands/project/download.js +5 -1
  11. package/commands/sandbox/__tests__/create.test.js +1 -48
  12. package/commands/sandbox/create.js +3 -30
  13. package/commands/testAccount/create.js +4 -0
  14. package/lang/en.d.ts +11 -0
  15. package/lang/en.js +11 -0
  16. package/lib/__tests__/buildAccount.test.js +1 -52
  17. package/lib/__tests__/sandboxes.test.js +1 -29
  18. package/lib/__tests__/serverlessLogs.test.js +10 -1
  19. package/lib/accountAuth.js +4 -0
  20. package/lib/buildAccount.d.ts +1 -6
  21. package/lib/buildAccount.js +9 -42
  22. package/lib/constants.d.ts +0 -2
  23. package/lib/constants.js +0 -2
  24. package/lib/errors/PromptExitError.d.ts +4 -0
  25. package/lib/errors/PromptExitError.js +8 -0
  26. package/lib/projects/__tests__/components.test.js +14 -0
  27. package/lib/projects/components.js +12 -2
  28. package/lib/projects/localDev/AppDevModeInterface.js +4 -0
  29. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +4 -0
  30. package/lib/projects/localDev/helpers/account.js +5 -11
  31. package/lib/prompts/downloadProjectPrompt.js +11 -10
  32. package/lib/prompts/installAppPrompt.js +3 -2
  33. package/lib/prompts/personalAccessKeyPrompt.js +3 -2
  34. package/lib/prompts/projectDevTargetAccountPrompt.js +13 -16
  35. package/lib/prompts/selectHubDBTablePrompt.js +8 -4
  36. package/lib/prompts/selectPublicAppForMigrationPrompt.js +12 -6
  37. package/lib/sandboxes.d.ts +1 -9
  38. package/lib/sandboxes.js +0 -21
  39. package/lib/theme/cmsDevServerProcess.d.ts +12 -0
  40. package/lib/theme/cmsDevServerProcess.js +148 -0
  41. package/lib/theme/cmsDevServerRunner.d.ts +14 -0
  42. package/lib/theme/cmsDevServerRunner.js +90 -0
  43. package/lib/usageTracking.js +8 -5
  44. package/package.json +2 -3
  45. package/lib/__tests__/sandboxSync.test.d.ts +0 -1
  46. package/lib/__tests__/sandboxSync.test.js +0 -147
  47. package/lib/sandboxSync.d.ts +0 -4
  48. package/lib/sandboxSync.js +0 -102
@@ -10,6 +10,7 @@ import { makeYargsBuilder } from '../../lib/yargsUtils.js';
10
10
  import { commands } from '../../lang/en.js';
11
11
  import { uiLogger } from '../../lib/ui/logger.js';
12
12
  import { authenticateNewAccount } from '../../lib/accountAuth.js';
13
+ import { PromptExitError } from '../../lib/errors/PromptExitError.js';
13
14
  const TRACKING_STATUS = {
14
15
  STARTED: 'started',
15
16
  ERROR: 'error',
@@ -35,11 +36,20 @@ async function handler(args) {
35
36
  await trackAuthAction('account-auth', authType, TRACKING_STATUS.STARTED);
36
37
  }
37
38
  handleExit(deleteConfigFileIfEmpty);
38
- const updatedConfig = await authenticateNewAccount({
39
- env: args.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD,
40
- providedPersonalAccessKey,
41
- accountId: parsedUserProvidedAccountId,
42
- });
39
+ let updatedConfig;
40
+ try {
41
+ updatedConfig = await authenticateNewAccount({
42
+ env: args.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD,
43
+ providedPersonalAccessKey,
44
+ accountId: parsedUserProvidedAccountId,
45
+ });
46
+ }
47
+ catch (e) {
48
+ if (e instanceof PromptExitError) {
49
+ process.exit(e.exitCode);
50
+ }
51
+ throw e;
52
+ }
43
53
  if (!updatedConfig) {
44
54
  if (!disableTracking) {
45
55
  await trackAuthAction('account-auth', authType, TRACKING_STATUS.ERROR);
@@ -7,6 +7,7 @@ import { uiLogger } from '../../lib/ui/logger.js';
7
7
  import { selectAccountFromConfig, AUTHENTICATE_NEW_ACCOUNT_VALUE, } from '../../lib/prompts/accountsPrompt.js';
8
8
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
9
9
  import { authenticateNewAccount } from '../../lib/accountAuth.js';
10
+ import { PromptExitError } from '../../lib/errors/PromptExitError.js';
10
11
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
11
12
  const command = 'use [account]';
12
13
  const describe = commands.account.subcommands.use.describe;
@@ -24,10 +25,19 @@ async function handler(args) {
24
25
  }
25
26
  }
26
27
  if (newDefaultAccount === AUTHENTICATE_NEW_ACCOUNT_VALUE) {
27
- const updatedConfig = await authenticateNewAccount({
28
- env: args.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD,
29
- setAsDefaultAccount: true,
30
- });
28
+ let updatedConfig;
29
+ try {
30
+ updatedConfig = await authenticateNewAccount({
31
+ env: args.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD,
32
+ setAsDefaultAccount: true,
33
+ });
34
+ }
35
+ catch (e) {
36
+ if (e instanceof PromptExitError) {
37
+ process.exit(e.exitCode);
38
+ }
39
+ throw e;
40
+ }
31
41
  if (!updatedConfig) {
32
42
  process.exit(EXIT_CODES.ERROR);
33
43
  }
package/commands/auth.js CHANGED
@@ -16,6 +16,7 @@ import { authenticateWithOauth } from '../lib/oauth.js';
16
16
  import { EXIT_CODES } from '../lib/enums/exitCodes.js';
17
17
  import { uiFeatureHighlight } from '../lib/ui/index.js';
18
18
  import { logError } from '../lib/errorHandlers/index.js';
19
+ import { PromptExitError } from '../lib/errors/PromptExitError.js';
19
20
  import { commands } from '../lang/en.js';
20
21
  import { uiLogger } from '../lib/ui/logger.js';
21
22
  import { parseStringToNumber } from '../lib/parsing.js';
@@ -90,18 +91,21 @@ async function handler(args) {
90
91
  }
91
92
  break;
92
93
  case PERSONAL_ACCESS_KEY_AUTH_METHOD.value:
93
- const { personalAccessKey } = providedPersonalAccessKey
94
- ? { personalAccessKey: providedPersonalAccessKey }
95
- : await personalAccessKeyPrompt({
96
- env,
97
- account: parsedUserProvidedAccountId,
98
- });
99
94
  try {
95
+ const { personalAccessKey } = providedPersonalAccessKey
96
+ ? { personalAccessKey: providedPersonalAccessKey }
97
+ : await personalAccessKeyPrompt({
98
+ env,
99
+ account: parsedUserProvidedAccountId,
100
+ });
100
101
  token = await getAccessToken(personalAccessKey, env);
101
102
  defaultName = token.hubName ? toKebabCase(token.hubName) : undefined;
102
103
  updatedConfig = await updateConfigWithAccessToken(token, personalAccessKey, env);
103
104
  }
104
105
  catch (e) {
106
+ if (e instanceof PromptExitError) {
107
+ process.exit(e.exitCode);
108
+ }
105
109
  logError(e);
106
110
  }
107
111
  if (!updatedConfig) {
@@ -1,16 +1,12 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import cliProgress from 'cli-progress';
4
3
  import { commands } from '../../../lang/en.js';
5
4
  import { getCwd } from '@hubspot/local-dev-lib/path';
6
- import { FILE_UPLOAD_RESULT_TYPES } from '@hubspot/local-dev-lib/constants/files';
7
5
  import { getThemeJSONPath } from '@hubspot/local-dev-lib/cms/themes';
8
- import { createDevServer } from '@hubspot/cms-dev-server';
9
- import { getUploadableFileList } from '../../../lib/upload.js';
6
+ import { spawnDevServer } from '../../../lib/theme/cmsDevServerProcess.js';
10
7
  import { trackCommandUsage } from '../../../lib/usageTracking.js';
11
8
  import { previewPrompt, previewProjectPrompt, } from '../../../lib/prompts/previewPrompt.js';
12
9
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
13
- import { ApiErrorContext, logError } from '../../../lib/errorHandlers/index.js';
14
10
  import { getProjectConfig } from '../../../lib/projects/config.js';
15
11
  import { findProjectComponents } from '../../../lib/projects/structure.js';
16
12
  import { ComponentTypes } from '../../../types/Projects.js';
@@ -73,67 +69,16 @@ async function determineSrcAndDest(args) {
73
69
  async function handler(args) {
74
70
  const { derivedAccountId, noSsl, resetSession, port, generateFieldsTypes } = args;
75
71
  const { absoluteSrc, dest } = await determineSrcAndDest(args);
76
- const filePaths = await getUploadableFileList(absoluteSrc, false);
77
- function startProgressBar(numFiles) {
78
- const initialUploadProgressBar = new cliProgress.SingleBar({
79
- gracefulExit: true,
80
- format: '[{bar}] {percentage}% | {value}/{total} | {label}',
81
- hideCursor: true,
82
- }, cliProgress.Presets.rect);
83
- initialUploadProgressBar.start(numFiles, 0, {
84
- label: commands.cms.subcommands.theme.subcommands.preview
85
- .initialUploadProgressBar.start,
86
- });
87
- let uploadsHaveStarted = false;
88
- const uploadOptions = {
89
- onAttemptCallback: () => {
90
- /* Intentionally blank */
91
- },
92
- onSuccessCallback: () => {
93
- initialUploadProgressBar.increment();
94
- if (!uploadsHaveStarted) {
95
- uploadsHaveStarted = true;
96
- initialUploadProgressBar.update(0, {
97
- label: commands.cms.subcommands.theme.subcommands.preview
98
- .initialUploadProgressBar.uploading,
99
- });
100
- }
101
- },
102
- onFirstErrorCallback: () => {
103
- /* Intentionally blank */
104
- },
105
- onRetryCallback: () => {
106
- /* Intentionally blank */
107
- },
108
- onFinalErrorCallback: () => initialUploadProgressBar.increment(),
109
- onFinishCallback: (results) => {
110
- initialUploadProgressBar.update(numFiles, {
111
- label: commands.cms.subcommands.theme.subcommands.preview
112
- .initialUploadProgressBar.finish,
113
- });
114
- initialUploadProgressBar.stop();
115
- results.forEach(result => {
116
- if (result.resultType == FILE_UPLOAD_RESULT_TYPES.FAILURE) {
117
- uiLogger.error(commands.cms.subcommands.theme.subcommands.preview.errors.uploadFailed(result.file, dest));
118
- logError(result.error, new ApiErrorContext({
119
- accountId: derivedAccountId,
120
- request: dest,
121
- payload: result.file,
122
- }));
123
- }
124
- });
125
- },
126
- };
127
- return uploadOptions;
128
- }
129
72
  trackCommandUsage('preview', {}, derivedAccountId);
130
- if (port) {
131
- process.env['PORT'] = port.toString();
132
- }
133
- createDevServer(absoluteSrc, false, '', '', !noSsl, generateFieldsTypes, {
134
- filePaths,
73
+ // Spawn dev server in isolated subprocess to avoid React version conflicts
74
+ // File listing and progress bars are handled within the subprocess
75
+ await spawnDevServer({
76
+ absoluteSrc,
77
+ accountName: derivedAccountId?.toString(),
78
+ noSsl,
79
+ port,
80
+ generateFieldsTypes,
135
81
  resetSession: resetSession || false,
136
- startProgressBar,
137
82
  dest,
138
83
  });
139
84
  }
@@ -1,5 +1,6 @@
1
1
  import { uiLogger } from '../../lib/ui/logger.js';
2
2
  import { logError } from '../../lib/errorHandlers/index.js';
3
+ import { PromptExitError } from '../../lib/errors/PromptExitError.js';
3
4
  import { clearHubDbTableRows } from '@hubspot/local-dev-lib/hubdb';
4
5
  import { publishTable } from '@hubspot/local-dev-lib/api/hubdb';
5
6
  import { selectHubDBTablePrompt } from '../../lib/prompts/selectHubDBTablePrompt.js';
@@ -29,6 +30,9 @@ async function handler(args) {
29
30
  }
30
31
  }
31
32
  catch (e) {
33
+ if (e instanceof PromptExitError) {
34
+ process.exit(e.exitCode);
35
+ }
32
36
  logError(e);
33
37
  }
34
38
  }
@@ -1,5 +1,6 @@
1
1
  import { uiLogger } from '../../lib/ui/logger.js';
2
2
  import { logError } from '../../lib/errorHandlers/index.js';
3
+ import { PromptExitError } from '../../lib/errors/PromptExitError.js';
3
4
  import { deleteTable } from '@hubspot/local-dev-lib/api/hubdb';
4
5
  import { trackCommandUsage } from '../../lib/usageTracking.js';
5
6
  import { selectHubDBTablePrompt } from '../../lib/prompts/selectHubDBTablePrompt.js';
@@ -34,6 +35,9 @@ async function handler(args) {
34
35
  process.exit(EXIT_CODES.SUCCESS);
35
36
  }
36
37
  catch (e) {
38
+ if (e instanceof PromptExitError) {
39
+ process.exit(e.exitCode);
40
+ }
37
41
  uiLogger.error(commands.hubdb.subcommands.delete.errors.delete(args.tableId || ''));
38
42
  logError(e);
39
43
  }
@@ -1,5 +1,6 @@
1
1
  import { uiLogger } from '../../lib/ui/logger.js';
2
2
  import { logError } from '../../lib/errorHandlers/index.js';
3
+ import { PromptExitError } from '../../lib/errors/PromptExitError.js';
3
4
  import { downloadHubDbTable } from '@hubspot/local-dev-lib/hubdb';
4
5
  import { selectHubDBTablePrompt } from '../../lib/prompts/selectHubDBTablePrompt.js';
5
6
  import { trackCommandUsage } from '../../lib/usageTracking.js';
@@ -22,6 +23,9 @@ async function handler(args) {
22
23
  uiLogger.success(commands.hubdb.subcommands.fetch.success.fetch(tableId, filePath));
23
24
  }
24
25
  catch (e) {
26
+ if (e instanceof PromptExitError) {
27
+ process.exit(e.exitCode);
28
+ }
25
29
  logError(e);
26
30
  }
27
31
  }
package/commands/init.js CHANGED
@@ -12,6 +12,7 @@ import { setCLILogLevel } from '../lib/commonOpts.js';
12
12
  import { makeYargsBuilder } from '../lib/yargsUtils.js';
13
13
  import { handleExit } from '../lib/process.js';
14
14
  import { debugError, logError } from '../lib/errorHandlers/index.js';
15
+ import { PromptExitError } from '../lib/errors/PromptExitError.js';
15
16
  import { trackCommandUsage, trackAuthAction } from '../lib/usageTracking.js';
16
17
  import { promptUser } from '../lib/prompts/promptUtils.js';
17
18
  import { OAUTH_FLOW, personalAccessKeyPrompt, } from '../lib/prompts/personalAccessKeyPrompt.js';
@@ -150,6 +151,9 @@ async function handler(args) {
150
151
  process.exit(EXIT_CODES.SUCCESS);
151
152
  }
152
153
  catch (err) {
154
+ if (err instanceof PromptExitError) {
155
+ process.exit(err.exitCode);
156
+ }
153
157
  logError(err);
154
158
  if (!disableTracking) {
155
159
  await trackAuthAction('init', authType, TRACKING_STATUS.ERROR, parsedUserProvidedAccountId);
@@ -12,6 +12,7 @@ import { loadProfile } from '../../../lib/projects/projectProfiles.js';
12
12
  import { commands } from '../../../lang/en.js';
13
13
  import { uiLogger } from '../../../lib/ui/logger.js';
14
14
  import { logError } from '../../../lib/errorHandlers/index.js';
15
+ import { PromptExitError } from '../../../lib/errors/PromptExitError.js';
15
16
  import path from 'path';
16
17
  import { listPrompt } from '../../../lib/prompts/promptUtils.js';
17
18
  const command = 'dev';
@@ -99,26 +100,35 @@ async function handler(args) {
99
100
  }
100
101
  }
101
102
  trackCommandUsage('project-dev', {}, targetProjectAccountId);
102
- if (isV2Project(projectConfig.platformVersion)) {
103
- const targetTestingAccountId = testingAccount
104
- ? getConfigAccountIfExists(testingAccount)?.accountId
105
- : undefined;
106
- await unifiedProjectDevFlow({
107
- args,
108
- targetProjectAccountId,
109
- providedTargetTestingAccountId: targetTestingAccountId,
110
- projectConfig,
111
- projectDir,
112
- profileConfig: profile,
113
- });
103
+ try {
104
+ if (isV2Project(projectConfig.platformVersion)) {
105
+ const targetTestingAccountId = testingAccount
106
+ ? getConfigAccountIfExists(testingAccount)?.accountId
107
+ : undefined;
108
+ await unifiedProjectDevFlow({
109
+ args,
110
+ targetProjectAccountId,
111
+ providedTargetTestingAccountId: targetTestingAccountId,
112
+ projectConfig,
113
+ projectDir,
114
+ profileConfig: profile,
115
+ });
116
+ }
117
+ else {
118
+ await deprecatedProjectDevFlow({
119
+ args,
120
+ accountId: targetProjectAccountId,
121
+ projectConfig,
122
+ projectDir,
123
+ });
124
+ }
114
125
  }
115
- else {
116
- await deprecatedProjectDevFlow({
117
- args,
118
- accountId: targetProjectAccountId,
119
- projectConfig,
120
- projectDir,
121
- });
126
+ catch (e) {
127
+ if (e instanceof PromptExitError) {
128
+ process.exit(e.exitCode);
129
+ }
130
+ logError(e);
131
+ process.exit(EXIT_CODES.ERROR);
122
132
  }
123
133
  }
124
134
  function projectDevBuilder(yargs) {
@@ -3,6 +3,7 @@ import { getCwd, sanitizeFileName } from '@hubspot/local-dev-lib/path';
3
3
  import { extractZipArchive } from '@hubspot/local-dev-lib/archive';
4
4
  import { downloadProject, fetchProjectBuilds, } from '@hubspot/local-dev-lib/api/projects';
5
5
  import { logError, ApiErrorContext } from '../../lib/errorHandlers/index.js';
6
+ import { PromptExitError } from '../../lib/errors/PromptExitError.js';
6
7
  import { getProjectConfig } from '../../lib/projects/config.js';
7
8
  import { downloadProjectPrompt } from '../../lib/prompts/downloadProjectPrompt.js';
8
9
  import { commands } from '../../lang/en.js';
@@ -19,10 +20,10 @@ async function handler(args) {
19
20
  process.exit(EXIT_CODES.ERROR);
20
21
  }
21
22
  const { dest, build, derivedAccountId } = args;
22
- const { project: projectName } = await downloadProjectPrompt(args);
23
23
  let buildNumberToDownload = build;
24
24
  trackCommandUsage('project-download', undefined, derivedAccountId);
25
25
  try {
26
+ const { project: projectName } = await downloadProjectPrompt(args);
26
27
  if (!buildNumberToDownload) {
27
28
  const { data: projectBuildsResult } = await fetchProjectBuilds(derivedAccountId, projectName);
28
29
  const { results: projectBuilds } = projectBuildsResult;
@@ -45,6 +46,9 @@ async function handler(args) {
45
46
  process.exit(EXIT_CODES.SUCCESS);
46
47
  }
47
48
  catch (e) {
49
+ if (e instanceof PromptExitError) {
50
+ process.exit(e.exitCode);
51
+ }
48
52
  logError(e, new ApiErrorContext({
49
53
  accountId: derivedAccountId,
50
54
  request: 'project download',
@@ -1,7 +1,6 @@
1
1
  import yargs from 'yargs';
2
2
  import { addAccountOptions, addConfigOptions, addUseEnvironmentOptions, addTestingOptions, } from '../../../lib/commonOpts.js';
3
3
  import sandboxCreateCommand from '../create.js';
4
- import { hasFeature } from '../../../lib/hasFeature.js';
5
4
  import * as sandboxPrompts from '../../../lib/prompts/sandboxesPrompt.js';
6
5
  import * as accountNamePrompt from '../../../lib/prompts/accountNamePrompt.js';
7
6
  import * as configUtils from '@hubspot/local-dev-lib/config';
@@ -12,7 +11,6 @@ import * as buildAccount from '../../../lib/buildAccount.js';
12
11
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
13
12
  import { uiLogger } from '../../../lib/ui/logger.js';
14
13
  import * as sandboxesLib from '../../../lib/sandboxes.js';
15
- import * as sandboxSync from '../../../lib/sandboxSync.js';
16
14
  import { vi } from 'vitest';
17
15
  import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
18
16
  vi.mock('@hubspot/local-dev-lib/config');
@@ -29,15 +27,10 @@ const getConfigAccountByIdSpy = vi.spyOn(configUtils, 'getConfigAccountById');
29
27
  const promptUserSpy = vi.spyOn(promptUtils, 'promptUser');
30
28
  const sandboxTypePromptSpy = vi.spyOn(sandboxPrompts, 'sandboxTypePrompt');
31
29
  const processExitSpy = vi.spyOn(process, 'exit');
32
- const buildSandboxSpy = vi.spyOn(buildAccount, 'buildSandbox');
33
30
  const buildV2SandboxSpy = vi.spyOn(buildAccount, 'buildV2Sandbox');
34
31
  const getConfigAccountEnvironmentSpy = vi.spyOn(configUtils, 'getConfigAccountEnvironment');
35
- const getAvailableSyncTypesSpy = vi.spyOn(sandboxesLib, 'getAvailableSyncTypes');
36
- const syncSandboxSpy = vi.spyOn(sandboxSync, 'syncSandbox');
37
32
  const validateSandboxUsageLimitsSpy = vi.spyOn(sandboxesLib, 'validateSandboxUsageLimits');
38
33
  const hubspotAccountNamePromptSpy = vi.spyOn(accountNamePrompt, 'hubspotAccountNamePrompt');
39
- const mockedHasFeatureV2Sandboxes = hasFeature;
40
- const mockedHasFeatureV2Cli = hasFeature;
41
34
  describe('commands/sandbox/create', () => {
42
35
  const yargsMock = yargs;
43
36
  describe('command', () => {
@@ -103,22 +96,10 @@ describe('commands/sandbox/create', () => {
103
96
  contactRecordsSyncPrompt: false,
104
97
  });
105
98
  validateSandboxUsageLimitsSpy.mockResolvedValue(undefined);
106
- mockedHasFeatureV2Sandboxes.mockResolvedValue(false);
107
- mockedHasFeatureV2Cli.mockResolvedValue(false);
108
99
  getConfigAccountEnvironmentSpy.mockReturnValue(ENVIRONMENTS.PROD);
109
- buildSandboxSpy.mockResolvedValue({
110
- sandbox: mockSandbox,
111
- personalAccessKey: 'mock-personal-access-key',
112
- name: sandboxNameFromPrompt,
113
- });
114
100
  buildV2SandboxSpy.mockResolvedValue({
115
101
  sandbox: { ...mockSandbox, version: 'V2' },
116
102
  });
117
- getAvailableSyncTypesSpy.mockResolvedValue([
118
- { type: 'object-schemas' },
119
- { type: 'workflows' },
120
- ]);
121
- syncSandboxSpy.mockResolvedValue(undefined);
122
103
  // Spy on process.exit so our tests don't close when it's called
123
104
  // @ts-expect-error Doesn't match the actual signature because then the linter complains about unused variables
124
105
  processExitSpy.mockImplementation(() => { });
@@ -167,35 +148,7 @@ describe('commands/sandbox/create', () => {
167
148
  });
168
149
  expect(promptUserSpy).toHaveBeenCalledTimes(1);
169
150
  });
170
- it('should build a v1 sandbox if the parent account is not ungated for sandboxes:v2:enabled and not ungated for sandboxes:v2:cliEnabled', async () => {
171
- await sandboxCreateCommand.handler(args);
172
- expect(buildSandboxSpy).toHaveBeenCalledTimes(1);
173
- expect(buildSandboxSpy).toHaveBeenCalledWith(sandboxNameFromPrompt, {
174
- accountId: 1234567890,
175
- accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD,
176
- env: 'prod',
177
- }, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, 'prod', undefined // force
178
- );
179
- expect(getAvailableSyncTypesSpy).toHaveBeenCalledTimes(1);
180
- expect(syncSandboxSpy).toHaveBeenCalledTimes(1);
181
- });
182
- it('should build a v1 sandbox if the parent account is ungated for sandboxes:v2:enabled but not ungated for sandboxes:v2:cliEnabled', async () => {
183
- mockedHasFeatureV2Sandboxes.mockResolvedValue(true);
184
- mockedHasFeatureV2Cli.mockResolvedValue(false);
185
- await sandboxCreateCommand.handler(args);
186
- expect(buildSandboxSpy).toHaveBeenCalledTimes(1);
187
- expect(buildSandboxSpy).toHaveBeenCalledWith(sandboxNameFromPrompt, {
188
- accountId: 1234567890,
189
- accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD,
190
- env: 'prod',
191
- }, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, 'prod', undefined // force
192
- );
193
- expect(getAvailableSyncTypesSpy).toHaveBeenCalledTimes(1);
194
- expect(syncSandboxSpy).toHaveBeenCalledTimes(1);
195
- });
196
- it('should build a v2 sandbox if the parent account is ungated for both sandboxes:v2:enabled and sandboxes:v2:cliEnabled', async () => {
197
- mockedHasFeatureV2Sandboxes.mockResolvedValue(true);
198
- mockedHasFeatureV2Cli.mockResolvedValue(true);
151
+ it('should build a v2 sandbox', async () => {
199
152
  await sandboxCreateCommand.handler(args);
200
153
  expect(buildV2SandboxSpy).toHaveBeenCalledTimes(1);
201
154
  expect(buildV2SandboxSpy).toHaveBeenCalledWith(sandboxNameFromPrompt, {
@@ -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
@@ -3474,6 +3474,7 @@ export declare const lib: {
3474
3474
  invalidOauthClientSecretCopy: string;
3475
3475
  invalidPersonalAccessKey: string;
3476
3476
  invalidPersonalAccessKeyCopy: string;
3477
+ authCancelled: string;
3477
3478
  };
3478
3479
  };
3479
3480
  createTemplatePrompt: {
@@ -3977,4 +3978,14 @@ export declare const lib: {
3977
3978
  copyingProjectFilesFailed: string;
3978
3979
  };
3979
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
+ };
3980
3991
  };
package/lang/en.js CHANGED
@@ -3497,6 +3497,7 @@ export const lib = {
3497
3497
  invalidOauthClientSecretCopy: 'Please copy the actual OAuth2 client secret rather than the asterisks that mask it.',
3498
3498
  invalidPersonalAccessKey: 'You did not enter a valid access key. Please try again.',
3499
3499
  invalidPersonalAccessKeyCopy: 'Please copy the actual access key rather than the bullets that mask it.',
3500
+ authCancelled: 'Authentication cancelled.',
3500
3501
  },
3501
3502
  },
3502
3503
  createTemplatePrompt: {
@@ -4000,4 +4001,14 @@ export const lib = {
4000
4001
  copyingProjectFilesFailed: 'Unable to copy migrated project files',
4001
4002
  },
4002
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
+ },
4003
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',