name: release-please # Conventional-commit-driven versioning. On every push to main, release-please # maintains a "release vX.Y.Z" PR (bumping versions + CHANGELOG from the commit # history). Merging that PR tags `silo-vX.Y.Z` and creates the GitHub release. # # release-please pushes that tag with the default GITHUB_TOKEN, and tag pushes # made by GITHUB_TOKEN do NOT trigger other workflows — so release.yml's # `on: push: tags` would never fire on its own. We bridge it explicitly below # with a workflow_dispatch, which IS allowed to run from GITHUB_TOKEN. # # The same GITHUB_TOKEN rule blocks the release PR itself: a PR opened with the # default token does NOT fire `pull_request`, so ci.yml never runs on it and its # required checks (frontend/rust/pr-title) stay absent — leaving the PR forever # BLOCKED by branch protection. We mint a GitHub App installation token and hand # it to release-please so the PR is authored by a non-GITHUB_TOKEN identity (the # app's bot user), which lets `pull_request` fire and CI run on it normally. # # Setup (one-time): a GitHub App installed on this repo with Contents:write and # Pull requests:write. Its App ID is stored as the `RELEASE_PLEASE_APP_ID` repo # variable and its private key as the `RELEASE_PLEASE_APP_PRIVATE_KEY` secret. on: push: branches: [main] permissions: contents: write pull-requests: write actions: write # allows dispatching release.yml below jobs: release-please: runs-on: ubuntu-latest steps: - uses: actions/create-github-app-token@v2 id: app-token with: app-id: ${{ vars.RELEASE_PLEASE_APP_ID }} private-key: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }} - uses: googleapis/release-please-action@v5 id: release with: token: ${{ steps.app-token.outputs.token }} config-file: release-please-config.json manifest-file: .release-please-manifest.json # release-please bumps the version in Cargo.toml (the `generic` extra-file # updater) but never regenerates Cargo.lock, so the lock's own `silo` entry # drifts a version behind on every release. Nothing builds with --locked, so # this doesn't break CI — but it leaves a dirty Cargo.lock in the tree the # moment anyone builds locally, which then rides into unrelated PRs. # # When a release PR is open, sync the lock on its branch and push the fix # back onto the PR so the merged release commit is self-consistent. This # runs in the same job right after release-please, so even though # release-please force-pushes (and re-bumps) that branch on every reconcile, # the branch always ends each run with the lock in sync. Pushing to the # release branch (not main) re-runs the PR's CI but does NOT re-trigger this # workflow, so there's no loop. `cargo update -p silo` touches only the silo # lock entry — no dependency churn. - uses: actions/checkout@v6 if: ${{ steps.release.outputs.pr }} with: token: ${{ steps.app-token.outputs.token }} - uses: dtolnay/rust-toolchain@stable if: ${{ steps.release.outputs.pr }} - name: sync Cargo.lock on the release PR if: ${{ steps.release.outputs.pr }} env: # Short-circuit: GitHub evaluates this `env` expression eagerly (even # though the step's `if` is false when there's no open release PR), and # `fromJSON("")` hard-errors ("Error reading JToken ... Path ''"). The # `&&` skips fromJSON entirely when `pr` is empty. BRANCH: ${{ steps.release.outputs.pr && fromJSON(steps.release.outputs.pr).headBranchName }} run: | set -euo pipefail git fetch origin "$BRANCH" git checkout "$BRANCH" cargo update --manifest-path apps/desktop/src-tauri/Cargo.toml -p silo if git diff --quiet -- apps/desktop/src-tauri/Cargo.lock; then echo "Cargo.lock already in sync" exit 0 fi git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add apps/desktop/src-tauri/Cargo.lock git commit -m "chore: sync Cargo.lock to released version" git push origin "$BRANCH" # When a release was just created, kick off the installer build. main HEAD # is the release commit (version already bumped), so release.yml reads the # correct version from tauri.conf.json. # # NOTE: this is a manifest config (config-file + manifest-file), so the # action exposes the aggregate `releases_created` (plural) — NOT the bare # `release_created`, which is only set in legacy single-package mode and is # always empty here (the per-path output is `apps/desktop--release_created`). - name: trigger installer build if: ${{ steps.release.outputs['.--release_created'] }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh workflow run release.yml --ref main -R ${{ github.repository }} # SDK releases must not steal the "Latest" slot from Silo app releases # (the auto-updater endpoint uses /releases/latest/download/latest.json). # Mark them as pre-releases immediately so GitHub never promotes them. - name: mark SDK release as pre-release if: ${{ steps.release.outputs['packages/sdk--release_created'] }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh release edit "${{ steps.release.outputs['packages/sdk--tag_name'] }}" --prerelease -R ${{ github.repository }} - name: publish SDK to npm if: ${{ steps.release.outputs['packages/sdk--release_created'] }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh workflow run release-sdk.yml --ref main -R ${{ github.repository }} -f publish=true