mirror of https://gitee.com/bigwinds/arangodb
Quality of life improvements in Foxx
This commit is contained in:
parent
e1d384813e
commit
11c7db4375
|
@ -42,7 +42,7 @@ module.exports = class FoxxContext {
|
|||
}
|
||||
|
||||
use(path, router, name) {
|
||||
this.service.router.use(path, router, name);
|
||||
return this.service.router.use(path, router, name);
|
||||
}
|
||||
|
||||
registerType(type, def) {
|
||||
|
@ -85,17 +85,13 @@ module.exports = class FoxxContext {
|
|||
}
|
||||
|
||||
fileName(filename) {
|
||||
return fs.safeJoin(this.basePath, filename);
|
||||
return path.join(this.basePath, filename);
|
||||
}
|
||||
|
||||
file(filename, encoding) {
|
||||
return fs.readFileSync(this.fileName(filename), encoding);
|
||||
}
|
||||
|
||||
path(name) {
|
||||
return path.join(this.basePath, name);
|
||||
}
|
||||
|
||||
collectionName(name) {
|
||||
const fqn = (
|
||||
this.collectionPrefix + name
|
||||
|
@ -116,7 +112,7 @@ module.exports = class FoxxContext {
|
|||
}
|
||||
|
||||
get baseUrl() {
|
||||
return `/_db/${encodeURIComponent(internal.db._name())}/${this.service.mount.slice(1)}`;
|
||||
return `/_db/${encodeURIComponent(internal.db._name())}${this.service.mount}`;
|
||||
}
|
||||
|
||||
get collectionPrefix() {
|
||||
|
@ -139,10 +135,6 @@ module.exports = class FoxxContext {
|
|||
return !this.isDevelopment;
|
||||
}
|
||||
|
||||
get options() {
|
||||
return this.service.options;
|
||||
}
|
||||
|
||||
get configuration() {
|
||||
return this.service.configuration;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/*jshint esnext: true */
|
||||
/*global ArangoServerState, ArangoClusterInfo, ArangoClusterComm */
|
||||
'use strict';
|
||||
|
||||
|
@ -35,6 +34,7 @@ const fs = require('fs');
|
|||
const joi = require('joi');
|
||||
const util = require('util');
|
||||
const semver = require('semver');
|
||||
const dd = require('dedent');
|
||||
const il = require('@arangodb/util').inline;
|
||||
const utils = require('@arangodb/foxx/manager-utils');
|
||||
const store = require('@arangodb/foxx/store');
|
||||
|
@ -93,7 +93,7 @@ const manifestSchema = {
|
|||
),
|
||||
|
||||
lib: joi.string().default('.'),
|
||||
main: joi.string().optional(),
|
||||
main: joi.string().default('index.js'),
|
||||
|
||||
configuration: (
|
||||
joi.object().optional()
|
||||
|
@ -126,6 +126,17 @@ const manifestSchema = {
|
|||
))
|
||||
),
|
||||
|
||||
provides: (
|
||||
joi.alternatives().try(
|
||||
joi.string().optional(),
|
||||
joi.array().optional()
|
||||
.items(joi.string().required()),
|
||||
joi.object().optional()
|
||||
.pattern(RE_EMPTY, joi.forbidden())
|
||||
.pattern(RE_NOT_EMPTY, joi.string().required())
|
||||
)
|
||||
),
|
||||
|
||||
files: (
|
||||
joi.object().optional()
|
||||
.pattern(RE_EMPTY, joi.forbidden())
|
||||
|
@ -215,7 +226,10 @@ function lookupService(mount) {
|
|||
}
|
||||
throw new ArangoError({
|
||||
errorNum: errors.ERROR_APP_NOT_FOUND.code,
|
||||
errorMessage: 'Service not found at: ' + mount
|
||||
errorMessage: dd`
|
||||
${errors.ERROR_APP_NOT_FOUND.message}
|
||||
Service not found at "${mount}".
|
||||
`
|
||||
});
|
||||
}
|
||||
return serviceCache[dbname][mount];
|
||||
|
@ -288,86 +302,136 @@ function checkMountedSystemService(dbname) {
|
|||
|
||||
function checkManifest(filename, inputManifest, mount) {
|
||||
const serverVersion = plainServerVersion();
|
||||
const validationErrors = [];
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
const notices = [];
|
||||
const manifest = {};
|
||||
let legacy = false;
|
||||
|
||||
Object.keys(manifestSchema).forEach(function (key) {
|
||||
let schema = manifestSchema[key];
|
||||
let value = manifest[key];
|
||||
let result = joi.validate(value, schema);
|
||||
if (result.value !== undefined) {
|
||||
const schema = manifestSchema[key];
|
||||
const value = inputManifest[key];
|
||||
const result = joi.validate(value, schema);
|
||||
if (result.error) {
|
||||
const error = result.error.message.replace(/^"value"/, `Field "${key}"`);
|
||||
errors.push(`${error} (was "${util.format(value)}").`);
|
||||
} else {
|
||||
manifest[key] = result.value;
|
||||
}
|
||||
if (result.error) {
|
||||
let error = result.error.message.replace(/^"value"/, `"${key}"`);
|
||||
let message = `Manifest "${mount}": attribute ${error} (was "${util.format(value)}").`;
|
||||
validationErrors.push(message);
|
||||
console.error(message);
|
||||
}
|
||||
});
|
||||
|
||||
if (!manifest.engines && manifest.engine) {
|
||||
console.warn(il`
|
||||
Found unexpected "engine" field in manifest "${filename}" for service "${mount}".
|
||||
Did you mean "engines"?
|
||||
`);
|
||||
}
|
||||
|
||||
if (manifest.engines && manifest.engines.arangodb) {
|
||||
if (semver.gtr('3.0.0', manifest.engines.arangodb)) {
|
||||
legacy = true;
|
||||
console.warn(
|
||||
`Manifest "${filename}" for service "${mount}":`
|
||||
+ ` Service expects version ${manifest.engines.arangodb}`
|
||||
+ ` and will run in legacy compatibility mode.`
|
||||
);
|
||||
notices.push(il`
|
||||
Service expects version ${manifest.engines.arangodb}
|
||||
and will run in legacy compatibility mode.
|
||||
`);
|
||||
} else if (!semver.satisfies(serverVersion, manifest.engines.arangodb)) {
|
||||
console.warn(
|
||||
`Manifest "${filename}" for service "${mount}":`
|
||||
+ ` ArangoDB version ${serverVersion} probably not compatible`
|
||||
+ ` with expected version ${manifest.engines.arangodb}.`
|
||||
);
|
||||
warnings.push(il`
|
||||
ArangoDB version ${serverVersion} probably not compatible
|
||||
with expected version ${manifest.engines.arangodb}.
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(manifest).forEach(function (key) {
|
||||
if (!manifestSchema[key] && (!legacy || legacyManifestFields.indexOf(key) === -1)) {
|
||||
console.warn(`Manifest "${filename}" for service "${mount}": unknown attribute "${key}"`);
|
||||
for (const key of Object.keys(inputManifest)) {
|
||||
if (manifestSchema[key]) {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
manifest[key] = inputManifest[key];
|
||||
if (key === 'engine' && !inputManifest.engines) {
|
||||
warnings.push('Unknown field "engine". Did you mean "engines"?');
|
||||
} else if (!legacy || legacyManifestFields.indexOf(key) === -1) {
|
||||
warnings.push(`Unknown field "${key}".`);
|
||||
}
|
||||
}
|
||||
|
||||
if (validationErrors.length) {
|
||||
if (manifest.version && !semver.valid(manifest.version)) {
|
||||
warnings.push(`Not a valid version: "${manifest.verison}"`);
|
||||
}
|
||||
|
||||
if (manifest.provides) {
|
||||
if (typeof manifest.provides === 'string') {
|
||||
manifest.provides = [manifest.provides];
|
||||
}
|
||||
if (Array.isArray(manifest.provides)) {
|
||||
const provides = manifest.provides;
|
||||
manifest.provides = {};
|
||||
for (const provided of provides) {
|
||||
const tokens = provided.split(':');
|
||||
manifest.provides[tokens[0]] = tokens[1] || '*';
|
||||
}
|
||||
}
|
||||
for (const name of Object.keys(manifest.provides)) {
|
||||
const version = manifest.provides[name];
|
||||
if (!semver.valid(version)) {
|
||||
errors.push(`Provided "${name}" invalid version: "${version}".`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.dependencies) {
|
||||
for (const key of Object.keys(manifest.dependencies)) {
|
||||
if (typeof manifest.dependencies[key] === 'string') {
|
||||
const tokens = manifest.dependencies[key].split(':');
|
||||
manifest.dependencies[key] = {
|
||||
name: tokens[0] || '*',
|
||||
version: tokens[1] || '*',
|
||||
required: true
|
||||
};
|
||||
}
|
||||
const version = manifest.dependencies[key].version;
|
||||
if (!semver.validRange(version)) {
|
||||
errors.push(`Dependency "${key}" invalid version: "${version}".`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (notices.length) {
|
||||
console.infoLines(dd`
|
||||
Manifest for service at "${mount}":
|
||||
${notices.join('\n')}
|
||||
`);
|
||||
}
|
||||
|
||||
if (warnings.length) {
|
||||
console.warnLines(dd`
|
||||
Manifest for service at "${mount}":
|
||||
${warnings.join('\n')}
|
||||
`);
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
console.errorLines(dd`
|
||||
Manifest for service at "${mount}":
|
||||
${errors.join('\n')}
|
||||
`);
|
||||
throw new ArangoError({
|
||||
errorNum: errors.ERROR_INVALID_APPLICATION_MANIFEST.code,
|
||||
errorMessage: validationErrors.join('\n')
|
||||
errorMessage: dd`
|
||||
${errors.ERROR_INVALID_APPLICATION_MANIFEST.message}
|
||||
Manifest for service at "${mount}":
|
||||
${errors.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 (legacy && manifest.defaultDocument === undefined) {
|
||||
if (legacy) {
|
||||
if (manifest.defaultDocument === undefined) {
|
||||
manifest.defaultDocument = 'index.html';
|
||||
}
|
||||
|
||||
if (typeof manifest.controllers === 'string') {
|
||||
manifest.controllers = {'/': manifest.controllers};
|
||||
}
|
||||
|
||||
if (typeof manifest.tests === 'string') {
|
||||
manifest.tests = [manifest.tests];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof manifest.tests === 'string') {
|
||||
manifest.tests = [manifest.tests];
|
||||
}
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
|
||||
|
@ -378,34 +442,37 @@ function checkManifest(filename, inputManifest, mount) {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function validateManifestFile(filename, mount) {
|
||||
var mf, msg;
|
||||
let mf;
|
||||
if (!fs.exists(filename)) {
|
||||
msg = `Cannot find manifest file "${filename}"`;
|
||||
throwFileNotFound(msg);
|
||||
throwFileNotFound(`Cannot find manifest file "${filename}"`);
|
||||
}
|
||||
try {
|
||||
mf = JSON.parse(fs.read(filename));
|
||||
} catch (e) {
|
||||
const error = new ArangoError({
|
||||
errorNum: errors.ERROR_MALFORMED_MANIFEST_FILE.code,
|
||||
errorMessage: errors.ERROR_MALFORMED_MANIFEST_FILE.message
|
||||
+ '\nFile: ' + filename
|
||||
+ '\nCause: ' + e.stack
|
||||
});
|
||||
error.cause = e;
|
||||
throw error;
|
||||
throw Object.assign(
|
||||
new ArangoError({
|
||||
errorNum: errors.ERROR_MALFORMED_MANIFEST_FILE.code,
|
||||
errorMessage: dd`
|
||||
${errors.ERROR_MALFORMED_MANIFEST_FILE.message}
|
||||
File: ${filename}
|
||||
Cause: ${e.stack}
|
||||
`
|
||||
}), {cause: e}
|
||||
);
|
||||
}
|
||||
try {
|
||||
checkManifest(filename, mf);
|
||||
mf = checkManifest(filename, mf, mount);
|
||||
} catch (e) {
|
||||
const error = new ArangoError({
|
||||
errorNum: errors.ERROR_INVALID_APPLICATION_MANIFEST.code,
|
||||
errorMessage: errors.ERROR_INVALID_APPLICATION_MANIFEST.message
|
||||
+ '\nFile: ' + filename
|
||||
+ '\nCause: ' + e.stack
|
||||
});
|
||||
error.cause = e;
|
||||
throw error;
|
||||
throw Object.assign(
|
||||
new ArangoError({
|
||||
errorNum: errors.ERROR_INVALID_APPLICATION_MANIFEST.code,
|
||||
errorMessage: dd`
|
||||
${errors.ERROR_INVALID_APPLICATION_MANIFEST.message}
|
||||
File: ${filename}
|
||||
Cause: ${e.stack}
|
||||
`
|
||||
}), {cause: e}
|
||||
);
|
||||
}
|
||||
return mf;
|
||||
}
|
||||
|
@ -559,10 +626,6 @@ function uploadToPeerCoordinators(serviceInfo, coordinators) {
|
|||
function installServiceFromGenerator(targetPath, options) {
|
||||
var invalidOptions = [];
|
||||
// Set default values:
|
||||
options.name = options.name || 'MyService';
|
||||
options.author = options.author || 'Author';
|
||||
options.description = options.description || '';
|
||||
options.license = options.license || 'Apache 2';
|
||||
options.documentCollections = options.documentCollections || [];
|
||||
options.edgeCollections = options.edgeCollections || [];
|
||||
if (typeof options.name !== 'string') {
|
||||
|
@ -586,8 +649,10 @@ function installServiceFromGenerator(targetPath, options) {
|
|||
if (invalidOptions.length > 0) {
|
||||
throw new ArangoError({
|
||||
errorNum: errors.ERROR_INVALID_FOXX_OPTIONS.code,
|
||||
errorMessage: errors.ERROR_INVALID_FOXX_OPTIONS.message
|
||||
+ '\nOptions: ' + JSON.stringify(invalidOptions, undefined, 2)
|
||||
errorMessage: dd`
|
||||
${errors.ERROR_INVALID_FOXX_OPTIONS.message}
|
||||
Options: ${JSON.stringify(invalidOptions, undefined, 2)}
|
||||
`
|
||||
});
|
||||
}
|
||||
var cfg = generator.generate(options);
|
||||
|
|
|
@ -45,6 +45,17 @@ const APP_PATH = internal.appPath ? path.resolve(internal.appPath) : undefined;
|
|||
const STARTUP_PATH = internal.startupPath ? path.resolve(internal.startupPath) : undefined;
|
||||
const DEV_APP_PATH = internal.devAppPath ? path.resolve(internal.devAppPath) : undefined;
|
||||
|
||||
const LEGACY_ALIASES = [
|
||||
['@arangodb/foxx/authentication', '@arangodb/foxx/legacy/authentication'],
|
||||
['@arangodb/foxx/controller', '@arangodb/foxx/legacy/controller'],
|
||||
['@arangodb/foxx/model', '@arangodb/foxx/legacy/model'],
|
||||
['@arangodb/foxx/query', '@arangodb/foxx/legacy/query'],
|
||||
['@arangodb/foxx/repository', '@arangodb/foxx/legacy/repository'],
|
||||
['@arangodb/foxx/schema', '@arangodb/foxx/legacy/schema'],
|
||||
['@arangodb/foxx/sessions', '@arangodb/foxx/legacy/sessions'],
|
||||
['@arangodb/foxx/template_middleware', '@arangodb/foxx/legacy/template_middleware'],
|
||||
['@arangodb/foxx', '@arangodb/foxx/legacy']
|
||||
];
|
||||
|
||||
module.exports = class FoxxService {
|
||||
constructor(data) {
|
||||
|
@ -97,8 +108,8 @@ module.exports = class FoxxService {
|
|||
this.legacy = range ? semver.gtr('3.0.0', range) : false;
|
||||
if (this.legacy) {
|
||||
console.debugLines(dd`
|
||||
Running "${this.mount}" in 2.x compatibility mode.
|
||||
Requested version ${range} is lower than 3.0.0.
|
||||
Service "${this.mount}" is running in legacy compatibility mode.
|
||||
Requested version "${range}" is lower than "3.0.0".
|
||||
`);
|
||||
}
|
||||
|
||||
|
@ -380,17 +391,7 @@ module.exports = class FoxxService {
|
|||
this.main.filename = path.resolve(moduleRoot, '.foxx');
|
||||
this.main[$_MODULE_ROOT] = moduleRoot;
|
||||
this.main[$_MODULE_CONTEXT].console = foxxConsole;
|
||||
this.main.require.aliases = new Map(this.legacy ? [
|
||||
['@arangodb/foxx/authentication', '@arangodb/foxx/legacy/authentication'],
|
||||
['@arangodb/foxx/controller', '@arangodb/foxx/legacy/controller'],
|
||||
['@arangodb/foxx/model', '@arangodb/foxx/legacy/model'],
|
||||
['@arangodb/foxx/query', '@arangodb/foxx/legacy/query'],
|
||||
['@arangodb/foxx/repository', '@arangodb/foxx/legacy/repository'],
|
||||
['@arangodb/foxx/schema', '@arangodb/foxx/legacy/schema'],
|
||||
['@arangodb/foxx/sessions', '@arangodb/foxx/legacy/sessions'],
|
||||
['@arangodb/foxx/template_middleware', '@arangodb/foxx/legacy/template_middleware'],
|
||||
['@arangodb/foxx', '@arangodb/foxx/legacy']
|
||||
] : []);
|
||||
this.main.require.aliases = new Map(this.legacy ? LEGACY_ALIASES : []);
|
||||
this.main.require.cache = this.requireCache;
|
||||
this.main.context = new FoxxContext(this);
|
||||
this.router = new Router();
|
||||
|
@ -401,9 +402,15 @@ module.exports = class FoxxService {
|
|||
};
|
||||
|
||||
if (this.legacy) {
|
||||
this.main.context.path = this.main.context.fileName;
|
||||
this.main.context.fileName = (filename) => fs.safeJoin(this.basePath, filename);
|
||||
this.main.context.foxxFilename = this.main.context.fileName;
|
||||
this.main.context.version = this.version = this.manifest.version;
|
||||
this.main.context.name = this.name = this.manifest.name;
|
||||
this.main.context.options = this.options;
|
||||
this.main.context.use = undefined;
|
||||
this.main.context.apiDocumentation = undefined;
|
||||
this.main.context.registerType = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,19 +465,19 @@ module.exports = class FoxxService {
|
|||
|
||||
function createConfiguration(definitions) {
|
||||
const config = {};
|
||||
Object.keys(definitions).forEach(function (name) {
|
||||
for (const name of Object.keys(definitions)) {
|
||||
const def = definitions[name];
|
||||
if (def.default !== undefined) {
|
||||
config[name] = def.default;
|
||||
}
|
||||
});
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
function createDependencies(definitions, options) {
|
||||
const deps = {};
|
||||
Object.keys(definitions).forEach(function (name) {
|
||||
for (const name of Object.keys(definitions)) {
|
||||
Object.defineProperty(deps, name, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
|
@ -483,6 +490,6 @@ function createDependencies(definitions, options) {
|
|||
return FoxxManager.requireService('/' + mount.replace(/(^\/+|\/+$)/, ''));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return deps;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue