@hubspot/cli 8.0.1-experimental.0 → 8.0.3-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 (109) hide show
  1. package/bin/cli.js +8 -5
  2. package/commands/__tests__/getStarted.test.js +12 -0
  3. package/commands/__tests__/project.test.js +30 -0
  4. package/commands/account/auth.js +8 -97
  5. package/commands/account/use.js +19 -4
  6. package/commands/cms/module/marketplace-validate.js +23 -5
  7. package/commands/cms/theme/marketplace-validate.js +25 -6
  8. package/commands/getStarted.d.ts +2 -1
  9. package/commands/getStarted.js +38 -15
  10. package/commands/mcp/__tests__/start.test.js +1 -67
  11. package/commands/mcp/setup.js +1 -2
  12. package/commands/mcp/start.js +1 -19
  13. package/commands/mcp.js +1 -2
  14. package/commands/project.js +22 -1
  15. package/lang/en.d.ts +53 -7
  16. package/lang/en.js +59 -13
  17. package/lib/CLIWebSocketServer.d.ts +28 -0
  18. package/lib/CLIWebSocketServer.js +91 -0
  19. package/lib/__tests__/CLIWebSocketServer.test.d.ts +1 -0
  20. package/lib/__tests__/CLIWebSocketServer.test.js +252 -0
  21. package/lib/__tests__/accountAuth.test.d.ts +1 -0
  22. package/lib/__tests__/accountAuth.test.js +258 -0
  23. package/lib/__tests__/commandSuggestion.test.d.ts +1 -0
  24. package/lib/__tests__/commandSuggestion.test.js +119 -0
  25. package/lib/accountAuth.d.ts +10 -0
  26. package/lib/accountAuth.js +105 -0
  27. package/lib/app/urls.d.ts +1 -0
  28. package/lib/app/urls.js +4 -0
  29. package/lib/commandSuggestion.d.ts +3 -0
  30. package/lib/commandSuggestion.js +45 -0
  31. package/lib/constants.d.ts +0 -1
  32. package/lib/constants.js +0 -1
  33. package/lib/errors/ProjectErrors.d.ts +15 -0
  34. package/lib/errors/ProjectErrors.js +30 -0
  35. package/lib/getStarted/getStartedV2.d.ts +7 -0
  36. package/lib/getStarted/getStartedV2.js +18 -0
  37. package/lib/getStartedV2Actions.d.ts +37 -0
  38. package/lib/getStartedV2Actions.js +146 -0
  39. package/lib/marketplaceValidate.d.ts +1 -1
  40. package/lib/marketplaceValidate.js +23 -41
  41. package/lib/mcp/__tests__/setup.test.js +0 -17
  42. package/lib/mcp/setup.d.ts +0 -1
  43. package/lib/mcp/setup.js +59 -103
  44. package/lib/projects/ProjectLogsManager.d.ts +12 -3
  45. package/lib/projects/ProjectLogsManager.js +70 -12
  46. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +43 -175
  47. package/lib/projects/__tests__/ProjectLogsManager.test.js +131 -18
  48. package/lib/projects/__tests__/platformVersion.test.js +37 -1
  49. package/lib/projects/__tests__/projects.test.js +6 -2
  50. package/lib/projects/components.d.ts +6 -0
  51. package/lib/projects/components.js +1 -1
  52. package/lib/projects/config.js +9 -2
  53. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +2 -7
  54. package/lib/projects/localDev/LocalDevWebsocketServer.js +51 -98
  55. package/lib/projects/localDev/helpers/project.d.ts +4 -1
  56. package/lib/projects/localDev/helpers/project.js +13 -8
  57. package/lib/projects/localDev/localDevWebsocketServerUtils.d.ts +8 -7
  58. package/lib/projects/platformVersion.d.ts +8 -0
  59. package/lib/projects/platformVersion.js +31 -2
  60. package/lib/prompts/accountsPrompt.d.ts +2 -1
  61. package/lib/prompts/accountsPrompt.js +10 -2
  62. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +20 -3
  63. package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -10
  64. package/mcp-server/tools/project/CreateProjectTool.d.ts +24 -4
  65. package/mcp-server/tools/project/CreateProjectTool.js +5 -10
  66. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +5 -8
  67. package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
  68. package/mcp-server/tools/project/GetBuildLogsTool.js +3 -4
  69. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
  70. package/mcp-server/tools/project/GetBuildStatusTool.js +3 -4
  71. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +6 -1
  72. package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -6
  73. package/mcp-server/tools/project/constants.d.ts +12 -1
  74. package/mcp-server/tools/project/constants.js +12 -16
  75. package/mcp-server/utils/__tests__/project.test.js +0 -125
  76. package/mcp-server/utils/project.js +0 -8
  77. package/package.json +10 -5
  78. package/types/LocalDev.d.ts +0 -4
  79. package/ui/components/ActionSection.d.ts +12 -0
  80. package/ui/components/ActionSection.js +25 -0
  81. package/ui/components/BoxWithTitle.d.ts +4 -2
  82. package/ui/components/BoxWithTitle.js +2 -2
  83. package/ui/components/FullScreen.d.ts +6 -0
  84. package/ui/components/FullScreen.js +13 -0
  85. package/ui/components/InputField.d.ts +10 -0
  86. package/ui/components/InputField.js +10 -0
  87. package/ui/components/SelectInput.d.ts +11 -0
  88. package/ui/components/SelectInput.js +59 -0
  89. package/ui/components/StatusIcon.d.ts +9 -0
  90. package/ui/components/StatusIcon.js +17 -0
  91. package/ui/components/getStarted/GetStartedFlow.d.ts +8 -0
  92. package/ui/components/getStarted/GetStartedFlow.js +136 -0
  93. package/ui/components/getStarted/reducer.d.ts +59 -0
  94. package/ui/components/getStarted/reducer.js +72 -0
  95. package/ui/components/getStarted/screens/ProjectSetupScreen.d.ts +16 -0
  96. package/ui/components/getStarted/screens/ProjectSetupScreen.js +39 -0
  97. package/ui/components/getStarted/screens/UploadScreen.d.ts +7 -0
  98. package/ui/components/getStarted/screens/UploadScreen.js +43 -0
  99. package/ui/components/getStarted/selectors.d.ts +2 -0
  100. package/ui/components/getStarted/selectors.js +1 -0
  101. package/ui/constants.d.ts +6 -0
  102. package/ui/constants.js +6 -0
  103. package/ui/lib/constants.d.ts +16 -0
  104. package/ui/lib/constants.js +16 -0
  105. package/ui/playground/fixtures.js +47 -0
  106. package/ui/render.d.ts +4 -0
  107. package/ui/render.js +31 -0
  108. package/ui/styles.d.ts +3 -0
  109. package/ui/styles.js +3 -0
