From f4ae8a64f0d91faa89c631e50d1f539e8bec457e Mon Sep 17 00:00:00 2001 From: Simon Kjellberg Date: Sat, 24 Mar 2018 20:13:31 +0100 Subject: [PATCH] fix(cli): adjust colors for unstable version updates (#5498) This change special-cases the color indicator for updates to *unstable* package versions, to more accurately indicate backwards compatibility. For 0.x.y versions, increments to the *minor* version number will be considered a *major* version change, and increments to the *patch* number, will be considered a *minor* version change. For 0.0.x versions, increments to *any* version number will be considered a *major* version change. This matches the behavior of how yarn & npm treats updates to packages defined with a caret-range. --- __tests__/util/semver.js | 46 ++++++++++++++++++++++-- src/util/color-for-versions.js | 3 +- src/util/semver.js | 65 +++++++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/__tests__/util/semver.js b/__tests__/util/semver.js index 88cbed8d15..fb510287b1 100644 --- a/__tests__/util/semver.js +++ b/__tests__/util/semver.js @@ -1,8 +1,7 @@ /* @flow */ -import {satisfiesWithPrereleases} from '../../src/util/semver.js'; - -const semver = require('semver'); +import semver from 'semver'; +import {satisfiesWithPrereleases, diffWithUnstable} from '../../src/util/semver.js'; describe('satisfiesWithPrereleases', () => { it('matches the behavior of node-semver for non-prerelease versions', () => { @@ -54,3 +53,44 @@ describe('satisfiesWithPrereleases', () => { expect(satisfiesWithPrereleases('1.0.0', '>1.0.0-rc.1')).toBe(true); }); }); + +describe('diffWithUnstable', () => { + it('matches the behavior of node-semver for stable versions', () => { + expect(diffWithUnstable('2.0.0', '1.0.0')).toBe(semver.diff('2.0.0', '1.0.0')); + expect(diffWithUnstable('2.1.0', '2.0.0')).toBe(semver.diff('2.1.0', '2.0.0')); + expect(diffWithUnstable('2.1.1', '2.1.0')).toBe(semver.diff('2.1.1', '2.1.0')); + expect(diffWithUnstable('2.1.1-beta', '2.1.0')).toBe(semver.diff('2.1.1-beta', '2.1.0')); + expect(diffWithUnstable('2.1.1-alpha', '2.1.1')).toBe(semver.diff('2.1.1-alpha', '2.1.1')); + expect(diffWithUnstable('2.1.1', '2.1.1')).toBe(semver.diff('2.1.1', '2.1.1')); + expect(diffWithUnstable('2.1.1-rc', '2.1.1-rc')).toBe(semver.diff('2.1.1-rc', '2.1.1-rc')); + }); + + it('treats patch and minor releases for pre-major versions as minor & major respectively', () => { + expect(diffWithUnstable('0.2.0', '0.2.1')).toBe('minor'); + expect(diffWithUnstable('0.2.0', '0.2.1-rc')).toBe('preminor'); + expect(diffWithUnstable('0.1.0', '0.2.0')).toBe('major'); + expect(diffWithUnstable('0.1.0', '0.2.0-alpha')).toBe('premajor'); + expect(diffWithUnstable('0.2.1', '1.0.0')).toBe('major'); + expect(diffWithUnstable('0.2.1-rc', '1.0.0-beta')).toBe('premajor'); + expect(diffWithUnstable('0.2.2-rc.0', '0.2.2-rc.1')).toBe('prerelease'); + }); + + it('treats patch and minor releases for pre-minor versions as major', () => { + expect(diffWithUnstable('0.0.1', '0.0.2')).toBe('major'); + expect(diffWithUnstable('0.0.1', '0.0.2-rc')).toBe('premajor'); + expect(diffWithUnstable('0.0.2', '0.1.0')).toBe('major'); + expect(diffWithUnstable('0.0.2', '0.1.0-alpha')).toBe('premajor'); + expect(diffWithUnstable('0.0.2', '1.0.0')).toBe('major'); + expect(diffWithUnstable('0.0.2-rc', '1.0.0-beta')).toBe('premajor'); + expect(diffWithUnstable('0.0.2-rc.1', '0.0.2-rc.2')).toBe('prerelease'); + }); + + it('returns null for equal versions', () => { + expect(diffWithUnstable('1.0.0', '1.0.0')).toBeNull(); + expect(diffWithUnstable('2.0.1-beta.0', '2.0.1-beta.0')).toBeNull(); + expect(diffWithUnstable('0.2.1', '0.2.1')).toBeNull(); + expect(diffWithUnstable('0.2.3-alpha', '0.2.3-alpha')).toBeNull(); + expect(diffWithUnstable('0.0.11', '0.0.11')).toBeNull(); + expect(diffWithUnstable('0.0.100-beta.2', '0.0.100-beta.2')).toBeNull(); + }); +}); diff --git a/src/util/color-for-versions.js b/src/util/color-for-versions.js index 8ee65a353f..48caa3eea3 100644 --- a/src/util/color-for-versions.js +++ b/src/util/color-for-versions.js @@ -1,5 +1,6 @@ /* @flow */ import semver from 'semver'; +import {diffWithUnstable} from './semver.js'; import {VERSION_COLOR_SCHEME} from '../constants.js'; import type {VersionColor} from '../constants.js'; @@ -8,7 +9,7 @@ export default function(from: string, to: string): VersionColor { const validTo = semver.valid(to); let versionBump = 'unknown'; if (validFrom && validTo) { - versionBump = semver.diff(validFrom, validTo) || 'unchanged'; + versionBump = diffWithUnstable(validFrom, validTo) || 'unchanged'; } return VERSION_COLOR_SCHEME[versionBump]; } diff --git a/src/util/semver.js b/src/util/semver.js index dc4bcd4a71..7d87c340cb 100644 --- a/src/util/semver.js +++ b/src/util/semver.js @@ -1,6 +1,6 @@ /* @flow */ -const semver = require('semver'); +import semver, {type Release} from 'semver'; /** * Returns whether the given semver version satisfies the given range. Notably this supports @@ -45,3 +45,66 @@ export function satisfiesWithPrereleases(version: string, range: string, loose?: return !comparatorSet.some(comparator => !comparator.test(semverVersion)); }); } + +const PRE_RELEASES = { + major: 'premajor', + minor: 'preminor', + patch: 'prepatch', +}; + +/** + * Returns the difference between two versions as a semantic string representation. + * Similar to the `diff` method in node-semver, but it also accounts for unstable versions, + * like 0.x.x or 0.0.x. + */ + +export function diffWithUnstable(version1: string, version2: string): Release | null { + if (semver.eq(version1, version2) === false) { + const v1 = semver.parse(version1); + const v2 = semver.parse(version2); + + if (v1 != null && v2 != null) { + const isPreRelease = v1.prerelease.length > 0 || v2.prerelease.length > 0; + const preMajor = v1.major === 0 || v2.major === 0; + const preMinor = preMajor && (v1.minor === 0 || v2.minor === 0); + + let diff = null; + + if (v1.major !== v2.major) { + diff = 'major'; + } else if (v1.minor !== v2.minor) { + if (preMajor) { + // If the major version number is zero (0.x.x), treat a change + // of the minor version number as a major change. + diff = 'major'; + } else { + diff = 'minor'; + } + } else if (v1.patch !== v2.patch) { + if (preMinor) { + // If the major & minor version numbers are zero (0.0.x), treat a change + // of the patch version number as a major change. + diff = 'major'; + } else if (preMajor) { + // If the major version number is zero (0.x.x), treat a change + // of the patch version number as a minor change. + diff = 'minor'; + } else { + diff = 'patch'; + } + } + + if (isPreRelease) { + if (diff != null) { + diff = PRE_RELEASES[diff]; + } else { + diff = 'prerelease'; + } + } + + return diff; + } + } + + return null; +}