@hubspot/cli 8.0.4-experimental.1 → 8.0.5-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/commands/cms/__tests__/upload.test.js +4 -0
- package/commands/project/__tests__/migrate.test.js +2 -2
- package/commands/project/migrate.js +4 -4
- package/lang/en.d.ts +7 -2
- package/lang/en.js +8 -3
- package/lib/getStartedV2Actions.d.ts +13 -0
- package/lib/getStartedV2Actions.js +53 -0
- 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 +6 -7
- 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/__tests__/GetApiUsagePatternsByAppIdTool.test.js +0 -32
- package/mcp-server/tools/project/constants.d.ts +12 -1
- package/mcp-server/tools/project/constants.js +12 -16
- package/package.json +3 -5
- package/ui/components/getStarted/GetStartedFlow.js +79 -2
- package/ui/components/getStarted/reducer.d.ts +20 -0
- package/ui/components/getStarted/reducer.js +36 -0
- package/ui/components/getStarted/screens/InstallationScreen.d.ts +7 -0
- package/ui/components/getStarted/screens/InstallationScreen.js +16 -0
- package/ui/components/getStarted/screens/ProjectSetupScreen.js +2 -1
- package/ui/lib/constants.d.ts +1 -0
- package/ui/lib/constants.js +1 -0
|
@@ -8,6 +8,7 @@ import * as modulesLib from '@hubspot/local-dev-lib/cms/modules';
|
|
|
8
8
|
import * as ignoreRulesLib from '@hubspot/local-dev-lib/ignoreRules';
|
|
9
9
|
import * as themesLib from '@hubspot/local-dev-lib/cms/themes';
|
|
10
10
|
import * as configLib from '@hubspot/local-dev-lib/config';
|
|
11
|
+
import * as handleFieldsJSLib from '@hubspot/local-dev-lib/cms/handleFieldsJS';
|
|
11
12
|
import { uiLogger } from '../../../lib/ui/logger.js';
|
|
12
13
|
import * as errorHandlers from '../../../lib/errorHandlers/index.js';
|
|
13
14
|
import * as commonOpts from '../../../lib/commonOpts.js';
|
|
@@ -26,6 +27,7 @@ vi.mock('@hubspot/local-dev-lib/cms/modules');
|
|
|
26
27
|
vi.mock('@hubspot/local-dev-lib/ignoreRules');
|
|
27
28
|
vi.mock('@hubspot/local-dev-lib/cms/themes');
|
|
28
29
|
vi.mock('@hubspot/local-dev-lib/config');
|
|
30
|
+
vi.mock('@hubspot/local-dev-lib/cms/handleFieldsJS');
|
|
29
31
|
vi.mock('../../../lib/errorHandlers/index.js');
|
|
30
32
|
vi.mock('../../../lib/commonOpts.js');
|
|
31
33
|
vi.mock('../../../lib/prompts/uploadPrompt.js');
|
|
@@ -53,6 +55,7 @@ const hasUploadErrorsSpy = vi.spyOn(uploadFolderLib, 'hasUploadErrors');
|
|
|
53
55
|
const processExitSpy = vi.spyOn(process, 'exit');
|
|
54
56
|
const logErrorSpy = vi.spyOn(errorHandlers, 'logError');
|
|
55
57
|
const getConfigAccountIfExistsSpy = vi.spyOn(configLib, 'getConfigAccountIfExists');
|
|
58
|
+
const isConvertableFieldJsSpy = vi.spyOn(handleFieldsJSLib, 'isConvertableFieldJs');
|
|
56
59
|
describe('commands/cms/upload', () => {
|
|
57
60
|
beforeEach(() => {
|
|
58
61
|
// @ts-expect-error Mock implementation
|
|
@@ -67,6 +70,7 @@ describe('commands/cms/upload', () => {
|
|
|
67
70
|
getThemePreviewUrlSpy.mockReturnValue(undefined);
|
|
68
71
|
// Mock config to prevent reading actual config file in CI
|
|
69
72
|
getConfigAccountIfExistsSpy.mockReturnValue(undefined);
|
|
73
|
+
isConvertableFieldJsSpy.mockReturnValue(false);
|
|
70
74
|
});
|
|
71
75
|
describe('command', () => {
|
|
72
76
|
it('should have the correct command structure', () => {
|
|
@@ -9,7 +9,7 @@ import { uiBetaTag, uiCommandReference } from '../../../lib/ui/index.js';
|
|
|
9
9
|
vi.mock('../../../lib/app/migrate');
|
|
10
10
|
vi.mock('../../../lib/projects/config');
|
|
11
11
|
vi.mock('../../../lib/ui');
|
|
12
|
-
const { v2025_2 } = PLATFORM_VERSIONS;
|
|
12
|
+
const { v2025_2, v2026_3_beta } = PLATFORM_VERSIONS;
|
|
13
13
|
describe('commands/project/migrate', () => {
|
|
14
14
|
const yargsMock = yargs;
|
|
15
15
|
const optionsSpy = vi.spyOn(yargsMock, 'option').mockReturnValue(yargsMock);
|
|
@@ -48,7 +48,7 @@ describe('commands/project/migrate', () => {
|
|
|
48
48
|
migrateCommand.builder(yargsMock);
|
|
49
49
|
expect(optionsSpy).toHaveBeenCalledWith('platform-version', {
|
|
50
50
|
type: 'string',
|
|
51
|
-
choices: [v2025_2],
|
|
51
|
+
choices: [v2025_2, v2026_3_beta],
|
|
52
52
|
default: v2025_2,
|
|
53
53
|
});
|
|
54
54
|
expect(optionsSpy).toHaveBeenCalledWith('unstable', {
|
|
@@ -13,7 +13,7 @@ import { getHasMigratableThemes, migrateThemes2025_2, } from '../../lib/theme/mi
|
|
|
13
13
|
import { hasFeature } from '../../lib/hasFeature.js';
|
|
14
14
|
import { FEATURES } from '../../lib/constants.js';
|
|
15
15
|
import { trackCommandMetadataUsage, trackCommandUsage, } from '../../lib/usageTracking.js';
|
|
16
|
-
const { v2025_2 } = PLATFORM_VERSIONS;
|
|
16
|
+
const { v2025_2, v2026_3_beta } = PLATFORM_VERSIONS;
|
|
17
17
|
const command = 'migrate';
|
|
18
18
|
const describe = commands.project.migrate.describe;
|
|
19
19
|
async function handler(args) {
|
|
@@ -26,8 +26,8 @@ async function handler(args) {
|
|
|
26
26
|
}
|
|
27
27
|
if (projectConfig?.projectConfig) {
|
|
28
28
|
await renderInline(getWarningBox({
|
|
29
|
-
title: lib.migrate.projectMigrationWarningTitle,
|
|
30
|
-
message: lib.migrate.projectMigrationWarning,
|
|
29
|
+
title: lib.migrate.projectMigrationWarningTitle(platformVersion),
|
|
30
|
+
message: lib.migrate.projectMigrationWarning(platformVersion),
|
|
31
31
|
}));
|
|
32
32
|
}
|
|
33
33
|
try {
|
|
@@ -67,7 +67,7 @@ function projectMigrateBuilder(yargs) {
|
|
|
67
67
|
yargs
|
|
68
68
|
.option('platform-version', {
|
|
69
69
|
type: 'string',
|
|
70
|
-
choices: [v2025_2],
|
|
70
|
+
choices: [v2025_2, v2026_3_beta],
|
|
71
71
|
default: v2025_2,
|
|
72
72
|
})
|
|
73
73
|
.option('unstable', {
|
package/lang/en.d.ts
CHANGED
|
@@ -43,6 +43,11 @@ export declare const commands: {
|
|
|
43
43
|
checkOutConfig: (configPath: string) => string;
|
|
44
44
|
pressEnterToInstall: (accountName: string) => string;
|
|
45
45
|
pressKeyToExit: string;
|
|
46
|
+
installingApp: (appName: string, accountName: string) => string;
|
|
47
|
+
installInstructions: string;
|
|
48
|
+
browserFailedToOpen: (url: string) => string;
|
|
49
|
+
pollingTimeout: (minutes: number) => string;
|
|
50
|
+
pressEnterToContinueSetup: string;
|
|
46
51
|
prompts: {
|
|
47
52
|
selectOptionV2: string;
|
|
48
53
|
options: {
|
|
@@ -3918,8 +3923,8 @@ export declare const lib: {
|
|
|
3918
3923
|
componentsToBeMigrated: (components: string) => string;
|
|
3919
3924
|
componentsThatWillNotBeMigrated: (components: string) => string;
|
|
3920
3925
|
sourceContentsMoved: (newLocation: string) => string;
|
|
3921
|
-
projectMigrationWarningTitle: string;
|
|
3922
|
-
projectMigrationWarning: string;
|
|
3926
|
+
projectMigrationWarningTitle: (platformVersion: string) => string;
|
|
3927
|
+
projectMigrationWarning: (platformVersion: string) => string;
|
|
3923
3928
|
exitWithoutMigrating: string;
|
|
3924
3929
|
success: {
|
|
3925
3930
|
downloadedProject: (projectName: string, projectDest: string) => string;
|
package/lang/en.js
CHANGED
|
@@ -51,6 +51,11 @@ export const commands = {
|
|
|
51
51
|
checkOutConfig: (configPath) => `Check out ${chalk.cyan(configPath)} for the full configuration.`,
|
|
52
52
|
pressEnterToInstall: (accountName) => `? Press ${chalk.bold('<enter>')} to continue installing and previewing this app in ${chalk.bold(accountName)}`,
|
|
53
53
|
pressKeyToExit: `Press any key to exit...`,
|
|
54
|
+
installingApp: (appName, accountName) => `Installing ${chalk.bold(appName)} in ${chalk.bold(accountName)}...`,
|
|
55
|
+
installInstructions: `We'll take you to your HubSpot account and walk you through installing your app.`,
|
|
56
|
+
browserFailedToOpen: (url) => `⚠️ Failed to open browser automatically. Please open this URL manually:\n${chalk.cyan(url)}`,
|
|
57
|
+
pollingTimeout: (minutes) => `⚠️ Installation polling timed out after ${minutes} minutes. The app may still be installing in the background.`,
|
|
58
|
+
pressEnterToContinueSetup: `Press ${chalk.bold('<enter>')} to continue with card setup...`,
|
|
54
59
|
prompts: {
|
|
55
60
|
selectOptionV2: 'Choose a component type to get started',
|
|
56
61
|
options: {
|
|
@@ -3076,7 +3081,7 @@ export const lib = {
|
|
|
3076
3081
|
updateSucceeded: (latestVersion) => `Successfully updated HubSpot CLI to version ${chalk.bold(latestVersion)}`,
|
|
3077
3082
|
notInstalledGlobally: 'Cannot auto-update the HubSpot CLI because NPM is not installed globally',
|
|
3078
3083
|
updateFailed: (latestVersion) => `Failed to update HubSpot CLI to version ${chalk.bold(latestVersion)}`,
|
|
3079
|
-
enableAutoUpdatesMessage: `The HubSpot CLI can automatically keep itself up to date.\n\nThis helps ensure compatibility with the HubSpot platform. You can change this later at any time.\n\nRun${uiCommandReference('hs config set --allow-auto-updates=true')}`,
|
|
3084
|
+
enableAutoUpdatesMessage: `The HubSpot CLI can automatically keep itself up to date.\n\nThis helps ensure compatibility with the HubSpot platform. You can change this later at any time.\n\nRun ${uiCommandReference('hs config set --allow-auto-updates=true')}`,
|
|
3080
3085
|
},
|
|
3081
3086
|
},
|
|
3082
3087
|
projectProfiles: {
|
|
@@ -3941,8 +3946,8 @@ export const lib = {
|
|
|
3941
3946
|
componentsToBeMigrated: (components) => `The following features will be migrated: ${components}`,
|
|
3942
3947
|
componentsThatWillNotBeMigrated: (components) => `[NOTE] These features are not yet supported for migration but will be available later: ${components}`,
|
|
3943
3948
|
sourceContentsMoved: (newLocation) => `The contents of your old source directory have been moved to ${newLocation}, move any required files to the new source directory.`,
|
|
3944
|
-
projectMigrationWarningTitle:
|
|
3945
|
-
projectMigrationWarning: uiBetaTag(`Running the ${uiCommandReference('hs project migrate')} command will permanently upgrade your project to platformVersion
|
|
3949
|
+
projectMigrationWarningTitle: (platformVersion) => `Important: Migrating to platformVersion ${platformVersion} is irreversible`,
|
|
3950
|
+
projectMigrationWarning: (platformVersion) => uiBetaTag(`Running the ${uiCommandReference('hs project migrate')} command will permanently upgrade your project to platformVersion ${platformVersion}. This action cannot be undone. To ensure you have access to your original files, they will be copied to a new directory (archive) for safekeeping.\n\nThis command will guide you through the process, prompting you to enter the required fields and will download the new project source code into your project source directory.`, false),
|
|
3946
3951
|
exitWithoutMigrating: 'Exiting without migrating',
|
|
3947
3952
|
success: {
|
|
3948
3953
|
downloadedProject: (projectName, projectDest) => `Saved ${projectName} to ${projectDest}`,
|
|
@@ -35,3 +35,16 @@ export declare function uploadAndDeployAction({ accountId, projectDest, }: {
|
|
|
35
35
|
projectDest: string;
|
|
36
36
|
}): Promise<UploadAndDeployResult>;
|
|
37
37
|
export declare function trackGetStartedUsage(params: Record<string, unknown>, accountId: number): Promise<void>;
|
|
38
|
+
export type PollAppInstallationOptions = {
|
|
39
|
+
accountId: number;
|
|
40
|
+
projectId: number;
|
|
41
|
+
appUid: string;
|
|
42
|
+
requiredScopes?: string[];
|
|
43
|
+
optionalScopes?: string[];
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
intervalMs?: number;
|
|
46
|
+
onTimeout?: () => void;
|
|
47
|
+
};
|
|
48
|
+
export declare function pollAppInstallation({ accountId, projectId, appUid, requiredScopes, optionalScopes, timeoutMs, // 2 minutes
|
|
49
|
+
intervalMs, // 2 seconds
|
|
50
|
+
onTimeout, }: PollAppInstallationOptions): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fetchPublicAppsForPortal } from '@hubspot/local-dev-lib/api/appsDev';
|
|
4
|
+
import { fetchAppInstallationData } from '@hubspot/local-dev-lib/api/localDevAuth';
|
|
4
5
|
import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
|
|
5
6
|
import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
|
|
6
7
|
import { cloneGithubRepo } from '@hubspot/local-dev-lib/github';
|
|
@@ -144,3 +145,55 @@ export async function uploadAndDeployAction({ accountId, projectDest, }) {
|
|
|
144
145
|
export function trackGetStartedUsage(params, accountId) {
|
|
145
146
|
return trackCommandMetadataUsage('get-started', params, accountId);
|
|
146
147
|
}
|
|
148
|
+
export async function pollAppInstallation({ accountId, projectId, appUid, requiredScopes = [], optionalScopes = [], timeoutMs = 2 * 60 * 1000, // 2 minutes
|
|
149
|
+
intervalMs = 2000, // 2 seconds
|
|
150
|
+
onTimeout, }) {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
let consecutiveErrors = 0;
|
|
153
|
+
const MAX_CONSECUTIVE_ERRORS = 5;
|
|
154
|
+
let pollInterval = null;
|
|
155
|
+
let pollTimeout = null;
|
|
156
|
+
const cleanup = () => {
|
|
157
|
+
if (pollInterval) {
|
|
158
|
+
clearTimeout(pollInterval);
|
|
159
|
+
pollInterval = null;
|
|
160
|
+
}
|
|
161
|
+
if (pollTimeout) {
|
|
162
|
+
clearTimeout(pollTimeout);
|
|
163
|
+
pollTimeout = null;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
pollTimeout = setTimeout(() => {
|
|
167
|
+
cleanup();
|
|
168
|
+
if (onTimeout) {
|
|
169
|
+
onTimeout();
|
|
170
|
+
}
|
|
171
|
+
resolve(); // Resolve instead of reject to allow continuing with timeout state
|
|
172
|
+
}, timeoutMs);
|
|
173
|
+
const poll = async () => {
|
|
174
|
+
try {
|
|
175
|
+
const { data } = await fetchAppInstallationData(accountId, projectId, appUid, requiredScopes, optionalScopes);
|
|
176
|
+
// Reset error counter on successful fetch
|
|
177
|
+
consecutiveErrors = 0;
|
|
178
|
+
if (data.isInstalledWithScopeGroups) {
|
|
179
|
+
cleanup();
|
|
180
|
+
resolve();
|
|
181
|
+
}
|
|
182
|
+
else if (pollInterval) {
|
|
183
|
+
pollInterval = setTimeout(poll, intervalMs);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
consecutiveErrors++;
|
|
188
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
189
|
+
cleanup();
|
|
190
|
+
reject(new Error(`Failed to check app installation status after ${MAX_CONSECUTIVE_ERRORS} consecutive errors`, { cause: error }));
|
|
191
|
+
}
|
|
192
|
+
else if (pollInterval !== null) {
|
|
193
|
+
pollInterval = setTimeout(poll, intervalMs);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
pollInterval = setTimeout(poll, 0);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
@@ -5,9 +5,26 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
5
5
|
absoluteProjectPath: z.ZodString;
|
|
6
6
|
absoluteCurrentWorkingDirectory: z.ZodString;
|
|
7
7
|
addApp: z.ZodBoolean;
|
|
8
|
-
distribution: z.ZodOptional<z.
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
distribution: z.ZodOptional<z.ZodEnum<{
|
|
9
|
+
marketplace: "marketplace";
|
|
10
|
+
private: "private";
|
|
11
|
+
}>>;
|
|
12
|
+
auth: z.ZodOptional<z.ZodEnum<{
|
|
13
|
+
oauth: "oauth";
|
|
14
|
+
static: "static";
|
|
15
|
+
}>>;
|
|
16
|
+
features: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
17
|
+
card: "card";
|
|
18
|
+
settings: "settings";
|
|
19
|
+
"app-event": "app-event";
|
|
20
|
+
page: "page";
|
|
21
|
+
"workflow-action-tool": "workflow-action-tool";
|
|
22
|
+
webhooks: "webhooks";
|
|
23
|
+
"workflow-action": "workflow-action";
|
|
24
|
+
"app-function": "app-function";
|
|
25
|
+
"app-object": "app-object";
|
|
26
|
+
scim: "scim";
|
|
27
|
+
}>>>;
|
|
11
28
|
}, z.core.$strip>;
|
|
12
29
|
export type AddFeatureInputSchema = z.infer<typeof inputSchemaZodObject>;
|
|
13
30
|
export declare class AddFeatureToProjectTool extends Tool<AddFeatureInputSchema> {
|
|
@@ -15,17 +15,13 @@ const inputSchema = {
|
|
|
15
15
|
.boolean()
|
|
16
16
|
.describe('Should an app be added? If there is no app in the project, an app must be added to add a feature'),
|
|
17
17
|
distribution: z
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
]))
|
|
22
|
-
.describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Private is used if you do not wish to distribute your app on the HubSpot marketplace. '),
|
|
18
|
+
.enum([APP_DISTRIBUTION_TYPES.MARKETPLACE, APP_DISTRIBUTION_TYPES.PRIVATE])
|
|
19
|
+
.describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Private is used if you do not wish to distribute your app on the HubSpot marketplace. ')
|
|
20
|
+
.optional(),
|
|
23
21
|
auth: z
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
]))
|
|
28
|
-
.describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Static uses a static non changing authentication token, and is only available for private distribution. '),
|
|
22
|
+
.enum([APP_AUTH_TYPES.STATIC, APP_AUTH_TYPES.OAUTH])
|
|
23
|
+
.describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Static uses a static non changing authentication token, and is only available for private distribution. ')
|
|
24
|
+
.optional(),
|
|
29
25
|
features,
|
|
30
26
|
};
|
|
31
27
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -5,10 +5,30 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
5
5
|
absoluteCurrentWorkingDirectory: z.ZodString;
|
|
6
6
|
name: z.ZodOptional<z.ZodString>;
|
|
7
7
|
destination: z.ZodString;
|
|
8
|
-
projectBase: z.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
projectBase: z.ZodEnum<{
|
|
9
|
+
app: "app";
|
|
10
|
+
empty: "empty";
|
|
11
|
+
}>;
|
|
12
|
+
distribution: z.ZodOptional<z.ZodEnum<{
|
|
13
|
+
marketplace: "marketplace";
|
|
14
|
+
private: "private";
|
|
15
|
+
}>>;
|
|
16
|
+
auth: z.ZodOptional<z.ZodEnum<{
|
|
17
|
+
oauth: "oauth";
|
|
18
|
+
static: "static";
|
|
19
|
+
}>>;
|
|
20
|
+
features: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
21
|
+
card: "card";
|
|
22
|
+
settings: "settings";
|
|
23
|
+
"app-event": "app-event";
|
|
24
|
+
page: "page";
|
|
25
|
+
"workflow-action-tool": "workflow-action-tool";
|
|
26
|
+
webhooks: "webhooks";
|
|
27
|
+
"workflow-action": "workflow-action";
|
|
28
|
+
"app-function": "app-function";
|
|
29
|
+
"app-object": "app-object";
|
|
30
|
+
scim: "scim";
|
|
31
|
+
}>>>;
|
|
12
32
|
}, z.core.$strip>;
|
|
13
33
|
export type CreateProjectInputSchema = z.infer<typeof inputSchemaZodObject>;
|
|
14
34
|
export declare class CreateProjectTool extends Tool<CreateProjectInputSchema> {
|
|
@@ -18,19 +18,14 @@ const inputSchema = {
|
|
|
18
18
|
.string()
|
|
19
19
|
.describe('DO NOT use the current directory unless the user has explicitly stated to do so. Relative path to the directory the project will be created in.'),
|
|
20
20
|
projectBase: z
|
|
21
|
-
.
|
|
21
|
+
.enum([EMPTY_PROJECT, PROJECT_WITH_APP])
|
|
22
22
|
.describe('Empty will create an empty project, and app will create a project with an app inside of it.'),
|
|
23
23
|
distribution: z
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
]))
|
|
28
|
-
.describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Private is used if you do not wish to distribute your app on the HubSpot marketplace. '),
|
|
24
|
+
.enum([APP_DISTRIBUTION_TYPES.MARKETPLACE, APP_DISTRIBUTION_TYPES.PRIVATE])
|
|
25
|
+
.describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Private is used if you do not wish to distribute your app on the HubSpot marketplace. ')
|
|
26
|
+
.optional(),
|
|
29
27
|
auth: z
|
|
30
|
-
.
|
|
31
|
-
z.literal(APP_AUTH_TYPES.STATIC),
|
|
32
|
-
z.literal(APP_AUTH_TYPES.OAUTH),
|
|
33
|
-
]))
|
|
28
|
+
.enum([APP_AUTH_TYPES.STATIC, APP_AUTH_TYPES.OAUTH])
|
|
34
29
|
.describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Static uses a static non changing authentication token, and is only available for private distribution. ')
|
|
35
30
|
.optional(),
|
|
36
31
|
features,
|
|
@@ -12,18 +12,15 @@ const inputSchema = {
|
|
|
12
12
|
absoluteCurrentWorkingDirectory,
|
|
13
13
|
appId: z
|
|
14
14
|
.string()
|
|
15
|
-
.
|
|
16
|
-
.describe('The numeric app ID as a string (e.g., "3003909"). Use get-apps-info to find available app IDs.'),
|
|
15
|
+
.describe('The numeric app ID as a string (e.g., "3003909"). Must contain only digits. Use get-apps-info to find available app IDs.'),
|
|
17
16
|
startDate: z
|
|
18
17
|
.string()
|
|
19
|
-
.
|
|
20
|
-
.optional()
|
|
21
|
-
.describe('Start date for the usage patterns query in ISO 8601 format (e.g., 2025-01-01).'),
|
|
18
|
+
.describe('Start date for the usage patterns query in YYYY-MM-DD format (e.g., 2025-01-01).')
|
|
19
|
+
.optional(),
|
|
22
20
|
endDate: z
|
|
23
21
|
.string()
|
|
24
|
-
.
|
|
25
|
-
.optional()
|
|
26
|
-
.describe('End date for the usage patterns query in ISO 8601 format (e.g., 2025-12-31).'),
|
|
22
|
+
.describe('End date for the usage patterns query in YYYY-MM-DD format (e.g., 2025-12-31).')
|
|
23
|
+
.optional(),
|
|
27
24
|
};
|
|
28
25
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
26
|
const inputSchemaZodObject = z.object({ ...inputSchema });
|
|
@@ -5,12 +5,12 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
5
5
|
absoluteProjectPath: z.ZodString;
|
|
6
6
|
absoluteCurrentWorkingDirectory: z.ZodString;
|
|
7
7
|
buildId: z.ZodNumber;
|
|
8
|
-
logLevel: z.
|
|
8
|
+
logLevel: z.ZodOptional<z.ZodEnum<{
|
|
9
9
|
ERROR: "ERROR";
|
|
10
10
|
WARN: "WARN";
|
|
11
11
|
INFO: "INFO";
|
|
12
12
|
ALL: "ALL";
|
|
13
|
-
}
|
|
13
|
+
}>>;
|
|
14
14
|
}, z.core.$strip>;
|
|
15
15
|
export type GetBuildLogsInputSchema = z.infer<typeof inputSchemaZodObject>;
|
|
16
16
|
export declare class GetBuildLogsTool extends Tool<GetBuildLogsInputSchema> {
|
|
@@ -18,9 +18,8 @@ const inputSchema = {
|
|
|
18
18
|
.describe('Build ID to fetch logs for. Use get-build-status to find recent build IDs.'),
|
|
19
19
|
logLevel: z
|
|
20
20
|
.enum(['ERROR', 'WARN', 'INFO', 'ALL'])
|
|
21
|
-
.
|
|
22
|
-
.
|
|
23
|
-
.describe('Filter logs by level. ERROR: Show only errors, WARN: Show only warnings, INFO: Show only info, ALL: Show all logs.'),
|
|
21
|
+
.describe('Filter logs by level. ERROR: Show only errors, WARN: Show only warnings, INFO: Show only info, ALL: Show all logs. Defaults to ALL if not specified.')
|
|
22
|
+
.optional(),
|
|
24
23
|
};
|
|
25
24
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
26
25
|
const inputSchemaZodObject = z.object({ ...inputSchema });
|
|
@@ -85,14 +84,14 @@ export class GetBuildLogsTool extends Tool {
|
|
|
85
84
|
if (allLogs.length === 0) {
|
|
86
85
|
return formatTextContents(absoluteCurrentWorkingDirectory, `No logs found for build #${buildId} in project '${projectName}'.`);
|
|
87
86
|
}
|
|
88
|
-
const
|
|
87
|
+
const resolvedLogLevel = logLevel || 'ALL';
|
|
88
|
+
const filteredLogs = filterLogsByLevel(allLogs, resolvedLogLevel);
|
|
89
89
|
let output;
|
|
90
90
|
if (filteredLogs.length === 0) {
|
|
91
|
-
|
|
92
|
-
output = `No ${logLevel} level logs found for build #${buildId} in '${projectName}'.\nShowing all logs instead:\n\n${formatLogs(allLogs)}`;
|
|
91
|
+
output = `No ${resolvedLogLevel} level logs found for build #${buildId} in '${projectName}'.\nShowing all logs instead:\n\n${formatLogs(allLogs)}`;
|
|
93
92
|
}
|
|
94
93
|
else {
|
|
95
|
-
output = `Logs for build #${buildId} in '${projectName}' (${
|
|
94
|
+
output = `Logs for build #${buildId} in '${projectName}' (${resolvedLogLevel} level):\n\n${formatLogs(filteredLogs)}`;
|
|
96
95
|
}
|
|
97
96
|
return formatTextContents(absoluteCurrentWorkingDirectory, output);
|
|
98
97
|
}
|
|
@@ -5,7 +5,7 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
5
5
|
absoluteProjectPath: z.ZodString;
|
|
6
6
|
absoluteCurrentWorkingDirectory: z.ZodString;
|
|
7
7
|
buildId: z.ZodOptional<z.ZodNumber>;
|
|
8
|
-
limit: z.
|
|
8
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
9
9
|
}, z.core.$strip>;
|
|
10
10
|
export type GetBuildStatusInputSchema = z.infer<typeof inputSchemaZodObject>;
|
|
11
11
|
export declare class GetBuildStatusTool extends Tool<GetBuildStatusInputSchema> {
|
|
@@ -19,9 +19,8 @@ const inputSchema = {
|
|
|
19
19
|
.describe('Optional: Specific build ID to inspect. If omitted, shows recent builds to help identify the latest build.'),
|
|
20
20
|
limit: z
|
|
21
21
|
.number()
|
|
22
|
-
.
|
|
23
|
-
.
|
|
24
|
-
.describe('Number of recent builds to fetch when buildId is not specified.'),
|
|
22
|
+
.describe('Number of recent builds to fetch when buildId is not specified. Defaults to 3 if not specified.')
|
|
23
|
+
.optional(),
|
|
25
24
|
};
|
|
26
25
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
27
26
|
const inputSchemaZodObject = z.object({ ...inputSchema });
|
|
@@ -127,7 +126,7 @@ export class GetBuildStatusTool extends Tool {
|
|
|
127
126
|
}
|
|
128
127
|
else {
|
|
129
128
|
const response = await fetchProjectBuilds(accountId, projectName, {
|
|
130
|
-
limit,
|
|
129
|
+
limit: limit || 3,
|
|
131
130
|
});
|
|
132
131
|
const { results } = response.data;
|
|
133
132
|
if (!results || results.length === 0) {
|
|
@@ -3,7 +3,12 @@ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
declare const inputSchemaZodObject: z.ZodObject<{
|
|
5
5
|
absoluteCurrentWorkingDirectory: z.ZodString;
|
|
6
|
-
command: z.ZodOptional<z.
|
|
6
|
+
command: z.ZodOptional<z.ZodEnum<{
|
|
7
|
+
"hs auth": "hs auth";
|
|
8
|
+
"hs project create": "hs project create";
|
|
9
|
+
"hs project upload": "hs project upload";
|
|
10
|
+
"hs init": "hs init";
|
|
11
|
+
}>>;
|
|
7
12
|
}, z.core.$strip>;
|
|
8
13
|
type InputSchemaType = z.infer<typeof inputSchemaZodObject>;
|
|
9
14
|
export declare class GuidedWalkthroughTool extends Tool<InputSchemaType> {
|
|
@@ -14,12 +14,7 @@ const nextCommands = {
|
|
|
14
14
|
const inputSchema = {
|
|
15
15
|
absoluteCurrentWorkingDirectory,
|
|
16
16
|
command: z
|
|
17
|
-
.
|
|
18
|
-
z.literal('hs init'),
|
|
19
|
-
z.literal('hs auth'),
|
|
20
|
-
z.literal('hs project create'),
|
|
21
|
-
z.literal('hs project upload'),
|
|
22
|
-
])
|
|
17
|
+
.enum(['hs init', 'hs auth', 'hs project create', 'hs project upload'])
|
|
23
18
|
.describe('The command to learn more about. Start with `hs init`')
|
|
24
19
|
.optional(),
|
|
25
20
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { GetApiUsagePatternsByAppIdTool } from '../GetApiUsagePatternsByAppIdTool.js';
|
|
2
|
-
import { z } from 'zod';
|
|
3
2
|
import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
|
|
4
3
|
import { http } from '@hubspot/local-dev-lib/http';
|
|
5
4
|
import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
|
|
@@ -52,37 +51,6 @@ describe('mcp-server/tools/project/GetApiUsagePatternsByAppIdTool', () => {
|
|
|
52
51
|
expect(result).toBe(mockRegisteredTool);
|
|
53
52
|
});
|
|
54
53
|
});
|
|
55
|
-
describe('input validation', () => {
|
|
56
|
-
const inputSchema = z.object({
|
|
57
|
-
appId: z
|
|
58
|
-
.string()
|
|
59
|
-
.describe('The application ID to get API usage patterns for.'),
|
|
60
|
-
startDate: z
|
|
61
|
-
.string()
|
|
62
|
-
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Start date must be in YYYY-MM-DD format')
|
|
63
|
-
.optional()
|
|
64
|
-
.describe('Start date for the usage patterns query in ISO 8601 format (e.g., 2025-01-01).'),
|
|
65
|
-
endDate: z
|
|
66
|
-
.string()
|
|
67
|
-
.regex(/^\d{4}-\d{2}-\d{2}$/, 'End date must be in YYYY-MM-DD format')
|
|
68
|
-
.optional()
|
|
69
|
-
.describe('End date for the usage patterns query in ISO 8601 format (e.g., 2025-12-31).'),
|
|
70
|
-
});
|
|
71
|
-
it('should validate date format correctly', () => {
|
|
72
|
-
const validInput = {
|
|
73
|
-
appId: '12345',
|
|
74
|
-
startDate: '2025-01-01',
|
|
75
|
-
endDate: '2025-12-31',
|
|
76
|
-
};
|
|
77
|
-
const invalidInput = {
|
|
78
|
-
appId: '12345',
|
|
79
|
-
startDate: '2025-1-1',
|
|
80
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
81
|
-
};
|
|
82
|
-
expect(() => inputSchema.parse(validInput)).not.toThrow();
|
|
83
|
-
expect(() => inputSchema.parse(invalidInput)).toThrow();
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
54
|
describe('handler', () => {
|
|
87
55
|
const input = {
|
|
88
56
|
absoluteCurrentWorkingDirectory: '/test/dir',
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
export declare const absoluteProjectPath: z.ZodString;
|
|
3
3
|
export declare const absoluteCurrentWorkingDirectory: z.ZodString;
|
|
4
|
-
export declare const features: z.ZodOptional<z.ZodArray<z.
|
|
4
|
+
export declare const features: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
5
|
+
card: "card";
|
|
6
|
+
settings: "settings";
|
|
7
|
+
"app-event": "app-event";
|
|
8
|
+
page: "page";
|
|
9
|
+
"workflow-action-tool": "workflow-action-tool";
|
|
10
|
+
webhooks: "webhooks";
|
|
11
|
+
"workflow-action": "workflow-action";
|
|
12
|
+
"app-function": "app-function";
|
|
13
|
+
"app-object": "app-object";
|
|
14
|
+
scim: "scim";
|
|
15
|
+
}>>>;
|
|
5
16
|
export declare const docsSearchQuery: z.ZodString;
|
|
6
17
|
export declare const docUrl: z.ZodString;
|
|
@@ -6,23 +6,19 @@ export const absoluteCurrentWorkingDirectory = z
|
|
|
6
6
|
.string()
|
|
7
7
|
.describe('The absolute path to the current working directory.');
|
|
8
8
|
export const features = z
|
|
9
|
-
.array(z.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
z.literal('app-object'),
|
|
21
|
-
z.literal('app-event'),
|
|
22
|
-
z.literal('scim'),
|
|
23
|
-
z.literal('page'),
|
|
9
|
+
.array(z.enum([
|
|
10
|
+
'card',
|
|
11
|
+
'settings',
|
|
12
|
+
'app-function',
|
|
13
|
+
'webhooks',
|
|
14
|
+
'workflow-action',
|
|
15
|
+
'workflow-action-tool',
|
|
16
|
+
'app-object',
|
|
17
|
+
'app-event',
|
|
18
|
+
'scim',
|
|
19
|
+
'page',
|
|
24
20
|
]))
|
|
25
|
-
.describe('The features to include in the project, multiple options can be selected')
|
|
21
|
+
.describe('The features to include in the project, multiple options can be selected. "app-function" is also known as a public serverless function. "workflow-action" is also known as a custom workflow action. "workflow-action-tool" is also known as agent tools.')
|
|
26
22
|
.optional();
|
|
27
23
|
export const docsSearchQuery = z
|
|
28
24
|
.string()
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cli",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.5-experimental.0",
|
|
4
4
|
"description": "The official CLI for developing on HubSpot",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": "https://github.com/HubSpot/hubspot-cli",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@hubspot/cms-dev-server": "1.2.16",
|
|
10
|
-
"@hubspot/local-dev-lib": "
|
|
10
|
+
"@hubspot/local-dev-lib": "0.7.2-experimental.0",
|
|
11
11
|
"@hubspot/project-parsing-lib": "0.12.0",
|
|
12
12
|
"@hubspot/serverless-dev-runtime": "7.0.7",
|
|
13
13
|
"@hubspot/ui-extensions-dev-server": "1.1.8",
|
|
@@ -119,8 +119,6 @@
|
|
|
119
119
|
"registry": "https://registry.npmjs.org/"
|
|
120
120
|
},
|
|
121
121
|
"resolutions": {
|
|
122
|
-
"eslint-visitor-keys": "4.2.0"
|
|
123
|
-
"react": "19.2.3",
|
|
124
|
-
"react-dom": "19.2.3"
|
|
122
|
+
"eslint-visitor-keys": "4.2.0"
|
|
125
123
|
}
|
|
126
124
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { sanitizeFileName, untildify } from '@hubspot/local-dev-lib/path';
|
|
3
3
|
import { useApp, useFocus, useInput } from 'ink';
|
|
4
|
+
import open from 'open';
|
|
4
5
|
import { useCallback, useEffect, useReducer } from 'react';
|
|
5
6
|
import { commands } from '../../../lang/en.js';
|
|
6
|
-
import { createProjectAction, trackGetStartedUsage, uploadAndDeployAction, } from '../../../lib/getStartedV2Actions.js';
|
|
7
|
+
import { createProjectAction, pollAppInstallation, trackGetStartedUsage, uploadAndDeployAction, } from '../../../lib/getStartedV2Actions.js';
|
|
8
|
+
import { validateProjectDirectory } from '../../../lib/prompts/projectNameAndDestPrompt.js';
|
|
7
9
|
import { uiAccountDescription } from '../../../lib/ui/index.js';
|
|
8
10
|
import { ACTION_STATUSES, GET_STARTED_FLOW_STEPS, } from '../../lib/constants.js';
|
|
9
11
|
import { flowReducer } from './reducer.js';
|
|
12
|
+
import { InstallationScreen } from './screens/InstallationScreen.js';
|
|
10
13
|
import { ProjectSetupScreen } from './screens/ProjectSetupScreen.js';
|
|
11
14
|
import { UploadScreen } from './screens/UploadScreen.js';
|
|
12
15
|
import { getProject } from './selectors.js';
|
|
@@ -44,6 +47,7 @@ export function GetStartedFlow({ derivedAccountId, initialName, initialDest, })
|
|
|
44
47
|
statuses: {
|
|
45
48
|
create: initialName ? ACTION_STATUSES.RUNNING : ACTION_STATUSES.IDLE,
|
|
46
49
|
upload: ACTION_STATUSES.IDLE,
|
|
50
|
+
installApp: ACTION_STATUSES.IDLE,
|
|
47
51
|
},
|
|
48
52
|
});
|
|
49
53
|
const [state, dispatch] = useReducer(flowReducer, getInitialState());
|
|
@@ -68,6 +72,16 @@ export function GetStartedFlow({ derivedAccountId, initialName, initialDest, })
|
|
|
68
72
|
dispatch({ type: 'SET_STEP', payload: GET_STARTED_FLOW_STEPS.DEST_INPUT });
|
|
69
73
|
}, []);
|
|
70
74
|
const handleDestSubmit = useCallback(async () => {
|
|
75
|
+
const validationResult = validateProjectDirectory(project.destination);
|
|
76
|
+
if (validationResult !== true) {
|
|
77
|
+
dispatch({
|
|
78
|
+
type: 'SET_DEST_ERROR',
|
|
79
|
+
payload: typeof validationResult === 'string'
|
|
80
|
+
? validationResult
|
|
81
|
+
: commands.getStarted.v2.unknownError,
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
71
85
|
dispatch({ type: 'SET_STEP', payload: GET_STARTED_FLOW_STEPS.CREATING });
|
|
72
86
|
try {
|
|
73
87
|
await createProjectAction({
|
|
@@ -108,6 +122,57 @@ export function GetStartedFlow({ derivedAccountId, initialName, initialDest, })
|
|
|
108
122
|
dispatch({ type: 'UPLOAD_ERROR', payload: errorMessage });
|
|
109
123
|
}
|
|
110
124
|
}, [derivedAccountId, project.destination]);
|
|
125
|
+
const handlePollInstallation = useCallback(async () => {
|
|
126
|
+
const uploadApp = project.uploadResult?.app;
|
|
127
|
+
const projectId = project.uploadResult?.projectId;
|
|
128
|
+
if (!projectId || !uploadApp?.uid) {
|
|
129
|
+
dispatch({
|
|
130
|
+
type: 'INSTALL_APP_ERROR',
|
|
131
|
+
payload: commands.getStarted.v2.unknownError,
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await pollAppInstallation({
|
|
137
|
+
accountId: derivedAccountId,
|
|
138
|
+
projectId,
|
|
139
|
+
appUid: uploadApp.uid,
|
|
140
|
+
requiredScopes: uploadApp.config?.auth?.requiredScopes,
|
|
141
|
+
optionalScopes: uploadApp.config?.auth?.optionalScopes,
|
|
142
|
+
onTimeout: () => {
|
|
143
|
+
dispatch({ type: 'SET_POLLING_TIMED_OUT', payload: true });
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
dispatch({ type: 'INSTALL_APP_DONE' });
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
dispatch({
|
|
150
|
+
type: 'INSTALL_APP_ERROR',
|
|
151
|
+
payload: error instanceof Error
|
|
152
|
+
? error.message
|
|
153
|
+
: commands.getStarted.v2.unknownError,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}, [project.uploadResult, derivedAccountId]);
|
|
157
|
+
const handleBrowserOpen = useCallback(async (shouldOpen) => {
|
|
158
|
+
await trackGetStartedUsage({
|
|
159
|
+
step: 'open-install-page',
|
|
160
|
+
type: shouldOpen ? 'opened' : 'declined',
|
|
161
|
+
}, derivedAccountId);
|
|
162
|
+
if (shouldOpen && project.uploadResult?.installUrl) {
|
|
163
|
+
try {
|
|
164
|
+
await open(project.uploadResult.installUrl, { url: true });
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
dispatch({
|
|
168
|
+
type: 'SET_BROWSER_FAILED_URL',
|
|
169
|
+
payload: project.uploadResult.installUrl,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
dispatch({ type: 'START_INSTALL_APP' });
|
|
174
|
+
await handlePollInstallation();
|
|
175
|
+
}, [project.uploadResult, derivedAccountId, handlePollInstallation]);
|
|
111
176
|
const handleNameChange = useCallback((value) => {
|
|
112
177
|
dispatch({ type: 'SET_PROJECT_NAME', payload: value });
|
|
113
178
|
}, []);
|
|
@@ -116,7 +181,8 @@ export function GetStartedFlow({ derivedAccountId, initialName, initialDest, })
|
|
|
116
181
|
}, []);
|
|
117
182
|
useInput((_, key) => {
|
|
118
183
|
const hasError = state.statuses.create === ACTION_STATUSES.ERROR ||
|
|
119
|
-
state.statuses.upload === ACTION_STATUSES.ERROR
|
|
184
|
+
state.statuses.upload === ACTION_STATUSES.ERROR ||
|
|
185
|
+
state.statuses.installApp === ACTION_STATUSES.ERROR;
|
|
120
186
|
if (hasError) {
|
|
121
187
|
exit();
|
|
122
188
|
return;
|
|
@@ -126,11 +192,22 @@ export function GetStartedFlow({ derivedAccountId, initialName, initialDest, })
|
|
|
126
192
|
if (state.step === GET_STARTED_FLOW_STEPS.COMPLETE) {
|
|
127
193
|
handleUploadStart();
|
|
128
194
|
}
|
|
195
|
+
else if (state.step === GET_STARTED_FLOW_STEPS.OPEN_APP_PROMPT) {
|
|
196
|
+
handleBrowserOpen(true);
|
|
197
|
+
}
|
|
198
|
+
else if (state.step === GET_STARTED_FLOW_STEPS.INSTALLING_APP &&
|
|
199
|
+
state.statuses.installApp === ACTION_STATUSES.DONE) {
|
|
200
|
+
// Ready for card setup - will be handled in PR3
|
|
201
|
+
exit();
|
|
202
|
+
}
|
|
129
203
|
});
|
|
130
204
|
if (state.step === GET_STARTED_FLOW_STEPS.UPLOADING ||
|
|
131
205
|
state.step === GET_STARTED_FLOW_STEPS.OPEN_APP_PROMPT) {
|
|
132
206
|
return _jsx(UploadScreen, { state: state, accountName: accountName });
|
|
133
207
|
}
|
|
208
|
+
if (state.step === GET_STARTED_FLOW_STEPS.INSTALLING_APP) {
|
|
209
|
+
return _jsx(InstallationScreen, { state: state, accountName: accountName });
|
|
210
|
+
}
|
|
134
211
|
// Show project setup screen for initial flow
|
|
135
212
|
return (_jsx(ProjectSetupScreen, { state: state, onSelectOption: handleSelect, onNameChange: handleNameChange, onNameSubmit: handleNameSubmit, onDestChange: handleDestChange, onDestSubmit: handleDestSubmit }));
|
|
136
213
|
}
|
|
@@ -14,6 +14,7 @@ export type AppState = {
|
|
|
14
14
|
export type ActionStatuses = {
|
|
15
15
|
create: ActionStatus;
|
|
16
16
|
upload: ActionStatus;
|
|
17
|
+
installApp: ActionStatus;
|
|
17
18
|
};
|
|
18
19
|
export type FlowState = {
|
|
19
20
|
step: FlowStep;
|
|
@@ -21,6 +22,9 @@ export type FlowState = {
|
|
|
21
22
|
app: AppState;
|
|
22
23
|
statuses: ActionStatuses;
|
|
23
24
|
error?: string;
|
|
25
|
+
destError?: string;
|
|
26
|
+
browserFailedUrl?: string;
|
|
27
|
+
pollingTimedOut?: boolean;
|
|
24
28
|
};
|
|
25
29
|
type FlowAction = {
|
|
26
30
|
type: 'SET_STEP';
|
|
@@ -37,6 +41,9 @@ type FlowAction = {
|
|
|
37
41
|
} | {
|
|
38
42
|
type: 'SET_ERROR';
|
|
39
43
|
payload: string;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'SET_DEST_ERROR';
|
|
46
|
+
payload: string;
|
|
40
47
|
} | {
|
|
41
48
|
type: 'CLEAR_ERROR';
|
|
42
49
|
} | {
|
|
@@ -54,6 +61,19 @@ type FlowAction = {
|
|
|
54
61
|
} | {
|
|
55
62
|
type: 'UPLOAD_ERROR';
|
|
56
63
|
payload: string;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'START_INSTALL_APP';
|
|
66
|
+
} | {
|
|
67
|
+
type: 'INSTALL_APP_DONE';
|
|
68
|
+
} | {
|
|
69
|
+
type: 'INSTALL_APP_ERROR';
|
|
70
|
+
payload: string;
|
|
71
|
+
} | {
|
|
72
|
+
type: 'SET_BROWSER_FAILED_URL';
|
|
73
|
+
payload: string;
|
|
74
|
+
} | {
|
|
75
|
+
type: 'SET_POLLING_TIMED_OUT';
|
|
76
|
+
payload: boolean;
|
|
57
77
|
};
|
|
58
78
|
export declare function flowReducer(state: FlowState, action: FlowAction): FlowState;
|
|
59
79
|
export {};
|
|
@@ -17,6 +17,13 @@ export function flowReducer(state, action) {
|
|
|
17
17
|
return {
|
|
18
18
|
...state,
|
|
19
19
|
project: { ...state.project, destination: action.payload },
|
|
20
|
+
destError: undefined,
|
|
21
|
+
};
|
|
22
|
+
case 'SET_DEST_ERROR':
|
|
23
|
+
return {
|
|
24
|
+
...state,
|
|
25
|
+
step: GET_STARTED_FLOW_STEPS.DEST_INPUT,
|
|
26
|
+
destError: action.payload,
|
|
20
27
|
};
|
|
21
28
|
case 'SET_ERROR':
|
|
22
29
|
return { ...state, error: action.payload };
|
|
@@ -66,6 +73,35 @@ export function flowReducer(state, action) {
|
|
|
66
73
|
statuses: { ...state.statuses, upload: ACTION_STATUSES.ERROR },
|
|
67
74
|
error: action.payload,
|
|
68
75
|
};
|
|
76
|
+
case 'START_INSTALL_APP':
|
|
77
|
+
return {
|
|
78
|
+
...state,
|
|
79
|
+
step: GET_STARTED_FLOW_STEPS.INSTALLING_APP,
|
|
80
|
+
statuses: { ...state.statuses, installApp: ACTION_STATUSES.RUNNING },
|
|
81
|
+
error: undefined,
|
|
82
|
+
pollingTimedOut: false,
|
|
83
|
+
};
|
|
84
|
+
case 'INSTALL_APP_DONE':
|
|
85
|
+
return {
|
|
86
|
+
...state,
|
|
87
|
+
statuses: { ...state.statuses, installApp: ACTION_STATUSES.DONE },
|
|
88
|
+
};
|
|
89
|
+
case 'INSTALL_APP_ERROR':
|
|
90
|
+
return {
|
|
91
|
+
...state,
|
|
92
|
+
statuses: { ...state.statuses, installApp: ACTION_STATUSES.ERROR },
|
|
93
|
+
error: action.payload,
|
|
94
|
+
};
|
|
95
|
+
case 'SET_BROWSER_FAILED_URL':
|
|
96
|
+
return {
|
|
97
|
+
...state,
|
|
98
|
+
browserFailedUrl: action.payload,
|
|
99
|
+
};
|
|
100
|
+
case 'SET_POLLING_TIMED_OUT':
|
|
101
|
+
return {
|
|
102
|
+
...state,
|
|
103
|
+
pollingTimedOut: action.payload,
|
|
104
|
+
};
|
|
69
105
|
default:
|
|
70
106
|
return state;
|
|
71
107
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FlowState } from '../reducer.js';
|
|
2
|
+
type InstallationScreenProps = {
|
|
3
|
+
state: FlowState;
|
|
4
|
+
accountName: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function InstallationScreen({ state, accountName, }: InstallationScreenProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { commands } from '../../../../lang/en.js';
|
|
4
|
+
import { ActionSection } from '../../ActionSection.js';
|
|
5
|
+
import { BoxWithTitle } from '../../BoxWithTitle.js';
|
|
6
|
+
import { INK_COLORS } from '../../../styles.js';
|
|
7
|
+
import { getProject } from '../selectors.js';
|
|
8
|
+
import { ACTION_STATUSES, GET_STARTED_FLOW_STEPS, } from '../../../lib/constants.js';
|
|
9
|
+
export function InstallationScreen({ state, accountName, }) {
|
|
10
|
+
const project = getProject(state);
|
|
11
|
+
const titleText = commands.getStarted.v2.startTitle;
|
|
12
|
+
// If we get to the installation screen, the app is uploaded and we have the name
|
|
13
|
+
const appName = project.uploadResult?.app?.config.name;
|
|
14
|
+
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(Text, { children: commands.getStarted.v2.installInstructions }), _jsx(ActionSection, { status: state.statuses.installApp, statusText: commands.getStarted.v2.installingApp(appName, accountName) }), state.browserFailedUrl && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Text, { color: INK_COLORS.WARNING_YELLOW, children: commands.getStarted.v2.browserFailedToOpen(state.browserFailedUrl) }) })), state.pollingTimedOut && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Text, { color: INK_COLORS.WARNING_YELLOW, children: commands.getStarted.v2.pollingTimeout(2) }) })), state.step === GET_STARTED_FLOW_STEPS.INSTALLING_APP &&
|
|
15
|
+
state.statuses.installApp === ACTION_STATUSES.DONE && (_jsx(Text, { children: commands.getStarted.v2.pressEnterToContinueSetup }))] }) }));
|
|
16
|
+
}
|
|
@@ -35,5 +35,6 @@ export function ProjectSetupScreen({ state, onSelectOption, onNameChange, onName
|
|
|
35
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
36
|
? `${state.error}\n\n${commands.getStarted.v2.pressKeyToExit}`
|
|
37
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 })
|
|
38
|
+
state.step !== GET_STARTED_FLOW_STEPS.NAME_INPUT && (_jsxs(_Fragment, { children: [_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.destError &&
|
|
39
|
+
state.step === GET_STARTED_FLOW_STEPS.DEST_INPUT && (_jsx(Text, { color: INK_COLORS.ALERT_RED, children: state.destError }))] }))] }), 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
40
|
}
|
package/ui/lib/constants.d.ts
CHANGED