Merge pull request #45 from 1ngaOS/feat/develoop #12
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: wangamail-csharp | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| paths: | |
| - 'wangamail-csharp/**' | |
| - '.github/workflows/wangamail-csharp.yml' | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'wangamail-csharp/**' | |
| - '.github/workflows/wangamail-csharp.yml' | |
| workflow_dispatch: | |
| jobs: | |
| format: | |
| name: Format | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Restore | |
| run: | | |
| dotnet restore wangamail-csharp/WangaMail.CSharp.csproj | |
| dotnet restore wangamail-csharp/tests/WangaMail.CSharp.Tests/WangaMail.CSharp.Tests.csproj | |
| - name: Check formatting | |
| run: dotnet format wangamail-csharp/WangaMail.CSharp.csproj --verify-no-changes --verbosity minimal | |
| - name: Job summary | |
| if: always() | |
| run: | | |
| echo "## Format" >> $GITHUB_STEP_SUMMARY | |
| echo "**Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| needs: format | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Restore | |
| run: | | |
| dotnet restore wangamail-csharp/WangaMail.CSharp.csproj | |
| dotnet restore wangamail-csharp/tests/WangaMail.CSharp.Tests/WangaMail.CSharp.Tests.csproj | |
| - name: Build | |
| run: dotnet build wangamail-csharp/WangaMail.CSharp.csproj -c Release --no-restore | |
| - name: Job summary | |
| if: always() | |
| run: | | |
| echo "## Build" >> $GITHUB_STEP_SUMMARY | |
| echo "**Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Restore | |
| run: | | |
| dotnet restore wangamail-csharp/WangaMail.CSharp.csproj | |
| dotnet restore wangamail-csharp/tests/WangaMail.CSharp.Tests/WangaMail.CSharp.Tests.csproj | |
| - name: Test | |
| run: dotnet test wangamail-csharp/tests/WangaMail.CSharp.Tests/WangaMail.CSharp.Tests.csproj -c Release --no-restore | |
| - name: Job summary | |
| if: always() | |
| run: | | |
| echo "## Test" >> $GITHUB_STEP_SUMMARY | |
| echo "**Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY | |
| version-bump: | |
| name: Bump Version | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| outputs: | |
| version: ${{ steps.bump.outputs.new_version }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Get current version | |
| id: current | |
| run: | | |
| python3 - <<'PY' | |
| import os | |
| import re | |
| from pathlib import Path | |
| p = Path("wangamail-csharp/WangaMail.CSharp.csproj") | |
| text = p.read_text(encoding="utf-8") | |
| m = re.search(r"<Version>(\d+\.\d+\.\d+)</Version>", text) | |
| if not m: | |
| raise SystemExit("Error: Failed to extract Version from WangaMail.CSharp.csproj") | |
| version = m.group(1) | |
| with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f: | |
| f.write(f"version={version}\n") | |
| print(f"Current version: {version}") | |
| PY | |
| - name: Bump patch version | |
| id: bump | |
| run: | | |
| V="${{ steps.current.outputs.version }}" | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$V" | |
| PATCH=$((PATCH + 1)) | |
| NEW="${MAJOR}.${MINOR}.${PATCH}" | |
| echo "new_version=$NEW" >> $GITHUB_OUTPUT | |
| python3 - <<PY | |
| import re | |
| from pathlib import Path | |
| p = Path("wangamail-csharp/WangaMail.CSharp.csproj") | |
| text = p.read_text(encoding="utf-8") | |
| text2, n = re.subn( | |
| r"<Version>\d+\.\d+\.\d+</Version>", | |
| "<Version>${NEW}</Version>", | |
| text, | |
| count=1, | |
| ) | |
| if n != 1: | |
| raise SystemExit("Error: Failed to update Version in WangaMail.CSharp.csproj") | |
| p.write_text(text2, encoding="utf-8") | |
| print("New version: ${NEW}") | |
| PY | |
| - name: Configure Git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Commit and push version | |
| run: | | |
| git add wangamail-csharp/WangaMail.CSharp.csproj | |
| if git diff --staged --quiet; then | |
| echo "No version change to commit" | |
| else | |
| git commit -m "chore(wangamail-csharp): bump version to ${{ steps.bump.outputs.new_version }} [skip ci]" | |
| git fetch origin main || true | |
| git pull origin main --rebase || git pull origin main --no-rebase || true | |
| # Retry push to avoid stale-ref/lock failures when remote updates | |
| for _ in 1 2 3; do | |
| if git push origin main; then | |
| break | |
| fi | |
| git fetch origin main || true | |
| git pull origin main --rebase || git pull origin main --no-rebase || true | |
| git push origin main --force-with-lease || true | |
| done | |
| fi | |
| - name: Job summary | |
| if: always() | |
| run: | | |
| echo "## Bump Version" >> $GITHUB_STEP_SUMMARY | |
| echo "**New version:** ${{ steps.bump.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY | |
| build-release: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| needs: version-bump | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Pull latest (version bump) | |
| run: git pull origin main || true | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Restore | |
| run: dotnet restore wangamail-csharp/WangaMail.CSharp.csproj | |
| - name: Build release | |
| run: dotnet build wangamail-csharp/WangaMail.CSharp.csproj -c Release --no-restore | |
| - name: Job summary | |
| if: always() | |
| run: | | |
| echo "## Build" >> $GITHUB_STEP_SUMMARY | |
| echo "**Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY | |
| get-version: | |
| name: Get Version | |
| runs-on: ubuntu-latest | |
| needs: [version-bump, build-release] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Pull latest | |
| run: git pull origin main || true | |
| - name: Get version | |
| id: version | |
| run: | | |
| V=$(python3 - <<'PY' | |
| import re | |
| from pathlib import Path | |
| text = Path("wangamail-csharp/WangaMail.CSharp.csproj").read_text(encoding="utf-8") | |
| m = re.search(r"<Version>(\d+\.\d+\.\d+)</Version>", text) | |
| print(m.group(1) if m else "") | |
| PY | |
| ) | |
| if [ -z "$V" ]; then | |
| echo "Error: Failed to extract Version from WangaMail.CSharp.csproj" | |
| exit 1 | |
| fi | |
| echo "version=$V" >> $GITHUB_OUTPUT | |
| echo "Version: $V" | |
| - name: Job summary | |
| if: always() | |
| run: | | |
| echo "## Get Version" >> $GITHUB_STEP_SUMMARY | |
| echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| publish: | |
| name: Publish to NuGet | |
| runs-on: ubuntu-latest | |
| needs: get-version | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Pull latest | |
| run: git pull origin main || true | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Restore | |
| run: dotnet restore wangamail-csharp/WangaMail.CSharp.csproj | |
| - name: Pack | |
| run: dotnet pack wangamail-csharp/WangaMail.CSharp.csproj -c Release --no-restore | |
| - name: Publish to NuGet | |
| env: | |
| NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} | |
| run: | | |
| dotnet nuget push "wangamail-csharp/bin/Release/*.nupkg" \ | |
| --api-key "$NUGET_API_KEY" \ | |
| --source "https://api.nuget.org/v3/index.json" \ | |
| --skip-duplicate | |
| - name: Job summary | |
| if: always() | |
| run: | | |
| echo "## Publish" >> $GITHUB_STEP_SUMMARY | |
| echo "**Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Version:** ${{ needs.get-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| release: | |
| name: Create Release | |
| runs-on: ubuntu-latest | |
| needs: [get-version, publish] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Create tag and release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: wangamail-csharp-v${{ needs.get-version.outputs.version }} | |
| name: wangamail-csharp v${{ needs.get-version.outputs.version }} | |
| body: | | |
| ## wangamail-csharp ${{ needs.get-version.outputs.version }} | |
| Send email via Microsoft Graph API using app registration (client credentials). | |
| - [NuGet](https://www.nuget.org/packages/wangamail-csharp) | |
| draft: false | |
| prerelease: false | |
| - name: Job summary | |
| if: always() | |
| run: | | |
| echo "## Release" >> $GITHUB_STEP_SUMMARY | |
| echo "**Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tag:** wangamail-csharp-v${{ needs.get-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| monitor-failures: | |
| name: Monitor Failed Jobs | |
| runs-on: ubuntu-latest | |
| if: always() && failure() | |
| needs: [format, build, test, version-bump, build-release, get-version, publish, release] | |
| permissions: | |
| contents: read | |
| issues: write | |
| actions: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Create failure issue | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { data: run } = await github.rest.actions.getWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: context.runId | |
| }); | |
| const { data: jobsData } = await github.rest.actions.listJobsForWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: context.runId | |
| }); | |
| const failedJobs = jobsData.jobs.filter(job => | |
| job.conclusion === 'failure' || | |
| job.conclusion === 'cancelled' || | |
| job.conclusion === 'timed_out' | |
| ); | |
| if (failedJobs.length === 0) { | |
| return; | |
| } | |
| const branch = context.ref.replace('refs/heads/', ''); | |
| const title = `🚨 CI/CD Failure: wangamail-csharp - ${branch} branch`; | |
| let body = `## CI/CD Pipeline Failure\n\n`; | |
| body += `**Workflow:** [wangamail-csharp](${run.html_url})\n`; | |
| body += `**Branch:** \`${branch}\`\n`; | |
| body += `**Commit:** [${context.sha.substring(0, 7)}](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/commit/${context.sha})\n\n`; | |
| body += `### Failed Jobs (${failedJobs.length})\n\n`; | |
| for (const job of failedJobs) { | |
| body += `- **${job.name}**: ${job.conclusion} ([logs](${job.html_url}))\n`; | |
| } | |
| const milestoneTitle = 'CI/CD Failures'; | |
| let milestoneNumber = null; | |
| try { | |
| const { data: milestones } = await github.rest.issues.listMilestones({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open' | |
| }); | |
| let milestone = milestones.find(m => m.title === milestoneTitle); | |
| if (!milestone) { | |
| const { data: created } = await github.rest.issues.createMilestone({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: milestoneTitle, | |
| description: 'Track all CI/CD pipeline failures across the repository', | |
| state: 'open' | |
| }); | |
| milestone = created; | |
| } | |
| milestoneNumber = milestone.number; | |
| } catch (e) { | |
| console.log('Milestone setup failed:', e.message); | |
| } | |
| const { data: issues } = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| labels: 'ci-failure,wangamail-csharp' | |
| }); | |
| const existing = issues.find(issue => issue.title === title); | |
| if (existing) { | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: existing.number, | |
| body, | |
| state: 'open', | |
| milestone: milestoneNumber | |
| }); | |
| } else { | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title, | |
| body, | |
| labels: ['ci-failure', 'wangamail-csharp', 'bug', 'priority-high'], | |
| milestone: milestoneNumber | |
| }); | |
| } | |
| - name: Job summary | |
| if: always() | |
| run: | | |
| echo "## Monitor Failed Jobs" >> $GITHUB_STEP_SUMMARY | |
| echo "**Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY |