[fix] prevent older releases from overwriting update metadata

This commit is contained in:
dijunkun
2026-10-21 23:03:32 +08:00
parent 171e559b0d
commit 6abcafc205
2 changed files with 62 additions and 14 deletions
+57 -14
View File
@@ -50,7 +50,7 @@ def sign_update(payload, private_key, expected_public_key):
return {"payload": payload, "signature": signature.hex()}
def version_info(version, fallback_date=None):
def version_fields(version):
version = version.removeprefix("v")
base = r"[0-9]+(?:\.[0-9]+){1,3}"
for pattern in (
@@ -60,18 +60,41 @@ def version_info(version, fallback_date=None):
):
match = re.fullmatch(pattern, version)
if match:
fields = match.groupdict()
date = fields.get("date")
release_date = (datetime.datetime.strptime(date, "%Y%m%d").date().isoformat()
if date else fallback_date)
if not release_date:
raise ValueError(f"Release date is missing for {version}")
datetime.date.fromisoformat(release_date)
return {"latest_version": version, "version": version,
"releaseDate": release_date, "patch": int(fields.get("patch") or 0)}
return match.groupdict()
raise ValueError(f"Invalid release version: {version}")
def version_info(version, fallback_date=None):
fields = version_fields(version)
version = version.removeprefix("v")
date = fields.get("date")
release_date = (datetime.datetime.strptime(date, "%Y%m%d").date().isoformat()
if date else fallback_date)
if not release_date:
raise ValueError(f"Release date is missing for {version}")
datetime.date.fromisoformat(release_date)
return {"latest_version": version, "version": version,
"releaseDate": release_date, "patch": int(fields.get("patch") or 0)}
def release_order(metadata):
"""Match client ordering: zero-padded numeric components, then patch, never date."""
version = metadata.get("latest_version") or metadata.get("version")
if not version:
return None
fields = version_fields(version)
numbers = tuple(int(part) for part in fields["base"].split("."))
inline_patch = fields.get("patch") or "0"
patch = int(inline_patch) if len(inline_patch) <= 4 else 0
# Legacy manifests may store the effective patch outside the version string.
explicit_patch = metadata.get("patch")
if isinstance(explicit_patch, str) and re.fullmatch(r"[0-9]+", explicit_patch):
explicit_patch = int(explicit_patch)
if type(explicit_patch) is int and 0 <= explicit_patch <= 2147483647:
patch = explicit_patch
return numbers + (0,) * (4 - len(numbers)) + (patch,)
def artifact_downloads(directory):
downloads = {}
private_key = os.environ.get("CROSSDESK_UPDATE_PRIVATE_KEY", "")
@@ -129,10 +152,25 @@ def merge_release(current, tag, name, notes, downloads, published_date=None):
if not tag.startswith("v"):
raise ValueError(f"Expected a version tag, got {tag!r}")
metadata = version_info(tag, published_date)
current_order = release_order(current)
if current_order is not None and release_order(metadata) < current_order:
current_version = current.get("latest_version") or current.get("version")
print(f"Skipping {tag}: older than current desktop release {current_version}")
return dict(current)
accepted_downloads = {}
for platform, download in downloads.items():
previous_order = release_order(current["downloads"].get(platform, {}))
if previous_order is not None and release_order(download) < previous_order:
print(f"Skipping {tag} download for {platform}: a newer version is already published")
continue
accepted_downloads[platform] = download
if mobile and not accepted_downloads:
return dict(current)
result = dict(current)
result.pop("platforms", None)
result["downloads"] = {**current["downloads"], **downloads}
result["downloads"] = {**current["downloads"], **accepted_downloads}
if not mobile:
result.update({**metadata, "releaseName": name, "releaseNotes": notes, "tagName": tag})
return result
@@ -159,11 +197,16 @@ def main():
parser.error("Supply --release-json or --tag, --name and --notes")
tag, name, notes = args.tag, args.name, args.notes.read_text()
published_date = None
current = json.loads(args.current.read_text())
current_text = args.current.read_text()
current = json.loads(current_text)
downloads = artifact_downloads(args.artifacts)
result = merge_release(current, tag, name, notes, downloads, published_date)
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
print(f"Updated {tag}: {', '.join(downloads)}")
if result == current:
args.output.write_text(current_text)
print(f"No changes to version.json for {tag}")
else:
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
print(f"Updated version.json for {tag}")
if __name__ == "__main__":
+5
View File
@@ -59,6 +59,11 @@ GitHub Actions 从分支手动打包时,在 `Build and Release` 的
`v1.7.2-20261020`,再验证到 `v1.7.3-20261020` 的升级。
分支手动构建只上传 Actions 构建产物,不会自动发布版本清单。
发布脚本按数字版本和 patch 比较版本,忽略构建日期。编辑或重新发布旧的
桌面 Release 时,保留服务器现有清单并在 Actions 日志中说明跳过原因;
同版本仍可更新发布说明,新版本正常发布。各平台下载记录也不会被较低
版本覆盖,包括独立移动端发布和桌面 Release 附带的移动端安装包。
本地构建可以设置同名环境变量,或使用:
```sh