UNPKG

7.99 kBJavaScriptView Raw
1const debug = require('../internal/debug')
2const { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../internal/constants')
3const { 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: ${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) {
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)
186 break
187 case 'preminor':
188 this.prerelease.length = 0
189 this.patch = 0
190 this.minor++
191 this.inc('pre', identifier)
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)
199 this.inc('pre', identifier)
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)
206 }
207 this.inc('pre', identifier)
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 if (this.prerelease.length === 0) {
251 this.prerelease = [0]
252 } else {
253 let i = this.prerelease.length
254 while (--i >= 0) {
255 if (typeof this.prerelease[i] === 'number') {
256 this.prerelease[i]++
257 i = -2
258 }
259 }
260 if (i === -1) {
261 // didn't increment anything
262 this.prerelease.push(0)
263 }
264 }
265 if (identifier) {
266 // 1.2.0-beta.1 bumps to 1.2.0-beta.2,
267 // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
268 if (this.prerelease[0] === identifier) {
269 if (isNaN(this.prerelease[1])) {
270 this.prerelease = [identifier, 0]
271 }
272 } else {
273 this.prerelease = [identifier, 0]
274 }
275 }
276 break
277
278 default:
279 throw new Error(`invalid increment argument: ${release}`)
280 }
281 this.format()
282 this.raw = this.version
283 return this
284 }
285}
286
287module.exports = SemVer