diff --git a/CHANGELOG b/CHANGELOG index bb2aa180ad..0c27e4f686 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -133,6 +133,10 @@ v2.7.0 (XXXX-XX-XX) * fixed handling of optional parameters in Foxx manifest configurations +* added verbose Foxx manifest dependency format + +* added optional Foxx dependencies + * updated chai to 3.0. diff --git a/Documentation/Books/Users/Foxx/Develop/Manifest.mdpp b/Documentation/Books/Users/Foxx/Develop/Manifest.mdpp index 1d03c9c79c..ab57d02650 100644 --- a/Documentation/Books/Users/Foxx/Develop/Manifest.mdpp +++ b/Documentation/Books/Users/Foxx/Develop/Manifest.mdpp @@ -73,7 +73,12 @@ A more complete example for a Manifest file: "dependencies": { "sessions": "sessions@^1.0.0", - "systemUsers": "users" + "systemUsers": "users", + "mailer": { + "name": "mailer-postmark", + "version": "*", + "required": false + } } } ``` @@ -159,20 +164,26 @@ The **dependencies** object maps aliases to Foxx apps: * The **value** is a dependency definition. -The dependency definition can use any of the following formats: +The dependency definition is an object with any of the following properties: + +* **name** (optional): the name of the Foxx app this app depends on. +* **version** (Default: `"*"`): a [semver](http://semver.org) version or version range of the Foxx app this app depends on. +* **required** (Default: `true`): whether the dependency is required for this app to be usable or not. + +Alternatively the dependency definition can be a string using any of the following formats: * `*` will allow using any app to be used to meet the dependency. -* `sessions` or `sessions@*` will match any app with the name `sessions` +* `sessions` or `sessions:*` will match any app with the name `sessions` (such as the *sessions* app in the Foxx application store). -* `sessions@1.0.0` will match the version `1.0.0` of any app with the name `sessions`. +* `sessions:1.0.0` will match the version `1.0.0` of any app with the name `sessions`. Instead of using a specific version number, you can also use any expression supported by the [semver](https://github.com/npm/node-semver) module. -Currently the dependency definitions are not enforced in ArangoDB +Currently the dependency definition names and versions are not enforced in ArangoDB but this may change in a future version. -If an app declares any dependencies, +If an app declares any required dependencies, you need to fulfill its dependencies before it becomes active. In the meantime a fallback application will be mounted that responds to all requests with a HTTP 500 status code indicating a server-side error. diff --git a/js/apps/system/_admin/aardvark/APP/frontend/js/models/foxx.js b/js/apps/system/_admin/aardvark/APP/frontend/js/models/foxx.js index 4dc5e84130..cba474f22a 100644 --- a/js/apps/system/_admin/aardvark/APP/frontend/js/models/foxx.js +++ b/js/apps/system/_admin/aardvark/APP/frontend/js/models/foxx.js @@ -80,7 +80,7 @@ hasUnconfiguredDependencies: function () { return _.any(this.get('deps'), function (dep) { - return dep.current === undefined; + return dep.current === undefined && dep.definition.required !== false; }); }, diff --git a/js/apps/system/_admin/aardvark/APP/frontend/js/views/applicationDetailView.js b/js/apps/system/_admin/aardvark/APP/frontend/js/views/applicationDetailView.js index 527c7bd9f4..d9c49737cf 100644 --- a/js/apps/system/_admin/aardvark/APP/frontend/js/views/applicationDetailView.js +++ b/js/apps/system/_admin/aardvark/APP/frontend/js/views/applicationDetailView.js @@ -350,24 +350,27 @@ var tableContent = _.map(this.model.get('deps'), function(obj, name) { var currentValue = obj.current === undefined ? '' : String(obj.current); var defaultValue = ''; - var description = obj.definition; - var checks = [ - { - rule: Joi.string().optional().allow(''), - msg: 'Has to be a string.' - }, - { + var description = obj.definition.name; + if (obj.definition.version !== '*') { + description += '@' + obj.definition.version; + } + var checks = [{ + rule: Joi.string().optional().allow(''), + msg: 'Has to be a string.' + }]; + if (obj.definition.required) { + checks.push({ rule: Joi.string().required(), msg: 'This value is required.' - } - ]; + }); + } return window.modalView.createTextEntry( 'app_deps_' + name, obj.title, currentValue, description, defaultValue, - true, + obj.definition.required, checks ); }); diff --git a/js/server/modules/org/arangodb/foxx/arangoApp.js b/js/server/modules/org/arangodb/foxx/arangoApp.js index 0b5deffd06..8b4372f935 100644 --- a/js/server/modules/org/arangodb/foxx/arangoApp.js +++ b/js/server/modules/org/arangodb/foxx/arangoApp.js @@ -289,17 +289,19 @@ ArangoApp.prototype.updateDeps = function (deps) { if (!expected[name]) { invalid.push("Unexpected dependency " + name); } - this._options.dependencies[name] = mount; + this._options.dependencies[name] = mount || undefined; }, this); _.each(this._options.dependencies, function (mount, name) { - Object.defineProperty(this._dependencies, name, { - configurable: true, - enumerable: true, - get: function () { - return require("org/arangodb/foxx").requireApp(mount); - } - }); + if (mount) { + Object.defineProperty(this._dependencies, name, { + configurable: true, + enumerable: true, + get: function () { + return require("org/arangodb/foxx").requireApp(mount); + } + }); + } }, this); return invalid; @@ -406,7 +408,7 @@ ArangoApp.prototype.needsConfiguration = function() { return _.any(config, function (cfg) { return cfg.current === undefined && cfg.required !== false; }) || _.any(deps, function (dep) { - return dep.current === undefined; + return dep.current === undefined && dep.definition.required !== false; }); }; diff --git a/js/server/modules/org/arangodb/foxx/manager.js b/js/server/modules/org/arangodb/foxx/manager.js index 2b8f65bbd7..bb6c8d175f 100644 --- a/js/server/modules/org/arangodb/foxx/manager.js +++ b/js/server/modules/org/arangodb/foxx/manager.js @@ -116,7 +116,15 @@ const manifestSchema = { dependencies: ( joi.object().optional() .pattern(RE_EMPTY, joi.forbidden()) - .pattern(RE_NOT_EMPTY, joi.string().required()) + .pattern(RE_NOT_EMPTY, joi.alternatives().try( + joi.string().required(), + joi.object().required() + .keys({ + name: joi.string().default('*'), + version: joi.string().default('*'), + required: joi.boolean().default(true) + }) + )) ), description: joi.string().allow('').default(''), engines: ( @@ -368,19 +376,33 @@ function checkManifest(filename, manifest) { } }); - if (typeof manifest.controllers === 'string') { - manifest.controllers = {'/': manifest.controllers}; - } - - if (typeof manifest.tests === 'string') { - manifest.tests = [manifest.tests]; - } - if (validationErrors.length) { throw new ArangoError({ errorNum: errors.ERROR_INVALID_APPLICATION_MANIFEST.code, errorMessage: validationErrors.join('\n') }); + } else { + if (manifest.dependencies) { + Object.keys(manifest.dependencies).forEach(function (key) { + const dependency = manifest.dependencies[key]; + if (typeof dependency === 'string') { + const tokens = dependency.split(':'); + manifest.dependencies[key] = { + name: tokens[0] || '*', + version: tokens[1] || '*', + required: true + }; + } + }); + } + + if (typeof manifest.controllers === 'string') { + manifest.controllers = {'/': manifest.controllers}; + } + + if (typeof manifest.tests === 'string') { + manifest.tests = [manifest.tests]; + } } }