@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.
- package/bin/cli.js +8 -5
- package/commands/__tests__/getStarted.test.js +12 -0
- package/commands/__tests__/project.test.js +30 -0
- package/commands/account/auth.js +8 -97
- package/commands/account/use.js +19 -4
- package/commands/cms/module/marketplace-validate.js +23 -5
- package/commands/cms/theme/marketplace-validate.js +25 -6
- package/commands/getStarted.d.ts +2 -1
- package/commands/getStarted.js +38 -15
- package/commands/mcp/__tests__/start.test.js +1 -67
- package/commands/mcp/setup.js +1 -2
- package/commands/mcp/start.js +1 -19
- package/commands/mcp.js +1 -2
- package/commands/project.js +22 -1
- package/lang/en.d.ts +53 -7
- package/lang/en.js +59 -13
- package/lib/CLIWebSocketServer.d.ts +28 -0
- package/lib/CLIWebSocketServer.js +91 -0
- package/lib/__tests__/CLIWebSocketServer.test.d.ts +1 -0
- package/lib/__tests__/CLIWebSocketServer.test.js +252 -0
- package/lib/__tests__/accountAuth.test.d.ts +1 -0
- package/lib/__tests__/accountAuth.test.js +258 -0
- package/lib/__tests__/commandSuggestion.test.d.ts +1 -0
- package/lib/__tests__/commandSuggestion.test.js +119 -0
- package/lib/accountAuth.d.ts +10 -0
- package/lib/accountAuth.js +105 -0
- package/lib/app/urls.d.ts +1 -0
- package/lib/app/urls.js +4 -0
- package/lib/commandSuggestion.d.ts +3 -0
- package/lib/commandSuggestion.js +45 -0
- package/lib/constants.d.ts +0 -1
- package/lib/constants.js +0 -1
- package/lib/errors/ProjectErrors.d.ts +15 -0
- package/lib/errors/ProjectErrors.js +30 -0
- package/lib/getStarted/getStartedV2.d.ts +7 -0
- package/lib/getStarted/getStartedV2.js +18 -0
- package/lib/getStartedV2Actions.d.ts +37 -0
- package/lib/getStartedV2Actions.js +146 -0
- package/lib/marketplaceValidate.d.ts +1 -1
- package/lib/marketplaceValidate.js +23 -41
- package/lib/mcp/__tests__/setup.test.js +0 -17
- package/lib/mcp/setup.d.ts +0 -1
- package/lib/mcp/setup.js +59 -103
- package/lib/projects/ProjectLogsManager.d.ts +12 -3
- package/lib/projects/ProjectLogsManager.js +70 -12
- package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +43 -175
- package/lib/projects/__tests__/ProjectLogsManager.test.js +131 -18
- package/lib/projects/__tests__/platformVersion.test.js +37 -1
- package/lib/projects/__tests__/projects.test.js +6 -2
- package/lib/projects/components.d.ts +6 -0
- package/lib/projects/components.js +1 -1
- package/lib/projects/config.js +9 -2
- package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +2 -7
- package/lib/projects/localDev/LocalDevWebsocketServer.js +51 -98
- package/lib/projects/localDev/helpers/project.d.ts +4 -1
- package/lib/projects/localDev/helpers/project.js +13 -8
- package/lib/projects/localDev/localDevWebsocketServerUtils.d.ts +8 -7
- package/lib/projects/platformVersion.d.ts +8 -0
- package/lib/projects/platformVersion.js +31 -2
- package/lib/prompts/accountsPrompt.d.ts +2 -1
- package/lib/prompts/accountsPrompt.js +10 -2
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +20 -3
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -10
- package/mcp-server/tools/project/CreateProjectTool.d.ts +24 -4
- package/mcp-server/tools/project/CreateProjectTool.js +5 -10
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +5 -8
- package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
- package/mcp-server/tools/project/GetBuildLogsTool.js +3 -4
- package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
- package/mcp-server/tools/project/GetBuildStatusTool.js +3 -4
- package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +6 -1
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -6
- package/mcp-server/tools/project/constants.d.ts +12 -1
- package/mcp-server/tools/project/constants.js +12 -16
- package/mcp-server/utils/__tests__/project.test.js +0 -125
- package/mcp-server/utils/project.js +0 -8
- package/package.json +10 -5
- package/types/LocalDev.d.ts +0 -4
- package/ui/components/ActionSection.d.ts +12 -0
- package/ui/components/ActionSection.js +25 -0
- package/ui/components/BoxWithTitle.d.ts +4 -2
- package/ui/components/BoxWithTitle.js +2 -2
- package/ui/components/FullScreen.d.ts +6 -0
- package/ui/components/FullScreen.js +13 -0
- package/ui/components/InputField.d.ts +10 -0
- package/ui/components/InputField.js +10 -0
- package/ui/components/SelectInput.d.ts +11 -0
- package/ui/components/SelectInput.js +59 -0
- package/ui/components/StatusIcon.d.ts +9 -0
- package/ui/components/StatusIcon.js +17 -0
- package/ui/components/getStarted/GetStartedFlow.d.ts +8 -0
- package/ui/components/getStarted/GetStartedFlow.js +136 -0
- package/ui/components/getStarted/reducer.d.ts +59 -0
- package/ui/components/getStarted/reducer.js +72 -0
- package/ui/components/getStarted/screens/ProjectSetupScreen.d.ts +16 -0
- package/ui/components/getStarted/screens/ProjectSetupScreen.js +39 -0
- package/ui/components/getStarted/screens/UploadScreen.d.ts +7 -0
- package/ui/components/getStarted/screens/UploadScreen.js +43 -0
- package/ui/components/getStarted/selectors.d.ts +2 -0
- package/ui/components/getStarted/selectors.js +1 -0
- package/ui/constants.d.ts +6 -0
- package/ui/constants.js +6 -0
- package/ui/lib/constants.d.ts +16 -0
- package/ui/lib/constants.js +16 -0
- package/ui/playground/fixtures.js +47 -0
- package/ui/render.d.ts +4 -0
- package/ui/render.js +31 -0
- package/ui/styles.d.ts +3 -0
- 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,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 @@
|
|
|
1
|
+
export const getProject = (state) => state.project;
|
package/ui/constants.js
ADDED
|
@@ -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