ravel
Version:
Ravel Rapid Application Development Framework
114 lines (95 loc) • 3.99 kB
JavaScript
;
const recursive = require('fs-readdir-recursive');
const fs = require('fs');
const upath = require('upath');
const symbols = require('./symbols');
const Metadata = require('../util/meta');
/*!
* Simple, recursive directory scanning for `Module`s,
* `Resource`s and `Routes`.
* @external Ravel
*/
module.exports = function (Ravel) {
/**
* Recursively register `Module`s, `Resource`s and `Routes` with Ravel.
* Useful for [testing](#testing-ravel-applications).
*
* @param {...Function} classes - Class prototypes to load.
* @example
* const inject = require('ravel').inject;
* const Module = require('ravel').Module;
*
* // @Module('test')
* class MyModule {
* aMethod () {
* //...
* }
* }
* app.load(MyModule);
* await app.init();
*/
Ravel.prototype.load = function (...classes) {
for (const moduleClass of classes) {
// Determine module role
const role = Metadata.getClassMetaValue(moduleClass.prototype, '@role', 'type');
switch (role) {
case 'Module':
this[symbols.loadModule](moduleClass);
break;
case 'Resource':
this[symbols.loadResource](moduleClass);
break;
case 'Routes':
this[symbols.loadRoutes](moduleClass);
break;
default:
throw new this.$err.IllegalValue(`Class ${moduleClass.name} must be decorated with @Module, @Resource or @Routes`);
}
}
};
/**
* Recursively register `Module`s, `Resource`s and `Routes` with Ravel,
* automatically naming them (if necessary) based on their relative path.
* All files within this directory (recursively) should export a single
* class which is decorated with `@Module`, `@Resource` or `@Routes`.
*
* @param {...string} basePaths - The directories to recursively scan for .js files.
* These files should export a single class which is decorated
* with `@Module`, `@Resource` or `@Routes`.
* @example
* // recursively load all `Module`s, `Resource`s and `Routes` in a directory
* app.scan('./modules');
* // a Module 'modules/test.js' in ./modules can be injected as `@inject('test')`
* // a Module 'modules/stuff/test.js' in ./modules can be injected as `@inject('stuff.test')`
*/
Ravel.prototype.scan = function (...basePaths) {
for (const basePath of basePaths) {
const absPath = upath.toUnix(upath.isAbsolute(basePath) ? basePath : upath.toUnix(upath.posix.join(this.cwd, basePath)));
try {
if (!fs.lstatSync(absPath).isDirectory()) {
throw new this.$err.IllegalValue('Base module scanning path \'' + absPath + '\' is not a directory.');
}
} catch (err) {
throw new this.$err.IllegalValue('Base module scanning path \'' + absPath + '\' is not a directory, cannot be accessed or does not exist.');
}
const classes = recursive(absPath).filter(f => upath.extname(f) === '.js').map(f => {
const fullPath = upath.toUnix(upath.posix.join(absPath, f));
try {
const moduleClass = require(fullPath);
const role = Metadata.getClassMetaValue(moduleClass.prototype, '@role', 'type');
if (role === 'Module') {
// derive module name from filename, using subdirectories of basePath
// as namespacing (unless manually specified)
const derivedName = upath.trimExt(upath.toUnix(upath.posix.normalize(f))).split('/').join('.');
const name = Metadata.getClassMetaValue(moduleClass.prototype, '@role', 'name', derivedName);
Metadata.putClassMeta(moduleClass.prototype, '@role', 'name', name);
}
return moduleClass;
} catch (err) {
throw new this.$err.IllegalValue(`Unable to load file ${fullPath} as a @Module, @Resource or @Routes. File must export a class.`);
}
});
this.load(...classes);
}
};
};