@@ -0,0 +1,105 @@
1
+ import { updateConfigAccount, createEmptyConfigFile, getConfigFilePath, localConfigFileExists, globalConfigFileExists, setConfigAccountAsDefault, } from '@hubspot/local-dev-lib/config';
2
+ import { getAccessToken, updateConfigWithAccessToken, } from '@hubspot/local-dev-lib/personalAccessKey';
3
+ import { toKebabCase } from '@hubspot/local-dev-lib/text';
4
+ import { handleMerge, handleMigration } from './configMigrate.js';
5
+ import { debugError, logError } from './errorHandlers/index.js';
6
+ import { personalAccessKeyPrompt } from './prompts/personalAccessKeyPrompt.js';
7
+ import { cliAccountNamePrompt } from './prompts/accountNamePrompt.js';
8
+ import { setAsDefaultAccountPrompt } from './prompts/setAsDefaultAccountPrompt.js';
9
+ import { commands } from '../lang/en.js';
10
+ import { uiLogger } from './ui/logger.js';
11
+ async function updateConfigWithNewAccount(env, configAlreadyExists, providedPersonalAccessKey, accountId) {
12
+ try {
13
+ const { personalAccessKey } = providedPersonalAccessKey
14
+ ? { personalAccessKey: providedPersonalAccessKey }
15
+ : await personalAccessKeyPrompt({
16
+ env,
17
+ account: accountId,
18
+ });
19
+ const token = await getAccessToken(personalAccessKey, env);
20
+ const defaultAccountName = token.hubName
21
+ ? toKebabCase(token.hubName)
22
+ : undefined;
23
+ const accountName = configAlreadyExists
24
+ ? undefined
25
+ : (await cliAccountNamePrompt(defaultAccountName)).name;
26
+ const updatedConfig = await updateConfigWithAccessToken(token, personalAccessKey, env, accountName, !configAlreadyExists);
27
+ if (!updatedConfig)
28
+ return null;
29
+ // Can happen if the user is re-authenticating an account with no name
30
+ if (configAlreadyExists && !updatedConfig.name) {
31
+ updatedConfig.name = (await cliAccountNamePrompt(defaultAccountName)).name;
32
+ updateConfigAccount({
33
+ ...updatedConfig,
34
+ });
35
+ }
36
+ return updatedConfig;
37
+ }
38
+ catch (e) {
39
+ debugError(e);
40
+ return null;
41
+ }
42
+ }
43
+ async function handleConfigMigration() {
44
+ const deprecatedConfigExists = localConfigFileExists();
45
+ const globalConfigExists = globalConfigFileExists();
46
+ if (!deprecatedConfigExists) {
47
+ return true;
48
+ }
49
+ if (globalConfigExists) {
50
+ try {
51
+ const mergeConfirmed = await handleMerge();
52
+ if (!mergeConfirmed) {
53
+ uiLogger.log('');
54
+ uiLogger.log(commands.account.subcommands.auth.errors.mergeNotConfirmed);
55
+ }
56
+ return mergeConfirmed;
57
+ }
58
+ catch (error) {
59
+ logError(error);
60
+ return false;
61
+ }
62
+ }
63
+ try {
64
+ const migrationConfirmed = await handleMigration();
65
+ if (!migrationConfirmed) {
66
+ uiLogger.log('');
67
+ uiLogger.log(commands.account.subcommands.auth.errors.migrationNotConfirmed);
68
+ }
69
+ return migrationConfirmed;
70
+ }
71
+ catch (error) {
72
+ logError(error);
73
+ return false;
74
+ }
75
+ }
76
+ export async function authenticateNewAccount({ env, providedPersonalAccessKey, accountId, setAsDefaultAccount, }) {
77
+ const configMigrationSuccess = await handleConfigMigration();
78
+ if (!configMigrationSuccess) {
79
+ return null;
80
+ }
81
+ const configAlreadyExists = globalConfigFileExists();
82
+ if (!configAlreadyExists) {
83
+ createEmptyConfigFile(true);
84
+ }
85
+ const updatedConfig = await updateConfigWithNewAccount(env, configAlreadyExists, providedPersonalAccessKey, accountId);
86
+ if (!updatedConfig) {
87
+ uiLogger.error(commands.account.subcommands.auth.errors.failedToUpdateConfig);
88
+ return null;
89
+ }
90
+ const { accountId: newAccountId, name } = updatedConfig;
91
+ if (!configAlreadyExists) {
92
+ uiLogger.log('');
93
+ uiLogger.success(commands.account.subcommands.auth.success.configFileCreated(getConfigFilePath()));
94
+ uiLogger.success(commands.account.subcommands.auth.success.configFileUpdated(newAccountId));
95
+ }
96
+ else if (setAsDefaultAccount) {
97
+ setConfigAccountAsDefault(name);
98
+ uiLogger.log('');
99
+ uiLogger.success(commands.account.subcommands.auth.success.configFileUpdated(newAccountId));
100
+ }
101
+ else {
102
+ await setAsDefaultAccountPrompt(name);
103
+ }
104
+ return updatedConfig;
105
+ }
package/lib/app/urls.d.ts CHANGED
@@ -13,4 +13,5 @@ type PublicAppInstallUrlArgs = {
13
13
  };
