From 9c239e0ae55135f7ddd0dfa36e651257e3fc780a Mon Sep 17 00:00:00 2001 From: Joakim Carlstein Date: Fri, 10 Nov 2023 10:45:15 +0100 Subject: [PATCH] feat(plugins): automatically prefix plugins when loading if necessary I.e. when specifying only "--plugin generate-js" Emigrate will load the @emigrate/plugin-generate-js plugin. --- .changeset/nine-mugs-prove.md | 5 +++ .changeset/tall-zebras-march.md | 5 +++ packages/emigrate/package.json | 3 +- packages/emigrate/src/new-command.ts | 25 ++----------- packages/plugin-tools/package.json | 3 ++ packages/plugin-tools/src/index.ts | 56 +++++++++++++++++++++++++++- packages/plugin-tools/src/types.ts | 4 ++ pnpm-lock.yaml | 11 +++--- 8 files changed, 83 insertions(+), 29 deletions(-) create mode 100644 .changeset/nine-mugs-prove.md create mode 100644 .changeset/tall-zebras-march.md diff --git a/.changeset/nine-mugs-prove.md b/.changeset/nine-mugs-prove.md new file mode 100644 index 0000000..50e4043 --- /dev/null +++ b/.changeset/nine-mugs-prove.md @@ -0,0 +1,5 @@ +--- +'emigrate': minor +--- + +Automatically prefix plugin names when loading them if necessary. I.e. when specifying only "--plugin generate-js" Emigrate will load the @emigrate/plugin-generate-js plugin. It has a priority order that is: 1. the provided plugin name as is, 2. the name prefixed with "@emigrate/plugin-", 3. the name prefixed with "emigrate-plugin-" diff --git a/.changeset/tall-zebras-march.md b/.changeset/tall-zebras-march.md new file mode 100644 index 0000000..f1535ff --- /dev/null +++ b/.changeset/tall-zebras-march.md @@ -0,0 +1,5 @@ +--- +'@emigrate/plugin-tools': minor +--- + +Use import-from-esm to resolve plugins relative to the current working directory and add a convenient plugin loader helper (loadPlugin) diff --git a/packages/emigrate/package.json b/packages/emigrate/package.json index bb09aff..208ffbf 100644 --- a/packages/emigrate/package.json +++ b/packages/emigrate/package.json @@ -28,7 +28,6 @@ "author": "Aboviq AB (https://www.aboviq.com)", "license": "MIT", "dependencies": { - "@emigrate/plugin-tools": "workspace:*", - "import-from-esm": "1.1.3" + "@emigrate/plugin-tools": "workspace:*" } } diff --git a/packages/emigrate/src/new-command.ts b/packages/emigrate/src/new-command.ts index 7da71b0..20e4df0 100644 --- a/packages/emigrate/src/new-command.ts +++ b/packages/emigrate/src/new-command.ts @@ -1,7 +1,7 @@ import process from 'node:process'; import fs from 'node:fs/promises'; import path from 'node:path'; -import { getTimestampPrefix, sanitizeMigrationName, isGeneratorPlugin } from '@emigrate/plugin-tools'; +import { getTimestampPrefix, sanitizeMigrationName, loadPlugin } from '@emigrate/plugin-tools'; import { type GeneratorPlugin } from '@emigrate/plugin-tools/types'; import { ShowUsageError } from './show-usage-error.js'; @@ -45,27 +45,10 @@ export default async function newCommand({ directory, template, plugins, name }: let generatorPlugin: GeneratorPlugin | undefined; for await (const plugin of plugins) { - const pluginPath = plugin.startsWith('.') ? path.resolve(process.cwd(), plugin) : plugin; + generatorPlugin = await loadPlugin('generator', plugin); - try { - const pluginModule: unknown = await import(pluginPath); - - if (isGeneratorPlugin(pluginModule)) { - generatorPlugin = pluginModule; - break; - } - - if ( - pluginModule && - typeof pluginModule === 'object' && - 'default' in pluginModule && - isGeneratorPlugin(pluginModule.default) - ) { - generatorPlugin = pluginModule.default; - break; - } - } catch (error) { - throw new Error(`Failed to load plugin: ${plugin}`, { cause: error }); + if (generatorPlugin) { + break; } } diff --git a/packages/plugin-tools/package.json b/packages/plugin-tools/package.json index 5cad51c..3111d5a 100644 --- a/packages/plugin-tools/package.json +++ b/packages/plugin-tools/package.json @@ -32,5 +32,8 @@ "license": "MIT", "devDependencies": { "@emigrate/tsconfig": "workspace:*" + }, + "dependencies": { + "import-from-esm": "1.2.1" } } diff --git a/packages/plugin-tools/src/index.ts b/packages/plugin-tools/src/index.ts index 0757898..652bfbb 100644 --- a/packages/plugin-tools/src/index.ts +++ b/packages/plugin-tools/src/index.ts @@ -1,4 +1,5 @@ -import { type GeneratorPlugin, type StoragePlugin } from './types.js'; +import process from 'node:process'; +import { type PluginFromType, type PluginType, type GeneratorPlugin, type StoragePlugin } from './types.js'; export const createStoragePlugin = (initialize: StoragePlugin['initialize']): StoragePlugin => { return { @@ -38,6 +39,59 @@ export const isStoragePlugin = (plugin: any): plugin is StoragePlugin => { return false; }; +export const isPluginOfType = (type: T, plugin: any): plugin is PluginFromType => { + if (type === 'generator') { + return isGeneratorPlugin(plugin); + } + + if (type === 'storage') { + return isStoragePlugin(plugin); + } + + throw new Error(`Unknown plugin type: ${type}`); +}; + +export const loadPlugin = async ( + type: T, + plugin: string, +): Promise | undefined> => { + let importFromEsm = await import('import-from-esm'); + + // Because of "allowSyntheticDefaultImports" we need to do this ugly hack + if ((importFromEsm as any).default) { + importFromEsm = (importFromEsm as any).default as unknown as typeof importFromEsm; + } + + const importsToTry = plugin.startsWith('.') + ? [plugin] + : [plugin, `@emigrate/plugin-${plugin}`, `emigrate-plugin-${plugin}`]; + + for await (const importPath of importsToTry) { + try { + const pluginModule: unknown = await importFromEsm(process.cwd(), importPath); + + // Support module.exports = ... + if (isPluginOfType(type, pluginModule)) { + return pluginModule; + } + + // Support export default ... + if ( + pluginModule && + typeof pluginModule === 'object' && + 'default' in pluginModule && + isPluginOfType(type, pluginModule.default) + ) { + return pluginModule.default; + } + } catch { + // Ignore errors + } + } + + return undefined; +}; + /** * Get a timestamp string in the format YYYYMMDDHHmmssmmm based on the current time (UTC) * diff --git a/packages/plugin-tools/src/types.ts b/packages/plugin-tools/src/types.ts index 63e8087..c56f549 100644 --- a/packages/plugin-tools/src/types.ts +++ b/packages/plugin-tools/src/types.ts @@ -86,3 +86,7 @@ export type GeneratorPlugin = { }; export type Plugin = StoragePlugin | GeneratorPlugin; + +export type PluginType = Plugin['type']; + +export type PluginFromType = Extract; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95cc2f8..004c282 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,9 +53,6 @@ importers: '@emigrate/plugin-tools': specifier: workspace:* version: link:../plugin-tools - import-from-esm: - specifier: 1.1.3 - version: 1.1.3 devDependencies: '@emigrate/tsconfig': specifier: workspace:* @@ -72,6 +69,10 @@ importers: version: link:../tsconfig packages/plugin-tools: + dependencies: + import-from-esm: + specifier: 1.2.1 + version: 1.2.1 devDependencies: '@emigrate/tsconfig': specifier: workspace:* @@ -3134,8 +3135,8 @@ packages: resolve-from: 4.0.0 dev: false - /import-from-esm@1.1.3: - resolution: {integrity: sha512-1BxFAthpQf5qabfPBaFBRAGIh8TVt6WB4ujqedfoF4oVjwyl6S/dZv26gL5kgPhbO1XBqu4hcELUlV/+IPsC3A==} + /import-from-esm@1.2.1: + resolution: {integrity: sha512-Nly5Ab75rWZmOwtMa0B0NQNnHGcHOQ2zkU/bVENwK2lbPq+kamPDqNKNJ0hF7w7lR/ETD5nGgJq0XbofsZpYCA==} engines: {node: '>=16.20'} dependencies: import-meta-resolve: 4.0.0