diff --git a/package.json b/package.json index 584d472..0e549fd 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/nxus/core", "engines": { - "node": "~6" + "node": "~6,~8,~10~,~12" }, "dependencies": { "async": "^2.0.1", diff --git a/src/Application.js b/src/Application.js index ed878be..b1cef17 100755 --- a/src/Application.js +++ b/src/Application.js @@ -367,17 +367,19 @@ export default class Application extends Dispatcher { * @return {[type]} */ _bootPlugin(plugin) { - var name = plugin._pluginInfo.name - let path = plugin._pluginInfo.modulePath + let pluginInfo = plugin._pluginInfo + + if(plugin.default) + plugin = plugin.default + + let name = (plugin._moduleName && plugin._moduleName()) || pluginInfo.name let pluginInstance = null let promise = null - //if (this.config.debug) console.log(' ------- ', plugin) + if (this._pluginInstances[name] !== undefined) { this.log.error('Duplicate module found', name) process.exit() } - if(plugin.default) - plugin = plugin.default if(plugin.__appRef && plugin.__appRef() !== this) { this.log.error('Separate Nxus Core module detected in', name) @@ -393,7 +395,7 @@ export default class Application extends Dispatcher { this._pluginInstances[name] = pluginInstance promise = Promise.resolve(pluginInstance) } catch(e) { - this.log.error('Error booting module '+name+' from '+path, e) + this.log.error('Error booting module '+name+' from '+pluginInfo.modulePath, e) this.log.error(e.stack) process.exit() } diff --git a/src/NxusModule.js b/src/NxusModule.js index 8d395b8..f0b3d4c 100644 --- a/src/NxusModule.js +++ b/src/NxusModule.js @@ -1,17 +1,51 @@ import morph from 'morph' import path from 'path' +import fs from 'fs' import stackTrace from 'stack-trace' import ModuleProxy from './ModuleProxy' import {application} from './Application' import Logger from './Logger' import deepExtend from 'deep-extend' -function __dirName(constructorName) { +function _filenameOf(construct) { + for (let [k,v] of Object.entries(require.cache)) { + let exports = v.exports && v.exports.default ? v.exports : {default: v.exports} + for (let [,ex] of Object.entries(exports)) { + if (ex == construct) { + return v.filename + } + } + } for (let site of stackTrace.get()) { - if(site.getFunctionName() == constructorName) { - return path.dirname(site.getFileName()) + if(site.getFunctionName() == construct.name) { + return site.getFileName() + } + } +} + +const EXCLUDE_DIRNAMES = ['src', 'lib', 'test', 'modules'] + +function _modulePrefix(filename) { + let dirname = path.dirname(filename) + let dirs = dirname.split(path.sep) + let isIndex = path.basename(filename) == 'index.js' + let result = [] + let root + while (!root && dirs.length) { + let testFile = dirs.concat(['package.json']).join(path.sep) + if (fs.existsSync(testFile)) { + root = true + } else { + let p = dirs.pop() + if (!EXCLUDE_DIRNAMES.includes(p)) { + result.unshift(p) + } } } + if (isIndex) { + result.pop() + } + return result.join('/') } /** @@ -23,6 +57,7 @@ function __dirName(constructorName) { class NxusModule { constructor(app) { + this._dirName = path.dirname(_filenameOf(this.constructor)) this.__name = this.constructor._moduleName() this.__config_name = this.constructor._configName() this.log = Logger(this.__name) @@ -32,15 +67,18 @@ class NxusModule { application.setUserConfig(this.__config_name, userConfig) } - this._dirName = __dirName(this.constructor.name) - this.__proxy = application.get(this.__name) this.__proxy.use(this) } get config() { let _defaultConfig = this._defaultConfig() || {} - if(!this._config) this._config = Object.assign({}, deepExtend(_defaultConfig, application.config[this.__config_name])) + if (!this._config) { + this._config = Object.assign( + {}, + deepExtend(_defaultConfig, application.config[this.__config_name]) + ) + } return this._config } @@ -61,15 +99,23 @@ class NxusModule { } static _configName() { - return morph.toSnake(this.name) + return morph.toSnake(this._moduleName()) } - static _moduleName() { - return morph.toDashed(this.name) + static _moduleName(filename) { + if (filename === undefined) { + filename = _filenameOf(this) + } + let prefix = _modulePrefix(filename) + // this logic of ignoring class name for modules + // is in part to match src/PluginManager:_loadModulesFromDirectory + let name = prefix ? prefix + "/" + this.name : this.name + return morph.toDashed(name) } static getProxy() { - return application.get(this._moduleName()) + // force to caller file, assuming getProxy is called in same file as class definition + return application.get(this._moduleName(stackTrace.get()[1].getFileName())) } deregister() { diff --git a/src/PluginManager.js b/src/PluginManager.js index 5fdf199..20ae86a 100644 --- a/src/PluginManager.js +++ b/src/PluginManager.js @@ -89,7 +89,8 @@ class PluginManager { * @param {[type]} matches [description] * @return {[type]} [description] */ - _loadModulesFromDirectory(dir, isLocal, matches) { + _loadModulesFromDirectory(dir, isLocal, matches, prefix='') { + if (!fs.existsSync(dir)) return var moduleDirs = fs.readdirSync(dir) @@ -97,17 +98,18 @@ class PluginManager { moduleDirs.forEach((name) => { if(matches) name = multimatch([name], matches)[0] if(!name || (name && name[0] == ".")) return - this.app.log.debug('Loading module', name, isLocal ? "(app)": "(dep)") + let modName = prefix+name + this.app.log.debug('Loading module', modName, isLocal ? "(app)": "(dep)") let modulePath = path.resolve(path.join(dir, name)) try { var pkg = require(modulePath) - pkg._pluginInfo = {name, modulePath, isLocal} + pkg._pluginInfo = {name: modName, modulePath, isLocal} this.packages.push(pkg) + // this module prefix naming matches src/NxusModule:_moduleName we hope + let newPrefix = modName+"/" // Recurse for module modules - this._loadModulesFromDirectory(path.join(dir, name, 'modules'), isLocal) - this._loadModulesFromDirectory(path.join(dir, name, 'lib', 'modules'), isLocal) - //if(fs.existsSync(dir + "/" + name + "/node_modules")) - //this._loadModulesFromDirectory(dir + "/" + name + "/node_modules", matches) + this._loadModulesFromDirectory(path.join(dir, name, 'modules'), isLocal, null, newPrefix) + this._loadModulesFromDirectory(path.join(dir, name, 'lib', 'modules'), isLocal, null, newPrefix) } catch (e) { // kludgy message text match to distinguish subsidiary modules from primary if ((e.code === 'MODULE_NOT_FOUND') && e.message.includes(`'${modulePath}'`)) diff --git a/test/lib/NxusModule.js b/test/lib/NxusModule.js index 2e9c588..0e44a2f 100644 --- a/test/lib/NxusModule.js +++ b/test/lib/NxusModule.js @@ -8,6 +8,9 @@ import {NxusModule, application} from '../../lib/' +import OneModule from './modules/one' +import NestedSub from './modules/one/modules/sub' + class SubModule extends NxusModule { _defaultConfig() { @@ -40,6 +43,8 @@ describe("NxusModule", () => { it("should set itself in app modules with the config name (dashed)", () => { application._moduleProxies.should.have.property('sub-module') + // NestedSub + application._moduleProxies.should.have.property('one/nested-sub') }) it("should set defaultConfig", () => { @@ -78,4 +83,23 @@ describe("NxusModule", () => { }) }) + describe("_moduleName", () => { + it("should be the module name dashed", () => { + // SubModule isn't exported so not available + //SubModule._moduleName().should.equal('sub-module') + instance.__name.should.equal('sub-module') + }) + it("should not duplicate module name when index module", () => { + OneModule._moduleName().should.equal('one') + }) + it("should include nested directories and use module class name", () => { + NestedSub._moduleName().should.equal('one/nested-sub') + }) + it("should include nested directories for arbitrary internal modules", () => { + let n = new NestedSub() + n.controller.__name.should.equal('one/sub/controllers/nested-controller') + application._moduleProxies.should.have.property('one/sub/controllers/nested-controller') + }) + }) + }) diff --git a/test/lib/modules/one/index.js b/test/lib/modules/one/index.js new file mode 100644 index 0000000..ea317d0 --- /dev/null +++ b/test/lib/modules/one/index.js @@ -0,0 +1,5 @@ +import {NxusModule} from '../../../../lib' + +export default class One extends NxusModule { + +} diff --git a/test/lib/modules/one/modules/sub/controllers/nested.js b/test/lib/modules/one/modules/sub/controllers/nested.js new file mode 100644 index 0000000..f2e67a9 --- /dev/null +++ b/test/lib/modules/one/modules/sub/controllers/nested.js @@ -0,0 +1,5 @@ +import {NxusModule} from '../../../../../../../lib' + +export default class NestedController extends NxusModule { + +} diff --git a/test/lib/modules/one/modules/sub/index.js b/test/lib/modules/one/modules/sub/index.js new file mode 100644 index 0000000..908f590 --- /dev/null +++ b/test/lib/modules/one/modules/sub/index.js @@ -0,0 +1,14 @@ +import {NxusModule} from '../../../../../../lib' + +import NestedController from './controllers/nested' + +class NestedSub extends NxusModule { + constructor() { + super() + this.controller = new NestedController() + } + +} + +let nestedSub = NestedSub.getProxy() +export {NestedSub as default, nestedSub}