14
14
  export declare function getOauthAppInstallUrl({ targetAccountId, env, clientId, scopes, redirectUrls, }: PublicAppInstallUrlArgs): string;
15
15
  export declare function getStaticAuthAppInstallUrl({ targetAccountId, env, appId, }: PrivateAppInstallUrlArgs): string;
16
+ export declare function getAppCardSetupUrl({ targetAccountId, env, appId, }: PrivateAppInstallUrlArgs): string;
16
17
  export {};
package/lib/app/urls.js CHANGED
@@ -10,3 +10,7 @@ export function getStaticAuthAppInstallUrl({ targetAccountId, env, appId, }) {
10
10
  const websiteOrigin = getHubSpotWebsiteOrigin(env);
11
11
  return `${websiteOrigin}/static-token/${targetAccountId}/authorize?appId=${appId}`;
12
12
  }
13
+ export function getAppCardSetupUrl({ targetAccountId, env, appId, }) {
14
+ const websiteOrigin = getHubSpotWebsiteOrigin(env);
15
+ return `${websiteOrigin}/integrations-settings/${targetAccountId}/installed/framework/${appId}/app-cards?tourId=get-started`;
16
+ }
@@ -0,0 +1,3 @@
1
+ import { Argv } from 'yargs';
2
+ export declare const commandSuggestionMappings: Record<string, string>;
3
+ export declare function addCommandSuggestions(yargsInstance: Argv): Argv;
@@ -0,0 +1,45 @@
1
+ import { uiLogger } from './ui/logger.js';
2
+ import { uiCommandReference } from './ui/index.js';
3
+ import { EXIT_CODES } from './enums/exitCodes.js';
4
+ export const commandSuggestionMappings = {
5
+ create: 'hs cms app|theme|module|webpack|function|template create',
6
+ fetch: 'hs cms fetch',
7
+ lint: 'hs cms lint',
8
+ list: 'hs cms list',
9
+ mv: 'hs cms mv',
10
+ remove: 'hs cms delete',
11
+ upload: 'hs cms upload',
12
+ watch: 'hs cms watch',
13
+ 'function deploy': 'hs cms function deploy',
14
+ 'function list': 'hs cms function list',
15
+ 'function server': 'hs cms function server',
16
+ logs: 'hs cms function logs',
17
+ 'module marketplace-validate': 'hs cms module marketplace-validate',
18
+ 'theme generate-selectors': 'hs cms theme generate-selectors',
19
+ 'theme marketplace-validate': 'hs cms theme marketplace-validate',
20
+ 'theme preview': 'hs cms theme preview',
21
+ 'custom-object schema create': 'hs custom-object create-schema',
22
+ 'custom-object schema delete': 'hs custom-object delete-schema',
23
+ 'custom-object schema fetch-all': 'hs custom-object fetch-all-schemas',
24
+ 'custom-object schema fetch': 'hs custom-object fetch-schema',
25
+ 'custom-object schema list': 'hs custom-object list-schemas',
26
+ 'custom-object schema update': 'hs custom-object update-schema',
27
+ };
28
+ function createCommandSuggestionHandler(newCommand) {
29
+ return () => {
30
+ uiLogger.error(`Did you mean ${uiCommandReference(newCommand)}?`);
31
+ process.exit(EXIT_CODES.ERROR);
32
+ };
33
+ }
34
+ function createCommandSuggestion(oldCommand, newCommand) {
35
+ return {
36
+ command: oldCommand,
37
+ builder: async (yargs) => {
38
+ return yargs.strict(false);
39
+ },
40
+ handler: createCommandSuggestionHandler(newCommand),
41
+ };
42
+ }
43
+ export function addCommandSuggestions(yargsInstance) {
44
+ return Object.entries(commandSuggestionMappings).reduce((yargs, [oldCommand, newCommand]) => yargs.command(createCommandSuggestion(oldCommand, newCommand)), yargsInstance);
45
+ }
@@ -94,7 +94,6 @@ export declare const LOCAL_DEV_UI_MESSAGE_SEND_TYPES: {
94
94
  UPDATE_APP_DATA: string;
95
95
  UPDATE_PROJECT_DATA: string;
96
96
  UPDATE_UPLOAD_WARNINGS: string;
97
- CLI_METADATA: string;
98
97
  DEV_SERVERS_STARTED: string;
99
98
  };
