-
Notifications
You must be signed in to change notification settings - Fork 48
fix: Reverse the direction of the semver check #16
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,21 +23,27 @@ const re = /^(\d+)\.(\d+)\.(\d+)(?:-(.*))?$/; | |
* | ||
* The returned function has the following semantics: | ||
* - Exact match is always compatible | ||
* - Major versions must always match | ||
* - The minor version of the API module requesting access to the global API must be greater or equal to the minor version of this API | ||
* - Major versions must match exactly | ||
* - 1.x package cannot use global 2.x package | ||
* - 2.x package cannot use global 1.x package | ||
* - The minor version of the API module requesting access to the global API must be less than or equal to the minor version of this API | ||
* - 1.3 package may use 1.4 global because the later global contains all functions 1.3 expects | ||
* - 1.4 package may NOT use 1.3 global because it may try to call functions which don't exist on 1.3 | ||
* - If the major version is 0, the minor version is treated as the major and the patch is treated as the minor | ||
* - Patch and build tag differences are not considered at this time | ||
* | ||
* @param ownVersion version which should be checked against | ||
*/ | ||
export function _makeCompatibilityCheck( | ||
ownVersion: string | ||
): (version: string) => boolean { | ||
): (globalVersion: string) => boolean { | ||
const acceptedVersions = new Set<string>([ownVersion]); | ||
const rejectedVersions = new Set<string>(); | ||
|
||
const myVersionMatch = ownVersion.match(re); | ||
if (!myVersionMatch) { | ||
throw new Error('Cannot parse own version'); | ||
// we cannot guarantee compatibility so we always return noop | ||
return () => false; | ||
} | ||
|
||
const ownVersionParsed = { | ||
|
@@ -46,66 +52,73 @@ export function _makeCompatibilityCheck( | |
patch: +myVersionMatch[3], | ||
}; | ||
|
||
return function isCompatible(version: string): boolean { | ||
if (acceptedVersions.has(version)) { | ||
function _reject(v: string) { | ||
rejectedVersions.add(v); | ||
return false; | ||
} | ||
|
||
function _accept(v: string) { | ||
acceptedVersions.add(v); | ||
return true; | ||
} | ||
|
||
return function isCompatible(globalVersion: string): boolean { | ||
if (acceptedVersions.has(globalVersion)) { | ||
return true; | ||
} | ||
|
||
if (rejectedVersions.has(version)) { | ||
if (rejectedVersions.has(globalVersion)) { | ||
return false; | ||
} | ||
|
||
const m = version.match(re); | ||
if (!m) { | ||
const globalVersionMatch = globalVersion.match(re); | ||
if (!globalVersionMatch) { | ||
// cannot parse other version | ||
rejectedVersions.add(version); | ||
return false; | ||
// we cannot guarantee compatibility so we always noop | ||
return _reject(globalVersion); | ||
} | ||
|
||
const otherVersionParsed = { | ||
major: +m[1], | ||
minor: +m[2], | ||
patch: +m[3], | ||
const globalVersionParsed = { | ||
major: +globalVersionMatch[1], | ||
minor: +globalVersionMatch[2], | ||
patch: +globalVersionMatch[3], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Semver also has "-+", considering we are about to release an "-RC1" this starts to get complicated... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, just noticed the tests below using "alpha".. so ignore the above about pre-release and build# There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Someone note the time @MSNev advocated for an increased bundle size 🤣 |
||
}; | ||
|
||
// major versions must match | ||
if (ownVersionParsed.major !== otherVersionParsed.major) { | ||
rejectedVersions.add(version); | ||
return false; | ||
if (ownVersionParsed.major !== globalVersionParsed.major) { | ||
return _reject(globalVersion); | ||
} | ||
|
||
// if major version is 0, minor is treated like major and patch is treated like minor | ||
if (ownVersionParsed.major === 0) { | ||
if (ownVersionParsed.minor !== otherVersionParsed.minor) { | ||
rejectedVersions.add(version); | ||
return false; | ||
} | ||
|
||
if (ownVersionParsed.patch < otherVersionParsed.patch) { | ||
rejectedVersions.add(version); | ||
return false; | ||
if ( | ||
ownVersionParsed.minor === globalVersionParsed.minor && | ||
ownVersionParsed.patch <= globalVersionParsed.patch | ||
) { | ||
return _accept(globalVersion); | ||
} | ||
|
||
acceptedVersions.add(version); | ||
return true; | ||
return _reject(globalVersion); | ||
} | ||
|
||
if (ownVersionParsed.minor < otherVersionParsed.minor) { | ||
rejectedVersions.add(version); | ||
return false; | ||
if (ownVersionParsed.minor <= globalVersionParsed.minor) { | ||
return _accept(globalVersion); | ||
} | ||
|
||
acceptedVersions.add(version); | ||
return true; | ||
return _reject(globalVersion); | ||
}; | ||
} | ||
|
||
/** | ||
* Test an API version to see if it is compatible with this API. | ||
* | ||
* - Exact match is always compatible | ||
* - Major versions must always match | ||
* - The minor version of the API module requesting access to the global API must be greater or equal to the minor version of this API | ||
* - Major versions must match exactly | ||
* - 1.x package cannot use global 2.x package | ||
* - 2.x package cannot use global 1.x package | ||
* - The minor version of the API module requesting access to the global API must be less than or equal to the minor version of this API | ||
* - 1.3 package may use 1.4 global because the later global contains all functions 1.3 expects | ||
* - 1.4 package may NOT use 1.3 global because it may try to call functions which don't exist on 1.3 | ||
* - If the major version is 0, the minor version is treated as the major and the patch is treated as the minor | ||
* - Patch and build tag differences are not considered at this time | ||
* | ||
* @param version version of the API requesting an instance of the global API | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes me wonder if we couldn't just vendor
semver
package ? Seems error prone to re-implement the logic ourselves. That would weight for browser but i'm just throwing the idea out thereThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The semver satisfies is more strict than we are. This could be a good thing though. If global is
1.3.3
, we will be able to call it from1.3.2
and1.3.4
. With the semver package we would only be able to call it from1.3.2
. Semver will never say an earlier version of a package satisfies. This would require end-user applications to always have the latest patch versions. Honestly may not be such a bad thing.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, you can pimp a version like 1.3.3 to
1.3.x
thensemver.satisfies()
will be fine with 1.3.0 and 1.3.5.semver offers a lot APIs to parse/modify versions. It's also possible to e.g. include pre-releases.
I think if we need something in version checking which semver can't do it's most likely problematic.
Main question is do we want one more dependency in API. Don't think there is any real world node application out there which hasn't semver already in it's node_modules tree somewhere.
Can't tell for browser.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without semver:
With semver:
Looks like a 0.17 MiB difference if we add the dependency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my experience this is about right
I suspect it's much lower since semver is mostly used for dependency management and the browser doesn't often have reason for that after compilation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes these things are for sure possible. We need to decide what behavior we want in this regard either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't get concensus right now, i think we could still refactor this later as its purely internal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I revamped the test suite to be much more comprehensive and much more clear 2b60af9. Makes me more comfortable leaving the semver package out of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not purely internal. If we adopt semver, we will get stricter checks for prerelease versions. If we release our version, then
1.0.0
,1.0.0-alpha.1
, and1.0.0-alpha.2
will all be compatible with each other in both directions, which means we will be unable to make any changes once we release RC.