@hubspot/cli 7.7.32-experimental.0 → 7.7.33-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/commands/getStarted.js +5 -4
  2. package/commands/project/__tests__/add.test.js +3 -5
  3. package/commands/project/__tests__/deploy.test.js +3 -2
  4. package/commands/project/add.js +2 -4
  5. package/commands/project/deploy.js +9 -61
  6. package/commands/project/dev/index.js +1 -1
  7. package/commands/project/dev/unifiedFlow.js +3 -0
  8. package/commands/project/upload.js +2 -2
  9. package/commands/project/validate.js +1 -1
  10. package/commands/project/watch.js +2 -2
  11. package/lang/en.d.ts +7 -3
  12. package/lang/en.js +8 -4
  13. package/lib/__tests__/hasFeature.test.js +145 -7
  14. package/lib/__tests__/importData.test.js +1 -1
  15. package/lib/app/migrate.js +9 -2
  16. package/lib/constants.d.ts +2 -0
  17. package/lib/constants.js +2 -0
  18. package/lib/errorHandlers/index.d.ts +4 -0
  19. package/lib/errorHandlers/index.js +1 -1
  20. package/lib/hasFeature.js +6 -0
  21. package/lib/importData.js +1 -1
  22. package/lib/projects/__tests__/AppDevModeInterface.test.js +61 -44
  23. package/lib/projects/__tests__/LocalDevProcess.test.js +1 -0
  24. package/lib/projects/__tests__/deploy.test.js +164 -0
  25. package/lib/projects/__tests__/platformVersion.test.d.ts +1 -0
  26. package/lib/projects/__tests__/{buildAndDeploy.test.js → platformVersion.test.js} +2 -2
  27. package/lib/projects/add/__tests__/legacyAddComponent.test.js +49 -6
  28. package/lib/projects/add/__tests__/v3AddComponent.test.js +71 -1
  29. package/lib/projects/add/legacyAddComponent.d.ts +1 -1
  30. package/lib/projects/add/legacyAddComponent.js +5 -1
  31. package/lib/projects/add/v3AddComponent.d.ts +1 -0
  32. package/lib/projects/add/v3AddComponent.js +2 -2
  33. package/lib/projects/create/__tests__/v3.test.js +97 -9
  34. package/lib/projects/create/index.js +2 -2
  35. package/lib/projects/create/legacy.js +1 -1
  36. package/lib/projects/create/v3.d.ts +2 -2
  37. package/lib/projects/create/v3.js +35 -12
  38. package/lib/projects/deploy.d.ts +13 -0
  39. package/lib/projects/deploy.js +63 -0
  40. package/lib/projects/localDev/AppDevModeInterface.d.ts +0 -2
  41. package/lib/projects/localDev/AppDevModeInterface.js +65 -36
  42. package/lib/projects/localDev/DevServerManagerV2.js +1 -0
  43. package/lib/projects/localDev/LocalDevProcess.js +3 -1
  44. package/lib/projects/localDev/LocalDevState.d.ts +5 -2
  45. package/lib/projects/localDev/LocalDevState.js +9 -1
  46. package/lib/projects/localDev/helpers/project.js +1 -1
  47. package/lib/projects/platformVersion.d.ts +1 -0
  48. package/lib/projects/platformVersion.js +10 -0
  49. package/lib/projects/{buildAndDeploy.d.ts → pollProjectBuildAndDeploy.d.ts} +0 -1
  50. package/lib/projects/{buildAndDeploy.js → pollProjectBuildAndDeploy.js} +0 -10
  51. package/lib/projects/upload.js +1 -1
  52. package/lib/projects/urls.d.ts +1 -0
  53. package/lib/projects/urls.js +3 -0
  54. package/lib/prompts/__tests__/projectAddPrompt.test.d.ts +1 -0
  55. package/lib/prompts/__tests__/projectAddPrompt.test.js +143 -0
  56. package/lib/prompts/__tests__/selectProjectTemplatePrompt.test.d.ts +1 -0
  57. package/lib/prompts/__tests__/selectProjectTemplatePrompt.test.js +160 -0
  58. package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +1 -0
  59. package/lib/prompts/importDataFilePathPrompt.js +4 -2
  60. package/lib/prompts/installAppPrompt.d.ts +6 -1
  61. package/lib/prompts/installAppPrompt.js +6 -1
  62. package/lib/prompts/projectAddPrompt.js +1 -1
  63. package/lib/prompts/selectProjectTemplatePrompt.js +1 -1
  64. package/mcp-server/tools/cms/HsFunctionLogsTool.d.ts +32 -0
  65. package/mcp-server/tools/cms/HsFunctionLogsTool.js +76 -0
  66. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.d.ts +1 -0
  67. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +183 -0
  68. package/mcp-server/tools/index.js +2 -0
  69. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +3 -3
  70. package/mcp-server/tools/project/CreateProjectTool.d.ts +3 -3
  71. package/mcp-server/tools/project/GetConfigValuesTool.js +3 -3
  72. package/mcp-server/tools/project/constants.d.ts +1 -1
  73. package/mcp-server/tools/project/constants.js +6 -4
  74. package/package.json +3 -3
  75. package/types/LocalDev.d.ts +2 -1
  76. package/types/Projects.d.ts +1 -0
  77. /package/lib/projects/__tests__/{buildAndDeploy.test.d.ts → deploy.test.d.ts} +0 -0
