Skip to content
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

Merged
merged 15 commits into from
Aug 13, 2019
Merged
3 changes: 3 additions & 0 deletions packages/gasket-cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# `@gasket/cli`

###

- Moving `package-identifier.js` to `@gasket/resolve`.
- Able to define default plugins

### 2.3.0
Expand Down
1 change: 1 addition & 0 deletions packages/gasket-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@gasket/git-plugin": "^1.0.0",
"@gasket/lifecycle-plugin": "^1.0.1",
"@gasket/plugin-engine": "^1.3.1",
"@gasket/resolve": "^1.3.0",
"@gasket/utils": "^1.1.0",
"@oclif/command": "^1.5.14",
"@oclif/config": "^1.13.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/gasket-cli/src/scaffold/actions/load-preset.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const path = require('path');
const action = require('../action-wrapper');
const PackageFetcher = require('../fetcher');
const { pluginIdentifier, presetIdentifier } = require('../package-identifier');
const { pluginIdentifier, presetIdentifier } = require('@gasket/resolve');

/**
* Fetches the preset package and reads package.json contents
Expand Down
2 changes: 1 addition & 1 deletion packages/gasket-cli/src/scaffold/actions/prompt-hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const inquirer = require('inquirer');
const action = require('../action-wrapper');
const PackageManager = require('../package-manager');
const { addPluginsToContext, addPluginsToPkg } = require('../plugin-utils');
const { pluginIdentifier } = require('../package-identifier');
const { pluginIdentifier } = require('@gasket/resolve');
const createEngine = require('../create-engine');

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/gasket-cli/src/scaffold/actions/setup-pkg.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const action = require('../action-wrapper');
const ConfigBuilder = require('../config-builder');
const { addPluginsToPkg } = require('../plugin-utils');
const { presetIdentifier } = require('../package-identifier');
const { presetIdentifier } = require('@gasket/resolve');

/**
* Initializes the ConfigBuilder builder and adds to context.
Expand Down
2 changes: 1 addition & 1 deletion packages/gasket-cli/src/scaffold/plugin-utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { pluginIdentifier } = require('./package-identifier');
const { pluginIdentifier } = require('@gasket/resolve');

/**
* Pushes plugins short names for use in gasket.config.js
Expand Down
2 changes: 1 addition & 1 deletion packages/gasket-command-plugin/test/plugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('The plugin', () => {
BaseCommand: MockBaseCommand
});

expect(gasket.exec).toBeCalledWith('getCommands', {
expect(gasket.exec).toHaveBeenCalledWith('getCommands', {
oclifConfig,
BaseCommand: MockBaseCommand
});
Expand Down
3 changes: 3 additions & 0 deletions packages/gasket-plugin-engine/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# CHANGELOG

###

- Including presets and plugins paths into `config.metadata`
- Warns user about bad plugin timings

### 1.2.0
Expand Down
70 changes: 64 additions & 6 deletions packages/gasket-plugin-engine/lib/engine.js
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 = {};
Expand All @@ -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
Copy link
Member

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.

* @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) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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:
https://github.com/godaddy/gasket/blob/master/packages/gasket-resolve/resolver.js#L52-L55

Maybe add a related resolver.tryResolve like:

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 resolveFrom option, instead of deriving your own rootPath.

See the require.resolve docs.

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', '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need really this fixup to remove -plugin? Seems like that could give confusing results, being a short name, which is reserved for @gasket/<name>-plugin formatted plugins.

In fact, it would probably be best to include plugins/ in this key name, to delineate an app plugin, from a node_module plugin.

So, for example, if a user has these plugins:

  • example — resolved short name for @gasket/example-plugin
  • example-plugin
  • plugins/example-plugin — avoids collision with example-plugin

Copy link
Contributor Author

@DanielSanudo DanielSanudo Aug 12, 2019

Choose a reason for hiding this comment

The 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 plugin/something-plugin, it will not affect to any @gasket/<name>-plugin. What I am doing is getting something-pluginand removing -plugin from it to use it as the key.
Metadata will appear as this:

metadata: {
  plugins: {
    pluginA: 'plugins/pluginA-plugin'
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Expand Down Expand Up @@ -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()}'`);
}
});

Expand Down
2 changes: 1 addition & 1 deletion packages/gasket-plugin-engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
},
"homepage": "https://github.com/godaddy/gasket/tree/master/packages/gasket-plugin-engine",
"scripts": {
"pretest": "npm run lint",
"test": "npm run unit",
"posttest": "npm run lint",
"lint": "eslint lib test",
"lint:fix": "npm run lint -- --fix",
"unit": "jest --coverage"
Expand Down
132 changes: 132 additions & 0 deletions packages/gasket-plugin-engine/test/config-metadata.test.js
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({});
});
});
Loading