chore: clean slate for fresh releases #52
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: PR Checks | |
| on: | |
| pull_request: | |
| branches: [main] | |
| types: [opened, synchronize, reopened] | |
| push: | |
| branches: [main] | |
| # Cancel in-progress runs for the same PR | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| XCODE_VERSION: '26.0' | |
| APP_NAME: MyMacCleaner | |
| SCHEME: MyMacCleaner | |
| DESTINATION: 'platform=macOS' | |
| jobs: | |
| # ============================================ | |
| # Build Validation | |
| # ============================================ | |
| build: | |
| name: Build | |
| runs-on: macos-26 | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Select Xcode | |
| run: | | |
| sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app || \ | |
| sudo xcode-select -s /Applications/Xcode.app | |
| xcodebuild -version | |
| - name: Cache Swift Package Dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/Library/Developer/Xcode/DerivedData/**/SourcePackages | |
| .build | |
| key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved', '**/*.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} | |
| restore-keys: | | |
| ${{ runner.os }}-spm- | |
| - name: Resolve Dependencies | |
| run: | | |
| xcodebuild -resolvePackageDependencies \ | |
| -project ${{ env.APP_NAME }}.xcodeproj \ | |
| -scheme ${{ env.SCHEME }} | |
| - name: Build (Debug) | |
| run: | | |
| xcodebuild build \ | |
| -project ${{ env.APP_NAME }}.xcodeproj \ | |
| -scheme ${{ env.SCHEME }} \ | |
| -destination "${{ env.DESTINATION }}" \ | |
| -configuration Debug \ | |
| CODE_SIGN_IDENTITY="-" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| 2>&1 | tee build.log | |
| # Check for warnings (optional - remove if too strict) | |
| # if grep -q "warning:" build.log; then | |
| # echo "::warning::Build produced warnings" | |
| # fi | |
| - name: Build (Release) | |
| run: | | |
| xcodebuild build \ | |
| -project ${{ env.APP_NAME }}.xcodeproj \ | |
| -scheme ${{ env.SCHEME }} \ | |
| -destination "${{ env.DESTINATION }}" \ | |
| -configuration Release \ | |
| CODE_SIGN_IDENTITY="-" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO | |
| # ============================================ | |
| # Unit Tests | |
| # ============================================ | |
| test: | |
| name: Unit Tests | |
| runs-on: macos-26 | |
| timeout-minutes: 20 | |
| needs: build | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Select Xcode | |
| run: | | |
| sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app || \ | |
| sudo xcode-select -s /Applications/Xcode.app | |
| - name: Cache Swift Package Dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/Library/Developer/Xcode/DerivedData/**/SourcePackages | |
| .build | |
| key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved', '**/*.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} | |
| restore-keys: | | |
| ${{ runner.os }}-spm- | |
| - name: Run Unit Tests | |
| run: | | |
| xcodebuild test \ | |
| -project ${{ env.APP_NAME }}.xcodeproj \ | |
| -scheme ${{ env.SCHEME }} \ | |
| -destination "${{ env.DESTINATION }}" \ | |
| -testPlan "MyMacCleanerTests" \ | |
| -enableCodeCoverage YES \ | |
| CODE_SIGN_IDENTITY="-" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| 2>&1 | xcpretty --report junit --output test-results.xml || true | |
| - name: Upload Test Results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: test-results | |
| path: test-results.xml | |
| # ============================================ | |
| # UI Tests (optional, runs on macOS host) | |
| # ============================================ | |
| ui-test: | |
| name: UI Tests | |
| runs-on: macos-26 | |
| timeout-minutes: 30 | |
| needs: build | |
| # UI tests may be flaky in CI - make optional | |
| continue-on-error: true | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Select Xcode | |
| run: | | |
| sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app || \ | |
| sudo xcode-select -s /Applications/Xcode.app | |
| - name: Cache Swift Package Dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/Library/Developer/Xcode/DerivedData/**/SourcePackages | |
| .build | |
| key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved', '**/*.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} | |
| restore-keys: | | |
| ${{ runner.os }}-spm- | |
| - name: Run UI Tests | |
| run: | | |
| xcodebuild test \ | |
| -project ${{ env.APP_NAME }}.xcodeproj \ | |
| -scheme ${{ env.SCHEME }} \ | |
| -destination "${{ env.DESTINATION }}" \ | |
| -testPlan "MyMacCleanerUITests" \ | |
| CODE_SIGN_IDENTITY="-" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| 2>&1 | xcpretty || true | |
| # ============================================ | |
| # Archive Test (ensures release builds work) | |
| # ============================================ | |
| archive: | |
| name: Archive Test | |
| runs-on: macos-26 | |
| timeout-minutes: 20 | |
| needs: [build, test] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Select Xcode | |
| run: | | |
| sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app || \ | |
| sudo xcode-select -s /Applications/Xcode.app | |
| - name: Cache Swift Package Dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/Library/Developer/Xcode/DerivedData/**/SourcePackages | |
| .build | |
| key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved', '**/*.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} | |
| restore-keys: | | |
| ${{ runner.os }}-spm- | |
| - name: Archive Build | |
| run: | | |
| xcodebuild archive \ | |
| -project ${{ env.APP_NAME }}.xcodeproj \ | |
| -scheme ${{ env.SCHEME }} \ | |
| -archivePath build/${{ env.APP_NAME }}.xcarchive \ | |
| -configuration Release \ | |
| CODE_SIGN_IDENTITY="-" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| ONLY_ACTIVE_ARCH=NO | |
| # Verify archive was created | |
| if [ ! -d "build/${{ env.APP_NAME }}.xcarchive" ]; then | |
| echo "::error::Archive was not created" | |
| exit 1 | |
| fi | |
| # Verify app exists in archive | |
| if [ ! -d "build/${{ env.APP_NAME }}.xcarchive/Products/Applications/${{ env.APP_NAME }}.app" ]; then | |
| echo "::error::App not found in archive" | |
| exit 1 | |
| fi | |
| echo "Archive created successfully" | |
| ls -la "build/${{ env.APP_NAME }}.xcarchive/Products/Applications/" | |
| # ============================================ | |
| # SwiftLint (code style) | |
| # ============================================ | |
| lint: | |
| name: SwiftLint | |
| runs-on: macos-26 | |
| timeout-minutes: 5 | |
| # Don't block merge on lint issues - just warn | |
| continue-on-error: true | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install SwiftLint | |
| run: | | |
| brew install swiftlint || true | |
| - name: Run SwiftLint | |
| run: | | |
| if command -v swiftlint &> /dev/null; then | |
| swiftlint lint --reporter github-actions-logging || true | |
| else | |
| echo "SwiftLint not available, skipping" | |
| fi | |
| # ============================================ | |
| # Code Quality Checks (based on CODE_REVIEW_GUIDELINES.md) | |
| # ============================================ | |
| code-quality: | |
| name: Code Quality | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Check for print() statements | |
| run: | | |
| echo "Checking for print() statements in production code..." | |
| # Exclude test files and comments | |
| PRINTS=$(grep -rn "print(" ${{ env.APP_NAME }}/ --include="*.swift" | grep -v "// " | grep -v "Test" | grep -v "#Preview" || true) | |
| if [ -n "$PRINTS" ]; then | |
| echo "::warning::Found print() statements in production code:" | |
| echo "$PRINTS" | |
| echo "" | |
| echo "Consider removing print() statements before merging." | |
| else | |
| echo "✅ No print() statements found" | |
| fi | |
| - name: Check for force unwraps | |
| run: | | |
| echo "Checking for force unwraps (!)..." | |
| # Look for force unwraps, excluding comments and common safe patterns | |
| FORCE_UNWRAPS=$(grep -rn '\![^=]' ${{ env.APP_NAME }}/ --include="*.swift" | grep -v "//" | grep -v "!=" | grep -v "!important" | grep -v "@IBOutlet" | grep -v "canImport" || true) | |
| COUNT=$(echo "$FORCE_UNWRAPS" | grep -c "!" || echo "0") | |
| if [ "$COUNT" -gt "0" ] && [ -n "$FORCE_UNWRAPS" ]; then | |
| echo "::warning::Found potential force unwraps:" | |
| echo "$FORCE_UNWRAPS" | head -20 | |
| echo "" | |
| echo "Review these for safety. Force unwraps should have justification." | |
| else | |
| echo "✅ No obvious force unwraps found" | |
| fi | |
| - name: Check macOS 26 availability patterns | |
| run: | | |
| echo "Checking macOS 26 compatibility patterns..." | |
| # Check for #unavailable (should use #available instead) | |
| UNAVAILABLE=$(grep -rn "#unavailable" ${{ env.APP_NAME }}/ --include="*.swift" || true) | |
| if [ -n "$UNAVAILABLE" ]; then | |
| echo "::error::Found #unavailable patterns (use #available with else instead):" | |
| echo "$UNAVAILABLE" | |
| exit 1 | |
| fi | |
| echo "✅ No #unavailable patterns found" | |
| # Check that glassEffect usage has availability checks | |
| GLASS_NO_CHECK=$(grep -rn "\.glassEffect" ${{ env.APP_NAME }}/ --include="*.swift" | grep -v "#available" | grep -v "//" || true) | |
| if [ -n "$GLASS_NO_CHECK" ]; then | |
| echo "::warning::Found .glassEffect without #available check:" | |
| echo "$GLASS_NO_CHECK" | |
| echo "" | |
| echo "Ensure all glassEffect calls are wrapped in #available(macOS 26, *)" | |
| else | |
| echo "✅ All glassEffect calls appear to have availability checks" | |
| fi | |
| # Verify macOS 26 patterns exist (sanity check) | |
| MACOS26_COUNT=$(grep -rn "#available(macOS 26" ${{ env.APP_NAME }}/ --include="*.swift" | wc -l || echo "0") | |
| echo "Found $MACOS26_COUNT macOS 26 availability checks" | |
| - name: Check for large files | |
| run: | | |
| echo "Checking for oversized files..." | |
| # Flag Swift files over 500 lines (per guidelines) | |
| LARGE_FILES=$(find ${{ env.APP_NAME }} -name "*.swift" -exec wc -l {} + | awk '$1 > 500 {print}' | grep -v "total" || true) | |
| if [ -n "$LARGE_FILES" ]; then | |
| echo "::warning::Found files exceeding 500 lines:" | |
| echo "$LARGE_FILES" | |
| echo "" | |
| echo "Consider splitting these files per CODE_REVIEW_GUIDELINES.md" | |
| else | |
| echo "✅ All files within size guidelines" | |
| fi | |
| - name: Check for TODO/FIXME comments | |
| run: | | |
| echo "Checking for TODO/FIXME comments..." | |
| TODOS=$(grep -rn "TODO\|FIXME\|HACK\|XXX" ${{ env.APP_NAME }}/ --include="*.swift" | grep -v "// TODO: (template)" || true) | |
| COUNT=$(echo "$TODOS" | grep -c "TODO\|FIXME\|HACK\|XXX" || echo "0") | |
| if [ "$COUNT" -gt "0" ] && [ -n "$TODOS" ]; then | |
| echo "::warning::Found $COUNT TODO/FIXME comments:" | |
| echo "$TODOS" | |
| echo "" | |
| echo "Ensure these are tracked issues, not forgotten work." | |
| else | |
| echo "✅ No TODO/FIXME comments found" | |
| fi | |
| - name: Check Theme usage | |
| run: | | |
| echo "Checking for hardcoded values that should use Theme..." | |
| # Check for hardcoded fonts | |
| HARDCODED_FONTS=$(grep -rn "\.font(\.system(size:" ${{ env.APP_NAME }}/ --include="*.swift" | grep -v "Theme" || true) | |
| if [ -n "$HARDCODED_FONTS" ]; then | |
| echo "::warning::Found hardcoded font sizes (should use Theme.Typography):" | |
| echo "$HARDCODED_FONTS" | head -10 | |
| fi | |
| # Check for hardcoded cornerRadius with numbers | |
| HARDCODED_RADIUS=$(grep -rn "cornerRadius: [0-9]" ${{ env.APP_NAME }}/ --include="*.swift" | grep -v "Theme" || true) | |
| if [ -n "$HARDCODED_RADIUS" ]; then | |
| echo "::warning::Found hardcoded cornerRadius (should use Theme.CornerRadius):" | |
| echo "$HARDCODED_RADIUS" | head -10 | |
| fi | |
| echo "✅ Theme usage check complete" | |
| # ============================================ | |
| # Documentation Check | |
| # ============================================ | |
| docs: | |
| name: Documentation | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Check CLAUDE.md exists | |
| run: | | |
| if [ ! -f "CLAUDE.md" ]; then | |
| echo "::error::CLAUDE.md is missing" | |
| exit 1 | |
| fi | |
| - name: Check README.md exists | |
| run: | | |
| if [ ! -f "README.md" ]; then | |
| echo "::error::README.md is missing" | |
| exit 1 | |
| fi | |
| - name: Check docs folder | |
| run: | | |
| if [ ! -d "docs" ]; then | |
| echo "::warning::docs folder is missing" | |
| fi | |
| # ============================================ | |
| # Appcast Validation (for Sparkle updates) | |
| # ============================================ | |
| validate-appcast: | |
| name: Validate Appcast | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install xmllint | |
| run: sudo apt-get update && sudo apt-get install -y libxml2-utils | |
| - name: Validate appcast.xml | |
| run: | | |
| if [ -f "appcast.xml" ]; then | |
| # Check if it's valid XML | |
| if ! xmllint --noout appcast.xml; then | |
| echo "::error::appcast.xml is not valid XML" | |
| exit 1 | |
| fi | |
| echo "✅ appcast.xml is valid XML" | |
| else | |
| echo "No appcast.xml found (OK for PRs)" | |
| fi | |
| # ============================================ | |
| # Summary Job (required for branch protection) | |
| # ============================================ | |
| pr-check-complete: | |
| name: PR Checks Complete | |
| runs-on: ubuntu-latest | |
| needs: [build, test, archive, docs, code-quality, validate-appcast] | |
| if: always() | |
| steps: | |
| - name: Check Results | |
| run: | | |
| if [ "${{ needs.build.result }}" != "success" ]; then | |
| echo "::error::Build failed" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.test.result }}" != "success" ]; then | |
| echo "::error::Tests failed" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.archive.result }}" != "success" ]; then | |
| echo "::error::Archive test failed" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.docs.result }}" != "success" ]; then | |
| echo "::error::Documentation check failed" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.code-quality.result }}" != "success" ]; then | |
| echo "::error::Code quality checks failed" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.validate-appcast.result }}" != "success" ]; then | |
| echo "::error::Appcast validation failed" | |
| exit 1 | |
| fi | |
| echo "All required checks passed!" |