release: v1.4.0
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
VERSION_FILE = ROOT / "VERSION"
|
||||
CHANGELOG_FILE = ROOT / "CHANGELOG.md"
|
||||
|
||||
|
||||
def parse_version(raw: str) -> tuple[int, int, int]:
|
||||
match = re.fullmatch(r"(\d+)\.(\d+)\.(\d+)", raw.strip())
|
||||
if not match:
|
||||
raise ValueError(f"Invalid version: {raw!r}")
|
||||
return tuple(int(part) for part in match.groups())
|
||||
|
||||
|
||||
def format_version(parts: tuple[int, int, int]) -> str:
|
||||
return ".".join(str(part) for part in parts)
|
||||
|
||||
|
||||
def bump(parts: tuple[int, int, int], level: str) -> tuple[int, int, int]:
|
||||
major, minor, patch = parts
|
||||
if level == "patch":
|
||||
return major, minor, patch + 1
|
||||
if level == "minor":
|
||||
return major, minor + 1, 0
|
||||
if level == "major":
|
||||
return major + 1, 0, 0
|
||||
raise ValueError(f"Unsupported bump level: {level}")
|
||||
|
||||
|
||||
def ensure_changelog_entry(version: str) -> None:
|
||||
text = CHANGELOG_FILE.read_text(encoding="utf-8")
|
||||
if re.search(rf"^## {re.escape(version)} - ", text, flags=re.MULTILINE):
|
||||
return
|
||||
heading = f"## {version} - TBD"
|
||||
lines = text.splitlines()
|
||||
if not lines:
|
||||
updated = f"# Changelog\n\n{heading}\n\n- TBD\n"
|
||||
else:
|
||||
updated = "\n".join([lines[0], "", heading, "", "- TBD", "", *lines[1:]]) + "\n"
|
||||
CHANGELOG_FILE.write_text(updated, encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("Usage: python scripts/bump_version.py [patch|minor|major|X.Y.Z]")
|
||||
|
||||
current = parse_version(VERSION_FILE.read_text(encoding="utf-8"))
|
||||
arg = sys.argv[1].strip()
|
||||
|
||||
if re.fullmatch(r"\d+\.\d+\.\d+", arg):
|
||||
new_version = arg
|
||||
else:
|
||||
new_version = format_version(bump(current, arg))
|
||||
|
||||
VERSION_FILE.write_text(new_version + "\n", encoding="utf-8")
|
||||
ensure_changelog_entry(new_version)
|
||||
print(f"Bumped version: {format_version(current)} -> {new_version}")
|
||||
print("Next step: python scripts/sync_version.py")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,70 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
VERSION_FILE = ROOT / "VERSION"
|
||||
VERSION = VERSION_FILE.read_text(encoding="utf-8").strip()
|
||||
DISPLAY_VERSION = f"v{VERSION}"
|
||||
|
||||
|
||||
TEXT_REPLACEMENTS = {
|
||||
ROOT / "README.md": [
|
||||
(r"- Version: `v[\d.]+`", f"- Version: `{DISPLAY_VERSION}`"),
|
||||
],
|
||||
ROOT / "README_ZH.md": [
|
||||
(r"- 版本:`v[\d.]+`", f"- 版本:`{DISPLAY_VERSION}`"),
|
||||
],
|
||||
ROOT / "FRONTEND_REDESIGN_REPORT.md": [
|
||||
(r"前端交付与重构报告(v[\d.]+)", f"前端交付与重构报告({DISPLAY_VERSION})"),
|
||||
],
|
||||
ROOT / "docs" / "API_ZH.md": [
|
||||
(r"PolyWeather API 文档(v[\d.]+)", f"PolyWeather API 文档({DISPLAY_VERSION})"),
|
||||
],
|
||||
ROOT / "docs" / "SUPABASE_SETUP_ZH.md": [
|
||||
(r"Supabase \+ 登录 \+ 支付接入说明(v[\d.]+)", f"Supabase + 登录 + 支付接入说明({DISPLAY_VERSION})"),
|
||||
],
|
||||
ROOT / "docs" / "TECH_DEBT.md": [
|
||||
(r"技术债与工程待办(v[\d.]+)", f"技术债与工程待办({DISPLAY_VERSION})"),
|
||||
],
|
||||
ROOT / "docs" / "TECH_DEBT_ZH.md": [
|
||||
(r"技术债与工程待办(v[\d.]+)", f"技术债与工程待办({DISPLAY_VERSION})"),
|
||||
],
|
||||
ROOT / "docs" / "payments" / "POLYGONSCAN_VERIFY.md": [
|
||||
(r"PolyWeatherCheckout PolygonScan 验证(v[\d.]+)", f"PolyWeatherCheckout PolygonScan 验证({DISPLAY_VERSION})"),
|
||||
],
|
||||
ROOT / "docs" / "deep-research-report.md": [
|
||||
(r"README 标注 `v[\d.]+`", f"README 标注 `{DISPLAY_VERSION}`"),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def sync_text_file(path: Path, replacements: list[tuple[str, str]]) -> None:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
updated = text
|
||||
for pattern, replacement in replacements:
|
||||
updated = re.sub(pattern, replacement, updated)
|
||||
if updated != text:
|
||||
path.write_text(updated, encoding="utf-8")
|
||||
|
||||
|
||||
def sync_frontend_package() -> None:
|
||||
path = ROOT / "frontend" / "package.json"
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
if data.get("version") != VERSION:
|
||||
data["version"] = VERSION
|
||||
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
for path, replacements in TEXT_REPLACEMENTS.items():
|
||||
sync_text_file(path, replacements)
|
||||
sync_frontend_package()
|
||||
print(f"Synchronized version to {VERSION}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user