When executing some commands, have you ever encountered a message indicating that the version is too late and you need to upgrade the version? What rules are used to restrict and match the version number? Semver is a semantic version number management module, which can achieve version number parsing and comparison, standardize version number format.

Basic rules for version numbers

structure

The version number generally has three parts to. Separate, as in X.Y.Z, where

  • X: major version number, incompatible major changes
  • Y: Minor version number, functional change
  • Z: Revised version number, problem fixed

Each part is an integer (>=0) and changes according to the increment rule.

Additional information can be added to the revision number using the – concatenation, such as:

  • X.Y.Z – Alpha: closed beta version
  • X.Y.Z – Beta: Beta
  • X.Y.Z – Stable: Stable version

Scoping rules

In package.json, dependencies that we install are described with version numbers, such as dependencies that are automatically installed in package.json using the React project initialization

"devDependencies": {
    "react": "^" 15.6.1."react-dom": "^" 15.6.1
  }
Copy the code

In fact, we usually see version numbers, not only have the prefix ^, but also ~, so what do they represent?

^: allows left-most non-zero digits to change without modifying [Major, Minor, patch] (matches the version numbers of updates Y, Z greater than X, Y, Z)

In X.Y.Z, X, Y, and Z are all non-negative integers. This means that from left to right, the first non-zero digit is unmodifiable, and the next digit can be changed, for example:

  • X, Y, and Z are not 0,^ 15.6.1"The leftmost non-zero number is15, so X is not allowed to update, that is, the major version number cannot exceed 15, which is the version number> = 15.6.1 && < 16.0.0
  • If X is 0, then the first non-zero number is Y, and you can only change z,^ 0.1.2Indicates version number> = 0.1.2 && < 0.2.0
  • If the X and Y digits are both 0, the first non-zero digit is Z, indicating that the version number is not allowed to be updated.^ hundreds, the major and minor version numbers are 0, and the revision number is non-zero, indicating the version number> = hundreds && < 0.0.3

~: matches the version number of newer Z that is greater than X.Y.Z

  • X, Y, and Z are not 0,~ 1.2.3Indicates version number> = 1.2.3 && < 1.3.0
  • X is 0,~ 0.2.3Indicates version number> = 0.2.3 && < 0.3.0In this case,~Is equivalent to^
  • X and Y are 0,0.0.3Indicates version number> = 0.0.3 && < 0.1.0 from

X: indicates that the position can be replaced by x, Y, or Z, indicating that the position can be updated

  • X 1.2.: > = 1.2.0 && < 1.3.0
  • 1.x: > = 1.0.0 && < 2.0.0
  • *: Any version is acceptable

The x above can be replaced by *, in fact, the x or * can be omitted, such as 1.2. X and 1.2 mean the same thing

– : the range containing the first version number and the second version number represents a closed range. – Both versions of the connection range are included

  • 0.1.0 from2: > = 0.1.0 from && < 3.0.0
  • 0.1.0 from2.1.1: > = 0.1.0 from && < = 2.1.1

The installation

npm install semver
Copy the code

usage

// import module const semver = require('semver')
 
semver.clean('= v1.1.1); // 1.1.1, parse the version number, ignoring the symbol semver.valid('1.1.1'); // true, is the version number valid semver.valid('a.b.c'); // falseSemver. Satisfies ('1'.'1.2.3-1.2.5'); // trueTo determine whether the version is in a certain rangeCopy the code

Here is only a partial list of usage, the details can be viewed in the documentation.

Realize the principle of

Read the source code of Semver and sort out the realization principle of some methods

clean

. exports.clean = clean;functionVar s = parse(version.trim().replace(/^[=v]+/,' '), loose);
  returns ? s.version : null; }...Copy the code

valid

. exports.valid = valid;function valid(version, loose) {
  var v = parse(version, loose);
  returnv ? v.version : null; }...Copy the code

Both clean and Valid use a method called parse, which parses the version number to see if it is canonical and returns a canonical format

