@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,136 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { sanitizeFileName, untildify } from '@hubspot/local-dev-lib/path';
3
+ import { useApp, useFocus, useInput } from 'ink';
4
+ import { useCallback, useEffect, useReducer } from 'react';
5
+ import { commands } from '../../../lang/en.js';
6
+ import { createProjectAction, trackGetStartedUsage, uploadAndDeployAction, } from '../../../lib/getStartedV2Actions.js';
7
+ import { uiAccountDescription } from '../../../lib/ui/index.js';
8
+ import { ACTION_STATUSES, GET_STARTED_FLOW_STEPS, } from '../../lib/constants.js';
9
+ import { flowReducer } from './reducer.js';
10
+ import { ProjectSetupScreen } from './screens/ProjectSetupScreen.js';
11
+ import { UploadScreen } from './screens/UploadScreen.js';
12
+ import { getProject } from './selectors.js';
13
+ export const DEFAULT_PROJECT_NAME = 'my-project';
14
+ export function getGetStartedFlow(props) {
15
+ return _jsx(GetStartedFlow, { ...props });
16
+ }
17
+ function determineInitialStep(initialName, initialDest) {
18
+ if (initialName && initialDest) {
19
+ return GET_STARTED_FLOW_STEPS.CREATING;
20
+ }
21
+ else if (initialName) {
22
+ return GET_STARTED_FLOW_STEPS.DEST_INPUT;
23
+ }
24
+ else if (initialDest) {
25
+ return GET_STARTED_FLOW_STEPS.NAME_INPUT;
26
+ }
27
+ return GET_STARTED_FLOW_STEPS.SELECT;
28
+ }
29
+ export function GetStartedFlow({ derivedAccountId, initialName, initialDest, }) {
30
+ useFocus({ autoFocus: true });
31
+ const { exit } = useApp();
32
+ const accountName = uiAccountDescription(derivedAccountId);
33
+ const getInitialState = () => ({
34
+ step: determineInitialStep(initialName, initialDest),
35
+ project: {
36
+ name: initialName || DEFAULT_PROJECT_NAME,
37
+ destination: initialDest
38
+ ? untildify(initialDest)
39
+ : sanitizeFileName(DEFAULT_PROJECT_NAME),
40
+ },
41
+ app: {
42
+ selectedLabel: initialName ? commands.getStarted.prompts.options.app : '',
43
+ },
44
+ statuses: {
45
+ create: initialName ? ACTION_STATUSES.RUNNING : ACTION_STATUSES.IDLE,
46
+ upload: ACTION_STATUSES.IDLE,
47
+ },
48
+ });
49
+ const [state, dispatch] = useReducer(flowReducer, getInitialState());
50
+ const project = getProject(state);
51
+ // Only auto-update dest from name if dest wasn't provided via CLI
52
+ useEffect(() => {
53
+ if (!initialDest) {
54
+ dispatch({
55
+ type: 'SET_PROJECT_DEST',
56
+ payload: sanitizeFileName(project.name),
57
+ });
58
+ }
59
+ }, [project.name, initialDest]);
60
+ const handleSelect = useCallback(async (item) => {
61
+ if (item.disabled)
62
+ return;
63
+ await trackGetStartedUsage({ step: 'select-option', type: item.value }, derivedAccountId);
64
+ dispatch({ type: 'SET_SELECTED_LABEL', payload: item.label });
65
+ dispatch({ type: 'START_PROJECT_CREATION' });
66
+ }, [derivedAccountId]);
67
+ const handleNameSubmit = useCallback(() => {
68
+ dispatch({ type: 'SET_STEP', payload: GET_STARTED_FLOW_STEPS.DEST_INPUT });
69
+ }, []);
70
+ const handleDestSubmit = useCallback(async () => {
71
+ dispatch({ type: 'SET_STEP', payload: GET_STARTED_FLOW_STEPS.CREATING });
72
+ try {
73
+ await createProjectAction({
74
+ projectName: project.name,
75
+ projectDest: project.destination,
76
+ });
77
+ await trackGetStartedUsage({ successful: true, step: 'github-clone' }, derivedAccountId);
78
+ await trackGetStartedUsage({ successful: true, step: 'project-creation' }, derivedAccountId);
79
+ dispatch({ type: 'PROJECT_CREATION_SUCCESS' });
80
+ }
81
+ catch (error) {
82
+ const errorMessage = error instanceof Error
83
+ ? error.message
84
+ : commands.getStarted.v2.unknownError;
85
+ await trackGetStartedUsage({ successful: false, step: 'project-creation' }, derivedAccountId);
86
+ dispatch({ type: 'PROJECT_CREATION_ERROR', payload: errorMessage });
87
+ }
88
+ }, [derivedAccountId, project.name, project.destination]);
89
+ const handleUploadStart = useCallback(async () => {
90
+ await trackGetStartedUsage({ step: 'upload-decision', type: 'upload' }, derivedAccountId);
91
+ dispatch({ type: 'START_UPLOAD' });
92
+ try {
93
+ const result = await uploadAndDeployAction({
94
+ accountId: derivedAccountId,
95
+ projectDest: project.destination,
96
+ });
97
+ await trackGetStartedUsage({ successful: true, step: 'upload' }, derivedAccountId);
98
+ dispatch({
99
+ type: 'UPLOAD_SUCCESS',
100
+ payload: result,
101
+ });
102
+ }
103
+ catch (error) {
104
+ const errorMessage = error instanceof Error
105
+ ? error.message
106
+ : commands.getStarted.v2.unknownError;
107
+ await trackGetStartedUsage({ successful: false, step: 'upload' }, derivedAccountId);
108
+ dispatch({ type: 'UPLOAD_ERROR', payload: errorMessage });
109
+ }
110
+ }, [derivedAccountId, project.destination]);
111
+ const handleNameChange = useCallback((value) => {
112
+ dispatch({ type: 'SET_PROJECT_NAME', payload: value });
113
+ }, []);
114
+ const handleDestChange = useCallback((value) => {
115
+ dispatch({ type: 'SET_PROJECT_DEST', payload: value });
116
+ }, []);
117
+ useInput((_, key) => {
118
+ const hasError = state.statuses.create === ACTION_STATUSES.ERROR ||
119
+ state.statuses.upload === ACTION_STATUSES.ERROR;
120
+ if (hasError) {
121
+ exit();
122
+ return;
123
+ }
124
+ if (!key.return)
125
+ return;
126
+ if (state.step === GET_STARTED_FLOW_STEPS.COMPLETE) {
127
+ handleUploadStart();
128
+ }
129
+ });
130
+ if (state.step === GET_STARTED_FLOW_STEPS.UPLOADING ||
131
+ state.step === GET_STARTED_FLOW_STEPS.OPEN_APP_PROMPT) {
132
+ return _jsx(UploadScreen, { state: state, accountName: accountName });
133
+ }
134
+ // Show project setup screen for initial flow
135
+ return (_jsx(ProjectSetupScreen, { state: state, onSelectOption: handleSelect, onNameChange: handleNameChange, onNameSubmit: handleNameSubmit, onDestChange: handleDestChange, onDestSubmit: handleDestSubmit }));
136
+ }
@@ -0,0 +1,59 @@
1
+ import { ValueOf } from '@hubspot/local-dev-lib/types/Utils';
2
+ import { type UploadAndDeployResult } from '../../../lib/getStartedV2Actions.js';
3
+ import { ACTION_STATUSES, GET_STARTED_FLOW_STEPS } from '../../lib/constants.js';
4
+ export type FlowStep = ValueOf<typeof GET_STARTED_FLOW_STEPS>;
5
+ export type ActionStatus = ValueOf<typeof ACTION_STATUSES>;
6
+ export type ProjectState = {
7
+ name: string;
8
+ destination: string;
9
+ uploadResult?: UploadAndDeployResult;
10
+ };
11
+ export type AppState = {
12
+ selectedLabel: string;
13
+ };
14
+ export type ActionStatuses = {
15
+ create: ActionStatus;
16
+ upload: ActionStatus;
17
+ };
18
+ export type FlowState = {
19
+ step: FlowStep;
20
+ project: ProjectState;
21
+ app: AppState;
22
+ statuses: ActionStatuses;
23
+ error?: string;
24
+ };
25
+ type FlowAction = {
26
+ type: 'SET_STEP';
27
+ payload: FlowStep;
28
+ } | {
29
+ type: 'SET_SELECTED_LABEL';
30
+ payload: string;
31
+ } | {
32
+ type: 'SET_PROJECT_NAME';
33
+ payload: string;
34
+ } | {
35
+ type: 'SET_PROJECT_DEST';
36
+ payload: string;
37
+ } | {
38
+ type: 'SET_ERROR';
39
+ payload: string;
40
+ } | {
41
+ type: 'CLEAR_ERROR';
42
+ } | {
43
+ type: 'START_PROJECT_CREATION';
44
+ } | {
45
+ type: 'PROJECT_CREATION_SUCCESS';
46
+ } | {
47
+ type: 'PROJECT_CREATION_ERROR';
48
+ payload: string;
49
+ } | {
50
+ type: 'START_UPLOAD';
51
+ } | {
52
+ type: 'UPLOAD_SUCCESS';
53
+ payload: UploadAndDeployResult;
54
+ } | {
55
+ type: 'UPLOAD_ERROR';
56
+ payload: string;
57
+ };
58
+ export declare function flowReducer(state: FlowState, action: FlowAction): FlowState;
59
+ export {};
@@ -0,0 +1,72 @@
1
+ import { ACTION_STATUSES, GET_STARTED_FLOW_STEPS, } from '../../lib/constants.js';
2
+ export function flowReducer(state, action) {
3
+ switch (action.type) {
4
+ case 'SET_STEP':
5
+ return { ...state, step: action.payload };
6
+ case 'SET_SELECTED_LABEL':
7
+ return {
8
+ ...state,
9
+ app: { ...state.app, selectedLabel: action.payload },
10
+ };
11
+ case 'SET_PROJECT_NAME':
12
+ return {
13
+ ...state,
14
+ project: { ...state.project, name: action.payload },
15
+ };
16
+ case 'SET_PROJECT_DEST':
17
+ return {
18
+ ...state,
19
+ project: { ...state.project, destination: action.payload },
20
+ };
21
+ case 'SET_ERROR':
22
+ return { ...state, error: action.payload };
23
+ case 'CLEAR_ERROR':
24
+ return { ...state, error: undefined };
25
+ case 'START_PROJECT_CREATION':
26
+ return {
27
+ ...state,
28
+ step: GET_STARTED_FLOW_STEPS.NAME_INPUT,
29
+ statuses: { ...state.statuses, create: ACTION_STATUSES.RUNNING },
30
+ error: undefined,
31
+ };
32
+ case 'PROJECT_CREATION_SUCCESS':
33
+ return {
34
+ ...state,
35
+ step: GET_STARTED_FLOW_STEPS.COMPLETE,
36
+ statuses: { ...state.statuses, create: ACTION_STATUSES.DONE },
37
+ error: undefined,
38
+ };
39
+ case 'PROJECT_CREATION_ERROR':
40
+ return {
41
+ ...state,
42
+ statuses: { ...state.statuses, create: ACTION_STATUSES.ERROR },
43
+ error: action.payload,
44
+ };
45
+ case 'START_UPLOAD':
46
+ return {
47
+ ...state,
48
+ step: GET_STARTED_FLOW_STEPS.UPLOADING,
49
+ statuses: { ...state.statuses, upload: ACTION_STATUSES.RUNNING },
50
+ error: undefined,
51
+ };
52
+ case 'UPLOAD_SUCCESS':
53
+ return {
54
+ ...state,
55
+ step: GET_STARTED_FLOW_STEPS.OPEN_APP_PROMPT,
56
+ statuses: { ...state.statuses, upload: ACTION_STATUSES.DONE },
57
+ project: {
58
+ ...state.project,
59
+ uploadResult: action.payload,
60
+ },
61
+ error: undefined,
62
+ };
63
+ case 'UPLOAD_ERROR':
64
+ return {
65
+ ...state,
66
+ statuses: { ...state.statuses, upload: ACTION_STATUSES.ERROR },
67
+ error: action.payload,
68
+ };
69
+ default:
70
+ return state;
71
+ }
72
+ }
@@ -0,0 +1,16 @@
1
+ import { SelectInputItem } from '../../SelectInput.js';
2
+ import { FlowState } from '../reducer.js';
3
+ export declare const GET_STARTED_FLOW_OPTIONS: SelectInputItem[];
4
+ type ProjectSetupScreenProps = {
5
+ state: FlowState;
6
+ onSelectOption: (item: {
7
+ label: string;
8
+ value: string;
9
+ }) => void;
10
+ onNameChange: (value: string) => void;
11
+ onNameSubmit: () => void;
12
+ onDestChange: (value: string) => void;
13
+ onDestSubmit: () => void;
14
+ };
15
+ export declare function ProjectSetupScreen({ state, onSelectOption, onNameChange, onNameSubmit, onDestChange, onDestSubmit, }: ProjectSetupScreenProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { commands } from '../../../../lang/en.js';
4
+ import { GET_STARTED_OPTIONS } from '../../../../lib/constants.js';
5
+ import { ActionSection } from '../../ActionSection.js';
6
+ import { BoxWithTitle } from '../../BoxWithTitle.js';
7
+ import { InputField } from '../../InputField.js';
8
+ import { SelectInput } from '../../SelectInput.js';
9
+ import { INK_COLORS } from '../../../styles.js';
10
+ import { getProject } from '../selectors.js';
11
+ import { ACTION_STATUSES, GET_STARTED_FLOW_STEPS, } from '../../../lib/constants.js';
12
+ export const GET_STARTED_FLOW_OPTIONS = [
13
+ {
14
+ label: commands.getStarted.prompts.options.app,
15
+ value: GET_STARTED_OPTIONS.APP,
16
+ },
17
+ {
18
+ label: commands.getStarted.prompts.options.cmsTheme,
19
+ value: 'CMS_THEME',
20
+ disabled: true,
21
+ },
22
+ {
23
+ label: commands.getStarted.prompts.options.cmsReactModule,
24
+ value: 'CMS_REACT_MODULE',
25
+ disabled: true,
26
+ },
27
+ ];
28
+ export function ProjectSetupScreen({ state, onSelectOption, onNameChange, onNameSubmit, onDestChange, onDestSubmit, }) {
29
+ const project = getProject(state);
30
+ const titleText = commands.getStarted.v2.startTitle;
31
+ const overviewText = commands.getStarted.v2.guideOverview(state.app.selectedLabel);
32
+ const projectsText = commands.getStarted.v2.projects;
33
+ const selectPrompt = commands.getStarted.v2.prompts.selectOptionV2;
34
+ const runningProjectCreateText = commands.getStarted.v2.runningProjectCreate;
35
+ return (_jsx(BoxWithTitle, { flexGrow: 1, title: "hs get-started", borderColor: INK_COLORS.HUBSPOT_ORANGE, titleBackgroundColor: INK_COLORS.HUBSPOT_ORANGE, children: _jsxs(Box, { flexDirection: "column", rowGap: 1, children: [_jsx(Text, { bold: true, children: titleText }), state.step === GET_STARTED_FLOW_STEPS.SELECT ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: overviewText }), _jsx(Text, { children: projectsText }), _jsxs(Box, { flexDirection: "row", flexWrap: "wrap", columnGap: 1, children: [_jsx(Text, { color: INK_COLORS.HUBSPOT_TEAL, children: "?" }), _jsx(Text, { children: selectPrompt })] }), _jsx(SelectInput, { items: GET_STARTED_FLOW_OPTIONS, onSelect: onSelectOption })] })) : (_jsxs(Box, { flexDirection: "row", flexWrap: "wrap", columnGap: 1, children: [_jsx(Text, { color: INK_COLORS.HUBSPOT_TEAL, children: "?" }), _jsx(Text, { children: `${selectPrompt}` }), _jsx(Text, { color: INK_COLORS.INFO_BLUE, children: state.app.selectedLabel })] })), _jsxs(ActionSection, { status: state.statuses.create, statusText: runningProjectCreateText, errorMessage: state.statuses.create === ACTION_STATUSES.ERROR
36
+ ? `${state.error}\n\n${commands.getStarted.v2.pressKeyToExit}`
37
+ : undefined, children: [state.step !== GET_STARTED_FLOW_STEPS.SELECT && (_jsx(InputField, { flag: "name", prompt: "Enter your project name", value: project.name, isEditing: state.step === GET_STARTED_FLOW_STEPS.NAME_INPUT, onChange: onNameChange, onSubmit: onNameSubmit })), state.step !== GET_STARTED_FLOW_STEPS.SELECT &&
38
+ state.step !== GET_STARTED_FLOW_STEPS.NAME_INPUT && (_jsx(InputField, { flag: "dest", prompt: "Choose where to create the project", value: project.destination, isEditing: state.step === GET_STARTED_FLOW_STEPS.DEST_INPUT, onChange: onDestChange, onSubmit: onDestSubmit }))] }), state.step === GET_STARTED_FLOW_STEPS.COMPLETE && (_jsxs(Box, { flexDirection: "row", flexWrap: "wrap", columnGap: 1, children: [_jsx(Text, { color: INK_COLORS.HUBSPOT_TEAL, children: "?" }), _jsx(Text, { children: commands.getStarted.v2.pressEnterToContinueDeploy(state.app.selectedLabel) })] }))] }) }));
39
+ }
@@ -0,0 +1,7 @@
1
+ import { FlowState } from '../reducer.js';
2
+ type UploadScreenProps = {
3
+ state: FlowState;
4
+ accountName: string;
5
+ };
6
+ export declare function UploadScreen({ state, accountName }: UploadScreenProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import path from 'path';
3
+ import { Box, Text } from 'ink';
4
+ import { APP_KEY as AppKey } from '@hubspot/project-parsing-lib/constants';
5
+ import { commands } from '../../../../lang/en.js';
6
+ import { buildProjectTree, } from '../../../../lib/projects/components.js';
7
+ import { ActionSection } from '../../ActionSection.js';
8
+ import { BoxWithTitle } from '../../BoxWithTitle.js';
9
+ import { INK_COLORS } from '../../../styles.js';
10
+ import { getProject } from '../selectors.js';
11
+ import { ACTION_STATUSES } from '../../../lib/constants.js';
12
+ function renderProjectTree(projectName, app, projectMetadata) {
13
+ const componentsByType = new Map();
14
+ const uids = [];
15
+ // Get app UID
16
+ uids.push(app.uid || app.config?.name || 'unknown');
17
+ // Build componentsByType from projectMetadata
18
+ Object.entries(projectMetadata.components).forEach(([componentType, metadata]) => {
19
+ if (componentType === AppKey || !metadata?.hsMetaFiles)
20
+ return;
21
+ const components = metadata.hsMetaFiles.map((filePath) => ({
22
+ filename: path.basename(filePath),
23
+ isNew: false,
24
+ }));
25
+ if (components.length > 0) {
26
+ componentsByType.set(componentType, components);
27
+ }
28
+ });
29
+ const tree = buildProjectTree(projectName, uids, componentsByType, false);
30
+ return (_jsx(Box, { flexDirection: "column", children: tree.split('\n').map((line, i) => (_jsx(Text, { children: line }, i))) }));
31
+ }
32
+ export function UploadScreen({ state, accountName }) {
33
+ const project = getProject(state);
34
+ const titleText = commands.getStarted.v2.startTitle;
35
+ const uploadingProjectText = commands.getStarted.v2.uploadingProject;
36
+ return (_jsx(BoxWithTitle, { flexGrow: 1, title: "hs get-started", borderColor: INK_COLORS.HUBSPOT_ORANGE, titleBackgroundColor: INK_COLORS.HUBSPOT_ORANGE, children: _jsxs(Box, { flexDirection: "column", rowGap: 1, children: [_jsx(Text, { bold: true, children: titleText }), _jsx(ActionSection, { status: state.statuses.upload, statusText: uploadingProjectText, errorMessage: state.statuses.upload === ACTION_STATUSES.ERROR
37
+ ? `${state.error}\n\n${commands.getStarted.v2.pressKeyToExit}`
38
+ : undefined }), state.statuses.upload === ACTION_STATUSES.DONE &&
39
+ project.uploadResult?.projectName && (_jsxs(_Fragment, { children: [_jsx(Text, { children: commands.getStarted.v2.appDeployedReady }), project.uploadResult.app &&
40
+ project.uploadResult.projectMetadata && (_jsxs(Box, { flexDirection: "column", rowGap: 1, children: [renderProjectTree(project.uploadResult.projectName, project.uploadResult.app, project.uploadResult.projectMetadata), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: commands.getStarted.v2.appConfigDetails }), _jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { flexDirection: "row", columnGap: 1, children: [_jsx(Text, { bold: true, children: commands.getStarted.v2.distribution }), _jsx(Text, { children: "\u2192" }), _jsx(Text, { children: project.uploadResult.app.config?.distribution ||
41
+ 'private' })] }), _jsxs(Box, { flexDirection: "row", columnGap: 1, children: [_jsx(Text, { bold: true, children: commands.getStarted.v2.authType }), _jsx(Text, { children: "\u2192" }), _jsx(Text, { children: project.uploadResult.app.config?.auth?.type ||
42
+ 'static' })] })] })] })] })), project.uploadResult.projectDir && (_jsx(Text, { children: commands.getStarted.v2.checkOutConfig(`${project.uploadResult.projectDir}/src/app/app-hsmeta.json`) })), _jsx(Text, { children: commands.getStarted.v2.pressEnterToInstall(accountName) })] }))] }) }));
43
+ }
@@ -0,0 +1,2 @@
1
+ import { FlowState, ProjectState } from './reducer.js';
2
+ export declare const getProject: (state: FlowState) => ProjectState;
@@ -0,0 +1 @@
1
+ export const getProject = (state) => state.project;
@@ -0,0 +1,6 @@
1
+ export declare const ACTION_STATUSES: {
2
+ readonly IDLE: "idle";
3
+ readonly RUNNING: "running";
4
+ readonly DONE: "done";
5
+ readonly ERROR: "error";
6
+ };
@@ -0,0 +1,6 @@
1
+ export const ACTION_STATUSES = {
2
+ IDLE: 'idle',
3
+ RUNNING: 'running',
4
+ DONE: 'done',
5
+ ERROR: 'error',
6
+ };
@@ -0,0 +1,16 @@
1
+ export declare const ACTION_STATUSES: {
2
+ readonly IDLE: "idle";
3
+ readonly RUNNING: "running";
4
+ readonly DONE: "done";
5
+ readonly ERROR: "error";
6
+ };
7
+ export declare const GET_STARTED_FLOW_STEPS: {
8
+ readonly SELECT: "select";
9
+ readonly NAME_INPUT: "name-input";
10
+ readonly DEST_INPUT: "dest-input";
11
+ readonly CREATING: "creating";
12
+ readonly INSTALLING: "installing";
13
+ readonly UPLOADING: "uploading";
14
+ readonly OPEN_APP_PROMPT: "open-app-prompt";
15
+ readonly COMPLETE: "complete";
16
+ };
@@ -0,0 +1,16 @@
1
+ export const ACTION_STATUSES = {
2
+ IDLE: 'idle',
3
+ RUNNING: 'running',
4
+ DONE: 'done',
5
+ ERROR: 'error',
6
+ };
7
+ export const GET_STARTED_FLOW_STEPS = {
8
+ SELECT: 'select',
9
+ NAME_INPUT: 'name-input',
10
+ DEST_INPUT: 'dest-input',
11
+ CREATING: 'creating',
12
+ INSTALLING: 'installing',
13
+ UPLOADING: 'uploading',
14
+ OPEN_APP_PROMPT: 'open-app-prompt',
15
+ COMPLETE: 'complete',
16
+ };
@@ -1,8 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
1
2
  import { getSuccessBox, getInfoBox, getWarningBox, getAlertBox, } from '../components/StatusMessageBoxes.js';
