@hubspot/cli 7.8.0-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.
Files changed (77) hide show
  1. package/bin/cli.js +0 -2
  2. package/commands/getStarted.d.ts +1 -1
  3. package/commands/getStarted.js +64 -16
  4. package/commands/mcp/setup.js +8 -0
  5. package/commands/project/dev/unifiedFlow.js +1 -1
  6. package/commands/project/migrate.js +30 -21
  7. package/lang/en.d.ts +4 -1
  8. package/lang/en.js +5 -1
  9. package/lib/__tests__/hasFeature.test.js +145 -7
  10. package/lib/app/__tests__/migrate.test.js +14 -51
  11. package/lib/app/migrate.d.ts +2 -8
  12. package/lib/app/migrate.js +5 -80
  13. package/lib/constants.d.ts +3 -0
  14. package/lib/constants.js +3 -0
  15. package/lib/dependencyManagement.d.ts +0 -5
  16. package/lib/dependencyManagement.js +0 -9
  17. package/lib/hasFeature.js +6 -0
  18. package/lib/links.d.ts +1 -0
  19. package/lib/links.js +10 -3
  20. package/lib/mcp/setup.js +1 -1
  21. package/lib/projects/create/v3.js +3 -2
  22. package/lib/projects/localDev/helpers/project.d.ts +2 -2
  23. package/lib/projects/localDev/helpers/project.js +5 -6
  24. package/lib/theme/__tests__/migrate.test.d.ts +1 -0
  25. package/lib/theme/__tests__/migrate.test.js +233 -0
  26. package/lib/theme/migrate.d.ts +13 -0
  27. package/lib/theme/migrate.js +90 -0
  28. package/lib/ui/SpinniesManager.js +105 -8
  29. package/lib/usageTracking.js +2 -2
  30. package/mcp-server/tools/cms/HsCreateFunctionTool.js +1 -1
  31. package/mcp-server/tools/cms/HsCreateModuleTool.js +1 -1
  32. package/mcp-server/tools/cms/HsCreateTemplateTool.js +1 -1
  33. package/mcp-server/tools/cms/HsFunctionLogsTool.js +2 -2
  34. package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
  35. package/mcp-server/tools/cms/HsListTool.js +1 -1
  36. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -1
  37. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +1 -1
  38. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +1 -1
  39. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +2 -2
  40. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +1 -1
  41. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +1 -1
  42. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +3 -3
  43. package/mcp-server/tools/project/AddFeatureToProjectTool.js +3 -3
  44. package/mcp-server/tools/project/CreateProjectTool.d.ts +3 -3
  45. package/mcp-server/tools/project/CreateProjectTool.js +5 -5
  46. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  47. package/mcp-server/tools/project/DocFetchTool.js +2 -2
  48. package/mcp-server/tools/project/DocsSearchTool.d.ts +4 -1
  49. package/mcp-server/tools/project/DocsSearchTool.js +7 -7
  50. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +4 -1
  51. package/mcp-server/tools/project/GetConfigValuesTool.js +11 -5
  52. package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -1
  53. package/mcp-server/tools/project/UploadProjectTools.js +2 -2
  54. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  55. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +1 -1
  56. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -1
  57. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +1 -1
  58. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +2 -2
  59. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +14 -12
  60. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +9 -8
  61. package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +1 -1
  62. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +1 -1
  63. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +1 -1
  64. package/mcp-server/tools/project/constants.d.ts +1 -1
  65. package/mcp-server/tools/project/constants.js +9 -3
  66. package/mcp-server/utils/__tests__/cliConfig.test.d.ts +1 -0
  67. package/mcp-server/utils/__tests__/cliConfig.test.js +110 -0
  68. package/mcp-server/utils/cliConfig.d.ts +1 -0
  69. package/mcp-server/utils/cliConfig.js +12 -0
  70. package/package.json +2 -7
  71. package/ui/components/HorizontalSelectPrompt.js +1 -1
  72. package/commands/getStartedV2.d.ts +0 -9
  73. package/commands/getStartedV2.js +0 -39
  74. package/ui/components/Ascii.d.ts +0 -10
  75. package/ui/components/Ascii.js +0 -11
  76. package/ui/views/GetStarted.d.ts +0 -7
  77. package/ui/views/GetStarted.js +0 -157
@@ -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
  }
@@ -81,6 +81,9 @@ export declare const FEATURES: {
81
81
  readonly SANDBOXES_V2: "sandboxes:v2:enabled";
82
82
  readonly SANDBOXES_V2_CLI: "sandboxes:v2:cliEnabled";
83
83
  readonly APP_EVENTS: "Developers:UnifiedApps:AppEventsAccess";
84
+ readonly APPS_HOME: "UIE:AppHome";
85
+ readonly MCP_ACCESS: "Developers:CLIMCPAccess";
86
+ readonly THEME_MIGRATION_2025_2: "Developers:ProjectThemeMigrations:2025.2";
84
87
  };
