1const debug = require('../internal/debug') 2const { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../internal/constants') 3const { safeRe: re, t } = require('../internal/re') 4 5const parseOptions = require('../internal/parse-options') 6const { compareIdentifiers } = require('../internal/identifiers') 7class SemVer { 8 constructor (version, options) { 9 options = parseOptions(options) 10 11 if (version instanceof SemVer) { 12 if (version.loose === !!options.loose && 13 version.includePrerelease === !!options.includePrerelease) { 14 return version 15 } else { 16 version = version.version 17 } 18 } else if (typeof version !== 'string') { 19 throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`) 20 } 21 22 if (version.length > MAX_LENGTH) { 23 throw new TypeError( 24 `version is longer than ${MAX_LENGTH} characters` 25 ) 26 } 27 28 debug('SemVer', version, options) 29 this.options = options 30 this.loose = !!options.loose 31 // this isn't actually relevant for versions, but keep it so that we 32 // don't run into trouble passing this.options around. 33 this.includePrerelease = !!options.includePrerelease 34 35 const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL]) 36 37 if (!m) { 38 throw new TypeError(`Invalid Version: ${version}`) 39 } 40 41 this.raw = version 42 43 // these are actually numbers 44 this.major = +m[1] 45 this.minor = +m[2] 46 this.patch = +m[3] 47 48 if (this.major > MAX_SAFE_INTEGER || this.major < 0) { 49 throw new TypeError('Invalid major version') 50 } 51 52 if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { 53 throw new TypeError('Invalid minor version') 54 } 55 56 if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { 57 throw new TypeError('Invalid patch version') 58 } 59 60 // numberify any prerelease numeric ids 61 if (!m[4]) { 62 this.prerelease = [] 63 } else { 64 this.prerelease = m[4].split('.').map((id) => { 65 if (/^[0-9]+$/.test(id)) { 66 const num = +id 67 if (num >= 0 && num < MAX_SAFE_INTEGER) { 68 return num 69 } 70 } 71 return id 72 }) 73 } 74 75 this.build = m[5] ? m[5].split('.') : [] 76 this.format() 77 } 78 79 format () { 80 this.version = `${this.major}.${this.minor}.${this.patch}` 81 if (this.prerelease.length) { 82 this.version += `-${this.prerelease.join('.')}` 83 } 84 return this.version 85 } 86 87 toString () { 88 return this.version 89 } 90 91 compare (other) { 92 debug('SemVer.compare', this.version, this.options, other) 93 if (!(other instanceof SemVer)) { 94 if (typeof other === 'string' && other === this.version) { 95 return 0 96 } 97 other = new SemVer(other, this.options) 98 } 99 100 if (other.version === this.version) { 101 return 0 102 } 103 104 return this.compareMain(other) || this.comparePre(other) 105 } 106 107 compareMain (other) { 108 if (!(other instanceof SemVer)) { 109 other = new SemVer(other, this.options) 110 } 111 112 return ( 113 compareIdentifiers(this.major, other.major) || 114 compareIdentifiers(this.minor, other.minor) || 115 compareIdentifiers(this.patch, other.patch) 116 ) 117 } 118 119 comparePre (other) { 120 if (!(other instanceof SemVer)) { 121 other = new SemVer(other, this.options) 122 } 123 124 // NOT having a prerelease is > having one 125 if (this.prerelease.length && !other.prerelease.length) { 126 return -1 127 } else if (!this.prerelease.length && other.prerelease.length) { 128 return 1 129 } else if (!this.prerelease.length && !other.prerelease.length) { 130 return 0 131 } 132 133 let i = 0 134 do { 135 const a = this.prerelease[i] 136 const b = other.prerelease[i] 137 debug('prerelease compare', i, a, b) 138 if (a === undefined && b === undefined) { 139 return 0 140 } else if (b === undefined) { 141 return 1 142 } else if (a === undefined) { 143 return -1 144 } else if (a === b) { 145 continue 146 } else { 147 return compareIdentifiers(a, b) 148 } 149 } while (++i) 150 } 151 152 compareBuild (other) { 153 if (!(other instanceof SemVer)) { 154 other = new SemVer(other, this.options) 155 } 156 157 let i = 0 158 do { 159 const a = this.build[i] 160 const b = other.build[i] 161 debug('prerelease compare', i, a, b) 162 if (a === undefined && b === undefined) { 163 return 0 164 } else if (b === undefined) { 165 return 1 166 } else if (a === undefined) { 167 return -1 168 } else if (a === b) { 169 continue 170 } else { 171 return compareIdentifiers(a, b) 172 } 173 } while (++i) 174 } 175 176 // preminor will bump the version up to the next minor release, and immediately 177 // down to pre-release. premajor and prepatch work the same way. 178 inc (release, identifier, identifierBase) { 179 switch (release) { 180 case 'premajor': 181 this.prerelease.length = 0 182 this.patch = 0 183 this.minor = 0 184 this.major++ 185 this.inc('pre', identifier, identifierBase) 186 break 187 case 'preminor': 188 this.prerelease.length = 0 189 this.patch = 0 190 this.minor++ 191 this.inc('pre', identifier, identifierBase) 192 break 193 case 'prepatch': 194 // If this is already a prerelease, it will bump to the next version 195 // drop any prereleases that might already exist, since they are not 196 // relevant at this point. 197 this.prerelease.length = 0 198 this.inc('patch', identifier, identifierBase) 199 this.inc('pre', identifier, identifierBase) 200 break 201 // If the input is a non-prerelease version, this acts the same as 202 // prepatch. 203 case 'prerelease': 204 if (this.prerelease.length === 0) { 205 this.inc('patch', identifier, identifierBase) 206 } 207 this.inc('pre', identifier, identifierBase) 208 break 209 210 case 'major': 211 // If this is a pre-major version, bump up to the same major version. 212 // Otherwise increment major. 213 // 1.0.0-5 bumps to 1.0.0 214 // 1.1.0 bumps to 2.0.0 215 if ( 216 this.minor !== 0 || 217 this.patch !== 0 || 218 this.prerelease.length === 0 219 ) { 220 this.major++ 221 } 222 this.minor = 0 223 this.patch = 0 224 this.prerelease = [] 225 break 226 case 'minor': 227 // If this is a pre-minor version, bump up to the same minor version. 228 // Otherwise increment minor. 229 // 1.2.0-5 bumps to 1.2.0 230 // 1.2.1 bumps to 1.3.0 231 if (this.patch !== 0 || this.prerelease.length === 0) { 232 this.minor++ 233 } 234 this.patch = 0 235 this.prerelease = [] 236 break 237 case 'patch': 238 // If this is not a pre-release version, it will increment the patch. 239 // If it is a pre-release it will bump up to the same patch version. 240 // 1.2.0-5 patches to 1.2.0 241 // 1.2.0 patches to 1.2.1 242 if (this.prerelease.length === 0) { 243 this.patch++ 244 } 245 this.prerelease = [] 246 break 247 // This probably shouldn't be used publicly. 248 // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction. 249 case 'pre': { 250 const base = Number(identifierBase) ? 1 : 0 251 252 if (!identifier && identifierBase === false) { 253 throw new Error('invalid increment argument: identifier is empty') 254 } 255 256 if (this.prerelease.length === 0) { 257 this.prerelease = [base] 258 } else { 259 let i = this.prerelease.length 260 while (--i >= 0) { 261 if (typeof this.prerelease[i] === 'number') { 262 this.prerelease[i]++ 263 i = -2 264 } 265 } 266 if (i === -1) { 267 // didn't increment anything 268 if (identifier === this.prerelease.join('.') && identifierBase === false) { 269 throw new Error('invalid increment argument: identifier already exists') 270 } 271 this.prerelease.push(base) 272 } 273 } 274 if (identifier) { 275 // 1.2.0-beta.1 bumps to 1.2.0-beta.2, 276 // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 277 let prerelease = [identifier, base] 278 if (identifierBase === false) { 279 prerelease = [identifier] 280 } 281 if (compareIdentifiers(this.prerelease[0], identifier) === 0) { 282 if (isNaN(this.prerelease[1])) { 283 this.prerelease = prerelease 284 } 285 } else { 286 this.prerelease = prerelease 287 } 288 } 289 break 290 } 291 default: 292 throw new Error(`invalid increment argument: ${release}`) 293 } 294 this.raw = this.format() 295 if (this.build.length) { 296 this.raw += `+${this.build.join('.')}` 297 } 298 return this 299 } 300} 301 302module.exports = SemVer 303