@boost/core
Version:
Robust pipeline for creating dev tools that separate logic into routines and tasks.
146 lines (145 loc) • 5.79 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const chalk_1 = __importDefault(require("chalk"));
const common_1 = require("@boost/common");
const debug_1 = require("@boost/debug");
const formatModuleName_1 = __importDefault(require("./helpers/formatModuleName"));
class ModuleLoader {
constructor(tool, typeName, contract = null, scopes = []) {
this.contract = null;
this.contract = contract;
this.debug = debug_1.createDebugger(`${typeName}-loader`);
this.scopes = scopes;
this.tool = tool;
this.typeName = typeName;
}
/**
* Import a class definition from a Node module and instantiate the class
* with the provided options object.
*/
importModule(name, args = []) {
const { typeName } = this;
const { appName, scoped } = this.tool.options;
// Determine modules to attempt to load
const modulesToAttempt = [];
let isFilePath = false;
let importedModule = null;
let moduleName;
// File path
if (name.match(/^\.|\/|\\|[A-Z]:/u)) {
this.debug('Locating %s from path %s', typeName, chalk_1.default.cyan(name));
modulesToAttempt.push(path_1.default.normalize(name));
isFilePath = true;
// Module name
}
else {
this.debug('Locating %s module %s', typeName, chalk_1.default.yellow(name));
if (scoped) {
modulesToAttempt.push(formatModuleName_1.default(appName, typeName, name, true));
}
modulesToAttempt.push(formatModuleName_1.default(appName, typeName, name));
// Additional scopes to load
this.scopes.forEach(otherScope => {
modulesToAttempt.push(formatModuleName_1.default(otherScope, typeName, name, true), formatModuleName_1.default(otherScope, typeName, name));
});
this.debug('Resolving in order: %s', modulesToAttempt.join(', '));
}
modulesToAttempt.some(modName => {
try {
importedModule = common_1.requireModule(modName);
moduleName = modName;
return true;
}
catch (error) {
this.debug('Failed to import module: %s', error.message);
return false;
}
});
if (!importedModule || !moduleName) {
throw new Error(this.tool.msg('errors:moduleImportFailed', {
modules: modulesToAttempt.join(', '),
typeName,
}));
}
if (!this.contract) {
return importedModule;
}
// An instance was returned instead of the class definition
if (common_1.instanceOf(importedModule, this.contract)) {
throw new TypeError(this.tool.msg('errors:moduleClassInstanceExported', {
appName,
moduleName,
typeName,
}));
}
else if (typeof importedModule !== 'function') {
throw new TypeError(this.tool.msg('errors:moduleClassDefRequired', { moduleName, typeName }));
}
const ModuleClass = importedModule;
const module = new ModuleClass(...args);
if (!common_1.instanceOf(module, this.contract)) {
throw new TypeError(this.tool.msg('errors:moduleExportInvalid', {
moduleName,
typeName,
}));
}
if (isFilePath) {
this.debug('Found with file path %s', chalk_1.default.cyan(moduleName));
}
else {
this.debug('Found with module %s', chalk_1.default.yellow(moduleName));
module.name = name;
module.moduleName = moduleName;
}
return module;
}
/**
* If loading from an object, extract the module name and use the remaining object
* as options for the class instance.
*/
importModuleFromOptions(baseOptions, args = []) {
const { typeName } = this;
const options = Object.assign({}, baseOptions);
const module = options[typeName];
delete options[typeName];
if (!module || typeof module !== 'string') {
throw new TypeError(this.tool.msg('errors:moduleOptionMissingKey', { typeName }));
}
const nextArgs = [...args];
if (nextArgs.length === 0) {
nextArgs.push(options);
}
else if (common_1.isObject(nextArgs[0])) {
nextArgs[0] = Object.assign({}, nextArgs[0], options);
}
return this.importModule(module, nextArgs);
}
/**
* Load and or instantiate a module for the `typeName` configuration property.
* If a class instance, use directly. If a string, attempt to load and
* instantiate from a module. If an object, extract the name and run the previous.
*/
loadModule(module, args = []) {
if (this.contract && common_1.instanceOf(module, this.contract)) {
return module;
}
else if (typeof module === 'string') {
return this.importModule(module, args);
}
else if (common_1.isObject(module)) {
return this.importModuleFromOptions(module, args);
}
throw new TypeError(this.tool.msg('errors:moduleTypeInvalid', { typeName: this.typeName }));
}
/**
* Load multiple modules.
*/
loadModules(modules = [], args = []) {
return modules.map(module => this.loadModule(module, args));
}
}
exports.default = ModuleLoader;