mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-17 01:49:15 +08:00
@@ -50,6 +50,7 @@ ruff check .
|
||||
5. Use English for all new comments.
|
||||
6. For path handling, use `pathlib.Path` instead of string paths, and use `astrbot.core.utils.path_utils` to get the AstrBot data and temp directory.
|
||||
7. When backend API routes, request/response schemas, or OpenAPI definitions change, regenerate the frontend API client by running `cd dashboard && pnpm generate:api`.
|
||||
8. When updating the project version, keep `[project].version` in `pyproject.toml` and `VERSION` in `astrbot/core/config/default.py` in sync.
|
||||
|
||||
### KISS and First Principles
|
||||
|
||||
@@ -108,7 +109,7 @@ Prepare a release from a clean worktree with:
|
||||
uv run python scripts/prepare_release.py 4.25.0
|
||||
```
|
||||
|
||||
The script updates `pyproject.toml`, creates `changelogs/v4.25.0.md`, runs the required Python checks, and prints the remaining steps. Use these flags when needed:
|
||||
The script updates `pyproject.toml` and `astrbot/core/config/default.py`, creates `changelogs/v4.25.0.md`, runs the required Python checks, and prints the remaining steps. Use these flags when needed:
|
||||
|
||||
```bash
|
||||
uv run python scripts/prepare_release.py 4.25.0 --generate-api-client
|
||||
|
||||
@@ -1,39 +1,11 @@
|
||||
"""如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from importlib.metadata import PackageNotFoundError
|
||||
from importlib.metadata import version as package_version
|
||||
from pathlib import Path
|
||||
|
||||
from astrbot.core.computer.booters.cua_defaults import CUA_DEFAULT_CONFIG
|
||||
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
||||
from astrbot.core.utils.toml_parser import read_pyproject_project_version
|
||||
|
||||
try:
|
||||
import tomllib
|
||||
except ModuleNotFoundError:
|
||||
# <= Python 3.10 compatibility
|
||||
tomllib = None
|
||||
|
||||
try:
|
||||
pyproject_path = Path(__file__).resolve().parents[3] / "pyproject.toml"
|
||||
if tomllib is None:
|
||||
VERSION = read_pyproject_project_version(pyproject_path)
|
||||
else:
|
||||
with pyproject_path.open("rb") as f:
|
||||
VERSION = tomllib.load(f)["project"]["version"]
|
||||
except (FileNotFoundError, IndexError, KeyError, TypeError, ValueError):
|
||||
try:
|
||||
VERSION = package_version("astrbot") # PEP 440 version style, e.g. 1.2.3a4
|
||||
match = re.match(r"^(\d+(?:\.\d+)*)(a|b|rc)(\d+)$", VERSION)
|
||||
if match:
|
||||
release, prerelease, number = match.groups()
|
||||
prerelease = {"a": "alpha", "b": "beta", "rc": "rc"}[prerelease]
|
||||
VERSION = f"{release}-{prerelease}.{number}"
|
||||
except PackageNotFoundError:
|
||||
VERSION = "0.0.0"
|
||||
|
||||
VERSION = "4.26.0-beta.10"
|
||||
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
||||
PERSONAL_WECHAT_CONFIG_METADATA = {
|
||||
"weixin_oc_base_url": {
|
||||
|
||||
@@ -72,46 +72,6 @@ def _read_dependency_array(raw_value: str) -> list[str]:
|
||||
raise ValueError("Unterminated project.dependencies array")
|
||||
|
||||
|
||||
def read_pyproject_project_version(pyproject_path: Path) -> str:
|
||||
"""Read the project version from a pyproject.toml file.
|
||||
|
||||
Args:
|
||||
pyproject_path: Path to the pyproject.toml file.
|
||||
|
||||
Returns:
|
||||
The value of the project.version field.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: The pyproject.toml file does not exist.
|
||||
ValueError: The project.version field is missing or unsupported.
|
||||
"""
|
||||
in_project_section = False
|
||||
for raw_line in pyproject_path.read_text(encoding="utf-8").splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
|
||||
if line.startswith("[") and line.endswith("]"):
|
||||
in_project_section = line == "[project]"
|
||||
continue
|
||||
|
||||
if not in_project_section:
|
||||
continue
|
||||
|
||||
key, separator, raw_value = line.partition("=")
|
||||
if key.strip() != "version":
|
||||
continue
|
||||
if not separator:
|
||||
raise ValueError("Missing value separator for project.version")
|
||||
|
||||
version, tail = _read_quoted_value(raw_value, "project.version")
|
||||
if tail and not tail.startswith("#"):
|
||||
raise ValueError("Unsupported content after project.version")
|
||||
return version
|
||||
|
||||
raise ValueError("Missing project.version")
|
||||
|
||||
|
||||
def read_pyproject_project_dependencies(pyproject_path: Path) -> list[str]:
|
||||
"""Read project dependencies from a pyproject.toml file.
|
||||
|
||||
|
||||
@@ -193,6 +193,37 @@ def update_pyproject_version(version: str) -> Path:
|
||||
raise ReleaseError("Missing [project].version in pyproject.toml")
|
||||
|
||||
|
||||
def update_default_config_version(version: str) -> Path:
|
||||
"""Update the hard-coded runtime version in default.py.
|
||||
|
||||
Args:
|
||||
version: Release version to write.
|
||||
|
||||
Returns:
|
||||
Path to the modified default.py file.
|
||||
|
||||
Raises:
|
||||
ReleaseError: The runtime version constant cannot be found or parsed.
|
||||
"""
|
||||
default_config_path = REPO_ROOT / "astrbot" / "core" / "config" / "default.py"
|
||||
lines = default_config_path.read_text(encoding="utf-8").splitlines(keepends=True)
|
||||
|
||||
for index, line in enumerate(lines):
|
||||
match = re.match(
|
||||
r"^(\s*VERSION\s*=\s*)([\"'])(.*?)(\2)(\s*(?:#.*)?)(\n?)$",
|
||||
line,
|
||||
)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
prefix, quote, _current, _closing_quote, suffix, newline = match.groups()
|
||||
lines[index] = f"{prefix}{quote}{version}{quote}{suffix}{newline}"
|
||||
default_config_path.write_text("".join(lines), encoding="utf-8")
|
||||
return default_config_path
|
||||
|
||||
raise ReleaseError("Missing VERSION in astrbot/core/config/default.py")
|
||||
|
||||
|
||||
def write_changelog(version: str, commits: list[str]) -> Path:
|
||||
"""Write a changelog draft for the release.
|
||||
|
||||
@@ -297,7 +328,14 @@ def commit_and_maybe_push(
|
||||
Raises:
|
||||
ReleaseError: Git add, commit, or push fails.
|
||||
"""
|
||||
git(["add", "pyproject.toml", str(changelog_path.relative_to(REPO_ROOT))])
|
||||
git(
|
||||
[
|
||||
"add",
|
||||
"pyproject.toml",
|
||||
"astrbot/core/config/default.py",
|
||||
str(changelog_path.relative_to(REPO_ROOT)),
|
||||
]
|
||||
)
|
||||
if args.generate_api_client:
|
||||
git(["add", "dashboard/src/api/generated"])
|
||||
|
||||
@@ -331,7 +369,9 @@ def print_next_steps(
|
||||
else:
|
||||
print("Next:")
|
||||
print(f"1. Review and polish {changelog_rel}")
|
||||
print(f"2. git add pyproject.toml {changelog_rel}")
|
||||
print(
|
||||
f"2. git add pyproject.toml astrbot/core/config/default.py {changelog_rel}"
|
||||
)
|
||||
print(f'3. git commit -m "chore: bump version to {version}"')
|
||||
print(f"4. git push -u {args.remote} {branch}")
|
||||
|
||||
@@ -414,6 +454,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
|
||||
commits = release_commits(tag)
|
||||
update_pyproject_version(version)
|
||||
update_default_config_version(version)
|
||||
changelog_path = write_changelog(version, commits)
|
||||
run_validation(args)
|
||||
|
||||
|
||||
@@ -2,87 +2,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from astrbot.core.utils.toml_parser import (
|
||||
read_pyproject_project_dependencies,
|
||||
read_pyproject_project_version,
|
||||
)
|
||||
|
||||
|
||||
def test_read_pyproject_project_version_reads_project_section(tmp_path: Path) -> None:
|
||||
pyproject_path = tmp_path / "pyproject.toml"
|
||||
pyproject_path.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
'version = "ignored"',
|
||||
"[project]",
|
||||
'name = "AstrBot"',
|
||||
'version = "1.2.3-beta.4" # release version',
|
||||
"[tool.example]",
|
||||
'version = "ignored-again"',
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert read_pyproject_project_version(pyproject_path) == "1.2.3-beta.4"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("version_line", "expected"),
|
||||
[
|
||||
('version = "1.2.3"', "1.2.3"),
|
||||
("version='1.2.3-beta.4'", "1.2.3-beta.4"),
|
||||
(' version = "1.2.3-rc.1" ', "1.2.3-rc.1"),
|
||||
],
|
||||
)
|
||||
def test_read_pyproject_project_version_accepts_simple_variants(
|
||||
tmp_path: Path,
|
||||
version_line: str,
|
||||
expected: str,
|
||||
) -> None:
|
||||
pyproject_path = tmp_path / "pyproject.toml"
|
||||
pyproject_path.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"[project]",
|
||||
'name = "AstrBot"',
|
||||
version_line,
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert read_pyproject_project_version(pyproject_path) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("version_line", "message"),
|
||||
[
|
||||
("version", "Missing value separator for project.version"),
|
||||
('version = "1.2.3', "Unterminated project.version string"),
|
||||
('version = "1.2.3" extra', "Unsupported content after project.version"),
|
||||
('version = ""', "Empty project.version value"),
|
||||
],
|
||||
)
|
||||
def test_read_pyproject_project_version_rejects_invalid_values(
|
||||
tmp_path: Path,
|
||||
version_line: str,
|
||||
message: str,
|
||||
) -> None:
|
||||
pyproject_path = tmp_path / "pyproject.toml"
|
||||
pyproject_path.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"[project]",
|
||||
'name = "AstrBot"',
|
||||
version_line,
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match=message):
|
||||
read_pyproject_project_version(pyproject_path)
|
||||
from astrbot.core.utils.toml_parser import read_pyproject_project_dependencies
|
||||
|
||||
|
||||
def test_read_pyproject_project_dependencies_reads_multiline_array(
|
||||
@@ -174,11 +94,3 @@ def test_read_pyproject_project_dependencies_rejects_invalid_values(
|
||||
|
||||
with pytest.raises(ValueError, match=message):
|
||||
read_pyproject_project_dependencies(pyproject_path)
|
||||
|
||||
|
||||
def test_read_pyproject_project_version_raises_when_missing(tmp_path: Path) -> None:
|
||||
pyproject_path = tmp_path / "pyproject.toml"
|
||||
pyproject_path.write_text('[project]\nname = "AstrBot"\n', encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="Missing project.version"):
|
||||
read_pyproject_project_version(pyproject_path)
|
||||
|
||||
Reference in New Issue
Block a user