fix:修复IEC61850客户端无法解析服务端主动报告的bug #16
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Windows | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build: | |
| runs-on: windows-2022 | |
| timeout-minutes: 120 | |
| outputs: | |
| release_tag: ${{ steps.version.outputs.release_tag }} | |
| release_version: ${{ steps.version.outputs.release_version }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Prepare release version from tag | |
| id: version | |
| shell: pwsh | |
| run: | | |
| if ("${{ github.ref_type }}" -ne "tag") { | |
| Write-Error "This release workflow must be triggered by a version tag, for example v1.4.1." | |
| exit 1 | |
| } | |
| $tag = "${{ github.ref_name }}".Trim() | |
| if ($tag -notmatch '^v\d+\.\d+\.\d+$') { | |
| Write-Error "Release tag must match vMAJOR.MINOR.PATCH, for example v1.4.1. Got: $tag" | |
| exit 1 | |
| } | |
| $version = $tag.Substring(1) | |
| "release_version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| "release_tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| Write-Host "Release tag: $tag" | |
| Write-Host "Release version: $version" | |
| - name: Sync release version to build metadata | |
| shell: pwsh | |
| run: | | |
| $version = "${{ steps.version.outputs.release_version }}" | |
| $path = "pyproject.toml" | |
| $content = Get-Content -Path $path -Raw -Encoding UTF8 | |
| $pattern = '(?m)^version\s*=\s*"[^"]+"' | |
| if (-not [regex]::IsMatch($content, $pattern)) { | |
| Write-Error "Cannot find version line in pyproject.toml" | |
| exit 1 | |
| } | |
| $newContent = [regex]::Replace($content, $pattern, "version = `"$version`"", 1) | |
| Set-Content -Path $path -Value $newContent -Encoding UTF8 | |
| python scripts/sync_version.py | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "Version sync failed" | |
| exit 1 | |
| } | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v5 | |
| - name: Install Python build dependencies | |
| run: uv sync --extra build | |
| - name: Setup Rust cache | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src-tauri -> src-tauri/target | |
| - name: Install frontend dependencies | |
| working-directory: front | |
| run: npm ci | |
| - name: Build Windows application (Tauri + Sidecar) | |
| shell: pwsh | |
| run: | | |
| .venv\Scripts\Activate.ps1 | |
| .\scripts\build_tauri_windows.ps1 | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ems-simulate-windows-${{ steps.version.outputs.release_tag }} | |
| path: | | |
| src-tauri/target/release/ems-simulate.exe | |
| src-tauri/target/release/bundle/ | |
| compression-level: 0 | |
| if-no-files-found: error | |
| release: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Generate changelog | |
| id: changelog | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| current_tag="${{ needs.build.outputs.release_tag }}" | |
| git fetch --tags --force | |
| previous_tag="$(git tag --sort=-version:refname | grep -E '^v[0-9]+(\.[0-9]+){1,2}$' | grep -vFx "$current_tag" | head -n 1 || true)" | |
| if [ -n "$previous_tag" ]; then | |
| commit_range="${previous_tag}..${GITHUB_SHA}" | |
| range_label="Changes since ${previous_tag}" | |
| else | |
| commit_range="${GITHUB_SHA}" | |
| range_label="Initial release" | |
| fi | |
| workdir="$(mktemp -d)" | |
| trap 'rm -rf "$workdir"' EXIT | |
| for section in breaking feat fix perf refactor docs build ci test chore other; do | |
| : > "$workdir/$section.md" | |
| done | |
| cc_header_re='^[A-Za-z]+(\([^)]+\))?(!)?[::][[:space:]]*.+$' | |
| cc_full_re='^([A-Za-z]+)(\(([^)]+)\))?(!)?[::][[:space:]]*(.+)$' | |
| trim() { | |
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<< "$1" | |
| } | |
| escape_md() { | |
| local value="$1" | |
| value="${value//\`/\\\`}" | |
| printf '%s' "$value" | |
| } | |
| clean_line() { | |
| local value | |
| value="$(trim "$1")" | |
| value="$(sed -E 's/^```[[:alnum:]_-]*[[:space:]]*//' <<< "$value")" | |
| value="$(sed -E 's/[[:space:]]*```$//' <<< "$value")" | |
| value="$(sed -E 's/^([-*+]|[0-9]+\.)[[:space:]]+//' <<< "$value")" | |
| trim "$value" | |
| } | |
| format_body() { | |
| local body="$1" | |
| local line cleaned | |
| while IFS= read -r line; do | |
| cleaned="$(clean_line "$line")" | |
| [ -z "$cleaned" ] && continue | |
| [[ "$cleaned" == \`\`\`* ]] && continue | |
| printf ' - %s\n' "$(escape_md "$cleaned")" | |
| done <<< "$body" | |
| } | |
| write_entry() { | |
| local file="$1" | |
| local scope="$2" | |
| local description="$3" | |
| local short_sha="$4" | |
| local body="$5" | |
| if [ -n "$scope" ]; then | |
| printf -- '- **%s**: %s (`%s`)\n' "$(escape_md "$scope")" "$(escape_md "$description")" "$short_sha" >> "$file" | |
| else | |
| printf -- '- %s (`%s`)\n' "$(escape_md "$description")" "$short_sha" >> "$file" | |
| fi | |
| format_body "$body" >> "$file" | |
| } | |
| parse_commit() { | |
| local short_sha="$1" | |
| local subject="$2" | |
| local body="$3" | |
| local parsed_subject type scope description bang target_file promoted line remaining_body | |
| parsed_subject="$(clean_line "$subject")" | |
| remaining_body="$body" | |
| if ! [[ "$parsed_subject" =~ $cc_header_re ]]; then | |
| promoted="" | |
| remaining_body="" | |
| while IFS= read -r line; do | |
| line="$(clean_line "$line")" | |
| [ -z "$line" ] && continue | |
| if [ -z "$promoted" ] && [[ "$line" =~ $cc_header_re ]]; then | |
| promoted="$line" | |
| continue | |
| fi | |
| remaining_body+="$line"$'\n' | |
| done <<< "$body" | |
| if [ -n "$promoted" ]; then | |
| parsed_subject="$promoted" | |
| fi | |
| fi | |
| if [[ "$parsed_subject" =~ $cc_full_re ]]; then | |
| type="$(tr '[:upper:]' '[:lower:]' <<< "${BASH_REMATCH[1]}")" | |
| scope="${BASH_REMATCH[3]:-}" | |
| bang="${BASH_REMATCH[4]:-}" | |
| description="${BASH_REMATCH[5]}" | |
| case "$type" in | |
| feat) target_file="$workdir/feat.md" ;; | |
| fix) target_file="$workdir/fix.md" ;; | |
| perf) target_file="$workdir/perf.md" ;; | |
| refactor) target_file="$workdir/refactor.md" ;; | |
| docs) target_file="$workdir/docs.md" ;; | |
| build) target_file="$workdir/build.md" ;; | |
| ci) target_file="$workdir/ci.md" ;; | |
| test) target_file="$workdir/test.md" ;; | |
| chore|style) target_file="$workdir/chore.md" ;; | |
| *) target_file="$workdir/other.md" ;; | |
| esac | |
| if [ "$bang" = "!" ] || grep -qiE '^BREAKING CHANGE:' <<< "$remaining_body"; then | |
| write_entry "$workdir/breaking.md" "$scope" "$description" "$short_sha" "$remaining_body" | |
| else | |
| write_entry "$target_file" "$scope" "$description" "$short_sha" "$remaining_body" | |
| fi | |
| else | |
| parsed_subject="$(clean_line "$subject")" | |
| [ -z "$parsed_subject" ] && parsed_subject="No subject" | |
| write_entry "$workdir/other.md" "" "$parsed_subject" "$short_sha" "$body" | |
| fi | |
| } | |
| while IFS= read -r -d $'\x1e' record; do | |
| [ -z "$record" ] && continue | |
| sha="${record%%$'\x1f'*}" | |
| rest="${record#*$'\x1f'}" | |
| short_sha="${rest%%$'\x1f'*}" | |
| rest="${rest#*$'\x1f'}" | |
| subject="${rest%%$'\x1f'*}" | |
| body="${rest#*$'\x1f'}" | |
| parse_commit "$short_sha" "$subject" "$body" | |
| done < <(git log "$commit_range" --no-merges --pretty=format:'%H%x1f%h%x1f%s%x1f%b%x1e') | |
| notes_file="release-notes.md" | |
| { | |
| printf '## %s\n\n' "$current_tag" | |
| printf '%s.\n\n' "$range_label" | |
| } > "$notes_file" | |
| add_section() { | |
| local file="$1" | |
| local title="$2" | |
| if [ -s "$file" ]; then | |
| printf '### %s\n\n' "$title" >> "$notes_file" | |
| cat "$file" >> "$notes_file" | |
| printf '\n' >> "$notes_file" | |
| fi | |
| } | |
| add_section "$workdir/breaking.md" "💥 Breaking Changes" | |
| add_section "$workdir/feat.md" "✨ 新功能" | |
| add_section "$workdir/fix.md" "🐛 Bug 修复" | |
| add_section "$workdir/perf.md" "⚡ 性能优化" | |
| add_section "$workdir/refactor.md" "♻️ 代码重构" | |
| add_section "$workdir/docs.md" "📝 文档更新" | |
| add_section "$workdir/build.md" "📦 构建相关" | |
| add_section "$workdir/ci.md" "👷 CI 配置" | |
| add_section "$workdir/test.md" "✅ 测试" | |
| add_section "$workdir/chore.md" "🔧 杂项" | |
| add_section "$workdir/other.md" "其他变更" | |
| if ! grep -q '^### ' "$notes_file"; then | |
| printf 'No commit changes found for this release.\n' >> "$notes_file" | |
| fi | |
| printf 'notes_file=%s\n' "$notes_file" >> "$GITHUB_OUTPUT" | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: ems-simulate-windows-* | |
| path: release-assets | |
| merge-multiple: true | |
| - name: Delete existing release (if any) to avoid stale assets | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const tag = '${{ needs.build.outputs.release_tag }}'; | |
| try { | |
| const { data: release } = await github.rest.repos.getReleaseByTag({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag: tag, | |
| }); | |
| if (release) { | |
| // Delete all existing assets first | |
| for (const asset of release.assets) { | |
| await github.rest.repos.deleteReleaseAsset({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| asset_id: asset.id, | |
| }); | |
| console.log(`Deleted asset: ${asset.name}`); | |
| } | |
| // Delete the release itself | |
| await github.rest.repos.deleteRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: release.id, | |
| }); | |
| console.log(`Deleted existing release: ${tag}`); | |
| } | |
| } catch (err) { | |
| if (err.status === 404) { | |
| console.log(`No existing release found for tag: ${tag}`); | |
| } else { | |
| throw err; | |
| } | |
| } | |
| - name: Publish GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.build.outputs.release_tag }} | |
| name: EMS Simulate ${{ needs.build.outputs.release_tag }} | |
| target_commitish: ${{ github.sha }} | |
| draft: false | |
| prerelease: false | |
| make_latest: true | |
| body_path: ${{ steps.changelog.outputs.notes_file }} | |
| files: release-assets/*.msi | |
| fail_on_unmatched_files: true | |
| generate_release_notes: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |