• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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