2
3
  import { getBoxWithTitle } from '../components/BoxWithTitle.js';
3
4
  import { getTable } from '../components/Table.js';
5
+ import { getActionSection } from '../components/ActionSection.js';
6
+ import { getInputField } from '../components/InputField.js';
7
+ import { getSelectInput } from '../components/SelectInput.js';
8
+ import { getStatusIcon } from '../components/StatusIcon.js';
4
9
  import { SuccessBox, InfoBox, WarningBox, AlertBox, } from '../components/StatusMessageBoxes.js';
5
10
  import { BoxWithTitle } from '../components/BoxWithTitle.js';
11
+ import { ActionSection } from '../components/ActionSection.js';
12
+ import { InputField } from '../components/InputField.js';
13
+ import { SelectInput } from '../components/SelectInput.js';
14
+ import { StatusIcon } from '../components/StatusIcon.js';
15
+ import { ACTION_STATUSES } from '../constants.js';
16
+ import { Text } from 'ink';
6
17
  /**
7
18
  * These components will be used by the playground. Please add any new components here.
8
19
  */
@@ -73,6 +84,42 @@ export const populatedComponents = {
73
84
  }),
74
85
  signature: '',
75
86
  },
87
+ ActionSection: {
88
+ component: getActionSection({
89
+ status: ACTION_STATUSES.DONE,
90
+ statusText: 'Action completed successfully',
91
+ children: _jsx(Text, { children: "This is an action section" }),
92
+ }),
93
+ signature: ActionSection.toString(),
94
+ },
95
+ InputField: {
96
+ component: getInputField({
97
+ flag: 'name',
98
+ prompt: 'Enter your name',
99
+ value: 'example',
100
+ isEditing: false,
101
+ onChange: () => { },
102
+ onSubmit: () => { },
103
+ }),
104
+ signature: InputField.toString(),
105
+ },
106
+ SelectInput: {
107
+ component: getSelectInput({
108
+ items: [
109
+ { label: 'Option 1', value: 'option1' },
110
+ { label: 'Option 2', value: 'option2' },
111
+ { label: 'Option 3', value: 'option3' },
112
+ ],
113
+ onSelect: () => { },
114
+ }),
115
+ signature: SelectInput.toString(),
116
+ },
117
+ StatusIcon: {
118
+ component: getStatusIcon({
119
+ status: ACTION_STATUSES.DONE,
120
+ }),
121
+ signature: StatusIcon.toString(),
122
+ },
76
123
  };
