Release new version #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release new version | |
| on: | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: write | |
| packages: write | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| USERNAME: sshcrack | |
| VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg | |
| FEED_URL: https://nuget.pkg.github.com/sshcrack/index.json | |
| VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/sshcrack/index.json,readwrite" | |
| jobs: | |
| # Job to extract version and do initial validation | |
| prepare: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.VERSION }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: true | |
| - name: Extract version from CMakeLists.txt | |
| id: version | |
| run: | | |
| VERSION=$(grep -Po 'project\([^ ]+ VERSION \K[0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt) | |
| echo "VERSION=$VERSION" >> $GITHUB_OUTPUT | |
| - name: Check if tag or release exists | |
| id: check_tag | |
| run: | | |
| TAG="v${{ steps.version.outputs.VERSION }}" | |
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| echo "Tag $TAG already exists!" >&2 | |
| exit 1 | |
| fi | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| echo "Release $TAG already exists!" >&2 | |
| exit 1 | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - uses: pnpm/action-setup@v4 | |
| name: Install pnpm | |
| with: | |
| version: 10 | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| cache-dependency-path: "react-native/pnpm-lock.yaml" | |
| - name: Install React Native dependencies | |
| working-directory: react-native | |
| run: pnpm install | |
| - name: Build React Native web version | |
| working-directory: react-native | |
| run: pnpm exec expo export --platform web | |
| - name: Upload web build artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: web-build | |
| path: react-native/dist/ | |
| # Build matrix for led-matrix and desktop app | |
| build: | |
| runs-on: ${{ matrix.os }} | |
| needs: prepare | |
| strategy: | |
| fail-fast: true | |
| matrix: | |
| include: | |
| - name: "led-matrix" | |
| os: ubuntu-latest | |
| preset: "cross-compile" | |
| artifact-name: "led-matrix-build" | |
| artifact-path: | | |
| build/led-matrix-${{ needs.prepare.outputs.version }}-arm64.tar.gz | |
| requires-toolchain: true | |
| - name: "desktop-linux" | |
| os: ubuntu-latest | |
| preset: "desktop-linux" | |
| artifact-name: "desktop-linux-build" | |
| artifact-path: desktop_build/led-matrix-desktop-${{ needs.prepare.outputs.version }}-Linux.tar.gz | |
| requires-toolchain: false | |
| - name: "desktop-windows" | |
| os: windows-latest | |
| preset: "desktop-windows" | |
| artifact-name: "desktop-windows-build" | |
| artifact-path: | | |
| desktop_build/led-matrix-desktop-${{ needs.prepare.outputs.version }}-win64.zip | |
| desktop_build/led-matrix-desktop-${{ needs.prepare.outputs.version }}-win64.exe | |
| requires-toolchain: false | |
| env: | |
| BUILD_TYPE: RelWithDebInfo | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: recursive | |
| - uses: awalsh128/cache-apt-pkgs-action@latest | |
| if: runner.os != 'Windows' | |
| with: | |
| packages: python3-jinja2 pkg-config autoconf automake libtool python3 linux-libc-dev curl libltdl-dev libx11-dev libxft-dev libxext-dev libwayland-dev libxkbcommon-dev libegl1-mesa-dev libibus-1.0-dev mono-complete libxrandr-dev libxrandr2 wayland-protocols extra-cmake-modules xorg-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev libgtk-3-dev libayatana-appindicator3-dev | |
| version: 1.0 | |
| - name: Update certificates | |
| if: runner.os != 'Windows' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install --reinstall ca-certificates | |
| sudo update-ca-certificates | |
| sudo cert-sync /etc/ssl/certs/ca-certificates.crt | |
| cert-sync --user /etc/ssl/certs/ca-certificates.crt | |
| - uses: lukka/get-cmake@latest | |
| with: | |
| cmakeVersion: "~3.25.0" | |
| - name: Setup anew (or from cache) vcpkg (and does not build any package) | |
| uses: lukka/run-vcpkg@v11 | |
| with: | |
| vcpkgConfigurationJsonGlob: "vcpkg-configuration.json" | |
| - name: Add NuGet sources (Linux) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| env: | |
| VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg | |
| USERNAME: sshcrack | |
| FEED_URL: https://nuget.pkg.github.com/sshcrack/index.json | |
| run: | | |
| mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ | |
| sources add \ | |
| -Source "${{ env.FEED_URL }}" \ | |
| -StorePasswordInClearText \ | |
| -Name GitHubPackages \ | |
| -UserName "${{ env.USERNAME }}" \ | |
| -Password "${{ secrets.GH_PACKAGES_TOKEN }}" | |
| mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ | |
| setapikey "${{ secrets.GH_PACKAGES_TOKEN }}" \ | |
| -Source "${{ env.FEED_URL }}" | |
| - name: Add NuGet sources (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg.exe | |
| USERNAME: sshcrack | |
| FEED_URL: https://nuget.pkg.github.com/sshcrack/index.json | |
| run: | | |
| .$(${{ env.VCPKG_EXE }} fetch nuget) ` | |
| sources add ` | |
| -Source "${{ env.FEED_URL }}" ` | |
| -StorePasswordInClearText ` | |
| -Name GitHubPackages ` | |
| -UserName "${{ env.USERNAME }}" ` | |
| -Password "${{ secrets.GH_PACKAGES_TOKEN }}" | |
| .$(${{ env.VCPKG_EXE }} fetch nuget) ` | |
| setapikey "${{ secrets.GH_PACKAGES_TOKEN }}" ` | |
| -Source "${{ env.FEED_URL }}" | |
| - name: Download web build artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: web-build | |
| path: react-native/dist/ | |
| - name: Cache cross-compile toolchain | |
| if: matrix.requires-toolchain | |
| id: cache-toolchain | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ runner.temp }}/cross-compile | |
| key: cross-compile-toolchain-v0.0.1-beta | |
| - name: Download and extract cross-compile toolchain | |
| if: matrix.requires-toolchain && steps.cache-toolchain.outputs.cache-hit != 'true' | |
| run: | | |
| mkdir -p ${{ runner.temp }}/cross-compile | |
| cd ${{ runner.temp }}/cross-compile | |
| wget -O cross-compile.tar.xz "https://github.com/sshcrack/led-matrix/releases/download/v0.0.1-beta/cross-compile.tar.xz" | |
| tar -xvf cross-compile.tar.xz | |
| rm cross-compile.tar.xz | |
| - name: Build ${{ matrix.name }} | |
| uses: lukka/run-cmake@v10 | |
| with: | |
| configurePreset: ${{ matrix.preset }} | |
| configurePresetAdditionalArgs: "['-DSKIP_WEB_BUILD=ON']" | |
| buildPreset: ${{ matrix.preset }} | |
| buildPresetAdditionalArgs: ${{ runner.os == 'Windows' && '[''--target'', ''package'', ''--config'', ''RelWithDebInfo'']' || '[''--target'', ''package'']' }} | |
| env: | |
| VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/sshcrack/index.json,readwrite" | |
| CROSS_COMPILE_ROOT: ${{ matrix.requires-toolchain && runner.temp }}/cross-compile | |
| - name: Upload ${{ matrix.name }} artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.artifact-name }} | |
| path: ${{ matrix.artifact-path }} | |
| # Build React Native APK | |
| build-react-native: | |
| runs-on: ubuntu-latest | |
| needs: prepare | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: true | |
| - uses: pnpm/action-setup@v4 | |
| name: Install pnpm | |
| with: | |
| version: 10 | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| cache-dependency-path: "react-native/pnpm-lock.yaml" | |
| - name: Install dependencies | |
| working-directory: react-native | |
| run: pnpm install | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '17' | |
| # Install Android SDK build-tools for apksigner | |
| - name: Setup Android SDK | |
| uses: android-actions/setup-android@v3 | |
| - name: Install Android SDK build-tools | |
| run: | | |
| echo "y" | sdkmanager --install "build-tools;34.0.0" "platform-tools" | |
| echo "$ANDROID_HOME/build-tools/34.0.0" >> $GITHUB_PATH | |
| - name: 🏗 Setup EAS | |
| uses: expo/expo-github-action@v8 | |
| with: | |
| eas-version: latest | |
| packager: pnpm | |
| token: ${{ secrets.EXPO_TOKEN }} | |
| # Cache bundletool to avoid downloading it every time | |
| - name: Cache bundletool | |
| id: cache-bundletool | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ runner.temp }}/bundletool | |
| key: bundletool-1.18.1 | |
| - name: Install bundletool | |
| if: steps.cache-bundletool.outputs.cache-hit != 'true' | |
| run: | | |
| mkdir -p ${{ runner.temp }}/bundletool | |
| curl -L -o ${{ runner.temp }}/bundletool/bundletool.jar https://github.com/google/bundletool/releases/download/1.18.1/bundletool-all-1.18.1.jar | |
| # Decode release keystore from GitHub Secrets | |
| - name: Decode release keystore | |
| run: | | |
| mkdir -p ${{ runner.temp }}/keystore | |
| echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 -d > ${{ runner.temp }}/keystore/release.keystore | |
| - name: Build React Native AAB | |
| working-directory: react-native | |
| env: | |
| EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} | |
| run: | | |
| pnpx expo prebuild -p android | |
| eas build -p android --local --non-interactive --output ../app-v${{ needs.prepare.outputs.version }}.aab | |
| - name: Convert AAB to APK with release signing | |
| run: | | |
| # Convert AAB to APKS (signed with release key) | |
| java -jar ${{ runner.temp }}/bundletool/bundletool.jar build-apks \ | |
| --bundle=app-v${{ needs.prepare.outputs.version }}.aab \ | |
| --output=${{ runner.temp }}/app.apks \ | |
| --mode=universal \ | |
| --ks=${{ runner.temp }}/keystore/release.keystore \ | |
| --ks-pass=pass:"${{ secrets.KEYSTORE_PASSWORD }}" \ | |
| --ks-key-alias="${{ secrets.KEY_ALIAS }}" \ | |
| --key-pass=pass:"${{ secrets.KEY_PASSWORD }}" | |
| # Extract the universal APK | |
| unzip -p ${{ runner.temp }}/app.apks universal.apk > app-v${{ needs.prepare.outputs.version }}.apk | |
| # Verify APK signature with apksigner (now guaranteed to be available) | |
| echo "Verifying APK signature with apksigner..." | |
| apksigner verify --verbose app-v${{ needs.prepare.outputs.version }}.apk | |
| # Show detailed certificate information | |
| echo "APK certificate information:" | |
| apksigner verify --print-certs app-v${{ needs.prepare.outputs.version }}.apk | |
| # Clean up sensitive keystore file | |
| - name: Clean up keystore | |
| if: always() | |
| run: | | |
| rm -f ${{ runner.temp }}/keystore/release.keystore | |
| - name: Upload React Native APK | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: react-native-apk | |
| path: app-v${{ needs.prepare.outputs.version }}.apk | |
| # Create release and upload all artifacts | |
| create-release: | |
| runs-on: ubuntu-latest | |
| needs: [prepare, build, build-react-native] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: true | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Create release | |
| id: create_release | |
| run: | | |
| TAG="v${{ needs.prepare.outputs.version }}" | |
| # Get previous tag (if any) | |
| PREV_TAG=$(git tag --sort=-creatordate | grep -v "$TAG" | head -n1 || true) | |
| if [ -n "$PREV_TAG" ]; then | |
| # Fetch merged PRs between previous and current tag, with author | |
| PRS=$(gh pr list --state merged --search "merged:>=$(git log -1 --format=%aI $PREV_TAG)" --json number,title,author,mergedAt,mergeCommit --jq '.[] | select(.mergeCommit != null) | "- #\(.number) (\(.title)) - (@\(.author.login))"') | |
| FULL_CHANGELOG_LINK="[**Full Changelog**](https://github.com/${{ github.repository }}/compare/$PREV_TAG...$TAG)" | |
| if [ -n "$PRS" ]; then | |
| # Use template with PRs | |
| CHANGELOG=$(cat .github/changelog-template.md | sed "s|{{PRS}}|$PRS|g" | sed "s|{{FULL_CHANGELOG_LINK}}|$FULL_CHANGELOG_LINK|g") | |
| else | |
| # Use template for no PRs | |
| CHANGELOG=$(cat .github/changelog-no-prs-template.md | sed "s|{{FULL_CHANGELOG_LINK}}|$FULL_CHANGELOG_LINK|g") | |
| fi | |
| else | |
| # Fallback for edge case (no previous tag but not initial release) | |
| CHANGELOG="Release v${{ needs.prepare.outputs.version }}" | |
| fi | |
| gh release create "$TAG" \ | |
| --title "$TAG" \ | |
| --notes "$CHANGELOG" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload all artifacts to release | |
| run: | | |
| TAG="v${{ needs.prepare.outputs.version }}" | |
| # Upload led-matrix (cross-compile) | |
| gh release upload "$TAG" artifacts/led-matrix-build/led-matrix-${{ needs.prepare.outputs.version }}-arm64.tar.gz | |
| # Upload desktop-linux | |
| gh release upload "$TAG" artifacts/desktop-linux-build/led-matrix-desktop-${{ needs.prepare.outputs.version }}-Linux.tar.gz | |
| # Upload desktop-windows | |
| gh release upload "$TAG" artifacts/desktop-windows-build/led-matrix-desktop-${{ needs.prepare.outputs.version }}-win64.zip | |
| gh release upload "$TAG" artifacts/desktop-windows-build/led-matrix-desktop-${{ needs.prepare.outputs.version }}-win64.exe | |
| # Upload React Native APK | |
| gh release upload "$TAG" artifacts/react-native-apk/app-v${{ needs.prepare.outputs.version }}.apk | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |