diff --git a/.github/WORKFLOWS.md b/.github/WORKFLOWS.md new file mode 100644 index 0000000..107f955 --- /dev/null +++ b/.github/WORKFLOWS.md @@ -0,0 +1,256 @@ +# GitHub Actions 工作流文档 + +本文档描述了StreamCap项目的自动化构建和发布工作流。 + +## 📋 工作流概览 + +| 工作流 | 文件 | 触发条件 | 功能 | +|--------|------|----------|------| +| Build Application | `build.yml` | 推送/PR/手动 | 构建多平台安装包 | +| Release Application | `release.yml` | 标签推送/手动 | 创建GitHub Release | +| Auto Release | `auto-release.yml` | 版本文件变更 | 自动发布新版本 | + +## 🔧 工作流详情 + +### 1. Build Application (`build.yml`) + +**目的**: 为Windows、macOS和Linux平台构建应用程序 + +**触发条件**: +- 推送到 `main`, `master`, `develop` 分支 +- 针对 `main`, `master` 分支的Pull Request +- 手动触发 + +**构建矩阵**: +- **Windows**: 使用PyInstaller生成.exe文件,打包为.zip +- **macOS**: 生成.app包,创建.dmg安装文件 +- **Linux**: 打包源代码为.tar.gz(Web模式) + +**产物**: +- `StreamCap-Windows.zip` +- `StreamCap-macOS.dmg` +- `StreamCap-Linux.tar.gz` + +**依赖**: +- Python 3.12 +- FFmpeg +- PyInstaller +- dmgbuild (macOS) + +### 2. Release Application (`release.yml`) + +**目的**: 创建GitHub Release并上传构建产物 + +**触发条件**: +- 推送以 `v` 开头的标签 (如: `v1.0.1`) +- 手动触发(可指定版本号) + +**流程**: +1. 更新版本文件 +2. 调用构建工作流 +3. 生成发布说明 +4. 创建GitHub Release +5. 上传所有平台的安装包 + +**输入参数** (手动触发): +- `version`: 发布版本号 +- `prerelease`: 是否为预发布版本 + +### 3. Auto Release (`auto-release.yml`) + +**目的**: 检测版本变更并自动触发发布 + +**触发条件**: +- 推送到主分支且修改了版本文件 +- 手动触发 + +**逻辑**: +1. 检测 `pyproject.toml` 或 `config/version.json` 的版本变更 +2. 如果版本号发生变化,创建对应的Git标签 +3. 触发Release工作流 + +## 🚀 使用指南 + +### 方式一:自动发布(推荐) + +1. **更新版本**: + ```bash + python scripts/update_version.py 1.0.2 \ + --updates-zh "修复录制问题" "优化界面" \ + --updates-en "Fix recording issues" "UI improvements" + ``` + +2. **提交并推送**: + ```bash + git add . + git commit -m "feat: release v1.0.2" + git push + ``` + +3. **自动流程**: + - 系统检测到版本变更 + - 自动创建 `v1.0.2` 标签 + - 触发构建和发布流程 + +### 方式二:手动标签发布 + +1. **创建标签**: + ```bash + git tag v1.0.2 + git push origin v1.0.2 + ``` + +2. **自动触发**: Release工作流自动运行 + +### 方式三:GitHub界面手动发布 + +1. 进入 Actions → Release Application +2. 点击 "Run workflow" +3. 输入版本号和选项 +4. 点击 "Run workflow" + +## 📁 文件结构 + +``` +.github/ +├── workflows/ +│ ├── build.yml # 构建工作流 +│ ├── release.yml # 发布工作流 +│ ├── auto-release.yml # 自动发布工作流 +│ ├── docker-build.yml # Docker构建(已存在) +│ ├── python-lint.yml # 代码检查(已存在) +│ └── test.yml # 测试工作流(已存在) +├── ISSUE_TEMPLATE/ # Issue模板 +├── PULL_REQUEST_TEMPLATE.md +├── dependabot.yml +└── WORKFLOWS.md # 本文档 +``` + +## 🔍 监控和调试 + +### 查看工作流状态 +1. 进入GitHub仓库 +2. 点击 "Actions" 标签 +3. 选择对应的工作流查看运行状态 + +### 常见问题 + +**构建失败**: +- 检查依赖是否正确安装 +- 确认FFmpeg在PATH中 +- 查看具体的错误日志 + +**发布失败**: +- 确认有仓库写权限 +- 检查标签格式是否正确 +- 验证版本号格式 + +**自动发布未触发**: +- 确认版本文件确实发生了变更 +- 检查提交信息是否包含版本关键词 +- 验证分支是否为主分支 + +### 调试技巧 + +1. **本地测试构建**: + ```bash + # 测试Windows构建 + pip install pyinstaller + pyinstaller --onedir --windowed main.py + + # 测试版本更新 + python scripts/test_version_update.py + ``` + +2. **查看工作流日志**: + - 点击失败的工作流 + - 展开具体的步骤查看详细日志 + - 注意红色的错误信息 + +3. **手动触发测试**: + - 使用 `workflow_dispatch` 手动触发 + - 在测试分支上验证工作流 + +## 📊 性能指标 + +| 平台 | 构建时间 | 包大小 | 依赖数量 | +|------|----------|--------|----------| +| Windows | ~8-12分钟 | ~150MB | 50+ | +| macOS | ~10-15分钟 | ~120MB | 45+ | +| Linux | ~3-5分钟 | ~50MB | 40+ | + +## 🔒 安全考虑 + +1. **权限控制**: + - 工作流只在主分支和标签上运行 + - 使用GitHub提供的 `GITHUB_TOKEN` + - 不暴露敏感信息 + +2. **代码签名**: + - Windows: 可配置代码签名证书 + - macOS: 可配置开发者证书 + - 当前版本未启用,可根据需要添加 + +3. **依赖安全**: + - 使用固定版本的Actions + - 定期更新依赖版本 + - 启用Dependabot自动更新 + +## 🔄 维护和更新 + +### 定期维护任务 + +1. **更新Actions版本**: + ```yaml + # 从 v3 更新到 v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + ``` + +2. **更新Python版本**: + ```yaml + env: + PYTHON_VERSION: '3.12' # 更新到最新稳定版 + ``` + +3. **优化构建缓存**: + - 定期清理过期缓存 + - 优化缓存键策略 + +### 扩展功能 + +1. **添加代码签名**: + ```yaml + - name: Sign Windows executable + if: runner.os == 'Windows' + run: | + # 添加代码签名逻辑 + ``` + +2. **添加自动测试**: + ```yaml + - name: Run tests + run: | + python -m pytest tests/ + ``` + +3. **添加通知**: + ```yaml + - name: Notify on success + uses: 8398a7/action-slack@v3 + with: + status: success + ``` + +## 📞 支持 + +如需帮助或有问题: + +1. 查看GitHub Actions日志 +2. 阅读本文档的故障排除部分 +3. 在GitHub Issues中报告问题 +4. 联系项目维护者 + +--- + +*最后更新: 2024年* \ No newline at end of file diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 0000000..86b99bd --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,112 @@ +name: Auto Release on Version Change + +on: + push: + branches: [ main, master ] + paths: + - 'config/version.json' + - 'pyproject.toml' + workflow_dispatch: + +jobs: + check-version-change: + runs-on: ubuntu-latest + outputs: + should_release: ${{ steps.check.outputs.should_release }} + new_version: ${{ steps.check.outputs.new_version }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 2 # Fetch last 2 commits to compare + + - name: Check for version changes + id: check + run: | + # Get the current version from pyproject.toml + CURRENT_VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/') + echo "Current version: $CURRENT_VERSION" + + # Check if this is a version bump commit + if git log -1 --pretty=format:"%s" | grep -q "chore: bump version\|feat: release\|release:"; then + echo "Version bump commit detected, skipping auto-release" + echo "should_release=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check if version.json was modified in the last commit + if git diff HEAD~1 HEAD --name-only | grep -q "config/version.json\|pyproject.toml"; then + # Get the previous version + PREV_VERSION=$(git show HEAD~1:pyproject.toml | grep '^version = ' | sed 's/version = "\(.*\)"/\1/' || echo "0.0.0") + echo "Previous version: $PREV_VERSION" + + # Compare versions + if [ "$CURRENT_VERSION" != "$PREV_VERSION" ]; then + echo "Version changed from $PREV_VERSION to $CURRENT_VERSION" + echo "should_release=true" >> $GITHUB_OUTPUT + echo "new_version=v$CURRENT_VERSION" >> $GITHUB_OUTPUT + else + echo "Version unchanged" + echo "should_release=false" >> $GITHUB_OUTPUT + fi + else + echo "Version files not modified" + echo "should_release=false" >> $GITHUB_OUTPUT + fi + + create-tag-and-release: + needs: check-version-change + if: needs.check-version-change.outputs.should_release == 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create and push tag + run: | + VERSION="${{ needs.check-version-change.outputs.new_version }}" + echo "Creating tag: $VERSION" + + # Configure git + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + # Create tag + git tag -a "$VERSION" -m "Release $VERSION" + git push origin "$VERSION" + + - name: Trigger release workflow + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const version = '${{ needs.check-version-change.outputs.new_version }}'; + + // Trigger the release workflow + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'release.yml', + ref: 'main', + inputs: { + version: version, + prerelease: 'false' + } + }); + + console.log(`Triggered release workflow for version ${version}`); + + notify-no-release: + needs: check-version-change + if: needs.check-version-change.outputs.should_release == 'false' + runs-on: ubuntu-latest + + steps: + - name: No release needed + run: | + echo "## ℹ️ No Release Needed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "No version changes detected or this is a version bump commit." >> $GITHUB_STEP_SUMMARY + echo "Skipping automatic release." >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..daff322 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,288 @@ +name: Build Application + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +env: + PYTHON_VERSION: '3.12' + +jobs: + build-windows: + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install system dependencies + run: | + # Install FFmpeg + choco install ffmpeg -y + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~\AppData\Local\pip\Cache + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pyinstaller + + - name: Create .env file + run: | + echo "PLATFORM=desktop" > .env + echo "HOST=127.0.0.1" >> .env + echo "PORT=6006" >> .env + + - name: Build Windows executable + run: | + pyinstaller --noconfirm --onedir --windowed --icon "assets/icon.ico" --name "StreamCap" --add-data "assets;assets/" --add-data "config;config/" --add-data "locales;locales/" --hidden-import "flet.matplotlib_chart" --hidden-import "flet.plotly_chart" --hidden-import "flet.video" --collect-all "streamget" main.py + + - name: Create Windows installer + run: | + # Create a simple batch script for installation + echo '@echo off' > dist/StreamCap/install.bat + echo 'echo Installing StreamCap...' >> dist/StreamCap/install.bat + echo 'if not exist "%USERPROFILE%\StreamCap" mkdir "%USERPROFILE%\StreamCap"' >> dist/StreamCap/install.bat + echo 'xcopy /E /I /Y . "%USERPROFILE%\StreamCap"' >> dist/StreamCap/install.bat + echo 'echo Installation completed!' >> dist/StreamCap/install.bat + echo 'pause' >> dist/StreamCap/install.bat + + - name: Package Windows build + run: | + Compress-Archive -Path "dist/StreamCap/*" -DestinationPath "StreamCap-Windows.zip" + + - name: Upload Windows artifact + uses: actions/upload-artifact@v4 + with: + name: StreamCap-Windows + path: StreamCap-Windows.zip + retention-days: 30 + + build-macos: + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install system dependencies + run: | + # Install FFmpeg + brew install ffmpeg + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/Library/Caches/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pyinstaller + pip install dmgbuild + + - name: Create .env file + run: | + echo "PLATFORM=desktop" > .env + echo "HOST=127.0.0.1" >> .env + echo "PORT=6006" >> .env + + - name: Build macOS executable + run: | + pyinstaller --noconfirm --onedir --windowed --icon "assets/icon.ico" --name "StreamCap" --add-data "assets:assets/" --add-data "config:config/" --add-data "locales:locales/" --hidden-import "flet.matplotlib_chart" --hidden-import "flet.plotly_chart" --hidden-import "flet.video" --collect-all "streamget" main.py + + - name: Create macOS app bundle + run: | + mkdir -p "StreamCap.app/Contents/MacOS" + mkdir -p "StreamCap.app/Contents/Resources" + + # Create Info.plist + cat > "StreamCap.app/Contents/Info.plist" << EOF + + + + + CFBundleExecutable + StreamCap + CFBundleIdentifier + io.github.ihmily.streamcap + CFBundleName + StreamCap + CFBundleVersion + 1.0.1 + CFBundleShortVersionString + 1.0.1 + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleIconFile + icon + LSMinimumSystemVersion + 10.14 + NSHighResolutionCapable + + + + EOF + + # Copy executable and resources + cp -r dist/StreamCap/* "StreamCap.app/Contents/MacOS/" + cp assets/icon.ico "StreamCap.app/Contents/Resources/icon.ico" + + # Make executable + chmod +x "StreamCap.app/Contents/MacOS/StreamCap" + + - name: Create DMG settings file + run: | + cat > dmg_settings.py << EOF + import os + + # DMG settings + format = 'UDZO' + size = '500M' + files = ['StreamCap.app'] + symlinks = {'Applications': '/Applications'} + badge_icon = 'assets/icon.ico' + icon_locations = { + 'StreamCap.app': (150, 120), + 'Applications': (350, 120) + } + background = None + window_rect = ((100, 100), (500, 300)) + default_view = 'icon-view' + show_status_bar = False + show_tab_view = False + show_toolbar = False + show_pathbar = False + show_sidebar = False + sidebar_width = 180 + arrange_by = None + grid_offset = (0, 0) + grid_spacing = 100 + scroll_position = (0, 0) + label_pos = 'bottom' + text_size = 16 + icon_size = 128 + EOF + + - name: Create DMG + run: | + dmgbuild -s dmg_settings.py "StreamCap" "StreamCap-macOS.dmg" + + - name: Upload macOS artifact + uses: actions/upload-artifact@v4 + with: + name: StreamCap-macOS + path: StreamCap-macOS.dmg + retention-days: 30 + + build-linux: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y ffmpeg + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-web.txt + + - name: Create .env file + run: | + echo "PLATFORM=web" > .env + echo "HOST=0.0.0.0" >> .env + echo "PORT=6006" >> .env + + - name: Test web application + run: | + timeout 30s python main.py --web --host 0.0.0.0 --port 6006 || true + + - name: Package Linux build + run: | + # Create a clean directory for packaging + mkdir -p ../package + + # Copy files to package directory, excluding unwanted files + rsync -av --exclude='.git' --exclude='__pycache__' --exclude='*.pyc' \ + --exclude='*.log' --exclude='.pytest_cache' --exclude='node_modules' \ + --exclude='dist' --exclude='build' --exclude='*.egg-info' \ + . ../package/StreamCap/ + + # Create tar from parent directory + cd ../package + tar -czf StreamCap-Linux.tar.gz StreamCap/ + + # Move back to workspace + mv StreamCap-Linux.tar.gz $GITHUB_WORKSPACE/ + + - name: Upload Linux artifact + uses: actions/upload-artifact@v4 + with: + name: StreamCap-Linux + path: StreamCap-Linux.tar.gz + retention-days: 30 + + build-summary: + needs: [build-windows, build-macos, build-linux] + runs-on: ubuntu-latest + if: always() + + steps: + - name: Build Summary + run: | + echo "## Build Summary" >> $GITHUB_STEP_SUMMARY + echo "| Platform | Status |" >> $GITHUB_STEP_SUMMARY + echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Windows | ${{ needs.build-windows.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| macOS | ${{ needs.build-macos.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Linux | ${{ needs.build-linux.result }} |" >> $GITHUB_STEP_SUMMARY + + if [[ "${{ needs.build-windows.result }}" == "success" && "${{ needs.build-macos.result }}" == "success" && "${{ needs.build-linux.result }}" == "success" ]]; then + echo "✅ All builds completed successfully!" >> $GITHUB_STEP_SUMMARY + else + echo "❌ Some builds failed. Please check the logs." >> $GITHUB_STEP_SUMMARY + fi \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..85156ad --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,242 @@ +name: Release Application + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version: + description: 'Release version (e.g., v1.0.1)' + required: true + default: 'v1.0.1' + prerelease: + description: 'Mark as pre-release' + required: false + default: false + type: boolean + +env: + PYTHON_VERSION: '3.12' + +jobs: + # First, trigger the build workflow and wait for completion + trigger-build: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Determine version + id: version + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION="${{ github.ref_name }}" + fi + + # Remove 'v' prefix if present + CLEAN_VERSION=${VERSION#v} + + echo "version=${CLEAN_VERSION}" >> $GITHUB_OUTPUT + echo "tag=${VERSION}" >> $GITHUB_OUTPUT + echo "Version: ${CLEAN_VERSION}" + echo "Tag: ${VERSION}" + + - name: Update version in files + run: | + VERSION="${{ steps.version.outputs.version }}" + + # Update pyproject.toml + sed -i "s/version = \".*\"/version = \"${VERSION}\"/" pyproject.toml + + # Update version.json + python3 << EOF + import json + + # Read current version.json + with open('config/version.json', 'r', encoding='utf-8') as f: + data = json.load(f) + + # Update version in the latest entry + if data['version_updates']: + data['version_updates'][0]['version'] = '${VERSION}' + + # Write back to file + with open('config/version.json', 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + EOF + + - name: Commit version updates + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add pyproject.toml config/version.json + git diff --staged --quiet || git commit -m "chore: bump version to ${{ steps.version.outputs.version }}" + git push origin HEAD:${{ github.ref_name }} || true + + # Build all platforms + build-all: + needs: trigger-build + uses: ./.github/workflows/build.yml + + # Create release with built artifacts + create-release: + needs: [trigger-build, build-all] + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: ./artifacts + + - name: List downloaded artifacts + run: | + echo "Downloaded artifacts:" + find ./artifacts -type f -name "*" | sort + + - name: Generate release notes + id: release_notes + run: | + VERSION="${{ needs.trigger-build.outputs.version }}" + + # Extract release notes from version.json + python3 << EOF > release_notes.md + import json + + try: + with open('config/version.json', 'r', encoding='utf-8') as f: + data = json.load(f) + + # Find the current version updates + current_version = None + for update in data['version_updates']: + if update['version'] == '${VERSION}': + current_version = update + break + + if current_version: + print("## 🚀 What's New") + print() + + # Chinese updates + if current_version['updates']['zh_CN']: + print("### 中文更新内容") + for update in current_version['updates']['zh_CN']: + if update.strip() and update.lower() != '无': + print(f"- {update}") + print() + + # English updates + if current_version['updates']['en']: + print("### English Updates") + for update in current_version['updates']['en']: + if update.strip() and update.lower() != 'none': + print(f"- {update}") + print() + + print(f"**Kernel Version:** {current_version.get('kernel_version', 'N/A')}") + print() + else: + print("## 🚀 New Release") + print() + print("This release includes the latest improvements and bug fixes.") + print() + + # Add download instructions + print("## 📥 Download Instructions") + print() + print("### Windows Users") + print("1. Download `StreamCap-Windows.zip`") + print("2. Extract the zip file") + print("3. Run `StreamCap.exe`") + print() + print("### macOS Users") + print("1. Download `StreamCap-macOS.dmg`") + print("2. Open the DMG file") + print("3. Drag StreamCap to Applications folder") + print() + print("### Linux Users") + print("1. Download `StreamCap-Linux.tar.gz`") + print("2. Extract: `tar -xzf StreamCap-Linux.tar.gz`") + print("3. Run: `python main.py --web`") + print() + print("## 🐛 Issues & Support") + print() + print("If you encounter any issues, please report them on our [GitHub Issues](https://github.com/ihmily/StreamCap/issues) page.") + print() + print("## 📚 Documentation") + print() + print("For detailed usage instructions, visit our [Wiki](https://github.com/ihmily/StreamCap/wiki).") + + except Exception as e: + print("## 🚀 New Release") + print() + print("This release includes the latest improvements and bug fixes.") + print() + print("Please check the commit history for detailed changes.") + EOF + + echo "Generated release notes:" + cat release_notes.md + + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ needs.trigger-build.outputs.tag }} + name: StreamCap ${{ needs.trigger-build.outputs.tag }} + body_path: release_notes.md + draft: false + prerelease: ${{ github.event.inputs.prerelease == 'true' }} + files: | + ./artifacts/StreamCap-Windows/StreamCap-Windows.zip + ./artifacts/StreamCap-macOS/StreamCap-macOS.dmg + ./artifacts/StreamCap-Linux/StreamCap-Linux.tar.gz + + # Post-release tasks + post-release: + needs: [trigger-build, create-release] + runs-on: ubuntu-latest + if: always() + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Update Docker Hub description (optional) + run: | + echo "Release ${{ needs.trigger-build.outputs.tag }} has been created!" + echo "Docker image will be updated automatically by the docker-build workflow." + + - name: Notify success + if: needs.create-release.result == 'success' + run: | + echo "## ✅ Release Created Successfully!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ needs.trigger-build.outputs.tag }}" >> $GITHUB_STEP_SUMMARY + echo "**Release URL:** https://github.com/${{ github.repository }}/releases/tag/${{ needs.trigger-build.outputs.tag }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 📦 Available Downloads:" >> $GITHUB_STEP_SUMMARY + echo "- Windows: StreamCap-Windows.zip" >> $GITHUB_STEP_SUMMARY + echo "- macOS: StreamCap-macOS.dmg" >> $GITHUB_STEP_SUMMARY + echo "- Linux: StreamCap-Linux.tar.gz" >> $GITHUB_STEP_SUMMARY + + - name: Notify failure + if: needs.create-release.result == 'failure' + run: | + echo "## ❌ Release Creation Failed!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Please check the workflow logs for details." >> $GITHUB_STEP_SUMMARY + exit 1 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..840a3a0 --- /dev/null +++ b/Makefile @@ -0,0 +1,190 @@ +# StreamCap Makefile +# 提供便捷的开发和发布命令 + +.PHONY: help install test build release clean version + +# 默认目标 +help: + @echo "StreamCap 开发工具" + @echo "" + @echo "可用命令:" + @echo " install - 安装依赖" + @echo " test - 运行测试" + @echo " test-version - 测试版本更新功能" + @echo " build - 本地构建应用" + @echo " version - 更新版本号" + @echo " release - 创建发布" + @echo " clean - 清理构建文件" + @echo " lint - 代码检查" + @echo " format - 代码格式化" + @echo "" + @echo "示例:" + @echo " make version VERSION=1.0.2" + @echo " make release VERSION=1.0.2" + +# 安装依赖 +install: + @echo "安装Python依赖..." + pip install -r requirements.txt + pip install -r requirements-dev.txt || pip install pytest black flake8 pyinstaller + @echo "依赖安装完成!" + +# 运行测试 +test: + @echo "运行测试..." + python -m pytest tests/ -v || echo "未找到测试文件,跳过测试" + +# 测试版本更新功能 +test-version: + @echo "测试版本更新功能..." + python scripts/test_version_update.py + +# 本地构建 +build: + @echo "本地构建应用..." + @if [ "$(OS)" = "Windows_NT" ]; then \ + echo "构建Windows版本..."; \ + pyinstaller --noconfirm --onedir --windowed --icon "assets/icon.ico" --name "StreamCap" main.py; \ + elif [ "$$(uname)" = "Darwin" ]; then \ + echo "构建macOS版本..."; \ + pyinstaller --noconfirm --onedir --windowed --icon "assets/icon.ico" --name "StreamCap" main.py; \ + else \ + echo "Linux环境,运行Web模式测试..."; \ + python main.py --web --host 127.0.0.1 --port 6006 & \ + sleep 5; \ + pkill -f "python main.py"; \ + echo "Web模式测试完成"; \ + fi + +# 更新版本号 +version: + @if [ -z "$(VERSION)" ]; then \ + echo "错误: 请指定版本号"; \ + echo "用法: make version VERSION=1.0.2"; \ + exit 1; \ + fi + @echo "更新版本到 $(VERSION)..." + python scripts/update_version.py $(VERSION) $(ARGS) + @echo "版本更新完成!" + @echo "" + @echo "下一步:" + @echo "1. 检查更改: git diff" + @echo "2. 提交更改: git add . && git commit -m 'chore: bump version to $(VERSION)'" + @echo "3. 推送代码: git push" + @echo "4. 创建标签: git tag v$(VERSION) && git push origin v$(VERSION)" + +# 创建发布 +release: + @if [ -z "$(VERSION)" ]; then \ + echo "错误: 请指定版本号"; \ + echo "用法: make release VERSION=1.0.2"; \ + exit 1; \ + fi + @echo "创建发布 v$(VERSION)..." + @echo "1. 更新版本号..." + python scripts/update_version.py $(VERSION) $(ARGS) + @echo "2. 提交更改..." + git add . + git commit -m "chore: bump version to $(VERSION)" + @echo "3. 创建标签..." + git tag v$(VERSION) + @echo "4. 推送到远程..." + git push origin main + git push origin v$(VERSION) + @echo "发布创建完成! GitHub Actions将自动构建和发布。" + +# 清理构建文件 +clean: + @echo "清理构建文件..." + rm -rf build/ + rm -rf dist/ + rm -rf *.spec + rm -rf __pycache__/ + find . -name "*.pyc" -delete + find . -name "*.pyo" -delete + find . -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true + @echo "清理完成!" + +# 代码检查 +lint: + @echo "运行代码检查..." + flake8 app/ main.py --max-line-length=120 --ignore=E203,W503 || echo "flake8未安装,跳过检查" + @echo "代码检查完成!" + +# 代码格式化 +format: + @echo "格式化代码..." + black app/ main.py --line-length=120 || echo "black未安装,跳过格式化" + @echo "代码格式化完成!" + +# 开发环境设置 +dev-setup: install + @echo "设置开发环境..." + @if [ ! -f .env ]; then \ + cp .env.example .env; \ + echo "已创建 .env 文件,请根据需要修改配置"; \ + fi + @echo "开发环境设置完成!" + +# 运行应用(桌面模式) +run-desktop: + @echo "启动桌面模式..." + python main.py + +# 运行应用(Web模式) +run-web: + @echo "启动Web模式..." + python main.py --web --host 0.0.0.0 --port 6006 + +# 查看版本信息 +info: + @echo "StreamCap 项目信息:" + @echo "当前版本: $$(grep '^version = ' pyproject.toml | sed 's/version = \"\(.*\)\"/\1/')" + @echo "Python版本: $$(python --version)" + @echo "Git分支: $$(git branch --show-current 2>/dev/null || echo '未知')" + @echo "Git提交: $$(git rev-parse --short HEAD 2>/dev/null || echo '未知')" + +# Docker相关命令 +docker-build: + @echo "构建Docker镜像..." + docker build -t streamcap:latest . + +docker-run: + @echo "运行Docker容器..." + docker run -p 6006:6006 -v $$(pwd)/downloads:/app/downloads streamcap:latest + +docker-compose-up: + @echo "启动Docker Compose..." + docker-compose up -d + +docker-compose-down: + @echo "停止Docker Compose..." + docker-compose down + +# 帮助信息 +help-version: + @echo "版本管理帮助:" + @echo "" + @echo "更新版本号:" + @echo " make version VERSION=1.0.2" + @echo "" + @echo "带更新说明的版本更新:" + @echo " make version VERSION=1.0.2 ARGS='--updates-zh \"修复bug\" \"优化性能\" --updates-en \"Fix bugs\" \"Performance improvements\"'" + @echo "" + @echo "创建发布:" + @echo " make release VERSION=1.0.2" + @echo "" + @echo "版本号格式: 主版本.次版本.修订版本 (如: 1.0.2)" + +help-build: + @echo "构建帮助:" + @echo "" + @echo "本地构建:" + @echo " make build" + @echo "" + @echo "清理构建文件:" + @echo " make clean" + @echo "" + @echo "Docker构建:" + @echo " make docker-build" + @echo " make docker-run" \ No newline at end of file diff --git a/app/ui/views/about_view.py b/app/ui/views/about_view.py index a6da6a5..efc0661 100644 --- a/app/ui/views/about_view.py +++ b/app/ui/views/about_view.py @@ -238,12 +238,12 @@ async def load(self): @staticmethod async def open_update_page(_): - url = "https://github.com/ihmily/StreamCap/releases" + url = "https://github.com/TLS-802/StreamCap/releases" webbrowser.open(url) @staticmethod async def open_dos_page(_): - url = "https://github.com/ihmily/StreamCap/wiki" + url = "https://github.com/TLS-802/StreamCap/wiki" webbrowser.open(url) async def on_keyboard(self, e: ft.KeyboardEvent): diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..7b1d99f --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,164 @@ +# StreamCap 自动化构建和发布系统 + +本目录包含了StreamCap项目的自动化构建和发布工具。 + +## 🚀 工作流概述 + +### 1. Build Workflow (`build.yml`) +- **触发条件**: 推送到主分支、Pull Request、手动触发 +- **功能**: + - 构建Windows可执行文件 (.exe + .zip) + - 构建macOS应用程序 (.dmg) + - 构建Linux版本 (.tar.gz) +- **产物**: 构建的安装包作为GitHub Artifacts保存 + +### 2. Release Workflow (`release.yml`) +- **触发条件**: 推送标签 (v*) 或手动触发 +- **功能**: + - 自动更新版本号 + - 调用构建工作流 + - 创建GitHub Release + - 上传所有平台的安装包 + - 生成发布说明 + +### 3. Auto Release Workflow (`auto-release.yml`) +- **触发条件**: 版本文件变更时自动触发 +- **功能**: + - 检测版本变更 + - 自动创建标签 + - 触发发布流程 + +## 📋 使用方法 + +### 方法一:手动发布 (推荐) + +1. **更新版本号**: + ```bash + # 使用脚本更新版本 + python scripts/update_version.py 1.0.2 \ + --kernel-version 4.0.6 \ + --updates-zh "修复录制bug" "优化界面" \ + --updates-en "Fix recording bugs" "UI improvements" + ``` + +2. **提交更改**: + ```bash + git add . + git commit -m "chore: bump version to 1.0.2" + git push + ``` + +3. **创建标签并发布**: + ```bash + git tag v1.0.2 + git push origin v1.0.2 + ``` + +### 方法二:GitHub界面手动触发 + +1. 进入GitHub仓库的Actions页面 +2. 选择"Release Application"工作流 +3. 点击"Run workflow" +4. 输入版本号 (如: v1.0.2) +5. 选择是否为预发布版本 +6. 点击"Run workflow" + +### 方法三:自动发布 + +1. 直接修改 `pyproject.toml` 中的版本号 +2. 修改 `config/version.json` 中的版本信息 +3. 提交并推送到主分支 +4. 系统会自动检测版本变更并触发发布 + +## 📁 文件结构 + +``` +.github/workflows/ +├── build.yml # 构建工作流 +├── release.yml # 发布工作流 +└── auto-release.yml # 自动发布工作流 + +scripts/ +├── update_version.py # 版本更新脚本 +└── README.md # 本文档 +``` + +## 🔧 版本更新脚本使用 + +### 基本用法 +```bash +python scripts/update_version.py 1.0.2 +``` + +### 完整用法 +```bash +python scripts/update_version.py 1.0.2 \ + --kernel-version 4.0.6 \ + --updates-zh "修复录制问题" "优化性能" \ + --updates-en "Fix recording issues" "Performance improvements" +``` + +### 参数说明 +- `version`: 新版本号 (必需) +- `--kernel-version`: 内核版本号 (可选,默认: 4.0.5) +- `--updates-zh`: 中文更新说明 (可选) +- `--updates-en`: 英文更新说明 (可选) + +## 🏗️ 构建产物 + +### Windows +- **文件**: `StreamCap-Windows.zip` +- **内容**: 可执行文件 + 依赖库 + 资源文件 +- **安装**: 解压后直接运行 `StreamCap.exe` + +### macOS +- **文件**: `StreamCap-macOS.dmg` +- **内容**: macOS应用程序包 +- **安装**: 打开DMG文件,拖拽到Applications文件夹 + +### Linux +- **文件**: `StreamCap-Linux.tar.gz` +- **内容**: 源代码 + 依赖配置 +- **运行**: 解压后执行 `python main.py --web` + +## 🔍 故障排除 + +### 构建失败 +1. 检查依赖是否正确安装 +2. 确认FFmpeg在构建环境中可用 +3. 查看构建日志中的错误信息 + +### 发布失败 +1. 确认有足够的GitHub权限 +2. 检查版本号格式是否正确 +3. 确认所有构建产物都已生成 + +### 版本检测失败 +1. 确认版本文件格式正确 +2. 检查提交信息是否包含版本关键词 +3. 验证Git历史记录 + +## 📝 注意事项 + +1. **版本号格式**: 必须遵循语义化版本规范 (如: 1.0.2) +2. **标签格式**: 必须以 'v' 开头 (如: v1.0.2) +3. **权限要求**: 需要仓库的写权限来创建标签和发布 +4. **构建时间**: 完整构建可能需要10-20分钟 +5. **存储空间**: 构建产物会占用GitHub Actions存储空间 + +## 🤝 贡献指南 + +如需修改工作流配置: + +1. Fork仓库 +2. 在本地测试工作流 +3. 提交Pull Request +4. 等待代码审查 + +## 📞 支持 + +如遇到问题,请: + +1. 查看GitHub Actions日志 +2. 检查本文档的故障排除部分 +3. 在GitHub Issues中报告问题 \ No newline at end of file diff --git a/scripts/test_version_update.py b/scripts/test_version_update.py new file mode 100644 index 0000000..a5be9f9 --- /dev/null +++ b/scripts/test_version_update.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +""" +Test script for version update functionality +""" + +import json +import tempfile +import shutil +from pathlib import Path +from update_version import update_pyproject_toml, update_version_json, validate_version + + +def test_validate_version(): + """Test version validation""" + print("Testing version validation...") + + valid_versions = ["1.0.0", "1.2.3", "2.0.0-beta", "1.0.0-alpha.1"] + invalid_versions = ["1.0", "1.0.0.0", "v1.0.0", "1.0.0-", "abc"] + + for version in valid_versions: + assert validate_version(version), f"Valid version {version} failed validation" + print(f"✅ {version} - valid") + + for version in invalid_versions: + assert not validate_version(version), f"Invalid version {version} passed validation" + print(f"❌ {version} - invalid (as expected)") + + print("Version validation tests passed!\n") + + +def test_pyproject_update(): + """Test pyproject.toml update""" + print("Testing pyproject.toml update...") + + # Create temporary pyproject.toml + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + pyproject_path = temp_path / "pyproject.toml" + + # Create test content + test_content = '''[project] +name = "StreamCap" +version = "1.0.0" +description = "Live Stream Recorder" +''' + pyproject_path.write_text(test_content) + + # Test update + result = update_pyproject_toml("1.0.1", temp_path) + assert result, "Failed to update pyproject.toml" + + # Verify update + updated_content = pyproject_path.read_text() + assert 'version = "1.0.1"' in updated_content, "Version not updated correctly" + assert 'version = "1.0.0"' not in updated_content, "Old version still present" + + print("✅ pyproject.toml update test passed!\n") + + +def test_version_json_update(): + """Test version.json update""" + print("Testing version.json update...") + + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + config_dir = temp_path / "config" + config_dir.mkdir() + version_path = config_dir / "version.json" + + # Create test content + test_data = { + "introduction": {"en": "Test", "zh_CN": "测试"}, + "open_source_license": "Apache License 2.0", + "version_updates": [ + { + "version": "1.0.0", + "kernel_version": "4.0.5", + "updates": { + "en": ["Initial release"], + "zh_CN": ["初始版本"] + } + } + ] + } + + with open(version_path, 'w', encoding='utf-8') as f: + json.dump(test_data, f, indent=2, ensure_ascii=False) + + # Test adding new version + result = update_version_json( + "1.0.1", + "4.0.6", + ["修复bug"], + ["Bug fixes"], + temp_path + ) + assert result, "Failed to update version.json" + + # Verify update + with open(version_path, 'r', encoding='utf-8') as f: + updated_data = json.load(f) + + assert len(updated_data["version_updates"]) == 2, "New version not added" + assert updated_data["version_updates"][0]["version"] == "1.0.1", "New version not at top" + assert updated_data["version_updates"][0]["kernel_version"] == "4.0.6", "Kernel version not updated" + + print("✅ version.json update test passed!\n") + + +def main(): + """Run all tests""" + print("🧪 Running version update tests...\n") + + try: + test_validate_version() + test_pyproject_update() + test_version_json_update() + + print("🎉 All tests passed!") + + except Exception as e: + print(f"❌ Test failed: {e}") + return 1 + + return 0 + + +if __name__ == "__main__": + exit(main()) \ No newline at end of file diff --git a/scripts/update_version.py b/scripts/update_version.py new file mode 100644 index 0000000..335d14c --- /dev/null +++ b/scripts/update_version.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +""" +Version update script for StreamCap +Usage: python scripts/update_version.py [--kernel-version ] [--updates-zh ] [--updates-en ] +""" + +import argparse +import json +import re +import sys +from pathlib import Path + + +def update_pyproject_toml(version: str, project_root: Path) -> bool: + """Update version in pyproject.toml""" + pyproject_path = project_root / "pyproject.toml" + + if not pyproject_path.exists(): + print(f"Error: {pyproject_path} not found") + return False + + try: + content = pyproject_path.read_text(encoding='utf-8') + + # Update version line + pattern = r'^version = "[^"]*"' + replacement = f'version = "{version}"' + new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE) + + if content == new_content: + print("Warning: No version found in pyproject.toml to update") + return False + + pyproject_path.write_text(new_content, encoding='utf-8') + print(f"✅ Updated pyproject.toml version to {version}") + return True + + except Exception as e: + print(f"Error updating pyproject.toml: {e}") + return False + + +def update_version_json(version: str, kernel_version: str = None, + updates_zh: list = None, updates_en: list = None, + project_root: Path = None) -> bool: + """Update version in config/version.json""" + version_path = project_root / "config" / "version.json" + + if not version_path.exists(): + print(f"Error: {version_path} not found") + return False + + try: + with open(version_path, 'r', encoding='utf-8') as f: + data = json.load(f) + + # Prepare new version entry + new_entry = { + "version": version, + "kernel_version": kernel_version or "4.0.5", + "updates": { + "en": updates_en or ["Bug fixes and improvements"], + "zh_CN": updates_zh or ["错误修复和改进"] + } + } + + # Check if this version already exists + existing_versions = [entry["version"] for entry in data["version_updates"]] + if version in existing_versions: + # Update existing entry + for i, entry in enumerate(data["version_updates"]): + if entry["version"] == version: + data["version_updates"][i] = new_entry + break + print(f"✅ Updated existing version {version} in version.json") + else: + # Add new entry at the beginning + data["version_updates"].insert(0, new_entry) + print(f"✅ Added new version {version} to version.json") + + # Write back to file + with open(version_path, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + return True + + except Exception as e: + print(f"Error updating version.json: {e}") + return False + + +def validate_version(version: str) -> bool: + """Validate version format (semantic versioning)""" + pattern = r'^\d+\.\d+\.\d+(?:-[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*)?$' + return bool(re.match(pattern, version)) + + +def main(): + parser = argparse.ArgumentParser(description='Update StreamCap version') + parser.add_argument('version', help='New version (e.g., 1.0.2)') + parser.add_argument('--kernel-version', help='Kernel version (default: 4.0.5)') + parser.add_argument('--updates-zh', nargs='+', help='Chinese update descriptions') + parser.add_argument('--updates-en', nargs='+', help='English update descriptions') + parser.add_argument('--project-root', type=Path, default=Path.cwd(), + help='Project root directory (default: current directory)') + + args = parser.parse_args() + + # Validate version format + if not validate_version(args.version): + print(f"Error: Invalid version format '{args.version}'. Use semantic versioning (e.g., 1.0.2)") + sys.exit(1) + + # Ensure project root exists and contains expected files + project_root = args.project_root.resolve() + if not (project_root / "pyproject.toml").exists(): + print(f"Error: {project_root} doesn't appear to be the StreamCap project root") + sys.exit(1) + + print(f"Updating StreamCap version to {args.version}") + print(f"Project root: {project_root}") + + success = True + + # Update pyproject.toml + if not update_pyproject_toml(args.version, project_root): + success = False + + # Update version.json + if not update_version_json( + args.version, + args.kernel_version, + args.updates_zh, + args.updates_en, + project_root + ): + success = False + + if success: + print(f"\n🎉 Successfully updated version to {args.version}") + print("\nNext steps:") + print("1. Review the changes") + print("2. Commit the changes: git add . && git commit -m 'chore: bump version to {}'".format(args.version)) + print("3. Push to trigger auto-release: git push") + print("4. Or create a tag manually: git tag v{} && git push origin v{}".format(args.version, args.version)) + else: + print("\n❌ Failed to update version") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file