From 9d04f20096326c667a99cf9004a67992e0ae7e78 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 18 Mar 2026 14:55:23 +0200 Subject: [PATCH] feat: improve error formating for service errors --- .changeset/whole-teeth-juggle.md | 5 +++ .../src/command-types/PowerSyncCommand.ts | 21 ++++++++---- .../format-powersync-service-error-display.ts | 33 +++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 .changeset/whole-teeth-juggle.md create mode 100644 packages/cli-core/src/utils/format-powersync-service-error-display.ts diff --git a/.changeset/whole-teeth-juggle.md b/.changeset/whole-teeth-juggle.md new file mode 100644 index 0000000..948ebfb --- /dev/null +++ b/.changeset/whole-teeth-juggle.md @@ -0,0 +1,5 @@ +--- +'powersync': patch +--- + +Improved formatting for PowerSync management service call errors. diff --git a/packages/cli-core/src/command-types/PowerSyncCommand.ts b/packages/cli-core/src/command-types/PowerSyncCommand.ts index 7945ecf..3685903 100644 --- a/packages/cli-core/src/command-types/PowerSyncCommand.ts +++ b/packages/cli-core/src/command-types/PowerSyncCommand.ts @@ -2,6 +2,10 @@ import { JourneyError } from '@journeyapps-labs/micro-errors'; import { Command, ux } from '@oclif/core'; import { join } from 'node:path'; +import { + formatPowersyncServiceErrorDisplay, + isPowersyncAuthServiceError +} from '../utils/format-powersync-service-error-display.js'; import { CommandHelpGroup } from './HelpGroup.js'; export type StyledErrorParams = { @@ -35,20 +39,25 @@ export abstract class PowerSyncCommand extends Command { styledError(params: StyledErrorParams): never { const { error, exitCode = 1, message, suggestions } = params; // Journey SDK errors contain additional fields that we want to pass to the error handler. - const journeyError = + const serviceError = error != null && typeof error === 'object' && 'is_journey_error' in error ? (error as JourneyError) : undefined; - const journeyErrorMessage = journeyError ? JSON.stringify(journeyError.toJSON(), null, '\t') : undefined; + const serviceErrorDetails = serviceError ? formatPowersyncServiceErrorDisplay(serviceError) : undefined; const errorDetails = - journeyErrorMessage ?? (error == null ? '' : error instanceof Error ? error.message : String(error)); + serviceErrorDetails ?? (error == null ? '' : error instanceof Error ? error.message : String(error)); const displayMessage = errorDetails ? `${message}, :: ${errorDetails}` : message; + const authSuggestions = + serviceError && isPowersyncAuthServiceError(serviceError) && !suggestions?.length + ? ['Run `powersync login` to refresh your credentials.'] + : suggestions; + this.error(ux.colorize('red', displayMessage), { ...(error instanceof Error ? error : {}), - ...(journeyError?.errorData?.code && { code: journeyError.errorData.code }), + ...(serviceError?.errorData?.code && { code: serviceError.errorData.code }), exit: exitCode, - message: journeyErrorMessage ?? message, - ...(suggestions?.length && { suggestions }) + message: serviceErrorDetails ?? message, + ...(authSuggestions?.length && { suggestions: authSuggestions }) }); } } diff --git a/packages/cli-core/src/utils/format-powersync-service-error-display.ts b/packages/cli-core/src/utils/format-powersync-service-error-display.ts new file mode 100644 index 0000000..a355456 --- /dev/null +++ b/packages/cli-core/src/utils/format-powersync-service-error-display.ts @@ -0,0 +1,33 @@ +import { JourneyError } from '@journeyapps-labs/micro-errors'; + +const AUTH_HINT = 'Authentication failed. Your PAT TOKEN might be invalid — run `powersync login` to refresh.'; + +/** HTTP 401-style and explicit authorization failures from microservice clients. */ +export function isPowersyncAuthServiceError(err: JourneyError): boolean { + const { code, status } = err.errorData; + return status === 401 || code === 'AUTHORIZATION' || err.name === 'AuthorizationError'; +} + +function displayNameFor(err: JourneyError): string { + if (isPowersyncAuthServiceError(err)) { + return 'PowerSyncAuthError'; + } + + const n = err.errorData.name ?? err.name; + return n === 'JourneyError' ? 'PowerSyncError' : (n ?? 'PowerSyncError'); +} + +/** + * Serializes microservice {@link JourneyError} for CLI output with PowerSync branding + * and extra context for authentication failures. + */ +export function formatPowersyncServiceErrorDisplay(err: JourneyError): string { + const base = { ...(err.toJSON() as Record) }; + base.name = displayNameFor(err); + if (isPowersyncAuthServiceError(err)) { + base.category = 'authentication'; + base.hint = AUTH_HINT; + } + + return JSON.stringify(base, null, '\t'); +}