100
99
  export declare const LOCAL_DEV_UI_MESSAGE_RECEIVE_TYPES: {
package/lib/constants.js CHANGED
@@ -86,7 +86,6 @@ export const LOCAL_DEV_UI_MESSAGE_SEND_TYPES = {
86
86
  UPDATE_APP_DATA: 'server:updateAppData',
87
87
  UPDATE_PROJECT_DATA: 'server:updateProjectData',
88
88
  UPDATE_UPLOAD_WARNINGS: 'server:updateUploadWarnings',
89
- CLI_METADATA: 'server:cliMetadata',
90
89
  DEV_SERVERS_STARTED: 'server:devServersStarted',
91
90
  };
92
91
  export const LOCAL_DEV_UI_MESSAGE_RECEIVE_TYPES = {
@@ -0,0 +1,15 @@
1
+ export declare class ProjectNestingError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class ProjectConfigNotFoundError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ export declare class ProjectValidationError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ export declare class ProjectUploadError extends Error {
11
+ constructor(message: string, cause?: unknown);
12
+ }
13
+ export declare class ProjectBuildDeployError extends Error {
14
+ constructor(message: string);
15
+ }
@@ -0,0 +1,30 @@
1
+ export class ProjectNestingError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'ProjectNestingError';
5
+ }
6
+ }
7
+ export class ProjectConfigNotFoundError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = 'ProjectConfigNotFoundError';
11
+ }
12
+ }
13
+ export class ProjectValidationError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = 'ProjectValidationError';
17
+ }
18
+ }
19
+ export class ProjectUploadError extends Error {
20
+ constructor(message, cause) {
21
+ super(message, { cause });
22
+ this.name = 'ProjectUploadError';
23
+ }
24
+ }
25
+ export class ProjectBuildDeployError extends Error {
26
+ constructor(message) {
27
+ super(message);
28
+ this.name = 'ProjectBuildDeployError';
29
+ }
30
+ }
@@ -0,0 +1,7 @@
1
+ import { CommonArgs } from '../../types/Yargs.js';
2
+ type GetStartedV2Args = CommonArgs & {
3
+ name?: string;
4
+ dest?: string;
5
+ };
6
+ export declare function runGetStartedV2({ derivedAccountId, name, dest, }: GetStartedV2Args): Promise<void>;
7
+ export {};
@@ -0,0 +1,18 @@
1
+ import { LOG_LEVEL, setLogLevel } from '@hubspot/local-dev-lib/logger';
2
+ import { trackGetStartedUsage } from '../../lib/getStartedV2Actions.js';
3
+ import { renderInteractive } from '../../ui/render.js';
4
+ import { getGetStartedFlow } from '../../ui/components/getStarted/GetStartedFlow.js';
5
+ export async function runGetStartedV2({ derivedAccountId, name, dest, }) {
6
+ setLogLevel(LOG_LEVEL.NONE);
7
+ try {
8
+ await renderInteractive(getGetStartedFlow({
9
+ derivedAccountId,
10
+ initialName: name,
11
+ initialDest: dest,
12
+ }), { fullScreen: true });
13
+ await trackGetStartedUsage({ successful: true, step: 'command-completed' }, derivedAccountId);
14
+ }
15
+ finally {
16
+ setLogLevel(LOG_LEVEL.LOG);
17
+ }
18
+ }
@@ -0,0 +1,37 @@
1
+ import { type ProjectMetadata } from '@hubspot/project-parsing-lib/projects';
2
+ import { type IntermediateRepresentationNode } from '@hubspot/project-parsing-lib/translate';
3
+ export type CreateProjectResult = {
4
+ projectName: string;
5
+ projectDest: string;
6
+ };
7
+ export type AppConfig = {
8
+ name?: string;
9
+ uid?: string;
10
+ distribution?: string;
11
+ auth?: {
12
+ type?: string;
13
+ requiredScopes?: string[];
14
+ optionalScopes?: string[];
15
+ };
16
+ };
17
+ export type AppIRNode = IntermediateRepresentationNode & {
18
+ config: AppConfig;
19
+ };
20
+ export declare function createProjectAction({ projectName, projectDest, }: {
21
+ projectName: string;
22
+ projectDest: string;
23
+ }): Promise<CreateProjectResult>;
24
+ export type UploadAndDeployResult = {
25
+ appId: number;
26
+ projectId: number;
27
+ installUrl: string;
28
+ projectDir: string;
29
+ app: AppIRNode;
30
+ projectName: string;
31
+ projectMetadata: ProjectMetadata;
32
+ };
33
+ export declare function uploadAndDeployAction({ accountId, projectDest, }: {
34
+ accountId: number;
35
+ projectDest: string;
36
+ }): Promise<UploadAndDeployResult>;
37
+ export declare function trackGetStartedUsage(params: Record<string, unknown>, accountId: number): Promise<void>;
@@ -0,0 +1,146 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { fetchPublicAppsForPortal } from '@hubspot/local-dev-lib/api/appsDev';
4
+ import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
5
+ import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
6
+ import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
7
+ import { getCwd } from '@hubspot/local-dev-lib/path';
8
+ import { getProjectMetadata, } from '@hubspot/project-parsing-lib/projects';
9
+ import { translate, } from '@hubspot/project-parsing-lib/translate';
10
+ import { commands, lib } from '../lang/en.js';
11
+ import { getStaticAuthAppInstallUrl } from '../lib/app/urls.js';
12
+ import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, PROJECT_CONFIG_FILE, } from '../lib/constants.js';
13
+ import { ProjectNestingError, ProjectConfigNotFoundError, ProjectValidationError, ProjectUploadError, ProjectBuildDeployError, } from './errors/ProjectErrors.js';
14
+ import { getProjectConfig, validateProjectConfig, writeProjectConfig, } from '../lib/projects/config.js';
15
+ import { isV2Project } from '../lib/projects/platformVersion.js';
16
+ import { pollProjectBuildAndDeploy } from '../lib/projects/pollProjectBuildAndDeploy.js';
17
+ import { handleProjectUpload } from '../lib/projects/upload.js';
18
+ import { validateProjectDirectory } from '../lib/prompts/projectNameAndDestPrompt.js';
19
+ import { trackCommandMetadataUsage } from './usageTracking.js';
20
+ export async function createProjectAction({ projectName, projectDest, }) {
21
+ if (!projectName || projectName.trim() === '') {
22
+ throw new Error(lib.prompts.projectNameAndDestPrompt.errors.nameRequired);
23
+ }
24
+ const validationResult = validateProjectDirectory(projectDest);
25
+ if (validationResult !== true) {
26
+ throw new Error(typeof validationResult === 'string'
27
+ ? validationResult
28
+ : 'Invalid project directory');
29
+ }
30
+ const projectDestAbsolute = path.resolve(getCwd(), projectDest);
31
+ const { projectConfig: existingProjectConfig, projectDir: existingProjectDir, } = await getProjectConfig(projectDestAbsolute);
32
+ if (existingProjectConfig &&
33
+ existingProjectDir &&
34
+ projectDestAbsolute.startsWith(existingProjectDir)) {
35
+ throw new ProjectNestingError(commands.project.create.errors.cannotNestProjects(existingProjectDir));
36
+ }
37
+ try {
38
+ await cloneGithubRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, projectDestAbsolute, {
39
+ sourceDir: '2025.2/private-app-get-started-template',
40
+ hideLogs: true,
41
+ });
42
+ const projectConfigPath = path.join(projectDestAbsolute, PROJECT_CONFIG_FILE);
43
+ const parsedConfigFile = JSON.parse(fs.readFileSync(projectConfigPath, 'utf8'));
44
+ writeProjectConfig(projectConfigPath, {
45
+ ...parsedConfigFile,
46
+ name: projectName,
47
+ });
48
+ return { projectName, projectDest: projectDestAbsolute };
49
+ }
50
+ catch (error) {
51
+ throw new Error(commands.project.create.errors.failedToDownloadProject, {
52
+ cause: error,
53
+ });
54
+ }
55
+ }
56
+ async function fetchAppAfterDeploy(accountId, env) {
57
+ const { data: { results }, } = await fetchPublicAppsForPortal(accountId);
58
+ const lastCreatedApp = results.sort((a, b) => b.createdAt - a.createdAt)[0];
59
+ if (!lastCreatedApp) {
60
+ throw new Error(commands.getStarted.errors.noAppsFound);
61
+ }
62
+ const installUrl = getStaticAuthAppInstallUrl({
63
+ targetAccountId: accountId,
64
+ env,
65
+ appId: lastCreatedApp.id,
66
+ });
67
+ return {
68
+ appId: lastCreatedApp.id,
69
+ installUrl,
70
+ };
71
+ }
72
+ async function buildProjectMetadata(projectDir, projectConfig, accountId) {
73
+ const srcDir = path.join(projectDir, projectConfig.srcDir);
74
+ const projectMetadata = await getProjectMetadata(srcDir);
75
+ const intermediateRepresentation = await translate({
76
+ projectSourceDir: srcDir,
77
+ platformVersion: projectConfig.platformVersion,
78
+ accountId,
79
+ }, { skipValidation: false });
80
+ const apps = Object.values(intermediateRepresentation.intermediateNodesIndexedByUid).filter(node => node.componentType === 'APPLICATION');
81
+ if (apps.length === 0) {
82
+ throw new Error(commands.getStarted.errors.noAppsFound);
83
+ }
84
+ return {
85
+ projectMetadata,
86
+ app: apps[0],
87
+ };
88
+ }
89
+ export async function uploadAndDeployAction({ accountId, projectDest, }) {
90
+ const { projectConfig, projectDir } = await getProjectConfig(projectDest);
91
+ if (!projectConfig || !projectDir) {
92
+ throw new ProjectConfigNotFoundError(commands.getStarted.errors.configFileNotFound);
93
+ }
94
+ try {
95
+ validateProjectConfig(projectConfig, projectDir);
96
+ }
97
+ catch (error) {
98
+ const message = error instanceof Error ? error.message : String(error);
99
+ throw new ProjectValidationError(message);
100
+ }
101
+ const env = getConfigAccountEnvironment(accountId);
102
+ try {
103
+ const { result, uploadError } = await handleProjectUpload({
104
+ accountId,
105
+ projectConfig,
106
+ projectDir,
107
+ callbackFunc: pollProjectBuildAndDeploy,
108
+ uploadMessage: commands.getStarted.logs.initialUploadMessage,
109
+ forceCreate: true,
110
+ isUploadCommand: false,
111
+ sendIR: isV2Project(projectConfig.platformVersion),
112
+ skipValidation: false,
113
+ });
114
+ if (uploadError) {
115
+ throw new ProjectUploadError(commands.getStarted.errors.uploadActionFailed, uploadError);
116
+ }
117
+ if (!result || !result.succeeded) {
118
+ throw new ProjectBuildDeployError(commands.getStarted.errors.buildOrDeployFailed);
119
+ }
120
+ const { data: projectData } = await fetchProject(accountId, projectConfig.name);
121
+ const projectId = projectData.id;
122
+ const { appId, installUrl } = await fetchAppAfterDeploy(accountId, env);
123
+ const { projectMetadata, app } = await buildProjectMetadata(projectDir, projectConfig, accountId);
124
+ return {
125
+ appId,
126
+ projectId,
127
+ installUrl,
128
+ projectDir,
129
+ app,
130
+ projectName: projectConfig.name,
131
+ projectMetadata,
132
+ };
133
+ }
134
+ catch (error) {
135
+ if (error instanceof ProjectUploadError ||
136
+ error instanceof ProjectBuildDeployError) {
137
+ throw error;
138
+ }
139
+ throw new Error(commands.getStarted.errors.failedToUploadAndDeploy, {
140
+ cause: error,
141
+ });
142
+ }
143
+ }
144
+ export function trackGetStartedUsage(params, accountId) {
145
+ return trackCommandMetadataUsage('get-started', params, accountId);
146
+ }
@@ -2,7 +2,7 @@ import { GetValidationResultsResponse } from '@hubspot/local-dev-lib/types/Marke
2
2
  export declare function kickOffValidation(accountId: number, assetType: string, src: string): Promise<number>;
