deep-package-manager
Version:
DEEP Package Manager
341 lines (275 loc) • 8.31 kB
JavaScript
/**
* Created by AlexanderC on 2/4/16.
*/
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DependenciesResolver = undefined;
var _WaitFor = require('../../Helpers/WaitFor');
var _MissingModuleDBException = require('./Exception/MissingModuleDBException');
var _SemVerStrategy = require('./Strategy/SemVerStrategy');
var _NoVersionMatchingException = require('./Exception/NoVersionMatchingException');
var _Context = require('../Context/Context');
class DependenciesResolver {
/**
* @param {ModuleConfig|*} baseModuleConfig
* @param {Storage|*} storage
* @param {SemVerStrategy|AbstractStrategy|null|*} strategy
*/
constructor(baseModuleConfig, storage, strategy = null) {
this._baseModuleConfig = baseModuleConfig;
this._storage = storage;
this._strategy = strategy || new _SemVerStrategy.SemVerStrategy();
this._dbCache = {};
this._configCache = {};
// circular deps avoidance stack
this._resolveUqStack = [];
}
/**
* @param {Storage|*} storage
* @param {SemVerStrategy|AbstractStrategy|null|*} strategy
* @param {Context} moduleContext
* @param {Function} cb
*
* @todo: Find out an elegant way to define optional strategy
*/
static createUsingRawVersion(storage, strategy /* = null */, moduleContext, cb) {
console.debug(`Looking for '${moduleContext.name}' module DB`);
storage.moduleDbExists(moduleContext, (error, state) => {
if (error) {
cb(error, null);
} else if (!state) {
cb(new _MissingModuleDBException.MissingModuleDBException(moduleContext), null);
} else {
console.debug(`Fetching '${moduleContext.name}' module DB`);
storage.readModuleDb(moduleContext, (error, moduleDB) => {
if (error) {
cb(error, null);
return;
}
strategy = strategy || new _SemVerStrategy.SemVerStrategy();
let matchedVersion = strategy.resolve(moduleDB, moduleContext.version);
if (!matchedVersion) {
cb(new _NoVersionMatchingException.NoVersionMatchingException(moduleContext, moduleDB), null);
return;
}
moduleContext.version = matchedVersion;
console.debug(`Fetching '${moduleContext}' module config`);
storage.readModuleConfig(moduleContext, (error, moduleConfig) => {
if (error) {
cb(error, null);
return;
}
cb(null, new DependenciesResolver(moduleConfig, storage, strategy));
});
});
}
});
}
/**
* @param {SemVerStrategy|AbstractStrategy} strategy
*/
set strategy(strategy) {
this._strategy = strategy;
}
/**
* @returns {SemVerStrategy|AbstractStrategy}
*/
get strategy() {
return this._strategy;
}
/**
* @returns {Storage|*}
*/
get storage() {
return this._storage;
}
/**
* @returns {ModuleConfig|*}
*/
get baseModuleConfig() {
return this._baseModuleConfig;
}
/**
* @param {Function} cb
*/
resolve(cb) {
this._resolve(this._baseModuleConfig.config.dependencies || {}, (error, depsTree) => {
if (error) {
cb(error, null);
return;
}
cb(null, {
context: this._baseModuleConfig.context,
rawVersion: this._baseModuleConfig.moduleVersion,
dependencies: depsTree
});
});
}
/**
* @param {Object} deps
* @param {Function} cb
* @private
*/
_resolve(deps, cb) {
let cbCalled = false;
let remaining = Object.keys(deps).length;
let wait = new _WaitFor.WaitFor();
let depsTree = {};
wait.push(() => {
return remaining <= 0;
});
for (let dependencyName in deps) {
if (!deps.hasOwnProperty(dependencyName)) {
continue;
}
let dependencyVersion = deps[dependencyName];
let moduleContext = _Context.Context.create(dependencyName, dependencyVersion);
console.debug(`Resolving dependency '${dependencyName}@${dependencyVersion}'`);
this._resolveSingle(moduleContext, (error, moduleContext) => {
if (error || cbCalled) {
remaining--;
if (cbCalled) {
return;
}
cbCalled = true;
cb(error, null);
return;
}
// @todo: remove circular deps check?
let depUqKey = moduleContext.toString();
if (this._resolveUqStack.indexOf(depUqKey) !== -1) {
console.warn(`Circular dependency '${depUqKey}' found`);
depsTree[moduleContext.name] = {
context: moduleContext,
rawVersion: dependencyVersion,
dependencies: null,
_isCircular: true
};
remaining--;
return;
} else {
this._resolveUqStack.push(depUqKey);
}
let dependencyObject = {
context: moduleContext,
rawVersion: dependencyVersion,
dependencies: {}
};
this._resolveNested(moduleContext, (error, nestedDepsTree) => {
if (error) {
if (!cbCalled) {
cbCalled = true;
cb(error, null);
}
} else {
dependencyObject.dependencies = nestedDepsTree;
depsTree[moduleContext.name] = dependencyObject;
}
remaining--;
});
});
}
wait.ready(() => {
if (!cbCalled) {
cb(null, depsTree);
}
});
}
/**
* @param {Context} moduleContext
* @param {Function} cb
* @private
*/
_resolveNested(moduleContext, cb) {
this._getModuleConfig(moduleContext, (error, moduleConfig) => {
if (error) {
cb(error, null);
return;
}
this._resolve(moduleConfig.config.dependencies || {}, cb);
});
}
/**
* @param {Context} moduleContext
* @param {Function} cb
* @private
*/
_resolveSingle(moduleContext, cb) {
this._getModuleDb(moduleContext, (error, moduleDB) => {
if (error) {
cb(error, null);
return;
}
console.debug(`Resolving '${moduleContext}'`);
let matchedVersion = this._strategy.resolve(moduleDB, moduleContext.version);
if (!matchedVersion) {
cb(new _NoVersionMatchingException.NoVersionMatchingException(moduleContext, moduleDB), null);
return;
}
moduleContext.version = matchedVersion;
cb(null, moduleContext);
});
}
/**
* @param {Context} moduleContext
* @param {Function} cb
* @private
*/
_getModuleConfig(moduleContext, cb) {
let cacheKey = moduleContext.toString();
if (this._configCache.hasOwnProperty(cacheKey)) {
console.debug(`Reading '${moduleContext}' module config from cache`);
cb(null, this._configCache[cacheKey]);
return;
}
console.debug(`Fetching '${moduleContext}' module config`);
this._storage.readModuleConfig(moduleContext, (error, moduleConfig) => {
if (error) {
cb(error, null);
return;
}
this._configCache[cacheKey] = moduleConfig;
cb(null, moduleConfig);
});
}
/**
* @param {Context} moduleContext
* @param {Function} cb
* @private
*/
_getModuleDb(moduleContext, cb) {
let moduleName = moduleContext.name;
if (this._dbCache.hasOwnProperty(moduleName)) {
console.debug(`Reading '${moduleName}' module DB from cache`);
cb(null, this._dbCache[moduleName]);
return;
}
console.debug(`Looking for '${moduleName}' module DB`);
this._storage.moduleDbExists(moduleContext, (error, state) => {
if (error) {
cb(error, null);
} else if (!state) {
cb(new _MissingModuleDBException.MissingModuleDBException(moduleContext), null);
} else {
console.debug(`Fetching '${moduleName}' module DB`);
this._storage.readModuleDb(moduleContext, (error, moduleDB) => {
if (error) {
cb(error, null);
return;
}
this._dbCache[moduleName] = moduleDB;
cb(null, moduleDB);
});
}
});
}
/**
* @returns {String}
*/
static get VERSION_ANY() {
return '*';
}
}
exports.DependenciesResolver = DependenciesResolver;