Add plugin release workflow with version extraction and changelog management

Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-31 11:14:41 +00:00
parent 02fc846574
commit 10bddd2026
7 changed files with 1072 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
# GitHub Actions Workflow for Plugin Version Change Detection
# 插件版本变化检测工作流
#
# This workflow detects version changes in plugins when PRs are created or updated.
# 此工作流在创建或更新 PR 时检测插件的版本变化。
#
# What it does:
# 1. Compares plugin versions between base and head branches
# 2. Generates a summary of version changes
# 3. Comments on the PR with the changes (optional)
name: Plugin Version Check / 插件版本检查
on:
pull_request:
branches:
- main
paths:
- 'plugins/**/*.py'
permissions:
contents: read
pull-requests: write
jobs:
check-versions:
runs-on: ubuntu-latest
steps:
- name: Checkout PR head
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
path: head
- name: Checkout base branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha }}
path: base
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Extract and compare versions
id: compare
run: |
# Extract base versions
cd base
if [ -f scripts/extract_plugin_versions.py ]; then
python scripts/extract_plugin_versions.py --json --output ../base_versions.json
else
# Fallback if script doesn't exist in base
echo "[]" > ../base_versions.json
fi
cd ..
# Extract head versions
cd head
python scripts/extract_plugin_versions.py --json --output ../head_versions.json
# Compare versions
python scripts/extract_plugin_versions.py --compare ../base_versions.json --output ../changes.md
cd ..
echo "=== Version Changes ==="
cat changes.md
# Check if there are any changes
if grep -q "No changes detected" changes.md; then
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "has_changes=true" >> $GITHUB_OUTPUT
fi
# Store changes for comment
{
echo 'changes<<EOF'
cat changes.md
echo 'EOF'
} >> $GITHUB_OUTPUT
- name: Comment on PR
if: steps.compare.outputs.has_changes == 'true'
uses: actions/github-script@v7
with:
script: |
const changes = `${{ steps.compare.outputs.changes }}`;
const body = `## 🔍 Plugin Version Changes / 插件版本变化
${changes}
---
*This comment was generated automatically. / 此评论由自动生成。*
`;
// Find existing comment from github-actions bot
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment =>
(comment.user.login === 'github-actions[bot]' || comment.user.type === 'Bot') &&
comment.body.includes('Plugin Version Changes')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body,
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body,
});
}
- name: Summary
run: |
echo "## 🔍 Plugin Version Check Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.compare.outputs.has_changes }}" = "true" ]; then
echo "✅ **Version changes detected!**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
cat changes.md >> $GITHUB_STEP_SUMMARY
else
echo " **No version changes detected.**" >> $GITHUB_STEP_SUMMARY
fi

