class ParsedVersion { final int major; final int minor; final int patch; final String? prerelease; final String? build; const ParsedVersion({ required this.major, required this.minor, required this.patch, this.prerelease, this.build, }); bool get hasPrerelease => prerelease != null && prerelease!.isNotEmpty; bool sameCore(ParsedVersion other) { return major == other.major && minor == other.minor && patch == other.patch; } } final RegExp _versionPattern = RegExp( r'^[vV]?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$', ); ParsedVersion? parseVersion(String tag) { final match = _versionPattern.firstMatch(tag.trim()); if (match == null) return null; return ParsedVersion( major: int.parse(match.group(1)!), minor: int.parse(match.group(2)!), patch: int.parse(match.group(3)!), prerelease: match.group(4), build: match.group(5), ); } bool isNewerVersion(String localTag, String remoteTag) { final local = parseVersion(localTag); final remote = parseVersion(remoteTag); if (local == null || remote == null) return false; if (remote.hasPrerelease) { if (!local.hasPrerelease || !local.sameCore(remote)) return false; return _comparePrerelease(remote.prerelease!, local.prerelease!) > 0; } final coreCompare = _compareCore(remote, local); if (coreCompare != 0) return coreCompare > 0; return local.hasPrerelease; } int _compareCore(ParsedVersion a, ParsedVersion b) { final major = a.major.compareTo(b.major); if (major != 0) return major; final minor = a.minor.compareTo(b.minor); if (minor != 0) return minor; return a.patch.compareTo(b.patch); } int _comparePrerelease(String a, String b) { final left = a.split('.'); final right = b.split('.'); final length = left.length > right.length ? left.length : right.length; for (var i = 0; i < length; i++) { if (i >= left.length) return -1; if (i >= right.length) return 1; final leftPart = left[i]; final rightPart = right[i]; final leftNumber = int.tryParse(leftPart); final rightNumber = int.tryParse(rightPart); if (leftNumber != null && rightNumber != null) { final numberCompare = leftNumber.compareTo(rightNumber); if (numberCompare != 0) return numberCompare; continue; } if (leftNumber != null) return -1; if (rightNumber != null) return 1; final textCompare = leftPart.compareTo(rightPart); if (textCompare != 0) return textCompare; } return 0; }