Release

name: Release

on:
  workflow_dispatch:
    inputs:
      release_notes:
        description: Release notes (use \n for newlines)
        type: string
        required: false
      bump:
        description: 'Bump type'
        required: false
        default: 'next'
        type: choice
        options:
          - 'next'
          - 'major'
          - 'minor'
          - 'patch'
          - 'current'
      github_release:
        description: 'Create Github Release'
        default: true
        type: boolean
      conveyor-copied-site:
        description: 'Conveyor copied site'
        default: true
        type: boolean
      docker_release:
        description: 'Push Docker images'
        default: true
        type: boolean
      msstore_release:
        description: 'Release to the MS Store'
        default: true
        type: boolean

jobs:
  version:
    runs-on: macos-latest
    outputs:
      version_current: ${{ steps.versions.outputs.version_current }}
      version_next: ${{ steps.versions.outputs.version_next }}
      should_release: ${{ steps.versions.outputs.should_release }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Set up Homebrew
        id: set-up-homebrew
        uses: Homebrew/actions/setup-homebrew@master
      - name: Install svu
        run: brew install caarlos0/tap/svu
      - name: Compute next version for release
        run: |
          echo "VERSION_NEXT=`svu --pattern="[0-9]*" --strip-prefix ${{ inputs.bump }}`" | tee -a $GITHUB_ENV
          echo "VERSION_NEXT_SUFFIX=`svu --pattern="[0-9]*" --strip-prefix ${{ inputs.bump }}`" | tee -a $GITHUB_ENV
      - name: Set Versions
        id: versions
        run: |
          echo "version_current=`svu --pattern="[0-9]*" --strip-prefix current`" >> $GITHUB_OUTPUT
          echo "version_next=${{ env.VERSION_NEXT_SUFFIX }}" >> $GITHUB_OUTPUT
          [[ `svu --pattern="[0-9]*" --strip-prefix current` != ${{ env.VERSION_NEXT }} ]] && echo "should_release=true" >> $GITHUB_OUTPUT || echo

  release:
    name: Release
    runs-on: ubuntu-latest
    needs: version
    steps:
      - name: Remove unnecessary files
        run: |
          sudo rm -rf /usr/share/dotnet
          sudo rm -rf "$AGENT_TOOLSDIRECTORY"

      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Display versions
        run: |
          echo "Current version: ${{ needs.version.outputs.version_current }}, should release: ${{ needs.version.outputs.version_next }}"

      - name: Update version in gradle.properties
        if: needs.version.outputs.should_release #only redo if the version changed
        run: sed -i -e "s/version=.*/version=${{ needs.version.outputs.version_next }}/" gradle.properties

      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: 'npm'
          cache-dependency-path: komga-webui/package-lock.json

      - uses: actions/setup-java@v4
        with:
          java-version: 21
          java-package: 'jdk'
          distribution: 'temurin'

      - uses: actions/setup-java@v4
        with:
          java-version: 17
          java-package: 'jdk'
          distribution: 'temurin'

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - uses: gradle/actions/setup-gradle@v3

      - name: Build
        run: ./gradlew :komga:prepareThymeLeaf :komga:bootJar :komga-tray:jar
        env:
          NODE_OPTIONS: "--max-old-space-size=4096"

      - name: Generate OpenAPI docs
        if: needs.version.outputs.should_release #only redo if the version changed
        run: ./gradlew :komga:generateOpenApiDocs

      - name: Create release notes
        run: |
          mkdir release_notes
          echo -e "${{ inputs.release_notes }}" >> release_notes/release_notes.md
          echo "Release notes:"
          cat release_notes/release_notes.md
          echo ""

      - name: JReleaser Changelog append
        if: needs.version.outputs.should_release #only redo if the version changed
        run: ./gradlew jreleaserChangelog
        env:
          JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: JReleaser Changelog output
        if: always() &&  needs.version.outputs.should_release
        uses: actions/upload-artifact@v4
        with:
          name: jreleaser-changelog
          path: |
            build/jreleaser/trace.log
            build/jreleaser/output.properties

      - name: Release commit and push
        uses: EndBug/add-and-commit@v9
        if: needs.version.outputs.should_release #only redo if the version changed
        with:
          message: 'chore(release): ${{ needs.version.outputs.version_next }} [skip ci]'
          tag: '${{ needs.version.outputs.version_next }}'
          default_author: github_actions

      - name: Retrieve the Apple private key and decode it to a file
        if: inputs.github_release || inputs.conveyor-copied-site
        env:
          APPLE_PRIVATE_KEY: ${{ secrets.APPLE_PRIVATE_KEY }}
        run: |
          mkdir ./secret
          echo $APPLE_PRIVATE_KEY | base64 --decode > ./secret/apple_private_key.p8

      - name: Conveyor make copied-site
        uses: hydraulic-software/conveyor/actions/build@v14.3
        if: inputs.conveyor-copied-site
        with:
          command: --cache-limit=2.0 -f conveyor.ci.conf make copied-site -o ./output/site
          signing_key: ${{ secrets.CONVEYOR_SIGNING_KEY }}
          agree_to_license: 1
        env:
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          AWS_S3_BUCKET: ${{ secrets.B2_BUCKET }}
          AWS_S3_ENDPOINT: 'https://s3.us-east-005.backblazeb2.com'
          AWS_ACCESS_KEY_ID: ${{ secrets.B2_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.B2_SECRET_ACCESS_KEY }}
      - name: Upload Conveyor log
        if: always() && inputs.conveyor-copied-site
        uses: actions/upload-artifact@v4
        with:
          name: conveyor-make-copied-site
          path: ~/.cache/hydraulic/conveyor/logs/log.latest.txt

      - name: JReleaser Release
        if: inputs.github_release
        run: ./gradlew jreleaserRelease
        env:
          JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: JReleaser Release output
        if: always() && inputs.github_release
        uses: actions/upload-artifact@v4
        with:
          name: jreleaser-release
          path: |
            build/jreleaser/trace.log
            build/jreleaser/output.properties

      - name: JReleaser Announce
        if: inputs.github_release
        run: ./gradlew jreleaserAnnounce
        env:
          JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: JReleaser Announce output
        if: always() && inputs.github_release
        uses: actions/upload-artifact@v4
        with:
          name: jreleaser-announce
          path: |
            build/jreleaser/trace.log
            build/jreleaser/output.properties

      # Sometimes the workflow will fail because it's out of disk space
      - name: Cleanup Conveyor output
        run: rm -fr ./output

      - name: JReleaser Publish
        if: inputs.docker_release
        run: ./gradlew jreleaserPublish
        env:
          JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: JReleaser Publish output
        if: always() && inputs.docker_release
        uses: actions/upload-artifact@v4
        with:
          name: jreleaser-publish
          path: |
            build/jreleaser/trace.log
            build/jreleaser/output.properties

      - name: Conveyor - publish to Microsoft Store
        uses: hydraulic-software/conveyor/actions/build@v14.3
        if: inputs.msstore_release
        with:
          command: --cache-limit=2.0 -f conveyor.msstore.ci.conf make ms-store-release -o ./output/msstore
          signing_key: ${{ secrets.CONVEYOR_SIGNING_KEY }}
          agree_to_license: 1
        env:
          CONVEYOR_MSSTORE_CLIENT_ID: ${{ secrets.CONVEYOR_MSSTORE_CLIENT_ID }}
          CONVEYOR_MSSTORE_CLIENT_SECRET: ${{ secrets.CONVEYOR_MSSTORE_CLIENT_SECRET }}
          CONVEYOR_MSSTORE_TENANT_ID: ${{ secrets.CONVEYOR_MSSTORE_TENANT_ID }}
          AWS_S3_BUCKET: ${{ secrets.B2_BUCKET }}
          AWS_S3_ENDPOINT: 'https://s3.us-east-005.backblazeb2.com'
          AWS_ACCESS_KEY_ID: ${{ secrets.B2_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.B2_SECRET_ACCESS_KEY }}
      - name: Upload Conveyor log
        if: always() && inputs.msstore_release
        uses: actions/upload-artifact@v4
        with:
          name: conveyor-ms-store-release
          path: ~/.cache/hydraulic/conveyor/logs/log.latest.txt


Tests

name: Tests

on:
  pull_request:
    paths-ignore:
      - 'komga-webui/src/locales/*'
      - 'komga-tray/src/main/resources/org/gotson/komga/*'
  push:
    branches-ignore:
      - 'dependabot/**'
  workflow_dispatch:

jobs:
  test:
    runs-on: ubuntu-latest
    name: Test server
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: 21
          java-package: 'jdk'
          distribution: 'temurin'
      - uses: actions/setup-java@v4
        with:
          java-version: 17
          java-package: 'jdk'
          distribution: 'temurin'

      - uses: gradle/actions/setup-gradle@v3

      - name: Build
        run: ./gradlew build :komga-tray:jar

      - name: Upload Unit Test Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: komga/build/test-results/

      - name: Upload Unit Test Reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-reports
          path: komga/build/reports/tests/

      - name: Conveyor - compute JDK module list
        if: github.event_name == 'push'
        uses: hydraulic-software/conveyor/actions/build@v14.3
        with:
          command: -f conveyor.detect.conf -Kapp.machines=mac.aarch64 make processed-jars
          signing_key: ${{ secrets.CONVEYOR_SIGNING_KEY }}
          agree_to_license: 1

      - name: Compare JDK required modules
        id: conveyor_compare
        if: github.event_name == 'push'
        run: diff --unified  ./komga-tray/conveyor/required-jdk-modules.txt ./output/required-jdk-modules.txt

      - name: Upload JDK required modules
        if: steps.conveyor_compare.outcome == 'failure'
        uses: actions/upload-artifact@v4
        with:
          name: conveyor-required-jdk-modules
          path: ./output/required-jdk-modules.txt

  webui:
    runs-on: ubuntu-latest
    name: Test webui builds
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: 'npm'
          cache-dependency-path: komga-webui/package-lock.json
      - name: npm install
        working-directory: komga-webui
        run: npm install
      - name: npm build
        working-directory: komga-webui
        run: npm run build
        env:
          NODE_OPTIONS: "--max-old-space-size=4096"
      - name: npm test
        working-directory: komga-webui
        run: npm run test:unit


Build (Android)

name: Build (Android)
on:
  push:
    paths:
      - 'noty-android/**'
  pull_request:
    paths:
      - 'noty-android/**'

jobs:
  build:
    name: Build Android
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v1
        with:
          java-version: 17

      - name: Cache Gradle and wrapper
        uses: actions/cache@v2
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Grant Permission to Execute
        run: chmod +x gradlew
        working-directory: noty-android

      - name: 🏗 Build with Gradle 🛠
        run: ./gradlew build --stacktrace
        working-directory: noty-android

      - name: 🚀 Upload APK 📱
        uses: actions/upload-artifact@v2
        with:
          name: app
          path: noty-android/**/*.apk
          retention-days: 3

      - name: 📊 Generate compose compiler report
        if: github.event_name == 'push'
        run: ./gradlew releaseComposeCompilerHtmlReport --stacktrace
        working-directory: noty-android

      - name: ⬆ Update compose compiler report in the project
        if: github.event_name == 'push'
        run: |
          mv -f noty-android/app/composeapp/build/compose_report/index.html docs/pages/noty-android/compose_report.html
          git config user.name $GITHUB_ACTOR
          git config user.email action@github.com
          echo "Compose Report Generated by $GITHUB_RUN_NUMBER via $GITHUB_EVENT_NAME on $(date +"%Y-%m-%d %r")" > docs/pages/noty-android/compose_report.meta
          git add docs/pages/noty-android/compose_report.html
          git add docs/pages/noty-android/compose_report.meta
          git commit -m "Update compose compiler metric and report"
          git status
          git push


Build (API)

name: Build (API)
on:
  push:
    paths:
      - 'noty-api/**'
  pull_request:
    paths:
      - 'noty-api/**'

jobs:
  build:
    name: Build API
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1

      - name: Set up JDK
        uses: actions/setup-java@v1
        with:
          java-version: 11

      - name: Cache Gradle
        uses: actions/cache@v2
        with:
          path: ~/noty-android/.gradle/caches/
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Cache Gradle wrapper
        uses: actions/cache@v2
        with:
          path: ~/noty-android/.gradle/wrapper/
          key: cache-clean-wrapper-${{ runner.os }}-${{ matrix.jdk }}

      - name: Grant Permission to Execute
        run: chmod +x gradlew
        working-directory: noty-api

      - name: 🏗 Build with Gradle 🛠
        run: ./gradlew build --stacktrace
        working-directory: noty-api


Release

name: Release
on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    name: Release NotyKT Android App
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1

      - name: Set up JDK 17
        uses: actions/setup-java@v1
        with:
          java-version: 17

      - name: Cache Gradle and wrapper
        uses: actions/cache@v2
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Grant Permission to Execute
        run: chmod +x gradlew
        working-directory: noty-android

      - name: Build APKs ⚙🛠
        run: bash ./gradlew assembleDebug
        working-directory: noty-android

      - name: Create Release ✅
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: ${{ github.ref }}
          draft: true
          prerelease: false

      - name: Upload NotyKT Simple App 🚀
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: noty-android/app/simpleapp/build/outputs/apk/debug/simpleapp-debug.apk
          asset_name: noty-android-simple.apk
          asset_content_type: application/apk

      - name: Upload NotyKT Compose App 🚀
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: noty-android/app/composeapp/build/outputs/apk/debug/composeapp-debug.apk
          asset_name: noty-android-compose.apk
          asset_content_type: application/apk


Build

name: Build

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    name: Build

    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: 17
          distribution: temurin

      - name: Setup Android SDK
        uses: android-actions/setup-android@v2

      - name: Build App
        run: ./gradlew --no-daemon assembleDebug


Android CI

name: Android CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ '*' ]

