Skip to content

Commit 4e486c6

Browse files
committed
chore(cli): wrap third-party errors (get-port, latest-version, inquirer, stream-json) in CliError
Made-with: Cursor
1 parent dffcdcd commit 4e486c6

8 files changed

Lines changed: 97 additions & 74 deletions

File tree

packages/cli/cli-v2/src/context/adapter/TaskContextAdapter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createLogger, LOG_LEVELS, Logger, LogLevel } from "@fern-api/logger";
22
import {
3-
type CliError,
3+
type CliError.Code,
44
type CreateInteractiveTaskParams,
55
type Finishable,
66
type InteractiveTaskContext,
@@ -74,7 +74,7 @@ export class TaskContextAdapter implements TaskContext {
7474
}
7575

7676
public captureException(error: unknown, code?: CliError.Code): void {
77-
const errorCode = resolveErrorCode(error, code);
77+
const errorCode = resolveErrorCode(error, code) ?? "INTERNAL_ERROR";
7878
this.context.telemetry.captureException(error, { errorCode });
7979
}
8080

packages/cli/cli-v2/src/context/withContext.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { LogLevel } from "@fern-api/logger";
2-
import { CliError, resolveErrorCode, shouldReportToSentry, TaskAbortSignal } from "@fern-api/task-context";
2+
import {
3+
CliError,
4+
type CliError.Code,
5+
resolveErrorCode,
6+
shouldReportToSentry,
7+
TaskAbortSignal
8+
} from "@fern-api/task-context";
39

410
import chalk from "chalk";
511
import { KeyringUnavailableError } from "../auth/errors/KeyringUnavailableError.js";

packages/cli/cli/src/cli-context/CliContext.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Project } from "@fern-api/project-loader";
55
import { isVersionAhead } from "@fern-api/semver-utils";
66
import {
77
CliError,
8+
type CliError.Code,
89
Finishable,
910
PosthogEvent,
1011
resolveErrorCode,
@@ -457,7 +458,15 @@ export class CliContext {
457458
* @returns Promise<string> representing the user's input
458459
*/
459460
public async getInput(config: { message: string; default?: string }): Promise<string> {
460-
return await input({ message: config.message, default: config.default });
461+
try {
462+
return await input({ message: config.message, default: config.default });
463+
} catch (error) {
464+
if ((error as Error)?.name === "ExitPromptError") {
465+
this.logger.info("\nCancelled by user.");
466+
throw new TaskAbortSignal();
467+
}
468+
throw error;
469+
}
461470
}
462471
}
463472

packages/cli/cli/src/cli-context/TaskContextImpl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { addPrefixToString } from "@fern-api/core-utils";
33
import { createLogger, LogLevel } from "@fern-api/logger";
44
import {
55
CliError,
6+
type CliError.Code,
67
CreateInteractiveTaskParams,
78
Finishable,
89
InteractiveTaskContext,

packages/cli/cli/src/cli-context/upgrade-utils/getLatestVersionOfCli.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { CliError} from "@fern-api/task-context";
12
import latestVersion from "latest-version";
23

34
import { CliEnvironment } from "../CliEnvironment.js";
@@ -14,7 +15,14 @@ export async function getLatestVersionOfCli({
1415
if (cliEnvironment.packageName !== "fern-api" || cliEnvironment.packageVersion === "0.0.0") {
1516
return cliEnvironment.packageVersion;
1617
}
17-
return latestVersion(cliEnvironment.packageName, {
18-
version: includePreReleases ? "prerelease" : "latest"
19-
});
18+
try {
19+
return await latestVersion(cliEnvironment.packageName, {
20+
version: includePreReleases ? "prerelease" : "latest"
21+
});
22+
} catch (error) {
23+
throw new CliError({
24+
message: `Failed to resolve latest CLI version: ${error instanceof Error ? error.message : String(error)}`,
25+
code: CliError.Code.NetworkError
26+
});
27+
}
2028
}
Lines changed: 64 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,20 @@
1-
export class CliError extends Error {
2-
public readonly code: CliError.Code;
3-
public readonly docsLink?: string;
4-
5-
constructor({ message, code, docsLink }: { message: string; code: CliError.Code; docsLink?: string }) {
6-
super(message);
7-
Object.setPrototypeOf(this, CliError.prototype);
8-
this.code = code;
9-
this.docsLink = docsLink;
10-
}
1+
export const CliError.Code = {
2+
InternalError: "INTERNAL_ERROR",
3+
ResolutionError: "RESOLUTION_ERROR",
4+
IrConversionError: "IR_CONVERSION_ERROR",
5+
ContainerError: "CONTAINER_ERROR",
6+
VersionError: "VERSION_ERROR",
7+
ParseError: "PARSE_ERROR",
8+
EnvironmentError: "ENVIRONMENT_ERROR",
9+
ReferenceError: "REFERENCE_ERROR",
10+
ValidationError: "VALIDATION_ERROR",
11+
NetworkError: "NETWORK_ERROR",
12+
AuthError: "AUTH_ERROR",
13+
ConfigError: "CONFIG_ERROR",
14+
Unclassified: "UNCLASSIFIED"
15+
} as const;
1116

12-
public static authRequired(message?: string): CliError {
13-
return new CliError({
14-
message:
15-
message ??
16-
"Authentication required. Please run 'fern login' or set the FERN_TOKEN environment variable.",
17-
code: CliError.Code.AuthError
18-
});
19-
}
20-
21-
public static unauthorized(message?: string): CliError {
22-
return new CliError({
23-
message:
24-
message ?? "Unauthorized. Please run 'fern auth login' or set the FERN_TOKEN environment variable.",
25-
code: CliError.Code.AuthError
26-
});
27-
}
28-
29-
public static notFound(message: string): CliError {
30-
return new CliError({ message, code: CliError.Code.ConfigError });
31-
}
32-
33-
public static badRequest(message: string): CliError {
34-
return new CliError({ message, code: CliError.Code.NetworkError });
35-
}
36-
37-
public static validationError(message: string): CliError {
38-
return new CliError({ message, code: CliError.Code.ValidationError });
39-
}
40-
41-
public static internalError(message: string): CliError {
42-
return new CliError({ message, code: CliError.Code.InternalError });
43-
}
44-
}
45-
46-
export namespace CliError {
47-
export type Code = (typeof Code)[keyof typeof Code];
48-
export const Code = {
49-
InternalError: "INTERNAL_ERROR",
50-
ResolutionError: "RESOLUTION_ERROR",
51-
IrConversionError: "IR_CONVERSION_ERROR",
52-
ContainerError: "CONTAINER_ERROR",
53-
VersionError: "VERSION_ERROR",
54-
ParseError: "PARSE_ERROR",
55-
EnvironmentError: "ENVIRONMENT_ERROR",
56-
ReferenceError: "REFERENCE_ERROR",
57-
ValidationError: "VALIDATION_ERROR",
58-
NetworkError: "NETWORK_ERROR",
59-
AuthError: "AUTH_ERROR",
60-
ConfigError: "CONFIG_ERROR",
61-
Unclassified: "UNCLASSIFIED"
62-
} as const;
63-
}
17+
export type CliError.Code = (typeof CliError.Code)[keyof typeof CliError.Code];
6418

6519
const SENTRY_REPORTABLE: Record<CliError.Code, boolean> = {
6620
INTERNAL_ERROR: true,
@@ -106,10 +60,55 @@ export function resolveErrorCode(error: unknown, explicitCode?: CliError.Code):
10660
return error.code;
10761
}
10862
if (isSchemaValidationError(error)) {
109-
return CliError.Code.ParseError;
63+
return "PARSE_ERROR";
11064
}
11165
if (isNodeVersionError(error)) {
112-
return CliError.Code.EnvironmentError;
66+
return "ENVIRONMENT_ERROR";
67+
}
68+
return "UNCLASSIFIED";
69+
}
70+
71+
export class CliError extends Error {
72+
public readonly code: CliError.Code;
73+
public readonly docsLink?: string;
74+
75+
constructor({ message, code, docsLink }: { message: string; code: CliError.Code; docsLink?: string }) {
76+
super(message);
77+
Object.setPrototypeOf(this, CliError.prototype);
78+
this.code = code;
79+
this.docsLink = docsLink;
80+
}
81+
82+
public static authRequired(message?: string): CliError {
83+
return new CliError({
84+
message:
85+
message ??
86+
"Authentication required. Please run 'fern login' or set the FERN_TOKEN environment variable.",
87+
code: "AUTH_ERROR"
88+
});
89+
}
90+
91+
public static unauthorized(message?: string): CliError {
92+
return new CliError({
93+
message:
94+
message ?? "Unauthorized. Please run 'fern auth login' or set the FERN_TOKEN environment variable.",
95+
code: "AUTH_ERROR"
96+
});
97+
}
98+
99+
public static notFound(message: string): CliError {
100+
return new CliError({ message, code: "CONFIG_ERROR" });
101+
}
102+
103+
public static badRequest(message: string): CliError {
104+
return new CliError({ message, code: "NETWORK_ERROR" });
105+
}
106+
107+
public static validationError(message: string): CliError {
108+
return new CliError({ message, code: "VALIDATION_ERROR" });
109+
}
110+
111+
public static internalError(message: string): CliError {
112+
return new CliError({ message, code: "INTERNAL_ERROR" });
113113
}
114-
return CliError.Code.Unclassified;
115114
}

packages/cli/task-context/src/MockTaskContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CONSOLE_LOGGER, Logger } from "@fern-api/logger";
22

3-
import { type CliError } from "./CliError.js";
3+
import { type CliError.Code } from "./CliError.js";
44
import { TaskAbortSignal } from "./TaskAbortSignal.js";
55
import { TaskContext, TaskResult } from "./TaskContext.js";
66

packages/cli/task-context/src/TaskContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Logger } from "@fern-api/logger";
22

3-
import { type CliError } from "./CliError.js";
3+
import { type CliError.Code } from "./CliError.js";
44

55
export interface TaskContext {
66
logger: Logger;

0 commit comments

Comments
 (0)