77
124
  export function getComponentOptions() {
78
125
  return Object.keys(populatedComponents);
package/ui/render.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Scalar } from './components/Table.js';
2
+ import { ReactNode } from 'react';
2
3
  /**
3
4
  * Renders an Ink component to stdout and immediately unmounts.
4
5
  * Uses a proxy to report large viewport dimensions, preventing Ink from clipping output.
@@ -17,3 +18,6 @@ export declare function renderTable(tableHeaders: string[], tableData: Scalar[][
17
18
  * @param items - 2D array where each inner array is a row (typically single-item arrays for a simple list).
18
19
  */
19
20
  export declare function renderList(items: string[][]): Promise<void>;
21
+ export declare function renderInteractive(component: ReactNode, options?: {
22
+ fullScreen?: boolean;
23
+ }): Promise<void>;
package/ui/render.js CHANGED
@@ -1,6 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
1
2
  import { render } from 'ink';
2
3
  import { getTable } from './components/Table.js';
3
4
  import { mapTableDataToObjects } from './lib/table.js';
5
+ import { FullScreen } from './components/FullScreen.js';
6
+ import SpinniesManager from '../lib/ui/SpinniesManager.js';
4
7
  // Ink 6 clips output when it exceeds stdout.rows — we can use a large row count to prevent this.
