refactor: bundled webui static files into wheel and replace astrbot cli log with English (#5665)

* refactor: bundled webui static files into wheel and replace astrbot cli log with English

- Translated and standardized log messages in cmd_conf.py for better clarity.
- Updated initialization logic in cmd_init.py to provide clearer user prompts and error handling.
- Improved plugin management commands in cmd_plug.py with consistent language and error messages.
- Enhanced run command in cmd_run.py with clearer status messages and error handling.
- Updated utility functions in basic.py and plugin.py to improve readability and maintainability.
- Added version comparison logic in version_comparator.py with clearer comments.
- Enhanced logging configuration in log.py to suppress noisy loggers.
- Updated the updater logic in updator.py to provide clearer error messages for users.
- Improved IO utility functions in io.py to handle dashboard versioning more effectively.
- Enhanced dashboard server logic in server.py to prioritize bundled assets and improve user feedback.
- Updated pyproject.toml to include bundled dashboard assets and custom build hooks.
- Added a custom build script (hatch_build.py) to automate dashboard builds during package creation.

* refactor: improve exception messages and formatting in CLI command validation

* perf: change npm install to npm ci for consistent dependency installation

* fix
This commit is contained in:
Soulter
2026-03-03 12:58:59 +08:00
committed by GitHub
parent 0711ec346f
commit 6b642d7674
15 changed files with 310 additions and 187 deletions

View File

@@ -13,22 +13,22 @@ from .version_comparator import VersionComparator
class PluginStatus(str, Enum):
INSTALLED = "已安装"
NEED_UPDATE = "需更新"
NOT_INSTALLED = "未安装"
NOT_PUBLISHED = "未发布"
INSTALLED = "installed"
NEED_UPDATE = "needs-update"
NOT_INSTALLED = "not-installed"
NOT_PUBLISHED = "unpublished"
def get_git_repo(url: str, target_path: Path, proxy: str | None = None) -> None:
"""从 Git 仓库下载代码并解压到指定路径"""
"""Download code from a Git repository and extract to the specified path"""
temp_dir = Path(tempfile.mkdtemp())
try:
# 解析仓库信息
# Parse repository info
repo_namespace = url.split("/")[-2:]
author = repo_namespace[0]
repo = repo_namespace[1]
# 尝试获取最新的 release
# Try to get the latest release
release_url = f"https://api.github.com/repos/{author}/{repo}/releases"
try:
with httpx.Client(
@@ -40,21 +40,21 @@ def get_git_repo(url: str, target_path: Path, proxy: str | None = None) -> None:
releases = resp.json()
if releases:
# 使用最新的 release
# Use the latest release
download_url = releases[0]["zipball_url"]
else:
# 没有 release,使用默认分支
click.echo(f"正在从默认分支下载 {author}/{repo}")
# No release found, use default branch
click.echo(f"Downloading {author}/{repo} from default branch")
download_url = f"https://github.com/{author}/{repo}/archive/refs/heads/master.zip"
except Exception as e:
click.echo(f"获取 release 信息失败: {e},将直接使用提供的 URL")
click.echo(f"Failed to get release info: {e}. Using provided URL directly")
download_url = url
# 应用代理
# Apply proxy
if proxy:
download_url = f"{proxy}/{download_url}"
# 下载并解压
# Download and extract
with httpx.Client(
proxy=proxy if proxy else None,
follow_redirects=True,
@@ -65,7 +65,7 @@ def get_git_repo(url: str, target_path: Path, proxy: str | None = None) -> None:
and "archive/refs/heads/master.zip" in download_url
):
alt_url = download_url.replace("master.zip", "main.zip")
click.echo("master 分支不存在,尝试下载 main 分支")
click.echo("Branch 'master' not found, trying 'main' branch")
resp = client.get(alt_url)
resp.raise_for_status()
else:
@@ -84,13 +84,13 @@ def get_git_repo(url: str, target_path: Path, proxy: str | None = None) -> None:
def load_yaml_metadata(plugin_dir: Path) -> dict:
""" metadata.yaml 文件加载插件元数据
"""Load plugin metadata from metadata.yaml file
Args:
plugin_dir: 插件目录路径
plugin_dir: Plugin directory path
Returns:
dict: 包含元数据的字典,如果读取失败则返回空字典
dict: Dictionary containing metadata, or empty dict if loading fails
"""
yaml_path = plugin_dir / "metadata.yaml"
@@ -98,33 +98,33 @@ def load_yaml_metadata(plugin_dir: Path) -> dict:
try:
return yaml.safe_load(yaml_path.read_text(encoding="utf-8")) or {}
except Exception as e:
click.echo(f"读取 {yaml_path} 失败: {e}", err=True)
click.echo(f"Failed to read {yaml_path}: {e}", err=True)
return {}
def build_plug_list(plugins_dir: Path) -> list:
"""构建插件列表,包含本地和在线插件信息
"""Build plugin list containing local and online plugin information
Args:
plugins_dir (Path): 插件目录路径
plugins_dir (Path): Plugin directory path
Returns:
list: 包含插件信息的字典列表
list: List of dicts containing plugin information
"""
# 获取本地插件信息
# Get local plugin info
result = []
if plugins_dir.exists():
for plugin_name in [d.name for d in plugins_dir.glob("*") if d.is_dir()]:
plugin_dir = plugins_dir / plugin_name
# metadata.yaml 加载元数据
# Load metadata from metadata.yaml
metadata = load_yaml_metadata(plugin_dir)
if "desc" not in metadata and "description" in metadata:
metadata["desc"] = metadata["description"]
# 如果成功加载元数据,添加到结果列表
# If metadata loaded successfully, add to result list
if metadata and all(
k in metadata for k in ["name", "desc", "version", "author", "repo"]
):
@@ -140,7 +140,7 @@ def build_plug_list(plugins_dir: Path) -> list:
},
)
# 获取在线插件列表
# Get online plugin list
online_plugins = []
try:
with httpx.Client() as client:
@@ -160,13 +160,13 @@ def build_plug_list(plugins_dir: Path) -> list:
},
)
except Exception as e:
click.echo(f"获取在线插件列表失败: {e}", err=True)
click.echo(f"Failed to get online plugin list: {e}", err=True)
# 与在线插件比对,更新状态
# Compare with online plugins and update status
online_plugin_names = {plugin["name"] for plugin in online_plugins}
for local_plugin in result:
if local_plugin["name"] in online_plugin_names:
# 查找对应的在线插件
# Find the corresponding online plugin
online_plugin = next(
p for p in online_plugins if p["name"] == local_plugin["name"]
)
@@ -179,10 +179,10 @@ def build_plug_list(plugins_dir: Path) -> list:
):
local_plugin["status"] = PluginStatus.NEED_UPDATE
else:
# 本地插件未在线上发布
# Local plugin is not published online
local_plugin["status"] = PluginStatus.NOT_PUBLISHED
# 添加未安装的在线插件
# Add uninstalled online plugins
for online_plugin in online_plugins:
if not any(plugin["name"] == online_plugin["name"] for plugin in result):
result.append(online_plugin)
@@ -196,19 +196,19 @@ def manage_plugin(
is_update: bool = False,
proxy: str | None = None,
) -> None:
"""安装或更新插件
"""Install or update a plugin
Args:
plugin (dict): 插件信息字典
plugins_dir (Path): 插件目录
is_update (bool, optional): 是否为更新操作. 默认为 False
proxy (str, optional): 代理服务器地址
plugin (dict): Plugin info dict
plugins_dir (Path): Plugins directory
is_update (bool, optional): Whether this is an update operation. Defaults to False
proxy (str, optional): Proxy server address
"""
plugin_name = plugin["name"]
repo_url = plugin["repo"]
# 如果是更新且有本地路径,直接使用本地路径
# If updating and local path exists, use it directly
if is_update and plugin.get("local_path"):
target_path = Path(plugin["local_path"])
else:
@@ -216,11 +216,13 @@ def manage_plugin(
backup_path = Path(f"{target_path}_backup") if is_update else None
# 检查插件是否存在
# Check if plugin exists
if is_update and not target_path.exists():
raise click.ClickException(f"插件 {plugin_name} 未安装,无法更新")
raise click.ClickException(
f"Plugin {plugin_name} is not installed and cannot be updated"
)
# 备份现有插件
# Backup existing plugin
if is_update and backup_path is not None and backup_path.exists():
shutil.rmtree(backup_path)
if is_update and backup_path is not None:
@@ -228,19 +230,21 @@ def manage_plugin(
try:
click.echo(
f"正在从 {repo_url} {'更新' if is_update else '下载'}插件 {plugin_name}...",
f"{'Updating' if is_update else 'Downloading'} plugin {plugin_name} from {repo_url}...",
)
get_git_repo(repo_url, target_path, proxy)
# 更新成功,删除备份
# Update succeeded, delete backup
if is_update and backup_path is not None and backup_path.exists():
shutil.rmtree(backup_path)
click.echo(f"插件 {plugin_name} {'更新' if is_update else '安装'}成功")
click.echo(
f"Plugin {plugin_name} {'updated' if is_update else 'installed'} successfully"
)
except Exception as e:
if target_path.exists():
shutil.rmtree(target_path, ignore_errors=True)
if is_update and backup_path is not None and backup_path.exists():
shutil.move(backup_path, target_path)
raise click.ClickException(
f"{'更新' if is_update else '安装'}插件 {plugin_name} 时出错: {e}",
f"Error {'updating' if is_update else 'installing'} plugin {plugin_name}: {e}",
)