chore(sbot): sync release notes for v0.2.3 #8
Workflow file for this run
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 App | |
| on: | |
| push: | |
| tags: ['app-v*'] | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| check: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| android_signed: ${{ steps.detect.outputs.android_signed }} | |
| tag: ${{ steps.release.outputs.tag }} | |
| version: ${{ steps.release.outputs.version }} | |
| notes: ${{ steps.release.outputs.notes }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| package-manager-cache: false | |
| - id: detect | |
| shell: bash | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| run: | | |
| if [ -n "$ANDROID_KEYSTORE_BASE64" ] && | |
| [ -n "$ANDROID_KEYSTORE_PASSWORD" ] && | |
| [ -n "$ANDROID_KEY_ALIAS" ] && | |
| [ -n "$ANDROID_KEY_PASSWORD" ]; then | |
| echo "android_signed=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "android_signed=false" >> "$GITHUB_OUTPUT" | |
| if [ -n "$ANDROID_KEYSTORE_BASE64$ANDROID_KEYSTORE_PASSWORD$ANDROID_KEY_ALIAS$ANDROID_KEY_PASSWORD" ]; then | |
| echo "::warning::Android signing secrets are incomplete; Android APK build will be skipped." | |
| fi | |
| fi | |
| - id: release | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| APP_VERSION="$(node -p "require('./packages/app/package.json').version")" | |
| if [[ "${GITHUB_REF:-}" == refs/tags/* ]]; then | |
| TAG="${GITHUB_REF_NAME}" | |
| VERSION="${TAG#app-v}" | |
| else | |
| VERSION="$APP_VERSION" | |
| TAG="app-v${VERSION}" | |
| fi | |
| if [ "$VERSION" != "$APP_VERSION" ]; then | |
| echo "::error::Tag version ($VERSION) does not match packages/app/package.json version ($APP_VERSION). Run pnpm run release:app to keep them in sync." | |
| exit 1 | |
| fi | |
| BODY=$(awk -v v="$VERSION" ' | |
| $1 == "##" && $2 == v { capture=1; next } | |
| capture && $1 == "##" { exit } | |
| capture { print } | |
| ' packages/app/CHANGELOG.md) | |
| [ -z "$BODY" ] && BODY="Release $VERSION" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| { | |
| echo "notes<<RELEASE_NOTES_EOF" | |
| echo "$BODY" | |
| echo "RELEASE_NOTES_EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| prepare-release: | |
| needs: check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Create or refresh GitHub Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ needs.check.outputs.tag }} | |
| VERSION: ${{ needs.check.outputs.version }} | |
| RELEASE_NOTES: ${{ needs.check.outputs.notes }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if gh release view "$TAG" -R "$REPO" >/dev/null 2>&1; then | |
| gh release edit "$TAG" -R "$REPO" \ | |
| --title "app v${VERSION}" \ | |
| --notes "$RELEASE_NOTES" | |
| else | |
| gh release create "$TAG" -R "$REPO" \ | |
| --target "$GITHUB_SHA" \ | |
| --title "app v${VERSION}" \ | |
| --notes "$RELEASE_NOTES" | |
| fi | |
| gh api "/repos/$REPO/releases/tags/$TAG" --jq '.assets[] | "\(.id)\t\(.name)"' | | |
| while IFS=$'\t' read -r id name; do | |
| case "$name" in | |
| "sbot_${VERSION}_android.apk"|sbot-vscode-*.vsix) | |
| continue | |
| ;; | |
| "latest.json"|"latest-app.json"|"sbot.app.tar.gz"|"sbot.app.tar.gz.sig"|sbot_*.app.tar.gz|sbot_*.app.tar.gz.sig|sbot_${VERSION}_*) | |
| echo "delete stale release asset: $name" | |
| gh api -X DELETE "/repos/$REPO/releases/assets/$id" >/dev/null | |
| ;; | |
| esac | |
| done | |
| release-desktop: | |
| needs: [check, prepare-release] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: macos-latest | |
| args: '--target aarch64-apple-darwin' | |
| rust-target: aarch64-apple-darwin | |
| - platform: macos-latest | |
| args: '--target x86_64-apple-darwin' | |
| rust-target: x86_64-apple-darwin | |
| - platform: ubuntu-22.04 | |
| args: '' | |
| rust-target: '' | |
| - platform: windows-latest | |
| args: '' | |
| rust-target: '' | |
| - platform: windows-latest | |
| args: '--target aarch64-pc-windows-msvc' | |
| rust-target: aarch64-pc-windows-msvc | |
| runs-on: ${{ matrix.platform }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: 10.33.0 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.rust-target }} | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| packages/app/src-tauri/target | |
| key: ${{ matrix.platform }}-${{ matrix.rust-target || 'default' }}-cargo-app-${{ hashFiles('packages/app/src-tauri/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ matrix.platform }}-${{ matrix.rust-target || 'default' }}-cargo-app- | |
| ${{ matrix.platform }}-cargo-app- | |
| - name: Install Linux deps | |
| if: matrix.platform == 'ubuntu-22.04' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf | |
| - run: pnpm install --frozen-lockfile | |
| - uses: tauri-apps/tauri-action@v0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| with: | |
| projectPath: packages/app | |
| tagName: ${{ needs.check.outputs.tag }} | |
| releaseName: 'app v${{ needs.check.outputs.version }}' | |
| releaseBody: ${{ needs.check.outputs.notes }} | |
| releaseDraft: false | |
| prerelease: false | |
| includeUpdaterJson: false | |
| args: ${{ matrix.args }} | |
| release-android: | |
| needs: [check, release-desktop] | |
| if: ${{ !cancelled() && needs.release-desktop.result == 'success' && needs.check.outputs.android_signed == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: 10.33.0 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Setup Android SDK | |
| uses: android-actions/setup-android@v3 | |
| - name: Install Android SDK packages | |
| run: | | |
| sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0" "ndk;27.0.11902837" | |
| echo "NDK_HOME=$ANDROID_HOME/ndk/27.0.11902837" >> "$GITHUB_ENV" | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| packages/app/src-tauri/target | |
| key: android-cargo-app-${{ hashFiles('packages/app/src-tauri/Cargo.lock') }} | |
| restore-keys: | | |
| android-cargo-app- | |
| - run: pnpm install --frozen-lockfile | |
| - name: Decode Android keystore | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| run: | | |
| echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > "$RUNNER_TEMP/release.jks" | |
| echo "ANDROID_KEYSTORE_PATH=$RUNNER_TEMP/release.jks" >> "$GITHUB_ENV" | |
| - name: Build APK | |
| working-directory: packages/app | |
| env: | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| run: pnpm android:build --apk | |
| - name: Upload APK to release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ needs.check.outputs.tag }} | |
| VERSION: ${{ needs.check.outputs.version }} | |
| run: | | |
| APK=$(find packages/app/src-tauri/gen/android/app/build/outputs/apk -name '*.apk' ! -name '*unsigned*' | head -n1) | |
| if [ -z "$APK" ]; then | |
| echo "::error::Signed APK was not found. Check Android signing configuration." | |
| exit 1 | |
| fi | |
| DEST="sbot_${VERSION}_android.apk" | |
| cp "$APK" "$DEST" | |
| gh release upload "$TAG" "$DEST" -R "$REPO" --clobber | |
| publish-vscode: | |
| needs: [check, release-desktop] | |
| if: ${{ !cancelled() && needs.release-desktop.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: 10.33.0 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - name: Build extension | |
| run: pnpm run build:vscode | |
| - name: Verify VS Code extension version | |
| id: vscode | |
| shell: bash | |
| run: | | |
| VERSION=$(node -p "require('./packages/vscode-extension/package.json').version") | |
| if [ "$VERSION" != "${{ needs.check.outputs.version }}" ]; then | |
| echo "::error::App version (${{ needs.check.outputs.version }}) does not match packages/vscode-extension/package.json version ($VERSION). Run pnpm run release:app to keep them in sync." | |
| exit 1 | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Package VSIX | |
| working-directory: packages/vscode-extension | |
| run: npx --yes @vscode/vsce package --no-dependencies --out "sbot-vscode-${{ steps.vscode.outputs.version }}.vsix" | |
| - name: Upload VSIX to release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ needs.check.outputs.tag }} | |
| VERSION: ${{ steps.vscode.outputs.version }} | |
| run: gh release upload "$TAG" "packages/vscode-extension/sbot-vscode-${VERSION}.vsix" -R "$REPO" --clobber | |
| publish-manifest: | |
| needs: [check, release-desktop] | |
| if: ${{ !cancelled() && needs.release-desktop.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| package-manager-cache: false | |
| - name: Force release body from CHANGELOG | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ needs.check.outputs.tag }} | |
| RELEASE_NOTES: ${{ needs.check.outputs.notes }} | |
| shell: bash | |
| run: gh release edit "$TAG" -R "$REPO" --notes "$RELEASE_NOTES" | |
| - name: Rename desktop assets with OS tag | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ needs.check.outputs.tag }} | |
| ASSET_VERSION: ${{ needs.check.outputs.version }} | |
| run: | | |
| gh api "/repos/$REPO/releases/tags/$TAG" --jq '.assets[] | "\(.id)\t\(.name)"' | | |
| while IFS=$'\t' read -r id name; do | |
| new=$(printf '%s' "$name" | sed -E \ | |
| -e "s#^sbot_[0-9][0-9.]*_aarch64\.dmg#sbot_${ASSET_VERSION}_macos_arm64.dmg#" \ | |
| -e "s#^sbot_[0-9][0-9.]*_x64\.dmg#sbot_${ASSET_VERSION}_macos_x64.dmg#" \ | |
| -e "s#^sbot_aarch64\.app\.tar\.gz#sbot_${ASSET_VERSION}_macos_arm64.app.tar.gz#" \ | |
| -e "s#^sbot_x64\.app\.tar\.gz#sbot_${ASSET_VERSION}_macos_x64.app.tar.gz#" \ | |
| -e "s#^sbot_[0-9][0-9.]*_amd64\.(AppImage|deb)#sbot_${ASSET_VERSION}_linux_x64.\1#" \ | |
| -e "s#^sbot_[0-9][0-9.]*_x64-setup\.nsis\.zip#sbot_${ASSET_VERSION}_windows_x64.nsis.zip#" \ | |
| -e "s#^sbot_[0-9][0-9.]*_arm64-setup\.nsis\.zip#sbot_${ASSET_VERSION}_windows_arm64.nsis.zip#" \ | |
| -e "s#^sbot_[0-9][0-9.]*_x64-setup\.exe#sbot_${ASSET_VERSION}_windows_x64.exe#" \ | |
| -e "s#^sbot_[0-9][0-9.]*_arm64-setup\.exe#sbot_${ASSET_VERSION}_windows_arm64.exe#") | |
| if [ "$new" != "$name" ]; then | |
| echo "rename: $name -> $new" | |
| gh api -X PATCH "/repos/$REPO/releases/assets/$id" -f name="$new" >/dev/null | |
| fi | |
| done | |
| - name: Generate updater manifest | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ needs.check.outputs.tag }} | |
| ASSET_VERSION: ${{ needs.check.outputs.version }} | |
| RELEASE_NOTES: ${{ needs.check.outputs.notes }} | |
| run: | | |
| set -euo pipefail | |
| gh api "/repos/$REPO/releases/tags/$TAG" > release.json | |
| node <<'NODE' | |
| const fs = require('node:fs'); | |
| const { execFileSync } = require('node:child_process'); | |
| const repo = process.env.REPO; | |
| const version = process.env.ASSET_VERSION; | |
| const release = JSON.parse(fs.readFileSync('release.json', 'utf8')); | |
| const assets = new Map(release.assets.map((asset) => [asset.name, asset])); | |
| const platforms = {}; | |
| const missing = []; | |
| function signatureFor(assetName) { | |
| const sigName = `${assetName}.sig`; | |
| const sigAsset = assets.get(sigName); | |
| if (!sigAsset) { | |
| throw new Error(`missing signature asset: ${sigName}`); | |
| } | |
| return execFileSync('gh', [ | |
| 'api', | |
| `/repos/${repo}/releases/assets/${sigAsset.id}`, | |
| '-H', | |
| 'Accept: application/octet-stream', | |
| ], { encoding: 'utf8' }).trim(); | |
| } | |
| function add(keys, candidates) { | |
| const asset = candidates.map((name) => assets.get(name)).find(Boolean); | |
| if (!asset) { | |
| missing.push(candidates.join(' or ')); | |
| return; | |
| } | |
| const entry = { | |
| signature: signatureFor(asset.name), | |
| url: asset.browser_download_url, | |
| }; | |
| for (const key of keys) { | |
| platforms[key] = entry; | |
| } | |
| } | |
| add(['darwin-x86_64', 'darwin-x86_64-app'], [ | |
| `sbot_${version}_macos_x64.app.tar.gz`, | |
| ]); | |
| add(['darwin-aarch64', 'darwin-aarch64-app'], [ | |
| `sbot_${version}_macos_arm64.app.tar.gz`, | |
| ]); | |
| add(['windows-x86_64', 'windows-x86_64-nsis'], [ | |
| `sbot_${version}_windows_x64.nsis.zip`, | |
| `sbot_${version}_windows_x64.exe`, | |
| ]); | |
| add(['windows-aarch64', 'windows-aarch64-nsis'], [ | |
| `sbot_${version}_windows_arm64.nsis.zip`, | |
| `sbot_${version}_windows_arm64.exe`, | |
| ]); | |
| add(['linux-x86_64', 'linux-x86_64-appimage'], [ | |
| `sbot_${version}_linux_x64.AppImage.tar.gz`, | |
| `sbot_${version}_linux_x64.AppImage`, | |
| ]); | |
| if (missing.length > 0) { | |
| throw new Error(`missing updater payload asset(s): ${missing.join(', ')}`); | |
| } | |
| fs.writeFileSync( | |
| 'latest-app.json', | |
| `${JSON.stringify({ | |
| version, | |
| notes: process.env.RELEASE_NOTES || `Release ${version}`, | |
| pub_date: new Date().toISOString(), | |
| platforms, | |
| }, null, 2)}\n`, | |
| ); | |
| NODE | |
| if gh release view updater -R "$REPO" >/dev/null 2>&1; then | |
| gh release upload updater latest-app.json -R "$REPO" --clobber | |
| else | |
| gh release create updater -R "$REPO" \ | |
| latest-app.json \ | |
| --target "$GITHUB_SHA" \ | |
| --title "Updater Manifests" \ | |
| --notes "Auto-maintained by CI. Hosts updater JSON for app and helper." \ | |
| --latest=false | |
| fi | |
| for stale in latest.json latest-app.json; do | |
| old_id=$(gh api "/repos/$REPO/releases/tags/$TAG" --jq ".assets[] | select(.name == \"$stale\") | .id" || true) | |
| if [ -n "$old_id" ]; then | |
| gh api -X DELETE "/repos/$REPO/releases/assets/$old_id" >/dev/null | |
| fi | |
| done |