-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Including the presets and plugins module path into config.metadata
#43
Changes from all commits
4147b69
31b1e4f
9aa2016
602af6a
0b8b235
0795686
8422a9c
9c64893
a5b3e94
0151543
df751c8
d694db9
f5ed2a8
04f6f59
c002a8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,16 @@ | ||
const flatten = require('./flatten'); | ||
const Resolver = require('./resolver'); | ||
const path = require('path'); | ||
|
||
let dynamicNamingId = 0; | ||
|
||
class PluginEngine { | ||
constructor(config, { resolveFrom } = {}) { | ||
this.config = config || {}; | ||
this.resolver = new Resolver({ resolveFrom }); | ||
this.config.root = this.config.root || process.cwd(); | ||
this.config.metadata = this.config.metadata || {}; | ||
this.config.metadata = { ...this.config.metadata, plugins: {}, presets: {} }; | ||
this.resolver = new Resolver({ resolveFrom, root: this.config.root }); | ||
|
||
this._hooks = {}; | ||
this._plans = {}; | ||
|
@@ -30,25 +34,79 @@ class PluginEngine { | |
const { presets = [], add = [], remove } = pluginConfig || { presets: ['default'] }; | ||
const pluginsToRemove = new Set(remove || []); | ||
|
||
|
||
// TODO: check to see if presets include duplicate plugins | ||
const presetEntries = flatten(presets) | ||
const presetsFlattened = flatten(presets); | ||
|
||
// Adding presets module paths into config.metadata | ||
this._registerPresetsModulePath(presetsFlattened); | ||
|
||
const presetEntries = presetsFlattened | ||
.map(name => this._resolvePresetSafe(name)) | ||
.reduce((acc, plugins) => [...acc, ...plugins], []) | ||
.map(plugin => [plugin.shortName, plugin.required]); | ||
|
||
// TODO: check to see if addPlugins have duplicates from presetPlugins | ||
const addEntries = add.map(name => [name, this.resolver.pluginFor(name)]); | ||
|
||
return presetEntries | ||
const pluginsResolved = presetEntries | ||
.concat(addEntries) | ||
.filter(([pluginName]) => !pluginsToRemove.has(pluginName)) | ||
.filter(([pluginName]) => !pluginsToRemove.has(pluginName)); | ||
|
||
// Adding plugins module paths into config.metadata | ||
this._registerPluginsModulePath(pluginsResolved); | ||
|
||
return pluginsResolved | ||
.reduce((acc, [pluginName, plugin]) => { | ||
acc[this._extractName(pluginName)] = plugin; | ||
return acc; | ||
}, {}); | ||
} | ||
|
||
/** | ||
* Saves into the gasket config the module path of the presets | ||
* | ||
* @param {Array} presets Array of presets | ||
* @private | ||
*/ | ||
_registerPresetsModulePath(presets) { | ||
presets.forEach(presetName => { | ||
try { | ||
const relativePath = this.resolver.tryResolvePresetRelativePath(presetName); | ||
this.config.metadata.presets[presetName] = { modulePath: relativePath }; | ||
} catch (err) { | ||
console.error(`Preset '${presetName}' couldn't be resolved`); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Saves into the gasket config the module path of the plugins | ||
* | ||
* @param {Array} plugins Array of plugins | ||
* @private | ||
*/ | ||
_registerPluginsModulePath(plugins) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels like a lot of this code should be owned by the Resolver, and is related to: Maybe add a related tryResolve(moduleName) {
const options = this.resolveFrom ? { paths: [this.resolveFrom] } : {};
// may want to wrap this in a try/catch
return require.resolve(moduleName, options);
} That way, you can utilize the See the |
||
plugins.forEach(([plugin]) => { | ||
const pluginName = plugin.name || plugin; | ||
try { | ||
const relativePath = this.resolver.tryResolvePluginRelativePath(pluginName); | ||
|
||
var pluginNameKey; | ||
// Plugins that are defined locally to the app | ||
if (pluginName.indexOf(this.config.root) !== -1) { | ||
pluginNameKey = path.basename(pluginName).replace('-plugin', ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need really this fixup to remove In fact, it would probably be best to include So, for example, if a user has these plugins:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code is only applied to the local plugins that are defined under metadata: {
plugins: {
pluginA: 'plugins/pluginA-plugin'
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you prefer to have something else as key I can work on that. |
||
// External plugins | ||
} else { | ||
pluginNameKey = pluginName; | ||
} | ||
|
||
this.config.metadata.plugins[pluginNameKey] = { modulePath: relativePath }; | ||
} catch (err) { | ||
console.error(`Plugin '${pluginName}' couldn't be resolved`); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Early versions of @gasket/*-preset could export shortName strings. | ||
* This method allows for backwards compatibility with that API. | ||
|
@@ -511,7 +569,7 @@ class PluginEngine { | |
if (follower in subscribers) { | ||
subscribers[follower].ordering.after.push(plugin); | ||
} else { | ||
console.warn(`Plugin '${follower}' does not have hook: '${hookName.replace('bound', '').trim()}'`) | ||
console.warn(`Plugin '${follower}' does not have hook: '${hookName.replace('bound', '').trim()}'`); | ||
} | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
|
||
const PluginEngine = require('../lib/engine'); | ||
const Resolver = require('../lib/resolver'); | ||
|
||
/* | ||
* Simple helper to create the data structure @gasket/resolve | ||
* generates in presets. | ||
*/ | ||
function createPreset({ name: preset, plugins }) { | ||
return plugins.map(plugin => { | ||
return { | ||
required: plugin, | ||
kind: 'plugin', | ||
from: preset, | ||
shortName: plugin.name, | ||
name: `@gasket/${plugin.name}-plugin`, | ||
range: 'latest' | ||
}; | ||
}); | ||
} | ||
|
||
describe('PluginEngine', () => { | ||
|
||
beforeEach(() => { | ||
const testAPlugin = { name: 'testa', hooks: { mockEvent: jest.fn() } }; | ||
const customPlugin = { name: 'custom-plugin' }; | ||
const somePreset = createPreset({ | ||
name: '@gasket/somePreset-preset', | ||
plugins: [testAPlugin] | ||
}); | ||
|
||
jest | ||
.doMock('@gasket/testa-plugin', () => testAPlugin, { virtual: true }) | ||
.doMock('@gasket/somePreset-preset', () => somePreset, { virtual: true }) | ||
.doMock('@something/custom-plugin', () => customPlugin, { virtual: true }); | ||
|
||
jest.spyOn(Resolver.prototype, 'tryResolve').mockImplementation(arg => { | ||
return `${process.cwd()}/node_modules/${arg}`; | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.resetModules(); | ||
jest.restoreAllMocks(); | ||
}); | ||
|
||
it('includes the plugin path into gasket.config.metadata.plugins when using a plugin name', async () => { | ||
const engine = new PluginEngine({ | ||
plugins: { | ||
add: ['testa'] | ||
} | ||
}); | ||
|
||
expect(engine.config.metadata.plugins).toStrictEqual({ testa: { modulePath: 'node_modules/@gasket/testa-plugin' } }); | ||
expect(engine.config.metadata.presets).toStrictEqual({}); | ||
}); | ||
|
||
it('includes the plugin path into gasket.config.metadata.plugins when using a custom plugin name', async () => { | ||
const engine = new PluginEngine({ | ||
plugins: { | ||
add: ['@something/custom-plugin'] | ||
} | ||
}); | ||
|
||
expect(engine.config.metadata.plugins).toStrictEqual({ '@something/custom-plugin': { modulePath: 'node_modules/@something/custom-plugin' } }); | ||
expect(engine.config.metadata.presets).toStrictEqual({}); | ||
}); | ||
|
||
it('includes the plugin path into gasket.config.metadata.plugins when using a plugin object', async () => { | ||
const dynamicHook = jest.fn(); | ||
const engine = new PluginEngine({ | ||
plugins: { | ||
add: [ | ||
{ | ||
name: 'testa', | ||
hooks: { | ||
init(gasket) { | ||
gasket.hook({ event: 'foo', handler: dynamicHook }); | ||
} | ||
} | ||
} | ||
] | ||
} | ||
}); | ||
|
||
expect(engine.config.metadata.plugins).toStrictEqual({ testa: { modulePath: 'node_modules/@gasket/testa-plugin' } }); | ||
expect(engine.config.metadata.presets).toStrictEqual({}); | ||
}); | ||
|
||
it('does not include the plugin path if it is removed', async () => { | ||
const engine = new PluginEngine({ | ||
plugins: { | ||
add: ['testa'], | ||
remove: ['testa'] | ||
} | ||
}); | ||
|
||
expect(engine.config.metadata.plugins).toStrictEqual({}); | ||
expect(engine.config.metadata.presets).toStrictEqual({}); | ||
}); | ||
|
||
it('includes the preset path and its plugins paths into gasket.config.metadata when using a preset', async () => { | ||
const engine = new PluginEngine({ | ||
plugins: { | ||
presets: ['somePreset'] | ||
} | ||
}); | ||
|
||
expect(engine.config.metadata.presets).toStrictEqual({ | ||
somePreset: { | ||
modulePath: 'node_modules/@gasket/somePreset-preset' | ||
} | ||
}); | ||
expect(engine.config.metadata.plugins).toStrictEqual({ testa: { modulePath: 'node_modules/@gasket/testa-plugin' } }); | ||
}); | ||
|
||
it('includes the preset path but not the plugins paths into gasket.config.metadata when using a preset', async () => { | ||
const engine = new PluginEngine({ | ||
plugins: { | ||
presets: ['somePreset'], | ||
remove: ['testa'] | ||
} | ||
}); | ||
|
||
expect(engine.config.metadata.presets).toStrictEqual({ | ||
somePreset: { | ||
modulePath: 'node_modules/@gasket/somePreset-preset' | ||
} | ||
}); | ||
expect(engine.config.metadata.plugins).toStrictEqual({}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: this order is usually inverted, e.g. params first and then scope.