3
3
  export declare function pollForValidationFinish(accountId: number, validationId: number): Promise<void>;
4
4
  export declare function fetchValidationResults(accountId: number, validationId: number): Promise<GetValidationResultsResponse>;
5
- export declare function processValidationErrors(invalidPathError: (path: string) => string, validationResults: GetValidationResultsResponse): void;
5
+ export declare function hasProcessValidationErrors(invalidPathError: (path: string) => string, validationResults: GetValidationResultsResponse): boolean;
6
6
  type ResultsCopy = {
7
7
  noErrors: string;
8
8
  required: string;
@@ -1,54 +1,35 @@
1
1
  import chalk from 'chalk';
2
2
  import { requestValidation, getValidationStatus, getValidationResults, } from '@hubspot/local-dev-lib/api/marketplaceValidation';
3
3
  import { uiLogger } from './ui/logger.js';
4
- import { EXIT_CODES } from './enums/exitCodes.js';
5
4
  const SLEEP_TIME = 2000;
6
5
  export async function kickOffValidation(accountId, assetType, src) {
7
6
  const requestGroup = 'EXTERNAL_DEVELOPER';
8
- try {
9
- const { data: requestResult } = await requestValidation(accountId, {
10
- path: src,
11
- assetType,
12
- requestGroup,
13
- });
14
- return requestResult;
15
- }
16
- catch (err) {
17
- uiLogger.debug(err);
18
- process.exit(EXIT_CODES.ERROR);
19
- }
7
+ const { data: requestResult } = await requestValidation(accountId, {
8
+ path: src,
9
+ assetType,
10
+ requestGroup,
11
+ });
12
+ return requestResult;
20
13
  }
21
14
  export async function pollForValidationFinish(accountId, validationId) {
22
- try {
23
- const checkValidationStatus = async () => {
24
- const { data: validationStatus } = await getValidationStatus(accountId, {
25
- validationId,
26
- });
27
- if (validationStatus === 'REQUESTED') {
28
- await new Promise(resolve => setTimeout(resolve, SLEEP_TIME));
29
- await checkValidationStatus();
30
- }
31
- };
32
- await checkValidationStatus();
33
- }
34
- catch (err) {
35
- uiLogger.debug(err);
36
- process.exit(EXIT_CODES.ERROR);
37
- }
38
- }
39
- export async function fetchValidationResults(accountId, validationId) {
40
- try {
41
- const { data: validationResults } = await getValidationResults(accountId, {
15
+ async function checkValidationStatus() {
16
+ const { data: validationStatus } = await getValidationStatus(accountId, {
42
17
  validationId,
43
18
  });
44
- return validationResults;
45
- }
46
- catch (err) {
47
- uiLogger.debug(err);
48
- process.exit(EXIT_CODES.ERROR);
19
+ if (validationStatus === 'REQUESTED') {
20
+ await new Promise(resolve => setTimeout(resolve, SLEEP_TIME));
21
+ await checkValidationStatus();
22
+ }
49
23
  }
24
+ await checkValidationStatus();
25
+ }
26
+ export async function fetchValidationResults(accountId, validationId) {
27
+ const { data: validationResults } = await getValidationResults(accountId, {
28
+ validationId,
29
+ });
30
+ return validationResults;
50
31
  }
51
- export function processValidationErrors(invalidPathError, validationResults) {
32
+ export function hasProcessValidationErrors(invalidPathError, validationResults) {
52
33
  if (validationResults.errors.length) {
53
34
  const { assetPath, errors } = validationResults;
54
35
  errors.forEach(err => {
@@ -56,11 +37,12 @@ export function processValidationErrors(invalidPathError, validationResults) {
56
37
  uiLogger.error(invalidPathError(assetPath));
57
38
  }
58
39
  else {
59
- uiLogger.error(`${err.context}`);
40
+ uiLogger.error(err.context);
60
41
  }
61
42
  });
62
- process.exit(EXIT_CODES.ERROR);
43
+ return true;
63
44
  }
45
+ return false;
64
46
  }
65
47
  function displayFileInfo(file, line, resultsCopy) {
66
48
  if (file) {
@@ -132,23 +132,6 @@ describe('lib/mcp/setup', () => {
132
132
  });
133
133
  expect(mockedLogError).toHaveBeenCalledWith(error);
134
134
  });
135
- it('should pass through environment variables in command', async () => {
136
- const mockMcpCommandWithEnv = {
137
- command: 'test-command',
138
- args: ['--arg1'],
139
- env: { HUBSPOT_MCP_STANDALONE: 'true' },
140
- };
141
- mockedExecAsync.mockResolvedValueOnce({
142
- stdout: 'codex version 1.0.0',
143
- stderr: '',
144
- });
145
- mockedExecAsync.mockResolvedValueOnce({ stdout: '', stderr: '' });
146
- const result = await setupCodex(mockMcpCommandWithEnv);
147
- expect(result).toBe(true);
148
- // The env is passed through buildCommandWithAgentString, but not used in the command string
149
- // This is expected as env vars are set by the client when starting the MCP server
150
- expect(mockedExecAsync).toHaveBeenCalledWith('codex mcp add "HubSpotDev" -- test-command --arg1 --ai-agent codex');
151
- });
152
135
  });
153
136
  describe('setupGemini', () => {
154
137
  const mockMcpCommand = {
@@ -5,7 +5,6 @@ export declare const supportedTools: {
5
5
  interface McpCommand {
6
6
  command: string;
7
7
  args: string[];
8
- env?: Record<string, string>;
9
8
  }
10
9
  export declare function addMcpServerToConfig(targets: string[] | undefined): Promise<string[]>;
11
10
  export declare function setupVsCode(mcpCommand?: McpCommand): Promise<boolean>;