85
88
  export declare const LOCAL_DEV_UI_MESSAGE_SEND_TYPES: {
86
89
  UPLOAD_SUCCESS: string;
package/lib/constants.js CHANGED
@@ -73,6 +73,9 @@ export const FEATURES = {
73
73
  SANDBOXES_V2: 'sandboxes:v2:enabled',
74
74
  SANDBOXES_V2_CLI: 'sandboxes:v2:cliEnabled',
75
75
  APP_EVENTS: 'Developers:UnifiedApps:AppEventsAccess',
76
+ APPS_HOME: 'UIE:AppHome',
77
+ MCP_ACCESS: 'Developers:CLIMCPAccess',
78
+ THEME_MIGRATION_2025_2: 'Developers:ProjectThemeMigrations:2025.2',
76
79
  };
77
80
  export const LOCAL_DEV_UI_MESSAGE_SEND_TYPES = {
78
81
  UPLOAD_SUCCESS: 'server:uploadSuccess',
@@ -2,10 +2,5 @@ export declare function installPackages({ packages, installLocations, }: {
2
2
  packages?: string[];
3
3
  installLocations?: string[];
4
4
  }): Promise<void>;
5
- export declare function installPackagesV2({ packages, installLocations, }: {
6
- packages?: string[];
7
- installLocations?: string[];
8
- }): Promise<void>;
9
- export declare function installPackagesInDirectoryV2(directory: string, packages?: string[]): Promise<void>;
10
5
  export declare function getProjectPackageJsonLocations(dir?: string): Promise<string[]>;
11
6
  export declare function hasMissingPackages(directory: string): Promise<boolean>;
@@ -22,15 +22,6 @@ export async function installPackages({ packages, installLocations, }) {
22
22
  await installPackagesInDirectory(dir, packages);
23
23
  }));
24
24
  }
