From 1122d7037ac9172fd7110c1cf4a677e41fce0818 Mon Sep 17 00:00:00 2001 From: Robert Field Date: Fri, 29 Sep 2023 21:26:37 +0100 Subject: [PATCH] feat: improved error handling --- packages/composable-cli/package.json | 1 + .../src/commands/config/config-command.tsx | 19 ++++++--- .../src/commands/feedback/feedback-command.ts | 8 ++-- .../src/commands/generate/d2c/d2c-command.tsx | 5 ++- .../commands/generate/generate-command.tsx | 5 +-- .../commands/insights/insights-command.tsx | 3 +- .../algolia/algolia-integration-command.tsx | 3 +- .../integration/integration-command.tsx | 3 +- .../src/commands/login/login-command.ts | 11 ++--- .../src/commands/logout/logout-command.ts | 9 +++-- .../ep-payments/ep-payments-command.tsx | 3 +- .../commands/payments/payments-command.tsx | 3 +- .../src/commands/store/store-command.tsx | 7 ++-- .../src/commands/ui/error/error.tsx | 29 ++++++++++++++ .../src/commands/ui/login/welcome-note.tsx | 10 +++++ packages/composable-cli/src/types/command.ts | 2 + packages/composable-cli/src/util/command.ts | 40 +++++++++++++++++++ .../composable-cli/src/util/error-handler.ts | 37 +++++++++-------- yarn.lock | 12 ++++++ 19 files changed, 154 insertions(+), 56 deletions(-) create mode 100644 packages/composable-cli/src/commands/ui/error/error.tsx diff --git a/packages/composable-cli/package.json b/packages/composable-cli/package.json index c11e10d6..74f58a6d 100644 --- a/packages/composable-cli/package.json +++ b/packages/composable-cli/package.json @@ -34,6 +34,7 @@ "ink-big-text": "1", "ink-gradient": "2", "ink-link": "2", + "ink-table": "^3.0.0", "inquirer": "8.2.4", "node-fetch": "2.7.0", "open": "8", diff --git a/packages/composable-cli/src/commands/config/config-command.tsx b/packages/composable-cli/src/commands/config/config-command.tsx index 36f771fc..8269d945 100644 --- a/packages/composable-cli/src/commands/config/config-command.tsx +++ b/packages/composable-cli/src/commands/config/config-command.tsx @@ -1,7 +1,6 @@ import yargs from "yargs" import Conf from "conf" import { CommandContext, CommandHandlerFunction } from "../../types/command" -import { handleErrors } from "../../util/error-handler" import { ConfigCommandArguments, ConfigCommandData, @@ -16,7 +15,7 @@ export function configClearCommand(store: Conf): void { export function createConfigCommand( ctx: CommandContext, ): yargs.CommandModule<{}, ConfigCommandArguments> { - const { store, logger } = ctx + const { store, logger, handleErrors } = ctx return { command: "config", @@ -26,16 +25,24 @@ export function createConfigCommand( .command({ command: "list", describe: "List all stored configuration", - handler: (_args) => { + handler: handleErrors(async (_args) => { logger.info(JSON.stringify(store.store, null, 2)) - }, + return { + success: true, + data: {}, + } + }), }) .command({ command: "clear", describe: "Clear all stored configuration", - handler: (_args) => { + handler: handleErrors(async (_args) => { configClearCommand(store) - }, + return { + success: true, + data: {}, + } + }), }) .example("$0 config list", "list all stored configuration") .example("$0 config clear", "clear all stored configuration") diff --git a/packages/composable-cli/src/commands/feedback/feedback-command.ts b/packages/composable-cli/src/commands/feedback/feedback-command.ts index 5cc73eeb..d9c6e530 100644 --- a/packages/composable-cli/src/commands/feedback/feedback-command.ts +++ b/packages/composable-cli/src/commands/feedback/feedback-command.ts @@ -4,7 +4,6 @@ import { CommandHandlerFunction, RootCommandArguments, } from "../../types/command" -import { handleErrors } from "../../util/error-handler" import { renderInk } from "../../lib/ink/render-ink" import React from "react" import { @@ -17,19 +16,20 @@ import { Feedback } from "../ui/feedback/feedback" import { trackCommandHandler } from "../../util/track-command-handler" import { isTTY } from "../../util/is-tty" export function createFeedbackCommand( - ctx: CommandContext + ctx: CommandContext, ): yargs.CommandModule { + const { handleErrors } = ctx return { command: "feedback", describe: "Feedback to the Composable CLI", handler: handleErrors( - trackCommandHandler(ctx, createFeedbackCommandHandler) + trackCommandHandler(ctx, createFeedbackCommandHandler), ), } } export function createFeedbackCommandHandler( - _ctx: CommandContext + _ctx: CommandContext, ): CommandHandlerFunction< FeedbackCommandData, FeedbackCommandError, diff --git a/packages/composable-cli/src/commands/generate/d2c/d2c-command.tsx b/packages/composable-cli/src/commands/generate/d2c/d2c-command.tsx index 1c2bf3dd..2970adcb 100644 --- a/packages/composable-cli/src/commands/generate/d2c/d2c-command.tsx +++ b/packages/composable-cli/src/commands/generate/d2c/d2c-command.tsx @@ -24,7 +24,6 @@ import { CommandHandlerFunction, CommandResult, } from "../../../types/command" -import { handleErrors } from "../../../util/error-handler" import { getRegion, resolveHostFromRegion } from "../../../util/resolve-region" import { createApplicationKeys } from "../../../util/create-client-secret" import { renderInk } from "../../../lib/ink/render-ink" @@ -113,7 +112,9 @@ export function createD2CCommand( return addSchemaOptionsToCommand(result, options) }, - handler: handleErrors(trackCommandHandler(ctx, createD2CCommandHandler)), + handler: ctx.handleErrors( + trackCommandHandler(ctx, createD2CCommandHandler), + ), } } diff --git a/packages/composable-cli/src/commands/generate/generate-command.tsx b/packages/composable-cli/src/commands/generate/generate-command.tsx index cc46f406..b85f1bff 100644 --- a/packages/composable-cli/src/commands/generate/generate-command.tsx +++ b/packages/composable-cli/src/commands/generate/generate-command.tsx @@ -4,7 +4,6 @@ import { CommandHandlerFunction, RootCommandArguments, } from "../../types/command" -import { handleErrors } from "../../util/error-handler" import { GenerateCommandArguments, GenerateCommandData, @@ -67,7 +66,7 @@ export function createGenerateCommand( .demandCommand(1) .strict() }, - handler: handleErrors( + handler: ctx.handleErrors( trackCommandHandler(ctx, createGenerateCommandHandler), ), } @@ -125,7 +124,7 @@ export function createActiveStoreMiddleware( return } - return handleErrors(createSetStoreCommandHandler(ctx))(args) + return ctx.handleErrors(createSetStoreCommandHandler(ctx))(args) } } diff --git a/packages/composable-cli/src/commands/insights/insights-command.tsx b/packages/composable-cli/src/commands/insights/insights-command.tsx index a971d54e..4149e064 100644 --- a/packages/composable-cli/src/commands/insights/insights-command.tsx +++ b/packages/composable-cli/src/commands/insights/insights-command.tsx @@ -4,7 +4,6 @@ import { CommandHandlerFunction, RootCommandArguments, } from "../../types/command" -import { handleErrors } from "../../util/error-handler" import { InsightsCommandArguments, InsightsCommandData, @@ -30,7 +29,7 @@ export function createInsightsCommand( }) .help() }, - handler: handleErrors( + handler: ctx.handleErrors( trackCommandHandler(ctx, createInsightsCommandHandler), ), } diff --git a/packages/composable-cli/src/commands/integration/algolia/algolia-integration-command.tsx b/packages/composable-cli/src/commands/integration/algolia/algolia-integration-command.tsx index c7ef6362..db0d78a2 100644 --- a/packages/composable-cli/src/commands/integration/algolia/algolia-integration-command.tsx +++ b/packages/composable-cli/src/commands/integration/algolia/algolia-integration-command.tsx @@ -5,7 +5,6 @@ import { AlgoliaIntegrationCommandError, } from "./algolia-integration.types" import { CommandContext, CommandHandlerFunction } from "../../../types/command" -import { handleErrors } from "../../../util/error-handler" import { trackCommandHandler } from "../../../util/track-command-handler" import { createActiveStoreMiddleware, @@ -77,7 +76,7 @@ export function createAlgoliaIntegrationCommand( .fail(false) .help() }, - handler: handleErrors( + handler: ctx.handleErrors( trackCommandHandler(ctx, createAlgoliaIntegrationCommandHandler), ), } diff --git a/packages/composable-cli/src/commands/integration/integration-command.tsx b/packages/composable-cli/src/commands/integration/integration-command.tsx index 76a2429b..09557467 100644 --- a/packages/composable-cli/src/commands/integration/integration-command.tsx +++ b/packages/composable-cli/src/commands/integration/integration-command.tsx @@ -4,7 +4,6 @@ import { CommandHandlerFunction, RootCommandArguments, } from "../../types/command" -import { handleErrors } from "../../util/error-handler" import { IntegrationCommandArguments, IntegrationCommandData, @@ -29,7 +28,7 @@ export function createIntegrationCommand( .demandCommand(1) .strict() }, - handler: handleErrors( + handler: ctx.handleErrors( trackCommandHandler(ctx, createIntegrationCommandHandler), ), } diff --git a/packages/composable-cli/src/commands/login/login-command.ts b/packages/composable-cli/src/commands/login/login-command.ts index 19319f31..d0210f12 100644 --- a/packages/composable-cli/src/commands/login/login-command.ts +++ b/packages/composable-cli/src/commands/login/login-command.ts @@ -9,7 +9,6 @@ import { CommandHandlerFunction, RootCommandArguments, } from "../../types/command" -import { handleErrors } from "../../util/error-handler" import { LoginCommandArguments, LoginCommandData, @@ -81,7 +80,9 @@ export function createLoginCommand( "strip-aliased": true, }) }, - handler: handleErrors(trackCommandHandler(ctx, createLoginCommandHandler)), + handler: ctx.handleErrors( + trackCommandHandler(ctx, createLoginCommandHandler), + ), } } @@ -97,9 +98,9 @@ export function createAuthenticationMiddleware( return } - return handleErrors(trackCommandHandler(ctx, createLoginCommandHandler))( - args, - ) + return ctx.handleErrors( + trackCommandHandler(ctx, createLoginCommandHandler), + )(args) } } diff --git a/packages/composable-cli/src/commands/logout/logout-command.ts b/packages/composable-cli/src/commands/logout/logout-command.ts index 4202c856..185964b8 100644 --- a/packages/composable-cli/src/commands/logout/logout-command.ts +++ b/packages/composable-cli/src/commands/logout/logout-command.ts @@ -1,6 +1,5 @@ import yargs from "yargs" import { CommandContext, CommandHandlerFunction } from "../../types/command" -import { handleErrors } from "../../util/error-handler" import { LogoutCommandArguments, LogoutCommandData, @@ -14,17 +13,19 @@ import { handleClearCredentials } from "../../util/conf-store/store-credentials" import { trackCommandHandler } from "../../util/track-command-handler" export function createLogoutCommand( - ctx: CommandContext + ctx: CommandContext, ): yargs.CommandModule<{}, LogoutCommandArguments> { return { command: "logout", describe: "Logout of the Composable CLI", - handler: handleErrors(trackCommandHandler(ctx, createLogoutCommandHandler)), + handler: ctx.handleErrors( + trackCommandHandler(ctx, createLogoutCommandHandler), + ), } } export function createLogoutCommandHandler( - ctx: CommandContext + ctx: CommandContext, ): CommandHandlerFunction< LogoutCommandData, LogoutCommandError, diff --git a/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx b/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx index 192c4a21..bbb420df 100644 --- a/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx +++ b/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx @@ -6,7 +6,6 @@ import { EPPaymentsCommandErrorAlreadyExists, } from "./ep-payments-integration.types" import { CommandContext, CommandHandlerFunction } from "../../../types/command" -import { handleErrors } from "../../../util/error-handler" import { trackCommandHandler } from "../../../util/track-command-handler" import { createActiveStoreMiddleware, @@ -55,7 +54,7 @@ export function createEPPaymentsCommand( .fail(false) .help() }, - handler: handleErrors( + handler: ctx.handleErrors( trackCommandHandler(ctx, createEPPaymentsCommandHandler), ), } diff --git a/packages/composable-cli/src/commands/payments/payments-command.tsx b/packages/composable-cli/src/commands/payments/payments-command.tsx index ea347f3f..0b29ff83 100644 --- a/packages/composable-cli/src/commands/payments/payments-command.tsx +++ b/packages/composable-cli/src/commands/payments/payments-command.tsx @@ -4,7 +4,6 @@ import { CommandHandlerFunction, RootCommandArguments, } from "../../types/command" -import { handleErrors } from "../../util/error-handler" import { trackCommandHandler } from "../../util/track-command-handler" import { createEPPaymentsCommand } from "./ep-payments/ep-payments-command" @@ -32,7 +31,7 @@ export function createPaymentsCommand( .demandCommand(1) .strict() }, - handler: handleErrors( + handler: ctx.handleErrors( trackCommandHandler(ctx, createPaymentsCommandHandler), ), } diff --git a/packages/composable-cli/src/commands/store/store-command.tsx b/packages/composable-cli/src/commands/store/store-command.tsx index eb840dd6..9e31fbf2 100644 --- a/packages/composable-cli/src/commands/store/store-command.tsx +++ b/packages/composable-cli/src/commands/store/store-command.tsx @@ -5,7 +5,6 @@ import { CommandHandlerFunction, RootCommandArguments, } from "../../types/command" -import { handleErrors } from "../../util/error-handler" import { SetStoreCommandArguments, SetStoreCommandData, @@ -47,7 +46,9 @@ export function createStoreCommand( .demandCommand(1) .strict() }, - handler: handleErrors(trackCommandHandler(ctx, createStoreCommandHandler)), + handler: ctx.handleErrors( + trackCommandHandler(ctx, createStoreCommandHandler), + ), } } @@ -65,7 +66,7 @@ export function createSetStoreCommand( }) .help() }, - handler: handleErrors( + handler: ctx.handleErrors( trackCommandHandler(ctx, createSetStoreCommandHandler), ), } diff --git a/packages/composable-cli/src/commands/ui/error/error.tsx b/packages/composable-cli/src/commands/ui/error/error.tsx new file mode 100644 index 00000000..3a4deb30 --- /dev/null +++ b/packages/composable-cli/src/commands/ui/error/error.tsx @@ -0,0 +1,29 @@ +import React from "react" +import { Box, Newline, Text } from "ink" +import Table from "ink-table" + +export function ErrorTable({ data }: { data: Record }) { + const errors = Object.keys(data).map((key) => { + return { key, value: data[key] } + }) + + return ( + + + + There was an issue! + + + + To get support on this issue, report it on our slack community. + + Join us at + + {" "} + https://elasticpathcommunity.slack.com/join/shared_invite/zt-1upzq3nlc-O3sy1bT0UJYcOWEQQCtnqw + + + + + ) +} diff --git a/packages/composable-cli/src/commands/ui/login/welcome-note.tsx b/packages/composable-cli/src/commands/ui/login/welcome-note.tsx index e449ecb3..d4bccbb0 100644 --- a/packages/composable-cli/src/commands/ui/login/welcome-note.tsx +++ b/packages/composable-cli/src/commands/ui/login/welcome-note.tsx @@ -21,6 +21,16 @@ export function WelcomeNote({ name }: { name: string }) { A CLI for managing your Elasticpath powered storefront + + + + To get support or ask any question, join us in our slack community. + + + https://elasticpathcommunity.slack.com/join/shared_invite/zt-1upzq3nlc-O3sy1bT0UJYcOWEQQCtnqw + + + ) diff --git a/packages/composable-cli/src/types/command.ts b/packages/composable-cli/src/types/command.ts index 1c2c222b..53258e52 100644 --- a/packages/composable-cli/src/types/command.ts +++ b/packages/composable-cli/src/types/command.ts @@ -8,6 +8,7 @@ import { logging } from "@angular-devkit/core" import ansiColors from "ansi-colors" import { Moltin } from "@moltin/sdk" import { ComposableRc } from "../lib/composable-rc-schema" +import { ErrorHandler } from "../util/error-handler" export type CommandResult = | { @@ -34,6 +35,7 @@ export type CommandContext = { epClient?: Moltin composableRc?: ComposableRc workspaceRoot?: string + handleErrors: ErrorHandler } export type RootCommandArguments = { diff --git a/packages/composable-cli/src/util/command.ts b/packages/composable-cli/src/util/command.ts index e851e722..e2a45aa5 100644 --- a/packages/composable-cli/src/util/command.ts +++ b/packages/composable-cli/src/util/command.ts @@ -8,6 +8,10 @@ import path from "path" import ws from "ws" import { createConsoleLogger, ProcessOutput } from "@angular-devkit/core/node" import * as ansiColors from "ansi-colors" +import { makeErrorWrapper } from "./error-handler" +import { renderInk } from "../lib/ink/render-ink" +import React from "react" +import { ErrorTable } from "../commands/ui/error/error" // polyfill fetch & websocket const globalAny = global as any @@ -77,9 +81,45 @@ export function createCommandContext({ stderr: resolvedStderr, logger: defaultLogger, colors, + handleErrors: makeErrorWrapper( + (err) => { + if (err instanceof Error) { + console.error(err.name) + console.error(err.message) + console.error(err.stack) + console.error(err.cause) + return Promise.resolve() + } + console.error("There was an unexpected error!") + return Promise.resolve() + }, + async (result) => { + if (!result.success) { + if (result.error instanceof Error) { + console.error(`Error Code: ${result.error.name}`) + console.error(`Error Message: ${result.error.message}`) + console.error(`Error Stack: ${result.error.stack}`) + console.error(`Error Cause: ${result.error.cause}`) + } else if (isRecordStringAny(result.error)) { + await renderInk( + React.createElement(ErrorTable, { data: result.error }), + ) + } else { + console.warn( + "Error was not an known error type! Could not parse a useful error message.", + ) + } + } + }, + defaultLogger, + ), } } +function isRecordStringAny(obj: unknown): obj is Record { + return typeof obj === "object" && obj !== null +} + function createRequester(store: Conf): EpccRequester { return async function requester( url: RequestInfo, diff --git a/packages/composable-cli/src/util/error-handler.ts b/packages/composable-cli/src/util/error-handler.ts index 57dc4945..d7184868 100644 --- a/packages/composable-cli/src/util/error-handler.ts +++ b/packages/composable-cli/src/util/error-handler.ts @@ -1,23 +1,34 @@ -const makeErrorWrapper = - (errorHandler: (err: unknown) => T) => - (fn: (...a: A) => Promise) => +import { logging } from "@angular-devkit/core" +import { Result } from "../types/results" + +export type ErrorHandler = >( + fn: (...a: A) => Promise, +) => (...a: A) => Promise + +export const makeErrorWrapper = + >( + errorHandler: (err: unknown, logger: logging.Logger) => T, + resultHandler: (result: R) => Promise | void, + logger: logging.Logger, + ) => + (fn: (...a: A) => Promise) => async (...a: A): Promise => { try { const result = await fn(...a) if (isResultError(result)) { - return Promise.reject(result.error) + await errorHandler(result.error, logger) } - + await resultHandler(result) return Promise.resolve() } catch (err) { - await errorHandler(err) + await errorHandler(err, logger) return Promise.resolve() } } function isResultError( - result: any + result: any, ): result is { success: false; error: unknown } { return ( !!result && @@ -26,15 +37,3 @@ function isResultError( !result.success ) } - -export const handleErrors = makeErrorWrapper((err) => { - if (err instanceof Error) { - console.error(err.name) - console.error(err.message) - console.error(err.stack) - console.error(err.cause) - return Promise.resolve() - } - console.error("There was an unexpected error!") - return Promise.resolve() -}) diff --git a/yarn.lock b/yarn.lock index 013b9965..f197c0c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11130,6 +11130,13 @@ ink-link@2: prop-types "^15.7.2" terminal-link "^2.1.1" +ink-table@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ink-table/-/ink-table-3.0.0.tgz#109fb2ce0709567f0e38d14b2b82f311277a3628" + integrity sha512-RtcYjenHKZWjnwVNQ6zSYWMOLKwkWscDAJsqUQXftyjkYho1gGrluGss87NOoIzss0IKr74lKasd6MtlQYALiA== + dependencies: + object-hash "^2.0.3" + ink@*: version "4.4.1" resolved "https://registry.yarnpkg.com/ink/-/ink-4.4.1.tgz#ae684a141e92524af3eccf740c38f03618b48028" @@ -14211,6 +14218,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^2.0.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + object-hash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"