jobs:
  lint:
    name: Spotless check
    runs-on: macos-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v4.1.7
      - name: Set up JDK
        uses: actions/setup-java@v4.2.1
        with:
          distribution: 'zulu'
          java-version: 17
      - name: spotless
        run: ./gradlew spotlessCheck

  api_check:
    name: API check
    runs-on: macos-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v4.1.7
      - name: Set up JDK
        uses: actions/setup-java@v4.2.1
        with:
          distribution: 'zulu'
          java-version: 17
      - name: API check
        run: ./gradlew apiCheck

  build:
    name: Build and Tests
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4.1.7

      - name: set up JDK
        uses: actions/setup-java@v4.2.1
        with:
          distribution: 'zulu'
          java-version: 17

      - name: Cache Gradle and wrapper
        uses: actions/cache@v4
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Make Gradle executable
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: |
          ./gradlew --scan --stacktrace \
              assemble -x :benchmark-landscapist:pixel6api31Setup -x :benchmark-landscapist:pixel6api31NonMinifiedReleaseAndroidTest -x :benchmark-landscapist:collectNonMinifiedReleaseBaselineProfile

      - name: Run unit tests
        run: |
          ./gradlew --scan --stacktrace \
              testDebugUnitTest


baseline-profiles

# Workflow name
name: baseline-profiles

# Workflow title
run-name: ${{ github.actor }} requested a workflow

# This should be a manual trigger so this actions gets executed every time make a new pull request.
# Change this event to what suits your project best.
# Read more at https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows
on:
  workflow_dispatch:

# Environment variables (Optional)
# Small projects might have signingConfigs locally. This could lead to failures on GitHub Actions.
# If that's the case, upload your properties defined locally to GitHub Secrets.

# On your signingConfigs, you can recover GitHub Secrets using: variable = System.getenv("VARIABLE")

# Then uncomment this block properly defining your uploaded variables
# env:
#  VARIABLE: ${{ secrets.VARIABLE }}

# Read more at https://docs.github.com/en/actions/security-guides/encrypted-secrets

# Jobs to executed on GitHub machines
jobs:

  # Job name
  generate-baseline-profiles:

    # Operating system where the job gets to be executed
    runs-on: macos-latest

    # Job steps
    steps:

      # Checks your code out on the machine
      - uses: actions/checkout@v4

      # Sets java up
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17

      # Sets gradle up
      - name: Setup Gradle
        uses: gradle/gradle-build-action@v3.4.2

      # Grants execute permission to gradle (safety step)
      - name: Grant Permissions to gradlew
        run: chmod +x gradlew

      # This allows us to build most of what we need without the emulator running
      # and using resources
      - name: Build app and benchmark
        run: ./gradlew :benchmark-landscapist-app:assembleBenchmark

      # Cleans managed device if previously settle and space currently is not available
      - name: Clean Managed Devices
        run: ./gradlew cleanManagedDevices --unused-only

      # Generates Baseline Profile
      - name: Generate Baseline Profile
        run: ./gradlew generateBaselineProfile -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile -Pandroid.experimental.testOptions.managedDevices.setupTimeoutMinutes=20 -Dorg.gradle.workers.max=4

      # Create Pull Request
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v6
        with:
          commit-message: "Generate baseline profiles"
          title: "Generate baseline profiles"
          delete-branch: true
          reviewers: skydoves
          branch: actions/baseline-profiles


Publish Docs

name: Publish Docs
on:
  push:
    branches:
      - main

permissions:
  contents: write

jobs:
  deploy:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: 3.x
      - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
      - uses: actions/cache@v4
        with:
          key: mkdocs-material-${{ env.cache_id }}
          path: .cache
          restore-keys: |
            mkdocs-material-
      - run: pip install mkdocs-material
      - run: mkdocs gh-deploy --force


Publish

name: Publish

on:
  release:
    types: [ released ]
  workflow_dispatch:

jobs:
  publish:
    name: Release build and publish
    runs-on: macos-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v4.1.7

      - name: Set up JDK 17
        uses: actions/setup-java@v4.2.1
        with:
          distribution: 'zulu'
          java-version: 17

      - name: Release build
        run: ./gradlew assemble --scan  -x :benchmark-landscapist:pixel6api31Setup -x :benchmark-landscapist:pixel6api31NonMinifiedReleaseAndroidTest -x :benchmark-landscapist:collectNonMinifiedReleaseBaselineProfile

      - name: Publish to MavenCentral
        run: |
          ./gradlew publishAllPublicationsToMavenCentral --no-configuration-cache
        env:
          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OSSRH_USERNAME }}
          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OSSRH_PASSWORD }}
          ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
          ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
          ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}


Build

name: Build

on:
  pull_request:
    paths-ignore:
      - 'docs/**'
  workflow_dispatch:

jobs:
  linux-build:
    name: Build on Linux
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Install Java
        uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: 17
      - name: Update dependencies
        run: sudo apt-get update
      - name: Install dependencies
        run: sudo apt-get install nodejs chromium-browser
      - name: Build
        uses: gradle/gradle-build-action@v2
        with:
          arguments: build -Dsplit_targets
  macos-build:
    name: Build on macOS
    runs-on: macos-14
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Install Java
        uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: 17
      - name: Build project
        uses: gradle/gradle-build-action@v2
        with:
          arguments: build -Dsplit_targets
      - name: Build iOS sample
        run: xcodebuild -project sample/app-ios/app-ios.xcodeproj -scheme app-ios -sdk iphonesimulator -arch x86_64 build
      - name: Build iOS Compose sample
        run: xcodebuild -project sample/app-ios-compose/app-ios-compose.xcodeproj -scheme app-ios-compose -sdk iphonesimulator -arch x86_64 build


name: build and deploy material mkdocs to gh-pages
on:
  push:
    branches:
      - master
      - main
  workflow_dispatch:
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
        with:
          python-version: 3.x
      - run: pip install mkdocs-material
      - run: pip install mkdocs-minify-plugin
      - run: mkdocs gh-deploy --force


Publish

name: Publish

on:
  release:
    types: [ created ]
  workflow_dispatch:

