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

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 (49) hide show
  1. package/api/migrate.js +5 -1
  2. package/commands/account/auth.js +5 -15
  3. package/commands/account/use.js +4 -14
  4. package/commands/app/migrate.js +2 -2
  5. package/commands/auth.js +6 -10
  6. package/commands/cms/__tests__/upload.test.js +4 -0
  7. package/commands/hubdb/clear.js +0 -4
  8. package/commands/hubdb/delete.js +0 -4
  9. package/commands/hubdb/fetch.js +0 -4
  10. package/commands/init.js +0 -4
  11. package/commands/project/__tests__/migrate.test.js +2 -2
  12. package/commands/project/dev/index.js +19 -29
  13. package/commands/project/download.js +1 -5
  14. package/commands/project/migrate.js +6 -6
  15. package/commands/sandbox/__tests__/create.test.js +48 -1
  16. package/commands/sandbox/create.js +30 -3
  17. package/commands/testAccount/create.js +0 -4
  18. package/lang/en.d.ts +2 -3
  19. package/lang/en.js +2 -3
  20. package/lib/__tests__/buildAccount.test.js +52 -1
  21. package/lib/__tests__/sandboxSync.test.d.ts +1 -0
  22. package/lib/__tests__/sandboxSync.test.js +147 -0
  23. package/lib/__tests__/sandboxes.test.js +29 -1
  24. package/lib/accountAuth.js +0 -4
  25. package/lib/buildAccount.d.ts +6 -1
  26. package/lib/buildAccount.js +42 -9
  27. package/lib/constants.d.ts +2 -0
  28. package/lib/constants.js +2 -0
  29. package/lib/projects/__tests__/components.test.js +0 -14
  30. package/lib/projects/components.js +2 -12
  31. package/lib/projects/localDev/AppDevModeInterface.js +0 -4
  32. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +0 -4
  33. package/lib/projects/localDev/helpers/account.js +11 -5
  34. package/lib/prompts/downloadProjectPrompt.js +10 -11
  35. package/lib/prompts/installAppPrompt.js +2 -3
  36. package/lib/prompts/personalAccessKeyPrompt.js +2 -3
  37. package/lib/prompts/projectDevTargetAccountPrompt.js +16 -13
  38. package/lib/prompts/selectHubDBTablePrompt.js +4 -8
  39. package/lib/prompts/selectPublicAppForMigrationPrompt.js +6 -12
  40. package/lib/sandboxSync.d.ts +4 -0
  41. package/lib/sandboxSync.js +102 -0
  42. package/lib/sandboxes.d.ts +9 -1
  43. package/lib/sandboxes.js +21 -0
  44. package/lib/theme/__tests__/migrate.test.js +6 -11
  45. package/lib/theme/migrate.d.ts +1 -1
  46. package/lib/theme/migrate.js +1 -5
  47. package/package.json +3 -3
  48. package/lib/errors/PromptExitError.d.ts +0 -4
  49. package/lib/errors/PromptExitError.js +0 -8
package/api/migrate.js CHANGED
@@ -23,7 +23,11 @@ function mapPlatformVersionToEnum(platformVersion) {
23
23
  if (platformVersion === PLATFORM_VERSIONS.unstable) {
24
24
  return PLATFORM_VERSIONS.unstable.toUpperCase();
25
25
  }
26
- return `V${platformVersion.replace('.', '_')}`;
26
+ const reformattedPlatformVersion = platformVersion
27
+ .replace('.', '_')
28
+ .replace('-', '_')
29
+ .toUpperCase();
30
+ return `V${reformattedPlatformVersion}`;
27
31
  }
