@hubspot/cli 8.8.0 → 8.9.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/commands/account/auth.d.ts +3 -1
  2. package/commands/account/auth.js +17 -1
  3. package/commands/app/logDetails.d.ts +9 -0
  4. package/commands/app/logDetails.js +86 -0
  5. package/commands/app/logs.d.ts +13 -0
  6. package/commands/app/logs.js +122 -0
  7. package/commands/app.js +8 -1
  8. package/commands/auth.js +1 -1
  9. package/commands/init.js +1 -1
  10. package/commands/project/lint.js +8 -0
  11. package/lang/en.d.ts +118 -1
  12. package/lang/en.js +119 -3
  13. package/lib/CLIWebSocketServer.d.ts +5 -3
  14. package/lib/CLIWebSocketServer.js +31 -4
  15. package/lib/accountAuth.d.ts +3 -1
  16. package/lib/accountAuth.js +43 -17
  17. package/lib/api/usageTracking.d.ts +1 -0
  18. package/lib/api/usageTracking.js +0 -17
  19. package/lib/app/logs.d.ts +38 -0
  20. package/lib/app/logs.js +225 -0
  21. package/lib/app/urls.d.ts +2 -0
  22. package/lib/app/urls.js +7 -0
  23. package/lib/auth/awaitPersonalAccessKeyOverWebsocket.d.ts +4 -0
  24. package/lib/auth/awaitPersonalAccessKeyOverWebsocket.js +145 -0
  25. package/lib/buildAccount.js +1 -1
  26. package/lib/constants.d.ts +8 -0
  27. package/lib/constants.js +8 -0
  28. package/lib/middleware/commandTargetingUtils.js +1 -0
  29. package/lib/projects/localDev/LocalDevWebsocketServer.js +1 -1
  30. package/lib/projects/workspaces.d.ts +14 -6
  31. package/lib/projects/workspaces.js +75 -29
  32. package/lib/prompts/personalAccessKeyPrompt.d.ts +2 -5
  33. package/lib/prompts/personalAccessKeyPrompt.js +7 -5
  34. package/lib/prompts/selectAppPrompt.js +1 -0
  35. package/lib/prompts/setAsDefaultAccountPrompt.js +2 -1
  36. package/lib/serverlessLogs.js +2 -2
  37. package/lib/ui/appLogs.d.ts +32 -0
  38. package/lib/ui/appLogs.js +175 -0
  39. package/lib/usageTracking.js +28 -4
  40. package/mcp-server/utils/command.js +3 -1
  41. package/mcp-server/utils/config.js +1 -0
  42. package/package.json +4 -4
  43. package/ui/components/ActionSection.d.ts +1 -1
  44. package/ui/components/InputField.d.ts +1 -1
  45. package/ui/components/SelectInput.d.ts +1 -1
  46. package/ui/components/StatusIcon.d.ts +1 -1
  47. package/ui/components/Table.d.ts +5 -5
  48. package/ui/components/getStarted/GetStartedFlow.d.ts +1 -1
  49. package/ui/components/getStarted/screens/InstallationScreen.d.ts +1 -1
  50. package/ui/components/getStarted/screens/ProjectSetupScreen.d.ts +1 -1
  51. package/ui/components/getStarted/screens/UploadScreen.d.ts +1 -1
