Skip to content

Commit

Permalink
Backport requirements. Closes #3869
Browse files Browse the repository at this point in the history
  • Loading branch information
hueniverse committed Nov 6, 2018
1 parent 4eed069 commit 3faaa4b
Show file tree
Hide file tree
Showing 6 changed files with 412 additions and 16 deletions.
29 changes: 22 additions & 7 deletions API.md 100644 → 100755
@@ -1,4 +1,4 @@
# 16.6.x API Reference
# 16.7.x API Reference

- [Server](#server)
- [`new Server([options])`](#new-serveroptions)
Expand Down Expand Up @@ -1028,11 +1028,17 @@ server.route({

### `server.dependency(dependencies, [after])`

Used within a plugin to declare a required dependency on other [plugins](#plugins) where:
- `dependencies` - a single string or array of plugin name strings which must be registered in
order for this plugin to operate. Plugins listed must be registered before the server is initialized or started.
Does not provide version dependency which should be implemented using
[npm peer dependencies](http://blog.nodejs.org/2013/02/07/peer-dependencies/).

Used within a plugin to declare a required dependency on other [plugins](#plugins) required for
the current plugin to operate (plugins listed must be registered before the server is initialized
or started) where:

- `dependencies` - one of:
- a single plugin name string.
- an array of plugin name strings.
- an object where each key is a plugin name and each matching value is a
[version range string](https://www.npmjs.com/package/semver) which must match the registered
plugin version.
- `after` - an optional function called after all the specified dependencies have been registered
and before the server starts. The function is only called if the server is initialized or started. If a circular
dependency is detected, an exception is thrown (e.g. two plugins each has an `after` function
Expand Down Expand Up @@ -1073,10 +1079,19 @@ exports.register = function (server, options, next) {
register.attributes = {
name: 'test',
version: '1.0.0',
dependencies: 'yar'
dependencies: {
yar: '1.x.x'
}
};
```

The `dependencies` configuration accepts one of:
- a single plugin name string.
- an array of plugin name strings.
- an object where each key is a plugin name and each matching value is a
[version range string](https://www.npmjs.com/package/semver) which must match the registered
plugin version.

### `server.emit(criteria, data, [callback])`

Emits a custom application event update to all the subscribed listeners where:
Expand Down
25 changes: 23 additions & 2 deletions lib/plugin.js
Expand Up @@ -6,6 +6,7 @@ const Catbox = require('catbox');
const Hoek = require('hoek');
const Items = require('items');
const Podium = require('podium');
const Somever = require('somever');
const Connection = require('./connection');
const Ext = require('./ext');
const Package = require('../package.json');
Expand Down Expand Up @@ -223,6 +224,7 @@ internals.Plugin.prototype.register = function (plugins /*, [options], callback
pluginOptions: plugin.options,
dependencies: attributes.dependencies,
connections: attributes.connections,
requirements: attributes.requirements,
options: {
once: attributes.once || (plugin.once !== undefined ? plugin.once : options.once),
routes: {
Expand Down Expand Up @@ -252,6 +254,12 @@ internals.Plugin.prototype.register = function (plugins /*, [options], callback
attributes: item.register.attributes
};

// Validate requirements

const requirements = item.requirements;
Hoek.assert(!requirements.node || Somever.match(process.version, requirements.node), 'Plugin', item.name, 'requires node version', requirements.node, 'but found', process.version);
Hoek.assert(!requirements.hapi || Somever.match(this.version, requirements.hapi), 'Plugin', item.name, 'requires hapi version', requirements.hapi, 'but found', this.version);

// Protect against multiple registrations

const connectionless = (item.connections === 'conditional' ? selection.connections.length === 0 : !item.connections);
Expand Down Expand Up @@ -408,11 +416,24 @@ internals.Plugin.prototype.dependency = function (dependencies, after) {
Hoek.assert(this.realm.plugin, 'Cannot call dependency() outside of a plugin');
Hoek.assert(!after || typeof after === 'function', 'Invalid after method');

dependencies = [].concat(dependencies);
// Normalize to { plugin: version }

if (typeof dependencies === 'string') {
dependencies = { [dependencies]: '*' };
}
else if (Array.isArray(dependencies)) {
const map = {};
for (const dependency of dependencies) {
map[dependency] = '*';
}

dependencies = map;
}

this.root._dependencies.push({ plugin: this.realm.plugin, connections: this.connections, deps: dependencies });

if (after) {
this.ext('onPreStart', after, { after: dependencies });
this.ext('onPreStart', after, { after: Object.keys(dependencies) });
}
};

Expand Down
15 changes: 13 additions & 2 deletions lib/schema.js
Expand Up @@ -330,6 +330,9 @@ internals.register = Joi.object({
});


internals.semver = Joi.string();


internals.plugin = internals.register.keys({
register: Joi.func().keys({
attributes: Joi.object({
Expand All @@ -345,9 +348,17 @@ internals.plugin = internals.register.keys({
.when('pkg.name', { is: Joi.exist(), otherwise: Joi.required() }),
version: Joi.string(),
multiple: Joi.boolean().default(false),
dependencies: Joi.array().items(Joi.string()).single(),
dependencies: [
Joi.array().items(Joi.string()).single(),
Joi.object().pattern(/.+/, internals.semver)
],
connections: Joi.boolean().allow('conditional').default(true),
once: Joi.boolean().valid(true)
once: Joi.boolean().valid(true),
requirements: Joi.object({
hapi: Joi.string(),
node: Joi.string()
})
.default()
})
.required()
.unknown()
Expand Down
27 changes: 23 additions & 4 deletions lib/server.js
Expand Up @@ -9,6 +9,7 @@ const Hoek = require('hoek');
const Items = require('items');
const Mimos = require('mimos');
const Podium = require('podium');
const Somever = require('somever');
const Connection = require('./connection');
const Defaults = require('./defaults');
const Ext = require('./ext');
Expand Down Expand Up @@ -296,20 +297,38 @@ internals.Server.prototype._validateDeps = function () {
if (dependency.connections) {
for (let j = 0; j < dependency.connections.length; ++j) {
const connection = dependency.connections[j];
for (let k = 0; k < dependency.deps.length; ++k) {
const dep = dependency.deps[k];
const deps = Object.keys(dependency.deps);
for (let k = 0; k < deps.length; ++k) {
const dep = deps[k];
const version = dependency.deps[dep];

if (!connection.registrations[dep]) {
return new Error('Plugin ' + dependency.plugin + ' missing dependency ' + dep + ' in connection: ' + connection.info.uri);
}

if (version !== '*' &&
!Somever.match(connection.registrations[dep].version, version)) {

return new Error('Plugin ' + dependency.plugin + ' requires ' + dep + ' version ' + version + ' but found ' + connection.registrations[dep].version + ' in connection: ' + connection.info.uri);
}
}
}
}
else {
for (let j = 0; j < dependency.deps.length; ++j) {
const dep = dependency.deps[j];
const deps = Object.keys(dependency.deps);
for (let j = 0; j < deps.length; ++j) {
const dep = deps[j];
const version = dependency.deps[dep];

if (!this._registrations[dep]) {
return new Error('Plugin ' + dependency.plugin + ' missing dependency ' + dep);
}

if (version !== '*' &&
!Somever.match(this._registrations[dep].version, version)) {

return new Error('Plugin ' + dependency.plugin + ' requires ' + dep + ' version ' + version + ' but found ' + this._registrations[dep].version);
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -30,6 +30,7 @@
"mimos": "3.x.x",
"podium": "1.x.x",
"shot": "3.x.x",
"somever": "1.x.x",
"statehood": "5.x.x",
"subtext": "5.x.x",
"topo": "2.x.x"
Expand Down

0 comments on commit 3faaa4b

Please sign in to comment.