diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..6d2420b --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,110 @@ +# Release Drafter Configuration +# Documentation: https://github.com/release-drafter/release-drafter + +name-template: 'Version $RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' + +# Categories for organizing release notes +categories: + - title: '🚀 Features' + labels: + - 'feature' + - 'enhancement' + - title: '🐛 Bug Fixes' + labels: + - 'bug' + - 'fix' + - title: '🔧 Maintenance' + labels: + - 'maintenance' + - 'chore' + - 'refactor' + - 'dependencies' + - title: '📚 Documentation' + labels: + - 'documentation' + - 'docs' + - title: '⚡ Performance' + labels: + - 'performance' + - title: '🔒 Security' + labels: + - 'security' + +# Exclude certain labels from release notes +exclude-labels: + - 'skip-changelog' + - 'wip' + +# Change template (how each PR is listed) +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +change-title-escapes: '\<*_&' # Escape special markdown characters + +# Template for the release body +template: | + ## What's Changed + + $CHANGES + + ## đŸ“Ļ NuGet Packages + + The following packages are included in this release: + + - `MyCSharp.HttpUserAgentParser` + - `MyCSharp.HttpUserAgentParser.AspNetCore` + - `MyCSharp.HttpUserAgentParser.MemoryCache` + + ### Installation + + ```bash + dotnet add package MyCSharp.HttpUserAgentParser + dotnet add package MyCSharp.HttpUserAgentParser.AspNetCore + dotnet add package MyCSharp.HttpUserAgentParser.MemoryCache + ``` + + ## Contributors + + $CONTRIBUTORS + + --- + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION + +# Automatically label PRs based on modified files +autolabeler: + - label: 'documentation' + files: + - '*.md' + - 'docs/**/*' + - label: 'bug' + branch: + - '/fix\/.+/' + title: + - '/fix/i' + - label: 'feature' + branch: + - '/feature\/.+/' + title: + - '/feature/i' + - label: 'dependencies' + files: + - '**/packages.lock.json' + - '**/*.csproj' + - 'Directory.Packages.props' + - 'Directory.Build.props' + - label: 'github-actions' + files: + - '.github/workflows/**/*' + - label: 'tests' + files: + - 'tests/**/*' + - '**/*Tests.cs' + - '**/*Test.cs' + - label: 'performance' + files: + - 'perf/**/*' + - '**/*Benchmark*.cs' + +# Version resolver (uses version from workflow input) +version-resolver: + default: patch diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..0e567c6 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,89 @@ +name: Build and Test (Reusable) + +on: + workflow_call: + inputs: + dotnet-version: + description: '.NET version to use (can be multi-line for multiple versions)' + required: false + type: string + default: | + 8.0.x + 9.0.x + 10.0.x + configuration: + description: 'Build configuration' + required: false + type: string + default: 'Release' + upload-test-results: + description: 'Whether to upload test results as artifacts' + required: false + type: boolean + default: false + create-pack: + description: 'Whether to pack NuGet packages' + required: false + type: boolean + default: false + outputs: + version: + description: 'The calculated version from NBGV' + value: ${{ jobs.build.outputs.version }} + +jobs: + build: + name: Build and Test + runs-on: ubuntu-latest + outputs: + version: ${{ steps.nbgv.outputs.version }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for GitVersion + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ inputs.dotnet-version }} + + - name: Install Nerdbank.GitVersioning + run: dotnet tool install -g nbgv + + - name: Set version with NBGV + id: nbgv + run: | + nbgv get-version --format json > version.json + VERSION=$(nbgv get-version -v NuGetPackageVersion) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Calculated version: $VERSION" + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --configuration ${{ inputs.configuration }} --no-restore + + - name: Test + run: dotnet test --configuration ${{ inputs.configuration }} --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" + + - name: Upload test results + if: always() && inputs.upload-test-results + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ inputs.dotnet-version }} + path: '**/TestResults/**/*.trx' + + - name: Pack NuGet packages + if: inputs.create-pack + run: dotnet pack --configuration ${{ inputs.configuration }} --no-build --output ./artifacts + + - name: Upload NuGet packages + if: inputs.create-pack + uses: actions/upload-artifact@v4 + with: + name: nuget-packages + path: ./artifacts/*.nupkg + retention-days: 30 diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index 749a9c2..c0c71ec 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -10,118 +10,82 @@ permissions: packages: write jobs: - build-and-pack: - name: Build, Pack and Create Draft Release + build-and-test: + name: Build, Test and Pack + uses: ./.github/workflows/build-and-test.yml + with: + create-pack: true + + create-draft-release: + name: Create Draft Release + needs: build-and-test runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 # Required for GitVersion - - name: Setup .NET - uses: actions/setup-dotnet@v4 + - name: Download NuGet packages + uses: actions/download-artifact@v4 with: - dotnet-version: | - 8.0.x - 9.0.x - 10.0.x - - - name: Restore dependencies - run: dotnet restore - - - name: Build - run: dotnet build --configuration Release --no-restore - - - name: Test - run: dotnet test --configuration Release --no-build --verbosity normal - - - name: Pack NuGet packages - run: dotnet pack --configuration Release --no-build --output ./artifacts + name: nuget-packages + path: ./artifacts - - name: Get version from packages - id: get-version + - name: Check if tag exists + id: check-tag run: | - # Extract version from the first package - VERSION=$(ls ./artifacts/*.nupkg | head -1 | sed -n 's/.*\.MyCSharp\.HttpUserAgentParser\.\([0-9]\+\.[0-9]\+\.[0-9]\+.*\)\.nupkg/\1/p') - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Version: $VERSION" - - - name: Check for existing draft release - id: check-draft - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - DRAFT_RELEASE=$(gh release list --limit 100 --json isDraft,name,tagName | jq -r '.[] | select(.isDraft == true) | .tagName' | head -1) - if [ -n "$DRAFT_RELEASE" ]; then + TAG="v${{ needs.build-and-test.outputs.version }}" + if git rev-parse "$TAG" >/dev/null 2>&1; then echo "exists=true" >> $GITHUB_OUTPUT - echo "tag=$DRAFT_RELEASE" >> $GITHUB_OUTPUT - echo "Found existing draft release: $DRAFT_RELEASE" + echo "âš ī¸ Tag $TAG already exists" else echo "exists=false" >> $GITHUB_OUTPUT - echo "No existing draft release found" + echo "✅ Tag $TAG does not exist yet" fi - - name: Delete existing draft release - if: steps.check-draft.outputs.exists == 'true' + - name: Create/Update Draft Release + if: steps.check-tag.outputs.exists == 'false' + uses: release-drafter/release-drafter@v6 env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - echo "Deleting existing draft release: ${{ steps.check-draft.outputs.tag }}" - gh release delete ${{ steps.check-draft.outputs.tag }} --yes --cleanup-tag || true - - - name: Create draft release + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + config-name: release-drafter.yml + version: v${{ needs.build-and-test.outputs.version }} + tag: v${{ needs.build-and-test.outputs.version }} + name: Version ${{ needs.build-and-test.outputs.version }} + publish: false + prerelease: false + + - name: Upload packages to draft release + if: steps.check-tag.outputs.exists == 'false' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - VERSION="${{ steps.get-version.outputs.version }}" - TAG="v${VERSION}" - - # Create release notes - cat > release-notes.md << 'EOF' - ## What's Changed - - This is an automated draft release created from the main branch. - - ### Packages - - The following NuGet packages are included in this release: - - EOF - - # List all packages + TAG="v${{ needs.build-and-test.outputs.version }}" + # Wait a moment for the release to be created + sleep 2 + # Upload artifacts to the draft release for file in ./artifacts/*.nupkg; do - filename=$(basename "$file") - echo "- \`$filename\`" >> release-notes.md + gh release upload "$TAG" "$file" --clobber done + echo "✅ Uploaded NuGet packages to draft release" - cat >> release-notes.md << 'EOF' - - ### Installation - - ```bash - dotnet add package MyCSharp.HttpUserAgentParser - dotnet add package MyCSharp.HttpUserAgentParser.AspNetCore - dotnet add package MyCSharp.HttpUserAgentParser.MemoryCache - ``` - - **Full Changelog**: https://github.com/${{ github.repository }}/commits/${{ github.sha }} - EOF - - # Create draft release - gh release create "$TAG" \ - ./artifacts/*.nupkg \ - --draft \ - --title "Release $VERSION" \ - --notes-file release-notes.md \ - --target ${{ github.sha }} - - echo "Created draft release: $TAG" - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: nuget-packages - path: ./artifacts/*.nupkg - retention-days: 30 + - name: Summary + if: steps.check-tag.outputs.exists == 'false' + run: | + echo "✅ Draft release created/updated" >> $GITHUB_STEP_SUMMARY + echo "Version: v${{ needs.build-and-test.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Next steps:" >> $GITHUB_STEP_SUMMARY + echo "1. Go to [Releases](../../releases)" >> $GITHUB_STEP_SUMMARY + echo "2. Review the draft release" >> $GITHUB_STEP_SUMMARY + echo "3. Edit release notes if needed" >> $GITHUB_STEP_SUMMARY + echo "4. Publish release to trigger production deployment" >> $GITHUB_STEP_SUMMARY + + - name: Release already exists + if: steps.check-tag.outputs.exists == 'true' + run: | + echo "â„šī¸ Release v${{ needs.build-and-test.outputs.version }} already exists" >> $GITHUB_STEP_SUMMARY + echo "No action taken" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index bf1f32f..dea70ab 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -10,37 +10,23 @@ permissions: pull-requests: read jobs: - validate: - name: Build and Test - runs-on: ubuntu-latest - - strategy: - matrix: - dotnet-version: ['8.0.x', '9.0.x', '10.0.x'] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Required for GitVersion - - - name: Setup .NET ${{ matrix.dotnet-version }} - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ matrix.dotnet-version }} - - - name: Restore dependencies - run: dotnet restore - - - name: Build - run: dotnet build --configuration Release --no-restore - - - name: Test - run: dotnet test --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-results-${{ matrix.dotnet-version }} - path: '**/TestResults/**/*.trx' + validate-dotnet-8: + name: Test on .NET 8.0 + uses: ./.github/workflows/build-and-test.yml + with: + dotnet-version: '8.0.x' + upload-test-results: true + + validate-dotnet-9: + name: Test on .NET 9.0 + uses: ./.github/workflows/build-and-test.yml + with: + dotnet-version: '9.0.x' + upload-test-results: true + + validate-dotnet-10: + name: Test on .NET 10.0 + uses: ./.github/workflows/build-and-test.yml + with: + dotnet-version: '10.0.x' + upload-test-results: true