@emigrate/cli
Advanced tools
Comparing version
@@ -1,3 +0,3 @@ | ||
import { type MigrationHistoryEntry, type MigrationMetadata, type MigrationMetadataFinished } from '@emigrate/plugin-tools/types'; | ||
import { type MigrationHistoryEntry, type MigrationMetadata, type MigrationMetadataFinished } from '@emigrate/types'; | ||
export declare function collectMigrations(cwd: string, directory: string, history: AsyncIterable<MigrationHistoryEntry>, getMigrations?: (cwd: string, directory: string) => Promise<MigrationMetadata[]>): AsyncIterable<MigrationMetadata | MigrationMetadataFinished>; | ||
//# sourceMappingURL=collect-migrations.d.ts.map |
import process from 'node:process'; | ||
import { getOrLoadReporter, getOrLoadStorage } from '@emigrate/plugin-tools'; | ||
import { BadOptionError, MissingOptionError, StorageInitError } from '../errors.js'; | ||
import { BadOptionError, MissingOptionError, StorageInitError, toError } from '../errors.js'; | ||
import { exec } from '../exec.js'; | ||
@@ -12,3 +12,3 @@ import { migrationRunner } from '../migration-runner.js'; | ||
if (!directory) { | ||
throw new MissingOptionError('directory'); | ||
throw MissingOptionError.fromOption('directory'); | ||
} | ||
@@ -18,7 +18,7 @@ const cwd = process.cwd(); | ||
if (!storagePlugin) { | ||
throw new BadOptionError('storage', 'No storage found, please specify a storage using the storage option'); | ||
throw BadOptionError.fromOption('storage', 'No storage found, please specify a storage using the storage option'); | ||
} | ||
const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); | ||
if (!reporter) { | ||
throw new BadOptionError('reporter', 'No reporter found, please specify an existing reporter using the reporter option'); | ||
throw BadOptionError.fromOption('reporter', 'No reporter found, please specify an existing reporter using the reporter option'); | ||
} | ||
@@ -28,20 +28,29 @@ await reporter.onInit?.({ command: 'list', version, cwd, dry: false, directory }); | ||
if (storageError) { | ||
await reporter.onFinished?.([], new StorageInitError('Could not initialize storage', { cause: storageError })); | ||
await reporter.onFinished?.([], StorageInitError.fromError(storageError)); | ||
return 1; | ||
} | ||
const collectedMigrations = collectMigrations(cwd, directory, storage.getHistory()); | ||
const error = await migrationRunner({ | ||
dry: true, | ||
reporter, | ||
storage, | ||
migrations: await arrayFromAsync(collectedMigrations), | ||
async validate() { | ||
// No-op | ||
}, | ||
async execute() { | ||
throw new Error('Unexpected execute call'); | ||
}, | ||
}); | ||
return error ? 1 : 0; | ||
try { | ||
const collectedMigrations = collectMigrations(cwd, directory, storage.getHistory()); | ||
const error = await migrationRunner({ | ||
dry: true, | ||
reporter, | ||
storage, | ||
migrations: await arrayFromAsync(collectedMigrations), | ||
async validate() { | ||
// No-op | ||
}, | ||
async execute() { | ||
throw new Error('Unexpected execute call'); | ||
}, | ||
}); | ||
return error ? 1 : 0; | ||
} | ||
catch (error) { | ||
await reporter.onFinished?.([], toError(error)); | ||
return 1; | ||
} | ||
finally { | ||
await storage.end(); | ||
} | ||
} | ||
//# sourceMappingURL=list.js.map |
@@ -5,15 +5,17 @@ import process from 'node:process'; | ||
import { getTimestampPrefix, sanitizeMigrationName, getOrLoadPlugin, getOrLoadReporter } from '@emigrate/plugin-tools'; | ||
import { BadOptionError, MissingArgumentsError, MissingOptionError, UnexpectedError } from '../errors.js'; | ||
import { isFailedMigration } from '@emigrate/types'; | ||
import { BadOptionError, EmigrateError, MissingArgumentsError, MissingOptionError, UnexpectedError, toError, } from '../errors.js'; | ||
import { withLeadingPeriod } from '../with-leading-period.js'; | ||
import { version } from '../get-package-info.js'; | ||
import { getDuration } from '../get-duration.js'; | ||
const lazyDefaultReporter = async () => import('../reporters/default.js'); | ||
export default async function newCommand({ directory, template, reporter: reporterConfig, plugins = [], extension }, name) { | ||
if (!directory) { | ||
throw new MissingOptionError('directory'); | ||
throw MissingOptionError.fromOption('directory'); | ||
} | ||
if (!name) { | ||
throw new MissingArgumentsError('name'); | ||
throw MissingArgumentsError.fromArgument('name'); | ||
} | ||
if (!extension && !template && plugins.length === 0) { | ||
throw new MissingOptionError(['extension', 'template', 'plugin']); | ||
throw MissingOptionError.fromOption(['extension', 'template', 'plugin']); | ||
} | ||
@@ -23,5 +25,6 @@ const cwd = process.cwd(); | ||
if (!reporter) { | ||
throw new BadOptionError('reporter', 'No reporter found, please specify an existing reporter using the reporter option'); | ||
throw BadOptionError.fromOption('reporter', 'No reporter found, please specify an existing reporter using the reporter option'); | ||
} | ||
await reporter.onInit?.({ command: 'new', version, cwd, dry: false, directory }); | ||
const start = process.hrtime(); | ||
let filename; | ||
@@ -58,3 +61,3 @@ let content; | ||
if (!filename || content === undefined) { | ||
throw new BadOptionError('plugin', 'No generator plugin found, please specify a generator plugin using the plugin option'); | ||
throw BadOptionError.fromOption('plugin', 'No generator plugin found, please specify a generator plugin using the plugin option'); | ||
} | ||
@@ -72,11 +75,24 @@ const directoryPath = path.resolve(process.cwd(), directory); | ||
await reporter.onNewMigration?.(migration, content); | ||
let saveError; | ||
const finishedMigrations = []; | ||
try { | ||
await createDirectory(directoryPath); | ||
await saveFile(filePath, content); | ||
const duration = getDuration(start); | ||
finishedMigrations.push({ ...migration, status: 'done', duration }); | ||
} | ||
catch (error) { | ||
saveError = error instanceof Error ? error : new Error(String(error)); | ||
const duration = getDuration(start); | ||
const errorInstance = toError(error); | ||
finishedMigrations.push({ ...migration, status: 'failed', duration, error: errorInstance }); | ||
} | ||
await reporter.onFinished?.([{ ...migration, status: saveError ? 'failed' : 'done', error: saveError, duration: 0 }], saveError); | ||
// eslint-disable-next-line unicorn/no-array-callback-reference | ||
const firstFailed = finishedMigrations.find(isFailedMigration); | ||
const firstError = firstFailed?.error instanceof EmigrateError | ||
? firstFailed.error | ||
: firstFailed | ||
? new UnexpectedError(`Failed to create migration file: ${firstFailed.relativeFilePath}`, { | ||
cause: firstFailed?.error, | ||
}) | ||
: undefined; | ||
await reporter.onFinished?.(finishedMigrations, firstError); | ||
} | ||
@@ -83,0 +99,0 @@ async function createDirectory(directoryPath) { |
@@ -11,6 +11,6 @@ import process from 'node:process'; | ||
if (!directory) { | ||
throw new MissingOptionError('directory'); | ||
throw MissingOptionError.fromOption('directory'); | ||
} | ||
if (!name) { | ||
throw new MissingArgumentsError('name'); | ||
throw MissingArgumentsError.fromArgument('name'); | ||
} | ||
@@ -20,15 +20,20 @@ const cwd = process.cwd(); | ||
if (!storagePlugin) { | ||
throw new BadOptionError('storage', 'No storage found, please specify a storage using the storage option'); | ||
throw BadOptionError.fromOption('storage', 'No storage found, please specify a storage using the storage option'); | ||
} | ||
const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); | ||
if (!reporter) { | ||
throw new BadOptionError('reporter', 'No reporter found, please specify an existing reporter using the reporter option'); | ||
throw BadOptionError.fromOption('reporter', 'No reporter found, please specify an existing reporter using the reporter option'); | ||
} | ||
const [storage, storageError] = await exec(async () => storagePlugin.initializeStorage()); | ||
if (storageError) { | ||
await reporter.onFinished?.([], new StorageInitError('Could not initialize storage', { cause: storageError })); | ||
await reporter.onFinished?.([], StorageInitError.fromError(storageError)); | ||
return 1; | ||
} | ||
await reporter.onInit?.({ command: 'remove', version, cwd, dry: false, directory }); | ||
const migrationFile = await getMigration(cwd, directory, name, !force); | ||
const [migrationFile, fileError] = await exec(async () => getMigration(cwd, directory, name, !force)); | ||
if (fileError) { | ||
await reporter.onFinished?.([], fileError); | ||
await storage.end(); | ||
return 1; | ||
} | ||
const finishedMigrations = []; | ||
@@ -42,3 +47,3 @@ let historyEntry; | ||
if (migrationHistoryEntry.status === 'done' && !force) { | ||
removalError = new OptionNeededError('force', `The migration "${migrationFile.name}" is not in a failed state. Use the "force" option to force its removal`); | ||
removalError = OptionNeededError.fromOption('force', `The migration "${migrationFile.name}" is not in a failed state. Use the "force" option to force its removal`); | ||
} | ||
@@ -64,3 +69,3 @@ else { | ||
else if (!removalError) { | ||
removalError = new MigrationNotRunError(`Migration "${migrationFile.name}" is not in the migration history`, migrationFile); | ||
removalError = MigrationNotRunError.fromMetadata(migrationFile); | ||
} | ||
@@ -67,0 +72,0 @@ if (removalError) { |
import process from 'node:process'; | ||
import { getOrLoadPlugins, getOrLoadReporter, getOrLoadStorage } from '@emigrate/plugin-tools'; | ||
import { isFinishedMigration } from '@emigrate/plugin-tools/types'; | ||
import { BadOptionError, MigrationLoadError, MissingOptionError, StorageInitError } from '../errors.js'; | ||
import { isFinishedMigration } from '@emigrate/types'; | ||
import { BadOptionError, MigrationLoadError, MissingOptionError, StorageInitError, toError } from '../errors.js'; | ||
import { withLeadingPeriod } from '../with-leading-period.js'; | ||
@@ -16,11 +16,11 @@ import { exec } from '../exec.js'; | ||
if (!directory) { | ||
throw new MissingOptionError('directory'); | ||
throw MissingOptionError.fromOption('directory'); | ||
} | ||
const storagePlugin = await getOrLoadStorage([storageConfig]); | ||
if (!storagePlugin) { | ||
throw new BadOptionError('storage', 'No storage found, please specify a storage using the storage option'); | ||
throw BadOptionError.fromOption('storage', 'No storage found, please specify a storage using the storage option'); | ||
} | ||
const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); | ||
if (!reporter) { | ||
throw new BadOptionError('reporter', 'No reporter found, please specify an existing reporter using the reporter option'); | ||
throw BadOptionError.fromOption('reporter', 'No reporter found, please specify an existing reporter using the reporter option'); | ||
} | ||
@@ -30,39 +30,46 @@ await reporter.onInit?.({ command: 'up', version, cwd, dry, directory }); | ||
if (storageError) { | ||
await reporter.onFinished?.([], new StorageInitError('Could not initialize storage', { cause: storageError })); | ||
await reporter.onFinished?.([], StorageInitError.fromError(storageError)); | ||
return 1; | ||
} | ||
const collectedMigrations = filterAsync(collectMigrations(cwd, directory, storage.getHistory(), getMigrations), (migration) => !isFinishedMigration(migration) || migration.status === 'failed'); | ||
const loaderPlugins = await getOrLoadPlugins('loader', [lazyPluginLoaderJs, ...plugins]); | ||
const loaderByExtension = new Map(); | ||
const getLoaderByExtension = (extension) => { | ||
if (!loaderByExtension.has(extension)) { | ||
const loader = loaderPlugins.find((plugin) => plugin.loadableExtensions.some((loadableExtension) => withLeadingPeriod(loadableExtension) === extension)); | ||
loaderByExtension.set(extension, loader); | ||
} | ||
return loaderByExtension.get(extension); | ||
}; | ||
const error = await migrationRunner({ | ||
dry, | ||
reporter, | ||
storage, | ||
migrations: await arrayFromAsync(collectedMigrations), | ||
async validate(migration) { | ||
const loader = getLoaderByExtension(migration.extension); | ||
if (!loader) { | ||
throw new BadOptionError('plugin', `No loader plugin found for file extension: ${migration.extension}`); | ||
try { | ||
const collectedMigrations = filterAsync(collectMigrations(cwd, directory, storage.getHistory(), getMigrations), (migration) => !isFinishedMigration(migration) || migration.status === 'failed'); | ||
const loaderPlugins = await getOrLoadPlugins('loader', [lazyPluginLoaderJs, ...plugins]); | ||
const loaderByExtension = new Map(); | ||
const getLoaderByExtension = (extension) => { | ||
if (!loaderByExtension.has(extension)) { | ||
const loader = loaderPlugins.find((plugin) => plugin.loadableExtensions.some((loadableExtension) => withLeadingPeriod(loadableExtension) === extension)); | ||
loaderByExtension.set(extension, loader); | ||
} | ||
}, | ||
async execute(migration) { | ||
const loader = getLoaderByExtension(migration.extension); | ||
const [migrationFunction, loadError] = await exec(async () => loader.loadMigration(migration)); | ||
if (loadError) { | ||
throw new MigrationLoadError(`Failed to load migration file: ${migration.relativeFilePath}`, migration, { | ||
cause: loadError, | ||
}); | ||
} | ||
await migrationFunction(); | ||
}, | ||
}); | ||
return error ? 1 : 0; | ||
return loaderByExtension.get(extension); | ||
}; | ||
const error = await migrationRunner({ | ||
dry, | ||
reporter, | ||
storage, | ||
migrations: await arrayFromAsync(collectedMigrations), | ||
async validate(migration) { | ||
const loader = getLoaderByExtension(migration.extension); | ||
if (!loader) { | ||
throw BadOptionError.fromOption('plugin', `No loader plugin found for file extension: ${migration.extension}`); | ||
} | ||
}, | ||
async execute(migration) { | ||
const loader = getLoaderByExtension(migration.extension); | ||
const [migrationFunction, loadError] = await exec(async () => loader.loadMigration(migration)); | ||
if (loadError) { | ||
throw MigrationLoadError.fromMetadata(migration, loadError); | ||
} | ||
await migrationFunction(); | ||
}, | ||
}); | ||
return error ? 1 : 0; | ||
} | ||
catch (error) { | ||
await reporter.onFinished?.([], toError(error)); | ||
return 1; | ||
} | ||
finally { | ||
await storage.end(); | ||
} | ||
} | ||
//# sourceMappingURL=up.js.map |
import { describe, it, mock } from 'node:test'; | ||
import assert from 'node:assert'; | ||
import path from 'node:path'; | ||
import { serializeError } from '@emigrate/plugin-tools'; | ||
import { deserializeError } from 'serialize-error'; | ||
import { version } from '../get-package-info.js'; | ||
@@ -89,3 +89,3 @@ import upCommand from './up.js'; | ||
assert.strictEqual(reporter.onMigrationError.mock.calls.length, 1); | ||
assert.strictEqual(getErrorCause(reporter.onMigrationError.mock.calls[0]?.arguments[1]), failedEntry.error); | ||
assert.deepStrictEqual(getErrorCause(reporter.onMigrationError.mock.calls[0]?.arguments[1]), deserializeError(failedEntry.error)); | ||
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 1); | ||
@@ -95,3 +95,3 @@ assert.strictEqual(reporter.onFinished.mock.calls.length, 1); | ||
assert.strictEqual(error?.message, `Migration ${failedEntry.name} is in a failed state, it should be fixed and removed`); | ||
assert.strictEqual(getErrorCause(error), failedEntry.error); | ||
assert.deepStrictEqual(getErrorCause(error), deserializeError(failedEntry.error)); | ||
assert.strictEqual(entries?.length, 2); | ||
@@ -120,3 +120,3 @@ assert.deepStrictEqual(entries.map((entry) => `${entry.name} (${entry.status})`), ['some_failed_migration.js (failed)', 'some_file.js (skipped)']); | ||
assert.strictEqual(reporter.onMigrationError.mock.calls.length, 1); | ||
assert.strictEqual(getErrorCause(reporter.onMigrationError.mock.calls[0]?.arguments[1]), failedEntry.error); | ||
assert.deepStrictEqual(getErrorCause(reporter.onMigrationError.mock.calls[0]?.arguments[1]), deserializeError(failedEntry.error)); | ||
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 1); | ||
@@ -126,3 +126,3 @@ assert.strictEqual(reporter.onFinished.mock.calls.length, 1); | ||
assert.strictEqual(error?.message, `Migration ${failedEntry.name} is in a failed state, it should be fixed and removed`); | ||
assert.strictEqual(getErrorCause(error), failedEntry.error); | ||
assert.deepStrictEqual(getErrorCause(error), deserializeError(failedEntry.error)); | ||
assert.strictEqual(entries?.length, 2); | ||
@@ -283,4 +283,7 @@ assert.deepStrictEqual(entries.map((entry) => `${entry.name} (${entry.status})`), ['some_failed_migration.js (failed)', 'some_file.js (pending)']); | ||
} | ||
function toEntry(name, status = 'done') { | ||
if (typeof name === 'string') { | ||
function toEntry(name, status) { | ||
if (typeof name !== 'string') { | ||
return name.status === 'failed' ? name : name; | ||
} | ||
if (status === 'failed') { | ||
return { | ||
@@ -290,9 +293,13 @@ name, | ||
date: new Date(), | ||
error: status === 'failed' ? serializeError(new Error('Failed')) : undefined, | ||
error: { name: 'Error', message: 'Failed' }, | ||
}; | ||
} | ||
return name; | ||
return { | ||
name, | ||
status: status ?? 'done', | ||
date: new Date(), | ||
}; | ||
} | ||
function toEntries(names, status = 'done') { | ||
return names.map((name) => toEntry(name, status)); | ||
function toEntries(names, status) { | ||
return names.map((name) => (typeof name === 'string' ? toEntry(name, status) : name)); | ||
} | ||
@@ -299,0 +306,0 @@ async function noop() { |
@@ -1,6 +0,7 @@ | ||
import { type MigrationHistoryEntry, type MigrationMetadata } from '@emigrate/plugin-tools/types'; | ||
import { type SerializedError, type MigrationMetadata, type FailedMigrationMetadata, type FailedMigrationHistoryEntry } from '@emigrate/types'; | ||
export declare const toError: (error: unknown) => Error; | ||
export declare const toSerializedError: (error: unknown) => SerializedError; | ||
export declare class EmigrateError extends Error { | ||
code: string; | ||
constructor(code: string, message: string, options?: ErrorOptions); | ||
code?: string | undefined; | ||
constructor(message: string | undefined, options?: ErrorOptions, code?: string | undefined); | ||
} | ||
@@ -11,38 +12,43 @@ export declare class ShowUsageError extends EmigrateError { | ||
option: string | string[]; | ||
constructor(option: string | string[]); | ||
static fromOption(option: string | string[]): MissingOptionError; | ||
constructor(message: string | undefined, options?: ErrorOptions, option?: string | string[]); | ||
} | ||
export declare class MissingArgumentsError extends ShowUsageError { | ||
argument: string; | ||
constructor(argument: string); | ||
static fromArgument(argument: string): MissingArgumentsError; | ||
constructor(message: string | undefined, options?: ErrorOptions, argument?: string); | ||
} | ||
export declare class OptionNeededError extends ShowUsageError { | ||
option: string; | ||
constructor(option: string, message: string); | ||
static fromOption(option: string, message: string): OptionNeededError; | ||
constructor(message: string | undefined, options?: ErrorOptions, option?: string); | ||
} | ||
export declare class BadOptionError extends ShowUsageError { | ||
option: string; | ||
constructor(option: string, message: string); | ||
static fromOption(option: string, message: string): BadOptionError; | ||
constructor(message: string | undefined, options?: ErrorOptions, option?: string); | ||
} | ||
export declare class UnexpectedError extends EmigrateError { | ||
constructor(message: string, options?: ErrorOptions); | ||
constructor(message: string | undefined, options?: ErrorOptions); | ||
} | ||
export declare class MigrationHistoryError extends EmigrateError { | ||
entry: MigrationHistoryEntry; | ||
constructor(message: string, entry: MigrationHistoryEntry); | ||
static fromHistoryEntry(entry: FailedMigrationHistoryEntry): MigrationHistoryError; | ||
constructor(message: string | undefined, options?: ErrorOptions); | ||
} | ||
export declare class MigrationLoadError extends EmigrateError { | ||
metadata: MigrationMetadata; | ||
constructor(message: string, metadata: MigrationMetadata, options?: ErrorOptions); | ||
static fromMetadata(metadata: MigrationMetadata, cause?: Error): MigrationLoadError; | ||
constructor(message: string | undefined, options?: ErrorOptions); | ||
} | ||
export declare class MigrationRunError extends EmigrateError { | ||
metadata: MigrationMetadata; | ||
constructor(message: string, metadata: MigrationMetadata, options?: ErrorOptions); | ||
static fromMetadata(metadata: FailedMigrationMetadata): MigrationRunError; | ||
constructor(message: string | undefined, options?: ErrorOptions); | ||
} | ||
export declare class MigrationNotRunError extends EmigrateError { | ||
metadata: MigrationMetadata; | ||
constructor(message: string, metadata: MigrationMetadata, options?: ErrorOptions); | ||
static fromMetadata(metadata: MigrationMetadata, cause?: Error): MigrationNotRunError; | ||
constructor(message: string | undefined, options?: ErrorOptions); | ||
} | ||
export declare class StorageInitError extends EmigrateError { | ||
constructor(message: string, options?: ErrorOptions); | ||
static fromError(error: Error): StorageInitError; | ||
constructor(message: string | undefined, options?: ErrorOptions); | ||
} | ||
//# sourceMappingURL=errors.d.ts.map |
@@ -0,6 +1,11 @@ | ||
import { serializeError, errorConstructors, deserializeError } from 'serialize-error'; | ||
const formatter = new Intl.ListFormat('en', { style: 'long', type: 'disjunction' }); | ||
export const toError = (error) => (error instanceof Error ? error : new Error(String(error))); | ||
export const toSerializedError = (error) => { | ||
const errorInstance = toError(error); | ||
return serializeError(errorInstance); | ||
}; | ||
export class EmigrateError extends Error { | ||
code; | ||
constructor(code, message, options) { | ||
constructor(message, options, code) { | ||
super(message, options); | ||
@@ -14,4 +19,7 @@ this.code = code; | ||
option; | ||
constructor(option) { | ||
super('ERR_MISSING_OPT', `Missing required option: ${Array.isArray(option) ? formatter.format(option) : option}`); | ||
static fromOption(option) { | ||
return new MissingOptionError(`Missing required option: ${Array.isArray(option) ? formatter.format(option) : option}`, undefined, option); | ||
} | ||
constructor(message, options, option = '') { | ||
super(message, options, 'ERR_MISSING_OPT'); | ||
this.option = option; | ||
@@ -22,4 +30,7 @@ } | ||
argument; | ||
constructor(argument) { | ||
super('ERR_MISSING_ARGS', `Missing required argument: ${argument}`); | ||
static fromArgument(argument) { | ||
return new MissingArgumentsError(`Missing required argument: ${argument}`, undefined, argument); | ||
} | ||
constructor(message, options, argument = '') { | ||
super(message, options, 'ERR_MISSING_ARGS'); | ||
this.argument = argument; | ||
@@ -30,4 +41,7 @@ } | ||
option; | ||
constructor(option, message) { | ||
super('ERR_OPT_NEEDED', message); | ||
static fromOption(option, message) { | ||
return new OptionNeededError(message, undefined, option); | ||
} | ||
constructor(message, options, option = '') { | ||
super(message, options, 'ERR_OPT_NEEDED'); | ||
this.option = option; | ||
@@ -38,4 +52,7 @@ } | ||
option; | ||
constructor(option, message) { | ||
super('ERR_BAD_OPT', message); | ||
static fromOption(option, message) { | ||
return new BadOptionError(message, undefined, option); | ||
} | ||
constructor(message, options, option = '') { | ||
super(message, options, 'ERR_BAD_OPT'); | ||
this.option = option; | ||
@@ -46,38 +63,59 @@ } | ||
constructor(message, options) { | ||
super('ERR_UNEXPECTED', message, options); | ||
super(message, options, 'ERR_UNEXPECTED'); | ||
} | ||
} | ||
export class MigrationHistoryError extends EmigrateError { | ||
entry; | ||
constructor(message, entry) { | ||
super('ERR_MIGRATION_HISTORY', message, { cause: entry.error }); | ||
this.entry = entry; | ||
static fromHistoryEntry(entry) { | ||
return new MigrationHistoryError(`Migration ${entry.name} is in a failed state, it should be fixed and removed`, { | ||
cause: deserializeError(entry.error), | ||
}); | ||
} | ||
constructor(message, options) { | ||
super(message, options, 'ERR_MIGRATION_HISTORY'); | ||
} | ||
} | ||
export class MigrationLoadError extends EmigrateError { | ||
metadata; | ||
constructor(message, metadata, options) { | ||
super('ERR_MIGRATION_LOAD', message, options); | ||
this.metadata = metadata; | ||
static fromMetadata(metadata, cause) { | ||
return new MigrationLoadError(`Failed to load migration file: ${metadata.relativeFilePath}`, { cause }); | ||
} | ||
constructor(message, options) { | ||
super(message, options, 'ERR_MIGRATION_LOAD'); | ||
} | ||
} | ||
export class MigrationRunError extends EmigrateError { | ||
metadata; | ||
constructor(message, metadata, options) { | ||
super('ERR_MIGRATION_RUN', message, options); | ||
this.metadata = metadata; | ||
static fromMetadata(metadata) { | ||
return new MigrationRunError(`Failed to run migration: ${metadata.relativeFilePath}`, { cause: metadata.error }); | ||
} | ||
constructor(message, options) { | ||
super(message, options, 'ERR_MIGRATION_RUN'); | ||
} | ||
} | ||
export class MigrationNotRunError extends EmigrateError { | ||
metadata; | ||
constructor(message, metadata, options) { | ||
super('ERR_MIGRATION_NOT_RUN', message, options); | ||
this.metadata = metadata; | ||
static fromMetadata(metadata, cause) { | ||
return new MigrationNotRunError(`Migration "${metadata.name}" is not in the migration history`, { cause }); | ||
} | ||
constructor(message, options) { | ||
super(message, options, 'ERR_MIGRATION_NOT_RUN'); | ||
} | ||
} | ||
export class StorageInitError extends EmigrateError { | ||
static fromError(error) { | ||
return new StorageInitError('Could not initialize storage', { cause: error }); | ||
} | ||
constructor(message, options) { | ||
super('ERR_STORAGE_INIT', message, options); | ||
super(message, options, 'ERR_STORAGE_INIT'); | ||
} | ||
} | ||
errorConstructors.set('EmigrateError', EmigrateError); | ||
errorConstructors.set('ShowUsageError', ShowUsageError); | ||
errorConstructors.set('MissingOptionError', MissingOptionError); | ||
errorConstructors.set('MissingArgumentsError', MissingArgumentsError); | ||
errorConstructors.set('OptionNeededError', OptionNeededError); | ||
errorConstructors.set('BadOptionError', BadOptionError); | ||
errorConstructors.set('UnexpectedError', UnexpectedError); | ||
errorConstructors.set('MigrationHistoryError', MigrationHistoryError); | ||
errorConstructors.set('MigrationLoadError', MigrationLoadError); | ||
errorConstructors.set('MigrationRunError', MigrationRunError); | ||
errorConstructors.set('MigrationNotRunError', MigrationNotRunError); | ||
errorConstructors.set('StorageInitError', StorageInitError); | ||
//# sourceMappingURL=errors.js.map |
@@ -1,3 +0,3 @@ | ||
import { type MigrationMetadata } from '@emigrate/plugin-tools/types'; | ||
import { type MigrationMetadata } from '@emigrate/types'; | ||
export declare const getMigration: (cwd: string, directory: string, name: string, requireExists?: boolean) => Promise<MigrationMetadata>; | ||
//# sourceMappingURL=get-migration.d.ts.map |
@@ -13,3 +13,3 @@ import path from 'node:path'; | ||
catch { | ||
throw new OptionNeededError('force', `The given migration name "${name}" does not exist or is not a file. Use the "force" option to ignore this error`); | ||
throw OptionNeededError.fromOption('force', `The given migration name "${name}" does not exist or is not a file. Use the "force" option to ignore this error`); | ||
} | ||
@@ -16,0 +16,0 @@ }; |
@@ -1,4 +0,4 @@ | ||
import { type MigrationMetadata } from '@emigrate/plugin-tools/types'; | ||
import { type MigrationMetadata } from '@emigrate/types'; | ||
export type GetMigrationsFunction = typeof getMigrations; | ||
export declare const getMigrations: (cwd: string, directory: string) => Promise<MigrationMetadata[]>; | ||
//# sourceMappingURL=get-migrations.d.ts.map |
import path from 'node:path'; | ||
import fs from 'node:fs/promises'; | ||
import { withLeadingPeriod } from './with-leading-period.js'; | ||
import { BadOptionError } from './errors.js'; | ||
const tryReadDirectory = async (directoryPath) => { | ||
try { | ||
return await fs.readdir(directoryPath, { | ||
withFileTypes: true, | ||
}); | ||
} | ||
catch { | ||
throw BadOptionError.fromOption('directory', `Couldn't read directory: ${directoryPath}`); | ||
} | ||
}; | ||
export const getMigrations = async (cwd, directory) => { | ||
const allFilesInMigrationDirectory = await fs.readdir(path.resolve(cwd, directory), { | ||
withFileTypes: true, | ||
}); | ||
const directoryPath = path.resolve(cwd, directory); | ||
const allFilesInMigrationDirectory = await tryReadDirectory(directoryPath); | ||
const migrationFiles = allFilesInMigrationDirectory | ||
@@ -12,3 +22,3 @@ .filter((file) => file.isFile() && !file.name.startsWith('.') && !file.name.startsWith('_')) | ||
.map(({ name }) => { | ||
const filePath = path.resolve(cwd, directory, name); | ||
const filePath = path.join(directoryPath, name); | ||
return { | ||
@@ -15,0 +25,0 @@ name, |
import fs from 'node:fs/promises'; | ||
import { fileURLToPath } from 'node:url'; | ||
import { UnexpectedError } from './errors.js'; | ||
const getPackageInfo = async () => { | ||
@@ -18,5 +19,5 @@ const packageInfoPath = fileURLToPath(new URL('../package.json', import.meta.url)); | ||
} | ||
throw new Error(`Could not read package info from: ${packageInfoPath}`); | ||
throw new UnexpectedError(`Could not read package info from: ${packageInfoPath}`); | ||
}; | ||
export const { version } = await getPackageInfo(); | ||
//# sourceMappingURL=get-package-info.js.map |
@@ -1,2 +0,2 @@ | ||
import { type EmigrateReporter, type MigrationMetadata, type MigrationMetadataFinished, type Storage } from '@emigrate/plugin-tools/types'; | ||
import { type EmigrateReporter, type MigrationMetadata, type MigrationMetadataFinished, type Storage } from '@emigrate/types'; | ||
type MigrationRunnerParameters = { | ||
@@ -3,0 +3,0 @@ dry: boolean; |
import process from 'node:process'; | ||
import { isFinishedMigration, } from '@emigrate/plugin-tools/types'; | ||
import { toError, EmigrateError, MigrationRunError } from './errors.js'; | ||
import { isFinishedMigration, isFailedMigration, } from '@emigrate/types'; | ||
import { toError, EmigrateError, MigrationRunError, toSerializedError } from './errors.js'; | ||
import { exec } from './exec.js'; | ||
@@ -20,3 +20,2 @@ import { getDuration } from './get-duration.js'; | ||
status: dry ? 'pending' : 'skipped', | ||
duration: 0, | ||
}); | ||
@@ -31,3 +30,3 @@ } | ||
for await (const migration of migrationsToRun) { | ||
finishedMigrations.push({ ...migration, status: 'skipped', duration: 0 }); | ||
finishedMigrations.push({ ...migration, status: 'skipped' }); | ||
} | ||
@@ -48,3 +47,3 @@ migrationsToRun.length = 0; | ||
for await (const migration of migrationsToRun) { | ||
finishedMigrations.push({ ...migration, duration: 0, status: 'skipped' }); | ||
finishedMigrations.push({ ...migration, status: 'skipped' }); | ||
} | ||
@@ -82,3 +81,2 @@ migrationsToRun.length = 0; | ||
status: dry ? 'pending' : 'skipped', | ||
duration: 0, | ||
}; | ||
@@ -93,33 +91,37 @@ await reporter.onMigrationSkip?.(finishedMigration); | ||
const duration = getDuration(start); | ||
const finishedMigration = { | ||
...migration, | ||
status: migrationError ? 'failed' : 'done', | ||
duration, | ||
error: migrationError, | ||
}; | ||
finishedMigrations.push(finishedMigration); | ||
if (migrationError) { | ||
await storage.onError(finishedMigration, migrationError); | ||
const finishedMigration = { | ||
...migration, | ||
status: 'failed', | ||
duration, | ||
error: migrationError, | ||
}; | ||
await storage.onError(finishedMigration, toSerializedError(migrationError)); | ||
await reporter.onMigrationError?.(finishedMigration, migrationError); | ||
finishedMigrations.push(finishedMigration); | ||
skip = true; | ||
} | ||
else { | ||
const finishedMigration = { | ||
...migration, | ||
status: 'done', | ||
duration, | ||
}; | ||
await storage.onSuccess(finishedMigration); | ||
await reporter.onMigrationSuccess?.(finishedMigration); | ||
finishedMigrations.push(finishedMigration); | ||
} | ||
} | ||
const [, unlockError] = dry ? [] : await exec(async () => storage.unlock(lockedMigrations ?? [])); | ||
const firstFailed = finishedMigrations.find((migration) => migration.status === 'failed'); | ||
// eslint-disable-next-line unicorn/no-array-callback-reference | ||
const firstFailed = finishedMigrations.find(isFailedMigration); | ||
const firstError = firstFailed?.error instanceof EmigrateError | ||
? firstFailed.error | ||
: firstFailed | ||
? new MigrationRunError(`Failed to run migration: ${firstFailed.relativeFilePath}`, firstFailed, { | ||
cause: firstFailed?.error, | ||
}) | ||
? MigrationRunError.fromMetadata(firstFailed) | ||
: undefined; | ||
const error = unlockError ?? firstError ?? lockError; | ||
await reporter.onFinished?.(finishedMigrations, error); | ||
await storage.end(); | ||
return error; | ||
}; | ||
//# sourceMappingURL=migration-runner.js.map |
@@ -1,4 +0,4 @@ | ||
import { type LoaderPlugin } from '@emigrate/plugin-tools/types'; | ||
import { type LoaderPlugin } from '@emigrate/types'; | ||
declare const loaderJs: LoaderPlugin; | ||
export default loaderJs; | ||
//# sourceMappingURL=plugin-loader-js.d.ts.map |
@@ -1,2 +0,2 @@ | ||
import { type MigrationMetadata, type MigrationMetadataFinished, type EmigrateReporter, type ReporterInitParameters, type Awaitable } from '@emigrate/plugin-tools/types'; | ||
import { type MigrationMetadata, type MigrationMetadataFinished, type EmigrateReporter, type ReporterInitParameters, type Awaitable } from '@emigrate/types'; | ||
declare class DefaultFancyReporter implements Required<EmigrateReporter> { | ||
@@ -3,0 +3,0 @@ #private; |
@@ -7,3 +7,2 @@ import { black, blueBright, bold, cyan, dim, faint, gray, green, red, redBright, yellow } from 'ansis'; | ||
import prettyMs from 'pretty-ms'; | ||
import { EmigrateError } from '../errors.js'; | ||
const interactive = isInteractive(); | ||
@@ -97,9 +96,7 @@ const spinner = interactive ? elegantSpinner() : () => figures.pointerSmall; | ||
} | ||
const codeString = typeof others['code'] === 'string' ? others['code'] : undefined; | ||
const codeString = typeof others['code'] === 'string' || typeof others['code'] === 'number' ? others['code'] : undefined; | ||
const code = codeString ? ` [${codeString}]` : ''; | ||
const errorTitle = error.name | ||
? `${error.name}${codeString && !error.name.includes(codeString) ? code : ''}: ${error.message}` | ||
: error.message; | ||
const errorTitle = error.name ? `${error.name}${code}: ${error.message}` : error.message; | ||
const parts = [`${indent}${bold.red(errorTitle)}`, ...stack.map((line) => `${indent} ${dim(line.trim())}`)]; | ||
if (properties.length > 0 && !(error instanceof EmigrateError)) { | ||
if (properties.length > 0) { | ||
parts.push(`${indent} ${JSON.stringify(others, undefined, 2).split('\n').join(`\n${indent} `)}`); | ||
@@ -106,0 +103,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { type MigrationHistoryEntry, type MigrationMetadataFinished } from '@emigrate/plugin-tools/types'; | ||
import { type MigrationHistoryEntry, type MigrationMetadataFinished } from '@emigrate/types'; | ||
export declare const toMigrationMetadata: (entry: MigrationHistoryEntry, { cwd, directory }: { | ||
@@ -3,0 +3,0 @@ cwd: string; |
@@ -6,3 +6,16 @@ import path from 'node:path'; | ||
const filePath = path.resolve(cwd, directory, entry.name); | ||
const finishedMigration = { | ||
if (entry.status === 'failed') { | ||
return { | ||
name: entry.name, | ||
status: entry.status, | ||
filePath, | ||
relativeFilePath: path.relative(cwd, filePath), | ||
extension: withLeadingPeriod(path.extname(entry.name)), | ||
directory, | ||
cwd, | ||
duration: 0, | ||
error: MigrationHistoryError.fromHistoryEntry(entry), | ||
}; | ||
} | ||
return { | ||
name: entry.name, | ||
@@ -17,7 +30,3 @@ status: entry.status, | ||
}; | ||
if (entry.status === 'failed') { | ||
finishedMigration.error = new MigrationHistoryError(`Migration ${entry.name} is in a failed state, it should be fixed and removed`, entry); | ||
} | ||
return finishedMigration; | ||
}; | ||
//# sourceMappingURL=to-migration-metadata.js.map |
@@ -1,2 +0,2 @@ | ||
import { type EmigrateStorage, type Awaitable, type Plugin, type EmigrateReporter } from '@emigrate/plugin-tools/types'; | ||
import { type EmigrateStorage, type Awaitable, type Plugin, type EmigrateReporter } from '@emigrate/types'; | ||
export type EmigratePlugin = Plugin; | ||
@@ -3,0 +3,0 @@ type StringOrModule<T> = string | T | (() => Awaitable<T>) | (() => Awaitable<{ |
{ | ||
"name": "@emigrate/cli", | ||
"version": "0.9.0", | ||
"version": "0.10.0", | ||
"publishConfig": { | ||
@@ -46,3 +46,5 @@ "access": "public" | ||
"pretty-ms": "8.0.0", | ||
"@emigrate/plugin-tools": "0.7.0" | ||
"serialize-error": "11.0.3", | ||
"@emigrate/plugin-tools": "0.8.0", | ||
"@emigrate/types": "0.8.0" | ||
}, | ||
@@ -49,0 +51,0 @@ "volta": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
199746
5.16%1979
5.72%10
25%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
Updated