jobs:
  create-staging-repository:
    runs-on: ubuntu-latest
    name: Create staging repository
    outputs:
      repository_id: ${{ steps.create.outputs.repository_id }}
    steps:
      - id: create
        uses: nexus-actions/create-nexus-staging-repo@v1.3.0
        with:
          username: arkivanov
          password: ${{ secrets.SONATYPE_PASSWORD }}
          staging_profile_id: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
          description: Created by GitHub Actions
          base_url: https://oss.sonatype.org/service/local/
  publish:
    name: Publish
    runs-on: macos-14
    needs: create-staging-repository
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Install Java
        uses: actions/setup-java@v1
        with:
          java-version: 17
      - name: Publish
        env:
          SONATYPE_REPOSITORY_ID: ${{ needs.create-staging-repository.outputs.repository_id }}
          SONATYPE_USER_NAME: ${{ secrets.SONATYPE_USER_NAME }}
          SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
          SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
          SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
        run: ./gradlew publish
  close-staging-repository:
    name: Close staging repository
    runs-on: ubuntu-latest
    needs: [ create-staging-repository, publish ]
    steps:
      - name: Close staging repository
        uses: nexus-actions/release-nexus-staging-repo@v1.1
        with:
          username: arkivanov
          password: ${{ secrets.SONATYPE_PASSWORD }}
          staging_repository_id: ${{ needs.create-staging-repository.outputs.repository_id }}
          base_url: https://oss.sonatype.org/service/local/
          close_only: 'true'
  check-publication:
    name: Check publication
    runs-on: macos-14
    needs: close-staging-repository
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Install Java
        uses: actions/setup-java@v1
        with:
          java-version: 17
      - name: Check publication
        run: ./gradlew kotlinUpgradeYarnLock :tools:check-publication:build -Pcheck_publication


Delete Unused Caches

name: Delete Unused Caches

on:
  pull_request:
    types: [ closed ]

jobs:

  # This deletes the caches created by the branch while it was in PR.
  # Those caches are inaccessible to it once it's merged, so they can be deleted.
  delete-orphaned-caches:
    runs-on: ubuntu-latest
    steps:
      - uses: snnaplab/delete-branch-cache-action@v1
        with:
          # Specify explicitly because the ref at the time of merging
          # will be a branch name such as 'main', 'develop'
          ref: refs/pull/${{ github.event.number }}/merge


Kotlin CI

name : Kotlin CI

on :
  pull_request :
  merge_group :

env:
  ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL: 720 # 12 minutes; default is 20s

# If CI is already running for a branch when that branch is updated, cancel the older jobs.
concurrency :
  group : ci-${{ github.ref }}-${{ github.head_ref }}
  cancel-in-progress : true

jobs :

  build-all :
    name : Build all
    runs-on : workflow-kotlin-test-runner-ubuntu-4core
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : main build
        uses : ./.github/actions/gradle-task
        with :
          task : compileKotlin assembleDebug
          write-cache-key : main-build-artifacts

  dokka :
    name : Dokka
    runs-on : ubuntu-latest
    needs : build-all
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : Run dokka to validate kdoc
        uses : ./.github/actions/gradle-task
        with :
          task : siteDokka
          write-cache-key : main-build-artifacts

  shards-and-version :
    name : Shard Matrix Yaml
    runs-on : workflow-kotlin-test-runner-ubuntu-4core
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : check published artifacts
        uses : ./.github/actions/gradle-task-with-commit
        with :
          check-task : connectedCheckShardMatrixYamlCheck checkVersionIsSnapshot
          fix-task : connectedCheckShardMatrixYamlUpdate checkVersionIsSnapshot
          personal-access-token: ${{ secrets.PR_UPDATE_TOKEN }}
          write-cache-key : build-logic

  artifacts-check :
    name : ArtifactsCheck
    # the `artifactsCheck` task has to run on macOS in order to see the iOS KMP artifacts
    runs-on : macos-latest
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : check published artifacts
        uses : ./.github/actions/gradle-task-with-commit
        with :
          check-task : artifactsCheck
          fix-task : artifactsDump
          personal-access-token: ${{ secrets.PR_UPDATE_TOKEN }}
          write-cache-key : build-logic

  dependency-guard :
    name : Dependency Guard
    runs-on : ubuntu-latest
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      # If the PR was made by a maintainer or Renovate, automatically update baselines and push
      # so that no one has to check out the branch and update the baselines manually.
      - name : dependency-guard
        uses : ./.github/actions/gradle-task-with-commit
        with :
          check-task : dependencyGuard --refresh-dependencies
          fix-task : dependencyGuardBaseline --refresh-dependencies
          personal-access-token: ${{ secrets.PR_UPDATE_TOKEN }}
          write-cache-key : build-logic

  ktlint :
    name : KtLint
    runs-on : ubuntu-latest
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      # If the PR was made by a maintainer or Renovate, automatically format and push
      # so that no one has to check out the branch and do it manually.
      - name : KtLint
        uses : ./.github/actions/gradle-task-with-commit
        with :
          check-task : ktLintCheck
          fix-task : ktLintFormat
          personal-access-token: ${{ secrets.PR_UPDATE_TOKEN }}
          write-cache-key : build-logic

  api-check :
    name : Api check
    runs-on : ubuntu-latest
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      # If the PR was made by a maintainer or Renovate, automatically format and push
      # so that no one has to check out the branch and do it manually.
      - name : binary compatibility
        uses : ./.github/actions/gradle-task-with-commit
        with :
          check-task : apiCheck
          fix-task : apiDump
          personal-access-token: ${{ secrets.PR_UPDATE_TOKEN }}
          write-cache-key : build-logic

  android-lint :
    name : Android Lint
    runs-on : ubuntu-latest
    needs : build-all
    timeout-minutes : 20
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : Check with Gradle
        uses : ./.github/actions/gradle-task
        with :
          task : lint
          write-cache-key : main-build-artifacts

  check :
    name : Check
    runs-on : ubuntu-latest
    needs : build-all
    timeout-minutes : 20
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : Check with Gradle
        uses : ./.github/actions/gradle-task
        with :
          task : |
            allTests
            test
            --continue
          restore-cache-key : build-logic
          write-cache-key : main-build-artifacts

      # Report as GitHub Pull Request Check.
      - name : Publish Test Report
        uses : mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4
        if : always() # always run even if the previous step fails
        with :
          report_paths : '**/build/test-results/test/TEST-*.xml'

  tutorials :
    name : Build Tutorials
    runs-on : ubuntu-latest
    timeout-minutes : 15
    steps :
      # These setup steps should be common across all jobs in this workflow.
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : build tutorials
        uses : ./.github/actions/gradle-task
        with :
          task : build
          build-root-directory : samples/tutorial
          restore-cache-key : main-build-artifacts

  jvm-conflate-runtime-test :
    name : Conflate Stale Renderings Runtime JVM Tests
    runs-on : ubuntu-latest
    timeout-minutes : 20
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : Check with Gradle
        uses : ./.github/actions/gradle-task
        with :
          task : jvmTest --continue -Pworkflow.runtime=conflate
          restore-cache-key : main-build-artifacts

      # Report as GitHub Pull Request Check.
      - name : Publish Test Report
        uses : mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4
        if : always() # always run even if the previous step fails
        with :
          report_paths : '**/build/test-results/test/TEST-*.xml'

  jvm-stateChange-runtime-test :
    name : Render On State Change Only Runtime JVM Tests
    runs-on : ubuntu-latest
    timeout-minutes : 20
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : Check with Gradle
        uses : ./.github/actions/gradle-task
        with :
          task : jvmTest --continue -Pworkflow.runtime=baseline-stateChange
          restore-cache-key : main-build-artifacts

      # Report as GitHub Pull Request Check.
      - name : Publish Test Report
        uses : mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4
        if : always() # always run even if the previous step fails
        with :
          report_paths : '**/build/test-results/test/TEST-*.xml'

  jvm-conflate-stateChange-runtime-test :
    name : Render On State Change Only and Conflate Stale Runtime JVM Tests
    runs-on : ubuntu-latest
    timeout-minutes : 20
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : Check with Gradle
        uses : ./.github/actions/gradle-task
        with :
          task : jvmTest --continue -Pworkflow.runtime=conflate-stateChange
          restore-cache-key : main-build-artifacts

      # Report as GitHub Pull Request Check.
      - name : Publish Test Report
        uses : mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4
        if : always() # always run even if the previous step fails
        with :
          report_paths : '**/build/test-results/test/TEST-*.xml'

  ios-tests :
    name : iOS Tests
    runs-on : macos-latest
    timeout-minutes : 30
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : Check with Gradle
        uses : ./.github/actions/gradle-task
        with :
          task : iosX64Test
          restore-cache-key : main-build-artifacts

      # Report as GitHub Pull Request Check.
      - name : Publish Test Report
        uses : mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4
        if : always() # always run even if the previous step fails
        with :
          report_paths : '**/build/test-results/test/TEST-*.xml'

  js-tests :
    name : JS Tests
    runs-on : ubuntu-latest
    timeout-minutes : 20
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      ## JS Specific Tests (for KMP js actuals in core and runtime).
      - name : Check with Gradle
        uses : ./.github/actions/gradle-task
        with :
          task : jsTest
          restore-cache-key : main-build-artifacts

      # Report as GitHub Pull Request Check.
      - name : Publish Test Report
        uses : mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4
        if : always() # always run even if the previous step fails
        with :
          report_paths : '**/build/test-results/test/TEST-*.xml'

  performance-tests :
    name : Performance tests
    runs-on : workflow-kotlin-test-runner-ubuntu-4core
    timeout-minutes : 45
    strategy :
      # Allow tests to continue on other devices if they fail on one device.
      fail-fast : false
      matrix :
        api-level :
          - 31
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : Instrumented tests
        uses : ./.github/actions/gradle-tasks-with-emulator
        with :
          api-level : ${{ matrix.api-level }}
          prepare-task : :benchmarks:performance-poetry:complex-poetry:prepareDebugAndroidTestArtifacts
          test-task : :benchmarks:performance-poetry:complex-poetry:connectedCheck --continue
          restore-cache-key : androidTest-build-artifacts

  instrumentation-tests :
    name : Instrumentation tests
    runs-on : workflow-kotlin-test-runner-ubuntu-4core
    timeout-minutes : 60
    strategy :
      # Allow tests to continue on other devices if they fail on one device.
      fail-fast : false
      matrix :
        api-level :
          - 31
        ### <start-connected-check-shards>
        shardNum: [ 1, 2, 3 ]
        ### <end-connected-check-shards>
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : Instrumented tests
        uses : ./.github/actions/gradle-tasks-with-emulator
        with :
          api-level : ${{ matrix.api-level }}
          prepare-task : prepareConnectedCheckShard${{matrix.shardNum}}
          test-task : connectedCheckShard${{matrix.shardNum}} -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck
          write-cache-key : androidTest-build-artifacts-${{matrix.shardNum}}
          restore-cache-key : main-build-artifacts

  runtime-instrumentation-tests :
    name : Alternate Runtime Instrumentation tests
    runs-on : workflow-kotlin-test-runner-ubuntu-4core
    timeout-minutes : 60
    strategy :
      # Allow tests to continue on other devices if they fail on one device.
      fail-fast : false
      matrix :
        api-level :
          - 31
        ### <start-connected-check-shards>
        shardNum: [ 1, 2, 3 ]
        ### <end-connected-check-shards>
        runtime : [ conflate, baseline-stateChange, conflate-stateChange ]
    steps :
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name : Instrumented tests
        uses : ./.github/actions/gradle-tasks-with-emulator
        with :
          api-level : ${{ matrix.api-level }}
          prepare-task : prepareConnectedCheckShard${{matrix.shardNum}} -Pworkflow.runtime=${{matrix.runtime}}
          test-task : connectedCheckShard${{matrix.shardNum}} -Pworkflow.runtime=${{matrix.runtime}} -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck
          write-cache-key : androidTest-build-artifacts-${{matrix.shardNum}}-${{matrix.runtime}}
          restore-cache-key : main-build-artifacts

  all-green :
    if : always()
    runs-on : ubuntu-latest
    needs :
      - android-lint
      - api-check
      - artifacts-check
      - check
      - dependency-guard
      - dokka
      - instrumentation-tests
      - ios-tests
      - js-tests
      - jvm-conflate-runtime-test
      - jvm-conflate-stateChange-runtime-test
      - jvm-stateChange-runtime-test
      - ktlint
      - performance-tests
      - runtime-instrumentation-tests
      - shards-and-version
      - tutorials

    steps :
      - name : require that all other jobs have passed
        uses : re-actors/alls-green@release/v1
        with :
          jobs : ${{ toJSON(needs) }}


