diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 71634218..1bfbe512 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,28 +1,50 @@ -# This workflow will build a .NET project +# This workflow will build and test a .NET project # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net -name: .NET +name: .NET CI on: push: - branches: [ "*" ] + branches: [ main, develop ] pull_request: - branches: [ "*" ] + branches: [ main, develop ] jobs: build: - runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 10.0.x + + - name: Cache NuGet packages + uses: actions/cache@v4 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} + restore-keys: | + ${{ runner.os }}-nuget- + - name: Restore dependencies run: dotnet restore + - name: Build - run: dotnet build --no-restore + run: dotnet build --no-restore --configuration Release + - name: Test - run: dotnet test --no-build --verbosity normal + run: dotnet test --no-build --configuration Release --verbosity normal + + - name: Code Coverage + run: dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --results-directory ./TestResults + + - name: Upload coverage reports + uses: actions/upload-artifact@v4 + if: always() + with: + name: coverage-reports + path: ./TestResults/**/coverage.cobertura.xml + retention-days: 30 diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml new file mode 100644 index 00000000..30a482f0 --- /dev/null +++ b/.github/workflows/nuget-publish.yml @@ -0,0 +1,282 @@ +name: NuGet Publish + +on: + push: + branches: [ master, develop ] + tags: [ '*' ] + workflow_dispatch: + inputs: + version: + description: 'Override version (optional)' + required: false + type: string + environment: + description: 'Publish environment' + required: false + default: 'auto' + type: choice + options: + - auto + - staging + - production + create_release: + description: 'Create GitHub release (production only)' + required: false + type: boolean + default: false + release_notes: + description: 'Release notes' + required: false + type: string + +env: + DOTNET_VERSION: 10.0.x + NUGET_SOURCE: https://api.nuget.org/v3/index.json + +jobs: + build: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.setup.outputs.version }} + is-nightly: ${{ steps.setup.outputs.is-nightly }} + is-release: ${{ steps.setup.outputs.is-release }} + should-publish: ${{ steps.setup.outputs.should-publish }} + environment: ${{ steps.setup.outputs.environment }} + create-release: ${{ steps.setup.outputs.create-release }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Cache NuGet packages + uses: actions/cache@v4 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} + restore-keys: | + ${{ runner.os }}-nuget- + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore --configuration Release + + - name: Test + run: dotnet test --no-build --configuration Release --verbosity normal + + - name: Setup version and publishing strategy + id: setup + uses: actions/github-script@v7 + with: + script: | + const core = require('@actions/core'); + + // Get current context + const ref = context.ref; + const eventName = context.eventName; + const inputs = context.payload.inputs || {}; + + // Initialize variables + let version = ''; + let isNightly = false; + let isRelease = false; + let shouldPublish = false; + let environment = 'auto'; + let createRelease = false; + + // Handle manual override + if (inputs.version) { + version = inputs.version; + environment = inputs.environment || 'auto'; + isNightly = version.includes('nightly'); + isRelease = !isNightly && !version.includes('preview'); + shouldPublish = true; + createRelease = inputs.create_release === true && isRelease; + } else { + // Automatic version detection + if (ref === 'refs/heads/develop') { + // Nightly builds for develop branch + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + const commitSha = context.sha.substring(0, 7); + version = `1.0.0-nightly-${timestamp}-${commitSha}`; + isNightly = true; + shouldPublish = true; + environment = 'nightly'; + } else if (ref.startsWith('refs/tags/')) { + // Release from git tag + version = ref.replace('refs/tags/v', ''); + version = ref.replace('refs/tags/', ''); + isRelease = true; + shouldPublish = true; + environment = 'production'; + // Create GitHub release for all tags + createRelease = true; + } else if (eventName === 'release') { + // Release from GitHub release + version = context.payload.release.tag_name; + isRelease = true; + shouldPublish = true; + environment = 'production'; + } else if (ref === 'refs/heads/main') { + // Main branch builds (preview versions) + version = `1.0.0-preview-${context.runNumber}`; + environment = 'staging'; + shouldPublish = false; // Don't auto-publish from main + } + } + + // Resolve auto environment + if (environment === 'auto') { + environment = isNightly ? 'nightly' : isRelease ? 'production' : 'staging'; + } + + // Validate version format (basic check) + if (!version.match(/^\d+\.\d+\.\d+(-[\w\.\-]+)?$/)) { + core.setFailed(`Invalid version format: ${version}`); + return; + } + + // Set outputs + core.setOutput('version', version); + core.setOutput('is-nightly', isNightly); + core.setOutput('is-release', isRelease); + core.setOutput('should-publish', shouldPublish); + core.setOutput('environment', environment); + core.setOutput('create-release', createRelease && ref.startsWith('refs/tags/')); + + // Log information + console.log(`Version: ${version}`); + console.log(`Is Nightly: ${isNightly}`); + console.log(`Is Release: ${isRelease}`); + console.log(`Should Publish: ${shouldPublish}`); + console.log(`Environment: ${environment}`); + console.log(`Create Release: ${createRelease}`); + + - name: Pack + run: | + dotnet pack Netorrent/Netorrent.csproj \ + --configuration Release \ + --output ./artifacts \ + -p:PackageVersion=${{ steps.setup.outputs.version }} \ + -p:ContinuousIntegrationBuild=true \ + -p:IncludeSymbols=true + + - name: Validate packages + run: | + dotnet nuget verify ./artifacts/*.nupkg --all + dotnet nuget verify ./artifacts/*.snupkg --all + + - name: List packages + if: steps.setup.outputs.should-publish == 'true' + run: | + echo "Generated packages:" + ls -la ./artifacts/ || echo "No artifacts found" + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + if: steps.setup.outputs.should-publish == 'true' + with: + name: nuget-packages-${{ steps.setup.outputs.version }} + path: ./artifacts/*.nupkg + retention-days: ${{ steps.setup.outputs.is-nightly == 'true' && '30' || '90' }} + + publish: + needs: build + runs-on: ubuntu-latest + if: needs.build.outputs.should-publish == 'true' + environment: ${{ needs.build.outputs.environment }} + + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: nuget-packages-${{ needs.build.outputs.version }} + path: ./artifacts + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Publish to NuGet + run: | + dotnet nuget push ./artifacts/*.nupkg \ + --source ${{ env.NUGET_SOURCE }} \ + --api-key ${{ secrets.NUGET_API_KEY }} \ + --skip-duplicate + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + + - name: Publish symbols + run: | + dotnet nuget push ./artifacts/*.snupkg \ + --source ${{ env.NUGET_SOURCE }} \ + --api-key ${{ secrets.NUGET_API_KEY }} \ + --skip-duplicate + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + + - name: Create GitHub Release + if: needs.build.outputs.create-release == 'true' + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ needs.build.outputs.version }} + release_name: Release ${{ needs.build.outputs.version }} + body: | + ## 📦 Package Information + - **Version**: `${{ needs.build.outputs.version }}` + - **NuGet**: [Netorrent.${{ needs.build.outputs.version }}](https://www.nuget.org/packages/Netorrent/${{ needs.build.outputs.version }}) + - **Installation**: `dotnet add package Netorrent --version ${{ needs.build.outputs.version }}` + + ${{ github.event.inputs.release_notes || '' }} + + --- + + 🤖 *This release was automatically generated. Check [Actions](https://github.com/${{ github.repository }}/actions) for build details.* + draft: false + prerelease: ${{ contains(needs.build.outputs.version, 'nightly') || contains(needs.build.outputs.version, 'preview') || contains(needs.build.outputs.version, 'rc') }} + + - name: Upload release assets + if: needs.build.outputs.create-release == 'true' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = require('path'); + + const version = '${{ needs.build.outputs.version }}'; + const artifactsDir = './artifacts'; + + // Get release ID + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: version + }); + + // Upload all .nupkg and .snupkg files + const files = fs.readdirSync(artifactsDir) + .filter(file => file.endsWith('.nupkg') || file.endsWith('.snupkg')); + + for (const file of files) { + const filePath = path.join(artifactsDir, file); + const fileData = fs.readFileSync(filePath); + + await github.rest.repos.uploadReleaseAsset({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release.id, + name: file, + data: fileData + }); + } + diff --git a/Netorrent/Netorrent.csproj b/Netorrent/Netorrent.csproj index cb82282f..6b415652 100644 --- a/Netorrent/Netorrent.csproj +++ b/Netorrent/Netorrent.csproj @@ -1,11 +1,25 @@ - - + + Library net10.0 enable true true enable + + Netorrent + Your Name + A .NET library for torrent operations with support for peer-to-peer networking + https://github.com/yourusername/Netorrent + https://github.com/yourusername/Netorrent + MIT + false + torrent;dotnet;networking;p2p;filesharing;bittorrent + true + true + snupkg + git + Copyright © 2025 CS8600;CS8602;CS8603;CS8604;CS8618;CS8625 diff --git a/README.md b/README.md index 9288dce1..4e5ecaeb 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,21 @@ A high-performance, async-first .NET 10.0 BitTorrent client library for download ## Installation +### Stable Release ```bash dotnet add package Netorrent ``` +### Nightly Builds (for testing latest features) +```bash +dotnet add package Netorrent --version 1.0.0-nightly-* +``` + +### Preview Releases +```bash +dotnet add package Netorrent --prerelease +``` + **Requirements:** - .NET 10.0 or higher - Dependencies automatically included: @@ -134,6 +145,55 @@ The library is designed with async-first architecture for optimal performance: - Concurrent collections for thread-safe operations - Minimal allocations in hot paths +## Package Versions + +### 📦 Stable Releases +- **Purpose**: Production-ready versions with stable APIs +- **Versioning**: Semantic Versioning (e.g., 1.0.0, 1.1.0, 1.0.1) +- **Updates**: Bug fixes and new features +- **Installation**: `dotnet add package Netorrent` + +### 🌙 Nightly Builds +- **Purpose**: Latest code from every commit to develop branch +- **Versioning**: Timestamped with commit hash (e.g., `1.0.0-nightly-20250114-1430-a1b2c3d`) +- **Updates**: Per-commit builds for immediate testing +- **Installation**: `dotnet add package Netorrent --version 1.0.0-nightly-*` +- **Warning**: May contain breaking changes or bugs + +### 🚀 Preview Releases +- **Purpose**: Pre-release testing of upcoming features +- **Versioning**: Pre-release suffix (e.g., `1.0.0-preview-123`) +- **Updates**: Periodic builds from main branch +- **Installation**: `dotnet add package Netorrent --prerelease` + +### Version Selection Strategies + +**For Production Applications:** +```xml + +``` + +**For Testing Latest Features:** +```xml + +``` + +**For Early Adopters:** +```xml + +``` + +## CI/CD Integration + +The project uses GitHub Actions for automated publishing: + +- **Per-commit nightly builds** from `develop` branch +- **Release builds** from git tags +- **Manual publishing** for custom versions +- **Comprehensive testing** before all publications + +See [`.github/workflows/`](.github/workflows/) for complete workflow configurations. + ## Features - [x] **Torrent files** - Complete .torrent file support diff --git a/docs/NuGet-Publishing.md b/docs/NuGet-Publishing.md new file mode 100644 index 00000000..75d5aa71 --- /dev/null +++ b/docs/NuGet-Publishing.md @@ -0,0 +1,289 @@ +# NuGet Publishing Setup Guide + +This guide explains how the Netorrent library uses GitHub Actions to publish packages to NuGet with both nightly and stable releases. + +## Overview + +The project implements a dual-feed publishing strategy: +- **Nightly builds**: Per-commit packages from `develop` branch +- **Stable releases**: Versioned packages from git tags + +## Required Setup + +### 1. GitHub Secrets + +Add these secrets to your GitHub repository: + +#### `NUGET_API_KEY` +- **Purpose**: Authenticate with NuGet.org for publishing +- **How to get**: + 1. Go to [nuget.org](https://www.nuget.org/) + 2. Sign in with your Microsoft account + 3. Go to Account > API Keys + 4. Create a new API key with `Push` scope + 5. Set the glob pattern to `Netorrent.*` (or leave blank for all packages) + 6. Copy the generated key + +### 2. Environment Protection Rules + +Configure these environments in GitHub repository settings: + +#### `nightly` Environment +- **Purpose**: Protect nightly package publishing +- **Protection rules**: + - No reviewers required (for rapid deployment) + - Wait timer: 0 minutes + +#### `production` Environment +- **Purpose**: Protect stable release publishing +- **Protection rules**: + - Require reviewers (1-2 reviewers recommended) + - Wait timer: 5 minutes (optional) + - Prevent self-approval + +## Workflow Triggers + +### Automatic Triggers + +#### Nightly Builds (Per-Commit) +- **Trigger**: Every push to `develop` branch +- **Version**: `1.0.0-nightly-YYYYMMDD-HHMM-commit` +- **Package Type**: Pre-release +- **Symbol Packages**: No +- **Retention**: 30 days + +#### Release Builds +- **Trigger**: Git tags matching `v*` (e.g., `v1.0.0`, `v1.1.0`) +- **Version**: From git tag +- **Package Type**: Stable +- **Symbol Packages**: Yes +- **GitHub Release**: Created automatically + +### Manual Triggers + +#### Manual Publishing Workflow +- **Trigger**: Workflow dispatch from GitHub Actions tab +- **Options**: + - Custom version specification + - Environment selection (staging/production) + - Optional GitHub release creation + - Custom release notes + +## Version Strategy + +### Nightly Version Format +``` +1.0.0-nightly-20250114-1430-a1b2c3d +│ │ │ │ │ +│ │ │ │ └─ Short commit SHA +│ │ │ └─────── Timestamp (HHMM) +│ │ └───────────── Date (YYYYMMDD) +│ └───────────────────── Base version +└───────────────────────── Nightly identifier +``` + +### Release Version Format +``` +1.2.3 +│ │ │ +│ │ └─ Patch: Bug fixes +│ └─── Minor: New features (backward compatible) +└───── Major: Breaking changes +``` + +## Configuration Files + +### Main Workflow: `.github/workflows/nuget-publish.yml` + +Key features: +- Smart version detection based on trigger type +- Comprehensive testing before publishing +- Conditional symbol package inclusion +- Artifact management with retention policies +- GitHub release automation + +### Manual Workflow: `.github/workflows/manual-publish.yml` + +Key features: +- Version format validation +- Environment-specific publishing +- Optional GitHub release creation +- Release artifact attachment + +### CI Workflow: `.github/workflows/dotnet.yml` + +Updated to avoid conflicts with publishing workflows: +- Limited to main and develop branches +- Added package caching +- Code coverage collection +- Artifact upload for debugging + +## Publishing Process + +### 1. Development Flow + +```bash +# Create feature branch +git checkout -b feature/new-feature +# Make changes... +git commit -m "Add new feature" +git push origin feature/new-feature +# Create PR to develop +# Merge to develop +# → Automatic nightly build triggered +``` + +### 2. Release Flow + +```bash +# Ensure develop is stable +git checkout develop +git pull origin develop + +# Create release branch +git checkout -b release/v1.0.0 + +# Update version in project file if needed +# Commit version changes +git commit -m "Bump version to 1.0.0" +git push origin release/v1.0.0 + +# Merge to main +git checkout main +git merge release/v1.0.0 +git push origin main + +# Create and push tag +git tag v1.0.0 +git push origin v1.0.0 +# → Automatic release build triggered +``` + +### 3. Manual Publishing + +1. Go to GitHub Actions tab +2. Select "Manual NuGet Publish" workflow +3. Click "Run workflow" +4. Fill in parameters: + - Version: `1.0.0`, `1.0.0-preview-123`, or `1.0.0-nightly-custom` + - Environment: `staging` or `production` + - Create Release: Optional for production releases +5. Click "Run workflow" + +## Consumer Usage + +### Stable Installation +```xml + +``` + +### Range Installation +```xml + +``` + +### Nightly Installation +```xml + +``` + +### Pre-release Installation +```bash +dotnet add package Netorrent --prerelease +``` + +## Troubleshooting + +### Common Issues + +#### 1. Package Already Exists +- **Cause**: Version already published +- **Solution**: Use `--skip-duplicate` flag (already configured) + +#### 2. API Key Invalid +- **Cause**: Expired or incorrect API key +- **Solution**: Regenerate API key and update GitHub secrets + +#### 3. Version Format Error +- **Cause**: Invalid semantic version format +- **Solution**: Ensure version follows `X.Y.Z[-suffix]` format + +#### 4. Tests Failed +- **Cause**: Breaking changes introduced +- **Solution**: Fix test failures before publishing + +### Debugging + +#### Workflow Logs +- Check GitHub Actions workflow runs +- Review build and test logs +- Verify package creation steps + +#### Package Verification +```bash +# Download and inspect package +dotnet nuget push --source ./local-feed package.nupkg + +# Verify package contents +dotnet nuget locals --list all +``` + +## Best Practices + +### 1. Version Management +- Follow semantic versioning +- Update version numbers for breaking changes +- Use consistent version formatting + +### 2. Testing +- Ensure all tests pass before publishing +- Maintain code coverage +- Test package installation in clean environment + +### 3. Release Notes +- Provide meaningful release notes +- Document breaking changes +- Include migration guides for major versions + +### 4. Security +- Keep API keys secure +- Use environment protection rules +- Regularly rotate API keys + +### 5. Monitoring +- Monitor package download statistics +- Set up alerts for publishing failures +- Track consumer feedback + +## Advanced Configuration + +### Multiple Package Feeds + +For organizations requiring separate feeds: + +```yaml +# Custom feed configuration +NIGHTLY_FEED: https://api.nuget.org/v3/index.json +STABLE_FEED: https://api.nuget.org/v3/index.json +PRIVATE_FEED: https://pkgs.dev.azure.com/yourorg/_packaging/yourfeed/nuget/v3/index.json +``` + +### Conditional Publishing + +```yaml +# Only publish on specific conditions +- if: github.actor == 'dependabot[bot]' + run: echo "Skipping publishing for dependabot updates" +``` + +### Package Validation + +```yaml +# Additional validation steps +- name: Validate package + run: | + dotnet nuget verify artifacts/*.nupkg + dotnet nuget verify artifacts/*.snupkg +``` + +This setup provides a robust, automated NuGet publishing pipeline that supports both rapid development cycles through nightly builds and stable releases for production use. \ No newline at end of file