167
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,167 @@
# GitHub Actions Workflow for Plugin Release
# 插件发布工作流
#
# This workflow automates the release process for OpenWebUI plugins.
# 此工作流自动化 OpenWebUI 插件的发布流程。
#
# Triggers:
# - Manual trigger (workflow_dispatch) with custom release notes
# - Push of version tags (v*)
#
# What it does:
# 1. Scans all plugins for version information
# 2. Generates release notes with plugin versions
# 3. Creates a GitHub Release with the changelog
name: Plugin Release / 插件发布
on:
# Manual trigger with inputs
# 手动触发并提供输入
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., v1.0.0)'
required: true
type: string
release_title:
description: 'Release title (optional)'
required: false
type: string
release_notes:
description: 'Additional release notes (Markdown)'
required: false
type: string
prerelease:
description: 'Mark as pre-release'
required: false
type: boolean
default: false
# Trigger on version tags
# 版本标签触发
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Determine version
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
else
VERSION="${GITHUB_REF#refs/tags/}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Release version: $VERSION"
- name: Extract plugin versions
id: plugins
run: |
# Run the version extraction script
python scripts/extract_plugin_versions.py --json --output plugin_versions.json
python scripts/extract_plugin_versions.py --markdown --output plugin_table.md
# Output for debugging
echo "=== Plugin Versions (JSON) ==="
cat plugin_versions.json
echo ""
echo "=== Plugin Versions (Markdown) ==="
cat plugin_table.md
- name: Generate release notes
id: notes
run: |
VERSION="${{ steps.version.outputs.version }}"
TITLE="${{ github.event.inputs.release_title }}"
NOTES="${{ github.event.inputs.release_notes }}"
# Generate release notes header with version
echo "# ${VERSION} Release / 发布" > release_notes.md
echo "" >> release_notes.md
# Add custom title if provided
if [ -n "$TITLE" ]; then
echo "## $TITLE" >> release_notes.md
echo "" >> release_notes.md
fi
# Add custom notes if provided
if [ -n "$NOTES" ]; then
echo "## Release Notes / 发布说明" >> release_notes.md
echo "" >> release_notes.md
echo "$NOTES" >> release_notes.md
echo "" >> release_notes.md
fi
# Add plugin versions table
echo "## Plugin Versions / 插件版本" >> release_notes.md
echo "" >> release_notes.md
cat plugin_table.md >> release_notes.md
echo "" >> release_notes.md
# Add installation instructions
cat >> release_notes.md << 'INSTALL_INSTRUCTIONS'
## Installation / 安装
### From OpenWebUI Community
1. Open OpenWebUI Admin Panel
2. Navigate to Functions/Tools
3. Search for the plugin name
4. Click Install
### Manual Installation / 手动安装
1. Download the plugin file (`.py`)
2. Open OpenWebUI Admin Panel → Functions
3. Click "Create Function" → Import
4. Paste the plugin code
---
📚 [Documentation / 文档](https://fu-jie.github.io/awesome-openwebui/)
🐛 [Report Issues / 报告问题](https://github.com/Fu-Jie/awesome-openwebui/issues)
INSTALL_INSTRUCTIONS
echo "=== Release Notes ==="
cat release_notes.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
name: ${{ github.event.inputs.release_title || steps.version.outputs.version }}
body_path: release_notes.md
prerelease: ${{ github.event.inputs.prerelease || false }}
files: |
plugin_versions.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Summary
run: |
echo "## 🚀 Release Created Successfully!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Plugin Versions" >> $GITHUB_STEP_SUMMARY
cat plugin_table.md >> $GITHUB_STEP_SUMMARY

75
CHANGELOG.md Normal file
View File

@@ -0,0 +1,75 @@
# Changelog / 更新日志
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
本项目的所有重要更改都将记录在此文件中。
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
---
## [Unreleased] / 未发布
### Added / 新增
- 插件发布工作流 (Plugin release workflow)
### Changed / 变更
### Fixed / 修复
### Removed / 移除
---
## Plugin Versions / 插件版本
### Actions
| Plugin / 插件 | Version / 版本 |
|---------------|----------------|
| Smart Mind Map / 思维导图 | 0.8.0 |
| Flash Card / 闪记卡 | 0.2.1 |
| Export to Word / 导出为 Word | 0.1.0 |
| Export to Excel / 导出为 Excel | 0.3.3 |
| Deep Reading & Summary / 精读 | 0.1.0 / 2.0.0 |
| Smart Infographic / 智能信息图 | 1.3.0 |
### Filters
| Plugin / 插件 | Version / 版本 |
|---------------|----------------|
| Async Context Compression / 异步上下文压缩 | 1.1.0 |
| Context & Model Enhancement Filter | 0.2 |
| Gemini Manifold Companion | 1.7.0 |
| Gemini 多模态过滤器 | 0.3.2 |
### Pipes
| Plugin / 插件 | Version / 版本 |
|---------------|----------------|
| Gemini Manifold google_genai | 1.26.0 |
---
<!--
Release Template / 发布模板:
## [x.x.x] - YYYY-MM-DD
### Added / 新增
- New feature description
### Changed / 变更
- Change description
### Fixed / 修复
- Bug fix description
### Plugin Updates / 插件更新
- `plugin_name`: v0.x.0 -> v0.y.0
- Feature 1
- Feature 2
-->

View File

@@ -48,4 +48,40 @@
4. 推送到分支 (`git push origin feature/AmazingFeature`)。
5. 开启一个 Pull Request。
## 📦 版本更新与发布
当你更新插件时,请遵循以下流程:
### 1. 更新版本号
在插件文件的 docstring 中更新版本号(遵循[语义化版本](https://semver.org/lang/zh-CN/)
```python
"""
title: 我的插件
version: 0.2.0 # 更新此处
...
"""
```
### 2. 更新更新日志
在 `CHANGELOG.md` 的 `[Unreleased]` 部分添加你的更改:
```markdown
### Added / 新增
- 新功能描述
### Fixed / 修复
- Bug 修复描述
```
### 3. 发布流程
维护者会通过以下方式发布新版本:
- 手动触发 GitHub Actions 中的 "Plugin Release" 工作流
- 或创建版本标签 (`v*`)
详细说明请参阅 [发布工作流文档](docs/release-workflow.zh.md)。
再次感谢你的贡献!🚀

201
docs/release-workflow.md Normal file
View File

@@ -0,0 +1,201 @@
# Plugin Release Workflow / 插件发布工作流
This document describes the workflow for releasing plugin updates.
本文档描述了发布插件更新的工作流程。
---
## Overview / 概述
The release workflow consists of the following components:
发布工作流包含以下组件:
1. **CHANGELOG.md** - Records all notable changes / 记录所有重要更改
2. **Version Extraction Script** - Automatically extracts plugin versions / 自动提取插件版本
3. **GitHub Actions Workflows** - Automates the release process / 自动化发布流程
---
## Release Process / 发布流程
### Step 1: Update Plugin Version / 更新插件版本
When you make changes to a plugin, update the version number in the plugin's docstring:
当您对插件进行更改时,更新插件文档字符串中的版本号:
```python
"""
title: My Plugin
author: Fu-Jie
version: 0.2.0 # <- Update this / 更新这里
...
"""
```
### Step 2: Update CHANGELOG / 更新更新日志
Add your changes to the `[Unreleased]` section in `CHANGELOG.md`:
`CHANGELOG.md``[Unreleased]` 部分添加您的更改:
```markdown
## [Unreleased] / 未发布
### Added / 新增
- New feature in Smart Mind Map / 智能思维导图新功能
### Changed / 变更
- Improved performance / 性能优化
### Fixed / 修复
- Fixed bug in export / 修复导出 bug
```
### Step 3: Create a Release / 创建发布
#### Option A: Manual Release (Recommended) / 手动发布(推荐)
1. Go to GitHub Actions → "Plugin Release / 插件发布"
2. Click "Run workflow"
3. Fill in the details:
- **version**: e.g., `v1.0.0`
- **release_title**: e.g., "Smart Mind Map Major Update"
- **release_notes**: Additional notes in Markdown
- **prerelease**: Check if this is a pre-release
1. 前往 GitHub Actions → "Plugin Release / 插件发布"
2. 点击 "Run workflow"
3. 填写详细信息:
- **version**: 例如 `v1.0.0`
- **release_title**: 例如 "智能思维导图重大更新"
- **release_notes**: Markdown 格式的附加说明
- **prerelease**: 如果是预发布版本则勾选
#### Option B: Tag-based Release / 基于标签的发布
```bash
# Create and push a version tag / 创建并推送版本标签
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
```
This will automatically trigger the release workflow.
这将自动触发发布工作流。
### Step 4: Finalize CHANGELOG / 完成更新日志
After the release, move the `[Unreleased]` content to a new version section:
发布后,将 `[Unreleased]` 的内容移动到新的版本部分:
```markdown
## [Unreleased] / 未发布
<!-- Empty for now / 暂时为空 -->
## [1.0.0] - 2024-01-15
### Added / 新增
- New feature in Smart Mind Map
### Plugin Updates / 插件更新
- `Smart Mind Map`: v0.7.0 → v0.8.0
```
---
## Version Numbering / 版本编号
We follow [Semantic Versioning](https://semver.org/):
我们遵循[语义化版本](https://semver.org/lang/zh-CN/)
- **MAJOR (主版本)**: Breaking changes / 不兼容的变更
- **MINOR (次版本)**: New features, backwards compatible / 新功能,向后兼容
- **PATCH (补丁版本)**: Bug fixes / Bug 修复
### Examples / 示例
| Change Type / 变更类型 | Version Change / 版本变化 |
|----------------------|--------------------------|
| Bug fix / Bug 修复 | 0.1.0 → 0.1.1 |
| New feature / 新功能 | 0.1.1 → 0.2.0 |
| Breaking change / 不兼容变更 | 0.2.0 → 1.0.0 |
---
## GitHub Actions Workflows / GitHub Actions 工作流
### release.yml
**Trigger / 触发条件:**
- Manual workflow dispatch / 手动触发
- Push of version tags (`v*`) / 推送版本标签
**Actions / 动作:**
1. Extracts all plugin versions / 提取所有插件版本
2. Generates release notes / 生成发布说明
3. Creates GitHub Release / 创建 GitHub Release
### plugin-version-check.yml
**Trigger / 触发条件:**
- Pull requests that modify `plugins/**/*.py` / 修改 `plugins/**/*.py` 的 PR
**Actions / 动作:**
1. Compares plugin versions between base and PR / 比较基础分支和 PR 的插件版本
2. Comments on PR with version changes / 在 PR 上评论版本变化
---
## Scripts / 脚本
### extract_plugin_versions.py
Usage / 用法:
```bash
# Output to console / 输出到控制台
python scripts/extract_plugin_versions.py
# Output as JSON / 输出为 JSON
python scripts/extract_plugin_versions.py --json
# Output as Markdown table / 输出为 Markdown 表格
python scripts/extract_plugin_versions.py --markdown
# Compare with previous version / 与之前版本比较
python scripts/extract_plugin_versions.py --compare old_versions.json
# Save to file / 保存到文件
python scripts/extract_plugin_versions.py --json --output versions.json
```
---
## Best Practices / 最佳实践
1. **Always update version numbers** when making functional changes to plugins
- 对插件进行功能性更改时**始终更新版本号**
2. **Write clear changelog entries** describing what changed and why
- 编写清晰的更新日志条目,描述更改内容和原因
3. **Test locally** before creating a release
- 在创建发布之前**本地测试**
4. **Use pre-releases** for testing new features
- 使用**预发布**测试新功能
5. **Reference issues** in changelog entries when applicable
- 在适用时在更新日志条目中**引用 issue**
---
## Author
Fu-Jie
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)

168
docs/release-workflow.zh.md Normal file
View File

@@ -0,0 +1,168 @@
# 插件发布工作流
本文档描述了发布插件更新的工作流程。
---
## 概述
发布工作流包含以下组件:
1. **CHANGELOG.md** - 记录所有重要更改
2. **版本提取脚本** - 自动提取插件版本信息
3. **GitHub Actions 工作流** - 自动化发布流程
---
## 发布流程
### 第 1 步:更新插件版本
当您对插件进行更改时,更新插件文档字符串中的版本号:
```python
"""
title: 我的插件
author: Fu-Jie
version: 0.2.0 # <- 更新这里
...
"""
```
### 第 2 步:更新更新日志
`CHANGELOG.md``[Unreleased]` 部分添加您的更改:
```markdown
## [Unreleased] / 未发布
### Added / 新增
- 智能思维导图新功能
### Changed / 变更
- 性能优化
### Fixed / 修复
- 修复导出 bug
```
### 第 3 步:创建发布
#### 方式 A手动发布推荐
1. 前往 GitHub Actions → "Plugin Release / 插件发布"
2. 点击 "Run workflow"
3. 填写详细信息:
- **version**: 例如 `v1.0.0`
- **release_title**: 例如 "智能思维导图重大更新"
- **release_notes**: Markdown 格式的附加说明
- **prerelease**: 如果是预发布版本则勾选
#### 方式 B基于标签的发布
```bash
# 创建并推送版本标签
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
```
这将自动触发发布工作流。
### 第 4 步:完成更新日志
发布后,将 `[Unreleased]` 的内容移动到新的版本部分:
```markdown
## [Unreleased] / 未发布
<!-- 暂时为空 -->
## [1.0.0] - 2024-01-15
### Added / 新增
- 智能思维导图新功能
### Plugin Updates / 插件更新
- `Smart Mind Map / 思维导图`: v0.7.0 → v0.8.0
```
---
## 版本编号
我们遵循[语义化版本](https://semver.org/lang/zh-CN/)
- **主版本 (MAJOR)**: 不兼容的 API 变更
- **次版本 (MINOR)**: 向后兼容的新功能
- **补丁版本 (PATCH)**: 向后兼容的 Bug 修复
### 示例
| 变更类型 | 版本变化 |
|---------|---------|
| Bug 修复 | 0.1.0 → 0.1.1 |
| 新功能 | 0.1.1 → 0.2.0 |
| 不兼容变更 | 0.2.0 → 1.0.0 |
---
## GitHub Actions 工作流
### release.yml
**触发条件:**
- 手动触发 (workflow_dispatch)
- 推送版本标签 (`v*`)
**动作:**
1. 提取所有插件版本
2. 生成发布说明
3. 创建 GitHub Release
### plugin-version-check.yml
**触发条件:**
- 修改 `plugins/**/*.py` 的 Pull Request
**动作:**
1. 比较基础分支和 PR 的插件版本
2. 在 PR 上评论版本变化
---
## 脚本使用
### extract_plugin_versions.py
```bash
# 输出到控制台
python scripts/extract_plugin_versions.py
# 输出为 JSON
python scripts/extract_plugin_versions.py --json
# 输出为 Markdown 表格
python scripts/extract_plugin_versions.py --markdown
# 与之前版本比较
python scripts/extract_plugin_versions.py --compare old_versions.json
# 保存到文件
python scripts/extract_plugin_versions.py --json --output versions.json
```
---
## 最佳实践
1. **始终更新版本号** - 对插件进行功能性更改时
2. **编写清晰的更新日志** - 描述更改内容和原因
3. **本地测试** - 在创建发布之前
4. **使用预发布** - 测试新功能
5. **引用 issue** - 在适用时在更新日志条目中
---
## 作者
Fu-Jie
GitHub: [Fu-Jie/awesome-openwebui](https://github.com/Fu-Jie/awesome-openwebui)

View File

@@ -0,0 +1,284 @@
#!/usr/bin/env python3
"""
Script to extract plugin version information from Python files.
用于从 Python 插件文件中提取版本信息的脚本。
This script scans the plugins directory and extracts metadata (title, version, author, description)
from Python files that follow the OpenWebUI plugin docstring format.
Usage:
python extract_plugin_versions.py # Output to console
python extract_plugin_versions.py --json # Output as JSON
python extract_plugin_versions.py --markdown # Output as Markdown table
python extract_plugin_versions.py --compare old.json # Compare with previous version file
"""
import argparse
import json
import os
import re
import sys
from pathlib import Path
from typing import Any
def extract_plugin_metadata(file_path: str) -> dict[str, Any] | None:
"""
Extract plugin metadata from a Python file's docstring.
从 Python 文件的文档字符串中提取插件元数据。
Args:
file_path: Path to the Python file
Returns:
Dictionary containing plugin metadata or None if not a valid plugin file
"""
try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return None
# Match the docstring at the beginning of the file (allowing leading whitespace/comments)
docstring_pattern = r'^\s*"""(.*?)"""'
match = re.search(docstring_pattern, content, re.DOTALL)
if not match:
return None
docstring = match.group(1)
# Extract metadata fields
metadata = {}
field_patterns = {
"title": r"title:\s*(.+?)(?:\n|$)",
"author": r"author:\s*(.+?)(?:\n|$)",
"author_url": r"author_url:\s*(.+?)(?:\n|$)",
"funding_url": r"funding_url:\s*(.+?)(?:\n|$)",
"version": r"version:\s*(.+?)(?:\n|$)",
"description": r"description:\s*(.+?)(?:\n|$)",
"requirements": r"requirements:\s*(.+?)(?:\n|$)",
}
for field, pattern in field_patterns.items():
field_match = re.search(pattern, docstring, re.IGNORECASE)
if field_match:
metadata[field] = field_match.group(1).strip()
# Only return if we found at least title and version
if "title" in metadata and "version" in metadata:
metadata["file_path"] = file_path
return metadata
return None
def scan_plugins_directory(plugins_dir: str) -> list[dict[str, Any]]:
"""
Scan the plugins directory and extract metadata from all plugin files.
扫描 plugins 目录并从所有插件文件中提取元数据。
Args:
plugins_dir: Path to the plugins directory
Returns:
List of plugin metadata dictionaries
"""
plugins = []
plugins_path = Path(plugins_dir)
if not plugins_path.exists():
print(f"Plugins directory not found: {plugins_dir}", file=sys.stderr)
return plugins
# Walk through all subdirectories
for root, _dirs, files in os.walk(plugins_path):
for file in files:
if file.endswith(".py") and not file.startswith("__"):
file_path = os.path.join(root, file)
metadata = extract_plugin_metadata(file_path)
if metadata:
# Determine plugin type from directory structure
rel_path = os.path.relpath(file_path, plugins_dir)
parts = rel_path.split(os.sep)
if len(parts) > 0:
metadata["type"] = parts[0] # actions, filters, pipes, etc.
plugins.append(metadata)
return plugins
def compare_versions(
current: list[dict], previous_file: str
) -> dict[str, list[dict]]:
"""
Compare current plugin versions with a previous version file.
比较当前插件版本与之前的版本文件。
Args:
current: List of current plugin metadata
previous_file: Path to JSON file with previous versions
Returns:
Dictionary with 'added', 'updated', 'removed' lists
"""
try:
with open(previous_file, "r", encoding="utf-8") as f:
previous = json.load(f)
except FileNotFoundError:
return {"added": current, "updated": [], "removed": []}
except json.JSONDecodeError:
print(f"Error parsing {previous_file}", file=sys.stderr)
return {"added": current, "updated": [], "removed": []}
# Create lookup dictionaries by title
current_by_title = {p["title"]: p for p in current}
previous_by_title = {p["title"]: p for p in previous}
result = {"added": [], "updated": [], "removed": []}
# Find added and updated plugins
for title, plugin in current_by_title.items():
if title not in previous_by_title:
result["added"].append(plugin)
elif plugin["version"] != previous_by_title[title]["version"]:
result["updated"].append(
{
"current": plugin,
"previous": previous_by_title[title],
}
)
# Find removed plugins
for title, plugin in previous_by_title.items():
if title not in current_by_title:
result["removed"].append(plugin)
return result
def format_markdown_table(plugins: list[dict]) -> str:
"""
Format plugins as a Markdown table.
将插件格式化为 Markdown 表格。
"""
lines = [
"| Plugin / 插件 | Version / 版本 | Type / 类型 | Description / 描述 |",
"|---------------|----------------|-------------|---------------------|",
]
for plugin in sorted(plugins, key=lambda x: (x.get("type", ""), x.get("title", ""))):
title = plugin.get("title", "Unknown")
version = plugin.get("version", "Unknown")
plugin_type = plugin.get("type", "Unknown").capitalize()
full_description = plugin.get("description", "")
description = full_description[:50]
if len(full_description) > 50:
description += "..."
lines.append(f"| {title} | {version} | {plugin_type} | {description} |")
return "\n".join(lines)
def format_release_notes(comparison: dict[str, list]) -> str:
"""
Format version comparison as release notes.
将版本比较格式化为发布说明。
"""
lines = []
if comparison["added"]:
lines.append("### 新增插件 / New Plugins")
for plugin in comparison["added"]:
lines.append(f"- **{plugin['title']}** v{plugin['version']}")
if plugin.get("description"):
lines.append(f" - {plugin['description']}")
lines.append("")
if comparison["updated"]:
lines.append("### 插件更新 / Plugin Updates")
for update in comparison["updated"]:
curr = update["current"]
prev = update["previous"]
lines.append(
f"- **{curr['title']}**: v{prev['version']} → v{curr['version']}"
)
lines.append("")
if comparison["removed"]:
lines.append("### 移除插件 / Removed Plugins")
for plugin in comparison["removed"]:
lines.append(f"- **{plugin['title']}** v{plugin['version']}")
lines.append("")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Extract and compare plugin version information"
)
parser.add_argument(
"--plugins-dir",
default="plugins",
help="Path to plugins directory (default: plugins)",
)
parser.add_argument(
"--json",
action="store_true",
help="Output as JSON",
)
parser.add_argument(
"--markdown",
action="store_true",
help="Output as Markdown table",
)
parser.add_argument(
"--compare",
metavar="FILE",
help="Compare with previous version JSON file",
)
parser.add_argument(
"--output",
"-o",
metavar="FILE",
help="Write output to file instead of stdout",
)
args = parser.parse_args()
# Scan plugins
plugins = scan_plugins_directory(args.plugins_dir)
# Generate output
if args.compare:
comparison = compare_versions(plugins, args.compare)
if args.json:
output = json.dumps(comparison, indent=2, ensure_ascii=False)
else:
output = format_release_notes(comparison)
if not output.strip():
output = "No changes detected. / 未检测到更改。"
elif args.json:
output = json.dumps(plugins, indent=2, ensure_ascii=False)
elif args.markdown:
output = format_markdown_table(plugins)
else:
# Default: simple list
lines = []
for plugin in sorted(plugins, key=lambda x: x.get("title", "")):
lines.append(f"{plugin.get('title', 'Unknown')}: v{plugin.get('version', '?')}")
output = "\n".join(lines)
# Write output
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(output)
print(f"Output written to {args.output}")
else:
print(output)
if __name__ == "__main__":
main()