@hubspot/cli 8.0.0 → 8.0.2-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/getStarted.d.ts +2 -1
- package/commands/getStarted.js +38 -15
- package/lang/en.d.ts +31 -5
- package/lang/en.js +33 -7
- 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__/commandSuggestion.test.d.ts +1 -0
- package/lib/__tests__/commandSuggestion.test.js +119 -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/getStarted/getStartedV2.d.ts +7 -0
- package/lib/getStarted/getStartedV2.js +56 -0
- package/lib/getStartedV2Actions.d.ts +8 -0
- package/lib/getStartedV2Actions.js +51 -0
- package/lib/mcp/setup.js +48 -54
- package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +43 -175
- package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +2 -7
- package/lib/projects/localDev/LocalDevWebsocketServer.js +51 -98
- package/lib/projects/localDev/localDevWebsocketServerUtils.d.ts +8 -7
- package/lib/projects/platformVersion.js +1 -1
- package/package.json +9 -4
- 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/GetStartedFlow.d.ts +24 -0
- package/ui/components/GetStartedFlow.js +128 -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/constants.d.ts +6 -0
- package/ui/constants.js +6 -0
- package/ui/playground/fixtures.js +47 -0
- package/ui/render.d.ts +4 -0
- package/ui/render.js +25 -0
- package/ui/styles.d.ts +3 -0
- package/ui/styles.js +3 -0
|
@@ -1,41 +1,24 @@
|
|
|
1
|
-
import { WebSocketServer } from 'ws';
|
|
2
|
-
import { isPortManagerServerRunning, requestPorts, } from '@hubspot/local-dev-lib/portManager';
|
|
3
|
-
import { uiLogger } from '../../ui/logger.js';
|
|
4
1
|
import { addLocalStateFlag } from '@hubspot/local-dev-lib/config';
|
|
5
2
|
import { LOCAL_DEV_UI_MESSAGE_SEND_TYPES, LOCAL_DEV_SERVER_MESSAGE_TYPES, CONFIG_LOCAL_STATE_FLAGS, LOCAL_DEV_WEBSOCKET_SERVER_INSTANCE_ID, } from '../../constants.js';
|
|
6
|
-
import { lib } from '../../../lang/en.js';
|
|
7
3
|
import { removeAnsiCodes } from '../../ui/removeAnsiCodes.js';
|
|
8
4
|
import { isDeployWebsocketMessage, isViewedWelcomeScreenWebsocketMessage, isUploadWebsocketMessage, isAppInstallFailureWebsocketMessage, isAppInstallSuccessWebsocketMessage, isAppInstallInitiatedWebsocketMessage, } from './localDevWebsocketServerUtils.js';
|
|
9
|
-
import
|
|
5
|
+
import CLIWebSocketServer from '../../CLIWebSocketServer.js';
|
|
10
6
|
const LOCAL_DEV_WEBSOCKET_SERVER_VERSION = 2;
|
|
11
7
|
const LOG_PREFIX = '[LocalDevWebsocketServer]';
|
|
12
|
-
const DOMAINS = ['hubspot.com', 'hubspotqa.com'];
|
|
13
|
-
const SUBDOMAINS = ['local', 'app', 'app-na2', 'app-na3', 'app-ap1', 'app-eu1'];
|
|
14
|
-
const ALLOWED_ORIGIN_REGEX = new RegExp(`^https://(${SUBDOMAINS.join('|')})\\.(${DOMAINS.join('|')})$`);
|
|
15
8
|
class LocalDevWebsocketServer {
|
|
16
|
-
|
|
17
|
-
debug;
|
|
9
|
+
cliWebSocketServer;
|
|
18
10
|
localDevProcess;
|
|
19
11
|
constructor(localDevProcess, debug) {
|
|
20
12
|
this.localDevProcess = localDevProcess;
|
|
21
|
-
this.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
logError(message) {
|
|
29
|
-
if (this.debug) {
|
|
30
|
-
uiLogger.error(`${LOG_PREFIX} ${message}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
sendMessage(websocket, message) {
|
|
34
|
-
websocket.send(JSON.stringify(message));
|
|
13
|
+
this.cliWebSocketServer = new CLIWebSocketServer({
|
|
14
|
+
instanceId: LOCAL_DEV_WEBSOCKET_SERVER_INSTANCE_ID,
|
|
15
|
+
logPrefix: LOG_PREFIX,
|
|
16
|
+
debug,
|
|
17
|
+
});
|
|
35
18
|
}
|
|
36
19
|
async handleUpload(websocket) {
|
|
37
20
|
const { uploadSuccess, buildSuccess, deploySuccess, deployId } = await this.localDevProcess.uploadProject();
|
|
38
|
-
this.sendMessage(websocket, {
|
|
21
|
+
this.cliWebSocketServer.sendMessage(websocket, {
|
|
39
22
|
type: uploadSuccess
|
|
40
23
|
? LOCAL_DEV_UI_MESSAGE_SEND_TYPES.UPLOAD_SUCCESS
|
|
41
24
|
: LOCAL_DEV_UI_MESSAGE_SEND_TYPES.UPLOAD_FAILURE,
|
|
@@ -50,7 +33,7 @@ class LocalDevWebsocketServer {
|
|
|
50
33
|
}
|
|
51
34
|
async handleDeploy(websocket, force) {
|
|
52
35
|
const { success, deployId } = await this.localDevProcess.deployLatestBuild(force);
|
|
53
|
-
this.sendMessage(websocket, {
|
|
36
|
+
this.cliWebSocketServer.sendMessage(websocket, {
|
|
54
37
|
type: success
|
|
55
38
|
? LOCAL_DEV_UI_MESSAGE_SEND_TYPES.DEPLOY_SUCCESS
|
|
56
39
|
: LOCAL_DEV_UI_MESSAGE_SEND_TYPES.DEPLOY_FAILURE,
|
|
@@ -70,52 +53,35 @@ class LocalDevWebsocketServer {
|
|
|
70
53
|
async handleAppInstallInitiated() {
|
|
71
54
|
this.localDevProcess.sendDevServerMessage(LOCAL_DEV_SERVER_MESSAGE_TYPES.OAUTH_APP_INSTALL_INITIATED);
|
|
72
55
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
else {
|
|
100
|
-
this.logError(lib.LocalDevWebsocketServer.errors.unknownMessageType(message.type));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch (e) {
|
|
104
|
-
this.logError(lib.LocalDevWebsocketServer.errors.invalidJSON(data.toString()));
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
sendCliMetadata(websocket) {
|
|
109
|
-
this.sendMessage(websocket, {
|
|
110
|
-
type: LOCAL_DEV_UI_MESSAGE_SEND_TYPES.CLI_METADATA,
|
|
111
|
-
data: {
|
|
112
|
-
cliVersion: pkg.version,
|
|
113
|
-
localDevWebsocketServerVersion: LOCAL_DEV_WEBSOCKET_SERVER_VERSION,
|
|
114
|
-
},
|
|
115
|
-
});
|
|
56
|
+
handleMessage(websocket, message) {
|
|
57
|
+
if (isUploadWebsocketMessage(message)) {
|
|
58
|
+
this.handleUpload(websocket);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
else if (isDeployWebsocketMessage(message)) {
|
|
62
|
+
this.handleDeploy(websocket, message.data.force);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
else if (isViewedWelcomeScreenWebsocketMessage(message)) {
|
|
66
|
+
addLocalStateFlag(CONFIG_LOCAL_STATE_FLAGS.LOCAL_DEV_UI_WELCOME);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
else if (isAppInstallSuccessWebsocketMessage(message)) {
|
|
70
|
+
this.handleAppInstallSuccess();
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
else if (isAppInstallFailureWebsocketMessage(message)) {
|
|
74
|
+
this.handleAppInstallFailure();
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
else if (isAppInstallInitiatedWebsocketMessage(message)) {
|
|
78
|
+
this.handleAppInstallInitiated();
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
116
82
|
}
|
|
117
83
|
sendProjectData(websocket) {
|
|
118
|
-
this.sendMessage(websocket, {
|
|
84
|
+
this.cliWebSocketServer.sendMessage(websocket, {
|
|
119
85
|
type: LOCAL_DEV_UI_MESSAGE_SEND_TYPES.UPDATE_PROJECT_DATA,
|
|
120
86
|
data: {
|
|
121
87
|
projectName: this.localDevProcess.projectData.name,
|
|
@@ -129,7 +95,7 @@ class LocalDevWebsocketServer {
|
|
|
129
95
|
}
|
|
130
96
|
setupProjectNodesListener(websocket) {
|
|
131
97
|
const listener = (nodes) => {
|
|
132
|
-
this.sendMessage(websocket, {
|
|
98
|
+
this.cliWebSocketServer.sendMessage(websocket, {
|
|
133
99
|
type: LOCAL_DEV_UI_MESSAGE_SEND_TYPES.UPDATE_PROJECT_NODES,
|
|
134
100
|
data: nodes,
|
|
135
101
|
});
|
|
@@ -141,7 +107,7 @@ class LocalDevWebsocketServer {
|
|
|
141
107
|
}
|
|
142
108
|
setupAppDataListener(websocket) {
|
|
143
109
|
const listener = (appData) => {
|
|
144
|
-
this.sendMessage(websocket, {
|
|
110
|
+
this.cliWebSocketServer.sendMessage(websocket, {
|
|
145
111
|
type: LOCAL_DEV_UI_MESSAGE_SEND_TYPES.UPDATE_APP_DATA,
|
|
146
112
|
data: appData,
|
|
147
113
|
});
|
|
@@ -154,7 +120,7 @@ class LocalDevWebsocketServer {
|
|
|
154
120
|
setupUploadWarningsListener(websocket) {
|
|
155
121
|
const listener = (uploadWarnings) => {
|
|
156
122
|
const formattedUploadWarnings = Array.from(uploadWarnings).map(removeAnsiCodes);
|
|
157
|
-
this.sendMessage(websocket, {
|
|
123
|
+
this.cliWebSocketServer.sendMessage(websocket, {
|
|
158
124
|
type: LOCAL_DEV_UI_MESSAGE_SEND_TYPES.UPDATE_UPLOAD_WARNINGS,
|
|
159
125
|
data: { uploadWarnings: formattedUploadWarnings },
|
|
160
126
|
});
|
|
@@ -167,7 +133,7 @@ class LocalDevWebsocketServer {
|
|
|
167
133
|
setupDevServersStartedListener(websocket) {
|
|
168
134
|
const listener = (devServersStarted) => {
|
|
169
135
|
if (devServersStarted) {
|
|
170
|
-
this.sendMessage(websocket, {
|
|
136
|
+
this.cliWebSocketServer.sendMessage(websocket, {
|
|
171
137
|
type: LOCAL_DEV_UI_MESSAGE_SEND_TYPES.DEV_SERVERS_STARTED,
|
|
172
138
|
});
|
|
173
139
|
}
|
|
@@ -184,33 +150,20 @@ class LocalDevWebsocketServer {
|
|
|
184
150
|
this.setupDevServersStartedListener(websocket);
|
|
185
151
|
}
|
|
186
152
|
async start() {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
this.server.on('connection', (ws, req) => {
|
|
198
|
-
const origin = req.headers.origin;
|
|
199
|
-
if (!origin || !ALLOWED_ORIGIN_REGEX.test(origin)) {
|
|
200
|
-
ws.close(1008, lib.LocalDevWebsocketServer.errors.originNotAllowed(origin));
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
this.sendCliMetadata(ws);
|
|
204
|
-
this.sendProjectData(ws);
|
|
205
|
-
this.setupMessageHandlers(ws);
|
|
206
|
-
this.setupStateListeners(ws);
|
|
207
|
-
this.localDevProcess.sendDevServerMessage(LOCAL_DEV_SERVER_MESSAGE_TYPES.WEBSOCKET_SERVER_CONNECTED);
|
|
153
|
+
return this.cliWebSocketServer.start({
|
|
154
|
+
metadata: {
|
|
155
|
+
localDevWebsocketServerVersion: LOCAL_DEV_WEBSOCKET_SERVER_VERSION,
|
|
156
|
+
},
|
|
157
|
+
onConnection: ws => {
|
|
158
|
+
this.sendProjectData(ws);
|
|
159
|
+
this.setupStateListeners(ws);
|
|
160
|
+
this.localDevProcess.sendDevServerMessage(LOCAL_DEV_SERVER_MESSAGE_TYPES.WEBSOCKET_SERVER_CONNECTED);
|
|
161
|
+
},
|
|
162
|
+
onMessage: (ws, message) => this.handleMessage(ws, message),
|
|
208
163
|
});
|
|
209
|
-
this.server.on('close', () => { });
|
|
210
164
|
}
|
|
211
165
|
shutdown() {
|
|
212
|
-
this.
|
|
213
|
-
this.server = undefined;
|
|
166
|
+
this.cliWebSocketServer.shutdown();
|
|
214
167
|
}
|
|
215
168
|
}
|
|
216
169
|
export default LocalDevWebsocketServer;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { LocalDevDeployWebsocketMessage
|
|
2
|
-
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function
|
|
1
|
+
import { LocalDevDeployWebsocketMessage } from '../../../types/LocalDev.js';
|
|
2
|
+
import { CLIWebSocketMessage } from '../../CLIWebSocketServer.js';
|
|
3
|
+
export declare function isUploadWebsocketMessage(message: CLIWebSocketMessage): boolean;
|
|
4
|
+
export declare function isDeployWebsocketMessage(message: CLIWebSocketMessage): message is LocalDevDeployWebsocketMessage;
|
|
5
|
+
export declare function isViewedWelcomeScreenWebsocketMessage(message: CLIWebSocketMessage): boolean;
|
|
6
|
+
export declare function isAppInstallSuccessWebsocketMessage(message: CLIWebSocketMessage): boolean;
|
|
7
|
+
export declare function isAppInstallInitiatedWebsocketMessage(message: CLIWebSocketMessage): boolean;
|
|
8
|
+
export declare function isAppInstallFailureWebsocketMessage(message: CLIWebSocketMessage): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cli",
|
|
3
|
-
"version": "8.0.0",
|
|
3
|
+
"version": "8.0.2-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",
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@hubspot/cms-dev-server": "1.2.1",
|
|
10
10
|
"@hubspot/local-dev-lib": "5.1.1",
|
|
11
|
-
"@hubspot/project-parsing-lib": "0.
|
|
11
|
+
"@hubspot/project-parsing-lib": "0.12.0",
|
|
12
12
|
"@hubspot/serverless-dev-runtime": "7.0.7",
|
|
13
|
-
"@hubspot/ui-extensions-dev-server": "1.1.
|
|
13
|
+
"@hubspot/ui-extensions-dev-server": "1.1.8",
|
|
14
14
|
"@inquirer/prompts": "7.1.0",
|
|
15
15
|
"@modelcontextprotocol/sdk": "1.25.0",
|
|
16
16
|
"archiver": "7.0.1",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"findup-sync": "4.0.0",
|
|
23
23
|
"fs-extra": "8.1.0",
|
|
24
24
|
"ink": "6.6.0",
|
|
25
|
+
"ink-spinner": "5.0.0",
|
|
26
|
+
"ink-text-input": "6.0.0",
|
|
25
27
|
"js-yaml": "4.1.0",
|
|
26
28
|
"minimatch": "10.0.1",
|
|
27
29
|
"moment": "2.30.1",
|
|
@@ -80,7 +82,7 @@
|
|
|
80
82
|
"list-all-commands": "yarn tsx ./scripts/get-all-commands.ts",
|
|
81
83
|
"local-link": "hubspot-linking",
|
|
82
84
|
"mcp-local": "yarn tsx ./scripts/mcp-local.ts",
|
|
83
|
-
"prettier:write": "prettier --write './**/*.{ts,js,json}'",
|
|
85
|
+
"prettier:write": "prettier --write './**/*.{ts,js,json,tsx}'",
|
|
84
86
|
"release": "yarn tsx ./scripts/release.ts release",
|
|
85
87
|
"sync-to-public": "yarn tsx ./scripts/sync-to-public.ts repo-sync",
|
|
86
88
|
"ink-playground": "yarn build && yarn tsx ./scripts/ink-playground.ts",
|
|
@@ -115,5 +117,8 @@
|
|
|
115
117
|
"publishConfig": {
|
|
116
118
|
"access": "public",
|
|
117
119
|
"registry": "https://registry.npmjs.org/"
|
|
120
|
+
},
|
|
121
|
+
"resolutions": {
|
|
122
|
+
"eslint-visitor-keys": "4.2.0"
|
|
118
123
|
}
|
|
119
124
|
}
|
package/types/LocalDev.d.ts
CHANGED
|
@@ -20,10 +20,6 @@ export type LocalDevStateConstructorOptions = {
|
|
|
20
20
|
initialProjectProfileData: HSProfileVariables;
|
|
21
21
|
env: Environment;
|
|
22
22
|
};
|
|
23
|
-
export type LocalDevWebsocketMessage = {
|
|
24
|
-
type: string;
|
|
25
|
-
data?: unknown;
|
|
26
|
-
};
|
|
27
23
|
export type LocalDevDeployWebsocketMessage = {
|
|
28
24
|
type: typeof LOCAL_DEV_UI_MESSAGE_RECEIVE_TYPES.DEPLOY;
|
|
29
25
|
data: {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ValueOf } from '@hubspot/local-dev-lib/types/Utils';
|
|
2
|
+
import { ACTION_STATUSES } from '../constants.js';
|
|
3
|
+
type ActionStatus = ValueOf<typeof ACTION_STATUSES>;
|
|
4
|
+
export type ActionSectionProps = {
|
|
5
|
+
status: ActionStatus;
|
|
6
|
+
statusText: string;
|
|
7
|
+
errorMessage?: string;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
};
|
|
10
|
+
export declare function ActionSection({ status, statusText, errorMessage, children, }: ActionSectionProps): import("react/jsx-runtime").JSX.Element | null;
|
|
11
|
+
export declare function getActionSection(props: ActionSectionProps): React.ReactNode;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { ACTION_STATUSES } from '../constants.js';
|
|
4
|
+
import { INK_COLORS } from '../styles.js';
|
|
5
|
+
import { StatusIcon } from './StatusIcon.js';
|
|
6
|
+
const LEFT_BORDER_BOX_PROPS = {
|
|
7
|
+
flexDirection: 'column',
|
|
8
|
+
borderStyle: 'single',
|
|
9
|
+
borderColor: INK_COLORS.GRAY,
|
|
10
|
+
borderLeft: true,
|
|
11
|
+
borderTop: false,
|
|
12
|
+
borderRight: false,
|
|
13
|
+
borderBottom: false,
|
|
14
|
+
paddingLeft: 2,
|
|
15
|
+
marginLeft: 1,
|
|
16
|
+
};
|
|
17
|
+
export function ActionSection({ status, statusText, errorMessage, children, }) {
|
|
18
|
+
if (status === ACTION_STATUSES.IDLE) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "row", columnGap: 1, children: [_jsx(StatusIcon, { status: status }), _jsx(Text, { children: statusText })] }), _jsxs(Box, { ...LEFT_BORDER_BOX_PROPS, children: [children, status === ACTION_STATUSES.ERROR && errorMessage && (_jsx(Text, { color: INK_COLORS.ALERT_RED, wrap: "wrap", children: errorMessage }))] })] }));
|
|
22
|
+
}
|
|
23
|
+
export function getActionSection(props) {
|
|
24
|
+
return _jsx(ActionSection, { ...props });
|
|
25
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export interface BoxWithTitleProps {
|
|
2
2
|
title: string;
|
|
3
|
-
message
|
|
3
|
+
message?: string;
|
|
4
4
|
titleBackgroundColor?: string;
|
|
5
5
|
borderColor?: string;
|
|
6
6
|
textCentered?: boolean;
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
flexGrow?: number;
|
|
7
9
|
}
|
|
8
10
|
export declare function getBoxWithTitle(props: BoxWithTitleProps): React.ReactNode;
|
|
9
|
-
export declare function BoxWithTitle({ title, message, titleBackgroundColor, borderColor, textCentered, }: BoxWithTitleProps): React.ReactNode;
|
|
11
|
+
export declare function BoxWithTitle({ title, message, titleBackgroundColor, borderColor, textCentered, children, flexGrow, }: BoxWithTitleProps): React.ReactNode;
|
|
@@ -4,6 +4,6 @@ import { CONTAINER_STYLES } from '../styles.js';
|
|
|
4
4
|
export function getBoxWithTitle(props) {
|
|
5
5
|
return _jsx(BoxWithTitle, { ...props });
|
|
6
6
|
}
|
|
7
|
-
export function BoxWithTitle({ title, message, titleBackgroundColor, borderColor, textCentered, }) {
|
|
8
|
-
return (_jsxs(Box, { ...CONTAINER_STYLES, borderStyle: "round", borderColor: borderColor,
|
|
7
|
+
export function BoxWithTitle({ title, message, titleBackgroundColor, borderColor, textCentered, children, flexGrow, }) {
|
|
8
|
+
return (_jsxs(Box, { ...CONTAINER_STYLES, flexGrow: flexGrow, borderStyle: "round", borderColor: borderColor, children: [_jsx(Box, { position: "absolute", marginTop: -2, paddingX: 0, alignSelf: "flex-start", justifyContent: "center", alignItems: "center", children: _jsx(Text, { backgroundColor: titleBackgroundColor, bold: true, children: ` ${title} ` }) }), _jsxs(Box, { flexDirection: "column", rowGap: 1, children: [message?.split('\n\n').map((section, sectionIndex) => (_jsx(Box, { flexDirection: "column", alignItems: textCentered ? 'center' : 'flex-start', children: section.split('\n').map((line, lineIndex) => (_jsx(Text, { children: line }, `${sectionIndex}-${lineIndex}`))) }, sectionIndex))), children] })] }));
|
|
9
9
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { useTerminalSize } from '../lib/useTerminalSize.js';
|
|
4
|
+
export function FullScreen({ children }) {
|
|
5
|
+
// Use rows - 1 to prevent flickering
|
|
6
|
+
// See: https://github.com/vadimdemedes/ink/issues/359
|
|
7
|
+
const { rows, columns } = useTerminalSize();
|
|
8
|
+
const height = rows - 1;
|
|
9
|
+
return (_jsx(Box, { flexDirection: "column", width: columns, height: height, overflow: "hidden", children: children }));
|
|
10
|
+
}
|
|
11
|
+
export function getFullScreen(props) {
|
|
12
|
+
return _jsx(FullScreen, { ...props });
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { SelectInputItem } from '../components/SelectInput.js';
|
|
2
|
+
export declare const DEFAULT_PROJECT_NAME = "get-started-project";
|
|
3
|
+
export declare const GET_STARTED_FLOW_OPTIONS: SelectInputItem[];
|
|
4
|
+
export declare const FLOW_STEPS: {
|
|
5
|
+
readonly SELECT: "select";
|
|
6
|
+
readonly NAME_INPUT: "name-input";
|
|
7
|
+
readonly DEST_INPUT: "dest-input";
|
|
8
|
+
readonly CREATING: "creating";
|
|
9
|
+
readonly COMPLETE: "complete";
|
|
10
|
+
};
|
|
11
|
+
export type GetStartedFlowProps = {
|
|
12
|
+
derivedAccountId: number;
|
|
13
|
+
onRunCreateProject: (args: {
|
|
14
|
+
projectName: string;
|
|
15
|
+
projectDest: string;
|
|
16
|
+
}) => Promise<{
|
|
17
|
+
projectName: string;
|
|
18
|
+
projectDest: string;
|
|
19
|
+
}>;
|
|
20
|
+
initialName?: string;
|
|
21
|
+
initialDest?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function getGetStartedFlow(props: GetStartedFlowProps): React.ReactNode;
|
|
24
|
+
export declare function GetStartedFlow({ derivedAccountId, onRunCreateProject, initialName, initialDest, }: GetStartedFlowProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { sanitizeFileName, untildify } from '@hubspot/local-dev-lib/path';
|
|
3
|
+
import { Box, Text, useApp, useFocus, useInput } from 'ink';
|
|
4
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
5
|
+
import { commands } from '../../lang/en.js';
|
|
6
|
+
import { GET_STARTED_OPTIONS } from '../../lib/constants.js';
|
|
7
|
+
import { uiAccountDescription } from '../../lib/ui/index.js';
|
|
8
|
+
import { ActionSection } from '../components/ActionSection.js';
|
|
9
|
+
import { BoxWithTitle } from '../components/BoxWithTitle.js';
|
|
10
|
+
import { InputField } from '../components/InputField.js';
|
|
11
|
+
import { SelectInput } from '../components/SelectInput.js';
|
|
12
|
+
import { ACTION_STATUSES } from '../constants.js';
|
|
13
|
+
import { INK_COLORS } from '../styles.js';
|
|
14
|
+
export const DEFAULT_PROJECT_NAME = 'get-started-project';
|
|
15
|
+
export const GET_STARTED_FLOW_OPTIONS = [
|
|
16
|
+
{
|
|
17
|
+
label: commands.getStarted.prompts.options.app,
|
|
18
|
+
value: GET_STARTED_OPTIONS.APP,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: commands.getStarted.prompts.options.cmsTheme,
|
|
22
|
+
value: 'CMS_THEME',
|
|
23
|
+
disabled: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: commands.getStarted.prompts.options.cmsReactModule,
|
|
27
|
+
value: 'CMS_REACT_MODULE',
|
|
28
|
+
disabled: true,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
export const FLOW_STEPS = {
|
|
32
|
+
SELECT: 'select',
|
|
33
|
+
NAME_INPUT: 'name-input',
|
|
34
|
+
DEST_INPUT: 'dest-input',
|
|
35
|
+
CREATING: 'creating',
|
|
36
|
+
COMPLETE: 'complete',
|
|
37
|
+
};
|
|
38
|
+
export function getGetStartedFlow(props) {
|
|
39
|
+
return _jsx(GetStartedFlow, { ...props });
|
|
40
|
+
}
|
|
41
|
+
export function GetStartedFlow({ derivedAccountId, onRunCreateProject, initialName, initialDest, }) {
|
|
42
|
+
useFocus({ autoFocus: true });
|
|
43
|
+
const { exit } = useApp();
|
|
44
|
+
const accountName = uiAccountDescription(derivedAccountId);
|
|
45
|
+
// Determine initial step based on what was provided via CLI
|
|
46
|
+
const getInitialStep = () => {
|
|
47
|
+
if (initialName && initialDest) {
|
|
48
|
+
return FLOW_STEPS.CREATING;
|
|
49
|
+
}
|
|
50
|
+
else if (initialName) {
|
|
51
|
+
return FLOW_STEPS.DEST_INPUT;
|
|
52
|
+
}
|
|
53
|
+
else if (initialDest) {
|
|
54
|
+
return FLOW_STEPS.NAME_INPUT;
|
|
55
|
+
}
|
|
56
|
+
return FLOW_STEPS.SELECT;
|
|
57
|
+
};
|
|
58
|
+
const hasInitialValues = Boolean(initialName && initialDest);
|
|
59
|
+
const [step, setStep] = useState(getInitialStep());
|
|
60
|
+
const [selectedLabel, setSelectedLabel] = useState(initialName ? commands.getStarted.prompts.options.app : '');
|
|
61
|
+
const [projectName, setProjectName] = useState(initialName || DEFAULT_PROJECT_NAME);
|
|
62
|
+
const [projectDest, setProjectDest] = useState(initialDest
|
|
63
|
+
? untildify(initialDest)
|
|
64
|
+
: sanitizeFileName(DEFAULT_PROJECT_NAME));
|
|
65
|
+
const [createStatus, setCreateStatus] = useState(initialName ? ACTION_STATUSES.RUNNING : ACTION_STATUSES.IDLE);
|
|
66
|
+
const [createError, setCreateError] = useState('');
|
|
67
|
+
// Only auto-update dest from name if dest wasn't provided via CLI
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!initialDest) {
|
|
70
|
+
setProjectDest(sanitizeFileName(projectName));
|
|
71
|
+
}
|
|
72
|
+
}, [projectName, initialDest]);
|
|
73
|
+
const handleSelect = useCallback((item) => {
|
|
74
|
+
if (item.disabled)
|
|
75
|
+
return;
|
|
76
|
+
setSelectedLabel(item.label);
|
|
77
|
+
setStep(FLOW_STEPS.NAME_INPUT);
|
|
78
|
+
setCreateStatus(ACTION_STATUSES.RUNNING);
|
|
79
|
+
}, []);
|
|
80
|
+
const handleNameSubmit = useCallback(() => {
|
|
81
|
+
setStep(FLOW_STEPS.DEST_INPUT);
|
|
82
|
+
}, []);
|
|
83
|
+
const handleDestChange = useCallback((value) => {
|
|
84
|
+
setProjectDest(value);
|
|
85
|
+
if (createError) {
|
|
86
|
+
setCreateError('');
|
|
87
|
+
}
|
|
88
|
+
}, [createError]);
|
|
89
|
+
const handleDestSubmit = useCallback(async () => {
|
|
90
|
+
setStep(FLOW_STEPS.CREATING);
|
|
91
|
+
setCreateError('');
|
|
92
|
+
setCreateStatus(ACTION_STATUSES.RUNNING);
|
|
93
|
+
try {
|
|
94
|
+
await onRunCreateProject({
|
|
95
|
+
projectName,
|
|
96
|
+
projectDest,
|
|
97
|
+
});
|
|
98
|
+
setCreateStatus(ACTION_STATUSES.DONE);
|
|
99
|
+
setStep(FLOW_STEPS.COMPLETE);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
const errorMessage = error instanceof Error
|
|
103
|
+
? error.message
|
|
104
|
+
: commands.getStarted.v2.unknownError;
|
|
105
|
+
setCreateStatus(ACTION_STATUSES.ERROR);
|
|
106
|
+
setCreateError(errorMessage);
|
|
107
|
+
setStep(FLOW_STEPS.DEST_INPUT);
|
|
108
|
+
}
|
|
109
|
+
}, [onRunCreateProject, projectName, projectDest]);
|
|
110
|
+
// Auto-start project creation when initial values are provided
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (hasInitialValues && step === FLOW_STEPS.CREATING) {
|
|
113
|
+
setCreateStatus(ACTION_STATUSES.RUNNING);
|
|
114
|
+
handleDestSubmit();
|
|
115
|
+
}
|
|
116
|
+
}, [hasInitialValues, step, handleDestSubmit]);
|
|
117
|
+
useInput((_, key) => {
|
|
118
|
+
if (step === FLOW_STEPS.COMPLETE && key.return) {
|
|
119
|
+
exit();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const titleText = commands.getStarted.v2.startTitle;
|
|
123
|
+
const overviewText = commands.getStarted.v2.guideOverview(accountName);
|
|
124
|
+
const projectsText = commands.getStarted.v2.projects;
|
|
125
|
+
const selectPrompt = commands.getStarted.v2.prompts.selectOptionV2;
|
|
126
|
+
const runningProjectCreateText = commands.getStarted.v2.runningProjectCreate;
|
|
127
|
+
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 }), step === 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: handleSelect })] })) : (_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: selectedLabel })] })), _jsxs(ActionSection, { status: createStatus, statusText: runningProjectCreateText, errorMessage: createError, children: [step !== FLOW_STEPS.SELECT && (_jsx(InputField, { flag: "name", prompt: "Enter your project name", value: projectName, isEditing: step === FLOW_STEPS.NAME_INPUT, onChange: setProjectName, onSubmit: handleNameSubmit })), step !== FLOW_STEPS.SELECT && step !== FLOW_STEPS.NAME_INPUT && (_jsx(InputField, { flag: "dest", prompt: "Choose where to create the project", value: projectDest, isEditing: step === FLOW_STEPS.DEST_INPUT, onChange: handleDestChange, onSubmit: handleDestSubmit }))] }), step === 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(accountName) })] }))] }) }));
|
|
128
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type InputFieldProps = {
|
|
2
|
+
flag: string;
|
|
3
|
+
prompt: string;
|
|
4
|
+
value: string;
|
|
5
|
+
isEditing: boolean;
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
onSubmit: () => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function InputField({ flag, prompt, value, isEditing, onChange, onSubmit, }: InputFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function getInputField(props: InputFieldProps): React.ReactNode;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
import { INK_COLORS } from '../styles.js';
|
|
5
|
+
export function InputField({ flag, prompt, value, isEditing, onChange, onSubmit, }) {
|
|
6
|
+
return (_jsxs(Box, { flexDirection: "row", flexWrap: "wrap", columnGap: 1, marginBottom: 1, children: [_jsx(Text, { color: INK_COLORS.HUBSPOT_TEAL, children: "?" }), _jsxs(Text, { children: ["[--", flag, "] ", prompt] }), _jsx(Text, { color: INK_COLORS.INFO_BLUE, children: _jsx(TextInput, { focus: isEditing, value: value, onChange: onChange, onSubmit: onSubmit }) })] }));
|
|
7
|
+
}
|
|
8
|
+
export function getInputField(props) {
|
|
9
|
+
return _jsx(InputField, { ...props });
|
|
10
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type SelectInputItem = {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type SelectInputProps = {
|
|
7
|
+
items: SelectInputItem[];
|
|
8
|
+
onSelect: (item: SelectInputItem) => void;
|
|
9
|
+
};
|
|
10
|
+
export declare function SelectInput({ items, onSelect }: SelectInputProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function getSelectInput(props: SelectInputProps): React.ReactNode;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { INK_COLORS } from '../styles.js';
|
|
5
|
+
export function SelectInput({ items, onSelect }) {
|
|
6
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
7
|
+
useInput((_, key) => {
|
|
8
|
+
if (key.upArrow) {
|
|
9
|
+
setSelectedIndex(prevIndex => {
|
|
10
|
+
let newIndex = prevIndex - 1;
|
|
11
|
+
if (newIndex < 0) {
|
|
12
|
+
newIndex = items.length - 1;
|
|
13
|
+
}
|
|
14
|
+
// Skip disabled items
|
|
15
|
+
while (items[newIndex]?.disabled && newIndex !== prevIndex) {
|
|
16
|
+
newIndex--;
|
|
17
|
+
if (newIndex < 0) {
|
|
18
|
+
newIndex = items.length - 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return newIndex;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (key.downArrow) {
|
|
25
|
+
setSelectedIndex(prevIndex => {
|
|
26
|
+
let newIndex = prevIndex + 1;
|
|
27
|
+
if (newIndex >= items.length) {
|
|
28
|
+
newIndex = 0;
|
|
29
|
+
}
|
|
30
|
+
// Skip disabled items
|
|
31
|
+
while (items[newIndex]?.disabled && newIndex !== prevIndex) {
|
|
32
|
+
newIndex++;
|
|
33
|
+
if (newIndex >= items.length) {
|
|
34
|
+
newIndex = 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return newIndex;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (key.return) {
|
|
41
|
+
const selectedItem = items[selectedIndex];
|
|
42
|
+
if (selectedItem && !selectedItem.disabled) {
|
|
43
|
+
onSelect(selectedItem);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return (_jsx(Box, { flexDirection: "column", children: items.map((item, index) => {
|
|
48
|
+
const isSelected = index === selectedIndex;
|
|
49
|
+
const isDisabled = item.disabled;
|
|
50
|
+
return (_jsxs(Box, { flexDirection: "row", columnGap: 1, children: [_jsx(Text, { color: isSelected ? INK_COLORS.INFO_BLUE : undefined, children: isSelected ? '❯' : ' ' }), _jsx(Text, { color: isDisabled
|
|
51
|
+
? INK_COLORS.GRAY
|
|
52
|
+
: isSelected
|
|
53
|
+
? INK_COLORS.INFO_BLUE
|
|
54
|
+
: undefined, dimColor: isDisabled, children: item.label })] }, item.value));
|
|
55
|
+
}) }));
|
|
56
|
+
}
|
|
57
|
+
export function getSelectInput(props) {
|
|
58
|
+
return _jsx(SelectInput, { ...props });
|
|
59
|
+
}
|