UNPKG

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