Publish Release

name: Publish Release

on:
  workflow_dispatch:

jobs:
  publish-release:
    # macos-latest is too slow. -14 will become latest in Q2 '24
    runs-on: macos-14
    if: github.repository == 'square/workflow-kotlin'
    timeout-minutes: 35

    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
      - uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2

      - name: Ensure this isn't a -SNAPSHOT version
        uses: ./.github/actions/gradle-task
        with:
          task: checkVersionIsNotSnapshot

      - name: Assemble
        uses: ./.github/actions/gradle-task
        with:
          task: assemble

      - name: Check
        uses: ./.github/actions/gradle-task
        with:
          task: check -x artifactsCheck

      - name: Publish Release
        uses: ./.github/actions/gradle-task
        with:
          task: publish
        env:
          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
          ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ARTIFACT_SIGNING_PRIVATE_KEY }}


Publish snapshot

name: Publish snapshot

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  publish-snapshot:
    # macos-latest is too slow. -14 will become latest in Q2 '24
    runs-on: macos-14
    if: github.repository == 'square/workflow-kotlin'
    timeout-minutes: 35

    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
      - uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2

      - name: Check for -SNAPSHOT version
        uses: ./.github/actions/gradle-task
        with:
          task: checkVersionIsSnapshot
          write-cache-key: build-logic

      - name: Assemble
        uses: ./.github/actions/gradle-task
        with:
          task: assemble
          write-cache-key: main-build-artifacts

      - name: Check
        uses: ./.github/actions/gradle-task
        with:
          task: check -x artifactsCheck

      - name: Publish Snapshots
        uses: ./.github/actions/gradle-task
        with:
          task: publish
        env:
          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}


Validate Codeowners

name: Validate Codeowners

on:
  push:
    branches:
      - main
  pull_request:
  merge_group:

jobs:
  codeowners:
    name: Validate Codeowners
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
      # https://github.com/marketplace/actions/github-codeowners-validator
      - uses: mszostok/codeowners-validator@7f3f5e28c6d7b8dfae5731e54ce2272ca384592f # v0.7.4
        with:
          # TODO(https://github.com/square/workflow-kotlin/issues/316) Add the owners check
          # back once this is fixed, or we implement a workaround.
          checks: "files,syntax,duppatterns" #,owners
          experimental_checks: "notowned"
          # Wardell is not in the Square org apparently.
          owner_checker_ignored_owners: "@wardellbagby"
          # GitHub access token is required only if the `owners` check is enabled
          github_access_token: "${{ secrets.OWNERS_VALIDATOR_GITHUB_SECRET }}"


Validate documentation site

name: Validate documentation site

on:
  pull_request:
    paths:
      # Rebuild when workflow configs change.
      - .github/workflows/validate-documentation.yml
      - '**.md'
      - lint_docs.sh
      - .markdownlint.rb

jobs:
  lint:
    name: Lint Markdown files
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
      - name: Set up Ruby 2.6
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.7
      - name: Install dependencies
        run: gem install mdl
      - name: Lint docs
        run: ./lint_docs.sh


Build

name: Build

on:
  push:
    branches:
      - main
    paths-ignore:
      - '.idea/**'
      - '.gitattributes'
      - '.github/**.json'
      - '.gitignore'
      - '.gitmodules'
      - '**.md'
      - 'LICENSE'
      - 'NOTICE'
  pull_request:
    paths-ignore:
      - '.idea/**'
      - '.gitattributes'
      - '.github/**.json'
      - '.gitignore'
      - '.gitmodules'
      - '**.md'
      - 'LICENSE'
      - 'NOTICE'
