UNPKG

ravel

Version:

Ravel Rapid Application Development Framework

114 lines (95 loc) 3.99 kB
'use strict'; 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); } }; };