@@ -0,0 +1,175 @@
1
+ import moment from 'moment';
2
+ import chalk from 'chalk';
3
+ import { Styles } from '@hubspot/local-dev-lib/logger';
4
+ import { uiLogger } from './logger.js';
5
+ import { indent, uiLine } from './index.js';
6
+ import { commands } from '../../lang/en.js';
7
+ import { SYSTEM_TYPE_DISPLAY_NAMES, toTypeChoice } from '../app/logs.js';
8
+ import { getAppLogDetailsUrl, getAppLogsUrl } from '../app/urls.js';
9
+ import { renderTable } from '../../ui/render.js';
10
+ const SEPARATOR = ' - ';
11
+ function formatJsonBody(body) {
12
+ try {
13
+ return JSON.stringify(JSON.parse(body), null, 2);
14
+ }
15
+ catch {
16
+ return body;
17
+ }
18
+ }
19
+ const LOG_STATUS_COLORS = {
20
+ SUCCESS: Styles.success,
21
+ ERROR: Styles.error,
22
+ };
23
+ function formatLogHeader(log, options) {
24
+ const color = LOG_STATUS_COLORS[log.status] || ((s) => s);
25
+ const timestamp = chalk.whiteBright(moment(log.createdAt).toISOString());
26
+ const status = color(log.status);
27
+ if (options.compact) {
28
+ const parts = [timestamp, status];
29
+ if (log.executionTimeMillis != null) {
30
+ parts.push(`${log.executionTimeMillis}ms`);
31
+ }
32
+ parts.push(log.id);
33
+ return parts.join(SEPARATOR);
34
+ }
35
+ return `${timestamp}${SEPARATOR}${status}`;
36
+ }
37
+ function formatLogDetails(log, options) {
38
+ if (options.compact) {
39
+ return '';
40
+ }
41
+ const lines = [];
42
+ lines.push(`${indent(1)}${commands.app.subcommands.logs.outputMessages.logDetails.id}: ${log.id}`);
43
+ if (log.executionTimeMillis != null) {
44
+ lines.push(`${indent(1)}${commands.app.subcommands.logs.outputMessages.logDetails.duration}: ${log.executionTimeMillis}ms`);
45
+ }
46
+ if (log.portalId != null) {
47
+ lines.push(`${indent(1)}${commands.app.subcommands.logs.outputMessages.logDetails.portal}: ${log.portalId}`);
48
+ }
49
+ if (log.traceId != null && log.traceId !== '') {
50
+ lines.push(`${indent(1)}${commands.app.subcommands.logs.outputMessages.logDetails.trace}: ${log.traceId}`);
51
+ }
52
+ if (log.status === 'ERROR' && log.errorType) {
53
+ lines.push(`${indent(1)}${commands.app.subcommands.logs.outputMessages.logDetails.error}: ${log.errorType}`);
54
+ if (log.errorMessage) {
55
+ lines.push(`${indent(2)}${log.errorMessage}`);
56
+ }
57
+ }
58
+ const detailUrl = getAppLogDetailsUrl(options.accountId, options.appId, options.systemType, log.id);
59
+ lines.push(`${indent(1)}${commands.app.subcommands.logs.outputMessages.logDetails.viewDetails(log.id, options.appId, toTypeChoice(options.systemType))}`);
60
+ lines.push(`${indent(1)}${commands.app.subcommands.logs.outputMessages.logDetails.viewInUI(detailUrl)}`);
61
+ return lines.join(`\n${indent(1)}`);
62
+ }
63
+ function formatLog(log, options) {
64
+ const header = formatLogHeader(log, options);
65
+ const details = formatLogDetails(log, options);
66
+ return details ? `${header}\n${details}` : header;
67
+ }
68
+ export async function outputAppLogs(response, options) {
69
+ const { results, total } = response;
70
+ if (results.length === 0) {
71
+ uiLogger.log(commands.app.subcommands.logs.errors.noLogs);
72
+ return;
73
+ }
74
+ results.forEach(log => {
75
+ uiLogger.log(formatLog(log, options));
76
+ if (!options.compact) {
77
+ uiLogger.log('');
78
+ }
79
+ });
80
+ if (!options.compact && !options.tail) {
81
+ uiLine();
82
+ uiLogger.log('');
83
+ const tableHeaders = [
84
+ commands.app.subcommands.logs.outputMessages.tableHeaders.appId,
85
+ commands.app.subcommands.logs.outputMessages.tableHeaders.type,
86
+ commands.app.subcommands.logs.outputMessages.tableHeaders.logsFound,
87
+ ];
88
+ const tableData = [
89
+ [options.appId.toString(), options.typeName, total.toString()],
90
+ ];
91
+ await renderTable(tableHeaders, tableData, true);
92
+ uiLogger.log('');
93
+ const listUrl = getAppLogsUrl(options.accountId, options.appId, options.systemType);
94
+ uiLogger.log(commands.app.subcommands.logs.outputMessages.viewInHubSpot(listUrl));
95
+ }
96
+ }
97
+ export function outputAppLogDetails(details, options) {
98
+ const status = details.errorType ? 'ERROR' : 'SUCCESS';
99
+ const color = LOG_STATUS_COLORS[status] || ((s) => s);
100
+ uiLogger.log(chalk.bold(commands.app.subcommands.logDetails.outputMessages.logDetailsHeader));
101
+ uiLogger.log('');
102
+ uiLogger.log(`${commands.app.subcommands.logDetails.outputMessages.basicInfo.id}: ${details.id}`);
103
+ uiLogger.log(`${commands.app.subcommands.logDetails.outputMessages.basicInfo.timestamp}: ${chalk.whiteBright(moment(details.requestExecutionTimestamp).toISOString())}`);
104
+ uiLogger.log(`${commands.app.subcommands.logDetails.outputMessages.basicInfo.status}: ${color(status)}`);
105
+ if (details.duration != null) {
106
+ uiLogger.log(`${commands.app.subcommands.logDetails.outputMessages.basicInfo.duration}: ${details.duration}ms`);
107
+ }
108
+ if (details.loggingSystemType) {
109
+ const typeName = SYSTEM_TYPE_DISPLAY_NAMES[details.loggingSystemType] ||
110
+ details.loggingSystemType;
111
+ uiLogger.log(`${commands.app.subcommands.logDetails.outputMessages.basicInfo.systemType}: ${typeName}`);
112
+ }
113
+ const hasContextInfo = details.portalId ||
114
+ details.traceId ||
115
+ details.serverlessFunction ||
116
+ details.location ||
117
+ details.cardName;
118
+ if (hasContextInfo) {
119
+ uiLogger.log('');
120
+ uiLogger.log(chalk.bold(commands.app.subcommands.logDetails.outputMessages.contextInfo.header));
121
+ if (details.portalId) {
122
+ uiLogger.log(`${indent(1)}${commands.app.subcommands.logDetails.outputMessages.contextInfo.portalId}: ${details.portalId}`);
123
+ }
124
+ if (details.traceId) {
125
+ uiLogger.log(`${indent(1)}${commands.app.subcommands.logDetails.outputMessages.contextInfo.traceId}: ${details.traceId}`);
126
+ }
127
+ if (details.serverlessFunction) {
128
+ uiLogger.log(`${indent(1)}${commands.app.subcommands.logDetails.outputMessages.contextInfo.function}: ${details.serverlessFunction}`);
129
+ }
130
+ if (details.location) {
131
+ uiLogger.log(`${indent(1)}${commands.app.subcommands.logDetails.outputMessages.contextInfo.location}: ${details.location}`);
132
+ }
133
+ if (details.cardName) {
134
+ uiLogger.log(`${indent(1)}${commands.app.subcommands.logDetails.outputMessages.contextInfo.card}: ${details.cardName}`);
135
+ }
136
+ if (details.userId) {
137
+ uiLogger.log(`${indent(1)}${commands.app.subcommands.logDetails.outputMessages.contextInfo.userId}: ${details.userId}`);
138
+ }
139
+ }
140
+ if (details.requestBody || details.responseBody) {
141
+ uiLogger.log('');
142
+ uiLogger.log(chalk.bold(commands.app.subcommands.logDetails.outputMessages.requestResponse
143
+ .header));
144
+ if (details.requestBody) {
145
+ uiLogger.log(`${indent(1)}${commands.app.subcommands.logDetails.outputMessages.requestResponse.requestBody}:`);
146
+ uiLogger.log(chalk.gray(formatJsonBody(details.requestBody)));
147
+ }
148
+ if (details.responseBody) {
149
+ uiLogger.log(`${indent(1)}${commands.app.subcommands.logDetails.outputMessages.requestResponse.responseBody}:`);
150
+ uiLogger.log(chalk.gray(formatJsonBody(details.responseBody)));
151
+ }
152
+ }
153
+ if (details.errorType || details.errorMessage) {
154
+ uiLogger.log('');
155
+ uiLogger.log(chalk.bold(commands.app.subcommands.logDetails.outputMessages.errorInfo.header));
156
+ if (details.errorType) {
157
+ uiLogger.log(`${indent(1)}${commands.app.subcommands.logDetails.outputMessages.errorInfo.errorType}: ${Styles.error(details.errorType)}`);
158
+ }
159
+ if (details.errorMessage) {
160
+ uiLogger.log(`${indent(1)}${commands.app.subcommands.logDetails.outputMessages.errorInfo.errorMessage}: ${details.errorMessage}`);
161
+ }
162
+ }
163
+ if (details.extraInfo && Object.keys(details.extraInfo).length > 0) {
164
+ uiLogger.log('');
165
+ uiLogger.log(chalk.bold(commands.app.subcommands.logDetails.outputMessages.additionalInfo.header));
166
+ Object.entries(details.extraInfo).forEach(([key, value]) => {
167
+ uiLogger.log(`${indent(1)}${key}: ${value}`);
168
+ });
169
+ }
170
+ uiLogger.log('');
171
+ uiLine();
172
+ uiLogger.log('');
173
+ const detailUrl = getAppLogDetailsUrl(options.accountId, options.appId, details.loggingSystemType, details.id);
174
+ uiLogger.log(commands.app.subcommands.logDetails.outputMessages.viewInHubSpot(detailUrl));
175
+ }
@@ -1,5 +1,6 @@
1
1
  import { getConfig, getConfigAccountById, getConfigFilePath, getGlobalConfigFilePath, } from '@hubspot/local-dev-lib/config';
