release: prepare Dairo CLI v0.0.8 #16
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 CLI | |
| # Release tags are always v-prefixed (vX.Y.Z). The tag version must match | |
| # Cargo.toml or the release job fails. npm publishing is launch-gated by the | |
| # NPM_PUBLISH repo variable (plus the NPM_TOKEN secret); everything else is | |
| # mandatory — a missing secret fails the release instead of silently skipping. | |
| on: | |
| push: | |
| tags: | |
| - 'v*.*.*' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to package, e.g. 0.1.0. Defaults to Cargo.toml version.' | |
| required: false | |
| type: string | |
| publish_npm: | |
| description: 'Publish npm packages (requires NPM_TOKEN).' | |
| required: false | |
| default: false | |
| type: boolean | |
| update_homebrew: | |
| description: 'Update dairo-app/homebrew-tap (requires HOMEBREW_TAP_TOKEN).' | |
| required: false | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: read | |
| jobs: | |
| build: | |
| name: Build ${{ matrix.target }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: macos-14 | |
| target: aarch64-apple-darwin | |
| exe: dairo | |
| - os: macos-15-intel | |
| target: x86_64-apple-darwin | |
| exe: dairo | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| exe: dairo | |
| - os: ubuntu-24.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| exe: dairo | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| exe: dairo.exe | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: release-${{ matrix.target }} | |
| - run: cargo build --release --locked --target ${{ matrix.target }} | |
| - name: Stage binary | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist/binaries/${{ matrix.target }} | |
| cp target/${{ matrix.target }}/release/${{ matrix.exe }} dist/binaries/${{ matrix.target }}/${{ matrix.exe }} | |
| if [ "${{ runner.os }}" != "Windows" ]; then chmod +x dist/binaries/${{ matrix.target }}/${{ matrix.exe }}; fi | |
| # Every runner in the matrix is native for its target, so the staged | |
| # binary must run and report the Cargo.toml version before we ship it. | |
| - name: Smoke test binary | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cargo_version="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)" | |
| output="$(dist/binaries/${{ matrix.target }}/${{ matrix.exe }} --version)" | |
| echo "$output" | |
| case "$output" in | |
| *"$cargo_version"*) ;; | |
| *) | |
| echo "::error::--version reported '$output', expected Cargo.toml version $cargo_version" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: binary-${{ matrix.target }} | |
| path: dist/binaries/${{ matrix.target }} | |
| if-no-files-found: error | |
| release: | |
| name: Create GitHub release artifacts | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| outputs: | |
| version: ${{ steps.meta.outputs.version }} | |
| tag: ${{ steps.meta.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: binary-* | |
| path: dist/downloaded | |
| - name: Normalize downloaded binaries | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist/binaries | |
| for dir in dist/downloaded/binary-*; do | |
| target="${dir##*/binary-}" | |
| mkdir -p "dist/binaries/$target" | |
| cp -R "$dir"/* "dist/binaries/$target/" | |
| done | |
| # actions/download-artifact strips file modes; restore the execute | |
| # bit so the published tarballs contain runnable binaries. | |
| find dist/binaries -type f -name dairo -exec chmod 0755 {} + | |
| - id: meta | |
| env: | |
| INPUT_VERSION: ${{ inputs.version }} | |
| run: | | |
| set -euo pipefail | |
| if [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then | |
| TAG="${GITHUB_REF_NAME}" | |
| VERSION="${TAG#v}" | |
| elif [ -n "${INPUT_VERSION:-}" ]; then | |
| VERSION="${INPUT_VERSION#v}" | |
| TAG="v$VERSION" | |
| else | |
| VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)" | |
| TAG="v$VERSION" | |
| fi | |
| if ! printf '%s\n' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$'; then | |
| echo "Invalid release version: $VERSION" >&2 | |
| exit 1 | |
| fi | |
| CARGO_VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)" | |
| if [ "$CARGO_VERSION" != "$VERSION" ]; then | |
| echo "Cargo.toml version ($CARGO_VERSION) must match release tag version ($VERSION)." >&2 | |
| exit 1 | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| - name: Create archives and checksums | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist/release | |
| for target_dir in dist/binaries/*; do | |
| target="${target_dir##*/}" | |
| if [ -f "$target_dir/dairo.exe" ]; then | |
| archive="dairo-${target}.zip" | |
| (cd "$target_dir" && zip -q "../../release/$archive" dairo.exe) | |
| else | |
| archive="dairo-${target}.tar.gz" | |
| tar -C "$target_dir" -czf "dist/release/$archive" dairo | |
| if ! tar -tvzf "dist/release/$archive" | grep -q '^-rwx'; then | |
| echo "::error::$archive contains a non-executable dairo binary" >&2 | |
| exit 1 | |
| fi | |
| fi | |
| done | |
| cp install/install.sh dist/release/dairo-install.sh | |
| cp install/install.ps1 dist/release/dairo-install.ps1 | |
| (cd dist/release && shasum -a 256 dairo-* > checksums.txt) | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-assets | |
| path: dist/release/* | |
| - id: gcp-auth | |
| name: Authenticate to Google Cloud | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} | |
| service_account: ${{ vars.GCP_RELEASE_NOTES_SERVICE_ACCOUNT }} | |
| token_format: access_token | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| - name: Generate release notes | |
| env: | |
| RELEASE_TAG: ${{ steps.meta.outputs.tag }} | |
| RELEASE_VERSION: ${{ steps.meta.outputs.version }} | |
| RELEASE_NOTES_FILE: release-notes.md | |
| GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID }} | |
| GCP_LOCATION: ${{ vars.GCP_LOCATION || 'eu' }} | |
| VERTEX_MODEL: ${{ vars.VERTEX_MODEL || 'gemini-3.5-flash' }} | |
| VERTEX_ACCESS_TOKEN: ${{ steps.gcp-auth.outputs.access_token }} | |
| run: node scripts/generate-release-notes.mjs | |
| - id: release-app-token | |
| name: Create dairo-bot release token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| client-id: ${{ vars.RELEASE_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} | |
| owner: dairo-app | |
| repositories: dairo-cli | |
| permission-contents: write | |
| - name: Publish GitHub release | |
| env: | |
| GH_TOKEN: ${{ steps.release-app-token.outputs.token }} | |
| run: | | |
| set -euo pipefail | |
| TAG='${{ steps.meta.outputs.tag }}' | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| gh release upload "$TAG" dist/release/* --clobber | |
| gh release edit "$TAG" --title "Dairo CLI $TAG" --notes-file release-notes.md | |
| else | |
| gh release create "$TAG" dist/release/* \ | |
| --title "Dairo CLI $TAG" \ | |
| --notes-file release-notes.md | |
| fi | |
| npm: | |
| name: Build npm packages | |
| needs: [build, release] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| # Trusted publishing (OIDC) needs npm >= 11.5.1; node 24 ships it. | |
| node-version: 24 | |
| registry-url: https://registry.npmjs.org | |
| scope: '@dairo-app' | |
| - name: Require an OIDC-capable npm | |
| run: | | |
| set -euo pipefail | |
| npm_version="$(npm --version)" | |
| if [ "$(printf '%s\n11.5.1\n' "$npm_version" | sort -V | head -1)" != "11.5.1" ]; then | |
| echo "::error::npm $npm_version is too old for trusted publishing (need >= 11.5.1)." >&2 | |
| exit 1 | |
| fi | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: binary-* | |
| path: dist/downloaded | |
| - name: Normalize downloaded binaries | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist/binaries | |
| for dir in dist/downloaded/binary-*; do | |
| target="${dir##*/binary-}" | |
| mkdir -p "dist/binaries/$target" | |
| cp -R "$dir"/* "dist/binaries/$target/" | |
| done | |
| - name: Prepare npm packages | |
| env: | |
| DAIRO_CLI_VERSION: ${{ needs.release.outputs.version }} | |
| run: node scripts/prepare-npm-packages.mjs | |
| - name: Pack npm packages | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist/npm-tarballs | |
| for dir in dist/npm/*; do | |
| (cd "$dir" && npm pack --pack-destination ../../npm-tarballs) | |
| done | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: npm-packages | |
| path: dist/npm-tarballs/*.tgz | |
| # Launch gate: set the NPM_PUBLISH repo variable to 'true' to publish on | |
| # tag pushes. Auth is trusted publishing (OIDC via id-token: write) — no | |
| # npm token exists; every package must have this repo + workflow | |
| # registered as a trusted publisher on npmjs.com, and provenance is | |
| # attested automatically. | |
| - name: Publish npm packages | |
| if: ${{ (github.event_name == 'push' && vars.NPM_PUBLISH == 'true') || (github.event_name == 'workflow_dispatch' && inputs.publish_npm) }} | |
| env: | |
| VERSION: ${{ needs.release.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| for pkg in cli-darwin-arm64 cli-darwin-x64 cli-linux-x64 cli-linux-arm64 cli-win32-x64 cli dairo-cli; do | |
| if [ "$pkg" = "dairo-cli" ]; then | |
| name="dairo-cli" | |
| else | |
| name="@dairo-app/$pkg" | |
| fi | |
| if [ "$(npm view "$name@$VERSION" version 2>/dev/null || true)" = "$VERSION" ]; then | |
| echo "$name@$VERSION is already published; leaving it untouched." | |
| continue | |
| fi | |
| npm publish "dist/npm/$pkg" --access public | |
| done | |
| homebrew: | |
| name: Update Homebrew tap | |
| needs: release | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| if: ${{ github.event_name == 'push' || inputs.update_homebrew }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: release-assets | |
| path: dist/release | |
| - name: Update formula in dairo-app/homebrew-tap | |
| env: | |
| GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| VERSION: ${{ needs.release.outputs.version }} | |
| TAG: ${{ needs.release.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${GH_TOKEN:-}" ]; then | |
| echo "::error::HOMEBREW_TAP_TOKEN is not configured; the Homebrew tap cannot be updated." >&2 | |
| exit 1 | |
| fi | |
| gh repo clone dairo-app/homebrew-tap tap | |
| cd tap | |
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/dairo-app/homebrew-tap.git" | |
| mkdir -p Formula | |
| python3 ../scripts/render-homebrew-formula.py "$VERSION" "$TAG" ../dist/release/checksums.txt > Formula/dairo.rb | |
| git config user.name "dairo-release-bot" | |
| git config user.email "release-bot@dairo.app" | |
| git add Formula/dairo.rb | |
| if git diff --cached --quiet; then | |
| echo "Formula already up to date for $TAG." | |
| else | |
| git commit -m "Update Dairo CLI to $TAG" | |
| git push | |
| fi |