concurrency:
  group: build-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test_and_apk:
    name: "Local tests and APKs"
    runs-on: ubuntu-latest

    permissions:
      contents: write
      security-events: write
      pull-requests: write

    timeout-minutes: 60

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: 'true'
          fetch-depth: 0

      - name: Delete unnecessary tools 🔧
        uses: jlumbroso/free-disk-space@v1.3.1
        with:
          android: false # Don't remove Android tools
          tool-cache: true # Remove image tool cache - rm -rf "$AGENT_TOOLSDIRECTORY"
          dotnet: true # rm -rf /usr/share/dotnet
          haskell: true # rm -rf /opt/ghc...
          swap-storage: true # rm -f /mnt/swapfile (4GiB)
          docker-images: false # Takes 16s, enable if needed in the future
          large-packages: false # includes google-cloud-sdk and it's slow

      - name: Write sign info
        if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && github.repository == 'lihenggui/Blocker'
        run: |
          if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
            echo releaseStorePassword='${{ secrets.SIGNING_STORE_PASSWORD }}' >> gradle.properties
            echo releaseKeyAlias='${{ secrets.SIGNING_KEY_ALIAS }}' >> gradle.properties
            echo releaseKeyPassword='${{ secrets.SIGNING_KEY_PASSWORD }}' >> gradle.properties
            echo releaseStoreFile='${{ github.workspace }}/key.jks' >> gradle.properties
            echo ${{ secrets.KEYSTORE }} | base64 --decode > ${{ github.workspace }}/key.jks
          fi

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3
        with:
          validate-wrappers: true
          gradle-home-cache-cleanup: true

      - name: Accept Android licenses
        run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --licenses || true

      - name: Check build-logic
        run: ./gradlew check -p build-logic

      - name: Check spotless
        run: ./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache

      - name: Check Dependency Guard
        id: dependencyguard_verify
        continue-on-error: true
        run: ./gradlew dependencyGuard

      - name: Prevent updating Dependency Guard baselines if this is a fork
        id: checkfork_dependencyguard
        continue-on-error: false
        if: steps.dependencyguard_verify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
        run: |
          echo "::error::Dependency Guard failed, please update baselines with: ./gradlew dependencyGuardBaseline" && exit 1
        # Runs if previous job failed
      - name: Generate new Dependency Guard baselines if verification failed and it's a PR
        id: dependencyguard_baseline
        if: steps.dependencyguard_verify.outcome == 'failure' && github.event_name == 'pull_request'
        run: |
          ./gradlew dependencyGuardBaseline
      - name: Push new Dependency Guard baselines if available
        uses: stefanzweifel/git-auto-commit-action@v5
        if: steps.dependencyguard_baseline.outcome == 'success'
        with:
          file_pattern: '**/dependencies/*.txt'
          disable_globbing: true
          commit_message: "🤖 Updates baselines for Dependency Guard"

      - name: Run all local screenshot tests (Roborazzi)
        id: screenshotsverify
        continue-on-error: true
        run: ./gradlew verifyRoborazziFossDebug

      - name: Prevent pushing new screenshots if this is a fork
        id: checkfork_screenshots
        continue-on-error: false
        if: steps.screenshotsverify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
        run: |
          echo "::error::Screenshot tests failed, please create a PR in your fork first." && exit 1

      # Runs if previous job failed
      - name: Generate new screenshots if verification failed and it's a PR
        id: screenshotsrecord
        if: steps.screenshotsverify.outcome == 'failure' && github.event_name == 'pull_request'
        run: |
          ./gradlew recordRoborazziFossDebug

      - name: Push new screenshots if available
        uses: stefanzweifel/git-auto-commit-action@v5
        if: steps.screenshotsrecord.outcome == 'success'
        with:
          file_pattern: '*/*.png'
          disable_globbing: true
          commit_message: "🤖 Updates screenshots"

      # Run local tests after screenshot tests to avoid wrong UP-TO-DATE. TODO: Ignore screenshots.
      - name: Run local tests and create report
        if: always()
        run: ./gradlew testFossDebug :lint:test

      # Replace task exclusions with `-Pandroidx.baselineprofile.skipgeneration` when
      # https://android-review.googlesource.com/c/platform/frameworks/support/+/2602790 landed in a
      # release build
      - name: Build all build type and flavor permutations
        run: ./gradlew :app-compose:assemble :benchmarks:assemble
          -x pixel6Api33MarketNonMinifiedReleaseAndroidTest
          -x pixel6Api33FossNonMinifiedReleaseAndroidTest
          -x collectFossNonMinifiedReleaseBaselineProfile
          -x collectMarketNonMinifiedReleaseBaselineProfile

      - name: Upload Foss apk
        if: success() && github.event_name != 'pull_request' && github.repository == 'lihenggui/Blocker'
        uses: actions/upload-artifact@v4
        with:
          name: Foss-APK
          path: ${{ github.workspace }}/app-compose/build/outputs/apk/foss/release

      - name: Upload Market apk
        if: success() && github.event_name != 'pull_request' && github.repository == 'lihenggui/Blocker'
        uses: actions/upload-artifact@v4
        with:
          name: Market-APK
          path: ${{ github.workspace }}/app-compose/build/outputs/apk/market/release

      - name: Upload build outputs (APKs)
        uses: actions/upload-artifact@v4
        with:
          name: APKs
          path: '**/build/outputs/apk/**/*.apk'

      - name: Upload mappings
        if: success() && github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
        uses: actions/upload-artifact@v4
        with:
          name: mappings
          path: "app-compose/build/outputs/mapping/marketRelease"

      - name: Set apk path
        id: apk-path
        run: |
          foss_path=$(find **/build/outputs/apk/foss -name '*.apk' -type f | head -1)
          echo "foss_path=$foss_path" >> $GITHUB_OUTPUT
          market_path=$(find **/build/outputs/apk/market -name '*.apk' -type f | head -1)
          echo "market_path=$market_path" >> $GITHUB_OUTPUT

      - name: Get apk info
        if: success() && github.event_name != 'pull_request' && github.repository == 'lihenggui/Blocker'
        id: apk-info
        uses: zhaobozhen/apk-info-action@v1.1.3
        with:
          apk-path: ${{ steps.apk-path.outputs.foss_path }}

      - name: Upload JVM local results (XML)
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: local-test-results
          path: '**/build/test-results/test*UnitTest/**.xml'

      - name: Check lint
        run: ./gradlew :app-compose:lintMarketRelease :lint:lint

      - name: Upload lint reports (HTML)
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: lint-reports
          path: '**/build/reports/lint-results-*.html'

      - name: Upload lint reports (SARIF)
        if: success()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: './'

      - name: Check badging
        id: check_badging
        continue-on-error: true
        run: ./gradlew :app-compose:checkMarketReleaseBadging

      - name: Prevent updating badging if this is a fork
        id: checkfork_badging
        continue-on-error: false
        if: steps.check_badging.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
        run: |
          echo "::error::Badging check failed, please create a PR in your fork first." && exit 1

      - name: Update badging if verification failed and it's a PR
        id: update_badging
        if: steps.check_badging.outcome == 'failure' && github.event_name == 'pull_request'
        run: |
          ./gradlew updateMarketReleaseBadging

      - name: Push new badging if available
        uses: stefanzweifel/git-auto-commit-action@v5
        if: steps.update_badging.outcome == 'success'
        with:
          file_pattern: '*/*-badging.txt'
          disable_globbing: true
          commit_message: "🤖 Updates badging"

  androidTest:
    runs-on: ubuntu-latest
    timeout-minutes: 90
    strategy:
      matrix:
        api-level: [ 26, 30, 34 ]

    steps:
      - name: Delete unnecessary tools 🔧
        uses: jlumbroso/free-disk-space@v1.3.1
        with:
          android: false # Don't remove Android tools
          tool-cache: true # Remove image tool cache - rm -rf "$AGENT_TOOLSDIRECTORY"
          dotnet: true # rm -rf /usr/share/dotnet
          haskell: true # rm -rf /opt/ghc...
          swap-storage: true # rm -f /mnt/swapfile (4GiB)
          docker-images: false # Takes 16s, enable if needed in the future
          large-packages: false # includes google-cloud-sdk and it's slow

      - name: Enable KVM group perms
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm
          ls /dev/kvm

      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: 'true'
          fetch-depth: 0

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3
        with:
          validate-wrappers: true
          gradle-home-cache-cleanup: true

      # Cache AVD. See https://github.com/ReactiveCircus/android-emulator-runner
      - name: AVD cache
        uses: actions/cache@v4
        id: avdcache
        with:
          path: |
            ~/.android/avd/*
            ~/.android/adb*
          key: avd-${{ matrix.api-level }}

      - name: create AVD and generate snapshot for caching
        if: steps.avdcache.outputs.cache-hit != 'true'
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: ${{ matrix.api-level }}
          force-avd-creation: false
          arch: x86_64
          target: default # No ATD in API 26
          emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
          disable-animations: false
          disk-size: 6000M
          heap-size: 600M
          script: echo "Generated AVD snapshot for caching."

      - name: Build projects and run instrumentation tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: ${{ matrix.api-level }}
          arch: x86_64
          disable-animations: true
          disk-size: 6000M
          heap-size: 600M
          script: ./gradlew connectedMarketDebugAndroidTest --daemon

      - name: Run local tests (including Roborazzi) for the combined coverage report (only API 30)
        if: matrix.api-level == 30
        # There is no need to verify Roborazzi tests to generate coverage.
        run: ./gradlew testFossDebugUnitTest -Proborazzi.test.verify=false # Add Prod if we ever add JVM tests for prod

      # Add `createProdDebugUnitTestCoverageReport` if we ever add JVM tests for prod
      - name: Generate coverage reports for Debug variants (only API 30)
        if: matrix.api-level == 30
        run: ./gradlew createFossDebugCombinedCoverageReport

      - name: Upload test reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-reports-${{ matrix.api-level }}
          path: '**/build/reports/androidTests'

      - name: Display local test coverage (only API 30)
        if: matrix.api-level == 30
        id: jacoco
        uses: madrapps/jacoco-report@v1.6.1
        with:
          title: Combined test coverage report
          min-coverage-overall: 40
          min-coverage-changed-files: 60
          paths: |
            ${{ github.workspace }}/**/build/reports/jacoco/**/*Report.xml
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Upload local coverage reports (XML + HTML) (only API 30)
        if: matrix.api-level == 30
        uses: actions/upload-artifact@v4
        with:
          name: coverage-reports
          if-no-files-found: error
          compression-level: 1
          overwrite: false
          path: '**/build/reports/jacoco/'


GitHub Release with APKs

name: GitHub Release with APKs

on:
  push:
    tags:
      - 'v*'
      - '!v*-fdroid'

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 120

    steps:
      - name: Enable KVM group perms
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm
          ls /dev/kvm

      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: 'true'
          fetch-depth: 0

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Write sign info
        run: |
          if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
            echo releaseStorePassword='${{ secrets.SIGNING_STORE_PASSWORD }}' >> gradle.properties
            echo releaseKeyAlias='${{ secrets.SIGNING_KEY_ALIAS }}' >> gradle.properties
            echo releaseKeyPassword='${{ secrets.SIGNING_KEY_PASSWORD }}' >> gradle.properties
            echo releaseStoreFile='${{ github.workspace }}/key.jks' >> gradle.properties
            echo ${{ secrets.KEYSTORE }} | base64 --decode > ${{ github.workspace }}/key.jks
          fi

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3
        with:
          validate-wrappers: true
          gradle-home-cache-cleanup: true

      - name: Install GMD image for baseline profile generation
        run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager "system-images;android-33;aosp_atd;arm64-v8a"

      - name: Accept Android licenses
        run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --licenses || true

      - name: Build release variant including baseline profile generation
        run: ./gradlew :app-compose:assembleRelease :app-compose:bundleRelease
          -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
          -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
          -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true
          -Pandroid.experimental.androidTest.numManagedDeviceShards=1
          -Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1

      - name: Set apk path
        id: apk-path
        run: |
          foss_path=$(find **/build/outputs/apk/foss -name '*.apk' -type f | head -1)
          echo "foss_path=$foss_path" >> $GITHUB_OUTPUT
          market_path=$(find **/build/outputs/apk/market -name '*.apk' -type f | head -1)
          echo "market_path=$market_path" >> $GITHUB_OUTPUT

      - name: Get apk info
        id: apk-info
        uses: zhaobozhen/apk-info-action@v1.1.3
        with:
          apk-path: ${{ steps.apk-path.outputs.foss_path }}

      - name: Build Changelog
        id: build_changelog
        uses: mikepenz/release-changelog-builder-action@v4.2.2
        env:
          GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }}

      - name: Write whats new info
        run: |
          mkdir -p ${{ github.workspace }}/distribution/whatsnew/
          echo '${{ steps.build_changelog.outputs.changelog }}' | head -c 490 >> ${{ github.workspace }}/distribution/whatsnew/whatsnew-en-US

      - name: Upload built Market apk
        if: success()
        uses: actions/upload-artifact@v4
        with:
          name: Blocker-${{ steps.apk-info.outputs.version-name }}-market-release.apk
          path: ${{ steps.apk-path.outputs.market_path }}

      - name: Upload built Foss apk
        if: success()
        uses: actions/upload-artifact@v4
        with:
          name: Blocker-${{ steps.apk-info.outputs.version-name }}-foss-release.apk
          path: ${{ steps.apk-path.outputs.foss_path }}

      - name: Upload mappings
        if: success()
        uses: actions/upload-artifact@v4
        with:
          name: mappings
          path: "app-compose/build/outputs/mapping/marketRelease"

      - name: Publish to Play Store open beta track
        uses: r0adkll/upload-google-play@v1.1.3
        with:
          serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON }}
          packageName: com.merxury.blocker
          releaseFiles: app-compose/build/outputs/bundle/marketRelease/app-compose-market-release.aab
          track: beta
          inAppUpdatePriority: 2
          mappingFile: app-compose/build/outputs/mapping/marketRelease/mapping.txt
          whatsNewDirectory: ${{ github.workspace }}/distribution/whatsnew

      - name: Rename output APKs
        run: |
          mv ${{ steps.apk-path.outputs.market_path }} Blocker-${{ steps.apk-info.outputs.version-name }}-market-release.apk
          mv ${{ steps.apk-path.outputs.foss_path }} Blocker-${{ steps.apk-info.outputs.version-name }}-foss-release.apk

      - name: Create Release
        id: create_release
        uses: softprops/action-gh-release@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }}
        with:
          tag_name: v${{ steps.apk-info.outputs.version-name }}
          draft: false
          prerelease: true
          append_body: true
          generate_release_notes: true
          body: ${{steps.build_changelog.outputs.changelog}}
          files: |
            Blocker-${{ steps.apk-info.outputs.version-name }}-market-release.apk
            Blocker-${{ steps.apk-info.outputs.version-name }}-foss-release.apk


