@hubspot/cli 7.7.35-experimental.0 → 7.8.1-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.
@@ -4,9 +4,17 @@ import { commands } from '../../lang/en.js';
4
4
  import { uiLogger } from '../../lib/ui/logger.js';
5
5
  import { addMcpServerToConfig, supportedTools } from '../../lib/mcp/setup.js';
6
6
  import { trackCommandUsage } from '../../lib/usageTracking.js';
7
+ import { hasFeature } from '../../lib/hasFeature.js';
8
+ import { FEATURES } from '../../lib/constants.js';
7
9
  const command = ['setup', 'update'];
8
10
  const describe = undefined; // Leave hidden for now
9
11
  async function handler(args) {
12
+ const { derivedAccountId } = args;
13
+ const hasMcpAccess = await hasFeature(derivedAccountId, FEATURES.MCP_ACCESS);
14
+ if (!hasMcpAccess) {
15
+ uiLogger.error(commands.mcp.setup.errors.needsMcpAccess(derivedAccountId));
16
+ process.exit(EXIT_CODES.ERROR);
17
+ }
10
18
  try {
11
19
  await import('@modelcontextprotocol/sdk/server/mcp.js');
12
20
  }
@@ -125,7 +125,7 @@ function projectCreateBuilder(yargs) {
125
125
  hidden: true,
126
126
  type: 'string',
127
127
  choices: [v2023_2, v2025_1, v2025_2],
128
- default: v2025_2,
128
+ default: v2023_2,
129
129
  },
130
130
  'project-base': {
131
131
  describe: commands.project.create.options.projectBase.describe,
@@ -8,6 +8,9 @@ import { uiCommandReference } from '../../lib/ui/index.js';
8
8
  import { commands, lib } from '../../lang/en.js';
9
9
  import { uiLogger } from '../../lib/ui/logger.js';
10
10
  import { logInBox } from '../../lib/ui/boxen.js';
11
+ import { getHasMigratableThemes, migrateThemes2025_2, } from '../../lib/theme/migrate.js';
12
+ import { hasFeature } from '../../lib/hasFeature.js';
13
+ import { FEATURES } from '../../lib/constants.js';
11
14
  const { v2025_2 } = PLATFORM_VERSIONS;
12
15
  const command = 'migrate';
13
16
  const describe = undefined; // commands.project.migrate.describe
@@ -26,13 +29,29 @@ async function handler(args) {
26
29
  }
27
30
  const { derivedAccountId } = args;
28
31
  try {
29
- await migrateApp2025_2(derivedAccountId, {
30
- ...args,
31
- name: projectConfig?.projectConfig?.name,
32
- platformVersion: unstable
33
- ? PLATFORM_VERSIONS.unstable
34
- : platformVersion,
35
- }, projectConfig);
32
+ const { hasMigratableThemes, migratableThemesCount } = await getHasMigratableThemes(projectConfig);
33
+ if (hasMigratableThemes) {
34
+ const hasThemeMigrationAccess = await hasFeature(derivedAccountId, FEATURES.THEME_MIGRATION_2025_2);
35
+ if (!hasThemeMigrationAccess) {
36
+ uiLogger.error(commands.project.migrate.errors.noThemeMigrationAccess(derivedAccountId));
37
+ return process.exit(EXIT_CODES.ERROR);
38
+ }
39
+ await migrateThemes2025_2(derivedAccountId, {
40
+ ...args,
41
+ platformVersion: unstable
42
+ ? PLATFORM_VERSIONS.unstable
43
+ : platformVersion,
44
+ }, migratableThemesCount, projectConfig);
45
+ }
46
+ else {
47
+ await migrateApp2025_2(derivedAccountId, {
48
+ ...args,
49
+ name: projectConfig?.projectConfig?.name,
50
+ platformVersion: unstable
51
+ ? PLATFORM_VERSIONS.unstable
52
+ : platformVersion,
53
+ }, projectConfig);
54
+ }
36
55
  }
37
56
  catch (error) {
38
57
  logError(error);
package/lang/en.d.ts CHANGED
@@ -872,6 +872,7 @@ Global configuration replaces hubspot.config.yml, and you will be prompted to mi
872
872
  };
873
873
  readonly success: (derivedTargets: string[]) => string;
874
874
  readonly errors: {
875
+ readonly needsMcpAccess: (accountId?: number) => string;
875
876
  readonly needsNode20: "This feature requires node >=20";
876
877
  readonly errorParsingJsonFIle: (filename: string, errorMessage: string) => string;
877
878
  };
@@ -1136,6 +1137,7 @@ ${string}`;
1136
1137
  readonly describe: "Migrate an existing project to the new version of the projects framework.";
1137
1138
  readonly errors: {
1138
1139
  readonly noProjectConfig: (command: string) => string;
1140
+ readonly noThemeMigrationAccess: (accountId?: number) => string;
1139
1141
  };
1140
1142
  readonly examples: {
1141
1143
  readonly default: "Migrate an existing project to the new version of the projects framework.";
@@ -3388,6 +3390,7 @@ Run ${string} to upgrade to version ${string}`;
3388
3390
  readonly sourceContentsMoved: (newLocation: string) => string;
3389
3391
  readonly projectMigrationWarningTitle: "Important: Migrating to platformVersion 2025.2 is irreversible";
3390
3392
  readonly projectMigrationWarning: string;
3393
+ readonly exitWithoutMigrating: "Exiting without migrating";
3391
3394
  readonly success: {
3392
3395
  readonly downloadedProject: (projectName: string, projectDest: string) => string;
3393
3396
  readonly themesMigrationSuccess: (platformVersion: string) => string;
package/lang/en.js CHANGED
@@ -5,6 +5,7 @@ import { PERSONAL_ACCESS_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constant
5
5
  import { ARCHIVED_HUBSPOT_CONFIG_YAML_FILE_NAME, GLOBAL_CONFIG_PATH, DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME, } from '@hubspot/local-dev-lib/constants/config';
6
6
  import { uiAccountDescription, uiBetaTag, uiCommandReference, uiLink, UI_COLORS, } from '../lib/ui/index.js';
7
7
  import { getProjectDetailUrl, getProjectSettingsUrl, getLocalDevUiUrl, getAppAllowlistUrl, } from '../lib/projects/urls.js';
8
+ import { getProductUpdatesUrl } from '../lib/links.js';
8
9
  import { APP_DISTRIBUTION_TYPES, APP_AUTH_TYPES, PROJECT_CONFIG_FILE, PROJECT_WITH_APP, } from '../lib/constants.js';
9
10
  import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier';
10
11
  export const commands = {
@@ -875,6 +876,7 @@ export const commands = {
875
876
  },
876
877
  success: (derivedTargets) => `You can now use the HubSpot CLI MCP Server in ${derivedTargets.join(', ')}. ${chalk.bold('You may need to restart these tools to apply the changes')}.`,
877
878
  errors: {
879
+ needsMcpAccess: (accountId) => `You must opt in to the developer MCP beta to use this feature on ${uiAccountDescription(accountId)}. Try again with a different account or ${uiLink('join the beta now', getProductUpdatesUrl('239890', accountId))}`,
878
880
  needsNode20: `This feature requires node >=20`,
879
881
  errorParsingJsonFIle: (filename, errorMessage) => `Unable to update ${chalk.bold(filename)} due to invalid JSON: ${errorMessage}`,
880
882
  },
@@ -1135,6 +1137,7 @@ export const commands = {
1135
1137
  describe: 'Migrate an existing project to the new version of the projects framework.',
1136
1138
  errors: {
1137
1139
  noProjectConfig: (command) => `No project detected. Please run this command again from a project directory. If you are trying to migrate an app, run ${command}`,
1140
+ noThemeMigrationAccess: (accountId) => `This project contains a CMS theme. You must opt in to theme migration beta to continue updating it on ${uiAccountDescription(accountId)}. Try again with a different account or ${uiLink('join the beta now', getProductUpdatesUrl('253920', accountId))}`,
1138
1141
  },
1139
1142
  examples: {
1140
1143
  default: 'Migrate an existing project to the new version of the projects framework.',
@@ -3382,6 +3385,7 @@ export const lib = {
3382
3385
  sourceContentsMoved: (newLocation) => `The contents of your old source directory have been moved to ${newLocation}, move any required files to the new source directory.`,
3383
3386
  projectMigrationWarningTitle: 'Important: Migrating to platformVersion 2025.2 is irreversible',
3384
3387
  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),
3388
+ exitWithoutMigrating: 'Exiting without migrating',
3385
3389
  success: {
3386
3390
  downloadedProject: (projectName, projectDest) => `Saved ${projectName} to ${projectDest}`,
3387
3391
  themesMigrationSuccess: (platformVersion) => `Successfully migrated project to platformVersion ${chalk.bold(platformVersion)}. Upload your project using ${uiCommandReference('hs project upload')}`,
@@ -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, getProjectThemeDetails, } from '@hubspot/project-parsing-lib';
4
+ import { validateUid } 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';
@@ -12,7 +12,7 @@ import { poll } from '../../polling.js';
12
12
  import { CLI_UNMIGRATABLE_REASONS, continueAppMigration, initializeAppMigration, listAppsForMigration, } from '../../../api/migrate.js';
13
13
  import { lib } from '../../../lang/en.js';
14
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';
15
+ import { getUnmigratableReason, generateFilterAppsByProjectNameFunction, buildErrorMessageFromMigrationStatus, fetchMigrationApps, promptForAppToMigrate, selectAppToMigrate, handleMigrationSetup, beginAppMigration, pollMigrationStatus, finalizeAppMigration, downloadProjectFiles, migrateApp2025_2, logInvalidAccountError, validateMigrationApps, } 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,7 +40,6 @@ const mockedListPrompt = listPrompt;
40
40
  const mockedEnsureProjectExists = ensureProjectExists;
41
41
  const mockedPoll = poll;
42
42
  const mockedListAppsForMigration = listAppsForMigration;
43
- const mockedGetProjectThemeDetails = getProjectThemeDetails;
44
43
  const mockedInitializeAppMigration = initializeAppMigration;
45
44
  const mockedContinueAppMigration = continueAppMigration;
46
45
  const mockedHasUnfiedAppsAccess = hasUnfiedAppsAccess;
@@ -205,58 +204,31 @@ describe('lib/app/migrate', () => {
205
204
  expect(result.migratableApps[0].projectName).toBe(PROJECT_NAME);
206
205
  });
207
206
  });
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', () => {
207
+ describe('validateMigrationApps', () => {
238
208
  const mockMigratableApp1 = createMockMigratableApp(1, 'App 1', PROJECT_NAME);
239
209
  const mockMigratableApp2 = createMockMigratableApp(2, 'App 2', PROJECT_NAME);
240
210
  it('should throw an error when multiple apps are found for a project', async () => {
241
211
  const projectConfig = createLoadedProjectConfig(PROJECT_NAME);
242
- await expect(validateMigrationAppsAndThemes(APP_ID, ACCOUNT_ID, {
212
+ await expect(validateMigrationApps(APP_ID, ACCOUNT_ID, {
243
213
  migratableApps: [mockMigratableApp1, mockMigratableApp2],
244
214
  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);
215
+ }, projectConfig)).rejects.toThrow(lib.migrate.errors.project.multipleApps);
250
216
  });
251
217
  it('should throw an error when no apps are found for a project', async () => {
252
218
  const projectConfig = createLoadedProjectConfig(PROJECT_NAME);
253
- await expect(validateMigrationAppsAndThemes(APP_ID, ACCOUNT_ID, { migratableApps: [], unmigratableApps: [] }, false, projectConfig)).rejects.toThrow(lib.migrate.errors.noAppsForProject(PROJECT_NAME));
219
+ await expect(validateMigrationApps(APP_ID, ACCOUNT_ID, { migratableApps: [], unmigratableApps: [] }, projectConfig)).rejects.toThrow(lib.migrate.errors.noAppsForProject(PROJECT_NAME));
254
220
  });
255
221
  it('should throw an error when no migratable apps are found', async () => {
256
- await expect(validateMigrationAppsAndThemes(APP_ID, ACCOUNT_ID, { migratableApps: [], unmigratableApps: mockUnmigratableApps }, false)).rejects.toThrow(/No apps in account/);
222
+ await expect(validateMigrationApps(APP_ID, ACCOUNT_ID, {
223
+ migratableApps: [],
224
+ unmigratableApps: mockUnmigratableApps,
225
+ })).rejects.toThrow(/No apps in account/);
257
226
  });
258
227
  it('should throw an error when appId is provided but not found', async () => {
259
- await expect(validateMigrationAppsAndThemes(APP_ID, ACCOUNT_ID, { migratableApps: [], unmigratableApps: [] }, false)).rejects.toThrow(/No apps in account/);
228
+ await expect(validateMigrationApps(APP_ID, ACCOUNT_ID, {
229
+ migratableApps: [],
230
+ unmigratableApps: [],
231
+ })).rejects.toThrow(/No apps in account/);
260
232
  });
261
233
  });
262
234
  describe('promptForAppToMigrate', () => {
@@ -330,10 +302,6 @@ describe('lib/app/migrate', () => {
330
302
  unmigratableApps: [],
331
303
  },
332
304
  });
333
- mockedGetProjectThemeDetails.mockResolvedValue({
334
- legacyThemeDetails: [],
335
- legacyReactThemeDetails: [],
336
- });
337
305
  mockedListPrompt.mockResolvedValue({ appId: 1 });
338
306
  mockedConfirmPrompt.mockResolvedValue(true);
339
307
  mockedEnsureProjectExists.mockResolvedValue({ projectExists: false });
@@ -352,16 +320,11 @@ describe('lib/app/migrate', () => {
352
320
  unmigratableApps: [],
353
321
  },
354
322
  });
355
- mockedGetProjectThemeDetails.mockResolvedValueOnce({
356
- legacyThemeDetails: [],
357
- legacyReactThemeDetails: [],
358
- });
359
323
  const result = await handleMigrationSetup(ACCOUNT_ID, defaultOptions, projectConfig);
360
324
  expect(result).toEqual({
361
325
  appIdToMigrate: 1,
362
326
  projectName: PROJECT_NAME,
363
327
  projectDest: MOCK_PROJECT_DIR,
364
- isThemesMigration: false,
365
328
  });
366
329
  });
367
330
  it('should prompt for project name when not provided', async () => {
@@ -12,18 +12,14 @@ 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 getHasMigratableThemes(projectConfig?: LoadedProjectConfig): Promise<{
16
- hasMigratableThemes: boolean;
17
- migratableThemesCount: number;
18
- }>;
19
15
  export declare function fetchMigrationApps(derivedAccountId: number, platformVersion: string, projectConfig?: LoadedProjectConfig): Promise<{
20
16
  migratableApps: MigratableApp[];
21
17
  unmigratableApps: UnmigratableApp[];
22
18
  }>;
23
- export declare function validateMigrationAppsAndThemes(appId: MigrateAppArgs['appId'], derivedAccountId: number, { migratableApps, unmigratableApps, }: {
19
+ export declare function validateMigrationApps(appId: MigrateAppArgs['appId'], derivedAccountId: number, { migratableApps, unmigratableApps, }: {
24
20
  migratableApps: MigratableApp[];
25
21
  unmigratableApps: UnmigratableApp[];
26
- }, hasMigratableThemes: boolean, projectConfig?: LoadedProjectConfig): Promise<void>;
22
+ }, projectConfig?: LoadedProjectConfig): Promise<void>;
27
23
  export declare function promptForAppToMigrate(allApps: MigrationApp[], derivedAccountId: number): Promise<number>;
28
24
  export declare function selectAppToMigrate(allApps: MigrationApp[], derivedAccountId: number, appId?: number): Promise<{
29
25
  proceed: boolean;
@@ -33,9 +29,7 @@ export declare function handleMigrationSetup(derivedAccountId: number, options:
33
29
  appIdToMigrate?: number | undefined;
34
30
  projectName?: string;
35
31
  projectDest?: string;
36
- isThemesMigration?: boolean;
37
32
  }>;
38
- export declare function handleThemesMigration(projectConfig: LoadedProjectConfig, platformVersion: string): Promise<void>;
39
33
  export declare function beginAppMigration(derivedAccountId: number, appId: number, platformVersion: string): Promise<{
40
34
  migrationId: number;
41
35
  uidMap: Record<string, string>;
@@ -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, migrateThemes, getProjectThemeDetails, } from '@hubspot/project-parsing-lib';
5
+ import { validateUid } 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,19 +10,15 @@ 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';
14
13
  import { ensureProjectExists } from '../projects/ensureProjectExists.js';
15
14
  import SpinniesManager from '../ui/SpinniesManager.js';
16
15
  import { DEFAULT_POLLING_STATUS_LOOKUP, poll } from '../polling.js';
17
16
  import { checkMigrationStatusV2, CLI_UNMIGRATABLE_REASONS, continueAppMigration, initializeAppMigration, isMigrationStatus, listAppsForMigration, } from '../../api/migrate.js';
18
17
  import fs from 'fs';
19
18
  import { lib } from '../../lang/en.js';
20
- import { PROJECT_CONFIG_FILE } from '../constants.js';
21
19
  import { hasUnfiedAppsAccess } from '../hasFeature.js';
22
20
  import { getProjectBuildDetailUrl, getProjectDetailUrl, } from '../projects/urls.js';
23
21
  import { uiLogger } from '../ui/logger.js';
24
- import { debugError } from '../errorHandlers/index.js';
25
- import { useV3Api } from '../projects/platformVersion.js';
26
22
  export function getUnmigratableReason(reasonCode, projectName, accountId) {
27
23
  switch (reasonCode) {
28
24
  case UNMIGRATABLE_REASONS.UP_TO_DATE:
@@ -59,17 +55,6 @@ export function buildErrorMessageFromMigrationStatus(error) {
59
55
  })
60
56
  .join('\n\t- ')}`;
61
57
  }
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
58
  export async function fetchMigrationApps(derivedAccountId, platformVersion, projectConfig) {
74
59
  const { data: { migratableApps, unmigratableApps }, } = await listAppsForMigration(derivedAccountId, platformVersion);
75
60
  const filteredMigratableApps = migratableApps.filter(generateFilterAppsByProjectNameFunction(projectConfig));
@@ -79,23 +64,11 @@ export async function fetchMigrationApps(derivedAccountId, platformVersion, proj
79
64
  unmigratableApps: filteredUnmigratableApps,
80
65
  };
81
66
  }
82
- export async function validateMigrationAppsAndThemes(appId, derivedAccountId, { migratableApps, unmigratableApps, }, hasMigratableThemes, projectConfig) {
67
+ export async function validateMigrationApps(appId, derivedAccountId, { migratableApps, unmigratableApps, }, projectConfig) {
83
68
  const allApps = [...migratableApps, ...unmigratableApps];
84
69
  if (allApps.length > 1 && projectConfig) {
85
70
  throw new Error(lib.migrate.errors.project.multipleApps);
86
71
  }
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
- }
99
72
  if (!projectConfig?.projectConfig) {
100
73
  allApps.forEach(app => {
101
74
  if (app.projectName) {
@@ -178,35 +151,20 @@ export async function handleMigrationSetup(derivedAccountId, options, projectCon
178
151
  text: lib.migrate.spinners.checkingForMigratableComponents,
179
152
  });
180
153
  const { name, dest, appId } = options;
181
- const { hasMigratableThemes, migratableThemesCount } = await getHasMigratableThemes(projectConfig);
182
154
  const { migratableApps, unmigratableApps } = await fetchMigrationApps(derivedAccountId, options.platformVersion, projectConfig);
183
155
  SpinniesManager.remove('checkingForMigratableComponents');
184
- await validateMigrationAppsAndThemes(appId, derivedAccountId, { migratableApps, unmigratableApps }, hasMigratableThemes, projectConfig);
156
+ await validateMigrationApps(appId, derivedAccountId, { migratableApps, unmigratableApps }, projectConfig);
185
157
  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
- }
158
+ const { proceed, appIdToMigrate } = await selectAppToMigrate(allApps, derivedAccountId, appId);
199
159
  if (!proceed) {
200
160
  return {};
201
161
  }
202
162
  // 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
204
163
  if (projectConfig &&
205
164
  projectConfig?.projectConfig &&
206
165
  projectConfig?.projectDir) {
207
166
  return {
208
167
  appIdToMigrate,
209
- isThemesMigration: hasMigratableThemes,
210
168
  projectName: projectConfig.projectConfig.name,
211
169
  projectDest: projectConfig.projectDir,
212
170
  };
@@ -231,35 +189,6 @@ export async function handleMigrationSetup(derivedAccountId, options, projectCon
231
189
  }));
232
190
  return { appIdToMigrate, projectName, projectDest };
233
191
  }
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
- let migrated = false;
240
- let failureReason;
241
- try {
242
- const migrationResult = await migrateThemes(projectConfig.projectDir, projectSrcDir);
243
- migrated = migrationResult.migrated;
244
- failureReason = migrationResult.failureReason;
245
- }
246
- catch (error) {
247
- debugError(error);
248
- throw new Error(lib.migrate.errors.project.failedToMigrateThemes);
249
- }
250
- if (!migrated) {
251
- throw new Error(failureReason || lib.migrate.errors.project.failedToMigrateThemes);
252
- }
253
- const newProjectConfig = { ...projectConfig.projectConfig };
254
- newProjectConfig.platformVersion = platformVersion;
255
- const projectConfigPath = path.join(projectConfig.projectDir, PROJECT_CONFIG_FILE);
256
- const success = writeProjectConfig(projectConfigPath, newProjectConfig);
257
- if (!success) {
258
- throw new Error(lib.migrate.errors.project.failedToUpdateProjectConfig);
259
- }
260
- uiLogger.log('');
261
- uiLogger.log(lib.migrate.success.themesMigrationSuccess(platformVersion));
262
- }
263
192
  export async function beginAppMigration(derivedAccountId, appId, platformVersion) {
264
193
  SpinniesManager.add('beginningMigration', {
265
194
  text: lib.migrate.spinners.beginningMigration,
@@ -412,11 +341,7 @@ export async function migrateApp2025_2(derivedAccountId, options, projectConfig)
412
341
  throw new Error(lib.migrate.errors.project.doesNotExist(derivedAccountId));
413
342
  }
414
343
  }
415
- const { appIdToMigrate, projectName, projectDest, isThemesMigration } = await handleMigrationSetup(derivedAccountId, options, projectConfig);
416
- if (isThemesMigration) {
417
- await handleThemesMigration(projectConfig, options.platformVersion);
418
- return;
419
- }
344
+ const { appIdToMigrate, projectName, projectDest } = await handleMigrationSetup(derivedAccountId, options, projectConfig);
420
345
  if (!appIdToMigrate || !projectName || !projectDest) {
421
346
  return;
422
347
  }
@@ -82,6 +82,8 @@ export declare const FEATURES: {
82
82
  readonly SANDBOXES_V2_CLI: "sandboxes:v2:cliEnabled";
83
83
  readonly APP_EVENTS: "Developers:UnifiedApps:AppEventsAccess";
84
84
  readonly APPS_HOME: "UIE:AppHome";
85
+ readonly MCP_ACCESS: "Developers:CLIMCPAccess";
86
+ readonly THEME_MIGRATION_2025_2: "Developers:ProjectThemeMigrations:2025.2";
85
87
  };
86
88
  export declare const LOCAL_DEV_UI_MESSAGE_SEND_TYPES: {
87
89
  UPLOAD_SUCCESS: string;
package/lib/constants.js CHANGED
@@ -74,6 +74,8 @@ export const FEATURES = {
74
74
  SANDBOXES_V2_CLI: 'sandboxes:v2:cliEnabled',
75
75
  APP_EVENTS: 'Developers:UnifiedApps:AppEventsAccess',
76
76
  APPS_HOME: 'UIE:AppHome',
77
+ MCP_ACCESS: 'Developers:CLIMCPAccess',
78
+ THEME_MIGRATION_2025_2: 'Developers:ProjectThemeMigrations:2025.2',
77
79
  };
78
80
  export const LOCAL_DEV_UI_MESSAGE_SEND_TYPES = {
79
81
  UPLOAD_SUCCESS: 'server:uploadSuccess',
package/lib/links.d.ts CHANGED
@@ -7,4 +7,5 @@ type SiteLink = {
7
7
  export declare function getSiteLinksAsArray(accountId: number): SiteLink[];
8
8
  export declare function logSiteLinks(accountId: number): void;
9
9
  export declare function openLink(accountId: number, shortcut: string): void;
10
+ export declare function getProductUpdatesUrl(rolloutId: string, accountId?: number): string;
10
11
  export {};
package/lib/links.js CHANGED
@@ -4,7 +4,7 @@ import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
4
4
  import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
5
5
  import { logger } from '@hubspot/local-dev-lib/logger';
6
6
  import { getTableContents, getTableHeader } from './ui/table.js';
7
- const SITE_LINKS = {
7
+ const COMMON_SITE_LINKS = {
8
8
  APPS_MARKETPLACE: {
9
9
  shortcut: 'apps-marketplace',
10
10
  alias: 'apm',
@@ -76,7 +76,7 @@ const SITE_LINKS = {
76
76
  };
77
77
  export function getSiteLinksAsArray(accountId) {
78
78
  const baseUrl = getHubSpotWebsiteOrigin(getEnv() === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD);
79
- return Object.values(SITE_LINKS)
79
+ return Object.values(COMMON_SITE_LINKS)
80
80
  .sort((a, b) => (a.shortcut < b.shortcut ? -1 : 1))
81
81
  .map(l => ({ ...l, url: l.getUrl(accountId, baseUrl) }));
82
82
  }
@@ -90,7 +90,7 @@ export function logSiteLinks(accountId) {
90
90
  logger.log(getTableContents(linksAsArray));
91
91
  }
92
92
  export function openLink(accountId, shortcut) {
93
- const match = Object.values(SITE_LINKS).find(l => l.shortcut === shortcut || (l.alias && l.alias === shortcut));
93
+ const match = Object.values(COMMON_SITE_LINKS).find(l => l.shortcut === shortcut || (l.alias && l.alias === shortcut));
94
94
  if (!match) {
95
95
  logger.error(`We couldn't find a shortcut matching ${shortcut}. Type 'hs open list' to see a list of available shortcuts`);
96
96
  return;
@@ -99,3 +99,10 @@ export function openLink(accountId, shortcut) {
99
99
  open(match.getUrl(accountId, baseUrl), { url: true });
100
100
  logger.success(`We opened ${match.getUrl(accountId, baseUrl)} in your browser`);
101
101
  }
102
+ export function getProductUpdatesUrl(rolloutId, accountId) {
103
+ const baseUrl = getHubSpotWebsiteOrigin(getEnv() === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD);
104
+ if (accountId) {
105
+ return `${baseUrl}/product-updates/${accountId}/in-beta?rollout=${rolloutId}`;
106
+ }
107
+ return `${baseUrl}/l/product-updates/in-beta?rollout=${rolloutId}`;
108
+ }
@@ -0,0 +1 @@
1
+ export {};