@hubspot/cli 7.7.34-experimental.0 → 7.8.0-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 (74) hide show
  1. package/bin/cli.js +2 -0
  2. package/commands/app.js +1 -6
  3. package/commands/getStarted.d.ts +1 -1
  4. package/commands/getStarted.js +16 -64
  5. package/commands/getStartedV2.d.ts +9 -0
  6. package/commands/getStartedV2.js +39 -0
  7. package/commands/project/dev/unifiedFlow.js +1 -1
  8. package/commands/project/migrate.js +14 -4
  9. package/commands/project/upload.d.ts +2 -2
  10. package/commands/project/upload.js +1 -1
  11. package/commands/testAccount/create.js +0 -3
  12. package/lang/en.d.ts +1 -23
  13. package/lang/en.js +1 -23
  14. package/lib/__tests__/hasFeature.test.js +7 -145
  15. package/lib/constants.d.ts +0 -1
  16. package/lib/constants.js +0 -1
  17. package/lib/dependencyManagement.d.ts +5 -0
  18. package/lib/dependencyManagement.js +9 -0
  19. package/lib/hasFeature.js +0 -6
  20. package/lib/mcp/setup.js +1 -1
  21. package/lib/projectProfiles.d.ts +1 -1
  22. package/lib/projectProfiles.js +2 -10
  23. package/lib/projects/create/v3.js +2 -3
  24. package/lib/projects/localDev/helpers/project.d.ts +2 -2
  25. package/lib/projects/localDev/helpers/project.js +6 -5
  26. package/lib/projects/structure.d.ts +2 -2
  27. package/lib/projects/upload.d.ts +1 -2
  28. package/lib/projects/upload.js +0 -1
  29. package/lib/ui/SpinniesManager.js +8 -105
  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.js +2 -2
  49. package/mcp-server/tools/project/GetConfigValuesTool.js +1 -1
  50. package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -1
  51. package/mcp-server/tools/project/UploadProjectTools.js +2 -2
  52. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  53. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +1 -1
  54. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +1 -1
  55. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +1 -1
  56. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +2 -2
  57. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +2 -2
  58. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +1 -1
  59. package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +1 -1
  60. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +1 -1
  61. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +1 -1
  62. package/mcp-server/tools/project/constants.d.ts +1 -1
  63. package/mcp-server/tools/project/constants.js +3 -9
  64. package/package.json +7 -2
  65. package/types/Yargs.d.ts +1 -1
  66. package/ui/components/Ascii.d.ts +10 -0
  67. package/ui/components/Ascii.js +11 -0
  68. package/ui/components/HorizontalSelectPrompt.js +1 -1
  69. package/ui/views/GetStarted.d.ts +7 -0
  70. package/ui/views/GetStarted.js +157 -0
  71. package/commands/app/__tests__/install.test.d.ts +0 -1
  72. package/commands/app/__tests__/install.test.js +0 -47
  73. package/commands/app/install.d.ts +0 -8
  74. package/commands/app/install.js +0 -122
