Coalesce bold runs and close them at block boundaries #253
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: Rust Checks and Release | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| workflow_dispatch: | |
| inputs: | |
| release_mode: | |
| description: 'Manual release mode' | |
| required: true | |
| type: choice | |
| default: 'instant' | |
| options: | |
| - instant | |
| - changelog-pr | |
| bump_type: | |
| description: 'Manual release type' | |
| required: true | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| description: | |
| description: 'Manual release description (optional)' | |
| required: false | |
| type: string | |
| # Concurrency: Only one workflow run per branch at a time | |
| # - For main branch (releases): cancel older runs to prevent blocking newer releases | |
| # - For PR branches: queue runs to avoid cancelling checks on force-pushes | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUSTFLAGS: -Dwarnings | |
| # Support both CARGO_REGISTRY_TOKEN (cargo's native env var) and CARGO_TOKEN (for backwards compatibility) | |
| CARGO_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} | |
| defaults: | |
| run: | |
| working-directory: rust | |
| jobs: | |
| # === DETECT CHANGES - determines which jobs should run === | |
| detect-changes: | |
| name: Rust - Detect Changes | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'workflow_dispatch' | |
| outputs: | |
| rust-code-changed: ${{ steps.changes.outputs.rust-code-changed }} | |
| rust-changed: ${{ steps.changes.outputs.rust-changed }} | |
| rust-scripts-changed: ${{ steps.changes.outputs.rust-scripts-changed }} | |
| rust-workflow-changed: ${{ steps.changes.outputs.rust-workflow-changed }} | |
| any-rust-code-changed: ${{ steps.changes.outputs.any-rust-code-changed }} | |
| any-code-changed: ${{ steps.changes.outputs.any-code-changed }} | |
| docs-changed: ${{ steps.changes.outputs.docs-changed }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect changes | |
| id: changes | |
| working-directory: . | |
| env: | |
| GITHUB_EVENT_NAME: ${{ github.event_name }} | |
| GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: node scripts/detect-code-changes.mjs | |
| # === PUBLISHABLE CHANGES CHECK === | |
| # Warns when PR touches publishable paths without version bump | |
| # This is informational — auto-release on main handles the gap | |
| publishable-check: | |
| name: Rust - Publishable Changes Check | |
| runs-on: ubuntu-latest | |
| needs: [detect-changes] | |
| if: github.event_name == 'pull_request' && needs.detect-changes.outputs.any-rust-code-changed == 'true' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for publishable changes without version bump | |
| working-directory: . | |
| env: | |
| GITHUB_BASE_REF: ${{ github.base_ref }} | |
| run: node scripts/check-publishable-changes.mjs | |
| # Linting and formatting - runs when Rust-relevant files change | |
| lint: | |
| name: Rust - Lint and Format Check | |
| runs-on: ubuntu-latest | |
| needs: [detect-changes] | |
| if: | | |
| always() && !cancelled() && ( | |
| github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch' || | |
| needs.detect-changes.outputs.any-rust-code-changed == 'true' || | |
| needs.detect-changes.outputs.docs-changed == 'true' | |
| ) | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| rust/target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| - name: Run Clippy | |
| run: cargo clippy --all-targets --all-features -- -D warnings | |
| # Test matrix: Rust on multiple OS - only runs when Rust code changes | |
| test: | |
| name: Rust - Test (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| needs: [detect-changes] | |
| if: | | |
| always() && !cancelled() && ( | |
| github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch' || | |
| needs.detect-changes.outputs.any-rust-code-changed == 'true' | |
| ) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| rust/target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Run tests | |
| run: cargo test --all-features --verbose | |
| - name: Run doc tests | |
| run: cargo test --doc --verbose | |
| - name: Run Google Docs public-document live integration tests (issue #90) | |
| working-directory: rust | |
| env: | |
| GDOCS_INTEGRATION: '1' | |
| run: cargo test --test integration gdocs_public_doc::live -- --nocapture | |
| timeout-minutes: 10 | |
| # Build check - only runs when Rust code changes | |
| build: | |
| name: Rust - Build | |
| runs-on: ubuntu-latest | |
| needs: [lint, test] | |
| if: | | |
| !cancelled() && | |
| needs.lint.result == 'success' && | |
| needs.test.result == 'success' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| rust/target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Build release | |
| run: cargo build --release | |
| - name: Check package | |
| run: cargo package --list --allow-dirty | |
| - name: Build Docker image | |
| run: docker build -t web-capture-rust . | |
| # Release - only runs on main after tests pass (for push events) | |
| # Self-healing: detects unreleased commits and auto-bumps patch version | |
| release: | |
| name: Rust - Release | |
| needs: [lint, test, build] | |
| if: | | |
| !cancelled() && | |
| github.ref == 'refs/heads/main' && | |
| github.event_name == 'push' && | |
| needs.build.result == 'success' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| rust/target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Check if release is needed | |
| id: release_check | |
| run: node ../scripts/rust-check-release-needed.mjs | |
| - name: Auto-bump patch version for unreleased changes | |
| if: steps.release_check.outputs.should_release == 'true' && steps.release_check.outputs.needs_auto_bump == 'true' | |
| id: auto_bump | |
| # Uses scripts/safe-git-push.mjs for fetch+rebase+retry so concurrent | |
| # pushes from the JS release workflow don't cause non-fast-forward | |
| # rejections. See docs/case-studies/issue-94. | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| node ../scripts/rust-version-bump.mjs --bump-type patch --description "Auto-release unreleased changes" | |
| git add -A | |
| NEW_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| git commit -m "chore(rust): auto-bump to ${NEW_VERSION} for unreleased changes" | |
| node ../scripts/safe-git-push.mjs --branch main | |
| echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT | |
| echo "Auto-bumped to ${NEW_VERSION}" | |
| - name: Determine release version | |
| id: version | |
| run: | | |
| if [ -n "${{ steps.auto_bump.outputs.version }}" ]; then | |
| echo "version=${{ steps.auto_bump.outputs.version }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "version=${{ steps.release_check.outputs.version }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Build release | |
| if: steps.release_check.outputs.should_release == 'true' | |
| run: cargo build --release | |
| - name: Publish to crates.io | |
| if: steps.release_check.outputs.should_release == 'true' | |
| id: publish | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} | |
| CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} | |
| run: node ../scripts/rust-publish-crate.mjs | |
| - name: Create GitHub Release | |
| if: steps.publish.outputs.published == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: node ../scripts/rust-create-github-release.mjs --release-version "${{ steps.version.outputs.version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}" | |
| # Manual Instant Release | |
| instant-release: | |
| name: Rust - Instant Release | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| rust/target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Version and commit | |
| id: version | |
| run: node ../scripts/rust-version-and-commit.mjs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" | |
| - name: Publish to crates.io | |
| if: steps.version.outputs.version_committed == 'true' | |
| id: publish | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} | |
| CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} | |
| run: node ../scripts/rust-publish-crate.mjs | |
| - name: Create GitHub Release | |
| if: steps.version.outputs.version_committed == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: node ../scripts/rust-create-github-release.mjs --release-version "${{ steps.version.outputs.version }}" --repository "${{ github.repository }}" --description "${{ github.event.inputs.description }}" --commit-sha "${{ github.sha }}" | |
| # Manual Changelog PR - creates a PR with version bump for review before release | |
| changelog-pr: | |
| name: Rust - Create Changelog PR | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'changelog-pr' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| - name: Bump version in Cargo.toml and CHANGELOG.md | |
| working-directory: . | |
| run: | | |
| cd rust | |
| node ../scripts/rust-version-bump.mjs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" | |
| - name: Create Pull Request | |
| uses: peter-evans/create-pull-request@v8 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| commit-message: 'chore(rust): bump version (${{ github.event.inputs.bump_type }})' | |
| branch: changelog-rust-manual-release-${{ github.run_id }} | |
| delete-branch: true | |
| title: 'chore(rust): manual ${{ github.event.inputs.bump_type }} release' | |
| body: | | |
| ## Manual Release Request (Rust) | |
| This PR was created by a manual workflow trigger to prepare a **${{ github.event.inputs.bump_type }}** release. | |
| ### Release Details | |
| - **Type:** ${{ github.event.inputs.bump_type }} | |
| - **Description:** ${{ github.event.inputs.description || 'Manual release' }} | |
| - **Triggered by:** @${{ github.actor }} | |
| ### Next Steps | |
| 1. Review the version bump and changelog in this PR | |
| 2. Merge this PR to main | |
| 3. The automated release workflow will publish to crates.io and create a GitHub release |