FlorisBoard CI

name: FlorisBoard CI

on:
  push:
    branches: [ master ]
    paths-ignore:
      - ".github/ISSUE_TEMPLATE/**"
      - ".github/FUNDING.yml"
      - ".editorconfig"
      - "fastlane/**"
      - "CONTRIBUTING.md"
      - "CODE_OF_CONDUCT.md"
      - "LICENSE"
      - "README.md"
      - "ROADMAP.md"
  pull_request:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
        with:
          submodules: recursive

      - uses: gradle/wrapper-validation-action@v1
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: 17
          distribution: temurin
          cache: gradle
      - name: Set up CMake and Ninja
        uses: lukka/get-cmake@latest
      - name: Build with Gradle
        # MUST call gradlew separately because of an OSS license plugin issue.
        # See https://github.com/google/play-services-plugins/issues/199
        run: ./gradlew clean && ./gradlew assembleDebug
      - uses: actions/upload-artifact@v3
        with:
          name: app-debug.apk
          path: app/build/outputs/apk/debug/app-debug.apk


Crowdin Upload Sources

name: Crowdin Upload Sources

on:
  push:
    branches: [ master ]
    paths:
      - "app/src/main/res/values/strings.xml"
      - ".github/workflows/crowdin-upload.yml"

jobs:
  upload-to-crowdin:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Upload
        uses: crowdin/github-action@1.4.0
        with:
          config: "crowdin.yml"
          upload_sources: true
          upload_translations: false
          download_translations: false
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          FSEC_CROWDIN_PROJECT_ID: ${{ secrets.FSEC_CROWDIN_PROJECT_ID }}
          FSEC_CROWDIN_PERSONAL_TOKEN: ${{ secrets.FSEC_CROWDIN_PERSONAL_TOKEN }}


CI

name: CI

