UNPKG

cobolget

Version:
334 lines (333 loc) 15.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Resolver = void 0; // Cloned from https://github.com/pghalliday/semver-resolver const semver = require("semver"); const lodash = require("lodash"); const uuid = require("uuid"); class Resolver { constructor(graph, dependencies) { Resolver.graph = graph; const rootName = uuid.v4(); this.root = rootName; let state = this.state = {}; let rootState = state[rootName] = { dependencies }; rootState.dependencies = lodash.mapValues(dependencies, range => { return { range: range }; }); this.queuedCalculations = Object.keys(dependencies); this.queuedConstraintUpdates = []; this.cachedVersions = {}; this.cachedDependencies = {}; } resolve() { return __awaiter(this, void 0, void 0, function* () { yield this.start(); let resolution = lodash.mapValues(this.state, value => value.version); delete resolution[this.root]; return resolution; }); } cleanQueuedCalculations() { let state = this.state; let knownLibraries = Object.keys(state); knownLibraries.forEach(library => { let dependencies = state[library].dependencies; // dependencies will always be populated // here because we just finished updating // from the queued constraints - if it isn't // then something probably changed around // the refillQueues/updateConstraints functions knownLibraries = lodash.union(knownLibraries, Object.keys(dependencies)); }); this.queuedCalculations = lodash.intersection(this.queuedCalculations, knownLibraries); } cleanQueuedConstraintUpdates() { let state = this.state; let knownLibraries = Object.keys(state); // we only want to look up dependencies for // libraries still in the state this.queuedConstraintUpdates = lodash.intersection(this.queuedConstraintUpdates, knownLibraries); } dropLibrary(library) { let queuedCalculations = this.queuedCalculations; let state = this.state; let libraryState = state[library]; if (libraryState) { // remove from state delete state[library]; let dependencies = libraryState.dependencies; if (dependencies) { lodash.forEach(Object.keys(dependencies), dependency => { // drop old data for dependency if we have it // already as it should not // be used in calculations anymore this.dropLibrary(dependency); // queue dependency for recalculation // as a constraint has been dropped // but it may still be a dependency // of another library still in the tree queuedCalculations.push(dependency); }); } } } updateConstraints(library) { let state = this.state; let cachedDependencies = this.cachedDependencies; let libraryState = state[library]; // check if this library is still in the state. // it may already have been dropped in an earlier // update, in which case the information we would // apply now is invalid anyway if (libraryState) { let version = libraryState.version; let dependencies = cachedDependencies[library][version]; let queuedCalculations = this.queuedCalculations; libraryState.dependencies = dependencies; // We don't need to worry about the possibility that there were already // dependencies attached to the library. It should // never happen as the only way to get into the update // queue is from the calculation queue and the only way // into the caclulation queue is on initialisation or // immediately after being dropped from the state. Thus // all these dependency constraints are new and none // will be dropped. Object.keys(dependencies).forEach(dependency => { // drop old data for dependency if we have it // already as it should not // be used in calculations anymore this.dropLibrary(dependency); // queue dependency for recalculation // as a constraint has been dropped // but it may still be a dependency // of another library still in the tree queuedCalculations.push(dependency); }); } } maxSatisfying(library) { let state = this.state; let versions = this.cachedVersions[library]; let queuedCalculations = this.queuedCalculations; let dependencyLibraries = []; // first collect all the constraints and the max versions // satisfying them, keyed by the parent that adds the constraint lodash.forOwn(state, (libraryState, parent) => { let dependencies = libraryState.dependencies; if (dependencies) { let dependencyLibrary = dependencies[library]; if (dependencyLibrary) { if (!dependencyLibrary.maxSatisfying) { let range = dependencyLibrary.range; let maxSatisfying = semver.maxSatisfying(versions, range); if (maxSatisfying === null) { let backtrackedDueTo = dependencyLibrary.backtrackedDueTo; let constrainingLibrary = 'root'; let version = libraryState.version; if (version) { constrainingLibrary = `${parent}@${version}`; } if (backtrackedDueTo) { throw new Error(`Unable to satisfy backtracked version constraint: ` + `${library}@${range} from ` + `${constrainingLibrary} due to shared ` + `constraint on ${backtrackedDueTo}`); } else { throw new Error(`Unable to satisfy version constraint: ` + `${library}@${range} from ` + `${constrainingLibrary}`); } } dependencyLibrary.maxSatisfying = maxSatisfying; } dependencyLibraries[parent] = dependencyLibrary; } } }); // next scan the max versions to find the minimum let lowestMaxSatisfying = null; lodash.forOwn(dependencyLibraries, (dependencyLibrary, parent) => { let maxSatisfying = dependencyLibrary.maxSatisfying; if (lowestMaxSatisfying === null) { lowestMaxSatisfying = { parent: parent, version: maxSatisfying }; } if (maxSatisfying < lowestMaxSatisfying.version) { lowestMaxSatisfying.parent = parent; lowestMaxSatisfying.version = maxSatisfying; } }); // then check if that minimum satisfies the other constraints // if a conflicting constraint is found then we have no version and should // drop and requeue the library version that adds the conflict, with // a new constraint to check for an earlier version of it let constrainingParent = lowestMaxSatisfying.parent; let version = lowestMaxSatisfying.version; let resolutionFound = true; lodash.forOwn(dependencyLibraries, (dependencyLibrary, parent) => { if (parent !== constrainingParent) { let range = dependencyLibrary.range; if (!semver.satisfies(version, range)) { // check if parent is root as root // cannot be backtracked let constrainingState = state[constrainingParent]; let constrainedState = state[parent]; let constrainedStateVersion = constrainedState.version; if (!constrainedStateVersion) { throw new Error(`Unable to satisfy version constraint: ` + `${library}@${range} from root due to ` + 'shared constraint from ' + `${constrainingParent}@${constrainingState.version}`); } // constraint cannot be met so add a new constraint // to the parent providing the lowest version for this // conflicting parent to backtrack to the next lowest version constrainingState.dependencies[parent] = { range: `<${constrainedStateVersion}`, backtrackedDueTo: library }; // drop old data for dependency if we have it // already as it should not // be used in calculations anymore this.dropLibrary(parent); // queue dependency for recalculation // as a constraint has been dropped // but it may still be a dependency // of another library still in the tree queuedCalculations.push(parent); resolutionFound = false; return resolutionFound; } } }); if (resolutionFound) { return version; } return null; } cacheVersions() { return __awaiter(this, void 0, void 0, function* () { let cachedVersions = this.cachedVersions; let librariesAlreadyCached = Object.keys(cachedVersions); let queuedCalculations = this.queuedCalculations; let librariesToCache = lodash.difference(queuedCalculations, librariesAlreadyCached); const versionsArray = yield Promise.all(librariesToCache.map(this.getVersions)); versionsArray.forEach((versions, index) => { cachedVersions[librariesToCache[index]] = versions.slice(0).sort(semver.rcompare); }); }); } resolveVersions() { let queuedCalculations = this.queuedCalculations; let nextQueuedCalculations = this.queuedCalculations = []; let state = this.state; let queuedConstraintUpdates = this.queuedConstraintUpdates; queuedCalculations.forEach(library => { // don't calculate now if the library was already requeued // due to backtracking - it may have been orphaned // and anyway tracking the state gets complicated if (!lodash.includes(nextQueuedCalculations, library)) { let version = this.maxSatisfying(library); if (version) { state[library] = { version: version }; queuedConstraintUpdates.push(library); } } }); // clean up the queued constraint updates // as some of the libraries may no longer // even be in dependencies this.cleanQueuedConstraintUpdates(); } cacheDependencies() { return __awaiter(this, void 0, void 0, function* () { let state = this.state; let cachedDependencies = this.cachedDependencies; let queuedConstraintUpdates = this.queuedConstraintUpdates; let dependenciesToCache = queuedConstraintUpdates.filter(library => { let version = state[library].version; let versions = cachedDependencies[library]; if (versions) { if (versions[version]) { return false; } } return true; }); const dependenciesArray = yield Promise.all(dependenciesToCache.map(library_1 => { return this.getDependencies(library_1, state[library_1].version); })); dependenciesArray.forEach((dependencies, index) => { let library_2 = dependenciesToCache[index]; cachedDependencies[library_2] = cachedDependencies[library_2] || {}; cachedDependencies[library_2][state[library_2].version] = lodash.mapValues(dependencies, range => { return { range: range }; }); }); }); } refillQueues() { let queuedConstraintUpdates = lodash.uniq(this.queuedConstraintUpdates); this.queuedConstraintUpdates = []; queuedConstraintUpdates.forEach(library => { this.updateConstraints(library); }); // clean up the queued calculations // as some of the libraries may no longer // even be in dependencies this.cleanQueuedCalculations(); } recurse() { if (this.queuedCalculations.length) { return this.start(); } } start() { return this.cacheVersions() .then(this.resolveVersions.bind(this)) .then(this.cacheDependencies.bind(this)) .then(this.refillQueues.bind(this)) .then(this.recurse.bind(this)); } getVersions(name) { return __awaiter(this, void 0, void 0, function* () { return Promise.resolve().then(() => { let versions = Resolver.graph[name]; if (!versions) { throw new Error(`Package '${name}' not found`); } return Object.keys(versions); }); }); } getDependencies(name, version) { return __awaiter(this, void 0, void 0, function* () { return Promise.resolve().then(() => { return Resolver.graph[name][version]; }); }); } } exports.Resolver = Resolver;