28
32
  export async function initializeAppMigration(accountId, applicationId, platformVersion) {
29
33
  return http.post(accountId, {
@@ -10,7 +10,6 @@ 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';
14
13
  const TRACKING_STATUS = {
15
14
  STARTED: 'started',
16
15
  ERROR: 'error',
@@ -36,20 +35,11 @@ async function handler(args) {
36
35
  await trackAuthAction('account-auth', authType, TRACKING_STATUS.STARTED);
37
36
  }
38
37
  handleExit(deleteConfigFileIfEmpty);
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
- }
38
+ const updatedConfig = await authenticateNewAccount({
39
+ env: args.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD,
40
+ providedPersonalAccessKey,
41
+ accountId: parsedUserProvidedAccountId,
42
+ });
53
43
  if (!updatedConfig) {
54
44
  if (!disableTracking) {
55
45
  await trackAuthAction('account-auth', authType, TRACKING_STATUS.ERROR);
@@ -7,7 +7,6 @@ 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';
11
10
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
12
11
  const command = 'use [account]';
13
12
  const describe = commands.account.subcommands.use.describe;
@@ -25,19 +24,10 @@ async function handler(args) {
25
24
  }
26
25
  }
27
26
  if (newDefaultAccount === AUTHENTICATE_NEW_ACCOUNT_VALUE) {
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
- }
27
+ const updatedConfig = await authenticateNewAccount({
28
+ env: args.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD,
29
+ setAsDefaultAccount: true,
30
+ });
41
31
  if (!updatedConfig) {
42
32
  process.exit(EXIT_CODES.ERROR);
43
33
  }
@@ -8,7 +8,7 @@ import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
8
8
  import { migrateApp } from '../../lib/app/migrate.js';
9
9
  import { getIsInProject } from '../../lib/projects/config.js';
10
10
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
11
- const { v2025_2 } = PLATFORM_VERSIONS;
11
+ const { v2025_2, v2026_03_beta } = PLATFORM_VERSIONS;
12
12
  const command = 'migrate';
13
13
  const describe = commands.project.migrateApp.describe;
14
14
  export function handlerGenerator(commandTrackingName) {
@@ -67,7 +67,7 @@ function appMigrateBuilder(yargs) {
67
67
  },
68
68
  'platform-version': {
69
69
  type: 'string',
70
- choices: [v2025_2],
70
+ choices: [v2025_2, v2026_03_beta],
71
71
  default: v2025_2,
72
72
  },
73
73
  unstable: {
package/commands/auth.js CHANGED
@@ -16,7 +16,6 @@ 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';
20
19
  import { commands } from '../lang/en.js';
21
20
  import { uiLogger } from '../lib/ui/logger.js';
22
21
  import { parseStringToNumber } from '../lib/parsing.js';
@@ -91,21 +90,18 @@ async function handler(args) {
91
90
  }
92
91
  break;
93
92
  case PERSONAL_ACCESS_KEY_AUTH_METHOD.value:
93
+ const { personalAccessKey } = providedPersonalAccessKey
94
+ ? { personalAccessKey: providedPersonalAccessKey }
95
+ : await personalAccessKeyPrompt({
96
+ env,
97
+ account: parsedUserProvidedAccountId,
98
+ });
94
99
  try {
95
- const { personalAccessKey } = providedPersonalAccessKey
96
- ? { personalAccessKey: providedPersonalAccessKey }
97
- : await personalAccessKeyPrompt({
98
- env,
99
- account: parsedUserProvidedAccountId,
100
- });
101
100
  token = await getAccessToken(personalAccessKey, env);
102
101
  defaultName = token.hubName ? toKebabCase(token.hubName) : undefined;
103
102
  updatedConfig = await updateConfigWithAccessToken(token, personalAccessKey, env);
104
103
  }
105
104
  catch (e) {
106
- if (e instanceof PromptExitError) {
107
- process.exit(e.exitCode);
108
- }
109
105
  logError(e);
110
106
  }
111
107
  if (!updatedConfig) {
@@ -8,6 +8,7 @@ import * as modulesLib from '@hubspot/local-dev-lib/cms/modules';
8
8
  import * as ignoreRulesLib from '@hubspot/local-dev-lib/ignoreRules';
9
9
  import * as themesLib from '@hubspot/local-dev-lib/cms/themes';
10
10
  import * as configLib from '@hubspot/local-dev-lib/config';
11
+ import * as handleFieldsJSLib from '@hubspot/local-dev-lib/cms/handleFieldsJS';
11
12
  import { uiLogger } from '../../../lib/ui/logger.js';
12
13
  import * as errorHandlers from '../../../lib/errorHandlers/index.js';
13
14
  import * as commonOpts from '../../../lib/commonOpts.js';
@@ -26,6 +27,7 @@ vi.mock('@hubspot/local-dev-lib/cms/modules');
26
27
  vi.mock('@hubspot/local-dev-lib/ignoreRules');
27
28
  vi.mock('@hubspot/local-dev-lib/cms/themes');
28
29
  vi.mock('@hubspot/local-dev-lib/config');
30
+ vi.mock('@hubspot/local-dev-lib/cms/handleFieldsJS');
29
31
  vi.mock('../../../lib/errorHandlers/index.js');
30
32
  vi.mock('../../../lib/commonOpts.js');
31
33
  vi.mock('../../../lib/prompts/uploadPrompt.js');
@@ -53,6 +55,7 @@ const hasUploadErrorsSpy = vi.spyOn(uploadFolderLib, 'hasUploadErrors');
53
55
  const processExitSpy = vi.spyOn(process, 'exit');
54
56
  const logErrorSpy = vi.spyOn(errorHandlers, 'logError');
55
57
  const getConfigAccountIfExistsSpy = vi.spyOn(configLib, 'getConfigAccountIfExists');
58
+ const isConvertableFieldJsSpy = vi.spyOn(handleFieldsJSLib, 'isConvertableFieldJs');
56
59
  describe('commands/cms/upload', () => {
57
60
  beforeEach(() => {
58
61
  // @ts-expect-error Mock implementation
@@ -67,6 +70,7 @@ describe('commands/cms/upload', () => {
67
70
  getThemePreviewUrlSpy.mockReturnValue(undefined);
68
71
  // Mock config to prevent reading actual config file in CI
69
72
  getConfigAccountIfExistsSpy.mockReturnValue(undefined);
73
+ isConvertableFieldJsSpy.mockReturnValue(false);
70
74
  });
71
75
  describe('command', () => {
72
76
  it('should have the correct command structure', () => {
@@ -1,6 +1,5 @@
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';
4
3
  import { clearHubDbTableRows } from '@hubspot/local-dev-lib/hubdb';
5
4
  import { publishTable } from '@hubspot/local-dev-lib/api/hubdb';
6
5
  import { selectHubDBTablePrompt } from '../../lib/prompts/selectHubDBTablePrompt.js';
@@ -30,9 +29,6 @@ async function handler(args) {
30
29
  }
31
30
  }
32
31
  catch (e) {
33
- if (e instanceof PromptExitError) {
34
- process.exit(e.exitCode);
35
- }
36
32
  logError(e);
37
33
  }
38
34
  }
@@ -1,6 +1,5 @@
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';
4
3
  import { deleteTable } from '@hubspot/local-dev-lib/api/hubdb';
5
4
  import { trackCommandUsage } from '../../lib/usageTracking.js';
6
5
  import { selectHubDBTablePrompt } from '../../lib/prompts/selectHubDBTablePrompt.js';
@@ -35,9 +34,6 @@ async function handler(args) {
35
34
  process.exit(EXIT_CODES.SUCCESS);
36
35
  }
37
36
  catch (e) {
38
- if (e instanceof PromptExitError) {
39
- process.exit(e.exitCode);
40
- }
41
37
  uiLogger.error(commands.hubdb.subcommands.delete.errors.delete(args.tableId || ''));
42
38
  logError(e);
43
39
  }
@@ -1,6 +1,5 @@
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';
4
3
  import { downloadHubDbTable } from '@hubspot/local-dev-lib/hubdb';
5
4
  import { selectHubDBTablePrompt } from '../../lib/prompts/selectHubDBTablePrompt.js';
6
5
  import { trackCommandUsage } from '../../lib/usageTracking.js';
@@ -23,9 +22,6 @@ async function handler(args) {
23
22
  uiLogger.success(commands.hubdb.subcommands.fetch.success.fetch(tableId, filePath));
24
23
  }
25
24
  catch (e) {
26
- if (e instanceof PromptExitError) {
27
- process.exit(e.exitCode);
28
- }
29
25
  logError(e);
30
26
  }
31
27
  }
package/commands/init.js CHANGED
@@ -12,7 +12,6 @@ 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';
16
15
  import { trackCommandUsage, trackAuthAction } from '../lib/usageTracking.js';
17
16
  import { promptUser } from '../lib/prompts/promptUtils.js';
18
17
  import { OAUTH_FLOW, personalAccessKeyPrompt, } from '../lib/prompts/personalAccessKeyPrompt.js';
@@ -151,9 +150,6 @@ async function handler(args) {
151
150
  process.exit(EXIT_CODES.SUCCESS);
152
151
  }
153
152
  catch (err) {
154
- if (err instanceof PromptExitError) {
155
- process.exit(err.exitCode);
156
- }
157
153
  logError(err);
158
154
  if (!disableTracking) {
159
155
  await trackAuthAction('init', authType, TRACKING_STATUS.ERROR, parsedUserProvidedAccountId);
@@ -9,7 +9,7 @@ import { uiBetaTag, uiCommandReference } from '../../../lib/ui/index.js';
9
9
  vi.mock('../../../lib/app/migrate');
10
10
  vi.mock('../../../lib/projects/config');
11
11
  vi.mock('../../../lib/ui');
12
- const { v2025_2 } = PLATFORM_VERSIONS;
12
+ const { v2025_2, v2026_03_beta, v2026_03 } = PLATFORM_VERSIONS;
13
13
  describe('commands/project/migrate', () => {
14
14
  const yargsMock = yargs;
15
15
  const optionsSpy = vi.spyOn(yargsMock, 'option').mockReturnValue(yargsMock);
@@ -48,7 +48,7 @@ describe('commands/project/migrate', () => {
48
48
  migrateCommand.builder(yargsMock);
49
49
  expect(optionsSpy).toHaveBeenCalledWith('platform-version', {
50
50
  type: 'string',
51
- choices: [v2025_2],
51
+ choices: [v2025_2, v2026_03_beta, v2026_03],
52
52
  default: v2025_2,
53
53
  });
54
54
  expect(optionsSpy).toHaveBeenCalledWith('unstable', {
@@ -12,7 +12,6 @@ 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';
16
15
  import path from 'path';
17
16
  import { listPrompt } from '../../../lib/prompts/promptUtils.js';
18
17
  const command = 'dev';
@@ -100,35 +99,26 @@ async function handler(args) {
100
99
  }
101
100
  }
102
101
  trackCommandUsage('project-dev', {}, targetProjectAccountId);
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
- }
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
+ });
125
114
  }
126
- catch (e) {
127
- if (e instanceof PromptExitError) {
128
- process.exit(e.exitCode);
129
- }
130
- logError(e);
131
- process.exit(EXIT_CODES.ERROR);
115
+ else {
116
+ await deprecatedProjectDevFlow({
117
+ args,
118
+ accountId: targetProjectAccountId,
119
+ projectConfig,
120
+ projectDir,
121
+ });
132
122
  }
133
123
  }
134
124
  function projectDevBuilder(yargs) {
@@ -3,7 +3,6 @@ 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';
7
6
  import { getProjectConfig } from '../../lib/projects/config.js';
8
7
  import { downloadProjectPrompt } from '../../lib/prompts/downloadProjectPrompt.js';
9
8
  import { commands } from '../../lang/en.js';
@@ -20,10 +19,10 @@ async function handler(args) {
20
19
  process.exit(EXIT_CODES.ERROR);
21
20
  }
22
21
  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);
27
26
  if (!buildNumberToDownload) {
28
27
  const { data: projectBuildsResult } = await fetchProjectBuilds(derivedAccountId, projectName);
29
28
  const { results: projectBuilds } = projectBuildsResult;
@@ -46,9 +45,6 @@ async function handler(args) {
46
45
  process.exit(EXIT_CODES.SUCCESS);
47
46
  }
48
47
  catch (e) {
49
- if (e instanceof PromptExitError) {
50
- process.exit(e.exitCode);
51
- }
52
48
  logError(e, new ApiErrorContext({
53
49
  accountId: derivedAccountId,
54
50
  request: 'project download',
@@ -9,11 +9,11 @@ import { commands, lib } from '../../lang/en.js';
9
9
  import { uiLogger } from '../../lib/ui/logger.js';
10
10
  import { renderInline } from '../../ui/render.js';
11
11
  import { getWarningBox } from '../../ui/components/StatusMessageBoxes.js';
12
- import { getHasMigratableThemes, migrateThemes2025_2, } from '../../lib/theme/migrate.js';
12
+ import { getHasMigratableThemes, migrateThemesV2, } from '../../lib/theme/migrate.js';
13
13
  import { hasFeature } from '../../lib/hasFeature.js';
14
14
  import { FEATURES } from '../../lib/constants.js';
15
15
  import { trackCommandMetadataUsage, trackCommandUsage, } from '../../lib/usageTracking.js';
16
- const { v2025_2 } = PLATFORM_VERSIONS;
16
+ const { v2025_2, v2026_03_beta, v2026_03 } = PLATFORM_VERSIONS;
17
17
  const command = 'migrate';
18
18
  const describe = commands.project.migrate.describe;
19
19
  async function handler(args) {
@@ -26,8 +26,8 @@ async function handler(args) {
26
26
  }
27
27
  if (projectConfig?.projectConfig) {
28
28
  await renderInline(getWarningBox({
29
- title: lib.migrate.projectMigrationWarningTitle,
30
- message: lib.migrate.projectMigrationWarning,
29
+ title: lib.migrate.projectMigrationWarningTitle(platformVersion),
30
+ message: lib.migrate.projectMigrationWarning(platformVersion),
31
31
  }));
32
32
  }
33
33
  try {
@@ -38,7 +38,7 @@ async function handler(args) {
38
38
  uiLogger.error(commands.project.migrate.errors.noThemeMigrationAccess(derivedAccountId));
39
39
  return process.exit(EXIT_CODES.ERROR);
40
40
  }
41
- await migrateThemes2025_2(derivedAccountId, {
41
+ await migrateThemesV2(derivedAccountId, {
42
42
  ...args,
43
43
  platformVersion: unstable
44
44
  ? PLATFORM_VERSIONS.unstable
@@ -67,7 +67,7 @@ function projectMigrateBuilder(yargs) {
67
67
  yargs
68
68
  .option('platform-version', {
69
69
  type: 'string',
70
- choices: [v2025_2],
70
+ choices: [v2025_2, v2026_03_beta, v2026_03],
71
71
  default: v2025_2,
72
72
  })
73
73
  .option('unstable', {
@@ -1,6 +1,7 @@
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';
4
5
  import * as sandboxPrompts from '../../../lib/prompts/sandboxesPrompt.js';
5
6
  import * as accountNamePrompt from '../../../lib/prompts/accountNamePrompt.js';
6
7
  import * as configUtils from '@hubspot/local-dev-lib/config';
@@ -11,6 +12,7 @@ import * as buildAccount from '../../../lib/buildAccount.js';
11
12
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
12
13
  import { uiLogger } from '../../../lib/ui/logger.js';
13
14
  import * as sandboxesLib from '../../../lib/sandboxes.js';
15
+ import * as sandboxSync from '../../../lib/sandboxSync.js';
14
16
  import { vi } from 'vitest';
15
17
  import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
16
18
  vi.mock('@hubspot/local-dev-lib/config');
@@ -27,10 +29,15 @@ const getConfigAccountByIdSpy = vi.spyOn(configUtils, 'getConfigAccountById');
27
29
  const promptUserSpy = vi.spyOn(promptUtils, 'promptUser');
28
30
  const sandboxTypePromptSpy = vi.spyOn(sandboxPrompts, 'sandboxTypePrompt');
29
31
  const processExitSpy = vi.spyOn(process, 'exit');
32
+ const buildSandboxSpy = vi.spyOn(buildAccount, 'buildSandbox');
30
33
  const buildV2SandboxSpy = vi.spyOn(buildAccount, 'buildV2Sandbox');
31
34
  const getConfigAccountEnvironmentSpy = vi.spyOn(configUtils, 'getConfigAccountEnvironment');
35
+ const getAvailableSyncTypesSpy = vi.spyOn(sandboxesLib, 'getAvailableSyncTypes');
36
+ const syncSandboxSpy = vi.spyOn(sandboxSync, 'syncSandbox');
32
37
  const validateSandboxUsageLimitsSpy = vi.spyOn(sandboxesLib, 'validateSandboxUsageLimits');
33
38
  const hubspotAccountNamePromptSpy = vi.spyOn(accountNamePrompt, 'hubspotAccountNamePrompt');
39
+ const mockedHasFeatureV2Sandboxes = hasFeature;
40
+ const mockedHasFeatureV2Cli = hasFeature;
34
41
  describe('commands/sandbox/create', () => {
35
42
  const yargsMock = yargs;
36
43
  describe('command', () => {
@@ -96,10 +103,22 @@ describe('commands/sandbox/create', () => {
96
103
  contactRecordsSyncPrompt: false,
97
104
  });
98
105
  validateSandboxUsageLimitsSpy.mockResolvedValue(undefined);
106
+ mockedHasFeatureV2Sandboxes.mockResolvedValue(false);
107
+ mockedHasFeatureV2Cli.mockResolvedValue(false);
99
108
  getConfigAccountEnvironmentSpy.mockReturnValue(ENVIRONMENTS.PROD);
109
+ buildSandboxSpy.mockResolvedValue({
110
+ sandbox: mockSandbox,
111
+ personalAccessKey: 'mock-personal-access-key',
112
+ name: sandboxNameFromPrompt,
113
+ });
100
114
  buildV2SandboxSpy.mockResolvedValue({
101
115
  sandbox: { ...mockSandbox, version: 'V2' },
102
116
  });
117
+ getAvailableSyncTypesSpy.mockResolvedValue([
118
+ { type: 'object-schemas' },
119
+ { type: 'workflows' },
120
+ ]);
121
+ syncSandboxSpy.mockResolvedValue(undefined);
103
122
  // Spy on process.exit so our tests don't close when it's called
104
123
  // @ts-expect-error Doesn't match the actual signature because then the linter complains about unused variables
105
124
  processExitSpy.mockImplementation(() => { });
@@ -148,7 +167,35 @@ describe('commands/sandbox/create', () => {
148
167
  });
149
168
  expect(promptUserSpy).toHaveBeenCalledTimes(1);
150
169
  });
151
- it('should build a v2 sandbox', async () => {
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);
152
199
  await sandboxCreateCommand.handler(args);
153
200
  expect(buildV2SandboxSpy).toHaveBeenCalledTimes(1);
154
201
  expect(buildV2SandboxSpy).toHaveBeenCalledWith(sandboxNameFromPrompt, {
@@ -6,14 +6,17 @@ 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, validateSandboxUsageLimits, } from '../../lib/sandboxes.js';
9
+ import { SANDBOX_TYPE_MAP, getAvailableSyncTypes, SYNC_TYPES, 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';
13
14
  import { logError } from '../../lib/errorHandlers/index.js';
14
- import { buildV2Sandbox } from '../../lib/buildAccount.js';
15
+ import { buildSandbox, buildV2Sandbox } from '../../lib/buildAccount.js';
15
16
  import { hubspotAccountNamePrompt } from '../../lib/prompts/accountNamePrompt.js';
16
17
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
18
+ import { hasFeature } from '../../lib/hasFeature.js';
19
+ import { FEATURES } from '../../lib/constants.js';
17
20
  const command = 'create';
18
21
  const describe = uiBetaTag(commands.sandbox.subcommands.create.describe, false);
19
22
  async function handler(args) {
@@ -95,14 +98,38 @@ async function handler(args) {
95
98
  contactRecordsSyncPromptResult = contactRecordsSyncPrompt;
96
99
  }
97
100
  }
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;
98
105
  try {
99
- const result = await buildV2Sandbox(sandboxName, accountConfig, sandboxType, contactRecordsSyncPromptResult, env, force);
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
+ }
100
113
  const sandboxAccountConfig = getConfigAccountById(result.sandbox.sandboxHubId);
101
114
  // Check if sandbox account config exists
102
115
  if (!sandboxAccountConfig) {
103
116
  uiLogger.error(commands.sandbox.subcommands.create.failure.noSandboxAccountConfig(result.sandbox.sandboxHubId));
104
117
  process.exit(EXIT_CODES.ERROR);
105
118
  }
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
+ }
106
133
  const highlightItems = ['accountsUseCommand', 'projectCreateCommand'];
107
134
  if (sandboxType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX) {
108
135
  highlightItems.push('projectDevCommand');
@@ -12,7 +12,6 @@ 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';
16
15
  import SpinniesManager from '../../lib/ui/SpinniesManager.js';
17
16
  import { createDeveloperTestAccountV2, saveAccountToConfig, } from '../../lib/buildAccount.js';
18
17
  import { ACCOUNT_LEVEL_CHOICES, ACCOUNT_LEVELS } from '../../lib/constants.js';
@@ -131,9 +130,6 @@ async function handler(args) {
131
130
  }
132
131
  }
133
132
  catch (e) {
134
- if (e instanceof PromptExitError) {
135
- process.exit(e.exitCode);
136
- }
137
133
  debugError(e);
138
134
  uiLogger.error(commands.testAccount.create.errors.saveAccountToConfigFailure(testAccountConfig.accountName));
139
135
  process.exit(EXIT_CODES.ERROR);
package/lang/en.d.ts CHANGED
@@ -3474,7 +3474,6 @@ export declare const lib: {
3474
3474
  invalidOauthClientSecretCopy: string;
3475
3475
  invalidPersonalAccessKey: string;
3476
3476
  invalidPersonalAccessKeyCopy: string;
3477
- authCancelled: string;
3478
3477
  };
3479
3478
  };
3480
3479
  createTemplatePrompt: {
@@ -3920,8 +3919,8 @@ export declare const lib: {
3920
3919
  componentsToBeMigrated: (components: string) => string;
3921
3920
  componentsThatWillNotBeMigrated: (components: string) => string;
3922
3921
  sourceContentsMoved: (newLocation: string) => string;
3923
- projectMigrationWarningTitle: string;
3924
- projectMigrationWarning: string;
3922
+ projectMigrationWarningTitle: (platformVersion: string) => string;
3923
+ projectMigrationWarning: (platformVersion: string) => string;
3925
3924
  exitWithoutMigrating: string;
3926
3925
  success: {
3927
3926
  downloadedProject: (projectName: string, projectDest: string) => string;
package/lang/en.js CHANGED
@@ -3497,7 +3497,6 @@ 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.',
3501
3500
  },
3502
3501
  },
3503
3502
  createTemplatePrompt: {
@@ -3943,8 +3942,8 @@ export const lib = {
3943
3942
  componentsToBeMigrated: (components) => `The following features will be migrated: ${components}`,
3944
3943
  componentsThatWillNotBeMigrated: (components) => `[NOTE] These features are not yet supported for migration but will be available later: ${components}`,
3945
3944
  sourceContentsMoved: (newLocation) => `The contents of your old source directory have been moved to ${newLocation}, move any required files to the new source directory.`,
3946
- projectMigrationWarningTitle: 'Important: Migrating to platformVersion 2025.2 is irreversible',
3947
- projectMigrationWarning: uiBetaTag(`Running the ${uiCommandReference('hs project migrate')} command will permanently upgrade your project to platformVersion 2025.2. This action cannot be undone. To ensure you have access to your original files, they will be copied to a new directory (archive) for safekeeping.\n\nThis command will guide you through the process, prompting you to enter the required fields and will download the new project source code into your project source directory.`, false),
3945
+ projectMigrationWarningTitle: (platformVersion) => `Important: Migrating to platformVersion ${platformVersion} is irreversible`,
3946
+ projectMigrationWarning: (platformVersion) => uiBetaTag(`Running the ${uiCommandReference('hs project migrate')} command will permanently upgrade your project to platformVersion ${platformVersion}. This action cannot be undone. To ensure you have access to your original files, they will be copied to a new directory (archive) for safekeeping.\n\nThis command will guide you through the process, prompting you to enter the required fields and will download the new project source code into your project source directory.`, false),
3948
3947
  exitWithoutMigrating: 'Exiting without migrating',
3949
3948
  success: {
3950
3949
  downloadedProject: (projectName, projectDest) => `Saved ${projectName} to ${projectDest}`,