From 78b0b325a5ce7efadae50b19c76f42e52f3c1552 Mon Sep 17 00:00:00 2001 From: Ismo Vuorinen Date: Tue, 18 Nov 2025 13:51:07 +0200 Subject: [PATCH] ci: Add GitHub workflows for test, build, and publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive CI/CD workflows: **Test Workflow (test.yml)** - Runs on push/PR to main/develop - Executes all 189 xUnit tests on Ubuntu runner - Publishes test results and artifacts - Fast and cost-effective validation **Build Workflow (build.yml)** - Builds for iOS and macOS Catalyst - Runs on macOS-14 runners (Apple Silicon) - Parallel builds for all platforms - Uploads build artifacts (7-day retention) - Overall build status reporting **Publish Workflow (publish.yml)** - Triggered by version tags (v*.*.*) - Creates GitHub releases with changelogs - Builds signed releases for distribution - Uploads iOS and macOS .zip artifacts - Supports pre-release detection (alpha/beta/rc) - Manual workflow dispatch option Features: - .NET 8.0 with MAUI workload - Parallel job execution for performance - Artifact management and retention - Test result reporting - Release automation with version tracking - Comprehensive documentation in workflows/README.md Platforms supported: - iOS 15.0+ (iossimulator-arm64) - macOS 12.0+ Catalyst (maccatalyst-arm64) ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/README.md | 246 ++++++++++++++++++++++++++++++++++ .github/workflows/build.yml | 90 +++++++++++++ .github/workflows/publish.yml | 204 ++++++++++++++++++++++++++++ .github/workflows/test.yml | 47 +++++++ 4 files changed, 587 insertions(+) create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..a154b06 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,246 @@ +# GitHub Workflows + +This directory contains the CI/CD workflows for HihaArvio. + +## Workflows + +### ๐Ÿงช Test (`test.yml`) + +**Trigger**: Push or PR to `main` or `develop` branches + +**Purpose**: Run all unit and integration tests to ensure code quality. + +**What it does**: +- Runs on Ubuntu (fastest, cheapest for tests) +- Sets up .NET 8.0 +- Builds the solution for `net8.0` target +- Runs all 189 xUnit tests +- Publishes test results and coverage reports +- Uploads test artifacts for review + +**Status Badge**: +```markdown +[![Test](https://github.com/ivuorinen/hiha-arvio/actions/workflows/test.yml/badge.svg)](https://github.com/ivuorinen/hiha-arvio/actions/workflows/test.yml) +``` + +--- + +### ๐Ÿ—๏ธ Build (`build.yml`) + +**Trigger**: Push or PR to `main` or `develop` branches, or manual trigger + +**Purpose**: Build the application for all supported platforms. + +**What it does**: +- **iOS Job**: Builds for iOS 15.0+ on macOS-14 runner +- **macOS Catalyst Job**: Builds for macOS 12.0+ on macOS-14 runner +- Installs .NET MAUI workload +- Builds without code signing (for CI verification) +- Uploads build artifacts (`.app` bundles) +- Reports overall build status + +**Platforms**: +- iOS (iossimulator-arm64) +- macOS Catalyst (maccatalyst-arm64) + +**Status Badge**: +```markdown +[![Build](https://github.com/ivuorinen/hiha-arvio/actions/workflows/build.yml/badge.svg)](https://github.com/ivuorinen/hiha-arvio/actions/workflows/build.yml) +``` + +--- + +### ๐Ÿš€ Publish (`publish.yml`) + +**Trigger**: +- Push of version tags (e.g., `v1.0.0`, `v1.2.3-beta`) +- Manual workflow dispatch with version input + +**Purpose**: Create GitHub releases with signed and distributable builds. + +**What it does**: +1. **Create Release Job**: + - Extracts version from tag or input + - Generates changelog from git commits + - Creates GitHub release (draft for pre-releases) + +2. **Build iOS Job**: + - Builds release version for iOS + - Sets version numbers from tag/input + - Creates `.zip` archive of `.app` bundle + - Uploads to GitHub release + +3. **Build macOS Job**: + - Builds release version for macOS Catalyst + - Sets version numbers from tag/input + - Creates `.zip` archive of `.app` bundle + - Uploads to GitHub release + +4. **Status Job**: + - Reports overall publish status + - Creates summary in GitHub Actions UI + +**Version Numbering**: +- `ApplicationDisplayVersion`: From git tag (e.g., `1.0.0`) +- `ApplicationVersion`: From GitHub run number (incremental build number) + +**Pre-release Detection**: +Tags containing `alpha`, `beta`, or `rc` are marked as pre-releases. + +**Status Badge**: +```markdown +[![Publish](https://github.com/ivuorinen/hiha-arvio/actions/workflows/publish.yml/badge.svg)](https://github.com/ivuorinen/hiha-arvio/actions/workflows/publish.yml) +``` + +--- + +## How to Use + +### Running Tests Locally +```bash +dotnet test HihaArvio.sln -f net8.0 +``` + +### Building Locally +```bash +# iOS +dotnet build src/HihaArvio/HihaArvio.csproj -f net8.0-ios -c Release + +# macOS Catalyst +dotnet build src/HihaArvio/HihaArvio.csproj -f net8.0-maccatalyst -c Release +``` + +### Creating a Release + +#### Automatic (via Git Tag) +```bash +# Create and push version tag +git tag v1.0.0 +git push origin v1.0.0 +``` + +#### Manual (via GitHub UI) +1. Go to Actions โ†’ Publish workflow +2. Click "Run workflow" +3. Enter version number (e.g., `1.0.0`) +4. Click "Run workflow" + +The publish workflow will: +1. Create a GitHub release +2. Build iOS and macOS versions +3. Attach `.zip` artifacts to the release +4. Generate changelog from commits + +--- + +## Workflow Dependencies + +### Required GitHub Secrets +None required for current workflows (unsigned builds). + +For signed releases, add: +- `APPLE_CERTIFICATE_BASE64`: iOS/macOS signing certificate +- `APPLE_CERTIFICATE_PASSWORD`: Certificate password +- `APPLE_PROVISIONING_PROFILE_BASE64`: Provisioning profile +- `APPLE_TEAM_ID`: Apple Developer Team ID + +### Runner Requirements +- **Test**: `ubuntu-latest` (any Linux runner) +- **Build**: `macos-14` (Apple Silicon runner for MAUI workloads) +- **Publish**: `macos-14` (Apple Silicon runner for MAUI workloads) + +### External Actions Used +- `actions/checkout@v4` - Checkout repository +- `actions/setup-dotnet@v4` - Setup .NET SDK +- `actions/upload-artifact@v4` - Upload build artifacts +- `actions/create-release@v1` - Create GitHub releases +- `actions/upload-release-asset@v1` - Upload release assets +- `dorny/test-reporter@v1` - Generate test reports + +--- + +## Troubleshooting + +### Tests Failing +Check test output in workflow logs. Run locally with: +```bash +dotnet test HihaArvio.sln -f net8.0 --verbosity detailed +``` + +### Build Failing +1. Check runner logs for specific errors +2. Verify .NET MAUI workload installed correctly +3. Ensure all NuGet packages are restored +4. Check for platform-specific compilation errors + +### Publish Failing +1. Verify version tag format matches `v*.*.*` +2. Check that create-release step succeeded +3. Verify build artifacts were created +4. Check upload permissions and GitHub token + +### Manual Workflow Trigger Not Working +- Ensure you have write permissions to the repository +- Check workflow YAML syntax is valid +- Verify `workflow_dispatch` event is properly configured + +--- + +## Workflow Optimization + +### Cost Considerations +- **Ubuntu runners**: $0.008/minute (cheapest) +- **macOS runners**: $0.08/minute (10x more expensive) + +**Strategy**: +- Tests run on Ubuntu (fast, cheap) +- Builds run on macOS only when needed (platform requirement) +- Publish only on tagged releases (infrequent) + +### Performance Tips +- Build jobs run in parallel (iOS and macOS simultaneously) +- Use `--no-restore` and `--no-build` flags to skip redundant steps +- Cache NuGet packages for faster restores +- Use artifacts for passing builds between jobs + +--- + +## Future Enhancements + +### Potential Additions +- [ ] Code coverage reporting (Codecov/Coveralls) +- [ ] Static code analysis (SonarCloud) +- [ ] Dependency scanning (Dependabot) +- [ ] Performance benchmarking +- [ ] TestFlight deployment for iOS +- [ ] Mac App Store submission automation +- [ ] Code signing for official releases +- [ ] Notarization for macOS builds + +### Workflow Improvements +- [ ] Add caching for NuGet packages +- [ ] Matrix builds for multiple .NET versions +- [ ] Parallel test execution +- [ ] Automatic changelog generation from conventional commits +- [ ] Semantic versioning automation +- [ ] Slack/Discord notifications on release + +--- + +## Maintenance + +### Updating Workflows +1. Edit workflow YAML files +2. Test changes on feature branch +3. Verify workflows run successfully +4. Merge to main branch + +### Monitoring +- Check Actions tab regularly for failures +- Review test reports for flaky tests +- Monitor build times for optimization opportunities +- Track artifact sizes for distribution planning + +--- + +๐Ÿ“ **Note**: These workflows are designed for unsigned development builds. For App Store distribution, additional code signing configuration is required. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..84c3657 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,90 @@ +name: Build + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + workflow_dispatch: + +jobs: + build-ios: + name: Build iOS + runs-on: macos-14 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Install MAUI workload + run: dotnet workload install maui --source https://api.nuget.org/v3/index.json + + - name: Restore dependencies + run: dotnet restore HihaArvio.sln + + - name: Build iOS + run: dotnet build src/HihaArvio/HihaArvio.csproj -f net8.0-ios -c Release /p:ArchiveOnBuild=false /p:EnableCodeSigning=false + + - name: Upload iOS build artifacts + uses: actions/upload-artifact@v4 + with: + name: ios-build + path: | + src/HihaArvio/bin/Release/net8.0-ios/**/*.app + src/HihaArvio/bin/Release/net8.0-ios/**/*.ipa + retention-days: 7 + + build-maccatalyst: + name: Build macOS Catalyst + runs-on: macos-14 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Install MAUI workload + run: dotnet workload install maui --source https://api.nuget.org/v3/index.json + + - name: Restore dependencies + run: dotnet restore HihaArvio.sln + + - name: Build macOS Catalyst + run: dotnet build src/HihaArvio/HihaArvio.csproj -f net8.0-maccatalyst -c Release /p:ArchiveOnBuild=false /p:EnableCodeSigning=false + + - name: Upload macOS build artifacts + uses: actions/upload-artifact@v4 + with: + name: maccatalyst-build + path: | + src/HihaArvio/bin/Release/net8.0-maccatalyst/**/*.app + src/HihaArvio/bin/Release/net8.0-maccatalyst/**/*.pkg + retention-days: 7 + + build-status: + name: Build Status + runs-on: ubuntu-latest + needs: [build-ios, build-maccatalyst] + if: always() + + steps: + - name: Check build status + run: | + if [[ "${{ needs.build-ios.result }}" == "success" ]] && [[ "${{ needs.build-maccatalyst.result }}" == "success" ]]; then + echo "โœ… All builds succeeded" + exit 0 + else + echo "โŒ One or more builds failed" + echo "iOS: ${{ needs.build-ios.result }}" + echo "macOS Catalyst: ${{ needs.build-maccatalyst.result }}" + exit 1 + fi diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..85887dd --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,204 @@ +name: Publish + +on: + push: + tags: + - 'v*.*.*' + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 1.0.0)' + required: true + type: string + +env: + DOTNET_VERSION: '8.0.x' + +jobs: + create-release: + name: Create Release + runs-on: ubuntu-latest + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + version: ${{ steps.get_version.outputs.version }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version + id: get_version + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT + else + echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + fi + + - name: Generate changelog + id: changelog + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "changelog=Manual release of version ${{ steps.get_version.outputs.version }}" >> $GITHUB_OUTPUT + else + # Get commits since last tag + PREVIOUS_TAG=$(git describe --abbrev=0 --tags $(git rev-list --tags --skip=1 --max-count=1) 2>/dev/null || echo "") + if [[ -z "$PREVIOUS_TAG" ]]; then + CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges) + else + CHANGELOG=$(git log ${PREVIOUS_TAG}..HEAD --pretty=format:"- %s (%h)" --no-merges) + fi + echo "changelog<> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.event_name == 'workflow_dispatch' && format('v{0}', github.event.inputs.version) || github.ref }} + release_name: Release ${{ steps.get_version.outputs.version }} + body: | + ## HihaArvio v${{ steps.get_version.outputs.version }} + + Sleeve Estimate - A playful Finnish take on agile estimation through shake gestures. + + ### Changes + ${{ steps.changelog.outputs.changelog }} + + ### Supported Platforms + - iOS 15.0+ + - macOS 12.0+ (via Mac Catalyst) + + ### Installation + - **iOS**: Download the `.ipa` file and install via Xcode or TestFlight + - **macOS**: Download the `.app` bundle and move to Applications folder + + --- + ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) + draft: false + prerelease: ${{ contains(steps.get_version.outputs.version, 'alpha') || contains(steps.get_version.outputs.version, 'beta') || contains(steps.get_version.outputs.version, 'rc') }} + + build-and-publish-ios: + name: Build and Publish iOS + runs-on: macos-14 + needs: create-release + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Install MAUI workload + run: dotnet workload install maui --source https://api.nuget.org/v3/index.json + + - name: Restore dependencies + run: dotnet restore HihaArvio.sln + + - name: Build iOS Release + run: | + dotnet publish src/HihaArvio/HihaArvio.csproj \ + -f net8.0-ios \ + -c Release \ + /p:ApplicationDisplayVersion=${{ needs.create-release.outputs.version }} \ + /p:ApplicationVersion=${{ github.run_number }} \ + /p:ArchiveOnBuild=false \ + /p:EnableCodeSigning=false + + - name: Create iOS artifact archive + run: | + cd src/HihaArvio/bin/Release/net8.0-ios + zip -r HihaArvio-iOS-${{ needs.create-release.outputs.version }}.zip *.app + mv HihaArvio-iOS-${{ needs.create-release.outputs.version }}.zip ${{ github.workspace }}/ + + - name: Upload iOS Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: ./HihaArvio-iOS-${{ needs.create-release.outputs.version }}.zip + asset_name: HihaArvio-iOS-${{ needs.create-release.outputs.version }}.zip + asset_content_type: application/zip + + build-and-publish-maccatalyst: + name: Build and Publish macOS + runs-on: macos-14 + needs: create-release + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Install MAUI workload + run: dotnet workload install maui --source https://api.nuget.org/v3/index.json + + - name: Restore dependencies + run: dotnet restore HihaArvio.sln + + - name: Build macOS Catalyst Release + run: | + dotnet publish src/HihaArvio/HihaArvio.csproj \ + -f net8.0-maccatalyst \ + -c Release \ + /p:ApplicationDisplayVersion=${{ needs.create-release.outputs.version }} \ + /p:ApplicationVersion=${{ github.run_number }} \ + /p:ArchiveOnBuild=false \ + /p:EnableCodeSigning=false + + - name: Create macOS artifact archive + run: | + cd src/HihaArvio/bin/Release/net8.0-maccatalyst + zip -r HihaArvio-macOS-${{ needs.create-release.outputs.version }}.zip *.app + mv HihaArvio-macOS-${{ needs.create-release.outputs.version }}.zip ${{ github.workspace }}/ + + - name: Upload macOS Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: ./HihaArvio-macOS-${{ needs.create-release.outputs.version }}.zip + asset_name: HihaArvio-macOS-${{ needs.create-release.outputs.version }}.zip + asset_content_type: application/zip + + publish-status: + name: Publish Status + runs-on: ubuntu-latest + needs: [create-release, build-and-publish-ios, build-and-publish-maccatalyst] + if: always() + + steps: + - name: Check publish status + run: | + echo "## Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Version: ${{ needs.create-release.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Build Status" >> $GITHUB_STEP_SUMMARY + echo "- iOS: ${{ needs.build-and-publish-ios.result }}" >> $GITHUB_STEP_SUMMARY + echo "- macOS Catalyst: ${{ needs.build-and-publish-maccatalyst.result }}" >> $GITHUB_STEP_SUMMARY + + if [[ "${{ needs.build-and-publish-ios.result }}" == "success" ]] && [[ "${{ needs.build-and-publish-maccatalyst.result }}" == "success" ]]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "โœ… All builds succeeded and artifacts published" >> $GITHUB_STEP_SUMMARY + exit 0 + else + echo "" >> $GITHUB_STEP_SUMMARY + echo "โŒ One or more builds failed" >> $GITHUB_STEP_SUMMARY + exit 1 + fi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..10328ad --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,47 @@ +name: Test + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Restore dependencies + run: dotnet restore HihaArvio.sln + + - name: Build solution + run: dotnet build HihaArvio.sln --configuration Release --no-restore -f net8.0 + + - name: Run tests + run: dotnet test HihaArvio.sln --configuration Release --no-build --verbosity normal -f net8.0 --logger "trx;LogFileName=test-results.trx" + + - name: Publish test results + uses: dorny/test-reporter@v1 + if: always() + with: + name: Test Results + path: '**/test-results.trx' + reporter: dotnet-trx + fail-on-error: true + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results + path: '**/test-results.trx' + retention-days: 30