name: Desktop Release on: push: tags: - "v*" - "desktop-v*" - "desktop-macos-v*" - "desktop-linux-v*" - "desktop-windows-v*" workflow_dispatch: inputs: tag: description: "Existing tag to build (e.g. v0.1.0)" required: true type: string platform: description: "Optional desktop platform to build." required: false default: "all" type: choice options: - all - macos - linux - windows concurrency: group: desktop-release-${{ github.ref }} cancel-in-progress: false env: SOURCE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }} jobs: publish-macos: if: ${{ (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos')) || (github.event_name == 'push' && (startsWith(github.ref_name, 'v') || startsWith(github.ref_name, 'desktop-v') || startsWith(github.ref_name, 'desktop-macos-v'))) }} strategy: fail-fast: false matrix: include: - runner: macos-14 rust_target: aarch64-apple-darwin - runner: macos-15-intel rust_target: x86_64-apple-darwin permissions: contents: write packages: read runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }} - name: Normalize release tag shell: bash run: | set -euo pipefail source_tag="${SOURCE_TAG}" if [[ "$source_tag" == desktop-windows-v* ]]; then release_tag="v${source_tag#desktop-windows-v}" elif [[ "$source_tag" == desktop-linux-v* ]]; then release_tag="v${source_tag#desktop-linux-v}" elif [[ "$source_tag" == desktop-macos-v* ]]; then release_tag="v${source_tag#desktop-macos-v}" elif [[ "$source_tag" == desktop-v* ]]; then release_tag="v${source_tag#desktop-v}" else release_tag="$source_tag" fi echo "RELEASE_TAG=$release_tag" >> "$GITHUB_ENV" - name: Setup Node uses: actions/setup-node@v4 with: node-version: "22" cache: "npm" registry-url: "https://npm.pkg.github.com" scope: "@boudra" - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.rust_target }} - name: Install JS dependencies run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build web app for Tauri run: npm run build:web --workspace=@getpaseo/app - name: Build managed runtime for macOS run: npm run prepare:managed-runtime --workspace=@getpaseo/desktop - name: Import Apple code-signing certificate uses: apple-actions/import-codesign-certs@v3 with: p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - name: Build packaged app for macOS smoke env: APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: npm run tauri --workspace=@getpaseo/desktop build -- --target ${{ matrix.rust_target }} - name: Smoke check managed runtime for macOS env: PASEO_MANAGED_SMOKE_SKIP_BUILD: "1" PASEO_MANAGED_SMOKE_RUST_TARGET: ${{ matrix.rust_target }} run: npm run smoke:managed-daemon --workspace=@getpaseo/desktop - name: Sign bundled managed runtime for macOS env: APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} run: node ./packages/desktop/scripts/sign-managed-runtime-macos.mjs - name: Set desktop version from tag shell: bash run: | node <<'NODE' const fs = require('node:fs'); const path = require('node:path'); const rawTag = process.env.SOURCE_TAG; if (!rawTag) throw new Error('SOURCE_TAG env var is missing'); const version = rawTag.replace(/^desktop-(windows-|linux-|macos-)?/, '').replace(/^v/, ''); console.log(`Using desktop version ${version} from tag ${rawTag}`); const tauriConfPath = path.join('packages', 'desktop', 'src-tauri', 'tauri.conf.json'); const tauriConfText = fs.readFileSync(tauriConfPath, 'utf8'); const tauriRe = /("version"\s*:\s*")([^"]+)(")/; if (!tauriRe.test(tauriConfText)) { throw new Error(`Failed to find version field in ${tauriConfPath}`); } fs.writeFileSync(tauriConfPath, tauriConfText.replace(tauriRe, `$1${version}$3`)); const cargoTomlPath = path.join('packages', 'desktop', 'src-tauri', 'Cargo.toml'); const cargoLines = fs.readFileSync(cargoTomlPath, 'utf8').split(/\r?\n/); let inPackage = false; let updated = false; const nextLines = cargoLines.map((line) => { if (/^\[package\]\s*$/.test(line)) inPackage = true; else if (inPackage && /^\[/.test(line)) inPackage = false; if (inPackage && /^version\s*=\s*".*"\s*$/.test(line)) { updated = true; return `version = "${version}"`; } return line; }); if (!updated) throw new Error(`Failed to update Cargo package version in ${cargoTomlPath}`); fs.writeFileSync(cargoTomlPath, `${nextLines.join('\n')}\n`); NODE - name: Detect existing GitHub release state env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | set -euo pipefail if release_draft="$(gh release view "$RELEASE_TAG" --repo "${{ github.repository }}" --json isDraft --jq '.isDraft' 2>/dev/null)"; then : else release_draft="false" fi echo "RELEASE_DRAFT=$release_draft" >> "$GITHUB_ENV" - name: Build and publish macOS Tauri release uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} with: projectPath: packages/desktop tagName: ${{ env.RELEASE_TAG }} releaseName: Paseo ${{ env.RELEASE_TAG }} releaseBody: See the assets to download and install this version. releaseDraft: ${{ env.RELEASE_DRAFT }} prerelease: false args: --target ${{ matrix.rust_target }} publish-linux: if: ${{ (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux')) || (github.event_name == 'push' && (startsWith(github.ref_name, 'v') || startsWith(github.ref_name, 'desktop-v') || startsWith(github.ref_name, 'desktop-linux-v'))) }} permissions: contents: write packages: read runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }} - name: Normalize release tag shell: bash run: | set -euo pipefail source_tag="${SOURCE_TAG}" if [[ "$source_tag" == desktop-windows-v* ]]; then release_tag="v${source_tag#desktop-windows-v}" elif [[ "$source_tag" == desktop-linux-v* ]]; then release_tag="v${source_tag#desktop-linux-v}" elif [[ "$source_tag" == desktop-macos-v* ]]; then release_tag="v${source_tag#desktop-macos-v}" elif [[ "$source_tag" == desktop-v* ]]; then release_tag="v${source_tag#desktop-v}" else release_tag="$source_tag" fi echo "RELEASE_TAG=$release_tag" >> "$GITHUB_ENV" - name: Install Linux packaging dependencies run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev libappindicator3-dev librsvg2-dev patchelf - name: Setup Node uses: actions/setup-node@v4 with: node-version: "22" cache: "npm" registry-url: "https://npm.pkg.github.com" scope: "@boudra" - name: Install Rust stable uses: dtolnay/rust-toolchain@stable - name: Install JS dependencies run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build web app for Tauri run: npm run build:web --workspace=@getpaseo/app - name: Build managed runtime for Linux run: npm run prepare:managed-runtime --workspace=@getpaseo/desktop - name: Smoke check managed runtime for Linux run: npm run smoke:managed-daemon --workspace=@getpaseo/desktop - name: Set desktop version from tag shell: bash run: | node <<'NODE' const fs = require('node:fs'); const path = require('node:path'); const rawTag = process.env.SOURCE_TAG; if (!rawTag) throw new Error('SOURCE_TAG env var is missing'); const version = rawTag.replace(/^desktop-(windows-|linux-|macos-)?/, '').replace(/^v/, ''); console.log(`Using desktop version ${version} from tag ${rawTag}`); const tauriConfPath = path.join('packages', 'desktop', 'src-tauri', 'tauri.conf.json'); const tauriConfText = fs.readFileSync(tauriConfPath, 'utf8'); const tauriRe = /("version"\s*:\s*")([^"]+)(")/; if (!tauriRe.test(tauriConfText)) { throw new Error(`Failed to find version field in ${tauriConfPath}`); } fs.writeFileSync(tauriConfPath, tauriConfText.replace(tauriRe, `$1${version}$3`)); const cargoTomlPath = path.join('packages', 'desktop', 'src-tauri', 'Cargo.toml'); const cargoLines = fs.readFileSync(cargoTomlPath, 'utf8').split(/\r?\n/); let inPackage = false; let updated = false; const nextLines = cargoLines.map((line) => { if (/^\[package\]\s*$/.test(line)) inPackage = true; else if (inPackage && /^\[/.test(line)) inPackage = false; if (inPackage && /^version\s*=\s*".*"\s*$/.test(line)) { updated = true; return `version = "${version}"`; } return line; }); if (!updated) throw new Error(`Failed to update Cargo package version in ${cargoTomlPath}`); fs.writeFileSync(cargoTomlPath, `${nextLines.join('\n')}\n`); NODE - name: Detect existing GitHub release state env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | set -euo pipefail if release_draft="$(gh release view "$RELEASE_TAG" --repo "${{ github.repository }}" --json isDraft --jq '.isDraft' 2>/dev/null)"; then : else release_draft="false" fi echo "RELEASE_DRAFT=$release_draft" >> "$GITHUB_ENV" - name: Build and publish Linux Tauri release 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/desktop tagName: ${{ env.RELEASE_TAG }} releaseName: Paseo ${{ env.RELEASE_TAG }} releaseBody: See the assets to download and install this version. releaseDraft: ${{ env.RELEASE_DRAFT }} prerelease: false args: --bundles appimage publish-windows: if: ${{ (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows')) || (github.event_name == 'push' && (startsWith(github.ref_name, 'v') || startsWith(github.ref_name, 'desktop-v') || startsWith(github.ref_name, 'desktop-windows-v'))) }} permissions: contents: write packages: read runs-on: windows-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }} - name: Normalize release tag shell: bash run: | set -euo pipefail source_tag="${SOURCE_TAG}" if [[ "$source_tag" == desktop-windows-v* ]]; then release_tag="v${source_tag#desktop-windows-v}" elif [[ "$source_tag" == desktop-linux-v* ]]; then release_tag="v${source_tag#desktop-linux-v}" elif [[ "$source_tag" == desktop-macos-v* ]]; then release_tag="v${source_tag#desktop-macos-v}" elif [[ "$source_tag" == desktop-v* ]]; then release_tag="v${source_tag#desktop-v}" else release_tag="$source_tag" fi echo "RELEASE_TAG=$release_tag" >> "$GITHUB_ENV" - name: Setup Node uses: actions/setup-node@v4 with: node-version: "22" cache: "npm" registry-url: "https://npm.pkg.github.com" scope: "@boudra" - name: Install Rust stable uses: dtolnay/rust-toolchain@stable - name: Install JS dependencies run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build web app for Tauri run: npm run build:web --workspace=@getpaseo/app - name: Build managed runtime for Windows run: npm run prepare:managed-runtime --workspace=@getpaseo/desktop - name: Smoke check managed runtime for Windows run: npm run smoke:managed-daemon --workspace=@getpaseo/desktop - name: Set desktop version from tag shell: bash run: | node <<'NODE' const fs = require('node:fs'); const path = require('node:path'); const rawTag = process.env.SOURCE_TAG; if (!rawTag) throw new Error('SOURCE_TAG env var is missing'); const version = rawTag.replace(/^desktop-(windows-|linux-|macos-)?/, '').replace(/^v/, ''); console.log(`Using desktop version ${version} from tag ${rawTag}`); const tauriConfPath = path.join('packages', 'desktop', 'src-tauri', 'tauri.conf.json'); const tauriConfText = fs.readFileSync(tauriConfPath, 'utf8'); const tauriRe = /("version"\s*:\s*")([^"]+)(")/; if (!tauriRe.test(tauriConfText)) { throw new Error(`Failed to find version field in ${tauriConfPath}`); } fs.writeFileSync(tauriConfPath, tauriConfText.replace(tauriRe, `$1${version}$3`)); const cargoTomlPath = path.join('packages', 'desktop', 'src-tauri', 'Cargo.toml'); const cargoLines = fs.readFileSync(cargoTomlPath, 'utf8').split(/\r?\n/); let inPackage = false; let updated = false; const nextLines = cargoLines.map((line) => { if (/^\[package\]\s*$/.test(line)) inPackage = true; else if (inPackage && /^\[/.test(line)) inPackage = false; if (inPackage && /^version\s*=\s*".*"\s*$/.test(line)) { updated = true; return `version = "${version}"`; } return line; }); if (!updated) throw new Error(`Failed to update Cargo package version in ${cargoTomlPath}`); fs.writeFileSync(cargoTomlPath, `${nextLines.join('\n')}\n`); NODE - name: Detect existing GitHub release state env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | set -euo pipefail if release_draft="$(gh release view "$RELEASE_TAG" --repo "${{ github.repository }}" --json isDraft --jq '.isDraft' 2>/dev/null)"; then : else release_draft="false" fi echo "RELEASE_DRAFT=$release_draft" >> "$GITHUB_ENV" - name: Build and publish Windows Tauri release 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/desktop tagName: ${{ env.RELEASE_TAG }} releaseName: Paseo ${{ env.RELEASE_TAG }} releaseBody: See the assets to download and install this version. releaseDraft: ${{ env.RELEASE_DRAFT }} prerelease: false args: --bundles nsis,msi