@@ -8,7 +8,8 @@ import path from 'path';
8
8
  import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, DEFAULT_PROJECT_TEMPLATE_BRANCH, } from '../../constants.js';
9
9
  import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
10
10
  import { uiLogger } from '../../ui/logger.js';
11
- export async function legacyAddComponent(args, projectDir, projectConfig) {
11
+ import { trackCommandUsage } from '../../usageTracking.js';
12
+ export async function legacyAddComponent(args, projectDir, projectConfig, derivedAccountId) {
12
13
  // We currently only support adding private apps to projects
13
14
  let projectContainsPublicApp = false;
14
15
  try {
@@ -27,6 +28,9 @@ export async function legacyAddComponent(args, projectDir, projectConfig) {
27
28
  throw new Error(commands.project.add.error.failedToFetchComponentList);
28
29
  }
29
30
  const projectAddPromptResponse = await projectAddPrompt(components, args);
31
+ trackCommandUsage('project-add', {
32
+ type: projectAddPromptResponse.componentTemplate.type,
33
+ }, derivedAccountId);
30
34
  try {
31
35
  const componentPath = path.join(projectDir, projectConfig.srcDir, projectAddPromptResponse.name);
32
36
  await cloneGithubRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, componentPath, {
@@ -5,4 +5,5 @@ export declare function v3AddComponent(args: {
5
5
  features?: string[];
6
6
  auth?: string;
7
7
  distribution?: string;
8
+ derivedAccountId: number;
8
9
  }, projectDir: string, projectConfig: ProjectConfig, accountId: number): Promise<void>;
@@ -46,9 +46,9 @@ export async function v3AddComponent(args, projectDir, projectConfig, accountId)
46
46
  derivedDistribution = apps[0].config?.distribution;
47
47
  derivedAuthType = apps[0].config?.auth?.type;
48
48
  }
49
- const componentTemplateChoices = calculateComponentTemplateChoices(components, derivedAuthType, derivedDistribution, currentProjectMetadata);
49
+ const componentTemplateChoices = await calculateComponentTemplateChoices(components, derivedAuthType, derivedDistribution, args.derivedAccountId, currentProjectMetadata);
50
50
  const projectAddPromptResponse = await projectAddPromptV3(componentTemplateChoices, args.features);
51
- const componentTypes = projectAddPromptResponse.componentTemplate?.map(componentTemplate => componentTemplate.type);
51
+ const componentTypes = projectAddPromptResponse.componentTemplate?.map(componentTemplate => componentTemplate.cliSelector || componentTemplate.type);
52
52
  await trackCommandUsage('project-add', {
53
53
  type: componentTypes?.join(','),
54
54
  }, accountId);
@@ -26,9 +26,9 @@ describe('lib/projects/create/v3', () => {
26
26
  card: { count: 3, maxCount: 3, hsMetaFiles: [] },
27
27
  },
28
28
  };
29
- it('returns enabled components when they meet all requirements', () => {
30
- const choices = calculateComponentTemplateChoices(mockComponents, 'oauth', 'private', mockProjectMetadataForChoices);
31
- expect(choices).toHaveLength(3); // includes separator
29
+ it('returns enabled components when they meet all requirements', async () => {
30
+ const choices = await calculateComponentTemplateChoices(mockComponents, 'oauth', 'private', 123, mockProjectMetadataForChoices);
31
+ expect(choices).toHaveLength(4); // includes separator
32
32
  expect(choices[0]).toEqual({
33
33
  name: 'Module Component',
34
34
  value: mockComponents[0],
@@ -39,8 +39,8 @@ describe('lib/projects/create/v3', () => {
39
39
  disabled: expect.stringContaining('maximum'),
40
40
  });
41
41
  });
42
- it('disables components when auth type is not supported', () => {
43
- const choices = calculateComponentTemplateChoices(mockComponents, 'privatekey', 'private', mockProjectMetadataForChoices);
42
+ it('disables components when auth type is not supported', async () => {
43
+ const choices = await calculateComponentTemplateChoices(mockComponents, 'privatekey', 'private', 123, mockProjectMetadataForChoices);
44
44
  // All components should be disabled, so they come after the separator
45
45
  expect(choices[1]).toEqual({
46
46
  name: expect.stringContaining('Module Component'),
@@ -48,8 +48,8 @@ describe('lib/projects/create/v3', () => {
48
48
  disabled: expect.stringContaining('privatekey'),
49
49
  });
50
50
  });
51
- it('disables components when distribution is not supported', () => {
52
- const choices = calculateComponentTemplateChoices(mockComponents, 'oauth', 'enterprise', mockProjectMetadataForChoices);
51
+ it('disables components when distribution is not supported', async () => {
52
+ const choices = await calculateComponentTemplateChoices(mockComponents, 'oauth', 'enterprise', 123, mockProjectMetadataForChoices);
53
53
  // All components should be disabled, so they come after the separator
54
54
  expect(choices[1]).toEqual({
55
55
  name: expect.stringContaining('Module Component'),
@@ -57,7 +57,7 @@ describe('lib/projects/create/v3', () => {
57
57
  disabled: expect.stringContaining('enterprise'),
58
58
  });
59
59
  });
60
- it('handles components without auth type or distribution restrictions', () => {
60
+ it('handles components without auth type or distribution restrictions', async () => {
61
61
  const componentsWithoutRestrictions = [
62
62
  {
63
63
  label: 'Unrestricted Component',
@@ -65,7 +65,7 @@ describe('lib/projects/create/v3', () => {
65
65
  type: 'module',
66
66
  },
67
67
  ];
68
- const choices = calculateComponentTemplateChoices(componentsWithoutRestrictions, 'oauth', 'private', {
68
+ const choices = await calculateComponentTemplateChoices(componentsWithoutRestrictions, 'oauth', 'private', 123, {
69
69
  hsMetaFiles: [],
70
70
  components: { module: { count: 0, maxCount: 5, hsMetaFiles: [] } },
71
71
  });
@@ -74,5 +74,93 @@ describe('lib/projects/create/v3', () => {
74
74
  value: componentsWithoutRestrictions[0],
75
75
  });
76
76
  });
77
+ it('handles components with cliSelector field (metadata compatibility)', async () => {
78
+ const componentWithCliSelector = [
79
+ {
80
+ label: 'Workflow Action Tool',
81
+ path: 'workflow-action-tool',
82
+ type: 'workflow-action',
83
+ cliSelector: 'workflow-action-tool',
84
+ supportedAuthTypes: ['oauth'],
85
+ supportedDistributions: ['private'],
86
+ },
87
+ ];
88
+ const projectMetadataWithWorkflowAction = {
89
+ hsMetaFiles: [],
90
+ components: {
91
+ 'workflow-action': { count: 2, maxCount: 3, hsMetaFiles: [] },
92
+ },
93
+ };
94
+ const choices = await calculateComponentTemplateChoices(componentWithCliSelector, 'oauth', 'private', 213, projectMetadataWithWorkflowAction);
95
+ expect(choices).toHaveLength(1); // no disabled components
96
+ expect(choices[0]).toEqual({
97
+ name: 'Workflow Action Tool',
98
+ value: componentWithCliSelector[0],
99
+ });
100
+ });
101
+ it('disables component when project metadata count exceeds maximum', async () => {
102
+ const componentWithCliSelector = [
103
+ {
104
+ label: 'Workflow Action Tool',
105
+ path: 'workflow-action-tool',
106
+ type: 'workflow-action',
107
+ cliSelector: 'workflow-action-tool',
108
+ supportedAuthTypes: ['oauth'],
109
+ supportedDistributions: ['private'],
110
+ },
111
+ ];
112
+ const projectMetadataAtMaxWorkflowAction = {
113
+ hsMetaFiles: [],
114
+ components: {
115
+ 'workflow-action': { count: 3, maxCount: 3, hsMetaFiles: [] },
116
+ },
117
+ };
118
+ const choices = await calculateComponentTemplateChoices(componentWithCliSelector, 'oauth', 'private', 123, projectMetadataAtMaxWorkflowAction);
119
+ expect(choices).toHaveLength(3); // includes separators
120
+ expect(choices[1]).toEqual({
121
+ name: expect.stringContaining('Workflow Action Tool'),
122
+ value: componentWithCliSelector[0],
123
+ disabled: expect.stringContaining('maximum'),
124
+ });
125
+ });
126
+ it('handles undefined projectMetadata without throwing errors', async () => {
127
+ const componentWithCliSelector = [
128
+ {
129
+ label: 'Workflow Action Tool',
130
+ path: 'workflow-action-tool',
131
+ type: 'workflow-action',
132
+ cliSelector: 'workflow-action-tool',
133
+ supportedAuthTypes: ['oauth'],
134
+ supportedDistributions: ['private'],
135
+ },
136
+ ];
137
+ const choices = await calculateComponentTemplateChoices(componentWithCliSelector, 'oauth', 'private', 123, undefined);
138
+ expect(choices).toHaveLength(1); // no disabled components
139
+ expect(choices[0]).toEqual({
140
+ name: 'Workflow Action Tool',
141
+ value: componentWithCliSelector[0],
142
+ });
143
+ });
144
+ it('handles projectMetadata with undefined components property (after fix)', async () => {
145
+ const componentWithCliSelector = [
146
+ {
147
+ label: 'Workflow Action Tool',
148
+ path: 'workflow-action-tool',
149
+ type: 'workflow-action',
150
+ cliSelector: 'workflow-action-tool',
151
+ supportedAuthTypes: ['oauth'],
152
+ supportedDistributions: ['private'],
153
+ },
154
+ ];
155
+ const projectMetadataWithoutComponents = {
156
+ hsMetaFiles: [],
157
+ components: undefined,
158
+ };
159
+ // This test verifies the null check fix works
160
+ // Currently this will fail because the fix checks for projectMetadata.components
161
+ await expect(async () => calculateComponentTemplateChoices(componentWithCliSelector, 'oauth', 'private', 123,
162
+ // @ts-expect-error breaking stuff on purpose
163
+ projectMetadataWithoutComponents)).rejects.toThrow();
164
+ });
77
165
  });
78
166
  });
@@ -1,7 +1,7 @@
1
1
  import { selectProjectTemplatePrompt, } from '../../prompts/selectProjectTemplatePrompt.js';
2
2
  import { projectNameAndDestPrompt } from '../../prompts/projectNameAndDestPrompt.js';
3
3
  import { DEFAULT_PROJECT_TEMPLATE_BRANCH, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, EMPTY_PROJECT, } from '../../constants.js';
4
- import { useV3Api } from '../buildAndDeploy.js';
4
+ import { useV3Api } from '../platformVersion.js';
5
5
  import { v3ComponentFlow } from './v3.js';
6
6
  import { getProjectTemplateListFromRepo } from './legacy.js';
7
7
  import { uiLogger } from '../../ui/logger.js';
@@ -12,7 +12,7 @@ export async function handleProjectCreationFlow(args) {
12
12
  const repo = templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH;
13
13
  const projectNameAndDestPromptResponse = await projectNameAndDestPrompt(args);
14
14
  if (useV3Api(platformVersion)) {
15
- const { componentTemplateChoices, authType, distribution, repoConfig, projectContents, } = await v3ComponentFlow(platformVersion, projectBase, providedAuth, providedDistribution);
15
+ const { componentTemplateChoices, authType, distribution, repoConfig, projectContents, } = await v3ComponentFlow(platformVersion, projectBase, providedAuth, providedDistribution, args.derivedAccountId);
16
16
  const selectProjectTemplatePromptResponse = await selectProjectTemplatePrompt(args, undefined, projectContents !== EMPTY_PROJECT ? componentTemplateChoices : undefined);
17
17
  return {
18
18
  authType,
@@ -3,7 +3,7 @@ import { DEFAULT_PROJECT_TEMPLATE_BRANCH, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH
3
3
  import { EXIT_CODES } from '../../enums/exitCodes.js';
4
4
  import { debugError } from '../../errorHandlers/index.js';
5
5
  import { uiLogger } from '../../ui/logger.js';
6
- import { useV3Api } from '../buildAndDeploy.js';
6
+ import { useV3Api } from '../platformVersion.js';
7
7
  import { lib } from '../../../lang/en.js';
8
8
  const PROJECT_TEMPLATE_PROPERTIES = ['name', 'label', 'path'];
9
9
  export const EMPTY_PROJECT_TEMPLATE_NAME = 'no-template';
@@ -6,7 +6,7 @@ export declare function createV3App(providedAuth: string | undefined, providedDi
6
6
  authType: string;
7
7
  distribution: string;
8
8
  }>;
9
- export declare function calculateComponentTemplateChoices(components: ComponentTemplate[], authType: string | undefined, distribution: string | undefined, projectMetadata?: ProjectMetadata): (ComponentTemplateChoice | Separator)[];
9
+ export declare function calculateComponentTemplateChoices(components: ComponentTemplate[], authType: string | undefined, distribution: string | undefined, accountId: number, projectMetadata?: ProjectMetadata): Promise<(ComponentTemplateChoice | Separator)[]>;
10
10
  type V3ComponentInfo = {
11
11
  authType?: string;
12
12
  distribution?: string;
@@ -14,7 +14,7 @@ type V3ComponentInfo = {
14
14
  projectContents?: string;
15
15
  componentTemplateChoices?: (ComponentTemplateChoice | Separator)[];
16
16
  };
17
- export declare function v3ComponentFlow(platformVersion: string, projectBase: string | undefined, providedAuth: string | undefined, providedDistribution: string | undefined): Promise<V3ComponentInfo>;
17
+ export declare function v3ComponentFlow(platformVersion: string, projectBase: string | undefined, providedAuth: string | undefined, providedDistribution: string | undefined, accountId: number): Promise<V3ComponentInfo>;
18
18
  export declare function generateComponentPaths({ selectProjectTemplatePromptResponse, platformVersion, repoConfig, projectContents, authType, distribution, }: {
19
19
  selectProjectTemplatePromptResponse: SelectProjectTemplatePromptResponse;
20
20
  platformVersion: string;
@@ -1,13 +1,15 @@
1
1
  import { Separator } from '@inquirer/prompts';
2
- import { marketplaceDistribution, oAuth, privateDistribution, staticAuth, EMPTY_PROJECT, PROJECT_WITH_APP, } from '../../constants.js';
2
+ import { marketplaceDistribution, oAuth, privateDistribution, staticAuth, EMPTY_PROJECT, PROJECT_WITH_APP, FEATURES, } from '../../constants.js';
3
3
  import { commands, lib } from '../../../lang/en.js';
4
4
  import { listPrompt } from '../../prompts/promptUtils.js';
5
5
  import chalk from 'chalk';
6
- import { useV3Api } from '../buildAndDeploy.js';
6
+ import { useV3Api } from '../platformVersion.js';
7
7
  import path from 'path';
8
8
  import { getConfigForPlatformVersion } from './legacy.js';
9
9
  import { logError } from '../../errorHandlers/index.js';
10
10
  import { EXIT_CODES } from '../../enums/exitCodes.js';
11
+ import { hasFeature } from '../../hasFeature.js';
12
+ import { AppEventsKey, PagesKey, } from '@hubspot/project-parsing-lib/src/lib/constants.js';
11
13
  export async function createV3App(providedAuth, providedDistribution) {
12
14
  let authType;
13
15
  if (providedAuth &&
@@ -47,16 +49,26 @@ export async function createV3App(providedAuth, providedDistribution) {
47
49
  authType: authType,
48
50
  };
49
51
  }
50
- export function calculateComponentTemplateChoices(components, authType, distribution, projectMetadata) {
52
+ const componentTypeToGateMap = {
53
+ [AppEventsKey]: FEATURES.APP_EVENTS,
54
+ [PagesKey]: FEATURES.APPS_HOME,
55
+ };
56
+ export async function calculateComponentTemplateChoices(components, authType, distribution, accountId, projectMetadata) {
51
57
  const enabledComponents = [];
52
58
  const disabledComponents = [];
53
- components.forEach(template => {
59
+ for (const template of components) {
54
60
  const { supportedAuthTypes, supportedDistributions } = template;
55
61
  const disabledReasons = [];
56
62
  if (projectMetadata) {
57
- const { count, maxCount } = projectMetadata.components[template.type];
58
- if (count >= maxCount) {
59
- disabledReasons.push(commands.project.add.error.maxExceeded(maxCount));
63
+ const componentMetadata = projectMetadata.components[template.type];
64
+ if (!componentMetadata) {
65
+ disabledReasons.push(commands.project.add.error.invalidComponentType(template.type));
66
+ }
67
+ else {
68
+ const { count, maxCount } = componentMetadata;
69
+ if (count >= maxCount) {
70
+ disabledReasons.push(commands.project.add.error.maxExceeded(maxCount));
71
+ }
60
72
  }
61
73
  }
62
74
  if (Array.isArray(supportedAuthTypes) &&
@@ -69,9 +81,15 @@ export function calculateComponentTemplateChoices(components, authType, distribu
69
81
  !supportedDistributions.includes(distribution)) {
70
82
  disabledReasons.push(commands.project.add.error.distributionNotAllowed(distribution));
71
83
  }
84
+ if (componentTypeToGateMap[template.type]) {
85
+ const isUngated = await hasFeature(accountId, componentTypeToGateMap[template.type]);
86
+ if (!isUngated) {
87
+ disabledReasons.push(commands.project.add.error.portalDoesNotHaveAccessToThisFeature(accountId));
88
+ }
89
+ }
72
90
  if (disabledReasons.length > 0) {
73
91
  disabledComponents.push({
74
- name: `[${chalk.yellow('DISABLED')}] ${template.label}`,
92
+ name: `[${chalk.yellow('DISABLED')}] ${template.label} -`,
75
93
  value: template,
76
94
  disabled: disabledReasons.join(' '),
77
95
  });
@@ -82,12 +100,17 @@ export function calculateComponentTemplateChoices(components, authType, distribu
82
100
  value: template,
83
101
  });
84
102
  }
85
- });
103
+ }
86
104
  return disabledComponents.length
87
- ? [...enabledComponents, new Separator(), ...disabledComponents]
105
+ ? [
106
+ ...enabledComponents,
107
+ new Separator(),
108
+ ...disabledComponents,
109
+ new Separator(),
110
+ ]
88
111
  : [...enabledComponents];
89
112
  }
90
- export async function v3ComponentFlow(platformVersion, projectBase, providedAuth, providedDistribution) {
113
+ export async function v3ComponentFlow(platformVersion, projectBase, providedAuth, providedDistribution, accountId) {
91
114
  let repoConfig = undefined;
92
115
  let authType;
93
116
  let distribution;
@@ -113,7 +136,7 @@ export async function v3ComponentFlow(platformVersion, projectBase, providedAuth
113
136
  authType = selectedAuthType;
114
137
  distribution = selectedDistribution;
115
138
  }
116
- const componentTemplateChoices = calculateComponentTemplateChoices(repoConfig?.components || [], authType, distribution);
139
+ const componentTemplateChoices = await calculateComponentTemplateChoices(repoConfig?.components || [], authType, distribution, accountId);
117
140
  return {
118
141
  componentTemplateChoices,
119
142
  authType,
@@ -0,0 +1,13 @@
1
+ import { Deploy } from '@hubspot/local-dev-lib/types/Deploy';
2
+ export declare function validateBuildIdForDeploy(buildId: number, deployedBuildId: number | undefined, latestBuildId: number, projectName: string | undefined, accountId: number): boolean | string;
3
+ export declare function logDeployErrors(errorData: {
4
+ message: string;
5
+ errors: Array<{
6
+ message: string;
7
+ subCategory: string;
8
+ context: {
9
+ COMPONENT_NAME: string;
10
+ };
11
+ }>;
12
+ }): void;
13
+ export declare function handleProjectDeploy(targetAccountId: number, projectName: string, buildId: number, useV3Api: boolean, force: boolean): Promise<Deploy | undefined>;
@@ -0,0 +1,63 @@
1
+ import { uiLogger } from '../ui/logger.js';
2
+ import { commands } from '../../lang/en.js';
3
+ import { PROJECT_ERROR_TYPES } from '../constants.js';
4
+ import { deployProject } from '@hubspot/local-dev-lib/api/projects';
5
+ import { pollDeployStatus } from './pollProjectBuildAndDeploy.js';
6
+ export function validateBuildIdForDeploy(buildId, deployedBuildId, latestBuildId, projectName, accountId) {
7
+ if (Number(buildId) > latestBuildId) {
8
+ return commands.project.deploy.errors.buildIdDoesNotExist(accountId, buildId, projectName);
9
+ }
10
+ if (Number(buildId) === deployedBuildId) {
11
+ return commands.project.deploy.errors.buildAlreadyDeployed(accountId, buildId, projectName);
12
+ }
13
+ return true;
14
+ }
15
+ export function logDeployErrors(errorData) {
16
+ uiLogger.error(errorData.message);
17
+ errorData.errors.forEach(err => {
18
+ // This is how the pre-deploy check manifests itself in < 2025.2 projects
19
+ if (err.subCategory === PROJECT_ERROR_TYPES.DEPLOY_CONTAINS_REMOVALS) {
20
+ uiLogger.log(commands.project.deploy.errors.deployContainsRemovals(err.context.COMPONENT_NAME));
21
+ }
22
+ else {
23
+ uiLogger.log(err.message);
24
+ }
25
+ });
26
+ }
27
+ function handleBlockedDeploy(deployResp) {
28
+ const deployCanBeForced = deployResp.issues.every(issue => issue.blockingMessages.every(message => message.isWarning));
29
+ uiLogger.log('');
30
+ if (deployCanBeForced) {
31
+ uiLogger.warn(commands.project.deploy.errors.deployWarningsHeader);
32
+ uiLogger.log('');
33
+ }
34
+ else {
35
+ uiLogger.error(commands.project.deploy.errors.deployBlockedHeader);
36
+ uiLogger.log('');
37
+ }
38
+ deployResp.issues.forEach(issue => {
39
+ if (issue.blockingMessages.length > 0) {
40
+ issue.blockingMessages.forEach(message => {
41
+ uiLogger.log(commands.project.deploy.errors.deployIssueComponentWarning(issue.uid, issue.componentTypeName, message.message));
42
+ });
43
+ }
44
+ else {
45
+ uiLogger.log(commands.project.deploy.errors.deployIssueComponentGeneric(issue.uid, issue.componentTypeName));
46
+ }
47
+ uiLogger.log('');
48
+ });
49
+ }
50
+ export async function handleProjectDeploy(targetAccountId, projectName, buildId, useV3Api, force) {
51
+ const { data: deployResp } = await deployProject(targetAccountId, projectName, buildId, useV3Api, force);
52
+ if (!deployResp || deployResp.buildResultType !== 'DEPLOY_QUEUED') {
53
+ if (deployResp?.buildResultType === 'DEPLOY_BLOCKED') {
54
+ handleBlockedDeploy(deployResp);
55
+ }
56
+ else {
57
+ uiLogger.error(commands.project.deploy.errors.deploy);
58
+ }
59
+ return;
60
+ }
61
+ const deployResult = await pollDeployStatus(targetAccountId, projectName, Number(deployResp.id), buildId);
62
+ return deployResult;
63
+ }
@@ -15,11 +15,9 @@ declare class AppDevModeInterface {
15
15
  private get appNode();
16
16
  private get appData();
17
17
  private set appData(value);
18
- private isAutomaticallyInstallable;
19
18
  private getAppInstallUrl;
20
19
  private fetchAppData;
21
20
  private checkMarketplaceAppInstalls;
22
- private autoInstallStaticAuthApp;
23
21
  private installAppOrOpenInstallUrl;
24
22
  private checkTestAccountAppInstallation;
25
23
  private onDevServerMessage;
@@ -1,19 +1,21 @@
1
1
  import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
2
- import { fetchPublicAppsForPortal, fetchPublicAppProductionInstallCounts, installStaticAuthAppOnTestAccount, } from '@hubspot/local-dev-lib/api/appsDev';
2
+ import { fetchPublicAppsForPortal, fetchPublicAppProductionInstallCounts,
3
+ // installStaticAuthAppOnTestAccount,
4
+ } from '@hubspot/local-dev-lib/api/appsDev';
3
5
  import { DevModeUnifiedInterface as UIEDevModeInterface } from '@hubspot/ui-extensions-dev-server';
4
6
  import { requestPorts } from '@hubspot/local-dev-lib/portManager';
5
- import { getAccountConfig } from '@hubspot/local-dev-lib/config';
6
7
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, APP_INSTALLATION_STATES, LOCAL_DEV_SERVER_MESSAGE_TYPES, } from '../../constants.js';
7
8
  import { EXIT_CODES } from '../../enums/exitCodes.js';
8
9
  import { isAppIRNode } from '../../projects/structure.js';
9
10
  import { uiLine } from '../../ui/index.js';
10
11
  import { logError } from '../../errorHandlers/index.js';
11
- import { installAppAutoPrompt, installAppBrowserPrompt, } from '../../prompts/installAppPrompt.js';
12
+ import {
13
+ // installAppAutoPrompt,
14
+ installAppBrowserPrompt, } from '../../prompts/installAppPrompt.js';
12
15
  import { confirmPrompt } from '../../prompts/promptUtils.js';
13
16
  import { lib } from '../../../lang/en.js';
14
17
  import { uiLogger } from '../../ui/logger.js';
15
18
  import { getOauthAppInstallUrl, getStaticAuthAppInstallUrl, } from '../../app/urls.js';
16
- import { isDeveloperTestAccount, isSandbox } from '../../accountTypes.js';
17
19
  import SpinniesManager from '../../ui/SpinniesManager.js';
18
20
  class AppDevModeInterface {
19
21
  localDevState;
@@ -52,19 +54,26 @@ class AppDevModeInterface {
52
54
  }
53
55
  this.localDevState.setAppDataForUid(this.appNode.uid, appData);
54
56
  }
55
- isAutomaticallyInstallable() {
56
- const targetTestingAccount = getAccountConfig(this.localDevState.targetTestingAccountId);
57
- if (!targetTestingAccount) {
58
- return false;
59
- }
60
- const isTestAccount = isDeveloperTestAccount(targetTestingAccount) ||
61
- isSandbox(targetTestingAccount);
62
- const hasCorrectParent = targetTestingAccount.parentAccountId ===
63
- this.localDevState.targetProjectAccountId;
64
- return (isTestAccount &&
65
- hasCorrectParent &&
66
- this.appNode?.config.auth.type === APP_AUTH_TYPES.STATIC);
67
- }
57
+ // @TODO: Restore test account auto install functionality
58
+ // private isAutomaticallyInstallable(): boolean {
59
+ // const targetTestingAccount = getAccountConfig(
60
+ // this.localDevState.targetTestingAccountId
61
+ // );
62
+ // if (!targetTestingAccount) {
63
+ // return false;
64
+ // }
65
+ // const isTestAccount =
66
+ // isDeveloperTestAccount(targetTestingAccount) ||
67
+ // isSandbox(targetTestingAccount);
68
+ // const hasCorrectParent =
69
+ // targetTestingAccount.parentAccountId ===
70
+ // this.localDevState.targetProjectAccountId;
71
+ // return (
72
+ // isTestAccount &&
73
+ // hasCorrectParent &&
74
+ // this.appNode?.config.auth.type === APP_AUTH_TYPES.STATIC
75
+ // );
76
+ // }
68
77
  async getAppInstallUrl() {
69
78
  if (this.appNode?.config.auth.type === APP_AUTH_TYPES.OAUTH) {
70
79
  return getOauthAppInstallUrl({
@@ -134,27 +143,47 @@ class AppDevModeInterface {
134
143
  }
135
144
  this.localDevState.addUploadWarning(lib.AppDevModeInterface.defaultMarketplaceAppWarning(this.marketplaceAppInstalls));
136
145
  }
137
- async autoInstallStaticAuthApp() {
138
- const shouldInstall = await installAppAutoPrompt();
139
- if (!shouldInstall) {
140
- uiLogger.log(lib.AppDevModeInterface.autoInstallDeclined);
141
- process.exit(EXIT_CODES.SUCCESS);
142
- }
143
- await installStaticAuthAppOnTestAccount(this.appData.id, this.localDevState.targetTestingAccountId, this.appData.scopeGroupIds);
144
- }
146
+ // @TODO: Restore test account auto install functionality
147
+ // private async autoInstallStaticAuthApp(): Promise<void> {
148
+ // const shouldInstall = await installAppAutoPrompt();
149
+ // if (!shouldInstall) {
150
+ // uiLogger.log(lib.AppDevModeInterface.autoInstallDeclined);
151
+ // process.exit(EXIT_CODES.SUCCESS);
152
+ // }
153
+ // await installStaticAuthAppOnTestAccount(
154
+ // this.appData!.id,
155
+ // this.localDevState.targetTestingAccountId,
156
+ // this.appData!.scopeGroupIds
157
+ // );
158
+ // }
145
159
  async installAppOrOpenInstallUrl(isReinstall) {
146
- if (this.isAutomaticallyInstallable()) {
147
- try {
148
- await this.autoInstallStaticAuthApp();
149
- uiLogger.success(lib.AppDevModeInterface.autoInstallSuccess(this.appData.name, this.localDevState.targetTestingAccountId));
150
- return;
151
- }
152
- catch (e) {
153
- uiLogger.error(lib.AppDevModeInterface.autoInstallError(this.appData.name, this.localDevState.targetTestingAccountId));
154
- }
155
- }
160
+ // @TODO: Restore test account auto install functionality
161
+ // if (this.isAutomaticallyInstallable()) {
162
+ // try {
163
+ // await this.autoInstallStaticAuthApp();
164
+ // uiLogger.success(
165
+ // lib.AppDevModeInterface.autoInstallSuccess(
166
+ // this.appData!.name,
167
+ // this.localDevState.targetTestingAccountId
168
+ // )
169
+ // );
170
+ // return;
171
+ // } catch (e) {
172
+ // uiLogger.error(
173
+ // lib.AppDevModeInterface.autoInstallError(
174
+ // this.appData!.name,
175
+ // this.localDevState.targetTestingAccountId
176
+ // )
177
+ // );
178
+ // }
179
+ // }
156
180
  const installUrl = await this.getAppInstallUrl();
157
- await installAppBrowserPrompt(installUrl, isReinstall);
181
+ await installAppBrowserPrompt(installUrl, isReinstall, {
182
+ testingAccountId: this.localDevState.targetTestingAccountId,
183
+ projectAccountId: this.localDevState.targetProjectAccountId,
184
+ projectName: this.localDevState.projectConfig.name,
185
+ appUid: this.appNode.uid,
186
+ });
158
187
  }
159
188
  async checkTestAccountAppInstallation() {
160
189
  if (!this.appNode || !this.appData) {
@@ -34,6 +34,7 @@ class DevServerManagerV2 {
34
34
  // @TODO: In the future, update UIE Dev Server to use LocalDevState
35
35
  await serverInterface.setup({
36
36
  components: this.localDevState.projectNodes,
37
+ profileData: this.localDevState.projectProfileData,
37
38
  logger,
38
39
  urls: {
39
40
  api: getHubSpotApiOrigin(env),
@@ -8,7 +8,7 @@ import DevServerManagerV2 from './DevServerManagerV2.js';
8
8
  import { EXIT_CODES } from '../../enums/exitCodes.js';
9
9
  import { getProjectConfig } from '../config.js';
10
10
  import { handleProjectUpload } from '../upload.js';
11
- import { pollProjectBuildAndDeploy } from '../buildAndDeploy.js';
11
+ import { pollProjectBuildAndDeploy } from '../pollProjectBuildAndDeploy.js';
12
12
  import { getLocalDevUiUrl } from '../urls.js';
13
13
  import { CONFIG_LOCAL_STATE_FLAGS } from '../../constants.js';
14
14
  import { isAutoOpenBrowserEnabled } from '../../configOptions.js';
@@ -101,11 +101,13 @@ class LocalDevProcess {
101
101
  const intermediateRepresentation = await this.getIntermediateRepresentation(this.state.projectNodesAtLastUpload);
102
102
  this.state.projectNodes =
103
103
  intermediateRepresentation.intermediateNodesIndexedByUid;
104
+ this.state.projectProfileData = intermediateRepresentation.profileData;
104
105
  }
105
106
  async updateProjectNodesAfterUpload() {
106
107
  const intermediateRepresentation = await this.getIntermediateRepresentation();
107
108
  this.state.projectNodes =
108
109
  intermediateRepresentation.intermediateNodesIndexedByUid;
110
+ this.state.projectProfileData = intermediateRepresentation.profileData;
109
111
  this.state.projectNodesAtLastUpload =
110
112
  intermediateRepresentation.intermediateNodesIndexedByUid;
111
113
  }
@@ -1,4 +1,4 @@
1
- import { IntermediateRepresentationNodeLocalDev } from '@hubspot/project-parsing-lib/src/lib/types.js';
1
+ import { IntermediateRepresentationNodeLocalDev, HSProfileVariables } from '@hubspot/project-parsing-lib/src/lib/types.js';
2
2
  import { Environment } from '@hubspot/local-dev-lib/types/Config';
3
3
  import { ProjectConfig } from '../../../types/Projects.js';
4
4
  import { LocalDevStateConstructorOptions, LocalDevStateListener, AppLocalDevData, LocalDevServerMessage } from '../../../types/LocalDev.js';
@@ -12,13 +12,14 @@ declare class LocalDevState {
12
12
  private _projectName;
13
13
  private _debug;
14
14
  private _projectNodes;
15
+ private _projectProfileData;
15
16
  private _projectNodesAtLastUpload;
16
17
  private _env;
17
18
  private _listeners;
18
19
  private _appData;
19
20
  private _devServerMessage;
20
21
  private _uploadWarnings;
21
- constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, initialProjectNodes, profile, env, }: LocalDevStateConstructorOptions);
22
+ constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectId, projectName, debug, initialProjectNodes, initialProjectProfileData, profile, env, }: LocalDevStateConstructorOptions);
22
23
  private runListeners;
23
24
  get targetProjectAccountId(): number;
24
25
  get targetTestingAccountId(): number;
@@ -34,6 +35,8 @@ declare class LocalDevState {
34
35
  set projectNodes(nodes: {
35
36
  [key: string]: IntermediateRepresentationNodeLocalDev;
36
37
  });
38
+ get projectProfileData(): HSProfileVariables;
39
+ set projectProfileData(profileData: HSProfileVariables);
37
40
  get projectNodesAtLastUpload(): {
38
41
  [key: string]: IntermediateRepresentationNodeLocalDev;
39
42
  };