diff --git a/.changeset/friendly-bears-trade.md b/.changeset/friendly-bears-trade.md new file mode 100644 index 0000000..8e5df61 --- /dev/null +++ b/.changeset/friendly-bears-trade.md @@ -0,0 +1,5 @@ +--- +'@emigrate/storage-fs': minor +--- + +Handle the serialized errors coming from Emigrate, so no need to serialize errors ourselves diff --git a/.changeset/gold-suits-judge.md b/.changeset/gold-suits-judge.md new file mode 100644 index 0000000..30cc55c --- /dev/null +++ b/.changeset/gold-suits-judge.md @@ -0,0 +1,5 @@ +--- +'@emigrate/plugin-tools': minor +--- + +Add serializeError utility function for serializing Error instances diff --git a/.changeset/sixty-horses-kiss.md b/.changeset/sixty-horses-kiss.md new file mode 100644 index 0000000..b7374b7 --- /dev/null +++ b/.changeset/sixty-horses-kiss.md @@ -0,0 +1,5 @@ +--- +'@emigrate/cli': minor +--- + +Serialize errors before passing them to the storage so that storage plugins doesn't have to care about serialization of errors diff --git a/packages/cli/src/commands/up.ts b/packages/cli/src/commands/up.ts index 5002547..e20a926 100644 --- a/packages/cli/src/commands/up.ts +++ b/packages/cli/src/commands/up.ts @@ -1,6 +1,6 @@ import path from 'node:path'; import process from 'node:process'; -import { getOrLoadPlugins, getOrLoadReporter, getOrLoadStorage } from '@emigrate/plugin-tools'; +import { getOrLoadPlugins, getOrLoadReporter, getOrLoadStorage, serializeError } from '@emigrate/plugin-tools'; import { type LoaderPlugin, type MigrationFunction, @@ -219,15 +219,16 @@ export default async function upCommand({ finishedMigrations.push(finishedMigration); } catch (error) { const errorInstance = error instanceof Error ? error : new Error(String(error)); + const serializedError = serializeError(errorInstance); const duration = getDuration(start); const finishedMigration: MigrationMetadataFinished = { ...migration, status: 'failed', duration, - error: errorInstance, + error: serializedError, }; - await storage.onError(finishedMigration, errorInstance); + await storage.onError(finishedMigration, serializedError); await reporter.onMigrationError?.(finishedMigration, errorInstance); finishedMigrations.push(finishedMigration); diff --git a/packages/plugin-tools/src/index.ts b/packages/plugin-tools/src/index.ts index a617b3a..67a1801 100644 --- a/packages/plugin-tools/src/index.ts +++ b/packages/plugin-tools/src/index.ts @@ -7,8 +7,18 @@ import { type EmigrateStorage, type LoaderPlugin, type StringOrModule, + type SerializedError, } from './types.js'; +export const serializeError = (error: Error): SerializedError => { + return { + name: error.name, + message: error.message, + stack: error.stack, + cause: error.cause instanceof Error ? serializeError(error.cause) : error.cause, + }; +}; + export const isGeneratorPlugin = (plugin: any): plugin is GeneratorPlugin => { if (!plugin || typeof plugin !== 'object') { return false; diff --git a/packages/plugin-tools/src/types.ts b/packages/plugin-tools/src/types.ts index 5386b4b..138fbbc 100644 --- a/packages/plugin-tools/src/types.ts +++ b/packages/plugin-tools/src/types.ts @@ -4,11 +4,18 @@ export type StringOrModule = string | T | (() => Awaitable) | (() => Await export type MigrationStatus = 'failed' | 'done' | 'pending'; +export type SerializedError = { + name: string; + message: string; + stack?: string; + cause?: unknown; +}; + export type MigrationHistoryEntry = { name: string; status: MigrationStatus; date: Date; - error?: unknown; + error?: SerializedError; }; export type Storage = { @@ -66,7 +73,7 @@ export type Storage = { * @param migration The name of the migration that should be marked as failed. * @param error The error that caused the migration to fail. */ - onError(migration: MigrationMetadataFinished, error: Error): Promise; + onError(migration: MigrationMetadataFinished, error: SerializedError): Promise; }; export type EmigrateStorage = { @@ -142,7 +149,7 @@ export type MigrationMetadata = { export type MigrationMetadataFinished = MigrationMetadata & { status: MigrationStatus | 'skipped'; duration: number; - error?: Error; + error?: SerializedError; }; export type LoaderPlugin = { diff --git a/packages/storage-fs/src/index.ts b/packages/storage-fs/src/index.ts index 8b1fdc9..00ad93b 100644 --- a/packages/storage-fs/src/index.ts +++ b/packages/storage-fs/src/index.ts @@ -1,28 +1,12 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; -import { type EmigrateStorage, type MigrationStatus } from '@emigrate/plugin-tools/types'; +import { type SerializedError, type EmigrateStorage, type MigrationStatus } from '@emigrate/plugin-tools/types'; export type StorageFsOptions = { filename: string; }; -type SerializedError = { - name: string; - message: string; - stack?: string; - cause?: unknown; -}; - -const serializeError = (error: Error): SerializedError => { - return { - name: error.name, - message: error.message, - stack: error.stack, - cause: error.cause instanceof Error ? serializeError(error.cause) : error.cause, - }; -}; - export default function storageFs({ filename }: StorageFsOptions): EmigrateStorage { const filePath = path.resolve(process.cwd(), filename); const lockFilePath = `${filePath}.lock`; @@ -42,7 +26,7 @@ export default function storageFs({ filename }: StorageFsOptions): EmigrateStora let lastUpdate: Promise = Promise.resolve(); - const update = async (migration: string, status: MigrationStatus, error?: Error) => { + const update = async (migration: string, status: MigrationStatus, error?: SerializedError) => { lastUpdate = lastUpdate.then(async () => { const history = await read(); @@ -51,7 +35,7 @@ export default function storageFs({ filename }: StorageFsOptions): EmigrateStora [migration]: { status, date: new Date().toISOString(), - error: error ? serializeError(error) : undefined, + error, }, }; @@ -118,7 +102,7 @@ export default function storageFs({ filename }: StorageFsOptions): EmigrateStora name, status, date: new Date(date), - error: error ? new Error(error.message) : undefined, + error, }; } },