UNPKG

npm

Version:

a package manager for JavaScript

210 lines (174 loc) 5.07 kB
const npa = require('npm-package-arg') const semver = require('semver') const { log } = require('proc-log') class OverrideSet { constructor ({ overrides, key, parent }) { this.parent = parent this.children = new Map() if (typeof overrides === 'string') { overrides = { '.': overrides } } // change a literal empty string to * so we can use truthiness checks on // the value property later if (overrides['.'] === '') { overrides['.'] = '*' } if (parent) { const spec = npa(key) if (!spec.name) { throw new Error(`Override without name: ${key}`) } this.name = spec.name spec.name = '' this.key = key this.keySpec = spec.toString() this.value = overrides['.'] || this.keySpec } for (const [key, childOverrides] of Object.entries(overrides)) { if (key === '.') { continue } const child = new OverrideSet({ parent: this, key, overrides: childOverrides, }) this.children.set(child.key, child) } } childrenAreEqual (other) { if (this.children.size !== other.children.size) { return false } for (const [key] of this.children) { if (!other.children.has(key)) { return false } if (this.children.get(key).value !== other.children.get(key).value) { return false } if (!this.children.get(key).childrenAreEqual(other.children.get(key))) { return false } } return true } isEqual (other) { if (this === other) { return true } if (!other) { return false } if (this.key !== other.key || this.value !== other.value) { return false } if (!this.childrenAreEqual(other)) { return false } if (!this.parent) { return !other.parent } return this.parent.isEqual(other.parent) } getEdgeRule (edge) { for (const rule of this.ruleset.values()) { if (rule.name !== edge.name) { continue } // if keySpec is * we found our override if (rule.keySpec === '*') { return rule } // We need to use the rawSpec here, because the spec has the overrides applied to it already. // rawSpec can be undefined, so we need to use the fallback value of spec if it is. let spec = npa(`${edge.name}@${edge.rawSpec || edge.spec}`) if (spec.type === 'alias') { spec = spec.subSpec } if (spec.type === 'git') { if (spec.gitRange && semver.intersects(spec.gitRange, rule.keySpec)) { return rule } continue } if (spec.type === 'range' || spec.type === 'version') { if (semver.intersects(spec.fetchSpec, rule.keySpec)) { return rule } continue } // if we got this far, the spec type is one of tag, directory or file // which means we have no real way to make version comparisons, so we // just accept the override return rule } return this } getNodeRule (node) { for (const rule of this.ruleset.values()) { if (rule.name !== node.name) { continue } if (semver.satisfies(node.version, rule.keySpec) || semver.satisfies(node.version, rule.value)) { return rule } } return this } getMatchingRule (node) { for (const rule of this.ruleset.values()) { if (rule.name !== node.name) { continue } if (semver.satisfies(node.version, rule.keySpec) || semver.satisfies(node.version, rule.value)) { return rule } } return null } * ancestry () { for (let ancestor = this; ancestor; ancestor = ancestor.parent) { yield ancestor } } get isRoot () { return !this.parent } get ruleset () { const ruleset = new Map() for (const override of this.ancestry()) { for (const kid of override.children.values()) { if (!ruleset.has(kid.key)) { ruleset.set(kid.key, kid) } } if (!override.isRoot && !ruleset.has(override.key)) { ruleset.set(override.key, override) } } return ruleset } static findSpecificOverrideSet (first, second) { for (let overrideSet = second; overrideSet; overrideSet = overrideSet.parent) { if (overrideSet.isEqual(first)) { return second } } for (let overrideSet = first; overrideSet; overrideSet = overrideSet.parent) { if (overrideSet.isEqual(second)) { return first } } // The override sets are incomparable. Neither one contains the other. log.silly('Conflicting override sets', first, second) } static doOverrideSetsConflict (first, second) { // If override sets contain one another then we can try to use the more specific one. // If neither one is more specific, then we consider them to be in conflict. return (this.findSpecificOverrideSet(first, second) === undefined) } } module.exports = OverrideSet