@@ -16,7 +16,7 @@ const inputSchema = {
16
16
  const inputSchemaZodObject = z.object({
17
17
  ...inputSchema,
18
18
  });
19
- const toolName = 'get-feature-config-schema';
19
+ const toolName = 'get-hubspot-feature-config-schema';
20
20
  export class GetConfigValuesTool extends Tool {
21
21
  constructor(mcpServer) {
22
22
  super(mcpServer);
@@ -24,7 +24,7 @@ const inputSchema = {
24
24
  const inputSchemaZodObject = z.object({
25
25
  ...inputSchema,
26
26
  });
27
- const toolName = 'guided-walkthrough-cli';
27
+ const toolName = 'guided-walkthrough-hubspot-cli';
28
28
  export class GuidedWalkthroughTool extends Tool {
29
29
  constructor(mcpServer) {
30
30
  super(mcpServer);
@@ -11,7 +11,7 @@ const inputSchema = {
11
11
  const inputSchemaZodObject = z.object({
12
12
  ...inputSchema,
13
13
  });
14
- const toolName = 'upload-project';
14
+ const toolName = 'upload-hubspot-project';
15
15
  export class UploadProjectTools extends Tool {
16
16
  constructor(mcpServer) {
17
17
  super(mcpServer);
@@ -24,7 +24,7 @@ export class UploadProjectTools extends Tool {
24
24
  register() {
25
25
  return this.mcpServer.registerTool(toolName, {
26
26
  title: 'Upload HubSpot Project',
27
- description: 'DO NOT run this tool unless the user specifies they would like to upload the project, it is potentially destructive. Uploads the HubSpot project in current working directory. If the project does not exist, it will be created. MUST be ran from within the project directory.',
27
+ description: 'Uploads the HubSpot project in current working directory. If the project does not exist, it will be created. MUST be ran from within the project directory. DO NOT run this tool unless the user specifies they would like to upload the project, it is potentially destructive',
28
28
  inputSchema,
29
29
  }, this.handler);
30
30
  }
@@ -9,7 +9,7 @@ const inputSchema = {
9
9
  };
10
10
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
11
11
  const inputSchemaZodObject = z.object({ ...inputSchema });
12
- const toolName = 'validate-project';
12
+ const toolName = 'validate-hubspot-project';
13
13
  export class ValidateProjectTool extends Tool {
14
14
  constructor(mcpServer) {
15
15
  super(mcpServer);
@@ -28,7 +28,7 @@ describe('mcp-server/tools/project/AddFeatureToProject', () => {
28
28
  describe('register', () => {
29
29
  it('should register tool with correct parameters', () => {
30
30
  const result = tool.register();
31
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('add-feature-to-project', expect.objectContaining({
31
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('add-feature-to-hubspot-project', expect.objectContaining({
32
32
  title: 'Add feature to HubSpot Project',
33
33
  description: expect.stringContaining('Adds a feature to an existing HubSpot project'),
34
34
  inputSchema: expect.any(Object),
@@ -29,7 +29,7 @@ describe('mcp-server/tools/project/CreateProjectTool', () => {
29
29
  describe('register', () => {
30
30
  it('should register tool with correct parameters', () => {
31
31
  const result = tool.register();
32
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-project', {
32
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-hubspot-project', {
33
33
  title: 'Create HubSpot Project',
34
34
  description: 'Creates a HubSpot project with the provided name and outputs it in the provided destination',
35
35
  inputSchema: expect.any(Object),
@@ -26,7 +26,7 @@ describe('mcp-server/tools/project/DeployProject', () => {
26
26
  describe('register', () => {
27
27
  it('should register tool with correct parameters', () => {
28
28
  const result = tool.register();
29
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('deploy-project', {
29
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('deploy-hubspot-project', {
30
30
  title: 'Deploy a build of HubSpot Project',
31
31
  description: expect.stringContaining('Takes a build number and a project name and deploys that build of the project'),
32
32
  inputSchema: expect.any(Object),
@@ -24,9 +24,9 @@ describe('mcp-server/tools/project/DocFetchTool', () => {
24
24
  describe('register', () => {
25
25
  it('should register tool with correct parameters', () => {
26
26
  const result = tool.register();
27
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('fetch-doc', {
27
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('fetch-hubspot-doc', {
28
28
  title: 'Fetch HubSpot Developer Documentation (single file)',
29
- description: 'Always use this immediately after `search-docs` and before creating a plan, writing code, or answering technical questions. This tool retrieves the full, authoritative content of a HubSpot Developer Documentation page from its URL, ensuring responses are accurate, up-to-date, and grounded in the official docs.',
29
+ description: 'Always use this immediately after `search-hubspot-docs` and before creating a plan, writing code, or answering technical questions. This tool retrieves the full, authoritative content of a HubSpot Developer Documentation page from its URL, ensuring responses are accurate, up-to-date, and grounded in the official docs.',
30
30
  inputSchema: expect.any(Object),
31
31
  }, tool.handler);
32
32
  expect(result).toBe(mockRegisteredTool);
@@ -27,9 +27,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
27
27
  describe('register', () => {
28
28
  it('should register tool with correct parameters', () => {
29
29
  const result = tool.register();
30
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('search-docs', {
30
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('search-hubspot-docs', {
31
31
  title: 'Search HubSpot Developer Documentation',
32
- description: 'Use this first whenever you need details about HubSpot APIs, SDKs, integrations, or developer platform features. This searches the official HubSpot Developer Documentation and returns the most relevant pages, each with a URL for use in `fetch-doc`. Always follow this with a fetch to get the full, authoritative content before making plans or writing answers.',
32
+ description: 'Use this first whenever you need details about HubSpot APIs, SDKs, integrations, or developer platform features. This searches the official HubSpot Developer Documentation and returns the most relevant pages, each with a URL for use in `fetch-hubspot-doc`. Always follow this with a fetch to get the full, authoritative content before making plans or writing answers.',
33
33
  inputSchema: expect.any(Object),
34
34
  }, tool.handler);
35
35
  expect(result).toBe(mockRegisteredTool);
@@ -25,7 +25,7 @@ describe('mcp-server/tools/project/GetConfigValuesTool', () => {
25
25
  describe('register', () => {
26
26
  it('should register tool with correct parameters', () => {
27
27
  const result = tool.register();
28
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('get-feature-config-schema', {
28
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('get-hubspot-feature-config-schema', {
29
29
  title: 'Fetch the JSON Schema for component',
30
30
  description: expect.stringContaining('Fetches and returns the JSON schema for the provided feature'),
31
31
  inputSchema: expect.objectContaining({
@@ -21,7 +21,7 @@ describe('mcp-server/tools/project/GuidedWalkthroughTool', () => {
21
21
  describe('register', () => {
22
22
  it('should register tool with correct parameters', () => {
23
23
  const result = tool.register();
24
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('guided-walkthrough-cli', {
24
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('guided-walkthrough-hubspot-cli', {
25
25
  title: 'Guided walkthrough of the CLI',
26
26
  description: 'Give the user a guided walkthrough of the HubSpot CLI.',
27
27
  inputSchema: expect.any(Object),
@@ -21,7 +21,7 @@ describe('mcp-server/tools/project/UploadProjectTools', () => {
21
21
  describe('register', () => {
22
22
  it('should register tool with correct parameters', () => {
23
23
  const result = tool.register();
24
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('upload-project', {
24
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('upload-hubspot-project', {
25
25
  title: 'Upload HubSpot Project',
26
26
  description: expect.stringContaining('Uploads the HubSpot project in current working directory.'),
27
27
  inputSchema: expect.any(Object),
@@ -21,7 +21,7 @@ describe('mcp-server/tools/project/ValidateProjectTool', () => {
21
21
  describe('register', () => {
22
22
  it('should register tool with correct parameters', () => {
23
23
  const result = tool.register();
24
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('validate-project', {
24
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('validate-hubspot-project', {
25
25
  title: expect.stringContaining('Validate HubSpot Project'),
26
26
  description: expect.stringContaining('Validates the HubSpot project and its configuration files.'),
27
27
  inputSchema: expect.any(Object),
@@ -1,6 +1,6 @@
1
1
  import z from 'zod';
2
2
  export declare const absoluteProjectPath: z.ZodString;
3
3
  export declare const absoluteCurrentWorkingDirectory: z.ZodString;
4
- export declare const features: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">, z.ZodLiteral<"workflow-action">, z.ZodLiteral<"workflow-action-tool">, z.ZodLiteral<"app-object">, z.ZodLiteral<"app-event">, z.ZodLiteral<"scim">, z.ZodLiteral<"page">]>, "many">>;
4
+ export declare const features: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">, z.ZodLiteral<"workflow-action">, z.ZodLiteral<"workflow-action-tool">, z.ZodLiteral<"app-object">, z.ZodLiteral<"scim">]>, "many">>;
5
5
  export declare const docsSearchQuery: z.ZodString;
6
6
  export declare const docUrl: z.ZodString;
@@ -9,18 +9,12 @@ export const features = z
9
9
  .array(z.union([
10
10
  z.literal('card'),
11
11
  z.literal('settings'),
12
- z
13
- .literal('app-function')
14
- .describe('Also known as a public serverless function'),
12
+ z.literal('app-function'),
15
13
  z.literal('webhooks'),
16
- z
17
- .literal('workflow-action')
18
- .describe('Also known as a custom workflow action.'),
19
- z.literal('workflow-action-tool').describe('Also known as agent tools.'),
14
+ z.literal('workflow-action'),
15
+ z.literal('workflow-action-tool'),
20
16
  z.literal('app-object'),
21
- z.literal('app-event'),
22
17
  z.literal('scim'),
23
- z.literal('page'),
24
18
  ]))
25
19
  .describe('The features to include in the project, multiple options can be selected')
26
20
  .optional();
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "7.7.34-experimental.0",
3
+ "version": "7.8.0-experimental.0",
4
4
  "description": "The official CLI for developing on HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": "https://github.com/HubSpot/hubspot-cli",
7
7
  "type": "module",
8
8
  "dependencies": {
9
9
  "@hubspot/local-dev-lib": "3.18.0",
10
- "@hubspot/project-parsing-lib": "0.8.3",
10
+ "@hubspot/project-parsing-lib": "0.8.2",
11
11
  "@hubspot/serverless-dev-runtime": "7.0.6",
12
12
  "@hubspot/theme-preview-dev-server": "0.0.10",
13
13
  "@hubspot/ui-extensions-dev-server": "0.9.8",
14
+ "@inkjs/ui": "^2.0.0",
14
15
  "archiver": "7.0.1",
15
16
  "boxen": "8.0.1",
16
17
  "chalk": "5.4.1",
@@ -18,9 +19,12 @@
18
19
  "cli-cursor": "3.1.0",
19
20
  "cli-progress": "3.12.0",
20
21
  "express": "4.21.2",
22
+ "figlet": "^1.8.2",
23
+ "figures": "^6.1.0",
21
24
  "findup-sync": "4.0.0",
22
25
  "fs-extra": "8.1.0",
23
26
  "ink": "5.2.1",
27
+ "ink-link": "^4.1.0",
24
28
  "inquirer": "12.7.0",
25
29
  "js-yaml": "4.1.0",
26
30
  "moment": "2.30.1",
@@ -39,6 +43,7 @@
39
43
  "@types/archiver": "^6.0.3",
40
44
  "@types/cli-progress": "^3.11.6",
41
45
  "@types/express": "^5.0.0",
46
+ "@types/figlet": "^1.7.0",
42
47
  "@types/findup-sync": "^4.0.5",
43
48
  "@types/fs-extra": "^11.0.4",
44
49
  "@types/inquirer": "^9.0.8",
package/types/Yargs.d.ts CHANGED
@@ -15,7 +15,7 @@ export type AccountArgs = {
15
15
  account?: string;
16
16
  };
17
17
  export type EnvironmentArgs = {
18
- 'use-env'?: boolean;
18
+ 'use-env'?: string;
19
19
  };
20
20
  export type OverwriteArgs = Options & {
21
21
  o?: boolean;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import figlet from 'figlet';
3
+ type AsciiProps = {
4
+ font?: figlet.Fonts;
5
+ horizontalLayout?: figlet.KerningMethods;
6
+ verticalLayout?: figlet.KerningMethods;
7
+ text?: string;
8
+ };
9
+ export declare function Ascii({ font, horizontalLayout, verticalLayout, text, }: AsciiProps): React.ReactNode;
10
+ export default Ascii;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import figlet from 'figlet';
3
+ import { Box, Text } from 'ink';
4
+ export function Ascii({ font = 'Slant Relief', horizontalLayout = 'default', verticalLayout = 'default', text = '', }) {
5
+ return (_jsx(Box, { alignSelf: "center", children: _jsx(Text, { color: "#FF7A59", children: figlet.textSync(text, {
6
+ font,
7
+ horizontalLayout,
8
+ verticalLayout,
9
+ }) }) }));
10
+ }
11
+ export default Ascii;
@@ -26,5 +26,5 @@ export function HorizontalSelectPrompt({ defaultOption, options, onSelect, promp
26
26
  onSelect(options[selectedIndex]);
27
27
  }
28
28
  });
29
- return (_jsxs(Box, { ...CONTAINER_STYLES, flexDirection: "column", marginTop: 1, width: "100%", alignSelf: "center", justifyContent: "center", children: [prompt && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: prompt }) })), _jsx(Box, { flexDirection: "row", justifyContent: "center", flexWrap: "wrap", width: "100%", gap: 1, children: options.map((option, index) => (_jsx(Box, { children: _jsx(Text, { backgroundColor: index === selectedIndex ? INK_COLORS.INFO_BLUE : undefined, bold: index === selectedIndex, children: ` ${option} ` }) }, index))) }), _jsx(Box, { marginTop: 1, alignSelf: "center", justifyContent: "center", children: _jsx(Text, { dimColor: true, children: "Use arrow keys to navigate, Enter to select" }) })] }));
29
+ return (_jsxs(Box, { ...CONTAINER_STYLES, flexDirection: "column", marginTop: 1, width: "100%", justifyContent: "flex-start", children: [prompt && (_jsx(Box, { marginBottom: 1, alignSelf: "flex-start", children: _jsx(Text, { children: prompt }) })), _jsx(Box, { flexDirection: "row", justifyContent: "flex-start", flexWrap: "wrap", width: "100%", gap: 1, children: options.map((option, index) => (_jsx(Box, { children: _jsx(Text, { backgroundColor: index === selectedIndex ? INK_COLORS.INFO_BLUE : undefined, bold: index === selectedIndex, children: ` ${option} ` }) }, index))) }), _jsx(Box, { marginTop: 1, alignSelf: "flex-start", justifyContent: "flex-start", children: _jsx(Text, { dimColor: true, children: "Use arrow keys to navigate, Enter to select" }) })] }));
30
30
  }
@@ -0,0 +1,7 @@
1
+ import { ArgumentsCamelCase } from 'yargs';
2
+ import { GetStartedArgs } from '../../commands/getStarted.js';
3
+ export type GetStartedProps = {
4
+ args: ArgumentsCamelCase<GetStartedArgs>;
5
+ };
6
+ export declare function getGetStarted(props: GetStartedProps): React.ReactNode;
7
+ export declare function GetStarted({ args }: GetStartedProps): React.ReactNode;
@@ -0,0 +1,157 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import fs from 'fs-extra';
4
+ import { useTerminalSize } from '../lib/useTerminalSize.js';
5
+ import { uiAccountDescription } from '../../lib/ui/index.js';
6
+ import { Box, Text } from 'ink';
7
+ import figures from 'figures';
8
+ import { AlertBox, InfoBox, } from '../components/StatusMessageBoxes.js';
9
+ import { commands } from '../../lang/en.js';
10
+ import Ascii from '../components/Ascii.js';
11
+ import { HorizontalSelectPrompt } from '../components/HorizontalSelectPrompt.js';
12
+ import { INK_COLORS } from '../styles.js';
13
+ import { TextInput, Spinner, ConfirmInput, ThemeProvider, extendTheme, defaultTheme, Badge, } from '@inkjs/ui';
14
+ import { getCwd } from '@hubspot/local-dev-lib/path';
15
+ import path from 'path';
16
+ import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
17
+ import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, PROJECT_CONFIG_FILE, } from '../../lib/constants.js';
18
+ import { getProjectConfig, validateProjectConfig, writeProjectConfig, } from '../../lib/projects/config.js';
19
+ import { getProjectPackageJsonLocations, installPackages, } from '../../lib/dependencyManagement.js';
20
+ import { handleProjectUpload } from '../../lib/projects/upload.js';
21
+ import { useV3Api } from '../../lib/projects/platformVersion.js';
22
+ import { pollProjectBuildAndDeploy } from '../../lib/projects/pollProjectBuildAndDeploy.js';
23
+ import { getProjectBuildDetailUrl, getProjectDeployDetailUrl, } from '../../lib/projects/urls.js';
24
+ import { BoxWithTitle } from '../components/BoxWithTitle.js';
25
+ export function getGetStarted(props) {
26
+ return _jsx(GetStarted, { ...props });
27
+ }
28
+ const customTheme = extendTheme(defaultTheme, {
29
+ components: {
30
+ Spinner: {
31
+ styles: {
32
+ frame: () => ({
33
+ color: '#FF7A59',
34
+ }),
35
+ },
36
+ },
37
+ },
38
+ });
39
+ export function GetStarted({ args }) {
40
+ const { derivedAccountId } = args;
41
+ const toolTips = [
42
+ 'Run hs doctor to diagnose common cli installation problems',
43
+ 'Use hs project add to quickly add new features to your project',
44
+ 'Unified apps are coming to the cli soon',
45
+ 'Use hs test-account create to create and seed test accounts',
46
+ ];
47
+ const accountName = uiAccountDescription(derivedAccountId);
48
+ const { columns, rows } = useTerminalSize();
49
+ const templateSource = 'robrown-hubspot/hubspot-project-components-ua-app-objects-beta';
50
+ const projectTemplate = {
51
+ name: 'private-app-get-started-template',
52
+ label: 'CRM getting started project with private apps',
53
+ path: 'projects/private-app-get-started-template',
54
+ };
55
+ const repo = templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH;
56
+ const cloneRepo = async () => {
57
+ await cloneGithubRepo(repo, projectSource, {
58
+ sourceDir: projectTemplate.path,
59
+ tag: undefined,
60
+ hideLogs: true,
61
+ });
62
+ setIsCloned(true);
63
+ };
64
+ const [projectType, setProjectType] = useState('');
65
+ const [showProjectTypeError, setShowProjectTypeError] = useState(false);
66
+ const [projectName, setProjectName] = useState('');
67
+ const [projectSource, setProjectSource] = useState('');
68
+ const [isCloned, setIsCloned] = useState(false);
69
+ const [isConfigUpdated, setIsConfigUpdated] = useState(false);
70
+ const [areDepsInstalled, setAreDepsInstalled] = useState(false);
71
+ const [isUploading, setIsUploading] = useState(null);
72
+ const [isUploaded, setIsUploaded] = useState(false);
73
+ const [projectPollResult, setProjectPollResult] = useState(null);
74
+ useEffect(() => {
75
+ if (projectType === 'CMS') {
76
+ setShowProjectTypeError(true);
77
+ }
78
+ else {
79
+ setShowProjectTypeError(false);
80
+ }
81
+ }, [projectType]);
82
+ const updateProjectConfig = () => {
83
+ const projectConfigPath = path.join(projectSource, PROJECT_CONFIG_FILE);
84
+ const parsedConfigFile = JSON.parse(fs.readFileSync(projectConfigPath).toString());
85
+ writeProjectConfig(projectConfigPath, {
86
+ ...parsedConfigFile,
87
+ name: projectName,
88
+ });
89
+ setIsConfigUpdated(true);
90
+ };
91
+ const installDeps = async () => {
92
+ const installLocations = await getProjectPackageJsonLocations(projectSource);
93
+ await installPackages({
94
+ installLocations: installLocations,
95
+ });
96
+ setAreDepsInstalled(true);
97
+ };
98
+ useEffect(() => {
99
+ if (projectSource && !isCloned) {
100
+ setTimeout(() => {
101
+ cloneRepo();
102
+ }, 500);
103
+ }
104
+ if (projectSource && isCloned) {
105
+ setTimeout(() => {
106
+ updateProjectConfig();
107
+ }, 500);
108
+ }
109
+ }, [projectSource, isCloned]);
110
+ useEffect(() => {
111
+ if (isConfigUpdated && !areDepsInstalled) {
112
+ setTimeout(() => {
113
+ installDeps();
114
+ }, 500);
115
+ }
116
+ }, [isConfigUpdated]);
117
+ const uploadProject = async () => {
118
+ const { projectConfig: newProjectConfig, projectDir: newProjectDir } = await getProjectConfig(projectSource);
119
+ validateProjectConfig(newProjectConfig, newProjectDir);
120
+ const { result, uploadError } = await handleProjectUpload({
121
+ accountId: derivedAccountId,
122
+ projectConfig: newProjectConfig,
123
+ projectDir: newProjectDir,
124
+ callbackFunc: pollProjectBuildAndDeploy,
125
+ uploadMessage: 'Initial upload from get-started command',
126
+ forceCreate: true, // Auto-create project on HubSpot
127
+ isUploadCommand: false,
128
+ sendIR: useV3Api(newProjectConfig.platformVersion),
129
+ skipValidation: false,
130
+ });
131
+ if (result) {
132
+ setIsUploaded(true);
133
+ setProjectPollResult(result);
134
+ }
135
+ };
136
+ const [toolTipIndex, setToolTipIndex] = useState(0);
137
+ useEffect(() => {
138
+ const interval = setInterval(() => {
139
+ setToolTipIndex(prevIndex => (prevIndex + 1) % toolTips.length);
140
+ }, 5000);
141
+ return () => clearInterval(interval);
142
+ }, []);
143
+ useEffect(() => {
144
+ if (isUploading === true) {
145
+ setTimeout(() => {
146
+ uploadProject();
147
+ }, 500);
148
+ }
149
+ }, [isUploading]);
150
+ return (_jsx(ThemeProvider, { theme: customTheme, children: _jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsx(Ascii, { text: "HubSpot CLI", font: "Standard" }), _jsx(InfoBox, { title: commands.getStarted.startTitle, message: commands.getStarted.startDescription +
151
+ '\n' +
152
+ commands.getStarted.guideOverview(accountName) }), projectType !== 'App' && (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(HorizontalSelectPrompt, { prompt: commands.getStarted.prompts.selectOption, options: ['App', 'CMS'], onSelect: setProjectType, defaultOption: 'App' }), showProjectTypeError && (_jsx(AlertBox, { title: "Bad Choice", message: "I didn't hook up the CMS flow for this demo" }))] })), projectType === 'App' && (_jsx(_Fragment, { children: _jsxs(Box, { flexDirection: "column", padding: 1, gap: 1, borderStyle: "round", borderColor: "#FF7A59", children: [_jsxs(Box, { flexDirection: "row", width: "100%", gap: 1, children: [_jsxs(Box, { flexDirection: "row", flexBasis: "50%", gap: 1, children: [_jsx(Text, { color: "#FF7A59", children: "Project Name:" }), projectName === '' ? (_jsx(TextInput, { placeholder: "give your project a name", onSubmit: setProjectName })) : (_jsx(Text, { children: projectName }))] }), _jsxs(Box, { flexDirection: "row", flexBasis: "50%", gap: 1, children: [_jsx(Text, { color: "#FF7A59", children: "Project Source:" }), projectSource === '' && projectName !== '' ? (_jsx(TextInput, { defaultValue: path.resolve(getCwd(), projectName), placeholder: path.resolve(getCwd(), projectName), onSubmit: setProjectSource })) : (_jsx(Text, { children: projectSource }))] })] }), projectSource && (_jsx(Box, { flexDirection: "row", width: "100%", gap: 1, children: isCloned ? (_jsxs(Text, { color: INK_COLORS.SUCCESS_GREEN, children: [figures.tick, " Project downloaded"] })) : (_jsx(Spinner, { type: "dots", label: "Downloading project" })) })), projectSource && isCloned && (_jsx(Box, { flexDirection: "row", width: "100%", gap: 1, children: isConfigUpdated ? (_jsxs(Text, { color: INK_COLORS.SUCCESS_GREEN, children: [figures.tick, " Project config updated"] })) : (_jsx(Spinner, { type: "dots", label: "Updating project config" })) })), isConfigUpdated && (_jsxs(Box, { flexDirection: "column", width: "100%", gap: 0, children: [_jsxs(Box, { flexDirection: "row", width: "100%", gap: 1, children: [_jsx(Badge, { color: INK_COLORS.SUCCESS_GREEN, children: "Success" }), _jsx(Text, { children: commands.project.create.logs.success(projectName, projectSource) })] }), _jsx(Text, { children: commands.getStarted.prompts.projectCreated.title }), _jsx(Text, { children: commands.getStarted.prompts.projectCreated.description })] })), isConfigUpdated && (_jsx(Box, { flexDirection: "row", width: "100%", gap: 1, children: areDepsInstalled ? (_jsxs(Text, { color: INK_COLORS.SUCCESS_GREEN, children: [figures.tick, " Project dependencies installed"] })) : (_jsx(Spinner, { type: "dots", label: "Installing project dependencies" })) })), areDepsInstalled && isUploading === null && (_jsxs(Box, { flexDirection: "row", width: "100%", gap: 1, children: [_jsx(Text, { color: INK_COLORS.WARNING_YELLOW, children: "Would you like to upload your project to HubSpot?" }), isUploading === null ? (_jsx(ConfirmInput, { onConfirm: () => {
153
+ setIsUploading(true);
154
+ }, onCancel: () => {
155
+ setIsUploading(false);
156
+ } })) : (_jsx(Text, { color: INK_COLORS.SUCCESS_GREEN, children: isUploading }))] })), isUploading === true && (_jsx(Box, { flexDirection: "row", width: "100%", gap: 1, children: isUploaded ? (_jsxs(Text, { color: INK_COLORS.SUCCESS_GREEN, children: [figures.tick, " Project uploaded, built, and deployed"] })) : (_jsx(Spinner, { type: "dots", label: "Uploading, building, and deploying project" })) })), projectPollResult && (_jsxs(Box, { flexDirection: "column", width: "100%", gap: 1, children: [_jsxs(Box, { flexDirection: "row", alignSelf: "center", width: "100%", gap: 1, children: [_jsx(Text, { color: "#FF7A59", children: "View build in HubSpot:" }), _jsx(Text, { color: INK_COLORS.INFO_BLUE, children: getProjectBuildDetailUrl(projectName, projectPollResult.buildId, derivedAccountId) })] }), _jsxs(Box, { flexDirection: "row", alignSelf: "center", width: "100%", gap: 1, children: [_jsx(Text, { color: "#FF7A59", children: "View deploy in HubSpot:" }), _jsx(Text, { color: INK_COLORS.INFO_BLUE, children: getProjectDeployDetailUrl(projectName, projectPollResult.deployResult?.deployId || 0, derivedAccountId) })] })] }))] }) })), _jsx(BoxWithTitle, { title: "Tips", message: toolTips[toolTipIndex], borderColor: "gray" })] }) }));
157
+ }
@@ -1 +0,0 @@
1
- export {};
@@ -1,47 +0,0 @@
1
- import yargs from 'yargs';
2
- import { addAccountOptions, addConfigOptions, addUseEnvironmentOptions, addGlobalOptions, addJSONOutputOptions, } from '../../../lib/commonOpts.js';
3
- import installCommand from '../install.js';
4
- vi.mock('../../../lib/commonOpts');
5
- describe('commands/app/install', () => {
6
- const yargsMock = yargs;
7
- describe('command', () => {
8
- it('should have the correct command structure', () => {
9
- expect(installCommand.command).toEqual('install <test-account-id>');
10
- });
11
- });
12
- describe('describe', () => {
13
- it('should provide a description', () => {
14
- expect(installCommand.describe).not.toBeDefined();
15
- });
16
- });
17
- describe('builder', () => {
18
- it('should support the correct options', () => {
19
- installCommand.builder(yargsMock);
20
- expect(addGlobalOptions).toHaveBeenCalledTimes(1);
21
- expect(addGlobalOptions).toHaveBeenCalledWith(yargsMock);
22
- expect(addAccountOptions).toHaveBeenCalledTimes(1);
23
- expect(addAccountOptions).toHaveBeenCalledWith(yargsMock);
24
- expect(addConfigOptions).toHaveBeenCalledTimes(1);
25
- expect(addConfigOptions).toHaveBeenCalledWith(yargsMock);
26
- expect(addUseEnvironmentOptions).toHaveBeenCalledTimes(1);
27
- expect(addUseEnvironmentOptions).toHaveBeenCalledWith(yargsMock);
28
- expect(addJSONOutputOptions).toHaveBeenCalledTimes(1);
29
- expect(addJSONOutputOptions).toHaveBeenCalledWith(yargsMock);
30
- expect(yargsMock.positional).toHaveBeenCalledTimes(1);
31
- expect(yargsMock.positional).toHaveBeenCalledWith('test-account-id', expect.objectContaining({
32
- type: 'number',
33
- required: true,
34
- describe: expect.any(String),
35
- }));
36
- expect(yargsMock.option).toHaveBeenCalledTimes(2);
37
- expect(yargsMock.option).toHaveBeenCalledWith('app-uid', expect.objectContaining({
38
- type: 'string',
39
- describe: expect.any(String),
40
- }));
41
- expect(yargsMock.option).toHaveBeenCalledWith('project-name', expect.objectContaining({
42
- type: 'string',
43
- describe: expect.any(String),
44
- }));
45
- });
46
- });
47
- });
@@ -1,8 +0,0 @@
1
- import { CommonArgs, ConfigArgs, AccountArgs, EnvironmentArgs, YargsCommandModule, JSONOutputArgs } from '../../types/Yargs.js';
2
- type InstallAppArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & JSONOutputArgs & {
3
- appUid?: string;
4
- projectName?: string;
5
- testAccountId: number;
6
- };
7
- declare const installAppCommand: YargsCommandModule<unknown, InstallAppArgs>;
8
- export default installAppCommand;
@@ -1,122 +0,0 @@
1
- import { fetchDeveloperTestAccountOauthAppInstallStatus, installOauthAppIntoDeveloperTestAccount, } from '@hubspot/local-dev-lib/api/developerTestAccounts';
2
- import { trackCommandUsage } from '../../lib/usageTracking.js';
3
- import { commands } from '../../lang/en.js';
4
- import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
5
- import { makeYargsBuilder } from '../../lib/yargsUtils.js';
6
- import { APP_AUTH_TYPES } from '../../lib/constants.js';
7
- import { uiLogger } from '../../lib/ui/logger.js';
8
- import SpinniesManager from '../../lib/ui/SpinniesManager.js';
9
- import { poll } from '../../lib/polling.js';
10
- import { getProjectConfig, validateProjectConfig, } from '../../lib/projects/config.js';
11
- import { handleTranslate } from '../../lib/projects/upload.js';
12
- import { isAppIRNode } from '../../lib/projects/structure.js';
13
- import { logError } from '../../lib/errorHandlers/index.js';
14
- const command = 'install <test-account-id>';
15
- const describe = undefined; // commands.app.subcommands.install.describe;
16
- async function handler(args) {
17
- const { derivedAccountId, appUid, projectName, testAccountId, formatOutputAsJson, } = args;
18
- trackCommandUsage('app-install', {}, derivedAccountId);
19
- const jsonOutput = {};
20
- let targetProjectName = projectName;
21
- let targetAppUid = appUid;
22
- const { projectConfig, projectDir } = await getProjectConfig();
23
- if (!targetProjectName) {
24
- validateProjectConfig(projectConfig, projectDir);
25
- targetProjectName = projectConfig?.name;
26
- }
27
- if (!targetProjectName) {
28
- uiLogger.error(commands.app.subcommands.install.errors.mustSpecifyProjectName);
29
- process.exit(EXIT_CODES.ERROR);
30
- }
31
- let isAppOauth = true;
32
- if (!targetAppUid) {
33
- const intermediateRepresentation = await handleTranslate(projectDir, projectConfig, derivedAccountId, true, undefined);
34
- if (intermediateRepresentation) {
35
- Object.values(intermediateRepresentation.intermediateNodesIndexedByUid).forEach(node => {
36
- if (isAppIRNode(node)) {
37
- targetAppUid = node.uid;
38
- isAppOauth = node.config.auth.type === APP_AUTH_TYPES.OAUTH;
39
- }
40
- });
41
- }
42
- }
43
- if (!targetAppUid) {
44
- uiLogger.error(commands.app.subcommands.install.errors.noAppUidFound);
45
- process.exit(EXIT_CODES.ERROR);
46
- }
47
- if (!isAppOauth) {
48
- uiLogger.error(commands.app.subcommands.install.errors.appMustBeOauth);
49
- process.exit(EXIT_CODES.ERROR);
50
- }
51
- try {
52
- const { data } = await installOauthAppIntoDeveloperTestAccount(derivedAccountId, testAccountId, targetProjectName, targetAppUid);
53
- if (data?.authCodes.length > 0) {
54
- jsonOutput.authCode = data.authCodes[0].authCode;
55
- }
56
- }
57
- catch (err) {
58
- logError(err);
59
- process.exit(EXIT_CODES.ERROR);
60
- }
61
- SpinniesManager.init({
62
- succeedColor: 'white',
63
- });
64
- SpinniesManager.add('installApp', {
65
- text: commands.app.subcommands.install.polling.start,
66
- });
67
- let appInstallSucceeded = false;
68
- try {
69
- await poll(() => fetchDeveloperTestAccountOauthAppInstallStatus(derivedAccountId, targetProjectName, targetAppUid), {
70
- successStates: ['SUCCESS'],
71
- errorStates: [],
72
- });
73
- appInstallSucceeded = true;
74
- }
75
- catch (err) {
76
- SpinniesManager.fail('installApp');
77
- logError(err);
78
- process.exit(EXIT_CODES.ERROR);
79
- }
80
- if (!appInstallSucceeded) {
81
- SpinniesManager.fail('installApp');
82
- process.exit(EXIT_CODES.ERROR);
83
- }
84
- SpinniesManager.succeed('installApp', {
85
- text: commands.app.subcommands.install.polling.success,
86
- });
87
- if (formatOutputAsJson) {
88
- uiLogger.json(jsonOutput);
89
- }
90
- process.exit(EXIT_CODES.SUCCESS);
91
- }
92
- function installAppBuilder(yargs) {
93
- yargs.positional('test-account-id', {
94
- describe: commands.app.subcommands.install.positionals.testAccountId,
95
- required: true,
96
- type: 'number',
97
- });
98
- yargs.option('app-uid', {
99
- describe: commands.app.subcommands.install.options.appUid,
100
- type: 'string',
101
- });
102
- yargs.option('project-name', {
103
- describe: commands.app.subcommands.install.options.projectName,
104
- type: 'string',
105
- });
106
- yargs.example('install 1234567890 --app-uid=my-app-uid --project-name=my-project', commands.app.subcommands.install.example);
107
- return yargs;
108
- }
109
- const builder = makeYargsBuilder(installAppBuilder, command, commands.app.subcommands.install.describe, {
110
- useGlobalOptions: true,
111
- useAccountOptions: true,
112
- useConfigOptions: true,
113
- useEnvironmentOptions: true,
114
- useJSONOutputOptions: true,
115
- });
116
- const installAppCommand = {
117
- command,
118
- describe,
119
- handler,
120
- builder,
121
- };
122
- export default installAppCommand;