@emigrate/cli
Advanced tools
Comparing version 0.5.1 to 0.6.0
@@ -211,3 +211,3 @@ #!/usr/bin/env node --enable-source-maps | ||
const { default: listCommand } = await import('./commands/list.js'); | ||
await listCommand({ directory, storage, reporter }); | ||
process.exitCode = await listCommand({ directory, storage, reporter }); | ||
} | ||
@@ -283,3 +283,3 @@ catch (error) { | ||
const { default: removeCommand } = await import('./commands/remove.js'); | ||
await removeCommand({ directory, storage, reporter, force }, positionals[0] ?? ''); | ||
process.exitCode = await removeCommand({ directory, storage, reporter, force }, positionals[0] ?? ''); | ||
} | ||
@@ -286,0 +286,0 @@ catch (error) { |
import { type Config } from '../types.js'; | ||
export default function listCommand({ directory, reporter: reporterConfig, storage: storageConfig }: Config): Promise<void>; | ||
export default function listCommand({ directory, reporter: reporterConfig, storage: storageConfig }: Config): Promise<1 | 0>; | ||
//# sourceMappingURL=list.d.ts.map |
import process from 'node:process'; | ||
import path from 'node:path'; | ||
import { getOrLoadReporter, getOrLoadStorage } from '@emigrate/plugin-tools'; | ||
import { BadOptionError, MigrationHistoryError, MissingOptionError } from '../errors.js'; | ||
import { BadOptionError, MigrationHistoryError, MissingOptionError, StorageInitError } from '../errors.js'; | ||
import { withLeadingPeriod } from '../with-leading-period.js'; | ||
import { getMigrations } from '../get-migrations.js'; | ||
import { exec } from '../exec.js'; | ||
const lazyDefaultReporter = async () => import('../reporters/default.js'); | ||
@@ -17,3 +18,2 @@ export default async function listCommand({ directory, reporter: reporterConfig, storage: storageConfig }) { | ||
} | ||
const storage = await storagePlugin.initializeStorage(); | ||
const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); | ||
@@ -24,2 +24,7 @@ if (!reporter) { | ||
await reporter.onInit?.({ command: 'list', cwd, dry: false, directory }); | ||
const [storage, storageError] = await exec(async () => storagePlugin.initializeStorage()); | ||
if (storageError) { | ||
await reporter.onFinished?.([], new StorageInitError('Could not initialize storage', { cause: storageError })); | ||
return 1; | ||
} | ||
const migrationFiles = await getMigrations(cwd, directory); | ||
@@ -61,3 +66,5 @@ let migrationHistoryError; | ||
await reporter.onFinished?.(finishedMigrations, migrationHistoryError); | ||
await storage.end(); | ||
return migrationHistoryError ? 1 : 0; | ||
} | ||
//# sourceMappingURL=list.js.map |
@@ -5,4 +5,4 @@ import { type Config } from '../types.js'; | ||
}; | ||
export default function removeCommand({ directory, reporter: reporterConfig, storage: storageConfig, force }: Config & ExtraFlags, name: string): Promise<void>; | ||
export default function removeCommand({ directory, reporter: reporterConfig, storage: storageConfig, force }: Config & ExtraFlags, name: string): Promise<1 | 0>; | ||
export {}; | ||
//# sourceMappingURL=remove.d.ts.map |
import process from 'node:process'; | ||
import { getOrLoadReporter, getOrLoadStorage } from '@emigrate/plugin-tools'; | ||
import { BadOptionError, MigrationNotRunError, MissingArgumentsError, MissingOptionError, OptionNeededError, } from '../errors.js'; | ||
import { BadOptionError, MigrationNotRunError, MissingArgumentsError, MissingOptionError, OptionNeededError, StorageInitError, } from '../errors.js'; | ||
import { getMigration } from '../get-migration.js'; | ||
import { getDuration } from '../get-duration.js'; | ||
import { exec } from '../exec.js'; | ||
const lazyDefaultReporter = async () => import('../reporters/default.js'); | ||
@@ -19,3 +20,2 @@ export default async function removeCommand({ directory, reporter: reporterConfig, storage: storageConfig, force }, name) { | ||
} | ||
const storage = await storagePlugin.initializeStorage(); | ||
const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); | ||
@@ -25,2 +25,8 @@ if (!reporter) { | ||
} | ||
const [storage, storageError] = await exec(async () => storagePlugin.initializeStorage()); | ||
if (storageError) { | ||
await reporter.onFinished?.([], new StorageInitError('Could not initialize storage', { cause: storageError })); | ||
return 1; | ||
} | ||
await reporter.onInit?.({ command: 'remove', cwd, dry: false, directory }); | ||
const migrationFile = await getMigration(cwd, directory, name, !force); | ||
@@ -35,7 +41,8 @@ const finishedMigrations = []; | ||
if (migrationHistoryEntry.status === 'done' && !force) { | ||
throw new OptionNeededError('force', `The migration "${migrationFile.name}" is not in a failed state. Use the "force" option to force its removal`); | ||
removalError = new OptionNeededError('force', `The migration "${migrationFile.name}" is not in a failed state. Use the "force" option to force its removal`); | ||
} | ||
historyEntry = migrationHistoryEntry; | ||
else { | ||
historyEntry = migrationHistoryEntry; | ||
} | ||
} | ||
await reporter.onInit?.({ command: 'remove', cwd, dry: false, directory }); | ||
await reporter.onMigrationRemoveStart?.(migrationFile); | ||
@@ -70,3 +77,5 @@ const start = process.hrtime(); | ||
await reporter.onFinished?.(finishedMigrations, removalError); | ||
await storage.end(); | ||
return removalError ? 1 : 0; | ||
} | ||
//# sourceMappingURL=remove.js.map |
import path from 'node:path'; | ||
import process from 'node:process'; | ||
import { getOrLoadPlugins, getOrLoadReporter, getOrLoadStorage } from '@emigrate/plugin-tools'; | ||
import { BadOptionError, EmigrateError, MigrationHistoryError, MigrationLoadError, MigrationRunError, MissingOptionError, } from '../errors.js'; | ||
import { getOrLoadPlugins, getOrLoadReporter, getOrLoadStorage, serializeError } from '@emigrate/plugin-tools'; | ||
import { BadOptionError, EmigrateError, MigrationHistoryError, MigrationLoadError, MigrationRunError, MissingOptionError, StorageInitError, toError, } from '../errors.js'; | ||
import { withLeadingPeriod } from '../with-leading-period.js'; | ||
import { getMigrations as getMigrationsOriginal } from '../get-migrations.js'; | ||
import { getDuration } from '../get-duration.js'; | ||
import { exec } from '../exec.js'; | ||
const lazyDefaultReporter = async () => import('../reporters/default.js'); | ||
@@ -18,3 +19,2 @@ const lazyPluginLoaderJs = async () => import('../plugin-loader-js.js'); | ||
} | ||
const storage = await storagePlugin.initializeStorage(); | ||
const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); | ||
@@ -24,2 +24,8 @@ if (!reporter) { | ||
} | ||
await reporter.onInit?.({ command: 'up', cwd, dry, directory }); | ||
const [storage, storageError] = await exec(async () => storagePlugin.initializeStorage()); | ||
if (storageError) { | ||
await reporter.onFinished?.([], new StorageInitError('Could not initialize storage', { cause: storageError })); | ||
return 1; | ||
} | ||
const migrationFiles = await getMigrations(cwd, directory); | ||
@@ -56,8 +62,26 @@ const failedEntries = []; | ||
])); | ||
for (const [extension, loader] of loaderByExtension) { | ||
for await (const [extension, loader] of loaderByExtension) { | ||
if (!loader) { | ||
throw new BadOptionError('plugin', `No loader plugin found for file extension: ${extension}`); | ||
const finishedMigrations = [...failedEntries]; | ||
for await (const failedEntry of failedEntries) { | ||
await reporter.onMigrationError?.(failedEntry, failedEntry.error); | ||
} | ||
for await (const migration of migrationFiles) { | ||
if (migration.extension === extension) { | ||
const error = new BadOptionError('plugin', `No loader plugin found for file extension: ${extension}`); | ||
const finishedMigration = { ...migration, duration: 0, status: 'failed', error }; | ||
await reporter.onMigrationError?.(finishedMigration, error); | ||
finishedMigrations.push(finishedMigration); | ||
} | ||
else { | ||
const finishedMigration = { ...migration, duration: 0, status: 'skipped' }; | ||
await reporter.onMigrationSkip?.(finishedMigration); | ||
finishedMigrations.push(finishedMigration); | ||
} | ||
} | ||
await reporter.onFinished?.(finishedMigrations, new BadOptionError('plugin', `No loader plugin found for file extension: ${extension}`)); | ||
await storage.end(); | ||
return 1; | ||
} | ||
} | ||
await reporter.onInit?.({ command: 'up', cwd, dry, directory }); | ||
await reporter.onCollectedMigrations?.([...failedEntries, ...migrationFiles]); | ||
@@ -79,2 +103,3 @@ if (migrationFiles.length === 0 || dry || failedEntries.length > 0) { | ||
await reporter.onFinished?.([...failedEntries, ...finishedMigrations], error); | ||
await storage.end(); | ||
return failedEntries.length > 0 ? 1 : 0; | ||
@@ -91,3 +116,4 @@ } | ||
} | ||
await reporter.onFinished?.([], error instanceof Error ? error : new Error(String(error))); | ||
await reporter.onFinished?.([], toError(error)); | ||
await storage.end(); | ||
return 1; | ||
@@ -106,3 +132,3 @@ } | ||
process.off('SIGTERM', cleanup); | ||
cleaningUp = storage.unlock(lockedMigrationFiles); | ||
cleaningUp = storage.unlock(lockedMigrationFiles).then(async () => storage.end()); | ||
return cleaningUp; | ||
@@ -143,3 +169,4 @@ }; | ||
catch (error) { | ||
const errorInstance = error instanceof Error ? error : new Error(String(error)); | ||
const errorInstance = toError(error); | ||
const serializedError = serializeError(errorInstance); | ||
const duration = getDuration(start); | ||
@@ -150,5 +177,5 @@ const finishedMigration = { | ||
duration, | ||
error: errorInstance, | ||
error: serializedError, | ||
}; | ||
await storage.onError(finishedMigration, errorInstance); | ||
await storage.onError(finishedMigration, serializedError); | ||
await reporter.onMigrationError?.(finishedMigration, errorInstance); | ||
@@ -155,0 +182,0 @@ finishedMigrations.push(finishedMigration); |
@@ -7,3 +7,3 @@ import { describe, it, mock } from 'node:test'; | ||
it('returns 0 and finishes without an error when there are no migrations to run', async () => { | ||
const { reporter, run } = getUpCommand([], []); | ||
const { reporter, run } = getUpCommand([], getStorage([])); | ||
const exitCode = await run(); | ||
@@ -29,11 +29,7 @@ assert.strictEqual(exitCode, 0); | ||
}); | ||
it('throws when there are migration file extensions without a corresponding loader plugin', async () => { | ||
const { reporter, run } = getUpCommand(['some_file.sql'], []); | ||
await assert.rejects(async () => { | ||
return run(); | ||
}, { | ||
name: 'Error [ERR_BAD_OPT]', | ||
message: 'No loader plugin found for file extension: .sql', | ||
}); | ||
assert.strictEqual(reporter.onInit.mock.calls.length, 0); | ||
it('returns 1 and finishes with an error when there are migration file extensions without a corresponding loader plugin', async () => { | ||
const { reporter, run } = getUpCommand(['some_other.js', 'some_file.sql'], getStorage([])); | ||
const exitCode = await run(); | ||
assert.strictEqual(exitCode, 1); | ||
assert.strictEqual(reporter.onInit.mock.calls.length, 1); | ||
assert.strictEqual(reporter.onCollectedMigrations.mock.calls.length, 0); | ||
@@ -43,15 +39,17 @@ assert.strictEqual(reporter.onLockedMigrations.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationSuccess.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationError.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onFinished.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationError.mock.calls.length, 1); | ||
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 1); | ||
const args = reporter.onFinished.mock.calls[0]?.arguments; | ||
assert.strictEqual(args?.length, 2); | ||
const entries = args[0]; | ||
const error = args[1]; | ||
assert.strictEqual(entries.length, 2); | ||
assert.deepStrictEqual(entries.map((entry) => `${entry.name} (${entry.status})`), ['some_other.js (skipped)', 'some_file.sql (failed)']); | ||
assert.strictEqual(error?.message, 'No loader plugin found for file extension: .sql'); | ||
}); | ||
it('throws when there are migration file extensions without a corresponding loader plugin in dry-run mode as well', async () => { | ||
const { reporter, run } = getUpCommand(['some_file.sql'], []); | ||
await assert.rejects(async () => { | ||
return run(true); | ||
}, { | ||
name: 'Error [ERR_BAD_OPT]', | ||
message: 'No loader plugin found for file extension: .sql', | ||
}); | ||
assert.strictEqual(reporter.onInit.mock.calls.length, 0); | ||
it('returns 1 and finishes with an error when there are migration file extensions without a corresponding loader plugin in dry-run mode as well', async () => { | ||
const { reporter, run } = getUpCommand(['some_other.js', 'some_file.sql'], getStorage([])); | ||
const exitCode = await run(); | ||
assert.strictEqual(exitCode, 1); | ||
assert.strictEqual(reporter.onInit.mock.calls.length, 1); | ||
assert.strictEqual(reporter.onCollectedMigrations.mock.calls.length, 0); | ||
@@ -61,9 +59,15 @@ assert.strictEqual(reporter.onLockedMigrations.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationSuccess.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationError.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onFinished.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationError.mock.calls.length, 1); | ||
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 1); | ||
const args = reporter.onFinished.mock.calls[0]?.arguments; | ||
assert.strictEqual(args?.length, 2); | ||
const entries = args[0]; | ||
const error = args[1]; | ||
assert.strictEqual(entries.length, 2); | ||
assert.deepStrictEqual(entries.map((entry) => `${entry.name} (${entry.status})`), ['some_other.js (skipped)', 'some_file.sql (failed)']); | ||
assert.strictEqual(error?.message, 'No loader plugin found for file extension: .sql'); | ||
}); | ||
it('returns 1 and finishes with an error when there are failed migrations in the history', async () => { | ||
const failedEntry = toEntry('some_failed_migration.js', 'failed'); | ||
const { reporter, run } = getUpCommand([failedEntry.name], [failedEntry]); | ||
const { reporter, run } = getUpCommand([failedEntry.name], getStorage([failedEntry])); | ||
const exitCode = await run(); | ||
@@ -97,3 +101,38 @@ assert.strictEqual(exitCode, 1); | ||
}); | ||
it("returns 1 and finishes with an error when the storage couldn't be initialized", async () => { | ||
const { reporter, run } = getUpCommand(['some_migration.js']); | ||
const exitCode = await run(); | ||
assert.strictEqual(exitCode, 1); | ||
assert.strictEqual(reporter.onInit.mock.calls.length, 1); | ||
assert.deepStrictEqual(reporter.onInit.mock.calls[0]?.arguments, [ | ||
{ | ||
command: 'up', | ||
cwd: '/emigrate', | ||
dry: false, | ||
directory: 'migrations', | ||
}, | ||
]); | ||
assert.strictEqual(reporter.onCollectedMigrations.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onLockedMigrations.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationStart.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationSuccess.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationError.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 0); | ||
assert.strictEqual(reporter.onFinished.mock.calls.length, 1); | ||
const args = reporter.onFinished.mock.calls[0]?.arguments; | ||
assert.strictEqual(args?.length, 2); | ||
const entries = args[0]; | ||
const error = args[1]; | ||
const cause = getErrorCause(error); | ||
assert.deepStrictEqual(entries, []); | ||
assert.strictEqual(error?.message, 'Could not initialize storage'); | ||
assert.strictEqual(cause?.message, 'No storage configured'); | ||
}); | ||
}); | ||
function getErrorCause(error) { | ||
if (error?.cause instanceof Error) { | ||
return error.cause; | ||
} | ||
return undefined; | ||
} | ||
function toMigration(cwd, directory, name) { | ||
@@ -129,3 +168,17 @@ return { | ||
} | ||
function getUpCommand(migrationFiles, historyEntries, plugins) { | ||
function getStorage(historyEntries) { | ||
const storage = { | ||
lock: mock.fn(), | ||
unlock: mock.fn(), | ||
getHistory: mock.fn(async function* () { | ||
yield* toEntries(historyEntries); | ||
}), | ||
remove: mock.fn(), | ||
onSuccess: mock.fn(), | ||
onError: mock.fn(), | ||
end: mock.fn(), | ||
}; | ||
return storage; | ||
} | ||
function getUpCommand(migrationFiles, storage, plugins) { | ||
const reporter = { | ||
@@ -145,12 +198,2 @@ onFinished: mock.fn(noop), | ||
}; | ||
const storage = { | ||
lock: mock.fn(), | ||
unlock: mock.fn(), | ||
getHistory: mock.fn(async function* () { | ||
yield* toEntries(historyEntries); | ||
}), | ||
remove: mock.fn(), | ||
onSuccess: mock.fn(), | ||
onError: mock.fn(), | ||
}; | ||
const run = async (dry = false) => { | ||
@@ -162,2 +205,5 @@ return upCommand({ | ||
async initializeStorage() { | ||
if (!storage) { | ||
throw new Error('No storage configured'); | ||
} | ||
return storage; | ||
@@ -164,0 +210,0 @@ }, |
import { type MigrationHistoryEntry, type MigrationMetadata } from '@emigrate/plugin-tools/types'; | ||
export declare const toError: (error: unknown) => Error; | ||
export declare class EmigrateError extends Error { | ||
@@ -43,2 +44,5 @@ code: string; | ||
} | ||
export declare class StorageInitError extends EmigrateError { | ||
constructor(message: string, options?: ErrorOptions); | ||
} | ||
//# sourceMappingURL=errors.d.ts.map |
const formatter = new Intl.ListFormat('en', { style: 'long', type: 'disjunction' }); | ||
export const toError = (error) => (error instanceof Error ? error : new Error(String(error))); | ||
export class EmigrateError extends Error { | ||
@@ -73,2 +74,7 @@ code; | ||
} | ||
export class StorageInitError extends EmigrateError { | ||
constructor(message, options) { | ||
super('ERR_STORAGE_INIT', message, options); | ||
} | ||
} | ||
//# sourceMappingURL=errors.js.map |
{ | ||
"name": "@emigrate/cli", | ||
"version": "0.5.1", | ||
"version": "0.6.0", | ||
"publishConfig": { | ||
@@ -46,3 +46,3 @@ "access": "public" | ||
"pretty-ms": "8.0.0", | ||
"@emigrate/plugin-tools": "0.4.1" | ||
"@emigrate/plugin-tools": "0.5.0" | ||
}, | ||
@@ -49,0 +49,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
165382
72
1628
+ Added@emigrate/plugin-tools@0.5.0(transitive)
- Removed@emigrate/plugin-tools@0.4.1(transitive)
Updated@emigrate/plugin-tools@0.5.0