@emigrate/plugin-tools
Advanced tools
Comparing version 0.3.0 to 0.4.0
@@ -1,94 +0,38 @@ | ||
import { type PluginFromType, type GeneratorPlugin, type StoragePlugin, type Plugin, type LoaderPlugin } from './types.js'; | ||
import { type PluginFromType, type GeneratorPlugin, type EmigrateReporter, type EmigrateStorage, type LoaderPlugin, type StringOrModule } from './types.js'; | ||
export declare const isGeneratorPlugin: (plugin: any) => plugin is GeneratorPlugin; | ||
export declare const isStoragePlugin: (plugin: any) => plugin is StoragePlugin; | ||
export declare const isEmigrateStorage: (plugin: any) => plugin is EmigrateStorage; | ||
export declare const isLoaderPlugin: (plugin: any) => plugin is LoaderPlugin; | ||
export declare const isReporterPlugin: (plugin: any) => plugin is Partial<{ | ||
onInit(parameters: { | ||
directory: string; | ||
cwd: string; | ||
dry: boolean; | ||
}): void | PromiseLike<void>; | ||
onCollectedMigrations(migrations: import("./types.js").MigrationMetadata[]): void | PromiseLike<void>; | ||
onLockedMigrations(migrations: import("./types.js").MigrationMetadata[]): void | PromiseLike<void>; | ||
onMigrationStart(migration: import("./types.js").MigrationMetadata): void | PromiseLike<void>; | ||
onMigrationSuccess(migration: import("./types.js").MigrationMetadataFinished): void | PromiseLike<void>; | ||
onMigrationError(migration: import("./types.js").MigrationMetadataFinished, error: Error): void | PromiseLike<void>; | ||
onMigrationSkip(migration: import("./types.js").MigrationMetadataFinished): void | PromiseLike<void>; | ||
onFinished(migrations: import("./types.js").MigrationMetadataFinished[], error?: Error | undefined): void | PromiseLike<void>; | ||
export declare const isEmigrateReporter: (plugin: any) => plugin is Partial<{ | ||
onInit(parameters: import("./types.js").ReporterInitParameters): import("./types.js").Awaitable<void>; | ||
onCollectedMigrations(migrations: import("./types.js").MigrationMetadata[]): import("./types.js").Awaitable<void>; | ||
onLockedMigrations(migrations: import("./types.js").MigrationMetadata[]): import("./types.js").Awaitable<void>; | ||
onNewMigration(migration: import("./types.js").MigrationMetadata, content: string): import("./types.js").Awaitable<void>; | ||
onMigrationRemoveStart(migration: import("./types.js").MigrationMetadata): import("./types.js").Awaitable<void>; | ||
onMigrationRemoveSuccess(migration: import("./types.js").MigrationMetadataFinished): import("./types.js").Awaitable<void>; | ||
onMigrationRemoveError(migration: import("./types.js").MigrationMetadataFinished, error: Error): import("./types.js").Awaitable<void>; | ||
onMigrationStart(migration: import("./types.js").MigrationMetadata): import("./types.js").Awaitable<void>; | ||
onMigrationSuccess(migration: import("./types.js").MigrationMetadataFinished): import("./types.js").Awaitable<void>; | ||
onMigrationError(migration: import("./types.js").MigrationMetadataFinished, error: Error): import("./types.js").Awaitable<void>; | ||
onMigrationSkip(migration: import("./types.js").MigrationMetadataFinished): import("./types.js").Awaitable<void>; | ||
onFinished(migrations: import("./types.js").MigrationMetadataFinished[], error?: Error | undefined): import("./types.js").Awaitable<void>; | ||
}>; | ||
export declare const isPluginOfType: <T extends keyof { | ||
storage: StoragePlugin; | ||
generator: GeneratorPlugin; | ||
loader: LoaderPlugin; | ||
reporter: Partial<{ | ||
onInit(parameters: { | ||
directory: string; | ||
cwd: string; | ||
dry: boolean; | ||
}): void | PromiseLike<void>; | ||
onCollectedMigrations(migrations: import("./types.js").MigrationMetadata[]): void | PromiseLike<void>; | ||
onLockedMigrations(migrations: import("./types.js").MigrationMetadata[]): void | PromiseLike<void>; | ||
onMigrationStart(migration: import("./types.js").MigrationMetadata): void | PromiseLike<void>; | ||
onMigrationSuccess(migration: import("./types.js").MigrationMetadataFinished): void | PromiseLike<void>; | ||
onMigrationError(migration: import("./types.js").MigrationMetadataFinished, error: Error): void | PromiseLike<void>; | ||
onMigrationSkip(migration: import("./types.js").MigrationMetadataFinished): void | PromiseLike<void>; | ||
onFinished(migrations: import("./types.js").MigrationMetadataFinished[], error?: Error | undefined): void | PromiseLike<void>; | ||
}>; | ||
}>(type: T, plugin: any) => plugin is PluginFromType<T>; | ||
export declare const getOrLoadStorage: (potentialStorages: Array<StringOrModule<unknown>>) => Promise<EmigrateStorage | undefined>; | ||
export declare const getOrLoadReporter: (potentialReporters: Array<StringOrModule<unknown>>) => Promise<EmigrateReporter | undefined>; | ||
export declare const getOrLoadPlugin: <T extends keyof { | ||
storage: StoragePlugin; | ||
generator: GeneratorPlugin; | ||
loader: LoaderPlugin; | ||
reporter: Partial<{ | ||
onInit(parameters: { | ||
directory: string; | ||
cwd: string; | ||
dry: boolean; | ||
}): void | PromiseLike<void>; | ||
onCollectedMigrations(migrations: import("./types.js").MigrationMetadata[]): void | PromiseLike<void>; | ||
onLockedMigrations(migrations: import("./types.js").MigrationMetadata[]): void | PromiseLike<void>; | ||
onMigrationStart(migration: import("./types.js").MigrationMetadata): void | PromiseLike<void>; | ||
onMigrationSuccess(migration: import("./types.js").MigrationMetadataFinished): void | PromiseLike<void>; | ||
onMigrationError(migration: import("./types.js").MigrationMetadataFinished, error: Error): void | PromiseLike<void>; | ||
onMigrationSkip(migration: import("./types.js").MigrationMetadataFinished): void | PromiseLike<void>; | ||
onFinished(migrations: import("./types.js").MigrationMetadataFinished[], error?: Error | undefined): void | PromiseLike<void>; | ||
}>; | ||
}>(type: T, plugins: Array<Plugin | string>) => Promise<PluginFromType<T> | undefined>; | ||
}>(type: T, plugins: Array<StringOrModule<unknown>>) => Promise<PluginFromType<T> | undefined>; | ||
export declare const getOrLoadPlugins: <T extends keyof { | ||
storage: StoragePlugin; | ||
generator: GeneratorPlugin; | ||
loader: LoaderPlugin; | ||
reporter: Partial<{ | ||
onInit(parameters: { | ||
directory: string; | ||
cwd: string; | ||
dry: boolean; | ||
}): void | PromiseLike<void>; | ||
onCollectedMigrations(migrations: import("./types.js").MigrationMetadata[]): void | PromiseLike<void>; | ||
onLockedMigrations(migrations: import("./types.js").MigrationMetadata[]): void | PromiseLike<void>; | ||
onMigrationStart(migration: import("./types.js").MigrationMetadata): void | PromiseLike<void>; | ||
onMigrationSuccess(migration: import("./types.js").MigrationMetadataFinished): void | PromiseLike<void>; | ||
onMigrationError(migration: import("./types.js").MigrationMetadataFinished, error: Error): void | PromiseLike<void>; | ||
onMigrationSkip(migration: import("./types.js").MigrationMetadataFinished): void | PromiseLike<void>; | ||
onFinished(migrations: import("./types.js").MigrationMetadataFinished[], error?: Error | undefined): void | PromiseLike<void>; | ||
}>; | ||
}>(type: T, plugins: Array<Plugin | string>) => Promise<PluginFromType<T>[]>; | ||
}>(type: T, plugins: Array<StringOrModule<unknown>>) => Promise<PluginFromType<T>[]>; | ||
export declare const loadStorage: (name: string) => Promise<EmigrateStorage | undefined>; | ||
export declare const loadReporter: (name: string) => Promise<EmigrateReporter | undefined>; | ||
export declare const loadPlugin: <T extends keyof { | ||
storage: StoragePlugin; | ||
generator: GeneratorPlugin; | ||
loader: LoaderPlugin; | ||
reporter: Partial<{ | ||
onInit(parameters: { | ||
directory: string; | ||
cwd: string; | ||
dry: boolean; | ||
}): void | PromiseLike<void>; | ||
onCollectedMigrations(migrations: import("./types.js").MigrationMetadata[]): void | PromiseLike<void>; | ||
onLockedMigrations(migrations: import("./types.js").MigrationMetadata[]): void | PromiseLike<void>; | ||
onMigrationStart(migration: import("./types.js").MigrationMetadata): void | PromiseLike<void>; | ||
onMigrationSuccess(migration: import("./types.js").MigrationMetadataFinished): void | PromiseLike<void>; | ||
onMigrationError(migration: import("./types.js").MigrationMetadataFinished, error: Error): void | PromiseLike<void>; | ||
onMigrationSkip(migration: import("./types.js").MigrationMetadataFinished): void | PromiseLike<void>; | ||
onFinished(migrations: import("./types.js").MigrationMetadataFinished[], error?: Error | undefined): void | PromiseLike<void>; | ||
}>; | ||
}>(type: T, plugin: string) => Promise<PluginFromType<T> | undefined>; | ||
@@ -95,0 +39,0 @@ /** |
@@ -8,3 +8,3 @@ import process from 'node:process'; | ||
}; | ||
export const isStoragePlugin = (plugin) => { | ||
export const isEmigrateStorage = (plugin) => { | ||
if (!plugin || typeof plugin !== 'object') { | ||
@@ -21,3 +21,3 @@ return false; | ||
}; | ||
export const isReporterPlugin = (plugin) => { | ||
export const isEmigrateReporter = (plugin) => { | ||
if (!plugin || typeof plugin !== 'object') { | ||
@@ -30,2 +30,6 @@ return false; | ||
'onLockedMigrations', | ||
'onNewMigration', | ||
'onMigrationRemoveStart', | ||
'onMigrationRemoveSuccess', | ||
'onMigrationRemoveError', | ||
'onMigrationStart', | ||
@@ -43,25 +47,15 @@ 'onMigrationSuccess', | ||
} | ||
if (type === 'storage') { | ||
return isStoragePlugin(plugin); | ||
} | ||
if (type === 'loader') { | ||
return isLoaderPlugin(plugin); | ||
} | ||
if (type === 'reporter') { | ||
return isReporterPlugin(plugin); | ||
} | ||
throw new Error(`Unknown plugin type: ${type}`); | ||
}; | ||
export const getOrLoadStorage = async (potentialStorages) => { | ||
return getOrLoad(potentialStorages, isEmigrateStorage); | ||
}; | ||
export const getOrLoadReporter = async (potentialReporters) => { | ||
return getOrLoad(potentialReporters, isEmigrateReporter); | ||
}; | ||
export const getOrLoadPlugin = async (type, plugins) => { | ||
const reversePlugins = [...plugins].reverse(); | ||
for await (const plugin of reversePlugins) { | ||
if (isPluginOfType(type, plugin)) { | ||
return plugin; | ||
} | ||
const loadedPlugin = typeof plugin === 'string' ? await loadPlugin(type, plugin) : undefined; | ||
if (loadedPlugin) { | ||
return loadedPlugin; | ||
} | ||
} | ||
return undefined; | ||
return getOrLoad(plugins, (value) => isPluginOfType(type, value)); | ||
}; | ||
@@ -71,3 +65,6 @@ export const getOrLoadPlugins = async (type, plugins) => { | ||
const reversePlugins = [...plugins].reverse(); | ||
for await (const plugin of reversePlugins) { | ||
for await (let plugin of reversePlugins) { | ||
if (typeof plugin === 'function') { | ||
plugin = await plugin(); | ||
} | ||
if (isPluginOfType(type, plugin)) { | ||
@@ -77,2 +74,7 @@ result.push(plugin); | ||
} | ||
// Support export default ... | ||
if (plugin && typeof plugin === 'object' && 'default' in plugin && isPluginOfType(type, plugin.default)) { | ||
result.push(plugin.default); | ||
continue; | ||
} | ||
const loadedPlugin = typeof plugin === 'string' ? await loadPlugin(type, plugin) : undefined; | ||
@@ -85,24 +87,50 @@ if (loadedPlugin) { | ||
}; | ||
export const loadPlugin = async (type, plugin) => { | ||
let importFromEsm = await import('import-from-esm'); | ||
const getOrLoad = async (potentials, check) => { | ||
const reversed = [...potentials].reverse(); | ||
for await (let potential of reversed) { | ||
if (typeof potential === 'function') { | ||
potential = await potential(); | ||
} | ||
if (check(potential)) { | ||
return potential; | ||
} | ||
// Support export default ... | ||
if (potential && typeof potential === 'object' && 'default' in potential && check(potential.default)) { | ||
return potential.default; | ||
} | ||
} | ||
return undefined; | ||
}; | ||
const getImportFromEsm = async () => { | ||
const importFromEsm = await import('import-from-esm'); | ||
// Because of "allowSyntheticDefaultImports" we need to do this ugly hack | ||
if (importFromEsm.default) { | ||
importFromEsm = importFromEsm.default; | ||
return importFromEsm.default; | ||
} | ||
const importsToTry = plugin.startsWith('.') | ||
? [plugin] | ||
: [plugin, `@emigrate/plugin-${plugin}`, `emigrate-plugin-${plugin}`]; | ||
return importFromEsm; | ||
}; | ||
export const loadStorage = async (name) => { | ||
return load(name, ['@emigrate/', '@emigrate/storage-', 'emigrate-storage-', '@emigrate/plugin-storage-'], isEmigrateStorage); | ||
}; | ||
export const loadReporter = async (name) => { | ||
return load(name, ['@emigrate/reporter-', 'emigrate-reporter-'], isEmigrateReporter); | ||
}; | ||
export const loadPlugin = async (type, plugin) => { | ||
return load(plugin, ['@emigrate/', '@emigrate/plugin-', 'emigrate-plugin-'], (value) => { | ||
return isPluginOfType(type, value); | ||
}); | ||
}; | ||
const load = async (name, prefixes, check) => { | ||
const importFromEsm = await getImportFromEsm(); | ||
const importsToTry = name.startsWith('.') ? [name] : [name, ...prefixes.map((prefix) => `${prefix}${name}`)]; | ||
for await (const importPath of importsToTry) { | ||
try { | ||
const pluginModule = await importFromEsm(process.cwd(), importPath); | ||
const module = await importFromEsm(process.cwd(), importPath); | ||
// Support module.exports = ... | ||
if (isPluginOfType(type, pluginModule)) { | ||
return pluginModule; | ||
if (check(module)) { | ||
return module; | ||
} | ||
// Support export default ... | ||
if (pluginModule && | ||
typeof pluginModule === 'object' && | ||
'default' in pluginModule && | ||
isPluginOfType(type, pluginModule.default)) { | ||
return pluginModule.default; | ||
if (module && typeof module === 'object' && 'default' in module && check(module.default)) { | ||
return module.default; | ||
} | ||
@@ -133,4 +161,3 @@ } | ||
.trim() | ||
.replace(/^_|_$/, '') | ||
.toLocaleLowerCase(); | ||
.replace(/^_|_$/, ''); | ||
//# sourceMappingURL=index.js.map |
@@ -29,6 +29,6 @@ import { describe, it } from 'node:test'; | ||
}); | ||
it('should lower case the filename', () => { | ||
assert.strictEqual(sanitizeMigrationName('Are you, Foo?'), 'are_you_foo'); | ||
it('should keep upper cased letters in the filename', () => { | ||
assert.strictEqual(sanitizeMigrationName('Are you, Foo?'), 'Are_you_Foo'); | ||
}); | ||
}); | ||
//# sourceMappingURL=index.test.js.map |
@@ -1,3 +0,6 @@ | ||
type Awaitable<T> = T | PromiseLike<T>; | ||
export type MigrationStatus = 'failed' | 'done'; | ||
export type Awaitable<T> = T | PromiseLike<T>; | ||
export type StringOrModule<T> = string | T | (() => Awaitable<T>) | (() => Awaitable<{ | ||
default: T; | ||
}>); | ||
export type MigrationStatus = 'failed' | 'done' | 'pending'; | ||
export type MigrationHistoryEntry = { | ||
@@ -31,2 +34,10 @@ name: string; | ||
/** | ||
* Remove a migration from the history. | ||
* | ||
* This is used to remove a migration from the history which is needed for failed migrations to be re-executed. | ||
* | ||
* @param migration The migration that should be removed from the history. | ||
*/ | ||
remove(migration: MigrationMetadata): Promise<void>; | ||
/** | ||
* Get the history of previously executed migrations. | ||
@@ -58,6 +69,6 @@ * | ||
}; | ||
export type StoragePlugin = { | ||
export type EmigrateStorage = { | ||
initializeStorage(): Promise<Storage>; | ||
}; | ||
export type InitializeStorageFunction = StoragePlugin['initializeStorage']; | ||
export type InitializeStorageFunction = EmigrateStorage['initializeStorage']; | ||
export type MigrationFile = { | ||
@@ -140,4 +151,8 @@ /** | ||
}; | ||
type InitParameters = { | ||
export type ReporterInitParameters = { | ||
/** | ||
* The command that is being executed | ||
*/ | ||
command: 'up' | 'new' | 'list' | 'remove'; | ||
/** | ||
* The directory where the migration files are located | ||
@@ -152,10 +167,12 @@ */ | ||
* Specifies whether the migration process is a dry run or not. | ||
* | ||
* Will only be true when the command is 'up' and the --dry option is specified. | ||
*/ | ||
dry: boolean; | ||
}; | ||
export type ReporterPlugin = Partial<{ | ||
export type EmigrateReporter = Partial<{ | ||
/** | ||
* Called when the plugin is initialized, which happens before the migrations are collected. | ||
* Called when the reporter is initialized, which is the first method that is called when a command is executed. | ||
*/ | ||
onInit(parameters: InitParameters): Awaitable<void>; | ||
onInit(parameters: ReporterInitParameters): Awaitable<void>; | ||
/** | ||
@@ -177,4 +194,30 @@ * Called when all pending migrations that should be executed have been collected. | ||
/** | ||
* Called when a new migration file has been generated. | ||
* | ||
* This is only called when the command is 'new'. | ||
*/ | ||
onNewMigration(migration: MigrationMetadata, content: string): Awaitable<void>; | ||
/** | ||
* Called when a migration is about to be removed from the migration history. | ||
* | ||
* This is only called when the command is 'remove'. | ||
*/ | ||
onMigrationRemoveStart(migration: MigrationMetadata): Awaitable<void>; | ||
/** | ||
* Called when a migration is successfully removed from the migration history. | ||
* | ||
* This is only called when the command is 'remove'. | ||
*/ | ||
onMigrationRemoveSuccess(migration: MigrationMetadataFinished): Awaitable<void>; | ||
/** | ||
* Called when a migration couldn't be removed from the migration history. | ||
* | ||
* This is only called when the command is 'remove'. | ||
*/ | ||
onMigrationRemoveError(migration: MigrationMetadataFinished, error: Error): Awaitable<void>; | ||
/** | ||
* Called when a migration is about to be executed. | ||
* | ||
* Will only be called for each migration when the command is "up". | ||
* | ||
* @param migration Information about the migration that is about to be executed. | ||
@@ -186,2 +229,5 @@ */ | ||
* | ||
* Will be called after a successful migration when the command is "up" | ||
* or for each successful migration from the history when the command is "list". | ||
* | ||
* @param migration Information about the migration that was executed. | ||
@@ -193,2 +239,5 @@ */ | ||
* | ||
* Will be called after a failed migration when the command is "up" | ||
* or for each failed migration from the history when the command is "list" (will be at most one in this case). | ||
* | ||
* @param migration Information about the migration that failed. | ||
@@ -199,4 +248,10 @@ * @param error The error that caused the migration to fail. | ||
/** | ||
* Called when a migration has been skipped because a previous migration failed, it couldn't be successfully locked, or in case of a dry run. | ||
* Called when a migration is skipped | ||
* | ||
* Will be called when a migration is skipped because a previous migration failed, | ||
* it couldn't be successfully locked, or in case of a dry run when the command is "up". | ||
* When the command is "list" this will be called for each pending migration (i.e. those that have not run yet). | ||
* When the command is "remove" this will be called when the removal of some migrations are skipped | ||
* because the removal of a previous migration failed. | ||
* | ||
* @param migration Information about the migration that was skipped. | ||
@@ -206,7 +261,10 @@ */ | ||
/** | ||
* Called when the migration process has finished. | ||
* Called as a final step after all migrations have been executed, listed or removed. | ||
* | ||
* This is called either after all migrations have been executed successfully, at the end of a dry run, or when a migration has failed. | ||
* This is called either after all migrations have been listed successfully for the "list" command | ||
* or for the "up" command when they are executed successfully, at the end of a dry run, or when a migration has failed. | ||
* It is also called after migrations have been removed from the history with the "remove" command. | ||
* It is also called after a migration file has been generated with the "new" command. | ||
* | ||
* @param migrations Information about all migrations that were executed, their status and any error that occurred. | ||
* @param migrations Information about all migrations that were executed or listed, their status and any error that occurred. | ||
* @param error If the migration process failed, this will be the error that caused the failure. | ||
@@ -216,8 +274,6 @@ */ | ||
}>; | ||
export type Plugin = StoragePlugin | GeneratorPlugin | LoaderPlugin | ReporterPlugin; | ||
export type Plugin = GeneratorPlugin | LoaderPlugin; | ||
type PluginTypeMap = { | ||
storage: StoragePlugin; | ||
generator: GeneratorPlugin; | ||
loader: LoaderPlugin; | ||
reporter: ReporterPlugin; | ||
}; | ||
@@ -224,0 +280,0 @@ export type PluginType = keyof PluginTypeMap; |
{ | ||
"name": "@emigrate/plugin-tools", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"publishConfig": { | ||
@@ -48,4 +48,5 @@ "access": "public" | ||
"test": "glob -c \"node --import tsx --test-reporter spec --test\" \"./src/**/*.test.ts\"", | ||
"test:watch": "glob -c \"node --watch --import tsx --test-reporter spec --test\" \"./src/**/*.test.ts\"" | ||
"test:watch": "glob -c \"node --watch --import tsx --test-reporter spec --test\" \"./src/**/*.test.ts\"", | ||
"lint": "xo --cwd=../.. $(pwd)" | ||
} | ||
} |
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
64280
513