From 02fc8465749a738bc0975842656311f179411703 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 11:05:36 +0000 Subject: [PATCH 1/3] Initial plan From 10bddd2026341c5fa4a3f25b03a383e1e2c357c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 11:14:41 +0000 Subject: [PATCH 2/3] Add plugin release workflow with version extraction and changelog management Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com> --- .github/workflows/plugin-version-check.yml | 141 ++++++++++ .github/workflows/release.yml | 167 ++++++++++++ CHANGELOG.md | 75 ++++++ CONTRIBUTING.md | 36 +++ docs/release-workflow.md | 201 +++++++++++++++ docs/release-workflow.zh.md | 168 ++++++++++++ scripts/extract_plugin_versions.py | 284 +++++++++++++++++++++ 7 files changed, 1072 insertions(+) create mode 100644 .github/workflows/plugin-version-check.yml create mode 100644 .github/workflows/release.yml create mode 100644 CHANGELOG.md create mode 100644 docs/release-workflow.md create mode 100644 docs/release-workflow.zh.md create mode 100644 scripts/extract_plugin_versions.py diff --git a/.github/workflows/plugin-version-check.yml b/.github/workflows/plugin-version-check.yml new file mode 100644 index 0000000..b0e0d2b --- /dev/null +++ b/.github/workflows/plugin-version-check.yml @@ -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<> $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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1144ec8 --- /dev/null +++ b/.github/workflows/release.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dbcdcbd --- /dev/null +++ b/CHANGELOG.md @@ -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 | + +--- + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ea56ca..80eda20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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)。 + 再次感谢你的贡献!🚀 diff --git a/docs/release-workflow.md b/docs/release-workflow.md new file mode 100644 index 0000000..8f272cf --- /dev/null +++ b/docs/release-workflow.md @@ -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] / 未发布 + + +## [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) diff --git a/docs/release-workflow.zh.md b/docs/release-workflow.zh.md new file mode 100644 index 0000000..7929fd6 --- /dev/null +++ b/docs/release-workflow.zh.md @@ -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) diff --git a/scripts/extract_plugin_versions.py b/scripts/extract_plugin_versions.py new file mode 100644 index 0000000..dda79a6 --- /dev/null +++ b/scripts/extract_plugin_versions.py @@ -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() From 5b8750c438d0f9657cd3a1c03974a69c61f9d77e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:06:31 +0000 Subject: [PATCH 3/3] Enhance release workflow with auto-release on merge and PR validation Co-authored-by: Fu-Jie <33599649+Fu-Jie@users.noreply.github.com> --- .github/workflows/plugin-version-check.yml | 89 +++++++- .github/workflows/release.yml | 243 ++++++++++++++++++--- docs/release-workflow.md | 210 +++++++++--------- docs/release-workflow.zh.md | 111 ++++++---- 4 files changed, 457 insertions(+), 196 deletions(-) diff --git a/.github/workflows/plugin-version-check.yml b/.github/workflows/plugin-version-check.yml index b0e0d2b..f98432d 100644 --- a/.github/workflows/plugin-version-check.yml +++ b/.github/workflows/plugin-version-check.yml @@ -7,7 +7,9 @@ # 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) +# 3. Comments on the PR with the changes +# 4. FAILS the check if plugin files are modified but no version update is detected +# (enforces version bump requirement for plugin changes) name: Plugin Version Check / 插件版本检查 @@ -82,16 +84,54 @@ jobs: echo 'EOF' } >> $GITHUB_OUTPUT - - name: Comment on PR + - name: Check PR description for update notes + id: check_description if: steps.compare.outputs.has_changes == 'true' uses: actions/github-script@v7 with: script: | + const prBody = context.payload.pull_request.body || ''; + + // Check if PR has meaningful description (at least 20 characters, excluding whitespace) + // Use [\s\S]*? for multiline HTML comment matching (compatible across JS engines) + const cleanBody = prBody.replace(/\s+/g, '').replace(//g, ''); + const hasDescription = cleanBody.length >= 20; + + console.log(`PR body length (cleaned): ${cleanBody.length}`); + console.log(`Has meaningful description: ${hasDescription}`); + + core.setOutput('has_description', hasDescription.toString()); + return hasDescription; + + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + const hasChanges = '${{ steps.compare.outputs.has_changes }}' === 'true'; + const hasDescription = '${{ steps.check_description.outputs.has_description }}' === 'true'; const changes = `${{ steps.compare.outputs.changes }}`; - const body = `## 🔍 Plugin Version Changes / 插件版本变化 + let statusIcon = ''; + let statusMessage = ''; - ${changes} + if (hasChanges && hasDescription) { + statusIcon = '✅'; + statusMessage = '版本更新检测通过!PR 包含版本变化和更新说明。\n\nVersion check passed! PR contains version changes and update description.'; + } else if (hasChanges && !hasDescription) { + statusIcon = '⚠️'; + statusMessage = '检测到版本更新,但 PR 描述过短。请在 PR 描述中添加更新说明(至少 20 个字符)。\n\nVersion update detected, but PR description is too short. Please add update notes in PR description (at least 20 characters).'; + } else { + statusIcon = '❌'; + statusMessage = '未检测到版本更新!修改插件文件时必须更新版本号。\n\nNo version update detected! You must update the version number when modifying plugin files.'; + } + + const body = `## ${statusIcon} Plugin Version Check / 插件版本检查 + + ${statusMessage} + + --- + + ${hasChanges ? `### 版本变化 / Version Changes\n\n${changes}` : ''} --- *This comment was generated automatically. / 此评论由自动生成。* @@ -106,11 +146,10 @@ jobs: const botComment = comments.find(comment => (comment.user.login === 'github-actions[bot]' || comment.user.type === 'Bot') && - comment.body.includes('Plugin Version Changes') + comment.body.includes('Plugin Version Check') ); if (botComment) { - // Update existing comment await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, @@ -118,7 +157,6 @@ jobs: body: body, }); } else { - // Create new comment await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, @@ -127,6 +165,36 @@ jobs: }); } + - name: Enforce version update requirement + if: steps.compare.outputs.has_changes == 'false' + run: | + echo "::error::❌ 未检测到版本更新!修改插件文件时必须更新版本号。" + echo "::error::No version update detected! You must update the version number when modifying plugin files." + echo "" + echo "请在插件文件的 docstring 中更新版本号:" + echo "Please update the version in your plugin's docstring:" + echo "" + echo '"""' + echo 'title: Your Plugin' + echo 'version: 0.2.0 # <- 更新此处 / Update this' + echo '...' + echo '"""' + exit 1 + + - name: Enforce PR description requirement + if: steps.compare.outputs.has_changes == 'true' && steps.check_description.outputs.has_description == 'false' + run: | + echo "::error::⚠️ PR 描述过短!请添加更新说明。" + echo "::error::PR description is too short! Please add update notes." + echo "" + echo "请在 PR 描述中添加以下内容:" + echo "Please add the following to your PR description:" + echo "" + echo "- 更新了哪些功能 / What features were updated" + echo "- 修复了哪些问题 / What issues were fixed" + echo "- 其他重要变更 / Other important changes" + exit 1 + - name: Summary run: | echo "## 🔍 Plugin Version Check Results" >> $GITHUB_STEP_SUMMARY @@ -136,6 +204,11 @@ jobs: echo "✅ **Version changes detected!**" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY cat changes.md >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.check_description.outputs.has_description }}" = "true" ]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "✅ **PR description check passed!**" >> $GITHUB_STEP_SUMMARY + fi else - echo "ℹ️ **No version changes detected.**" >> $GITHUB_STEP_SUMMARY + echo "❌ **No version changes detected - check failed!**" >> $GITHUB_STEP_SUMMARY fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1144ec8..07486c0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,24 +5,34 @@ # 此工作流自动化 OpenWebUI 插件的发布流程。 # # Triggers: +# - Push to main branch when plugins are modified (auto-release) # - 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 +# 1. Detects plugin version changes compared to the last release +# 2. Generates release notes with updated plugin information +# 3. Creates a GitHub Release with plugin files as downloadable assets +# 4. Supports multiple plugin updates in a single release name: Plugin Release / 插件发布 on: + # Auto-trigger on push to main when plugins are modified + push: + branches: + - main + paths: + - 'plugins/**/*.py' + tags: + - 'v*' + # Manual trigger with inputs - # 手动触发并提供输入 workflow_dispatch: inputs: version: - description: 'Release version (e.g., v1.0.0)' - required: true + description: 'Release version (e.g., v1.0.0). Leave empty for auto-generated version.' + required: false type: string release_title: description: 'Release title (optional)' @@ -38,17 +48,102 @@ on: type: boolean default: false - # Trigger on version tags - # 版本标签触发 - push: - tags: - - 'v*' - permissions: contents: write jobs: + check-changes: + runs-on: ubuntu-latest + outputs: + has_changes: ${{ steps.detect.outputs.has_changes }} + changed_plugins: ${{ steps.detect.outputs.changed_plugins }} + release_notes: ${{ steps.detect.outputs.release_notes }} + + 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: Detect plugin changes + id: detect + run: | + # Get the last release tag + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + if [ -z "$LAST_TAG" ]; then + echo "No previous release found, treating all plugins as new" + COMPARE_REF="$(git rev-list --max-parents=0 HEAD)" + else + echo "Comparing with last release: $LAST_TAG" + COMPARE_REF="$LAST_TAG" + fi + + # Get current plugin versions + python scripts/extract_plugin_versions.py --json --output current_versions.json + + # Get previous plugin versions by checking out old plugins + if git worktree add /tmp/old_repo ${COMPARE_REF} 2>/dev/null; then + if [ -d /tmp/old_repo/plugins ]; then + python scripts/extract_plugin_versions.py --plugins-dir /tmp/old_repo/plugins --json --output old_versions.json + else + echo "[]" > old_versions.json + fi + git worktree remove /tmp/old_repo 2>/dev/null || true + else + echo "Failed to create worktree, using empty version list" + echo "[]" > old_versions.json + fi + + # Compare versions and generate release notes + python scripts/extract_plugin_versions.py --compare old_versions.json --output changes.md + python scripts/extract_plugin_versions.py --compare old_versions.json --json --output changes.json + + 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 + echo "changed_plugins=" >> $GITHUB_OUTPUT + else + echo "has_changes=true" >> $GITHUB_OUTPUT + + # Extract changed plugin file paths using Python + python3 -c " + import json + with open('changes.json', 'r') as f: + data = json.load(f) + files = [] + for plugin in data.get('added', []): + if 'file_path' in plugin: + files.append(plugin['file_path']) + for update in data.get('updated', []): + if 'current' in update and 'file_path' in update['current']: + files.append(update['current']['file_path']) + print('\n'.join(files)) + " > changed_files.txt + + echo "changed_plugins<> $GITHUB_OUTPUT + cat changed_files.txt >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + # Store release notes + { + echo 'release_notes<> $GITHUB_OUTPUT + release: + needs: check-changes + if: needs.check-changes.outputs.has_changes == 'true' || github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest steps: @@ -65,10 +160,13 @@ jobs: - name: Determine version id: version run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.version }}" ]; then VERSION="${{ github.event.inputs.version }}" - else + elif [[ "${{ github.ref }}" == refs/tags/v* ]]; then VERSION="${GITHUB_REF#refs/tags/}" + else + # Auto-generate version based on date and run number + VERSION="v$(date +'%Y.%m.%d')-${{ github.run_number }}" fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Release version: $VERSION" @@ -76,61 +174,133 @@ jobs: - 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) ===" + echo "=== Plugin Versions ===" cat plugin_table.md + - name: Collect plugin files for release + id: collect_files + run: | + mkdir -p release_plugins + + CHANGED_PLUGINS="${{ needs.check-changes.outputs.changed_plugins }}" + + if [ -n "$CHANGED_PLUGINS" ]; then + echo "Collecting changed plugin files..." + echo "$CHANGED_PLUGINS" | while read -r file; do + if [ -n "$file" ] && [ -f "$file" ]; then + dir=$(dirname "$file") + mkdir -p "release_plugins/$dir" + cp "$file" "release_plugins/$file" + echo "Added: $file" + fi + done + else + echo "Collecting all plugin files..." + find plugins -name "*.py" -type f ! -name "__*" | while read -r file; do + dir=$(dirname "$file") + mkdir -p "release_plugins/$dir" + cp "$file" "release_plugins/$file" + done + fi + + # Create a zip file with error handling + cd release_plugins + if [ -n "$(ls -A . 2>/dev/null)" ]; then + if zip -r ../plugins_release.zip .; then + echo "Successfully created plugins_release.zip" + else + echo "Warning: Failed to create zip file, creating empty placeholder" + touch ../plugins_release.zip + fi + else + echo "No plugin files to zip, creating empty placeholder" + touch ../plugins_release.zip + fi + cd .. + + echo "=== Collected Files ===" + find release_plugins -name "*.py" -type f | head -20 + + - name: Get commit messages + id: commits + if: github.event_name == 'push' + run: | + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + if [ -n "$LAST_TAG" ]; then + COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"- %s" --no-merges -- plugins/ | head -20) + else + COMMITS=$(git log --pretty=format:"- %s" --no-merges -10 -- plugins/) + fi + + { + echo 'commits<> $GITHUB_OUTPUT + - name: Generate release notes id: notes run: | VERSION="${{ steps.version.outputs.version }}" TITLE="${{ github.event.inputs.release_title }}" NOTES="${{ github.event.inputs.release_notes }}" + DETECTED_CHANGES="${{ needs.check-changes.outputs.release_notes }}" + COMMITS="${{ steps.commits.outputs.commits }}" - # 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 "$DETECTED_CHANGES" ] && ! echo "$DETECTED_CHANGES" | grep -q "No changes detected"; then + echo "## What's Changed / 更新内容" >> release_notes.md + echo "" >> release_notes.md + echo "$DETECTED_CHANGES" >> release_notes.md + echo "" >> release_notes.md + fi + + if [ -n "$COMMITS" ]; then + echo "## Commits / 提交记录" >> release_notes.md + echo "" >> release_notes.md + echo "$COMMITS" >> release_notes.md + echo "" >> release_notes.md + fi + if [ -n "$NOTES" ]; then - echo "## Release Notes / 发布说明" >> release_notes.md + echo "## Additional 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 "## All 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' + cat >> release_notes.md << 'EOF' - ## Installation / 安装 + ## Download / 下载 - ### From OpenWebUI Community + 📦 **plugins_release.zip** - 包含本次更新的所有插件文件 / Contains all updated plugin files + + ### 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`) + #### Manual Installation / 手动安装 + 1. Download the plugin file (`.py`) from the assets below 2. Open OpenWebUI Admin Panel → Functions 3. Click "Create Function" → Import 4. Paste the plugin code @@ -139,8 +309,7 @@ jobs: 📚 [Documentation / 文档](https://fu-jie.github.io/awesome-openwebui/) 🐛 [Report Issues / 报告问题](https://github.com/Fu-Jie/awesome-openwebui/issues) - - INSTALL_INSTRUCTIONS + EOF echo "=== Release Notes ===" cat release_notes.md @@ -154,6 +323,7 @@ jobs: prerelease: ${{ github.event.inputs.prerelease || false }} files: | plugin_versions.json + plugins_release.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -163,5 +333,8 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "### Plugin Versions" >> $GITHUB_STEP_SUMMARY + echo "### Updated Plugins" >> $GITHUB_STEP_SUMMARY + echo "${{ needs.check-changes.outputs.release_notes }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### All Plugin Versions" >> $GITHUB_STEP_SUMMARY cat plugin_table.md >> $GITHUB_STEP_SUMMARY diff --git a/docs/release-workflow.md b/docs/release-workflow.md index 8f272cf..b8fb6aa 100644 --- a/docs/release-workflow.md +++ b/docs/release-workflow.md @@ -1,197 +1,189 @@ -# Plugin Release Workflow / 插件发布工作流 +# Plugin Release Workflow This document describes the workflow for releasing plugin updates. -本文档描述了发布插件更新的工作流程。 - --- -## Overview / 概述 +## 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 / 自动化发布流程 +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 / 发布流程 +## Automatic Release Process ⭐ -### Step 1: Update Plugin Version / 更新插件版本 +When a plugin update PR is merged to `main` branch, the release process is **triggered automatically**: -When you make changes to a plugin, update the version number in the plugin's docstring: +### PR Merge Requirements -当您对插件进行更改时,更新插件文档字符串中的版本号: +PRs that modify plugin files must meet the following conditions to merge: + +1. ✅ **Version must be updated** - The plugin's `version` field must be changed +2. ✅ **PR description must contain update notes** - At least 20 characters of description + +If these conditions are not met, the PR check will fail and cannot be merged. + +### Automatic Release Contents + +After successful merge, the system will automatically: + +1. 🔍 Detect version changes (compared to last release) +2. 📝 Generate release notes (with update content and commit history) +3. 📦 Create GitHub Release (with downloadable plugin files) +4. 🏷️ Auto-generate version number (format: `vYYYY.MM.DD-run_number`) + +### Release Includes + +- **plugins_release.zip** - All updated plugin files packaged +- **plugin_versions.json** - All plugin version information (JSON format) +- **Release Notes** - Includes: + - List of new/updated plugins + - Related commit history + - Complete plugin version table + - Installation instructions + +--- + +## Release Process + +### Step 1: Update Plugin Version + +When you make changes to a plugin, you **must** update the version number: ```python """ title: My Plugin author: Fu-Jie -version: 0.2.0 # <- Update this / 更新这里 +version: 0.2.0 # <- Must update this! ... """ ``` -### Step 2: Update CHANGELOG / 更新更新日志 +### Step 2: Create PR with Update Notes -Add your changes to the `[Unreleased]` section in `CHANGELOG.md`: - -在 `CHANGELOG.md` 的 `[Unreleased]` 部分添加您的更改: +Add update notes in your PR description (at least 20 characters): ```markdown -## [Unreleased] / 未发布 +## Changes -### Added / 新增 -- New feature in Smart Mind Map / 智能思维导图新功能 - -### Changed / 变更 -- Improved performance / 性能优化 - -### Fixed / 修复 -- Fixed bug in export / 修复导出 bug +- Added XXX feature +- Fixed YYY issue +- Improved ZZZ performance ``` -### Step 3: Create a Release / 创建发布 +### Step 3: Merge PR -#### Option A: Manual Release (Recommended) / 手动发布(推荐) +After checks pass, merge the PR to `main` branch - the system will automatically create a Release. + +--- + +## Manual Release (Optional) + +In addition to automatic release, you can also trigger manually: + +### Option A: Manual Trigger 1. Go to GitHub Actions → "Plugin Release / 插件发布" 2. Click "Run workflow" 3. Fill in the details: - - **version**: e.g., `v1.0.0` + - **version**: e.g., `v1.0.0` (leave empty for auto-generation) - **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 / 基于标签的发布 +### Option B: Tag-based Release ```bash -# Create and push a version tag / 创建并推送版本标签 +# 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] / 未发布 - - -## [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 / 版本编号 +## 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 -- **MAJOR (主版本)**: Breaking changes / 不兼容的变更 -- **MINOR (次版本)**: New features, backwards compatible / 新功能,向后兼容 -- **PATCH (补丁版本)**: Bug fixes / Bug 修复 +### Examples -### 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 | +| Change Type | Version Change | +|-------------|----------------| +| Bug fix | 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 工作流 +## GitHub Actions Workflows ### release.yml -**Trigger / 触发条件:** -- Manual workflow dispatch / 手动触发 -- Push of version tags (`v*`) / 推送版本标签 +**Triggers:** +- ⭐ Push to `main` branch with `plugins/**/*.py` changes (auto-release) +- Manual workflow dispatch +- Push of version tags (`v*`) -**Actions / 动作:** -1. Extracts all plugin versions / 提取所有插件版本 -2. Generates release notes / 生成发布说明 -3. Creates GitHub Release / 创建 GitHub Release +**Actions:** +1. Detects version changes compared to last release +2. Collects updated plugin files +3. Generates release notes (with commit history) +4. Creates GitHub Release (with downloadable attachments) ### plugin-version-check.yml -**Trigger / 触发条件:** -- Pull requests that modify `plugins/**/*.py` / 修改 `plugins/**/*.py` 的 PR +**Trigger:** +- Pull requests that modify `plugins/**/*.py` -**Actions / 动作:** -1. Compares plugin versions between base and PR / 比较基础分支和 PR 的插件版本 -2. Comments on PR with version changes / 在 PR 上评论版本变化 +**Actions:** +1. Compares plugin versions between base and PR +2. Checks if version was updated +3. Checks if PR description is detailed enough +4. ❌ Fails if no version update detected +5. ⚠️ Fails if PR description is too short --- -## Scripts / 脚本 +## Scripts ### extract_plugin_versions.py -Usage / 用法: +Usage: ```bash -# Output to console / 输出到控制台 +# Output to console python scripts/extract_plugin_versions.py -# Output as JSON / 输出为 JSON +# Output as JSON python scripts/extract_plugin_versions.py --json -# Output as Markdown table / 输出为 Markdown 表格 +# Output as Markdown table python scripts/extract_plugin_versions.py --markdown -# Compare with previous version / 与之前版本比较 +# Compare with previous version python scripts/extract_plugin_versions.py --compare old_versions.json -# Save to file / 保存到文件 +# 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 - - 在创建发布之前**本地测试** +## Best Practices +1. **Always update version numbers** when making functional changes (required) +2. **Write clear PR descriptions** describing what changed and why (required) +3. **Test locally** before creating a PR 4. **Use pre-releases** for testing new features - - 使用**预发布**测试新功能 - -5. **Reference issues** in changelog entries when applicable - - 在适用时在更新日志条目中**引用 issue** +5. **Reference issues** in PR descriptions --- diff --git a/docs/release-workflow.zh.md b/docs/release-workflow.zh.md index 7929fd6..fa2dd6f 100644 --- a/docs/release-workflow.zh.md +++ b/docs/release-workflow.zh.md @@ -14,51 +14,88 @@ --- +## 自动发布流程 ⭐ + +当插件更新的 PR 合并到 `main` 分支时,会**自动触发**发布流程: + +### PR 合并要求 + +修改插件文件的 PR 必须满足以下条件才能合并: + +1. ✅ **版本号必须更新** - 插件的 `version` 字段必须有变化 +2. ✅ **PR 描述必须包含更新说明** - 至少 20 个字符的描述 + +如果不满足这些条件,PR 检查会失败,无法合并。 + +### 自动发布内容 + +合并成功后,系统会自动: + +1. 🔍 检测版本变化(与上次 release 对比) +2. 📝 生成发布说明(包含更新内容和提交记录) +3. 📦 创建 GitHub Release(包含可下载的插件文件) +4. 🏷️ 自动生成版本号(格式:`vYYYY.MM.DD-运行号`) + +### Release 包含内容 + +- **plugins_release.zip** - 本次更新的所有插件文件打包 +- **plugin_versions.json** - 所有插件版本信息 (JSON 格式) +- **发布说明** - 包含: + - 新增/更新的插件列表 + - 相关提交记录 + - 所有插件版本表 + - 安装说明 + +--- + ## 发布流程 ### 第 1 步:更新插件版本 -当您对插件进行更改时,更新插件文档字符串中的版本号: +当您对插件进行更改时,**必须**更新插件文档字符串中的版本号: ```python """ title: 我的插件 author: Fu-Jie -version: 0.2.0 # <- 更新这里 +version: 0.2.0 # <- 必须更新这里! ... """ ``` -### 第 2 步:更新更新日志 +### 第 2 步:创建 PR 并添加更新说明 -在 `CHANGELOG.md` 的 `[Unreleased]` 部分添加您的更改: +在 PR 描述中说明更新内容(至少 20 个字符): ```markdown -## [Unreleased] / 未发布 +## 更新内容 -### Added / 新增 -- 智能思维导图新功能 - -### Changed / 变更 -- 性能优化 - -### Fixed / 修复 -- 修复导出 bug +- 新增 XXX 功能 +- 修复 YYY 问题 +- 优化 ZZZ 性能 ``` -### 第 3 步:创建发布 +### 第 3 步:合并 PR -#### 方式 A:手动发布(推荐) +满足检查条件后,合并 PR 到 `main` 分支,系统会自动创建 Release。 + +--- + +## 手动发布(可选) + +除了自动发布,您也可以手动触发发布: + +### 方式 A:手动触发 1. 前往 GitHub Actions → "Plugin Release / 插件发布" 2. 点击 "Run workflow" 3. 填写详细信息: - - **version**: 例如 `v1.0.0` + - **version**: 例如 `v1.0.0`(留空则自动生成) - **release_title**: 例如 "智能思维导图重大更新" - **release_notes**: Markdown 格式的附加说明 - **prerelease**: 如果是预发布版本则勾选 -#### 方式 B:基于标签的发布 +### 方式 B:基于标签的发布 ```bash # 创建并推送版本标签 @@ -66,25 +103,6 @@ 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 -``` - --- ## 版本编号 @@ -110,13 +128,15 @@ git push origin v1.0.0 ### release.yml **触发条件:** +- ⭐ 推送到 `main` 分支且修改了 `plugins/**/*.py`(自动发布) - 手动触发 (workflow_dispatch) - 推送版本标签 (`v*`) **动作:** -1. 提取所有插件版本 -2. 生成发布说明 -3. 创建 GitHub Release +1. 检测与上次 Release 的版本变化 +2. 收集更新的插件文件 +3. 生成发布说明(含提交记录) +4. 创建 GitHub Release(含可下载附件) ### plugin-version-check.yml @@ -125,7 +145,10 @@ git push origin v1.0.0 **动作:** 1. 比较基础分支和 PR 的插件版本 -2. 在 PR 上评论版本变化 +2. 检查是否有版本更新 +3. 检查 PR 描述是否足够详细 +4. ❌ 如果没有版本更新,检查失败 +5. ⚠️ 如果 PR 描述过短,检查失败 --- @@ -154,11 +177,11 @@ python scripts/extract_plugin_versions.py --json --output versions.json ## 最佳实践 -1. **始终更新版本号** - 对插件进行功能性更改时 -2. **编写清晰的更新日志** - 描述更改内容和原因 -3. **本地测试** - 在创建发布之前 +1. **始终更新版本号** - 对插件进行功能性更改时(必需) +2. **编写清晰的 PR 描述** - 描述更改内容和原因(必需) +3. **本地测试** - 在创建 PR 之前 4. **使用预发布** - 测试新功能 -5. **引用 issue** - 在适用时在更新日志条目中 +5. **引用 issue** - 在 PR 描述中 ---