parse

Parsing the format of the version number to determine whether it is valid is used in many method implementations

exports.parse = parse; function parse(version, loose) {
  if (version instanceof SemVer)
    returnversion; if(typeof version ! = ='string')
    returnnull; if (version.length > MAX_LENGTH)
    returnnull; Var r = loose? re[LOOSE] : re[FULL];if(! r.test(version))returnnull; Try {return new SemVer(version, loose);
  } catch (er) {
    returnnull; }} /* * the loose parameter indicates whether the version number is loose. * loose istrueFor example, defining numeric identifiers defines a looser matching pattern * // /## Numeric Identifier// A single '0', or A non-zero digit followed by zero or more digits. Var NUMERICIDENTIFIER = R++; src[NUMERICIDENTIFIER] ='0|[1-9]\\d*'; Var NUMERICIDENTIFIERLOOSE = R++; src[NUMERICIDENTIFIERLOOSE] ='[0-9] +'; // 1 or more digits from 0 to 9Copy the code

satisfies

exports.satisfies = satisfies;
functionNo distribution found for the input and return a formatted range = new range (range, loose); } catch (er) {return false;
  }
  return range.test(version);
}

Copy the code

Distribution calls Range for normalizing the Range of user input

exports.Range = Range;
function Range(range, loose) {
  if (range instanceof Range) {
    if (range.loose === loose) {
      return range;
    } else {
      returnnew Range(range.raw, loose); }}if (range instanceof Comparator) {
    return new Range(range.value, loose);
  }

  if(! (this instanceof Range))returnnew Range(range, loose); this.loose = loose; / * * will range according to the '| |' * separately for each range resolution, and filter out the scope of the meaningless * / / / First, split -based on Boolean or | | this. Raw = range; / / in the scope of the split the input into an array. This set = range. The split (/ \ | the \ | \ \ s * s * /). The map (function(range) {// Parse each item of the arrayreturn this.parseRange(range.trim());
  }, this).filter(function(c) {
    // throw out any that are not relevant for whatever reason
    returnc.length; }); if(! this.set.length) { throw new TypeError('Invalid SemVer Range: '+ range); } this. The format (); }Copy the code
/ * * to parse user input Range test, return to the format of the specification * / Range in the prototype. ParseRange =function(range) { var loose = this.loose; range = range.trim(); // Remove space before and after debug('range', range, loose); // Replace the hyphen with the comparison symbol, '1.2.3-1.2.4' => '>=1.2.3 <=1.2.4 var hr = loose? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]; range = range.replace(hr, hyphenReplace); debug('hyphen replace', range); // '>1.2.3 <1.2.5' => '>1.2.3 <1.2.5' range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace); debug('comparator trim', range, re[COMPARATORTRIM]); // '~ detri' => '~ detri' range = range.replace(re[TILDETRIM], tilDEDEDETRI replace); // '^1.2.3' => '^1.2.3' range = range.replace(re[CARETTRIM], caretTrimReplace); Range = range.split(/\s+/).join(/\s+/).join(' '); // At this point, the range is completely trimmed and // ready to be split into comparators. Var compRe = loose? re[COMPARATORLOOSE] : re[COMPARATOR]; // Divide the string representing the range into arrays by Spaces, parse each array direction, return the canonical representation and reconcatenate into the string varset = range.split(' ').map(function(comp) {
    return parseComparator(comp, loose);
  }).join(' ').split(/\s+/);
  if(this.loose) {// In loose mode, filter out all illegal comparatorsset = set.filter(function(comp) 
      return!!!!! comp.match(compRe); }); }set = set.map(function(comp) {
    returnnew Comparator(comp, loose); }); return set; }; /** * reconcatenates the normalized Range string and returns */ range.prototype. format =function() {
  this.range = this.set.map(function(comps) {
    return comps.join(' ').trim();
  }).join('| |').trim();
  return this.range;
};
Copy the code

Reference: Semver documentation Semver source address