@hubspot/cli 7.7.27-experimental.2 → 7.7.28-experimental.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +0 -4
  2. package/api/__tests__/migrate.test.js +5 -5
  3. package/api/migrate.d.ts +10 -4
  4. package/api/migrate.js +2 -2
  5. package/commands/__tests__/create.test.js +20 -0
  6. package/commands/__tests__/testAccount.test.js +2 -0
  7. package/commands/app/__tests__/migrate.test.js +1 -0
  8. package/commands/create/function.js +2 -2
  9. package/commands/create/module.js +2 -2
  10. package/commands/create/template.js +2 -2
  11. package/commands/create.js +47 -0
  12. package/commands/getStarted.js +66 -4
  13. package/commands/mcp/setup.d.ts +0 -1
  14. package/commands/mcp/setup.js +3 -11
  15. package/commands/project/__tests__/create.test.js +57 -0
  16. package/commands/project/__tests__/devUnifiedFlow.test.js +18 -30
  17. package/commands/project/create.js +6 -1
  18. package/commands/project/deploy.js +31 -1
  19. package/commands/project/dev/deprecatedFlow.js +2 -1
  20. package/commands/project/dev/index.js +32 -12
  21. package/commands/project/dev/unifiedFlow.d.ts +1 -1
  22. package/commands/project/dev/unifiedFlow.js +10 -16
  23. package/commands/project/profile/delete.js +26 -14
  24. package/commands/project/upload.d.ts +2 -2
  25. package/commands/project/upload.js +1 -1
  26. package/commands/testAccount/__tests__/importData.test.d.ts +1 -0
  27. package/commands/testAccount/__tests__/importData.test.js +93 -0
  28. package/commands/testAccount/create.js +23 -13
  29. package/commands/testAccount/importData.d.ts +9 -0
  30. package/commands/testAccount/importData.js +61 -0
  31. package/commands/testAccount.js +2 -0
  32. package/lang/en.d.ts +160 -46
  33. package/lang/en.js +175 -59
  34. package/lang/en.lyaml +35 -14
  35. package/lib/__tests__/importData.test.d.ts +1 -0
  36. package/lib/__tests__/importData.test.js +89 -0
  37. package/lib/accountTypes.js +2 -3
  38. package/lib/app/__tests__/migrate.test.js +81 -36
  39. package/lib/app/migrate.d.ts +17 -4
  40. package/lib/app/migrate.js +97 -19
  41. package/lib/constants.d.ts +1 -0
  42. package/lib/constants.js +1 -0
  43. package/lib/hasFeature.d.ts +1 -0
  44. package/lib/hasFeature.js +7 -0
  45. package/lib/importData.d.ts +3 -0
  46. package/lib/importData.js +50 -0
  47. package/lib/mcp/setup.d.ts +0 -2
  48. package/lib/mcp/setup.js +0 -24
  49. package/lib/process.js +15 -4
  50. package/lib/projectProfiles.d.ts +1 -1
  51. package/lib/projectProfiles.js +10 -2
  52. package/lib/projects/__tests__/AppDevModeInterface.test.js +3 -3
  53. package/lib/projects/__tests__/LocalDevProcess.test.js +5 -95
  54. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +6 -6
  55. package/lib/projects/__tests__/components.test.js +164 -7
  56. package/lib/projects/__tests__/localDevProjectHelpers.test.d.ts +1 -0
  57. package/lib/projects/__tests__/localDevProjectHelpers.test.js +118 -0
  58. package/lib/projects/add/v3AddComponent.js +16 -4
  59. package/lib/projects/components.d.ts +1 -0
  60. package/lib/projects/components.js +27 -1
  61. package/lib/projects/localDev/AppDevModeInterface.js +35 -3
  62. package/lib/projects/localDev/LocalDevLogger.d.ts +0 -4
  63. package/lib/projects/localDev/LocalDevLogger.js +2 -19
  64. package/lib/projects/localDev/LocalDevManager.js +1 -1
  65. package/lib/projects/localDev/LocalDevProcess.d.ts +1 -2
  66. package/lib/projects/localDev/LocalDevProcess.js +3 -26
  67. package/lib/projects/localDev/LocalDevState.d.ts +6 -7
  68. package/lib/projects/localDev/LocalDevState.js +16 -15
  69. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +1 -0
  70. package/lib/projects/localDev/LocalDevWebsocketServer.js +17 -2
  71. package/lib/projects/localDev/{helpers.d.ts → helpers/account.d.ts} +1 -7
  72. package/lib/projects/localDev/{helpers.js → helpers/account.js} +44 -144
  73. package/lib/projects/localDev/helpers/project.d.ts +12 -0
  74. package/lib/projects/localDev/helpers/project.js +173 -0
  75. package/lib/projects/urls.d.ts +1 -0
  76. package/lib/projects/urls.js +4 -0
  77. package/lib/prompts/__tests__/createFunctionPrompt.test.d.ts +1 -0
  78. package/lib/prompts/__tests__/createFunctionPrompt.test.js +129 -0
  79. package/lib/prompts/__tests__/createModulePrompt.test.d.ts +1 -0
  80. package/lib/prompts/__tests__/createModulePrompt.test.js +187 -0
  81. package/lib/prompts/__tests__/createTemplatePrompt.test.d.ts +1 -0
  82. package/lib/prompts/__tests__/createTemplatePrompt.test.js +102 -0
  83. package/lib/prompts/confirmImportDataPrompt.d.ts +1 -0
  84. package/lib/prompts/confirmImportDataPrompt.js +12 -0
  85. package/lib/prompts/createFunctionPrompt.d.ts +2 -1
  86. package/lib/prompts/createFunctionPrompt.js +36 -7
  87. package/lib/prompts/createModulePrompt.d.ts +2 -1
  88. package/lib/prompts/createModulePrompt.js +48 -1
  89. package/lib/prompts/createTemplatePrompt.d.ts +3 -24
  90. package/lib/prompts/createTemplatePrompt.js +9 -1
  91. package/lib/prompts/importDataFilePathPrompt.d.ts +1 -0
  92. package/lib/prompts/importDataFilePathPrompt.js +24 -0
  93. package/lib/prompts/importDataTestAccountSelectPrompt.d.ts +3 -0
  94. package/lib/prompts/importDataTestAccountSelectPrompt.js +29 -0
  95. package/lib/prompts/projectDevTargetAccountPrompt.js +1 -0
  96. package/lib/prompts/promptUtils.d.ts +7 -1
  97. package/lib/prompts/promptUtils.js +14 -1
  98. package/lib/ui/__tests__/removeAnsiCodes.test.d.ts +1 -0
  99. package/lib/ui/__tests__/removeAnsiCodes.test.js +84 -0
  100. package/lib/ui/index.js +3 -6
  101. package/lib/ui/removeAnsiCodes.d.ts +1 -0
  102. package/lib/ui/removeAnsiCodes.js +4 -0
  103. package/mcp-server/server.js +2 -1
  104. package/mcp-server/tools/cms/HsListTool.d.ts +23 -0
  105. package/mcp-server/tools/cms/HsListTool.js +58 -0
  106. package/mcp-server/tools/cms/__tests__/HsListTool.test.d.ts +1 -0
  107. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +120 -0
  108. package/mcp-server/tools/index.d.ts +1 -0
  109. package/mcp-server/tools/index.js +8 -0
  110. package/mcp-server/tools/project/DocFetchTool.d.ts +17 -0
  111. package/mcp-server/tools/project/DocFetchTool.js +49 -0
  112. package/mcp-server/tools/project/DocsSearchTool.d.ts +26 -0
  113. package/mcp-server/tools/project/DocsSearchTool.js +62 -0
  114. package/mcp-server/tools/project/GetConfigValuesTool.js +3 -2
  115. package/mcp-server/tools/project/__tests__/DocFetchTool.test.d.ts +1 -0
  116. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +117 -0
  117. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.d.ts +1 -0
  118. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +190 -0
  119. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +1 -1
  120. package/mcp-server/tools/project/constants.d.ts +2 -0
  121. package/mcp-server/tools/project/constants.js +6 -0
  122. package/mcp-server/utils/toolUsageTracking.d.ts +3 -1
  123. package/mcp-server/utils/toolUsageTracking.js +2 -1
  124. package/package.json +9 -6
  125. package/types/Cms.d.ts +16 -0
  126. package/types/Cms.js +25 -1
  127. package/types/LocalDev.d.ts +0 -3
  128. package/types/Prompts.d.ts +1 -0
  129. package/types/Yargs.d.ts +1 -1
  130. package/ui/index.d.ts +1 -0
  131. package/ui/index.js +6 -0
