Skip to content

Commit 3faaa4b

Browse files
committed
Backport requirements. Closes #3869
1 parent 4eed069 commit 3faaa4b

File tree

6 files changed

+412
-16
lines changed

6 files changed

+412
-16
lines changed

API.md

100644100755
Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 16.6.x API Reference
1+
# 16.7.x API Reference
22

33
- [Server](#server)
44
- [`new Server([options])`](#new-serveroptions)
@@ -1028,11 +1028,17 @@ server.route({
10281028

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

1031-
Used within a plugin to declare a required dependency on other [plugins](#plugins) where:
1032-
- `dependencies` - a single string or array of plugin name strings which must be registered in
1033-
order for this plugin to operate. Plugins listed must be registered before the server is initialized or started.
1034-
Does not provide version dependency which should be implemented using
1035-
[npm peer dependencies](http://blog.nodejs.org/2013/02/07/peer-dependencies/).
1031+
1032+
Used within a plugin to declare a required dependency on other [plugins](#plugins) required for
1033+
the current plugin to operate (plugins listed must be registered before the server is initialized
1034+
or started) where:
1035+
1036+
- `dependencies` - one of:
1037+
- a single plugin name string.
1038+
- an array of plugin name strings.
1039+
- an object where each key is a plugin name and each matching value is a
1040+
[version range string](https://www.npmjs.com/package/semver) which must match the registered
1041+
plugin version.
10361042
- `after` - an optional function called after all the specified dependencies have been registered
10371043
and before the server starts. The function is only called if the server is initialized or started. If a circular
10381044
dependency is detected, an exception is thrown (e.g. two plugins each has an `after` function
@@ -1073,10 +1079,19 @@ exports.register = function (server, options, next) {
10731079
register.attributes = {
10741080
name: 'test',
10751081
version: '1.0.0',
1076-
dependencies: 'yar'
1082+
dependencies: {
1083+
yar: '1.x.x'
1084+
}
10771085
};
10781086
```
10791087

1088+
The `dependencies` configuration accepts one of:
1089+
- a single plugin name string.
1090+
- an array of plugin name strings.
1091+
- an object where each key is a plugin name and each matching value is a
1092+
[version range string](https://www.npmjs.com/package/semver) which must match the registered
1093+
plugin version.
1094+
10801095
### `server.emit(criteria, data, [callback])`
10811096

10821097
Emits a custom application event update to all the subscribed listeners where:

lib/plugin.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const Catbox = require('catbox');
66
const Hoek = require('hoek');
77
const Items = require('items');
88
const Podium = require('podium');
9+
const Somever = require('somever');
910
const Connection = require('./connection');
1011
const Ext = require('./ext');
1112
const Package = require('../package.json');
@@ -223,6 +224,7 @@ internals.Plugin.prototype.register = function (plugins /*, [options], callback
223224
pluginOptions: plugin.options,
224225
dependencies: attributes.dependencies,
225226
connections: attributes.connections,
227+
requirements: attributes.requirements,
226228
options: {
227229
once: attributes.once || (plugin.once !== undefined ? plugin.once : options.once),
228230
routes: {
@@ -252,6 +254,12 @@ internals.Plugin.prototype.register = function (plugins /*, [options], callback
252254
attributes: item.register.attributes
253255
};
254256

257+
// Validate requirements
258+
259+
const requirements = item.requirements;
260+
Hoek.assert(!requirements.node || Somever.match(process.version, requirements.node), 'Plugin', item.name, 'requires node version', requirements.node, 'but found', process.version);
261+
Hoek.assert(!requirements.hapi || Somever.match(this.version, requirements.hapi), 'Plugin', item.name, 'requires hapi version', requirements.hapi, 'but found', this.version);
262+
255263
// Protect against multiple registrations
256264

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

411-
dependencies = [].concat(dependencies);
419+
// Normalize to { plugin: version }
420+
421+
if (typeof dependencies === 'string') {
422+
dependencies = { [dependencies]: '*' };
423+
}
424+
else if (Array.isArray(dependencies)) {
425+
const map = {};
426+
for (const dependency of dependencies) {
427+
map[dependency] = '*';
428+
}
429+
430+
dependencies = map;
431+
}
432+
412433
this.root._dependencies.push({ plugin: this.realm.plugin, connections: this.connections, deps: dependencies });
413434

414435
if (after) {
415-
this.ext('onPreStart', after, { after: dependencies });
436+
this.ext('onPreStart', after, { after: Object.keys(dependencies) });
416437
}
417438
};
418439

lib/schema.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@ internals.register = Joi.object({
330330
});
331331

332332

333+
internals.semver = Joi.string();
334+
335+
333336
internals.plugin = internals.register.keys({
334337
register: Joi.func().keys({
335338
attributes: Joi.object({
@@ -345,9 +348,17 @@ internals.plugin = internals.register.keys({
345348
.when('pkg.name', { is: Joi.exist(), otherwise: Joi.required() }),
346349
version: Joi.string(),
347350
multiple: Joi.boolean().default(false),
348-
dependencies: Joi.array().items(Joi.string()).single(),
351+
dependencies: [
352+
Joi.array().items(Joi.string()).single(),
353+
Joi.object().pattern(/.+/, internals.semver)
354+
],
349355
connections: Joi.boolean().allow('conditional').default(true),
350-
once: Joi.boolean().valid(true)
356+
once: Joi.boolean().valid(true),
357+
requirements: Joi.object({
358+
hapi: Joi.string(),
359+
node: Joi.string()
360+
})
361+
.default()
351362
})
352363
.required()
353364
.unknown()

lib/server.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const Hoek = require('hoek');
99
const Items = require('items');
1010
const Mimos = require('mimos');
1111
const Podium = require('podium');
12+
const Somever = require('somever');
1213
const Connection = require('./connection');
1314
const Defaults = require('./defaults');
1415
const Ext = require('./ext');
@@ -296,20 +297,38 @@ internals.Server.prototype._validateDeps = function () {
296297
if (dependency.connections) {
297298
for (let j = 0; j < dependency.connections.length; ++j) {
298299
const connection = dependency.connections[j];
299-
for (let k = 0; k < dependency.deps.length; ++k) {
300-
const dep = dependency.deps[k];
300+
const deps = Object.keys(dependency.deps);
301+
for (let k = 0; k < deps.length; ++k) {
302+
const dep = deps[k];
303+
const version = dependency.deps[dep];
304+
301305
if (!connection.registrations[dep]) {
302306
return new Error('Plugin ' + dependency.plugin + ' missing dependency ' + dep + ' in connection: ' + connection.info.uri);
303307
}
308+
309+
if (version !== '*' &&
310+
!Somever.match(connection.registrations[dep].version, version)) {
311+
312+
return new Error('Plugin ' + dependency.plugin + ' requires ' + dep + ' version ' + version + ' but found ' + connection.registrations[dep].version + ' in connection: ' + connection.info.uri);
313+
}
304314
}
305315
}
306316
}
307317
else {
308-
for (let j = 0; j < dependency.deps.length; ++j) {
309-
const dep = dependency.deps[j];
318+
const deps = Object.keys(dependency.deps);
319+
for (let j = 0; j < deps.length; ++j) {
320+
const dep = deps[j];
321+
const version = dependency.deps[dep];
322+
310323
if (!this._registrations[dep]) {
311324
return new Error('Plugin ' + dependency.plugin + ' missing dependency ' + dep);
312325
}
326+
327+
if (version !== '*' &&
328+
!Somever.match(this._registrations[dep].version, version)) {
329+
330+
return new Error('Plugin ' + dependency.plugin + ' requires ' + dep + ' version ' + version + ' but found ' + this._registrations[dep].version);
331+
}
313332
}
314333
}
315334
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"mimos": "3.x.x",
3131
"podium": "1.x.x",
3232
"shot": "3.x.x",
33+
"somever": "1.x.x",
3334
"statehood": "5.x.x",
3435
"subtext": "5.x.x",
3536
"topo": "2.x.x"

0 commit comments

Comments
 (0)