on:
  push:
    branches:
      - release/*
  pull_request:
  workflow_dispatch:
    inputs:
      simple_server_branch:
        required: false
        type: string
        default: master
  workflow_call:

concurrency:
  group: ci-${{ github.head_ref }}
  cancel-in-progress: true

jobs:
  qa_lint:
    runs-on: [ ubuntu-latest ]
    steps:
      - uses: actions/checkout@v4

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: QA Lint
        run: ./gradlew --build-cache --no-daemon lintQaDebug

  qa_unit_tests:
    runs-on: [ ubuntu-latest ]
    steps:
      - uses: actions/checkout@v4

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: QA Unit Tests
        run: ./gradlew --build-cache --no-daemon testQaDebugUnitTest

  generate_server_app_name:
    runs-on: [ ubuntu-latest ]
    outputs:
      heroku_app_name: ${{ steps.generate-server-app-name.outputs.heroku_app_name }}
    steps:
      - name: Checkout Android source
        uses: actions/checkout@v4

      - name: Generate server app name
        id: generate-server-app-name
        run: .github/scripts/generate_heroku_app_name.sh ${{ github.ref }}

  create_server_on_heroku:
    needs: generate_server_app_name
    runs-on: [ ubuntu-latest ]
    outputs:
      heroku_app_name: ${{ steps.create-server-on-heroku.outputs.heroku_app_name }}
      heroku_app_url: ${{ steps.create-server-on-heroku.outputs.heroku_app_url }}
      heroku_app_exists: ${{ steps.create-server-on-heroku.outputs.heroku_app_exists }}
    env:
      HEROKU_API_KEY: ${{ secrets.SERVICES_HEROKU_API_KEY }}
    steps:
      - name: Checkout Android source
        uses: actions/checkout@v4
        with:
          path: android-app

      - name: Checkout Server source
        uses: actions/checkout@v4
        with:
          repository: simpledotorg/simple-server
          fetch-depth: 0 # Full clone needed for Heroku deploys (https://devcenter.heroku.com/changelog-items/775)
          path: server-app
          ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.simple_server_branch || 'master' }}

      - name: Install Heroku CLI
        if: steps.node-dependency-cache.outputs.cache-hit != 'true'
        run: npm install heroku

      - name: Link Heroku CLI globally
        run: npm link heroku

      - name: Create server on Heroku
        id: create-server-on-heroku
        run: |
          android-app/.github/scripts/create_heroku_app.sh \
          ${{ needs.generate_server_app_name.outputs.heroku_app_name }} \
          server-app

  deploy_server_on_heroku:
    needs: [create_server_on_heroku]
    runs-on: [ ubuntu-latest ]
    timeout-minutes: 60
    outputs:
      heroku_app_name: ${{ steps.start-simple-server.outputs.heroku_app_name }}
    env:
      HEROKU_API_KEY: ${{ secrets.SERVICES_HEROKU_API_KEY }}
    steps:
      - name: Checkout Android source
        uses: actions/checkout@v4
        with:
          path: android-app

      - name: Checkout server app source
        uses: actions/checkout@v4
        with:
          repository: simpledotorg/simple-server
          fetch-depth: 0 # Full clone needed for Heroku deploys (https://devcenter.heroku.com/changelog-items/775)
          path: server-app
          ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.simple_server_branch || 'master' }}

      - name: Create node dependency hash
        id: create_node_modules_hash
        run: android-app/.github/scripts/compute_node_dependency_hash.sh

      - name: Cache node modules
        id: node-dependency-cache
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            package-lock.json
          key: ${{ runner.os }}-npm-${{ steps.create_node_modules_hash.outputs.node_dep_hash }}

      - name: Install Heroku CLI
        if: steps.node-dependency-cache.outputs.cache-hit != 'true'
        run: npm install heroku

      - name: Link Heroku CLI globally
        run: npm link heroku

      - name: Install Heroku Buildpacks registry
        run: heroku plugins:install buildpack-registry

      - name: Install heroku buildpacks plugin
        run: heroku plugins:install buildpacks

      - name: Deploy the server on Heroku
        id: start-simple-server
        run: |
          android-app/.github/scripts/setup_heroku_instance.sh \
          ${{ needs.create_server_on_heroku.outputs.heroku_app_name }} \
          ${{ env.HEROKU_API_KEY }} \
          ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.simple_server_branch || 'master' }} \
          server-app \
          android-app \
          ${{ needs.create_server_on_heroku.outputs.heroku_app_exists }} \
          ${{ secrets.HEROKU_SECRET_PROPERTIES }}

  build_qa_android_tests_apk:
    needs: create_server_on_heroku
    runs-on: [ ubuntu-latest ]
    timeout-minutes: 60
    steps:
      - name: Checkout Android source
        uses: actions/checkout@v4

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: Build QA Test Artifacts
        id: build-instrumented-tests
        run: |
          ./gradlew --build-cache --no-daemon -PmanifestEndpoint=${{ needs.create_server_on_heroku.outputs.heroku_app_url }}api/ assembleQaDebug assembleQaDebugAndroidTest

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: test-artifacts
          path: app/build/outputs/apk

  # reactivecircus/android-emulator-runner@v2 requires MacOS to run on
  # https://github.com/ReactiveCircus/android-emulator-runner
  qa_android_tests:
    needs: [ deploy_server_on_heroku, build_qa_android_tests_apk ]
    runs-on: [ ubuntu-latest ]
    timeout-minutes: 60
    env:
      AVD_API_LEVEL: 34
      AVD_ARCH: x86_64
    steps:
      - name: Enable KVM group perms
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm

      - name: Checkout Android source
        uses: actions/checkout@v4

      - name: Cache AVD
        uses: actions/cache@v4
        id: avd-cache
        with:
          path: |
            ~/.android/avd/*
            ~/.android/adb*
          key: ${{ runner.os }}-avd-${{ env.AVD_API_LEVEL }}-${{ env.AVD_ARCH }}

      - name: create AVD and generate snapshot for caching
        if: steps.avd-cache.outputs.cache-hit != 'true'
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: ${{ env.AVD_API_LEVEL }}
          arch: ${{ env.AVD_ARCH }}
          force-avd-creation: false
          emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
          disable-animations: false
          disk-size: 8G
          script: echo "Generated AVD snapshot for caching."

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: Create build directory
        run: mkdir app/build

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: test-artifacts
          path: app/build/outputs/apk

      - name: QA Android Tests
        id: run-instrumented-tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: ${{ env.AVD_API_LEVEL }}
          arch: ${{ env.AVD_ARCH }}
          force-avd-creation: false
          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
          disable-animations: true
          disk-size: 8G
          script: |
            adb root
            mkdir -p app/build/outputs/test-artifacts
            adb install app/build/outputs/apk/qa/debug/app-qa-debug.apk
            adb install app/build/outputs/apk/androidTest/qa/debug/app-qa-debug-androidTest.apk
            adb shell am instrument -w -e filter org.simple.clinic.benchmark.SelectBenchmarkTests -e benchmark_app_performance false  org.simple.clinic.qa.debug.test/org.simple.clinic.AndroidTestJUnitRunner >app/build/outputs/test-artifacts/logs.txt 2>app/build/outputs/test-artifacts/logs.txt
            cat app/build/outputs/test-artifacts/logs.txt
            adb pull /storage/emulated/0/Android/data/org.simple.clinic.qa.debug/ app/build/outputs/test-artifacts/ || true
            adb uninstall org.simple.clinic.qa.debug
            adb uninstall org.simple.clinic.qa.debug.test
            ! grep -q "FAILURES\!\!\!" app/build/outputs/test-artifacts/logs.txt

      - name: Upload failed test artifacts
        if: always() && steps.run-instrumented-tests.outcome != 'success'
        uses: actions/upload-artifact@v4
        with:
          name: failed-test-artifacts
          path: app/build/outputs/test-artifacts

      - name: Teardown Heroku instance
        if: always() && (steps.start-simple-server.outcome != 'success' || contains(github.ref, '/release/'))
        run: |
          heroku apps:destroy \
          --app=${{ needs.deploy_server_on_heroku.outputs.heroku_app_name }} \
          --confirm=${{ needs.deploy_server_on_heroku.outputs.heroku_app_name }}

  mobius_migration_tests:
    runs-on: [ ubuntu-latest ]
    steps:
      - uses: actions/checkout@v4

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: Mobius Migration Tests
        run: ./gradlew :mobius-migration:test

  verify_release_bundles:
    runs-on: [ ubuntu-latest ]
    strategy:
      matrix:
        buildType: [ 'Production' ]
    steps:
      - uses: actions/checkout@v4

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: Build ${{ matrix.buildType }} Release bundle
        run: |
          ./gradlew \
          -PrunProguard=true \
          -PdefaultProguardFile=proguard-android.txt \
          bundle${{ matrix.buildType }}Release

  verify_room_schemas:
    runs-on: [ ubuntu-latest ]
    steps:
      - name: Checkout master
        uses: actions/checkout@v4
        with:
          ref: master
          path: master

      - name: Checkout current
        uses: actions/checkout@v4
        with:
          path: current

      - name: Verify existing Room schemas are not modified
        run: current/.github/scripts/compare_directory_contents.sh master/app/schemas/org.simple.clinic.AppDatabase current/app/schemas/org.simple.clinic.AppDatabase

  verify_pr_description:
    runs-on: [ ubuntu-latest ]
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
      - name: Verify PR description
        run: kotlinc -script ".github/scripts/PrCommentCheck.main.kts" "${{ github.event.pull_request.body }}"

  verify_changelog_updated:
    runs-on: [ ubuntu-latest ]
    if: github.event_name == 'pull_request'
    steps:
      - name: Checkout master
        uses: actions/checkout@v4
        with:
          ref: master
          path: master

      - name: Checkout current
        uses: actions/checkout@v4
        with:
          path: current

      - name: Verify CHANGELOG was updated
        run: current/.github/scripts/verify_file_was_changed.sh master/CHANGELOG.md current/CHANGELOG.md

  fix_strings_ellipsise:
    runs-on: [ ubuntu-latest ]
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{github.event.pull_request.head.ref}}
          fetch-depth: 0
          token: ${{ secrets.SERVICES_ACCESS_TOKEN }}

      - name: Fix ellipsise in strings
        run: .github/scripts/strings_reader.py

      - name: Commit and push
        env:
          USER_NAME: ${{ secrets.USER_NAME }}
          USER_EMAIL: ${{ secrets.USER_EMAIL }}
        run: .github/scripts/commit_and_push_string_fixes.sh


Auto Request Review

name: Auto Request Review

on:
  pull_request:
    types: [ opened, ready_for_review, reopened ]

jobs:
  auto-request-review:
    name: Auto Request Review
    if: |
      github.event.label.name != 'work in progress' &&
      github.event.pull_request.requested_reviewers[0] == null &&
      github.event.pull_request.requested_teams[0] == null &&
      github.event.pull_request.draft == false
    runs-on: [ ubuntu-latest ]
    steps:
      - name: Request Android Devs for review
        uses: necojackarc/auto-request-review@v0.13.0
        with:
          token: ${{ secrets.SERVICES_ACCESS_TOKEN }}
          config: .github/reviewers.yml
      - name: Enable auto merge
        run: gh pr merge --auto -d -s "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}


name: Cleanup dangling Heroku review instances

on:
  push:
    branches:
      - master
  # Also run a nightly (at 12 AM IST) workflow to clean up dangling instances for redundancy
  schedule:
    - cron: '30 18 * * 1-5'
  workflow_dispatch:

jobs:
  teardown_heroku_instance:
    runs-on: [ ubuntu-latest ]
    env:
      HEROKU_API_KEY: ${{ secrets.SERVICES_HEROKU_API_KEY }}
    steps:
      - name: Checkout Android app source
        uses: actions/checkout@v4

      - name: Teardown dangling Heroku instances
        run: .github/scripts/teardown_heroku_instances.py
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


Performance Regression Test Suite

name: Performance Regression Test Suite

on:
  push:
    branches:
      - master
  workflow_dispatch:

concurrency:
  group: benchmark-suite
  cancel-in-progress: true

jobs:
  benchmark_suite:
    runs-on: [ ubuntu-latest ]
    steps:
      - name: Enable KVM group perms
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm

      - name: Checkout Android source
        uses: actions/checkout@v4

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: Benchmark tests
        id: run-benchmark-tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 30
          arch: x86_64
          disable-animations: true
          disk-size: 8G
          script: |
            adb root
            ./gradlew --no-configuration-cache -PmanifestEndpoint=https://api-sandbox.simple.org/api/ installQaDebug installQaDebugAndroidTest lockClocks
            adb shell am instrument -w -e filter org.simple.clinic.benchmark.SelectBenchmarkTests -e benchmark_app_performance true -e dd_client_token ${{ secrets.DD_PERF_CLIENT_TOKEN }} -e dd_application_id ${{ secrets.DD_PERF_APPLICATION_ID }} org.simple.clinic.qa.debug.test/org.simple.clinic.AndroidTestJUnitRunner
            adb uninstall org.simple.clinic.qa.debug
            adb uninstall org.simple.clinic.qa.debug.test


on:
  # Run the workflow every day at 12 PM IST (6:30 AM UTC)
  schedule:
    - cron: '30 6 * * *'

jobs:
  run:
    runs-on: [ ubuntu-latest ]
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: '0'
          token: ${{ secrets.SERVICES_ACCESS_TOKEN }}

      - name: Cut release branch
        run: kotlinc -script ".github/scripts/CutReleaseBranch.main.kts"
        env:
          CUT_RELEASE_BRANCH: ${{ secrets.CUT_RELEASE_BRANCH_ENABLED }}


# Trigger Bitrise to make a demo and prod release on `release/*` branch creation

name: trigger_release

on: create

concurrency:
  group: demo-release-${{ github.ref }}
  cancel-in-progress: true

jobs:
  qa_lint:
    runs-on: [ ubuntu-latest ]
    if: ${{ contains(github.ref, 'refs/heads/release/') }}
    steps:
      - uses: actions/checkout@v4

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: QA Lint
        run: ./gradlew --build-cache --no-daemon lintQaDebug

  qa_unit_tests:
    runs-on: [ ubuntu-latest ]
    if: ${{ contains(github.ref, 'refs/heads/release/') }}
    steps:
      - uses: actions/checkout@v4

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: QA Unit Tests
        run: ./gradlew --build-cache --no-daemon testQaDebugUnitTest

  # reactivecircus/android-emulator-runner@v2 requires MacOS to run on
  # https://github.com/ReactiveCircus/android-emulator-runner
  qa_android_tests:
    runs-on: macos-14
    if: ${{ contains(github.ref, 'refs/heads/release/') }}
    timeout-minutes: 60
    env:
      AVD_API_LEVEL: 30
      AVD_ARCH: x86_64
      HEROKU_API_KEY: ${{ secrets.SERVICES_HEROKU_API_KEY }}
    steps:
      - name: Checkout Android source
        uses: actions/checkout@v4
        with:
          path: android-app

      - name: Checkout server app source
        uses: actions/checkout@v4
        with:
          repository: simpledotorg/simple-server
          fetch-depth: 0 # Full clone needed for Heroku deploys (https://devcenter.heroku.com/changelog-items/775)
          path: server-app

      - name: Create node dependency hash
        id: create_node_modules_hash
        run: android-app/.github/scripts/compute_node_dependency_hash.sh

      - name: Cache node modules
        id: node-dependency-cache
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            package-lock.json
          key: ${{ runner.os }}-npm-${{ steps.create_node_modules_hash.outputs.node_dep_hash }}

      - name: Install Heroku CLI
        if: steps.node-dependency-cache.outputs.cache-hit != 'true'
        run: npm install heroku

      - name: Link Heroku CLI globally
        run: npm link heroku

      - name: Install Heroku Buildpacks registry
        run: heroku plugins:install buildpack-registry

      - name: Link Heroku CLI globally
        run: heroku plugins:install buildpacks

      - name: Cache AVD
        uses: actions/cache@v4
        id: avd-cache
        with:
          path: |
            ~/.android/avd/*
            ~/.android/adb*
          key: ${{ runner.os }}-avd-${{ env.AVD_API_LEVEL }}-${{ env.AVD_ARCH }}

      - name: create AVD and generate snapshot for caching
        if: steps.avd-cache.outputs.cache-hit != 'true'
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: ${{ env.AVD_API_LEVEL }}
          arch: ${{ env.AVD_ARCH }}
          force-avd-creation: false
          emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
          disable-animations: false
          disk-size: 8G
          script: echo "Generated AVD snapshot for caching."

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: Generate server app name
        id: generate-server-app-name
        run: android-app/.github/scripts/generate_heroku_app_name.sh ${{ github.ref }}

      - name: Deploy the server on Heroku
        id: start-simple-server
        run: |
          android-app/.github/scripts/setup_heroku_instance.sh \
          ${{ steps.generate-server-app-name.outputs.heroku_app_name }} \
          ${{ env.HEROKU_API_KEY }} \
          master \
          server-app \
          android-app \
          ${{ secrets.HEROKU_SECRET_PROPERTIES }}

      - name: QA Android Tests
        id: run-instrumented-tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: ${{ env.AVD_API_LEVEL }}
          arch: ${{ env.AVD_ARCH }}
          force-avd-creation: false
          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
          disable-animations: true
          disk-size: 8G
          script: |
            adb root
            mkdir -p android-app/app/build/outputs/test-artifacts
            android-app/gradlew -p android-app --build-cache --no-daemon -PmanifestEndpoint=${{ steps.start-simple-server.outputs.heroku_app_url }}api/ installQaDebug installQaDebugAndroidTest
            adb shell am instrument -w -e filter org.simple.clinic.benchmark.SelectBenchmarkTests -e benchmark_app_performance false  org.simple.clinic.qa.debug.test/org.simple.clinic.AndroidTestJUnitRunner >android-app/app/build/outputs/test-artifacts/logs.txt 2>android-app/app/build/outputs/test-artifacts/logs.txt
            cat android-app/app/build/outputs/test-artifacts/logs.txt
            adb pull /storage/emulated/0/Android/data/org.simple.clinic.qa.debug/ android-app/app/build/outputs/test-artifacts/ || true
            adb uninstall org.simple.clinic.qa.debug
            adb uninstall org.simple.clinic.qa.debug.test
            ! grep -q "FAILURES\!\!\!" android-app/app/build/outputs/test-artifacts/logs.txt

      - name: Upload failed test artifacts
        if: always() && steps.run-instrumented-tests.outcome != 'success'
        uses: actions/upload-artifact@v4
        with:
          name: test-artifacts
          path: android-app/app/build/outputs/test-artifacts

      - name: Teardown Heroku instance
        if: always() && (steps.start-simple-server.outcome != 'success' || contains(github.ref, '/release/'))
        run: |
          heroku apps:destroy \
          --app=${{ steps.generate-server-app-name.outputs.heroku_app_name }} \
          --confirm=${{ steps.generate-server-app-name.outputs.heroku_app_name }}

  verify_release_bundles:
    runs-on: [ ubuntu-latest ]
    if: ${{ contains(github.ref, 'refs/heads/release/') }}
    strategy:
      matrix:
        buildType: [ 'Production' ]
    steps:
      - uses: actions/checkout@v4

      - name: set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: 17
          cache: 'gradle'

      - name: Build ${{ matrix.buildType }} Release bundle
        run: |
          ./gradlew \
          -PrunProguard=true \
          -PdefaultProguardFile=proguard-android.txt \
          bundle${{ matrix.buildType }}Release

  verify_room_schemas:
    runs-on: [ ubuntu-latest ]
    if: ${{ contains(github.ref, 'refs/heads/release/') }}
    steps:
      - name: Checkout master
        uses: actions/checkout@v4
        with:
          ref: master
          path: master

      - name: Checkout current
        uses: actions/checkout@v4
        with:
          path: current

      - name: Verify existing Room schemas are not modified
        run: current/.github/scripts/compare_directory_contents.sh master/app/schemas/org.simple.clinic.AppDatabase current/app/schemas/org.simple.clinic.AppDatabase

  demo_release:
    needs: [ qa_lint, qa_unit_tests, qa_android_tests, verify_release_bundles, verify_room_schemas ]
    runs-on: [ ubuntu-latest ]
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{github.ref}}

      - name: Trigger Demo Release
        run: kotlinc -script ".github/scripts/TriggerBitriseRelease.main.kts" "${{ github.ref }}" "${{ secrets.BITRISE_ACCESS_TOKEN }}" "${{ secrets.BITRISE_APP_SLUG }}" "${{ secrets.BITRISE_DEMO_WORKFLOW }}"

  prod_release:
    needs: [ qa_lint, qa_unit_tests, qa_android_tests, verify_release_bundles, verify_room_schemas ]
    runs-on: [ ubuntu-latest ]
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{github.ref}}

      - name: Trigger Prod Release
        run: kotlinc -script ".github/scripts/TriggerBitriseRelease.main.kts" "${{ github.ref }}" "${{ secrets.BITRISE_ACCESS_TOKEN }}" "${{ secrets.BITRISE_APP_SLUG }}" "${{ secrets.BITRISE_PROD_WORKFLOW }}"


Pipeline

name: Pipeline

on: [push, pull_request]

jobs:
  build:
    name: "Build, Test and Publish"
    runs-on: ubuntu-latest
    env:
      TERM: xterm-256color
      GPG_KEY_ID: 6D76AD03 # Run `gpg -K` to get this, take last eight characters
      GRADLE_OPTS: "-Dorg.gradle.internal.launcher.welcomeMessageEnabled=false"

    permissions:
      contents: write # Required to be able to publish releases, see https://docs.github.com/en/rest/reference/permissions-required-for-github-apps#permission-on-contents

    steps:
      - name: Check out code
        uses: actions/checkout@v4.1.7
        with:
          fetch-depth: 0

      - name: Validate Gradle wrapper
        uses: gradle/wrapper-validation-action@v3.4.2

      - name: Set up JDK
        uses: actions/setup-java@v4.2.1
        with:
          java-version: 17
          distribution: temurin

      - name: Setup Gradle
        uses: gradle/gradle-build-action@v3.4.2

      - name: Build
        run: ./gradlew assemble

      - name: Check
        run: ./gradlew check

      - name: Assemble release
        run: ./gradlew assembleRelease
        env:
          GPG_KEY_RING: ${{ secrets.GPG_KEY_RING }} # Run `gpg --export-secret-keys "<key user name goes here>" | base64` to get this
          GPG_KEY_PASSPHRASE: ${{ secrets.GPG_KEY_PASSPHRASE }}
        if: github.repository == 'charleskorn/kaml' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'

      - name: Publish snapshot
        run: ./gradlew publishSnapshot
        env:
          OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
          OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
          GPG_KEY_RING: ${{ secrets.GPG_KEY_RING }} # Run `gpg --export-secret-keys "<key user name goes here>" | base64` to get this
          GPG_KEY_PASSPHRASE: ${{ secrets.GPG_KEY_PASSPHRASE }}
        if: github.repository == 'charleskorn/kaml' && github.ref == 'refs/heads/main' && github.event_name == 'push'

      - name: Publish release
        run: ./gradlew publishRelease
        env:
          OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
          OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
          GPG_KEY_RING: ${{ secrets.GPG_KEY_RING }} # Run `gpg --export-secret-keys "<key user name goes here>" | base64` to get this
          GPG_KEY_PASSPHRASE: ${{ secrets.GPG_KEY_PASSPHRASE }}
        if: github.repository == 'charleskorn/kaml' && startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'

      - name: Create GitHub release
        uses: softprops/action-gh-release@v2.0.6
        with:
          files: build/release/*
          fail_on_unmatched_files: true
          draft: true # Update draft release with matching tag, if there is one.
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        if: github.repository == 'charleskorn/kaml' && startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'


Kotlin CI

# This workflow will build a package using Gradle and then publish it to maven

name: Kotlin CI

on:
  push:
    branches:
      - '**' # We want to run this on all branch pushes
    tags-ignore:
      - '**' # We don't want this to run on tags pushes
  pull_request:
  release:
    types: [ published ]

jobs:
  build:
    name: Build Kord
    runs-on: ubuntu-latest
    if: |
      !contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.pull_request.title, '[ci skip]')
      && !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'kordlib/kord')
    env:
      KORD_TEST_TOKEN: ${{ secrets.KORD_TEST_TOKEN }}
    steps:
      - uses: actions/checkout@v4
      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 11
      - name: Set up Gradle
        uses: gradle/actions/setup-gradle@v3
      - name: Build with Gradle
        run: ./gradlew --stacktrace --info build
  release:
    name: Publish artifacts
    runs-on: ubuntu-latest
    needs: build
    if: |
      !contains(github.event.head_commit.message, '[publish skip]') && github.event_name != 'pull_request' &&  github.ref != 'refs/heads/master'
    env:
      KORD_TEST_TOKEN: ${{ secrets.KORD_TEST_TOKEN }}
      NEXUS_USER: ${{ secrets.NEXUS_USER }}
      NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
      SIGNING_KEY: ${{ secrets.signingKey }}
      SIGNING_PASSWORD: ${{ secrets.signingPassword }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 11
      - name: Set up Gradle
        uses: gradle/actions/setup-gradle@v3
      - name: Build with Gradle
        run: ./gradlew --stacktrace --info build
      - name: Publish with Gradle
        run: ./gradlew --no-parallel -x test publish


Docs

# This workflow will build and deploy docs to GitHub Pages whenever something is pushed to the main branch

name: Docs

on:
  push:
    branches:
      - main

permissions:
  contents: write

jobs:
  docs:
    name: Build and deploy docs
    runs-on: ubuntu-latest
    concurrency: # Allow one concurrent deployment
      group: pages
      cancel-in-progress: true
    steps:
      - uses: actions/checkout@v4
      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 11
      - name: Set up Gradle
        uses: gradle/actions/setup-gradle@v3
      - name: Build docs with Gradle
        run: ./gradlew --stacktrace --info dokkaHtmlMultiModule
      - name: Deploy docs to GitHub Pages
        uses: JamesIves/github-pages-deploy-action@v4
        with:
          folder: build/dokka/htmlMultiModule
          branch: gh-pages
          git-config-name: GitHub Actions
          git-config-email: actions@github.com
          commit-message: Update docs


Gradle Wrapper Validation

# This workflow will validate the checksums of Gradle Wrapper JAR files

name: Gradle Wrapper Validation

on: [ push, pull_request ]

jobs:
  validation:
    name: Validate Gradle Wrapper
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Validate Gradle Wrapper
        uses: gradle/actions/wrapper-validation@v3


Check PR and notify

name: Check PR and notify

on:
  pull_request:

jobs:
  notify:
    if: ${{ !startsWith(github.head_ref, 'l10n_version/') }}
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: Notify to Telegram
        uses: appleboy/telegram-action@master
        with:
          to: ${{secrets.TELEGRAM_TO}}
          token: ${{secrets.TELEGRAM_TOKEN}}
          disable_web_page_preview: true
          message: |
            ${{ github.event.pull_request.html_url }}

            Title : ${{github.event.pull_request.title}}
            Author : @${{github.actor}}


Android CI (release)

name: Android CI (release)

on:
  push:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Setup Ruby v3
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.0.2
          bundler-cache: true

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Set Build Number
        env:
          APPCENTER_NUMBER: 147
        run: |
          echo "BUILD_NUMBER_NEW=$(($APPCENTER_NUMBER+$GITHUB_RUN_NUMBER))" >> $GITHUB_ENV

      - name: Fastlane Build
        uses: maierj/fastlane-action@v3.0.0
        with:
          lane: 'app_build_release'
          options: '{ "app_name": "Unstoppable-Release", "api_token": "${{secrets.API_TOKEN_RELEASE}}" }'
        env:
          BUILD_NUMBER: ${{env.BUILD_NUMBER_NEW}}