@@ -0,0 +1,89 @@
1
+ import { uiLogger } from '../ui/logger.js';
2
+ import { createImport } from '@hubspot/local-dev-lib/api/crm';
3
+ import { getAccountConfig, getAccountId } from '@hubspot/local-dev-lib/config';
4
+ import { handleImportData, handleTargetTestAccountSelectionFlow, } from '../importData.js';
5
+ import { lib } from '../../lang/en.js';
6
+ import { isDeveloperTestAccount, isStandardAccount, isAppDeveloperAccount, } from '../accountTypes.js';
7
+ import { importDataTestAccountSelectPrompt } from '../prompts/importDataTestAccountSelectPrompt.js';
8
+ vi.mock('../ui/logger');
9
+ vi.mock('@hubspot/local-dev-lib/api/crm');
10
+ vi.mock('@hubspot/local-dev-lib/config');
11
+ vi.mock('../accountTypes');
12
+ vi.mock('../prompts/importDataTestAccountSelectPrompt');
13
+ describe('lib/importData', () => {
14
+ const mockUiLogger = vi.mocked(uiLogger);
15
+ const mockCreateImport = vi.mocked(createImport);
16
+ const mockGetAccountConfig = vi.mocked(getAccountConfig);
17
+ const mockGetAccountId = vi.mocked(getAccountId);
18
+ const mockIsDeveloperTestAccount = vi.mocked(isDeveloperTestAccount);
19
+ const mockIsStandardAccount = vi.mocked(isStandardAccount);
20
+ const mockIsAppDeveloperAccount = vi.mocked(isAppDeveloperAccount);
21
+ const mockImportDataTestAccountSelectPrompt = vi.mocked(importDataTestAccountSelectPrompt);
22
+ beforeEach(() => {
23
+ mockUiLogger.info.mockReset();
24
+ mockUiLogger.success.mockReset();
25
+ mockUiLogger.error.mockReset();
26
+ mockCreateImport.mockReset();
27
+ mockGetAccountConfig.mockReset();
28
+ mockGetAccountId.mockReset();
29
+ mockIsDeveloperTestAccount.mockReset();
30
+ mockIsStandardAccount.mockReset();
31
+ mockIsAppDeveloperAccount.mockReset();
32
+ mockImportDataTestAccountSelectPrompt.mockReset();
33
+ });
34
+ describe('handleImportData', () => {
35
+ const targetAccountId = 123456789;
36
+ const dataFileNames = ['test-file.json'];
37
+ const importRequest = {
38
+ name: 'test-import',
39
+ };
40
+ it('should log the correct success message', async () => {
41
+ // @ts-expect-error - mockCreateImport is not typed correctly
42
+ mockCreateImport.mockResolvedValue({
43
+ data: { id: '123' },
44
+ });
45
+ await handleImportData(targetAccountId, dataFileNames, importRequest);
46
+ expect(mockUiLogger.success).toHaveBeenCalledWith(lib.importData.viewImportLink('https://app.hubspot.com', targetAccountId, '123'));
47
+ });
48
+ it('should log the correct error message', async () => {
49
+ mockCreateImport.mockRejectedValue(new Error('test-error'));
50
+ // weird because we catch the error, log a specific message, and then throw it again
51
+ await expect(handleImportData(targetAccountId, dataFileNames, importRequest)).rejects.toThrow('test-error');
52
+ expect(mockUiLogger.error).toHaveBeenCalledWith(lib.importData.errors.failedToImportData);
53
+ });
54
+ });
55
+ describe('handleTargetTestAccountSelectionFlow', () => {
56
+ const userProvidedAccountId = '1234';
57
+ const derivedAccountId = 123456789;
58
+ it('should error if the userProvidedAccountId is not the right account type', async () => {
59
+ mockGetAccountConfig.mockReturnValue({});
60
+ mockGetAccountId.mockReturnValue(1234);
61
+ mockIsDeveloperTestAccount.mockReturnValue(false);
62
+ await expect(handleTargetTestAccountSelectionFlow(derivedAccountId, userProvidedAccountId)).rejects.toThrow(lib.importData.errors.notDeveloperTestAccount);
63
+ });
64
+ it('should error if the derivedAccountId belongs to the wrong account type', async () => {
65
+ mockGetAccountConfig.mockReturnValue({});
66
+ mockIsDeveloperTestAccount.mockReturnValue(false);
67
+ mockIsStandardAccount.mockReturnValue(false);
68
+ mockIsAppDeveloperAccount.mockReturnValue(false);
69
+ await expect(handleTargetTestAccountSelectionFlow(derivedAccountId, undefined)).rejects.toThrow(lib.importData.errors.incorrectAccountType(derivedAccountId));
70
+ });
71
+ it('should return the derivedAccountId if it is a developer test account', async () => {
72
+ mockGetAccountConfig.mockReturnValue({});
73
+ mockIsDeveloperTestAccount.mockReturnValue(true);
74
+ const result = await handleTargetTestAccountSelectionFlow(derivedAccountId, undefined);
75
+ expect(result).toBe(derivedAccountId);
76
+ });
77
+ it('should return the result of the importDataTestAccountSelectPrompt if the derivedAccountId is a standard or app developer account', async () => {
78
+ mockGetAccountConfig.mockReturnValue({});
79
+ mockIsDeveloperTestAccount.mockReturnValue(false);
80
+ mockIsStandardAccount.mockReturnValue(true);
81
+ mockIsAppDeveloperAccount.mockReturnValue(true);
82
+ mockImportDataTestAccountSelectPrompt.mockResolvedValue({
83
+ selectedAccountId: 890223,
84
+ });
85
+ const result = await handleTargetTestAccountSelectionFlow(derivedAccountId, undefined);
86
+ expect(result).toBe(890223);
87
+ });
88
+ });
89
+ });
@@ -1,6 +1,5 @@
1
1
  import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
