The Tauri updater plugin requires the .app.tar.gz artifact as the update URL — not the .dmg installer. The DMG is for fresh installs only. This was causing 'Install Update' to silently fail on all macOS builds since the auto-updater infrastructure was set up. Change ARM_DMG → ARM_TARBALL, pointing to the .app.tar.gz artifact.
282 lines
12 KiB
YAML
282 lines
12 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
|
|
concurrency:
|
|
group: release-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# ─────────────────────────────────────────────────────────────
|
|
# Phase 1: Compute the version string once
|
|
# ─────────────────────────────────────────────────────────────
|
|
version:
|
|
name: Compute version
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.ver.outputs.version }}
|
|
tag: ${{ steps.ver.outputs.tag }}
|
|
steps:
|
|
- id: ver
|
|
run: |
|
|
VERSION="0.$(date -u +%Y%m%d).${GITHUB_RUN_NUMBER}"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "### Version: \`$VERSION\`" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# Phase 2: Build each architecture in parallel
|
|
# tauri build handles signing automatically via env vars
|
|
# ─────────────────────────────────────────────────────────────
|
|
build:
|
|
name: Build (${{ matrix.arch }})
|
|
needs: version
|
|
runs-on: macos-15
|
|
strategy:
|
|
fail-fast: true
|
|
matrix:
|
|
include:
|
|
- arch: aarch64
|
|
target: aarch64-apple-darwin
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v4
|
|
with:
|
|
version: 10
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
cache: 'pnpm'
|
|
|
|
- name: Setup Bun (required for bundle-qmd.sh)
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: latest
|
|
|
|
- name: Setup Rust
|
|
uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
|
|
- name: Cache Rust dependencies
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
src-tauri/target
|
|
key: ${{ runner.os }}-release-cargo-${{ hashFiles('src-tauri/Cargo.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-release-cargo-
|
|
|
|
- name: Install frontend dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Set version
|
|
run: |
|
|
VERSION="${{ needs.version.outputs.version }}"
|
|
jq --arg v "$VERSION" '.version = $v' src-tauri/tauri.conf.json > tmp.json && mv tmp.json src-tauri/tauri.conf.json
|
|
sed -i '' "s/^version = \".*\"/version = \"$VERSION\"/" src-tauri/Cargo.toml
|
|
|
|
- name: Import Apple Developer certificate into keychain
|
|
env:
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
run: |
|
|
# Decode and import the certificate so codesign can use it in beforeBuildCommand
|
|
CERT_PATH="$RUNNER_TEMP/apple_cert.p12"
|
|
KEYCHAIN_PATH="$RUNNER_TEMP/laputa-signing.keychain-db"
|
|
KEYCHAIN_PASSWORD="$(uuidgen)"
|
|
echo "$APPLE_CERTIFICATE" | base64 --decode > "$CERT_PATH"
|
|
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
security import "$CERT_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
|
|
security list-keychain -d user -s "$KEYCHAIN_PATH"
|
|
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
|
|
|
|
- name: Build Tauri app (with signing + notarization)
|
|
env:
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
run: |
|
|
pnpm tauri build --target ${{ matrix.target }}
|
|
|
|
- name: Upload .dmg
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: dmg-${{ matrix.arch }}
|
|
path: src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
|
|
retention-days: 1
|
|
|
|
- name: Upload updater artifacts (.tar.gz + .sig)
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: updater-${{ matrix.arch }}
|
|
path: |
|
|
src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.app.tar.gz
|
|
src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.app.tar.gz.sig
|
|
retention-days: 1
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# Phase 3: Publish GitHub Release
|
|
# No lipo/re-signing — use the per-arch artifacts directly
|
|
# ─────────────────────────────────────────────────────────────
|
|
release:
|
|
name: GitHub Release
|
|
needs: [version, build]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
|
|
- name: Generate release notes
|
|
run: |
|
|
PREV_TAG=$(git tag --sort=-version:refname | head -n 1 || echo "")
|
|
if [ -z "$PREV_TAG" ]; then
|
|
NOTES=$(git log --oneline --no-merges -20)
|
|
else
|
|
NOTES=$(git log --oneline --no-merges "${PREV_TAG}..HEAD")
|
|
fi
|
|
{
|
|
echo "## What's Changed"
|
|
echo ""
|
|
echo "$NOTES" | while IFS= read -r line; do echo "- $line"; done
|
|
echo ""
|
|
echo "---"
|
|
echo "**Requires Apple Silicon (M1/M2/M3)**"
|
|
echo ""
|
|
echo "*Built from \`$(git rev-parse --short HEAD)\` on $(date -u +%Y-%m-%d)*"
|
|
} > release_notes.md
|
|
|
|
- name: Build latest.json
|
|
run: |
|
|
VERSION="${{ needs.version.outputs.version }}"
|
|
TAG="${{ needs.version.outputs.tag }}"
|
|
REPO="refactoringhq/laputa-app"
|
|
|
|
ARM_SIG=$(cat updater-aarch64/*.app.tar.gz.sig)
|
|
ARM_TARBALL=$(ls updater-aarch64/*.app.tar.gz | xargs basename)
|
|
|
|
cat > latest.json << EOF
|
|
{
|
|
"version": "${VERSION}",
|
|
"notes": "See https://refactoringhq.github.io/laputa-app/ for full release notes.",
|
|
"pub_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"platforms": {
|
|
"darwin-aarch64": {
|
|
"signature": "${ARM_SIG}",
|
|
"url": "https://github.com/${REPO}/releases/download/${TAG}/${ARM_TARBALL}"
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
echo "latest.json:"; cat latest.json
|
|
|
|
- name: Publish GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: ${{ needs.version.outputs.tag }}
|
|
name: Laputa ${{ needs.version.outputs.version }}
|
|
body_path: release_notes.md
|
|
draft: false
|
|
prerelease: false
|
|
files: |
|
|
dmg-aarch64/*.dmg
|
|
updater-aarch64/*.app.tar.gz
|
|
updater-aarch64/*.app.tar.gz.sig
|
|
latest.json
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# Phase 4: Update GitHub Pages with release history
|
|
# ─────────────────────────────────────────────────────────────
|
|
pages:
|
|
name: Update release history page
|
|
needs: [version, release]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Build release history page
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
mkdir -p _site
|
|
gh api repos/${{ github.repository }}/releases --paginate > _site/releases.json
|
|
# Copy latest.json to GitHub Pages for auto-updater endpoint
|
|
gh release download --repo ${{ github.repository }} --pattern "latest.json" --output _site/latest.json || true
|
|
cat > _site/index.html << 'HTMLEOF'
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Laputa — Release History</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #F7F6F3; color: #37352F; line-height: 1.6; padding: 2rem; max-width: 720px; margin: 0 auto; }
|
|
h1 { font-size: 1.75rem; font-weight: 600; margin-bottom: 0.5rem; }
|
|
.subtitle { color: #787774; margin-bottom: 2rem; }
|
|
.release { background: #fff; border: 1px solid #E9E9E7; border-radius: 8px; padding: 1.25rem 1.5rem; margin-bottom: 1rem; }
|
|
.release h2 { font-size: 1.125rem; font-weight: 600; margin-bottom: 0.25rem; }
|
|
.release .meta { font-size: 0.8125rem; color: #787774; margin-bottom: 0.75rem; }
|
|
.release .body { font-size: 0.875rem; white-space: pre-wrap; }
|
|
.release .downloads { margin-top: 0.75rem; display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
|
.release .downloads a { display: inline-block; padding: 0.375rem 0.75rem; background: #155DFF; color: #fff; border-radius: 6px; text-decoration: none; font-size: 0.8125rem; font-weight: 500; }
|
|
.release .downloads a:hover { background: #1248CC; }
|
|
.empty { color: #787774; text-align: center; padding: 3rem; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Laputa Release History</h1>
|
|
<p class="subtitle">Auto-updated on every release</p>
|
|
<div id="releases"></div>
|
|
<script>
|
|
fetch('releases.json').then(r=>r.json()).then(releases=>{
|
|
const el=document.getElementById('releases');
|
|
if(!releases.length){el.innerHTML='<p class="empty">No releases yet.</p>';return;}
|
|
releases.forEach(r=>{
|
|
const date=new Date(r.published_at).toLocaleDateString('en-US',{year:'numeric',month:'long',day:'numeric'});
|
|
const dmgs=(r.assets||[]).filter(a=>a.name.endsWith('.dmg'));
|
|
const links=dmgs.map(a=>'<a href="'+a.browser_download_url+'">'+a.name+'</a>').join('');
|
|
const body=(r.body||'').replace(/</g,'<').replace(/>/g,'>');
|
|
const div=document.createElement('div');
|
|
div.className='release';
|
|
div.innerHTML='<h2>'+(r.name||r.tag_name)+'</h2><div class="meta">'+date+' · '+r.tag_name+'</div><div class="body">'+body+'</div>'+(links?'<div class="downloads">'+links+'</div>':'');
|
|
el.appendChild(div);
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTMLEOF
|
|
|
|
- name: Deploy to GitHub Pages
|
|
uses: peaceiris/actions-gh-pages@v4
|
|
with:
|
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
publish_dir: ./_site
|
|
commit_message: "Update release history for ${{ needs.version.outputs.tag }}"
|