feat(cli): storage and reporter are now their own options
Instead of mixing storages and reporters with other plugins in the plugin option they now have their own separate options. This is for increased future flexibility and to be more similar to other CLI tools. BREAKING CHANGE: the storage to use must now be specified using the "storage" configuration option or the "--storage" CLI option instead of having it among other plugins.
This commit is contained in:
parent
509cd41663
commit
8e87ade5c0
9 changed files with 150 additions and 95 deletions
|
|
@ -3,10 +3,10 @@ import {
|
|||
type PluginFromType,
|
||||
type PluginType,
|
||||
type GeneratorPlugin,
|
||||
type StoragePlugin,
|
||||
type Plugin,
|
||||
type EmigrateReporter,
|
||||
type EmigrateStorage,
|
||||
type LoaderPlugin,
|
||||
type ReporterPlugin,
|
||||
type StringOrModule,
|
||||
} from './types.js';
|
||||
|
||||
export const isGeneratorPlugin = (plugin: any): plugin is GeneratorPlugin => {
|
||||
|
|
@ -17,7 +17,7 @@ export const isGeneratorPlugin = (plugin: any): plugin is GeneratorPlugin => {
|
|||
return typeof plugin.generateMigration === 'function';
|
||||
};
|
||||
|
||||
export const isStoragePlugin = (plugin: any): plugin is StoragePlugin => {
|
||||
export const isEmigrateStorage = (plugin: any): plugin is EmigrateStorage => {
|
||||
if (!plugin || typeof plugin !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ export const isLoaderPlugin = (plugin: any): plugin is LoaderPlugin => {
|
|||
return typeof plugin.loadMigration === 'function' && Array.isArray(plugin.loadableExtensions);
|
||||
};
|
||||
|
||||
export const isReporterPlugin = (plugin: any): plugin is ReporterPlugin => {
|
||||
export const isEmigrateReporter = (plugin: any): plugin is EmigrateReporter => {
|
||||
if (!plugin || typeof plugin !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -57,50 +57,44 @@ export const isPluginOfType = <T extends PluginType>(type: T, plugin: any): plug
|
|||
return isGeneratorPlugin(plugin);
|
||||
}
|
||||
|
||||
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: Array<StringOrModule<unknown>>,
|
||||
): Promise<EmigrateStorage | undefined> => {
|
||||
return getOrLoad(potentialStorages, isEmigrateStorage);
|
||||
};
|
||||
|
||||
export const getOrLoadReporter = async (
|
||||
potentialReporters: Array<StringOrModule<unknown>>,
|
||||
): Promise<EmigrateReporter | undefined> => {
|
||||
return getOrLoad(potentialReporters, isEmigrateReporter);
|
||||
};
|
||||
|
||||
export const getOrLoadPlugin = async <T extends PluginType>(
|
||||
type: T,
|
||||
plugins: Array<Plugin | string>,
|
||||
plugins: Array<StringOrModule<unknown>>,
|
||||
): Promise<PluginFromType<T> | undefined> => {
|
||||
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: unknown): value is PluginFromType<T> => isPluginOfType(type, value));
|
||||
};
|
||||
|
||||
export const getOrLoadPlugins = async <T extends PluginType>(
|
||||
type: T,
|
||||
plugins: Array<Plugin | string>,
|
||||
plugins: Array<StringOrModule<unknown>>,
|
||||
): Promise<Array<PluginFromType<T>>> => {
|
||||
const result: Array<PluginFromType<T>> = [];
|
||||
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)) {
|
||||
result.push(plugin);
|
||||
continue;
|
||||
|
|
@ -116,10 +110,28 @@ export const getOrLoadPlugins = async <T extends PluginType>(
|
|||
return result;
|
||||
};
|
||||
|
||||
export const loadPlugin = async <T extends PluginType>(
|
||||
type: T,
|
||||
plugin: string,
|
||||
): Promise<PluginFromType<T> | undefined> => {
|
||||
const getOrLoad = async <T>(potentials: Array<StringOrModule<unknown>>, check: (value: unknown) => value is T) => {
|
||||
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 () => {
|
||||
let importFromEsm = await import('import-from-esm');
|
||||
|
||||
// Because of "allowSyntheticDefaultImports" we need to do this ugly hack
|
||||
|
|
@ -127,27 +139,47 @@ export const loadPlugin = async <T extends PluginType>(
|
|||
importFromEsm = (importFromEsm as any).default as unknown as typeof importFromEsm;
|
||||
}
|
||||
|
||||
const importsToTry = plugin.startsWith('.')
|
||||
? [plugin]
|
||||
: [plugin, `@emigrate/plugin-${plugin}`, `emigrate-plugin-${plugin}`];
|
||||
return importFromEsm;
|
||||
};
|
||||
|
||||
export const loadStorage = async (name: string): Promise<EmigrateStorage | undefined> => {
|
||||
return load(name, ['@emigrate/storage-', 'emigrate-storage-', '@emigrate/plugin-storage-'], isEmigrateStorage);
|
||||
};
|
||||
|
||||
export const loadReporter = async (name: string): Promise<EmigrateReporter | undefined> => {
|
||||
return load(name, ['@emigrate/reporter-', 'emigrate-reporter-'], isEmigrateReporter);
|
||||
};
|
||||
|
||||
export const loadPlugin = async <T extends PluginType>(
|
||||
type: T,
|
||||
plugin: string,
|
||||
): Promise<PluginFromType<T> | undefined> => {
|
||||
return load(plugin, ['@emigrate/plugin-', 'emigrate-plugin-'], (value: unknown): value is PluginFromType<T> => {
|
||||
return isPluginOfType(type, value);
|
||||
});
|
||||
};
|
||||
|
||||
const load = async <T>(
|
||||
name: string,
|
||||
prefixes: string[],
|
||||
check: (value: unknown) => value is T,
|
||||
): Promise<T | undefined> => {
|
||||
const importFromEsm = await getImportFromEsm();
|
||||
|
||||
const importsToTry = name.startsWith('.') ? [name] : [name, ...prefixes.map((prefix) => `${prefix}${name}`)];
|
||||
|
||||
for await (const importPath of importsToTry) {
|
||||
try {
|
||||
const pluginModule: unknown = await importFromEsm(process.cwd(), importPath);
|
||||
const module: unknown = 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;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
type Awaitable<T> = T | PromiseLike<T>;
|
||||
export type Awaitable<T> = T | PromiseLike<T>;
|
||||
|
||||
export type StringOrModule<T> = string | T | (() => Awaitable<T>) | (() => Awaitable<{ default: T }>);
|
||||
|
||||
export type MigrationStatus = 'failed' | 'done';
|
||||
|
||||
|
|
@ -58,11 +60,11 @@ export type Storage = {
|
|||
onError(migration: MigrationMetadataFinished, error: Error): Promise<void>;
|
||||
};
|
||||
|
||||
export type StoragePlugin = {
|
||||
export type EmigrateStorage = {
|
||||
initializeStorage(): Promise<Storage>;
|
||||
};
|
||||
|
||||
export type InitializeStorageFunction = StoragePlugin['initializeStorage'];
|
||||
export type InitializeStorageFunction = EmigrateStorage['initializeStorage'];
|
||||
|
||||
export type MigrationFile = {
|
||||
/**
|
||||
|
|
@ -163,7 +165,7 @@ type InitParameters = {
|
|||
dry: boolean;
|
||||
};
|
||||
|
||||
export type ReporterPlugin = Partial<{
|
||||
export type EmigrateReporter = Partial<{
|
||||
/**
|
||||
* Called when the plugin is initialized, which happens before the migrations are collected.
|
||||
*/
|
||||
|
|
@ -219,13 +221,11 @@ export type ReporterPlugin = Partial<{
|
|||
onFinished(migrations: MigrationMetadataFinished[], error?: Error): Awaitable<void>;
|
||||
}>;
|
||||
|
||||
export type Plugin = StoragePlugin | GeneratorPlugin | LoaderPlugin | ReporterPlugin;
|
||||
export type Plugin = GeneratorPlugin | LoaderPlugin;
|
||||
|
||||
type PluginTypeMap = {
|
||||
storage: StoragePlugin;
|
||||
generator: GeneratorPlugin;
|
||||
loader: LoaderPlugin;
|
||||
reporter: ReporterPlugin;
|
||||
};
|
||||
|
||||
export type PluginType = keyof PluginTypeMap;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue