@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.
- package/README.md +0 -4
- package/api/__tests__/migrate.test.js +5 -5
- package/api/migrate.d.ts +10 -4
- package/api/migrate.js +2 -2
- package/commands/__tests__/create.test.js +20 -0
- package/commands/__tests__/testAccount.test.js +2 -0
- package/commands/app/__tests__/migrate.test.js +1 -0
- package/commands/create/function.js +2 -2
- package/commands/create/module.js +2 -2
- package/commands/create/template.js +2 -2
- package/commands/create.js +47 -0
- package/commands/getStarted.js +66 -4
- package/commands/mcp/setup.d.ts +0 -1
- package/commands/mcp/setup.js +3 -11
- package/commands/project/__tests__/create.test.js +57 -0
- package/commands/project/__tests__/devUnifiedFlow.test.js +18 -30
- package/commands/project/create.js +6 -1
- package/commands/project/deploy.js +31 -1
- package/commands/project/dev/deprecatedFlow.js +2 -1
- package/commands/project/dev/index.js +32 -12
- package/commands/project/dev/unifiedFlow.d.ts +1 -1
- package/commands/project/dev/unifiedFlow.js +10 -16
- package/commands/project/profile/delete.js +26 -14
- package/commands/project/upload.d.ts +2 -2
- package/commands/project/upload.js +1 -1
- package/commands/testAccount/__tests__/importData.test.d.ts +1 -0
- package/commands/testAccount/__tests__/importData.test.js +93 -0
- package/commands/testAccount/create.js +23 -13
- package/commands/testAccount/importData.d.ts +9 -0
- package/commands/testAccount/importData.js +61 -0
- package/commands/testAccount.js +2 -0
- package/lang/en.d.ts +160 -46
- package/lang/en.js +175 -59
- package/lang/en.lyaml +35 -14
- package/lib/__tests__/importData.test.d.ts +1 -0
- package/lib/__tests__/importData.test.js +89 -0
- package/lib/accountTypes.js +2 -3
- package/lib/app/__tests__/migrate.test.js +81 -36
- package/lib/app/migrate.d.ts +17 -4
- package/lib/app/migrate.js +97 -19
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +1 -0
- package/lib/hasFeature.d.ts +1 -0
- package/lib/hasFeature.js +7 -0
- package/lib/importData.d.ts +3 -0
- package/lib/importData.js +50 -0
- package/lib/mcp/setup.d.ts +0 -2
- package/lib/mcp/setup.js +0 -24
- package/lib/process.js +15 -4
- package/lib/projectProfiles.d.ts +1 -1
- package/lib/projectProfiles.js +10 -2
- package/lib/projects/__tests__/AppDevModeInterface.test.js +3 -3
- package/lib/projects/__tests__/LocalDevProcess.test.js +5 -95
- package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +6 -6
- package/lib/projects/__tests__/components.test.js +164 -7
- package/lib/projects/__tests__/localDevProjectHelpers.test.d.ts +1 -0
- package/lib/projects/__tests__/localDevProjectHelpers.test.js +118 -0
- package/lib/projects/add/v3AddComponent.js +16 -4
- package/lib/projects/components.d.ts +1 -0
- package/lib/projects/components.js +27 -1
- package/lib/projects/localDev/AppDevModeInterface.js +35 -3
- package/lib/projects/localDev/LocalDevLogger.d.ts +0 -4
- package/lib/projects/localDev/LocalDevLogger.js +2 -19
- package/lib/projects/localDev/LocalDevManager.js +1 -1
- package/lib/projects/localDev/LocalDevProcess.d.ts +1 -2
- package/lib/projects/localDev/LocalDevProcess.js +3 -26
- package/lib/projects/localDev/LocalDevState.d.ts +6 -7
- package/lib/projects/localDev/LocalDevState.js +16 -15
- package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +1 -0
- package/lib/projects/localDev/LocalDevWebsocketServer.js +17 -2
- package/lib/projects/localDev/{helpers.d.ts → helpers/account.d.ts} +1 -7
- package/lib/projects/localDev/{helpers.js → helpers/account.js} +44 -144
- package/lib/projects/localDev/helpers/project.d.ts +12 -0
- package/lib/projects/localDev/helpers/project.js +173 -0
- package/lib/projects/urls.d.ts +1 -0
- package/lib/projects/urls.js +4 -0
- package/lib/prompts/__tests__/createFunctionPrompt.test.d.ts +1 -0
- package/lib/prompts/__tests__/createFunctionPrompt.test.js +129 -0
- package/lib/prompts/__tests__/createModulePrompt.test.d.ts +1 -0
- package/lib/prompts/__tests__/createModulePrompt.test.js +187 -0
- package/lib/prompts/__tests__/createTemplatePrompt.test.d.ts +1 -0
- package/lib/prompts/__tests__/createTemplatePrompt.test.js +102 -0
- package/lib/prompts/confirmImportDataPrompt.d.ts +1 -0
- package/lib/prompts/confirmImportDataPrompt.js +12 -0
- package/lib/prompts/createFunctionPrompt.d.ts +2 -1
- package/lib/prompts/createFunctionPrompt.js +36 -7
- package/lib/prompts/createModulePrompt.d.ts +2 -1
- package/lib/prompts/createModulePrompt.js +48 -1
- package/lib/prompts/createTemplatePrompt.d.ts +3 -24
- package/lib/prompts/createTemplatePrompt.js +9 -1
- package/lib/prompts/importDataFilePathPrompt.d.ts +1 -0
- package/lib/prompts/importDataFilePathPrompt.js +24 -0
- package/lib/prompts/importDataTestAccountSelectPrompt.d.ts +3 -0
- package/lib/prompts/importDataTestAccountSelectPrompt.js +29 -0
- package/lib/prompts/projectDevTargetAccountPrompt.js +1 -0
- package/lib/prompts/promptUtils.d.ts +7 -1
- package/lib/prompts/promptUtils.js +14 -1
- package/lib/ui/__tests__/removeAnsiCodes.test.d.ts +1 -0
- package/lib/ui/__tests__/removeAnsiCodes.test.js +84 -0
- package/lib/ui/index.js +3 -6
- package/lib/ui/removeAnsiCodes.d.ts +1 -0
- package/lib/ui/removeAnsiCodes.js +4 -0
- package/mcp-server/server.js +2 -1
- package/mcp-server/tools/cms/HsListTool.d.ts +23 -0
- package/mcp-server/tools/cms/HsListTool.js +58 -0
- package/mcp-server/tools/cms/__tests__/HsListTool.test.d.ts +1 -0
- package/mcp-server/tools/cms/__tests__/HsListTool.test.js +120 -0
- package/mcp-server/tools/index.d.ts +1 -0
- package/mcp-server/tools/index.js +8 -0
- package/mcp-server/tools/project/DocFetchTool.d.ts +17 -0
- package/mcp-server/tools/project/DocFetchTool.js +49 -0
- package/mcp-server/tools/project/DocsSearchTool.d.ts +26 -0
- package/mcp-server/tools/project/DocsSearchTool.js +62 -0
- package/mcp-server/tools/project/GetConfigValuesTool.js +3 -2
- package/mcp-server/tools/project/__tests__/DocFetchTool.test.d.ts +1 -0
- package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +117 -0
- package/mcp-server/tools/project/__tests__/DocsSearchTool.test.d.ts +1 -0
- package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +190 -0
- package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +1 -1
- package/mcp-server/tools/project/constants.d.ts +2 -0
- package/mcp-server/tools/project/constants.js +6 -0
- package/mcp-server/utils/toolUsageTracking.d.ts +3 -1
- package/mcp-server/utils/toolUsageTracking.js +2 -1
- package/package.json +9 -6
- package/types/Cms.d.ts +16 -0
- package/types/Cms.js +25 -1
- package/types/LocalDev.d.ts +0 -3
- package/types/Prompts.d.ts +1 -0
- package/types/Yargs.d.ts +1 -1
- package/ui/index.d.ts +1 -0
- 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
|
+
});
|
package/lib/accountTypes.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
|
|
2
|
-
import {
|
|
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
|
|
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,
|
|
12
|
+
import { CLI_UNMIGRATABLE_REASONS, continueAppMigration, initializeAppMigration, listAppsForMigration, } from '../../../api/migrate.js';
|
|
13
13
|
import { lib } from '../../../lang/en.js';
|
|
14
|
-
import {
|
|
15
|
-
import { getUnmigratableReason, generateFilterAppsByProjectNameFunction, buildErrorMessageFromMigrationStatus, fetchMigrationApps, promptForAppToMigrate, selectAppToMigrate, handleMigrationSetup,
|
|
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
|
|
44
|
-
const
|
|
45
|
-
const
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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('
|
|
384
|
+
describe('beginAppMigration', () => {
|
|
340
385
|
beforeEach(() => {
|
|
341
386
|
// @ts-expect-error
|
|
342
|
-
|
|
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
|
|
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
|
|
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(
|
|
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('
|
|
440
|
+
describe('finalizeAppMigration', () => {
|
|
396
441
|
const uidMap = { '1': 'test-uid' };
|
|
397
442
|
beforeEach(() => {
|
|
398
443
|
// @ts-expect-error
|
|
399
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
507
|
+
mockedHasUnfiedAppsAccess.mockResolvedValue(true);
|
|
463
508
|
});
|
|
464
509
|
it('should throw an error when account is not ungated for unified apps', async () => {
|
|
465
|
-
|
|
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 () => {
|
package/lib/app/migrate.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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;
|
package/lib/app/migrate.js
CHANGED
|
@@ -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,
|
|
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 {
|
|
20
|
-
import {
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
148
|
-
const {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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)));
|
package/lib/constants.d.ts
CHANGED
|
@@ -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',
|