5
8
  // This value is arbitrary but large enough to prevent clipping for any realistic static output.
6
9
  const INK_VIEWPORT_ROWS_FOR_STATIC_OUTPUT = 1000;
@@ -42,3 +45,31 @@ export async function renderTable(tableHeaders, tableData, borderless) {
42
45
  export async function renderList(items) {
43
46
  await renderTable([''], items, true);
44
47
  }
48
+ export async function renderInteractive(component, options = {
49
+ fullScreen: false,
50
+ }) {
51
+ // Disable SpinniesManager output during Ink rendering to prevent spinner text
52
+ // from interfering with Ink's terminal control (especially when using fullScreen
53
+ // mode's alternative buffer). Re-enable after rendering completes.
54
+ SpinniesManager.setDisableOutput(true);
55
+ if (options.fullScreen) {
56
+ // Enter alternative buffer
57
+ process.stdout.write('\x1b[?1049h');
58
+ let instance;
59
+ try {
60
+ instance = render(_jsx(FullScreen, { children: component }), {
61
+ patchConsole: true,
62
+ });
63
+ await instance.waitUntilExit();
64
+ }
65
+ finally {
66
+ // Exit alternative buffer
67
+ process.stdout.write('\x1b[?1049l');
68
+ }
69
+ }
70
+ else {
71
+ const instance = render(component);
72
+ await instance.waitUntilExit();
73
+ }
74
+ SpinniesManager.setDisableOutput(false);
75
+ }
package/ui/styles.d.ts CHANGED
@@ -14,5 +14,8 @@ export declare const INK_COLORS: {
14
14
  SUCCESS_GREEN: string;
15
15
  INFO_BLUE: string;
16
16
  WARNING_YELLOW: string;
17
+ HUBSPOT_ORANGE: string;
18
+ HUBSPOT_TEAL: string;
17
19
  WHITE: string;
20
+ GRAY: string;
18
21
  };
package/ui/styles.js CHANGED
@@ -14,5 +14,8 @@ export const INK_COLORS = {
14
14
  SUCCESS_GREEN: '#4deb7a',
15
15
  INFO_BLUE: '#4dcbeb',
16
16
  WARNING_YELLOW: '#EEB117',
17
+ HUBSPOT_ORANGE: '#FF7A59',
18
+ HUBSPOT_TEAL: '#00BDA5',
17
19
  WHITE: 'white',
20
+ GRAY: '#99ACC2',
18
21
  };