name: Release on: workflow_dispatch: inputs: version: description: "Release version, e.g. 0.2.0. Leave empty to bump the patch number." required: false type: string permissions: contents: write jobs: prepare: runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} tag: ${{ steps.version.outputs.tag }} release_id: ${{ steps.release.outputs.release_id }} steps: - uses: actions/checkout@v7 - name: Determine version id: version run: | if [ -n "${{ inputs.version }}" ]; then VERSION="${{ inputs.version }}" VERSION="${VERSION#v}" else CURRENT=$(jq -r .version src-tauri/tauri.conf.json) IFS=. read -r MAJOR MINOR PATCH <<< "$CURRENT" VERSION="$MAJOR.$MINOR.$((PATCH + 1))" fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" echo "Releasing v$VERSION" - name: Bump version in manifests env: VERSION: ${{ steps.version.outputs.version }} run: | tmp=$(mktemp) jq --arg v "$VERSION" '.version = $v' src-tauri/tauri.conf.json > "$tmp" && mv "$tmp" src-tauri/tauri.conf.json jq --arg v "$VERSION" '.version = $v' package.json > "$tmp" && mv "$tmp" package.json sed -i "0,/^version = \".*\"/s//version = \"$VERSION\"/" src-tauri/Cargo.toml - name: Commit and tag env: TAG: ${{ steps.version.outputs.tag }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add src-tauri/tauri.conf.json package.json src-tauri/Cargo.toml git commit -m "chore(release): $TAG" for attempt in 1 2 3 4 5; do git fetch origin main git rebase origin/main if git push origin HEAD; then break fi if [ "$attempt" = "5" ]; then echo "::error::main kept advancing; could not push release bump after 5 attempts." exit 1 fi echo "main advanced during release; rebasing and retrying ($attempt)…" sleep 3 done git tag "$TAG" git push origin "$TAG" - name: Create draft release id: release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ steps.version.outputs.tag }} run: | gh release create "$TAG" --draft --title "Margin Calendar $TAG" --notes "Release $TAG" ID=$(gh release view "$TAG" --json databaseId --jq .databaseId) echo "release_id=$ID" >> "$GITHUB_OUTPUT" build: needs: prepare strategy: fail-fast: false matrix: include: - os: macos-26 args: "--target universal-apple-darwin --config src-tauri/tauri.release.conf.json" rust-targets: "aarch64-apple-darwin,x86_64-apple-darwin" # Ubuntu 22.04 is the glibc baseline docs/setup.md commits to: the bundle will not run on # anything older than the glibc it was linked against, so build on the oldest supported. - os: ubuntu-22.04 args: "--config src-tauri/tauri.release.conf.json" rust-targets: "" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v7 with: ref: ${{ needs.prepare.outputs.tag }} - name: Install Linux dependencies if: startsWith(matrix.os, 'ubuntu') run: | sudo apt-get update sudo apt-get install -y \ libwebkit2gtk-4.1-dev \ libgtk-3-dev \ libayatana-appindicator3-dev \ librsvg2-dev \ patchelf \ libxdo-dev \ libssl-dev \ build-essential \ curl \ wget \ file - uses: actions/setup-node@v6 with: node-version: 26 - uses: pnpm/action-setup@v6 with: version: 10 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.rust-targets }} - uses: swatinem/rust-cache@v2 with: workspaces: src-tauri -> target - name: Install frontend dependencies run: pnpm install --frozen-lockfile - name: Provision Google credentials shell: bash env: GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }} run: | if [ -n "$GOOGLE_CREDENTIALS" ]; then printf '%s' "$GOOGLE_CREDENTIALS" > google-credentials.json echo "Wrote google-credentials.json from GOOGLE_CREDENTIALS secret." else cp google-credentials.example.json google-credentials.json echo "::warning::GOOGLE_CREDENTIALS secret not set — embedding placeholder credentials; this build cannot connect to Google Calendar." fi - name: Build and upload 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: releaseId: ${{ needs.prepare.outputs.release_id }} args: ${{ matrix.args }} publish: needs: [prepare, build] runs-on: ubuntu-latest steps: - name: Verify manifest is complete, then publish env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} TAG: ${{ needs.prepare.outputs.tag }} run: | gh release download "$TAG" --repo "$REPO" --pattern latest.json --output latest.json --clobber echo "Platforms in latest.json:" jq '.platforms | keys' latest.json for key in darwin-aarch64 darwin-x86_64 linux-x86_64; do if ! jq -e ".platforms[\"$key\"].url" latest.json > /dev/null; then echo "::error::latest.json is missing platform '$key' — refusing to publish a partial update manifest. Re-run the release." exit 1 fi done gh release edit "$TAG" --repo "$REPO" --draft=false --latest # Arch gets its own package rather than the AppImage. The AppImage carries Ubuntu's GTK stack, # which cannot talk to a modern Wayland compositor and silently falls back to Xwayland; a package # linked against the system webkit2gtk runs as a native Wayland client. Runs after publish so it # can only ever point at a release that survived the manifest check. aur: needs: [prepare, publish] runs-on: ubuntu-latest steps: # The tag, not main, so the template and the licence are the ones this release shipped. - uses: actions/checkout@v7 with: ref: ${{ needs.prepare.outputs.tag }} - name: Render the PKGBUILD for this release env: VERSION: ${{ needs.prepare.outputs.version }} REPO: ${{ github.repository }} run: | URL="https://github.com/$REPO/releases/download/v$VERSION/Margin.Calendar_${VERSION}_amd64.deb" # From the published asset, so the checksum is of the artifact users will actually fetch. curl -fsSL --retry 3 -o package.deb "$URL" SHA=$(sha256sum package.deb | cut -d' ' -f1) # The PKGBUILD fetches the licence from the tag, so checksum the copy at that same tag. LICENSE_SHA=$(sha256sum LICENSE | cut -d' ' -f1) sed -e "s/@VERSION@/$VERSION/g" \ -e "s/@SHA256@/$SHA/g" \ -e "s/@LICENSE_SHA256@/$LICENSE_SHA/g" \ packaging/aur/PKGBUILD.in > PKGBUILD echo "pkgver $VERSION, deb $SHA, licence $LICENSE_SHA" - name: Generate .SRCINFO # makepkg is Arch-only and refuses to run as root, hence the container. It runs as the # runner's own uid so nothing in the bind-mounted checkout changes owner; chowning it to a # container user left the workspace unwritable for the push step. --printsrcinfo parses the # PKGBUILD, it does not build anything. run: | docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD:/w" -w /w \ archlinux:base-devel makepkg --printsrcinfo > .SRCINFO cat .SRCINFO - name: Push to the AUR env: AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} VERSION: ${{ needs.prepare.outputs.version }} run: | if [ -z "$AUR_SSH_PRIVATE_KEY" ]; then echo "::warning::AUR_SSH_PRIVATE_KEY is not set, so the AUR package was built but not published. The release itself is unaffected." exit 0 fi mkdir -p ~/.ssh && chmod 700 ~/.ssh printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur chmod 600 ~/.ssh/aur ssh-keyscan -t ed25519,rsa aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null export GIT_SSH_COMMAND="ssh -i ~/.ssh/aur -o IdentitiesOnly=yes" git clone ssh://aur@aur.archlinux.org/margin-calendar-bin.git aur cp PKGBUILD .SRCINFO aur/ cd aur # A package that does not exist yet clones as an empty repo, where the local branch name # is whatever git defaults to. The AUR only accepts master. git checkout -B master git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add PKGBUILD .SRCINFO if git diff --cached --quiet; then echo "AUR is already at this version, nothing to push." exit 0 fi git commit -m "margin-calendar-bin $VERSION" git push origin master