Skip to content

Commit 35ce373

Browse files
committed
添加依赖自动更新
1 parent 20ebfdd commit 35ce373

2 files changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
from typing import Iterable
5+
from urllib.request import urlopen
6+
from packaging.requirements import Requirement
7+
from packaging.utils import canonicalize_name
8+
from packaging.version import InvalidVersion, Version
9+
import json
10+
import os
11+
12+
import tomlkit
13+
14+
15+
PACKAGE_NAME = os.environ.get("DASHBOARD_PACKAGE_NAME", "maibot-dashboard")
16+
PYPROJECT_PATH = Path("pyproject.toml")
17+
REQUIREMENTS_PATH = Path("requirements.txt")
18+
PYPI_JSON_URL = f"https://pypi.org/pypi/{PACKAGE_NAME}/json"
19+
20+
21+
def find_dashboard_requirement(requirements: Iterable[str]) -> Requirement:
22+
normalized_package_name = canonicalize_name(PACKAGE_NAME)
23+
24+
for dependency in requirements:
25+
parsed_requirement = Requirement(dependency)
26+
if canonicalize_name(parsed_requirement.name) == normalized_package_name:
27+
return parsed_requirement
28+
29+
raise RuntimeError(f"未在依赖列表中找到 {PACKAGE_NAME}")
30+
31+
32+
def extract_pinned_min_version(requirement: Requirement) -> Version:
33+
matched_versions = [
34+
Version(specifier.version)
35+
for specifier in requirement.specifier
36+
if specifier.operator in {">=", "=="}
37+
]
38+
39+
if len(matched_versions) != 1:
40+
raise RuntimeError(f"{requirement} 的版本约束无法唯一定位当前 dashboard 版本")
41+
42+
return matched_versions[0]
43+
44+
45+
def get_latest_dev_version() -> Version:
46+
with urlopen(PYPI_JSON_URL, timeout=30) as response:
47+
pypi_data = json.load(response)
48+
49+
dev_versions: list[Version] = []
50+
for release_version, release_files in pypi_data["releases"].items():
51+
if not release_files or all(release_file.get("yanked", False) for release_file in release_files):
52+
continue
53+
54+
try:
55+
parsed_version = Version(release_version)
56+
except InvalidVersion:
57+
continue
58+
59+
if parsed_version.is_devrelease:
60+
dev_versions.append(parsed_version)
61+
62+
if not dev_versions:
63+
raise RuntimeError(f"PyPI 上没有找到 {PACKAGE_NAME} 的 dev 版本")
64+
65+
return max(dev_versions)
66+
67+
68+
def update_pyproject(latest_version: Version) -> bool:
69+
document = tomlkit.parse(PYPROJECT_PATH.read_text(encoding="utf-8"))
70+
dependencies = document["project"]["dependencies"]
71+
current_requirement = find_dashboard_requirement(str(item) for item in dependencies)
72+
current_version = extract_pinned_min_version(current_requirement)
73+
74+
if latest_version <= current_version:
75+
print(f"pyproject.toml 已是最新 dev 版本: {current_version}")
76+
return False
77+
78+
normalized_package_name = canonicalize_name(PACKAGE_NAME)
79+
updated_dependency = f"{PACKAGE_NAME}>={latest_version}"
80+
81+
for index, dependency in enumerate(dependencies):
82+
parsed_requirement = Requirement(str(dependency))
83+
if canonicalize_name(parsed_requirement.name) == normalized_package_name:
84+
dependencies[index] = updated_dependency
85+
break
86+
87+
PYPROJECT_PATH.write_text(tomlkit.dumps(document), encoding="utf-8")
88+
print(f"pyproject.toml: {current_version} -> {latest_version}")
89+
return True
90+
91+
92+
def update_requirements(latest_version: Version) -> bool:
93+
lines = REQUIREMENTS_PATH.read_text(encoding="utf-8").splitlines(keepends=True)
94+
current_requirement = find_dashboard_requirement(
95+
line.strip() for line in lines if line.strip() and not line.strip().startswith("#")
96+
)
97+
current_version = extract_pinned_min_version(current_requirement)
98+
99+
if latest_version <= current_version:
100+
print(f"requirements.txt 已是最新 dev 版本: {current_version}")
101+
return False
102+
103+
normalized_package_name = canonicalize_name(PACKAGE_NAME)
104+
updated_requirement = f"{PACKAGE_NAME}>={latest_version}"
105+
106+
for index, line in enumerate(lines):
107+
stripped_line = line.strip()
108+
if not stripped_line or stripped_line.startswith("#"):
109+
continue
110+
111+
parsed_requirement = Requirement(stripped_line)
112+
if canonicalize_name(parsed_requirement.name) == normalized_package_name:
113+
if line.endswith("\r\n"):
114+
newline = "\r\n"
115+
elif line.endswith("\n"):
116+
newline = "\n"
117+
else:
118+
newline = ""
119+
lines[index] = f"{updated_requirement}{newline}"
120+
break
121+
122+
REQUIREMENTS_PATH.write_text("".join(lines), encoding="utf-8")
123+
print(f"requirements.txt: {current_version} -> {latest_version}")
124+
return True
125+
126+
127+
def main() -> None:
128+
latest_version = get_latest_dev_version()
129+
print(f"PyPI 最新 dashboard dev 版本: {latest_version}")
130+
131+
pyproject_updated = update_pyproject(latest_version)
132+
requirements_updated = update_requirements(latest_version)
133+
134+
if pyproject_updated != requirements_updated:
135+
raise RuntimeError("pyproject.toml 与 requirements.txt 的 dashboard 依赖更新状态不一致")
136+
137+
138+
if __name__ == "__main__":
139+
main()
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Update Dashboard Dependency
2+
3+
on:
4+
workflow_run:
5+
workflows:
6+
- Publish WebUI Dist
7+
types:
8+
- completed
9+
branches:
10+
- dev
11+
schedule:
12+
- cron: "17 * * * *"
13+
workflow_dispatch:
14+
15+
permissions:
16+
contents: write
17+
18+
concurrency:
19+
group: update-dashboard-dependency-dev
20+
cancel-in-progress: false
21+
22+
jobs:
23+
update-dev-dependency:
24+
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
25+
runs-on: ubuntu-24.04
26+
steps:
27+
- name: Checkout dev
28+
uses: actions/checkout@v4
29+
with:
30+
ref: dev
31+
fetch-depth: 0
32+
33+
- name: Setup uv
34+
uses: astral-sh/setup-uv@v5
35+
36+
- name: Check PyPI latest dashboard dev package
37+
run: |
38+
uv run --no-project --with packaging --with tomlkit python .github/scripts/update_dashboard_dependency.py
39+
40+
- name: Commit dependency update
41+
run: |
42+
set -euo pipefail
43+
44+
if git diff --quiet -- pyproject.toml requirements.txt; then
45+
echo "没有发现 dashboard dev 依赖更新"
46+
exit 0
47+
fi
48+
49+
git config user.name "github-actions[bot]"
50+
git config user.email "github-actions[bot]@users.noreply.github.com"
51+
git add pyproject.toml requirements.txt
52+
git commit -m "chore: 更新 dashboard dev 依赖"
53+
git pull --rebase origin dev
54+
git push origin HEAD:dev

0 commit comments

Comments
 (0)