Skip to content

Commit

Permalink
CLI error tips (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
tzyl authored Feb 14, 2024
1 parent bf4135e commit 342c805
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 13 deletions.
5 changes: 5 additions & 0 deletions packages/cli/changelog/@unreleased/pr-61.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: improvement
improvement:
description: CLI error tips
links:
- https://github.com/palantir/osdk-ts/pull/61
6 changes: 5 additions & 1 deletion packages/cli/src/ExitProcessError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
*/

export class ExitProcessError extends Error {
constructor(public readonly errorCode: number, msg?: string) {
constructor(
public readonly errorCode: number,
public readonly msg?: string,
public readonly tip?: string,
) {
super(msg);
}
}
7 changes: 6 additions & 1 deletion packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { consola } from "consola";
import { colorize } from "consola/utils";
import type { Argv } from "yargs";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
Expand Down Expand Up @@ -61,13 +62,17 @@ export async function cli(args: string[] = process.argv) {
.fail(async (msg, err, argv) => {
if (err instanceof ExitProcessError) {
consola.error(err.message);
if (err.tip != null) {
consola.log(colorize("bold", `💡 Tip: ${err.tip}`));
consola.log("");
}
consola.debug(err.stack);
} else {
if (err && err instanceof YargsCheckError === false) {
throw err;
} else {
argv.showHelp();
consola.log(""); // intentional blank line
consola.log("");
consola.error(msg);
}
}
Expand Down
17 changes: 17 additions & 0 deletions packages/cli/src/net/UserAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2023 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export const USER_AGENT = `osdk-cli/${process.env.PACKAGE_VERSION}`;
57 changes: 46 additions & 11 deletions packages/cli/src/net/createFetch.mts
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,72 @@
* limitations under the License.
*/

import { createFetchHeaderMutator } from "@osdk/shared.net";
import {
createFetchHeaderMutator,
createFetchOrThrow,
PalantirApiError,
} from "@osdk/shared.net";
import { consola } from "consola";
import { ExitProcessError } from "../ExitProcessError.js";
import { USER_AGENT } from "./UserAgent.js";

export function createFetch(
tokenProvider: () => Promise<string> | string,
fetchFn: typeof fetch = fetch,
) {
return createFetchHeaderMutator(
createDebugLoggingFetch(fetchFn),
createRequestLoggingFetch(
createErrorExitingFetch(
createFetchOrThrow(fetchFn),
),
),
async (headers) => {
const token = await tokenProvider();
headers.set("Authorization", `Bearer ${token}`);
headers.set("Fetch-User-Agent", "");
headers.set("Fetch-User-Agent", USER_AGENT);
return headers;
},
);
}

function createDebugLoggingFetch(fetchFn: typeof fetch = fetch): typeof fetch {
return function debugLoggingFetch(
function createErrorExitingFetch(fetchFn: typeof fetch = fetch): typeof fetch {
return function errorExitingFetch(
input: RequestInfo | URL,
init?: RequestInit,
) {
if (typeof input === "string" || input instanceof URL) {
consola.debug(`${init?.method ?? "GET"}: ${input.toString().trim()}`);
} else {
consola.debug(`${input.method ?? "GET"}: ${input.url.toString().trim()}`);
}
return fetchFn(input, init).catch(handleFetchError);
};
}

function handleFetchError(e: unknown): Promise<Response> {
if (!(e instanceof PalantirApiError)) {
throw new ExitProcessError(1, "Unexpected fetch error");
}

let tip;
if (e.statusCode === 401) {
tip = "Check your token is valid and has not expired or been disabled";
} else if (e.statusCode === 403) {
tip = "Check your token has the required scopes for this operation";
}

throw new ExitProcessError(1, e.message, tip);
}

function createRequestLoggingFetch(
fetchFn: typeof fetch = fetch,
): typeof fetch {
return function requestLoggingFetch(
input: RequestInfo | URL,
init?: RequestInit,
) {
const requestLog = typeof input === "string" || input instanceof URL
? `${init?.method ?? "GET"}: ${input.toString().trim()}`
: `${input.method ?? "GET"}: ${input.url.toString().trim()}`;

consola.trace(requestLog);
return fetchFn(input, init).then((a) => {
consola.debug("Finished fetch");
consola.trace(`FINISH ${requestLog}`);
return a;
});
};
Expand Down
5 changes: 5 additions & 0 deletions packages/shared.net/changelog/@unreleased/pr-61.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: improvement
improvement:
description: Surface text/plain error messages
links:
- https://github.com/palantir/osdk-ts/pull/61
1 change: 1 addition & 0 deletions packages/shared.net/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export { PalantirApiError } from "./PalantirApiError.js";
export { isOk, type ResultOrError } from "./ResultOrError.js";
export { UnknownError } from "./UnknownError.js";
export { createFetchHeaderMutator } from "./util/createFetchHeaderMutator.js";
export { createFetchOrThrow } from "./util/createFetchOrThrow.js";
4 changes: 4 additions & 0 deletions packages/shared.net/src/util/createFetchOrThrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export function createFetchOrThrow(fetchFn: typeof fetch = fetch) {
}

if (!response.ok) {
if (response.headers.get("Content-Type") === "text/plain") {
throw new PalantirApiError(await response.text());
}

let body;
try {
body = await response.json();
Expand Down

0 comments on commit 342c805

Please sign in to comment.