name: Release on: push: tags: - 'v*' concurrency: group: release-${{ github.ref }} cancel-in-progress: false jobs: quality-gate: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: submodules: true - name: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.5 - name: Cache dependencies uses: actions/cache@v4 with: path: | ~/.bun/install/cache node_modules stats/node_modules vendor/subminer-yomitan/node_modules key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }} restore-keys: | ${{ runner.os }}-bun- - name: Install dependencies run: | bun install --frozen-lockfile cd stats && bun install --frozen-lockfile - name: Lint stats (formatting) run: bun run lint:stats - name: Build (TypeScript check) run: bun run typecheck - name: Test suite (source) run: bun run test:fast - name: Launcher smoke suite (source) run: bun run test:launcher:smoke:src - name: Upload launcher smoke artifacts (on failure) if: failure() uses: actions/upload-artifact@v4 with: name: launcher-smoke path: .tmp/launcher-smoke/** if-no-files-found: ignore - name: Build (bundle) run: bun run build - name: Immersion SQLite verification run: bun run test:immersion:sqlite:dist - name: Dist smoke suite run: bun run test:smoke:dist build-linux: needs: [quality-gate] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: submodules: true - name: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.5 - name: Cache dependencies uses: actions/cache@v4 with: path: | ~/.bun/install/cache node_modules stats/node_modules vendor/texthooker-ui/node_modules vendor/subminer-yomitan/node_modules key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }} restore-keys: | ${{ runner.os }}-bun- - name: Install dependencies run: | bun install --frozen-lockfile cd stats && bun install --frozen-lockfile - name: Build texthooker-ui run: | cd vendor/texthooker-ui bun install bun run build - name: Build AppImage run: bun run build:appimage - name: Build unversioned AppImage run: | shopt -s nullglob appimages=(release/SubMiner-*.AppImage) if [ "${#appimages[@]}" -eq 0 ]; then echo "No versioned AppImage found to create unversioned artifact." ls -la release exit 1 fi cp "${appimages[0]}" release/SubMiner.AppImage - name: Upload AppImage artifact uses: actions/upload-artifact@v4 with: name: appimage path: release/*.AppImage build-macos: needs: [quality-gate] runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v4 with: submodules: true - name: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.5 - name: Cache dependencies uses: actions/cache@v4 with: path: | ~/.bun/install/cache node_modules stats/node_modules vendor/texthooker-ui/node_modules vendor/subminer-yomitan/node_modules key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }} restore-keys: | ${{ runner.os }}-bun- - name: Validate macOS signing/notarization secrets run: | missing=0 for name in CSC_LINK CSC_KEY_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do if [ -z "${!name}" ]; then echo "Missing required secret: $name" missing=1 fi done if [ "$missing" -ne 0 ]; then echo "Set all required macOS signing/notarization secrets and rerun." exit 1 fi env: CSC_LINK: ${{ secrets.CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - name: Install dependencies run: | bun install --frozen-lockfile cd stats && bun install --frozen-lockfile - name: Build texthooker-ui run: | cd vendor/texthooker-ui bun install bun run build - name: Build signed + notarized macOS artifacts run: bun run build:mac env: CSC_LINK: ${{ secrets.CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - name: Upload macOS artifacts uses: actions/upload-artifact@v4 with: name: macos path: | release/*.dmg release/*.zip build-windows: needs: [quality-gate] runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@v4 with: submodules: true - name: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.5 - name: Cache dependencies uses: actions/cache@v4 with: path: | ~/.bun/install/cache node_modules stats/node_modules vendor/texthooker-ui/node_modules vendor/subminer-yomitan/node_modules key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }} restore-keys: | ${{ runner.os }}-bun- - name: Install dependencies run: | bun install --frozen-lockfile cd stats && bun install --frozen-lockfile - name: Build texthooker-ui shell: powershell run: | Set-Location vendor/texthooker-ui bun install bun run build - name: Build unsigned Windows artifacts run: bun run build:win:unsigned - name: Upload Windows artifacts uses: actions/upload-artifact@v4 with: name: windows path: | release/*.exe release/*.zip if-no-files-found: error release: needs: [build-linux, build-macos, build-windows] runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download AppImage uses: actions/download-artifact@v4 with: name: appimage path: release - name: Download macOS artifacts uses: actions/download-artifact@v4 with: name: macos path: release - name: Download Windows artifacts uses: actions/download-artifact@v4 with: name: windows path: release - name: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.5 - name: Cache dependencies uses: actions/cache@v4 with: path: | ~/.bun/install/cache node_modules key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} restore-keys: | ${{ runner.os }}-bun- - name: Install dependencies run: bun install --frozen-lockfile - name: Build Bun subminer wrapper run: make build-launcher - name: Verify Bun subminer wrapper run: dist/launcher/subminer --help >/dev/null - name: Enforce generated launcher workflow run: bash scripts/verify-generated-launcher.sh - name: Verify generated config examples run: bun run verify:config-example - name: Package optional assets bundle run: | tar -czf "release/subminer-assets.tar.gz" \ config.example.jsonc \ plugin/subminer \ plugin/subminer.conf \ assets/themes/subminer.rasi - name: Generate checksums run: | shopt -s nullglob files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz dist/launcher/subminer) if [ "${#files[@]}" -eq 0 ]; then echo "No release artifacts found for checksum generation." exit 1 fi sha256sum "${files[@]}" > release/SHA256SUMS.txt - name: Get version from tag id: version run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" - name: Build changelog artifacts for release run: | if find changes -maxdepth 1 -name '*.md' -not -name README.md -print -quit | grep -q .; then bun run changelog:build --version "${{ steps.version.outputs.VERSION }}" else echo "No pending changelog fragments found." fi - name: Verify changelog is ready for tagged release run: bun run changelog:check --version "${{ steps.version.outputs.VERSION }}" - name: Generate release notes from changelog run: bun run changelog:release-notes --version "${{ steps.version.outputs.VERSION }}" - name: Publish Release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail if gh release view "${{ steps.version.outputs.VERSION }}" >/dev/null 2>&1; then # Do not pass the prerelease flag here; gh defaults to a normal release. gh release edit "${{ steps.version.outputs.VERSION }}" \ --draft=false \ --title "${{ steps.version.outputs.VERSION }}" \ --notes-file release/release-notes.md else gh release create "${{ steps.version.outputs.VERSION }}" \ --title "${{ steps.version.outputs.VERSION }}" \ --notes-file release/release-notes.md fi shopt -s nullglob artifacts=( release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz release/SHA256SUMS.txt dist/launcher/subminer ) if [ "${#artifacts[@]}" -eq 0 ]; then echo "No release artifacts found for upload." exit 1 fi for asset in "${artifacts[@]}"; do gh release upload "${{ steps.version.outputs.VERSION }}" "$asset" --clobber done aur-publish: needs: [release] runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout uses: actions/checkout@v4 - name: Get version from tag id: version run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" - name: Validate AUR SSH secret env: AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} run: | set -euo pipefail if [ -z "${AUR_SSH_PRIVATE_KEY}" ]; then echo "Missing required secret: AUR_SSH_PRIVATE_KEY" exit 1 fi - name: Configure SSH for AUR env: AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} run: | set -euo pipefail install -dm700 ~/.ssh printf '%s\n' "${AUR_SSH_PRIVATE_KEY}" > ~/.ssh/aur chmod 600 ~/.ssh/aur ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts chmod 644 ~/.ssh/known_hosts - name: Clone AUR repo env: GIT_SSH_COMMAND: ssh -i ~/.ssh/aur -o IdentitiesOnly=yes run: git clone ssh://aur@aur.archlinux.org/subminer-bin.git aur-subminer-bin - name: Download release assets for AUR env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail version="${{ steps.version.outputs.VERSION }}" install -dm755 .tmp/aur-release-assets gh release download "$version" \ --dir .tmp/aur-release-assets \ --pattern "SubMiner-${version#v}.AppImage" \ --pattern "subminer" \ --pattern "subminer-assets.tar.gz" - name: Update AUR packaging metadata run: | set -euo pipefail version_no_v="${{ steps.version.outputs.VERSION }}" version_no_v="${version_no_v#v}" cp packaging/aur/subminer-bin/PKGBUILD aur-subminer-bin/PKGBUILD cp packaging/aur/subminer-bin/.SRCINFO aur-subminer-bin/.SRCINFO bash scripts/update-aur-package.sh \ --pkg-dir aur-subminer-bin \ --version "${{ steps.version.outputs.VERSION }}" \ --appimage ".tmp/aur-release-assets/SubMiner-${version_no_v}.AppImage" \ --wrapper ".tmp/aur-release-assets/subminer" \ --assets ".tmp/aur-release-assets/subminer-assets.tar.gz" - name: Commit and push AUR update working-directory: aur-subminer-bin env: GIT_SSH_COMMAND: ssh -i ~/.ssh/aur -o IdentitiesOnly=yes run: | set -euo pipefail if git diff --quiet -- PKGBUILD .SRCINFO; then echo "AUR packaging already up to date." exit 0 fi git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add PKGBUILD .SRCINFO git commit -m "Update to ${{ steps.version.outputs.VERSION }}" git push origin HEAD:master