fix(release): support fix releases on top of a weekly one

`v2026.40.1` matched neither the weekly pattern nor plain semver (2026 is
past the MSI major limit), so a fix release could not be built. Weekly
tags now take an optional patch: Cargo 2026.40.1, MSI 26.40.1, display
2026.40.1 (week zero-padded, as build.rs and the update check expect).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-05 15:58:53 +03:00
1 parent 47eef134cb
commit a3bcded498
4 files changed
+29 -15

No files matched your search

+7 -4
View File
@@ -6,10 +6,13 @@ use std::path::Path;
fn main() {
let version = std::env::var("CARGO_PKG_VERSION").expect("Cargo package version");
let parts: Vec<&str> = version.split('.').collect();
let app_version = if parts.len() == 3 && parts[0].len() == 4
&& parts[0].starts_with("20") && parts[2] == "0"
{
format!("{}.{:02}", parts[0], parts[1].parse::<u32>().expect("week number"))
let app_version = if parts.len() == 3 && parts[0].len() == 4 && parts[0].starts_with("20") {
let week = parts[1].parse::<u32>().expect("week number");
if parts[2] == "0" {
format!("{}.{week:02}", parts[0])
} else {
format!("{}.{week:02}.{}", parts[0], parts[2])
}
} else {
version.clone()
};
+9 -6
View File
@@ -21,12 +21,14 @@ def gh(*args):
def versions(tag):
version = tag.removeprefix("v")
weekly = re.fullmatch(r"(20\d{2})\.(\d{2})", version)
# `2026.40` is the weekly release; `2026.40.1` a fix release on top of it.
weekly = re.fullmatch(r"(20\d{2})\.(\d{2})(?:\.([1-9]\d*))?", version)
if weekly:
year, week = map(int, weekly.groups())
year, week = int(weekly[1]), int(weekly[2])
patch = int(weekly[3] or 0)
date.fromisocalendar(year, week, 1)
cargo = f"{year}.{week}.0"
msi = f"{year - 2000}.{week}.0"
cargo = f"{year}.{week}.{patch}"
msi = f"{year - 2000}.{week}.{patch}"
elif re.fullmatch(r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)", version):
cargo = msi = version
major, minor, patch = map(int, version.split("."))
@@ -43,8 +45,9 @@ def cargo_version():
def display_version(cargo):
year, week, patch = cargo.split(".")
if len(year) == 4 and year.startswith("20") and patch == "0":
return versions(f"{year}.{int(week):02}")["version"]
if len(year) == 4 and year.startswith("20"):
weekly = f"{year}.{int(week):02}"
return versions(weekly if patch == "0" else f"{weekly}.{patch}")["version"]
return cargo
+5 -1
View File
@@ -50,7 +50,7 @@ class ReleaseTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as temp:
binary = str(Path(temp) / "build-script")
subprocess.run(["rustc", str(Path(__file__).resolve().parents[1] / "build.rs"), "-o", binary], check=True)
for cargo in ("0.9.8", "2026.9.0", "2026.35.0", "2026.53.0", "2027.1.0"):
for cargo in ("0.9.8", "2026.9.0", "2026.35.0", "2026.53.0", "2027.1.0", "2026.40.1", "2026.5.2"):
result = subprocess.check_output([binary], text=True, env={**os.environ, "CARGO_PKG_VERSION": cargo})
self.assertIn(f"cargo:rustc-env=OCS_APP_VERSION={release.display_version(cargo)}\n", result)
@@ -61,6 +61,10 @@ class ReleaseTests(unittest.TestCase):
self.assertEqual(release.versions("2026.09")["cargo"], "2026.9.0")
self.assertEqual(release.display_version("2026.9.0"), "2026.09")
self.assertEqual(release.display_version("0.9.8"), "0.9.8")
self.assertEqual(release.versions("v2026.40.1"), {
"version": "2026.40.1", "cargo": "2026.40.1", "msi": "26.40.1", "tag": "v2026.40.1",
})
self.assertEqual(release.display_version("2026.5.2"), "2026.05.2")
self.assertEqual(datetime(2027, 1, 3, tzinfo=timezone.utc).strftime("v%G.%V"), "v2026.53")
for value in ("2026.00", "2026.54", "2027.53", "2026.9", "2026.35.0", "bad", "0.09.8"):
with self.assertRaises(ValueError, msg=value):
+8 -4
View File
@@ -137,10 +137,11 @@ fn parse_iso8601_utc(s: &str) -> Option<u64> {
fn is_newer(latest: &str, installed: &str) -> bool {
let parse = |version: &str| {
let version = version.trim_start_matches('v');
if let Some((year, week)) = version.split_once('.') {
if year.len() == 4 && year.starts_with("20") && !week.contains('.') {
return Some(semver::Version::new(year.parse().ok()?, week.parse().ok()?, 0));
}
// `2026.40` and its fix release `2026.40.1`; the week may be zero-padded.
let parts: Vec<&str> = version.split('.').collect();
if (2..=3).contains(&parts.len()) && parts[0].len() == 4 && parts[0].starts_with("20") {
let patch = parts.get(2).map_or(Some(0), |patch| patch.parse().ok())?;
return Some(semver::Version::new(parts[0].parse().ok()?, parts[1].parse().ok()?, patch));
}
semver::Version::parse(version).ok()
};
@@ -157,6 +158,9 @@ mod tests {
assert!(is_newer("2026.10", "2026.09"));
assert!(is_newer("2027.01", "2026.53"));
assert!(!is_newer("2026.35", "2026.35"));
assert!(is_newer("2026.40.1", "2026.40"));
assert!(is_newer("2026.05.2", "2026.05.1"));
assert!(is_newer("2026.41", "2026.40.1"));
assert!(!is_newer("0.9.8", "2026.35"));
assert!(!is_newer("invalid", "2026.35"));
}