2
- import { API_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constants/auth';
2
+ import { API_KEY_AUTH_METHOD, PERSONAL_ACCESS_KEY_AUTH_METHOD, } from '@hubspot/local-dev-lib/constants/auth';
3
+ import { getAccessToken } from '@hubspot/local-dev-lib/personalAccessKey';
3
4
  import { uiLogger } from './ui/logger.js';
4
5
  import { pkg } from './jsonLoader.js';
5
6
  import { debugError } from './errorHandlers/index.js';
@@ -57,6 +58,24 @@ export function getExecutionEnvironmentMeta() {
57
58
  executionSource: getExecutionSource(),
58
59
  };
59
60
  }
61
+ async function getUsageTrackingUserId(accountId) {
62
+ if (!accountId) {
63
+ return undefined;
64
+ }
65
+ try {
66
+ const accountConfig = getConfigAccountById(accountId);
67
+ if (accountConfig.authType !== PERSONAL_ACCESS_KEY_AUTH_METHOD.value) {
68
+ return undefined;
69
+ }
70
+ const accessToken = await getAccessToken(accountConfig.personalAccessKey, accountConfig.env, accountConfig.accountId);
71
+ return typeof accessToken.userId === 'number'
72
+ ? accessToken.userId
73
+ : undefined;
74
+ }
75
+ catch (_e) {
76
+ return undefined;
77
+ }
78
+ }
60
79
  export async function trackCommandUsage(command, meta = {}, accountId) {
61
80
  if (isUsageTrackingDisableFlagSet()) {
62
81
  uiLogger.debug(usageTrackingDiabled);
@@ -170,13 +189,18 @@ async function trackCliInteraction({ action, accountId, command, authType, meta
170
189
  ...meta,
171
190
  };
172
191
  try {
173
- uiLogger.debug('Sent usage tracking command event:', usageTrackingEvent);
174
- await sendUsageEvent({
192
+ const userId = await getUsageTrackingUserId(accountId);
193
+ const request = {
175
194
  eventName: 'cli-interaction',
176
195
  eventClass: EventClass.INTERACTION,
177
196
  meta: usageTrackingEvent,
178
197
  accountId,
179
- });
198
+ };
199
+ if (userId !== undefined) {
200
+ request.userId = userId;
201
+ }
202
+ uiLogger.debug('Sent usage tracking command event:', usageTrackingEvent);
203
+ await sendUsageEvent(request);
180
204
  }
181
205
  catch (error) {
182
206
  debugError(error);
@@ -24,10 +24,12 @@ export async function runCommandInDir(directory, command) {
24
24
  }
25
25
  finalCommand = addFlag(finalCommand, 'disable-usage-tracking', true);
26
26
  }
27
+ const resolvedDir = path.resolve(directory);
27
28
  return execAsync(finalCommand, {
28
- cwd: path.resolve(directory),
29
+ cwd: resolvedDir,
29
30
  env: {
30
31
  ...process.env,
32
+ INIT_CWD: resolvedDir,
31
33
  },
32
34
  });
33
35
  }
@@ -3,6 +3,7 @@ export function setupHubSpotConfig(absoluteCurrentWorkingDirectory) {
3
3
  if (!absoluteCurrentWorkingDirectory) {
4
4
  return;
5
5
  }
6
+ process.env.INIT_CWD = absoluteCurrentWorkingDirectory;
6
7
  const configPath = getLocalConfigFilePathIfExists(absoluteCurrentWorkingDirectory);
7
8
  if (configPath) {
8
9
  process.env.HUBSPOT_CONFIG_PATH = configPath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "8.8.0",
3
+ "version": "8.9.0-beta.1",
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",
@@ -10,10 +10,10 @@
10
10
  "!**/__tests__/**"
11
11
  ],
12
12
  "dependencies": {
13
- "@hubspot/local-dev-lib": "5.7.1",
14
- "@hubspot/project-parsing-lib": "0.16.0",
13
+ "@hubspot/local-dev-lib": "5.8.0",
14
+ "@hubspot/project-parsing-lib": "0.17.0",
15
15
  "@hubspot/serverless-dev-runtime": "7.0.7",
16
- "@hubspot/ui-extensions-dev-server": "2.0.7",
16
+ "@hubspot/ui-extensions-dev-server": "2.0.8",
17
17
  "@inquirer/prompts": "7.1.0",
18
18
  "@modelcontextprotocol/sdk": "1.29.0",
19
19
  "archiver": "7.0.1",
@@ -7,6 +7,6 @@ export type ActionSectionProps = {
7
7
  errorMessage?: string;
8
8
  children?: React.ReactNode;
9
9
  };
10
- export declare function ActionSection({ status, statusText, errorMessage, children, }: ActionSectionProps): import("react/jsx-runtime").JSX.Element | null;
10
+ export declare function ActionSection({ status, statusText, errorMessage, children, }: ActionSectionProps): import("react").JSX.Element | null;
11
11
  export declare function getActionSection(props: ActionSectionProps): React.ReactNode;
12
12
  export {};
@@ -6,5 +6,5 @@ export type InputFieldProps = {
6
6
  onChange: (value: string) => void;
7
7
  onSubmit: () => void;
8
8
  };
9
- export declare function InputField({ flag, prompt, value, isEditing, onChange, onSubmit, }: InputFieldProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function InputField({ flag, prompt, value, isEditing, onChange, onSubmit, }: InputFieldProps): import("react").JSX.Element;
10
10
  export declare function getInputField(props: InputFieldProps): React.ReactNode;
@@ -7,5 +7,5 @@ export type SelectInputProps = {
7
7
  items: SelectInputItem[];
8
8
  onSelect: (item: SelectInputItem) => void;
9
9
  };
10
- export declare function SelectInput({ items, onSelect }: SelectInputProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function SelectInput({ items, onSelect }: SelectInputProps): import("react").JSX.Element;
11
11
  export declare function getSelectInput(props: SelectInputProps): React.ReactNode;
@@ -4,6 +4,6 @@ type ActionStatus = ValueOf<typeof ACTION_STATUSES>;
4
4
  export type StatusIconProps = {
5
5
  status: ActionStatus;
6
6
  };
7
- export declare function StatusIcon({ status }: StatusIconProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function StatusIcon({ status }: StatusIconProps): import("react").JSX.Element;
8
8
  export declare function getStatusIcon(props: StatusIconProps): React.ReactNode;
9
9
  export {};
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  export type Scalar = string | number | boolean | null | undefined;
3
- export declare function getTable<T extends ScalarDict>(props: Pick<TableProps<T>, 'data'> & Partial<TableProps<T>>): import("react/jsx-runtime").JSX.Element;
3
+ export declare function getTable<T extends ScalarDict>(props: Pick<TableProps<T>, 'data'> & Partial<TableProps<T>>): React.JSX.Element;
4
4
  export type ScalarDict = {
5
5
  [key: string]: Scalar;
6
6
  };
@@ -62,7 +62,7 @@ export default class Table<T extends ScalarDict> extends React.Component<Pick<Ta
62
62
  getSeparatorRow(): (props: RowProps<T>) => JSX.Element;
63
63
  getDataRow(): (props: RowProps<T>) => JSX.Element;
64
64
  getFooterRow(): (props: RowProps<T>) => JSX.Element;
65
- render(): import("react/jsx-runtime").JSX.Element;
65
+ render(): React.JSX.Element;
66
66
  }
67
67
  type RowProps<T extends ScalarDict> = {
68
68
  key: string;
@@ -77,13 +77,13 @@ type Column<T> = {
77
77
  /**
78
78
  * Renders the header of a table.
79
79
  */
80
- export declare function Header(props: React.PropsWithChildren<object>): import("react/jsx-runtime").JSX.Element;
80
+ export declare function Header(props: React.PropsWithChildren<object>): React.JSX.Element;
81
81
  /**
82
82
  * Renders a cell in the table.
83
83
  */
84
- export declare function Cell(props: CellProps): import("react/jsx-runtime").JSX.Element;
84
+ export declare function Cell(props: CellProps): React.JSX.Element;
85
85
  /**
86
86
  * Redners the scaffold of the table.
87
87
  */
88
- export declare function Skeleton(props: React.PropsWithChildren<object>): import("react/jsx-runtime").JSX.Element;
88
+ export declare function Skeleton(props: React.PropsWithChildren<object>): React.JSX.Element;
89
89
  export {};
@@ -5,4 +5,4 @@ export type GetStartedFlowProps = {
5
5
  initialDest?: string;
6
6
  };
7
7
  export declare function getGetStartedFlow(props: GetStartedFlowProps): React.ReactNode;
8
- export declare function GetStartedFlow({ derivedAccountId, initialName, initialDest, }: GetStartedFlowProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function GetStartedFlow({ derivedAccountId, initialName, initialDest, }: GetStartedFlowProps): import("react").JSX.Element;
@@ -3,5 +3,5 @@ type InstallationScreenProps = {
3
3
  state: FlowState;
4
4
  accountName: string;
5
5
  };
6
- export declare function InstallationScreen({ state, accountName, }: InstallationScreenProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function InstallationScreen({ state, accountName, }: InstallationScreenProps): import("react").JSX.Element;
7
7
  export {};
@@ -12,5 +12,5 @@ type ProjectSetupScreenProps = {
12
12
  onDestChange: (value: string) => void;
13
13
  onDestSubmit: () => void;
14
14
  };
15
- export declare function ProjectSetupScreen({ state, onSelectOption, onNameChange, onNameSubmit, onDestChange, onDestSubmit, }: ProjectSetupScreenProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function ProjectSetupScreen({ state, onSelectOption, onNameChange, onNameSubmit, onDestChange, onDestSubmit, }: ProjectSetupScreenProps): import("react").JSX.Element;
16
16
  export {};
@@ -3,5 +3,5 @@ type UploadScreenProps = {
3
3
  state: FlowState;
4
4
  accountName: string;
5
5
  };
6
- export declare function UploadScreen({ state, accountName }: UploadScreenProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function UploadScreen({ state, accountName }: UploadScreenProps): import("react").JSX.Element;
7
7
  export {};