2
- import { hasFeature } from './hasFeature.js';
3
- import { FEATURES } from './constants.js';
2
+ import { hasUnfiedAppsAccess } from './hasFeature.js';
4
3
  import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier';
5
4
  function isAccountType(accountConfig, accountType) {
6
5
  return Boolean(accountConfig.accountType && accountType.includes(accountConfig.accountType));
@@ -40,5 +39,5 @@ export async function isUnifiedAccount(account) {
40
39
  if (!accountId) {
41
40
  return false;
42
41
  }
43
- return hasFeature(accountId, FEATURES.UNIFIED_APPS);
42
+ return hasUnfiedAppsAccess(accountId);
44
43
  }
@@ -1,7 +1,7 @@
1
1
  import { logger } from '@hubspot/local-dev-lib/logger';
2
2
  import { getCwd, sanitizeFileName } from '@hubspot/local-dev-lib/path';
3
3
  import { extractZipArchive } from '@hubspot/local-dev-lib/archive';
4
- import { validateUid } from '@hubspot/project-parsing-lib';
4
+ import { validateUid, getProjectThemeDetails, } from '@hubspot/project-parsing-lib';
5
5
  import { UNMIGRATABLE_REASONS } from '@hubspot/local-dev-lib/constants/projects';
6
6
  import { MIGRATION_STATUS } from '@hubspot/local-dev-lib/types/Migration';
7
7
  import { downloadProject } from '@hubspot/local-dev-lib/api/projects';
@@ -9,10 +9,10 @@ import fs from 'fs';
9
9
  import { confirmPrompt, inputPrompt, listPrompt, } from '../../prompts/promptUtils.js';
10
10
  import { ensureProjectExists } from '../../projects/ensureProjectExists.js';
11
11
  import { poll } from '../../polling.js';
12
- import { CLI_UNMIGRATABLE_REASONS, continueMigration, initializeMigration, listAppsForMigration, } from '../../../api/migrate.js';
12
+ import { CLI_UNMIGRATABLE_REASONS, continueAppMigration, initializeAppMigration, listAppsForMigration, } from '../../../api/migrate.js';
13
13
  import { lib } from '../../../lang/en.js';
14
- import { hasFeature } from '../../hasFeature.js';
15
- import { getUnmigratableReason, generateFilterAppsByProjectNameFunction, buildErrorMessageFromMigrationStatus, fetchMigrationApps, promptForAppToMigrate, selectAppToMigrate, handleMigrationSetup, beginMigration, pollMigrationStatus, finalizeMigration, downloadProjectFiles, migrateApp2025_2, logInvalidAccountError, } from '../migrate.js';
14
+ import { hasUnfiedAppsAccess } from '../../hasFeature.js';
15
+ import { getUnmigratableReason, generateFilterAppsByProjectNameFunction, buildErrorMessageFromMigrationStatus, fetchMigrationApps, promptForAppToMigrate, selectAppToMigrate, handleMigrationSetup, getHasMigratableThemes, beginAppMigration, pollMigrationStatus, finalizeAppMigration, downloadProjectFiles, migrateApp2025_2, logInvalidAccountError, validateMigrationAppsAndThemes, } from '../migrate.js';
16
16
  vi.mock('@hubspot/local-dev-lib/logger');
17
17
  vi.mock('@hubspot/local-dev-lib/path');
18
18
  vi.mock('@hubspot/local-dev-lib/archive');
@@ -40,9 +40,10 @@ const mockedListPrompt = listPrompt;
40
40
  const mockedEnsureProjectExists = ensureProjectExists;
41
41
  const mockedPoll = poll;
42
42
  const mockedListAppsForMigration = listAppsForMigration;
43
- const mockedInitializeMigration = initializeMigration;
44
- const mockedContinueMigration = continueMigration;
45
- const mockedHasFeature = hasFeature;
43
+ const mockedGetProjectThemeDetails = getProjectThemeDetails;
44
+ const mockedInitializeAppMigration = initializeAppMigration;
45
+ const mockedContinueAppMigration = continueAppMigration;
46
+ const mockedHasUnfiedAppsAccess = hasUnfiedAppsAccess;
46
47
  const mockedFs = fs;
47
48
  const createMockMigratableApp = (id, name, projectName) => ({
48
49
  appId: id,
@@ -60,7 +61,7 @@ const createMockUnmigratableApp = (id, name, reason) => ({
60
61
  migrationComponents: [],
61
62
  });
62
63
  const createLoadedProjectConfig = (name) => ({
63
- projectConfig: { name },
64
+ projectConfig: { name, srcDir: 'src' },
64
65
  projectDir: MOCK_PROJECT_DIR,
65
66
  });
66
67
  const ACCOUNT_ID = 123;
@@ -81,7 +82,7 @@ describe('lib/app/migrate', () => {
81
82
  mockedGetCwd.mockReturnValue(MOCK_CWD);
82
83
  mockedSanitizeFileName.mockImplementation(name => name);
83
84
  mockedValidateUid.mockReturnValue(undefined);
84
- mockedHasFeature.mockResolvedValue(true);
85
+ mockedHasUnfiedAppsAccess.mockResolvedValue(true);
85
86
  mockedFs.renameSync.mockImplementation(() => { });
86
87
  });
87
88
  describe('getUnmigratableReason', () => {
@@ -191,36 +192,71 @@ describe('lib/app/migrate', () => {
191
192
  });
192
193
  it('should return all apps when no projectConfig is provided', async () => {
193
194
  setupMockApps([createMockMigratableApp(1, 'App 1')]);
194
- const result = await fetchMigrationApps(undefined, ACCOUNT_ID, PLATFORM_VERSION);
195
- expect(result).toHaveLength(1);
196
- expect(result[0].appId).toBe(1);
195
+ const result = await fetchMigrationApps(ACCOUNT_ID, PLATFORM_VERSION);
196
+ expect(result.migratableApps).toHaveLength(1);
197
+ expect(result.migratableApps[0].appId).toBe(1);
198
+ expect(result.unmigratableApps).toHaveLength(0);
197
199
  });
198
200
  it('should filter apps by project name when projectConfig is provided', async () => {
199
201
  const projectConfig = createLoadedProjectConfig(PROJECT_NAME);
200
202
  setupMockApps([createMockMigratableApp(1, 'App 1', PROJECT_NAME)]);
201
- const result = await fetchMigrationApps(undefined, ACCOUNT_ID, PLATFORM_VERSION, projectConfig);
202
- expect(result).toHaveLength(1);
203
- expect(result[0].projectName).toBe(PROJECT_NAME);
203
+ const result = await fetchMigrationApps(ACCOUNT_ID, PLATFORM_VERSION, projectConfig);
204
+ expect(result.migratableApps).toHaveLength(1);
205
+ expect(result.migratableApps[0].projectName).toBe(PROJECT_NAME);
204
206
  });
207
+ });
208
+ describe('getHasMigratableThemes', () => {
209
+ it('should return false when no projectConfig is provided', async () => {
210
+ const result = await getHasMigratableThemes();
211
+ expect(result).toEqual({
212
+ hasMigratableThemes: false,
213
+ migratableThemesCount: 0,
214
+ });
215
+ });
216
+ it('should return true when there are migratable themes', async () => {
217
+ mockedGetProjectThemeDetails.mockResolvedValue({
218
+ legacyThemeDetails: [
219
+ {
220
+ configFilepath: 'src/theme.json',
221
+ themePath: 'src/theme',
222
+ themeConfig: {
223
+ secret_names: ['my-secret'],
224
+ },
225
+ },
226
+ ],
227
+ legacyReactThemeDetails: [],
228
+ });
229
+ const projectConfig = createLoadedProjectConfig(PROJECT_NAME);
230
+ const result = await getHasMigratableThemes(projectConfig);
231
+ expect(result).toEqual({
232
+ hasMigratableThemes: true,
233
+ migratableThemesCount: 1,
234
+ });
235
+ });
236
+ });
237
+ describe('validateMigrationAppsAndThemes', () => {
238
+ const mockMigratableApp1 = createMockMigratableApp(1, 'App 1', PROJECT_NAME);
239
+ const mockMigratableApp2 = createMockMigratableApp(2, 'App 2', PROJECT_NAME);
205
240
  it('should throw an error when multiple apps are found for a project', async () => {
206
241
  const projectConfig = createLoadedProjectConfig(PROJECT_NAME);
207
- setupMockApps([
208
- createMockMigratableApp(1, 'App 1', PROJECT_NAME),
209
- createMockMigratableApp(2, 'App 2', PROJECT_NAME),
210
- ]);
211
- await expect(fetchMigrationApps(undefined, ACCOUNT_ID, PLATFORM_VERSION, projectConfig)).rejects.toThrow(lib.migrate.errors.project.multipleApps);
242
+ await expect(validateMigrationAppsAndThemes(APP_ID, ACCOUNT_ID, {
243
+ migratableApps: [mockMigratableApp1, mockMigratableApp2],
244
+ unmigratableApps: [],
245
+ }, false, projectConfig)).rejects.toThrow(lib.migrate.errors.project.multipleApps);
246
+ });
247
+ it('should throw an error when cms themes are found for a project', async () => {
248
+ const projectConfig = createLoadedProjectConfig(PROJECT_NAME);
249
+ await expect(validateMigrationAppsAndThemes(APP_ID, ACCOUNT_ID, { migratableApps: [mockMigratableApp1], unmigratableApps: [] }, true, projectConfig)).rejects.toThrow(lib.migrate.errors.project.themesAndAppsNotAllowed);
212
250
  });
213
251
  it('should throw an error when no apps are found for a project', async () => {
214
252
  const projectConfig = createLoadedProjectConfig(PROJECT_NAME);
215
- setupMockApps([], []);
216
- await expect(fetchMigrationApps(undefined, ACCOUNT_ID, PLATFORM_VERSION, projectConfig)).rejects.toThrow(lib.migrate.errors.noAppsForProject(PROJECT_NAME));
253
+ await expect(validateMigrationAppsAndThemes(APP_ID, ACCOUNT_ID, { migratableApps: [], unmigratableApps: [] }, false, projectConfig)).rejects.toThrow(lib.migrate.errors.noAppsForProject(PROJECT_NAME));
217
254
  });
218
255
  it('should throw an error when no migratable apps are found', async () => {
219
- setupMockApps([], mockUnmigratableApps);
220
- await expect(fetchMigrationApps(undefined, ACCOUNT_ID, PLATFORM_VERSION)).rejects.toThrow(/No apps in account/);
256
+ await expect(validateMigrationAppsAndThemes(APP_ID, ACCOUNT_ID, { migratableApps: [], unmigratableApps: mockUnmigratableApps }, false)).rejects.toThrow(/No apps in account/);
221
257
  });
222
258
  it('should throw an error when appId is provided but not found', async () => {
223
- await expect(fetchMigrationApps(999, ACCOUNT_ID, PLATFORM_VERSION)).rejects.toThrow(/No apps in account/);
259
+ await expect(validateMigrationAppsAndThemes(APP_ID, ACCOUNT_ID, { migratableApps: [], unmigratableApps: [] }, false)).rejects.toThrow(/No apps in account/);
224
260
  });
225
261
  });
226
262
  describe('promptForAppToMigrate', () => {
@@ -294,6 +330,10 @@ describe('lib/app/migrate', () => {
294
330
  unmigratableApps: [],
295
331
  },
296
332
  });
333
+ mockedGetProjectThemeDetails.mockResolvedValue({
334
+ legacyThemeDetails: [],
335
+ legacyReactThemeDetails: [],
336
+ });
297
337
  mockedListPrompt.mockResolvedValue({ appId: 1 });
298
338
  mockedConfirmPrompt.mockResolvedValue(true);
299
339
  mockedEnsureProjectExists.mockResolvedValue({ projectExists: false });
@@ -312,11 +352,16 @@ describe('lib/app/migrate', () => {
312
352
  unmigratableApps: [],
313
353
  },
314
354
  });
355
+ mockedGetProjectThemeDetails.mockResolvedValueOnce({
356
+ legacyThemeDetails: [],
357
+ legacyReactThemeDetails: [],
358
+ });
315
359
  const result = await handleMigrationSetup(ACCOUNT_ID, defaultOptions, projectConfig);
316
360
  expect(result).toEqual({
317
361
  appIdToMigrate: 1,
318
362
  projectName: PROJECT_NAME,
319
363
  projectDest: MOCK_PROJECT_DIR,
364
+ isThemesMigration: false,
320
365
  });
321
366
  });
322
367
  it('should prompt for project name when not provided', async () => {
@@ -336,10 +381,10 @@ describe('lib/app/migrate', () => {
336
381
  await expect(handleMigrationSetup(ACCOUNT_ID, defaultOptions)).rejects.toThrow(lib.migrate.errors.project.alreadyExists(PROJECT_NAME));
337
382
  });
338
383
  });
339
- describe('beginMigration', () => {
384
+ describe('beginAppMigration', () => {
340
385
  beforeEach(() => {
341
386
  // @ts-expect-error
342
- mockedInitializeMigration.mockResolvedValue({
387
+ mockedInitializeAppMigration.mockResolvedValue({
343
388
  data: { migrationId: MIGRATION_ID },
344
389
  });
345
390
  mockedPoll.mockResolvedValue({
@@ -350,7 +395,7 @@ describe('lib/app/migrate', () => {
350
395
  mockedInputPrompt.mockResolvedValue('test-uid');
351
396
  });
352
397
  it('should initialize migration and return migrationId and uidMap', async () => {
353
- const result = await beginMigration(ACCOUNT_ID, APP_ID, PLATFORM_VERSION);
398
+ const result = await beginAppMigration(ACCOUNT_ID, APP_ID, PLATFORM_VERSION);
354
399
  expect(result).toEqual({
355
400
  migrationId: MIGRATION_ID,
356
401
  uidMap: {},
@@ -368,7 +413,7 @@ describe('lib/app/migrate', () => {
368
413
  },
369
414
  },
370
415
  });
371
- await beginMigration(ACCOUNT_ID, APP_ID, PLATFORM_VERSION);
416
+ await beginAppMigration(ACCOUNT_ID, APP_ID, PLATFORM_VERSION);
372
417
  expect(mockedInputPrompt).toHaveBeenCalledWith(lib.migrate.prompt.uidForComponent("card 'test-card' (ID: 1)"), {
373
418
  defaultAnswer: componentHint,
374
419
  validate: expect.any(Function),
@@ -376,7 +421,7 @@ describe('lib/app/migrate', () => {
376
421
  });
377
422
  it('should throw an error when migration fails', async () => {
378
423
  mockedPoll.mockRejectedValue(new Error('Failed'));
379
- await expect(beginMigration(ACCOUNT_ID, APP_ID, PLATFORM_VERSION)).rejects.toThrow(/Migration Failed/);
424
+ await expect(beginAppMigration(ACCOUNT_ID, APP_ID, PLATFORM_VERSION)).rejects.toThrow(/Migration Failed/);
380
425
  });
381
426
  });
382
427
  describe('pollMigrationStatus', () => {
@@ -392,11 +437,11 @@ describe('lib/app/migrate', () => {
392
437
  expect(result).toBe(mockStatus);
393
438
  });
394
439
  });
395
- describe('finalizeMigration', () => {
440
+ describe('finalizeAppMigration', () => {
396
441
  const uidMap = { '1': 'test-uid' };
397
442
  beforeEach(() => {
398
443
  // @ts-expect-error
399
- mockedContinueMigration.mockResolvedValue({
444
+ mockedContinueAppMigration.mockResolvedValue({
400
445
  data: { migrationId: MIGRATION_ID },
401
446
  });
402
447
  mockedPoll.mockResolvedValue({
@@ -406,12 +451,12 @@ describe('lib/app/migrate', () => {
406
451
  });
407
452
  });
408
453
  it('should continue migration and return buildId', async () => {
409
- const result = await finalizeMigration(ACCOUNT_ID, MIGRATION_ID, uidMap, PROJECT_NAME);
454
+ const result = await finalizeAppMigration(ACCOUNT_ID, MIGRATION_ID, uidMap, PROJECT_NAME);
410
455
  expect(result).toBe(BUILD_ID);
411
456
  });
412
457
  it('should throw an error when migration fails', async () => {
413
458
  mockedPoll.mockRejectedValue(new Error('Test error'));
414
- await expect(finalizeMigration(ACCOUNT_ID, MIGRATION_ID, uidMap, PROJECT_NAME)).rejects.toThrow(/Migration Failed/);
459
+ await expect(finalizeAppMigration(ACCOUNT_ID, MIGRATION_ID, uidMap, PROJECT_NAME)).rejects.toThrow(/Migration Failed/);
415
460
  });
416
461
  });
417
462
  describe('downloadProjectFiles', () => {
@@ -459,10 +504,10 @@ describe('lib/app/migrate', () => {
459
504
  unstable: false,
460
505
  };
461
506
  beforeEach(() => {
462
- mockedHasFeature.mockResolvedValue(true);
507
+ mockedHasUnfiedAppsAccess.mockResolvedValue(true);
463
508
  });
464
509
  it('should throw an error when account is not ungated for unified apps', async () => {
465
- mockedHasFeature.mockResolvedValueOnce(false);
510
+ mockedHasUnfiedAppsAccess.mockResolvedValueOnce(false);
466
511
  await expect(migrateApp2025_2(ACCOUNT_ID, options)).rejects.toThrowError(/isn't enrolled in the required product beta to access this command./);
467
512
  });
468
513
  it('should throw an error when projectConfig is invalid', async () => {
@@ -1,6 +1,6 @@
1
1
  import { ArgumentsCamelCase } from 'yargs';
2
2
  import { LoadedProjectConfig } from '../projects/config.js';
3
- import { MigrationApp, MigrationFailed, MigrationStatus } from '../../api/migrate.js';
3
+ import { MigrationApp, MigratableApp, UnmigratableApp, MigrationFailed, MigrationStatus } from '../../api/migrate.js';
4
4
  import { AccountArgs, CommonArgs, ConfigArgs, EnvironmentArgs } from '../../types/Yargs.js';
5
5
  export type MigrateAppArgs = CommonArgs & AccountArgs & EnvironmentArgs & ConfigArgs & {
6
6
  name?: string;
@@ -12,7 +12,18 @@ export type MigrateAppArgs = CommonArgs & AccountArgs & EnvironmentArgs & Config
12
12
  export declare function getUnmigratableReason(reasonCode: string, projectName: string | undefined, accountId: number): string;
13
13
  export declare function generateFilterAppsByProjectNameFunction(projectConfig?: LoadedProjectConfig): (app: MigrationApp) => boolean;
14
14
  export declare function buildErrorMessageFromMigrationStatus(error: MigrationFailed): string;
15
- export declare function fetchMigrationApps(appId: MigrateAppArgs['appId'], derivedAccountId: number, platformVersion: string, projectConfig?: LoadedProjectConfig): Promise<MigrationApp[]>;
15
+ export declare function getHasMigratableThemes(projectConfig?: LoadedProjectConfig): Promise<{
16
+ hasMigratableThemes: boolean;
17
+ migratableThemesCount: number;
18
+ }>;
19
+ export declare function fetchMigrationApps(derivedAccountId: number, platformVersion: string, projectConfig?: LoadedProjectConfig): Promise<{
20
+ migratableApps: MigratableApp[];
21
+ unmigratableApps: UnmigratableApp[];
22
+ }>;
23
+ export declare function validateMigrationAppsAndThemes(appId: MigrateAppArgs['appId'], derivedAccountId: number, { migratableApps, unmigratableApps, }: {
24
+ migratableApps: MigratableApp[];
25
+ unmigratableApps: UnmigratableApp[];
26
+ }, hasMigratableThemes: boolean, projectConfig?: LoadedProjectConfig): Promise<void>;
16
27
  export declare function promptForAppToMigrate(allApps: MigrationApp[], derivedAccountId: number): Promise<number>;
17
28
  export declare function selectAppToMigrate(allApps: MigrationApp[], derivedAccountId: number, appId?: number): Promise<{
18
29
  proceed: boolean;
@@ -22,13 +33,15 @@ export declare function handleMigrationSetup(derivedAccountId: number, options:
22
33
  appIdToMigrate?: number | undefined;
23
34
  projectName?: string;
24
35
  projectDest?: string;
36
+ isThemesMigration?: boolean;
25
37
  }>;
26
- export declare function beginMigration(derivedAccountId: number, appId: number, platformVersion: string): Promise<{
38
+ export declare function handleThemesMigration(projectConfig: LoadedProjectConfig, platformVersion: string): Promise<void>;
39
+ export declare function beginAppMigration(derivedAccountId: number, appId: number, platformVersion: string): Promise<{
27
40
  migrationId: number;
28
41
  uidMap: Record<string, string>;
29
42
  } | undefined>;
30
43
  export declare function pollMigrationStatus(derivedAccountId: number, migrationId: number, successStates?: string[]): Promise<MigrationStatus>;
31
- export declare function finalizeMigration(derivedAccountId: number, migrationId: number, uidMap: Record<string, string>, projectName: string): Promise<number>;
44
+ export declare function finalizeAppMigration(derivedAccountId: number, migrationId: number, uidMap: Record<string, string>, projectName: string): Promise<number>;
32
45
  export declare function downloadProjectFiles(derivedAccountId: number, projectName: string, buildId: number, projectDest: string, projectConfig?: LoadedProjectConfig): Promise<void>;
33
46
  export declare function migrateApp2025_2(derivedAccountId: number, options: ArgumentsCamelCase<MigrateAppArgs>, projectConfig?: LoadedProjectConfig): Promise<void>;
34
47
  export declare function logInvalidAccountError(): void;
@@ -2,7 +2,7 @@ import path from 'path';
2
2
  import { getCwd, sanitizeFileName } from '@hubspot/local-dev-lib/path';
3
3
  import { extractZipArchive } from '@hubspot/local-dev-lib/archive';
4
4
  import chalk from 'chalk';
5
- import { validateUid } from '@hubspot/project-parsing-lib';
5
+ import { validateUid, migrateThemes, getProjectThemeDetails, } from '@hubspot/project-parsing-lib';
6
6
  import { UNMIGRATABLE_REASONS } from '@hubspot/local-dev-lib/constants/projects';
7
7
  import { mapToUserFacingType } from '@hubspot/project-parsing-lib/src/lib/transform.js';
8
8
  import { MIGRATION_STATUS } from '@hubspot/local-dev-lib/types/Migration';
@@ -10,16 +10,19 @@ import { downloadProject } from '@hubspot/local-dev-lib/api/projects';
10
10
  import { Separator } from '@inquirer/prompts';
11
11
  import { confirmPrompt, inputPrompt, listPrompt, } from '../prompts/promptUtils.js';
12
12
  import { uiAccountDescription, uiCommandReference, uiLine, uiLink, } from '../ui/index.js';
13
+ import { writeProjectConfig } from '../projects/config.js';
13
14
  import { ensureProjectExists } from '../projects/ensureProjectExists.js';
14
15
  import SpinniesManager from '../ui/SpinniesManager.js';
15
16
  import { DEFAULT_POLLING_STATUS_LOOKUP, poll } from '../polling.js';
16
- import { checkMigrationStatusV2, CLI_UNMIGRATABLE_REASONS, continueMigration, initializeMigration, isMigrationStatus, listAppsForMigration, } from '../../api/migrate.js';
17
+ import { checkMigrationStatusV2, CLI_UNMIGRATABLE_REASONS, continueAppMigration, initializeAppMigration, isMigrationStatus, listAppsForMigration, } from '../../api/migrate.js';
17
18
  import fs from 'fs';
18
19
  import { lib } from '../../lang/en.js';
19
- import { hasFeature } from '../hasFeature.js';
20
- import { FEATURES } from '../constants.js';
20
+ import { PROJECT_CONFIG_FILE } from '../constants.js';
21
+ import { hasUnfiedAppsAccess } from '../hasFeature.js';
21
22
  import { getProjectBuildDetailUrl, getProjectDetailUrl, } from '../projects/urls.js';
22
23
  import { uiLogger } from '../ui/logger.js';
24
+ import { debugError } from '../errorHandlers/index.js';
25
+ import { useV3Api } from '../projects/buildAndDeploy.js';
23
26
  export function getUnmigratableReason(reasonCode, projectName, accountId) {
24
27
  switch (reasonCode) {
25
28
  case UNMIGRATABLE_REASONS.UP_TO_DATE:
@@ -56,14 +59,43 @@ export function buildErrorMessageFromMigrationStatus(error) {
56
59
  })
57
60
  .join('\n\t- ')}`;
58
61
  }
59
- export async function fetchMigrationApps(appId, derivedAccountId, platformVersion, projectConfig) {
62
+ export async function getHasMigratableThemes(projectConfig) {
63
+ if (!projectConfig?.projectConfig?.name || !projectConfig?.projectDir) {
64
+ return { hasMigratableThemes: false, migratableThemesCount: 0 };
65
+ }
66
+ const projectSrcDir = path.resolve(projectConfig.projectDir, projectConfig.projectConfig.srcDir);
67
+ const { legacyThemeDetails, legacyReactThemeDetails } = await getProjectThemeDetails(projectSrcDir);
68
+ return {
69
+ hasMigratableThemes: legacyThemeDetails.length > 0 || legacyReactThemeDetails.length > 0,
70
+ migratableThemesCount: legacyThemeDetails.length + legacyReactThemeDetails.length,
71
+ };
72
+ }
73
+ export async function fetchMigrationApps(derivedAccountId, platformVersion, projectConfig) {
60
74
  const { data: { migratableApps, unmigratableApps }, } = await listAppsForMigration(derivedAccountId, platformVersion);
61
75
  const filteredMigratableApps = migratableApps.filter(generateFilterAppsByProjectNameFunction(projectConfig));
62
76
  const filteredUnmigratableApps = unmigratableApps.filter(generateFilterAppsByProjectNameFunction(projectConfig));
63
- const allApps = [...filteredMigratableApps, ...filteredUnmigratableApps];
77
+ return {
78
+ migratableApps: filteredMigratableApps,
79
+ unmigratableApps: filteredUnmigratableApps,
80
+ };
81
+ }
82
+ export async function validateMigrationAppsAndThemes(appId, derivedAccountId, { migratableApps, unmigratableApps, }, hasMigratableThemes, projectConfig) {
83
+ const allApps = [...migratableApps, ...unmigratableApps];
64
84
  if (allApps.length > 1 && projectConfig) {
65
85
  throw new Error(lib.migrate.errors.project.multipleApps);
66
86
  }
87
+ if (hasMigratableThemes) {
88
+ if (useV3Api(projectConfig?.projectConfig?.platformVersion)) {
89
+ throw new Error(lib.migrate.errors.project.themesAlreadyMigrated);
90
+ }
91
+ if (allApps.length > 0 && projectConfig) {
92
+ throw new Error(lib.migrate.errors.project.themesAndAppsNotAllowed);
93
+ }
94
+ if (!projectConfig) {
95
+ throw new Error(lib.migrate.errors.project.noProjectForThemesMigration);
96
+ }
97
+ return;
98
+ }
67
99
  if (!projectConfig?.projectConfig) {
68
100
  allApps.forEach(app => {
69
101
  if (app.projectName) {
@@ -77,7 +109,7 @@ export async function fetchMigrationApps(appId, derivedAccountId, platformVersio
77
109
  throw new Error(lib.migrate.errors.noAppsForProject(projectConfig?.projectConfig?.name || ''));
78
110
  }
79
111
  if (allApps.length === 0 || !allApps.some(app => app.isMigratable)) {
80
- const reasons = filteredUnmigratableApps.map(app => `${chalk.bold(app.appName)}: ${getUnmigratableReason(app.unmigratableReason, app.projectName, derivedAccountId)}`);
112
+ const reasons = unmigratableApps.map(app => `${chalk.bold(app.appName)}: ${getUnmigratableReason(app.unmigratableReason, app.projectName, derivedAccountId)}`);
81
113
  throw new Error(lib.migrate.errors.noAppsEligible(uiAccountDescription(derivedAccountId), reasons));
82
114
  }
83
115
  if (appId &&
@@ -86,7 +118,6 @@ export async function fetchMigrationApps(appId, derivedAccountId, platformVersio
86
118
  })) {
87
119
  throw new Error(lib.migrate.errors.appWithAppIdNotFound(appId));
88
120
  }
89
- return allApps;
90
121
  }
91
122
  export async function promptForAppToMigrate(allApps, derivedAccountId) {
92
123
  const appChoices = allApps.map(app => ({
@@ -143,18 +174,39 @@ export async function selectAppToMigrate(allApps, derivedAccountId, appId) {
143
174
  };
144
175
  }
145
176
  export async function handleMigrationSetup(derivedAccountId, options, projectConfig) {
177
+ SpinniesManager.add('checkingForMigratableComponents', {
178
+ text: lib.migrate.spinners.checkingForMigratableComponents,
179
+ });
146
180
  const { name, dest, appId } = options;
147
- const allApps = await fetchMigrationApps(appId, derivedAccountId, options.platformVersion, projectConfig);
148
- const { proceed, appIdToMigrate } = await selectAppToMigrate(allApps, derivedAccountId, appId);
181
+ const { hasMigratableThemes, migratableThemesCount } = await getHasMigratableThemes(projectConfig);
182
+ const { migratableApps, unmigratableApps } = await fetchMigrationApps(derivedAccountId, options.platformVersion, projectConfig);
183
+ SpinniesManager.remove('checkingForMigratableComponents');
184
+ await validateMigrationAppsAndThemes(appId, derivedAccountId, { migratableApps, unmigratableApps }, hasMigratableThemes, projectConfig);
185
+ const allApps = [...migratableApps, ...unmigratableApps];
186
+ let proceed = false;
187
+ let appIdToMigrate;
188
+ if (hasMigratableThemes) {
189
+ uiLogger.log(lib.migrate.prompt.themesMigration(migratableThemesCount));
190
+ proceed = await confirmPrompt(lib.migrate.prompt.proceed, {
191
+ defaultAnswer: false,
192
+ });
193
+ }
194
+ else {
195
+ const appSelectionPrompt = await selectAppToMigrate(allApps, derivedAccountId, appId);
196
+ appIdToMigrate = appSelectionPrompt.appIdToMigrate;
197
+ proceed = appSelectionPrompt.proceed;
198
+ }
149
199
  if (!proceed) {
150
200
  return {};
151
201
  }
152
202
  // If it's a project we don't want to prompt for dest and name, so just return early
203
+ // Theme migrations can only be initiated from the project directory
153
204
  if (projectConfig &&
154
205
  projectConfig?.projectConfig &&
155
206
  projectConfig?.projectDir) {
156
207
  return {
157
208
  appIdToMigrate,
209
+ isThemesMigration: hasMigratableThemes,
158
210
  projectName: projectConfig.projectConfig.name,
159
211
  projectDest: projectConfig.projectDir,
160
212
  };
@@ -179,12 +231,34 @@ export async function handleMigrationSetup(derivedAccountId, options, projectCon
179
231
  }));
180
232
  return { appIdToMigrate, projectName, projectDest };
181
233
  }
182
- export async function beginMigration(derivedAccountId, appId, platformVersion) {
234
+ export async function handleThemesMigration(projectConfig, platformVersion) {
235
+ if (!projectConfig?.projectDir || !projectConfig?.projectConfig?.srcDir) {
236
+ throw new Error(lib.migrate.errors.project.invalidConfig);
237
+ }
238
+ const projectSrcDir = path.resolve(projectConfig.projectDir, projectConfig.projectConfig.srcDir);
239
+ try {
240
+ await migrateThemes(projectConfig.projectDir, projectSrcDir);
241
+ }
242
+ catch (error) {
243
+ debugError(error);
244
+ throw new Error(lib.migrate.errors.project.failedToMigrateThemes);
245
+ }
246
+ const newProjectConfig = { ...projectConfig.projectConfig };
247
+ newProjectConfig.platformVersion = platformVersion;
248
+ const projectConfigPath = path.join(projectConfig.projectDir, PROJECT_CONFIG_FILE);
249
+ const success = writeProjectConfig(projectConfigPath, newProjectConfig);
250
+ if (!success) {
251
+ throw new Error(lib.migrate.errors.project.failedToUpdateProjectConfig);
252
+ }
253
+ uiLogger.log('');
254
+ uiLogger.log(lib.migrate.success.themesMigrationSuccess(platformVersion));
255
+ }
256
+ export async function beginAppMigration(derivedAccountId, appId, platformVersion) {
183
257
  SpinniesManager.add('beginningMigration', {
184
258
  text: lib.migrate.spinners.beginningMigration,
185
259
  });
186
260
  const uidMap = {};
187
- const { data } = await initializeMigration(derivedAccountId, appId, platformVersion);
261
+ const { data } = await initializeAppMigration(derivedAccountId, appId, platformVersion);
188
262
  const { migrationId } = data;
189
263
  let pollResponse;
190
264
  try {
@@ -247,13 +321,13 @@ export async function pollMigrationStatus(derivedAccountId, migrationId, success
247
321
  errorStates: [...DEFAULT_POLLING_STATUS_LOOKUP.errorStates],
248
322
  });
249
323
  }
250
- export async function finalizeMigration(derivedAccountId, migrationId, uidMap, projectName) {
324
+ export async function finalizeAppMigration(derivedAccountId, migrationId, uidMap, projectName) {
251
325
  let pollResponse;
252
326
  try {
253
327
  SpinniesManager.add('finishingMigration', {
254
328
  text: lib.migrate.spinners.finishingMigration,
255
329
  });
256
- await continueMigration(derivedAccountId, migrationId, uidMap, projectName);
330
+ await continueAppMigration(derivedAccountId, migrationId, uidMap, projectName);
257
331
  pollResponse = await pollMigrationStatus(derivedAccountId, migrationId, [
258
332
  MIGRATION_STATUS.SUCCESS,
259
333
  ]);
@@ -307,7 +381,7 @@ export async function downloadProjectFiles(derivedAccountId, projectName, buildI
307
381
  SpinniesManager.succeed('fetchingMigratedProject', {
308
382
  text: lib.migrate.spinners.downloadingProjectContentsComplete,
309
383
  });
310
- uiLogger.success(`Saved ${projectName} to ${projectDest}`);
384
+ uiLogger.success(lib.migrate.success.downloadedProject(projectName, projectDest));
311
385
  }
312
386
  catch (error) {
313
387
  SpinniesManager.fail('fetchingMigratedProject', {
@@ -318,7 +392,7 @@ export async function downloadProjectFiles(derivedAccountId, projectName, buildI
318
392
  }
319
393
  export async function migrateApp2025_2(derivedAccountId, options, projectConfig) {
320
394
  SpinniesManager.init();
321
- const ungatedForUnifiedApps = await hasFeature(derivedAccountId, FEATURES.UNIFIED_APPS);
395
+ const ungatedForUnifiedApps = await hasUnfiedAppsAccess(derivedAccountId);
322
396
  if (!ungatedForUnifiedApps) {
323
397
  throw new Error(lib.migrate.errors.notUngatedForUnifiedApps(uiAccountDescription(derivedAccountId)));
324
398
  }
@@ -331,16 +405,20 @@ export async function migrateApp2025_2(derivedAccountId, options, projectConfig)
331
405
  throw new Error(lib.migrate.errors.project.doesNotExist(derivedAccountId));
332
406
  }
333
407
  }
334
- const { appIdToMigrate, projectName, projectDest } = await handleMigrationSetup(derivedAccountId, options, projectConfig);
408
+ const { appIdToMigrate, projectName, projectDest, isThemesMigration } = await handleMigrationSetup(derivedAccountId, options, projectConfig);
409
+ if (isThemesMigration) {
410
+ await handleThemesMigration(projectConfig, options.platformVersion);
411
+ return;
412
+ }
335
413
  if (!appIdToMigrate || !projectName || !projectDest) {
336
414
  return;
337
415
  }
338
- const migrationInProgress = await beginMigration(derivedAccountId, appIdToMigrate, options.platformVersion);
416
+ const migrationInProgress = await beginAppMigration(derivedAccountId, appIdToMigrate, options.platformVersion);
339
417
  if (!migrationInProgress) {
340
418
  return;
341
419
  }
342
420
  const { migrationId, uidMap } = migrationInProgress;
343
- const buildId = await finalizeMigration(derivedAccountId, migrationId, uidMap, projectConfig?.projectConfig?.name || projectName);
421
+ const buildId = await finalizeAppMigration(derivedAccountId, migrationId, uidMap, projectConfig?.projectConfig?.name || projectName);
344
422
  await downloadProjectFiles(derivedAccountId, projectName, buildId, projectDest, projectConfig);
345
423
  uiLogger.log(uiLink('Project Details', getProjectDetailUrl(projectName, derivedAccountId)));
346
424
  uiLogger.log(uiLink('Build Details', getProjectBuildDetailUrl(projectName, buildId, derivedAccountId)));
@@ -87,6 +87,7 @@ export declare const LOCAL_DEV_UI_MESSAGE_SEND_TYPES: {
87
87
  UPDATE_PROJECT_NODES: string;
88
88
  UPDATE_APP_DATA: string;
89
89
  UPDATE_PROJECT_DATA: string;
90
+ UPDATE_UPLOAD_WARNINGS: string;
90
91
  };
91
92
  export declare const LOCAL_DEV_UI_MESSAGE_RECEIVE_TYPES: {
92
93
  UPLOAD: string;
package/lib/constants.js CHANGED
@@ -79,6 +79,7 @@ export const LOCAL_DEV_UI_MESSAGE_SEND_TYPES = {
79
79
  UPDATE_PROJECT_NODES: 'server:updateProjectNodes',
80
80
  UPDATE_APP_DATA: 'server:updateAppData',
81
81
  UPDATE_PROJECT_DATA: 'server:updateProjectData',
82
+ UPDATE_UPLOAD_WARNINGS: 'server:updateUploadWarnings',
82
83
  };
83
84
  export const LOCAL_DEV_UI_MESSAGE_RECEIVE_TYPES = {
84
85
  UPLOAD: 'client:upload',