25
- export async function installPackagesV2({ packages, installLocations, }) {
26
- const installDirs = installLocations || (await getProjectPackageJsonLocations());
27
- await Promise.all(installDirs.map(async (dir) => {
28
- await installPackagesInDirectoryV2(dir, packages);
29
- }));
30
- }
31
- export async function installPackagesInDirectoryV2(directory, packages) {
32
- await executeInstall(packages, null, { cwd: directory });
33
- }
34
25
  async function installPackagesInDirectory(directory, packages) {
35
26
  const spinner = `installingDependencies-${directory}`;
36
27
  const relativeDir = path.relative(process.cwd(), directory);
package/lib/hasFeature.js CHANGED
@@ -1,7 +1,13 @@
1
1
  import { http } from '@hubspot/local-dev-lib/http';
2
2
  import { fetchEnabledFeatures } from '@hubspot/local-dev-lib/api/localDevAuth';
3
+ import { FEATURES } from './constants.js';
4
+ const FEATURES_THAT_DEFAULT_ON = [FEATURES.APPS_HOME];
3
5
  export async function hasFeature(accountId, feature) {
4
6
  const { data: { enabledFeatures }, } = await fetchEnabledFeatures(accountId);
7
+ if (enabledFeatures[feature] === undefined &&
8
+ FEATURES_THAT_DEFAULT_ON.includes(feature)) {
9
+ return true;
10
+ }
5
11
  return Boolean(enabledFeatures[feature]);
6
12
  }
7
13
  export async function hasUnfiedAppsAccess(accountId) {
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
+ }
package/lib/mcp/setup.js CHANGED
@@ -8,7 +8,7 @@ import path from 'path';
8
8
  import os from 'os';
9
9
  import fs from 'fs-extra';
10
10
  import { existsSync } from 'fs';
11
- const mcpServerName = 'hubspot-cli-mcp';
11
+ const mcpServerName = 'HubSpot';
12
12
  const claudeCode = 'claude';
13
13
  const windsurf = 'windsurf';
14
14
  const cursor = 'cursor';
@@ -9,7 +9,7 @@ import { getConfigForPlatformVersion } from './legacy.js';
9
9
  import { logError } from '../../errorHandlers/index.js';
10
10
  import { EXIT_CODES } from '../../enums/exitCodes.js';
11
11
  import { hasFeature } from '../../hasFeature.js';
12
- import { AppEventsKey } from '@hubspot/project-parsing-lib/src/lib/constants.js';
12
+ import { AppEventsKey, PagesKey, } from '@hubspot/project-parsing-lib/src/lib/constants.js';
13
13
  export async function createV3App(providedAuth, providedDistribution) {
14
14
  let authType;
15
15
  if (providedAuth &&
@@ -51,6 +51,7 @@ export async function createV3App(providedAuth, providedDistribution) {
51
51
  }
52
52
  const componentTypeToGateMap = {
53
53
  [AppEventsKey]: FEATURES.APP_EVENTS,
54
+ [PagesKey]: FEATURES.APPS_HOME,
54
55
  };
55
56
  export async function calculateComponentTemplateChoices(components, authType, distribution, accountId, projectMetadata) {
56
57
  const enabledComponents = [];
@@ -88,7 +89,7 @@ export async function calculateComponentTemplateChoices(components, authType, di
88
89
  }
89
90
  if (disabledReasons.length > 0) {
90
91
  disabledComponents.push({
91
- name: `[${chalk.yellow('DISABLED')}] ${template.label}`,
92
+ name: `[${chalk.yellow('DISABLED')}] ${template.label} -`,
92
93
  value: template,
93
94
  disabled: disabledReasons.join(' '),
94
95
  });
@@ -6,7 +6,7 @@ export declare function createNewProjectForLocalDev(projectConfig: ProjectConfig
6
6
  export declare function createInitialBuildForNewProject(projectConfig: ProjectConfig, projectDir: string, targetAccountId: number, sendIR?: boolean, profile?: string): Promise<Build>;
7
7
  export declare function compareLocalProjectToDeployed(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number | undefined, localProjectNodes: {
8
8
  [key: string]: IntermediateRepresentationNodeLocalDev;
9
- }): Promise<void>;
9
+ }, profile?: string): Promise<void>;
10
10
  export declare function isDeployedProjectUpToDateWithLocal(projectConfig: ProjectConfig, accountId: number, deployedBuildId: number, localProjectNodes: {
11
11
  [key: string]: IntermediateRepresentationNodeLocalDev;
12
- }): Promise<boolean>;
12
+ }, profile?: string): Promise<boolean>;
@@ -123,7 +123,7 @@ export async function createInitialBuildForNewProject(projectConfig, projectDir,
123
123
  }
124
124
  return initialUploadResult.buildResult;
125
125
  }
126
- export async function compareLocalProjectToDeployed(projectConfig, accountId, deployedBuildId, localProjectNodes) {
126
+ export async function compareLocalProjectToDeployed(projectConfig, accountId, deployedBuildId, localProjectNodes, profile) {
127
127
  uiLogger.log('');
128
128
  if (!deployedBuildId) {
129
129
  uiLogger.error(lib.localDevHelpers.project.compareLocalProjectToDeployed.noDeployedBuild(projectConfig.name, uiAccountDescription(accountId)));
@@ -132,7 +132,7 @@ export async function compareLocalProjectToDeployed(projectConfig, accountId, de
132
132
  SpinniesManager.add('compareLocalProjectToDeployed', {
133
133
  text: lib.localDevHelpers.project.compareLocalProjectToDeployed.checking,
134
134
  });
135
- const isUpToDate = await isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes);
135
+ const isUpToDate = await isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes, profile);
136
136
  if (isUpToDate) {
137
137
  SpinniesManager.succeed('compareLocalProjectToDeployed', {
138
138
  text: lib.localDevHelpers.project.compareLocalProjectToDeployed.upToDate,
@@ -144,12 +144,11 @@ export async function compareLocalProjectToDeployed(projectConfig, accountId, de
144
144
  .notUpToDate,
145
145
  });
146
146
  uiLogger.log('');
147
- uiLogger.log(lib.localDevHelpers.project.compareLocalProjectToDeployed
148
- .notUpToDateExplanation);
147
+ uiLogger.log(lib.localDevHelpers.project.compareLocalProjectToDeployed.notUpToDateExplanation(profile));
149
148
  process.exit(EXIT_CODES.SUCCESS);
150
149
  }
151
150
  }
152
- export async function isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes) {
151
+ export async function isDeployedProjectUpToDateWithLocal(projectConfig, accountId, deployedBuildId, localProjectNodes, profile) {
153
152
  let tempDir = null;
154
153
  try {
155
154
  tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hubspot-project-compare-'));
@@ -161,7 +160,7 @@ export async function isDeployedProjectUpToDateWithLocal(projectConfig, accountI
161
160
  projectSourceDir: deployedProjectSourceDir,
162
161
  platformVersion: projectConfig.platformVersion,
163
162
  accountId: accountId,
164
- }, {});
163
+ }, { profile });
165
164
  return isDeepEqual(localProjectNodes, deployedProjectNodes, ['localDev']);
166
165
  }
167
166
  finally {
@@ -0,0 +1 @@
1
+ export {};