1const npa = require('npm-package-arg') 2const semver = require('semver') 3 4class OverrideSet { 5 constructor ({ overrides, key, parent }) { 6 this.parent = parent 7 this.children = new Map() 8 9 if (typeof overrides === 'string') { 10 overrides = { '.': overrides } 11 } 12 13 // change a literal empty string to * so we can use truthiness checks on 14 // the value property later 15 if (overrides['.'] === '') { 16 overrides['.'] = '*' 17 } 18 19 if (parent) { 20 const spec = npa(key) 21 if (!spec.name) { 22 throw new Error(`Override without name: ${key}`) 23 } 24 25 this.name = spec.name 26 spec.name = '' 27 this.key = key 28 this.keySpec = spec.toString() 29 this.value = overrides['.'] || this.keySpec 30 } 31 32 for (const [key, childOverrides] of Object.entries(overrides)) { 33 if (key === '.') { 34 continue 35 } 36 37 const child = new OverrideSet({ 38 parent: this, 39 key, 40 overrides: childOverrides, 41 }) 42 43 this.children.set(child.key, child) 44 } 45 } 46 47 getEdgeRule (edge) { 48 for (const rule of this.ruleset.values()) { 49 if (rule.name !== edge.name) { 50 continue 51 } 52 53 // if keySpec is * we found our override 54 if (rule.keySpec === '*') { 55 return rule 56 } 57 58 let spec = npa(`${edge.name}@${edge.spec}`) 59 if (spec.type === 'alias') { 60 spec = spec.subSpec 61 } 62 63 if (spec.type === 'git') { 64 if (spec.gitRange && semver.intersects(spec.gitRange, rule.keySpec)) { 65 return rule 66 } 67 68 continue 69 } 70 71 if (spec.type === 'range' || spec.type === 'version') { 72 if (semver.intersects(spec.fetchSpec, rule.keySpec)) { 73 return rule 74 } 75 76 continue 77 } 78 79 // if we got this far, the spec type is one of tag, directory or file 80 // which means we have no real way to make version comparisons, so we 81 // just accept the override 82 return rule 83 } 84 85 return this 86 } 87 88 getNodeRule (node) { 89 for (const rule of this.ruleset.values()) { 90 if (rule.name !== node.name) { 91 continue 92 } 93 94 if (semver.satisfies(node.version, rule.keySpec) || 95 semver.satisfies(node.version, rule.value)) { 96 return rule 97 } 98 } 99 100 return this 101 } 102 103 getMatchingRule (node) { 104 for (const rule of this.ruleset.values()) { 105 if (rule.name !== node.name) { 106 continue 107 } 108 109 if (semver.satisfies(node.version, rule.keySpec) || 110 semver.satisfies(node.version, rule.value)) { 111 return rule 112 } 113 } 114 115 return null 116 } 117 118 * ancestry () { 119 for (let ancestor = this; ancestor; ancestor = ancestor.parent) { 120 yield ancestor 121 } 122 } 123 124 get isRoot () { 125 return !this.parent 126 } 127 128 get ruleset () { 129 const ruleset = new Map() 130 131 for (const override of this.ancestry()) { 132 for (const kid of override.children.values()) { 133 if (!ruleset.has(kid.key)) { 134 ruleset.set(kid.key, kid) 135 } 136 } 137 138 if (!override.isRoot && !ruleset.has(override.key)) { 139 ruleset.set(override.key, override) 140 } 141 } 142 143 return ruleset 144 } 145} 146 147module.exports = OverrideSet 148